aboutsummaryrefslogtreecommitdiff
path: root/advtrains
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains')
-rw-r--r--advtrains/api_doc.txt9
-rw-r--r--advtrains/atc.lua53
-rw-r--r--advtrains/copytool.lua29
-rw-r--r--advtrains/couple.lua56
-rw-r--r--advtrains/craft_items.lua11
-rw-r--r--advtrains/crafting.lua17
-rw-r--r--advtrains/debugitems.lua86
-rw-r--r--advtrains/doc/advtrains_speed_lessp.3advtrains.md15
-rw-r--r--advtrains/doc/advtrains_speed_set_restriction.3advtrains.md18
-rw-r--r--advtrains/doc/signal_aspect.7advtrains.md24
-rw-r--r--advtrains/formspec.lua114
-rw-r--r--advtrains/helpers.lua195
-rw-r--r--advtrains/init.lua111
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/lzb.lua23
-rw-r--r--advtrains/misc_nodes.lua11
-rw-r--r--advtrains/nodedb.lua16
-rw-r--r--advtrains/occupation.lua128
-rw-r--r--advtrains/p_mesecon_iface.lua26
-rw-r--r--advtrains/passive.lua94
-rw-r--r--advtrains/path.lua75
-rw-r--r--advtrains/po/README.md70
-rw-r--r--advtrains/po/advtrains.pot625
-rw-r--r--advtrains/po/de.po925
-rw-r--r--advtrains/po/fr.po1024
-rwxr-xr-xadvtrains/po/update-translations.sh28
-rw-r--r--advtrains/po/zh_CN.po863
-rw-r--r--advtrains/po/zh_TW.po863
-rw-r--r--advtrains/poconvert.lua185
-rw-r--r--advtrains/protection.lua16
-rw-r--r--advtrains/settingtypes.txt7
-rw-r--r--advtrains/signals.lua178
-rw-r--r--advtrains/sounds/advtrains_crossing_bell.oggbin47722 -> 24656 bytes
-rw-r--r--advtrains/spec/poconvert_spec.lua70
-rw-r--r--advtrains/spec/speed_spec.lua70
-rw-r--r--advtrains/spec/texture_spec.lua19
-rw-r--r--advtrains/spec/wagons_spec.lua40
-rw-r--r--advtrains/speed.lua88
-rw-r--r--advtrains/texture.lua228
-rw-r--r--advtrains/textures/advtrains_wagon_prop_tool.pngbin0 -> 779 bytes
-rw-r--r--advtrains/track_reg_helper.lua751
-rw-r--r--advtrains/trackdb_legacy.lua27
-rw-r--r--advtrains/trackplacer.lua629
-rw-r--r--advtrains/tracks.lua1026
-rw-r--r--advtrains/trainhud.lua198
-rw-r--r--advtrains/trainlogic.lua185
-rw-r--r--advtrains/wagonprop_tool.lua47
-rw-r--r--advtrains/wagons.lua535
50 files changed, 7962 insertions, 2031 deletions
diff --git a/advtrains/api_doc.txt b/advtrains/api_doc.txt
index 1e49df3..6b338a7 100644
--- a/advtrains/api_doc.txt
+++ b/advtrains/api_doc.txt
@@ -18,8 +18,6 @@ advtrains.register_wagon(name, prototype, description, inventory_image)
# Wagon prototype properties
{
... all regular luaentity properties (mesh, textures, collisionbox a.s.o)...
- drives_on = {default=true},
- ^- used to define the tracktypes (see below) that wagon can drive on. The tracktype identifiers are given as keys, similar to privileges)
max_speed = 10,
^- optional, default 10: defines the maximum speed this wagon can drive. The maximum speed of a train is determined by the wagon with the lowest max_speed value.
seats = {
@@ -85,6 +83,13 @@ advtrains.register_wagon(name, prototype, description, inventory_image)
wagon_span=2,
^- How far this wagon extends from its base position. Is the half of the wagon length.
^- Used to determine in which distance the other wagons have to be positioned. Will require tweaking.
+ wheel_positions = {1.5, -1.5},
+ ^- Optional: if defined, the wagon will be placed so that these 2 wheel positions are on the track
+ ^- This parameter is recommended for long wagons (wagon_span >= 2).
+ ^- The position is a distance relative to the center of the wagon.
+ ^- Must have exactly 2 entries, corresponding to the front (1) and rear (2) wheel of the wagon object. 1st must be greater than 2nd.
+ ^- If not provided, the simple 1-position positioning logic will be used (wagon is positioned with the center on the track)
+
extent_h = 1,
^- Determines the collision box extent in x/z direction. Defaults to 1 (=3x3)
^- The actual bounding box size is (extent_h*2)+1, so 0 means 1x1, 1 means 3x3 and 2 means 5x5
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index 8cb3e8f..e14e5c1 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -1,6 +1,9 @@
--atc.lua
--registers and controls the ATC system
+-- Get current translator
+local S = advtrains.translate
+
local atc={}
local eval_conditional
@@ -93,6 +96,8 @@ function atc.train_reset_command(train, keep_tarvel)
train.atc_delay=nil
train.atc_brake_target=nil
train.atc_wait_finish=nil
+ train.atc_wait_autocouple=nil
+ train.atc_wait_signal=nil
train.atc_arrow=nil
if not keep_tarvel then
train.tarvelocity=nil
@@ -105,7 +110,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", S("Unconfigured ATC controller"))
meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
end
end
@@ -127,27 +132,15 @@ advtrains.atc_function = function(def, preset, suffix, rotation)
local meta=minetest.get_meta(pos)
if meta then
if not fields.save then
- --[[--maybe only the dropdown changed
- if fields.mode then
- meta:set_string("mode", idxtrans[fields.mode])
- if fields.mode=="digiline" then
- meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", fields.mode, meta:get_string("command")) )
- else
- meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", fields.mode, meta:get_string("command")) )
- end
- meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
- end]]--
return
end
--meta:set_string("mode", idxtrans[fields.mode])
meta:set_string("command", fields.command)
--meta:set_string("command_on", fields.command_on)
meta:set_string("channel", fields.channel)
- --if fields.mode=="digiline" then
- -- meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", fields.mode, meta:get_string("command")) )
- --else
- meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", "-", meta:get_string("command")) )
- --end
+
+ meta:set_string("infotext", S("ATC controller, Command: @1", meta:get_string("command")) )
+
meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
local pts=minetest.pos_to_string(pos)
@@ -176,16 +169,11 @@ function atc.get_atc_controller_formspec(pos, meta)
if mode<3 then
formspec=formspec
.."style[command;font=mono]"
- .."field[0.8,1.5;7,1;command;"..attrans("Command")..";"..minetest.formspec_escape(command).."]"
- if tonumber(mode)==2 then
- formspec=formspec
- .."style[command_on;font=mono]"
- .."field[0.8,3;7,1;command_on;"..attrans("Command (on)")..";"..minetest.formspec_escape(command_on).."]"
- end
+ .."field[0.8,1.5;7,1;command;"..S("Command")..";"..minetest.formspec_escape(command).."]"
else
- formspec=formspec.."field[0.8,1.5;7,1;channel;"..attrans("Digiline channel")..";"..minetest.formspec_escape(channel).."]"
+ formspec=formspec.."field[0.8,1.5;7,1;channel;"..S("Digiline channel")..";"..minetest.formspec_escape(channel).."]"
end
- return formspec.."button_exit[0.5,4.5;7,1;save;"..attrans("Save").."]"
+ return formspec.."button_exit[0.5,4.5;7,1;save;"..S("Save").."]"
end
--from trainlogic.lua train step
@@ -232,7 +220,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), S("ATC Reverse command warning: didn't reverse train, train moving."))
end
return 1
end,
@@ -244,11 +232,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), S("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), S("ATC Kick command warning: train moving."))
return 1
end
local tp = train.trainparts
@@ -277,6 +265,10 @@ local matchptn={
train.atc_wait_autocouple=true
return 3
end,
+ ["G"]=function(id, train)
+ train.atc_wait_signal=true
+ return 1
+ end,
}
eval_conditional = function(command, arrow, speed)
@@ -318,7 +310,7 @@ eval_conditional = function(command, arrow, speed)
local nest, pos, elsepos=0, 1
while nest>=0 do
if pos>#rest then
- atwarn(sid(id), attrans("ATC command syntax error: I statement not closed: @1",command))
+ atwarn(sid(id), S("ATC command syntax error: I statement not closed: @1",command))
return ""
end
local char=string.sub(rest, pos, pos)
@@ -374,14 +366,15 @@ function atc.execute_atc_command(id, train)
train.atc_command=string.sub(command, patlen+1)
if train.atc_delay<=0
and not train.atc_wait_finish
- and not train.atc_wait_autocouple then
+ and not train.atc_wait_autocouple
+ and not train.atc_wait_signal then
--continue (recursive, cmds shouldn't get too long, and it's a end-recursion.)
atc.execute_atc_command(id, train)
end
return
end
end
- atwarn(sid(id), attrans("ATC command parse error: Unknown command: @1", command))
+ atwarn(sid(id), S("ATC command parse error: Unknown command: @1", command))
atc.train_reset_command(train, true)
end
diff --git a/advtrains/copytool.lua b/advtrains/copytool.lua
index 0c1cdfe..cdfbd2e 100644
--- a/advtrains/copytool.lua
+++ b/advtrains/copytool.lua
@@ -4,8 +4,11 @@
-- 4.712389 = 1.5pi; sin(1.5pi) = -1
-- 7.853981 = 2.5pi; sin(2.5pi) = 1
+-- Get current translator
+local S = advtrains.translate
+
minetest.register_tool("advtrains:copytool", {
- description = attrans("Train copy/paste tool\n\nLeft-click: copy train\nRight-click: paste train"),
+ description = S("Train copy/paste tool\n\nLeft-click: copy train\nRight-click: paste train"),
inventory_image = "advtrains_copytool.png",
wield_image = "advtrains_copytool.png",
stack_max = 1,
@@ -21,12 +24,12 @@ minetest.register_tool("advtrains:copytool", {
local node=minetest.get_node_or_nil(pointed_thing.under)
if not node then atprint("[advtrains]Ignore at placer position") return itemstack end
local nodename=node.name
- if(not advtrains.is_track_and_drives_on(nodename, {default=true})) then
+ if(not advtrains.is_track(nodename)) then
atprint("no track here, not placing.")
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,23 +41,23 @@ 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, S("The track you are trying to place the wagon on is not long enough."))
return
end
local meta = itemstack:get_meta()
if not meta then
- minetest.chat_send_player(pname, attrans("The clipboard couldn't access the metadata. Paste failed."))
+ minetest.chat_send_player(pname, S("The clipboard couldn't access the metadata. Paste failed."))
return
end
local clipboard = meta:get_string("clipboard")
if (clipboard == "") then
- minetest.chat_send_player(pname, "The clipboard is empty.");
+ minetest.chat_send_player(pname, S("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, S("The clipboard is empty."));
return
end
@@ -71,7 +74,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, S("Back of train would end up off track, cancelling."))
advtrains.remove_train(id)
return
end
@@ -89,19 +92,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(), S("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(), S("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(), S("No such train: @1.", wagon.train_id))
return
end
@@ -173,11 +176,11 @@ minetest.register_tool("advtrains:copytool", {
local meta = itemstack:get_meta()
if not meta then
- minetest.chat_send_player(pname, attrans("The clipboard couldn't access the metadata. Copy failed."))
+ minetest.chat_send_player(pname, S("The clipboard couldn't access the metadata. Copy failed."))
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(), S("Train copied."))
return itemstack
end
})
diff --git a/advtrains/couple.lua b/advtrains/couple.lua
index c421f61..cb325a0 100644
--- a/advtrains/couple.lua
+++ b/advtrains/couple.lua
@@ -18,6 +18,9 @@
-- train.couple_* contain references to ObjectRefs of couple objects, which contain all relevant information
-- These objectRefs will delete themselves once the couples no longer match (see below)
+-- Get current translator
+local S = advtrains.translate
+
advtrains.coupler_types = {}
function advtrains.register_coupler_type(code, name)
@@ -25,9 +28,23 @@ function advtrains.register_coupler_type(code, name)
end
-- Register some default couplers
-advtrains.register_coupler_type("chain", attrans("Buffer and Chain Coupler"))
-advtrains.register_coupler_type("scharfenberg", attrans("Scharfenberg Coupler"))
+advtrains.register_coupler_type("chain", S("Buffer and Chain Coupler"))
+advtrains.register_coupler_type("scharfenberg", S("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 +96,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 +127,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 +201,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 +244,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(S("Cannot couple @1 and @2 - train would have length @3 which is above the limit of @4", stat_train.id, init_train.id, tot_len, 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
@@ -252,6 +280,8 @@ function advtrains.couple_trains(init_train, invert_init_train, stat_train, stat
init_train.index = advtrains.path_get_index_by_offset(init_train, init_train.index, stat_trainlen)
advtrains.update_trainpart_properties(init_train.id)
+ advtrains.update_train_start_and_end(init_train)
+
advtrains.couple_invalidate(init_train)
return true
end
@@ -308,7 +338,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
@@ -324,12 +354,12 @@ 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]=S("<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
- 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, ","))
+ if #t2_cplhr==0 then t2_cplhr[1]=S("<No coupler>") end
+ return false, S("Can not couple: The couplers of the trains do not match (@1 and @2).", table.concat(t1_cplhr, ","), table.concat(t2_cplhr, ","))
end
-- DECOUPLING --
@@ -426,7 +456,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
@@ -473,7 +503,7 @@ minetest.register_entity("advtrains:couple", {
self.object:remove()
end,
on_step=function(self, dtime)
- if advtrains.wagon_outside_range(self.object:getpos()) then
+ if advtrains.wagon_outside_range(self.object:get_pos()) then
--atdebug("Couple Removing outside range")
self.object:remove()
return
@@ -512,7 +542,7 @@ minetest.register_entity("advtrains:couple", {
tp2=advtrains.path_get_interpolated(train2, train2.end_index)
end
local pos_median=advtrains.pos_median(tp1, tp2)
- if not vector.equals(pos_median, self.object:getpos()) then
+ if not vector.equals(pos_median, self.object:get_pos()) then
self.object:set_pos(pos_median)
end
self.position_set=true
diff --git a/advtrains/craft_items.lua b/advtrains/craft_items.lua
index 0e693eb..6b1dcef 100644
--- a/advtrains/craft_items.lua
+++ b/advtrains/craft_items.lua
@@ -1,23 +1,26 @@
+-- Get current translator
+local S = advtrains.translate
+
core.register_craftitem("advtrains:boiler", {
- description = attrans("Boiler"),
+ description = S("Boiler"),
inventory_image = "advtrains_boiler.png",
})
core.register_craftitem("advtrains:driver_cab", {
- description = attrans("driver's cab"),
+ description = S("Driver's cab"),
inventory_image = "advtrains_driver_cab.png",
})
core.register_craftitem("advtrains:wheel", {
- description = attrans("Wheel"),
+ description = S("Wheel"),
inventory_image = "advtrains_wheel.png",
})
core.register_craftitem("advtrains:chimney", {
- description = attrans("Chimney"),
+ description = S("Chimney"),
inventory_image = "advtrains_chimney.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/debugitems.lua b/advtrains/debugitems.lua
index e672308..2236cba 100644
--- a/advtrains/debugitems.lua
+++ b/advtrains/debugitems.lua
@@ -51,3 +51,89 @@ minetest.register_chatcommand("atyaw",
end
end,
})
+
+minetest.register_tool("advtrains:wagonpos_tester",
+{
+ description = "Wagon position tester",
+ groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
+ inventory_image = "drwho_screwdriver.png",
+ wield_image = "drwho_screwdriver.png",
+ stack_max = 1,
+ range = 7.0,
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ end,
+ --[[
+ ^ Shall place item and return the leftover itemstack
+ ^ default: minetest.item_place ]]
+ on_use = function(itemstack, user, pointed_thing)
+ if pointed_thing.type=="node" then
+ local pos = pointed_thing.under
+ local trains = advtrains.occ.get_trains_at(pos)
+ for train_id, index in pairs(trains) do
+ local wagon_num, wagon_id, wagon_data, offset_from_center = advtrains.get_wagon_at_index(train_id, index)
+ if wagon_num then
+ atdebug(wagon_num, wagon_id, offset_from_center)
+ end
+ end
+ end
+ end,
+}
+)
+
+
+local function trackitest(initial_pos, initial_connid)
+ local ti, pos, connid, ok
+ ti = advtrains.get_track_iterator(initial_pos, initial_connid, 500, true)
+ atdebug("Starting at pos:",initial_pos,initial_connid)
+ while ti:has_next_branch() do
+ pos, connid = ti:next_branch() -- in first iteration, this will be the node at initial_pos. In subsequent iterations this will be the switch node from which we are branching off
+ atdebug("Next Branch:",pos, connid)
+ ok = true
+ while ok do
+ ok, pos, connid = ti:next_track()
+ atdebug("Next Track:", ok, pos, connid)
+ end
+ end
+ atdebug("End of traverse. Visited: ",table.concat(ti.visited, ","))
+end
+
+minetest.register_tool("advtrains:trackitest",
+{
+ description = "Track Iterator Tester (leftclick conn 1, rightclick conn 2)",
+ groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
+ inventory_image = "advtrains_track_swlcr_45.png",
+ wield_image = "advtrains_track_swlcr_45.png",
+ stack_max = 1,
+ range = 7.0,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ if pointed_thing.type ~= "node" then return end
+ trackitest(pointed_thing.under, 2)
+ end,
+ on_use = function(itemstack, user, pointed_thing)
+ if pointed_thing.type ~= "node" then return end
+ trackitest(pointed_thing.under, 1)
+ end,
+}
+)
+
+minetest.register_chatcommand("at_trackdef_audit",
+ {
+ params = "",
+ description = "Performs an audit of all track definitions currently loaded and checks for potential problems",
+ func = function(name, param)
+ for name, ndef in pairs(minetest.registered_nodes) do
+ --TODO finish this!
+ if ndef.at_conns then
+ -- check if conn_map is there and if it has enough entries
+ if #ndef.at_conns > 2 then
+ if #ndef.at_conn_map < #ndef.at_conns then
+ atwarn("AUDIT: Node",name,"- Not enough connmap entries! Check ndef:",ndef)
+ end
+ end
+ end
+ end
+ end,
+})
diff --git a/advtrains/doc/advtrains_speed_lessp.3advtrains.md b/advtrains/doc/advtrains_speed_lessp.3advtrains.md
new file mode 100644
index 0000000..663aa42
--- /dev/null
+++ b/advtrains/doc/advtrains_speed_lessp.3advtrains.md
@@ -0,0 +1,15 @@
+% advtrains_speed_lessp(3advtrains) | Advtrains Developer's Manual
+
+# NAME
+`advtrains.speed.lessp`, `advtrains.speed.greaterp`, `advtrains.speed.not_lessp`, `advtrains.speed_not_greaterp`, `advtrains.speed.equalp`, `advtrains.speed.not_equalp`, `advtrains.speed.max`, `advtrains.speed.min` - speed restriction comparison functions
+
+# SYNOPSIS
+Each function takes two arguments and returns a boolean or (for `advtrains.speed.max` and `advtrains.speed.min`) a valid speed limit
+
+# DESCRIPTION
+
+The functions above correspond to the arithmetic `<`, `>`, `>=`, `<=`, `==`, `~=` operators and the `math.max` and `math.min` functions, respectively. The constants `nil` and `false` are treated as -1.
+
+# NOTES
+
+These functions are trivial to implement and the implementation can be easily embedded into existing code. They are simply provided for convenience.
diff --git a/advtrains/doc/advtrains_speed_set_restriction.3advtrains.md b/advtrains/doc/advtrains_speed_set_restriction.3advtrains.md
new file mode 100644
index 0000000..b3183c6
--- /dev/null
+++ b/advtrains/doc/advtrains_speed_set_restriction.3advtrains.md
@@ -0,0 +1,18 @@
+% advtrains_speed_set_restriction(3advtrains) | Advtrains Developer's Manual
+
+# NAME
+`advtrains.speed.set_restriction`, `advtrains.speed.merge_aspect` - modify speed restriction
+
+# SYNOPSIS
+* `advtrains.speed.set_restriction(train, rtype, rval)`
+* `advtrains.speed.merge_aspect(train, asp)`
+
+# DESCRIPTION
+
+The `advtrains.speed.set_restriction` function sets the speed restriction of type `rtype` of `train` to `rval` and updates the speed restriction value to the strictest speed restriction in the table, or `nil` if all speed restrictions are `nil` or `-1`. If the speed restriction table does not exist, it is created with the `"main"` speed restriction being the speed restriction value of `train`.
+
+The `advtrains.speed.merge_aspect` function merges the main aspect of `asp` into the speed restriction table with the same procedure described above. If the signal aspect table does not provide the type of speed restriction, the restriction type `"main"` is assumed.
+
+# SIDE EFFECTS
+
+Both functions modify `train.speed_restriction` and `train.speed_restrictions_t`.
diff --git a/advtrains/doc/signal_aspect.7advtrains.md b/advtrains/doc/signal_aspect.7advtrains.md
new file mode 100644
index 0000000..827760d
--- /dev/null
+++ b/advtrains/doc/signal_aspect.7advtrains.md
@@ -0,0 +1,24 @@
+% signal_aspect(7advtrains) | Advtrains Developer's Manual
+
+# DESCRIPTION
+
+The signal aspect table used by advtrains has the following fields:
+
+* `main`: The main speed restriction
+* `dst`: The `main` aspect of the distant signal (not implemented)
+* `type`: The type of speed restriction given by the signal
+* `shunt`: Whether shunting is allowed
+* `proceed_as_main`: Whether to proceed without shunting
+
+The `main` and `dst` fields may contain the following values:
+* `-1`: No speed restriction
+* `nil`: No information is available
+
+The `type` field can be any valid table index, but it should usually be one of the following values:
+* "main": The main signal aspect used before the introduction of speed restriction types. This is the default value if the `type` field is absent.
+* "line": The speed limit for the physical line.
+* "temp": The speed limit that is temporarily introduced.
+
+# NOTES
+
+A signal with the `main` aspect of zero should not provide distant signal aspect.
diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua
new file mode 100644
index 0000000..86d8b27
--- /dev/null
+++ b/advtrains/formspec.lua
@@ -0,0 +1,114 @@
+local sformat = string.format
+local fsescape = minetest.formspec_escape
+
+-- Get current translator
+local S = advtrains.translate
+
+local function make_list(entries)
+ local t = {}
+ for k, v in ipairs(entries) do
+ t[k] = fsescape(v)
+ end
+ return table.concat(t, ",")
+end
+
+local function S_wrapper(f, i0)
+ return function(...)
+ local args = {...}
+ args[i0] = S(unpack(args,i0))
+ return f(unpack(args,1,i0))
+ end
+end
+
+local function f_button(x, y, w, id, text)
+ return sformat("button[%f,%f;%f,0.75;%s;%s]", x, y, w, id, text)
+end
+
+local function f_checkbox(x, y, name, selected, label)
+ return sformat("checkbox[%f,%f;%s;%s;%s]", x, y+0.25, name, label, selected and "true" or "false")
+end
+
+local function f_button_exit(x, y, w, id, text)
+ return sformat("button_exit[%f,%f;%f,0.75;%s;%s]", x, y, w, id, text)
+end
+
+local function f_dropdown(x, y, w, id, entries, sel, indexed)
+ return sformat("dropdown[%f,%f;%f,0.75;%s;%s;%d%s]",
+ x, y, w, id, make_list(entries),
+ sel or 1,
+ indexed and ";true" or "")
+end
+
+local function f_image_button(x, y, w, h, texture, id, label, noclip, drawborder, pressed)
+ local st = {string.format("%f,%f;%f,%f;%s;%s;%s", x, y, w, h, fsescape(texture), fsescape(id), fsescape(label))}
+ if pressed then
+ st[#st+1] = tostring(noclip or false)
+ st[#st+1] = tostring(drawborder or false)
+ st[#st+1] = fsescape(pressed)
+ end
+ return sformat("image_button[%s]", table.concat(st, ";"))
+end
+
+local function f_image_button_exit(x, y, w, h, texture, id, label)
+ local st = {string.format("%f,%f;%f,%f;%s;%s;%s", x, y, w, h, fsescape(texture), fsescape(id), fsescape(label))}
+ return sformat("image_button_exit[%s]", table.concat(st, ";"))
+end
+
+local function f_label(x, y, text)
+ return sformat("label[%f,%f;%s]", x, y+0.25, fsescape(text))
+end
+
+local function f_field_aux(x, y, w, id, default)
+ return sformat("field[%f,%f;%f,0.75;%s;;%s]", x, y, w, id, default)
+end
+
+local function f_field(x, y, w, id, label, default)
+ return f_label(x, y-0.5, label) .. f_field_aux(x, y, w, id, default)
+end
+
+local function f_tabheader(x, y, w, h, id, entries, sel, transparent, border)
+ local st = {string.format("%f,%f",x, y)}
+ if h then
+ if w then
+ st[#st+1] = string.format("%f,%f", w, h)
+ else
+ st[#st+1] = tostring(h)
+ end
+ end
+ st[#st+1] = tostring(id)
+ st[#st+1] = make_list(entries)
+ st[#st+1] = tostring(sel)
+ if transparent ~= nil then
+ st[#st+1] = tostring(transparent)
+ if border ~= nil then
+ st[#st+1] = tostring(border)
+ end
+ end
+ return string.format("tabheader[%s]", table.concat(st, ";"))
+end
+
+local function f_textlist(x, y, w, h, id, entries, sel, transparent)
+ local st = {string.format("%f,%f;%f,%f;%s;%s", x, y, w, h, id, make_list(entries))}
+ if sel then
+ st[#st+1] = tostring(sel)
+ st[#st+1] = tostring(transparent or false)
+ end
+ return string.format("textlist[%s]", table.concat(st, ";"))
+end
+
+return {
+ button = f_button,
+ S_button = S_wrapper(f_button, 5),
+ checkbox = f_checkbox,
+ S_checkbox = S_wrapper(f_checkbox, 5),
+ button_exit = f_button_exit,
+ S_button_exit = S_wrapper(f_button_exit, 5),
+ dropdown = f_dropdown,
+ field = f_field,
+ image_button = f_image_button,
+ image_button_exit = f_image_button_exit,
+ label = f_label,
+ S_label = S_wrapper(f_label, 3),
+ tabheader = f_tabheader,
+ textlist = f_textlist,
+}
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index cf890ca..e58625f 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -292,18 +292,19 @@ function advtrains.conn_matches_to(conn, other_conns)
end
-- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid
--- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>
+-- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>, (adjacent conns table), (adjacent connmap table)
-- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided.
-function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on)
+function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx)
local this_pos = advtrains.round_vector_floor_y(this_posnr)
local this_conns = this_conns_p
+ local _
if not this_conns then
_, this_conns = advtrains.get_rail_info_at(this_pos)
end
if not conn_idx then
for coni, _ in ipairs(this_conns) do
- local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
- if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end
+ local adj_pos, adj_conn_idx, _, nry, nco, ncm = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
+ if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco, ncm end
end
return nil
end
@@ -317,34 +318,46 @@ function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_
adj_pos.y = adj_pos.y + 1
end
- local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
+ local nextnode_ok, nextconns, nextrail_y, nextconnmap=advtrains.get_rail_info_at(adj_pos)
if not nextnode_ok then
adj_pos.y = adj_pos.y - 1
conn_y = conn_y + 1
- nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
+ nextnode_ok, nextconns, nextrail_y, nextconnmap=advtrains.get_rail_info_at(adj_pos)
if not nextnode_ok then
return nil
end
end
local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns)
if adj_connid then
- return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns
+ return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns, nextconnmap
end
return nil
end
-- when a train enters a rail on connid 'conn', which connid will it go out?
--- nconns: number of connections in connection table:
--- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub)
+-- Since 2.5: This mapping is contained in the conn_map table in the node definition!
-- returns: connid_out
-local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}}
-function advtrains.get_matching_conn(conn, nconns)
- return connlku[nconns][conn]
+function advtrains.get_matching_conn(conn, conn_map)
+ if tonumber(conn_map) then
+ error("Legacy call to get_matching_conn! Instead of nconns, conn_map needs to be provided!")
+ end
+ if not conn_map then
+ --OK for two-conn rails, just return the other
+ if conn==1 then return 2 end
+ if conn==2 then return 1 end
+ error("get_matching_conn: For connid >=3, conn_map must not be nil!")
+ end
+ local cout = conn_map[conn]
+ if not cout then
+ error("get_matching_conn: Connid "..conn.." not found in conn_map which is "..atdump(conn_map))
+ end
+ return cout
end
-function advtrains.random_id()
+function advtrains.random_id(lenp)
local idst=""
- for i=0,5 do
+ local len = lenp or 6
+ for i=1,len do
idst=idst..(math.random(0,9))
end
return idst
@@ -461,7 +474,13 @@ end
local active_node_range = tonumber(minetest.settings:get("active_block_range"))*16 + 16
-- Function to check whether node at position(pos) is "loaded"/"active"
-- That is, whether it is within the active_block_range to a player
-if minetest.is_block_active then -- define function differently whether minetest.is_block_active is available or not
+if core.compare_block_status then
+ -- latest API
+ function advtrains.is_node_loaded(pos)
+ return core.compare_block_status(pos, "active")
+ end
+elseif minetest.is_block_active then -- define function differently whether minetest.is_block_active is available or not
+ -- API added by my PR but later superseded by the above and now removed
advtrains.is_node_loaded = minetest.is_block_active
else
function advtrains.is_node_loaded(pos)
@@ -470,3 +489,149 @@ else
end
end
end
+
+
+-- TrackIterator interface --
+
+-- Metatable:
+local trackiter_mt = {
+ -- Internal State:
+ -- branches: A list of {pos, connid, limit} for where to restart
+ -- pos: The *next* position that the track iterator will return
+ -- bconnid: The connid of the connection of the rail at pos that points backward
+ -- tconns: The connections of the rail at pos
+ -- limit: the current limit
+ -- visited: a key-boolean table of already visited rails
+
+ -- get whether there are still unprocessed branches
+ has_next_branch = function(self)
+ return #self.branches > 0
+ end,
+ -- go to the next unprocessed branch
+ -- returns track_pos, track_connid of the switch/crossing node where the track branches off
+ next_branch = function(self)
+ local br = table.remove(self.branches, 1)
+ -- Advance internal state
+ local adj_pos, adj_connid, _, _, adj_conns, adj_connmap = advtrains.get_adjacent_rail(br.pos, nil, br.connid)
+ self.pos = adj_pos
+ self.bconnid = adj_connid
+ self.tconns = adj_conns
+ self.tconnmap = adj_connmap
+ self.limit = br.limit - 1
+ self.visited[advtrains.encode_pos(br.pos)] = true
+ self.last_track_already_visited = false
+ return br.pos, br.connid
+ end,
+ -- get the next track along the current branch,
+ -- potentially adding branching tracks to the unprocessed branches list
+ -- returns track_pos, track_connid, track_backwards_connid
+ -- On error, returns nil, reason; reason is one of "track_end", "limit_hit", "already_visited"
+ next_track = function(self)
+ if self.last_track_already_visited then
+ -- see comment below
+ return nil, "already_visited"
+ end
+ local pos = self.pos
+ if not pos then
+ -- last run found track end. Return false
+ return false, "track_end"
+ end
+ -- if limit hit, return nil to signal this
+ if self.limit <= 0 then
+ return nil, "limit_hit"
+ end
+ -- select next conn (main conn to follow is the associated connection)
+ local old_bconnid = self.bconnid
+ local mconnid = advtrains.get_matching_conn(self.bconnid, self.tconnmap)
+ if self.visited[advtrains.encode_pos(pos)] then
+ -- node was already seen
+ -- Due to special requirements for the track section updater, return this first already visited track once
+ -- but do not process any further rails on this branch
+ -- The next call will then throw already_visited error
+ self.last_track_already_visited = true
+ return pos, mconnid, old_bconnid
+ end
+ -- If there are more connections, add these to branches
+ for nconnid,_ in ipairs(self.tconns) do
+ if nconnid~=mconnid and nconnid~=self.bconnid then
+ table.insert(self.branches, {pos = self.pos, connid = nconnid, limit=self.limit})
+ end
+ end
+ -- Advance internal state
+ local adj_pos, adj_connid, _, _, adj_conns, adj_connmap = advtrains.get_adjacent_rail(pos, self.tconns, mconnid)
+ self.pos = adj_pos
+ self.bconnid = adj_connid
+ self.tconns = adj_conns
+ self.tconnmap = adj_connmap
+ self.limit = self.limit - 1
+ self.visited[advtrains.encode_pos(pos)] = true
+ self.last_track_already_visited = false
+ return pos, mconnid, old_bconnid
+ end,
+
+ add_branch = function(self, pos, connid)
+ table.insert(self.branches, {pos = pos, connid = connid, limit=self.limit})
+ end,
+
+ is_visited = function(self, pos)
+ return self.visited[advtrains.encode_pos(pos)]
+ end,
+}
+
+-- Returns a new TrackIterator object
+
+-- Parameters:
+-- initial_pos: the initial track position of the track iterator
+-- initial_connid: the connection index in which to traverse. If nil, adds a "branch" for every connection of the track (traverse in all directions)
+-- limit: maximum distance from the start point after which the traverser stops
+-- follow_all: NOT IMPLEMENTED (supposed: if true, follows all branches at multi-connection tracks, even the ones pointing backwards or the crossing track on crossings. If false, follows only switches in driving direction.)
+
+-- Functions of the returned TrackIterator can be called via the Lua : notation, such as ti:next_track()
+-- If only the main track needs to be followed, use only the ti:next_track() function and do not call ti:next_branch().
+function advtrains.get_track_iterator(initial_pos, initial_connid, limit, follow_all)
+ local ti = {
+ visited = {}
+ }
+ if initial_connid then
+ ti.branches = { {pos = initial_pos, connid = initial_connid, limit=limit} }
+ else
+ -- get track info here
+ local node_ok, conns, rail_y=advtrains.get_rail_info_at(initial_pos)
+ assert(node_ok, "get_track_iterator called with non-track node!")
+ ti.branches = {}
+ for coni, _ in pairs(conns) do
+ table.insert(ti.branches, {pos = initial_pos, connid = coni, limit=limit})
+ end
+ end
+ ti.limit = limit -- safeguard if someone adds a branch before calling anything
+ setmetatable(ti, {__index=trackiter_mt})
+ return ti
+end
+
+--[[
+Example TrackIterator usage structure:
+
+local ti, pos, connid, ok
+ti = advtrains.get_track_iterator(initial_pos, initial_connid, 500, true)
+while ti:has_next_branch() do
+ pos, connid = ti:next_branch() -- in first iteration, this will be the node at initial_pos. In subsequent iterations this will be the switch node from which we are branching off
+ repeat
+ <do something with the track>
+ if <track satisfies an abort condition> then break end --for example, when traversing should stop at TCBs this can check if there is a tcb here
+ pos, connid = ti:next_track()
+ until not pos -- this stops the loop when either the track end is reached or the limit is hit
+ -- while loop continues with the next branch ( diverging branch of one of the switches/crossings) until no more are left
+end
+
+Example for walking only a single track (without branching):
+
+local ti, pos, connid, ok
+ti = advtrains.get_track_iterator(initial_pos, initial_connid, 500, true)
+
+pos, connid = ti:next_branch() -- this always needs to be done at least one time, and gets the track at initial_pos
+repeat
+ <do something with the track>
+ if <track satisfies an abort condition> then break end --for example, when traversing should stop at TCBs this can check if there is a tcb here
+ ok, pos, connid = ti:next_track()
+until not ok -- this stops the loop when either the track end is reached or the limit is hit
+]]
diff --git a/advtrains/init.lua b/advtrains/init.lua
index 0882237..cd63104 100644
--- a/advtrains/init.lua
+++ b/advtrains/init.lua
@@ -22,9 +22,6 @@ Copyright (C) 2016-2020 Moritz Blei (orwell96) and contributors
local lot = os.clock()
minetest.log("action", "[advtrains] Loading...")
--- There is no need to support 0.4.x anymore given that the compatitability with it is already broken by 1bb1d825f46af3562554c12fba35a31b9f7973ff
-attrans = minetest.get_translator ("advtrains")
-
--advtrains
advtrains = {trains={}, player_to_train_mapping={}}
@@ -48,8 +45,22 @@ 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
+
-- ==========================================================================
+advtrains.modpath = minetest.get_modpath("advtrains")
+
+-- Initialize internationalization (using ywang's poconvert)
+advtrains.poconvert = dofile(advtrains.modpath.."/poconvert.lua")
+advtrains.poconvert.from_flat("advtrains")
+-- ask engine for translator instance, this will load the translation files
+advtrains.translate = core.get_translator("advtrains")
+
+-- Get current translator
+local S = advtrains.translate
+
-- Use a global slowdown factor to slow down train movements. Now a setting
advtrains.DTIME_LIMIT = tonumber(minetest.settings:get("advtrains_dtime_limit")) or 0.2
advtrains.SAVE_INTERVAL = tonumber(minetest.settings:get("advtrains_save_interval")) or 60
@@ -67,7 +78,7 @@ end
local no_action=false
local function reload_saves()
- atwarn("Restoring saved state in 1 second...")
+ atwarn(S("Restoring saved state in 1 second..."))
no_action=true
advtrains.lock_path_inval = false
--read last save state and continue, as if server was restarted
@@ -78,13 +89,11 @@ local function reload_saves()
end
minetest.after(1, function()
advtrains.load()
- atwarn("Reload successful!")
+ atwarn(S("Reload successful!"))
advtrains.ndb.restore_all()
end)
end
-advtrains.modpath = minetest.get_modpath("advtrains")
-
--Advtrains dump (special treatment of pos and sigd)
function atdump(t, intend)
local str
@@ -178,7 +187,7 @@ function assertt(var, typ)
end
end
-dofile(advtrains.modpath.."/helpers.lua");
+dofile(advtrains.modpath.."/helpers.lua")
--dofile(advtrains.modpath.."/debugitems.lua");
advtrains.meseconrules =
@@ -198,18 +207,23 @@ advtrains.meseconrules =
advtrains.fpath=minetest.get_worldpath().."/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")
dofile(advtrains.modpath.."/atc.lua")
dofile(advtrains.modpath.."/wagons.lua")
dofile(advtrains.modpath.."/protection.lua")
-dofile(advtrains.modpath.."/trackdb_legacy.lua")
dofile(advtrains.modpath.."/nodedb.lua")
dofile(advtrains.modpath.."/couple.lua")
@@ -227,6 +241,9 @@ end
dofile(advtrains.modpath.."/lzb.lua")
+if minetest.settings:get_bool("advtrains_register_debugitems") then
+ dofile(advtrains.modpath.."/debugitems.lua")
+end
--load/save
@@ -331,7 +348,7 @@ function advtrains.avt_load()
end
end
for wid, _ in pairs(todel) do
- atwarn("Removing unused wagon", wid, "from wagon_save table.")
+ atwarn(S("Removing unused wagon"), wid, S("from wagon_save table."))
advtrains.wagon_save[wid]=nil
end
else
@@ -396,7 +413,7 @@ function advtrains.load_version_4()
end
end
for wid, _ in pairs(todel) do
- atwarn("Removing unused wagon", wid, "from wagon_save table.")
+ atwarn(S("Removing unused wagon"), wid, S("from wagon_save table."))
advtrains.wagon_save[wid]=nil
end
end
@@ -409,6 +426,16 @@ function advtrains.load_version_4()
if il_save then
advtrains.interlocking.db.load(il_save)
end
+
+ -- TODO 2.5.0 backwards compatibility fallback: Store the pre-v2.5.0 save file so that it can be reverted to if needed
+ local fallback_file = advtrains.fpath.."_interlocking.ls.pre250"
+ local file = io.open(fallback_file, "rb")
+ if file then
+ io.close(file)
+ else
+ atwarn("Backing up pre-2.5.0 version of Interlocking save file to",fallback_file," for potential downgrade to older versions")
+ os.rename(advtrains.fpath.."_interlocking.ls", fallback_file)
+ end
end
--== load lines ==
@@ -467,13 +494,14 @@ advtrains.avt_save = function(remove_players_from_wagons)
"trainparts", "recently_collided_with_env",
"atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open",
"text_outside", "text_inside", "line", "routingcode",
- "il_sections", "speed_restriction", "is_shunt",
- "points_split", "autocouple", "atc_wait_autocouple", "ars_disable",
+ "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
else
- atwarn("Train",id,"had no wagons left because of some bug. It is being deleted. Wave it goodbye!")
+ atwarn(S("Train @1 had no wagons left because of some bug. It is being deleted. Wave it goodbye!", id))
advtrains.remove_train(id)
end
end
@@ -554,7 +582,7 @@ advtrains.avt_save = function(remove_players_from_wagons)
local succ, err = serialize_lib.save_atomic_multiple(parts_table, advtrains.fpath.."_", callbacks_table)
if not succ then
- atwarn("Saving failed: "..err)
+ atwarn(S("Saving failed: @1", err))
else
-- store version
advtrains.save_component(4, "version")
@@ -657,7 +685,7 @@ end
function advtrains.save(remove_players_from_wagons)
if not init_load then
--wait... we haven't loaded yet?!
- atwarn("Instructed to save() but load() was never called!")
+ atwarn(S("Instructed to save() but load() was never called!"))
return
end
@@ -686,7 +714,7 @@ function advtrains.save(remove_players_from_wagons)
end
minetest.register_on_shutdown(function()
if within_mainstep then
- atwarn("Crash during advtrains main step - skipping the shutdown save operation to not save inconsistent data!")
+ atwarn(S("Crash during advtrains main step - skipping the shutdown save operation to not save inconsistent data!"))
else
advtrains.save()
end
@@ -698,10 +726,10 @@ end)
minetest.register_chatcommand("at_empty_seats",
{
params = "", -- Short parameter description
- description = "Detach all players, especially the offline ones, from all trains. Use only when no one serious is on a train.", -- Full description
+ description = S("Detach all players, especially the offline ones, from all trains. Use only when no one serious is on a train."), -- Full description
privs = {train_operator=true, server=true}, -- Require the "privs" privilege to run
func = function(name, param)
- atwarn("Data is being saved. While saving, advtrains will remove the players from trains. Save files will be reloaded afterwards!")
+ atwarn(S("Data is being saved. While saving, advtrains will remove the players from trains. Save files will be reloaded afterwards!"))
advtrains.save(true)
reload_saves()
end,
@@ -710,49 +738,74 @@ minetest.register_chatcommand("at_empty_seats",
minetest.register_chatcommand("at_reroute",
{
params = "",
- description = "Delete all train routes, force them to recalculate",
+ description = S("Delete all train routes, force them to recalculate"),
privs = {train_operator=true}, -- Only train operator is required, since this is relatively safe.
func = function(name, param)
advtrains.invalidate_all_paths()
- return true, "Successfully invalidated train routes"
+ return true, S("Successfully invalidated train routes")
end,
})
minetest.register_chatcommand("at_whereis",
{
params = "<train id>",
- description = "Returns the position of the train with the given id",
+ description = S("Returns the position of the train with the given id"),
privs = {train_operator = 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"
+ return false, S("Train @1 does not exist or is invalid", param)
else
- return true, "Train "..param.." is at "..minetest.pos_to_string(train.last_pos)
+ return true, S("Train @1 is at @2", param, minetest.pos_to_string(train.last_pos))
+ end
+ end,
+})
+minetest.register_chatcommand("at_tp",
+ {
+ params = "<train id>",
+ description = S("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, S("Train @1 does not exist or is invalid", param)
+ else
+ minetest.get_player_by_name(name):set_pos(train.last_pos)
+ return true, S("Teleporting to train @1", param)
end
end,
})
minetest.register_chatcommand("at_disable_step",
{
params = "<yes/no>",
- description = "Disable the advtrains globalstep temporarily",
+ description = S("Disable the advtrains globalstep temporarily"),
privs = {server=true},
func = function(name, param)
if minetest.is_yes(param) then
-- disable everything, and turn off saving
no_action = true;
- atwarn("The advtrains globalstep has been disabled. Trains are not moving, and no data is saved! Run '/at_disable_step no' to enable again!")
- return true, "Disabled advtrains successfully"
+ atwarn(S("The advtrains globalstep has been disabled. Trains are not moving, and no data is saved! Run '/at_disable_step no' to enable again!"))
+ return true, S("Disabled advtrains successfully")
elseif no_action then
- atwarn("Re-enabling advtrains globalstep...")
+ atwarn(S("Re-enabling advtrains globalstep..."))
reload_saves()
return true
else
- return false, "Advtrains is already running normally!"
+ return false, S("Advtrains is already running normally!")
end
end,
})
+minetest.register_chatcommand("at_status",
+ {
+ params = "",
+ description = S("Print advtrains status info"),
+ privs = {train_operator = true},
+ func = function(name, param)
+ return true, S("Advtrains Status: no_action @1 slowdown @2 (log @3)", no_action, advtrains.global_slowdown, 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/lzb.lua b/advtrains/lzb.lua
index cbdc422..116777c 100644
--- a/advtrains/lzb.lua
+++ b/advtrains/lzb.lua
@@ -29,8 +29,9 @@ The LZB subsystem keeps track of "checkpoints" the train will pass in the future
To perform 2, it populates the train.path_speed table which is handled along with the path subsystem.
This table is used in trainlogic.lua/train_step_b() and applied to the velocity calculations.
-Note: in contrast to node enter callbacks, which are called when the train passes the .5 index mark, LZB callbacks are executed on passing the .0 index mark!
-If an LZB checkpoint has speed 0, the train will still enter the node (the enter callback will be called), but will stop at the 0.9 index mark (for details, see SLOW_APPROACH in trainlogic.lua)
+Note: As of 2024-11-25, node enter callbacks as well as LZB callbacks are executed on passing the .0 index mark!
+If an LZB checkpoint has speed 0, it will stop at the 0.9 index mark (for details, see SLOW_APPROACH in trainlogic.lua) and will not enter the node.
+(previous behavior: node enter callback was called at .5 index mark)
The start point for the LZB traverser (and thus the first node that will receive an approach callback) is floor(train.index) + 1. This means, once the LZB checkpoint callback has fired,
this path node will not receive any further approach callbacks for the same approach situation
@@ -48,7 +49,7 @@ local params = {
ZONE_HOLD = 5, -- added on top of ZONE_ROLL
ZONE_VSLOW = 3, -- When speed is <2, still allow accelerating
- DST_FACTOR = 1.5,
+ DST_FACTOR = 3,--1.5,
SHUNT_SPEED_MAX = advtrains.SHUNT_SPEED_MAX,
}
@@ -90,7 +91,7 @@ local function look_ahead(id, train)
--local brake_i = advtrains.path_get_index_by_offset(train, train.index, brakedst + params.BRAKE_SPACE)
-- worst case (don't use index_by_offset)
local brake_i = atfloor(train.index + brakedst + params.BRAKE_SPACE)
- atprint("LZB: looking ahead up to ", brake_i)
+ --atprint("LZB: looking ahead up to ", brake_i)
--local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)
@@ -134,7 +135,7 @@ local function call_runover_callbacks(id, train)
local ckp = train.lzb.checkpoints
while ckp[i] do
if ckp[i].index <= idx then
- atprint("LZB: checkpoint run over: i=",ckp[i].index,"s=",ckp[i].speed)
+ --atprint("LZB: checkpoint run over: i=",ckp[i].index,"s=",ckp[i].speed,"p=",ckp[i].pos)
-- call callback
local it = ckp[i]
if it.callback then
@@ -153,7 +154,7 @@ local function apply_checkpoint_to_path(train, checkpoint)
if not checkpoint.speed then
return
end
- atprint("LZB: applying checkpoint: i=",checkpoint.index,"s=",checkpoint.speed)
+ --atprint("LZB: applying checkpoint: i=",checkpoint.index,"s=",checkpoint.speed,"p=",checkpoint.pos)
if checkpoint.speed == 0 then
train.lzb.zero_checkpoint = true
@@ -196,6 +197,9 @@ s = v0 * ------- + - * | ------- | = -----------
-- Removes all LZB checkpoints and restarts the traverser at the current train index
function advtrains.lzb_invalidate(train)
+ --advtrains.atprint_context_tid = train.id
+ --atprint("LZB: invalidate")
+ --advtrains.atprint_context_tid = nil
train.lzb = {
trav_index = atfloor(train.index) + 1,
checkpoints = {},
@@ -205,8 +209,11 @@ end
-- LZB part of path_invalidate_ahead. Clears all checkpoints that are ahead of start_idx
-- in contrast to path_inv_ahead, doesn't complain if start_idx is behind train.index, clears everything then
function advtrains.lzb_invalidate_ahead(train, start_idx)
+ --advtrains.atprint_context_tid = train.id
+ --atprint("LZB: invalidate ahead i=",start_idx)
if train.lzb then
local idx = atfloor(start_idx)
+ --atprint("LZB: invalidate ahead p=",train.path[start_idx])
local i = 1
while train.lzb.checkpoints[i] do
if train.lzb.checkpoints[i].index >= idx then
@@ -225,6 +232,7 @@ function advtrains.lzb_invalidate_ahead(train, start_idx)
apply_checkpoint_to_path(train, ckp)
end
end
+ --advtrains.atprint_context_tid = nil
end
-- Add LZB control point
@@ -257,7 +265,8 @@ end
advtrains.te_register_on_new_path(function(id, train)
advtrains.lzb_invalidate(train)
-- Taken care of in pre-move hook (see train_step_b)
- --look_ahead(id, train)
+ -- 2025-01-28 - do anyway, there seems to be an issue
+ look_ahead(id, train)
end)
advtrains.te_register_on_invalidate_ahead(function(id, train, start_idx)
diff --git a/advtrains/misc_nodes.lua b/advtrains/misc_nodes.lua
index bcf7329..8720272 100644
--- a/advtrains/misc_nodes.lua
+++ b/advtrains/misc_nodes.lua
@@ -1,5 +1,8 @@
--all nodes that do not fit in any other category
+-- Get current translator
+local S = advtrains.translate
+
function advtrains.register_platform(modprefix, preset)
local ndef=minetest.registered_nodes[preset]
if not ndef then
@@ -13,7 +16,7 @@ function advtrains.register_platform(modprefix, preset)
local desc=ndef.description or ""
local nodename=string.match(preset, ":(.+)$")
minetest.register_node(modprefix .. ":platform_low_"..nodename, {
- description = attrans("@1 Platform (low)", desc),
+ description = S("@1 Platform (low)", desc),
tiles = {btex.."^advtrains_platform.png", btex, btex, btex, btex, btex},
groups = {cracky = 1, not_blocking_trains = 1, platform=1},
sounds = ndef.sounds,
@@ -30,7 +33,7 @@ function advtrains.register_platform(modprefix, preset)
sunlight_propagates = true,
})
minetest.register_node(modprefix .. ":platform_high_"..nodename, {
- description = attrans("@1 Platform (high)", desc),
+ description = S("@1 Platform (high)", desc),
tiles = {btex.."^advtrains_platform.png", btex, btex, btex, btex, btex},
groups = {cracky = 1, not_blocking_trains = 1, platform=2},
sounds = ndef.sounds,
@@ -56,7 +59,7 @@ function advtrains.register_platform(modprefix, preset)
}
}
minetest.register_node(modprefix..":platform_45_"..nodename, {
- description = attrans("@1 Platform (45 degree)", desc),
+ description = S("@1 Platform (45 degree)", desc),
groups = {cracky = 1, not_blocking_trains = 1, platform=2},
sounds = ndef.sounds,
drawtype = "mesh",
@@ -78,7 +81,7 @@ function advtrains.register_platform(modprefix, preset)
}
}
minetest.register_node(modprefix..":platform_45_low_"..nodename, {
- description = attrans("@1 Platform (low, 45 degree)", desc),
+ description = S("@1 Platform (low, 45 degree)", desc),
groups = {cracky = 1, not_blocking_trains = 1, platform=2},
sounds = ndef.sounds,
drawtype = "mesh",
diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua
index 36b5dea..62405ce 100644
--- a/advtrains/nodedb.lua
+++ b/advtrains/nodedb.lua
@@ -212,6 +212,10 @@ function ndb.get_node(pos)
end
return n
end
+function ndb.get_ndef(pos)
+ local n=ndb.get_node_or_nil(pos)
+ return n and minetest.registered_nodes[n.name]
+end
function ndb.get_node_raw(pos)
local cid=ndbget(pos.x, pos.y, pos.z)
if cid then
@@ -264,23 +268,23 @@ end
--get_node with pseudoload. now we only need track data, so we can use the trackdb as second fallback
--nothing new will be saved inside the trackdb.
--returns:
---true, conn1, conn2, rely1, rely2, railheight in case everything's right.
+--true, conns, railheight, connmap in case everything's right.
--false if it's not a rail or the train does not drive on this rail, but it is loaded or
--nil if the node is neither loaded nor in trackdb
--the distraction between false and nil will be needed only in special cases.(train initpos)
-function advtrains.get_rail_info_at(pos, drives_on)
+function advtrains.get_rail_info_at(pos)
local rdp=advtrains.round_vector_floor_y(pos)
local node=ndb.get_node_or_nil(rdp)
if not node then return end
local nodename=node.name
- if(not advtrains.is_track_and_drives_on(nodename, drives_on)) then
+ if(not advtrains.is_track(nodename)) then
return false
end
- local conns, railheight, tracktype=advtrains.get_track_connections(node.name, node.param2)
+ local conns, railheight, connmap = advtrains.get_track_connections(node.name, node.param2)
- return true, conns, railheight
+ return true, conns, railheight, connmap
end
local IGNORE_WORLD = advtrains.IGNORE_WORLD
@@ -302,7 +306,7 @@ ndb.run_lbm = function(pos, node)
minetest.swap_node(pos, newnode)
local ndef=minetest.registered_nodes[nodeid]
if ndef and ndef.advtrains and ndef.advtrains.on_updated_from_nodedb then
- ndef.advtrains.on_updated_from_nodedb(pos, newnode)
+ ndef.advtrains.on_updated_from_nodedb(pos, newnode, node)
end
return true
end
diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua
index db39991..20a986e 100644
--- a/advtrains/occupation.lua
+++ b/advtrains/occupation.lua
@@ -86,10 +86,11 @@ 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
- break
+ if t[i]==train_id and t[i+1]==idx then
+ return
end
i = i + 2
end
@@ -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/p_mesecon_iface.lua b/advtrains/p_mesecon_iface.lua
index 33fcecd..0426e2b 100644
--- a/advtrains/p_mesecon_iface.lua
+++ b/advtrains/p_mesecon_iface.lua
@@ -14,13 +14,11 @@ minetest.override_item("mesecons_switch:mesecon_switch_off", {
minetest.sound_play("mesecons_switch", {pos=pos})
end,
advtrains = {
- getstate = "off",
- setstate = function(pos, node, newstate)
- if newstate=="on" then
- advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2})
- if advtrains.is_node_loaded(pos) then
- mesecon.receptor_on(pos)
- end
+ node_state = "off",
+ node_state_map = { on = "mesecons_switch:mesecon_switch_on", off = "mesecons_switch:mesecon_switch_off" },
+ node_on_switch_state = function(pos, new_node, old_state, new_state)
+ if advtrains.is_node_loaded(pos) then
+ mesecon.receptor_on(pos)
end
end,
on_updated_from_nodedb = function(pos, node)
@@ -41,16 +39,14 @@ minetest.override_item("mesecons_switch:mesecon_switch_on", {
minetest.sound_play("mesecons_switch", {pos=pos})
end,
advtrains = {
- getstate = "on",
- setstate = function(pos, node, newstate)
- if newstate=="off" then
- advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2})
- if advtrains.is_node_loaded(pos) then
- mesecon.receptor_off(pos)
- end
+ node_state = "on",
+ node_state_map = { on = "mesecons_switch:mesecon_switch_on", off = "mesecons_switch:mesecon_switch_off" },
+ node_on_switch_state = function(pos, new_node, old_state, new_state)
+ if advtrains.is_node_loaded(pos) then
+ mesecon.receptor_off(pos)
end
end,
- fallback_state = "off",
+ node_fallback_state = "off",
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_on(pos)
end,
diff --git a/advtrains/passive.lua b/advtrains/passive.lua
index fe4790c..37b79e4 100644
--- a/advtrains/passive.lua
+++ b/advtrains/passive.lua
@@ -1,9 +1,5 @@
-- passive.lua
--- API to passive components, as described in passive_api.txt of advtrains_luaautomation
--- This has been moved to the advtrains core in turn with the interlocking system,
--- to prevent a dependency on luaautomation.
-
-local deprecation_warned = {}
+-- Rework for advtrains 2.5: The passive API now uses the reworked node_state system. Please see the comment in tracks.lua
function advtrains.getstate(parpos, pnode)
local pos
@@ -19,20 +15,8 @@ function advtrains.getstate(parpos, pnode)
local node=pnode or advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
local st
- if ndef and ndef.advtrains and ndef.advtrains.getstate then
- st=ndef.advtrains.getstate
- elseif ndef and ndef.luaautomation and ndef.luaautomation.getstate then
- if not deprecation_warned[node.name] then
- minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
- end
- st=ndef.luaautomation.getstate
- else
- return nil
- end
- if type(st)=="function" then
- return st(pos, node)
- else
- return st
+ if ndef and ndef.advtrains then
+ return ndef.advtrains.node_state
end
end
@@ -45,31 +29,48 @@ function advtrains.setstate(parpos, newstate, pnode)
end
if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
debug.sethook()
- error("Invalid position supplied to getstate")
+ error("Invalid position supplied to setstate")
end
local node=pnode or advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
- local st
- if ndef and ndef.advtrains and ndef.advtrains.setstate then
- st=ndef.advtrains.setstate
- elseif ndef and ndef.luaautomation and ndef.luaautomation.setstate then
- if not deprecation_warned[node.name] then
- minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
- end
- st=ndef.luaautomation.setstate
- else
- return nil
+
+ if not ndef or not ndef.advtrains then
+ return false, "missing_node_def"
+ end
+ local old_state = ndef.advtrains.node_state
+
+ if old_state == newstate then
+ -- nothing needs to be done
+ return true
end
+ if not ndef.advtrains.node_state_map then
+ return false, "missing_node_state_map"
+ end
+ local new_node_name = ndef.advtrains.node_state_map[newstate]
+ if not new_node_name then
+ return false, "no_such_state"
+ end
+
+ -- prevent state switching when route lock or train is present
if advtrains.get_train_at_pos(pos) then
- return false
+ return false, "train_here"
end
- if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.pos_to_string(pos)) then
- return false
+ if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(advtrains.encode_pos(pos)) then
+ return false, "route_lock_here"
end
- st(pos, node, newstate)
+ -- perform the switch
+ local new_node = {name = new_node_name, param2 = node.param2}
+ advtrains.ndb.swap_node(pos, new_node)
+ -- if callback is present, call it
+ if ndef.advtrains.node_on_switch_state then
+ ndef.advtrains.node_on_switch_state(pos, new_node, old_state, newstate)
+ end
+ -- invalidate paths (only relevant if this is a track)
+ advtrains.invalidate_all_paths(pos)
+
return true
end
@@ -86,12 +87,7 @@ function advtrains.is_passive(parpos, pnode)
end
local node=pnode or advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
- if ndef and ndef.advtrains and ndef.advtrains.getstate then
- return true
- elseif ndef and ndef.luaautomation and ndef.luaautomation.getstate then
- if not deprecation_warned[node.name] then
- minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
- end
+ if ndef and ndef.advtrains and ndef.advtrains.node_state_map then
return true
else
return false
@@ -102,20 +98,10 @@ end
function advtrains.set_fallback_state(pos, pnode)
local node=pnode or advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
- local st
- if ndef and ndef.advtrains and ndef.advtrains.setstate
- and ndef.advtrains.fallback_state then
- if advtrains.get_train_at_pos(pos) then
- return false
- end
-
- if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.pos_to_string(pos)) then
- return false
- end
-
- ndef.advtrains.setstate(pos, node, ndef.advtrains.fallback_state)
- return true
- end
+ if not ndef or not ndef.advtrains or not ndef.advtrains.node_fallback_state then
+ return false, "no_fallback_state"
+ end
+ return advtrains.setstate(pos, ndef.advtrains.node_fallback_state, node)
end
diff --git a/advtrains/path.lua b/advtrains/path.lua
index f2b8a13..57829ad 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -33,17 +33,16 @@
-- If you need to proceed along the path by a specific actual distance, it does NOT work to simply add it to the index. You should use the path_get_index_by_offset() function.
-- creates the path data structure, reconstructing the train from a position and a connid
--- Important! train.drives_on must exist while calling this method
-- returns: true - successful
-- nil - node not yet available/unloaded, please wait
-- false - node definitely gone, remove train
function advtrains.path_create(train, pos, connid, rel_index)
local posr = advtrains.round_vector_floor_y(pos)
- local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, train.drives_on)
+ local node_ok, conns, rhe, connmap = advtrains.get_rail_info_at(pos)
if not node_ok then
return node_ok
end
- local mconnid = advtrains.get_matching_conn(connid, #conns)
+ local mconnid = advtrains.get_matching_conn(connid, connmap)
train.index = rel_index
train.path = { [0] = { x=posr.x, y=posr.y+rhe, z=posr.z } }
train.path_cn = { [0] = connid }
@@ -113,14 +112,21 @@ end
-- before returning from the calling function.
function advtrains.path_invalidate(train, ignore_lock)
if advtrains.lock_path_inval and not ignore_lock then
- atwarn("Train ",train.train_id,": Illegal path invalidation has occured during train step:")
+ atwarn("Train ",train.id,": Illegal path invalidation has occured during train step:")
atwarn(debug.traceback())
end
-
if train.path then
+ --atdebug("path_invalidate for",train.id)
+ local _cnt = 0
for i,p in pairs(train.path) do
- advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(p))
+ _cnt = _cnt + 1
+ if _cnt > 10000 then
+ atdebug(train)
+ error("Loop trap in advtrains.path_invalidate was triggered!")
+ end
+ advtrains.occ.clear_all_items(train.id, advtrains.round_vector_floor_y(p))
end
+ --atdebug("occ cleared")
end
train.path = nil
train.path_dist = nil
@@ -162,7 +168,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
@@ -207,23 +213,19 @@ function advtrains.path_get(train, index)
while index > pef do
local pos = train.path[pef]
local connid = train.path_cn[pef]
- local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns, next_connmap
if pef == train.path_trk_f then
node_ok, this_conns = advtrains.get_rail_info_at(pos)
if not node_ok then error("For train "..train.id..": Path item "..pef.." on-track but not a valid node!") end
- adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ adj_pos, adj_connid, conn_idx, nextrail_y, next_conns, next_connmap = advtrains.get_adjacent_rail(pos, this_conns, connid)
end
pef = pef + 1
if adj_pos then
advtrains.occ.set_item(train.id, adj_pos, pef)
-
- -- If we have split points, notify accordingly
- local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
- if #next_conns==3 and adj_connid==1 and train.points_split and train.points_split[advtrains.encode_pos(adj_pos)] then
- --atdebug(id,"has split points restored at",adj_pos)
- mconnid = 3
- end
-
+
+ local mconnid = advtrains.get_matching_conn(adj_connid, next_connmap)
+ -- NO split points handling here. It is only required for backwards path calculation
+
adj_pos.y = adj_pos.y + nextrail_y
train.path_cp[pef] = adj_connid
train.path_cn[pef] = mconnid
@@ -246,23 +248,26 @@ function advtrains.path_get(train, index)
while index < peb do
local pos = train.path[peb]
local connid = train.path_cp[peb]
- local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns, next_connmap
if peb == train.path_trk_b then
node_ok, this_conns = advtrains.get_rail_info_at(pos)
if not node_ok then error("For train "..train.id..": Path item "..peb.." on-track but not a valid node!") end
- adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ adj_pos, adj_connid, conn_idx, nextrail_y, next_conns, next_connmap = advtrains.get_adjacent_rail(pos, this_conns, connid)
end
peb = peb - 1
if adj_pos then
advtrains.occ.set_item(train.id, adj_pos, peb)
- -- If we have split points, notify accordingly
- local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
- if #next_conns==3 and adj_connid==1 and train.points_split and train.points_split[advtrains.encode_pos(adj_pos)] then
- -- atdebug(id,"has split points restored at",adj_pos)
- mconnid = 3
+ local mconnid = advtrains.get_matching_conn(adj_connid, next_connmap)
+ -- If, for this position, we have remembered the origin conn, apply it here
+ if next_connmap then -- only needs to be done when this track is a turnout (>2 conns)
+ local origin_conn = train.path_ori_cp[advtrains.encode_pos(adj_pos)]
+ if origin_conn then
+ --atdebug("Train",train.id,"at",adj_pos,"restoring turnout origin CP",origin_conn,"for path item",index)
+ mconnid = origin_conn
+ end
end
-
+
adj_pos.y = adj_pos.y + nextrail_y
train.path_cn[peb] = adj_connid
train.path_cp[peb] = mconnid
@@ -375,12 +380,25 @@ function advtrains.path_get_index_by_offset(train, index, offset)
return c_idx + frac
end
+
+-- The path_dist[] table contains absolute distance values for every whole index.
+-- Use this function to retrieve the correct absolute distance for a fractional index value (interpolate between floor and ceil index)
+-- returns: absolute distance from path item 0
+function advtrains.path_get_path_dist_fractional(train, index)
+ local start_index_f = atfloor(index)
+ local frac = index - start_index_f
+ -- ensure path exists
+ advtrains.path_get_adjacent(train, index)
+ local dist1, dist2 = train.path_dist[start_index_f], train.path_dist[start_index_f+1]
+ return dist1 + (dist2-dist1)*frac
+end
+
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
@@ -421,18 +439,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..db19fc9
--- /dev/null
+++ b/advtrains/po/advtrains.pot
@@ -0,0 +1,625 @@
+# 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: 2025-06-10 21:40+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"
+
+#: atc.lua
+msgid "Unconfigured ATC controller"
+msgstr ""
+
+#: atc.lua
+msgid "ATC controller, Command: @1"
+msgstr ""
+
+#: atc.lua
+msgid "Command"
+msgstr ""
+
+#: atc.lua
+msgid "Digiline channel"
+msgstr ""
+
+#: atc.lua wagons.lua
+msgid "Save"
+msgstr ""
+
+#: atc.lua
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr ""
+
+#: atc.lua
+msgid "ATC Kick command warning: doors are closed."
+msgstr ""
+
+#: atc.lua
+msgid "ATC Kick command warning: train moving."
+msgstr ""
+
+#: atc.lua
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr ""
+
+#: atc.lua
+msgid "ATC command parse error: Unknown command: @1"
+msgstr ""
+
+#: copytool.lua
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+
+#: copytool.lua
+msgid "You do not have the @1 privilege."
+msgstr ""
+
+#: copytool.lua
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr ""
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr ""
+
+#: copytool.lua
+msgid "The clipboard is empty."
+msgstr ""
+
+#: copytool.lua
+msgid "Back of train would end up off track, cancelling."
+msgstr ""
+
+#: copytool.lua
+msgid "No such lua entity."
+msgstr ""
+
+#: copytool.lua
+msgid "No such wagon: @1."
+msgstr ""
+
+#: copytool.lua
+msgid "No such train: @1."
+msgstr ""
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr ""
+
+#: copytool.lua
+msgid "Train copied."
+msgstr ""
+
+#: couple.lua
+msgid "Buffer and Chain Coupler"
+msgstr ""
+
+#: couple.lua
+msgid "Scharfenberg Coupler"
+msgstr ""
+
+#: couple.lua
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr ""
+
+#: couple.lua
+msgid ""
+"Cannot couple @1 and @2 - train would have length @3 which is above the "
+"limit of @4"
+msgstr ""
+
+#: couple.lua
+msgid "<No coupler>"
+msgstr ""
+
+#: couple.lua
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr ""
+
+#: craft_items.lua
+msgid "Boiler"
+msgstr ""
+
+#: craft_items.lua
+msgid "Driver's cab"
+msgstr ""
+
+#: craft_items.lua
+msgid "Wheel"
+msgstr ""
+
+#: craft_items.lua
+msgid "Chimney"
+msgstr ""
+
+#: init.lua
+msgid "Restoring saved state in 1 second..."
+msgstr ""
+
+#: init.lua
+msgid "Reload successful!"
+msgstr ""
+
+#: init.lua
+msgid "Removing unused wagon"
+msgstr ""
+
+#: init.lua
+msgid "from wagon_save table."
+msgstr ""
+
+#: init.lua
+msgid ""
+"Train @1 had no wagons left because of some bug. It is being deleted. Wave "
+"it goodbye!"
+msgstr ""
+
+#: init.lua
+msgid "Saving failed: @1"
+msgstr ""
+
+#: init.lua
+msgid "Instructed to save() but load() was never called!"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Crash during advtrains main step - skipping the shutdown save operation to "
+"not save inconsistent data!"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Detach all players, especially the offline ones, from all trains. Use only "
+"when no one serious is on a train."
+msgstr ""
+
+#: init.lua
+msgid ""
+"Data is being saved. While saving, advtrains will remove the players from "
+"trains. Save files will be reloaded afterwards!"
+msgstr ""
+
+#: init.lua
+msgid "Delete all train routes, force them to recalculate"
+msgstr ""
+
+#: init.lua
+msgid "Successfully invalidated train routes"
+msgstr ""
+
+#: init.lua
+msgid "Returns the position of the train with the given id"
+msgstr ""
+
+#: init.lua
+msgid "Train @1 does not exist or is invalid"
+msgstr ""
+
+#: init.lua
+msgid "Train @1 is at @2"
+msgstr ""
+
+#: init.lua
+msgid "Teleports you to the position of the train with the given id"
+msgstr ""
+
+#: init.lua
+msgid "Teleporting to train @1"
+msgstr ""
+
+#: init.lua
+msgid "Disable the advtrains globalstep temporarily"
+msgstr ""
+
+#: init.lua
+msgid ""
+"The advtrains globalstep has been disabled. Trains are not moving, and no "
+"data is saved! Run '/at_disable_step no' to enable again!"
+msgstr ""
+
+#: init.lua
+msgid "Disabled advtrains successfully"
+msgstr ""
+
+#: init.lua
+msgid "Re-enabling advtrains globalstep..."
+msgstr ""
+
+#: init.lua
+msgid "Advtrains is already running normally!"
+msgstr ""
+
+#: init.lua
+msgid "Print advtrains status info"
+msgstr ""
+
+#: init.lua
+msgid "Advtrains Status: no_action @1 slowdown @2 (log @3)"
+msgstr ""
+
+#: misc_nodes.lua
+msgid "@1 Platform (low)"
+msgstr ""
+
+#: misc_nodes.lua
+msgid "@1 Platform (high)"
+msgstr ""
+
+#: misc_nodes.lua
+msgid "@1 Platform (45 degree)"
+msgstr ""
+
+#: misc_nodes.lua
+msgid "@1 Platform (low, 45 degree)"
+msgstr ""
+
+#: protection.lua
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: protection.lua
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: protection.lua
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: protection.lua
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: protection.lua
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr ""
+
+#: protection.lua
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr ""
+
+#: protection.lua
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr ""
+
+#: protection.lua
+msgid "You are not allowed to build tracks at this protected position."
+msgstr ""
+
+#: protection.lua
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr ""
+
+#: signals.lua
+msgid "Lampless Signal (deprecated!)"
+msgstr ""
+
+#: signals.lua
+msgid "Signal (deprecated!)"
+msgstr ""
+
+#: signals.lua
+msgid "Wallmounted Signal (left) (deprecated!)"
+msgstr ""
+
+#: signals.lua
+msgid "Wallmounted Signal (right) (deprecated!)"
+msgstr ""
+
+#: signals.lua
+msgid "Wallmounted Signal (top) (deprecated!)"
+msgstr ""
+
+#: signals.lua
+msgid "Andrew's Cross"
+msgstr ""
+
+#: track_reg_helper.lua tracks.lua
+msgid "This track can not be removed!"
+msgstr ""
+
+#: track_reg_helper.lua
+msgid "@1 Slope"
+msgstr ""
+
+#: track_reg_helper.lua
+msgid "Can't place: not pointing at node"
+msgstr ""
+
+#: track_reg_helper.lua
+msgid "Can't place: space occupied!"
+msgstr ""
+
+#: track_reg_helper.lua
+msgid "Can't place: Not enough slope items left (@1 required)"
+msgstr ""
+
+#: track_reg_helper.lua
+msgid "Can't place: There's no slope of length @1"
+msgstr ""
+
+#: track_reg_helper.lua
+msgid "Can't place: no supporting node at upper end."
+msgstr ""
+
+#: trackplacer.lua
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+
+#: trackplacer.lua
+msgid "This node can't be rotated using the trackworker!"
+msgstr ""
+
+#: trackplacer.lua
+msgid "This track can not be rotated!"
+msgstr ""
+
+#: trackplacer.lua
+msgid "This node can't be changed using the trackworker!"
+msgstr ""
+
+#: tracks.lua
+msgid "Position is occupied by a train."
+msgstr ""
+
+#: tracks.lua
+msgid "There's a Track Circuit Break here."
+msgstr ""
+
+#: tracks.lua
+msgid "There's a Signal Influence Point here."
+msgstr ""
+
+#: trainhud.lua
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: wagonprop_tool.lua
+msgid ""
+"Wagon Properties Tool\n"
+"Punch a wagon to view and edit the Wagon Properties"
+msgstr ""
+
+#: wagonprop_tool.lua
+msgid "Wagon Properties Tool"
+msgstr ""
+
+#: wagonprop_tool.lua
+msgid "Insufficient privileges to use this!"
+msgstr ""
+
+#: wagons.lua
+msgid "Uninitialized, removing"
+msgstr ""
+
+#: wagons.lua
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr ""
+
+#: wagons.lua
+msgid ""
+"Destroying wagon with inventory, but inventory is not found? Shouldn't "
+"happen!"
+msgstr ""
+
+#: wagons.lua
+msgid "The wagon's inventory is not empty."
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+
+#: wagons.lua
+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 ""
+
+#: wagons.lua
+msgid "!!! Train off track !!!"
+msgstr ""
+
+#: wagons.lua
+msgid " units"
+msgstr ""
+
+#: wagons.lua
+msgid "Liquid: "
+msgstr ""
+
+#: wagons.lua
+msgid "Liquid: empty"
+msgstr ""
+
+#: wagons.lua
+msgid "Show Inventory"
+msgstr ""
+
+#: wagons.lua
+msgid "Onboard Computer"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon properties"
+msgstr ""
+
+#: wagons.lua
+msgid "Get off"
+msgstr ""
+
+#: wagons.lua
+msgid "Get off (forced)"
+msgstr ""
+
+#: wagons.lua
+msgid "(Doors closed)"
+msgstr ""
+
+#: wagons.lua
+msgid "This wagon has no seats."
+msgstr ""
+
+#: wagons.lua
+msgid "This wagon is full."
+msgstr ""
+
+#: wagons.lua
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr ""
+
+#: wagons.lua
+msgid "You can't get on this wagon."
+msgstr ""
+
+#: wagons.lua
+msgid "Select seat:"
+msgstr ""
+
+#: wagons.lua
+msgid "This Wagon ID"
+msgstr ""
+
+#: wagons.lua
+msgid "Allow these players to access your wagon:"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon road number:"
+msgstr ""
+
+#: wagons.lua
+msgid "Freight Code:"
+msgstr ""
+
+#: wagons.lua
+msgid "Prev FC"
+msgstr ""
+
+#: wagons.lua
+msgid "Current FC: "
+msgstr ""
+
+#: wagons.lua
+msgid "Next FC:"
+msgstr ""
+
+#: wagons.lua
+msgid "Save wagon properties"
+msgstr ""
+
+#: wagons.lua
+msgid "Train ID"
+msgstr ""
+
+#: wagons.lua
+msgid "Text displayed outside on train"
+msgstr ""
+
+#: wagons.lua
+msgid "Text displayed inside train"
+msgstr ""
+
+#: wagons.lua
+msgid "Line"
+msgstr ""
+
+#: wagons.lua
+msgid "Routingcode"
+msgstr ""
+
+#: wagons.lua
+msgid "Train overview /coupling control:"
+msgstr ""
+
+#: wagons.lua
+msgid "Train overview / coupling control is only shown when the train stands."
+msgstr ""
+
+#: wagons.lua
+msgid "Remote Routesetting"
+msgstr ""
+
+#: wagons.lua
+msgid "Clear 'Disable ARS' flag"
+msgstr ""
+
+#: wagons.lua
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr ""
+
+#: wagons.lua
+msgid "You are not allowed to access the driver stand."
+msgstr ""
+
+#: wagons.lua
+msgid "Missing train_operator privilege"
+msgstr ""
+
+#: wagons.lua
+msgid "Not allowed to do this."
+msgstr ""
+
+#: wagons.lua
+msgid "You don't have the train_operator privilege."
+msgstr ""
+
+#: wagons.lua
+msgid "The track you are trying to place the wagon on is not long enough!"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon placeholder"
+msgstr ""
+
+#: wagons.lua
+msgid "Please specify a player name to transfer ownership to."
+msgstr ""
+
+#: wagons.lua
+msgid "That player does not exist!"
+msgstr ""
+
+#: wagons.lua
+msgid "Not a valid wagon id."
+msgstr ""
+
+#: wagons.lua
+msgid "That wagon does not exist!"
+msgstr ""
+
+#: wagons.lua
+msgid "You have been given ownership of wagon @1"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon @1 ownership changed from @2 to @3"
+msgstr ""
diff --git a/advtrains/po/de.po b/advtrains/po/de.po
new file mode 100644
index 0000000..f83b760
--- /dev/null
+++ b/advtrains/po/de.po
@@ -0,0 +1,925 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2025-06-10 21:40+0200\n"
+"PO-Revision-Date: 2025-06-11 22:55+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.2.2\n"
+
+#: atc.lua
+msgid "ATC Kick command warning: doors are closed."
+msgstr ""
+"Zugbeeinflussung: Wegen geschlossener Türen werden Fahrgäste nicht zum "
+"Ausstieg gezwungen."
+
+#: atc.lua
+msgid "ATC Kick command warning: train moving."
+msgstr ""
+"Zugbeeinflussung: Der Zug befindet sich in Bewegung, Fahrgäste werden nicht "
+"zum Ausstieg gezwungen."
+
+#: atc.lua
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr ""
+"Zugbeeinflussung: Der Zug befindet sich in Bewegung und kann nicht umgekehrt "
+"werden."
+
+#: atc.lua
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "Zugbeeinflussung: Unbekannter Befehl: @1"
+
+#: atc.lua
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "Zugbeeinflussung: Unvollständiger I-Befehl: @1"
+
+#: atc.lua
+msgid "ATC controller, Command: @1"
+msgstr "Zugbeeinflussungsgleis, Befehl: @1"
+
+#: atc.lua
+msgid "Command"
+msgstr "Befehl"
+
+#: atc.lua
+msgid "Digiline channel"
+msgstr "Digiline-Kanal"
+
+#: atc.lua wagons.lua
+msgid "Save"
+msgstr "Speichern"
+
+#: atc.lua
+msgid "Unconfigured ATC controller"
+msgstr "Nicht konfiguiertes Zugbeeinflussungsgleis"
+
+#: copytool.lua
+msgid "Back of train would end up off track, cancelling."
+msgstr "Der hinterer Teil dez Zuges wäre nicht auf dem Gleis."
+
+#: copytool.lua
+msgid "No such lua entity."
+msgstr ""
+"Sie zeigen nicht auf einem Objekt, das mit diesem Werkzeug kopiert werden "
+"kann."
+
+#: copytool.lua
+msgid "No such train: @1."
+msgstr "Es gibt keinen mit „@1“ identifizierbaren Zug."
+
+#: copytool.lua
+msgid "No such wagon: @1."
+msgstr "Es gibt keinen mit „@1“ identifizierbaren Waggon."
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr ""
+"Wegen des fehlgeschlagenen Zugriffs auf die Metadaten konnte der Zug nicht "
+"kopiert werden."
+
+#: copytool.lua
+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."
+
+#: copytool.lua
+msgid "The clipboard is empty."
+msgstr "Das Clipboard ist leer."
+
+#: copytool.lua
+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."
+
+#: copytool.lua
+msgid "Train copied."
+msgstr "Der Zug wurde kopiert."
+
+#: copytool.lua
+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"
+
+#: copytool.lua
+msgid "You do not have the @1 privilege."
+msgstr "Ihnen fehlt das „@1“-Privileg."
+
+#: couple.lua
+msgid "<No coupler>"
+msgstr "<Keine Kupplung vorhanden>"
+
+#: couple.lua
+msgid "Buffer and Chain Coupler"
+msgstr "Schraubenkupplung"
+
+#: couple.lua
+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)."
+
+#: couple.lua
+msgid ""
+"Cannot couple @1 and @2 - train would have length @3 which is above the "
+"limit of @4"
+msgstr ""
+"Züge @1 und @2 können nicht gekuppelt werden - die resultierende Zuglänge @3 "
+"wäre länger als das Maximum von @4"
+
+#: couple.lua
+msgid "Scharfenberg Coupler"
+msgstr "Scharfenbergkupplung"
+
+#: couple.lua
+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."
+
+#: craft_items.lua
+msgid "Boiler"
+msgstr "Kessel"
+
+#: craft_items.lua
+msgid "Chimney"
+msgstr "Schornstein"
+
+#: craft_items.lua
+msgid "Driver's cab"
+msgstr "Führerstand"
+
+#: craft_items.lua
+msgid "Wheel"
+msgstr "Rad"
+
+#: init.lua
+msgid "Advtrains Status: no_action @1 slowdown @2 (log @3)"
+msgstr ""
+"Advtrains Status: keine Aktion: @1 Verlangsamungsfaktor: @2 (logarithmisch "
+"@3)"
+
+#: init.lua
+msgid "Advtrains is already running normally!"
+msgstr "Advtrains läuft bereits normal!"
+
+#: init.lua
+msgid ""
+"Crash during advtrains main step - skipping the shutdown save operation to "
+"not save inconsistent data!"
+msgstr ""
+"Advtrains ist während der Ausführung des Main Step abgestürzt - Daten werden "
+"nicht gespeichert um Inkonsistenzen zu verhindern!"
+
+#: init.lua
+msgid ""
+"Data is being saved. While saving, advtrains will remove the players from "
+"trains. Save files will be reloaded afterwards!"
+msgstr ""
+"Daten werden gespeichert. Beim Speichern werden alle Spieler aus Waggons "
+"entfernt. Anschließend werden die Speicherdateien neu geladen!"
+
+#: init.lua
+msgid "Delete all train routes, force them to recalculate"
+msgstr "Erzwingt die Neuberechnung der Bewegungspfade aller Züge"
+
+#: init.lua
+msgid ""
+"Detach all players, especially the offline ones, from all trains. Use only "
+"when no one serious is on a train."
+msgstr ""
+"Bewirkt, dass alle Spieler (insbesondere Offline-Spieler) aus Waggons "
+"entfernt werden. Nur benutzen, wenn niemand ernsthaft in einem Zug sitzt."
+
+#: init.lua
+msgid "Disable the advtrains globalstep temporarily"
+msgstr "Advtrains-Schrittverarbeitung temporär deaktivieren"
+
+#: init.lua
+msgid "Disabled advtrains successfully"
+msgstr "Advtrains erfolgreich deaktiviert"
+
+#: init.lua
+msgid "Instructed to save() but load() was never called!"
+msgstr "Speichern angefordert, aber Speicherdateien wurden nie geladen!"
+
+#: init.lua
+msgid "Print advtrains status info"
+msgstr "Advtrains-Statusinformationen ausgeben"
+
+#: init.lua
+msgid "Re-enabling advtrains globalstep..."
+msgstr "Advtrains-Schrittverarbeitung wird wieder aktiviert..."
+
+#: init.lua
+msgid "Reload successful!"
+msgstr "Neu Laden Erfolgreich!"
+
+#: init.lua
+msgid "Removing unused wagon"
+msgstr "Ungenutzer Waggon wird entfernt"
+
+#: init.lua
+msgid "Restoring saved state in 1 second..."
+msgstr "Speicherstand wird in 1 Sekunde wiederhergestellt..."
+
+#: init.lua
+msgid "Returns the position of the train with the given id"
+msgstr "Gibt die Position des Zugs mit der gegebenen ID aus"
+
+#: init.lua
+msgid "Saving failed: @1"
+msgstr "Speichern ist fehlgeschlagen: @1"
+
+#: init.lua
+msgid "Successfully invalidated train routes"
+msgstr "Bewegungspfade der Züge erfolgreich invalidiert"
+
+#: init.lua
+msgid "Teleporting to train @1"
+msgstr "Teleportiere zu Zug @1"
+
+#: init.lua
+msgid "Teleports you to the position of the train with the given id"
+msgstr "Teleportiere dich zu dem Zug mit der gegebenen ID"
+
+#: init.lua
+msgid ""
+"The advtrains globalstep has been disabled. Trains are not moving, and no "
+"data is saved! Run '/at_disable_step no' to enable again!"
+msgstr ""
+"Advtrains-Schrittverarbeitung ist deaktiviert. Züge bewegen sich nicht und "
+"Änderungen werden nicht gespeichert. Führe '/at_disable_step no' aus um sie "
+"wieder zu aktivieren!"
+
+#: init.lua
+msgid "Train @1 does not exist or is invalid"
+msgstr "Zug @1 existiert nicht oder ist ungültig"
+
+#: init.lua
+msgid ""
+"Train @1 had no wagons left because of some bug. It is being deleted. Wave "
+"it goodbye!"
+msgstr ""
+"Zug @1 hat keine Wagen mehr und sollte nicht mehr existieren. Er wird "
+"gelöscht. Auf Wiedersehen!"
+
+#: init.lua
+msgid "Train @1 is at @2"
+msgstr "Zug @1 ist bei @2"
+
+#: init.lua
+msgid "from wagon_save table."
+msgstr "aus der wagon_save table"
+
+#: misc_nodes.lua
+msgid "@1 Platform (45 degree)"
+msgstr "Hoher @1-Bahnsteig (45°)"
+
+#: misc_nodes.lua
+msgid "@1 Platform (high)"
+msgstr "Hoher @1-Bahnsteig"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low)"
+msgstr "Niedriger @1-Bahnsteig"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low, 45 degree)"
+msgstr "Niedriger @1-Bahnsteig (45°)"
+
+#: protection.lua
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr "Darf Weichen und Signale in ungeschütztem Gebiet stellen"
+
+#: protection.lua
+msgid "Can place and dig tracks in unprotected areas"
+msgstr "Darf Gleise in ungeschütztem Gebiet verlegen und entfernen"
+
+#: protection.lua
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr "Darf, unabhängig von Besitzer oder Schutz, sämtliche Züge steuern"
+
+#: protection.lua
+msgid "Can place, remove and operate trains"
+msgstr "Darf Züge platzieren, entfernen und steuern"
+
+#: protection.lua
+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."
+
+#: protection.lua
+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."
+
+#: protection.lua
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "Sie dürfen an geschützten Stellen kein Gleis bauen."
+
+#: protection.lua
+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."
+
+#: protection.lua
+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."
+
+#: signals.lua
+msgid "Andrew's Cross"
+msgstr "Andreaskreuz"
+
+#: signals.lua
+msgid "Lampless Signal (deprecated!)"
+msgstr "Mechanisches Signal (veraltet!)"
+
+#: signals.lua
+msgid "Signal (deprecated!)"
+msgstr "Signal (veraltet!)"
+
+#: signals.lua
+msgid "Wallmounted Signal (left) (deprecated!)"
+msgstr "An der linken Seite montiertes Signal (veraltet!)"
+
+#: signals.lua
+msgid "Wallmounted Signal (right) (deprecated!)"
+msgstr "An der rechten Seite montiertes Signal (veraltet!)"
+
+#: signals.lua
+msgid "Wallmounted Signal (top) (deprecated!)"
+msgstr "An der Decke montiertes Signal (veraltet!)"
+
+#: track_reg_helper.lua
+msgid "@1 Slope"
+msgstr "@1 Steigung"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: 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."
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: There's no slope of length @1"
+msgstr ""
+"Es kann nicht platziert werden: die Steigung der Länge @1 ist nicht "
+"definiert."
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: no supporting node at upper end."
+msgstr ""
+"Es kann nicht platziert werden: es gibt keinen unterstützenden Block am Ende "
+"der Steigung."
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: not pointing at node"
+msgstr "Es kann nicht platziert werden: Sie zeigen nicht auf einem Block."
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: space occupied!"
+msgstr "Es kann nicht platziert werden: Diese Position ist besetzt."
+
+#: track_reg_helper.lua tracks.lua
+#, fuzzy
+msgid "This track can not be removed!"
+msgstr "Dieses Gleis kann nicht entfernt werden."
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This node can't be changed using the trackworker!"
+msgstr "Dieser Block kann nicht mit dem Gleiswerkzeug bearbeitet werden."
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This node can't be rotated using the trackworker!"
+msgstr "Dieser Block kann nicht mit dem Gleiswerkzeug gedreht werden."
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This track can not be rotated!"
+msgstr "Dieses Gleis kann nicht gedreht werden."
+
+#: trackplacer.lua
+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"
+
+#: tracks.lua
+msgid "Position is occupied by a train."
+msgstr "Ein Zug steht an dieser Position."
+
+#: tracks.lua
+msgid "There's a Signal Influence Point here."
+msgstr "Hier ist ein Signal-Beeinflussungspunkt."
+
+#: tracks.lua
+msgid "There's a Track Circuit Break here."
+msgstr "Hier ist eine Gleisabschnittsgrenze (TCB)."
+
+#: trainhud.lua
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+"ROTES SIGNAL ÜBERFAHREN! Situation überprüfen und Fahrtrichtung wechseln, um "
+"Bewegungssperre aufzuheben."
+
+#: wagonprop_tool.lua
+msgid "Insufficient privileges to use this!"
+msgstr "Unzureichende Privilegien, um dies zu benutzen!"
+
+#: wagonprop_tool.lua
+#, fuzzy
+msgid "Wagon Properties Tool"
+msgstr "Waggon-Einstellungen"
+
+#: wagonprop_tool.lua
+msgid ""
+"Wagon Properties Tool\n"
+"Punch a wagon to view and edit the Wagon Properties"
+msgstr ""
+"Waggon-Einstellungstool\n"
+"Auf Waggon anwenden um Waggon-Einstellungen aufzurufen"
+
+#: wagons.lua
+msgid " units"
+msgstr " Einheiten"
+
+#: wagons.lua
+msgid "!!! Train off track !!!"
+msgstr "!!! Abseits vom Gleis !!!"
+
+#: wagons.lua
+msgid "(Doors closed)"
+msgstr "(Türen geschlossen)"
+
+#: wagons.lua
+msgid "Allow these players to access your wagon:"
+msgstr "Diese Spieler dürfen auf den Waggon zugreifen:"
+
+#: wagons.lua
+msgid "Clear 'Disable ARS' flag"
+msgstr "ARS-Sperre aufheben"
+
+#: wagons.lua
+msgid "Current FC: "
+msgstr "Aktueller FC:"
+
+#: wagons.lua
+msgid ""
+"Destroying wagon with inventory, but inventory is not found? Shouldn't "
+"happen!"
+msgstr ""
+"Waggon mit Inventar zerstört, aber Inventar nicht gefunden? Sollte nicht "
+"passieren!"
+
+#: wagons.lua
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr "Die Türen sind geschlossen! (Probiere Schleichen-Taste!)"
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+msgid "Freight Code:"
+msgstr "Frachtcode:"
+
+#: wagons.lua
+msgid "Get off"
+msgstr "Aussteigen"
+
+#: wagons.lua
+msgid "Get off (forced)"
+msgstr "Ausstieg zwingen"
+
+#: wagons.lua
+msgid "Line"
+msgstr "Linie"
+
+#: wagons.lua
+msgid "Liquid: "
+msgstr "Flüssigkeit:"
+
+#: wagons.lua
+msgid "Liquid: empty"
+msgstr "Flüssigkeit: leer"
+
+#: wagons.lua
+msgid "Missing train_operator privilege"
+msgstr "Fehlendes train_operator Privileg"
+
+#: wagons.lua
+msgid "Next FC:"
+msgstr "Nächster FC:"
+
+#: wagons.lua
+msgid "Not a valid wagon id."
+msgstr "Keine gültige Waggon-ID"
+
+#: wagons.lua
+msgid "Not allowed to do this."
+msgstr "Sie dürfen dies nicht tun."
+
+#: wagons.lua
+msgid "Onboard Computer"
+msgstr "Bordcomputer"
+
+#: wagons.lua
+msgid "Please specify a player name to transfer ownership to."
+msgstr ""
+"Geben Sie einen Spielernamen an, an den der Besitz übertragen werden soll."
+
+#: wagons.lua
+msgid "Prev FC"
+msgstr "Vorheriger FC"
+
+#: wagons.lua
+msgid "Remote Routesetting"
+msgstr "Fahrstraße entfernt stellen"
+
+#: wagons.lua
+msgid "Routingcode"
+msgstr "Routingcode"
+
+#: wagons.lua
+msgid "Save wagon properties"
+msgstr "Waggon-Einstellungen speichern"
+
+#: wagons.lua
+msgid "Select seat:"
+msgstr "Wählen Sie einen Sitzplatz aus:"
+
+#: wagons.lua
+msgid "Show Inventory"
+msgstr "Inventar Zeigen"
+
+#: wagons.lua
+msgid "Text displayed inside train"
+msgstr "Innere Anzeige"
+
+#: wagons.lua
+msgid "Text displayed outside on train"
+msgstr "Äußere Anzeige"
+
+#: wagons.lua
+msgid "That player does not exist!"
+msgstr "Dieser Spieler existiert nicht!"
+
+#: wagons.lua
+msgid "That wagon does not exist!"
+msgstr "Dieser Wagen existiert nicht!"
+
+#: wagons.lua
+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!"
+
+#: wagons.lua
+msgid "The wagon's inventory is not empty."
+msgstr "Das Inventar dieses Waggons ist nicht leer."
+
+#: wagons.lua
+msgid "This Wagon ID"
+msgstr "Dieser Wagen ID"
+
+#: wagons.lua
+msgid "This wagon has no seats."
+msgstr "In diesem Waggon ist kein Sitzplatz vorhanden."
+
+#: wagons.lua
+msgid "This wagon is full."
+msgstr "Der Waggon ist voll."
+
+#: wagons.lua
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "Dieser Waggon gehört @1, Sie dürfen ihn nicht abbauen."
+
+#: wagons.lua
+msgid "Train ID"
+msgstr "Zug-ID"
+
+#: wagons.lua
+msgid "Train overview / coupling control is only shown when the train stands."
+msgstr "Zugübersicht / Kupplungssteuerung nur im Stillstand verfügbar."
+
+#: wagons.lua
+msgid "Train overview /coupling control:"
+msgstr "Zugübersicht / Kupplungssteuerung:"
+
+#: wagons.lua
+msgid "Uninitialized, removing"
+msgstr "Nicht initialisiert, entferne"
+
+#: wagons.lua
+msgid "Wagon @1 ownership changed from @2 to @3"
+msgstr "Besitzer von Waggon @1 geändert von @2 auf @3"
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+msgid "Wagon placeholder"
+msgstr "Waggon-Platzhalter"
+
+#: wagons.lua
+msgid "Wagon properties"
+msgstr "Waggon-Einstellungen"
+
+#: wagons.lua
+msgid "Wagon road number:"
+msgstr "Wagennummer:"
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+msgid "You are not allowed to access the driver stand."
+msgstr "Sie haben keinen Zugang zum Führerstand."
+
+#: wagons.lua
+msgid "You can't get on this wagon."
+msgstr "Sie können nicht in diesen Waggon einsteigen."
+
+#: wagons.lua
+#, fuzzy
+msgid "You don't have the train_operator privilege."
+msgstr "Ihnen fehlt das „@1“-Privileg."
+
+#: wagons.lua
+msgid "You have been given ownership of wagon @1"
+msgstr "Sie sind nun der Besitzer von Waggon @1"
+
+#~ msgid "3-way turnout"
+#~ msgstr "Dreiwegweiche"
+
+#~ msgid "90+Angle Diamond Crossing Track"
+#~ msgstr "Kreuzung mit einem achsenparallelen Gleis"
+
+#~ msgid "ATC controller"
+#~ msgstr "Zugbeeinflussungsgleis"
+
+#~ 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 "Big Industrial Train Engine"
+#~ msgstr "Große Industrielle Lokomotive"
+
+#~ msgid "Box Wagon"
+#~ msgstr "Güterwaggon"
+
+#~ msgid "Bumper"
+#~ msgstr "Prellbock"
+
+#~ 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)"
+
+#~ 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 "Command (on)"
+#~ msgstr "Befehl (wenn aktiviert)"
+
+#~ msgid "Default Seat"
+#~ msgstr "Standardsitzplatz"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "Standardsitzplatz (Führerstand)"
+
+#~ msgid "Dep. Speed"
+#~ msgstr "Zielgeschwindigkeit bei Abfahrt"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "ausrangiertes Gleis, nicht verwenden."
+
+#~ msgid "Detailed Steam Engine"
+#~ msgstr "Detaillierte Dampflokomotive"
+
+#~ msgid "Detector Rail"
+#~ msgstr "Detektorgleis"
+
+#~ msgid "Diagonal Diamond Crossing Track"
+#~ msgstr "Diagonale Gleiskreuzung"
+
+#~ msgid "Door Delay"
+#~ msgstr "Zeit für die Türschließung"
+
+#~ msgid "Door Side"
+#~ msgstr "Türseite"
+
+#, fuzzy
+#~ msgid "Driver Stand"
+#~ msgstr "Führerstand"
+
+#~ msgid "Driver Stand (left)"
+#~ msgstr "Führerstand Links"
+
+#~ msgid "Driver Stand (right)"
+#~ msgstr "Führerstand Rechts"
+
+#~ msgid "Driver stand"
+#~ msgstr "Führerstand"
+
+#~ msgid "Industrial Train Engine"
+#~ msgstr "Industrielle Lokomotive"
+
+#~ msgid "Industrial tank wagon"
+#~ msgstr "Tankwaggon"
+
+#~ msgid "Industrial wood wagon"
+#~ msgstr "Holztransportwaggon"
+
+#~ msgid "Japanese Train Engine"
+#~ msgstr "Japanische Personenzug-Lokomotive"
+
+#~ msgid "Japanese Train Inter-Wagon Connection"
+#~ msgstr "Waggonzwischenverbindung Japanischer Personenzüge"
+
+#~ msgid "Japanese Train Wagon"
+#~ msgstr "Japanischer Personenzug-Passagierwaggon"
+
+#, fuzzy
+#~ msgid "Japanese signal pole"
+#~ msgstr "Japanischer Personenzug-Passagierwaggon"
+
+#~ msgid "Kick out passengers"
+#~ msgstr "Fahrgäste zum Ausstieg zwingen"
+
+#~ msgid "Loading Track"
+#~ msgstr "Beladungsgleis"
+
+#~ msgid "Lock couples"
+#~ msgstr "Kupplungen sperren"
+
+#~ msgid "LuaATC component with error: @1"
+#~ msgstr "LuaATC-Bauteil mit Fehlermeldung: @1"
+
+#~ msgid "Passenger Wagon"
+#~ msgstr "Passagierwaggon"
+
+#, fuzzy
+#~ msgid "Passenger area"
+#~ msgstr "Passagierwaggon"
+
+#~ 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."
+
+#~ msgid "Perpendicular Diamond Crossing Track"
+#~ msgstr "Kreuzung mit zueinander orthogonalen Gleisen"
+
+#~ msgid "Point Speed Restriction Track"
+#~ msgstr "Geschwindigkeitskontrollgleis"
+
+#~ msgid "Point speed restriction: @1"
+#~ msgstr "Geschwindigkeitskontrolle: @1"
+
+#~ msgid "Reverse train"
+#~ msgstr "Zug Umkehren"
+
+#~ msgid "Signal"
+#~ msgstr "Lichtsignal"
+
+#~ msgid "Speed:"
+#~ msgstr "Geschw.:"
+
+#~ msgid "Station Code"
+#~ msgstr "Kennzeichen der Haltestelle"
+
+#~ msgid "Station Name"
+#~ msgstr "Name der Haltestelle"
+
+#~ 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."
+
+#~ msgid "Station/Stop Track"
+#~ msgstr "Gleis zur Kennzeichnung einer Haltestelle"
+
+#~ msgid "Steam Engine"
+#~ msgstr "Dampflokomotive"
+
+#~ msgid "Stop Time"
+#~ msgstr "Wartezeit"
+
+#~ msgid "Subway Passenger Wagon"
+#~ msgstr "U-Bahn-Waggon"
+
+#~ msgid "Target:"
+#~ msgstr "Zielges.:"
+
+#~ msgid "This position is protected!"
+#~ msgstr "Diese Position ist geschützt!"
+
+#~ 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."
+
+#~ msgid "This track can not be changed."
+#~ msgstr "Dieses Gleis kann nicht geändert werden."
+
+#~ msgid "Track"
+#~ msgstr "Gleis"
+
+#, fuzzy
+#~ msgid "Train "
+#~ msgstr "Der Zug wurde Kopiert."
+
+#~ msgid "Unconfigured LuaATC component"
+#~ msgstr "Nicht konfiguierter LuaATC-Bauteil"
+
+#~ msgid "Unloading Track"
+#~ msgstr "Abladungsgleis"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr ""
+#~ "Nutzen Sie Schleichen+Rechtsklick, um trotz geschlossener Türen "
+#~ "einzusteigen."
+
+#~ msgid "Y-turnout"
+#~ msgstr "Y-Weiche"
+
+#~ 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."
+
+#~ 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."
+
+#~ msgid "You are not allowed to configure this track."
+#~ msgstr "Sie dürfen dieses Gleis nicht konfigurieren."
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "Sie dürfen an geschützten Stellen kein Gleis bauen."
+
+#~ 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."
+
+#~ 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..b2a54da
--- /dev/null
+++ b/advtrains/po/fr.po
@@ -0,0 +1,1024 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2025-06-10 21:40+0200\n"
+"PO-Revision-Date: 2025-03-25 15:06+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"
+
+#: atc.lua
+msgid "ATC Kick command warning: doors are closed."
+msgstr "Avertissement commande ATC Éjecter : portes closes."
+
+#: atc.lua
+msgid "ATC Kick command warning: train moving."
+msgstr "Avertissement commande ATC Éjecter : train en mouvement."
+
+#: atc.lua
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr ""
+"Attention : Commande ATC de renversement impossible car le train se déplace."
+
+#: atc.lua
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "Erreur d'analyse de commande ATC : Commande inconnue : @1"
+
+#: atc.lua
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "Erreur de syntaxe de commande ATC : instruction \"I\" incomplète : @1"
+
+#: atc.lua
+#, fuzzy
+msgid "ATC controller, Command: @1"
+msgstr ""
+"Controlleur ATC, mode @1\n"
+"Commande : @2"
+
+#: atc.lua
+msgid "Command"
+msgstr "Commande"
+
+#: atc.lua
+msgid "Digiline channel"
+msgstr "Canal Digiline"
+
+#: atc.lua wagons.lua
+msgid "Save"
+msgstr "Sauvegarder"
+
+#: atc.lua
+msgid "Unconfigured ATC controller"
+msgstr "Controlleur ATC, non-configuré"
+
+#: copytool.lua
+msgid "Back of train would end up off track, cancelling."
+msgstr "La fin du train serait hors voie : annulation."
+
+#: copytool.lua
+msgid "No such lua entity."
+msgstr "Pas de telle entité lua."
+
+#: copytool.lua
+msgid "No such train: @1."
+msgstr "Pas de tel train : @1."
+
+#: copytool.lua
+msgid "No such wagon: @1."
+msgstr "Pas de tel wagon : @1."
+
+#: copytool.lua
+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."
+
+#: copytool.lua
+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."
+
+#: copytool.lua
+msgid "The clipboard is empty."
+msgstr "Le presse-papier est vide."
+
+#: copytool.lua
+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."
+
+#: copytool.lua
+msgid "Train copied."
+msgstr "Train copié."
+
+#: copytool.lua
+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"
+
+#: copytool.lua
+msgid "You do not have the @1 privilege."
+msgstr "Vous ne possédez pas le privilège \"@1\"."
+
+#: couple.lua
+msgid "<No coupler>"
+msgstr "<Pas de coupleur>"
+
+#: couple.lua
+msgid "Buffer and Chain Coupler"
+msgstr "Attelage à tampon et vis"
+
+#: couple.lua
+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)."
+
+#: couple.lua
+msgid ""
+"Cannot couple @1 and @2 - train would have length @3 which is above the "
+"limit of @4"
+msgstr ""
+
+#: couple.lua
+msgid "Scharfenberg Coupler"
+msgstr "Attelage Scharfenberg"
+
+#: couple.lua
+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\"."
+
+#: craft_items.lua
+msgid "Boiler"
+msgstr "Chaudière à vapeur"
+
+#: craft_items.lua
+msgid "Chimney"
+msgstr "Cheminée"
+
+#: craft_items.lua
+msgid "Driver's cab"
+msgstr "Cabine de pilotage"
+
+#: craft_items.lua
+msgid "Wheel"
+msgstr "Roue"
+
+#: init.lua
+#, fuzzy
+msgid "Advtrains Status: no_action @1 slowdown @2 (log @3)"
+msgstr "État d'advtrains : aucune action"
+
+#: init.lua
+msgid "Advtrains is already running normally!"
+msgstr "Advtrains fonctionne déjà correctement !"
+
+#: init.lua
+msgid ""
+"Crash during advtrains main step - skipping the shutdown save operation to "
+"not save inconsistent data!"
+msgstr ""
+"Crash durant le pas principal d'advtrains - saut de l'opération de "
+"sauvegarde de terminaison pour éviter l'enregistrement de données "
+"corrompues !"
+
+#: init.lua
+msgid ""
+"Data is being saved. While saving, advtrains will remove the players from "
+"trains. Save files will be reloaded afterwards!"
+msgstr ""
+"Données en cours de sauvegarde. Durant cette phase, advtrains débarquera les "
+"joueurs des trains. Les fichiers de sauvegarde seront ultérieurement "
+"rechargés !"
+
+# Routage est il le bon terme ?
+#: init.lua
+msgid "Delete all train routes, force them to recalculate"
+msgstr "Suppression et recalcul de tous les routages"
+
+#: init.lua
+msgid ""
+"Detach all players, especially the offline ones, from all trains. Use only "
+"when no one serious is on a train."
+msgstr ""
+"Débarque tous les joueurs, en particulier ceux déconnectés, de tous les "
+"trains. À n'utiliser que quand aucun joueur sérieux n'a embarqué."
+
+#: init.lua
+msgid "Disable the advtrains globalstep temporarily"
+msgstr "Désactive temporairement le pas global d'advtrains"
+
+#: init.lua
+msgid "Disabled advtrains successfully"
+msgstr "Succès de la désactivation d'advtrains"
+
+#: init.lua
+msgid "Instructed to save() but load() was never called!"
+msgstr "Appel de save() requis sans appel préalable de load() !"
+
+#: init.lua
+msgid "Print advtrains status info"
+msgstr "Affiche les informations d'état d'advtrains"
+
+#: init.lua
+msgid "Re-enabling advtrains globalstep..."
+msgstr "Réacivation du pas global d'advtrains..."
+
+#: init.lua
+msgid "Reload successful!"
+msgstr "Succès du rechargement !"
+
+#: init.lua
+msgid "Removing unused wagon"
+msgstr "Suppression d'un wagon inutilisé"
+
+#: init.lua
+msgid "Restoring saved state in 1 second..."
+msgstr "Restauration du l'état sauvegardé dans une seconde..."
+
+#: init.lua
+msgid "Returns the position of the train with the given id"
+msgstr "Affiche la position du train identifié"
+
+#: init.lua
+#, fuzzy
+msgid "Saving failed: @1"
+msgstr "Échec de sauvegarde : "
+
+# Routage est il le bon terme ?
+#: init.lua
+msgid "Successfully invalidated train routes"
+msgstr "Succès d'invalidation des routages des trains"
+
+#: init.lua
+#, fuzzy
+msgid "Teleporting to train @1"
+msgstr "Téléportation au train "
+
+#: init.lua
+msgid "Teleports you to the position of the train with the given id"
+msgstr "Vous téléporte à la position du train identifié"
+
+#: init.lua
+msgid ""
+"The advtrains globalstep has been disabled. Trains are not moving, and no "
+"data is saved! Run '/at_disable_step no' to enable again!"
+msgstr ""
+"Le pas global d'advtrains est désactivé. Les trains sont immobiles et aucune "
+"donnée n'est sauvegardée. Exécutez '/at_disable_step no ' pour le réactiver !"
+
+#: init.lua
+#, fuzzy
+msgid "Train @1 does not exist or is invalid"
+msgstr " n'existe pas ou est invalide"
+
+#: init.lua
+#, fuzzy
+msgid ""
+"Train @1 had no wagons left because of some bug. It is being deleted. Wave "
+"it goodbye!"
+msgstr ""
+"n'a plus de wagon à cause d'un bug quelconque. Il est détruit. Faites lui "
+"coucou !"
+
+#: init.lua
+msgid "Train @1 is at @2"
+msgstr ""
+
+#: init.lua
+msgid "from wagon_save table."
+msgstr "de la table wagon_save."
+
+#: misc_nodes.lua
+msgid "@1 Platform (45 degree)"
+msgstr "Quai @1 (haut, 45°)"
+
+#: misc_nodes.lua
+msgid "@1 Platform (high)"
+msgstr "Quai @1 (haut)"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low)"
+msgstr "Quai @1 (bas)"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low, 45 degree)"
+msgstr "Quai @1 (bas, 45°)"
+
+#: protection.lua
+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"
+
+#: protection.lua
+msgid "Can place and dig tracks in unprotected areas"
+msgstr "Possibilité de poser ou retirer des voies dans les zones non protégées"
+
+#: protection.lua
+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"
+
+#: protection.lua
+msgid "Can place, remove and operate trains"
+msgstr "Possibilité de poser, retirer ou opérer les trains"
+
+#: protection.lua
+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é."
+
+#: protection.lua
+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\" (?)"
+
+#: protection.lua
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "Vous ne pouvez pas construire une voie à cet emplacement protégé."
+
+#: protection.lua
+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\"."
+
+#: protection.lua
+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)"
+
+#: signals.lua
+msgid "Andrew's Cross"
+msgstr "Croix de Saint André"
+
+#: signals.lua
+#, fuzzy
+msgid "Lampless Signal (deprecated!)"
+msgstr "Sémaphore"
+
+#: signals.lua
+msgid "Signal (deprecated!)"
+msgstr ""
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (left) (deprecated!)"
+msgstr "Signal mural (gauche)"
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (right) (deprecated!)"
+msgstr "Signal mural (droit)"
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (top) (deprecated!)"
+msgstr "Signal mural (plafond)"
+
+#: track_reg_helper.lua
+msgid "@1 Slope"
+msgstr "Pente @1"
+
+#: track_reg_helper.lua
+msgid "Can't place: Not enough slope items left (@1 required)"
+msgstr ""
+"Placement impossible : quantité insuffisante de voie pentue (@1 manquant)"
+
+#: track_reg_helper.lua
+msgid "Can't place: There's no slope of length @1"
+msgstr "Placement impossible : il n'y a pas de voie pentue de longueur @1"
+
+#: track_reg_helper.lua
+msgid "Can't place: no supporting node at upper end."
+msgstr "Placement impossible : pas de nœud d'appui à l'extrémité supérieure."
+
+#: track_reg_helper.lua
+msgid "Can't place: not pointing at node"
+msgstr "Placement impossible : ne pointe pas un nœud"
+
+#: track_reg_helper.lua
+msgid "Can't place: space occupied!"
+msgstr "Placement impossible : espace occupé !"
+
+#: track_reg_helper.lua tracks.lua
+msgid "This track can not be removed!"
+msgstr "Cette voie ne peut pas être enlevée !"
+
+#: trackplacer.lua
+msgid "This node can't be changed using the trackworker!"
+msgstr "Ce nœud ne peut être modifié avec l'outil \"Trackworker\" !"
+
+#: trackplacer.lua
+msgid "This node can't be rotated using the trackworker!"
+msgstr "Ce nœud ne peut être tourné avec l'outil \"Trackworker\" !"
+
+#: trackplacer.lua
+msgid "This track can not be rotated!"
+msgstr "Cette voie ne peut pas être tournée !"
+
+#: trackplacer.lua
+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"
+
+#: tracks.lua
+msgid "Position is occupied by a train."
+msgstr "Cet emplacement est occupé par un train."
+
+#: tracks.lua
+msgid "There's a Signal Influence Point here."
+msgstr "Il y a un \"Signal Influence Point\" ici."
+
+#: tracks.lua
+msgid "There's a Track Circuit Break here."
+msgstr "Il y a un \"Track Circuit Break\" ici."
+
+#: trainhud.lua
+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."
+
+#: wagonprop_tool.lua
+msgid "Insufficient privileges to use this!"
+msgstr "Privilèges insuffisants pour utiliser ceci !"
+
+#: wagonprop_tool.lua
+msgid "Wagon Properties Tool"
+msgstr "Outil de propriété du wagon"
+
+#: wagonprop_tool.lua
+msgid ""
+"Wagon Properties Tool\n"
+"Punch a wagon to view and edit the Wagon Properties"
+msgstr ""
+"Outil de propriété du wagon\n"
+"Frappez un wagon pour voir et modifier ses propriétés"
+
+#: wagons.lua
+msgid " units"
+msgstr " Unités"
+
+#: wagons.lua
+msgid "!!! Train off track !!!"
+msgstr "!!! Train hors voie !!!"
+
+#: wagons.lua
+msgid "(Doors closed)"
+msgstr "(Portes closes)"
+
+#: wagons.lua
+msgid "Allow these players to access your wagon:"
+msgstr "Autoriser ces joueurs à embarquer :"
+
+#: wagons.lua
+msgid "Clear 'Disable ARS' flag"
+msgstr "Effacer le drapeau \"Désactiver l'ARS\""
+
+#: wagons.lua
+msgid "Current FC: "
+msgstr "Code de fret courant: "
+
+#: wagons.lua
+msgid ""
+"Destroying wagon with inventory, but inventory is not found? Shouldn't "
+"happen!"
+msgstr "Desctruction d'un wagon avec inventaire introuvable ? Anomalie !"
+
+#: wagons.lua
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr "Portes closes : (Essayez la \"sneak key\"!\")"
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+msgid "Freight Code:"
+msgstr "Code de frêt :"
+
+#: wagons.lua
+msgid "Get off"
+msgstr "Débarquer"
+
+#: wagons.lua
+msgid "Get off (forced)"
+msgstr "Débarquer (de force)"
+
+#: wagons.lua
+msgid "Line"
+msgstr "Ligne"
+
+#: wagons.lua
+msgid "Liquid: "
+msgstr "Liquide : "
+
+#: wagons.lua
+msgid "Liquid: empty"
+msgstr "Liquide : vide"
+
+#: wagons.lua
+msgid "Missing train_operator privilege"
+msgstr "Privilège \"train_operator\" manquant"
+
+#: wagons.lua
+msgid "Next FC:"
+msgstr "Code de fret suivant :"
+
+#: wagons.lua
+msgid "Not a valid wagon id."
+msgstr "Identificateur de wagon invalide."
+
+#: wagons.lua
+msgid "Not allowed to do this."
+msgstr "Vous n'êtes pas autorisé effectuer ceci."
+
+#: wagons.lua
+msgid "Onboard Computer"
+msgstr "Ordinateur embarqué"
+
+#: wagons.lua
+msgid "Please specify a player name to transfer ownership to."
+msgstr ""
+"Spécifiez le nom du joueur à qui la propriété doit être transférée, SVP."
+
+#: wagons.lua
+msgid "Prev FC"
+msgstr "Code de fret précédent"
+
+#: wagons.lua
+msgid "Remote Routesetting"
+msgstr "Routage à distance"
+
+#: wagons.lua
+msgid "Routingcode"
+msgstr "Code de routage"
+
+#: wagons.lua
+msgid "Save wagon properties"
+msgstr "Sauvegarder les propriétés du wagon"
+
+#: wagons.lua
+msgid "Select seat:"
+msgstr "Choisir le siège :"
+
+#: wagons.lua
+msgid "Show Inventory"
+msgstr "Montrer le stock"
+
+#: wagons.lua
+msgid "Text displayed inside train"
+msgstr "Texte affiché à l'intérieur du train"
+
+#: wagons.lua
+msgid "Text displayed outside on train"
+msgstr "Texte affiché à l'extérieur du train"
+
+#: wagons.lua
+msgid "That player does not exist!"
+msgstr "Ce joueur n'existe pas !"
+
+#: wagons.lua
+msgid "That wagon does not exist!"
+msgstr "Ce wagon n'a pas de siège !"
+
+#: wagons.lua
+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 !"
+
+#: wagons.lua
+msgid "The wagon's inventory is not empty."
+msgstr "Le stock de ce wagon n'est pas vide."
+
+#: wagons.lua
+msgid "This Wagon ID"
+msgstr "Identificateur du wagon"
+
+#: wagons.lua
+msgid "This wagon has no seats."
+msgstr "Ce wagon n'a pas de siège."
+
+#: wagons.lua
+msgid "This wagon is full."
+msgstr "Ce wagon est plein."
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+msgid "Train ID"
+msgstr "Identificateur du train"
+
+#: wagons.lua
+msgid "Train overview / coupling control is only shown when the train stands."
+msgstr ""
+"Aperçu du train / commande d'accouplement montré uniquement à l'arrêt du "
+"train."
+
+#: wagons.lua
+msgid "Train overview /coupling control:"
+msgstr "Aperçu du train / commande d'accouplement :"
+
+#: wagons.lua
+msgid "Uninitialized, removing"
+msgstr "Non initialisé, retiré"
+
+#: wagons.lua
+msgid "Wagon @1 ownership changed from @2 to @3"
+msgstr "La propriété du wagon @1 a été transférée de @2 à @3"
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+#, fuzzy
+msgid "Wagon placeholder"
+msgstr ", dans un espace réservé"
+
+#: wagons.lua
+msgid "Wagon properties"
+msgstr "Propriétés du wagon"
+
+#: wagons.lua
+msgid "Wagon road number:"
+msgstr "Immatriculation du wagon :"
+
+#: wagons.lua
+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."
+
+#: wagons.lua
+msgid "You are not allowed to access the driver stand."
+msgstr "Accès interdit au poste de pilotage."
+
+#: wagons.lua
+msgid "You can't get on this wagon."
+msgstr "Montée impossible dans ce wagon."
+
+#: wagons.lua
+msgid "You don't have the train_operator privilege."
+msgstr "Vous ne possédez pas le privilège \"train_operator\"."
+
+#: wagons.lua
+msgid "You have been given ownership of wagon @1"
+msgstr "La propriété du wagon @1 vous a été transférée"
+
+#~ msgid " is at "
+#~ msgstr " est à la position "
+
+#~ msgid " wagon:destroy(): data is not set!"
+#~ msgstr " Appel de wagon:destroy() : données non définies !"
+
+#~ msgid "(log"
+#~ msgstr "(log"
+
+#~ msgid "3-way turnout"
+#~ msgstr "Embranchement triple"
+
+#~ msgid "90+Angle Diamond Crossing Track"
+#~ msgstr "Croisement perpendiculo-diagonal"
+
+#~ msgid "ATC controller"
+#~ msgstr "Controlleur ATC"
+
+#~ 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 "Big Industrial Train Engine"
+#~ msgstr "Grosse locomotive industrielle"
+
+#~ msgid "Box Wagon"
+#~ msgstr "Wagon de frêt"
+
+#~ msgid "Bumper"
+#~ msgstr "Heurtoir"
+
+#~ 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"
+
+#~ 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 "Caution"
+#~ msgstr "Attention"
+
+#~ msgid "Clear (proceed)"
+#~ msgstr "Autorisation (procédez)"
+
+#~ msgid "Clear Local Environment"
+#~ msgstr "Effacer l'environnement LuaATC"
+
+#~ msgid "Closed"
+#~ msgstr "Fermé"
+
+#~ msgid "Code"
+#~ msgstr "Code"
+
+#~ msgid "Command (on)"
+#~ msgstr "Commande (marche)"
+
+#~ msgid "Danger (halt)"
+#~ msgstr "Danger (stop)"
+
+#~ msgid "Default Seat"
+#~ msgstr "Siège par défaut"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "Siège par défaut (poste de pilotage)"
+
+#~ msgid "Dep. Speed"
+#~ msgstr "Vit. de départ"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "Voie déconseillée"
+
+#~ msgid "Detailed Steam Engine"
+#~ msgstr "Locomotive à vapeur complexe"
+
+#~ msgid "Detector Rail"
+#~ msgstr "Voie détectrice"
+
+#~ msgid "Diagonal Diamond Crossing Track"
+#~ msgstr "Croisement diagonal"
+
+#~ msgid "Door Delay"
+#~ msgstr "Durée d'ouverture des portes"
+
+#~ msgid "Door Side"
+#~ msgstr "Ouv. des portes coté"
+
+#~ msgid "Driver Stand"
+#~ msgstr "Poste de pilotage"
+
+#~ msgid "Driver Stand (left)"
+#~ msgstr "Poste de pilotage (gauche)"
+
+#~ msgid "Driver Stand (right)"
+#~ msgstr "Poste de pilotage (droit)"
+
+#~ msgid "Driver stand"
+#~ msgstr "Poste de pilotage"
+
+#~ msgid "Industrial Train Engine"
+#~ msgstr "Locomotive industrielle"
+
+#~ msgid "Industrial tank wagon"
+#~ msgstr "Wagon-citerne industriel"
+
+#~ msgid "Industrial wood wagon"
+#~ msgstr "Wagon grumier industriel"
+
+#~ msgid "Japanese Train Engine"
+#~ msgstr "Motrice Japonaise"
+
+#~ msgid "Japanese Train Inter-Wagon Connection"
+#~ msgstr "Passage inter-voiture de train Japonais"
+
+#~ msgid "Japanese Train Wagon"
+#~ msgstr "Voiture Japonaise"
+
+#~ msgid "Japanese signal pole"
+#~ msgstr "Voiture Japonaise"
+
+#~ msgid "Kick out passengers"
+#~ msgstr "Éjecter les passagers"
+
+#~ msgid "Left"
+#~ msgstr "Gauche"
+
+#~ msgid "Left,Right,Closed;"
+#~ msgstr "Gauche,Droit,Fermé;"
+
+#~ msgid "Loading Track"
+#~ msgstr "Voie de Chargement"
+
+#~ msgid "Lock couples"
+#~ msgstr "Verrouiller l'accouplement"
+
+#~ msgid "LuaATC Environment"
+#~ msgstr "Environnement LuaATC"
+
+#~ msgid "LuaATC Mesecon Controller"
+#~ msgstr "Commande Mesecon de LuaATC"
+
+#~ msgid "LuaATC Operation Panel"
+#~ msgstr "Panneau de commande de LuaATC"
+
+#~ msgid "LuaATC component assigned to an invalid environment"
+#~ msgstr "Composant LuaATC assigné à un environnement invalide"
+
+#~ msgid "LuaATC component assigned to environment '@1'"
+#~ msgstr "Composant LuaATC assigné à l'environnement '@1'"
+
+#~ msgid "LuaATC component with error: @1"
+#~ msgstr "Erreur @1 du composant LuaATC"
+
+#~ msgid "Munich U-Bahn Distant Signal ("
+#~ msgstr "Signal distant métro de Munich ("
+
+#~ msgid "Munich U-Bahn Main Signal ("
+#~ msgstr "Signal principal métro de Munich ("
+
+#~ msgid "Next Stop:\n"
+#~ msgstr "Prochain arrêt :\n"
+
+#~ msgid "No callback to handle schedule"
+#~ msgstr "Absence de fonction de gestion de planning"
+
+#~ msgid "Passenger Wagon"
+#~ msgstr "Voiture passager"
+
+#~ msgid "Passenger area"
+#~ msgstr "Voiture Passager"
+
+#~ 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."
+
+#~ msgid "Perpendicular Diamond Crossing Track"
+#~ msgstr "Croisement perpendiculaire"
+
+#~ msgid "Point Speed Restriction Track"
+#~ msgstr "Voie de point de limitation de vitesse"
+
+#~ msgid "Point speed restriction: @1"
+#~ msgstr "Point de limitation de vitesse : @1"
+
+#~ msgid "Reduced speed"
+#~ msgstr "Vitesse réduite"
+
+#~ msgid "Restricted speed"
+#~ msgstr "Vitesse limitée"
+
+#~ msgid "Reverse train"
+#~ msgstr "Inversion du sens de marche"
+
+#~ msgid "Right"
+#~ msgstr "Droit"
+
+#~ msgid "Route state changed."
+#~ msgstr "Changement d'état de l'itinéraire."
+
+#~ msgid "Set name of component (empty to clear)"
+#~ msgstr "Nommer le composant (chaîne vide pour effacer)"
+
+#~ msgid "Set point speed restriction:"
+#~ msgstr "Placez un point de limitation de vitesse :"
+
+#~ msgid "Signal"
+#~ msgstr "Signal"
+
+#~ msgid "Speed:"
+#~ msgstr "Vitesse : "
+
+#~ msgid "Station Code"
+#~ msgstr "Code de Station"
+
+#~ msgid "Station Name"
+#~ msgstr "Nom de Station"
+
+#~ msgid "Station code \"@1\" already exists and is owned by @2."
+#~ msgstr "Le code de station \"@1\" existe et est possédé par @2."
+
+#~ msgid "Station/Stop Track"
+#~ msgstr "Voie d'arrêt en station"
+
+#~ msgid "Steam Engine"
+#~ msgstr "Locomotive à vapeur"
+
+#~ msgid "Stop Time"
+#~ msgstr "Durée d'arrêt"
+
+#~ msgid "Subway Passenger Wagon"
+#~ msgstr "Voiture de Métropolitain"
+
+#~ 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 "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."
+
+#~ msgid "This track can not be changed."
+#~ msgstr "Cette voie ne peut pas être modifiée."
+
+#~ msgid "Track"
+#~ msgstr "Voie"
+
+#~ msgid "Train"
+#~ msgstr "Identificateur du train"
+
+#~ msgid "Train "
+#~ msgstr "Identificateur du train "
+
+#~ msgid "Trains stopping here (ARS rules)"
+#~ msgstr "Trains marquant l'arrêt (règles ARS)"
+
+#~ msgid "Unable to load wagon type"
+#~ msgstr "Impossible de charger le type du wagon"
+
+#~ msgid "Unconfigured LuaATC component"
+#~ msgstr "Composant LuaATC non configuré"
+
+#~ msgid "Uninitialized init="
+#~ msgstr "Variable init non initialisée"
+
+#~ msgid "Unknown Station"
+#~ msgstr "Gare inconnue"
+
+#~ msgid "Unloading Track"
+#~ msgstr "Voie de Déchargement"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr ""
+#~ "Utilisez \"Marcher lentement (Sneak)\" et Clic-Droit pour franchir les "
+#~ "portes closes !"
+
+#~ msgid "Wait for signal to clear"
+#~ msgstr "En attente de signal d'autorisation"
+
+#~ msgid "Y-turnout"
+#~ msgstr "Embranchement en Y"
+
+#~ 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."
+
+#~ 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."
+
+#~ msgid "You are not allowed to configure this track."
+#~ msgstr "Vous n'êtes pas autorisé à configurer cette voie."
+
+#, 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 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."
+
+#~ 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."
+
+#~ msgid "slowdown"
+#~ msgstr "ralentissement"
diff --git a/advtrains/po/update-translations.sh b/advtrains/po/update-translations.sh
new file mode 100755
index 0000000..3228037
--- /dev/null
+++ b/advtrains/po/update-translations.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+MODNAME="advtrains"
+MSGID_BUGS_ADDR='advtrains-discuss@lists.sr.ht'
+
+PODIR=`dirname "$0"`
+ATDIR="$PODIR/.."
+POTFILE="$PODIR/$MODNAME.pot"
+
+xgettext \
+ -D "$ATDIR" \
+ -d "$MODNAME" \
+ -o "$POTFILE" \
+ -p . \
+ -L lua \
+ --add-location=file \
+ --from-code=UTF-8 \
+ --sort-by-file \
+ --keyword='S' \
+ --package-name="$MODNAME" \
+ --msgid-bugs-address="$MSGID_BUGS_ADDR" \
+ `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..4fa4502
--- /dev/null
+++ b/advtrains/po/zh_CN.po
@@ -0,0 +1,863 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2025-06-10 21:40+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"
+
+#: atc.lua
+msgid "ATC Kick command warning: doors are closed."
+msgstr "ATC 警告:车门已关闭,无法踢出乘客。"
+
+#: atc.lua
+msgid "ATC Kick command warning: train moving."
+msgstr "ATC 警告:火车正在移动,无法踢出乘客。"
+
+#: atc.lua
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr "ATC 警告:火车正在移动,无法改变行车方向。"
+
+#: atc.lua
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "ATC 语法错误:未知命令:@1"
+
+#: atc.lua
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "ATC 语法错误:“I”命令不完整:@1"
+
+#: atc.lua
+#, fuzzy
+msgid "ATC controller, Command: @1"
+msgstr ""
+"ATC 控制器\n"
+"模式:@1\n"
+"命令:@2"
+
+#: atc.lua
+msgid "Command"
+msgstr "命令"
+
+#: atc.lua
+msgid "Digiline channel"
+msgstr "Digiline 频道"
+
+#: atc.lua wagons.lua
+msgid "Save"
+msgstr "保存"
+
+#: atc.lua
+msgid "Unconfigured ATC controller"
+msgstr "ATC 控制器 (未配置)"
+
+#: copytool.lua
+msgid "Back of train would end up off track, cancelling."
+msgstr "火车后部不在轨道上。"
+
+#: copytool.lua
+msgid "No such lua entity."
+msgstr "您没有指向一个可以用火车复制工具复制的物体。"
+
+#: copytool.lua
+msgid "No such train: @1."
+msgstr "ID 为“@1”的列车不存在。"
+
+#: copytool.lua
+msgid "No such wagon: @1."
+msgstr "ID 为“@1”的车厢不存在。"
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr "无法复制:剪贴板无法访问元数据。"
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr "无法粘贴:剪贴板无法访问元数据。"
+
+#: copytool.lua
+msgid "The clipboard is empty."
+msgstr "剪贴板是空的。"
+
+#: copytool.lua
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr "轨道太短。"
+
+#: copytool.lua
+msgid "Train copied."
+msgstr "已复制列车。"
+
+#: copytool.lua
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+"火车复制工具\n"
+"\n"
+"左键单击:复制\n"
+"右键单击:粘帖"
+
+#: copytool.lua
+msgid "You do not have the @1 privilege."
+msgstr "您没有“@1”权限。"
+
+#: couple.lua
+msgid "<No coupler>"
+msgstr "<没有车钩>"
+
+#: couple.lua
+msgid "Buffer and Chain Coupler"
+msgstr "链式车钩"
+
+#: couple.lua
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr "您无法连接这两节车厢:这两节车厢使用不同的车钩 (@1和@2)。"
+
+#: couple.lua
+msgid ""
+"Cannot couple @1 and @2 - train would have length @3 which is above the "
+"limit of @4"
+msgstr ""
+
+#: couple.lua
+msgid "Scharfenberg Coupler"
+msgstr "Scharfenberg 式车钩"
+
+#: couple.lua
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr "您没有“train_operator”权限,不能连接这两节车厢。"
+
+#: craft_items.lua
+msgid "Boiler"
+msgstr "锅炉"
+
+#: craft_items.lua
+msgid "Chimney"
+msgstr "烟囱"
+
+#: craft_items.lua
+msgid "Driver's cab"
+msgstr "驾驶室"
+
+#: craft_items.lua
+msgid "Wheel"
+msgstr "车轮"
+
+#: init.lua
+msgid "Advtrains Status: no_action @1 slowdown @2 (log @3)"
+msgstr ""
+
+#: init.lua
+msgid "Advtrains is already running normally!"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Crash during advtrains main step - skipping the shutdown save operation to "
+"not save inconsistent data!"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Data is being saved. While saving, advtrains will remove the players from "
+"trains. Save files will be reloaded afterwards!"
+msgstr ""
+
+#: init.lua
+msgid "Delete all train routes, force them to recalculate"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Detach all players, especially the offline ones, from all trains. Use only "
+"when no one serious is on a train."
+msgstr ""
+
+#: init.lua
+msgid "Disable the advtrains globalstep temporarily"
+msgstr ""
+
+#: init.lua
+msgid "Disabled advtrains successfully"
+msgstr ""
+
+#: init.lua
+msgid "Instructed to save() but load() was never called!"
+msgstr ""
+
+#: init.lua
+msgid "Print advtrains status info"
+msgstr ""
+
+#: init.lua
+msgid "Re-enabling advtrains globalstep..."
+msgstr ""
+
+#: init.lua
+msgid "Reload successful!"
+msgstr ""
+
+#: init.lua
+msgid "Removing unused wagon"
+msgstr ""
+
+#: init.lua
+msgid "Restoring saved state in 1 second..."
+msgstr ""
+
+#: init.lua
+msgid "Returns the position of the train with the given id"
+msgstr ""
+
+#: init.lua
+msgid "Saving failed: @1"
+msgstr ""
+
+#: init.lua
+msgid "Successfully invalidated train routes"
+msgstr ""
+
+#: init.lua
+msgid "Teleporting to train @1"
+msgstr ""
+
+#: init.lua
+msgid "Teleports you to the position of the train with the given id"
+msgstr ""
+
+#: init.lua
+msgid ""
+"The advtrains globalstep has been disabled. Trains are not moving, and no "
+"data is saved! Run '/at_disable_step no' to enable again!"
+msgstr ""
+
+#: init.lua
+msgid "Train @1 does not exist or is invalid"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Train @1 had no wagons left because of some bug. It is being deleted. Wave "
+"it goodbye!"
+msgstr ""
+
+#: init.lua
+msgid "Train @1 is at @2"
+msgstr ""
+
+#: init.lua
+msgid "from wagon_save table."
+msgstr ""
+
+#: misc_nodes.lua
+msgid "@1 Platform (45 degree)"
+msgstr "较高的@1站台 (45°)"
+
+#: misc_nodes.lua
+msgid "@1 Platform (high)"
+msgstr "较高的@1站台"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low)"
+msgstr "较低的@1站台"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low, 45 degree)"
+msgstr "较低的@1站台 (45°)"
+
+#: protection.lua
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: protection.lua
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: protection.lua
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: protection.lua
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: protection.lua
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr "这里已被保护,您不能在这里的铁路附近建任何东西。"
+
+#: protection.lua
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr "您没有“train_operator”权限,不能在铁路附近建任何东西。"
+
+#: protection.lua
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "这里已被保护,您不能在这里建造铁路。"
+
+#: protection.lua
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr "您没有“train_operator”权限,不能在这里建造铁路。"
+
+#: protection.lua
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr "您没有“railway_operator”权限,不能控制铁路设施。"
+
+#: signals.lua
+msgid "Andrew's Cross"
+msgstr "铁路道口信号灯"
+
+#: signals.lua
+#, fuzzy
+msgid "Lampless Signal (deprecated!)"
+msgstr "臂板信号机"
+
+#: signals.lua
+msgid "Signal (deprecated!)"
+msgstr ""
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (left) (deprecated!)"
+msgstr "壁挂式信号灯 (左侧)"
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (right) (deprecated!)"
+msgstr "壁挂式信号灯 (右侧)"
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (top) (deprecated!)"
+msgstr "悬挂式信号灯"
+
+#: track_reg_helper.lua
+msgid "@1 Slope"
+msgstr "@1斜坡"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: Not enough slope items left (@1 required)"
+msgstr "无法放置斜坡:您没有足够的铁路斜坡放置工具 (您总共需要@1个)"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: There's no slope of length @1"
+msgstr "无法放置斜坡:advtrains 不支持长度为@1米的斜坡。"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: no supporting node at upper end."
+msgstr "无法放置斜坡:较高端没有支撑方块。"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: not pointing at node"
+msgstr "无法放置斜坡:您没有选择任何方块。"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: space occupied!"
+msgstr "无法放置斜坡:此区域已被占用。"
+
+#: track_reg_helper.lua tracks.lua
+#, fuzzy
+msgid "This track can not be removed!"
+msgstr "您不能移除这段轨道。"
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This node can't be changed using the trackworker!"
+msgstr "您不能使用铁路调整工具调整这个方块。"
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This node can't be rotated using the trackworker!"
+msgstr "您不能使用铁路调整工具旋转这个方块。"
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This track can not be rotated!"
+msgstr "您不能旋转这段轨道。"
+
+#: trackplacer.lua
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+"铁路调整工具\n"
+"\n"
+"左键单击:切换轨道类型\n"
+"右键单击:旋转方块"
+
+#: tracks.lua
+msgid "Position is occupied by a train."
+msgstr ""
+
+#: tracks.lua
+msgid "There's a Signal Influence Point here."
+msgstr ""
+
+#: tracks.lua
+msgid "There's a Track Circuit Break here."
+msgstr ""
+
+#: trainhud.lua
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: wagonprop_tool.lua
+msgid "Insufficient privileges to use this!"
+msgstr ""
+
+#: wagonprop_tool.lua
+#, fuzzy
+msgid "Wagon Properties Tool"
+msgstr "车厢属性"
+
+#: wagonprop_tool.lua
+msgid ""
+"Wagon Properties Tool\n"
+"Punch a wagon to view and edit the Wagon Properties"
+msgstr ""
+
+#: wagons.lua
+msgid " units"
+msgstr ""
+
+#: wagons.lua
+msgid "!!! Train off track !!!"
+msgstr ""
+
+#: wagons.lua
+msgid "(Doors closed)"
+msgstr "(车门已关闭)"
+
+#: wagons.lua
+msgid "Allow these players to access your wagon:"
+msgstr ""
+
+#: wagons.lua
+msgid "Clear 'Disable ARS' flag"
+msgstr ""
+
+#: wagons.lua
+msgid "Current FC: "
+msgstr ""
+
+#: wagons.lua
+msgid ""
+"Destroying wagon with inventory, but inventory is not found? Shouldn't "
+"happen!"
+msgstr ""
+
+#: wagons.lua
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr ""
+
+#: wagons.lua
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr "车门已关闭,请使用潜行+右键单击下车。"
+
+#: wagons.lua
+msgid "Freight Code:"
+msgstr ""
+
+#: wagons.lua
+msgid "Get off"
+msgstr "下车"
+
+#: wagons.lua
+msgid "Get off (forced)"
+msgstr "强制下车"
+
+#: wagons.lua
+msgid "Line"
+msgstr "火车线路"
+
+#: wagons.lua
+msgid "Liquid: "
+msgstr ""
+
+#: wagons.lua
+msgid "Liquid: empty"
+msgstr ""
+
+#: wagons.lua
+msgid "Missing train_operator privilege"
+msgstr ""
+
+#: wagons.lua
+msgid "Next FC:"
+msgstr ""
+
+#: wagons.lua
+msgid "Not a valid wagon id."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "Not allowed to do this."
+msgstr "您不能调整这段轨道。"
+
+#: wagons.lua
+msgid "Onboard Computer"
+msgstr ""
+
+#: wagons.lua
+msgid "Please specify a player name to transfer ownership to."
+msgstr ""
+
+#: wagons.lua
+msgid "Prev FC"
+msgstr ""
+
+#: wagons.lua
+msgid "Remote Routesetting"
+msgstr ""
+
+#: wagons.lua
+msgid "Routingcode"
+msgstr "路由码"
+
+#: wagons.lua
+msgid "Save wagon properties"
+msgstr "保存车厢属性"
+
+#: wagons.lua
+msgid "Select seat:"
+msgstr "请选择座位:"
+
+#: wagons.lua
+msgid "Show Inventory"
+msgstr "显示物品栏"
+
+#: wagons.lua
+msgid "Text displayed inside train"
+msgstr "车厢内部显示"
+
+#: wagons.lua
+msgid "Text displayed outside on train"
+msgstr "车厢外部显示"
+
+#: wagons.lua
+msgid "That player does not exist!"
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "That wagon does not exist!"
+msgstr "这节车厢没有座位。"
+
+#: wagons.lua
+#, fuzzy
+msgid "The track you are trying to place the wagon on is not long enough!"
+msgstr "轨道太短。"
+
+#: wagons.lua
+msgid "The wagon's inventory is not empty."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "This Wagon ID"
+msgstr "车厢已满。"
+
+#: wagons.lua
+msgid "This wagon has no seats."
+msgstr "这节车厢没有座位。"
+
+#: wagons.lua
+msgid "This wagon is full."
+msgstr "车厢已满。"
+
+#: wagons.lua
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "这是 @1 的车厢,您不能摧毁它。"
+
+#: wagons.lua
+msgid "Train ID"
+msgstr ""
+
+#: wagons.lua
+msgid "Train overview / coupling control is only shown when the train stands."
+msgstr ""
+
+#: wagons.lua
+msgid "Train overview /coupling control:"
+msgstr ""
+
+#: wagons.lua
+msgid "Uninitialized, removing"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon @1 ownership changed from @2 to @3"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "Wagon placeholder"
+msgstr "车厢属性"
+
+#: wagons.lua
+msgid "Wagon properties"
+msgstr "车厢属性"
+
+#: wagons.lua
+msgid "Wagon road number:"
+msgstr ""
+
+#: wagons.lua
+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 ""
+"警告:如果您摧毁此车厢,您只能拿到一些钢方块。如果您确定要摧毁这节车厢,请按"
+"潜行键并左键单击此车厢。"
+
+#: wagons.lua
+msgid "You are not allowed to access the driver stand."
+msgstr ""
+
+#: wagons.lua
+msgid "You can't get on this wagon."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "You don't have the train_operator privilege."
+msgstr "您没有“@1”权限。"
+
+#: wagons.lua
+msgid "You have been given ownership of wagon @1"
+msgstr ""
+
+#~ msgid "3-way turnout"
+#~ msgstr "三开道岔"
+
+#~ msgid "90+Angle Diamond Crossing Track"
+#~ msgstr "交叉轨道 (其中一条轨道与坐标轴平行)"
+
+#~ msgid "ATC controller"
+#~ msgstr "ATC 控制器"
+
+#~ msgid ""
+#~ "ATC controller, mode @1\n"
+#~ "Channel: @2"
+#~ msgstr ""
+#~ "ATC 控制器\n"
+#~ "模式:@1\n"
+#~ "频道:@2"
+
+#~ msgid "Access to @1"
+#~ msgstr "可前往@1"
+
+#~ msgid "Big Industrial Train Engine"
+#~ msgstr "大型工业用火车头"
+
+#~ msgid "Box Wagon"
+#~ msgstr "货运车厢"
+
+#~ msgid "Bumper"
+#~ msgstr "保险杠"
+
+#~ msgid "Can't get on: wagon full or doors closed!"
+#~ msgstr "无法上车:车门已关闭或车厢已满。"
+
+#~ msgid "Can't place: protected position!"
+#~ msgstr "无法放置:此区域已被保护。"
+
+#~ msgid "Command (on)"
+#~ msgstr "命令 (激活时)"
+
+#~ msgid "Default Seat"
+#~ msgstr "默认座位"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "默认座位 (司机座位)"
+
+#~ msgid "Dep. Speed"
+#~ msgstr "出发速度"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "请不要使用"
+
+#~ msgid "Detailed Steam Engine"
+#~ msgstr "精细的蒸汽机车"
+
+#~ msgid "Detector Rail"
+#~ msgstr "探测轨道"
+
+#~ msgid "Diagonal Diamond Crossing Track"
+#~ msgstr "交叉轨道"
+
+#~ msgid "Door Delay"
+#~ msgstr "车门关闭时间"
+
+#, fuzzy
+#~ msgid "Driver Stand"
+#~ msgstr "司机座位"
+
+#~ msgid "Driver Stand (left)"
+#~ msgstr "左侧司机座位"
+
+#~ msgid "Driver Stand (right)"
+#~ msgstr "右侧司机座位"
+
+#~ msgid "Driver stand"
+#~ msgstr "司机座位"
+
+#~ msgid "Industrial Train Engine"
+#~ msgstr "工业用火车头"
+
+#~ msgid "Industrial tank wagon"
+#~ msgstr "液体运输车厢"
+
+#~ msgid "Industrial wood wagon"
+#~ msgstr "木材运输车厢"
+
+#~ msgid "Japanese Train Engine"
+#~ msgstr "高速列车车头"
+
+#~ msgid "Japanese Train Inter-Wagon Connection"
+#~ msgstr "日本火车车钩"
+
+#~ msgid "Japanese Train Wagon"
+#~ msgstr "高速列车车厢"
+
+#, fuzzy
+#~ msgid "Japanese signal pole"
+#~ msgstr "高速列车车厢"
+
+#~ msgid "Kick out passengers"
+#~ msgstr "踢出乘客"
+
+#~ msgid "Loading Track"
+#~ msgstr "装货轨道"
+
+#~ msgid "Lock couples"
+#~ msgstr "锁定连接处"
+
+#~ msgid "Passenger Wagon"
+#~ msgstr "客车"
+
+#, fuzzy
+#~ msgid "Passenger area"
+#~ msgstr "客车"
+
+#~ msgid ""
+#~ "Passive Component Naming Tool\n"
+#~ "\n"
+#~ "Right-click to name a passive component."
+#~ msgstr ""
+#~ "被动元件命名工具\n"
+#~ "\n"
+#~ "右键单击命名所选元件。"
+
+#~ msgid "Perpendicular Diamond Crossing Track"
+#~ msgstr "垂直交叉轨道"
+
+#~ msgid "Reverse train"
+#~ msgstr "改变行车方向"
+
+#~ msgid "Signal"
+#~ msgstr "信号灯"
+
+#~ msgid "Speed:"
+#~ msgstr "速度"
+
+#~ msgid "Station Code"
+#~ msgstr "车站代码"
+
+#~ msgid "Station Name"
+#~ msgstr "车站名称"
+
+#~ msgid "Station/Stop Track"
+#~ msgstr "车站轨道"
+
+#~ msgid "Steam Engine"
+#~ msgstr "蒸汽机车"
+
+#~ msgid "Stop Time"
+#~ msgstr "停站时间"
+
+#~ msgid "Subway Passenger Wagon"
+#~ msgstr "地铁车厢"
+
+#~ msgid "Target:"
+#~ msgstr "目标速度"
+
+#, fuzzy
+#~ msgid "This node can't be rotated using the trackworker,"
+#~ msgstr "您不能使用铁路调整工具旋转这个方块。"
+
+#~ msgid "This position is protected!"
+#~ msgstr "这里已被保护。"
+
+#~ msgid "This track can not be changed."
+#~ msgstr "您不能调整这段轨道。"
+
+#~ msgid "Track"
+#~ msgstr "轨道"
+
+#, fuzzy
+#~ msgid "Train "
+#~ msgstr "已复制列车。"
+
+#~ msgid "Unconfigured LuaATC component"
+#~ msgstr "LuaATC 部件 (未配置)"
+
+#~ msgid "Unloading Track"
+#~ msgstr "卸货轨道"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr "请使用潜行+右键上车。"
+
+#~ msgid "Y-turnout"
+#~ msgstr "对称道岔"
+
+#~ msgid ""
+#~ "You are not allowed to configure this LuaATC component without the @1 "
+#~ "privilege."
+#~ msgstr "您没有“@1”权限,不能配置这个 LuaATC 部件。"
+
+#~ msgid ""
+#~ "You are not allowed to configure this track without the @1 privilege."
+#~ msgstr "您没有“@1”权限,不能调整这段轨道。"
+
+#~ msgid "You are not allowed to configure this track."
+#~ msgstr "您不能调整这段轨道。"
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "这里已被保护,您不能在这里建造铁路。"
+
+#~ msgid ""
+#~ "You are not allowed to name LuaATC passive components without the @1 "
+#~ "privilege."
+#~ msgstr "您没有“@1”权限,不能命名被动元件。"
+
+#~ 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..b46de64
--- /dev/null
+++ b/advtrains/po/zh_TW.po
@@ -0,0 +1,863 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: advtrains\n"
+"Report-Msgid-Bugs-To: advtrains-discuss@lists.sr.ht\n"
+"POT-Creation-Date: 2025-06-10 21:40+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"
+
+#: atc.lua
+msgid "ATC Kick command warning: doors are closed."
+msgstr "ATC 警告:車門已關閉,無法踢出乘客。"
+
+#: atc.lua
+msgid "ATC Kick command warning: train moving."
+msgstr "ATC 警告:火車正在移動,無法踢出乘客。"
+
+#: atc.lua
+msgid "ATC Reverse command warning: didn't reverse train, train moving."
+msgstr "ATC 警告:火車正在移動,無法改變行車方向。"
+
+#: atc.lua
+msgid "ATC command parse error: Unknown command: @1"
+msgstr "ATC 語法錯誤:未知命令:@1"
+
+#: atc.lua
+msgid "ATC command syntax error: I statement not closed: @1"
+msgstr "ATC 語法錯誤:「I」命令不完整:@1"
+
+#: atc.lua
+#, fuzzy
+msgid "ATC controller, Command: @1"
+msgstr ""
+"ATC 控制器\n"
+"模式:@1\n"
+"命令:@2"
+
+#: atc.lua
+msgid "Command"
+msgstr "命令"
+
+#: atc.lua
+msgid "Digiline channel"
+msgstr "Digiline 頻道"
+
+#: atc.lua wagons.lua
+msgid "Save"
+msgstr "儲存"
+
+#: atc.lua
+msgid "Unconfigured ATC controller"
+msgstr "ATC 控制器 (未配置)"
+
+#: copytool.lua
+msgid "Back of train would end up off track, cancelling."
+msgstr "火車後部不在軌道上。"
+
+#: copytool.lua
+msgid "No such lua entity."
+msgstr "您沒有指向一個可以用火車複製工具複製的物體。"
+
+#: copytool.lua
+msgid "No such train: @1."
+msgstr "ID 為「@1」的列車不存在。"
+
+#: copytool.lua
+msgid "No such wagon: @1."
+msgstr "ID 為「@1」的車廂不存在。"
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Copy failed."
+msgstr "無法複製:剪貼簿無法訪問元資料。"
+
+#: copytool.lua
+msgid "The clipboard couldn't access the metadata. Paste failed."
+msgstr "無法貼上:剪貼簿無法訪問元資料。"
+
+#: copytool.lua
+msgid "The clipboard is empty."
+msgstr "剪貼簿是空的。"
+
+#: copytool.lua
+msgid "The track you are trying to place the wagon on is not long enough."
+msgstr "軌道太短。"
+
+#: copytool.lua
+msgid "Train copied."
+msgstr "已複製火車。"
+
+#: copytool.lua
+msgid ""
+"Train copy/paste tool\n"
+"\n"
+"Left-click: copy train\n"
+"Right-click: paste train"
+msgstr ""
+"火車複製工具\n"
+"\n"
+"左鍵單擊:複製\n"
+"右鍵單擊:粘帖"
+
+#: copytool.lua
+msgid "You do not have the @1 privilege."
+msgstr "您沒有「@1」許可權。"
+
+#: couple.lua
+msgid "<No coupler>"
+msgstr "<無連結器>"
+
+#: couple.lua
+msgid "Buffer and Chain Coupler"
+msgstr "鏈式連結器"
+
+#: couple.lua
+msgid "Can not couple: The couplers of the trains do not match (@1 and @2)."
+msgstr "您無法連結這兩節車廂:這兩節車廂使用不同的連結器 (@1和@2)。"
+
+#: couple.lua
+msgid ""
+"Cannot couple @1 and @2 - train would have length @3 which is above the "
+"limit of @4"
+msgstr ""
+
+#: couple.lua
+msgid "Scharfenberg Coupler"
+msgstr "Scharfenberg 式連結器"
+
+#: couple.lua
+msgid ""
+"You are not allowed to couple trains without the train_operator privilege."
+msgstr "您沒有「train_operator」許可權,不能連結這兩節車廂。"
+
+#: craft_items.lua
+msgid "Boiler"
+msgstr "鍋爐"
+
+#: craft_items.lua
+msgid "Chimney"
+msgstr "煙囪"
+
+#: craft_items.lua
+msgid "Driver's cab"
+msgstr "駕駛室"
+
+#: craft_items.lua
+msgid "Wheel"
+msgstr "車輪"
+
+#: init.lua
+msgid "Advtrains Status: no_action @1 slowdown @2 (log @3)"
+msgstr ""
+
+#: init.lua
+msgid "Advtrains is already running normally!"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Crash during advtrains main step - skipping the shutdown save operation to "
+"not save inconsistent data!"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Data is being saved. While saving, advtrains will remove the players from "
+"trains. Save files will be reloaded afterwards!"
+msgstr ""
+
+#: init.lua
+msgid "Delete all train routes, force them to recalculate"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Detach all players, especially the offline ones, from all trains. Use only "
+"when no one serious is on a train."
+msgstr ""
+
+#: init.lua
+msgid "Disable the advtrains globalstep temporarily"
+msgstr ""
+
+#: init.lua
+msgid "Disabled advtrains successfully"
+msgstr ""
+
+#: init.lua
+msgid "Instructed to save() but load() was never called!"
+msgstr ""
+
+#: init.lua
+msgid "Print advtrains status info"
+msgstr ""
+
+#: init.lua
+msgid "Re-enabling advtrains globalstep..."
+msgstr ""
+
+#: init.lua
+msgid "Reload successful!"
+msgstr ""
+
+#: init.lua
+msgid "Removing unused wagon"
+msgstr ""
+
+#: init.lua
+msgid "Restoring saved state in 1 second..."
+msgstr ""
+
+#: init.lua
+msgid "Returns the position of the train with the given id"
+msgstr ""
+
+#: init.lua
+msgid "Saving failed: @1"
+msgstr ""
+
+#: init.lua
+msgid "Successfully invalidated train routes"
+msgstr ""
+
+#: init.lua
+msgid "Teleporting to train @1"
+msgstr ""
+
+#: init.lua
+msgid "Teleports you to the position of the train with the given id"
+msgstr ""
+
+#: init.lua
+msgid ""
+"The advtrains globalstep has been disabled. Trains are not moving, and no "
+"data is saved! Run '/at_disable_step no' to enable again!"
+msgstr ""
+
+#: init.lua
+msgid "Train @1 does not exist or is invalid"
+msgstr ""
+
+#: init.lua
+msgid ""
+"Train @1 had no wagons left because of some bug. It is being deleted. Wave "
+"it goodbye!"
+msgstr ""
+
+#: init.lua
+msgid "Train @1 is at @2"
+msgstr ""
+
+#: init.lua
+msgid "from wagon_save table."
+msgstr ""
+
+#: misc_nodes.lua
+msgid "@1 Platform (45 degree)"
+msgstr "較高的@1月臺 (45°)"
+
+#: misc_nodes.lua
+msgid "@1 Platform (high)"
+msgstr "較高的@1月臺"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low)"
+msgstr "較低的@1月臺"
+
+#: misc_nodes.lua
+msgid "@1 Platform (low, 45 degree)"
+msgstr "較低的@1月臺 (45°)"
+
+#: protection.lua
+msgid "Can operate turnouts and signals in unprotected areas"
+msgstr ""
+
+#: protection.lua
+msgid "Can place and dig tracks in unprotected areas"
+msgstr ""
+
+#: protection.lua
+msgid ""
+"Can place, remove and operate any train, regardless of owner, whitelist, or "
+"protection"
+msgstr ""
+
+#: protection.lua
+msgid "Can place, remove and operate trains"
+msgstr ""
+
+#: protection.lua
+msgid "You are not allowed to build near tracks at this protected position."
+msgstr "這裡已被保護,您不能在這裡的鐵路附近建任何東西。"
+
+#: protection.lua
+msgid ""
+"You are not allowed to build near tracks without the track_builder privilege."
+msgstr "您沒有「train_operator」許可權,不能在鐵路附近建任何東西。"
+
+#: protection.lua
+msgid "You are not allowed to build tracks at this protected position."
+msgstr "這裡已被保護,您不能在這裡建造鐵路。"
+
+#: protection.lua
+msgid ""
+"You are not allowed to build tracks without the track_builder privilege."
+msgstr "您沒有「train_operator」許可權,不能在這裡建造鐵路。"
+
+#: protection.lua
+msgid ""
+"You are not allowed to operate turnouts and signals without the "
+"railway_operator privilege."
+msgstr "您沒有「railway_operator」許可權,不能控制鐵路設施。"
+
+#: signals.lua
+msgid "Andrew's Cross"
+msgstr "平交道號誌燈"
+
+#: signals.lua
+#, fuzzy
+msgid "Lampless Signal (deprecated!)"
+msgstr "臂木式號誌機"
+
+#: signals.lua
+msgid "Signal (deprecated!)"
+msgstr ""
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (left) (deprecated!)"
+msgstr "壁掛式色燈號誌機 (左側)"
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (right) (deprecated!)"
+msgstr "壁掛式色燈號誌機 (右側)"
+
+#: signals.lua
+#, fuzzy
+msgid "Wallmounted Signal (top) (deprecated!)"
+msgstr "懸掛式色燈號誌機"
+
+#: track_reg_helper.lua
+msgid "@1 Slope"
+msgstr "@1斜坡"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: Not enough slope items left (@1 required)"
+msgstr "無法放置斜坡:您沒有足夠的鐵路斜坡放置工具 (您總共需要@1個)"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: There's no slope of length @1"
+msgstr "無法放置斜坡:advtrains 不支援長度為@1米的斜坡。"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: no supporting node at upper end."
+msgstr "無法放置斜坡:較高階沒有支撐方塊。"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: not pointing at node"
+msgstr "無法放置斜坡:您沒有選擇任何方塊。"
+
+#: track_reg_helper.lua
+#, fuzzy
+msgid "Can't place: space occupied!"
+msgstr "無法放置斜坡:此區域已被佔用。"
+
+#: track_reg_helper.lua tracks.lua
+#, fuzzy
+msgid "This track can not be removed!"
+msgstr "您不能移除這段軌道。"
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This node can't be changed using the trackworker!"
+msgstr "您不能使用鐵路調整工具調整這個方塊。"
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This node can't be rotated using the trackworker!"
+msgstr "您不能使用鐵路調整工具旋轉這個方塊。"
+
+#: trackplacer.lua
+#, fuzzy
+msgid "This track can not be rotated!"
+msgstr "您不能旋轉這段軌道。"
+
+#: trackplacer.lua
+msgid ""
+"Track Worker Tool\n"
+"\n"
+"Left-click: change rail type (straight/curve/switch)\n"
+"Right-click: rotate object"
+msgstr ""
+"鐵路調整工具\n"
+"\n"
+"左鍵單擊:切換軌道型別\n"
+"右鍵單擊:旋轉方塊"
+
+#: tracks.lua
+msgid "Position is occupied by a train."
+msgstr ""
+
+#: tracks.lua
+msgid "There's a Signal Influence Point here."
+msgstr ""
+
+#: tracks.lua
+msgid "There's a Track Circuit Break here."
+msgstr ""
+
+#: trainhud.lua
+msgid "OVERRUN RED SIGNAL! Examine situation and reverse train to move again."
+msgstr ""
+
+#: wagonprop_tool.lua
+msgid "Insufficient privileges to use this!"
+msgstr ""
+
+#: wagonprop_tool.lua
+#, fuzzy
+msgid "Wagon Properties Tool"
+msgstr "車廂屬性"
+
+#: wagonprop_tool.lua
+msgid ""
+"Wagon Properties Tool\n"
+"Punch a wagon to view and edit the Wagon Properties"
+msgstr ""
+
+#: wagons.lua
+msgid " units"
+msgstr ""
+
+#: wagons.lua
+msgid "!!! Train off track !!!"
+msgstr ""
+
+#: wagons.lua
+msgid "(Doors closed)"
+msgstr "(車門已關閉)"
+
+#: wagons.lua
+msgid "Allow these players to access your wagon:"
+msgstr ""
+
+#: wagons.lua
+msgid "Clear 'Disable ARS' flag"
+msgstr ""
+
+#: wagons.lua
+msgid "Current FC: "
+msgstr ""
+
+#: wagons.lua
+msgid ""
+"Destroying wagon with inventory, but inventory is not found? Shouldn't "
+"happen!"
+msgstr ""
+
+#: wagons.lua
+msgid "Doors are closed! (Try holding sneak key!)"
+msgstr ""
+
+#: wagons.lua
+msgid ""
+"Doors are closed. Use Sneak+rightclick to ignore the closed doors and get "
+"off."
+msgstr "車門已關閉,請使用潛行+右鍵單擊下車。"
+
+#: wagons.lua
+msgid "Freight Code:"
+msgstr ""
+
+#: wagons.lua
+msgid "Get off"
+msgstr "下車"
+
+#: wagons.lua
+msgid "Get off (forced)"
+msgstr "強制下車"
+
+#: wagons.lua
+msgid "Line"
+msgstr "火車線路"
+
+#: wagons.lua
+msgid "Liquid: "
+msgstr ""
+
+#: wagons.lua
+msgid "Liquid: empty"
+msgstr ""
+
+#: wagons.lua
+msgid "Missing train_operator privilege"
+msgstr ""
+
+#: wagons.lua
+msgid "Next FC:"
+msgstr ""
+
+#: wagons.lua
+msgid "Not a valid wagon id."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "Not allowed to do this."
+msgstr "您不能調整這段軌道。"
+
+#: wagons.lua
+msgid "Onboard Computer"
+msgstr ""
+
+#: wagons.lua
+msgid "Please specify a player name to transfer ownership to."
+msgstr ""
+
+#: wagons.lua
+msgid "Prev FC"
+msgstr ""
+
+#: wagons.lua
+msgid "Remote Routesetting"
+msgstr ""
+
+#: wagons.lua
+msgid "Routingcode"
+msgstr "路由碼"
+
+#: wagons.lua
+msgid "Save wagon properties"
+msgstr "儲存車廂屬性"
+
+#: wagons.lua
+msgid "Select seat:"
+msgstr "請選擇座位:"
+
+#: wagons.lua
+msgid "Show Inventory"
+msgstr "顯示物品欄"
+
+#: wagons.lua
+msgid "Text displayed inside train"
+msgstr "車廂內部顯示"
+
+#: wagons.lua
+msgid "Text displayed outside on train"
+msgstr "車廂外部顯示"
+
+#: wagons.lua
+msgid "That player does not exist!"
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "That wagon does not exist!"
+msgstr "這節車廂沒有座位。"
+
+#: wagons.lua
+#, fuzzy
+msgid "The track you are trying to place the wagon on is not long enough!"
+msgstr "軌道太短。"
+
+#: wagons.lua
+msgid "The wagon's inventory is not empty."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "This Wagon ID"
+msgstr "車廂已滿。"
+
+#: wagons.lua
+msgid "This wagon has no seats."
+msgstr "這節車廂沒有座位。"
+
+#: wagons.lua
+msgid "This wagon is full."
+msgstr "車廂已滿。"
+
+#: wagons.lua
+msgid "This wagon is owned by @1, you can't destroy it."
+msgstr "這是 @1 的車廂,您不能摧毀它。"
+
+#: wagons.lua
+msgid "Train ID"
+msgstr ""
+
+#: wagons.lua
+msgid "Train overview / coupling control is only shown when the train stands."
+msgstr ""
+
+#: wagons.lua
+msgid "Train overview /coupling control:"
+msgstr ""
+
+#: wagons.lua
+msgid "Uninitialized, removing"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon @1 ownership changed from @2 to @3"
+msgstr ""
+
+#: wagons.lua
+msgid "Wagon needs to be decoupled from other wagons in order to destroy it."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "Wagon placeholder"
+msgstr "車廂屬性"
+
+#: wagons.lua
+msgid "Wagon properties"
+msgstr "車廂屬性"
+
+#: wagons.lua
+msgid "Wagon road number:"
+msgstr ""
+
+#: wagons.lua
+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 ""
+"警告:如果您摧毀此車廂,您只能拿到一些鋼方塊。如果您確定要摧毀這節車廂,請按"
+"潛行鍵並左鍵單擊此車廂。"
+
+#: wagons.lua
+msgid "You are not allowed to access the driver stand."
+msgstr ""
+
+#: wagons.lua
+msgid "You can't get on this wagon."
+msgstr ""
+
+#: wagons.lua
+#, fuzzy
+msgid "You don't have the train_operator privilege."
+msgstr "您沒有「@1」許可權。"
+
+#: wagons.lua
+msgid "You have been given ownership of wagon @1"
+msgstr ""
+
+#~ msgid "3-way turnout"
+#~ msgstr "三開道岔"
+
+#~ msgid "90+Angle Diamond Crossing Track"
+#~ msgstr "交叉軌道 (其中一條軌道與座標軸平行)"
+
+#~ msgid "ATC controller"
+#~ msgstr "ATC 控制器"
+
+#~ msgid ""
+#~ "ATC controller, mode @1\n"
+#~ "Channel: @2"
+#~ msgstr ""
+#~ "ATC 控制器\n"
+#~ "模式:@1\n"
+#~ "頻道:@2"
+
+#~ msgid "Access to @1"
+#~ msgstr "可前往@1"
+
+#~ msgid "Big Industrial Train Engine"
+#~ msgstr "大型工業用火車頭"
+
+#~ msgid "Box Wagon"
+#~ msgstr "貨運車廂"
+
+#~ msgid "Bumper"
+#~ msgstr "保險槓"
+
+#~ msgid "Can't get on: wagon full or doors closed!"
+#~ msgstr "無法上車:車門已關閉或車廂已滿。"
+
+#~ msgid "Can't place: protected position!"
+#~ msgstr "無法放置:此區域已被保護。"
+
+#~ msgid "Command (on)"
+#~ msgstr "命令 (啟用時)"
+
+#~ msgid "Default Seat"
+#~ msgstr "預設座位"
+
+#~ msgid "Default Seat (driver stand)"
+#~ msgstr "預設座位 (司機座位)"
+
+#~ msgid "Dep. Speed"
+#~ msgstr "出發速度"
+
+#~ msgid "Deprecated Track"
+#~ msgstr "請不要使用"
+
+#~ msgid "Detailed Steam Engine"
+#~ msgstr "精細的蒸汽機車"
+
+#~ msgid "Detector Rail"
+#~ msgstr "探測軌道"
+
+#~ msgid "Diagonal Diamond Crossing Track"
+#~ msgstr "交叉軌道"
+
+#~ msgid "Door Delay"
+#~ msgstr "車門關閉時間"
+
+#, fuzzy
+#~ msgid "Driver Stand"
+#~ msgstr "司機座位"
+
+#~ msgid "Driver Stand (left)"
+#~ msgstr "左側司機座位"
+
+#~ msgid "Driver Stand (right)"
+#~ msgstr "右側司機座位"
+
+#~ msgid "Driver stand"
+#~ msgstr "司機座位"
+
+#~ msgid "Industrial Train Engine"
+#~ msgstr "工業用火車頭"
+
+#~ msgid "Industrial tank wagon"
+#~ msgstr "液體運輸車廂"
+
+#~ msgid "Industrial wood wagon"
+#~ msgstr "木材運輸車廂"
+
+#~ msgid "Japanese Train Engine"
+#~ msgstr "高速列車車頭"
+
+#~ msgid "Japanese Train Inter-Wagon Connection"
+#~ msgstr "日本火車連結器"
+
+#~ msgid "Japanese Train Wagon"
+#~ msgstr "高速列車車廂"
+
+#, fuzzy
+#~ msgid "Japanese signal pole"
+#~ msgstr "高速列車車廂"
+
+#~ msgid "Kick out passengers"
+#~ msgstr "踢出乘客"
+
+#~ msgid "Loading Track"
+#~ msgstr "裝貨軌道"
+
+#~ msgid "Lock couples"
+#~ msgstr "鎖定連結處"
+
+#~ msgid "Passenger Wagon"
+#~ msgstr "客車"
+
+#, fuzzy
+#~ msgid "Passenger area"
+#~ msgstr "客車"
+
+#~ msgid ""
+#~ "Passive Component Naming Tool\n"
+#~ "\n"
+#~ "Right-click to name a passive component."
+#~ msgstr ""
+#~ "被動元件命名工具\n"
+#~ "\n"
+#~ "右鍵單擊命名所選元件。"
+
+#~ msgid "Perpendicular Diamond Crossing Track"
+#~ msgstr "垂直交叉軌道"
+
+#~ msgid "Reverse train"
+#~ msgstr "改變行車方向"
+
+#~ msgid "Signal"
+#~ msgstr "色燈號誌機"
+
+#~ msgid "Speed:"
+#~ msgstr "速度"
+
+#~ msgid "Station Code"
+#~ msgstr "車站碼"
+
+#~ msgid "Station Name"
+#~ msgstr "車站名稱"
+
+#~ msgid "Station/Stop Track"
+#~ msgstr "車站軌道"
+
+#~ msgid "Steam Engine"
+#~ msgstr "蒸汽機車"
+
+#~ msgid "Stop Time"
+#~ msgstr "停站時間"
+
+#~ msgid "Subway Passenger Wagon"
+#~ msgstr "地鐵車廂"
+
+#~ msgid "Target:"
+#~ msgstr "目標速度"
+
+#, fuzzy
+#~ msgid "This node can't be rotated using the trackworker,"
+#~ msgstr "您不能使用鐵路調整工具旋轉這個方塊。"
+
+#~ msgid "This position is protected!"
+#~ msgstr "這裡已被保護。"
+
+#~ msgid "This track can not be changed."
+#~ msgstr "您不能調整這段軌道。"
+
+#~ msgid "Track"
+#~ msgstr "軌道"
+
+#, fuzzy
+#~ msgid "Train "
+#~ msgstr "已複製火車。"
+
+#~ msgid "Unconfigured LuaATC component"
+#~ msgstr "LuaATC 元件 (未配置)"
+
+#~ msgid "Unloading Track"
+#~ msgstr "卸貨軌道"
+
+#~ msgid "Use Sneak+rightclick to bypass closed doors!"
+#~ msgstr "請使用潛行+右鍵上車。"
+
+#~ msgid "Y-turnout"
+#~ msgstr "對稱道岔"
+
+#~ msgid ""
+#~ "You are not allowed to configure this LuaATC component without the @1 "
+#~ "privilege."
+#~ msgstr "您沒有「@1」許可權,不能配置這個 LuaATC 元件。"
+
+#~ msgid ""
+#~ "You are not allowed to configure this track without the @1 privilege."
+#~ msgstr "您沒有「@1」許可權,不能調整這段軌道。"
+
+#~ msgid "You are not allowed to configure this track."
+#~ msgstr "您不能調整這段軌道。"
+
+#, fuzzy
+#~ msgid "You are not allowed to modify this protected track."
+#~ msgstr "這裡已被保護,您不能在這裡建造鐵路。"
+
+#~ msgid ""
+#~ "You are not allowed to name LuaATC passive components without the @1 "
+#~ "privilege."
+#~ msgstr "您沒有「@1」許可權,不能命名這個元件。"
+
+#~ 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..6bb1ccb 100644
--- a/advtrains/protection.lua
+++ b/advtrains/protection.lua
@@ -1,27 +1,29 @@
-- advtrains
-- protection.lua: privileges and rail protection, and some helpers
+-- Get current translator
+local S = advtrains.translate
-- 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 = S("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 = S("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 = S("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 = S("Can operate turnouts and signals in unprotected areas"),
give_to_singleplayer= true,
});
@@ -145,12 +147,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 S("You are not allowed to build near tracks without the track_builder privilege.") or S("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 S("You are not allowed to build near tracks at this protected position.") or S("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 +183,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, S("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 2b627cb..a09da71 100644
--- a/advtrains/settingtypes.txt
+++ b/advtrains/settingtypes.txt
@@ -7,6 +7,10 @@ advtrains_show_ids (Show ID's in infotext) bool false
# You probably want to leave this setting set to false.
advtrains_enable_debugging (Enable debugging) bool false
+# Register certain debug items, for example the tunnelborer
+# Do not use on productive servers!
+advtrains_register_debugitems (Register Debug Items) bool false
+
# Enable the logging of certain events related to advtrains
# Logs are saved in the world directory as advtrains.log
# This setting is useful for multiplayer servers
@@ -61,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 5fb1d1b..198507a 100644
--- a/advtrains/signals.lua
+++ b/advtrains/signals.lua
@@ -1,24 +1,27 @@
--advtrains by orwell96
--signals.lua
+-- Get current translator
+local S = advtrains.translate
+
local mrules_wallsignal = advtrains.meseconrules
-local function can_dig_func(pos)
+local function can_dig_func(pos, player)
if advtrains.interlocking then
- return advtrains.interlocking.signal_can_dig(pos)
+ return advtrains.interlocking.signal.can_dig(pos, player)
end
return true
end
-local function after_dig_func(pos)
+local function after_dig_func(pos, oldnode, oldmetadata, digger)
if advtrains.interlocking then
- return advtrains.interlocking.signal_after_dig(pos)
+ return advtrains.interlocking.signal.after_dig(pos, oldnode, oldmetadata, digger)
end
return true
end
local function aspect(b)
return {
- main = (not b) and 0, -- b ? false : 0
+ main = b and -1 or 0,
shunt = false,
proceed_as_main = true,
dst = false,
@@ -26,22 +29,21 @@ return {
}
end
-local suppasp = {
- main = {0, false},
- dst = {false},
- shunt = nil,
- proceed_as_main = true,
- info = {
- call_on = false,
- dead_end = false,
- w_speed = nil,
- }
+local main_aspects = {
+ { name = "free", description = "Free" }
}
-for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do
+local function simple_apply_aspect(offname, onname)
+ return function(pos, node, main_aspect, rem_aspect, rem_aspinfo)
+ if main_aspect.halt then
+ advtrains.ndb.swap_node(pos, {name = offname, param2 = node.param2})
+ else
+ advtrains.ndb.swap_node(pos, {name = onname, param2 = node.param2})
+ end
+ end
+end
- advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "")
- advtrains.trackplacer.register_tracktype("advtrains:signal", "")
+for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do
for rotid, rotation in ipairs({"", "_30", "_45", "_60"}) do
local crea=1
@@ -60,7 +62,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=S("Lampless Signal (deprecated!)"),
sunlight_propagates=true,
groups = {
cracky=3,
@@ -74,42 +76,41 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
["action_"..f.as] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
if advtrains.interlocking then
- advtrains.interlocking.signal_on_aspect_changed(pos)
+ -- forcefully clears any set aspect, so that aspect system doesnt override it again
+ advtrains.interlocking.signal.unregister_aspect(pos)
+ -- notify trains
+ advtrains.interlocking.signal.notify_trains(pos)
end
end
}},
on_rightclick=function(pos, node, player)
local pname = player:get_player_name()
local sigd = advtrains.interlocking and advtrains.interlocking.db.get_sigd_for_signal(pos)
- if sigd then
+ if sigd and not player:get_player_control().aux1 then
advtrains.interlocking.show_signalling_form(sigd, pname)
elseif advtrains.interlocking and player:get_player_control().aux1 then
advtrains.interlocking.show_ip_form(pos, pname)
elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
if advtrains.interlocking then
- advtrains.interlocking.signal_on_aspect_changed(pos)
+ -- forcefully clears any set aspect, so that aspect system doesnt override it again
+ advtrains.interlocking.signal.unregister_aspect(pos)
+ -- notify trains
+ advtrains.interlocking.signal.notify_trains(pos)
end
end
end,
- -- new signal API
+ -- very new signal API
advtrains = {
- set_aspect = function(pos, node, asp)
- if asp.main ~= 0 then
- advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_on"..rotation, param2 = node.param2}, true)
- else
- advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_off"..rotation, param2 = node.param2}, true)
- end
- end,
- get_aspect = function(pos, node)
- return aspect(r=="on")
- end,
- supported_aspects = suppasp,
+ main_aspects = main_aspects,
+ apply_aspect = simple_apply_aspect("advtrains:retrosignal_off"..rotation, "advtrains:retrosignal_on"..rotation),
+ get_aspect_info = function() return aspect(r=="on") end,
+ route_role = "main",
},
can_dig = can_dig_func,
after_dig_node = after_dig_func,
+ --TODO add rotation using trackworker
})
- advtrains.trackplacer.add_worked("advtrains:retrosignal", r, rotation, nil)
minetest.register_node("advtrains:signal_"..r..rotation, {
drawtype = "mesh",
@@ -124,7 +125,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=S("Signal (deprecated!)"),
groups = {
cracky=3,
not_blocking_trains=1,
@@ -138,55 +139,51 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
rules=advtrains.meseconrules,
["action_"..f.as] = function (pos, node)
advtrains.setstate(pos, f.als, node)
- if advtrains.interlocking then
- advtrains.interlocking.signal_on_aspect_changed(pos)
- end
end
}},
on_rightclick=function(pos, node, player)
local pname = player:get_player_name()
local sigd = advtrains.interlocking and advtrains.interlocking.db.get_sigd_for_signal(pos)
- if sigd then
+ if sigd and not player:get_player_control().aux1 then
advtrains.interlocking.show_signalling_form(sigd, pname)
elseif advtrains.interlocking and player:get_player_control().aux1 then
advtrains.interlocking.show_ip_form(pos, pname)
elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
advtrains.setstate(pos, f.als, node)
- if advtrains.interlocking then
- advtrains.interlocking.signal_on_aspect_changed(pos)
- end
end
end,
- -- new signal API
+ -- very new signal API
advtrains = {
- set_aspect = function(pos, node, asp)
- if asp.main ~= 0 then
- advtrains.ndb.swap_node(pos, {name = "advtrains:signal_on"..rotation, param2 = node.param2}, true)
- else
- advtrains.ndb.swap_node(pos, {name = "advtrains:signal_off"..rotation, param2 = node.param2}, true)
- end
- end,
- get_aspect = function(pos, node)
- return aspect(r=="on")
- end,
- supported_aspects = suppasp,
- getstate = f.ls,
- setstate = function(pos, node, newstate)
- if newstate == f.als then
- advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true)
+ main_aspects = main_aspects,
+ apply_aspect = simple_apply_aspect("advtrains:signal_off"..rotation, "advtrains:signal_on"..rotation),
+ get_aspect_info = function() return aspect(r=="on") end,
+ route_role = "main",
+ node_state = f.ls,
+ node_state_map = { red = "advtrains:signal_off"..rotation, green = "advtrains:signal_on"..rotation},
+ node_on_switch_state = function(pos, new_node, old_state, new_state)
+ if advtrains.interlocking then
+ -- forcefully clears any set aspect, so that aspect system doesnt override it again
+ advtrains.interlocking.signal.unregister_aspect(pos)
+ -- notify trains
+ advtrains.interlocking.signal.notify_trains(pos)
end
end,
},
can_dig = can_dig_func,
after_dig_node = after_dig_func,
+ --TODO add rotation using trackworker
})
- advtrains.trackplacer.add_worked("advtrains:signal", r, rotation, nil)
end
local crea=1
if r=="off" then crea=0 end
--tunnel signals. no rotations.
+ local swdesc = { -- needed for xgettext
+ l = S("Wallmounted Signal (left) (deprecated!)"),
+ r = S("Wallmounted Signal (right) (deprecated!)"),
+ t = S("Wallmounted Signal (top) (deprecated!)"),
+ }
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",
@@ -200,7 +197,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,
@@ -214,42 +211,33 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
rules = mrules_wallsignal,
["action_"..f.as] = function (pos, node)
advtrains.setstate(pos, f.als, node)
- if advtrains.interlocking then
- advtrains.interlocking.signal_on_aspect_changed(pos)
- end
end
}},
on_rightclick=function(pos, node, player)
local pname = player:get_player_name()
local sigd = advtrains.interlocking and advtrains.interlocking.db.get_sigd_for_signal(pos)
- if sigd then
+ if sigd and not player:get_player_control().aux1 then
advtrains.interlocking.show_signalling_form(sigd, pname)
elseif advtrains.interlocking and player:get_player_control().aux1 then
advtrains.interlocking.show_ip_form(pos, pname)
elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
advtrains.setstate(pos, f.als, node)
- if advtrains.interlocking then
- advtrains.interlocking.signal_on_aspect_changed(pos)
- end
end
end,
- -- new signal API
+ -- very new signal API
advtrains = {
- set_aspect = function(pos, node, asp)
- if asp.main ~= 0 then
- advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_on", param2 = node.param2}, true)
- else
- advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_off", param2 = node.param2}, true)
- end
- end,
- get_aspect = function(pos, node)
- return aspect(r=="on")
- end,
- supported_aspects = suppasp,
- getstate = f.ls,
- setstate = function(pos, node, newstate)
- if newstate == f.als then
- advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true)
+ main_aspects = main_aspects,
+ apply_aspect = simple_apply_aspect("advtrains:signal_wall_"..loc.."_off", "advtrains:signal_wall_"..loc.."_on"),
+ get_aspect_info = function() return aspect(r=="on") end,
+ route_role = "main",
+ node_state = f.ls,
+ node_state_map = { red = "advtrains:signal_wall_"..loc.."_off", green = "advtrains:signal_wall_"..loc.."_on" },
+ node_on_switch_state = function(pos, new_node, old_state, new_state)
+ if advtrains.interlocking then
+ -- forcefully clears any set aspect, so that aspect system doesnt override it again
+ advtrains.interlocking.signal.unregister_aspect(pos)
+ -- notify trains
+ advtrains.interlocking.signal.notify_trains(pos)
end
end,
},
@@ -273,7 +261,7 @@ minetest.register_node("advtrains:across_off", {
mesh = "advtrains_across.obj",
tiles = {"advtrains_across.png"},
drop="advtrains:across_off",
- description=attrans("Andrew's Cross"),
+ description=S("Andrew's Cross"),
groups = {
cracky=3,
not_blocking_trains=1,
@@ -289,12 +277,8 @@ minetest.register_node("advtrains:across_off", {
end
}},
advtrains = {
- getstate = "off",
- setstate = function(pos, node, newstate)
- if newstate == "on" then
- advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
- end
- end,
+ node_state = "off",
+ node_state_map = { on = "advtrains:across_on", off = "advtrains:across_off" },
},
on_rightclick=function(pos, node, player)
if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
@@ -314,7 +298,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=S("Andrew's Cross"),
groups = {
cracky=3,
not_blocking_trains=1,
@@ -330,13 +314,9 @@ minetest.register_node("advtrains:across_on", {
end
}},
advtrains = {
- getstate = "on",
- setstate = function(pos, node, newstate)
- if newstate == "off" then
- advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
- end
- end,
- fallback_state = "off",
+ node_state = "on",
+ node_state_map = { on = "advtrains:across_on", off = "advtrains:across_off" },
+ node_fallback_state = "off",
},
on_rightclick=function(pos, node, player)
if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
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/speed_spec.lua b/advtrains/spec/speed_spec.lua
new file mode 100644
index 0000000..97f8ffa
--- /dev/null
+++ b/advtrains/spec/speed_spec.lua
@@ -0,0 +1,70 @@
+package.path = "../?.lua;" .. package.path
+advtrains = {}
+_G.advtrains = advtrains
+local speed = require("speed")
+
+describe("Arithmetic functions on speed restrictions", function()
+ it("should work", function()
+ local a = math.random()
+ local b = math.random(20)
+ -- This test is basically a "typo check"
+ assert.is_true (speed.lessp(a, b))
+ assert.is_false(speed.greaterp(a, b))
+ assert.is_false(speed.not_lessp(a, b))
+ assert.is_true (speed.not_greaterp(a, b))
+ assert.is_false(speed.lessp(a, a))
+ assert.is_false(speed.greaterp(a, a))
+ assert.is_true (speed.equalp(a, a))
+ assert.is_false(speed.not_equalp(a, a))
+ assert.equal(b, speed.max(a, b))
+ assert.equal(a, speed.min(a, b))
+ end)
+ it("should handle -1", function()
+ assert.is_false(speed.lessp(-1, math.random()))
+ end)
+ it("should handle nil", function()
+ assert.is_true(speed.greaterp(nil, math.random()))
+ end)
+ it("should handle mixed nil and -1", function()
+ assert.is_true(speed.equalp(nil, -1))
+ end)
+end)
+
+describe("The speed restriction setter", function()
+ it("should set the signal aspect", function()
+ local t = {speed_restrictions_t = {x = 5, y = 9}}
+ local u = {speed_restrictions_t = {x = 7, y = 9}, speed_restriction = 7}
+ speed.merge_aspect(t, {main = 7, type = "x"})
+ assert.same(u, t)
+ end)
+ it("should work with existing signal aspect tables", function()
+ local t = {speed_restrictions_t = {main = 5, foo = 3}}
+ local u = {speed_restrictions_t = {main = 7, foo = 3}, speed_restriction = 3}
+ speed.merge_aspect(t, {main = 7})
+ assert.same(u, t)
+ end)
+ it("should work with distant signals", function()
+ local t = {speed_restrictions_t = {main = 5}}
+ local u = {speed_restrictions_t = {main = 5}, speed_restriction = 5}
+ speed.merge_aspect(t, {})
+ assert.same(u, t)
+ end)
+ it("should create the restriction table if necessary", function()
+ local t = {speed_restriction = 5}
+ local u = {speed_restriction = 3, speed_restrictions_t = {main = 5, foo = 3}}
+ speed.merge_aspect(t, {main = 3, type = "foo"})
+ assert.same(u, t)
+ end)
+ it("should also create the restriction table for trains without any speed limit", function()
+ local t = {}
+ local u = {speed_restrictions_t = {}}
+ speed.merge_aspect(t, {})
+ assert.same(u, t)
+ end)
+ it("should set the speed restriction to nil if that is the case", function()
+ local t = {speed_restriction = math.random(20)}
+ local u = {speed_restrictions_t = {main = -1}}
+ speed.merge_aspect(t, {main = -1})
+ assert.same(u, t)
+ 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/speed.lua b/advtrains/speed.lua
new file mode 100644
index 0000000..ec4f928
--- /dev/null
+++ b/advtrains/speed.lua
@@ -0,0 +1,88 @@
+-- auxiliary functions for the reworked speed restriction system
+
+local function s_lessp(a, b)
+ if not a or a == -1 then
+ return false
+ elseif not b or b == -1 then
+ return true
+ else
+ return a < b
+ end
+end
+
+local function s_greaterp(a, b)
+ return s_lessp(b, a)
+end
+
+local function s_not_lessp(a, b)
+ return not s_lessp(a, b)
+end
+
+local function s_not_greaterp(a, b)
+ return not s_greaterp(a, b)
+end
+
+local function s_equalp(a, b)
+ return (a or -1) == (b or -1)
+end
+
+local function s_not_equalp(a, b)
+ return (a or -1) ~= (b or -1)
+end
+
+local function s_max(a, b)
+ if s_lessp(a, b) then
+ return b
+ else
+ return a
+ end
+end
+
+local function s_min(a, b)
+ if s_lessp(a, b) then
+ return a
+ else
+ return b
+ end
+end
+
+local function get_speed_restriction_from_table (tbl)
+ local strictest = -1
+ for _, v in pairs(tbl) do
+ strictest = s_min(strictest, v)
+ end
+ if strictest == -1 then
+ return nil
+ end
+ return strictest
+end
+
+local function set_speed_restriction (tbl, rtype, rval)
+ if rval then
+ tbl[rtype or "main"] = rval
+ end
+ return tbl
+end
+
+local function set_speed_restriction_for_train (train, rtype, rval)
+ local t = train.speed_restrictions_t or {main = train.speed_restriction}
+ train.speed_restrictions_t = set_speed_restriction(t, rtype, rval)
+ train.speed_restriction = get_speed_restriction_from_table(t)
+end
+
+local function merge_speed_restriction_from_aspect_to_train (train, asp)
+ return set_speed_restriction_for_train(train, asp.type, asp.main)
+end
+
+return {
+ lessp = s_lessp,
+ greaterp = s_greaterp,
+ not_lessp = s_not_lessp,
+ not_greaterp = s_not_greaterp,
+ equalp = s_equalp,
+ not_equalp = s_not_equalp,
+ max = s_max,
+ min = s_min,
+ set_restriction = set_speed_restriction_for_train,
+ merge_aspect = merge_speed_restriction_from_aspect_to_train,
+}
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/track_reg_helper.lua b/advtrains/track_reg_helper.lua
new file mode 100644
index 0000000..fe1d11d
--- /dev/null
+++ b/advtrains/track_reg_helper.lua
@@ -0,0 +1,751 @@
+-- New track registration helper
+-- Retains the old table-template-based definition format, but adapts it to the new (advtrains 2.5)
+-- track definition system
+-- Note to future: This is actually just a work-saver, avoiding me to port over all the crossing nodes as well as the linetrack tracks.
+-- Future track mods should please directly use the appropriate advtrains.register_node_4rot() API and not rely on this!
+
+-- Get current translator
+local S = advtrains.translate
+
+--definition preparation
+local function conns(c1, c2, r1, r2) return {{c=c1, y=r1}, {c=c2, y=r2}} end
+local function conns3(c1, c2, c3, r1, r2, r3) return {{c=c1, y=r1}, {c=c2, y=r2}, {c=c3, y=r3}} end
+
+advtrains.ap={}
+advtrains.ap.t_30deg_flat={
+ v25_format = true,
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "cr",
+ },
+ cr={
+ conns = conns(0,7),
+ desc = "curve",
+ tpdouble = true,
+ trackworker = "swlst",
+ },
+ swlst={
+ conns = conns3(0,8,7),
+ desc = "left switch (straight)",
+ trackworker = "swrst",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ switchprefix = "swl",
+ conn_map = {2,1,1},
+ stmref = "swl",
+ },
+ swlcr={
+ conns = conns3(0,8,7),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ switchprefix = "swl",
+ conn_map = {3,1,1},
+ stmref = "swl",
+ },
+ swrst={
+ conns = conns3(0,8,9),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ switchprefix = "swr",
+ conn_map = {2,1,1},
+ stmref = "swr",
+ },
+ swrcr={
+ conns = conns3(0,8,9),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ switchprefix = "swr",
+ conn_map = {3,1,1},
+ stmref = "swr",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ trackworker={
+ ["swrcr"]="st",
+ ["swrst"]="st",
+ ["cr"]="swlst",
+ ["swlcr"]="swrcr",
+ ["swlst"]="swrst",
+ },
+ rotation={"", "_30", "_45", "_60"},
+ statemaps = {
+ swl = { st = "swlst", cr = "swlcr"},
+ swr = { st = "swrst", cr = "swrcr"}
+ }
+}
+advtrains.ap.t_yturnout={
+ v25_format = true,
+ regstep=1,
+ variant={
+ l={
+ conns = conns3(0,7,9),
+ desc = "Y-turnout (left)",
+ switchalt = "r",
+ switchmc = "off",
+ switchst = "l",
+ switchprefix = "",
+ conn_map = {2,1,1},
+ stmref = "sw",
+ tpsingle = true,
+ },
+ r={
+ conns = conns3(0,7,9),
+ desc = "Y-turnout (right)",
+ switchalt = "l",
+ switchmc = "on",
+ switchst = "r",
+ switchprefix = "",
+ conn_map = {3,1,1},
+ stmref = "sw",
+ }
+ },
+ regtp=true,
+ tpdefault="l",
+ rotation={"", "_30", "_45", "_60"},
+ statemaps = {
+ sw = { l = "l", r = "r"}
+ }
+}
+advtrains.ap.t_s3way={
+ v25_format = true,
+ regstep=1,
+ variant={
+ l={
+ conns = { {c=0}, {c=7}, {c=8}, {c=9}},
+ desc = "3-way turnout (left)",
+ switchalt = "s",
+ switchst="l",
+ switchprefix = "",
+ conn_map = {2,1,1,1},
+ stmref = "sw",
+ },
+ s={
+ conns = { {c=0}, {c=7}, {c=8}, {c=9}},
+ desc = "3-way turnout (straight)",
+ switchalt ="r",
+ switchst = "s",
+ switchprefix = "",
+ conn_map = {3,1,1,1},
+ stmref = "sw",
+ tpsingle = true,
+ },
+ r={
+ conns = { {c=0}, {c=7}, {c=8}, {c=9}},
+ desc = "3-way turnout (right)",
+ switchalt = "l",
+ switchst="r",
+ switchprefix = "",
+ conn_map = {4,1,1,1},
+ stmref = "sw",
+ }
+ },
+ regtp=true,
+ tpdefault="l",
+ rotation={"", "_30", "_45", "_60"},
+ statemaps = {
+ sw = { l = "l", s = "s", r = "r"}
+ }
+}
+advtrains.ap.t_30deg_slope={
+ v25_format = true,
+ regstep=1,
+ variant={
+ vst1={conns = conns(8,0,0,0.5), rail_y = 0.25, desc = "steep uphill 1/2", slope=true},
+ vst2={conns = conns(8,0,0.5,1), rail_y = 0.75, desc = "steep uphill 2/2", slope=true},
+ vst31={conns = conns(8,0,0,0.33), rail_y = 0.16, desc = "uphill 1/3", slope=true},
+ vst32={conns = conns(8,0,0.33,0.66), rail_y = 0.5, desc = "uphill 2/3", slope=true},
+ vst33={conns = conns(8,0,0.66,1), rail_y = 0.83, desc = "uphill 3/3", slope=true},
+ },
+ regsp=true,
+ slopeplacer={
+ [2]={"vst1", "vst2"},
+ [3]={"vst31", "vst32", "vst33"},
+ max=3,--highest entry
+ },
+ slopeplacer_45={
+ [2]={"vst1_45", "vst2_45"},
+ max=2,
+ },
+ rotation={"", "_30", "_45", "_60"},
+ trackworker={},
+ increativeinv={},
+}
+advtrains.ap.t_30deg_straightonly={
+ v25_format = true,
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_30deg_straightonly_noplacer={
+ v25_format = true,
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_45deg={
+ v25_format = true,
+ regstep=2,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "cr",
+ },
+ cr={
+ conns = conns(0,6),
+ desc = "curve",
+ tpdouble = true,
+ trackworker = "swlst",
+ },
+ swlst={
+ conns = conns3(0,8,6),
+ desc = "left switch (straight)",
+ trackworker = "swrst",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swlcr={
+ conns = conns3(0,6,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ swrst={
+ conns = conns3(0,8,10),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swrcr={
+ conns = conns3(0,10,8),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ trackworker={
+ ["swrcr"]="st",
+ ["swrst"]="st",
+ ["cr"]="swlst",
+ ["swlcr"]="swrcr",
+ ["swlst"]="swrst",
+ },
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_perpcrossing={
+ v25_format = true,
+ regstep = 1,
+ variant={
+ st={
+ conns = { {c=0}, {c=8}, {c=4}, {c=12} },
+ desc = "perpendicular crossing",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ conn_map = {2,1,4,3},
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_90plusx_crossing={
+ v25_format = true,
+ regstep = 1,
+ variant={
+ ["30l"]={
+ conns = { {c=0}, {c=8}, {c=1}, {c=9} },
+ desc = "30/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "45l",
+ conn_map = {2,1,4,3},
+ },
+ ["45l"]={
+ conns = { {c=0}, {c=8}, {c=2}, {c=10} },
+ desc = "45/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "60l",
+ conn_map = {2,1,4,3},
+ },
+ ["60l"]={
+ conns = { {c=0}, {c=8}, {c=3}, {c=11}},
+ desc = "60/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "60r",
+ conn_map = {2,1,4,3},
+ },
+ ["60r"]={
+ conns = { {c=0}, {c=8}, {c=5}, {c=13} },
+ desc = "60/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "45r",
+ conn_map = {2,1,4,3},
+ },
+ ["45r"]={
+ conns = { {c=0}, {c=8}, {c=6}, {c=14} },
+ desc = "45/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "30r",
+ conn_map = {2,1,4,3},
+ },
+ ["30r"]={
+ conns = { {c=0}, {c=8}, {c=7}, {c=15}},
+ desc = "30/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "30l",
+ conn_map = {2,1,4,3},
+ },
+ },
+ regtp=true,
+ tpdefault="30l",
+ rotation={""},
+ trackworker = {
+ ["30l"] = "45l",
+ ["45l"] = "60l",
+ ["60l"] = "60r",
+ ["60r"] = "45r",
+ ["45r"] = "30r",
+ ["30r"] = "30l",
+ }
+}
+
+advtrains.ap.t_diagonalcrossing = {
+ v25_format = true,
+ regstep=1,
+ variant={
+ ["30l45r"]={
+ conns = {{c=1}, {c=9}, {c=6}, {c=14}},
+ desc = "30left-45right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l30l",
+ conn_map = {2,1,4,3},
+ },
+ ["60l30l"]={
+ conns = {{c=3}, {c=11}, {c=1}, {c=9}},
+ desc = "30left-60right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l45r",
+ conn_map = {2,1,4,3},
+ },
+ ["60l45r"]={
+ conns = {{c=3}, {c=11}, {c=6}, {c=14}},
+ desc = "60left-45right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l60r",
+ conn_map = {2,1,4,3},
+ },
+ ["60l60r"]={
+ conns = {{c=3}, {c=11}, {c=5}, {c=13}},
+ desc = "60left-60right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60r45l",
+ conn_map = {2,1,4,3},
+ },
+ --If 60l60r had a mirror image, it would be here, but it's symmetric.
+ -- 60l60r is also equivalent to 30l30r but rotated 90 degrees.
+ ["60r45l"]={
+ conns = {{c=5}, {c=13}, {c=2}, {c=10}},
+ desc = "60right-45left diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60r30r",
+ conn_map = {2,1,4,3},
+ },
+ ["60r30r"]={
+ conns = {{c=5}, {c=13}, {c=7}, {c=15}},
+ desc = "60right-30right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="30r45l",
+ conn_map = {2,1,4,3},
+ },
+ ["30r45l"]={
+ conns = {{c=7}, {c=15}, {c=2}, {c=10}},
+ desc = "30right-45left diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="30l45r",
+ conn_map = {2,1,4,3},
+ },
+
+ },
+ regtp=true,
+ tpdefault="30l45r",
+ rotation={""},
+ trackworker = {
+ ["30l45r"] = "60l30l",
+ ["60l30l"] = "60l45r",
+ ["60l45r"] = "60l60r",
+ ["60l60r"] = "60r45l",
+ ["60r45l"] = "60r30r",
+ ["60r30r"] = "30r45l",
+ ["30r45l"] = "30l45r",
+ }
+}
+
+advtrains.trackpresets = advtrains.ap
+
+--definition format: ([] optional)
+--[[{
+ nodename_prefix
+ texture_prefix
+ [shared_texture]
+ models_prefix
+ models_suffix (with dot)
+ [shared_model]
+ formats={
+ st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
+ (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
+ }
+ common={} change something on common rail appearance
+}
+[18.12.17] Note on new connection system:
+In order to support real rail crossing nodes and finally make the trackplacer respect switches, I changed the connection system.
+There can be a variable number of connections available. These are specified as tuples {c=<connection>, y=<rely>}
+The table "at_conns" consists of {<conn1>, <conn2>...}
+the "at_rail_y" property holds the value that was previously called "railheight"
+Depending on the number of connections:
+2 conns: regular rail
+3 conns: switch:
+ - when train passes in at conn1, will move out of conn2
+ - when train passes in at conn2 or conn3, will move out of conn1
+4 conns: cross (or cross switch, depending on arrangement of conns):
+ - conn1 <> conn2
+ - conn3 <> conn4
+]]
+
+-- Notify the user if digging the rail is not allowed
+local function can_dig_callback(pos, player)
+ local ok, reason = advtrains.can_dig_or_modify_track(pos)
+ if not ok and player then
+ minetest.chat_send_player(player:get_player_name(), S("This track can not be removed!") .. " " .. reason)
+ end
+ return ok
+end
+
+local function append_statemap_suffix(state_map, nnpref, rot)
+ local t = {}
+ for state, nn in pairs(state_map) do
+ t[state] = nnpref .. "_" .. nn .. rot
+ end
+ return t
+end
+
+function advtrains.default_suitable_substrate(upos)
+ return core.registered_nodes[core.get_node(upos).name]
+ and core.registered_nodes[core.get_node(upos).name].walkable
+end
+
+function advtrains.register_tracks(tracktype, def, preset)
+ if not preset.v25_format then
+ error("advtrains.register_tracks(): A track preset for pre-v2.5 is used with advtrains 2.5+. Mod probably defines own track preset instead of using it from the advtrains.ap table! Please check track mod compatibility!")
+ end
+
+ if preset.regtp then
+ local nnprefix = def.nodename_prefix
+ minetest.register_craftitem(":"..nnprefix.."_placer", {
+ description = def.description,
+ inventory_image = def.texture_prefix.."_placer.png",
+ wield_image = def.texture_prefix.."_placer.png",
+ groups={advtrains_trackplacer=1, digtron_on_place=1},
+ liquids_pointable = false,
+ on_place = function(itemstack, placer, pointed_thing)
+ local name = placer:get_player_name()
+ if not name then
+ return itemstack, false
+ end
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.above
+ local upos=vector.subtract(pointed_thing.above, {x=0, y=1, z=0})
+ if not advtrains.check_track_protection(pos, name) then
+ return itemstack, false
+ end
+ if minetest.registered_nodes[minetest.get_node(pos).name] and minetest.registered_nodes[minetest.get_node(pos).name].buildable_to then
+ if (def.suitable_substrate and def.suitable_substrate or advtrains.default_suitable_substrate)(upos) then
+ -- minetest.chat_send_all(nnprefix)
+ local yaw = placer:get_look_horizontal()
+ advtrains.trackplacer.place_track(pos, nnprefix, name, yaw)
+ if not advtrains.is_creative(name) then
+ itemstack:take_item()
+ end
+ end
+ end
+ end
+ return itemstack, true
+ end,
+ })
+
+ advtrains.trackplacer.set_default_place_candidate(def.nodename_prefix, def.nodename_prefix.."_"..preset.tpdefault)
+ end
+ if preset.regsp then
+ advtrains.slope.register_placer(def, preset)
+ end
+
+ for suffix, var in pairs(preset.variant) do
+ for rotid, rotation in ipairs(preset.rotation) do
+ if not def.formats[suffix] or def.formats[suffix][rotid] then
+ local img_suffix = suffix..rotation
+ local ndef = advtrains.merge_tables({
+ description=def.description.."("..(var.desc or "any")..rotation..")",
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ use_texture_alpha = "clip",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/2-1/16, -1/2, -1/2, 1/2+1/16, -1/2+2/16, 1/2},
+ },
+
+ mesh = def.shared_model or (def.models_prefix.."_"..img_suffix..def.models_suffix),
+ tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture},
+
+ groups = {
+ attached_node = advtrains.IGNORE_WORLD and 0 or 1,
+ advtrains_track=1,
+ ["advtrains_track_"..tracktype]=1,
+ save_in_at_nodedb=1,
+ dig_immediate=2,
+ not_in_creative_inventory=1,
+ not_blocking_trains=1,
+ },
+
+ can_dig = advtrains.track_can_dig_callback,
+ after_dig_node = advtrains.track_update_callback,
+ after_place_node = advtrains.track_update_callback,
+
+ at_rail_y = var.rail_y
+ }, def.common or {})
+
+ if preset.regtp then
+ ndef.drop = def.nodename_prefix.."_placer"
+ end
+ if preset.regsp and var.slope then
+ ndef.drop = def.nodename_prefix.."_slopeplacer"
+ end
+
+ --connections
+ ndef.at_conns = advtrains.rotate_conn_by(var.conns, (rotid-1)*preset.regstep)
+ -- NEW since 2.5
+ ndef.at_conn_map = var.conn_map
+
+ local ndef_avt_table = {}
+
+ if var.switchalt and var.switchst then
+ -- NEW since 2.5
+ ndef.on_rightclick = advtrains.state_node_on_rightclick_callback
+ ndef_avt_table.node_state = var.switchst
+ ndef_avt_table.node_next_state = var.switchalt
+ -- obtain and build statemap
+ local state_map = preset.statemaps[var.stmref]
+ if not state_map then error("On registering "..def.nodename_prefix.."_"..suffix..rotation..", stmref of variant doesn't reference entry in preset.statemaps") end
+ ndef_avt_table.node_state_map = append_statemap_suffix(state_map, def.nodename_prefix, rotation)
+
+ if var.switchmc then
+ local vswitchalt = var.switchalt
+ ndef.mesecons = {effector = {
+ ["action_"..var.switchmc] = function(pos, node)
+ advtrains.setstate(pos, vswitchalt, node)
+ end,
+ rules=advtrains.meseconrules
+ }}
+ end
+ end
+
+ local adef={}
+ if def.get_additional_definiton then
+ adef=def.get_additional_definiton(def, preset, suffix, rotation)
+ end
+ ndef = advtrains.merge_tables(ndef, adef)
+
+ -- insert getstate/setstate functions after merging the additional definitions
+ if ndef_avt_table then
+ ndef.advtrains = advtrains.merge_tables(ndef.advtrains or {}, ndef_avt_table)
+ end
+
+ -- NEW since 2.5: add appropriate fields for trackworker rotation
+ -- get the next rotation step
+ local num_rots = #preset.rotation
+ if rotid >= num_rots then
+ ndef.advtrains.trackworker_next_rot = def.nodename_prefix.."_"..suffix..preset.rotation[1]
+ ndef.advtrains.trackworker_rot_incr_param2 = true
+ else
+ ndef.advtrains.trackworker_next_rot = def.nodename_prefix.."_"..suffix..preset.rotation[rotid+1]
+ end
+ if var.trackworker then
+ ndef.advtrains.trackworker_next_var = def.nodename_prefix.."_"..var.trackworker..rotation
+ end
+
+ local the_node_name = def.nodename_prefix.."_"..suffix..rotation
+
+ --trackplacer
+ if preset.regtp and (var.tpsingle or var.tpdouble) then
+ advtrains.trackplacer.register_candidate(
+ def.nodename_prefix,
+ the_node_name,
+ ndef,
+ var.tpsingle,
+ var.tpdouble,
+ true)
+ end
+
+ -- All set, go ahead and register node!
+ --atdebug("track_reg_helper: Registering ",the_node_name," as",ndef)
+ minetest.register_node(":"..the_node_name, ndef)
+
+ end
+ end
+ end
+end
+
+-- slope placer. Defined in register_tracks.
+--crafted with rail and gravel
+local sl={}
+function sl.register_placer(def, preset)
+ minetest.register_craftitem(":"..def.nodename_prefix.."_slopeplacer",{
+ description = S("@1 Slope", def.description),
+ inventory_image = def.texture_prefix.."_slopeplacer.png",
+ wield_image = def.texture_prefix.."_slopeplacer.png",
+ groups={},
+ on_place = sl.create_slopeplacer_on_place(def, preset)
+ })
+end
+--(itemstack, placer, pointed_thing)
+function sl.create_slopeplacer_on_place(def, preset)
+ return function(istack, player, pt)
+ if not pt.type=="node" then
+ minetest.chat_send_player(player:get_player_name(), S("Can't place: not pointing at node"))
+ return istack
+ end
+ local pos=pt.above
+ if not pos then
+ minetest.chat_send_player(player:get_player_name(), S("Can't place: not pointing at node"))
+ return istack
+ end
+ local node=minetest.get_node(pos)
+ if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to then
+ minetest.chat_send_player(player:get_player_name(), S("Can't place: space occupied!"))
+ return istack
+ end
+ if not advtrains.check_track_protection(pos, player:get_player_name()) then
+ minetest.record_protection_violation(pos, player:get_player_name())
+ return istack
+ end
+ --determine player orientation (only horizontal component)
+ --get_look_horizontal may not be available
+ local yaw=player.get_look_horizontal and player:get_look_horizontal() or (player:get_look_yaw() - math.pi/2)
+
+ --rounding unit vectors is a nice way for selecting 1 of 8 directions since sin(30°) is 0.5.
+ local dirvec={x=math.floor(math.sin(-yaw)+0.5), y=0, z=math.floor(math.cos(-yaw)+0.5)}
+ --translate to direction to look up inside the preset table
+ local param2, rot45=({
+ [-1]={
+ [-1]=2,
+ [0]=3,
+ [1]=3,
+ },
+ [0]={
+ [-1]=2,
+ [1]=0,
+ },
+ [1]={
+ [-1]=1,
+ [0]=1,
+ [1]=0,
+ },
+ })[dirvec.x][dirvec.z], dirvec.x~=0 and dirvec.z~=0
+ local lookup=preset.slopeplacer
+ if rot45 then lookup=preset.slopeplacer_45 end
+
+ --go unitvector forward and look how far the next node is
+ local step=1
+ while step<=lookup.max do
+ local node=minetest.get_node(vector.add(pos, dirvec))
+ --next node solid?
+ if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to or advtrains.is_protected(pos, player:get_player_name()) then
+ --do slopes of this distance exist?
+ if lookup[step] then
+ if minetest.settings:get_bool("creative_mode") or istack:get_count()>=step then
+ --start placing
+ local placenodes=lookup[step]
+ while step>0 do
+ minetest.set_node(pos, {name=def.nodename_prefix.."_"..placenodes[step], param2=param2})
+ if not minetest.settings:get_bool("creative_mode") then
+ istack:take_item()
+ end
+ step=step-1
+ pos=vector.subtract(pos, dirvec)
+ end
+ else
+ minetest.chat_send_player(player:get_player_name(), S("Can't place: Not enough slope items left (@1 required)", step))
+ end
+ else
+ minetest.chat_send_player(player:get_player_name(), S("Can't place: There's no slope of length @1",step))
+ end
+ return istack
+ end
+ step=step+1
+ pos=vector.add(pos, dirvec)
+ end
+ minetest.chat_send_player(player:get_player_name(), S("Can't place: no supporting node at upper end."))
+ return itemstack
+ end
+end
+
+advtrains.slope=sl
diff --git a/advtrains/trackdb_legacy.lua b/advtrains/trackdb_legacy.lua
deleted file mode 100644
index 99349e8..0000000
--- a/advtrains/trackdb_legacy.lua
+++ /dev/null
@@ -1,27 +0,0 @@
---trackdb_legacy.lua
---loads the (old) track database. the only use for this is to provide data for rails that haven't been written into the ndb database.
---nothing will be saved.
---if the user thinks that he has loaded every track in his world at least once, he can delete the track database.
-
---trackdb[[y][x][z]={conn1, conn2, rely1, rely2, railheight}
-
-
---trackdb keeps its own save file.
-advtrains.fpath_tdb=minetest.get_worldpath().."/advtrains_trackdb2"
-local file, err = io.open(advtrains.fpath_tdb, "r")
-if not file then
- atprint("Not loading a trackdb file.")
-else
- local tbl = minetest.deserialize(file:read("*a"))
- if type(tbl) == "table" then
- advtrains.trackdb=tbl
- atprint("Loaded trackdb file.")
- end
- file:close()
-end
-
-
-
-
-
-
diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua
index fe76290..0bcde77 100644
--- a/advtrains/trackplacer.lua
+++ b/advtrains/trackplacer.lua
@@ -1,431 +1,338 @@
--trackplacer.lua
--holds code for the track-placing system. the default 'track' item will be a craftitem that places rails as needed. this will neither place or change switches nor place vertical rails.
+-- Get current translator
+local S = advtrains.translate
+
--all new trackplacer code
local tp={
- tracks={}
+ groups={}
}
-function tp.register_tracktype(nnprefix, n_suffix)
- if tp.tracks[nnprefix] then return end--due to the separate registration of slopes and flats for the same nnpref, definition would be overridden here. just don't.
- tp.tracks[nnprefix]={
- default=n_suffix,
- single_conn={},
- single_conn_1={},
- single_conn_2={},
- double_conn={},
- double_conn_1={},
- double_conn_2={},
- --keys:conn1_conn2 (example:1_4)
- --values:{name=x, param2=x}
- twcycle={},
- twrotate={},--indexed by suffix, list, tells order of rotations
- modify={},
- }
-end
-function tp.add_double_conn(nnprefix, suffix, rotation, conns)
- local nodename=nnprefix.."_"..suffix..rotation
- for i=0,3 do
- tp.tracks[nnprefix].double_conn[((conns.conn1+4*i)%16).."_"..((conns.conn2+4*i)%16)]={name=nodename, param2=i}
- tp.tracks[nnprefix].double_conn[((conns.conn2+4*i)%16).."_"..((conns.conn1+4*i)%16)]={name=nodename, param2=i}
- tp.tracks[nnprefix].double_conn_1[((conns.conn1+4*i)%16).."_"..((conns.conn2+4*i)%16)]={name=nodename, param2=i}
- tp.tracks[nnprefix].double_conn_2[((conns.conn2+4*i)%16).."_"..((conns.conn1+4*i)%16)]={name=nodename, param2=i}
- end
- tp.tracks[nnprefix].modify[nodename]=true
-end
-function tp.add_single_conn(nnprefix, suffix, rotation, conns)
- local nodename=nnprefix.."_"..suffix..rotation
- for i=0,3 do
- tp.tracks[nnprefix].single_conn[((conns.conn1+4*i)%16)]={name=nodename, param2=i}
- tp.tracks[nnprefix].single_conn[((conns.conn2+4*i)%16)]={name=nodename, param2=i}
- tp.tracks[nnprefix].single_conn_1[((conns.conn1+4*i)%16)]={name=nodename, param2=i}
- tp.tracks[nnprefix].single_conn_2[((conns.conn2+4*i)%16)]={name=nodename, param2=i}
- end
- tp.tracks[nnprefix].modify[nodename]=true
-end
+--[[ New in version 2.5:
+The track placer no longer uses hacky nodename pattern matching.
+The base criterion for rotating or matching tracks is the common "ndef.advtrains.track_place_group" property.
+Only rails where this field is set are considered for replacement. Other rails can still be considered for connection.
+Replacement ("bending") of rails can only happen within their respective track place group. Only two-conn rails are allowed in the trackplacer.
-function tp.add_worked(nnprefix, suffix, rotation, cycle_follows)
- tp.tracks[nnprefix].twcycle[suffix]=cycle_follows
- if not tp.tracks[nnprefix].twrotate[suffix] then tp.tracks[nnprefix].twrotate[suffix]={} end
- table.insert(tp.tracks[nnprefix].twrotate[suffix], rotation)
-end
+The track registration functions register the candidates for any given track_place_group in two separate collections:
+- double: tracks that can be used to connect both ends of the rail
+- single: tracks that will be used to connect conn1 when only a single end is to be connected
+When track placing is requested, the calling code just supplies the track_place_group to be placed.
---[[
- rewrite algorithm.
- selection criteria: these will never be changed or even selected:
- - tracks being already connected on both sides
- - tracks that are already connected on one side but are not bendable to the desired position
- the following situations can occur:
- 1. there are two more than two rails around
- 1.1 there is one or more subset(s) that can be directly connected
- -> choose the first possibility
- 2.2 not
- -> choose the first one and orient straight
- 2. there's exactly 1 rail around
- -> choose and orient straight
- 3. there's no rail around
- -> set straight
-]]
+]]--
-local function istrackandbc(pos_p, conn)
- local tpos = pos_p
- local cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c))
- if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then
- local cconns=advtrains.get_track_connections(cnode.name, cnode.param2)
- return advtrains.conn_matches_to(conn, cconns)
- end
- --try the same 1 node below
- tpos = {x=tpos.x, y=tpos.y-1, z=tpos.z}
- cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c))
- if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then
- local cconns=advtrains.get_track_connections(cnode.name, cnode.param2)
- return advtrains.conn_matches_to(conn, cconns)
- end
- return false
+local function rotate(conn, rot)
+ return (conn + rot) % 16
end
-function tp.find_already_connected(pos)
- local dnode=minetest.get_node(pos)
- local dconns=advtrains.get_track_connections(dnode.name, dnode.param2)
- local found_conn
- for connid, conn in ipairs(dconns) do
- if istrackandbc(pos, conn) then
- if found_conn then --we found one in previous iteration
- return true, true --signal that it's connected
- else
- found_conn = conn.c
+-- Register a track node as candidate
+-- tpg: the track place group to register the candidates for
+-- NOTE: This value is automatically added to the node definition (ndef) in field ndef.advtrains.track_place_group!
+-- name, ndef: the node name and node definition table to register
+-- as_single: whether the rail should be considered as candidate for one-endpoint connection
+-- Typically only set for the straight rail variants
+-- as_double: whether the rail should be considered as candidate for two-endpoint connection
+-- Typically set for straights and curves
+-- ignore_2conn_for_legacy_xing: skips the 2-connection assertion - ONLY for compatibility with the legacy crossing nodes, DO NOT USE!
+function tp.register_candidate(tpg, name, ndef, as_single, as_double, ignore_2conn_for_legacy_xing)
+ --atdebug("TP Register candidate:",tpg, name, as_single, as_double)
+ --get or create TP group
+ if not tp.groups[tpg] then
+ tp.groups[tpg] = {double = {}, single1 = {}, single2 = {}, default = {name = name, param2 = 0} }
+ -- note: this causes the first candidate to ever be registered to be the default (which is typically what you want)
+ -- But it can be overwritten using tp.set_default_place_candidate
+ end
+ local g = tp.groups[tpg]
+
+ -- get conns
+ if not ignore_2conn_for_legacy_xing then
+ assert(#ndef.at_conns == 2)
+ end
+ local c1, c2 = ndef.at_conns[1].c, ndef.at_conns[2].c
+ local is_symmetrical = (rotate(c1, 8) == c2)
+
+ -- store all possible rotations (param2 values)
+ for i=0,3 do
+ if as_double then
+ g.double[rotate(c1,i*4).."_"..rotate(c2,i*4)] = {name=name, param2=i}
+ if not is_symmetrical then
+ g.double[rotate(c2,i*4).."_"..rotate(c1,i*4)] = {name=name, param2=i}
+ -- if the track is unsymmetric (e.g. a curve), we may require the "wrong" orientation to fill a gap.
end
end
+ if as_single then
+ g.single1[rotate(c1,i*4)] = {name=name, param2=i}
+ g.single2[rotate(c2,i*4)] = {name=name, param2=i}
+ end
+ end
+
+ -- Set track place group on the node
+ if not ndef.advtrains then
+ ndef.advtrains = {}
end
- return found_conn
+ ndef.advtrains.track_place_group = tpg
end
-function tp.rail_and_can_be_bent(originpos, conn)
- local pos=advtrains.dirCoordSet(originpos, conn)
- local newdir=(conn+8)%16
- local node=minetest.get_node(pos)
- if not advtrains.is_track_and_drives_on(node.name, advtrains.all_tracktypes) then
+
+-- Sets the node that is placed by the track placer when there is no track nearby. param2 defaults to 0
+function tp.set_default_place_candidate(tpg, name, param2)
+ if not tp.groups[tpg] then
+ tp.groups[tpg] = {double = {}, single1 = {}, single2 = {}, default = {name = name, param2 = param2 or 0} }
+ else
+ tp.groups[tpg].default = {name = name, param2 = param2 or 0}
+ end
+end
+
+local function check_or_bend_rail(origin, dir, pname, commit)
+ local pos = advtrains.pos_add_dir(origin, dir)
+ local back_dir = advtrains.oppd(dir);
+
+ local node_ok, conns = advtrains.get_rail_info_at(pos)
+ if not node_ok then
+ -- try the node one level below
+ pos.y = pos.y - 1
+ node_ok, conns = advtrains.get_rail_info_at(pos)
+ end
+ if not node_ok then
return false
end
- local ndef=minetest.registered_nodes[node.name]
- local nnpref = ndef and ndef.at_nnpref
- if not nnpref then return false end
- local tr=tp.tracks[nnpref]
- if not tr then return false end
- if not tr.modify[node.name] then
- --we actually can use this rail, but only if it already points to the desired direction.
- if advtrains.is_track_and_drives_on(node.name, advtrains.all_tracktypes) then
- local cconns=advtrains.get_track_connections(node.name, node.param2)
- return advtrains.conn_matches_to(conn, cconns)
+ -- if one conn of the node here already points towards us, nothing to do
+ for connid, conn in ipairs(conns) do
+ if back_dir == conn.c then
+ return true
end
end
- -- If the rail is not allowed to be modified, also only use if already in desired direction
+ -- can we bend the node here?
+ local node = advtrains.ndb.get_node(pos)
+ local ndef = minetest.registered_nodes[node.name]
+ if not ndef or not ndef.advtrains or not ndef.advtrains.track_place_group then
+ return false
+ end
+ -- now the track must be two-conn, else it wouldn't be allowed to have track_place_group set.
+ --assert(#conns == 2) -- cannot check here, because of legacy crossing hack
+ -- Is player and game allowed to do this?
if not advtrains.can_dig_or_modify_track(pos) then
- local cconns=advtrains.get_track_connections(node.name, node.param2)
- return advtrains.conn_matches_to(conn, cconns)
+ return false
end
- --rail at other end?
- local adj1, adj2=tp.find_already_connected(pos)
- if adj1 and adj2 then
- return false--dont destroy existing track
- elseif adj1 and not adj2 then
- if tr.double_conn[adj1.."_"..newdir] then
- return true--if exists, connect new rail and old end
- end
+ if not advtrains.check_track_protection(pos, pname) then
return false
- else
- if tr.single_conn[newdir] then--just rotate old rail to right orientation
- return true
+ end
+ -- we confirmed that track can be modified. Does there exist a suitable connection candidate?
+ -- check if there are any unbound ends
+ local bound_connids = {}
+ for connid, conn in ipairs(conns) do
+ local adj_pos, adj_connid = advtrains.get_adjacent_rail(pos, conns, connid)
+ if adj_pos then
+ bound_connids[#bound_connids+1] = connid
end
- return false
end
-end
-function tp.bend_rail(originpos, conn)
- local pos=advtrains.dirCoordSet(originpos, conn)
- local newdir=advtrains.oppd(conn)
- local node=minetest.get_node(pos)
- local ndef=minetest.registered_nodes[node.name]
- local nnpref = ndef and ndef.at_nnpref
- if not nnpref then return false end
- local tr=tp.tracks[nnpref]
- if not tr then return false end
- --is rail already connected? no need to bend.
- local conns=advtrains.get_track_connections(node.name, node.param2)
- if advtrains.conn_matches_to(conn, conns) then
- return
+ -- depending on the nummber of ends, decide
+ if #bound_connids == 2 then
+ -- rail is within a fixed track, do not break up
+ return false
end
- --rail at other end?
- local adj1, adj2=tp.find_already_connected(pos)
- if adj1 and adj2 then
- return false--dont destroy existing track
- elseif adj1 and not adj2 then
- if tr.double_conn[adj1.."_"..newdir] then
- advtrains.ndb.swap_node(pos, tr.double_conn[adj1.."_"..newdir])
- return true--if exists, connect new rail and old end
+ -- obtain the group table
+ local g = tp.groups[ndef.advtrains.track_place_group]
+ if #bound_connids == 1 then
+ -- we can attempt double
+ local bound_dir = conns[bound_connids[1]].c
+ if g.double[back_dir.."_"..bound_dir] then
+ if commit then
+ advtrains.ndb.swap_node(pos, g.double[back_dir.."_"..bound_dir])
+ end
+ return true
end
- return false
else
- if tr.single_conn[newdir] then--just rotate old rail to right orientation
- advtrains.ndb.swap_node(pos, tr.single_conn[newdir])
+ -- rail is entirely unbound, we can attempt single1
+ if g.single1[back_dir] then
+ if commit then
+ advtrains.ndb.swap_node(pos, g.single1[back_dir])
+ end
return true
end
- return false
end
end
-function tp.placetrack(pos, nnpref, placer, itemstack, pointed_thing, yaw)
- --1. find all rails that are likely to be connected
- local tr=tp.tracks[nnpref]
- local p_rails={}
- local p_railpos={}
+
+local function track_place_node(pos, node, ndef_p, pname)
+ --atdebug("track_place_node: ",pos, node)
+ advtrains.ndb.swap_node(pos, node)
+ local ndef = ndef_p or minetest.registered_nodes[node.name]
+ if ndef and ndef.after_place_node then
+ -- resolve player again
+ local player = pname and core.get_player_by_name(pname) or nil
+ ndef.after_place_node(pos, player) -- note: itemstack and pointed_thing are NOT available here anymore (crap!)
+ end
+end
+
+
+-- Main API function to place a track. Replaces the older "placetrack"
+-- This function will attempt to place a track of the specified track placing group at the specified position, connecting it
+-- with neighboring rails. Neighboring rails can themselves be replaced ("bent") within their own track place group,
+-- if the player is permitted to do this.
+-- Order of preference is:
+-- Connect two track ends if possible
+-- Connect one track end if any rail is near
+-- Place the default track if no tracks are near
+-- The function returns true on success.
+function tp.place_track(pos, tpg, pname, yaw)
+ -- 1. collect neighboring tracks and whether they can be connected
+ --atdebug("tp.place_track(",pos, tpg, pname, yaw,")")
+ local cand = {}
for i=0,15 do
- if tp.rail_and_can_be_bent(pos, i, nnpref) then
- p_rails[#p_rails+1]=i
- p_railpos[i] = pos
- else
- local upos = {x=pos.x, y=pos.y-1, z=pos.z}
- if tp.rail_and_can_be_bent(upos, i, nnpref) then
- p_rails[#p_rails+1]=i
- p_railpos[i] = upos
- end
+ if check_or_bend_rail(pos, i, pname) then
+ cand[#cand+1] = i
end
end
-
- -- try double_conn
- if #p_rails > 1 then
- --iterate subsets
- for k1, conn1 in ipairs(p_rails) do
- for k2, conn2 in ipairs(p_rails) do
- if k1~=k2 then
- local dconn1 = tr.double_conn_1
- local dconn2 = tr.double_conn_2
- if not (advtrains.yawToDirection(yaw, conn1, conn2) == conn1) then
- dconn1 = tr.double_conn_2
- dconn2 = tr.double_conn_1
- end
- -- Checks are made this way round so that dconn1 has priority (this will make arrows of atc rails
- -- point in the right direction)
- local using
- if (dconn2[conn1.."_"..conn2]) then
- using = dconn2[conn1.."_"..conn2]
- end
- if (dconn1[conn1.."_"..conn2]) then
- using = dconn1[conn1.."_"..conn2]
- end
- if using then
- -- has found a fitting rail in either direction
- -- if not, continue loop
- tp.bend_rail(p_railpos[conn1], conn1, nnpref)
- tp.bend_rail(p_railpos[conn2], conn2, nnpref)
- advtrains.ndb.swap_node(pos, using)
- local nname=using.name
- if minetest.registered_nodes[nname] and minetest.registered_nodes[nname].after_place_node then
- minetest.registered_nodes[nname].after_place_node(pos, placer, itemstack, pointed_thing)
- end
- return
+ --atdebug("Candidates: ",cand)
+ -- obtain the group table
+ local g = tp.groups[tpg]
+ if not g then
+ error("tp.place_track: for tpg="..tpg.." couldn't find the group table")
+ end
+ --atdebug("Group table:",g)
+ -- 2. try all possible two-endpoint connections
+ for k1, conn1 in ipairs(cand) do
+ for k2, conn2 in ipairs(cand) do
+ if k1~=k2 then
+ -- order of conn1/conn2: prefer conn1 being in the direction of the player facing.
+ -- the combination the other way round will be run through in a later loop iteration
+ if advtrains.yawToDirection(yaw, conn1, conn2) == conn1 then
+ -- does there exist a suitable double-connection rail?
+ --atdebug("Try double conn: ",conn1, conn2)
+ local node = g.double[conn1.."_"..conn2]
+ if node then
+ check_or_bend_rail(pos, conn1, pname, true)
+ check_or_bend_rail(pos, conn2, pname, true)
+ track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly
+ return true
end
end
end
end
end
- -- try single_conn
- if #p_rails > 0 then
- for ix, p_rail in ipairs(p_rails) do
- local sconn1 = tr.single_conn_1
- local sconn2 = tr.single_conn_2
- if not (advtrains.yawToDirection(yaw, p_rail, (p_rail+8)%16) == p_rail) then
- sconn1 = tr.single_conn_2
- sconn2 = tr.single_conn_1
- end
- if sconn1[p_rail] then
- local using = sconn1[p_rail]
- tp.bend_rail(p_railpos[p_rail], p_rail, nnpref)
- advtrains.ndb.swap_node(pos, using)
- local nname=using.name
- if minetest.registered_nodes[nname] and minetest.registered_nodes[nname].after_place_node then
- minetest.registered_nodes[nname].after_place_node(pos, placer, itemstack, pointed_thing)
- end
- return
- end
- if sconn2[p_rail] then
- local using = sconn2[p_rail]
- tp.bend_rail(p_railpos[p_rail], p_rail, nnpref)
- advtrains.ndb.swap_node(pos, using)
- local nname=using.name
- if minetest.registered_nodes[nname] and minetest.registered_nodes[nname].after_place_node then
- minetest.registered_nodes[nname].after_place_node(pos, placer, itemstack, pointed_thing)
- end
- return
- end
+ -- 3. try all possible one_endpoint connections
+ for k1, conn1 in ipairs(cand) do
+ -- select single1 or single2? depending on yaw
+ local single
+ if advtrains.yawToDirection(yaw, conn1, advtrains.oppd(conn1)) == conn1 then
+ single = g.single1
+ else
+ single = g.single2
+ end
+ --atdebug("Try single conn: ",conn1)
+ local node = single[conn1]
+ if node then
+ check_or_bend_rail(pos, conn1, pname, true)
+ track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly
+ return true
end
end
- --use default
- minetest.set_node(pos, {name=nnpref.."_"..tr.default})
- if minetest.registered_nodes[nnpref.."_"..tr.default] and minetest.registered_nodes[nnpref.."_"..tr.default].after_place_node then
- minetest.registered_nodes[nnpref.."_"..tr.default].after_place_node(pos, placer, itemstack, pointed_thing)
- end
+ -- 4. if nothing worked, set the default
+ local node = g.default
+ track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly
+ return true
end
-function tp.register_track_placer(nnprefix, imgprefix, dispname, def)
- minetest.register_craftitem(":"..nnprefix.."_placer",{
- description = dispname,
- inventory_image = imgprefix.."_placer.png",
- wield_image = imgprefix.."_placer.png",
- groups={advtrains_trackplacer=1, digtron_on_place=1},
- liquids_pointable = def.liquids_pointable,
- on_place = function(itemstack, placer, pointed_thing)
- local name = placer:get_player_name()
- if not name then
- return itemstack, false
- end
- if pointed_thing.type=="node" then
- local pos=pointed_thing.above
- local upos=vector.subtract(pointed_thing.above, {x=0, y=1, z=0})
- if not advtrains.check_track_protection(pos, name) then
- return itemstack, false
- end
- if minetest.registered_nodes[minetest.get_node(pos).name] and minetest.registered_nodes[minetest.get_node(pos).name].buildable_to then
- local s
- if def.suitable_substrate then
- s = def.suitable_substrate(upos)
- else
- s = minetest.registered_nodes[minetest.get_node(upos).name] and minetest.registered_nodes[minetest.get_node(upos).name].walkable
- end
- if s then
--- minetest.chat_send_all(nnprefix)
- local yaw = placer:get_look_horizontal()
- tp.placetrack(pos, nnprefix, placer, itemstack, pointed_thing, yaw)
- if not advtrains.is_creative(name) then
- itemstack:take_item()
- end
- end
- end
- end
- return itemstack, true
- end,
- })
-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 = S("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",
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
- local name = placer:get_player_name()
- if not name then
+ local name = placer:get_player_name()
+ if not name then
+ return
+ end
+ local has_aux1_down = placer:get_player_control().aux1
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.under
+ if not advtrains.check_track_protection(pos, name) then
return
end
- local has_aux1_down = placer:get_player_control().aux1
- if pointed_thing.type=="node" then
- local pos=pointed_thing.under
- if not advtrains.check_track_protection(pos, name) then
- return
- end
- local node=minetest.get_node(pos)
+ local node=minetest.get_node(pos)
- --if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
-
- local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
- --atdebug(node.name.."\npattern recognizes:"..nnprefix.." / "..suffix.." / "..rotation)
- --atdebug("nntab: ",tp.tracks[nnprefix])
- if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twrotate[suffix] then
- nnprefix, suffix=string.match(node.name, "^(.+)_([^_]+)$")
- rotation = ""
- if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twrotate[suffix] then
- minetest.chat_send_player(placer:get_player_name(), attrans("This node can't be rotated using the trackworker!"))
- return
- end
- end
-
- -- check if the node is modify-protected
- if advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then
- -- is a track, we can query
- local can_modify, reason = advtrains.can_dig_or_modify_track(pos)
- if not can_modify then
- local str = attrans("This track can not be rotated!")
- if reason then
- str = str .. " " .. reason
- end
- minetest.chat_send_player(placer:get_player_name(), str)
- return
+ -- New since 2.5: only the fields in the node definition are considered, no more hacky pattern matching on the nodename
+
+ local ndef = minetest.registered_nodes[node.name]
+
+ if not ndef.advtrains or not ndef.advtrains.trackworker_next_rot then
+ minetest.chat_send_player(placer:get_player_name(), S("This node can't be rotated using the trackworker!"))
+ return
+ end
+
+ -- check if the node is modify-protected
+ if advtrains.is_track(node.name) then
+ -- is a track, we can query
+ local can_modify, reason = advtrains.can_dig_or_modify_track(pos)
+ if not can_modify then
+ local str = S("This track can not be rotated!")
+ if reason then
+ str = str .. " " .. reason
end
- end
-
- if has_aux1_down then
- --feature: flip the node by 180°
- --i've always wanted this!
- advtrains.ndb.swap_node(pos, {name=node.name, param2=(node.param2+2)%4})
- return
- end
-
- local modext=tp.tracks[nnprefix].twrotate[suffix]
-
- if rotation==modext[#modext] then --increase param2
- advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..suffix..modext[1], param2=(node.param2+1)%4})
+ minetest.chat_send_player(placer:get_player_name(), str)
return
- else
- local modpos
- for k,v in pairs(modext) do
- if v==rotation then modpos=k end
- end
- if not modpos then
- minetest.chat_send_player(placer:get_player_name(), attrans("This node can't be rotated using the trackworker!"))
- return
- end
- advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..suffix..modext[modpos+1], param2=node.param2})
end
end
+
+ if has_aux1_down then
+ --feature: flip the node by 180°
+ --i've always wanted this!
+ advtrains.ndb.swap_node(pos, {name=node.name, param2=(node.param2+2)%4})
+ return
+ end
+
+ local new_node = {name = ndef.advtrains.trackworker_next_rot, param2 = node.param2}
+ if ndef.advtrains.trackworker_rot_incr_param2 then
+ new_node.param2 = ((node.param2 + 1) % 4)
+ end
+ advtrains.ndb.swap_node(pos, new_node)
+ end
end,
- on_use=function(itemstack, user, pointed_thing)
- local name = user:get_player_name()
- if not name then
- return
+ on_use=function(itemstack, player, pointed_thing)
+ local name = player:get_player_name()
+ if not name then
+ return
+ end
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.under
+ local node=minetest.get_node(pos)
+ if not advtrains.check_track_protection(pos, name) then
+ return
end
- if pointed_thing.type=="node" then
- local pos=pointed_thing.under
- local node=minetest.get_node(pos)
- if not advtrains.check_track_protection(pos, name) then
- return
- end
-
- --if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
- if advtrains.get_train_at_pos(pos) then return end
- local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
- --atdebug(node.name.."\npattern recognizes:"..nodeprefix.." / "..railtype.." / "..rotation)
- if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twcycle[suffix] then
- nnprefix, suffix=string.match(node.name, "^(.+)_([^_]+)$")
- rotation = ""
- if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twcycle[suffix] then
- minetest.chat_send_player(user:get_player_name(), attrans("This node can't be changed using the trackworker!"))
- return
- end
- end
-
- -- check if the node is modify-protected
- if advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then
- -- is a track, we can query
- local can_modify, reason = advtrains.can_dig_or_modify_track(pos)
- if not can_modify then
- local str = attrans("This track can not be changed!")
- if reason then
- str = str .. " " .. reason
- end
- minetest.chat_send_player(user:get_player_name(), str)
- return
+
+ -- New since 2.5: only the fields in the node definition are considered, no more hacky pattern matching on the nodename
+
+ local ndef = minetest.registered_nodes[node.name]
+
+ if not ndef.advtrains or not ndef.advtrains.trackworker_next_var then
+ minetest.chat_send_player(name, S("This node can't be changed using the trackworker!"))
+ return
+ end
+
+ -- check if the node is modify-protected
+ if advtrains.is_track(node.name) then
+ -- is a track, we can query
+ local can_modify, reason = advtrains.can_dig_or_modify_track(pos)
+ if not can_modify then
+ local str = S("This track can not be rotated!")
+ if reason then
+ str = str .. " " .. reason
end
+ minetest.chat_send_player(name, str)
+ return
end
-
- local nextsuffix=tp.tracks[nnprefix].twcycle[suffix]
- advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..nextsuffix..rotation, param2=node.param2})
-
- else
- atprint(name, dump(tp.tracks))
end
+
+ local new_node = {name = ndef.advtrains.trackworker_next_var, param2 = node.param2}
+ advtrains.ndb.swap_node(pos, new_node)
+ end
end,
})
diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua
index 261818e..e6d08a7 100644
--- a/advtrains/tracks.lua
+++ b/advtrains/tracks.lua
@@ -1,751 +1,275 @@
---advtrains by orwell96, see readme.txt
-
---dev-time settings:
---EDIT HERE
---If the old non-model rails on straight tracks should be replaced by the new...
---false: no
---true: yes
-advtrains.register_replacement_lbms=false
-
---[[TracksDefinition
-nodename_prefix
-texture_prefix
-description
-common={}
-straight={}
-straight45={}
-curve={}
-curve45={}
-lswitchst={}
-lswitchst45={}
-rswitchst={}
-rswitchst45={}
-lswitchcr={}
-lswitchcr45={}
-rswitchcr={}
-rswitchcr45={}
-vert1={
- --you'll probably want to override mesh here
-}
-vert2={
- --you'll probably want to override mesh here
-}
-]]--
-advtrains.all_tracktypes={}
-
---definition preparation
-local function conns(c1, c2, r1, r2) return {{c=c1, y=r1}, {c=c2, y=r2}} end
-local function conns3(c1, c2, c3, r1, r2, r3) return {{c=c1, y=r1}, {c=c2, y=r2}, {c=c3, y=r3}} end
-
-advtrains.ap={}
-advtrains.ap.t_30deg_flat={
- regstep=1,
- variant={
- st={
- conns = conns(0,8),
- desc = "straight",
- tpdouble = true,
- tpsingle = true,
- trackworker = "cr",
- },
- cr={
- conns = conns(0,7),
- desc = "curve",
- tpdouble = true,
- trackworker = "swlst",
- },
- swlst={
- conns = conns3(0,8,7),
- desc = "left switch (straight)",
- trackworker = "swrst",
- switchalt = "cr",
- switchmc = "on",
- switchst = "st",
- switchprefix = "swl",
- },
- swlcr={
- conns = conns3(0,7,8),
- desc = "left switch (curve)",
- trackworker = "swrcr",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- switchprefix = "swl",
- },
- swrst={
- conns = conns3(0,8,9),
- desc = "right switch (straight)",
- trackworker = "st",
- switchalt = "cr",
- switchmc = "on",
- switchst = "st",
- switchprefix = "swr",
- },
- swrcr={
- conns = conns3(0,9,8),
- desc = "right switch (curve)",
- trackworker = "st",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- switchprefix = "swr",
- },
- },
- regtp=true,
- tpdefault="st",
- trackworker={
- ["swrcr"]="st",
- ["swrst"]="st",
- ["cr"]="swlst",
- ["swlcr"]="swrcr",
- ["swlst"]="swrst",
- },
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_yturnout={
- regstep=1,
- variant={
- l={
- conns = conns3(0,7,9),
- desc = "Y-turnout (left)",
- switchalt = "r",
- switchmc = "off",
- switchst = "l",
- switchprefix = "",
- },
- r={
- conns = conns3(0,9,7),
- desc = "Y-turnout (right)",
- switchalt = "l",
- switchmc = "on",
- switchst = "r",
- switchprefix = "",
- }
- },
- regtp=true,
- tpdefault="l",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_s3way={
- regstep=1,
- variant={
- l={
- conns = { {c=0}, {c=7}, {c=8}, {c=9}, {c=0} },
- desc = "3-way turnout (left)",
- switchalt = "s",
- switchst="l",
- switchprefix = "",
- },
- s={
- conns = { {c=0}, {c=8}, {c=7}, {c=9}, {c=0} },
- desc = "3-way turnout (straight)",
- switchalt ="r",
- switchst = "s",
- switchprefix = "",
- },
- r={
- conns = { {c=0}, {c=9}, {c=8}, {c=7}, {c=0} },
- desc = "3-way turnout (right)",
- switchalt = "l",
- switchst="r",
- switchprefix = "",
- }
- },
- regtp=true,
- tpdefault="l",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_30deg_slope={
- regstep=1,
- variant={
- vst1={conns = conns(8,0,0,0.5), rail_y = 0.25, desc = "steep uphill 1/2", slope=true},
- vst2={conns = conns(8,0,0.5,1), rail_y = 0.75, desc = "steep uphill 2/2", slope=true},
- vst31={conns = conns(8,0,0,0.33), rail_y = 0.16, desc = "uphill 1/3", slope=true},
- vst32={conns = conns(8,0,0.33,0.66), rail_y = 0.5, desc = "uphill 2/3", slope=true},
- vst33={conns = conns(8,0,0.66,1), rail_y = 0.83, desc = "uphill 3/3", slope=true},
- },
- regsp=true,
- slopeplacer={
- [2]={"vst1", "vst2"},
- [3]={"vst31", "vst32", "vst33"},
- max=3,--highest entry
- },
- slopeplacer_45={
- [2]={"vst1_45", "vst2_45"},
- max=2,
- },
- rotation={"", "_30", "_45", "_60"},
- trackworker={},
- increativeinv={},
-}
-advtrains.ap.t_30deg_straightonly={
- regstep=1,
- variant={
- st={
- conns = conns(0,8),
- desc = "straight",
- tpdouble = true,
- tpsingle = true,
- trackworker = "st",
- },
- },
- regtp=true,
- tpdefault="st",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_30deg_straightonly_noplacer={
- regstep=1,
- variant={
- st={
- conns = conns(0,8),
- desc = "straight",
- tpdouble = true,
- tpsingle = true,
- trackworker = "st",
- },
- },
- tpdefault="st",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_45deg={
- regstep=2,
- variant={
- st={
- conns = conns(0,8),
- desc = "straight",
- tpdouble = true,
- tpsingle = true,
- trackworker = "cr",
- },
- cr={
- conns = conns(0,6),
- desc = "curve",
- tpdouble = true,
- trackworker = "swlst",
- },
- swlst={
- conns = conns3(0,8,6),
- desc = "left switch (straight)",
- trackworker = "swrst",
- switchalt = "cr",
- switchmc = "on",
- switchst = "st",
- },
- swlcr={
- conns = conns3(0,6,8),
- desc = "left switch (curve)",
- trackworker = "swrcr",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- },
- swrst={
- conns = conns3(0,8,10),
- desc = "right switch (straight)",
- trackworker = "st",
- switchalt = "cr",
- switchmc = "on",
- switchst = "st",
- },
- swrcr={
- conns = conns3(0,10,8),
- desc = "right switch (curve)",
- trackworker = "st",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- },
- },
- regtp=true,
- tpdefault="st",
- trackworker={
- ["swrcr"]="st",
- ["swrst"]="st",
- ["cr"]="swlst",
- ["swlcr"]="swrcr",
- ["swlst"]="swrst",
- },
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_perpcrossing={
- regstep = 1,
- variant={
- st={
- conns = { {c=0}, {c=8}, {c=4}, {c=12} },
- desc = "perpendicular crossing",
- tpdouble = true,
- tpsingle = true,
- trackworker = "st",
- },
- },
- regtp=true,
- tpdefault="st",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_90plusx_crossing={
- regstep = 1,
- variant={
- ["30l"]={
- conns = { {c=0}, {c=8}, {c=1}, {c=9} },
- desc = "30/90 degree crossing (left)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "45l"
- },
- ["45l"]={
- conns = { {c=0}, {c=8}, {c=2}, {c=10} },
- desc = "45/90 degree crossing (left)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "60l",
- },
- ["60l"]={
- conns = { {c=0}, {c=8}, {c=3}, {c=11}},
- desc = "60/90 degree crossing (left)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "60r",
- },
- ["60r"]={
- conns = { {c=0}, {c=8}, {c=5}, {c=13} },
- desc = "60/90 degree crossing (right)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "45r"
- },
- ["45r"]={
- conns = { {c=0}, {c=8}, {c=6}, {c=14} },
- desc = "45/90 degree crossing (right)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "30r",
- },
- ["30r"]={
- conns = { {c=0}, {c=8}, {c=7}, {c=15}},
- desc = "30/90 degree crossing (right)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "30l",
- },
- },
- regtp=true,
- tpdefault="30l",
- rotation={""},
- trackworker = {
- ["30l"] = "45l",
- ["45l"] = "60l",
- ["60l"] = "60r",
- ["60r"] = "45r",
- ["45r"] = "30r",
- ["30r"] = "30l",
- }
-}
-
-advtrains.ap.t_diagonalcrossing = {
- regstep=1,
- variant={
- ["30l45r"]={
- conns = {{c=1}, {c=9}, {c=6}, {c=14}},
- desc = "30left-45right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60l30l",
- },
- ["60l30l"]={
- conns = {{c=3}, {c=11}, {c=1}, {c=9}},
- desc = "30left-60right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60l45r"
- },
- ["60l45r"]={
- conns = {{c=3}, {c=11}, {c=6}, {c=14}},
- desc = "60left-45right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60l60r"
- },
- ["60l60r"]={
- conns = {{c=3}, {c=11}, {c=5}, {c=13}},
- desc = "60left-60right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60r45l",
- },
- --If 60l60r had a mirror image, it would be here, but it's symmetric.
- -- 60l60r is also equivalent to 30l30r but rotated 90 degrees.
- ["60r45l"]={
- conns = {{c=5}, {c=13}, {c=2}, {c=10}},
- desc = "60right-45left diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60r30r",
- },
- ["60r30r"]={
- conns = {{c=5}, {c=13}, {c=7}, {c=15}},
- desc = "60right-30right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="30r45l",
- },
- ["30r45l"]={
- conns = {{c=7}, {c=15}, {c=2}, {c=10}},
- desc = "30right-45left diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="30l45r",
- },
-
- },
- regtp=true,
- tpdefault="30l45r",
- rotation={""},
- trackworker = {
- ["30l45r"] = "60l30l",
- ["60l30l"] = "60l45r",
- ["60l45r"] = "60l60r",
- ["60l60r"] = "60r45l",
- ["60r45l"] = "60r30r",
- ["60r30r"] = "30r45l",
- ["30r45l"] = "30l45r",
- }
-}
-
-advtrains.trackpresets = advtrains.ap
-
---definition format: ([] optional)
---[[{
- nodename_prefix
- texture_prefix
- [shared_texture]
- models_prefix
- models_suffix (with dot)
- [shared_model]
- formats={
- st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
- (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
- }
- common={} change something on common rail appearance
-}
-[18.12.17] Note on new connection system:
-In order to support real rail crossing nodes and finally make the trackplacer respect switches, I changed the connection system.
-There can be a variable number of connections available. These are specified as tuples {c=<connection>, y=<rely>}
-The table "at_conns" consists of {<conn1>, <conn2>...}
-the "at_rail_y" property holds the value that was previously called "railheight"
-Depending on the number of connections:
-2 conns: regular rail
-3 conns: switch:
- - when train passes in at conn1, will move out of conn2
- - when train passes in at conn2 or conn3, will move out of conn1
-4 conns: cross (or cross switch, depending on arrangement of conns):
- - conn1 <> conn2
- - conn3 <> conn4
-]]
-
--- Notify the user if digging the rail is not allowed
-local function can_dig_callback(pos, player)
- local ok, reason = advtrains.can_dig_or_modify_track(pos)
- if not ok and player then
- minetest.chat_send_player(player:get_player_name(), attrans("This track can not be removed!") .. " " .. reason)
- end
- return ok
-end
-
-function advtrains.register_tracks(tracktype, def, preset)
- advtrains.trackplacer.register_tracktype(def.nodename_prefix, preset.tpdefault)
- if preset.regtp then
- advtrains.trackplacer.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description, def)
- end
- if preset.regsp then
- advtrains.slope.register_placer(def, preset)
- end
- for suffix, var in pairs(preset.variant) do
- for rotid, rotation in ipairs(preset.rotation) do
- if not def.formats[suffix] or def.formats[suffix][rotid] then
- local img_suffix = suffix..rotation
- local ndef = advtrains.merge_tables({
- description=def.description.."("..(var.desc or "any")..rotation..")",
- drawtype = "mesh",
- paramtype="light",
- paramtype2="facedir",
- walkable = false,
- selection_box = {
- type = "fixed",
- fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
- },
-
- mesh = def.shared_model or (def.models_prefix.."_"..img_suffix..def.models_suffix),
- tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture},
-
- groups = {
- attached_node = advtrains.IGNORE_WORLD and 0 or 1,
- advtrains_track=1,
- ["advtrains_track_"..tracktype]=1,
- save_in_at_nodedb=1,
- dig_immediate=2,
- not_in_creative_inventory=1,
- not_blocking_trains=1,
- },
-
- can_dig = can_dig_callback,
- after_dig_node=function(pos)
- advtrains.ndb.update(pos)
- end,
- after_place_node=function(pos)
- advtrains.ndb.update(pos)
- end,
- at_nnpref = def.nodename_prefix,
- at_suffix = suffix,
- at_rotation = rotation,
- at_rail_y = var.rail_y
- }, def.common or {})
-
- if preset.regtp then
- ndef.drop = def.nodename_prefix.."_placer"
- end
- if preset.regsp and var.slope then
- ndef.drop = def.nodename_prefix.."_slopeplacer"
- end
-
- --connections
- ndef.at_conns = advtrains.rotate_conn_by(var.conns, (rotid-1)*preset.regstep)
-
- local ndef_avt_table
-
- if var.switchalt and var.switchst then
- local switchfunc=function(pos, node, newstate)
- newstate = newstate or var.switchalt -- support for 3 (or more) state switches
- -- this code is only called from the internal setstate function, which
- -- ensures that it is safe to switch the turnout
- if newstate~=var.switchst then
- advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..(var.switchprefix or "")..newstate..rotation, param2=node.param2})
- advtrains.invalidate_all_paths(pos)
- end
- end
- ndef.on_rightclick = function(pos, node, player)
- if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
- advtrains.setstate(pos, nil, node)
- advtrains.log("Switch", player:get_player_name(), pos)
- end
- end
- if var.switchmc then
- ndef.mesecons = {effector = {
- ["action_"..var.switchmc] = function(pos, node)
- advtrains.setstate(pos, nil, node)
- end,
- rules=advtrains.meseconrules
- }}
- end
- ndef_avt_table = {
- getstate = var.switchst,
- setstate = switchfunc,
- }
- end
-
- local adef={}
- if def.get_additional_definiton then
- adef=def.get_additional_definiton(def, preset, suffix, rotation)
- end
- ndef = advtrains.merge_tables(ndef, adef)
-
- -- insert getstate/setstate functions after merging the additional definitions
- if ndef_avt_table then
- ndef.advtrains = advtrains.merge_tables(ndef.advtrains or {}, ndef_avt_table)
- end
-
- minetest.register_node(":"..def.nodename_prefix.."_"..suffix..rotation, ndef)
- --trackplacer
- if preset.regtp then
- local tpconns = {conn1=ndef.at_conns[1].c, conn2=ndef.at_conns[2].c}
- if var.tpdouble then
- advtrains.trackplacer.add_double_conn(def.nodename_prefix, suffix, rotation, tpconns)
- end
- if var.tpsingle then
- advtrains.trackplacer.add_single_conn(def.nodename_prefix, suffix, rotation, tpconns)
- end
- end
- advtrains.trackplacer.add_worked(def.nodename_prefix, suffix, rotation, var.trackworker)
- end
- end
- end
- advtrains.all_tracktypes[tracktype]=true
-end
-
-function advtrains.is_track_and_drives_on(nodename, drives_on_p)
- local drives_on = drives_on_p
- if not drives_on then drives_on = advtrains.all_tracktypes end
- local hasentry = false
- for _,_ in pairs(drives_on) do
- hasentry=true
- end
- if not hasentry then drives_on = advtrains.all_tracktypes end
-
- if not minetest.registered_nodes[nodename] then
- return false
- end
- local nodedef=minetest.registered_nodes[nodename]
- for k,v in pairs(drives_on) do
- if nodedef.groups["advtrains_track_"..k] then
- return true
- end
- end
- return false
-end
-
-function advtrains.get_track_connections(name, param2)
- local nodedef=minetest.registered_nodes[name]
- if not nodedef then atprint(" get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return nil end
- local noderot=param2
- if not param2 then noderot=0 end
- if noderot > 3 then atprint(" get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
-
- local tracktype
- for k,_ in pairs(nodedef.groups) do
- local tt=string.match(k, "^advtrains_track_(.+)$")
- if tt then
- tracktype=tt
- end
- end
- return advtrains.rotate_conn_by(nodedef.at_conns, noderot*AT_CMAX/4), (nodedef.at_rail_y or 0), tracktype
-end
-
--- Function called when a track is about to be dug or modified by the trackworker
--- Returns either true (ok) or false,"translated string describing reason why it isn't allowed"
-function advtrains.can_dig_or_modify_track(pos)
- if advtrains.get_train_at_pos(pos) then
- return false, attrans("Position is occupied by a train.")
- end
- -- interlocking: tcb, signal IP a.s.o.
- if advtrains.interlocking then
- -- TCB?
- if advtrains.interlocking.db.get_tcb(pos) then
- return false, attrans("There's a Track Circuit Break here.")
- end
- -- signal ip?
- if advtrains.interlocking.db.is_ip_at(pos, true) then -- is_ip_at with purge parameter
- return false, attrans("There's a Signal Influence Point here.")
- end
- end
- return true
-end
-
--- slope placer. Defined in register_tracks.
---crafted with rail and gravel
-local sl={}
-function sl.register_placer(def, preset)
- minetest.register_craftitem(":"..def.nodename_prefix.."_slopeplacer",{
- description = attrans("@1 Slope", def.description),
- inventory_image = def.texture_prefix.."_slopeplacer.png",
- wield_image = def.texture_prefix.."_slopeplacer.png",
- groups={},
- on_place = sl.create_slopeplacer_on_place(def, preset)
- })
-end
---(itemstack, placer, pointed_thing)
-function sl.create_slopeplacer_on_place(def, preset)
- return function(istack, player, pt)
- if not pt.type=="node" then
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
- return istack
- end
- local pos=pt.above
- if not pos then
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
- return istack
- end
- local node=minetest.get_node(pos)
- if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to then
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: space occupied!"))
- return istack
- end
- if not advtrains.check_track_protection(pos, player:get_player_name()) then
- minetest.record_protection_violation(pos, player:get_player_name())
- return istack
- end
- --determine player orientation (only horizontal component)
- --get_look_horizontal may not be available
- local yaw=player.get_look_horizontal and player:get_look_horizontal() or (player:get_look_yaw() - math.pi/2)
-
- --rounding unit vectors is a nice way for selecting 1 of 8 directions since sin(30°) is 0.5.
- local dirvec={x=math.floor(math.sin(-yaw)+0.5), y=0, z=math.floor(math.cos(-yaw)+0.5)}
- --translate to direction to look up inside the preset table
- local param2, rot45=({
- [-1]={
- [-1]=2,
- [0]=3,
- [1]=3,
- },
- [0]={
- [-1]=2,
- [1]=0,
- },
- [1]={
- [-1]=1,
- [0]=1,
- [1]=0,
- },
- })[dirvec.x][dirvec.z], dirvec.x~=0 and dirvec.z~=0
- local lookup=preset.slopeplacer
- if rot45 then lookup=preset.slopeplacer_45 end
-
- --go unitvector forward and look how far the next node is
- local step=1
- while step<=lookup.max do
- local node=minetest.get_node(vector.add(pos, dirvec))
- --next node solid?
- if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to or advtrains.is_protected(pos, player:get_player_name()) then
- --do slopes of this distance exist?
- if lookup[step] then
- if minetest.settings:get_bool("creative_mode") or istack:get_count()>=step then
- --start placing
- local placenodes=lookup[step]
- while step>0 do
- minetest.set_node(pos, {name=def.nodename_prefix.."_"..placenodes[step], param2=param2})
- if not minetest.settings:get_bool("creative_mode") then
- istack:take_item()
- end
- step=step-1
- pos=vector.subtract(pos, dirvec)
- end
- else
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: Not enough slope items left (@1 required)", step))
- end
- else
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: There's no slope of length @1",step))
- end
- return istack
- end
- step=step+1
- pos=vector.add(pos, dirvec)
- end
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: no supporting node at upper end."))
- return itemstack
- end
-end
-
-advtrains.slope=sl
-
---END code, BEGIN definition
---definition format: ([] optional)
---[[{
- nodename_prefix
- texture_prefix
- [shared_texture]
- models_prefix
- models_suffix (with dot)
- [shared_model]
- formats={
- st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
- (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
- }
- common={} change something on common rail appearance
-}]]
-
-
-
-
-
-
-
-
-
+-- tracks.lua
+-- rewritten with advtrains 2.5 according to new track registration system
+
+-- Get current translator
+local S = advtrains.translate
+
+--[[
+
+Tracks in advtrains are defined by the node definition. They must have at least 2 connections, but can have any number.
+Switchable nodes (turnouts, single/double-slip switches) are implemented by having a separate node (node name) for each of the possible states.
+
+ minetest.register_node(nodename, {
+ ... usual node definition ...
+ groups = {
+ advtrains_track = 1,
+ advtrains_track_<tracktype>=1
+ ^- these groups tell that the node is a track
+ not_blocking_trains=1,
+ ^- this group tells that the node should not block trains although it's walkable.
+ },
+
+ at_rail_y = 0,
+ ^- Height of this rail node (the y position of a wagon that stands centered on this rail)
+ at_conns = {
+ [1] = { c=0..15, y=0..1 },
+ [2] = { c=0..15, y=0..1 },
+ ( [3] = { c=0..15, y=0..1 }, )
+ ( [4] = { c=0..15, y=0..1 }, )
+ ( ... )
+ }
+ ^- Connections of this rail. There are two general cases:
+ a) SIMPLE TRACK - the track has exactly 2 connections, and does not feature a turnout, crossing or other contraption
+ For simple tracks, except for the at_conns table no further setup needs to be specified. A train entering on conn 1 will go out at conn 2 and vice versa.
+ A track with only one connection defined is not permitted.
+ b) COMPOUND TRACK - the track has more than 2 connections
+ This will be used for turnouts and crossings. Tracks with more than 2 conns MUST define 'at_conn_map'.
+ Switchable nodes, whose state can be changed (e.g. turnouts) MUST define a 'state_map' within the advtrains table as well.
+ This differs from the behavior up until 2.4.2, where the conn mapping was fixed.
+ ^- Connection definition:
+ - c is the direction of the connection (0-16). For the mapping to world directions see helpers.lua.
+ - Connections will be auto-rotated with param2 of the node (horizontal, param2 values 0-3 only)
+ - y is the height of the connection (rail will only connect when this matches)
+ ^- The index of a connection inside the conns table (1, 2, 3, ...) is referred throughout advtrains code as 'connid'
+ ^- IMPORTANT: For switchable nodes (any kind of turnout), it is crucial that for all of the node's variants the at_conns table stays the same. See below.
+
+ at_conn_map = {
+ [1] = 2,
+ [2] = 1,
+ [3] = 1,
+ }
+ ^- Connection map of this rail. It specifies when a train enters the track on connid X, on which connid it will leave
+ This field MUST be specified when the number of connections in at_conns is greater than 2
+ This field may, and obviously will, vary between nodes for switchable nodes.
+
+ can_dig = advtrains.track_can_dig_callback
+ after_dig_node = advtrains.track_update_callback
+ after_place_node = advtrains.track_update_callback
+ ^- the code in these 3 default minetest API functions is required for advtrains to work, however you can add your own code
+
+ on_rightclick = advtrains.state_node_on_rightclick_callback
+ ^- Must be added if the node is a turnout and if it should be switched by right-click. It will cause the turnout to be switched to next_state.
+
+ advtrains = {
+ on_train_enter=function(pos, train_id, train, index) end
+ ^- called when a train enters the rail
+ on_train_leave=function(pos, train_id, train, index) end
+ ^- called when a train leaves the rail
+
+ -- The following function is only in effect when interlocking is enabled:
+ on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
+ ^- called when a train is approaching this position, called exactly once for every path recalculation (which can happen at any time)
+ ^- This is called so that if the train would start braking now, it would come to halt about(wide approx) 5 nodes before the rail.
+ ^- has_entered: when true, the train is already standing on this node with its front tip, and the enter callback has already been called.
+ Possibly, some actions need not to be taken in this case. Only set if it's the very first node the train is standing on.
+ ^- lzbdata should be ignored and nothing should be assigned to it
+
+ -- The following information is required if the node is a turnout (e.g. can be switched into different positions)
+ node_state = "st"
+ ^- The name of the state this node represents
+ ^- Conventions for this field are as follows:
+ - Two-way straight/turn switches: 'st'=straight branch, 'cr'=diverting/turn branch
+ - 3-way turnouts, Y-turnouts: 'l'=left branch, 's'=straight branch, 'r'=right branch
+
+ node_next_state = "cr"
+ ^- The name of the state that the turnout should be switched to when it is right-clicked
+
+ node_fallback_state = "st"
+ ^- The name of the state that the turnout should "fall back" to when it is released
+ Only used by the interlocking system, when a route on the node is released it is switched back to this state.
+
+ node_state_map = {
+ ["st"] = "<node name of the st variant>",
+ ["cr"] = "<node name of the cr variant>",
+ ... etc ...
+ }
+ ^- Map of state name to the appropriate node name that should be set by advtrains when a switch is requested
+ Note that for all of those nodes, the at_conns table must be identical (however the conn_map will vary)
+
+ node_on_switch_state = function(pos, node, oldstate, newstate)
+ ^- Called when the node state is switched by advtrains, after the node replacement has commenced.
+
+ Turnout switching can happen programmatically via advtrains.setstate(pos, state), via user right_click or via the interlocking system.
+ In no other situation is it permissible to exchange track nodes in-place, unless both at_conns and at_conn_map stay identical.
+
+ Note that the fields node_state, node_next_state and node_state_map completely replace the getstate/setstate functions.
+ There must be a one-to-one mapping between states and node names and no function can be defined for state switching.
+ This principle enables the seamless working of the interlocking autorouter and reduces failure points.
+ The node_state_* system can also be used as drop-in replacement for the passive-API-enabled nodes (andrews-cross, mesecon_switch etc.)
+ The advtrains API functions advtrains.getstate() and advtrains.setstate() remain the programmatic access points, but will now utilize the new state system.
+
+
+ trackworker_next_rot = <nodename of next rotation step>,
+ ^- if set, right-click with trackworker will set this node
+ trackworker_rot_incr_param2 = true
+ ^- if set, trackworker will increase node param2 on rightclick
+
+ trackworker_next_var = <nodename of next variant>
+ ^- if set, left-click with trackworker will set this node
+ }
+ })
+
+]]--
+
+-- This file provides some utilities to register tracks, but tries to not get into the way too much
+
+
+function advtrains.track_can_dig_callback(pos, player)
+ local ok, reason = advtrains.can_dig_or_modify_track(pos)
+ if not ok and player then
+ minetest.chat_send_player(player:get_player_name(), S("This track can not be removed!") .. " " .. reason)
+ end
+ return ok
+end
+
+function advtrains.track_update_callback(pos)
+ advtrains.ndb.update(pos)
+end
+
+function advtrains.state_node_on_rightclick_callback(pos, node, player)
+ if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ local ndef = minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.node_next_state then
+ advtrains.setstate(pos, ndef.advtrains.node_next_state, node)
+ advtrains.log("Switch", player:get_player_name(), pos)
+ end
+ end
+end
+
+-- advtrains.register_node_4rot(name, nodedef)
+-- Registers four rotations for the node defined by nodedef (0°, 30°, 45° and 60°; the 4 90°-steps are already handled by the param2, resulting in 16 directions total).
+-- You must provide the definition for the base node, and certain fields are altered automatically for the 3 additional rotations:
+-- name: appends the suffix "_30", "_45" or "_60"
+-- description: appends the rotation (human-readable) in parenthesis
+-- tiles_prefix: if defined, "tiles" field will be set as prefix..rotationExtension..".png"
+-- mesh_prefix, mesh_suffix: if defined, "mesh" field will be set as prefix..rotationExtension..suffix
+-- at_conns: are rotated according to the node rotation
+-- node_state_map, trackworker_next_var: appends the suffix appropriately.
+-- groups: applies save_in_at_nodedb and not_blocking_trains groups if not already present
+-- The nodes are registered in the trackworker to be rotated with right-click.
+-- definition_mangling_function is an optional parameter. For each of the 4 rotations, it gets passed the modified node definition and may perform final modifications to it.
+-- signature: function definition_mangling_function(name, nodedef, rotationIndex, rotationSuffix)
+-- Example usage: define the setstate function of turnouts (if that is not done via the "automatic" way of state_node_map)
+local rotations = {
+ {i = 0, s = "", h = " (0)", n = "_30"},
+ {i = 1, s = "_30", h = " (30)", n = "_45"},
+ {i = 2, s = "_45", h = " (45)", n = "_60"},
+ {i = 3, s = "_60", h = " (60)", n = ""},
+}
+function advtrains.register_node_4rot(ori_name, ori_ndef, definition_mangling_function)
+ for _, rot in ipairs(rotations) do
+ local ndef = table.copy(ori_ndef)
+ if ori_ndef.advtrains then
+ -- make sure advtrains table is deep-copied because we may need to replace node_state_map
+ ndef.advtrains = table.copy(ori_ndef.advtrains)
+ else
+ ndef.advtrains = {} -- we need the table later for trackworker
+ end
+ -- Perform the name mangling
+ local suffix = rot.s
+ local name = ori_name..suffix
+ ndef.description = ori_ndef.description .. rot.h
+ if ori_ndef.tiles_prefix then
+ ndef.tiles = { ori_ndef.tiles_prefix .. suffix .. ".png" }
+ end
+ if ori_ndef.mesh_prefix then
+ ndef.mesh = ori_ndef.mesh_prefix .. suffix .. ori_ndef.mesh_suffix
+ end
+ -- rotate connections
+ if ori_ndef.at_conns then
+ ndef.at_conns = advtrains.rotate_conn_by(ori_ndef.at_conns, rot.i)
+ end
+ -- update node state map if present
+ if ori_ndef.advtrains then
+ if ori_ndef.advtrains.node_state_map then
+ local new_nsm = {}
+ for state, nname in pairs(ori_ndef.advtrains.node_state_map) do
+ new_nsm[state] = nname .. suffix
+ end
+ ndef.advtrains.node_state_map = new_nsm
+ end
+ if ori_ndef.advtrains.trackworker_next_var then
+ ndef.advtrains.trackworker_next_var = ori_ndef.advtrains.trackworker_next_var .. suffix
+ end
+ -- apply trackworker rot field
+ ndef.advtrains.trackworker_next_rot = ori_name .. rot.n
+ ndef.advtrains.trackworker_rot_incr_param2 = (rot.n=="")
+ end
+ -- apply groups
+ ndef.groups.save_in_at_nodedb = 1
+ ndef.groups.not_blocking_trains = 1
+
+ -- give the definition mangling function an option to do some adjustments
+ if definition_mangling_function then
+ definition_mangling_function(name, ndef, rot.i, suffix)
+ end
+
+ -- register node
+ minetest.register_node(":"..name, ndef)
+
+ -- if this has the track_place_group set, register as a candidate for the track_place_group
+ if ndef.advtrains.track_place_group then
+ advtrains.trackplacer.register_candidate(ndef.advtrains.track_place_group, name, ndef, ndef.advtrains.track_place_single, true)
+ end
+ end
+end
+
+-- track-related helper functions
+
+function advtrains.is_track(nodename)
+ if not minetest.registered_nodes[nodename] then
+ return false
+ end
+ local nodedef=minetest.registered_nodes[nodename]
+ if nodedef and nodedef.groups.advtrains_track then
+ return true
+ end
+ return false
+end
+
+-- returns the connection tables of the track with given node details
+-- returns: conns table, railheight, conn_map table
+function advtrains.get_track_connections(name, param2)
+ local nodedef=minetest.registered_nodes[name]
+ if not nodedef then atprint(" get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return nil end
+ local noderot=param2
+ if not param2 then noderot=0 end
+ if noderot > 3 then atprint(" get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
+
+ if not nodedef.at_conns then
+ return nil
+ end
+ --atdebug("Track connections of ",name,param2,":",nodedef.at_conns)
+ return advtrains.rotate_conn_by(nodedef.at_conns, noderot*AT_CMAX/4), (nodedef.at_rail_y or 0), nodedef.at_conn_map
+end
+
+-- Function called when a track is about to be dug or modified by the trackworker
+-- Returns either true (ok) or false,"translated string describing reason why it isn't allowed"
+-- Impl Note: possibly duplicate code in "self contained TCB" - see interlocking/tcb_ts_ui.lua!
+function advtrains.can_dig_or_modify_track(pos)
+ if advtrains.get_train_at_pos(pos) then
+ return false, S("Position is occupied by a train.")
+ end
+ -- interlocking: tcb, signal IP a.s.o.
+ if advtrains.interlocking then
+ -- TCB?
+ if advtrains.interlocking.db.get_tcb(pos) then
+ return false, S("There's a Track Circuit Break here.")
+ end
+ -- signal ip?
+ if advtrains.interlocking.db.is_ip_at(pos, true) then -- is_ip_at with purge parameter
+ return false, S("There's a Signal Influence Point here.")
+ end
+ end
+ return true
+end
diff --git a/advtrains/trainhud.lua b/advtrains/trainhud.lua
index 6e69455..eeebff0 100644
--- a/advtrains/trainhud.lua
+++ b/advtrains/trainhud.lua
@@ -1,5 +1,10 @@
--trainhud.lua: holds all the code for train controlling
+-- Get current translator
+local S = advtrains.translate
+
+local T = advtrains.texture
+
advtrains.hud = {}
advtrains.hhud = {}
@@ -8,6 +13,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)
@@ -46,6 +53,10 @@ function advtrains.on_control_change(pc, train, flip)
train.ctrl_user = 1
act=true
end
+ if train.ars_disable and pc.up then
+ -- up clears ars disable flag in any situation
+ train.ars_disable = nil
+ end
-- If atc command set, only "Jump" key can clear command. To prevent accidental control.
if train.tarvelocity or train.atc_command then
return
@@ -101,19 +112,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 +135,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 +165,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,136 +202,108 @@ 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 lzb = train.lzb
- if lzb and lzb.checkpoints then
- local oc = lzb.checkpoints
- for i = 1, #oc do
- local spd = oc[i].speed
- 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)
- end
- local floor = math.floor
- local dist = 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)
+ local lzbdisp = {c = "darkslategray", d = 888}
+ if lzb and lzb.checkpoints and lzb.checkpoints[1] then
+ local cp = lzb.checkpoints[1]
+ if advtrains.interlocking and cp.udata and cp.udata.signal_pos then
+ local sigd = advtrains.interlocking.db.get_sigd_for_signal(cp.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
- break
end
end
+ local spd = cp.speed
+ local c, arrow
+ if not spd or spd == -1 then
+ c = "lime"
+ elseif spd == 0 then
+ c = "red"
+ elseif not res or spd <= res then
+ c = "orange"
+ arrow = true
+ elseif spd <= max then
+ c = "yellow"
+ arrow = true
+ else
+ c = "cyan"
+ arrow = true
+ end
+ if arrow then
+ hud:add(1+spd*11, 50, T"advtrains_hud_arrow.png":multiply"red")
+ end
+ local dist = math.floor(((cp.index or train.index)-train.index))
+ dist = math.max(0, math.min(999, dist))
+ lzbdisp = {c = c, d = dist}
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, S("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 "")
+ local delay_str = ""
+ if train.atc_delay and train.atc_delay >= 0 then
+ delay_str = advtrains.abs_ceil(train.atc_delay).."s "
+ end
+ if train.atc_wait_finish then
+ delay_str = delay_str.."[W] "
+ end
+ if train.atc_wait_autocouple then
+ delay_str = delay_str.."[Cpl] "
+ end
+ if train.atc_wait_signal then
+ delay_str = delay_str.."[G] "
+ end
+ table.insert(st, ("ATC: %s%s"):format(delay_str, 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 4650f9e..c0ac179 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -1,6 +1,9 @@
--trainlogic.lua
--controls train entities stuff about connecting/disconnecting/colliding trains and other things
+-- Get current translator
+local S = advtrains.translate
+
local setting_overrun_mode = minetest.settings:get("advtrains_overrun_mode")
local benchmark=false
@@ -142,10 +145,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.train_id==id then
- wagon:reattach_all()
- end
+ for _, wagon in advtrains.wagon_entity_pairs_in_train(id) do
+ wagon:reattach_all()
end
end
end)
@@ -157,12 +158,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
@@ -251,6 +250,11 @@ local callbacks_update, run_callbacks_update = mkcallback("update")
local callbacks_create, run_callbacks_create = mkcallback("create")
local callbacks_remove, run_callbacks_remove = mkcallback("remove")
+-- required to call from couple.lua
+function advtrains.update_train_start_and_end(train)
+ recalc_end_index(train)
+ run_callbacks_update(train.id, train)
+end
-- train_ensure_init: responsible for creating a state that we can work on, after one of the following events has happened:
-- - the train's path got cleared
@@ -262,6 +266,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
@@ -272,10 +280,12 @@ function advtrains.train_ensure_init(id, train)
assertdef(train, "velocity", 0)
--assertdef(train, "tarvelocity", 0)
assertdef(train, "acceleration", 0)
- assertdef(train, "id", id)
-
+ if train.id ~= id then
+ train.id = id
+ end
+ assertdef(train, "path_ori_cp", {})
- if not train.drives_on or not train.max_speed then
+ if not train.max_speed then
--atprint("in ensure_init: missing properties, updating!")
advtrains.update_trainpart_properties(id)
end
@@ -387,7 +397,7 @@ function advtrains.train_step_b(id, train, dtime)
-- interlocking speed restriction
elseif train.speed_restriction then
--atprint("in train_step_b: applying interlocking speed restriction",train.speed_restriction)
- sit_v_cap = train.speed_restriction
+ sit_v_cap = math.min(sit_v_cap or math.huge, train.speed_restriction)
end
--apply off-track handling:
@@ -420,7 +430,8 @@ function advtrains.train_step_b(id, train, dtime)
if train.atc_command then
if (not train.atc_delay or train.atc_delay<=0)
and not train.atc_wait_finish
- and not train.atc_wait_autocouple then
+ and not train.atc_wait_autocouple
+ and not train.atc_wait_signal then
advtrains.atc.execute_atc_command(id, train)
elseif train.atc_delay and train.atc_delay > 0 then
train.atc_delay=train.atc_delay-dtime
@@ -449,6 +460,15 @@ function advtrains.train_step_b(id, train, dtime)
train.atc_wait_finish=nil
end
end
+ -- clear atc_wait_signal immediately when the next LZB checkpoint is not a "stop"
+ -- (but make sure lzb is initialized, otherwise wait for it)
+ if train.atc_wait_signal and train.lzb then
+ local first_ckp = train.lzb.checkpoints and train.lzb.checkpoints[1]
+ -- no checkpoint exists, or it has a speed of either nil or >0
+ if not first_ckp or first_ckp.speed ~= 0 then
+ train.atc_wait_signal = nil
+ end
+ end
if train.tarvelocity and train.tarvelocity>v0 then
--atprint("in train_step_b: applying ATC ACCEL", train.tarvelocity)
@@ -611,7 +631,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
@@ -623,7 +643,7 @@ function advtrains.train_step_b(id, train, dtime)
local ocn = otrn.path_cn[ob_idx]
local ocp = otrn.path_cp[ob_idx]
- local target_is_inside, ref_index, facing
+ local target_is_inside, ref_index, facing, same_dir
if base_cn == ocn then
-- same direction
@@ -641,9 +661,10 @@ 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 then
+ 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
-- ON_TRACK COLLISION IS HAPPENING
-- the actual collision is handled in train_step_c, so set appropriate signal variables
train.ontrack_collision_info = {
@@ -794,9 +815,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
@@ -833,7 +857,7 @@ local callbacks_leave_node, run_callbacks_leave_node = mknodecallback("leave")
-- Node callback for approaching
-- Might be called multiple times, whenever path is recalculated. Also called for the first node the train is standing on, then has_entered is true.
-- signature is function(pos, id, train, index, has_entered, lzbdata)
--- has_entered: true if the "enter" callback has already been executed for this train in this location
+-- has_entered: Provided for legacy reasons. Always false. (used to signify that the enter callback has already been called, at a time where enter was still called at .5 index)
-- lzbdata: arbitrary data (shared between all callbacks), deleted when LZB is restarted.
-- These callbacks are called in order of distance as train progresses along tracks, so lzbdata can be used to
-- keep track of a train's state once it passes this point
@@ -852,13 +876,10 @@ local function tnc_call_enter_callback(pos, train_id, train, index)
run_callbacks_enter_node(pos, train_id, train, index)
-- check for split points
- if mregnode and mregnode.at_conns and #mregnode.at_conns == 3 and train.path_cp[index] == 3 then
- -- train came from connection 3 of a switch, so it split points.
- if not train.points_split then
- train.points_split = {}
- end
- train.points_split[advtrains.encode_pos(pos)] = true
- --atdebug(train_id,"split points at",pos)
+ if mregnode and mregnode.at_conn_map then
+ -- If this node has >2 conns (and a connmap), remember the connection where we came from to handle split points
+ --atdebug("Train",train_id,"at",pos,"saving turnout origin CP",train.path_cp[index],"for path item",index)
+ train.path_ori_cp[advtrains.encode_pos(pos)] = train.path_cp[index]
end
end
local function tnc_call_leave_callback(pos, train_id, train, index)
@@ -873,23 +894,16 @@ local function tnc_call_leave_callback(pos, train_id, train, index)
run_callbacks_leave_node(pos, train_id, train, index)
-- split points do not matter anymore. clear them
- if train.points_split then
- if train.points_split[advtrains.encode_pos(pos)] then
- train.points_split[advtrains.encode_pos(pos)] = nil
- --atdebug(train_id,"has passed split points at",pos)
- end
- -- any entries left?
- for _,_ in pairs(train.points_split) do
- return
- end
- train.points_split = nil
+ if mregnode and mregnode.at_conn_map then
+ -- If this node has >2 conns (and a connmap), remember the connection where we came from to handle split points
+ --atdebug("Train",train_id,"at",pos,"removing turnout origin CP for path item",index," because train has left it")
+ train.path_ori_cp[advtrains.encode_pos(pos)] = nil
end
- -- WARNING possibly unreachable place!
end
function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbdata)
--atdebug("tnc approach",pos,train_id, lzbdata)
- local has_entered = atround(train.index) == index
+ local has_entered = false -- 2024-11-25: has_entered is now always false, because enter point = LZB hit point!
local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
local mregnode=minetest.registered_nodes[node.name]
@@ -902,18 +916,21 @@ function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbda
end
-- === te callback definition for tnc node callbacks ===
+-- Change 2024-11-25: Enter node happens when index surpasses whole number (i.e. at center of rail)
+-- Instead of atround (prev behavior) nouw use floor and ceil (note this also fixes issues where index was exactly on .0)
+local atceil = math.ceil
advtrains.te_register_on_new_path(function(id, train)
train.tnc = {
- old_index = atround(train.index),
- old_end_index = atround(train.end_index),
+ old_index = atfloor(train.index),
+ old_end_index = atceil(train.end_index),
}
--atdebug(id,"tnc init",train.index,train.end_index)
end)
advtrains.te_register_on_update(function(id, train)
- local new_index = atround(train.index)
- local new_end_index = atround(train.end_index)
+ local new_index = atfloor(train.index)
+ local new_end_index = atceil(train.end_index)
local old_index = train.tnc.old_index
local old_end_index = train.tnc.old_end_index
while old_index < new_index do
@@ -931,8 +948,8 @@ advtrains.te_register_on_update(function(id, train)
end)
advtrains.te_register_on_create(function(id, train)
- local index = atround(train.index)
- local end_index = atround(train.end_index)
+ local index = atfloor(train.index)
+ local end_index = atceil(train.end_index)
while end_index <= index do
local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,end_index))
tnc_call_enter_callback(pos, id, train, end_index)
@@ -942,8 +959,8 @@ advtrains.te_register_on_create(function(id, train)
end)
advtrains.te_register_on_remove(function(id, train)
- local index = atround(train.index)
- local end_index = atround(train.end_index)
+ local index = atfloor(train.index)
+ local end_index = atceil(train.end_index)
while end_index <= index do
local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,end_index))
tnc_call_leave_callback(pos, id, train, end_index)
@@ -1025,10 +1042,9 @@ end
-- Note: safe_decouple_wagon() has been moved to wagons.lua
--- this function sets wagon's pos_in_train(parts) properties and train's max_speed and drives_on (and more)
+-- this function sets wagon's pos_in_train(parts) properties and train's max_speed (and more)
function advtrains.update_trainpart_properties(train_id, invert_flipstate)
local train=advtrains.trains[train_id]
- train.drives_on=advtrains.merge_tables(advtrains.all_tracktypes)
--FIX: deep-copy the table!!!
train.max_speed=20
train.extent_h = 0;
@@ -1044,7 +1060,16 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate)
if data then
local wagon = advtrains.wagon_prototypes[data.type or data.entity_name]
if not wagon then
- atwarn("Wagon '",data.type,"' couldn't be found. Please check that all required modules are loaded!")
+ local ent = advtrains.wagon_objects[w_id]
+ local pdesc
+ if ent and ent:get_pos() then
+ pdesc = "at " .. minetest.pos_to_string(ent:get_pos())
+ elseif train.last_pos then
+ pdesc = "near " .. minetest.pos_to_string(train.last_pos)
+ else
+ pdesc = "at an unknown location"
+ end
+ atwarn(string.format("Wagon %q %s could not be found. Please check that all required modules are loaded!", data.type, pdesc))
wagon = advtrains.wagon_prototypes["advtrains:wagon_placeholder"]
end
@@ -1061,13 +1086,6 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate)
end
rel_pos=rel_pos+wagon.wagon_span
- if wagon.drives_on then
- for k,_ in pairs(train.drives_on) do
- if not wagon.drives_on[k] then
- train.drives_on[k]=nil
- end
- end
- end
train.max_speed=math.min(train.max_speed, wagon.max_speed)
train.extent_h = math.max(train.extent_h, wagon.extent_h or 1);
end
@@ -1099,8 +1117,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
@@ -1113,6 +1140,7 @@ end
function advtrains.split_train_at_index(train, index)
-- this function splits a train at index, creating a new train from the back part of the train.
+ --atdebug("split_train_at_index invoked on",train.id,"index",index)
local train_id=train.id
if index > #train.trainparts then
@@ -1135,6 +1163,7 @@ function advtrains.split_train_at_index(train, index)
local p_index=advtrains.path_get_index_by_offset(train, train.index, - data.pos_in_train + wagon.wagon_span)
local pos, connid, frac = advtrains.path_getrestore(train, p_index)
+ --atdebug("new train position p_index",p_index,"pos",pos,"connid",connid,"frac",frac)
local tp = {}
for k,v in ipairs(train.trainparts) do
if k >= index then
@@ -1144,12 +1173,14 @@ function advtrains.split_train_at_index(train, index)
end
advtrains.update_trainpart_properties(train_id)
recalc_end_index(train)
+ --atdebug("old train index",train.index,"end_index",train.end_index)
run_callbacks_update(train_id, train)
--create subtrain
local newtrain_id=advtrains.create_new_train_at(pos, connid, frac, tp)
local newtrain=advtrains.trains[newtrain_id]
-
+ --atdebug("new train created with ID",newtrain_id,"index",newtrain.index,"end_index",newtrain.end_index)
+
newtrain.velocity=train.velocity
-- copy various properties from the old to the new train
newtrain.door_open = train.door_open
@@ -1158,10 +1189,13 @@ function advtrains.split_train_at_index(train, index)
newtrain.line = train.line
newtrain.routingcode = train.routingcode
newtrain.speed_restriction = train.speed_restriction
+ newtrain.speed_restrictions_t = table.copy(train.speed_restrictions_t or {main=train.speed_restriction})
newtrain.is_shunt = train.is_shunt
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
@@ -1174,8 +1208,24 @@ function advtrains.invert_train(train_id)
return
end
+ -- Before flipping the train, we must check if there are any points on the path
+ -- which will become split on rotating, and store their cn (which will become the cp)
+ local ori_cp_after_flip = {}
+ for index = atround(train.end_index),atround(train.index) do
+ local pos = advtrains.path_get(train, index)
+ local ok, conns, railheight, connmap = advtrains.get_rail_info_at(pos)
+ if ok and connmap then
+ --atdebug("Reversing Train",train.id," ori_cp Checks: at",pos,"saving turnout origin CP",train.path_cn[index],"for path item",index)
+ ori_cp_after_flip[advtrains.encode_pos(pos)] = train.path_cn[index]
+ end
+ end
+
+ -- Actual rotation happens here! This sets the path restore position to the end of the train, inverting the connid.
advtrains.path_setrestore(train, true)
+ -- clear the origin cp list because it is now invalid, and replace it by what we built prior.
+ train.path_ori_cp = ori_cp_after_flip
+
-- rotate some other stuff
if train.door_open then
train.door_open = - train.door_open
@@ -1195,15 +1245,14 @@ 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
train.is_shunt = true
- train.speed_restriction = advtrains.SHUNT_SPEED_MAX
+ advtrains.speed.set_restriction(train, "main", advtrains.SHUNT_SPEED_MAX)
else
train.is_shunt = false
- train.speed_restriction = nil
+ advtrains.speed.set_restriction(train, "main", -1)
end
end
@@ -1221,7 +1270,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
@@ -1234,7 +1283,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..f32dd03
--- /dev/null
+++ b/advtrains/wagonprop_tool.lua
@@ -0,0 +1,47 @@
+
+-- Get current translator
+local S = advtrains.translate
+
+minetest.register_craftitem("advtrains:wagon_prop_tool",{ --craftitem because it does nothing on its own
+ description = S("Wagon Properties Tool\nPunch a wagon to view and edit the Wagon Properties"),
+ short_description = S("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, S("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
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index 4093f06..52ce577 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -7,13 +7,22 @@
-- An entity is ONLY spawned by update_trainpart_properties when it finds it useful.
-- Only data that are only important to the entity itself are stored in the luaentity
+-- Get current translator
+local S = advtrains.translate
+
-- TP delay when getting off wagon
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
@@ -148,12 +157,12 @@ function wagon:ensure_init()
end
end
if not self.noninitticks then
- atwarn("wagon",self.id,"uninitialized init=",self.initialized)
+ atwarn("Wagon",self.id,"Uninitialized init=",self.initialized)
self.noninitticks=0
end
self.noninitticks=self.noninitticks+1
if self.noninitticks>20 then
- atwarn("wagon",self.id,"uninitialized, removing")
+ atwarn("Wagon",self.id,S("Uninitialized, removing"))
self:destroy()
else
self.object:set_velocity({x=0,y=0,z=0})
@@ -176,7 +185,7 @@ function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direct
return
end
if data.owner and puncher:get_player_name()~=data.owner and (not minetest.check_player_privs(puncher, {train_admin = true })) then
- minetest.chat_send_player(puncher:get_player_name(), attrans("This wagon is owned by @1, you can't destroy it.", data.owner));
+ minetest.chat_send_player(puncher:get_player_name(), S("This wagon is owned by @1, you can't destroy it.", data.owner));
return
end
@@ -195,25 +204,25 @@ function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direct
if self.has_inventory then
local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})
if not inv then -- inventory is not initialized when wagon was never loaded - should never happen
- atwarn("Destroying wagon with inventory, but inventory is not found? Shouldn't happen!")
+ atwarn(S("Destroying wagon with inventory, but inventory is not found? Shouldn't happen!"))
return
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(), S("The wagon's inventory is not empty."));
return
end
end
end
if #(self:train().trainparts)>1 then
- minetest.chat_send_player(puncher:get_player_name(), attrans("Wagon needs to be decoupled from other wagons in order to destroy it."));
+ minetest.chat_send_player(puncher:get_player_name(), S("Wagon needs to be decoupled from other wagons in order to destroy it."));
return
end
local pc=puncher:get_player_control()
if not pc.sneak then
- minetest.chat_send_player(puncher:get_player_name(), attrans("Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon."))
+ minetest.chat_send_player(puncher:get_player_name(), S("Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon."))
return
end
@@ -234,7 +243,7 @@ function wagon:destroy()
if self.id then
local data = advtrains.wagons[self.id]
if not data then
- atwarn("wagon:destroy(): data is not set!")
+ atwarn(" wagon:destroy(): data is not set!")
return
end
@@ -280,7 +289,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
@@ -361,7 +370,16 @@ function wagon:on_step(dtime)
--show off-track information in outside text instead of notifying the whole server about this
if train.off_track then
- outside = outside .."\n!!! Train off track !!!"
+ outside = outside .."\n"..S("!!! 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 .."\n"..S("Liquid: ")..data.techage_liquid.name..", "..data.techage_liquid.amount..S(" units")
+ else
+ outside = outside .."\n"..S("Liquid: empty")
+ end
end
if self.infotext_cache~=outside then
@@ -413,34 +431,67 @@ function wagon:on_step(dtime)
end
-- Calculate new position, yaw and direction vector
+ -- note: "index" is needed to be the center index, required by door code
local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
- local pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
- local vdir = vector.normalize(vector.subtract(npos2, npos))
+ local pos, yaw, npos, npos2, vdir
+
+ -- use new position logic?
+ if self.wheel_positions then
+ -- request two positions, calculate difference and yaw from this
+ -- depending on flipstate, need to invert wheel pos indices -> wheelpos * fct
+ local index1 = advtrains.path_get_index_by_offset(train, index, self.wheel_positions[1] * fct)
+ local index2 = advtrains.path_get_index_by_offset(train, index, self.wheel_positions[2] * fct)
+ local pos1 = advtrains.path_get_interpolated(train, index1)
+ local pos2 = advtrains.path_get_interpolated(train, index2)
+ npos = advtrains.path_get(train, atfloor(index)) -- need npos just for node loaded check
+ -- calculate center of 2 positions and vdir vector
+ -- if wheel positions are asymmetric, needs to weight by the difference!
+ local fact = self.wheel_positions[1] / (self.wheel_positions[1]-self.wheel_positions[2])
+ pos = {x=pos1.x-(pos1.x-pos2.x)*fact, y=pos1.y-(pos1.y-pos2.y)*fact, z=pos1.z-(pos1.z-pos2.z)*fact}
+ if data.wagon_flipped then
+ vdir = vector.normalize(vector.subtract(pos2, pos1))
+ else
+ vdir = vector.normalize(vector.subtract(pos1, pos2))
+ end
+ yaw = math.atan2(-vdir.x, vdir.z)
+ else
+ --old position logic (for small wagons): use center index and just get position
+ pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
+ vdir = vector.normalize(vector.subtract(npos2, npos))
+ end
--automatic get_on
--needs to know index and path
- if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
+ if train.velocity==0 and self.door_entry and train.door_open and train.door_open~=0 then
--using the mapping created by the trainlogic globalstep
+ local platform_offset = math.floor(self.wagon_width / 2)
for i, ino in ipairs(self.door_entry) do
--fct is the flipstate flag from door animation above
local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)
local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
- local add = { x = (ix2.z-ix1.z)*train.door_open, y = 0, z = (ix1.x-ix2.x)*train.door_open }
- local pts1=vector.round(vector.add(ix1, add))
- local pts2=vector.round(vector.add(ix2, add))
- if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then
- local ckpts={
- pts1,
- pts2,
- vector.add(pts1, {x=0, y=1, z=0}),
- vector.add(pts2, {x=0, y=1, z=0}),
- }
- for _,ckpos in ipairs(ckpts) do
- local cpp=minetest.pos_to_string(ckpos)
- if advtrains.playersbypts[cpp] then
- self:on_rightclick(advtrains.playersbypts[cpp])
+ local add = {
+ x = atround((ix2.z-ix1.z)*train.door_open),
+ y = 0,
+ z = atround((ix1.x-ix2.x)*train.door_open)
+ }
+ for offset = (platform_offset == 0 and 0 or 1), platform_offset do
+ local scaled_add = vector.multiply(add, offset)
+ local pts1=vector.add(ix1, scaled_add)
+ local pts2=vector.add(ix2, scaled_add)
+ if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then
+ local ckpts={
+ pts1,
+ pts2,
+ vector.add(pts1, {x=0, y=1, z=0}),
+ vector.add(pts2, {x=0, y=1, z=0}),
+ }
+ for _,ckpos in ipairs(ckpts) do
+ local cpp=minetest.pos_to_string(ckpos)
+ if advtrains.playersbypts[cpp] then
+ self:on_rightclick(advtrains.playersbypts[cpp])
+ end
end
end
end
@@ -470,28 +521,32 @@ function wagon:on_step(dtime)
end
end
- --DisCouple
+ -- Spawn discouple object when train stands, in all other cases remove it.
-- FIX: Need to do this after the yaw calculation
- if is_in_loaded_area and data.pos_in_trainparts and data.pos_in_trainparts>1 then
- if train.velocity==0 then
- if not self.discouple or not self.discouple.object:get_yaw() then
- atprint(self.id,"trying to spawn discouple")
- local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
- local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
- if object then
- local le=object:get_luaentity()
- le.wagon=self
- --box is hidden when attached, so unuseful.
- --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
- self.discouple=le
- end
- end
- else
- if self.discouple and self.discouple.object:get_yaw() then
- self.discouple.object:remove()
- atprint(self.id," removing discouple")
+ if train.velocity==0 and is_in_loaded_area and data.pos_in_trainparts and data.pos_in_trainparts>1 then
+ if not self.discouple or not self.discouple.object:get_yaw() then
+ atprint(self.id,"trying to spawn discouple")
+ local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
+ local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
+ if object then
+ local le=object:get_luaentity()
+ le.wagon=self
+ --box is hidden when attached, so unuseful.
+ --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
+ self.discouple=le
end
end
+ else
+ if self.discouple and self.discouple.object:get_yaw() then
+ self.discouple.object:remove()
+ atprint(self.id," removing discouple")
+ end
+ end
+
+ -- object yaw (corrected by flipstate)
+ local oyaw = yaw
+ if data.wagon_flipped then
+ oyaw = yaw + math.pi
end
--FIX: use index of the wagon, not of the train.
@@ -500,10 +555,6 @@ function wagon:on_step(dtime)
local velocityvec = vector.multiply(vdir, velocity)
local accelerationvec = vector.multiply(vdir, acceleration)
- if data.wagon_flipped then
- yaw=yaw+math.pi
- end
-
-- this timer runs off every 2 seconds.
self.updatepct_timer=(self.updatepct_timer or 0)-dtime
local updatepct_timer_elapsed = self.updatepct_timer<=0
@@ -540,19 +591,19 @@ function wagon:on_step(dtime)
or not vector.equals(velocityvec, self.old_velocity_vector)
or not self.old_acceleration_vector
or not vector.equals(accelerationvec, self.old_acceleration_vector)
- or self.old_yaw~=yaw
+ or self.old_yaw~=oyaw
or updatepct_timer_elapsed then--only send update packet if something changed
self.object:set_pos(pos)
self.object:set_velocity(velocityvec)
self.object:set_acceleration(accelerationvec)
- if #self.seats > 0 and self.old_yaw ~= yaw then
+ if #self.seats > 0 and self.old_yaw ~= oyaw then
if not self.player_yaw then
self.player_yaw = {}
end
if not self.old_yaw then
- self.old_yaw=yaw
+ self.old_yaw=oyaw
end
for _,name in pairs(data.seatp) do
local p = minetest.get_player_by_name(name)
@@ -562,11 +613,11 @@ function wagon:on_step(dtime)
self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
end
-- set player looking direction using calculated offset
- p:set_look_horizontal((self.player_yaw[name] or 0)+yaw)
+ p:set_look_horizontal((self.player_yaw[name] or 0)+oyaw)
end
end
self.turning = true
- elseif self.old_yaw == yaw then
+ elseif self.old_yaw == oyaw then
-- train is no longer turning
self.turning = false
end
@@ -576,9 +627,9 @@ function wagon:on_step(dtime)
if data.wagon_flipped then
pitch = -pitch
end
- self.object:set_rotation({x=pitch, y=yaw, z=0})
+ self.object:set_rotation({x=pitch, y=oyaw, z=0})
else
- self.object:set_yaw(yaw)
+ self.object:set_yaw(oyaw)
end
if self.update_animation then
@@ -597,7 +648,7 @@ function wagon:on_step(dtime)
self.old_velocity_vector=velocityvec
self.old_velocity = train.velocity
self.old_acceleration_vector=accelerationvec
- self.old_yaw=yaw
+ self.old_yaw=oyaw
atprintbm("wagon step", t)
end
@@ -621,21 +672,21 @@ function wagon:on_rightclick(clicker)
end
end
if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- poss[#poss+1]={name=attrans("Show Inventory"), key="inv"}
+ poss[#poss+1]={name=S("Show Inventory"), key="inv"}
end
if self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- poss[#poss+1]={name=attrans("Onboard Computer"), key="bordcom"}
+ poss[#poss+1]={name=S("Onboard Computer"), key="bordcom"}
end
if data.owner==pname then
- poss[#poss+1]={name=attrans("Wagon properties"), key="prop"}
+ poss[#poss+1]={name=S("Wagon properties"), key="prop"}
end
if not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0 then
- poss[#poss+1]={name=attrans("Get off"), key="off"}
+ poss[#poss+1]={name=S("Get off"), key="off"}
else
if clicker:get_player_control().sneak then
- poss[#poss+1]={name=attrans("Get off (forced)"), key="off"}
+ poss[#poss+1]={name=S("Get off (forced)"), key="off"}
else
- poss[#poss+1]={name=attrans("(Doors closed)"), key="dcwarn"}
+ poss[#poss+1]={name=S("(Doors closed)"), key="dcwarn"}
end
end
if #poss==0 then
@@ -645,7 +696,7 @@ function wagon:on_rightclick(clicker)
else
local form = "size[5,"..1+(#poss).."]"
for pos,ent in ipairs(poss) do
- form = form .. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"
+ form = form.. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"
end
minetest.show_formspec(pname, "advtrains_seating_"..self.id, form)
end
@@ -664,7 +715,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, S("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
@@ -675,16 +726,16 @@ function wagon:on_rightclick(clicker)
self:get_on(clicker, seatid)
return
else
- rsn="Wagon is full."
+ rsn=S("This wagon is full.")
end
else
- rsn="Doors are closed! (try holding sneak key!)"
+ rsn=S("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 S("You can't get on this wagon."))
else
self:show_get_on_form(pname)
end
@@ -780,8 +831,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
@@ -810,7 +861,7 @@ function wagon:show_get_on_form(pname)
end
return
end
- local form, comma="size[5,8]label[0.5,0.5;"..attrans("Select seat:").."]textlist[0.5,1;4,6;seat;", ""
+ local form, comma="size[5,8]label[0.5,0.5;"..S("Select seat:").."]textlist[0.5,1;4,6;seat;", ""
for seatno, seattbl in ipairs(self.seats) do
local addtext, colorcode="", ""
if data.seatp and data.seatp[seatno] then
@@ -822,7 +873,7 @@ function wagon:show_get_on_form(pname)
end
form=form..";0,false]"
if self.has_inventory and self.get_inventory_formspec then
- form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]"
+ form=form.."button_exit[1,7;3,1;inv;"..S("Show Inventory").."]"
end
minetest.show_formspec(pname, "advtrains_geton_"..self.id, form)
end
@@ -834,31 +885,32 @@ function wagon:show_wagon_properties(pname)
]]
local data = advtrains.wagons[self.id]
local form="size[5,5]"
- 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 "").."]"
+ form=form.."label[0.2,0;"..S("This Wagon ID")..": "..self.id.." ("..data.owner..")]"
+ form = form.."field[0.5,1;4.5,1;whitelist;"..S("Allow these players to access your wagon:")..";"..minetest.formspec_escape(data.whitelist or "").."]"
+ form = form.."field[0.5,2;4.5,1;roadnumber;"..S("Wagon road number:")..";"..minetest.formspec_escape(data.roadnumber or "").."]"
local fc = ""
if data.fc then
fc = table.concat(data.fc, "!")
end
- form = form .. "field[0.5,3;4.5,1;fc;Freight Code:;"..fc.."]"
+ form = form.. "field[0.5,3;4.5,1;fc;"..S("Freight Code:")..";"..fc.."]"
if data.fc then
if not data.fcind then data.fcind = 1 end
if data.fcind > 1 then
- form=form.."button[0.5,3.5;1,1;fcp;prev FC]"
+ form=form.."button[0.5,3.5;1,1;fcp;"..S("Prev FC").."]"
end
- form=form.."label[1.5,3.5;Current FC:]"
+ form=form.."label[1.5,3.5;"..S("Current FC: ").."]"
local cur = data.fc[data.fcind] or ""
form=form.."label[1.5,3.75;"..minetest.formspec_escape(cur).."]"
- form=form.."button[3.5,3.5;1,1;fcn;next FC]"
+ form=form.."button[3.5,3.5;1,1;fcn;"..S("Next FC:").."]"
end
- form=form.."button_exit[0.5,4.5;4,1;save;"..attrans("Save wagon properties").."]"
+ form=form.."button_exit[0.5,4.5;4,1;save;"..S("Save wagon properties").."]"
minetest.show_formspec(pname, "advtrains_prop_"..self.id, form)
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()
@@ -937,29 +989,30 @@ function wagon:show_bordcom(pname)
local linhei
local form = "size[11,9]label[0.5,0;AdvTrains Boardcom v0.1]"
- 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 "")).."]"
- form=form.."field[7.5,3.25;3,1;routingcode;"..attrans("Routingcode")..";"..(minetest.formspec_escape(train.routingcode or "")).."]"
+ form=form.."textarea[7.5,0.05;10,1;;"..S("Train ID")..": "..(minetest.formspec_escape(train.id or ""))..";]"
+ form=form.."textarea[0.5,1.5;7,1;text_outside;"..S("Text displayed outside on train")..";"..(minetest.formspec_escape(train.text_outside or "")).."]"
+ form=form.."textarea[0.5,3;7,1;text_inside;"..S("Text displayed inside train")..";"..(minetest.formspec_escape(train.text_inside or "")).."]"
+ form=form.."field[7.5,1.75;3,1;line;"..S("Line")..";"..(minetest.formspec_escape(train.line or "")).."]"
+ form=form.."field[7.5,3.25;3,1;routingcode;"..S("Routingcode")..";"..(minetest.formspec_escape(train.routingcode or "")).."]"
--row 5 : train overview and autocoupling
if train.velocity==0 then
- form=form.."label[0.5,4;Train overview /coupling control:]"
+ form=form.."label[0.5,4;"..S("Train overview /coupling control:").."])"
linhei=5
local pre_own, pre_wl, owns_any = nil, nil, minetest.check_player_privs(pname, "train_admin")
for i, tpid in ipairs(train.trainparts) do
local ent = advtrains.wagons[tpid]
if ent then
local roadnumber = ent.roadnumber or ""
- form = form .. string.format("button[%d,%d;%d,%d;%s;%s]", i, linhei, 1, 0.2, "wgprp"..i, roadnumber)
+ form = form.. string.format("button[%d,%d;%d,%d;%s;%s]", i, linhei, 1, 0.2, "wgprp"..i, roadnumber)
local ename = ent.type
- form = form .. "item_image["..i..","..(linhei+0.5)..";1,1;"..ename.."]"
+ form = form.. "item_image["..i..","..(linhei+0.5)..";1,1;"..ename.."]"
if i~=1 then
if checklock(pname, ent.owner, pre_own, ent.whitelist, pre_wl) then
- form = form .. "image_button["..(i-0.5)..","..(linhei+1.5)..";1,1;advtrains_discouple.png;dcpl_"..i..";]"
+ form = form.. "image_button["..(i-0.5)..","..(linhei+1.5)..";1,1;advtrains_discouple.png;dcpl_"..i..";]"
end
end
if i == data.pos_in_trainparts then
- form = form .. "box["..(i-0.1)..","..(linhei+0.4)..";1,1;green]"
+ form = form.. "box["..(i-0.1)..","..(linhei+0.4)..";1,1;green]"
end
pre_own = ent.owner
pre_wl = ent.whitelist
@@ -968,24 +1021,24 @@ function wagon:show_bordcom(pname)
end
if train.movedir==1 then
- form = form .. "label["..(#train.trainparts+1)..","..(linhei)..";-->]"
+ form = form.. "label["..(#train.trainparts+1)..","..(linhei)..";-->]"
else
- form = form .. "label[0.5,"..(linhei)..";<--]"
+ form = form.. "label[0.5,"..(linhei)..";<--]"
end
--check cpl_eid_front and _back of train
local couple_front = checkcouple(train.cpl_front)
local couple_back = checkcouple(train.cpl_back)
if couple_front then
- form = form .. "image_button[0.5,"..(linhei+1)..";1,1;advtrains_couple.png;cpl_f;]"
+ form = form.. "image_button[0.5,"..(linhei+1)..";1,1;advtrains_couple.png;cpl_f;]"
end
if couple_back then
- form = form .. "image_button["..(#train.trainparts+0.5)..","..(linhei+1)..";1,1;advtrains_couple.png;cpl_b;]"
+ form = form.. "image_button["..(#train.trainparts+0.5)..","..(linhei+1)..";1,1;advtrains_couple.png;cpl_b;]"
end
else
- form=form.."label[0.5,4.5;Train overview / coupling control is only shown when the train stands.]"
+ form=form.."label[0.5,4.5;"..S("Train overview / coupling control is only shown when the train stands.").."]"
end
- form = form .. "button[0.5,8;3,1;save;Save]"
+ form = form.. "button[0.5,8;3,1;save;"..S("Save").."]"
-- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
-- from inside the train
@@ -995,14 +1048,14 @@ function wagon:show_bordcom(pname)
local oci = train.lzb.checkpoints[i]
if oci.udata and oci.udata.signal_pos then
if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then
- form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]"
+ form = form.. "button[4.5,8;5,1;ilrs;"..S("Remote Routesetting").."]"
break
end
end
i=i+1
end
if train.ars_disable then
- form = form .. "button[4.5,7;5,1;ilarsenable;Clear 'Disable ARS' flag]"
+ form = form.. "button[4.5,7;5,1;ilarsenable;"..S("Clear 'Disable ARS' flag").."]"
end
end
@@ -1054,12 +1107,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
@@ -1105,44 +1157,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
@@ -1164,29 +1220,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
@@ -1213,7 +1272,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, S("Doors are closed. Use Sneak+rightclick to ignore the closed doors and get off."))
end
if fields.off then
self:get_off(no)
@@ -1222,10 +1281,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, S("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
@@ -1242,7 +1301,7 @@ end
function advtrains.safe_decouple_wagon(w_id, pname, try_run)
if not minetest.check_player_privs(pname, "train_operator") then
- minetest.chat_send_player(pname, "Missing train_operator privilege")
+ minetest.chat_send_player(pname, S("Missing train_operator privilege"))
return false
end
local data = advtrains.wagons[w_id]
@@ -1260,7 +1319,7 @@ function advtrains.safe_decouple_wagon(w_id, pname, try_run)
end
if not checklock(pname, data.owner, owdata.owner, data.whitelist, owdata.whitelist) then
- minetest.chat_send_player(pname, "Not allowed to do this.")
+ minetest.chat_send_player(pname, S("Not allowed to do this."))
return false
end
@@ -1283,11 +1342,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
- atwarn("Unable to load wagon type",wt,", using placeholder")
- wt="advtrains:wagon_placeholder"
+ local rt, proto = advtrains.resolve_wagon_alias(wt)
+ if not rt then
+ --atwarn(S("Unable to load wagon type"),wt,S(", using placeholder"))
+ rt = "advtrains:wagon_placeholder"
+ proto = advtrains.wagon_prototypes[rt]
end
- return wt, advtrains.wagon_prototypes[wt]
+ 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
+ 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)
@@ -1300,7 +1381,7 @@ function advtrains.standard_inventory_formspec(self, pname, invname)
local r = "size[8,11]"..
"list["..invname..";box;0,0;8,3;]"
if data.owner==pname then
- r = r .. "button_exit[0,9;4,1;prop;"..attrans("Wagon properties").."]"
+ r = r .. "button_exit[0,9;4,1;prop;"..S("Wagon properties").."]"
end
r = r .. "list[current_player;main;0,5;8,4;]"..
"listring[]"
@@ -1316,47 +1397,66 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
minetest.register_entity(":"..sysname,prototype)
advtrains.wagon_prototypes[sysname] = prototype
+ --group classification to make recipe searching easier
+ local wagon_groups = { not_in_creative_inventory = nincreative and 1 or 0}
+ if prototype.is_locomotive then wagon_groups['at_loco'] = 1 end
+ if prototype.seat_groups then
+ if prototype.seat_groups.dstand then wagon_groups['at_control'] = 1 end
+ if prototype.seat_groups.pass then wagon_groups['at_pax'] = 1 end
+ end
+ if prototype.has_inventory then wagon_groups['at_freight'] = 1 end
+
minetest.register_craftitem(":"..sysname, {
description = desc,
inventory_image = inv_img,
wield_image = inv_img,
stack_max = 1,
- groups = { not_in_creative_inventory = nincreative and 1 or 0},
-
+ 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_and_drives_on(nodename, prototype.drives_on)) then
+ if(not advtrains.is_track(nodename)) then
atprint("no track here, not placing.")
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 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, prototype.drives_on)
+ 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!")
+ minetest.chat_send_player(pname, S("The track you are trying to place the wagon on is not long enough!"))
return
end
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()
@@ -1373,7 +1473,6 @@ advtrains.register_wagon("advtrains:wagon_placeholder", {
collisionbox = {-0.3,-0.3,-0.3, 0.3,0.3,0.3},
visual_size = {x=0.7, y=0.7},
initial_sprite_basepos = {x=0, y=0},
- drives_on = advtrains.all_tracktypes,
max_speed = 5,
seats = {
},
@@ -1382,5 +1481,119 @@ advtrains.register_wagon("advtrains:wagon_placeholder", {
assign_to_seat_group = {},
wagon_span=1,
drops={},
-}, "Wagon placeholder", "advtrains_wagon_placeholder.png", true)
+}, S("Wagon placeholder"), "advtrains_wagon_placeholder.png", true)
+
+
+
+-- Helper function to retrieve the wagon at a certain position in a train, given its train ID and the desired index within that train's path
+--
+-- Returns: wagon_num, wagon_id, wagon_data, offset_from_center
+-- wagon_num: The n'th wagon in the train (index into "trainparts" table)
+-- wagon_id: The wagon ID. Obtain wagon data from advtrains.wagons[wagon_id], and subsequently the wagon prototype via advtrains.get_wagon_prototype(data)
+-- offset_from_center: The offset (an absolute distance value) from the center point of the wagon. Positive is towards the end of the train, negative towards the start. (note that this is inverse to the counting direction of the index!)
+--
+--[[ To get the wagon standing at a certain world position, you first need to retrieve the index via the occupation table, as follows:
+ local trains = advtrains.occ.get_trains_at(pos)
+ for train_id, index in pairs(trains) do
+ local wagon_num, wagon_id, wagon_data, offset_from_center = advtrains.get_wagon_at_index(train_id, index)
+ if wagon_num then
+ ...
+ end
+ end
+]]--
+function advtrains.get_wagon_at_index(train_id, w_index)
+ local train = advtrains.trains[train_id]
+ if not train then error("Passed train id "..train_id.." doesnt exist") end
+ -- ensure init - always required
+ advtrains.train_ensure_init(train_id, train)
+ -- Use path dist to determine the offset from the start of the train
+ local dstart = advtrains.path_get_path_dist_fractional(train, train.index)
+ local dtarget = advtrains.path_get_path_dist_fractional(train, w_index)
+ local dist_from_start = dstart - dtarget -- NOTE: dist_from_start is supposed to be positive, but dtarget will be smaller than dstart
+ -- if dist_from_start is <0, we are outside of train
+ if dist_from_start < 0 then
+ return nil
+ end
+ -- scan over wagons to see if dist_from_start falls into its window
+ local start_pos = 0
+ local center_pos
+ local end_pos
+ local i = 1
+ while train.trainparts[i] do
+ local w_id = train.trainparts[i]
+ -- get wagon prototype to retrieve wagon span
+ local wdata = advtrains.wagons[w_id]
+ if wdata then
+ local wtype, wproto = advtrains.get_wagon_prototype(wdata)
+ local wagon_span = wproto.wagon_span
+ -- determine center and end pos
+ center_pos = start_pos + wagon_span
+ end_pos = center_pos + wagon_span
+ if start_pos <= dist_from_start and dist_from_start < end_pos then
+ -- Found the correct wagon in the train!
+ local offset_from_center = dist_from_start - center_pos
+ return i, w_id, wdata, offset_from_center
+ end
+ -- go on
+ start_pos = end_pos
+ else
+ error("Wagon "..w_id.." from train "..train_id.." doesnt exist!")
+ end
+ i = i + 1
+ end
+ -- nothing found, dist must be further back
+ return nil
+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
+
+minetest.register_chatcommand("at_chown", {
+ params = "<wagon_id> <player_name>",
+ description = "Change the owner of an advtrains wagon",
+ privs = {train_admin=true},
+ func = function(name, param)
+ local params = string.split(param," ")
+ local wid = params[1]
+ local new_owner = params[2]
+ if not wid then return false end --no params added
+ --player name checks
+ if not new_owner then return false, S("Please specify a player name to transfer ownership to.") end --no player name argument
+ if not core.player_exists(new_owner) then return false, S("That player does not exist!") end --is a valid player
+ --wagon id checks
+ if not wid:match("%d%d%d%d%d%d") then return false, S("Not a valid wagon id.") end -- invalid wagon id
+ local w_data = advtrains.wagons[wid]
+ if not w_data then return false, S("That wagon does not exist!") end
+ -- actually chown the wagon
+ local curr_owner = w_data.owner
+ w_data.owner = new_owner
+ advtrains.wagons[wid] = w_data
+ advtrains.log("Chown", name, core.get_player_by_name(name):get_pos(), "wid="..wid..", from="..curr_owner..", to="..new_owner)
+
+ if name ~= new_owner then
+ core.chat_send_player(new_owner, S("You have been given ownership of wagon @1", wid))
+ end
+ return true, S("Wagon @1 ownership changed from @2 to @3", wid, curr_owner, new_owner)
+ end
+})