Pet Battles: Fix slot duplication, aura persistence, trap anim, achievement name

Fix four bugs reported by tester:

- Slot swap duplication: HandleBattlePetSetBattleSlot now properly swaps
  pets between source and target slots instead of only assigning the target,
  which left the pet duplicated in the old slot

- Aura persistence: Populate petInfo.Auras in BuildPetBattlePlayerUpdate
  so the client knows which auras are active on each pet. Fix aura round
  effect params to send AuraInstanceID + AbilityID (matching wire format)
  and look up real RoundsRemaining/CurrentRound instead of hardcoding 0

- Trap animation: Resolve the actual BattlePetAbilityEffect.ID from the
  DB2 chain for ability 427 instead of using the raw ability ID, so the
  client can look up the correct visual spell for the capture animation

- Achievement name: Change hotfix_data status from 3 (insert) to 1 (valid)
  for achievement 7433 which already exists in the retail client DB2;
  status=3 corrupted the client-side entry causing empty achievement links
This commit is contained in:
luis
2026-02-27 20:22:09 -03:00
parent 057b22a0f6
commit 564599b32d
2 changed files with 124 additions and 31 deletions
+54 -25
View File
@@ -659,6 +659,24 @@ namespace PetBattles
float captureChance = GetCaptureChance(trapLevel, healthPct, wildPet.Quality, _trapFailBonus);
// Resolve the actual BattlePetAbilityEffect ID for the trap ability so the
// client can look up the visual spell/animation (AbilityID != EffectID)
uint32 trapEffectID = 0;
if (std::vector<uint32> const* turnIDs = sPetBattleMgr->GetAbilityTurns(PET_BATTLE_TRAP_ABILITY_ID))
{
for (uint32 turnID : *turnIDs)
{
if (std::vector<BattlePetAbilityEffectEntry const*> const* effects = sPetBattleMgr->GetTurnEffectsFull(turnID))
{
if (!effects->empty())
{
trapEffectID = effects->front()->ID;
break;
}
}
}
}
if (frand(0.0f, 1.0f) < captureChance)
{
// Capture success ? pet is captured alive (keeps its current HP)
@@ -667,7 +685,7 @@ namespace PetBattles
// Emit STATUS_CHANGE effect with TRAPPED status so client plays capture crate animation
{
PetBattleRoundEffect effect;
effect.AbilityEffectID = team.TrapAbilityID;
effect.AbilityEffectID = trapEffectID;
effect.EffectType = PET_BATTLE_EFFECT_STATUS_CHANGE;
effect.SourceTeam = teamIdx;
effect.SourcePet = team.FrontPetIndex;
@@ -699,7 +717,7 @@ namespace PetBattles
// Emit STATUS_CHANGE with MISS flag so client shows failed trap animation
PetBattleRoundEffect effect;
effect.AbilityEffectID = team.TrapAbilityID;
effect.AbilityEffectID = trapEffectID;
effect.EffectType = PET_BATTLE_EFFECT_STATUS_CHANGE;
effect.Flags = PET_BATTLE_EFFECT_FLAG_MISS;
effect.SourceTeam = teamIdx;
@@ -1002,16 +1020,20 @@ namespace PetBattles
auraType, auraDuration, tickDamage, attacker.PetType,
attackerTeam, attackerPet);
PetBattleRoundEffect roundEffect;
roundEffect.AbilityEffectID = effect->ID;
roundEffect.EffectType = PET_BATTLE_EFFECT_AURA_APPLY;
roundEffect.SourceTeam = attackerTeam;
roundEffect.SourcePet = attackerPet;
roundEffect.TargetTeam = defenderTeam;
roundEffect.TargetPet = defenderPet;
roundEffect.Param1 = abilityID;
roundEffect.Param2 = auraDuration;
_roundEffects.push_back(roundEffect);
{
PetBattlePetData const& targetPet = _teams[defenderTeam].Pets[defenderPet];
PetBattleAura const& newAura = targetPet.Auras.back();
PetBattleRoundEffect roundEffect;
roundEffect.AbilityEffectID = effect->ID;
roundEffect.EffectType = PET_BATTLE_EFFECT_AURA_APPLY;
roundEffect.SourceTeam = attackerTeam;
roundEffect.SourcePet = attackerPet;
roundEffect.TargetTeam = defenderTeam;
roundEffect.TargetPet = defenderPet;
roundEffect.Param1 = newAura.AuraInstanceID;
roundEffect.Param2 = abilityID;
_roundEffects.push_back(roundEffect);
}
break;
}
case 13: // Periodic heal aura
@@ -1026,16 +1048,20 @@ namespace PetBattles
PET_BATTLE_AURA_HOT, auraDuration, tickHealing, attacker.PetType,
attackerTeam, attackerPet);
PetBattleRoundEffect roundEffect;
roundEffect.AbilityEffectID = effect->ID;
roundEffect.EffectType = PET_BATTLE_EFFECT_AURA_APPLY;
roundEffect.SourceTeam = attackerTeam;
roundEffect.SourcePet = attackerPet;
roundEffect.TargetTeam = attackerTeam;
roundEffect.TargetPet = attackerPet;
roundEffect.Param1 = abilityID;
roundEffect.Param2 = auraDuration;
_roundEffects.push_back(roundEffect);
{
PetBattlePetData const& selfPet = _teams[attackerTeam].Pets[attackerPet];
PetBattleAura const& newAura = selfPet.Auras.back();
PetBattleRoundEffect roundEffect;
roundEffect.AbilityEffectID = effect->ID;
roundEffect.EffectType = PET_BATTLE_EFFECT_AURA_APPLY;
roundEffect.SourceTeam = attackerTeam;
roundEffect.SourcePet = attackerPet;
roundEffect.TargetTeam = attackerTeam;
roundEffect.TargetPet = attackerPet;
roundEffect.Param1 = newAura.AuraInstanceID;
roundEffect.Param2 = abilityID;
_roundEffects.push_back(roundEffect);
}
break;
}
case 3: // Change state
@@ -1320,7 +1346,8 @@ namespace PetBattles
removeEffect.EffectType = PET_BATTLE_EFFECT_AURA_CANCEL;
removeEffect.TargetTeam = targetTeam;
removeEffect.TargetPet = targetPet;
removeEffect.Param1 = pet.Auras.front().AbilityID;
removeEffect.Param1 = pet.Auras.front().AuraInstanceID;
removeEffect.Param2 = pet.Auras.front().AbilityID;
_roundEffects.push_back(removeEffect);
pet.Auras.erase(pet.Auras.begin());
@@ -1359,7 +1386,8 @@ namespace PetBattles
roundEffect.EffectType = PET_BATTLE_EFFECT_AURA_CANCEL;
roundEffect.TargetTeam = targetTeam;
roundEffect.TargetPet = targetPet;
roundEffect.Param1 = abilityID;
roundEffect.Param1 = it->AuraInstanceID;
roundEffect.Param2 = abilityID;
_roundEffects.push_back(roundEffect);
pet.Auras.erase(it);
@@ -1440,7 +1468,8 @@ namespace PetBattles
roundEffect.EffectType = PET_BATTLE_EFFECT_AURA_CANCEL;
roundEffect.TargetTeam = t;
roundEffect.TargetPet = p;
roundEffect.Param1 = aura.AbilityID;
roundEffect.Param1 = aura.AuraInstanceID;
roundEffect.Param2 = aura.AbilityID;
_roundEffects.push_back(roundEffect);
pet.Auras.erase(pet.Auras.begin() + i);
+70 -6
View File
@@ -42,9 +42,39 @@ void WorldSession::HandleBattlePetRequestJournalLock(WorldPackets::BattlePet::Ba
void WorldSession::HandleBattlePetSetBattleSlot(WorldPackets::BattlePet::BattlePetSetBattleSlot& battlePetSetBattleSlot)
{
if (BattlePets::BattlePet* pet = GetBattlePetMgr()->GetPet(battlePetSetBattleSlot.PetGuid))
if (WorldPackets::BattlePet::BattlePetSlot* slot = GetBattlePetMgr()->GetSlot(BattlePets::BattlePetSlot(battlePetSetBattleSlot.Slot)))
slot->Pet = pet->PacketInfo;
BattlePets::BattlePetMgr* petMgr = GetBattlePetMgr();
BattlePets::BattlePet* pet = petMgr->GetPet(battlePetSetBattleSlot.PetGuid);
if (!pet)
return;
WorldPackets::BattlePet::BattlePetSlot* targetSlot = petMgr->GetSlot(BattlePets::BattlePetSlot(battlePetSetBattleSlot.Slot));
if (!targetSlot || targetSlot->Locked)
return;
// Find which slot this pet currently occupies (if any)
WorldPackets::BattlePet::BattlePetSlot* sourceSlot = nullptr;
for (uint8 i = 0; i < uint8(BattlePets::BattlePetSlot::Count); ++i)
{
WorldPackets::BattlePet::BattlePetSlot* slot = petMgr->GetSlot(BattlePets::BattlePetSlot(i));
if (slot && slot->Pet.Guid == battlePetSetBattleSlot.PetGuid)
{
sourceSlot = slot;
break;
}
}
if (sourceSlot == targetSlot)
return; // Already in that slot
// Swap: move whatever is in the target slot to the source slot
if (sourceSlot)
{
sourceSlot->Pet = targetSlot->Pet;
}
// Place the pet in the target slot
targetSlot->Pet = pet->PacketInfo;
}
void WorldSession::HandleBattlePetModifyName(WorldPackets::BattlePet::BattlePetModifyName& battlePetModifyName)
@@ -201,6 +231,18 @@ static void BuildPetBattlePlayerUpdate(WorldPackets::BattlePet::PetBattlePlayerU
petInfo.Abilities.push_back(ability);
}
// Populate auras currently active on this pet
for (PetBattles::PetBattleAura const& aura : petData.Auras)
{
WorldPackets::BattlePet::PetBattleAuraInfo auraInfo;
auraInfo.AbilityID = aura.AbilityID;
auraInfo.InstanceID = aura.AuraInstanceID;
auraInfo.RoundsRemaining = aura.RemainingRounds;
auraInfo.CurrentRound = aura.CurrentRound;
auraInfo.CasterPBOID = aura.CasterTeam * PetBattles::MAX_PET_BATTLE_TEAM_SIZE + aura.CasterPet;
petInfo.Auras.push_back(auraInfo);
}
// Populate States with base breed+species stats from DB2 (retail format)
petInfo.States.push_back({ BattlePets::STATE_STAT_POWER, petData.BasePower });
petInfo.States.push_back({ BattlePets::STATE_STAT_STAMINA, petData.BaseStamina });
@@ -293,12 +335,34 @@ static void BuildRoundEffects(std::vector<WorldPackets::BattlePet::PetBattleEffe
case PetBattles::PET_BATTLE_EFFECT_AURA_APPLY:
case PetBattles::PET_BATTLE_EFFECT_AURA_CANCEL:
case PetBattles::PET_BATTLE_EFFECT_AURA_CHANGE:
target.Type = 1; // Aura
{
target.Type = 1; // Aura: 4 params
target.Params.push_back(roundEffect.Param1); // AuraInstanceID
target.Params.push_back(roundEffect.Param2); // AuraAbilityID
target.Params.push_back(0); // RoundsRemaining
target.Params.push_back(0); // CurrentRound
// Look up remaining rounds from the live aura on the target pet
int32 roundsRemaining = 0;
int32 currentRound = 0;
if (roundEffect.EffectType != PetBattles::PET_BATTLE_EFFECT_AURA_CANCEL)
{
PetBattles::PetBattleTeamData const& targetTeam = battle->GetTeam(roundEffect.TargetTeam);
if (roundEffect.TargetPet < targetTeam.PetCount)
{
for (PetBattles::PetBattleAura const& aura : targetTeam.Pets[roundEffect.TargetPet].Auras)
{
if (aura.AuraInstanceID == static_cast<uint32>(roundEffect.Param1))
{
roundsRemaining = aura.RemainingRounds;
currentRound = aura.CurrentRound;
break;
}
}
}
}
target.Params.push_back(roundsRemaining);
target.Params.push_back(currentRound);
break;
}
case PetBattles::PET_BATTLE_EFFECT_SET_STATE:
target.Type = 2; // State
target.Params.push_back(roundEffect.Param1); // StateID