--trainlogic.lua--controls train entities stuff about connecting/disconnecting/colliding trains and other things-- TODO: what should happen when a train has no trainparts anymore?local benchmark=falselocal bm={}local bmlt=0local bmsteps=0local bmstepint=200
atprintbm=function(action, ta)if not benchmark then return endlocal t=(os.clock()-ta)*1000if not bm[action]then
bm[action]=t
else
bm[action]=bm[action]+t
end
bmlt=bmlt+t
endfunctionendstep()if not benchmark then return end
bmsteps=bmsteps-1if bmsteps<=0then
bmsteps=bmstepint
for key, value inpairs(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=0endend--acceleration for lever modes (trainhud.lua), per wagonlocal t_accel_all={[0] = -10,[1] = -3,[2] = -0.5,[4] =0.5,}--acceleration per enginelocal t_accel_eng={[0] =0,[1] =0,[2] =0,[4] =1.5,}
advtrains.mainloop_trainlogic=function(dtime)--build a table of all players indexed by pts. used by damage and door system.
advtrains.playersbypts={}for _, player inpairs(minetest.get_connected_players())doif not advtrains.player_to_train_mapping[player:get_player_name()]then--players in train are not subject to damagelocal ptspos=minetest.pos_to_string(vector.round(player:getpos()))
advtrains.playersbypts[ptspos]=player
endend--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 inpairs(advtrains.trains)do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
advtrains.train_ensure_clean(k, v)endfor k,v inpairs(advtrains.trains)do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
advtrains.train_step_b(k, v, dtime)endfor k,v inpairs(advtrains.trains)do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
advtrains.train_step_c(k, v, dtime)end
advtrains.atprint_context_tid=nil
advtrains.atprint_context_tid_full=nil
advtrains.occ.end_step()atprintbm("trainsteps", t)endstep()end
minetest.register_on_joinplayer(function(player)return advtrains.pcall(function()local pname=player:get_player_name()local id=advtrains.player_to_train_mapping[pname]if id thenlocal 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:setpos(train.last_pos_prev)--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()for _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.train_id==id then
wagon:reattach_all()endendendend)end)
minetest.register_on_dieplayer(function(player)return advtrains.pcall(function()local pname=player:get_player_name()local id=advtrains.player_to_train_mapping[pname]if id thenlocal train=advtrains.trains[id]if not train then advtrains.player_to_train_mapping[pname]=nil return endfor _,wagon inpairs(minetest.luaentities)doif 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)endendendend)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_IDSThe occupation system has been abandoned. The constants will still be usedto determine the couple distance(because of the reverse lookup, the couple system simplifies a lot...)]]---- unless otherwise stated, in meters.local SAFETY_ZONE =10local COUPLE_ZONE =2--value in index positions!local BRAKE_SPACE =10local AWARE_ZONE =10local WINDOW_ZONE_IDS = {2,-- 1 - SafetyB4,-- 2 - CpB1,-- 3 - Train5,-- 4 - CpF3,-- 5 - SafetyF6,-- 6 - Brake7,-- 7 - Aware}-- If a variable does not exist in the table, it is assigned the default valuelocal functionassertdef(tbl, var, def)if not tbl[var]then
tbl[var] = def
endend-- Calculates the indices where the window borders of the occupation windows are.-- TODO adapt this code to new system, probably into a callbacklocal functioncalc_occwindows(id, train)local end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
train.end_index = end_index
local cpl_b = end_index - COUPLE_ZONE
local safety_b = advtrains.path_get_index_by_offset(train, cpl_b, -SAFETY_ZONE)local cpl_f = end_index + COUPLE_ZONE
local safety_f = advtrains.path_get_index_by_offset(train, cpl_f, SAFETY_ZONE)-- calculate brake distancelocal acc_all = t_accel_all[1]local acc_eng = t_accel_eng[1]local nwagons = #train.trainparts
local acc = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
local vel = train.velocity
local brakedst = (vel*vel) / (2*acc)local brake_i = math.max(advtrains.path_get_index_by_offset(train, train.index, brakedst + BRAKE_SPACE), safety_f)local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)return{
safety_b,
cpl_b,
end_index,
train.index,
cpl_f,
safety_f,
brake_i,
aware_i,}end-- Small local util function to recalculate train's end indexlocal functionrecalc_end_index(train)
train.end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)end-- Occupation Callback system-- see occupation.lualocal callbacks_new_path = {}local callbacks_update = {}function advtrains.tb_register_on_new_path(func)assertt(func,"function")table.insert(callbacks_new_path, func)endfunction advtrains.tb_register_on_update(func)assertt(func,"function")table.insert(callbacks_update, func)endlocal functionrun_callbacks_new_path(id, train)for _,f inipairs(callbacks_new_path)dof(id, train)endendlocal functionrun_callbacks_update(id, train)for _,f inipairs(callbacks_new_path)dof(id, train)endend-- 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-- - 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)
train.dirty =trueif train.no_step then return endassertdef(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
advtrains.update_trainpart_properties(id)end--restore pathif not train.path thenif not train.last_pos thenatwarn("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 =truereturnendif not train.last_connid thenatwarn("Train",id,": Restoring path: no last_connid set! Will assume 1")endlocal result = advtrains.path_create(train, train.last_pos, train.last_connid or1, train.last_frac or0)if result==false thenatwarn("Train",id,": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to fix the issue in the save file.")
train.no_step =truereturnelseif result==nil thenif not train.wait_for_path thenatwarn("Train",id,": Can't initialize: Waiting for the (yet unloaded) node at",train.last_pos," to be loaded.")end
train.wait_for_path =trueend-- 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 callbacksrun_callbacks_new_path(id, train)end
train.dirty =false-- TODO einbauen!endfunction advtrains.train_step_b(id, train, dtime)if train.no_step or train.wait_for_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))--- 3. handle velocity influences ---local train_moves=(train.velocity~=0)local tarvel_cap
if train.recently_collided_with_env then
tarvel_cap=0
train.active_control=falseif not train_moves then
train.recently_collided_with_env=nil--reset status when stoppedendendif train.locomotives_in_train==0then
tarvel_cap=0end--- 3a. this can be useful for debugs/warnings and is used for check_trainpartload ---local t_info, train_pos=sid(id), advtrains.path_get(train,atfloor(train.index))if train_pos then
t_info=t_info.." @"..minetest.pos_to_string(train_pos)--atprint("train_pos:",train_pos)end--apply off-track handling:local front_off_track = train.index>train.path_trk_f
local back_off_track=train.end_index<train.path_trk_b
local pprint
if front_off_track then
tarvel_cap=0endif back_off_track then-- eventually overrides front_off_track restriction
tarvel_cap=1end--interpret ATC command and apply auto-lever control when not actively controlledlocal trainvelocity = train.velocity
if not train.lever then train.lever=3endif train.active_control then
advtrains.atc.train_reset_command(id)elselocal braketar = train.atc_brake_target
local emerg =false-- atc_brake_target==-1 means emergency brake (BB command)if braketar == -1then
braketar =0
emerg =trueendif braketar and braketar>=trainvelocity then
train.atc_brake_target=nil
braketar =nilendif train.atc_wait_finish thenif not train.atc_brake_target and train.velocity==train.tarvelocity then
train.atc_wait_finish=nilendendif train.atc_command thenif train.atc_delay<=0and not train.atc_wait_finish then
advtrains.atc.execute_atc_command(id, train)else
train.atc_delay=train.atc_delay-dtime
endend
train.lever =3if train.tarvelocity>trainvelocity then train.lever=4endif train.tarvelocity<trainvelocity thenif(braketar and braketar<trainvelocity)thenif emerg then
train.lever =0else
train.lever=1endelse
train.lever=2endendendif tarvel_cap and tarvel_cap<train.tarvelocity then
train.tarvelocity=tarvel_cap
endlocal tmp_lever = train.lever
if tarvel_cap and trainvelocity>tarvel_cap then
tmp_lever =0end--- 3a. actually calculate new velocity ---if tmp_lever~=3thenlocal acc_all = t_accel_all[tmp_lever]local acc_eng = t_accel_eng[tmp_lever]local nwagons = #train.trainparts
local accel = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
local vdiff = accel*dtime
if not train.active_control thenlocal tvdiff = train.tarvelocity - trainvelocity
if math.abs(vdiff) > math.abs(tvdiff)then--applying this change would cross tarvelocity
vdiff=tvdiff
endendif tarvel_cap and trainvelocity<=tarvel_cap and trainvelocity+vdiff>tarvel_cap then
vdiff = tarvel_cap - train.velocity
endif trainvelocity+vdiff <0then
vdiff = - trainvelocity
endlocal mspeed = (train.max_speed or10)if trainvelocity+vdiff > mspeed then
vdiff = mspeed - trainvelocity
end
train.acceleration=vdiff
train.velocity=train.velocity+vdiff
if train.active_control then
train.tarvelocity = train.velocity
endelse
train.acceleration =0end--- 4. move train ---
train.index=train.index and train.index+((train.velocity/(train.path_dist[math.floor(train.index)]or1))*dtime)or0recalc_end_index()endfunction 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 windowrun_callbacks_update(id, train)
advtrains.path_clear_unused(train)
advtrains.path_setrestore(train)-- less important stuff
train.check_trainpartload=(train.check_trainpartload or0)-dtime
if train.check_trainpartload<=0then
advtrains.spawn_wagons(id)
train.check_trainpartload=2end--- 8. check for collisions with other trains and damage players ---local train_moves=(train.velocity~=0)if train_moves thenlocal collpos
local coll_grace=1local collindex = advtrains.path_get_index_by_offset(train, train.index, coll_grace)
collpos = advtrains.path_get(train,atround(collindex))if collpos thenlocal rcollpos=advtrains.round_vector_floor_y(collpos)for x=-train.extent_h,train.extent_h dofor z=-train.extent_h,train.extent_h dolocal testpos=vector.add(rcollpos, {x=x, y=0, z=z})--- 8a Check collision ---if advtrains.occ.check_collision(testpos, id)then--collides--advtrains.collide_and_spawn_couple(id, testpos, advtrains.detector.get(testpos, id), train.movedir==-1)
train.velocity =0
train.tarvelocity =0atwarn("Train",id,"collided!")end--- 8b damage players ---if not minetest.settings:get_bool("creative_mode")thenlocal testpts = minetest.pos_to_string(testpos)local player=advtrains.playersbypts[testpts]if player and not minetest.check_player_privs(player,"creative")and train.velocity>3then--instantly kill player--drop inventory contents first, to not to spawn boneslocal player_inv=player:get_inventory()for i=1,player_inv:get_size("main")do
minetest.add_item(testpos, player_inv:get_stack("main", i))endfor 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", {})
player:set_hp(0)endendendend--- 8c damage other objects ---local objs = minetest.get_objects_inside_radius(rcollpos,2)for _,obj inipairs(objs)doif not obj:is_player()and obj:get_armor_groups().fleshy and obj:get_armor_groups().fleshy >0and 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)endendendendend-- Default occupation callbacks for node callbacks-- (remember, train.end_index is set separately because callbacks are-- asserted to rely on this)local functiontnc_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 caselocal 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)endendlocal functiontnc_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 caselocal 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)endend
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 +1local pos = advtrains.round_vector_floor_y(advtrains.path_get(old_index))tnc_call_enter_callback(pos, id)endwhile old_end_index > new_end_index dolocal pos = advtrains.round_vector_floor_y(advtrains.path_get(old_end_index))tnc_call_leave_callback(pos, id)
old_end_index = old_end_index -1endend)--TODO: Collisions!--returns new idfunction 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 uniquenesslocal 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)
advtrains.train_ensure_clean(new_id, advtrains.trains[new_id])return new_id
endfunction advtrains.remove_train(id)local train = advtrains.trains[id]
advtrains.train_ensure_clean(id, train)
advtrains.path_invalidate(train)local tp = train.trainparts
advtrains.trains[id] =nilreturn tp
endfunction advtrains.add_wagon_to_train(wagon_id, train_id, index)local train=advtrains.trains[train_id]
advtrains.train_ensure_clean(train_id, train)if index thentable.insert(train.trainparts, index, wagon_id)elsetable.insert(train.trainparts, wagon_id)end
advtrains.update_trainpart_properties(train_id)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)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=0local count_l=0local shift_dcpl_lock=falsefor i, w_id inipairs(train.trainparts)dolocal data = advtrains.wagons[w_id]-- 1st: update wagon data (pos_in_train a.s.o)if data thenlocal wagon = advtrains.wagon_prototypes[data.type]if not wagon thenatwarn("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+1endif 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 thenfor k,_ inpairs(train.drives_on)doif not wagon.drives_on[k]then
train.drives_on[k]=nilendendend
train.max_speed=math.min(train.max_speed, wagon.max_speed)
train.extent_h = math.max(train.extent_h, wagon.extent_h or1);endend
train.trainlen = rel_pos
train.locomotives_in_train = count_l
end-- This function checks whether entities need to be spawned for certain wagons, and spawns them.function advtrains.spawn_wagons(train_id)local train = advtrains.trains[train_id]for i, w_id inipairs(train.trainparts)dolocal data = advtrains.wagons[w_id]if data thenif not data.object or not data.object:getyaw()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 minetest.get_node_or_nil(pos)thenlocal wt = advtrains.get_wagon_prototype(data)local wagon = minetest.add_entity(pos, wt):get_luaentity()
wagon:set_id(w_id)endendendendendfunction advtrains.split_train_at_wagon(wagon_id)--get trainlocal data = advtrains.wagons[wagon_id]local old_id = data.train_id
local train=advtrains.trains[old_id]local _, wagon = advtrains.get_wagon_prototype(data)
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))-- find new initial path position for this trainlocal pos, connid, frac = advtrains.path_getrestore(train, index)-- build trainparts table, passing it directly to the train constructorlocal tp = {}for k,v inipairs(train.trainparts)doif k>=wagon.pos_in_trainparts thentable.insert(tp, v)
train.trainparts[k]=nilendend--create subtrainlocal newtrain_id=advtrains.create_new_train_at(pos, connid, frac, tp)local newtrain=advtrains.trains[newtrain_id]
train.tarvelocity=0
newtrain.velocity=train.velocity
newtrain.tarvelocity=0
newtrain.couple_lck_back=train.couple_lck_back
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:--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--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 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]for i=advtrains.minN(train2.path), advtrains.maxN(train2.path)doif vector.equals(sr_pos, train2.path[i])thenif train2.path[i+1]and vector.equals(sr_pos_p, train2.path[i+1])then return true endif train2.path[i-1]and vector.equals(sr_pos_p, train2.path[i-1])then return false endreturn nilendendreturn nilendfunction advtrains.collide_and_spawn_couple(id1, pos, id2, t1_is_backpos)if minetest.settings:get_bool("advtrains_disable_collisions")thenreturnendatprint("COLLISION: ",sid(id1)," and ",sid(id2)," at ",pos,", t1_is_backpos=",(t1_is_backpos and"true"or"false"))--TODO:local train1=advtrains.trains[id1]-- do collision
train1.recently_collided_with_env=true
train1.velocity=0.5*train1.velocity
train1.movedir=train1.movedir*-1
train1.tarvelocity=0local train2=advtrains.trains[id2]if not train1 or not train2 then return endlocal found
for i=advtrains.minN(train1.path), advtrains.maxN(train1.path)doif vector.equals(train1.path[i], pos)then
found=trueendendif not found thenatprint("Err: pos not in path. Not spawning a couple")returnendlocal frontpos2=train2.path[atround(train2.detector_old_index)]local backpos2=train2.path[atround(train2.detector_old_end_index)]local t2_is_backpos
atprint("End positions: ",frontpos2,backpos2)
t2_is_backpos = vector.distance(backpos2, pos) < vector.distance(frontpos2, pos)atprint("t2_is_backpos="..(t2_is_backpos and"true"or"false"))local t1_has_couple, t1_couple_lck
if t1_is_backpos then
t1_has_couple=train1.couple_eid_back
t1_couple_lck=train1.couple_lck_back
else
t1_has_couple=train1.couple_eid_front
t1_couple_lck=train1.couple_lck_front
endlocal t2_has_couple, t2_couple_lck
if t2_is_backpos then
t2_has_couple=train2.couple_eid_back
t2_couple_lck=train2.couple_lck_back
else
t2_has_couple=train2.couple_eid_front
t2_couple_lck=train2.couple_lck_front
endif t1_has_couple thenif minetest.object_refs[t1_has_couple]then minetest.object_refs[t1_has_couple]:remove()endendif t2_has_couple thenif minetest.object_refs[t2_has_couple]then minetest.object_refs[t2_has_couple]:remove()endendif t1_couple_lck or t2_couple_lck then
minetest.add_entity(pos,"advtrains:lockmarker")returnendlocal obj=minetest.add_entity(pos,"advtrains:couple")if not obj thenatprint("failed creating object")return endlocal le=obj:get_luaentity()
le.train_id_1=id1
le.train_id_2=id2
le.train1_is_backpos=t1_is_backpos
le.train2_is_backpos=t2_is_backpos
--find in object_refslocal p_aoi
for aoi, compare inpairs(minetest.object_refs)doif compare==obj thenif t1_is_backpos then
train1.couple_eid_back=aoi
else
train1.couple_eid_front=aoi
endif t2_is_backpos then
train2.couple_eid_back=aoi
else
train2.couple_eid_front=aoi
end
p_aoi=aoi
endendatprint("Couple spawned (ActiveObjectID ",p_aoi,")")end--order of trains may be irrelevant in some cases. check opposite cases. TODO does this work?--pos1 and pos2 are just needed to form a median.function advtrains.do_connect_trains(first_id, second_id, player)local first, second=advtrains.trains[first_id], advtrains.trains[second_id]if not first or not second or not first.index or not second.index or not first.end_index or not second.end_index thenreturn falseendif first.couple_lck_back or second.couple_lck_front then-- trains are ordered correctly!if player then
minetest.chat_send_player(player:get_player_name(),"Can't couple: couples locked!")endreturnendlocal first_wagoncnt=#first.trainparts
local second_wagoncnt=#second.trainparts
for _,v inipairs(second.trainparts)dotable.insert(first.trainparts, v)end--kick it like physics (with mass being #wagons)local new_velocity=((first.velocity*first_wagoncnt)+(second.velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)local tmp_cpl_lck=second.couple_lck_back
advtrains.trains[second_id]=nil
advtrains.update_trainpart_properties(first_id)local train1=advtrains.trains[first_id]
train1.velocity=new_velocity
train1.tarvelocity=0
train1.couple_eid_front=nil
train1.couple_eid_back=nil
train1.couple_lck_back=tmp_cpl_lck
return trueendfunction advtrains.invert_train(train_id)local train=advtrains.trains[train_id]
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
advtrains.path_invalidate(train)local old_trainparts=train.trainparts
train.trainparts={}for k,v inipairs(old_trainparts)dotable.insert(train.trainparts,1, v)--notice insertion at first placeend
advtrains.update_trainpart_properties(train_id,true)endfunction advtrains.get_train_at_pos(pos)return advtrains.detector.get(pos)endfunction advtrains.invalidate_all_paths(pos)--if a position is given, only invalidate inside a radius to save performancelocal inv_radius=50atprint("invalidating all paths")for k,v inpairs(advtrains.trains)dolocal exec=trueif pos and v.path and v.index and v.end_index then--start and end pos of the trainlocal cmp1=v.path[atround(v.index)]local cmp2=v.path[atround(v.end_index)]if vector.distance(pos, cmp1)>inv_radius and vector.distance(pos, cmp2)>inv_radius then
exec=falseendendif exec then
advtrains.invalidate_path(k)endendendfunction advtrains.invalidate_path(id)local v=advtrains.trains[id]if not v then return end
advtrains.path_invalidate(v)
v.dirty =trueend--not blocking trains groupfunction advtrains.train_collides(node)if node and minetest.registered_nodes[node.name]and minetest.registered_nodes[node.name].walkable thenif not minetest.registered_nodes[node.name].groups.not_blocking_trains thenreturn trueendendreturn falseendlocal nonblocknodes={"default:fence_wood","default:fence_acacia_wood","default:fence_aspen_wood","default:fence_pine_wood","default:fence_junglewood","default:torch","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 inipairs(nonblocknodes)doif minetest.registered_nodes[name]then
minetest.registered_nodes[name].groups.not_blocking_trains=1endendend)