From 0cf5e53094b927f31e4886f80cb985deb875deb6 Mon Sep 17 00:00:00 2001 From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54> Date: Fri, 1 Jul 2011 22:26:17 +0000 Subject: [PATCH] Improved online profile code, it should be more efficient and more controlled. Serverside storage changed completly, it will allow for much nicer server features. Due to the online profile storage change localy stored achievements will be lost, if you use an online profile they should come back correctly. When b29 is released the new server code will switch to prod, all previous betas will continue to sync but it will not update data seen by the server anymore and >=b29 versions wont see it. Basically: upgrade git-svn-id: http://svn.net-core.org/repos/t-engine4@3770 51575b47-30f0-44d4-a5cc-537603b46e54 --- game/engines/default/engine/Module.lua | 11 +- game/engines/default/engine/PlayerProfile.lua | 223 +++++++++++------- .../engine/interface/WorldAchievements.lua | 2 +- game/engines/default/engine/utils.lua | 4 +- game/engines/default/modules/boot/init.lua | 4 +- game/modules/tome/class/Actor.lua | 2 +- game/modules/tome/class/Game.lua | 5 +- game/modules/tome/class/Object.lua | 2 +- .../tome/class/interface/PlayerStats.lua | 33 +-- .../tome/data/general/objects/egos/boots.lua | 23 +- .../tome/data/zones/town-angolwen/npcs.lua | 1 - game/modules/tome/init.lua | 12 + game/profile-thread/Client.lua | 36 ++- ideas/quests.ods | Bin 12301 -> 12568 bytes src/profile.c | 2 + 15 files changed, 217 insertions(+), 143 deletions(-) diff --git a/game/engines/default/engine/Module.lua b/game/engines/default/engine/Module.lua index a2fed66a42..c338eed101 100644 --- a/game/engines/default/engine/Module.lua +++ b/game/engines/default/engine/Module.lua @@ -246,11 +246,6 @@ function _M:instanciate(mod, name, new_game, no_reboot) mod.version_name = ("%s-%d.%d.%d"):format(mod.short_name, mod.version[1], mod.version[2], mod.version[3]) - profile.generic.modules_loaded = profile.generic.modules_loaded or {} - profile.generic.modules_loaded[mod.short_name] = (profile.generic.modules_loaded[mod.short_name] or 0) + 1 - - profile:saveGenericProfile("modules_loaded", profile.generic.modules_loaded) - -- Turn based by default core.game.setRealtime(0) @@ -290,7 +285,8 @@ function _M:instanciate(mod, name, new_game, no_reboot) end profile:addStatFields(unpack(mod.profile_stats_fields or {})) - profile:loadModuleProfile(mod.short_name) + profile:setConfigsBatch(true) + profile:loadModuleProfile(mod.short_name, mod) profile:currentCharacter(mod.version_string, "game did not tell us") -- Init the module code @@ -339,6 +335,9 @@ function _M:instanciate(mod, name, new_game, no_reboot) end end print("[MODULE LOADER] done loading module", mod.long_name) + + profile:saveGenericProfile("modules_loaded", {name=mod.short_name, nb={"inc", 1}}) + profile:setConfigsBatch(false) end --- Setup write dir for a module diff --git a/game/engines/default/engine/PlayerProfile.lua b/game/engines/default/engine/PlayerProfile.lua index 2606d4ae17..b844bddd82 100644 --- a/game/engines/default/engine/PlayerProfile.lua +++ b/game/engines/default/engine/PlayerProfile.lua @@ -83,7 +83,7 @@ function _M:init() { [checkstats] = { invalid = { read={online=true}, write="online" }, valid = { read={online=true}, write="online" } }, ["^allow_build$"] = { invalid = { read={offline=true,online=true}, write="offline" }, valid = { read={offline=true,online=true}, write="online" } }, - ["^achievement%..*$"] = { invalid = { read={offline=true,online=true}, write="offline" }, valid = { read={online=true}, write="online" } }, + ["^achievements$"] = { invalid = { read={offline=true,online=true}, write="offline" }, valid = { read={online=true}, write="online" } }, } self.auth = false @@ -131,6 +131,14 @@ function _M:umountProfile(online, pop) if pop then fs.setWritePath(pop) end end +-- Define the fields that are sync'ed online, and how they are sync'ed +local generic_profile_defs = { + firstrun = {nosync=true, {firstrun="number"}, receive=function(data, save) save.firstrun = data.firstrun end }, + online = {nosync=true, {login="string:40", pass="string:40"}, receive=function(data, save) save.login = data.login save.pass = data.pass end }, + modules_played = { {name="index:string:30"}, {time_played="number"}, receive=function(data, save) max_set(save, data.name, data, "time_played") end, export=function(env) for k, v in pairs(env) do add{name=k, time_played=v} end end }, + modules_loaded = { {name="index:string:30"}, {nb="number"}, receive=function(data, save) max_set(save, data.name, data, "nb") end, export=function(env) for k, v in pairs(env) do add{name=k, nb=v} end end }, +} + --- Loads profile generic profile from disk -- Generic profile is always read from the "online" profile function _M:loadGenericProfile() @@ -207,7 +215,7 @@ function _M:filterSaveData(field) end --- Loads profile module profile from disk -function _M:loadModuleProfile(short_name) +function _M:loadModuleProfile(short_name, mod_def) if short_name == "boot" then return end -- Delay when we are currently saving @@ -238,84 +246,106 @@ function _M:loadModuleProfile(short_name) load(false) -- Load from offline profile load(true) -- Load from online profile - self:getConfigs(short_name) - self:syncOnline(short_name) - self.mod = self.modules[short_name] self.mod_name = short_name + + self:getConfigs(short_name, nil, mod_def) + self:syncOnline(short_name, mod_def) end --- Saves a profile data -function _M:saveGenericProfile(name, data, nosync) +function _M:saveGenericProfile(name, data, no_sync, nowrite) -- Delay when we are currently saving if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("saveGenericProfile", function() self:saveGenericProfile(name, data, nosync) end) return end - data = serialize(data) + if not generic_profile_defs[name] then print("[PROFILE] refusing unknown generic data", name) return end - -- Check for readability - local f, err = loadstring(data) - if not f then print("[PROFILE] cannot save generic data ", name, data, "it does not parse:") print(err) return end - setfenv(f, {}) - local ok, err = pcall(f) - if not ok and err then print("[PROFILE] cannot save generic data", name, data, "it does not parse") print(err) return end + profile.generic[name] = profile.generic[name] or {} + local dataenv = profile.generic[name] + local f = generic_profile_defs[name].receive + setfenv(f, { + inc_set=function(dataenv, key, data, dkey) + local v = data[dkey] + if type(v) == "number" then + elseif type(v) == "table" and v[1] == "inc" then v = (dataenv[key] or 0) + v[2] + end + dataenv[key] = v + data[dkey] = v + end, + max_set=function(dataenv, key, data, dkey) + local v = data[dkey] + if type(v) == "number" then + elseif type(v) == "table" and v[1] == "inc" then v = (dataenv[key] or 0) + v[2] + end + v = math.max(v, dataenv[key] or 0) + dataenv[key] = v + data[dkey] = v + end, + }) + f(data, dataenv) - local pop = self:mountProfile(true) - local f = fs.open("/generic/"..name..".profile", "w") - f:write(data) - f:close() - self:umountProfile(true, pop) + if not nowrite then + local pop = self:mountProfile(true) + local f = fs.open("/generic/"..name..".profile", "w") + f:write(serialize(dataenv)) + f:close() + self:umountProfile(true, pop) + end - if not nosync then self:setConfigs("generic", name, data) end + if not nosync and not generic_profile_defs[name].no_sync then self:setConfigs("generic", name, data) end end --- Saves a module profile data -function _M:saveModuleProfile(name, data, module, nosync) +function _M:saveModuleProfile(name, data, nosync, nowrite) if module == "boot" then return end + if not game or not game.__mod_info.profile_defs then 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, module, nosync) end) return end + if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("saveModuleProfile", function() self:saveModuleProfile(name, data, nosync) end) return end - data = serialize(data) - module = module or self.mod_name + local module = self.mod_name -- Check for readability - local f, err = loadstring(data) - if not f then print("[PROFILE] cannot save module data ", name, data, "it does not parse:") print(err) return end - setfenv(f, {}) - local ok, err = pcall(f) - if not ok and err then print("[PROFILE] cannot save module data", name, data, "it does not parse") print(err) return end - - local online = self:filterSaveData(name) - local pop = self:mountProfile(online, module) ---[[ - local path = "current-profile/modules/"..module.."/"..name..".profile" - local f, msg = fs.open(path, "w") - print("[PROFILE] search path: ") - table.foreach(fs.getSearchPath(), print) - print("[PROFILE] path: ", path) - print("[PROFILE] real path: ", fs.getRealPath(path), "::", fs.exists(path) and "exists" or "does not exist") - print("[PROFILE] write path is: ", fs.getWritePath()) - if f then - f:write(data) + profile.mod[name] = profile.mod[name] or {} + local dataenv = profile.mod[name] + local f = game.__mod_info.profile_defs[name].receive + setfenv(f, { + inc_set=function(dataenv, key, data, dkey) + local v = data[dkey] + if type(v) == "number" then + elseif type(v) == "table" and v[1] == "inc" then v = (dataenv[key] or 0) + v[2] + end + dataenv[key] = v + data[dkey] = v + end, + max_set=function(dataenv, key, data, dkey) + local v = data[dkey] + if type(v) == "number" then + elseif type(v) == "table" and v[1] == "inc" then v = (dataenv[key] or 0) + v[2] + end + v = math.max(v, dataenv[key] or 0) + dataenv[key] = v + data[dkey] = v + end, + }) + f(data, dataenv) + + if not nowrite then + local online = self:filterSaveData(name) + local pop = self:mountProfile(online, module) + local f = fs.open("/modules/"..module.."/"..name..".profile", "w") + f:write(serialize(dataenv)) f:close() - else - print("[PROFILE] physfs error:", msg) + self:umountProfile(online, pop) end -]] - local f = fs.open("/modules/"..module.."/"..name..".profile", "w") - f:write(data) - f:close() - - self:umountProfile(online, pop) - if not nosync then self:setConfigs(module, name, data) end + if not nosync and not game.__mod_info.profile_defs[name].no_sync then self:setConfigs(module, name, data) end end function _M:checkFirstRun() local result = self.generic.firstrun if not result then - firstrun = { When=os.time() } - self:saveGenericProfile("firstrun", firstrun, false) + self:saveGenericProfile("firstrun", {firstrun=os.time()}) end return result end @@ -328,8 +358,7 @@ function _M:performlogin(login, pass) self:tryAuth() self:waitFirstAuth() if (profile.auth) then - self.generic.online = { login=login, pass=pass } - self:saveGenericProfile("online", self.generic.online) + self:saveGenericProfile("online", {login=login, pass=pass}) self:getConfigs("generic") self:syncOnline("generic") end @@ -411,24 +440,13 @@ function _M:eventGetConfigs(e) local data = zlib.decompress(e.data):unserialize() local module = e.module if not data then print("[ONLINE PROFILE] get configs") return end - for name, val in pairs(data) do - print("[ONLINE PROFILE] config ", name) + for i = 1, #data do + local val = data[i] - local field = name - local f, err = loadstring(val) - if not f and err then - print("Error loading profile config: ", err) + if module == "generic" then + self:saveGenericProfile(e.kind, val, true, i < #data) else - if module == "generic" then - self.generic[field] = self.generic[field] or {} - self:loadData(f, self.generic[field]) - self:saveGenericProfile(field, self.generic[field], true) - else - self.modules[module] = self.modules[module] or {} - self.modules[module][field] = self.modules[module][field] or {} - self:loadData(f, self.modules[module][field]) - self:saveModuleProfile(field, self.modules[module][field], module, true) - end + self:saveModuleProfile(e.kind, val, true, i < #data) end end if self.evt_cbs.GetConfigs then self.evt_cbs.GetConfigs(e) self.evt_cbs.GetConfigs = nil end @@ -489,36 +507,81 @@ function _M:logOut() self:umountProfile(true, pop) end -function _M:getConfigs(module, cb) +function _M:getConfigs(module, cb, mod_def) self:waitFirstAuth() if not self.auth then return end self.evt_cbs.GetConfigs = cb - core.profile.pushOrder(table.serialize{o="GetConfigs", module=module}) + if module == "generic" then + for k, _ in pairs(generic_profile_defs) do + if not _.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) do + if not _.no_sync then + core.profile.pushOrder(table.serialize{o="GetConfigs", module=module, kind=k}) + end + end + end end -function _M:setConfigs(module, name, val) +function _M:setConfigsBatch(v) + core.profile.pushOrder(table.serialize{o="SetConfigsBatch", v=v and true or false}) +end + +function _M:setConfigs(module, name, data) self:waitFirstAuth() if not self.auth then return end if name == "online" then return end - if type(val) ~= "string" then val = serialize(val) end - core.profile.pushOrder(table.serialize{o="SetConfigs", module=module, data=zlib.compress(table.serialize{[name] = val})}) + if module ~= "generic" then + if not game.__mod_info.profile_defs then print("[PROFILE] saving config but no profile defs", module, name) return end + if not game.__mod_info.profile_defs[name] then print("[PROFILE] saving config but no profile def kind", module, name) return end + else + if not generic_profile_defs[name] then print("[PROFILE] saving config but no profile def kind", module, name) return end + end + core.profile.pushOrder(table.serialize{o="SetConfigs", module=module, kind=name, data=zlib.compress(table.serialize(data))}) end -function _M:syncOnline(module) +function _M:syncOnline(module, mod_def) self:waitFirstAuth() if not self.auth then return end local sync = self.generic if module ~= "generic" then sync = self.modules[module] end if not sync then return end - local data = {} - for k, v in pairs(sync) do if k ~= "online" then data[k] = serialize(v) end end - - core.profile.pushOrder(table.serialize{o="SetConfigs", module=module, data=zlib.compress(table.serialize(data))}) + self:setConfigsBatch(true) + if module == "generic" then + for k, def in pairs(generic_profile_defs) do + if not def.no_sync 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})) + f(sync[k]) + for i, r in ipairs(ret) do + core.profile.pushOrder(table.serialize{o="SetConfigs", module=module, kind=k, data=zlib.compress(table.serialize(r))}) + end + end + end + else + for k, def in pairs((mod_def or game.__mod_info).profile_defs) do + if not def.no_sync 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})) + f(sync[k]) + for i, r in ipairs(ret) do + core.profile.pushOrder(table.serialize{o="SetConfigs", module=module, kind=k, data=zlib.compress(table.serialize(r))}) + end + end + end + end + self:setConfigsBatch(false) end function _M:checkModuleHash(module, md5) self.hash_valid = false +do self.hash_valid = true return true end -- if not self.auth then return nil, "no online profile active" end if config.settings.cheat then return nil, "cheat mode active" end if game and game:isTainted() then return nil, "savefile tainted" end diff --git a/game/engines/default/engine/interface/WorldAchievements.lua b/game/engines/default/engine/interface/WorldAchievements.lua index 6fd3a68ae7..1cb397e117 100644 --- a/game/engines/default/engine/interface/WorldAchievements.lua +++ b/game/engines/default/engine/interface/WorldAchievements.lua @@ -131,7 +131,7 @@ function _M:gainAchievement(id, src, ...) self:gainPersonalAchievement(true, id, src, ...) self.achieved[id] = {turn=game.turn, who=self:achievementWho(src), when=os.date("%Y-%m-%d %H:%M:%S")} - profile:saveModuleProfile("achievement."..id, self.achieved[id]) + profile:saveModuleProfile("achievements", {id=id, turn=game.turn, who=self:achievementWho(src), gained_on=os.date("%Y-%m-%d %H:%M:%S")}) game.log("#LIGHT_GREEN#New Achievement: %s!", a.name) self:showAchievement("New Achievement: #LIGHT_GREEN#"..a.name, a) profile.chat:achievement(a.name) diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua index ce8e002d30..26a17fc6cf 100644 --- a/game/engines/default/engine/utils.lua +++ b/game/engines/default/engine/utils.lua @@ -1057,9 +1057,7 @@ function util.showMainMenu(no_reboot, reboot_engine, reboot_engine_version, rebo if game and type(game) == "table" and game.__session_time_played_start then if game.onDealloc then game:onDealloc() end - profile.generic.modules_played = profile.generic.modules_played or {} - profile.generic.modules_played[game.__mod_info.short_name] = (profile.generic.modules_played[game.__mod_info.short_name] or 0) + (os.time() - game.__session_time_played_start) - profile:saveGenericProfile("modules_played", profile.generic.modules_played) + profile:saveGenericProfile("modules_played", {name=game.__mod_info.short_name, time_played={"inc", os.time() - game.__session_time_played_start}}) end -- Join threads diff --git a/game/engines/default/modules/boot/init.lua b/game/engines/default/modules/boot/init.lua index 6c74d50df2..1dd00d698b 100644 --- a/game/engines/default/modules/boot/init.lua +++ b/game/engines/default/modules/boot/init.lua @@ -23,8 +23,8 @@ short_name = "boot" author = { "DarkGod", "darkgod@te4.org" } homepage = "http://te4.org/" is_boot = true -version = {0,9,26} -engine = {0,9,26,"te4"} +version = {0,9,29} +engine = {0,9,29,"te4"} description = [[ Bootmenu! ]] diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 255f9b02f7..1567c6a7e2 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -1835,7 +1835,7 @@ function _M:onAddObject(o) -- Achievement checks if self.player then - if o.unique then + if o.unique and not o.lore and not o.randart then game.player:registerArtifactsPicked(o) end world:gainAchievement("DEUS_EX_MACHINA", self, o) diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index f7beb01196..a8370de221 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -1438,12 +1438,11 @@ function _M:setAllowedBuild(what, notify) -- Do not unlock things in easy mode --if self.difficulty == self.DIFFICULTY_EASY then return end - profile.mod.allow_build = profile.mod.allow_build or {} + profile:saveModuleProfile("allow_build", {name=what}) + if profile.mod.allow_build[what] then return end profile.mod.allow_build[what] = true - profile:saveModuleProfile("allow_build", profile.mod.allow_build) - if notify then self.state:checkDonation() -- They gained someting nice, they could be more receptive self:registerDialog(require("mod.dialogs.UnlockDialog").new(what)) diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua index 2c289dda29..7788808e40 100644 --- a/game/modules/tome/class/Object.lua +++ b/game/modules/tome/class/Object.lua @@ -981,7 +981,7 @@ function _M:on_identify() game.player:learnLore(self.on_id_lore) end if self.unique and self.desc and not self.no_unique_lore then - game.player:additionalLore(self.unique, self:getName(), "artifacts", self.desc) + game.player:additionalLore(self.unique, self:getName{no_add_name=true, do_color=false, no_count=true}, "artifacts", self.desc) game.player:learnLore(self.unique) end end diff --git a/game/modules/tome/class/interface/PlayerStats.lua b/game/modules/tome/class/interface/PlayerStats.lua index 2ebf2ad857..aedea4c56c 100644 --- a/game/modules/tome/class/interface/PlayerStats.lua +++ b/game/modules/tome/class/interface/PlayerStats.lua @@ -28,53 +28,34 @@ end function _M:registerDeath(src) local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) local name = src.name - - profile.mod.deaths = profile.mod.deaths or {} - profile.mod.deaths.count = (profile.mod.deaths.count or 0) + 1 - - profile.mod.deaths.sources = profile.mod.deaths.sources or {} - profile.mod.deaths.sources[pid] = profile.mod.deaths.sources[pid] or {} - profile.mod.deaths.sources[pid][name] = (profile.mod.deaths.sources[pid][name] or 0) + 1 - profile:saveModuleProfile("deaths", profile.mod.deaths) + profile:saveModuleProfile("deaths", {source=name, cid=pid, nb={"inc",1}}) end function _M:registerUniqueKilled(who) local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) - profile.mod.uniques = profile.mod.uniques or { uniques={} } - profile.mod.uniques.uniques[who.name] = profile.mod.uniques.uniques[who.name] or {} - profile.mod.uniques.uniques[who.name][pid] = (profile.mod.uniques.uniques[who.name][pid] or 0) + 1 - profile:saveModuleProfile("uniques", profile.mod.uniques) + profile:saveModuleProfile("uniques", {victim=who.name, cid=pid, nb={"inc",1}}) end function _M:registerArtifactsPicked(what) if what.stat_picked_up then return end what.stat_picked_up = true local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) - local name = what:getName{do_color=false, do_count=false, force_id=true} + local name = what:getName{do_color=false, do_count=false, force_id=true, no_add_name=true} - profile.mod.artifacts = profile.mod.artifacts or { artifacts={} } - profile.mod.artifacts.artifacts[name] = profile.mod.artifacts.artifacts[name] or {} - profile.mod.artifacts.artifacts[name][pid] = (profile.mod.artifacts.artifacts[name][pid] or 0) + 1 - profile:saveModuleProfile("artifacts", profile.mod.artifacts) + profile:saveModuleProfile("artifacts", {name=name, cid=pid, nb={"inc",1}}) end function _M:registerCharacterPlayed() local pid = self:playerStatGetCharacterIdentifier(game.party:findMember{main=true}) - profile.mod.characters = profile.mod.characters or { characters={} } - profile.mod.characters.characters[pid] = (profile.mod.characters.characters[pid] or 0) + 1 - profile:saveModuleProfile("characters", profile.mod.characters) + profile:saveModuleProfile("characters", {cid=pid, nb={"inc",1}}) end function _M:registerLoreFound(lore) - profile.mod.lore = profile.mod.lore or { lore={} } - profile.mod.lore.lore[lore] = true - profile:saveModuleProfile("lore", profile.mod.lore) + profile:saveModuleProfile("lore", {name=lore, nb={"inc",1}}) end function _M:registerEscorts(status) - profile.mod.escorts = profile.mod.escorts or { saved=0, lost=0, betrayed=0, zigur=0 } - profile.mod.escorts[status] = profile.mod.escorts[status] + 1 - profile:saveModuleProfile("escorts", profile.mod.escorts) + profile:saveModuleProfile("escorts", {fate=status, nb={"inc",1}}) end diff --git a/game/modules/tome/data/general/objects/egos/boots.lua b/game/modules/tome/data/general/objects/egos/boots.lua index dc29aec601..667a71de9f 100644 --- a/game/modules/tome/data/general/objects/egos/boots.lua +++ b/game/modules/tome/data/general/objects/egos/boots.lua @@ -223,7 +223,6 @@ newEntity{ rarity = 35, cost = 80, max_power = 80, power_regen = 1, - use_talent = { id = Talents.T_DISENGAGE, level = 2, power = 80 }, wielder = { resists={ [DamageType.NATURE] = resolvers.mbonus_material(20, 10, function(e, v) return 0, -v end), @@ -234,7 +233,7 @@ newEntity{ }, pin_immune = resolvers.mbonus_material(50, 40, function(e, v) v=v/100 return 0, v end), combat_spellpower = resolvers.mbonus_material(7, 3), - }, + }, } newEntity{ @@ -251,7 +250,7 @@ newEntity{ blind_immune = resolvers.mbonus_material(15, 5, function(e, v) v=v/100 return 0, v end), confusion_immune = resolvers.mbonus_material(15, 5, function(e, v) v=v/100 return 0, v end), disease_immune = resolvers.mbonus_material(15, 5, function(e, v) v=v/100 return 0, v end), - }, + }, } newEntity{ @@ -269,7 +268,7 @@ newEntity{ [DamageType.COLD] = resolvers.mbonus_material(10, 5), }, combat_armor = resolvers.mbonus_material(7, 3), - }, + }, } newEntity{ @@ -283,7 +282,7 @@ newEntity{ max_mana = resolvers.mbonus_material(40, 20), mana_regen = resolvers.mbonus_material(50, 10, function(e, v) v=v/100 return 0, v end), combat_spellcrit = resolvers.mbonus_material(4, 1), - }, + }, } newEntity{ @@ -300,7 +299,7 @@ newEntity{ stun_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, v end), pin_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, v end), confusion_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, v end), - }, + }, } newEntity{ @@ -320,7 +319,7 @@ newEntity{ pin_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, v end), poison_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, -v end), disease_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, -v end), - }, + }, } newEntity{ @@ -334,7 +333,7 @@ newEntity{ resource_leech_chance = resolvers.mbonus_material(10, 5), resource_leech_value = resolvers.mbonus_material(1, 1), max_life = resolvers.mbonus_material(70, 40, function(e, v) return 0, -v end), - }, + }, } newEntity{ @@ -348,10 +347,10 @@ newEntity{ disarm_immune = resolvers.mbonus_material(15, 10, function(e, v) v=v/100 return 0, v end), combat_physcrit = resolvers.mbonus_material(4, 1), combat_dam = resolvers.mbonus_material(3, 3), - resists_pen = { + resists_pen = { [DamageType.PHYSICAL] = resolvers.mbonus_material(10, 5), }, - }, + }, } newEntity{ @@ -367,7 +366,7 @@ newEntity{ inc_stats = { [Stats.STAT_MAG] = resolvers.mbonus_material(5, 1), }, - }, + }, } newEntity{ @@ -383,5 +382,5 @@ newEntity{ combat_mentalresist = resolvers.mbonus_material(7, 1), combat_physresist = resolvers.mbonus_material(7, 1), combat_spellresist = resolvers.mbonus_material(7, 1), - }, + }, } \ No newline at end of file diff --git a/game/modules/tome/data/zones/town-angolwen/npcs.lua b/game/modules/tome/data/zones/town-angolwen/npcs.lua index 867a79d2f3..085646f185 100644 --- a/game/modules/tome/data/zones/town-angolwen/npcs.lua +++ b/game/modules/tome/data/zones/town-angolwen/npcs.lua @@ -94,7 +94,6 @@ newEntity{ define_as = "TARELION", level_range = {30, nil}, exp_worth = 2, rank = 4, size_category = 3, - female = true, mana_regen = 120, max_mana = 2000, max_life = 350, life_rating = 24, fixed_rating = true, diff --git a/game/modules/tome/init.lua b/game/modules/tome/init.lua index 5b649c668b..a8a3094a35 100644 --- a/game/modules/tome/init.lua +++ b/game/modules/tome/init.lua @@ -48,3 +48,15 @@ starter = "mod.load" profile_stats_fields = {"artifacts", "characters", "deaths", "uniques", "lore", "escorts"} allow_userchat = true -- We can talk to the online community no_get_name = true -- Name setting for new characters is done by the module itself + +-- Define the fields that are sync'ed online, and how they are sync'ed +profile_defs = { + allow_build = { {name="index:string:30"}, 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 }, + 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 }, +} diff --git a/game/profile-thread/Client.lua b/game/profile-thread/Client.lua index ed2d011cf4..a79d0987ef 100644 --- a/game/profile-thread/Client.lua +++ b/game/profile-thread/Client.lua @@ -30,7 +30,7 @@ end function _M:connected() if self.sock then return true end - self.sock = socket.connect("te4.org", 2257) + self.sock = socket.connect("te4.org", 2259) if not self.sock then return false end -- self.sock:settimeout(10) print("[PROFILE] Thread connected to te4.org") @@ -43,7 +43,7 @@ end --- Connects the second tcp channel to receive data function _M:connectedPull() if self.psock then return true end - self.psock = socket.connect("te4.org", 2258) + self.psock = socket.connect("te4.org", 2260) if not self.psock then return false end -- self.psock:settimeout(10) print("[PROFILE] Pull socket connected to te4.org") @@ -256,21 +256,43 @@ end function _M:orderGetConfigs(o) if not self.auth then return end - self:command("GCFS", o.module) + self:command("CGET", o.module, o.kind) if self:read("200") then local _, _, size = self.last_line:find("^([0-9]+)") size = tonumber(size) if not size or size < 1 then return end local body = self:receive(size) - cprofile.pushEvent(string.format("e='GetConfigs' module=%q data=%q", o.module, body)) + cprofile.pushEvent(string.format("e='GetConfigs' module=%q kind=%q data=%q", o.module, o.kind, body)) + end +end + +function _M:orderSetConfigsBatch(o) + if not o.v then + if not self.setConfigsBatching then return end + self.setConfigsBatchingLevel = self.setConfigsBatchingLevel - 1 + if self.setConfigsBatchingLevel > 0 then return end + + print("[PROFILE THREAD] flushing CSETs") + + local data = zlib.compress(table.serialize(self.setConfigsBatching)) + self:command("FSET", data:len()) + if self:read("200") then self.sock:send(data) end + + self.setConfigsBatching = nil + else + print("[PROFILE THREAD] batching CSETs") + self.setConfigsBatching = self.setConfigsBatching or {} + self.setConfigsBatchingLevel = (self.setConfigsBatchingLevel or 0) + 1 end end function _M:orderSetConfigs(o) if not self.auth then return end - self:command("SCFS", o.data:len(), o.module) - if self:read("200") then - self.sock:send(o.data) + if self.setConfigsBatching then + self.setConfigsBatching[#self.setConfigsBatching+1] = o + else + self:command("CSET", o.data:len(), o.module, o.kind) + if self:read("200") then self.sock:send(o.data) end end end diff --git a/ideas/quests.ods b/ideas/quests.ods index 267fd2f43118649e1707a2466a6528a062a536b1..9f355f20f4a250d07c5ab1717b42eb549c5008b2 100644 GIT binary patch delta 9886 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 delta 9572 zcmcI~WmFwYwlENa6C}7h1UdM@El7~yo<MMScN%v|IJg{Kf=h4+?(PmD!QC~$OY-iW zH#6(|GwXe?diC17y0=tU)!J5_Y#6PNt|a#i9tQ>n2?nOrzx)%r^0VK-fD|BIa$N$S z`Lx09L#L*osfjFliw!bYycV7dz4Bn!C87IjM5<71OH6&(@#;HnpEbq#df$an7#fzw zMET;pS&dZ0K04T8eN#3bwDv-vd61?V-Y8gOtnSLywVt3zi~e<Vri~BqXmYVIy9RyQ z{H&mg)#Z@$gX-C4juuns6To>;cHrQCZ(YK+r{%<SQ1zW~-Uy$3pG0kD-~H=lRVXf0 ziw6?jeBXQCN~+UXkn#EOwVtr$iv=F&aRz~~&0@+TmGX9TVUxu+6H~4l^-N8=fWxy$ zrdQKFHD%#Wx2O7S847L%_X3Mih+c|NLBgAQ?oNjl5Np&$S)4lzHh`2&dT@AvUR+1W zyNV-XDG(dSSdEq3o3|eG6jRa&8!2z!M3X#%ka}E0p_lZdchZIL!|N^gPI)7~^JsiY z6A=p#<Nbj5T2%7@_M<PDw<qbbZ+G9SkwaTmYueRctqRNIhegQj`9tk2ckwhOvuo^; zz1y`u<|EWfsI}&H8bCrcE%)#?J5L^JC}<e9r6#IdBG!*Y0?BObOG*S)%JHKiuDh(v z)5(QCZtw2bxWf^)*)kv5kszCpYR<*8cL(Kj3Xu@>*E(<;Ol}S0H*%Lcq&2(+X_8*l zS@MHr9IaF(%m6=2czg%vSNeAy0k*C0$)IT7x$o?s_gf}R12#ju?dmV2#@{y3h=p-P zN{FwI-=69mOFmdumzZ{a65XL`<@+ApgY~}o*fzK0wi&iHvcDw@j)ih0MLn;9{#zBN zjy6%fn)RkuKW+1mMtrPd-*GmSJ!2dZrWKe|;#8WI4v{*jCLUj@s6Y{Uo_R`cd><w) zrriBci}BJ+BLMG_j!|%lpFMy&RC!k&orBVBZfv`GGR+(fWgsMSTp_p?mBn%0P)#+7 z<Q`iQ(Jj!IGTjBiz1J6}Yk@Fs6Hh{Tqp>b?Z4%tlK-`n!^6>$Sq|Gn$1|vAM?}KH6 zU-d`kp0nB6jCDS(-nBD6)^xh;2x<|95%(u21C6TH62L#<<w*(VA(WY;R{b&^AlPhR zZ0Chd%)fIF8g2-a@(YN1hP`9iU45%YV_b9PCQRC!@&<U3)~rNaj#}H5@vxHX+HhC_ zvCgdPI@H%~Kz9py!{Q*IgKvu{(AAnKDD)2BR?*cJ1Nx=waO$!wxb>k6+>m0z*uArh z-hDD42xzS13Z?rID2}Hd9g&b=85~J9RQe@OghvgTf34?y{-wh!+*cd@nEs7b=hErY zuCCB9n=jnUO>Z=1lO~l9(r!j7PA^e9tf{O)x24^MB+==%XzICd>3QS0J`vYqzSlC< z6`ci1j-s1j{UmpzdYfU3QVT{Nb;--1#sJ(9AAoaSBMrGvl^CuMZS6Q@dQs6Jwbv!$ zM!a`oQBRZ3R<;DluN=iKo}Vkq_k7Pc3UyW3HrZZ;P~e{+^sX8w@xAbWEC{RL>zh&` zlX^#o3KGpi_YBg}y-xKa=hr|_bS{+E=$z4gHZA`XlV!eTSmnBF%j$&?LpjJ~P3Yh@ zw;Rygim)a)O^Y*1xjw&98y)F8t_jiJ;;GxP<aDg17(V2*W3Mt%FynbyLQu{yL~*RY zdxmhiJV@7;wxn+{t^Ji?*Vr{9uYW>B_5i+J2p}TCc&c%J-FczVsB};^ZCm0kDB^Yu z6WrH`g<_WEXklPw&@hSoHNP3dLoSwHi5*}*4+a;hAx8HRg9(l+d))7@V>GMUZj%vb zk|{~`z_p+JNg4t-7G_tEPJBC=VWU<LSd?CIH271*xDk79&})QqfKbDhnftb9DS<vN z1e=+Su{Ig#i$T527q&4=n3H1E(rX6$E**EWIsv<I^YRuqM&>@S;G-b;>%o=Pmq3M@ zEs|OvHrSfWV!y#bS%m>=KVy4^E(Jh1%)Z1YIwVvEnej#5($1^*x5Puc-fw2Xy4ok? zrUgW}!&&zW(w+$2KPQc`W%pJhQG{`fn$4e{8ohnUpCCjvvg2IWf=g;*!w6cz4?uYH zA#<-&9-P#HV7!5nFX}qFHn%~+4jc&*!X|xDc&I4x+qYAX^jqW6IP?479OK}nbRdjM z<UJ_A$k(>k@tnWotrlIa3*bY6R>wZ(PZ+yYa6Y5JyHIo_e_rCA!*0unf1(|;A{F;k z9ryC4(Bdd=Ggy{Q{~&#RTUs_ZqcGPc>$^<P&#P~qb1T#`i3r%HDA{1>4}jAz^2mo> z-j={24e5?LQwR<3{lyc{5`~vduz7h_X=)yP{FK3rqmw@jS(sBaO7r9#;xIn8$wwp- z=P8%EDGu4jjTNU<oVp^P`EEf17F|RYgKgY}k&h_W&lv^jq<&N@Oohdkg833ETt<&y zCMvVXV>M;5xCPDfg{c+gRRc(el%)(H6)QLN39|sH=>Tl_q9Km#1``E>ssXV^umjht z_<dm-MEt=6=G@bi8A7(8*;ZlC=#mVdvU69;&WP9N2^z>!P_>Cs(<TZrtUc_~mqr|n z!n^uK##rjqBeP7DDl&^mU*+3!xFcLBmFG|J;f)E&cMTP=MJmXQhJg4w7Q&Es42h~Z z61{{81Wp68R%wo1en=-+shr@=nzM(#G^T1*N=W}xR`|8ys`<r9u29(5QsRgi+K)b* zO$3UJTS=3mgI2=e%LdKrsu86b-H)bn%FdcoRk5<Kt%c>H!l<)VbW&RIOKt`WNob>d z$_-`ndq@vRNx_k$C_v9@j%FYjlHr$9o2EOp@;O(kp(VauE_?9#q@N6E7%ae!T>MbA z`prG!t(mR;XlqecR0*M%T-V${B~Mw|wm5c`sX)f)4&h?+w?69NltMP*Y@#mpmJVBQ zTZ+N)#b$;H{(WJ=v6g(%3ds)Vn*5#Gm;qcb6O{56gS?#pd7yQh_TXOs$upi>MPBb) zyBIwsA22QDs)T;#x4ElpKwrDGDKixIWyAk{;kl#Ym;*IfGly>HP$EnlV^?48Y9kf8 z|B44$V1Rp4^?lU>{~oRhRUl&=RSCHG5S$wrLZgi{kU&W`0mV8LuadQ(UPJsmWh$W_ zyMuCBb9m7k1++L_R?`y%(+<qi#;coF=eW0?s&M%)^%T98VU}LqLYW}UILfEyWs5F2 zj8=C-IuJ-MH++$HU^u;(6R|r-DS0Ykg&M`V#NWTGEsEFI=XxGUPD*w}3QEdu-m}H< z{IrNmF)E@>;FisQAN4vn6ZH$yRcq;(@PT_vF2WB|7vSCgiZQ~<>+XCvk#Uidq2^GR z0@Rlk=<m!Ve?%9b_PZRzFB&;A7hRmJ5hlBLdd-Un@Cc4kT4*oL8{fl^1h*-aQWWNR znT73{^Oo#%sxY>0#WVTIElv+g5h%F5NFez@l1{kDb&p&?F%q?aEVaFJ0KLmtK1W|0 z7}-hZ8wAium0#N6%0oE(Jk7x!o3`{+fOYk)iX3v4kymIuthLW-u8fYz?C=~W8bPd4 zWnvDi!ac=QU2S!&%oWM_M7MOIvUOJG12|%COo)KI>iT(s9;`$t>Zz$&+)~cHDrSYT z;Z6+I6<))_x9IvaL-dL*Fa1fYFZ&1Y^9!-#8~_YYwVr_DH={1^*J@<I33dF!8`=tz zH0=rKn=tc|ou!L!hMh4`x4EX5AB5O~Pvt-~=#`CzVPthzW;nV-*jsdR3$ZJ=l_4d0 zq4L>&Wl3c){yx`No3HaDB(^KOg0GQr-M(pI&#B&$=eD+0fmbvVt|{Em3wSv0;MDR) zihzm(o->4z37pm+FeOgB3HyO??eFpXxgz6=7-Yw#SaUO|yBr5_nbibO8PPv50QB*8 zOV5-&G^Fi63?Y0Gb}T}*SbJ#5=|V`_l!@I`6#N?Yy;Ez)Y{+yYlJql<iZg53K2mfm zxKySG6dd1z$ofN#iR&h_G6{;igJvHki2~5NEIXoJvxifhvZCOA@<Gb<SE5ny{$aEl z2otv;p(~Z&?K{LaC1nsey57Q2B>bhE_KNe2_E;w16jutv<|VB>O1j3kAtP*ImA5{~ zYpAS_3|`s3Et>1^AC|lJ0_x93w=x4lLy{Oj$;eCGVMk31rB4VsEgH|jW@uetp4<SW zwgG-*L@4gERe2ah^Q)DyyBiEa@d{LtA_ciGrD9Q$7WS&ULn=M|=bi0jWgE_=2<2-h z9z`?!334w-a5ba2q$B0OC{TfRb9^ScasUVP;4n`W*gfIRNQJhS@rfG`9XCXI6`aFb zy{zM+at^eMAM~bRhA{+5&Y0qF8npndVIyqG^7OEsH@$s1o30jJ?zEd<S?-ev@K@g8 z=qWb^w@&xsG+KG>O6PjJFo1vN;~%tzFg2Yw)JFJNQ_W^N-|ob8?|;F^d_ZC$SB_D- zRV9ORnRN2=?6i$%%cesP9-~fWdcx(Cc%MbQDG9nm4~~g_HTLd^*4;cK@D>h0%W%oB zdX=!;1x6-aT`G)QCP^2aS!iV!I-Q?eUHO=>hG$@s!$0@B)&-XgLh>OwB}xTT!~r+B zq>7*ZRE~3~mBv@D^6Nz<*?X0frM*d%n4js&tMumL0J(e!C|R~iPmjJ=Y|tpwQ9Gb$ zbWd)&BaI1yCl04MZg!7G<X;E4O>>1aV~JAG1{mmFJ@3st2MyhDXI%8Qc_HO6cT0L? zwd5qPDNz!ST&Puby0#UeMj{k&u$Hjzv4_(njGBv+Q(;+P7}zK9>io=?ZNz<XyTqrI zDnpxrjE}$UT+yN8fD2l}B<x{muQ_6(mCv>mvmVRnqY2`xW|+`?x90#{e{MauA0fWS zdV2OuOz299eJQDEqTZdLa8CsrELA&-`2sKY5}o~tBtx)ohm28r-h5V%?ffyU;57V@ zi%6!-YhI<ir2?Nz(CgPH!R<;#_H9XNq!5jkZd^pm;pGxHj_h>e%MyM^x#ssUzEPXB z8lvS-{xOS4kxD?CLrV)(bzuJdBvlVveg>=CaG3>vxoTvLc-+HYjUO_VWLrDn$tUUh zR27rZRx{z!Gm`?}e?M<8l9uIT<5uCL=P0Bj)_FCj_wy<?XQkr`cdP-L^%YI<&0&%A z+msaKnJ(DHo8;{c3NKZKq(+<EA5o{dYlZ1yQM5A^;HcM$#=X`6DEJ(Mv$$7MZwbF= z>T~ki<M~p>Y-Fch+vC-m4L^NuLhBe=Y1&EC64UdLeS9cS55F<})h_*!2um+o8-o+W zOPiPyel5F`jGTa-@hETfCXXvIc5nH5>>DNupZ4AxEfETo@du1?4l3<SJQ1LFPAkg` z2Pe4gn#3hIV15UPs*5~Y?bBv8Sv7<?cX}UOxhEtrgTkLSUQaqM|6v+R;FK~<Zb*Ua zIksMQz2L0UE-%JpnZf_9l6Inw&__41*&XhYvDDh@@o~~#AaTx0Aa(`8J9COGmmQ9H z0xq&vHd+Ie?4$=rHdg|YdM8hxx*1%v`ekZUVKooP+ZzJh(X<R!`X2G-BHC|CI<N7i zn>xwCn^Bm{FY0Rz^==w5J**DTV0Kd?PP-A>dZ&zzockKGo@Fn^|E!#Q`C`WBos6Pj z)1j;XGcL@{kAWf=Sd;R5AE!m{R3~XZvaLH;;#y?lu+yx}jy;hV-K46T5X;h)amp7| z4)^55Dk0|p7|Ak3v=dI!>g`H#p9gQJhnm0Qhq!XhiKUxTZt@_X_#V;RUSxMiFpONF z^9?o_s||h|QJV*?$Cw1OGM6SHn;FGK5b)jkmEI#s!(-LUK{q?wtJ)?PbH8duiR~v; zr*4!?pJyPYSR*EcA}nRqf{NVH5}sL#coTwSyg5mLn6b{)PqC5h-EP|{nz>zNSB=@K ziEupY&lEb%O57VhL1irohZ_V<H`jG15(6Wd3ht!$JGV7ULf4;_&FF>4psM6yg%dqK z{REWc5RsG^342~)!N3s4z`*=>#RUlo>379N80McE$}il>*~8kz2?Qa<Mn@q)QTozu zF6-Lb3*o|M0N`k_JcQ3qyhBB@y&W3mzE6ap47fKrw^Z#%neUzZ<xfoB?jJHaAL*3) zHheSa_ijRJ$fV~|mo83A9>jt#bW~OYW$5IgD$qb8<7w1D@onD-<x8?8Nn6=;acK*x zveyxGVl|u#LlCU2sS|ZDju-s)C;TavvO9=SQ)j!x0bwq8dLtaijdL362-3>=9%i35 z=UG`3)TsKULw5mECoBUy;Y=e{E985;q)jStQF|X(K@(?qYgH(d^=n+hK}Dne_({RW z0>er!iBB3DoMN=1`VEUoT|~uwTt92W5t^-DUiKeUgcBTaKC^viLWEX7$W_!wCsF2g z<`ttS4&>jS-d(p=NN{{%jkb~AL`_5|Gxaa;a~fq)CslguD(?2ZNzvg{N8B#9vp(Pz zqow^5!!mu99qzVFlDXeG8wKWy`H-f0HsZ?C85(M;LpJj#`x`^Vy|)Kyx+OG6nZ-?Y z0a8>*{Mn*r5eAE$d1Ay;^=8qv2Mv!-)}phMDu9BzCb?ZjWx2R#W6<+#ohqwl_^5tN z9R@L#&+=*t)730WVlmPD(lu<+cxCNGoE??WSHpq!Q~~yA7^B6D`mbN4$w_-h!0q)p zx+pWF)w!z)S`8$Pjs;+0qHS3}m)7#GVXX+=Bf^=8M?oNqdrxQb>=XND7xNNk*bMgi z4_%;5UzSl!hQssCbHWE?MrTY92@X)En|@@(4;;I^oB>_luXzB(t#Q}x0#)%@sw~k( z^?^iY6#dbVo%V-x`r=b~E*cLVC`WOkTwQNRe_@rYymA7T7?oHx_aOYdJOjcTNDj*Q zILeq(dmhzi;e0<>Gv8`_#k3Yu^DwYA-WeeIc9MY}D}G`c=SEO50Y;<ktNn{-So4{A zJ{s)v#jPlJ2K6PygNY@n@r7Sj0#`p9HWAN;K6kq?+<z;=uSXiLrHga#+1Scct;%p2 zx=-RKwW->v(yby%9fqm}?@WnRkQ)QGA@&8qr%Ty2HJvS+*)3a=5R-Mm<;6P6ZhQe^ z;k9CDX{5B#5a8h|BS`Z#XQTqQQ}{5`W~*#Iyf@>nHe!plIpP`MjxDLX!b1r@Y+u}& zNvOFy&ID{C5u;1_8EvVPr?x$eHj}$P9WtZo-td#q?2&Eo6xvZLzUB}RoAkqdzZBBV z=ks$zIS<NOb}F6NlfPJG@YcIeXXF^Lw7vci@wCUO?RAviUV))d4QX)kb*uMWt#8Bu z=zhUh%Xi;s1cKj3PaW;e4qVq>YB>6=8+8S$UeokK)7$5#j)zXlHtu}M9^rbS(DwNn zR+9rp1*Mmt?amTkyuN1Tc7vQliJm%&fVgmW0b%xte%Hqjcy1h5;36N^5a2$rqDYWT zpjy-NiksZ>%B!1y%e2@#tq&(WMX%PZ-GfL8t0rE9V896PLbQ%~yh2F2+#Mr_t9W<% z=b_e%_k%gy?uc=?)8#e7Io6b;8<1%W6f4_p47=wso0~kc7ff?gG`$Ee%b}LYl<dk8 zG-JVEG0;<5Uj%u)3CDYBS_D8nDZ@<;Qyb5rxHM}#P0rp(c{xw6GfhE0<8D<VCWE_9 zqbqURjC($DdFuQj(v0q>y;vOqIlE&kU>R@w5nW+Rr+prJ;bKNcDJ)6$#`Q+as9l#t zcLl^EdD%AJK-jDxL`;0tyz*;4?XI0Gx3cK!k51BSVx1Q`tsuy^7$9>*P=tL!CM+zA zu^_wL94`WUmAQ3GcK6z`yfv`zAzl`w=U?z$`&Z$ae0i|b??C=*{=sgl6$tL3V2!kS zd)t?TgXwN(m|G9w)2FN(He}J3bH=Ef<4aR0!VIJDR`cD`o~n5JCyE@Fk}th?FfGn{ zxc3b;4#yGd8Z;7%bil@=pD~JyTwe^^W#f3&IPQfe{#KWO_U-%fS1jWtLqkKT-db9T z#)woT8y~i-7OPW&#JA<GrkK7|wYE(dy&~s2{C-k*R{Usb!h)i%iarsm(#}FELU*Ws zD;7*WsPJx3qZ!DnXP4heG>j#~=j~C`3B?&xeD|8m(-J3z&lwmOASp0EZbVVA!V4N+ zAbwo27s6=d+z@inSXDh3tpvT&HvhKQrz}Y@@3oC;*OWZWybHSLy7l2!jJ78=f7I0; z*>=%azDj8cZ8j4)dUXG`_s2ISetD1yM1X<$!0@+k`s*}6xK#<X{vI9%=J$;RQ!_%~ z2#3zi!p^0}&dblv$<M(Jc}vUy&m+&y2k{_gh2?>KCa!>GgJ_d%y!h?ftUgG*f&LN< zMS#eWvI9dfFt9L4aKC(9TnBC;3=D#ajD(n)d-~zxq3w5mg5U|Y?~itkW_gyppG7{T z#p{lnmyuJ;iXYHmWV>u%-QIeL4j<0E{6>B)YCuj--bK86aEtoY^%6oVe6c7({B&DC z|9JKmDiBQ{<6^VjMG5X}U8#TcJgIFy5B%YLvJE(~3$k7(ajorLFB}x|cAh7Vl!kPz z#CkVup1eh;pBigRaec~K12}E5RGlJH57X8Yn^{JkfYm$BI>C=V^*_9#y3Soz_1)j4 z?ShTC`T&u<3Luo#f^JH%JaoT)VL_iS#dPsO&CF=!df^RUdT$l~z=-YrVeCK(;i_<m z7XXz%3!r}@(P$w~B(EOQNF^1%Tg*6jG2qzFcq39trb6Cd+tuP~UT<_ZQ8Vmaw{S)_ zTVh9Cb7;10zO5cS0$Gi!{&^$tHS<eo2m#ryBeVu}LS*%pfwxXk|A#C255d(Fv6_9~ znuU`Ggon1vt61N9-x){42ZSzUe9jcz5MadOz}pHu;Q3?0ZJ)C--L=C~Qe#K_ywg_i z^0bVt8b>Ozv)^*AYjtYK?B)!a>n@>l6YZXR=?zfdy0w)0XpeRLXwrHmbpK5B$@r=H z)U1B6W!a!@ELUKz=K1NuMacfv$U-Jd!8KEUPbOj_ggInKvHxU9+qLKWmCtt9CUCzn zBRDv8y?y#rg4?wTSx^_uj9+LrbCN9c*lOuAiF^p(p>InUcc68RvC=q+F^n5=VcZhA zcLeKH-MPQbArzh(F5^qNK137=)%|LAh5xysH%W6WlJLk|OOUI3>>8|Nwzy{VRK7}n z-I#gM!&2uPS`XX`E7*W}hDY*mfrYYK;H<Ow!ai$V43B)DbNk1Nc5Y_0%dfz-1$l%{ z+xx}QTWj#LiqE<$ozu@&P`zpH*7F*>j5U>-`ndyYQ{J@o@>Ikje^Zen+=|p_t+!q` z4P^TVoiuK}x{oc~f|KjMAs&arHtw=(ZnfV#Ngu<>Q5q?I4dO&dHXAv;fwj&X9)>99 z;7>E<4IsUf<u!i*h##`j%(k!=)&uqlNaw6DPL7s@O0|TArP};4lJ3dg(Gh^E&<dxA z*IT~wEWPI(w`R$L_S$EA^BM{bEF7D6xpXXaM{r-%UzSxig+G$uj-jndVX%mjuOtwU z7Pk4iz<2coEa+if9U}?=NgnR~Bga3}>|!4h%97F^Ni@={&(msS9bIMiB!3#7mM+Yk zy?flKczzEKY;J=k%g|~~y%FfU!I|nzj9jdt76j_sXwk^WLP|0P*P24S(nea8*5by9 z511>)gfG&}?lQ)%PImNOt%_YfU3iVuhv>~Nd26pGhBOn6o_j+82C3zRnekE69!gd~ zpMUKTZ1&!cZFwEF)wK3|BjQtOD=lasO=k<gn(X&ZR3Xqtp_Vf+e)W6C^8ebN|C@)v z|Ao;1Kql~i@J;`L`cB~g|CIhG>idEJ&Eow>n*QI4XbH5RV0To@mvmgS=YJ<%GLi}s z<*)Sv{v{uSEYqm{J_3hu((?R)Hng07U<(~3q?;D!ANbF|a2r}m2p1jpU$m4E%fE1d zj{h%OZs6bZJhb0=o{6)wg{_$r$lb>J@ZFlzA}^+ITU)@X0PM@SS5VQ=@m*Rsi?j}! z!%O*jRC6jTpE$H^;9&rKfjwtSVw8V4t%#O*dFWYtzdIFMGj|PZRVF4K;cdR~Mz`eo zvUuj%=6F*|t4&+=huBAe_m>s${C2Y=32Q%-m=*6US?BBq%J|}8E*T&oO_T6~9#;B| z$BU5m>y`9SJAv@3ivgkBpSI>@@hBMzW;F{WHZieE%mP>XY&$e%_nJZ*s7=gPj6V0- zuOxn2G8}#6hY;+$G(RZZtyvzXdt;_TxflBko^Eq%GW;(J<#FKw2kUpueN|TCa%s-9 zIAFb8)6Gk-<?TdSels>aQB#6<_foa!F4UWYL@s0eYXO3&Hz;_6XlHNsO4P|N!fi51 zqI_YU8wW3!kFGx?1lu9fSgRg&3EgO`Rt}Ko=EaVJs#t!oS%V&;l2nj0U}hN<5bw32 zy;+1JW)g@SuilUYZhZJbj-Qp_X&vpck;A_e(W*DW3(q7o#(sJlARU~t9LHza!QY5r z<1toi6(_GYs(kGrni!}w%^3_m^(D{dFS*EmLOAJz=2%IO7Y4DSc21Qbe7&wW-CFEY zh;-i+UG^mOj`fU@HBh2HG3(YZP_`V|A-G7aWQa_~^Qf={(E70r10Fzk7LxXE!D%AT zM;xz7KEbZfN{1tzeR3l61qK<u@5+%&6Q-Pqzq)HM*a^ud4zVS;b2Hv?dna9APFS3o zk$)&AK7}sN!?iMgaV$Lry3D?`gIAfMDZK9uYudvEORQkJh)<&{KSfFCPNyH3Cb5(m zXEy6o3oS4Je90by{!t_041)DL7>u)a5Q8T(E$eSOFpbT4nB;yqCQAWq+8=FAhP@H| z!=KJ>cXbe+gcw!FXDm-%Z4kHwph+U68t90*9$z%f34C^6J~P)rm)H!DKJ>&nd1VXo zyP+5+aL|O2eHjgVA$Lou66I)L-$9b%OeyeV*06gMph;hkmwvwBU!Gg7@=dN$Ae7tG z07WI%E&FF(^GXC-eDK&BGau$^^p>!$s`ier{I}atP5BHQUvI_y=FL@EibB0_o89e4 zn8Hubk&Az_L|%x|NDH=)HQKV9zCBM)#Mxs_S46t@ODURY6FaHO3(|YDtco?D9X_or zqZz-p0t5*wE4Ap9xVuAs9ZOIZF_fHaOLSU1r8`p9X>22EMp;nmf1&C#=&$3!?k3LZ zuK=&4Xu!8!aT%yY>QbH-zN4lzdoRAY`0<LH6%@6fOUnLr4me9HrX`qI#@4T<3UFU+ zLq^DkA>`CJ9Gb+R*kjErSUWiS)WmP!qq36~q*^NHBE**_fkGeTfi)G-4zmW~UM{FN z7CAu{x5)8U<v!QYXA7h#y`l`>t3DqoTUD6w*`D|lo7*?we0=dM`etYOJ2wBTZd4=8 zlnVRDz$`Iy{)x8#r^EyyVPQi4ZFBxa_7Ei&T)w(rwWq%-OTX-mk{m1?4$MFP`uVpw z|F>=YyE#ChEcg`v8qCAM5d8i64?H7*D3TIEj#<$DjOUSs7_OHZLJZ>m!}rgmB>&uj zA*F%XgBbttWPpg_G-)8-v?S>NGBU_pHe$GFnmAU5KV-jqJv5L#TC#s5`_2DA1Nq2G z_YZNLv=A-2e;4<}N(^T~3yGnlhKR7C{6l5|Eo6v}jPbu!|6Q2-=c)f~u>Ys6(n6}( li2iVo{u&V-BSfAFA5Tf{`LA3D1_txj1^%^U9ZbJ_{{??_2fF|O diff --git a/src/profile.c b/src/profile.c index 3c796a1a95..7afd243618 100644 --- a/src/profile.c +++ b/src/profile.c @@ -68,6 +68,7 @@ int pop_order(lua_State *L) if (q) { lua_pushlstring(L, q->payload, q->payload_len); +// printf("[profile order POP] %s\n", lua_tostring(L,-1)); free(q->payload); free(q); } @@ -139,6 +140,7 @@ int thread_profile(void *data) luaopen_core(L); luaopen_socket_core(L); luaopen_mime_core(L); + luaopen_zlib(L); luaL_openlib(L, "cprofile", threadlib, 0); lua_pop(L, 1); // Override "print" if requested -- GitLab