Signal assignment and route programming procedure

This commit is contained in:
orwell96 2018-07-04 17:48:33 +02:00
parent 9586a4d709
commit 031aab4633
15 changed files with 476 additions and 17 deletions

View File

@ -133,7 +133,9 @@ end
function ndb.swap_node(pos, node, no_inval)
minetest.swap_node(pos, node)
if minetest.get_node_or_nil(pos) then
minetest.swap_node(pos, node)
end
ndb.update(pos, node)
end
@ -294,6 +296,5 @@ minetest.register_chatcommand("at_sync_ndb",
return true, text
end)
end,
privs = {train_operator=true}, -- Require the "privs" privilege to run
})

View File

@ -95,6 +95,8 @@ local ildb = {}
local track_circuit_breaks = {}
local track_sections = {}
local signal_assignments = {}
function ildb.load(data)
if not data then return end
if data.tcbs then
@ -103,10 +105,13 @@ function ildb.load(data)
if data.ts then
track_sections = data.ts
end
if data.signalass then
signal_assignments = data.signalass
end
end
function ildb.save()
return {tcbs = track_circuit_breaks, ts=track_sections}
return {tcbs = track_circuit_breaks, ts=track_sections, signalass = signal_assignments}
end
--
@ -123,6 +128,7 @@ TCB data structure
-- while the signal still displays danger and nothing is written to the TCs
-- As soon as the route can actually be set, all relevant TCs and turnouts are set and this field
-- is set true, clearing the signal
aspect = <asp> -- The aspect the signal should show. If this is nil, should show the most restrictive aspect (red)
},
[2] = { -- Variant: end of track-circuited area (initial state of TC)
ts_id = nil, -- this is the indication for end_of_interlocking
@ -145,6 +151,11 @@ Track section
Signal specifier (sigd) (a pair of TCB/Side):
{p = <pos>, s = <1/2>}
Signal Assignments: reverse lookup of signals assigned to TCBs
signal_assignments = {
[<signal pts>] = <sigd>
}
]]
@ -406,6 +417,19 @@ function ildb.get_ts_at_pos(pos)
return nil
end
-- returns the sigd the signal at pos belongs to, if this is known
function ildb.get_sigd_for_signal(pos)
local pts = advtrains.roundfloorpts(pos)
return signal_assignments[pts]
end
function ildb.set_sigd_for_signal(pos, sigd)
local pts = advtrains.roundfloorpts(pos)
signal_assignments[pts] = sigd
end
advtrains.interlocking.db = ildb

View File

