From 9adf04031bd86eb72376636f261b2feed2abb491 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Mon, 9 Dec 2024 14:48:54 -0300 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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; }