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/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 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-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() diff --git a/data/items/items.xml b/data/items/items.xml index 407f909b6e8..67bb50a17ff 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -24931,13 +24931,13 @@ - + + - + - @@ -61565,6 +61565,7 @@ + @@ -65289,6 +65290,7 @@ + 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 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); } } 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; 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);