diff --git a/game/engines/default/engine/Module.lua b/game/engines/default/engine/Module.lua index 808d25e845b1930ab71dfd1cdb929d363f436b54..edd55a82f23d6be70fd610206b36fb9a1ddeca85 100644 --- a/game/engines/default/engine/Module.lua +++ b/game/engines/default/engine/Module.lua @@ -1016,6 +1016,7 @@ function _M:instanciate(mod, name, new_game, no_reboot, extra_module_info) profile:addStatFields(unpack(mod.profile_stats_fields or {})) profile:setConfigsBatch(true) profile:loadModuleProfile(mod.short_name, mod) + profile:incrLoadProfile(mod) profile:currentCharacter(mod.full_version_string, "game did not tell us") UIBase:clearCache() diff --git a/game/engines/default/engine/PlayerProfile.lua b/game/engines/default/engine/PlayerProfile.lua index 3a5ca60d17d01608b715b1db9719348489c7b142..2b97f37207052de5d0f77260c3ecd475ecbb525e 100644 --- a/game/engines/default/engine/PlayerProfile.lua +++ b/game/engines/default/engine/PlayerProfile.lua @@ -79,6 +79,7 @@ function _M:init() self.generic = {} self.modules = {} self.evt_cbs = {} + self.data_log = {log={}} self.stats_fields = {} local checkstats = function(self, field) return self.stats_fields[field] end self.config_settings = @@ -236,6 +237,7 @@ function _M:loadModuleProfile(short_name, mod_def) -- Delay when we are currently saving if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("loadModuleProfile", function() self:loadModuleProfile(short_name) end) return end + local def = mod_def.profile_defs or {} local function load(online) local pop = self:mountProfile(online, short_name) local d = "/current-profile/modules/"..short_name.."/" @@ -251,6 +253,12 @@ function _M:loadModuleProfile(short_name, mod_def) else self.modules[short_name][field] = self.modules[short_name][field] or {} self:loadData(f, self.modules[short_name][field]) + if def[field].incr_only then + if not self.modules[short_name][field].incr_only then + print("[PROFILE] Old non incremental data for "..field..": discarding") + self.modules[short_name][field] = {} + end + end end end end @@ -319,6 +327,7 @@ function _M:saveModuleProfile(name, data, nosync, nowrite) if module == "boot" then return end core.game.resetLocale() if not game or not game.__mod_info.profile_defs then return end + if game.__mod_info.profile_defs[name].incr_only then print("[PROFILE] data in incr only mode but called with saveModuleProfile", name) return end -- Delay when we are currently saving if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("saveModuleProfile", function() self:saveModuleProfile(name, data, nosync) end) return end @@ -364,6 +373,49 @@ function _M:saveModuleProfile(name, data, nosync, nowrite) if not nosync and not game.__mod_info.profile_defs[name].no_sync then self:setConfigs(module, name, data) end end +--- Loads the incremental log data +function _M:incrLoadProfile(mod_def) + if not mod_def or not mod_def.short_name then return end + local pop = self:mountProfile(true) + local file = "/current-profile/modules/"..mod_def.short_name.."/incr.log" + if fs.exists(file) then + local f, err = loadfile(file) + if not f and err then + print("Error loading incr log", file, err) + else + self:loadData(f, self.data_log) + end + end + self:umountProfile(true, pop) +end + +--- Saves a incr profile data +function _M:incrDataProfile(name, data) + if module == "boot" then return end + core.game.resetLocale() + if not game or not game.__mod_info.profile_defs then return end + if not game.__mod_info.profile_defs[name].incr_only then print("[PROFILE] data in non-incr only mode but called with incrDataProfile", name) return end + + -- Delay when we are currently saving + if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("incrDataProfile", function() self:incrDataProfile(name, data) end) return end + + local module = self.mod_name + + -- Check for readability + local dataenv = self.data_log.log + dataenv[#dataenv+1] = {module=game.__mod_info.short_name, kind=name, data=data} + + local pop = self:mountProfile(true, module) + local f = fs.open("/modules/"..module.."/incr.log", "w") + if f then + f:write(serialize(self.data_log)) + f:close() + end + self:umountProfile(true, pop) + + self:syncIncrData() +end + function _M:checkFirstRun() local result = self.generic.firstrun if not result then @@ -503,6 +555,16 @@ function _M:eventGetNews(e) end end +function _M:eventIncrLogConsume(e) + local module = game.__mod_info.short_name + if not module then return end + print("[PROFILE] Server accepted our incr log, deleting") + local pop = self:mountProfile(true, module) + fs.delete("/modules/"..module.."/incr.log") + self:umountProfile(true, pop) + self.data_log.log = {} +end + function _M:eventGetConfigs(e) local data = zlib.decompress(e.data):unserialize() local module = e.module @@ -607,14 +669,14 @@ function _M:getConfigs(module, cb, mod_def) if not self.auth then return end self.evt_cbs.GetConfigs = cb if module == "generic" then - for k, _ in pairs(generic_profile_defs) do - if not _.no_sync then + for k, def in pairs(generic_profile_defs) do + if not def.no_sync then core.profile.pushOrder(table.serialize{o="GetConfigs", module=module, kind=k}) end end else - for k, _ in pairs((mod_def or game.__mod_info).profile_defs or {}) do - if not _.no_sync then + for k, def in pairs((mod_def or game.__mod_info).profile_defs or {}) do + if not def.no_sync and not def.incr_only then core.profile.pushOrder(table.serialize{o="GetConfigs", module=module, kind=k}) end end @@ -625,6 +687,15 @@ function _M:setConfigsBatch(v) core.profile.pushOrder(table.serialize{o="SetConfigsBatch", v=v and true or false}) end +function _M:syncIncrData() + self:waitFirstAuth() + if not self.auth then return end + local module = game and game.__mod_info.short_name + if not module then return end + + core.profile.pushOrder(table.serialize{o="SendIncrLog", data=zlib.compress(table.serialize(self.data_log.log))}) +end + function _M:setConfigs(module, name, data) self:waitFirstAuth() if not self.auth then return end @@ -660,7 +731,7 @@ function _M:syncOnline(module, mod_def) end else for k, def in pairs((mod_def or game.__mod_info).profile_defs or {}) do - if not def.no_sync and def.export and sync[k] then + if not def.no_sync and not def.incr_only and def.export and sync[k] then local f = def.export local ret = {} setfenv(f, setmetatable({add=function(d) ret[#ret+1] = d end}, {__index=_G})) diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index 8d048a6ea8b4e01f5c9f6371d98afd7475ea3a8a..911ec5c7a3f7c4cb5fda4464a13b49949eb2d602 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -1857,8 +1857,6 @@ do return end print(f, err) setfenv(f, setmetatable({level=self.level, zone=self.zone}, {__index=_G})) print(pcall(f)) -do return end - self:registerDialog(require("mod.dialogs.DownloadCharball").new()) end end, [{"_f","ctrl"}] = function() if config.settings.cheat then self.player.quests["love-melinda"] = nil diff --git a/game/modules/tome/class/interface/PlayerStats.lua b/game/modules/tome/class/interface/PlayerStats.lua index 9508c7f4536a5050fb575a2a3d2e37121a63851f..a020464f064e3d1179a48bfc5ce8faf570749ed3 100644 --- a/game/modules/tome/class/interface/PlayerStats.lua +++ b/game/modules/tome/class/interface/PlayerStats.lua @@ -29,13 +29,13 @@ end function _M:registerDeath(src) local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) local name = src.name - profile:saveModuleProfile("deaths", {source=name, cid=pid, nb={"inc",1}}) + profile:incrDataProfile("deaths", {source=name, cid=pid, nb=1}) end function _M:registerUniqueKilled(who) local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) - profile:saveModuleProfile("uniques", {victim=who.name, cid=pid, nb={"inc",1}}) + profile:incrDataProfile("uniques", {victim=who.name, cid=pid, nb=1}) end function _M:registerArtifactsPicked(what) @@ -44,13 +44,13 @@ function _M:registerArtifactsPicked(what) local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) local name = what:getName{do_color=false, do_count=false, force_id=true, no_add_name=true} - profile:saveModuleProfile("artifacts", {name=name, cid=pid, nb={"inc",1}}) + profile:incrDataProfile("artifacts", {name=name, cid=pid, nb=1}) end function _M:registerCharacterPlayed() local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) - profile:saveModuleProfile("characters", {cid=pid, nb={"inc",1}}) + profile:incrDataProfile("characters", {cid=pid, nb=1}) end function _M:registerLoreFound(lore) diff --git a/game/modules/tome/dialogs/DownloadCharball.lua b/game/modules/tome/dialogs/DownloadCharball.lua deleted file mode 100644 index 99932141acfc0ad7fb9f83d25d4f1d396a9fc319..0000000000000000000000000000000000000000 --- a/game/modules/tome/dialogs/DownloadCharball.lua +++ /dev/null @@ -1,99 +0,0 @@ --- ToME - Tales of Maj'Eyal --- Copyright (C) 2009 - 2017 Nicolas Casalini --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU 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 General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see <http://www.gnu.org/licenses/>. --- --- Nicolas Casalini "DarkGod" --- darkgod@te4.org - -require "engine.class" -local Dialog = require "engine.ui.Dialog" -local ListColumns = require "engine.ui.ListColumns" -local TextzoneList = require "engine.ui.TextzoneList" -local Separator = require "engine.ui.Separator" -local Image = require "engine.ui.Image" - -module(..., package.seeall, class.inherit(Dialog)) - -function _M:init() - Dialog.init(self, "Download charball", game.w * 0.8, game.h * 0.8) - - self:generateList() - - self.c_list = ListColumns.new{width=math.floor(self.iw - 10), height=self.ih - 10, scrollbar=true, sortable=true, columns={ - {name="Player", width=30, display_prop="player", sort="player"}, - {name="Character", width=70, display_prop="character", sort="character"}, - }, list=self.list, fct=function(item) self:importCharball(item) end, select=function(item, sel) self:select(item) end} - - self:loadUI{ - {left=0, top=0, ui=self.c_list}, - } - self:setFocus(self.c_list) - self:setupUI() - self:select(self.list[1]) - - self.key:addBinds{ - EXIT = function() game:unregisterDialog(self) end, - } -end - -function _M:generateList() - profile.chat:selectChannel("tome") - - -- Makes up the list - local list = {} - for login, user in pairs(profile.chat.channels.tome.users) do - if user.valid == "validate" and user.current_char_data and user.current_char_data.uuid then - list[#list+1] = { player=user.name, character=user.current_char, id=user.id, uuid=user.current_char_data.uuid } - end - end - -- Add known artifacts - table.sort(list, function(a, b) return a.character < b.character end) - self.list = list -end - -function _M:select(item) - if item then - end -end - -function _M:importCharball(item) - if not item or not item.uuid then return end - - local data = profile:getCharball(item.id, item.uuid) - local f = fs.open("/charballs/__import.charball", "w") - f:write(data) - f:close() - - savefile_pipe:ignoreSaveToken(true) - local ep = savefile_pipe:doLoad("__import", "entity", "engine.CharacterBallSave", "__import") - savefile_pipe:ignoreSaveToken(false) - for a, _ in pairs(ep.members) do - if a.__CLASSNAME == "mod.class.Player" then - mod.class.NPC.castAs(a) - engine.interface.ActorAI.init(a, a) - a.quests = {} - a.ai = "tactical" - a.ai_state = {talent_in=1} - a.no_drops = true - a.keep_inven_on_death = false - a.energy.value = 0 - a.player = nil - a.faction = "enemies" - game.zone:addEntity(game.level, a, "actor", game.player.x, game.player.y-1) - - game:unregisterDialog(self) - end - end -end diff --git a/game/modules/tome/init.lua b/game/modules/tome/init.lua index cf3cd115a8cd8c3200f7603d240ae2487c18d238..a4dd4110e08c01f3b6fc23b190ce9debc0c5d9fc 100644 --- a/game/modules/tome/init.lua +++ b/game/modules/tome/init.lua @@ -148,15 +148,15 @@ load_tips = { profile_defs = { allow_build = { {name="index:string:50"}, receive=function(data, save) save[data.name] = true end, export=function(env) for k, _ in pairs(env) do add{name=k} end end }, lore = { {name="index:string:30"}, receive=function(data, save) save.lore = save.lore or {} save.lore[data.name] = true end, export=function(env) for k, v in pairs(env.lore or {}) do add{name=k} end end }, - escorts = { {fate="index:enum(lost,betrayed,zigur,saved)"}, {nb="number"}, receive=function(data, save) inc_set(save, data.fate, data, "nb") end, export=function(env) for k, v in pairs(env) do add{fate=k, nb=v} end end }, - artifacts = { {cid="index:string:50"}, {name="index:string:40"}, {nb="number"}, receive=function(data, save) save.artifacts = save.artifacts or {} save.artifacts[data.cid] = save.artifacts[data.cid] or {} inc_set(save.artifacts[data.cid], data.name, data, "nb") end, export=function(env) for cid, d in pairs(env.artifacts or {}) do for name, v in pairs(d) do add{cid=cid, name=name, nb=v} end end end }, - characters = { {cid="index:string:50"}, {nb="number"}, receive=function(data, save) save.characters = save.characters or {} inc_set(save.characters, data.cid, data, "nb") end, export=function(env) for k, v in pairs(env.characters or {}) do add{cid=k, nb=v} end end }, - uniques = { {cid="index:string:50"}, {victim="index:string:50"}, {nb="number"}, receive=function(data, save) save.uniques = save.uniques or {} save.uniques[data.cid] = save.uniques[data.cid] or {} inc_set(save.uniques[data.cid], data.victim, data, "nb") end, export=function(env) for cid, d in pairs(env.uniques or {}) do for name, v in pairs(d) do add{cid=cid, victim=name, nb=v} end end end }, - deaths = { {cid="index:string:50"}, {source="index:string:50"}, {nb="number"}, receive=function(data, save) save.sources = save.sources or {} save.sources[data.cid] = save.sources[data.cid] or {} inc_set(save.sources[data.cid], data.source, data, "nb") end, export=function(env) for cid, d in pairs(env.sources or {}) do for name, v in pairs(d) do add{cid=cid, source=name, nb=v} end end end }, + escorts = { incr_only=true, {fate="index:enum(lost,betrayed,zigur,saved)"}, {nb="number"}, receive=function(data, save) inc_set(save, data.fate, data, "nb") end, export=function(env) for k, v in pairs(env) do add{fate=k, nb=v} end end }, + artifacts = { incr_only=true, {cid="index:string:50"}, {name="index:string:40"}, {nb="number"}, receive=function(data, save) save.artifacts = save.artifacts or {} save.artifacts[data.cid] = save.artifacts[data.cid] or {} inc_set(save.artifacts[data.cid], data.name, data, "nb") end, export=function(env) for cid, d in pairs(env.artifacts or {}) do for name, v in pairs(d) do add{cid=cid, name=name, nb=v} end end end }, + characters = { incr_only=true, {cid="index:string:50"}, {nb="number"}, receive=function(data, save) save.characters = save.characters or {} inc_set(save.characters, data.cid, data, "nb") end, export=function(env) for k, v in pairs(env.characters or {}) do add{cid=k, nb=v} end end }, + uniques = { incr_only=true, {cid="index:string:50"}, {victim="index:string:50"}, {nb="number"}, receive=function(data, save) save.uniques = save.uniques or {} save.uniques[data.cid] = save.uniques[data.cid] or {} inc_set(save.uniques[data.cid], data.victim, data, "nb") end, export=function(env) for cid, d in pairs(env.uniques or {}) do for name, v in pairs(d) do add{cid=cid, victim=name, nb=v} end end end }, + deaths = { incr_only=true, {cid="index:string:50"}, {source="index:string:50"}, {nb="number"}, receive=function(data, save) save.sources = save.sources or {} save.sources[data.cid] = save.sources[data.cid] or {} inc_set(save.sources[data.cid], data.source, data, "nb") end, export=function(env) for cid, d in pairs(env.sources or {}) do for name, v in pairs(d) do add{cid=cid, source=name, nb=v} end end end }, achievements = { {id="index:string:40"}, {gained_on="timestamp"}, {who="string:50"}, {turn="number"}, receive=function(data, save) save[data.id] = {who=data.who, when=data.gained_on, turn=data.turn} end, export=function(env) for id, v in pairs(env) do add{id=id, who=v.who, gained_on=v.when, turn=v.turn} end end }, donations = { no_sync=true, {last_ask="timestamp"}, receive=function(data, save) save.last_ask = data.last_ask end, export=function(env) add{last_ask=env.last_ask} end }, scores = { - nosync=true, + no_sync=true, receive=function(data,save) save.sc = save.sc or {} save.sc[data.world] = save.sc[data.world] or {} diff --git a/game/profile-thread/Client.lua b/game/profile-thread/Client.lua index dea4d4ac1f21f12b9769d3cb4b795675473d3a1e..c3ab20f1b1bd923ee511475e1075850d6d367c92 100644 --- a/game/profile-thread/Client.lua +++ b/game/profile-thread/Client.lua @@ -392,6 +392,12 @@ function _M:orderSetConfigs(o) end end +function _M:orderSendIncrLog(o) + if not self.auth then cprofile.pushEvent("e='IncrLogConsume' ok=false") return end + self:command("CINC", o.data:len()) + if self:read("200") then self.sock:send(o.data) cprofile.pushEvent("e='IncrLogConsume' ok=true") end +end + function _M:orderSendError(o) o = table.serialize(o) self:command("ERR_", o:len())