Gabriel Pérez-Cerezo 2017-02-05 16:32:53 +01:00
commit 746e328b7b
No known key found for this signature in database
GPG Key ID: 90422B01A46D0B3E
43 changed files with 1578 additions and 290 deletions

Binary file not shown.

View File

@ -134,7 +134,7 @@ minetest.register_node(nodename, {
^- the height value of this rail that is saved in the path. usually the median of rely1 and rely2.
can_dig=function(pos)
return not advtrains.is_train_at_pos(pos)
return not advtrains.get_train_at_pos(pos)
end,
after_dig_node=function(pos)
advtrains.invalidate_all_paths()

View File

@ -5,19 +5,20 @@ local atc={}
-- ATC persistence table. advtrains.atc is created by init.lua when it loads the save file.
atc.controllers = {}
function atc.load_data(data)
atc.controllers = data and data.controllers or {}
local temp = data and data.controllers or {}
--transcode atc controller data to node hashes: table access times for numbers are far less than for strings
for pts, data in pairs(temp) do
if type(pts)=="number" then
pts=minetest.pos_to_string(minetest.get_position_from_hash(pts))
end
atc.controllers[pts] = data
end
end
function atc.save_data()
return {controllers = atc.controllers}
end
--contents: {command="...", arrowconn=0-15 where arrow points}
--call from advtrains.detector subprogram
function atc.trigger_controller_train_enter(pos, train_id)
atc.send_command(pos)
end
--general
function atc.send_command(pos)
@ -40,10 +41,22 @@ function atc.send_command(pos)
)
advtrains.trains[train_id].atc_command=atc.controllers[pts].command
atprint("Sending ATC Command: "..atc.controllers[pts].command)
return true
end
end
atwarn("ATC rail at", pos, ": Rail not on train's path! Can't determine arrow direction. Assuming +!")
advtrains.trains[train_id].atc_arrow=true
advtrains.trains[train_id].atc_command=atc.controllers[pts].command
atprint("Sending ATC Command: "..atc.controllers[pts].command)
else
atwarn("ATC rail at", pos, ": Sending command failed: The train",train_id,"does not exist. This seems to be a bug.")
end
else
atwarn("ATC rail at", pos, ": Sending command failed: There's no train at this position. This seems to be a bug.")
end
else
atwarn("ATC rail at", pos, ": Sending command failed: Entry for controller not found.")
atwarn("ATC rail at", pos, ": Please visit controller and click 'Save'")
end
return false
end
@ -121,6 +134,11 @@ advtrains.register_tracks("default", {
atc.send_command(pos)
end
end,
advtrains = {
on_train_enter = function(pos, train_id)
atc.send_command(pos)
end,
},
}
end
}, advtrains.trackpresets.t_30deg_straightonly)
@ -176,7 +194,7 @@ local matchptn={
train.movedir=train.movedir*-1
train.atc_arrow = not train.atc_arrow
else
minetest.chat_send_all(attrans("ATC Reverse command warning: didn't reverse train, train moving!"))
atwarn(sid(id), attrans("ATC Reverse command warning: didn't reverse train, train moving!"))
end
return 1
end,
@ -235,7 +253,7 @@ function atc.execute_atc_command(id, train)
local nest, pos, elsepos=0, 1
while nest>=0 do
if pos>#rest then
minetest.chat_send_all(attrans("ATC command syntax error: I statement not closed: @1",command))
atwarn(sid(id), attrans("ATC command syntax error: I statement not closed: @1",command))
atc.train_reset_command(id)
return
end
@ -278,7 +296,7 @@ function atc.execute_atc_command(id, train)
end
end
end
minetest.chat_send_all(attrans("ATC command parse error: Unknown command: @1", command))
atwarn(sid(id), attrans("ATC command parse error: Unknown command: @1", command))
atc.train_reset_command(id)
end

View File

@ -0,0 +1,23 @@
core.register_craftitem("advtrains:boiler", {
description = "Boiler",
inventory_image = "advtrains_boiler.png",
})
core.register_craftitem("advtrains:driver_cab", {
description = "driver's cab",
inventory_image = "advtrains_driver_cab.png",
})
core.register_craftitem("advtrains:wheel", {
description = "Wheel",
inventory_image = "advtrains_wheel.png",
})
core.register_craftitem("advtrains:chimney", {
description = "Chimney",
inventory_image = "advtrains_chimney.png",
})

View File

@ -65,6 +65,45 @@ minetest.register_craft({
},
})
--boiler
minetest.register_craft({
output = 'advtrains:boiler',
recipe = {
{'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
{'doors:trapdoor_steel', '', 'default:steel_ingot'},
{'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
},
})
--drivers'cab
minetest.register_craft({
output = 'advtrains:driver_cab',
recipe = {
{'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
{'', '', 'default:glass'},
{'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
},
})
--drivers'cab
minetest.register_craft({
output = 'advtrains:wheel',
recipe = {
{'', 'default:steel_ingot', ''},
{'default:steel_ingot', 'group:stick', 'default:steel_ingot'},
{'', 'default:steel_ingot', ''},
},
})
--chimney
minetest.register_craft({
output = 'advtrains:chimney',
recipe = {
{'', 'default:steel_ingot', ''},
{'', 'default:steel_ingot', 'default:torch'},
{'', 'default:steel_ingot', ''},
},
})
--misc_nodes

View File

@ -11,7 +11,7 @@ advtrains = {trains={}, wagon_save={}}
advtrains.modpath = minetest.get_modpath("advtrains")
local function print_concat_table(a)
function advtrains.print_concat_table(a)
local str=""
local stra=""
for i=1,50 do
@ -43,7 +43,16 @@ local function print_concat_table(a)
end
atprint=function() end
if minetest.setting_getbool("advtrains_debug") then
atprint=function(t, ...) minetest.log("action", "[advtrains]"..print_concat_table({t, ...})) minetest.chat_send_all("[advtrains]"..print_concat_table({t, ...})) end
atprint=function(t, ...)
local text=advtrains.print_concat_table({t, ...})
minetest.log("action", "[advtrains]"..text)
minetest.chat_send_all("[advtrains]"..text)
end
end
atwarn=function(t, ...)
local text=advtrains.print_concat_table({t, ...})
minetest.log("warning", "[advtrains]"..text)
minetest.chat_send_all("[advtrains] -!- "..text)
end
sid=function(id) return string.sub(id, -4) end
@ -80,6 +89,8 @@ dofile(advtrains.modpath.."/damage.lua")
dofile(advtrains.modpath.."/signals.lua")
dofile(advtrains.modpath.."/misc_nodes.lua")
dofile(advtrains.modpath.."/crafting.lua")
dofile(advtrains.modpath.."/craft_items.lua")
--load/save
@ -96,7 +107,6 @@ else
advtrains.wagon_save = tbl.wagon_save
advtrains.ndb.load_data(tbl.ndb)
advtrains.atc.load_data(tbl.atc)
--advtrains.latc.load_data(tbl.latc)
else
--oh no, its the old one...
advtrains.trains=tbl
@ -165,7 +175,6 @@ advtrains.save = function()
trains = advtrains.trains,
wagon_save = advtrains.wagon_save,
atc = advtrains.atc.save_data(),
--latc = advtrains.latc.save_data(),
ndb = advtrains.ndb.save_data(),
version = 1,
}

View File

@ -1,166 +0,0 @@
-------------
--LUA ATC controllers
local latc={}
function latc.load_data(data)
end
function latc.save_data()
return stuff
end
latc.data
latc.env_cdata
latc.init_code=""
latc.step_code=""
advtrains.fpath_latc=minetest.get_worldpath().."/advtrains_latc"
local file, err = io.open(advtrains.fpath_atc, "r")
if not file then
local er=err or "Unknown Error"
atprint("Failed loading advtrains latc save file "..er)
else
local tbl = minetest.deserialize(file:read("*a"))
if type(tbl) == "table" then
atc.controllers=tbl.controllers
end
file:close()
end
function latc.save()
local datastr = minetest.serialize({controllers = atc.controllers})
if not datastr then
minetest.log("error", " Failed to serialize latc data!")
return
end
local file, err = io.open(advtrains.fpath_atc, "w")
if err then
return err
end
file:write(datastr)
file:close()
end
--Privilege
--Only trusted players should be enabled to build stuff which can break the server.
--If I later decide to have multiple environments ('data' tables), I better store an owner for every controller for future reference.
minetest.register_privilege("advtrains_lua_atc", { description = "Player can place and modify LUA ATC components. Grant with care! Allows to execute bad LUA code.", give_to_singleplayer = false, default= false })
--Environment
--Code from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
local safe_globals = {
"assert", "error", "ipairs", "next", "pairs", "select",
"tonumber", "tostring", "type", "unpack", "_VERSION"
}
local function safe_print(param)
print(dump(param))
end
local function safe_date()
return(os.date("*t",os.time()))
end
-- string.rep(str, n) with a high value for n can be used to DoS
-- the server. Therefore, limit max. length of generated string.
local function safe_string_rep(str, n)
if #str * n > mesecon.setting("luacontroller_string_rep_max", 64000) then
debug.sethook() -- Clear hook
error("string.rep: string length overflow", 2)
end
return string.rep(str, n)
end
-- string.find with a pattern can be used to DoS the server.
-- Therefore, limit string.find to patternless matching.
local function safe_string_find(...)
if (select(4, ...)) ~= true then
debug.sethook() -- Clear hook
error("string.find: 'plain' (fourth parameter) must always be true in a LuaController")
end
return string.find(...)
end
latc.static_env = {
print = safe_print,
string = {
byte = string.byte,
char = string.char,
format = string.format,
len = string.len,
lower = string.lower,
upper = string.upper,
rep = safe_string_rep,
reverse = string.reverse,
sub = string.sub,
find = safe_string_find,
},
math = {
abs = math.abs,
acos = math.acos,
asin = math.asin,
atan = math.atan,
atan2 = math.atan2,
ceil = math.ceil,
cos = math.cos,
cosh = math.cosh,
deg = math.deg,
exp = math.exp,
floor = math.floor,
fmod = math.fmod,
frexp = math.frexp,
huge = math.huge,
ldexp = math.ldexp,
log = math.log,
log10 = math.log10,
max = math.max,
min = math.min,
modf = math.modf,
pi = math.pi,
pow = math.pow,
rad = math.rad,
random = math.random,
sin = math.sin,
sinh = math.sinh,
sqrt = math.sqrt,
tan = math.tan,
tanh = math.tanh,
},
table = {
concat = table.concat,
insert = table.insert,
maxn = table.maxn,
remove = table.remove,
sort = table.sort,
},
os = {
clock = os.clock,
difftime = os.difftime,
time = os.time,
datetable = safe_date,
},
}
latc.static_env._G = env
for _, name in pairs(safe_globals) do
latc.static_env[name] = _G[name]
end
--The environment all code calls get is a proxy table with a metatable.
--When an index is read:
-- Look in static_env
-- Look in volatile_env (user_written functions and userdata)
-- Look in saved_env (everything that's not a function or userdata)
--when an index is written:
-- If in static_env, do not allow
-- if function or userdata, volatile_env
-- if table, see below
-- else, save in saved_env
advtrains.latc=latc

Binary file not shown.

Binary file not shown.

View File

@ -2,42 +2,22 @@
--database of all nodes that have 'save_in_nodedb' field set to true in node definition
--serialization format:
--(6byte poshash) (2byte contentid)
--(2byte z) (2byte y) (2byte x) (2byte contentid)
--contentid := (14bit nodeid, 2bit param2)
local function hash_to_bytes(x)
local aH = math.floor(x / 1099511627776) % 256;
local aL = math.floor(x / 4294967296) % 256;
local bH = math.floor(x / 16777216) % 256;
local bL = math.floor(x / 65536) % 256;
local cH = math.floor(x / 256) % 256;
local cL = math.floor(x ) % 256;
return(string.char(aH, aL, bH, bL, cH, cL));
end
local function cid_to_bytes(x)
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_hash(bytes)
local t={string.byte(bytes,1,-1)}
local n =
t[1] * 1099511627776 +
t[2] * 4294967296 +
t[3] * 16777216 +
t[4] * 65536 +
t[5] * 256 +
t[6]
return n
end
local function bytes_to_cid(bytes)
local function bytes_to_int(bytes)
local t={string.byte(bytes,1,-1)}
local n =
t[1] * 256 +
t[2]
return n
return n-32768
end
local function l2b(x)
return x%4
@ -51,25 +31,50 @@ local ndb={}
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
--load
--nodeids get loaded by advtrains init.lua and passed here
function ndb.load_data(data)
ndb_nodeids = data and data.nodeids or {}
end
local path=minetest.get_worldpath().."/advtrains_ndb"
local path=minetest.get_worldpath().."/advtrains_ndb2"
local file, err = io.open(path, "r")
if not file then
atprint("load ndb failed: ", err or "Unknown Error")
else
local cnt=0
local hst=file:read(6)
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 and #hst==6 and cid and #cid==2 do
ndb_nodes[bytes_to_hash(hst)]=bytes_to_cid(cid)
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=file:read(6)
hst_z=file:read(2)
hst_y=file:read(2)
hst_x=file:read(2)
cid=file:read(2)
end
atprint("nodedb: read", cnt, "nodes.")
@ -80,11 +85,17 @@ end
function ndb.save_data()
local file, err = io.open(path, "w")
if not file then
atprint("load ndb failed: ", err or "Unknown Error")
atprint("save ndb failed: ", err or "Unknown Error")
else
for hash, cid in pairs(ndb_nodes) do
file:write(hash_to_bytes(hash))
file:write(cid_to_bytes(cid))
for y, ny in pairs(ndb_nodes) do
for x, nx in pairs(ny) do
for z, cid in pairs(nx) do
file:write(int_to_bytes(z))
file:write(int_to_bytes(y))
file:write(int_to_bytes(x))
file:write(int_to_bytes(cid))
end
end
end
file:close()
end
@ -98,7 +109,7 @@ function ndb.get_node_or_nil(pos)
return node
else
--maybe we have the node in the database...
local cid=ndb_nodes[minetest.hash_node_position(pos)]
local cid=ndbget(pos.x, pos.y, pos.z)
if cid then
local nodeid = ndb_nodeids[u14b(cid)]
if nodeid then
@ -136,19 +147,16 @@ function ndb.update(pos, pnode)
nid=#ndb_nodeids+1
ndb_nodeids[nid]=node.name
end
local hash = minetest.hash_node_position(pos)
ndb_nodes[hash] = (nid * 4) + (l2b(node.param2 or 0))
ndbset(pos.x, pos.y, pos.z, (nid * 4) + (l2b(node.param2 or 0)) )
--atprint("nodedb: updating node", pos, "stored nid",nid,"assigned",ndb_nodeids[nid],"resulting cid",ndb_nodes[hash])
else
--at this position there is no longer a node that needs to be tracked.
local hash = minetest.hash_node_position(pos)
ndb_nodes[hash] = nil
ndbset(pos.x, pos.y, pos.z, nil)
end
end
function ndb.clear(pos)
local hash = minetest.hash_node_position(pos)
ndb_nodes[hash] = nil
ndbset(pos.x, pos.y, pos.z, nil)
end
@ -195,7 +203,7 @@ minetest.register_abm({
nodenames = {"group:save_in_nodedb"},
run_at_every_load = true,
action = function(pos, node)
local cid=ndb_nodes[minetest.hash_node_position(pos)]
local cid=ndbget(pos.x, pos.y, pos.z)
if cid then
--if in database, detect changes and apply.
local nodeid = ndb_nodeids[u14b(cid)]
@ -208,6 +216,10 @@ minetest.register_abm({
if (nodeid~=node.name or param2~=node.param2) then
atprint("nodedb: lbm replaced", pos, "with nodeid", nodeid, "param2", param2, "cid is", cid)
minetest.swap_node(pos, {name=nodeid, param2 = param2})
local ndef=minetest.registered_nodes[nodeid]
if ndef and ndef.on_updated_from_nodedb then
ndef.on_updated_from_nodedb(pos, node)
end
end
end
else
@ -224,9 +236,13 @@ minetest.register_on_dignode(function(pos, oldnode, digger)
ndb.clear(pos)
end)
function ndb.t()
return ndb_nodes[141061759008906]
function ndb.get_nodes()
return ndb_nodes
end
function ndb.get_nodeids()
return ndb_nodeids
end
advtrains.ndb=ndb

View File

@ -1,6 +1,34 @@
--advtrains by orwell96
--signals.lua
for r,f in pairs({on="off", off="on"}) do
--this code /should/ work but does not.
local mrules_wallsignal_l, mrules_wallsignal_r
if mesecon then
mrules_wallsignal_l = function(node, isright)
local rules = mesecon.rules.buttonlike
if node.param2 == 1 then
rules=mesecon.rotate_rules_left(rules)
elseif node.param2 == 2 then
rules=mesecon.rotate_rules_right(mesecon.rotate_rules_right(rules))
elseif node.param2 == 3 then
rules=mesecon.rotate_rules_right(rules)
end
return rules
end
mrules_wallsignal_r = function(node)
local rules = mesecon.rules.buttonlike
if node.param2 == 3 then
rules=mesecon.rotate_rules_left(rules)
elseif node.param2 == 0 then
rules=mesecon.rotate_rules_right(mesecon.rotate_rules_right(rules))
elseif node.param2 == 1 then
rules=mesecon.rotate_rules_right(rules)
end
return rules
end
end
for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do
advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "")
advtrains.trackplacer.register_tracktype("advtrains:signal", "")
@ -32,12 +60,13 @@ for r,f in pairs({on="off", off="on"}) do
save_in_nodedb=1,
},
mesecons = {effector = {
["action_"..f] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f..rotation, param2 = node.param2})
rules=advtrains.meseconrules,
["action_"..f.as] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2})
end
}},
on_rightclick=function(pos, node, clicker)
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f..rotation, param2 = node.param2})
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2})
end,
})
advtrains.trackplacer.add_worked("advtrains:retrosignal", r, rotation, nil)
@ -65,14 +94,107 @@ for r,f in pairs({on="off", off="on"}) do
light_source = 1,
sunlight_propagates=true,
mesecons = {effector = {
["action_"..f] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f..rotation, param2 = node.param2})
rules=advtrains.meseconrules,
["action_"..f.as] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2})
end
}},
luaautomation = {
getstate = f.ls,
setstate = function(pos, node, newstate)
if newstate == f.als then
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2})
end
end,
},
on_rightclick=function(pos, node, clicker)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f..rotation, param2 = node.param2})
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2})
end,
})
advtrains.trackplacer.add_worked("advtrains:signal", r, rotation, nil)
end
local crea=1
if r=="off" then crea=0 end
--tunnel signals. no rotations.
minetest.register_node("advtrains:signal_wall_l_"..r, {
drawtype = "mesh",
paramtype="light",
paramtype2="facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/4, 0, 1/2, 1/4},
},
mesh = "advtrains_signal_wall_l.b3d",
tiles = {"advtrains_signal_wall_"..r..".png"},
drop="advtrains:signal_wall_l_off",
description=attrans("Wallmounted Signal, left"),
groups = {
choppy=3,
not_blocking_trains=1,
not_in_creative_inventory=crea,
save_in_nodedb=1,
},
light_source = 1,
sunlight_propagates=true,
mesecons = {effector = {
mrules_wallsignal_l,
["action_"..f.as] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_l_"..f.as, param2 = node.param2})
end
}},
luaautomation = {
getstate = f.ls,
setstate = function(pos, node, newstate)
if newstate == f.als then
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_l_"..f.as, param2 = node.param2})
end
end,
},
on_rightclick=function(pos, node, clicker)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_l_"..f.as, param2 = node.param2})
end,
})
minetest.register_node("advtrains:signal_wall_r_"..r, {
drawtype = "mesh",
paramtype="light",
paramtype2="facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {0, -1/2, -1/4, 1/2, 1/2, 1/4},
},
mesh = "advtrains_signal_wall_r.b3d",
tiles = {"advtrains_signal_wall_"..r..".png"},
drop="advtrains:signal_wall_r_off",
description=attrans("Wallmounted Signal, right"),
groups = {
choppy=3,
not_blocking_trains=1,
not_in_creative_inventory=crea,
save_in_nodedb=1,
},
light_source = 1,
sunlight_propagates=true,
mesecons = {effector = {
rules = mrules_wallsignal_r,
["action_"..f.as] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_r_"..f.as, param2 = node.param2})
end
}},
luaautomation = {
getstate = f.ls,
setstate = function(pos, node, newstate)
if newstate == f.als then
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_r_"..f.as, param2 = node.param2})
end
end,
},
on_rightclick=function(pos, node, clicker)
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_r_"..f.as, param2 = node.param2})
end,
})
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

