diff options
Diffstat (limited to 'advtrains/trainlogic.lua')
-rw-r--r-- | advtrains/trainlogic.lua | 1005 |
1 files changed, 1005 insertions, 0 deletions
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua new file mode 100644 index 0000000..d8130e9 --- /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 (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 + + --- 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) |