aboutsummaryrefslogtreecommitdiff
path: root/advtrains/trainlogic.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains/trainlogic.lua')
-rw-r--r--advtrains/trainlogic.lua1005
1 files changed, 1005 insertions, 0 deletions
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
new file mode 100644
index 0000000..483ea52
--- /dev/null
+++ b/advtrains/trainlogic.lua
@@ -0,0 +1,1005 @@
+--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
+- update node coverage
+- do less important stuff such as checking trainpartload or removing
+
+-- break --
+- 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
+ atprint("train hasn't saved last-pos, removing train.")
+ 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("no track here, (fail) removing train.")
+ advtrains.trains[id]=nil
+ return false
+ end
+
+ if not train.last_pos_prev then
+ --no chance to recover
+ atprint("train hasn't saved last-pos_prev, removing train.")
+ 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("no track at prev, (fail) removing train.")
+ advtrains.trains[id]=nil
+ 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 = math.floor(train.index)
+ train.detector_old_end_index = math.floor(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[math.floor(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=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=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+0.5)]
+ train.last_pos_prev=train.path[math.floor(train.index-0.5)]
+ 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= math.floor(- train.trainlen - path_pregen_keep)
+
+ local delete_min=math.min(train.max_index_on_track - offtrack_keep, math.floor(train.index)+gen_back_keep)
+ local delete_max=math.max(train.min_index_on_track + offtrack_keep, math.floor(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
+
+ --- 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, math.floor(train.index), train.detector_old_end_index, math.floor(train.end_index)
+
+ 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
+ for i=ibn, ifn do
+ if path[i] then
+ local pts=minetest.pos_to_string(path[i])
+ if not (advtrains.detector.on_node[pts] and advtrains.detector.on_node[pts]~=id) then
+ advtrains.detector.stay_node(path[i], id)
+ end
+ end
+ end
+
+ if ifn>ifo then
+ for i=ifo+1, ifn do
+ if path[i] then
+ local pts=minetest.pos_to_string(path[i])
+ if advtrains.detector.on_node[pts] and advtrains.detector.on_node[pts]~=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) @",pts,"with",sid(advtrains.detector.on_node[pts]))
+ advtrains.collide_and_spawn_couple(id, path[i], advtrains.detector.on_node[pts], false)
+ end
+ atprint("enter_node (front) @index",i,"@",pts,"on_node",sid(advtrains.detector.on_node[pts]))
+ 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.on_node[pts] and advtrains.detector.on_node[pts]~=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) @",pts,"on_node",sid(advtrains.detector.on_node[pts]))
+ advtrains.collide_and_spawn_couple(id, path[i], advtrains.detector.on_node[pts], true)
+ end
+ atprint("enter_node (back) @index",i,"@",pts,"with",sid(advtrains.detector.on_node[pts]))
+ 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
+
+ --- 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 ((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
+
+ --- 8. check for collisions with other trains and damage players ---
+
+ local train_moves=(train.velocity~=0)
+
+ if train_moves then
+ 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=-1,1 do
+ for z=-1,1 do
+ local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
+ --- 8a Check collision ---
+ local testpts=minetest.pos_to_string(testpos)
+ if advtrains.detector.on_node[testpts] and advtrains.detector.on_node[testpts]~=id then
+ --collides
+ advtrains.collide_and_spawn_couple(id, testpos, advtrains.detector.on_node[testpts], train.movedir==-1)
+ end
+ --- 8b damage players ---
+ if not minetest.settings:get_bool("creative_mode") then
+ local player=advtrains.playersbypts[testpts]
+ if player 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
+ 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.all_tracktypes
+ train.max_speed=20
+ 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)
+ 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
+ atprint(w_id.." not loaded and no save available")
+ --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-1+wagon.wagon_span)]
+
+ --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[math.floor(train1.index)]
+ local sr_pos_p=train1.path[math.floor(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[math.floor(train2.detector_old_index)]
+ local backpos2=train2.path[math.floor(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)
+ local ph=minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
+ return advtrains.detector.on_node[ph]
+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[math.floor(v.index)]
+ local cmp2=v.path[math.floor(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+0.5)
+ 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)