From dcf5b8670e19ad7603a0e305ec8515653555084f Mon Sep 17 00:00:00 2001 From: Gabriel Pérez-Cerezo Date: Sun, 11 Oct 2020 12:36:06 +0200 Subject: Remove last files with CR-LF line endings. --- advtrains/helpers.lua | 894 +++++++-------- advtrains/tracks.lua | 1478 ++++++++++++------------- advtrains/wagons.lua | 2874 ++++++++++++++++++++++++------------------------- readme.txt | 94 +- 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)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 - 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: , , , --- 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)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 + 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: , , , +-- 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=, y=} -The table "at_conns" consists of {, ...} -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=, y=} +The table "at_conns" consists of {, ...} +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. -- cgit v1.2.3