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:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user