View File

@ -230,7 +230,7 @@ minetest.register_craftitem("advtrains:trackworker",{
local node=minetest.get_node(pos)
--if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.is_train_at_pos(pos) then return end
if advtrains.get_train_at_pos(pos) then return end
local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
--atprint(node.name.."\npattern recognizes:"..nodeprefix.." / "..railtype.." / "..rotation)
@ -272,7 +272,7 @@ minetest.register_craftitem("advtrains:trackworker",{
end
--if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.is_train_at_pos(pos) then return end
if advtrains.get_train_at_pos(pos) then return end
local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
--atprint(node.name.."\npattern recognizes:"..nodeprefix.." / "..railtype.." / "..rotation)
if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twcycle[suffix] then

View File

@ -77,6 +77,12 @@ ap.t_30deg={
swrst="on",
swrcr="off",
},
switchst={
swlst="st",
swlcr="cr",
swrst="st",
swrcr="cr",
},
regtp=true,
trackplacer={
st=true,
@ -195,6 +201,12 @@ ap.t_45deg={
swrst="on",
swrcr="off",
},
switchst={
swlst="st",
swlcr="cr",
swrst="st",
swrcr="cr",
},
regtp=true,
trackplacer={
st=true,
@ -233,16 +245,27 @@ advtrains.trackpresets = ap
common={} change something on common rail appearance
}]]
function advtrains.register_tracks(tracktype, def, preset)
local function make_switchfunc(suffix_target, mesecon_state)
local switchfunc=function(pos, node)
advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..suffix_target, param2=node.param2})
local function make_switchfunc(suffix_target, mesecon_state, is_state)
local switchfunc=function(pos, node, newstate)
if newstate~=is_state then
advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..suffix_target, param2=node.param2})
end
advtrains.invalidate_all_paths()
end
return switchfunc, {effector = {
["action_"..mesecon_state] = switchfunc,
rules=advtrains.meseconrules
}}
local mesec
if mesecon_state then -- if mesecons is not wanted, do not.
mesec = {effector = {
["action_"..mesecon_state] = switchfunc,
rules=advtrains.meseconrules
}}
end
return switchfunc, mesec,
{
getstate = is_state,
setstate = switchfunc,
}
end
local function make_overdef(suffix, rotation, conns, switchfunc, mesecontbl, in_creative_inv, drop_slope)
local function make_overdef(suffix, rotation, conns, switchfunc, mesecontbl, luaautomation, in_creative_inv, drop_slope)
local img_suffix=suffix..rotation
return {
mesh = def.shared_model or (def.models_prefix.."_"..img_suffix..def.models_suffix),
@ -266,6 +289,7 @@ function advtrains.register_tracks(tracktype, def, preset)
not_blocking_trains=1,
},
mesecons=mesecontbl,
luaautomation=luaautomation,
drop = increativeinv and def.nodename_prefix.."_"..suffix..rotation or (drop_slope and def.nodename_prefix.."_slopeplacer" or def.nodename_prefix.."_placer"),
}
end
@ -294,7 +318,7 @@ function advtrains.register_tracks(tracktype, def, preset)
railheight=0,
drop=def.nodename_prefix.."_placer",
can_dig=function(pos)
return not advtrains.is_train_at_pos(pos)
return not advtrains.get_train_at_pos(pos)
end,
after_dig_node=function(pos)
advtrains.invalidate_all_paths()
@ -315,9 +339,9 @@ function advtrains.register_tracks(tracktype, def, preset)
for suffix, conns 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 switchfunc, mesecontbl
local switchfunc, mesecontbl, luaautomation
if preset.switch[suffix] then
switchfunc, mesecontbl=make_switchfunc(preset.switch[suffix]..rotation, preset.switchmc[suffix])
switchfunc, mesecontbl, luaautomation=make_switchfunc(preset.switch[suffix]..rotation, preset.switchmc[suffix], preset.switchst[suffix])
end
local adef={}
if def.get_additional_definiton then
@ -329,7 +353,7 @@ function advtrains.register_tracks(tracktype, def, preset)
make_overdef(
suffix, rotation,
cycle_conns(conns, rotid),
switchfunc, mesecontbl, preset.increativeinv[suffix], preset.slopenodes[suffix]
switchfunc, mesecontbl, luaautomation, preset.increativeinv[suffix], preset.slopenodes[suffix]
),
adef
)
@ -410,19 +434,16 @@ end
function advtrains.detector.call_enter_callback(pos, train_id)
--atprint("instructed to call enter calback")
local node = minetest.get_node(pos) --this spares the check if node is nil, it has a name in any case
local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
local mregnode=minetest.registered_nodes[node.name]
if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_enter then
mregnode.advtrains.on_train_enter(pos, train_id)
end
--atc code wants to be notified too
advtrains.atc.trigger_controller_train_enter(pos, train_id)
end
function advtrains.detector.call_leave_callback(pos, train_id)
--atprint("instructed to call leave calback")
local node = minetest.get_node(pos) --this spares the check if node is nil, it has a name in any case
local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
local mregnode=minetest.registered_nodes[node.name]
if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_leave then
mregnode.advtrains.on_train_leave(pos, train_id)
@ -594,7 +615,7 @@ if mesecon then
},
advtrains = {
on_train_enter=function(pos, train_id)
minetest.swap_node(pos, {name="advtrains:dtrack_detector_on".."_"..suffix..rotation, param2=minetest.get_node(pos).param2})
advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_on".."_"..suffix..rotation, param2=minetest.get_node(pos).param2})
mesecon.receptor_on(pos, advtrains.meseconrules)
end
}
@ -619,7 +640,7 @@ if mesecon then
},
advtrains = {
on_train_leave=function(pos, train_id)
minetest.swap_node(pos, {name="advtrains:dtrack_detector_off".."_"..suffix..rotation, param2=minetest.get_node(pos).param2})
advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_off".."_"..suffix..rotation, param2=minetest.get_node(pos).param2})
mesecon.receptor_off(pos, advtrains.meseconrules)
end
}

