Pet Battles: Fix all packet wire formats to match WoW 12.0 sniff

Analyzed a 12.0.1.66017 packet sniff using WowPacketParser and compared
every pet battle packet structure against our implementation. Fixed all
discrepancies to match Blizzard's actual wire format:

- SMSG_PET_BATTLE_FINALIZE_LOCATION: Replace single Position with full
  PetBattleLocation struct (LocationResult, BattleOrigin, BattleFacing,
  PlayerPositions[2])
- SMSG_PET_BATTLE_FINAL_ROUND: Completely restructured - no longer
  contains round data. Now contains Abandoned/PvpBattle flags, Winners,
  NpcCreatureIDs, and per-pet final results (PetBattleFinalPet)
- PetBattleEffectInfo: Add SourceAuraInstanceID (u16), TurnInstanceID
  (u16), fix CasterPBOID to uint8, reorder fields to match wire format
- PetBattleEffectTargetInfo: Type is now 3 bits (not uint8), variable-
  length params based on type (0-7 target types with 0-4 int32s each)
- PetBattleCooldownInfo: Add AbilityIndex and Pboid fields to match
  PetBattleAbilityInfo format (same wire structure from 6.2.4+)
- Round packets (FirstRound, RoundResult, ReplacementsMade): All three
  now share identical wire format via WriteRoundResult. Cooldowns moved
  from per-player to round-level flat array. Added PetXDied (3-bit
  count + PBOID entries)
- SMSG_PET_BATTLE_REPLACEMENTS_MADE: Now includes Effects (was missing)
- CMSG_PET_BATTLE_INPUT: MoveType changed from uint8 to int32, Round
  from uint8 to int32, added DebugFlags (int32) and BattleInterrupted
  (uint8) fields. Verified against raw hex: 19 bytes total
- CMSG_PET_BATTLE_REQUEST_WILD/PVP: Replace MovementInfo with proper
  PetBattleLocation struct matching WPP V6 parser
