Skip to content

Commit

Permalink
Client movement control management rework
Browse files Browse the repository at this point in the history
More elaborate and more accurate functionality, adds some more support for charms.

Additional serverside API for client control querying.
  • Loading branch information
Warlockbugs committed Nov 2, 2017
1 parent 85acd4b commit bd3b867
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/game/BattleGround/BattleGround.cpp
Expand Up @@ -1015,7 +1015,7 @@ void BattleGround::RewardQuestComplete(Player* plr)

void BattleGround::BlockMovement(Player* plr)
{
plr->SetClientControl(plr, 0); // movement disabled NOTE: the effect will be automatically removed by client when the player is teleported from the battleground, so no need to send with uint8(1) in RemovePlayerAtLeave()
plr->UpdateClientControl(plr, false); // movement disabled NOTE: the effect will be automatically removed by client when the player is teleported from the battleground, so no need to send with uint8(1) in RemovePlayerAtLeave()
}

void BattleGround::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool SendPacket)
Expand Down
58 changes: 48 additions & 10 deletions src/game/Entities/Player.cpp
Expand Up @@ -21132,20 +21132,58 @@ void Player::ResurectUsingRequestData()
SpawnCorpseBones();
}

bool Player::IsClientControl(Unit* target) const
bool Player::IsClientControl(Unit const* 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 == this || target->GetMasterGuid() == GetObjectGuid()));
if (!target)
return false;

// Applies only to player controlled units
if (!target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
return false;

// These flags are meant to be used with client control taken away (4/5 confirmed by data)
if (target->HasFlag(UNIT_FIELD_FLAGS, (UNIT_FLAG_UNK_0 | UNIT_FLAG_NON_MOVING_DEPRECATED | UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING | UNIT_FLAG_TAXI_FLIGHT)))
return false;

// Player in completed battleground during "score screen"
// TODO: research if its actually done serverside with any of flags listed above;
// It would make perfect sense and clean this implementation up a bit
if (target->GetTypeId() == TYPEID_PLAYER)
{
Player const* player = static_cast<Player const*>(target);
if (player->InBattleGround())
{
if (const BattleGround* bg = player->GetBattleGround())
{
if (bg->GetStatus() == STATUS_WAIT_LEAVE)
return false;
}
}
}

// If unit is possessed, it must be charmed by the player
if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED))
return (target->GetCharmerGuid() == GetObjectGuid());

// Players only have control over self by default
return (target == this);
}

void Player::SetClientControl(Unit* target, uint8 allowMove) const
void Player::UpdateClientControl(Unit const* target, bool enabled, bool forced) const
{
WorldPacket data(SMSG_CLIENT_CONTROL_UPDATE, target->GetPackGUID().size() + 1);
data << target->GetPackGUID();
data << uint8(allowMove);
GetSession()->SendPacket(data);
if (target)
{
// Sending disabled control multiple times for the same unit is harmless (seen in data all the time)
// Do a double-check if we should enable it only
if (forced || !enabled || IsClientControl(target))
{
const PackedGuid &packedGuid = target->GetPackGUID();
WorldPacket data(SMSG_CLIENT_CONTROL_UPDATE, packedGuid.size() + 1);
data << packedGuid;
data << uint8(enabled);
GetSession()->SendPacket(data);
}
}
}

void Player::Uncharm()
Expand Down
6 changes: 4 additions & 2 deletions src/game/Entities/Player.h
Expand Up @@ -2172,11 +2172,13 @@ class 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) const;
bool IsClientControl(Unit const* target) const;
void UpdateClientControl(Unit const* target, bool enabled, bool forced = false) const;

void SetMover(Unit* target) { m_mover = target ? target : this; }
Unit* GetMover() const { return m_mover; }
bool IsSelfMover() const { return m_mover == this; }// normal case for player not controlling other unit

void Uncharm() override;

ObjectGuid const& GetFarSightGuid() const { return GetGuidValue(PLAYER_FARSIGHT); }
Expand Down
96 changes: 73 additions & 23 deletions src/game/Entities/Unit.cpp
Expand Up @@ -6905,6 +6905,34 @@ Player const* Unit::GetControllingPlayer() const
return nullptr;
}

Player const* Unit::GetControllingClientPlayer() const
{
// Serverside reverse "mover" deduction logic at controlled unit
// Applies only to player controlled units
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
{
// Charm always removes control from original client...
if (ObjectGuid const& charmerGuid = GetCharmerGuid())
{
// ... but if it is a possessing charm, some other client may have control
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED))
{
Unit const* charmer = ObjectAccessor::GetUnit(*this, charmerGuid);
if (charmer && charmer->GetTypeId() == TYPEID_PLAYER)
return static_cast<Player const*>(charmer);
}
}
else if(GetTypeId() == TYPEID_PLAYER)
{
// Check if anything prevents original client from controlling
Player const* player = static_cast<Player const*>(this);
if (player->IsClientControl(player))
return player;
}
}
return nullptr;
}

