aboutsummaryrefslogtreecommitdiff
path: root/advtrains/signals.lua
blob: b01314e056dc065c1601d586d837e867f2331b8e (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
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
--advtrains by orwell96
--signals.lua

--this code /should/ work but does not.
local mrules_wallsignal = advtrains.meseconrules

for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do

	advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "")
	advtrains.trackplacer.register_tracktype("advtrains:signal", "")

	for rotid, rotation in ipairs({"", "_30", "_45", "_60"}) do
		local crea=1
		if rotid==1 and r=="off" then crea=0 end
		
		minetest.register_node("advtrains:retrosignal_"..r..rotation, {
			drawtype = "mesh",
			paramtype="light",
			paramtype2="facedir",
			walkable = false,
			selection_box = {
				type = "fixed",
				fixed = {-1/4, -1/2, -1/4, 1/4, 2, 1/4},
			},
			mesh = "advtrains_retrosignal_"..r..rotation..".b3d",
			tiles = {"advtrains_retrosignal.png"},
			inventory_image="advtrains_retrosignal_inv.png",
			drop="advtrains:retrosignal_off",
			description=attrans("Lampless Signal (@1)", attrans(r..rotation)),
			sunlight_propagates=true,
			groups = {
				cracky=3,
				not_blocking_trains=1,
				not_in_creative_inventory=crea,
				save_in_at_nodedb=1,
			},
			mesecons = {effector = {
				rules=advtrains.meseconrules,
				["action_"..f.as] = function (pos, node)
					advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
				end
			}},
			on_rightclick=function(pos, node, player)
				if minetest.check_player_privs(player:get_player_name(), {train_operator=true}) then
					advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
				end
			end,
		})
		advtrains.trackplacer.add_worked("advtrains:retrosignal", r, rotation, nil)
		minetest.register_node("advtrains:signal_"..r..rotation, {
			drawtype = "mesh",
			paramtype="light",
			paramtype2="facedir",
			walkable = false,
			selection_box = {
				type = "fixed",
				fixed = {-1/4, -1/2, -1/4, 1/4, 2, 1/4},
			},
			mesh = "advtrains_signal"..rotation..".b3d",
			tiles = {"advtrains_signal_"..r..".png"},
			inventory_image="advtrains_signal_inv.png",
			drop="advtrains:signal_off",
			description=attrans("Signal (@1)", attrans(r..rotation)),
			groups = {
				cracky=3,
				not_blocking_trains=1,
				not_in_creative_inventory=crea,
				save_in_at_nodedb=1,
			},
			light_source = 1,
			sunlight_propagates=true,
			mesecons = {effector = {
				rules=advtrains.meseconrules,
				["action_"..f.as] = function (pos, node)
					advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true)
				end
			}},
			luaautomation = {
				getstate = f.ls,
				setstate = function(pos, node, newstate)
					if newstate == f.als then
						advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true)
					end
				end,
			},
			on_rightclick=function(pos, node, player)
				if minetest.check_player_privs(player:get_player_name(), {train_operator=true}) then
					advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true)
				end
			end,
		})
		advtrains.trackplacer.add_worked("advtrains:signal", r, rotation, nil)
	end
	
	local crea=1
	if r=="off" then crea=0 end
	
	--tunnel signals. no rotations.
	for loc, sbox in pairs({l={-1/2, -1/2, -1/4, 0, 1/2, 1/4}, r={0, -1/2, -1/4, 1/2, 1/2, 1/4}, t={-1/2, 0, -1/4, 1/2, 1/2, 1/4}}) do
		minetest.register_node("advtrains:signal_wall_"..loc.."_"..r, {
			drawtype = "mesh",
			paramtype="light",
			paramtype2="facedir",
			walkable = false,
			selection_box = {
				type = "fixed",
				fixed = sbox,
			},
			mesh = "advtrains_signal_wall_"..loc..".b3d",
			tiles = {"advtrains_signal_wall_"..r..".png"},
			drop="advtrains:signal_wall_"..loc.."_off",
			description=attrans("Wallmounted Signal ("..loc..")"),
			groups = {
				cracky=3,
				not_blocking_trains=1,
				not_in_creative_inventory=crea,
				save_in_at_nodedb=1,
			},
			light_source = 1,
			sunlight_propagates=true,
			mesecons = {effector = {
				rules = mrules_wallsignal,
				["action_"..f.as] = function (pos, node)
					advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true)
				end
			}},
			luaautomation = {
				getstate = f.ls,
				setstate = function(pos, node, newstate)
					if newstate == f.als then
						advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true)
					end
				end,
			},
			on_rightclick=function(pos, node, player)
				if minetest.check_player_privs(player:get_player_name(), {train_operator=true}) then
					advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true)
				end
			end,
		})
	end
end

