aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authororwell96 <orwell@bleipb.de>2018-05-17 11:16:04 +0200
committerorwell96 <orwell@bleipb.de>2018-06-14 17:39:42 +0200
commitb420a719398fa0e342d5ee8547464179664ae6b9 (patch)
tree541999867946eea1dc94fe0c9930af67e39434e7
parentcaf2bda7bc02f0bbe6dbbeb415e7881158d143b9 (diff)
downloadadvtrains-b420a719398fa0e342d5ee8547464179664ae6b9.tar.gz
advtrains-b420a719398fa0e342d5ee8547464179664ae6b9.tar.bz2
advtrains-b420a719398fa0e342d5ee8547464179664ae6b9.zip
Implement a reverse path lookup for trains instead of an occupations window system
-rw-r--r--advtrains/api_doc.txt26
-rw-r--r--advtrains/atc.lua2
-rw-r--r--advtrains/helpers.lua8
-rw-r--r--advtrains/occupation.lua186
-rw-r--r--advtrains/path.lua60
-rw-r--r--advtrains/trainlogic.lua224
-rw-r--r--advtrains/wagons.lua44
7 files changed, 258 insertions, 292 deletions
diff --git a/advtrains/api_doc.txt b/advtrains/api_doc.txt
index e34b32a..3cb58e5 100644
--- a/advtrains/api_doc.txt
+++ b/advtrains/api_doc.txt
@@ -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
}
})
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index 940cb08..1d3b04c 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -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)
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index 9add1b8..dc923a5 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -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
diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua
index e72d668..3007723 100644
--- a/advtrains/occupation.lua
+++ b/advtrains/occupation.lua
@@ -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...
-))
-
-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
+---------------------
+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:
+
+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.
+
+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,29 +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)
- local t = occgetcreate(pos)
- local i = 1
- while t[i] do
- 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)
+function o.set_item(train_id, pos, idx)
local t = occgetcreate(pos)
local i = 1
while t[i] do
@@ -143,16 +128,12 @@ function o.set_occupation(train_id, pos, oid)
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
diff --git a/advtrains/path.lua b/advtrains/path.lua
index ad5aeb3..d1d7ad9 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -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
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
index fa3ff9e..373e7ce 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -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
+-- 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
+
+-- 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 apply_occupation_changes(old, new, train_id, train)
- -- TODO
+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
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index bf128d9..38c551e 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -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)