Unit* Unit::GetSpawner() const
{
if (ObjectGuid guid = GetSpawnerGuid())
Expand Down Expand Up @@ -11162,11 +11190,9 @@ void Unit::SetIncapacitatedState(bool apply, uint32 state, ObjectGuid casterGuid
if (!state || !(state & filter) || (state & ~filter))
return;

Player* controller = GetBeneficiaryPlayer();
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);
const bool stun = (state & UNIT_FLAG_STUNNED);
const bool fleeing = (state & UNIT_FLAG_FLEEING);

if (apply)
{
Expand All @@ -11177,11 +11203,28 @@ void Unit::SetIncapacitatedState(bool apply, uint32 state, ObjectGuid casterGuid
else
state &= ~UNIT_FLAG_FLEEING;
}

// Apply confusion or fleeing: update client control state before altering flags
if (state & (UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING))
{
if (const Player* controllingClientPlayer = GetControllingClientPlayer())
controllingClientPlayer->UpdateClientControl(this, false);
}

SetFlag(UNIT_FIELD_FLAGS, state);
}
else
{
RemoveFlag(UNIT_FIELD_FLAGS, state);

// Remove confusion or fleeing: update client control state after altering flags
if (state & (UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING))
{
if (const Player* controllingClientPlayer = GetControllingClientPlayer())
controllingClientPlayer->UpdateClientControl(this, true);
}
}

if (movement)
GetMotionMaster()->MovementExpired(false);
if (apply)
Expand Down Expand Up @@ -11221,16 +11264,6 @@ void Unit::SetIncapacitatedState(bool apply, uint32 state, ObjectGuid casterGuid
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))
{
Expand Down Expand Up @@ -12321,7 +12354,8 @@ Unit* Unit::TakePossessOf(SpellEntry const* spellEntry, SummonPropertiesEntry co
if (player)
{
player->GetCamera().SetView(pCreature); // modify camera view to the creature view
player->SetClientControl(pCreature, 1); // transfer client control to the creature
// Force client control (required to function propely)
player->UpdateClientControl(pCreature, true, true); // transfer client control to the creature after altering flags
player->SetMover(pCreature); // set mover so now we know that creature is "moved" by this unit
player->SendForcedObjectUpdate(); // we have to update client data here to avoid problem with the "release spirit" windows reappear.

Expand All @@ -12344,6 +12378,10 @@ Unit* Unit::TakePossessOf(SpellEntry const* spellEntry, SummonPropertiesEntry co
if (CharmInfo* charmInfo = pCreature->InitCharmInfo(pCreature))
charmInfo->InitPossessCreateSpells();
player->PossessSpellInitialize();

// Take away client control immediately if we are not supposed to have control at the moment
if (!player->IsClientControl(pCreature))
player->UpdateClientControl(pCreature, false);
}

// Creature Linking, Initial load is handled like respawn
Expand All @@ -12360,6 +12398,10 @@ bool Unit::TakePossessOf(Unit* possessed)
if (GetTypeId() == TYPEID_PLAYER)
player = static_cast<Player *>(this);

// Update possessed's client control status before altering flags
if (const Player* controllingClientPlayer = possessed->GetControllingClientPlayer())
controllingClientPlayer->UpdateClientControl(possessed, false);

// stop combat but keep threat list
possessed->AttackStop(true, true);
possessed->ClearInCombat();
Expand Down Expand Up @@ -12404,7 +12446,8 @@ bool Unit::TakePossessOf(Unit* possessed)
if (player)
{
player->GetCamera().SetView(possessed);
player->SetClientControl(possessed, player->IsClientControl(possessed));
// Force client control (required to function propely)
player->UpdateClientControl(possessed, true, true);
player->SetMover(possessed);
player->SendForcedObjectUpdate();

Expand All @@ -12426,10 +12469,11 @@ bool Unit::TakePossessOf(Unit* possessed)
possessed->AI()->SetReactState(REACT_PASSIVE);
charmInfo->SetCommandState(COMMAND_STAY);
player->PossessSpellInitialize();
}

if (possessedPlayer)
possessedPlayer->SetClientControl(possessed, 0);
// Take away client control immediately if we are not supposed to have control at the moment
if (!player->IsClientControl(possessed))
player->UpdateClientControl(possessed, false);
}

return true;
}
Expand All @@ -12446,6 +12490,10 @@ bool Unit::TakeCharmOf(Unit* charmed)
charmerPlayer->UnsummonPetTemporaryIfAny();
}

// Update charmed's client control status before altering flags
if (const Player* controllingClientPlayer = charmed->GetControllingClientPlayer())
controllingClientPlayer->UpdateClientControl(charmed, false);

