aboutsummaryrefslogtreecommitdiff
path: root/advtrains/trainlogic.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains/trainlogic.lua')
-rw-r--r--advtrains/trainlogic.lua1400
1 files changed, 1400 insertions, 0 deletions
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
new file mode 100644
index 0000000..d83d89f
--- /dev/null
+++ b/advtrains/trainlogic.lua
@@ -0,0 +1,1400 @@
+--trainlogic.lua
+--controls train entities stuff about connecting/disconnecting/colliding trains and other things
+
+local setting_overrun_mode = minetest.settings:get("advtrains_overrun_mode")
+
+local benchmark=false
+local bm={}
+local bmlt=0
+local bmsteps=0
+local bmstepint=200
+atprintbm=function(action, ta)
+ if not benchmark then return end
+ local t=(os.clock()-ta)*1000
+ if not bm[action] then
+ bm[action]=t
+ else
+ bm[action]=bm[action]+t
+ end
+ bmlt=bmlt+t
+end
+function endstep()
+ if not benchmark then return end
+ bmsteps=bmsteps-1
+ if bmsteps<=0 then
+ bmsteps=bmstepint
+ for key, value in pairs(bm) do
+ minetest.chat_send_all(key.." "..(value/bmstepint).." ms avg.")
+ end
+ minetest.chat_send_all("Total time consumed by all advtrains actions per step: "..(bmlt/bmstepint).." ms avg.")
+ bm={}
+ bmlt=0
+ end
+end
+
+--acceleration for lever modes (trainhud.lua), per wagon
+local t_accel_all={
+ [0] = -10,
+ [1] = -3,
+ [11] = -2, -- calculation base for LZB
+ [2] = -0.5,
+ [4] = 0.5,
+}
+--acceleration per engine
+local t_accel_eng={
+ [0] = 0,
+ [1] = 0,
+ [11] = 0,
+ [2] = 0,
+ [4] = 1.5,
+}
+
+local VLEVER_EMERG = 0
+local VLEVER_BRAKE = 1
+local VLEVER_LZBCALC = 11
+local VLEVER_ROLL = 2
+local VLEVER_HOLD = 3
+local VLEVER_ACCEL = 4
+
+-- How far in front of a whole index with LZB 0 restriction the train should come to a halt
+-- value must be between 0 and 0.5, exclusively
+local LZB_ZERO_APPROACH_DIST = 0.1
+-- Speed the train temporarily approaches the stop point with
+local LZB_ZERO_APPROACH_SPEED = 0.2
+
+
+
+tp_player_tmr = 0
+
+advtrains.mainloop_trainlogic=function(dtime, stepno)
+ --build a table of all players indexed by pts. used by damage and door system.
+ advtrains.playersbypts={}
+ for _, player in pairs(minetest.get_connected_players()) do
+ if not advtrains.player_to_train_mapping[player:get_player_name()] then
+ --players in train are not subject to damage
+ local ptspos=minetest.pos_to_string(vector.round(player:get_pos()))
+ advtrains.playersbypts[ptspos]=player
+ end
+ end
+
+ if tp_player_tmr<=0 then
+ -- teleport players to their train every 2 seconds
+ for _, player in pairs(minetest.get_connected_players()) do
+ advtrains.tp_player_to_train(player)
+ end
+ tp_player_tmr = 2
+ else
+ tp_player_tmr = tp_player_tmr - dtime
+ end
+ --regular train step
+ --[[ structure:
+ 1. make trains calculate their occupation windows when needed (a)
+ 2. when occupation tells us so, restore the occupation tables (a)
+ 4. make trains move and update their new occupation windows and write changes
+ to occupation tables (b)
+ 5. make trains do other stuff (c)
+ ]]--
+ local t=os.clock()
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=k
+ --atprint("=== Step",stepno,"===")
+ advtrains.train_ensure_init(k, v)
+ end
+
+ advtrains.lock_path_inval = true
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=k
+ advtrains.train_step_b(k, v, dtime)
+ end
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=k
+ advtrains.train_step_c(k, v, dtime)
+ end
+
+ advtrains.lock_path_inval = false
+
+ advtrains.atprint_context_tid=nil
+
+ atprintbm("trainsteps", t)
+ endstep()
+end
+
+function advtrains.tp_player_to_train(player)
+ local pname = player:get_player_name()
+ local id=advtrains.player_to_train_mapping[pname]
+ if id then
+ local train=advtrains.trains[id]
+ if not train then advtrains.player_to_train_mapping[pname]=nil return end
+ --set the player to the train position.
+ --minetest will emerge the area and load the objects, which then will call reattach_all().
+ --because player is in mapping, it will not be subject to dying.
+ player:set_pos(train.last_pos)
+ end
+end
+minetest.register_on_joinplayer(function(player)
+ advtrains.hud[player:get_player_name()] = nil
+ advtrains.hhud[player:get_player_name()] = nil
+ --independent of this, cause all wagons of the train which are loaded to reattach their players
+ --needed because already loaded wagons won't call reattach_all()
+ local id=advtrains.player_to_train_mapping[pname]
+ if id then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
+ wagon:reattach_all()
+ end
+ end
+ end
+end)
+
+
+minetest.register_on_dieplayer(function(player)
+ local pname=player:get_player_name()
+ local id=advtrains.player_to_train_mapping[pname]
+ if id then
+ local train=advtrains.trains[id]
+ if not train then advtrains.player_to_train_mapping[pname]=nil return end
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
+ --when player dies, detach him from the train
+ --call get_off_plr on every wagon since we don't know which one he's on.
+ wagon:get_off_plr(pname)
+ end
+ end
+ -- just in case no wagon felt responsible for this player: clear train mapping
+ advtrains.player_to_train_mapping[pname] = nil
+ end
+end)
+
+--[[
+
+Zone diagram of a train (copy from occupation.lua!):
+ |___| |___| --> Direction of travel
+ oo oo+oo oo
+=|=======|===|===========|===|=======|===================|========|===
+ |SafetyB|CpB| Train |CpF|SafetyF| Brake |Aware |
+[1] [2] [3] [4] [5] [6] [7] [8]
+This mapping from indices in occwindows to zone ids is contained in WINDOW_ZONE_IDS
+
+
+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.
+local SAFETY_ZONE = 10
+local COUPLE_ZONE = 2 --value in index positions!
+local BRAKE_SPACE = 10
+local AWARE_ZONE = 10
+local WINDOW_ZONE_IDS = {
+ 2, -- 1 - SafetyB
+ 4, -- 2 - CpB
+ 1, -- 3 - Train
+ 5, -- 4 - CpF
+ 3, -- 5 - SafetyF
+ 6, -- 6 - Brake
+ 7, -- 7 - Aware
+}
+
+
+-- If a variable does not exist in the table, it is assigned the default value
+local function assertdef(tbl, var, def)
+ if not tbl[var] then
+ tbl[var] = def
+ end
+end
+
+function advtrains.get_acceleration(train, lever)
+ local acc_all = t_accel_all[lever]
+ if not acc_all then return 0 end
+
+ local acc_eng = t_accel_eng[lever]
+ local nwagons = #train.trainparts
+ if nwagons == 0 then
+ -- empty train! avoid division through zero
+ return -1
+ end
+ local acc = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
+ return acc
+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
+
+-- Occupation Callback system
+-- see occupation.lua
+-- signature is advtrains.te_register_on_<?>(function(id, train) ... end)
+
+local function mkcallback(name)
+ local callt = {}
+ advtrains["te_register_on_"..name] = function(func)
+ assertt(func, "function")
+ table.insert(callt, func)
+ end
+ return callt, function(id, train, param1, param2, param3)
+ for _,f in ipairs(callt) do
+ f(id, train, param1, param2, param3)
+ end
+ end
+end
+
+local callbacks_new_path, run_callbacks_new_path = mkcallback("new_path")
+local callbacks_invahead
+callbacks_invahead, advtrains.run_callbacks_invahead = mkcallback("invalidate_ahead") -- (id, train, start_idx)
+local callbacks_update, run_callbacks_update = mkcallback("update")
+local callbacks_create, run_callbacks_create = mkcallback("create")
+local callbacks_remove, run_callbacks_remove = mkcallback("remove")
+
+
+-- train_ensure_init: responsible for creating a state that we can work on, after one of the following events has happened:
+-- - the train's path 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_init(id, train)
+ if not train then
+ atwarn("train_ensure_init: Called with id =",id,"but a nil train!")
+ atwarn(debug.traceback())
+ return nil
+ end
+
+ train.dirty = true
+ if train.no_step then
+ --atprint("in ensure_init: no_step set, train step ignored!")
+ return nil
+ 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
+ --atprint("in ensure_init: missing properties, updating!")
+ advtrains.update_trainpart_properties(id)
+ end
+
+ --restore path
+ if not train.path then
+ --atprint("in ensure_init: Needs restoring path...")
+ if not train.last_pos then
+ atlog("Train",id,": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.")
+ train.no_step = true
+ return nil
+ end
+ if not train.last_connid then
+ atwarn("Train",id,": Restoring path: no last_connid set! Will assume 1")
+ train.last_connid = 1
+ --[[
+ Why this fix was necessary:
+ Issue: Migration problems on Grand Theft Auto Minetest
+ 1. Run of this code, warning printed.
+ 2. path_create failed with result==nil (there was an unloaded node, wait_for_path set)
+ 3. in consequence, the supposed call to path_setrestore does not happen
+ 4. train.last_connid is still unset
+ 5. next step, warning is printed again
+ Result: log flood.
+ ]]
+ end
+
+ local result = advtrains.path_create(train, train.last_pos, train.last_connid or 1, train.last_frac or 0)
+
+ --atprint("in ensure_init: path_create result ",result)
+
+ if result==false then
+ atlog("Train",id,": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to place a rail at this position and restart the server.")
+ train.no_step = true
+ return nil
+ elseif result==nil then
+ if not train.wait_for_path then
+ atlog("Train",id,": Can't initialize: Waiting for the (yet unloaded) node at",train.last_pos," to be loaded.")
+ end
+ train.wait_for_path = true
+ return false
+ end
+ -- by now, we should have a working initial path
+ train.wait_for_path = false
+
+ 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)
+
+ -- run on_new_path callbacks
+ run_callbacks_new_path(id, train)
+ end
+
+ train.dirty = false -- TODO einbauen!
+ return true
+end
+
+local function v_target_apply(v_targets, lever, vel)
+ v_targets[lever] = v_targets[lever] and math.min(v_targets[lever], vel) or vel
+end
+
+function advtrains.train_step_b(id, train, dtime)
+ if train.no_step or train.wait_for_path or not train.path then return end
+
+ -- in this code, we check variables such as path_trk_? and path_dist. We need to ensure that the path is known for the whole 'Train' zone
+ advtrains.path_get(train, atfloor(train.index + 2))
+ advtrains.path_get(train, atfloor(train.end_index - 1))
+
+ -- run pre-move hooks
+ -- TODO: if more pre-move hooks are added, make a separate callback hook
+ advtrains.lzb_look_ahead(id, train)
+
+ --[[ again, new velocity control:
+ There are two heterogenous means of control:
+ -> set a fixed acceleration and ignore speed (user)
+ -> steer towards a target speed, distance doesn't matter
+ -> needs to specify the maximum acceleration/deceleration values they are willing to accelerate/brake with
+ -> Reach a target speed after a certain distance (LZB, handled specially)
+
+ ]]--
+
+ --- 3. handle velocity influences ---
+
+ local v0 = train.velocity
+ local sit_v_cap = train.max_speed -- Maximum speed in current situation (multiple limit factors)
+ -- The desired speed change issued by the active control (user or atc)
+ local ctrl_v_tar -- desired speed which should not be crossed by braking or accelerating
+ local ctrl_accelerating = false -- whether the train should accelerate
+ local ctrl_braking = false -- whether the train should brake
+ local ctrl_lever -- the lever value to use to calculate the acceleration
+ -- the final speed change after applying LZB
+ local v_cap -- absolute maximum speed
+ local v_tar -- desired speed which should not be crossed by braking or accelerating
+ local accelerating = false-- whether the train should accelerate
+ local braking = false -- whether the train should brake
+ local lever -- the lever value to use to calculate the acceleration
+ local train_moves = (v0 > 0)
+
+ if train.recently_collided_with_env then
+ if not train_moves then
+ train.recently_collided_with_env=nil--reset status when stopped
+ end
+ --atprint("in train_step_b: applying collided_with_env")
+ sit_v_cap = 0
+ elseif train.locomotives_in_train==0 then
+ --atprint("in train_step_b: applying no_locomotives")
+ sit_v_cap = 0
+ -- interlocking speed restriction
+ elseif train.speed_restriction then
+ --atprint("in train_step_b: applying interlocking speed restriction",train.speed_restriction)
+ sit_v_cap = train.speed_restriction
+ end
+
+ --apply off-track handling:
+ local front_off_track = train.index>train.path_trk_f
+ local back_off_track=train.end_index<train.path_trk_b
+ train.off_track = front_off_track or back_off_track
+
+ if back_off_track and (not v_cap or v_cap > 1) then
+ --atprint("in train_step_b: applying back_off_track")
+ sit_v_cap = 1
+ elseif front_off_track then
+ --atprint("in train_step_b: applying front_off_track")
+ sit_v_cap = 0
+ end
+
+
+ --interpret ATC command and apply auto-lever control when not actively controlled
+ local userc = train.ctrl_user
+ if userc then
+ --atprint("in train_step_b: ctrl_user active",userc)
+ advtrains.atc.train_reset_command(train)
+
+ if userc >= VLEVER_ACCEL then
+ ctrl_accelerating = true
+ else
+ ctrl_braking = true
+ end
+ ctrl_lever = userc
+ else
+ if train.atc_command then
+ if (not train.atc_delay or train.atc_delay<=0) and not train.atc_wait_finish then
+ advtrains.atc.execute_atc_command(id, train)
+ else
+ train.atc_delay=train.atc_delay-dtime
+ end
+ elseif train.atc_delay then
+ train.atc_delay = nil
+ end
+
+ 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
+ --atprint("in train_step_b: ATC: brake state braketar=",braketar,"emerg=",emerg)
+ if braketar and braketar>=v0 then
+ --atprint("in train_step_b: ATC: brake target cleared")
+ train.atc_brake_target=nil
+ braketar = nil
+ end
+ --if train.tarvelocity and train.velocity==train.tarvelocity then
+ -- train.tarvelocity = nil
+ --end
+ if train.atc_wait_finish then
+ if not train.atc_brake_target and (not train.tarvelocity or train.velocity==train.tarvelocity) then
+ train.atc_wait_finish=nil
+ end
+ end
+
+ if train.tarvelocity and train.tarvelocity>v0 then
+ --atprint("in train_step_b: applying ATC ACCEL", train.tarvelocity)
+ ctrl_accelerating = true
+ ctrl_lever = VLEVER_ACCEL
+ elseif train.tarvelocity and train.tarvelocity<v0 then
+ ctrl_braking = true
+
+ if (braketar and braketar<v0) then
+ if emerg then
+ --atprint("in train_step_b: applying ATC EMERG", train.tarvelocity)
+ ctrl_lever = VLEVER_EMERG
+ else
+ --atprint("in train_step_b: applying ATC BRAKE", train.tarvelocity)
+ ctrl_v_tar = braketar
+ ctrl_lever = VLEVER_BRAKE
+ end
+ else
+ --atprint("in train_step_b: applying ATC ROLL", train.tarvelocity)
+ ctrl_v_tar = train.tarvelocity
+ ctrl_lever = VLEVER_ROLL
+ end
+ end
+ end
+
+ --- 2b. look at v_target, determine the effective v_target and desired acceleration ---
+ --atprint("in train_step_b: Resulting control before LZB: accelerating",ctrl_accelerating,"braking",ctrl_braking,"lever", ctrl_lever, "target", ctrl_v_tar)
+ --train.debug = dump({tv_target,tv_lever})
+
+ --atprint("in train_step_b: Current index",train.index,"end",train.end_index,"vel",v0)
+ --- 3a. calculate the acceleration required to reach the speed restriction in path_speed (LZB) ---
+ -- Iterates over the path nodes we WOULD pass if we were continuing with the current speed
+ -- and determines the MINIMUM of path_speed in this range.
+ -- Then, determines acceleration so that we can reach this 'overridden' target speed in this step (but short-circuited)
+ local lzb_next_zero_barrier -- if defined, train should not pass this point as it's a 0-LZB
+ local new_index_curr_tv -- pre-calculated new train index in lzb check
+ local lzb_v_cap -- the maximum speed that LZB dictates
+
+ local dst_curr_v = v0 * dtime
+ new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
+ local i = atfloor(train.index)
+ local psp
+ while true do
+ psp = train.path_speed[i]
+ if psp then
+ lzb_v_cap = lzb_v_cap and math.min(lzb_v_cap, psp) or psp
+ if psp == 0 and not lzb_next_zero_barrier then
+ --atprint("in train_step_b: Found zero barrier: ",i)
+ lzb_next_zero_barrier = i - LZB_ZERO_APPROACH_DIST
+ end
+ end
+ if i > new_index_curr_tv then
+ break
+ end
+ i = i + 1
+ end
+
+ if lzb_next_zero_barrier and train.index < lzb_next_zero_barrier then
+ lzb_v_cap = LZB_ZERO_APPROACH_SPEED
+ end
+
+ --atprint("in train_step_b: LZB calculation yields newindex=",new_index_curr_tv,"lzbtarget=",lzb_v_cap,"zero_barr=",lzb_next_zero_barrier,"")
+
+ -- LZB HUD: decrement timer and delete when 0
+ if train.hud_lzb_effect_tmr then
+ if train.hud_lzb_effect_tmr <=0 then
+ train.hud_lzb_effect_tmr = nil
+ else
+ train.hud_lzb_effect_tmr = train.hud_lzb_effect_tmr - 1
+ end
+ end
+
+ -- We now need to bring ctrl_*, sit_v_cap and lzb_v_cap together to determine the final controls.
+ local v_cap = sit_v_cap -- always defined, by default train.max_speed
+ if lzb_v_cap and lzb_v_cap < v_cap then
+ v_cap = lzb_v_cap
+ lever = VLEVER_BRAKE -- actually irrelevant, acceleration is not considered anyway unless v_tar is also set.
+ -- display LZB control override in the HUD
+ if lzb_v_cap <= v0 then
+ train.hud_lzb_effect_tmr = 1
+ -- This is to signal the HUD that LZB is active. This works as a timer to avoid HUD blinking
+ end
+ end
+
+ v_tar = ctrl_v_tar
+ -- if v_cap is smaller than the current speed, we need to brake in all cases.
+ if v_cap < v0 then
+ braking = true
+ lever = VLEVER_BRAKE
+ -- set v_tar to v_cap to not slow down any further than required.
+ -- unless control wants us to brake too, then we use control's v_tar.
+ if not ctrl_v_tar or ctrl_v_tar > v_cap then
+ v_tar = v_cap
+ end
+ else -- else, use what the ctrl says
+ braking = ctrl_braking
+ accelerating = ctrl_accelerating and not braking
+ lever = ctrl_lever
+ end
+ train.lever = lever
+
+ --atprint("in train_step_b: final control: accelerating",accelerating,"braking",braking,"lever", lever, "target", v_tar)
+
+ -- reset train acceleration when holding speed
+ if not braking and not accelerating then
+ train.acceleration = 0
+ end
+
+ --- 3b. if braking, modify the velocity BEFORE the movement
+ if braking then
+ local dv = advtrains.get_acceleration(train, lever) * dtime
+ local v1 = v0 + dv
+ if v_tar and v1 < v_tar then
+ --atprint("in train_step_b: Braking: Hit v_tar!")
+ v1 = v_tar
+ end
+ if v1 > v_cap then
+ --atprint("in train_step_b: Braking: Hit v_cap!")
+ v1 = v_cap
+ end
+ if v1 < 0 then
+ --atprint("in train_step_b: Braking: Hit 0!")
+ v1 = 0
+ end
+
+ train.acceleration = (v1 - v0) / dtime
+ train.velocity = v1
+ --atprint("in train_step_b: Braking: New velocity",v1," (yields acceleration",train.acceleration,")")
+ -- make saved new_index_curr_tv invalid because speed has changed
+ new_index_curr_tv = nil
+ end
+
+ --- 4. move train ---
+ -- if we have calculated the new end index before, don't do that again
+ if not new_index_curr_tv then
+ local dst_curr_v = train.velocity * dtime
+ new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
+ --atprint("in train_step_b: movement calculation (re)done, yields newindex=",new_index_curr_tv)
+ else
+ --atprint("in train_step_b: movement calculation reusing from LZB newindex=",new_index_curr_tv)
+ end
+
+ -- if the zeroappr mechanism has hit, go no further than zeroappr index
+ if lzb_next_zero_barrier and new_index_curr_tv > lzb_next_zero_barrier then
+ --atprint("in train_step_b: Zero barrier hit, clipping to newidx_tv=",new_index_curr_tv, "zb_idx=",lzb_next_zero_barrier)
+ new_index_curr_tv = lzb_next_zero_barrier
+ end
+ train.index = new_index_curr_tv
+
+ recalc_end_index(train)
+ --atprint("in train_step_b: New index",train.index,"end",train.end_index,"vel",train.velocity)
+
+ --- 4a. if accelerating, modify the velocity AFTER the movement
+ if accelerating then
+ local dv = advtrains.get_acceleration(train, lever) * dtime
+ local v1 = v0 + dv
+ if v_tar and v1 > v_tar then
+ --atprint("in train_step_b: Accelerating: Hit v_tar!")
+ v1 = v_tar
+ end
+ if v1 > v_cap then
+ --atprint("in train_step_b: Accelerating: Hit v_cap!")
+ v1 = v_cap
+ end
+
+ train.acceleration = (v1 - v0) / dtime
+ train.velocity = v1
+ --atprint("in train_step_b: Accelerating: New velocity",v1," (yields acceleration",train.acceleration,")")
+ end
+end
+
+function advtrains.train_step_c(id, train, dtime)
+ if train.no_step or train.wait_for_path or not train.path then return end
+
+ -- all location/extent-critical actions have been done.
+ -- calculate the new occupation window
+ run_callbacks_update(id, train)
+
+ -- Return if something(TM) damaged the path
+ if train.no_step or train.wait_for_path or not train.path then return end
+
+ advtrains.path_clear_unused(train)
+
+ advtrains.path_setrestore(train)
+
+ -- less important stuff
+
+ train.check_trainpartload=(train.check_trainpartload or 0)-dtime
+ if train.check_trainpartload<=0 then
+ advtrains.spawn_wagons(id)
+ train.check_trainpartload=2
+ end
+
+ --- 8. check for collisions with other trains and damage players ---
+
+ local train_moves=(train.velocity~=0)
+
+ --- Check whether this train can be coupled to another, and set couple entities accordingly
+ if not train.was_standing and not train_moves then
+ advtrains.train_check_couples(train)
+ end
+ train.was_standing = not train_moves
+
+ if train_moves then
+
+ local collided = false
+ local coll_grace=1
+ local collindex = advtrains.path_get_index_by_offset(train, train.index, -coll_grace)
+ local collpos = advtrains.path_get(train, atround(collindex))
+ if collpos then
+ local rcollpos=advtrains.round_vector_floor_y(collpos)
+ local is_loaded_area = advtrains.is_node_loaded(rcollpos)
+ for x=-train.extent_h,train.extent_h do
+ for z=-train.extent_h,train.extent_h do
+ local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
+ --- 8a Check collision ---
+ if not collided then
+
+ local col_tr = advtrains.occ.check_collision(testpos, id)
+ if col_tr then
+ advtrains.train_check_couples(train)
+ train.velocity = 0
+ advtrains.atc.train_reset_command(train)
+ collided = true
+ end
+
+ --- 8b damage players ---
+ if is_loaded_area and train.velocity > 3 and (setting_overrun_mode=="drop" or setting_overrun_mode=="normal") then
+ local testpts = minetest.pos_to_string(testpos)
+ local player=advtrains.playersbypts[testpts]
+ if player and player:get_hp()>0 and advtrains.is_damage_enabled(player:get_player_name()) then
+ --atdebug("damage found",player:get_player_name())
+ if setting_overrun_mode=="drop" then
+ --instantly kill player
+ --drop inventory contents first, to not to spawn bones
+ local player_inv=player:get_inventory()
+ for i=1,player_inv:get_size("main") do
+ minetest.add_item(testpos, player_inv:get_stack("main", i))
+ end
+ for i=1,player_inv:get_size("craft") do
+ minetest.add_item(testpos, player_inv:get_stack("craft", i))
+ end
+ -- empty lists main and craft
+ player_inv:set_list("main", {})
+ player_inv:set_list("craft", {})
+ end
+ player:set_hp(0)
+ end
+ end
+ end
+ end
+ end
+ --- 8c damage other objects ---
+ if is_loaded_area then
+ local objs = minetest.get_objects_inside_radius(rcollpos, 2)
+ for _,obj in ipairs(objs) do
+ if not obj:is_player() and obj:get_armor_groups().fleshy and obj:get_armor_groups().fleshy > 0
+ and obj:get_luaentity() and obj:get_luaentity().name~="signs:text" then
+ obj:punch(obj, 1, { full_punch_interval = 1.0, damage_groups = {fleshy = 1000}, }, nil)
+ end
+ 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 mknodecallback(name)
+ local callt = {}
+ advtrains["tnc_register_on_"..name] = function(func, prio)
+ assertt(func, "function")
+ if prio then
+ table.insert(callt, 1, func)
+ else
+ table.insert(callt, func)
+ end
+ end
+ return callt, function(pos, id, train, index, paramx1, paramx2, paramx3)
+ for _,f in ipairs(callt) do
+ f(pos, id, train, index, paramx1, paramx2, paramx3)
+ end
+ end
+end
+
+-- enter/leave-node callbacks
+-- signature is advtrains.tnc_register_on_enter/leave(function(pos, id, train, index) ... end)
+local callbacks_enter_node, run_callbacks_enter_node = mknodecallback("enter")
+local callbacks_leave_node, run_callbacks_leave_node = mknodecallback("leave")
+
+-- Node callback for approaching
+-- Might be called multiple times, whenever path is recalculated. Also called for the first node the train is standing on, then has_entered is true.
+-- signature is function(pos, id, train, index, has_entered, lzbdata)
+-- has_entered: true if the "enter" callback has already been executed for this train in this location
+-- lzbdata: arbitrary data (shared between all callbacks), deleted when LZB is restarted.
+-- These callbacks are called in order of distance as train progresses along tracks, so lzbdata can be used to
+-- keep track of a train's state once it passes this point
+local callbacks_approach_node, run_callbacks_approach_node = mknodecallback("approach")
+
+
+local function tnc_call_enter_callback(pos, train_id, train, index)
+ --atdebug("tnc enter",pos,train_id)
+ 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, train, index)
+ end
+
+ -- call other registered callbacks
+ run_callbacks_enter_node(pos, train_id, train, index)
+
+ -- check for split points
+ if mregnode and mregnode.at_conns and #mregnode.at_conns == 3 and train.path_cp[index] == 3 then
+ -- train came from connection 3 of a switch, so it split points.
+ if not train.points_split then
+ train.points_split = {}
+ end
+ train.points_split[advtrains.encode_pos(pos)] = true
+ --atdebug(train_id,"split points at",pos)
+ end
+end
+local function tnc_call_leave_callback(pos, train_id, train, index)
+ --atdebug("tnc leave",pos,train_id)
+ 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, train, index)
+ end
+
+ -- call other registered callbacks
+ run_callbacks_leave_node(pos, train_id, train, index)
+
+ -- split points do not matter anymore. clear them
+ if train.points_split then
+ if train.points_split[advtrains.encode_pos(pos)] then
+ train.points_split[advtrains.encode_pos(pos)] = nil
+ --atdebug(train_id,"has passed split points at",pos)
+ end
+ -- any entries left?
+ for _,_ in pairs(train.points_split) do
+ return
+ end
+ train.points_split = nil
+ end
+ -- WARNING possibly unreachable place!
+end
+
+function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbdata)
+ --atdebug("tnc approach",pos,train_id, lzbdata)
+ local has_entered = atround(train.index) == index
+
+ 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_approach then
+ mregnode.advtrains.on_train_approach(pos, train_id, train, index, has_entered, lzbdata)
+ end
+
+ -- call other registered callbacks
+ run_callbacks_approach_node(pos, train_id, train, index, has_entered, lzbdata)
+end
+
+-- === te callback definition for tnc node callbacks ===
+
+advtrains.te_register_on_new_path(function(id, train)
+ train.tnc = {
+ old_index = atround(train.index),
+ old_end_index = atround(train.end_index),
+ }
+ --atdebug(id,"tnc init",train.index,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(train,old_index))
+ tnc_call_enter_callback(pos, id, train, old_index)
+ end
+ while old_end_index < new_end_index do
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,old_end_index))
+ tnc_call_leave_callback(pos, id, train, old_end_index)
+ old_end_index = old_end_index + 1
+ end
+ train.tnc.old_index = new_index
+ train.tnc.old_end_index = new_end_index
+end)
+
+advtrains.te_register_on_create(function(id, train)
+ local index = atround(train.index)
+ local end_index = atround(train.end_index)
+ while end_index <= index do
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,end_index))
+ tnc_call_enter_callback(pos, id, train, end_index)
+ end_index = end_index + 1
+ end
+ --atdebug(id,"tnc create",train.index,train.end_index)
+end)
+
+advtrains.te_register_on_remove(function(id, train)
+ local index = atround(train.index)
+ local end_index = atround(train.end_index)
+ while end_index <= index do
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,end_index))
+ tnc_call_leave_callback(pos, id, train, end_index)
+ end_index = end_index + 1
+ end
+ --atdebug(id,"tnc remove",train.index,train.end_index)
+end)
+
+--returns new id
+function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
+ local new_id=advtrains.random_id()
+ while advtrains.trains[new_id] do new_id=advtrains.random_id() end--ensure uniqueness
+
+ local t={}
+ t.id = new_id
+
+ t.last_pos=pos
+ t.last_connid=connid
+ t.last_frac=ioff
+
+ --t.tarvelocity=0
+ t.velocity=0
+ t.trainparts=trainparts
+
+ advtrains.trains[new_id] = t
+ --atdebug("Created new train:",t)
+
+ if not advtrains.train_ensure_init(new_id, advtrains.trains[new_id]) then
+ atwarn("create_new_train_at",pos,connid,"failed! This might lead to temporary bugs.")
+ return
+ end
+
+ run_callbacks_create(new_id, advtrains.trains[new_id])
+
+ return new_id
+end
+
+function advtrains.remove_train(id)
+ local train = advtrains.trains[id]
+
+ if not advtrains.train_ensure_init(id, train) then
+ atwarn("remove_train",id,"failed! This might lead to temporary bugs.")
+ return
+ end
+
+ run_callbacks_remove(id, train)
+
+ advtrains.path_invalidate(train)
+ advtrains.couple_invalidate(train)
+
+ local tp = train.trainparts
+ --atdebug("Removing train",id,"leftover trainparts:",tp)
+
+ advtrains.trains[id] = nil
+
+ return tp
+
+end
+
+
+function advtrains.add_wagon_to_train(wagon_id, train_id, index)
+ local train=advtrains.trains[train_id]
+
+ if not advtrains.train_ensure_init(train_id, train) then
+ atwarn("Train",train_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ if index then
+ table.insert(train.trainparts, index, wagon_id)
+ else
+ table.insert(train.trainparts, wagon_id)
+ end
+
+ advtrains.update_trainpart_properties(train_id)
+ recalc_end_index(train)
+ run_callbacks_update(train_id, train)
+end
+
+-- Note: safe_decouple_wagon() has been moved to wagons.lua
+
+-- this function sets wagon's pos_in_train(parts) properties and train's max_speed and drives_on (and more)
+function advtrains.update_trainpart_properties(train_id, invert_flipstate)
+ local train=advtrains.trains[train_id]
+ train.drives_on=advtrains.merge_tables(advtrains.all_tracktypes)
+ --FIX: deep-copy the table!!!
+ train.max_speed=20
+ train.extent_h = 0;
+
+ local rel_pos=0
+ local count_l=0
+ local shift_dcpl_lock=false
+ for i, w_id in ipairs(train.trainparts) do
+
+ local data = advtrains.wagons[w_id]
+
+ -- 1st: update wagon data (pos_in_train a.s.o)
+ if data then
+ local wagon = advtrains.wagon_prototypes[data.type or data.entity_name]
+ if not wagon then
+ atwarn("Wagon '",data.type,"' couldn't be found. Please check that all required modules are loaded!")
+ wagon = advtrains.wagon_prototypes["advtrains:wagon_placeholder"]
+
+ end
+ rel_pos=rel_pos+wagon.wagon_span
+ data.train_id=train_id
+ data.pos_in_train=rel_pos
+ data.pos_in_trainparts=i
+ if wagon.is_locomotive then
+ count_l=count_l+1
+ end
+ if invert_flipstate then
+ data.wagon_flipped = not data.wagon_flipped
+ shift_dcpl_lock, data.dcpl_lock = data.dcpl_lock, shift_dcpl_lock
+ end
+ rel_pos=rel_pos+wagon.wagon_span
+
+ if wagon.drives_on then
+ for k,_ in pairs(train.drives_on) do
+ if not wagon.drives_on[k] then
+ train.drives_on[k]=nil
+ end
+ end
+ end
+ train.max_speed=math.min(train.max_speed, wagon.max_speed)
+ train.extent_h = math.max(train.extent_h, wagon.extent_h or 1);
+ end
+ end
+ train.trainlen = rel_pos
+ train.locomotives_in_train = count_l
+end
+
+
+local ablkrng = advtrains.wagon_load_range
+-- This function checks whether entities need to be spawned for certain wagons, and spawns them.
+-- Called from train_step_*(), not required to check init.
+function advtrains.spawn_wagons(train_id)
+ local train = advtrains.trains[train_id]
+
+ for i = 1, #train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if data then
+ if data.train_id ~= train_id then
+ atwarn("Train",train_id,"Wagon #",i,": Saved train ID",data.train_id,"did not match!")
+ data.train_id = train_id
+ end
+ if not advtrains.wagon_objects[w_id] or not advtrains.wagon_objects[w_id]:get_yaw() then
+ -- eventually need to spawn new object. check if position is loaded.
+ local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
+ local pos = advtrains.path_get(train, atfloor(index))
+
+ if advtrains.position_in_range(pos, ablkrng) then
+ --atdebug("wagon",w_id,"spawning")
+ local wt = advtrains.get_wagon_prototype(data)
+ local wagon = minetest.add_entity(pos, wt):get_luaentity()
+ wagon:set_id(w_id)
+ end
+ end
+ else
+ atwarn("Train",train_id,"Wagon #",1,": A wagon with id",w_id,"does not exist! Wagon will be removed from train.")
+ table.remove(train.trainparts, i)
+ i = i - 1
+ end
+ end
+end
+
+function advtrains.split_train_at_fc(train, count_empty, length_limit)
+ -- splits train at first different current FC by convention,
+ -- locomotives have empty FC so are ignored
+ -- count_empty is used to split off locomotives
+ -- length_limit limits the length of the first train to length_limit wagons
+ local train_id = train.id
+ local fc = false
+ local ind = 0
+ for i = 1, #train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if length_limit and i > length_limit then
+ ind = i
+ break
+ end
+ if data then
+ local wfc = advtrains.get_cur_fc(data)
+ if wfc ~= "" or count_empty then
+ if fc then
+ if fc ~= wfc then
+ ind = i
+ break
+ end
+ else
+ fc = wfc
+ end
+ end
+ end
+ end
+ if ind > 0 then
+ return advtrains.split_train_at_index(train, ind), fc
+ end
+ if fc then
+ return nil, fc
+ end
+end
+
+function advtrains.train_step_fc(train)
+ for i=1,#train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if data then
+ advtrains.step_fc(data)
+ end
+ end
+end
+
+function advtrains.split_train_at_index(train, index)
+ -- this function splits a train at index, creating a new train from the back part of the train.
+
+ local train_id=train.id
+ if index > #train.trainparts then
+ -- index specified too long
+ return
+ end
+ local w_id = train.trainparts[index]
+ local data = advtrains.wagons[w_id]
+ local _, wagon = advtrains.get_wagon_prototype(data)
+ if not advtrains.train_ensure_init(train_id, train) then
+ atwarn("Train",train_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ -- make sure that the train is fully on track before splitting. May cause problems otherwise
+ if train.index > train.path_trk_f or train.end_index < train.path_trk_b then
+ atwarn("Train",train_id,": cannot split train because it is off track!")
+ return
+ end
+
+ local p_index=advtrains.path_get_index_by_offset(train, train.index, - data.pos_in_train + wagon.wagon_span)
+ local pos, connid, frac = advtrains.path_getrestore(train, p_index)
+ local tp = {}
+ for k,v in ipairs(train.trainparts) do
+ if k >= index then
+ table.insert(tp, v)
+ train.trainparts[k] = nil
+ end
+ end
+ advtrains.update_trainpart_properties(train_id)
+ recalc_end_index(train)
+ run_callbacks_update(train_id, train)
+
+ --create subtrain
+ local newtrain_id=advtrains.create_new_train_at(pos, connid, frac, tp)
+ local newtrain=advtrains.trains[newtrain_id]
+
+ newtrain.velocity=train.velocity
+ return newtrain_id -- return new train ID, so new train can be manipulated
+
+end
+
+function advtrains.split_train_at_wagon(wagon_id)
+ --get train
+ local data = advtrains.wagons[wagon_id]
+ advtrains.split_train_at_index(advtrains.trains[data.train_id], data.pos_in_trainparts)
+end
+
+-- coupling
+local CPL_CHK_DST = -1
+local CPL_ZONE = 2
+
+-- train.couple_* contain references to ObjectRefs of couple objects, which contain all relevant information
+-- These objectRefs will delete themselves once the couples no longer match
+local function createcouple(pos, train1, t1_is_front, train2, t2_is_front)
+ local id1 = train1.id
+ local id2 = train2.id
+ if train1.autocouple or train2.autocouple then
+ -- couple trains
+ train1.autocouple = nil
+ train2.autocouple = nil
+ minetest.after(0, advtrains.safe_couple_trains, id1, id2, t1_is_front, t2_is_front, false, false, train1.velocity, train2.velocity)
+ return
+ end
+
+ local obj=minetest.add_entity(pos, "advtrains:couple")
+ if not obj then error("Failed creating couple object!") return end
+ local le=obj:get_luaentity()
+ le.train_id_1=id1
+ le.train_id_2=id2
+ le.t1_is_front=t1_is_front
+ le.t2_is_front=t2_is_front
+ --atdebug("created couple between",train1.id,t1_is_front,train2.id,t2_is_front)
+ if t1_is_front then
+ train1.cpl_front = obj
+ else
+ train1.cpl_back = obj
+ end
+ if t2_is_front then
+ train2.cpl_front = obj
+ else
+ train2.cpl_back = obj
+ end
+
+end
+
+function advtrains.train_check_couples(train)
+ --atdebug("rechecking couples")
+ if train.cpl_front then
+ if not train.cpl_front:get_yaw() then
+ -- objectref is no longer valid. reset.
+ train.cpl_front = nil
+ end
+ end
+ if not train.cpl_front then
+ -- recheck front couple
+ local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST)
+ if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ for tid, idx in pairs(front_trains) do
+ local other_train = advtrains.trains[tid]
+ if not advtrains.train_ensure_init(tid, other_train) then
+ atwarn("Train",tid,"is not initialized! Couldn't check couples!")
+ return
+ end
+ --atdebug(train.id,"front: ",idx,"on",tid,atround(other_train.index),atround(other_train.end_index))
+ if other_train.velocity == 0 then
+ if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
+ createcouple(pos, train, true, other_train, true)
+ break
+ end
+ if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
+ createcouple(pos, train, true, other_train, false)
+ break
+ end
+ end
+ end
+ end
+ end
+ if train.cpl_back then
+ if not train.cpl_back:get_yaw() then
+ -- objectref is no longer valid. reset.
+ train.cpl_back = nil
+ end
+ end
+ if not train.cpl_back then
+ -- recheck back couple
+ local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST)
+ if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ for tid, idx in pairs(back_trains) do
+ local other_train = advtrains.trains[tid]
+ if not advtrains.train_ensure_init(tid, other_train) then
+ atwarn("Train",tid,"is not initialized! Couldn't check couples!")
+ return
+ end
+ if other_train.velocity == 0 then
+ if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
+ createcouple(pos, train, false, other_train, true)
+ break
+ end
+ if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
+ createcouple(pos, train, false, other_train, false)
+ break
+ end
+ end
+ end
+ end
+ end
+end
+
+function advtrains.couple_invalidate(train)
+ if train.cpl_back then
+ train.cpl_back:remove()
+ train.cpl_back = nil
+ end
+ if train.cpl_front then
+ train.cpl_front:remove()
+ train.cpl_front = nil
+ end
+ train.was_standing = nil
+end
+
+-- relevant code for this comment is in couple.lua
+
+--there are 4 cases:
+--1/2. F<->R F<->R regular, put second train behind first
+--->frontpos of first train will match backpos of second
+--3. F<->R R<->F flip one of these trains, take the other as new train
+--->backpos's will match
+--4. R<->F F<->R flip one of these trains and take it as new parent
+--->frontpos's will match
+
+
+function advtrains.do_connect_trains(first_id, second_id, vel)
+ local first, second=advtrains.trains[first_id], advtrains.trains[second_id]
+
+ if not advtrains.train_ensure_init(first_id, first) then
+ atwarn("Train",first_id,"is not initialized! Operation aborted!")
+ return
+ end
+ if not advtrains.train_ensure_init(second_id, second) then
+ atwarn("Train",second_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ local first_wagoncnt=#first.trainparts
+ local second_wagoncnt=#second.trainparts
+
+ for _,v in ipairs(second.trainparts) do
+ table.insert(first.trainparts, v)
+ end
+
+ advtrains.remove_train(second_id)
+ if vel < 0 then
+ advtrains.invert_train(first_id)
+ vel = -vel
+ end
+ first.velocity= vel or 0
+
+ advtrains.update_trainpart_properties(first_id)
+ advtrains.couple_invalidate(first)
+ return true
+end
+
+function advtrains.invert_train(train_id)
+ local train=advtrains.trains[train_id]
+
+ if not advtrains.train_ensure_init(train_id, train) then
+ atwarn("Train",train_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ advtrains.path_setrestore(train, true)
+
+ -- rotate some other stuff
+ if train.door_open then
+ train.door_open = - train.door_open
+ end
+ if train.atc_command then
+ train.atc_arrow = not train.atc_arrow
+ end
+
+ advtrains.path_invalidate(train, true)
+ advtrains.couple_invalidate(train)
+
+ local old_trainparts=train.trainparts
+ train.trainparts={}
+ for k,v in ipairs(old_trainparts) do
+ table.insert(train.trainparts, 1, v)--notice insertion at first place
+ end
+ advtrains.update_trainpart_properties(train_id, true)
+
+ -- recalculate path
+ advtrains.train_ensure_init(train_id, train)
+
+ -- If interlocking present, check whether this train is in a section and then set as shunt move after reversion
+ if advtrains.interlocking and train.il_sections and #train.il_sections > 0 then
+ train.is_shunt = true
+ train.speed_restriction = advtrains.SHUNT_SPEED_MAX
+ else
+ train.is_shunt = false
+ train.speed_restriction = nil
+ end
+end
+
+-- returns: train id, index of one of the trains that stand at this position.
+function advtrains.get_train_at_pos(pos)
+ local t = advtrains.occ.get_trains_at(pos)
+ for tid,idx in pairs(t) do
+ return tid, idx
+ end
+end
+
+
+-- ehm... I never adapted this function to the new path system ?!
+function advtrains.invalidate_all_paths(pos)
+ local tab
+ if pos then
+ -- if position given, check occupation system
+ tab = advtrains.occ.get_trains_over(pos)
+ else
+ tab = advtrains.trains
+ end
+
+ for id, _ in pairs(tab) do
+ advtrains.invalidate_path(id)
+ end
+end
+
+-- Calls invalidate_path_ahead on all trains occupying (having paths over) this node
+-- Can be called during train step.
+function advtrains.invalidate_all_paths_ahead(pos)
+ local tab = advtrains.occ.get_trains_over(pos)
+
+ for id,index in pairs(tab) do
+ local train = advtrains.trains[id]
+ advtrains.path_invalidate_ahead(train, index, true)
+ end
+end
+
+function advtrains.invalidate_path(id)
+ --atdebug("Path invalidate:",id)
+ local v=advtrains.trains[id]
+ if not v then return end
+ advtrains.path_invalidate(v)
+ advtrains.couple_invalidate(v)
+ v.dirty = true
+end
+
+--not blocking trains group
+function advtrains.train_collides(node)
+ if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
+ if not minetest.registered_nodes[node.name].groups.not_blocking_trains then
+ return true
+ end
+ end
+ return false
+end
+
+local nonblocknodes={
+ "default:fence_wood",
+ "default:fence_acacia_wood",
+ "default:fence_aspen_wood",
+ "default:fence_pine_wood",
+ "default:fence_junglewood",
+ "default:torch",
+ "bones:bones",
+
+ "default:sign_wall",
+ "signs:sign_wall",
+ "signs:sign_wall_blue",
+ "signs:sign_wall_brown",
+ "signs:sign_wall_orange",
+ "signs:sign_wall_green",
+ "signs:sign_yard",
+ "signs:sign_wall_white_black",
+ "signs:sign_wall_red",
+ "signs:sign_wall_white_red",
+ "signs:sign_wall_yellow",
+ "signs:sign_post",
+ "signs:sign_hanging",
+
+
+}
+minetest.after(0, function()
+ for _,name in ipairs(nonblocknodes) do
+ if minetest.registered_nodes[name] then
+ minetest.registered_nodes[name].groups.not_blocking_trains=1
+ end
+ end
+end)