aboutsummaryrefslogtreecommitdiff
path: root/advtrains
diff options
context:
space:
mode:
authororwell96 <orwell@bleipb.de>2017-12-18 21:44:36 +0100
committerorwell96 <orwell@bleipb.de>2017-12-18 23:09:23 +0100
commit46c4447da089146c662f217bf3269d78d4c462c2 (patch)
treee846a588b6ddfb03c456b0c3c4d0653cd3402b85 /advtrains
parentfaa60e2bd4d35054f23fda68c06a601f2a197257 (diff)
downloadadvtrains-46c4447da089146c662f217bf3269d78d4c462c2.tar.gz
advtrains-46c4447da089146c662f217bf3269d78d4c462c2.tar.bz2
advtrains-46c4447da089146c662f217bf3269d78d4c462c2.zip
Rewrite rail connection system...
...to support an arbitrary number of connections for rails, which leads to these new features: - switches now get recognized by the trackworker correctly - ability to add real rail crosses During this, I also rewrote the rail registering system and the conway function (important part of path prediction) Note, developers: the track preset format changed, you might need to rewrite them according to the presets in tracks.lua if you wrote your own (possibly breaks advcarts)
Diffstat (limited to 'advtrains')
-rw-r--r--advtrains/atc.lua8
-rw-r--r--advtrains/helpers.lua197
-rw-r--r--advtrains/init.lua12
-rw-r--r--advtrains/nodedb.lua26
-rw-r--r--advtrains/path.lua82
-rw-r--r--advtrains/signals.lua28
-rw-r--r--advtrains/trackplacer.lua53
-rw-r--r--advtrains/tracks.lua480
-rw-r--r--advtrains/trainlogic.lua57
-rw-r--r--advtrains/wagons.lua9
10 files changed, 474 insertions, 478 deletions
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index c8f7c90..bdcf144 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -124,8 +124,8 @@ advtrains.atc_function = function(def, preset, suffix, rotation)
meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
local pts=minetest.pos_to_string(pos)
- local _, conn1=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
- atc.controllers[pts]={command=fields.command, arrowconn=conn1}
+ local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ atc.controllers[pts]={command=fields.command, arrowconn=conns[1].c}
if advtrains.detector.on_node[pts] then
atc.send_command(pos)
end
@@ -145,8 +145,8 @@ function atc.get_atc_controller_formspec(pos, meta)
local command=meta:get_string("command")
local command_on=meta:get_string("command_on")
local channel=meta:get_string("channel")
- local formspec="size[8,6]"..
- "dropdown[0,0;3;mode;static,mesecon,digiline;"..mode.."]"
+ local formspec="size[8,6]"
+ -- "dropdown[0,0;3;mode;static,mesecon,digiline;"..mode.."]"
if mode<3 then
formspec=formspec.."field[0.5,1.5;7,1;command;"..attrans("Command")..";"..minetest.formspec_escape(command).."]"
if tonumber(mode)==2 then
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index f2e969f..3864d81 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -52,97 +52,6 @@ function atround(number)
return math.floor(number+0.5)
end
---vertical_transmit:
---[[
-rely1, rely2 tell to which height the connections are pointed to. 1 means it will go up the next node
-
-]]
-
-function advtrains.conway(midreal, prev, drives_on)--in order prev,mid,return
- local mid=advtrains.round_vector_floor_y(midreal)
-
- local midnode_ok, middir1, middir2, midrely1, midrely2=advtrains.get_rail_info_at(mid, drives_on)
- if not midnode_ok then
- return nil
- end
-
- local next, chkdir, chkrely, y_offset
- y_offset=0
- --atprint(" in order mid1,mid2",middir1,middir2)
- --try if it is dir1
- local cor1=advtrains.dirCoordSet(mid, middir2)--<<<<
- if cor1.x==prev.x and cor1.z==prev.z then--this was previous
- next=advtrains.dirCoordSet(mid, middir1)
- if midrely1>=1 then
- next.y=next.y+1
- --atprint("found midrely1 to be >=1: next is now "..(next and minetest.pos_to_string(next) or "nil"))
- y_offset=1
- end
- chkdir=middir1
- chkrely=midrely1
- --atprint("dir2 applied next pos:",minetest.pos_to_string(next),"(chkdir is ",chkdir,")")
- end
- --dir2???
- local cor2=advtrains.dirCoordSet(mid, middir1)--<<<<
- if atround(cor2.x)==atround(prev.x) and atround(cor2.z)==atround(prev.z) then
- next=advtrains.dirCoordSet(mid, middir2)--dir2 wird überprüft, alles gut.
- if midrely2>=1 then
- next.y=next.y+1
- --atprint("found midrely2 to be >=1: next is now "..(next and minetest.pos_to_string(next) or "nil"))
- y_offset=1
- end
- chkdir=middir2
- chkrely=midrely2
- --atprint(" dir2 applied next pos:",minetest.pos_to_string(next),"(chkdir is ",chkdir,")")
- end
- --atprint("dir applied next pos: "..(next and minetest.pos_to_string(next) or "nil").."(chkdir is "..(chkdir or "nil")..", y-offset "..y_offset..")")
- --is there a next
- if not next then
- --atprint("in conway: no next rail(nil), returning!")
- return nil
- end
-
- local nextnode_ok, nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(next), drives_on)
-
- --is it a rail?
- if(not nextnode_ok) then
- --atprint("in conway: next "..minetest.pos_to_string(next).." not a rail, trying one node below!")
- next.y=next.y-1
- y_offset=y_offset-1
-
- nextnode_ok, nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(next), drives_on)
- if(not nextnode_ok) then
- --atprint("in conway: one below "..minetest.pos_to_string(next).." is not a rail either, returning!")
- return nil
- end
- end
-
- --is this next rail connecting to the mid?
- if not ( (((nextdir1+8)%16)==chkdir and nextrely1==chkrely-y_offset) or (((nextdir2+8)%16)==chkdir and nextrely2==chkrely-y_offset) ) then
- --atprint("in conway: next "..minetest.pos_to_string(next).." not connecting, trying one node below!")
- next.y=next.y-1
- y_offset=y_offset-1
-
- nextnode_ok, nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(next), drives_on)
- if(not nextnode_ok) then
- --atprint("in conway: (at connecting if check again) one below "..minetest.pos_to_string(next).." is not a rail either, returning!")
- return nil
- end
- if not ( (((nextdir1+8)%16)==chkdir and nextrely1==chkrely) or (((nextdir2+8)%16)==chkdir and nextrely2==chkrely) ) then
- --atprint("in conway: one below "..minetest.pos_to_string(next).." rail not connecting, returning!")
- --atprint(" in order mid1,2,next1,2,chkdir "..middir1.." "..middir2.." "..nextdir1.." "..nextdir2.." "..chkdir)
- return nil
- end
- end
-
- --atprint("conway found rail.")
- return vector.add(advtrains.round_vector_floor_y(next), {x=0, y=nextrailheight, z=0}), chkdir
-end
---TODO use this
-function advtrains.oppd(dir)
- return ((dir+8)%16)
-end
-
function advtrains.round_vector_floor_y(vec)
return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
end
@@ -176,6 +85,20 @@ function advtrains.yawToAnyDir(yaw)
end
return min_conn
end
+function advtrains.yawToClosestConn(yaw, conns)
+ local min_connid, min_diff=1, 10
+ for connid, conn in ipairs(conns) do
+ local uvec = vector.normalize(advtrains.dirToCoord(conn.c))
+ local yaw1 = math.atan2(uvec.z, uvec.x)
+ local diff = advtrains.minAngleDiffRad(yaw, yaw1)
+ if diff < min_diff then
+ min_connid = connid
+ min_diff = diff
+ end
+ end
+ return min_connid
+end
+
function advtrains.minAngleDiffRad(r1, r2)
local pi, pi2 = math.pi, 2*math.pi
@@ -300,3 +223,95 @@ 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
+
+--TODO use this
+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
+
+
+-- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>
+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 = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
+ if adj_pos then return adj_pos,adj_conn_idx,coni,nry 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
+ end
+ return nil
+end
+
+local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}}
+function advtrains.get_matching_conn(conn, nconns)
+ return connlku[nconns][conn]
+end
+
diff --git a/advtrains/init.lua b/advtrains/init.lua
index 5975b81..e3a19e4 100644
--- a/advtrains/init.lua
+++ b/advtrains/init.lua
@@ -7,6 +7,9 @@ end
--advtrains
+--Constant for maximum connection value/division of the circle
+AT_CMAX = 16
+
advtrains = {trains={}, wagon_save={}, player_to_train_mapping={}}
--pcall
@@ -99,6 +102,13 @@ atwarn=function(t, ...)
end
sid=function(id) if id then return string.sub(id, -6) end end
+--TEMP
+atdebug=function(t, ...)
+ local text=advtrains.print_concat_table({t, ...})
+ minetest.log("action", "[advtrains]"..text)
+ minetest.chat_send_all("[advtrains]"..text)
+ end
+
if minetest.settings:get_bool("advtrains_enable_debugging") then
atprint=function(t, ...)
local context=advtrains.atprint_context_tid or ""
@@ -135,7 +145,7 @@ advtrains.meseconrules =
{x=0, y=-2, z=0}}
-
+dofile(advtrains.modpath.."/path.lua")
dofile(advtrains.modpath.."/trainlogic.lua")
dofile(advtrains.modpath.."/trainhud.lua")
dofile(advtrains.modpath.."/trackplacer.lua")
diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua
index 2f014f8..763edbc 100644
--- a/advtrains/nodedb.lua
+++ b/advtrains/nodedb.lua
@@ -132,9 +132,12 @@ function ndb.get_node_raw(pos)
end
-function ndb.swap_node(pos, node)
+function ndb.swap_node(pos, node, no_inval)
minetest.swap_node(pos, node)
ndb.update(pos, node)
+ if not no_inval then
+ advtrains.invalidate_all_paths(pos)
+ end
end
function ndb.update(pos, pnode)
@@ -175,30 +178,15 @@ function advtrains.get_rail_info_at(pos, drives_on)
local rdp=advtrains.round_vector_floor_y(pos)
local node=ndb.get_node_or_nil(rdp)
+ if not node then return end
- --still no node?
- --advtrains.trackdb is nil when there's no data available.
- if not node then
- if advtrains.trackdb then
- --try raildb (see trackdb_legacy.lua)
- local dbe=(advtrains.trackdb[rdp.y] and advtrains.trackdb[rdp.y][rdp.x] and advtrains.trackdb[rdp.y][rdp.x][rdp.z])
- if dbe then
- for tt,_ in pairs(drives_on) do
- if not dbe.tracktype or tt==dbe.tracktype then
- return true, dbe.conn1, dbe.conn2, dbe.rely1 or 0, dbe.rely2 or 0, dbe.railheight or 0
- end
- end
- end
- end
- return nil
- end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, drives_on)) then
return false
end
- local conn1, conn2, rely1, rely2, railheight, tracktype=advtrains.get_track_connections(node.name, node.param2)
+ local conns, railheight, tracktype=advtrains.get_track_connections(node.name, node.param2)
- return true, conn1, conn2, rely1, rely2, railheight
+ return true, conns, railheight
end
ndb.run_lbm = function(pos, node)
diff --git a/advtrains/path.lua b/advtrains/path.lua
new file mode 100644
index 0000000..506f27f
--- /dev/null
+++ b/advtrains/path.lua
@@ -0,0 +1,82 @@
+-- path.lua
+-- Functions for pathpredicting, put in a separate file.
+
+function advtrains.conway(midreal, prev, drives_on)--in order prev,mid,return
+ local mid=advtrains.round_vector_floor_y(midreal)
+
+ local midnode_ok, midconns=advtrains.get_rail_info_at(mid, drives_on)
+ if not midnode_ok then
+ return nil
+ end
+ local pconnid
+ for connid, conn in ipairs(midconns) do
+ local tps = advtrains.dirCoordSet(mid, conn.c)
+ if tps.x==prev.x and tps.z==prev.z then
+ pconnid=connid
+ end
+ end
+ local nconnid = advtrains.get_matching_conn(pconnid, #midconns)
+
+ local next, next_connid, _, nextrailheight = advtrains.get_adjacent_rail(mid, midconns, nconnid, drives_on)
+ if not next then
+ return nil
+ end
+ return vector.add(advtrains.round_vector_floor_y(next), {x=0, y=nextrailheight, z=0}), midconns[nconnid].c
+end
+
+--about regular: Used by 1. to ensure path gets generated far enough, since end index is not known at this time.
+function advtrains.pathpredict(id, train, regular)
+ --TODO duplicate code under 5b.
+ local path_pregen=10
+
+ local gen_front= path_pregen
+ local gen_back= - train.trainlen - path_pregen
+ if regular then
+ gen_front=math.max(train.index, train.detector_old_index) + path_pregen
+ gen_back=math.min(train.end_index, train.detector_old_end_index) - path_pregen
+ end
+
+ local maxn=train.path_extent_max or 0
+ while maxn < gen_front do--pregenerate
+ local conway
+ if train.max_index_on_track == maxn then
+ --atprint("maxn conway for ",maxn,train.path[maxn],maxn-1,train.path[maxn-1])
+ conway=advtrains.conway(train.path[maxn], train.path[maxn-1], train.drives_on)
+ end
+ if conway then
+ train.path[maxn+1]=conway
+ train.max_index_on_track=maxn+1
+ else
+ --do as if nothing has happened and preceed with path
+ --but do not update max_index_on_track
+ atprint("over-generating path max to index ",(maxn+1)," (position ",train.path[maxn]," )")
+ train.path[maxn+1]=vector.add(train.path[maxn], vector.subtract(train.path[maxn], train.path[maxn-1]))
+ end
+ train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn])
+ maxn=maxn+1
+ end
+ train.path_extent_max=maxn
+
+ local minn=train.path_extent_min or -1
+ while minn > gen_back do
+ local conway
+ if train.min_index_on_track == minn then
+ --atprint("minn conway for ",minn,train.path[minn],minn+1,train.path[minn+1])
+ conway=advtrains.conway(train.path[minn], train.path[minn+1], train.drives_on)
+ end
+ if conway then
+ train.path[minn-1]=conway
+ train.min_index_on_track=minn-1
+ else
+ --do as if nothing has happened and preceed with path
+ --but do not update min_index_on_track
+ atprint("over-generating path min to index ",(minn-1)," (position ",train.path[minn]," )")
+ train.path[minn-1]=vector.add(train.path[minn], vector.subtract(train.path[minn], train.path[minn+1]))
+ end
+ train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1])
+ minn=minn-1
+ end
+ train.path_extent_min=minn
+ if not train.min_index_on_track then train.min_index_on_track=-1 end
+ if not train.max_index_on_track then train.max_index_on_track=0 end
+end
diff --git a/advtrains/signals.lua b/advtrains/signals.lua
index a42f5e7..b01314e 100644
--- a/advtrains/signals.lua
+++ b/advtrains/signals.lua
@@ -37,12 +37,12 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
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})
+ 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})
+ advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
end
end,
})
@@ -72,20 +72,20 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
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})
+ 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})
+ 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})
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true)
end
end,
})
@@ -121,20 +121,20 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
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})
+ 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})
+ 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})
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true)
end
end,
})
@@ -167,20 +167,20 @@ minetest.register_node("advtrains:across_off", {
mesecons = {effector = {
rules = advtrains.meseconrules,
action_on = function (pos, node)
- advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2})
+ 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})
+ 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})
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
end
end,
})
@@ -208,20 +208,20 @@ minetest.register_node("advtrains:across_on", {
mesecons = {effector = {
rules = advtrains.meseconrules,
action_off = function (pos, node)
- advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2})
+ 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})
+ 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})
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
end
end,
})
diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua
index c484440..c61bbb4 100644
--- a/advtrains/trackplacer.lua
+++ b/advtrains/trackplacer.lua
@@ -71,31 +71,35 @@ end
local function istrackandbc(pos_p, conn)
local tpos = pos_p
- local cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn))
- local bconn=(conn+8)%16
+ local cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c))
if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then
- local cconn1, cconn2=advtrains.get_track_connections(cnode.name, cnode.param2)
- return cconn1==bconn or cconn2==bconn
+ local cconns=advtrains.get_track_connections(cnode.name, cnode.param2)
+ return advtrains.conn_matches_to(conn, cconns)
end
--try the same 1 node below
tpos = {x=tpos.x, y=tpos.y-1, z=tpos.z}
- cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn))
- bconn=(conn+8)%16
+ cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c))
if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then
- local cconn1, cconn2=advtrains.get_track_connections(cnode.name, cnode.param2)
- return cconn1==bconn or cconn2==bconn
+ local cconns=advtrains.get_track_connections(cnode.name, cnode.param2)
+ return advtrains.conn_matches_to(conn, cconns)
end
return false
end
function tp.find_already_connected(pos)
local dnode=minetest.get_node(pos)
- local dconn1, dconn2=advtrains.get_track_connections(dnode.name, dnode.param2)
- if istrackandbc(pos, dconn1) and istrackandbc(pos, dconn2) then return dconn1, dconn2
- elseif istrackandbc(pos, dconn1) then return dconn1
- elseif istrackandbc(pos, dconn2) then return dconn2
+ local dconns=advtrains.get_track_connections(dnode.name, dnode.param2)
+ local found_conn
+ for connid, conn in ipairs(dconns) do
+ if istrackandbc(pos, conn) then
+ if found_conn then --we found one in previous iteration
+ return true, true --signal that it's connected
+ else
+ found_conn = conn.c
+ end
+ end
end
- return nil
+ return found_conn
end
function tp.rail_and_can_be_bent(originpos, conn)
local pos=advtrains.dirCoordSet(originpos, conn)
@@ -105,16 +109,15 @@ function tp.rail_and_can_be_bent(originpos, conn)
return false
end
local ndef=minetest.registered_nodes[node.name]
- local nnpref = ndef and ndef.nnpref
+ local nnpref = ndef and ndef.at_nnpref
if not nnpref then return false end
local tr=tp.tracks[nnpref]
if not tr then return false end
if not tr.modify[node.name] then
--we actually can use this rail, but only if it already points to the desired direction.
- local bconn=(conn+8)%16
if advtrains.is_track_and_drives_on(node.name, advtrains.all_tracktypes) then
- local cconn1, cconn2=advtrains.get_track_connections(node.name, node.param2)
- return cconn1==bconn or cconn2==bconn
+ local cconns=advtrains.get_track_connections(node.name, node.param2)
+ return advtrains.conn_matches_to(conn, cconns)
end
end
--rail at other end?
@@ -135,16 +138,16 @@ function tp.rail_and_can_be_bent(originpos, conn)
end
function tp.bend_rail(originpos, conn)
local pos=advtrains.dirCoordSet(originpos, conn)
- local newdir=(conn+8)%16
+ local newdir=advtrains.oppd(conn)
local node=minetest.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
- local nnpref = ndef and ndef.nnpref
+ local nnpref = ndef and ndef.at_nnpref
if not nnpref then return false end
local tr=tp.tracks[nnpref]
if not tr then return false end
--is rail already connected? no need to bend.
- local conn1, conn2=advtrains.get_track_connections(node.name, node.param2)
- if newdir==conn1 or newdir==conn2 then
+ local conns=advtrains.get_track_connections(node.name, node.param2)
+ if advtrains.conn_matches_to(conn, conns) then
return
end
--rail at other end?
@@ -309,6 +312,7 @@ minetest.register_craftitem("advtrains:trackworker",{
if not name then
return
end
+ local has_aux1_down = placer:get_player_control().aux1
if pointed_thing.type=="node" then
local pos=pointed_thing.under
if advtrains.is_protected(pos, name) then
@@ -319,6 +323,13 @@ minetest.register_craftitem("advtrains:trackworker",{
--if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.get_train_at_pos(pos) then return end
+
+ if has_aux1_down then
+ --feature: flip the node by 180°
+ --i've always wanted this!
+ advtrains.ndb.swap_node(pos, {name=node.name, param2=(node.param2+2)%4})
+ return
+ end
local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
--atprint(node.name.."\npattern recognizes:"..nodeprefix.." / "..railtype.." / "..rotation)
diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua
index 1cbbc0b..6d218b1 100644
--- a/advtrains/tracks.lua
+++ b/advtrains/tracks.lua
@@ -34,90 +34,80 @@ vert2={
advtrains.all_tracktypes={}
--definition preparation
-local function conns(c1, c2, r1, r2, rh, rots) return {conn1=c1, conn2=c2, rely1=r1, rely2=r2, railheight=rh} end
+local function conns(c1, c2, r1, r2) return {{c=c1, y=r1}, {c=c2, y=r2}} end
+local function conns3(c1, c2, c3, r1, r2, r3) return {{c=c1, y=r1}, {c=c2, y=r2}, {c=c3, y=r3}} end
advtrains.ap={}
advtrains.ap.t_30deg_flat={
regstep=1,
variant={
- st=conns(0,8),
- cr=conns(0,7),
- swlst=conns(0,8),
- swlcr=conns(0,7),
- swrst=conns(0,8),
- swrcr=conns(0,9),
- },
- description={
- st="straight",
- cr="curve",
- swlst="left switch (straight)",
- swlcr="left switch (curve)",
- swrst="right switch (straight)",
- swrcr="right switch (curve)",
- },
- switch={
- swlst="swlcr",
- swlcr="swlst",
- swrst="swrcr",
- swrcr="swrst",
- },
- switchmc={
- swlst="on",
- swlcr="off",
- swrst="on",
- swrcr="off",
- },
- switchst={
- swlst="st",
- swlcr="cr",
- swrst="st",
- swrcr="cr",
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "cr",
+ },
+ cr={
+ conns = conns(0,7),
+ desc = "curve",
+ tpdouble = true,
+ trackworker = "swlst",
+ },
+ swlst={
+ conns = conns3(0,8,7),
+ desc = "left switch (straight)",
+ trackworker = "swrst",
+ switchalt = "swlcr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swlcr={
+ conns = conns3(0,7,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "swlst",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ swrst={
+ conns = conns3(0,8,9),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "swrcr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swrcr={
+ conns = conns3(0,9,8),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "swrst",
+ switchmc = "off",
+ switchst = "cr",
+ },
},
regtp=true,
- trackplacer={
- st=true,
- cr=true,
- },
- tpsingle={
- st=true,
- },
tpdefault="st",
trackworker={
["swrcr"]="st",
["swrst"]="st",
- ["st"]="cr",
["cr"]="swlst",
["swlcr"]="swrcr",
["swlst"]="swrst",
},
rotation={"", "_30", "_45", "_60"},
- slopenodes={},
- increativeinv={},
}
advtrains.ap.t_30deg_slope={
regstep=1,
variant={
- vst1=conns(8,0,0,0.5,0.25),
- vst2=conns(8,0,0.5,1,0.75),
- vst31=conns(8,0,0,0.33,0.16),
- vst32=conns(8,0,0.33,0.66,0.5),
- vst33=conns(8,0,0.66,1,0.83),
- },
- description={
- vst1="steep uphill 1/2",
- vst2="steep uphill 2/2",
- vst31="uphill 1/3",
- vst32="uphill 2/3",
- vst33="uphill 3/3",
+ vst1={conns = conns(8,0,0,0.5), rail_y = 0.25, desc = "steep uphill 1/2", slope=true},
+ vst2={conns = conns(8,0,0.5,1), rail_y = 0.75, desc = "steep uphill 2/2", slope=true},
+ vst31={conns = conns(8,0,0,0.33), rail_y = 0.16, desc = "uphill 1/3", slope=true},
+ vst32={conns = conns(8,0,0.33,0.66), rail_y = 0.5, desc = "uphill 2/3", slope=true},
+ vst33={conns = conns(8,0,0.66,1), rail_y = 0.83, desc = "uphill 3/3", slope=true},
},
- switch={},
- switchmc={},
- switchst={},
regsp=true,
- slopenodes={
- vst1=true, vst2=true,
- vst31=true, vst32=true, vst33=true,
- },
slopeplacer={
[2]={"vst1", "vst2"},
[3]={"vst31", "vst32", "vst33"},
@@ -134,125 +124,91 @@ advtrains.ap.t_30deg_slope={
advtrains.ap.t_30deg_straightonly={
regstep=1,
variant={
- st=conns(0,8),
- },
- description={
- st="straight",
- },
- switch={
- },
- switchmc={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
},
regtp=true,
- trackplacer={
- },
- tpsingle={
- },
tpdefault="st",
- trackworker={
- ["st"]="st",
- },
- trackplacer={
- st=true,
- },
- tpsingle={
- st=true,
- },
- slopenodes={},
rotation={"", "_30", "_45", "_60"},
- increativeinv={"st"},
}
advtrains.ap.t_30deg_straightonly_noplacer={
regstep=1,
variant={
- st=conns(0,8),
- },
- description={
- st="straight",
- },
- switch={
- },
- switchmc={
- },
- regtp=false,
- trackplacer={
- },
- tpsingle={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
},
tpdefault="st",
- trackworker={
- ["st"]="st",
- },
- trackplacer={
- st=true,
- },
- tpsingle={
- st=true,
- },
- slopenodes={},
rotation={"", "_30", "_45", "_60"},
- increativeinv={"st"},
}
advtrains.ap.t_45deg={
regstep=2,
variant={
- st=conns(0,8),
- cr=conns(0,6),
- swlst=conns(0,8),
- swlcr=conns(0,6),
- swrst=conns(0,8),
- swrcr=conns(0,10),
- vst1=conns(8,0,0,0.5,0.25),
- vst2=conns(8,0,0.5,1,0.75),
- },
- description={
- st="straight",
- cr="curve",
- swlst="left switch (straight)",
- swlcr="left switch (curve)",
- swrst="right switch (straight)",
- swrcr="right switch (curve)",
- vst1="vertical lower node",
- vst2="vertical upper node",
- },
- switch={
- swlst="swlcr",
- swlcr="swlst",
- swrst="swrcr",
- swrcr="swrst",
- },
- switchmc={
- swlst="on",
- swlcr="off",
- swrst="on",
- swrcr="off",
- },
- switchst={
- swlst="st",
- swlcr="cr",
- swrst="st",
- swrcr="cr",
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "cr",
+ },
+ cr={
+ conns = conns(0,6),
+ desc = "curve",
+ tpdouble = true,
+ trackworker = "swlst",
+ },
+ swlst={
+ conns = conns3(0,8,6),
+ desc = "left switch (straight)",
+ trackworker = "swrst",
+ switchalt = "swlcr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swlcr={
+ conns = conns3(0,6,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "swlst",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ swrst={
+ conns = conns3(0,8,10),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "swrcr",
+ switchmc = "on",
+ switchst = "st",
+QMBʳocNaOFOt`sS+Y[*ܸqwov'&s&?(@A8^c;mA'x"eebԥۧ´al!9P7 CA"`y{^1#Pa8PC"h't?sw**JuNѐ1=aX`W[dkc7Գ=5)I 鑥üB# NRS hijh2_vCV艨8dx2 XLfu6DR^,]Whd |+-W&Ap8ROJRIJK,̌QRNSO4?t(ӞͿi0b@#kgyJ [ǿ 0Ϳ-lM\M'%O2TaK_R"\ <93A5Шƪ~*Pn^,N|Jӓm6:? +pcH򳶆QS)Fn=*VV{xžVD0Y)_'P 8Bb*&EInW߫ZoJrLhAèGT/s`d"S(`Ц*<~v+ݾJ] 蕤gbILU=N:]5eg<*.2LjftU(!0~!uʏ%=VKFpDXA p/}F&}hR9p_Gg&xo &ZTXaWӲ” _!``qo.BrDcw:#aЕ6@%el0}7:(VQ?l za]OzhPDC/a~PR/XY\O!<pmcֲ1rjgyiLZ{Y lnT]gժZjX*?}Lj(uKAECRR !%k\!Sro7=zW>W>/;\r~X{^M PU~B.=CL Cvޅ^ ϾՑe_ A'Y`!š^uϥ2wZ5M:bhFXe9v$u,g 왿E.>c˼tXAlncB ӣ6(,U ^rǣwү.4kV4ʻ?(1!ͩho!'3I]o(I7UٗDӧ'Wq^%J#'[@.8L3hC6?h\lE`!ORR>duQ-,y"{?ݎpDDO0qjZ@%k @K7N2 ?v'H6bH1d (m 1.0P tb)z2JquB~gfHi` ^!gozo\ܾQFB>{*@}#N$ߨ>>V?gy~K]|g<Hm< 8ؘ:/-Ӓ@ rL-6T7M*`[{)7iSe{`wC4*M^nrcX'l\`Or^/Bx|tEΞEx0i}hlPc030aHs#*PpĦot&K ( I*OiGc58 \g2h 5 ']NUO`Ty2bwIgTG@ dE]?@9zKKb@rvvYYdLXJR_.J9ȋrr)4;ziU:xq^ ƙ@we*dҸ{ϬʿhA~xŕ YBfHQnb|prbݒ1WM7/`DiHuדאZ;Wv9{Ii6 _G_6$0+p.J3nZ p[`C,{52nryp}!u)tԞ9G>ߑ,9u%$әWL_+J->x"l+B.~J{rgj!o'd35YkZb57UoiέF/Kӧ#ܺ}uthL'}aЦsoMf=f;4nȞMFٰzɲKtcu^O+rI)\-6hN=i!" 6U0P>ޏ,C *%*B#b{YRS<e+iȱL\jT\Pα>?ؕ_Vq|塻qɶ$OY:x`J\rND EGPu0l|"$ې1Ewo+M9i92h!$YYiIT|ڤ1vh^xLCS)*8x=h.ոd"uƔ!b`Q9B74hɂ,f8<Au.r2H߹xY~jx4nqєV3A>FKnohJH'ѽ^UlDE =jUoSNv֔_ux:·_zqN5ah\?i%BTQo[{̸g1ܕXS ήQ-q0Ho4|kvisSQƜQq~&֖BemICt<9Ciq]DY)j%GE>`\'Hb ikcx6 rPMȫxk0?΂.O̫'u%;HK ;qK;0{YP{54cV\6p~L#b,CiJJuDVt6+bEZL܄SS?IE{d^ԄEHH^SG`߿fMCEcpCjlrixj[ik߫Rw2DW ~a ;%q$[zO ̑+>j ',!&[C@lI5 iKȘBz$ f="lD霸X²K<:K73 ۂ vn`ta80}".i1 Rs8 4ʡ[#0αS7.Sg$n ]L;}2H<+vMBZUJv!i i YR5+ĩX3Ѿ;OT'<6̼OcQDTlASUμmĸXXRcpH_ |b}i'~ m \VGw4n:^şvI[ZCGع4lx[(FB}nQp,q8 bh2I|hOA J#sYѤ1>N+EnC&3|Nx L:$~QԗWx[̞00ЍX1 8ǁ+[,|7x.JM졢 4Y`ή,ơ'᧻qxrBK BLO(0@3y٬E.w$QGδjCRy~eʢ(Nʹ\Ne) `$bb>lMK4$#%%kox\qфʚ*O6]M]Q|{atDm1!~ӟ Cj`202w4CI1KJ(E6KU7Vk}"r@yw6ˡY3uIPP5=R>PJk5QOYRcUI)\~(O c( !e_{>+ӄKy@Y1jZ#똕9YE"+ͣ*#3WQZ\9> Hf!?/%]'/\ua`08F^Pۅwv%T #8B*?HdN 5.jRG!=d&z7(E3{7ܛ7x[}GEC)B[D٧H-[SކTu:-G  n'V}-!~ޣ 8}fi 1O%d<Ǫx"GѭZH/#v9L3Μrn|qhXt 4a2L7 Wnad>XMy ,Q]o੹Qd2!Z$sC,z ;#"lD̖jrN.,՟9g?N =<Ԋ|*UP‹,DԂ`=^ņ҇y궁0jD)AFKa1!\u3;ȲGx8׋>^O GȄ&v&7Ӥ)ZE9ԖRǻ+A?$C^{\ŚhRF7?vUlbslGC0Pp<ˎ/ <,>PL6$CCћu c>$JMx`q@~ଵ #{*CeyQsYvŊeykW#)%+qn!ƣ&5sC?NRs@neEFmqf#_3D5*!o^coÀʰqaJjuYlfѺ7w*ѫ送 ^w(.Xa&,O%?, m23 c Rr8|x]6y4ui-% @W٪Bϫ༚}XB,|9qhw%:u.owу"[k6m[n:;mAuieq"VN;cV23 :,zYs7 nYAݸ'(\٧ʎZ%g6Dtdq]_W1±- /-/.B TGi:um†\!w/.\k,o_ο^A!LR!p=tsb=PI 8Oգ~!>o8ŪbAnM#ފ0?\]ؒs t_2"k_(~N#dގ/iiO("=x V4ӶL53ڃ.'2 F7~D׿o#ƪn{I%NBt۳Z^16գqO[n(҇e袿-l zGRvn㉒2q5(?0x❿FZ0++:}-(+)+DϦ姂%?jbJlsu_ӓ~ԕm̫Kl7_Y9樔o Dx8h=0T i?yA0QhpX0ArxLMPA`@w Tn&(6߉ >(l=.} ޑGD]ÕT.}?΋gn\fVˉ6Z D%rͣ ^Uaʓ+U_nPIBKԌpb.vf|D928*=&"G9+ZJyqmBg&>9)"Wv+_ok.~y<mdSR0rΆ9-FƵ&ӈ30wj5:%E2i_yvv ҌB̖m)}UlƘ;-ՙ֔!*0<S s9vm)FXo(q#Wڙ96FJ'N(92x<`<39_2wZ~{44<3EqNSaYiA۞2)Jl9~9mX;02z㯩Sq^BMe3i .M0p?Q{Zd΃_k{xM vlJ(b!>'H'9ձA3Ew3K}ӽ-U 4=bQ;[L”,~|HxkKy[S7eDVỀv7||)eBXf4h ;C'b9=@Iרu $t*M@B HBSl(`A,`b *TDTĎbEĮػ&@ӻ~;;̛޼3_AXNÔo߯ Z681 :\cQ;^␐Q۵kftٝ{ELt5J:mU,ŻO(OԘ!ͼ30r+ I7>^R^R}Ŗ6Wfqxw:_TG' WBklqj]ΪԇP9̏}wF=35h,yf++2<,\7Hn{MQe#vq)ڤ>sb:GnijpBXKAjY9w/C]i8wM}K6>;<^ɬ'&k[_{ktT}EFKC.N=j>ɹ*\xuyn+-MS/=hoDPt~TFJLfϝ=Mi jX,,D\=Xhqʫ\RY:o㞋'0z2n"WgU9 zLxq?qaM4>Ϙ,ؖ5v_ozGw&dG`v[zՃJۺ)Ψ5}c><~bn&x:n]#_=Yyo^|0 BŝSU˯,Ӧ 5{$/Yq([6wS'Ai>/x,){f3hTjGçg?qJ'r&m70Mv6 e{l!fpn]W9,83ðj}֓眴;yW9;4,1=5̱M}ϝɵuWm:|fPՄCA{ݯ^o]+(ܙECx$Ou߱ѻFY8+sˁgE!/S8}%cNJ BVh~p,zn=glښ_yXQuR_N! l0@gv5kX6;VҩS`FUP*Ө7qqإyFУ)ۦsi Q,;׹z1EiMrwoN%Q173NӿU{ l귉S~>ܴ:W{[&Vţ5>X=pT=lOUfuҔ‘WՇ0 wSVZHpR4ͮ{+F|o;#X7qe9wJݛ'&(L@~&w,B'ۮT6Sl˜{a~XGI;ׇ;^8#b.nR1pasI_Wvbmʹ'f/zL ]N;ou{`D#]Ǣۢ& P>Q5Pj.^doҳGBPwnսu^촉1Y6HsF,CkyaUۦiNF>B0 ~[Mg4N v$36 9A:nڛmJ SߡHy۽ҙ{=En s%[G:>ix|0_iU[nsL )=Wl:eTǼe>s33tһ;Y!GX~he4;CM++nGͬ]7Gqi_W{5,ѐӝ]u<[a=Xءj;vt..]mCҚ$l e*M]´g3&y9>5R}Jm#Uv3O)x[rJ;ϟ5Fj^Rفv/h:-c_g8W||?{߶tʆɪ|[v+6n&>\o@N=1ñ]Jzp{?QV[Uxi>::J{= D +pZUvr'=puεg)fNuiQ]Wo?IьL欻fŤ:7c_ys.` cs9t\J:Y30w^tȗׇ"Tջw]ܫPњKfoȤyVܿq\bsf6_eiR-3_-۹"Tol7aX;ÍE[WU_Oi:h3n]5㎮;˄m+qß[[#F|>*fnU''O'8uɧ/>ͤ޷p^utWifn]tߝ$$>G"nl-yr5k~cҖ4ѧ+]̽vڇ[*oh ߘi삲so::u0z>+慸ޙ;z[?s#u<}0wk -TВDkgYuU۟74:ea{kϟ}(QWltvЦs[jB15<ֲuE0{G4N5$㴭T<ʔ7ZZ¸3dhnvnWjhLR l 9"ZC̣f26bcڷj5~>Οm\k\xqV "fY0JNo'uNޟ1$j$-_5nP)+'-Icԇ8^*x 0o}?t֍5!=K,yodzML3/yz0NYHaK6`?j7rԨ' fntꉋY9xԍF-{^ص>;ªdx Bw0Mf!s'Kw`yMD ϳo9DW!$GRP˨ @qH<&ʫ |3(̖3,hNե';dT?YvY;nt|Y6[[[~S&:Lzx@^'$Ǘ\ͣ^cOŔ3" v9|欌Y;ә>;?}P6Pl@qYXg_~#kN^|FhM]am'-͘9i6iCbT:Z;{Alu̼t 9GS,Lwx-_T=i֍=T9,Ϋ?:afa]rAn ;i*#Yn´:?jL"R[ 'Q)Sl3UoA=n2eA*?V򄏊iӛMJLZB.61(,;Ms+K9<%eɪJbfS.w\-7X3,LVWQA=q?@pAC/L|<$:lņ/z uG{~QXɾYfj/iϟ_PmWk%֘EUq*ًr4xUshjɵOnc,|SO'ε8v-T-x^}IqyM;3t㱫ү ֛DOqϚkmRQ8 őVY0-䔪>Ff^rjIrގ>!<Zkqʬ>Ln`N9#VЛBƔ!3+nn1>3a<.c='W{aCxΕ۷CW +,cr~ 9[;St- wXu.R(PLU:}R+oS6ݘq='MĞVC}>P}Еeann/v^1ysuu$14T]][f|1ggN<x,05?XrP[vL7>ɬo\Lhݮzx@]x#i~.9b:v1TnW3rڈ_v^Lm-uvt I zU?Z/"gɣgƧ5~aDj1$ j]#hc~9O)oKm!9,UE%[ylV&uʔv}YV%;<4(ˋ?;e݈ڹKhV{ LuVPbb)ϬzGT?Q|Q?lf_-BUO׳NӜ=-;;¥ nۇO}=`s-E?'_*:~Fu-7 ~)m_Ky}ǿ` niNv.Q°Ks/xk_Q}:恸O=Mn)Bͼl%]S/w%YNŌθZV"Ps;mQksݒ5oۂ Fon}ޡhT=Iդ/.NYoSjָVo*7Z9xe=yaщ5Q;U-(ѼLޝVwY\,RȌ"^5墰J趣w8k_OZLtDobHm%38uwic?8n;;ө'.'D7\?;Հ6!Җ́.8vV߫DqVK?;{?c:ٽL=j儥v޵/c"$SX@ zN?p%'S,Iu!"O{*=3e?t{'\;xcBr 3mbwc0{W]8 k>Խnj-= Jl=_Z]ׄ\sPYQk?P3y+pg1Li|5t[D`eڿjSbt 7;v`]5p-`YKܮEMN%/PG@-zBMB)o4}>ۈw&jQk.nl;8pẆz+S[zhO%KT/#U z E;׸]ѝo/Ұ.{Z顾T5Qp~gX{fjM3ɺM`,jSr[pu7ssv./{|o2yiض>tXe)sը>(rtm1]pvlp6=ɔ9%.J0媓AnQ^[a" 6騸0a:vKT*Ï C4vNA"s~3Ƣ쎘94mK sfidߜMm%W\ezI nyp$c{X.ֻ^9ORy C=Λtb^ؒfR9j)zKHyECAa;j^X+3OYlZ?IY7J|B! /vxAٺQ=|lWjz6Ѿ7>o]Z*5i@eM|oy5{vlft/ƙ,Yt!*}t-Gor_F*;K-k٤}>u5aHUϙ"|߻ڧd :k-iw-i2>B٥i,͝nV^Z{_m4>p tpԑMIbUiȍy+볦mR|z7{rî~eh\4xަ놽K9*cRBÊ\khwۂ.q'ߊ0WA<[OX٭ۮi#bo^o) cKңAY|ԭwhtF.~Ǖ#ݨ)J]tк3 ԰`cq\jKH.a-Vi>GTd s`]Rb| 7ܳczc̺5#=S9M0Abr>_tQ٫j*-MرC==<h0g~l*=~lsh:g1QjC>zLrE }ש: vv+3?y> tgAd]}d9:t۶eϥuxS܆Xnuez6uRqU}(zw+ir`sA \Jx4kxZ{s .G>biW.ʌRmG-81fhu9~3ܚâd*iiks&OX@ RpwX]=N[}Գ߰SR|,ywfa9XvU8O-$dn1c7.W+αB0vTxaK{Mq(c捾9#'=3iᚪ7=>&3m@_ʔ`ΰgm=p&bzJ VvOvض,?+@7%_|^*Yw=9?2>4}&I3qPʃŞ*WMg[di5]p"pȦbsٸk',xd9{ER:ܺioK#* hg[\eTބvtg %N-F|R1eOdU,us ز7h_,iRv*ƥJw=%躪 — 7>3;3u=*loaqٗ~{̵ns#;Y*SWz CWk->E5%O9>w3}٩xwMk3˓m͞iU־>jN/ffozqK-6q<4mwKҢukxp0c fecSªnio j)T5[Q`ۖo7l ϵn˞9LkͧY>#t}]qdջN*gJ&SV iZƘx Řv>i*_T3w [:R}碢յe2s_~'zspzQ їg~]X9w2{;{>Z>y,h2 nU:,!$eNA~tݘt/MKZ70^z: /LM,YVaS5Fٚ_yE҃:pӄg;l=gCϳsO ֧}8ƻg:* o?_2q8A:CˤQk1^l֐Ԛ ViVx(2Bk̀gFlCOdž+9m+5:Yf-Ζgvܷl`xs,Oe*:pdo;˄gǟ334|95k_`P1X7rfE\*GA7|Zwyޗ)N<}KpݮE'^K禪=N93n-|rI[^I~WK^gQ<)Ή58z]+"fe^tvRcWҐqRI%I7>8?=SC]{qٞia؅ 16e\ioavsNs,2~yȅ(CƯ9wx߮Lͺg רt$_ւ{Uz{7nCݚؓ淼 . &TĶQ_fp9 fɑm*Z{^k=4a^0i?S>Cs9G҇LO.㕬TWJ^xs0u! fGpvF=_;.?`<%pdo7|yT?IYsヘ&?[xGnݳ~..P1l 4NvjL.WWqԍ^qDc%vx*{ ws5qC[5;U0tTl5UifGJo~2gk˭#N8ͭ񾃔zwg{WKS-lUGBDsPa\Cwܡe6fa&My|>xojyz& Kv1-5>H T_M ?{iA9Ϭ=,l+%TSnmlf]i儉_.=xoBe-xnX7~2k[V)z9IQBAo>3vr+cƺ.մb d$>MO`1CݥmPxGå>?=d&UwŒ3UTXsV+߫^inәfxAC-w1UѽbKb#H|^Z@JŴXo:1}Y̛^ϧ98`oZd_5a{lnn׏X_qlp:Cg@zEAq-15K2;fԖ ӧeYXƬ|M~̌6ө]>ae[`)*mDv9z۟t9gMQnm}NμGFMݜĂ(X^N3FnT.ï}Hro4[zEgs;YiƋFX˚r6;)\ۧVU)JIJGLx/Gfp+LTΊ~[3]5m2 n&/,P77fQ5ҕzӽ=suyI sʑS[GlH)}6+g>1랧*cΥSN-|leF/oB /)A:z5 _਌5[20"9ϖo64[aF#/]UzżCR麯M5ۖYM$#g`:l-m}{sWZ:^*6Fwѕ ߡ Hs1jf׶?~4=p.a>xه0b=zXV3WnTt<:#uBNK޹̫Sk͚?1CatR~J`{sW$lݸk ")NƋYU3Zx8טc-8wuaUO$ZcbA= J5U.7E G5XeVnt9gí5[1hzz# /țdyS홅#wVqW6ϸ 0w-yhusJ凷 ~<1ܧk;gߢ;Տ[y$01ϼHxH7m$Wt>Z>p?xC֝ys 9z9#f,+atI.WPq& l-xԜ'&wP>uc_/yFժ4T}|ogRm J袠Чq'<"Of߁:T]&½bCYэRGxcmܭGa?vߨVlӈ~r?~eL>vE Ͻ[ ^9/gGV.QO;,gyq\i%+ $]y>јk fL Lf }_ K8w*y[_;^62^tO Ջ=TZҒ҆tŹYW{a÷m<_<$C7{/ W;gC|O]8Ff_t }b1D%#tvfDFfsOZ+uܩ*'pAu Tzz f{cf̻7kVmz_vNjif~qN?E)[1F-%'_m [MR_