diff options
author | orwell96 <mono96.mml@gmail.com> | 2016-12-20 14:17:39 +0100 |
---|---|---|
committer | orwell96 <mono96.mml@gmail.com> | 2016-12-20 14:17:39 +0100 |
commit | f806ed9eee8c13eb0b4868641311d25257c63f46 (patch) | |
tree | 0657366d21caf17881266054baa1dfae2365b9d8 /advtrains/trainlogic.lua | |
parent | 05ce694decc0653997f45fa4f9a24b40057dbd01 (diff) | |
download | advtrains-f806ed9eee8c13eb0b4868641311d25257c63f46.tar.gz advtrains-f806ed9eee8c13eb0b4868641311d25257c63f46.tar.bz2 advtrains-f806ed9eee8c13eb0b4868641311d25257c63f46.zip |
Turning mod into a modpack and separating the trains from the core mod
Diffstat (limited to 'advtrains/trainlogic.lua')
-rw-r--r-- | advtrains/trainlogic.lua | 895 |
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) |