From 68bbe7cc999f0120462f99d1f2b3cd5266adff7c Mon Sep 17 00:00:00 2001 From: un000000 <101636937+un000000@users.noreply.github.com> Date: Fri, 29 Sep 2023 20:59:56 +0200 Subject: [PATCH 01/18] fix: questlog message (#1648) --- src/creatures/players/player.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 305fe6ee8eb..ab2032d118d 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -861,12 +861,13 @@ void Player::addStorageValue(const uint32_t key, const int32_t value, const bool } if (value != -1) { + int32_t oldValue = getStorageValue(key); storageMap[key] = value; if (!isLogin) { auto currentFrameTime = g_dispatcher().getDispatcherCycle(); - g_events().eventOnStorageUpdate(static_self_cast(), key, value, getStorageValue(key), currentFrameTime); - g_callbacks().executeCallback(EventCallback_t::playerOnStorageUpdate, &EventCallback::playerOnStorageUpdate, getPlayer(), key, value, getStorageValue(key), currentFrameTime); + g_events().eventOnStorageUpdate(static_self_cast(), key, value, oldValue, currentFrameTime); + g_callbacks().executeCallback(EventCallback_t::playerOnStorageUpdate, &EventCallback::playerOnStorageUpdate, getPlayer(), key, value, oldValue, currentFrameTime); } } else { storageMap.erase(key); From d0281303eb273a5f3a9ada94cd9f716ac0caf4a0 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Fri, 29 Sep 2023 23:37:01 -0300 Subject: [PATCH 02/18] improve: revision of codes in data/ (#1374) --- data/events/scripts/creature.lua | 24 ++---- data/events/scripts/party.lua | 36 ++++---- data/events/scripts/player.lua | 137 ++++++++++++++----------------- data/global.lua | 6 +- data/libs/functions/creature.lua | 34 ++++++-- data/libs/functions/player.lua | 15 ++-- data/libs/functions/position.lua | 3 +- data/libs/functions/tile.lua | 2 +- 8 files changed, 125 insertions(+), 132 deletions(-) diff --git a/data/events/scripts/creature.lua b/data/events/scripts/creature.lua index 99d9f189d01..4da8217988d 100644 --- a/data/events/scripts/creature.lua +++ b/data/events/scripts/creature.lua @@ -1,5 +1,5 @@ -local function removeCombatProtection(cid) - local player = Player(cid) +local function removeCombatProtection(playerUid) + local player = Player(playerUid) if not player then return true end @@ -14,30 +14,22 @@ local function removeCombatProtection(cid) end player:setStorageValue(Global.Storage.CombatProtectionStorage, 2) - addEvent(function(cid) - local player = Player(cid) - if not player then + addEvent(function(playerFuncUid) + local playerEvent = Player(playerFuncUid) + if not playerEvent then return end - player:setStorageValue(Global.Storage.CombatProtectionStorage, 0) - player:remove() - end, time * 1000, cid) + playerEvent:setStorageValue(Global.Storage.CombatProtectionStorage, 0) + playerEvent:remove() + end, time * 1000, playerUid) end -picIf = {} function Creature:onTargetCombat(target) if not self then return true end - if not picIf[target.uid] then - if target:isMonster() then - target:registerEvent("RewardSystemSlogan") - picIf[target.uid] = {} - end - end - if target:isPlayer() then if self:isMonster() then local protectionStorage = target:getStorageValue(Global.Storage.CombatProtectionStorage) diff --git a/data/events/scripts/party.lua b/data/events/scripts/party.lua index 16ed0b733d9..b27965994b8 100644 --- a/data/events/scripts/party.lua +++ b/data/events/scripts/party.lua @@ -1,38 +1,38 @@ function Party:onJoin(player) - local playerId = player:getId() - addEvent(function() - player = Player(playerId) - if not player then + local playerUid = player:getGuid() + addEvent(function(playerFuncUid) + local playerEvent = Player(playerFuncUid) + if not playerEvent then return end - local party = player:getParty() + local party = playerEvent:getParty() if not party then return end party:refreshHazard() - end, 100) + end, 100, playerUid) return true end function Party:onLeave(player) - local playerId = player:getId() + local playerUid = player:getGuid() local members = self:getMembers() table.insert(members, self:getLeader()) - local memberIds = {} + local memberUids = {} for _, member in ipairs(members) do - if member:getId() ~= playerId then - table.insert(memberIds, member:getId()) + if member:getGuid() ~= playerUid then + table.insert(memberUids, member:getGuid()) end end - addEvent(function() - player = Player(playerId) - if player then - player:updateHazard() + addEvent(function(playerFuncUid, memberUidsTableEvent) + local playerEvent = Player(playerFuncUid) + if playerEvent then + playerEvent:updateHazard() end - for _, memberId in ipairs(memberIds) do - local member = Player(memberId) + for _, memberUid in ipairs(memberUidsTableEvent) do + local member = Player(memberUid) if member then local party = member:getParty() if party then @@ -41,7 +41,7 @@ function Party:onLeave(player) end end end - end, 100) + end, 100, playerUid, memberUids) return true end @@ -86,5 +86,5 @@ function Party:onShareExperience(exp) sharedExperienceMultiplier = 1.0 + ((size * (5 * (size - 1) + 10)) / 100) end - return (exp * sharedExperienceMultiplier) / (#self:getMembers() + 1) + return math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1)) end diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index 49ab18e8a07..f176e5000c5 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -40,64 +40,52 @@ local storeItemID = { -- Players cannot throw items on teleports if set to true local blockTeleportTrashing = true -local titles = { - { storageID = 14960, title = " Scout" }, - { storageID = 14961, title = " Sentinel" }, - { storageID = 14962, title = " Steward" }, - { storageID = 14963, title = " Warden" }, - { storageID = 14964, title = " Squire" }, - { storageID = 14965, title = " Warrior" }, - { storageID = 14966, title = " Keeper" }, - { storageID = 14967, title = " Guardian" }, - { storageID = 14968, title = " Sage" }, - { storageID = 14969, title = " Tutor" }, - { storageID = 14970, title = " Senior Tutor" }, - { storageID = 14971, title = " King" }, -} - local config = { maxItemsPerSeconds = 1, exhaustTime = 2000, } -if not pushDelay then - pushDelay = {} -end +local pushDelay = {} + +local function antiPush(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + if not player then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end -local function antiPush(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) if toPosition.x == CONTAINER_POSITION then return true end local tile = Tile(toPosition) if not tile then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) return false end - local cid = self:getId() - if not pushDelay[cid] then - pushDelay[cid] = { items = 0, time = 0 } + local playerId = player:getId() + if not pushDelay[playerId] then + pushDelay[playerId] = { items = 0, time = 0 } end - pushDelay[cid].items = pushDelay[cid].items + 1 + pushDelay[playerId].items = pushDelay[playerId].items + 1 local currentTime = systemTime() - if pushDelay[cid].time == 0 then - pushDelay[cid].time = currentTime - elseif pushDelay[cid].time == currentTime then - pushDelay[cid].items = pushDelay[cid].items + 1 - elseif currentTime > pushDelay[cid].time then - pushDelay[cid].time = 0 - pushDelay[cid].items = 0 + if pushDelay[playerId].time == 0 then + pushDelay[playerId].time = currentTime + elseif pushDelay[playerId].time == currentTime then + pushDelay[playerId].items = pushDelay[playerId].items + 1 + elseif currentTime > pushDelay[playerId].time then + pushDelay[playerId].time = 0 + pushDelay[playerId].items = 0 end - if pushDelay[cid].items > config.maxItemsPerSeconds then - pushDelay[cid].time = currentTime + config.exhaustTime + if pushDelay[playerId].items > config.maxItemsPerSeconds then + pushDelay[playerId].time = currentTime + config.exhaustTime end - if pushDelay[cid].time > currentTime then - self:sendCancelMessage("You can't move that item so fast.") + if pushDelay[playerId].time > currentTime then + player:sendCancelMessage("You can't move that item so fast.") return false end @@ -263,13 +251,12 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, if exhaust[pid] then self:sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED) return false - else - exhaust[pid] = true - addEvent(function() - exhaust[pid] = false - end, 2000, pid) - return true end + exhaust[playerId] = true + addEvent(function() + exhaust[playerId] = false + end, 2000, playerId) + return true end -- Bath tube @@ -304,9 +291,8 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, if parent:getSize() == parent:getCapacity() then self:sendTextMessage(MESSAGE_FAILURE, Game.getReturnMessage(RETURNVALUE_CONTAINERNOTENOUGHROOM)) return false - else - return moveItem:moveTo(parent) end + return moveItem:moveTo(parent) end end @@ -370,24 +356,27 @@ function Player:onItemMoved(item, count, fromPosition, toPosition, fromCylinder, local topos = Position(33052, 31932, 15) -- Checagem local removeItem = false if self:getPosition():isInRange(frompos, topos) and item:getId() == 23729 then - local tileBoss = Tile(toPosition) - if tileBoss and tileBoss:getTopCreature() and tileBoss:getTopCreature():isMonster() then - if tileBoss:getTopCreature():getName():lower() == "the remorseless corruptor" then - tileBoss:getTopCreature():addHealth(-17000) - tileBoss:getTopCreature():remove() - local monster = Game.createMonster("The Corruptor of Souls", toPosition) - if not monster then - return false - end - removeItem = true - monster:registerEvent("CheckTile") - if Game.getStorageValue("healthSoul") > 0 then - monster:addHealth(-(monster:getHealth() - Game.getStorageValue("healthSoul"))) + local tile = Tile(toPosition) + if tile then + local tileBoss = tile:getTopCreature() + if tileBoss and tileBoss:isMonster() then + if tileBoss:getName():lower() == "the remorseless corruptor" then + tileBoss:addHealth(-17000) + tileBoss:remove() + local monster = Game.createMonster("The Corruptor of Souls", toPosition) + if not monster then + return false + end + removeItem = true + monster:registerEvent("CheckTile") + if Game.getStorageValue("healthSoul") > 0 then + monster:addHealth(-(monster:getHealth() - Game.getStorageValue("healthSoul"))) + end + Game.setStorageValue("CheckTile", os.time() + 30) + elseif tileBoss:getName():lower() == "the corruptor of souls" then + Game.setStorageValue("CheckTile", os.time() + 30) + removeItem = true end - Game.setStorageValue("CheckTile", os.time() + 30) - elseif tileBoss:getTopCreature():getName():lower() == "the corruptor of souls" then - Game.setStorageValue("CheckTile", os.time() + 30) - removeItem = true end end if removeItem then @@ -401,7 +390,7 @@ end function Player:onMoveCreature(creature, fromPosition, toPosition) local player = creature:getPlayer() - if player and onExerciseTraining[player:getId()] and self:getGroup():hasFlag(PlayerFlag_CanPushAllCreatures) == false then + if player and onExerciseTraining[player:getId()] and not self:getGroup():hasFlag(PlayerFlag_CanPushAllCreatures) then self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) return false end @@ -409,13 +398,12 @@ function Player:onMoveCreature(creature, fromPosition, toPosition) end local function hasPendingReport(name, targetName, reportType) - local f = io.open(string.format("%s/reports/players/%s-%s-%d.txt", CORE_DIRECTORY, name, targetName, reportType), "r") - if f then - io.close(f) + local file = io.open(string.format("%s/reports/players/%s-%s-%d.txt", CORE_DIRECTORY, name, targetName, reportType), "r") + if file then + io.close(file) return true - else - return false end + return false end function Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) @@ -483,7 +471,6 @@ function Player:onTurn(direction) if self:getGroup():getAccess() and self:getDirection() == direction then local nextPosition = self:getPosition() nextPosition:getNextPosition(direction) - self:teleportTo(nextPosition, true) end @@ -569,31 +556,28 @@ function Player:onGainSkillTries(skill, tries) if IsRunningGlobalDatapack() and isSkillGrowthLimited(self, skill) then return 0 end - if APPLY_SKILL_MULTIPLIER == false then + if not APPLY_SKILL_MULTIPLIER then return tries end -- Event scheduler skill rate + local STAGES_DEFAULT = nil if configManager.getBoolean(configKeys.RATE_USE_STAGES) then STAGES_DEFAULT = skillsStages - else - STAGES_DEFAULT = nil end - SKILL_DEFAULT = self:getSkillLevel(skill) - RATE_DEFAULT = configManager.getNumber(configKeys.RATE_SKILL) + local SKILL_DEFAULT = self:getSkillLevel(skill) + local RATE_DEFAULT = configManager.getNumber(configKeys.RATE_SKILL) if skill == SKILL_MAGLEVEL then -- Magic Level if configManager.getBoolean(configKeys.RATE_USE_STAGES) then STAGES_DEFAULT = magicLevelStages - else - STAGES_DEFAULT = nil end SKILL_DEFAULT = self:getBaseMagicLevel() RATE_DEFAULT = configManager.getNumber(configKeys.RATE_MAGIC) end - skillOrMagicRate = getRateFromTable(STAGES_DEFAULT, SKILL_DEFAULT, RATE_DEFAULT) + local skillOrMagicRate = getRateFromTable(STAGES_DEFAULT, SKILL_DEFAULT, RATE_DEFAULT) if SCHEDULE_SKILL_RATE ~= 100 then skillOrMagicRate = math.max(0, (skillOrMagicRate * SCHEDULE_SKILL_RATE) / 100) @@ -618,9 +602,8 @@ function Player:onCombat(target, item, primaryDamage, primaryType, secondaryDama if ItemType(item:getId()):getWeaponType() == WEAPON_AMMO then if table.contains({ ITEM_OLD_DIAMOND_ARROW, ITEM_DIAMOND_ARROW }, item:getId()) then return primaryDamage, primaryType, secondaryDamage, secondaryType - else - item = self:getSlotItem(CONST_SLOT_LEFT) end + item = self:getSlotItem(CONST_SLOT_LEFT) end return primaryDamage, primaryType, secondaryDamage, secondaryType diff --git a/data/global.lua b/data/global.lua index 88d1d8103c0..9fad23e8787 100644 --- a/data/global.lua +++ b/data/global.lua @@ -43,8 +43,6 @@ DIRECTIONS_TABLE = { DIRECTION_NORTHEAST, } -STORAGEVALUE_PROMOTION = 30018 - SERVER_NAME = configManager.getString(configKeys.SERVER_NAME) SERVER_MOTD = configManager.getString(configKeys.SERVER_MOTD) @@ -85,16 +83,16 @@ ropeSpots = { 386, 421, 386, 7762, 12202, 12936, 14238, 17238, 23363, 21965, 219 specialRopeSpots = { 12935 } -- Impact Analyser --- Every 2 seconds -updateInterval = 2 if not GlobalBosses then GlobalBosses = {} end + -- Healing -- Global table to insert data if healingImpact == nil then healingImpact = {} end + -- Damage -- Global table to insert data if damageImpact == nil then diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua index 86245cc2491..ec4aa41c5bd 100644 --- a/data/libs/functions/creature.lua +++ b/data/libs/functions/creature.lua @@ -193,13 +193,33 @@ function Creature.checkCreatureInsideDoor(player, toPosition) end end -function Creature:addEventStamina(target) - local player = self:getPlayer() - local monster = target:getMonster() - if player and monster and monster:getName() == staminaBonus.target then - local playerId = player:getId() - if not staminaBonus.eventsTrainer[playerId] then - staminaBonus.eventsTrainer[playerId] = addEvent(addStamina, staminaBonus.period, playerId) +function Creature:canAccessPz() + if self:isMonster() or (self:isPlayer() and self:isPzLocked()) then + return false + end + return true +end + +function Creature.getKillers(self, onlyPlayers) + local killers = {} + local inFightTicks = configManager.getNumber(configKeys.PZ_LOCKED) + local timeNow = os.mtime() + local getCreature = onlyPlayers and Player or Creature + for cid, cb in pairs(self:getDamageMap()) do + local creature = getCreature(cid) + if creature and creature ~= self and (timeNow - cb.ticks) <= inFightTicks then + killers[#killers + 1] = { + creature = creature, + damage = cb.total, + } end end + + table.sort(killers, function(a, b) + return a.damage > b.damage + end) + for i, killer in pairs(killers) do + killers[i] = killer.creature + end + return killers end diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index ad35e6851f6..c2ae610cfd2 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -132,6 +132,10 @@ function Player.allowMovement(self, allow) return self:setStorageValue(Global.Storage.BlockMovementStorage, allow and -1 or 1) end +function Player.hasAllowMovement(self) + return self:getStorageValue(Global.Storage.BlockMovementStorage) ~= 1 +end + function Player.checkGnomeRank(self) if not IsRunningGlobalDatapack() then return true @@ -236,19 +240,14 @@ function Player:removeMoneyBank(amount) self:sendTextMessage(MESSAGE_TRADE, ("Paid %s from inventory and %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(moneyCount), FormatNumber(amount - moneyCount), FormatNumber(self:getBankBalance()))) return true - else - self:setBankBalance(bankCount - amount) - self:sendTextMessage(MESSAGE_TRADE, ("Paid %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(amount), FormatNumber(self:getBankBalance()))) - return true end + self:setBankBalance(bankCount - amount) + self:sendTextMessage(MESSAGE_TRADE, ("Paid %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(amount), FormatNumber(self:getBankBalance()))) + return true end return false end -function Player.hasAllowMovement(self) - return self:getStorageValue(Global.Storage.BlockMovementStorage) ~= 1 -end - function Player.hasRookgaardShield(self) -- Wooden Shield, Studded Shield, Brass Shield, Plate Shield, Copper Shield return self:getItemCount(3412) > 0 or self:getItemCount(3426) > 0 or self:getItemCount(3411) > 0 or self:getItemCount(3410) > 0 or self:getItemCount(3430) > 0 diff --git a/data/libs/functions/position.lua b/data/libs/functions/position.lua index 36ddc337aed..d3ac6667be3 100644 --- a/data/libs/functions/position.lua +++ b/data/libs/functions/position.lua @@ -36,7 +36,8 @@ function Position:moveUpstairs() direction = DIRECTION_WEST end - local position = self + Position.directionOffset[direction] + local position = Position(self) + position:getNextPosition(direction) toTile = Tile(position) if toTile and toTile:isWalkable(false, false, false, false, true) then swap(self, position) diff --git a/data/libs/functions/tile.lua b/data/libs/functions/tile.lua index 70690c65f7a..0201459018c 100644 --- a/data/libs/functions/tile.lua +++ b/data/libs/functions/tile.lua @@ -78,7 +78,7 @@ end -- Functions from OTServbr-Global function Tile.isHouse(self) local house = self:getHouse() - return not not house + return house and true or false end function Tile.isPz(self) From b9b3d88b23d15bf8bd7e1b372be630da192236b9 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Fri, 29 Sep 2023 23:36:53 -0700 Subject: [PATCH 03/18] fix: add back missing method (#1655) --- data/libs/functions/creature.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua index ec4aa41c5bd..d43a31f287d 100644 --- a/data/libs/functions/creature.lua +++ b/data/libs/functions/creature.lua @@ -223,3 +223,14 @@ function Creature.getKillers(self, onlyPlayers) end return killers end + +function Creature:addEventStamina(target) + local player = self:getPlayer() + local monster = target:getMonster() + if player and monster and monster:getName() == staminaBonus.target then + local playerId = player:getId() + if not staminaBonus.eventsTrainer[playerId] then + staminaBonus.eventsTrainer[playerId] = addEvent(addStamina, staminaBonus.period, playerId) + end + end +end From 067e4913568ef343bdfa5aec08ecc28b16178d24 Mon Sep 17 00:00:00 2001 From: Beats Date: Mon, 2 Oct 2023 17:34:28 -0300 Subject: [PATCH 04/18] feat: cyclopedia highscore cache (#1660) Fixed delay/freeze using cyclopedia highscore. --- src/game/game.cpp | 229 ++++++++++++++++++++++++++++++++-------------- src/game/game.hpp | 26 ++++++ 2 files changed, 184 insertions(+), 71 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index c89e438fbb1..be44b0d703f 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -7929,6 +7929,157 @@ void Game::playerCyclopediaCharacterInfo(std::shared_ptr player, uint32_ } } +std::string Game::generateHighscoreQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { + std::ostringstream query; + uint32_t startPage = (static_cast(page - 1) * static_cast(entriesPerPage)); + uint32_t endPage = startPage + static_cast(entriesPerPage); + + query << "SELECT *, @row AS `entries`, " << page << " AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn` FROM (SELECT `id`, `name`, `level`, `vocation`, `" + << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName + << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0) `r` WHERE `group_id` < " + << static_cast(account::GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`"; + + if (vocation != 0xFFFFFFFF) { + query << generateVocationConditionHighscore(vocation); + } + query << ") `T` WHERE `rn` > " << startPage << " AND `rn` <= " << endPage; + + return query.str(); +} + +std::string Game::generateHighscoreQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation) { + std::ostringstream query; + std::string entriesStr = std::to_string(entriesPerPage); + + query << "SELECT *, @row AS `entries`, (@ourRow DIV " << entriesStr << ") + 1 AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn`, @ourRow := IF(`id` = " + << playerGUID << ", @row - 1, @ourRow) AS `rw` FROM (SELECT `id`, `name`, `level`, `vocation`, `" << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" + << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0, @ourRow := 0) `r` WHERE `group_id` < " + << static_cast(account::GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`"; + + if (vocation != 0xFFFFFFFF) { + query << generateVocationConditionHighscore(vocation); + } + query << ") `T` WHERE `rn` > ((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") AND `rn` <= (((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") + " << entriesStr << ")"; + + return query.str(); +} + +std::string Game::generateVocationConditionHighscore(uint32_t vocation) { + std::ostringstream queryPart; + bool firstVocation = true; + + const auto vocationsMap = g_vocations().getVocations(); + for (const auto &it : vocationsMap) { + const auto &voc = it.second; + if (voc.getFromVocation() == vocation) { + if (firstVocation) { + queryPart << " WHERE `vocation` = " << voc.getId(); + firstVocation = false; + } else { + queryPart << " OR `vocation` = " << voc.getId(); + } + } + } + + return queryPart.str(); +} + +void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage) { + std::shared_ptr player = g_game().getPlayerByID(playerID); + if (!player) { + return; + } + + player->resetAsyncOngoingTask(PlayerAsyncTask_Highscore); + + if (!result) { + player->sendHighscoresNoData(); + return; + } + + uint16_t page = result->getNumber("page"); + uint32_t pages = result->getNumber("entries"); + pages += entriesPerPage - 1; + pages /= entriesPerPage; + + std::ostringstream cacheKeyStream; + cacheKeyStream << "Highscore_" << static_cast(category) << "_" << static_cast(vocation) << "_" << static_cast(entriesPerPage); + std::string cacheKey = cacheKeyStream.str(); + + auto it = highscoreCache.find(cacheKey); + auto now = std::chrono::steady_clock::now(); + if (it != highscoreCache.end() && (now - it->second.timestamp < HIGHSCORE_CACHE_EXPIRATION_TIME)) { + auto &cacheEntry = it->second; + player->sendHighscores(cacheEntry.characters, category, vocation, page, static_cast(pages)); + } else { + std::vector characters; + characters.reserve(result->countResults()); + if (result) { + do { + uint8_t characterVocation; + const auto &voc = g_vocations().getVocation(result->getNumber("vocation")); + if (voc) { + characterVocation = voc->getClientId(); + } else { + characterVocation = 0; + } + characters.emplace_back(std::move(result->getString("name")), result->getNumber("points"), result->getNumber("id"), result->getNumber("rank"), result->getNumber("level"), characterVocation); + } while (result->next()); + } + + player->sendHighscores(characters, category, vocation, page, static_cast(pages)); + highscoreCache[cacheKey] = { characters, now }; + } +} + +std::string Game::getCachedQueryHighscore(const std::string &key) { + auto it = queryCache.find(key); + if (it != queryCache.end()) { + auto now = std::chrono::steady_clock::now(); + if (now - it->second.timestamp < CACHE_EXPIRATION_TIME) { + return it->second.query; + } + } + return ""; +} + +void Game::cacheQueryHighscore(const std::string &key, const std::string &query) { + auto now = std::chrono::steady_clock::now(); + queryCache[key] = { query, now }; +} + +std::string Game::generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { + std::ostringstream cacheKeyStream; + cacheKeyStream << "Entries_" << categoryName << "_" << page << "_" << static_cast(entriesPerPage) << "_" << vocation; + std::string cacheKey = cacheKeyStream.str(); + + std::string cachedQuery = getCachedQueryHighscore(cacheKey); + if (!cachedQuery.empty()) { + return cachedQuery; + } + + std::string newQuery = generateHighscoreQueryForEntries(categoryName, page, entriesPerPage, vocation); + cacheQueryHighscore(cacheKey, newQuery); + + return newQuery; +} + +std::string Game::generateHighscoreOrGetCachedQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation) { + std::ostringstream cacheKeyStream; + cacheKeyStream << "OurRank_" << categoryName << "_" << static_cast(entriesPerPage) << "_" << playerGUID << "_" << vocation; + std::string cacheKey = cacheKeyStream.str(); + + std::string cachedQuery = getCachedQueryHighscore(cacheKey); + if (!cachedQuery.empty()) { + return cachedQuery; + } + + std::string newQuery = generateHighscoreQueryForOurRank(categoryName, entriesPerPage, playerGUID, vocation); + cacheQueryHighscore(cacheKey, newQuery); + + return newQuery; +} + void Game::playerHighscores(std::shared_ptr player, HighscoreType_t type, uint8_t category, uint32_t vocation, const std::string &, uint16_t page, uint8_t entriesPerPage) { if (player->hasAsyncOngoingTask(PlayerAsyncTask_Highscore)) { return; @@ -7967,83 +8118,19 @@ void Game::playerHighscores(std::shared_ptr player, HighscoreType_t type } } - std::ostringstream query; + std::string query; if (type == HIGHSCORE_GETENTRIES) { - uint32_t startPage = (static_cast(page - 1) * static_cast(entriesPerPage)); - uint32_t endPage = startPage + static_cast(entriesPerPage); - query << "SELECT *, @row AS `entries`, " << page << " AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn` FROM (SELECT `id`, `name`, `level`, `vocation`, `" << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0) `r` WHERE `group_id` < " << static_cast(account::GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`"; - if (vocation != 0xFFFFFFFF) { - bool firstVocation = true; - - const auto vocationsMap = g_vocations().getVocations(); - for (const auto &it : vocationsMap) { - const Vocation &voc = it.second; - if (voc.getFromVocation() == vocation) { - if (firstVocation) { - query << " WHERE `vocation` = " << voc.getId(); - firstVocation = false; - } else { - query << " OR `vocation` = " << voc.getId(); - } - } - } - } - query << ") `T` WHERE `rn` > " << startPage << " AND `rn` <= " << endPage; + query = generateHighscoreOrGetCachedQueryForEntries(categoryName, page, entriesPerPage, vocation); } else if (type == HIGHSCORE_OURRANK) { - std::string entriesStr = std::to_string(entriesPerPage); - query << "SELECT *, @row AS `entries`, (@ourRow DIV " << entriesStr << ") + 1 AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn`, @ourRow := IF(`id` = " << player->getGUID() << ", @row - 1, @ourRow) AS `rw` FROM (SELECT `id`, `name`, `level`, `vocation`, `" << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0, @ourRow := 0) `r` WHERE `group_id` < " << static_cast(account::GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`"; - if (vocation != 0xFFFFFFFF) { - bool firstVocation = true; - - const auto vocationsMap = g_vocations().getVocations(); - for (const auto &it : vocationsMap) { - const Vocation &voc = it.second; - if (voc.getFromVocation() == vocation) { - if (firstVocation) { - query << " WHERE `vocation` = " << voc.getId(); - firstVocation = false; - } else { - query << " OR `vocation` = " << voc.getId(); - } - } - } - } - query << ") `T` WHERE `rn` > ((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") AND `rn` <= (((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") + " << entriesStr << ")"; + query = generateHighscoreOrGetCachedQueryForOurRank(categoryName, entriesPerPage, player->getGUID(), vocation); } uint32_t playerID = player->getID(); - std::function callback = [playerID, category, vocation, entriesPerPage](DBResult_ptr result, bool) { - std::shared_ptr player = g_game().getPlayerByID(playerID); - if (!player) { - return; - } - - player->resetAsyncOngoingTask(PlayerAsyncTask_Highscore); - if (!result) { - player->sendHighscoresNoData(); - return; - } - - uint16_t page = result->getNumber("page"); - uint32_t pages = result->getNumber("entries"); - pages += entriesPerPage - 1; - pages /= entriesPerPage; - - std::vector characters; - characters.reserve(result->countResults()); - do { - uint8_t characterVocation; - const Vocation* voc = g_vocations().getVocation(result->getNumber("vocation")); - if (voc) { - characterVocation = voc->getClientId(); - } else { - characterVocation = 0; - } - characters.emplace_back(std::move(result->getString("name")), result->getNumber("points"), result->getNumber("id"), result->getNumber("rank"), result->getNumber("level"), characterVocation); - } while (result->next()); - player->sendHighscores(characters, category, vocation, page, static_cast(pages)); + std::function callback = [this, playerID, category, vocation, entriesPerPage](DBResult_ptr result, bool) { + processHighscoreResults(result, playerID, category, vocation, entriesPerPage); }; - g_databaseTasks().store(query.str(), callback); + + g_databaseTasks().store(query, callback); player->addAsyncOngoingTask(PlayerAsyncTask_Highscore); } diff --git a/src/game/game.hpp b/src/game/game.hpp index 583862ccee4..0753eca000d 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -44,6 +44,19 @@ static constexpr int32_t EVENT_DECAY_BUCKETS = 4; static constexpr int32_t EVENT_FORGEABLEMONSTERCHECKINTERVAL = 300000; static constexpr int32_t EVENT_LUA_GARBAGE_COLLECTION = 60000 * 10; // 10min +static constexpr std::chrono::minutes CACHE_EXPIRATION_TIME { 10 }; // 10min +static constexpr std::chrono::minutes HIGHSCORE_CACHE_EXPIRATION_TIME { 10 }; // 10min + +struct QueryHighscoreCacheEntry { + std::string query; + std::chrono::time_point timestamp; +}; + +struct HighscoreCacheEntry { + std::vector characters; + std::chrono::time_point timestamp; +}; + class Game { public: Game(); @@ -752,6 +765,9 @@ class Game { */ ReturnValue collectRewardChestItems(std::shared_ptr player, uint32_t maxMoveItems = 0); + phmap::flat_hash_map queryCache; + phmap::flat_hash_map highscoreCache; + phmap::flat_hash_map> m_uniqueLoginPlayerNames; phmap::flat_hash_map> players; phmap::flat_hash_map> mappedPlayerNames; @@ -874,6 +890,16 @@ class Game { // Variable members (m_) std::unique_ptr m_IOWheel; + + void cacheQueryHighscore(const std::string &key, const std::string &query); + void processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage); + + std::string getCachedQueryHighscore(const std::string &key); + std::string generateVocationConditionHighscore(uint32_t vocation); + std::string generateHighscoreQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); + std::string generateHighscoreQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation); + std::string generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); + std::string generateHighscoreOrGetCachedQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation); }; constexpr auto g_game = Game::getInstance; From 8179d215bbaf86792a998913d0e671482ae86bdb Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Mon, 2 Oct 2023 17:51:45 -0700 Subject: [PATCH 05/18] fix: safer zone refreshes (#1657) On the rare occasion when a zone has an invalid Tile in it, getting an items vector can return null. This adds a check around it to avoid potential crashes. --- src/game/zones/zone.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/game/zones/zone.cpp b/src/game/zones/zone.cpp index a000c45d524..e06975ff7a6 100644 --- a/src/game/zones/zone.cpp +++ b/src/game/zones/zone.cpp @@ -35,16 +35,8 @@ std::shared_ptr Zone::addZone(const std::string &name) { void Zone::addArea(Area area) { for (const Position &pos : area) { positions.insert(pos); - std::shared_ptr tile = g_game().map.getTile(pos); - if (tile) { - for (auto item : *tile->getItemList()) { - itemAdded(item); - } - for (auto creature : *tile->getCreatures()) { - creatureAdded(creature); - } - } } + refresh(); } void Zone::subtractArea(Area area) { @@ -281,10 +273,18 @@ void Zone::refresh() { if (!tile) { continue; } - for (const auto &item : *tile->getItemList()) { + const auto &items = tile->getItemList(); + if (!items) { + continue; + } + for (const auto &item : *items) { itemAdded(item); } - for (const auto &creature : *tile->getCreatures()) { + const auto &creatures = tile->getCreatures(); + if (!creatures) { + continue; + } + for (const auto &creature : *creatures) { creatureAdded(creature); } } From 3ef53077f7da831ee45667c5a44ba6090dadf55c Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Mon, 2 Oct 2023 19:15:49 -0700 Subject: [PATCH 06/18] fix: scoped KV, save KV to DB, boolean vals (#1661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A previous PR, optimizing saves, ended up canceling out saves here. Also: • Fixed nested scopes (and added a unit test) • Added boolean as a possible type for the KV values • Simplified the logic inside KV by removing a lot of templates and made scopes lighter weight with a simpler hierarchy. --- CMakePresets.json | 2 +- src/creatures/players/player.hpp | 4 +- src/items/item.cpp | 1 - src/kv/README.md | 4 +- src/kv/kv.cpp | 7 +- src/kv/kv.hpp | 112 ++++++++---------- src/kv/kv_sql.cpp | 13 +- src/kv/kv_sql.hpp | 6 +- src/kv/value_wrapper.cpp | 3 + src/kv/value_wrapper.hpp | 8 +- src/kv/value_wrapper_proto.hpp | 9 ++ src/lua/functions/core/libs/kv_functions.cpp | 29 +++-- src/lua/functions/core/libs/kv_functions.hpp | 8 +- .../creatures/player/player_functions.cpp | 4 +- src/lua/functions/lua_functions_loader.hpp | 2 +- src/protobuf/kv.pb.cc | 55 +++++++-- src/protobuf/kv.pb.h | 55 +++++++++ src/protobuf/kv.proto | 1 + tests/unit/kv/kv_test.cpp | 64 +++++++--- 19 files changed, 261 insertions(+), 126 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 35ba674b484..0dbcece6773 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -28,7 +28,7 @@ }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { - "hostOS": [ "Windows" ] + "hostOS": ["Windows"] } }, "condition": { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index a70df45b3c0..620d38ffae9 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -471,8 +471,8 @@ class Player final : public Creature, public Cylinder, public Bankable { int32_t getStorageValueByName(const std::string &storageName) const; void addStorageValueByName(const std::string &storageName, const int32_t value, const bool isLogin = false); - std::shared_ptr kv() const { - return g_kv().scoped("player")->scoped(getID()); + std::shared_ptr kv() const { + return g_kv().scoped("player")->scoped(fmt::format("{}", getID())); } void genReservedStorageRange(); diff --git a/src/items/item.cpp b/src/items/item.cpp index 9d185eacfbc..ea04be89385 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -281,7 +281,6 @@ void Item::onRemoved() { } void Item::setID(uint16_t newid) { - g_logger().debug("[Item::setID] - Setting item id from {} to {}", id, newid); const ItemType &prevIt = Item::items[id]; id = newid; diff --git a/src/kv/README.md b/src/kv/README.md index 6c2349572c7..11aa54611ff 100644 --- a/src/kv/README.md +++ b/src/kv/README.md @@ -21,10 +21,10 @@ The Canary KV Library is designed to offer a simple, efficient, persistent, and #include // In your class constructor -MyClass(KVStore &kv) : kv(kv) {} +MyClass(KV &kv) : kv(kv) {} // Or use the global singleton -KVStore &kv = g_kv(); +KV &kv = g_kv(); ``` ### Basic Usage diff --git a/src/kv/kv.cpp b/src/kv/kv.cpp index c71767a1517..cd2a8b974b5 100644 --- a/src/kv/kv.cpp +++ b/src/kv/kv.cpp @@ -73,6 +73,11 @@ std::optional KVStore::get(const std::string &key, bool forceLoad return value; } -void KVStore::remove(const std::string &key) { +void KV::remove(const std::string &key) { set(key, ValueWrapper::deleted()); } + +std::shared_ptr KVStore::scoped(const std::string &scope) { + logger.debug("KVStore::scoped({})", scope); + return std::make_shared(logger, *this, scope); +} diff --git a/src/kv/kv.hpp b/src/kv/kv.hpp index 674df87449e..40fe449834f 100644 --- a/src/kv/kv.hpp +++ b/src/kv/kv.hpp @@ -18,43 +18,49 @@ #include "lib/logging/logger.hpp" #include "kv/value_wrapper.hpp" -class KVStore { +class KV : public std::enable_shared_from_this { public: - static constexpr size_t MAX_SIZE = 10000; - - static KVStore &getInstance(); + virtual void set(const std::string &key, const std::initializer_list &init_list) = 0; + virtual void set(const std::string &key, const std::initializer_list> &init_list) = 0; + virtual void set(const std::string &key, const ValueWrapper &value) = 0; - explicit KVStore(Logger &logger) : - logger(logger) { } - virtual ~KVStore() = default; + virtual std::optional get(const std::string &key, bool forceLoad = false) = 0; - template - void set(const std::string &key, const std::vector &vec); - virtual void set(const std::string &key, const std::initializer_list &init_list); - virtual void set(const std::string &key, const std::initializer_list> &init_list); - virtual void set(const std::string &key, const ValueWrapper &value); + virtual bool saveAll() { + return true; + } - virtual std::optional get(const std::string &key, bool forceLoad = false); + virtual std::shared_ptr scoped(const std::string &scope) = 0; void remove(const std::string &key); - template - T get(const std::string &key, bool forceLoad = false); - - virtual bool saveAll() { - return true; + virtual void flush() { + saveAll(); } +}; - template - std::shared_ptr scoped(const T &scope); +class KVStore : public KV { +public: + static constexpr size_t MAX_SIZE = 10000; + static KVStore &getInstance(); + + explicit KVStore(Logger &logger) : + logger(logger) { } - friend class ScopedKV; + void set(const std::string &key, const std::initializer_list &init_list) override; + void set(const std::string &key, const std::initializer_list> &init_list) override; + void set(const std::string &key, const ValueWrapper &value) override; - void flush() { - saveAll(); + std::optional get(const std::string &key, bool forceLoad = false) override; + + void flush() override { + std::scoped_lock lock(mutex_); + KV::flush(); store_.clear(); } + std::shared_ptr scoped(const std::string &scope) override final; + protected: phmap::parallel_flat_hash_map::iterator>> getStore() { std::scoped_lock lock(mutex_); @@ -64,11 +70,13 @@ class KVStore { } return copy; } - virtual std::optional load(const std::string &key) = 0; - virtual bool save(const std::string &key, const ValueWrapper &value) = 0; +protected: Logger &logger; + virtual std::optional load(const std::string &key) = 0; + virtual bool save(const std::string &key, const ValueWrapper &value) = 0; + private: void setLocked(const std::string &key, const ValueWrapper &value); @@ -77,42 +85,23 @@ class KVStore { std::mutex mutex_; }; -template -void KVStore::set(const std::string &key, const std::vector &vec) { - ValueWrapper wrapped(vec); - set(key, wrapped); -} - -template -T KVStore::get(const std::string &key, bool forceLoad /*= false */) { - auto optValue = get(key, forceLoad); - if (optValue.has_value()) { - return optValue->get(); - } - return T {}; -} - -class ScopedKV final : public KVStore { +class ScopedKV final : public KV { public: - ScopedKV(KVStore &parentKV, const std::string &prefix) : - KVStore(parentKV.logger), parentKV_(parentKV), prefix_(prefix) { } + ScopedKV(Logger &logger, KVStore &rootKV, const std::string &prefix) : + logger(logger), rootKV_(rootKV), prefix_(prefix) { } - template - void set(const std::string &key, const std::vector &vec) { - parentKV_.set(buildKey(key), vec); - } void set(const std::string &key, const std::initializer_list &init_list) override { - parentKV_.set(buildKey(key), init_list); + rootKV_.set(buildKey(key), init_list); } void set(const std::string &key, const std::initializer_list> &init_list) override { - parentKV_.set(buildKey(key), init_list); + rootKV_.set(buildKey(key), init_list); } void set(const std::string &key, const ValueWrapper &value) override { - parentKV_.set(buildKey(key), value); + rootKV_.set(buildKey(key), value); } std::optional get(const std::string &key, bool forceLoad = false) override { - return parentKV_.get(buildKey(key), forceLoad); + return rootKV_.get(buildKey(key), forceLoad); } template @@ -125,29 +114,22 @@ class ScopedKV final : public KVStore { } bool saveAll() override { - return parentKV_.saveAll(); + return rootKV_.saveAll(); } -protected: - std::optional load(const std::string &key) override { - return parentKV_.load(buildKey(key)); - } - bool save(const std::string &key, const ValueWrapper &value) override { - return parentKV_.save(buildKey(key), value); + std::shared_ptr scoped(const std::string &scope) override final { + logger.debug("ScopedKV::scoped({})", buildKey(scope)); + return std::make_shared(logger, rootKV_, buildKey(scope)); } private: std::string buildKey(const std::string &key) const { - return prefix_ + "." + key; + return fmt::format("{}.{}", prefix_, key); } - KVStore &parentKV_; + Logger &logger; + KVStore &rootKV_; std::string prefix_; }; -template -std::shared_ptr KVStore::scoped(const T &scope) { - return std::make_shared(*this, fmt::format("{}", scope)); -} - constexpr auto g_kv = KVStore::getInstance; diff --git a/src/kv/kv_sql.cpp b/src/kv/kv_sql.cpp index f967b50609e..a0f1623ed2c 100644 --- a/src/kv/kv_sql.cpp +++ b/src/kv/kv_sql.cpp @@ -58,8 +58,7 @@ bool KVSQL::prepareSave(const std::string &key, const ValueWrapper &value, DBIns return db.executeQuery(query); } - update.upsert({ "timestamp", "value" }); - update.addRow(fmt::format("({}, {}, {})", db.escapeString(key), getTimeMsNow(), db.escapeString(data))); + update.addRow(fmt::format("{}, {}, {}", db.escapeString(key), getTimeMsNow(), db.escapeString(data))); return true; } @@ -67,10 +66,12 @@ bool KVSQL::saveAll() { auto store = getStore(); bool success = DBTransaction::executeWithinTransaction([this, &store]() { auto update = dbUpdate(); - return std::ranges::all_of(store, [this, &update](const auto &kv) { - const auto &[key, value] = kv; - return prepareSave(key, value.first, update); - }); + if (!std::ranges::all_of(store, [this, &update](const auto &kv) { + const auto &[key, value] = kv; + return prepareSave(key, value.first, update); + })) { + return false; + } return update.execute(); }); diff --git a/src/kv/kv_sql.hpp b/src/kv/kv_sql.hpp index 8f107a20954..8cb4dce89f7 100644 --- a/src/kv/kv_sql.hpp +++ b/src/kv/kv_sql.hpp @@ -29,8 +29,10 @@ class KVSQL final : public KVStore { bool save(const std::string &key, const ValueWrapper &value) override; bool prepareSave(const std::string &key, const ValueWrapper &value, DBInsert &update); - DBInsert dbUpdate() const { - return DBInsert("INSERT INTO `kv_store` (`key_name`, `timestamp`, `value`) VALUES"); + DBInsert dbUpdate() { + auto insert = DBInsert("INSERT INTO `kv_store` (`key_name`, `timestamp`, `value`) VALUES"); + insert.upsert({ "key_name", "timestamp", "value" }); + return insert; } Database &db; diff --git a/src/kv/value_wrapper.cpp b/src/kv/value_wrapper.cpp index a88930a8c84..023beba1595 100644 --- a/src/kv/value_wrapper.cpp +++ b/src/kv/value_wrapper.cpp @@ -9,6 +9,9 @@ ValueWrapper::ValueWrapper(const ValueVariant &value, uint64_t timestamp) : ValueWrapper::ValueWrapper(const std::string &value, uint64_t timestamp) : data_(value), timestamp_(timestamp) { } +ValueWrapper::ValueWrapper(bool value, uint64_t timestamp) : + data_(value), timestamp_(timestamp) { } + ValueWrapper::ValueWrapper(int value, uint64_t timestamp) : data_(value), timestamp_(timestamp) { } diff --git a/src/kv/value_wrapper.hpp b/src/kv/value_wrapper.hpp index 11c3c588dfb..9317eb6b8fd 100644 --- a/src/kv/value_wrapper.hpp +++ b/src/kv/value_wrapper.hpp @@ -22,18 +22,20 @@ class ValueWrapper; using StringType = std::string; +using BooleanType = bool; using IntType = int; using DoubleType = double; using ArrayType = std::vector; using MapType = phmap::flat_hash_map>; -using ValueVariant = std::variant; +using ValueVariant = std::variant; class ValueWrapper { public: explicit ValueWrapper(uint64_t timestamp = 0); explicit(false) ValueWrapper(const ValueVariant &value, uint64_t timestamp = 0); explicit(false) ValueWrapper(const std::string &value, uint64_t timestamp = 0); + explicit(false) ValueWrapper(bool value, uint64_t timestamp = 0); explicit(false) ValueWrapper(int value, uint64_t timestamp = 0); explicit(false) ValueWrapper(double value, uint64_t timestamp = 0); explicit(false) ValueWrapper(const phmap::flat_hash_map &value, uint64_t timestamp = 0); @@ -85,6 +87,10 @@ class ValueWrapper { return get(); } + explicit(false) operator bool() const { + return get(); + } + explicit(false) operator int() const { return get(); } diff --git a/src/kv/value_wrapper_proto.hpp b/src/kv/value_wrapper_proto.hpp index 1d05de1f5e3..15802c543fa 100644 --- a/src/kv/value_wrapper_proto.hpp +++ b/src/kv/value_wrapper_proto.hpp @@ -31,6 +31,10 @@ namespace ProtoHelpers { protoValue.set_str_value(arg); } + void setProtoBooleanValue(Canary::protobuf::kv::ValueWrapper &protoValue, const BooleanType &arg) { + protoValue.set_bool_value(arg); + } + void setProtoIntValue(Canary::protobuf::kv::ValueWrapper &protoValue, const IntType &arg) { protoValue.set_int_value(arg); } @@ -64,6 +68,8 @@ inline Canary::protobuf::kv::ValueWrapper ProtoSerializable::toPro using T = std::decay_t; if constexpr (std::is_same_v) { ProtoHelpers::setProtoStringValue(protoValue, arg); + } else if constexpr (std::is_same_v) { + ProtoHelpers::setProtoBooleanValue(protoValue, arg); } else if constexpr (std::is_same_v) { ProtoHelpers::setProtoIntValue(protoValue, arg); } else if constexpr (std::is_same_v) { @@ -86,6 +92,9 @@ inline ValueWrapper ProtoSerializable::fromProto(const Canary::pro case Canary::protobuf::kv::ValueWrapper::kStrValue: data = protoValue.str_value(); break; + case Canary::protobuf::kv::ValueWrapper::kBoolValue: + data = protoValue.bool_value(); + break; case Canary::protobuf::kv::ValueWrapper::kIntValue: data = protoValue.int_value(); break; diff --git a/src/lua/functions/core/libs/kv_functions.cpp b/src/lua/functions/core/libs/kv_functions.cpp index 432cf3a506d..769c20bff8a 100644 --- a/src/lua/functions/core/libs/kv_functions.cpp +++ b/src/lua/functions/core/libs/kv_functions.cpp @@ -16,11 +16,20 @@ #include "lua/scripts/lua_environment.hpp" int KVFunctions::luaKVScoped(lua_State* L) { - // KV.scoped(key) + // KV.scoped(key) | KV:scoped(key) auto key = getString(L, -1); + + if (isUserdata(L, 1)) { + auto scopedKV = getUserdataShared(L, 1); + auto newScope = scopedKV->scoped(key); + pushUserdata(L, newScope); + setMetatable(L, -1, "KV"); + return 1; + } + auto scopedKV = g_kv().scoped(key); - pushUserdata(L, scopedKV); - setMetatable(L, -1, "KVStore"); + pushUserdata(L, scopedKV); + setMetatable(L, -1, "KV"); return 1; } @@ -36,7 +45,7 @@ int KVFunctions::luaKVSet(lua_State* L) { } if (isUserdata(L, 1)) { - auto scopedKV = getUserdata(L, 1); + auto scopedKV = getUserdataShared(L, 1); scopedKV->set(key, valueWrapper.value()); pushBoolean(L, true); return 1; @@ -57,7 +66,7 @@ int KVFunctions::luaKVGet(lua_State* L) { key = getString(L, -2); } if (isUserdata(L, 1)) { - auto scopedKV = getUserdata(L, 1); + auto scopedKV = getUserdataShared(L, 1); valueWrapper = scopedKV->get(key, forceLoad); } else { valueWrapper = g_kv().get(key, forceLoad); @@ -72,14 +81,14 @@ int KVFunctions::luaKVGet(lua_State* L) { } std::optional KVFunctions::getValueWrapper(lua_State* L) { - if (isString(L, -1)) { - return ValueWrapper(getString(L, -1)); + if (isBoolean(L, -1)) { + return ValueWrapper(getBoolean(L, -1)); } if (isNumber(L, -1)) { return ValueWrapper(getNumber(L, -1)); } - if (isBoolean(L, -1)) { - return ValueWrapper(getBoolean(L, -1)); + if (isString(L, -1)) { + return ValueWrapper(getString(L, -1)); } if (isTable(L, -1) && lua_objlen(L, -1) > 0) { @@ -154,6 +163,8 @@ void KVFunctions::pushValueWrapper(lua_State* L, const ValueWrapper &valueWrappe using T = std::decay_t; if constexpr (std::is_same_v) { pushStringValue(L, arg); + } else if constexpr (std::is_same_v) { + pushBoolean(L, arg); } else if constexpr (std::is_same_v) { pushIntValue(L, arg); } else if constexpr (std::is_same_v) { diff --git a/src/lua/functions/core/libs/kv_functions.hpp b/src/lua/functions/core/libs/kv_functions.hpp index 5d4c1829a06..243a0ed36e3 100644 --- a/src/lua/functions/core/libs/kv_functions.hpp +++ b/src/lua/functions/core/libs/kv_functions.hpp @@ -19,10 +19,10 @@ class KVFunctions final : LuaScriptInterface { registerMethod(L, "kv", "set", KVFunctions::luaKVSet); registerMethod(L, "kv", "get", KVFunctions::luaKVGet); - registerClass(L, "KVStore", ""); - registerMethod(L, "KVStore", "scoped", KVFunctions::luaKVScoped); - registerMethod(L, "KVStore", "set", KVFunctions::luaKVSet); - registerMethod(L, "KVStore", "get", KVFunctions::luaKVGet); + registerClass(L, "KV", ""); + registerMethod(L, "KV", "scoped", KVFunctions::luaKVScoped); + registerMethod(L, "KV", "set", KVFunctions::luaKVSet); + registerMethod(L, "KV", "get", KVFunctions::luaKVGet); } private: diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index a69891a9369..eebf081e2bc 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -4089,7 +4089,7 @@ int PlayerFunctions::luaPlayerKV(lua_State* L) { return 1; } - pushUserdata(L, player->kv()); - setMetatable(L, -1, "KVStore"); + pushUserdata(L, player->kv()); + setMetatable(L, -1, "KV"); return 1; } diff --git a/src/lua/functions/lua_functions_loader.hpp b/src/lua/functions/lua_functions_loader.hpp index 3ae8e3ec15b..49fe5e6246e 100644 --- a/src/lua/functions/lua_functions_loader.hpp +++ b/src/lua/functions/lua_functions_loader.hpp @@ -24,7 +24,7 @@ class Player; class Thing; class Guild; class Zone; -class KVStore; +class KV; #define reportErrorFunc(a) reportError(__FUNCTION__, a, true) diff --git a/src/protobuf/kv.pb.cc b/src/protobuf/kv.pb.cc index bde2e92a31b..74cbaece407 100644 --- a/src/protobuf/kv.pb.cc +++ b/src/protobuf/kv.pb.cc @@ -96,6 +96,7 @@ const uint32_t TableStruct_kv_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(proto ::_pbi::kInvalidFieldOffsetTag, ::_pbi::kInvalidFieldOffsetTag, ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, PROTOBUF_FIELD_OFFSET(::Canary::protobuf::kv::ValueWrapper, _impl_.value_), ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::Canary::protobuf::kv::ArrayType, _internal_metadata_), @@ -122,9 +123,9 @@ const uint32_t TableStruct_kv_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(proto }; static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = { { 0, -1, -1, sizeof(::Canary::protobuf::kv::ValueWrapper)}, - { 12, -1, -1, sizeof(::Canary::protobuf::kv::ArrayType)}, - { 19, -1, -1, sizeof(::Canary::protobuf::kv::KeyValuePair)}, - { 27, -1, -1, sizeof(::Canary::protobuf::kv::MapType)}, + { 13, -1, -1, sizeof(::Canary::protobuf::kv::ArrayType)}, + { 20, -1, -1, sizeof(::Canary::protobuf::kv::KeyValuePair)}, + { 28, -1, -1, sizeof(::Canary::protobuf::kv::MapType)}, }; static const ::_pb::Message* const file_default_instances[] = { @@ -135,21 +136,22 @@ static const ::_pb::Message* const file_default_instances[] = { }; const char descriptor_table_protodef_kv_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = - "\n\010kv.proto\022\022Canary.protobuf.kv\"\301\001\n\014Value" + "\n\010kv.proto\022\022Canary.protobuf.kv\"\327\001\n\014Value" "Wrapper\022\023\n\tstr_value\030\001 \001(\tH\000\022\023\n\tint_valu" "e\030\002 \001(\005H\000\022\026\n\014double_value\030\003 \001(\001H\000\0224\n\013arr" "ay_value\030\004 \001(\0132\035.Canary.protobuf.kv.Arra" "yTypeH\000\0220\n\tmap_value\030\005 \001(\0132\033.Canary.prot" - "obuf.kv.MapTypeH\000B\007\n\005value\"=\n\tArrayType\022" - "0\n\006values\030\001 \003(\0132 .Canary.protobuf.kv.Val" - "ueWrapper\"L\n\014KeyValuePair\022\013\n\003key\030\001 \001(\t\022/" - "\n\005value\030\002 \001(\0132 .Canary.protobuf.kv.Value" - "Wrapper\":\n\007MapType\022/\n\005items\030\001 \003(\0132 .Cana" - "ry.protobuf.kv.KeyValuePairb\006proto3" + "obuf.kv.MapTypeH\000\022\024\n\nbool_value\030\006 \001(\010H\000B" + "\007\n\005value\"=\n\tArrayType\0220\n\006values\030\001 \003(\0132 ." + "Canary.protobuf.kv.ValueWrapper\"L\n\014KeyVa" + "luePair\022\013\n\003key\030\001 \001(\t\022/\n\005value\030\002 \001(\0132 .Ca" + "nary.protobuf.kv.ValueWrapper\":\n\007MapType" + "\022/\n\005items\030\001 \003(\0132 .Canary.protobuf.kv.Key" + "ValuePairb\006proto3" ; static ::_pbi::once_flag descriptor_table_kv_2eproto_once; const ::_pbi::DescriptorTable descriptor_table_kv_2eproto = { - false, false, 435, descriptor_table_protodef_kv_2eproto, + false, false, 457, descriptor_table_protodef_kv_2eproto, "kv.proto", &descriptor_table_kv_2eproto_once, nullptr, 0, 4, schemas, file_default_instances, TableStruct_kv_2eproto::offsets, @@ -251,6 +253,10 @@ ValueWrapper::ValueWrapper(const ValueWrapper& from) from._internal_map_value()); break; } + case kBoolValue: { + _this->_internal_set_bool_value(from._internal_bool_value()); + break; + } case VALUE_NOT_SET: { break; } @@ -317,6 +323,10 @@ void ValueWrapper::clear_value() { } break; } + case kBoolValue: { + // No need to clear + break; + } case VALUE_NOT_SET: { break; } @@ -383,6 +393,14 @@ const char* ValueWrapper::_InternalParse(const char* ptr, ::_pbi::ParseContext* } else goto handle_unusual; continue; + // bool bool_value = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _internal_set_bool_value(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr)); + CHK_(ptr); + } else + goto handle_unusual; + continue; default: goto handle_unusual; } // switch @@ -448,6 +466,12 @@ uint8_t* ValueWrapper::_InternalSerialize( _Internal::map_value(this).GetCachedSize(), target, stream); } + // bool bool_value = 6; + if (_internal_has_bool_value()) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(6, this->_internal_bool_value(), target); + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); @@ -496,6 +520,11 @@ size_t ValueWrapper::ByteSizeLong() const { *_impl_.value_.map_value_); break; } + // bool bool_value = 6; + case kBoolValue: { + total_size += 1 + 1; + break; + } case VALUE_NOT_SET: { break; } @@ -541,6 +570,10 @@ void ValueWrapper::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::P from._internal_map_value()); break; } + case kBoolValue: { + _this->_internal_set_bool_value(from._internal_bool_value()); + break; + } case VALUE_NOT_SET: { break; } diff --git a/src/protobuf/kv.pb.h b/src/protobuf/kv.pb.h index c1688e4210f..c40c0a46502 100644 --- a/src/protobuf/kv.pb.h +++ b/src/protobuf/kv.pb.h @@ -123,6 +123,7 @@ class ValueWrapper final : kDoubleValue = 3, kArrayValue = 4, kMapValue = 5, + kBoolValue = 6, VALUE_NOT_SET = 0, }; @@ -209,6 +210,7 @@ class ValueWrapper final : kDoubleValueFieldNumber = 3, kArrayValueFieldNumber = 4, kMapValueFieldNumber = 5, + kBoolValueFieldNumber = 6, }; // string str_value = 1; bool has_str_value() const; @@ -290,6 +292,19 @@ class ValueWrapper final : ::Canary::protobuf::kv::MapType* map_value); ::Canary::protobuf::kv::MapType* unsafe_arena_release_map_value(); + // bool bool_value = 6; + bool has_bool_value() const; + private: + bool _internal_has_bool_value() const; + public: + void clear_bool_value(); + bool bool_value() const; + void set_bool_value(bool value); + private: + bool _internal_bool_value() const; + void _internal_set_bool_value(bool value); + public: + void clear_value(); ValueCase value_case() const; // @@protoc_insertion_point(class_scope:Canary.protobuf.kv.ValueWrapper) @@ -300,6 +315,7 @@ class ValueWrapper final : void set_has_double_value(); void set_has_array_value(); void set_has_map_value(); + void set_has_bool_value(); inline bool has_value() const; inline void clear_has_value(); @@ -316,6 +332,7 @@ class ValueWrapper final : double double_value_; ::Canary::protobuf::kv::ArrayType* array_value_; ::Canary::protobuf::kv::MapType* map_value_; + bool bool_value_; } value_; mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; uint32_t _oneof_case_[1]; @@ -1123,6 +1140,44 @@ inline ::Canary::protobuf::kv::MapType* ValueWrapper::mutable_map_value() { return _msg; } +// bool bool_value = 6; +inline bool ValueWrapper::_internal_has_bool_value() const { + return value_case() == kBoolValue; +} +inline bool ValueWrapper::has_bool_value() const { + return _internal_has_bool_value(); +} +inline void ValueWrapper::set_has_bool_value() { + _impl_._oneof_case_[0] = kBoolValue; +} +inline void ValueWrapper::clear_bool_value() { + if (_internal_has_bool_value()) { + _impl_.value_.bool_value_ = false; + clear_has_value(); + } +} +inline bool ValueWrapper::_internal_bool_value() const { + if (_internal_has_bool_value()) { + return _impl_.value_.bool_value_; + } + return false; +} +inline void ValueWrapper::_internal_set_bool_value(bool value) { + if (!_internal_has_bool_value()) { + clear_value(); + set_has_bool_value(); + } + _impl_.value_.bool_value_ = value; +} +inline bool ValueWrapper::bool_value() const { + // @@protoc_insertion_point(field_get:Canary.protobuf.kv.ValueWrapper.bool_value) + return _internal_bool_value(); +} +inline void ValueWrapper::set_bool_value(bool value) { + _internal_set_bool_value(value); + // @@protoc_insertion_point(field_set:Canary.protobuf.kv.ValueWrapper.bool_value) +} + inline bool ValueWrapper::has_value() const { return value_case() != VALUE_NOT_SET; } diff --git a/src/protobuf/kv.proto b/src/protobuf/kv.proto index e9f9bfc9d9e..7fe99e609a7 100644 --- a/src/protobuf/kv.proto +++ b/src/protobuf/kv.proto @@ -11,6 +11,7 @@ message ValueWrapper { double double_value = 3; ArrayType array_value = 4; MapType map_value = 5; + bool bool_value = 6; } } diff --git a/tests/unit/kv/kv_test.cpp b/tests/unit/kv/kv_test.cpp index 2e6b2c06c68..15efd57cf72 100644 --- a/tests/unit/kv/kv_test.cpp +++ b/tests/unit/kv/kv_test.cpp @@ -20,26 +20,32 @@ suite<"kv"> kvTest = [] { test("Set and get integer value") = [&injectionFixture] { auto [kv] = injectionFixture.get(); kv.set("keyInt", 42); - expect(eq(kv.get("keyInt"), 42)); + expect(eq(kv.get("keyInt")->get(), 42)); + }; + + test("Set and get boolean value") = [&injectionFixture] { + auto [kv] = injectionFixture.get(); + kv.set("keyBool", true); + expect(eq(kv.get("keyBool")->get(), true)); }; test("Set and get string value") = [&injectionFixture] { auto [kv] = injectionFixture.get(); kv.set("keyString", std::string("hello")); - expect(eq(kv.get("keyString"), std::string("hello"))); + expect(eq(kv.get("keyString")->get(), std::string("hello"))); }; test("Set and get double value") = [&injectionFixture] { auto [kv] = injectionFixture.get(); kv.set("keyDouble", 3.14); - expect(eq(kv.get("keyDouble"), 3.14)); + expect(eq(kv.get("keyDouble")->get(), 3.14)); }; test("Overwrite existing key") = [&injectionFixture] { auto [kv] = injectionFixture.get(); kv.set("keyInt", 42); kv.set("keyInt", 43); - expect(eq(kv.get("keyInt"), 43)); + expect(eq(kv.get("keyInt")->get(), 43)); }; test("Multiple key-value pairs") = [&injectionFixture] { @@ -47,54 +53,76 @@ suite<"kv"> kvTest = [] { kv.set("key1", 1); kv.set("key2", 2); kv.set("key3", 3); - expect(eq(kv.get("key1"), 1)); - expect(eq(kv.get("key2"), 2)); - expect(eq(kv.get("key3"), 3)); + expect(eq(kv.get("key1")->get(), 1)); + expect(eq(kv.get("key2")->get(), 2)); + expect(eq(kv.get("key3")->get(), 3)); }; test("non-existant key") = [&injectionFixture] { auto [kv] = injectionFixture.get(); expect(!kv.get("non-existant").has_value()); - // default values - expect(eq(kv.get("non-existant"), 0)); - expect(eq(kv.get("non-existant"), std::string(""))); - expect(kv.get("non-existant").empty()); - expect(kv.get("non-existant").empty()); + expect(!kv.get("non-existant2").has_value()); }; test("Set and get MapType value") = [&injectionFixture] { auto [kv] = injectionFixture.get(); ValueWrapper mapValue { { "key1", 1 }, { "key2", 2 } }; kv.set("keyMap", mapValue); - expect(eq(kv.get("keyMap"), mapValue.getVariant())); + expect(eq(kv.get("keyMap")->get(), mapValue.getVariant())); }; test("Set and get ArrayType value") = [&injectionFixture] { auto [kv] = injectionFixture.get(); ValueWrapper arrayValue(ArrayType({ 1, 2, 3 })); kv.set("keyArray", arrayValue); - expect(eq(kv.get("keyArray"), arrayValue.getVariant())); + expect(eq(kv.get("keyArray")->get(), arrayValue.getVariant())); }; test("Mixed types in MapType") = [&injectionFixture] { auto [kv] = injectionFixture.get(); ValueWrapper mixedMap { { "keyInt", 1 }, { "keyString", std::string("hello") }, { "keyDouble", 3.14 } }; kv.set("keyMixedMap", mixedMap); - expect(eq(kv.get("keyMixedMap"), mixedMap.getVariant())); + expect(eq(kv.get("keyMixedMap")->get(), mixedMap.getVariant())); }; test("Nested MapType and ArrayType") = [&injectionFixture] { auto [kv] = injectionFixture.get(); ValueWrapper nestedMap { { "keyArray", ValueWrapper { 1, 2 } }, { "keyInnerMap", ValueWrapper { { "key3", 3 } } } }; kv.set("keyNested", nestedMap); - expect(eq(kv.get("keyNested"), nestedMap.getVariant())); + expect(eq(kv.get("keyNested")->get(), nestedMap.getVariant())); }; test("Scoped KV") = [&injectionFixture] { auto [kv] = injectionFixture.get(); auto scoped = kv.scoped("scope-name"); scoped->set("key1", 1); - expect(eq(scoped->get("key1"), 1)); - expect(eq(kv.get("scope-name.key1"), 1)); + expect(eq(scoped->get("key1")->get(), 1)); + expect(eq(kv.get("scope-name.key1")->get(), 1)); + }; + + test("Nested Scoped KV") = [&injectionFixture] { + auto [kv] = injectionFixture.get(); + auto scoped = kv.scoped("scope-name"); + auto nestedScoped = scoped->scoped("nested-scope-name"); + auto nestedScoped2 = nestedScoped->scoped("nested-scope-name2"); + nestedScoped2->set("key1", 1); + expect(eq(nestedScoped2->get("key1")->get(), 1)); + expect(eq(nestedScoped->get("nested-scope-name2.key1")->get(), 1)); + expect(eq(scoped->get("nested-scope-name.nested-scope-name2.key1")->get(), 1)); + expect(eq(kv.get("scope-name.nested-scope-name.nested-scope-name2.key1")->get(), 1)); }; + + test("Removing an element") + = [&injectionFixture] { + auto [kv] = injectionFixture.get(); + kv.set("key1", 1); + kv.set("key2", 2); + expect(eq(kv.get("key1")->get(), 1)); + expect(eq(kv.get("key2")->get(), 2)); + kv.set("key1", ValueWrapper::deleted()); + expect(!kv.get("key1").has_value()); + expect(eq(kv.get("key2")->get(), 2)); + kv.remove("key2"); + expect(!kv.get("key2").has_value()); + }; }; From 653648f6cfc9c783f618fd9468357e9621a7d796 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 2 Oct 2023 23:45:13 -0300 Subject: [PATCH 07/18] fix: remove save player function (#1667) --- src/io/functions/iologindata_load_player.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 0fa1cd86795..0b1cf0c1f3e 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -30,7 +30,6 @@ void IOLoginDataLoad::loadItems(ItemsMap &itemsMap, DBResult_ptr result, const s if (item) { if (!item->unserializeAttr(propStream)) { g_logger().warn("[{}] - Failed to deserialize item attributes {}, from player {}, from account id {}", __FUNCTION__, item->getID(), player->getName(), player->getAccountId()); - savePlayer(player); g_logger().info("[{}] - Deleting wrong item: {}", __FUNCTION__, item->getID()); continue; From 1e1fd0c0a008f0c8e892da3e47948a8ec3db6503 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 3 Oct 2023 16:48:19 -0300 Subject: [PATCH 08/18] refactor: spectators system (#1645) This refactoring consists of: 1- Improved caching algorithm 2- Applied pipeline concept 3- Use of vector_set instead of hashset. **code sample:** ```c++ // [OLD] SpectatorHashSet spectators; getSpectators(spectators, Position(x, y, z), ...); for(const auto spec: spectators) {} ``` ```c++ // [NEW] // Get All Creatures Spectators().find(Position(x, y, z), ...); // Get All Players Spectators().find(Position(x, y, z), ...); // Method Filter // Note: This method returns a new instance of spectators { auto spectators = Spectators().find(Position(x, y, z), ...); // Get All Npcs from spectators variable auto npcSpectators = spectators.filter(); // Get All Players from spectators variable auto playerSpectators = spectators.filter(); } // Pipeline Code { auto spectators = Spectators() .find({100, 200, 100}) .find({110, 210, 110}) .find({99, 101, 50}); for(const auto spec : spectators) {} for(const auto spec : spectators.filter()) {} for(const auto spec : spectators.filter()) {} } ``` **[BENCHMARK]** (Set SPECTATORS_USE_HASHSET if you want to benchmark with 'hashset'.)
Vector ![image](https://github.com/opentibiabr/canary/assets/2267386/a74564c9-ee99-4dc8-a290-c1420a394bf4) Hashset ![hashset_cache](https://github.com/opentibiabr/canary/assets/2267386/468a2c1e-bdf7-4c3b-8df7-efbed40b4ddf)
**[BENCHMARK CODE]**
```C++ void Map::moveCreature(Creature &creature, Tile &newTile, bool forceTeleport /* = false*/) { . . . auto spectators = Spectators() .find(oldPos, true) .find(newPos, true); const int64_t start = OTSYS_TIME(); for (int_fast32_t i = -1; ++i < 999999;) { for (const auto spec : Spectators() .find(oldPos, true) .find(newPos, true)) { } } g_logger().info("Benchmark std::vector({}) | cache enabled: {}ms", spectators.size(), static_cast(OTSYS_TIME() - start) / 1000.f); . . . } ```
--------- Co-authored-by: GitHub Actions Co-authored-by: Luan Santos --- src/creatures/combat/combat.cpp | 18 +- src/creatures/combat/combat.hpp | 2 +- src/creatures/combat/condition.cpp | 6 +- src/creatures/creature.cpp | 19 +- src/creatures/monsters/monster.cpp | 8 +- .../monsters/spawns/spawn_monster.cpp | 6 +- src/creatures/npcs/npc.cpp | 14 +- src/creatures/npcs/npc.hpp | 2 +- src/creatures/npcs/spawns/spawn_npc.cpp | 6 +- src/creatures/players/player.cpp | 41 +- src/creatures/players/player.hpp | 3 +- src/game/game.cpp | 461 ++++++------------ src/game/game.hpp | 19 +- src/items/containers/container.cpp | 22 +- src/items/containers/mailbox/mailbox.cpp | 9 +- src/items/tile.cpp | 66 ++- src/items/tile.hpp | 5 +- .../functions/core/game/game_functions.cpp | 29 +- .../creatures/creature_functions.cpp | 3 +- .../creatures/monster/monster_functions.cpp | 9 +- .../functions/creatures/npc/npc_functions.cpp | 6 +- .../creatures/player/player_functions.cpp | 7 +- src/lua/functions/map/position_functions.cpp | 22 +- src/map/CMakeLists.txt | 1 + src/map/map.cpp | 188 +------ src/map/map.hpp | 12 - src/map/spectators.cpp | 182 +++++++ src/map/spectators.hpp | 135 +++++ src/map/utils/qtreenode.cpp | 2 +- src/map/utils/qtreenode.hpp | 3 +- src/pch.hpp | 1 + src/utils/vectorset.hpp | 113 +++++ vcproj/canary.vcxproj | 3 + 33 files changed, 742 insertions(+), 681 deletions(-) create mode 100644 src/map/spectators.cpp create mode 100644 src/map/spectators.hpp create mode 100644 src/utils/vectorset.hpp diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 30b0208ad0a..4a8e6bd7060 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -18,6 +18,7 @@ #include "creatures/monsters/monster.hpp" #include "creatures/monsters/monsters.hpp" #include "items/weapons/weapons.hpp" +#include "map/spectators.hpp" int32_t Combat::getLevelFormula(std::shared_ptr player, const std::shared_ptr wheelSpell, const CombatDamage &damage) const { if (!player) { @@ -754,7 +755,7 @@ void Combat::CombatNullFunc(std::shared_ptr caster, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms) { +void Combat::combatTileEffects(const CreatureVector &spectators, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms) { if (params.itemId != 0) { uint16_t itemId = params.itemId; switch (itemId) { @@ -998,7 +999,6 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin getCombatArea(pos, pos, area, tileList); } - SpectatorHashSet spectators; uint32_t maxX = 0; uint32_t maxY = 0; @@ -1019,7 +1019,6 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin const int32_t rangeX = maxX + MAP_MAX_VIEW_PORT_X; const int32_t rangeY = maxY + MAP_MAX_VIEW_PORT_Y; - g_game().map.getSpectators(spectators, pos, true, true, rangeX, rangeX, rangeY, rangeY); int affected = 0; for (std::shared_ptr tile : tileList) { @@ -1071,6 +1070,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin } // Wheel of destiny get beam affected total + auto spectators = Spectators().find(pos, true, rangeX, rangeX, rangeY, rangeY); std::shared_ptr casterPlayer = caster ? caster->getPlayer() : nullptr; uint8_t beamAffectedTotal = casterPlayer ? casterPlayer->wheel()->getBeamAffectedTotal(tmpDamage) : 0; uint8_t beamAffectedCurrent = 0; @@ -1110,7 +1110,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin } } } - combatTileEffects(spectators, caster, tile, params); + combatTileEffects(spectators.data(), caster, tile, params); } // Wheel of destiny update beam mastery damage @@ -1339,11 +1339,10 @@ void Combat::doCombatDefault(std::shared_ptr caster, std::shared_ptr caster, std::shared_ptr target, const Position &origin, const CombatParams ¶ms) { if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target, params.aggressive) == RETURNVALUE_NOERROR)) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, target->getPosition(), true, true); + auto spectators = Spectators().find(target->getPosition(), true); CombatNullFunc(caster, target, params, nullptr); - combatTileEffects(spectators, caster, target->getTile(), params); + combatTileEffects(spectators.data(), caster, target->getTile(), params); if (params.targetCallback) { params.targetCallback->onTargetCombat(caster, target); @@ -1396,13 +1395,12 @@ std::vector>> Combat::pickChainTargets const int maxBacktrackingAttempts = 10; // Can be adjusted as needed while (!targets.empty() && targets.size() <= maxTargets) { auto currentTarget = targets.back(); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, currentTarget->getPosition(), false, false, chainDistance, chainDistance, chainDistance, chainDistance); + auto spectators = Spectators().find(currentTarget->getPosition(), false, chainDistance, chainDistance, chainDistance, chainDistance); g_logger().debug("Combat::pickChainTargets: currentTarget: {}, spectators: {}", currentTarget->getName(), spectators.size()); double closestDistance = std::numeric_limits::max(); std::shared_ptr closestSpectator = nullptr; - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { if (!spectator || visited.contains(spectator->getID())) { continue; } diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index 56096b9fd71..88c2db31312 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -368,7 +368,7 @@ class Combat { static void CombatDispelFunc(std::shared_ptr caster, std::shared_ptr target, const CombatParams ¶ms, CombatDamage* data); static void CombatNullFunc(std::shared_ptr caster, std::shared_ptr target, const CombatParams ¶ms, CombatDamage* data); - static void combatTileEffects(const SpectatorHashSet &spectators, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms); + static void combatTileEffects(const CreatureVector &spectators, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms); /** * @brief Calculate the level formula for combat. diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 8b58e6ff0fa..fc6ee1206f1 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -13,6 +13,7 @@ #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" #include "io/fileloader.hpp" +#include "map/spectators.hpp" /** * Condition @@ -1190,13 +1191,12 @@ bool ConditionRegeneration::executeCondition(std::shared_ptr creature, message.primary.color = TEXTCOLOR_PASTELRED; player->sendTextMessage(message); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, player->getPosition(), false, true); + auto spectators = Spectators().find(player->getPosition()); spectators.erase(player); if (!spectators.empty()) { message.type = MESSAGE_HEALED_OTHERS; message.text = player->getName() + " was healed for " + healString; - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->sendTextMessage(message); } } diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 5c01bb4e5a2..8d6eb19721b 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -16,6 +16,7 @@ #include "creatures/monsters/monster.hpp" #include "game/scheduling/scheduler.hpp" #include "game/zones/zone.hpp" +#include "map/spectators.hpp" double Creature::speedA = 857.36; double Creature::speedB = 261.29; @@ -1201,8 +1202,7 @@ void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr targ master->onGainExperience(gainExp, target); if (!m->isFamiliar()) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, false, true); + auto spectators = Spectators().find(position); if (spectators.empty()) { return; } @@ -1212,7 +1212,7 @@ void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr targ message.primary.color = TEXTCOLOR_WHITE_EXP; message.primary.value = gainExp; - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->sendTextMessage(message); } } @@ -1800,16 +1800,7 @@ void Creature::iconChanged() { return; } - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, tile->getPosition(), true); - for (auto spectator : spectators) { - if (!spectator) { - continue; - } - - auto player = spectator->getPlayer(); - if (player) { - player->sendCreatureIcon(getCreature()); - } + for (const auto &spectator : Spectators().find(tile->getPosition(), true)) { + spectator->getPlayer()->sendCreatureIcon(getCreature()); } } diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 1948c24581c..4ef06634933 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -17,6 +17,7 @@ #include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" +#include "map/spectators.hpp" int32_t Monster::despawnRange; int32_t Monster::despawnRadius; @@ -349,11 +350,8 @@ void Monster::updateTargetList() { } } - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, true); - spectators.erase(this); - for (auto spectator : spectators) { - if (canSee(spectator->getPosition())) { + for (const auto &spectator : Spectators().find(position, true)) { + if (spectator.get() != this && canSee(spectator->getPosition())) { onCreatureFound(spectator); } } diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 045038e6e45..414e3df1028 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -18,6 +18,7 @@ #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "utils/pugicast.hpp" +#include "map/spectators.hpp" static constexpr int32_t MONSTER_MINSPAWN_INTERVAL = 1000; // 1 second static constexpr int32_t MONSTER_MAXSPAWN_INTERVAL = 86400000; // 1 day @@ -155,9 +156,8 @@ SpawnMonster::~SpawnMonster() { } bool SpawnMonster::findPlayer(const Position &pos) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, pos, false, true); - for (std::shared_ptr spectator : spectators) { + auto spectators = Spectators().find(pos); + for (const auto &spectator : spectators) { if (!spectator->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByMonsters)) { return true; } diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index ab9ca5795d5..e3723762631 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -16,6 +16,7 @@ #include "lua/callbacks/creaturecallback.hpp" #include "game/scheduling/dispatcher.hpp" #include "game/scheduling/scheduler.hpp" +#include "map/spectators.hpp" int32_t Npc::despawnRange; int32_t Npc::despawnRadius; @@ -151,7 +152,7 @@ void Npc::onPlayerAppear(std::shared_ptr player) { if (player->hasFlag(PlayerFlags_t::IgnoredByNpcs) || playerSpectators.contains(player)) { return; } - playerSpectators.insert(player); + playerSpectators.emplace_back(player); manageIdle(); } @@ -531,7 +532,7 @@ void Npc::onThinkWalk(uint32_t interval) { void Npc::onCreatureWalk() { Creature::onCreatureWalk(); - phmap::erase_if(playerSpectators, [this](const auto &creature) { return !this->canSee(creature->getPosition()); }); + playerSpectators.erase_if([this](const auto &creature) { return !this->canSee(creature->getPosition()); }); } void Npc::onPlacedCreature() { @@ -539,11 +540,10 @@ void Npc::onPlacedCreature() { } void Npc::loadPlayerSpectators() { - SpectatorHashSet spec; - g_game().map.getSpectators(spec, position, true, true); - for (auto creature : spec) { - if (creature->getPlayer() || creature->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByNpcs)) { - playerSpectators.insert(creature); + auto spec = Spectators().find(position, true); + for (const auto &creature : spec) { + if (!creature->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByNpcs)) { + playerSpectators.emplace_back(creature->getPlayer()); } } } diff --git a/src/creatures/npcs/npc.hpp b/src/creatures/npcs/npc.hpp index 45941706f65..27f46cbeec6 100644 --- a/src/creatures/npcs/npc.hpp +++ b/src/creatures/npcs/npc.hpp @@ -188,7 +188,7 @@ class Npc final : public Creature { bool ignoreHeight; - SpectatorHashSet playerSpectators; + stdext::vector_set> playerSpectators; Position masterPos; friend class LuaScriptInterface; diff --git a/src/creatures/npcs/spawns/spawn_npc.cpp b/src/creatures/npcs/spawns/spawn_npc.cpp index c7605f36c06..878c1830b15 100644 --- a/src/creatures/npcs/spawns/spawn_npc.cpp +++ b/src/creatures/npcs/spawns/spawn_npc.cpp @@ -17,6 +17,7 @@ #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "utils/pugicast.hpp" +#include "map/spectators.hpp" static constexpr int32_t MINSPAWN_INTERVAL = 1000; // 1 second static constexpr int32_t MAXSPAWN_INTERVAL = 86400000; // 1 day @@ -141,9 +142,8 @@ SpawnNpc::~SpawnNpc() { } bool SpawnNpc::findPlayer(const Position &pos) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, pos, false, true); - for (std::shared_ptr spectator : spectators) { + auto spectators = Spectators().find(pos); + for (const auto &spectator : spectators) { if (!spectator->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByNpcs)) { return true; } diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index ab2032d118d..59b9292662d 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -30,6 +30,7 @@ #include "items/bed.hpp" #include "items/weapons/weapons.hpp" #include "core.hpp" +#include "map/spectators.hpp" MuteCountMap Player::muteCountMap; @@ -2244,8 +2245,7 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool message.primary.color = TEXTCOLOR_WHITE_EXP; sendTextMessage(message); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, false, true); + auto spectators = Spectators().find(position); spectators.erase(static_self_cast()); if (!spectators.empty()) { message.type = MESSAGE_EXPERIENCE_OTHERS; @@ -2338,8 +2338,7 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) { message.primary.color = TEXTCOLOR_RED; sendTextMessage(message); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, false, true); + auto spectators = Spectators().find(position); spectators.erase(static_self_cast()); if (!spectators.empty()) { message.type = MESSAGE_EXPERIENCE_OTHERS; @@ -2770,14 +2769,9 @@ bool Player::spawn() { return false; } - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, true); - for (std::shared_ptr spectator : spectators) { - if (!spectator) { - continue; - } - - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { + auto spectators = Spectators().find(position, true); + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureAppear(static_self_cast(), pos, true); } @@ -2813,18 +2807,13 @@ void Player::despawn() { std::vector oldStackPosVector; - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, tile->getPosition(), true); + auto spectators = Spectators().find(tile->getPosition(), true); size_t i = 0; - for (std::shared_ptr spectator : spectators) { - if (!spectator) { - continue; - } - - if (const auto player = spectator->getPlayer()) { + for (const auto &spectator : spectators) { + if (const auto &player = spectator->getPlayer()) { oldStackPosVector.push_back(player->canSeeCreature(static_self_cast()) ? tile->getStackposOfCreature(player, getPlayer()) : -1); } - if (auto player = spectator->getPlayer()) { + if (const auto &player = spectator->getPlayer()) { player->sendRemoveTileThing(tile->getPosition(), oldStackPosVector[i++]); } @@ -6772,7 +6761,7 @@ bool Player::saySpell( SpeakClasses type, const std::string &text, bool ghostMode, - SpectatorHashSet* spectatorsPtr /* = nullptr*/, + Spectators* spectatorsPtr /* = nullptr*/, const Position* pos /* = nullptr*/ ) { if (text.empty()) { @@ -6784,17 +6773,17 @@ bool Player::saySpell( pos = &getPosition(); } - SpectatorHashSet spectators; + Spectators spectators; if (!spectatorsPtr || spectatorsPtr->empty()) { - // This somewhat complex construct ensures that the cached SpectatorHashSet + // This somewhat complex construct ensures that the cached Spectators // is used if available and if it can be used, else a local vector is // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { - g_game().map.getSpectators(spectators, *pos, false, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y); + spectators.find(*pos, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y); } else { - g_game().map.getSpectators(spectators, *pos, true, false, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2); + spectators.find(*pos, true, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2); } } else { spectators = (*spectatorsPtr); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 620d38ffae9..19614aa3d7e 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -47,6 +47,7 @@ class PreySlot; class TaskHuntingSlot; class Spell; class PlayerWheel; +class Spectators; enum class ForgeConversion_t : uint8_t { FORGE_ACTION_FUSION = 0, @@ -2285,7 +2286,7 @@ class Player final : public Creature, public Cylinder, public Bankable { SpeakClasses type, const std::string &text, bool ghostMode, - SpectatorHashSet* spectatorsPtr = nullptr, + Spectators* spectatorsPtr = nullptr, const Position* pos = nullptr ); diff --git a/src/game/game.cpp b/src/game/game.cpp index be44b0d703f..2aba04f7ea3 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -45,6 +45,7 @@ #include "protobuf/appearances.pb.h" #include "server/network/protocol/protocollogin.hpp" #include "server/network/protocol/protocolstatus.hpp" +#include "map/spectators.hpp" #include "kv/kv.hpp" @@ -877,10 +878,8 @@ bool Game::placeCreature(std::shared_ptr creature, const Position &pos } bool hasPlayerSpectators = false; - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true); - for (std::shared_ptr spectator : spectators) { - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); hasPlayerSpectators = true; } @@ -905,10 +904,11 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = std::vector oldStackPosVector; - SpectatorHashSet spectators; - map.getSpectators(spectators, tile->getPosition(), true); - for (auto spectator : spectators) { - if (auto player = spectator->getPlayer()) { + auto spectators = Spectators().find(tile->getPosition(), true); + auto playersSpectators = spectators.filter(); + + for (const auto &spectator : playersSpectators) { + if (const auto &player = spectator->getPlayer()) { oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); } } @@ -919,8 +919,8 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = // Send to client size_t i = 0; - for (auto spectator : spectators) { - if (auto player = spectator->getPlayer()) { + for (const auto &spectator : playersSpectators) { + if (const auto &player = spectator->getPlayer()) { player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } @@ -3066,17 +3066,13 @@ void Game::playerOpenPrivateChannel(uint32_t playerId, std::string &receiver) { } void Game::playerCloseNpcChannel(uint32_t playerId) { - std::shared_ptr player = getPlayerByID(playerId); + const auto &player = getPlayerByID(playerId); if (!player) { return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, player->getPosition()); - for (std::shared_ptr spectator : spectators) { - if (auto npc = spectator->getNpc()) { - npc->onPlayerCloseChannel(player); - } + for (const auto &spectator : Spectators().find(player->getPosition()).filter()) { + spectator->getNpc()->onPlayerCloseChannel(player); } } @@ -3752,14 +3748,9 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos item->removeAttribute(ItemAttribute_t::NAME); } - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, pos, true); - // Send to client - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendUpdateTileItem(tile, pos, item); - } + for (const auto &spectator : Spectators().find(pos, true)) { + spectator->getPlayer()->sendUpdateTileItem(tile, pos, item); } } @@ -5492,12 +5483,11 @@ bool Game::playerSaySpell(std::shared_ptr player, SpeakClasses type, con } void Game::playerWhisper(std::shared_ptr player, const std::string &text) { - SpectatorHashSet spectators; - map.getSpectators(spectators, player->getPosition(), false, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y); + auto spectators = Spectators().find(player->getPosition(), false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y); // Send to client - for (auto spectator : spectators) { - if (auto spectatorPlayer = spectator->getPlayer()) { + for (const auto &spectator : spectators) { + if (const auto &spectatorPlayer = spectator->getPlayer()) { if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); } else { @@ -5507,7 +5497,7 @@ void Game::playerWhisper(std::shared_ptr player, const std::string &text } // event method - for (auto spectator : spectators) { + for (const auto &spectator : spectators) { spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); } } @@ -5570,12 +5560,8 @@ void Game::playerSpeakToNpc(std::shared_ptr player, const std::string &t return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, player->getPosition()); - for (auto spectator : spectators) { - if (spectator->getNpc()) { - spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text); - } + for (const auto &spectator : Spectators().find(player->getPosition()).filter()) { + spectator->getNpc()->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text); } player->updateUIExhausted(); @@ -5599,25 +5585,18 @@ bool Game::internalCreatureTurn(std::shared_ptr creature, Direction di return false; } - if (std::shared_ptr player = creature->getPlayer()) { + if (const auto &player = creature->getPlayer()) { player->cancelPush(); } creature->setDirection(dir); - // Send to client - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); - if (!tmpPlayer) { - continue; - } - tmpPlayer->sendCreatureTurn(creature); + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureTurn(creature); } return true; } -bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, SpectatorHashSet* spectatorsPtr /* = nullptr*/, const Position* pos /* = nullptr*/) { +bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, Spectators* spectatorsPtr /* = nullptr*/, const Position* pos /* = nullptr*/) { if (text.empty()) { return false; } @@ -5626,25 +5605,25 @@ bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses pos = &creature->getPosition(); } - SpectatorHashSet spectators; + Spectators spectators; if (!spectatorsPtr || spectatorsPtr->empty()) { - // This somewhat complex construct ensures that the cached SpectatorHashSet + // This somewhat complex construct ensures that the cached Spectators // is used if available and if it can be used, else a local vector is // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { - map.getSpectators(spectators, *pos, false, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y); + spectators.find(*pos, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y); } else { - map.getSpectators(spectators, *pos, true, false, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2); + spectators.find(*pos, true, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2); } } else { spectators = (*spectatorsPtr); } // Send to client - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { tmpPlayer->sendCreatureSay(creature, type, text, pos); } @@ -5652,7 +5631,7 @@ bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses } // event method - for (auto spectator : spectators) { + for (const auto &spectator : spectators) { spectator->onCreatureSay(creature, type, text); if (creature != spectator) { g_events().eventCreatureOnHear(spectator, creature, text, type); @@ -5737,15 +5716,8 @@ void Game::changeSpeed(std::shared_ptr creature, int32_t varSpeedDelta creature->setSpeed(varSpeed); // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), false, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendChangeSpeed(creature, creature->getStepSpeed()); + for (const auto &spectator : Spectators().find(creature->getPosition())) { + spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); } } @@ -5753,15 +5725,8 @@ void Game::setCreatureSpeed(std::shared_ptr creature, int32_t speed) { creature->setBaseSpeed(static_cast(speed)); // Send creature speed to client - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), false, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendChangeSpeed(creature, creature->getStepSpeed()); + for (const auto &spectator : Spectators().find(creature->getPosition())) { + spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); } } @@ -5772,21 +5737,8 @@ void Game::changePlayerSpeed(const std::shared_ptr &player, int32_t varS player->setSpeed(varSpeed); // Send new player speed to the spectators - SpectatorHashSet spectators; - map.getSpectators(spectators, player->getPosition(), false, true); - for (std::shared_ptr creatureSpectator : spectators) { - if (creatureSpectator == nullptr) { - g_logger().error("[Game::changePlayerSpeed] - Creature spectator is nullptr"); - continue; - } - - std::shared_ptr playerSpectator = creatureSpectator->getPlayer(); - if (playerSpectator == nullptr) { - g_logger().error("[Game::changePlayerSpeed] - Player spectator is nullptr"); - continue; - } - - playerSpectator->sendChangeSpeed(player, player->getStepSpeed()); + for (const auto &creatureSpectator : Spectators().find(player->getPosition())) { + creatureSpectator->getPlayer()->sendChangeSpeed(player, player->getStepSpeed()); } } @@ -5806,57 +5758,29 @@ void Game::internalCreatureChangeOutfit(std::shared_ptr creature, cons } // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendCreatureChangeOutfit(creature, outfit); + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); } } void Game::internalCreatureChangeVisible(std::shared_ptr creature, bool visible) { // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendCreatureChangeVisible(creature, visible); + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); } } void Game::changeLight(std::shared_ptr creature) { // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendCreatureLight(creature); + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureLight(creature); } } void Game::updateCreatureIcon(std::shared_ptr creature) { // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendCreatureIcon(creature); + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureIcon(creature); } } @@ -5866,14 +5790,8 @@ void Game::reloadCreature(std::shared_ptr creature) { return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), false, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); - if (!tmpPlayer) { - continue; - } - tmpPlayer->reloadCreature(creature); + for (const auto &spectator : Spectators().find(creature->getPosition())) { + spectator->getPlayer()->reloadCreature(creature); } } @@ -5882,21 +5800,18 @@ void Game::sendSingleSoundEffect(const Position &pos, SoundEffect_t soundId, std return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, pos, false, true); - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - SourceEffect_t source = SourceEffect_t::CREATURES; - if (!actor || actor->getNpc()) { - source = SourceEffect_t::GLOBAL; - } else if (actor == spectator) { - source = SourceEffect_t::OWN; - } else if (actor->getPlayer()) { - source = SourceEffect_t::OTHERS; - } - - tmpPlayer->sendSingleSoundEffect(pos, soundId, source); + using enum SourceEffect_t; + for (const auto &spectator : Spectators().find(pos)) { + SourceEffect_t source = CREATURES; + if (!actor || actor->getNpc()) { + source = GLOBAL; + } else if (actor == spectator) { + source = OWN; + } else if (actor->getPlayer()) { + source = OTHERS; } + + spectator->getPlayer()->sendSingleSoundEffect(pos, soundId, source); } } @@ -5906,21 +5821,18 @@ void Game::sendDoubleSoundEffect(const Position &pos, SoundEffect_t mainSoundEff return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, pos, false, true); - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - SourceEffect_t source = SourceEffect_t::CREATURES; - if (!actor || actor->getNpc()) { - source = SourceEffect_t::GLOBAL; - } else if (actor == spectator) { - source = SourceEffect_t::OWN; - } else if (actor->getPlayer()) { - source = SourceEffect_t::OTHERS; - } - - tmpPlayer->sendDoubleSoundEffect(pos, mainSoundEffect, source, secondarySoundEffect, source); + using enum SourceEffect_t; + for (const auto &spectator : Spectators().find(pos)) { + SourceEffect_t source = CREATURES; + if (!actor || actor->getNpc()) { + source = GLOBAL; + } else if (actor == spectator) { + source = OWN; + } else if (actor->getPlayer()) { + source = OTHERS; } + + spectator->getPlayer()->sendDoubleSoundEffect(pos, mainSoundEffect, source, secondarySoundEffect, source); } } @@ -6230,7 +6142,7 @@ void Game::handleHazardSystemAttack(CombatDamage &damage, std::shared_ptr attackerPlayer, std::shared_ptr targetMonster) { +void Game::notifySpectators(const CreatureVector &spectators, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetMonster) { if (!spectators.empty()) { for (auto spectator : spectators) { if (!spectator) { @@ -6400,10 +6312,8 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt message.primary.value = realHealthChange; message.primary.color = TEXTCOLOR_PASTELRED; - SpectatorHashSet spectators; - map.getSpectators(spectators, targetPos, false, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); + for (const auto &spectator : Spectators().find(targetPos)) { + const auto &tmpPlayer = spectator->getPlayer(); if (!tmpPlayer) { continue; } @@ -6455,14 +6365,9 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt return true; } - std::shared_ptr attackerPlayer; - if (attacker) { - attackerPlayer = attacker->getPlayer(); - } else { - attackerPlayer = nullptr; - } + const auto &attackerPlayer = attacker ? attacker->getPlayer() : nullptr; - auto targetPlayer = target->getPlayer(); + const auto &targetPlayer = target->getPlayer(); if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } @@ -6539,8 +6444,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt return true; } - SpectatorHashSet spectators; - map.getSpectators(spectators, targetPos, true, true); + auto spectators = Spectators().find(targetPos, true); if (targetPlayer && attackerMonster) { handleHazardSystemAttack(damage, targetPlayer, attackerMonster, false); @@ -6548,15 +6452,15 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt handleHazardSystemAttack(damage, attackerPlayer, targetMonster, true); if (damage.primary.value == 0 && damage.secondary.value == 0) { - notifySpectators(spectators, targetPos, attackerPlayer, targetMonster); + notifySpectators(spectators.data(), targetPos, attackerPlayer, targetMonster); return true; } } if (damage.fatal) { - addMagicEffect(spectators, targetPos, CONST_ME_FATAL); + addMagicEffect(spectators.data(), targetPos, CONST_ME_FATAL); } else if (damage.critical) { - addMagicEffect(spectators, targetPos, CONST_ME_CRITICAL_DAMAGE); + addMagicEffect(spectators.data(), targetPos, CONST_ME_CRITICAL_DAMAGE); } if (!damage.extension && attackerMonster && targetPlayer) { @@ -6609,7 +6513,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt target->removeCondition(CONDITION_MANASHIELD); } - addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); + addMagicEffect(spectators.data(), targetPos, CONST_ME_LOSEENERGY); std::string damageString = std::to_string(manaDamage); @@ -6618,17 +6522,9 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt message.primary.value = manaDamage; message.primary.color = TEXTCOLOR_BLUE; - for (std::shared_ptr spectator : spectators) { - if (!spectator) { - continue; - } - - std::shared_ptr tmpPlayer = spectator->getPlayer(); - if (!tmpPlayer) { - continue; - } - - if (tmpPlayer->getPosition().z != targetPos.z) { + for (const auto &spectator : spectators) { + const auto &tmpPlayer = spectator->getPlayer(); + if (!tmpPlayer || tmpPlayer->getPosition().z != targetPos.z) { continue; } @@ -6730,10 +6626,10 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt } if (spectators.empty()) { - map.getSpectators(spectators, targetPos, true, true); + spectators.find(targetPos, true); } - addCreatureHealth(spectators, target); + addCreatureHealth(spectators.data(), target); sendDamageMessageAndEffects( attacker, @@ -6743,7 +6639,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt attackerPlayer, targetPlayer, message, - spectators, + spectators.data(), realDamage ); @@ -6778,7 +6674,7 @@ void Game::updatePlayerPartyHuntAnalyzer(const CombatDamage &damage, std::shared void Game::sendDamageMessageAndEffects( std::shared_ptr attacker, std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetPlayer, - TextMessage &message, const SpectatorHashSet &spectators, int32_t realDamage + TextMessage &message, const CreatureVector &spectators, int32_t realDamage ) { message.primary.value = damage.primary.value; message.secondary.value = damage.secondary.value; @@ -6797,7 +6693,7 @@ bool Game::shouldSendMessage(const TextMessage &message) const { void Game::sendMessages( std::shared_ptr attacker, std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetPlayer, - TextMessage &message, const SpectatorHashSet &spectators, int32_t realDamage + TextMessage &message, const CreatureVector &spectators, int32_t realDamage ) const { if (attackerPlayer) { attackerPlayer->updateImpactTracker(damage.primary.type, damage.primary.value); @@ -6912,7 +6808,7 @@ void Game::buildMessageAsAttacker( void Game::sendEffects( std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, TextMessage &message, - const SpectatorHashSet &spectators + const CreatureVector &spectators ) { uint16_t hitEffect; if (message.primary.value) { @@ -7070,10 +6966,8 @@ bool Game::combatChangeMana(std::shared_ptr attacker, std::shared_ptr< message.primary.value = realManaChange; message.primary.color = TEXTCOLOR_MAYABLUE; - SpectatorHashSet spectators; - map.getSpectators(spectators, targetPos, false, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); + for (const auto &spectator : Spectators().find(targetPos)) { + const auto &tmpPlayer = spectator->getPlayer(); if (!tmpPlayer) { continue; } @@ -7169,10 +7063,8 @@ bool Game::combatChangeMana(std::shared_ptr attacker, std::shared_ptr< message.primary.value = manaLoss; message.primary.color = TEXTCOLOR_BLUE; - SpectatorHashSet spectators; - map.getSpectators(spectators, targetPos, false, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); + for (const auto &spectator : Spectators().find(targetPos)) { + const auto &tmpPlayer = spectator->getPlayer(); if (!tmpPlayer) { continue; } @@ -7220,91 +7112,81 @@ bool Game::combatChangeMana(std::shared_ptr attacker, std::shared_ptr< } void Game::addCreatureHealth(std::shared_ptr target) { - SpectatorHashSet spectators; - map.getSpectators(spectators, target->getPosition(), true, true); - addCreatureHealth(spectators, target); + auto spectators = Spectators().find(target->getPosition(), true); + addCreatureHealth(spectators.data(), target); } -void Game::addCreatureHealth(const SpectatorHashSet &spectators, std::shared_ptr target) { +void Game::addCreatureHealth(const CreatureVector &spectators, std::shared_ptr target) { uint8_t healthPercent = std::ceil((static_cast(target->getHealth()) / std::max(target->getMaxHealth(), 1)) * 100); - if (std::shared_ptr targetPlayer = target->getPlayer()) { - if (std::shared_ptr party = targetPlayer->getParty()) { + if (const auto &targetPlayer = target->getPlayer()) { + if (const auto &party = targetPlayer->getParty()) { party->updatePlayerHealth(targetPlayer, target, healthPercent); } - } else if (std::shared_ptr master = target->getMaster()) { - if (std::shared_ptr masterPlayer = master->getPlayer()) { - if (std::shared_ptr party = masterPlayer->getParty()) { + } else if (const auto &master = target->getMaster()) { + if (const auto &masterPlayer = master->getPlayer()) { + if (const auto &party = masterPlayer->getParty()) { party->updatePlayerHealth(masterPlayer, target, healthPercent); } } } - for (std::shared_ptr spectator : spectators) { - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureHealth(target); } } } void Game::addPlayerMana(std::shared_ptr target) { - if (std::shared_ptr party = target->getParty()) { + if (const auto &party = target->getParty()) { uint8_t manaPercent = std::ceil((static_cast(target->getMana()) / std::max(target->getMaxMana(), 1)) * 100); party->updatePlayerMana(target, manaPercent); } } void Game::addPlayerVocation(std::shared_ptr target) { - if (auto party = target->getParty()) { + if (const auto &party = target->getParty()) { party->updatePlayerVocation(target); } - SpectatorHashSet spectators; - map.getSpectators(spectators, target->getPosition(), true, true); - - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendPlayerVocation(target); - } + for (const auto &spectator : Spectators().find(target->getPosition(), true)) { + spectator->getPlayer()->sendPlayerVocation(target); } } void Game::addMagicEffect(const Position &pos, uint16_t effect) { - SpectatorHashSet spectators; - map.getSpectators(spectators, pos, true, true); - addMagicEffect(spectators, pos, effect); + auto spectators = Spectators().find(pos, true); + addMagicEffect(spectators.data(), pos, effect); } -void Game::addMagicEffect(const SpectatorHashSet &spectators, const Position &pos, uint16_t effect) { - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { +void Game::addMagicEffect(const CreatureVector &spectators, const Position &pos, uint16_t effect) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendMagicEffect(pos, effect); } } } void Game::removeMagicEffect(const Position &pos, uint16_t effect) { - SpectatorHashSet spectators; - map.getSpectators(spectators, pos, true, true); - removeMagicEffect(spectators, pos, effect); + auto spectators = Spectators().find(pos, true); + removeMagicEffect(spectators.data(), pos, effect); } -void Game::removeMagicEffect(const SpectatorHashSet &spectators, const Position &pos, uint16_t effect) { - for (auto spectator : spectators) { - if (const auto tmpPlayer = spectator->getPlayer()) { +void Game::removeMagicEffect(const CreatureVector &spectators, const Position &pos, uint16_t effect) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->removeMagicEffect(pos, effect); } } } void Game::addDistanceEffect(const Position &fromPos, const Position &toPos, uint16_t effect) { - SpectatorHashSet spectators; - map.getSpectators(spectators, fromPos, false, true); - map.getSpectators(spectators, toPos, false, true); - addDistanceEffect(spectators, fromPos, toPos, effect); + auto spectators = Spectators().find(fromPos).find(toPos); + addDistanceEffect(spectators.data(), fromPos, toPos, effect); } -void Game::addDistanceEffect(const SpectatorHashSet &spectators, const Position &fromPos, const Position &toPos, uint16_t effect) { - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { +void Game::addDistanceEffect(const CreatureVector &spectators, const Position &fromPos, const Position &toPos, uint16_t effect) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); } } @@ -7453,14 +7335,8 @@ void Game::broadcastMessage(const std::string &text, MessageClasses type) const void Game::updateCreatureWalkthrough(std::shared_ptr creature) { // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); - if (!tmpPlayer) { - continue; - } - + for (const auto spectator : Spectators().find(creature->getPosition(), true)) { + const auto &tmpPlayer = spectator->getPlayer(); tmpPlayer->sendCreatureWalkthrough(creature, tmpPlayer->canWalkthroughEx(creature)); } } @@ -7470,28 +7346,14 @@ void Game::updateCreatureSkull(std::shared_ptr creature) { return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendCreatureSkull(creature); + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureSkull(creature); } } void Game::updatePlayerShield(std::shared_ptr player) { - SpectatorHashSet spectators; - map.getSpectators(spectators, player->getPosition(), true, true); - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - player->sendCreatureShield(player); + for (const auto &spectator : Spectators().find(player->getPosition(), true)) { + spectator->getPlayer()->sendCreatureShield(player); } } @@ -7516,26 +7378,14 @@ void Game::updateCreatureType(std::shared_ptr creature) { } // Send to clients - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); + auto spectators = Spectators().find(creature->getPosition(), true); if (creatureType == CREATURETYPE_SUMMON_OTHERS) { - for (auto spectator : spectators) { - auto player = spectator->getPlayer(); - if (!player) { - continue; - } - - if (masterPlayer == player) { - player->sendCreatureType(creature, CREATURETYPE_SUMMON_PLAYER); - } else { - player->sendCreatureType(creature, creatureType); - } + for (const auto &spectator : spectators) { + spectator->getPlayer()->sendCreatureType(creature, masterPlayer == spectator ? CREATURETYPE_SUMMON_PLAYER : creatureType); } } else { - for (auto spectator : spectators) { - if (auto player = spectator->getPlayer()) { - player->sendCreatureType(creature, creatureType); - } + for (const auto &spectator : spectators) { + spectator->getPlayer()->sendCreatureType(creature, creatureType); } } } @@ -7645,15 +7495,8 @@ void Game::updatePlayerHelpers(std::shared_ptr player) { uint16_t helpers = player->getHelpers(); - SpectatorHashSet spectators; - map.getSpectators(spectators, player->getPosition(), true, true); - for (auto spectator : spectators) { - auto specPlayer = spectator->getPlayer(); - if (!specPlayer) { - continue; - } - - specPlayer->sendCreatureHelpers(player->getID(), helpers); + for (const auto &spectator : Spectators().find(player->getPosition(), true)) { + spectator->getPlayer()->sendCreatureHelpers(player->getID(), helpers); } } @@ -8188,26 +8031,26 @@ void Game::playerTaskHuntingAction(uint32_t playerId, uint8_t slot, uint8_t acti } void Game::playerNpcGreet(uint32_t playerId, uint32_t npcId) { - std::shared_ptr player = getPlayerByID(playerId); + const auto &player = getPlayerByID(playerId); if (!player) { return; } - std::shared_ptr npc = getNpcByID(npcId); + const auto &npc = getNpcByID(npcId); if (!npc) { return; } - SpectatorHashSet spectators; + auto spectators = Spectators().find(player->getPosition(), true); spectators.insert(npc); - map.getSpectators(spectators, player->getPosition(), true, true); internalCreatureSay(player, TALKTYPE_SAY, "hi", false, &spectators); - spectators.clear(); - spectators.insert(npc); + + auto npcsSpectators = spectators.filter(); + if (npc->getSpeechBubble() == SPEECHBUBBLE_TRADE) { - internalCreatureSay(player, TALKTYPE_PRIVATE_PN, "trade", false, &spectators); + internalCreatureSay(player, TALKTYPE_PRIVATE_PN, "trade", false, &npcsSpectators); } else { - internalCreatureSay(player, TALKTYPE_PRIVATE_PN, "sail", false, &spectators); + internalCreatureSay(player, TALKTYPE_PRIVATE_PN, "sail", false, &npcsSpectators); } } @@ -9106,14 +8949,8 @@ void Game::playerSetMonsterPodium(uint32_t playerId, uint32_t monsterRaceId, con item->removeAttribute(ItemAttribute_t::NAME); } - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, pos, true); - - // Send to client - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendUpdateTileItem(tile, pos, item); - } + for (const auto &spectator : Spectators().find(pos, true)) { + spectator->getPlayer()->sendUpdateTileItem(tile, pos, item); } } @@ -9463,12 +9300,8 @@ void Game::sendUpdateCreature(std::shared_ptr creature) { return; } - SpectatorHashSet spectators; - map.getSpectators(spectators, creature->getPosition(), true); - for (auto spectator : spectators) { - if (const auto tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendUpdateCreature(creature); - } + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendUpdateCreature(creature); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 0753eca000d..9cf467b4b45 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -36,6 +36,7 @@ class IOWheel; class ItemClassification; class Guild; class Mounts; +class Spectators; static constexpr int32_t EVENT_MS = 10000; static constexpr int32_t EVENT_LIGHTINTERVAL_MS = 10000; @@ -229,7 +230,7 @@ class Game { bool internalCreatureTurn(std::shared_ptr creature, Direction dir); - bool internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, SpectatorHashSet* spectatorsPtr = nullptr, const Position* pos = nullptr); + bool internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, Spectators* spectatorsPtr = nullptr, const Position* pos = nullptr); ObjectCategory_t getObjectCategory(const std::shared_ptr item); @@ -441,7 +442,7 @@ class Game { // Hazard combat helpers void handleHazardSystemAttack(CombatDamage &damage, std::shared_ptr player, const std::shared_ptr monster, bool isPlayerAttacker); - void notifySpectators(const SpectatorHashSet &spectators, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetMonster); + void notifySpectators(const CreatureVector &spectators, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetMonster); // Wheel of destiny combat helpers void applyWheelOfDestinyHealing(CombatDamage &damage, std::shared_ptr attackerPlayer, std::shared_ptr target); @@ -463,15 +464,15 @@ class Game { // Animation help functions void addCreatureHealth(const std::shared_ptr target); - static void addCreatureHealth(const SpectatorHashSet &spectators, const std::shared_ptr target); + static void addCreatureHealth(const CreatureVector &spectators, const std::shared_ptr target); void addPlayerMana(const std::shared_ptr target); void addPlayerVocation(const std::shared_ptr target); void addMagicEffect(const Position &pos, uint16_t effect); - static void addMagicEffect(const SpectatorHashSet &spectators, const Position &pos, uint16_t effect); + static void addMagicEffect(const CreatureVector &spectators, const Position &pos, uint16_t effect); void removeMagicEffect(const Position &pos, uint16_t effect); - static void removeMagicEffect(const SpectatorHashSet &spectators, const Position &pos, uint16_t effect); + static void removeMagicEffect(const CreatureVector &spectators, const Position &pos, uint16_t effect); void addDistanceEffect(const Position &fromPos, const Position &toPos, uint16_t effect); - static void addDistanceEffect(const SpectatorHashSet &spectators, const Position &fromPos, const Position &toPos, uint16_t effect); + static void addDistanceEffect(const CreatureVector &spectators, const Position &fromPos, const Position &toPos, uint16_t effect); int32_t getLightHour() const { return lightHour; @@ -851,20 +852,20 @@ class Game { void sendDamageMessageAndEffects( std::shared_ptr attacker, std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetPlayer, TextMessage &message, - const SpectatorHashSet &spectators, int32_t realDamage + const CreatureVector &spectators, int32_t realDamage ); void updatePlayerPartyHuntAnalyzer(const CombatDamage &damage, std::shared_ptr player) const; void sendEffects( std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, - TextMessage &message, const SpectatorHashSet &spectators + TextMessage &message, const CreatureVector &spectators ); void sendMessages( std::shared_ptr attacker, std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetPlayer, - TextMessage &message, const SpectatorHashSet &spectators, int32_t realDamage + TextMessage &message, const CreatureVector &spectators, int32_t realDamage ) const; bool shouldSendMessage(const TextMessage &message) const; diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 262a647e0d1..67884eb4fcc 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -13,6 +13,7 @@ #include "items/decay/decay.hpp" #include "io/iomap.hpp" #include "game/game.hpp" +#include "map/spectators.hpp" Container::Container(uint16_t type) : Container(type, items[type].maxItems) { @@ -361,46 +362,43 @@ bool Container::isHoldingItemWithId(const uint16_t id) { } void Container::onAddContainerItem(std::shared_ptr item) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + auto spectators = Spectators().find(getPosition(), false, 2, 2, 2, 2); // send to client - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->sendAddContainerItem(getContainer(), item); } // event methods - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->onAddContainerItem(item); } } void Container::onUpdateContainerItem(uint32_t index, std::shared_ptr oldItem, std::shared_ptr newItem) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + auto spectators = Spectators().find(getPosition(), false, 2, 2, 2, 2); // send to client - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->sendUpdateContainerItem(getContainer(), index, newItem); } // event methods - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->onUpdateContainerItem(getContainer(), oldItem, newItem); } } void Container::onRemoveContainerItem(uint32_t index, std::shared_ptr item) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + auto spectators = Spectators().find(getPosition(), false, 2, 2, 2, 2); // send change to client - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->sendRemoveContainerItem(getContainer(), index); } // event methods - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->onRemoveContainerItem(getContainer(), item); } } diff --git a/src/items/containers/mailbox/mailbox.cpp b/src/items/containers/mailbox/mailbox.cpp index bfc732939e0..959ca818bb5 100644 --- a/src/items/containers/mailbox/mailbox.cpp +++ b/src/items/containers/mailbox/mailbox.cpp @@ -12,6 +12,7 @@ #include "items/containers/mailbox/mailbox.hpp" #include "game/game.hpp" #include "io/iologindata.hpp" +#include "map/spectators.hpp" ReturnValue Mailbox::queryAdd(int32_t, const std::shared_ptr &thing, uint32_t, uint32_t, std::shared_ptr) { std::shared_ptr item = thing->getItem(); @@ -81,12 +82,8 @@ bool Mailbox::sendItem(std::shared_ptr item) const { } if (item && item->getContainer() && item->getTile()) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, item->getTile()->getPosition(), false, true); - for (auto spectator : spectators) { - if (spectator && spectator->getPlayer()) { - spectator->getPlayer()->autoCloseContainers(item->getContainer()); - } + for (const auto &spectator : Spectators().find(item->getTile()->getPosition())) { + spectator->getPlayer()->autoCloseContainers(item->getContainer()); } } diff --git a/src/items/tile.cpp b/src/items/tile.cpp index 37063181cf8..25f7f865688 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -21,6 +21,7 @@ #include "items/trashholder.hpp" #include "map/house/housetile.hpp" #include "io/iomap.hpp" +#include "map/spectators.hpp" auto real_nullptr_tile = std::make_shared(0xFFFF, 0xFFFF, 0xFF); const std::shared_ptr &Tile::nullptr_tile = real_nullptr_tile; @@ -352,18 +353,17 @@ void Tile::onAddTileItem(std::shared_ptr item) { const Position &cylinderMapPos = getPosition(); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, cylinderMapPos, true); + auto spectators = Spectators().find(cylinderMapPos, true); // send to client - for (std::shared_ptr spectator : spectators) { - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendAddTileItem(static_self_cast(), cylinderMapPos, item); } } // event methods - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->onAddTileItem(static_self_cast(), cylinderMapPos); } @@ -437,23 +437,22 @@ void Tile::onUpdateTileItem(std::shared_ptr oldItem, const ItemType &oldTy const Position &cylinderMapPos = getPosition(); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, cylinderMapPos, true); + auto spectators = Spectators().find(cylinderMapPos, true); // send to client - for (std::shared_ptr spectator : spectators) { - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendUpdateTileItem(static_self_cast(), cylinderMapPos, newItem); } } // event methods - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->onUpdateTileItem(static_self_cast(), cylinderMapPos, oldItem, oldType, newItem, newType); } } -void Tile::onRemoveTileItem(const SpectatorHashSet &spectators, const std::vector &oldStackPosVector, std::shared_ptr item) { +void Tile::onRemoveTileItem(const CreatureVector &spectators, const std::vector &oldStackPosVector, std::shared_ptr item) { if ((item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVEABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) { auto it = g_game().browseFields.find(getTile()); if (it != g_game().browseFields.end()) { @@ -536,7 +535,7 @@ void Tile::onRemoveTileItem(const SpectatorHashSet &spectators, const std::vecto } } -void Tile::onUpdateTile(const SpectatorHashSet &spectators) { +void Tile::onUpdateTile(const CreatureVector &spectators) { const Position &cylinderMapPos = getPosition(); // send to clients @@ -551,7 +550,6 @@ ReturnValue Tile::queryAdd(int32_t, const std::shared_ptr &thing, uint32_ } if (auto creature = thing->getCreature()) { - if (creature->getNpc()) { ReturnValue returnValue = checkNpcCanWalkIntoTile(); if (returnValue != RETURNVALUE_NOERROR) { @@ -944,7 +942,7 @@ void Tile::addThing(int32_t, std::shared_ptr thing) { std::shared_ptr creature = thing->getCreature(); if (creature) { - g_game().map.clearSpectatorCache(); + Spectators::clearCache(); creature->setParent(static_self_cast()); CreatureVector* creatures = makeCreatures(); creatures->insert(creatures->begin(), creature); @@ -1142,7 +1140,7 @@ void Tile::removeThing(std::shared_ptr thing, uint32_t count) { if (creatures) { auto it = std::find(creatures->begin(), creatures->end(), thing); if (it != creatures->end()) { - g_game().map.clearSpectatorCache(); + Spectators::clearCache(); creatures->erase(it); } } @@ -1163,9 +1161,8 @@ void Tile::removeThing(std::shared_ptr thing, uint32_t count) { ground->resetParent(); ground = nullptr; - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), true); - onRemoveTileItem(spectators, std::vector(spectators.size(), 0), item); + auto spectators = Spectators().find(getPosition(), true); + onRemoveTileItem(spectators.data(), std::vector(spectators.size(), 0), item); return; } @@ -1182,17 +1179,16 @@ void Tile::removeThing(std::shared_ptr thing, uint32_t count) { std::vector oldStackPosVector; - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), true); - for (std::shared_ptr spectator : spectators) { - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { + auto spectators = Spectators().find(getPosition(), true); + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); } } item->resetParent(); items->erase(it); - onRemoveTileItem(spectators, oldStackPosVector, item); + onRemoveTileItem(spectators.data(), oldStackPosVector, item); } else { auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item); if (it == items->getEndDownItem()) { @@ -1207,18 +1203,17 @@ void Tile::removeThing(std::shared_ptr thing, uint32_t count) { } else { std::vector oldStackPosVector; - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), true); - for (std::shared_ptr spectator : spectators) { - if (std::shared_ptr tmpPlayer = spectator->getPlayer()) { - oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); + auto spectators = Spectators().find(getPosition(), true); + for (const auto &spectator : spectators) { + if (const auto &tmpPlayer = spectator->getPlayer()) { + oldStackPosVector.push_back(getStackposOfItem(spectator->getPlayer(), item)); } } item->resetParent(); items->erase(it); items->decreaseDownItemCount(); - onRemoveTileItem(spectators, oldStackPosVector, item); + onRemoveTileItem(spectators.data(), oldStackPosVector, item); } } } @@ -1440,9 +1435,7 @@ std::shared_ptr Tile::getThing(size_t index) const { } void Tile::postAddNotification(std::shared_ptr thing, std::shared_ptr oldParent, int32_t index, CylinderLink_t link /*= LINK_OWNER*/) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), true, true); - for (auto spectator : spectators) { + for (const auto &spectator : Spectators().find(getPosition(), true)) { spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); } @@ -1483,14 +1476,13 @@ void Tile::postAddNotification(std::shared_ptr thing, std::shared_ptr thing, std::shared_ptr newParent, int32_t index, CylinderLink_t) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, getPosition(), true, true); + auto spectators = Spectators().find(getPosition(), true); if (getThingCount() > 8) { - onUpdateTile(spectators); + onUpdateTile(spectators.data()); } - for (auto spectator : spectators) { + for (const auto &spectator : spectators) { spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR); } @@ -1538,7 +1530,7 @@ void Tile::internalAddThing(uint32_t, std::shared_ptr thing) { std::shared_ptr creature = thing->getCreature(); if (creature) { - g_game().map.clearSpectatorCache(); + Spectators::clearCache(); CreatureVector* creatures = makeCreatures(); creatures->insert(creatures->begin(), creature); } else { diff --git a/src/items/tile.hpp b/src/items/tile.hpp index 35f5e36232a..2158d635de8 100644 --- a/src/items/tile.hpp +++ b/src/items/tile.hpp @@ -25,7 +25,6 @@ class Zone; using CreatureVector = std::vector>; using ItemVector = std::vector>; -using SpectatorHashSet = phmap::flat_hash_set>; class TileItemVector : private ItemVector { public: @@ -250,8 +249,8 @@ class Tile : public Cylinder, public SharedObject { private: void onAddTileItem(std::shared_ptr item); void onUpdateTileItem(std::shared_ptr oldItem, const ItemType &oldType, std::shared_ptr newItem, const ItemType &newType); - void onRemoveTileItem(const SpectatorHashSet &spectators, const std::vector &oldStackPosVector, std::shared_ptr item); - void onUpdateTile(const SpectatorHashSet &spectators); + void onRemoveTileItem(const CreatureVector &spectators, const std::vector &oldStackPosVector, std::shared_ptr item); + void onUpdateTile(const CreatureVector &spectators); void setTileFlags(std::shared_ptr item); void resetTileFlags(std::shared_ptr item); diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index c10d6988295..86358f1daf7 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -27,6 +27,7 @@ #include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" +#include "map/spectators.hpp" // Game int GameFunctions::luaGameCreateMonsterType(lua_State* L) { @@ -69,8 +70,13 @@ int GameFunctions::luaGameGetSpectators(lua_State* L) { int32_t minRangeY = getNumber(L, 6, 0); int32_t maxRangeY = getNumber(L, 7, 0); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, multifloor, onlyPlayers, minRangeX, maxRangeX, minRangeY, maxRangeY); + Spectators spectators; + + if (onlyPlayers) { + spectators.find(position, multifloor, minRangeX, maxRangeX, minRangeY, maxRangeY); + } else { + spectators.find(position, multifloor, minRangeX, maxRangeX, minRangeY, maxRangeY); + } lua_createtable(L, spectators.size(), 0); @@ -312,7 +318,6 @@ int GameFunctions::luaGameCreateItem(lua_State* L) { if (position.x != 0) { std::shared_ptr tile = g_game().map.getTile(position); if (!tile) { - if (!hasTable) { lua_pushnil(L); } @@ -321,7 +326,6 @@ int GameFunctions::luaGameCreateItem(lua_State* L) { ReturnValue ret = g_game().internalAddItem(tile, item, INDEX_WHEREEVER, FLAG_NOLIMIT); if (ret != RETURNVALUE_NOERROR) { - if (!hasTable) { lua_pushnil(L); } @@ -370,7 +374,6 @@ int GameFunctions::luaGameCreateContainer(lua_State* L) { const Position &position = getPosition(L, 3); std::shared_ptr tile = g_game().map.getTile(position); if (!tile) { - lua_pushnil(L); return 1; } @@ -388,7 +391,7 @@ int GameFunctions::luaGameCreateContainer(lua_State* L) { int GameFunctions::luaGameCreateMonster(lua_State* L) { // Game.createMonster(monsterName, position[, extended = false[, force = false[, master = nil]]]) - std::shared_ptr monster = Monster::createMonster(getString(L, 1)); + const auto &monster = Monster::createMonster(getString(L, 1)); if (!monster) { lua_pushnil(L); return 1; @@ -396,8 +399,7 @@ int GameFunctions::luaGameCreateMonster(lua_State* L) { bool isSummon = false; if (lua_gettop(L) >= 5) { - std::shared_ptr master = getCreature(L, 5); - if (master) { + if (const auto &master = getCreature(L, 5)) { monster->setMaster(master, true); isSummon = true; } @@ -409,13 +411,11 @@ int GameFunctions::luaGameCreateMonster(lua_State* L) { if (g_game().placeCreature(monster, position, extended, force)) { g_events().eventMonsterOnSpawn(monster, position); g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, position); - auto mtype = monster->getMonsterType(); + const auto &mtype = monster->getMonsterType(); if (mtype && mtype->info.raceid > 0 && mtype->info.bosstiaryRace == BosstiaryRarity_t::RARITY_ARCHFOE) { - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, monster->getPosition(), true); - for (std::shared_ptr spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - auto bossesOnTracker = g_ioBosstiary().getBosstiaryCooldownRaceId(tmpPlayer); + for (const auto &spectator : Spectators().find(monster->getPosition(), true)) { + if (const auto &tmpPlayer = spectator->getPlayer()) { + const auto &bossesOnTracker = g_ioBosstiary().getBosstiaryCooldownRaceId(tmpPlayer); // If not have boss to update, then kill loop for economize resources if (bossesOnTracker.size() == 0) { break; @@ -466,7 +466,6 @@ int GameFunctions::luaGameCreateNpc(lua_State* L) { pushUserdata(L, npc); setMetatable(L, -1, "Npc"); } else { - lua_pushnil(L); } return 1; diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index 6257bf1ea25..14d86f1e291 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -12,6 +12,7 @@ #include "game/game.hpp" #include "creatures/creature.hpp" #include "lua/functions/creatures/creature_functions.hpp" +#include "map/spectators.hpp" int CreatureFunctions::luaCreatureCreate(lua_State* L) { // Creature(id or name or userdata) @@ -817,7 +818,7 @@ int CreatureFunctions::luaCreatureSay(lua_State* L) { return 1; } - SpectatorHashSet spectators; + Spectators spectators; if (target) { spectators.insert(target); } diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp index fef180f08ae..60e60fe876c 100644 --- a/src/lua/functions/creatures/monster/monster_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_functions.cpp @@ -14,6 +14,7 @@ #include "creatures/monsters/monster.hpp" #include "creatures/monsters/monsters.hpp" #include "lua/functions/creatures/monster/monster_functions.hpp" +#include "map/spectators.hpp" int MonsterFunctions::luaMonsterCreate(lua_State* L) { // Monster(id or userdata) @@ -92,12 +93,8 @@ int MonsterFunctions::luaMonsterSetType(lua_State* L) { } } // Reload creature on spectators - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, monster->getPosition(), true); - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendCreatureReload(monster); - } + for (const auto &spectator : Spectators().find(monster->getPosition(), true)) { + spectator->getPlayer()->sendCreatureReload(monster); } pushBoolean(L, true); } else { diff --git a/src/lua/functions/creatures/npc/npc_functions.cpp b/src/lua/functions/creatures/npc/npc_functions.cpp index 56d8411d909..fc8c8272c68 100644 --- a/src/lua/functions/creatures/npc/npc_functions.cpp +++ b/src/lua/functions/creatures/npc/npc_functions.cpp @@ -13,6 +13,7 @@ #include "creatures/creature.hpp" #include "creatures/npcs/npc.hpp" #include "lua/functions/creatures/npc/npc_functions.hpp" +#include "map/spectators.hpp" int NpcFunctions::luaNpcCreate(lua_State* L) { // Npc([id or name or userdata]) @@ -190,7 +191,7 @@ int NpcFunctions::luaNpcSay(lua_State* L) { return 1; } - SpectatorHashSet spectators; + Spectators spectators; if (target) { spectators.insert(target); } @@ -535,7 +536,6 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { } if (g_game().internalPlayerAddItem(player, container, ignoreCap, CONST_SLOT_WHEREEVER) != RETURNVALUE_NOERROR) { - break; } @@ -556,7 +556,6 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { while (remainingAmount > 0) { if (g_game().internalAddItem(container->getContainer(), item, INDEX_WHEREEVER, 0) != RETURNVALUE_NOERROR) { - break; } @@ -589,7 +588,6 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { while (remainingAmount > 0) { if (g_game().internalPlayerAddItem(player, item, ignoreCap, CONST_SLOT_WHEREEVER) != RETURNVALUE_NOERROR) { - break; } diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index eebf081e2bc..38debb995ff 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -19,6 +19,7 @@ #include "io/ioprey.hpp" #include "items/item.hpp" #include "lua/functions/creatures/player/player_functions.hpp" +#include "map/spectators.hpp" int PlayerFunctions::luaPlayerSendInventory(lua_State* L) { // player:sendInventory() @@ -2947,10 +2948,8 @@ int PlayerFunctions::luaPlayerSetGhostMode(lua_State* L) { std::shared_ptr tile = player->getTile(); const Position &position = player->getPosition(); - SpectatorHashSet spectators; - g_game().map.getSpectators(spectators, position, true, true); - for (auto spectator : spectators) { - auto tmpPlayer = spectator->getPlayer(); + for (const auto &spectator : Spectators().find(position, true)) { + const auto &tmpPlayer = spectator->getPlayer(); if (tmpPlayer != player && !tmpPlayer->isAccessPlayer()) { if (enabled) { tmpPlayer->sendRemoveTileThing(position, tile->getStackposOfCreature(tmpPlayer, player)); diff --git a/src/lua/functions/map/position_functions.cpp b/src/lua/functions/map/position_functions.cpp index ba0633a819e..5cee3f2d847 100644 --- a/src/lua/functions/map/position_functions.cpp +++ b/src/lua/functions/map/position_functions.cpp @@ -12,6 +12,7 @@ #include "game/game.hpp" #include "game/movement/position.hpp" #include "lua/functions/map/position_functions.hpp" +#include "map/spectators.hpp" int PositionFunctions::luaPositionCreate(lua_State* L) { // Position([x = 0[, y = 0[, z = 0[, stackpos = 0]]]]) @@ -147,11 +148,10 @@ int PositionFunctions::luaPositionGetZones(lua_State* L) { int PositionFunctions::luaPositionSendMagicEffect(lua_State* L) { // position:sendMagicEffect(magicEffect[, player = nullptr]) - SpectatorHashSet spectators; + CreatureVector spectators; if (lua_gettop(L) >= 3) { - std::shared_ptr player = getPlayer(L, 3); - if (player) { - spectators.insert(player); + if (const auto &player = getPlayer(L, 3)) { + spectators.emplace_back(player); } } @@ -175,11 +175,10 @@ int PositionFunctions::luaPositionSendMagicEffect(lua_State* L) { int PositionFunctions::luaPositionRemoveMagicEffect(lua_State* L) { // position:removeMagicEffect(magicEffect[, player = nullptr]) - SpectatorHashSet spectators; + CreatureVector spectators; if (lua_gettop(L) >= 3) { - std::shared_ptr player = getPlayer(L, 3); - if (player) { - spectators.insert(player); + if (const auto &player = getPlayer(L, 3)) { + spectators.emplace_back(player); } } @@ -203,11 +202,10 @@ int PositionFunctions::luaPositionRemoveMagicEffect(lua_State* L) { int PositionFunctions::luaPositionSendDistanceEffect(lua_State* L) { // position:sendDistanceEffect(positionEx, distanceEffect[, player = nullptr]) - SpectatorHashSet spectators; + CreatureVector spectators; if (lua_gettop(L) >= 4) { - std::shared_ptr player = getPlayer(L, 4); - if (player) { - spectators.insert(player); + if (const auto &player = getPlayer(L, 4)) { + spectators.emplace_back(player); } } diff --git a/src/map/CMakeLists.txt b/src/map/CMakeLists.txt index d8ce78b7a59..7d744950c1f 100644 --- a/src/map/CMakeLists.txt +++ b/src/map/CMakeLists.txt @@ -5,4 +5,5 @@ target_sources(${PROJECT_NAME}_lib PRIVATE utils/qtreenode.cpp map.cpp mapcache.cpp + spectators.cpp ) diff --git a/src/map/map.cpp b/src/map/map.cpp index 17e678e7338..39fbe47872d 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -17,6 +17,7 @@ #include "game/zones/zone.hpp" #include "io/iomap.hpp" #include "io/iomapserialize.hpp" +#include "map/spectators.hpp" void Map::load(const std::string &identifier, const Position &pos) { try { @@ -291,18 +292,18 @@ void Map::moveCreature(const std::shared_ptr &creature, const std::sha bool teleport = forceTeleport || !newTile->getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos); - SpectatorHashSet spectators; - getSpectators(spectators, oldPos, true); - getSpectators(spectators, newPos, true); + auto spectators = Spectators() + .find(oldPos, true) + .find(newPos, true); + + auto playersSpectators = spectators.filter(); std::vector oldStackPosVector; - for (std::shared_ptr spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - if (tmpPlayer->canSeeCreature(creature)) { - oldStackPosVector.push_back(oldTile->getClientIndexOfCreature(tmpPlayer, creature)); - } else { - oldStackPosVector.push_back(-1); - } + for (const auto &spec : playersSpectators) { + if (spec->canSeeCreature(creature)) { + oldStackPosVector.push_back(oldTile->getClientIndexOfCreature(spec->getPlayer(), creature)); + } else { + oldStackPosVector.push_back(-1); } } @@ -337,18 +338,17 @@ void Map::moveCreature(const std::shared_ptr &creature, const std::sha // send to client size_t i = 0; - for (auto spectator : spectators) { - if (auto tmpPlayer = spectator->getPlayer()) { - // Use the correct stackpos - int32_t stackpos = oldStackPosVector[i++]; - if (stackpos != -1) { - tmpPlayer->sendCreatureMove(creature, newPos, newTile->getStackposOfCreature(tmpPlayer, creature), oldPos, stackpos, teleport); - } + for (const auto &spectator : playersSpectators) { + // Use the correct stackpos + int32_t stackpos = oldStackPosVector[i++]; + if (stackpos != -1) { + const auto &player = spectator->getPlayer(); + player->sendCreatureMove(creature, newPos, newTile->getStackposOfCreature(player, creature), oldPos, stackpos, teleport); } } // event method - for (std::shared_ptr spectator : spectators) { + for (const auto &spectator : spectators) { spectator->onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); } @@ -357,158 +357,6 @@ void Map::moveCreature(const std::shared_ptr &creature, const std::sha g_game().afterCreatureZoneChange(creature, fromZones, toZones); } -void Map::getSpectatorsInternal(SpectatorHashSet &spectators, const Position ¢erPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const { - int_fast32_t min_y = centerPos.y + minRangeY; - int_fast32_t min_x = centerPos.x + minRangeX; - int_fast32_t max_y = centerPos.y + maxRangeY; - int_fast32_t max_x = centerPos.x + maxRangeX; - - int32_t minoffset = centerPos.getZ() - maxRangeZ; - uint16_t x1 = std::min(0xFFFF, std::max(0, (min_x + minoffset))); - uint16_t y1 = std::min(0xFFFF, std::max(0, (min_y + minoffset))); - - int32_t maxoffset = centerPos.getZ() - minRangeZ; - uint16_t x2 = std::min(0xFFFF, std::max(0, (max_x + maxoffset))); - uint16_t y2 = std::min(0xFFFF, std::max(0, (max_y + maxoffset))); - - int32_t startx1 = x1 - (x1 % FLOOR_SIZE); - int32_t starty1 = y1 - (y1 % FLOOR_SIZE); - int32_t endx2 = x2 - (x2 % FLOOR_SIZE); - int32_t endy2 = y2 - (y2 % FLOOR_SIZE); - - const auto startLeaf = QTreeNode::getLeafStatic(&root, startx1, starty1); - const QTreeLeafNode* leafS = startLeaf; - const QTreeLeafNode* leafE; - - for (int_fast32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) { - leafE = leafS; - for (int_fast32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) { - if (leafE) { - const auto node_list = (onlyPlayers ? leafE->player_list : leafE->creature_list); - for (std::shared_ptr creature : node_list) { - const Position &cpos = creature->getPosition(); - if (minRangeZ > cpos.z || maxRangeZ < cpos.z) { - continue; - } - - int_fast16_t offsetZ = Position::getOffsetZ(centerPos, cpos); - if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || (max_x + offsetZ) < cpos.x) { - continue; - } - - spectators.insert(creature); - } - leafE = leafE->leafE; - } else { - leafE = QTreeNode::getLeafStatic(&root, nx + FLOOR_SIZE, ny); - } - } - - if (leafS) { - leafS = leafS->leafS; - } else { - leafS = QTreeNode::getLeafStatic(&root, startx1, ny + FLOOR_SIZE); - } - } -} - -void Map::getSpectators(SpectatorHashSet &spectators, const Position ¢erPos, bool multifloor /*= false*/, bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) { - if (centerPos.z >= MAP_MAX_LAYERS) { - return; - } - - bool foundCache = false; - bool cacheResult = false; - - minRangeX = (minRangeX == 0 ? -MAP_MAX_VIEW_PORT_X : -minRangeX); - maxRangeX = (maxRangeX == 0 ? MAP_MAX_VIEW_PORT_X : maxRangeX); - minRangeY = (minRangeY == 0 ? -MAP_MAX_VIEW_PORT_Y : -minRangeY); - maxRangeY = (maxRangeY == 0 ? MAP_MAX_VIEW_PORT_Y : maxRangeY); - - if (minRangeX == -MAP_MAX_VIEW_PORT_X && maxRangeX == MAP_MAX_VIEW_PORT_X && minRangeY == -MAP_MAX_VIEW_PORT_Y && maxRangeY == MAP_MAX_VIEW_PORT_Y && multifloor) { - if (onlyPlayers) { - auto it = playersSpectatorCache.find(centerPos); - if (it != playersSpectatorCache.end()) { - if (!spectators.empty()) { - const SpectatorHashSet &cachedSpectators = it->second; - spectators.insert(cachedSpectators.begin(), cachedSpectators.end()); - } else { - spectators = it->second; - } - - foundCache = true; - } - } - - if (!foundCache) { - auto it = spectatorCache.find(centerPos); - if (it != spectatorCache.end()) { - if (!onlyPlayers) { - if (!spectators.empty()) { - const SpectatorHashSet &cachedSpectators = it->second; - spectators.insert(cachedSpectators.begin(), cachedSpectators.end()); - } else { - spectators = it->second; - } - } else { - const SpectatorHashSet &cachedSpectators = it->second; - for (std::shared_ptr spectator : cachedSpectators) { - if (spectator->getPlayer()) { - spectators.insert(spectator); - } - } - } - - foundCache = true; - } else { - cacheResult = true; - } - } - } - - if (!foundCache) { - int32_t minRangeZ; - int32_t maxRangeZ; - - if (multifloor) { - if (centerPos.z > MAP_INIT_SURFACE_LAYER) { - // underground - - // 8->15 - minRangeZ = std::max(centerPos.getZ() - MAP_LAYER_VIEW_LIMIT, 0); - maxRangeZ = std::min(centerPos.getZ() + MAP_LAYER_VIEW_LIMIT, MAP_MAX_LAYERS - 1); - } else if (centerPos.z == MAP_INIT_SURFACE_LAYER - 1) { - minRangeZ = 0; - maxRangeZ = (MAP_INIT_SURFACE_LAYER - 1) + MAP_LAYER_VIEW_LIMIT; - } else if (centerPos.z == MAP_INIT_SURFACE_LAYER) { - minRangeZ = 0; - maxRangeZ = MAP_INIT_SURFACE_LAYER + MAP_LAYER_VIEW_LIMIT; - } else { - minRangeZ = 0; - maxRangeZ = MAP_INIT_SURFACE_LAYER; - } - } else { - minRangeZ = centerPos.z; - maxRangeZ = centerPos.z; - } - - getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); - - if (cacheResult) { - if (onlyPlayers) { - playersSpectatorCache[centerPos] = spectators; - } else { - spectatorCache[centerPos] = spectators; - } - } - } -} - -void Map::clearSpectatorCache() { - spectatorCache.clear(); - playersSpectatorCache.clear(); -} - bool Map::canThrowObjectTo(const Position &fromPos, const Position &toPos, bool checkLineOfSight /*= true*/, int32_t rangex /*= MAP_MAX_CLIENT_VIEW_PORT_X*/, int32_t rangey /*= MAP_MAX_CLIENT_VIEW_PORT_Y*/) { // z checks // underground 8->15 diff --git a/src/map/map.hpp b/src/map/map.hpp index 0a98fc23a52..501f7578b6d 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -23,8 +23,6 @@ class Map; struct FindPathParams; -using SpectatorCache = std::map; - class FrozenPathingConditionCall; /** @@ -96,10 +94,6 @@ class Map : protected MapCache { void moveCreature(const std::shared_ptr &creature, const std::shared_ptr &newTile, bool forceTeleport = false); - void getSpectators(SpectatorHashSet &spectators, const Position ¢erPos, bool multifloor = false, bool onlyPlayers = false, int32_t minRangeX = 0, int32_t maxRangeX = 0, int32_t minRangeY = 0, int32_t maxRangeY = 0); - - void clearSpectatorCache(); - /** * Checks if you can throw an object to that position * \param fromPos from Source point @@ -154,9 +148,6 @@ class Map : protected MapCache { setTile(pos.x, pos.y, pos.z, newTile); } - SpectatorCache spectatorCache; - SpectatorCache playersSpectatorCache; - std::filesystem::path path; std::string monsterfile; std::string housefile; @@ -165,9 +156,6 @@ class Map : protected MapCache { uint32_t width = 0; uint32_t height = 0; - // Actually scans the map for spectators - void getSpectatorsInternal(SpectatorHashSet &spectators, const Position ¢erPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const; - friend class Game; friend class IOMap; friend class MapCache; diff --git a/src/map/spectators.cpp b/src/map/spectators.cpp new file mode 100644 index 00000000000..7eabde6850c --- /dev/null +++ b/src/map/spectators.cpp @@ -0,0 +1,182 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#include "spectators.hpp" +#include "game/game.hpp" + +phmap::flat_hash_map Spectators::spectatorsCache; + +void Spectators::clearCache() { + spectatorsCache.clear(); +} + +bool Spectators::checkCache(const SpectatorsCache::FloorData &specData, bool onlyPlayers, const Position ¢erPos, bool checkDistance, bool multifloor, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY) { + const auto &list = multifloor || !specData.floor ? specData.multiFloor : specData.floor; + + if (!list) { + return false; + } + + if (!multifloor && !specData.floor) { + // Force check the distance of creatures as we only need to pick up creatures from the Floor(centerPos.z) + checkDistance = true; + } + + if (checkDistance) { + SpectatorList spectators; + spectators.reserve(creatures.size()); + for (const auto creature : *list) { + const auto &specPos = creature->getPosition(); + if (centerPos.x - specPos.x >= minRangeX + && centerPos.y - specPos.y >= minRangeY + && centerPos.x - specPos.x <= maxRangeX + && centerPos.y - specPos.y <= maxRangeY + && (multifloor || specPos.z == centerPos.z) + && (!onlyPlayers || creature->getPlayer())) { + spectators.emplace_back(creature); + } + } + insertAll(spectators); + } else { + insertAll(*list); + } + + return true; +} + +Spectators Spectators::find(const Position ¢erPos, bool multifloor, bool onlyPlayers, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY) { + minRangeX = (minRangeX == 0 ? -MAP_MAX_VIEW_PORT_X : -minRangeX); + maxRangeX = (maxRangeX == 0 ? MAP_MAX_VIEW_PORT_X : maxRangeX); + minRangeY = (minRangeY == 0 ? -MAP_MAX_VIEW_PORT_Y : -minRangeY); + maxRangeY = (maxRangeY == 0 ? MAP_MAX_VIEW_PORT_Y : maxRangeY); + + const auto &it = spectatorsCache.find(centerPos); + const bool cacheFound = it != spectatorsCache.end(); + if (cacheFound) { + auto &cache = it->second; + if (minRangeX < cache.minRangeX || maxRangeX > cache.maxRangeX || minRangeY < cache.minRangeY || maxRangeY > cache.maxRangeY) { + // recache with new range + cache.minRangeX = minRangeX = std::min(minRangeX, cache.minRangeX); + cache.minRangeY = minRangeY = std::min(minRangeY, cache.minRangeY); + cache.maxRangeX = maxRangeX = std::max(maxRangeX, cache.maxRangeX); + cache.maxRangeY = maxRangeY = std::max(maxRangeY, cache.maxRangeY); + } else { + const bool checkDistance = minRangeX != cache.minRangeX || maxRangeX != cache.maxRangeX || minRangeY != cache.minRangeY || maxRangeY != cache.maxRangeY; + + if (onlyPlayers) { + // check players cache + if (checkCache(cache.players, true, centerPos, checkDistance, multifloor, minRangeX, maxRangeX, minRangeY, maxRangeY)) { + return *this; + } + + // if there is no player cache, look for players in the creatures cache. + if (checkCache(cache.creatures, true, centerPos, true, multifloor, minRangeX, maxRangeX, minRangeY, maxRangeY)) { + return *this; + } + + // All Creatures + } else if (checkCache(cache.creatures, false, centerPos, checkDistance, multifloor, minRangeX, maxRangeX, minRangeY, maxRangeY)) { + return *this; + } + } + } + + uint8_t minRangeZ = centerPos.z; + uint8_t maxRangeZ = centerPos.z; + + if (multifloor) { + if (centerPos.z > MAP_INIT_SURFACE_LAYER) { + minRangeZ = static_cast(std::max(centerPos.z - MAP_LAYER_VIEW_LIMIT, 0u)); + maxRangeZ = static_cast(std::min(centerPos.z + MAP_LAYER_VIEW_LIMIT, MAP_MAX_LAYERS - 1)); + } else if (centerPos.z == MAP_INIT_SURFACE_LAYER - 1) { + minRangeZ = 0; + maxRangeZ = (MAP_INIT_SURFACE_LAYER - 1) + MAP_LAYER_VIEW_LIMIT; + } else if (centerPos.z == MAP_INIT_SURFACE_LAYER) { + minRangeZ = 0; + maxRangeZ = MAP_INIT_SURFACE_LAYER + MAP_LAYER_VIEW_LIMIT; + } else { + minRangeZ = 0; + maxRangeZ = MAP_INIT_SURFACE_LAYER; + } + } + + const int_fast32_t min_y = centerPos.y + minRangeY; + const int_fast32_t min_x = centerPos.x + minRangeX; + const int_fast32_t max_y = centerPos.y + maxRangeY; + const int_fast32_t max_x = centerPos.x + maxRangeX; + + const int_fast16_t minoffset = centerPos.getZ() - maxRangeZ; + const int_fast32_t x1 = std::min(0xFFFF, std::max(0, (min_x + minoffset))); + const int_fast32_t y1 = std::min(0xFFFF, std::max(0, (min_y + minoffset))); + + const int_fast16_t maxoffset = centerPos.getZ() - minRangeZ; + const int_fast32_t x2 = std::min(0xFFFF, std::max(0, (max_x + maxoffset))); + const int_fast32_t y2 = std::min(0xFFFF, std::max(0, (max_y + maxoffset))); + + const uint_fast16_t startx1 = x1 - (x1 % FLOOR_SIZE); + const uint_fast16_t starty1 = y1 - (y1 % FLOOR_SIZE); + const uint_fast16_t endx2 = x2 - (x2 % FLOOR_SIZE); + const uint_fast16_t endy2 = y2 - (y2 % FLOOR_SIZE); + + const auto startLeaf = g_game().map.getQTNode(static_cast(startx1), static_cast(starty1)); + const QTreeLeafNode* leafS = startLeaf; + const QTreeLeafNode* leafE; + + SpectatorList spectators; + spectators.reserve(std::max(MAP_MAX_VIEW_PORT_X, MAP_MAX_VIEW_PORT_Y) * 2); + + for (uint_fast16_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) { + leafE = leafS; + for (uint_fast16_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) { + if (leafE) { + const auto &node_list = (onlyPlayers ? leafE->player_list : leafE->creature_list); + for (const auto creature : node_list) { + const auto &cpos = creature->getPosition(); + if (minRangeZ > cpos.z || maxRangeZ < cpos.z) { + continue; + } + + const int_fast16_t offsetZ = Position::getOffsetZ(centerPos, cpos); + if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || (max_x + offsetZ) < cpos.x) { + continue; + } + + spectators.emplace_back(creature); + } + leafE = leafE->leafE; + } else { + leafE = g_game().map.getQTNode(static_cast(nx + FLOOR_SIZE), static_cast(ny)); + } + } + + if (leafS) { + leafS = leafS->leafS; + } else { + leafS = g_game().map.getQTNode(static_cast(startx1), static_cast(ny + FLOOR_SIZE)); + } + } + + // It is necessary to create the cache even if no spectators is found, so that there is no future query. + auto &cache = cacheFound ? it->second : spectatorsCache.emplace(centerPos, SpectatorsCache { .minRangeX = minRangeX, .maxRangeX = maxRangeX, .minRangeY = minRangeY, .maxRangeY = maxRangeY }).first->second; + auto &creaturesCache = onlyPlayers ? cache.players : cache.creatures; + auto &creatureList = (multifloor ? creaturesCache.multiFloor : creaturesCache.floor); + if (creatureList) { + creatureList->clear(); + } else { + creatureList.emplace(); + } + + if (!spectators.empty()) { + insertAll(spectators); + + creatureList->insert(creatureList->end(), spectators.begin(), spectators.end()); + } + + return *this; +} diff --git a/src/map/spectators.hpp b/src/map/spectators.hpp new file mode 100644 index 00000000000..886a51d8c1f --- /dev/null +++ b/src/map/spectators.hpp @@ -0,0 +1,135 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include "pch.hpp" +#include "creatures/creature.hpp" + +class Player; +class Monster; +class Npc; +struct Position; + +using SpectatorList = std::vector>; + +struct SpectatorsCache { + struct FloorData { + std::optional floor; + std::optional multiFloor; + }; + + int32_t minRangeX { 0 }; + int32_t maxRangeX { 0 }; + int32_t minRangeY { 0 }; + int32_t maxRangeY { 0 }; + + FloorData creatures; + FloorData players; +}; + +class Spectators { +public: + static void clearCache(); + + template + requires std::is_same_v || std::is_same_v + Spectators find(const Position ¢erPos, bool multifloor = false, int32_t minRangeX = 0, int32_t maxRangeX = 0, int32_t minRangeY = 0, int32_t maxRangeY = 0) { + constexpr bool onlyPlayers = std::is_same_v; + return find(centerPos, multifloor, onlyPlayers, minRangeX, maxRangeX, minRangeY, maxRangeY); + } + + template + requires std::is_base_of_v + Spectators filter(); + + bool contains(const std::shared_ptr &creature) { + return creatures.contains(creature); + } + + bool erase(const std::shared_ptr &creature) { + return creatures.erase(creature); + } + + template + bool erase_if(F fnc) { + return std::erase_if(creatures, std::move(fnc)) > 0; + } + + Spectators insert(const std::shared_ptr &creature) { + if (creature) { + creatures.emplace_back(creature); + } + return *this; + } + + Spectators insertAll(const SpectatorList &list) { + if (!list.empty()) { + creatures.insertAll(list); + } + return *this; + } + + Spectators join(Spectators &anotherSpectators) { + return insertAll(anotherSpectators.creatures.data()); + } + + bool empty() const noexcept { + return creatures.empty(); + } + + size_t size() noexcept { + return creatures.size(); + } + + auto begin() noexcept { + return creatures.begin(); + } + + auto end() noexcept { + return creatures.end(); + } + + const auto &data() noexcept { + return creatures.data(); + } + +private: + static phmap::flat_hash_map spectatorsCache; + + Spectators find(const Position ¢erPos, bool multifloor = false, bool onlyPlayers = false, int32_t minRangeX = 0, int32_t maxRangeX = 0, int32_t minRangeY = 0, int32_t maxRangeY = 0); + bool checkCache(const SpectatorsCache::FloorData &specData, bool onlyPlayers, const Position ¢erPos, bool checkDistance, bool multifloor, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY); + + stdext::vector_set> creatures; +}; + +template + requires std::is_base_of_v +Spectators Spectators::filter() { + auto specs = Spectators(); + specs.creatures.reserve(creatures.size()); + + for (const auto &c : creatures) { + if constexpr (std::is_same_v) { + if (c->getPlayer() != nullptr) { + specs.insert(c); + } + } else if constexpr (std::is_same_v) { + if (c->getMonster() != nullptr) { + specs.insert(c); + } + } else if constexpr (std::is_same_v) { + if (c->getNpc() != nullptr) { + specs.insert(c); + } + } + } + + return specs; +} diff --git a/src/map/utils/qtreenode.cpp b/src/map/utils/qtreenode.cpp index 06cfef62f87..92d0a0423ee 100644 --- a/src/map/utils/qtreenode.cpp +++ b/src/map/utils/qtreenode.cpp @@ -71,7 +71,7 @@ QTreeLeafNode* QTreeNode::getBestLeaf(uint32_t x, uint32_t y, uint32_t level) { return tempLeaf; } -void QTreeLeafNode::addCreature(std::shared_ptr c) { +void QTreeLeafNode::addCreature(const std::shared_ptr &c) { creature_list.push_back(c); if (c->getPlayer()) { diff --git a/src/map/utils/qtreenode.hpp b/src/map/utils/qtreenode.hpp index aefa5fcd28c..45c95e554fc 100644 --- a/src/map/utils/qtreenode.hpp +++ b/src/map/utils/qtreenode.hpp @@ -76,7 +76,7 @@ class QTreeLeafNode final : public QTreeNode { return array[z]; } - void addCreature(std::shared_ptr c); + void addCreature(const std::shared_ptr &c); void removeCreature(std::shared_ptr c); private: @@ -92,4 +92,5 @@ class QTreeLeafNode final : public QTreeNode { friend class Map; friend class MapCache; friend class QTreeNode; + friend class Spectators; }; diff --git a/src/pch.hpp b/src/pch.hpp index 1c6100611a3..c2308989b8f 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -17,6 +17,7 @@ #include "utils/benchmark.hpp" #include "utils/definitions.hpp" #include "utils/simd.hpp" +#include "utils/vectorset.hpp" // -------------------- // STL Includes diff --git a/src/utils/vectorset.hpp b/src/utils/vectorset.hpp new file mode 100644 index 00000000000..29c6b37665e --- /dev/null +++ b/src/utils/vectorset.hpp @@ -0,0 +1,113 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include +#include + +// # Mehah +// vector_set is an container that contains a sorted set of unique objects. +// Note: this is faster than std::set + +namespace stdext { + template + class vector_set { + public: + bool contains(const T &v) { + update(); + return v && std::ranges::binary_search(container, v); + } + + bool erase(const T &v) { + update(); + + const auto &it = std::ranges::lower_bound(container, v); + if (it == container.end()) { + return false; + } + container.erase(it); + + return true; + } + + template + bool erase_if(F fnc) { + update(); + return std::erase_if(container, std::move(fnc)) > 0; + } + + void push_back(const T &v) { + needUpdate = true; + return container.push_back(v); + } + + template + auto emplace_back(_Valty &&... v) { + needUpdate = true; + return container.emplace_back(v...); + } + + auto insertAll(const vector_set &list) { + needUpdate = true; + return container.insert(container.end(), list.begin(), list.end()); + } + + auto insertAll(const std::vector &list) { + needUpdate = true; + return container.insert(container.end(), list.begin(), list.end()); + } + + bool empty() const noexcept { + return container.empty(); + } + + size_t size() noexcept { + update(); + return container.size(); + } + + auto begin() noexcept { + update(); + return container.begin(); + } + + auto end() noexcept { + return container.end(); + } + + void clear() noexcept { + return container.clear(); + } + + void reserve(size_t newCap) noexcept { + container.reserve(newCap); + } + + const auto &data() noexcept { + update(); + return container; + } + + private: + void update() noexcept { + if (!needUpdate) { + return; + } + + needUpdate = false; + std::ranges::sort(container); + const auto &[f, l] = std::ranges::unique(container); + container.erase(f, l); + } + + std::vector container; + bool needUpdate = false; + }; +} diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index 0564dcae626..f3a4530b260 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -194,6 +194,7 @@ + @@ -219,6 +220,7 @@ + @@ -369,6 +371,7 @@ + From 992816b91627c9ef913d4e1a4882aab19f4841d6 Mon Sep 17 00:00:00 2001 From: Beats Date: Tue, 3 Oct 2023 16:53:58 -0300 Subject: [PATCH 09/18] improve: migrate ChatChannel | PrivateChatChannel and derivatives to shared_ptr (#1630) --- src/creatures/interactions/chat.cpp | 110 +++++++++--------- src/creatures/interactions/chat.hpp | 26 ++--- src/game/game.cpp | 8 +- .../functions/core/game/global_functions.cpp | 4 +- .../creatures/player/player_functions.cpp | 2 +- src/server/network/protocol/protocolgame.cpp | 2 +- 6 files changed, 76 insertions(+), 76 deletions(-) diff --git a/src/creatures/interactions/chat.cpp b/src/creatures/interactions/chat.cpp index d58da460728..3dba27371a3 100644 --- a/src/creatures/interactions/chat.cpp +++ b/src/creatures/interactions/chat.cpp @@ -262,7 +262,7 @@ bool ChatChannel::executeOnSpeakEvent(const std::shared_ptr &player, Spe Chat::Chat() : scriptInterface("Chat Interface"), - dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") { + dummyPrivate(std::make_shared(CHANNEL_PRIVATE, "Private Chat Channel")) { scriptInterface.initState(); } @@ -284,48 +284,48 @@ bool Chat::load() { auto it = normalChannels.find(channelId); if (it != normalChannels.end()) { - ChatChannel &channel = it->second; - channel.publicChannel = isPublic; - channel.name = channelName; + const auto &channel = it->second; + channel->publicChannel = isPublic; + channel->name = channelName; if (scriptAttribute) { if (scriptInterface.loadFile(coreFolder + "/chatchannels/scripts/" + std::string(scriptAttribute.as_string()), scriptAttribute.as_string()) == 0) { - channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); - channel.canJoinEvent = scriptInterface.getEvent("canJoin"); - channel.onJoinEvent = scriptInterface.getEvent("onJoin"); - channel.onLeaveEvent = scriptInterface.getEvent("onLeave"); + channel->onSpeakEvent = scriptInterface.getEvent("onSpeak"); + channel->canJoinEvent = scriptInterface.getEvent("canJoin"); + channel->onJoinEvent = scriptInterface.getEvent("onJoin"); + channel->onLeaveEvent = scriptInterface.getEvent("onLeave"); } else { g_logger().warn("[Chat::load] - Can not load script: {}", scriptAttribute.as_string()); } } - UsersMap tempUserMap = std::move(channel.users); + UsersMap tempUserMap = std::move(channel->users); for (const auto &pair : tempUserMap) { - channel.addUser(pair.second); + channel->addUser(pair.second); } continue; } - ChatChannel channel(channelId, channelName); - channel.publicChannel = isPublic; + const auto &channel(std::make_shared(channelId, channelName)); + channel->publicChannel = isPublic; if (scriptAttribute) { if (scriptInterface.loadFile(coreFolder + "/chatchannels/scripts/" + std::string(scriptAttribute.as_string()), scriptAttribute.as_string()) == 0) { - channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); - channel.canJoinEvent = scriptInterface.getEvent("canJoin"); - channel.onJoinEvent = scriptInterface.getEvent("onJoin"); - channel.onLeaveEvent = scriptInterface.getEvent("onLeave"); + channel->onSpeakEvent = scriptInterface.getEvent("onSpeak"); + channel->canJoinEvent = scriptInterface.getEvent("canJoin"); + channel->onJoinEvent = scriptInterface.getEvent("onJoin"); + channel->onLeaveEvent = scriptInterface.getEvent("onLeave"); } else { g_logger().warn("[Chat::load] Can not load script: {}", scriptAttribute.as_string()); } } - normalChannels[channel.id] = channel; + normalChannels[channel->id] = channel; } return true; } -ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t channelId) { +std::shared_ptr Chat::createChannel(const std::shared_ptr &player, uint16_t channelId) { if (getChannel(player, channelId) != nullptr) { return nullptr; } @@ -334,8 +334,8 @@ ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t case CHANNEL_GUILD: { const auto guild = player->getGuild(); if (guild != nullptr) { - auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName()))); - return &ret.first->second; + auto ret = guildChannels.emplace(std::make_pair(guild->getId(), std::make_shared(channelId, guild->getName()))); + return ret.first->second; } break; } @@ -343,8 +343,8 @@ ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t case CHANNEL_PARTY: { auto party = player->getParty(); if (party != nullptr) { - auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party"))); - return &ret.first->second; + auto ret = partyChannels.emplace(std::make_pair(party, std::make_shared(channelId, "Party"))); + return ret.first->second; } break; } @@ -357,11 +357,11 @@ ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t // find a free private channel slot for (uint16_t i = 100; i < 10000; ++i) { - auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player->getName() + "'s Channel"))); + auto ret = privateChannels.emplace(std::make_pair(i, std::make_shared(i, player->getName() + "'s Channel"))); if (ret.second) { // second is a bool that indicates that a new channel has been placed in the map - auto &newChannel = (*ret.first).second; - newChannel.setOwner(player->getGUID()); - return &newChannel; + const auto &newChannel = (*ret.first).second; + newChannel->setOwner(player->getGUID()); + return newChannel; } } break; @@ -411,7 +411,7 @@ bool Chat::deleteChannel(const std::shared_ptr &player, uint16_t channel return false; } - it->second.closeChannel(); + it->second->closeChannel(); privateChannels.erase(it); break; @@ -420,8 +420,8 @@ bool Chat::deleteChannel(const std::shared_ptr &player, uint16_t channel return true; } -ChatChannel* Chat::addUserToChannel(const std::shared_ptr &player, uint16_t channelId) { - ChatChannel* channel = getChannel(player, channelId); +std::shared_ptr Chat::addUserToChannel(const std::shared_ptr &player, uint16_t channelId) { + const auto &channel = getChannel(player, channelId); if ((channel != nullptr) && channel->addUser(player)) { return channel; } @@ -429,7 +429,7 @@ ChatChannel* Chat::addUserToChannel(const std::shared_ptr &player, uint1 } bool Chat::removeUserFromChannel(const std::shared_ptr &player, uint16_t channelId) { - ChatChannel* channel = getChannel(player, channelId); + const auto &channel = getChannel(player, channelId); if ((channel == nullptr) || !channel->removeUser(player)) { return false; } @@ -442,20 +442,20 @@ bool Chat::removeUserFromChannel(const std::shared_ptr &player, uint16_t void Chat::removeUserFromAllChannels(const std::shared_ptr &player) { for (auto &it : normalChannels) { - it.second.removeUser(player); + it.second->removeUser(player); } for (auto &it : partyChannels) { - it.second.removeUser(player); + it.second->removeUser(player); } for (auto &it : guildChannels) { - it.second.removeUser(player); + it.second->removeUser(player); } auto it = privateChannels.begin(); while (it != privateChannels.end()) { - PrivateChatChannel* channel = &it->second; + const auto &channel = it->second; channel->removeInvite(player->getGUID()); channel->removeUser(player); if (channel->getOwner() == player->getGUID()) { @@ -468,7 +468,7 @@ void Chat::removeUserFromAllChannels(const std::shared_ptr &player) { } bool Chat::talkToChannel(const std::shared_ptr &player, SpeakClasses type, const std::string &text, uint16_t channelId) { - ChatChannel* channel = getChannel(player, channelId); + const auto &channel = getChannel(player, channelId); if (channel == nullptr) { return false; } @@ -494,7 +494,7 @@ bool Chat::talkToChannel(const std::shared_ptr &player, SpeakClasses typ ChannelList Chat::getChannelList(const std::shared_ptr &player) { ChannelList list; if (player->getGuild()) { - ChatChannel* channel = getChannel(player, CHANNEL_GUILD); + auto channel = getChannel(player, CHANNEL_GUILD); if (channel) { list.push_back(channel); } else { @@ -506,7 +506,7 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) { } if (player->getParty()) { - ChatChannel* channel = getChannel(player, CHANNEL_PARTY); + auto channel = getChannel(player, CHANNEL_PARTY); if (channel) { list.push_back(channel); } else { @@ -518,7 +518,7 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) { } for (const auto &it : normalChannels) { - ChatChannel* channel = getChannel(player, it.first); + const auto &channel = getChannel(player, it.first); if (channel) { list.push_back(channel); } @@ -526,7 +526,7 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) { bool hasPrivate = false; for (auto &it : privateChannels) { - if (PrivateChatChannel* channel = &it.second) { + if (const auto &channel = it.second) { uint32_t guid = player->getGUID(); if (channel->isInvited(guid)) { list.push_back(channel); @@ -539,19 +539,19 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) { } if (!hasPrivate && player->isPremium()) { - list.push_front(&dummyPrivate); + list.push_front(dummyPrivate); } return list; } -ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t channelId) { +std::shared_ptr Chat::getChannel(const std::shared_ptr &player, uint16_t channelId) { switch (channelId) { case CHANNEL_GUILD: { const auto guild = player->getGuild(); if (guild != nullptr) { auto it = guildChannels.find(guild->getId()); if (it != guildChannels.end()) { - return &it->second; + return it->second; } } break; @@ -562,7 +562,7 @@ ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t ch if (party != nullptr) { auto it = partyChannels.find(party); if (it != partyChannels.end()) { - return &it->second; + return it->second; } } break; @@ -571,16 +571,16 @@ ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t ch default: { auto it = normalChannels.find(channelId); if (it != normalChannels.end()) { - ChatChannel &channel = it->second; - if (!channel.executeCanJoinEvent(player)) { + const auto &channel = it->second; + if (!channel->executeCanJoinEvent(player)) { return nullptr; } - return &channel; + return channel; } auto it2 = privateChannels.find(channelId); - if (it2 != privateChannels.end() && it2->second.isInvited(player->getGUID())) { - return &it2->second; + if (it2 != privateChannels.end() && it2->second->isInvited(player->getGUID())) { + return it2->second; } break; } @@ -588,26 +588,26 @@ ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t ch return nullptr; } -ChatChannel* Chat::getGuildChannelById(uint32_t guildId) { +std::shared_ptr Chat::getGuildChannelById(uint32_t guildId) { auto it = guildChannels.find(guildId); if (it == guildChannels.end()) { return nullptr; } - return &it->second; + return it->second; } -ChatChannel* Chat::getChannelById(uint16_t channelId) { +std::shared_ptr Chat::getChannelById(uint16_t channelId) { auto it = normalChannels.find(channelId); if (it == normalChannels.end()) { return nullptr; } - return &it->second; + return it->second; } -PrivateChatChannel* Chat::getPrivateChannel(const std::shared_ptr &player) { +std::shared_ptr Chat::getPrivateChannel(const std::shared_ptr &player) { for (auto &it : privateChannels) { - if (it.second.getOwner() == player->getGUID()) { - return &it.second; + if (it.second->getOwner() == player->getGUID()) { + return it.second; } } return nullptr; diff --git a/src/creatures/interactions/chat.hpp b/src/creatures/interactions/chat.hpp index 31d2a612e1c..36768fa4f44 100644 --- a/src/creatures/interactions/chat.hpp +++ b/src/creatures/interactions/chat.hpp @@ -98,7 +98,7 @@ class PrivateChatChannel final : public ChatChannel { void closeChannel() const; - const InvitedMap* getInvitedUsers() const override { + [[nodiscard]] const InvitedMap* getInvitedUsers() const override { return &invites; } @@ -107,7 +107,7 @@ class PrivateChatChannel final : public ChatChannel { uint32_t owner = 0; }; -using ChannelList = std::list; +using ChannelList = std::list>; class Chat { public: @@ -123,10 +123,10 @@ class Chat { bool load(); - ChatChannel* createChannel(const std::shared_ptr &player, uint16_t channelId); + std::shared_ptr createChannel(const std::shared_ptr &player, uint16_t channelId); bool deleteChannel(const std::shared_ptr &player, uint16_t channelId); - ChatChannel* addUserToChannel(const std::shared_ptr &player, uint16_t channelId); + std::shared_ptr addUserToChannel(const std::shared_ptr &player, uint16_t channelId); bool removeUserFromChannel(const std::shared_ptr &player, uint16_t channelId); void removeUserFromAllChannels(const std::shared_ptr &player); @@ -134,24 +134,24 @@ class Chat { ChannelList getChannelList(const std::shared_ptr &player); - ChatChannel* getChannel(const std::shared_ptr &player, uint16_t channelId); - ChatChannel* getChannelById(uint16_t channelId); - ChatChannel* getGuildChannelById(uint32_t guildId); - PrivateChatChannel* getPrivateChannel(const std::shared_ptr &player); + std::shared_ptr getChannel(const std::shared_ptr &player, uint16_t channelId); + std::shared_ptr getChannelById(uint16_t channelId); + std::shared_ptr getGuildChannelById(uint32_t guildId); + std::shared_ptr getPrivateChannel(const std::shared_ptr &player); LuaScriptInterface* getScriptInterface() { return &scriptInterface; } private: - std::map normalChannels; - std::map privateChannels; - std::map, ChatChannel> partyChannels; - std::map guildChannels; + std::map> normalChannels; + std::map> privateChannels; + std::map, std::shared_ptr> partyChannels; + std::map> guildChannels; LuaScriptInterface scriptInterface; - PrivateChatChannel dummyPrivate; + std::shared_ptr dummyPrivate; }; constexpr auto g_chat = Chat::getInstance; diff --git a/src/game/game.cpp b/src/game/game.cpp index 2aba04f7ea3..88264725b41 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -2952,7 +2952,7 @@ void Game::playerCreatePrivateChannel(uint32_t playerId) { return; } - ChatChannel* channel = g_chat().createChannel(player, CHANNEL_PRIVATE); + const auto &channel = g_chat().createChannel(player, CHANNEL_PRIVATE); if (!channel || !channel->addUser(player)) { return; } @@ -2966,7 +2966,7 @@ void Game::playerChannelInvite(uint32_t playerId, const std::string &name) { return; } - PrivateChatChannel* channel = g_chat().getPrivateChannel(player); + const auto &channel = g_chat().getPrivateChannel(player); if (!channel) { return; } @@ -2989,7 +2989,7 @@ void Game::playerChannelExclude(uint32_t playerId, const std::string &name) { return; } - PrivateChatChannel* channel = g_chat().getPrivateChannel(player); + const auto &channel = g_chat().getPrivateChannel(player); if (!channel) { return; } @@ -3021,7 +3021,7 @@ void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) { return; } - const ChatChannel* channel = g_chat().addUserToChannel(player, channelId); + const auto &channel = g_chat().addUserToChannel(player, channelId); if (!channel) { return; } diff --git a/src/lua/functions/core/game/global_functions.cpp b/src/lua/functions/core/game/global_functions.cpp index e0a2ddcc710..8bc9b55b014 100644 --- a/src/lua/functions/core/game/global_functions.cpp +++ b/src/lua/functions/core/game/global_functions.cpp @@ -774,7 +774,7 @@ int GlobalFunctions::luaGetWaypointPositionByName(lua_State* L) { int GlobalFunctions::luaSendChannelMessage(lua_State* L) { // sendChannelMessage(channelId, type, message) uint16_t channelId = getNumber(L, 1); - const ChatChannel* channel = g_chat().getChannelById(channelId); + const auto &channel = g_chat().getChannelById(channelId); if (!channel) { pushBoolean(L, false); return 1; @@ -790,7 +790,7 @@ int GlobalFunctions::luaSendChannelMessage(lua_State* L) { int GlobalFunctions::luaSendGuildChannelMessage(lua_State* L) { // sendGuildChannelMessage(guildId, type, message) uint32_t guildId = getNumber(L, 1); - const ChatChannel* channel = g_chat().getGuildChannelById(guildId); + const auto &channel = g_chat().getGuildChannelById(guildId); if (!channel) { pushBoolean(L, false); return 1; diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 38debb995ff..7145525c3bd 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -2075,7 +2075,7 @@ int PlayerFunctions::luaPlayerSendTextMessage(lua_State* L) { TextMessage message(getNumber(L, 2), getString(L, 3)); if (parameters == 4) { uint16_t channelId = getNumber(L, 4); - ChatChannel* channel = g_chat().getChannel(player, channelId); + const auto &channel = g_chat().getChannel(player, channelId); if (!channel || !channel->hasUser(player)) { pushBoolean(L, false); return 1; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index c6a9ac45395..2848013701c 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -4120,7 +4120,7 @@ void ProtocolGame::sendChannelsDialog() { const ChannelList &list = g_chat().getChannelList(player); msg.addByte(list.size()); - for (ChatChannel* channel : list) { + for (const auto &channel : list) { msg.add(channel->getId()); msg.addString(channel->getName()); } From 4863c1d36685fa6bde8576a4b0becda7c158640e Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Wed, 4 Oct 2023 23:39:56 -0300 Subject: [PATCH 10/18] improve: AreaCombat::areas to array (#1653) Accessing the array index is almost ~2x faster than using a std::map. There would only be advantages to using std::map if there were a large number of records, which is not the case. Memory consumption may be a little higher with an array, as it will allocate memory to directions that may not be used, but it is irrelevant compared to the performance gain. Benchmark: ![image](https://github.com/opentibiabr/canary/assets/2267386/e409577f-bbae-4c1b-91ea-18063296fe62)
struct Teste { bool any = false; }; std::array, 8> arrayTest; std::map> mapTest; arrayTest[3] = std::make_unique(); arrayTest[4] = std::make_unique(); arrayTest[5] = std::make_unique(); arrayTest[6] = std::make_unique(); Benchmark bm; for (size_t i = 0; i < 999999999; ++i) { const auto &v = arrayTest[3 + (rand() % 6)]; } g_logger().info("Array: " + std::to_string(bm.duration()) + "ms"); bm.reset(); bm.start(); for (size_t i = 0; i < 999999999; ++i) { const auto &v = mapTest[3 + (rand() % 6)]; } g_logger().info("Map: " + std::to_string(bm.duration()) + "ms");
--- src/creatures/combat/combat.cpp | 8 +++++--- src/creatures/combat/combat.hpp | 11 ++--------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 4a8e6bd7060..606a1c132fb 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1768,13 +1768,15 @@ bool ChainPickerCallback::onChainCombat(std::shared_ptr creature, std: //**********************************************************// void AreaCombat::clear() { - areas.clear(); + std::ranges::fill(areas, nullptr); } AreaCombat::AreaCombat(const AreaCombat &rhs) { hasExtArea = rhs.hasExtArea; - for (const auto &it : rhs.areas) { - areas[it.first] = it.second->clone(); + for (uint_fast8_t i = 0; i <= Direction::DIRECTION_LAST; ++i) { + if (const auto &area = rhs.areas[i]) { + areas[i] = area->clone(); + } } } diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index 88c2db31312..16062cb685d 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -21,8 +21,6 @@ class Spell; class Player; class MatrixArea; -static const std::unique_ptr &MatrixAreaNull {}; - // for luascript callback class ValueCallback final : public CallBack { public: @@ -247,15 +245,10 @@ class AreaCombat { } } - auto it = areas.find(dir); - if (it == areas.end()) { - return MatrixAreaNull; - } - - return it->second; + return areas[dir]; } - std::map> areas; + std::array, Direction::DIRECTION_LAST + 1> areas {}; bool hasExtArea = false; }; From edbf957ca7b1c1b0557854147ff27536108b97be Mon Sep 17 00:00:00 2001 From: SRNT-GG <95472530+SRNT-GG@users.noreply.github.com> Date: Thu, 5 Oct 2023 06:16:45 +0200 Subject: [PATCH 11/18] docs: fix url of tools in readme (#1675) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16b6d73cb14..d3d83eecc48 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ source of the Canary, so that it will be the first repository to use this engine To connect to the server and to take a stable experience, you can use [mehah's otclient](https://github.com/mehah/otclient) or [tibia client](https://github.com/dudantas/tibia-client/releases/latest) and if you want to edit something, check -our [customized tools](https://docs.opentibiabr.com/others/downloads/tools). +our [customized tools](https://docs.opentibiabr.com/opentibiabr/downloads/tools). If you want edit the map, use the [own remere's map editor](https://github.com/opentibiabr/remeres-map-editor/). From 04ba21a9639c7bd085d85a3f0550423b4b32793c Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 5 Oct 2023 01:18:18 -0300 Subject: [PATCH 12/18] fix: fungus wrong id reference (#1676) --- .../scripts/globalevents/worldchanges/their_masters_voice.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua b/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua index fa1b9adbe33..2061c254cf9 100644 --- a/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua +++ b/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua @@ -12,7 +12,7 @@ function theirmastersvoice.onStartup() if item then local slimeChance = math.random(100) if slimeChance <= 30 then - item:transform(math.random(13585, 13589)) + item:transform(math.random(12059, 12063)) position:sendMagicEffect(CONST_ME_YELLOW_RINGS) end end From 72adc6e4bd02db4e8bef76af09367419ad9f7d8d Mon Sep 17 00:00:00 2001 From: Beats Date: Thu, 5 Oct 2023 01:51:37 -0300 Subject: [PATCH 13/18] improve: migrate Weapon and derivatives to shared_ptr (#1659) --- src/config/configmanager.cpp | 1 - src/creatures/combat/combat.cpp | 4 +- src/creatures/monsters/monster.cpp | 1 - src/creatures/npcs/npcs.cpp | 1 - src/creatures/players/player.cpp | 2 +- src/game/game.cpp | 6 -- src/game/scheduling/events_scheduler.cpp | 1 - src/game/zones/zone.cpp | 1 - src/io/iologindata.cpp | 1 - src/io/ioprey.cpp | 1 - src/items/item.cpp | 1 - src/items/items.cpp | 1 - src/items/tile.cpp | 1 - src/items/weapons/weapons.cpp | 6 +- src/items/weapons/weapons.hpp | 9 +- src/kv/kv.cpp | 1 - src/lua/callbacks/event_callback.cpp | 2 - .../functions/core/game/game_functions.cpp | 1 - src/lua/functions/core/game/lua_enums.cpp | 3 - .../functions/items/imbuement_functions.cpp | 1 - src/lua/functions/items/weapon_functions.cpp | 92 +++++++++---------- src/lua/functions/items/weapon_functions.hpp | 2 +- src/lua/functions/lua_functions_loader.cpp | 2 - src/lua/scripts/lua_environment.hpp | 6 +- src/lua/scripts/luascript.hpp | 2 +- src/lua/scripts/script_environment.cpp | 1 - src/map/utils/qtreenode.cpp | 1 - src/server/network/connection/connection.cpp | 1 - src/server/network/message/networkmessage.cpp | 1 - 29 files changed, 62 insertions(+), 91 deletions(-) diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 940ea29eb51..3eacd9e9ade 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -12,7 +12,6 @@ #include "config/configmanager.hpp" #include "declarations.hpp" #include "game/game.hpp" -#include "lua/scripts/luajit_sync.hpp" #include "server/network/webhook/webhook.hpp" #if LUA_VERSION_NUM >= 502 diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 606a1c132fb..8b650253567 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -73,7 +73,7 @@ CombatDamage Combat::getCombatDamage(std::shared_ptr creature, std::sh ); } else if (formulaType == COMBAT_FORMULA_SKILL) { std::shared_ptr tool = player->getWeapon(); - const Weapon* weapon = g_weapons().getWeapon(tool); + const WeaponShared_ptr weapon = g_weapons().getWeapon(tool); if (weapon) { damage.primary.value = normal_random( static_cast(minb), @@ -1514,7 +1514,7 @@ void ValueCallback::getMinMaxValues(std::shared_ptr player, CombatDamage case COMBAT_FORMULA_SKILL: { // onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor) std::shared_ptr tool = player->getWeapon(); - const Weapon* weapon = g_weapons().getWeapon(tool); + const WeaponShared_ptr weapon = g_weapons().getWeapon(tool); std::shared_ptr item = nullptr; if (weapon) { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 4ef06634933..eef9aaa3830 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -14,7 +14,6 @@ #include "creatures/players/wheel/player_wheel.hpp" #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" -#include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "map/spectators.hpp" diff --git a/src/creatures/npcs/npcs.cpp b/src/creatures/npcs/npcs.cpp index 432b0adbe15..c18897d1c4c 100644 --- a/src/creatures/npcs/npcs.cpp +++ b/src/creatures/npcs/npcs.cpp @@ -11,7 +11,6 @@ #include "declarations.hpp" #include "creatures/combat/combat.hpp" -#include "creatures/creature.hpp" #include "lua/scripts/lua_environment.hpp" #include "creatures/combat/spells.hpp" #include "creatures/npcs/npcs.hpp" diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 59b9292662d..0ec4bedf8a4 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -4222,7 +4222,7 @@ void Player::doAttacking(uint32_t) { bool result = false; std::shared_ptr tool = getWeapon(); - const Weapon* weapon = g_weapons().getWeapon(tool); + const WeaponShared_ptr weapon = g_weapons().getWeapon(tool); uint32_t delay = getAttackSpeed(); bool classicSpeed = g_configManager().getBoolean(CLASSIC_ATTACK_SPEED); diff --git a/src/game/game.cpp b/src/game/game.cpp index 88264725b41..9f853ab6b22 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -12,13 +12,11 @@ #include "lua/creature/actions.hpp" #include "items/bed.hpp" #include "creatures/creature.hpp" -#include "lua/creature/creatureevent.hpp" #include "database/databasetasks.hpp" #include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "game/game.hpp" -#include "game/functions/game_reload.hpp" #include "game/zones/zone.hpp" #include "lua/global/globalevent.hpp" #include "io/iologindata.hpp" @@ -34,13 +32,9 @@ #include "creatures/combat/spells.hpp" #include "lua/creature/talkaction.hpp" #include "items/weapons/weapons.hpp" -#include "lua/scripts/scripts.hpp" -#include "lua/modules/modules.hpp" #include "creatures/players/imbuements/imbuements.hpp" -#include "account/account.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "creatures/npcs/npc.hpp" -#include "creatures/npcs/npcs.hpp" #include "server/network/webhook/webhook.hpp" #include "protobuf/appearances.pb.h" #include "server/network/protocol/protocollogin.hpp" diff --git a/src/game/scheduling/events_scheduler.cpp b/src/game/scheduling/events_scheduler.cpp index f7bd197979e..d5c71378f4a 100644 --- a/src/game/scheduling/events_scheduler.cpp +++ b/src/game/scheduling/events_scheduler.cpp @@ -12,7 +12,6 @@ #include "config/configmanager.hpp" #include "game/scheduling/events_scheduler.hpp" #include "lua/scripts/scripts.hpp" -#include "utils/pugicast.hpp" bool EventsScheduler::loadScheduleEventFromXml() { pugi::xml_document doc; diff --git a/src/game/zones/zone.cpp b/src/game/zones/zone.cpp index e06975ff7a6..f1aa300efee 100644 --- a/src/game/zones/zone.cpp +++ b/src/game/zones/zone.cpp @@ -14,7 +14,6 @@ #include "creatures/monsters/monster.hpp" #include "creatures/npcs/npc.hpp" #include "creatures/players/player.hpp" -#include "game/scheduling/dispatcher.hpp" phmap::parallel_flat_hash_map> Zone::zones = {}; const static std::shared_ptr nullZone = nullptr; diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 307d41a9cb3..f32b90568a4 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -15,7 +15,6 @@ #include "game/game.hpp" #include "creatures/monsters/monster.hpp" #include "creatures/players/wheel/player_wheel.hpp" -#include "io/ioprey.hpp" bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, const std::string &password, std::string &characterName, uint32_t &accountId, bool oldProtocol) { account::Account account(accountDescriptor); diff --git a/src/io/ioprey.cpp b/src/io/ioprey.cpp index b81dad1fdf7..50c3070b4a0 100644 --- a/src/io/ioprey.cpp +++ b/src/io/ioprey.cpp @@ -9,7 +9,6 @@ #include "pch.hpp" -#include "declarations.hpp" #include "creatures/monsters/monster.hpp" #include "creatures/players/player.hpp" #include "config/configmanager.hpp" diff --git a/src/items/item.cpp b/src/items/item.cpp index ea04be89385..b6902575bb2 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -10,7 +10,6 @@ #include "pch.hpp" #include "items/item.hpp" -#include "items/functions/item/item_parse.hpp" #include "items/containers/container.hpp" #include "items/decay/decay.hpp" #include "game/movement/teleport.hpp" diff --git a/src/items/items.cpp b/src/items/items.cpp index ca8e20e5b30..690e01a5b92 100644 --- a/src/items/items.cpp +++ b/src/items/items.cpp @@ -11,7 +11,6 @@ #include "items/functions/item/item_parse.hpp" #include "items/items.hpp" -#include "items/weapons/weapons.hpp" #include "game/game.hpp" #include "utils/pugicast.hpp" diff --git a/src/items/tile.cpp b/src/items/tile.cpp index 25f7f865688..0d9a5db0ead 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -19,7 +19,6 @@ #include "lua/creature/movement.hpp" #include "game/movement/teleport.hpp" #include "items/trashholder.hpp" -#include "map/house/housetile.hpp" #include "io/iomap.hpp" #include "map/spectators.hpp" diff --git a/src/items/weapons/weapons.cpp b/src/items/weapons/weapons.cpp index 995b71518c0..08f1d75c032 100644 --- a/src/items/weapons/weapons.cpp +++ b/src/items/weapons/weapons.cpp @@ -17,7 +17,7 @@ Weapons::Weapons() = default; Weapons::~Weapons() = default; -const Weapon* Weapons::getWeapon(std::shared_ptr item) const { +const WeaponShared_ptr Weapons::getWeapon(std::shared_ptr item) const { if (!item) { return nullptr; } @@ -33,7 +33,7 @@ void Weapons::clear() { weapons.clear(); } -bool Weapons::registerLuaEvent(Weapon* event) { +bool Weapons::registerLuaEvent(WeaponShared_ptr event) { weapons[event->getID()] = event; return true; } @@ -524,7 +524,7 @@ bool WeaponDistance::useWeapon(std::shared_ptr player, std::shared_ptr mainWeaponItem = player->getWeapon(true); - const Weapon* mainWeapon = g_weapons().getWeapon(mainWeaponItem); + const WeaponShared_ptr mainWeapon = g_weapons().getWeapon(mainWeaponItem); if (mainWeapon) { damageModifier = mainWeapon->playerWeaponCheck(player, target, mainWeaponItem->getShootRange()); } else { diff --git a/src/items/weapons/weapons.hpp b/src/items/weapons/weapons.hpp index 90785983e17..78a7195fb3c 100644 --- a/src/items/weapons/weapons.hpp +++ b/src/items/weapons/weapons.hpp @@ -21,7 +21,8 @@ class WeaponMelee; class WeaponDistance; class WeaponWand; -using Weapon_ptr = std::unique_ptr; +using WeaponUnique_ptr = std::unique_ptr; +using WeaponShared_ptr = std::shared_ptr; class Weapons final : public Scripts { public: @@ -36,16 +37,16 @@ class Weapons final : public Scripts { return inject(); } - const Weapon* getWeapon(std::shared_ptr item) const; + const WeaponShared_ptr getWeapon(std::shared_ptr item) const; static int32_t getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue); static int32_t getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor, bool isMelee); - bool registerLuaEvent(Weapon* event); + bool registerLuaEvent(WeaponShared_ptr event); void clear(); private: - std::map weapons; + std::map weapons; }; constexpr auto g_weapons = Weapons::getInstance; diff --git a/src/kv/kv.cpp b/src/kv/kv.cpp index cd2a8b974b5..e5dbc974b76 100644 --- a/src/kv/kv.cpp +++ b/src/kv/kv.cpp @@ -11,7 +11,6 @@ #include "kv/kv.hpp" #include "lib/di/container.hpp" -#include "utils/tools.hpp" KVStore &KVStore::getInstance() { return inject(); diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp index 6444446bde9..8510502b226 100644 --- a/src/lua/callbacks/event_callback.cpp +++ b/src/lua/callbacks/event_callback.cpp @@ -11,8 +11,6 @@ #include "lua/callbacks/event_callback.hpp" -#include "lua/callbacks/event_callback.hpp" -#include "lua/callbacks/events_callbacks.hpp" #include "utils/tools.hpp" #include "items/item.hpp" #include "creatures/players/player.hpp" diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index 86358f1daf7..ec59b8b1c34 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -23,7 +23,6 @@ #include "lua/creature/talkaction.hpp" #include "lua/functions/creatures/npc/npc_type_functions.hpp" #include "lua/scripts/lua_environment.hpp" -#include "lua/scripts/scripts.hpp" #include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 8d86185290f..fc1586f3363 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -11,15 +11,12 @@ #include "lua/functions/core/game/lua_enums.hpp" -#include "account/account.hpp" #include "creatures/players/wheel/wheel_definitions.hpp" #include "io/io_bosstiary.hpp" #include "config/configmanager.hpp" #include "creatures/creature.hpp" -#include "lua/creature/creatureevent.hpp" #include "declarations.hpp" #include "game/functions/game_reload.hpp" -#include "game/game.hpp" #define registerEnumClass(luaState, enumClassType) \ { \ diff --git a/src/lua/functions/items/imbuement_functions.cpp b/src/lua/functions/items/imbuement_functions.cpp index f512e56a62b..2a4e85d7d88 100644 --- a/src/lua/functions/items/imbuement_functions.cpp +++ b/src/lua/functions/items/imbuement_functions.cpp @@ -9,7 +9,6 @@ #include "pch.hpp" -#include "items/item.hpp" #include "items/weapons/weapons.hpp" #include "creatures/players/imbuements/imbuements.hpp" #include "lua/functions/items/imbuement_functions.hpp" diff --git a/src/lua/functions/items/weapon_functions.cpp b/src/lua/functions/items/weapon_functions.cpp index 2e5fde7ff15..3a41d0ac75d 100644 --- a/src/lua/functions/items/weapon_functions.cpp +++ b/src/lua/functions/items/weapon_functions.cpp @@ -25,7 +25,7 @@ int WeaponFunctions::luaCreateWeapon(lua_State* L) { case WEAPON_AXE: case WEAPON_CLUB: { if (auto weaponPtr = g_luaEnvironment().createWeaponObject(getScriptEnv()->getScriptInterface())) { - pushUserdata(L, weaponPtr.get()); + pushUserdata(L, weaponPtr); setMetatable(L, -1, "Weapon"); weaponPtr->weaponType = type; } else { @@ -37,7 +37,7 @@ int WeaponFunctions::luaCreateWeapon(lua_State* L) { case WEAPON_DISTANCE: case WEAPON_AMMO: { if (auto weaponPtr = g_luaEnvironment().createWeaponObject(getScriptEnv()->getScriptInterface())) { - pushUserdata(L, weaponPtr.get()); + pushUserdata(L, weaponPtr); setMetatable(L, -1, "Weapon"); weaponPtr->weaponType = type; } else { @@ -47,7 +47,7 @@ int WeaponFunctions::luaCreateWeapon(lua_State* L) { } case WEAPON_WAND: { if (auto weaponPtr = g_luaEnvironment().createWeaponObject(getScriptEnv()->getScriptInterface())) { - pushUserdata(L, weaponPtr.get()); + pushUserdata(L, weaponPtr); setMetatable(L, -1, "Weapon"); weaponPtr->weaponType = type; } else { @@ -65,7 +65,7 @@ int WeaponFunctions::luaCreateWeapon(lua_State* L) { int WeaponFunctions::luaWeaponAction(lua_State* L) { // weapon:action(callback) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { std::string typeName = getString(L, 2); std::string tmpStr = asLowerCaseString(typeName); @@ -90,15 +90,15 @@ int WeaponFunctions::luaWeaponAction(lua_State* L) { int WeaponFunctions::luaWeaponRegister(lua_State* L) { // weapon:register() - Weapon** weaponPtr = getRawUserdata(L, 1); + WeaponShared_ptr* weaponPtr = getRawUserDataShared(L, 1); if (weaponPtr && *weaponPtr) { - Weapon* weapon = *weaponPtr; + WeaponShared_ptr weapon = *weaponPtr; if (weapon->weaponType == WEAPON_DISTANCE || weapon->weaponType == WEAPON_AMMO || weapon->weaponType == WEAPON_MISSILE) { - weapon = getUserdata(L, 1); + weapon = getUserdataShared(L, 1); } else if (weapon->weaponType == WEAPON_WAND) { - weapon = getUserdata(L, 1); + weapon = getUserdataShared(L, 1); } else { - weapon = getUserdata(L, 1); + weapon = getUserdataShared(L, 1); } uint16_t id = weapon->getID(); @@ -123,7 +123,7 @@ int WeaponFunctions::luaWeaponRegister(lua_State* L) { int WeaponFunctions::luaWeaponOnUseWeapon(lua_State* L) { // weapon:onUseWeapon(callback) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { if (!weapon->loadCallback()) { pushBoolean(L, false); @@ -139,7 +139,7 @@ int WeaponFunctions::luaWeaponOnUseWeapon(lua_State* L) { int WeaponFunctions::luaWeaponUnproperly(lua_State* L) { // weapon:wieldedUnproperly(bool) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setWieldUnproperly(getBoolean(L, 2)); pushBoolean(L, true); @@ -151,7 +151,7 @@ int WeaponFunctions::luaWeaponUnproperly(lua_State* L) { int WeaponFunctions::luaWeaponLevel(lua_State* L) { // weapon:level(lvl) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setRequiredLevel(getNumber(L, 2)); weapon->setWieldInfo(WIELDINFO_LEVEL); @@ -164,7 +164,7 @@ int WeaponFunctions::luaWeaponLevel(lua_State* L) { int WeaponFunctions::luaWeaponMagicLevel(lua_State* L) { // weapon:magicLevel(lvl) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setRequiredMagLevel(getNumber(L, 2)); weapon->setWieldInfo(WIELDINFO_MAGLV); @@ -177,7 +177,7 @@ int WeaponFunctions::luaWeaponMagicLevel(lua_State* L) { int WeaponFunctions::luaWeaponMana(lua_State* L) { // weapon:mana(mana) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setMana(getNumber(L, 2)); pushBoolean(L, true); @@ -189,7 +189,7 @@ int WeaponFunctions::luaWeaponMana(lua_State* L) { int WeaponFunctions::luaWeaponManaPercent(lua_State* L) { // weapon:manaPercent(percent) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setManaPercent(getNumber(L, 2)); pushBoolean(L, true); @@ -201,7 +201,7 @@ int WeaponFunctions::luaWeaponManaPercent(lua_State* L) { int WeaponFunctions::luaWeaponHealth(lua_State* L) { // weapon:health(health) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setHealth(getNumber(L, 2)); pushBoolean(L, true); @@ -213,7 +213,7 @@ int WeaponFunctions::luaWeaponHealth(lua_State* L) { int WeaponFunctions::luaWeaponHealthPercent(lua_State* L) { // weapon:healthPercent(percent) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setHealthPercent(getNumber(L, 2)); pushBoolean(L, true); @@ -225,7 +225,7 @@ int WeaponFunctions::luaWeaponHealthPercent(lua_State* L) { int WeaponFunctions::luaWeaponSoul(lua_State* L) { // weapon:soul(soul) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setSoul(getNumber(L, 2)); pushBoolean(L, true); @@ -237,7 +237,7 @@ int WeaponFunctions::luaWeaponSoul(lua_State* L) { int WeaponFunctions::luaWeaponBreakChance(lua_State* L) { // weapon:breakChance(percent) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setBreakChance(getNumber(L, 2)); pushBoolean(L, true); @@ -249,7 +249,7 @@ int WeaponFunctions::luaWeaponBreakChance(lua_State* L) { int WeaponFunctions::luaWeaponWandDamage(lua_State* L) { // weapon:damage(damage[min, max]) only use this if the weapon is a wand! - WeaponWand* weapon = getUserdata(L, 1); + const auto &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setMinChange(getNumber(L, 2)); if (lua_gettop(L) > 2) { @@ -266,7 +266,7 @@ int WeaponFunctions::luaWeaponWandDamage(lua_State* L) { int WeaponFunctions::luaWeaponElement(lua_State* L) { // weapon:element(combatType) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { if (!getNumber(L, 2)) { std::string element = getString(L, 2); @@ -300,7 +300,7 @@ int WeaponFunctions::luaWeaponElement(lua_State* L) { int WeaponFunctions::luaWeaponPremium(lua_State* L) { // weapon:premium(bool) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setNeedPremium(getBoolean(L, 2)); weapon->setWieldInfo(WIELDINFO_PREMIUM); @@ -313,7 +313,7 @@ int WeaponFunctions::luaWeaponPremium(lua_State* L) { int WeaponFunctions::luaWeaponVocation(lua_State* L) { // weapon:vocation(vocName[, showInDescription = false, lastVoc = false]) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->addVocWeaponMap(getString(L, 2)); weapon->setWieldInfo(WIELDINFO_VOCREQ); @@ -352,7 +352,7 @@ int WeaponFunctions::luaWeaponVocation(lua_State* L) { int WeaponFunctions::luaWeaponId(lua_State* L) { // weapon:id(id) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { weapon->setID(getNumber(L, 2)); pushBoolean(L, true); @@ -364,7 +364,7 @@ int WeaponFunctions::luaWeaponId(lua_State* L) { int WeaponFunctions::luaWeaponAttack(lua_State* L) { // weapon:attack(atk) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -378,7 +378,7 @@ int WeaponFunctions::luaWeaponAttack(lua_State* L) { int WeaponFunctions::luaWeaponDefense(lua_State* L) { // weapon:defense(defense[, extraDefense]) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -395,7 +395,7 @@ int WeaponFunctions::luaWeaponDefense(lua_State* L) { int WeaponFunctions::luaWeaponRange(lua_State* L) { // weapon:range(range) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -409,7 +409,7 @@ int WeaponFunctions::luaWeaponRange(lua_State* L) { int WeaponFunctions::luaWeaponCharges(lua_State* L) { // weapon:charges(charges[, showCharges = true]) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { bool showCharges = true; if (lua_gettop(L) > 2) { @@ -428,7 +428,7 @@ int WeaponFunctions::luaWeaponCharges(lua_State* L) { int WeaponFunctions::luaWeaponDuration(lua_State* L) { // weapon:duration(duration[, showDuration = true]) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { bool showDuration = true; if (lua_gettop(L) > 2) { @@ -447,7 +447,7 @@ int WeaponFunctions::luaWeaponDuration(lua_State* L) { int WeaponFunctions::luaWeaponDecayTo(lua_State* L) { // weapon:decayTo([itemid = 0] - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t itemid = 0; if (lua_gettop(L) > 1) { @@ -465,7 +465,7 @@ int WeaponFunctions::luaWeaponDecayTo(lua_State* L) { int WeaponFunctions::luaWeaponTransformEquipTo(lua_State* L) { // weapon:transformEquipTo(itemid) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -479,7 +479,7 @@ int WeaponFunctions::luaWeaponTransformEquipTo(lua_State* L) { int WeaponFunctions::luaWeaponTransformDeEquipTo(lua_State* L) { // weapon:transformDeEquipTo(itemid) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -493,7 +493,7 @@ int WeaponFunctions::luaWeaponTransformDeEquipTo(lua_State* L) { int WeaponFunctions::luaWeaponShootType(lua_State* L) { // weapon:shootType(type) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -507,7 +507,7 @@ int WeaponFunctions::luaWeaponShootType(lua_State* L) { int WeaponFunctions::luaWeaponSlotType(lua_State* L) { // weapon:slotType(slot) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -527,7 +527,7 @@ int WeaponFunctions::luaWeaponSlotType(lua_State* L) { int WeaponFunctions::luaWeaponAmmoType(lua_State* L) { // weapon:ammoType(type) - WeaponDistance* weapon = getUserdata(L, 1); + const auto &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -553,7 +553,7 @@ int WeaponFunctions::luaWeaponAmmoType(lua_State* L) { int WeaponFunctions::luaWeaponHitChance(lua_State* L) { // weapon:hitChance(chance) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -567,7 +567,7 @@ int WeaponFunctions::luaWeaponHitChance(lua_State* L) { int WeaponFunctions::luaWeaponMaxHitChance(lua_State* L) { // weapon:maxHitChance(max) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); @@ -581,34 +581,34 @@ int WeaponFunctions::luaWeaponMaxHitChance(lua_State* L) { int WeaponFunctions::luaWeaponExtraElement(lua_State* L) { // weapon:extraElement(atk, combatType) - Weapon* weapon = getUserdata(L, 1); + const WeaponShared_ptr &weapon = getUserdataShared(L, 1); if (weapon) { uint16_t id = weapon->getID(); ItemType &it = Item::items.getItemType(id); - it.abilities.get()->elementDamage = getNumber(L, 2); + it.abilities->elementDamage = getNumber(L, 2); if (!getNumber(L, 3)) { std::string element = getString(L, 3); std::string tmpStrValue = asLowerCaseString(element); if (tmpStrValue == "earth") { - it.abilities.get()->elementType = COMBAT_EARTHDAMAGE; + it.abilities->elementType = COMBAT_EARTHDAMAGE; } else if (tmpStrValue == "ice") { - it.abilities.get()->elementType = COMBAT_ICEDAMAGE; + it.abilities->elementType = COMBAT_ICEDAMAGE; } else if (tmpStrValue == "energy") { - it.abilities.get()->elementType = COMBAT_ENERGYDAMAGE; + it.abilities->elementType = COMBAT_ENERGYDAMAGE; } else if (tmpStrValue == "fire") { - it.abilities.get()->elementType = COMBAT_FIREDAMAGE; + it.abilities->elementType = COMBAT_FIREDAMAGE; } else if (tmpStrValue == "death") { - it.abilities.get()->elementType = COMBAT_DEATHDAMAGE; + it.abilities->elementType = COMBAT_DEATHDAMAGE; } else if (tmpStrValue == "holy") { - it.abilities.get()->elementType = COMBAT_HOLYDAMAGE; + it.abilities->elementType = COMBAT_HOLYDAMAGE; } else { g_logger().warn("[WeaponFunctions:luaWeaponExtraElement] - " "Type {} does not exist", element); } } else { - it.abilities.get()->elementType = getNumber(L, 3); + it.abilities->elementType = getNumber(L, 3); } pushBoolean(L, true); } else { diff --git a/src/lua/functions/items/weapon_functions.hpp b/src/lua/functions/items/weapon_functions.hpp index 2f0d2e8ccb6..01ba6966317 100644 --- a/src/lua/functions/items/weapon_functions.hpp +++ b/src/lua/functions/items/weapon_functions.hpp @@ -14,7 +14,7 @@ class WeaponFunctions final : LuaScriptInterface { public: static void init(lua_State* L) { - registerClass(L, "Weapon", "", WeaponFunctions::luaCreateWeapon); + registerSharedClass(L, "Weapon", "", WeaponFunctions::luaCreateWeapon); registerMethod(L, "Weapon", "action", WeaponFunctions::luaWeaponAction); registerMethod(L, "Weapon", "register", WeaponFunctions::luaWeaponRegister); registerMethod(L, "Weapon", "id", WeaponFunctions::luaWeaponId); diff --git a/src/lua/functions/lua_functions_loader.cpp b/src/lua/functions/lua_functions_loader.cpp index 944849b1e6f..cf377592988 100644 --- a/src/lua/functions/lua_functions_loader.cpp +++ b/src/lua/functions/lua_functions_loader.cpp @@ -12,13 +12,11 @@ #include "creatures/combat/spells.hpp" #include "creatures/monsters/monster.hpp" #include "creatures/npcs/npc.hpp" -#include "creatures/players/imbuements/imbuements.hpp" #include "creatures/players/player.hpp" #include "creatures/players/grouping/guild.hpp" #include "game/zones/zone.hpp" #include "game/game.hpp" #include "game/movement/teleport.hpp" -#include "items/weapons/weapons.hpp" #include "lua/functions/core/core_functions.hpp" #include "lua/functions/creatures/creature_functions.hpp" #include "lua/functions/events/events_functions.hpp" diff --git a/src/lua/scripts/lua_environment.hpp b/src/lua/scripts/lua_environment.hpp index 216abb8ec78..042a21fe1d8 100644 --- a/src/lua/scripts/lua_environment.hpp +++ b/src/lua/scripts/lua_environment.hpp @@ -25,7 +25,7 @@ class LuaEnvironment : public LuaScriptInterface { static bool shuttingDown; LuaEnvironment(); - ~LuaEnvironment(); + ~LuaEnvironment() override; lua_State* getLuaState() override; @@ -38,7 +38,7 @@ class LuaEnvironment : public LuaScriptInterface { } bool initState() override; - bool reInitState(); + bool reInitState() override; bool closeState() override; LuaScriptInterface* getTestInterface(); @@ -50,7 +50,7 @@ class LuaEnvironment : public LuaScriptInterface { template std::shared_ptr createWeaponObject(LuaScriptInterface* interface) { auto weapon = std::make_shared(interface); - int weaponId = ++lastWeaponId; + auto weaponId = ++lastWeaponId; weaponMap[weaponId] = weapon; weaponIdMap[interface].push_back(weaponId); return weapon; diff --git a/src/lua/scripts/luascript.hpp b/src/lua/scripts/luascript.hpp index 80541e86d18..8f0b3b36c19 100644 --- a/src/lua/scripts/luascript.hpp +++ b/src/lua/scripts/luascript.hpp @@ -23,7 +23,7 @@ class LuaScriptInterface : public LuaFunctionsLoader { LuaScriptInterface &operator=(const LuaScriptInterface &) = delete; virtual bool initState(); - bool reInitState(); + virtual bool reInitState(); int32_t loadFile(const std::string &file, const std::string &scriptName); diff --git a/src/lua/scripts/script_environment.cpp b/src/lua/scripts/script_environment.cpp index 8899a954f69..ccebdb1a8dd 100644 --- a/src/lua/scripts/script_environment.cpp +++ b/src/lua/scripts/script_environment.cpp @@ -9,7 +9,6 @@ #include "pch.hpp" -#include "declarations.hpp" #include "game/game.hpp" #include "lua/scripts/luascript.hpp" #include "lua/scripts/script_environment.hpp" diff --git a/src/map/utils/qtreenode.cpp b/src/map/utils/qtreenode.cpp index 92d0a0423ee..ace469e14aa 100644 --- a/src/map/utils/qtreenode.cpp +++ b/src/map/utils/qtreenode.cpp @@ -10,7 +10,6 @@ #include "pch.hpp" #include "creatures/creature.hpp" -#include "map/mapcache.hpp" #include "qtreenode.hpp" bool QTreeLeafNode::newLeaf = false; diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index 04e7a82a64f..c2df08b2824 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -12,7 +12,6 @@ #include "server/network/connection/connection.hpp" #include "server/network/message/outputmessage.hpp" #include "server/network/protocol/protocol.hpp" -#include "server/network/protocol/protocolgame.hpp" #include "game/scheduling/scheduler.hpp" #include "game/scheduling/dispatcher.hpp" #include "server/server.hpp" diff --git a/src/server/network/message/networkmessage.cpp b/src/server/network/message/networkmessage.cpp index 9c7b5a333cb..457e3b32be0 100644 --- a/src/server/network/message/networkmessage.cpp +++ b/src/server/network/message/networkmessage.cpp @@ -11,7 +11,6 @@ #include "server/network/message/networkmessage.hpp" #include "items/containers/container.hpp" -#include "creatures/creature.hpp" int32_t NetworkMessage::decodeHeader() { int32_t newSize = buffer[0] | buffer[1] << 8; From 5eac8f5c02b865f2df4183c9506f3f2cf937586b Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 5 Oct 2023 07:04:03 -0700 Subject: [PATCH 14/18] fix: allow players to send in bug reports (#1666) --- data/events/scripts/player.lua | 1 + data/libs/functions/fs.lua | 32 ++++++++++++++++++++ data/libs/functions/load.lua | 1 + src/server/network/protocol/protocolgame.cpp | 6 +--- 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 data/libs/functions/fs.lua diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index f176e5000c5..816aa523b70 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -445,6 +445,7 @@ end function Player:onReportBug(message, position, category) local name = self:getName() + FS.mkdir_p(string.format("%s/reports/bugs/%s", CORE_DIRECTORY, name)) local file = io.open(string.format("%s/reports/bugs/%s/report.txt", CORE_DIRECTORY, name), "a") if not file then diff --git a/data/libs/functions/fs.lua b/data/libs/functions/fs.lua new file mode 100644 index 00000000000..f95fba51b05 --- /dev/null +++ b/data/libs/functions/fs.lua @@ -0,0 +1,32 @@ +FS = {} + +function FS.exists(path) + local file = io.open(path, "r") + if file then + file:close() + return true + end + return false +end + +function FS.mkdir(path) + if FS.exists(path) then + return true + end + local success, err = os.execute("mkdir " .. path) + if not success then + return false, err + end + return true +end + +function FS.mkdir_p(path) + if path == "" then + return true + end + if FS.exists(path) then + return true + end + FS.mkdir_p(path:match("(.*[/\\])")) + return FS.mkdir(path) +end diff --git a/data/libs/functions/load.lua b/data/libs/functions/load.lua index 705fdf712d6..0ab33f5de11 100644 --- a/data/libs/functions/load.lua +++ b/data/libs/functions/load.lua @@ -5,6 +5,7 @@ dofile(CORE_DIRECTORY .. "/libs/functions/constants.lua") dofile(CORE_DIRECTORY .. "/libs/functions/container.lua") dofile(CORE_DIRECTORY .. "/libs/functions/creature.lua") dofile(CORE_DIRECTORY .. "/libs/functions/functions.lua") +dofile(CORE_DIRECTORY .. "/libs/functions/fs.lua") dofile(CORE_DIRECTORY .. "/libs/functions/game.lua") dofile(CORE_DIRECTORY .. "/libs/functions/item.lua") dofile(CORE_DIRECTORY .. "/libs/functions/itemtype.lua") diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 2848013701c..59c2867e519 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -6039,11 +6039,7 @@ void ProtocolGame::sendAllowBugReport() { NetworkMessage msg; msg.addByte(0x1A); - if (player->getAccountType() >= account::ACCOUNT_TYPE_NORMAL) { - msg.addByte(0x01); - } else { - msg.addByte(0x00); - } + msg.addByte(0x00); // 0x01 = DISABLE bug report writeToOutputBuffer(msg); } From 2b4c0c76ca798ef8edb1915985fcbb18ea9b2ca6 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 5 Oct 2023 07:11:28 -0700 Subject: [PATCH 15/18] fix: fury gates maps/teleport (#1669) --- .../world_changes/fury_gates/abdendriel.otbm | Bin 500 -> 2150 bytes .../world_changes/fury_gates/ankrahmun.otbm | Bin 573 -> 2408 bytes .../world/world_changes/fury_gates/carlin.otbm | Bin 472 -> 2030 bytes .../world_changes/fury_gates/darashia.otbm | Bin 676 -> 2557 bytes .../world/world_changes/fury_gates/edron.otbm | Bin 595 -> 2358 bytes .../world_changes/fury_gates/kazordoon.otbm | Bin 508 -> 2685 bytes .../world_changes/fury_gates/libertybay.otbm | Bin 580 -> 2488 bytes .../world_changes/fury_gates/porthope.otbm | Bin 456 -> 2187 bytes .../world/world_changes/fury_gates/thais.otbm | Bin 675 -> 3533 bytes .../world/world_changes/fury_gates/venore.otbm | Bin 587 -> 3646 bytes data/items/items.xml | 1 - 11 files changed, 1 deletion(-) diff --git a/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm b/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm index 9e6b2698eee239b2260d59cc2a06a80616390eed..c41c56415e9b25caa04bf53c4cbb98fd775a2710 100644 GIT binary patch literal 2150 zcmYjS$&M3O6fK4rUIq{i3Q0O7CIV#@3keoku>+KWEE44d+_)qxb+=pY4vEBqzW|A4 z%@Z^sQlyL@knix^o;Dd5<=lHt*{)5M?tS;1d+t!LEX%TT>$p6#NArg#YOu=nq^n|lY% z)^4@_px)fBZ*AMP*WR@1TMc`+(Qew?AMLbu_YdrL?Yo`riyP&G-MxG2eB(mddB63| zzOC;3r1i*pcq)Iz=@|E1UitK`#X^iOj8ACv$|rpjOW2hS4Rbr@xft)z=o)`*^vvPf z=p(F(Ft20&J11DLYyQN$Xa2>zPuuU9ztr}NbH8UYVY&pc--ncUHx_y2`KCM$zg8VV z$NZz1fU+eP$LgW>%PT;nC>9y?O;F1#zX-DOQr%K+Uis{_GKPhUu#0lvR{XPd!mzi7 z#_Ill6R5s&qCAMX`MSh>j$9GRepMYHqr9kZo(Ngz;>qb% zIXS&1jXA3K(2|&>AE-iVk6AP1N*YlSv(g_l1C=Jl6p}9HM5?3*u^eYY3{;5+ zG$!qGOli;jt*uY{d}p2F-8I3g@T_NEit#=KXT_kM0cOdqaE6?|GXhNdEu78~B3Oev zU_y7u{c%S)WTbn94P)cHBGMaDinoef?u&PXsu}JN*0cOgJB01ZFi?NE+&7WqlSjs< z2DRNjqeeiApA`K<*MWl#hG70t-LiKYDCNn@Ej_DEJc1k9B{4QpqQe2!iJKG-#Zc$S zJe9S$%QYnsi9=+F%9v;_S$H#U(N{8Bw)n97@ifa*O>0a=pAA4B!BOkYvzCV*7_Ok%_tP!m~# ziFvVN2^dbb0r_c!hSnFa>K+829yn%>#?54?X_*fdJZ2rk;}cqIAo)Ned~K0 z|8PIJiFt`-lT65XMYhDNw9eW}$yN$#wV|Z-xK$prXK8iT;dow6GnhW@G#C3o+JCMb zT)3XURDRgJ`_xLj>Gc#8=B6Lg>SCl}8L+Ag2RQ_wDonuaT^G$UUR&5sn4!$LCwK4^oR3Sd|yz=tX|8<|PYz=zLJnB>B=&4A%lEHPP0c(_fa)Gyyp{WAan diff --git a/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm b/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm index 04b378fa5780ac1edb1ad17d486954f75fa3d97b..d426bf442236303c41f258d6abccc85914252aed 100644 GIT binary patch literal 2408 zcmY*a*^c8x6!k0}C}5WE*3d{Tq&$FlB0}O}c$$}Nj_u?Dz939T>WEI#O7{#r&in+M z#E#e(!B_Aj2+6S>JKk-{p>CZU+PvhdZk>DX@u{jCP1CeWyDoo|r{*B?OodSCxZp*Db z+u&%516r?ic4WZOP-Z~c%9IQeYcyt;HpY$ zmEkEM8!f}S2IzNJE#=Alrzq0;!Ewf;Z8s`1;gvKVAP z9Lsw488*m_RJb<9J!#}GYGGR<-q*w7)bM7@K^yy0*^#`uIsU&?(`#;44mS?nL@GsfqP&pDqnf5H3( Z=L;UiCG(X0U8Neu+e-eg-`Q@r{{m@?u+{(o literal 573 zcmYk2yH3ME5JfjH-iRUv719U?DS27i^7bmh6nyQ0tAy6HPE zztXO?%gPCRakvFlm)?hK_I=Z_xflwwC*FHf+ix;qf4ai32KI&57b9EyvU-RARl^y0 zGE4uec2I!S_gSAsvsJGwqg_tYW(7s zh=wjy8y=rMlpXV@l7dGfXev<9DIO{^qfJo=v$BM+sK*!3RCP(2*C6GKC|U$fb!_NI zG5MAhV+B(GT~VMl#f^|6u`Ivmg~Gv8uq8Ra8`E!-^fxDrW&7V2z|}Yb4up$qulf_>54j zA-wGv63B}$;5)L;F3-g%-F@zrMK->5`}R3~y8DbA1VPXQ5&tdk4)4T#UXVx>o(*nP zH*LRltKJ{9uG?)pv7hg?)~ems#eTg%p0vJfpKqTFKMlSbx3)Rjw|l)wy}MtJcUslW zs@|%)Tee*;1-)vrRquSUJ>J>dx0Ckm?XA-bKDe{nlfa3SJ}?-6y=NuVB*ER_PS&L1 zIz2cwZPG~2#$5Yo%9q#$llfrkf(F4$xnySR^i<%vd!%*xmT9U5c`RJ8bZ6Mn8dtSO zSkVDzUQwE`OOJ#m>}kfCrD=}DKKNU3B#GwsaPOLB1Z3q%Ut8oEVFH91;h6vg8_6#G z3JE}@6|PQEX_GDpS-dSj{$gHRi5aY@rm~_#rp9K=`MH%ndMqu3eVx)LOEQbl>SEkw zLtj?#vPpkSdeo;^ObFLTmL6&lnQS7-*kpq?2C%pf|HPK7y*J!=gC4aTbGXc1{K_Q$ zL)g^tj}lQ+K3Nvhr2oHcbd*>Pb53L4oYF+lzLJQNVB^UqeY5O0o`USD z!1LSbWA_foCp@Lm}}=4>oXlD$x>#1v$|#HL;&B)P@HQ4WG#Mr z1Z`d81I{?c>k&Jq0~_jsTNVM8|tPKxW}$P3vWwiypK>|8rS)dcA8Bn{(8 zfPM~B0;4uXcgbjuB?bCOb5j7dBCpyvor2;AiC}IiC|WuOGobtIDEP;K;<2cCt==$0 zdd}0w$c!6FHc+cO&%KZFZDd?_2Fym7X%4;cNDko7B8hhs}+)$T0AOXnwxh?%y sLqH>qhS&u;fd$g>Skzcx;TBhnu)xwSwseax-Qr7qpEm59#p!hVAJ7JCP5=M^ literal 472 zcmY+9y-ve06os9{xk(ccNDNF!9zaY)LP(6=_*r0p2LLxQ#8v*3gqES|1M(cqJc7eb zj9szV*WWol$0mdjMO<9t?Q-McJ|BteoRVk05*b^|K4(v&6s7P7kN8osWQ&$8IJD6c;{F(O6DmTUs$ z!;6C3a2S;Da0U0%jPw31@z7L#*_e6oJ>?;uZ3vQMUlPciGODL zX7cE}$pf_LSAkUl)yTt+ucN6FkKf1i=L|`>!!z{j!kJBraMEe9sn#E$xiQnFvG~$F T2T=X%+D)jA@q@zO9^n29BNfX> diff --git a/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm b/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm index 5f96244586bcd8d2568b5ba2987225a75afc987e..4e25939682431fd70573c8bb712067300098ec16 100644 GIT binary patch literal 2557 zcmZWq$*vn!5bfk84VS_0d2kzKArNdZNGyO&b}SHt1$=?Yv-B*z_R!t7+nX%%5r~AD zpfH34i9te$9SHFwh#fn|MVqQR)qXA5`0Ca@=Tz0br`ncfS&em`e$#99)gxLjape5D zN3GW;o9XdrJF8AcuTJx6F@3xoU7jpQFC5RRMKOA2{M`8Ia}QasEJis+kEi98Vz#Vi zi}`4>naoZnub)oG_XpPTq?nW^v&obBVqR9$VqE8^v2zxlFRyUuLGLVdvRIc>4mKC8 zYu43!nv1>M{A%rKo$I=wwXt8mcTd4SA2#-jiEJ3r*q0uV4Z{fG00FoI1mKP$TutEO z5J(qUpyfg1Yj@wK@__x;Jk47;U9xw(0OMSy+J>0gqJ5wKKus~>~#u+$LG&0Tarx-qcA z#yupt`HPqrler`TCt1jbx~7vfH~%mqotxif#UYS`9KpdHpuxiY$)GGZf6J=zY=Jbx z9OuKi`Ge1gitjD)w7r$Cg{~#7@dr-e>sXggK3kcj$wNCVN$f(_72td3n3u zMmBib-c~khHs(-;eTMVONM0F4Yp>RIr*Yo5FVNSZgJM^b%hBE()Txf;I!Vq2Qk{yn zU@sSNJhppit8LT+5o#WYYVpGSaMV`hmFA}-%HuPfS0L5Xh51nqOLJ@7*dMUl?`Gm) zs3Uox4FtviU?9H3#4m*yF3&KiEnG?DEfsH{Foal_TACXfl^8+dLnc5Xg2cNV`Bc(G zg?U4;%85}ghByXX1PI>VSj?QpzK=S#fA;AI*A znYlYsNP;%5b7`M(E>dPHF6RCJgwthrz#!Hc>9iw;R#ND^$2wGQ+sa@mUl5(em52^x zY6l$&`BrHdI@n}O9o7|wF)MT}bvTz8>fuUkt|0iaxV0Fai-RB?gZ(xq6GI?Lypa7;EjhoTufMIHTz7rDF%I3E zSoR4xd23u|XBFI-t?^fqZS*f@t1i{^;aXj~(Z8H6a1K7uFoB~VXcs|L?(R^hS5pm? z(c2-jU&H_Y4$48T>kjd-HXh?c-A6S!i}ukD6~wxacc>)MeX>L4X^lLo>JLjte9gASx^&B+7JD5DNH$1D_O2acpcSag`!p$B)qR31`Mx z--$GLtC`u^*=39|$=Z0meYxx4pVdgab)DK@My_DmLj+qvXEyyUqt*aPx*Egl=1TNAGk=?xu`+e zWqY0Kl09=J`?En^S+T-wV8q(c5$kBy0X9GMj-oxSBOn z#hRINF>^J;C=_dKR03nGShG}^4VNY#Txgn2F3NQHrP_v&kk|o6)kwz}10w?$6|Lo> zO>Eq(iG6G^g_ySWw1I^alBtcVHmKyFk|zE?ayT(w diff --git a/data-otservbr-global/world/world_changes/fury_gates/edron.otbm b/data-otservbr-global/world/world_changes/fury_gates/edron.otbm index 72d3239b4bb021cddd6cd94880c61fa98a30a22c..3ad2c77ca4a26f8a09a777beb730c07f68fd840b 100644 GIT binary patch literal 2358 zcmZWq%Z?jW5bY#0PA(9l35yVhhXp~bVj)1{A(R~p0$~B-1Ds4tu{0hJo*}TvCO^Sz z3nX^C+mE*0&0FqwH{ZdU9nVFZsyeshR@k^zbo;F+z1Dj1#8bw5y=IrPd*;!N ze&_JG(>rLkZ?!x7?d$ty>){>4-0SxaE_ZteN5^Kr^=Wti!daa=IK08}*>gI++57m& z*4V-k|g^oH?weN9tQj zzjKB$!9cxQ!*FJ(zNXBZr)AlaVDg~^BMJx+4kUT?aZZMSzbk5?rEEbkvTOhm4b*58 z01m;(Qg>+u+z6a2k5ISQL@h=g9SoI4*!H7bG`3_5(2qfU(j62VwT_Y8uG`kUc_ zEeIMn2adFhNe5$fw^cUggkrU_G2W#g4V=gt>6VSbE>lBWG9IIU;ve`bbT;Yl#H1l) zeZ~w~@v1m1oTx8%D^>)t9~q;~107;ftq2mrMwtj~G#7&{U1)27O%3Q>=rnX(?n1k3 zqCTT&JF#p3jRb7blbR@#jMZ0yov6?Gn_`k=iLwD-r;a#YGDKtybE;jYT&${`+3HVf zZ##47T*`(c@C+w*ktHo;FnBo>%2eaYZ5)2?=Z|& z%2HG|Me`GG6?;r;>=CCis0`;J{y+m6&OYN=yS+t1f_TT}+3p#;mCcLGoT zaJbED2{Bnw&5!LqtNsLKR9!+VpU7^as}F%rNNlP)o$)WhgfAEV0_ w$|VpOnVxIr=_|*_Q;;t(uzaa65l`p!da*!UEcLCS`GwCq>yln~xnqo%Pq&I_sNTt?tqnYpWL*D*E+}tE_wW zLPfXI`E0Yr+H$UTqjvplIp5eZD|6Fw-c{+T^i>8bLzNNB((&h}1d4aeHw&fn@xrup z-g=QW$&UG+G|lsTNO#OlGR-%3aYtiM<-W=uq^ovU?XKEgwR>v!)b6R>Q@gKrU+uoy zeYV?J19b}2DPX74i6xPsXC6}6Elwml%dv;*9h=#iOZyn9l|?GM=!0|>X6F|6#FZI;QdxjQRzvZu;@lBIL`9K#bP zE}i5VKBUafa?gBAN(ysk*_Y@Mc-oWJU6Ix`4k!!4G!9iBsXT^s)$Xd@RlBQpPwk%C zJ+*sk_t|b+#L{Fo~09@KL@rzYjwjsasVTL2&$8v^)_ zR3$tVmSqO6^9S__k1ToVtO;Lu{G=uVkFA}pb988KE|t!2=;o0e9xf8|&rJWW+!`H= z`VZFJUX}_52lh^TLjsCB?E8y^k{qcoV{D!& zfc7S$N&%Ehs2297RMDy4*yvMv$-Oa9IjoesF{&I~^>o?OJ_2_r)s$C*N8pak1L~mc z4OHA?papn?Cjs8@PiQC_u|~}Wg<9t!jcZ84%>V;s8U38Pop8KbR>)Gl05Xr zxH346N#)>J;LrK6RotS@Enc=6} zmLbgbw_B*p@TY(=+cWnFeuZbYBkr00DY70}h%48>SdQPxCOG$HBrCAIN985=Lfs4f zM;7>50aoB2saIo{{LWX)-od10hcZf7e3`D1OxH+$AoU=3j;!jLt7opB1s0@4_<+X; z*%es)cUgnKbb-}P8jc=|R$>w#jY%I#Mh}a2wd`RUZvtc(Q0kWLUxrynL-M`=WTLTx zb5Q)sS<6@pR5mA4&W8e#?XM&w^S6{1WIt7*95WcQh{lz{lX@fsEolgG;h@1U( literal 508 zcmYjN%SyvQ6rDbd#Ro3Ba3#z_ab-XRL6atF!Ijp9e!*x48cZ@FiLFSX|L1r3epLGd zQ%^FT=w|NW+;h)8cL*Uwk~-erWHxS~It5d&ZIeqjx!NW->|Uh45b<7?uu*K@MdA8?b|7vu zUa+!BCS<%Umz-y5^jq^e(2F#jwF0?M8dI4!%@o91kA(CIPzT^eVlqyvc(cfc)7 zAy!@ILmRltN=uHv^_T^u64;-5%m$~dI#aE4&|PUD!9%+Y{-u$N#^Yvlx`STpPvBO9 xqaW>N0jXd%&|>RMZ}6(SJ%m-dZiRX{MIGu@nYvi5a(1z;{GVcMF7Z2_~Vilx~!GX!!;?)(4{ z;e>(`vJSD^Znt}}&-@M~Zrp&tp@(|)mB+sHQ`P&bUe#AiTb5;2)=~O~H;Y@x=)7Vy zM~}Q=ec@l%QT^-Q-nIJYdPDE%%e(cf{&xMtsJFMZQ-8PdLF4Tsm#ojW>Kmku^lrG* z+urMKZPxwkesA3m)^+2BHEX>W=$*YAfq&zzjjheyJ-yTTW@G()ZTVtzJ2cAEtINu3 zTVL&JqppryH?3RGRVSK#^=##U&Vl;ZoUOjPvr^fYY6nIbuzF6>6YaeczZ|Ic3mJUp=N*U*(6Kfx1V+%f{-l z!NAn*tG`Ty?m+!#&Te1*&8I}(j3twyS|!sH&5?SsR@oGo5fT=DVWAbPKdj0gnAk3x zSAb~^)deyjOl2d9)`)Za$zb|0P1Fw;EBjMUrXBP3k8)kviShXSG5Y*=mbn?K?+7iz zNPSNNl4&P&LveLtM*M#M_@8f**~x`%XW6zhVi?Hkh^-TIb(q};9Gx@dfN~SY(*<+R zq4oTEC*fF-r5mdUhvd6Mb~-1YLUcg_v&Cw9tFq6YGYPs0KR8#DrW>n&xGF>Hr^;Sq zSa*cdbQ6@WIbNzpGhG%Yszx<D(~|CCBqRcQSH7*N%91Qikn=bJ*NAHWNeqLt}_4 zI~g|`ce^z4m`xXB!DOPHT)5>sazYQf@LjI3ka1=9zcfO3%mech4@@^zPl#*q)15F3 ziFGsekZ$0=nPbG81!!%SB2O_$tsGTq6>^psq}G%QL~pipA-RyVRHs=#r7Y>blLOZ& zM~ z{C9KJC)mflt-6J}PlB*hs=M5)Q=Fb`6i+sc-$$bU0E7g6^mZ@+fuYEu+(%*`iL7O} zYQcLk2=|8Y>y6;YGhi7xHAcj#e6nP{;CBTO<^x;r~9O1E0 zW-pTaNbDoAj}rLN;$II%mf6(_Nbtt$R|*!Z+Do|&X$4WF9y)K8)9}!(Ale^uf>;KA z%EdwJ`mjOk`q@&Yg0YaEa+L`_@8iJO(0c)%z+i;}57VV(7Q#DkNJJ8595CLC#3o9K zs`;*pCj9q7ei0&~{YLNUF&Nf>qg;9ddUZyZ^BQ_Nj~ycO(BGNa4ey;{qR`(3x3q^| zMSJ==b>e(`_%P&cE(F04`@o-4riO-`Q^H;d3iXH_Albmogh7}m=nR*qCoH%Os8(3= z1`$EvQO-(U?pXIx!Q`?A*r?>{paCMJ0<4y8qh*`uQaz=}P<9O@Vs6gBYilNZW=0cF z;dbP7%*h*IzB1gI4DL+*Y!dTkoD1gNhwwgA9n6+mDx6V51dj}YPR-{*b?TrZ@WKD- CG literal 580 zcmY+8yH3ME5JfjH6G0FKNK{B$fT*y9kSNnpK`5Z}BhK1hDVFRwvQ0voydczZ*@#CEK*qB~~ zQITf!%se+wYX{^yr3ttp&x0%)7g0K*Y{H_L`7t-Q_DCH0JS%2Co1NriHXWJwNxc2f z9;SCWFKy8zbF$bFE&D+jmu)FU%UKGehYGxIr}SJ<=vhySvtER8^~c$d!q{v`VSMeD zunkD1p4FNI$oGEp=Lo3w8g(@409ti{hg~ju`U!=2qV=kj{idc4co^WktHWGSRn&)4 zec#2PJKwm6kqoSx+4G&xd9~OeQd3C Rhp#(=>H;Y`QtU`6e*x^=8f*Xn diff --git a/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm b/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm index f0b1b943c76bc6c716f9a7ad5a15fd05307007cd..2232ecbc988b575df858bd4b4a47c0bbe8f2b697 100644 GIT binary patch literal 2187 zcmZWp$!-&A5N=2?G%(P6_umP%IAmADa?E>+d{)&93-SypN7rLS4rT$>_!l`VVs%vygnR`dDn zUbDN9-OiWtPJV7ByV+=G*XEnuRwuh$zf!-n=a_Y?l`WBVK3|#ZG~3-~YdLGIHkyl# zzZdiRp}=alI^BgR3x|E9#kO5c7`FKcI}$$gSB&u8d!aEd-dpV3}V02dU% z7En&IMbKa3>91!Ls zAv~!lQP2L#{YBg#$yoKcZ~x1B(zAbv>_eKK{f%{__*CbU_U(Vz?hK^QKe>=+GAF0_ z%`z0rIqsnf97)A8zE|8DYIg_rgPEN%x`im}8H$2_-H`-6%|YKy2ICFvN4so~*;J&* zM3y7_F-spsXUu)i+?$wnp>BoyBgqG60FJC?RyR(eg2H}C`E)DQ0s|B%n|mvh2?x?} z#%$UpvBQG4QPy5S!I);bD%#wx8Do-Fznl~s4RnzO77-0q2i(ZE;{moB7uaU3ptuT3 z28fUpdICe7MgkCy$Y>-}z-bg@M!1hu%Skm;Iz-TPgtDA<>HV~n<$Mt31e6Wb=4^_Q zUK|a^U7wr`-3D90K>&9{uyjo90g>*A6c5A`dKPT?@JpzjWqSxg$FytN6d*lWV)^T` zd+gOm2nqU>SAY}rH+CX30c^StQUX*V2ZrSa2tf;EH;A-pR#1NX2*pLZ5N!InkUIqN zI1~`ia(DXDnN6?=963)!i9jSQySZDINcQds8)Is`RFSFnlPbvhSUCviQ&ssair(55 zPTXZUC3G3d5+FUUtqQ~YSlO|Mz^gJn4;+lg1BaeAMf1VIKEbm-HzLyCaG-#!%cbehA(ezYbqzFvjvi)uXXQ!ac>T UaN#PA$`qhE%CKG1TR%$je|dcVzyJUM literal 456 zcmYk0OHRWu5Qg1XsGta9r?g!lc>!WYB!tAWJ62E@Z~{Xt#ZppRNod*Sc09_Xmtf5i zY$kOa*d*g`zJF#ALP(=Fd?)3)?7)9ok#_3{-LVCa=u#9hz2zy-_(@J@Owr3o6f&dd z;YE1XIz%^;ra&W}KV(7`Le42$Fp;qPgoisADVY_qRQxnoY&j2~(q#7^jOAm_E2z7u zMCF$5`7her?ilT@h%vf{;rTu4{u~48|6%g1y{mpD5D>06RRC-{s8|5ZcK6?1<-fX- z{#(OVF5!b`r{KmTP|fTP=-MiPY`v|?4x29ibRPcNOmWW&rx~jU%wg#iI09gV&7|dcLVc@`l#T-CM}9P^EBYv+p0{lLuEw?Evg zi)Fr4xKX%XoFBEC^jG+R$Ceq}^smf|P5MX27X6{4vD?OOx9Cr?+ie=H&aIQt1D;S5 zMrzUD;v_gvsMBde*=bQf%oq+3KQh`n74gsQW&XKji<;Y1;6uuGTXdgUig^oeP=65W zbXf>qlLm}={kTqAG~&rc??BJX#bUzTdQ)}$Cf#J<8^_rKWgi)`g+=|gW^qdhaF--6 z_LKlavZXeSp~9X3nZH~VfuKdJ?9n_CF=*2hZ23tt=lmE`ZqoS-^M0t77Cp|EZv@kT zrA=qp^4-D`Ht7Ynd?A*riV0h^zQlTA8)MX@3;#z%1`!!V+|)QZjaU$K>%x+x5gS;g zNzbs5?$efrsgKx0>d}tFP`yq^0h>Co3s-a*bN->f~!lo>}7VWfqzAvxzn&TWqj% z5o}0szls~|$)ZVYNZc2TbA*3>i3Lc|0gE7^81w=4=o4lfr9OSOoN*j*v{sB|x6=j8 z=`l>^eazhsk6B;42ILw|*MPhZeJUi+_&xDwXU^->=i>AcQwx7jWBLZ}8#x~f{7OoA zM-iot9sxZmsCfwsRFxSpC=^fT)>@H&aL7s!)80BkExX8^Zjf;l5yR&E!av4+=Oz~;EZ`QJ_I4wZ#%cXJZV z;>x&Vy@#=f9tx-33xWqWupZbyOB)#i5-^uD*t!f&2|MOPLRqN2&pKRt^qoJ}{J2#( zn-V0xlnA_iVi|Iy%81z7ExJXf8Nt@8_j_TP{u}6(r5&)XIsrP#T=NjqOp#;&9ZeDv z)`TK%siW2ukE=B^k8Wxn-3cY)FHV6g8hcXtjI|zko??%I0xTc2?ewtfa2}CV85-v^Yj_x#wP$_|<4e@LsSqDr!Xr5ro z=UR-jyfM`m20_amebaKcOu0+Xvw*hDz80d~!wX#Q>!bLhzItH?#;|Kf0}At?$bcdP ziVP^qgJJ`U4JbCCI1j2AP{n{M22=s)CA`D}I(W4Oc`y<9+%mAfu368+2XZKum7R3x zDPFkNa#I(cboElsEZKvf^t3{mnd_VM=`2%qw{{kaWgIho3N}#6Aw9{!{O1y`NVH8E z_XV0U94J=P1Unqk(^(j7UkJ7#@F8V4JkdbEa`ZL?I~vkmc5~}y&uWB=2F)CexwCmK z@j$=va>N*S0|%K~uj;o6;web60mZ3a9B@{EG_^orGr>tU6A{iH uf~s6p9IXoYWsOpu@pCW6RG%pr6H#YlB1jo$xH*!PQt3ke8^Kq_gZ}_qDm@hd literal 675 zcmYjO%TB^T6dhiD(3S!&T$#*5UM%Mod^;H2PS(f=>i9|vJ}k#;A-K_&034YcFjGMYM7|Iz3Q%f2YRZE&PE(NZAXQuvCil*Hd2Dy4n_qa!+K diff --git a/data-otservbr-global/world/world_changes/fury_gates/venore.otbm b/data-otservbr-global/world/world_changes/fury_gates/venore.otbm index 1ca21d586d1592ce89e867863c75802fb41a100a..1922f21ab60638f031a404dcba69b5ac3bbeece1 100644 GIT binary patch literal 3646 zcmZWs$&Oo95OpTgH)J5mB9S5_FF>qlAt50oPjB)Y0(hS70G)0pF-Z^VZUUP~+3*WU ztbxoE0mOpeVTt$zH&y4{*M5P`dve`dr>0X?&$(PKx5yopXLN6R_n5pZDmi-Oom^+_ z&TOOb&3yl6p*`E2?atoYD}1)LQ>bms_qTToAFh79`oWR6bM@`Qrbsttd+WRNo&EXt zR$=YV+Whv~wcE4RH_zwp%(k|7XYX%rZ|&{Rc2~dMynXRZD%{#xSHbDGQo+sbulHsu zS{%>a%iVoraiUl^@%aOJH~22q&5!36`I{okUq5poCnY@*34WA$yrIX*B|R=TmI`0t zPK9?i`J-VT3*MKiz~ibOi(<85?x!a=aJSerEwOa=g5nliz`cGRKicMj++9ZucvOMTinTQWn@`obQYNctKa`Qx`B_8UV5OD6%Wwlx9nD0gD zM3re+hox!@mLv@@qQ_Lcrjl1`gn?A8cCl+dRs3Slv?YR*7vu(^ihW71o(vFwCY(|i zMo@ItxkPQ7)V}#d6-xs^k``s^AaN3_9G81bl&9+0E#|7vfXjnSX)0ZbHFdp0ML@k& z#Ta#}^#6mmbww|T4Uvv71kN(FID%xZ z5~ia#1~Yd^(}{U5tRpi^L)$l_3}H%RgnF7RoGjB#fb!6M$I@ulMo212Uml~^Y=$R< zt_&G#WyH!>#`ZmdCNZl+bQ4O*&Z~~3YH2G}$8dr^S0}hDi4gn6DNaBqLX{{1udTzB z2vQW;cx`ZG=m#Rn$tiBBd2pn+`~~Kpl)N?$#mc2G0MVxc$_qVATj;A zNt6XXM+u_~>WB^XwM^@v}au6WTuMS zGC#2UN$P4hxI zrG3G|M|1#pX)bEB-J=d>0k6eP^OGpz8b?xW_ek94l!!a*g`_F(bmS-YQqtxOP8bRs z#nIU*0XTMSI(^iZOm;XdA1rI@a?~>auIku87@|vUbOyW09iR~b!L90XXCQp3x}^ti zdt3sD5NB*JuWz17;27-a z-WWkVAaufLGZ{^&$eoce`v{AHCYfbyhZ^^A=1b8TA!L5cIb*O1PLmqW1XV=#2;mMf zvbpZv5ftDOV{*sjf;%x!QciJ$d9%|}vT{C&7}P7{@=_{vlt PSLOGqgT>i{gM)tox9nEF literal 587 zcmX|-O;5r=5QY~Dwecf*^JKCIjVGHJ6E8}EvZY=`0l(!^?Itv#l(e;YA>qe*_9twe zI?P_$+2@^^ecxq_F~#ce_1BY?0e`0=_1ZBT2ZapzGRmiXBx9M%(~RE+3+{zco}~Q3 zzO>J4M{JPr7-%T7&oo-(Q8MR25k#}#VPNAW;KV;-m& zv;(#`CcrGRE->tKbrhk43LRAFc%Gi^gNj@F$Y{O$N`m`FX?riE_CUoe#XG4!R2xGR MtV#t>vtO3wAJ8=dQvd(} diff --git a/data/items/items.xml b/data/items/items.xml index 8c826d3fd64..d23468cabf3 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -15098,7 +15098,6 @@ - From 1ac6d8ab436e05d3397195ad6c07da65c9b298a5 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 5 Oct 2023 14:41:06 -0300 Subject: [PATCH 16/18] feat: reward durable exercise weapon (#1566) Created a command to enable players to receive a durable exercise weapon at just one time. --- config.lua.dist | 2 + data-otservbr-global/lib/core/storages.lua | 2 +- .../customs/reward_exercise.lua | 13 ++++ data/scripts/talkactions/player/reward.lua | 65 +++++++++++++++++++ src/config/config_definitions.hpp | 2 + src/config/configmanager.cpp | 2 + .../functions/core/game/config_functions.cpp | 2 + 7 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua create mode 100644 data/scripts/talkactions/player/reward.lua diff --git a/config.lua.dist b/config.lua.dist index 76508f6d7d7..a00bb061f81 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -186,6 +186,7 @@ onlyPremiumAccount = false -- NOTE: buyAolCommandFee will add fee when player buy aol by command (!aol), active changing value more than 0 (fee value. ex: 1 = 1gp aol will be 50001) -- NOTE: buyBlessCommandFee will add fee when player buy bless by command (!bless), active changing value between 1 and 100 (fee percent. ex: 3 = 3%, 30 = 30%) -- NOTE: teleportPlayerToVocationRoom will enable oressa to teleport player to his/her room vocation +-- NOTE: toggleReceiveReward = true, will enable players to choose one of reward exercise weapon by command !reward weatherRain = false thunderEffect = false allConsoleLog = false @@ -200,6 +201,7 @@ toggleTravelsFree = false buyAolCommandFee = 0 buyBlessCommandFee = 0 teleportPlayerToVocationRoom = true +toggleReceiveReward = false -- Teleport summon -- Set to true will never remove the summon diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index d5f434274b0..b1795ce1dde 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -140,7 +140,7 @@ Storage = { PremiumAccount = 30058, BattleAxeQuest = 30059, ShrineEntrance = 30060, - + PlayerWeaponReward = 30061, --[[ Old storages Over time, this will be dropped and replaced by the table above diff --git a/data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua b/data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua new file mode 100644 index 00000000000..f17304cec00 --- /dev/null +++ b/data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua @@ -0,0 +1,13 @@ +local winReward = CreatureEvent("WinReward") + +function winReward.onLogin(player) + if configManager.getBoolean(configKeys.TOGGLE_RECEIVE_REWARD) and player:getTown():getId() >= TOWNS_LIST.AB_DENDRIEL then + -- check user won exercise weapon and send message + if player:getStorageValue(tonumber(Storage.PlayerWeaponReward)) ~= 1 then + player:sendTextMessage(MESSAGE_LOGIN, "You can receive an exercise weapon using command !reward") + end + end + return true +end + +winReward:register() diff --git a/data/scripts/talkactions/player/reward.lua b/data/scripts/talkactions/player/reward.lua new file mode 100644 index 00000000000..7c2999a587e --- /dev/null +++ b/data/scripts/talkactions/player/reward.lua @@ -0,0 +1,65 @@ +local config = { + items = { + { id = 35284, charges = 64000 }, + { id = 35279, charges = 64000 }, + { id = 35281, charges = 64000 }, + { id = 35283, charges = 64000 }, + { id = 35282, charges = 64000 }, + { id = 35280, charges = 64000 }, + }, + storage = tonumber(Storage.PlayerWeaponReward), -- storage key, player can only win once +} + +local function sendExerciseRewardModal(player) + local window = ModalWindow({ + title = "Exercise Reward", + message = "choose a item", + }) + for _, it in pairs(config.items) do + local iType = ItemType(it.id) + if iType then + window:addChoice(iType:getName(), function(player, button, choice) + if button.name ~= "Select" then + return true + end + + local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX) + if inbox and inbox:getEmptySlots() > 0 then + local item = inbox:addItem(it.id, it.charges) + if item then + item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) + else + player:sendTextMessage(MESSAGE_LOOK, "You need to have capacity and empty slots to receive.") + return + end + player:sendTextMessage(MESSAGE_LOOK, string.format("Congratulations, you received a %s with %i charges in your store inbox.", iType:getName(), it.charges)) + player:setStorageValue(config.storage, 1) + else + player:sendTextMessage(MESSAGE_LOOK, "You need to have capacity and empty slots to receive.") + end + end) + end + end + window:addButton("Select") + window:addButton("Close") + window:setDefaultEnterButton(0) + window:setDefaultEscapeButton(1) + window:sendToPlayer(player) +end + +local exerciseRewardModal = TalkAction("!reward") +function exerciseRewardModal.onSay(player, words, param) + if not configManager.getBoolean(configKeys.TOGGLE_RECEIVE_REWARD) or player:getTown():getId() < TOWNS_LIST.AB_DENDRIEL then + return true + end + if player:getStorageValue(config.storage) > 0 then + player:sendTextMessage(MESSAGE_LOOK, "You already received your exercise weapon reward!") + return true + end + sendExerciseRewardModal(player) + return true +end + +exerciseRewardModal:separator(" ") +exerciseRewardModal:groupType("normal") +exerciseRewardModal:register() diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 6a785280e56..d3bf0b87eec 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -91,6 +91,8 @@ enum booleanConfig_t { TOGGLE_MOUNT_IN_PZ, TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART, + TOGGLE_RECEIVE_REWARD, + LAST_BOOLEAN_CONFIG }; diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 3eacd9e9ade..d0abcc9352b 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -392,6 +392,8 @@ bool ConfigManager::load() { boolean[TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART] = getGlobalBoolean(L, "togglehouseTransferOnRestart", false); + boolean[TOGGLE_RECEIVE_REWARD] = getGlobalBoolean(L, "toggleReceiveReward", false); + loaded = true; lua_close(L); return true; diff --git a/src/lua/functions/core/game/config_functions.cpp b/src/lua/functions/core/game/config_functions.cpp index a64f1d91b66..bdeaa226fc1 100644 --- a/src/lua/functions/core/game/config_functions.cpp +++ b/src/lua/functions/core/game/config_functions.cpp @@ -251,6 +251,8 @@ void ConfigFunctions::init(lua_State* L) { registerEnumIn(L, "configKeys", VIP_FAMILIAR_TIME_COOLDOWN_REDUCTION); registerEnumIn(L, "configKeys", TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART); + + registerEnumIn(L, "configKeys", TOGGLE_RECEIVE_REWARD); #undef registerEnumIn } From dbe353c22878b7e6fae08aebbed50794bc1e96b4 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 6 Oct 2023 11:48:57 -0300 Subject: [PATCH 17/18] fix: suppress compilation warning (#1684) Suppress false positive warning for type comparison by leveraging compile-time checks --- src/kv/value_wrapper.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/kv/value_wrapper.hpp b/src/kv/value_wrapper.hpp index 9317eb6b8fd..a4e899adcb0 100644 --- a/src/kv/value_wrapper.hpp +++ b/src/kv/value_wrapper.hpp @@ -179,7 +179,10 @@ inline bool operator==(const ValueVariant &lhs, const ValueVariant &rhs) { return b.contains(key) && value.get() == b.at(key).get(); }); } - return a == b; + // Compares a and b if types A and B are the same, at compile-time + if constexpr (std::is_same_v) { + return a == b; + } }, lhs, rhs ); From e133007fbbfe489b10a71dffd342db48862d4b2e Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Fri, 6 Oct 2023 15:07:07 -0700 Subject: [PATCH 18/18] feat: monster critical chance and multipliers refactor (#1679) --- .../monster/bosses/doctor_marrow.lua | 110 ++++++++++++++ .../scripts/lib/register_monster_type.lua | 3 + src/creatures/combat/combat.cpp | 139 ++++++++---------- src/creatures/combat/combat.hpp | 2 + src/creatures/monsters/monster.cpp | 33 +---- src/creatures/monsters/monster.hpp | 29 ++-- src/creatures/monsters/monsters.hpp | 1 + .../monster/monster_type_functions.cpp | 14 ++ .../monster/monster_type_functions.hpp | 9 +- 9 files changed, 218 insertions(+), 122 deletions(-) create mode 100644 data-otservbr-global/monster/bosses/doctor_marrow.lua diff --git a/data-otservbr-global/monster/bosses/doctor_marrow.lua b/data-otservbr-global/monster/bosses/doctor_marrow.lua new file mode 100644 index 00000000000..3de9bc41825 --- /dev/null +++ b/data-otservbr-global/monster/bosses/doctor_marrow.lua @@ -0,0 +1,110 @@ +local mType = Game.createMonsterType("Doctor Marrow") +local monster = {} + +monster.description = "Doctor Marrow" +monster.experience = 0 +monster.outfit = { + lookType = 1611, + lookHead = 57, + lookBody = 0, + lookLegs = 0, + lookFeet = 95, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 1800 +monster.maxHealth = 1800 +monster.race = "blood" +monster.corpse = 18074 +monster.speed = 125 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + critChance = 10, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "You can't stop the future!", yell = false }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 }, +} + +monster.defenses = { + defense = 54, + armor = 59, + mitigation = 3.7, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onThink = function(monster, interval) end + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +mType.onDisappear = function(monster, creature) end + +mType.onMove = function(monster, creature, fromPosition, toPosition) end + +mType.onSay = function(monster, creature, type, message) end + +mType:register(monster) diff --git a/data-otservbr-global/scripts/lib/register_monster_type.lua b/data-otservbr-global/scripts/lib/register_monster_type.lua index 68983418159..21a10d94aac 100644 --- a/data-otservbr-global/scripts/lib/register_monster_type.lua +++ b/data-otservbr-global/scripts/lib/register_monster_type.lua @@ -199,6 +199,9 @@ registerMonsterType.flags = function(mtype, mask) if mask.flags.canPushCreatures ~= nil then mtype:canPushCreatures(mask.flags.canPushCreatures) end + if mask.flags.critChance ~= nil then + mtype:critChance(mask.flags.critChance) + end if mask.flags.targetDistance then mtype:targetDistance(math.max(1, mask.flags.targetDistance)) end diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 8b650253567..75cec14c30f 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1139,41 +1139,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptrgetPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - // Charm low blow rune) - if (target && target->getMonster() && damage.primary.type != COMBAT_HEALING) { - uint16_t playerCharmRaceid = caster->getPlayer()->parseRacebyCharm(CHARM_LOW, false, 0); - if (playerCharmRaceid != 0) { - const auto mType = g_monsters().getMonsterType(target->getName()); - if (mType && playerCharmRaceid == mType->info.raceid) { - const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW); - if (charm) { - chance += charm->percent; - g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster); - } - } - } - } - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - - // Fatal hit (onslaught) - if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT); - playerWeapon != nullptr && playerWeapon->getTier()) { - double_t fatalChance = playerWeapon->getFatalChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) { - damage.fatal = true; - damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); - damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); - } - } - } + applyExtensions(caster, target, damage, params); if (canCombat) { if (target && caster && params.distanceEffect != CONST_ANI_NONE) { @@ -1194,27 +1160,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) { - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (damage.primary.type != COMBAT_HEALING && chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - - // Fatal hit (onslaught) - if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT); - playerWeapon != nullptr && playerWeapon->getTier() > 0) { - double_t fatalChance = playerWeapon->getFatalChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) { - damage.fatal = true; - damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); - damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); - } - } - } + applyExtensions(caster, nullptr, damage, params); const auto origin = caster ? caster->getPosition() : Position(); CombatFunc(caster, origin, position, area, params, CombatHealthFunc, &damage); } @@ -1231,15 +1177,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptrgetPosition(), params.impactEffect); } - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - } + applyExtensions(caster, target, damage, params); if (canCombat) { if (caster && target && params.distanceEffect != CONST_ANI_NONE) { @@ -1260,15 +1198,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) { - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - } + applyExtensions(caster, nullptr, damage, params); const auto origin = caster ? caster->getPosition() : Position(); CombatFunc(caster, origin, position, area, params, CombatManaFunc, &damage); } @@ -2080,3 +2010,64 @@ void MagicField::onStepInField(const std::shared_ptr &creature) { creature->addCondition(conditionCopy); } } + +void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms) { + if (damage.extension || !caster || damage.primary.type == COMBAT_HEALING) { + return; + } + + g_logger().debug("[Combat::applyExtensions] - Applying extensions for {} on {}. Initial damage: {}", caster->getName(), target ? target->getName() : "null", damage.primary.value); + + // Critical hit + uint16_t chance = 0; + int32_t multiplier = 50; + auto player = caster->getPlayer(); + auto monster = caster->getMonster(); + if (player) { + chance = player->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE); + multiplier = player->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE); + + if (target) { + uint16_t playerCharmRaceid = player->parseRacebyCharm(CHARM_LOW, false, 0); + if (playerCharmRaceid != 0) { + const auto mType = g_monsters().getMonsterType(target->getName()); + if (mType && playerCharmRaceid == mType->info.raceid) { + const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW); + if (charm) { + chance += charm->percent; + g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster); + } + } + } + } + } else if (monster) { + chance = monster->critChance(); + } + + multiplier += damage.criticalDamage; + multiplier = 1 + multiplier / 100; + chance += (uint16_t)damage.criticalChance; + + if (chance != 0 && uniform_random(1, 100) <= chance) { + damage.critical = true; + damage.primary.value *= multiplier; + damage.secondary.value *= multiplier; + } + + if (player) { + // Fatal hit (onslaught) + if (auto playerWeapon = player->getInventoryItem(CONST_SLOT_LEFT); + playerWeapon != nullptr && playerWeapon->getTier() > 0) { + double_t fatalChance = playerWeapon->getFatalChance(); + double_t randomChance = uniform_random(0, 10000) / 100; + if (fatalChance > 0 && randomChance < fatalChance) { + damage.fatal = true; + damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); + damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); + } + } + } else if (monster) { + damage.primary.value *= monster->getAttackMultiplier(); + damage.secondary.value *= monster->getAttackMultiplier(); + } +} diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index 16062cb685d..2bb2d0a3589 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -260,6 +260,8 @@ class Combat { Combat(const Combat &) = delete; Combat &operator=(const Combat &) = delete; + static void applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms); + static void doCombatHealth(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms); static void doCombatHealth(std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms); diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index eef9aaa3830..0ce9432ea68 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -811,12 +811,6 @@ void Monster::doAttacking(uint32_t interval) { bool resetTicks = interval != 0; attackTicks += interval; - float forgeAttackBonus = 0; - if (monsterForgeClassification > ForgeClassifications_t::FORGE_NORMAL_MONSTER) { - uint16_t damageBase = 3; - forgeAttackBonus = static_cast(damageBase + 100) / 100.f; - } - const Position &myPos = getPosition(); const Position &targetPos = attackedCreature->getPosition(); @@ -834,20 +828,8 @@ void Monster::doAttacking(uint32_t interval) { updateLook = false; } - float multiplier; - if (maxCombatValue > 0) { // Defense - multiplier = getDefenseMultiplier(); - } else { // Attack - multiplier = getAttackMultiplier(); - } - - minCombatValue = spellBlock.minCombatValue * multiplier; - maxCombatValue = spellBlock.maxCombatValue * multiplier; - - if (maxCombatValue <= 0 && forgeAttackBonus > 0) { - minCombatValue *= static_cast(forgeAttackBonus); - maxCombatValue *= static_cast(forgeAttackBonus); - } + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; if (spellBlock.spell == nullptr) { continue; @@ -1912,15 +1894,8 @@ bool Monster::getCombatValues(int32_t &min, int32_t &max) { return false; } - float multiplier; - if (maxCombatValue > 0) { // Defense - multiplier = getDefenseMultiplier(); - } else { // Attack - multiplier = getAttackMultiplier(); - } - - min = minCombatValue * multiplier; - max = maxCombatValue * multiplier; + min = minCombatValue; + max = maxCombatValue; return true; } diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 5f589af0e28..858b3ac8dd8 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -77,13 +77,13 @@ class Monster final : public Creature { return mType->info.race; } float getMitigation() const override { - return mType->info.mitigation; + return mType->info.mitigation * getDefenseMultiplier(); } int32_t getArmor() const override { - return mType->info.armor; + return mType->info.armor * getDefenseMultiplier(); } int32_t getDefense() const override { - return mType->info.defense; + return mType->info.defense * getDefenseMultiplier(); } Faction_t getFaction() const override { @@ -126,6 +126,9 @@ class Monster final : public Creature { bool canSeeInvisibility() const override { return isImmune(CONDITION_INVISIBLE); } + uint16_t critChance() const { + return mType->info.critChance; + } uint32_t getManaCost() const { return mType->info.manaCost; } @@ -325,6 +328,16 @@ class Monster final : public Creature { bool isImmune(ConditionType_t conditionType) const override; bool isImmune(CombatType_t combatType) const override; + float getAttackMultiplier() const { + float multiplier = mType->getAttackMultiplier(); + return multiplier * std::pow(1.03f, getForgeStack()); + } + + float getDefenseMultiplier() const { + float multiplier = mType->getDefenseMultiplier(); + return multiplier * std::pow(1.01f, getForgeStack()); + } + private: CreatureWeakHashMap friendList; CreatureIDList targetIDList; @@ -436,14 +449,4 @@ class Monster final : public Creature { void doRandomStep(Direction &nextDirection, bool &result); void onConditionStatusChange(const ConditionType_t &type); - - float getAttackMultiplier() const { - float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.03f, getForgeStack()); - } - - float getDefenseMultiplier() const { - float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.01f, getForgeStack()); - } }; diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp index a13792fd297..b214b478e3b 100644 --- a/src/creatures/monsters/monsters.hpp +++ b/src/creatures/monsters/monsters.hpp @@ -128,6 +128,7 @@ class MonsterType { int32_t changeTargetChance = 0; int32_t defense = 0; int32_t armor = 0; + uint16_t critChance = 0; int32_t strategiesTargetNearest = 0; int32_t strategiesTargetHealth = 0; int32_t strategiesTargetDamage = 0; diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index 1beea8de66b..ba77c49a90a 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -280,6 +280,20 @@ int MonsterTypeFunctions::luaMonsterTypeCanPushCreatures(lua_State* L) { return 1; } +int MonsterTypeFunctions::luaMonsterTypeCritChance(lua_State* L) { + // get: monsterType:critChance() set: monsterType:critChance(int) + const auto monsterType = getUserdataShared(L, 1); + if (monsterType) { + if (lua_gettop(L) == 2) { + monsterType->info.critChance = getNumber(L, 2); + } + lua_pushnumber(L, monsterType->info.critChance); + } else { + lua_pushnil(L); + } + return 1; +} + int32_t MonsterTypeFunctions::luaMonsterTypeName(lua_State* L) { // get: monsterType:name() set: monsterType:name(name) const auto monsterType = getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/monster/monster_type_functions.hpp b/src/lua/functions/creatures/monster/monster_type_functions.hpp index d9c513d2bce..9d892219250 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.hpp @@ -35,6 +35,8 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "canPushItems", MonsterTypeFunctions::luaMonsterTypeCanPushItems); registerMethod(L, "MonsterType", "canPushCreatures", MonsterTypeFunctions::luaMonsterTypeCanPushCreatures); + registerMethod(L, "MonsterType", "critChance", MonsterTypeFunctions::luaMonsterTypeCritChance); + registerMethod(L, "MonsterType", "name", MonsterTypeFunctions::luaMonsterTypeName); registerMethod(L, "MonsterType", "nameDescription", MonsterTypeFunctions::luaMonsterTypeNameDescription); @@ -260,10 +262,5 @@ class MonsterTypeFunctions final : LuaScriptInterface { static int luaMonsterTypeAddSound(lua_State* L); static int luaMonsterTypeGetSounds(lua_State* L); static int luaMonsterTypedeathSound(lua_State* L); - - // Hazard system - static int luaMonsterTypeHazardSystemCrit(lua_State* L); - static int luaMonsterTypeHazardSystemDodge(lua_State* L); - static int luaMonsterTypeHazardSystemSpawnPod(lua_State* L); - static int luaMonsterTypeHazardSystemDamageBoost(lua_State* L); + static int luaMonsterTypeCritChance(lua_State* L); };