From 690fe1559541fc1ad80a1c9becccb56c363ed290 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Tue, 5 Dec 2023 09:38:25 -0800 Subject: [PATCH 1/8] fix: event indexing race in encounter (#1963) This event cleaning code was fundamentally incorrect: the index changes when we remove something, which makes the next event not properly remove itself. This caused `cancelEvents` to not behave properly and, in the case of the Magma Bubble fight, for example, the mechanic sometimes didn't reset, or worse, reset itself repeatedly. --- .../quests/primal_ordeal_quest/magma_bubble_fight.lua | 2 +- data/libs/encounters_lib.lua | 11 +++++------ data/libs/functions/set.lua | 3 +++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua b/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua index 7f05611cbc0..c2642008a09 100644 --- a/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua +++ b/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua @@ -35,7 +35,7 @@ local encounter = Encounter("Magma Bubble", { function encounter:onReset(position) encounter:removeMonsters() bossZone:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("The Magma Bubble has been defeated. You have %i seconds to leave the room.", config.timeToLeftAfterKill)) - addEvent(function(zn) + self:addEvent(function(zn) zn:refresh() zn:removePlayers() end, config.timeToLeftAfterKill * 1000, bossZone) diff --git a/data/libs/encounters_lib.lua b/data/libs/encounters_lib.lua index dee7a38c0c1..6258f399580 100644 --- a/data/libs/encounters_lib.lua +++ b/data/libs/encounters_lib.lua @@ -83,26 +83,25 @@ function Encounter:resetConfig(config) self.registered = false self.global = config.global or false self.timeToSpawnMonsters = ParseDuration(config.timeToSpawnMonsters or "3s") - self.events = {} + self.events = Set() end ---@param callable function The callable function for the event ---@param delay number The delay time for the event function Encounter:addEvent(callable, delay, ...) - local index = #self.events + 1 local event = addEvent(function(callable, ...) pcall(callable, ...) - table.remove(self.events, index) + self.events:remove(event) end, ParseDuration(delay), callable, ...) - table.insert(self.events, index, event) + self.events:insert(event) end ---Cancels all the events associated with the encounter function Encounter:cancelEvents() - for _, event in ipairs(self.events) do + for event in self.events:iter() do stopEvent(event) end - self.events = {} + self.events = Set() end ---Returns the stage of the encounter by the given stage number diff --git a/data/libs/functions/set.lua b/data/libs/functions/set.lua index e3e6b6f921d..35065a7334b 100644 --- a/data/libs/functions/set.lua +++ b/data/libs/functions/set.lua @@ -52,6 +52,9 @@ function Set:insert(key) end function Set:remove(key) + if not self:contains(key) then + return + end key = self:__key(key) self.values[key] = nil end From 1981dacbbacfd35061c35983179315a5208ca6d0 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Wed, 6 Dec 2023 03:16:26 -0800 Subject: [PATCH 2/8] fix: sell items while wearing an imbued version (#1965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client filters equipped items locally, so we don't have control over what it's doing, and on retail tibia you can sell imbued items to NPCs, so everything should count as far as the client is concerned. Here's what this PR does, to fix selling items while having one equipped, but also preventing accidental sale of imbued equipment: • We'll send the count of all items to the client, imbued or not. • Client will attempt to sell all the non-equipped ones (assuming "sell equipped" is unchecked). • Server will then sell only the non-imbued ones. This might cause a weird behavior, where you think you can sell 2 crown helmets, but you can only sell 1 (because one of them is imbued), but it's better than the alternative where the user will just simply think they can't sell the thing at all. --- src/creatures/players/player.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 0c68ec3a659..1fe7a0b1ca2 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -3942,9 +3942,7 @@ std::map &Player::getAllItemTypeCount(std::map &Player::getAllSaleItemIdAndCount(std::map &countMap) const { for (const auto item : getAllInventoryItems(false, true)) { - if (!item->hasImbuements()) { - countMap[item->getID()] += item->getItemCount(); - } + countMap[item->getID()] += item->getItemCount(); } return countMap; From 779b479706863a7a786bc49c50503c1d69e58fa4 Mon Sep 17 00:00:00 2001 From: Pedro Cruz Date: Wed, 6 Dec 2023 08:22:27 -0300 Subject: [PATCH 3/8] feat: add sound for open and close doors (#1978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added open sound to custom/normal doors • Added open sound to key doors • Added open sound to level doors • Added open sound to quest doors --- data-otservbr-global/scripts/actions/door/custom_door.lua | 2 ++ data-otservbr-global/scripts/actions/door/key_door.lua | 6 ++++++ data-otservbr-global/scripts/actions/door/level_door.lua | 1 + data-otservbr-global/scripts/actions/door/quest_door.lua | 1 + .../scripts/movements/others/closing_door.lua | 2 ++ 5 files changed, 12 insertions(+) diff --git a/data-otservbr-global/scripts/actions/door/custom_door.lua b/data-otservbr-global/scripts/actions/door/custom_door.lua index 50e27feaf46..2dadeb68488 100644 --- a/data-otservbr-global/scripts/actions/door/custom_door.lua +++ b/data-otservbr-global/scripts/actions/door/custom_door.lua @@ -18,12 +18,14 @@ function customDoor.onUse(player, item, fromPosition, target, toPosition, isHotk for index, value in ipairs(CustomDoorTable) do if value.closedDoor == item.itemid then item:transform(value.openDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_OPEN_DOOR) return true end end for index, value in ipairs(CustomDoorTable) do if value.openDoor == item.itemid then item:transform(value.closedDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_CLOSE_DOOR) return true end end diff --git a/data-otservbr-global/scripts/actions/door/key_door.lua b/data-otservbr-global/scripts/actions/door/key_door.lua index 15a38a884d1..4f748b6b635 100644 --- a/data-otservbr-global/scripts/actions/door/key_door.lua +++ b/data-otservbr-global/scripts/actions/door/key_door.lua @@ -37,6 +37,7 @@ function keyDoor.onUse(player, item, fromPosition, target, toPosition, isHotkey) for index, value in ipairs(KeyDoorTable) do if value.closedDoor == item.itemid then item:transform(value.openDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_OPEN_DOOR) return true end end @@ -46,6 +47,7 @@ function keyDoor.onUse(player, item, fromPosition, target, toPosition, isHotkey) return false end item:transform(value.closedDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_CLOSE_DOOR) return true end end @@ -60,8 +62,12 @@ function keyDoor.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.actionid == target.actionid then if value.lockedDoor == target.itemid then target:transform(value.openDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_OPEN_DOOR) return true elseif table.contains({ value.openDoor, value.closedDoor }, target.itemid) then + if value.openDoor == item.itemid then + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_CLOSE_DOOR) + end target:transform(value.lockedDoor) return true end diff --git a/data-otservbr-global/scripts/actions/door/level_door.lua b/data-otservbr-global/scripts/actions/door/level_door.lua index 177c0894c4b..6c90fca6df9 100644 --- a/data-otservbr-global/scripts/actions/door/level_door.lua +++ b/data-otservbr-global/scripts/actions/door/level_door.lua @@ -15,6 +15,7 @@ function levelDoor.onUse(player, item, fromPosition, target, toPosition, isHotke if value.closedDoor == item.itemid then if item.actionid > 0 and player:getLevel() >= item.actionid - 1000 then item:transform(value.openDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_OPEN_DOOR) player:teleportTo(toPosition, true) return true else diff --git a/data-otservbr-global/scripts/actions/door/quest_door.lua b/data-otservbr-global/scripts/actions/door/quest_door.lua index 438a23a5591..16dffc37919 100644 --- a/data-otservbr-global/scripts/actions/door/quest_door.lua +++ b/data-otservbr-global/scripts/actions/door/quest_door.lua @@ -15,6 +15,7 @@ function questDoor.onUse(player, item, fromPosition, target, toPosition, isHotke if value.closedDoor == item.itemid then if item.actionid > 0 and player:getStorageValue(item.actionid) ~= -1 then item:transform(value.openDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_OPEN_DOOR) player:teleportTo(toPosition, true) return true else diff --git a/data-otservbr-global/scripts/movements/others/closing_door.lua b/data-otservbr-global/scripts/movements/others/closing_door.lua index 750d6defc9b..a4e286d0a71 100644 --- a/data-otservbr-global/scripts/movements/others/closing_door.lua +++ b/data-otservbr-global/scripts/movements/others/closing_door.lua @@ -103,11 +103,13 @@ function closingDoor.onStepOut(creature, item, position, fromPosition) for index, value in ipairs(LevelDoorTable) do if value.openDoor == item.itemid then item:transform(value.closedDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_CLOSE_DOOR) end end for index, value in ipairs(QuestDoorTable) do if value.openDoor == item.itemid then item:transform(value.closedDoor) + item:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ACTION_CLOSE_DOOR) end end return true From a21fe2bed1668a01bf82d1b8b091ee466f2c9830 Mon Sep 17 00:00:00 2001 From: un000000 <101636937+un000000@users.noreply.github.com> Date: Thu, 7 Dec 2023 01:41:27 +0100 Subject: [PATCH 4/8] fix: correctly reflect damage (#1941) Damaging enemy with type they reflect damages you according to reflect map when using any damage source. Fixes #1719 --- src/game/game.cpp | 103 ++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 53 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index 5dcba79adc1..893a9dcec8d 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5990,6 +5990,31 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack std::shared_ptr targetPlayer = target->getPlayer(); if (damage.primary.type != COMBAT_NONE) { + + damage.primary.value = -damage.primary.value; + // Damage healing primary + if (attacker) { + if (target->getMonster()) { + uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type); + if (primaryHealing > 0) { + damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.)); + canHeal = true; + } + } + if (targetPlayer && attacker->getAbsorbPercent(damage.primary.type) != 0) { + damageAbsorbMessage = true; + } + if (attacker->getPlayer() && attacker->getIncreasePercent(damage.primary.type) != 0) { + damageIncreaseMessage = true; + } + damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.; + } + damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.; + + primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); + + damage.primary.value = -damage.primary.value; + InternalGame::sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition(), attacker); // Damage reflection primary if (!damage.extension && attacker) { if (targetPlayer && attacker->getMonster() && damage.primary.type != COMBAT_HEALING) { @@ -6028,57 +6053,12 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack } } } - damage.primary.value = -damage.primary.value; - // Damage healing primary - if (attacker) { - if (target->getMonster()) { - uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type); - if (primaryHealing > 0) { - damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.)); - canHeal = true; - } - } - if (targetPlayer && attacker->getAbsorbPercent(damage.primary.type) != 0) { - damageAbsorbMessage = true; - } - if (attacker->getPlayer() && attacker->getIncreasePercent(damage.primary.type) != 0) { - damageIncreaseMessage = true; - } - damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.; - } - damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.; - - primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); - - damage.primary.value = -damage.primary.value; - InternalGame::sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition(), attacker); } else { primaryBlockType = BLOCK_NONE; } if (damage.secondary.type != COMBAT_NONE) { - // Damage reflection secondary - if (!damage.extension && attacker && target->getMonster()) { - uint32_t secondaryReflectPercent = target->getReflectPercent(damage.secondary.type, true); - uint32_t secondaryReflectFlat = target->getReflectFlat(damage.secondary.type, true); - if (secondaryReflectPercent > 0 || secondaryReflectFlat > 0) { - if (!canReflect) { - damageReflected.primary.type = damage.secondary.type; - damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); - if (!damageReflected.exString.empty()) { - damageReflected.exString += ", "; - } - damageReflected.extension = true; - damageReflected.exString += "damage reflection"; - damageReflectedParams.combatType = damage.primary.type; - damageReflectedParams.aggressive = true; - canReflect = true; - } else { - damageReflected.secondary.type = damage.secondary.type; - damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); - } - } - } + damage.secondary.value = -damage.secondary.value; // Damage healing secondary if (attacker && target->getMonster()) { @@ -6102,9 +6082,32 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack damage.secondary.value = -damage.secondary.value; InternalGame::sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition(), attacker); + + if (!damage.extension && attacker && target->getMonster()) { + int32_t secondaryReflectPercent = target->getReflectPercent(damage.secondary.type, true); + int32_t secondaryReflectFlat = target->getReflectFlat(damage.secondary.type, true); + if (secondaryReflectPercent > 0 || secondaryReflectFlat > 0) { + if (!canReflect) { + damageReflected.primary.type = damage.secondary.type; + damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); + if (!damageReflected.exString.empty()) { + damageReflected.exString += ", "; + } + damageReflected.extension = true; + damageReflected.exString += "damage reflection"; + damageReflectedParams.combatType = damage.primary.type; + damageReflectedParams.aggressive = true; + canReflect = true; + } else { + damageReflected.secondary.type = damage.secondary.type; + damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); + } + } + } } else { secondaryBlockType = BLOCK_NONE; } + // Damage reflection secondary if (damage.primary.type == COMBAT_HEALING) { damage.primary.value *= target->getBuff(BUFF_HEALINGRECEIVED) / 100.; @@ -7223,13 +7226,7 @@ bool Game::combatChangeMana(std::shared_ptr attacker, std::shared_ptr< } target->drainMana(attacker, manaLoss); - if (targetPlayer) { - std::string cause = "(other)"; - if (attacker) { - cause = attacker->getName(); - } - targetPlayer->updateInputAnalyzer(damage.primary.type, damage.primary.value * -1, cause); - } + std::stringstream ss; std::string damageString = std::to_string(manaLoss); From 3f75671782a714ef6f8ff1d0d896208a93cf66a6 Mon Sep 17 00:00:00 2001 From: Jonyrewind Date: Thu, 7 Dec 2023 01:50:53 +0100 Subject: [PATCH 5/8] fix: icicle resistances and dragon egg damage. (#1957) Fixes #1936 --- .../quests/forgotten_knowledge/icicle.lua | 22 +++++++++---------- .../scripts/spells/monster/icicle_heal.lua | 21 +++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/data-otservbr-global/monster/quests/forgotten_knowledge/icicle.lua b/data-otservbr-global/monster/quests/forgotten_knowledge/icicle.lua index 4f154635a47..7b795b7d3e8 100644 --- a/data-otservbr-global/monster/quests/forgotten_knowledge/icicle.lua +++ b/data-otservbr-global/monster/quests/forgotten_knowledge/icicle.lua @@ -70,20 +70,20 @@ monster.defenses = { defense = 199, armor = 199, mitigation = 0.50, - { name = "icicle heal", interval = 2000, chance = 25, target = false }, + { name = "icicle heal", interval = 2000, chance = 60, target = false }, } 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 }, + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, } monster.immunities = { diff --git a/data-otservbr-global/scripts/spells/monster/icicle_heal.lua b/data-otservbr-global/scripts/spells/monster/icicle_heal.lua index 65231af0a4b..f2bae458bd0 100644 --- a/data-otservbr-global/scripts/spells/monster/icicle_heal.lua +++ b/data-otservbr-global/scripts/spells/monster/icicle_heal.lua @@ -4,16 +4,17 @@ combat:setParameter(COMBAT_PARAM_AGGRESSIVE, 0) combat:setArea(createCombatArea(AREA_CIRCLE3X3)) function onTargetCreature(creature, target) - local min = 400 - local max = 600 - - local master = target:getMaster() - if target:isPlayer() and not master or master and master:isPlayer() then - return true + local spectators, spectator = Game.getSpectators(creature:getPosition(), false, false, 3, 3, 3, 3) + for i = 1, #spectators do + spectator = spectators[i] + if spectator:isMonster() and spectator:getName():lower() == "dragon egg" then + creature:setTarget("dragon egg") + if target:getName():lower() == "dragon egg" then + target:addHealth(-100) + return true + end + end end - - doTargetCombatHealth(0, target, COMBAT_HEALING, min, max, CONST_ME_NONE) - return true end combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") @@ -29,5 +30,5 @@ spell:words("###436") spell:isAggressive(true) spell:blockWalls(true) spell:needLearn(true) -spell:isSelfTarget(true) +spell:isSelfTarget(false) spell:register() From 0e1143f9184879ae7bfc1d642fb862d815238ee4 Mon Sep 17 00:00:00 2001 From: Pedro Cruz Date: Wed, 6 Dec 2023 22:54:30 -0300 Subject: [PATCH 6/8] fix: quivers not showing attributes (#1971) - Added showattributes property to alicorn and eldritch quivers in items.xml closes #1914 --- data/items/items.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/items/items.xml b/data/items/items.xml index 407f909b6e8..678a85f3d12 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -61565,6 +61565,7 @@ + @@ -65289,6 +65290,7 @@ + From 953742ffe6226493257030fa272254c50d536624 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Wed, 6 Dec 2023 23:42:29 -0300 Subject: [PATCH 7/8] fix: door description (#1980) --- data/items/items.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/items/items.xml b/data/items/items.xml index 678a85f3d12..67bb50a17ff 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -24931,13 +24931,13 @@ - + + - + - From 3838427fb427e9d8035fafb9871af41f5c6f7d7c Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 7 Dec 2023 09:38:44 -0300 Subject: [PATCH 8/8] fix: division by zero in spell cooldown calculation (#1988) Fixes potential division by zero error in the spell cooldown calculation. We have introduced a safety check to ensure rateCooldown is not zero or extremely close to zero before performing the division. In cases where rateCooldown is too small; it is reset to a minimal safe value to maintain functionality and avoid arithmetic exceptions. This change ensures stable and error-free operation even with small rateCooldown values. Resolves #1920 Resolves #1977 --- src/creatures/combat/spells.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index 4ae9d69ae6b..d8c71c2a93c 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -609,14 +609,19 @@ void Spell::setWheelOfDestinyBoost(WheelSpellBoost_t boost, WheelSpellGrade_t gr void Spell::applyCooldownConditions(std::shared_ptr player) const { WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName()); bool isUpgraded = getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0; - auto rate_cooldown = (int32_t)g_configManager().getFloat(RATE_SPELL_COOLDOWN, __FUNCTION__); + // Safety check to prevent division by zero + auto rateCooldown = g_configManager().getFloat(RATE_SPELL_COOLDOWN, __FUNCTION__); + if (std::abs(rateCooldown) < std::numeric_limits::epsilon()) { + rateCooldown = 0.1; // Safe minimum value + } + if (cooldown > 0) { int32_t spellCooldown = cooldown; if (isUpgraded) { spellCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::COOLDOWN, spellGrade); } if (spellCooldown > 0) { - std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rate_cooldown, 0, false, spellId); + std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rateCooldown, 0, false, spellId); player->addCondition(condition); } } @@ -627,7 +632,7 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const { spellGroupCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::GROUP_COOLDOWN, spellGrade); } if (spellGroupCooldown > 0) { - std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellGroupCooldown / rate_cooldown, 0, false, group); + std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellGroupCooldown / rateCooldown, 0, false, group); player->addCondition(condition); } } @@ -638,7 +643,7 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const { spellSecondaryGroupCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::SECONDARY_GROUP_COOLDOWN, spellGrade); } if (spellSecondaryGroupCooldown > 0) { - std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellSecondaryGroupCooldown / rate_cooldown, 0, false, secondaryGroup); + std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellSecondaryGroupCooldown / rateCooldown, 0, false, secondaryGroup); player->addCondition(condition); } }