aboutsummaryrefslogtreecommitdiff
path: root/advtrains_interlocking/route_prog.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains_interlocking/route_prog.lua')
-rw-r--r--advtrains_interlocking/route_prog.lua323
1 files changed, 323 insertions, 0 deletions
diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua
new file mode 100644
index 0000000..72f72ca
--- /dev/null
+++ b/advtrains_interlocking/route_prog.lua
@@ -0,0 +1,323 @@
+-- Route programming system
+
+--[[
+Progamming routes:
+1. Select "program new route" in the signalling dialog
+-> route_start marker will appear to designate route-program mode
+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)
+ 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)
+ 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},
+ })
+
+ markerent[context][key] = obj
+end
+
+--[[
+Route definition:
+route = {
+ name = <string>
+ tcbpath = {
+ [n] = <sigd>
+ }
+ pcfix = {
+ [<pts>] = "state"
+ }
+}
+The first item in the TCB path is always the start signal of this route,
+so this is left out.
+
+
+]]--
+
+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
+function advtrains.interlocking.visualize_route(origin, route, context)
+ advtrains.interlocking.clear_visu_context(context)
+ for k,sigd in ipairs(route.tcbpath) do
+ 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[sigd.s].c)
+ end
+ local img = "at_il_route_set.png"
+ if k == #route.tcbpath then img = "at_il_route_end.png" end
+ routemarker(context, sigd.p, "rte"..k, img, yaw, route.name.." #"..k)
+ end
+ for pts, state in pairs(route.pcfix) do
+ local pos = minetest.string_to_pos(pts)
+ routesprite(context, pos, "fix"..pts, "at_il_route_lock.png", "Fixed in state '"..state.."' by route "..route.name)
+ end
+end
+
+
+local player_rte_prog = {}
+
+function advtrains.interlocking.init_route_prog(pname, sigd)
+ player_rte_prog[pname] = {
+ origin = sigd,
+ route = {
+ name = "PROG["..pname.."]",
+ tcbpath = {},
+ pcfix = {},
+ }
+ }
+ 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.tcbpath == 0 then
+ return origin
+ end
+ return route.tcbpath[#route.tcbpath]
+end
+
+local function chat(pname, message)
+ minetest.chat_send_player(pname, "[Route programming] "..message)
+end
+local function otherside(s)
+ if s==1 then return 2 else return 1 end
+end
+
+-- Central route programming punch callback
+minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
+ local pname = player:get_player_name()
+ 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)
+
+ -- track circuit break, try to advance route over it
+ local lri = get_last_route_item(rp.origin, rp.route)
+ if vector.equals(lri.p, tcbpos) then
+ chat(pname, "You cannot continue the route to where you came from!")
+ return
+ end
+
+ local start_tcbs = advtrains.interlocking.db.get_tcbs(lri)
+ if not start_tcbs.ts_id then
+ chat(pname, "The previous TCB was End of Interlocking. Please complete route programming using '/at_rp_set <name>'")
+ return
+ end
+
+ local ts = advtrains.interlocking.db.get_ts(start_tcbs.ts_id)
+ if not ts then atwarn("Internal error, ts inexistant for id!") return end
+ local found = nil
+ for _,sigd in ipairs(ts.tc_breaks) do
+ if vector.equals(sigd.p, tcbpos) then
+ found = otherside(sigd.s)
+ end
+ end
+ if not found then
+ chat(pname, "Previous and this TCB belong to different track sections!")
+ return
+ end
+ -- everything worked, just add the other side to the list
+ table.insert(rp.route.tcbpath, {p = tcbpos, s = found})
+ chat(pname, "Added track section '"..ts.name.."' to the route (revert with /at_rp_back)")
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname)
+ return
+ end
+ local ndef = minetest.registered_nodes[node.name]
+ if ndef and ndef.luaautomation and ndef.luaautomation.getstate then
+ local pts = advtrains.roundfloorpts(pos)
+ if rp.route.pcfix[pts] then
+ rp.route.pcfix[pts] = nil
+ chat(pname, pts.." is no longer affected when this route is set.")
+ else
+ local state = ndef.luaautomation.getstate
+ if type(state)=="function" then
+ state = state(pos, node)
+ end
+ rp.route.pcfix[pts] = state
+ chat(pname, pts.." is held in "..state.." position when this route is set.")
+ end
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname)
+ return
+ end
+
+ end
+end)
+
+minetest.register_chatcommand("at_rp_set",
+ {
+ params = "<name>", -- Short parameter description
+ description = "Completes route programming procedure", -- Full description
+ privs = {}, -- TODO
+ func = function(pname, param)
+ return advtrains.pcall(function()
+ if param=="" then
+ return false, "Missing name parameter!"
+ end
+ local rp = player_rte_prog[pname]
+ if rp then
+ if #rp.route.tcbpath <= 0 then
+ return false, "Cannot program route without a target"
+ end
+ rp.route.name = param
+ -- TODO save that route somewhere in origin
+ atdebug("ROUTE RESULT:",rp)
+ advtrains.interlocking.clear_visu_context("prog_"..pname)
+ return true, "Successfully programmed route"
+ end
+ return false, "You were not programming a route!"
+ end)
+ end,
+ })
+
+minetest.register_chatcommand("at_rp_back",
+ {
+ params = "", -- Short parameter description
+ description = "Remove last route segment", -- Full description
+ privs = {}, -- Require the "privs" privilege to run
+ func = function(pname, param)
+ return advtrains.pcall(function()
+ local rp = player_rte_prog[pname]
+ if rp then
+ if #rp.route.tcbpath <= 0 then
+ return false, "Cannot backtrack when there are no route elements"
+ end
+ rp.route.tcbpath[#rp.route.tcbpath] = nil
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname)
+ return true, "Route section "..(#rp.route.tcbpath+1).." removed."
+ end
+ return false, "You were not programming a route!"
+ end)
+ end,
+ })
+minetest.register_chatcommand("at_rp_mark",
+ {
+ params = "", -- Short parameter description
+ description = "Re-set route programming markers", -- Full description
+ privs = {}, -- TODO
+ func = function(pname, param)
+ return advtrains.pcall(function()
+ local rp = player_rte_prog[pname]
+ if rp then
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname)
+ return true, "Redrawn route markers"
+ end
+ return false, "You were not programming a route!"
+ end)
+ end,
+ })
+minetest.register_chatcommand("at_rp_discard",
+ {
+ params = "", -- Short parameter description
+ description = "Discards the currently programmed route", -- Full description
+ privs = {}, -- Require the "privs" privilege to run
+ func = function(pname, param)
+ return advtrains.pcall(function()
+ player_rte_prog[pname] = nil
+ advtrains.interlocking.clear_visu_context("prog_"..pname)
+ return true, "Route discarded"
+ end)
+ end,
+ })
+
+
+--TODO on route setting
+-- locked turnouts need to somehow know the TS they're associated to, which isn't possible with the current route programming and saving method
+-- unify luaautomation get/setstate interface to the core
+-- privileges for route programming
+-- for now, that locking aspect will be ignored and turnouts just set when route gets commited.