aboutsummaryrefslogtreecommitdiff
path: root/advtrains
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains')
-rw-r--r--advtrains/atc.lua52
-rw-r--r--advtrains/occupation.lua279
-rw-r--r--advtrains/path.lua38
-rw-r--r--advtrains/trainhud.lua6
-rw-r--r--advtrains/trainlogic.lua626
-rw-r--r--advtrains/wagons.lua24
6 files changed, 642 insertions, 383 deletions
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index 10321d9..940cb08 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -20,34 +20,39 @@ end
--contents: {command="...", arrowconn=0-15 where arrow points}
--general
+function atc.train_set_command(train_id, command, arrow)
+ atc.train_reset_command(train_id)
+ advtrains.trains[train_id].atc_arrow = arrow
+ advtrains.trains[train_id].atc_command = command
+end
function atc.send_command(pos, par_tid)
local pts=minetest.pos_to_string(pos)
if atc.controllers[pts] then
--atprint("Called send_command at "..pts)
- local train_id = par_tid or advtrains.detector.get(pos)
+ local train_id = par_tid or advtrains.detector.get(pos) -- TODO: succesively replace those detector calls!
if train_id then
if advtrains.trains[train_id] then
--atprint("send_command inside if: "..sid(train_id))
- atc.train_reset_command(train_id)
- local arrowconn=atc.controllers[pts].arrowconn
- local train=advtrains.trains[train_id]
- for index, ppos in pairs(train.path) do
- if vector.equals(advtrains.round_vector_floor_y(ppos), pos) then
- advtrains.trains[train_id].atc_arrow =
- vector.equals(
- advtrains.dirCoordSet(pos, arrowconn),
- advtrains.round_vector_floor_y(train.path[index+train.movedir])
- )
- advtrains.trains[train_id].atc_command=atc.controllers[pts].command
- atprint("Sending ATC Command: ", atc.controllers[pts].command)
- return true
- end
+ if atc.controllers[pts].arrowconn then
+ atlog("ATC controller at",pts,": This controller had an arrowconn of", atc.controllers[pts].arrowconn, "set. Since this field is now deprecated, it was removed.")
+ atc.controllers[pts].arrowconn = nil
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)
+
+ local train = advtrains.trains[train_id]
+ local index = advtrains.path_lookup(train, pos)
+
+ local iconnid = 1
+ if index then
+ iconnid = train.path_cn[index]
+ else
+ atwarn("ATC rail at", pos, ": Rail not on train's path! Can't determine arrow direction. Assuming +!")
+ end
+
+ atc.train_set_command(train_id, atc.controllers[pts].command, iconnid==1)
+ atprint("Sending ATC Command to", train_id, ":", atc.controllers[pts].command, "iconnid=",iconnid)
+ return true
+
else
atwarn("ATC rail at", pos, ": Sending command failed: The train",train_id,"does not exist. This seems to be a bug.")
end
@@ -125,7 +130,7 @@ advtrains.atc_function = function(def, preset, suffix, rotation)
local pts=minetest.pos_to_string(pos)
local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
- atc.controllers[pts]={command=fields.command, arrowconn=conns[1].c}
+ atc.controllers[pts]={command=fields.command}
if advtrains.detector.get(pos) then
atc.send_command(pos)
end
@@ -168,8 +173,11 @@ local matchptn={
train.tarvelocity=tonumber(match)
return #match+1
end,
- ["B([0-9]+)"]=function(id, train, match)
- if train.velocity>tonumber(match) then
+ ["B([0-9B]+)"]=function(id, train, match)
+ if match=="B" then
+ train.atc_brake_target = -1 -- this means emergency brake. TODO don't forget to implement in train step!
+ train.tarvelocity = 0
+ elseif train.velocity>tonumber(match) then
train.atc_brake_target=tonumber(match)
if train.tarvelocity>train.atc_brake_target then
train.tarvelocity=train.atc_brake_target
diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua
new file mode 100644
index 0000000..f6fa6fc
--- /dev/null
+++ b/advtrains/occupation.lua
@@ -0,0 +1,279 @@
+-- occupation.lua
+--[[
+Collects and manages positions where trains occupy and/or reserve/require space
+
+Zone diagram of a train:
+ |___| |___| --> Direction of travel
+ oo oo+oo oo
+=|=======|===|===========|===|=======|===================|========|===
+ |SafetyB|CpB| Train |CpF|SafetyF| Brake |Aware |
+[1] [2] [3] [4] [5] [6] [7] [8]
+
+ID|Name |Desc
+ 0 Free Zone that was occupied before, which has now been left
+ 1 Train Zone where the train actually is.
+ 2 SafetyB Safety zone behind the train. extends 4m
+ 3 SafetyF Safety zone in front of the train. extends 4m
+ If a train is about to enter this zone, immediately brake it down to 2
+ 4 CpB Backside coupling zone. If the coupling zones of 2 trains overlap, they can be coupled
+ 5 CpF Frontside coupling zone
+ 6 Brake Brake distance of the train. Extends to the point ~5 nodes in front
+ of the point where the train would stop if it would regularily brake now.
+ 7 Aware Awareness zone. Extends 10-20 nodes beyond the Brake zone
+ Whenever any of the non-aware zones of other trains are detected here, the train will start to brake.
+
+Table format:
+occ[y][x][z] = {
+ [1] = train 1 id
+ [2] = train 1 ZoneID
+// [3] = entry seqnum*
+ ...
+ [2n-1] = train n id
+ [2n ] = train n ZoneID
+// [3n-2] = train n id
+// [3n-1] = train n ZoneID
+// [3n ] = entry seqnum*
+}
+occ_chg[n] = {
+ pos = vector,
+ train_id,
+ old_val, (0 when entry did not exist before)
+ new_val, (0 when entry was deleted)
+}
+
+*Sequence number:
+Sequence number system reserved for possible future use, but unused.
+The train will (and has to) memorize it's zone path indexes ("windows"), and do all actions that in any way modify these zone lengths
+in the movement phase (after restore, but before reporting occupations)
+((
+The sequence number is used to determine out-of-date entries to the occupation list
+The current sequence number (seqnum) is increased each step, until it rolls over MAX_SEQNUM, which is when a complete reset is triggered
+Inside a step, when a train updates an occupation, the sequence number is set to the currently active sequence number
+Whenever checking an entry for other occupations (e.g. in the aware zone), all entries that have a seqnum different from the current seqnum
+are considered not existant, and are cleared.
+Note that those outdated entries are only cleared on-demand, so there will be a large memory overhead over time. This is why in certain time intervals
+complete resets are required (however, this method should be much more performant than resetting the whole occ table each step, to spare continuous memory allocations)
+This complex behavior is required because there is no way to reliably determine which positions are _no longer_ occupied...
+))
+
+Composition of a step:
+
+1. (only when needed) restore step - write all current occupations into the table
+2. trains move
+3. trains pass new occupations to here. We keep track of which entries have changed
+4. we iterate our change lists and determine what to do
+
+]]--
+local o
+
+o.restore_required = true
+
+local MAX_SEQNUM = 65500
+local seqnum = 0
+
+local occ = {}
+local occ_chg = {}
+
+
+local function occget(p)
+ local t = occ[p.y]
+ if not t then
+ occ[p.y] = {}
+ t = occ[p.y]
+ end
+ t = t[p.x]
+ if not t then
+ t[p.x] = {}
+ t = t[p.x]
+ end
+ return t[p.z]
+end
+local function occgetcreate(p)
+ local t = occ[p.y]
+ if not t then
+ occ[p.y] = {}
+ t = occ[p.y]
+ end
+ t = t[p.x]
+ if not t then
+ t[p.x] = {}
+ t = t[p.x]
+ end
+ t = t[p.z]
+ if not t then
+ t[p.z] = {}
+ t = t[p.z]
+ end
+ return t
+end
+
+-- Resets the occupation memory, and sets the o.restore_required flag that instructs trains to report their occupations before moving
+function o.reset()
+ o.restore_required = true
+ occ = {}
+ occ_chg = {}
+ seqnum = 0
+end
+
+-- set occupation inside the restore step
+function o.init_occupation(train_id, pos, oid)
+ local t = occgetcreate(pos)
+ local i = 1
+ while t[i]
+ if t[i]==train_id then
+ break
+ end
+ i = i + 2
+ end
+ t[i] = train_id
+ t[i+1] = oid
+end
+
+function o.set_occupation(train_id, pos, oid)
+ local t = occgetcreate(pos)
+ local i = 1
+ while t[i]
+ if t[i]==train_id then
+ break
+ end
+ i = i + 2
+ end
+ local oldoid = t[i+1] or 0
+ if oldoid ~= oid then
+ addchg(pos, train_id, oldoid, oid)
+ end
+ t[i] = train_id
+ t[i+1] = oid
+end
+
+
+function o.clear_occupation(train_id, pos)
+ local t = occget(pos)
+ if not t then return end
+ local i = 1
+ local moving = false
+ while t[i] do
+ if t[i]==train_id then
+ if moving then
+ -- if, for some occasion, there should be a duplicate entry, erase this one too
+ atwarn("Duplicate occupation entry at",pos,"for train",train_id,":",t)
+ i = i - 2
+ end
+ local oldoid = t[i+1] or 0
+ addchg(pos, train_id, oldoid, 0)
+ moving = true
+ end
+ if moving then
+ t[i] = t[i+2]
+ t[i+1] = t[i+3]
+ end
+ i = i + 2
+ end
+end
+
+local function addchg(pos, train_id, old, new)
+ occ_chg[#occ_chg + 1] = {
+ pos = pos,
+ train_id = train_id,
+ old_val = old,
+ new_val = new,
+ }
+end
+
+-- Called after all occupations have been fed in
+-- This function is doing the interesting work...
+function o.end_step()
+ count_chg = false
+
+ for _,chg in ipairs(occ_chg) do
+ local t = occget(chg.pos)
+ if not t then
+ atwarn("o.end_step() change entry but there's no entry in occ table!",chg)
+ end
+ handle_chg(t, chg.pos, chg.train_id, chg.old_val, chg.new_val)
+ end
+
+ seqnum = seqnum + 1
+end
+
+local function handle_chg(t, pos, train_id, old, new)
+ -- Handling the actual "change" is only necessary on_train_enter (change to 1) and on_train_leave (change from 1)
+ if new==1 then
+ o.call_enter_callback(pos, train_id)
+ elseif old==1 then
+ o.call_leave_callback(pos, train_id)
+ end
+
+ --all other cases check the simultaneous presence of 2 or more occupations
+ if #t<=2 then
+ return
+ end
+ local blocking = {}
+ local aware = {}
+ local i = 1
+ while t[i] do
+ if t[i+1] ~= 7 then --anything not aware zone:
+ blocking[#blocking+1] = i
+ else
+ aware[#aware+1] = i
+ end
+ i = i + 2
+ end
+ if #blocking > 0 then
+ -- the aware trains should brake
+ for _, ix in ipairs(aware) do
+ atc.train_set_command(t[ix], "B2")
+ end
+ if #blocking > 1 then
+ -- not good, 2 trains interfered with their blocking zones
+ -- make them brake too
+ local txt = {}
+ for _, ix in ipairs(blocking) do
+ atc.train_set_command(t[ix], "B2")
+ txt[#txt+1] = t[ix]
+ end
+ atwarn("Trains",table.concat(txt, ","), "interfered with their blocking zones, braking...")
+ -- TODO: different behavior for automatic trains! they need to be notified of those brake events and handle them!
+ -- To drive in safety zone is ok when train is controlled by hand
+ end
+ end
+
+end
+
+function o.call_enter_callback(pos, train_id)
+ --atprint("instructed to call enter calback")
+
+ 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
+end
+function o.call_leave_callback(pos, train_id)
+ --atprint("instructed to call leave calback")
+
+ 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)
+ end
+end
+
+-- Checks whether some other train (apart from train_id) has it's 0 zone here
+function o.check_collision(pos, train_id)
+ local npos = advtrains.round_vector_floor_y(pos)
+ local t = occget(npos)
+ if not t then return end
+ local i = 1
+ while t[i] do
+ if t[i]~=train_id then
+ if t[i+1] ~= 7 then
+ return true
+ end
+ end
+ i = i + 2
+ end
+ return false
+end
+
+advtrains.occ = o
diff --git a/advtrains/path.lua b/advtrains/path.lua
index 43add95..a98a1b5 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -183,6 +183,13 @@ function advtrains.path_get(train, index)
train.path_dist[train.path_ext_b] = vector.distance(pos, adj_pos)
end
+ if index < train.path_req_b then
+ train.path_req_b = index
+ end
+ if index > train.path_req_f then
+ train.path_req_f = index
+ end
+
return train.path[index], (index<=train.path_trk_f and index>=train.path_trk_b)
end
@@ -233,3 +240,34 @@ function advtrains.path_get_index_by_offset(train, index, offset)
end
return index
end
+
+local PATH_CLEAR_KEEP = 2
+
+function advtrains.path_clear_unused(train)
+ for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do
+ train.path[i] = nil
+ train.path_dist[i] = nil
+ train.path_cp[i] = nil
+ train.path_cn[i] = nil
+ train.path_dir[i] = nil
+ end
+ for i = train.path_req_f + PATH_CLEAR_KEEP, train.path_ext_f do
+ train.path[i] = nil
+ train.path_dist[i-1] = nil
+ train.path_cp[i] = nil
+ train.path_cn[i] = nil
+ train.path_dir[i+1] = nil
+ end
+ train.path_req_f = math.ceil(train.index)
+ train.path_req_b = math.floor(train.end_index or train.index)
+end
+
+function advtrains.path_lookup(train, pos)
+ local cp = advtrains.round_vector_floor_y(pos)
+ for i = train.path_ext_b, train.path_ext_f do
+ if vector.equals(advtrains.round_vector_floor_y(train.path[i]), cp) then
+ return i
+ end
+ end
+ return nil
+end
diff --git a/advtrains/trainhud.lua b/advtrains/trainhud.lua
index 9b7b9e8..e48e3e4 100644
--- a/advtrains/trainhud.lua
+++ b/advtrains/trainhud.lua
@@ -67,14 +67,14 @@ function advtrains.on_control_change(pc, train, flip)
if train.door_open ~= 0 then
train.door_open = 0
else
- train.door_open = -train.movedir
+ train.door_open = -1
end
end
if pc.right then
if train.door_open ~= 0 then
train.door_open = 0
else
- train.door_open = train.movedir
+ train.door_open = 1
end
end
train.active_control = act
@@ -176,7 +176,7 @@ function advtrains.hud_train_format(train, flip)
local topLine, firstLine, secondLine
- topLine=" ["..mletter[fct*train.movedir].."] {"..levers.."} "..doorstr[(train.door_open or 0) * train.movedir]
+ topLine=" ["..mletter[fct].."] {"..levers.."} "..doorstr[(train.door_open or 0) * fct]
firstLine=attrans("Speed:").." |"..string.rep("+", vel)..string.rep("_", max-vel).."> "..vel_kmh.." km/h"
secondLine=attrans("Target:").." |"..string.rep("+", tvel)..string.rep("_", max-tvel).."> "..tvel_kmh.." km/h"
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
index 412d229..110d3f6 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -1,6 +1,7 @@
--trainlogic.lua
--controls train entities stuff about connecting/disconnecting/colliding trains and other things
+-- TODO: what should happen when a train has no trainparts anymore?
local benchmark=false
local bm={}
@@ -57,21 +58,31 @@ advtrains.mainloop_trainlogic=function(dtime)
end
end
--regular train step
- -- do in two steps:
- -- a: predict path and add all nodes to the advtrains.detector.on_node table
- -- b: check for collisions based on these data
- -- (and more)
+ --[[ structure:
+ 1. make trains calculate their occupation windows when needed (a)
+ 2. when occupation tells us so, restore the occupation tables (a)
+ 4. make trains move and update their new occupation windows and write changes
+ to occupation tables (b)
+ 5. make trains do other stuff (c)
+ ]]--
local t=os.clock()
- advtrains.detector.on_node={}
+
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
- advtrains.train_step_a(k, v, dtime)
+ train_ensure_clean(k, v, dtime)
end
+
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
- advtrains.train_step_b(k, v, dtime)
+ train_step_b(k, v, dtime)
+ end
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=sid(k)
+ advtrains.atprint_context_tid_full=k
+ train_step_c(k, v, dtime)
end
advtrains.atprint_context_tid=nil
@@ -120,118 +131,176 @@ minetest.register_on_dieplayer(function(player)
end
end)
end)
+
--[[
-train step structure:
+The occupation window system.
+
+Each train occupies certain nodes as certain occupation types. See occupation.lua for a graphic and an ID listing.
+There's an occwindows table in the train table. This is clearable, such as the path, and therefore needs to be exactly reconstructible.
+During runtime, the extents (in meters) of the zones are determined. the occwindows table holds the assigned fractional path indices.
+After the train moves, the occupation windows are re-calculated, and all differences are written to the occupation tables.
+
+Zone diagram of a train (copy from occupation.lua!):
+ |___| |___| --> Direction of travel
+ oo oo+oo oo
+=|=======|===|===========|===|=======|===================|========|===
+ |SafetyB|CpB| Train |CpF|SafetyF| Brake |Aware |
+[1] [2] [3] [4] [5] [6] [7] [8]
+This mapping from indices in occwindows to zone ids is contained in WINDOW_ZONE_IDS
+
+occwindows = {
+[n] = (index of the position determined in the graphic above,
+ where floor(i) belongs to the left zone and floor(i+1) belongs to the right.
+}
+]]--
+-- unless otherwise stated, in meters.
+local SAFETY_ZONE = 10
+local COUPLE_ZONE = 2 --value in index positions!
+local BRAKE_SPACE = 10
+local AWARE_ZONE = 10
+local WINDOW_ZONE_IDS = {
+ 2, -- 1 - SafetyB
+ 4, -- 2 - CpB
+ 1, -- 3 - Train
+ 5, -- 4 - CpF
+ 3, -- 5 - SafetyF
+ 6, -- 6 - Brake
+ 7, -- 7 - Aware
+}
-- legacy stuff
-- preparing the initial path and creating index
-- setting node coverage old indices
-- handle velocity influences:
- - off-track
- - atc
- - player controls
- - environment collision
-- update index = move
-- create path
-- call stay_node on all old positions to register train there, for collision system
-- do less important stuff such as checking trainpartload or removing
--- break --
-- Call enter_node and leave_node callbacks (required here because stay_node needs to be called on all trains first)
-- handle train collisions
+-- If a variable does not exist in the table, it is assigned the default value
+local function assertdef(tbl, var, def)
+ if not tbl[var] then
+ tbl[var] = def
+ end
+end
-]]
-function advtrains.train_step_a(id, train, dtime)
- --atprint("--- runcnt ",advtrains.mainloop_runcnt,": index",train.index,"end_index", train.end_index,"| max_iot", train.max_index_on_track, "min_iot", train.min_index_on_track, "<> pe_min", train.path_extent_min,"pe_max", train.path_extent_max)
- if train.min_index_on_track then
- assert(math.floor(train.min_index_on_track)==train.min_index_on_track)
+-- Calculates the indices where the window borders of the occupation windows are.
+local function calc_occwindows(id, train)
+ local end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
+ train.end_index = end_index
+ local cpl_b = end_index - COUPLE_ZONE
+ local safety_b = advtrains.path_get_index_by_offset(train, cpl_b, -SAFETY_ZONE)
+ local cpl_f = end_index + COUPLE_ZONE
+ local safety_f = advtrains.path_get_index_by_offset(train, cpl_f, SAFETY_ZONE)
+
+ -- calculate brake distance
+ local acc_all = t_accel_all[1]
+ local acc_eng = t_accel_eng[1]
+ local nwagons = #train.trainparts
+ local acc = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
+ local vel = train.velocity
+ local brakedst = (vel*vel) / (2*acc)
+
+ local brake_i = math.max(advtrains.path_get_index_by_offset(train, train.index, brakedst + BRAKE_SPACE), safety_f)
+ local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)
+
+ return {
+ safety_b,
+ cpl_b,
+ end_index,
+ train.index,
+ cpl_f,
+ safety_f,
+ brake_i,
+ aware_i,
+ }
+end
+
+-- this function either inits (no write_mode), sets(1) or clears(2) the occupations for train
+local function write_occupation(win, train_id, train, write_mode)
+ local n_window = 2
+ local c_index = math.ceil(win[1])
+ while win[n_window] do
+ local winix = win[n_window]
+ local oid = WINDOW_ZONE_IDS[n_windows - 1]
+ while winix > c_index do
+ local pos = advtrains.path_get(train, c_index)
+ if write_mode == 1 then
+ advtrains.occ.set_occupation(train_id, pos, oid)
+ elseif write_mode == 2 then
+ advtrains.occ.clear_occupation(train_id, pos)
+ else
+ advtrains.occ.init_occupation(train_id, pos, oid)
+ end
+ c_index = c_index + 1
+ end
+ c_index = math.ceil(winix)
+ n_window = n_window + 1
end
- --- 1. not exactly legacy. required now because of saving ---
+
+end
+local function apply_occupation_changes(old, new, train_id, train)
+ -- TODO
+end
+
+
+-- train_ensure_clean: responsible for creating a state that we can work on, after one of the following events has happened:
+-- - the train's path got cleared
+-- - the occupation table got cleared
+-- Additionally, this gets called outside the step cycle to initialize and/or remove a train, then occ_write_mode is set.
+local function train_ensure_clean(id, train, dtime, report_occupations, occ_write_mode)
+ train.dirty = true
+ if train.no_step then return end
+
+ assertdef(train, "velocity", 0)
+ assertdef(train, "tarvelocity", 0)
+ assertdef(train, "acceleration", 0)
+
+
if not train.drives_on or not train.max_speed then
advtrains.update_trainpart_properties(id)
end
- --TODO check for all vars to be present
- if not train.velocity then
- train.velocity=0
- end
- if not train.movedir or (train.movedir~=1 and train.movedir~=-1) then
- train.movedir=1
- end
- --- 2. prepare initial path and index if needed ---
- if not train.index then train.index=0 end
+
+ --restore path
if not train.path then
if not train.last_pos then
- --no chance to recover
- atwarn("Unable to restore train ",id,": missing last_pos")
- advtrains.trains[id]=nil
- return false
+ atwarn("Train",id": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.")
+ train.no_step = true
+ return
end
-
- local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.drives_on)
-
- if node_ok==nil then
- --block not loaded, do nothing
- atprint("last_pos", advtrains.round_vector_floor_y(train.last_pos), "not loaded and not in ndb, waiting")
- return nil
- elseif node_ok==false then
- atprint("Unable to restore train ",id,": No rail at train's position")
- return false
+ if not train.last_connid then
+ atwarn("Train",id": Restoring path failed, no last_connid set! Will assume 1")
end
- if not train.last_pos_prev then
- --no chance to recover
- atwarn("Unable to restore train ",id,": missing last_pos_prev")
- advtrains.trains[id]=nil
- return false
- end
+ local result = advtrains.path_create(train, train.last_pos, train.last_connid, train.last_frac or 0)
- local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos_prev), train.drives_on)
-
- if prevnode_ok==nil then
- --block not loaded, do nothing
- atprint("last_pos_prev", advtrains.round_vector_floor_y(train.last_pos_prev), "not loaded and not in ndb, waiting")
- return nil
- elseif prevnode_ok==false then
- atprint("Unable to restore train ",id,": No rail at train's position")
- return false
+ if result==false then
+ atwarn("Train",id": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to fix the issue in the save file.")
+ train.no_step = true
+ return
+ elseif result==nil then
+ if not train.wait_for_path then
+ atwarn("Train",id": Can't initialize: Waiting for the (yet unloaded) node at",train.last_pos," to be loaded.")
+ end
+ train.wait_for_path = true
end
-
- train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
- --restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
- --savedpos_off_track_index_offset is set if train went off track. see below.
- train.path={}
- train.path_dist={}
- train.path[0]=train.last_pos
- train.path[-1]=train.last_pos_prev
- train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
- train.path_extent_min=-1
- train.path_extent_max=0
- train.min_index_on_track=-1
- train.max_index_on_track=0
-
- --[[
- Bugfix for trains randomly ignoring ATC rails:
- - Paths have been invalidated. 1 gets executed and ensures an initial path
- - 2a sets train end index. The problem is that path_dist is not known for the whole path, so train end index will be nearly trainlen
- - Since the detector indices are also unknown, they get set to the new (wrong) train_end_index. Enter_node calls are not executed for the nodes that lie in between real end_index and trainlen.
- - The next step, mistake is recognized, train leaves some positions. From there, everything works again.
- To overcome this, we will generate the full required path here so that path_dist is available for get_train_end_index().
- ]]
- advtrains.pathpredict(id, train, 3, -train.trainlen-3)
+ -- by now, we should have a working initial path
+ train.occwindows = nil
+ advtrains.update_trainpart_properties(id)
+ -- TODO recoverposition?!
end
- --- 2a. set train.end_index which is required in different places, IF IT IS NOT SET YET by STMT afterwards. ---
- --- table entry to avoid triple recalculation ---
- if not train.end_index then
- train.end_index=advtrains.get_train_end_index(train)
+ --restore occupation windows
+ if not train.occwindows then
+ train.occwindows = calc_occwindows(id, train)
+ end
+ if report_occupations then
+ write_occupation(train.occwindows, train, occ_write_mode)
end
- --- 2b. set node coverage old indices ---
+ train.dirty = false -- TODO einbauen!
+end
+
+local function train_step_b(id, train, dtime)
+ if train.no_step or train.wait_for_path then return end
- train.detector_old_index = atround(train.index)
- train.detector_old_end_index = atround(train.end_index)
+ -- in this code, we check variables such as path_trk_? and path_dist. We need to ensure that the path is known for the whole 'Train' zone
+ advtrains.path_get(train, train.index + 1)
+ advtrains.path_get(train, train.end_index - 1)
--- 3. handle velocity influences ---
local train_moves=(train.velocity~=0)
@@ -249,33 +318,22 @@ function advtrains.train_step_a(id, train, dtime)
end
--- 3a. this can be useful for debugs/warnings and is used for check_trainpartload ---
- local t_info, train_pos=sid(id), train.path[atround(train.index)]
+ local t_info, train_pos=sid(id), advtrains.path_get(atfloor(train.index))
if train_pos then
t_info=t_info.." @"..minetest.pos_to_string(train_pos)
--atprint("train_pos:",train_pos)
end
--apply off-track handling:
- local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track
- local back_off_track=train.min_index_on_track and train.end_index<train.min_index_on_track
+ local front_off_track = train.index>train.path_trk_f
+ local back_off_track=train.end_index<train.path_trk_b
local pprint
- if front_off_track and back_off_track then--allow movement in both directions
+ if front_off_track then
+ tarvel_cap=0
+ end
+ if back_off_track then -- eventually overrides front_off_track restriction
tarvel_cap=1
- elseif front_off_track then--allow movement only backward
- if train.movedir==1 then
- tarvel_cap=0
- end
- if train.movedir==-1 then
- tarvel_cap=1
- end
- elseif back_off_track then--allow movement only forward
- if train.movedir==1 then
- tarvel_cap=1
- end
- if train.movedir==-1 then
- tarvel_cap=0
- end
end
--interpret ATC command and apply auto-lever control when not actively controlled
@@ -343,7 +401,7 @@ function advtrains.train_step_a(id, train, dtime)
if trainvelocity+vdiff > mspeed then
vdiff = mspeed - trainvelocity
end
- train.last_accel=(vdiff*train.movedir)
+ train.acceleration=vdiff
train.velocity=train.velocity+vdiff
if train.active_control then
train.tarvelocity = train.velocity
@@ -354,218 +412,60 @@ function advtrains.train_step_a(id, train, dtime)
--- 4. move train ---
- train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
-
- --- 4a. update train.end_index to the new position ---
- train.end_index=advtrains.get_train_end_index(train)
-
- --- 4b calculate how far a path is required ---
- local path_req_dd = 10 -- path required in driving direction
- local path_req_ndd = 4 -- path required against driving direction
-
- -- when using easyBSS (block signalling), we need to make sure that the whole brake distance is known
- if advtrains_easybss then
- local acc_all = t_accel_all[1]
- local acc_eng = t_accel_eng[1]
- local nwagons = #train.trainparts
- local acc = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
- local vel = train.velocity
- local brakedst = (vel*vel) / (2*acc)
- path_req_dd = math.ceil(brakedst+10)
- end
-
-
- local idx_front=math.max(train.index, train.detector_old_index)
- local idx_back=math.min(train.end_index, train.detector_old_end_index)
- local path_req_front, path_req_back
- if train.movedir == 1 then
- path_req_front = atround(idx_front + path_req_dd)
- path_req_back = atround(idx_back - path_req_ndd)
- else
- path_req_front = atround(idx_front + path_req_ndd)
- path_req_back = atround(idx_back - path_req_dd)
- end
-
- --- 5. extend path as necessary ---
- --why this is an extra function, see under 3.
- advtrains.pathpredict(id, train, path_req_front, path_req_back)
-
- --- 5a. make pos/yaw available for possible recover calls ---
- if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here
- train.savedpos_off_track_index_offset=atround(train.index)-train.max_index_on_track
- train.last_pos=train.path[train.max_index_on_track]
- train.last_pos_prev=train.path[train.max_index_on_track-1]
- atprint("train is off-track (front), last positions kept at", train.last_pos, "/", train.last_pos_prev)
- elseif train.min_index_on_track+1>train.index then --whoops, train went even more far. same behavior
- train.savedpos_off_track_index_offset=atround(train.index)-train.min_index_on_track
- train.last_pos=train.path[train.min_index_on_track+1]
- train.last_pos_prev=train.path[train.min_index_on_track]
- atprint("train is off-track (back), last positions kept at", train.last_pos, "/", train.last_pos_prev)
- else --regular case
- train.savedpos_off_track_index_offset=nil
- train.last_pos=train.path[math.floor(train.index+1)]
- train.last_pos_prev=train.path[math.floor(train.index)]
- end
-
- --- 5b. Remove path items that are no longer used ---
- -- Necessary since path items are no longer invalidated in save steps
- local del_keep=8
- local offtrack_keep=4
-
- local delete_min=math.min(train.max_index_on_track - offtrack_keep, path_req_back - del_keep)
- local delete_max=math.max(train.min_index_on_track + offtrack_keep, path_req_front + del_keep)
-
- if train.path_extent_min<delete_min then
- --atprint(sid(id),"clearing path min ",train.path_extent_min," to ",delete_min)
- for i=train.path_extent_min,delete_min-1 do
- train.path[i]=nil
- train.path_dist[i]=nil
- end
- train.path_extent_min=delete_min
- train.min_index_on_track=math.max(train.min_index_on_track, delete_min)
- end
- if train.path_extent_max>delete_max then
- --atprint(sid(id),"clearing path max ",train.path_extent_max," to ",delete_max)
- train.path_dist[delete_max]=nil
- for i=delete_max+1,train.path_extent_max do
- train.path[i]=nil
- train.path_dist[i]=nil
- end
- train.path_extent_max=delete_max
- train.max_index_on_track=math.min(train.max_index_on_track, delete_max)
- end
+ train.index=train.index and train.index+((train.velocity/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
+
+end
+
+local function train_recalc_occupation()
+ local new_occwindows = calc_occwindows(id, train)
+ apply_occupation_changes(train.occwindows, new_occwindows, id)
+ train.occwindows = new_occwindows
+end
+
+local function train_step_c(id, train, dtime)
+if train.no_step or train.wait_for_path then return end
- --- 6b. call stay_node to register trains in the location table - actual enter_node stuff is done in step b ---
+ -- all location/extent-critical actions have been done.
+ -- calculate the new occupation window
+ train_recalc_occupation(id, train)
- local ifo, ibo = train.detector_old_index, train.detector_old_end_index
- local path=train.path
+ advtrains.path_clear_unused(train)
- for i=ibo, ifo do
- if path[i] then
- advtrains.detector.stay_node(path[i], id)
- end
- end
+ -- Set our path restoration position
+ local fli = atfloor(train.index)
+ train.last_pos = advtrains.path_get(fli)
+ train.last_connid = train.path_cn[fli]
+ train.last_frac = train.index - fli
- --- 7. do less important stuff ---
- --check for any trainpart entities if they have been unloaded. do this only if train is near a player, to not spawn entities into unloaded areas
+ -- less important stuff
train.check_trainpartload=(train.check_trainpartload or 0)-dtime
- local node_range=(math.max((minetest.settings:get("active_block_range") or 0),1)*16)
if train.check_trainpartload<=0 then
- local ori_pos=train_pos --see 3a.
- --atprint("[train "..id.."] at "..minetest.pos_to_string(vector.round(ori_pos)))
-
- local should_check=false
- for _,p in ipairs(minetest.get_connected_players()) do
- should_check=should_check or (ori_pos and ((vector.distance(ori_pos, p:getpos())<node_range)))
- end
- if should_check then
- advtrains.update_trainpart_properties(id)
- end
+ advtrains.spawn_wagons(id)
train.check_trainpartload=2
end
- --remove?
- if #train.trainparts==0 then
- atprint("[train "..sid(id).."] has empty trainparts, removing.")
- advtrains.detector.leave_node(path[train.detector_old_index], id)
- advtrains.trains[id]=nil
- return
- end
-end
-
-
-function advtrains.train_step_b(id, train, dtime)
-
- --hacky fix: if train_step_a returned in phase 2, end_index may not be set.
- --just return
- if not train.index or not train.end_index then
- return
- end
- --- 6. update node coverage ---
-
- -- when paths get cleared, the old indices set above will be up-to-date and represent the state in which the last run of this code was made
- local ifo, ifn, ibo, ibn = train.detector_old_index, atround(train.index), train.detector_old_end_index, atround(train.end_index)
- --atprint(ifo,">", ifn, "<==>", ibo,">", ibn)
-
- local path=train.path
-
- if train.enter_node_all then
- --field set by create_new_train_at.
- --ensures that new train calls enter_node on all nodes
- for i=ibn, ifn do
- if path[i] then
- advtrains.detector.enter_node(path[i], id)
- end
- end
- train.enter_node_all=nil
- else
- if ifn>ifo then
- for i=ifo+1, ifn do
- if path[i] then
- if advtrains.detector.occupied(path[i], id) then
- --if another train has signed up for this position first, it won't be recognized in train_step_b. So do collision here.
- atprint("Collision detected in enter_node callbacks (front) @",path[i],"with",sid(advtrains.detector.get(path[i], id)))
- advtrains.collide_and_spawn_couple(id, path[i], advtrains.detector.get(path[i], id), false)
- end
- atprint("enter_node (front) @index",i,"@",path[i],"on_node",sid(advtrains.detector.get(path[i], id)))
- advtrains.detector.enter_node(path[i], id)
- end
- end
- elseif ifn<ifo then
- for i=ifn+1, ifo do
- if path[i] then
- advtrains.detector.leave_node(path[i], id)
- end
- end
- end
- if ibn<ibo then
- for i=ibn, ibo-1 do
- if path[i] then
- local pts=minetest.pos_to_string(path[i])
- if advtrains.detector.occupied(path[i], id) then
- --if another train has signed up for this position first, it won't be recognized in train_step_b. So do collision here.
- atprint("Collision detected in enter_node callbacks (back) @",path[i],"with",sid(advtrains.detector.get(path[i], id)))
- advtrains.collide_and_spawn_couple(id, path[i], advtrains.detector.get(path[i], id), true)
- end
- atprint("enter_node (back) @index",i,"@",path[i],"on_node",sid(advtrains.detector.get(path[i], id)))
- advtrains.detector.enter_node(path[i], id)
- end
- end
- elseif ibn>ibo then
- for i=ibo, ibn-1 do
- if path[i] then
- advtrains.detector.leave_node(path[i], id)
- end
- end
- end
- end
-
--- 8. check for collisions with other trains and damage players ---
local train_moves=(train.velocity~=0)
if train_moves then
-
- --TO BE REMOVED:
- if not train.extent_h then advtrains.update_trainpart_properties(id) end
local collpos
local coll_grace=1
- if train.movedir==1 then
- collpos=advtrains.get_real_index_position(train.path, train.index-coll_grace)
- else
- collpos=advtrains.get_real_index_position(train.path, train.end_index+coll_grace)
- end
+ collpos=advtrains.path_get_index_by_offset(train, train.index-coll_grace)
if collpos then
local rcollpos=advtrains.round_vector_floor_y(collpos)
for x=-train.extent_h,train.extent_h do
for z=-train.extent_h,train.extent_h do
local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
--- 8a Check collision ---
- if advtrains.detector.occupied(testpos, id) then
+ if advtrains.occ.check_collision(testpos, id) then
--collides
- advtrains.collide_and_spawn_couple(id, testpos, advtrains.detector.get(testpos, id), train.movedir==-1)
+ --advtrains.collide_and_spawn_couple(id, testpos, advtrains.detector.get(testpos, id), train.movedir==-1)
+ train.velocity = 0
+ train.tarvelocity = 0
+ atwarn("Train",id,"collided!")
end
--- 8b damage players ---
if not minetest.settings:get_bool("creative_mode") then
@@ -601,37 +501,64 @@ function advtrains.train_step_b(id, train, dtime)
end
end
+--TODO: Collisions!
+
+
--returns new id
-function advtrains.create_new_train_at(pos, pos_prev)
- local newtrain_id=advtrains.random_id()
- while advtrains.trains[newtrain_id] do newtrain_id=advtrains.random_id() end--ensure uniqueness
+function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
+ local new_id=advtrains.random_id()
+ while advtrains.trains[new_id] do new_id=advtrains.random_id() end--ensure uniqueness
+
+ t={}
+ t.id = newtrain_id
- advtrains.trains[newtrain_id]={}
- advtrains.trains[newtrain_id].id = newtrain_id
- advtrains.trains[newtrain_id].last_pos=pos
- advtrains.trains[newtrain_id].last_pos_prev=pos_prev
- advtrains.trains[newtrain_id].tarvelocity=0
- advtrains.trains[newtrain_id].velocity=0
- advtrains.trains[newtrain_id].trainparts={}
+ t.last_pos=pos
+ t.last_connid=connid
+ t.last_frac=ioff
- advtrains.trains[newtrain_id].enter_node_all=true
+ t.tarvelocity=0
+ t.velocity=0
+ t.trainparts=trainparts
+
+
+ advtrains.trains[new_id] = t
+
+ advtrains.update_trainpart_properties(new_id)
+
+ train_ensure_clean(new_id, advtrains.trains[new_id], 0, true, 1)
return newtrain_id
end
-
-function advtrains.get_train_end_index(train)
- return advtrains.get_real_path_index(train, train.trainlen or 2)--this function can be found inside wagons.lua since it's more related to wagons. we just set trainlen as pos_in_train
+function advtrains.remove_train(id)
+ local train = advtrains.trains[id]
+
+ advtrains.update_trainpart_properties(id)
+
+ train_ensure_clean(id, train, 0, true, 2)
+
+ local tp = train.trainparts
+
+ advtrains.trains[id] = nil
+
+ return tp
+
end
+
function advtrains.add_wagon_to_train(wagon_id, train_id, index)
local train=advtrains.trains[train_id]
+
+ train_ensure_clean(train_id, train)
+
if index then
table.insert(train.trainparts, index, wagon_id)
else
table.insert(train.trainparts, wagon_id)
end
+
advtrains.update_trainpart_properties(train_id)
+ train_recalc_occupation(train_id, train)
end
-- this function sets wagon's pos_in_train(parts) properties and train's max_speed and drives_on (and more)
@@ -685,7 +612,7 @@ end
-- This function checks whether entities need to be spawned for certain wagons, and spawns them.
function advtrains.spawn_wagons(train_id)
- local train=advtrains.trains[train_id]
+ local train = advtrains.trains[train_id]
for i, w_id in ipairs(train.trainparts) do
local data = advtrains.wagons[w_id]
@@ -696,11 +623,7 @@ function advtrains.spawn_wagons(train_id)
local pos = advtrains.path_get(train, atfloor(index))
if minetest.get_node_or_nil(pos) then
- local wt = data.type
- if not minetest.registered_luaentities[wt] then
- atprint("Unable to load",w_id,"of type",wt,", using placeholder")
- wt="advtrains:wagon_placeholder"
- end
+ local wt = advtrains.get_wagon_prototype(data)
wagon=minetest.add_entity(pos, wt):get_luaentity()
wagon:set_id(w_id)
end
@@ -712,54 +635,55 @@ function advtrains.spawn_wagons(train_id)
end
-function advtrains.split_train_at_wagon(wagon)
+function advtrains.split_train_at_wagon(wagon_id)
--get train
- local train=advtrains.trains[wagon.train_id]
- if not train.path then return end
+ local data = advtrains.wagons[wagon_id]
+ local train=advtrains.trains[data.train_id]
+ local _, wagon = advtrains.get_wagon_prototype(data)
+
+ train_ensure_clean(data.train_id, train)
- local real_pos_in_train=advtrains.get_real_path_index(train, wagon.pos_in_train)
- local pos_for_new_train=train.path[math.floor(real_pos_in_train+wagon.wagon_span)]
- local pos_for_new_train_prev=train.path[math.floor(real_pos_in_train+wagon.wagon_span-1)]
+ local index=advtrains.path_get_index_by_offset(train, train.index, -(data.pos_in_train + wagon.wagon_span))
+ if index < train.path_trk_b or index > train.path_trk_f then
+ atprint("split_train: pos_for_new_train is off-track") -- TODO function for finding initial positions from a path
+ return false
+ end
+
+ local pos, _, frac = advtrains.path_get_adjacent(train, index)
+ local nconn = train.path_cn[atfloor(index)]
--before doing anything, check if both are rails. else do not allow
- if not pos_for_new_train then
+ if not pos then
atprint("split_train: pos_for_new_train not set")
return false
end
- local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(pos_for_new_train), train.drives_on)
+ local npos = advtrains.round_vector_floor_y(pos)
+ local node_ok=advtrains.get_rail_info_at(npos, train.drives_on)
if not node_ok then
atprint("split_train: pos_for_new_train ",advtrains.round_vector_floor_y(pos_for_new_train_prev)," not loaded or is not a rail")
return false
end
- if not train.last_pos_prev then
- atprint("split_train: pos_for_new_train_prev not set")
- return false
- end
-
- local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(pos_for_new_train), train.drives_on)
- if not prevnode_ok then
- atprint("split_train: pos_for_new_train_prev ", advtrains.round_vector_floor_y(pos_for_new_train_prev), " not loaded or is not a rail")
- return false
- end
-
- --create subtrain
- local newtrain_id=advtrains.create_new_train_at(pos_for_new_train, pos_for_new_train_prev)
- local newtrain=advtrains.trains[newtrain_id]
- --insert all wagons to new train
+ -- build trainparts table, passing it directly to the train constructor
+ local tp = {}
for k,v in ipairs(train.trainparts) do
if k>=wagon.pos_in_trainparts then
- table.insert(newtrain.trainparts, v)
+ table.insert(tp, v)
train.trainparts[k]=nil
end
end
+
+ --create subtrain
+ local newtrain_id=advtrains.create_new_train_at(npos, nconn, frac, tp)
+ local newtrain=advtrains.trains[newtrain_id]
+
--update train parts
- advtrains.update_trainpart_properties(wagon.train_id)--atm it still is the desierd id.
- advtrains.update_trainpart_properties(newtrain_id)
+ advtrains.update_trainpart_properties(data.train_id)--atm it still is the desierd id.
+
train.tarvelocity=0
newtrain.velocity=train.velocity
newtrain.tarvelocity=0
- newtrain.enter_node_all=true
+
newtrain.couple_lck_back=train.couple_lck_back
newtrain.couple_lck_front=false
train.couple_lck_back=false
@@ -777,6 +701,7 @@ end
--true when trains are facing each other. needed on colliding.
-- check done by iterating paths and checking their direction
--returns nil when not on the same track at all OR when required path items are not generated. this distinction may not always be needed.
+-- TODO do we need to change this behavior, since direct path accesses are now discouraged?
function advtrains.trains_facing(train1, train2)
local sr_pos=train1.path[atround(train1.index)]
local sr_pos_p=train1.path[atround(train1.index)-1]
@@ -922,6 +847,7 @@ function advtrains.do_connect_trains(first_id, second_id, player)
return true
end
+-- TODO
function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id]
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index 6abd9b7..83f919e 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -10,7 +10,7 @@
advtrains.wagons = {}
--
-function advtrains.create_wagon(type, train_id, owner)
+function advtrains.create_wagon(type, owner)
local new_id=advtrains.random_id()
while advtrains.wagons[new_id] do new_id=advtrains.random_id() end
local wgn = {}
@@ -18,7 +18,7 @@ function advtrains.create_wagon(type, train_id, owner)
wgn.seatp = {}
wgn.owner = owner
wgn.id = new_id
- wgn.train_id = train_id
+ ---wgn.train_id = train_id --- will get this via update_trainpart_properties
advtrains.wagons[new_id] = wgn
return new_id
end
@@ -39,7 +39,8 @@ local wagon={
function wagon:train()
- return advtrains.trains[self.train_id]
+ local data = advtrains.wagons[self.id]
+ return advtrains.trains[data.train_id]
end
@@ -272,7 +273,7 @@ function wagon:on_step(dtime)
local train=self:train()
--show off-track information in outside text instead of notifying the whole server about this
- if train.index < train.path_trk_b or train.index > train.path_trk_f then
+ if not train.dirty and train.end_index < train.path_trk_b or train.index > train.path_trk_f then
outside = outside .."\n!!! Train off track !!!"
end
@@ -1021,10 +1022,18 @@ function wagon:safe_decouple(pname)
end
atprint("wagon:discouple() Splitting train", self.train_id)
advtrains.log("Discouple", pname, self.object:getpos(), self:train().text_outside)
- advtrains.split_train_at_wagon(self)--found in trainlogic.lua
+ advtrains.split_train_at_wagon(self.id)--found in trainlogic.lua
return true
end
+function advtrains.get_wagon_prototype(data)
+ local wt = data.type
+ if not minetest.registered_luaentities[wt] then
+ atprint("Unable to load wagon type",wt,", using placeholder")
+ wt="advtrains:wagon_placeholder"
+ end
+ return wt, minetest.registered_luaentities[wt]
+end
function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreative)
local sysname = sysname_p
@@ -1072,11 +1081,10 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
minetest.chat_send_player(pname, "The track you are trying to place the wagon on is not long enough!")
return
end
- local id=advtrains.create_new_train_at(pointed_thing.under, plconnid)
- local wid = advtrains.create_wagon(type, id, pname)
+ local wid = advtrains.create_wagon(type, pname)
- advtrains.add_wagon_to_train(wid, id)
+ local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, {wid})
if not advtrains.is_creative(pname) then
itemstack:take_item()