aboutsummaryrefslogtreecommitdiff
path: root/advtrains/nodedb.lua
Commit message (Collapse)AuthorAge
* LuaATC: Add Mesecons LuaATC Controller (similar to Mesecons Luacontroller)orwell962021-11-02
|
* Serialization: strip trailing \r also in nodedb callbackrelease-2.3.1orwell962021-05-07
|
* Add save interval setting, fix accidental global variable, at_sync_ndb on ↵orwell962021-02-10
| | | | server priv not limited, timetaking for restore_all
* Add IGNORE_WORLD mode to test using only the advtrains save dataorwell962021-02-10
|
* Start using path_invalidate_ahead()orwell962021-02-10
|
* Remove pcall wrappers completely, add command to disable advtrains mainlooporwell962021-02-10
|
* Make is_node_loaded() checks also for mesecon interactions and ↵orwell962021-02-04
| | | | digiline_send(), move on_updated_from_nodedb to advtrains table
* Discard get_node_or_nil() for area-loaded check and use either a ↵orwell962021-02-03
| | | | | | distance-based approach or minetest.is_block_active() if available See also https://github.com/minetest/minetest/pull/10897
* Backport ndb format from cellworld to also store the cids in the ndb file; ↵orwell962021-01-12
| | | | integrate ndb in serialize_lib atomic system
* Add windows compatibility in nodedb saving (H#153)orwell962020-10-07
| | | | Note: it does not simply add os.delete() but reverts to the "old" behavior of directly overwriting the file, because this did work before.
* Deprecate old 4590 crossingsBlockhead2020-08-21
| | | | | | | This is done with a task that runs once when the nodedb is loaded. A new field of advtrains_ndb will keep a version integer to indicate migration versions. This introduces verson 1, no version being equivalent to 0. An LBM is also registered to replace the tracks in the world.
* No longer require server priv for /at_sync_ndbGabriel Pérez-Cerezo2019-07-17
| | | | | The check for server priv was hidden where no one would have expected it.
* Various small fixesGabriel Pérez-Cerezo2019-07-16
| | | | | | - Also do atomic operations on nodedb - no longer require Worldedit for at_sync_ndb - "overrun LZB 0 restriction" should now show where it happened.
* Fix for path invalidation within update callbacks (issue was caused by ↵orwell962019-01-22
| | | | detector rails)
* Fix node database ATC rail bugorwell962018-10-17
| | | | | | | After successfully registering newly-placed ATC rails in the nodedb, the rail's after_place_node() callback (local apn_func()) immediately cleared it again by passing a "node" object that was actually a player object and thus had no "name" or "param2" to ndb.update(), which it handled how it should not in this situation. Since those ndb.update calls were unnecessary anyways, I removed them completely.
* Signal assignment and route programming procedureorwell962018-07-04
|
* Update nodedb.luaimcasper2018-06-14
| | | correct read/write file binary data
* Bugfixes part 1orwell962018-06-14
| | | | There's something wrong with the new paths, next time build a path validity checker to trace the issue
* Log certain events in a logfile in the world directoryGabriel Pérez-Cerezo2018-02-21
| | | | (also fix the output of /at_sync_ndb)
* Use directory delimter constantorwell962018-01-15
| | | Reported by @kostett, probably the cause of buggy nodedb saving
* Remove path invalidation statement.orwell962017-12-18
| | | | | Caused problems on multiple detector rails in a row, because paths got cleared in-between a train step Also optimize some code
* Do not spam the server chat with messages from /at_sync_ndb and trains going ↵orwell962017-12-18
| | | | | | off_track The off-track warning has moved into the info text of wagons
* Rewrite rail connection system...orwell962017-12-18
| | | | | | | | | ...to support an arbitrary number of connections for rails, which leads to these new features: - switches now get recognized by the trackworker correctly - ability to add real rail crosses During this, I also rewrote the rail registering system and the conway function (important part of path prediction) Note, developers: the track preset format changed, you might need to rewrite them according to the presets in tracks.lua if you wrote your own (possibly breaks advcarts)
* Implement sound api and some soundsorwell962017-12-06
| | | | | | | - Level crossing bell - Horns - Subway train driving and door sounds ...to be continued...
* Change name of the node database group in order to clone node database code ↵orwell962017-10-31
| | | | | | into a library mod advtrains will keep its own node database code for reasons of crash recovery, with the handicap that improvements to nplib need to be manually backported.
* Prefer saved nodedb node before node loaded from maporwell962017-10-11
| | | | Possibly fixes bug on linuxworks server.
* Remove zip release files, move mod to root, exclude assets from Makefile (#92)rubenwardy2017-09-20
n class="hl com">2. Do those actions in any order: A. punch a TCB marker node to proceed route along this TCB. This will only work if this is actually a TCB bordering the current TS, and will place a route_set marker and shift to the next TS B. right-click a turnout to switch it (no impact to route programming C. punch a turnout (or some other passive component) to fix its state (toggle) for the route. A sprite telling "Route Fix" will show that fact. 3. To complete route setting, use the chat command '/at_program_route <route name>'. The last punched TCB will get a 'route end' marker The end of a route should be at another signal facing the same direction as the entrance signal, however this is not enforced and left up to the signal engineer (the programmer) The route visualization will also be used to visualize routes after they have been programmed. ]]-- -- table with objectRefs local markerent = {} minetest.register_entity("advtrains_interlocking:routemarker", { visual = "mesh", mesh = "trackplane.b3d", textures = {"at_il_route_set.png"}, collisionbox = {-1,-0.5,-1, 1,-0.4,1}, visual_size = {x=10, y=10}, on_punch = function(self) self.object:remove() end, get_staticdata = function() return "STATIC" end, on_activate = function(self, sdata) if sdata=="STATIC" then self.object:remove() end end, static_save = false, }) -- Spawn or update a route marker entity -- pos: position where this is going to be -- key: something unique to determine which entity to remove if this was set before -- img: texture local function routemarker(context, pos, key, img, yaw, itex) if not markerent[context] then markerent[context] = {} end if markerent[context][key] then markerent[context][key]:remove() end local obj = minetest.add_entity(vector.add(pos, {x=0, y=0.3, z=0}), "advtrains_interlocking:routemarker") if not obj then return end obj:set_yaw(yaw) obj:set_properties({ infotext = itex, textures = {img}, }) markerent[context][key] = obj end minetest.register_entity("advtrains_interlocking:routesprite", { visual = "sprite", textures = {"at_il_turnout_free.png"}, collisionbox = {-0.2,-0.2,-0.2, 0.2,0.2,0.2}, visual_size = {x=1, y=1}, on_punch = function(self) if self.callback then self.callback() end self.object:remove() end, get_staticdata = function() return "STATIC" end, on_activate = function(self, sdata) if sdata=="STATIC" then self.object:remove() end end, static_save = false, }) -- Spawn or update a route sprite entity -- pos: position where this is going to be -- key: something unique to determine which entity to remove if this was set before -- img: texture local function routesprite(context, pos, key, img, itex, callback) if not markerent[context] then markerent[context] = {} end if markerent[context][key] then markerent[context][key]:remove() end local obj = minetest.add_entity(vector.add(pos, {x=0, y=0, z=0}), "advtrains_interlocking:routesprite") if not obj then return end obj:set_properties({ infotext = itex, textures = {img}, }) if callback then obj:get_luaentity().callback = callback end markerent[context][key] = obj end --[[ Route definition: route = { name = <string> [n] = { next = <sigd>, -- of the next (note: next) TCB on the route locks = {<pts> = "state"} -- route locks of this route segment } terminal = <sigd>, aspect = <signal aspect>,--note, might change in future } The first item in the TCB path (namely i=0) is always the start signal of this route, so this is left out. All subsequent entries, starting from 1, contain: - all route locks of the segment on TS between the (i-1). and the i. TCB - the next TCB signal describer in proceeding direction of the route. 'Terminal' once again repeats the "next" entry of the last route segment. It is needed for distant signal aspect determination. If it is not set, the distant signal aspect is determined as DANGER. ]]-- local function chat(pname, message) minetest.chat_send_player(pname, "[Route programming] "..message) end local function clear_lock(locks, pname, pts) locks[pts] = nil chat(pname, pts.." is no longer affected when this route is set.") end local function otherside(s) if s==1 then return 2 else return 1 end end function advtrains.interlocking.clear_visu_context(context) if not markerent[context] then return end for key, obj in pairs(markerent[context]) do obj:remove() end markerent[context] = nil end -- visualize route. 'context' is a string that identifies the context of this visualization -- e.g. prog_<player> or vis_<pts> for later visualizations -- last 2 parameters are only to be used in the context of route programming! function advtrains.interlocking.visualize_route(origin, route, context, tmp_lcks, pname) advtrains.interlocking.clear_visu_context(context) local oyaw = 0 local onode_ok, oconns, orhe = advtrains.get_rail_info_at(origin.p, advtrains.all_tracktypes) if onode_ok then oyaw = advtrains.dir_to_angle(oconns[origin.s].c) end routemarker(context, origin.p, "rte_origin", "at_il_route_start.png", oyaw, route.name) local c_sigd = origin for k,v in ipairs(route) do c_sigd = v.next -- display route path -- Final "next" marker can be EOI, thus undefined. This is legitimate. if c_sigd then local yaw = 0 local node_ok, conns, rhe = advtrains.get_rail_info_at(c_sigd.p, advtrains.all_tracktypes) if node_ok then yaw = advtrains.dir_to_angle(conns[c_sigd.s].c) end local img = "at_il_route_set.png" if k==#route and not tmp_lcks then img = "at_il_route_end.png" end routemarker(context, c_sigd.p, "rte"..k, img, yaw, route.name.." #"..k) end -- display locks for pts, state in pairs(v.locks) do local pos = minetest.string_to_pos(pts) routesprite(context, pos, "fix"..k..pts, "at_il_route_lock.png", "Fixed in state '"..state.."' by route "..route.name.." until segment #"..k.." is freed.") end end -- The presence of tmp_lcks tells us that we are displaying during route programming. if tmp_lcks then -- display route end markers at appropriate places (check next TS, if it exists) local terminal = c_sigd if terminal then local term_tcbs = advtrains.interlocking.db.get_tcbs(terminal) if term_tcbs.ts_id then local over_ts = advtrains.interlocking.db.get_ts(term_tcbs.ts_id) for i, sigd in ipairs(over_ts.tc_breaks) do if not vector.equals(sigd.p, terminal.p) then local yaw = 0 local node_ok, conns, rhe = advtrains.get_rail_info_at(sigd.p, advtrains.all_tracktypes) if node_ok then yaw = advtrains.dir_to_angle(conns[otherside(sigd.s)].c) end routemarker(context, sigd.p, "rteterm"..i, "at_il_route_end.png", yaw, route.name.." Terminal "..i) end end end end -- display locks set by player for pts, state in pairs(tmp_lcks) do local pos = minetest.string_to_pos(pts) routesprite(context, pos, "fixp"..pts, "at_il_route_lock_edit.png", "Fixed in state '"..state.."' by route "..route.name.." (punch to unfix)", function() clear_lock(tmp_lcks, pname, pts) end) end end end local player_rte_prog = {} function advtrains.interlocking.init_route_prog(pname, sigd) if not minetest.check_player_privs(pname, "interlocking") then minetest.chat_send_player(pname, "Insufficient privileges to use this!") return end player_rte_prog[pname] = { origin = sigd, route = { name = "PROG["..pname.."]", }, tmp_lcks = {}, } advtrains.interlocking.visualize_route(sigd, player_rte_prog[pname].route, "prog_"..pname, player_rte_prog[pname].tmp_lcks, pname) minetest.chat_send_player(pname, "Route programming mode active. Punch TCBs to add route segments, punch turnouts to lock them.") end local function get_last_route_item(origin, route) if #route == 0 then return origin end return route[#route].next end local function do_advance_route(pname, rp, sigd, tsname) table.insert(rp.route, {next = sigd, locks = rp.tmp_lcks}) rp.tmp_lcks = {} chat(pname, "Added track section '"..tsname.."' to the route.") end local function finishrpform(pname) local rp = player_rte_prog[pname] if not rp then return end local form = "size[7,6]label[0.5,0.5;Finish programming route]" local terminal = get_last_route_item(rp.origin, rp.route) if terminal then local term_tcbs = advtrains.interlocking.db.get_tcbs(terminal) if term_tcbs.signal then form = form .. "label[0.5,1.5;Route ends at signal:]" form = form .. "label[0.5,2 ;"..term_tcbs.signal_name.."]" else form = form .. "label[0.5,1.5;WARNING: Route does not end at a signal.]" form = form .. "label[0.5,2 ;Routes should in most cases end at signals.]" form = form .. "label[0.5,2.5;Cancel if you are unsure!]" end else form = form .. "label[0.5,1.5;Route leads into]" form = form .. "label[0.5,2 ;non-interlocked area]" end form = form.."field[0.8,3.5;5.2,1;name;Enter Route Name;]" form = form.."button_exit[0.5,4.5; 5,1;save;Save Route]" minetest.show_formspec(pname, "at_il_routepf", form) end local function check_advance_valid(tcbpos, rp) -- track circuit break, try to advance route over it local lri = get_last_route_item(rp.origin, rp.route) if not lri then return false, false end local is_endpoint = false local this_sigd, this_ts, adv_side if vector.equals(lri.p, tcbpos) then -- If the player just punched the last TCB again, it's of course possible to -- finish the route here (although it can't be advanced by here. -- Fun fact: you can now program routes that end exactly where they begin :) is_endpoint = true this_sigd = lri else -- else, we need to check whether this TS actually borders local start_tcbs = advtrains.interlocking.db.get_tcbs(lri) if not start_tcbs.ts_id then return false, false end this_ts = advtrains.interlocking.db.get_ts(start_tcbs.ts_id) for _,sigd in ipairs(this_ts.tc_breaks) do if vector.equals(sigd.p, tcbpos) then adv_side = otherside(sigd.s) end end if not adv_side then -- this TCB is not bordering to the section return false, false end this_sigd = {p=tcbpos, s=adv_side} end -- check whether the ts at the other end is capable of "end over" local adv_tcbs = advtrains.interlocking.db.get_tcbs(this_sigd) local next_tsid = adv_tcbs.ts_id local can_over, over_ts, next_tc_bs = false, nil, nil local cannotover_rsn = "Next section is diverging (>2 TCBs)" if next_tsid then -- you may not advance over EOI. While this is technically possible, -- in practise this just enters an unnecessary extra empty route item. over_ts = advtrains.interlocking.db.get_ts(adv_tcbs.ts_id) next_tc_bs = over_ts.tc_breaks can_over = #next_tc_bs <= 2 else cannotover_rsn = "End of interlocking" end local over_sigd = nil if can_over then if next_tc_bs and #next_tc_bs == 2 then local sdt if vector.equals(next_tc_bs[1].p, tcbpos) then sdt = next_tc_bs[2] end if vector.equals(next_tc_bs[2].p, tcbpos) then sdt = next_tc_bs[1] end if not sdt then error("Inconsistency: "..dump(next_ts)) end -- swap TCB direction over_sigd = {p = sdt.p, s = otherside(sdt.s) } end end return is_endpoint, true, this_sigd, this_ts, can_over, over_ts, over_sigd, cannotover_rsn end local function show_routing_form(pname, tcbpos, message) local rp = player_rte_prog[pname] if not rp then return end local is_endpoint, advance_valid, this_sigd, this_ts, can_over, over_ts, over_sigd, cannotover_rsn = check_advance_valid(tcbpos, rp) -- at this place, advance_valid shows whether the current route can be advanced -- over this TCB. -- If it can: -- Advance over (continue programming) -- End here -- Advance and end (only <=2 TCBs, terminal signal needs to be known) -- if not: -- show nothing at all -- In all cases, Discard and Backtrack buttons needed. local form = "size[7,9.5]label[0.5,0.5;Advance/Complete Route]" if message then form = form .. "label[0.5,1;"..message.."]" end if advance_valid and not is_endpoint then form = form.. "label[0.5,1.8;Advance to next route section]" form = form.."image_button[0.5,2.2; 5,1;at_il_routep_advance.png;advance;]" form = form.. "label[0.5,3.5;-------------------------]" else form = form.. "label[0.5,2.3;This TCB is not suitable as]" form = form.. "label[0.5,2.8;route continuation.]" end if advance_valid or is_endpoint then form = form.. "label[0.5,3.8;Finish route HERE]" form = form.."image_button[0.5, 4.2; 5,1;at_il_routep_end_here.png;endhere;]" if can_over then form = form.. "label[0.5,5.3;Finish route at end of NEXT section]" form = form.."image_button[0.5,5.7; 5,1;at_il_routep_end_over.png;endover;]" else form = form.. "label[0.5,5.3;Advancing over next section is]" form = form.. "label[0.5,5.8;impossible at this place.]" if cannotover_rsn then form = form.. "label[0.5,6.3;"..cannotover_rsn.."]" end end end form = form.. "label[0.5,7;-------------------------]" if #rp.route > 0 then form = form.."button[0.5,7.4; 5,1;retract;Step back one section]" end form = form.."button[0.5,8.4; 5,1;cancel;Cancel route programming]" minetest.show_formspec(pname, "at_il_rprog_"..minetest.pos_to_string(tcbpos), form) end minetest.register_on_player_receive_fields(function(player, formname, fields) local pname = player:get_player_name() local tcbpts = string.match(formname, "^at_il_rprog_([^_]+)$") local tcbpos if tcbpts then tcbpos = minetest.string_to_pos(tcbpts) end if tcbpos then -- RPROG form local rp = player_rte_prog[pname] if not rp then minetest.close_formspec(pname, formname) return end local is_endpoint, advance_valid, this_sigd, this_ts, can_over, over_ts, over_sigd = check_advance_valid(tcbpos, rp) if advance_valid then if fields.advance then -- advance route if not is_endpoint then do_advance_route(pname, rp, this_sigd, this_ts.name) end end if fields.endhere then if not is_endpoint then do_advance_route(pname, rp, this_sigd, this_ts.name) end finishrpform(pname) end if can_over and fields.endover then if not is_endpoint then do_advance_route(pname, rp, this_sigd, this_ts.name) end do_advance_route(pname, rp, over_sigd, over_ts and over_ts.name or "--EOI--") finishrpform(pname) end end if fields.retract then if #rp.route <= 0 then minetest.close_formspec(pname, formname) return end rp.tmp_locks = rp.route[#rp.route].locks rp.route[#rp.route] = nil chat(pname, "Route section "..(#rp.route+1).." removed.") end if fields.cancel then player_rte_prog[pname] = nil advtrains.interlocking.clear_visu_context("prog_"..pname) chat(pname, "Route discarded.") minetest.close_formspec(pname, formname) return end advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) minetest.close_formspec(pname, formname) return end if formname == "at_il_routepf" then if not fields.save or not fields.name then return end if fields.name == "" then -- show form again finishrpform(pname) return end local rp = player_rte_prog[pname] if rp then if #rp.route <= 0 then chat(pname, "Cannot program route without a target") return end local tcbs = advtrains.interlocking.db.get_tcbs(rp.origin) if not tcbs then chat(pname, "The origin TCB has become unknown during programming. Try again.") return end local terminal = get_last_route_item(rp.origin, rp.route) rp.route.terminal = terminal rp.route.name = fields.name table.insert(tcbs.routes, rp.route) advtrains.interlocking.clear_visu_context("prog_"..pname) player_rte_prog[pname] = nil chat(pname, "Successfully programmed route.") advtrains.interlocking.show_route_edit_form(pname, rp.origin, #tcbs.routes) return end end end) -- Central route programming punch callback minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local pname = player:get_player_name() if not minetest.check_player_privs(pname, "interlocking") then return end local rp = player_rte_prog[pname] if rp then -- determine what the punched node is if minetest.get_item_group(node.name, "at_il_track_circuit_break") >= 1 then -- get position of the assigned tcb local meta = minetest.get_meta(pos) local tcbpts = meta:get_string("tcb_pos") if tcbpts == "" then chat(pname, "This TCB is unconfigured, you first need to assign it to a rail") return end local tcbpos = minetest.string_to_pos(tcbpts) -- show formspec show_routing_form(pname, tcbpos) advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) return end if advtrains.is_passive(pos) then local pts = advtrains.roundfloorpts(pos) if rp.tmp_lcks[pts] then clear_lock(rp.tmp_lcks, pname, pts) else local state = advtrains.getstate(pos) rp.tmp_lcks[pts] = state chat(pname, pts.." is held in "..state.." position when this route is set and freed ") end advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) return end end end) --TODO on route setting -- routes should end at signals. complete route setting by punching a signal, and command as exceptional route completion -- Create simpler way to advance a route to the next tcb/signal on simple sections without turnouts