From 2776812b2e2a64b1366b40b15e8c98d4ec9e14db Mon Sep 17 00:00:00 2001 From: H0zen Date: Mon, 4 Apr 2016 11:13:13 +0300 Subject: [PATCH] Improved Combat Movement handling * Improved Combat Movement handling * Some fixes to previous commit * Another bunch of fixes combat movement --- src/game/Object/CreatureAI.cpp | 88 ++++++++++------- src/game/Object/CreatureAI.h | 39 ++++---- src/game/Object/CreatureEventAI.cpp | 142 +++++++++++++++++----------- src/game/Object/CreatureEventAI.h | 3 + src/game/Object/Unit.cpp | 42 ++++---- src/game/WorldHandlers/Spell.cpp | 6 +- 6 files changed, 188 insertions(+), 132 deletions(-) diff --git a/src/game/Object/CreatureAI.cpp b/src/game/Object/CreatureAI.cpp index 7e83821c..24194f59 100644 --- a/src/game/Object/CreatureAI.cpp +++ b/src/game/Object/CreatureAI.cpp @@ -32,6 +32,11 @@ static_assert(MAXIMAL_AI_EVENT_EVENTAI <= 32, "Maximal 32 AI_EVENTs supported with EventAI"); +CreatureAI::CreatureAI(Creature* creature) : m_creature(creature), m_combatMovement(CM_SCRIPT), + m_attackDistance(0.0f), m_attackAngle(0.0f) +{ +} + CreatureAI::~CreatureAI() { } @@ -52,7 +57,7 @@ CanCastResult CreatureAI::CanCastSpell(Unit* pTarget, const SpellEntry* pSpell, { return CAST_FAIL_STATE; } if (pSpell->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && m_creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED)) - { return CAST_FAIL_STATE; } + { return CAST_FAIL_SILENCED; } if (pSpell->PreventionType == SPELL_PREVENTION_TYPE_PACIFY && m_creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) { return CAST_FAIL_STATE; } @@ -94,7 +99,6 @@ CanCastResult CreatureAI::DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 if (uiCastFlags & CAST_FORCE_TARGET_SELF) { pCaster = pTarget; } - // Allowed to cast only if not casting (unless we interrupt ourself) or if spell is triggered if (!pCaster->IsNonMeleeSpellCasted(false) || (uiCastFlags & (CAST_TRIGGERED | CAST_INTERRUPT_PREVIOUS))) { if (const SpellEntry* pSpell = sSpellStore.LookupEntry(uiSpell)) @@ -112,24 +116,27 @@ CanCastResult CreatureAI::DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 CanCastResult castResult = CanCastSpell(pTarget, pSpell, uiCastFlags & CAST_TRIGGERED); if (castResult != CAST_OK) - { return castResult; } + { + return castResult; + } } - // Interrupt any previous spell - if (uiCastFlags & CAST_INTERRUPT_PREVIOUS && pCaster->IsNonMeleeSpellCasted(false)) - { pCaster->InterruptNonMeleeSpells(false); } + if ( (uiCastFlags & CAST_INTERRUPT_PREVIOUS) && pCaster->IsNonMeleeSpellCasted(false)) + pCaster->CastStop(); + + pCaster->StopMoving(); pCaster->CastSpell(pTarget, pSpell, uiCastFlags & CAST_TRIGGERED, NULL, NULL, uiOriginalCasterGUID); return CAST_OK; } else { - sLog.outErrorDb("DoCastSpellIfCan by creature entry %u attempt to cast spell %u but spell does not exist.", m_creature->GetEntry(), uiSpell); + sLog.outErrorDb("DoCastSpellIfCan: %s attempt to cast spell %u but spell does not exist.", + m_creature->GetGuidStr().c_str(), uiSpell); return CAST_FAIL_OTHER; } } - else - { return CAST_FAIL_IS_CASTING; } + return CAST_FAIL_IS_CASTING; } bool CreatureAI::DoMeleeAttackIfReady() @@ -137,46 +144,54 @@ bool CreatureAI::DoMeleeAttackIfReady() return m_creature->UpdateMeleeAttackingState(); } -void CreatureAI::AddCombatMovementFlags(uint32 cmFlags) -{ - if (!m_combatMovement) - { m_creature->clearUnitState(UNIT_STAT_NO_COMBAT_MOVEMENT); } - - m_combatMovement |= cmFlags; -} - -void CreatureAI::ClearCombatMovementFlags(uint32 cmFlags) -{ - m_combatMovement &= ~cmFlags; - - if (!m_combatMovement) - { m_creature->addUnitState(UNIT_STAT_NO_COMBAT_MOVEMENT); } -} - void CreatureAI::SetCombatMovement(bool enable, bool stopOrStartMovement /*=false*/) { - if (stopOrStartMovement && m_creature->getVictim()) // Only change current movement while in combat + SetCombatMovementFlag(CM_SCRIPT, enable); + + if (stopOrStartMovement) // Only change current movement while in combat + SetChase(enable); +} + +void CreatureAI::SetCombatMovementFlag(uint8 flag, bool setFlag) +{ + if (setFlag) + { + m_combatMovement |= flag; + if (m_combatMovement) + m_creature->clearUnitState(UNIT_STAT_NO_COMBAT_MOVEMENT); + } + else + { + m_combatMovement &= ~flag; + if (m_combatMovement == 0) + m_creature->addUnitState(UNIT_STAT_NO_COMBAT_MOVEMENT); + } +} + +void CreatureAI::SetChase(bool chase) +{ + if (IsCombatMovement() && m_creature->getVictim()) { MotionMaster* creatureMotion = m_creature->GetMotionMaster(); - creatureMotion->MovementExpired(false); - if (enable) + if (chase) { switch(creatureMotion->GetCurrentMovementGeneratorType()) { + case IDLE_MOTION_TYPE: case CHASE_MOTION_TYPE: case FOLLOW_MOTION_TYPE: creatureMotion->Clear(false); + creatureMotion->MoveChase(m_creature->getVictim(), 0.0f, 0.0f); break; default: break; } - creatureMotion->MoveChase(m_creature->getVictim(), m_attackDistance, m_creature->GetAngle(m_creature->getVictim())); } else if (creatureMotion->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) { - creatureMotion->Clear(false); m_creature->StopMoving(); - creatureMotion->MoveIdle(); + if (!m_creature->CanReachWithMeleeAttack(m_creature->getVictim())) + m_creature->SendMeleeAttackStop(m_creature->getVictim()); } } } @@ -184,15 +199,16 @@ void CreatureAI::SetCombatMovement(bool enable, bool stopOrStartMovement /*=fals void CreatureAI::HandleMovementOnAttackStart(Unit* victim) { MotionMaster* creatureMotion = m_creature->GetMotionMaster(); - creatureMotion->MovementExpired(false); - + MovementGeneratorType mmgen = creatureMotion->GetCurrentMovementGeneratorType(); + if (IsCombatMovement()) - { creatureMotion->MoveChase(victim, m_attackDistance, m_attackAngle); } + { creatureMotion->MoveChase(victim, m_attackDistance, m_attackAngle); } + // TODO - adapt this to only stop OOC-MMGens when MotionMaster rewrite is finished - else if (creatureMotion->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE || creatureMotion->GetCurrentMovementGeneratorType() == RANDOM_MOTION_TYPE) + else if (mmgen == WAYPOINT_MOTION_TYPE || mmgen == RANDOM_MOTION_TYPE) { - m_creature->StopMoving(); creatureMotion->MoveIdle(); + m_creature->StopMoving(); } } diff --git a/src/game/Object/CreatureAI.h b/src/game/Object/CreatureAI.h index f36bc85c..04a2a1d0 100644 --- a/src/game/Object/CreatureAI.h +++ b/src/game/Object/CreatureAI.h @@ -53,7 +53,8 @@ enum CanCastResult CAST_FAIL_POWER = 5, CAST_FAIL_STATE = 6, CAST_FAIL_TARGET_AURA = 7, - CAST_FAIL_NO_LOS = 8 + CAST_FAIL_NO_LOS = 8, + CAST_FAIL_SILENCED = 9 }; enum CastFlags @@ -68,10 +69,8 @@ enum CastFlags enum CombatMovementFlags { - COMBAT_MOVEMENT_SCRIPT = 0x01, // Combat movement enforced by script - COMBAT_MOVEMENT_LOS = 0x02, // Combat movement triggered by LoS issues - COMBAT_MOVEMENT_OOM = 0x04, // Combat movement triggered by power exhaustion - COMBAT_MOVEMENT_DISTANCE = 0x08 // Combat movement triggered by distance checks + CM_SCRIPT = 0x01, + CM_SPELL = 0x02, }; enum AIEventType @@ -109,13 +108,7 @@ enum AIEventType class CreatureAI { public: - explicit CreatureAI(Creature* creature) : - m_creature(creature), - m_combatMovement(0), - m_attackDistance(0.0f), - m_attackAngle(0.0f) - { AddCombatMovementFlags(COMBAT_MOVEMENT_SCRIPT); } - + explicit CreatureAI(Creature* creature); virtual ~CreatureAI(); ///== Information about AI ======================== @@ -232,6 +225,12 @@ class CreatureAI */ virtual void SpellHit(Unit* /*pCaster*/, const SpellEntry* /*pSpell*/) {} + /** + * Called when the current casted spell is processed + * @param pSpell The spell that is casted currently + * @param reason The spell state (see SpellCastResult enum) + */ + virtual void OnSpellCastChange(const SpellEntry* /*pSpell*/, SpellCastResult /*reason*/) {} /** * Called when spell hits creature's target * @param pTarget Target that we hit with the spell @@ -318,14 +317,14 @@ class CreatureAI * @param uiCastFlags Some flags to define how to cast, see enum CastFlags * @param OriginalCasterGuid the original caster of the spell if required, empty by default */ - CanCastResult DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags = 0, ObjectGuid OriginalCasterGuid = ObjectGuid()); + virtual CanCastResult DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags = 0, ObjectGuid OriginalCasterGuid = ObjectGuid()); /// Combat movement functions void SetCombatMovement(bool enable, bool stopOrStartMovement = false); bool IsCombatMovement() const { return m_combatMovement != 0; } - void AddCombatMovementFlags(uint32 cmFlags); - void ClearCombatMovementFlags(uint32 cmFlags); - + uint8 GetCombatMovementFlags() const { return m_combatMovement; } + void SetCombatMovementFlag(uint8 flag, bool setFlag = true); + ///== Event Handling =============================== /* @@ -354,21 +353,23 @@ class CreatureAI virtual void ReceiveAIEvent(AIEventType /*eventType*/, Creature* /*pSender*/, Unit* /*pInvoker*/, uint32 /*miscValue*/) {} // Reset should be defined here, as it is called from out the AI ctor now - virtual void Reset() {} + virtual void Reset() { m_combatMovement = CM_SCRIPT; } protected: void HandleMovementOnAttackStart(Unit* victim); + void SetChase(bool chase); ///== Fields ======================================= /// Pointer to the Creature controlled by this AI Creature* const m_creature; - /// Combat movement currently enabled - uint32 m_combatMovement; /// How should an enemy be chased float m_attackDistance; float m_attackAngle; + private: + /// Combat movement currently enabled + uint8 m_combatMovement; }; struct SelectableAI : public FactoryHolder, public Permissible diff --git a/src/game/Object/CreatureEventAI.cpp b/src/game/Object/CreatureEventAI.cpp index 8a3ed0ae..dde4836a 100644 --- a/src/game/Object/CreatureEventAI.cpp +++ b/src/game/Object/CreatureEventAI.cpp @@ -84,6 +84,7 @@ void CreatureEventAI::GetAIInformation(ChatHandler& reader) CreatureEventAI::CreatureEventAI(Creature* c) : CreatureAI(c), m_Phase(0), m_MeleeEnabled(true), + m_currSpell(0), m_HasOOCLoSEvent(false), m_InvinceabilityHpLevel(0), m_throwAIEventMask(0), @@ -627,7 +628,6 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 { uint32 selectFlags = 0; uint32 spellId = 0; - uint32 cmFlags = 0; if (!(action.cast.castFlags & (CAST_TRIGGERED | CAST_FORCE_CAST | CAST_FORCE_TARGET_SELF))) { @@ -643,46 +643,9 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 return; } - CanCastResult castResult = DoCastSpellIfCan(target, action.cast.spellId, action.cast.castFlags); + DoCastSpellIfCan(target, action.cast.spellId, action.cast.castFlags); - switch (castResult) - { - case CAST_FAIL_POWER: - cmFlags |= COMBAT_MOVEMENT_OOM; - break; - case CAST_FAIL_TOO_FAR: - cmFlags |= COMBAT_MOVEMENT_DISTANCE; - break; - case CAST_FAIL_NO_LOS: - cmFlags |= COMBAT_MOVEMENT_LOS; - break; - default: - break; - } - - // Melee current victim if flag not set - if (cmFlags) - { - if (!(action.cast.castFlags & CAST_NO_MELEE_IF_OOM)) - { - AddCombatMovementFlags(cmFlags); - SetCombatMovement(true,true); - } - } - else - { - if (m_combatMovement & (COMBAT_MOVEMENT_LOS | COMBAT_MOVEMENT_DISTANCE | COMBAT_MOVEMENT_OOM)) - { - ClearCombatMovementFlags(COMBAT_MOVEMENT_LOS | COMBAT_MOVEMENT_DISTANCE | COMBAT_MOVEMENT_OOM); - - if (!IsCombatMovement() && m_creature->IsNonMeleeSpellCasted(false) && m_creature->IsInCombat() && m_creature->getVictim()) - { - SetCombatMovement(false,true); - m_creature->SendMeleeAttackStop(m_creature->getVictim()); - } - } - } - break; + break; } case ACTION_T_SUMMON: //12 { @@ -766,26 +729,22 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 m_MeleeEnabled = action.auto_attack.state != 0; break; case ACTION_T_COMBAT_MOVEMENT: //21 + { // ignore no affect case - if (IsCombatMovement() == (action.combat_movement.state != 0) || m_creature->IsNonMeleeSpellCasted(false)) - { - if (IsCombatMovement()) - { AddCombatMovementFlags(COMBAT_MOVEMENT_SCRIPT); } + if ((GetCombatMovementFlags() & CM_SCRIPT) == (action.combat_movement.state != 0)) return; + + SetCombatMovement(action.combat_movement.state != 0, true); + + if (m_creature->IsInCombat() && m_creature->getVictim()) + { + if (IsCombatMovement() && action.combat_movement.melee) + m_creature->SendMeleeAttackStart(m_creature->getVictim()); + else if (action.combat_movement.melee) + m_creature->SendMeleeAttackStop(m_creature->getVictim()); } - - if (action.combat_movement.state != 0) - { AddCombatMovementFlags(COMBAT_MOVEMENT_SCRIPT); } - else - { ClearCombatMovementFlags(COMBAT_MOVEMENT_SCRIPT); } - - SetCombatMovement(IsCombatMovement(), true); - - if (IsCombatMovement() && action.combat_movement.melee && m_creature->IsInCombat() && m_creature->getVictim()) - { m_creature->SendMeleeAttackStart(m_creature->getVictim()); } - else if (action.combat_movement.melee && m_creature->IsInCombat() && m_creature->getVictim()) - { m_creature->SendMeleeAttackStop(m_creature->getVictim()); } break; + } case ACTION_T_SET_PHASE: //22 m_Phase = action.set_phase.phase; DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_SET_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase); @@ -1111,6 +1070,7 @@ void CreatureEventAI::JustRespawned() // NOTE that this is void CreatureEventAI::Reset() { + m_currSpell = 0; m_EventUpdateTime = EVENT_UPDATE_TIME; m_EventDiff = 0; m_throwAIEventStep = 0; @@ -1539,7 +1499,7 @@ void CreatureEventAI::ReceiveEmote(Player* pPlayer, uint32 text_emote) } } -#define HEALTH_STEPS 3 +#define HEALTH_STEPS 3 void CreatureEventAI::DamageTaken(Unit* dealer, uint32& damage) { @@ -1618,3 +1578,71 @@ bool CreatureEventAI::SpawnedEventConditionsCheck(CreatureEventAI_Event const& e return false; } + +void CreatureEventAI::OnSpellCastChange(const SpellEntry* pSpell, SpellCastResult reason) +{ + //sLog.outError("Received reason %u for %s, spell %u (curSpell = %u)", reason, m_creature->GetGuidStr().c_str(), pSpell->Id, m_currSpell); + + if (!pSpell || pSpell->Id != m_currSpell) + return; + + m_currSpell = 0; + + switch (reason) + { + case SPELL_CAST_OK: + break; + // Add more cases here + case SPELL_FAILED_OUT_OF_RANGE: + case SPELL_FAILED_LINE_OF_SIGHT: + case SPELL_FAILED_NO_POWER: + case SPELL_FAILED_INTERRUPTED: + case SPELL_FAILED_SILENCED: + m_creature->CastStop(); + SetCombatMovementFlag(CM_SPELL); + SetChase(true); + return; + default: + m_creature->CastStop(); + break; + } + SetCombatMovementFlag(CM_SPELL, false); + SetChase(false); + +} + +CanCastResult CreatureEventAI::DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags, ObjectGuid uiOriginalCasterGuid) +{ + SetCombatMovementFlag(CM_SPELL, false); + m_currSpell = uiSpell; + SetChase(false); + + CanCastResult result = CreatureAI::DoCastSpellIfCan(pTarget, uiSpell, uiCastFlags, uiOriginalCasterGuid); + switch (result) + { + case CAST_OK: + break; + case CAST_FAIL_TOO_FAR: + case CAST_FAIL_NO_LOS: + case CAST_FAIL_POWER: + case CAST_FAIL_SILENCED: + { + if (!(uiCastFlags & CAST_NO_MELEE_IF_OOM)) + { + SetCombatMovementFlag(CM_SPELL); + SetChase(true); + } + else + { + m_currSpell = 0; + } + break; + } + default: + m_currSpell = 0; + break; + } + + return result; +} + diff --git a/src/game/Object/CreatureEventAI.h b/src/game/Object/CreatureEventAI.h index 0f9ddd20..4210d94e 100644 --- a/src/game/Object/CreatureEventAI.h +++ b/src/game/Object/CreatureEventAI.h @@ -665,6 +665,8 @@ class CreatureEventAI : public CreatureAI void AttackStart(Unit* who) override; void MoveInLineOfSight(Unit* who) override; void SpellHit(Unit* pUnit, const SpellEntry* pSpell) override; + void OnSpellCastChange(const SpellEntry* pSpell, SpellCastResult reason) override; + virtual CanCastResult DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags = 0, ObjectGuid OriginalCasterGuid = ObjectGuid()); void DamageTaken(Unit* done_by, uint32& damage) override; void HealedBy(Unit* healer, uint32& healedAmount) override; void UpdateAI(const uint32 diff) override; @@ -701,6 +703,7 @@ class CreatureEventAI : public CreatureAI bool m_MeleeEnabled; // If we allow melee auto attack bool m_HasOOCLoSEvent; // Cache if a OOC-LoS Event exists uint32 m_InvinceabilityHpLevel; // Minimal health level allowed at damage apply + uint32 m_currSpell; // track current spell from ACTION_T_CAST if any uint32 m_throwAIEventMask; // Automatically throw AIEvents that are encoded into this mask // Note that Step 100 means that AI_EVENT_GOT_FULL_HEALTH was sent diff --git a/src/game/Object/Unit.cpp b/src/game/Object/Unit.cpp index 30e866d5..c05bd539 100644 --- a/src/game/Object/Unit.cpp +++ b/src/game/Object/Unit.cpp @@ -8591,7 +8591,9 @@ void Unit::SetFeared(bool apply, ObjectGuid casterGuid, uint32 spellID, uint32 t SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING); + StopMoving(GetTypeId() == TYPEID_PLAYER); GetMotionMaster()->MovementExpired(false); + CastStop(GetObjectGuid() == casterGuid ? spellID : 0); if (GetTypeId() == TYPEID_UNIT) @@ -8635,39 +8637,41 @@ void Unit::SetConfused(bool apply, ObjectGuid casterGuid, uint32 spellID) { if (apply) { - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); - GetMotionMaster()->MovementExpired(false); - CastStop(GetObjectGuid() == casterGuid ? spellID : 0); + StopMoving(GetTypeId() == TYPEID_PLAYER); + GetMotionMaster()->MovementExpired(false); - if (GetTypeId() == TYPEID_UNIT) - SetTargetGuid(ObjectGuid()); + CastStop(GetObjectGuid() == casterGuid ? spellID : 0); + + if (GetTypeId() == TYPEID_UNIT) + SetTargetGuid(ObjectGuid()); GetMotionMaster()->MoveConfused(); } else { - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); - GetMotionMaster()->MovementExpired(GetTypeId() == TYPEID_PLAYER); - if (GetTypeId() == TYPEID_PLAYER) - StopMoving(true); + GetMotionMaster()->MovementExpired(GetTypeId() == TYPEID_PLAYER); + if (GetTypeId() == TYPEID_PLAYER) + StopMoving(true); - if (GetTypeId() != TYPEID_PLAYER && IsAlive()) - { + if (GetTypeId() != TYPEID_PLAYER && IsAlive()) + { // restore appropriate movement generator - if (getVictim()) - { - SetTargetGuid(getVictim()->GetObjectGuid()); - GetMotionMaster()->MoveChase(getVictim()); - } - else + if (getVictim()) + { + SetTargetGuid(getVictim()->GetObjectGuid()); + GetMotionMaster()->MoveChase(getVictim()); + } + else { GetMotionMaster()->Initialize(); } - } + } } if (GetTypeId() == TYPEID_PLAYER) - { ((Player*)this)->SetClientControl(this, !apply); } + { ((Player*)this)->SetClientControl(this, !apply); } } void Unit::SetFeignDeath(bool apply, ObjectGuid casterGuid /*= ObjectGuid()*/) diff --git a/src/game/WorldHandlers/Spell.cpp b/src/game/WorldHandlers/Spell.cpp index 1a74793f..c370f7a8 100644 --- a/src/game/WorldHandlers/Spell.cpp +++ b/src/game/WorldHandlers/Spell.cpp @@ -3261,7 +3261,11 @@ void Spell::finish(bool ok) void Spell::SendCastResult(SpellCastResult result) { if (m_caster->GetTypeId() != TYPEID_PLAYER) - { return; } + { + if (((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->OnSpellCastChange(m_spellInfo, result); + return; + } if (((Player*)m_caster)->GetSession()->PlayerLoading()) // don't send cast results at loading time { return; }