View File

@ -40,7 +40,14 @@ advtrains.audit_interval=10
advtrains.save_and_audit_timer=advtrains.audit_interval
minetest.register_globalstep(function(dtime)
minetest.register_globalstep(function(dtime_mt)
--limit dtime: if trains move too far in one step, automation may cause stuck and wrongly braking trains
local dtime=dtime_mt
if dtime>0.2 then
atprint("Limiting dtime to 0.2!")
dtime=0.2
end
advtrains.save_and_audit_timer=advtrains.save_and_audit_timer-dtime
if advtrains.save_and_audit_timer<=0 then
local t=os.clock()
@ -251,8 +258,8 @@ function advtrains.train_step_a(id, train, dtime)
--- 5. extend path as necessary ---
local gen_front=math.max(train.index, train.detector_old_index) + 2
local gen_back=math.min(train.end_index, train.detector_old_end_index) - 2
local gen_front=math.max(train.index, train.detector_old_index) + 10
local gen_back=math.min(train.end_index, train.detector_old_end_index) - 10
local maxn=train.path_extent_max or 0
while maxn < gen_front do--pregenerate
@ -747,31 +754,11 @@ function advtrains.invert_train(train_id)
advtrains.update_trainpart_properties(train_id, true)
end
function advtrains.is_train_at_pos(pos)
--atprint("istrainat: pos "..minetest.pos_to_string(pos))
local checked_trains={}
local objrefs=minetest.get_objects_inside_radius(pos, 2)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id and not checked_trains[le.train_id] then
--atprint("istrainat: checking "..le.train_id)
checked_trains[le.train_id]=true
local path=le:train().path
if path then
--atprint("has path")
for i=math.floor(advtrains.get_train_end_index(le:train())+0.5),math.floor(le:train().index+0.5) do
if path[i] then
--atprint("has pathitem "..i.." "..minetest.pos_to_string(path[i]))
if vector.equals(advtrains.round_vector_floor_y(path[i]), pos) then
return true
end
end
end
end
end
end
return false
function advtrains.get_train_at_pos(pos)
local ph=minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
return advtrains.detector.on_node[ph]
end
function advtrains.invalidate_all_paths()
--atprint("invalidating all paths")
for k,v in pairs(advtrains.trains) do

View File

@ -667,7 +667,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if pname~=wagon.owner then
return true
end
if not fields.quit then
if fields.save or not fields.quit then
for sgr,sgrdef in pairs(wagon.seat_groups) do
if fields["sgr_"..sgr] then
local fcont = fields["sgr_"..sgr]

View File

@ -0,0 +1 @@
advtrains

View File

@ -0,0 +1,144 @@
local map_def={
example = {
p1x=168,
p1z=530,
p2x=780,
p2z=1016,
background="itm_example.png",
},
}
local itm_cache={}
local itm_pdata={}
local itm_conf_mindia=0.1
minetest.register_privilege("itm", { description = "Allows to display train map", give_to_singleplayer = true, default = false })
local function create_map_form_with_bg(d)
local minx, minz, maxx, maxz = math.min(d.p1x, d.p2x), math.min(d.p1z, d.p2z), math.max(d.p1x, d.p2x), math.max(d.p1z, d.p2z)
local form_x, form_z=10,10
local edge_x, edge_z = form_x/(maxx-minx), form_z/(maxz-minz)
local len_x, len_z=math.max(edge_x, itm_conf_mindia), math.max(edge_z, itm_conf_mindia)
local form="size["..(form_x+edge_x)..","..(form_z+edge_z).."] background[0,0;0,0;"..d.background..";true] "
local lbl={}
for pts, tid in pairs(advtrains.detector.on_node) do
local pos=minetest.string_to_pos(pts)
form=form.."box["..(edge_x*(pos.x-minx))..","..(form_z-(edge_z*(pos.z-minz)))..";"..len_x..","..len_z..";red]"
lbl[sid(tid)]=pos
end
for t_id, xz in pairs(lbl) do
form=form.."label["..(edge_x*(xz.x-minx))..","..(form_x-(edge_z*(xz.z-minz)))..";"..t_id.."]"
end
return form
end
local function create_map_form(d)
if d.background then
return create_map_form_with_bg(d)
end
local minx, minz, maxx, maxz = math.min(d.p1x, d.p2x), math.min(d.p1z, d.p2z), math.max(d.p1x, d.p2x), math.max(d.p1z, d.p2z)
local form_x, form_z=10,10
local edge_x, edge_z = form_x/(maxx-minx), form_z/(maxz-minz)
local len_x, len_z=math.max(edge_x, itm_conf_mindia), math.max(edge_z, itm_conf_mindia)
local form="size["..(form_x+edge_x)..","..(form_z+edge_z).."]"
local lbl={}
for x,itx in pairs(itm_cache) do
if x>=minx and x<=maxx then
for z,y in pairs(itx) do
if z>=minz and z<=maxz then
local adn=advtrains.detector.on_node[minetest.pos_to_string({x=x, y=y, z=z})]
local color="gray"
if adn then
color="red"
lbl[sid(adn)]={x=x, z=z}
end
form=form.."box["..(edge_x*(x-minx))..","..(form_z-(edge_z*(z-minz)))..";"..len_x..","..len_z..";"..color.."]"
end
end
end
end
for t_id, xz in pairs(lbl) do
form=form.."label["..(edge_x*(xz.x-minx))..","..(form_x-(edge_z*(xz.z-minz)))..";"..t_id.."]"
end
return form
end
local function cache_ndb()
itm_cache={}
local ndb_nodes=advtrains.ndb.get_nodes()
for phs,_ in pairs(ndb_nodes) do
local pos=minetest.get_position_from_hash(phs)
if not itm_cache[pos.x] then
itm_cache[pos.x]={}
end
itm_cache[pos.x][pos.z]=pos.y
end
end
minetest.register_chatcommand("itm", {
params="[x1 z1 x2 z2] or [mdef]",
description="Display advtrains train map of given area.\nFirst form:[x1 z1 x2 z2] - specify area directly.\nSecond form:[mdef] - Use a predefined map background(see init.lua)\nThird form: No parameters - use WorldEdit position markers.",
privs={itm=true},
func = function(name, param)
local mdef=string.match(param, "^(%S+)$")
if mdef then
local d=map_def[mdef]
if not d then
return false, "Map definiton not found: "..mdef
end
itm_pdata[name]=map_def[mdef]
minetest.show_formspec(name, "itrainmap", create_map_form(d))
return true, "Showing train map: "..mdef
end
local x1, z1, x2, z2=string.match(param, "^(%S+) (%S+) (%S+) (%S+)$")
if not (x1 and z1 and x2 and z2) then
if worldedit then
local wep1, wep2=worldedit.pos1[name], worldedit.pos2[name]
if wep1 and wep2 then
x1, z1, x2, z2=wep1.x, wep1.z, wep2.x, wep2.z
end
end
end
if not (x1 and z1 and x2 and z2) then
return false, "Invalid parameters and no WE positions set"
end
local d={p1x=x1, p1z=z1, p2x=x2, p2z=z2}
itm_pdata[name]=d
minetest.show_formspec(name, "itrainmap", create_map_form(d))
return true, "Showing ("..x1..","..z1..")-("..x2..","..z2..")"
end,
})
minetest.register_chatcommand("itm_cache_ndb", {
params="",
description="Cache advtrains node database again. Run when tracks changed.",
privs={itm=true},
func = function(name, param)
cache_ndb()
return true, "Done caching node database."
end,
})
local timer=0
minetest.register_globalstep(function(dtime)
timer=timer-math.min(dtime, 0.1)
if timer<=0 then
for pname,d in pairs(itm_pdata) do
minetest.show_formspec(pname, "itrainmap", create_map_form(d))
end
timer=2
end
end)
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname=="itrainmap" and fields.quit then
itm_pdata[player:get_player_name()]=nil
end
end)
--automatically run itm_cache_ndb
minetest.after(2, cache_ndb)

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -0,0 +1,155 @@
#### Advtrains - Lua Automation features
This mod offers components that run LUA code and interface with each other through a global environment. It makes complex automated railway systems possible.
### atlatc
The mod is sometimes abbreviated as 'atlatc'. This stands for AdvTrainsLuaATC. This short name has been chosen for user convenience, since the name of this mod ('advtrains_luaautomation') is very long.
### Privilege
To perform any operations using this mod (except executing operation panels), players need the "atlatc" privilege.
This privilege should never be granted to anyone except trusted administrators. Even though the LUA environment is sandboxed, it is still possible to DoS the server by coding infinite loops or requesting expotentially growing interrupts.
### Active and passive
Active components are these who have LUA code running in them. They are triggered on specific events. Passive components are dumb, they only have a state and can be set to another state, they can't perform actions themselves.
### Environments
Each active component is assigned to an environment. This is where all data are held. Components in different environments can't inferface with each other.
This system allows multiple independent automation systems to run simultaneously without polluting each other's environment.
/env_create <env_name>
Create environment with the given name. To be able to do anything, you first need to create an environment. Choose the name wisely, you can't change it afterwards.
/env_setup <env_name>
Invoke the form to edit the environment's initialization code. For more information, see the section on active components. You can also delete an environment from here.
### Active components
The code of every active component is run on specific events which are explained soon. When run, every variable written that is not local and is no function or userdata is saved over code re-runs and over server restarts. Additionally, the following global variables are defined:
# event
The variable 'event' contains a table with information on the current event. How this table can look is explained below.
# S
The variable 'S' contains a table which is shared between all components of the environment. Its contents are persistent over server restarts. May not contain functions, every other value is allowed.
Example:
Component 1: S.stuff="foo"
Component 2: print(S.stuff)
-> foo
# F
The variable 'F' also contains a table which is shared between all components of the environment. Its contents are discarded on server shutdown or when the init code gets re-run. Every data type is allowed, even functions.
The purpose of this table is not to save data, but to provide static value and function definitions. The table should be populated by the init code.
# Standard Lua functions
The following standard Lua libraries are available:
string, math, table, os
The following standard Lua functions are available:
assert, error, ipairs, pairs, next, select, tonumber, tostring, type, unpack
Every attempt to overwrite any of the predefined values results in an error.
# LuaAutomation-specific global functions
POS(x,y,z)
Shorthand function to create a position vector {x=?, y=?, z=?} with less characters
getstate(pos)
Get the state of the passive component at position 'pos'. See section on passive components for more info.
setstate(pos, newstate)
Set the state of the passive component at position 'pos'.
interrupt(time, message)
Cause LuaAutomation to trigger an 'int' event on this component after the given time in seconds with the specified 'message' field. 'message' can be of any Lua data type.
Not available in init code!
interrupt_pos(pos, message)
Immediately trigger an 'ext_int' event on the active component at position pos. 'message' is like in interrupt().
USE WITH CARE, or better don't use! Incorrect use can result in expotential growth of interrupts.
## Components and events
The event table is a table of the following format:
{
type = "<type>",
<type> = true,
... additional content ...
}
You can check for the event type by either using
if event.type == "wanted" then ...do stuff... end
or
if event.wanted then ...do stuff... end
(if 'wanted' is the event type to check for)
# Init code
The initialization code is not a component as such, but rather a part of the whole environment. It can (and should) be used to make definitions that other components can refer to.
Examples:
A table with the positions of signals mapped to memorizable names, like this:
F.signals={
station_platform1_leave_north=POS(204,5,678),
station_platform1_leave_south=POS(202,5,643),
station_platform2_leave_north=POS(208,5,678),
station_platform2_leave_south=POS(210,5,643),
}
A function to define behavior for trains in subway stations:
function F.station()
if event.train then atc_send("B0WOL") end
if event.int and event.message="depart" then atc_send("OCD1SM") end
end
The init code is run whenever the F table needs to be refilled with data. This is the case on server startup and whenever the init code is changed and you choose to run it.
Functions are run in the environment of the currently active node, regardless of where they were defined. So, the 'event' table always reflects the state of the calling node.
The 'event' table of the init code is always {type="init", init=true}.
# ATC rails
The Lua-controlled ATC rails are the only components that can actually interface with trains. The following event types are generated:
{type="train", train=true, id="<train_id>"}
This event is fired when a train enters the rail. The field 'id' is the unique train ID, which is a long string (generated by concatenating os.time() and os.clock() at creation time). The Itrainmap mod displays the last 4 digits of this ID.
{type="int", int=true, message=<message>}
Fired when an interrupt set by the 'interrupt' function runs out. 'message' is the message passed to the interrupt function.
{type="ext_int", ext_int=true, message=<message>}
Fired when another node called 'interrupt_pos' on this position. 'message' is the message passed to the interrupt_pos function.
In addition to the default environment functions, the following functions are available:
atc_send(<atc_command>)
Sends the specified ATC command to the train and returns true. If there is no train, returns false and does nothing.
atc_reset()
Resets the train's current ATC command. If there is no train, returns false and does nothing.
atc_arrow
Boolean, true when the train is driving in the direction of the arrows of the ATC rail. Nil if there is no train.
atc_id
Train ID of the train currently passing the controller. Nil if there's no train.
atc_speed
Speed of the train, or nil if there is no train.
# Operator panel
This simple node executes its actions when punched. It can be used to change a switch and update the corresponding signals or similar applications.
The event fired is {type="punch", punch=true} by default. In case of an interrupt, the events are similar to the ones of the ATC rail.
### Passive components
All passive components can be interfaced with the setstate and getstate functions(see above).
Below, each apperance is mapped to the "state" of that node.
## Signals
The light signals are interfaceable, the analog signals are not.
"green" - Signal shows green light
"red" - Signal shows red light
## Switches
All default rail switches are interfaceable, independent of orientation.
"cr" - The switch is set in the direction that is not straight.
"st" - The switch is set in the direction that is straight.
## Mesecon Switch
The Mesecon switch can be switched using LuaAutomation. Note that this is not possible on levers, only the full-node 'Switch' block.
"on" - the switch is switched on
"off" - the switch is switched off

View File

@ -0,0 +1,133 @@
local ac = {nodes={}}
function ac.load(data)
if data then
ac.nodes=data.nodes
end
end
function ac.save()
return {nodes = ac.nodes}
end
function ac.after_place_node(pos, player)
advtrains.ndb.update(pos)
local meta=minetest.get_meta(pos)
meta:set_string("formspec", ac.getform(pos, meta))
meta:set_string("infotext", "LuaAutomation component, unconfigured.")
local ph=minetest.pos_to_string(pos)
--just get first available key!
for en,_ in pairs(atlatc.envs) do
ac.nodes[ph]={env=en}
return
end
end
function ac.getform(pos, meta_p)
local meta = meta_p or minetest.get_meta(pos)
local envs_asvalues={}
local ph=minetest.pos_to_string(pos)
local nodetbl = ac.nodes[ph]
local env, code, err = nil, "", ""
if nodetbl then
code=nodetbl.code or ""
err=nodetbl.err or ""
env=nodetbl.env or ""
end
local sel = 1
for n,_ in pairs(atlatc.envs) do
envs_asvalues[#envs_asvalues+1]=n
if n==env then
sel=#envs_asvalues
end
end
local form = "size[10,10]dropdown[0,0;3;env;"..table.concat(envs_asvalues, ",")..";"..sel.."]"
.."button[4,0;2,1;save;Save]button[7,0;2,1;cle;Clear local env] textarea[0.2,1;10,10;code;Code;"..minetest.formspec_escape(code).."]"
.."label[0,9.8;"..err.."]"
return form
end
function ac.after_dig_node(pos, node, player)
advtrains.invalidate_all_paths()
advtrains.ndb.clear(pos)
local ph=minetest.pos_to_string(pos)
ac.nodes[ph]=nil
end
function ac.on_receive_fields(pos, formname, fields, player)
if not minetest.check_player_privs(player:get_player_name(), {atlatc=true}) then
minetest.chat_send_player(player:get_player_name(), "Missing privilege: atlatc - Operation cancelled!")
end
local meta=minetest.get_meta(pos)
local ph=minetest.pos_to_string(pos)
local nodetbl = ac.nodes[ph] or {}
--if fields.quit then return end
if fields.env then
nodetbl.env=fields.env
end
if fields.code then
nodetbl.code=fields.code
end
if fields.save then
nodetbl.err=nil
end
if fields.cle then
nodetbl.data={}
end
ac.nodes[ph]=nodetbl
meta:set_string("formspec", ac.getform(pos, meta))
if nodetbl.env then
meta:set_string("infotext", "LuaAutomation component, assigned to environment '"..nodetbl.env.."'")
else
meta:set_string("infotext", "LuaAutomation component, invalid enviroment set!")
end
end
function ac.run_in_env(pos, evtdata, customfct_p)
local ph=minetest.pos_to_string(pos)
local nodetbl = ac.nodes[ph]
if not nodetbl then
atwarn("LuaAutomation component at",ph,": Data not in memory! Please visit component and click 'Save'!")
return
end
local meta
if minetest.get_node(pos) then
meta=minetest.get_meta(pos)
end
if not nodetbl.env or not atlatc.envs[nodetbl.env] then
atwarn("LuaAutomation component at",ph,": Not an existing environment: "..(nodetbl.env or "<nil>"))
return false
end
if not nodetbl.code or nodetbl.code=="" then
atwarn("LuaAutomation component at",ph,": No code to run! (insert -- to suppress warning)")
return false
end
local customfct=customfct_p or {}
customfct.interrupt=function(t, imesg)
atlatc.interrupt.add(t, pos, {type="int", int=true, message=imesg})
end
local datain=nodetbl.data or {}
local succ, dataout = atlatc.envs[nodetbl.env]:execute_code(datain, nodetbl.code, evtdata, customfct)
if succ then
atlatc.active.nodes[ph].data=atlatc.remove_invalid_data(dataout)
else
atlatc.active.nodes[ph].err=dataout
atwarn("LuaAutomation ATC interface rail at",ph,": LUA Error:",dataout)
if meta then
meta:set_string("infotext", "LuaAutomation ATC interface rail, ERROR:"..dataout)
end
end
if meta then
meta:set_string("formspec", ac.getform(pos, meta))
end
end
atlatc.active=ac

View File

@ -0,0 +1,106 @@
-- atc_rail.lua
-- registers and handles the ATC rail. Active component.
-- This is the only component that can interface with trains, so train interface goes here too.
--Using subtable
local r={}
function r.fire_event(pos, evtdata)
local ph=minetest.pos_to_string(pos)
local railtbl = atlatc.active.nodes[ph]
if not railtbl then
atwarn("LuaAutomation ATC interface rail at",ph,": Data not in memory! Please visit position and click 'Save'!")
return
end
local arrowconn = railtbl.arrowconn
--prepare ingame API for ATC. Regenerate each time since pos needs to be known
--If no train, then return false.
local train_id=advtrains.detector.on_node[ph]
local train, atc_arrow, tvel
if train_id then train=advtrains.trains[train_id] end
if train then
if not train.path then
--we happened to get in between an invalidation step
--delay
atlatc.interrupt.add(0,pos,evtdata)
return
end
for index, ppos in pairs(train.path) do
if vector.equals(advtrains.round_vector_floor_y(ppos), pos) then
atc_arrow =
vector.equals(
advtrains.dirCoordSet(pos, arrowconn),
advtrains.round_vector_floor_y(train.path[index+train.movedir])
)
end
end
if atc_arrow==nil then
atwarn("LuaAutomation ATC rail at", pos, ": Rail not on train's path! Can't determine arrow direction. Assuming +!")
atc_arrow=true
tvel=train.velocity
end
end
local customfct={
atc_send = function(cmd)
if not train_id then return false end
advtrains.atc.train_reset_command(train_id)
train.atc_command=cmd
train.atc_arrow=atc_arrow
return true
end,
atc_reset = function(cmd)
if not train_id then return false end
advtrains.atc.train_reset_command(train_id)
return true
end,
atc_arrow = atc_arrow,
atc_id = train_id,
atc_speed = tvel,
}
atlatc.active.run_in_env(pos, evtdata, customfct)
end
advtrains.register_tracks("default", {
nodename_prefix="advtrains_luaautomation:dtrack",
texture_prefix="advtrains_dtrack_atc",
models_prefix="advtrains_dtrack_detector",
models_suffix=".b3d",
shared_texture="advtrains_dtrack_rail_atc.png",
description=atltrans("LuaAutomation ATC Rail"),
formats={},
get_additional_definiton = function(def, preset, suffix, rotation)
return {
after_place_node = atlatc.active.after_place_node,
after_dig_node = atlatc.active.after_dig_node,
on_receive_fields = function(pos, ...)
atlatc.active.on_receive_fields(pos, ...)
--set arrowconn (for ATC)
local ph=minetest.pos_to_string(pos)
local _, conn1=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
atlatc.active.nodes[ph].arrowconn=conn1
end,
advtrains = {
on_train_enter = function(pos, train_id)
--do async. Event is fired in train steps
atlatc.interrupt.add(0, pos, {type="train", train=true, id=train_id})
end,
},
luaautomation = {
fire_event=r.fire_event
}
}
end
}, advtrains.trackpresets.t_30deg_straightonly)
atlatc.rail = r

View File

@ -0,0 +1,84 @@
--chatcmds.lua
--Registers commands to modify the init and step code for LuaAutomation
--position helper.
--punching a node will result in that position being saved and inserted into a text field on the top of init form.
local punchpos={}
minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
local pname=player:get_player_name()
punchpos[pname]=pos
end)
local function get_init_form(env, pname)
local err = env.init_err or ""
local code = env.init_code or ""
local ppos=punchpos[pname]
local pp=""
if ppos then
pp="POS"..minetest.pos_to_string(ppos)
end
local form = "size[10,10]button[0,0;2,1;run;Run InitCode]button[2,0;2,1;cls;Clear S]"
.."button[4,0;2,1;save;Save] button[6,0;2,1;del;Delete Env.] field[8.1,0.5;2,1;punchpos;Last punched position;"..pp.."]"
.."textarea[0.2,1;10,10;code;Environment initialization code;"..minetest.formspec_escape(code).."]"
.."label[0,9.8;"..err.."]"
return form
end
core.register_chatcommand("env_setup", {
params = "<environment name>",
description = "Set up and modify AdvTrains LuaAutomation environment",
privs = {atlatc=true},
func = function(name, param)
local env=atlatc.envs[param]
if not env then return false,"Invalid environment name!" end
minetest.show_formspec(name, "atlatc_envsetup_"..param, get_init_form(env, name))
return true
end,
})
core.register_chatcommand("env_create", {
params = "<environment name>",
description = "Create an AdvTrains LuaAutomation environment",
privs = {atlatc=true},
func = function(name, param)
if not param or param=="" then return false, "Name required!" end
if atlatc.envs[param] then return false, "Environment already exists!" end
atlatc.envs[param] = atlatc.env_new(param)
return true, "Created environment '"..param.."'. Use '/env_setup "..param.."' to define global initialization code, or start building LuaATC components!"
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
local pname=player:get_player_name()
if not minetest.check_player_privs(pname, {atlatc=true}) then return end
local envname=string.match(formname, "^atlatc_delconfirm_(.+)$")
if envname and fields.sure=="YES" then
atlatc.envs[envname]=nil
minetest.chat_send_player(pname, "Environment deleted!")
return
end
envname=string.match(formname, "^atlatc_envsetup_(.+)$")
if not envname then return end
local env=atlatc.envs[envname]
if not env then return end
if fields.del then
minetest.show_formspec(pname, "atlatc_delconfirm_"..envname, "field[sure;"..minetest.formspec_escape("SURE TO DELETE ENVIRONMENT "..envname.."? Type YES (all uppercase) to continue or just quit form to cancel.")..";]")
return
end
env.init_err=nil
if fields.code then
env.init_code=fields.code
end
if fields.run then
env:run_initcode()
minetest.show_formspec(pname, formname, get_init_form(env, pname))
end
end)

View File

@ -0,0 +1,2 @@
advtrains
mesecons?

View File

@ -0,0 +1,288 @@
-------------
-- lua sandboxed environment
-- function to cross out functions and userdata.
-- modified from dump()
function atlatc.remove_invalid_data(o, nested)
if o==nil then return nil end
local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
if type(o) ~= "table" then
--check valid data type
if not valid_dt[type(o)] then
return nil
end
return o
end
-- Contains table -> true/nil of currently nested tables
nested = nested or {}
if nested[o] then
return nil
end
nested[o] = true
for k, v in pairs(o) do
v = atlatc.remove_invalid_data(v, nested)
end
nested[o] = nil
return o
end
function atlatc.replace_function_envs(o, fenv, nested)
if o==nil then return nil end
local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
if type(o) ~= "table" then
--check valid data type
if type(o)=="function" then
setfenv(o, fenv)
end
return o
end
-- Contains table -> true/nil of currently nested tables
nested = nested or {}
if nested[o] then
return nil
end
nested[o] = true
for k, v in pairs(o) do
v = atlatc.replace_function_envs(v, fenv, nested)
end
nested[o] = nil
return o
end
local env_proto={
load = function(self, envname, data)
self.name=envname
self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
self.fdata={}
self.init_code=data.init_code or ""
self.step_code=data.step_code or ""
end,
save = function(self)
-- throw any function values out of the sdata table
self.sdata = atlatc.remove_invalid_data(self.sdata)
return {sdata = self.sdata, init_code=self.init_code, step_code=self.step_code}
end,
}
--Environment
--Code modified from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
local safe_globals = {
"assert", "error", "ipairs", "next", "pairs", "select",
"tonumber", "tostring", "type", "unpack", "_VERSION"
}
--print is actually minetest.chat_send_all()
--using advtrains.print_concat_table because it's cool
local function safe_print(t, ...)
local str=advtrains.print_concat_table({t, ...})
minetest.log("action", "[atlatc] "..str)
minetest.chat_send_all(str)
end
local function safe_date()
return(os.date("*t",os.time()))
end
-- string.rep(str, n) with a high value for n can be used to DoS
-- the server. Therefore, limit max. length of generated string.
local function safe_string_rep(str, n)
if #str * n > 2000 then
debug.sethook() -- Clear hook
error("string.rep: string length overflow", 2)
end
return string.rep(str, n)
end
-- string.find with a pattern can be used to DoS the server.
-- Therefore, limit string.find to patternless matching.
-- Note: Disabled security since there are enough security leaks and this would be unneccessary anyway to DoS the server
local function safe_string_find(...)
--if (select(4, ...)) ~= true then
-- debug.sethook() -- Clear hook
-- error("string.find: 'plain' (fourth parameter) must always be true for security reasons.")
--end
return string.find(...)
end
local mp=minetest.get_modpath("advtrains_luaautomation")
local p_api_getstate, p_api_setstate = dofile(mp.."/passive.lua")
local static_env = {
--core LUA functions
print = safe_print,
string = {
byte = string.byte,
char = string.char,
format = string.format,
len = string.len,
lower = string.lower,
upper = string.upper,
rep = safe_string_rep,
reverse = string.reverse,
sub = string.sub,
find = safe_string_find,
},
math = {
abs = math.abs,
acos = math.acos,
asin = math.asin,
atan = math.atan,
atan2 = math.atan2,
ceil = math.ceil,
cos = math.cos,
cosh = math.cosh,
deg = math.deg,
exp = math.exp,
floor = math.floor,
fmod = math.fmod,
frexp = math.frexp,
huge = math.huge,
ldexp = math.ldexp,
log = math.log,
log10 = math.log10,
max = math.max,
min = math.min,
modf = math.modf,
pi = math.pi,
pow = math.pow,
rad = math.rad,
random = math.random,
sin = math.sin,
sinh = math.sinh,
sqrt = math.sqrt,
tan = math.tan,
tanh = math.tanh,
},
table = {
concat = table.concat,
insert = table.insert,
maxn = table.maxn,
remove = table.remove,
sort = table.sort,
},
os = {
clock = os.clock,
difftime = os.difftime,
time = os.time,
date = safe_date,
},
POS = function(x,y,z) return {x=x, y=y, z=z} end,
getstate = p_api_getstate,
setstate = p_api_setstate,
--interrupts are handled per node, position unknown.
--however external interrupts can be set here.
interrupt_pos = function(pos, imesg)
if not type(pos)=="table" or not pos.x or not pos.y or not pos.z then
debug.sethook()
error("Invalid position supplied to interrupt_pos")
end
atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg})
end,
}
for _, name in pairs(safe_globals) do
static_env[name] = _G[name]
end
--The environment all code calls get is a table that has set static_env as metatable.
--In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed
--Init code and step code's environments are not saved
-- S - Table that can contain any save data global to the environment. Will be saved statically. Can't contain functions or userdata or circular references.
-- F - Table global to the environment, can contain volatile data that is deleted when server quits.
-- The init code should populate this table with functions and other definitions.
-- returns: true, fenv if successful; nil, error if error
function env_proto:execute_code(fenv, code, evtdata, customfct)
local metatbl ={
__index = function(t, i)
if i=="S" then
return self.sdata
elseif i=="F" then
return self.fdata
elseif i=="event" then
return evtdata
elseif customfct and customfct[i] then
return customfct[i]
end
return static_env[i]
end,
__newindex = function(t, i, v)
if i=="S" or i=="F" or i=="event" or (customfct and customfct[i]) or static_env[i] then
debug.sethook()
error("Trying to overwrite environment contents")
end
rawset(t,i,v)
end,
}
setmetatable(fenv, metatbl)
local fun, err=loadstring(code)
if not fun then
return false, err
end
--set function environment for all functions residing in F, so they get the right variables. Else it's a huge mess...
atlatc.replace_function_envs(self.fdata, fenv)
setfenv(fun, fenv)
local succ, data = pcall(fun)
if succ then
data=fenv
end
return succ, data
end
function env_proto:run_initcode()
if self.init_code and self.init_code~="" then
self.fdata = {}
atprint("[atlatc]Running initialization code for environment '"..self.name.."'")
local succ, err = self:execute_code({}, self.init_code, {type="init", init=true})
if not succ then
self.init_err=err
end
end
end
function env_proto:run_stepcode()
if self.step_code and self.step_code~="" then
local succ, err = self:execute_code({}, self.step_code, nil, {})
if not succ then
--TODO
end
end
end
--- class interface
function atlatc.env_new(name)
local newenv={
name=name,
init_code="",
step_code="",
sdata={}
}
setmetatable(newenv, {__index=env_proto})
return newenv
end
function atlatc.env_load(name, data)
local newenv={}
setmetatable(newenv, {__index=env_proto})
newenv:load(name, data)
return newenv
end
function atlatc.run_initcode()
for envname, env in pairs(atlatc.envs) do
env:run_initcode()
end
end
function atlatc.run_stepcode()
for envname, env in pairs(atlatc.envs) do
env:run_stepcode()
end
end

