Implement a reverse path lookup for trains instead of an occupations window system

This commit is contained in:
orwell96 2018-05-17 11:16:04 +02:00
parent caf2bda7bc
commit b420a71939
7 changed files with 257 additions and 291 deletions

View File

@ -140,7 +140,7 @@ If you can't enter or leave a train because the doors are closed, holding the Sn
Most modders will be satisfied with the built-in tracks. If cog railways, maglev trains and mine trains are added, it is necessary to understand the definition of tracks. Although the tracks API is there, explaining it would require more effort than me creating the wanted definitions myself. Contact me if you need to register your own rails using my registration functions. Most modders will be satisfied with the built-in tracks. If cog railways, maglev trains and mine trains are added, it is necessary to understand the definition of tracks. Although the tracks API is there, explaining it would require more effort than me creating the wanted definitions myself. Contact me if you need to register your own rails using my registration functions.
However, it is still possible to register single rails by understanding the node properties of rails. However, it is still possible to register single rails by understanding the node properties of rails.
minetest.register_node(nodename, { minetest.register_node(nodename, { -- TODO this is outdated!
... usual node definition ... ... usual node definition ...
groups = { groups = {
advtrains_track_<tracktype>=1 advtrains_track_<tracktype>=1
@ -151,27 +151,27 @@ minetest.register_node(nodename, {
connect1 = 0, connect1 = 0,
connect2 = 8, connect2 = 8,
^- These values tell the direction (horizontal) the rail ends are pointing to. 0 means +Z, then rotation values increase clockwise. For a translation of directions to positions see helpers.lua. ^- These values tell the direction (horizontal) the rail ends are pointing to. 0 means +Z, then rotation values increase clockwise. For a translation of directions to positions see helpers.lua.
rely1=0, rely1=0,
rely2=0, rely2=0,
^- the Y height of the rail end 1/2. A value of >=1 means that the rail end points to the next y layer at rely-1 ^- the Y height of the rail end 1/2. A value of >=1 means that the rail end points to the next y layer at rely-1
railheight=0, railheight=0,
^- the height value of this rail that is saved in the path. usually the median of rely1 and rely2. ^- the height value of this rail that is saved in the path. usually the median of rely1 and rely2.
can_dig=function(pos) can_dig=function(pos)
return not advtrains.get_train_at_pos(pos) return not advtrains.get_train_at_pos(pos)
end, end,
after_dig_node=function(pos) after_dig_node=function(pos)
advtrains.ndb.update(pos) advtrains.ndb.update(pos)
end, end,
after_place_node=function(pos) after_place_node=function(pos)
advtrains.ndb.update(pos) advtrains.ndb.update(pos)
end, end,
^- the code in these 3 default minetest API functions is required for advtrains to work, however you can add your own code ^- the code in these 3 default minetest API functions is required for advtrains to work, however you can add your own code
advtrains = { advtrains = {
on_train_enter=function(pos, train_id) end on_train_enter=function(pos, train_id) end
^- called when a train enters the rail ^- called when a train enters the rail
on_train_leave=function(pos, train_id) end on_train_leave=function(pos, train_id) end
^- called when a train leaves the rail ^- called when a train leaves the rail
} }
}) })

View File

@ -175,7 +175,7 @@ local matchptn={
end, end,
["B([0-9B]+)"]=function(id, train, match) ["B([0-9B]+)"]=function(id, train, match)
if match=="B" then if match=="B" then
train.atc_brake_target = -1 -- this means emergency brake. TODO don't forget to implement in train step! train.atc_brake_target = -1
train.tarvelocity = 0 train.tarvelocity = 0
elseif train.velocity>tonumber(match) then elseif train.velocity>tonumber(match) then
train.atc_brake_target=tonumber(match) train.atc_brake_target=tonumber(match)

View File

@ -109,7 +109,7 @@ end
function advtrains.dir_to_angle(dir) function advtrains.dir_to_angle(dir)
local uvec = vector.normalize(advtrains.dirToCoord(dir)) local uvec = vector.normalize(advtrains.dirToCoord(dir))
return math.atan2(uvec.z, uvec.x) return math.atan2(uvec.x, uvec.z)
end end
local pi, pi2 = math.pi, 2*math.pi local pi, pi2 = math.pi, 2*math.pi
@ -153,8 +153,6 @@ function advtrains.conn_angle_median(cdir1, cdir2)
return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2 return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
end end
-- TODO removed dumppath, where is this used?
function advtrains.merge_tables(a, ...) function advtrains.merge_tables(a, ...)
local new={} local new={}
for _,t in ipairs({a,...}) do for _,t in ipairs({a,...}) do
@ -170,8 +168,6 @@ function advtrains.save_keys(tbl, keys)
return new return new
end end
-- TODO yaw_from_3_positions and get_wagon_yaw removed
function advtrains.get_real_index_position(path, index) function advtrains.get_real_index_position(path, index)
if not path or not index then return end if not path or not index then return end
@ -258,7 +254,7 @@ function advtrains.rotate_conn_by(conn, rotate)
return tmp return tmp
end end
--TODO use this
function advtrains.oppd(dir) function advtrains.oppd(dir)
return advtrains.rotate_conn_by(dir, AT_CMAX/2) return advtrains.rotate_conn_by(dir, AT_CMAX/2)
end end

View File

@ -1,6 +1,7 @@
-- occupation.lua -- occupation.lua
--[[ --[[
Collects and manages positions where trains occupy and/or reserve/require space Collects and manages positions where trains occupy and/or reserve/require space
THIS SECTION ABOVE IS OUTDATED, look below
Zone diagram of a train: Zone diagram of a train:
|___| |___| --> Direction of travel |___| |___| --> Direction of travel
@ -41,41 +42,46 @@ occ_chg[n] = {
new_val, (0 when entry was deleted) new_val, (0 when entry was deleted)
} }
*Sequence number: ---------------------
Sequence number system reserved for possible future use, but unused. It turned out that, especially for the TSS, some more, even overlapping zones are required.
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 Packing those into a data structure would just become a huge mess!
in the movement phase (after restore, but before reporting occupations) Instead, this occupation system will store the path indices of positions in the corresponding.
(( train's paths.
The sequence number is used to determine out-of-date entries to the occupation list So, the occupation is a reverse lookup of paths.
The current sequence number (seqnum) is increased each step, until it rolls over MAX_SEQNUM, which is when a complete reset is triggered Then, a callback system will handle changes in those indices, as follows:
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: Whenever the train generates new path items (path_get/path_create), their counterpart indices will be filled in here.
Whenever a path gets invalidated or path items are deleted, their index counterpart is erased from here.
1. (only when needed) restore step - write all current occupations into the table When a train needs to know whether a position is blocked by another train, it will (and is permitted to)
2. trains move query the train.index and train.end_index and compare them to the blocked position's index.
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 Callback system for 3rd-party path checkers: TODO
advtrains.te_register_on_new_path(func(id, train))
-- Called when a train's path is re-initalized, either when it was invalidated
-- or the saves were just loaded
-- It can be assumed that everything is in the state of when the last run
-- of on_update was made, but all indices are shifted by an unknown amount.
advtrains.te_register_on_update(func(id, train))
-- Called each step and after a train moved, its length changed or some other event occured
-- The path is unmodified, and train.index and train.end_index can be reliably
-- queried for the new position and length of the train.
-- note that this function might be called multiple times per step, and this
-- function being called does not necessarily mean that something has changed.
-- It is ensured that on_new_path callbacks are executed prior to these callbacks whenever
-- an invalidation or a reload occured.
All callbacks are allowed to save certain values inside the train table, but they must ensure that
those are reinitialized in the on_new_path callback. The on_new_path callback must explicitly
set ALL OF those values to nil or to a new updated value, and must not rely on their existence.
]]-- ]]--
local o = {} local o = {}
o.restore_required = true
local MAX_SEQNUM = 65500
local seqnum = 0
local occ = {} local occ = {}
local occ_chg = {} local occ_chg = {}
local addchg, handle_chg
local function occget(p) local function occget(p)
local t = occ[p.y] local t = occ[p.y]
@ -112,16 +118,8 @@ local function occgetcreate(p)
return t return t
end 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.set_item(train_id, pos, idx)
function o.init_occupation(train_id, pos, oid)
local t = occgetcreate(pos) local t = occgetcreate(pos)
local i = 1 local i = 1
while t[i] do while t[i] do
@ -131,28 +129,11 @@ function o.init_occupation(train_id, pos, oid)
i = i + 2 i = i + 2
end end
t[i] = train_id t[i] = train_id
t[i+1] = oid t[i+1] = idx
end
function o.set_occupation(train_id, pos, oid)
local t = occgetcreate(pos)
local i = 1
while t[i] do
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 end
function o.clear_occupation(train_id, pos) function o.clear_item(train_id, pos)
local t = occget(pos) local t = occget(pos)
if not t then return end if not t then return end
local i = 1 local i = 1
@ -176,94 +157,6 @@ function o.clear_occupation(train_id, pos)
end end
end end
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
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
advtrains.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
advtrains.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 -- Checks whether some other train (apart from train_id) has it's 0 zone here
function o.check_collision(pos, train_id) function o.check_collision(pos, train_id)
local npos = advtrains.round_vector_floor_y(pos) local npos = advtrains.round_vector_floor_y(pos)
@ -272,7 +165,10 @@ function o.check_collision(pos, train_id)
local i = 1 local i = 1
while t[i] do while t[i] do
if t[i]~=train_id then if t[i]~=train_id then
if t[i+1] ~= 7 then local idx = t[i+1]
local train = advtrains.trains[train_id]
advtrains.train_ensure_clean(train_id, train)
if idx >= train.end_index and idx <= train.index then
return true return true
end end
end end

View File

@ -123,12 +123,58 @@ function advtrains.path_create(train, pos, connid, rel_index)
train.path_req_f=0 train.path_req_f=0
train.path_req_b=0 train.path_req_b=0
advtrains.occ.set_item(train.id, posr, 0)
end
-- Sets position and connid to properly restore after a crash, e.g. in order
-- to save the train or to invalidate its path
-- Assumes that the train is in clean state
-- if invert ist true, setrestore will use the end index
function advtrains.path_setrestore(train, invert)
local idx = train.index
if invert then
idx = train.end_index
end
local pos, connid, frac = advtrains.path_getrestore(train, idx, invert)
train.last_pos = pos
train.last_connid = connid
train.last_frac = frac
end
-- Get restore position, connid and frac (in this order) for a train that will originate at the passed index
-- If invert is set, it will return path_cp and multiply frac by -1, in order to reverse the train there.
function advtrains.path_getrestore(train, index, invert)
local idx = train.index
local cns = train.path_cn
if invert then
idx = train.end_index
cns = train.path_cp
end
fli = atfloor(train.index)
if fli > train.path_trk_f then
fli = train.path_trk_f
end
if fli < train.path_trk_b then
fli = train.path_trk_b
end
return advtrains.path_get(train, fli),
cns[fli],
(idx - fli) * (invert and -1 or 1)
end end
-- Invalidates a path -- Invalidates a path
-- TODO: this is supposed to clear stuff from the occupation tables -- this is supposed to clear stuff from the occupation tables
-- (note: why didn't I think of that earlier?)
function advtrains.path_invalidate(train) function advtrains.path_invalidate(train)
if train.path then
for i,p in pairs(train.path) do
advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(p))
end
end
train.path = nil train.path = nil
train.path_dist = nil train.path_dist = nil
train.path_cp = nil train.path_cp = nil
@ -172,6 +218,8 @@ function advtrains.path_get(train, index)
end end
pef = pef + 1 pef = pef + 1
if adj_pos then if adj_pos then
advtrains.occ.set_item(train.id, adj_pos, pef)
adj_pos.y = adj_pos.y + nextrail_y adj_pos.y = adj_pos.y + nextrail_y
train.path_cp[pef] = adj_connid train.path_cp[pef] = adj_connid
local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns) local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
@ -199,6 +247,8 @@ function advtrains.path_get(train, index)
end end
peb = peb - 1 peb = peb - 1
if adj_pos then if adj_pos then
advtrains.occ.set_item(train.id, adj_pos, peb)
adj_pos.y = adj_pos.y + nextrail_y adj_pos.y = adj_pos.y + nextrail_y
train.path_cn[peb] = adj_connid train.path_cn[peb] = adj_connid
local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns) local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
@ -207,7 +257,7 @@ function advtrains.path_get(train, index)
train.path_trk_b = peb train.path_trk_b = peb
else else
-- off-track fallback behavior -- off-track fallback behavior
adj_pos = advtrains.pos_add_angle(pos, train.path_dir[peb+1]) adj_pos = advtrains.pos_add_angle(pos, train.path_dir[peb+1] + math.pi)
train.path_dir[peb] = train.path_dir[peb+1] train.path_dir[peb] = train.path_dir[peb+1]
end end
train.path[peb] = adj_pos train.path[peb] = adj_pos
@ -241,7 +291,7 @@ function advtrains.path_get_interpolated(train, index)
local ang = advtrains.minAngleDiffRad(a_floor, a_ceil) local ang = advtrains.minAngleDiffRad(a_floor, a_ceil)
return vector.add(p_floor, vector.multiply(vector.subtract(p_ceil, p_floor), frac)), (a_floor + frac * ang)%(2*math.pi), p_floor, p_ceil -- TODO does this behave correctly? return vector.add(p_floor, vector.multiply(vector.subtract(p_ceil, p_floor), frac)), (a_floor + frac * ang)%(2*math.pi), p_floor, p_ceil
end end
-- returns the 2 path positions directly adjacent to index and the fraction on how to interpolate between them -- returns the 2 path positions directly adjacent to index and the fraction on how to interpolate between them
-- returns: pos_floor, pos_ceil, fraction -- returns: pos_floor, pos_ceil, fraction
@ -293,6 +343,7 @@ local PATH_CLEAR_KEEP = 4
function advtrains.path_clear_unused(train) function advtrains.path_clear_unused(train)
local i local i
for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do
advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
train.path[i] = nil train.path[i] = nil
train.path_dist[i-1] = nil train.path_dist[i-1] = nil
train.path_cp[i] = nil train.path_cp[i] = nil
@ -302,6 +353,7 @@ function advtrains.path_clear_unused(train)
end end
for i = train.path_ext_f,train.path_req_f + PATH_CLEAR_KEEP,-1 do for i = train.path_ext_f,train.path_req_f + PATH_CLEAR_KEEP,-1 do
advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
train.path[i] = nil train.path[i] = nil
train.path_dist[i] = nil train.path_dist[i] = nil
train.path_cp[i] = nil train.path_cp[i] = nil

View File

@ -70,7 +70,7 @@ advtrains.mainloop_trainlogic=function(dtime)
for k,v in pairs(advtrains.trains) do for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k) advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k advtrains.atprint_context_tid_full=k
advtrains.train_ensure_clean(k, v, dtime, advtrains.occ.restore_required) advtrains.train_ensure_clean(k, v)
end end
for k,v in pairs(advtrains.trains) do for k,v in pairs(advtrains.trains) do
@ -135,12 +135,6 @@ minetest.register_on_dieplayer(function(player)
end) end)
--[[ --[[
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!): Zone diagram of a train (copy from occupation.lua!):
|___| |___| --> Direction of travel |___| |___| --> Direction of travel
@ -150,10 +144,10 @@ Zone diagram of a train (copy from occupation.lua!):
[1] [2] [3] [4] [5] [6] [7] [8] [1] [2] [3] [4] [5] [6] [7] [8]
This mapping from indices in occwindows to zone ids is contained in WINDOW_ZONE_IDS 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, The occupation system has been abandoned. The constants will still be used
where floor(i) belongs to the left zone and floor(i+1) belongs to the right. to determine the couple distance
} (because of the reverse lookup, the couple system simplifies a lot...)
]]-- ]]--
-- unless otherwise stated, in meters. -- unless otherwise stated, in meters.
@ -181,6 +175,7 @@ end
-- Calculates the indices where the window borders of the occupation windows are. -- Calculates the indices where the window borders of the occupation windows are.
-- TODO adapt this code to new system, probably into a callback
local function calc_occwindows(id, train) local function calc_occwindows(id, train)
local end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen) local end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
train.end_index = end_index train.end_index = end_index
@ -212,45 +207,50 @@ local function calc_occwindows(id, train)
} }
end end
-- this function either inits (no write_mode), sets(1) or clears(2) the occupations for train -- Small local util function to recalculate train's end index
local function write_occupation(win, train_id, train, write_mode) local function recalc_end_index(train)
local n_window = 2 train.end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
local c_index = math.ceil(win[1])
while win[n_window] do
local winix = win[n_window]
local oid = WINDOW_ZONE_IDS[n_window - 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
end end
local function apply_occupation_changes(old, new, train_id, train)
-- TODO -- Occupation Callback system
-- see occupation.lua
local callbacks_new_path = {}
local callbacks_update = {}
function advtrains.tb_register_on_new_path(func)
assertt(func, "function")
table.insert(callbacks_new_path, func)
end
function advtrains.tb_register_on_update(func)
assertt(func, "function")
table.insert(callbacks_update, func)
end
local function run_callbacks_new_path(id, train)
for _,f in ipairs(callbacks_new_path) do
f(id, train)
end
end
local function run_callbacks_update(id, train)
for _,f in ipairs(callbacks_new_path) do
f(id, train)
end
end end
-- train_ensure_clean: responsible for creating a state that we can work on, after one of the following events has happened: -- 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 train's path got cleared
-- - the occupation table got cleared -- - save files were loaded
-- Additionally, this gets called outside the step cycle to initialize and/or remove a train, then occ_write_mode is set. -- Additionally, this gets called outside the step cycle to initialize and/or remove a train, then occ_write_mode is set.
function advtrains.train_ensure_clean(id, train, dtime, report_occupations, occ_write_mode) function advtrains.train_ensure_clean(id, train)
train.dirty = true train.dirty = true
if train.no_step then return end if train.no_step then return end
assertdef(train, "velocity", 0) assertdef(train, "velocity", 0)
assertdef(train, "tarvelocity", 0) assertdef(train, "tarvelocity", 0)
assertdef(train, "acceleration", 0) assertdef(train, "acceleration", 0)
assertdef(train, "id", id)
if not train.drives_on or not train.max_speed then if not train.drives_on or not train.max_speed then
@ -282,18 +282,14 @@ function advtrains.train_ensure_clean(id, train, dtime, report_occupations, occ_
end end
-- by now, we should have a working initial path -- by now, we should have a working initial path
train.wait_for_path = false train.wait_for_path = false
train.occwindows = nil
advtrains.update_trainpart_properties(id) advtrains.update_trainpart_properties(id)
recalc_end_index(train)
atdebug("Train",id,": Successfully restored path at",train.last_pos," connid",train.last_connid," frac",train.last_frac) atdebug("Train",id,": Successfully restored path at",train.last_pos," connid",train.last_connid," frac",train.last_frac)
-- TODO recoverposition?!
end -- run on_new_path callbacks
run_callbacks_new_path(id, train)
--restore occupation windows
if not train.occwindows then
train.occwindows = calc_occwindows(id, train)
end
if report_occupations then
write_occupation(train.occwindows, id, train, occ_write_mode)
end end
train.dirty = false -- TODO einbauen! train.dirty = false -- TODO einbauen!
@ -346,8 +342,15 @@ function advtrains.train_step_b(id, train, dtime)
if train.active_control then if train.active_control then
advtrains.atc.train_reset_command(id) advtrains.atc.train_reset_command(id)
else else
if train.atc_brake_target and train.atc_brake_target>=trainvelocity then local braketar = train.atc_brake_target
local emerg = false -- atc_brake_target==-1 means emergency brake (BB command)
if braketar == -1 then
braketar = 0
emerg = true
end
if braketar and braketar>=trainvelocity then
train.atc_brake_target=nil train.atc_brake_target=nil
braketar = nil
end end
if train.atc_wait_finish then if train.atc_wait_finish then
if not train.atc_brake_target and train.velocity==train.tarvelocity then if not train.atc_brake_target and train.velocity==train.tarvelocity then
@ -365,8 +368,12 @@ function advtrains.train_step_b(id, train, dtime)
train.lever = 3 train.lever = 3
if train.tarvelocity>trainvelocity then train.lever=4 end if train.tarvelocity>trainvelocity then train.lever=4 end
if train.tarvelocity<trainvelocity then if train.tarvelocity<trainvelocity then
if (train.atc_brake_target and train.atc_brake_target<trainvelocity) then if (braketar and braketar<trainvelocity) then
train.lever=1 if emerg then
train.lever = 0
else
train.lever=1
end
else else
train.lever=2 train.lever=2
end end
@ -417,30 +424,20 @@ function advtrains.train_step_b(id, train, dtime)
--- 4. move train --- --- 4. move train ---
train.index=train.index and train.index+((train.velocity/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0 train.index=train.index and train.index+((train.velocity/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
recalc_end_index()
end end
local function train_recalc_occupation(id, train)
local new_occwindows = calc_occwindows(id, train)
apply_occupation_changes(train.occwindows, new_occwindows, id)
train.occwindows = new_occwindows
end
function advtrains.train_step_c(id, train, dtime) function advtrains.train_step_c(id, train, dtime)
if train.no_step or train.wait_for_path then return end if train.no_step or train.wait_for_path then return end
-- all location/extent-critical actions have been done. -- all location/extent-critical actions have been done.
-- calculate the new occupation window -- calculate the new occupation window
train_recalc_occupation(id, train) run_callbacks_update(id, train)
advtrains.path_clear_unused(train) advtrains.path_clear_unused(train)
-- Set our path restoration position advtrains.path_setrestore(train)
-- TODO make a common function to find a restore positionon the path, in case the wanted position is off-track
local fli = atfloor(train.index)
train.last_pos = advtrains.path_get(train, fli)
train.last_connid = train.path_cn[fli]
train.last_frac = train.index - fli
-- less important stuff -- less important stuff
@ -507,6 +504,52 @@ if train.no_step or train.wait_for_path then return end
end end
end end
-- Default occupation callbacks for node callbacks
-- (remember, train.end_index is set separately because callbacks are
-- asserted to rely on this)
local function tnc_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
local function tnc_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
advtrains.te_register_on_new_path(function(id, train)
train.tnc = {
old_index = atround(train.index)
old_end_index = atround(train.end_index)
}
end)
advtrains.te_register_on_update(function(id, train)
local new_index = atround(train.index)
local new_end_index = atround(train.end_index)
local old_index = train.tnc.old_index
local old_end_index = train.tnc.old_end_index
while old_index < new_index do
old_index = old_index + 1
local pos = advtrains.round_vector_floor_y(advtrains.path_get(old_index))
tnc_call_enter_callback(pos, id)
end
while old_end_index > new_end_index do
local pos = advtrains.round_vector_floor_y(advtrains.path_get(old_end_index))
tnc_call_leave_callback(pos, id)
old_end_index = old_end_index - 1
end
end)
--TODO: Collisions! --TODO: Collisions!
@ -529,9 +572,8 @@ function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
advtrains.trains[new_id] = t advtrains.trains[new_id] = t
atdebug("Created new train:",t) atdebug("Created new train:",t)
advtrains.update_trainpart_properties(new_id)
advtrains.train_ensure_clean(new_id, advtrains.trains[new_id], 0, true, 1) advtrains.train_ensure_clean(new_id, advtrains.trains[new_id])
return new_id return new_id
end end
@ -541,9 +583,7 @@ function advtrains.remove_train(id)
advtrains.train_ensure_clean(id, train) advtrains.train_ensure_clean(id, train)
advtrains.update_trainpart_properties(id) advtrains.path_invalidate(train)
advtrains.train_ensure_clean(id, train, 0, true, 2)
local tp = train.trainparts local tp = train.trainparts
@ -566,7 +606,8 @@ function advtrains.add_wagon_to_train(wagon_id, train_id, index)
end end
advtrains.update_trainpart_properties(train_id) advtrains.update_trainpart_properties(train_id)
train_recalc_occupation(train_id, train) recalc_end_index(train)
run_callbacks_update(train_id, train)
end end
-- this function sets wagon's pos_in_train(parts) properties and train's max_speed and drives_on (and more) -- this function sets wagon's pos_in_train(parts) properties and train's max_speed and drives_on (and more)
@ -646,31 +687,17 @@ end
function advtrains.split_train_at_wagon(wagon_id) function advtrains.split_train_at_wagon(wagon_id)
--get train --get train
local data = advtrains.wagons[wagon_id] local data = advtrains.wagons[wagon_id]
local train=advtrains.trains[data.train_id] local old_id = data.train_id
local train=advtrains.trains[old_id]
local _, wagon = advtrains.get_wagon_prototype(data) local _, wagon = advtrains.get_wagon_prototype(data)
advtrains.train_ensure_clean(data.train_id, train) advtrains.train_ensure_clean(old_id, train)
local index=advtrains.path_get_index_by_offset(train, train.index, -(data.pos_in_train + wagon.wagon_span)) 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 -- find new initial path position for this train
atprint("split_train: pos_for_new_train is off-track") -- TODO function for finding initial positions from a path local pos, connid, frac = advtrains.path_getrestore(train, index)
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 then
atprint("split_train: pos_for_new_train not set")
return false
end
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
-- build trainparts table, passing it directly to the train constructor -- build trainparts table, passing it directly to the train constructor
local tp = {} local tp = {}
@ -682,12 +709,9 @@ function advtrains.split_train_at_wagon(wagon_id)
end end
--create subtrain --create subtrain
local newtrain_id=advtrains.create_new_train_at(npos, nconn, frac, tp) local newtrain_id=advtrains.create_new_train_at(pos, connid, frac, tp)
local newtrain=advtrains.trains[newtrain_id] local newtrain=advtrains.trains[newtrain_id]
--update train parts
advtrains.update_trainpart_properties(data.train_id)--atm it still is the desierd id.
train.tarvelocity=0 train.tarvelocity=0
newtrain.velocity=train.velocity newtrain.velocity=train.velocity
newtrain.tarvelocity=0 newtrain.tarvelocity=0
@ -696,6 +720,11 @@ function advtrains.split_train_at_wagon(wagon_id)
newtrain.couple_lck_front=false newtrain.couple_lck_front=false
train.couple_lck_back=false train.couple_lck_back=false
--update train parts
advtrains.update_trainpart_properties(old_id)
recalc_end_index(train)
run_callbacks_update(old_id, train)
end end
--there are 4 cases: --there are 4 cases:
@ -709,7 +738,7 @@ end
--true when trains are facing each other. needed on colliding. --true when trains are facing each other. needed on colliding.
-- check done by iterating paths and checking their direction -- 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. --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? -- TODO Will be changed when implementing coupling.
function advtrains.trains_facing(train1, train2) function advtrains.trains_facing(train1, train2)
local sr_pos=train1.path[atround(train1.index)] local sr_pos=train1.path[atround(train1.index)]
local sr_pos_p=train1.path[atround(train1.index)-1] local sr_pos_p=train1.path[atround(train1.index)-1]
@ -858,12 +887,9 @@ end
function advtrains.invert_train(train_id) function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id] local train=advtrains.trains[train_id]
advtrains.train_ensure_clean(train_id, train, 0) advtrains.train_ensure_clean(train_id, train)
-- Set the path restoration position to the opposite direction
local fli = atfloor(train.end_index) + 1 advtrains.path_setrestore(train, true)
train.last_pos = advtrains.path_get(train, fli)
train.last_connid = train.path_cp[fli]
train.last_frac = fli - train.end_index
-- rotate some other stuff -- rotate some other stuff
train.couple_lck_back, train.couple_lck_front = train.couple_lck_front, train.couple_lck_back train.couple_lck_back, train.couple_lck_front = train.couple_lck_front, train.couple_lck_back

View File

@ -196,8 +196,6 @@ function wagon:destroy()
self.object:remove() self.object:remove()
end end
local pihalf = math.pi/2
function wagon:on_step(dtime) function wagon:on_step(dtime)
return advtrains.pcall(function() return advtrains.pcall(function()
if not self:ensure_init() then return end if not self:ensure_init() then return end
@ -296,12 +294,12 @@ function wagon:on_step(dtime)
local fct=data.wagon_flipped and -1 or 1 local fct=data.wagon_flipped and -1 or 1
--set line number --set line number
if self.name == "advtrains:subway_wagon" and train.line and train.line~=self.line_cache then if self.name == "advtrains:subway_wagon" and train.line and train.line~=self.line_cache then
local new_line_tex="advtrains_subway_wagon.png^advtrains_subway_wagon_line"..gp.line..".png" local new_line_tex="advtrains_subway_wagon.png^advtrains_subway_wagon_line"..train.line..".png"
self.object:set_properties({ self.object:set_properties({
textures={new_line_tex}, textures={new_line_tex},
}) })
self.line_cache=train.line self.line_cache=train.line
elseif self.line_cache~=nil and gp.line==nil then elseif self.line_cache~=nil and train.line==nil then
self.object:set_properties({ self.object:set_properties({
textures=self.textures, textures=self.textures,
}) })
@ -366,8 +364,9 @@ function wagon:on_step(dtime)
end end
end end
--for path to be available. if not, skip step --for path to be available. if not, skip step
if not train.path then if not train.path or train.no_step then
self.object:setvelocity({x=0, y=0, z=0}) self.object:setvelocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=0, z=0})
return return
end end
if not data.pos_in_train then if not data.pos_in_train then
@ -376,11 +375,9 @@ function wagon:on_step(dtime)
-- Calculate new position, yaw and direction vector -- Calculate new position, yaw and direction vector
local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train) local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
local pos, tyaw, npos, npos2 = advtrains.path_get_interpolated(train, index) local pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
local vdir = vector.normalize(vector.subtract(npos2, npos)) local vdir = vector.normalize(vector.subtract(npos2, npos))
local yaw = tyaw - pihalf
--automatic get_on --automatic get_on
--needs to know index and path --needs to know index and path
if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
@ -391,7 +388,7 @@ function wagon:on_step(dtime)
local ix1, ix2 = advtrains.path_get_adjacent(train, aci) local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg) -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141) -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
local add = { x = (ix2.z-ix1.z)*gp.door_open, y = 0, z = (ix1.x-ix2.x)*gp.door_open } local add = { x = (ix2.z-ix1.z)*train.door_open, y = 0, z = (ix1.x-ix2.x)*train.door_open }
local pts1=vector.round(vector.add(ix1, add)) local pts1=vector.round(vector.add(ix1, add))
local pts2=vector.round(vector.add(ix2, add)) local pts2=vector.round(vector.add(ix2, add))
if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then
@ -420,10 +417,9 @@ function wagon:on_step(dtime)
for y=0,exv do for y=0,exv do
for z=-exh,exh do for z=-exh,exh do
local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z})) local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z}))
-- TODO if (advtrains.train_collides(node)) then
--if (advtrains.train_collides(node)) then collides=true
-- collides=true end
--end
end end
end end
end end
@ -431,13 +427,13 @@ function wagon:on_step(dtime)
if self.collision_count and self.collision_count>10 then if self.collision_count and self.collision_count>10 then
--enable collision mercy to get trains stuck in walls out of walls --enable collision mercy to get trains stuck in walls out of walls
--actually do nothing except limiting the velocity to 1 --actually do nothing except limiting the velocity to 1
gp.velocity=math.min(gp.velocity, 1) train.velocity=math.min(train.velocity, 1)
gp.tarvelocity=math.min(gp.tarvelocity, 1) train.tarvelocity=math.min(train.tarvelocity, 1)
else else
gp.recently_collided_with_env=true train.recently_collided_with_env=true
gp.velocity=2*gp.velocity train.velocity=0
gp.movedir=-gp.movedir -- TODO what should happen when a train collides?
gp.tarvelocity=0 train.tarvelocity=0
self.collision_count=(self.collision_count or 0)+1 self.collision_count=(self.collision_count or 0)+1
end end
else else
@ -481,7 +477,7 @@ function wagon:on_step(dtime)
self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
end end
-- set player looking direction using calculated offset -- set player looking direction using calculated offset
--TODO p:set_look_horizontal((self.player_yaw[name] or 0)+yaw) p:set_look_horizontal((self.player_yaw[name] or 0)+yaw)
end end
end end
self.turning = true self.turning = true
@ -493,7 +489,7 @@ function wagon:on_step(dtime)
self.object:setyaw(yaw) self.object:setyaw(yaw)
self.updatepct_timer=2 self.updatepct_timer=2
if self.update_animation then if self.update_animation then
self:update_animation(gp.velocity, self.old_velocity) self:update_animation(train.velocity, self.old_velocity)
end end
if self.custom_on_velocity_change then if self.custom_on_velocity_change then
self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime) self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime)
@ -664,13 +660,13 @@ function wagon:get_off(seatno)
if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 and train.index and train.path then if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 and train.index and train.path then
local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train) local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
for i, ino in ipairs(self.door_entry) do for i, ino in ipairs(self.door_entry) do
--fct is the flipstate flag from door animation above local fct=data.wagon_flipped and -1 or 1
local aci = advtrains.path_get_index_by_offset(train, index, ino*fct) local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)
local ix1, ix2 = advtrains.path_get_adjacent(train, aci) local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg) -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141) -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
local add = { x = (ix2.z-ix1.z)*gp.door_open, y = 0, z = (ix1.x-ix2.x)*gp.door_open } local add = { x = (ix2.z-ix1.z)*train.door_open, y = 0, z = (ix1.x-ix2.x)*train.door_open }
local oadd = { x = (ix2.z-ix1.z)*gp.door_open*2, y = 1, z = (ix1.x-ix2.x)*gp.door_open*2} local oadd = { x = (ix2.z-ix1.z)*train.door_open*2, y = 1, z = (ix1.x-ix2.x)*train.door_open*2}
local platpos=vector.round(vector.add(ix1, add)) local platpos=vector.round(vector.add(ix1, add))
local offpos=vector.round(vector.add(ix1, oadd)) local offpos=vector.round(vector.add(ix1, oadd))
atprint("platpos:", platpos, "offpos:", offpos) atprint("platpos:", platpos, "offpos:", offpos)