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);