View File

@ -0,0 +1,105 @@
-- advtrains_luaautomation/init.lua
-- Lua automation features for advtrains
-- Uses global table 'atlatc' (AdvTrains_LuaATC)
-- Boilerplate to support localized strings if intllib mod is installed.
if minetest.get_modpath("intllib") then
atltrans = intllib.Getter()
else
atltrans = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end
end
--Privilege
--Only trusted players should be enabled to build stuff which can break the server.
atlatc = { envs = {}}
minetest.register_privilege("atlatc", { description = "Player can place and modify LUA ATC components. Grant with care! Allows to execute bad LUA code.", give_to_singleplayer = false, default= false })
local mp=minetest.get_modpath("advtrains_luaautomation")
if not mp then
error("Mod name error: Mod folder is not named 'advtrains_luaautomation'!")
end
dofile(mp.."/environment.lua")
dofile(mp.."/interrupt.lua")
dofile(mp.."/active_common.lua")
dofile(mp.."/atc_rail.lua")
dofile(mp.."/operation_panel.lua")
if mesecon then
dofile(mp.."/p_mesecon_iface.lua")
end
dofile(mp.."/chatcmds.lua")
local filename=minetest.get_worldpath().."/advtrains_luaautomation"
local file, err = io.open(filename, "r")
if not file then
minetest.log("error", " Failed to read advtrains_luaautomation save data from file "..filename..": "..(err or "Unknown Error"))
else
atprint("luaautomation reading file:",filename)
local tbl = minetest.deserialize(file:read("*a"))
if type(tbl) == "table" then
atprint(tbl)
if tbl.version==1 then
for envname, data in pairs(tbl.envs) do
atlatc.envs[envname]=atlatc.env_load(envname, data)
end
atlatc.active.load(tbl.active)
atlatc.interrupt.load(tbl.interrupt)
end
else
minetest.log("error", " Failed to read advtrains_luaautomation save data from file "..filename..": Not a table!")
end
file:close()
end
-- run init code of all environments
atlatc.run_initcode()
atlatc.save = function()
--versions:
-- 1 - Initial save format.
local envdata={}
for envname, env in pairs(atlatc.envs) do
envdata[envname]=env:save()
end
local save_tbl={
version = 1,
envs=envdata,
active = atlatc.active.save(),
interrupt = atlatc.interrupt.save(),
}
local datastr = minetest.serialize(save_tbl)
if not datastr then
minetest.log("error", " Failed to save advtrains_luaautomation save data to file "..filename..": Can't serialize!")
return
end
local file, err = io.open(filename, "w")
if err then
minetest.log("error", " Failed to save advtrains_luaautomation save data to file "..filename..": "..(err or "Unknown Error"))
return
end
file:write(datastr)
file:close()
end
minetest.register_on_shutdown(atlatc.save)
-- globalstep for step code
local timer, step_int=0, 2
local stimer, sstep_int=0, 10
minetest.register_globalstep(function(dtime)
timer=timer+dtime
if timer>step_int then
timer=0
atlatc.run_stepcode()
end
stimer=stimer+dtime
if stimer>sstep_int then
stimer=0
atlatc.save()
end
end)

