--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 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_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 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 --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_index1 then train.tarvelocity=1 end if train.tarvelocity<-1 then train.tarvelocity=-1 end elseif front_off_track then--allow movement only backward if train.tarvelocity>0 then train.tarvelocity=0 end if train.tarvelocity<-1 then train.tarvelocity=-1 end elseif back_off_track then--allow movement only forward if train.tarvelocity>1 then train.tarvelocity=1 end if train.tarvelocity<0 then train.tarvelocity=0 end end 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 --new train collisions (only search in the direction of the driving train) local coll_search_radius=2 local coll_grace=0 local collpos if train.velocity>0 then collpos=advtrains.get_real_index_position(path, train.index-coll_grace) elseif train.velocity<0 then collpos=advtrains.get_real_index_position(path, train_end_index+coll_grace) end if collpos then local objrefs=minetest.get_objects_inside_radius(collpos, coll_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 train.recently_collided_with_env=true train.velocity=-0.5*train.velocity train.tarvelocity=0 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())/* Minetest Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "test.h" #include "ban.h" class TestBan : public TestBase { public: TestBan() { TestManager::registerTestModule(this); } const char *getName() { return "TestBan"; } void runTests(IGameDef *gamedef); private: void testCreate(); void testAdd(); void testRemove(); void testModificationFlag(); void testGetBanName(); void testGetBanDescription(); void reinitTestEnv(); }; static TestBan g_test_instance; void TestBan::runTests(IGameDef *gamedef) { reinitTestEnv(); TEST(testCreate); reinitTestEnv(); TEST(testAdd); reinitTestEnv(); TEST(testRemove); reinitTestEnv(); TEST(testModificationFlag); reinitTestEnv(); TEST(testGetBanName); reinitTestEnv(); TEST(testGetBanDescription); // Delete leftover files reinitTestEnv(); } // This module is stateful due to disk writes, add helper to remove files void TestBan::reinitTestEnv() { fs::DeleteSingleFileOrEmptyDirectory("testbm.txt"); fs::DeleteSingleFileOrEmptyDirectory("testbm2.txt"); } void TestBan::testCreate() { // test save on object removal { BanManager bm("testbm.txt"); } UASSERT(std::ifstream("testbm.txt", std::ios::binary).is_open()); // test manual save { BanManager bm("testbm2.txt"); bm.save(); UASSERT(std::ifstream("testbm2.txt", std::ios::binary).is_open()); } } void TestBan::testAdd() { std::string bm_test1_entry = "192.168.0.246"; std::string bm_test1_result = "test_username"; BanManager bm("testbm.txt"); bm.add(bm_test1_entry, bm_test1_result); UASSERT(bm.getBanName(bm_test1_entry) == bm_test1_result); } void TestBan::testRemove() { std::string bm_test1_entry = "192.168.0.249"; std::string bm_test1_result = "test_username"; std::string bm_test2_entry = "192.168.0.250"; std::string bm_test2_result = "test_username7"; BanManager bm("testbm.txt"); // init data bm.add(bm_test1_entry, bm_test1_result); bm.add(bm_test2_entry, bm_test2_result); // the test bm.remove(bm_test1_entry); UASSERT(bm.getBanName(bm_test1_entry).empty()); bm.remove(bm_test2_result); UASSERT(bm.getBanName(bm_test2_result).empty()); } void TestBan::testModificationFlag() { BanManager bm("testbm.txt"); bm.add("192.168.0.247", "test_username"); UASSERT(bm.isModified()); bm.remove("192.168.0.247"); UASSERT(bm.isModified()); // Clear the modification flag bm.save(); // Test modification flag is entry was not present bm.remove("test_username"); UASSERT(!bm.isModified()); } void TestBan::testGetBanName() { std::string bm_test1_entry = "192.168.0.247"; std::string bm_test1_result = "test_username"; BanManager bm("testbm.txt"); bm.add(bm_test1_entry, bm_test1_result); // Test with valid entry UASSERT(bm.getBanName(bm_test1_entry) == bm_test1_result); // Test with invalid entry UASSERT(bm.getBanName("---invalid---").empty()); } void TestBan::testGetBanDescription() { std::string bm_test1_entry = "192.168.0.247"; std::string bm_test1_entry2 = "test_username"; std::string bm_test1_result = "192.168.0.247|test_username"; BanManager bm("testbm.txt"); bm.add(bm_test1_entry, bm_test1_entry2); UASSERT(bm.getBanDescription(bm_test1_entry) == bm_test1_result); UASSERT(bm.getBanDescription(bm_test1_entry2) == bm_test1_result); } 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.index=nil v.min_index_on_track=nil v.max_index_on_track=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: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)