aboutsummaryrefslogtreecommitdiff
path: root/advtrains/trainlogic.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains/trainlogic.lua')
-rw-r--r--advtrains/trainlogic.lua895
1 files changed, 895 insertions, 0 deletions
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
new file mode 100644
index 0000000..5e3936f
--- /dev/null
+++ b/advtrains/trainlogic.lua
@@ -0,0 +1,895 @@
+--trainlogic.lua
+--controls train entities stuff about connecting/disconnecting/colliding trains and other things
+
+--local print=function(t, ...) minetest.log("action", table.concat({t, ...}, " ")) minetest.chat_send_all(table.concat({t, ...}, " ")) end
+local print=function() end
+
+local benchmark=false
+--printbm=function(str, t) print("[advtrains]"..str.." "..((os.clock()-t)*1000).."ms") end
+local bm={}
+local bmlt=0
+local bmsteps=0
+local bmstepint=200
+printbm=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
+
+--TODO: these values need to be integrated when i remove traintypes.
+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.audit_interval=30
+
+advtrains.all_traintypes={}
+function advtrains.register_train_type(name, drives_on, max_speed)
+ advtrains.all_traintypes[name]={}
+ advtrains.all_traintypes[name].drives_on=drives_on
+ advtrains.all_traintypes[name].max_speed=max_speed or 10
+end
+
+
+advtrains.trains={}
+advtrains.wagon_save={}
+
+--load initially
+advtrains.fpath=minetest.get_worldpath().."/advtrains"
+local file, err = io.open(advtrains.fpath, "r")
+if not file then
+ local er=err or "Unknown Error"
+ print("[advtrains]Failed loading advtrains save file "..er)
+else
+ local tbl = minetest.deserialize(file:read("*a"))
+ if type(tbl) == "table" then
+ advtrains.trains=tbl
+ end
+ file:close()
+end
+advtrains.fpath_ws=minetest.get_worldpath().."/advtrains_wagon_save"
+local file, err = io.open(advtrains.fpath_ws, "r")
+if not file then
+ local er=err or "Unknown Error"
+ print("[advtrains]Failed loading advtrains save file "..er)
+else
+ local tbl = minetest.deserialize(file:read("*a"))
+ if type(tbl) == "table" then
+ advtrains.wagon_save=tbl
+ end
+ file:close()
+end
+
+
+advtrains.save = function()
+ --print("[advtrains]saving")
+ advtrains.invalidate_all_paths()
+ local datastr = minetest.serialize(advtrains.trains)
+ if not datastr then
+ minetest.log("error", "[advtrains] Failed to serialize train data!")
+ return
+ end
+ local file, err = io.open(advtrains.fpath, "w")
+ if err then
+ return err
+ end
+ file:write(datastr)
+ file:close()
+
+ -- update wagon saves
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized then
+ wagon:get_staticdata()
+ end
+ end
+ --cross out userdata
+ for w_id, data in pairs(advtrains.wagon_save) do
+ data.name=nil
+ data.object=nil
+ if data.driver then
+ data.driver_name=data.driver:get_player_name()
+ data.driver=nil
+ else
+ data.driver_name=nil
+ end
+ if data.discouple then
+ data.discouple.object:remove()
+ data.discouple=nil
+ end
+ end
+ --print(dump(advtrains.wagon_save))
+ datastr = minetest.serialize(advtrains.wagon_save)
+ if not datastr then
+ minetest.log("error", "[advtrains] Failed to serialize train data!")
+ return
+ end
+ file, err = io.open(advtrains.fpath_ws, "w")
+ if err then
+ return err
+ end
+ file:write(datastr)
+ file:close()
+
+ advtrains.save_trackdb()
+end
+minetest.register_on_shutdown(advtrains.save)
+
+advtrains.save_and_audit_timer=advtrains.audit_interval
+minetest.register_globalstep(function(dtime)
+ advtrains.save_and_audit_timer=advtrains.save_and_audit_timer-dtime
+ if advtrains.save_and_audit_timer<=0 then
+ local t=os.clock()
+ --print("[advtrains] audit step")
+ --clean up orphaned trains
+ for k,v in pairs(advtrains.trains) do
+ --advtrains.update_trainpart_properties(k)
+ if #v.trainparts==0 then
+ print("[advtrains][train "..k.."] has empty trainparts, removing.")
+ advtrains.trains[k]=nil
+ end
+ end
+ --save
+ advtrains.save()
+ advtrains.save_and_audit_timer=advtrains.audit_interval
+ printbm("saving", t)
+ end
+ --regular train step
+ local t=os.clock()
+ for k,v in pairs(advtrains.trains) do
+ advtrains.train_step(k, v, dtime)
+ end
+
+ --see tracks.lua
+ if advtrains.detector.clean_step_before then
+ advtrains.detector.finalize_restore()
+ end
+
+ printbm("trainsteps", t)
+ endstep()
+end)
+
+function advtrains.train_step(id, train, dtime)
+
+ --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
+ --very unimportant thing: check if couple is here
+ if train.couple_eid_front and (not minetest.luaentities[train.couple_eid_front] or not minetest.luaentities[train.couple_eid_front].is_couple) then train.couple_eid_front=nil end
+ if train.couple_eid_back and (not minetest.luaentities[train.couple_eid_back] or not minetest.luaentities[train.couple_eid_back].is_couple) then train.couple_eid_back=nil end
+
+ --skip certain things (esp. collision) when not moving
+ local train_moves=(train.velocity~=0)
+
+ --if not train.last_pos then advtrains.trains[id]=nil return end
+
+ if not advtrains.pathpredict(id, train) then
+ print("pathpredict failed(returned false)")
+ train.velocity=0
+ train.tarvelocity=0
+ return
+ end
+
+ local path=advtrains.get_or_create_path(id, train)
+ if not path then
+ train.velocity=0
+ train.tarvelocity=0
+ print("train has no path for whatever reason")
+ return
+ end
+
+ local train_end_index=advtrains.get_train_end_index(train)
+ --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
+ if front_off_track and back_off_track then--allow movement in both directions
+ if train.tarvelocity>1 then train.tarvelocity=1 end
+ elseif front_off_track then--allow movement only backward
+ if train.movedir==1 and train.tarvelocity>0 then train.tarvelocity=0 end
+ if train.movedir==-1 and train.tarvelocity>1 then train.tarvelocity=1 end
+ elseif back_off_track then--allow movement only forward
+ if train.movedir==-1 and train.tarvelocity>0 then train.tarvelocity=0 end
+ if train.movedir==1 and train.tarvelocity>1 then train.tarvelocity=1 end
+ end
+
+ --update advtrains.detector
+ if not train.detector_old_index then
+ train.detector_old_index = math.floor(train_end_index)
+ train.detector_old_end_index = math.floor(train_end_index)
+ end
+ local ifo, ifn, ibo, ibn = train.detector_old_index, math.floor(train.index), train.detector_old_end_index, math.floor(train_end_index)
+ if ifn>ifo then
+ for i=ifo, ifn do
+ if path[i] then
+ advtrains.detector.enter_node(path[i], id)
+ end
+ end
+ elseif ifn<ifo then
+ for i=ifn, ifo do
+ if path[i] then
+ advtrains.detector.leave_node(path[i], id)
+ end
+ end
+ end
+ if ibn<ibo then
+ for i=ibn, ibn do
+ if path[i] then
+ advtrains.detector.enter_node(path[i], id)
+ end
+ end
+ elseif ibn>ibo then
+ for i=ibo, ibn do
+ if path[i] then
+ advtrains.detector.leave_node(path[i], id)
+ end
+ end
+ end
+ train.detector_old_index = math.floor(train.index)
+ train.detector_old_end_index = math.floor(train_end_index)
+
+ if train_moves then
+ --check for collisions by finding objects
+ --front
+ local search_radius=4
+
+ --coupling
+ local couple_outward=1
+ local posfront=advtrains.get_real_index_position(path, train.index+couple_outward)
+ local posback=advtrains.get_real_index_position(path, train_end_index-couple_outward)
+ for _,pos in ipairs({posfront, posback}) do
+ if pos then
+ local objrefs=minetest.get_objects_inside_radius(pos, search_radius)
+ for _,v in pairs(objrefs) do
+ local le=v:get_luaentity()
+ if le and le.is_wagon and le.initialized and le.train_id~=id then
+ advtrains.try_connect_trains(id, le.train_id)
+ end
+ end
+ end
+ end
+ --heh, new collision again.
+ --this time, based on NODES and the advtrains.detector.on_node table.
+ local collpos
+ local coll_grace=1
+ if train.movedir==1 then
+ collpos=advtrains.get_real_index_position(path, train.index-coll_grace)
+ else
+ collpos=advtrains.get_real_index_position(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 testpts=minetest.pos_to_string(vector.add(rcollpos, {x=x, y=0, z=z}))
+ if advtrains.detector.on_node[testpts] and advtrains.detector.on_node[testpts]~=id then
+ --collides
+ train.recently_collided_with_env=true
+ train.velocity=0.5*train.velocity
+ train.movedir=train.movedir*-1
+ train.tarvelocity=0
+ end
+ end
+ end
+ end
+ end
+ --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.setting_get("active_block_range") or 0),1)*16)
+ if train.check_trainpartload<=0 then
+ local ori_pos=advtrains.get_real_index_position(path, train.index) --not much to calculate
+ print("[advtrains][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
+ --it is better to iterate luaentites only once
+ --print("check_trainpartload")
+ local found_uids={}
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
+ if found_uids[wagon.unique_id] then
+ --duplicate found, delete it
+ if wagon.object then wagon.object:remove() end
+ else
+ found_uids[wagon.unique_id]=true
+ end
+ end
+ end
+ --print("found_uids: "..dump(found_uids))
+ --now iterate trainparts and check. then cross them out to see if there are wagons over for any reason
+ for pit, w_id in ipairs(train.trainparts) do
+ if found_uids[w_id] then
+ --print(w_id.." still loaded")
+ elseif advtrains.wagon_save[w_id] then
+ --print(w_id.." not loaded, but save available")
+ --spawn a new and initialize it with the properties from wagon_save
+ local le=minetest.env:add_entity(ori_pos, advtrains.wagon_save[w_id].entity_name):get_luaentity()
+ le:init_from_wagon_save(w_id)
+ else
+ print(w_id.." not loaded and no save available")
+ --what the hell...
+ table.remove(train.trainparts, pit)
+ end
+ end
+ end
+ train.check_trainpartload=2
+ end
+
+
+ --handle collided_with_env
+ 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
+ --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
+ --apply tarvel(but with physics in mind!)
+ 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.
+ --print("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.
+ --print("braking with emergency force")
+ applydiff= -(math.min((advtrains.train_emerg_force*dtime), math.abs(diff)))
+ elseif train.brake then
+ --print("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
+ --print("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), advtrains.all_traintypes[train.traintype].max_speed)
+ else
+ train.last_accel=0
+ end
+
+ --move
+ --TODO 3,5 + 0.7
+ train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
+
+end
+
+
+--structure of train table:
+--[[
+trains={
+ [train_id]={
+ trainparts={
+ [n]=wagon_id
+ }
+ path={path}
+ velocity
+ tarvelocity
+ index
+ trainlen
+ path_inv_level
+ last_pos |
+ last_dir | for pathpredicting.
+ }
+}
+--a wagon itself has the following properties:
+wagon={
+ unique_id
+ train_id
+ pos_in_train (is index difference, including train_span stuff)
+ pos_in_trainparts (is index in trainparts tabel of trains)
+}
+inherited by metatable:
+wagon_proto={
+ wagon_span
+}
+]]
+
+--returns new id
+function advtrains.create_new_train_at(pos, pos_prev, traintype)
+ 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].traintype=traintype
+ advtrains.trains[newtrain_id].tarvelocity=0
+ advtrains.trains[newtrain_id].velocity=0
+ advtrains.trains[newtrain_id].trainparts={}
+ return newtrain_id
+end
+
+--returns false on failure. handle this case!
+function advtrains.pathpredict(id, train)
+
+ --print("pos ",x,y,z)
+ --::rerun::
+ if not train.index then train.index=0 end
+ if not train.path or #train.path<2 then
+ if not train.last_pos then
+ --no chance to recover
+ print("[advtrains]train hasn't saved last-pos, removing train.")
+ advtrains.train[id]=nil
+ return false
+ end
+
+ local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.traintype)
+
+ if node_ok==nil then
+ --block not loaded, do nothing
+ return nil
+ elseif node_ok==false then
+ print("[advtrains]no track here, (fail) removing train.")
+ advtrains.trains[id]=nil
+ return false
+ end
+
+ if not train.last_pos_prev then
+ --no chance to recover
+ print("[advtrains]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.traintype)
+
+ if prevnode_ok==nil then
+ --block not loaded, do nothing
+ return nil
+ elseif prevnode_ok==false then
+ print("[advtrains]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)
+ end
+
+ local pregen_front=2
+ local pregen_back=2
+ if train.velocity>0 then
+ if train.movedir>0 then
+ pregen_front=2+math.ceil(train.velocity*0.15) --assumes server step of 0.1 seconds, +50% tolerance
+ else
+ pregen_back=2+math.ceil(train.velocity*0.15)
+ end
+ end
+
+
+ local maxn=advtrains.maxN(train.path)
+ while (maxn-train.index) < pregen_front do--pregenerate
+ --print("[advtrains]maxn conway for ",maxn,minetest.pos_to_string(path[maxn]),maxn-1,minetest.pos_to_string(path[maxn-1]))
+ local conway=advtrains.conway(train.path[maxn], train.path[maxn-1], train.traintype)
+ if conway then
+ train.path[maxn+1]=conway
+ train.max_index_on_track=maxn
+ else
+ --do as if nothing has happened and preceed with path
+ --but do not update max_index_on_track
+ --print("over-generating path max to index "..maxn+1)
+ 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=advtrains.maxN(train.path)
+ end
+
+ local minn=advtrains.minN(train.path)
+ while (train.index-minn) < (train.trainlen or 0) + pregen_back do --post_generate. has to be at least trainlen. (we let go of the exact calculation here since this would be unuseful here)
+ --print("[advtrains]minn conway for ",minn,minetest.pos_to_string(path[minn]),minn+1,minetest.pos_to_string(path[minn+1]))
+ local conway=advtrains.conway(train.path[minn], train.path[minn+1], train.traintype)
+ if conway then
+ train.path[minn-1]=conway
+ train.min_index_on_track=minn
+ else
+ --do as if nothing has happened and preceed with path
+ --but do not update min_index_on_track
+ --print("over-generating path min to index "..minn-1)
+ 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=advtrains.minN(train.path)
+ end
+ if not train.min_index_on_track then train.min_index_on_track=0 end
+ if not train.max_index_on_track then train.max_index_on_track=0 end
+
+ --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]
+ --print("train is off-track (front), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(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]
+ --print("train is off-track (back), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(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
+ return train.path
+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.get_or_create_path(id, train)
+ if not train.path then return advtrains.pathpredict(id, train) end
+ return train.path
+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]
+ local rel_pos=0
+ local count_l=0
+ for i, w_id in ipairs(train.trainparts) do
+ local any_loaded=false
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.unique_id==w_id 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
+ any_loaded=true
+ end
+ end
+ if not any_loaded then
+ print("update_trainpart_properties wagon "..w_id.." not loaded, ignoring it.")
+ 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]
+ local real_pos_in_train=advtrains.get_real_path_index(train, wagon.pos_in_train)
+ local pos_for_new_train=advtrains.get_or_create_path(wagon.train_id, train)[math.floor(real_pos_in_train+wagon.wagon_span)]
+ local pos_for_new_train_prev=advtrains.get_or_create_path(wagon.train_id, train)[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
+ print("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.traintype)
+ if not node_ok then
+ print("split_train: pos_for_new_train "..minetest.pos_to_string(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
+ print("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.traintype)
+ if not prevnode_ok then
+ print("split_train: pos_for_new_train_prev "..minetest.pos_to_string(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, train.traintype)
+ 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
+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
+function advtrains.try_connect_trains(id1, id2)
+ local train1=advtrains.trains[id1]
+ local train2=advtrains.trains[id2]
+ if not train1 or not train2 then return end
+ if not train1.path or not train2.path then return end
+ if #train1.trainparts==0 or #train2.trainparts==0 then return end
+
+ local frontpos1=advtrains.get_real_index_position(train1.path, train1.index)
+ local backpos1=advtrains.get_real_index_position(train1.path, advtrains.get_train_end_index(train1))
+ --couple logic
+ if train1.traintype==train2.traintype then
+ local frontpos2=advtrains.get_real_index_position(train2.path, train2.index)
+ local backpos2=advtrains.get_real_index_position(train2.path, advtrains.get_train_end_index(train2))
+
+ if not frontpos1 or not frontpos2 or not backpos1 or not backpos2 then return end
+
+ local couple_spawnradius=0.7
+ --case 1 (first train is front)
+ if vector.distance(frontpos2, backpos1)<couple_spawnradius then
+ advtrains.spawn_couple_if_neccessary(backpos1, frontpos2, id1, id2, true, false)
+ --case 2 (second train is front)
+ elseif vector.distance(frontpos1, backpos2)<couple_spawnradius then
+ advtrains.spawn_couple_if_neccessary(backpos2, frontpos1, id2, id1, true, false)
+ --case 3
+ elseif vector.distance(backpos2, backpos1)<couple_spawnradius then
+ advtrains.spawn_couple_if_neccessary(backpos1, backpos2, id1, id2, true, true)
+ --case 4
+ elseif vector.distance(frontpos2, frontpos1)<couple_spawnradius then
+ advtrains.spawn_couple_if_neccessary(frontpos1, frontpos2, id1, id2, false, false)
+ end
+ end
+end
+--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
+
+--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.spawn_couple_if_neccessary(pos1, pos2, tid1, tid2, train1_is_backpos, train2_is_backpos)
+ --print("spawn_couple_if_neccessary..."..dump({pos1=pos1, pos2=pos2, train1_is_backpos=train1_is_backpos, train2_is_backpos=train2_is_backpos}))
+ local train1=advtrains.trains[tid1]
+ local train2=advtrains.trains[tid2]
+ local t1_has_couple
+ if train1_is_backpos then
+ t1_has_couple=train1.couple_eid_back
+ else
+ t1_has_couple=train1.couple_eid_front
+ end
+ local t2_has_couple
+ if train2_is_backpos then
+ t2_has_couple=train2.couple_eid_back
+ else
+ t2_has_couple=train2.couple_eid_front
+ end
+
+ if t1_has_couple and t2_has_couple then
+ if t1_has_couple~=t2_has_couple then--what the hell
+ if minetest.object_refs[t2_has_couple] then minetest.object_refs[t2_has_couple]:remove() end
+ if train2_is_backpos then
+ train2.couple_eid_back=t1_has_couple
+ else
+ train2.couple_eid_front=t1_has_couple
+ end
+ end
+ --[[elseif t1_has_couple and not t2_has_couple then
+ if train2_is_backpos then
+ train2.couple_eid_back=t1_has_couple
+ else
+ train2.couple_eid_front=t1_has_couple
+ end
+ elseif not t1_has_couple and t2_has_couple then
+ if train1_is_backpos then
+ train1.couple_eid_back=t2_has_couple
+ else
+ train1.couple_eid_front=t2_has_couple
+ end]]
+ else
+ local pos=advtrains.pos_median(pos1, pos2)
+ local obj=minetest.add_entity(pos, "advtrains:couple")
+ if not obj then print("failed creating object") return end
+ local le=obj:get_luaentity()
+ le.train_id_1=tid1
+ le.train_id_2=tid2
+ le.train1_is_backpos=train1_is_backpos
+ le.train2_is_backpos=train2_is_backpos
+ --find in object_refs
+ for aoi, compare in pairs(minetest.object_refs) do
+ if compare==obj then
+ if train1_is_backpos then
+ train1.couple_eid_back=aoi
+ else
+ train1.couple_eid_front=aoi
+ end
+ if train2_is_backpos then
+ train2.couple_eid_back=aoi
+ else
+ train2.couple_eid_front=aoi
+ end
+ end
+ end
+ end
+end
+
+function advtrains.do_connect_trains(first_id, second_id)
+ local first_wagoncnt=#advtrains.trains[first_id].trainparts
+ local second_wagoncnt=#advtrains.trains[second_id].trainparts
+
+ for _,v in ipairs(advtrains.trains[second_id].trainparts) do
+ table.insert(advtrains.trains[first_id].trainparts, v)
+ end
+ --kick it like physics (with mass being #wagons)
+ local new_velocity=((advtrains.trains[first_id].velocity*first_wagoncnt)+(advtrains.trains[second_id].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
+end
+
+function advtrains.invert_train(train_id)
+ local train=advtrains.trains[train_id]
+
+ local old_path=advtrains.get_or_create_path(train_id, train)
+ train.path={}
+ train.index= - advtrains.get_train_end_index(train)
+ train.velocity=-train.velocity
+ train.tarvelocity=-train.tarvelocity
+ for k,v in pairs(old_path) do
+ train.path[-k]=v
+ 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.is_train_at_pos(pos)
+ --print("istrainat: pos "..minetest.pos_to_string(pos))
+ local checked_trains={}
+ local objrefs=minetest.get_objects_inside_radius(pos, 2)
+ for _,v in pairs(objrefs) do
+ local le=v:get_luaentity()
+ if le and le.is_wagon and le.initialized and le.train_id and not checked_trains[le.train_id] then
+ --print("istrainat: checking "..le.train_id)
+ checked_trains[le.train_id]=true
+ local path=advtrains.get_or_create_path(le.train_id, le:train())
+ if path then
+ --print("has path")
+ for i=math.floor(advtrains.get_train_end_index(le:train())+0.5),math.floor(le:train().index+0.5) do
+ if path[i] then
+ --print("has pathitem "..i.." "..minetest.pos_to_string(path[i]))
+ if vector.equals(advtrains.round_vector_floor_y(path[i]), pos) then
+ return true
+ end
+ end
+ end
+ end
+ end
+ end
+ return false
+end
+function advtrains.invalidate_all_paths()
+ --print("invalidating all paths")
+ for k,v in pairs(advtrains.trains) do
+ 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.min_index_on_track=nil
+ v.max_index_on_track=nil
+
+ advtrains.detector.setup_restore()
+ v.detector_old_index=nil
+ v.detector_old_end_index=nil
+ 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)