View File

@ -0,0 +1,48 @@
-- interrupt.lua
-- implements interrupt queue
--to be saved: pos and evtdata
local iq={}
local queue={}
local timer=0
local run=false
function iq.load(data)
local d=data or {}
queue = d.queue or {}
timer = d.timer or 0
end
function iq.save()
return {queue = queue}
end
function iq.add(t, pos, evtdata)
queue[#queue+1]={t=t+timer, p=pos, e=evtdata}
run=true
end
minetest.register_globalstep(function(dtime)
if run then
timer=timer + math.min(dtime, 0.2)
for i=1,#queue do
local qe=queue[i]
if not qe then
table.remove(queue, i)
i=i-1
elseif timer>qe.t then
local pos, evtdata=queue[i].p, queue[i].e
local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
if ndef and ndef.luaautomation and ndef.luaautomation.fire_event then
ndef.luaautomation.fire_event(pos, evtdata)
end
table.remove(queue, i)
i=i-1
end
end
end
end)
atlatc.interrupt=iq

View File

@ -0,0 +1,23 @@
local function on_punch(pos, player)
atlatc.interrupt.add(0, pos, {type="punch", punch=true})
end
minetest.register_node("advtrains_luaautomation:oppanel", {
drawtype = "normal",
tiles={"atlatc_oppanel.png"},
description = "LuaAutomation operation panel",
groups = {
choppy = 1,
save_in_nodedb=1,
},
after_place_node = atlatc.active.after_place_node,
after_dig_node = atlatc.active.after_dig_node,
on_receive_fields = atlatc.active.on_receive_fields,
on_punch = on_punch,
luaautomation = {
fire_event=atlatc.active.run_in_env
}
})

View File

@ -0,0 +1,60 @@
-- p_mesecon_iface.lua
-- Mesecons interface by overriding the switch
if not mesecon then return end
minetest.override_item("mesecons_switch:mesecon_switch_off", {
groups = {
dig_immediate=2,
save_in_nodedb=1,
},
on_rightclick = function (pos, node)
if(mesecon.flipstate(pos, node) == "on") then
mesecon.receptor_on(pos)
else
mesecon.receptor_off(pos)
end
minetest.sound_play("mesecons_switch", {pos=pos})
advtrains.ndb.update(pos, node)
end,
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_off(pos)
end,
luaautomation = {
getstate = "off",
setstate = function(pos, node, newstate)
if newstate=="on" then
advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2})
mesecon.receptor_on(pos)
end
end,
},
})
minetest.override_item("mesecons_switch:mesecon_switch_on", {
groups = {
dig_immediate=2,
save_in_nodedb=1,
},
on_rightclick = function (pos, node)
if(mesecon.flipstate(pos, node) == "on") then
mesecon.receptor_on(pos)
else
mesecon.receptor_off(pos)
end
minetest.sound_play("mesecons_switch", {pos=pos})
advtrains.ndb.update(pos, node)
end,
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_on(pos)
end,
luaautomation = {
getstate = "on",
setstate = function(pos, node, newstate)
if newstate=="off" then
advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2})
mesecon.receptor_off(pos)
end
end,
},
})

