advtrains/advtrains/trainlogic.lua

1023 lines
35 KiB
Lua

--trainlogic.lua
--controls train entities stuff about connecting/disconnecting/colliding trains and other things
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
advtrains.train_accel_force=2--per second and divided by number of wagons
advtrains.train_brake_force=3--per second, not divided by number of wagons
advtrains.train_roll_force=0.5--per second, not divided by number of wagons, acceleration when rolling without brake
advtrains.train_emerg_force=10--for emergency brakes(when going off track)
advtrains.mainloop_trainlogic=function(dtime)
--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:getpos()))
advtrains.playersbypts[ptspos]=player
end
end
--regular train step
-- do in two steps:
-- a: predict path and add all nodes to the advtrains.detector.on_node table
-- b: check for collisions based on these data
-- (and more)
local t=os.clock()
advtrains.detector.on_node={}
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
advtrains.train_step_a(k, v, dtime)
end
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
advtrains.train_step_b(k, v, dtime)
end
advtrains.atprint_context_tid=nil
advtrains.atprint_context_tid_full=nil
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 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: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 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)
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 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
end
end)
end)
--[[
train step structure:
- legacy stuff
- preparing the initial path and creating index
- setting node coverage old indices
- handle velocity influences:
- off-track
- atc
- player controls
- environment collision
- update index = move
- create path
- call stay_node on all old positions to register train there, for collision system
- do less important stuff such as checking trainpartload or removing
-- break --
- Call enter_node and leave_node callbacks (required here because stay_node needs to be called on all trains first)
- handle train collisions
]]
function advtrains.train_step_a(id, train, dtime)
--atprint("--- runcnt ",advtrains.mainloop_runcnt,": index",train.index,"end_index", train.end_index,"| max_iot", train.max_index_on_track, "min_iot", train.min_index_on_track, "<> pe_min", train.path_extent_min,"pe_max", train.path_extent_max)
if train.min_index_on_track then
assert(math.floor(train.min_index_on_track)==train.min_index_on_track)
end
--- 1. LEGACY STUFF ---
if not train.drives_on or not train.max_speed then
advtrains.update_trainpart_properties(id)
end
--TODO check for all vars to be present
if not train.velocity then
train.velocity=0
end
if not train.movedir or (train.movedir~=1 and train.movedir~=-1) then
train.movedir=1
end
--- 2. prepare initial path and index if needed ---
if not train.index then train.index=0 end
if not train.path then
if not train.last_pos then
--no chance to recover
atwarn("Unable to restore train ",id,": missing last_pos")
advtrains.trains[id]=nil
return false
end
local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.drives_on)
if node_ok==nil then
--block not loaded, do nothing
atprint("last_pos", advtrains.round_vector_floor_y(train.last_pos), "not loaded and not in ndb, waiting")
return nil
elseif node_ok==false then
atprint("Unable to restore train ",id,": No rail at train's position")
return false
end
if not train.last_pos_prev then
--no chance to recover
atwarn("Unable to restore train ",id,": missing last_pos_prev")
advtrains.trains[id]=nil
return false
end
local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos_prev), train.drives_on)
if prevnode_ok==nil then
--block not loaded, do nothing
atprint("last_pos_prev", advtrains.round_vector_floor_y(train.last_pos_prev), "not loaded and not in ndb, waiting")
return nil
elseif prevnode_ok==false then
atprint("Unable to restore train ",id,": No rail at train's position")
return false
end
train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
--restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
--savedpos_off_track_index_offset is set if train went off track. see below.
train.path={}
train.path_dist={}
train.path[0]=train.last_pos
train.path[-1]=train.last_pos_prev
train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
train.path_extent_min=-1
train.path_extent_max=0
train.min_index_on_track=-1
train.max_index_on_track=0
--[[
Bugfix for trains randomly ignoring ATC rails:
- Paths have been invalidated. 1 gets executed and ensures an initial path
- 2a sets train end index. The problem is that path_dist is not known for the whole path, so train end index will be nearly trainlen
- Since the detector indices are also unknown, they get set to the new (wrong) train_end_index. Enter_node calls are not executed for the nodes that lie in between real end_index and trainlen.
- The next step, mistake is recognized, train leaves some positions. From there, everything works again.
To overcome this, we will generate the full required path here so that path_dist is available for get_train_end_index().
]]
advtrains.pathpredict(id, train)
end
--- 2a. set train.end_index which is required in different places, IF IT IS NOT SET YET by STMT afterwards. ---
--- table entry to avoid triple recalculation ---
if not train.end_index then
train.end_index=advtrains.get_train_end_index(train)
end
--- 2b. set node coverage old indices ---
train.detector_old_index = atround(train.index)
train.detector_old_end_index = atround(train.end_index)
--- 3. handle velocity influences ---
local train_moves=(train.velocity~=0)
if train.recently_collided_with_env then
train.tarvelocity=0
if not train_moves then
train.recently_collided_with_env=false--reset status when stopped
end
end
if train.locomotives_in_train==0 then
train.tarvelocity=0
end
--- 3a. this can be useful for debugs/warnings and is used for check_trainpartload ---
local t_info, train_pos=sid(id), train.path[atround(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.max_index_on_track and train.index>train.max_index_on_track
local back_off_track=train.min_index_on_track and train.end_index<train.min_index_on_track
local pprint
if front_off_track and back_off_track then--allow movement in both directions
if train.tarvelocity>1 then
train.tarvelocity=1
atwarn("Train",t_info,"is off track at both ends. Clipping velocity to 1")
pprint=true
end
elseif front_off_track then--allow movement only backward
if train.movedir==1 and train.tarvelocity>0 then
train.tarvelocity=0
atwarn("Train",t_info,"is off track. Trying to drive further out. Velocity clipped to 0")
pprint=true
end
if train.movedir==-1 and train.tarvelocity>1 then
train.tarvelocity=1
atwarn("Train",t_info,"is off track. Velocity clipped to 1")
pprint=true
end
elseif back_off_track then--allow movement only forward
if train.movedir==-1 and train.tarvelocity>0 then
train.tarvelocity=0
atwarn("Train",t_info,"is off track. Trying to drive further out. Velocity clipped to 0")
pprint=true
end
if train.movedir==1 and train.tarvelocity>1 then
train.tarvelocity=1
atwarn("Train",t_info,"is off track. Velocity clipped to 1")
pprint=true
end
end
if pprint then
atprint("max_iot", train.max_index_on_track, "min_iot", train.min_index_on_track, "<> index", train.index, "end_index", train.end_index)
end
--interpret ATC command
if train.atc_brake_target and train.atc_brake_target>=train.velocity then
train.atc_brake_target=nil
end
if train.atc_wait_finish then
if not train.atc_brake_target and train.velocity==train.tarvelocity then
train.atc_wait_finish=nil
end
end
if train.atc_command then
if 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
end
--make brake adjust the tarvelocity if necessary
if train.brake and (math.ceil(train.velocity)-1)<train.tarvelocity then
train.tarvelocity=math.max((math.ceil(train.velocity)-1), 0)
end
--- 3a. actually calculate new velocity ---
if train.velocity~=train.tarvelocity then
local applydiff=0
local mass=#train.trainparts
local diff=train.tarvelocity-train.velocity
if diff>0 then--accelerating, force will be brought on only by locomotives.
--atprint("accelerating with default force")
applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff)))
else--decelerating
if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass.
--atprint("braking with emergency force")
applydiff= -(math.min((advtrains.train_emerg_force*dtime), math.abs(diff)))
elseif train.brake or (train.atc_brake_target and train.atc_brake_target<train.velocity) then
--atprint("braking with default force")
--no math.min, because it can grow beyond tarvelocity, see up there
--dont worry, it will never fall below zero.
applydiff= -((advtrains.train_brake_force*dtime))
else
--atprint("roll")
applydiff= -(math.min((advtrains.train_roll_force*dtime), math.abs(diff)))
end
end
train.last_accel=(applydiff*train.movedir)
train.velocity=math.min(math.max( train.velocity+applydiff , 0), train.max_speed or 10)
else
train.last_accel=0
end
--- 4. move train ---
train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
--- 4a. update train.end_index to the new position ---
train.end_index=advtrains.get_train_end_index(train)
--- 5. extend path as necessary ---
--why this is an extra function, see under 3.
advtrains.pathpredict(id, train, true)
--- 5a. make pos/yaw available for possible recover calls ---
if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here
train.savedpos_off_track_index_offset=atround(train.index)-train.max_index_on_track
train.last_pos=train.path[train.max_index_on_track]
train.last_pos_prev=train.path[train.max_index_on_track-1]
atprint("train is off-track (front), last positions kept at", train.last_pos, "/", train.last_pos_prev)
elseif train.min_index_on_track+1>train.index then --whoops, train went even more far. same behavior
train.savedpos_off_track_index_offset=atround(train.index)-train.min_index_on_track
train.last_pos=train.path[train.min_index_on_track+1]
train.last_pos_prev=train.path[train.min_index_on_track]
atprint("train is off-track (back), last positions kept at", train.last_pos, "/", train.last_pos_prev)
else --regular case
train.savedpos_off_track_index_offset=nil
train.last_pos=train.path[math.floor(train.index+1)]
train.last_pos_prev=train.path[math.floor(train.index)]
end
--- 5b. Remove path items that are no longer used ---
-- Necessary since path items are no longer invalidated in save steps
local path_pregen_keep=20
local offtrack_keep=4
local gen_front_keep= path_pregen_keep
local gen_back_keep= atround(- train.trainlen - path_pregen_keep)
local delete_min=math.min(train.max_index_on_track - offtrack_keep, atround(train.index)+gen_back_keep)
local delete_max=math.max(train.min_index_on_track + offtrack_keep, atround(train.index)+gen_front_keep)
if train.path_extent_min<delete_min then
--atprint(sid(id),"clearing path min ",train.path_extent_min," to ",delete_min)
for i=train.path_extent_min,delete_min-1 do
train.path[i]=nil
train.path_dist[i]=nil
end
train.path_extent_min=delete_min
train.min_index_on_track=math.max(train.min_index_on_track, delete_min)
end
if train.path_extent_max>delete_max then
--atprint(sid(id),"clearing path max ",train.path_extent_max," to ",delete_max)
train.path_dist[delete_max]=nil
for i=delete_max+1,train.path_extent_max do
train.path[i]=nil
train.path_dist[i]=nil
end
train.path_extent_max=delete_max
train.max_index_on_track=math.min(train.max_index_on_track, delete_max)
end
--- 6b. call stay_node to register trains in the location table - actual enter_node stuff is done in step b ---
local ifo, ibo = train.detector_old_index, train.detector_old_end_index
local path=train.path
for i=ibo, ifo do
if path[i] then
advtrains.detector.stay_node(path[i], id)
end
end
--- 7. do less important stuff ---
--check for any trainpart entities if they have been unloaded. do this only if train is near a player, to not spawn entities into unloaded areas
train.check_trainpartload=(train.check_trainpartload or 0)-dtime
local node_range=(math.max((minetest.settings:get("active_block_range") or 0),1)*16)
if train.check_trainpartload<=0 then
local ori_pos=train_pos --see 3a.
--atprint("[train "..id.."] at "..minetest.pos_to_string(vector.round(ori_pos)))
local should_check=false
for _,p in ipairs(minetest.get_connected_players()) do
should_check=should_check or (ori_pos and ((vector.distance(ori_pos, p:getpos())<node_range)))
end
if should_check then
advtrains.update_trainpart_properties(id)
end
train.check_trainpartload=2
end
--remove?
if #train.trainparts==0 then
atprint("[train "..sid(id).."] has empty trainparts, removing.")
advtrains.detector.leave_node(path[train.detector_old_index], id)
advtrains.trains[id]=nil
return
end
end
--about regular: Used by 1. to ensure path gets generated far enough, since end index is not known at this time.
function advtrains.pathpredict(id, train, regular)
--TODO duplicate code under 5b.
local path_pregen=10
local gen_front= path_pregen
local gen_back= - train.trainlen - path_pregen
if regular then
gen_front=math.max(train.index, train.detector_old_index) + path_pregen
gen_back=math.min(train.end_index, train.detector_old_end_index) - path_pregen
end
local maxn=train.path_extent_max or 0
while maxn < gen_front do--pregenerate
local conway
if train.max_index_on_track == maxn then
--atprint("maxn conway for ",maxn,train.path[maxn],maxn-1,train.path[maxn-1])
conway=advtrains.conway(train.path[maxn], train.path[maxn-1], train.drives_on)
end
if conway then
train.path[maxn+1]=conway
train.max_index_on_track=maxn+1
else
--do as if nothing has happened and preceed with path
--but do not update max_index_on_track
atprint("over-generating path max to index ",(maxn+1)," (position ",train.path[maxn]," )")
train.path[maxn+1]=vector.add(train.path[maxn], vector.subtract(train.path[maxn], train.path[maxn-1]))
end
train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn])
maxn=maxn+1
end
train.path_extent_max=maxn
local minn=train.path_extent_min or -1
while minn > gen_back do
local conway
if train.min_index_on_track == minn then
--atprint("minn conway for ",minn,train.path[minn],minn+1,train.path[minn+1])
conway=advtrains.conway(train.path[minn], train.path[minn+1], train.drives_on)
end
if conway then
train.path[minn-1]=conway
train.min_index_on_track=minn-1
else
--do as if nothing has happened and preceed with path
--but do not update min_index_on_track
atprint("over-generating path min to index ",(minn-1)," (position ",train.path[minn]," )")
train.path[minn-1]=vector.add(train.path[minn], vector.subtract(train.path[minn], train.path[minn+1]))
end
train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1])
minn=minn-1
end
train.path_extent_min=minn
if not train.min_index_on_track then train.min_index_on_track=-1 end
if not train.max_index_on_track then train.max_index_on_track=0 end
end
function advtrains.train_step_b(id, train, dtime)
--hacky fix: if train_step_a returned in phase 2, end_index may not be set.
--just return
if not train.end_index then
return
end
--- 6. update node coverage ---
-- when paths get cleared, the old indices set above will be up-to-date and represent the state in which the last run of this code was made
local ifo, ifn, ibo, ibn = train.detector_old_index, atround(train.index), train.detector_old_end_index, atround(train.end_index)
--atprint(ifo,">", ifn, "<==>", ibo,">", ibn)
local path=train.path
if train.enter_node_all then
--field set by create_new_train_at.
--ensures that new train calls enter_node on all nodes
for i=ibn, ifn do
if path[i] then
advtrains.detector.enter_node(path[i], id)
end
end
train.enter_node_all=nil
else
if ifn>ifo then
for i=ifo+1, ifn do
if path[i] then
if advtrains.detector.occupied(path[i], id) then
--if another train has signed up for this position first, it won't be recognized in train_step_b. So do collision here.
atprint("Collision detected in enter_node callbacks (front) @",path[i],"with",sid(advtrains.detector.get(path[i], id)))
advtrains.collide_and_spawn_couple(id, path[i], advtrains.detector.get(path[i], id), false)
end
atprint("enter_node (front) @index",i,"@",path[i],"on_node",sid(advtrains.detector.get(path[i], id)))
advtrains.detector.enter_node(path[i], id)
end
end
elseif ifn<ifo then
for i=ifn+1, ifo do
if path[i] then
advtrains.detector.leave_node(path[i], id)
end
end
end
if ibn<ibo then
for i=ibn, ibo-1 do
if path[i] then
local pts=minetest.pos_to_string(path[i])
if advtrains.detector.occupied(path[i], id) then
--if another train has signed up for this position first, it won't be recognized in train_step_b. So do collision here.
atprint("Collision detected in enter_node callbacks (back) @",path[i],"with",sid(advtrains.detector.get(path[i], id)))
advtrains.collide_and_spawn_couple(id, path[i], advtrains.detector.get(path[i], id), true)
end
atprint("enter_node (back) @index",i,"@",path[i],"on_node",sid(advtrains.detector.get(path[i], id)))
advtrains.detector.enter_node(path[i], id)
end
end
elseif ibn>ibo then
for i=ibo, ibn-1 do
if path[i] then
advtrains.detector.leave_node(path[i], id)
end
end
end
end
--- 8. check for collisions with other trains and damage players ---
local train_moves=(train.velocity~=0)
if train_moves then
--TO BE REMOVED:
if not train.extent_h then advtrains.update_trainpart_properties(id) end
local collpos
local coll_grace=1
if train.movedir==1 then
collpos=advtrains.get_real_index_position(train.path, train.index-coll_grace)
else
collpos=advtrains.get_real_index_position(train.path, train.end_index+coll_grace)
end
if collpos then
local rcollpos=advtrains.round_vector_floor_y(collpos)
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 advtrains.detector.occupied(testpos, id) then
--collides
advtrains.collide_and_spawn_couple(id, testpos, advtrains.detector.get(testpos, id), train.movedir==-1)
end
--- 8b damage players ---
if not minetest.settings:get_bool("creative_mode") then
local testpts = minetest.pos_to_string(testpos)
local player=advtrains.playersbypts[testpts]
if player and not minetest.check_player_privs(player, "creative") and train.velocity>3 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", {})
player:set_hp(0)
end
end
end
end
--- 8c damage other objects ---
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
--returns new id
function advtrains.create_new_train_at(pos, pos_prev)
local newtrain_id=os.time()..os.clock()
while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary)
advtrains.trains[newtrain_id]={}
advtrains.trains[newtrain_id].last_pos=pos
advtrains.trains[newtrain_id].last_pos_prev=pos_prev
advtrains.trains[newtrain_id].tarvelocity=0
advtrains.trains[newtrain_id].velocity=0
advtrains.trains[newtrain_id].trainparts={}
advtrains.trains[newtrain_id].enter_node_all=true
return newtrain_id
end
function advtrains.get_train_end_index(train)
return advtrains.get_real_path_index(train, train.trainlen or 2)--this function can be found inside wagons.lua since it's more related to wagons. we just set trainlen as pos_in_train
end
function advtrains.add_wagon_to_train(wagon, train_id, index)
local train=advtrains.trains[train_id]
if index then
table.insert(train.trainparts, index, wagon.unique_id)
else
table.insert(train.trainparts, wagon.unique_id)
end
--this is not the usual case!!!
--we may set initialized because the wagon has no chance to step()
wagon.initialized=true
--TODO is this art or can we throw it away?
advtrains.update_trainpart_properties(train_id)
end
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
for i, w_id in ipairs(train.trainparts) do
local wagon=nil
for aoid,iwagon in pairs(minetest.luaentities) do
if iwagon.is_wagon and iwagon.unique_id==w_id then
if wagon then
--duplicate
atprint("update_trainpart_properties: Removing duplicate wagon with id="..aoid)
iwagon.object:remove()
else
wagon=iwagon
end
end
end
if not wagon then
if advtrains.wagon_save[w_id] then
--spawn a new and initialize it with the properties from wagon_save
wagon=minetest.add_entity(train.last_pos, advtrains.wagon_save[w_id].entity_name):get_luaentity()
if not wagon then
minetest.chat_send_all("[advtrains] Warning: Wagon "..advtrains.wagon_save[w_id].entity_name.." does not exist. Make sure all required modules are loaded!")
else
wagon:init_from_wagon_save(w_id)
end
end
end
if wagon then
rel_pos=rel_pos+wagon.wagon_span
wagon.train_id=train_id
wagon.pos_in_train=rel_pos
wagon.pos_in_trainparts=i
wagon.old_velocity_vector=nil
if wagon.is_locomotive then
count_l=count_l+1
end
if invert_flipstate then
wagon.wagon_flipped = not wagon.wagon_flipped
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);
if i==1 then
train.couple_lock_front=wagon.lock_couples
end
if i==#train.trainparts then
train.couple_lock_back=wagon.lock_couples
end
else
atwarn("Did not find save data for wagon",w_id,". The wagon will be deleted.")
--what the hell...
table.remove(train.trainparts, pit)
end
end
train.trainlen=rel_pos
train.locomotives_in_train=count_l
end
function advtrains.split_train_at_wagon(wagon)
--get train
local train=advtrains.trains[wagon.train_id]
if not train.path then return end
local real_pos_in_train=advtrains.get_real_path_index(train, wagon.pos_in_train)
local pos_for_new_train=train.path[math.floor(real_pos_in_train+wagon.wagon_span)]
local pos_for_new_train_prev=train.path[math.floor(real_pos_in_train+wagon.wagon_span-1)]
--before doing anything, check if both are rails. else do not allow
if not pos_for_new_train then
atprint("split_train: pos_for_new_train not set")
return false
end
local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(pos_for_new_train), 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
if not train.last_pos_prev then
atprint("split_train: pos_for_new_train_prev not set")
return false
end
local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(pos_for_new_train), train.drives_on)
if not prevnode_ok then
atprint("split_train: pos_for_new_train_prev ", advtrains.round_vector_floor_y(pos_for_new_train_prev), " not loaded or is not a rail")
return false
end
--create subtrain
local newtrain_id=advtrains.create_new_train_at(pos_for_new_train, pos_for_new_train_prev)
local newtrain=advtrains.trains[newtrain_id]
--insert all wagons to new train
for k,v in ipairs(train.trainparts) do
if k>=wagon.pos_in_trainparts then
table.insert(newtrain.trainparts, v)
train.trainparts[k]=nil
end
end
--update train parts
advtrains.update_trainpart_properties(wagon.train_id)--atm it still is the desierd id.
advtrains.update_trainpart_properties(newtrain_id)
train.tarvelocity=0
newtrain.velocity=train.velocity
newtrain.tarvelocity=0
newtrain.enter_node_all=true
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.
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) do
if vector.equals(sr_pos, train2.path[i]) then
if train2.path[i+1] and vector.equals(sr_pos_p, train2.path[i+1]) then return true end
if train2.path[i-1] and vector.equals(sr_pos_p, train2.path[i-1]) then return false end
return nil
end
end
return nil
end
function advtrains.collide_and_spawn_couple(id1, pos, id2, t1_is_backpos)
if minetest.settings:get_bool("advtrains_disable_collisions") then
return
end
atprint("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=0
local train2=advtrains.trains[id2]
if not train1 or not train2 then return end
local found
for i=advtrains.minN(train1.path), advtrains.maxN(train1.path) do
if vector.equals(train1.path[i], pos) then
found=true
end
end
if not found then
atprint("Err: pos not in path. Not spawning a couple")
return
end
local 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
if t1_is_backpos then
t1_has_couple=train1.couple_eid_back
else
t1_has_couple=train1.couple_eid_front
end
local t2_has_couple
if t2_is_backpos then
t2_has_couple=train2.couple_eid_back
else
t2_has_couple=train2.couple_eid_front
end
if t1_has_couple then
if minetest.object_refs[t1_has_couple] then minetest.object_refs[t1_has_couple]:remove() end
end
if t2_has_couple then
if minetest.object_refs[t2_has_couple] then minetest.object_refs[t2_has_couple]:remove() end
end
local obj=minetest.add_entity(pos, "advtrains:couple")
if not obj then atprint("failed creating object") return end
local 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_refs
local p_aoi
for aoi, compare in pairs(minetest.object_refs) do
if compare==obj then
if t1_is_backpos then
train1.couple_eid_back=aoi
else
train1.couple_eid_front=aoi
end
if t2_is_backpos then
train2.couple_eid_back=aoi
else
train2.couple_eid_front=aoi
end
p_aoi=aoi
end
end
atprint("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 then
return false
end
if first.couple_lock_back or second.couple_lock_front then
-- trains are ordered correctly!
if player then
minetest.chat_send_player(player:get_player_name(), "Can't couple: couples locked!")
end
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
--kick it like physics (with mass being #wagons)
local new_velocity=((first.velocity*first_wagoncnt)+(second.velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)
advtrains.trains[second_id]=nil
advtrains.update_trainpart_properties(first_id)
advtrains.trains[first_id].velocity=new_velocity
advtrains.trains[first_id].tarvelocity=0
return true
end
function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id]
local old_path=train.path
local old_path_dist=train.path_dist
train.path={}
train.path_dist={}
train.index, train.end_index= -train.end_index, -train.index
train.path_extent_min, train.path_extent_max = -train.path_extent_max, -train.path_extent_min
train.min_index_on_track, train.max_index_on_track = -train.max_index_on_track, -train.min_index_on_track
train.detector_old_index, train.detector_old_end_index = -train.detector_old_end_index, -train.detector_old_index
train.velocity=-train.velocity
train.tarvelocity=-train.tarvelocity
for k,v in pairs(old_path) do
train.path[-k]=v
train.path_dist[-k-1]=old_path_dist[k]
end
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)
end
function advtrains.get_train_at_pos(pos)
return advtrains.detector.get(pos)
end
function advtrains.invalidate_all_paths(pos)
--if a position is given, only invalidate inside a radius to save performance
local inv_radius=50
atprint("invalidating all paths")
for k,v in pairs(advtrains.trains) do
local exec=true
if pos and v.path and v.index and v.end_index then
--start and end pos of the train
local 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=false
end
end
if exec then
--TODO duplicate code in init.lua avt_save()!
if v.index then
v.restore_add_index=v.index-math.floor(v.index+1)
end
v.path=nil
v.path_dist=nil
v.index=nil
v.end_index=nil
v.min_index_on_track=nil
v.max_index_on_track=nil
v.path_extent_min=nil
v.path_extent_max=nil
v.detector_old_index=nil
v.detector_old_end_index=nil
end
end
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",
"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)