From 2ff8f5fd4649cff554cc6afab3d92ac7efedb809 Mon Sep 17 00:00:00 2001 From: orwell96 Date: Wed, 19 Dec 2018 17:16:13 +0100 Subject: Initial version --- helpers.lua | 431 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.lua | 230 ++++++++++++++++++++++++++++++ nodedb.lua | 163 ++++++++++++++++++++++ serialize.lua | 221 +++++++++++++++++++++++++++++ track_defs.lua | 152 ++++++++++++++++++++ tracks.lua | 265 +++++++++++++++++++++++++++++++++++ vector.lua | 145 +++++++++++++++++++ 7 files changed, 1607 insertions(+) create mode 100644 helpers.lua create mode 100644 main.lua create mode 100644 nodedb.lua create mode 100755 serialize.lua create mode 100644 track_defs.lua create mode 100644 tracks.lua create mode 100755 vector.lua diff --git a/helpers.lua b/helpers.lua new file mode 100644 index 0000000..511d32e --- /dev/null +++ b/helpers.lua @@ -0,0 +1,431 @@ +--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.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 + + +-- returns: , , , +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 + +local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}} +function advtrains.get_matching_conn(conn, nconns) + return connlku[nconns][conn] +end + +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) + 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/main.lua b/main.lua new file mode 100644 index 0000000..460c29d --- /dev/null +++ b/main.lua @@ -0,0 +1,230 @@ +-- advtrains track map generator +-- Usage:... + +-- Viewport maximum coordinate in all directions +local maxc = 5000 + +-- embed an image called "world.png" +local wimg = false +-- image file resolution (not world resolution!) +local wimresx = 3000 +local wimresy = 3000 +-- one pixel is ... nodes +local wimscale = 4 + + + +--Constant for maximum connection value/division of the circle +AT_CMAX = 16 + +advtrains = {} +minetest = {} +core = minetest + +--table for track nodes/connections +trackconns = {} + +-- math library seems to be missing this function +math.hypot = function(a,b) return math.sqrt(a*a + b*b) end + +-- need to declare this for trackdefs +function attrans(str) return str end + +-- pos to string +local function pts(pos) + return pos.x .. "," .. pos.y .. "," .. pos.z +end + +--Advtrains dump (special treatment of pos and sigd) +function atdump(t, intend) + local str + if type(t)=="table" then + if t.x and t.y and t.z then + str=minetest.pos_to_string(t) + elseif t.p and t.s then -- interlocking sigd + str="S["..minetest.pos_to_string(t.p).."/"..t.s.."]" + else + str="{" + local intd = (intend or "") .. " " + for k,v in pairs(t) do + if type(k)~="string" or not string.match(k, "^path[_]?") then + -- do not print anything path-related + str = str .. "\n" .. intd .. atdump(k, intd) .. " = " ..atdump(v, intd) + end + end + str = str .. "\n" .. (intend or "") .. "}" + end + elseif type(t)=="boolean" then + if t then + str="true" + else + str="false" + end + elseif type(t)=="function" then + str="" + elseif type(t)=="userdata" then + str="" + else + str=""..t + end + return str +end + +dofile("vector.lua") +dofile("serialize.lua") +dofile("helpers.lua") +dofile("tracks.lua") +dofile("track_defs.lua") + +dofile("nodedb.lua") + + +-- Load saves +local file, err = io.open("advtrains", "r") +local tbl = minetest.deserialize(file:read("*a")) +if type(tbl) ~= "table" then + error("not a table") +end +if tbl.version then + + advtrains.ndb.load_data(tbl.ndb) + +else + error("Incompatible save format!") +end +file:close() + +-- open svg file + +local svgfile = io.open("out.svg", "w") + +svgfile:write([[ + + +') + + +svgfile:write([[ + +]]) + +if wimg then + local wimx = -(wimresx*wimscale/2) + local wimy = -(wimresy*wimscale/2) + local wimw = wimresx*wimscale + local wimh = wimresy*wimscale + + svgfile:write('') +end + +local function writec(text) + print(text) + svgfile:write("\n") +end + + +-- everything set up. Start generating an SVG +-- All nodes that have been fit into a polyline are removed from the NDB, in order to not draw them again. + +-- "Restart points" for the breadth-first traverser (set when more than 2 conns present) +-- {pos = , connid = , conn = } +-- Note that the node at "pos" is already deleted from the NDB at the time of recall, therefore "conn" is specified +local bfs_rsp = {} + + +-- Points of the current polyline. Inserted as xyz vectors, maybe we'll use the y value one day +local current_polyline = {} + +-- Traverser function from interlocking, highly modified +local function gen_rsp_polyline(rsp) + + -- trick a bit + local pos, connid, conns = rsp.pos, 1, {rsp.conn} + current_polyline[#current_polyline+1] = pos + + while true do + local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid) + if not adj_pos then + return + end + -- continue traversing + local conn_mainbranch + for nconnid, nconn in ipairs(next_conns) do + if adj_connid ~= nconnid then + if not conn_mainbranch then + --use the first one found to continue + conn_mainbranch = nconnid + --writec(nconnid.." nconn mainbranch") + else + -- insert bfs reminders for other conns + table.insert(bfs_rsp, {pos = adj_pos, connid = nconnid, conn = nconn}) + --writec(nconnid.." nconn bfs") + end + end + end + + -- save in polyline and delete from ndb + --writec("Saved pos: "..pts(adj_pos).." mainbranch cont "..conn_mainbranch.." nextconns "..atdump(next_conns)) + current_polyline[#current_polyline+1] = adj_pos + advtrains.ndb.clear(adj_pos) + + pos, connid, conns = adj_pos, conn_mainbranch, next_conns + + end +end + + +local function polyline_write(pl) + local str = {'\n') + + svgfile:write(table.concat(str)) +end + + +-- while there are entries in the nodedb +-- 1. find a starting point + local stpos, conns = advtrains.ndb.mapper_find_starting_point() +while stpos do + + writec("Restart at position "..pts(stpos)) + for connid, conn in ipairs(conns) do + table.insert(bfs_rsp, {pos = stpos, connid = connid, conn = conn}) + end + advtrains.ndb.clear(stpos) + + -- 2. while there are BFS entries + while #bfs_rsp > 0 do + -- make polylines + local current_rsp = bfs_rsp[#bfs_rsp] + bfs_rsp[#bfs_rsp] = nil + --print("Starting polyline at "..pts(current_rsp.pos).."/"..current_rsp.connid) + + + current_polyline = {} + + gen_rsp_polyline(current_rsp) + + polyline_write(current_polyline) + end + stpos, conns = advtrains.ndb.mapper_find_starting_point() +end + +svgfile:write("") +svgfile:close() + diff --git a/nodedb.lua b/nodedb.lua new file mode 100644 index 0000000..f79dbbd --- /dev/null +++ b/nodedb.lua @@ -0,0 +1,163 @@ +--nodedb.lua +--database of all nodes that have 'save_in_at_nodedb' field set to true in node definition + + +--serialization format: +--(2byte z) (2byte y) (2byte x) (2byte contentid) +--contentid := (14bit nodeid, 2bit param2) + +local function int_to_bytes(i) + local x=i+32768--clip to positive integers + local cH = math.floor(x / 256) % 256; + local cL = math.floor(x ) % 256; + return(string.char(cH, cL)); +end +local function bytes_to_int(bytes) + local t={string.byte(bytes,1,-1)} + local n = + t[1] * 256 + + t[2] + return n-32768 +end +local function l2b(x) + return x%4 +end +local function u14b(x) + return math.floor(x/4) +end +local ndb={} + +--local variables for performance +local ndb_nodeids={} +local ndb_nodes={} + +local function ndbget(x,y,z) + local ny=ndb_nodes[y] + if ny then + local nx=ny[x] + if nx then + return nx[z] + end + end + return nil +end +local function ndbset(x,y,z,v) + if not ndb_nodes[y] then + ndb_nodes[y]={} + end + if not ndb_nodes[y][x] then + ndb_nodes[y][x]={} + end + ndb_nodes[y][x][z]=v +end + + +local path="advtrains_ndb2" +--load +--nodeids get loaded by advtrains init.lua and passed here +function ndb.load_data(data) + ndb_nodeids = data and data.nodeids or {} + local file, err = io.open(path, "rb") + if not file then + print("Couldn't load the node database: ", err or "Unknown Error") + else + local cnt=0 + local hst_z=file:read(2) + local hst_y=file:read(2) + local hst_x=file:read(2) + local cid=file:read(2) + while hst_z and hst_y and hst_x and cid and #hst_z==2 and #hst_y==2 and #hst_x==2 and #cid==2 do + ndbset(bytes_to_int(hst_x), bytes_to_int(hst_y), bytes_to_int(hst_z), bytes_to_int(cid)) + cnt=cnt+1 + hst_z=file:read(2) + hst_y=file:read(2) + hst_x=file:read(2) + cid=file:read(2) + end + print("nodedb: read", cnt, "nodes.") + file:close() + end +end + +--function to get node. track database is not helpful here. +function ndb.get_node_or_nil(pos) + -- FIX for bug found on linuxworks server: + -- a loaded node might get read before the LBM has updated its state, resulting in wrongly set signals and switches + -- -> Using the saved node prioritarily. + local node = ndb.get_node_raw(pos) + if node then + return node + else + -- no minetest here + return nil + end +end +function ndb.get_node(pos) + local n=ndb.get_node_or_nil(pos) + if not n then + return {name="ignore", param2=0} + end + return n +end +function ndb.get_node_raw(pos) + local cid=ndbget(pos.x, pos.y, pos.z) + if cid then + local nodeid = ndb_nodeids[u14b(cid)] + if nodeid then + return {name=nodeid, param2 = l2b(cid)} + end + end + return nil +end + +function ndb.clear(pos) + ndbset(pos.x, pos.y, pos.z, nil) +end + + +--get_node with pseudoload. now we only need track data, so we can use the trackdb as second fallback +--nothing new will be saved inside the trackdb. +--returns: +--true, conn1, conn2, rely1, rely2, railheight in case everything's right. +--false if it's not a rail or the train does not drive on this rail, but it is loaded or +--nil if the node is neither loaded nor in trackdb +--the distraction between false and nil will be needed only in special cases.(train initpos) +function advtrains.get_rail_info_at(pos) + local rdp=advtrains.round_vector_floor_y(pos) + + local node=ndb.get_node_or_nil(rdp) + if not node then return end + + local nodename=node.name + + local conns, railheight, tracktype=advtrains.get_track_connections(node.name, node.param2) + + if not conns then + return false + end + + return true, conns, railheight +end + + +-- mapper-specific + +function ndb.mapper_find_starting_point() + for y, ty in pairs(ndb_nodes) do + for x, tx in pairs(ty) do + for z, v in pairs(tx) do + local pos = {x=x, y=y, z=z} + local node_ok, conns, _ = advtrains.get_rail_info_at(pos) + if node_ok then + return pos, conns + else + -- this is a signal or something similar, ignore. + tx[z]=nil + end + end + end + end +end + + +advtrains.ndb = ndb diff --git a/serialize.lua b/serialize.lua new file mode 100755 index 0000000..692ddd5 --- /dev/null +++ b/serialize.lua @@ -0,0 +1,221 @@ +--- Lua module to serialize values as Lua code. +-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua +-- License: MIT +-- @copyright 2006-2997 Fabien Fleutot +-- @author Fabien Fleutot +-- @author ShadowNinja +-------------------------------------------------------------------------------- + +--- Serialize an object into a source code string. This string, when passed as +-- an argument to deserialize(), returns an object structurally identical to +-- the original one. The following are currently supported: +-- * Booleans, numbers, strings, and nil. +-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode! +-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved. +-- This works in two phases: +-- 1. Recursively find and record multiple references and recursion. +-- 2. Recursively dump the value into a string. +-- @param x Value to serialize (nil is allowed). +-- @return load()able string containing the value. +function core.serialize(x) + local local_index = 1 -- Top index of the "_" local table in the dump + -- table->nil/1/2 set of tables seen. + -- nil = not seen, 1 = seen once, 2 = seen multiple times. + local seen = {} + + -- nest_points are places where a table appears within itself, directly + -- or not. For instance, all of these chunks create nest points in + -- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x", + -- "x = {}; x[1] = {y = {x}}". + -- To handle those, two tables are used by mark_nest_point: + -- * nested - Transient set of tables being currently traversed. + -- Used for detecting nested tables. + -- * nest_points - parent->{key=value, ...} table cantaining the nested + -- keys and values in the parent. They're all dumped after all the + -- other table operations have been performed. + -- + -- mark_nest_point(p, k, v) fills nest_points with information required + -- to remember that key/value (k, v) creates a nest point in table + -- parent. It also marks "parent" and the nested item(s) as occuring + -- multiple times, since several references to it will be required in + -- order to patch the nest points. + local nest_points = {} + local nested = {} + local function mark_nest_point(parent, k, v) + local nk, nv = nested[k], nested[v] + local np = nest_points[parent] + if not np then + np = {} + nest_points[parent] = np + end + np[k] = v + seen[parent] = 2 + if nk then seen[k] = 2 end + if nv then seen[v] = 2 end + end + + -- First phase, list the tables and functions which appear more than + -- once in x. + local function mark_multiple_occurences(x) + local tp = type(x) + if tp ~= "table" and tp ~= "function" then + -- No identity (comparison is done by value, not by instance) + return + end + if seen[x] == 1 then + seen[x] = 2 + elseif seen[x] ~= 2 then + seen[x] = 1 + end + + if tp == "table" then + nested[x] = true + for k, v in pairs(x) do + if nested[k] or nested[v] then + mark_nest_point(x, k, v) + else + mark_multiple_occurences(k) + mark_multiple_occurences(v) + end + end + nested[x] = nil + end + end + + local dumped = {} -- object->varname set + local local_defs = {} -- Dumped local definitions as source code lines + + -- Mutually recursive local functions: + local dump_val, dump_or_ref_val + + -- If x occurs multiple times, dump the local variable rather than + -- the value. If it's the first time it's dumped, also dump the + -- content in local_defs. + function dump_or_ref_val(x) + if seen[x] ~= 2 then + return dump_val(x) + end + local var = dumped[x] + if var then -- Already referenced + return var + end + -- First occurence, create and register reference + local val = dump_val(x) + local i = local_index + local_index = local_index + 1 + var = "_["..i.."]" + local_defs[#local_defs + 1] = var.." = "..val + dumped[x] = var + return var + end + + -- Second phase. Dump the object; subparts occuring multiple times + -- are dumped in local variables which can be referenced multiple + -- times. Care is taken to dump local vars in a sensible order. + function dump_val(x) + local tp = type(x) + if x == nil then return "nil" + elseif tp == "string" then return string.format("%q", x) + elseif tp == "boolean" then return x and "true" or "false" + elseif tp == "function" then + return string.format("loadstring(%q)", string.dump(x)) + elseif tp == "number" then + -- Serialize integers with string.format to prevent + -- scientific notation, which doesn't preserve + -- precision and breaks things like node position + -- hashes. Serialize floats normally. + if math.floor(x) == x then + return string.format("%d", x) + else + return tostring(x) + end + elseif tp == "table" then + local vals = {} + local idx_dumped = {} + local np = nest_points[x] + for i, v in ipairs(x) do + if not np or not np[i] then + vals[#vals + 1] = dump_or_ref_val(v) + end + idx_dumped[i] = true + end + for k, v in pairs(x) do + if (not np or not np[k]) and + not idx_dumped[k] then + vals[#vals + 1] = "["..dump_or_ref_val(k).."] = " + ..dump_or_ref_val(v) + end + end + return "{"..table.concat(vals, ", ").."}" + else + error("Can't serialize data of type "..tp) + end + end + + local function dump_nest_points() + for parent, vals in pairs(nest_points) do + for k, v in pairs(vals) do + local_defs[#local_defs + 1] = dump_or_ref_val(parent) + .."["..dump_or_ref_val(k).."] = " + ..dump_or_ref_val(v) + end + end + end + + mark_multiple_occurences(x) + local top_level = dump_or_ref_val(x) + dump_nest_points() + + if next(local_defs) then + return "local _ = {}\n" + ..table.concat(local_defs, "\n") + .."\nreturn "..top_level + else + return "return "..top_level + end +end + +-- Deserialization + +local env = { + loadstring = loadstring, +} + +local safe_env = { + loadstring = function() end, +} + +function core.deserialize(str, safe) + if type(str) ~= "string" then + return nil, "Cannot deserialize type '"..type(str) + .."'. Argument must be a string." + end + if str:byte(1) == 0x1B then + return nil, "Bytecode prohibited" + end + local f, err = loadstring(str) + if not f then return nil, err end + setfenv(f, safe and safe_env or env) + + local good, data = pcall(f) + if good then + return data + else + return nil, data + end +end + + +-- Unit tests +local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}} +local test_out = core.deserialize(core.serialize(test_in)) + +assert(test_in.cat.sound == test_out.cat.sound) +assert(test_in.cat.speed == test_out.cat.speed) +assert(test_in.dog.sound == test_out.dog.sound) + +test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"} +test_out = core.deserialize(core.serialize(test_in)) +assert(test_in.escape_chars == test_out.escape_chars) +assert(test_in.non_european == test_out.non_european) + diff --git a/track_defs.lua b/track_defs.lua new file mode 100644 index 0000000..aac2ebf --- /dev/null +++ b/track_defs.lua @@ -0,0 +1,152 @@ +--== Insert track defs here! ==-- +-- Default tracks for advtrains +-- (c) orwell96 and contributors + +--flat +advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack", + texture_prefix="advtrains_dtrack", + models_prefix="advtrains_dtrack", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_shared.png", + description=attrans("Track"), + formats={}, +}, advtrains.ap.t_30deg_flat) +--slopes +advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack", + texture_prefix="advtrains_dtrack", + models_prefix="advtrains_dtrack", + models_suffix=".obj", + shared_texture="advtrains_dtrack_shared.png", + second_texture="default_gravel.png", + description=attrans("Track"), + formats={vst1={true, false, true}, vst2={true, false, true}, vst31={true}, vst32={true}, vst33={true}}, +}, advtrains.ap.t_30deg_slope) + +--bumpers +advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack_bumper", + texture_prefix="advtrains_dtrack_bumper", + models_prefix="advtrains_dtrack_bumper", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_rail.png", + --bumpers still use the old texture until the models are redone. + description=attrans("Bumper"), + formats={}, +}, advtrains.ap.t_30deg_straightonly) + + + +-- atc track +advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack_atc", + texture_prefix="advtrains_dtrack_atc", + models_prefix="advtrains_dtrack", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_shared_atc.png", + description=attrans("ATC controller"), + formats={}, + get_additional_definiton = advtrains.atc_function +}, advtrains.trackpresets.t_30deg_straightonly) + +advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack_unload", + texture_prefix="advtrains_dtrack_unload", + models_prefix="advtrains_dtrack", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_shared_unload.png", + description=attrans("Unloading Track"), + formats={}, + get_additional_definiton = function(def, preset, suffix, rotation) + return { + after_dig_node=function(pos) + advtrains.invalidate_all_paths() + advtrains.ndb.clear(pos) + end, + advtrains = { + on_train_enter = function(pos, train_id) + train_load(pos, train_id, true) + end, + }, + } + end + }, advtrains.trackpresets.t_30deg_straightonly) +advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack_load", + texture_prefix="advtrains_dtrack_load", + models_prefix="advtrains_dtrack", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_shared_load.png", + description=attrans("Loading Track"), + formats={}, + get_additional_definiton = function(def, preset, suffix, rotation) + return { + after_dig_node=function(pos) + advtrains.invalidate_all_paths() + advtrains.ndb.clear(pos) + end, + + advtrains = { + on_train_enter = function(pos, train_id) + train_load(pos, train_id, false) + end, + }, + } + end + }, advtrains.trackpresets.t_30deg_straightonly) + + + advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack_detector_off", + texture_prefix="advtrains_dtrack_detector", + models_prefix="advtrains_dtrack", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_shared_detector_off.png", + description=attrans("Detector Rail"), + formats={}, + get_additional_definiton = function(def, preset, suffix, rotation) + return { + mesecons = { + receptor = { + state = mesecon.state.off, + rules = advtrains.meseconrules + } + }, + advtrains = { + on_train_enter=function(pos, train_id) + advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_on".."_"..suffix..rotation, param2=advtrains.ndb.get_node(pos).param2}) + mesecon.receptor_on(pos, advtrains.meseconrules) + end + } + } + end + }, advtrains.ap.t_30deg_straightonly) + advtrains.register_tracks("default", { + nodename_prefix="advtrains:dtrack_detector_on", + texture_prefix="advtrains_dtrack", + models_prefix="advtrains_dtrack", + models_suffix=".b3d", + shared_texture="advtrains_dtrack_shared_detector_on.png", + description="Detector(on)(you hacker you)", + formats={}, + get_additional_definiton = function(def, preset, suffix, rotation) + return { + mesecons = { + receptor = { + state = mesecon.state.on, + rules = advtrains.meseconrules + } + }, + advtrains = { + on_train_leave=function(pos, train_id) + advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_off".."_"..suffix..rotation, param2=advtrains.ndb.get_node(pos).param2}) + mesecon.receptor_off(pos, advtrains.meseconrules) + end + } + } + end + }, advtrains.ap.t_30deg_straightonly_noplacer) + + +--== END insert track defs ==-- diff --git a/tracks.lua b/tracks.lua new file mode 100644 index 0000000..b04c585 --- /dev/null +++ b/tracks.lua @@ -0,0 +1,265 @@ +--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 = "swlcr", + switchmc = "on", + switchst = "st", + }, + swlcr={ + conns = conns3(0,7,8), + desc = "left switch (curve)", + trackworker = "swrcr", + switchalt = "swlst", + switchmc = "off", + switchst = "cr", + }, + swrst={ + conns = conns3(0,8,9), + desc = "right switch (straight)", + trackworker = "st", + switchalt = "swrcr", + switchmc = "on", + switchst = "st", + }, + swrcr={ + conns = conns3(0,9,8), + desc = "right switch (curve)", + trackworker = "st", + switchalt = "swrst", + switchmc = "off", + switchst = "cr", + }, + }, + regtp=true, + tpdefault="st", + trackworker={ + ["swrcr"]="st", + ["swrst"]="st", + ["cr"]="swlst", + ["swlcr"]="swrcr", + ["swlst"]="swrst", + }, + 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 = "swlcr", + switchmc = "on", + switchst = "st", + }, + swlcr={ + conns = conns3(0,6,8), + desc = "left switch (curve)", + trackworker = "swrcr", + switchalt = "swlst", + switchmc = "off", + switchst = "cr", + }, + swrst={ + conns = conns3(0,8,10), + desc = "right switch (straight)", + trackworker = "st", + switchalt = "swrcr", + switchmc = "on", + switchst = "st", + }, + swrcr={ + conns = conns3(0,10,8), + desc = "right switch (curve)", + trackworker = "st", + switchalt = "swrst", + switchmc = "off", + switchst = "cr", + }, + }, + regtp=true, + tpdefault="st", + trackworker={ + ["swrcr"]="st", + ["swrst"]="st", + ["cr"]="swlst", + ["swlcr"]="swrcr", + ["swlst"]="swrst", + }, + rotation={"", "_30", "_45", "_60"}, +} +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) + 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 + + --connections + local at_conns = advtrains.rotate_conn_by(var.conns, (rotid-1)*preset.regstep) + + trackconns[def.nodename_prefix.."_"..suffix..rotation] = at_conns + + end + end + end +end + + +function advtrains.get_track_connections(name, param2) + + if not trackconns[name] then return end + + return advtrains.rotate_conn_by(trackconns[name], param2*AT_CMAX/4), nil, nil +end diff --git a/vector.lua b/vector.lua new file mode 100755 index 0000000..0549f9a --- /dev/null +++ b/vector.lua @@ -0,0 +1,145 @@ + +vector = {} + +function vector.new(a, b, c) + if type(a) == "table" then + assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()") + return {x=a.x, y=a.y, z=a.z} + elseif a then + assert(b and c, "Invalid arguments for vector.new()") + return {x=a, y=b, z=c} + end + return {x=0, y=0, z=0} +end + +function vector.equals(a, b) + return a.x == b.x and + a.y == b.y and + a.z == b.z +end + +function vector.length(v) + return math.hypot(v.x, math.hypot(v.y, v.z)) +end + +function vector.normalize(v) + local len = vector.length(v) + if len == 0 then + return {x=0, y=0, z=0} + else + return vector.divide(v, len) + end +end + +function vector.floor(v) + return { + x = math.floor(v.x), + y = math.floor(v.y), + z = math.floor(v.z) + } +end + +function vector.round(v) + return { + x = math.floor(v.x + 0.5), + y = math.floor(v.y + 0.5), + z = math.floor(v.z + 0.5) + } +end + +function vector.apply(v, func) + return { + x = func(v.x), + y = func(v.y), + z = func(v.z) + } +end + +function vector.distance(a, b) + local x = a.x - b.x + local y = a.y - b.y + local z = a.z - b.z + return math.hypot(x, math.hypot(y, z)) +end + +function vector.direction(pos1, pos2) + local x_raw = pos2.x - pos1.x + local y_raw = pos2.y - pos1.y + local z_raw = pos2.z - pos1.z + local x_abs = math.abs(x_raw) + local y_abs = math.abs(y_raw) + local z_abs = math.abs(z_raw) + if x_abs >= y_abs and + x_abs >= z_abs then + y_raw = y_raw * (1 / x_abs) + z_raw = z_raw * (1 / x_abs) + x_raw = x_raw / x_abs + end + if y_abs >= x_abs and + y_abs >= z_abs then + x_raw = x_raw * (1 / y_abs) + z_raw = z_raw * (1 / y_abs) + y_raw = y_raw / y_abs + end + if z_abs >= y_abs and + z_abs >= x_abs then + x_raw = x_raw * (1 / z_abs) + y_raw = y_raw * (1 / z_abs) + z_raw = z_raw / z_abs + end + return {x=x_raw, y=y_raw, z=z_raw} +end + + +function vector.add(a, b) + if type(b) == "table" then + return {x = a.x + b.x, + y = a.y + b.y, + z = a.z + b.z} + else + return {x = a.x + b, + y = a.y + b, + z = a.z + b} + end +end + +function vector.subtract(a, b) + if type(b) == "table" then + return {x = a.x - b.x, + y = a.y - b.y, + z = a.z - b.z} + else + return {x = a.x - b, + y = a.y - b, + z = a.z - b} + end +end + +function vector.multiply(a, b) + if type(b) == "table" then + return {x = a.x * b.x, + y = a.y * b.y, + z = a.z * b.z} + else + return {x = a.x * b, + y = a.y * b, + z = a.z * b} + end +end + +function vector.divide(a, b) + if type(b) == "table" then + return {x = a.x / b.x, + y = a.y / b.y, + z = a.z / b.z} + else + return {x = a.x / b, + y = a.y / b, + z = a.z / b} + end +end + +function vector.sort(a, b) + return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, + {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} +end -- cgit v1.2.3