aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.build.yml39
-rw-r--r--.gitignore2
-rw-r--r--advtrains/atc.lua8
-rw-r--r--advtrains/copytool.lua18
-rw-r--r--advtrains/couple.lua41
-rw-r--r--advtrains/craft_items.lua2
-rw-r--r--advtrains/crafting.lua17
-rw-r--r--advtrains/init.lua37
l---------advtrains/locale/README.md1
-rw-r--r--advtrains/locale/advtrains.de.tr77
-rw-r--r--advtrains/locale/advtrains.zh_CN.tr107
-rw-r--r--advtrains/occupation.lua126
-rw-r--r--advtrains/path.lua11
-rw-r--r--advtrains/po/README.md70
-rw-r--r--advtrains/po/advtrains.pot632
-rw-r--r--advtrains/po/de.po724
-rw-r--r--advtrains/po/fr.po728
-rwxr-xr-xadvtrains/po/update-translations.sh28
-rw-r--r--advtrains/po/zh_CN.po696
-rw-r--r--advtrains/po/zh_TW.po696
-rw-r--r--advtrains/poconvert.lua185
-rw-r--r--advtrains/protection.lua14
-rw-r--r--advtrains/settingtypes.txt3
-rw-r--r--advtrains/signals.lua13
-rw-r--r--advtrains/sounds/advtrains_crossing_bell.oggbin47722 -> 24656 bytes
-rw-r--r--advtrains/spec/poconvert_spec.lua70
-rw-r--r--advtrains/spec/texture_spec.lua19
-rw-r--r--advtrains/spec/wagons_spec.lua40
-rw-r--r--advtrains/texture.lua228
-rw-r--r--advtrains/textures/advtrains_wagon_prop_tool.pngbin0 -> 779 bytes
-rw-r--r--advtrains/trackplacer.lua4
-rw-r--r--advtrains/trainhud.lua157
-rw-r--r--advtrains/trainlogic.lua56
-rw-r--r--advtrains/wagonprop_tool.lua43
-rw-r--r--advtrains/wagons.lua203
-rw-r--r--advtrains_interlocking/route_prog.lua27
-rw-r--r--advtrains_interlocking/route_ui.lua14
-rw-r--r--advtrains_interlocking/routesetting.lua8
-rwxr-xr-xadvtrains_interlocking/tcb_ts_ui.lua35
-rw-r--r--advtrains_interlocking/tsr_rail.lua12
-rw-r--r--advtrains_line_automation/stoprail.lua10
-rw-r--r--advtrains_luaautomation/README.md24
-rw-r--r--advtrains_luaautomation/active_common.lua24
-rw-r--r--[-rwxr-xr-x]advtrains_luaautomation/atc_rail.lua25
-rw-r--r--advtrains_luaautomation/environment.lua18
-rw-r--r--advtrains_luaautomation/init.lua6
-rw-r--r--advtrains_luaautomation/mesecon_controller.lua3
-rw-r--r--[-rwxr-xr-x]advtrains_luaautomation/operation_panel.lua3
-rw-r--r--advtrains_luaautomation/pcnaming.lua21
-rwxr-xr-xadvtrains_signals_ks/init.lua8
-rw-r--r--advtrains_train_track/init.lua133
-rw-r--r--advtrains_train_track/settingtypes.txt4
52 files changed, 4917 insertions, 553 deletions
diff --git a/.build.yml b/.build.yml
index 54768a9..0208e50 100644
--- a/.build.yml
+++ b/.build.yml
@@ -3,6 +3,12 @@ packages:
- git
- lua5.1
- luarocks
+- curl
+- minetest-server
+- unzip
+- wget
+- lua-busted
+- luajit
sources :
- https://git.sr.ht/~gpcf/advtrains
@@ -26,4 +32,37 @@ tasks:
~/.luarocks/bin/mineunit -r
sed -n '/^File/,$p' luacov.report.out
mv luacov.report.out ~/$i.luacov.report.out
+- install_mt_game : |
+ curl -L https://github.com/minetest/minetest_game/archive/master.zip -o master.zip
+ mkdir -p .minetest/games/
+ cd .minetest/games
+ unzip ../../master.zip
+ mv minetest_game-master minetest_game
+- install_test_world: |
+ mkdir -p .minetest/worlds/
+ curl https://lifomaps.de/advtrains-test/testworld.tar.gz -o ~/testworld.tar.gz
+ cd .minetest/worlds/
+ tar xf ../../testworld.tar.gz
+- run_unit_tests : |
+ cd advtrains/advtrains
+ busted
+ cd ../advtrains_interlocking
+ busted
+ cd ../serialize_lib
+ busted
+- activate_test_env: |
+ cd advtrains
+ git merge --no-commit origin/luaatcdebug
+- install_advtrains : |
+ mkdir .minetest/mods
+ cp -r advtrains .minetest/mods
+ cd .minetest/mods
+ git clone https://git.bananach.space/basic_trains.git/
+- run_test_world: |
+ echo "bind_address = 127.0.0.1" > minetest.conf
+ minetestserver --port 31111 --gameid minetest_game --config ~/minetest.conf --world ~/.minetest/worlds/advtrains_testworld --logfile ~/minetest.log
+- test_po_files : |
+ cd advtrains/advtrains
+ for f in po/*.po; do
+ luajit -e 'require("poconvert").from_string("advtrains", io.input():read("*a"))' < $f
done
diff --git a/.gitignore b/.gitignore
index bef77f1..42eaf06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
.project
.settings
luacov.*
+advtrains/locale/*.tr
+advtrains/po/*~
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index c1ff218..b572cdc 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -106,7 +106,7 @@ local apn_func=function(pos)
-- FIX for long-persisting ndb bug: there's no node in parameter 2 of this function!
local meta=minetest.get_meta(pos)
if meta then
- meta:set_string("infotext", attrans("ATC controller, unconfigured."))
+ meta:set_string("infotext", attrans("Unconfigured ATC controller"))
meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
end
end
@@ -233,7 +233,7 @@ local matchptn={
advtrains.train_ensure_init(id, train)
-- no one minds if this failed... this shouldn't even be called without train being initialized...
else
- atwarn(sid(id), attrans("ATC Reverse command warning: didn't reverse train, train moving!"))
+ atwarn(sid(id), attrans("ATC Reverse command warning: didn't reverse train, train moving."))
end
return 1
end,
@@ -245,11 +245,11 @@ local matchptn={
end,
["K"] = function(id, train)
if train.door_open == 0 then
- atwarn(sid(id), attrans("ATC Kick command warning: Doors closed"))
+ atwarn(sid(id), attrans("ATC Kick command warning: doors are closed."))
return 1
end
if train.velocity > 0 then
- atwarn(sid(id), attrans("ATC Kick command warning: Train moving"))
+ atwarn(sid(id), attrans("ATC Kick command warning: train moving."))
return 1
end
local tp = train.trainparts
diff --git a/advtrains/copytool.lua b/advtrains/copytool.lua
index 8a6d2f7..c63551e 100644
--- a/advtrains/copytool.lua
+++ b/advtrains/copytool.lua
@@ -26,7 +26,7 @@ minetest.register_tool("advtrains:copytool", {
return itemstack
end
if not minetest.check_player_privs(placer, {train_operator = true }) then
- minetest.chat_send_player(pname, "You don't have the train_operator privilege.")
+ minetest.chat_send_player(pname, S("You do not have the @1 privilege.", "train_operator"))
return itemstack
end
if not minetest.check_player_privs(placer, {train_admin = true }) and minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
@@ -38,7 +38,7 @@ minetest.register_tool("advtrains:copytool", {
local prevpos = advtrains.get_adjacent_rail(pointed_thing.under, tconns, plconnid, {default=true})
if not prevpos then
- minetest.chat_send_player(pname, "The track you are trying to place the wagon on is not long enough!")
+ minetest.chat_send_player(pname, attrans("The track you are trying to place the wagon on is not long enough."))
return
end
@@ -49,12 +49,12 @@ minetest.register_tool("advtrains:copytool", {
end
local clipboard = meta:get_string("clipboard")
if (clipboard == "") then
- minetest.chat_send_player(pname, "The clipboard is empty.");
+ minetest.chat_send_player(pname, attrans("The clipboard is empty."));
return
end
clipboard = minetest.deserialize(clipboard)
if (clipboard.wagons == nil) then
- minetest.chat_send_player(pname, "The clipboard is empty.");
+ minetest.chat_send_player(pname, attrans("The clipboard is empty."));
return
end
@@ -71,7 +71,7 @@ minetest.register_tool("advtrains:copytool", {
local train = advtrains.trains[id]
train.off_track = train.end_index<train.path_trk_b
if (train.off_track) then
- minetest.chat_send_player(pname, "Back of train would end up off track, cancelling.")
+ minetest.chat_send_player(pname, attrans("Back of train would end up off track, cancelling."))
advtrains.remove_train(id)
return
end
@@ -89,19 +89,19 @@ minetest.register_tool("advtrains:copytool", {
local le = pointed_thing.ref:get_luaentity()
if (le == nil) then
- minetest.chat_send_player(user:get_player_name(), "No such lua entity!")
+ minetest.chat_send_player(user:get_player_name(), attrans("No such lua entity."))
return
end
local wagon = advtrains.wagons[le.id]
if (not (le.id and advtrains.wagons[le.id])) then
- minetest.chat_send_player(user:get_player_name(), string.format("No such wagon: %s", le.id))
+ minetest.chat_send_player(user:get_player_name(), attrans("No such wagon: @1.", le.id))
return
end
local train = advtrains.trains[wagon.train_id]
if (not train) then
- minetest.chat_send_player(user:get_player_name(), string.format("No such train: %s", wagon.train_id))
+ minetest.chat_send_player(user:get_player_name(), attrans("No such train: @1.", wagon.train_id))
return
end
@@ -177,7 +177,7 @@ minetest.register_tool("advtrains:copytool", {
return
end
meta:set_string("clipboard", minetest.serialize(clipboard))
- minetest.chat_send_player(user:get_player_name(), attrans("Train copied!"))
+ minetest.chat_send_player(user:get_player_name(), attrans("Train copied."))
return itemstack
end
})
diff --git a/advtrains/couple.lua b/advtrains/couple.lua
index 49c8a5d..4933dd8 100644
--- a/advtrains/couple.lua
+++ b/advtrains/couple.lua
@@ -28,6 +28,20 @@ end
advtrains.register_coupler_type("chain", attrans("Buffer and Chain Coupler"))
advtrains.register_coupler_type("scharfenberg", attrans("Scharfenberg Coupler"))
+for _, name in pairs {"couple", "decouple"} do
+ local t = {}
+ local function reg(f)
+ table.insert(t, f)
+ end
+ local function cb(...)
+ for _, f in ipairs(t) do
+ f(...)
+ end
+ end
+ advtrains["te_registered_on_" .. name] = t
+ advtrains["te_register_on_" .. name] = reg
+ advtrains["te_run_callbacks_on_" .. name] = cb
+end
local function create_couple_entity(pos, train1, t1_is_front, train2, t2_is_front)
local id1 = train1.id
@@ -79,8 +93,9 @@ function advtrains.train_check_couples(train)
end
if not train.cpl_front then
-- recheck front couple
- local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST)
+ local pos = advtrains.path_get(train, atround(train.index) + CPL_CHK_DST)
if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ local front_trains = advtrains.occ.reverse_lookup_sel(pos, "in_train")
for tid, idx in pairs(front_trains) do
local other_train = advtrains.trains[tid]
if not advtrains.train_ensure_init(tid, other_train) then
@@ -109,8 +124,9 @@ function advtrains.train_check_couples(train)
end
if not train.cpl_back then
-- recheck back couple
- local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST)
+ local pos = advtrains.path_get(train, atround(train.end_index) - CPL_CHK_DST)
if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ local back_trains = advtrains.occ.reverse_lookup_sel(pos, "in_train")
for tid, idx in pairs(back_trains) do
local other_train = advtrains.trains[tid]
if not advtrains.train_ensure_init(tid, other_train) then
@@ -182,8 +198,8 @@ end
function advtrains.safe_couple_trains(train1, t1_is_front, train2, t2_is_front, pname)
if pname and not minetest.check_player_privs(pname, "train_operator") then
- minetest.chat_send_player(pname, "Missing train_operator privilege")
- return false
+ minetest.chat_send_player(pname, S("You are not allowed to couple trains without the train_operator privilege."))
+ return false
end
local wck_t1, wck_t2
@@ -225,6 +241,15 @@ function advtrains.couple_trains(init_train, invert_init_train, stat_train, stat
local stp = stat_train.trainparts
local stat_wagoncnt = #stp
local stat_trainlen = stat_train.trainlen -- save the train length of stat train, to be added to index
+
+ -- sanity check, prevent coupling if train would be longer than 20 after coupling
+ local tot_len = init_wagoncnt + stat_wagoncnt
+ if tot_len > advtrains.TRAIN_MAX_WAGONS then
+ atwarn("Cannot couple",stat_train.id,"and",init_train.id,"- train would have length",tot_len,"which is above the limit of",advtrains.TRAIN_MAX_WAGONS)
+ return
+ end
+
+ advtrains.te_run_callbacks_on_couple(init_train, stat_train)
if stat_train_opposite then
-- insert wagons in inverse order and set their wagon_flipped state
@@ -310,7 +335,7 @@ function advtrains.check_matching_coupler_types(t1, t1_front, t2, t2_front)
--atdebug("CMCT: t1",t1_cplt,"t2",t2_cplt,"")
-- if at least one of the trains has no couplers table, it always couples (fallback behavior and mode for universal shunters)
- if not t1_cplt or not t2_cplt then
+ if minetest.settings:get_bool("advtrains_universal_couplers", false) or not t1_cplt or not t2_cplt then
return true
end
@@ -326,11 +351,11 @@ function advtrains.check_matching_coupler_types(t1, t1_front, t2, t2_front)
for typ,_ in pairs(t1_cplt) do
table.insert(t1_cplhr, advtrains.coupler_types[typ] or typ)
end
- if #t1_cplhr==0 then t1_cplhr[1]=attrans("<none>") end
+ if #t1_cplhr==0 then t1_cplhr[1]=attrans("<No coupler>") end
for typ,_ in pairs(t2_cplt) do
table.insert(t2_cplhr, advtrains.coupler_types[typ] or typ)
end
- if #t2_cplhr==0 then t2_cplhr[1]=attrans("<none>") end
+ if #t2_cplhr==0 then t2_cplhr[1]=attrans("<No coupler>") end
return false, attrans("Can not couple: The couplers of the trains do not match (@1 and @2).", table.concat(t1_cplhr, ","), table.concat(t2_cplhr, ","))
end
@@ -428,7 +453,7 @@ minetest.register_entity("advtrains:discouple", {
self.object:remove()
return
end
- --getyaw seems to be a reliable method to check if an object is loaded...if it returns nil, it is not.
+ --get_yaw seems to be a reliable method to check if an object is loaded...if it returns nil, it is not.
if not self.wagon.object:get_yaw() then
self.object:remove()
return
diff --git a/advtrains/craft_items.lua b/advtrains/craft_items.lua
index 0e693eb..1188b64 100644
--- a/advtrains/craft_items.lua
+++ b/advtrains/craft_items.lua
@@ -6,7 +6,7 @@ core.register_craftitem("advtrains:boiler", {
core.register_craftitem("advtrains:driver_cab", {
- description = attrans("driver's cab"),
+ description = attrans("Driver's cab"),
inventory_image = "advtrains_driver_cab.png",
})
diff --git a/advtrains/crafting.lua b/advtrains/crafting.lua
index 7626d55..9f80456 100644
--- a/advtrains/crafting.lua
+++ b/advtrains/crafting.lua
@@ -22,6 +22,14 @@ minetest.register_craft({
})
--Wallmounted Signal
minetest.register_craft({
+ output = 'advtrains:signal_wall_l_off 2',
+ recipe = {
+ {'default:steel_ingot', 'default:steel_ingot', 'dye:red'},
+ {'', 'default:steel_ingot', ''},
+ {'default:steel_ingot', 'default:steel_ingot', 'dye:dark_green'},
+ },
+})
+minetest.register_craft({
output = 'advtrains:signal_wall_r_off 2',
recipe = {
{'dye:red', 'default:steel_ingot', 'default:steel_ingot'},
@@ -29,6 +37,15 @@ minetest.register_craft({
{'dye:dark_green', 'default:steel_ingot', 'default:steel_ingot'},
},
})
+minetest.register_craft({
+ output = 'advtrains:signal_wall_t_off 2',
+ recipe = {
+ {'default:steel_ingot', '', 'default:steel_ingot'},
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ {'dye:dark_green', '', 'dye:red'},
+ },
+})
+
--Wallmounted Signals can be converted into every orientation by shapeless crafting
minetest.register_craft({
diff --git a/advtrains/init.lua b/advtrains/init.lua
index bca39df..3918ab3 100644
--- a/advtrains/init.lua
+++ b/advtrains/init.lua
@@ -51,6 +51,9 @@ advtrains.IGNORE_WORLD = false
local NO_SAVE = false
-- Do not save any data to advtrains save files
+advtrains.TRAIN_MAX_WAGONS = 20
+-- Limit on the maximum number of wagons that may be in a train
+
-- ==========================================================================
-- Use a global slowdown factor to slow down train movements. Now a setting
@@ -181,7 +184,7 @@ function assertt(var, typ)
end
end
-dofile(advtrains.modpath.."/helpers.lua");
+dofile(advtrains.modpath.."/helpers.lua")
--dofile(advtrains.modpath.."/debugitems.lua");
advtrains.meseconrules =
@@ -201,14 +204,20 @@ advtrains.meseconrules =
advtrains.fpath=minetest.get_worldpath().."/advtrains"
+advtrains.poconvert = dofile(advtrains.modpath.."/poconvert.lua")
+advtrains.poconvert.from_flat("advtrains")
+attrans = minetest.get_translator("advtrains")
+
advtrains.speed = dofile(advtrains.modpath.."/speed.lua")
advtrains.formspec = dofile(advtrains.modpath.."/formspec.lua")
+advtrains.texture = dofile(advtrains.modpath.."/texture.lua")
dofile(advtrains.modpath.."/path.lua")
dofile(advtrains.modpath.."/trainlogic.lua")
dofile(advtrains.modpath.."/trainhud.lua")
dofile(advtrains.modpath.."/trackplacer.lua")
dofile(advtrains.modpath.."/copytool.lua")
+dofile(advtrains.modpath.."/wagonprop_tool.lua")
dofile(advtrains.modpath.."/tracks.lua")
dofile(advtrains.modpath.."/track_reg_helper.lua")
dofile(advtrains.modpath.."/occupation.lua")
@@ -479,6 +488,7 @@ advtrains.avt_save = function(remove_players_from_wagons)
"text_outside", "text_inside", "line", "routingcode",
"il_sections", "speed_restriction", "speed_restrictions_t", "is_shunt",
"path_ori_cp", "autocouple", "atc_wait_autocouple", "ars_disable",
+ "staticdata",
})
--then save it
tmp_trains[id]=v
@@ -742,6 +752,21 @@ minetest.register_chatcommand("at_whereis",
end
end,
})
+minetest.register_chatcommand("at_tp",
+ {
+ params = "<train id>",
+ description = "Teleports you to the position of the train with the given id",
+ privs = {train_operator = true, teleport = true},
+ func = function(name,param)
+ local train = advtrains.trains[param]
+ if not train or not train.last_pos then
+ return false, "Train "..param.." does not exist or is invalid"
+ else
+ minetest.get_player_by_name(name):set_pos(train.last_pos)
+ return true, "Teleporting to train "..param
+ end
+ end,
+})
minetest.register_chatcommand("at_disable_step",
{
params = "<yes/no>",
@@ -763,6 +788,16 @@ minetest.register_chatcommand("at_disable_step",
end,
})
+minetest.register_chatcommand("at_status",
+ {
+ params = "",
+ description = "Print advtrains status info",
+ privs = {train_operator = true},
+ func = function(name, param)
+ return true, advtrains.print_concat_table({"Advtrains Status: no_action",no_action,"slowdown",advtrains.global_slowdown,"(log",math.log(advtrains.global_slowdown),")"})
+ end,
+})
+
advtrains.is_no_action = function()
return no_action
end
diff --git a/advtrains/locale/README.md b/advtrains/locale/README.md
new file mode 120000
index 0000000..61e473c
--- /dev/null
+++ b/advtrains/locale/README.md
@@ -0,0 +1 @@
+../po/README.md \ No newline at end of file
diff --git a/advtrains/locale/advtrains.de.tr b/advtrains/locale/advtrains.de.tr
deleted file mode 100644
index 6abbc12..0000000
--- a/advtrains/locale/advtrains.de.tr
+++ /dev/null
@@ -1,77 +0,0 @@
-# textdomain: advtrains
-This wagon is owned by @1, you can't destroy it.=Dieser Waggon gehört @1, du kannst ihn nicht abbauen.
-Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon.=Warnung: Du erhältst nur etwas Stahl zurück. Wenn du sicher bist, dass du den Waggon zerstören willst, halte 'Schleichen' und klicke links.
-Show Inventory=Zeige Inventar
-Select seat:=Wähle einen Sitzplatz aus:
-ATC controller, unconfigured.=Zugbeeinflussungsschiene, nicht konfiguiert.
-ATC controller=Zugbeeinflussungsschiene
-ATC controller, mode @1@nChannel: @2=Zugbeeinflussungsschiene in Betriebsart "@1"@nKanal: @2
-ATC controller, mode @1@nCommand: @2=Zugbeeinflussungsschiene in Betriebsart "@1"@nBefehl: @2
-Command=Befehl
-Command (on)=Befehl (wenn ein)
-Digiline channel=Digiline-Kanal
-Save=Speichern
-ATC Reverse command warning: didn't reverse train, train moving!=Zugbeeinflussung - Warnung: Befehl 'R' nicht ausgeführt, Zug in Bewegung!
-ATC command syntax error: I statement not closed: @1=Zugbeeinflussung - Syntaxfehler: I-Anweisung nicht geschlossen: @1
-ATC command parse error: Unknown command: @1=Zugbeeinflussung - Fehler: Unbekannter Befehl: @1
-This position is protected!=Diese Position ist geschützt!
-You need to own at least one neighboring wagon to destroy this couple.=Du musst Besitzer eines angrenzenden Waggons sein, um hier abzukuppeln.
-@1 Platform (low)=Niedriger @1-Bahnsteig
-@1 Platform (high)=Hoher @1-Bahnsteig
-off=aus
-on=ein
-Lampless Signal (@1)=Mechanisches Signal (@1)
-Signal (@1)=Lichtsignal (@1)
-Track Worker Tool@n@nLeft-click: change rail type (straight/curve/switch)@nRight-click: rotate rail/bumper/signal/etc.=Schienenwerkzeug@n@nLinksklick: Schienentyp ändern, Rechtsklick: Objekt drehen.
-This node can't be rotated using the trackworker!=Kann diesen Block nicht mit dem Schienenwerkzeug drehen.
-This node can't be changed using the trackworker!=Kann diesen Block nicht mit dem Schienenwerkzeug bearbeiten.
-Can't place: not pointing at node=Kann nicht platzieren: Du zeigst nicht auf einen Block.
-Can't place: space occupied!=Kann nicht platzieren: Platz besetzt.
-Can't place: protected position!=Kann nicht platzieren: Position geschützt.
-Can't place: Not enough slope items left (@1 required)=Kann nicht platzieren: nicht genug Steigungsblöcke, es werden insgesamt @1 benötigt.
-Can't place: There's no slope of length @1=Kann nicht platzieren: Keine Steigung der Länge @1 definiert.
-Can't place: no supporting node at upper end.=Kann nicht platzieren: kein unterstützender Block am Ende der Steigung.
-Deprecated Track=ausrangierte Schiene, nicht verwenden.
-Track=Schiene
-Bumper=Prellbock
-Detector Rail=Detektorschiene
-Speed:=Geschw.:
-Target:=Zielges.:
-@1 Slope=@1 Steigung
-Can't get on: wagon full or doors closed!=Kann nicht einsteigen: Waggon voll oder Türen geschlossen.
-Use Sneak+rightclick to bypass closed doors!=Nutze Sneak+Rechtsklick, um die Türnotöffnung zu aktivieren und trotzdem einzusteigen.
-Lock couples=Kupplungen sperren
-Save wagon properties=Waggon-Einstellungen speichern
-Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!=Türen sind geschlossen! Sneak+Rechtsklick, um die Türnotöffnung zu aktivieren und trotzdem auszusteigen.
-Wagon properties=Waggon-Einstellungen
-Get off=Aussteigen
-Get off (forced)=Aussteigen (erzwingen)
-(Doors closed)=(Türen geschlossen)
-Access to @1=Zugang zu @1
-Default Seat=Standardsitzplatz
-Default Seat (driver stand)=Standardsitzplatz (Führerstand)
-Driver Stand=Führerstand
-Driver Stand (left)=Führerstand Links
-Driver Stand (right)=Führerstand Rechts
-Industrial Train Engine=Industrielle Lokomotive
-Industrial tank wagon=Tankwaggon
-Industrial wood wagon=Holztransportwaggon
-Japanese Train Engine=Japanische Personenzug-Lokomotive
-Japanese Train Wagon=Japanischer Personenzug-Passagierwaggon
-Steam Engine=Dampflokomotive
-Detailed Steam Engine=detaillierte Dampflokomotive
-Passenger Wagon=Passagierwaggon
-Box wagon=Güterwaggon
-Subway Passenger Wagon=U-Bahn-Waggon
-The wagon's inventory is not empty!=Das Inventar dieses Waggons ist nicht leer!
-This track can not be changed!=Diese Schiene kann nicht geändert werden!
-This track can not be rotated!=Diese Schiene kann nicht gedreht werden!
-This track can not be removed!=Diese Schiene kann nicht entfernt werden!
-Position is occupied by a train.=Ein Zug steht an dieser Position.
-There's a Track Circuit Break here.=Hier ist eine Gleisabschnittsgrenze (TCB).
-There's a Signal Influence Point here.=Hier ist ein Signal-Beeinflussungspunkt.
-Buffer and Chain Coupler=Schraubenkupplung
-Scharfenberg Coupler=Scharfenbergkupplung
-Japanese Train Inter-Wagon Connection=Waggonzwischenverbindung Japanischer Personenzug
-Can not couple: The couplers of the trains do not match (@1 and @2).=Kann nicht ankuppeln: Die Kupplungen der Züge passen nicht zueinander (@1 und @2)
-<none>=<keine>
diff --git a/advtrains/locale/advtrains.zh_CN.tr b/advtrains/locale/advtrains.zh_CN.tr
deleted file mode 100644
index ef9c99b..0000000
--- a/advtrains/locale/advtrains.zh_CN.tr
+++ /dev/null
@@ -1,107 +0,0 @@
-# textdomain: advtrains
-
-# Advtrains Core (unorganized)
-This wagon is owned by @1, you can't destroy it.=这是@1的车厢, 你不能摧毁它.
-Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon.=警告: 如果你摧毁此车厢, 你只能拿到一些钢方块. 如果你确定要摧毁这个车厢,请按潜行键并左键单击此车厢.
-ATC controller, unconfigured.=ATC控制器 (未配置)
-ATC controller=ATC控制器
-ATC controller, mode @1@nChannel: @2=ATC控制器@n模式: @1@n频道: @2
-ATC controller, mode @1@nCommand: @2=ATC控制器@n模式: @1@n命令: @2
-Command=命令
-Command (on)=命令(激活时)
-Digiline channel=Digiline 频道
-ATC Reverse command warning: didn't reverse train, train moving!=ATC警告:未执行“R”命令, 火车在移动
-ATC command syntax error: I statement not closed: @1=ATC语法错误: "I"命令不完整: @1
-ATC command parse error: Unknown command: @1=ATC语法错误: 未知命令: @1
-This position is protected!=这里已被保护.
-You need to own at least one neighboring wagon to destroy this couple.=你必须至少拥有其中一个车厢才能解耦这两个车厢.
-This node can't be rotated using the trackworker!=你不能使用铁路调整工具旋转这个方块.
-This node can't be changed using the trackworker!=你不能使用铁路调整工具调整这个方块.
-Can't place: not pointing at node=无法放置: 你没有选择任何方块.
-Can't place: space occupied!=无法放置: 此区域已被占用.
-Can't place: protected position!=无法放置: 此区域已被保护.
-Can't place: Not enough slope items left (@1 required)=无法放置: 你没有足够的铁路斜坡放置工具 (你需要@1个)
-Can't place: There's no slope of length @1=无法放置: advtrains不支持长度为@1m的斜坡.
-Can't place: no supporting node at upper end.=无法放置: 较高端没有支撑方块.
-Deprecated Track=请不要使用
-Can't get on: wagon full or doors closed!=无法上车: 车门已关闭或车厢已满
-Use Sneak+rightclick to bypass closed doors!=请使用潜行+右键上车
-Lock couples=锁定连接处
-Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!=车门已关闭, 请使用潜行+右键单击下车
-Access to @1=可前往@1
-The clipboard couldn't access the metadata. Paste failed.=无法粘贴: 剪贴板无法访问元数据
-The clipboard couldn't access the metadata. Copy failed.=无法复制: 剪贴板无法访问元数据
-
-# Train HUD/Formspecs
-Speed:=速度:
-Target:=目标速度:
-Show Inventory=显示物品栏
-Select seat:=请选择座位
-Wagon properties=车厢属性
-Save wagon properties=保存车厢属性
-Text displayed outside on train=车厢外部显示
-Text displayed inside train=车厢内部显示
-Line=火车线路
-Routingcode=路由码
-Get off=下车
-Get off (forced)=强制下车
-(Doors closed)=(车门已关闭)
-
-# General
-Save=保存
-# "off" and "on" can be translated differently depending on the context and are therefore not translated.
-off=off
-on=on
-
-# Line automation
-Station Code=车站代码
-Station Name=车站名称
-Door Delay=车门关闭时间
-Departure Speed=出发速度
-Stop Time=停站时间
-
-# Items
-Track Worker Tool@n@nLeft-click: change rail type (straight/curve/switch)@nRight-click: rotate rail/bumper/signal/etc.=铁路调整工具@n@n左键单击: 切换轨道类型@n右键单击: 旋转方块
-Passive Component Naming Tool@n@nRight-click to name a passive component.=被动元件命名工具@n@n右键单击命名所选元件.
-Train copy/paste tool@n@nLeft-click: copy train@nRight-click: paste train=火车复制工具@n@n左键单击: 复制@n右键单击: 粘帖
-Track=铁轨
-Perpendicular Diamond Crossing Track=垂直交叉铁轨
-45/90 Degree Diamond Crossing Track=45度交叉铁轨
-Unloading Track=卸货铁轨
-Loading Track=装货铁轨
-Bumper=保险杠
-Detector Rail=探测铁轨
-@1 Slope=@1斜坡
-@1 Platform (low)=50cm高的@1站台
-@1 Platform (high)=1m高的@1站台
-@1 Platform (45 degree)=1m高的@1站台 (45度)
-Lampless Signal (@1)=臂板信号机 (@1)
-Signal (@1)=信号灯 (@1)
-Wallmounted Signal (l)=壁挂式信号灯 (左侧)
-Wallmounted Signal (r)=壁挂式信号灯 (右侧)
-Wallmounted Signal (t)=悬挂式信号灯
-Andrew's Cross=铁路道口信号灯
-Boiler=锅炉
-driver's cab=驾驶室
-Wheel=车轮
-Chimney=烟囱
-
-# Seats
-Default Seat=默认座位
-Default Seat (driver stand)=默认座位 (司机座位)
-Driver Stand=司机座位
-Driver Stand (left)=左侧司机座位
-Driver Stand (right)=右侧司机座位
-
-# Wagon/engine types
-Industrial Train Engine=工业用火车头
-Big Industrial Train Engine=大型工业用火车头
-Industrial tank wagon=液体运输车厢
-Industrial wood wagon=木材运输车厢
-Japanese Train Engine=高速列车车头
-Japanese Train Wagon=高速列车车厢
-Steam Engine=蒸汽机车
-Detailed Steam Engine=精细的蒸汽机车
-Passenger Wagon=客车
-Box Wagon=货运车厢
-Subway Passenger Wagon=地铁车厢
diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua
index db39991..26e1f79 100644
--- a/advtrains/occupation.lua
+++ b/advtrains/occupation.lua
@@ -86,9 +86,10 @@ end
function o.set_item(train_id, pos, idx)
local t = occgetcreate(pos)
+ assert(idx)
local i = 1
while t[i] do
- if t[i]==train_id then
+ if t[i]==train_id and t[i+1]==index then
break
end
i = i + 2
@@ -98,25 +99,30 @@ function o.set_item(train_id, pos, idx)
end
-function o.clear_item(train_id, pos)
+function o.clear_all_items(train_id, pos)
local t = occget(pos)
if not t then return end
local i = 1
- local moving = false
while t[i] do
if t[i]==train_id then
- if moving then
- -- if, for some occasion, there should be a duplicate entry, erase this one too
- atwarn("Duplicate occupation entry at",pos,"for train",train_id,":",t)
- i = i - 2
- end
- moving = true
+ table.remove(t, i)
+ table.remove(t, i)
+ else
+ i = i + 2
end
- if moving then
- t[i] = t[i+2]
- t[i+1] = t[i+3]
+ end
+end
+function o.clear_specific_item(train_id, pos, index)
+ local t = occget(pos)
+ if not t then return end
+ local i = 1
+ while t[i] do
+ if t[i]==train_id and t[i+1]==index then
+ table.remove(t, i)
+ table.remove(t, i)
+ else
+ i = i + 2
end
- i = i + 2
end
end
@@ -143,64 +149,88 @@ function o.check_collision(pos, train_id)
return false
end
--- Gets a mapping of train id's to indexes of trains that share this path item with this train
--- The train itself will not be included.
--- If the requested index position is off-track, returns {}.
--- returns (table with train_id->index), position
-function o.get_occupations(train, index)
- local ppos, ontrack = advtrains.path_get(train, index)
- if not ontrack then
- atlog("Train",train.id,"get_occupations requested off-track",index)
- return {}, ppos
- end
+-- Gets a mapping of train id's to indexes of trains that have a path item at this position
+-- Note that the case where 2 or more indices are at a position only occurs if there is a track loop.
+-- returns (table with train_id->{index1, index2...})
+function o.reverse_lookup(ppos)
local pos = advtrains.round_vector_floor_y(ppos)
local t = occget(pos)
if not t then return {} end
local r = {}
local i = 1
- local train_id = train.id
while t[i] do
- if t[i]~=train_id then
- r[t[i]] = t[i+1]
- end
+ if not r[t[i]] then r[t[i]] = {} end
+ table.insert(r[t[i]], t[i+1])
i = i + 2
end
- return r, pos
+ return r
end
--- Gets a mapping of train id's to indexes of trains that stand or drive over
+
+-- Gets a mapping of train id's to indexes of trains that have a path item at this position.
+-- Quick variant: will only return one index per train (the latest one added)
-- returns (table with train_id->index)
-function o.get_trains_at(ppos)
+function o.reverse_lookup_quick(ppos)
local pos = advtrains.round_vector_floor_y(ppos)
local t = occget(pos)
if not t then return {} end
local r = {}
local i = 1
while t[i] do
- local train = advtrains.trains[t[i]]
- local idx = t[i+1]
- if train.end_index - 0.5 <= idx and idx <= train.index + 0.5 then
- r[t[i]] = idx
- end
+ r[t[i]] = t[i+1]
i = i + 2
end
return r
end
--- Gets a mapping of train id's to indexes of trains that have a path
--- generated over this node
--- returns (table with train_id->index)
-function o.get_trains_over(ppos)
- local pos = advtrains.round_vector_floor_y(ppos)
- local t = occget(pos)
- if not t then return {} end
+local OCC_CLOSE_PROXIMITY = 3
+-- Gets a mapping of train id's to index of trains that have a path item at this position. Selects at most one index based on a given heuristic, or even none if it does not match the heuristic criterion
+-- returns (table with train_id->index), position
+-- "in_train": first index that lies between train index and end index
+-- "train_at_node": first index where the train is standing on that node (like in_train but with +-0.5 added to index)
+-- "first_ahead": smallest index that is > current index
+-- "before_end"(default): smallest index that is > end index
+-- "close_proximity": within 3 indices close to the train index and end_index
+-- "any": just output the first index found and do not check further (also occurs if both "in_train" and "first_ahead" heuristics have failed
+function o.reverse_lookup_sel(pos, heuristic)
+ if not heuristic then heuristic = "before_end" end
+ local om = o.reverse_lookup(pos)
local r = {}
- local i = 1
- while t[i] do
- local idx = t[i+1]
- r[t[i]] = idx
- i = i + 2
+ for tid, idxs in pairs(om) do
+ r[tid] = idxs[1]
+ if heuristic~="any" then
+ --must run a heuristic
+ --atdebug("reverse_lookup_sel is running heuristic for", pos,heuristic,"idxs",table.concat(idxs,","))
+ local otrn = advtrains.trains[tid]
+ advtrains.train_ensure_init(tid, otrn)
+ local h_value
+ for _,idx in ipairs(idxs) do
+ if heuristic == "first_ahead" and idx > otrn.index and (not h_value or h_value>idx) then
+ h_value = idx
+ end
+ if heuristic == "before_end" and idx > otrn.end_index and (not h_value or h_value>idx) then
+ h_value = idx
+ end
+ if heuristic == "in_train" and idx < otrn.index and idx > otrn.end_index then
+ h_value = idx
+ end
+ if heuristic == "train_at_node" and idx < (otrn.index+0.5) and idx > (otrn.end_index-0.5) then
+ h_value = idx
+ end
+ if heuristic == "close_proximity" and idx < (otrn.index + OCC_CLOSE_PROXIMITY) and idx > (otrn.end_index - OCC_CLOSE_PROXIMITY) then
+ h_value = idx
+ end
+ end
+ r[tid] = h_value
+ --atdebug(h_value,"chosen")
+ end
end
- return r
+ return r, pos
+end
+-- Gets a mapping of train id's to indexes of trains that stand or drive over
+-- returns (table with train_id->index)
+function o.get_trains_at(ppos)
+ local pos = advtrains.round_vector_floor_y(ppos)
+ return o.reverse_lookup_sel(pos, "train_at_node")
end
advtrains.occ = o
diff --git a/advtrains/path.lua b/advtrains/path.lua
index d54aebe..4807361 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -118,7 +118,7 @@ function advtrains.path_invalidate(train, ignore_lock)
if train.path then
for i,p in pairs(train.path) do
- advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(p))
+ advtrains.occ.clear_all_items(train.id, advtrains.round_vector_floor_y(p))
end
end
train.path = nil
@@ -161,7 +161,7 @@ function advtrains.path_invalidate_ahead(train, start_idx, ignore_when_passed)
-- leave current node in path, it won't change. What might change is the path onward from here (e.g. switch)
local i = idx + 1
while train.path[i] do
- advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
+ advtrains.occ.clear_specific_item(train.id, advtrains.round_vector_floor_y(train.path[i]), i)
i = i+1
end
train.path_ext_f=idx
@@ -391,7 +391,7 @@ local PATH_CLEAR_KEEP = 4
function advtrains.path_clear_unused(train)
local i
for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do
- advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
+ advtrains.occ.clear_specific_item(train.id, advtrains.round_vector_floor_y(train.path[i]), i)
train.path[i] = nil
train.path_dist[i-1] = nil
train.path_cp[i] = nil
@@ -432,18 +432,19 @@ end
-- Projects the path of "train" onto the path of "onto_train_id", and returns the index on onto_train's path
-- that corresponds to "index" on "train"'s path, as well as whether both trains face each other
-- index may be fractional
+-- heuristic: see advtrains.occ.reverse_lookup_sel()
-- returns: res_index, trains_facing
-- returns nil when path can not be projected, either because trains are on different tracks or
-- node at "index" happens to be on a turnout and it's the wrong direction
-- Note - duplicate with similar functionality is in train_step_b() - that code combines train detection with projecting
-function advtrains.path_project(train, index, onto_train_id)
+function advtrains.path_project(train, index, onto_train_id, heuristic)
local base_idx = atfloor(index)
local frac_part = index - base_idx
local base_pos = advtrains.path_get(train, base_idx)
local base_cn = train.path_cn[base_idx]
local otrn = advtrains.trains[onto_train_id]
-- query occupation
- local occ = advtrains.occ.get_trains_over(base_pos)
+ local occ = advtrains.occ.reverse_lookup_sel(base_pos, heuristic)
-- is wanted train id contained?
local ob_idx = occ[onto_train_id]
if not ob_idx then
diff --git a/advtrains/po/README.md b/advtrains/po/README.md
new file mode 100644
index 0000000..3e94682
--- /dev/null
+++ b/advtrains/po/README.md
@@ -0,0 +1,70 @@
+# Translations
+Please read this document before working on any translations.
+
+Unlike many other mods, Advtrains uses `.po` files for localization,
+which are then automatically converted to `.tr` files when the mod is
+loaded. Therefore, please submit patches that edit the `.po` files.
+
+## Getting Started
+The translation files can be edited like any other `.po` file.
+
+If the translation file for your language does not exist, create it by
+copying `template.txt` to `advtrains.XX.tr`, where `XX` is replaced by
+the language code.
+
+Feel free to use the [discussion mailing list][srht-discuss] if you
+have any questions regarding localization.
+
+You can share your `.po` file directly or [as a patch][gsm] to the [dev
+mailing list][srht-devel]. The latter is encouraged, but, unlike code
+changes, translation files sent directly are also accepted.
+
+[tr-format]: https://minetest.gitlab.io/minetest/translations/#translation-file-format
+[srht-discuss]: https://lists.sr.ht/~gpcf/advtrains-discuss
+[srht-devel]: https://lists.sr.ht/~gpcf/advtrains-devel
+[gsm]: https://git-send-email.io
+
+## Translation Notes
+* Translations should be consistent. You can use other entries or the
+translations in Minetest as a reference.
+* Translations do not have to fully correspond to the original text -
+they only need to provide the same information. In particular,
+translations do not need to have the same linguistical structure as the
+original text.
+* Replacement sequences (`@1`, `@2`, etc) should not be translated.
+* Certain abbreviations or names, such as "Ks" or "Zs 3", should
+generally not be translated.
+
+### (de) German
+* Verwenden Sie die neue Rechtschreibung und die Sie-Form.
+* Mit der deutschen Tastaturbelegung unter Linux können die
+Anführungszeichen „“ mit AltGr-V bzw. AltGr-B eingegeben werden.
+
+### (zh) Chinese
+(This section is written in English to avoid writing the note twice or
+using only one of the variants, as most of this section applies to both
+the traditional and simplified variants.)
+
+* Please use the 「」 quotation marks for Traditional Chinese and “”
+for Simplified Chinese.
+* Please use the fullwidth variants of: , 、 。 ? ! : ;
+* Please use the halfwidth variants of: ( ) [ ] / \ |
+* Please do not leave any space between Han characters (including
+fullwidth punctuation marks).
+* Please leave a space between Han characters (excluding fullwidth
+punctuation marks) and characters from other scripts (including
+halfwidth punctuation marks). However, do not leave any space between
+Han characters and Arabic numerals.
+
+## Notes for developers
+* The `update-translations.sh` script can be used to update the
+translation files. However, please make sure to install the
+`basic_trains` mod before running the script.
+* Please make sure that the first argument to `S` (or `attrans`) _only_
+includes string literals without formatting or concatenation. This is
+unfortunately a limitation of the `xgettext` utility.
+* Avoid word-by-word translations.
+* Avoid manipulating translated strings (except for concatenation). Use
+server-side translations if you have to modify the text sent to users.
+* Avoid truncating strings unless multibyte characters are handled
+properly.
diff --git a/advtrains/po/advtrains.pot b/advtrains/po/advtrains.pot
new file mode 100644
index 0000000..6fda1d7
--- /dev/null
+++ b/advtrains/po/advtrains.pot
@@ -0,0 +1,632 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the advtrains package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2023-10-09 11:02+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: advtrains/atc.lua:109
+msgid "Unconfigured ATC controller"
+msgstr ""
+
+#: advtrains/atc.lua:150
+msgid ""
+"ATC controller, mode @1\n"
+"Command: @2"
+msgstr ""
+
+#: advtrains/atc.lua:180
+msgid "Command"
+msgstr ""
+
+#: advtrains/atc.lua:184
+msgid "Command (on)"
+msgstr ""
+
+#: advtrains/atc.lua:187
+msgid "Digiline channel"
+msgstr ""
+
+#: advtrains/atc.lua:189 advtrains_line_automation/stoprail.lua:65
+#: advtrains_luaautomation/active_common.lua:48
+msgid "Save"
+msgstr ""
+
+#: advtrains/atc.lua:236
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr ""
+
+#: advtrains/atc.lua:248
+msgid "ATC Kick command warning: doors are closed."
+msgstr ""
+
+#: advtrains/atc.lua:252
+msgid "ATC Kick command warning: train moving."
+msgstr ""
+
+#: advtrains/atc.lua:322
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr ""
+
+#: advtrains/atc.lua:385
+msgid "ATC command parse error: Unknown command: @1"
+msgstr ""
+
+#: advtrains/copytool.lua:8
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+
+#: advtrains/copytool.lua:29
+msgid "You do not have the @1 privilege."
+msgstr ""
+
+#: advtrains/copytool.lua:41
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr ""
+
+#: advtrains/copytool.lua:47
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr ""
+
+#: advtrains/copytool.lua:52 advtrains/copytool.lua:57
+msgid "The clipboard is empty."
+msgstr ""
+
+#: advtrains/copytool.lua:74
+msgid "Back of train would end up off track, cancelling."
+msgstr ""
+
+#: advtrains/copytool.lua:92
+msgid "No such lua entity."
+msgstr ""
+
+#: advtrains/copytool.lua:98
+msgid "No such wagon: @1."
+msgstr ""
+
+#: advtrains/copytool.lua:104
+msgid "No such train: @1."
+msgstr ""
+
+#: advtrains/copytool.lua:176
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr ""
+
+#: advtrains/copytool.lua:180
+msgid "Train copied."
+msgstr ""
+
+#: advtrains/couple.lua:28
+msgid "Buffer and Chain Coupler"
+msgstr ""
+
+#: advtrains/couple.lua:29
+msgid "Scharfenberg Coupler"
+msgstr ""
+
+#: advtrains/couple.lua:185
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr ""
+
+#: advtrains/couple.lua:329 advtrains/couple.lua:333
+msgid "<No coupler>"
+msgstr ""
+
+#: advtrains/couple.lua:334
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr ""
+
+#: advtrains/craft_items.lua:3
+msgid "Boiler"
+msgstr ""
+
+#: advtrains/craft_items.lua:9
+msgid "Driver's cab"
+msgstr ""
+
+#: advtrains/craft_items.lua:15
+msgid "Wheel"
+msgstr ""
+
+#: advtrains/craft_items.lua:21
+msgid "Chimney"
+msgstr ""
+
+#: advtrains/misc_nodes.lua:16
+msgid "@1 Platform (low)"
+msgstr ""
+
+#: advtrains/misc_nodes.lua:33
+msgid "@1 Platform (high)"
+msgstr ""
+
+#: advtrains/misc_nodes.lua:59
+msgid "@1 Platform (45 degree)"
+msgstr ""
+
+#: advtrains/misc_nodes.lua:81
+msgid "@1 Platform (low, 45 degree)"
+msgstr ""
+
+#: advtrains/protection.lua:7
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: advtrains/protection.lua:12
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: advtrains/protection.lua:18
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:24
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr ""
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr ""
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr ""
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build tracks at this protected position."
+msgstr ""
+
+#: advtrains/protection.lua:184
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr ""
+
+#: advtrains/signals.lua:63
+msgid "Lampless Signal"
+msgstr ""
+
+#: advtrains/signals.lua:127
+msgid "Signal"
+msgstr ""
+
+#: advtrains/signals.lua:191
+msgid "Wallmounted Signal (left)"
+msgstr ""
+
+#: advtrains/signals.lua:192
+msgid "Wallmounted Signal (right)"
+msgstr ""
+
+#: advtrains/signals.lua:193
+msgid "Wallmounted Signal (top)"
+msgstr ""
+
+#: advtrains/signals.lua:281 advtrains/signals.lua:322
+msgid "Andrew's Cross"
+msgstr ""
+
+#: advtrains/trackplacer.lua:313
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+
+#: advtrains/trackplacer.lua:340 advtrains/trackplacer.lua:377
+msgid "This node can't be rotated using the trackworker."
+msgstr ""
+
+#: advtrains/trackplacer.lua:350
+msgid "This track can not be rotated."
+msgstr ""
+
+#: advtrains/trackplacer.lua:404
+msgid "This node can't be changed using the trackworker."
+msgstr ""
+
+#: advtrains/trackplacer.lua:414
+msgid "This track can not be changed."
+msgstr ""
+
+#: advtrains/tracks.lua:449
+msgid "This track can not be removed."
+msgstr ""
+
+#: advtrains/tracks.lua:616
+msgid "Position is occupied by a train."
+msgstr ""
+
+#: advtrains/tracks.lua:622
+msgid "There's a Track Circuit Break here."
+msgstr ""
+
+#: advtrains/tracks.lua:626
+msgid "There's a Signal Influence Point here."
+msgstr ""
+
+#: advtrains/tracks.lua:637
+msgid "@1 Slope"
+msgstr ""
+
+#: advtrains/tracks.lua:648 advtrains/tracks.lua:653
+msgid "Can't place slope: not pointing at node."
+msgstr ""
+
+#: advtrains/tracks.lua:658
+msgid "Can't place slope: space occupied."
+msgstr ""
+
+#: advtrains/tracks.lua:711
+msgid "Can't place slope: Not enough slope items left (@1 required)."
+msgstr ""
+
+#: advtrains/tracks.lua:714
+msgid "Can't place slope: There's no slope of length @1."
+msgstr ""
+
+#: advtrains/tracks.lua:721
+msgid "Can't place slope: no supporting node at upper end."
+msgstr ""
+
+#: advtrains/trainhud.lua:305
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: advtrains/wagons.lua:179
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr ""
+
+#: advtrains/wagons.lua:203
+msgid "The wagon's inventory is not empty."
+msgstr ""
+
+#: advtrains/wagons.lua:210
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+
+#: advtrains/wagons.lua:216
+msgid ""
+"Warning: If you destroy this wagon, you only get some steel back! If you are "
+"sure, hold Sneak and left-click the wagon."
+msgstr ""
+
+#: advtrains/wagons.lua:649 advtrains/wagons.lua:850
+msgid "Show Inventory"
+msgstr ""
+
+#: advtrains/wagons.lua:652
+msgid "Onboard Computer"
+msgstr ""
+
+#: advtrains/wagons.lua:655 advtrains/wagons.lua:1328
+msgid "Wagon properties"
+msgstr ""
+
+#: advtrains/wagons.lua:658
+msgid "Get off"
+msgstr ""
+
+#: advtrains/wagons.lua:661
+msgid "Get off (forced)"
+msgstr ""
+
+#: advtrains/wagons.lua:663
+msgid "(Doors closed)"
+msgstr ""
+
+#: advtrains/wagons.lua:692
+msgid "This wagon has no seats."
+msgstr ""
+
+#: advtrains/wagons.lua:703
+msgid "This wagon is full."
+msgstr ""
+
+#: advtrains/wagons.lua:706
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr ""
+
+#: advtrains/wagons.lua:712
+msgid "You can't get on this wagon."
+msgstr ""
+
+#: advtrains/wagons.lua:838
+msgid "Select seat:"
+msgstr ""
+
+#: advtrains/wagons.lua:880
+msgid "Save wagon properties"
+msgstr ""
+
+#: advtrains/wagons.lua:965
+msgid "Text displayed outside on train"
+msgstr ""
+
+#: advtrains/wagons.lua:966
+msgid "Text displayed inside train"
+msgstr ""
+
+#: advtrains/wagons.lua:967
+msgid "Line"
+msgstr ""
+
+#: advtrains/wagons.lua:968
+msgid "Routingcode"
+msgstr ""
+
+#: advtrains/wagons.lua:1241
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr ""
+
+#: advtrains/wagons.lua:1250
+msgid "You are not allowed to access the driver stand."
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:13
+msgid "Point speed restriction: @1"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:14
+msgid "Set point speed restriction:"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:30
+msgid "You are not allowed to configure this track without the @1 privilege."
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:34
+#: advtrains_line_automation/stoprail.lua:31
+#: advtrains_line_automation/stoprail.lua:76
+msgid "You are not allowed to configure this track."
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:64
+msgid "Point Speed Restriction Track"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:54
+msgid "Station Code"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:55
+msgid "Station Name"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:56
+msgid "Door Delay"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:57
+msgid "Dep. Speed"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:58 advtrains_train_track/init.lua:11
+#: advtrains_train_track/init.lua:156
+msgid "Track"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:59
+msgid "Stop Time"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:60
+msgid "Door Side"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:62
+msgid "Reverse train"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:63
+msgid "Kick out passengers"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:97
+msgid "Station code \"@1\" already exists and is owned by @2."
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:111
+msgid "This station is owned by @1. You are not allowed to edit its name."
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:221
+msgid "Station/Stop Track"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:17
+msgid "Unconfigured LuaATC component"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:46
+msgid "LuaATC Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:49
+msgid "Clear Local Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:50
+msgid "Code"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:64
+msgid ""
+"You are not allowed to configure this LuaATC component without the @1 "
+"privilege."
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:94
+msgid "LuaATC component assigned to environment '@1'"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:96
+msgid "LuaATC component assigned to an invalid environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:171
+msgid "LuaATC component with error: @1"
+msgstr ""
+
+#: advtrains_luaautomation/init.lua:13
+msgid ""
+"Can place and configure LuaATC components, including execute potentially "
+"harmful Lua code"
+msgstr ""
+
+#: advtrains_luaautomation/mesecon_controller.lua:211
+msgid "LuaATC Mesecon Controller"
+msgstr ""
+
+#: advtrains_luaautomation/operation_panel.lua:11
+msgid "LuaATC Operation Panel"
+msgstr ""
+
+#: advtrains_luaautomation/pcnaming.lua:28
+msgid ""
+"Passive Component Naming Tool\n"
+"\n"
+"Right-click to name a passive component."
+msgstr ""
+
+#: advtrains_luaautomation/pcnaming.lua:39
+msgid ""
+"You are not allowed to name LuaATC passive components without the @1 "
+"privilege."
+msgstr ""
+
+#: advtrains_luaautomation/pcnaming.lua:62
+msgid "Set name of component (empty to clear)"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:10
+#: advtrains_train_industrial/init.lua:49 advtrains_train_steam/init.lua:20
+#: advtrains_train_steam/init.lua:91
+msgid "Driver Stand (right)"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:17
+#: advtrains_train_industrial/init.lua:56 advtrains_train_steam/init.lua:14
+#: advtrains_train_steam/init.lua:85
+msgid "Driver Stand (left)"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:40
+msgid "Industrial Train Engine"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:79
+msgid "Big Industrial Train Engine"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:98
+msgid "Industrial tank wagon"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:116
+msgid "Industrial wood wagon"
+msgstr ""
+
+#: advtrains_train_japan/init.lua:4
+msgid "Japanese Train Inter-Wagon Connection"
+msgstr ""
+
+#: advtrains_train_japan/init.lua:37
+msgid "Driver stand"
+msgstr ""
+
+#: advtrains_train_japan/init.lua:101
+msgid "Japanese Train Engine"
+msgstr ""
+
+#: advtrains_train_japan/init.lua:176
+msgid "Japanese Train Wagon"
+msgstr ""
+
+#: advtrains_train_steam/init.lua:75
+msgid "Steam Engine"
+msgstr ""
+
+#: advtrains_train_steam/init.lua:159
+msgid "Detailed Steam Engine"
+msgstr ""
+
+#: advtrains_train_steam/init.lua:206
+msgid "Passenger Wagon"
+msgstr ""
+
+#: advtrains_train_steam/init.lua:226
+msgid "Box Wagon"
+msgstr ""
+
+#: advtrains_train_subway/init.lua:144
+msgid "Subway Passenger Wagon"
+msgstr ""
+
+#: advtrains_train_track/init.lua:31
+msgid "Y-turnout"
+msgstr ""
+
+#: advtrains_train_track/init.lua:49
+msgid "3-way turnout"
+msgstr ""
+
+#: advtrains_train_track/init.lua:69
+msgid "Perpendicular Diamond Crossing Track"
+msgstr ""
+
+#: advtrains_train_track/init.lua:91
+msgid "90+Angle Diamond Crossing Track"
+msgstr ""
+
+#: advtrains_train_track/init.lua:132
+msgid "Diagonal Diamond Crossing Track"
+msgstr ""
+
+#: advtrains_train_track/init.lua:179
+msgid "Bumper"
+msgstr ""
+
+#: advtrains_train_track/init.lua:201
+msgid "ATC controller"
+msgstr ""
+
+#: advtrains_train_track/init.lua:317
+msgid "Unloading Track"
+msgstr ""
+
+#: advtrains_train_track/init.lua:342
+msgid "Loading Track"
+msgstr ""
+
+#: advtrains_train_track/init.lua:406
+msgid "Detector Rail"
+msgstr ""
diff --git a/advtrains/po/de.po b/advtrains/po/de.po
new file mode 100644
index 0000000..8821fe3
--- /dev/null
+++ b/advtrains/po/de.po
@@ -0,0 +1,724 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2023-10-09 11:02+0200\n"
+"PO-Revision-Date: 2023-10-09 11:18+0200\n"
+"Last-Translator: Y. Wang <yw05@forksworld.de>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.3.2\n"
+
+#: advtrains/atc.lua:109
+msgid "Unconfigured ATC controller"
+msgstr "Nicht konfiguiertes Zugbeeinflussungsgleis"
+
+#: advtrains/atc.lua:150
+msgid ""
+"ATC controller, mode @1\n"
+"Command: @2"
+msgstr ""
+"Zugbeeinflussungsgleis in Betriebsart „@1“\n"
+"Befehl: @2"
+
+#: advtrains/atc.lua:180
+msgid "Command"
+msgstr "Befehl"
+
+#: advtrains/atc.lua:184
+msgid "Command (on)"
+msgstr "Befehl (wenn aktiviert)"
+
+#: advtrains/atc.lua:187
+msgid "Digiline channel"
+msgstr "Digiline-Kanal"
+
+#: advtrains/atc.lua:189 advtrains_line_automation/stoprail.lua:65
+#: advtrains_luaautomation/active_common.lua:48
+msgid "Save"
+msgstr "Speichern"
+
+#: advtrains/atc.lua:236
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr ""
+"Zugbeeinflussung: Der Zug befindet sich in Bewegung und kann nicht umgekehrt "
+"werden."
+
+#: advtrains/atc.lua:248
+msgid "ATC Kick command warning: doors are closed."
+msgstr ""
+"Zugbeeinflussung: Wegen geschlossener Türen werden Fahrgäste nicht zum "
+"Ausstieg gezwungen."
+
+#: advtrains/atc.lua:252
+msgid "ATC Kick command warning: train moving."
+msgstr ""
+"Zugbeeinflussung: Der Zug befindet sich in Bewegung, Fahrgäste werden nicht "
+"zum Ausstieg gezwungen."
+
+#: advtrains/atc.lua:322
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "Zugbeeinflussung: Unvollständiger I-Befehl: @1"
+
+#: advtrains/atc.lua:385
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "Zugbeeinflussung: Unbekannter Befehl: @1"
+
+#: advtrains/copytool.lua:8
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+"Werkzeug zur Erstellung von Zugkopien\n"
+"\n"
+"Linksklick: Zug ins Clipboard kopieren\n"
+"Right-click: Kopierten Zug einfügen"
+
+#: advtrains/copytool.lua:29
+msgid "You do not have the @1 privilege."
+msgstr "Ihnen fehlt das „@1“-Privileg."
+
+#: advtrains/copytool.lua:41
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr "Das Gleis, auf dem der Waggon platziert werden woll, ist zu kurz."
+
+#: advtrains/copytool.lua:47
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr ""
+"Wegen des fehlgeschlagenen Zugriffs auf die Metadaten konnte eine Kopie des "
+"Zuges nicht eingefügt werden."
+
+#: advtrains/copytool.lua:52 advtrains/copytool.lua:57
+msgid "The clipboard is empty."
+msgstr "Das Clipboard ist leer."
+
+#: advtrains/copytool.lua:74
+msgid "Back of train would end up off track, cancelling."
+msgstr "Der hinterer Teil dez Zuges wäre nicht auf dem Gleis."
+
+#: advtrains/copytool.lua:92
+msgid "No such lua entity."
+msgstr ""
+"Sie zeigen nicht auf einem Objekt, das mit diesem Werkzeug kopiert werden "
+"kann."
+
+#: advtrains/copytool.lua:98
+msgid "No such wagon: @1."
+msgstr "Es gibt keinen mit „@1“ identifizierbaren Waggon."
+
+#: advtrains/copytool.lua:104
+msgid "No such train: @1."
+msgstr "Es gibt keinen mit „@1“ identifizierbaren Zug."
+
+#: advtrains/copytool.lua:176
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr ""
+"Wegen des fehlgeschlagenen Zugriffs auf die Metadaten konnte der Zug nicht "
+"kopiert werden."
+
+#: advtrains/copytool.lua:180
+msgid "Train copied."
+msgstr "Der Zug wurde Kopiert."
+
+#: advtrains/couple.lua:28
+msgid "Buffer and Chain Coupler"
+msgstr "Schraubenkupplung"
+
+#: advtrains/couple.lua:29
+msgid "Scharfenberg Coupler"
+msgstr "Scharfenbergkupplung"
+
+#: advtrains/couple.lua:185
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr "Sie dürfen ohne das „train_operator“-Privileg keine Züge ankuppeln."
+
+#: advtrains/couple.lua:329 advtrains/couple.lua:333
+msgid "<No coupler>"
+msgstr "<Keine Kupplung vorhanden>"
+
+#: advtrains/couple.lua:334
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr "Die Kupplungen der Züge passen nicht zueinander (@1 und @2)."
+
+#: advtrains/craft_items.lua:3
+msgid "Boiler"
+msgstr ""
+
+#: advtrains/craft_items.lua:9
+msgid "Driver's cab"
+msgstr "Führerstand"
+
+#: advtrains/craft_items.lua:15
+msgid "Wheel"
+msgstr ""
+
+#: advtrains/craft_items.lua:21
+msgid "Chimney"
+msgstr ""
+
+#: advtrains/misc_nodes.lua:16
+msgid "@1 Platform (low)"
+msgstr "Niedriger @1-Bahnsteig"
+
+#: advtrains/misc_nodes.lua:33
+msgid "@1 Platform (high)"
+msgstr "Hoher @1-Bahnsteig"
+
+#: advtrains/misc_nodes.lua:59
+msgid "@1 Platform (45 degree)"
+msgstr "Hoher @1-Bahnsteig (45°)"
+
+#: advtrains/misc_nodes.lua:81
+msgid "@1 Platform (low, 45 degree)"
+msgstr "Niedriger @1-Bahnsteig (45°)"
+
+#: advtrains/protection.lua:7
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: advtrains/protection.lua:12
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: advtrains/protection.lua:18
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:24
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr ""
+"Sie dürfen ohne das „track_builder“-Privileg nicht in der Nähe von Gleisen "
+"bauen."
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr "Sie dürfen ohne das „track_builder“-Privileg kein Gleis bauen."
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr "Sie dürfen an geschützten Stellen nicht in der Nähe von Gleisen bauen."
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "Sie dürfen an geschützten Stellen kein Gleis bauen."
+
+#: advtrains/protection.lua:184
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr ""
+"Sie dürfen ohne das „railway_operator“-Privileg keine Bahnanlage operieren."
+
+#: advtrains/signals.lua:63
+msgid "Lampless Signal"
+msgstr "Mechanisches Signal"
+
+#: advtrains/signals.lua:127
+msgid "Signal"
+msgstr "Lichtsignal"
+
+#: advtrains/signals.lua:191
+msgid "Wallmounted Signal (left)"
+msgstr "An der linken Seite montiertes Signal"
+
+#: advtrains/signals.lua:192
+msgid "Wallmounted Signal (right)"
+msgstr "An der rechten Seite montiertes Signal"
+
+#: advtrains/signals.lua:193
+msgid "Wallmounted Signal (top)"
+msgstr "An der Decke montiertes Signal"
+
+#: advtrains/signals.lua:281 advtrains/signals.lua:322
+msgid "Andrew's Cross"
+msgstr "Andreaskreuz"
+
+#: advtrains/trackplacer.lua:313
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+"Gleiswerkzeug\n"
+"\n"
+"Linksklick: Gleistyp ändern\n"
+"Rechtsklick: Objekt drehen"
+
+#: advtrains/trackplacer.lua:340 advtrains/trackplacer.lua:377
+msgid "This node can't be rotated using the trackworker."
+msgstr "Dieser Block kann nicht mit dem Gleiswerkzeug gedreht werden."
+
+#: advtrains/trackplacer.lua:350
+msgid "This track can not be rotated."
+msgstr "Dieses Gleis kann nicht gedreht werden."
+
+#: advtrains/trackplacer.lua:404
+msgid "This node can't be changed using the trackworker."
+msgstr "Dieser Block kann nicht mit dem Gleiswerkzeug bearbeitet werden."
+
+#: advtrains/trackplacer.lua:414
+msgid "This track can not be changed."
+msgstr "Dieses Gleis kann nicht geändert werden."
+
+#: advtrains/tracks.lua:449
+msgid "This track can not be removed."
+msgstr "Dieses Gleis kann nicht entfernt werden."
+
+#: advtrains/tracks.lua:616
+msgid "Position is occupied by a train."
+msgstr "Ein Zug steht an dieser Position."
+
+#: advtrains/tracks.lua:622
+msgid "There's a Track Circuit Break here."
+msgstr "Hier ist eine Gleisabschnittsgrenze (TCB)."
+
+#: advtrains/tracks.lua:626
+msgid "There's a Signal Influence Point here."
+msgstr "Hier ist ein Signal-Beeinflussungspunkt."
+
+#: advtrains/tracks.lua:637
+msgid "@1 Slope"
+msgstr "@1 Steigung"
+
+#: advtrains/tracks.lua:648 advtrains/tracks.lua:653
+msgid "Can't place slope: not pointing at node."
+msgstr "Es kann nicht platziert werden: Sie zeigen nicht auf einem Block."
+
+#: advtrains/tracks.lua:658
+msgid "Can't place slope: space occupied."
+msgstr "Es kann nicht platziert werden: Diese Position ist besetzt."
+
+#: advtrains/tracks.lua:711
+msgid "Can't place slope: Not enough slope items left (@1 required)."
+msgstr ""
+"Es kann nicht platziert werden: Sie haben nicht genug Steigungsblöcke, es "
+"werden insgesamt @1 benötigt."
+
+#: advtrains/tracks.lua:714
+msgid "Can't place slope: There's no slope of length @1."
+msgstr ""
+"Es kann nicht platziert werden: die Steigung der Länge @1 ist nicht "
+"definiert."
+
+#: advtrains/tracks.lua:721
+msgid "Can't place slope: no supporting node at upper end."
+msgstr ""
+"Es kann nicht platziert werden: es gibt keinen unterstützenden Block am Ende "
+"der Steigung."
+
+#: advtrains/trainhud.lua:305
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: advtrains/wagons.lua:179
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "Dieser Waggon gehört @1, Sie dürfen ihn nicht abbauen."
+
+#: advtrains/wagons.lua:203
+msgid "The wagon's inventory is not empty."
+msgstr "Das Inventar dieses Waggons ist nicht leer."
+
+#: advtrains/wagons.lua:210
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr "Der Waggon muss abgekoppelt sein, damit Sie ihn abbauen können."
+
+#: advtrains/wagons.lua:216
+msgid ""
+"Warning: If you destroy this wagon, you only get some steel back! If you are "
+"sure, hold Sneak and left-click the wagon."
+msgstr ""
+"Warnung: Durch den Abbau des Waggons erhalten Sie nur etwas Stahl zurück. "
+"Nutzen Sie Schleichen+Linksklick, um dem Waggon abzubauen."
+
+#: advtrains/wagons.lua:649 advtrains/wagons.lua:850
+msgid "Show Inventory"
+msgstr "Inventar Zeigen"
+
+#: advtrains/wagons.lua:652
+msgid "Onboard Computer"
+msgstr ""
+
+#: advtrains/wagons.lua:655 advtrains/wagons.lua:1328
+msgid "Wagon properties"
+msgstr "Waggon-Einstellungen"
+
+#: advtrains/wagons.lua:658
+msgid "Get off"
+msgstr "Aussteigen"
+
+#: advtrains/wagons.lua:661
+msgid "Get off (forced)"
+msgstr "Ausstieg zwingen"
+
+#: advtrains/wagons.lua:663
+msgid "(Doors closed)"
+msgstr "(Türen geschlossen)"
+
+#: advtrains/wagons.lua:692
+msgid "This wagon has no seats."
+msgstr "In diesem Waggon ist kein Sitzplatz vorhanden."
+
+#: advtrains/wagons.lua:703
+msgid "This wagon is full."
+msgstr "Der Waggon ist voll."
+
+#: advtrains/wagons.lua:706
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr "Die Türen sind geschlossen."
+
+#: advtrains/wagons.lua:712
+msgid "You can't get on this wagon."
+msgstr "Sie können nicht in diesen Waggon einsteigen."
+
+#: advtrains/wagons.lua:838
+msgid "Select seat:"
+msgstr "Wählen Sie einen Sitzplatz aus:"
+
+#: advtrains/wagons.lua:880
+msgid "Save wagon properties"
+msgstr "Waggon-Einstellungen speichern"
+
+#: advtrains/wagons.lua:965
+msgid "Text displayed outside on train"
+msgstr "Äußere Anzeige"
+
+#: advtrains/wagons.lua:966
+msgid "Text displayed inside train"
+msgstr "Innere Anzeige"
+
+#: advtrains/wagons.lua:967
+msgid "Line"
+msgstr "Linie"
+
+#: advtrains/wagons.lua:968
+msgid "Routingcode"
+msgstr ""
+
+#: advtrains/wagons.lua:1241
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr ""
+"Die Türen sind geschlossen. Nutzen Sie Schleichen+Rechtsklick, um trotz "
+"geschlossener Türen auszusteigen."
+
+#: advtrains/wagons.lua:1250
+msgid "You are not allowed to access the driver stand."
+msgstr "Sie haben keinen Zugang zum Führerstand."
+
+#: advtrains_interlocking/tsr_rail.lua:13
+msgid "Point speed restriction: @1"
+msgstr "Geschwindigkeitskontrolle: @1"
+
+#: advtrains_interlocking/tsr_rail.lua:14
+msgid "Set point speed restriction:"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:30
+msgid "You are not allowed to configure this track without the @1 privilege."
+msgstr "Sie dürfen ohne das „@1“-Privileg dieses Gleis nicht konfigurieren."
+
+#: advtrains_interlocking/tsr_rail.lua:34
+#: advtrains_line_automation/stoprail.lua:31
+#: advtrains_line_automation/stoprail.lua:76
+msgid "You are not allowed to configure this track."
+msgstr "Sie dürfen dieses Gleis nicht konfigurieren."
+
+#: advtrains_interlocking/tsr_rail.lua:64
+msgid "Point Speed Restriction Track"
+msgstr "Geschwindigkeitskontrollgleis"
+
+#: advtrains_line_automation/stoprail.lua:54
+msgid "Station Code"
+msgstr "Kennzeichen der Haltestelle"
+
+#: advtrains_line_automation/stoprail.lua:55
+msgid "Station Name"
+msgstr "Name der Haltestelle"
+
+#: advtrains_line_automation/stoprail.lua:56
+msgid "Door Delay"
+msgstr "Zeit für die Türschließung"
+
+#: advtrains_line_automation/stoprail.lua:57
+msgid "Dep. Speed"
+msgstr "Zielgeschwindigkeit bei Abfahrt"
+
+#: advtrains_line_automation/stoprail.lua:58 advtrains_train_track/init.lua:11
+#: advtrains_train_track/init.lua:156
+msgid "Track"
+msgstr "Gleis"
+
+#: advtrains_line_automation/stoprail.lua:59
+msgid "Stop Time"
+msgstr "Wartezeit"
+
+#: advtrains_line_automation/stoprail.lua:60
+msgid "Door Side"
+msgstr "Türseite"
+
+#: advtrains_line_automation/stoprail.lua:62
+msgid "Reverse train"
+msgstr "Zug Umkehren"
+
+#: advtrains_line_automation/stoprail.lua:63
+msgid "Kick out passengers"
+msgstr "Fahrgäste zum Ausstieg zwingen"
+
+#: advtrains_line_automation/stoprail.lua:97
+msgid "Station code \"@1\" already exists and is owned by @2."
+msgstr ""
+"Die Haltestelle mit dem Kennzeichen „@1“ ist bereits vorhanden und wird von "
+"@2 verwaltet."
+
+#: advtrains_line_automation/stoprail.lua:111
+msgid "This station is owned by @1. You are not allowed to edit its name."
+msgstr ""
+"Diese Haltestelle wird von @1 verwaltet. Sie dürfen sie nicht umbenennen."
+
+#: advtrains_line_automation/stoprail.lua:221
+msgid "Station/Stop Track"
+msgstr "Gleis zur Kennzeichnung einer Haltestelle"
+
+#: advtrains_luaautomation/active_common.lua:17
+msgid "Unconfigured LuaATC component"
+msgstr "Nicht konfiguierter LuaATC-Bauteil"
+
+#: advtrains_luaautomation/active_common.lua:46
+msgid "LuaATC Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:49
+msgid "Clear Local Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:50
+msgid "Code"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:64
+msgid ""
+"You are not allowed to configure this LuaATC component without the @1 "
+"privilege."
+msgstr ""
+"Sie dürfen ohne das „@1“-Privileg diesen LuaATC-Bauteil nicht konfigurieren."
+
+#: advtrains_luaautomation/active_common.lua:94
+msgid "LuaATC component assigned to environment '@1'"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:96
+msgid "LuaATC component assigned to an invalid environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:171
+msgid "LuaATC component with error: @1"
+msgstr "LuaATC-Bauteil mit Fehlermeldung: @1"
+
+#: advtrains_luaautomation/init.lua:13
+msgid ""
+"Can place and configure LuaATC components, including execute potentially "
+"harmful Lua code"
+msgstr ""
+"Kann LuaATC-Bauteile platzieren und konfigurieren (auch evtl. schädliche "
+"Programme ausführen)"
+
+#: advtrains_luaautomation/mesecon_controller.lua:211
+msgid "LuaATC Mesecon Controller"
+msgstr ""
+
+#: advtrains_luaautomation/operation_panel.lua:11
+msgid "LuaATC Operation Panel"
+msgstr ""
+
+#: advtrains_luaautomation/pcnaming.lua:28
+msgid ""
+"Passive Component Naming Tool\n"
+"\n"
+"Right-click to name a passive component."
+msgstr ""
+"PC-Benennungswerkzeug\n"
+"\n"
+"Rechtsklick zur Benennung der passiven Komponente."
+
+#: advtrains_luaautomation/pcnaming.lua:39
+msgid ""
+"You are not allowed to name LuaATC passive components without the @1 "
+"privilege."
+msgstr "Sie dürfen ohne das „@1“ keinen passiven LuaATC-Bauteil benennen."
+
+#: advtrains_luaautomation/pcnaming.lua:62
+msgid "Set name of component (empty to clear)"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:10
+#: advtrains_train_industrial/init.lua:49 advtrains_train_steam/init.lua:20
+#: advtrains_train_steam/init.lua:91
+msgid "Driver Stand (right)"
+msgstr "Führerstand Rechts"
+
+#: advtrains_train_industrial/init.lua:17
+#: advtrains_train_industrial/init.lua:56 advtrains_train_steam/init.lua:14
+#: advtrains_train_steam/init.lua:85
+msgid "Driver Stand (left)"
+msgstr "Führerstand Links"
+
+#: advtrains_train_industrial/init.lua:40
+msgid "Industrial Train Engine"
+msgstr "Industrielle Lokomotive"
+
+#: advtrains_train_industrial/init.lua:79
+msgid "Big Industrial Train Engine"
+msgstr "Große Industrielle Lokomotive"
+
+#: advtrains_train_industrial/init.lua:98
+msgid "Industrial tank wagon"
+msgstr "Tankwaggon"
+
+#: advtrains_train_industrial/init.lua:116
+msgid "Industrial wood wagon"
+msgstr "Holztransportwaggon"
+
+#: advtrains_train_japan/init.lua:4
+msgid "Japanese Train Inter-Wagon Connection"
+msgstr "Waggonzwischenverbindung Japanischer Personenzüge"
+
+#: advtrains_train_japan/init.lua:37
+msgid "Driver stand"
+msgstr "Führerstand"
+
+#: advtrains_train_japan/init.lua:101
+msgid "Japanese Train Engine"
+msgstr "Japanische Personenzug-Lokomotive"
+
+#: advtrains_train_japan/init.lua:176
+msgid "Japanese Train Wagon"
+msgstr "Japanischer Personenzug-Passagierwaggon"
+
+#: advtrains_train_steam/init.lua:75
+msgid "Steam Engine"
+msgstr "Dampflokomotive"
+
+#: advtrains_train_steam/init.lua:159
+msgid "Detailed Steam Engine"
+msgstr "Detaillierte Dampflokomotive"
+
+#: advtrains_train_steam/init.lua:206
+msgid "Passenger Wagon"
+msgstr "Passagierwaggon"
+
+#: advtrains_train_steam/init.lua:226
+msgid "Box Wagon"
+msgstr "Güterwaggon"
+
+#: advtrains_train_subway/init.lua:144
+msgid "Subway Passenger Wagon"
+msgstr "U-Bahn-Waggon"
+
+#: advtrains_train_track/init.lua:31
+msgid "Y-turnout"
+msgstr "Y-Weiche"
+
+#: advtrains_train_track/init.lua:49
+msgid "3-way turnout"
+msgstr "Dreiwegweiche"
+
+#: advtrains_train_track/init.lua:69
+msgid "Perpendicular Diamond Crossing Track"
+msgstr "Kreuzung mit zueinander orthogonalen Gleisen"
+
+#: advtrains_train_track/init.lua:91
+msgid "90+Angle Diamond Crossing Track"
+msgstr "Kreuzung mit einem achsenparallelen Gleis"
+
+#: advtrains_train_track/init.lua:132
+msgid "Diagonal Diamond Crossing Track"
+msgstr "Diagonale Gleiskreuzung"
+
+#: advtrains_train_track/init.lua:179
+msgid "Bumper"
+msgstr "Prellbock"
+
+#: advtrains_train_track/init.lua:201
+msgid "ATC controller"
+msgstr "Zugbeeinflussungsgleis"
+
+#: advtrains_train_track/init.lua:317
+msgid "Unloading Track"
+msgstr "Abladungsgleis"
+
+#: advtrains_train_track/init.lua:342
+msgid "Loading Track"
+msgstr "Beladungsgleis"
+
+#: advtrains_train_track/init.lua:406
+msgid "Detector Rail"
+msgstr "Detektorgleis"
+
+#~ msgid ""
+#~ "ATC controller, mode @1\n"
+#~ "Channel: @2"
+#~ msgstr ""
+#~ "Zugbeeinflussungsgleis in Betriebsart „@1“\n"
+#~ "Kanal: @2"
+
+#~ msgid "Access to @1"
+#~ msgstr "Zugang zu @1"
+
+#~ msgid "Can't get on: wagon full or doors closed!"
+#~ msgstr ""
+#~ "Sie können nicht einsteigen: der Waggon ist voll oder die Türen sind "
+#~ "geschlossen."
+
+#~ msgid "Can't place: protected position!"
+#~ msgstr "Es kann nicht platziert werden: diese Position ist geschützt."
+
+#~ msgid "Default Seat"
+#~ msgstr "Standardsitzplatz"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "Standardsitzplatz (Führerstand)"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "ausrangiertes Gleis, nicht verwenden."
+
+#~ msgid "Lock couples"
+#~ msgstr "Kupplungen sperren"
+
+#~ msgid "Speed:"
+#~ msgstr "Geschw.:"
+
+#~ msgid "Target:"
+#~ msgstr "Zielges.:"
+
+#~ msgid "This position is protected!"
+#~ msgstr "Diese Position ist geschützt!"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr ""
+#~ "Nutzen Sie Schleichen+Rechtsklick, um trotz geschlossener Türen "
+#~ "einzusteigen."
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "Sie dürfen an geschützten Stellen kein Gleis bauen."
+
+#~ msgid ""
+#~ "You need to own at least one neighboring wagon to destroy this couple."
+#~ msgstr ""
+#~ "Sie müssen Besitzer eines angrenzenden Waggons sein, um hier abzukuppeln."
diff --git a/advtrains/po/fr.po b/advtrains/po/fr.po
new file mode 100644
index 0000000..c744d2c
--- /dev/null
+++ b/advtrains/po/fr.po
@@ -0,0 +1,728 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2023-10-09 11:02+0200\n"
+"PO-Revision-Date: 2024-11-02 21:31+0100\n"
+"Last-Translator: Tanavit <tanavit@posto.ovh>\n"
+"Language-Team: French\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.4.2\n"
+
+#: advtrains/atc.lua:109
+msgid "Unconfigured ATC controller"
+msgstr "Controlleur ATC, non-configuré"
+
+#: advtrains/atc.lua:150
+msgid ""
+"ATC controller, mode @1\n"
+"Command: @2"
+msgstr ""
+"Controlleur ATC, mode @1\n"
+"Commande : @2"
+
+#: advtrains/atc.lua:180
+msgid "Command"
+msgstr "Commande"
+
+#: advtrains/atc.lua:184
+msgid "Command (on)"
+msgstr "Commande (marche)"
+
+#: advtrains/atc.lua:187
+msgid "Digiline channel"
+msgstr "Canal Digiline"
+
+#: advtrains/atc.lua:189 advtrains_line_automation/stoprail.lua:65
+#: advtrains_luaautomation/active_common.lua:48
+msgid "Save"
+msgstr "Sauvegarder"
+
+#: advtrains/atc.lua:236
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr ""
+"Attention : Commande ATC de renversement impossible car le train se déplace."
+
+#: advtrains/atc.lua:248
+msgid "ATC Kick command warning: doors are closed."
+msgstr "Avertissement commande ATC Éjecter : portes closes."
+
+#: advtrains/atc.lua:252
+msgid "ATC Kick command warning: train moving."
+msgstr "Avertissement commande ATC Éjecter : train en mouvement."
+
+#: advtrains/atc.lua:322
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "Erreur de syntaxe de commande ATC : instruction \"I\" incomplète : @1"
+
+#: advtrains/atc.lua:385
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "Erreur d'analyse de commande ATC : Commande inconnue : @1"
+
+#: advtrains/copytool.lua:8
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+"Outil de copie/collage de train\n"
+"\n"
+"Clic-Gauche : copie\n"
+"\n"
+"Clic-Droit : collage"
+
+#: advtrains/copytool.lua:29
+msgid "You do not have the @1 privilege."
+msgstr "Vous ne possédez pas le privilège \"@1\"."
+
+#: advtrains/copytool.lua:41
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr "La voie sur laquelle vous tentez de placer le wagon est trop courte."
+
+#: advtrains/copytool.lua:47
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr "Le presse-papier ne peut accéder aux métadonnées. Échec du collage."
+
+#: advtrains/copytool.lua:52 advtrains/copytool.lua:57
+msgid "The clipboard is empty."
+msgstr "Le presse-papier est vide."
+
+#: advtrains/copytool.lua:74
+msgid "Back of train would end up off track, cancelling."
+msgstr "La fin du train serait hors voie : annulation."
+
+#: advtrains/copytool.lua:92
+msgid "No such lua entity."
+msgstr "Pas de telle entité lua."
+
+#: advtrains/copytool.lua:98
+msgid "No such wagon: @1."
+msgstr "Pas de tel wagon : @1."
+
+#: advtrains/copytool.lua:104
+msgid "No such train: @1."
+msgstr "Pas de tel train : @1."
+
+#: advtrains/copytool.lua:176
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr "Le presse-papier ne peut accéder aux métadonnées. Échec de la copie."
+
+#: advtrains/copytool.lua:180
+msgid "Train copied."
+msgstr "Train copié."
+
+#: advtrains/couple.lua:28
+msgid "Buffer and Chain Coupler"
+msgstr "Attelage à tampon et vis"
+
+#: advtrains/couple.lua:29
+msgid "Scharfenberg Coupler"
+msgstr "Attelage Scharfenberg"
+
+#: advtrains/couple.lua:185
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr ""
+"Vous n'êtes pas autorisé à coupler des trains sans le privilège "
+"\"train_operator\"."
+
+#: advtrains/couple.lua:329 advtrains/couple.lua:333
+msgid "<No coupler>"
+msgstr "<Pas de coupleur>"
+
+#: advtrains/couple.lua:334
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr ""
+"Accouplement impossible: les attelages des trains ne concordent pas (@1 et "
+"@2)."
+
+#: advtrains/craft_items.lua:3
+msgid "Boiler"
+msgstr "Chaudière à vapeur"
+
+#: advtrains/craft_items.lua:9
+msgid "Driver's cab"
+msgstr "Cabine de pilotage"
+
+#: advtrains/craft_items.lua:15
+msgid "Wheel"
+msgstr "Roue"
+
+#: advtrains/craft_items.lua:21
+msgid "Chimney"
+msgstr "Cheminée"
+
+#: advtrains/misc_nodes.lua:16
+msgid "@1 Platform (low)"
+msgstr "Quai @1 (bas)"
+
+#: advtrains/misc_nodes.lua:33
+msgid "@1 Platform (high)"
+msgstr "Quai @1 (haut)"
+
+#: advtrains/misc_nodes.lua:59
+msgid "@1 Platform (45 degree)"
+msgstr "Quai @1 (haut, 45°)"
+
+#: advtrains/misc_nodes.lua:81
+msgid "@1 Platform (low, 45 degree)"
+msgstr "Quai @1 (bas, 45°)"
+
+#: advtrains/protection.lua:7
+msgid "Can place, remove and operate trains"
+msgstr "Possibilité de poser, retirer ou opérer les trains"
+
+#: advtrains/protection.lua:12
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+"Possibilité de poser, retirer ou opérer un quelconque train, indépendamment "
+"du propriétaire, de la liste blanche ou de protection"
+
+#: advtrains/protection.lua:18
+msgid "Can place and dig tracks in unprotected areas"
+msgstr "Possibilité de poser ou retirer des voies dans les zones non protégées"
+
+#: advtrains/protection.lua:24
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+"Possibilité d'opérer des embranchements et signaux dans les zones non "
+"protégées"
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr ""
+"Vous ne pouvez pas construire à proximité d'une voie sans le privilège "
+"\"track_builder\" (?)"
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr ""
+"Vous ne pouvez pas construire une voie sans le privilège \"track_builder\"."
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr ""
+"Vous ne pouvez pas construire à proximité d'une voie à cet emplacement "
+"protégé."
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "Vous ne pouvez pas construire une voie à cet emplacement protégé."
+
+#: advtrains/protection.lua:184
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr ""
+"Vous ne pouvez pas actionner les aiguillages ou les signaux (privilège "
+"\"railway_operator\" manquant)"
+
+#: advtrains/signals.lua:63
+msgid "Lampless Signal"
+msgstr "Sémaphore"
+
+#: advtrains/signals.lua:127
+msgid "Signal"
+msgstr "Signal"
+
+#: advtrains/signals.lua:191
+msgid "Wallmounted Signal (left)"
+msgstr "Signal mural (gauche)"
+
+#: advtrains/signals.lua:192
+msgid "Wallmounted Signal (right)"
+msgstr "Signal mural (droit)"
+
+#: advtrains/signals.lua:193
+msgid "Wallmounted Signal (top)"
+msgstr "Signal mural (plafond)"
+
+#: advtrains/signals.lua:281 advtrains/signals.lua:322
+msgid "Andrew's Cross"
+msgstr "Croix de Saint André"
+
+#: advtrains/trackplacer.lua:313
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+"Outil \"Trackworker\"\n"
+"\n"
+"Clic-Gauche : change le type de rail (droit/courbé/aiguillage)\n"
+"\n"
+"Clic-Droit : tourne l'objet"
+
+#: advtrains/trackplacer.lua:340 advtrains/trackplacer.lua:377
+msgid "This node can't be rotated using the trackworker."
+msgstr "Ce nœud ne peut être tourné avec l'outil \"Trackworker\"."
+
+#: advtrains/trackplacer.lua:350
+msgid "This track can not be rotated."
+msgstr "Cette voie ne peut pas être tournée."
+
+#: advtrains/trackplacer.lua:404
+msgid "This node can't be changed using the trackworker."
+msgstr "Ce nœud ne peut être modifié avec l'outil \"Trackworker\"."
+
+#: advtrains/trackplacer.lua:414
+msgid "This track can not be changed."
+msgstr "Cette voie ne peut pas être modifiée."
+
+#: advtrains/tracks.lua:449
+msgid "This track can not be removed."
+msgstr "Cette voie ne peut pas être enlevée."
+
+#: advtrains/tracks.lua:616
+msgid "Position is occupied by a train."
+msgstr "Cet emplacement est occupé par un train."
+
+#: advtrains/tracks.lua:622
+msgid "There's a Track Circuit Break here."
+msgstr "Il y a un \"Track Circuit Break\" ici."
+
+#: advtrains/tracks.lua:626
+msgid "There's a Signal Influence Point here."
+msgstr "Il y a un \"Signal Influence Point\" ici."
+
+#: advtrains/tracks.lua:637
+msgid "@1 Slope"
+msgstr "Pente @1"
+
+#: advtrains/tracks.lua:648 advtrains/tracks.lua:653
+msgid "Can't place slope: not pointing at node."
+msgstr "Placement impossible : ne pointe pas un nœud."
+
+#: advtrains/tracks.lua:658
+msgid "Can't place slope: space occupied."
+msgstr "Placement impossible : espace occupé."
+
+#: advtrains/tracks.lua:711
+msgid "Can't place slope: Not enough slope items left (@1 required)."
+msgstr ""
+"Placement impossible : quantité insuffisante de voie pentue (@1 manquant)."
+
+#: advtrains/tracks.lua:714
+msgid "Can't place slope: There's no slope of length @1."
+msgstr "Placement impossible : il n'y a pas de voie pentue de longueur @1."
+
+#: advtrains/tracks.lua:721
+msgid "Can't place slope: no supporting node at upper end."
+msgstr "Placement impossible : pas de nœud d'appui à l'extrémité supérieure."
+
+#: advtrains/trainhud.lua:305
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+"Franchissement de signal rouge : examinez la situation et inversez le sens "
+"de marche du train."
+
+#: advtrains/wagons.lua:179
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "Ce wagon est la propriété de @1, vous ne pouvez pas le détruire."
+
+#: advtrains/wagons.lua:203
+msgid "The wagon's inventory is not empty."
+msgstr "Le stock de ce wagon n'est pas vide."
+
+#: advtrains/wagons.lua:210
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+"Les wagons doivent être désaccouplés des autres pour pouvoir être détruits."
+
+#: advtrains/wagons.lua:216
+msgid ""
+"Warning: If you destroy this wagon, you only get some steel back! If you are "
+"sure, hold Sneak and left-click the wagon."
+msgstr ""
+"Attention: Si vous détruisez ce wagon, vous ne récupérerez que de la "
+"ferraille ! Si vous êtes sûr de vous, appuyez la touche \"Marcher lentement "
+"(Sneak)\" et Clic-Gauche."
+
+#: advtrains/wagons.lua:649 advtrains/wagons.lua:850
+msgid "Show Inventory"
+msgstr "Montrer le stock"
+
+#: advtrains/wagons.lua:652
+msgid "Onboard Computer"
+msgstr "Ordinateur embarqué"
+
+#: advtrains/wagons.lua:655 advtrains/wagons.lua:1328
+msgid "Wagon properties"
+msgstr "Propriétés du wagon"
+
+#: advtrains/wagons.lua:658
+msgid "Get off"
+msgstr "Débarquer"
+
+#: advtrains/wagons.lua:661
+msgid "Get off (forced)"
+msgstr "Débarquer (de force)"
+
+#: advtrains/wagons.lua:663
+msgid "(Doors closed)"
+msgstr "(Portes closes)"
+
+#: advtrains/wagons.lua:692
+msgid "This wagon has no seats."
+msgstr "Ce wagon n'a pas de siège."
+
+#: advtrains/wagons.lua:703
+msgid "This wagon is full."
+msgstr "Ce wagon est plein."
+
+#: advtrains/wagons.lua:706
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr "Portes closes : (Essayez la \"sneak key\"!\")"
+
+#: advtrains/wagons.lua:712
+msgid "You can't get on this wagon."
+msgstr "Montée impossible dans ce wagon."
+
+#: advtrains/wagons.lua:838
+msgid "Select seat:"
+msgstr "Choisir le siège :"
+
+#: advtrains/wagons.lua:880
+msgid "Save wagon properties"
+msgstr "Sauvegarder les propriétés du wagon"
+
+#: advtrains/wagons.lua:965
+msgid "Text displayed outside on train"
+msgstr "Texte affiché à l'extérieur du train"
+
+#: advtrains/wagons.lua:966
+msgid "Text displayed inside train"
+msgstr "Texte affiché à l'intérieur du train"
+
+#: advtrains/wagons.lua:967
+msgid "Line"
+msgstr "Ligne"
+
+#: advtrains/wagons.lua:968
+msgid "Routingcode"
+msgstr "Code de routage"
+
+#: advtrains/wagons.lua:1241
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr ""
+"Portes closes ! Utilisez \"Marcher lentement (Sneak)\" et Clic-Droit pour "
+"franchir les portes et débarquer."
+
+#: advtrains/wagons.lua:1250
+msgid "You are not allowed to access the driver stand."
+msgstr "Accès interdit au poste de pilotage."
+
+#: advtrains_interlocking/tsr_rail.lua:13
+msgid "Point speed restriction: @1"
+msgstr "Point de limitation de vitesse : @1"
+
+#: advtrains_interlocking/tsr_rail.lua:14
+msgid "Set point speed restriction:"
+msgstr "Placez un point de limitation de vitesse :"
+
+#: advtrains_interlocking/tsr_rail.lua:30
+msgid "You are not allowed to configure this track without the @1 privilege."
+msgstr "Vous n'êtes pas autorisé à configurer cette voie sans le privilège @1."
+
+#: advtrains_interlocking/tsr_rail.lua:34
+#: advtrains_line_automation/stoprail.lua:31
+#: advtrains_line_automation/stoprail.lua:76
+msgid "You are not allowed to configure this track."
+msgstr "Vous n'êtes pas autorisé à configurer cette voie."
+
+#: advtrains_interlocking/tsr_rail.lua:64
+msgid "Point Speed Restriction Track"
+msgstr "Voie de point de limitation de vitesse"
+
+#: advtrains_line_automation/stoprail.lua:54
+msgid "Station Code"
+msgstr "Code de Station"
+
+#: advtrains_line_automation/stoprail.lua:55
+msgid "Station Name"
+msgstr "Nom de Station"
+
+#: advtrains_line_automation/stoprail.lua:56
+msgid "Door Delay"
+msgstr "Durée d'ouverture des portes"
+
+#: advtrains_line_automation/stoprail.lua:57
+msgid "Dep. Speed"
+msgstr "Vitesse de départ"
+
+#: advtrains_line_automation/stoprail.lua:58 advtrains_train_track/init.lua:11
+#: advtrains_train_track/init.lua:156
+msgid "Track"
+msgstr "Voie"
+
+#: advtrains_line_automation/stoprail.lua:59
+msgid "Stop Time"
+msgstr "Durée d'arrêt"
+
+#: advtrains_line_automation/stoprail.lua:60
+msgid "Door Side"
+msgstr "Coté d'ouvertures des portes"
+
+#: advtrains_line_automation/stoprail.lua:62
+msgid "Reverse train"
+msgstr "Inversion du sens de marche"
+
+#: advtrains_line_automation/stoprail.lua:63
+msgid "Kick out passengers"
+msgstr "Éjecter les passagers"
+
+#: advtrains_line_automation/stoprail.lua:97
+msgid "Station code \"@1\" already exists and is owned by @2."
+msgstr "Le code de station \"@1\" existe et est possédé par @2."
+
+#: advtrains_line_automation/stoprail.lua:111
+msgid "This station is owned by @1. You are not allowed to edit its name."
+msgstr ""
+"Cette station est la propriété de @1. Vous n'êtes pas autorisé à modifier "
+"son nom."
+
+#: advtrains_line_automation/stoprail.lua:221
+msgid "Station/Stop Track"
+msgstr "Voie d'arrêt en station"
+
+#: advtrains_luaautomation/active_common.lua:17
+msgid "Unconfigured LuaATC component"
+msgstr "Composant LuaATC non configuré"
+
+#: advtrains_luaautomation/active_common.lua:46
+msgid "LuaATC Environment"
+msgstr "Environnement LuaATC"
+
+#: advtrains_luaautomation/active_common.lua:49
+msgid "Clear Local Environment"
+msgstr "Effacer l'environnement LuaATC"
+
+#: advtrains_luaautomation/active_common.lua:50
+msgid "Code"
+msgstr "Code"
+
+#: advtrains_luaautomation/active_common.lua:64
+msgid ""
+"You are not allowed to configure this LuaATC component without the @1 "
+"privilege."
+msgstr "Vous ne pouvez configurer ce composant LuaATC sans le privilege @1."
+
+#: advtrains_luaautomation/active_common.lua:94
+msgid "LuaATC component assigned to environment '@1'"
+msgstr "Composant LuaATC assigné à l'environnement '@1'"
+
+#: advtrains_luaautomation/active_common.lua:96
+msgid "LuaATC component assigned to an invalid environment"
+msgstr "Composant LuaATC assigné à un environnement invalide"
+
+#: advtrains_luaautomation/active_common.lua:171
+msgid "LuaATC component with error: @1"
+msgstr "Erreur @1 du composant LuaATC"
+
+#: advtrains_luaautomation/init.lua:13
+msgid ""
+"Can place and configure LuaATC components, including execute potentially "
+"harmful Lua code"
+msgstr ""
+"Permet le placement et la configuration de composants LuaATC avec risque "
+"d'exécution de code Lua dangereux"
+
+#: advtrains_luaautomation/mesecon_controller.lua:211
+msgid "LuaATC Mesecon Controller"
+msgstr "Commande Mesecon de LuaATC"
+
+#: advtrains_luaautomation/operation_panel.lua:11
+msgid "LuaATC Operation Panel"
+msgstr "Panneau de commande de LuaATC"
+
+#: advtrains_luaautomation/pcnaming.lua:28
+msgid ""
+"Passive Component Naming Tool\n"
+"\n"
+"Right-click to name a passive component."
+msgstr ""
+"Outil de nommage de composant passif\n"
+"\n"
+"Clic-Droit pour nommer un composant passif."
+
+#: advtrains_luaautomation/pcnaming.lua:39
+msgid ""
+"You are not allowed to name LuaATC passive components without the @1 "
+"privilege."
+msgstr "Vous ne pouvez nommer un composant LuaATC passif sans le privilege @1."
+
+#: advtrains_luaautomation/pcnaming.lua:62
+msgid "Set name of component (empty to clear)"
+msgstr "Nommer le composant (chaîne vide pour effacer)"
+
+#: advtrains_train_industrial/init.lua:10
+#: advtrains_train_industrial/init.lua:49 advtrains_train_steam/init.lua:20
+#: advtrains_train_steam/init.lua:91
+msgid "Driver Stand (right)"
+msgstr "Poste de pilotage (droit)"
+
+#: advtrains_train_industrial/init.lua:17
+#: advtrains_train_industrial/init.lua:56 advtrains_train_steam/init.lua:14
+#: advtrains_train_steam/init.lua:85
+msgid "Driver Stand (left)"
+msgstr "Poste de pilotage (gauche)"
+
+#: advtrains_train_industrial/init.lua:40
+msgid "Industrial Train Engine"
+msgstr "Locomotive industrielle"
+
+#: advtrains_train_industrial/init.lua:79
+msgid "Big Industrial Train Engine"
+msgstr "Grosse locomotive industrielle"
+
+#: advtrains_train_industrial/init.lua:98
+msgid "Industrial tank wagon"
+msgstr "Wagon-citerne industriel"
+
+#: advtrains_train_industrial/init.lua:116
+msgid "Industrial wood wagon"
+msgstr "Wagon grumier industriel"
+
+#: advtrains_train_japan/init.lua:4
+msgid "Japanese Train Inter-Wagon Connection"
+msgstr "Passage inter-voiture de train Japonais"
+
+#: advtrains_train_japan/init.lua:37
+msgid "Driver stand"
+msgstr "Poste de pilotage"
+
+#: advtrains_train_japan/init.lua:101
+msgid "Japanese Train Engine"
+msgstr "Motrice Japonaise"
+
+#: advtrains_train_japan/init.lua:176
+msgid "Japanese Train Wagon"
+msgstr "Voiture Japonaise"
+
+#: advtrains_train_steam/init.lua:75
+msgid "Steam Engine"
+msgstr "Locomotive à vapeur"
+
+#: advtrains_train_steam/init.lua:159
+msgid "Detailed Steam Engine"
+msgstr "Locomotive à vapeur complexe"
+
+#: advtrains_train_steam/init.lua:206
+msgid "Passenger Wagon"
+msgstr "Voiture passager"
+
+#: advtrains_train_steam/init.lua:226
+msgid "Box Wagon"
+msgstr "Wagon de frêt"
+
+#: advtrains_train_subway/init.lua:144
+msgid "Subway Passenger Wagon"
+msgstr "Voiture de Métropolitain"
+
+#: advtrains_train_track/init.lua:31
+msgid "Y-turnout"
+msgstr "Embranchement en Y"
+
+#: advtrains_train_track/init.lua:49
+msgid "3-way turnout"
+msgstr "Embranchement triple"
+
+#: advtrains_train_track/init.lua:69
+msgid "Perpendicular Diamond Crossing Track"
+msgstr "Croisement perpendiculaire"
+
+#: advtrains_train_track/init.lua:91
+msgid "90+Angle Diamond Crossing Track"
+msgstr "Croisement perpendiculo-diagonal"
+
+#: advtrains_train_track/init.lua:132
+msgid "Diagonal Diamond Crossing Track"
+msgstr "Croisement diagonal"
+
+#: advtrains_train_track/init.lua:179
+msgid "Bumper"
+msgstr "Heurtoir"
+
+#: advtrains_train_track/init.lua:201
+msgid "ATC controller"
+msgstr "Controlleur ATC"
+
+#: advtrains_train_track/init.lua:317
+msgid "Unloading Track"
+msgstr "Voie de Déchargement"
+
+#: advtrains_train_track/init.lua:342
+msgid "Loading Track"
+msgstr "Voie de Chargement"
+
+#: advtrains_train_track/init.lua:406
+msgid "Detector Rail"
+msgstr "Voie détectrice"
+
+#~ msgid ""
+#~ "ATC controller, mode @1\n"
+#~ "Channel: @2"
+#~ msgstr ""
+#~ "Controlleur ATC, mode @1\n"
+#~ "Canal : @2"
+
+#~ msgid "Access to @1"
+#~ msgstr "Accès à @1"
+
+#~ msgid "Can't get on: wagon full or doors closed!"
+#~ msgstr ""
+#~ "Embarquement impossible : le wagon est plein ou ses portes sont closes !"
+
+#~ msgid "Can't place: protected position!"
+#~ msgstr "Placement impossible : emplacement protégé"
+
+#~ msgid "Default Seat"
+#~ msgstr "Siège par défaut"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "Siège par défaut (poste de pilotage)"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "Voie déconseillée"
+
+#~ msgid "Lock couples"
+#~ msgstr "Verrouiller l'accouplement"
+
+#~ msgid "Speed:"
+#~ msgstr "Vitesse : "
+
+#~ msgid "Target:"
+#~ msgstr "Destination : "
+
+#, fuzzy
+#~ msgid "This node can't be rotated using the trackworker,"
+#~ msgstr "Ce nœud ne peut être tourné avec l'outil \"Trackworker\" !"
+
+#~ msgid "This position is protected!"
+#~ msgstr "Cet emplacement est protégé !"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr ""
+#~ "Utilisez \"Marcher lentement (Sneak)\" et Clic-Droit pour franchir les "
+#~ "portes closes !"
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "Vous ne pouvez pas construire une voie à cet emplacement protégé"
+
+#~ msgid ""
+#~ "You need to own at least one neighboring wagon to destroy this couple."
+#~ msgstr ""
+#~ "Vous devez être propriétaire d'au moins un wagon voisin pour supprimer "
+#~ "cet attelage."
diff --git a/advtrains/po/update-translations.sh b/advtrains/po/update-translations.sh
new file mode 100755
index 0000000..3a56c7c
--- /dev/null
+++ b/advtrains/po/update-translations.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# NOTE: Please make sure you also have basic_trains installed, as it uses attrans for historical reasons
+
+PODIR=`dirname "$0"`
+ATDIR="$PODIR/../.."
+BTDIR="$ATDIR/../basic_trains"
+POTFILE="$PODIR/advtrains.pot"
+
+xgettext \
+ -D "$ATDIR" \
+ -D "$BTDIR" \
+ -d advtrains \
+ -o "$POTFILE" \
+ -p . \
+ -L lua \
+ --from-code=UTF-8 \
+ --sort-by-file \
+ --keyword='attrans' \
+ --keyword='S' \
+ --package-name='advtrains' \
+ --msgid-bugs-address='advtrains-discuss@lists.sr.ht' \
+ `find $ATDIR $BTDIR -name '*.lua' -printf '%P\n'` \
+ &&
+for i in "$PODIR"/*.po; do
+ msgmerge -U \
+ --sort-by-file \
+ $i "$POTFILE"
+done
diff --git a/advtrains/po/zh_CN.po b/advtrains/po/zh_CN.po
new file mode 100644
index 0000000..5bcc316
--- /dev/null
+++ b/advtrains/po/zh_CN.po
@@ -0,0 +1,696 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2023-10-09 11:02+0200\n"
+"PO-Revision-Date: 2023-10-09 11:24+0200\n"
+"Last-Translator: Y. Wang <yw05@forksworld.de>\n"
+"Language-Team: Chinese (Simplified)\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.3.2\n"
+
+#: advtrains/atc.lua:109
+msgid "Unconfigured ATC controller"
+msgstr "ATC 控制器 (未配置)"
+
+#: advtrains/atc.lua:150
+msgid ""
+"ATC controller, mode @1\n"
+"Command: @2"
+msgstr ""
+"ATC 控制器\n"
+"模式:@1\n"
+"命令:@2"
+
+#: advtrains/atc.lua:180
+msgid "Command"
+msgstr "命令"
+
+#: advtrains/atc.lua:184
+msgid "Command (on)"
+msgstr "命令 (激活时)"
+
+#: advtrains/atc.lua:187
+msgid "Digiline channel"
+msgstr "Digiline 频道"
+
+#: advtrains/atc.lua:189 advtrains_line_automation/stoprail.lua:65
+#: advtrains_luaautomation/active_common.lua:48
+msgid "Save"
+msgstr "保存"
+
+#: advtrains/atc.lua:236
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr "ATC 警告:火车正在移动,无法改变行车方向。"
+
+#: advtrains/atc.lua:248
+msgid "ATC Kick command warning: doors are closed."
+msgstr "ATC 警告:车门已关闭,无法踢出乘客。"
+
+#: advtrains/atc.lua:252
+msgid "ATC Kick command warning: train moving."
+msgstr "ATC 警告:火车正在移动,无法踢出乘客。"
+
+#: advtrains/atc.lua:322
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "ATC 语法错误:“I”命令不完整:@1"
+
+#: advtrains/atc.lua:385
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "ATC 语法错误:未知命令:@1"
+
+#: advtrains/copytool.lua:8
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+"火车复制工具\n"
+"\n"
+"左键单击:复制\n"
+"右键单击:粘帖"
+
+#: advtrains/copytool.lua:29
+msgid "You do not have the @1 privilege."
+msgstr "您没有“@1”权限。"
+
+#: advtrains/copytool.lua:41
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr "轨道太短。"
+
+#: advtrains/copytool.lua:47
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr "无法粘贴:剪贴板无法访问元数据。"
+
+#: advtrains/copytool.lua:52 advtrains/copytool.lua:57
+msgid "The clipboard is empty."
+msgstr "剪贴板是空的。"
+
+#: advtrains/copytool.lua:74
+msgid "Back of train would end up off track, cancelling."
+msgstr "火车后部不在轨道上。"
+
+#: advtrains/copytool.lua:92
+msgid "No such lua entity."
+msgstr "您没有指向一个可以用火车复制工具复制的物体。"
+
+#: advtrains/copytool.lua:98
+msgid "No such wagon: @1."
+msgstr "ID 为“@1”的车厢不存在。"
+
+#: advtrains/copytool.lua:104
+msgid "No such train: @1."
+msgstr "ID 为“@1”的列车不存在。"
+
+#: advtrains/copytool.lua:176
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr "无法复制:剪贴板无法访问元数据。"
+
+#: advtrains/copytool.lua:180
+msgid "Train copied."
+msgstr "已复制列车。"
+
+#: advtrains/couple.lua:28
+msgid "Buffer and Chain Coupler"
+msgstr "链式车钩"
+
+#: advtrains/couple.lua:29
+msgid "Scharfenberg Coupler"
+msgstr "Scharfenberg 式车钩"
+
+#: advtrains/couple.lua:185
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr "您没有“train_operator”权限,不能连接这两节车厢。"
+
+#: advtrains/couple.lua:329 advtrains/couple.lua:333
+msgid "<No coupler>"
+msgstr "<没有车钩>"
+
+#: advtrains/couple.lua:334
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr "您无法连接这两节车厢:这两节车厢使用不同的车钩 (@1和@2)。"
+
+#: advtrains/craft_items.lua:3
+msgid "Boiler"
+msgstr "锅炉"
+
+#: advtrains/craft_items.lua:9
+msgid "Driver's cab"
+msgstr "驾驶室"
+
+#: advtrains/craft_items.lua:15
+msgid "Wheel"
+msgstr "车轮"
+
+#: advtrains/craft_items.lua:21
+msgid "Chimney"
+msgstr "烟囱"
+
+#: advtrains/misc_nodes.lua:16
+msgid "@1 Platform (low)"
+msgstr "较低的@1站台"
+
+#: advtrains/misc_nodes.lua:33
+msgid "@1 Platform (high)"
+msgstr "较高的@1站台"
+
+#: advtrains/misc_nodes.lua:59
+msgid "@1 Platform (45 degree)"
+msgstr "较高的@1站台 (45°)"
+
+#: advtrains/misc_nodes.lua:81
+msgid "@1 Platform (low, 45 degree)"
+msgstr "较低的@1站台 (45°)"
+
+#: advtrains/protection.lua:7
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: advtrains/protection.lua:12
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: advtrains/protection.lua:18
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:24
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr "您没有“train_operator”权限,不能在铁路附近建任何东西。"
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr "您没有“train_operator”权限,不能在这里建造铁路。"
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr "这里已被保护,您不能在这里的铁路附近建任何东西。"
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "这里已被保护,您不能在这里建造铁路。"
+
+#: advtrains/protection.lua:184
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr "您没有“railway_operator”权限,不能控制铁路设施。"
+
+#: advtrains/signals.lua:63
+msgid "Lampless Signal"
+msgstr "臂板信号机"
+
+#: advtrains/signals.lua:127
+msgid "Signal"
+msgstr "信号灯"
+
+#: advtrains/signals.lua:191
+msgid "Wallmounted Signal (left)"
+msgstr "壁挂式信号灯 (左侧)"
+
+#: advtrains/signals.lua:192
+msgid "Wallmounted Signal (right)"
+msgstr "壁挂式信号灯 (右侧)"
+
+#: advtrains/signals.lua:193
+msgid "Wallmounted Signal (top)"
+msgstr "悬挂式信号灯"
+
+#: advtrains/signals.lua:281 advtrains/signals.lua:322
+msgid "Andrew's Cross"
+msgstr "铁路道口信号灯"
+
+#: advtrains/trackplacer.lua:313
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+"铁路调整工具\n"
+"\n"
+"左键单击:切换轨道类型\n"
+"右键单击:旋转方块"
+
+#: advtrains/trackplacer.lua:340 advtrains/trackplacer.lua:377
+msgid "This node can't be rotated using the trackworker."
+msgstr "您不能使用铁路调整工具旋转这个方块。"
+
+#: advtrains/trackplacer.lua:350
+msgid "This track can not be rotated."
+msgstr "您不能旋转这段轨道。"
+
+#: advtrains/trackplacer.lua:404
+msgid "This node can't be changed using the trackworker."
+msgstr "您不能使用铁路调整工具调整这个方块。"
+
+#: advtrains/trackplacer.lua:414
+msgid "This track can not be changed."
+msgstr "您不能调整这段轨道。"
+
+#: advtrains/tracks.lua:449
+msgid "This track can not be removed."
+msgstr "您不能移除这段轨道。"
+
+#: advtrains/tracks.lua:616
+msgid "Position is occupied by a train."
+msgstr ""
+
+#: advtrains/tracks.lua:622
+msgid "There's a Track Circuit Break here."
+msgstr ""
+
+#: advtrains/tracks.lua:626
+msgid "There's a Signal Influence Point here."
+msgstr ""
+
+#: advtrains/tracks.lua:637
+msgid "@1 Slope"
+msgstr "@1斜坡"
+
+#: advtrains/tracks.lua:648 advtrains/tracks.lua:653
+msgid "Can't place slope: not pointing at node."
+msgstr "无法放置斜坡:您没有选择任何方块。"
+
+#: advtrains/tracks.lua:658
+msgid "Can't place slope: space occupied."
+msgstr "无法放置斜坡:此区域已被占用。"
+
+#: advtrains/tracks.lua:711
+msgid "Can't place slope: Not enough slope items left (@1 required)."
+msgstr "无法放置斜坡:您没有足够的铁路斜坡放置工具 (您总共需要@1个)"
+
+#: advtrains/tracks.lua:714
+msgid "Can't place slope: There's no slope of length @1."
+msgstr "无法放置斜坡:advtrains 不支持长度为@1米的斜坡。"
+
+#: advtrains/tracks.lua:721
+msgid "Can't place slope: no supporting node at upper end."
+msgstr "无法放置斜坡:较高端没有支撑方块。"
+
+#: advtrains/trainhud.lua:305
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: advtrains/wagons.lua:179
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "这是 @1 的车厢,您不能摧毁它。"
+
+#: advtrains/wagons.lua:203
+msgid "The wagon's inventory is not empty."
+msgstr ""
+
+#: advtrains/wagons.lua:210
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+
+#: advtrains/wagons.lua:216
+msgid ""
+"Warning: If you destroy this wagon, you only get some steel back! If you are "
+"sure, hold Sneak and left-click the wagon."
+msgstr ""
+"警告:如果您摧毁此车厢,您只能拿到一些钢方块。如果您确定要摧毁这节车厢,请按"
+"潜行键并左键单击此车厢。"
+
+#: advtrains/wagons.lua:649 advtrains/wagons.lua:850
+msgid "Show Inventory"
+msgstr "显示物品栏"
+
+#: advtrains/wagons.lua:652
+msgid "Onboard Computer"
+msgstr ""
+
+#: advtrains/wagons.lua:655 advtrains/wagons.lua:1328
+msgid "Wagon properties"
+msgstr "车厢属性"
+
+#: advtrains/wagons.lua:658
+msgid "Get off"
+msgstr "下车"
+
+#: advtrains/wagons.lua:661
+msgid "Get off (forced)"
+msgstr "强制下车"
+
+#: advtrains/wagons.lua:663
+msgid "(Doors closed)"
+msgstr "(车门已关闭)"
+
+#: advtrains/wagons.lua:692
+msgid "This wagon has no seats."
+msgstr "这节车厢没有座位。"
+
+#: advtrains/wagons.lua:703
+msgid "This wagon is full."
+msgstr "车厢已满。"
+
+#: advtrains/wagons.lua:706
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr ""
+
+#: advtrains/wagons.lua:712
+msgid "You can't get on this wagon."
+msgstr ""
+
+#: advtrains/wagons.lua:838
+msgid "Select seat:"
+msgstr "请选择座位:"
+
+#: advtrains/wagons.lua:880
+msgid "Save wagon properties"
+msgstr "保存车厢属性"
+
+#: advtrains/wagons.lua:965
+msgid "Text displayed outside on train"
+msgstr "车厢外部显示"
+
+#: advtrains/wagons.lua:966
+msgid "Text displayed inside train"
+msgstr "车厢内部显示"
+
+#: advtrains/wagons.lua:967
+msgid "Line"
+msgstr "火车线路"
+
+#: advtrains/wagons.lua:968
+msgid "Routingcode"
+msgstr "路由码"
+
+#: advtrains/wagons.lua:1241
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr "车门已关闭,请使用潜行+右键单击下车。"
+
+#: advtrains/wagons.lua:1250
+msgid "You are not allowed to access the driver stand."
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:13
+msgid "Point speed restriction: @1"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:14
+msgid "Set point speed restriction:"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:30
+msgid "You are not allowed to configure this track without the @1 privilege."
+msgstr "您没有“@1”权限,不能调整这段轨道。"
+
+#: advtrains_interlocking/tsr_rail.lua:34
+#: advtrains_line_automation/stoprail.lua:31
+#: advtrains_line_automation/stoprail.lua:76
+msgid "You are not allowed to configure this track."
+msgstr "您不能调整这段轨道。"
+
+#: advtrains_interlocking/tsr_rail.lua:64
+msgid "Point Speed Restriction Track"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:54
+msgid "Station Code"
+msgstr "车站代码"
+
+#: advtrains_line_automation/stoprail.lua:55
+msgid "Station Name"
+msgstr "车站名称"
+
+#: advtrains_line_automation/stoprail.lua:56
+msgid "Door Delay"
+msgstr "车门关闭时间"
+
+#: advtrains_line_automation/stoprail.lua:57
+msgid "Dep. Speed"
+msgstr "出发速度"
+
+#: advtrains_line_automation/stoprail.lua:58 advtrains_train_track/init.lua:11
+#: advtrains_train_track/init.lua:156
+msgid "Track"
+msgstr "轨道"
+
+#: advtrains_line_automation/stoprail.lua:59
+msgid "Stop Time"
+msgstr "停站时间"
+
+#: advtrains_line_automation/stoprail.lua:60
+msgid "Door Side"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:62
+msgid "Reverse train"
+msgstr "改变行车方向"
+
+#: advtrains_line_automation/stoprail.lua:63
+msgid "Kick out passengers"
+msgstr "踢出乘客"
+
+#: advtrains_line_automation/stoprail.lua:97
+msgid "Station code \"@1\" already exists and is owned by @2."
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:111
+msgid "This station is owned by @1. You are not allowed to edit its name."
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:221
+msgid "Station/Stop Track"
+msgstr "车站轨道"
+
+#: advtrains_luaautomation/active_common.lua:17
+msgid "Unconfigured LuaATC component"
+msgstr "LuaATC 部件 (未配置)"
+
+#: advtrains_luaautomation/active_common.lua:46
+msgid "LuaATC Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:49
+msgid "Clear Local Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:50
+msgid "Code"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:64
+msgid ""
+"You are not allowed to configure this LuaATC component without the @1 "
+"privilege."
+msgstr "您没有“@1”权限,不能配置这个 LuaATC 部件。"
+
+#: advtrains_luaautomation/active_common.lua:94
+msgid "LuaATC component assigned to environment '@1'"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:96
+msgid "LuaATC component assigned to an invalid environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:171
+msgid "LuaATC component with error: @1"
+msgstr ""
+
+#: advtrains_luaautomation/init.lua:13
+msgid ""
+"Can place and configure LuaATC components, including execute potentially "
+"harmful Lua code"
+msgstr ""
+
+#: advtrains_luaautomation/mesecon_controller.lua:211
+msgid "LuaATC Mesecon Controller"
+msgstr ""
+
+#: advtrains_luaautomation/operation_panel.lua:11
+msgid "LuaATC Operation Panel"
+msgstr ""
+
+#: advtrains_luaautomation/pcnaming.lua:28
+msgid ""
+"Passive Component Naming Tool\n"
+"\n"
+"Right-click to name a passive component."
+msgstr ""
+"被动元件命名工具\n"
+"\n"
+"右键单击命名所选元件。"
+
+#: advtrains_luaautomation/pcnaming.lua:39
+msgid ""
+"You are not allowed to name LuaATC passive components without the @1 "
+"privilege."
+msgstr "您没有“@1”权限,不能命名被动元件。"
+
+#: advtrains_luaautomation/pcnaming.lua:62
+msgid "Set name of component (empty to clear)"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:10
+#: advtrains_train_industrial/init.lua:49 advtrains_train_steam/init.lua:20
+#: advtrains_train_steam/init.lua:91
+msgid "Driver Stand (right)"
+msgstr "右侧司机座位"
+
+#: advtrains_train_industrial/init.lua:17
+#: advtrains_train_industrial/init.lua:56 advtrains_train_steam/init.lua:14
+#: advtrains_train_steam/init.lua:85
+msgid "Driver Stand (left)"
+msgstr "左侧司机座位"
+
+#: advtrains_train_industrial/init.lua:40
+msgid "Industrial Train Engine"
+msgstr "工业用火车头"
+
+#: advtrains_train_industrial/init.lua:79
+msgid "Big Industrial Train Engine"
+msgstr "大型工业用火车头"
+
+#: advtrains_train_industrial/init.lua:98
+msgid "Industrial tank wagon"
+msgstr "液体运输车厢"
+
+#: advtrains_train_industrial/init.lua:116
+msgid "Industrial wood wagon"
+msgstr "木材运输车厢"
+
+#: advtrains_train_japan/init.lua:4
+msgid "Japanese Train Inter-Wagon Connection"
+msgstr "日本火车车钩"
+
+#: advtrains_train_japan/init.lua:37
+msgid "Driver stand"
+msgstr "司机座位"
+
+#: advtrains_train_japan/init.lua:101
+msgid "Japanese Train Engine"
+msgstr "高速列车车头"
+
+#: advtrains_train_japan/init.lua:176
+msgid "Japanese Train Wagon"
+msgstr "高速列车车厢"
+
+#: advtrains_train_steam/init.lua:75
+msgid "Steam Engine"
+msgstr "蒸汽机车"
+
+#: advtrains_train_steam/init.lua:159
+msgid "Detailed Steam Engine"
+msgstr "精细的蒸汽机车"
+
+#: advtrains_train_steam/init.lua:206
+msgid "Passenger Wagon"
+msgstr "客车"
+
+#: advtrains_train_steam/init.lua:226
+msgid "Box Wagon"
+msgstr "货运车厢"
+
+#: advtrains_train_subway/init.lua:144
+msgid "Subway Passenger Wagon"
+msgstr "地铁车厢"
+
+#: advtrains_train_track/init.lua:31
+msgid "Y-turnout"
+msgstr "对称道岔"
+
+#: advtrains_train_track/init.lua:49
+msgid "3-way turnout"
+msgstr "三开道岔"
+
+#: advtrains_train_track/init.lua:69
+msgid "Perpendicular Diamond Crossing Track"
+msgstr "垂直交叉轨道"
+
+#: advtrains_train_track/init.lua:91
+msgid "90+Angle Diamond Crossing Track"
+msgstr "交叉轨道 (其中一条轨道与坐标轴平行)"
+
+#: advtrains_train_track/init.lua:132
+msgid "Diagonal Diamond Crossing Track"
+msgstr "交叉轨道"
+
+#: advtrains_train_track/init.lua:179
+msgid "Bumper"
+msgstr "保险杠"
+
+#: advtrains_train_track/init.lua:201
+msgid "ATC controller"
+msgstr "ATC 控制器"
+
+#: advtrains_train_track/init.lua:317
+msgid "Unloading Track"
+msgstr "卸货轨道"
+
+#: advtrains_train_track/init.lua:342
+msgid "Loading Track"
+msgstr "装货轨道"
+
+#: advtrains_train_track/init.lua:406
+msgid "Detector Rail"
+msgstr "探测轨道"
+
+#~ msgid ""
+#~ "ATC controller, mode @1\n"
+#~ "Channel: @2"
+#~ msgstr ""
+#~ "ATC 控制器\n"
+#~ "模式:@1\n"
+#~ "频道:@2"
+
+#~ msgid "Access to @1"
+#~ msgstr "可前往@1"
+
+#~ msgid "Can't get on: wagon full or doors closed!"
+#~ msgstr "无法上车:车门已关闭或车厢已满。"
+
+#~ msgid "Can't place: protected position!"
+#~ msgstr "无法放置:此区域已被保护。"
+
+#~ msgid "Default Seat"
+#~ msgstr "默认座位"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "默认座位 (司机座位)"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "请不要使用"
+
+#~ msgid "Lock couples"
+#~ msgstr "锁定连接处"
+
+#~ msgid "Speed:"
+#~ msgstr "速度"
+
+#~ msgid "Target:"
+#~ msgstr "目标速度"
+
+#, fuzzy
+#~ msgid "This node can't be rotated using the trackworker,"
+#~ msgstr "您不能使用铁路调整工具旋转这个方块。"
+
+#~ msgid "This position is protected!"
+#~ msgstr "这里已被保护。"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr "请使用潜行+右键上车。"
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "这里已被保护,您不能在这里建造铁路。"
+
+#~ msgid ""
+#~ "You need to own at least one neighboring wagon to destroy this couple."
+#~ msgstr "您必须至少拥有其中一节车厢才能分开这两节车厢。"
diff --git a/advtrains/po/zh_TW.po b/advtrains/po/zh_TW.po
new file mode 100644
index 0000000..ece82c3
--- /dev/null
+++ b/advtrains/po/zh_TW.po
@@ -0,0 +1,696 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2023-10-09 11:02+0200\n"
+"PO-Revision-Date: 2023-10-09 11:31+0200\n"
+"Last-Translator: Y. Wang <yw05@forksworld.de>\n"
+"Language-Team: Chinese (Traditional)\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.3.2\n"
+
+#: advtrains/atc.lua:109
+msgid "Unconfigured ATC controller"
+msgstr "ATC 控制器 (未配置)"
+
+#: advtrains/atc.lua:150
+msgid ""
+"ATC controller, mode @1\n"
+"Command: @2"
+msgstr ""
+"ATC 控制器\n"
+"模式:@1\n"
+"命令:@2"
+
+#: advtrains/atc.lua:180
+msgid "Command"
+msgstr "命令"
+
+#: advtrains/atc.lua:184
+msgid "Command (on)"
+msgstr "命令 (啟用時)"
+
+#: advtrains/atc.lua:187
+msgid "Digiline channel"
+msgstr "Digiline 頻道"
+
+#: advtrains/atc.lua:189 advtrains_line_automation/stoprail.lua:65
+#: advtrains_luaautomation/active_common.lua:48
+msgid "Save"
+msgstr "儲存"
+
+#: advtrains/atc.lua:236
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr "ATC 警告:火車正在移動,無法改變行車方向。"
+
+#: advtrains/atc.lua:248
+msgid "ATC Kick command warning: doors are closed."
+msgstr "ATC 警告:車門已關閉,無法踢出乘客。"
+
+#: advtrains/atc.lua:252
+msgid "ATC Kick command warning: train moving."
+msgstr "ATC 警告:火車正在移動,無法踢出乘客。"
+
+#: advtrains/atc.lua:322
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "ATC 語法錯誤:「I」命令不完整:@1"
+
+#: advtrains/atc.lua:385
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "ATC 語法錯誤:未知命令:@1"
+
+#: advtrains/copytool.lua:8
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+"火車複製工具\n"
+"\n"
+"左鍵單擊:複製\n"
+"右鍵單擊:粘帖"
+
+#: advtrains/copytool.lua:29
+msgid "You do not have the @1 privilege."
+msgstr "您沒有「@1」許可權。"
+
+#: advtrains/copytool.lua:41
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr "軌道太短。"
+
+#: advtrains/copytool.lua:47
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr "無法貼上:剪貼簿無法訪問元資料。"
+
+#: advtrains/copytool.lua:52 advtrains/copytool.lua:57
+msgid "The clipboard is empty."
+msgstr "剪貼簿是空的。"
+
+#: advtrains/copytool.lua:74
+msgid "Back of train would end up off track, cancelling."
+msgstr "火車後部不在軌道上。"
+
+#: advtrains/copytool.lua:92
+msgid "No such lua entity."
+msgstr "您沒有指向一個可以用火車複製工具複製的物體。"
+
+#: advtrains/copytool.lua:98
+msgid "No such wagon: @1."
+msgstr "ID 為「@1」的車廂不存在。"
+
+#: advtrains/copytool.lua:104
+msgid "No such train: @1."
+msgstr "ID 為「@1」的列車不存在。"
+
+#: advtrains/copytool.lua:176
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr "無法複製:剪貼簿無法訪問元資料。"
+
+#: advtrains/copytool.lua:180
+msgid "Train copied."
+msgstr "已複製火車。"
+
+#: advtrains/couple.lua:28
+msgid "Buffer and Chain Coupler"
+msgstr "鏈式連結器"
+
+#: advtrains/couple.lua:29
+msgid "Scharfenberg Coupler"
+msgstr "Scharfenberg 式連結器"
+
+#: advtrains/couple.lua:185
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr "您沒有「train_operator」許可權,不能連結這兩節車廂。"
+
+#: advtrains/couple.lua:329 advtrains/couple.lua:333
+msgid "<No coupler>"
+msgstr "<無連結器>"
+
+#: advtrains/couple.lua:334
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr "您無法連結這兩節車廂:這兩節車廂使用不同的連結器 (@1和@2)。"
+
+#: advtrains/craft_items.lua:3
+msgid "Boiler"
+msgstr "鍋爐"
+
+#: advtrains/craft_items.lua:9
+msgid "Driver's cab"
+msgstr "駕駛室"
+
+#: advtrains/craft_items.lua:15
+msgid "Wheel"
+msgstr "車輪"
+
+#: advtrains/craft_items.lua:21
+msgid "Chimney"
+msgstr "煙囪"
+
+#: advtrains/misc_nodes.lua:16
+msgid "@1 Platform (low)"
+msgstr "較低的@1月臺"
+
+#: advtrains/misc_nodes.lua:33
+msgid "@1 Platform (high)"
+msgstr "較高的@1月臺"
+
+#: advtrains/misc_nodes.lua:59
+msgid "@1 Platform (45 degree)"
+msgstr "較高的@1月臺 (45°)"
+
+#: advtrains/misc_nodes.lua:81
+msgid "@1 Platform (low, 45 degree)"
+msgstr "較低的@1月臺 (45°)"
+
+#: advtrains/protection.lua:7
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: advtrains/protection.lua:12
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: advtrains/protection.lua:18
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:24
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr "您沒有「train_operator」許可權,不能在鐵路附近建任何東西。"
+
+#: advtrains/protection.lua:148
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr "您沒有「train_operator」許可權,不能在這裡建造鐵路。"
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr "這裡已被保護,您不能在這裡的鐵路附近建任何東西。"
+
+#: advtrains/protection.lua:153
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "這裡已被保護,您不能在這裡建造鐵路。"
+
+#: advtrains/protection.lua:184
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr "您沒有「railway_operator」許可權,不能控制鐵路設施。"
+
+#: advtrains/signals.lua:63
+msgid "Lampless Signal"
+msgstr "臂木式號誌機"
+
+#: advtrains/signals.lua:127
+msgid "Signal"
+msgstr "色燈號誌機"
+
+#: advtrains/signals.lua:191
+msgid "Wallmounted Signal (left)"
+msgstr "壁掛式色燈號誌機 (左側)"
+
+#: advtrains/signals.lua:192
+msgid "Wallmounted Signal (right)"
+msgstr "壁掛式色燈號誌機 (右側)"
+
+#: advtrains/signals.lua:193
+msgid "Wallmounted Signal (top)"
+msgstr "懸掛式色燈號誌機"
+
+#: advtrains/signals.lua:281 advtrains/signals.lua:322
+msgid "Andrew's Cross"
+msgstr "平交道號誌燈"
+
+#: advtrains/trackplacer.lua:313
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+"鐵路調整工具\n"
+"\n"
+"左鍵單擊:切換軌道型別\n"
+"右鍵單擊:旋轉方塊"
+
+#: advtrains/trackplacer.lua:340 advtrains/trackplacer.lua:377
+msgid "This node can't be rotated using the trackworker."
+msgstr "您不能使用鐵路調整工具旋轉這個方塊。"
+
+#: advtrains/trackplacer.lua:350
+msgid "This track can not be rotated."
+msgstr "您不能旋轉這段軌道。"
+
+#: advtrains/trackplacer.lua:404
+msgid "This node can't be changed using the trackworker."
+msgstr "您不能使用鐵路調整工具調整這個方塊。"
+
+#: advtrains/trackplacer.lua:414
+msgid "This track can not be changed."
+msgstr "您不能調整這段軌道。"
+
+#: advtrains/tracks.lua:449
+msgid "This track can not be removed."
+msgstr "您不能移除這段軌道。"
+
+#: advtrains/tracks.lua:616
+msgid "Position is occupied by a train."
+msgstr ""
+
+#: advtrains/tracks.lua:622
+msgid "There's a Track Circuit Break here."
+msgstr ""
+
+#: advtrains/tracks.lua:626
+msgid "There's a Signal Influence Point here."
+msgstr ""
+
+#: advtrains/tracks.lua:637
+msgid "@1 Slope"
+msgstr "@1斜坡"
+
+#: advtrains/tracks.lua:648 advtrains/tracks.lua:653
+msgid "Can't place slope: not pointing at node."
+msgstr "無法放置斜坡:您沒有選擇任何方塊。"
+
+#: advtrains/tracks.lua:658
+msgid "Can't place slope: space occupied."
+msgstr "無法放置斜坡:此區域已被佔用。"
+
+#: advtrains/tracks.lua:711
+msgid "Can't place slope: Not enough slope items left (@1 required)."
+msgstr "無法放置斜坡:您沒有足夠的鐵路斜坡放置工具 (您總共需要@1個)"
+
+#: advtrains/tracks.lua:714
+msgid "Can't place slope: There's no slope of length @1."
+msgstr "無法放置斜坡:advtrains 不支援長度為@1米的斜坡。"
+
+#: advtrains/tracks.lua:721
+msgid "Can't place slope: no supporting node at upper end."
+msgstr "無法放置斜坡:較高階沒有支撐方塊。"
+
+#: advtrains/trainhud.lua:305
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: advtrains/wagons.lua:179
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "這是 @1 的車廂,您不能摧毀它。"
+
+#: advtrains/wagons.lua:203
+msgid "The wagon's inventory is not empty."
+msgstr ""
+
+#: advtrains/wagons.lua:210
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+
+#: advtrains/wagons.lua:216
+msgid ""
+"Warning: If you destroy this wagon, you only get some steel back! If you are "
+"sure, hold Sneak and left-click the wagon."
+msgstr ""
+"警告:如果您摧毀此車廂,您只能拿到一些鋼方塊。如果您確定要摧毀這節車廂,請按"
+"潛行鍵並左鍵單擊此車廂。"
+
+#: advtrains/wagons.lua:649 advtrains/wagons.lua:850
+msgid "Show Inventory"
+msgstr "顯示物品欄"
+
+#: advtrains/wagons.lua:652
+msgid "Onboard Computer"
+msgstr ""
+
+#: advtrains/wagons.lua:655 advtrains/wagons.lua:1328
+msgid "Wagon properties"
+msgstr "車廂屬性"
+
+#: advtrains/wagons.lua:658
+msgid "Get off"
+msgstr "下車"
+
+#: advtrains/wagons.lua:661
+msgid "Get off (forced)"
+msgstr "強制下車"
+
+#: advtrains/wagons.lua:663
+msgid "(Doors closed)"
+msgstr "(車門已關閉)"
+
+#: advtrains/wagons.lua:692
+msgid "This wagon has no seats."
+msgstr "這節車廂沒有座位。"
+
+#: advtrains/wagons.lua:703
+msgid "This wagon is full."
+msgstr "車廂已滿。"
+
+#: advtrains/wagons.lua:706
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr ""
+
+#: advtrains/wagons.lua:712
+msgid "You can't get on this wagon."
+msgstr ""
+
+#: advtrains/wagons.lua:838
+msgid "Select seat:"
+msgstr "請選擇座位:"
+
+#: advtrains/wagons.lua:880
+msgid "Save wagon properties"
+msgstr "儲存車廂屬性"
+
+#: advtrains/wagons.lua:965
+msgid "Text displayed outside on train"
+msgstr "車廂外部顯示"
+
+#: advtrains/wagons.lua:966
+msgid "Text displayed inside train"
+msgstr "車廂內部顯示"
+
+#: advtrains/wagons.lua:967
+msgid "Line"
+msgstr "火車線路"
+
+#: advtrains/wagons.lua:968
+msgid "Routingcode"
+msgstr "路由碼"
+
+#: advtrains/wagons.lua:1241
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr "車門已關閉,請使用潛行+右鍵單擊下車。"
+
+#: advtrains/wagons.lua:1250
+msgid "You are not allowed to access the driver stand."
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:13
+msgid "Point speed restriction: @1"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:14
+msgid "Set point speed restriction:"
+msgstr ""
+
+#: advtrains_interlocking/tsr_rail.lua:30
+msgid "You are not allowed to configure this track without the @1 privilege."
+msgstr "您沒有「@1」許可權,不能調整這段軌道。"
+
+#: advtrains_interlocking/tsr_rail.lua:34
+#: advtrains_line_automation/stoprail.lua:31
+#: advtrains_line_automation/stoprail.lua:76
+msgid "You are not allowed to configure this track."
+msgstr "您不能調整這段軌道。"
+
+#: advtrains_interlocking/tsr_rail.lua:64
+msgid "Point Speed Restriction Track"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:54
+msgid "Station Code"
+msgstr "車站碼"
+
+#: advtrains_line_automation/stoprail.lua:55
+msgid "Station Name"
+msgstr "車站名稱"
+
+#: advtrains_line_automation/stoprail.lua:56
+msgid "Door Delay"
+msgstr "車門關閉時間"
+
+#: advtrains_line_automation/stoprail.lua:57
+msgid "Dep. Speed"
+msgstr "出發速度"
+
+#: advtrains_line_automation/stoprail.lua:58 advtrains_train_track/init.lua:11
+#: advtrains_train_track/init.lua:156
+msgid "Track"
+msgstr "軌道"
+
+#: advtrains_line_automation/stoprail.lua:59
+msgid "Stop Time"
+msgstr "停站時間"
+
+#: advtrains_line_automation/stoprail.lua:60
+msgid "Door Side"
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:62
+msgid "Reverse train"
+msgstr "改變行車方向"
+
+#: advtrains_line_automation/stoprail.lua:63
+msgid "Kick out passengers"
+msgstr "踢出乘客"
+
+#: advtrains_line_automation/stoprail.lua:97
+msgid "Station code \"@1\" already exists and is owned by @2."
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:111
+msgid "This station is owned by @1. You are not allowed to edit its name."
+msgstr ""
+
+#: advtrains_line_automation/stoprail.lua:221
+msgid "Station/Stop Track"
+msgstr "車站軌道"
+
+#: advtrains_luaautomation/active_common.lua:17
+msgid "Unconfigured LuaATC component"
+msgstr "LuaATC 元件 (未配置)"
+
+#: advtrains_luaautomation/active_common.lua:46
+msgid "LuaATC Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:49
+msgid "Clear Local Environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:50
+msgid "Code"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:64
+msgid ""
+"You are not allowed to configure this LuaATC component without the @1 "
+"privilege."
+msgstr "您沒有「@1」許可權,不能配置這個 LuaATC 元件。"
+
+#: advtrains_luaautomation/active_common.lua:94
+msgid "LuaATC component assigned to environment '@1'"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:96
+msgid "LuaATC component assigned to an invalid environment"
+msgstr ""
+
+#: advtrains_luaautomation/active_common.lua:171
+msgid "LuaATC component with error: @1"
+msgstr ""
+
+#: advtrains_luaautomation/init.lua:13
+msgid ""
+"Can place and configure LuaATC components, including execute potentially "
+"harmful Lua code"
+msgstr ""
+
+#: advtrains_luaautomation/mesecon_controller.lua:211
+msgid "LuaATC Mesecon Controller"
+msgstr ""
+
+#: advtrains_luaautomation/operation_panel.lua:11
+msgid "LuaATC Operation Panel"
+msgstr ""
+
+#: advtrains_luaautomation/pcnaming.lua:28
+msgid ""
+"Passive Component Naming Tool\n"
+"\n"
+"Right-click to name a passive component."
+msgstr ""
+"被動元件命名工具\n"
+"\n"
+"右鍵單擊命名所選元件。"
+
+#: advtrains_luaautomation/pcnaming.lua:39
+msgid ""
+"You are not allowed to name LuaATC passive components without the @1 "
+"privilege."
+msgstr "您沒有「@1」許可權,不能命名這個元件。"
+
+#: advtrains_luaautomation/pcnaming.lua:62
+msgid "Set name of component (empty to clear)"
+msgstr ""
+
+#: advtrains_train_industrial/init.lua:10
+#: advtrains_train_industrial/init.lua:49 advtrains_train_steam/init.lua:20
+#: advtrains_train_steam/init.lua:91
+msgid "Driver Stand (right)"
+msgstr "右側司機座位"
+
+#: advtrains_train_industrial/init.lua:17
+#: advtrains_train_industrial/init.lua:56 advtrains_train_steam/init.lua:14
+#: advtrains_train_steam/init.lua:85
+msgid "Driver Stand (left)"
+msgstr "左側司機座位"
+
+#: advtrains_train_industrial/init.lua:40
+msgid "Industrial Train Engine"
+msgstr "工業用火車頭"
+
+#: advtrains_train_industrial/init.lua:79
+msgid "Big Industrial Train Engine"
+msgstr "大型工業用火車頭"
+
+#: advtrains_train_industrial/init.lua:98
+msgid "Industrial tank wagon"
+msgstr "液體運輸車廂"
+
+#: advtrains_train_industrial/init.lua:116
+msgid "Industrial wood wagon"
+msgstr "木材運輸車廂"
+
+#: advtrains_train_japan/init.lua:4
+msgid "Japanese Train Inter-Wagon Connection"
+msgstr "日本火車連結器"
+
+#: advtrains_train_japan/init.lua:37
+msgid "Driver stand"
+msgstr "司機座位"
+
+#: advtrains_train_japan/init.lua:101
+msgid "Japanese Train Engine"
+msgstr "高速列車車頭"
+
+#: advtrains_train_japan/init.lua:176
+msgid "Japanese Train Wagon"
+msgstr "高速列車車廂"
+
+#: advtrains_train_steam/init.lua:75
+msgid "Steam Engine"
+msgstr "蒸汽機車"
+
+#: advtrains_train_steam/init.lua:159
+msgid "Detailed Steam Engine"
+msgstr "精細的蒸汽機車"
+
+#: advtrains_train_steam/init.lua:206
+msgid "Passenger Wagon"
+msgstr "客車"
+
+#: advtrains_train_steam/init.lua:226
+msgid "Box Wagon"
+msgstr "貨運車廂"
+
+#: advtrains_train_subway/init.lua:144
+msgid "Subway Passenger Wagon"
+msgstr "地鐵車廂"
+
+#: advtrains_train_track/init.lua:31
+msgid "Y-turnout"
+msgstr "對稱道岔"
+
+#: advtrains_train_track/init.lua:49
+msgid "3-way turnout"
+msgstr "三開道岔"
+
+#: advtrains_train_track/init.lua:69
+msgid "Perpendicular Diamond Crossing Track"
+msgstr "垂直交叉軌道"
+
+#: advtrains_train_track/init.lua:91
+msgid "90+Angle Diamond Crossing Track"
+msgstr "交叉軌道 (其中一條軌道與座標軸平行)"
+
+#: advtrains_train_track/init.lua:132
+msgid "Diagonal Diamond Crossing Track"
+msgstr "交叉軌道"
+
+#: advtrains_train_track/init.lua:179
+msgid "Bumper"
+msgstr "保險槓"
+
+#: advtrains_train_track/init.lua:201
+msgid "ATC controller"
+msgstr "ATC 控制器"
+
+#: advtrains_train_track/init.lua:317
+msgid "Unloading Track"
+msgstr "卸貨軌道"
+
+#: advtrains_train_track/init.lua:342
+msgid "Loading Track"
+msgstr "裝貨軌道"
+
+#: advtrains_train_track/init.lua:406
+msgid "Detector Rail"
+msgstr "探測軌道"
+
+#~ msgid ""
+#~ "ATC controller, mode @1\n"
+#~ "Channel: @2"
+#~ msgstr ""
+#~ "ATC 控制器\n"
+#~ "模式:@1\n"
+#~ "頻道:@2"
+
+#~ msgid "Access to @1"
+#~ msgstr "可前往@1"
+
+#~ msgid "Can't get on: wagon full or doors closed!"
+#~ msgstr "無法上車:車門已關閉或車廂已滿。"
+
+#~ msgid "Can't place: protected position!"
+#~ msgstr "無法放置:此區域已被保護。"
+
+#~ msgid "Default Seat"
+#~ msgstr "預設座位"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "預設座位 (司機座位)"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "請不要使用"
+
+#~ msgid "Lock couples"
+#~ msgstr "鎖定連結處"
+
+#~ msgid "Speed:"
+#~ msgstr "速度"
+
+#~ msgid "Target:"
+#~ msgstr "目標速度"
+
+#, fuzzy
+#~ msgid "This node can't be rotated using the trackworker,"
+#~ msgstr "您不能使用鐵路調整工具旋轉這個方塊。"
+
+#~ msgid "This position is protected!"
+#~ msgstr "這裡已被保護。"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr "請使用潛行+右鍵上車。"
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "這裡已被保護,您不能在這裡建造鐵路。"
+
+#~ msgid ""
+#~ "You need to own at least one neighboring wagon to destroy this couple."
+#~ msgstr "您必須至少擁有其中一節車廂才能分開這兩節車廂。"
diff --git a/advtrains/poconvert.lua b/advtrains/poconvert.lua
new file mode 100644
index 0000000..74f962e
--- /dev/null
+++ b/advtrains/poconvert.lua
@@ -0,0 +1,185 @@
+local unescape_string
+do
+ local schartbl = { -- https://en.wikipedia.org/wiki/Escape_sequences_in_C
+ a = "\a",
+ b = "\b",
+ e = string.char(0x1b),
+ f = "\f",
+ n = "\n",
+ r = "\r",
+ t = "\t",
+ v = "\v",
+ }
+ local function replace_single(pfx, c)
+ local pl = #pfx
+ if pl % 2 == 0 then
+ return string.sub(pfx, 1, pl/2) .. c
+ end
+ return string.sub(pfx, 1, math.floor(pl/2)) .. (schartbl[c] or c)
+ end
+ unescape_string = function(str)
+ return string.gsub(str, [[(\+)([abefnrtv'"?])]], replace_single)
+ end
+end
+
+local function readstring_aux(str, pos)
+ local _, spos = string.find(str, [[^%s*"]], pos)
+ if not spos then
+ return nil
+ end
+ local ipos = spos
+ while true do
+ local _, epos, m = string.find(str, [[(\*)"]], ipos+1)
+ if not epos then
+ return error("String extends beyond the end of input")
+ end
+ ipos = epos
+ if #m % 2 == 0 then
+ return unescape_string(string.sub(str, spos+1, epos-1)), epos+1
+ end
+ end
+end
+
+local function readstring(str, pos)
+ local st = {}
+ local nxt = pos
+ while true do
+ local s, npos = readstring_aux(str, nxt)
+ if not s then
+ if not st[1] then
+ return nil, nxt
+ else
+ return table.concat(st), nxt
+ end
+ end
+ nxt = npos
+ table.insert(st, s)
+ end
+end
+
+local function readtoken(str, pos)
+ local _, epos, tok = string.find(str, [[^%s*(%S+)]], pos)
+ if epos == nil then
+ return nil, pos
+ end
+ return tok, epos+1
+end
+
+local function readcomment_add_flags(flags, s)
+ for flag in string.gmatch(s, ",%s*([^,]+)") do
+ flags[flag] = true
+ end
+end
+
+local function readcomment_aux(str, pos)
+ local _, epos, sval = string.find(str, "^\n*#([^\n]*)", pos)
+ if not epos then
+ return nil
+ end
+ return sval, epos+1
+end
+
+local function readcomment(str, pos)
+ local st = {}
+ local nxt = pos
+ local flags = {}
+ while true do
+ local s, npos = readcomment_aux(str, nxt)
+ if not npos then
+ local t = {
+ comment = table.concat(st, "\n"),
+ flags = flags,
+ }
+ return t, nxt
+ end
+ if string.sub(s, 1, 1) == "," then
+ readcomment_add_flags(flags, s)
+ end
+ table.insert(st, s)
+ nxt = npos
+ end
+end
+
+local function readpo(str)
+ local st = {}
+ local pos = 1
+ while true do
+ local entry, nxt = readcomment(str, pos)
+ local msglines = 0
+ while true do
+ local tok, npos = readtoken(str, nxt)
+ if tok == nil or string.sub(tok, 1, 1) == "#" then
+ break
+ elseif string.sub(tok, 1, 3) ~= "msg" then
+ return error("Invalid token: " .. tok)
+ elseif entry[tok] ~= nil then
+ break
+ else
+ local value, npos = readstring(str, npos)
+ assert(value ~= nil, "No string provided for " .. tok)
+ entry[tok] = value
+ nxt = npos
+ msglines = msglines+1
+ end
+ end
+ if msglines == 0 then
+ return st
+ elseif entry.msgid ~= "" then
+ assert(entry.msgid ~= nil, "Missing untranslated string")
+ assert(entry.msgstr ~= nil, "Missing translated string")
+ table.insert(st, entry)
+ end
+ pos = nxt
+ end
+end
+
+local escape_lookup = {
+ ["="] = "@=",
+ ["\n"] = "@n"
+}
+local function escape_string(st)
+ return (string.gsub(st, "[=\n]", escape_lookup))
+end
+
+local function convert_po_string(textdomain, str)
+ local st = {string.format("# textdomain: %s", textdomain)}
+ for _, entry in ipairs(readpo(str)) do
+ local line = ("%s=%s"):format(escape_string(entry.msgid), escape_string(entry.msgstr))
+ if entry.flags.fuzzy then
+ line = "#" .. line
+ end
+ table.insert(st, line)
+ end
+ return table.concat(st, "\n")
+end
+
+local function convert_po_file(textdomain, inpath, outpath)
+ local f, err = io.open(inpath, "rb")
+ assert(f, err)
+ local str = convert_po_string(textdomain, f:read("*a"))
+ f:close()
+ minetest.safe_file_write(outpath, str)
+ return str
+end
+
+local function convert_flat_po_directory(textdomain, modpath)
+ assert(textdomain, "No textdomain specified for po file conversion")
+ local mp = modpath or minetest.get_modpath(textdomain)
+ assert(mp ~= nil, "No path to write for " .. textdomain)
+ local popath = mp .. "/po"
+ local trpath = mp .. "/locale"
+ for _, infile in pairs(minetest.get_dir_list(popath, false)) do
+ local lang = string.match(infile, [[^([^%.]+)%.po$]])
+ if lang then
+ local inpath = popath .. "/" .. infile
+ local outpath = ("%s/%s.%s.tr"):format(trpath, textdomain, lang)
+ convert_po_file(textdomain, inpath, outpath)
+ end
+ end
+end
+
+return {
+ from_string = convert_po_string,
+ from_file = convert_po_file,
+ from_flat = convert_flat_po_directory,
+}
diff --git a/advtrains/protection.lua b/advtrains/protection.lua
index 7474977..ac1cd66 100644
--- a/advtrains/protection.lua
+++ b/advtrains/protection.lua
@@ -4,24 +4,24 @@
-- Privileges to control TRAIN DRIVING/COUPLING
minetest.register_privilege("train_operator", {
- description = "Without this privilege, a player can't do anything about trains, neither place or remove them nor drive or couple them (but he can build tracks if he has track_builder)",
+ description = attrans("Can place, remove and operate trains"),
give_to_singleplayer= true,
});
minetest.register_privilege("train_admin", {
- description = "Player may drive, place or remove any trains from/to anywhere, regardless of owner, whitelist or protection",
+ description = attrans("Can place, remove and operate any train, regardless of owner, whitelist, or protection"),
give_to_singleplayer= true,
});
-- Privileges to control TRACK BUILDING
minetest.register_privilege("track_builder", {
- description = "Player can place and/or dig rails not protected from him. If he also has protection_bypass, he can place/dig any rails",
+ description = attrans("Can place and dig tracks in unprotected areas"),
give_to_singleplayer= true,
});
-- Privileges to control OPERATING TURNOUTS/SIGNALS
minetest.register_privilege("railway_operator", {
- description = "Player can operate turnouts and signals not protected from him. If he also has protection_bypass, he can operate any turnouts/signals",
+ description = attrans("Can operate turnouts and signals in unprotected areas"),
give_to_singleplayer= true,
});
@@ -145,12 +145,12 @@ function advtrains.check_track_protection(pos, pname, near, prot_p)
--atdebug("CTP: ",pos,pname,near,prot_p,"priv=",priv,"prot=",prot,"dprot=",dprot)
if not priv and (not boo or prot or not dprot) then
- minetest.chat_send_player(pname, "You are not allowed to build "..nears.."tracks without track_builder privilege")
+ minetest.chat_send_player(pname, near and attrans("You are not allowed to build near tracks without the track_builder privilege.") or attrans("You are not allowed to build tracks without the track_builder privilege."))
minetest.log("action", pname.." tried to modify terrain "..nears.."track at "..minetest.pos_to_string(apos).." but is not permitted to (no privilege)")
return false
end
if prot then
- minetest.chat_send_player(pname, "You are not allowed to build "..nears.."tracks at protected position!")
+ minetest.chat_send_player(pname, near and attrans("You are not allowed to build near tracks at this protected position.") or attrans("You are not allowed to build tracks at this protected position."))
minetest.record_protection_violation(pos, pname)
minetest.log("action", pname.." tried to modify "..nears.."track at "..minetest.pos_to_string(apos).." but position is protected!")
return false
@@ -181,7 +181,7 @@ function advtrains.check_turnout_signal_protection(pos, pname)
nocheck=false
return true
else
- minetest.chat_send_player(pname, "You are not allowed to operate turnouts and signals (missing railway_operator privilege)")
+ minetest.chat_send_player(pname, attrans("You are not allowed to operate turnouts and signals without the railway_operator privilege."))
minetest.log("action", pname.." tried to operate turnout/signal at "..minetest.pos_to_string(pos).." but does not have railway_operator")
nocheck=false
return false
diff --git a/advtrains/settingtypes.txt b/advtrains/settingtypes.txt
index 79a1e4c..a09da71 100644
--- a/advtrains/settingtypes.txt
+++ b/advtrains/settingtypes.txt
@@ -65,3 +65,6 @@ advtrains_save_interval (Save Interval) int 60 20 3600
# If enabled, trains only collide with nodes with "normal" drawtype.
advtrains_forgiving_collision (Forgiving Collision mode) bool false
+# Enable universal couplers for wagons
+# If enabled, wagons will bypass the checks that compare the coupler types when coupling.
+advtrains_universal_couplers (Universal Couplers) bool false \ No newline at end of file
diff --git a/advtrains/signals.lua b/advtrains/signals.lua
index 3f736c9..4dec7f5 100644
--- a/advtrains/signals.lua
+++ b/advtrains/signals.lua
@@ -59,7 +59,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
tiles = {"advtrains_retrosignal.png"},
inventory_image="advtrains_retrosignal_inv.png",
drop="advtrains:retrosignal_off",
- description=attrans("Lampless Signal (@1)", attrans(r..rotation)),
+ description=attrans("Lampless Signal"),
sunlight_propagates=true,
groups = {
cracky=3,
@@ -121,7 +121,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
tiles = {"advtrains_signal_"..r..".png"},
inventory_image="advtrains_signal_inv.png",
drop="advtrains:signal_off",
- description=attrans("Signal (@1)", attrans(r..rotation)),
+ description=attrans("Signal"),
groups = {
cracky=3,
not_blocking_trains=1,
@@ -174,6 +174,11 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
if r=="off" then crea=0 end
--tunnel signals. no rotations.
+ local swdesc = { -- needed for xgettext
+ l = attrans("Wallmounted Signal (left)"),
+ r = attrans("Wallmounted Signal (right)"),
+ t = attrans("Wallmounted Signal (top)"),
+ }
for loc, sbox in pairs({l={-1/2, -1/2, -1/4, 0, 1/2, 1/4}, r={0, -1/2, -1/4, 1/2, 1/2, 1/4}, t={-1/2, 0, -1/4, 1/2, 1/2, 1/4}}) do
minetest.register_node("advtrains:signal_wall_"..loc.."_"..r, {
drawtype = "mesh",
@@ -187,7 +192,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
mesh = "advtrains_signal_wall_"..loc..".b3d",
tiles = {"advtrains_signal_wall_"..r..".png"},
drop="advtrains:signal_wall_"..loc.."_off",
- description=attrans("Wallmounted Signal ("..loc..")"),
+ description=swdesc[loc],
groups = {
cracky=3,
not_blocking_trains=1,
@@ -287,7 +292,7 @@ minetest.register_node("advtrains:across_on", {
mesh = "advtrains_across.obj",
tiles = {{name="advtrains_across_anim.png", animation={type="vertical_frames", aspect_w=64, aspect_h=64, length=1.0}}},
drop="advtrains:across_off",
- description=attrans("Andrew's Cross (on) (you hacker you)"),
+ description=attrans("Andrew's Cross"),
groups = {
cracky=3,
not_blocking_trains=1,
diff --git a/advtrains/sounds/advtrains_crossing_bell.ogg b/advtrains/sounds/advtrains_crossing_bell.ogg
index 74df669..2b441ae 100644
--- a/advtrains/sounds/advtrains_crossing_bell.ogg
+++ b/advtrains/sounds/advtrains_crossing_bell.ogg
Binary files differ
diff --git a/advtrains/spec/poconvert_spec.lua b/advtrains/spec/poconvert_spec.lua
new file mode 100644
index 0000000..51f33e7
--- /dev/null
+++ b/advtrains/spec/poconvert_spec.lua
@@ -0,0 +1,70 @@
+package.path = "../?.lua;" .. package.path
+advtrains = {}
+_G.advtrains = advtrains
+local poconvert = require("poconvert")
+
+describe("PO file converter", function()
+ it("should convert PO files", function()
+ assert.equals([[
+# textdomain: foo
+foo=bar
+baz=
+#@=wh\at\\@n=@=w\as\\@n
+multiline@nstrings=multiline@nresult
+with context?=oder doch nicht]], poconvert.from_string("foo", [[
+msgid ""
+msgstr "whatever metadata"
+
+msgid "foo"
+msgstr "bar"
+
+msgid "baz"
+msgstr ""
+
+#, fuzzy
+msgid "=wh\\at\\\\\n"
+msgstr "=w\\as\\\\\n"
+
+msgid "multi"
+"line\n"
+"strings"
+msgstr "multi"
+"line\n"
+"result"
+
+msgctxt "i18n context"
+msgid "with context?"
+msgstr "oder doch nicht"]]))
+ end)
+ it("should reject invalid tokens", function()
+ assert.has.errors(function()
+ poconvert.from_string("", [[
+foo ""
+bar ""]])
+ end, "Invalid token: foo")
+ end)
+ it("should reject entries without a msgstr", function()
+ assert.has.errors(function()
+ poconvert.from_string("", [[msgid "foo"]])
+ end, "Missing translated string")
+ end)
+ it("should reject entries without a msgid", function()
+ assert.has.errors(function()
+ poconvert.from_string("", [[msgstr "foo"]])
+ end, "Missing untranslated string")
+ end)
+ it("should reject entries with improperly enclosed strings", function()
+ assert.has.errors(function()
+ poconvert.from_string("", [[
+msgid "foo"
+msgstr "bar \]])
+ end, "String extends beyond the end of input")
+ end)
+ it("should reject incomplete input", function()
+ assert.has.errors(function()
+ poconvert.from_string("", [[
+msgid "foo"
+msgstr]])
+ end, "No string provided for msgstr")
+ end)
+end)
diff --git a/advtrains/spec/texture_spec.lua b/advtrains/spec/texture_spec.lua
new file mode 100644
index 0000000..2e3bd5d
--- /dev/null
+++ b/advtrains/spec/texture_spec.lua
@@ -0,0 +1,19 @@
+package.path = "../?.lua;" .. package.path
+local T = require "texture"
+
+describe("Texture creation", function()
+ it("works", function()
+ assert.same("^.png", tostring(T.raw"^.png"))
+ assert.same("foo\\:bar.png", tostring(T"foo:bar.png"))
+ end)
+end)
+
+describe("Texture modifiers", function()
+ it("work", function()
+ assert.same("x^[colorize:c", tostring(T"x":colorize"c"))
+ assert.same("x^[colorize:c:alpha", tostring(T"x":colorize("c", "alpha")))
+ assert.same("x^[multiply:c", tostring(T"x":multiply"c"))
+ assert.same("x^[resize:2x3", tostring(T"x":resize(2, 3)))
+ assert.same("x^[transformI", tostring(T"x":transform"I"))
+ end)
+end)
diff --git a/advtrains/spec/wagons_spec.lua b/advtrains/spec/wagons_spec.lua
new file mode 100644
index 0000000..df0687b
--- /dev/null
+++ b/advtrains/spec/wagons_spec.lua
@@ -0,0 +1,40 @@
+require "mineunit"
+mineunit "core"
+
+_G.advtrains = {
+ wagon_load_range = 32
+}
+sourcefile "wagons"
+
+local myproto = {_test = true}
+advtrains.register_wagon(":mywagon", myproto, "My wagon", "", false)
+advtrains.register_wagon_alias(":myalias", ":mywagon")
+advtrains.register_wagon_alias(":myotheralias", ":myalias")
+
+local myotherproto = {_other = true}
+advtrains.register_wagon(":noalias", myotherproto, "Not aliased wagon", "", false)
+advtrains.register_wagon_alias(":noalias", ":mywagon")
+
+advtrains.register_wagon_alias(":nilalias", ":nil")
+
+advtrains.register_wagon_alias(":R1", ":R2")
+advtrains.register_wagon_alias(":R2", ":R3")
+advtrains.register_wagon_alias(":R3", ":R1")
+
+describe("wagon alias system", function()
+ it("should work", function()
+ assert.same({":mywagon", myproto}, {advtrains.resolve_wagon_alias(":myalias")})
+ assert.equal(myproto, advtrains.wagon_prototypes[":myalias"])
+ assert.same({":mywagon", myproto}, {advtrains.resolve_wagon_alias(":myotheralias")})
+ end)
+ it("should respect wagon registration", function()
+ assert.same({":noalias", myotherproto}, {advtrains.resolve_wagon_alias(":noalias")})
+ end)
+ it("should handle recursive loops", function()
+ assert.same({}, {advtrains.resolve_wagon_alias(":R1")})
+ end)
+ it("should return nil for missing entries", function()
+ assert.same({}, {advtrains.resolve_wagon_alias(":what")})
+ assert.same({}, {advtrains.resolve_wagon_alias(":nilalias")})
+ end)
+end)
diff --git a/advtrains/texture.lua b/advtrains/texture.lua
new file mode 100644
index 0000000..e6d83b0
--- /dev/null
+++ b/advtrains/texture.lua
@@ -0,0 +1,228 @@
+local tx = {}
+setmetatable(tx, {__call = function(_, ...) return tx.base(...) end})
+
+function tx.escape(str)
+ return (string.gsub(tostring(str), [[([%^:\])]], [[\%1]]))
+end
+
+local function getargs(...)
+ return select("#", ...), {...}
+end
+
+local function curry(f, x)
+ return function(...)
+ return f(x, ...)
+ end
+end
+
+local function xmkmodifier(func)
+ return function(self, ...)
+ table.insert(self, (func(...)))
+ return self
+ end
+end
+
+local function mkmodifier(fmt, spec)
+ return xmkmodifier(function(...)
+ local count = select("#", ...)
+ local args = {...}
+ for k, f in pairs(spec) do
+ args[k] = f(args[k])
+ end
+ return string.format(fmt, unpack(args, 1, count))
+ end)
+end
+
+-- Texture object
+local tx_lib = {}
+local tx_mt = {
+ __index = tx_lib,
+ __tostring = function(self)
+ return table.concat(self, "^")
+ end,
+ __concat = function(a, b)
+ return tx.raw(("%s^%s"):format(tostring(a), tostring(b)))
+ end,
+}
+
+function tx.raw(str)
+ return setmetatable({str}, tx_mt)
+end
+function tx.base(str)
+ return tx.raw(tx.escape(str))
+end
+-- TODO: use [fill when 5.8.0 becomes widely used client-side
+function tx.fill(w, h, color)
+ return tx"advtrains_hud_bg.png":resize(w, h):colorize(color)
+end
+
+-- Most texture modifiers
+tx_lib.colorize = xmkmodifier(function(c, a)
+ local str = ("[colorize:%s"):format(tx.escape(c))
+ if a then
+ str = str .. ":" .. a
+ end
+ return str
+end)
+tx_lib.multiply = mkmodifier("[multiply:%s", {tx.escape})
+tx_lib.resize = mkmodifier("[resize:%dx%d", {})
+tx_lib.transform = mkmodifier("[transform%s", {tx.escape})
+
+-- [combine
+
+local combine = {}
+
+function combine:add(x, y, ent)
+ table.insert(self.st, ([[%d,%d=%s]]):format(x, y, tx.escape(tostring(ent))))
+ return self
+end
+
+local combine_mt = {
+ __index = combine,
+ __tostring = function(self)
+ return table.concat(self.st, ":")
+ end,
+}
+
+function tx.combine(w, h, bg)
+ local base = ("[combine:%dx%d"):format(w, h)
+ local obj = setmetatable({width = w, height = h, st = {base}}, combine_mt)
+ if bg then
+ obj:add_fill(0, 0, w, h, bg)
+ end
+ return obj
+end
+
+function combine:add_fill(x, y, ...)
+ return self:add(x, y, tx.fill(...))
+end
+
+local function add_multicolor_fill(n, self, x, y, w, h, ...)
+ local argc, argv = getargs(...)
+ local t = 0
+ for k = 1, argc, 2 do
+ t = t + argv[k]
+ end
+ local newargs = {x, y, w, h}
+ local sk, wk = n, n+2
+ local s = newargs[wk]/t
+ for k = 1, argc, 2 do
+ local v = argv[k] * s
+ newargs[wk] = v
+ newargs[5] = argv[k+1]
+ self:add_fill(unpack(newargs))
+ newargs[sk] = newargs[sk] + v
+ end
+ return self
+end
+combine.add_multicolor_fill_topdown = curry(add_multicolor_fill, 2)
+combine.add_multicolor_fill_leftright = curry(add_multicolor_fill, 1)
+
+local function add_segmentbar(n, self, x, y, w, h, m, c, ...)
+ local argc, argv = getargs(...)
+ local baseargs = {x, y, w, h}
+ local ss = (baseargs[n+2]+m)/c
+ local bs = ss - m
+ for k = 1, argc, 3 do
+ local lower, upper, fill = argv[k], argv[k+1], argv[k+2]
+ lower = math.max(0, math.floor(lower))+1
+ upper = math.min(c, math.floor(upper))
+ if lower <= upper then
+ local args = {x, y, w, h, fill}
+ args[n+2] = bs
+ args[n] = args[n] + ss*(lower-1)
+ for i = lower, upper do
+ self:add_fill(unpack(args))
+ args[n] = args[n] + ss
+ end
+ end
+ end
+ return self
+end
+combine.add_segmentbar_topdown = curry(add_segmentbar, 2)
+combine.add_segmentbar_leftright = curry(add_segmentbar, 1)
+
+local function add_lever(n, self, x, y, w, h, hs, ss, val, hf, sf)
+ local baseargs = {x, y, w, h}
+ local sargs = {x, y, w, h, sf}
+ sargs[5-n] = ss
+ sargs[n+2] = baseargs[n+2] + ss - hs
+ for k = 1, 2 do
+ sargs[k] = baseargs[k] + (baseargs[k+2] - sargs[k+2])/2
+ end
+ self:add_fill(unpack(sargs))
+ local hargs = {x, y, w, h, hf}
+ hargs[n+2] = hs
+ hargs[n] = baseargs[n] + (baseargs[n+2]-hs)*val
+ self:add_fill(unpack(hargs))
+ return self
+end
+combine.add_lever_topdown = curry(add_lever, 2)
+combine.add_lever_leftright = curry(add_lever, 1)
+
+--[[ Seven-segment display
+ -1-
+6 2
+ -7-
+5 3
+ -4-
+--]]
+local sevenseg_digits = {
+ ["0"] = {1, 2, 3, 4, 5, 6},
+ ["1"] = {2, 3},
+ ["2"] = {1, 2, 4, 5, 7},
+ ["3"] = {1, 2, 3, 4, 7},
+ ["4"] = {2, 3, 6, 7},
+ ["5"] = {1, 3, 4, 6, 7},
+ ["6"] = {1, 3, 4, 5, 6, 7},
+ ["7"] = {1, 2, 3},
+ ["8"] = {1, 2, 3, 4, 5, 6, 7},
+ ["9"] = {1, 2, 3, 4, 6, 7},
+}
+
+function combine:add_str7seg(x0, y0, tw, th, str, fill)
+ --[[ w and h (as width/height of individual (horizontal) segments) have the following properties:
+ tw = n(w+3h)-h
+ th = 2w+3h
+ --]]
+ local len = #str
+ local h = (2*tw-len*th)/(3*len-2)
+ local w = (th-3*h)/2
+ local ws = w+3*h
+ local segs = {
+ {h, 0, w, h},
+ {w+h, h, h, w},
+ {w+h, w+2*h, h, w},
+ {h, 2*(w+h), w, h},
+ {0, w+2*h, h, w},
+ {0, h, h, w},
+ {h, w+h, w, h},
+ }
+ for i = 1, len do
+ for _, k in pairs(sevenseg_digits[string.sub(str, i, i)] or {}) do
+ local s = segs[k]
+ self:add_fill(s[1]+x0, s[2]+y0, s[3], s[4], fill)
+ end
+ x0 = x0 + ws
+ end
+ return self
+end
+
+function combine:add_n7seg(x, y, w, h, n, prec, ...)
+ if not (type(n) == "number" and type(prec) == "number") then
+ error("passed non-numeric value or precision to numeric display")
+ elseif prec < 0 then
+ error("negative length")
+ end
+ local pfx = ""
+ if n >= 0 then
+ n = math.min(10^prec-1, n)
+ else
+ n = math.min(10^(prec-1)-1, -n)
+ pfx = "-"
+ end
+ local str = ("%d"):format(n)
+ return self:add_str7seg(x, y, w, h, pfx .. ("0"):rep(prec-#str-#pfx) .. str, ...)
+end
+
+return tx
diff --git a/advtrains/textures/advtrains_wagon_prop_tool.png b/advtrains/textures/advtrains_wagon_prop_tool.png
new file mode 100644
index 0000000..6352c55
--- /dev/null
+++ b/advtrains/textures/advtrains_wagon_prop_tool.png
Binary files differ
diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua
index 597e8ec..6a2c7a8 100644
--- a/advtrains/trackplacer.lua
+++ b/advtrains/trackplacer.lua
@@ -230,10 +230,12 @@ function tp.place_track(pos, tpg, pname, yaw)
return true
end
+
-- TRACK WORKER --
+
minetest.register_craftitem("advtrains:trackworker",{
- description = attrans("Track Worker Tool\n\nLeft-click: change rail type (straight/curve/switch)\nRight-click: rotate rail/bumper/signal/etc."),
+ description = attrans("Track Worker Tool\n\nLeft-click: change rail type (straight/curve/switch)\nRight-click: rotate object"),
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "advtrains_trackworker.png",
wield_image = "advtrains_trackworker.png",
diff --git a/advtrains/trainhud.lua b/advtrains/trainhud.lua
index 22aa6cf..ce4b913 100644
--- a/advtrains/trainhud.lua
+++ b/advtrains/trainhud.lua
@@ -1,5 +1,7 @@
--trainhud.lua: holds all the code for train controlling
+local T = advtrains.texture
+
advtrains.hud = {}
advtrains.hhud = {}
@@ -8,6 +10,8 @@ advtrains.hud[player:get_player_name()] = nil
advtrains.hhud[player:get_player_name()] = nil
end)
+local hud_type_key = minetest.features.hud_def_type_field and "type" or "hud_elem_type"
+
local mletter={[1]="F", [-1]="R", [0]="N"}
function advtrains.on_control_change(pc, train, flip)
@@ -101,19 +105,21 @@ function advtrains.set_trainhud(name, text, driver)
if not player then
return
end
+ local drivertext = driver or ""
local driverhud = {
- hud_elem_type = "image",
+ [hud_type_key] = "image",
name = "ADVTRAINS_DRIVER",
position = {x=0.5, y=1},
offset = {x=0,y=-170},
- text = driver or "",
+ text = drivertext,
alignment = {x=0,y=-1},
- scale = {x=1,y=1},}
+ scale = {x=1,y=1},
+ }
if not hud then
- hud = {["driver"]={}}
+ hud = {}
advtrains.hud[name] = hud
hud.id = player:hud_add({
- hud_elem_type = "text",
+ [hud_type_key] = "text",
name = "ADVTRAINS",
number = 0xFFFFFF,
position = {x=0.5, y=1},
@@ -122,17 +128,22 @@ function advtrains.set_trainhud(name, text, driver)
scale = {x=200, y=60},
alignment = {x=0, y=-1},
})
- hud.oldText=text
hud.driver = player:hud_add(driverhud)
+ hud.oldText = text
+ hud.oldDriver = drivertext
else
if hud.oldText ~= text then
player:hud_change(hud.id, "text", text)
hud.oldText=text
end
if hud.driver then
- player:hud_change(hud.driver, "text", driver or "")
+ if hud.oldDriver ~= drivertext then
+ player:hud_change(hud.driver, "text", drivertext)
+ hud.oldDriver = drivertext
+ end
elseif driver then
hud.driver = player:hud_add(driverhud)
+ hud.oldDriver = drivertext
end
end
end
@@ -147,7 +158,7 @@ function advtrains.set_help_hud(name, text)
hud = {}
advtrains.hhud[name] = hud
hud.id = player:hud_add({
- hud_elem_type = "text",
+ [hud_type_key] = "text",
name = "ADVTRAINS_HELP",
number = 0xFFFFFF,
position = {x=1, y=0.3},
@@ -184,138 +195,94 @@ function advtrains.hud_train_format(train, flip)
local vel = advtrains.abs_ceil(train.velocity)
local vel_kmh=advtrains.abs_ceil(advtrains.ms_to_kmh(train.velocity))
- local tlev=train.lever or 1
+ local tlev=train.lever or 3
if train.velocity==0 and not train.active_control then tlev=1 end
if train.hud_lzb_effect_tmr then
tlev=1
end
- local ht = {"[combine:440x110:0,0=(advtrains_hud_bg.png^[resize\\:440x110)"}
+ local hud = T.combine(440, 110, "black")
local st = {}
if train.debug then st = {train.debug} end
- -- seven-segment display
- local function sevenseg(digit, x, y, w, h, m)
- --[[
- -1-
- 2 3
- -4-
- 5 6
- -7-
- ]]
- local segs = {
- {h, 0, w, h},
- {0, h, h, w},
- {w+h, h, h, w},
- {h, w+h, w, h},
- {0, w+2*h, h, w},
- {w+h, w+2*h, h, w},
- {h, 2*(w+h), w, h}}
- local trans = {
- [0] = {true, true, true, false, true, true, true},
- [1] = {false, false, true, false, false, true, false},
- [2] = {true, false, true, true, true, false, true},
- [3] = {true, false, true, true, false, true, true},
- [4] = {false, true, true, true, false, true, false},
- [5] = {true, true, false, true, false, true, true},
- [6] = {true, true, false, true, true, true, true},
- [7] = {true, false, true, false, false, true, false},
- [8] = {true, true, true, true, true, true, true},
- [9] = {true, true, true, true, false, true, true}}
- local ent = trans[digit or 10]
- if not ent then return end
- for i = 1, 7, 1 do
- if ent[i] then
- local s = segs[i]
- ht[#ht+1] = sformat("%d,%d=(advtrains_hud_bg.png^[resize\\:%dx%d^%s)",x+s[1], y+s[2], s[3], s[4], m)
- end
- end
- end
-
-- lever
- ht[#ht+1] = "275,10=(advtrains_hud_bg.png^[colorize\\:cyan^[resize\\:5x18)"
- ht[#ht+1] = "275,28=(advtrains_hud_bg.png^[colorize\\:white^[resize\\:5x18)"
- ht[#ht+1] = "275,46=(advtrains_hud_bg.png^[colorize\\:orange^[resize\\:5x36)"
- ht[#ht+1] = "275,82=(advtrains_hud_bg.png^[colorize\\:red^[resize\\:5x18)"
- ht[#ht+1] = "292,16=(advtrains_hud_bg.png^[colorize\\:darkslategray^[resize\\:6x78)"
- ht[#ht+1] = sformat("280,%s=(advtrains_hud_bg.png^[colorize\\:gray^[resize\\:30x18)",18*(4-tlev)+10)
+ hud:add_multicolor_fill_topdown(275, 10, 5, 90, 1, "cyan", 1, "white", 2, "orange", 1, "red")
+ hud:add_lever_topdown(280, 10, 30, 90, 18, 6, (4-tlev)/4, "gray", "darkslategray")
-- reverser
- ht[#ht+1] = sformat("245,10=(advtrains_hud_arrow.png^[transformFY%s)", flip and "" or "^[multiply\\:cyan")
- ht[#ht+1] = sformat("245,85=(advtrains_hud_arrow.png%s)", flip and "^[multiply\\:orange" or "")
- ht[#ht+1] = "250,35=(advtrains_hud_bg.png^[colorize\\:darkslategray^[resize\\:5x40)"
- ht[#ht+1] = sformat("240,%s=(advtrains_hud_bg.png^[resize\\:25x15^[colorize\\:gray)", flip and 65 or 30)
+ hud:add(245, 10, T"advtrains_hud_arrow.png":transform"FY":multiply(flip and "gray" or "cyan"))
+ hud:add(245, 85, T"advtrains_hud_arrow.png":multiply(flip and "orange" or "gray"))
+ hud:add_lever_topdown(240, 30, 25, 50, 15, 5, flip and 1 or 0, "gray", "darkslategray")
-- train control/safety indication
- if train.tarvelocity or train.atc_command then
- ht[#ht+1] = "10,10=(advtrains_hud_atc.png^[resize\\:30x30^[multiply\\:cyan)"
- end
- if train.hud_lzb_effect_tmr then
- ht[#ht+1] = "50,10=(advtrains_hud_lzb.png^[resize\\:30x30^[multiply\\:red)"
- end
- if train.is_shunt then
- ht[#ht+1] = "90,10=(advtrains_hud_shunt.png^[resize\\:30x30^[multiply\\:orange)"
- end
+ hud:add(10, 10, T"advtrains_hud_atc.png":resize(30, 30):multiply((train.tarvelocity or train.atc_command) and "cyan" or "darkslategray"))
+ hud:add(50, 10, T"advtrains_hud_lzb.png":resize(30, 30):multiply(train.hud_lzb_effect_tmr and "red" or "darkslategray"))
+ hud:add(90, 10, T"advtrains_hud_shunt.png":resize(30, 30):multiply(train.is_shunt and "orange" or "darkslategray"))
-- door
- ht[#ht+1] = "187,10=(advtrains_hud_bg.png^[resize\\:26x30^[colorize\\:white)"
- ht[#ht+1] = "189,12=(advtrains_hud_bg.png^[resize\\:22x11)"
- ht[#ht+1] = sformat("170,10=(advtrains_hud_bg.png^[resize\\:15x30^[colorize\\:%s)", train.door_open==-1 and "white" or "darkslategray")
- ht[#ht+1] = "172,12=(advtrains_hud_bg.png^[resize\\:11x11)"
- ht[#ht+1] = sformat("215,10=(advtrains_hud_bg.png^[resize\\:15x30^[colorize\\:%s)", train.door_open==1 and "white" or "darkslategray")
- ht[#ht+1] = "217,12=(advtrains_hud_bg.png^[resize\\:11x11)"
+ hud:add_fill(187, 10, 26, 30, "white"):add_fill(189, 12, 22, 11, "black")
+ hud:add_fill(170, 10, 15, 30, train.door_open==-1 and "white" or "darkslategray"):add_fill(172, 12, 11, 11, "black")
+ hud:add_fill(215, 10, 15, 30, train.door_open==1 and "white" or "darkslategray"):add_fill(217, 12, 11, 11, "black")
-- speed indication(s)
- sevenseg(math.floor(vel/10), 320, 10, 30, 10, "[colorize\\:red\\:255")
- sevenseg(vel%10, 380, 10, 30, 10, "[colorize\\:red\\:255")
- for i = 1, vel, 1 do
- ht[#ht+1] = sformat("%d,65=(advtrains_hud_bg.png^[resize\\:8x20^[colorize\\:white)", i*11-1)
- end
- for i = max+1, 20, 1 do
- ht[#ht+1] = sformat("%d,65=(advtrains_hud_bg.png^[resize\\:8x20^[colorize\\:darkslategray)", i*11-1)
- end
+ hud:add_n7seg(320, 10, 110, 90, vel, 2, "red")
+ hud:add_segmentbar_leftright(10, 65, 217, 20, 3, 20, max, 20, "darkslategray", 0, vel, "white")
if res and res > 0 then
- ht[#ht+1] = sformat("%d,60=(advtrains_hud_bg.png^[resize\\:3x30^[colorize\\:red\\:255)", 7+res*11)
+ hud:add_fill(7+res*11, 60, 3, 30, "red")
end
if train.tarvelocity then
- ht[#ht+1] = sformat("%d,85=(advtrains_hud_arrow.png^[multiply\\:cyan^[transformFY^[makealpha\\:#000000)", 1+train.tarvelocity*11)
+ hud:add(1+train.tarvelocity*11, 85, T"advtrains_hud_arrow.png":transform"FY":multiply"cyan")
end
+ local lzbdisp
local lzb = train.lzb
if lzb and lzb.checkpoints then
local oc = lzb.checkpoints
for i = 1, #oc do
+ if advtrains.interlocking then
+ local udata = oc[i].udata
+ if udata and udata.signal_pos then
+ local sigd = advtrains.interlocking.db.get_sigd_for_signal(udata.signal_pos)
+ if sigd then
+ local tcbs = advtrains.interlocking.db.get_tcbs(sigd) or {}
+ if tcbs.route_rsn then
+ table.insert(st, ("%s: %s"):format(minetest.pos_to_string(sigd.p), tcbs.route_rsn))
+ end
+ end
+ end
+ end
local spd = oc[i].speed
spd = advtrains.speed.min(spd, train.speed_restriction)
if spd == -1 then spd = nil end
local c = not spd and "lime" or (type(spd) == "number" and (spd == 0) and "red" or "orange") or nil
if c then
- ht[#ht+1] = sformat("130,10=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c)
- ht[#ht+1] = sformat("130,35=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c)
if spd and spd~=0 then
- ht[#ht+1] = sformat("%d,50=(advtrains_hud_arrow.png^[multiply\\:red^[makealpha\\:#000000)", 1+spd*11)
+ hud:add(1+spd*11, 50, T"advtrains_hud_arrow.png":multiply"red")
end
- local floor = math.floor
- local dist = floor(((oc[i].index or train.index)-train.index))
+ local dist = math.floor(((oc[i].index or train.index)-train.index))
dist = math.max(0, math.min(999, dist))
- for j = 1, 3, 1 do
- sevenseg(floor((dist/10^(3-j))%10), 119+j*11, 18, 4, 2, "[colorize\\:"..c)
- end
+ lzbdisp = {c = c, d = dist}
break
end
end
end
+ if not lzbdisp then
+ lzbdisp = {c = "darkslategray", d = 888}
+ end
+ hud:add_fill(130, 10, 30, 5, lzbdisp.c)
+ hud:add_fill(130, 35, 30, 5, lzbdisp.c)
+ hud:add_n7seg(131, 18, 28, 14, lzbdisp.d, 3, lzbdisp.c)
if res and res == 0 then
- st[#st+1] = attrans("OVERRUN RED SIGNAL! Examine situation and reverse train to move again.")
+ table.insert(st, attrans("OVERRUN RED SIGNAL! Examine situation and reverse train to move again."))
end
if train.atc_command then
- st[#st+1] = sformat("ATC: %s%s", train.atc_delay and advtrains.abs_ceil(train.atc_delay).."s " or "", train.atc_command or "")
+ table.insert(st, ("ATC: %s%s"):format(train.atc_delay and advtrains.abs_ceil(train.atc_delay).."s " or "", train.atc_command or ""))
end
- return table.concat(st,"\n"), table.concat(ht,":")
+ return table.concat(st,"\n"), tostring(hud)
end
local _, texture = advtrains.hud_train_format { -- dummy train object to demonstrate the train hud
max_speed = 15, speed_restriction = 15, velocity = 15, tarvelocity = 12,
active_control = true, lever = 3, ctrl = {lzb = true}, is_shunt = true,
- door_open = 1, lzb = {oncoming = {{spd=6, idx=125.7}}}, index = 0,
+ door_open = 1, lzb = {checkpoints = {{speed=6, index=125.7}}}, index = 100,
}
minetest.register_node("advtrains:hud_demo",{
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
index 9e9f214..1d44324 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -142,13 +142,8 @@ minetest.register_on_joinplayer(function(player)
local pname = player:get_player_name()
local id=advtrains.player_to_train_mapping[pname]
if id then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id then
- local wdata = advtrains.wagons[wagon.id]
- if wdata and wdata.train_id == id then
- wagon:reattach_all()
- end
- end
+ for _, wagon in advtrains.wagon_entity_pairs_in_train(id) do
+ wagon:reattach_all()
end
end
end)
@@ -160,12 +155,10 @@ minetest.register_on_dieplayer(function(player)
if id then
local train=advtrains.trains[id]
if not train then advtrains.player_to_train_mapping[pname]=nil return end
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
- --when player dies, detach him from the train
- --call get_off_plr on every wagon since we don't know which one he's on.
- wagon:get_off_plr(pname)
- end
+ for _, wagon in advtrains.wagon_entity_pairs_in_train(id) do
+ --when player dies, detach him from the train
+ --call get_off_plr on every wagon since we don't know which one he's on.
+ wagon:get_off_plr(pname)
end
-- just in case no wagon felt responsible for this player: clear train mapping
advtrains.player_to_train_mapping[pname] = nil
@@ -270,6 +263,10 @@ function advtrains.train_ensure_init(id, train)
atwarn(debug.traceback())
return nil
end
+
+ if not train.staticdata then
+ train.staticdata = {}
+ end
train.dirty = true
if train.no_step then
@@ -621,7 +618,7 @@ function advtrains.train_step_b(id, train, dtime)
local base_cn = train.path_cn[base_idx]
--atdebug(id,"Begin Checking for on-track collisions new_idx=",new_index_curr_tv,"base_idx=",base_idx,"base_pos=",base_pos,"base_cn=",base_cn)
-- query occupation
- local occ = advtrains.occ.get_trains_over(base_pos)
+ local occ = advtrains.occ.reverse_lookup_sel(base_pos, "close_proximity")
-- iterate other trains
for otid, ob_idx in pairs(occ) do
if otid ~= id then
@@ -651,7 +648,7 @@ function advtrains.train_step_b(id, train, dtime)
-- Phase 2 - project ref_index back onto our path and check again (necessary because there might be a turnout on the way and we are driving into the flank
if target_is_inside then
- local our_index = advtrains.path_project(otrn, ref_index, id)
+ local our_index = advtrains.path_project(otrn, ref_index, id, "before_end")
--atdebug("Backprojected our_index",our_index)
if our_index and our_index <= new_index_curr_tv
and our_index >= train.index then --FIX: If train was already past the collision point in the previous step, there is no collision! Fixes bug with split_at_index
@@ -805,9 +802,12 @@ function advtrains.train_step_c(id, train, dtime)
if is_loaded_area then
local objs = minetest.get_objects_inside_radius(rcollpos, 2)
for _,obj in ipairs(objs) do
- if not obj:is_player() and obj:get_armor_groups().fleshy and obj:get_armor_groups().fleshy > 0
- and obj:get_luaentity() and obj:get_luaentity().name~="signs_lib:text" then
- obj:punch(obj, 1, { full_punch_interval = 1.0, damage_groups = {fleshy = 1000}, }, nil)
+ if not obj:is_player() then
+ local armor = obj:get_armor_groups()
+ local luaentity = obj:get_luaentity()
+ if armor.fleshy and armor.fleshy > 0 and luaentity and luaentity.name ~= "signs_lib:text" then
+ obj:punch(obj, 1, { full_punch_interval = 1.0, damage_groups = {fleshy = 1000}, }, nil)
+ end
end
end
end
@@ -1101,8 +1101,17 @@ function advtrains.spawn_wagons(train_id)
if advtrains.position_in_range(pos, ablkrng) then
--atdebug("wagon",w_id,"spawning")
local wt = advtrains.get_wagon_prototype(data)
- local wagon = minetest.add_entity(pos, wt):get_luaentity()
- wagon:set_id(w_id)
+ local wobj = minetest.add_entity(pos, wt)
+ if not wobj then
+ atwarn("Failed to spawn wagon", w_id, "of type", wt)
+ else
+ local wagon = wobj:get_luaentity()
+ if not wagon then
+ atwarn("Wagon", w_id, "of type", wt, "spawned with nil luaentity")
+ else
+ wagon:set_id(w_id)
+ end
+ end
end
end
else
@@ -1169,6 +1178,8 @@ function advtrains.split_train_at_index(train, index)
newtrain.points_split = advtrains.merge_tables(train.points_split)
newtrain.autocouple = train.autocouple
+ advtrains.te_run_callbacks_on_decouple(train, newtrain, index)
+
return newtrain_id -- return new train ID, so new train can be manipulated
end
@@ -1218,7 +1229,6 @@ function advtrains.invert_train(train_id)
advtrains.update_trainpart_properties(train_id, true)
-- recalculate path
- advtrains.train_ensure_init(train_id, train)
-- If interlocking present, check whether this train is in a section and then set as shunt move after reversion
if advtrains.interlocking and train.il_sections and #train.il_sections > 0 then
@@ -1244,7 +1254,7 @@ function advtrains.invalidate_all_paths(pos)
local tab
if pos then
-- if position given, check occupation system
- tab = advtrains.occ.get_trains_over(pos)
+ tab = advtrains.occ.reverse_lookup_quick(pos)
else
tab = advtrains.trains
end
@@ -1257,7 +1267,7 @@ end
-- Calls invalidate_path_ahead on all trains occupying (having paths over) this node
-- Can be called during train step.
function advtrains.invalidate_all_paths_ahead(pos)
- local tab = advtrains.occ.get_trains_over(pos)
+ local tab = advtrains.occ.reverse_lookup_sel(pos, "first_ahead")
for id,index in pairs(tab) do
local train = advtrains.trains[id]
diff --git a/advtrains/wagonprop_tool.lua b/advtrains/wagonprop_tool.lua
new file mode 100644
index 0000000..2a4a9e2
--- /dev/null
+++ b/advtrains/wagonprop_tool.lua
@@ -0,0 +1,43 @@
+minetest.register_craftitem("advtrains:wagon_prop_tool",{ --craftitem because it does nothing on its own
+ description = attrans("Wagon Properties Tool\nPunch a wagon to view and edit the Wagon Properties"),
+ short_description = attrans("Wagon Properties Tool"),
+ groups = {},
+ inventory_image = "advtrains_wagon_prop_tool.png",
+ wield_image = "advtrains_wagon_prop_tool.png",
+ stack_max = 1,
+ on_use = function(itemstack, user, pointed_thing)
+ local pname = user:get_player_name()
+ if not pname or pname == "" then
+ return
+ end
+
+ --sanity checks in case of clicking the wrong entity/node/nothing
+ if pointed_thing.type ~= "object" then return end --not an entity
+ local object = pointed_thing.ref:get_luaentity()
+ if not object.id then return end --entity doesn't have an id field
+
+ local wagon = advtrains.wagons[object.id] --check if wagon exists in advtrains
+ if not wagon then --not a wagon
+ return
+ end --end sanity checks
+
+ --whitelist protection check
+ if not advtrains.check_driving_couple_protection(pname,wagon.owner,wagon.whitelist) then
+ minetest.chat_send_player(pname, attrans("Insufficient privileges to use this!"))
+ return
+ end
+ object:show_wagon_properties(pname)
+ return itemstack
+ end,
+})
+
+if minetest.get_modpath("default") then --register recipe
+ minetest.register_craft({
+ output = "advtrains:wagon_prop_tool",
+ recipe = {
+ {"advtrains:dtrack_placer","dye:black","default:paper"},
+ {"screwdriver:screwdriver","default:paper","default:paper"},
+ {"","","group:wood"},
+ }
+ })
+end \ No newline at end of file
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index f231546..ef057e5 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -13,7 +13,13 @@ local GETOFF_TP_DELAY = 0.5
local IGNORE_WORLD = advtrains.IGNORE_WORLD
advtrains.wagons = {}
-advtrains.wagon_prototypes = {}
+advtrains.wagon_alias = {}
+advtrains.wagon_prototypes = setmetatable({}, {
+ __index = function(t, k)
+ local _, proto = advtrains.resolve_wagon_alias(k)
+ return proto
+ end
+})
advtrains.wagon_objects = {}
local unload_wgn_range = advtrains.wagon_load_range + 32
@@ -200,7 +206,7 @@ function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direct
end
for listname, _ in pairs(inv:get_lists()) do
if not inv:is_empty(listname) then
- minetest.chat_send_player(puncher:get_player_name(), attrans("The wagon's inventory is not empty!"));
+ minetest.chat_send_player(puncher:get_player_name(), attrans("The wagon's inventory is not empty."));
return
end
end
@@ -280,7 +286,7 @@ function wagon:on_step(dtime)
local data = advtrains.wagons[self.id]
if not pos then
- --atdebug("["..self.id.."][fatal] missing position (object:getpos() returned nil)")
+ --atdebug("["..self.id.."][fatal] missing position (object:get_pos() returned nil)")
return
end
@@ -364,6 +370,15 @@ function wagon:on_step(dtime)
outside = outside .."\n!!! Train off track !!!"
end
+ -- liquid container: display liquid contents in infotext
+ if self.techage_liquid_capacity then
+ if data.techage_liquid and data.techage_liquid.name then
+ outside = outside .."\nLiquid: "..data.techage_liquid.name..", "..data.techage_liquid.amount.." units"
+ else
+ outside = outside .."\nLiquid: empty"
+ end
+ end
+
if self.infotext_cache~=outside then
self.object:set_properties({infotext=outside})
self.infotext_cache=outside
@@ -689,7 +704,7 @@ function wagon:on_rightclick(clicker)
end
local doors_open = self:train().door_open~=0 or clicker:get_player_control().sneak
- local allow, rsn=false, "Wagon has no seats!"
+ local allow, rsn=false, attrans("This wagon has no seats.")
for _,sgr in ipairs(self.assign_to_seat_group) do
allow, rsn = self:check_seat_group_access(pname, sgr)
if allow then
@@ -700,16 +715,16 @@ function wagon:on_rightclick(clicker)
self:get_on(clicker, seatid)
return
else
- rsn="Wagon is full."
+ rsn=attrans("This wagon is full.")
end
else
- rsn="Doors are closed! (try holding sneak key!)"
+ rsn=attrans("Doors are closed! (Try holding sneak key!)")
end
end
end
end
end
- minetest.chat_send_player(pname, attrans("Can't get on: "..rsn))
+ minetest.chat_send_player(pname, rsn or attrans("You can't get on this wagon."))
else
self:show_get_on_form(pname)
end
@@ -805,8 +820,8 @@ function wagon:get_off(seatno)
end
--if not door_entry, or paths missing, fall back to old method
--atdebug("using fallback")
- local objpos=advtrains.round_vector_floor_y(self.object:getpos())
- local yaw=self.object:getyaw()
+ local objpos=advtrains.round_vector_floor_y(self.object:get_pos())
+ local yaw=self.object:get_yaw()
local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4)
local offp
--abuse helper function
@@ -859,6 +874,7 @@ function wagon:show_wagon_properties(pname)
]]
local data = advtrains.wagons[self.id]
local form="size[5,5]"
+ form=form.."label[0.2,0;"..attrans("This Wagon ID")..": "..self.id.."]"
form = form .. "field[0.5,1;4.5,1;whitelist;Allow these players to access your wagon:;"..minetest.formspec_escape(data.whitelist or "").."]"
form = form .. "field[0.5,2;4.5,1;roadnumber;Wagon road number:;"..minetest.formspec_escape(data.roadnumber or "").."]"
local fc = ""
@@ -883,7 +899,7 @@ end
--BordCom
local function checkcouple(ent)
- if not ent or not ent:getyaw() then
+ if not ent or not ent:get_yaw() then
return nil
end
local le = ent:get_luaentity()
@@ -962,6 +978,7 @@ function wagon:show_bordcom(pname)
local linhei
local form = "size[11,9]label[0.5,0;AdvTrains Boardcom v0.1]"
+ form=form.."textarea[7.5,0.05;10,1;;"..attrans("Train ID")..": "..(minetest.formspec_escape(train.id or ""))..";]"
form=form.."textarea[0.5,1.5;7,1;text_outside;"..attrans("Text displayed outside on train")..";"..(minetest.formspec_escape(train.text_outside or "")).."]"
form=form.."textarea[0.5,3;7,1;text_inside;"..attrans("Text displayed inside train")..";"..(minetest.formspec_escape(train.text_inside or "")).."]"
form=form.."field[7.5,1.75;3,1;line;"..attrans("Line")..";"..(minetest.formspec_escape(train.line or "")).."]"
@@ -1079,12 +1096,11 @@ function wagon:handle_bordcom_fields(pname, formname, fields)
for i, tpid in ipairs(train.trainparts) do
if fields["dcpl_"..i] then
advtrains.safe_decouple_wagon(tpid, pname)
- elseif fields["wgprp"..i] then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==tpid and data.owner==pname then
- wagon:show_wagon_properties(pname)
- return
- end
+ elseif fields["wgprp"..i] and data.owner==pname then
+ local wagon = advtrains.get_wagon_entity(tpid)
+ if wagon then
+ wagon:show_wagon_properties(pname)
+ return
end
end
end
@@ -1130,44 +1146,48 @@ end
minetest.register_on_player_receive_fields(function(player, formname, fields)
local uid=string.match(formname, "^advtrains_geton_(.+)$")
if uid then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid then
- local data = advtrains.wagons[wagon.id]
- if fields.inv then
- if wagon.has_inventory and wagon.get_inventory_formspec then
- minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name(), make_inv_name(uid)))
- end
- elseif fields.seat then
- local val=minetest.explode_textlist_event(fields.seat)
- if val and val.type~="INV" and not data.seatp[player:get_player_name()] then
- --get on
- wagon:get_on(player, val.index)
- --will work with the new close_formspec functionality. close exactly this formspec.
- minetest.show_formspec(player:get_player_name(), formname, "")
- end
+ local wagon = advtrains.get_wagon_entity(uid)
+ if wagon then
+ local data = advtrains.wagons[wagon.id]
+ if fields.inv then
+ if wagon.has_inventory and wagon.get_inventory_formspec then
+ minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name(), make_inv_name(uid)))
+ end
+ elseif fields.seat then
+ local val=minetest.explode_textlist_event(fields.seat)
+ if val and val.type~="INV" and not data.seatp[player:get_player_name()] then
+ --get on
+ wagon:get_on(player, val.index)
+ --will work with the new close_formspec functionality. close exactly this formspec.
+ minetest.show_formspec(player:get_player_name(), formname, "")
end
end
end
+ return true
end
+
uid=string.match(formname, "^advtrains_seating_(.+)$")
if uid then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid then
- local pname=player:get_player_name()
- local no=wagon:get_seatno(pname)
- if no then
- if wagon.seat_groups then
- wagon:seating_from_key_helper(pname, fields, no)
- end
+ local wagon = advtrains.get_wagon_entity(uid)
+ if wagon then
+ local pname=player:get_player_name()
+ local no=wagon:get_seatno(pname)
+ if no then
+ if wagon.seat_groups then
+ wagon:seating_from_key_helper(pname, fields, no)
end
end
end
+ return true
end
+
uid=string.match(formname, "^advtrains_prop_(.+)$")
if uid then
local pname=player:get_player_name()
local data = advtrains.wagons[uid]
- if pname~=data.owner and not minetest.check_player_privs(pname, {train_admin = true}) then
+ if not data then
+ return true
+ elseif pname~=data.owner and not minetest.check_player_privs(pname, {train_admin = true}) then
return true
end
if fields.save or not fields.quit then
@@ -1189,29 +1209,32 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
wagon.show_wagon_properties({id=uid}, pname)
end
end
+ return true
end
uid=string.match(formname, "^advtrains_bordcom_(.+)$")
if uid then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid then
- wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
- end
+ local wagon = advtrains.get_wagon_entity(uid)
+ if wagon then
+ wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
end
+ return true
end
+
uid=string.match(formname, "^advtrains_inv_(.+)$")
if uid then
local pname=player:get_player_name()
local data = advtrains.wagons[uid]
if fields.prop and data.owner==pname then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid and data.owner==pname then
- wagon:show_wagon_properties(pname)
- --wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
- end
+ local wagon = advtrains.get_wagon_entity(uid)
+ if wagon then
+ wagon:show_wagon_properties(pname)
+ --wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
end
end
+ return true
end
end)
+
function wagon:seating_from_key_helper(pname, fields, no)
local data = advtrains.wagons[self.id]
local sgr=self.seats[no].group
@@ -1238,7 +1261,7 @@ function wagon:seating_from_key_helper(pname, fields, no)
self:show_bordcom(pname)
end
if fields.dcwarn then
- minetest.chat_send_player(pname, attrans("Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!"))
+ minetest.chat_send_player(pname, attrans("Doors are closed. Use Sneak+rightclick to ignore the closed doors and get off."))
end
if fields.off then
self:get_off(no)
@@ -1247,10 +1270,10 @@ end
function wagon:check_seat_group_access(pname, sgr)
local data = advtrains.wagons[self.id]
if self.seat_groups[sgr].driving_ctrl_access and not (advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)) then
- return false, "Not allowed to access a driver stand!"
+ return false, attrans("You are not allowed to access the driver stand.")
end
if self.seat_groups[sgr].driving_ctrl_access then
- advtrains.log("Drive", pname, self.object:getpos(), self:train().text_outside)
+ advtrains.log("Drive", pname, self.object:get_pos(), self:train().text_outside)
end
return true
end
@@ -1308,11 +1331,33 @@ function advtrains.get_wagon_prototype(data)
data.type = data.entity_name
data.entity_name = nil
end
- if not wt or not advtrains.wagon_prototypes[wt] then
+ local rt, proto = advtrains.resolve_wagon_alias(wt)
+ if not rt then
atwarn("Unable to load wagon type",wt,", using placeholder")
- wt="advtrains:wagon_placeholder"
+ rt = "advtrains:wagon_placeholder"
+ proto = advtrains.wagon_prototypes[rt]
+ end
+ return rt, proto
+end
+
+function advtrains.register_wagon_alias(src, dst)
+ advtrains.wagon_alias[src] = dst
+end
+
+local function recursive_resolve_alias(name, seen)
+ local prototype = rawget(advtrains.wagon_prototypes, name)
+ if prototype then
+ return name, prototype
end
- return wt, advtrains.wagon_prototypes[wt]
+ local resolved = advtrains.wagon_alias[name]
+ if resolved and not seen[resolved] then
+ seen[name] = true
+ return recursive_resolve_alias(resolved, seen)
+ end
+end
+
+function advtrains.resolve_wagon_alias(name)
+ return recursive_resolve_alias(name, {})
end
function advtrains.standard_inventory_formspec(self, pname, invname)
@@ -1359,13 +1404,23 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
groups = wagon_groups,
on_place = function(itemstack, placer, pointed_thing)
- if not pointed_thing.type == "node" then
+ if pointed_thing.type ~= "node" then
return
end
+
+ local pos = pointed_thing.under
+ local node = minetest.get_node(pos)
+ local pointed_def = minetest.registered_nodes[node.name]
+ if pointed_def and pointed_def.on_rightclick then
+ local controls = placer:get_player_control()
+ if not controls.sneak then
+ return pointed_def.on_rightclick(pos, node, placer, itemstack, pointed_thing)
+ end
+ end
+
local pname = placer:get_player_name()
- local node=minetest.get_node_or_nil(pointed_thing.under)
- if not node then atprint("[advtrains]Ignore at placer position") return itemstack end
+ if node.name == "ignore" then atprint("[advtrains]Ignore at placer position") return itemstack end
local nodename=node.name
if(not advtrains.is_track(nodename)) then
atprint("no track here, not placing.")
@@ -1375,14 +1430,14 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
minetest.chat_send_player(pname, "You don't have the train_operator privilege.")
return itemstack
end
- if not minetest.check_player_privs(placer, {train_admin = true }) and minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
+ if not minetest.check_player_privs(placer, {train_admin = true }) and minetest.is_protected(pos, placer:get_player_name()) then
return itemstack
end
local tconns=advtrains.get_track_connections(node.name, node.param2)
local yaw = placer:get_look_horizontal()
local plconnid = advtrains.yawToClosestConn(yaw, tconns)
- local prevpos = advtrains.get_adjacent_rail(pointed_thing.under, tconns, plconnid)
+ local prevpos = advtrains.get_adjacent_rail(pos, tconns, plconnid)
if not prevpos then
minetest.chat_send_player(pname, "The track you are trying to place the wagon on is not long enough!")
return
@@ -1390,7 +1445,7 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
local wid = advtrains.create_wagon(sysname, pname)
- local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, {wid})
+ local id=advtrains.create_new_train_at(pos, plconnid, 0, {wid})
if not advtrains.is_creative(pname) then
itemstack:take_item()
@@ -1477,4 +1532,28 @@ function advtrains.get_wagon_at_index(train_id, w_index)
end
-- nothing found, dist must be further back
return nil
-end \ No newline at end of file
+end
+
+function advtrains.get_wagon_entity(wagon_id)
+ if not advtrains.wagons[wagon_id] then return end
+ local object = advtrains.wagon_objects[wagon_id]
+ if object then
+ return object:get_luaentity()
+ end
+end
+
+function advtrains.next_wagon_entity_in_train(train, i)
+ local wagon_id = train.trainparts[i + 1]
+ if wagon_id then
+ local wagon = advtrains.get_wagon_entity(wagon_id)
+ if wagon then
+ return i + 1, wagon
+ end
+ end
+end
+
+function advtrains.wagon_entity_pairs_in_train(train_id)
+ local train = advtrains.trains[train_id]
+ if not train then return function() end end
+ return advtrains.next_wagon_entity_in_train, train, 0
+end
diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua
index 37f751a..3ab5686 100644
--- a/advtrains_interlocking/route_prog.lua
+++ b/advtrains_interlocking/route_prog.lua
@@ -219,19 +219,32 @@ end
local player_rte_prog = {}
-function advtrains.interlocking.init_route_prog(pname, sigd)
+function advtrains.interlocking.init_route_prog(pname, sigd, default_route)
if not minetest.check_player_privs(pname, "interlocking") then
minetest.chat_send_player(pname, "Insufficient privileges to use this!")
return
end
- player_rte_prog[pname] = {
+ local rp = {
origin = sigd,
- route = {
- name = "PROG["..pname.."]",
- },
- tmp_lcks = {},
}
- advtrains.interlocking.visualize_route(sigd, player_rte_prog[pname].route, "prog_"..pname, player_rte_prog[pname].tmp_lcks, pname)
+ if default_route then
+ rp.route = table.copy(default_route)
+
+ -- "Step back one section", but keeping turnouts
+ local last_route = rp.route[#rp.route]
+ if last_route then
+ rp.tmp_lcks = last_route.locks
+ rp.route[#rp.route] = nil
+ end
+ rp.route.name = "PROG["..pname.."]"
+ else
+ rp.route = {
+ name = "PROG["..pname.."]"
+ }
+ rp.tmp_lcks = {}
+ end
+ player_rte_prog[pname] = rp
+ advtrains.interlocking.visualize_route(sigd, rp.route, "prog_"..pname, rp.tmp_lcks, pname)
minetest.chat_send_player(pname, "Route programming mode active. Punch TCBs to add route segments, punch turnouts to lock them.")
end
diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua
index b01d449..478e8dc 100644
--- a/advtrains_interlocking/route_ui.lua
+++ b/advtrains_interlocking/route_ui.lua
@@ -25,7 +25,7 @@ function atil.show_route_edit_form(pname, sigd, routeid)
local route = tcbs.routes[routeid]
if not route then return end
- local form = "size[9,10]label[0.5,0.2;Route overview]"
+ local form = "size[9,11]label[0.5,0.2;Route overview]"
form = form.."field[0.8,1.2;6.5,1;name;Route name;"..minetest.formspec_escape(route.name).."]"
form = form.."button[7.0,0.9;1.5,1;setname;Set]"
@@ -114,11 +114,12 @@ function atil.show_route_edit_form(pname, sigd, routeid)
form = form.."button[3.5,6;2,1;noautogen;Clr Autogen]"
end
form = form.."button[5.5,6;3,1;delete;Delete Route]"
+ form = form.."button[5.5,7;3,1;newfrom;New From Route]"
--atdebug(route.ars)
form = form.."style[ars;font=mono]"
- form = form.."textarea[0.8,7.3;5,3;ars;ARS Rule List;"..atil.ars_to_text(route.ars).."]"
- form = form.."button[5.5,7.23;3,1;savears;Save ARS List]"
+ form = form.."textarea[0.8,8.3;5,3;ars;ARS Rule List;"..atil.ars_to_text(route.ars).."]"
+ form = form.."button[5.5,8.23;3,1;savears;Save ARS List]"
minetest.show_formspec(pname, "at_il_routeedit_"..minetest.pos_to_string(sigd.p).."_"..sigd.s.."_"..routeid, form)
@@ -185,6 +186,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
table.remove(tcbs.routes, routeid)
advtrains.interlocking.show_signalling_form(sigd, pname)
end
+
+ if fields.newfrom then
+ advtrains.interlocking.init_route_prog(pname, sigd, route)
+ minetest.close_formspec(pname, formname)
+ tcbs.ars_ignore_next = nil
+ return
+ end
if fields.ars and fields.savears then
route.ars = atil.text_to_ars(fields.ars)
diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua
index 0207519..a72f644 100644
--- a/advtrains_interlocking/routesetting.lua
+++ b/advtrains_interlocking/routesetting.lua
@@ -365,7 +365,13 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel)
end
if newrte then tcbs.routeset = newrte end
--atdebug("Setting:",tcbs.routeset)
- local succ, rsn, cbts, cblk = ilrs.set_route(sigd, tcbs.routes[tcbs.routeset])
+ local succ, rsn, cbts, cblk
+ if tcbs.routes[tcbs.routeset] then
+ succ, rsn, cbts, cblk = ilrs.set_route(sigd, tcbs.routes[tcbs.routeset])
+ else
+ succ = false
+ rsn = attrans("Route state changed.")
+ end
if not succ then
tcbs.route_rsn = rsn
--atdebug("Routesetting failed:",rsn)
diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua
index 82a57cf..e7ff685 100755
--- a/advtrains_interlocking/tcb_ts_ui.lua
+++ b/advtrains_interlocking/tcb_ts_ui.lua
@@ -494,6 +494,13 @@ minetest.register_entity("advtrains_interlocking:tcbmarker", {
static_save = false,
})
+function advtrains.interlocking.remove_tcb_marker_pts(pts)
+ if markerent[pts] then
+ markerent[pts]:remove()
+ markerent[pts] = nil
+ end
+end
+
function advtrains.interlocking.show_tcb_marker(pos)
--atdebug("showing tcb marker",pos)
local tcb = ildb.get_tcb(pos)
@@ -517,9 +524,7 @@ function advtrains.interlocking.show_tcb_marker(pos)
end
local pts = advtrains.roundfloorpts(pos)
- if markerent[pts] then
- markerent[pts]:remove()
- end
+ advtrains.interlocking.remove_tcb_marker_pts(pts)
local obj = minetest.add_entity(pos, "advtrains_interlocking:tcbmarker")
if not obj then return end
@@ -628,14 +633,22 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle
strtab[#strtab+1] = clr .. minetest.formspec_escape(route.name)
end
form = form.."label[0.5,2.5;Routes:]"
- form = form.."textlist[0.5,3;5,3;rtelist;"..table.concat(strtab, ",").."]"
+ form = form.."textlist[0.5,3;5,3;rtelist;"..table.concat(strtab, ",")
if sel_rte then
+ form = form .. ";" .. sel_rte .."]"
form = form.."button[0.5,6; 5,1;setroute;Set Route]"
form = form.."button[0.5,7;2,1;dsproute;Show]"
if hasprivs then
form = form.."button[3.5,7;2,1;editroute;Edit]"
+ if sel_rte > 1 then
+ form = form .. "button[5.5,4;0.5,0.3;moveup;↑]"
+ end
+ if sel_rte < #strtab then
+ form = form .. "button[5.5,4.7;0.5,0.3;movedown;↓]"
+ end
end
else
+ form = form .. "]"
if tcbs.ars_disabled then
form = form.."label[0.5,6 ;NOTE: ARS is disabled.]"
form = form.."label[0.5,6.5;Routes are not automatically set.]"
@@ -772,6 +785,20 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.noauto then
tcbs.route_auto = false
end
+
+ if sel_rte and tcbs.routes[sel_rte]and not tcbs.routeset then
+ if fields.moveup then
+ if tcbs.routes[sel_rte - 1] then
+ tcbs.routes[sel_rte - 1], tcbs.routes[sel_rte] = tcbs.routes[sel_rte], tcbs.routes[sel_rte - 1]
+ sel_rte = sel_rte - 1
+ end
+ elseif fields.movedown then
+ if tcbs.routes[sel_rte + 1] then
+ tcbs.routes[sel_rte + 1], tcbs.routes[sel_rte] = tcbs.routes[sel_rte], tcbs.routes[sel_rte + 1]
+ sel_rte = sel_rte + 1
+ end
+ end
+ end
advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, true)
return
diff --git a/advtrains_interlocking/tsr_rail.lua b/advtrains_interlocking/tsr_rail.lua
index f302540..c1e3c1c 100644
--- a/advtrains_interlocking/tsr_rail.lua
+++ b/advtrains_interlocking/tsr_rail.lua
@@ -3,13 +3,15 @@
-- Simple rail whose only purpose is to place a TSR on the position, as a temporary solution until the timetable system covers everything.
-- This code resembles the code in lines/stoprail.lua
+local S = attrans
+
local function updateform(pos)
local meta = minetest.get_meta(pos)
local pe = advtrains.encode_pos(pos)
local npr = advtrains.interlocking.npr_rails[pe] or 2
- meta:set_string("infotext", "Point speed restriction: "..npr)
- meta:set_string("formspec", "field[npr;Set point speed restriction:;"..npr.."]")
+ meta:set_string("infotext", S("Point speed restriction: @1",npr))
+ meta:set_string("formspec", "field[npr;"..S("Set point speed restriction:")..";"..npr.."]")
end
@@ -25,11 +27,11 @@ local adefunc = function(def, preset, suffix, rotation)
on_receive_fields = function(pos, formname, fields, player)
local pname = player:get_player_name()
if not minetest.check_player_privs(pname, {interlocking=true}) then
- minetest.chat_send_player(pname, "Interlocking privilege required!")
+ minetest.chat_send_player(pname, S("You are not allowed to configure this track without the @1 privilege.", "interlocking"))
return
end
if minetest.is_protected(pos, pname) then
- minetest.chat_send_player(pname, "This rail is protected!")
+ minetest.chat_send_player(pname, S("You are not allowed to configure this track."))
minetest.record_protection_violation(pos, pname)
return
end
@@ -59,7 +61,7 @@ if minetest.get_modpath("advtrains_train_track") ~= nil then
models_prefix="advtrains_dtrack",
models_suffix=".b3d",
shared_texture="advtrains_dtrack_shared_npr.png",
- description="Point Speed Restriction Rail",
+ description=S("Point Speed Restriction Track"),
formats={},
get_additional_definiton = adefunc,
}, advtrains.trackpresets.t_30deg_straightonly)
diff --git a/advtrains_line_automation/stoprail.lua b/advtrains_line_automation/stoprail.lua
index 55a4785..6c74a3d 100644
--- a/advtrains_line_automation/stoprail.lua
+++ b/advtrains_line_automation/stoprail.lua
@@ -28,7 +28,7 @@ local function show_stoprailform(pos, player)
local pe = advtrains.encode_pos(pos)
local pname = player:get_player_name()
if minetest.is_protected(pos, pname) then
- minetest.chat_send_player(pname, "Position is protected!")
+ minetest.chat_send_player(pname, attrans("You are not allowed to configure this track."))
return
end
@@ -73,7 +73,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local pos = advtrains.decode_pos(pe)
if pos then
if minetest.is_protected(pos, pname) then
- minetest.chat_send_player(pname, "Position is protected!")
+ minetest.chat_send_player(pname, attrans("You are not allowed to configure this track."))
return
end
@@ -94,7 +94,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then
stdata.stn = fields.stn
else
- minetest.chat_send_player(pname, "Station code '"..fields.stn.."' does already exist and is owned by "..stn.owner)
+ minetest.chat_send_player(pname, attrans("Station code \"@1\" already exists and is owned by @2.", fields.stn, stn.owner))
show_stoprailform(pos,player)
return
end
@@ -108,7 +108,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then
stn.name = fields.stnname
else
- minetest.chat_send_player(pname, "Not allowed to edit station name, owned by "..stn.owner)
+ minetest.chat_send_player(pname, attrans("This station is owned by @1. You are not allowed to edit its name.", stn.owner))
end
end
@@ -218,7 +218,7 @@ if minetest.get_modpath("advtrains_train_track") ~= nil then
models_prefix="advtrains_dtrack",
models_suffix=".b3d",
shared_texture="advtrains_dtrack_shared_stop.png",
- description="Station/Stop Rail",
+ description=attrans("Station/Stop Track"),
formats={},
get_additional_definiton = adefunc,
}, advtrains.trackpresets.t_30deg_straightonly)
diff --git a/advtrains_luaautomation/README.md b/advtrains_luaautomation/README.md
index a885075..275653c 100644
--- a/advtrains_luaautomation/README.md
+++ b/advtrains_luaautomation/README.md
@@ -93,6 +93,9 @@ Removes any pending interrupts of this node.
Make this active component send a digiline message on the specified channel.
Not available in init code.
+ - `trainparts(train_id)`
+ returns a table with the ids of the cars the train is composed of, or false if `train_id` is invalid. `train_id` can be replaced with `atc_id` when used in LuaATC Rails.
+
- `atc_send_to_train(<train_id>, <atc_command>)`
Sends the specified ATC command to the train specified by its train id. This happens regardless of where the train is in the world, and can be used to remote-control trains. Returns true on success. If the train ID does not exist, returns false and does nothing. See [atc_command.txt](../atc_command.txt) for the ATC command syntax.
@@ -142,22 +145,16 @@ asp = {
-- the character of call_on and dead_end is purely informative
call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
-
- w_speed = <integer>,
- -- "Warning speed restriction". Supposed for short-term speed
- -- restrictions which always override any other restrictions
- -- imposed by "speed" fields, until lifted by a value of -1
- -- (Example: german Langsamfahrstellen-Signale)
}
```
-As of January 2020, the 'dst', 'call_on' and 'dead_end' fields are not used.
+As of September 2024, the 'dst', 'call_on' and 'dead_end' fields are not used.
#### Lines
The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier.
Part of its functionality is also available in LuaATC:
-- `rwt.*` - all Railway Time functions are included as documented in [the wiki](https://advtrains.de/wiki/doku.php?id=dev:lines:rwt)
+- `rwt.*` - all Railway Time functions are included as documented in [the wiki](https://advtrains.de/wiki/doku.php?id=dev:api:railway_time_api)
- `schedule(rw_time, msg)`, `schedule_in(rw_dtime, msg)`
Schedules an event of type {type="schedule", schedule=true, msg=msg} at (resp. after) the specified railway time (which can be in any format). You can only schedule one event this way. (uses the new lines-internal scheduler)
@@ -273,10 +270,17 @@ Each wagon has a current FC, indicating its next destination.
Returns a table with the entire FC list for each wagon in the train.
Command: `get_fc()`
Result: `{"", "foo!bar", "testing", "fc_1!fc_2!fc_3!?", "hello_world"}`
+
+ - `get_fc_index()`
+ Returns a table with the current FC index for each wagon in the train. Use in conjunction with the result from `get_fc()` to find a the current FC for a wagon.
+ Command: `get_fc_index()`
+ Result: `{1, 1, 1, 2, 1}`
- - `set_fc(fc_list)`
+ - `set_fc(fc_list, reset_index)`
Overwrites the FC list according to a table `fc_list`. A false or nil entry will leave the wagon unaffected, however all others will be overwritten.
- Useful for mass-programming freight trains that use FC-shunting instead of walking to each wagon individually.
+ Useful for mass-programming freight trains that use FC-shunting instead of walking to each wagon individually. If the new FC entry for a wagon is shorter than the old entry, the index will clip to the last FC in the new entry.
+ If `reset_index` is true, all Current FC values will reset to the first entry in the list, instead of remaining at the current index.
+
Example: train has FC lists: `"", "foo!bar", "testing", "fc_1!fc_2!fc_3!?", "hello_world"`
Command: `set_fc({"", "foo!turtle", nil, "4tehlulz", false})`
Result: `""` `"foo!turtle"` `"testing"` `"4tehlulz"` `"hello_world"`
diff --git a/advtrains_luaautomation/active_common.lua b/advtrains_luaautomation/active_common.lua
index 50fb2bc..074d3b3 100644
--- a/advtrains_luaautomation/active_common.lua
+++ b/advtrains_luaautomation/active_common.lua
@@ -1,4 +1,4 @@
-
+local S = atltrans
local ac = {nodes={}}
@@ -14,7 +14,7 @@ end
function ac.after_place_node(pos, player)
local meta=minetest.get_meta(pos)
meta:set_string("formspec", ac.getform(pos, meta))
- meta:set_string("infotext", "LuaATC component, unconfigured.")
+ meta:set_string("infotext", S("Unconfigured LuaATC component"))
local ph=minetest.pos_to_string(pos)
--just get first available key!
for en,_ in pairs(atlatc.envs) do
@@ -43,11 +43,11 @@ function ac.getform(pos, meta_p)
end
local form = "size["..atlatc.CODE_FORM_SIZE.."]"
.."style[code;font=mono]"
- .."label[0,-0.1;Environment]"
+ .."label[0,-0.1;"..S("LuaATC Environment").."]"
.."dropdown[0,0.3;3;env;"..table.concat(envs_asvalues, ",")..";"..sel.."]"
- .."button[5,0.2;2,1;save;Save]"
- .."button[7,0.2;3,1;cle;Clear Local Env.]"
- .."textarea[0.3,1.5;"..atlatc.CODE_FORM_SIZE..";code;Code;"..minetest.formspec_escape(code).."]"
+ .."button[5,0.2;2,1;save;"..S("Save").."]"
+ .."button[7,0.2;3,1;cle;"..S("Clear Local Environment").."]"
+ .."textarea[0.3,1.5;"..atlatc.CODE_FORM_SIZE..";code;"..S("Code")..";"..minetest.formspec_escape(code).."]"
.."label["..atlatc.CODE_FORM_ERRLABELPOS..";"..err.."]"
return form
end
@@ -55,13 +55,17 @@ end
function ac.after_dig_node(pos, node, player)
advtrains.invalidate_all_paths(pos)
advtrains.ndb.clear(pos)
+ atlatc.interrupt.clear_ints_at_pos(pos)
+ if advtrains.lines and advtrains.lines.sched then
+ advtrains.lines.sched.discard_all(advtrains.encode_pos(pos))
+ end
local ph=minetest.pos_to_string(pos)
ac.nodes[ph]=nil
end
function ac.on_receive_fields(pos, formname, fields, player)
if not minetest.check_player_privs(player:get_player_name(), {atlatc=true}) then
- minetest.chat_send_player(player:get_player_name(), "Missing privilege: atlatc - Operation cancelled!")
+ minetest.chat_send_player(player:get_player_name(), S("You are not allowed to configure this LuaATC component without the @1 privilege.", "atlatc"))
return
end
@@ -91,9 +95,9 @@ function ac.on_receive_fields(pos, formname, fields, player)
meta:set_string("formspec", ac.getform(pos, meta))
if nodetbl.env then
- meta:set_string("infotext", "LuaATC component, assigned to environment '"..nodetbl.env.."'")
+ meta:set_string("infotext", S("LuaATC component assigned to environment '@1'", nodetbl.env))
else
- meta:set_string("infotext", "LuaATC component, invalid enviroment set!")
+ meta:set_string("infotext", S("LuaATC component assigned to an invalid environment"))
end
end
@@ -168,7 +172,7 @@ function ac.run_in_env(pos, evtdata, customfct_p, ignore_no_code)
atlatc.active.nodes[ph].err=dataout
env:log("error", "LuaATC component at",ph,": LUA Error:",dataout)
if meta then
- meta:set_string("infotext", "LuaATC component, ERROR:"..dataout)
+ meta:set_string("infotext", S("LuaATC component with error: @1", dataout))
end
--TODO temporary
--if customfct.atc_id then
diff --git a/advtrains_luaautomation/atc_rail.lua b/advtrains_luaautomation/atc_rail.lua
index aac11f0..dd26f51 100755..100644
--- a/advtrains_luaautomation/atc_rail.lua
+++ b/advtrains_luaautomation/atc_rail.lua
@@ -95,11 +95,19 @@ function r.fire_event(pos, evtdata, appr_internal)
if not train_id then return end
local fc_list = {}
for index,wagon_id in ipairs(train.trainparts) do
- fc_list[index] = table.concat(advtrains.wagons[wagon_id].fc,"!") or ""
+ fc_list[index] = table.concat(advtrains.wagons[wagon_id].fc or {},"!")
end
return fc_list
end,
- set_fc = function(fc_list)
+ get_fc_index = function()
+ if not train_id then return end
+ local fc_index_list = {}
+ for widx, wagon_id in ipars(train.trainparts) do
+ fc_index_list[widx] = advtrains.wagons[wagon_id].fcind or 1
+ end
+ return fc_index_list
+ end,
+ set_fc = function(fc_list,reset_index)
assertt(fc_list, "table")
if not train_id then return false end
-- safety type-check for entered values
@@ -113,11 +121,12 @@ function r.fire_event(pos, evtdata, appr_internal)
if fc_list[index] then -- has FC to enter to this wagon
local data = advtrains.wagons[wagon_id]
if data then -- wagon actually exists
- for _,wagon in pairs(minetest.luaentities) do -- find wagon entity
- if wagon.is_wagon and wagon.initialized and wagon.id==wagon_id then
- wagon.set_fc(data,fc_list[index]) -- overwrite to new FC
- break -- no point cycling through every other entity. we found our wagon
- end
+ --effectively copyied from wagons.lua, allowing for the :split function and reset_index
+ data.fc = fc_list[index]:split("!")
+ if reset_index or not data.fcind then
+ data.fcind = 1
+ elseif data.fcind > #data.fc then
+ data.fcind = #data.fc
end
end
end
@@ -219,7 +228,7 @@ advtrains.register_tracks("default", {
models_prefix="advtrains_dtrack",
models_suffix=".b3d",
shared_texture="advtrains_dtrack_shared_atc.png",
- description=atltrans("LuaATC Rail"),
+ description=atltrans("LuaATC Track"),
formats={},
get_additional_definiton = function(def, preset, suffix, rotation)
return {
diff --git a/advtrains_luaautomation/environment.lua b/advtrains_luaautomation/environment.lua
index d85bedc..b54d45c 100644
--- a/advtrains_luaautomation/environment.lua
+++ b/advtrains_luaautomation/environment.lua
@@ -153,6 +153,12 @@ local static_env = {
local pos=atlatc.pcnaming.resolve_pos(parpos, "interrupt_pos")
atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg})
end,
+ train_parts = function(train_id)
+ if not train_id then return false end
+ local train = advtrains.trains[train_id]
+ if not train then return false end
+ return table.copy(train.trainparts or {})
+ end,
-- sends an atc command to train regardless of where it is in the world
atc_send_to_train = function(train_id, command)
assertt(command, "string")
@@ -164,6 +170,9 @@ local static_env = {
return false
end
end,
+ get_slowdown = function()
+ return advtrains.global_slowdown
+ end
}
-- If interlocking is present, enable route setting functions
@@ -221,7 +230,7 @@ if advtrains.interlocking then
end
static_env.set_aspect = function(signal, asp)
local pos = atlatc.pcnaming.resolve_pos(signal)
- return advtrains.interlocking.signal_set_aspect(pos)
+ return advtrains.interlocking.signal_set_aspect(pos,asp)
end
--section_occupancy()
@@ -230,7 +239,7 @@ if advtrains.interlocking then
ts_id = tostring(ts_id)
local response = advtrains.interlocking.db.get_ts(ts_id)
if not response then return false end
- return table.copy(response.trains)
+ return (response.trains and table.copy(response.trains)) or {}
end
end
@@ -259,6 +268,11 @@ if advtrains.lines then
}
end
+
+atlatc.register_function = function (name, f)
+ static_env[name] = f
+end
+
for _, name in pairs(safe_globals) do
static_env[name] = _G[name]
end
diff --git a/advtrains_luaautomation/init.lua b/advtrains_luaautomation/init.lua
index c51aa71..b359142 100644
--- a/advtrains_luaautomation/init.lua
+++ b/advtrains_luaautomation/init.lua
@@ -2,15 +2,15 @@
-- Lua automation features for advtrains
-- Uses global table 'atlatc' (AdvTrains_LuaATC)
---TODO: re-add localization (if merging localization, discard this hunk please)
-atltrans = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end
+atltrans = attrans
+local S = atltrans
--Privilege
--Only trusted players should be enabled to build stuff which can break the server.
atlatc = { envs = {}}
-minetest.register_privilege("atlatc", { description = "Player can place and modify LUA ATC components. Grant with care! Allows to execute bad LUA code.", give_to_singleplayer = false, default= false })
+minetest.register_privilege("atlatc", { description = S("Can place and configure LuaATC components, including execute potentially harmful Lua code"), give_to_singleplayer = false, default= false })
--Size of code input forms in X,Y notation. Must be at least 10x10
atlatc.CODE_FORM_SIZE = "15,12"
diff --git a/advtrains_luaautomation/mesecon_controller.lua b/advtrains_luaautomation/mesecon_controller.lua
index bffff84..6981839 100644
--- a/advtrains_luaautomation/mesecon_controller.lua
+++ b/advtrains_luaautomation/mesecon_controller.lua
@@ -6,6 +6,7 @@
-- From Mesecons mod https://mesecons.net/
-- (c) Jeija and Contributors
+local S = atltrans
local BASENAME = "advtrains_luaautomation:mesecon_controller"
local rules = {
@@ -207,7 +208,7 @@ for d = 0, 1 do
}
minetest.register_node(node_name, {
- description = "LuaATC Mesecon Controller",
+ description = S("LuaATC Mesecon Controller"),
drawtype = "nodebox",
tiles = {
top,
diff --git a/advtrains_luaautomation/operation_panel.lua b/advtrains_luaautomation/operation_panel.lua
index c118ff3..8e12651 100755..100644
--- a/advtrains_luaautomation/operation_panel.lua
+++ b/advtrains_luaautomation/operation_panel.lua
@@ -1,3 +1,4 @@
+local S = atltrans
local function on_punch(pos,node,player)
atlatc.interrupt.add(0, pos, {type="punch", punch=true, name=player:get_player_name()})
@@ -7,7 +8,7 @@ end
minetest.register_node("advtrains_luaautomation:oppanel", {
drawtype = "normal",
tiles={"atlatc_oppanel.png"},
- description = "LuaATC operation panel",
+ description = S("LuaATC Operation Panel"),
groups = {
cracky = 1,
save_in_at_nodedb=1,
diff --git a/advtrains_luaautomation/pcnaming.lua b/advtrains_luaautomation/pcnaming.lua
index 71f4d9a..0089ae7 100644
--- a/advtrains_luaautomation/pcnaming.lua
+++ b/advtrains_luaautomation/pcnaming.lua
@@ -2,6 +2,8 @@
--a.k.a Passive component naming
--Allows to assign names to passive components, so they can be called like:
--setstate("iamasignal", "green")
+local S = atltrans
+
atlatc.pcnaming={name_map={}}
function atlatc.pcnaming.load(stuff)
if type(stuff)=="table" then
@@ -22,8 +24,11 @@ function atlatc.pcnaming.resolve_pos(pos, func_name)
error("Invalid position supplied to " .. (func_name or "???")..": " .. dump(pos))
end
+
+local pcrename = {}
+
minetest.register_craftitem("advtrains_luaautomation:pcnaming",{
- description = attrans("Passive Component Naming Tool\n\nRight-click to name a passive component."),
+ description = S("Passive Component Naming Tool\n\nRight-click to name a passive component."),
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "atlatc_pcnaming.png",
wield_image = "atlatc_pcnaming.png",
@@ -34,7 +39,7 @@ minetest.register_craftitem("advtrains_luaautomation:pcnaming",{
return
end
if not minetest.check_player_privs(pname, {atlatc=true}) then
- minetest.chat_send_player(pname, "Missing privilege: atlatc")
+ minetest.chat_send_player(pname, S("You are not allowed to name LuaATC passive components without the @1 privilege.", "atlatc"))
return
end
if pointed_thing.type=="node" then
@@ -43,6 +48,7 @@ minetest.register_craftitem("advtrains_luaautomation:pcnaming",{
minetest.record_protection_violation(pos, pname)
return
end
+
local node = advtrains.ndb.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if node.name and (
@@ -57,16 +63,17 @@ minetest.register_craftitem("advtrains_luaautomation:pcnaming",{
pn=name
end
end
- minetest.show_formspec(pname, "atlatc_naming_"..minetest.pos_to_string(pos), "field[pn;Set name of component (empty to clear);"..minetest.formspec_escape(pn).."]")
+ pcrename[pname] = pos
+ minetest.show_formspec(pname, "atlatc_naming", "field[pn;"..S("Set name of component (empty to clear)")..";"..minetest.formspec_escape(pn).."]")
end
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
- local pts=string.match(formname, "^atlatc_naming_(.+)")
- if pts then
- local pos=minetest.string_to_pos(pts)
- if fields.pn then
+ if formname == "atlatc_naming" then
+ local pname = player:get_player_name()
+ local pos=pcrename[pname]
+ if fields.pn and pos then
--first remove all occurences
for name, npos in pairs(atlatc.pcnaming.name_map) do
if vector.equals(npos, pos) then
diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua
index c449416..c0e74ea 100755
--- a/advtrains_signals_ks/init.lua
+++ b/advtrains_signals_ks/init.lua
@@ -333,6 +333,8 @@ for _, rtab in ipairs({
danger = {asp = { main = false, shunt = false }, n = "shuntd", ici=true},
shuntd = {asp = { main = false, shunt = true } , n = "danger"},
}) do
+ local sbox = table.copy(rtab.sbox)
+ sbox[5] = 0
minetest.register_node("advtrains_signals_ks:ra_"..typ.."_"..rot, {
description = "Ks Shunting Signal",
drawtype = "mesh",
@@ -346,7 +348,11 @@ for _, rtab in ipairs({
paramtype2 = "facedir",
selection_box = {
type = "fixed",
- fixed = {-1/4, -1/2, -1/4, 1/4, 0, 1/4}
+ fixed = {sbox, rotation_sbox}
+ },
+ collision_box = {
+ type = "fixed",
+ fixed = sbox,
},
groups = {
cracky = 2,
diff --git a/advtrains_train_track/init.lua b/advtrains_train_track/init.lua
index 5065155..32e1235 100644
--- a/advtrains_train_track/init.lua
+++ b/advtrains_train_track/init.lua
@@ -678,8 +678,45 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end)
+local function load_wagon(wagon_id, node_inv, node_fc, unload)
+ local inv_modified = false
+ local w_inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..wagon_id})
+ if w_inv and w_inv:get_list("box") then
+
+ local wagon_data = advtrains.wagons[wagon_id]
+ local wagon_fc
+ if wagon_data.fc then
+ if not wagon_data.fcind then wagon_data.fcind = 1 end
+ wagon_fc = tostring(wagon_data.fc[wagon_data.fcind]) or ""
+ end
+
+ if node_fc == "" or wagon_fc == node_fc then
+ if not unload then
+ for _, item in ipairs(node_inv:get_list("main")) do
+ if w_inv:get_list("box") and w_inv:room_for_item("box", item) then
+ w_inv:add_item("box", item)
+ node_inv:remove_item("main", item)
+ if item.name ~= "" then inv_modified = true end
+ end
+ end
+ else
+ for _, item in ipairs(w_inv:get_list("box")) do
+ if node_inv:get_list("main") and node_inv:room_for_item("main", item) then
+ w_inv:remove_item("box", item)
+ node_inv:add_item("main", item)
+ if item.name ~= "" then inv_modified = true end
+ end
+ end
+ end
+ end
+ end
+ return inv_modified
+end
-local function train_load(pos, train_id, unload)
+local function load_entire_train(pos, train_id, unload) -- flood load when not in an active area
+ if advtrains.is_node_loaded(pos) then -- leave the loading to the nodetimer if area is loaded
+ return
+ end
local train=advtrains.trains[train_id]
local below = get_far_node({x=pos.x, y=pos.y-1, z=pos.z})
if not string.match(below.name, "chest") then
@@ -692,43 +729,60 @@ local function train_load(pos, train_id, unload)
--track section is disabled
return
end
-
- local inv = minetest.get_inventory({type="node", pos={x=pos.x, y=pos.y-1, z=pos.z}})
- if inv and train.velocity < 2 then
- for k, v in ipairs(train.trainparts) do
- local i=minetest.get_inventory({type="detached", name="advtrains_wgn_"..v})
- if i and i:get_list("box") then
-
- local wagon_data = advtrains.wagons[v]
- local wagon_fc
- if wagon_data.fc then
- if not wagon_data.fcind then wagon_data.fcind = 1 end
- wagon_fc = tostring(wagon_data.fc[wagon_data.fcind]) or ""
- end
-
- if node_fc == "" or wagon_fc == node_fc then
- if not unload then
- for _, item in ipairs(inv:get_list("main")) do
- if i:get_list("box") and i:room_for_item("box", item) then
- i:add_item("box", item)
- inv:remove_item("main", item)
- end
- end
- else
- for _, item in ipairs(i:get_list("box")) do
- if inv:get_list("main") and inv:room_for_item("main", item) then
- i:remove_item("box", item)
- inv:add_item("main", item)
- end
+ local node_inv = minetest.get_inventory({type="node", pos={x=pos.x, y=pos.y-1, z=pos.z}})
+ if node_inv and train.velocity <= 2 then
+ for _, wagon_id in ipairs(train.trainparts) do
+ load_wagon(wagon_id, node_inv, node_fc, unload)
+ end
+ end
+end
+
+local function load_wagon_on_timer(pos, unload) -- loading ramp when in an active area
+ if not advtrains.is_node_loaded(pos) then -- leave the loading for the flood load function. we're out of area
+ return true -- reset the nodetimer until the node is loaded again
+ end
+ local tid, tidx = advtrains.get_train_at_pos(pos)
+ if not tid or tid == "" then
+ return true
+ end -- no train to load.
+
+ local train = advtrains.trains[tid]
+ local below = get_far_node({x=pos.x, y=pos.y-1, z=pos.z})
+ if not string.match(below.name, "chest") then
+ atprint("this is not a chest! at "..minetest.pos_to_string(pos))
+ return true
+ end
+ local node_fc = minetest.get_meta(pos):get_string("fc") or ""
+ if node_fc == "#" then
+ --track section is disabled
+ return true
+ end
+ local node_inv = minetest.get_inventory({type="node", pos={x=pos.x, y=pos.y-1, z=pos.z}})
+ if node_inv and train.velocity <= 2 then
+ local _, wagon_id, wagon_data = advtrains.get_wagon_at_index(tid, tidx)
+ if wagon_id then
+ local inv_modified = load_wagon(wagon_id, node_inv, node_fc, unload)
+ if inv_modified then
+ if advtrains.wagon_prototypes[advtrains.get_wagon_prototype(wagon_data)].set_textures then
+ local wagon_object = advtrains.wagon_objects[wagon_id]
+ if wagon_object and wagon_data then
+ local ent = wagon_object:get_luaentity()
+ if ent and ent.set_textures then
+ ent:set_textures(wagon_data)
end
end
end
end
end
end
+ return true
end
-
+local nodetimer_interval = minetest.settings:get("advtrains_loading_track_timer") or 1
+local function start_nodetimer(pos)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(nodetimer_interval)
+end
advtrains.register_tracks("default", {
nodename_prefix="advtrains:dtrack_unload",
@@ -747,9 +801,16 @@ advtrains.register_tracks("default", {
on_rightclick = function(pos, node, player)
show_fc_formspec(pos, player)
end,
+ after_place_node = function(pos)
+ advtrains.ndb.update(pos)
+ start_nodetimer(pos)
+ end,
+ on_timer = function(pos)
+ return load_wagon_on_timer(pos, true)
+ end,
advtrains = {
on_train_enter = function(pos, train_id)
- train_load(pos, train_id, true)
+ load_entire_train(pos, train_id, true)
end,
},
}
@@ -772,9 +833,16 @@ advtrains.register_tracks("default", {
on_rightclick = function(pos, node, player)
show_fc_formspec(pos, player)
end,
+ after_place_node = function(pos)
+ advtrains.ndb.update(pos)
+ start_nodetimer(pos)
+ end,
+ on_timer = function(pos)
+ return load_wagon_on_timer(pos, false)
+ end,
advtrains = {
on_train_enter = function(pos, train_id)
- train_load(pos, train_id, false)
+ load_entire_train(pos, train_id, false)
end,
},
}
@@ -788,7 +856,6 @@ if minetest.get_modpath("basic_materials") then
elseif minetest.get_modpath("technic") then
loader_core = "technic:control_logic_unit"
end
---print("Loader Core: "..loader_core)
minetest.register_craft({
type="shapeless",
diff --git a/advtrains_train_track/settingtypes.txt b/advtrains_train_track/settingtypes.txt
new file mode 100644
index 0000000..0af0081
--- /dev/null
+++ b/advtrains_train_track/settingtypes.txt
@@ -0,0 +1,4 @@
+# Set the nodetimer delay for the loading tracks.
+# A longer delay may cause wagons to be missed if the pass over too fast.
+# A shorter delay may cause lag as wagons are checked multiple times as they pass over.
+advtrains_loading_track_timer (Loading Track Timer) int 1 \ No newline at end of file