diff --git a/src/game/Object/Player.cpp b/src/game/Object/Player.cpp index 1db4c31e..58116773 100644 --- a/src/game/Object/Player.cpp +++ b/src/game/Object/Player.cpp @@ -18822,6 +18822,14 @@ void Player::ResurectUsingRequestData() SpawnCorpseBones(); } +bool Player::IsClientControl(Unit* target) const +{ + return (target && !target->IsFleeing() && !target->IsConfused() && !target->IsTaxiFlying() && + (target->GetTypeId() != TYPEID_PLAYER || + !((Player*)target)->InBattleGround() || ((Player*)target)->GetBattleGround()->GetStatus() != STATUS_WAIT_LEAVE) && + target->GetCharmerOrOwnerOrOwnGuid() == GetObjectGuid()); +} + void Player::SetClientControl(Unit* target, uint8 allowMove) { WorldPacket data(SMSG_CLIENT_CONTROL_UPDATE, target->GetPackGUID().size() + 1); diff --git a/src/game/Object/Player.h b/src/game/Object/Player.h index 6b407a1f..f61d2539 100644 --- a/src/game/Object/Player.h +++ b/src/game/Object/Player.h @@ -2196,6 +2196,7 @@ class Player : public Unit bool IsFlying() const { return false; } bool IsFreeFlying() const { return false; } + bool IsClientControl(Unit* target) const; void SetClientControl(Unit* target, uint8 allowMove); void SetMover(Unit* target) { m_mover = target ? target : this; } Unit* GetMover() const { return m_mover; } diff --git a/src/game/Object/Unit.cpp b/src/game/Object/Unit.cpp index afcadf39..724fd9ae 100644 --- a/src/game/Object/Unit.cpp +++ b/src/game/Object/Unit.cpp @@ -8599,98 +8599,132 @@ void Unit::InterruptMoving(bool forceSendStop /*=false*/) StopMoving(forceSendStop || isMoving); } -void Unit::SetFeared(bool apply, ObjectGuid casterGuid, uint32 spellID, uint32 time) +void Unit::SetImmobilizedState(bool apply, bool stun) { + const uint32 immobilized = (UNIT_STAT_ROOT | UNIT_STAT_STUNNED); + const uint32 state = stun ? UNIT_STAT_STUNNED : UNIT_STAT_ROOT; if (apply) { - if (HasAuraType(SPELL_AURA_PREVENTS_FLEEING)) - { return; } - - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING); - - StopMoving(GetTypeId() == TYPEID_PLAYER); - GetMotionMaster()->MovementExpired(false); - - CastStop(GetObjectGuid() == casterGuid ? spellID : 0); - - if (GetTypeId() == TYPEID_UNIT) - SetTargetGuid(ObjectGuid()); // creature feared loose its target - - Unit* caster = IsInWorld() ? GetMap()->GetUnit(casterGuid) : NULL; - - GetMotionMaster()->MoveFleeing(caster, time); // caster==NULL processed in MoveFleeing + addUnitState(state); + if (GetTypeId() != TYPEID_PLAYER) + StopMoving(); + else + { + // Clear unit movement flags + ((Player*)this)->m_movementInfo.SetMovementFlags(MOVEFLAG_NONE); + if (stun) + SetStandState(UNIT_STAND_STATE_STAND); // in 1.5 client + SetRoot(true); + } } else { - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING); - - GetMotionMaster()->MovementExpired(GetTypeId() == TYPEID_PLAYER); - if (GetTypeId() == TYPEID_PLAYER) - StopMoving(true); - - if (GetTypeId() != TYPEID_PLAYER && IsAlive()) - { - Creature* c = ((Creature*)this); - // restore appropriate movement generator - if (getVictim()) - { - SetTargetGuid(getVictim()->GetObjectGuid()); // restore target - GetMotionMaster()->MoveChase(getVictim()); - } - else - { GetMotionMaster()->Initialize(); } - - // attack caster if can - if (Unit* caster = IsInWorld() ? GetMap()->GetUnit(casterGuid) : NULL) - { c->AttackedBy(caster); } - } + clearUnitState(state); + // Prevent giving ability to move if more immobilizers are active + if (!hasUnitState(immobilized) && (GetTypeId() == TYPEID_PLAYER)) + SetRoot(false); } +} - if (GetTypeId() == TYPEID_PLAYER) - { ((Player*)this)->SetClientControl(this, !apply); } +void Unit::SetFeared(bool apply, ObjectGuid casterGuid, uint32 spellID, uint32 time) +{ + SetIncapacitatedState(apply, UNIT_FLAG_FLEEING, casterGuid, spellID, time); } void Unit::SetConfused(bool apply, ObjectGuid casterGuid, uint32 spellID) { + SetIncapacitatedState(apply, UNIT_FLAG_CONFUSED, casterGuid, spellID); +} + +void Unit::SetStunned(bool apply) +{ + SetIncapacitatedState(apply, UNIT_FLAG_STUNNED); +} + +void Unit::SetIncapacitatedState(bool apply, uint32 state, ObjectGuid casterGuid, uint32 spellID, uint32 time) +{ + // We are interested only in a particular subset of flags: + const uint32 filter = (UNIT_FLAG_STUNNED | UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING); + if (!state || !(state & filter) || (state & ~filter)) + return; + + Player* controller = GetCharmerOrOwnerPlayerOrPlayerItself(); + const bool control = controller ? controller->IsClientControl(this) : false; + const bool movement = (state != UNIT_FLAG_STUNNED); + const bool stun = (state & UNIT_FLAG_STUNNED); + const bool fleeing = (state & UNIT_FLAG_FLEEING); + if (apply) { - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); - - StopMoving(GetTypeId() == TYPEID_PLAYER); - GetMotionMaster()->MovementExpired(false); - - CastStop(GetObjectGuid() == casterGuid ? spellID : 0); - - if (GetTypeId() == TYPEID_UNIT) - SetTargetGuid(ObjectGuid()); - - GetMotionMaster()->MoveConfused(); + if (fleeing && HasAuraType(SPELL_AURA_PREVENTS_FLEEING)) + { + if (state == UNIT_FLAG_FLEEING) + return; + else + state &= ~UNIT_FLAG_FLEEING; + } + SetFlag(UNIT_FIELD_FLAGS, state); } else + RemoveFlag(UNIT_FIELD_FLAGS, state); + + if (movement) + GetMotionMaster()->MovementExpired(false); + if (apply) + CastStop(GetObjectGuid() == casterGuid ? spellID : 0); + + if (GetTypeId() == TYPEID_UNIT) { - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); - - GetMotionMaster()->MovementExpired(GetTypeId() == TYPEID_PLAYER); - if (GetTypeId() == TYPEID_PLAYER) - StopMoving(true); - - if (GetTypeId() != TYPEID_PLAYER && IsAlive()) + if (HasFlag(UNIT_FIELD_FLAGS, filter)) { - // restore appropriate movement generator - if (getVictim()) + if (!GetTargetGuid().IsEmpty()) // Incapacitated creature loses its target + SetTargetGuid(ObjectGuid()); + } + else if (IsAlive()) + { + if (Unit* victim = getVictim()) { - SetTargetGuid(getVictim()->GetObjectGuid()); - GetMotionMaster()->MoveChase(getVictim()); + SetTargetGuid(victim->GetObjectGuid()); // Restore target + if (movement) + GetMotionMaster()->MoveChase(victim); // Restore movement generator + } + else if (movement) + GetMotionMaster()->Initialize(); // Reset movement generator + + if (!apply && fleeing) + { + // Attack the caster if can on fear expiration + if (Unit* caster = IsInWorld() ? GetMap()->GetUnit(casterGuid) : nullptr) + ((Creature*)this)->AttackedBy(caster); } - else - { GetMotionMaster()->Initialize(); } } } - if (GetTypeId() == TYPEID_PLAYER) - { ((Player*)this)->SetClientControl(this, !apply); } + // Update stun if required: + if (stun) + SetImmobilizedState(apply, true); + + if (!movement) + return; + + // Check if we should return or remove player control after change + if (controller) + { + const bool remove = !controller->IsClientControl(this); + if (control && remove) + controller->SetClientControl(this, 0); + else if (!control && !remove) + controller->SetClientControl(this, 1); + } + + // Update incapacitated movement if required: + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED)) + GetMotionMaster()->MoveConfused(); + else if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING)) + GetMotionMaster()->MoveFleeing(IsInWorld() ? GetMap()->GetUnit(casterGuid) : nullptr, time); } + void Unit::SetFeignDeath(bool apply, ObjectGuid casterGuid /*= ObjectGuid()*/) { if (apply) diff --git a/src/game/Object/Unit.h b/src/game/Object/Unit.h index e4767135..f42c3ccf 100644 --- a/src/game/Object/Unit.h +++ b/src/game/Object/Unit.h @@ -451,7 +451,7 @@ enum UnitState UNIT_STAT_CONFUSED | UNIT_STAT_FLEEING, // AI disabled by some reason - UNIT_STAT_LOST_CONTROL = UNIT_STAT_FLEEING | UNIT_STAT_CONTROLLED, + UNIT_STAT_LOST_CONTROL = UNIT_STAT_CONFUSED | UNIT_STAT_FLEEING | UNIT_STAT_CONTROLLED, // above 2 state cases UNIT_STAT_CAN_NOT_REACT_OR_LOST_CONTROL = UNIT_STAT_CAN_NOT_REACT | UNIT_STAT_LOST_CONTROL, @@ -3627,8 +3627,23 @@ class Unit : public WorldObject void StopMoving(bool forceSendStop = false); void InterruptMoving(bool forceSendStop = false); + ///----------Various crowd control methods----------------- + bool IsImmobilized() const { return hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED); } + void SetImmobilizedState(bool apply, bool stun = false); + + // These getters operate on unit flags set by IncapacitatedState and are meant for formal usage in conjunction with spell effects only + // For actual internal movement states use UnitState flags + // TODO: The UnitState thing needs to be rewriten at some point, this kind of duality is bad + bool IsFleeing() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING); } + bool IsConfused() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED); } + bool IsStunned() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); } + bool IsIncapacitated() const { return (IsFleeing() || IsConfused() || IsStunned()); } + void SetFeared(bool apply, ObjectGuid casterGuid = ObjectGuid(), uint32 spellID = 0, uint32 time = 0); void SetConfused(bool apply, ObjectGuid casterGuid = ObjectGuid(), uint32 spellID = 0); + void SetStunned(bool apply); + void SetIncapacitatedState(bool apply, uint32 state = 0, ObjectGuid casterGuid = ObjectGuid(), uint32 spellID = 0, uint32 time = 0); + ///----------End of crowd control methods---------- void SetFeignDeath(bool apply, ObjectGuid casterGuid = ObjectGuid()); void AddComboPointHolder(uint32 lowguid) { m_ComboPointHolders.insert(lowguid); } diff --git a/src/game/WorldHandlers/SpellAuras.cpp b/src/game/WorldHandlers/SpellAuras.cpp index b7c9ba23..f12cee89 100644 --- a/src/game/WorldHandlers/SpellAuras.cpp +++ b/src/game/WorldHandlers/SpellAuras.cpp @@ -2343,6 +2343,10 @@ void Aura::HandleModConfuse(bool apply, bool Real) if (!Real) { return; } + // Do not remove it yet if more effects are up, do it for the last effect + if (!apply && GetTarget()->HasAuraType(SPELL_AURA_MOD_CONFUSE)) + return; + GetTarget()->SetConfused(apply, GetCasterGuid(), GetId()); } @@ -2351,6 +2355,10 @@ void Aura::HandleModFear(bool apply, bool Real) if (!Real) { return; } + // Do not remove it yet if more effects are up, do it for the last effect + if (!apply && GetTarget()->HasAuraType(SPELL_AURA_MOD_FEAR)) + return; + GetTarget()->SetFeared(apply, GetCasterGuid(), GetId()); } @@ -2402,21 +2410,7 @@ void Aura::HandleAuraModStun(bool apply, bool Real) if (GetSpellSchoolMask(GetSpellProto()) & SPELL_SCHOOL_MASK_FROST) { target->ModifyAuraState(AURA_STATE_FROZEN, apply); } - target->addUnitState(UNIT_STAT_STUNNED); - target->SetTargetGuid(ObjectGuid()); - - target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); - target->CastStop(target->GetObjectGuid() == GetCasterGuid() ? GetId() : 0); - - // Creature specific - if (target->GetTypeId() != TYPEID_PLAYER) - { target->StopMoving(); } - else - { - ((Player*)target)->m_movementInfo.SetMovementFlags(MOVEFLAG_NONE); - target->SetStandState(UNIT_STAND_STATE_STAND);// in 1.5 client - target->SetRoot(true); - } + target->SetStunned(true); } else { @@ -2447,16 +2441,7 @@ void Aura::HandleAuraModStun(bool apply, bool Real) if (target->HasAuraType(SPELL_AURA_MOD_STUN)) { return; } - target->clearUnitState(UNIT_STAT_STUNNED); - target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); - - if (!target->hasUnitState(UNIT_STAT_ROOT)) // prevent allow move if have also root effect - { - if (target->getVictim() && target->IsAlive()) - { target->SetTargetGuid(target->getVictim()->GetObjectGuid()); } - - target->SetRoot(false); - } + target->SetStunned(false); // Wyvern Sting if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_HUNTER && GetSpellProto()->SpellFamilyFlags & UI64LIT(0x00010000)) @@ -2691,11 +2676,9 @@ void Aura::HandleAuraModRoot(bool apply, bool Real) if (target->HasAuraType(SPELL_AURA_MOD_ROOT)) { return; } - target->clearUnitState(UNIT_STAT_ROOT); - - if (!target->hasUnitState(UNIT_STAT_STUNNED) && (target->GetTypeId() == TYPEID_PLAYER)) // prevent allow move if have also stun effect - { target->SetRoot(false); } } + + target->SetImmobilizedState(apply); } void Aura::HandleAuraModSilence(bool apply, bool Real) @@ -4851,10 +4834,12 @@ void Aura::HandlePreventFleeing(bool apply, bool Real) Unit::AuraList const& fearAuras = GetTarget()->GetAurasByType(SPELL_AURA_MOD_FEAR); if (!fearAuras.empty()) { + const Aura *first = fearAuras.front(); + if (apply) - { GetTarget()->SetFeared(false, fearAuras.front()->GetCasterGuid()); } + { GetTarget()->SetFeared(false, first->GetCasterGuid()); } else - { GetTarget()->SetFeared(true); } + { GetTarget()->SetFeared(true, first->GetCasterGuid(), first->GetId()); } } }