From 30d209a56caf37d38f956c46ca5b9d3504d18a46 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 2 Dec 2024 23:06:43 -0300 Subject: [PATCH 01/12] fix: console error related to wrong "monster" to "self" (#3166) --- data-otservbr-global/scripts/lib/monster_functions.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data-otservbr-global/scripts/lib/monster_functions.lua b/data-otservbr-global/scripts/lib/monster_functions.lua index cdb56c5e177..0b362de27a9 100644 --- a/data-otservbr-global/scripts/lib/monster_functions.lua +++ b/data-otservbr-global/scripts/lib/monster_functions.lua @@ -1,7 +1,7 @@ function Monster:handleCobraOnSpawn() if Game.getStorageValue(Global.Storage.CobraFlask) >= os.time() then - monster:setHealth(monster:getMaxHealth() * 0.75) - monster:getPosition():sendMagicEffect(CONST_ME_GREEN_RINGS) + self:setHealth(self:getMaxHealth() * 0.75) + self:getPosition():sendMagicEffect(CONST_ME_GREEN_RINGS) else Game.setStorageValue(Global.Storage.CobraFlask, -1) end From 42bd15bac180466751ff5dcf9cd8e66edc60a1aa Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 3 Dec 2024 00:46:02 -0300 Subject: [PATCH 02/12] fix: docker gha build workflow (#3167) --- .github/workflows/build-docker.yml | 16 ---------------- docker/Dockerfile.arm | 4 ++-- docker/Dockerfile.x86 | 4 ++-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index d99ba82878c..7dd7b657708 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -47,14 +47,6 @@ jobs: with: install: true - - name: Cache Docker layers - uses: actions/cache@main - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-x86-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-x86- - - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: @@ -109,14 +101,6 @@ jobs: with: install: true - - name: Cache Docker layers - uses: actions/cache@main - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-arm-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-arm- - - name: Build uses: docker/build-push-action@v3.2.0 with: diff --git a/docker/Dockerfile.arm b/docker/Dockerfile.arm index ef159ba142f..2fca1b525a0 100644 --- a/docker/Dockerfile.arm +++ b/docker/Dockerfile.arm @@ -1,5 +1,5 @@ # Stage 1: Download all dependencies -FROM ubuntu:23.04 AS dependencies +FROM ubuntu:24.04 AS dependencies RUN apt-get update && apt-get install -y --no-install-recommends cmake git \ unzip build-essential ca-certificates curl zip unzip tar \ @@ -30,7 +30,7 @@ WORKDIR /srv RUN export VCPKG_ROOT=/opt/vcpkg/ && VCPKG_FORCE_SYSTEM_BINARIES=1 cmake --preset linux-release && cmake --build --preset linux-release # Stage 3: load data and execute -FROM ubuntu:23.04 +FROM ubuntu:24.04 VOLUME [ "/data" ] diff --git a/docker/Dockerfile.x86 b/docker/Dockerfile.x86 index 3b035ecfd37..9e75c1078dd 100644 --- a/docker/Dockerfile.x86 +++ b/docker/Dockerfile.x86 @@ -1,5 +1,5 @@ # Stage 1: Download all dependencies -FROM ubuntu:23.04 AS dependencies +FROM ubuntu:24.04 AS dependencies RUN apt-get update && apt-get install -y --no-install-recommends cmake git \ unzip build-essential ca-certificates curl zip unzip tar \ @@ -30,7 +30,7 @@ WORKDIR /srv RUN export VCPKG_ROOT=/opt/vcpkg/ && cmake --preset linux-release && cmake --build --preset linux-release # Stage 3: load data and execute -FROM ubuntu:23.04 +FROM ubuntu:24.04 VOLUME [ "/data" ] COPY --from=build /srv/build/linux-release/bin/canary /bin/canary From b54a712ffa6c9ae2f0250b74d2f65ed51e53ba6b Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Thu, 5 Dec 2024 20:02:07 -0300 Subject: [PATCH 03/12] fix: check creatures crash (#3168) --- src/game/game.cpp | 30 ++++++++++++++++-------------- src/game/scheduling/task.hpp | 1 + 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index e69654a47ad..7a492348649 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -72,7 +72,7 @@ #include -std::vector> checkCreatureLists[EVENT_CREATURECOUNT]; +std::vector> checkCreatureLists[EVENT_CREATURECOUNT]; namespace InternalGame { void sendBlockEffect(BlockType_t blockType, CombatType_t combatType, const Position &targetPos, const std::shared_ptr &source) { @@ -6461,16 +6461,15 @@ void Game::addCreatureCheck(const std::shared_ptr &creature) { creature->creatureCheck.store(true); - if (creature->inCheckCreaturesVector.load()) { + if (creature->inCheckCreaturesVector.exchange(true)) { // already in a vector return; } - creature->inCheckCreaturesVector.store(true); - - creature->safeCall([this, creature] { - checkCreatureLists[uniform_random(0, EVENT_CREATURECOUNT - 1)].emplace_back(creature); - }); + g_dispatcher().addEvent([this, index = uniform_random(0, EVENT_CREATURECOUNT - 1), creature] { + checkCreatureLists[index].emplace_back(creature); + }, + "Game::addCreatureCheck"); } void Game::removeCreatureCheck(const std::shared_ptr &creature) { @@ -6484,15 +6483,18 @@ void Game::checkCreatures() { metrics::method_latency measure(__METRICS_METHOD_NAME__); static size_t index = 0; - std::erase_if(checkCreatureLists[index], [this](const std::shared_ptr creature) { - if (creature->creatureCheck && creature->isAlive()) { - creature->onThink(EVENT_CREATURE_THINK_INTERVAL); - creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); - creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); - return false; + std::erase_if(checkCreatureLists[index], [this](const std::weak_ptr &weak) { + if (const auto creature = weak.lock()) { + if (creature->creatureCheck && creature->isAlive()) { + creature->onThink(EVENT_CREATURE_THINK_INTERVAL); + creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); + creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + return false; + } + + creature->inCheckCreaturesVector = false; } - creature->inCheckCreaturesVector = false; return true; }); diff --git a/src/game/scheduling/task.hpp b/src/game/scheduling/task.hpp index c01dbe2f676..6cd9eef342d 100644 --- a/src/game/scheduling/task.hpp +++ b/src/game/scheduling/task.hpp @@ -75,6 +75,7 @@ class Task { "Game::createInfluencedMonsters", "Game::updateCreatureWalk", "Game::updateForgeableMonsters", + "Game::addCreatureCheck", "GlobalEvents::think", "LuaEnvironment::executeTimerEvent", "Modules::executeOnRecvbyte", From 9adf04031bd86eb72376636f261b2feed2abb491 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Mon, 9 Dec 2024 14:48:54 -0300 Subject: [PATCH 04/12] perf: multithreading in updateTargetList in all events (#3074) Previously, the multithreading of updateTargetList only worked for walk events, now all requests to these methods will be executed async. --- src/creatures/monsters/monster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 7276bea19d1..49472ec209f 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -602,7 +602,7 @@ bool Monster::removeTarget(const std::shared_ptr &creature) { } void Monster::updateTargetList() { - if (g_dispatcher().context().getGroup() == TaskGroup::Walk) { + if (!g_dispatcher().context().isAsync()) { setAsyncTaskFlag(UpdateTargetList, true); return; } From 3c98b4161cd9c25e57e61945fe6358d604242b27 Mon Sep 17 00:00:00 2001 From: Marco Date: Mon, 9 Dec 2024 15:14:36 -0300 Subject: [PATCH 05/12] refactor: split player death event handler into smaller functions (#3113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This refactors the PlayerDeath event script to improve code readability and maintainability. The original function has been split into smaller, more focused functions, each handling a specific part of the player death processing logic. This includes identifying the killer, logging deaths in the database, sending messages to the guild channel, and checking guild wars. With this refactoring, the code is more modular and easier to understand, making future modifications and maintenance more manageable. The PlayerDeath event logic remains unchanged, ensuring the system continues to function as expected. Changes: •. Split the onDeath function into smaller local functions. •. Improved SQL query formatting using string.format for clarity and to avoid repetitive code. •. Renamed local variables to follow the camelCase convention. --- .../scripts/creaturescripts/player_death.lua | 114 ----------- .../creaturescripts/others/player_death.lua | 146 -------------- .../creaturescripts_player_death.lua | 9 + ...ssing_login.lua => adventure_blessing.lua} | 0 data/scripts/creaturescripts/player/death.lua | 183 ++++++++++++++++++ 5 files changed, 192 insertions(+), 260 deletions(-) delete mode 100644 data-canary/scripts/creaturescripts/player_death.lua delete mode 100644 data-otservbr-global/scripts/creaturescripts/others/player_death.lua create mode 100644 data-otservbr-global/scripts/quests/svargrond_arena/creaturescripts_player_death.lua rename data/scripts/creaturescripts/player/{adventure_blessing_login.lua => adventure_blessing.lua} (100%) create mode 100644 data/scripts/creaturescripts/player/death.lua diff --git a/data-canary/scripts/creaturescripts/player_death.lua b/data-canary/scripts/creaturescripts/player_death.lua deleted file mode 100644 index c30e2e98d99..00000000000 --- a/data-canary/scripts/creaturescripts/player_death.lua +++ /dev/null @@ -1,114 +0,0 @@ -local playerDeath = CreatureEvent("PlayerDeath") - -local deathListEnabled = true -local maxDeathRecords = 5 - -function playerDeath.onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) - if not deathListEnabled then - return - end - - local byPlayer = 0 - local killerName - if killer then - if killer:isPlayer() then - byPlayer = 1 - else - local master = killer:getMaster() - if master and master ~= killer and master:isPlayer() then - killer = master - byPlayer = 1 - end - end - killerName = killer:getName() - else - killerName = "field item" - end - - local byPlayerMostDamage = 0 - local mostDamageKillerName - if mostDamageKiller then - if mostDamageKiller:isPlayer() then - byPlayerMostDamage = 1 - else - local master = mostDamageKiller:getMaster() - if master and master ~= mostDamageKiller and master:isPlayer() then - mostDamageKiller = master - byPlayerMostDamage = 1 - end - end - mostDamageName = mostDamageKiller:getName() - else - mostDamageName = "field item" - end - - player:takeScreenshot(byPlayer and SCREENSHOT_TYPE_DEATHPVP or SCREENSHOT_TYPE_DEATHPVE) - - if mostDamageKiller and mostDamageKiller:isPlayer() and killer ~= mostDamageKiller then - mostDamageKiller:takeScreenshot(SCREENSHOT_TYPE_PLAYERKILL) - end - - local playerGuid = player:getGuid() - db.query( - "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" - .. playerGuid - .. ", " - .. os.time() - .. ", " - .. player:getLevel() - .. ", " - .. db.escapeString(killerName) - .. ", " - .. byPlayer - .. ", " - .. db.escapeString(mostDamageName) - .. ", " - .. byPlayerMostDamage - .. ", " - .. (unjustified and 1 or 0) - .. ", " - .. (mostDamageUnjustified and 1 or 0) - .. ")" - ) - local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) - - local deathRecords = 0 - local tmpResultId = resultId - while tmpResultId ~= false do - tmpResultId = Result.next(resultId) - deathRecords = deathRecords + 1 - end - - if resultId ~= false then - Result.free(resultId) - end - - local limit = deathRecords - maxDeathRecords - if limit > 0 then - db.asyncQuery("DELETE FROM `player_deaths` WHERE `player_id` = " .. playerGuid .. " ORDER BY `time` LIMIT " .. limit) - end - - if byPlayer == 1 then - killer:takeScreenshot(SCREENSHOT_TYPE_PLAYERKILL) - local targetGuild = player:getGuild() - targetGuild = targetGuild and targetGuild:getId() or 0 - if targetGuild ~= 0 then - local killerGuild = killer:getGuild() - killerGuild = killerGuild and killerGuild:getId() or 0 - if killerGuild ~= 0 and targetGuild ~= killerGuild and isInWar(player:getId(), killer:getId()) then - local warId = false - resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. killerGuild .. " AND `guild2` = " .. targetGuild .. ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. killerGuild .. "))") - if resultId ~= false then - warId = Result.getNumber(resultId, "id") - Result.free(resultId) - end - - if warId ~= false then - db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. db.escapeString(killerName) .. ", " .. db.escapeString(player:getName()) .. ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. warId .. ")") - end - end - end - end -end - -playerDeath:register() diff --git a/data-otservbr-global/scripts/creaturescripts/others/player_death.lua b/data-otservbr-global/scripts/creaturescripts/others/player_death.lua deleted file mode 100644 index f3a84454cfd..00000000000 --- a/data-otservbr-global/scripts/creaturescripts/others/player_death.lua +++ /dev/null @@ -1,146 +0,0 @@ -local deathListEnabled = true - -local playerDeath = CreatureEvent("PlayerDeath") -function playerDeath.onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) - if player:getStorageValue(Storage.Quest.U8_0.BarbarianArena.PitDoor) > 0 then - player:setStorageValue(Storage.Quest.U8_0.BarbarianArena.PitDoor, 0) - end - - if not deathListEnabled then - return - end - - local byPlayer = 0 - local killerName - if killer ~= nil then - if killer:isPlayer() then - byPlayer = 1 - else - local master = killer:getMaster() - if master and master ~= killer and master:isPlayer() then - killer = master - byPlayer = 1 - end - end - killerName = killer:isMonster() and killer:getType():getNameDescription() or killer:getName() - else - killerName = "field item" - end - - local byPlayerMostDamage = 0 - local mostDamageKillerName - if mostDamageKiller ~= nil then - if mostDamageKiller:isPlayer() then - byPlayerMostDamage = 1 - else - local master = mostDamageKiller:getMaster() - if master and master ~= mostDamageKiller and master:isPlayer() then - mostDamageKiller = master - byPlayerMostDamage = 1 - end - end - mostDamageName = mostDamageKiller:isMonster() and mostDamageKiller:getType():getNameDescription() or mostDamageKiller:getName() - else - mostDamageName = "field item" - end - - player:takeScreenshot(byPlayer and SCREENSHOT_TYPE_DEATHPVP or SCREENSHOT_TYPE_DEATHPVE) - - if mostDamageKiller and mostDamageKiller:isPlayer() then - mostDamageKiller:takeScreenshot(SCREENSHOT_TYPE_PLAYERKILL) - end - - local playerGuid = player:getGuid() - db.query( - "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" - .. playerGuid - .. ", " - .. os.time() - .. ", " - .. player:getLevel() - .. ", " - .. db.escapeString(killerName) - .. ", " - .. byPlayer - .. ", " - .. db.escapeString(mostDamageName) - .. ", " - .. byPlayerMostDamage - .. ", " - .. (unjustified and 1 or 0) - .. ", " - .. (mostDamageUnjustified and 1 or 0) - .. ")" - ) - local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) - -- Start Webhook Player Death - Webhook.sendMessage(":skull_crossbones: " .. player:getMarkdownLink() .. " has died. Killed at level _" .. player:getLevel() .. "_ by **" .. killerName .. "**.", announcementChannels["player-kills"]) - -- End Webhook Player Death - - local deathRecords = 0 - local tmpResultId = resultId - while tmpResultId ~= false do - tmpResultId = Result.next(resultId) - deathRecords = deathRecords + 1 - end - - if resultId ~= false then - Result.free(resultId) - end - - if byPlayer == 1 then - killer:takeScreenshot(SCREENSHOT_TYPE_PLAYERKILL) - local targetGuild = player:getGuild() - local targetGuildId = targetGuild and targetGuild:getId() or 0 - if targetGuildId ~= 0 then - local killerGuild = killer:getGuild() - local killerGuildId = killerGuild and killerGuild:getId() or 0 - if killerGuildId ~= 0 and targetGuildId ~= killerGuildId and isInWar(player:getId(), killer:getId()) then - local warId = false - resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND \z - ((`guild1` = " .. killerGuildId .. " AND `guild2` = " .. targetGuildId .. ") OR \z - (`guild1` = " .. targetGuildId .. " AND `guild2` = " .. killerGuildId .. "))") - if resultId then - warId = Result.getNumber(resultId, "id") - Result.free(resultId) - end - - if warId then - local playerName = player:getName() - db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) \z - VALUES (" .. db.escapeString(killerName) .. ", " .. db.escapeString(playerName) .. ", " .. killerGuildId .. ", \z - " .. targetGuildId .. ", " .. os.time() .. ", " .. warId .. ")") - - resultId = db.storeQuery("SELECT `guild_wars`.`id`, `guild_wars`.`frags_limit`, (SELECT COUNT(1) FROM `guildwar_kills` \z - WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild1`) AS guild1_kills, \z - (SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild2`) AS guild2_kills \z - FROM `guild_wars` WHERE (`guild1` = " .. killerGuildId .. " OR `guild2` = " .. killerGuildId .. ") AND `status` = 1 AND `id` = " .. warId) - - if resultId then - local guild1_kills = Result.getNumber(resultId, "guild1_kills") - local guild2_kills = Result.getNumber(resultId, "guild2_kills") - local frags_limit = Result.getNumber(resultId, "frags_limit") - Result.free(resultId) - - local members = killerGuild:getMembersOnline() - for i = 1, #members do - members[i]:sendChannelMessage(members[i], string.format("%s was killed by %s. The new score is: %s %d:%d %s (frags limit: %d)", playerName, killerName, targetGuild:getName(), guild1_kills, guild2_kills, killerGuild:getName(), frags_limit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) - end - - local enemyMembers = targetGuild:getMembersOnline() - for i = 1, #enemyMembers do - enemyMembers[i]:sendChannelMessage(enemyMembers[i], string.format("%s was killed by %s. The new score is: %s %d:%d %s (frags limit: %d)", playerName, killerName, targetGuild:getName(), guild1_kills, guild2_kills, killerGuild:getName(), frags_limit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) - end - - if guild1_kills >= frags_limit or guild2_kills >= frags_limit then - db.query("UPDATE `guild_wars` SET `status` = 4, `ended` = " .. os.time() .. " WHERE `status` = 1 AND `id` = " .. warId) - Game.broadcastMessage(string.format("%s has just won the war against %s.", killerGuild:getName(), targetGuild:getName())) - end - end - end - end - end - end -end - -playerDeath:register() diff --git a/data-otservbr-global/scripts/quests/svargrond_arena/creaturescripts_player_death.lua b/data-otservbr-global/scripts/quests/svargrond_arena/creaturescripts_player_death.lua new file mode 100644 index 00000000000..e1ba265090a --- /dev/null +++ b/data-otservbr-global/scripts/quests/svargrond_arena/creaturescripts_player_death.lua @@ -0,0 +1,9 @@ +local svargrondArenaPlayerDeath = CreatureEvent("SvargrondArenaPlayerDeath") + +function svargrondArenaPlayerDeath.onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) + if player:getStorageValue(Storage.Quest.U8_0.BarbarianArena.PitDoor) > 0 then + player:setStorageValue(Storage.Quest.U8_0.BarbarianArena.PitDoor, 0) + end +end + +svargrondArenaPlayerDeath:register() diff --git a/data/scripts/creaturescripts/player/adventure_blessing_login.lua b/data/scripts/creaturescripts/player/adventure_blessing.lua similarity index 100% rename from data/scripts/creaturescripts/player/adventure_blessing_login.lua rename to data/scripts/creaturescripts/player/adventure_blessing.lua diff --git a/data/scripts/creaturescripts/player/death.lua b/data/scripts/creaturescripts/player/death.lua new file mode 100644 index 00000000000..42d143781d4 --- /dev/null +++ b/data/scripts/creaturescripts/player/death.lua @@ -0,0 +1,183 @@ +local deathListEnabled = true + +local function getKillerInfo(killer) + local byPlayer = 0 + local killerName + + if killer then + if killer:isPlayer() then + byPlayer = 1 + else + local master = killer:getMaster() + if master and master ~= killer and master:isPlayer() then + killer = master + byPlayer = 1 + end + end + + killerName = killer:isMonster() and killer:getType():getNameDescription() or killer:getName() + else + killerName = "field item" + end + + return killerName, byPlayer +end + +local function getMostDamageInfo(mostDamageKiller) + local byPlayerMostDamage = 0 + local mostDamageKillerName + + if mostDamageKiller then + if mostDamageKiller:isPlayer() then + byPlayerMostDamage = 1 + else + local master = mostDamageKiller:getMaster() + if master and master ~= mostDamageKiller and master:isPlayer() then + mostDamageKiller = master + byPlayerMostDamage = 1 + end + end + + mostDamageKillerName = mostDamageKiller:isMonster() and mostDamageKiller:getType():getNameDescription() or mostDamageKiller:getName() + else + mostDamageKillerName = "field item" + end + + return mostDamageKillerName, byPlayerMostDamage +end + +local function saveDeathRecord(playerGuid, player, killerName, byPlayer, mostDamageName, byPlayerMostDamage, unjustified, mostDamageUnjustified) + local query = string.format( + "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) " .. "VALUES (%d, %d, %d, '%s', %d, '%s', %d, %d, %d)", + playerGuid, + os.time(), + player:getLevel(), + db.escapeString(killerName), + byPlayer, + db.escapeString(mostDamageName), + byPlayerMostDamage, + unjustified and 1 or 0, + mostDamageUnjustified and 1 or 0 + ) + db.query(query) +end + +local function handleGuildWar(player, killer, mostDamageKiller, killerName, mostDamageName) + local deathRecords = getDeathRecords(player:getGuid()) + + if deathRecords > 0 then + local targetGuildId = player:getGuild() and player:getGuild():getId() or 0 + local killerGuildId = killer:getGuild() and killer:getGuild():getId() or 0 + + if targetGuildId ~= 0 and killerGuildId ~= 0 and targetGuildId ~= killerGuildId then + local warId = checkForGuildWar(targetGuildId, killerGuildId) + if warId then + recordGuildWarKill(killer, player, killerGuildId, targetGuildId, warId) + checkAndUpdateGuildWarScore(warId, targetGuildId, killerGuildId, player:getName(), killerName, mostDamageName) + end + end + end +end + +local function getDeathRecords(playerGuid) + local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) + local deathRecords = 0 + while resultId do + resultId = Result.next(resultId) + deathRecords = deathRecords + 1 + end + + if resultId then + Result.free(resultId) + end + + return deathRecords +end + +local function checkForGuildWar(targetGuildId, killerGuildId) + local resultId = db.storeQuery(string.format("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = %d AND `guild2` = %d) OR (`guild1` = %d AND `guild2` = %d))", killerGuildId, targetGuildId, targetGuildId, killerGuildId)) + + local warId = false + if resultId then + warId = Result.getNumber(resultId, "id") + Result.free(resultId) + end + + return warId +end + +local function recordGuildWarKill(killer, player, killerGuildId, targetGuildId, warId) + local playerName = player:getName() + db.asyncQuery(string.format("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES ('%s', '%s', %d, %d, %d, %d)", db.escapeString(killer:getName()), db.escapeString(playerName), killerGuildId, targetGuildId, os.time(), warId)) +end + +local function checkAndUpdateGuildWarScore(warId, targetGuildId, killerGuildId, playerName, killerName, mostDamageName) + local resultId = db.storeQuery( + string.format( + "SELECT `guild_wars`.`id`, `guild_wars`.`frags_limit`, " + .. "(SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild1`) AS guild1_kills, " + .. "(SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild2`) AS guild2_kills " + .. "FROM `guild_wars` WHERE (`guild1` = %d OR `guild2` = %d) AND `status` = 1 AND `id` = %d", + killerGuildId, + targetGuildId, + warId + ) + ) + + if resultId then + local guild1Kills = Result.getNumber(resultId, "guild1_kills") + local guild2Kills = Result.getNumber(resultId, "guild2_kills") + local fragsLimit = Result.getNumber(resultId, "frags_limit") + Result.free(resultId) + + local killerGuild = killer:getGuild() + local targetGuild = player:getGuild() + + updateGuildWarScore(killerGuild, targetGuild, playerName, killerName, guild1Kills, guild2Kills, fragsLimit) + endGuildWarIfLimitReached(guild1Kills, guild2Kills, fragsLimit, warId, killerGuild, targetGuild) + end +end + +local function updateGuildWarScore(killerGuild, targetGuild, playerName, killerName, guild1Kills, guild2Kills, fragsLimit) + local members = killerGuild:getMembersOnline() + for _, member in ipairs(members) do + member:sendChannelMessage(member, string.format("%s was killed by %s. The new score is: %s %d:%d %s (frags limit: %d)", playerName, killerName, targetGuild:getName(), guild1Kills, guild2Kills, killerGuild:getName(), fragsLimit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) + end + + local enemyMembers = targetGuild:getMembersOnline() + for _, enemy in ipairs(enemyMembers) do + enemy:sendChannelMessage(enemy, string.format("%s was killed by %s. The new score is: %s %d:%d %s (frags limit: %d)", playerName, killerName, targetGuild:getName(), guild1Kills, guild2Kills, killerGuild:getName(), fragsLimit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) + end +end + +local function endGuildWarIfLimitReached(guild1Kills, guild2Kills, fragsLimit, warId, killerGuild, targetGuild) + if guild1Kills >= fragsLimit or guild2Kills >= fragsLimit then + db.query(string.format("UPDATE `guild_wars` SET `status` = 4, `ended` = %d WHERE `status` = 1 AND `id` = %d", os.time(), warId)) + Game.broadcastMessage(string.format("%s has just won the war against %s.", killerGuild:getName(), targetGuild:getName())) + end +end + +local playerDeath = CreatureEvent("PlayerDeath") + +function playerDeath.onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) + if not deathListEnabled then + return + end + + local killerName, byPlayer = getKillerInfo(killer) + local mostDamageName, byPlayerMostDamage = getMostDamageInfo(mostDamageKiller) + + player:takeScreenshot(byPlayer and SCREENSHOT_TYPE_DEATHPVP or SCREENSHOT_TYPE_DEATHPVE) + + if mostDamageKiller and mostDamageKiller:isPlayer() then + mostDamageKiller:takeScreenshot(SCREENSHOT_TYPE_PLAYERKILL) + end + + local playerGuid = player:getGuid() + saveDeathRecord(playerGuid, player, killerName, byPlayer, mostDamageName, byPlayerMostDamage, unjustified, mostDamageUnjustified) + + Webhook.sendMessage(":skull_crossbones: " .. player:getMarkdownLink() .. " has died. Killed at level _" .. player:getLevel() .. "_ by **" .. killerName .. "**.", announcementChannels["player-kills"]) + handleGuildWar(player, killer, mostDamageKiller, killerName, mostDamageName) +end + +playerDeath:register() From 74b8ed0a5ffc2e9447aaf7d9f4c29ddf7434d0d9 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Mon, 9 Dec 2024 15:52:42 -0300 Subject: [PATCH 06/12] perf: onRemoveCreature->onCreatureLeave async (#3152) the call to onCreatureLeave in Monster::onRemoveCreature will be called asynchronously. --- src/creatures/monsters/monster.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 49472ec209f..72816fd1a43 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -354,7 +354,9 @@ void Monster::onRemoveCreature(const std::shared_ptr &creature, bool i setIdle(true); } else { - onCreatureLeave(creature); + addAsyncTask([this, creature] { + onCreatureLeave(creature); + }); } } From fbd622861dc56572007b79acb138836cd03a7dde Mon Sep 17 00:00:00 2001 From: Marco Date: Mon, 9 Dec 2024 15:53:51 -0300 Subject: [PATCH 07/12] fix: resolve nil value errors in handleGuildWar function (#3172) I made corrections to the `death.lua` script to resolve nil value errors in the `handleGuildWar` function. I added checks to ensure that both the player and the killer are valid and that both belong to a guild before proceeding with the guild war logic. This helps prevent failures when one of the objects is undefined. Fixes from: https://github.com/opentibiabr/canary/commit/3c98b4161cd9c25e57e61945fe6358d604242b27 --- data/scripts/creaturescripts/player/death.lua | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/data/scripts/creaturescripts/player/death.lua b/data/scripts/creaturescripts/player/death.lua index 42d143781d4..4c0c9a8b2ca 100644 --- a/data/scripts/creaturescripts/player/death.lua +++ b/data/scripts/creaturescripts/player/death.lua @@ -48,7 +48,7 @@ end local function saveDeathRecord(playerGuid, player, killerName, byPlayer, mostDamageName, byPlayerMostDamage, unjustified, mostDamageUnjustified) local query = string.format( - "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) " .. "VALUES (%d, %d, %d, '%s', %d, '%s', %d, %d, %d)", + "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) " .. "VALUES (%d, %d, %d, %s, %d, %s, %d, %d, %d)", playerGuid, os.time(), player:getLevel(), @@ -62,23 +62,6 @@ local function saveDeathRecord(playerGuid, player, killerName, byPlayer, mostDam db.query(query) end -local function handleGuildWar(player, killer, mostDamageKiller, killerName, mostDamageName) - local deathRecords = getDeathRecords(player:getGuid()) - - if deathRecords > 0 then - local targetGuildId = player:getGuild() and player:getGuild():getId() or 0 - local killerGuildId = killer:getGuild() and killer:getGuild():getId() or 0 - - if targetGuildId ~= 0 and killerGuildId ~= 0 and targetGuildId ~= killerGuildId then - local warId = checkForGuildWar(targetGuildId, killerGuildId) - if warId then - recordGuildWarKill(killer, player, killerGuildId, targetGuildId, warId) - checkAndUpdateGuildWarScore(warId, targetGuildId, killerGuildId, player:getName(), killerName, mostDamageName) - end - end - end -end - local function getDeathRecords(playerGuid) local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) local deathRecords = 0 @@ -94,6 +77,27 @@ local function getDeathRecords(playerGuid) return deathRecords end +local function handleGuildWar(player, killer, mostDamageKiller, killerName, mostDamageName) + if not player or not killer or not killer:isPlayer() or not player:getGuild() or not killer:getGuild() then + return + end + + local playerGuildId = player:getGuild():getId() + local killerGuildId = killer:getGuild():getId() + + if playerGuildId == killerGuildId then + return + end + + if getDeathRecords(player:getGuid()) > 0 then + local warId = checkForGuildWar(playerGuildId, killerGuildId) + if warId then + recordGuildWarKill(killer, player, killerGuildId, playerGuildId, warId) + checkAndUpdateGuildWarScore(warId, playerGuildId, killerGuildId, player:getName(), killerName, mostDamageName) + end + end +end + local function checkForGuildWar(targetGuildId, killerGuildId) local resultId = db.storeQuery(string.format("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = %d AND `guild2` = %d) OR (`guild1` = %d AND `guild2` = %d))", killerGuildId, targetGuildId, targetGuildId, killerGuildId)) From 8891f3afc1fffb86879dc5060aefe101c3b16b37 Mon Sep 17 00:00:00 2001 From: kokekanon <114332266+kokekanon@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:27:17 -0300 Subject: [PATCH 08/12] fix: packet interpretation parseSetOutfit for otcv8/old protocol (#3162) Resolves #3155 --- src/server/network/protocol/protocolgame.cpp | 30 +++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 2f7ff9e2479..9c09f5ead38 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -1689,7 +1689,7 @@ void ProtocolGame::parseSetOutfit(NetworkMessage &msg) { g_logger().debug("Bool isMounted: {}", isMounted); } - uint8_t isMountRandomized = msg.getByte(); + uint8_t isMountRandomized = !oldProtocol ? msg.getByte() : 0; g_game().playerChangeOutfit(player->getID(), newOutfit, isMountRandomized); } else if (outfitType == 1) { // This value probably has something to do with try outfit variable inside outfit window dialog @@ -3247,12 +3247,6 @@ void ProtocolGame::sendCreatureOutfit(const std::shared_ptr &creature, msg.add(creature->getID()); AddOutfit(msg, newOutfit); - if (!oldProtocol && newOutfit.lookMount != 0) { - msg.addByte(newOutfit.lookMountHead); - msg.addByte(newOutfit.lookMountBody); - msg.addByte(newOutfit.lookMountLegs); - msg.addByte(newOutfit.lookMountFeet); - } writeToOutputBuffer(msg); } @@ -7184,10 +7178,12 @@ void ProtocolGame::sendOutfitWindow() { return; } - msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountHead); - msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountBody); - msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountLegs); - msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountFeet); + if (currentOutfit.lookMount == 0) { + msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountHead); + msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountBody); + msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountLegs); + msg.addByte(isSupportOutfit ? 0 : currentOutfit.lookMountFeet); + } msg.add(currentOutfit.lookFamiliarsType); auto startOutfits = msg.getBufferPosition(); @@ -7750,12 +7746,6 @@ void ProtocolGame::AddCreature(NetworkMessage &msg, const std::shared_ptrisInGhostMode() && !creature->isInvisible()) { const Outfit_t &outfit = creature->getCurrentOutfit(); AddOutfit(msg, outfit); - if (!oldProtocol && outfit.lookMount != 0) { - msg.addByte(outfit.lookMountHead); - msg.addByte(outfit.lookMountBody); - msg.addByte(outfit.lookMountLegs); - msg.addByte(outfit.lookMountFeet); - } } else { static Outfit_t outfit; AddOutfit(msg, outfit); @@ -7945,6 +7935,12 @@ void ProtocolGame::AddOutfit(NetworkMessage &msg, const Outfit_t &outfit, bool a if (addMount) { msg.add(outfit.lookMount); + if (!oldProtocol && outfit.lookMount != 0) { + msg.addByte(outfit.lookMountHead); + msg.addByte(outfit.lookMountBody); + msg.addByte(outfit.lookMountLegs); + msg.addByte(outfit.lookMountFeet); + } } } From a81297ae5e9e487f1a437612075de23d47ad2820 Mon Sep 17 00:00:00 2001 From: odisk777 <65802862+odisk777@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:28:23 -0500 Subject: [PATCH 09/12] Compatibility with Basic CPUs (#3146) Some servers or vps now come with modifications for basic cpu such as: QEMU Virtual CPU version 2.5+, this function makes it possible to run canary --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d143fec490..d7ba93bafb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,11 @@ endif() # ***************************************************************************** list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) +# Configure build options for compatibility with commodity CPUs +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64 -mtune=generic -mno-avx -mno-sse4") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -mtune=generic -mno-avx -mno-sse4") + + # ***************************************************************************** # Include cmake tools # ***************************************************************************** From dec87cb0c6077032f9c95615e8381c6e61e73545 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Mon, 9 Dec 2024 16:29:12 -0300 Subject: [PATCH 10/12] Improve: creature actions (#3084) Some creature actions, such as checkCreatureAttack, checkCreatureWalk, updateCreatureWalk, were processed based on the creature ID, as canary works with smart pointer, we no longer have problems working with the object reference, before it used the ID to maintain security, because at the time of execution, the object could no longer exist. --- src/creatures/creature.cpp | 50 ++++++++++++++++++++++++------ src/creatures/creature.hpp | 6 ++++ src/creatures/monsters/monster.cpp | 2 +- src/creatures/players/player.cpp | 11 ++++--- src/game/game.cpp | 34 ++------------------ src/game/game.hpp | 4 --- src/game/scheduling/task.hpp | 3 +- 7 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 32f3c598b81..586f5b8c1b2 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -140,6 +140,20 @@ void Creature::onThink(uint32_t interval) { onThink(); } +void Creature::checkCreatureAttack(bool now) { + if (now) { + if (isAlive()) { + onAttacking(0); + } + return; + } + + g_dispatcher().addEvent([self = std::weak_ptr(getCreature())] { + if (const auto &creature = self.lock()) { + creature->checkCreatureAttack(true); + } }, "Creature::checkCreatureAttack"); +} + void Creature::onAttacking(uint32_t interval) { const auto &attackedCreature = getAttackedCreature(); if (!attackedCreature) { @@ -162,7 +176,7 @@ void Creature::onIdleStatus() { } void Creature::onCreatureWalk() { - if (checkingWalkCreature) { + if (checkingWalkCreature || isRemoved() || isDead()) { return; } @@ -170,7 +184,11 @@ void Creature::onCreatureWalk() { metrics::method_latency measure(__METRICS_METHOD_NAME__); - g_dispatcher().addWalkEvent([self = getCreature(), this] { + g_dispatcher().addWalkEvent([self = std::weak_ptr(getCreature()), this] { + if (!self.lock()) { + return; + } + checkingWalkCreature = false; if (isRemoved()) { return; @@ -269,12 +287,16 @@ void Creature::addEventWalk(bool firstStep) { safeCall([this, ticks]() { // Take first step right away, but still queue the next if (ticks == 1) { - g_game().checkCreatureWalk(getID()); + onCreatureWalk(); } eventWalk = g_dispatcher().scheduleEvent( - static_cast(ticks), - [creatureId = getID()] { g_game().checkCreatureWalk(creatureId); }, "Game::checkCreatureWalk" + static_cast(ticks), [self = std::weak_ptr(getCreature())] { + if (const auto &creature = self.lock()) { + creature->onCreatureWalk(); + } + }, + "Game::checkCreatureWalk" ); }); } @@ -421,7 +443,7 @@ void Creature::onCreatureMove(const std::shared_ptr &creature, const s if (followCreature && (creature.get() == this || creature == followCreature)) { if (hasFollowPath) { isUpdatingPath = true; - g_game().updateCreatureWalk(getID()); // internally uses addEventWalk. + updateCreatureWalk(); } if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { @@ -436,7 +458,7 @@ void Creature::onCreatureMove(const std::shared_ptr &creature, const s } else { if (hasExtraSwing()) { // our target is moving lets see if we can get in hit - g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, "Game::checkCreatureAttack"); + checkCreatureAttack(); } if (newTile->getZoneType() != oldTile->getZoneType()) { @@ -701,7 +723,13 @@ void Creature::changeHealth(int32_t healthChange, bool sendHealthChange /* = tru g_game().addCreatureHealth(static_self_cast()); } if (health <= 0) { - g_dispatcher().addEvent([creatureId = getID()] { g_game().executeDeath(creatureId); }, "Game::executeDeath"); + g_dispatcher().addEvent([self = std::weak_ptr(getCreature())] { + if (const auto &creature = self.lock()) { + if (!creature->isRemoved()) { + g_game().afterCreatureZoneChange(creature, creature->getZones(), {}); + creature->onDeath(); + } + } }, "Game::executeDeath"); } } @@ -874,6 +902,10 @@ void Creature::getPathSearchParams(const std::shared_ptr &, FindPathPa } void Creature::goToFollowCreature_async(std::function &&onComplete) { + if (isDead()) { + return; + } + if (!hasAsyncTaskFlag(Pathfinder) && onComplete) { g_dispatcher().addEvent(std::move(onComplete), "goToFollowCreature_async"); } @@ -1781,7 +1813,7 @@ void Creature::sendAsyncTasks() { setAsyncTaskFlag(AsyncTaskRunning, true); g_dispatcher().asyncEvent([self = std::weak_ptr(getCreature())] { if (const auto &creature = self.lock()) { - if (!creature->isRemoved()) { + if (!creature->isRemoved() && creature->isAlive()) { for (const auto &task : creature->asyncTasks) { task(); } diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index fcea0a6b0f2..18a4bd817ab 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -312,6 +312,9 @@ class Creature : virtual public Thing, public SharedObject { void addEventWalk(bool firstStep = false); void stopEventWalk(); + void updateCreatureWalk() { + goToFollowCreature_async(); + } void goToFollowCreature_async(std::function &&onComplete = nullptr); virtual void goToFollowCreature(); @@ -482,6 +485,9 @@ class Creature : virtual public Thing, public SharedObject { void setCreatureLight(LightInfo lightInfo); virtual void onThink(uint32_t interval); + + void checkCreatureAttack(bool now = false); + void onAttacking(uint32_t interval); virtual void onCreatureWalk(); virtual bool getNextStep(Direction &dir, uint32_t &flags); diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 72816fd1a43..4189ffc6c29 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -954,7 +954,7 @@ bool Monster::selectTarget(const std::shared_ptr &creature) { if (isHostile() || isSummon()) { if (setAttackedCreature(creature)) { - g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, __FUNCTION__); + checkCreatureAttack(); } } return setFollowCreature(creature); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 4335db2bd53..88223534497 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -3430,9 +3430,10 @@ void Player::doAttacking(uint32_t interval) { } const auto &task = createPlayerTask( - std::max(SCHEDULER_MINTICKS, delay), - [playerId = getID()] { g_game().checkCreatureAttack(playerId); }, - __FUNCTION__ + std::max(SCHEDULER_MINTICKS, delay), [self = std::weak_ptr(getCreature())] { + if (const auto &creature = self.lock()) { + creature->checkCreatureAttack(true); + } }, __FUNCTION__ ); if (!classicSpeed) { @@ -5296,7 +5297,7 @@ bool Player::setAttackedCreature(const std::shared_ptr &creature) { } if (creature) { - g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, __FUNCTION__); + checkCreatureAttack(); } return true; } @@ -9835,7 +9836,7 @@ void Player::onCreatureMove(const std::shared_ptr &creature, const std const auto &followCreature = getFollowCreature(); if (hasFollowPath && (creature == followCreature || (creature.get() == this && followCreature))) { isUpdatingPath = false; - g_game().updateCreatureWalk(getID()); // internally uses addEventWalk. + updateCreatureWalk(); } if (creature != getPlayer()) { diff --git a/src/game/game.cpp b/src/game/game.cpp index 7a492348649..4dc0b9e3e20 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1255,15 +1255,6 @@ bool Game::removeCreature(const std::shared_ptr &creature, bool isLogo return true; } -void Game::executeDeath(uint32_t creatureId) { - metrics::method_latency measure(__METRICS_METHOD_NAME__); - std::shared_ptr creature = getCreatureByID(creatureId); - if (creature && !creature->isRemoved()) { - afterCreatureZoneChange(creature, creature->getZones(), {}); - creature->onDeath(); - } -} - void Game::playerTeleport(uint32_t playerId, const Position &newPosition) { metrics::method_latency measure(__METRICS_METHOD_NAME__); const auto &player = getPlayerByID(playerId); @@ -5912,7 +5903,7 @@ void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) { } player->setAttackedCreature(attackCreature); - updateCreatureWalk(player->getID()); // internally uses addEventWalk. + player->updateCreatureWalk(); } void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { @@ -5922,7 +5913,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { } player->setAttackedCreature(nullptr); - updateCreatureWalk(player->getID()); // internally uses addEventWalk. + player->updateCreatureWalk(); player->setFollowCreature(getCreatureByID(creatureId)); } @@ -6433,27 +6424,6 @@ bool Game::internalCreatureSay(const std::shared_ptr &creature, SpeakC return true; } -void Game::checkCreatureWalk(uint32_t creatureId) { - const auto &creature = getCreatureByID(creatureId); - if (creature && creature->getHealth() > 0) { - creature->onCreatureWalk(); - } -} - -void Game::updateCreatureWalk(uint32_t creatureId) { - const auto &creature = getCreatureByID(creatureId); - if (creature && creature->getHealth() > 0) { - creature->goToFollowCreature_async(); - } -} - -void Game::checkCreatureAttack(uint32_t creatureId) { - const auto &creature = getCreatureByID(creatureId); - if (creature && creature->getHealth() > 0) { - creature->onAttacking(0); - } -} - void Game::addCreatureCheck(const std::shared_ptr &creature) { if (creature->isRemoved()) { return; diff --git a/src/game/game.hpp b/src/game/game.hpp index b1afd810100..ff2e127fd25 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -173,7 +173,6 @@ class Game { bool placeCreature(const std::shared_ptr &creature, const Position &pos, bool extendedPos = false, bool force = false); bool removeCreature(const std::shared_ptr &creature, bool isLogout = true); - void executeDeath(uint32_t creatureId); void addCreatureCheck(const std::shared_ptr &creature); static void removeCreatureCheck(const std::shared_ptr &creature); @@ -437,9 +436,6 @@ class Game { void setGameState(GameState_t newState); // Events - void checkCreatureWalk(uint32_t creatureId); - void updateCreatureWalk(uint32_t creatureId); - void checkCreatureAttack(uint32_t creatureId); void checkCreatures(); void checkLight(); diff --git a/src/game/scheduling/task.hpp b/src/game/scheduling/task.hpp index 6cd9eef342d..c4de64059bb 100644 --- a/src/game/scheduling/task.hpp +++ b/src/game/scheduling/task.hpp @@ -66,14 +66,13 @@ class Task { const static std::unordered_set tasksContext = { "Decay::checkDecay", "Dispatcher::asyncEvent", - "Game::checkCreatureAttack", + "Creature::checkCreatureAttack", "Game::checkCreatureWalk", "Game::checkCreatures", "Game::checkImbuements", "Game::checkLight", "Game::createFiendishMonsters", "Game::createInfluencedMonsters", - "Game::updateCreatureWalk", "Game::updateForgeableMonsters", "Game::addCreatureCheck", "GlobalEvents::think", From e3259655c855c58279cdef2dc50f95bdf9375fc5 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Mon, 9 Dec 2024 16:30:04 -0300 Subject: [PATCH 11/12] perf: onThink multithreading (#3075) --- src/creatures/creature.hpp | 3 ++- src/creatures/monsters/monster.cpp | 40 ++++++++++++++---------------- src/creatures/monsters/monster.hpp | 2 ++ src/game/game.cpp | 14 +++++++++-- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 18a4bd817ab..35f39292609 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -700,7 +700,8 @@ class Creature : virtual public Thing, public SharedObject { AsyncTaskRunning = 1 << 0, UpdateTargetList = 1 << 1, UpdateIdleStatus = 1 << 2, - Pathfinder = 1 << 3 + Pathfinder = 1 << 3, + OnThink = 1 << 4, }; virtual bool isDead() const { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 4189ffc6c29..d30c2a33471 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -423,27 +423,13 @@ void Monster::onCreatureMove(const std::shared_ptr &creature, const st if (const auto &nextTile = g_game().map.getTile(checkPosition)) { const auto &topCreature = nextTile->getTopCreature(); if (followCreature != topCreature && isOpponent(topCreature)) { - g_dispatcher().addEvent([selfWeak = std::weak_ptr(getMonster()), topCreatureWeak = std::weak_ptr(topCreature)] { - const auto &self = selfWeak.lock(); - const auto &topCreature = topCreatureWeak.lock(); - if (self && topCreature) { - self->selectTarget(topCreature); - } - }, - "Monster::onCreatureMove"); + selectTarget(topCreature); } } } } else if (isOpponent(creature)) { // we have no target lets try pick this one - g_dispatcher().addEvent([selfWeak = std::weak_ptr(getMonster()), creatureWeak = std::weak_ptr(creature)] { - const auto &self = selfWeak.lock(); - const auto &creaturePtr = creatureWeak.lock(); - if (self && creaturePtr) { - self->selectTarget(creaturePtr); - } - }, - "Monster::onCreatureMove"); + selectTarget(creature); } } }; @@ -978,7 +964,7 @@ void Monster::setIdle(bool idle) { } void Monster::updateIdleStatus() { - if (g_dispatcher().context().getGroup() == TaskGroup::Walk) { + if (!g_dispatcher().context().isAsync()) { setAsyncTaskFlag(UpdateIdleStatus, true); return; } @@ -1073,8 +1059,11 @@ void Monster::onThink(uint32_t interval) { } updateIdleStatus(); + setAsyncTaskFlag(OnThink, true); +} - if (isIdle) { +void Monster::onThink_async() { + if (isIdle) { // updateIdleStatus(); is executed before this method return; } @@ -1109,10 +1098,13 @@ void Monster::onThink(uint32_t interval) { } } - onThinkTarget(interval); - onThinkYell(interval); - onThinkDefense(interval); - onThinkSound(interval); + onThinkTarget(EVENT_CREATURE_THINK_INTERVAL); + + safeCall([this] { + onThinkYell(EVENT_CREATURE_THINK_INTERVAL); + onThinkDefense(EVENT_CREATURE_THINK_INTERVAL); + onThinkSound(EVENT_CREATURE_THINK_INTERVAL); + }); } void Monster::doAttacking(uint32_t interval) { @@ -2661,4 +2653,8 @@ void Monster::onExecuteAsyncTasks() { if (hasAsyncTaskFlag(UpdateIdleStatus)) { updateIdleStatus(); } + + if (hasAsyncTaskFlag(OnThink)) { + onThink_async(); + } } diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index b8a6087acc1..145b973fdc9 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -225,6 +225,8 @@ class Monster final : public Creature { void onExecuteAsyncTasks() override; private: + void onThink_async(); + auto getTargetIterator(const std::shared_ptr &creature) { return std::ranges::find_if(targetList.begin(), targetList.end(), [id = creature->getID()](const std::weak_ptr &ref) { const auto &target = ref.lock(); diff --git a/src/game/game.cpp b/src/game/game.cpp index 4dc0b9e3e20..5c7dc7aaf16 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6457,8 +6457,18 @@ void Game::checkCreatures() { if (const auto creature = weak.lock()) { if (creature->creatureCheck && creature->isAlive()) { creature->onThink(EVENT_CREATURE_THINK_INTERVAL); - creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); - creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + if (creature->getMonster()) { + // The monster's onThink is executed asynchronously, + // so the target is updated later, so we need to postpone the actions below. + g_dispatcher().addEvent([creature] { + if (creature->isAlive()) { + creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); + creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + } }, __FUNCTION__); + } else { + creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); + creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + } return false; } From ba62a415a6e9f819b852925e005e99e04a7f63db Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 11 Dec 2024 00:38:22 -0300 Subject: [PATCH 12/12] improve: migration database update (#3071) This improves the database migration process by removing the need to manually define if more migrations are required with return values in Lua scripts. The new approach automatically processes all migration files based on their version, reducing complexity and making the update process more robust. This change aims to streamline the database update logic and eliminate manual steps previously required. --- data-canary/migrations/0.lua | 3 - data-canary/migrations/1.lua | 5 -- data-canary/migrations/README.md | 45 +++++++++++++ data-otservbr-global/migrations/0.lua | 14 ---- data-otservbr-global/migrations/1.lua | 35 +++------- data-otservbr-global/migrations/10.lua | 1 - data-otservbr-global/migrations/11.lua | 1 - data-otservbr-global/migrations/12.lua | 1 - data-otservbr-global/migrations/13.lua | 1 - data-otservbr-global/migrations/14.lua | 1 - data-otservbr-global/migrations/15.lua | 1 - data-otservbr-global/migrations/16.lua | 1 - data-otservbr-global/migrations/17.lua | 1 - data-otservbr-global/migrations/18.lua | 2 - data-otservbr-global/migrations/19.lua | 2 - data-otservbr-global/migrations/2.lua | 2 - data-otservbr-global/migrations/20.lua | 1 - data-otservbr-global/migrations/21.lua | 1 - data-otservbr-global/migrations/22.lua | 1 - data-otservbr-global/migrations/23.lua | 1 - data-otservbr-global/migrations/24.lua | 1 - data-otservbr-global/migrations/25.lua | 1 - data-otservbr-global/migrations/26.lua | 1 - data-otservbr-global/migrations/27.lua | 1 - data-otservbr-global/migrations/28.lua | 1 - data-otservbr-global/migrations/29.lua | 1 - data-otservbr-global/migrations/3.lua | 1 - data-otservbr-global/migrations/30.lua | 1 - data-otservbr-global/migrations/31.lua | 1 - data-otservbr-global/migrations/32.lua | 1 - data-otservbr-global/migrations/33.lua | 1 - data-otservbr-global/migrations/34.lua | 1 - data-otservbr-global/migrations/35.lua | 1 - data-otservbr-global/migrations/36.lua | 1 - data-otservbr-global/migrations/37.lua | 1 - data-otservbr-global/migrations/38.lua | 1 - data-otservbr-global/migrations/39.lua | 1 - data-otservbr-global/migrations/4.lua | 1 - data-otservbr-global/migrations/40.lua | 2 - data-otservbr-global/migrations/41.lua | 2 - data-otservbr-global/migrations/42.lua | 2 - data-otservbr-global/migrations/43.lua | 2 - data-otservbr-global/migrations/44.lua | 2 - data-otservbr-global/migrations/45.lua | 2 - data-otservbr-global/migrations/46.lua | 2 - data-otservbr-global/migrations/47.lua | 23 ++++++- data-otservbr-global/migrations/5.lua | 1 - data-otservbr-global/migrations/6.lua | 1 - data-otservbr-global/migrations/7.lua | 1 - data-otservbr-global/migrations/8.lua | 1 - data-otservbr-global/migrations/9.lua | 1 - data-otservbr-global/migrations/README.md | 45 +++++++++++++ src/database/databasemanager.cpp | 81 +++++++++++++++-------- 53 files changed, 177 insertions(+), 129 deletions(-) delete mode 100644 data-canary/migrations/0.lua delete mode 100644 data-canary/migrations/1.lua create mode 100644 data-canary/migrations/README.md delete mode 100644 data-otservbr-global/migrations/0.lua create mode 100644 data-otservbr-global/migrations/README.md diff --git a/data-canary/migrations/0.lua b/data-canary/migrations/0.lua deleted file mode 100644 index d0ffd9c0cb3..00000000000 --- a/data-canary/migrations/0.lua +++ /dev/null @@ -1,3 +0,0 @@ -function onUpdateDatabase() - return false -end diff --git a/data-canary/migrations/1.lua b/data-canary/migrations/1.lua deleted file mode 100644 index 332f1838723..00000000000 --- a/data-canary/migrations/1.lua +++ /dev/null @@ -1,5 +0,0 @@ --- return true = There are others migrations file --- return false = This is the last migration file -function onUpdateDatabase() - return false -end diff --git a/data-canary/migrations/README.md b/data-canary/migrations/README.md new file mode 100644 index 00000000000..b23473bf29d --- /dev/null +++ b/data-canary/migrations/README.md @@ -0,0 +1,45 @@ +# Database Migration System + +This document provides an overview of the current database migration system for the project. The migration process has been streamlined to ensure that all migration scripts are automatically applied in order, making it easier to maintain database updates. + +## How It Works + +The migration system is designed to apply updates to the database schema or data whenever a new server version is started. Migration scripts are stored in the `migrations` directory, and the system will automatically apply any scripts that have not yet been executed. + +### Steps Involved + +1. **Retrieve Current Database Version**: + - The system first retrieves the current version of the database using `getDatabaseVersion()`. + - This version is used to determine which migration scripts need to be executed. + +2. **Migration Files Directory**: + - All migration scripts are stored in the `migrations` directory. + - Each migration script is named using a numerical pattern, such as `1.lua`, `2.lua`, etc. + - The naming convention helps determine the order in which scripts should be applied. + +3. **Execute Migration Scripts**: + - The migration system iterates through the migration directory and applies each migration script that has a version greater than the current database version. + - Only scripts that have not been applied are executed. + - The Lua state (`lua_State* L`) is initialized to run each script. + +4. **Update Database Version**: + - After each migration script is successfully applied, the system updates the database version to reflect the applied change. + - This ensures that the script is not re-applied on subsequent server startups. + +## Example Migration Script + +Below is an example of what a migration script might look like. Note that no return value is required, as all migration files are applied based on the current database version. + +```lua +-- Migration script example (for documentation purposes only) +-- This migration script should include all necessary SQL commands or operations to apply a specific update to the database. + +-- Example: Adding a new column to the "players" table +local query = [[ + ALTER TABLE players ADD COLUMN new_feature_flag TINYINT(1) NOT NULL DEFAULT 0; +]] + +-- Execute the query +db.execute(query) -- This function executes the given SQL query on the database. + +-- Note: Ensure that queries are validated to avoid errors during the migration process. diff --git a/data-otservbr-global/migrations/0.lua b/data-otservbr-global/migrations/0.lua deleted file mode 100644 index b40962e7c1f..00000000000 --- a/data-otservbr-global/migrations/0.lua +++ /dev/null @@ -1,14 +0,0 @@ -function onUpdateDatabase() - logger.info("Updating database to version 1 (sample players)") - -- Rook Sample - db.query("UPDATE `players` SET `level` = 2, `vocation` = 0, `health` = 155, `healthmax` = 155, `experience` = 100, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 60, `manamax` = 60, `town_id` = 1, `cap` = 410 WHERE `id` = 1;") - -- Sorcerer Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 1, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 2;") - -- Druid Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 2, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 3;") - -- Paladin Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 3, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 4;") - -- Knight Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 4, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 5;") - return true -end diff --git a/data-otservbr-global/migrations/1.lua b/data-otservbr-global/migrations/1.lua index 180a8b2ce90..1c5888f52d7 100644 --- a/data-otservbr-global/migrations/1.lua +++ b/data-otservbr-global/migrations/1.lua @@ -1,26 +1,13 @@ function onUpdateDatabase() - logger.info("Updating database to version 2 (hireling)") - - db.query([[ - CREATE TABLE IF NOT EXISTS `player_hirelings` ( - `id` INT NOT NULL PRIMARY KEY auto_increment, - `player_id` INT NOT NULL, - `name` varchar(255), - `active` tinyint unsigned NOT NULL DEFAULT '0', - `sex` tinyint unsigned NOT NULL DEFAULT '0', - `posx` int(11) NOT NULL DEFAULT '0', - `posy` int(11) NOT NULL DEFAULT '0', - `posz` int(11) NOT NULL DEFAULT '0', - `lookbody` int(11) NOT NULL DEFAULT '0', - `lookfeet` int(11) NOT NULL DEFAULT '0', - `lookhead` int(11) NOT NULL DEFAULT '0', - `looklegs` int(11) NOT NULL DEFAULT '0', - `looktype` int(11) NOT NULL DEFAULT '136', - - FOREIGN KEY(`player_id`) REFERENCES `players`(`id`) - ON DELETE CASCADE - ) - ]]) - - return true + logger.info("Updating database to version 1 (sample players)") + -- Rook Sample + db.query("UPDATE `players` SET `level` = 2, `vocation` = 0, `health` = 155, `healthmax` = 155, `experience` = 100, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 60, `manamax` = 60, `town_id` = 1, `cap` = 410 WHERE `id` = 1;") + -- Sorcerer Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 1, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 2;") + -- Druid Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 2, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 3;") + -- Paladin Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 3, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 4;") + -- Knight Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 4, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 5;") end diff --git a/data-otservbr-global/migrations/10.lua b/data-otservbr-global/migrations/10.lua index 0285bb0fee6..9dfded3813d 100644 --- a/data-otservbr-global/migrations/10.lua +++ b/data-otservbr-global/migrations/10.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 11 (Guilds Balance)") db.query("ALTER TABLE `guilds` ADD `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/11.lua b/data-otservbr-global/migrations/11.lua index 7f448f94a9d..08d40b66381 100644 --- a/data-otservbr-global/migrations/11.lua +++ b/data-otservbr-global/migrations/11.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 12 (Player get daily reward)") db.query("ALTER TABLE `players` ADD `isreward` tinyint(1) NOT NULL DEFAULT 1") - return true end diff --git a/data-otservbr-global/migrations/12.lua b/data-otservbr-global/migrations/12.lua index 0c6e27bb275..e83ca4e51f8 100644 --- a/data-otservbr-global/migrations/12.lua +++ b/data-otservbr-global/migrations/12.lua @@ -7,5 +7,4 @@ function onUpdateDatabase() db.query("ALTER TABLE boosted_creature ADD `lookbody` int(11) NOT NULL DEFAULT 0;") db.query("ALTER TABLE boosted_creature ADD `lookaddons` int(11) NOT NULL DEFAULT 0;") db.query("ALTER TABLE boosted_creature ADD `lookmount` int(11) DEFAULT 0;") - return true end diff --git a/data-otservbr-global/migrations/13.lua b/data-otservbr-global/migrations/13.lua index 5918b6cbd58..479b28eda79 100644 --- a/data-otservbr-global/migrations/13.lua +++ b/data-otservbr-global/migrations/13.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 14 (Fixed mana spent)") db.query("ALTER TABLE `players` CHANGE `manaspent` `manaspent` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/14.lua b/data-otservbr-global/migrations/14.lua index d2a1faf273b..7c23d8053b5 100644 --- a/data-otservbr-global/migrations/14.lua +++ b/data-otservbr-global/migrations/14.lua @@ -2,5 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 15 (Magic Shield Spell)") db.query("ALTER TABLE `players` ADD `manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0' AFTER `skill_manaleech_amount`") db.query("ALTER TABLE `players` ADD `max_manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0' AFTER `manashield`") - return true end diff --git a/data-otservbr-global/migrations/15.lua b/data-otservbr-global/migrations/15.lua index 1521e96101f..73daf3c5b31 100644 --- a/data-otservbr-global/migrations/15.lua +++ b/data-otservbr-global/migrations/15.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() db.query("UPDATE `players` SET `maglevel` = 2, `manaspent` = 5936, `skill_club` = 12, `skill_club_tries` = 155, `skill_sword` = 12, `skill_sword_tries` = 155, `skill_axe` = 12, `skill_axe_tries` = 155, `skill_dist` = 12, `skill_dist_tries` = 93 WHERE `id` = 1;") -- GOD db.query("UPDATE `players` SET `health` = 155, `healthmax` = 155, `experience` = 100, `looktype` = 75, `town_id` = 8 WHERE `id` = 6;") - return true end diff --git a/data-otservbr-global/migrations/16.lua b/data-otservbr-global/migrations/16.lua index c9ca340f0b3..a5766130bc3 100644 --- a/data-otservbr-global/migrations/16.lua +++ b/data-otservbr-global/migrations/16.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() print("Updating database to version 17 (Tutorial support)") db.query("ALTER TABLE `players` ADD `istutorial` SMALLINT(1) NOT NULL DEFAULT '0'") - return true -- true = There are others migrations file | false = this is the last migration file end diff --git a/data-otservbr-global/migrations/17.lua b/data-otservbr-global/migrations/17.lua index d25e4ccd5a9..9d5f0d8d624 100644 --- a/data-otservbr-global/migrations/17.lua +++ b/data-otservbr-global/migrations/17.lua @@ -2,5 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 18 (Fix guild creation myaac)") db.query("ALTER TABLE `guilds` ADD `level` int(11) NOT NULL DEFAULT 1") db.query("ALTER TABLE `guilds` ADD `points` int(11) NOT NULL DEFAULT 0") - return true end diff --git a/data-otservbr-global/migrations/18.lua b/data-otservbr-global/migrations/18.lua index 01ef6048033..e017b86e05b 100644 --- a/data-otservbr-global/migrations/18.lua +++ b/data-otservbr-global/migrations/18.lua @@ -48,6 +48,4 @@ function onUpdateDatabase() `monster_list` BLOB NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - - return true end diff --git a/data-otservbr-global/migrations/19.lua b/data-otservbr-global/migrations/19.lua index 382273dfef1..e7d27a859ef 100644 --- a/data-otservbr-global/migrations/19.lua +++ b/data-otservbr-global/migrations/19.lua @@ -4,6 +4,4 @@ function onUpdateDatabase() db.query("ALTER TABLE `accounts` ADD `tournament_coins` int(11) NOT NULL DEFAULT 0 AFTER `coins`") db.query("ALTER TABLE `store_history` ADD `coin_type` tinyint(1) NOT NULL DEFAULT 0 AFTER `description`") db.query("ALTER TABLE `store_history` DROP COLUMN `coins`") -- Not in use anywhere. - - return true end diff --git a/data-otservbr-global/migrations/2.lua b/data-otservbr-global/migrations/2.lua index e953d579a3e..72c797b4b0e 100644 --- a/data-otservbr-global/migrations/2.lua +++ b/data-otservbr-global/migrations/2.lua @@ -109,6 +109,4 @@ function onUpdateDatabase() ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - - return true end diff --git a/data-otservbr-global/migrations/20.lua b/data-otservbr-global/migrations/20.lua index 49bc9ecd56f..daaefc6f041 100644 --- a/data-otservbr-global/migrations/20.lua +++ b/data-otservbr-global/migrations/20.lua @@ -2,5 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 21 (Fix market price size)") db.query("ALTER TABLE `market_history` CHANGE `price` `price` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';") db.query("ALTER TABLE `market_offers` CHANGE `price` `price` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/21.lua b/data-otservbr-global/migrations/21.lua index a2e945576f7..cec635ed937 100644 --- a/data-otservbr-global/migrations/21.lua +++ b/data-otservbr-global/migrations/21.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() db.query("ALTER TABLE `market_history` ADD `tier` tinyint UNSIGNED NOT NULL DEFAULT '0';") db.query("ALTER TABLE `players` ADD `forge_dusts` bigint(21) NOT NULL DEFAULT '0';") db.query("ALTER TABLE `players` ADD `forge_dust_level` bigint(21) UNSIGNED NOT NULL DEFAULT '100';") - return true end diff --git a/data-otservbr-global/migrations/22.lua b/data-otservbr-global/migrations/22.lua index ee1caf87ac3..c4c5bd385bc 100644 --- a/data-otservbr-global/migrations/22.lua +++ b/data-otservbr-global/migrations/22.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() ALTER TABLE `players` MODIFY offlinetraining_skill tinyint(2) NOT NULL DEFAULT '-1'; ]]) - return true end diff --git a/data-otservbr-global/migrations/23.lua b/data-otservbr-global/migrations/23.lua index da05835f759..8edac8cef47 100644 --- a/data-otservbr-global/migrations/23.lua +++ b/data-otservbr-global/migrations/23.lua @@ -16,5 +16,4 @@ function onUpdateDatabase() FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - return true end diff --git a/data-otservbr-global/migrations/24.lua b/data-otservbr-global/migrations/24.lua index c3cc1563b56..fed9f189085 100644 --- a/data-otservbr-global/migrations/24.lua +++ b/data-otservbr-global/migrations/24.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 25 (random mount outfit window)") db.query("ALTER TABLE `players` ADD `randomize_mount` SMALLINT(1) NOT NULL DEFAULT '0'") - return true end diff --git a/data-otservbr-global/migrations/25.lua b/data-otservbr-global/migrations/25.lua index e486b9ddf76..4d229bb58e5 100644 --- a/data-otservbr-global/migrations/25.lua +++ b/data-otservbr-global/migrations/25.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 26 (reward bag fix)") db.query("UPDATE player_rewards SET pid = 0 WHERE itemtype = 19202;") - return true end diff --git a/data-otservbr-global/migrations/26.lua b/data-otservbr-global/migrations/26.lua index bbac31b8170..ddf821ca5cd 100644 --- a/data-otservbr-global/migrations/26.lua +++ b/data-otservbr-global/migrations/26.lua @@ -11,5 +11,4 @@ function onUpdateDatabase() PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`)) ]]) - return true end diff --git a/data-otservbr-global/migrations/27.lua b/data-otservbr-global/migrations/27.lua index b16b589da87..73e7bf5c4f7 100644 --- a/data-otservbr-global/migrations/27.lua +++ b/data-otservbr-global/migrations/27.lua @@ -23,5 +23,4 @@ function onUpdateDatabase() `bossIdSlotTwo` int NOT NULL DEFAULT 0, `removeTimes` int NOT NULL DEFAULT 1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;]]) - return true end diff --git a/data-otservbr-global/migrations/28.lua b/data-otservbr-global/migrations/28.lua index 2bd44799cfc..d7575edf3c8 100644 --- a/data-otservbr-global/migrations/28.lua +++ b/data-otservbr-global/migrations/28.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 29 (transfer coins)") db.query("ALTER TABLE `accounts` ADD `coins_transferable` int unsigned NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/29.lua b/data-otservbr-global/migrations/29.lua index 1b490d324c2..0c633e46b5d 100644 --- a/data-otservbr-global/migrations/29.lua +++ b/data-otservbr-global/migrations/29.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 30 (looktypeEx)") db.query("ALTER TABLE `boosted_boss` ADD `looktypeEx` int unsigned NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/3.lua b/data-otservbr-global/migrations/3.lua index 72fc0d41cd8..ae06343be07 100644 --- a/data-otservbr-global/migrations/3.lua +++ b/data-otservbr-global/migrations/3.lua @@ -5,5 +5,4 @@ function onUpdateDatabase() ALTER TABLE `prey_slots` ADD `tick` smallint(3) NOT NULL DEFAULT '0'; ]]) - return true end diff --git a/data-otservbr-global/migrations/30.lua b/data-otservbr-global/migrations/30.lua index f724284e258..4ee1632421d 100644 --- a/data-otservbr-global/migrations/30.lua +++ b/data-otservbr-global/migrations/30.lua @@ -3,5 +3,4 @@ function onUpdateDatabase() db.query([[ ALTER TABLE `accounts` ADD COLUMN `premdays_purchased` int(11) NOT NULL DEFAULT 0; ]]) - return true end diff --git a/data-otservbr-global/migrations/31.lua b/data-otservbr-global/migrations/31.lua index 4207776b90e..5ba21bbe561 100644 --- a/data-otservbr-global/migrations/31.lua +++ b/data-otservbr-global/migrations/31.lua @@ -13,5 +13,4 @@ function onUpdateDatabase() db.query([[ ALTER TABLE `accounts` MODIFY `password` TEXT NOT NULL; ]]) - return true end diff --git a/data-otservbr-global/migrations/32.lua b/data-otservbr-global/migrations/32.lua index 102a9aafd9f..078ef407da6 100644 --- a/data-otservbr-global/migrations/32.lua +++ b/data-otservbr-global/migrations/32.lua @@ -10,5 +10,4 @@ function onUpdateDatabase() ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - return true end diff --git a/data-otservbr-global/migrations/33.lua b/data-otservbr-global/migrations/33.lua index afac0cebdfc..2c77cbb6e24 100644 --- a/data-otservbr-global/migrations/33.lua +++ b/data-otservbr-global/migrations/33.lua @@ -24,5 +24,4 @@ function onUpdateDatabase() ALTER TABLE `player_wheeldata` ADD PRIMARY KEY (`player_id`); ]]) - return true end diff --git a/data-otservbr-global/migrations/34.lua b/data-otservbr-global/migrations/34.lua index 7f0a289f656..7537f6e6582 100644 --- a/data-otservbr-global/migrations/34.lua +++ b/data-otservbr-global/migrations/34.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 35 (bosstiary tracker)") db.query("ALTER TABLE `player_bosstiary` ADD `tracker` blob NOT NULL;") - return true end diff --git a/data-otservbr-global/migrations/35.lua b/data-otservbr-global/migrations/35.lua index 10bddc47138..70c820c32fd 100644 --- a/data-otservbr-global/migrations/35.lua +++ b/data-otservbr-global/migrations/35.lua @@ -12,7 +12,6 @@ function onUpdateDatabase() until not Result.next(resultQuery) Result.free(resultQuery) end - return true end function getNewValue(premDays, lastDay) diff --git a/data-otservbr-global/migrations/36.lua b/data-otservbr-global/migrations/36.lua index 7d35e223cf9..5f912763cc8 100644 --- a/data-otservbr-global/migrations/36.lua +++ b/data-otservbr-global/migrations/36.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 37 (add coin_type to accounts)") db.query("ALTER TABLE `coins_transactions` ADD `coin_type` tinyint(1) UNSIGNED NOT NULL DEFAULT '1';") - return true end diff --git a/data-otservbr-global/migrations/37.lua b/data-otservbr-global/migrations/37.lua index 70f2ec126ce..ae3dbefbc27 100644 --- a/data-otservbr-global/migrations/37.lua +++ b/data-otservbr-global/migrations/37.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 38 (add pronoun to players)") db.query("ALTER TABLE `players` ADD `pronoun` int(11) NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/38.lua b/data-otservbr-global/migrations/38.lua index 7981d5d7063..3412e4d488f 100644 --- a/data-otservbr-global/migrations/38.lua +++ b/data-otservbr-global/migrations/38.lua @@ -8,5 +8,4 @@ function onUpdateDatabase() PRIMARY KEY (`key_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - return true end diff --git a/data-otservbr-global/migrations/39.lua b/data-otservbr-global/migrations/39.lua index 2bf3815016f..181994882db 100644 --- a/data-otservbr-global/migrations/39.lua +++ b/data-otservbr-global/migrations/39.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 40 (house transfer ownership on startup)") db.query("ALTER TABLE `houses` ADD `new_owner` int(11) NOT NULL DEFAULT '-1';") - return true end diff --git a/data-otservbr-global/migrations/4.lua b/data-otservbr-global/migrations/4.lua index fa07383aeaa..891bc20915a 100644 --- a/data-otservbr-global/migrations/4.lua +++ b/data-otservbr-global/migrations/4.lua @@ -6,5 +6,4 @@ function onUpdateDatabase() `raceid` varchar(250) NOT NULL DEFAULT '', PRIMARY KEY (`date`) ) AS SELECT 0 AS date, "default" AS boostname, 0 AS raceid]]) - return true end diff --git a/data-otservbr-global/migrations/40.lua b/data-otservbr-global/migrations/40.lua index 22bfea0da95..be8794e7b25 100644 --- a/data-otservbr-global/migrations/40.lua +++ b/data-otservbr-global/migrations/40.lua @@ -12,6 +12,4 @@ function onUpdateDatabase() ALTER TABLE `house_lists` MODIFY `version` bigint(20) NOT NULL DEFAULT '0'; ]]) - - return true end diff --git a/data-otservbr-global/migrations/41.lua b/data-otservbr-global/migrations/41.lua index 15eb1d88e99..1fa9a40e36d 100644 --- a/data-otservbr-global/migrations/41.lua +++ b/data-otservbr-global/migrations/41.lua @@ -6,6 +6,4 @@ function onUpdateDatabase() MODIFY `xpboost_stamina` smallint(5) UNSIGNED DEFAULT NULL, MODIFY `xpboost_value` tinyint(4) UNSIGNED DEFAULT NULL ]]) - - return true end diff --git a/data-otservbr-global/migrations/42.lua b/data-otservbr-global/migrations/42.lua index 4d07b663daa..4b0b97b9987 100644 --- a/data-otservbr-global/migrations/42.lua +++ b/data-otservbr-global/migrations/42.lua @@ -5,6 +5,4 @@ function onUpdateDatabase() ALTER TABLE `guildwar_kills` DROP INDEX `guildwar_kills_unique` ]]) - - return true end diff --git a/data-otservbr-global/migrations/43.lua b/data-otservbr-global/migrations/43.lua index 6d3492a815a..438ba91b713 100644 --- a/data-otservbr-global/migrations/43.lua +++ b/data-otservbr-global/migrations/43.lua @@ -7,6 +7,4 @@ function onUpdateDatabase() ADD `payment` bigint(13) UNSIGNED NOT NULL DEFAULT '0', ADD `duration_days` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' ]]) - - return true end diff --git a/data-otservbr-global/migrations/44.lua b/data-otservbr-global/migrations/44.lua index c551fc79aeb..0e2140c3183 100644 --- a/data-otservbr-global/migrations/44.lua +++ b/data-otservbr-global/migrations/44.lua @@ -6,6 +6,4 @@ function onUpdateDatabase() MODIFY COLUMN `manashield` INT UNSIGNED NOT NULL DEFAULT '0', MODIFY COLUMN `max_manashield` INT UNSIGNED NOT NULL DEFAULT '0'; ]]) - - return true end diff --git a/data-otservbr-global/migrations/45.lua b/data-otservbr-global/migrations/45.lua index 4ceb5f7e3fd..a88de886861 100644 --- a/data-otservbr-global/migrations/45.lua +++ b/data-otservbr-global/migrations/45.lua @@ -51,6 +51,4 @@ function onUpdateDatabase() INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) SELECT 3, id, 'Trading Partners', 0 FROM `accounts`; ]]) - - return true end diff --git a/data-otservbr-global/migrations/46.lua b/data-otservbr-global/migrations/46.lua index 506da3a132b..da4ade8cbf3 100644 --- a/data-otservbr-global/migrations/46.lua +++ b/data-otservbr-global/migrations/46.lua @@ -2,6 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 47 (fix: creature speed and conditions)") db.query("ALTER TABLE `players` MODIFY `conditions` mediumblob NOT NULL;") - - return true end diff --git a/data-otservbr-global/migrations/47.lua b/data-otservbr-global/migrations/47.lua index 86a6d8ffec1..3c8908b5641 100644 --- a/data-otservbr-global/migrations/47.lua +++ b/data-otservbr-global/migrations/47.lua @@ -1,3 +1,24 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + logger.info("Updating database to version 46 (hireling)") + + db.query([[ + CREATE TABLE IF NOT EXISTS `player_hirelings` ( + `id` INT NOT NULL PRIMARY KEY auto_increment, + `player_id` INT NOT NULL, + `name` varchar(255), + `active` tinyint unsigned NOT NULL DEFAULT '0', + `sex` tinyint unsigned NOT NULL DEFAULT '0', + `posx` int(11) NOT NULL DEFAULT '0', + `posy` int(11) NOT NULL DEFAULT '0', + `posz` int(11) NOT NULL DEFAULT '0', + `lookbody` int(11) NOT NULL DEFAULT '0', + `lookfeet` int(11) NOT NULL DEFAULT '0', + `lookhead` int(11) NOT NULL DEFAULT '0', + `looklegs` int(11) NOT NULL DEFAULT '0', + `looktype` int(11) NOT NULL DEFAULT '136', + + FOREIGN KEY(`player_id`) REFERENCES `players`(`id`) + ON DELETE CASCADE + ) + ]]) end diff --git a/data-otservbr-global/migrations/5.lua b/data-otservbr-global/migrations/5.lua index 50a02a7feb5..dbc324198dd 100644 --- a/data-otservbr-global/migrations/5.lua +++ b/data-otservbr-global/migrations/5.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 6 (quickloot)") db.query("ALTER TABLE `players` ADD `quickloot_fallback` TINYINT DEFAULT 0") - return true end diff --git a/data-otservbr-global/migrations/6.lua b/data-otservbr-global/migrations/6.lua index 905a02ac146..91766a68ca7 100644 --- a/data-otservbr-global/migrations/6.lua +++ b/data-otservbr-global/migrations/6.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() `player_id` INT(16) NOT NULL, `item_id` INT(16) NOT NULL, `item_count` INT(32) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;]]) - return true -- true = There are others migrations file | false = this is the last migration file end diff --git a/data-otservbr-global/migrations/7.lua b/data-otservbr-global/migrations/7.lua index f5c7e6ed530..cade1faae1f 100644 --- a/data-otservbr-global/migrations/7.lua +++ b/data-otservbr-global/migrations/7.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 8 (recruiter system)") db.query("ALTER TABLE `accounts` ADD `recruiter` INT(6) DEFAULT 0") - return true end diff --git a/data-otservbr-global/migrations/8.lua b/data-otservbr-global/migrations/8.lua index bab5e908756..523b705c0c7 100644 --- a/data-otservbr-global/migrations/8.lua +++ b/data-otservbr-global/migrations/8.lua @@ -26,5 +26,4 @@ function onUpdateDatabase() `UsedRunesBit` VARCHAR(250) NULL , `UnlockedRunesBit` VARCHAR(250) NULL, `tracker list` BLOB NULL ) ENGINE = InnoDB DEFAULT CHARSET=utf8;]]) - return true -- true = There are others migrations file | false = this is the last migration file end diff --git a/data-otservbr-global/migrations/9.lua b/data-otservbr-global/migrations/9.lua index 23aa28daf75..7ce8e189768 100644 --- a/data-otservbr-global/migrations/9.lua +++ b/data-otservbr-global/migrations/9.lua @@ -5,5 +5,4 @@ function onUpdateDatabase() db.query("ALTER TABLE `players` ADD `lookmounthead` tinyint(3) unsigned NOT NULL DEFAULT '0'") db.query("ALTER TABLE `players` ADD `lookmountlegs` tinyint(3) unsigned NOT NULL DEFAULT '0'") db.query("ALTER TABLE `players` ADD `lookfamiliarstype` int(11) unsigned NOT NULL DEFAULT '0'") - return true end diff --git a/data-otservbr-global/migrations/README.md b/data-otservbr-global/migrations/README.md new file mode 100644 index 00000000000..b23473bf29d --- /dev/null +++ b/data-otservbr-global/migrations/README.md @@ -0,0 +1,45 @@ +# Database Migration System + +This document provides an overview of the current database migration system for the project. The migration process has been streamlined to ensure that all migration scripts are automatically applied in order, making it easier to maintain database updates. + +## How It Works + +The migration system is designed to apply updates to the database schema or data whenever a new server version is started. Migration scripts are stored in the `migrations` directory, and the system will automatically apply any scripts that have not yet been executed. + +### Steps Involved + +1. **Retrieve Current Database Version**: + - The system first retrieves the current version of the database using `getDatabaseVersion()`. + - This version is used to determine which migration scripts need to be executed. + +2. **Migration Files Directory**: + - All migration scripts are stored in the `migrations` directory. + - Each migration script is named using a numerical pattern, such as `1.lua`, `2.lua`, etc. + - The naming convention helps determine the order in which scripts should be applied. + +3. **Execute Migration Scripts**: + - The migration system iterates through the migration directory and applies each migration script that has a version greater than the current database version. + - Only scripts that have not been applied are executed. + - The Lua state (`lua_State* L`) is initialized to run each script. + +4. **Update Database Version**: + - After each migration script is successfully applied, the system updates the database version to reflect the applied change. + - This ensures that the script is not re-applied on subsequent server startups. + +## Example Migration Script + +Below is an example of what a migration script might look like. Note that no return value is required, as all migration files are applied based on the current database version. + +```lua +-- Migration script example (for documentation purposes only) +-- This migration script should include all necessary SQL commands or operations to apply a specific update to the database. + +-- Example: Adding a new column to the "players" table +local query = [[ + ALTER TABLE players ADD COLUMN new_feature_flag TINYINT(1) NOT NULL DEFAULT 0; +]] + +-- Execute the query +db.execute(query) -- This function executes the given SQL query on the database. + +-- Note: Ensure that queries are validated to avoid errors during the migration process. diff --git a/src/database/databasemanager.cpp b/src/database/databasemanager.cpp index 2941172f35a..142a1db332a 100644 --- a/src/database/databasemanager.cpp +++ b/src/database/databasemanager.cpp @@ -13,6 +13,19 @@ #include "lua/functions/core/libs/core_libs_functions.hpp" #include "lua/scripts/luascript.hpp" +namespace InternalDBManager { + int32_t extractVersionFromFilename(const std::string &filename) { + std::regex versionRegex(R"((\d+)\.lua)"); + std::smatch match; + + if (std::regex_search(filename, match, versionRegex) && match.size() > 1) { + return std::stoi(match.str(1)); + } + + return -1; + } +} + bool DatabaseManager::optimizeTables() { Database &db = Database::getInstance(); std::ostringstream query; @@ -73,48 +86,62 @@ int32_t DatabaseManager::getDatabaseVersion() { } void DatabaseManager::updateDatabase() { + Benchmark bm; lua_State* L = luaL_newstate(); if (!L) { return; } luaL_openlibs(L); - CoreLibsFunctions::init(L); - int32_t version = getDatabaseVersion(); - do { - std::ostringstream ss; - ss << g_configManager().getString(DATA_DIRECTORY) + "/migrations/" << version << ".lua"; - if (luaL_dofile(L, ss.str().c_str()) != 0) { - g_logger().error("DatabaseManager::updateDatabase - Version: {}" - "] {}", - version, lua_tostring(L, -1)); - break; - } + int32_t currentVersion = getDatabaseVersion(); + std::string migrationDirectory = g_configManager().getString(DATA_DIRECTORY) + "/migrations/"; - if (!LuaScriptInterface::reserveScriptEnv()) { - break; - } + std::vector> migrations; - lua_getglobal(L, "onUpdateDatabase"); - if (lua_pcall(L, 0, 1, 0) != 0) { - LuaScriptInterface::resetScriptEnv(); - g_logger().warn("[DatabaseManager::updateDatabase - Version: {}] {}", version, lua_tostring(L, -1)); - break; + for (const auto &entry : std::filesystem::directory_iterator(migrationDirectory)) { + if (entry.is_regular_file()) { + std::string filename = entry.path().filename().string(); + int32_t fileVersion = InternalDBManager::extractVersionFromFilename(filename); + migrations.emplace_back(fileVersion, entry.path().string()); } + } + + std::sort(migrations.begin(), migrations.end()); + + for (const auto &[fileVersion, scriptPath] : migrations) { + if (fileVersion > currentVersion) { + if (!LuaScriptInterface::reserveScriptEnv()) { + break; + } + + if (luaL_dofile(L, scriptPath.c_str()) != 0) { + g_logger().error("DatabaseManager::updateDatabase - Version: {}] {}", fileVersion, lua_tostring(L, -1)); + continue; + } + + lua_getglobal(L, "onUpdateDatabase"); + if (lua_pcall(L, 0, 1, 0) != 0) { + LuaScriptInterface::resetScriptEnv(); + g_logger().warn("[DatabaseManager::updateDatabase - Version: {}] {}", fileVersion, lua_tostring(L, -1)); + continue; + } + + currentVersion = fileVersion; + g_logger().info("Database has been updated to version {}", currentVersion); + registerDatabaseConfig("db_version", currentVersion); - if (!LuaScriptInterface::getBoolean(L, -1, false)) { LuaScriptInterface::resetScriptEnv(); - break; } + } - version++; - g_logger().info("Database has been updated to version {}", version); - registerDatabaseConfig("db_version", version); - - LuaScriptInterface::resetScriptEnv(); - } while (true); + double duration = bm.duration(); + if (duration < 1000.0) { + g_logger().debug("Database update completed in {:.2f} ms", duration); + } else { + g_logger().debug("Database update completed in {:.2f} seconds", duration / 1000.0); + } lua_close(L); }