From 1945d7567a1c5c445fb2473c5507b07172659c73 Mon Sep 17 00:00:00 2001 From: sruon Date: Sun, 19 Apr 2026 02:48:08 -0600 Subject: [PATCH 1/3] Crit fail synthesis when taking damage --- src/map/entities/battleentity.cpp | 13 ++++++++++++- src/map/utils/charutils.cpp | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/map/entities/battleentity.cpp b/src/map/entities/battleentity.cpp index 46f7453659d..6b1177f70c0 100644 --- a/src/map/entities/battleentity.cpp +++ b/src/map/entities/battleentity.cpp @@ -40,6 +40,7 @@ #include "ai/states/weaponskill_state.h" #include "attack.h" #include "attackround.h" +#include "entities/charentity.h" #include "items/item_weapon.h" #include "job_points.h" #include "lua/luautils.h" @@ -51,6 +52,7 @@ #include "status_effect_container.h" #include "trustentity.h" #include "utils/battleutils.h" +#include "utils/charutils.h" #include "utils/messageutils.h" #include "utils/mobutils.h" #include "utils/petutils.h" @@ -940,9 +942,18 @@ int32 CBattleEntity::takeDamage(int32 amount, CBattleEntity* attacker /* = nullp // RoE Damage Taken Trigger if (this->objtype == TYPE_PC) { + auto* PChar = static_cast(this); + if (amount > 0) { - roeutils::event(ROE_EVENT::ROE_DMGTAKEN, static_cast(this), RoeDatagram("dmg", amount)); + roeutils::event(ROE_EVENT::ROE_DMGTAKEN, PChar, RoeDatagram("dmg", amount)); + + // Taking 1~8 damage force fails the current synthesis. + // Threshold varies with unknown parameters. + if (PChar->isCrafting()) + { + charutils::forceSynthCritFail("CBattleEntity::takeDamage", PChar); + } } } else if (attacker && attacker->objtype == TYPE_PC) diff --git a/src/map/utils/charutils.cpp b/src/map/utils/charutils.cpp index 092ebc5c1ea..ffeebb6b7c9 100644 --- a/src/map/utils/charutils.cpp +++ b/src/map/utils/charutils.cpp @@ -7991,7 +7991,7 @@ void forceSynthCritFail(const std::string& sourceFunction, CCharEntity* PChar) // The broken rod can never be lost in a normal failed synth. It will only be lost if the synth is // interrupted in some way, such as by being attacked or moving to another area (e.g. ship docking). - ShowWarning("%s: %s attempting to zone in the middle of a synth, failing their synth!", sourceFunction, PChar->getName()); + ShowWarning("%s: Force crit-failing %s synthesis!", sourceFunction, PChar->getName()); synthutils::doSynthCriticalFail(PChar); PChar->CraftContainer->Clean(); // Clean to reset m_ItemCount to 0 From 339b47616e7573f52a01f345812b37f59359591f Mon Sep 17 00:00:00 2001 From: sruon Date: Mon, 4 May 2026 02:20:20 -0600 Subject: [PATCH 2/3] Streamline ATTACK/ON_ATTACK effects removal handling --- .../tests/systems/effects/attack_flags.lua | 206 ++++++++++++++++++ src/map/ai/states/item_state.cpp | 1 + src/map/entities/battleentity.cpp | 70 ++++-- src/map/entities/battleentity.h | 1 + src/map/entities/charentity.cpp | 3 + src/map/entities/petentity.cpp | 9 +- src/map/entities/trustentity.cpp | 3 + src/map/utils/battleutils.cpp | 10 - 8 files changed, 268 insertions(+), 35 deletions(-) create mode 100644 scripts/tests/systems/effects/attack_flags.lua diff --git a/scripts/tests/systems/effects/attack_flags.lua b/scripts/tests/systems/effects/attack_flags.lua new file mode 100644 index 00000000000..f6a3e9645ec --- /dev/null +++ b/scripts/tests/systems/effects/attack_flags.lua @@ -0,0 +1,206 @@ +describe('Status Effect Flags', function() + -- ATTACK flag: drops on melee/WS/mobskill/petskill emit + describe('ATTACK flag (Boost)', function() + ---@type CClientEntityPair + local player + + ---@type CTestEntity + local mob + + before_each(function() + player = xi.test.world:spawnPlayer( + { + zone = xi.zone.WEST_RONFAURE, + job = xi.job.MNK, + level = 99, + }) + player:setUnkillable(true) + player:setMod(xi.mod.ACC, 1000) + + mob = player.entities:moveTo('Wild_Rabbit') + mob:respawn() + mob.assert:isAlive() + end) + + it('drops on melee attack', function() + player:addStatusEffect(xi.effect.BOOST, { power = 25, duration = 180, origin = player }) + player.actions:engage(mob) + for i = 1, 5 do + xi.test.world:tickEntity(player) + xi.test.world:skipTime(5) + end + player.assert.no:hasEffect(xi.effect.BOOST) + end) + + it('does not drop on ranged attack', function() + player:changeJob(xi.job.RNG) + player:setLevel(99) + player:setMod(xi.mod.RACC, 1000) + player:addItem(xi.item.POWER_BOW) + player:addItem(xi.item.WOODEN_ARROW, 99) + player:equipItem(xi.item.POWER_BOW) + player:equipItem(xi.item.WOODEN_ARROW) + player:addStatusEffect(xi.effect.BOOST, { power = 25, duration = 180, origin = player }) + player.actions:engage(mob) + player.actions:rangedAttack(mob) + xi.test.world:skipTime(10) + player.assert:hasEffect(xi.effect.BOOST) + end) + + it('drops on weapon skill', function() + stub('xi.combat.physicalHitRate.getPhysicalHitRate', 1) + player:changeJob(xi.job.DNC) + player:setLevel(99) + player:addItem(xi.item.TERPSICHORE_99) + player:equipItem(xi.item.TERPSICHORE_99, nil, xi.slot.MAIN) + player:setTP(3000) + player:addStatusEffect(xi.effect.BOOST, { power = 25, duration = 180, origin = player }) + player.actions:engage(mob) + player.actions:useWeaponskill(mob, xi.weaponskill.PYRRHIC_KLEOS) + xi.test.world:skipTime(2) + player.assert.no:hasEffect(xi.effect.BOOST) + end) + + it('does not drop on JA on mob (Provoke)', function() + player:changeJob(xi.job.WAR) + player:setLevel(99) + player:addStatusEffect(xi.effect.BOOST, { power = 25, duration = 180, origin = player }) + player.actions:useAbility(mob, xi.jobAbility.PROVOKE) + xi.test.world:tickEntity(player) + player.assert:hasEffect(xi.effect.BOOST) + end) + + it('does not drop when casting spell on mob', function() + player:changeJob(xi.job.BLM) + player:setLevel(99) + player:addSpell(xi.magic.spell.STONE) + player:addStatusEffect(xi.effect.BOOST, { power = 25, duration = 180, origin = player }) + player.actions:useSpell(mob, xi.magic.spell.STONE) + xi.test.world:tickEntity(player) + xi.test.world:skipTime(10) + player.assert:hasEffect(xi.effect.BOOST) + end) + end) + + -- ON_ATTACK flag: drops on hostile action emit and receive + describe('ON_ATTACK flag (Mazurka)', function() + ---@type CClientEntityPair + local player + + ---@type CTestEntity + local mob + + before_each(function() + player = xi.test.world:spawnPlayer( + { + zone = xi.zone.WEST_RONFAURE, + job = xi.job.BRD, + level = 99, + }) + player:setUnkillable(true) + player:setMod(xi.mod.ACC, 1000) + + mob = player.entities:moveTo('Wild_Rabbit') + mob:respawn() + mob.assert:isAlive() + end) + + describe('emitting hostile actions', function() + it('drops on melee attack', function() + player:changeJob(xi.job.MNK) + player:setLevel(99) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + player.actions:engage(mob) + for i = 1, 5 do + xi.test.world:tickEntity(player) + xi.test.world:skipTime(5) + end + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + + it('drops on ranged attack', function() + player:changeJob(xi.job.RNG) + player:setLevel(99) + player:setMod(xi.mod.RACC, 1000) + player:addItem(xi.item.POWER_BOW) + player:addItem(xi.item.WOODEN_ARROW, 99) + player:equipItem(xi.item.POWER_BOW) + player:equipItem(xi.item.WOODEN_ARROW) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + player.actions:engage(mob) + player.actions:rangedAttack(mob) + xi.test.world:skipTime(10) + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + + it('drops on weapon skill', function() + stub('xi.combat.physicalHitRate.getPhysicalHitRate', 1) + player:changeJob(xi.job.DNC) + player:setLevel(99) + player:addItem(xi.item.TERPSICHORE_99) + player:equipItem(xi.item.TERPSICHORE_99, nil, xi.slot.MAIN) + player:setTP(3000) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + player.actions:engage(mob) + player.actions:useWeaponskill(mob, xi.weaponskill.PYRRHIC_KLEOS) + xi.test.world:skipTime(2) + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + + it('drops on JA on mob (Provoke)', function() + player:changeJob(xi.job.WAR) + player:setLevel(99) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + player.actions:useAbility(mob, xi.jobAbility.PROVOKE) + xi.test.world:tickEntity(player) + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + + it('drops when casting spell on mob', function() + player:changeJob(xi.job.BLM) + player:setLevel(99) + player:addSpell(xi.magic.spell.STONE) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + player.actions:useSpell(mob, xi.magic.spell.STONE) + xi.test.world:tickEntity(player) + xi.test.world:skipTime(10) + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + end) + + describe('receiving hostile actions', function() + it('drops on mob auto-attack', function() + stub('xi.combat.physicalHitRate.getPhysicalHitRate', 1) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + mob:updateEnmity(player) + for i = 1, 10 do + xi.test.world:tickEntity(mob) + xi.test.world:skipTime(5) + end + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + + it('drops when mob casts spell on player', function() + player:gotoZone(xi.zone.BEHEMOTHS_DOMINION) + local kb = player.entities:moveTo('King_Behemoth') + kb:spawn() + kb.assert:isAlive() + kb:updateClaim(player) + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + kb:castSpell(xi.magic.spell.METEOR, player) + xi.test.world:tickEntity(kb) + xi.test.world:skipTime(10) + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + + it('drops when mob uses mobskill on player', function() + player:gotoZone(xi.zone.BEAUCEDINE_GLACIER_S) + local ruszor = player.entities:moveTo('Ruszor') + player:addStatusEffect(xi.effect.MAZURKA, { power = 1, duration = 240, origin = player }) + ruszor:useMobAbility(xi.mobSkill.AQUA_BLAST, player, 0) + xi.test.world:skipTime(5) + player.assert.no:hasEffect(xi.effect.MAZURKA) + end) + end) + end) +end) diff --git a/src/map/ai/states/item_state.cpp b/src/map/ai/states/item_state.cpp index 07d9154e9ee..2aff99f3e4d 100644 --- a/src/map/ai/states/item_state.cpp +++ b/src/map/ai/states/item_state.cpp @@ -213,6 +213,7 @@ auto CItemState::Update(const timer::time_point tick) -> bool // Only send packet if action was populated (e.g. interrupts return early) if (!action.targets.empty()) { + m_PEntity->processActionEffectFlags(action); m_PEntity->loc.zone->PushPacket(m_PEntity, CHAR_INRANGE_SELF, std::make_unique(action)); } } diff --git a/src/map/entities/battleentity.cpp b/src/map/entities/battleentity.cpp index 6b1177f70c0..2fa1e349b88 100644 --- a/src/map/entities/battleentity.cpp +++ b/src/map/entities/battleentity.cpp @@ -2234,6 +2234,50 @@ void CBattleEntity::Die() SetBattleTargetID(0); } +void CBattleEntity::processActionEffectFlags(const action_t& action) const +{ + bool emittedHostile = false; + bool isMainTarget = true; + for (auto& target : action.targets) + { + auto* PTarget = dynamic_cast(zoneutils::GetEntity(target.actorId)); + if (!PTarget && loc.zone) + { + PTarget = loc.zone->GetCharByID(target.actorId); + } + + if (PTarget && this->allegiance != PTarget->allegiance) + { + emittedHostile = true; + if (isMainTarget) + { + // Main hostile target loses DETECTABLE + PTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); + } + + // Every hostile target loses ON_ATTACK + PTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ON_ATTACK); + } + + isMainTarget = false; + } + + if (emittedHostile) + { + // Hostile emit drops actor's ON_ATTACK + this->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ON_ATTACK); + + // ATTACK drops on physical hostile actions: melee/WS confirmed; mobskill/petskill unverified + if (action.actiontype == ActionCategory::BasicAttack || + action.actiontype == ActionCategory::SkillFinish || + action.actiontype == ActionCategory::MobSkillFinish || + action.actiontype == ActionCategory::PetSkillFinish) + { + this->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ATTACK); + } + } +} + void CBattleEntity::OnDeathTimer() { TracyZoneScoped; @@ -2486,13 +2530,7 @@ void CBattleEntity::OnCastFinished(CMagicState& state, action_t& action) } } - // TODO: Pixies will probably break here, once they're added. - if (this->allegiance != PActionTarget->allegiance) - { - // Should not be removed by AoE effects that don't target the player or - // buffs cast by other players or mobs. - PActionTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); - } + this->processActionEffectFlags(action); StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_MAGIC_END); @@ -2615,6 +2653,7 @@ void CBattleEntity::OnAbility(CAbilityState& state, action_t& action) PRecastContainer->Add(RECAST_ABILITY, static_cast(action.actionid), action.recast); } + this->processActionEffectFlags(action); } void CBattleEntity::OnWeaponSkillFinished(CWeaponSkillState& state, action_t& action) @@ -2885,11 +2924,6 @@ void CBattleEntity::OnMobSkillFinished(CMobSkillState& state, action_t& action) first = false; } - if (PSkill->getValidTargets() & TARGET_ENEMY) - { - PTargetFound->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); - } - if (PTargetFound->isDead()) { battleutils::ClaimMob(PTargetFound, this); @@ -2938,6 +2972,8 @@ void CBattleEntity::OnMobSkillFinished(CMobSkillState& state, action_t& action) } battleutils::DirtyExp(PTarget, this); } + + this->processActionEffectFlags(action); } bool CBattleEntity::CanAttack(CBattleEntity* PTarget, std::unique_ptr& errMsg) @@ -3094,13 +3130,6 @@ bool CBattleEntity::OnAttack(CAttackState& state, action_t& action) TracyZoneScoped; auto* PTarget = static_cast(state.GetTarget()); - if (PTarget->objtype == TYPE_PC) - { - // TODO: Should not be removed by AoE effects that don't target the player. - PTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); - PTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ON_ATTACK); - } - battleutils::ClaimMob(PTarget, this); // Mobs get claimed whether or not your attack actually is intimidated/paralyzed PTarget->LastAttacked = timer::now(); @@ -3447,7 +3476,8 @@ bool CBattleEntity::OnAttack(CAttackState& state, action_t& action) // End of attack loop ///////////////////////////////////////////////////////////////////////////////////////////// - this->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ATTACK | EFFECTFLAG_DETECTABLE); + this->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); + this->processActionEffectFlags(action); return true; } diff --git a/src/map/entities/battleentity.h b/src/map/entities/battleentity.h index 6c0debd8704..e55b9c1dbd2 100644 --- a/src/map/entities/battleentity.h +++ b/src/map/entities/battleentity.h @@ -511,6 +511,7 @@ class CBattleEntity : public CBaseEntity virtual void OnAbility(CAbilityState&, action_t&); virtual void OnRangedAttack(CRangeState&, action_t&); + void processActionEffectFlags(const action_t& action) const; // Drops status effects whose flags are tied to action emit/receive. virtual void OnDeathTimer(); virtual void OnRaise() { diff --git a/src/map/entities/charentity.cpp b/src/map/entities/charentity.cpp index ed9cd89000e..05976be99d2 100644 --- a/src/map/entities/charentity.cpp +++ b/src/map/entities/charentity.cpp @@ -1734,6 +1734,7 @@ void CCharEntity::OnWeaponSkillFinished(CWeaponSkillState& state, action_t& acti } PLatentEffectContainer->CheckLatentsWS(false); + this->processActionEffectFlags(action); } void CCharEntity::OnAbility(CAbilityState& state, action_t& action) @@ -2029,6 +2030,7 @@ void CCharEntity::OnAbility(CAbilityState& state, action_t& action) StatusEffectContainer->DelStatusEffect(PAbility->getPostActionEffectCleanup()); charutils::ApplyAbilityRecast(this, PAbility, charge, baseChargeTime, action.recast); + this->processActionEffectFlags(action); } else if (errMsg) { @@ -2392,6 +2394,7 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action) // Camouflage not up, so remove all detectable status effects StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); } + this->processActionEffectFlags(action); } bool CCharEntity::IsMobOwner(CBattleEntity* PBattleTarget) diff --git a/src/map/entities/petentity.cpp b/src/map/entities/petentity.cpp index 3a0c3ca0d8d..13b9c9bec9d 100644 --- a/src/map/entities/petentity.cpp +++ b/src/map/entities/petentity.cpp @@ -351,6 +351,8 @@ void CPetEntity::OnAbility(CAbilityState& state, action_t& action) { ActionInterrupts::AbilityInterrupt(this); } + + this->processActionEffectFlags(action); } bool CPetEntity::ValidTarget(CBattleEntity* PInitiator, uint16 targetFlags) @@ -591,11 +593,6 @@ void CPetEntity::OnPetSkillFinished(CPetSkillState& state, action_t& action) } } - if (PSkill->getValidTargets() & TARGET_ENEMY) - { - PTargetFound->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); - } - if (PTargetFound->isDead()) { battleutils::ClaimMob(PTargetFound, this); @@ -620,4 +617,6 @@ void CPetEntity::OnPetSkillFinished(CPetSkillState& state, action_t& action) } battleutils::DirtyExp(PTarget, this); } + + this->processActionEffectFlags(action); } diff --git a/src/map/entities/trustentity.cpp b/src/map/entities/trustentity.cpp index 6c75345a6e9..e089bb43cfa 100644 --- a/src/map/entities/trustentity.cpp +++ b/src/map/entities/trustentity.cpp @@ -314,6 +314,7 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action) // battleutils::RemoveAmmo(this, ammoConsumed); // only remove detectables StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DETECTABLE); + this->processActionEffectFlags(action); } bool CTrustEntity::ValidTarget(CBattleEntity* PInitiator, uint16 targetFlags) @@ -467,4 +468,6 @@ void CTrustEntity::OnWeaponSkillFinished(CWeaponSkillState& state, action_t& act { ActionInterrupts::WeaponSkillOutOfRange(this, PBattleTarget); } + + this->processActionEffectFlags(action); } diff --git a/src/map/utils/battleutils.cpp b/src/map/utils/battleutils.cpp index d5a63111823..a47a79bf7bd 100644 --- a/src/map/utils/battleutils.cpp +++ b/src/map/utils/battleutils.cpp @@ -2293,11 +2293,6 @@ int32 TakePhysicalDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, PHY ((CMobEntity*)PDefender)->PEnmityContainer->UpdateEnmityFromDamage(PAttacker, 0); } - if (PAttacker->objtype == TYPE_PC && !isRanged && !isCounter) - { - PAttacker->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ATTACK); - } - return damage; } @@ -2425,11 +2420,6 @@ int32 TakeWeaponskillDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, ((CMobEntity*)PDefender)->PEnmityContainer->UpdateEnmityFromDamage(PAttacker, 0); } - if (!isRanged) - { - PAttacker->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ATTACK); - } - // Apply TP PAttacker->addTP(std::max((PAttacker->getMod(Mod::SAVETP)), standbyTp)); From 362730de642528e92775626520f2f031f5090672 Mon Sep 17 00:00:00 2001 From: sruon Date: Mon, 4 May 2026 01:54:02 -0600 Subject: [PATCH 3/3] Cancel fishing on hostile action received --- scripts/tests/systems/effects/attack_flags.lua | 3 +++ src/map/entities/battleentity.cpp | 7 +++++++ src/map/utils/charutils.cpp | 3 +++ 3 files changed, 13 insertions(+) diff --git a/scripts/tests/systems/effects/attack_flags.lua b/scripts/tests/systems/effects/attack_flags.lua index f6a3e9645ec..f1e265d4e07 100644 --- a/scripts/tests/systems/effects/attack_flags.lua +++ b/scripts/tests/systems/effects/attack_flags.lua @@ -29,6 +29,7 @@ describe('Status Effect Flags', function() xi.test.world:tickEntity(player) xi.test.world:skipTime(5) end + player.assert.no:hasEffect(xi.effect.BOOST) end) @@ -115,6 +116,7 @@ describe('Status Effect Flags', function() xi.test.world:tickEntity(player) xi.test.world:skipTime(5) end + player.assert.no:hasEffect(xi.effect.MAZURKA) end) @@ -177,6 +179,7 @@ describe('Status Effect Flags', function() xi.test.world:tickEntity(mob) xi.test.world:skipTime(5) end + player.assert.no:hasEffect(xi.effect.MAZURKA) end) diff --git a/src/map/entities/battleentity.cpp b/src/map/entities/battleentity.cpp index 2fa1e349b88..a17838aff6c 100644 --- a/src/map/entities/battleentity.cpp +++ b/src/map/entities/battleentity.cpp @@ -53,6 +53,7 @@ #include "trustentity.h" #include "utils/battleutils.h" #include "utils/charutils.h" +#include "utils/fishingutils.h" #include "utils/messageutils.h" #include "utils/mobutils.h" #include "utils/petutils.h" @@ -2257,6 +2258,12 @@ void CBattleEntity::processActionEffectFlags(const action_t& action) const // Every hostile target loses ON_ATTACK PTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_ON_ATTACK); + + // Hostile action cancels fishing on PC targets + if (auto* PChar = dynamic_cast(PTarget); PChar && PChar->isFishing()) + { + fishingutils::InterruptFishing(PChar); + } } isMainTarget = false; diff --git a/src/map/utils/charutils.cpp b/src/map/utils/charutils.cpp index ffeebb6b7c9..0059a59f315 100644 --- a/src/map/utils/charutils.cpp +++ b/src/map/utils/charutils.cpp @@ -7995,6 +7995,9 @@ void forceSynthCritFail(const std::string& sourceFunction, CCharEntity* PChar) synthutils::doSynthCriticalFail(PChar); PChar->CraftContainer->Clean(); // Clean to reset m_ItemCount to 0 + PChar->animation = ANIMATION_NONE; + PChar->updatemask |= UPDATE_HP; + PChar->pushPacket(PChar); } void removeCharFromZone(CCharEntity* PChar)