diff options
Diffstat (limited to 'advtrains')
44 files changed, 7411 insertions, 1941 deletions
diff --git a/advtrains/api_doc.txt b/advtrains/api_doc.txt index 5668ba3..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 = { diff --git a/advtrains/atc.lua b/advtrains/atc.lua index c1ff218..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 @@ -94,6 +97,7 @@ function atc.train_reset_command(train, keep_tarvel) 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 @@ -106,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 @@ -128,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) @@ -177,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 @@ -233,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, @@ -245,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 @@ -278,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) @@ -319,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) @@ -375,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 b6a445e..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 @@ -184,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 @@ -231,10 +248,12 @@ function advtrains.couple_trains(init_train, invert_init_train, stat_train, stat -- sanity check, prevent coupling if train would be longer than 20 after coupling local tot_len = init_wagoncnt + stat_wagoncnt if tot_len > advtrains.TRAIN_MAX_WAGONS then - atwarn("Cannot couple",stat_train.id,"and",init_train.id,"- train would have length",tot_len,"which is above the limit of",advtrains.TRAIN_MAX_WAGONS) + 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 for i=1,stat_wagoncnt do @@ -319,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 @@ -335,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 -- @@ -437,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 @@ -484,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 @@ -523,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 e598216..2236cba 100644 --- a/advtrains/debugitems.lua +++ b/advtrains/debugitems.lua @@ -81,3 +81,59 @@ minetest.register_tool("advtrains:wagonpos_tester", 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/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 cc8f8d1..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={}} @@ -53,6 +50,17 @@ advtrains.TRAIN_MAX_WAGONS = 20 -- ========================================================================== +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 @@ -70,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 @@ -81,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 @@ -181,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 = @@ -202,19 +208,22 @@ 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") @@ -232,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 @@ -336,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 @@ -401,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 @@ -414,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 == @@ -473,12 +495,13 @@ advtrains.avt_save = function(remove_players_from_wagons) "atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open", "text_outside", "text_inside", "line", "routingcode", "il_sections", "speed_restriction", "speed_restrictions_t", "is_shunt", - "points_split", "autocouple", "atc_wait_autocouple", "ars_disable", + "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 @@ -559,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") @@ -662,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 @@ -691,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 @@ -703,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, @@ -715,45 +738,60 @@ 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, 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 - return true, "Train "..param.." is at "..minetest.pos_to_string(train.last_pos) + 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, }) @@ -761,10 +799,10 @@ minetest.register_chatcommand("at_disable_step", minetest.register_chatcommand("at_status", { params = "", - description = "Print advtrains status info", + description = S("Print advtrains status info"), privs = {train_operator = true}, func = function(name, param) - return true, advtrains.print_concat_table({"Advtrains Status: no_action",no_action,"slowdown",advtrains.global_slowdown,"(log",math.log(advtrains.global_slowdown),")"}) + return true, S("Advtrains Status: no_action @1 slowdown @2 (log @3)", no_action, advtrains.global_slowdown, math.log(advtrains.global_slowdown)) 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 64e4553..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, } @@ -264,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 41ac089..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 diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua index 7fce312..20a986e 100644 --- a/advtrains/occupation.lua +++ b/advtrains/occupation.lua @@ -89,8 +89,8 @@ function o.set_item(train_id, pos, idx) assert(idx) local i = 1 while t[i] do - if t[i]==train_id and t[i+1]==index then - break + if t[i]==train_id and t[i+1]==idx then + return end i = i + 2 end @@ -159,10 +159,8 @@ function o.reverse_lookup(ppos) local r = {} local i = 1 while t[i] do - if t[i]~=train_id then - if not r[t[i]] then r[t[i]] = {} end - table.insert(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 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 7676947..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 + _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 @@ -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 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 b26c950..198507a 100644 --- a/advtrains/signals.lua +++ b/advtrains/signals.lua @@ -1,17 +1,20 @@ --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 @@ -26,22 +29,21 @@ return { } end -local suppasp = { - main = {0, -1}, - 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/spec/poconvert_spec.lua b/advtrains/spec/poconvert_spec.lua new file mode 100644 index 0000000..51f33e7 --- /dev/null +++ b/advtrains/spec/poconvert_spec.lua @@ -0,0 +1,70 @@ +package.path = "../?.lua;" .. package.path +advtrains = {} +_G.advtrains = advtrains +local poconvert = require("poconvert") + +describe("PO file converter", function() + it("should convert PO files", function() + assert.equals([[ +# textdomain: foo +foo=bar +baz= +#@=wh\at\\@n=@=w\as\\@n +multiline@nstrings=multiline@nresult +with context?=oder doch nicht]], poconvert.from_string("foo", [[ +msgid "" +msgstr "whatever metadata" + +msgid "foo" +msgstr "bar" + +msgid "baz" +msgstr "" + +#, fuzzy +msgid "=wh\\at\\\\\n" +msgstr "=w\\as\\\\\n" + +msgid "multi" +"line\n" +"strings" +msgstr "multi" +"line\n" +"result" + +msgctxt "i18n context" +msgid "with context?" +msgstr "oder doch nicht"]])) + end) + it("should reject invalid tokens", function() + assert.has.errors(function() + poconvert.from_string("", [[ +foo "" +bar ""]]) + end, "Invalid token: foo") + end) + it("should reject entries without a msgstr", function() + assert.has.errors(function() + poconvert.from_string("", [[msgid "foo"]]) + end, "Missing translated string") + end) + it("should reject entries without a msgid", function() + assert.has.errors(function() + poconvert.from_string("", [[msgstr "foo"]]) + end, "Missing untranslated string") + end) + it("should reject entries with improperly enclosed strings", function() + assert.has.errors(function() + poconvert.from_string("", [[ +msgid "foo" +msgstr "bar \]]) + end, "String extends beyond the end of input") + end) + it("should reject incomplete input", function() + assert.has.errors(function() + poconvert.from_string("", [[ +msgid "foo" +msgstr]]) + end, "No string provided for msgstr") + end) +end) diff --git a/advtrains/spec/texture_spec.lua b/advtrains/spec/texture_spec.lua new file mode 100644 index 0000000..2e3bd5d --- /dev/null +++ b/advtrains/spec/texture_spec.lua @@ -0,0 +1,19 @@ +package.path = "../?.lua;" .. package.path +local T = require "texture" + +describe("Texture creation", function() + it("works", function() + assert.same("^.png", tostring(T.raw"^.png")) + assert.same("foo\\:bar.png", tostring(T"foo:bar.png")) + end) +end) + +describe("Texture modifiers", function() + it("work", function() + assert.same("x^[colorize:c", tostring(T"x":colorize"c")) + assert.same("x^[colorize:c:alpha", tostring(T"x":colorize("c", "alpha"))) + assert.same("x^[multiply:c", tostring(T"x":multiply"c")) + assert.same("x^[resize:2x3", tostring(T"x":resize(2, 3))) + assert.same("x^[transformI", tostring(T"x":transform"I")) + end) +end) diff --git a/advtrains/spec/wagons_spec.lua b/advtrains/spec/wagons_spec.lua new file mode 100644 index 0000000..df0687b --- /dev/null +++ b/advtrains/spec/wagons_spec.lua @@ -0,0 +1,40 @@ +require "mineunit" +mineunit "core" + +_G.advtrains = { + wagon_load_range = 32 +} +sourcefile "wagons" + +local myproto = {_test = true} +advtrains.register_wagon(":mywagon", myproto, "My wagon", "", false) +advtrains.register_wagon_alias(":myalias", ":mywagon") +advtrains.register_wagon_alias(":myotheralias", ":myalias") + +local myotherproto = {_other = true} +advtrains.register_wagon(":noalias", myotherproto, "Not aliased wagon", "", false) +advtrains.register_wagon_alias(":noalias", ":mywagon") + +advtrains.register_wagon_alias(":nilalias", ":nil") + +advtrains.register_wagon_alias(":R1", ":R2") +advtrains.register_wagon_alias(":R2", ":R3") +advtrains.register_wagon_alias(":R3", ":R1") + +describe("wagon alias system", function() + it("should work", function() + assert.same({":mywagon", myproto}, {advtrains.resolve_wagon_alias(":myalias")}) + assert.equal(myproto, advtrains.wagon_prototypes[":myalias"]) + assert.same({":mywagon", myproto}, {advtrains.resolve_wagon_alias(":myotheralias")}) + end) + it("should respect wagon registration", function() + assert.same({":noalias", myotherproto}, {advtrains.resolve_wagon_alias(":noalias")}) + end) + it("should handle recursive loops", function() + assert.same({}, {advtrains.resolve_wagon_alias(":R1")}) + end) + it("should return nil for missing entries", function() + assert.same({}, {advtrains.resolve_wagon_alias(":what")}) + assert.same({}, {advtrains.resolve_wagon_alias(":nilalias")}) + end) +end) diff --git a/advtrains/texture.lua b/advtrains/texture.lua new file mode 100644 index 0000000..e6d83b0 --- /dev/null +++ b/advtrains/texture.lua @@ -0,0 +1,228 @@ +local tx = {} +setmetatable(tx, {__call = function(_, ...) return tx.base(...) end}) + +function tx.escape(str) + return (string.gsub(tostring(str), [[([%^:\])]], [[\%1]])) +end + +local function getargs(...) + return select("#", ...), {...} +end + +local function curry(f, x) + return function(...) + return f(x, ...) + end +end + +local function xmkmodifier(func) + return function(self, ...) + table.insert(self, (func(...))) + return self + end +end + +local function mkmodifier(fmt, spec) + return xmkmodifier(function(...) + local count = select("#", ...) + local args = {...} + for k, f in pairs(spec) do + args[k] = f(args[k]) + end + return string.format(fmt, unpack(args, 1, count)) + end) +end + +-- Texture object +local tx_lib = {} +local tx_mt = { + __index = tx_lib, + __tostring = function(self) + return table.concat(self, "^") + end, + __concat = function(a, b) + return tx.raw(("%s^%s"):format(tostring(a), tostring(b))) + end, +} + +function tx.raw(str) + return setmetatable({str}, tx_mt) +end +function tx.base(str) + return tx.raw(tx.escape(str)) +end +-- TODO: use [fill when 5.8.0 becomes widely used client-side +function tx.fill(w, h, color) + return tx"advtrains_hud_bg.png":resize(w, h):colorize(color) +end + +-- Most texture modifiers +tx_lib.colorize = xmkmodifier(function(c, a) + local str = ("[colorize:%s"):format(tx.escape(c)) + if a then + str = str .. ":" .. a + end + return str +end) +tx_lib.multiply = mkmodifier("[multiply:%s", {tx.escape}) +tx_lib.resize = mkmodifier("[resize:%dx%d", {}) +tx_lib.transform = mkmodifier("[transform%s", {tx.escape}) + +-- [combine + +local combine = {} + +function combine:add(x, y, ent) + table.insert(self.st, ([[%d,%d=%s]]):format(x, y, tx.escape(tostring(ent)))) + return self +end + +local combine_mt = { + __index = combine, + __tostring = function(self) + return table.concat(self.st, ":") + end, +} + +function tx.combine(w, h, bg) + local base = ("[combine:%dx%d"):format(w, h) + local obj = setmetatable({width = w, height = h, st = {base}}, combine_mt) + if bg then + obj:add_fill(0, 0, w, h, bg) + end + return obj +end + +function combine:add_fill(x, y, ...) + return self:add(x, y, tx.fill(...)) +end + +local function add_multicolor_fill(n, self, x, y, w, h, ...) + local argc, argv = getargs(...) + local t = 0 + for k = 1, argc, 2 do + t = t + argv[k] + end + local newargs = {x, y, w, h} + local sk, wk = n, n+2 + local s = newargs[wk]/t + for k = 1, argc, 2 do + local v = argv[k] * s + newargs[wk] = v + newargs[5] = argv[k+1] + self:add_fill(unpack(newargs)) + newargs[sk] = newargs[sk] + v + end + return self +end +combine.add_multicolor_fill_topdown = curry(add_multicolor_fill, 2) +combine.add_multicolor_fill_leftright = curry(add_multicolor_fill, 1) + +local function add_segmentbar(n, self, x, y, w, h, m, c, ...) + local argc, argv = getargs(...) + local baseargs = {x, y, w, h} + local ss = (baseargs[n+2]+m)/c + local bs = ss - m + for k = 1, argc, 3 do + local lower, upper, fill = argv[k], argv[k+1], argv[k+2] + lower = math.max(0, math.floor(lower))+1 + upper = math.min(c, math.floor(upper)) + if lower <= upper then + local args = {x, y, w, h, fill} + args[n+2] = bs + args[n] = args[n] + ss*(lower-1) + for i = lower, upper do + self:add_fill(unpack(args)) + args[n] = args[n] + ss + end + end + end + return self +end +combine.add_segmentbar_topdown = curry(add_segmentbar, 2) +combine.add_segmentbar_leftright = curry(add_segmentbar, 1) + +local function add_lever(n, self, x, y, w, h, hs, ss, val, hf, sf) + local baseargs = {x, y, w, h} + local sargs = {x, y, w, h, sf} + sargs[5-n] = ss + sargs[n+2] = baseargs[n+2] + ss - hs + for k = 1, 2 do + sargs[k] = baseargs[k] + (baseargs[k+2] - sargs[k+2])/2 + end + self:add_fill(unpack(sargs)) + local hargs = {x, y, w, h, hf} + hargs[n+2] = hs + hargs[n] = baseargs[n] + (baseargs[n+2]-hs)*val + self:add_fill(unpack(hargs)) + return self +end +combine.add_lever_topdown = curry(add_lever, 2) +combine.add_lever_leftright = curry(add_lever, 1) + +--[[ Seven-segment display + -1- +6 2 + -7- +5 3 + -4- +--]] +local sevenseg_digits = { + ["0"] = {1, 2, 3, 4, 5, 6}, + ["1"] = {2, 3}, + ["2"] = {1, 2, 4, 5, 7}, + ["3"] = {1, 2, 3, 4, 7}, + ["4"] = {2, 3, 6, 7}, + ["5"] = {1, 3, 4, 6, 7}, + ["6"] = {1, 3, 4, 5, 6, 7}, + ["7"] = {1, 2, 3}, + ["8"] = {1, 2, 3, 4, 5, 6, 7}, + ["9"] = {1, 2, 3, 4, 6, 7}, +} + +function combine:add_str7seg(x0, y0, tw, th, str, fill) + --[[ w and h (as width/height of individual (horizontal) segments) have the following properties: + tw = n(w+3h)-h + th = 2w+3h + --]] + local len = #str + local h = (2*tw-len*th)/(3*len-2) + local w = (th-3*h)/2 + local ws = w+3*h + local segs = { + {h, 0, w, h}, + {w+h, h, h, w}, + {w+h, w+2*h, h, w}, + {h, 2*(w+h), w, h}, + {0, w+2*h, h, w}, + {0, h, h, w}, + {h, w+h, w, h}, + } + for i = 1, len do + for _, k in pairs(sevenseg_digits[string.sub(str, i, i)] or {}) do + local s = segs[k] + self:add_fill(s[1]+x0, s[2]+y0, s[3], s[4], fill) + end + x0 = x0 + ws + end + return self +end + +function combine:add_n7seg(x, y, w, h, n, prec, ...) + if not (type(n) == "number" and type(prec) == "number") then + error("passed non-numeric value or precision to numeric display") + elseif prec < 0 then + error("negative length") + end + local pfx = "" + if n >= 0 then + n = math.min(10^prec-1, n) + else + n = math.min(10^(prec-1)-1, -n) + pfx = "-" + end + local str = ("%d"):format(n) + return self:add_str7seg(x, y, w, h, pfx .. ("0"):rep(prec-#str-#pfx) .. str, ...) +end + +return tx diff --git a/advtrains/textures/advtrains_wagon_prop_tool.png b/advtrains/textures/advtrains_wagon_prop_tool.png Binary files differnew file mode 100644 index 0000000..6352c55 --- /dev/null +++ b/advtrains/textures/advtrains_wagon_prop_tool.png 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 3959232..e6d08a7 100644 --- a/advtrains/tracks.lua +++ b/advtrains/tracks.lua @@ -1,752 +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", - use_texture_alpha = "blend", - 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 = 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 22aa6cf..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,138 +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 - spd = advtrains.speed.min(spd, train.speed_restriction) - if spd == -1 then spd = nil end - local c = not spd and "lime" or (type(spd) == "number" and (spd == 0) and "red" or "orange") or nil - if c then - ht[#ht+1] = sformat("130,10=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c) - ht[#ht+1] = sformat("130,35=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c) - if spd and spd~=0 then - ht[#ht+1] = sformat("%d,50=(advtrains_hud_arrow.png^[multiply\\:red^[makealpha\\:#000000)", 1+spd*11) - 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 f136577..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,13 +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.id then - local wdata = advtrains.wagons[wagon.id] - if wdata and wdata.train_id == id then - wagon:reattach_all() - end - end + for _, wagon in advtrains.wagon_entity_pairs_in_train(id) do + wagon:reattach_all() end end end) @@ -160,12 +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 @@ -270,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 @@ -280,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 @@ -428,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 @@ -457,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) @@ -631,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 @@ -803,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 @@ -842,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 @@ -861,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) @@ -882,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] @@ -911,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 @@ -940,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) @@ -951,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) @@ -1034,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; @@ -1055,7 +1062,7 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate) if not wagon then local ent = advtrains.wagon_objects[w_id] local pdesc - if ent then + 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) @@ -1079,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 @@ -1117,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 @@ -1185,6 +1194,8 @@ function advtrains.split_train_at_index(train, index) newtrain.points_split = advtrains.merge_tables(train.points_split) newtrain.autocouple = train.autocouple + advtrains.te_run_callbacks_on_decouple(train, newtrain, index) + return newtrain_id -- return new train ID, so new train can be manipulated end @@ -1197,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 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 62e65af..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,15 +370,15 @@ 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 .."\nLiquid: "..data.techage_liquid.name..", "..data.techage_liquid.amount.." units" + outside = outside .."\n"..S("Liquid: ")..data.techage_liquid.name..", "..data.techage_liquid.amount..S(" units") else - outside = outside .."\nLiquid: empty" + outside = outside .."\n"..S("Liquid: empty") end end @@ -455,26 +464,34 @@ function wagon:on_step(dtime) --needs to know index and path 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 @@ -655,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 @@ -679,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 @@ -698,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 @@ -709,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 @@ -814,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 @@ -844,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 @@ -856,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 @@ -868,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() @@ -971,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 @@ -1002,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 @@ -1029,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 @@ -1088,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 @@ -1139,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 @@ -1198,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 @@ -1247,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) @@ -1256,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 @@ -1276,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] @@ -1294,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 @@ -1317,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) @@ -1334,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[]" @@ -1368,38 +1415,48 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati groups = wagon_groups, on_place = function(itemstack, placer, pointed_thing) - if not pointed_thing.type == "node" then + if pointed_thing.type ~= "node" then return end + + local pos = pointed_thing.under + local node = minetest.get_node(pos) + local pointed_def = minetest.registered_nodes[node.name] + if pointed_def and pointed_def.on_rightclick then + local controls = placer:get_player_control() + if not controls.sneak then + return pointed_def.on_rightclick(pos, node, placer, itemstack, pointed_thing) + end + end + local pname = placer:get_player_name() - local node=minetest.get_node_or_nil(pointed_thing.under) - if not node then atprint("[advtrains]Ignore at placer position") return itemstack end + if node.name == "ignore" then atprint("[advtrains]Ignore at placer position") return itemstack end local nodename=node.name - if(not advtrains.is_track_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() @@ -1416,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 = { }, @@ -1425,7 +1481,7 @@ 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) @@ -1487,4 +1543,57 @@ function advtrains.get_wagon_at_index(train_id, w_index) end -- nothing found, dist must be further back return nil -end
\ No newline at end of file +end + +function advtrains.get_wagon_entity(wagon_id) + if not advtrains.wagons[wagon_id] then return end + local object = advtrains.wagon_objects[wagon_id] + if object then + return object:get_luaentity() + end +end + +function advtrains.next_wagon_entity_in_train(train, i) + local wagon_id = train.trainparts[i + 1] + if wagon_id then + local wagon = advtrains.get_wagon_entity(wagon_id) + if wagon then + return i + 1, wagon + end + end +end + +function advtrains.wagon_entity_pairs_in_train(train_id) + local train = advtrains.trains[train_id] + if not train then return function() end end + return advtrains.next_wagon_entity_in_train, train, 0 +end + +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 +}) |