aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Pérez-Cerezo <gabriel@gpcf.eu>2020-10-11 12:36:06 +0200
committerGabriel Pérez-Cerezo <gabriel@gpcf.eu>2020-10-11 12:36:06 +0200
commitdcf5b8670e19ad7603a0e305ec8515653555084f (patch)
treecf82f2dfd30ddca0f4661420eeeec8e9a902ace6
parent2ecd474ed74bcf63eb7e8081ce1caf4422fd9a56 (diff)
downloadadvtrains-dcf5b8670e19ad7603a0e305ec8515653555084f.tar.gz
advtrains-dcf5b8670e19ad7603a0e305ec8515653555084f.tar.bz2
advtrains-dcf5b8670e19ad7603a0e305ec8515653555084f.zip
Remove last files with CR-LF line endings.
-rw-r--r--advtrains/helpers.lua894
-rw-r--r--advtrains/tracks.lua1478
-rw-r--r--advtrains/wagons.lua2874
-rw-r--r--readme.txt94
4 files changed, 2670 insertions, 2670 deletions
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index 65f25ba..3b0bedd 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -1,447 +1,447 @@
---advtrains by orwell96, see readme.txt
-
-local dir_trans_tbl={
- [0]={x=0, z=1, y=0},
- [1]={x=1, z=2, y=0},
- [2]={x=1, z=1, y=0},
- [3]={x=2, z=1, y=0},
- [4]={x=1, z=0, y=0},
- [5]={x=2, z=-1, y=0},
- [6]={x=1, z=-1, y=0},
- [7]={x=1, z=-2, y=0},
- [8]={x=0, z=-1, y=0},
- [9]={x=-1, z=-2, y=0},
- [10]={x=-1, z=-1, y=0},
- [11]={x=-2, z=-1, y=0},
- [12]={x=-1, z=0, y=0},
- [13]={x=-2, z=1, y=0},
- [14]={x=-1, z=1, y=0},
- [15]={x=-1, z=2, y=0},
-}
-
-local dir_angle_tbl={}
-for d,v in pairs(dir_trans_tbl) do
- local uvec = vector.normalize(v)
- dir_angle_tbl[d] = math.atan2(-uvec.x, uvec.z)
-end
-
-
-function advtrains.dir_to_angle(dir)
- return dir_angle_tbl[dir] or error("advtrains: in helpers.lua/dir_to_angle() given dir="..(dir or "nil"))
-end
-
-function advtrains.dirCoordSet(coord, dir)
- return vector.add(coord, advtrains.dirToCoord(dir))
-end
-advtrains.pos_add_dir = advtrains.dirCoordSet
-
-function advtrains.pos_add_angle(pos, ang)
- -- 0 is +Z -> meaning of sin/cos swapped
- return vector.add(pos, {x = -math.sin(ang), y = 0, z = math.cos(ang)})
-end
-
-function advtrains.dirToCoord(dir)
- return dir_trans_tbl[dir] or error("advtrains: in helpers.lua/dir_to_vector() given dir="..(dir or "nil"))
-end
-advtrains.dir_to_vector = advtrains.dirToCoord
-
-function advtrains.maxN(list, expectstart)
- local n=expectstart or 0
- while list[n] do
- n=n+1
- end
- return n-1
-end
-
-function advtrains.minN(list, expectstart)
- local n=expectstart or 0
- while list[n] do
- n=n-1
- end
- return n+1
-end
-
-function atround(number)
- return math.floor(number+0.5)
-end
-atfloor = math.floor
-
-
-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
-
-function advtrains.yawToDirection(yaw, conn1, conn2)
- if not conn1 or not conn2 then
- error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
- end
- local yaw1 = advtrains.dir_to_angle(conn1)
- local yaw2 = advtrains.dir_to_angle(conn2)
- local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
- local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
-
- if math.abs(adiff2)<math.abs(adiff1) then
- return conn2
- else
- return conn1
- end
-end
-
-function advtrains.yawToAnyDir(yaw)
- local min_conn, min_diff=0, 10
- for conn, vec in pairs(advtrains.dir_trans_tbl) do
- local yaw1 = advtrains.dir_to_angle(conn)
- local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
- if diff < min_diff then
- min_conn = conn
- min_diff = diff
- end
- 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 yaw1 = advtrains.dir_to_angle(conn.c)
- local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
- if diff < min_diff then
- min_connid = connid
- min_diff = diff
- end
- end
- return min_connid
-end
-
-local pi, pi2 = math.pi, 2*math.pi
-function advtrains.minAngleDiffRad(r1, r2)
- while r1>pi2 do
- r1=r1-pi2
- end
- while r1<0 do
- r1=r1+pi2
- end
- while r2>pi2 do
- r2=r2-pi2
- end
- while r1<0 do
- r2=r2+pi2
- end
- local try1=r2-r1
- local try2=r2+pi2-r1
- local try3=r2-pi2-r1
-
- local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3))
- if minabs==math.abs(try1) then
- return try1
- end
- if minabs==math.abs(try2) then
- return try2
- end
- if minabs==math.abs(try3) then
- return try3
- end
-end
-
-
--- Takes 2 connections (0...AT_CMAX) as argument
--- Returns the angle median of those 2 positions from the pov
--- of standing on the cdir1 side and looking towards cdir2
--- cdir1 - >NODE> - cdir2
-function advtrains.conn_angle_median(cdir1, cdir2)
- local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1))
- local ang2 = advtrains.dir_to_angle(cdir2)
- return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
-end
-
-function advtrains.merge_tables(a, ...)
- local new={}
- for _,t in ipairs({a,...}) do
- for k,v in pairs(t) do new[k]=v end
- end
- return new
-end
-function advtrains.save_keys(tbl, keys)
- local new={}
- for _,key in ipairs(keys) do
- new[key] = tbl[key]
- end
- return new
-end
-
-function advtrains.get_real_index_position(path, index)
- if not path or not index then return end
-
- local first_pos=path[math.floor(index)]
- local second_pos=path[math.floor(index)+1]
-
- if not first_pos or not second_pos then return nil end
-
- local factor=index-math.floor(index)
- local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}
- return actual_pos
-end
-function advtrains.pos_median(pos1, pos2)
- return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5}
-end
-function advtrains.abs_ceil(i)
- return math.ceil(math.abs(i))*math.sign(i)
-end
-
-function advtrains.serialize_inventory(inv)
- local ser={}
- local liszts=inv:get_lists()
- for lisztname, liszt in pairs(liszts) do
- ser[lisztname]={}
- for idx, item in ipairs(liszt) do
- local istring=item:to_string()
- if istring~="" then
- ser[lisztname][idx]=istring
- end
- end
- end
- return minetest.serialize(ser)
-end
-function advtrains.deserialize_inventory(sers, inv)
- local ser=minetest.deserialize(sers)
- if ser then
- inv:set_lists(ser)
- return true
- end
- return false
-end
-
---is_protected wrapper that checks for protection_bypass privilege
-function advtrains.is_protected(pos, name)
- if not name then
- error("advtrains.is_protected() called without name parameter!")
- end
- if minetest.check_player_privs(name, {protection_bypass=true}) then
- --player can bypass protection
- return false
- end
- return minetest.is_protected(pos, name)
-end
-
-function advtrains.is_creative(name)
- if not name then
- error("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
-]]
-
-
+--advtrains by orwell96, see readme.txt
+
+local dir_trans_tbl={
+ [0]={x=0, z=1, y=0},
+ [1]={x=1, z=2, y=0},
+ [2]={x=1, z=1, y=0},
+ [3]={x=2, z=1, y=0},
+ [4]={x=1, z=0, y=0},
+ [5]={x=2, z=-1, y=0},
+ [6]={x=1, z=-1, y=0},
+ [7]={x=1, z=-2, y=0},
+ [8]={x=0, z=-1, y=0},
+ [9]={x=-1, z=-2, y=0},
+ [10]={x=-1, z=-1, y=0},
+ [11]={x=-2, z=-1, y=0},
+ [12]={x=-1, z=0, y=0},
+ [13]={x=-2, z=1, y=0},
+ [14]={x=-1, z=1, y=0},
+ [15]={x=-1, z=2, y=0},
+}
+
+local dir_angle_tbl={}
+for d,v in pairs(dir_trans_tbl) do
+ local uvec = vector.normalize(v)
+ dir_angle_tbl[d] = math.atan2(-uvec.x, uvec.z)
+end
+
+
+function advtrains.dir_to_angle(dir)
+ return dir_angle_tbl[dir] or error("advtrains: in helpers.lua/dir_to_angle() given dir="..(dir or "nil"))
+end
+
+function advtrains.dirCoordSet(coord, dir)
+ return vector.add(coord, advtrains.dirToCoord(dir))
+end
+advtrains.pos_add_dir = advtrains.dirCoordSet
+
+function advtrains.pos_add_angle(pos, ang)
+ -- 0 is +Z -> meaning of sin/cos swapped
+ return vector.add(pos, {x = -math.sin(ang), y = 0, z = math.cos(ang)})
+end
+
+function advtrains.dirToCoord(dir)
+ return dir_trans_tbl[dir] or error("advtrains: in helpers.lua/dir_to_vector() given dir="..(dir or "nil"))
+end
+advtrains.dir_to_vector = advtrains.dirToCoord
+
+function advtrains.maxN(list, expectstart)
+ local n=expectstart or 0
+ while list[n] do
+ n=n+1
+ end
+ return n-1
+end
+
+function advtrains.minN(list, expectstart)
+ local n=expectstart or 0
+ while list[n] do
+ n=n-1
+ end
+ return n+1
+end
+
+function atround(number)
+ return math.floor(number+0.5)
+end
+atfloor = math.floor
+
+
+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
+
+function advtrains.yawToDirection(yaw, conn1, conn2)
+ if not conn1 or not conn2 then
+ error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
+ end
+ local yaw1 = advtrains.dir_to_angle(conn1)
+ local yaw2 = advtrains.dir_to_angle(conn2)
+ local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
+ local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
+
+ if math.abs(adiff2)<math.abs(adiff1) then
+ return conn2
+ else
+ return conn1
+ end
+end
+
+function advtrains.yawToAnyDir(yaw)
+ local min_conn, min_diff=0, 10
+ for conn, vec in pairs(advtrains.dir_trans_tbl) do
+ local yaw1 = advtrains.dir_to_angle(conn)
+ local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
+ if diff < min_diff then
+ min_conn = conn
+ min_diff = diff
+ end
+ 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 yaw1 = advtrains.dir_to_angle(conn.c)
+ local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
+ if diff < min_diff then
+ min_connid = connid
+ min_diff = diff
+ end
+ end
+ return min_connid
+end
+
+local pi, pi2 = math.pi, 2*math.pi
+function advtrains.minAngleDiffRad(r1, r2)
+ while r1>pi2 do
+ r1=r1-pi2
+ end
+ while r1<0 do
+ r1=r1+pi2
+ end
+ while r2>pi2 do
+ r2=r2-pi2
+ end
+ while r1<0 do
+ r2=r2+pi2
+ end
+ local try1=r2-r1
+ local try2=r2+pi2-r1
+ local try3=r2-pi2-r1
+
+ local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3))
+ if minabs==math.abs(try1) then
+ return try1
+ end
+ if minabs==math.abs(try2) then
+ return try2
+ end
+ if minabs==math.abs(try3) then
+ return try3
+ end
+end
+
+
+-- Takes 2 connections (0...AT_CMAX) as argument
+-- Returns the angle median of those 2 positions from the pov
+-- of standing on the cdir1 side and looking towards cdir2
+-- cdir1 - >NODE> - cdir2
+function advtrains.conn_angle_median(cdir1, cdir2)
+ local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1))
+ local ang2 = advtrains.dir_to_angle(cdir2)
+ return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
+end
+
+function advtrains.merge_tables(a, ...)
+ local new={}
+ for _,t in ipairs({a,...}) do
+ for k,v in pairs(t) do new[k]=v end
+ end
+ return new
+end
+function advtrains.save_keys(tbl, keys)
+ local new={}
+ for _,key in ipairs(keys) do
+ new[key] = tbl[key]
+ end
+ return new
+end
+
+function advtrains.get_real_index_position(path, index)
+ if not path or not index then return end
+
+ local first_pos=path[math.floor(index)]
+ local second_pos=path[math.floor(index)+1]
+
+ if not first_pos or not second_pos then return nil end
+
+ local factor=index-math.floor(index)
+ local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}
+ return actual_pos
+end
+function advtrains.pos_median(pos1, pos2)
+ return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5}
+end
+function advtrains.abs_ceil(i)
+ return math.ceil(math.abs(i))*math.sign(i)
+end
+
+function advtrains.serialize_inventory(inv)
+ local ser={}
+ local liszts=inv:get_lists()
+ for lisztname, liszt in pairs(liszts) do
+ ser[lisztname]={}
+ for idx, item in ipairs(liszt) do
+ local istring=item:to_string()
+ if istring~="" then
+ ser[lisztname][idx]=istring
+ end
+ end
+ end
+ return minetest.serialize(ser)
+end
+function advtrains.deserialize_inventory(sers, inv)
+ local ser=minetest.deserialize(sers)
+ if ser then
+ inv:set_lists(ser)
+ return true
+ end
+ return false
+end
+
+--is_protected wrapper that checks for protection_bypass privilege
+function advtrains.is_protected(pos, name)
+ if not name then
+ error("advtrains.is_protected() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, {protection_bypass=true}) then
+ --player can bypass protection
+ return false
+ end
+ return minetest.is_protected(pos, name)
+end
+
+function advtrains.is_creative(name)
+ if not name then
+ error("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
+]]
+
+
diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua
index 549363d..60969df 100644
--- a/advtrains/tracks.lua
+++ b/advtrains/tracks.lua
@@ -1,742 +1,742 @@
---advtrains by orwell96, see readme.txt
-
---dev-time settings:
---EDIT HERE
---If the old non-model rails on straight tracks should be replaced by the new...
---false: no
---true: yes
-advtrains.register_replacement_lbms=false
-
---[[TracksDefinition
-nodename_prefix
-texture_prefix
-description
-common={}
-straight={}
-straight45={}
-curve={}
-curve45={}
-lswitchst={}
-lswitchst45={}
-rswitchst={}
-rswitchst45={}
-lswitchcr={}
-lswitchcr45={}
-rswitchcr={}
-rswitchcr45={}
-vert1={
- --you'll probably want to override mesh here
-}
-vert2={
- --you'll probably want to override mesh here
-}
-]]--
-advtrains.all_tracktypes={}
-
---definition preparation
-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 = 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 = "cr",
- switchmc = "on",
- switchst = "st",
- switchprefix = "swl",
- },
- swlcr={
- conns = conns3(0,7,8),
- desc = "left switch (curve)",
- trackworker = "swrcr",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- switchprefix = "swl",
- },
- swrst={
- conns = conns3(0,8,9),
- desc = "right switch (straight)",
- trackworker = "st",
- switchalt = "cr",
- switchmc = "on",
- switchst = "st",
- switchprefix = "swr",
- },
- swrcr={
- conns = conns3(0,9,8),
- desc = "right switch (curve)",
- trackworker = "st",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- switchprefix = "swr",
- },
- },
- regtp=true,
- tpdefault="st",
- trackworker={
- ["swrcr"]="st",
- ["swrst"]="st",
- ["cr"]="swlst",
- ["swlcr"]="swrcr",
- ["swlst"]="swrst",
- },
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_yturnout={
- regstep=1,
- variant={
- l={
- conns = conns3(0,7,9),
- desc = "Y-turnout (left)",
- switchalt = "r",
- switchmc = "off",
- switchst = "l",
- switchprefix = "",
- },
- r={
- conns = conns3(0,9,7),
- desc = "Y-turnout (right)",
- switchalt = "l",
- switchmc = "on",
- switchst = "r",
- switchprefix = "",
- }
- },
- regtp=true,
- tpdefault="l",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_s3way={
- regstep=1,
- variant={
- l={
+--advtrains by orwell96, see readme.txt
+
+--dev-time settings:
+--EDIT HERE
+--If the old non-model rails on straight tracks should be replaced by the new...
+--false: no
+--true: yes
+advtrains.register_replacement_lbms=false
+
+--[[TracksDefinition
+nodename_prefix
+texture_prefix
+description
+common={}
+straight={}
+straight45={}
+curve={}
+curve45={}
+lswitchst={}
+lswitchst45={}
+rswitchst={}
+rswitchst45={}
+lswitchcr={}
+lswitchcr45={}
+rswitchcr={}
+rswitchcr45={}
+vert1={
+ --you'll probably want to override mesh here
+}
+vert2={
+ --you'll probably want to override mesh here
+}
+]]--
+advtrains.all_tracktypes={}
+
+--definition preparation
+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 = 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 = "cr",
+ switchmc = "on",
+ switchst = "st",
+ switchprefix = "swl",
+ },
+ swlcr={
+ conns = conns3(0,7,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ switchprefix = "swl",
+ },
+ swrst={
+ conns = conns3(0,8,9),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ switchprefix = "swr",
+ },
+ swrcr={
+ conns = conns3(0,9,8),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ switchprefix = "swr",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ trackworker={
+ ["swrcr"]="st",
+ ["swrst"]="st",
+ ["cr"]="swlst",
+ ["swlcr"]="swrcr",
+ ["swlst"]="swrst",
+ },
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_yturnout={
+ regstep=1,
+ variant={
+ l={
+ conns = conns3(0,7,9),
+ desc = "Y-turnout (left)",
+ switchalt = "r",
+ switchmc = "off",
+ switchst = "l",
+ switchprefix = "",
+ },
+ r={
+ conns = conns3(0,9,7),
+ desc = "Y-turnout (right)",
+ switchalt = "l",
+ switchmc = "on",
+ switchst = "r",
+ switchprefix = "",
+ }
+ },
+ regtp=true,
+ tpdefault="l",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_s3way={
+ regstep=1,
+ variant={
+ l={
conns = { {c=0}, {c=7}, {c=8}, {c=9}, {c=0} },
- desc = "3-way turnout (left)",
- switchalt = "s",
- switchst="l",
- switchprefix = "",
- },
- s={
+ desc = "3-way turnout (left)",
+ switchalt = "s",
+ switchst="l",
+ switchprefix = "",
+ },
+ s={
conns = { {c=0}, {c=8}, {c=7}, {c=9}, {c=0} },
- desc = "3-way turnout (straight)",
- switchalt ="r",
- switchst = "s",
- switchprefix = "",
- },
- r={
+ desc = "3-way turnout (straight)",
+ switchalt ="r",
+ switchst = "s",
+ switchprefix = "",
+ },
+ r={
conns = { {c=0}, {c=9}, {c=8}, {c=7}, {c=0} },
- desc = "3-way turnout (right)",
- switchalt = "l",
- switchst="r",
- switchprefix = "",
- }
- },
- regtp=true,
- tpdefault="l",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_30deg_slope={
- regstep=1,
- variant={
- 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},
- },
- regsp=true,
- slopeplacer={
- [2]={"vst1", "vst2"},
- [3]={"vst31", "vst32", "vst33"},
- max=3,--highest entry
- },
- slopeplacer_45={
- [2]={"vst1_45", "vst2_45"},
- max=2,
- },
- rotation={"", "_30", "_45", "_60"},
- trackworker={},
- increativeinv={},
-}
-advtrains.ap.t_30deg_straightonly={
- regstep=1,
- variant={
- st={
- conns = conns(0,8),
- desc = "straight",
- tpdouble = true,
- tpsingle = true,
- trackworker = "st",
- },
- },
- regtp=true,
- tpdefault="st",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_30deg_straightonly_noplacer={
- regstep=1,
- variant={
- st={
- conns = conns(0,8),
- desc = "straight",
- tpdouble = true,
- tpsingle = true,
- trackworker = "st",
- },
- },
- tpdefault="st",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_45deg={
- regstep=2,
- variant={
- 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 = "cr",
- switchmc = "on",
- switchst = "st",
- },
- swlcr={
- conns = conns3(0,6,8),
- desc = "left switch (curve)",
- trackworker = "swrcr",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- },
- swrst={
- conns = conns3(0,8,10),
- desc = "right switch (straight)",
- trackworker = "st",
- switchalt = "cr",
- switchmc = "on",
- switchst = "st",
- },
- swrcr={
- conns = conns3(0,10,8),
- desc = "right switch (curve)",
- trackworker = "st",
- switchalt = "st",
- switchmc = "off",
- switchst = "cr",
- },
- },
- regtp=true,
- tpdefault="st",
- trackworker={
- ["swrcr"]="st",
- ["swrst"]="st",
- ["cr"]="swlst",
- ["swlcr"]="swrcr",
- ["swlst"]="swrst",
- },
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_perpcrossing={
- regstep = 1,
- variant={
- st={
- conns = { {c=0}, {c=8}, {c=4}, {c=12} },
- desc = "perpendicular crossing",
- tpdouble = true,
- tpsingle = true,
- trackworker = "st",
- },
- },
- regtp=true,
- tpdefault="st",
- rotation={"", "_30", "_45", "_60"},
-}
-advtrains.ap.t_90plusx_crossing={
- regstep = 1,
- variant={
- ["30l"]={
- conns = { {c=0}, {c=8}, {c=1}, {c=9} },
- desc = "30/90 degree crossing (left)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "45l"
- },
- ["45l"]={
- conns = { {c=0}, {c=8}, {c=2}, {c=10} },
- desc = "45/90 degree crossing (left)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "60l",
- },
- ["60l"]={
- conns = { {c=0}, {c=8}, {c=3}, {c=11}},
- desc = "60/90 degree crossing (left)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "60r",
- },
- ["60r"]={
- conns = { {c=0}, {c=8}, {c=7}, {c=15} },
- desc = "60/90 degree crossing (right)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "45r"
- },
- ["45r"]={
- conns = { {c=0}, {c=8}, {c=6}, {c=14} },
- desc = "45/90 degree crossing (right)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "30r",
- },
- ["30r"]={
- conns = { {c=0}, {c=8}, {c=7}, {c=15}},
- desc = "30/90 degree crossing (right)",
- tpdouble = true,
- tpsingle = true,
- trackworker = "30l",
- },
- },
- regtp=true,
- tpdefault="30l",
- rotation={""},
- trackworker = {
- ["30l"] = "45l",
- ["45l"] = "60l",
- ["60l"] = "60r",
- ["60r"] = "45r",
- ["45r"] = "30r",
- ["30r"] = "30l",
- }
-}
-
-advtrains.ap.t_diagonalcrossing = {
- regstep=1,
- variant={
- ["30l45r"]={
- conns = {{c=1}, {c=9}, {c=6}, {c=14}},
- desc = "30left-45right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60l30l",
- },
- ["60l30l"]={
- conns = {{c=3}, {c=11}, {c=1}, {c=9}},
- desc = "30left-60right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60l45r"
- },
- ["60l45r"]={
- conns = {{c=3}, {c=11}, {c=6}, {c=14}},
- desc = "60left-45right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60l60r"
- },
- ["60l60r"]={
- conns = {{c=3}, {c=11}, {c=5}, {c=13}},
- desc = "60left-60right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60r45l",
- },
- --If 60l60r had a mirror image, it would be here, but it's symmetric.
- -- 60l60r is also equivalent to 30l30r but rotated 90 degrees.
- ["60r45l"]={
- conns = {{c=5}, {c=13}, {c=2}, {c=10}},
- desc = "60right-45left diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="60r30r",
- },
- ["60r30r"]={
- conns = {{c=5}, {c=13}, {c=7}, {c=15}},
- desc = "60right-30right diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="30r45l",
- },
- ["30r45l"]={
- conns = {{c=7}, {c=15}, {c=2}, {c=10}},
- desc = "30right-45left diagonal crossing",
- tpdouble=true,
- tpsingle=true,
- trackworker="30l45r",
- },
-
- },
- regtp=true,
- tpdefault="30l45r",
- rotation={""},
- trackworker = {
- ["30l45r"] = "60l30l",
- ["60l30l"] = "60l45r",
- ["60l45r"] = "60l60r",
- ["60l60r"] = "60r45l",
- ["60r45l"] = "60r30r",
- ["60r30r"] = "30r45l",
- ["30r45l"] = "30l45r",
- }
-}
-
-advtrains.trackpresets = advtrains.ap
-
---definition format: ([] optional)
---[[{
- nodename_prefix
- texture_prefix
- [shared_texture]
- models_prefix
- models_suffix (with dot)
- [shared_model]
- formats={
- st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
- (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
- }
- common={} change something on common rail appearance
-}
-[18.12.17] Note on new connection system:
-In order to support real rail crossing nodes and finally make the trackplacer respect switches, I changed the connection system.
-There can be a variable number of connections available. These are specified as tuples {c=<connection>, y=<rely>}
-The table "at_conns" consists of {<conn1>, <conn2>...}
-the "at_rail_y" property holds the value that was previously called "railheight"
-Depending on the number of connections:
-2 conns: regular rail
-3 conns: switch:
- - when train passes in at conn1, will move out of conn2
- - when train passes in at conn2 or conn3, will move out of conn1
-4 conns: cross (or cross switch, depending on arrangement of conns):
- - conn1 <> conn2
- - conn3 <> conn4
-]]
-
-function advtrains.register_tracks(tracktype, def, preset)
- advtrains.trackplacer.register_tracktype(def.nodename_prefix, preset.tpdefault)
- if preset.regtp then
- advtrains.trackplacer.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description, def)
- end
- if preset.regsp then
- advtrains.slope.register_placer(def, preset)
- end
- for suffix, var in pairs(preset.variant) do
- for rotid, rotation in ipairs(preset.rotation) do
- if not def.formats[suffix] or def.formats[suffix][rotid] then
- local img_suffix = suffix..rotation
- local ndef = advtrains.merge_tables({
- description=def.description.."("..(var.desc or "any")..rotation..")",
- drawtype = "mesh",
- paramtype="light",
- paramtype2="facedir",
- walkable = false,
- selection_box = {
- type = "fixed",
- fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
- },
-
- mesh = def.shared_model or (def.models_prefix.."_"..img_suffix..def.models_suffix),
- tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture},
-
- groups = {
- attached_node=1,
- advtrains_track=1,
- ["advtrains_track_"..tracktype]=1,
- save_in_at_nodedb=1,
- dig_immediate=2,
- not_in_creative_inventory=1,
- not_blocking_trains=1,
- },
-
- can_dig = advtrains.can_dig_or_modify_track,
- after_dig_node=function(pos)
- advtrains.ndb.update(pos)
- end,
- after_place_node=function(pos)
- advtrains.ndb.update(pos)
- end,
- at_nnpref = def.nodename_prefix,
- at_suffix = suffix,
- at_rotation = rotation,
- at_rail_y = var.rail_y
- }, def.common or {})
-
- if preset.regtp then
- ndef.drop = def.nodename_prefix.."_placer"
- end
- if preset.regsp and var.slope then
- ndef.drop = def.nodename_prefix.."_slopeplacer"
- end
-
- --connections
- ndef.at_conns = advtrains.rotate_conn_by(var.conns, (rotid-1)*preset.regstep)
-
- local ndef_avt_table
-
- if var.switchalt and var.switchst then
- local switchfunc=function(pos, node, newstate)
- newstate = newstate or var.switchalt -- support for 3 (or more) state switches
- -- this code is only called from the internal setstate function, which
- -- ensures that it is safe to switch the turnout
- if newstate~=var.switchst then
- advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..(var.switchprefix or "")..newstate..rotation, param2=node.param2})
- advtrains.invalidate_all_paths(pos)
- end
- end
- ndef.on_rightclick = function(pos, node, player)
- if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
- advtrains.setstate(pos, newstate, node)
- advtrains.log("Switch", player:get_player_name(), pos)
- end
- end
- if var.switchmc then
- ndef.mesecons = {effector = {
- ["action_"..var.switchmc] = function(pos, node)
- advtrains.setstate(pos, nil, node)
- end,
- rules=advtrains.meseconrules
- }}
- end
- ndef_avt_table = {
- getstate = var.switchst,
- setstate = switchfunc,
- }
- end
-
- local adef={}
- if def.get_additional_definiton then
- adef=def.get_additional_definiton(def, preset, suffix, rotation)
- end
- ndef = advtrains.merge_tables(ndef, adef)
-
- -- insert getstate/setstate functions after merging the additional definitions
- if ndef_avt_table then
- ndef.advtrains = advtrains.merge_tables(ndef.advtrains or {}, ndef_avt_table)
- end
-
- minetest.register_node(":"..def.nodename_prefix.."_"..suffix..rotation, ndef)
- --trackplacer
- if preset.regtp then
- local tpconns = {conn1=ndef.at_conns[1].c, conn2=ndef.at_conns[2].c}
- if var.tpdouble then
- advtrains.trackplacer.add_double_conn(def.nodename_prefix, suffix, rotation, tpconns)
- end
- if var.tpsingle then
- advtrains.trackplacer.add_single_conn(def.nodename_prefix, suffix, rotation, tpconns)
- end
- end
- advtrains.trackplacer.add_worked(def.nodename_prefix, suffix, rotation, var.trackworker)
- end
- end
- end
- advtrains.all_tracktypes[tracktype]=true
-end
-
-function advtrains.is_track_and_drives_on(nodename, drives_on_p)
- local drives_on = drives_on_p
- if not drives_on then drives_on = advtrains.all_tracktypes end
- local hasentry = false
- for _,_ in pairs(drives_on) do
- hasentry=true
- end
- if not hasentry then drives_on = advtrains.all_tracktypes end
-
- if not minetest.registered_nodes[nodename] then
- return false
- end
- local nodedef=minetest.registered_nodes[nodename]
- for k,v in pairs(drives_on) do
- if nodedef.groups["advtrains_track_"..k] then
- return true
- end
- end
- return false
-end
-
-function advtrains.get_track_connections(name, param2)
- local nodedef=minetest.registered_nodes[name]
- if not nodedef then atprint(" get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return nil end
- local noderot=param2
- if not param2 then noderot=0 end
- if noderot > 3 then atprint(" get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
-
- local tracktype
- for k,_ in pairs(nodedef.groups) do
- local tt=string.match(k, "^advtrains_track_(.+)$")
- if tt then
- tracktype=tt
- end
- end
- return advtrains.rotate_conn_by(nodedef.at_conns, noderot*AT_CMAX/4), (nodedef.at_rail_y or 0), tracktype
-end
-
--- Function called when a track is about to be dug or modified by the trackworker
--- Returns either true (ok) or false,"translated string describing reason why it isn't allowed"
-function advtrains.can_dig_or_modify_track(pos)
- if advtrains.get_train_at_pos(pos) then
- return false, attrans("Position is occupied by a train.")
- end
- -- interlocking: tcb, signal IP a.s.o.
- if advtrains.interlocking then
- -- TCB?
- if advtrains.interlocking.db.get_tcb(pos) then
- return false, attrans("There's a Track Circuit Break here.")
- end
- -- signal ip?
- if advtrains.interlocking.db.is_ip_at(pos) then
- return false, attrans("There's a Signal Influence Point here.")
- end
- end
- return true
-end
-
--- slope placer. Defined in register_tracks.
---crafted with rail and gravel
-local sl={}
-function sl.register_placer(def, preset)
- minetest.register_craftitem(":"..def.nodename_prefix.."_slopeplacer",{
- description = attrans("@1 Slope", def.description),
- inventory_image = def.texture_prefix.."_slopeplacer.png",
- wield_image = def.texture_prefix.."_slopeplacer.png",
- groups={},
- on_place = sl.create_slopeplacer_on_place(def, preset)
- })
-end
---(itemstack, placer, pointed_thing)
-function sl.create_slopeplacer_on_place(def, preset)
- return function(istack, player, pt)
- if not pt.type=="node" then
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
- return istack
- end
- local pos=pt.above
- if not pos then
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
- return istack
- end
- local node=minetest.get_node(pos)
- if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to then
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: space occupied!"))
- return istack
- end
- if not advtrains.check_track_protection(pos, player:get_player_name()) then
- minetest.record_protection_violation(pos, player:get_player_name())
- return istack
- end
- --determine player orientation (only horizontal component)
- --get_look_horizontal may not be available
- local yaw=player.get_look_horizontal and player:get_look_horizontal() or (player:get_look_yaw() - math.pi/2)
-
- --rounding unit vectors is a nice way for selecting 1 of 8 directions since sin(30°) is 0.5.
- dirvec={x=math.floor(math.sin(-yaw)+0.5), y=0, z=math.floor(math.cos(-yaw)+0.5)}
- --translate to direction to look up inside the preset table
- local param2, rot45=({
- [-1]={
- [-1]=2,
- [0]=3,
- [1]=3,
- },
- [0]={
- [-1]=2,
- [1]=0,
- },
- [1]={
- [-1]=1,
- [0]=1,
- [1]=0,
- },
- })[dirvec.x][dirvec.z], dirvec.x~=0 and dirvec.z~=0
- local lookup=preset.slopeplacer
- if rot45 then lookup=preset.slopeplacer_45 end
-
- --go unitvector forward and look how far the next node is
- local step=1
- while step<=lookup.max do
- local node=minetest.get_node(vector.add(pos, dirvec))
- --next node solid?
- if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to or advtrains.is_protected(pos, player:get_player_name()) then
- --do slopes of this distance exist?
- if lookup[step] then
- if minetest.settings:get_bool("creative_mode") or istack:get_count()>=step then
- --start placing
- local placenodes=lookup[step]
- while step>0 do
- minetest.set_node(pos, {name=def.nodename_prefix.."_"..placenodes[step], param2=param2})
- if not minetest.settings:get_bool("creative_mode") then
- istack:take_item()
- end
- step=step-1
- pos=vector.subtract(pos, dirvec)
- end
- else
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: Not enough slope items left (@1 required)", step))
- end
- else
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: There's no slope of length @1",step))
- end
- return istack
- end
- step=step+1
- pos=vector.add(pos, dirvec)
- end
- minetest.chat_send_player(player:get_player_name(), attrans("Can't place: no supporting node at upper end."))
- return itemstack
- end
-end
-
-advtrains.slope=sl
-
---END code, BEGIN definition
---definition format: ([] optional)
---[[{
- nodename_prefix
- texture_prefix
- [shared_texture]
- models_prefix
- models_suffix (with dot)
- [shared_model]
- formats={
- st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
- (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
- }
- common={} change something on common rail appearance
-}]]
-
-
-
-
-
-
-
-
-
+ desc = "3-way turnout (right)",
+ switchalt = "l",
+ switchst="r",
+ switchprefix = "",
+ }
+ },
+ regtp=true,
+ tpdefault="l",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_30deg_slope={
+ regstep=1,
+ variant={
+ 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},
+ },
+ regsp=true,
+ slopeplacer={
+ [2]={"vst1", "vst2"},
+ [3]={"vst31", "vst32", "vst33"},
+ max=3,--highest entry
+ },
+ slopeplacer_45={
+ [2]={"vst1_45", "vst2_45"},
+ max=2,
+ },
+ rotation={"", "_30", "_45", "_60"},
+ trackworker={},
+ increativeinv={},
+}
+advtrains.ap.t_30deg_straightonly={
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_30deg_straightonly_noplacer={
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_45deg={
+ regstep=2,
+ variant={
+ 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 = "cr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swlcr={
+ conns = conns3(0,6,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ swrst={
+ conns = conns3(0,8,10),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swrcr={
+ conns = conns3(0,10,8),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ trackworker={
+ ["swrcr"]="st",
+ ["swrst"]="st",
+ ["cr"]="swlst",
+ ["swlcr"]="swrcr",
+ ["swlst"]="swrst",
+ },
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_perpcrossing={
+ regstep = 1,
+ variant={
+ st={
+ conns = { {c=0}, {c=8}, {c=4}, {c=12} },
+ desc = "perpendicular crossing",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_90plusx_crossing={
+ regstep = 1,
+ variant={
+ ["30l"]={
+ conns = { {c=0}, {c=8}, {c=1}, {c=9} },
+ desc = "30/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "45l"
+ },
+ ["45l"]={
+ conns = { {c=0}, {c=8}, {c=2}, {c=10} },
+ desc = "45/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "60l",
+ },
+ ["60l"]={
+ conns = { {c=0}, {c=8}, {c=3}, {c=11}},
+ desc = "60/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "60r",
+ },
+ ["60r"]={
+ conns = { {c=0}, {c=8}, {c=7}, {c=15} },
+ desc = "60/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "45r"
+ },
+ ["45r"]={
+ conns = { {c=0}, {c=8}, {c=6}, {c=14} },
+ desc = "45/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "30r",
+ },
+ ["30r"]={
+ conns = { {c=0}, {c=8}, {c=7}, {c=15}},
+ desc = "30/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "30l",
+ },
+ },
+ regtp=true,
+ tpdefault="30l",
+ rotation={""},
+ trackworker = {
+ ["30l"] = "45l",
+ ["45l"] = "60l",
+ ["60l"] = "60r",
+ ["60r"] = "45r",
+ ["45r"] = "30r",
+ ["30r"] = "30l",
+ }
+}
+
+advtrains.ap.t_diagonalcrossing = {
+ regstep=1,
+ variant={
+ ["30l45r"]={
+ conns = {{c=1}, {c=9}, {c=6}, {c=14}},
+ desc = "30left-45right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l30l",
+ },
+ ["60l30l"]={
+ conns = {{c=3}, {c=11}, {c=1}, {c=9}},
+ desc = "30left-60right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l45r"
+ },
+ ["60l45r"]={
+ conns = {{c=3}, {c=11}, {c=6}, {c=14}},
+ desc = "60left-45right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l60r"
+ },
+ ["60l60r"]={
+ conns = {{c=3}, {c=11}, {c=5}, {c=13}},
+ desc = "60left-60right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60r45l",
+ },
+ --If 60l60r had a mirror image, it would be here, but it's symmetric.
+ -- 60l60r is also equivalent to 30l30r but rotated 90 degrees.
+ ["60r45l"]={
+ conns = {{c=5}, {c=13}, {c=2}, {c=10}},
+ desc = "60right-45left diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60r30r",
+ },
+ ["60r30r"]={
+ conns = {{c=5}, {c=13}, {c=7}, {c=15}},
+ desc = "60right-30right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="30r45l",
+ },
+ ["30r45l"]={
+ conns = {{c=7}, {c=15}, {c=2}, {c=10}},
+ desc = "30right-45left diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="30l45r",
+ },
+
+ },
+ regtp=true,
+ tpdefault="30l45r",
+ rotation={""},
+ trackworker = {
+ ["30l45r"] = "60l30l",
+ ["60l30l"] = "60l45r",
+ ["60l45r"] = "60l60r",
+ ["60l60r"] = "60r45l",
+ ["60r45l"] = "60r30r",
+ ["60r30r"] = "30r45l",
+ ["30r45l"] = "30l45r",
+ }
+}
+
+advtrains.trackpresets = advtrains.ap
+
+--definition format: ([] optional)
+--[[{
+ nodename_prefix
+ texture_prefix
+ [shared_texture]
+ models_prefix
+ models_suffix (with dot)
+ [shared_model]
+ formats={
+ st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
+ (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
+ }
+ common={} change something on common rail appearance
+}
+[18.12.17] Note on new connection system:
+In order to support real rail crossing nodes and finally make the trackplacer respect switches, I changed the connection system.
+There can be a variable number of connections available. These are specified as tuples {c=<connection>, y=<rely>}
+The table "at_conns" consists of {<conn1>, <conn2>...}
+the "at_rail_y" property holds the value that was previously called "railheight"
+Depending on the number of connections:
+2 conns: regular rail
+3 conns: switch:
+ - when train passes in at conn1, will move out of conn2
+ - when train passes in at conn2 or conn3, will move out of conn1
+4 conns: cross (or cross switch, depending on arrangement of conns):
+ - conn1 <> conn2
+ - conn3 <> conn4
+]]
+
+function advtrains.register_tracks(tracktype, def, preset)
+ advtrains.trackplacer.register_tracktype(def.nodename_prefix, preset.tpdefault)
+ if preset.regtp then
+ advtrains.trackplacer.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description, def)
+ end
+ if preset.regsp then
+ advtrains.slope.register_placer(def, preset)
+ end
+ for suffix, var in pairs(preset.variant) do
+ for rotid, rotation in ipairs(preset.rotation) do
+ if not def.formats[suffix] or def.formats[suffix][rotid] then
+ local img_suffix = suffix..rotation
+ local ndef = advtrains.merge_tables({
+ description=def.description.."("..(var.desc or "any")..rotation..")",
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
+ },
+
+ mesh = def.shared_model or (def.models_prefix.."_"..img_suffix..def.models_suffix),
+ tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture},
+
+ groups = {
+ attached_node=1,
+ advtrains_track=1,
+ ["advtrains_track_"..tracktype]=1,
+ save_in_at_nodedb=1,
+ dig_immediate=2,
+ not_in_creative_inventory=1,
+ not_blocking_trains=1,
+ },
+
+ can_dig = advtrains.can_dig_or_modify_track,
+ after_dig_node=function(pos)
+ advtrains.ndb.update(pos)
+ end,
+ after_place_node=function(pos)
+ advtrains.ndb.update(pos)
+ end,
+ at_nnpref = def.nodename_prefix,
+ at_suffix = suffix,
+ at_rotation = rotation,
+ at_rail_y = var.rail_y
+ }, def.common or {})
+
+ if preset.regtp then
+ ndef.drop = def.nodename_prefix.."_placer"
+ end
+ if preset.regsp and var.slope then
+ ndef.drop = def.nodename_prefix.."_slopeplacer"
+ end
+
+ --connections
+ ndef.at_conns = advtrains.rotate_conn_by(var.conns, (rotid-1)*preset.regstep)
+
+ local ndef_avt_table
+
+ if var.switchalt and var.switchst then
+ local switchfunc=function(pos, node, newstate)
+ newstate = newstate or var.switchalt -- support for 3 (or more) state switches
+ -- this code is only called from the internal setstate function, which
+ -- ensures that it is safe to switch the turnout
+ if newstate~=var.switchst then
+ advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..(var.switchprefix or "")..newstate..rotation, param2=node.param2})
+ advtrains.invalidate_all_paths(pos)
+ end
+ end
+ ndef.on_rightclick = function(pos, node, player)
+ if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.setstate(pos, newstate, node)
+ advtrains.log("Switch", player:get_player_name(), pos)
+ end
+ end
+ if var.switchmc then
+ ndef.mesecons = {effector = {
+ ["action_"..var.switchmc] = function(pos, node)
+ advtrains.setstate(pos, nil, node)
+ end,
+ rules=advtrains.meseconrules
+ }}
+ end
+ ndef_avt_table = {
+ getstate = var.switchst,
+ setstate = switchfunc,
+ }
+ end
+
+ local adef={}
+ if def.get_additional_definiton then
+ adef=def.get_additional_definiton(def, preset, suffix, rotation)
+ end
+ ndef = advtrains.merge_tables(ndef, adef)
+
+ -- insert getstate/setstate functions after merging the additional definitions
+ if ndef_avt_table then
+ ndef.advtrains = advtrains.merge_tables(ndef.advtrains or {}, ndef_avt_table)
+ end
+
+ minetest.register_node(":"..def.nodename_prefix.."_"..suffix..rotation, ndef)
+ --trackplacer
+ if preset.regtp then
+ local tpconns = {conn1=ndef.at_conns[1].c, conn2=ndef.at_conns[2].c}
+ if var.tpdouble then
+ advtrains.trackplacer.add_double_conn(def.nodename_prefix, suffix, rotation, tpconns)
+ end
+ if var.tpsingle then
+ advtrains.trackplacer.add_single_conn(def.nodename_prefix, suffix, rotation, tpconns)
+ end
+ end
+ advtrains.trackplacer.add_worked(def.nodename_prefix, suffix, rotation, var.trackworker)
+ end
+ end
+ end
+ advtrains.all_tracktypes[tracktype]=true
+end
+
+function advtrains.is_track_and_drives_on(nodename, drives_on_p)
+ local drives_on = drives_on_p
+ if not drives_on then drives_on = advtrains.all_tracktypes end
+ local hasentry = false
+ for _,_ in pairs(drives_on) do
+ hasentry=true
+ end
+ if not hasentry then drives_on = advtrains.all_tracktypes end
+
+ if not minetest.registered_nodes[nodename] then
+ return false
+ end
+ local nodedef=minetest.registered_nodes[nodename]
+ for k,v in pairs(drives_on) do
+ if nodedef.groups["advtrains_track_"..k] then
+ return true
+ end
+ end
+ return false
+end
+
+function advtrains.get_track_connections(name, param2)
+ local nodedef=minetest.registered_nodes[name]
+ if not nodedef then atprint(" get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return nil end
+ local noderot=param2
+ if not param2 then noderot=0 end
+ if noderot > 3 then atprint(" get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
+
+ local tracktype
+ for k,_ in pairs(nodedef.groups) do
+ local tt=string.match(k, "^advtrains_track_(.+)$")
+ if tt then
+ tracktype=tt
+ end
+ end
+ return advtrains.rotate_conn_by(nodedef.at_conns, noderot*AT_CMAX/4), (nodedef.at_rail_y or 0), tracktype
+end
+
+-- Function called when a track is about to be dug or modified by the trackworker
+-- Returns either true (ok) or false,"translated string describing reason why it isn't allowed"
+function advtrains.can_dig_or_modify_track(pos)
+ if advtrains.get_train_at_pos(pos) then
+ return false, attrans("Position is occupied by a train.")
+ end
+ -- interlocking: tcb, signal IP a.s.o.
+ if advtrains.interlocking then
+ -- TCB?
+ if advtrains.interlocking.db.get_tcb(pos) then
+ return false, attrans("There's a Track Circuit Break here.")
+ end
+ -- signal ip?
+ if advtrains.interlocking.db.is_ip_at(pos) then
+ return false, attrans("There's a Signal Influence Point here.")
+ end
+ end
+ return true
+end
+
+-- slope placer. Defined in register_tracks.
+--crafted with rail and gravel
+local sl={}
+function sl.register_placer(def, preset)
+ minetest.register_craftitem(":"..def.nodename_prefix.."_slopeplacer",{
+ description = attrans("@1 Slope", def.description),
+ inventory_image = def.texture_prefix.."_slopeplacer.png",
+ wield_image = def.texture_prefix.."_slopeplacer.png",
+ groups={},
+ on_place = sl.create_slopeplacer_on_place(def, preset)
+ })
+end
+--(itemstack, placer, pointed_thing)
+function sl.create_slopeplacer_on_place(def, preset)
+ return function(istack, player, pt)
+ if not pt.type=="node" then
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
+ return istack
+ end
+ local pos=pt.above
+ if not pos then
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
+ return istack
+ end
+ local node=minetest.get_node(pos)
+ if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to then
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: space occupied!"))
+ return istack
+ end
+ if not advtrains.check_track_protection(pos, player:get_player_name()) then
+ minetest.record_protection_violation(pos, player:get_player_name())
+ return istack
+ end
+ --determine player orientation (only horizontal component)
+ --get_look_horizontal may not be available
+ local yaw=player.get_look_horizontal and player:get_look_horizontal() or (player:get_look_yaw() - math.pi/2)
+
+ --rounding unit vectors is a nice way for selecting 1 of 8 directions since sin(30°) is 0.5.
+ dirvec={x=math.floor(math.sin(-yaw)+0.5), y=0, z=math.floor(math.cos(-yaw)+0.5)}
+ --translate to direction to look up inside the preset table
+ local param2, rot45=({
+ [-1]={
+ [-1]=2,
+ [0]=3,
+ [1]=3,
+ },
+ [0]={
+ [-1]=2,
+ [1]=0,
+ },
+ [1]={
+ [-1]=1,
+ [0]=1,
+ [1]=0,
+ },
+ })[dirvec.x][dirvec.z], dirvec.x~=0 and dirvec.z~=0
+ local lookup=preset.slopeplacer
+ if rot45 then lookup=preset.slopeplacer_45 end
+
+ --go unitvector forward and look how far the next node is
+ local step=1
+ while step<=lookup.max do
+ local node=minetest.get_node(vector.add(pos, dirvec))
+ --next node solid?
+ if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to or advtrains.is_protected(pos, player:get_player_name()) then
+ --do slopes of this distance exist?
+ if lookup[step] then
+ if minetest.settings:get_bool("creative_mode") or istack:get_count()>=step then
+ --start placing
+ local placenodes=lookup[step]
+ while step>0 do
+ minetest.set_node(pos, {name=def.nodename_prefix.."_"..placenodes[step], param2=param2})
+ if not minetest.settings:get_bool("creative_mode") then
+ istack:take_item()
+ end
+ step=step-1
+ pos=vector.subtract(pos, dirvec)
+ end
+ else
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: Not enough slope items left (@1 required)", step))
+ end
+ else
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: There's no slope of length @1",step))
+ end
+ return istack
+ end
+ step=step+1
+ pos=vector.add(pos, dirvec)
+ end
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: no supporting node at upper end."))
+ return itemstack
+ end
+end
+
+advtrains.slope=sl
+
+--END code, BEGIN definition
+--definition format: ([] optional)
+--[[{
+ nodename_prefix
+ texture_prefix
+ [shared_texture]
+ models_prefix
+ models_suffix (with dot)
+ [shared_model]
+ formats={
+ st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
+ (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
+ }
+ common={} change something on common rail appearance
+}]]
+
+
+
+
+
+
+
+
+
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index 90615a5..6b59293 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -1,1437 +1,1437 @@
--- wagon.lua
--- Holds all logic related to wagons
--- From now on, wagons are, just like trains, just entries in a table
--- All data that is static is stored in the entity prototype (self).
--- A copy of the entity prototype is always available inside wagon_prototypes
--- All dynamic data is stored in the (new) wagons table
--- An entity is ONLY spawned by update_trainpart_properties when it finds it useful.
--- Only data that are only important to the entity itself are stored in the luaentity
-
--- TP delay when getting off wagon
-local GETOFF_TP_DELAY = 0.5
-
-advtrains.wagons = {}
-advtrains.wagon_prototypes = {}
-advtrains.wagon_objects = {}
-
-local unload_wgn_range = advtrains.wagon_load_range + 32
-
-local setting_show_ids = minetest.settings:get_bool("advtrains_show_ids")
-
---
-function advtrains.create_wagon(wtype, owner)
- local new_id=advtrains.random_id()
- while advtrains.wagons[new_id] do new_id=advtrains.random_id() end
- local wgn = {}
- wgn.type = wtype
- wgn.seatp = {}
- wgn.owner = owner
- wgn.id = new_id
- ---wgn.train_id = train_id --- will get this via update_trainpart_properties
- advtrains.wagons[new_id] = wgn
- --atdebug("Created new wagon:",wgn)
- return new_id
-end
-
-local function make_inv_name(uid)
- return "detached:advtrains_wgn_"..uid
-end
-
-
-local wagon={
- collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
- --physical = true,
- visual = "mesh",
- mesh = "wagon.b3d",
- visual_size = {x=1, y=1},
- textures = {"black.png"},
- is_wagon=true,
- wagon_span=1,--how many index units of space does this wagon consume
- wagon_width=3, -- Wagon width in meters
- has_inventory=false,
- static_save=false,
-}
-
-
-function wagon:train()
- local data = advtrains.wagons[self.id]
- return advtrains.trains[data.train_id]
-end
-
-
-function wagon:on_activate(sd_uid, dtime_s)
- if sd_uid~="" then
- --destroy when loaded from static block.
- self.object:remove()
- return
- end
- self.object:set_armor_groups({immortal=1})
-end
-
-local function invcallback(id, pname, rtallow, rtfail)
- local data = advtrains.wagons[id]
- if data and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- return rtallow
- end
- return rtfail
-end
-
-function wagon:set_id(wid)
- self.id = wid
- self.initialized = true
-
- local data = advtrains.wagons[self.id]
- advtrains.wagon_objects[self.id] = self.object
-
- --atdebug("Created wagon entity:",self.name," w_id",wid," t_id",data.train_id)
-
- if self.has_inventory then
- --to be used later
- local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})
- -- create inventory, if not yet created
- if not inv then
- inv=minetest.create_detached_inventory("advtrains_wgn_"..self.id, {
- allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
- return invcallback(wid, player:get_player_name(), count, 0)
- end,
- allow_put = function(inv, listname, index, stack, player)
- return invcallback(wid, player:get_player_name(), stack:get_count(), 0)
- end,
- allow_take = function(inv, listname, index, stack, player)
- return invcallback(wid, player:get_player_name(), stack:get_count(), 0)
- end
- })
- if data.ser_inv then
- advtrains.deserialize_inventory(data.ser_inv, inv)
- end
- if self.inventory_list_sizes then
- for lst, siz in pairs(self.inventory_list_sizes) do
- inv:set_size(lst, siz)
- end
- end
- end
- end
- self.door_anim_timer=0
- self.door_state=0
-
- minetest.after(0.2, function() self:reattach_all() end)
-
-
-
- if self.set_textures then
- self:set_textures(data)
- end
-
- if self.custom_on_activate then
- self:custom_on_activate()
- end
-end
-
-function wagon:get_staticdata()
- return "STATIC"
-end
-
-function wagon:ensure_init()
- -- Note: A wagon entity won't exist when there's no train, because the train is
- -- the thing that actually creates the entity
- -- Train not being set just means that this will happen as soon as the train calls update_trainpart_properties.
- if self.initialized and self.id then
- local data = advtrains.wagons[self.id]
- if data and data.train_id and self:train() then
- if self.noninitticks then self.noninitticks=nil end
- return true
- end
- end
- if not self.noninitticks then
- atwarn("wagon",self.id,"uninitialized init=",self.initialized)
- self.noninitticks=0
- end
- self.noninitticks=self.noninitticks+1
- if self.noninitticks>20 then
- atwarn("wagon",self.id,"uninitialized, removing")
- self:destroy()
- else
- self.object:setvelocity({x=0,y=0,z=0})
- end
- return false
-end
-
-function wagon:train()
- local data = advtrains.wagons[self.id]
- return advtrains.trains[data.train_id]
-end
-
--- Remove the wagon
-function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
- return advtrains.pcall(function()
- if not self:ensure_init() then return end
-
- local data = advtrains.wagons[self.id]
-
- if not puncher or not puncher:is_player() then
- return
- end
- if data.owner and puncher:get_player_name()~=data.owner and (not minetest.check_player_privs(puncher, {train_admin = true })) then
- minetest.chat_send_player(puncher:get_player_name(), attrans("This wagon is owned by @1, you can't destroy it.", data.owner));
- return
- end
-
- if self.custom_may_destroy then
- if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then
- return
- end
- end
- local itemstack = puncher:get_wielded_item()
- -- WARNING: This part of the API is guaranteed to change! DO NOT USE!
- if self.set_livery and itemstack:get_name() == "bike:painter" then
- self:set_livery(puncher, itemstack, data)
- return
- end
- -- check whether wagon has an inventory. Is is empty?
- if self.has_inventory then
- local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})
- if not inv then -- inventory is not initialized when wagon was never loaded - should never happen
- atwarn("Destroying wagon with inventory, but inventory is not found? Shouldn't happen!")
- return
- end
- for listname, _ in pairs(inv:get_lists()) do
- if not inv:is_empty(listname) then
- minetest.chat_send_player(puncher:get_player_name(), attrans("The wagon's inventory is not empty!"));
- return
- end
- end
- end
-
- if #(self:train().trainparts)>1 then
- minetest.chat_send_player(puncher:get_player_name(), attrans("Wagon needs to be decoupled from other wagons in order to destroy it."));
- return
- end
-
- local pc=puncher:get_player_control()
- if not pc.sneak then
- minetest.chat_send_player(puncher:get_player_name(), attrans("Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon."))
- return
- end
-
-
- if not self:destroy() then return end
-
- local inv = puncher:get_inventory()
- for _,item in ipairs(self.drops or {self.name}) do
- inv:add_item("main", item)
- end
- end)
-end
-function wagon:destroy()
- --some rules:
- -- you get only some items back
- -- single left-click shows warning
- -- shift leftclick destroys
- -- not when a driver is inside
- if self.id then
- local data = advtrains.wagons[self.id]
- if not data then
- atwarn("wagon:destroy(): data is not set!")
- return
- end
-
- if self.custom_on_destroy then
- self.custom_on_destroy(self)
- end
-
- for seat,_ in pairs(data.seatp) do
- self:get_off(seat)
- end
-
- if data.train_id and self:train() then
- advtrains.remove_train(data.train_id)
- advtrains.wagons[self.id]=nil
- if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects
- end
- end
- --atdebug("[wagon ", self.id, "]: destroying")
-
- self.object:remove()
- return true
-end
-
-function wagon:on_step(dtime)
- return advtrains.pcall(function()
- if not self:ensure_init() then return end
-
- local t=os.clock()
- local pos = self.object:getpos()
- local data = advtrains.wagons[self.id]
-
- if not pos then
- --atdebug("["..self.id.."][fatal] missing position (object:getpos() returned nil)")
- return
- end
-
- if not data.seatp then
- data.seatp={}
- end
- if not self.seatpc then
- self.seatpc={}
- end
-
- local train=self:train()
-
- --custom on_step function
- if self.custom_on_step then
- self:custom_on_step(dtime, data, train)
- end
-
- --driver control
- for seatno, seat in ipairs(self.seats) do
- local pname=data.seatp[seatno]
- local driver=pname and minetest.get_player_by_name(pname)
- local has_driverstand = pname and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)
- if self.seat_groups then
- has_driverstand = has_driverstand and (seat.driving_ctrl_access or self.seat_groups[seat.group].driving_ctrl_access)
- else
- has_driverstand = has_driverstand and (seat.driving_ctrl_access)
- end
- if has_driverstand and driver then
- advtrains.update_driver_hud(driver:get_player_name(), self:train(), data.wagon_flipped)
- elseif driver then
- --only show the inside text
- local inside=self:train().text_inside or ""
- advtrains.set_trainhud(driver:get_player_name(), inside)
- end
- if driver and driver:get_player_control_bits()~=self.seatpc[seatno] then
- local pc=driver:get_player_control()
- self.seatpc[seatno]=driver:get_player_control_bits()
-
- if has_driverstand then
- --regular driver stand controls
- advtrains.on_control_change(pc, self:train(), data.wagon_flipped)
- --bordcom
- if pc.sneak and pc.jump then
- self:show_bordcom(data.seatp[seatno])
- end
- --sound horn when required
- if self.horn_sound and pc.aux1 and not pc.sneak and not self.horn_handle then
- self.horn_handle = minetest.sound_play(self.horn_sound, {
- object = self.object,
- gain = 1.0, -- default
- max_hear_distance = 128, -- default, uses an euclidean metric
- loop = true,
- })
- elseif not pc.aux1 and self.horn_handle then
- minetest.sound_stop(self.horn_handle)
- self.horn_handle = nil
- end
- else
- -- If on a passenger seat and doors are open, get off when W or D pressed.
- local pass = data.seatp[seatno] and minetest.get_player_by_name(data.seatp[seatno])
- if pass and self:train().door_open~=0 then
- local pc=pass:get_player_control()
- if pc.up or pc.down then
- self:get_off(seatno)
- end
- end
- end
- if pc.aux1 and pc.sneak then
- self:get_off(seatno)
- end
- end
- end
-
- --check infotext
- local outside=train.text_outside or ""
- if setting_show_ids then
- outside = outside .. "\nT:" .. data.train_id .. " W:" .. self.id .. " O:" .. data.owner
- end
-
-
- --show off-track information in outside text instead of notifying the whole server about this
- if train.off_track then
- outside = outside .."\n!!! Train off track !!!"
- end
-
- if self.infotext_cache~=outside then
- self.object:set_properties({infotext=outside})
- self.infotext_cache=outside
- end
-
- local fct=data.wagon_flipped and -1 or 1
-
- --door animation
- if self.doors then
- if (self.door_anim_timer or 0)<=0 then
- local dstate = (train.door_open or 0) * fct
- if dstate ~= self.door_state then
- local at
- --meaning of the train.door_open field:
- -- -1: left doors (rel. to train orientation)
- -- 0: closed
- -- 1: right doors
- --this code produces the following behavior:
- -- if changed from 0 to +-1, play open anim. if changed from +-1 to 0, play close.
- -- if changed from +-1 to -+1, first close and set 0, then it will detect state change again and run open.
- if self.door_state == 0 then
- if self.doors.open.sound then minetest.sound_play(self.doors.open.sound, {object = self.object}) end
- at=self.doors.open[dstate]
- self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false)
- self.door_state = dstate
- else
- if self.doors.close.sound then minetest.sound_play(self.doors.close.sound, {object = self.object}) end
- at=self.doors.close[self.door_state or 1]--in case it has not been set yet
- self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false)
- self.door_state = 0
- end
- self.door_anim_timer = at.time
- end
- else
- self.door_anim_timer = (self.door_anim_timer or 0) - dtime
- end
- end
-
- --for path to be available. if not, skip step
- if not train.path or train.no_step then
- self.object:setvelocity({x=0, y=0, z=0})
- self.object:setacceleration({x=0, y=0, z=0})
- return
- end
- if not data.pos_in_train then
- return
- end
-
- -- Calculate new position, yaw and direction vector
- local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
- local pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
- local vdir = vector.normalize(vector.subtract(npos2, npos))
-
- --automatic get_on
- --needs to know index and path
- if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
- --using the mapping created by the trainlogic globalstep
- for i, ino in ipairs(self.door_entry) do
- --fct is the flipstate flag from door animation above
- local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)
- local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
- -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
- -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
- local add = { x = (ix2.z-ix1.z)*train.door_open, y = 0, z = (ix1.x-ix2.x)*train.door_open }
- local pts1=vector.round(vector.add(ix1, add))
- local pts2=vector.round(vector.add(ix2, add))
- if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then
- local ckpts={
- pts1,
- pts2,
- vector.add(pts1, {x=0, y=1, z=0}),
- vector.add(pts2, {x=0, y=1, z=0}),
- }
- for _,ckpos in ipairs(ckpts) do
- local cpp=minetest.pos_to_string(ckpos)
- if advtrains.playersbypts[cpp] then
- self:on_rightclick(advtrains.playersbypts[cpp])
- end
- end
- end
- end
- end
-
- --checking for environment collisions(a 3x3 cube around the center)
- if not train.recently_collided_with_env then
- local collides=false
- local exh = self.extent_h or 1
- local exv = self.extent_v or 2
- for x=-exh,exh do
- for y=0,exv do
- for z=-exh,exh do
- local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z}))
- if (advtrains.train_collides(node)) then
- collides=true
- end
- end
- end
- end
- if collides then
- -- screw collision mercy
- train.recently_collided_with_env=true
- train.velocity=0
- advtrains.atc.train_reset_command(train)
- end
- end
-
- --DisCouple
- -- FIX: Need to do this after the yaw calculation
- if data.pos_in_trainparts and data.pos_in_trainparts>1 then
- if train.velocity==0 then
- if not self.discouple or not self.discouple.object:getyaw() then
- atprint(self.id,"trying to spawn discouple")
- local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
- local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
- if object then
- local le=object:get_luaentity()
- le.wagon=self
- --box is hidden when attached, so unuseful.
- --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
- self.discouple=le
- end
- end
- else
- if self.discouple and self.discouple.object:getyaw() then
- self.discouple.object:remove()
- atprint(self.id," removing discouple")
- end
- end
- end
-
- --FIX: use index of the wagon, not of the train.
- local velocity = train.velocity
- local acceleration = (train.acceleration or 0)
- local velocityvec = vector.multiply(vdir, velocity)
- local accelerationvec = vector.multiply(vdir, acceleration)
-
- if data.wagon_flipped then
- yaw=yaw+math.pi
- end
-
- -- this timer runs off every 2 seconds.
- self.updatepct_timer=(self.updatepct_timer or 0)-dtime
- local updatepct_timer_elapsed = self.updatepct_timer<=0
-
- if updatepct_timer_elapsed then
- --restart timer
- self.updatepct_timer=2
- -- perform checks that are not frequently needed
-
- -- unload entity if out of range (because relevant pr won't be merged in engine)
- -- This is a WORKAROUND!
- local players_in = false
- for sno,pname in pairs(data.seatp) do
- if minetest.get_player_by_name(pname) then
- -- Fix: If the RTT is too high, a wagon might be recognized out of range even if a player sits in it
- -- (client updates position not fast enough)
- players_in = true
- break
- end
- end
- if not players_in then
- local outofrange = true
- for _,p in pairs(minetest.get_connected_players()) do
- if vector.distance(p:get_pos(),pos)<=unload_wgn_range then
- outofrange = false
- end
- end
- if outofrange then
- --atdebug("wagon",self.id,"unloading (too far away)")
- self.object:remove()
- end
- end
- end
-
- if not self.old_velocity_vector
- or not vector.equals(velocityvec, self.old_velocity_vector)
- or not self.old_acceleration_vector
- or not vector.equals(accelerationvec, self.old_acceleration_vector)
- or self.old_yaw~=yaw
- or updatepct_timer_elapsed then--only send update packet if something changed
-
- self.object:setpos(pos)
- self.object:setvelocity(velocityvec)
- self.object:setacceleration(accelerationvec)
-
- if #self.seats > 0 and self.old_yaw ~= yaw then
- if not self.player_yaw then
- self.player_yaw = {}
- end
- if not self.old_yaw then
- self.old_yaw=yaw
- end
- for _,name in pairs(data.seatp) do
- local p = minetest.get_player_by_name(name)
- if p then
- if not self.turning then
- -- save player looking direction offset
- self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
- end
- -- set player looking direction using calculated offset
- p:set_look_horizontal((self.player_yaw[name] or 0)+yaw)
- end
- end
- self.turning = true
- elseif self.old_yaw == yaw then
- -- train is no longer turning
- self.turning = false
- end
-
- if self.object.set_rotation then
- local pitch = math.atan2(vdir.y, math.hypot(vdir.x, vdir.z))
- if data.wagon_flipped then
- pitch = -pitch
- end
- self.object:set_rotation({x=pitch, y=yaw, z=0})
- else
- self.object:setyaw(yaw)
- end
-
- if self.update_animation then
- self:update_animation(train.velocity, self.old_velocity)
- end
- if self.custom_on_velocity_change then
- self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime)
- end
- -- remove discouple object, because it will be in a wrong location
- if not updatepct_timer_elapsed and self.discouple then
- self.discouple.object:remove()
- end
- end
-
-
- self.old_velocity_vector=velocityvec
- self.old_velocity = train.velocity
- self.old_acceleration_vector=accelerationvec
- self.old_yaw=yaw
- atprintbm("wagon step", t)
- end)
-end
-
-function wagon:on_rightclick(clicker)
- return advtrains.pcall(function()
- if not self:ensure_init() then return end
- if not clicker or not clicker:is_player() then
- return
- end
-
- local data = advtrains.wagons[self.id]
-
- local pname=clicker:get_player_name()
- local no=self:get_seatno(pname)
- if no then
- if self.seat_groups then
- local poss={}
- local sgr=self.seats[no].group
- for _,access in ipairs(self.seat_groups[sgr].access_to) do
- if self:check_seat_group_access(pname, access) then
- poss[#poss+1]={name=self.seat_groups[access].name, key="sgr_"..access}
- end
- end
- if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- poss[#poss+1]={name=attrans("Show Inventory"), key="inv"}
- end
- if self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- poss[#poss+1]={name=attrans("Onboard Computer"), key="bordcom"}
- end
- if data.owner==pname then
- poss[#poss+1]={name=attrans("Wagon properties"), key="prop"}
- end
- if not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0 then
- poss[#poss+1]={name=attrans("Get off"), key="off"}
- else
- if clicker:get_player_control().sneak then
- poss[#poss+1]={name=attrans("Get off (forced)"), key="off"}
- else
- poss[#poss+1]={name=attrans("(Doors closed)"), key="dcwarn"}
- end
- end
- if #poss==0 then
- --can't do anything.
- elseif #poss==1 then
- self:seating_from_key_helper(pname, {[poss[1].key]=true}, no)
- else
- local form = "size[5,"..1+(#poss).."]"
- for pos,ent in ipairs(poss) do
- form = form .. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"
- end
- minetest.show_formspec(pname, "advtrains_seating_"..self.id, form)
- end
- else
- self:get_off(no)
- end
- else
- --do not attach if already on a train
- if advtrains.player_to_train_mapping[pname] then return end
- if self.seat_groups then
- if #self.seats==0 then
- if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
- end
- return
- end
-
- local doors_open = self:train().door_open~=0 or clicker:get_player_control().sneak
- local allow, rsn=false, "Wagon has no seats!"
- for _,sgr in ipairs(self.assign_to_seat_group) do
- allow, rsn = self:check_seat_group_access(pname, sgr)
- if allow then
- for seatid, seatdef in ipairs(self.seats) do
- if seatdef.group==sgr then
- if (not self.seat_groups[sgr].require_doors_open or doors_open) then
- if not data.seatp[seatid] then
- self:get_on(clicker, seatid)
- return
- else
- rsn="Wagon is full."
- end
- else
- rsn="Doors are closed! (try holding sneak key!)"
- end
- end
- end
- end
- end
- minetest.chat_send_player(pname, attrans("Can't get on: "..rsn))
- else
- self:show_get_on_form(pname)
- end
- end
- end)
-end
-
-function wagon:get_on(clicker, seatno)
-
- local data = advtrains.wagons[self.id]
-
- if not data.seatp then data.seatp={}end
- if not self.seatpc then self.seatpc={}end--player controls in driver stands
-
- if not self.seats[seatno] then return end
- local oldno=self:get_seatno(clicker:get_player_name())
- if oldno then
- atprint("get_on: clearing oldno",seatno)
- advtrains.player_to_train_mapping[clicker:get_player_name()]=nil
- advtrains.clear_driver_hud(clicker:get_player_name())
- data.seatp[oldno]=nil
- end
- if data.seatp[seatno] and data.seatp[seatno]~=clicker:get_player_name() then
- atprint("get_on: throwing off",data.seatp[seatno],"from seat",seatno)
- self:get_off(seatno)
- end
- atprint("get_on: attaching",clicker:get_player_name())
- data.seatp[seatno] = clicker:get_player_name()
- self.seatpc[seatno] = clicker:get_player_control_bits()
- advtrains.player_to_train_mapping[clicker:get_player_name()]=data.train_id
- clicker:set_attach(self.object, "", self.seats[seatno].attach_offset, {x=0,y=0,z=0})
- clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)
-end
-function wagon:get_off_plr(pname)
- local no=self:get_seatno(pname)
- if no then
- self:get_off(no)
- end
-end
-function wagon:get_seatno(pname)
-
- local data = advtrains.wagons[self.id]
-
- for no, cont in pairs(data.seatp) do
- if cont==pname then
- return no
- end
- end
- return nil
-end
-function wagon:get_off(seatno)
-
- local data = advtrains.wagons[self.id]
-
- if not data.seatp[seatno] then return end
- local pname = data.seatp[seatno]
- local clicker = minetest.get_player_by_name(pname)
- advtrains.player_to_train_mapping[pname]=nil
- advtrains.clear_driver_hud(pname)
- data.seatp[seatno]=nil
- self.seatpc[seatno]=nil
- if clicker then
- atprint("get_off: detaching",clicker:get_player_name())
- clicker:set_detach()
- clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
- local train=self:train()
- --code as in step - automatic get on
- if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 and train.index and train.path then
- local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
- for i, ino in ipairs(self.door_entry) do
- --atdebug("using door-based",i,ino)
- local fct=data.wagon_flipped and -1 or 1
- local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)
- local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
- local d = train.door_open
- if self.wagon_width then
- d = d * math.floor(self.wagon_width/2)
- end
- -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
- -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
- local add = { x = (ix2.z-ix1.z)*d, y = 0, z = (ix1.x-ix2.x)*d }
- local oadd = { x = (ix2.z-ix1.z)*(d+train.door_open), y = 1, z = (ix1.x-ix2.x)*(d+train.door_open)}
- local platpos=vector.round(vector.add(ix1, add))
- local offpos=vector.round(vector.add(ix1, oadd))
-
- --atdebug("platpos:", platpos, "offpos:", offpos)
- if minetest.get_item_group(minetest.get_node(platpos).name, "platform")>0 then
- minetest.after(GETOFF_TP_DELAY, function() clicker:setpos(offpos) end)
- --atdebug("tp",offpos)
- return
- end
- --atdebug("nope")
- end
- end
- --if not door_entry, or paths missing, fall back to old method
- --atdebug("using fallback")
- local objpos=advtrains.round_vector_floor_y(self.object:getpos())
- local yaw=self.object:getyaw()
- local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4)
- local offp
- --abuse helper function
- for _,r in ipairs({-1, 1}) do
- --atdebug("offset",r)
- local p=vector.add({x=isx and r or 0, y=0, z=not isx and r or 0}, objpos)
- offp=vector.add({x=isx and r*2 or 0, y=1, z=not isx and r*2 or 0}, objpos)
- --atdebug("platpos:", p, "offpos:", offp)
- if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then
- minetest.after(GETOFF_TP_DELAY, function() clicker:setpos(offp) end)
- --atdebug("tp",offp)
- return
- end
- end
- --atdebug("nope")
-
- end
-end
-function wagon:show_get_on_form(pname)
- if not self.initialized then return end
-
- local data = advtrains.wagons[self.id]
- if #self.seats==0 then
- if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
- end
- return
- end
- local form, comma="size[5,8]label[0.5,0.5;"..attrans("Select seat:").."]textlist[0.5,1;4,6;seat;", ""
- for seatno, seattbl in ipairs(self.seats) do
- local addtext, colorcode="", ""
- if data.seatp and data.seatp[seatno] then
- colorcode="#FF0000"
- addtext=" ("..data.seatp[seatno]..")"
- end
- form=form..comma..colorcode..seattbl.name..addtext
- comma=","
- end
- form=form..";0,false]"
- if self.has_inventory and self.get_inventory_formspec then
- form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]"
- end
- minetest.show_formspec(pname, "advtrains_geton_"..self.id, form)
-end
-function wagon:show_wagon_properties(pname)
- --[[
- fields:
- field: driving/couple whitelist
- button: save
- ]]
- local data = advtrains.wagons[self.id]
- local form="size[5,5]"
- form = form .. "field[0.5,1;4.5,1;whitelist;Allow these players to access your wagon:;"..minetest.formspec_escape(data.whitelist or "").."]"
- form = form .. "field[0.5,2;4.5,1;roadnumber;Wagon road number:;"..minetest.formspec_escape(data.roadnumber or "").."]"
- local fc = ""
- if data.fc then
- fc = table.concat(data.fc, "!")
- end
- form = form .. "field[0.5,3;4.5,1;fc;Freight Code:;"..fc.."]"
- if data.fc then
- if not data.fcind then data.fcind = 1 end
- if data.fcind > 1 then
- form=form.."button[0.5,3.5;1,1;fcp;prev FC]"
- end
- form=form.."label[1.5,3.5;Current FC:]"
-
- local cur = data.fc[data.fcind] or ""
- form=form.."label[1.5,3.75;"..minetest.formspec_escape(cur).."]"
- form=form.."button[3.5,3.5;1,1;fcn;next FC]"
- end
- form=form.."button_exit[0.5,4.5;4,1;save;"..attrans("Save wagon properties").."]"
- minetest.show_formspec(pname, "advtrains_prop_"..self.id, form)
-end
-
---BordCom
-local function checkcouple(ent)
- if not ent or not ent:getyaw() then
- return nil
- end
- local le = ent:get_luaentity()
- if not le or not le.is_couple then
- return nil
- end
- return le
-end
-local function checklock(pname, own1, own2, wl1, wl2)
- return advtrains.check_driving_couple_protection(pname, own1, wl1)
- or advtrains.check_driving_couple_protection(pname, own2, wl2)
-end
-
-local function split(str, sep)
- local fields = {}
- local pattern = string.format("([^%s]+)", sep)
- str:gsub(pattern, function(c) fields[#fields+1] = c end)
- return fields
-end
-
-function wagon.set_fc(data, fcstr)
- data.fc = split(fcstr, "!")
- if not data.fcind then
- data.fcind = 1
- elseif data.fcind > #data.fc then
- data.fcind = #data.fc
- end
-end
-
-function wagon.prev_fc(data)
- if data.fcind > 1 then
- data.fcind = data.fcind -1
- end
- if data.fcind == 1 and data.fcrev then
- data.fcrev = nil
- end
-end
-
-function wagon.next_fc(data)
- if not data.fc then return end
- if data.fcrev then
- wagon.prev_fc(data)
- return
- end
- if data.fcind < #data.fc then
- data.fcind = data.fcind + 1
- else
- data.fcind = 1
- end
- if data.fcind == #data.fc and data.fc[data.fcind] == "?" then
- data.fcrev = true
- wagon.prev_fc(data)
- return
- end
-end
-
-function advtrains.get_cur_fc(data)
- if not ( data.fc and data.fcind ) then
- return ""
- end
- return data.fc[data.fcind] or ""
-end
-
-function advtrains.step_fc(data)
- wagon.next_fc(data)
-end
-
-
-
-
-
-function wagon:show_bordcom(pname)
- if not self:train() then return end
- local train = self:train()
- local data = advtrains.wagons[self.id]
-
- local form = "size[11,9]label[0.5,0;AdvTrains Boardcom v0.1]"
- form=form.."textarea[0.5,1.5;7,1;text_outside;"..attrans("Text displayed outside on train")..";"..(minetest.formspec_escape(train.text_outside or "")).."]"
- form=form.."textarea[0.5,3;7,1;text_inside;"..attrans("Text displayed inside train")..";"..(minetest.formspec_escape(train.text_inside or "")).."]"
- form=form.."field[7.5,1.75;3,1;line;"..attrans("Line")..";"..(minetest.formspec_escape(train.line or "")).."]"
- form=form.."field[7.5,3.25;3,1;routingcode;"..attrans("Routingcode")..";"..(minetest.formspec_escape(train.routingcode or "")).."]"
- --row 5 : train overview and autocoupling
- if train.velocity==0 then
- form=form.."label[0.5,4;Train overview /coupling control:]"
- linhei=5
- local pre_own, pre_wl, owns_any = nil, nil, minetest.check_player_privs(pname, "train_admin")
- for i, tpid in ipairs(train.trainparts) do
- local ent = advtrains.wagons[tpid]
- if ent then
- local roadnumber = ent.roadnumber or ""
- form = form .. string.format("button[%d,%d;%d,%d;%s;%s]", i, linhei, 1, 0.2, "wgprp"..i, roadnumber)
- local ename = ent.type
- form = form .. "item_image["..i..","..(linhei+0.5)..";1,1;"..ename.."]"
- if i~=1 then
- if checklock(pname, ent.owner, pre_own, ent.whitelist, pre_wl) then
- form = form .. "image_button["..(i-0.5)..","..(linhei+1.5)..";1,1;advtrains_discouple.png;dcpl_"..i..";]"
- end
- end
- if i == data.pos_in_trainparts then
- form = form .. "box["..(i-0.1)..","..(linhei+0.4)..";1,1;green]"
- end
- pre_own = ent.owner
- pre_wl = ent.whitelist
- owns_any = owns_any or (not ent.owner or ent.owner==pname)
- end
- end
-
- if train.movedir==1 then
- form = form .. "label["..(#train.trainparts+1)..","..(linhei)..";-->]"
- else
- form = form .. "label[0.5,"..(linhei)..";<--]"
- end
- --check cpl_eid_front and _back of train
- local couple_front = checkcouple(train.cpl_front)
- local couple_back = checkcouple(train.cpl_back)
- if couple_front then
- form = form .. "image_button[0.5,"..(linhei+1)..";1,1;advtrains_couple.png;cpl_f;]"
- end
- if couple_back then
- form = form .. "image_button["..(#train.trainparts+0.5)..","..(linhei+1)..";1,1;advtrains_couple.png;cpl_b;]"
- end
-
- else
- form=form.."label[0.5,4.5;Train overview / coupling control is only shown when the train stands.]"
- end
- form = form .. "button[0.5,8;3,1;save;Save]"
-
- -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
- -- from inside the train
- if advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then
- local i=1
- while train.lzb.oncoming[i] do
- local oci = train.lzb.oncoming[i]
- if oci.udata and oci.udata.signal_pos then
- if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then
- form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]"
- break
- end
- end
- i=i+1
- end
- end
-
- minetest.show_formspec(pname, "advtrains_bordcom_"..self.id, form)
-end
-function wagon:handle_bordcom_fields(pname, formname, fields)
- local data = advtrains.wagons[self.id]
-
- local seatno=self:get_seatno(pname)
- if not seatno or not self.seat_groups[self.seats[seatno].group].driving_ctrl_access or not advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- return
- end
- local train = self:train()
- if not train then return end
- if fields.text_outside then
- if fields.text_outside~="" then
- train.text_outside=fields.text_outside
- else
- train.text_outside=nil
- end
- end
- if fields.text_inside then
- if fields.text_inside~="" then
- train.text_inside=fields.text_inside
- else
- train.text_inside=nil
- end
- end
- if fields.line then
- if fields.line~="" then
- if fields.line ~= train.line then
- train.line=fields.line
- minetest.after(0, advtrains.invalidate_path, train.id)
- end
- else
- train.line=nil
- end
- end
- if fields.routingcode then
- if fields.routingcode~="" then
- if fields.routingcode ~= train.routingcode then
- train.routingcode=fields.routingcode
- minetest.after(0, advtrains.invalidate_path, train.id)
- end
- else
- train.routingcode=nil
- end
- end
- for i, tpid in ipairs(train.trainparts) do
- if fields["dcpl_"..i] then
- advtrains.safe_decouple_wagon(tpid, pname)
- elseif fields["wgprp"..i] then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==tpid and data.owner==pname then
- wagon:show_wagon_properties(pname)
- return
- end
- end
- end
- end
- --check cpl_eid_front and _back of train
- local couple_front = checkcouple(train.cpl_front)
- local couple_back = checkcouple(train.cpl_back)
-
- if fields.cpl_f and couple_front then
- couple_front:on_rightclick(pname)
- end
- if fields.cpl_b and couple_back then
- couple_back:on_rightclick(pname)
- end
-
- -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
- -- from inside the train
- if fields.ilrs and advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then
- local i=1
- while train.lzb.oncoming[i] do
- local oci = train.lzb.oncoming[i]
- if oci.udata and oci.udata.signal_pos then
- local sigd = advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos)
- if sigd then
- advtrains.interlocking.show_signalling_form(sigd, pname)
- return
- end
- end
- i=i+1
- end
- end
-
-
- if not fields.quit then
- self:show_bordcom(pname)
- end
-end
-
-minetest.register_on_player_receive_fields(function(player, formname, fields)
- return advtrains.pcall(function()
- local uid=string.match(formname, "^advtrains_geton_(.+)$")
- if uid then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid then
- local data = advtrains.wagons[wagon.id]
- if fields.inv then
- if wagon.has_inventory and wagon.get_inventory_formspec then
- minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name(), make_inv_name(uid)))
- end
- elseif fields.seat then
- local val=minetest.explode_textlist_event(fields.seat)
- if val and val.type~="INV" and not data.seatp[player:get_player_name()] then
- --get on
- wagon:get_on(player, val.index)
- --will work with the new close_formspec functionality. close exactly this formspec.
- minetest.show_formspec(player:get_player_name(), formname, "")
- end
- end
- end
- end
- end
- uid=string.match(formname, "^advtrains_seating_(.+)$")
- if uid then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid then
- local pname=player:get_player_name()
- local no=wagon:get_seatno(pname)
- if no then
- if wagon.seat_groups then
- wagon:seating_from_key_helper(pname, fields, no)
- end
- end
- end
- end
- end
- uid=string.match(formname, "^advtrains_prop_(.+)$")
- if uid then
- local pname=player:get_player_name()
- local data = advtrains.wagons[uid]
- if pname~=data.owner and not minetest.check_player_privs(pname, {train_admin = true}) then
- return true
- end
- if fields.save or not fields.quit then
- if fields.whitelist then
- data.whitelist = fields.whitelist
- end
- if fields.roadnumber then
- data.roadnumber = fields.roadnumber
- end
- if fields.fc then
- wagon.set_fc(data, fields.fc)
- end
- if fields.fcp then
- wagon.prev_fc(data)
- wagon.show_wagon_properties({id=uid}, pname)
- end
- if fields.fcn then
- advtrains.step_fc(data)
- wagon.show_wagon_properties({id=uid}, pname)
- end
- end
- end
- uid=string.match(formname, "^advtrains_bordcom_(.+)$")
- if uid then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid then
- wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
- end
- end
- end
- uid=string.match(formname, "^advtrains_inv_(.+)$")
- if uid then
- local pname=player:get_player_name()
- local data = advtrains.wagons[uid]
- if fields.prop and data.owner==pname then
- for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.id==uid and data.owner==pname then
- wagon:show_wagon_properties(pname)
- --wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
- end
- end
- end
- end
- end)
-end)
-function wagon:seating_from_key_helper(pname, fields, no)
- local data = advtrains.wagons[self.id]
- local sgr=self.seats[no].group
- for _,access in ipairs(self.seat_groups[sgr].access_to) do
- if fields["sgr_"..access] and self:check_seat_group_access(pname, access) then
- for seatid, seatdef in ipairs(self.seats) do
- if seatdef.group==access and not data.seatp[seatid] then
- self:get_on(minetest.get_player_by_name(pname), seatid)
- return
- end
- end
- end
- end
- if fields.inv and self.has_inventory and self.get_inventory_formspec then
- minetest.close_formspec(pname, "advtrains_seating_"..self.id)
- minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
- end
- if fields.prop and data.owner==pname then
- minetest.close_formspec(pname, "advtrains_seating_"..self.id)
- self:show_wagon_properties(pname)
- end
- if fields.bordcom and self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
- minetest.close_formspec(pname, "advtrains_seating_"..self.id)
- self:show_bordcom(pname)
- end
- if fields.dcwarn then
- minetest.chat_send_player(pname, attrans("Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!"))
- end
- if fields.off then
- self:get_off(no)
- end
-end
-function wagon:check_seat_group_access(pname, sgr)
- local data = advtrains.wagons[self.id]
- if self.seat_groups[sgr].driving_ctrl_access and not (advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)) then
- return false, "Not allowed to access a driver stand!"
- end
- if self.seat_groups[sgr].driving_ctrl_access then
- advtrains.log("Drive", pname, self.object:getpos(), self:train().text_outside)
- end
- return true
-end
-function wagon:reattach_all()
- local data = advtrains.wagons[self.id]
- if not data.seatp then data.seatp={} end
- for seatno, pname in pairs(data.seatp) do
- local p=minetest.get_player_by_name(pname)
- if p then
- self:get_on(p ,seatno)
- end
- end
-end
-
-local function check_twagon_owner(train, b_first, pname)
- local wtp = b_first and 1 or #train.trainparts
- local wid = train.trainparts[wtp]
- local wdata = advtrains.wagons[wid]
- if wdata then
- return advtrains.check_driving_couple_protection(pname, wdata.owner, wdata.whitelist)
- end
- return false
-end
-
-function advtrains.safe_couple_trains(id1, id2, t1f, t2f, pname, try_run,v1,v2)
-
- if pname and not minetest.check_player_privs(pname, "train_operator") then
- minetest.chat_send_player(pname, "Missing train_operator privilege")
- return false
- end
-
- local train1=advtrains.trains[id1]
- local train2=advtrains.trains[id2]
-
- if not advtrains.train_ensure_init(id1, train1)
- or not advtrains.train_ensure_init(id2, train2) then
- return false
- end
- local wck_t1, wck_t2
- if pname then
- wck_t1 = check_twagon_owner(train1, t1f, pname)
- wck_t2 = check_twagon_owner(train2, t2f, pname)
- end
- if (wck_t1 or wck_t2) or not pname then
- if not v1 then
- v1 = 0
- end
- if not v2 then
- v2 = 0
- end
- if try_run then
- return true
- end
- if t1f then
- if t2f then
- v1 = -v1
- advtrains.invert_train(id1)
- advtrains.do_connect_trains(id1, id2, v1+v2)
- else
- advtrains.do_connect_trains(id2, id1, v1+v2)
- end
- else
- if t2f then
- advtrains.do_connect_trains(id1, id2, v1+v2)
- else
- v2 = -v2
- advtrains.invert_train(id2)
- advtrains.do_connect_trains(id1, id2, v1+v2)
- end
- end
- return true
- else
- minetest.chat_send_player(pname, "You must be authorized for at least one wagon.")
- return false
- end
-end
-
-
-function advtrains.safe_decouple_wagon(w_id, pname, try_run)
- if not minetest.check_player_privs(pname, "train_operator") then
- minetest.chat_send_player(pname, "Missing train_operator privilege")
- return false
- end
- local data = advtrains.wagons[w_id]
-
- local dpt = data.pos_in_trainparts
- if not dpt or dpt <= 1 then
- return false
- end
- local train = advtrains.trains[data.train_id]
- local owid = train.trainparts[dpt-1]
- local owdata = advtrains.wagons[owid]
-
- if not owdata then
- return
- end
-
- if not checklock(pname, data.owner, owdata.owner, data.whitelist, owdata.whitelist) then
- minetest.chat_send_player(pname, "Not allowed to do this.")
- return false
- end
-
- if try_run then
- return true
- end
-
- advtrains.log("Discouple", pname, train.last_pos, train.text_outside)
- advtrains.split_train_at_wagon(w_id)
- return true
-end
-
-
-
-function advtrains.get_wagon_prototype(data)
- local wt = data.type
- if not wt then
- -- LEGACY: Field was called "entity_name" in previous versions
- wt = data.entity_name
- data.type = data.entity_name
- data.entity_name = nil
- end
- if not wt or not advtrains.wagon_prototypes[wt] then
- atwarn("Unable to load wagon type",wt,", using placeholder")
- wt="advtrains:wagon_placeholder"
- end
- return wt, advtrains.wagon_prototypes[wt]
-end
-
-function advtrains.standard_inventory_formspec(self, pname, invname)
- --[[minetest.chat_send_player(pname, string.format("self=%s, pname=%s, invname=%s", self, pname, invname))
- for k,v in pairs(self) do
- minetest.chat_send_player(pname, string.format("%s=%s", k,v))
- end
- minetest.chat_send_player(pname, string.format("***%s***", self.object:get_pos()))--]]
- local data = advtrains.wagons[self.id]
- local r = "size[8,11]"..
- "list["..invname..";box;0,0;8,3;]"
- if data.owner==pname then
- r = r .. "button_exit[0,9;4,1;prop;"..attrans("Wagon properties").."]"
- end
- r = r .. "list[current_player;main;0,5;8,4;]"..
- "listring[]"
- return r
-end
-
-function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreative)
- local sysname = sysname_p
- if not string.match(sysname, ":") then
- sysname = "advtrains:"..sysname_p
- end
- setmetatable(prototype, {__index=wagon})
- minetest.register_entity(":"..sysname,prototype)
- advtrains.wagon_prototypes[sysname] = prototype
-
- minetest.register_craftitem(":"..sysname, {
- description = desc,
- inventory_image = inv_img,
- wield_image = inv_img,
- stack_max = 1,
-
- groups = { not_in_creative_inventory = nincreative and 1 or 0},
-
- on_place = function(itemstack, placer, pointed_thing)
- return advtrains.pcall(function()
- if not pointed_thing.type == "node" then
- return
- end
- local pname = placer:get_player_name()
-
- local node=minetest.get_node_or_nil(pointed_thing.under)
- if not node then atprint("[advtrains]Ignore at placer position") return itemstack end
- local nodename=node.name
- if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on)) then
- atprint("no track here, not placing.")
- return itemstack
- end
- if not minetest.check_player_privs(placer, {train_operator = true }) then
- minetest.chat_send_player(pname, "You don't have the train_operator privilege.")
- return itemstack
- end
- if not minetest.check_player_privs(placer, {train_admin = true }) and minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
- return itemstack
- end
- local tconns=advtrains.get_track_connections(node.name, node.param2)
- local yaw = placer:get_look_horizontal()
- local plconnid = advtrains.yawToClosestConn(yaw, tconns)
-
- local prevpos = advtrains.get_adjacent_rail(pointed_thing.under, tconns, plconnid, prototype.drives_on)
- if not prevpos then
- minetest.chat_send_player(pname, "The track you are trying to place the wagon on is not long enough!")
- return
- end
-
- local wid = advtrains.create_wagon(sysname, pname)
-
- local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, {wid})
-
- if not advtrains.is_creative(pname) then
- itemstack:take_item()
- end
- return itemstack
-
- end)
- end,
- })
-end
-
--- Placeholder wagon. Will be spawned whenever a mod is missing
-advtrains.register_wagon("advtrains:wagon_placeholder", {
- visual="sprite",
- textures = {"advtrains_wagon_placeholder.png"},
- collisionbox = {-0.3,-0.3,-0.3, 0.3,0.3,0.3},
- visual_size = {x=0.7, y=0.7},
- initial_sprite_basepos = {x=0, y=0},
- drives_on = advtrains.all_tracktypes,
- max_speed = 5,
- seats = {
- },
- seat_groups = {
- },
- assign_to_seat_group = {},
- wagon_span=1,
- drops={},
-}, "Wagon placeholder", "advtrains_wagon_placeholder.png", true)
-
+-- wagon.lua
+-- Holds all logic related to wagons
+-- From now on, wagons are, just like trains, just entries in a table
+-- All data that is static is stored in the entity prototype (self).
+-- A copy of the entity prototype is always available inside wagon_prototypes
+-- All dynamic data is stored in the (new) wagons table
+-- An entity is ONLY spawned by update_trainpart_properties when it finds it useful.
+-- Only data that are only important to the entity itself are stored in the luaentity
+
+-- TP delay when getting off wagon
+local GETOFF_TP_DELAY = 0.5
+
+advtrains.wagons = {}
+advtrains.wagon_prototypes = {}
+advtrains.wagon_objects = {}
+
+local unload_wgn_range = advtrains.wagon_load_range + 32
+
+local setting_show_ids = minetest.settings:get_bool("advtrains_show_ids")
+
+--
+function advtrains.create_wagon(wtype, owner)
+ local new_id=advtrains.random_id()
+ while advtrains.wagons[new_id] do new_id=advtrains.random_id() end
+ local wgn = {}
+ wgn.type = wtype
+ wgn.seatp = {}
+ wgn.owner = owner
+ wgn.id = new_id
+ ---wgn.train_id = train_id --- will get this via update_trainpart_properties
+ advtrains.wagons[new_id] = wgn
+ --atdebug("Created new wagon:",wgn)
+ return new_id
+end
+
+local function make_inv_name(uid)
+ return "detached:advtrains_wgn_"..uid
+end
+
+
+local wagon={
+ collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
+ --physical = true,
+ visual = "mesh",
+ mesh = "wagon.b3d",
+ visual_size = {x=1, y=1},
+ textures = {"black.png"},
+ is_wagon=true,
+ wagon_span=1,--how many index units of space does this wagon consume
+ wagon_width=3, -- Wagon width in meters
+ has_inventory=false,
+ static_save=false,
+}
+
+
+function wagon:train()
+ local data = advtrains.wagons[self.id]
+ return advtrains.trains[data.train_id]
+end
+
+
+function wagon:on_activate(sd_uid, dtime_s)
+ if sd_uid~="" then
+ --destroy when loaded from static block.
+ self.object:remove()
+ return
+ end
+ self.object:set_armor_groups({immortal=1})
+end
+
+local function invcallback(id, pname, rtallow, rtfail)
+ local data = advtrains.wagons[id]
+ if data and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ return rtallow
+ end
+ return rtfail
+end
+
+function wagon:set_id(wid)
+ self.id = wid
+ self.initialized = true
+
+ local data = advtrains.wagons[self.id]
+ advtrains.wagon_objects[self.id] = self.object
+
+ --atdebug("Created wagon entity:",self.name," w_id",wid," t_id",data.train_id)
+
+ if self.has_inventory then
+ --to be used later
+ local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})
+ -- create inventory, if not yet created
+ if not inv then
+ inv=minetest.create_detached_inventory("advtrains_wgn_"..self.id, {
+ allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
+ return invcallback(wid, player:get_player_name(), count, 0)
+ end,
+ allow_put = function(inv, listname, index, stack, player)
+ return invcallback(wid, player:get_player_name(), stack:get_count(), 0)
+ end,
+ allow_take = function(inv, listname, index, stack, player)
+ return invcallback(wid, player:get_player_name(), stack:get_count(), 0)
+ end
+ })
+ if data.ser_inv then
+ advtrains.deserialize_inventory(data.ser_inv, inv)
+ end
+ if self.inventory_list_sizes then
+ for lst, siz in pairs(self.inventory_list_sizes) do
+ inv:set_size(lst, siz)
+ end
+ end
+ end
+ end
+ self.door_anim_timer=0
+ self.door_state=0
+
+ minetest.after(0.2, function() self:reattach_all() end)
+
+
+
+ if self.set_textures then
+ self:set_textures(data)
+ end
+
+ if self.custom_on_activate then
+ self:custom_on_activate()
+ end
+end
+
+function wagon:get_staticdata()
+ return "STATIC"
+end
+
+function wagon:ensure_init()
+ -- Note: A wagon entity won't exist when there's no train, because the train is
+ -- the thing that actually creates the entity
+ -- Train not being set just means that this will happen as soon as the train calls update_trainpart_properties.
+ if self.initialized and self.id then
+ local data = advtrains.wagons[self.id]
+ if data and data.train_id and self:train() then
+ if self.noninitticks then self.noninitticks=nil end
+ return true
+ end
+ end
+ if not self.noninitticks then
+ atwarn("wagon",self.id,"uninitialized init=",self.initialized)
+ self.noninitticks=0
+ end
+ self.noninitticks=self.noninitticks+1
+ if self.noninitticks>20 then
+ atwarn("wagon",self.id,"uninitialized, removing")
+ self:destroy()
+ else
+ self.object:setvelocity({x=0,y=0,z=0})
+ end
+ return false
+end
+
+function wagon:train()
+ local data = advtrains.wagons[self.id]
+ return advtrains.trains[data.train_id]
+end
+
+-- Remove the wagon
+function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
+ return advtrains.pcall(function()
+ if not self:ensure_init() then return end
+
+ local data = advtrains.wagons[self.id]
+
+ if not puncher or not puncher:is_player() then
+ return
+ end
+ if data.owner and puncher:get_player_name()~=data.owner and (not minetest.check_player_privs(puncher, {train_admin = true })) then
+ minetest.chat_send_player(puncher:get_player_name(), attrans("This wagon is owned by @1, you can't destroy it.", data.owner));
+ return
+ end
+
+ if self.custom_may_destroy then
+ if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then
+ return
+ end
+ end
+ local itemstack = puncher:get_wielded_item()
+ -- WARNING: This part of the API is guaranteed to change! DO NOT USE!
+ if self.set_livery and itemstack:get_name() == "bike:painter" then
+ self:set_livery(puncher, itemstack, data)
+ return
+ end
+ -- check whether wagon has an inventory. Is is empty?
+ if self.has_inventory then
+ local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})
+ if not inv then -- inventory is not initialized when wagon was never loaded - should never happen
+ atwarn("Destroying wagon with inventory, but inventory is not found? Shouldn't happen!")
+ return
+ end
+ for listname, _ in pairs(inv:get_lists()) do
+ if not inv:is_empty(listname) then
+ minetest.chat_send_player(puncher:get_player_name(), attrans("The wagon's inventory is not empty!"));
+ return
+ end
+ end
+ end
+
+ if #(self:train().trainparts)>1 then
+ minetest.chat_send_player(puncher:get_player_name(), attrans("Wagon needs to be decoupled from other wagons in order to destroy it."));
+ return
+ end
+
+ local pc=puncher:get_player_control()
+ if not pc.sneak then
+ minetest.chat_send_player(puncher:get_player_name(), attrans("Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon."))
+ return
+ end
+
+
+ if not self:destroy() then return end
+
+ local inv = puncher:get_inventory()
+ for _,item in ipairs(self.drops or {self.name}) do
+ inv:add_item("main", item)
+ end
+ end)
+end
+function wagon:destroy()
+ --some rules:
+ -- you get only some items back
+ -- single left-click shows warning
+ -- shift leftclick destroys
+ -- not when a driver is inside
+ if self.id then
+ local data = advtrains.wagons[self.id]
+ if not data then
+ atwarn("wagon:destroy(): data is not set!")
+ return
+ end
+
+ if self.custom_on_destroy then
+ self.custom_on_destroy(self)
+ end
+
+ for seat,_ in pairs(data.seatp) do
+ self:get_off(seat)
+ end
+
+ if data.train_id and self:train() then
+ advtrains.remove_train(data.train_id)
+ advtrains.wagons[self.id]=nil
+ if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects
+ end
+ end
+ --atdebug("[wagon ", self.id, "]: destroying")
+
+ self.object:remove()
+ return true
+end
+
+function wagon:on_step(dtime)
+ return advtrains.pcall(function()
+ if not self:ensure_init() then return end
+
+ local t=os.clock()
+ local pos = self.object:getpos()
+ local data = advtrains.wagons[self.id]
+
+ if not pos then
+ --atdebug("["..self.id.."][fatal] missing position (object:getpos() returned nil)")
+ return
+ end
+
+ if not data.seatp then
+ data.seatp={}
+ end
+ if not self.seatpc then
+ self.seatpc={}
+ end
+
+ local train=self:train()
+
+ --custom on_step function
+ if self.custom_on_step then
+ self:custom_on_step(dtime, data, train)
+ end
+
+ --driver control
+ for seatno, seat in ipairs(self.seats) do
+ local pname=data.seatp[seatno]
+ local driver=pname and minetest.get_player_by_name(pname)
+ local has_driverstand = pname and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)
+ if self.seat_groups then
+ has_driverstand = has_driverstand and (seat.driving_ctrl_access or self.seat_groups[seat.group].driving_ctrl_access)
+ else
+ has_driverstand = has_driverstand and (seat.driving_ctrl_access)
+ end
+ if has_driverstand and driver then
+ advtrains.update_driver_hud(driver:get_player_name(), self:train(), data.wagon_flipped)
+ elseif driver then
+ --only show the inside text
+ local inside=self:train().text_inside or ""
+ advtrains.set_trainhud(driver:get_player_name(), inside)
+ end
+ if driver and driver:get_player_control_bits()~=self.seatpc[seatno] then
+ local pc=driver:get_player_control()
+ self.seatpc[seatno]=driver:get_player_control_bits()
+
+ if has_driverstand then
+ --regular driver stand controls
+ advtrains.on_control_change(pc, self:train(), data.wagon_flipped)
+ --bordcom
+ if pc.sneak and pc.jump then
+ self:show_bordcom(data.seatp[seatno])
+ end
+ --sound horn when required
+ if self.horn_sound and pc.aux1 and not pc.sneak and not self.horn_handle then
+ self.horn_handle = minetest.sound_play(self.horn_sound, {
+ object = self.object,
+ gain = 1.0, -- default
+ max_hear_distance = 128, -- default, uses an euclidean metric
+ loop = true,
+ })
+ elseif not pc.aux1 and self.horn_handle then
+ minetest.sound_stop(self.horn_handle)
+ self.horn_handle = nil
+ end
+ else
+ -- If on a passenger seat and doors are open, get off when W or D pressed.
+ local pass = data.seatp[seatno] and minetest.get_player_by_name(data.seatp[seatno])
+ if pass and self:train().door_open~=0 then
+ local pc=pass:get_player_control()
+ if pc.up or pc.down then
+ self:get_off(seatno)
+ end
+ end
+ end
+ if pc.aux1 and pc.sneak then
+ self:get_off(seatno)
+ end
+ end
+ end
+
+ --check infotext
+ local outside=train.text_outside or ""
+ if setting_show_ids then
+ outside = outside .. "\nT:" .. data.train_id .. " W:" .. self.id .. " O:" .. data.owner
+ end
+
+
+ --show off-track information in outside text instead of notifying the whole server about this
+ if train.off_track then
+ outside = outside .."\n!!! Train off track !!!"
+ end
+
+ if self.infotext_cache~=outside then
+ self.object:set_properties({infotext=outside})
+ self.infotext_cache=outside
+ end
+
+ local fct=data.wagon_flipped and -1 or 1
+
+ --door animation
+ if self.doors then
+ if (self.door_anim_timer or 0)<=0 then
+ local dstate = (train.door_open or 0) * fct
+ if dstate ~= self.door_state then
+ local at
+ --meaning of the train.door_open field:
+ -- -1: left doors (rel. to train orientation)
+ -- 0: closed
+ -- 1: right doors
+ --this code produces the following behavior:
+ -- if changed from 0 to +-1, play open anim. if changed from +-1 to 0, play close.
+ -- if changed from +-1 to -+1, first close and set 0, then it will detect state change again and run open.
+ if self.door_state == 0 then
+ if self.doors.open.sound then minetest.sound_play(self.doors.open.sound, {object = self.object}) end
+ at=self.doors.open[dstate]
+ self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false)
+ self.door_state = dstate
+ else
+ if self.doors.close.sound then minetest.sound_play(self.doors.close.sound, {object = self.object}) end
+ at=self.doors.close[self.door_state or 1]--in case it has not been set yet
+ self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false)
+ self.door_state = 0
+ end
+ self.door_anim_timer = at.time
+ end
+ else
+ self.door_anim_timer = (self.door_anim_timer or 0) - dtime
+ end
+ end
+
+ --for path to be available. if not, skip step
+ if not train.path or train.no_step then
+ self.object:setvelocity({x=0, y=0, z=0})
+ self.object:setacceleration({x=0, y=0, z=0})
+ return
+ end
+ if not data.pos_in_train then
+ return
+ end
+
+ -- Calculate new position, yaw and direction vector
+ local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
+ local pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
+ local vdir = vector.normalize(vector.subtract(npos2, npos))
+
+ --automatic get_on
+ --needs to know index and path
+ if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
+ --using the mapping created by the trainlogic globalstep
+ for i, ino in ipairs(self.door_entry) do
+ --fct is the flipstate flag from door animation above
+ local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)
+ local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
+ -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
+ -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
+ local add = { x = (ix2.z-ix1.z)*train.door_open, y = 0, z = (ix1.x-ix2.x)*train.door_open }
+ local pts1=vector.round(vector.add(ix1, add))
+ local pts2=vector.round(vector.add(ix2, add))
+ if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then
+ local ckpts={
+ pts1,
+ pts2,
+ vector.add(pts1, {x=0, y=1, z=0}),
+ vector.add(pts2, {x=0, y=1, z=0}),
+ }
+ for _,ckpos in ipairs(ckpts) do
+ local cpp=minetest.pos_to_string(ckpos)
+ if advtrains.playersbypts[cpp] then
+ self:on_rightclick(advtrains.playersbypts[cpp])
+ end
+ end
+ end
+ end
+ end
+
+ --checking for environment collisions(a 3x3 cube around the center)
+ if not train.recently_collided_with_env then
+ local collides=false
+ local exh = self.extent_h or 1
+ local exv = self.extent_v or 2
+ for x=-exh,exh do
+ for y=0,exv do
+ for z=-exh,exh do
+ local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z}))
+ if (advtrains.train_collides(node)) then
+ collides=true
+ end
+ end
+ end
+ end
+ if collides then
+ -- screw collision mercy
+ train.recently_collided_with_env=true
+ train.velocity=0
+ advtrains.atc.train_reset_command(train)
+ end
+ end
+
+ --DisCouple
+ -- FIX: Need to do this after the yaw calculation
+ if data.pos_in_trainparts and data.pos_in_trainparts>1 then
+ if train.velocity==0 then
+ if not self.discouple or not self.discouple.object:getyaw() then
+ atprint(self.id,"trying to spawn discouple")
+ local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
+ local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
+ if object then
+ local le=object:get_luaentity()
+ le.wagon=self
+ --box is hidden when attached, so unuseful.
+ --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
+ self.discouple=le
+ end
+ end
+ else
+ if self.discouple and self.discouple.object:getyaw() then
+ self.discouple.object:remove()
+ atprint(self.id," removing discouple")
+ end
+ end
+ end
+
+ --FIX: use index of the wagon, not of the train.
+ local velocity = train.velocity
+ local acceleration = (train.acceleration or 0)
+ local velocityvec = vector.multiply(vdir, velocity)
+ local accelerationvec = vector.multiply(vdir, acceleration)
+
+ if data.wagon_flipped then
+ yaw=yaw+math.pi
+ end
+
+ -- this timer runs off every 2 seconds.
+ self.updatepct_timer=(self.updatepct_timer or 0)-dtime
+ local updatepct_timer_elapsed = self.updatepct_timer<=0
+
+ if updatepct_timer_elapsed then
+ --restart timer
+ self.updatepct_timer=2
+ -- perform checks that are not frequently needed
+
+ -- unload entity if out of range (because relevant pr won't be merged in engine)
+ -- This is a WORKAROUND!
+ local players_in = false
+ for sno,pname in pairs(data.seatp) do
+ if minetest.get_player_by_name(pname) then
+ -- Fix: If the RTT is too high, a wagon might be recognized out of range even if a player sits in it
+ -- (client updates position not fast enough)
+ players_in = true
+ break
+ end
+ end
+ if not players_in then
+ local outofrange = true
+ for _,p in pairs(minetest.get_connected_players()) do
+ if vector.distance(p:get_pos(),pos)<=unload_wgn_range then
+ outofrange = false
+ end
+ end
+ if outofrange then
+ --atdebug("wagon",self.id,"unloading (too far away)")
+ self.object:remove()
+ end
+ end
+ end
+
+ if not self.old_velocity_vector
+ or not vector.equals(velocityvec, self.old_velocity_vector)
+ or not self.old_acceleration_vector
+ or not vector.equals(accelerationvec, self.old_acceleration_vector)
+ or self.old_yaw~=yaw
+ or updatepct_timer_elapsed then--only send update packet if something changed
+
+ self.object:setpos(pos)
+ self.object:setvelocity(velocityvec)
+ self.object:setacceleration(accelerationvec)
+
+ if #self.seats > 0 and self.old_yaw ~= yaw then
+ if not self.player_yaw then
+ self.player_yaw = {}
+ end
+ if not self.old_yaw then
+ self.old_yaw=yaw
+ end
+ for _,name in pairs(data.seatp) do
+ local p = minetest.get_player_by_name(name)
+ if p then
+ if not self.turning then
+ -- save player looking direction offset
+ self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
+ end
+ -- set player looking direction using calculated offset
+ p:set_look_horizontal((self.player_yaw[name] or 0)+yaw)
+ end
+ end
+ self.turning = true
+ elseif self.old_yaw == yaw then
+ -- train is no longer turning
+ self.turning = false
+ end
+
+ if self.object.set_rotation then
+ local pitch = math.atan2(vdir.y, math.hypot(vdir.x, vdir.z))
+ if data.wagon_flipped then
+ pitch = -pitch
+ end
+ self.object:set_rotation({x=pitch, y=yaw, z=0})
+ else
+ self.object:setyaw(yaw)
+ end
+
+ if self.update_animation then
+ self:update_animation(train.velocity, self.old_velocity)
+ end
+ if self.custom_on_velocity_change then
+ self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime)
+ end
+ -- remove discouple object, because it will be in a wrong location
+ if not updatepct_timer_elapsed and self.discouple then
+ self.discouple.object:remove()
+ end
+ end
+
+
+ self.old_velocity_vector=velocityvec
+ self.old_velocity = train.velocity
+ self.old_acceleration_vector=accelerationvec
+ self.old_yaw=yaw
+ atprintbm("wagon step", t)
+ end)
+end
+
+function wagon:on_rightclick(clicker)
+ return advtrains.pcall(function()
+ if not self:ensure_init() then return end
+ if not clicker or not clicker:is_player() then
+ return
+ end
+
+ local data = advtrains.wagons[self.id]
+
+ local pname=clicker:get_player_name()
+ local no=self:get_seatno(pname)
+ if no then
+ if self.seat_groups then
+ local poss={}
+ local sgr=self.seats[no].group
+ for _,access in ipairs(self.seat_groups[sgr].access_to) do
+ if self:check_seat_group_access(pname, access) then
+ poss[#poss+1]={name=self.seat_groups[access].name, key="sgr_"..access}
+ end
+ end
+ if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ poss[#poss+1]={name=attrans("Show Inventory"), key="inv"}
+ end
+ if self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ poss[#poss+1]={name=attrans("Onboard Computer"), key="bordcom"}
+ end
+ if data.owner==pname then
+ poss[#poss+1]={name=attrans("Wagon properties"), key="prop"}
+ end
+ if not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0 then
+ poss[#poss+1]={name=attrans("Get off"), key="off"}
+ else
+ if clicker:get_player_control().sneak then
+ poss[#poss+1]={name=attrans("Get off (forced)"), key="off"}
+ else
+ poss[#poss+1]={name=attrans("(Doors closed)"), key="dcwarn"}
+ end
+ end
+ if #poss==0 then
+ --can't do anything.
+ elseif #poss==1 then
+ self:seating_from_key_helper(pname, {[poss[1].key]=true}, no)
+ else
+ local form = "size[5,"..1+(#poss).."]"
+ for pos,ent in ipairs(poss) do
+ form = form .. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"
+ end
+ minetest.show_formspec(pname, "advtrains_seating_"..self.id, form)
+ end
+ else
+ self:get_off(no)
+ end
+ else
+ --do not attach if already on a train
+ if advtrains.player_to_train_mapping[pname] then return end
+ if self.seat_groups then
+ if #self.seats==0 then
+ if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
+ end
+ return
+ end
+
+ local doors_open = self:train().door_open~=0 or clicker:get_player_control().sneak
+ local allow, rsn=false, "Wagon has no seats!"
+ for _,sgr in ipairs(self.assign_to_seat_group) do
+ allow, rsn = self:check_seat_group_access(pname, sgr)
+ if allow then
+ for seatid, seatdef in ipairs(self.seats) do
+ if seatdef.group==sgr then
+ if (not self.seat_groups[sgr].require_doors_open or doors_open) then
+ if not data.seatp[seatid] then
+ self:get_on(clicker, seatid)
+ return
+ else
+ rsn="Wagon is full."
+ end
+ else
+ rsn="Doors are closed! (try holding sneak key!)"
+ end
+ end
+ end
+ end
+ end
+ minetest.chat_send_player(pname, attrans("Can't get on: "..rsn))
+ else
+ self:show_get_on_form(pname)
+ end
+ end
+ end)
+end
+
+function wagon:get_on(clicker, seatno)
+
+ local data = advtrains.wagons[self.id]
+
+ if not data.seatp then data.seatp={}end
+ if not self.seatpc then self.seatpc={}end--player controls in driver stands
+
+ if not self.seats[seatno] then return end
+ local oldno=self:get_seatno(clicker:get_player_name())
+ if oldno then
+ atprint("get_on: clearing oldno",seatno)
+ advtrains.player_to_train_mapping[clicker:get_player_name()]=nil
+ advtrains.clear_driver_hud(clicker:get_player_name())
+ data.seatp[oldno]=nil
+ end
+ if data.seatp[seatno] and data.seatp[seatno]~=clicker:get_player_name() then
+ atprint("get_on: throwing off",data.seatp[seatno],"from seat",seatno)
+ self:get_off(seatno)
+ end
+ atprint("get_on: attaching",clicker:get_player_name())
+ data.seatp[seatno] = clicker:get_player_name()
+ self.seatpc[seatno] = clicker:get_player_control_bits()
+ advtrains.player_to_train_mapping[clicker:get_player_name()]=data.train_id
+ clicker:set_attach(self.object, "", self.seats[seatno].attach_offset, {x=0,y=0,z=0})
+ clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)
+end
+function wagon:get_off_plr(pname)
+ local no=self:get_seatno(pname)
+ if no then
+ self:get_off(no)
+ end
+end
+function wagon:get_seatno(pname)
+
+ local data = advtrains.wagons[self.id]
+
+ for no, cont in pairs(data.seatp) do
+ if cont==pname then
+ return no
+ end
+ end
+ return nil
+end
+function wagon:get_off(seatno)
+
+ local data = advtrains.wagons[self.id]
+
+ if not data.seatp[seatno] then return end
+ local pname = data.seatp[seatno]
+ local clicker = minetest.get_player_by_name(pname)
+ advtrains.player_to_train_mapping[pname]=nil
+ advtrains.clear_driver_hud(pname)
+ data.seatp[seatno]=nil
+ self.seatpc[seatno]=nil
+ if clicker then
+ atprint("get_off: detaching",clicker:get_player_name())
+ clicker:set_detach()
+ clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
+ local train=self:train()
+ --code as in step - automatic get on
+ if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 and train.index and train.path then
+ local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
+ for i, ino in ipairs(self.door_entry) do
+ --atdebug("using door-based",i,ino)
+ local fct=data.wagon_flipped and -1 or 1
+ local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)
+ local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
+ local d = train.door_open
+ if self.wagon_width then
+ d = d * math.floor(self.wagon_width/2)
+ end
+ -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
+ -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
+ local add = { x = (ix2.z-ix1.z)*d, y = 0, z = (ix1.x-ix2.x)*d }
+ local oadd = { x = (ix2.z-ix1.z)*(d+train.door_open), y = 1, z = (ix1.x-ix2.x)*(d+train.door_open)}
+ local platpos=vector.round(vector.add(ix1, add))
+ local offpos=vector.round(vector.add(ix1, oadd))
+
+ --atdebug("platpos:", platpos, "offpos:", offpos)
+ if minetest.get_item_group(minetest.get_node(platpos).name, "platform")>0 then
+ minetest.after(GETOFF_TP_DELAY, function() clicker:setpos(offpos) end)
+ --atdebug("tp",offpos)
+ return
+ end
+ --atdebug("nope")
+ end
+ end
+ --if not door_entry, or paths missing, fall back to old method
+ --atdebug("using fallback")
+ local objpos=advtrains.round_vector_floor_y(self.object:getpos())
+ local yaw=self.object:getyaw()
+ local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4)
+ local offp
+ --abuse helper function
+ for _,r in ipairs({-1, 1}) do
+ --atdebug("offset",r)
+ local p=vector.add({x=isx and r or 0, y=0, z=not isx and r or 0}, objpos)
+ offp=vector.add({x=isx and r*2 or 0, y=1, z=not isx and r*2 or 0}, objpos)
+ --atdebug("platpos:", p, "offpos:", offp)
+ if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then
+ minetest.after(GETOFF_TP_DELAY, function() clicker:setpos(offp) end)
+ --atdebug("tp",offp)
+ return
+ end
+ end
+ --atdebug("nope")
+
+ end
+end
+function wagon:show_get_on_form(pname)
+ if not self.initialized then return end
+
+ local data = advtrains.wagons[self.id]
+ if #self.seats==0 then
+ if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
+ end
+ return
+ end
+ local form, comma="size[5,8]label[0.5,0.5;"..attrans("Select seat:").."]textlist[0.5,1;4,6;seat;", ""
+ for seatno, seattbl in ipairs(self.seats) do
+ local addtext, colorcode="", ""
+ if data.seatp and data.seatp[seatno] then
+ colorcode="#FF0000"
+ addtext=" ("..data.seatp[seatno]..")"
+ end
+ form=form..comma..colorcode..seattbl.name..addtext
+ comma=","
+ end
+ form=form..";0,false]"
+ if self.has_inventory and self.get_inventory_formspec then
+ form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]"
+ end
+ minetest.show_formspec(pname, "advtrains_geton_"..self.id, form)
+end
+function wagon:show_wagon_properties(pname)
+ --[[
+ fields:
+ field: driving/couple whitelist
+ button: save
+ ]]
+ local data = advtrains.wagons[self.id]
+ local form="size[5,5]"
+ form = form .. "field[0.5,1;4.5,1;whitelist;Allow these players to access your wagon:;"..minetest.formspec_escape(data.whitelist or "").."]"
+ form = form .. "field[0.5,2;4.5,1;roadnumber;Wagon road number:;"..minetest.formspec_escape(data.roadnumber or "").."]"
+ local fc = ""
+ if data.fc then
+ fc = table.concat(data.fc, "!")
+ end
+ form = form .. "field[0.5,3;4.5,1;fc;Freight Code:;"..fc.."]"
+ if data.fc then
+ if not data.fcind then data.fcind = 1 end
+ if data.fcind > 1 then
+ form=form.."button[0.5,3.5;1,1;fcp;prev FC]"
+ end
+ form=form.."label[1.5,3.5;Current FC:]"
+
+ local cur = data.fc[data.fcind] or ""
+ form=form.."label[1.5,3.75;"..minetest.formspec_escape(cur).."]"
+ form=form.."button[3.5,3.5;1,1;fcn;next FC]"
+ end
+ form=form.."button_exit[0.5,4.5;4,1;save;"..attrans("Save wagon properties").."]"
+ minetest.show_formspec(pname, "advtrains_prop_"..self.id, form)
+end
+
+--BordCom
+local function checkcouple(ent)
+ if not ent or not ent:getyaw() then
+ return nil
+ end
+ local le = ent:get_luaentity()
+ if not le or not le.is_couple then
+ return nil
+ end
+ return le
+end
+local function checklock(pname, own1, own2, wl1, wl2)
+ return advtrains.check_driving_couple_protection(pname, own1, wl1)
+ or advtrains.check_driving_couple_protection(pname, own2, wl2)
+end
+
+local function split(str, sep)
+ local fields = {}
+ local pattern = string.format("([^%s]+)", sep)
+ str:gsub(pattern, function(c) fields[#fields+1] = c end)
+ return fields
+end
+
+function wagon.set_fc(data, fcstr)
+ data.fc = split(fcstr, "!")
+ if not data.fcind then
+ data.fcind = 1
+ elseif data.fcind > #data.fc then
+ data.fcind = #data.fc
+ end
+end
+
+function wagon.prev_fc(data)
+ if data.fcind > 1 then
+ data.fcind = data.fcind -1
+ end
+ if data.fcind == 1 and data.fcrev then
+ data.fcrev = nil
+ end
+end
+
+function wagon.next_fc(data)
+ if not data.fc then return end
+ if data.fcrev then
+ wagon.prev_fc(data)
+ return
+ end
+ if data.fcind < #data.fc then
+ data.fcind = data.fcind + 1
+ else
+ data.fcind = 1
+ end
+ if data.fcind == #data.fc and data.fc[data.fcind] == "?" then
+ data.fcrev = true
+ wagon.prev_fc(data)
+ return
+ end
+end
+
+function advtrains.get_cur_fc(data)
+ if not ( data.fc and data.fcind ) then
+ return ""
+ end
+ return data.fc[data.fcind] or ""
+end
+
+function advtrains.step_fc(data)
+ wagon.next_fc(data)
+end
+
+
+
+
+
+function wagon:show_bordcom(pname)
+ if not self:train() then return end
+ local train = self:train()
+ local data = advtrains.wagons[self.id]
+
+ local form = "size[11,9]label[0.5,0;AdvTrains Boardcom v0.1]"
+ form=form.."textarea[0.5,1.5;7,1;text_outside;"..attrans("Text displayed outside on train")..";"..(minetest.formspec_escape(train.text_outside or "")).."]"
+ form=form.."textarea[0.5,3;7,1;text_inside;"..attrans("Text displayed inside train")..";"..(minetest.formspec_escape(train.text_inside or "")).."]"
+ form=form.."field[7.5,1.75;3,1;line;"..attrans("Line")..";"..(minetest.formspec_escape(train.line or "")).."]"
+ form=form.."field[7.5,3.25;3,1;routingcode;"..attrans("Routingcode")..";"..(minetest.formspec_escape(train.routingcode or "")).."]"
+ --row 5 : train overview and autocoupling
+ if train.velocity==0 then
+ form=form.."label[0.5,4;Train overview /coupling control:]"
+ linhei=5
+ local pre_own, pre_wl, owns_any = nil, nil, minetest.check_player_privs(pname, "train_admin")
+ for i, tpid in ipairs(train.trainparts) do
+ local ent = advtrains.wagons[tpid]
+ if ent then
+ local roadnumber = ent.roadnumber or ""
+ form = form .. string.format("button[%d,%d;%d,%d;%s;%s]", i, linhei, 1, 0.2, "wgprp"..i, roadnumber)
+ local ename = ent.type
+ form = form .. "item_image["..i..","..(linhei+0.5)..";1,1;"..ename.."]"
+ if i~=1 then
+ if checklock(pname, ent.owner, pre_own, ent.whitelist, pre_wl) then
+ form = form .. "image_button["..(i-0.5)..","..(linhei+1.5)..";1,1;advtrains_discouple.png;dcpl_"..i..";]"
+ end
+ end
+ if i == data.pos_in_trainparts then
+ form = form .. "box["..(i-0.1)..","..(linhei+0.4)..";1,1;green]"
+ end
+ pre_own = ent.owner
+ pre_wl = ent.whitelist
+ owns_any = owns_any or (not ent.owner or ent.owner==pname)
+ end
+ end
+
+ if train.movedir==1 then
+ form = form .. "label["..(#train.trainparts+1)..","..(linhei)..";-->]"
+ else
+ form = form .. "label[0.5,"..(linhei)..";<--]"
+ end
+ --check cpl_eid_front and _back of train
+ local couple_front = checkcouple(train.cpl_front)
+ local couple_back = checkcouple(train.cpl_back)
+ if couple_front then
+ form = form .. "image_button[0.5,"..(linhei+1)..";1,1;advtrains_couple.png;cpl_f;]"
+ end
+ if couple_back then
+ form = form .. "image_button["..(#train.trainparts+0.5)..","..(linhei+1)..";1,1;advtrains_couple.png;cpl_b;]"
+ end
+
+ else
+ form=form.."label[0.5,4.5;Train overview / coupling control is only shown when the train stands.]"
+ end
+ form = form .. "button[0.5,8;3,1;save;Save]"
+
+ -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
+ -- from inside the train
+ if advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then
+ local i=1
+ while train.lzb.oncoming[i] do
+ local oci = train.lzb.oncoming[i]
+ if oci.udata and oci.udata.signal_pos then
+ if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then
+ form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]"
+ break
+ end
+ end
+ i=i+1
+ end
+ end
+
+ minetest.show_formspec(pname, "advtrains_bordcom_"..self.id, form)
+end
+function wagon:handle_bordcom_fields(pname, formname, fields)
+ local data = advtrains.wagons[self.id]
+
+ local seatno=self:get_seatno(pname)
+ if not seatno or not self.seat_groups[self.seats[seatno].group].driving_ctrl_access or not advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ return
+ end
+ local train = self:train()
+ if not train then return end
+ if fields.text_outside then
+ if fields.text_outside~="" then
+ train.text_outside=fields.text_outside
+ else
+ train.text_outside=nil
+ end
+ end
+ if fields.text_inside then
+ if fields.text_inside~="" then
+ train.text_inside=fields.text_inside
+ else
+ train.text_inside=nil
+ end
+ end
+ if fields.line then
+ if fields.line~="" then
+ if fields.line ~= train.line then
+ train.line=fields.line
+ minetest.after(0, advtrains.invalidate_path, train.id)
+ end
+ else
+ train.line=nil
+ end
+ end
+ if fields.routingcode then
+ if fields.routingcode~="" then
+ if fields.routingcode ~= train.routingcode then
+ train.routingcode=fields.routingcode
+ minetest.after(0, advtrains.invalidate_path, train.id)
+ end
+ else
+ train.routingcode=nil
+ end
+ end
+ for i, tpid in ipairs(train.trainparts) do
+ if fields["dcpl_"..i] then
+ advtrains.safe_decouple_wagon(tpid, pname)
+ elseif fields["wgprp"..i] then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==tpid and data.owner==pname then
+ wagon:show_wagon_properties(pname)
+ return
+ end
+ end
+ end
+ end
+ --check cpl_eid_front and _back of train
+ local couple_front = checkcouple(train.cpl_front)
+ local couple_back = checkcouple(train.cpl_back)
+
+ if fields.cpl_f and couple_front then
+ couple_front:on_rightclick(pname)
+ end
+ if fields.cpl_b and couple_back then
+ couple_back:on_rightclick(pname)
+ end
+
+ -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
+ -- from inside the train
+ if fields.ilrs and advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then
+ local i=1
+ while train.lzb.oncoming[i] do
+ local oci = train.lzb.oncoming[i]
+ if oci.udata and oci.udata.signal_pos then
+ local sigd = advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos)
+ if sigd then
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ return
+ end
+ end
+ i=i+1
+ end
+ end
+
+
+ if not fields.quit then
+ self:show_bordcom(pname)
+ end
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ return advtrains.pcall(function()
+ local uid=string.match(formname, "^advtrains_geton_(.+)$")
+ if uid then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==uid then
+ local data = advtrains.wagons[wagon.id]
+ if fields.inv then
+ if wagon.has_inventory and wagon.get_inventory_formspec then
+ minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name(), make_inv_name(uid)))
+ end
+ elseif fields.seat then
+ local val=minetest.explode_textlist_event(fields.seat)
+ if val and val.type~="INV" and not data.seatp[player:get_player_name()] then
+ --get on
+ wagon:get_on(player, val.index)
+ --will work with the new close_formspec functionality. close exactly this formspec.
+ minetest.show_formspec(player:get_player_name(), formname, "")
+ end
+ end
+ end
+ end
+ end
+ uid=string.match(formname, "^advtrains_seating_(.+)$")
+ if uid then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==uid then
+ local pname=player:get_player_name()
+ local no=wagon:get_seatno(pname)
+ if no then
+ if wagon.seat_groups then
+ wagon:seating_from_key_helper(pname, fields, no)
+ end
+ end
+ end
+ end
+ end
+ uid=string.match(formname, "^advtrains_prop_(.+)$")
+ if uid then
+ local pname=player:get_player_name()
+ local data = advtrains.wagons[uid]
+ if pname~=data.owner and not minetest.check_player_privs(pname, {train_admin = true}) then
+ return true
+ end
+ if fields.save or not fields.quit then
+ if fields.whitelist then
+ data.whitelist = fields.whitelist
+ end
+ if fields.roadnumber then
+ data.roadnumber = fields.roadnumber
+ end
+ if fields.fc then
+ wagon.set_fc(data, fields.fc)
+ end
+ if fields.fcp then
+ wagon.prev_fc(data)
+ wagon.show_wagon_properties({id=uid}, pname)
+ end
+ if fields.fcn then
+ advtrains.step_fc(data)
+ wagon.show_wagon_properties({id=uid}, pname)
+ end
+ end
+ end
+ uid=string.match(formname, "^advtrains_bordcom_(.+)$")
+ if uid then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==uid then
+ wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
+ end
+ end
+ end
+ uid=string.match(formname, "^advtrains_inv_(.+)$")
+ if uid then
+ local pname=player:get_player_name()
+ local data = advtrains.wagons[uid]
+ if fields.prop and data.owner==pname then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==uid and data.owner==pname then
+ wagon:show_wagon_properties(pname)
+ --wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
+ end
+ end
+ end
+ end
+ end)
+end)
+function wagon:seating_from_key_helper(pname, fields, no)
+ local data = advtrains.wagons[self.id]
+ local sgr=self.seats[no].group
+ for _,access in ipairs(self.seat_groups[sgr].access_to) do
+ if fields["sgr_"..access] and self:check_seat_group_access(pname, access) then
+ for seatid, seatdef in ipairs(self.seats) do
+ if seatdef.group==access and not data.seatp[seatid] then
+ self:get_on(minetest.get_player_by_name(pname), seatid)
+ return
+ end
+ end
+ end
+ end
+ if fields.inv and self.has_inventory and self.get_inventory_formspec then
+ minetest.close_formspec(pname, "advtrains_seating_"..self.id)
+ minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
+ end
+ if fields.prop and data.owner==pname then
+ minetest.close_formspec(pname, "advtrains_seating_"..self.id)
+ self:show_wagon_properties(pname)
+ end
+ if fields.bordcom and self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then
+ minetest.close_formspec(pname, "advtrains_seating_"..self.id)
+ self:show_bordcom(pname)
+ end
+ if fields.dcwarn then
+ minetest.chat_send_player(pname, attrans("Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!"))
+ end
+ if fields.off then
+ self:get_off(no)
+ end
+end
+function wagon:check_seat_group_access(pname, sgr)
+ local data = advtrains.wagons[self.id]
+ if self.seat_groups[sgr].driving_ctrl_access and not (advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)) then
+ return false, "Not allowed to access a driver stand!"
+ end
+ if self.seat_groups[sgr].driving_ctrl_access then
+ advtrains.log("Drive", pname, self.object:getpos(), self:train().text_outside)
+ end
+ return true
+end
+function wagon:reattach_all()
+ local data = advtrains.wagons[self.id]
+ if not data.seatp then data.seatp={} end
+ for seatno, pname in pairs(data.seatp) do
+ local p=minetest.get_player_by_name(pname)
+ if p then
+ self:get_on(p ,seatno)
+ end
+ end
+end
+
+local function check_twagon_owner(train, b_first, pname)
+ local wtp = b_first and 1 or #train.trainparts
+ local wid = train.trainparts[wtp]
+ local wdata = advtrains.wagons[wid]
+ if wdata then
+ return advtrains.check_driving_couple_protection(pname, wdata.owner, wdata.whitelist)
+ end
+ return false
+end
+
+function advtrains.safe_couple_trains(id1, id2, t1f, t2f, pname, try_run,v1,v2)
+
+ if pname and not minetest.check_player_privs(pname, "train_operator") then
+ minetest.chat_send_player(pname, "Missing train_operator privilege")
+ return false
+ end
+
+ local train1=advtrains.trains[id1]
+ local train2=advtrains.trains[id2]
+
+ if not advtrains.train_ensure_init(id1, train1)
+ or not advtrains.train_ensure_init(id2, train2) then
+ return false
+ end
+ local wck_t1, wck_t2
+ if pname then
+ wck_t1 = check_twagon_owner(train1, t1f, pname)
+ wck_t2 = check_twagon_owner(train2, t2f, pname)
+ end
+ if (wck_t1 or wck_t2) or not pname then
+ if not v1 then
+ v1 = 0
+ end
+ if not v2 then
+ v2 = 0
+ end
+ if try_run then
+ return true
+ end
+ if t1f then
+ if t2f then
+ v1 = -v1
+ advtrains.invert_train(id1)
+ advtrains.do_connect_trains(id1, id2, v1+v2)
+ else
+ advtrains.do_connect_trains(id2, id1, v1+v2)
+ end
+ else
+ if t2f then
+ advtrains.do_connect_trains(id1, id2, v1+v2)
+ else
+ v2 = -v2
+ advtrains.invert_train(id2)
+ advtrains.do_connect_trains(id1, id2, v1+v2)
+ end
+ end
+ return true
+ else
+ minetest.chat_send_player(pname, "You must be authorized for at least one wagon.")
+ return false
+ end
+end
+
+
+function advtrains.safe_decouple_wagon(w_id, pname, try_run)
+ if not minetest.check_player_privs(pname, "train_operator") then
+ minetest.chat_send_player(pname, "Missing train_operator privilege")
+ return false
+ end
+ local data = advtrains.wagons[w_id]
+
+ local dpt = data.pos_in_trainparts
+ if not dpt or dpt <= 1 then
+ return false
+ end
+ local train = advtrains.trains[data.train_id]
+ local owid = train.trainparts[dpt-1]
+ local owdata = advtrains.wagons[owid]
+
+ if not owdata then
+ return
+ end
+
+ if not checklock(pname, data.owner, owdata.owner, data.whitelist, owdata.whitelist) then
+ minetest.chat_send_player(pname, "Not allowed to do this.")
+ return false
+ end
+
+ if try_run then
+ return true
+ end
+
+ advtrains.log("Discouple", pname, train.last_pos, train.text_outside)
+ advtrains.split_train_at_wagon(w_id)
+ return true
+end
+
+
+
+function advtrains.get_wagon_prototype(data)
+ local wt = data.type
+ if not wt then
+ -- LEGACY: Field was called "entity_name" in previous versions
+ wt = data.entity_name
+ data.type = data.entity_name
+ data.entity_name = nil
+ end
+ if not wt or not advtrains.wagon_prototypes[wt] then
+ atwarn("Unable to load wagon type",wt,", using placeholder")
+ wt="advtrains:wagon_placeholder"
+ end
+ return wt, advtrains.wagon_prototypes[wt]
+end
+
+function advtrains.standard_inventory_formspec(self, pname, invname)
+ --[[minetest.chat_send_player(pname, string.format("self=%s, pname=%s, invname=%s", self, pname, invname))
+ for k,v in pairs(self) do
+ minetest.chat_send_player(pname, string.format("%s=%s", k,v))
+ end
+ minetest.chat_send_player(pname, string.format("***%s***", self.object:get_pos()))--]]
+ local data = advtrains.wagons[self.id]
+ local r = "size[8,11]"..
+ "list["..invname..";box;0,0;8,3;]"
+ if data.owner==pname then
+ r = r .. "button_exit[0,9;4,1;prop;"..attrans("Wagon properties").."]"
+ end
+ r = r .. "list[current_player;main;0,5;8,4;]"..
+ "listring[]"
+ return r
+end
+
+function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreative)
+ local sysname = sysname_p
+ if not string.match(sysname, ":") then
+ sysname = "advtrains:"..sysname_p
+ end
+ setmetatable(prototype, {__index=wagon})
+ minetest.register_entity(":"..sysname,prototype)
+ advtrains.wagon_prototypes[sysname] = prototype
+
+ minetest.register_craftitem(":"..sysname, {
+ description = desc,
+ inventory_image = inv_img,
+ wield_image = inv_img,
+ stack_max = 1,
+
+ groups = { not_in_creative_inventory = nincreative and 1 or 0},
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return advtrains.pcall(function()
+ if not pointed_thing.type == "node" then
+ return
+ end
+ local pname = placer:get_player_name()
+
+ local node=minetest.get_node_or_nil(pointed_thing.under)
+ if not node then atprint("[advtrains]Ignore at placer position") return itemstack end
+ local nodename=node.name
+ if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on)) then
+ atprint("no track here, not placing.")
+ return itemstack
+ end
+ if not minetest.check_player_privs(placer, {train_operator = true }) then
+ minetest.chat_send_player(pname, "You don't have the train_operator privilege.")
+ return itemstack
+ end
+ if not minetest.check_player_privs(placer, {train_admin = true }) and minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
+ return itemstack
+ end
+ local tconns=advtrains.get_track_connections(node.name, node.param2)
+ local yaw = placer:get_look_horizontal()
+ local plconnid = advtrains.yawToClosestConn(yaw, tconns)
+
+ local prevpos = advtrains.get_adjacent_rail(pointed_thing.under, tconns, plconnid, prototype.drives_on)
+ if not prevpos then
+ minetest.chat_send_player(pname, "The track you are trying to place the wagon on is not long enough!")
+ return
+ end
+
+ local wid = advtrains.create_wagon(sysname, pname)
+
+ local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, {wid})
+
+ if not advtrains.is_creative(pname) then
+ itemstack:take_item()
+ end
+ return itemstack
+
+ end)
+ end,
+ })
+end
+
+-- Placeholder wagon. Will be spawned whenever a mod is missing
+advtrains.register_wagon("advtrains:wagon_placeholder", {
+ visual="sprite",
+ textures = {"advtrains_wagon_placeholder.png"},
+ collisionbox = {-0.3,-0.3,-0.3, 0.3,0.3,0.3},
+ visual_size = {x=0.7, y=0.7},
+ initial_sprite_basepos = {x=0, y=0},
+ drives_on = advtrains.all_tracktypes,
+ max_speed = 5,
+ seats = {
+ },
+ seat_groups = {
+ },
+ assign_to_seat_group = {},
+ wagon_span=1,
+ drops={},
+}, "Wagon placeholder", "advtrains_wagon_placeholder.png", true)
+
diff --git a/readme.txt b/readme.txt
index 3a540f9..05a5d5e 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,47 +1,47 @@
-
-## ADVTRAINS ## realistic trains in Minetest!
-by orwell96 and contributors(see below)
-
-For up-to-date information, visit https://advtrains.de/
-
-License of code: GNU AGPL version 3
-License of media: CC-BY-SA 3.0
-
-(up to commit 1bb1d8, the license has been LGPL 2.1)
-
-Contributions:
-
-Coding:
-Various features and bugfixes have been contributed by:
-- gpcf
-- Blockhead
-- ywang
-Small code contributions:
-- h-v-smacker
-- NaruTrey
-
-Assets:
-Gravel Texture : from Minetest Game
-Initial rail model/texture : DS-minetest
-Models for signals/bumpers : mbb
-Steam engine / wagon texture: mbb
-Detailed Steam engine : mbb / Krokoschlange(animation)
-Industrial engine/wagons : mbb
-Inventory images : mbb
-Mod Description : hajo
-Sounds:
-advtrains_crossing_bell : Codesound
-advtrains_japan_horn : Codesound
-advtrains_steam_whistle : googol
-advtrains_subway_horn : https://freesound.org/people/Mullumbimby/sounds/385283/
-advtrains_subway_* : Gabriel (gbl08ma)
-45 degree platforms design : Och_Noe
-
-Testers:
-gpcf (Linuxworks server)
-imcasper (tss Branch)
-
-
-If I forgot someone please punish me for that. Also see the Git commit log.
-
-You can see this mod in action on Linuxworks Next Generation server.
+
+## ADVTRAINS ## realistic trains in Minetest!
+by orwell96 and contributors(see below)
+
+For up-to-date information, visit https://advtrains.de/
+
+License of code: GNU AGPL version 3
+License of media: CC-BY-SA 3.0
+
+(up to commit 1bb1d8, the license has been LGPL 2.1)
+
+Contributions:
+
+Coding:
+Various features and bugfixes have been contributed by:
+- gpcf
+- Blockhead
+- ywang
+Small code contributions:
+- h-v-smacker
+- NaruTrey
+
+Assets:
+Gravel Texture : from Minetest Game
+Initial rail model/texture : DS-minetest
+Models for signals/bumpers : mbb
+Steam engine / wagon texture: mbb
+Detailed Steam engine : mbb / Krokoschlange(animation)
+Industrial engine/wagons : mbb
+Inventory images : mbb
+Mod Description : hajo
+Sounds:
+advtrains_crossing_bell : Codesound
+advtrains_japan_horn : Codesound
+advtrains_steam_whistle : googol
+advtrains_subway_horn : https://freesound.org/people/Mullumbimby/sounds/385283/
+advtrains_subway_* : Gabriel (gbl08ma)
+45 degree platforms design : Och_Noe
+
+Testers:
+gpcf (Linuxworks server)
+imcasper (tss Branch)
+
+
+If I forgot someone please punish me for that. Also see the Git commit log.
+
+You can see this mod in action on Linuxworks Next Generation server.