diff options
Diffstat (limited to 'advtrains/init.lua')
-rw-r--r-- | advtrains/init.lua | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/advtrains/init.lua b/advtrains/init.lua new file mode 100644 index 0000000..d6c3c92 --- /dev/null +++ b/advtrains/init.lua @@ -0,0 +1,597 @@ + +--[[ +Advanced Trains - Minetest Mod + +Copyright (C) 2016-2020 Moritz Blei (orwell96) and contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +]] + +local lot = os.clock() +minetest.log("action", "[advtrains] Loading...") + +-- There is no need to support 0.4.x anymore given that the compatitability with it is already broken by 1bb1d825f46af3562554c12fba35a31b9f7973ff +attrans = minetest.get_translator ("advtrains") + +--advtrains + +DUMP_DEBUG_SAVE = false +GENERATE_ATRICIFIAL_LAG = false + +--Constant for maximum connection value/division of the circle +AT_CMAX = 16 + +advtrains = {trains={}, player_to_train_mapping={}} + +-- get wagon loading range +advtrains.wagon_load_range = tonumber(minetest.settings:get("advtrains_wagon_load_range")) +if not advtrains.wagon_load_range then + advtrains.wagon_load_range = tonumber(minetest.settings:get("active_block_range"))*16 +end + +--pcall +local no_action=false + +local function reload_saves() + atwarn("Restoring saved state in 1 second...") + no_action=true + advtrains.lock_path_inval = false + --read last save state and continue, as if server was restarted + for aoi, le in pairs(minetest.luaentities) do + if le.is_wagon then + le.object:remove() + end + end + minetest.after(1, function() + advtrains.load() + atwarn("Reload successful!") + advtrains.ndb.restore_all() + end) +end + +function advtrains.pcall(fun) + if no_action then return end + + local succ, return1, return2, return3, return4=xpcall(fun, function(err) + atwarn("Lua Error occured: ", err) + atwarn(debug.traceback()) + if advtrains.atprint_context_tid then + advtrains.path_print(advtrains.trains[advtrains.atprint_context_tid], atdebug) + atwarn(advtrains.trains[advtrains.atprint_context_tid].debug) + end + end) + if not succ then + reload_saves() + else + return return1, return2, return3, return4 + end +end + + +advtrains.modpath = minetest.get_modpath("advtrains") + +--Advtrains dump (special treatment of pos and sigd) +function atdump(t, intend) + local str + if type(t)=="table" then + if t.x and t.y and t.z then + str=minetest.pos_to_string(t) + elseif t.p and t.s then -- interlocking sigd + str="S["..minetest.pos_to_string(t.p).."/"..t.s.."]" + elseif advtrains.lines and t.s and t.m then -- RwT + str=advtrains.lines.rwt.to_string(t) + else + str="{" + local intd = (intend or "") .. " " + for k,v in pairs(t) do + if type(k)~="string" or not string.match(k, "^path[_]?") then + -- do not print anything path-related + str = str .. "\n" .. intd .. atdump(k, intd) .. " = " ..atdump(v, intd) + end + end + str = str .. "\n" .. (intend or "") .. "}" + end + elseif type(t)=="boolean" then + if t then + str="true" + else + str="false" + end + elseif type(t)=="function" then + str="<function>" + elseif type(t)=="userdata" then + str="<userdata>" + else + str=""..t + end + return str +end + +function advtrains.print_concat_table(a) + local str="" + local stra="" + local t + for i=1,20 do + t=a[i] + if t==nil then + stra=stra.."nil " + else + str=str..stra + stra="" + str=str..atdump(t).." " + end + end + return str +end + +atprint=function() end +atlog=function(t, ...) + local text=advtrains.print_concat_table({t, ...}) + minetest.log("action", "[advtrains]"..text) +end +atwarn=function(t, ...) + local text=advtrains.print_concat_table({t, ...}) + minetest.log("warning", "[advtrains]"..text) + minetest.chat_send_all("[advtrains] -!- "..text) +end +sid=function(id) if id then return string.sub(id, -6) end end + + +--ONLY use this function for temporary debugging. for consistent debug prints use atprint +atdebug=function(t, ...) + local text=advtrains.print_concat_table({t, ...}) + minetest.log("action", "[advtrains]"..text) + minetest.chat_send_all("[advtrains]"..text) +end + +if minetest.settings:get_bool("advtrains_enable_debugging") then + atprint=function(t, ...) + local context=advtrains.atprint_context_tid or "" + if not context then return end + local text=advtrains.print_concat_table({t, ...}) + advtrains.drb_record(context, text) + + --atlog("@@",advtrains.atprint_context_tid,t,...) + end + dofile(advtrains.modpath.."/debugringbuffer.lua") + +end + +function assertt(var, typ) + if type(var)~=typ then + error("Assertion failed, variable has to be of type "..typ) + end +end + +dofile(advtrains.modpath.."/helpers.lua"); +--dofile(advtrains.modpath.."/debugitems.lua"); + +advtrains.meseconrules = +{{x=0, y=0, z=-1}, + {x=1, y=0, z=0}, + {x=-1, y=0, z=0}, + {x=0, y=0, z=1}, + {x=1, y=1, z=0}, + {x=1, y=-1, z=0}, + {x=-1, y=1, z=0}, + {x=-1, y=-1, z=0}, + {x=0, y=1, z=1}, + {x=0, y=-1, z=1}, + {x=0, y=1, z=-1}, + {x=0, y=-1, z=-1}, + {x=0, y=-2, z=0}} + +advtrains.fpath=minetest.get_worldpath().."/advtrains" + +dofile(advtrains.modpath.."/path.lua") +dofile(advtrains.modpath.."/trainlogic.lua") +dofile(advtrains.modpath.."/trainhud.lua") +dofile(advtrains.modpath.."/trackplacer.lua") +dofile(advtrains.modpath.."/copytool.lua") +dofile(advtrains.modpath.."/tracks.lua") +dofile(advtrains.modpath.."/occupation.lua") +dofile(advtrains.modpath.."/atc.lua") +dofile(advtrains.modpath.."/wagons.lua") +dofile(advtrains.modpath.."/protection.lua") + +dofile(advtrains.modpath.."/trackdb_legacy.lua") +dofile(advtrains.modpath.."/nodedb.lua") +dofile(advtrains.modpath.."/couple.lua") + +dofile(advtrains.modpath.."/signals.lua") +dofile(advtrains.modpath.."/misc_nodes.lua") +dofile(advtrains.modpath.."/crafting.lua") +dofile(advtrains.modpath.."/craft_items.lua") + +dofile(advtrains.modpath.."/log.lua") +dofile(advtrains.modpath.."/passive.lua") +if mesecon then + dofile(advtrains.modpath.."/p_mesecon_iface.lua") +end + + +dofile(advtrains.modpath.."/lzb.lua") + + +--load/save + +-- backup variables, used if someone should accidentally delete a sub-mod +local MDS_interlocking, MDS_lines + + +advtrains.fpath=minetest.get_worldpath().."/advtrains" +dofile(advtrains.modpath.."/log.lua") +function advtrains.read_component(name) + local path = advtrains.fpath.."_"..name + minetest.log("action", "[advtrains] loading "..path) + local file, err = io.open(path, "r") + if not file then + minetest.log("warning", " Failed to read advtrains save data from file "..path..": "..(err or "Unknown Error")) + minetest.log("warning", " (this is normal when first enabling advtrains on this world)") + return + end + local tbl = minetest.deserialize(file:read("*a")) + file:close() + return tbl +end + +function advtrains.avt_load() + -- check for new, split advtrains save file + + local version = advtrains.read_component("version") + local tbl + if version and version == 3 then + -- we are dealing with the new, split-up system + minetest.log("action", "[advtrains] loading savefiles version 3") + local il_save = { + tcbs = true, + ts = true, + signalass = true, + rs_locks = true, + rs_callbacks = true, + influence_points = true, + npr_rails = true, + } + tbl={ + trains = true, + wagon_save = true, + ptmap = true, + atc = true, + ndb = true, + lines = true, + version = 2, + } + for i,k in pairs(il_save) do + il_save[i] = advtrains.read_component("interlocking_"..i) + end + for i,k in pairs(tbl) do + tbl[i] = advtrains.read_component(i) + end + tbl["interlocking"] = il_save + else + local file, err = io.open(advtrains.fpath, "r") + if not file then + minetest.log("warning", " Failed to read advtrains save data from file "..advtrains.fpath..": "..(err or "Unknown Error")) + minetest.log("warning", " (this is normal when first enabling advtrains on this world)") + return + else + tbl = minetest.deserialize(file:read("*a")) + file:close() + end + end + if type(tbl) == "table" then + if tbl.version then + --congrats, we have the new save format. + advtrains.trains = tbl.trains + --Save the train id into the train table to avoid having to pass id around + for id, train in pairs(advtrains.trains) do + train.id = id + end + advtrains.wagons = tbl.wagon_save + advtrains.player_to_train_mapping = tbl.ptmap or {} + advtrains.ndb.load_data(tbl.ndb) + advtrains.atc.load_data(tbl.atc) + if advtrains.interlocking then + advtrains.interlocking.db.load(tbl.interlocking) + else + MDS_interlocking = tbl.interlocking + end + if advtrains.lines then + advtrains.lines.load(tbl.lines) + else + MDS_lines = tbl.lines + end + --remove wagon_save entries that are not part of a train + local todel=advtrains.merge_tables(advtrains.wagon_save) + for tid, train in pairs(advtrains.trains) do + train.id = tid + for _, wid in ipairs(train.trainparts) do + todel[wid]=nil + end + end + for wid, _ in pairs(todel) do + atwarn("Removing unused wagon", wid, "from wagon_save table.") + advtrains.wagon_save[wid]=nil + end + else + --oh no, its the old one... + advtrains.trains=tbl + --load ATC + advtrains.fpath_atc=minetest.get_worldpath().."/advtrains_atc" + local file, err = io.open(advtrains.fpath_atc, "r") + if not file then + local er=err or "Unknown Error" + atprint("Failed loading advtrains atc save file "..er) + else + local tbl = minetest.deserialize(file:read("*a")) + if type(tbl) == "table" then + advtrains.atc.controllers=tbl.controllers + end + file:close() + end + --load wagon saves + 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" + atprint("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 + end + else + minetest.log("error", " Failed to deserialize advtrains save data: Not a table!") + end +end + +advtrains.save_component = function (tbl, name) + -- Saves each component of the advtrains file separately + -- + -- required for now to shrink the advtrains db to overcome lua + -- limitations. + local datastr = minetest.serialize(tbl) + if not datastr then + minetest.log("error", " Failed to serialize advtrains save data!") + return + end + local path = advtrains.fpath.."_"..name + local success = minetest.safe_file_write(path, datastr) + + if not success then + minetest.log("error", " Failed to write advtrains save data to file "..path) + end + +end + +advtrains.avt_save = function(remove_players_from_wagons) + --atprint("saving") + + if remove_players_from_wagons then + for w_id, data in pairs(advtrains.wagons) do + data.seatp={} + end + advtrains.player_to_train_mapping={} + end + + local tmp_trains={} + for id, train in pairs(advtrains.trains) do + --first, deep_copy the train + if #train.trainparts > 0 then + local v=advtrains.save_keys(train, { + "last_pos", "last_connid", "last_frac", "velocity", "tarvelocity", + "trainparts", "recently_collided_with_env", + "atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open", + "text_outside", "text_inside", "line", "routingcode", + "il_sections", "speed_restriction", "is_shunt", "points_split", "autocouple" + }) + --then save it + tmp_trains[id]=v + else + atwarn("Train",id,"had no wagons left because of some bug. It is being deleted. Wave it goodbye!") + advtrains.remove_train(id) + end + end + + for id, wdata in pairs(advtrains.wagons) do + local _,proto = advtrains.get_wagon_prototype(wdata) + if proto.has_inventory then + local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..id}) + if inv then -- inventory is not initialized when wagon was never loaded + -- TOOD: What happens with unloading rails when they don't find the inventory? + wdata.ser_inv=advtrains.serialize_inventory(inv) + end + end + -- TODO apply save-keys here too + -- TODO temp + wdata.dcpl_lock = nil + end + + --versions: + -- 1 - Initial new save format. + -- 2 - version as of tss branch 11-2018+ + local il_save + if advtrains.interlocking then + il_save = advtrains.interlocking.db.save() + else + il_save = MDS_interlocking + end + local ln_save + if advtrains.lines then + ln_save = advtrains.lines.save() + else + ln_save = MDS_lines + end + + local save_tbl={ + trains = tmp_trains, + wagon_save = advtrains.wagons, + ptmap = advtrains.player_to_train_mapping, + atc = advtrains.atc.save_data(), + ndb = advtrains.ndb.save_data(), + lines = ln_save, + version = 3, + } + for i,k in pairs(save_tbl) do + advtrains.save_component(k,i) + end + + for i,k in pairs(il_save) do + advtrains.save_component(k,"interlocking_"..i) + end + + if DUMP_DEBUG_SAVE then + local file, err = io.open(advtrains.fpath.."_DUMP", "w") + if err then + return + end + file:write(dump(save_tbl)) + file:close() + end +end + +--## MAIN LOOP ##-- +--Calls all subsequent main tasks of both advtrains and atlatc +local init_load=false +local save_interval=20 +local save_timer=save_interval +advtrains.mainloop_runcnt=0 + + +local t = 0 +minetest.register_globalstep(function(dtime_mt) + return advtrains.pcall(function() + advtrains.mainloop_runcnt=advtrains.mainloop_runcnt+1 + --atprint("Running the main loop, runcnt",advtrains.mainloop_runcnt) + --call load once. see advtrains.load() comment + if not init_load then + advtrains.load() + end + + local dtime + if GENERATE_ATRICIFIAL_LAG then + dtime = 0.2 + if os.clock()<t then + return + end + + t = os.clock()+0.2 + else + --limit dtime: if trains move too far in one step, automation may cause stuck and wrongly braking trains + dtime=dtime_mt + if dtime>0.2 then + atprint("Limiting dtime to 0.2!") + dtime=0.2 + end + end + + advtrains.mainloop_trainlogic(dtime) + if advtrains_itm_mainloop then + advtrains_itm_mainloop(dtime) + end + if atlatc then + atlatc.mainloop_stepcode(dtime) + atlatc.interrupt.mainloop(dtime) + end + if advtrains.lines then + advtrains.lines.step(dtime) + end + + --trigger a save when necessary + save_timer=save_timer-dtime + if save_timer<=0 then + local t=os.clock() + --save + advtrains.save() + save_timer=save_interval + atprintbm("saving", t) + end + end) +end) + +--if something goes wrong in these functions, there is no help. no pcall here. + +--## MAIN LOAD ROUTINE ## +-- Causes the loading of everything +-- first time called in main loop (after the init phase) because luaautomation has to initialize first. +function advtrains.load() + advtrains.avt_load() --loading advtrains. includes ndb at advtrains.ndb.load_data() + if atlatc then + atlatc.load() --includes interrupts + end + if advtrains_itm_init then + advtrains_itm_init() + end + init_load=true + no_action=false + atlog("[load_all]Loaded advtrains save files") +end + +--## MAIN SAVE ROUTINE ## +-- Causes the saving of everything +function advtrains.save(remove_players_from_wagons) + if not init_load then + --wait... we haven't loaded yet?! + atwarn("Instructed to save() but load() was never called!") + return + end + advtrains.avt_save(remove_players_from_wagons) --saving advtrains. includes ndb at advtrains.ndb.save_data() + if atlatc then + atlatc.save() + end + atprint("[save_all]Saved advtrains save files") + + --TODO very simple yet hacky workaround for the "green signals" bug + advtrains.invalidate_all_paths() +end +minetest.register_on_shutdown(advtrains.save) + +-- This chat command provides a solution to the problem known on the LinuxWorks server +-- There are many players that joined a single time, got on a train and then left forever +-- These players still occupy seats in the trains. +minetest.register_chatcommand("at_empty_seats", + { + params = "", -- Short parameter description + description = "Detach all players, especially the offline ones, from all trains. Use only when no one serious is on a train.", -- Full description + privs = {train_operator=true, server=true}, -- Require the "privs" privilege to run + func = function(name, param) + return advtrains.pcall(function() + atwarn("Data is being saved. While saving, advtrains will remove the players from trains. Save files will be reloaded afterwards!") + advtrains.save(true) + reload_saves() + end) + end, +}) +-- This chat command solves another problem: Trains getting randomly stuck. +minetest.register_chatcommand("at_reroute", + { + params = "", + description = "Delete all train routes, force them to recalculate", + privs = {train_operator=true}, -- Only train operator is required, since this is relatively safe. + func = function(name, param) + return advtrains.pcall(function() + advtrains.invalidate_all_paths() + return true, "Successfully invalidated train routes" + end) + end, +}) + + +local tot=(os.clock()-lot)*1000 +minetest.log("action", "[advtrains] Loaded in "..tot.."ms") + |