-- level crossing
-- german version (Andrew's Cross)
minetest.register_node("advtrains:across_off", {
	drawtype = "mesh",
	paramtype="light",
	paramtype2="facedir",
	walkable = false,
	selection_box = {
		type = "fixed",
		fixed = {-1/4, -1/2, -1/2, 1/4, 1.5, 0},
	},
	mesh = "advtrains_across.obj",
	tiles = {"advtrains_across.png"},
	drop="advtrains:across_off",
	description=attrans("Andrew's Cross"),
	groups = {
		cracky=3,
		not_blocking_trains=1,
		save_in_at_nodedb=1,
		not_in_creative_inventory=nil,
	},
	light_source = 1,
	sunlight_propagates=true,
	mesecons = {effector = {
		rules = advtrains.meseconrules,
		action_on = function (pos, node)
			advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
		end
	}},
	luaautomation = {
		getstate = "off",
		setstate = function(pos, node, newstate)
			if newstate == "on" then
				advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
			end
		end,
	},
	on_rightclick=function(pos, node, player)
		if minetest.check_player_privs(player:get_player_name(), {train_operator=true}) then
			advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
		end
	end,
})
minetest.register_node("advtrains:across_on", {
	drawtype = "mesh",
	paramtype="light",
	paramtype2="facedir",
	walkable = false,
	selection_box = {
		type = "fixed",
		fixed = {-1/4, -1/2, -1/2, 1/4, 1.5, 0},
	},
	mesh = "advtrains_across.obj",
	tiles = {{name="advtrains_across_anim.png", animation={type="vertical_frames", aspect_w=64, aspect_h=64, length=1.0}}},
	drop="advtrains:across_off",
	description=attrans("Andrew's Cross (on) (you hacker you)"),
	groups = {
		cracky=3,
		not_blocking_trains=1,
		save_in_at_nodedb=1,
		not_in_creative_inventory=1,
	},
	light_source = 1,
	sunlight_propagates=true,
	mesecons = {effector = {
		rules = advtrains.meseconrules,
		action_off = function (pos, node)
			advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
		end
	}},
	luaautomation = {
		getstate = "on",
		setstate = function(pos, node, newstate)
			if newstate == "off" then
				advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
			end
		end,
	},
	on_rightclick=function(pos, node, player)
		if minetest.check_player_privs(player:get_player_name(), {train_operator=true}) then
			advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
		end
	end,
})

minetest.register_abm(
	{
        label = "Sound for Level Crossing",
        nodenames = {"advtrains:across_on"},
        interval = 3,
        chance = 1,
        action = function(pos, node, active_object_count, active_object_count_wider)
			minetest.sound_play("advtrains_crossing_bell", {
				pos = pos,
				gain = 1.0, -- default
				max_hear_distance = 16, -- default, uses an euclidean metric
			})
        end,
    }
)
class="hl opt">("advtrains.is_creative() called without name parameter!") end if minetest.check_player_privs(name, {creative=true}) then return true end return minetest.settings:get_bool("creative_mode") end function advtrains.is_damage_enabled(name) if not name then error("advtrains.is_damage_enabled() called without name parameter!") end if minetest.check_player_privs(name, "train_admin") then return false end return minetest.settings:get_bool("enable_damage") end function advtrains.ms_to_kmh(speed) return speed * 3.6 end -- 4 possible inputs: -- integer: just do that modulo calculation -- table with c set: rotate c -- table with tables: rotate each -- table with integers: rotate each (probably no use case) function advtrains.rotate_conn_by(conn, rotate) if tonumber(conn) then return (conn+rotate)%AT_CMAX elseif conn.c then return { c = (conn.c+rotate)%AT_CMAX, y = conn.y} end local tmp={} for connid, data in ipairs(conn) do tmp[connid]=advtrains.rotate_conn_by(data, rotate) end return tmp end function advtrains.oppd(dir) return advtrains.rotate_conn_by(dir, AT_CMAX/2) end --conn_to_match like rotate_conn_by --other_conns have to be a table of conn tables! function advtrains.conn_matches_to(conn, other_conns) if tonumber(conn) then for connid, data in ipairs(other_conns) do if advtrains.oppd(conn) == data.c then return connid end end return false elseif conn.c then for connid, data in ipairs(other_conns) do local cmp = advtrains.oppd(conn) if cmp.c == data.c and (cmp.y or 0) == (data.y or 0) then return connid end end return false end local tmp={} for connid, data in ipairs(conn) do local backmatch = advtrains.conn_matches_to(data, other_conns) if backmatch then return backmatch, connid end --returns <connid of other rail> <connid of this rail> end return false end -- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid -- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent> -- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided. function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on) local this_pos = advtrains.round_vector_floor_y(this_posnr) local this_conns = this_conns_p if not this_conns then _, this_conns = advtrains.get_rail_info_at(this_pos) end if not conn_idx then for coni, _ in ipairs(this_conns) do local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni) if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end end return nil end local conn = this_conns[conn_idx] local conn_y = conn.y or 0 local adj_pos = advtrains.dirCoordSet(this_pos, conn.c); while conn_y>=1 do conn_y = conn_y - 1 adj_pos.y = adj_pos.y + 1 end local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on) if not nextnode_ok then adj_pos.y = adj_pos.y - 1 conn_y = conn_y + 1 nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on) if not nextnode_ok then return nil end end local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns) if adj_connid then return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns end return nil end -- when a train enters a rail on connid 'conn', which connid will it go out? -- nconns: number of connections in connection table: -- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub) -- returns: connid_out local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}} function advtrains.get_matching_conn(conn, nconns) return connlku[nconns][conn] end function advtrains.random_id() local idst="" for i=0,5 do idst=idst..(math.random(0,9)) end return idst end -- Shorthand for pos_to_string and round_vector_floor_y function advtrains.roundfloorpts(pos) return minetest.pos_to_string(advtrains.round_vector_floor_y(pos)) end -- insert an element into a table if it does not yet exist there -- equalfunc is a function to compare equality, defaults to == -- returns true if the element was inserted function advtrains.insert_once(tab, elem, equalfunc) for _,e in pairs(tab) do if equalfunc and equalfunc(elem, e) or e==elem then return false end end tab[#tab+1] = elem return true end local hext = { [0]="0",[1]="1",[2]="2",[3]="3",[4]="4",[5]="5",[6]="6",[7]="7",[8]="8",[9]="9",[10]="A",[11]="B",[12]="C",[13]="D",[14]="E",[15]="F"} local dect = { ["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9,["A"]=10,["B"]=11,["C"]=12,["D"]=13,["E"]=14,["F"]=15} local f = atfloor local function hex(i) local x=i+32768 local c4 = x % 16 x = f(x / 16) local c3 = x % 16 x = f(x / 16) local c2 = x % 16 x = f(x / 16) local c1 = x % 16 return (hext[c1]) .. (hext[c2]) .. (hext[c3]) .. (hext[c4]) end local function c(s,i) return dect[string.sub(s,i,i)] end local function dec(s) return (c(s,1)*4096 + c(s,2)*256 + c(s,3)*16 + c(s,4))-32768 end -- Takes a position vector and outputs a encoded value suitable as table index -- This is essentially a hexadecimal representation of the position (+32768) -- Order (YYY)YXXXXZZZZ function advtrains.encode_pos(pos) return hex(pos.y) .. hex(pos.x) .. hex(pos.z) end -- decodes a position encoded with encode_pos function advtrains.decode_pos(pts) if not pts or not #pts==6 then return nil end local stry = string.sub(pts, 1,4) local strx = string.sub(pts, 5,8) local strz = string.sub(pts, 9,12) return vector.new(dec(strx), dec(stry), dec(strz)) end --[[ Benchmarking code local tdt = {} local tlt = {} local tet = {} for i=1,1000000 do tdt[i] = vector.new(math.random(-65536, 65535), math.random(-65536, 65535), math.random(-65536, 65535)) if i%1000 == 0 then tlt[#tlt+1] = tdt[i] end end local t1=os.clock() for i=1,1000000 do local pe = advtrains.encode_pos(tdt[i]) local pb = advtrains.decode_pos(pe) tet[pe] = i end for i,v in ipairs(tlt) do local lk = tet[advtrains.encode_pos(v)] end atdebug("endec",os.clock()-t1,"s") tet = {} t1=os.clock() for i=1,1000000 do local pe = minetest.pos_to_string(tdt[i]) local pb = minetest.string_to_pos(pe) tet[pe] = i end for i,v in ipairs(tlt) do local lk = tet[minetest.pos_to_string(v)] end atdebug("pts",os.clock()-t1,"s") --Results: --2018-11-29 16:57:08: ACTION[Main]: [advtrains]endec 1.786451 s --2018-11-29 16:57:10: ACTION[Main]: [advtrains]pts 2.566377 s ]] -- Function to check whether a position is near (within range of) any player function advtrains.position_in_range(pos, range) if not pos then return true end for _,p in pairs(minetest.get_connected_players()) do if vector.distance(p:get_pos(),pos)<=range then return true end end return false end local active_node_range = tonumber(minetest.settings:get("active_block_range"))*16 + 16 -- Function to check whether node at position(pos) is "loaded"/"active" -- That is, whether it is within the active_block_range to a player if minetest.is_block_active then -- define function differently whether minetest.is_block_active is available or not advtrains.is_node_loaded = minetest.is_block_active else function advtrains.is_node_loaded(pos) if advtrains.position_in_range(pos, active_node_range) then return true end end end