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.
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 ...
groups = {
advtrains_track_<tracktype>=1
@ -151,27 +151,27 @@ minetest.register_node(nodename, {
connect1 = 0,
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.
rely1=0,
rely1=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,
^- 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.get_train_at_pos(pos)
end,
after_dig_node=function(pos)
advtrains.ndb.update(pos)
end,
after_place_node=function(pos)
advtrains.ndb.update(pos)
can_dig=function(pos)
return not advtrains.get_train_at_pos(pos)
end,
after_dig_node=function(pos)
advtrains.ndb.update(pos)
end,
after_place_node=function(pos)
advtrains.ndb.update(pos)
end,
^- 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
^- called when a train enters the rail
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,
["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.atc_brake_target = -1
train.tarvelocity = 0
elseif train.velocity>tonumber(match) then
train.atc_brake_target=tonumber(match)

View File

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

View File

@ -1,6 +1,7 @@
-- occupation.lua
--[[
Collects and manages positions where trains occupy and/or reserve/require space
THIS SECTION ABOVE IS OUTDATED, look below
Zone diagram of a train:
|___| |___| --> Direction of travel
@ -41,41 +42,46 @@ occ_chg[n] = {
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...
))
---------------------
It turned out that, especially for the TSS, some more, even overlapping zones are required.
Packing those into a data structure would just become a huge mess!
Instead, this occupation system will store the path indices of positions in the corresponding.
train's paths.
So, the occupation is a reverse lookup of paths.
Then, a callback system will handle changes in those indices, as follows:
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
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
When a train needs to know whether a position is blocked by another train, it will (and is permitted to)
query the train.index and train.end_index and compare them to the blocked position's index.
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 = {}
o.restore_required = true
local MAX_SEQNUM = 65500
local seqnum = 0
local occ = {}
local occ_chg = {}
local addchg, handle_chg
local function occget(p)
local t = occ[p.y]
@ -112,16 +118,8 @@ local function occgetcreate(p)
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)
function o.set_item(train_id, pos, idx)
local t = occgetcreate(pos)
local i = 1
while t[i] do
@ -131,28 +129,11 @@ function o.init_occupation(train_id, pos, oid)
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] 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
t[i+1] = idx
end
function o.clear_occupation(train_id, pos)
function o.clear_item(train_id, pos)
local t = occget(pos)
if not t then return end
local i = 1
@ -176,94 +157,6 @@ function o.clear_occupation(train_id, pos)
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
function o.check_collision(pos, train_id)
local npos = advtrains.round_vector_floor_y(pos)
@ -272,7 +165,10 @@ function o.check_collision(pos, train_id)
local i = 1
while t[i] do
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
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_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
-- Invalidates a path
-- TODO: this is supposed to clear stuff from the occupation tables
-- (note: why didn't I think of that earlier?)
-- this is supposed to clear stuff from the occupation tables
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_dist = nil
train.path_cp = nil
@ -172,6 +218,8 @@ function advtrains.path_get(train, index)
end
pef = pef + 1
if adj_pos then
advtrains.occ.set_item(train.id, adj_pos, pef)
adj_pos.y = adj_pos.y + nextrail_y
train.path_cp[pef] = adj_connid
local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
@ -199,6 +247,8 @@ function advtrains.path_get(train, index)
end
peb = peb - 1
if adj_pos then
advtrains.occ.set_item(train.id, adj_pos, peb)
adj_pos.y = adj_pos.y + nextrail_y
train.path_cn[peb] = adj_connid
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
else
-- 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]
end
train.path[peb] = adj_pos
@ -241,7 +291,7 @@ function advtrains.path_get_interpolated(train, index)
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
-- returns the 2 path positions directly adjacent to index and the fraction on how to interpolate between them
-- returns: pos_floor, pos_ceil, fraction
@ -293,6 +343,7 @@ local PATH_CLEAR_KEEP = 4
function advtrains.path_clear_unused(train)
local i
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_dist[i-1] = nil
train.path_cp[i] = nil
@ -302,6 +353,7 @@ function advtrains.path_clear_unused(train)
end
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_dist[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
advtrains.atprint_context_tid=sid(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
for k,v in pairs(advtrains.trains) do
@ -135,12 +135,6 @@ minetest.register_on_dieplayer(function(player)
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!):
|___| |___| --> Direction of travel
@ -150,10 +144,10 @@ Zone diagram of a train (copy from occupation.lua!):
[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.
}
The occupation system has been abandoned. The constants will still be used
to determine the couple distance
(because of the reverse lookup, the couple system simplifies a lot...)
]]--
-- unless otherwise stated, in meters.
@ -181,6 +175,7 @@ end
-- 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 end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
train.end_index = end_index
@ -212,45 +207,50 @@ local function calc_occwindows(id, train)
}
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_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
-- Small local util function to recalculate train's end index
local function recalc_end_index(train)
train.end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
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
-- 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
-- - 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.
function advtrains.train_ensure_clean(id, train, dtime, report_occupations, occ_write_mode)
function advtrains.train_ensure_clean(id, train)
train.dirty = true
if train.no_step then return end
assertdef(train, "velocity", 0)
assertdef(train, "tarvelocity", 0)
assertdef(train, "acceleration", 0)
assertdef(train, "id", id)
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
-- by now, we should have a working initial path
train.wait_for_path = false
train.occwindows = nil
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)
-- TODO recoverposition?!
end
--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)
-- run on_new_path callbacks
run_callbacks_new_path(id, train)
end
train.dirty = false -- TODO einbauen!
@ -346,8 +342,15 @@ function advtrains.train_step_b(id, train, dtime)
if train.active_control then
advtrains.atc.train_reset_command(id)
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
braketar = nil
end
if train.atc_wait_finish 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
if train.tarvelocity>trainvelocity then train.lever=4 end
if train.tarvelocity<trainvelocity then
if (train.atc_brake_target and train.atc_brake_target<trainvelocity) then
train.lever=1
if (braketar and braketar<trainvelocity) then
if emerg then
train.lever = 0
else
train.lever=1
end
else
train.lever=2
end
@ -417,30 +424,20 @@ function advtrains.train_step_b(id, train, dtime)
--- 4. move train ---
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
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)
if train.no_step or train.wait_for_path then return end
-- all location/extent-critical actions have been done.
-- calculate the new occupation window
train_recalc_occupation(id, train)
run_callbacks_update(id, train)
advtrains.path_clear_unused(train)
-- Set our path restoration position
-- 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
advtrains.path_setrestore(train)
-- less important stuff
@ -507,6 +504,52 @@ if train.no_step or train.wait_for_path then return 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!
@ -529,9 +572,8 @@ function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
advtrains.trains[new_id] = 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
end
@ -541,9 +583,7 @@ function advtrains.remove_train(id)
advtrains.train_ensure_clean(id, train)
advtrains.update_trainpart_properties(id)
advtrains.train_ensure_clean(id, train, 0, true, 2)
advtrains.path_invalidate(train)
local tp = train.trainparts
@ -566,7 +606,8 @@ function advtrains.add_wagon_to_train(wagon_id, train_id, index)
end
advtrains.update_trainpart_properties(train_id)
train_recalc_occupation(train_id, train)
recalc_end_index(train)
run_callbacks_update(train_id, train)
end
-- 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)
--get train
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)
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))
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 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
-- find new initial path position for this train
local pos, connid, frac = advtrains.path_getrestore(train, index)
-- build trainparts table, passing it directly to the train constructor
local tp = {}
@ -682,12 +709,9 @@ function advtrains.split_train_at_wagon(wagon_id)
end
--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]
--update train parts
advtrains.update_trainpart_properties(data.train_id)--atm it still is the desierd id.
train.tarvelocity=0
newtrain.velocity=train.velocity
newtrain.tarvelocity=0
@ -696,6 +720,11 @@ function advtrains.split_train_at_wagon(wagon_id)
newtrain.couple_lck_front=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
--there are 4 cases:
@ -709,7 +738,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?
-- TODO Will be changed when implementing coupling.
function advtrains.trains_facing(train1, train2)
local sr_pos=train1.path[atround(train1.index)]
local sr_pos_p=train1.path[atround(train1.index)-1]
@ -858,12 +887,9 @@ end
function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id]
advtrains.train_ensure_clean(train_id, train, 0)
-- Set the path restoration position to the opposite direction
local fli = atfloor(train.end_index) + 1
train.last_pos = advtrains.path_get(train, fli)
train.last_connid = train.path_cp[fli]
train.last_frac = fli - train.end_index
advtrains.train_ensure_clean(train_id, train)
advtrains.path_setrestore(train, true)
-- rotate some other stuff
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()
end
local pihalf = math.pi/2
function wagon:on_step(dtime)
return advtrains.pcall(function()
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
--set line number
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({
textures={new_line_tex},
})
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({
textures=self.textures,
})
@ -366,8 +364,9 @@ function wagon:on_step(dtime)
end
end
--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:setacceleration({x=0, y=0, z=0})
return
end
if not data.pos_in_train then
@ -376,11 +375,9 @@ function wagon:on_step(dtime)
-- Calculate new position, yaw and direction vector
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 yaw = tyaw - pihalf
--automatic get_on
--needs to know index and path
if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
@ -391,7 +388,7 @@ function wagon:on_step(dtime)
local ix1, ix2 = advtrains.path_get_adjacent(train, aci)
-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
local add = { x = (ix2.z-ix1.z)*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 pts2=vector.round(vector.add(ix2, add))
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 z=-exh,exh do
local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z}))
-- TODO
--if (advtrains.train_collides(node)) then
-- collides=true
--end
if (advtrains.train_collides(node)) then
collides=true
end
end
end
end
@ -431,13 +427,13 @@ function wagon:on_step(dtime)
if self.collision_count and self.collision_count>10 then
--enable collision mercy to get trains stuck in walls out of walls
--actually do nothing except limiting the velocity to 1
gp.velocity=math.min(gp.velocity, 1)
gp.tarvelocity=math.min(gp.tarvelocity, 1)
train.velocity=math.min(train.velocity, 1)
train.tarvelocity=math.min(train.tarvelocity, 1)
else
gp.recently_collided_with_env=true
gp.velocity=2*gp.velocity
gp.movedir=-gp.movedir
gp.tarvelocity=0
train.recently_collided_with_env=true
train.velocity=0
-- TODO what should happen when a train collides?
train.tarvelocity=0
self.collision_count=(self.collision_count or 0)+1
end
else
@ -481,7 +477,7 @@ function wagon:on_step(dtime)
self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
end
-- 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
self.turning = true
@ -493,7 +489,7 @@ function wagon:on_step(dtime)
self.object:setyaw(yaw)
self.updatepct_timer=2
if self.update_animation then
self:update_animation(gp.velocity, self.old_velocity)
self:update_animation(train.velocity, self.old_velocity)
end
if self.custom_on_velocity_change then
self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime)
@ -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
local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
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 ix1, ix2 = advtrains.path_get_adjacent(train, aci)
-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)
-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)
local add = { x = (ix2.z-ix1.z)*gp.door_open, y = 0, z = (ix1.x-ix2.x)*gp.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 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)*train.door_open*2, y = 1, z = (ix1.x-ix2.x)*train.door_open*2}
local platpos=vector.round(vector.add(ix1, add))
local offpos=vector.round(vector.add(ix1, oadd))
atprint("platpos:", platpos, "offpos:", offpos)