Skip to content

Commit

Permalink
Crowd Control handling improvements
Browse files Browse the repository at this point in the history
Introducing a new unified logical construct: Incapacitated State.
Incapacitated State is a state causing unit to lose ability to move, attack/cast and creatures to lose their current targets.
The following unit states are counted as incapacitated:
* Stunned
* Confused
* Fleeing

Introducing new unified Root/Stun logical construct: Immobilized State.

Resolved issues:
* Creatures will no longer resume fighting on Fear or Confuse effect expiration if more Fear or Confuse effects are applied on it (Fear/Death Coil/Pplymorph/Blind/etc overlapping)
* Confused creatures will now correctly lose their current target
* Fleeing creatures will no longer be immobilized on confusion effect application and will wander around at walking speed instead
* Fixed a heavily exploitable case with confused creatures (including polymorphed) no longer being able to attack after effect overlapping by a fear (copy-pasted MovementExpired from Fear on confusion application)
* Fixed a case with wandering fleeing creatures continuing to auto-attack passers-by when fleeing preventing aura was removed and fleeing movement was re-initiated (Curse of Recklessnes)
* Fixed granting client control on expiration of Fear/Confusion effects to a player in a completed battleground (Score screen).
* Added UNIT_STAT_CONFUSED to UNIT_STAT_LOST_CONTROL
* Player will no longer be granted full control over MCed creature while confusion/fear effects present
* New CC handlers for Stun/Root under Immobilized State
  • Loading branch information
Warlockbugs committed Sep 20, 2016
1 parent 2eef2ef commit 2c933db
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 97 deletions.
8 changes: 8 additions & 0 deletions src/game/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20929,6 +20929,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);
Expand Down
1 change: 1 addition & 0 deletions src/game/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -2173,6 +2173,7 @@ class MANGOS_DLL_SPEC Player : public Unit
bool IsFreeFlying() const { return HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED) || HasAuraType(SPELL_AURA_FLY); }
bool CanStartFlyInArea(uint32 mapid, uint32 zone, uint32 area) const;

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; }
Expand Down
58 changes: 15 additions & 43 deletions src/game/SpellAuras.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4427,6 +4427,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());
}

Expand All @@ -4435,6 +4439,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());
}

Expand Down Expand Up @@ -4516,21 +4524,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);

Unit* charmer = target->GetCharmer();
if (target->GetTypeId() == TYPEID_PLAYER || (charmer && charmer->GetTypeId() == TYPEID_PLAYER))
{
target->m_movementInfo.SetMovementFlags(MOVEFLAG_NONE);
target->SetStandState(UNIT_STAND_STATE_STAND);// in 1.5 client
target->SetRoot(true);
}
else
target->StopMoving();
target->SetStunned(true);

// Summon the Naj'entus Spine GameObject on target if spell is Impaling Spine
if (GetId() == 39837)
Expand Down Expand Up @@ -4577,16 +4571,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 & uint64(0x0000100000000000))
Expand Down Expand Up @@ -4804,17 +4789,6 @@ void Aura::HandleAuraModRoot(bool apply, bool Real)
// Frost root aura -> freeze/unfreeze target
if (GetSpellSchoolMask(GetSpellProto()) & SPELL_SCHOOL_MASK_FROST)
target->ModifyAuraState(AURA_STATE_FROZEN, apply);

target->addUnitState(UNIT_STAT_ROOT);
target->SetRoot(true);

if (target->GetTypeId() == TYPEID_PLAYER)
{
// Clear unit movement flags
((Player*)target)->m_movementInfo.SetMovementFlags(MOVEFLAG_NONE);
}
else
target->StopMoving();
}
else
{
Expand Down Expand Up @@ -4844,12 +4818,9 @@ void Aura::HandleAuraModRoot(bool apply, bool Real)
// Real remove called after current aura remove from lists, check if other similar auras active
if (target->HasAuraType(SPELL_AURA_MOD_ROOT))
return;

target->clearUnitState(UNIT_STAT_ROOT);

if (!target->hasUnitState(UNIT_STAT_STUNNED)) // prevent allow move if have also stun effect
target->SetRoot(false);
}

target->SetImmobilizedState(apply);
}

void Aura::HandleAuraModSilence(bool apply, bool Real)
Expand Down Expand Up @@ -8611,10 +8582,11 @@ 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());
}
}

Expand Down
156 changes: 103 additions & 53 deletions src/game/Unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10466,81 +10466,131 @@ 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;
const Unit* charmer = GetCharmer();
const bool player = ((charmer ? charmer : this)->GetTypeId() == TYPEID_PLAYER);
if (apply)
{
if (HasAuraType(SPELL_AURA_PREVENTS_FLEEING))
return;
addUnitState(state);
if (!player)
StopMoving();
else
{
// Clear unit movement flags
m_movementInfo.SetMovementFlags(MOVEFLAG_NONE);
if (stun)
SetStandState(UNIT_STAND_STATE_STAND); // in 1.5 client
SetRoot(true);
}
}
else
{
clearUnitState(state);
// Prevent giving ability to move if more immobilizers are active
if (!hasUnitState(immobilized) && (player || m_movementInfo.HasMovementFlag(MOVEFLAG_ROOT)))
SetRoot(false);
}
}

SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING);
void Unit::SetFeared(bool apply, ObjectGuid casterGuid, uint32 spellID, uint32 time)
{
SetIncapacitatedState(apply, UNIT_FLAG_FLEEING, casterGuid, spellID, time);
}

GetMotionMaster()->MovementExpired(false);
CastStop(GetObjectGuid() == casterGuid ? spellID : 0);
void Unit::SetConfused(bool apply, ObjectGuid casterGuid, uint32 spellID)
{
SetIncapacitatedState(apply, UNIT_FLAG_CONFUSED, casterGuid, spellID);
}

if (GetTypeId() == TYPEID_UNIT)
SetTargetGuid(ObjectGuid()); // creature feared loose its target
void Unit::SetStunned(bool apply)
{
SetIncapacitatedState(apply, UNIT_FLAG_STUNNED);
}

Unit* caster = IsInWorld() ? GetMap()->GetUnit(casterGuid) : nullptr;
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);

GetMotionMaster()->MoveFleeing(caster, time); // caster==nullptr processed in MoveFleeing
if (apply)
{
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, UNIT_FLAG_FLEEING);
RemoveFlag(UNIT_FIELD_FLAGS, state);

if (movement)
GetMotionMaster()->MovementExpired(false);
if (apply)
CastStop(GetObjectGuid() == casterGuid ? spellID : 0);

if (GetTypeId() != TYPEID_PLAYER && isAlive())
if (GetTypeId() == TYPEID_UNIT)
{
if (HasFlag(UNIT_FIELD_FLAGS, filter))
{
Creature* c = ((Creature*)this);
// 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()); // restore target
GetMotionMaster()->MoveChase(getVictim());
SetTargetGuid(victim->GetObjectGuid()); // Restore target
if (movement)
GetMotionMaster()->MoveChase(victim); // Restore movement generator
}
else
GetMotionMaster()->Initialize();
else if (movement)
GetMotionMaster()->Initialize(); // Reset movement generator

// attack caster if can
if (Unit* caster = IsInWorld() ? GetMap()->GetUnit(casterGuid) : nullptr)
c->AttackedBy(caster);
if (!apply && fleeing)
{
// Attack the caster if can on fear expiration
if (Unit* caster = IsInWorld() ? GetMap()->GetUnit(casterGuid) : nullptr)
((Creature*)this)->AttackedBy(caster);
}
}
}

if (GetTypeId() == TYPEID_PLAYER)
((Player*)this)->SetClientControl(this, !apply);
}
// Update stun if required:
if (stun)
SetImmobilizedState(apply, true);

void Unit::SetConfused(bool apply, ObjectGuid casterGuid, uint32 spellID)
{
if (apply)
{
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED);

CastStop(GetObjectGuid() == casterGuid ? spellID : 0);
if (!movement)
return;

GetMotionMaster()->MoveConfused();
}
else
// Check if we should return or remove player control after change
if (controller)
{
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CONFUSED);

GetMotionMaster()->MovementExpired(false);

if (GetTypeId() != TYPEID_PLAYER && isAlive())
{
// restore appropriate movement generator
if (getVictim())
GetMotionMaster()->MoveChase(getVictim());
else
GetMotionMaster()->Initialize();
}
const bool remove = !controller->IsClientControl(this);
if (control && remove)
controller->SetClientControl(this, 0);
else if (!control && !remove)
controller->SetClientControl(this, 1);
}

if (GetTypeId() == TYPEID_PLAYER)
((Player*)this)->SetClientControl(this, !apply);
// 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()*/)
Expand Down Expand Up @@ -11582,7 +11632,7 @@ bool Unit::TakePossessOf(Unit* possessed)
if (player)
{
player->GetCamera().SetView(possessed);
player->SetClientControl(possessed, 1);
player->SetClientControl(possessed, player->IsClientControl(possessed));
player->SetMover(possessed);
player->SendForcedObjectUpdate();

Expand Down Expand Up @@ -11626,7 +11676,7 @@ void Unit::ResetControlState(bool attackCharmer /*= true*/)
if (player)
{
player->GetCamera().ResetView();
player->SetClientControl(player, 1);
player->SetClientControl(player, player->IsClientControl(player));
player->SetMover(nullptr);
}
return;
Expand Down Expand Up @@ -11660,7 +11710,7 @@ void Unit::ResetControlState(bool attackCharmer /*= true*/)
{
Player* possessedPlayer = static_cast<Player *>(possessed);
possessedPlayer->setFactionForRace(possessedPlayer->getRace());
possessedPlayer->SetClientControl(possessedPlayer, 1);
possessedPlayer->SetClientControl(possessedPlayer, possessedPlayer->IsClientControl(possessedPlayer));
}
else if (possessedCreature)
{
Expand Down
18 changes: 17 additions & 1 deletion src/game/Unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,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,
Expand Down Expand Up @@ -2082,8 +2082,24 @@ class MANGOS_DLL_SPEC 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); }
Expand Down

0 comments on commit 2c933db

Please sign in to comment.