aboutsummaryrefslogtreecommitdiff
path: root/advtrains/advtrains_luaautomation/init.lua
Commit message (Collapse)AuthorAge
* Commit 1.6.2orwell962017-02-04
| | | | | | | | - Add some more stuff to API for LuaATC rails - Warn on strange events even if debug info is disabled - save atlatc on shutdown too - fix detector rails in unloaded chunks - do not fail silently in simple ATC rails
* Revert change to node pos hashes, and rewrite trackdb to use individual ↵orwell962017-02-03
| | | | | | | | coordinates The precision of integers was not sufficient for saving pos node hashes in most cases, leading to strange bugs. This fixes broken ATC rails, broken LuaAutomation stuff and trackdb on Windows. Probably also fixes trains randomly stopping.
* LuaAutomation: Add interrupt to the ingame API and implement initialization ↵orwell962017-02-02
| | | | code handling and env management
* LuaAutomation - Basic component implementationorwell962017-02-02
Implements the base code for LuaAutomation, an ATC rail and a punch-operated 'operation panel' as well as interface for passive components. Changes in advtrains code where neccessary. Supported passive components are light signals, switches and mesecon switches
3' href='#n123'>123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
local F = advtrains.formspec

-- Get current translator
local S = advtrains.interlocking.translate

function advtrains.interlocking.show_signal_form(pos, node, pname, aux_key)
	local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
	if sigd and not aux_key then
		advtrains.interlocking.show_signalling_form(sigd, pname)
	else
		if advtrains.interlocking.signal.get_signal_cap_level(pos) >= 2 then
			advtrains.interlocking.show_ip_sa_form(pos, pname)
		else
			advtrains.interlocking.show_ip_form(pos, pname)
		end
	end
end

local players_assign_ip = {}
local players_assign_distant = {}

local function ipmarker(ipos, connid)
	local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes)
	if not node_ok then return end
	local yaw = advtrains.dir_to_angle(conns[connid].c)

	-- using tcbmarker here
	local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker")
	if not obj then return end
	obj:set_yaw(yaw)
	obj:set_properties({
		textures = { "at_il_signal_ip.png" },
	})
end

function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w)
	advtrains.interlocking.db.check_for_duplicate_ip(pos)
	local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos)
	if pts then
		-- display marker
		local ipos = minetest.string_to_pos(pts)
		ipmarker(ipos, connid)
		return table.concat {
			F.label(x, y, S("Influence point is set at @1.", string.format("%s/%s", pts, connid))),
			F.button_exit(x, y+0.5, w/2-0.125, "ip_set", S("Modify")),
			F.button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", S("Clear")),
		}
	else
		return table.concat {
			F.label(x, y, S("Influence point is not set.")),
			F.button_exit(x, y+0.5, w, "ip_set", S("Set influence point")),
		}
	end
end

-- shows small formspec to set the signal influence point
-- only_notset: show only if it is not set yet (used by signal tcb assignment)
function advtrains.interlocking.show_ip_form(pos, pname, only_notset)
	if not minetest.check_player_privs(pname, "interlocking") then
		return
	end
	local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7)
	local form = {
		"formspec_version[4]",
		"size[8,2.25]",
		ipform,
	}
	if not only_notset or not pts then
		minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form))
	end
end

-- shows larger formspec to set the signal influence point, main aspect and distant signal pos
-- only_notset: show only if it is not set yet (used by signal tcb assignment)
function advtrains.interlocking.show_ip_sa_form(pos, pname)
	if not minetest.check_player_privs(pname, "interlocking") then
		return
	end
	local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7)
	local ma, rpos = advtrains.interlocking.signal.get_aspect(pos)
	local form = {
		"formspec_version[4]",
		"size[8,4.5]",
		ipform,
	}
	-- Create Signal aspect formspec elements
	local ndef = advtrains.ndb.get_ndef(pos)
	if ndef and ndef.advtrains then
		form[#form+1] = F.label(0.5, 2, S("Signal Aspect:"))
		-- main aspect list
		if ndef.advtrains.main_aspects then
			local entries = { S("<none>") }
			local sel = 1
			for i, mae in ipairs(ndef.advtrains.main_aspects) do
				entries[i+1] = mae.description
				if ma and ma.name == mae.name then
					sel = i+1
				end
			end
			form[#form+1] = F.dropdown(0.5, 2.5, 4, "sa_mainaspect", entries, sel, true)
		end
		-- distant signal assign (is shown either when main_aspect is not none, or when pure distant signal)
		if rpos then
			form[#form+1] = F.button_exit(0.5, 3.5, 4, "sa_undistant", S("Dst: @1", minetest.pos_to_string(rpos)) )
		elseif (ma and not ma.halt) or not ndef.advtrains.main_aspects then
			form[#form+1] = F.button_exit(0.5, 3.5, 4, "sa_distant", S("<assign distant>"))
		end
	end
	
	minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form))
end

function advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields)
	if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then
		return
	end
	local ma, rpos = advtrains.interlocking.signal.get_aspect(pos)
	-- mainaspect dropdown
	if fields.sa_mainaspect then
		local idx = tonumber(fields.sa_mainaspect)
		local new_ma = nil
		if idx > 1 then
			local ndef = advtrains.ndb.get_ndef(pos)
			if ndef and ndef.advtrains and ndef.advtrains.main_aspects then
				new_ma = ndef.advtrains.main_aspects[idx - 1]
			end
		end
		if new_ma then
			advtrains.interlocking.signal.set_aspect(pos, new_ma.name, rpos)
		else
			-- reset everything
			advtrains.interlocking.signal.clear_aspect(pos)
		end
		
	end
	-- buttons
	if fields.ip_set then
		advtrains.interlocking.init_ip_assign(pos, pname)
		return
	elseif fields.ip_clear then
		advtrains.interlocking.db.clear_ip_by_signalpos(pos)
		return
	elseif fields.sa_distant then
		advtrains.interlocking.init_distant_assign(pos, pname)
		return
	elseif fields.sa_undistant then
		advtrains.interlocking.signal.set_aspect(pos, ma.name, nil)
		return
	end
	-- show the form again unless one of the buttons was clicked
	if not fields.quit then
		advtrains.interlocking.show_ip_sa_form(pos, pname)
	end
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	local pname = player:get_player_name()
	local pts = string.match(formname, "^at_il_ipsaform_([^_]+)$")
	local pos
	if pts then
		pos = minetest.string_to_pos(pts)
	end
	if pos then
		advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields)
	end
