aboutsummaryrefslogtreecommitdiff
path: root/advtrains_interlocking/distant_ui.lua
blob: 0907684afb7e41b7a7c54a38504aec156e93b617 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
local F = advtrains.formspec
local D = advtrains.distant
local I = advtrains.interlocking

function I.make_short_dst_formspec_component(pos, x, y, w)
	local main, set_by = D.get_main(pos)
	if main then
		local pts_main = minetest.pos_to_string(main)
		local desc = attrans("The assignment is made with an unknown method.")
		if set_by == "manual" then
			desc = attrans("The assignment is made manually.")
		elseif set_by == "routesetting" then
			desc = attrans("The assignment is made by the routesetting system.")
		end
		return table.concat {
			F.S_label(x, y, "This signal is a distant signal of @1.", pts_main),
			F.label(x, y+0.5, desc),
			F.S_button_exit(x, y+1, w/2-0.125, "dst_assign", "Reassign"),
			F.S_button_exit(x+w/2+0.125, y+1, w/2-0.125, "dst_unassign", "Unassign"),
		}
	else
		return table.concat {
			F.S_label(x, y, "This signal is not assigned to a main signal."),
			F.S_label(x, y+0.5, "The distant aspect of the signal is not used."),
			F.S_button_exit(x, y+1, w, "dst_assign", "Assign")
		}
	end
end

function I.make_dst_list_formspec_component(pos, x, y, w, h)
	local ymid = y+0.25+h/2
	local dstlist = {}
	for pos, _ in pairs(D.get_dst(pos)) do
		table.insert(dstlist, minetest.pos_to_string(advtrains.decode_pos(pos)))
	end
	return table.concat {
		F.S_label(x, y, "Distant signals:"),
		F.textlist(x, y+0.5, w-1, h-0.5, "dstlist", dstlist),
		F.image_button_exit(x+w-0.75, ymid-0.875, 0.75, 0.75, "cdb_add.png", "dst_add", ""),
		F.image_button_exit(x+w-0.75, ymid+0.125, 0.75, 0.75, "cdb_clear.png", "dst_del", ""),
	}
end

function I.make_dst_formspec_component(pos, x, y, w, h)
	return I.make_short_dst_formspec_component(pos, x, y, w, h)
		.. I.make_dst_list_formspec_component(pos, x, y+2, w, h-2)
end

function I.show_distant_signal_form(pos, pname)
	return I.show_ip_form(pos, pname)
end

local signal_pos = {}
local function init_signal_assignment(pname, pos)
	if not minetest.check_player_privs(pname, "interlocking") then
		minetest.chat_send_player(pname, attrans("This operation is not allowed without the @1 privilege.", "interlocking"))
		return
	end
	signal_pos[pname] = pos
	minetest.chat_send_player(pname, attrans("Please punch the signal to use as the main signal."))
end

local distant_pos = {}
local function init_distant_assignment(pname, pos)
	if not minetest.check_player_privs(pname, "interlocking") then
		minetest.send_chat_player(pname, attrans("This operation is now allowed without the @1 privilege.", "interlocking"))
		return
	end
	distant_pos[pname] = pos
	minetest.chat_send_player(pname, attrans("Please punch the signal to use as the distant signal."))
end

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 spos = signal_pos[pname]
	local distant = false
	if not spos then
		spos = distant_pos[pname]
		if not spos then
			return
		end
		distant = true
	end
	signal_pos[pname] = nil
	distant_pos[pname] = nil
	local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2
	if not is_signal then
		minetest.chat_send_player(pname, attrans("Incompatible signal."))
		return
	end
	minetest.chat_send_player(pname, attrans("Successfully assigned signal."))
	if distant then
		D.assign(spos, pos, "manual")
	else
		D.assign(pos, spos, "manual")
	end
end)

local dstsel = {}

function advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields)
	if not (pos and minetest.check_player_privs(pname, "interlocking")) then
		return
	end
	if fields.dst_unassign then
		D.unassign_dst(pos)
	elseif fields.dst_assign then
		init_signal_assignment(pname, pos)
	elseif fields.dst_add then
		init_distant_assignment(pname, pos)
	elseif fields.dstlist then
		dstsel[pname] = minetest.explode_textlist_event(fields.dstlist).index
	elseif fields.dst_del then
		local selid = dstsel[pname]
		if selid then
			local dsts = D.get_dst(pos)
			local pos
			for p, _ in pairs(dsts) do
				selid = selid-1
				if selid <= 0 then
					pos = p
					break
				end
			end
			if pos then
				D.unassign_dst(advtrains.decode_pos(pos))
			end
		end
	end
end
span>, 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, tsref) table.insert(rp.route, {next = sigd, locks = rp.tmp_lcks}) rp.tmp_lcks = {} chat(pname, "Added track section '"..(tsref and (tsref.name or "") or "--EOI--").."' 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 local signalname = (term_tcbs.signal_name or "") .. sigd_to_string(terminal) form = form .. "label[0.5,1.5;Route ends at signal:]" form = form .. "label[0.5,2 ;"..signalname.."]" 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