- SMSG_PET_BATTLE_PVP_CHALLENGE: Uses PetBattleLocation struct
This commit is contained in:
luis
2026-02-20 23:29:05 -03:00
parent bbbf99346b
commit fcc32587fa
4 changed files with 326 additions and 128 deletions
+5 -1
View File
@@ -549,7 +549,11 @@ namespace PetBattles
battle->Start();
WorldPackets::BattlePet::PetBattleFinalizeLocation finalizeLocation;
finalizeLocation.Location = p1->GetPosition();
finalizeLocation.Location.LocationResult = 0;
finalizeLocation.Location.BattleOrigin = p1->GetPosition();
finalizeLocation.Location.BattleFacing = p1->GetAbsoluteAngle(p2);
finalizeLocation.Location.PlayerPositions[0] = p1->GetPosition();
finalizeLocation.Location.PlayerPositions[1] = p2->GetPosition();
p1->SendDirectMessage(finalizeLocation.Write());
p2->SendDirectMessage(finalizeLocation.Write());
}
+157 -34
View File
@@ -211,22 +211,43 @@ static void BuildPetBattleRoundPlayerData(WorldPackets::BattlePet::PetBattleRoun
roundData.NextInputFlags = team.InputFlags;
roundData.NextTrapStatus = battle ? static_cast<int8>(battle->GetTrapStatus(teamIdx)) : static_cast<int8>(team.TrapStatus);
roundData.RoundTimeSecs = PetBattles::PET_BATTLE_MAX_ROUND_TIME;
}
for (uint8 i = 0; i < team.PetCount; ++i)
static void BuildRoundCooldowns(std::vector<WorldPackets::BattlePet::PetBattleCooldownInfo>& cooldowns,
PetBattles::PetBattle const* battle)
{
for (uint8 t = 0; t < PetBattles::MAX_PET_BATTLE_PLAYERS; ++t)
{
std::vector<WorldPackets::BattlePet::PetBattleCooldownInfo> petCds;
for (uint8 j = 0; j < PetBattles::MAX_PET_BATTLE_ABILITIES; ++j)
PetBattles::PetBattleTeamData const& team = battle->GetTeam(t);
for (uint8 i = 0; i < team.PetCount; ++i)
{
if (team.Pets[i].AbilityIDs[j] == 0)
continue;
for (uint8 j = 0; j < PetBattles::MAX_PET_BATTLE_ABILITIES; ++j)
{
if (team.Pets[i].AbilityIDs[j] == 0)
continue;
WorldPackets::BattlePet::PetBattleCooldownInfo cd;
cd.AbilityID = team.Pets[i].AbilityIDs[j];
cd.CooldownRemaining = team.Pets[i].AbilityCooldowns[j];
cd.LockdownRemaining = team.Pets[i].AbilityLockdowns[j];
petCds.push_back(cd);
WorldPackets::BattlePet::PetBattleCooldownInfo cd;
cd.AbilityID = team.Pets[i].AbilityIDs[j];
cd.CooldownRemaining = team.Pets[i].AbilityCooldowns[j];
cd.LockdownRemaining = team.Pets[i].AbilityLockdowns[j];
cd.AbilityIndex = static_cast<int8>(j);
cd.Pboid = t * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + i;
cooldowns.push_back(cd);
}
}
}
}
static void BuildPetXDied(std::vector<int8>& petXDied, PetBattles::PetBattle const* battle)
{
for (uint8 t = 0; t < PetBattles::MAX_PET_BATTLE_PLAYERS; ++t)
{
PetBattles::PetBattleTeamData const& team = battle->GetTeam(t);
for (uint8 i = 0; i < team.PetCount; ++i)
{
if (battle->WasPetKilledThisRound(t, i))
petXDied.push_back(static_cast<int8>(t * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + i));
}
roundData.PetCooldowns.push_back(std::move(petCds));
}
}
@@ -238,16 +259,65 @@ static void BuildRoundEffects(std::vector<WorldPackets::BattlePet::PetBattleEffe
WorldPackets::BattlePet::PetBattleEffectInfo effect;
effect.AbilityEffectID = roundEffect.AbilityEffectID;
effect.Flags = roundEffect.Flags;
effect.PetBattleEffectType = roundEffect.EffectType;
effect.CasterPBOID = static_cast<int8>(roundEffect.SourceTeam * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + roundEffect.SourcePet);
effect.SourceAuraInstanceID = 0;
effect.TurnInstanceID = 0;
effect.PetBattleEffectType = static_cast<int8>(roundEffect.EffectType);
effect.CasterPBOID = static_cast<uint8>(roundEffect.SourceTeam * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + roundEffect.SourcePet);
effect.StackDepth = 0;
WorldPackets::BattlePet::PetBattleEffectTargetInfo target;
target.Type = roundEffect.EffectType;
target.Petx = static_cast<uint8>(roundEffect.TargetTeam * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + roundEffect.TargetPet);
target.Param1 = roundEffect.Param1;
target.Param2 = roundEffect.Param2;
effect.Targets.push_back(target);
// Map effect type to target type and variable-length params
// Target types: 0=none, 1=aura(4 i32), 2=state(2 i32), 3=health(1 i32),
// 4=stat(1 i32), 5=trigger(1 i32), 6=cooldown(3 i32), 7=broadcast(1 i32)
switch (roundEffect.EffectType)
{
case PetBattles::PET_BATTLE_EFFECT_SET_HEALTH:
case PetBattles::PET_BATTLE_EFFECT_SET_MAX_HEALTH:
target.Type = 3; // Health
target.Params.push_back(roundEffect.Param1);
break;
case PetBattles::PET_BATTLE_EFFECT_AURA_APPLY:
case PetBattles::PET_BATTLE_EFFECT_AURA_CANCEL:
case PetBattles::PET_BATTLE_EFFECT_AURA_CHANGE:
target.Type = 1; // Aura
target.Params.push_back(roundEffect.Param1); // AuraInstanceID
target.Params.push_back(roundEffect.Param2); // AuraAbilityID
target.Params.push_back(0); // RoundsRemaining
target.Params.push_back(0); // CurrentRound
break;
case PetBattles::PET_BATTLE_EFFECT_SET_STATE:
target.Type = 2; // State
target.Params.push_back(roundEffect.Param1); // StateID
target.Params.push_back(roundEffect.Param2); // StateValue
break;
case PetBattles::PET_BATTLE_EFFECT_STATUS_CHANGE:
case PetBattles::PET_BATTLE_EFFECT_SET_SPEED:
case PetBattles::PET_BATTLE_EFFECT_SET_POWER:
target.Type = 4; // NewStatValue
target.Params.push_back(roundEffect.Param1);
break;
case PetBattles::PET_BATTLE_EFFECT_TRIGGER_ABILITY:
target.Type = 5; // TriggerAbilityID
target.Params.push_back(roundEffect.Param1);
break;
case PetBattles::PET_BATTLE_EFFECT_ABILITY_CHANGE:
target.Type = 6; // Cooldown
target.Params.push_back(roundEffect.Param1); // ChangedAbilityID
target.Params.push_back(roundEffect.Param2); // CooldownRemaining
target.Params.push_back(0); // LockdownRemaining
break;
case PetBattles::PET_BATTLE_EFFECT_NPC_EMOTE:
target.Type = 7; // BroadcastTextID
target.Params.push_back(roundEffect.Param1);
break;
default:
target.Type = 0; // No data
break;
}
effect.Targets.push_back(target);
effectList.push_back(std::move(effect));
}
}
@@ -330,7 +400,11 @@ void WorldSession::HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattle
// Send finalize location
WorldPackets::BattlePet::PetBattleFinalizeLocation finalizeLocation;
finalizeLocation.Location = player->GetPosition();
finalizeLocation.Location.LocationResult = 0;
finalizeLocation.Location.BattleOrigin = player->GetPosition();
finalizeLocation.Location.BattleFacing = player->GetAbsoluteAngle(creature);
finalizeLocation.Location.PlayerPositions[0] = player->GetPosition();
finalizeLocation.Location.PlayerPositions[1] = creature->GetPosition();
SendPacket(finalizeLocation.Write());
// Build and send initial update
@@ -391,18 +465,37 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
{
// Send final round
WorldPackets::BattlePet::PetBattleFinalRound finalRound;
finalRound.CurRound = battle->GetCurrentRound();
finalRound.NextPetBattleState = static_cast<uint8>(battle->GetBattleState());
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
BuildPetBattleRoundPlayerData(finalRound.Players[i], battle->GetTeam(i), battle, i);
BuildRoundEffects(finalRound.Effects, battle);
finalRound.Abandoned = false;
finalRound.PvpBattle = (battle->GetBattleType() == PetBattles::PET_BATTLE_TYPE_PVP || battle->GetBattleType() == PetBattles::PET_BATTLE_TYPE_LFPB);
finalRound.Winners[battle->GetWinnerTeam()] = true;
// Send to player team 1
for (uint8 t = 0; t < PetBattles::MAX_PET_BATTLE_PLAYERS; ++t)
{
PetBattles::PetBattleTeamData const& team = battle->GetTeam(t);
if (battle->GetBattleType() == PetBattles::PET_BATTLE_TYPE_NPC && t == PetBattles::PET_BATTLE_TEAM_2)
finalRound.NpcCreatureID[t] = team.Pets[0].CreatureID;
for (uint8 i = 0; i < team.PetCount; ++i)
{
WorldPackets::BattlePet::PetBattleFinalPet pet;
pet.Guid = team.Pets[i].BattlePetGUID;
pet.Level = team.Pets[i].Level;
pet.Xp = team.Pets[i].Xp;
pet.Health = team.Pets[i].Health;
pet.MaxHealth = team.Pets[i].MaxHealth;
pet.InitialLevel = team.Pets[i].Level; // TODO: track initial level before XP awards
pet.Pboid = t * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + i;
pet.Captured = team.Pets[i].IsCaptured;
pet.Caged = false;
pet.SeenAction = true;
pet.AwardedXP = battle->CanAwardXP() && (battle->GetWinnerTeam() == t);
finalRound.Pets.push_back(pet);
}
}
if (Player* p1 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_1))
p1->SendDirectMessage(finalRound.Write());
// Send to player team 2 (PvP)
if (Player* p2 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_2))
p2->SendDirectMessage(finalRound.Write());
@@ -438,10 +531,12 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
{
WorldPackets::BattlePet::PetBattleFirstRound firstRound;
firstRound.CurRound = battle->GetCurrentRound();
firstRound.NextPetBattleState = static_cast<uint8>(battle->GetBattleState());
firstRound.NextPetBattleState = static_cast<int8>(battle->GetBattleState());
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
BuildPetBattleRoundPlayerData(firstRound.Players[i], battle->GetTeam(i), battle, i);
BuildRoundEffects(firstRound.Effects, battle);
BuildRoundCooldowns(firstRound.Cooldowns, battle);
BuildPetXDied(firstRound.PetXDied, battle);
if (Player* p1 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_1))
p1->SendDirectMessage(firstRound.Write());
@@ -452,10 +547,12 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
{
WorldPackets::BattlePet::PetBattleRoundResult roundResult;
roundResult.CurRound = battle->GetCurrentRound();
roundResult.NextPetBattleState = static_cast<uint8>(battle->GetBattleState());
roundResult.NextPetBattleState = static_cast<int8>(battle->GetBattleState());
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
BuildPetBattleRoundPlayerData(roundResult.Players[i], battle->GetTeam(i), battle, i);
BuildRoundEffects(roundResult.Effects, battle);
BuildRoundCooldowns(roundResult.Cooldowns, battle);
BuildPetXDied(roundResult.PetXDied, battle);
if (Player* p1 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_1))
p1->SendDirectMessage(roundResult.Write());
@@ -495,9 +592,12 @@ void WorldSession::HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBa
WorldPackets::BattlePet::PetBattleReplacementsMade replacements;
replacements.CurRound = battle->GetCurrentRound();
replacements.NextPetBattleState = static_cast<uint8>(battle->GetBattleState());
replacements.NextPetBattleState = static_cast<int8>(battle->GetBattleState());
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
BuildPetBattleRoundPlayerData(replacements.Players[i], battle->GetTeam(i), battle, i);
BuildRoundEffects(replacements.Effects, battle);
BuildRoundCooldowns(replacements.Cooldowns, battle);
BuildPetXDied(replacements.PetXDied, battle);
if (Player* p1 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_1))
p1->SendDirectMessage(replacements.Write());
@@ -530,12 +630,31 @@ void WorldSession::HandlePetBattleQuitNotify(WorldPackets::BattlePet::PetBattleQ
// Send final round to both players
WorldPackets::BattlePet::PetBattleFinalRound finalRound;
finalRound.CurRound = battle->GetCurrentRound();
finalRound.NextPetBattleState = static_cast<uint8>(battle->GetBattleState());
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
BuildPetBattleRoundPlayerData(finalRound.Players[i], battle->GetTeam(i), battle, i);
finalRound.Abandoned = true;
finalRound.PvpBattle = (battle->GetBattleType() == PetBattles::PET_BATTLE_TYPE_PVP || battle->GetBattleType() == PetBattles::PET_BATTLE_TYPE_LFPB);
finalRound.Winners[battle->GetWinnerTeam()] = true;
for (uint8 t = 0; t < PetBattles::MAX_PET_BATTLE_PLAYERS; ++t)
{
PetBattles::PetBattleTeamData const& team = battle->GetTeam(t);
for (uint8 i = 0; i < team.PetCount; ++i)
{
WorldPackets::BattlePet::PetBattleFinalPet pet;
pet.Guid = team.Pets[i].BattlePetGUID;
pet.Level = team.Pets[i].Level;
pet.Xp = team.Pets[i].Xp;
pet.Health = team.Pets[i].Health;
pet.MaxHealth = team.Pets[i].MaxHealth;
pet.InitialLevel = team.Pets[i].Level;
pet.Pboid = t * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + i;
pet.Captured = team.Pets[i].IsCaptured;
pet.Caged = false;
pet.SeenAction = true;
pet.AwardedXP = false;
finalRound.Pets.push_back(pet);
}
}
if (Player* p1 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_1))
p1->SendDirectMessage(finalRound.Write());
if (Player* p2 = battle->GetPlayerForTeam(PetBattles::PET_BATTLE_TEAM_2))
@@ -655,7 +774,11 @@ void WorldSession::HandlePetBattleRequestPVP(WorldPackets::BattlePet::PetBattleR
// Send PVP challenge to target
WorldPackets::BattlePet::PetBattlePVPChallenge challenge;
challenge.ChallengerGUID = player->GetGUID();
challenge.BattleOrigin = player->GetPosition();
challenge.Location.LocationResult = 0;
challenge.Location.BattleOrigin = player->GetPosition();
challenge.Location.BattleFacing = player->GetAbsoluteAngle(target);
challenge.Location.PlayerPositions[0] = player->GetPosition();
challenge.Location.PlayerPositions[1] = target->GetPosition();
target->SendDirectMessage(challenge.Write());
}
@@ -16,7 +16,6 @@
*/
#include "BattlePetPackets.h"
#include "MovementPackets.h"
#include "PacketOperators.h"
namespace WorldPackets::BattlePet
@@ -327,10 +326,13 @@ namespace WorldPackets::BattlePet
ByteBuffer& operator<<(ByteBuffer& data, PetBattleEffectTargetInfo const& target)
{
data << uint8(target.Type);
data << Bits<3>(target.Type);
data.FlushBits();
data << uint8(target.Petx);
data << int32(target.Param1);
data << int32(target.Param2);
for (int32 param : target.Params)
data << int32(param);
return data;
}
@@ -339,10 +341,11 @@ namespace WorldPackets::BattlePet
{
data << uint32(effect.AbilityEffectID);
data << uint16(effect.Flags);
data << uint8(effect.SourceAuraPetInstance);
data << uint16(effect.SourceAuraInstanceID);
data << uint16(effect.TurnInstanceID);
data << int8(effect.PetBattleEffectType);
data << uint8(effect.CasterPBOID);
data << uint8(effect.StackDepth);
data << uint8(effect.PetBattleEffectType);
data << int8(effect.CasterPBOID);
data << uint32(effect.Targets.size());
for (PetBattleEffectTargetInfo const& target : effect.Targets)
@@ -356,46 +359,76 @@ namespace WorldPackets::BattlePet
data << int32(cd.AbilityID);
data << int16(cd.CooldownRemaining);
data << int16(cd.LockdownRemaining);
data << int8(cd.AbilityIndex);
data << uint8(cd.Pboid);
return data;
}
static void WriteRoundPlayerData(ByteBuffer& data, PetBattleRoundPlayerData const& player)
// Writes the common round result wire format shared by FirstRound, RoundResult, and ReplacementsMade
static void WriteRoundResult(ByteBuffer& data, uint32 curRound, int8 nextPetBattleState,
std::array<PetBattleRoundPlayerData, 2> const& players,
std::vector<PetBattleEffectInfo> const& effects,
std::vector<PetBattleCooldownInfo> const& cooldowns,
std::vector<int8> const& petXDied)
{
data << uint8(player.NextInputFlags);
data << int8(player.NextTrapStatus);
data << uint16(player.RoundTimeSecs);
data << uint32(curRound);
data << int8(nextPetBattleState);
data << uint32(player.PetCooldowns.size());
for (std::vector<PetBattleCooldownInfo> const& petCds : player.PetCooldowns)
{
data << uint32(petCds.size());
for (PetBattleCooldownInfo const& cd : petCds)
data << cd;
}
}
static void WriteRoundEffects(ByteBuffer& data, std::vector<PetBattleEffectInfo> const& effects)
{
data << uint32(effects.size());
for (std::size_t i = 0; i < 2; ++i)
{
data << uint8(players[i].NextInputFlags);
data << int8(players[i].NextTrapStatus);
data << uint16(players[i].RoundTimeSecs);
}
data << uint32(cooldowns.size());
for (PetBattleEffectInfo const& effect : effects)
data << effect;
for (PetBattleCooldownInfo const& cd : cooldowns)
data << cd;
data << Bits<3>(petXDied.size());
data.FlushBits();
for (int8 pboid : petXDied)
data << int8(pboid);
}
// ---- CMSG Read() implementations ----
static void ReadPetBattleLocation(ByteBuffer& data, PetBattleLocation& loc)
{
data >> loc.LocationResult;
data >> loc.BattleOrigin.m_positionX;
data >> loc.BattleOrigin.m_positionY;
data >> loc.BattleOrigin.m_positionZ;
data >> loc.BattleFacing;
for (std::size_t i = 0; i < 2; ++i)
{
data >> loc.PlayerPositions[i].m_positionX;
data >> loc.PlayerPositions[i].m_positionY;
data >> loc.PlayerPositions[i].m_positionZ;
}
}
void PetBattleRequestWild::Read()
{
_worldPacket >> TargetGUID;
_worldPacket >> LocationInfo;
BattleOrigin = LocationInfo.pos;
ReadPetBattleLocation(_worldPacket, Location);
}
void PetBattleInput::Read()
{
_worldPacket >> MoveType;
_worldPacket >> AbilityID;
_worldPacket >> NewFrontPetIndex;
_worldPacket >> DebugFlags;
_worldPacket >> BattleInterrupted;
_worldPacket >> AbilityID;
_worldPacket >> Round;
_worldPacket >> Bits<1>(IgnoreAbandonPenalty);
}
@@ -408,8 +441,7 @@ namespace WorldPackets::BattlePet
void PetBattleRequestPVP::Read()
{
_worldPacket >> TargetGUID;
_worldPacket >> LocationInfo;
BattleOrigin = LocationInfo.pos;
ReadPetBattleLocation(_worldPacket, Location);
}
void PetBattleQueueProposeMatchResult::Read()
@@ -425,12 +457,24 @@ namespace WorldPackets::BattlePet
// ---- SMSG Write() implementations ----
static void WritePetBattleLocation(ByteBuffer& data, PetBattleLocation const& loc)
{
data << int32(loc.LocationResult);
data << loc.BattleOrigin.m_positionX;
data << loc.BattleOrigin.m_positionY;
data << loc.BattleOrigin.m_positionZ;
data << loc.BattleFacing;
for (std::size_t i = 0; i < 2; ++i)
{
data << loc.PlayerPositions[i].m_positionX;
data << loc.PlayerPositions[i].m_positionY;
data << loc.PlayerPositions[i].m_positionZ;
}
}
WorldPacket const* PetBattleFinalizeLocation::Write()
{
_worldPacket << Location.m_positionX;
_worldPacket << Location.m_positionY;
_worldPacket << Location.m_positionZ;
_worldPacket << Location.GetOrientation();
WritePetBattleLocation(_worldPacket, Location);
return &_worldPacket;
}
@@ -474,59 +518,59 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleFirstRound::Write()
{
_worldPacket << uint32(CurRound);
_worldPacket << uint8(NextPetBattleState);
for (std::size_t i = 0; i < 2; ++i)
WriteRoundPlayerData(_worldPacket, Players[i]);
WriteRoundEffects(_worldPacket, Effects);
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
return &_worldPacket;
}
WorldPacket const* PetBattleRoundResult::Write()
{
_worldPacket << uint32(CurRound);
_worldPacket << uint8(NextPetBattleState);
for (std::size_t i = 0; i < 2; ++i)
WriteRoundPlayerData(_worldPacket, Players[i]);
WriteRoundEffects(_worldPacket, Effects);
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
return &_worldPacket;
}
WorldPacket const* PetBattleReplacementsMade::Write()
{
_worldPacket << uint32(CurRound);
_worldPacket << uint8(NextPetBattleState);
for (std::size_t i = 0; i < 2; ++i)
WriteRoundPlayerData(_worldPacket, Players[i]);
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
return &_worldPacket;
}
ByteBuffer& operator<<(ByteBuffer& data, PetBattleFinalPet const& pet)
{
data << pet.Guid;
data << uint16(pet.Level);
data << uint16(pet.Xp);
data << int32(pet.Health);
data << int32(pet.MaxHealth);
data << uint16(pet.InitialLevel);
data << uint8(pet.Pboid);
data << Bits<1>(pet.Captured);
data << Bits<1>(pet.Caged);
data << Bits<1>(pet.SeenAction);
data << Bits<1>(pet.AwardedXP);
data.FlushBits();
return data;
}
WorldPacket const* PetBattleFinalRound::Write()
{
_worldPacket << uint32(CurRound);
_worldPacket << uint8(NextPetBattleState);
for (std::size_t i = 0; i < 2; ++i)
WriteRoundPlayerData(_worldPacket, Players[i]);
WriteRoundEffects(_worldPacket, Effects);
_worldPacket << Bits<1>(AbandonedPVP);
_worldPacket << Bits<1>(PVPWinner);
_worldPacket << Bits<1>(Abandoned);
_worldPacket << Bits<1>(PvpBattle);
for (std::size_t i = 0; i < 2; ++i)
_worldPacket << Bits<1>(Winners[i]);
_worldPacket.FlushBits();
for (std::size_t i = 0; i < 2; ++i)
_worldPacket << uint32(NpcCreatureID[i]);
_worldPacket << int32(NpcCreatureID[i]);
_worldPacket << uint32(Pets.size());
for (PetBattleFinalPet const& pet : Pets)
_worldPacket << pet;
return &_worldPacket;
}
@@ -541,10 +585,7 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattlePVPChallenge::Write()
{
_worldPacket << ChallengerGUID;
_worldPacket << BattleOrigin.m_positionX;
_worldPacket << BattleOrigin.m_positionY;
_worldPacket << BattleOrigin.m_positionZ;
_worldPacket << BattleOrigin.GetOrientation();
WritePetBattleLocation(_worldPacket, Location);
return &_worldPacket;
}
@@ -20,7 +20,6 @@
#include "Packet.h"
#include "PacketUtilities.h"
#include "MovementInfo.h"
#include "ObjectGuid.h"
#include "Optional.h"
#include "Position.h"
@@ -349,20 +348,23 @@ namespace WorldPackets
struct PetBattleEffectTargetInfo
{
uint8 Type = 0;
uint8 Type = 0; // Written as 3 bits + ResetBitReader
uint8 Petx = 0;
int32 Param1 = 0;
int32 Param2 = 0;
// Variable-length params based on Type:
// 0: nothing, 1: 4 int32s (aura), 2: 2 int32s (state), 3: 1 int32 (health),
// 4: 1 int32 (stat), 5: 1 int32 (trigger), 6: 3 int32s (cooldown), 7: 1 int32 (broadcast)
std::vector<int32> Params;
};
struct PetBattleEffectInfo
{
uint32 AbilityEffectID = 0;
uint16 Flags = 0;
uint8 SourceAuraPetInstance = 0;
uint16 SourceAuraInstanceID = 0;
uint16 TurnInstanceID = 0;
int8 PetBattleEffectType = 0;
uint8 CasterPBOID = 0;
uint8 StackDepth = 0;
uint8 PetBattleEffectType = 0;
int8 CasterPBOID = 0;
std::vector<PetBattleEffectTargetInfo> Targets;
};
@@ -371,6 +373,8 @@ namespace WorldPackets
int32 AbilityID = 0;
int16 CooldownRemaining = 0;
int16 LockdownRemaining = 0;
int8 AbilityIndex = 0;
uint8 Pboid = 0;
};
struct PetBattleRoundPlayerData
@@ -378,7 +382,15 @@ namespace WorldPackets
uint8 NextInputFlags = 0;
int8 NextTrapStatus = 0;
uint16 RoundTimeSecs = 0;
std::vector<std::vector<PetBattleCooldownInfo>> PetCooldowns; // per pet
};
// Shared location structure used by multiple pet battle packets
struct PetBattleLocation
{
int32 LocationResult = 0;
Position BattleOrigin; // Only XYZ written
float BattleFacing = 0.0f;
std::array<Position, 2> PlayerPositions; // Only XYZ written
};
// ---- CMSG packets ----
@@ -391,8 +403,7 @@ namespace WorldPackets
void Read() override;
ObjectGuid TargetGUID;
MovementInfo LocationInfo;
Position BattleOrigin;
PetBattleLocation Location;
};
class PetBattleInput final : public ClientPacket
@@ -402,10 +413,12 @@ namespace WorldPackets
void Read() override;
uint8 MoveType = 0;
int32 AbilityID = 0;
int32 MoveType = 0;
int8 NewFrontPetIndex = -1;
uint8 Round = 0;
int32 DebugFlags = 0;
uint8 BattleInterrupted = 0;
int32 AbilityID = 0;
int32 Round = 0;
bool IgnoreAbandonPenalty = false;
};
@@ -443,8 +456,7 @@ namespace WorldPackets
void Read() override;
ObjectGuid TargetGUID;
MovementInfo LocationInfo;
Position BattleOrigin;
PetBattleLocation Location;
};
class JoinPetBattleQueue final : public ClientPacket
@@ -509,7 +521,7 @@ namespace WorldPackets
WorldPacket const* Write() override;
Position Location;
PetBattleLocation Location;
};
class PetBattleInitialUpdate final : public ServerPacket
@@ -541,9 +553,11 @@ namespace WorldPackets
WorldPacket const* Write() override;
uint32 CurRound = 0;
uint8 NextPetBattleState = 0;
int8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
std::vector<PetBattleEffectInfo> Effects;
std::vector<PetBattleCooldownInfo> Cooldowns;
std::vector<int8> PetXDied;
};
class PetBattleRoundResult final : public ServerPacket
@@ -554,9 +568,11 @@ namespace WorldPackets
WorldPacket const* Write() override;
uint32 CurRound = 0;
uint8 NextPetBattleState = 0;
int8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
std::vector<PetBattleEffectInfo> Effects;
std::vector<PetBattleCooldownInfo> Cooldowns;
std::vector<int8> PetXDied;
};
class PetBattleReplacementsMade final : public ServerPacket
@@ -567,8 +583,26 @@ namespace WorldPackets
WorldPacket const* Write() override;
uint32 CurRound = 0;
uint8 NextPetBattleState = 0;
int8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
std::vector<PetBattleEffectInfo> Effects;
std::vector<PetBattleCooldownInfo> Cooldowns;
std::vector<int8> PetXDied;
};
struct PetBattleFinalPet
{
ObjectGuid Guid;
uint16 Level = 0;
uint16 Xp = 0;
int32 Health = 0;
int32 MaxHealth = 0;
uint16 InitialLevel = 0;
uint8 Pboid = 0;
bool Captured = false;
bool Caged = false;
bool SeenAction = false;
bool AwardedXP = false;
};
class PetBattleFinalRound final : public ServerPacket
@@ -578,15 +612,11 @@ namespace WorldPackets
WorldPacket const* Write() override;
uint32 CurRound = 0;
uint8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
std::vector<PetBattleEffectInfo> Effects;
// Final state includes flags
bool AbandonedPVP = false;
bool PVPWinner = false;
bool Abandoned = false;
bool PvpBattle = false;
std::array<bool, 2> Winners = {};
std::array<uint32, 2> NpcCreatureID = {};
std::array<int32, 2> NpcCreatureID = {};
std::vector<PetBattleFinalPet> Pets;
};
class PetBattleFinished final : public ServerPacket
@@ -615,7 +645,7 @@ namespace WorldPackets
WorldPacket const* Write() override;
ObjectGuid ChallengerGUID;
Position BattleOrigin;
PetBattleLocation Location;
};
class PetBattleQueueStatus final : public ServerPacket