Start rework battlepets for 12.1

This commit is contained in:
luis
2026-09-10 22:41:35 -03:00
parent cb66441cae
commit fdf2335ab1
13 changed files with 688 additions and 771 deletions
+5 -3
View File
@@ -52,9 +52,11 @@ namespace BattlePets
if (breedState == _battlePetBreedStates.end()) // non existing breed id
return;
float health = breedState->second[STATE_STAT_STAMINA];
float power = breedState->second[STATE_STAT_POWER];
float speed = breedState->second[STATE_STAT_SPEED];
// double: the float32 DB2 quality multiplier (0.65f = 0.6499999761) must not be re-rounded
// (retail: species 1959 breed 16 q3 L1 = 171 HP, float locals give 172)
double health = breedState->second[STATE_STAT_STAMINA];
double power = breedState->second[STATE_STAT_POWER];
double speed = breedState->second[STATE_STAT_SPEED];
// modify stats depending on species - not all pets have this
auto speciesState = _battlePetSpeciesStates.find(PacketInfo.Species);
+1 -1
View File
@@ -99,7 +99,7 @@ enum FlagsControlType
FLAGS_CONTROL_TYPE_REMOVE = 2
};
// BattlePetState.db2 — LuaName-based IDs (verified via wago.tools/WoWDBDefs)
// BattlePetState.db2 ? LuaName-based IDs (verified via wago.tools/WoWDBDefs)
enum BattlePetState
{
STATE_IS_DEAD = 1,
+38 -46
View File
@@ -27,10 +27,10 @@
#include "PetBattleMgr.h"
#include "Player.h"
#include "Random.h"
#include "SceneObject.h"
#include "Util.h"
#include "WorldSession.h"
#include <algorithm>
#include <functional>
namespace PetBattles
{
@@ -278,14 +278,14 @@ namespace PetBattles
wildPet.BaseSpeed = baseSpeed;
// Use same formula as BattlePet::CalculateStats (DB2 values are scaled ~1000-2000)
float health = float(baseHP) * qualityMultiplier * wildPet.Level;
float power = float(basePower) * qualityMultiplier * wildPet.Level;
float speed = float(baseSpeed) * qualityMultiplier * wildPet.Level;
double health = double(baseHP) * qualityMultiplier * wildPet.Level;
double power = double(basePower) * qualityMultiplier * wildPet.Level;
double speed = double(baseSpeed) * qualityMultiplier * wildPet.Level;
wildPet.MaxHealth = int32(round(health / 20.0f) + 100);
wildPet.MaxHealth = int32(round(health / 20.0) + 100);
wildPet.Health = wildPet.MaxHealth;
wildPet.Power = int32(round(power / 100.0f));
wildPet.Speed = int32(round(speed / 100.0f));
wildPet.Power = int32(round(power / 100.0));
wildPet.Speed = int32(round(speed / 100.0));
wildPet.EffectivePower = wildPet.Power;
wildPet.EffectiveSpeed = wildPet.Speed;
@@ -338,14 +338,14 @@ namespace PetBattles
pet.Level = level;
float health = float(pet.BaseStamina) * qualityMultiplier * level;
float power = float(pet.BasePower) * qualityMultiplier * level;
float speed = float(pet.BaseSpeed) * qualityMultiplier * level;
double health = double(pet.BaseStamina) * qualityMultiplier * level;
double power = double(pet.BasePower) * qualityMultiplier * level;
double speed = double(pet.BaseSpeed) * qualityMultiplier * level;
pet.MaxHealth = int32(round(health / 20.0f) + 100);
pet.MaxHealth = int32(round(health / 20.0) + 100);
pet.Health = pet.MaxHealth; // PvP teams enter at full health
pet.Power = std::max(1, int32(round(power / 100.0f)));
pet.Speed = std::max(1, int32(round(speed / 100.0f)));
pet.Power = std::max(1, int32(round(power / 100.0)));
pet.Speed = std::max(1, int32(round(speed / 100.0)));
pet.EffectivePower = pet.Power;
pet.EffectiveSpeed = pet.Speed;
}
@@ -654,6 +654,7 @@ namespace PetBattles
{
PetBattleTeamData& team = _teams[teamIdx];
PetBattlePetData& activePet = team.Pets[team.FrontPetIndex];
activePet.SeenAction = true;
// Stunned pets skip their turn
if (activePet.IsStunned)
@@ -1685,8 +1686,8 @@ namespace PetBattles
{
DamageResult result;
// Formula: rawDamage = abilityPower * (attackerPower / 20.0f)
float rawDamage = abilityPower * (attackerPower / 20.0f);
// Formula (12.1.0.69587 wire, 25/25 hits): rawDamage = abilityPower * (1 + attackerPower / 20)
float rawDamage = abilityPower * (1.0f + attackerPower / 20.0f);
// Type effectiveness modifier
result.TypeMod = GetTypeEffectiveness(abilityType, PetBattlePetType(defender.PetType));
@@ -1742,13 +1743,15 @@ namespace PetBattles
if (attacker.PetType != PET_TYPE_ELEMENTAL)
critChance += _environments[PET_BATTLE_WEATHER_ENV_SLOT].GetState(BattlePets::STATE_STAT_CRIT_CHANCE) / 100.0f;
critChance = std::clamp(critChance, 0.0f, 1.0f);
// Retail truncates the modified damage, then applies the crit multiplier and truncates again
// (wire: 16.5 -> 16, 475.5 -> 475; crit 5 -> 7, 35 -> 52)
int32 damage = int32(std::floor(rawDamage));
if (frand(0.0f, 1.0f) < critChance)
{
rawDamage *= PET_BATTLE_CRIT_MULTIPLIER;
damage = int32(std::floor(damage * PET_BATTLE_CRIT_MULTIPLIER));
result.IsCrit = true;
}
int32 damage = std::max(1, int32(std::round(rawDamage)));
damage = std::max(1, damage);
// Flat damage taken modifier from environment (e.g. Sandstorm damage shield)
if (defender.PetType != PET_TYPE_ELEMENTAL)
@@ -1773,7 +1776,7 @@ namespace PetBattles
int32 PetBattle::CalculateAbilityHealing(int32 healPower, int32 attackerPower, PetBattlePetData const& healer)
{
float rawHealing = healPower * (attackerPower / 20.0f);
float rawHealing = healPower * (1.0f + attackerPower / 20.0f);
// Weather healing modifier from environment states (Elemental passive: ignores weather)
if (healer.PetType != PET_TYPE_ELEMENTAL)
@@ -1788,7 +1791,7 @@ namespace PetBattles
if (stateID == BattlePets::STATE_MOD_HEALING_DEALT_PERCENT && stateValue != 0)
rawHealing *= (1.0f + stateValue / 100.0f);
return std::max(1, int32(std::round(rawHealing)));
return std::max(1, int32(std::floor(rawHealing)));
}
// ============================================================================
@@ -2258,13 +2261,14 @@ namespace PetBattles
for (uint8 i = 0; i < loserTeam.PetCount; ++i)
maxOpponentLevel = std::max(maxOpponentLevel, loserTeam.Pets[i].Level);
// Award XP to all surviving pets on the winning team
// Award XP to every pet of the winning team that took the field (retail: a pet that died
// during the battle still gets XP, a pet that never fought gets none -- FINAL_ROUND SeenAction)
BattlePets::BattlePetMgr* petMgr = player->GetSession()->GetBattlePetMgr();
for (uint8 i = 0; i < winnerTeam.PetCount; ++i)
{
PetBattlePetData& pet = winnerTeam.Pets[i];
if (!pet.IsAlive() || pet.Level >= BattlePets::MAX_BATTLE_PET_LEVEL)
if (!pet.SeenAction || pet.Level >= BattlePets::MAX_BATTLE_PET_LEVEL)
continue;
if (pet.BattlePetGUID.IsEmpty())
@@ -2390,7 +2394,6 @@ namespace PetBattles
void PetBattle::SendFinalRoundPacket(bool abandoned)
{
WorldPackets::BattlePet::PetBattleFinalRound finalRound;
finalRound.SceneObjectGUID = _sceneObjectGUID;
finalRound.Abandoned = abandoned;
finalRound.PvpBattle = (_battleType == PET_BATTLE_TYPE_PVP || _battleType == PET_BATTLE_TYPE_LFPB);
// 12.0.7 (sniff-verified vs b_pets, 5 battles): the winner is the per-team flag pair in the FinalRound
@@ -2421,8 +2424,8 @@ namespace PetBattles
pet.Pboid = t * MAX_PET_BATTLE_TEAM_SIZE + i;
pet.Captured = team.Pets[i].IsCaptured;
pet.Caged = false;
pet.SeenAction = false;
pet.AwardedXP = _canAwardXP;
pet.SeenAction = team.Pets[i].SeenAction;
pet.AwardedXP = _canAwardXP && team.Pets[i].SeenAction && team.Pets[i].Level < BattlePets::MAX_BATTLE_PET_LEVEL;
finalRound.Pets.push_back(pet);
}
}
@@ -2478,7 +2481,6 @@ namespace PetBattles
// Send finished notification and sync pet health to journal
WorldPackets::BattlePet::PetBattleFinished finished;
finished.SceneObjectGUID = _sceneObjectGUID;
for (uint8 t = 0; t < MAX_PET_BATTLE_PLAYERS; ++t)
{
Player* teamPlayer = GetPlayerForTeam(t);
@@ -2489,12 +2491,20 @@ namespace PetBattles
BattlePets::BattlePetMgr* petMgr = teamPlayer->GetSession()->GetBattlePetMgr();
PetBattleTeamData const& team = _teams[t];
std::vector<std::reference_wrapper<BattlePets::BattlePet const>> changed;
for (uint8 p = 0; p < team.PetCount; ++p)
{
if (!team.Pets[p].BattlePetGUID.IsEmpty())
if (team.Pets[p].BattlePetGUID.IsEmpty())
continue;
petMgr->SyncBattlePetHealth(team.Pets[p].BattlePetGUID, team.Pets[p].Health);
if (BattlePets::BattlePet const* journalPet = petMgr->GetPet(team.Pets[p].BattlePetGUID))
changed.emplace_back(*journalPet);
}
petMgr->SendJournal();
// Retail (12.1.0.69587): after SMSG_PET_BATTLE_FINISHED the client receives
// SMSG_BATTLE_PET_UPDATES carrying only the pets whose health/xp/level changed --
// never a full SMSG_BATTLE_PET_JOURNAL resend (10 KB) at this point.
if (!changed.empty())
petMgr->SendUpdates(changed, false);
}
// NPC trainer post-battle: restore movement and play cry emote on loss
@@ -2699,24 +2709,6 @@ namespace PetBattles
return ObjectAccessor::FindPlayer(_teams[teamIdx].PlayerGUID);
}
void PetBattle::DespawnSceneObject()
{
if (_sceneObjectGUID.IsEmpty())
return;
for (uint8 t = 0; t < MAX_PET_BATTLE_PLAYERS; ++t)
{
if (Player* player = GetPlayerForTeam(t))
{
if (SceneObject* sceneObject = ObjectAccessor::GetSceneObject(*player, _sceneObjectGUID))
sceneObject->Remove();
break;
}
}
_sceneObjectGUID.Clear();
}
uint32 PetBattle::GetOpponentCreatureID(uint8 teamIdx) const
{
uint8 opponentTeam = 1 - teamIdx;
+1 -5
View File
@@ -124,6 +124,7 @@ struct PetBattlePetData
// Battle state
bool IsAlive() const { return Health > 0; }
bool SeenAction = false; // was on the field for at least one turn (FINAL_ROUND SeenAction, XP eligibility)
int32 GetState(uint32 stateID) const
{
for (auto const& [id, val] : States)
@@ -292,10 +293,6 @@ public:
uint8 GetWinnerTeam() const { return _winnerTeam; }
Player* GetPlayerForTeam(uint8 teamIdx) const;
ObjectGuid GetSceneObjectGUID() const { return _sceneObjectGUID; }
void SetSceneObjectGUID(ObjectGuid guid) { _sceneObjectGUID = guid; }
void DespawnSceneObject();
// Trap validation
uint8 GetTrapStatus(uint8 playerTeam) const;
@@ -381,7 +378,6 @@ private:
ObjectGuid _wildCreatureGUID;
ObjectGuid _npcTrainerGUID;
ObjectGuid _sceneObjectGUID;
};
} // namespace PetBattles
+64 -61
View File
@@ -21,13 +21,18 @@
#include "Creature.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
#include "GameTime.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "SceneObject.h"
#include "WorldSession.h"
#include <algorithm>
// Observed on retail 12.1.0.69587: QUEUED resent 7.3 s after the join; AvgWaitTime 60 s in both frames.
static constexpr uint32 PET_BATTLE_QUEUE_STATUS_UPDATE_INTERVAL = 7000;
static constexpr uint64 PET_BATTLE_QUEUE_AVG_WAIT_SECS = 60;
namespace PetBattles
{
@@ -568,16 +573,26 @@ namespace PetBattles
{
if (Player* player = ObjectAccessor::FindPlayer(guid))
{
WorldPackets::BattlePet::PetBattleQueueStatus timeoutStatus;
timeoutStatus.Status = PET_BATTLE_QUEUE_STATUS_PROPOSAL_TIMED_OUT;
player->SendDirectMessage(timeoutStatus.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_PROPOSAL_TIMED_OUT);
}
_pvpQueue.push_back({ guid, 0 });
Enqueue(guid);
}
_pendingProposal.reset();
}
}
// Retail (12.1.0.69587 capture): a queued client receives SMSG_PET_BATTLE_QUEUE_STATUS again
// while it waits -- QUEUED with the elapsed ClientWaitTime (observed once, 7.3 s after the join).
for (PvPQueueEntry& entry : _pvpQueue)
{
entry.UpdateTimer += diff;
if (entry.UpdateTimer < PET_BATTLE_QUEUE_STATUS_UPDATE_INTERVAL)
continue;
entry.UpdateTimer = 0;
if (Player* player = ObjectAccessor::FindPlayer(entry.PlayerGUID))
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_QUEUED, &entry);
}
// Try to match PvP queue players periodically (every 5 seconds)
_queueMatchTimer += diff;
if (_queueMatchTimer >= 5000)
@@ -601,9 +616,6 @@ namespace PetBattles
battle->SetBattleID(battleID);
battle->InitWildBattle(player, wildCreatureGUID);
if (SceneObject* sceneObject = SceneObject::CreatePetBattleSceneObject(player, player->GetPosition(), player->GetGUID()))
battle->SetSceneObjectGUID(sceneObject->GetGUID());
PetBattle* ptr = battle.get();
_activeBattles[battleID] = std::move(battle);
_playerToBattle[player->GetGUID()] = battleID;
@@ -627,9 +639,6 @@ namespace PetBattles
if (battle->IsFinished())
return nullptr; // Validation failed in InitPvPBattle
if (SceneObject* sceneObject = SceneObject::CreatePetBattleSceneObject(player1, player1->GetPosition(), ObjectGuid::Empty))
battle->SetSceneObjectGUID(sceneObject->GetGUID());
PetBattle* ptr = battle.get();
_activeBattles[battleID] = std::move(battle);
_playerToBattle[player1->GetGUID()] = battleID;
@@ -655,9 +664,6 @@ namespace PetBattles
battle->SetBattleID(battleID);
battle->InitNPCBattle(player, trainer, *npcTeam);
if (SceneObject* sceneObject = SceneObject::CreatePetBattleSceneObject(player, player->GetPosition(), player->GetGUID()))
battle->SetSceneObjectGUID(sceneObject->GetGUID());
PetBattle* ptr = battle.get();
_activeBattles[battleID] = std::move(battle);
_playerToBattle[player->GetGUID()] = battleID;
@@ -676,8 +682,6 @@ namespace PetBattles
PetBattle* battle = it->second.get();
battle->DespawnSceneObject();
// Remove player mappings
for (uint8 i = 0; i < MAX_PET_BATTLE_PLAYERS; ++i)
{
@@ -822,6 +826,36 @@ namespace PetBattles
// PvP Queue
// ============================================================================
void PetBattleMgr::Enqueue(ObjectGuid playerGUID)
{
PvPQueueEntry entry;
entry.PlayerGUID = playerGUID;
entry.TicketId = _nextQueueTicketId++;
entry.EnqueueTime = GameTime::GetGameTime();
_pvpQueue.push_back(entry);
}
// One packet shape for every queue outcome: the ticket names the requester (and the queue entry
// when there is one); the wait times ride along only while the player is actually queued.
void PetBattleMgr::SendQueueStatus(Player* player, uint32 status, PvPQueueEntry const* entry) const
{
WorldPackets::BattlePet::PetBattleQueueStatus packet;
packet.Status = status;
packet.Ticket.RequesterGuid = player->GetGUID();
packet.Ticket.Type = WorldPackets::LFG::RideType::PetBattle;
if (entry)
{
packet.Ticket.Id = entry->TicketId;
packet.Ticket.Time = entry->EnqueueTime;
packet.ClientWaitTime = uint64(std::max<time_t>(0, GameTime::GetGameTime() - entry->EnqueueTime));
packet.AvgWaitTime = PET_BATTLE_QUEUE_AVG_WAIT_SECS;
}
else
packet.Ticket.Time = GameTime::GetGameTime();
player->SendDirectMessage(packet.Write());
}
void PetBattleMgr::JoinQueue(ObjectGuid playerGUID)
{
// Don't add if already in queue
@@ -831,9 +865,7 @@ namespace PetBattles
{
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_ALREADY_QUEUED;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_ALREADY_QUEUED);
}
return;
}
@@ -844,9 +876,7 @@ namespace PetBattles
{
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_JOIN_FAILED;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_JOIN_FAILED);
}
return;
}
@@ -857,9 +887,7 @@ namespace PetBattles
BattlePets::BattlePetMgr* petMgr = player->GetSession()->GetBattlePetMgr();
if (!petMgr->HasJournalLock())
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_JOURNAL_LOCK;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_JOURNAL_LOCK);
return;
}
@@ -880,24 +908,15 @@ namespace PetBattles
if (!hasPet)
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_SLOTS;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_SLOTS);
return;
}
}
PvPQueueEntry entry;
entry.PlayerGUID = playerGUID;
entry.EnqueueTime = 0;
_pvpQueue.push_back(entry);
Enqueue(playerGUID);
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_QUEUED;
player->SendDirectMessage(status.Write());
}
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_QUEUED, &_pvpQueue.back());
TC_LOG_DEBUG("server.loading", "PetBattleMgr: Player {} joined PvP pet battle queue (queue size: {})",
playerGUID.ToString(), _pvpQueue.size());
@@ -921,15 +940,11 @@ namespace PetBattles
if (Player* other = ObjectAccessor::FindPlayer(otherGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED;
other->SendDirectMessage(status.Write());
SendQueueStatus(other, PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED);
}
// Re-add the other player to the queue
PvPQueueEntry requeue;
requeue.PlayerGUID = otherGUID;
_pvpQueue.push_back(requeue);
Enqueue(otherGUID);
_pendingProposal.reset();
}
@@ -937,9 +952,7 @@ namespace PetBattles
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_REMOVED;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_REMOVED);
}
}
@@ -960,24 +973,18 @@ namespace PetBattles
// Send declined status to the declining player
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_DECLINED;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_MATCH_DECLINED);
}
// Notify the other player and re-queue them
ObjectGuid otherGUID = (_pendingProposal->Player1 == playerGUID) ?
_pendingProposal->Player2 : _pendingProposal->Player1;
PvPQueueEntry requeue;
requeue.PlayerGUID = otherGUID;
_pvpQueue.push_back(requeue);
Enqueue(otherGUID);
if (Player* other = ObjectAccessor::FindPlayer(otherGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED;
other->SendDirectMessage(status.Write());
SendQueueStatus(other, PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED);
}
_pendingProposal.reset();
@@ -987,9 +994,7 @@ namespace PetBattles
// Send accepted status to this player
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_ACCEPTED;
player->SendDirectMessage(status.Write());
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_MATCH_ACCEPTED);
}
// Check if both accepted
@@ -1074,9 +1079,7 @@ namespace PetBattles
// Send matchmaking then proposal status to both players
for (Player* matchPlayer : { player1, player2 })
{
WorldPackets::BattlePet::PetBattleQueueStatus matchStatus;
matchStatus.Status = PET_BATTLE_QUEUE_STATUS_MATCHMAKING;
matchPlayer->SendDirectMessage(matchStatus.Write());
SendQueueStatus(matchPlayer, PET_BATTLE_QUEUE_STATUS_MATCHMAKING);
}
WorldPackets::BattlePet::PetBattleQueueProposeMatch proposeMatch;
+7 -1
View File
@@ -47,7 +47,9 @@ namespace PetBattles
struct PvPQueueEntry
{
ObjectGuid PlayerGUID;
uint32 EnqueueTime = 0;
uint32 TicketId = 0; // RideTicket.Id sent in every SMSG_PET_BATTLE_QUEUE_STATUS
time_t EnqueueTime = 0; // RideTicket.Time; ClientWaitTime = now - EnqueueTime
uint32 UpdateTimer = 0; // ms since the last QUEUED status resend
};
// Inferred targeting for ability auras based on the BattlePetAbilityState
@@ -167,6 +169,10 @@ namespace PetBattles
std::vector<PvPQueueEntry> _pvpQueue;
std::unique_ptr<PvPMatchProposal> _pendingProposal;
uint32 _queueMatchTimer = 0;
uint32 _nextQueueTicketId = 1;
void Enqueue(ObjectGuid playerGUID);
void SendQueueStatus(Player* player, uint32 status, PvPQueueEntry const* entry = nullptr) const;
};
} // namespace PetBattles
+44 -84
View File
@@ -18,7 +18,6 @@
#include "WorldSession.h"
#include "BattlePetMgr.h"
#include "BattlePetPackets.h"
#include "AccountMgr.h"
#include "Creature.h"
#include "DB2Stores.h"
#include "Log.h"
@@ -35,6 +34,12 @@ void WorldSession::HandleBattlePetRequestJournal(WorldPackets::BattlePet::Battle
void WorldSession::HandleBattlePetRequestJournalLock(WorldPackets::BattlePet::BattlePetRequestJournalLock& /*battlePetRequestJournalLock*/)
{
// Retail 12.1.0.69587 (three captures): the client sends this whenever the journal UI opens;
// a session that already holds the lock (SMSG_BATTLE_PET_JOURNAL_LOCK_ACQUIRED was pushed at
// login) gets NO reply -- no lock packet and no 10 KB journal resend.
if (GetBattlePetMgr()->HasJournalLock())
return;
GetBattlePetMgr()->SendJournalLockStatus();
if (GetBattlePetMgr()->HasJournalLock())
@@ -129,17 +134,6 @@ void WorldSession::HandleBattlePetDeletePet(WorldPackets::BattlePet::BattlePetDe
GetBattlePetMgr()->RemovePet(battlePetDeletePet.PetGuid);
}
void WorldSession::HandleBattlePetDeletePetCheat(WorldPackets::BattlePet::BattlePetDeletePetCheat& battlePetDeletePetCheat)
{
// Developer/GM cheat variant of CMSG_BATTLE_PET_DELETE_PET - the client only emits it in developer mode.
// Gate it on account security so a normal player cannot reach the cheat path; the effect is identical to
// the non-cheat handler (remove the pet from the journal).
if (AccountMgr::IsPlayerAccount(GetSecurity()))
return;
GetBattlePetMgr()->RemovePet(battlePetDeletePetCheat.PetGuid);
}
void WorldSession::HandleBattlePetSetFlags(WorldPackets::BattlePet::BattlePetSetFlags& battlePetSetFlags)
{
if (!GetBattlePetMgr()->HasJournalLock())
@@ -193,7 +187,7 @@ static WorldPackets::BattlePet::PetBattlePetUpdateInfo BuildPetUpdateInfo(
PetBattles::PetBattlePetData const& petData, uint8 teamIdx, uint8 petIdx);
static void BuildPetBattlePlayerUpdate(WorldPackets::BattlePet::PetBattlePlayerUpdateInfo& update,
PetBattles::PetBattleTeamData const& team, PetBattles::PetBattle const* battle, uint8 teamIdx)
PetBattles::PetBattleTeamData const& team, bool isWildTeam, PetBattles::PetBattle const* battle, uint8 teamIdx)
{
update.CharacterGUID = team.PlayerGUID;
update.TrapAbilityID = team.TrapAbilityID;
@@ -205,6 +199,8 @@ static void BuildPetBattlePlayerUpdate(WorldPackets::BattlePet::PetBattlePlayerU
for (uint8 i = 0; i < team.PetCount; ++i)
{
WorldPackets::BattlePet::PetBattlePetUpdateInfo petInfo = BuildPetUpdateInfo(team.Pets[i], teamIdx, i);
if (isWildTeam)
petInfo.DisplayID = 0;
update.Pets.push_back(std::move(petInfo));
}
}
@@ -361,12 +357,28 @@ static WorldPackets::BattlePet::PetBattlePetUpdateInfo BuildPetUpdateInfo(
petInfo.Auras.push_back(auraInfo);
}
petInfo.States.push_back({ BattlePets::STATE_STAT_POWER, petData.BasePower });
petInfo.States.push_back({ BattlePets::STATE_STAT_POWER, petData.Power }); // wire: final power, not the raw base
petInfo.States.push_back({ BattlePets::STATE_STAT_STAMINA, petData.BaseStamina });
petInfo.States.push_back({ BattlePets::STATE_STAT_SPEED, petData.BaseSpeed });
petInfo.States.push_back({ 40, 5 }); // CritChance = 5%
if (petData.PetType >= 0 && petData.PetType < PetBattles::PET_TYPE_COUNT)
petInfo.States.push_back({ uint32(44 + petData.PetType), 1 }); // Family passive flag
{
// BattlePetState passive ids (Passive_Critter 42 .. Passive_Aquatic 51) are not in PetType order
static constexpr uint32 passiveStateByPetType[PetBattles::PET_TYPE_COUNT] =
{
44, // Humanoid
46, // Dragonkin
45, // Flying
50, // Undead
42, // Critter
49, // Magic
47, // Elemental
43, // Beast
51, // Aquatic
48 // Mechanical
};
petInfo.States.push_back({ passiveStateByPetType[petData.PetType], 1 });
}
return petInfo;
}
@@ -408,12 +420,11 @@ static void BuildRoundEffects(std::vector<WorldPackets::BattlePet::PetBattleEffe
case PetBattles::PET_BATTLE_EFFECT_AURA_CANCEL:
case PetBattles::PET_BATTLE_EFFECT_AURA_CHANGE:
{
// Sniff-verified retail wire order is [AbilityID, InstanceID, RoundsRemaining, CurrentRound]
// (we historically stored Param1=InstanceID, Param2=AbilityID ? swap on the wire so
// existing call sites keep their semantic naming)
// 12.1.0.69587 wire order (three captures, WPP ReadPetBattleEffectTarget agrees):
// [AuraInstanceID, AuraAbilityID, RoundsRemaining, CurrentRound]
target.Type = 1; // Aura: 4 params
target.Params.push_back(roundEffect.Param2); // AuraAbilityID
target.Params.push_back(roundEffect.Param1); // AuraInstanceID
target.Params.push_back(roundEffect.Param2); // AuraAbilityID
target.Params.push_back(roundEffect.Param3); // RoundsRemaining
target.Params.push_back(roundEffect.Param4); // CurrentRound
break;
@@ -468,11 +479,6 @@ static void BuildRoundEffects(std::vector<WorldPackets::BattlePet::PetBattleEffe
}
void WorldSession::HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattleRequestWild& petBattleRequestWild)
{
StartWildPetBattle(petBattleRequestWild.TargetGUID);
}
void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
{
Player* player = GetPlayer();
if (!player)
@@ -529,7 +535,7 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
}
// Validate target creature
Creature* creature = ObjectAccessor::GetCreature(*player, targetGUID);
Creature* creature = ObjectAccessor::GetCreature(*player, petBattleRequestWild.TargetGUID);
if (!creature || !creature->IsWildBattlePet())
{
WorldPackets::BattlePet::PetBattleRequestFailed failed;
@@ -559,7 +565,7 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
}
// Create the battle
PetBattles::PetBattle* battle = sPetBattleMgr->CreateWildBattle(player, targetGUID);
PetBattles::PetBattle* battle = sPetBattleMgr->CreateWildBattle(player, petBattleRequestWild.TargetGUID);
if (!battle)
{
WorldPackets::BattlePet::PetBattleRequestFailed failed;
@@ -604,17 +610,16 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
// Build and send initial update
WorldPackets::BattlePet::PetBattleInitialUpdate initialUpdate;
initialUpdate.SceneObjectGUID = battle->GetSceneObjectGUID();
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), battle, PetBattles::PET_BATTLE_TEAM_1);
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), battle, PetBattles::PET_BATTLE_TEAM_2);
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), false, battle, PetBattles::PET_BATTLE_TEAM_1);
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), true, battle, PetBattles::PET_BATTLE_TEAM_2);
BuildPetBattleEnviros(initialUpdate.Enviros, battle);
initialUpdate.CurRound = battle->GetCurrentRound();
initialUpdate.CurPetBattleState = static_cast<int8>(battle->GetBattleState());
initialUpdate.NpcCreatureID = 0; // Wild battles have no NPC trainer; creature identified by InitialWildPetGUID
initialUpdate.NpcDisplayID = 0;
initialUpdate.InitialWildPetGUID = targetGUID;
initialUpdate.InitialWildPetGUID = petBattleRequestWild.TargetGUID;
initialUpdate.IsPVP = false;
initialUpdate.CanAwardXP = true;
initialUpdate.WaitingForFrontPetsMaxSecs = 30;
@@ -657,7 +662,6 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
// we don't have intro animations. This packet tells the client abilities are available.
{
WorldPackets::BattlePet::PetBattleFirstRound firstRound;
firstRound.SceneObjectGUID = battle->GetSceneObjectGUID();
firstRound.CurRound = 0;
firstRound.NextPetBattleState = static_cast<int8>(PetBattles::PET_BATTLE_STATE_ROUND_IN_PROGRESS);
@@ -678,7 +682,7 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
// its input when the player submits theirs (in HandlePetBattleInput).
TC_LOG_DEBUG("server.loading", "PetBattleHandler: Player {} started wild battle against {}",
player->GetGUID().ToString(), targetGUID.ToString());
player->GetGUID().ToString(), petBattleRequestWild.TargetGUID.ToString());
}
void WorldSession::StartNPCPetBattle(Creature* trainer)
@@ -783,10 +787,9 @@ void WorldSession::StartNPCPetBattle(Creature* trainer)
// Build and send initial update
WorldPackets::BattlePet::PetBattleInitialUpdate initialUpdate;
initialUpdate.SceneObjectGUID = battle->GetSceneObjectGUID();
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), battle, PetBattles::PET_BATTLE_TEAM_1);
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), battle, PetBattles::PET_BATTLE_TEAM_2);
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), false, battle, PetBattles::PET_BATTLE_TEAM_1);
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), false, battle, PetBattles::PET_BATTLE_TEAM_2);
BuildPetBattleEnviros(initialUpdate.Enviros, battle);
initialUpdate.CurRound = battle->GetCurrentRound();
@@ -807,7 +810,6 @@ void WorldSession::StartNPCPetBattle(Creature* trainer)
// Send first round to unlock abilities
{
WorldPackets::BattlePet::PetBattleFirstRound firstRound;
firstRound.SceneObjectGUID = battle->GetSceneObjectGUID();
firstRound.CurRound = 0;
firstRound.NextPetBattleState = static_cast<int8>(PetBattles::PET_BATTLE_STATE_ROUND_IN_PROGRESS);
@@ -875,7 +877,6 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
// after a delay by PetBattle::Update() when the battle is ending.
{
WorldPackets::BattlePet::PetBattleRoundResult roundResult;
roundResult.SceneObjectGUID = battle->GetSceneObjectGUID();
roundResult.CurRound = battle->GetCurrentRound();
roundResult.NextPetBattleState = static_cast<int8>(battle->GetBattleState());
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
@@ -966,7 +967,6 @@ void WorldSession::HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBa
// Send REPLACEMENTS_MADE to acknowledge the swap
WorldPackets::BattlePet::PetBattleReplacementsMade replacements;
replacements.SceneObjectGUID = battle->GetSceneObjectGUID();
replacements.CurRound = battle->GetCurrentRound();
replacements.NextPetBattleState = static_cast<int8>(PetBattles::PET_BATTLE_STATE_ROUND_IN_PROGRESS);
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
@@ -1144,45 +1144,10 @@ void WorldSession::HandleJoinPetBattleQueue(WorldPackets::BattlePet::JoinPetBatt
if (!player)
return;
if (sPetBattleMgr->IsPlayerInBattle(player->GetGUID()))
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = 0;
SendPacket(status.Write());
return;
}
// Validate that player has at least one alive pet in battle slots
{
BattlePets::BattlePetMgr* petMgr = GetBattlePetMgr();
bool hasPet = false;
for (uint8 i = 0; i < uint8(BattlePets::BattlePetSlot::Count); ++i)
{
WorldPackets::BattlePet::BattlePetSlot* slot = petMgr->GetSlot(BattlePets::BattlePetSlot(i));
if (slot && !slot->Locked && !slot->Pet.Guid.IsEmpty())
{
BattlePets::BattlePet* pet = petMgr->GetPet(slot->Pet.Guid);
if (pet && pet->PacketInfo.Health > 0)
{
hasPet = true;
break;
}
}
}
if (!hasPet)
{
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = 0;
SendPacket(status.Write());
return;
}
}
// PetBattleMgr::JoinQueue validates (in battle, journal lock, alive slotted pets) and answers
// with exactly one SMSG_PET_BATTLE_QUEUE_STATUS; retail sends a single QUEUED at join. The old
// handler sent a second copy (and status 0 on failure), which is not what the client expects.
sPetBattleMgr->JoinQueue(player->GetGUID());
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = 1; // Queued
SendPacket(status.Write());
}
void WorldSession::HandleLeavePetBattleQueue(WorldPackets::BattlePet::LeavePetBattleQueue& /*leavePetBattleQueue*/)
@@ -1191,11 +1156,7 @@ void WorldSession::HandleLeavePetBattleQueue(WorldPackets::BattlePet::LeavePetBa
if (!player)
return;
sPetBattleMgr->LeaveQueue(player->GetGUID());
WorldPackets::BattlePet::PetBattleQueueStatus status;
status.Status = 0; // Not in queue
SendPacket(status.Write());
sPetBattleMgr->LeaveQueue(player->GetGUID()); // LeaveQueue sends REMOVED itself
}
void WorldSession::HandlePetBattleQueueProposeMatchResult(WorldPackets::BattlePet::PetBattleQueueProposeMatchResult& petBattleQueueProposeMatchResult)
@@ -1232,9 +1193,8 @@ void WorldSession::HandlePetBattleRequestUpdate(WorldPackets::BattlePet::PetBatt
// Send initial update to both players
WorldPackets::BattlePet::PetBattleInitialUpdate initialUpdate;
initialUpdate.SceneObjectGUID = battle->GetSceneObjectGUID();
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), battle, PetBattles::PET_BATTLE_TEAM_1);
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), battle, PetBattles::PET_BATTLE_TEAM_2);
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), false, battle, PetBattles::PET_BATTLE_TEAM_1);
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), false, battle, PetBattles::PET_BATTLE_TEAM_2);
BuildPetBattleEnviros(initialUpdate.Enviros, battle);
initialUpdate.CurRound = battle->GetCurrentRound();
initialUpdate.CurPetBattleState = static_cast<int8>(battle->GetBattleState());
@@ -332,12 +332,13 @@ namespace WorldPackets::BattlePet
ByteBuffer& operator<<(ByteBuffer& data, PetBattleEffectTargetInfo const& target)
{
data << uint8(target.Type << 4); // Upper nibble (client reads uint8 >> 4)
data << uint8(target.Petx);
data << uint8(target.Type << 4); // Upper nibble (IDA-verified 12.0: client reads uint8 >> 4)
if (target.Type == 8 && target.EmbeddedPetUpdate)
data << *target.EmbeddedPetUpdate;
data << int32(target.Petx);
for (int32 param : target.Params)
data << int32(param);
@@ -347,11 +348,11 @@ namespace WorldPackets::BattlePet
ByteBuffer& operator<<(ByteBuffer& data, PetBattleEffectInfo const& effect)
{
data << int32(effect.AbilityEffectID);
data << uint16(effect.Flags);
data << int32(effect.Flags);
data << int16(effect.SourceAuraInstanceID);
data << int16(effect.TurnInstanceID);
data << int8(effect.PetBattleEffectType);
data << uint8(effect.CasterPBOID);
data << int32(effect.PetBattleEffectType);
data << int32(effect.CasterPBOID);
data << uint8(effect.StackDepth);
data << uint32(effect.Targets.size());
@@ -393,17 +394,18 @@ namespace WorldPackets::BattlePet
data << uint32(cooldowns.size());
// V12 wire order: Cooldowns, PetXDied count (3 bits), Effects, PetXDied data
// (V6 had Effects before Cooldowns, but V12 client expects this order)
// 12.1.0.69587 wire order (every FIRST_ROUND / ROUND_RESULT / REPLACEMENTS_MADE of two retail
// captures, verified byte-exact): Cooldowns, Effects, PetXDied count (3 bits) + flush, PetXDied.
// The count used to be written BEFORE the effects, which shifted every effect by one byte.
for (PetBattleCooldownInfo const& cd : cooldowns)
data << cd;
data << Bits<3>(petXDied.size());
data.FlushBits();
for (PetBattleEffectInfo const& effect : effects)
data << effect;
data << Bits<3>(petXDied.size());
data.FlushBits();
for (int8 pboid : petXDied)
data << int8(pboid);
}
@@ -501,9 +503,6 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleInitialUpdate::Write()
{
// SceneObjectGUID wrapper (see BEFUND/PLAN_B5) precedes the existing body, unchanged otherwise.
_worldPacket << SceneObjectGUID;
// Wire format matches BaseEntity.cpp:494-588
for (std::size_t i = 0; i < 2; ++i)
{
@@ -541,7 +540,6 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleFirstRound::Write()
{
_worldPacket << SceneObjectGUID;
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
return &_worldPacket;
@@ -549,7 +547,6 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleRoundResult::Write()
{
_worldPacket << SceneObjectGUID;
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
return &_worldPacket;
@@ -557,7 +554,6 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleReplacementsMade::Write()
{
_worldPacket << SceneObjectGUID;
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
return &_worldPacket;
@@ -584,8 +580,6 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleFinalRound::Write()
{
_worldPacket << SceneObjectGUID;
// 12.0.7 (68275) wire (sniff-verified vs b_pets, 5 battles): flags_byte, u32(=0), npcCreatureID(u32), pets.count, pets[].
// flags_byte = MSB-first bitfield{4}: bit7=Abandoned, bit6=PvpBattle, bit5=Winners[0] (team0), bit4=Winners[1] (team1).
_worldPacket << Bits<1>(Abandoned);
@@ -623,16 +617,20 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleQueueStatus::Write()
{
_worldPacket << uint32(Status);
_worldPacket << uint32(SlotResult[0]);
_worldPacket << uint32(SlotResult[1]);
_worldPacket << Size<uint32>(SlotResult);
for (uint32 result : SlotResult)
_worldPacket << uint32(result);
_worldPacket << Ticket;
_worldPacket << OptionalInit(ClientWaitTime);
_worldPacket << OptionalInit(AvgWaitTime);
_worldPacket.FlushBits();
if (ClientWaitTime)
_worldPacket << uint32(*ClientWaitTime);
_worldPacket << uint64(*ClientWaitTime);
if (AvgWaitTime)
_worldPacket << uint32(*AvgWaitTime);
_worldPacket << uint64(*AvgWaitTime);
return &_worldPacket;
}
@@ -649,18 +647,4 @@ namespace WorldPackets::BattlePet
_worldPacket << uint32(RoundsRemaining);
return &_worldPacket;
}
void BattlePetDeletePetCheat::Read()
{
_worldPacket >> PetGuid;
}
WorldPacket const* PetBattleFinished::Write()
{
// Body is disasm-verified to be exactly this one ObjectGuid (petbattle_wire_FULL_68275.json
// 0x42008a, read_count 1) - see BEFUND/PLAN_B5.
_worldPacket << SceneObjectGUID;
return &_worldPacket;
}
}
@@ -20,8 +20,10 @@
#include "Packet.h"
#include "PacketUtilities.h"
#include "LFGPacketsCommon.h"
#include "ObjectGuid.h"
#include "Optional.h"
#include "Position.h"
#include "UnitDefines.h"
#include <array>
#include <memory>
@@ -194,18 +196,6 @@ namespace WorldPackets
ObjectGuid PetGuid;
};
// Developer/GM cheat variant of DELETE_PET (opcode DELETE_PET+1). Same wire (one pet GUID); the handler
// is gated on GM security since the client only emits this in developer mode.
class BattlePetDeletePetCheat final : public ClientPacket
{
public:
explicit BattlePetDeletePetCheat(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_DELETE_PET_CHEAT, std::move(packet)) {}
void Read() override;
ObjectGuid PetGuid;
};
class BattlePetSetFlags final : public ClientPacket
{
public:
@@ -366,7 +356,7 @@ namespace WorldPackets
struct PetBattleEffectTargetInfo
{
uint8 Type = 0; // JamPetBattleEffectTarget field 0 (wire: uint8(Type << 4); client reads byte>>4)
int32 Petx = 0; // field 1 (petx@4) - target pet index / PBOID, always read after the discriminator
int32 Petx = 0; // field 1 (petx@4) ? target pet index / PBOID, always read after the discriminator
// Params carry the type-specific union members (ascending reflection offset):
// 1 aura: auraInstanceID, auraAbilityID, roundsRemaining, currentRound (fields 2-5)
// 2 state: stateID, stateValue (fields 6-7)
@@ -375,7 +365,7 @@ namespace WorldPackets
// 5 trigger: triggerAbilityID (field 10)
// 6 cooldown: changedAbilityID, cooldownRemaining, lockdownRemaining (fields 11-13)
// 7 broadcast:broadcastTextID (field 14)
// 8 pet: EmbeddedPetUpdate (field 15 'pet'); slot/newAbilityID (16-17) not serialized - see report
// 8 pet: EmbeddedPetUpdate (field 15 'pet'); slot/newAbilityID (16-17) not serialized ? see report
std::vector<int32> Params;
Optional<PetBattlePetUpdateInfo> EmbeddedPetUpdate; // Only when Type == 8
};
@@ -559,14 +549,10 @@ namespace WorldPackets
class PetBattleInitialUpdate final : public ServerPacket
{
public:
// 12.1 (69382, PLAN_B5): the client only listens for the round-broadcast body on the
// (SceneType::PetBattle). SMSG_PET_BATTLE_INITIAL_UPDATE is dead (TC-declared STATUS_NEVER,
// no longer dispatched by the retail client) - this class now targets the live opcode.
PetBattleInitialUpdate() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_INITIAL_UPDATE) {}
PetBattleInitialUpdate() : ServerPacket(SMSG_PET_BATTLE_INITIAL_UPDATE) {}
WorldPacket const* Write() override;
ObjectGuid SceneObjectGUID;
std::array<PetBattlePlayerUpdateInfo, 2> Players;
std::array<PetBattleEnviroInfo, 3> Enviros;
uint16 WaitingForFrontPetsMaxSecs = 30;
@@ -584,14 +570,10 @@ namespace WorldPackets
class PetBattleFirstRound final : public ServerPacket
{
public:
// See PetBattleInitialUpdate above: FIRST_ROUND/ROUND_RESULT/REPLACEMENTS_MADE share one
// client parser (JamPetBattleRoundResult, PLAN_B5) and only differ by which
// SMSG_SCENE_OBJECT_PET_BATTLE_* opcode wraps the identical body.
PetBattleFirstRound() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_FIRST_ROUND) {}
PetBattleFirstRound() : ServerPacket(SMSG_PET_BATTLE_FIRST_ROUND) {}
WorldPacket const* Write() override;
ObjectGuid SceneObjectGUID;
uint32 CurRound = 0;
int8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
@@ -603,11 +585,10 @@ namespace WorldPackets
class PetBattleRoundResult final : public ServerPacket
{
public:
PetBattleRoundResult() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_ROUND_RESULT) {}
PetBattleRoundResult() : ServerPacket(SMSG_PET_BATTLE_ROUND_RESULT) {}
WorldPacket const* Write() override;
ObjectGuid SceneObjectGUID;
uint32 CurRound = 0;
int8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
@@ -619,11 +600,10 @@ namespace WorldPackets
class PetBattleReplacementsMade final : public ServerPacket
{
public:
PetBattleReplacementsMade() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_REPLACEMENTS_MADE) {}
PetBattleReplacementsMade() : ServerPacket(SMSG_PET_BATTLE_REPLACEMENTS_MADE) {}
WorldPacket const* Write() override;
ObjectGuid SceneObjectGUID;
uint32 CurRound = 0;
int8 NextPetBattleState = 0;
std::array<PetBattleRoundPlayerData, 2> Players;
@@ -650,21 +630,14 @@ namespace WorldPackets
class PetBattleFinalRound final : public ServerPacket
{
public:
// 12.1 (69382, PLAN_B5): live opcode is SMSG_SCENE_OBJECT_PET_BATTLE_FINAL_ROUND; the send
// site (PetBattle::SendFinalRoundPacket) and the body already existed, only the opcode +
// SceneObjectGUID wrapper were missing (rank #2, "lowest fruit" in the cluster).
PetBattleFinalRound() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_FINAL_ROUND) {}
PetBattleFinalRound() : ServerPacket(SMSG_PET_BATTLE_FINAL_ROUND) {}
WorldPacket const* Write() override;
// UNVERIFIED (PLAN_B5): whether FINAL_ROUND really carries a SceneObjectGUID prefix is only
// confirmed by pattern-matching against FIRST_ROUND/ROUND_RESULT (both sniff-verified); no
// direct sniff of FINAL_ROUND itself exists yet.
ObjectGuid SceneObjectGUID;
bool Abandoned = false;
bool PvpBattle = false;
// 12.0.7 (68275) JamPetBattleFinalRound (sniff-verified vs b_pets, 5 battles): the winner is the
// per-team flag pair in the flag byte - bit5=Winners[0] (team0), bit4=Winners[1] (team1). The two
// per-team flag pair in the flag byte ? bit5=Winners[0] (team0), bit4=Winners[1] (team1). The two
// flat uint32s after the flush are field#1 (0 in every capture, role unknown, NOT winners) and
// NpcCreatureID (0 for wild battles, the trainer entry for NPC battles).
std::array<bool, 2> Winners = {};
@@ -675,14 +648,9 @@ namespace WorldPackets
class PetBattleFinished final : public ServerPacket
{
public:
// 12.1 (69382, PLAN_B5): live opcode is SMSG_SCENE_OBJECT_PET_BATTLE_FINISHED; body is
// disasm-verified to be exactly one ObjectGuid (petbattle_wire_FULL_68275.json 0x42008a,
// read_count 1) - the best-belegte opcode in the whole cluster.
PetBattleFinished() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_FINISHED, 20) {}
PetBattleFinished() : ServerPacket(SMSG_PET_BATTLE_FINISHED, 0) {}
WorldPacket const* Write() override;
ObjectGuid SceneObjectGUID;
WorldPacket const* Write() override { return &_worldPacket; }
};
class PetBattleRequestFailed final : public ServerPacket
@@ -713,10 +681,16 @@ namespace WorldPackets
WorldPacket const* Write() override;
// 12.1.0.69587 wire (friend capture, CMSG_JOIN_PET_BATTLE_QUEUE -> two QUEUED statuses):
// Status u32, SlotResult (sized, empty on QUEUED), RideTicket{RequesterGuid = player,
// Id, Type = 3 (PetBattle), Time = enqueue time, IsCrossFaction}, then the two optional
// wait times as 64-bit seconds (ClientWaitTime 0 -> 8 across a 7.3 s gap, AvgWaitTime 60).
// The previous layout (fixed SlotResult pair, no ticket, 32-bit times) was 32 bytes short.
uint32 Status = 0;
std::array<uint32, 2> SlotResult = {};
Optional<uint32> ClientWaitTime;
Optional<uint32> AvgWaitTime;
std::vector<uint32> SlotResult;
WorldPackets::LFG::RideTicket Ticket;
Optional<uint64> ClientWaitTime;
Optional<uint64> AvgWaitTime;
};
class PetBattleQueueProposeMatch final : public ServerPacket
@@ -30,6 +30,7 @@ namespace WorldPackets
None = 0,
Battlegrounds = 1,
Lfg = 2,
PetBattle = 3, // SMSG_PET_BATTLE_QUEUE_STATUS ticket (12.1.0.69587 capture)
LfgListListing = 4, // premade group listing tickets (sniff-verified, 68275)
LfgListApplication = 6 // premade group application tickets (sniff-verified, 68275)
};
+1 -1
View File
@@ -232,7 +232,7 @@ void OpcodeTable::InitializeClientOpcodes()
DEFINE_HANDLER(CMSG_BATTLE_PAY_START_VAS_PURCHASE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePayStartVasPurchase);
DEFINE_HANDLER(CMSG_BATTLE_PET_CLEAR_FANFARE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetClearFanfare);
DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetDeletePet);
DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET_CHEAT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetDeletePetCheat);
DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET_CHEAT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_BATTLE_PET_MODIFY_NAME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetModifyName);
DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetRequestJournal);
DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL_LOCK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetRequestJournalLock);
-2
View File
@@ -2586,7 +2586,6 @@ public:
void HandleBattlePetModifyName(WorldPackets::BattlePet::BattlePetModifyName& battlePetModifyName);
void HandleQueryBattlePetName(WorldPackets::BattlePet::QueryBattlePetName& queryBattlePetName);
void HandleBattlePetDeletePet(WorldPackets::BattlePet::BattlePetDeletePet& battlePetDeletePet);
void HandleBattlePetDeletePetCheat(WorldPackets::BattlePet::BattlePetDeletePetCheat& battlePetDeletePetCheat);
void HandleBattlePetSetFlags(WorldPackets::BattlePet::BattlePetSetFlags& battlePetSetFlags);
void HandleBattlePetClearFanfare(WorldPackets::BattlePet::BattlePetClearFanfare& battlePetClearFanfare);
void HandleBattlePetSummon(WorldPackets::BattlePet::BattlePetSummon& battlePetSummon);
@@ -2596,7 +2595,6 @@ public:
// Pet Battle combat
void HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattleRequestWild& petBattleRequestWild);
void StartNPCPetBattle(Creature* trainer);
void StartWildPetBattle(ObjectGuid targetGUID);
void HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput& petBattleInput);
void HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBattleReplaceFrontPet& petBattleReplaceFrontPet);
void HandlePetBattleQuitNotify(WorldPackets::BattlePet::PetBattleQuitNotify& petBattleQuitNotify);
+4 -3
View File
@@ -6361,11 +6361,12 @@ void Spell::EffectStartPetBattle()
Player* player = caster->ToPlayer();
Creature* creature = unitTarget->ToCreature();
if (creature->IsWildBattlePet())
/* if (creature->IsWildBattlePet())
{
player->GetSession()->StartWildPetBattle(creature->GetGUID());
}
else if (sPetBattleMgr->GetNPCTeam(creature->GetEntry()))
}*/
/*else*/ if (sPetBattleMgr->GetNPCTeam(creature->GetEntry()))
{
player->GetSession()->StartNPCPetBattle(creature);
}