View File

@ -0,0 +1,37 @@
-- passive.lua
-- API to passive components, as described in passive_api.txt
local function getstate(pos)
if not type(pos)=="table" or not pos.x or not pos.y or not pos.z then
debug.sethook()
error("Invalid position supplied to getstate")
end
local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
if ndef and ndef.luaautomation and ndef.luaautomation.getstate then
local st=ndef.luaautomation.getstate
if type(st)=="function" then
return st(pos, node)
else
return st
end
end
return nil
end
local function setstate(pos, newstate)
if not type(pos)=="table" or not pos.x or not pos.y or not pos.z then
debug.sethook()
error("Invalid position supplied to setstate")
end
local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
if ndef and ndef.luaautomation and ndef.luaautomation.setstate then
local st=ndef.luaautomation.setstate
st(pos, node, newstate)
end
end
-- gets called from environment.lua
-- return the values here to keep them local
return getstate, setstate

View File

@ -0,0 +1,23 @@
Lua Automation - Passive Component API
Passive components are nodes that do not have code running in them. However, active components can query these and request actions from them. Examples:
Switches
Signals
Displays
Mesecon Transmitter
All passive components have a table called 'luaautomation' in their node definition and have the group 'save_in_nodedb' set, so they work in unloaded chunks.
Example for a switch:
luaautomation = {
getstate = function(pos, node)
return "st"
end,
-- OR
getstate = "st",
setstate = function(pos, node, newstate)
if newstate=="cr" then
advtrains.ndb.swap_node(pos, <corresponding switch alt>)
end
end
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

View File

@ -139,7 +139,7 @@ advtrains.register_wagon("detailed_steam_engine", {
})
end,
drops={"default:steelblock 4"},
}, S("Detailed Steam Engine"), "advtrains_engine_steam_inv.png")
}, S("Detailed Steam Engine"), "advtrains_detailed_engine_steam_inv.png")
advtrains.register_wagon("wagon_default", {
mesh="advtrains_passenger_wagon.b3d",
@ -209,24 +209,34 @@ advtrains.register_wagon("wagon_box", {
minetest.register_craft({
output = 'advtrains:newlocomotive',
recipe = {
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'default:steelblock', 'dye:black', 'default:steelblock'},
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'', '', 'advtrains:chimney'},
{'advtrains:driver_cab', 'dye:black', 'advtrains:boiler'},
{'advtrains:wheel', 'advtrains:wheel', 'advtrains:wheel'},
},
})
minetest.register_craft({
output = 'advtrains:detailed_steam_engine',
recipe = {
{'', '', 'advtrains:chimney'},
{'advtrains:driver_cab', 'dye:green', 'advtrains:boiler'},
{'advtrains:wheel', 'advtrains:wheel', 'advtrains:wheel'},
},
})
minetest.register_craft({
output = 'advtrains:wagon_default',
recipe = {
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'default:steelblock', 'dye:dark_green', 'default:steelblock'},
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'default:glass', 'dye:dark_green', 'default:glass'},
{'advtrains:wheel', 'advtrains:wheel', 'advtrains:wheel'},
},
})
minetest.register_craft({
output = 'advtrains:wagon_box',
recipe = {
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'default:steelblock', 'default:chest', 'default:steelblock'},
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'group:wood', 'group:wood', 'group:wood'},
{'group:wood', 'default:chest', 'group:wood'},
{'advtrains:wheel', '', 'advtrains:wheel'},
},
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

BIN
assets/signal_wall.blend Normal file

Binary file not shown.

BIN
assets/signal_wall.blend1 Normal file

Binary file not shown.

BIN
assets/signal_wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB