Pet Battles: PR readiness fixes — validation, timeouts, logging, cleanup

- Add per-round AFK timeout (30s + 15s grace, auto-forfeit)
- Add input validation: MoveType range check, duplicate input guard,
  battle state checks, distance check for wild battles, queue loadout check
- Fix 28 TC_LOG_ERROR → TC_LOG_DEBUG/WARN for non-error messages
- Send SMSG_BATTLE_PETS_HEALED after HealBattlePetsPct
- Send SMSG_PET_BATTLE_MAX_GAME_LENGTH_WARNING 5 min before timeout
- Fix CMSG_BATTLE_PET_SUMMON threading (PROCESS_INPLACE → THREADUNSAFE)
- Remove unused ResolveSpeed() and opponentTeam variable
- Fix TOOD typo, add ChatRestricted TODO comment
This commit is contained in:
luis
2026-03-12 08:27:04 -03:00
parent dc30fdd09d
commit ade323f7b3
6 changed files with 119 additions and 43 deletions
+5 -1
View File
@@ -75,7 +75,7 @@ namespace BattlePets
speed *= battlePetBreedQuality->StateMultiplier;
break;
}
// TOOD: add check if pet has existing quality
// TODO: add check if pet has existing quality
}
// scale stats depending on level
@@ -881,6 +881,10 @@ namespace BattlePets
}
SendUpdates(updates, false);
// Notify the client that battle pets have been healed (triggers UI feedback)
WorldPackets::BattlePet::BattlePetsHealed healed;
_owner->SendPacket(healed.Write());
}
void BattlePetMgr::UpdateBattlePetData(ObjectGuid guid)
+47 -26
View File
@@ -136,7 +136,7 @@ namespace PetBattles
++loaded;
}
TC_LOG_ERROR("server.loading", "PetBattle LoadWildPetAbilities: Species={} Level={} entries={} loaded={} abilities=[{},{},{}]",
TC_LOG_DEBUG("server.loading", "PetBattle LoadWildPetAbilities: Species={} Level={} entries={} loaded={} abilities=[{},{},{}]",
pet.Species, pet.Level, speciesAbilities->size(), loaded,
pet.AbilityIDs[0], pet.AbilityIDs[1], pet.AbilityIDs[2]);
}
@@ -355,6 +355,32 @@ namespace PetBattles
}
}
// Per-round AFK timeout ? auto-forfeit if a player hasn't submitted input
if (_state == PET_BATTLE_STATE_ROUND_IN_PROGRESS || _state == PET_BATTLE_STATE_WAITING_FOR_FRONT_PET)
{
_roundTimerSecs++;
// Grace period: round time + 15 seconds
if (_roundTimerSecs > PET_BATTLE_MAX_ROUND_TIME + 15)
{
if (_battleType == PET_BATTLE_TYPE_PVP || _battleType == PET_BATTLE_TYPE_LFPB)
{
for (uint8 i = 0; i < MAX_PET_BATTLE_PLAYERS; ++i)
{
if (!_teams[i].HasInputThisRound)
{
Forfeit(i);
return;
}
}
}
else
{
Forfeit(PET_BATTLE_TEAM_1);
return;
}
}
}
// Handle finish delay ? wait for death animation before sending FinalRound
if (_state == PET_BATTLE_STATE_FINAL_ROUND && _finishDelayMs > 0)
{
@@ -382,6 +408,7 @@ namespace PetBattles
team.PendingAbilityID = abilityID;
team.PendingNewFrontPet = newFrontPet;
team.HasInputThisRound = true;
_roundTimerSecs = 0;
}
bool PetBattle::BothTeamsReady() const
@@ -398,6 +425,7 @@ namespace PetBattles
{
_state = PET_BATTLE_STATE_ROUND_IN_PROGRESS;
_currentRound++;
_roundTimerSecs = 0;
_roundEffects.clear();
_petKilledThisRound.fill(false);
_needsFrontPetSwap.fill(false);
@@ -427,7 +455,7 @@ namespace PetBattles
std::swap(firstTeam, secondTeam);
}
TC_LOG_ERROR("server.loading", "PetBattle ProcessRound: BEFORE TURNS - Team0 pet[{}] HP={}/{} Team1 pet[{}] HP={}/{}",
TC_LOG_DEBUG("server.loading", "PetBattle ProcessRound: BEFORE TURNS - Team0 pet[{}] HP={}/{} Team1 pet[{}] HP={}/{}",
_teams[0].FrontPetIndex, _teams[0].Pets[_teams[0].FrontPetIndex].Health, _teams[0].Pets[_teams[0].FrontPetIndex].MaxHealth,
_teams[1].FrontPetIndex, _teams[1].Pets[_teams[1].FrontPetIndex].Health, _teams[1].Pets[_teams[1].FrontPetIndex].MaxHealth);
@@ -436,7 +464,7 @@ namespace PetBattles
if (!IsFinished())
ProcessTurnForTeam(secondTeam);
TC_LOG_ERROR("server.loading", "PetBattle ProcessRound: AFTER TURNS - Team0 pet[{}] HP={}/{} Team1 pet[{}] HP={}/{} effects={}",
TC_LOG_DEBUG("server.loading", "PetBattle ProcessRound: AFTER TURNS - Team0 pet[{}] HP={}/{} Team1 pet[{}] HP={}/{} effects={}",
_teams[0].FrontPetIndex, _teams[0].Pets[_teams[0].FrontPetIndex].Health, _teams[0].Pets[_teams[0].FrontPetIndex].MaxHealth,
_teams[1].FrontPetIndex, _teams[1].Pets[_teams[1].FrontPetIndex].Health, _teams[1].Pets[_teams[1].FrontPetIndex].MaxHealth,
_roundEffects.size());
@@ -492,7 +520,7 @@ namespace PetBattles
{
if (!_teams[i].Pets[_teams[i].FrontPetIndex].IsAlive())
{
TC_LOG_ERROR("server.loading", "PetBattle: Team {} front pet {} died (HP={}), needs swap",
TC_LOG_DEBUG("server.loading", "PetBattle: Team {} front pet {} died (HP={}), needs swap",
i, _teams[i].FrontPetIndex, _teams[i].Pets[_teams[i].FrontPetIndex].Health);
_needsFrontPetSwap[i] = true;
@@ -503,7 +531,7 @@ namespace PetBattles
int8 nextAlive = _teams[i].GetFirstAlivePetIndex();
if (nextAlive >= 0)
{
TC_LOG_ERROR("server.loading", "PetBattle: Wild/NPC team auto-swap {} -> {}", _teams[i].FrontPetIndex, nextAlive);
TC_LOG_DEBUG("server.loading", "PetBattle: Wild/NPC team auto-swap {} -> {}", _teams[i].FrontPetIndex, nextAlive);
_teams[i].FrontPetIndex = nextAlive;
_needsFrontPetSwap[i] = false;
@@ -525,7 +553,7 @@ namespace PetBattles
if (anyPlayerNeedsSwap)
{
TC_LOG_ERROR("server.loading", "PetBattle: Setting state WAITING_FOR_FRONT_PET, player needs to pick replacement");
TC_LOG_DEBUG("server.loading", "PetBattle: Setting state WAITING_FOR_FRONT_PET, player needs to pick replacement");
_state = PET_BATTLE_STATE_WAITING_FOR_FRONT_PET;
return;
}
@@ -549,7 +577,6 @@ namespace PetBattles
void PetBattle::ProcessTurnForTeam(uint8 teamIdx)
{
PetBattleTeamData& team = _teams[teamIdx];
uint8 opponentTeam = teamIdx == PET_BATTLE_TEAM_1 ? PET_BATTLE_TEAM_2 : PET_BATTLE_TEAM_1;
PetBattlePetData& activePet = team.Pets[team.FrontPetIndex];
// Stunned pets skip their turn
@@ -575,7 +602,7 @@ namespace PetBattles
return;
}
TC_LOG_ERROR("server.loading", "PetBattle ProcessTurnForTeam[{}]: MoveType={} AbilityID={} FrontPet={} PetAlive={}",
TC_LOG_DEBUG("server.loading", "PetBattle ProcessTurnForTeam[{}]: MoveType={} AbilityID={} FrontPet={} PetAlive={}",
teamIdx, int(team.PendingMoveType), team.PendingAbilityID, team.FrontPetIndex, activePet.IsAlive());
switch (team.PendingMoveType)
@@ -596,7 +623,7 @@ namespace PetBattles
if (activePet.AbilityCooldowns[i] > 0)
{
abilityOnCooldown = true;
TC_LOG_ERROR("server.loading", "PetBattle: Ability {} on cooldown ({})", team.PendingAbilityID, activePet.AbilityCooldowns[i]);
TC_LOG_DEBUG("server.loading", "PetBattle: Ability {} on cooldown ({})", team.PendingAbilityID, activePet.AbilityCooldowns[i]);
break;
}
@@ -619,7 +646,7 @@ namespace PetBattles
// Apply ability effects through the DB2 chain
uint32 effectsBefore = _roundEffects.size();
ApplyAbilityEffects(teamIdx, team.FrontPetIndex, team.PendingAbilityID);
TC_LOG_ERROR("server.loading", "PetBattle: ApplyAbilityEffects({}) generated {} effects",
TC_LOG_DEBUG("server.loading", "PetBattle: ApplyAbilityEffects({}) generated {} effects",
team.PendingAbilityID, _roundEffects.size() - effectsBefore);
break;
}
@@ -706,7 +733,7 @@ namespace PetBattles
int8 nextAlive = wildTeam.GetFirstAlivePetIndex();
if (nextAlive >= 0)
{
TC_LOG_ERROR("server.loading", "PetBattle: Captured wild pet[{}], swapping to next alive wild pet[{}]",
TC_LOG_DEBUG("server.loading", "PetBattle: Captured wild pet[{}], swapping to next alive wild pet[{}]",
wildTeam.FrontPetIndex, nextAlive);
wildTeam.FrontPetIndex = nextAlive;
@@ -769,7 +796,7 @@ namespace PetBattles
// Get ability turns from DB2 index
std::vector<BattlePetAbilityTurnEntry const*> const* turns = sPetBattleMgr->GetAbilityTurnsFull(abilityID);
TC_LOG_ERROR("server.loading", "PetBattle ApplyAbilityEffects: abilityID={} turns={}", abilityID, turns ? turns->size() : 0);
TC_LOG_DEBUG("server.loading", "PetBattle ApplyAbilityEffects: abilityID={} turns={}", abilityID, turns ? turns->size() : 0);
if (!turns || turns->empty())
{
// Fallback: apply simple damage if no DB2 turn data exists
@@ -855,7 +882,7 @@ namespace PetBattles
// Get effects for this turn from DB2 index
std::vector<BattlePetAbilityEffectEntry const*> const* effects = sPetBattleMgr->GetTurnEffectsFull(turn->ID);
TC_LOG_ERROR("server.loading", "PetBattle: TurnID={} turnIndex={} effects={}", turn->ID, turnIndex, effects ? effects->size() : 0);
TC_LOG_DEBUG("server.loading", "PetBattle: TurnID={} turnIndex={} effects={}", turn->ID, turnIndex, effects ? effects->size() : 0);
if (!effects || effects->empty())
{
// Multi-turn turn with no DB2 effects ? emit a STATUS_CHANGE so the client
@@ -876,7 +903,7 @@ namespace PetBattles
// Process each effect in order
for (BattlePetAbilityEffectEntry const* effectEntry : *effects)
{
TC_LOG_ERROR("server.loading", "PetBattle: ProcessEffect effectID={} propsID={} Params=[{},{},{},{},{},{}]",
TC_LOG_DEBUG("server.loading", "PetBattle: ProcessEffect effectID={} propsID={} Params=[{},{},{},{},{},{}]",
effectEntry->ID, effectEntry->BattlePetEffectPropertiesID,
effectEntry->Param[0], effectEntry->Param[1], effectEntry->Param[2],
effectEntry->Param[3], effectEntry->Param[4], effectEntry->Param[5]);
@@ -931,10 +958,10 @@ namespace PetBattles
// We map the DB2 BattlePetEffectPropertiesID to our action types
// In practice, the ID maps to specific effect behaviors
BattlePetEffectPropertiesEntry const* effectProps = sBattlePetEffectPropertiesStore.LookupEntry(effectPropsID);
TC_LOG_ERROR("server.loading", "PetBattle ProcessEffect: propsID={} found={} basePower={} accuracy={}",
TC_LOG_DEBUG("server.loading", "PetBattle ProcessEffect: propsID={} found={} basePower={} accuracy={}",
effectPropsID, effectProps != nullptr, basePower, accuracy);
if (effectProps)
TC_LOG_ERROR("server.loading", "PetBattle ProcessEffect: effectCategory(ParamTypeEnum[0])={}", effectProps->ParamTypeEnum[0]);
TC_LOG_DEBUG("server.loading", "PetBattle ProcessEffect: effectCategory(ParamTypeEnum[0])={}", effectProps->ParamTypeEnum[0]);
if (!effectProps)
{
// If no properties entry, treat as simple damage with basePower
@@ -1405,7 +1432,7 @@ namespace PetBattles
break;
}
default:
TC_LOG_ERROR("server.loading", "PetBattle ProcessEffect: UNHANDLED effectCategory={} basePower={} defenderAlive={}",
TC_LOG_WARN("server.loading", "PetBattle ProcessEffect: UNHANDLED effectCategory={} basePower={} defenderAlive={}",
effectCategory, basePower, defender.IsAlive());
// Unhandled effect category - treat as damage if basePower > 0
if (basePower > 0 && defender.IsAlive())
@@ -1957,12 +1984,6 @@ namespace PetBattles
// Speed resolution
// ============================================================================
void PetBattle::ResolveSpeed()
{
// Speed already factored into ProcessRound() turn ordering
// This method is kept for compatibility
}
// ============================================================================
// Death checking
// ============================================================================
@@ -2317,7 +2338,7 @@ namespace PetBattles
if (availableAbilities.empty())
{
TC_LOG_ERROR("server.loading", "PetBattle GenerateWildTeamInput: NO available abilities! AbilityIDs=[{},{},{}] CDs=[{},{},{}] -> PASS",
TC_LOG_DEBUG("server.loading", "PetBattle GenerateWildTeamInput: NO available abilities! AbilityIDs=[{},{},{}] CDs=[{},{},{}] -> PASS",
frontPet.AbilityIDs[0], frontPet.AbilityIDs[1], frontPet.AbilityIDs[2],
frontPet.AbilityCooldowns[0], frontPet.AbilityCooldowns[1], frontPet.AbilityCooldowns[2]);
SubmitInput(PET_BATTLE_TEAM_2, PET_BATTLE_MOVE_PASS, 0, -1);
@@ -2368,7 +2389,7 @@ namespace PetBattles
cumulative += std::max(0.1f, opt.priority);
if (roll <= cumulative)
{
TC_LOG_ERROR("server.loading", "PetBattle GenerateWildTeamInput: SELECTED ability={} (avail={} roll={:.1f}/{:.1f})",
TC_LOG_DEBUG("server.loading", "PetBattle GenerateWildTeamInput: SELECTED ability={} (avail={} roll={:.1f}/{:.1f})",
opt.abilityID, availableAbilities.size(), roll, totalWeight);
SubmitInput(PET_BATTLE_TEAM_2, PET_BATTLE_MOVE_ABILITY, opt.abilityID, -1);
return;
@@ -2376,7 +2397,7 @@ namespace PetBattles
}
// Fallback: use first available ability
TC_LOG_ERROR("server.loading", "PetBattle GenerateWildTeamInput: FALLBACK ability={}", availableAbilities[0].abilityID);
TC_LOG_DEBUG("server.loading", "PetBattle GenerateWildTeamInput: FALLBACK ability={}", availableAbilities[0].abilityID);
SubmitInput(PET_BATTLE_TEAM_2, PET_BATTLE_MOVE_ABILITY, availableAbilities[0].abilityID, -1);
}
+1 -1
View File
@@ -308,7 +308,6 @@ namespace PetBattles
void ApplyPassiveOnDamageDealt(uint8 attackerTeam, uint8 attackerPet, uint8 defenderTeam, uint8 defenderPet, int32 damage);
bool ApplyPassiveOnDeath(uint8 teamIdx, uint8 petIdx);
void ResolveSpeed();
void ProcessTurnForTeam(uint8 teamIdx);
void CheckDeaths();
void AwardExperience();
@@ -327,6 +326,7 @@ namespace PetBattles
uint8 _winnerTeam = 0;
bool _canAwardXP = true;
bool _maxLengthWarningSent = false;
uint32 _roundTimerSecs = 0; // seconds since last input was accepted
uint32 _nextAuraInstanceID = 1;
std::array<PetBattleTeamData, MAX_PET_BATTLE_PLAYERS> _teams;
+61 -14
View File
@@ -475,6 +475,15 @@ void WorldSession::HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattle
return;
}
// Distance check ? player must be reasonably close to the creature
if (!player->IsWithinDistInMap(creature, 50.0f))
{
WorldPackets::BattlePet::PetBattleRequestFailed failed;
failed.Reason = PetBattles::PET_BATTLE_REQUEST_FAIL_TARGET_OUT_OF_RANGE;
SendPacket(failed.Write());
return;
}
// Validate that the creature has a valid BattlePetSpecies entry (Species=0 crashes client)
if (!BattlePets::BattlePetMgr::GetBattlePetSpeciesByCreature(creature->GetEntry()))
{
@@ -539,24 +548,24 @@ void WorldSession::HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattle
for (uint8 t = 0; t < 2; ++t)
{
auto const& pu = initialUpdate.Players[t];
TC_LOG_ERROR("server.loading", "PetBattle InitialUpdate Player[{}]: CharID={} TrapAbilityID={} TrapStatus={} RoundTimeSecs={} FrontPet={} InputFlags={} PetCount={}",
TC_LOG_DEBUG("server.loading", "PetBattle InitialUpdate Player[{}]: CharID={} TrapAbilityID={} TrapStatus={} RoundTimeSecs={} FrontPet={} InputFlags={} PetCount={}",
t, pu.CharacterID.ToString(), pu.TrapAbilityID, pu.TrapStatus, pu.RoundTimeSecs, pu.FrontPet, pu.InputFlags, pu.Pets.size());
for (uint8 p = 0; p < pu.Pets.size(); ++p)
{
auto const& pet = pu.Pets[p];
TC_LOG_ERROR("server.loading", " Pet[{}]: GUID={} Species={} Display={} Collar={} Lvl={} Xp={} HP={}/{} Pow={} Spd={} NpcTM={} Quality={} Status={} Slot={} Abilities={} Auras={} States={} Name='{}'",
TC_LOG_DEBUG("server.loading", " Pet[{}]: GUID={} Species={} Display={} Collar={} Lvl={} Xp={} HP={}/{} Pow={} Spd={} NpcTM={} Quality={} Status={} Slot={} Abilities={} Auras={} States={} Name='{}'",
p, pet.BattlePetGUID.ToString(), pet.SpeciesID, pet.DisplayID, pet.CollarID, pet.Level, pet.Xp,
pet.CurHealth, pet.MaxHealth, pet.Power, pet.Speed, pet.NpcTeamMemberID,
pet.BreedQuality, pet.StatusFlags, pet.Slot, pet.Abilities.size(), pet.Auras.size(), pet.States.size(), pet.CustomName);
for (uint8 a = 0; a < pet.Abilities.size(); ++a)
TC_LOG_ERROR("server.loading", " Ability[{}]: ID={} CD={} LD={} Idx={} Pboid={}",
TC_LOG_DEBUG("server.loading", " Ability[{}]: ID={} CD={} LD={} Idx={} Pboid={}",
a, pet.Abilities[a].AbilityID, pet.Abilities[a].CooldownRemaining, pet.Abilities[a].LockdownRemaining, pet.Abilities[a].AbilityIndex, pet.Abilities[a].Pboid);
for (uint8 s = 0; s < pet.States.size(); ++s)
TC_LOG_ERROR("server.loading", " State[{}]: ID={} Val={}",
TC_LOG_DEBUG("server.loading", " State[{}]: ID={} Val={}",
s, pet.States[s].StateID, pet.States[s].StateValue);
}
}
TC_LOG_ERROR("server.loading", "PetBattle InitialUpdate: CurRound={} State={} NpcCreature={} NpcDisplay={} WildGUID={} IsPVP={} CanAwardXP={} PktSize={}",
TC_LOG_DEBUG("server.loading", "PetBattle InitialUpdate: CurRound={} State={} NpcCreature={} NpcDisplay={} WildGUID={} IsPVP={} CanAwardXP={} PktSize={}",
initialUpdate.CurRound, initialUpdate.CurPetBattleState, initialUpdate.NpcCreatureID, initialUpdate.NpcDisplayID,
initialUpdate.InitialWildPetGUID.ToString(), initialUpdate.IsPVP, initialUpdate.CanAwardXP, pkt->size());
@@ -729,8 +738,12 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
if (!battle)
return;
// Client sends MoveType=FinalRoundOk while waiting for FinalNotify ? ignore it
if (battle->IsFinalRound())
// Only accept input during active round states
if (battle->IsFinalRound() || battle->IsFinished())
return;
// Validate MoveType range
if (petBattleInput.MoveType < 0 || petBattleInput.MoveType > static_cast<int32>(PetBattles::PET_BATTLE_MOVE_PASS))
return;
// Determine which team this player is
@@ -738,6 +751,10 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
if (battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2).PlayerGUID == player->GetGUID())
teamIdx = PetBattles::PET_BATTLE_TEAM_2;
// Don't accept duplicate input for the same round
if (battle->GetTeam(teamIdx).HasInputThisRound)
return;
battle->SubmitInput(teamIdx,
PetBattles::PetBattleMoveType(petBattleInput.MoveType),
petBattleInput.AbilityID,
@@ -767,7 +784,7 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
BuildRoundCooldowns(roundResult.Cooldowns, battle);
BuildPetXDied(roundResult.PetXDied, battle);
TC_LOG_ERROR("server.loading", "PetBattle ROUND_RESULT: Round={} State={} FinalRound={} Effects={} Cooldowns={} Deaths={} P0Flags={} P1Flags={}",
TC_LOG_DEBUG("server.loading", "PetBattle ROUND_RESULT: Round={} State={} FinalRound={} Effects={} Cooldowns={} Deaths={} P0Flags={} P1Flags={}",
roundResult.CurRound, roundResult.NextPetBattleState,
battle->IsFinalRound(),
roundResult.Effects.size(), roundResult.Cooldowns.size(), roundResult.PetXDied.size(),
@@ -777,7 +794,7 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
for (std::size_t e = 0; e < roundResult.Effects.size(); ++e)
{
auto const& eff = roundResult.Effects[e];
TC_LOG_ERROR("server.loading", " Effect[{}]: AbilEffID={} Flags=0x{:X} Idx={} CasterPBOID={} StackDepth={} Targets={}",
TC_LOG_DEBUG("server.loading", " Effect[{}]: AbilEffID={} Flags=0x{:X} Idx={} CasterPBOID={} StackDepth={} Targets={}",
e, eff.AbilityEffectID, eff.Flags, eff.EffectIndex, eff.CasterPBOID, eff.StackDepth, eff.Targets.size());
for (std::size_t tgt = 0; tgt < eff.Targets.size(); ++tgt)
{
@@ -785,7 +802,7 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
std::string paramStr;
for (int32 p : t.Params)
paramStr += std::to_string(p) + " ";
TC_LOG_ERROR("server.loading", " Target[{}]: Type={} Remaining(PBOID)={} Params=[{}]",
TC_LOG_DEBUG("server.loading", " Target[{}]: Type={} Remaining(PBOID)={} Params=[{}]",
tgt, t.Type, t.Remaining, paramStr);
}
}
@@ -811,6 +828,10 @@ void WorldSession::HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBa
if (!battle)
return;
// Only allow replacement during WAITING_FOR_FRONT_PET or ROUND_IN_PROGRESS (voluntary swap)
if (battle->IsFinalRound() || battle->IsFinished())
return;
uint8 teamIdx = PetBattles::PET_BATTLE_TEAM_1;
if (battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2).PlayerGUID == player->GetGUID())
teamIdx = PetBattles::PET_BATTLE_TEAM_2;
@@ -819,22 +840,22 @@ void WorldSession::HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBa
int8 newPetIdx = petBattleReplaceFrontPet.FrontPetIndex;
PetBattles::PetBattleTeamData& team = battle->GetTeam(teamIdx);
TC_LOG_ERROR("server.loading", "PetBattle ReplaceFrontPet: team={} newPetIdx={} petCount={} battleState={} needsSwap={}",
TC_LOG_DEBUG("server.loading", "PetBattle ReplaceFrontPet: team={} newPetIdx={} petCount={} battleState={} needsSwap={}",
teamIdx, newPetIdx, team.PetCount, int(battle->GetBattleState()), battle->NeedsFrontPetSwap(teamIdx));
if (newPetIdx < 0 || newPetIdx >= team.PetCount)
{
TC_LOG_ERROR("server.loading", "PetBattle ReplaceFrontPet: REJECTED - invalid index");
TC_LOG_DEBUG("server.loading", "PetBattle ReplaceFrontPet: REJECTED - invalid index");
return;
}
if (!team.Pets[newPetIdx].IsAlive())
{
TC_LOG_ERROR("server.loading", "PetBattle ReplaceFrontPet: REJECTED - pet {} is dead (HP={})",
TC_LOG_DEBUG("server.loading", "PetBattle ReplaceFrontPet: REJECTED - pet {} is dead (HP={})",
newPetIdx, team.Pets[newPetIdx].Health);
return;
}
TC_LOG_ERROR("server.loading", "PetBattle ReplaceFrontPet: Swapping team {} front pet {} -> {}",
TC_LOG_DEBUG("server.loading", "PetBattle ReplaceFrontPet: Swapping team {} front pet {} -> {}",
teamIdx, team.FrontPetIndex, newPetIdx);
// Set the new front pet and transition state back to ROUND_IN_PROGRESS
@@ -1030,6 +1051,32 @@ void WorldSession::HandleJoinPetBattleQueue(WorldPackets::BattlePet::JoinPetBatt
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;
}
}
sPetBattleMgr->JoinQueue(player->GetGUID());
WorldPackets::BattlePet::PetBattleQueueStatus status;
@@ -679,6 +679,10 @@ namespace WorldPackets
WorldPacket const* Write() override { return &_worldPacket; }
};
// TODO: SMSG_PET_BATTLE_CHAT_RESTRICTED should be sent when a player attempts to use
// restricted chat channels (e.g., General, Trade) while in an active pet battle.
// The check should be added to the chat message handler (ChatHandler.cpp) when the
// player's PetBattle state is active.
class PetBattleChatRestricted final : public ServerPacket
{
public:
+1 -1
View File
@@ -238,7 +238,7 @@ void OpcodeTable::InitializeClientOpcodes()
DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL_LOCK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetRequestJournalLock);
DEFINE_HANDLER(CMSG_BATTLE_PET_SET_BATTLE_SLOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetSetBattleSlot);
DEFINE_HANDLER(CMSG_BATTLE_PET_SET_FLAGS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetSetFlags);
DEFINE_HANDLER(CMSG_BATTLE_PET_SUMMON, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleBattlePetSummon);
DEFINE_HANDLER(CMSG_BATTLE_PET_SUMMON, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetSummon);
DEFINE_HANDLER(CMSG_BATTLE_PET_UPDATE_DISPLAY_NOTIFY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetUpdateDisplayNotify);
DEFINE_HANDLER(CMSG_BATTLE_PET_UPDATE_NOTIFY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetUpdateNotify);
DEFINE_HANDLER(CMSG_BEGIN_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBeginTradeOpcode);