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/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/). 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/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/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-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 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/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm b/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm index 9e6b2698eee..c41c56415e9 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm and b/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm differ 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 04b378fa578..d426bf44223 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm and b/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm differ diff --git a/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm b/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm index a112b2e45e6..15f3fc688b6 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm and b/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm differ 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 5f96244586b..4e259396824 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm and b/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm differ 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 72d3239b4bb..3ad2c77ca4a 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/edron.otbm and b/data-otservbr-global/world/world_changes/fury_gates/edron.otbm differ diff --git a/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm b/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm index c1aa4744aac..4a6e4386f29 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm and b/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm differ diff --git a/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm b/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm index ac1df9d0950..c9a57e0f45f 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm and b/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm differ 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 f0b1b943c76..2232ecbc988 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm and b/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm differ diff --git a/data-otservbr-global/world/world_changes/fury_gates/thais.otbm b/data-otservbr-global/world/world_changes/fury_gates/thais.otbm index dfb1039d07b..a131dd8d096 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/thais.otbm and b/data-otservbr-global/world/world_changes/fury_gates/thais.otbm differ 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 1ca21d586d1..1922f21ab60 100644 Binary files a/data-otservbr-global/world/world_changes/fury_gates/venore.otbm and b/data-otservbr-global/world/world_changes/fury_gates/venore.otbm differ 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..816aa523b70 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) @@ -457,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 @@ -483,7 +472,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 +557,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 +603,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/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 @@ - diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua index 86245cc2491..d43a31f287d 100644 --- a/data/libs/functions/creature.lua +++ b/data/libs/functions/creature.lua @@ -193,6 +193,37 @@ function Creature.checkCreatureInsideDoor(player, toPosition) end end +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 + function Creature:addEventStamina(target) local player = self:getPlayer() local monster = target:getMonster() 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/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) 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 940ea29eb51..d0abcc9352b 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 @@ -393,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/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 30b0208ad0a..75cec14c30f 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) { @@ -72,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), @@ -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 @@ -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); } @@ -1339,11 +1269,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 +1325,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; } @@ -1516,7 +1444,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) { @@ -1770,13 +1698,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(); + } } } @@ -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 56096b9fd71..2bb2d0a3589 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; }; @@ -267,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); @@ -368,7 +363,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 bab02aec428..d5c634eed7f 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; @@ -1206,8 +1207,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; } @@ -1217,7 +1217,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); } } @@ -1824,17 +1824,8 @@ 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/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/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 615f9211f01..dab6acfe1ba 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -14,9 +14,9 @@ #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" int32_t Monster::despawnRange; int32_t Monster::despawnRadius; @@ -349,11 +349,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); } } @@ -815,12 +812,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(); @@ -838,20 +829,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; @@ -1916,15 +1895,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 b168b18c3ea..9e3a9c52ddb 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/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 6b608add853..c31c13cc72d 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 61106c7b296..38cd9840a17 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/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/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 4fffefdc6b6..fefcf83f8bb 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; @@ -861,12 +862,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); @@ -2243,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; @@ -2337,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; @@ -2769,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); } @@ -2812,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++]); } @@ -4232,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); @@ -6771,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()) { @@ -6783,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 f348a207988..fba23cfd5ae 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, @@ -471,8 +472,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(); @@ -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 1d509f0e808..a0491f9f7c3 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,17 +32,14 @@ #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" #include "server/network/protocol/protocolstatus.hpp" +#include "map/spectators.hpp" #include "kv/kv.hpp" @@ -877,10 +872,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 +898,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 +913,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++]); } } @@ -2952,7 +2946,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 +2960,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 +2983,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 +3015,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; } @@ -3066,17 +3060,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 +3742,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 +5477,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 +5491,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 +5554,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 +5579,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 +5599,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 +5625,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 +5710,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 +5719,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 +5731,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 +5752,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 +5784,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 +5794,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 +5815,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 +6136,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 +6306,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 +6359,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 +6438,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 +6446,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 +6507,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 +6516,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 +6620,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 +6633,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt attackerPlayer, targetPlayer, message, - spectators, + spectators.data(), realDamage ); @@ -6778,7 +6668,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 +6687,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 +6802,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 +6960,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 +7057,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 +7106,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 +7329,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 +7340,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 +7372,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 +7489,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); } } @@ -7929,6 +7766,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 +7955,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); } @@ -8101,26 +8025,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); } } @@ -9019,14 +8943,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); } } @@ -9376,12 +9294,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 afd5376a6b9..cfefc28a2b5 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; @@ -44,6 +45,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(); @@ -216,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); @@ -428,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); @@ -450,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; @@ -752,6 +766,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; @@ -835,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; @@ -874,6 +891,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; 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 a000c45d524..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; @@ -35,16 +34,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 +272,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); } } 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; 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/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/item.cpp b/src/items/item.cpp index 9d185eacfbc..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" @@ -281,7 +280,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/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 7caa32e3e40..c0e2a8a8e2b 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -19,8 +19,8 @@ #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" auto real_nullptr_tile = std::make_shared(0xFFFF, 0xFFFF, 0xFF); const std::shared_ptr &Tile::nullptr_tile = real_nullptr_tile; @@ -368,18 +368,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); } @@ -453,23 +452,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()) { @@ -552,7 +550,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 @@ -959,7 +957,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()); std::scoped_lock l(creaturesMutex); @@ -1159,8 +1157,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(); std::scoped_lock l(creaturesMutex); creatures->erase(it); } @@ -1182,9 +1179,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; } @@ -1201,17 +1197,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()) { @@ -1226,18 +1221,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); } } } @@ -1459,9 +1453,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); } @@ -1502,14 +1494,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); } @@ -1557,8 +1548,8 @@ void Tile::internalAddThing(uint32_t, std::shared_ptr thing) { std::shared_ptr creature = thing->getCreature(); if (creature) { - g_game().map.clearSpectatorCache(); - + Spectators::clearCache(); + std::scoped_lock l(creaturesMutex); CreatureVector* creatures = makeCreatures(); creatures->insert(creatures->begin(), creature); diff --git a/src/items/tile.hpp b/src/items/tile.hpp index 50b79c40ea7..a88ba148452 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: @@ -258,8 +257,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(const std::shared_ptr &item); void resetTileFlags(const std::shared_ptr &item); 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/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..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(); @@ -73,6 +72,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..a4e899adcb0 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(); } @@ -173,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 ); 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/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/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 } diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index c10d6988295..ec59b8b1c34 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -23,10 +23,10 @@ #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" +#include "map/spectators.hpp" // Game int GameFunctions::luaGameCreateMonsterType(lua_State* L) { @@ -69,8 +69,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 +317,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 +325,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 +373,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 +390,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 +398,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 +410,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 +465,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/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/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/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/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/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); }; 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 a69891a9369..7145525c3bd 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() @@ -2074,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; @@ -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)); @@ -4089,7 +4088,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/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/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/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/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/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 1927eb0c6e5..142915fc451 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -19,6 +19,7 @@ #include "io/iomapserialize.hpp" #include #include "game/scheduling/dispatcher.hpp" +#include "map/spectators.hpp" void Map::load(const std::string &identifier, const Position &pos) { try { @@ -293,18 +294,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); } } @@ -339,18 +340,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); } @@ -359,158 +359,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 0b80c611f16..ba4721794f6 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 @@ -159,9 +153,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; @@ -170,9 +161,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..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; @@ -71,7 +70,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/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/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; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index c6a9ac45395..59c2867e519 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()); } @@ -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); } 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/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()); + }; }; 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 @@ +