// stop combat but keep threat list
charmed->AttackStop(true, true);
charmed->ClearInCombat();
Expand All @@ -12472,7 +12520,6 @@ bool Unit::TakeCharmOf(Unit* charmed)
charmInfo->SetCommandState(COMMAND_FOLLOW);
charmInfo->SetIsRetreating(true);

charmedPlayer->SetClientControl(charmedPlayer, 0);
charmedPlayer->SendForcedObjectUpdate();
}
else if (charmed->GetTypeId() == TYPEID_UNIT)
Expand Down Expand Up @@ -12537,7 +12584,7 @@ void Unit::ResetControlState(bool attackCharmer /*= true*/)
if (player)
{
player->GetCamera().ResetView();
player->SetClientControl(player, player->IsClientControl(player));
player->UpdateClientControl(player, true);
player->SetMover(nullptr);
}
return;
Expand Down Expand Up @@ -12633,17 +12680,20 @@ void Unit::ResetControlState(bool attackCharmer /*= true*/)
else
possessedPlayer->setFactionForRace(possessedPlayer->getRace());

possessedPlayer->SetClientControl(possessedPlayer, possessedPlayer->IsClientControl(possessedPlayer));
charmInfo->ResetCharmState();
possessedPlayer->DeleteCharmInfo();

while (possessedPlayer->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE)
possessedPlayer->GetMotionMaster()->MovementExpired(true);
}

// Update possessed's client control status after altering flags
if (const Player* controllingClientPlayer = possessed->GetControllingClientPlayer())
controllingClientPlayer->UpdateClientControl(possessed, true);

if (player)
{
player->SetClientControl(possessed, 0);
player->UpdateClientControl(possessed, false);
player->SetMover(nullptr);
player->GetCamera().ResetView();

Expand Down
3 changes: 3 additions & 0 deletions src/game/Entities/Unit.h
Expand Up @@ -1882,6 +1882,9 @@ class Unit : public WorldObject
// Controlling player: limited recursive master/beneficiary (clientside)
Player const* GetControllingPlayer() const;

// Controlling client: movement control owner at the moment (serverside)
Player const* GetControllingClientPlayer() const;

Unit* GetSpawner() const; // serverside only logic used to determine spawner of unit

Unit* GetSummoner() const;
Expand Down
4 changes: 2 additions & 2 deletions src/game/Entities/Vehicle.cpp
Expand Up @@ -528,7 +528,7 @@ void VehicleInfo::ApplySeatMods(Unit* passenger, uint32 seatFlags)
pVehicle->addUnitState(UNIT_STAT_POSSESSED);
pVehicle->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED);

pPlayer->SetClientControl(pVehicle, 1);
pPlayer->UpdateClientControl(pVehicle, true);
pPlayer->SetMover(pVehicle);

// Unconfirmed - default speed handling
Expand Down Expand Up @@ -593,7 +593,7 @@ void VehicleInfo::RemoveSeatMods(Unit* passenger, uint32 seatFlags)
pPlayer->SetCharm(nullptr);
pVehicle->SetCharmerGuid(ObjectGuid());

pPlayer->SetClientControl(pVehicle, 0);
pPlayer->UpdateClientControl(pVehicle, false);
pPlayer->SetMover(nullptr);

pVehicle->clearUnitState(UNIT_STAT_POSSESSED);
Expand Down
4 changes: 2 additions & 2 deletions src/game/MotionGenerators/WaypointMovementGenerator.cpp
Expand Up @@ -395,7 +395,7 @@ void FlightPathMovementGenerator::Finalize(Player& player)

player.Unmount();
player.RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_MOVING_DEPRECATED | UNIT_FLAG_TAXI_FLIGHT);
player.SetClientControl(&player, 1);
player.UpdateClientControl(&player, true);

if (player.m_taxi.GetLastNode() == player.m_taxi.GetFinalTaxiDestination())
{
Expand Down Expand Up @@ -430,8 +430,8 @@ void FlightPathMovementGenerator::Reset(Player& player)
{
player.getHostileRefManager().setOnlineOfflineState(false);
player.addUnitState(UNIT_STAT_TAXI_FLIGHT);
player.UpdateClientControl(&player, false);
player.SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_MOVING_DEPRECATED | UNIT_FLAG_TAXI_FLIGHT);
player.SetClientControl(&player, 0);

Movement::MoveSplineInit init(player);
uint32 end = GetPathAtMapEnd();
Expand Down

1 comment on commit bd3b867

@xfurry
Copy link
Member

@xfurry xfurry commented on bd3b867 Dec 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Warlockbugs vehicle control is broken after this patch.
The mover control isn't passed to player anymore after boarding the vehicle.

PS. After unboarding the vehicle player still can't move. Surely there is a bug here.

Please sign in to comment.