end)

-- inits the signal IP assignment process
function advtrains.interlocking.init_ip_assign(pos, pname)
	if not minetest.check_player_privs(pname, "interlocking") then
		minetest.chat_send_player(pname, S("Insufficient privileges to use this!"))
		return
	end
	--remove old IP
	--advtrains.interlocking.db.clear_ip_by_signalpos(pos)
	minetest.chat_send_player(pname, S("Configuring Signal: Please look in train's driving direction and punch rail to set influence point."))
	
	players_assign_ip[pname] = pos
end

-- inits the distant signal assignment process
function advtrains.interlocking.init_distant_assign(pos, pname)
	if not minetest.check_player_privs(pname, "interlocking") then
		minetest.chat_send_player(pname, S("Insufficient privileges to use this!"))
		return
	end
	minetest.chat_send_player(pname, S("Set distant signal: Punch the main signal to assign!"))
	
	players_assign_distant[pname] = pos
end

-- Tries to automatically find a TCB to assign to the signal, or a main signal if this is a pure distant signal and another signal is found before the TCB
local function try_auto_assign_to_tcb(signalpos, pos, connid, pname)
	local pure_distant = advtrains.interlocking.signal.get_signal_cap_level(signalpos) == 2 -- exactly 2: pure distant sig
	local is_past_first = false
	local ti = advtrains.get_track_iterator(pos, connid, pure_distant and 150 or 16, false) -- maximum 16 track nodes ahead
	local apos, aconnid = ti:next_branch()
	while apos do
		-- check for presence of a tcb
		local tcb = advtrains.interlocking.db.get_tcb(apos)
		if tcb then
			-- check on the pointing connid whether it has a signal already
			if not tcb[aconnid].signal then
				-- go ahead and assign
				local sigd = { p=apos, s=aconnid }
				advtrains.interlocking.db.assign_signal_to_tcbs(signalpos, sigd)
				-- use auto-naming
				advtrains.interlocking.add_autoname_to_tcbs(tcb[aconnid], pname)
				minetest.chat_send_player(pname, S("Assigned signal to the TCB at @1", core.pos_to_string(apos)) )
				advtrains.interlocking.show_tcb_marker(apos)
				advtrains.interlocking.show_signalling_form(sigd, pname)
			end
			-- in all cases return
			return
		elseif pure_distant and is_past_first then
			-- try to find another signal's influence point here which could be the remote of a distant signal
			local pts = advtrains.roundfloorpts(apos)
			local mainsig = advtrains.interlocking.db.get_ip_signal(pts, aconnid)
			if mainsig and advtrains.interlocking.signal.get_signal_cap_level(mainsig) >= 3 then
				advtrains.interlocking.signal.set_aspect(signalpos, "_default", mainsig)
				minetest.chat_send_player(pname, S("Assigned distant signal to the main signal at @1", core.pos_to_string(mainsig)) )
				return
			end
		end
		apos, aconnid = ti:next_track()
		is_past_first = true
	end
	-- if we end up here limit is up
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
	-- IP assignment
	local signalpos = players_assign_ip[pname]
	if signalpos then
		if vector.distance(pos, signalpos)<=50 then
			local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
			if node_ok and #conns == 2 then
				
				local yaw = player:get_look_horizontal()
				local plconnid = advtrains.yawToClosestConn(yaw, conns)
				
				-- add assignment if not already present.
				local pts = advtrains.roundfloorpts(pos)
				if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then
					advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos)
					ipmarker(pos, plconnid)
					minetest.chat_send_player(pname, S("Configuring Signal: Successfully set influence point"))
					-- Try to find a TCB ahead and auto assign this signal there
					local pc = player:get_player_control()
					local no_auto_assign = pc.aux1
					if not no_auto_assign and advtrains.interlocking.signal.get_signal_cap_level(signalpos) >= 2 then
						try_auto_assign_to_tcb(signalpos, pos, plconnid, pname)
					end
				else
					minetest.chat_send_player(pname, S("Configuring Signal: Influence point of another signal is already present!"))
				end
			else
				minetest.chat_send_player(pname, S("Configuring Signal: This is not a normal two-connection rail! Aborted."))
			end
		else
			minetest.chat_send_player(pname, S("Configuring Signal: Node is too far away. Aborted."))
		end
		players_assign_ip[pname] = nil
	end
	-- DST assignment
	signalpos = players_assign_distant[pname]
	if signalpos then
		-- get current mainaspect
		local ma, rpos = advtrains.interlocking.signal.get_aspect(signalpos)
		-- if punched pos is valid signal then set it as the new remote, otherwise nil
		local nrpos
		if advtrains.interlocking.signal.get_signal_cap_level(pos) > 1 then
			nrpos = pos
			if not ma or ma.halt then -- make sure that dst is never set without a main aspect (esp. for pure distant signal case)
				ma = "_default"
			end
			advtrains.interlocking.signal.set_aspect(signalpos, ma, nrpos)
		end
		players_assign_distant[pname] = nil
	end
end)