diff options
Diffstat (limited to 'advtrains')
-rw-r--r-- | advtrains/api_doc.txt | 2 | ||||
-rw-r--r-- | advtrains/copytool.lua | 2 | ||||
-rw-r--r-- | advtrains/debugitems.lua | 56 | ||||
-rw-r--r-- | advtrains/formspec.lua | 111 | ||||
-rw-r--r-- | advtrains/helpers.lua | 187 | ||||
-rw-r--r-- | advtrains/init.lua | 16 | ||||
-rw-r--r-- | advtrains/lzb.lua | 2 | ||||
-rw-r--r-- | advtrains/nodedb.lua | 14 | ||||
-rw-r--r-- | advtrains/oldtracks.lua | 751 | ||||
-rw-r--r-- | advtrains/p_mesecon_iface.lua | 24 | ||||
-rw-r--r-- | advtrains/passive.lua | 94 | ||||
-rw-r--r-- | advtrains/path.lua | 40 | ||||
-rw-r--r-- | advtrains/settingtypes.txt | 4 | ||||
-rw-r--r-- | advtrains/signals.lua | 143 | ||||
-rw-r--r-- | advtrains/track_reg_helper.lua | 743 | ||||
-rw-r--r-- | advtrains/trackplacer.lua | 635 | ||||
-rw-r--r-- | advtrains/tracks.lua | 1024 | ||||
-rw-r--r-- | advtrains/trainlogic.lua | 60 | ||||
-rw-r--r-- | advtrains/wagons.lua | 5 |
19 files changed, 2554 insertions, 1359 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/copytool.lua b/advtrains/copytool.lua index ca5cae1..c63551e 100644 --- a/advtrains/copytool.lua +++ b/advtrains/copytool.lua @@ -21,7 +21,7 @@ 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 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..8894354 --- /dev/null +++ b/advtrains/formspec.lua @@ -0,0 +1,111 @@ +local sformat = string.format +local fsescape = minetest.formspec_escape + +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] = attrans(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..d7e691d 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 @@ -470,3 +483,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: 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 4a0b928..3918ab3 100644 --- a/advtrains/init.lua +++ b/advtrains/init.lua @@ -22,12 +22,18 @@ 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") +function attrans_formspec(...) + return minetest.formspec_escape(attrans(...)) +end + --advtrains advtrains = {trains={}, player_to_train_mapping={}} -- =======================Development/debugging settings===================== -- DO NOT USE FOR NORMAL OPERATION -local DUMP_DEBUG_SAVE = false +local DUMP_DEBUG_SAVE = true -- dump the save files in human-readable format into advtrains_DUMP local GENERATE_ATRICIFIAL_LAG = false @@ -203,6 +209,7 @@ advtrains.poconvert.from_flat("advtrains") attrans = minetest.get_translator("advtrains") advtrains.speed = dofile(advtrains.modpath.."/speed.lua") +advtrains.formspec = dofile(advtrains.modpath.."/formspec.lua") advtrains.texture = dofile(advtrains.modpath.."/texture.lua") dofile(advtrains.modpath.."/path.lua") @@ -212,6 +219,7 @@ 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") @@ -235,6 +243,10 @@ end dofile(advtrains.modpath.."/lzb.lua") +if minetest.settings:get_bool("advtrains_register_debugitems") then + dofile(advtrains.modpath.."/debugitems.lua") +end + --load/save -- backup variables, used if someone should accidentally delete a sub-mod @@ -475,7 +487,7 @@ 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 diff --git a/advtrains/lzb.lua b/advtrains/lzb.lua index 64e4553..52c2289 100644 --- a/advtrains/lzb.lua +++ b/advtrains/lzb.lua @@ -48,7 +48,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, } 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/oldtracks.lua b/advtrains/oldtracks.lua new file mode 100644 index 0000000..c415143 --- /dev/null +++ b/advtrains/oldtracks.lua @@ -0,0 +1,751 @@ +--advtrains by orwell96, see readme.txt + +--dev-time settings: +--EDIT HERE +--If the old non-model rails on straight tracks should be replaced by the new... +--false: no +--true: yes +advtrains.register_replacement_lbms=false + +--[[TracksDefinition +nodename_prefix +texture_prefix +description +common={} +straight={} +straight45={} +curve={} +curve45={} +lswitchst={} +lswitchst45={} +rswitchst={} +rswitchst45={} +lswitchcr={} +lswitchcr45={} +rswitchcr={} +rswitchcr45={} +vert1={ + --you'll probably want to override mesh here +} +vert2={ + --you'll probably want to override mesh here +} +]]-- +advtrains.all_tracktypes={} + +--definition preparation +local function conns(c1, c2, r1, r2) return {{c=c1, y=r1}, {c=c2, y=r2}} end +local function conns3(c1, c2, c3, r1, r2, r3) return {{c=c1, y=r1}, {c=c2, y=r2}, {c=c3, y=r3}} end + +advtrains.ap={} +advtrains.ap.t_30deg_flat={ + regstep=1, + variant={ + st={ + conns = conns(0,8), + desc = "straight", + tpdouble = true, + tpsingle = true, + trackworker = "cr", + }, + cr={ + conns = conns(0,7), + desc = "curve", + tpdouble = true, + trackworker = "swlst", + }, + swlst={ + conns = conns3(0,8,7), + desc = "left switch (straight)", + trackworker = "swrst", + switchalt = "cr", + switchmc = "on", + switchst = "st", + switchprefix = "swl", + }, + swlcr={ + conns = conns3(0,7,8), + desc = "left switch (curve)", + trackworker = "swrcr", + switchalt = "st", + switchmc = "off", + switchst = "cr", + switchprefix = "swl", + }, + swrst={ + conns = conns3(0,8,9), + desc = "right switch (straight)", + trackworker = "st", + switchalt = "cr", + switchmc = "on", + switchst = "st", + switchprefix = "swr", + }, + swrcr={ + conns = conns3(0,9,8), + desc = "right switch (curve)", + trackworker = "st", + switchalt = "st", + switchmc = "off", + switchst = "cr", + switchprefix = "swr", + }, + }, + regtp=true, + tpdefault="st", + trackworker={ + ["swrcr"]="st", + ["swrst"]="st", + ["cr"]="swlst", + ["swlcr"]="swrcr", + ["swlst"]="swrst", + }, + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_yturnout={ + regstep=1, + variant={ + l={ + conns = conns3(0,7,9), + desc = "Y-turnout (left)", + switchalt = "r", + switchmc = "off", + switchst = "l", + switchprefix = "", + }, + r={ + conns = conns3(0,9,7), + desc = "Y-turnout (right)", + switchalt = "l", + switchmc = "on", + switchst = "r", + switchprefix = "", + } + }, + regtp=true, + tpdefault="l", + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_s3way={ + regstep=1, + variant={ + l={ + conns = { {c=0}, {c=7}, {c=8}, {c=9}, {c=0} }, + desc = "3-way turnout (left)", + switchalt = "s", + switchst="l", + switchprefix = "", + }, + s={ + conns = { {c=0}, {c=8}, {c=7}, {c=9}, {c=0} }, + desc = "3-way turnout (straight)", + switchalt ="r", + switchst = "s", + switchprefix = "", + }, + r={ + conns = { {c=0}, {c=9}, {c=8}, {c=7}, {c=0} }, + desc = "3-way turnout (right)", + switchalt = "l", + switchst="r", + switchprefix = "", + } + }, + regtp=true, + tpdefault="l", + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_30deg_slope={ + regstep=1, + variant={ + vst1={conns = conns(8,0,0,0.5), rail_y = 0.25, desc = "steep uphill 1/2", slope=true}, + vst2={conns = conns(8,0,0.5,1), rail_y = 0.75, desc = "steep uphill 2/2", slope=true}, + vst31={conns = conns(8,0,0,0.33), rail_y = 0.16, desc = "uphill 1/3", slope=true}, + vst32={conns = conns(8,0,0.33,0.66), rail_y = 0.5, desc = "uphill 2/3", slope=true}, + vst33={conns = conns(8,0,0.66,1), rail_y = 0.83, desc = "uphill 3/3", slope=true}, + }, + regsp=true, + slopeplacer={ + [2]={"vst1", "vst2"}, + [3]={"vst31", "vst32", "vst33"}, + max=3,--highest entry + }, + slopeplacer_45={ + [2]={"vst1_45", "vst2_45"}, + max=2, + }, + rotation={"", "_30", "_45", "_60"}, + trackworker={}, + increativeinv={}, +} +advtrains.ap.t_30deg_straightonly={ + regstep=1, + variant={ + st={ + conns = conns(0,8), + desc = "straight", + tpdouble = true, + tpsingle = true, + trackworker = "st", + }, + }, + regtp=true, + tpdefault="st", + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_30deg_straightonly_noplacer={ + regstep=1, + variant={ + st={ + conns = conns(0,8), + desc = "straight", + tpdouble = true, + tpsingle = true, + trackworker = "st", + }, + }, + tpdefault="st", + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_45deg={ + regstep=2, + variant={ + st={ + conns = conns(0,8), + desc = "straight", + tpdouble = true, + tpsingle = true, + trackworker = "cr", + }, + cr={ + conns = conns(0,6), + desc = "curve", + tpdouble = true, + trackworker = "swlst", + }, + swlst={ + conns = conns3(0,8,6), + desc = "left switch (straight)", + trackworker = "swrst", + switchalt = "cr", + switchmc = "on", + switchst = "st", + }, + swlcr={ + conns = conns3(0,6,8), + desc = "left switch (curve)", + trackworker = "swrcr", + switchalt = "st", + switchmc = "off", + switchst = "cr", + }, + swrst={ + conns = conns3(0,8,10), + desc = "right switch (straight)", + trackworker = "st", + switchalt = "cr", + switchmc = "on", + switchst = "st", + }, + swrcr={ + conns = conns3(0,10,8), + desc = "right switch (curve)", + trackworker = "st", + switchalt = "st", + switchmc = "off", + switchst = "cr", + }, + }, + regtp=true, + tpdefault="st", + trackworker={ + ["swrcr"]="st", + ["swrst"]="st", + ["cr"]="swlst", + ["swlcr"]="swrcr", + ["swlst"]="swrst", + }, + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_perpcrossing={ + regstep = 1, + variant={ + st={ + conns = { {c=0}, {c=8}, {c=4}, {c=12} }, + desc = "perpendicular crossing", + tpdouble = true, + tpsingle = true, + trackworker = "st", + }, + }, + regtp=true, + tpdefault="st", + rotation={"", "_30", "_45", "_60"}, +} +advtrains.ap.t_90plusx_crossing={ + regstep = 1, + variant={ + ["30l"]={ + conns = { {c=0}, {c=8}, {c=1}, {c=9} }, + desc = "30/90 degree crossing (left)", + tpdouble = true, + tpsingle = true, + trackworker = "45l" + }, + ["45l"]={ + conns = { {c=0}, {c=8}, {c=2}, {c=10} }, + desc = "45/90 degree crossing (left)", + tpdouble = true, + tpsingle = true, + trackworker = "60l", + }, + ["60l"]={ + conns = { {c=0}, {c=8}, {c=3}, {c=11}}, + desc = "60/90 degree crossing (left)", + tpdouble = true, + tpsingle = true, + trackworker = "60r", + }, + ["60r"]={ + conns = { {c=0}, {c=8}, {c=5}, {c=13} }, + desc = "60/90 degree crossing (right)", + tpdouble = true, + tpsingle = true, + trackworker = "45r" + }, + ["45r"]={ + conns = { {c=0}, {c=8}, {c=6}, {c=14} }, + desc = "45/90 degree crossing (right)", + tpdouble = true, + tpsingle = true, + trackworker = "30r", + }, + ["30r"]={ + conns = { {c=0}, {c=8}, {c=7}, {c=15}}, + desc = "30/90 degree crossing (right)", + tpdouble = true, + tpsingle = true, + trackworker = "30l", + }, + }, + regtp=true, + tpdefault="30l", + rotation={""}, + trackworker = { + ["30l"] = "45l", + ["45l"] = "60l", + ["60l"] = "60r", + ["60r"] = "45r", + ["45r"] = "30r", + ["30r"] = "30l", + } +} + +advtrains.ap.t_diagonalcrossing = { + regstep=1, + variant={ + ["30l45r"]={ + conns = {{c=1}, {c=9}, {c=6}, {c=14}}, + desc = "30left-45right diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="60l30l", + }, + ["60l30l"]={ + conns = {{c=3}, {c=11}, {c=1}, {c=9}}, + desc = "30left-60right diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="60l45r" + }, + ["60l45r"]={ + conns = {{c=3}, {c=11}, {c=6}, {c=14}}, + desc = "60left-45right diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="60l60r" + }, + ["60l60r"]={ + conns = {{c=3}, {c=11}, {c=5}, {c=13}}, + desc = "60left-60right diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="60r45l", + }, + --If 60l60r had a mirror image, it would be here, but it's symmetric. + -- 60l60r is also equivalent to 30l30r but rotated 90 degrees. + ["60r45l"]={ + conns = {{c=5}, {c=13}, {c=2}, {c=10}}, + desc = "60right-45left diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="60r30r", + }, + ["60r30r"]={ + conns = {{c=5}, {c=13}, {c=7}, {c=15}}, + desc = "60right-30right diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="30r45l", + }, + ["30r45l"]={ + conns = {{c=7}, {c=15}, {c=2}, {c=10}}, + desc = "30right-45left diagonal crossing", + tpdouble=true, + tpsingle=true, + trackworker="30l45r", + }, + + }, + regtp=true, + tpdefault="30l45r", + rotation={""}, + trackworker = { + ["30l45r"] = "60l30l", + ["60l30l"] = "60l45r", + ["60l45r"] = "60l60r", + ["60l60r"] = "60r45l", + ["60r45l"] = "60r30r", + ["60r30r"] = "30r45l", + ["30r45l"] = "30l45r", + } +} + +advtrains.trackpresets = advtrains.ap + +--definition format: ([] optional) +--[[{ + nodename_prefix + texture_prefix + [shared_texture] + models_prefix + models_suffix (with dot) + [shared_model] + formats={ + st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2 + (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all) + } + common={} change something on common rail appearance +} +[18.12.17] Note on new connection system: +In order to support real rail crossing nodes and finally make the trackplacer respect switches, I changed the connection system. +There can be a variable number of connections available. These are specified as tuples {c=<connection>, y=<rely>} +The table "at_conns" consists of {<conn1>, <conn2>...} +the "at_rail_y" property holds the value that was previously called "railheight" +Depending on the number of connections: +2 conns: regular rail +3 conns: switch: + - when train passes in at conn1, will move out of conn2 + - when train passes in at conn2 or conn3, will move out of conn1 +4 conns: cross (or cross switch, depending on arrangement of conns): + - conn1 <> conn2 + - conn3 <> conn4 +]] + +-- Notify the user if digging the rail is not allowed +local function can_dig_callback(pos, player) + local ok, reason = advtrains.can_dig_or_modify_track(pos) + if not ok and player then + minetest.chat_send_player(player:get_player_name(), attrans("This track can not be removed!") .. " " .. reason) + end + return ok +end + +function advtrains.register_tracks(tracktype, def, preset) + advtrains.trackplacer.register_tracktype(def.nodename_prefix, preset.tpdefault) + if preset.regtp then + advtrains.trackplacer.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description, def) + end + if preset.regsp then + advtrains.slope.register_placer(def, preset) + end + for suffix, var in pairs(preset.variant) do + for rotid, rotation in ipairs(preset.rotation) do + if not def.formats[suffix] or def.formats[suffix][rotid] then + local img_suffix = suffix..rotation + local ndef = advtrains.merge_tables({ + description=def.description.."("..(var.desc or "any")..rotation..")", + drawtype = "mesh", + paramtype="light", + paramtype2="facedir", + walkable = false, + selection_box = { + type = "fixed", + fixed = {-1/2-1/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 +}]] + + + + + + + + + diff --git a/advtrains/p_mesecon_iface.lua b/advtrains/p_mesecon_iface.lua index 33fcecd..0b99891 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,13 +39,11 @@ 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", 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..4807361 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 } @@ -207,23 +206,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 +241,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/settingtypes.txt b/advtrains/settingtypes.txt index a495d1e..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 diff --git a/advtrains/signals.lua b/advtrains/signals.lua index c03afbf..4dec7f5 100644 --- a/advtrains/signals.lua +++ b/advtrains/signals.lua @@ -5,13 +5,13 @@ local mrules_wallsignal = advtrains.meseconrules local function can_dig_func(pos) if advtrains.interlocking then - return advtrains.interlocking.signal_can_dig(pos) + return advtrains.interlocking.signal.can_dig(pos) end return true end local function after_dig_func(pos) if advtrains.interlocking then - return advtrains.interlocking.signal_after_dig(pos) + return advtrains.interlocking.signal.after_dig(pos) end return true end @@ -26,22 +26,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 @@ -74,7 +73,10 @@ 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 }}, @@ -88,28 +90,23 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", 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, }, 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", @@ -138,9 +135,6 @@ 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) @@ -152,35 +146,28 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", 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, + 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 @@ -219,9 +206,6 @@ 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) @@ -233,28 +217,21 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", 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, + 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, }, @@ -294,12 +271,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 @@ -335,12 +308,8 @@ 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, + node_state = "on", + node_state_map = { on = "advtrains:across_on", off = "advtrains:across_off" }, fallback_state = "off", }, on_rightclick=function(pos, node, player) diff --git a/advtrains/track_reg_helper.lua b/advtrains/track_reg_helper.lua new file mode 100644 index 0000000..dbb2d53 --- /dev/null +++ b/advtrains/track_reg_helper.lua @@ -0,0 +1,743 @@ +-- 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! + +--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(), attrans("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.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 + local s = minetest.registered_nodes[minetest.get_node(upos).name] and minetest.registered_nodes[minetest.get_node(upos).name].walkable + if s 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", + 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 = 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 diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua index 2588088..6a2c7a8 100644 --- a/advtrains/trackplacer.lua +++ b/advtrains/trackplacer.lua @@ -3,323 +3,235 @@ --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 + ndef.advtrains.track_place_group = tpg +end + +-- 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 - return found_conn 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 + +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) + --atdebug("track_place_node: ",pos, node) + advtrains.ndb.swap_node(pos, node) + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.after_place_node then + ndef.after_place_node(pos) + 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 conn2 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) == conn2 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) -- 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) -- 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) -- 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) - if pointed_thing.type=="node" then - do - local pointed_pos = pointed_thing.under - local pointed_node = minetest.get_node(pointed_pos) - local pointed_def = minetest.registered_nodes[pointed_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(pointed_pos, pointed_node, placer, itemstack, pointed_thing) - end - end - end - - local name = placer:get_player_name() - if not name then - return itemstack, false - end - - 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",{ @@ -329,116 +241,93 @@ minetest.register_craftitem("advtrains:trackworker",{ 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(), attrans("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 = attrans("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}) + minetest.chat_send_player(placer:get_player_name(), str) 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}) - 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, attrans("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 = attrans("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 ee82426..661da8a 100644 --- a/advtrains/tracks.lua +++ b/advtrains/tracks.lua @@ -1,752 +1,272 @@ ---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 slope: 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 slope: 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 slope: 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 slope: Not enough slope items left (@1 required).", step)) - end - else - minetest.chat_send_player(player:get_player_name(), attrans("Can't place slope: 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 slope: 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
+
+
+--[[
+
+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(), attrans("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"
+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
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua index ed49a4c..1d44324 100644 --- a/advtrains/trainlogic.lua +++ b/advtrains/trainlogic.lua @@ -277,10 +277,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 @@ -861,13 +863,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,18 +881,11 @@ 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) @@ -1034,10 +1026,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; @@ -1079,13 +1070,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 @@ -1208,8 +1192,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/wagons.lua b/advtrains/wagons.lua index cf15871..ef057e5 100644 --- a/advtrains/wagons.lua +++ b/advtrains/wagons.lua @@ -1422,7 +1422,7 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati 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 @@ -1437,7 +1437,7 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati local yaw = placer:get_look_horizontal() local plconnid = advtrains.yawToClosestConn(yaw, tconns) - local prevpos = advtrains.get_adjacent_rail(pos, 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!") return @@ -1462,7 +1462,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 = { }, |