@ -1,13 +1,16 @@
-- Demonstration signals
-- Those can display the 3 main aspects of Ks signals
-- Note that the group value of advtrains_signal is 2, which means "step 2 of signal capabilities"
-- advtrains_signal=1 is meant for signals that do not implement set_aspect.
minetest.register_node("advtrains_interlocking:ds_danger", {
description = "Demo signal at Danger",
tiles = {"at_il_signal_asp_danger.png"},
groups = {
cracky = 3,
advtrains_signal = 1,
advtrains_signal = 2,
save_in_at_nodedb = 1,
},
sounds = default.node_sound_stone_defaults(),
@ -35,7 +38,7 @@ minetest.register_node("advtrains_interlocking:ds_free", {
tiles = {"at_il_signal_asp_free.png"},
groups = {
cracky = 3,
advtrains_signal = 1,
advtrains_signal = 2,
save_in_at_nodedb = 1,
},
sounds = default.node_sound_stone_defaults(),
@ -63,7 +66,7 @@ minetest.register_node("advtrains_interlocking:ds_slow", {
tiles = {"at_il_signal_asp_slow.png"},
groups = {
cracky = 3,
advtrains_signal = 1,
advtrains_signal = 2,
save_in_at_nodedb = 1,
},
sounds = default.node_sound_stone_defaults(),

View File

@ -10,3 +10,4 @@ dofile(modpath.."tcb_ts_ui.lua")
dofile(modpath.."signal_api.lua")
dofile(modpath.."demosignals.lua")
dofile(modpath.."train_related.lua")
dofile(modpath.."route_prog.lua")

View File

@ -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.

View File

@ -33,6 +33,21 @@ on_rightclick = advtrains.interlocking.signal_rc_handler
]]--
local DANGER = {
main = {
free = false,
speed = 0,
},
shunt = {
free = false,
},
dst = {
free = false,
speed = 0,
},
info = {}
}
function advtrains.interlocking.signal_set_aspect(pos, asp)
local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
@ -43,7 +58,13 @@ end
function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing)
local pname = player:get_player_name()
minetest.show_formspec(pname, "at_il_sigasp_"..minetest.pos_to_string(pos), "field[aspect;Set Aspect (F/D)Speed(F/D)Speed(F/D);D0D0D]")
local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then
advtrains.interlocking.show_signalling_form(sigd, pname)
else
-- permit to set aspect manually
minetest.show_formspec(pname, "at_il_sigasp_"..minetest.pos_to_string(pos), "field[aspect;Set Aspect (F/D)Speed(F/D)Speed(F/D);D0D0D]")
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
@ -73,3 +94,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
advtrains.interlocking.signal_set_aspect(pos, asp)
end
end)
-- Returns the aspect the signal at pos is supposed to show
function advtrains.interlocking.signal_get_aspect(pos)
local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then
local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
if tcbs.aspect then
return tcbs.aspect
end
end
return DANGER;
end

View File

@ -1,6 +1,7 @@
-- Track Circuit Breaks and Track Sections - Player interaction
local players_assign_tcb = {}
local players_assign_signal = {}
local players_link_ts = {}
local lntrans = { "A", "B" }
@ -26,6 +27,7 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
cracky=3,
not_blocking_trains=1,
--save_in_at_nodedb=2,
at_il_track_circuit_break = 1,
},
after_place_node = function(pos, node, player)
local meta = minetest.get_meta(pos)
@ -46,14 +48,14 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
players_assign_tcb[pname] = pos
end
end,
on_punch = function(pos, node, player)
local meta = minetest.get_meta(pos)
local tcbpts = meta:get_string("tcb_pos")
if tcbpts ~= "" then
local tcbpos = minetest.string_to_pos(tcbpts)
advtrains.interlocking.show_tcb_marker(tcbpos)
end
end,
--on_punch = function(pos, node, player)
-- local meta = minetest.get_meta(pos)
-- local tcbpts = meta:get_string("tcb_pos")
-- if tcbpts ~= "" then
-- local tcbpos = minetest.string_to_pos(tcbpts)
-- advtrains.interlocking.show_tcb_marker(tcbpos)
-- end
--end,
can_dig = function(pos, player)
-- Those markers can only be dug when all adjacent TS's are set
-- as EOI.
@ -83,6 +85,7 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
local pname = player:get_player_name()
-- TCB assignment
local tcbnpos = players_assign_tcb[pname]
if tcbnpos then
if vector.distance(pos, tcbnpos)<=20 then
@ -109,6 +112,29 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
end
players_assign_tcb[pname] = nil
end
-- Signal assignment
local sigd = players_assign_signal[pname]
if sigd then
if vector.distance(pos, sigd.p)<=50 then
local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2
if is_signal then
local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
if tcbs then
tcbs.signal = pos
advtrains.interlocking.db.set_sigd_for_signal(pos, sigd)
minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.")
else
minetest.chat_send_player(pname, "Configuring TCB: Internal error, TCBS doesn't exist. Aborted.")
end
else
minetest.chat_send_player(pname, "Configuring TCB: Not a compatible signal. Aborted.")
end
else
minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.")
end
players_assign_tcb[pname] = nil
end
end)
@ -135,6 +161,11 @@ local function mktcbformspec(tcbs, btnpref, offset, pname)
form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setfree;Section is blocked]"
end
end
if tcbs.signal then
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"
else
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_asnsig;Assign a signal]"
end
return form
end
@ -143,9 +174,9 @@ function advtrains.interlocking.show_tcb_form(pos, pname)
local tcb = advtrains.interlocking.db.get_tcb(pos)
if not tcb then return end
local form = "size[6,7] label[0.5,0.5;Track Circuit Break Configuration]"
local form = "size[6,9] label[0.5,0.5;Track Circuit Break Configuration]"
form = form .. mktcbformspec(tcb[1], "A", 1, pname)
form = form .. mktcbformspec(tcb[2], "B", 4, pname)
form = form .. mktcbformspec(tcb[2], "B", 5, pname)
minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form)
advtrains.interlocking.show_tcb_marker(pos)
@ -174,6 +205,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local f_makeil = {fields.A_makeil, fields.B_makeil}
local f_setlocked = {fields.A_setlocked, fields.B_setlocked}
local f_setfree = {fields.A_setfree, fields.B_setfree}
local f_asnsig = {fields.A_asnsig, fields.B_asnsig}
local f_sigdia = {fields.A_sigdia, fields.B_sigdia}
for connid=1,2 do
local tcbs = tcb[connid]
@ -201,6 +234,17 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
tcbs.section_free = nil
end
end
if f_asnsig[connid] and not tcbs.signal then
minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.")
players_assign_signal[pname] = {p=pos, s=connid}
minetest.close_formspec(pname, formname)
return
end
if f_sigdia[connid] and tcbs.signal then
advtrains.interlocking.show_signalling_form({p=pos, s=connid}, pname)
return
end
end
advtrains.interlocking.show_tcb_form(pos, pname)
end
@ -373,3 +417,33 @@ function advtrains.interlocking.show_tcb_marker(pos)
markerent[pts] = obj
end
-- Signalling formspec - set routes a.s.o
function advtrains.interlocking.show_signalling_form(sigd, pname)
local form = "size[10,10]label[0.5,0.5;Track Section Detail - ]"
form = form.."field[0.8,2;5.2,1;name;Section name;]"
form = form.."button[5.5,1.7;1,1;setname;Set]"
--minetest.show_formspec(pname, "at_il_signalling_"..sigd.p.."_"..sigd.s, form)
--TODO this is temporary
advtrains.interlocking.init_route_prog(pname, sigd)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
local pname = player:get_player_name()
local pts, connids = string.match(formname, "^at_il_signalling_([^_]+)_(%d)$")
local pts = string.match(formname, "^at_il_tcbconfig_(.+)$")
local pos, connid
if pts then
pos = minetest.string_to_pos(pts)
connid = tonumber(connids)
if not connid or connid<1 or connid>2 then return end
end
if pos and connid and not fields.quit then
advtrains.interlocking.show_signalling_form(ts_id, pname, sel_tcb)
end
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B