Files
felwell-core/src/server/game/BattlePets/PetBattle.cpp
T
BlaMacfly afb770d096 Mascottes de combat : soin chez le maitre d ecurie et criteres de hauts faits
Signalement Discord : la quete 31589 (Panser ses plaies) est impossible a
valider, un clic sur Murog ouvre directement la boutique.

Donnees (sql/sylvania/battlepet_stablemaster_heal.sql) :
- Murog (47764) et Jaelysia (10085) n avaient aucun gossip_menu_id : ils
  retombaient sur le menu 0, dont la seule option compatible pour un
  non-chasseur est le vendeur, que le client selectionne automatiquement.
- le menu 9821 des maitres d ecurie avait perdu ses options vendeur et
  ecurie : seule restait l option de soin.
- 10 des 12 maitres d ecurie de la chaine On The Mend n avaient pas le
  SmartAI qui soigne les mascottes et donne le credit de quete.
- le credit de Jenova Stoneshield pointait sur Elwynn (64320) au lieu de
  Hurlevent (65214).

Core :
- CRITERIA_TYPE_BATTLEPET_WIN, _LEVEL_UP et CAPTURE_PET_IN_BATTLE etaient
  dans la liste  non implemente  : le systeme de combat de mascottes
  remontait bien les evenements, le gestionnaire de criteres les jetait.
- l ordre des miscValue de _LEVEL_UP ne correspondait pas a ce qu attendent
  les conditions (espece en miscValue1, niveau en miscValue2).
2026-09-10 19:28:17 +02:00

1787 lines
62 KiB
C++

/*
* This file is part of the DestinyCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PetBattle.h"
#include "PetBattleAbilityEffect.h"
#include "WildBattlePet.h"
#include "DB2Store.h"
#include "DatabaseEnv.h"
#include "AchievementMgr.h"
#include "ObjectMgr.h"
#include "ObjectAccessor.h"
#include "ScriptMgr.h"
#include "GameTables.h"
#include "BattlePetPackets.h"
#include "DBCEnums.h"
#define PETBATTLE_UPDATE_INTERVAL 300
void BattlePet::Load(Field* fields)
{
JournalID = ObjectGuid::Create<HighGuid::BattlePet>(fields[0].GetUInt64());
Slot = fields[1].GetInt32();
Name = fields[2].GetString();
NameTimeStamp = fields[3].GetUInt32();
Species = fields[4].GetUInt32();
Quality = fields[5].GetUInt32();
Breed = fields[6].GetUInt32();
Level = fields[7].GetUInt32();
XP = fields[8].GetUInt32();
DisplayModelID = fields[9].GetUInt32();
Health = fields[10].GetInt32();
Flags = fields[11].GetUInt32();
InfoPower = fields[12].GetInt32();
InfoMaxHealth = fields[13].GetInt32();
InfoSpeed = fields[14].GetInt32();
InfoGender = fields[15].GetInt32();
AccountID = fields[16].GetInt32();
DeclinedNames[0] = fields[17].GetString();
DeclinedNames[1] = fields[18].GetString();
DeclinedNames[2] = fields[19].GetString();
DeclinedNames[3] = fields[20].GetString();
DeclinedNames[4] = fields[21].GetString();
UpdateAbilities();
needSave = false;
needDelete = false;
}
void BattlePet::UpdateAbilities()
{
for (auto& ability : Abilities)
ability = 0;
for (auto const& speciesXAbilityInfo : sBattlePetSpeciesXAbilityStore)
{
if (speciesXAbilityInfo->BattlePetSpeciesID != Species ||
speciesXAbilityInfo->RequiredLevel > Level ||
speciesXAbilityInfo->SlotEnum < 0 ||
speciesXAbilityInfo->SlotEnum >= MAX_PETBATTLE_ABILITIES)
continue;
if (speciesXAbilityInfo->RequiredLevel < 5)
Abilities[speciesXAbilityInfo->SlotEnum] = speciesXAbilityInfo->BattlePetAbilityID;
else
{
switch (speciesXAbilityInfo->SlotEnum)
{
case 0:
if (Flags & BATTLEPET_FLAG_ABILITY_1_SECOND)
Abilities[speciesXAbilityInfo->SlotEnum] = speciesXAbilityInfo->BattlePetAbilityID;
break;
case 1:
if (Flags & BATTLEPET_FLAG_ABILITY_2_SECOND)
Abilities[speciesXAbilityInfo->SlotEnum] = speciesXAbilityInfo->BattlePetAbilityID;
break;
case 2:
if (Flags & BATTLEPET_FLAG_ABILITY_3_SECOND)
Abilities[speciesXAbilityInfo->SlotEnum] = speciesXAbilityInfo->BattlePetAbilityID;
break;
default:
break;
}
}
}
}
void BattlePet::CloneFrom(std::shared_ptr<BattlePet> & battlePet)
{
JournalID = battlePet->JournalID;
Slot = battlePet->Slot;
Name = battlePet->Name;
NameTimeStamp = battlePet->NameTimeStamp;
Species = battlePet->Species;
Quality = battlePet->Quality;
Breed = battlePet->Breed;
Level = battlePet->Level;
XP = battlePet->XP;
DisplayModelID = battlePet->DisplayModelID;
Health = battlePet->Health;
Flags = battlePet->Flags;
InfoPower = battlePet->InfoPower;
InfoMaxHealth = battlePet->InfoMaxHealth;
InfoSpeed = battlePet->InfoSpeed;
InfoGender = battlePet->InfoGender;
AccountID = battlePet->AccountID;
DeclinedNames[0] = battlePet->DeclinedNames[0];
DeclinedNames[1] = battlePet->DeclinedNames[1];
DeclinedNames[2] = battlePet->DeclinedNames[2];
DeclinedNames[3] = battlePet->DeclinedNames[3];
DeclinedNames[4] = battlePet->DeclinedNames[4];
for (uint8 i = 0; i < MAX_PETBATTLE_ABILITIES; ++i)
Abilities[i] = battlePet->Abilities[i];
}
void BattlePet::Save(CharacterDatabaseTransaction& trans)
{
if (!needSave || needDelete)
return;
CharacterDatabasePreparedStatement* statement = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PETBATTLE);
statement->setInt32(0, Slot);
statement->setString(1, Name);
statement->setUInt32(2, NameTimeStamp);
statement->setUInt32(3, Species);
statement->setUInt32(4, Quality);
statement->setUInt32(5, Breed);
statement->setUInt32(6, Level);
statement->setUInt32(7, XP);
statement->setUInt32(8, DisplayModelID);
statement->setInt32(9, Health);
statement->setUInt32(10, Flags);
statement->setInt32(11, InfoPower);
statement->setInt32(12, InfoMaxHealth);
statement->setInt32(13, InfoSpeed);
statement->setInt32(14, InfoGender);
statement->setString(15, DeclinedNames[0]);
statement->setString(16, DeclinedNames[1]);
statement->setString(17, DeclinedNames[2]);
statement->setString(18, DeclinedNames[3]);
statement->setString(19, DeclinedNames[4]);
statement->setInt32(20, AccountID);
statement->setUInt64(21, JournalID.GetCounter());
trans->Append(statement);
needSave = false;
}
void BattlePet::AddToPlayer(Player* player, CharacterDatabaseTransaction& trans)
{
AccountID = player->GetSession()->GetAccountId();
ObjectGuid::LowType guidlow = sObjectMgr->GetGenerator<HighGuid::BattlePet>().Generate();
JournalID = ObjectGuid::Create<HighGuid::BattlePet>(guidlow);
CharacterDatabasePreparedStatement* statement = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PETBATTLE);
statement->setInt32(0, Slot);
statement->setString(1, Name);
statement->setUInt32(2, NameTimeStamp);
statement->setUInt32(3, Species);
statement->setUInt32(4, Quality);
statement->setUInt32(5, Breed);
statement->setUInt32(6, Level);
statement->setUInt32(7, XP);
statement->setUInt32(8, DisplayModelID);
statement->setInt32(9, Health);
statement->setUInt32(10, Flags);
statement->setInt32(11, InfoPower);
statement->setInt32(12, InfoMaxHealth);
statement->setInt32(13, InfoSpeed);
statement->setInt32(14, InfoGender);
statement->setString(15, DeclinedNames[0]);
statement->setString(16, DeclinedNames[1]);
statement->setString(17, DeclinedNames[2]);
statement->setString(18, DeclinedNames[3]);
statement->setString(19, DeclinedNames[4]);
statement->setInt32(20, AccountID);
statement->setUInt64(21, JournalID.GetCounter());
trans->Append(statement);
needSave = false;
}
void BattlePet::Remove(Player* /*player*/)
{
CharacterDatabasePreparedStatement* statement = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETBATTLE);
statement->setUInt64(0, JournalID.GetCounter());
CharacterDatabase.Execute(statement);
needDelete = true;
}
ObjectGuid::LowType BattlePet::AddToPlayer(Player* player)
{
AccountID = player->GetSession()->GetAccountId();
auto guidlow = sObjectMgr->GetGenerator<HighGuid::BattlePet>().Generate();
JournalID = ObjectGuid::Create<HighGuid::BattlePet>(guidlow);
CharacterDatabasePreparedStatement* statement = CharacterDatabase.GetPreparedStatement(CHAR_INS_PETBATTLE);
//statement->setUInt64(0, JournalID.GetCounter());
statement->setInt32(0, Slot);
statement->setString(1, Name);
statement->setUInt32(2, NameTimeStamp);
statement->setUInt32(3, Species);
statement->setUInt32(4, Quality);
statement->setUInt32(5, Breed);
statement->setUInt32(6, Level);
statement->setUInt32(7, XP);
statement->setUInt32(8, DisplayModelID);
statement->setInt32(9, Health);
statement->setUInt32(10, Flags);
statement->setInt32(11, InfoPower);
statement->setInt32(12, InfoMaxHealth);
statement->setInt32(13, InfoSpeed);
statement->setInt32(14, InfoGender);
statement->setInt32(15, AccountID);
statement->setString(16, DeclinedNames[0]);
statement->setString(17, DeclinedNames[1]);
statement->setString(18, DeclinedNames[2]);
statement->setString(19, DeclinedNames[3]);
statement->setString(20, DeclinedNames[4]);
CharacterDatabase.Execute(statement);
player->UpdateCriteria(CRITERIA_TYPE_COLLECT_BATTLEPET, 1);
needSave = false;
return guidlow;
}
void BattlePet::UpdateStats()
{
int32 _InfoGender = InfoGender;
int32 _InfoPower = InfoPower;
int32 _InfoSpeed = InfoSpeed;
int32 _InfoMaxHealth = InfoMaxHealth;
int32* stats = &InfoPower;
InfoGender = 0;
for (size_t i = 0; i < 3; ++i)
stats[i] = 1000;
for (auto const& v : sBattlePetBreedStateStore)
{
if (v->BattlePetBreedID != Breed)
continue;
switch (v->BattlePetStateID)
{
case BATTLEPET_STATE_Stat_Power:
case BATTLEPET_STATE_Stat_Stamina:
case BATTLEPET_STATE_Stat_Speed:
stats[v->BattlePetStateID - BATTLEPET_STATE_Stat_Power] = v->Value;
break;
case BATTLEPET_STATE_Stat_Gender:
InfoGender = v->Value;
break;
default:
break;
}
}
for (auto const& v : sBattlePetSpeciesStateStore)
{
if (v->BattlePetSpeciesID != Species)
continue;
switch (v->BattlePetStateID)
{
case BATTLEPET_STATE_Stat_Power:
case BATTLEPET_STATE_Stat_Stamina:
case BATTLEPET_STATE_Stat_Speed:
stats[v->BattlePetStateID - BATTLEPET_STATE_Stat_Power] += v->Value;
break;
default:
break;
}
}
float qualityFactor = 1.0f;
if (auto aualityInfo = sBattlePetBreedQualityStore[7 + Quality])
qualityFactor = aualityInfo->StateMultiplier;
for (size_t i = 0; i < 3; ++i)
stats[i] = stats[i] * Level * qualityFactor;
InfoPower = InfoPower / 100;
InfoSpeed = InfoSpeed / 100;
InfoMaxHealth = int32(floor((10000.0f + InfoMaxHealth * 5.0f) / 100.0f + 0.5));
if (_InfoGender != InfoGender || _InfoPower != InfoPower || _InfoSpeed != InfoSpeed || _InfoMaxHealth != InfoMaxHealth || Health != InfoMaxHealth)
needSave = true;
Health = InfoMaxHealth;
}
BattlePetInstance::BattlePetInstance() : TeamID(0), ID(0), Cooldowns{}, Lockdowns{}, States{}, PetBattleInstance(nullptr), OldLevel(0), OldXP(0)
{
OriginalBattlePet = std::shared_ptr<BattlePet>();
OriginalCreature.Clear();
}
std::shared_ptr<BattlePetInstance> BattlePetInstance::CloneForBattle(std::shared_ptr<BattlePetInstance> const& battlePet)
{
auto battlePetInstance = std::make_shared<BattlePetInstance>();
battlePetInstance->JournalID = battlePet->JournalID;
battlePetInstance->Slot = battlePet->Slot;
battlePetInstance->Name = battlePet->Name;
battlePetInstance->NameTimeStamp = battlePet->NameTimeStamp;
battlePetInstance->Species = battlePet->Species;
battlePetInstance->Quality = battlePet->Quality;
battlePetInstance->Breed = battlePet->Breed;
battlePetInstance->Level = battlePet->Level;
battlePetInstance->XP = battlePet->XP;
battlePetInstance->DisplayModelID = battlePet->DisplayModelID;
battlePetInstance->Health = battlePet->Health;
battlePetInstance->Flags = battlePet->Flags;
battlePetInstance->InfoPower = battlePet->InfoPower;
battlePetInstance->InfoMaxHealth = battlePet->InfoMaxHealth;
battlePetInstance->InfoSpeed = battlePet->InfoSpeed;
battlePetInstance->InfoGender = battlePet->InfoGender;
battlePetInstance->AccountID = battlePet->AccountID;
battlePetInstance->DeclinedNames[0] = battlePet->DeclinedNames[0];
battlePetInstance->DeclinedNames[1] = battlePet->DeclinedNames[1];
battlePetInstance->DeclinedNames[2] = battlePet->DeclinedNames[2];
battlePetInstance->DeclinedNames[3] = battlePet->DeclinedNames[3];
battlePetInstance->DeclinedNames[4] = battlePet->DeclinedNames[4];
for (uint8 i = 0; i < MAX_PETBATTLE_ABILITIES; ++i)
battlePetInstance->Abilities[i] = battlePet->Abilities[i];
return battlePetInstance;
}
bool BattlePetInstance::IsAlive()
{
return States[BATTLEPET_STATE_Is_Dead] == 0;
}
bool BattlePetInstance::CanAttack()
{
return !States[BATTLEPET_STATE_turnLock] && !States[BATTLEPET_STATE_Mechanic_IsStunned];
}
int32 BattlePetInstance::GetMaxHealth()
{
return int32(floor((10000.0f + 5.0f * States[BATTLEPET_STATE_Stat_Stamina] + States[BATTLEPET_STATE_maxHealthBonus] * States[BATTLEPET_STATE_Internal_InitialLevel]) / 100.0f + 0.5));
}
int32 BattlePetInstance::GetSpeed()
{
int32 modPercent = States[BATTLEPET_STATE_Mod_SpeedPercent];
// Passive: flying
if (States[BATTLEPET_STATE_Passive_Flying])
modPercent += 50;
return (States[BATTLEPET_STATE_Stat_Speed] + CalculatePct(States[BATTLEPET_STATE_Stat_Speed], modPercent)) / 100;
}
uint32 BattlePetInstance::GetMaxXPForCurrentLevel()
{
if (auto xpTable = sBattlePetXPTable.GetRow(Level))
return xpTable->Xp * xpTable->Wins;
else
return 0;
}
uint32 BattlePetInstance::GetXPEarn(uint32 targetPetID)
{
auto targetPet = PetBattleInstance->Pets[targetPetID];
int32 levelDiff = targetPet->Level - Level;
if (levelDiff < -4 || levelDiff > 2)
return 0;
return (Level + 9) * (levelDiff + 5);
}
void BattlePetInstance::UpdateOriginalInstance(Player* player)
{
if (!OriginalBattlePet)
return;
bool sendUpdate = OriginalBattlePet->Level != Level || OriginalBattlePet->XP != XP;
OriginalBattlePet->JournalID = JournalID;
OriginalBattlePet->Slot = Slot;
OriginalBattlePet->Species = Species;
OriginalBattlePet->Quality = Quality;
OriginalBattlePet->Breed = Breed;
OriginalBattlePet->Level = Level;
OriginalBattlePet->XP = XP;
OriginalBattlePet->DisplayModelID = DisplayModelID;
OriginalBattlePet->Health = Health;
OriginalBattlePet->Flags = Flags;
OriginalBattlePet->InfoPower = InfoPower;
OriginalBattlePet->InfoMaxHealth = InfoMaxHealth;
OriginalBattlePet->InfoSpeed = InfoSpeed;
OriginalBattlePet->InfoGender = InfoGender;
for (uint8 i = 0; i < MAX_PETBATTLE_ABILITIES; ++i)
OriginalBattlePet->Abilities[i] = Abilities[i];
OriginalBattlePet->AccountID = AccountID;
OriginalBattlePet->needSave = true;
if (sendUpdate)
player->GetSession()->SendBattlePetUpdates(OriginalBattlePet.get());
}
PetBattleEventUpdate::PetBattleEventUpdate()
{
UpdateType = 0;
TargetPetID = PETBATTLE_NULL_ID;
Health = 0;
MaxHealth = 0;
TriggerAbilityId = 0;
Speed = 0;
}
PetBattleEvent::PetBattleEvent(uint32 eventType, int32 sourcePetID, uint32 flags, uint32 abilityEffectID, uint32 roundTurn, uint32 buffTurn, uint32 stackDepth)
{
Flags = flags;
SourcePetID = sourcePetID;
AbilityEffectID = abilityEffectID;
EventType = eventType;
BuffTurn = buffTurn;
RoundTurn = roundTurn;
StackDepth = stackDepth;
}
PetBattleEvent& PetBattleEvent::UpdateHealth(int8 targetPetID, int32 health)
{
PetBattleEventUpdate update;
update.UpdateType = PET_BATTLE_EFFECT_TARGET_EX_PET;
update.TargetPetID = targetPetID;
update.Health = health;
Updates.push_back(update);
return *this;
}
PetBattleEvent& PetBattleEvent::UpdateMaxHealth(int8 targetPetID, int32 maxHealth)
{
return UpdateSpeed(targetPetID, maxHealth);
}
PetBattleEvent& PetBattleEvent::UpdateState(int8 targetPetID, uint32 stateID, int32 value)
{
// TC_LOG_DEBUG(LOG_FILTER_BATTLEPET, "PetBattleEvent::UpdateStat stateID %u value %u", stateID, value);
PetBattleEventUpdate update;
update.UpdateType = PET_BATTLE_EFFECT_TARGET_EX_STATE;
update.TargetPetID = targetPetID;
update.State.ID = stateID;
update.State.Value = value;
Updates.push_back(update);
return *this;
}
PetBattleEvent& PetBattleEvent::UpdateFrontPet(int8 p_NewFrontPet)
{
PetBattleEventUpdate update;
update.UpdateType = PET_BATTLE_EFFECT_TARGET_EX_FRONT_PET;
update.TargetPetID = p_NewFrontPet;
Updates.push_back(update);
return *this;
}
PetBattleEvent& PetBattleEvent::UpdateBuff(int8 targetPetID, uint32 p_ID, uint32 abilityID, int32 duration, uint32 p_Turn)
{
PetBattleEventUpdate update;
update.UpdateType = PET_BATTLE_EFFECT_TARGET_EX_AURA;
update.TargetPetID = targetPetID;
update.Buff.AbilityID = abilityID;
update.Buff.Duration = duration;
update.Buff.Turn = p_Turn;
update.Buff.ID = p_ID;
Updates.push_back(update);
return *this;
}
PetBattleEvent& PetBattleEvent::UpdateSpeed(int8 targetPetID, int32 p_Speed)
{
PetBattleEventUpdate update;
update.UpdateType = PET_BATTLE_EFFECT_TARGET_EX_STAT_CHANGE;
update.TargetPetID = targetPetID;
update.Speed = p_Speed;
Updates.push_back(update);
return *this;
}
PetBattleEvent& PetBattleEvent::Trigger(int8 targetPetID, uint32 p_AbilityId)
{
PetBattleEventUpdate update;
update.UpdateType = PET_BATTLE_EFFECT_TARGET_EX_TRIGGER_ABILITY;
update.TargetPetID = targetPetID;
update.TriggerAbilityId = p_AbilityId;
Updates.push_back(update);
return *this;
}
void PetBattleAura::Apply(PetBattle* petBattle)
{
for (auto const& v : sBattlePetAbilityStateStore)
{
if (v->BattlePetAbilityID != AbilityID)
continue;
uint32 flags = 0;
// Passive: elemental
// TODO: add state flags for positive / negative
// FIXME: need more work; check weather ability, dont change state value on remove
if (petBattle->Pets[TargetPetID]->States[BATTLEPET_STATE_Passive_Elemental])
{
switch (v->BattlePetStateID)
{
case BATTLEPET_STATE_Mod_HealingTakenPercent:
case BATTLEPET_STATE_Stat_Accuracy:
if (v->Value < 0)
flags |= PETBATTLE_EVENT_FLAG_IMMUNE;
break;
case BATTLEPET_STATE_Mechanic_IsBlind:
case BATTLEPET_STATE_Mechanic_IsChilled:
case BATTLEPET_STATE_Mechanic_IsBurning:
case BATTLEPET_STATE_swapOutLock:
if (v->Value > 0)
flags |= PETBATTLE_EVENT_FLAG_IMMUNE;
break;
default:
break;
}
}
int32 value = petBattle->Pets[TargetPetID]->States[v->BattlePetStateID];
if (!flags)
value += v->Value;
petBattle->SetPetState(CasterPetID, TargetPetID, 0, v->BattlePetStateID, value, false, flags);
}
if (AbilityID == 577) ///< Healthy http://www.wowhead.com/petability=576/perk-up
{
auto pet = petBattle->Pets[TargetPetID];
float healthPct = float(pet->Health) / float(pet->GetMaxHealth() - 5 * pet->Level);
pet->InfoMaxHealth = pet->GetMaxHealth();
pet->Health = healthPct * float(pet->GetMaxHealth());
PetBattleEvent eventSetMaxHealth(PETBATTLE_EVENT_SET_MAX_HEALTH, CasterPetID, 0, 577, petBattle->RoundTurn, 0, 1);
eventSetMaxHealth.UpdateMaxHealth(TargetPetID, pet->InfoMaxHealth);
petBattle->RoundEvents.push_back(eventSetMaxHealth);
PetBattleEvent eventSetHealth(PETBATTLE_EVENT_SET_HEALTH, CasterPetID, 0, 577, petBattle->RoundTurn, 0, 1);
eventSetHealth.UpdateHealth(TargetPetID, pet->Health);
petBattle->RoundEvents.push_back(eventSetHealth);
}
}
void PetBattleAura::Remove(PetBattle* petBattle)
{
if (AbilityID == petBattle->WeatherAbilityId)
petBattle->WeatherAbilityId = 0;
for (auto const& v : sBattlePetAbilityStateStore)
if (v->BattlePetAbilityID == AbilityID)
petBattle->SetPetState(CasterPetID, TargetPetID, 0, v->BattlePetStateID, petBattle->Pets[TargetPetID]->States[v->BattlePetStateID] - v->Value);
if (AbilityID == 577) ///< Healthy http://www.wowhead.com/petability=576/perk-up
{
auto pet = petBattle->Pets[TargetPetID];
float healthPct = float(pet->Health) / float(pet->GetMaxHealth() + 5 * pet->Level);
pet->InfoMaxHealth = pet->GetMaxHealth();
pet->Health = healthPct * float(pet->GetMaxHealth());
PetBattleEvent eventSetHealth(PETBATTLE_EVENT_SET_HEALTH, CasterPetID, 0, 577, petBattle->RoundTurn, Turn, 1);
eventSetHealth.UpdateHealth(TargetPetID, pet->Health);
petBattle->RoundEvents.push_back(eventSetHealth);
PetBattleEvent eventSetMaxHealth(PETBATTLE_EVENT_SET_MAX_HEALTH, CasterPetID, 0, 577, petBattle->RoundTurn, Turn, 1);
eventSetMaxHealth.UpdateMaxHealth(TargetPetID, pet->InfoMaxHealth);
petBattle->RoundEvents.push_back(eventSetMaxHealth);
}
}
void PetBattleAura::Process(PetBattle* petBattle)
{
if (Duration != -1 && Turn > MaxDuration && !Expired)
{
Expire(petBattle);
return;
}
if (auto abilityInfo = sBattlePetAbilityStore.LookupEntry(AbilityID))
{
PetBattleAbilityTurn abilityTurn;
memset(&abilityTurn, 0, sizeof abilityTurn);
uint8 turnCount = 0;
uint8 maxTurnID = 0;
for (auto const& abilityTurnInfo : sBattlePetAbilityTurnStore)
{
if (abilityTurnInfo->BattlePetAbilityID != AbilityID)
continue;
turnCount++;
maxTurnID = std::max(maxTurnID, abilityTurnInfo->OrderIndex);
}
for (auto const& abilityTurnInfo : sBattlePetAbilityTurnStore)
{
if (abilityTurnInfo->BattlePetAbilityID != AbilityID)
continue;
if (abilityTurnInfo->OrderIndex != Turn && turnCount != 1 && maxTurnID != 1)
continue;
if (abilityTurnInfo->TurnTypeEnum && abilityTurnInfo->EventTypeEnum != PETBATTLE_ABILITY_TURN0_PROC_ON_TURN)
continue;
for (auto const& abilityEffectInfo : sBattlePetAbilityEffectStore)
{
if (abilityEffectInfo->BattlePetAbilityTurnID != abilityTurnInfo->ID)
continue;
PetBattleAbilityEffect effect;
effect.EffectInfo = abilityEffectInfo;
effect.AbilityTurn = &abilityTurn;
effect.PetBattleInstance = petBattle;
effect.Caster = CasterPetID;
effect.StopChain = false;
effect.AbilityID = AbilityID;
effect.IsTriggered = abilityInfo->Flags & BATTLEPET_ABILITY_FLAG_TRIGGER;
effect.ReportFailAsImmune = false;
effect.Targets.push_back(TargetPetID);
auto itr = petBattle->RoundEvents.end();
if (!effect.Execute())
abilityTurn.ChainFailure |= 1 << (abilityEffectInfo->OrderIndex - 1);
/// Update "buff turn" on all event triggered by the current ability effect
for (; itr != petBattle->RoundEvents.end(); ++itr)
itr->BuffTurn = Turn;
if (effect.StopChain)
break;
}
}
}
PetBattleEvent eventBuffChange(PETBATTLE_EVENT_BUFF_CHANGE, CasterPetID);
eventBuffChange.UpdateBuff(TargetPetID, ID, AbilityID, Duration, Turn);
petBattle->RoundEvents.push_back(eventBuffChange);
Turn++;
Duration--;
}
void PetBattleAura::Expire(PetBattle* petBattle)
{
if (Expired)
return;
PetBattleEvent battleEvent(PETBATTLE_EVENT_BUFF_CANCEL, CasterPetID, 0, 0, 1, 0, 1);
battleEvent.UpdateBuff(TargetPetID, ID, AbilityID, 0, Turn);
petBattle->RoundEvents.push_back(battleEvent);
Expired = true;
petBattle->Cast(CasterPetID, AbilityID, 0, PETBATTLE_ABILITY_TURN0_PROC_ON_AURA_REMOVED, PETBATTLE_CAST_TRIGGER_ALL);
Remove(petBattle);
}
bool PetBattleTeam::Update()
{
if (Ready || !isRun)
return false;
if (PetBattleInstance->BattleStatus == PETBATTLE_STATUS_FINISHED)
{
Ready = false;
return false;
}
auto frontPet = PetBattleInstance->Pets[ActivePetID];
auto isFrontPetAlive = frontPet->IsAlive();
uint32 thisTeamID = PetBattleInstance->Teams[0] == this ? PETBATTLE_TEAM_1 : PETBATTLE_TEAM_2;
if (!isFrontPetAlive)
{
auto availablesPets = GetAvailablesPets();
if (availablesPets.empty())
{
PetBattleInstance->Finish(!thisTeamID, false, false);
return true;
}
if (availablesPets.size() == 1)
{
PetBattleInstance->SwapPet(thisTeamID, availablesPets[0]);
if (auto player = ObjectAccessor::FindPlayer(PlayerGuid))
player->GetSession()->SendPetBattleReplacementMade(PetBattleInstance);
if (PetBattleInstance->BattleType == PETBATTLE_TYPE_PVE && thisTeamID == PETBATTLE_PVE_TEAM_ID)
PetBattleInstance->Teams[PETBATTLE_TEAM_1]->Ready = true;
}
else if (PetBattleInstance->BattleType == PETBATTLE_TYPE_PVE && thisTeamID == PETBATTLE_PVE_TEAM_ID)
{
PetBattleInstance->SwapPet(thisTeamID, availablesPets[rand() % availablesPets.size()]);
if (auto player = ObjectAccessor::FindPlayer(PlayerGuid))
player->GetSession()->SendPetBattleReplacementMade(PetBattleInstance);
PetBattleInstance->Teams[PETBATTLE_TEAM_1]->Ready = true;
Ready = true;
}
}
else if (HasPendingMultiTurnCast() && isFrontPetAlive)
Ready = true;
else if (isFrontPetAlive)
{
if (PetBattleInstance->BattleType == PETBATTLE_TYPE_PVE && thisTeamID == PETBATTLE_PVE_TEAM_ID)
{
std::vector<uint32> availablesAbilities;
for (auto ability : frontPet->Abilities)
if (PetBattleInstance->CanCast(thisTeamID, ability))
availablesAbilities.push_back(ability);
if (!availablesAbilities.empty())
PetBattleInstance->PrepareCast(thisTeamID, availablesAbilities[rand() % availablesAbilities.size()]);
else
PetBattleInstance->SwapPet(thisTeamID, ActivePetID); ///< Skip turn
Ready = true;
}
}
return false;
}
void PetBattleTeam::DoCasts(uint32 turn0ProcCond)
{
if (ActiveAbilityId && PetBattleInstance->Pets[ActivePetID]->IsAlive() && PetBattleInstance->Pets[ActivePetID]->CanAttack())
{
if (turn0ProcCond == PETBATTLE_ABILITY_TURN0_PROC_ON_NONE && PetBattleInstance->PetXDied.empty())
PetBattleInstance->Cast(ActivePetID, ActiveAbilityId, activeAbilityTurn, turn0ProcCond, PETBATTLE_CAST_TRIGGER_NONE);
else
PetBattleInstance->Cast(ActivePetID, ActiveAbilityId, 0, turn0ProcCond, PETBATTLE_CAST_TRIGGER_ALL);
for (auto& aura : PetBattleInstance->PetAuras)
if (!aura->Expired && aura->CasterPetID == ActivePetID)
PetBattleInstance->Cast(aura->CasterPetID, aura->AbilityID, 0, PETBATTLE_ABILITY_TURN0_PROC_ON_ABILITY, PETBATTLE_CAST_TRIGGER_ALL);
}
}
bool PetBattleTeam::HasPendingMultiTurnCast()
{
if (ActiveAbilityId && activeAbilityTurn <= activeAbilityTurnMax)
return true;
return false;
}
bool PetBattleTeam::CanCastAny()
{
if (HasPendingMultiTurnCast())
return false;
if (PetBattleInstance->Pets[ActivePetID]->States[BATTLEPET_STATE_turnLock])
return false;
if (PetBattleInstance->Pets[ActivePetID]->States[BATTLEPET_STATE_Mechanic_IsStunned])
return false;
return true;
}
bool PetBattleTeam::CanSwap(int8 replacementPet)
{
if (HasPendingMultiTurnCast())
return false;
if (PetBattleInstance->Pets[ActivePetID]->IsAlive() && (PetBattleInstance->Pets[ActivePetID]->States[BATTLEPET_STATE_swapOutLock] || PetBattleInstance->Pets[ActivePetID]->States[BATTLEPET_STATE_Mechanic_IsWebbed]))
return false;
if (replacementPet != PETBATTLE_NULL_ID)
{
if (!PetBattleInstance->Pets[replacementPet]->IsAlive())
return false;
if (PetBattleInstance->Pets[replacementPet]->States[BATTLEPET_STATE_swapInLock])
return false;
}
return true;
}
uint8 PetBattleTeam::CanCatchOpponentTeamFrontPet()
{
if (PetBattleInstance->BattleType != PETBATTLE_TYPE_PVE)
return 0;
if (!GetCatchAbilityID())
return 0;
if (PetBattleInstance->BattleType == PETBATTLE_TYPE_PVE && PetBattleInstance->PveBattleType == PVE_PETBATTLE_TRAINER)
return 0;
if (CapturedPet != PETBATTLE_NULL_ID)
return PETBATTLE_TEAM_CATCH_FLAG_ONE_CATCH_PER_FIGHT;
uint32 thisTeamID = PetBattleInstance->Teams[PETBATTLE_TEAM_1] == this ? PETBATTLE_TEAM_1 : PETBATTLE_TEAM_2;
if (thisTeamID == PETBATTLE_PVE_TEAM_ID)
return 0;
if (PetBattleInstance->Teams[!thisTeamID]->ActivePetID == PETBATTLE_NULL_ID)
return 0;
auto targetPet = PetBattleInstance->Pets[PetBattleInstance->Teams[!thisTeamID]->ActivePetID];
if (!targetPet || !targetPet->IsAlive())
return 0;
if (sDB2Manager.HasBattlePetSpeciesFlag(targetPet->Species, BATTLEPET_SPECIES_FLAG_UNTAMEABLE))
return 0;
if (targetPet->Health * 100 / targetPet->InfoMaxHealth >= 35)
return PETBATTLE_TEAM_CATCH_FLAG_TOO_MUCH_HP;
/// Max 3 same pet for player
if (CapturedSpeciesCount.find(targetPet->Species) != CapturedSpeciesCount.end())
if (CapturedSpeciesCount[targetPet->Species] > 2)
return 0;
/// todo more check
return PETBATTLE_TEAM_CATCH_FLAG_ENABLE_TRAP;
}
uint32 PetBattleTeam::GetTeamTrapStatus()
{
uint32 flags = CanCatchOpponentTeamFrontPet();
/// @TODO PETBATTLE_TEAM_CATCH_FLAG_NEED_LVL3_PET
return flags;
}
uint32 PetBattleTeam::GetTeamInputFlags()
{
uint32 flags = 0;
if (PetBattleInstance->BattleType != PETBATTLE_TYPE_PVE && PetBattleInstance->Teams[PETBATTLE_TEAM_1]->ActivePetID == PETBATTLE_NULL_ID && PetBattleInstance->Teams[PETBATTLE_TEAM_2]->ActivePetID == PETBATTLE_NULL_ID)
flags |= PETBATTLE_TEAM_INPUT_FLAG_LOCK_ABILITIES_2 | PETBATTLE_TEAM_INPUT_FLAG_SELECT_NEW_PET;
if (!CanCastAny())
flags |= PETBATTLE_TEAM_INPUT_FLAG_LOCK_ABILITIES_1 | PETBATTLE_TEAM_INPUT_FLAG_LOCK_ABILITIES_2;
if (!CanSwap())
flags |= PETBATTLE_TEAM_INPUT_FLAG_LOCK_PET_SWAP;
if (!PetBattleInstance->Pets[ActivePetID]->IsAlive())
{
if (GetAvailablesPets().size() > 1)
flags |= PETBATTLE_TEAM_INPUT_FLAG_LOCK_ABILITIES_2 | PETBATTLE_TEAM_INPUT_FLAG_SELECT_NEW_PET;
else
flags |= PETBATTLE_TEAM_INPUT_FLAG_LOCK_ABILITIES_2 | PETBATTLE_TEAM_INPUT_FLAG_LOCK_PET_SWAP;
}
uint32 thisTeamID = PetBattleInstance->Pets[ActivePetID]->TeamID;
if (PetBattleInstance->Teams[!thisTeamID]->ActivePetID != PETBATTLE_NULL_ID && PetBattleInstance->Pets[PetBattleInstance->Teams[!thisTeamID]->ActivePetID] && !PetBattleInstance->Pets[PetBattleInstance->Teams[!thisTeamID]->ActivePetID]->IsAlive())
flags |= PETBATTLE_TEAM_INPUT_FLAG_LOCK_ABILITIES_2 | PETBATTLE_TEAM_INPUT_FLAG_LOCK_PET_SWAP;
return flags;
}
std::vector<uint32> PetBattleTeam::GetAvailablesPets()
{
std::vector<uint32> availablesPets;
uint32 thisTeamID = PetBattleInstance->Teams[PETBATTLE_TEAM_1] == this ? PETBATTLE_TEAM_1 : PETBATTLE_TEAM_2;
for (uint32 i = 0; i < MAX_PETBATTLE_TEAM * MAX_PETBATTLE_SLOTS; i++)
if (PetBattleInstance->Pets[i] && PetBattleInstance->Pets[i]->IsAlive() && PetBattleInstance->Pets[i]->TeamID == thisTeamID && CanSwap(i))
availablesPets.push_back(i);
return availablesPets;
}
uint32 PetBattleTeam::GetCatchAbilityID()
{
uint32 thisTeamID = PetBattleInstance->Teams[PETBATTLE_TEAM_1] == this ? PETBATTLE_TEAM_1 : PETBATTLE_TEAM_2;
if (PetBattleInstance->BattleType == PETBATTLE_TYPE_PVE && thisTeamID == PETBATTLE_PVE_TEAM_ID)
return 0;
static const uint32 petBattleCatchAbilityID[] = {427, 77, 135};
if (auto player = ObjectAccessor::FindPlayer(PlayerGuid))
return petBattleCatchAbilityID[player->GetBattlePetTrapLevel() - 1];
return petBattleCatchAbilityID[0];
}
PetBattle::PetBattle()
{
BattleStatus = PETBATTLE_STATUS_CREATION;
RoundStatus = PETBATTLE_ROUND_FINISHED;
RoundResult = PETBATTLE_ROUND_RESULT_NONE;
TotalPetCount = 0;
WeatherAbilityId = 0;
for (auto& pet : Pets)
pet = nullptr;
for (auto& team : Teams)
{
team = new PetBattleTeam();
team->PetBattleInstance = this;
team->TeamPetCount = 0;
team->ActiveAbilityId = 0;
team->activeAbilityTurn = 0;
team->activeAbilityTurnMax = 0;
team->CapturedPet = PETBATTLE_NULL_ID;
team->Ready = false;
team->isRun = false;
for (auto& teamPet : team->TeamPets)
teamPet = nullptr;
}
WinnerTeamId = -1;
CatchedPetId = PETBATTLE_NULL_ID;
m_UpdateTimer.SetInterval(PETBATTLE_UPDATE_INTERVAL);
}
PetBattle::~PetBattle()
{
for (size_t i = 0; i < MAX_PETBATTLE_TEAM; ++i)
{
if (BattleType == PETBATTLE_TYPE_PVE && i != PETBATTLE_PVE_TEAM_ID || BattleType != PETBATTLE_TYPE_PVE)
for (uint32 petID = 0; petID < MAX_PETBATTLE_SLOTS; petID++)
if (Pets[petID])
Pets[petID] = std::shared_ptr<BattlePetInstance>();
delete Teams[i];
}
for (auto& aura : PetAuras)
delete aura;
}
void PetBattle::AddPet(uint32 teamID, std::shared_ptr<BattlePetInstance> pet)
{
if (pet == nullptr || pet->Slot < 0 || pet->Slot >= MAX_PETBATTLE_SLOTS)
return;
assert(teamID < MAX_PETBATTLE_TEAM);
assert(Teams[teamID]->TeamPetCount < MAX_PETBATTLE_SLOTS);
Teams[teamID]->TeamPets[Teams[teamID]->TeamPetCount] = pet;
pet->ID = (teamID == PETBATTLE_TEAM_2 ? MAX_PETBATTLE_SLOTS : 0) + Teams[teamID]->TeamPetCount;
pet->TeamID = teamID;
pet->PetBattleInstance = this;
pet->OldLevel = pet->Level;
TotalPetCount++;
Teams[teamID]->TeamPetCount++;
Pets[pet->ID] = pet;
for (auto& state : pet->States)
state = 0;
pet->States[0] = 1;
for (uint32 i = 0; i < MAX_PETBATTLE_ABILITIES; i++)
{
pet->Cooldowns[i] = -1;
pet->Lockdowns[i] = 0;
}
for (auto breedStateInfo : sBattlePetBreedStateStore)
if (breedStateInfo->BattlePetBreedID == pet->Breed)
pet->States[breedStateInfo->BattlePetStateID] += breedStateInfo->Value;
for (auto speciesStateInfo : sBattlePetSpeciesStateStore)
{
if (speciesStateInfo->BattlePetSpeciesID != pet->Species)
continue;
if (speciesStateInfo->BattlePetStateID > NUM_BATTLEPET_STATES)
continue;
pet->States[speciesStateInfo->BattlePetStateID] += speciesStateInfo->Value;
}
// Primary stats is scaled with level and quality
if (auto qualityInfo = sBattlePetBreedQualityStore.LookupEntry(7 + pet->Quality))
for (size_t i = 0; i < 3; ++i)
pet->States[BATTLEPET_STATE_Stat_Power + i] *= pet->Level * qualityInfo->StateMultiplier;
// Populate infos
pet->InfoPower = pet->States[BATTLEPET_STATE_Stat_Power] / 100;
pet->InfoSpeed = pet->States[BATTLEPET_STATE_Stat_Speed] / 100;
pet->InfoMaxHealth = int32(floor((10000.0f + pet->States[BATTLEPET_STATE_Stat_Stamina] * 5.0f) / 100.0f + 0.5));
pet->InfoGender = pet->States[BATTLEPET_STATE_Stat_Gender];
if (pet->Health > pet->InfoMaxHealth)
pet->Health = pet->InfoMaxHealth;
}
void PetBattle::Begin()
{
// TC_LOG_DEBUG(LOG_FILTER_BATTLEPET, "PetBattle::Begin()");
RoundStatus = PETBATTLE_ROUND_RUNNING;
RoundResult = PETBATTLE_ROUND_RESULT_NORMAL;
Turn = 0;
for (size_t i = 0; i < MAX_PETBATTLE_TEAM; ++i)
{
Teams[i]->ActiveAbilityId = 0;
Teams[i]->ActivePetID = i == PETBATTLE_TEAM_2 ? MAX_PETBATTLE_SLOTS : 0;
if (Player* player = ObjectAccessor::FindPlayer(Teams[i]->OwnerGuid))
player->UnsummonCurrentBattlePetIfAny(true);
}
/// In PVE battle the front pets (slot 0) are auto lock
if (BattleType == PETBATTLE_TYPE_PVE)
{
/// Team 1 pet selection
SwapPet(PETBATTLE_TEAM_1, Teams[PETBATTLE_TEAM_1]->ActivePetID, true);
/// Team 2 pet selection
SwapPet(PETBATTLE_TEAM_2, Teams[PETBATTLE_TEAM_2]->ActivePetID, true);
if (Player* player = ObjectAccessor::FindPlayer(Teams[PETBATTLE_TEAM_1]->OwnerGuid))
{
player->PetBattleCountBattleSpecies();
player->GetSession()->SendPetBattleInitialUpdate(this);
player->GetSession()->SendPetBattleFirstRound(this);
}
RoundEvents.clear();
PetXDied.clear();
RoundPetSpeedUpdate.clear();
}
else if (BattleType == PETBATTLE_TYPE_PVP_DUEL || BattleType == PETBATTLE_TYPE_PVP_MATCHMAKING)
{
Player* playerA = ObjectAccessor::FindPlayer(Teams[PETBATTLE_TEAM_1]->OwnerGuid);
Player* playerB = ObjectAccessor::FindPlayer(Teams[PETBATTLE_TEAM_2]->OwnerGuid);
if (playerA && playerB)
{
playerA->GetSession()->SendPetBattleInitialUpdate(this);
playerB->GetSession()->SendPetBattleInitialUpdate(this);
playerA->GetSession()->SendPetBattleFirstRound(this);
playerB->GetSession()->SendPetBattleFirstRound(this);
}
RoundEvents.clear();
PetXDied.clear();
RoundPetSpeedUpdate.clear();
}
Turn++;
// Initialize pet health and auras
for (uint32 petID = 0; petID < MAX_PETBATTLE_TEAM * MAX_PETBATTLE_SLOTS; ++petID)
Cast(petID, 291, 1, 0, PETBATTLE_CAST_TRIGGER_NONE);
RoundEvents.clear();
PetXDied.clear();
RoundPetSpeedUpdate.clear();
RoundResult = PETBATTLE_ROUND_RESULT_NONE;
RoundStatus = PETBATTLE_ROUND_FINISHED;
BattleStatus = PETBATTLE_STATUS_RUNNING;
}
void PetBattle::ProceedRound()
{
if (!Teams[PETBATTLE_TEAM_1]->Ready || !Teams[PETBATTLE_TEAM_2]->Ready)
return;
RoundStatus = PETBATTLE_ROUND_RUNNING;
RoundTurn = 1;
for (auto& pet : Pets)
{
if (pet)
{
pet->States[BATTLEPET_STATE_Condition_DidDamageThisRound] = 0;
pet->States[BATTLEPET_STATE_Condition_WasDamagedThisTurn] = 0;
}
}
RoundFirstTeamCasting = GetFirstAttackingTeam();
uint32 firstTeam = RoundFirstTeamCasting;
Teams[firstTeam]->DoCasts(PETBATTLE_ABILITY_TURN0_PROC_ON_ROUND_START);
Teams[!firstTeam]->DoCasts(PETBATTLE_ABILITY_TURN0_PROC_ON_ROUND_START);
firstTeam = RoundFirstTeamCasting;
Teams[firstTeam]->DoCasts();
Teams[!firstTeam]->DoCasts();
PetBattleEvent eventAuraProccesing(PETBATTLE_EVENT_AURA_PROCESSING_BEGIN);
eventAuraProccesing.UpdateFrontPet();
RoundEvents.push_back(eventAuraProccesing);
firstTeam = RoundFirstTeamCasting;
for (auto& aura : PetAuras)
if (!aura->Expired && Pets[aura->CasterPetID]->TeamID == firstTeam)
aura->Process(this);
for (auto& aura : PetAuras)
if (!aura->Expired && Pets[aura->CasterPetID]->TeamID == !firstTeam)
aura->Process(this);
PetBattleEvent eventAuraProcessingEnd(PETBATTLE_EVENT_AURA_PROCESSING_END);
eventAuraProcessingEnd.UpdateFrontPet();
RoundEvents.push_back(eventAuraProcessingEnd);
for (auto& itr : RoundPetSpeedUpdate)
{
PetBattleEvent eventSetSpeed(PETBATTLE_EVENT_SET_SPEED, itr.first, 0, itr.second, RoundTurn++, 0, 1);
eventSetSpeed.UpdateSpeed(itr.first, Pets[itr.first]->GetSpeed());
RoundEvents.push_back(eventSetSpeed);
}
firstTeam = RoundFirstTeamCasting;
Teams[firstTeam]->DoCasts(PETBATTLE_ABILITY_TURN0_PROC_ON_ROUND_END);
Teams[!firstTeam]->DoCasts(PETBATTLE_ABILITY_TURN0_PROC_ON_ROUND_END);
// Passive: Humanoid
for (uint8 i = 0; i < MAX_PETBATTLE_TEAM * MAX_PETBATTLE_SLOTS; ++i)
if (Pets[i] && Pets[i]->States[BATTLEPET_STATE_Passive_Humanoid] && Pets[i]->States[BATTLEPET_STATE_Condition_DidDamageThisRound])
Cast(i, 726, 1, PETBATTLE_ABILITY_TURN0_PROC_ON_NONE, PETBATTLE_CAST_TRIGGER_NONE);
// Passive: Dragonkin
if (Pets[Teams[0]->ActivePetID]->IsAlive() && Pets[Teams[1]->ActivePetID]->IsAlive())
for (size_t teamID = 0; teamID < MAX_PETBATTLE_TEAM; ++teamID)
if (Pets[Teams[teamID]->ActivePetID]->States[BATTLEPET_STATE_Passive_Dragon])
if (Pets[Teams[!teamID]->ActivePetID]->Health * 100 / Pets[Teams[!teamID]->ActivePetID]->GetMaxHealth() < 50)
AddAura(Teams[teamID]->ActivePetID, Teams[teamID]->ActivePetID, 245, 1, 0, 0, 0);
std::list<PetBattleAura*> aurasToRemove;
for (auto& aura : PetAuras)
if (aura->Expired)
aurasToRemove.push_back(aura);
for (auto& itr : aurasToRemove)
{
PetAuras.remove(itr);
delete itr;
}
for (auto& team : Teams)
{
team->activeAbilityTurn++;
if (team->activeAbilityTurn > team->activeAbilityTurnMax)
{
team->ActiveAbilityId = 0;
team->activeAbilityTurn = 0;
team->activeAbilityTurnMax = 0;
}
}
for (auto& Pet : Pets)
for (auto& cooldown : Pet->Cooldowns)
if (Pet && cooldown != -1)
cooldown--;
if (RoundResult == PETBATTLE_ROUND_RESULT_NONE)
RoundResult = PETBATTLE_ROUND_RESULT_NORMAL;
for (auto& team : Teams)
{
if (!team->PlayerGuid)
continue;
if (auto player = ObjectAccessor::FindConnectedPlayer(team->PlayerGuid))
player->GetSession()->SendPetBattleRoundResult(this);
}
// To next turn
++Turn;
RoundEvents.clear();
PetXDied.clear();
RoundPetSpeedUpdate.clear();
RoundResult = PETBATTLE_ROUND_RESULT_NONE;
RoundStatus = PETBATTLE_ROUND_FINISHED;
}
void PetBattle::Finish(uint32 winnerTeamID, bool aborted, bool ignoreAbandonPenalty)
{
if (aborted)
if (Pets[Teams[winnerTeamID]->ActivePetID] && Pets[Teams[winnerTeamID]->ActivePetID]->IsAlive() && Pets[Teams[winnerTeamID]->ActivePetID]->States[BATTLEPET_STATE_Mechanic_IsWebbed])
return;
RoundStatus = PETBATTLE_ROUND_FINISHED;
WinnerTeamId = winnerTeamID;
for (size_t currentTeamID = 0; currentTeamID < MAX_PETBATTLE_TEAM; ++currentTeamID)
{
if (Teams[currentTeamID])
{
auto player = ObjectAccessor::FindConnectedPlayer(Teams[currentTeamID]->PlayerGuid);
if (!player)
continue;
sScriptMgr->OnPetBattleFinish(player);
uint32 availablePetCount = Teams[currentTeamID]->GetAvailablesPets().size();
for (size_t currentPetID = 0; currentPetID < Teams[currentTeamID]->TeamPetCount; ++currentPetID)
{
auto currentPet = Teams[currentTeamID]->TeamPets[currentPetID];
if (!currentPet)
continue;
if (currentPet->Health < 0)
currentPet->Health = 0;
if (ignoreAbandonPenalty && aborted && winnerTeamID != currentTeamID)
currentPet->Health = AddPct(currentPet->Health, -GetForfeitHealthPenalityPct());
if (winnerTeamID == currentTeamID && availablePetCount && BattleType == PETBATTLE_TYPE_PVE && currentPet->IsAlive() && currentPet->Level < BATTLEPET_MAX_LEVEL && FightedPets.find(currentPet->ID) != FightedPets.end())
{
uint32 xpEarn = 0;
uint32 child = 0;
float xpMod[] = { 1.0f, 1.0f, 0.5f };
uint32 xpvalue = 0;
uint32 xpbasewin = 75;
for (uint32 opponentTeamCurrentPet = 0; opponentTeamCurrentPet < Teams[PETBATTLE_PVE_TEAM_ID]->TeamPetCount; opponentTeamCurrentPet++)
{
if (!Teams[PETBATTLE_PVE_TEAM_ID]->TeamPets[opponentTeamCurrentPet] || FightedPets.find(Teams[PETBATTLE_PVE_TEAM_ID]->TeamPets[opponentTeamCurrentPet]->ID) == FightedPets.end())
continue;
xpEarn += float(currentPet->GetXPEarn(Teams[PETBATTLE_PVE_TEAM_ID]->TeamPets[opponentTeamCurrentPet]->ID)) * xpMod[opponentTeamCurrentPet] / availablePetCount;
}
AddPct(xpEarn, player->GetTotalAuraModifier(SPELL_AURA_MOD_BATTLE_PET_XP_GAIN));
auto xpToNextLevel = currentPet->GetMaxXPForCurrentLevel();
if (currentPet->XP + xpEarn >= xpToNextLevel)
{
currentPet->XP = currentPet->XP + xpEarn - xpToNextLevel;
currentPet->Level++;
currentPet->UpdateStats();
currentPet->UpdateAbilities();
currentPet->Health = currentPet->InfoMaxHealth;
if (currentPet->Level == BATTLEPET_MAX_LEVEL)
currentPet->XP = 0;
if (auto speciesInfo = sBattlePetSpeciesStore.LookupEntry(currentPet->Species))
{
player->UpdateCriteria(CRITERIA_TYPE_BATTLEPET_LEVEL_UP, currentPet->Species, currentPet->Level, speciesInfo->PetTypeEnum);
player->UpdateCriteria(CRITERIA_TYPE_LEVEL_BATTLE_PET_CREDIT, speciesInfo->ID, currentPet->Level, currentPet->Species);
}
if (currentPet->Level >= 3)
{
player->CompletedAchievement(7433);
/// Quest progress for 12 x Level Up!
player->KilledMonsterCredit(65876);
player->GetSession()->SendPetBattleSlotUpdates();
}
if (currentPet->Level >= 5)
{
player->CompletedAchievement(6566);
player->GetSession()->SendPetBattleSlotUpdates();
}
}
else
currentPet->XP += xpEarn;
}
currentPet->UpdateOriginalInstance(player);
}
if (winnerTeamID == currentTeamID)
{
auto speciesInfo = sDB2Manager.GetSpeciesByCreatureID(InitialWildPetGUID.GetEntry());
player->UpdateCriteria(CRITERIA_TYPE_BATTLEPET_WIN, speciesInfo ? speciesInfo->ID : 0, BattleType != PETBATTLE_TYPE_PVE, 0);
if (speciesInfo)
player->QuestObjectiveSatisfy(speciesInfo->ID, 1, QUEST_OBJECTIVE_DEFEATBATTLEPET, InitialWildPetGUID);
}
if (BattleType == PETBATTLE_TYPE_PVE && PveBattleType == PVE_PETBATTLE_WILD)
{
/// Quest progress for 12 x Learning the Ropes
player->KilledMonsterCredit(65355);
}
if (BattleType == PETBATTLE_TYPE_PVE && currentTeamID == winnerTeamID && Teams[currentTeamID]->CapturedPet != PETBATTLE_NULL_ID)
{
if (Pets[Teams[currentTeamID]->CapturedPet]->Level >= 16 && Pets[Teams[currentTeamID]->CapturedPet]->Level <= 20)
Pets[Teams[currentTeamID]->CapturedPet]->Level -= 1;
else if (Pets[Teams[currentTeamID]->CapturedPet]->Level >= 21 && Pets[Teams[currentTeamID]->CapturedPet]->Level <= 25)
Pets[Teams[currentTeamID]->CapturedPet]->Level -= 2;
if (Pets[Teams[currentTeamID]->CapturedPet]->Health <= CalculatePct(Pets[Teams[currentTeamID]->CapturedPet]->InfoMaxHealth, 5))
if (auto achievement = sAchievementStore.LookupEntry(6571))
player->CompletedAchievement(achievement);
Pets[Teams[currentTeamID]->CapturedPet]->UpdateStats();
Pets[Teams[currentTeamID]->CapturedPet]->Health = Pets[Teams[currentTeamID]->CapturedPet]->InfoMaxHealth;
Pets[Teams[currentTeamID]->CapturedPet]->Slot = PETBATTLE_NULL_SLOT;
Pets[Teams[currentTeamID]->CapturedPet]->AddToPlayer(player);
player->_battlePets.emplace(Pets[Teams[currentTeamID]->CapturedPet]->JournalID, Pets[Teams[currentTeamID]->CapturedPet]);
if (auto speciesInfo = sBattlePetSpeciesStore.LookupEntry(Pets[Teams[currentTeamID]->CapturedPet]->Species))
{
player->UpdateCriteria(CRITERIA_TYPE_ADD_BATTLE_PET_JOURNAL, speciesInfo->CreatureID);
player->UpdateCriteria(CRITERIA_TYPE_CAPTURE_SPECIFIC_BATTLEPET, speciesInfo->ID, 0, speciesInfo->ID);
player->UpdateCriteria(CRITERIA_TYPE_CAPTURE_PET_IN_BATTLE, 1, Pets[Teams[currentTeamID]->CapturedPet]->Quality, speciesInfo->ID);
}
/// Quest progress for 12 x Got One!
player->KilledMonsterCredit(65356);
}
if (BattleType == PETBATTLE_TYPE_PVE && PveBattleType == PVE_PETBATTLE_TRAINER && currentTeamID == winnerTeamID)
{
if (auto trainer = ObjectAccessor::GetCreature(*player, Teams[PETBATTLE_PVE_TEAM_ID]->OwnerGuid))
{
trainer->Respawn(true);
player->QuestObjectiveSatisfy(trainer->GetEntry(), 1, QUEST_OBJECTIVE_PET_TRAINER_DEFEAT, trainer->GetGUID());
}
}
else if (BattleType == PETBATTLE_TYPE_PVE && PveBattleType == PVE_PETBATTLE_TRAINER)
{
if (auto trainer = ObjectAccessor::GetCreature(*player, Teams[PETBATTLE_PVE_TEAM_ID]->OwnerGuid))
{
trainer->Respawn(true);
}
}
}
}
for (size_t currentTeamID = 0; currentTeamID < MAX_PETBATTLE_TEAM; ++currentTeamID)
{
if (Teams[currentTeamID]->PlayerGuid)
{
auto player = ObjectAccessor::FindPlayer(Teams[currentTeamID]->PlayerGuid);
if (!player)
continue;
//TODO: update achievement criteria
player->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED | UNIT_FLAG_IMMUNE_TO_NPC);
// Send battle result
CombatResult = aborted ? PETBATTLE_RESULT_ABANDON : currentTeamID == winnerTeamID ? PETBATTLE_RESULT_WON : PETBATTLE_RESULT_LOOSE;
player->GetSession()->SendPetBattleFinalRound(this);
player->SetControlled(false, UNIT_STATE_ROOT);
player->GetScheduler().Schedule(Seconds(10), Seconds(11), [](TaskContext context)
{
Player* plr = GetContextPlayer();
plr->SummonLastSummonedBattlePet();
});
player->_petBattleId.Clear();
player->GetSession()->SendBattlePetJournal();
if (BattleType == PETBATTLE_TYPE_PVP_MATCHMAKING)
player->TeleportTo(player->m_recall_location);
}
else
{
if (BattleType == PETBATTLE_TYPE_PVE && PveBattleType == PVE_PETBATTLE_WILD)
{
for (size_t i = 0; i < Teams[currentTeamID]->TeamPetCount; ++i)
{
auto currentPet = Teams[currentTeamID]->TeamPets[i];
if (!currentPet || currentPet->OriginalCreature)
continue;
auto wildPet = ObjectAccessor::FindCreature(currentPet->OriginalCreature);
if (!wildPet)
continue;
wildPet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED | UNIT_FLAG_IMMUNE_TO_PC);
wildPet->SetControlled(false, UNIT_STATE_ROOT);
wildPet->_petBattleId.Clear();
sWildBattlePetMgr->LeaveBattle(wildPet, winnerTeamID != PETBATTLE_PVE_TEAM_ID);
}
}
}
}
BattleStatus = PETBATTLE_STATUS_FINISHED;
}
void PetBattle::Update(uint32 diff)
{
if (BattleStatus != PETBATTLE_STATUS_RUNNING)
return;
if (RoundStatus == PETBATTLE_ROUND_RUNNING)
return;
m_UpdateTimer.Update(diff);
if (m_UpdateTimer.Passed())
{
m_UpdateTimer.Reset();
Teams[PETBATTLE_TEAM_1]->Update();
if (Teams[PETBATTLE_TEAM_2]->Update())
{
Teams[PETBATTLE_TEAM_1]->Ready = false;
Teams[PETBATTLE_TEAM_2]->Ready = false;
return;
}
for (uint8 i = 0; i < PETBATTLE_TEAM_2; i++)
{
if (!Teams[i]->Ready && Teams[i]->ActivePetID != PETBATTLE_NULL_ID && !Pets[Teams[i]->ActivePetID]->CanAttack())
{
if (Teams[i]->isRun)
Teams[i]->Ready = true;
Teams[i]->ActiveAbilityId = 0;
Teams[i]->activeAbilityTurn = 0;
Teams[i]->activeAbilityTurnMax = 0;
}
}
if (Teams[PETBATTLE_TEAM_1]->Ready && Teams[PETBATTLE_TEAM_2]->Ready)
{
ProceedRound();
Teams[PETBATTLE_TEAM_1]->Ready = false;
Teams[PETBATTLE_TEAM_2]->Ready = false;
}
}
}
void PetBattle::SwapPet(uint32 teamID, int32 newFrontPetID, bool initial)
{
assert(teamID < MAX_PETBATTLE_TEAM);
if (newFrontPetID >= MAX_PETBATTLE_TEAM * MAX_PETBATTLE_SLOTS)
return;
if (!Pets[newFrontPetID])
return;
if (!Teams[teamID]->CanSwap())
return;
int32 oldTeamFrontPetID = Teams[teamID]->ActivePetID;
bool const petChanged = newFrontPetID != oldTeamFrontPetID;
if (petChanged)
for (auto& aura : PetAuras)
if (!aura->Expired)
Cast(aura->CasterPetID, aura->AbilityID, 0, PETBATTLE_ABILITY_TURN0_PROC_ON_SWAP_OUT, PETBATTLE_CAST_TRIGGER_ALL);
uint32 eventFlags = 0;
if (!petChanged && BattleStatus != PETBATTLE_STATUS_CREATION)
eventFlags |= PETBATTLE_EVENT_FLAG_SKIP_TURN;
Teams[teamID]->ActivePetID = newFrontPetID;
PetBattleEvent battleEvent(PETBATTLE_EVENT_PET_SWAP, newFrontPetID, eventFlags);
battleEvent.UpdateFrontPet(!initial && newFrontPetID == oldTeamFrontPetID ? PETBATTLE_NULL_ID : newFrontPetID);
RoundEvents.push_back(battleEvent);
if (petChanged)
for (auto& aura : PetAuras)
if (!aura->Expired)
Cast(aura->CasterPetID, aura->AbilityID, 0, PETBATTLE_ABILITY_TURN0_PROC_ON_SWAP_IN, PETBATTLE_CAST_TRIGGER_ALL);
Teams[teamID]->ActiveAbilityId = 0;
Teams[teamID]->activeAbilityTurn = 0;
Teams[teamID]->activeAbilityTurnMax = 0;
if (!initial && Teams[teamID]->isRun)
Teams[teamID]->Ready = true;
FightedPets[newFrontPetID] = true;
}
bool PetBattle::CanCast(uint32 teamID, uint32 abilityID)
{
assert(teamID < MAX_PETBATTLE_TEAM);
auto team = Teams[teamID];
if (!team)
return false;
if (!team->CanCastAny())
return false;
if (!sBattlePetAbilityStore.LookupEntry(abilityID))
return false;
for (uint32 i = 0; i < MAX_PETBATTLE_ABILITIES; i++)
if (Pets[team->ActivePetID]->Abilities[i] == abilityID && Pets[team->ActivePetID]->Cooldowns[i] <= 0)
return true;
return false;
}
void PetBattle::PrepareCast(uint32 teamID, uint32 abilityID)
{
// PetBattleAbilityTurn abilityTurn;
// memset(&abilityTurn, 0, sizeof(abilityTurn));
uint8 maxTurnID = 0;
for (auto const& abilityTurnInfo : sBattlePetAbilityTurnStore)
if (abilityTurnInfo->BattlePetAbilityID == abilityID)
maxTurnID = std::max(maxTurnID, abilityTurnInfo->OrderIndex);
Teams[teamID]->ActiveAbilityId = abilityID;
Teams[teamID]->activeAbilityTurn = 1;
Teams[teamID]->activeAbilityTurnMax = maxTurnID;
Teams[teamID]->Ready = true;
}
PetBattleCastResult PetBattle::Cast(uint32 casterPetID, uint32 abilityID, uint32 p_Turn, uint32 p_Turn0ProcCondition, uint32 p_TriggerFlag)
{
if (!Pets[casterPetID])
return PETBATTLE_CAST_INTERNAL_ERROR;
if (RoundResult == PETBATTLE_ROUND_RESULT_CATCH_OR_KILL)
return PETBATTLE_CAST_OK;
/// check if the ability exist
auto abilityInfo = sBattlePetAbilityStore.LookupEntry(abilityID);
if (!abilityInfo)
return PETBATTLE_CAST_INVALID_ID;
// States
if (!p_Turn && p_TriggerFlag == PETBATTLE_CAST_TRIGGER_NONE)
for (auto abilityStateInfo : sBattlePetAbilityStateStore)
if (abilityStateInfo->BattlePetAbilityID == abilityID)
SetPetState(casterPetID, casterPetID, 0, abilityStateInfo->BattlePetStateID, Pets[casterPetID]->States[abilityStateInfo->BattlePetStateID] + abilityStateInfo->Value);
PetBattleAbilityTurn abilityTurn;
memset(&abilityTurn, 0, sizeof abilityTurn);
for (auto abilityTurnInfo : sBattlePetAbilityTurnStore)
{
if (abilityTurnInfo->BattlePetAbilityID != abilityID || abilityTurnInfo->OrderIndex != p_Turn)
continue;
if (abilityTurnInfo->OrderIndex == 0 && abilityTurnInfo->EventTypeEnum != int8(p_Turn0ProcCondition))
continue;
for (auto abilityEffectInfo : sBattlePetAbilityEffectStore)
{
if (abilityEffectInfo->BattlePetAbilityTurnID != abilityTurnInfo->ID)
continue;
PetBattleAbilityEffect abilityEffect;
abilityEffect.EffectInfo = abilityEffectInfo;
abilityEffect.AbilityTurn = &abilityTurn;
abilityEffect.PetBattleInstance = this;
abilityEffect.Caster = casterPetID;
abilityEffect.StopChain = false;
abilityEffect.AbilityID = abilityID;
abilityEffect.IsTriggered = abilityInfo->Flags & BATTLEPET_ABILITY_FLAG_TRIGGER;
abilityEffect.ReportFailAsImmune = false;
abilityEffect.SelectTargets();
if (!abilityEffect.Execute())
abilityTurn.ChainFailure |= 1 << (abilityEffectInfo->OrderIndex - 1);
if (abilityEffect.StopChain)
break;
if (RoundResult == PETBATTLE_ROUND_RESULT_CATCH_OR_KILL)
return PETBATTLE_CAST_OK;
}
}
if (!(p_TriggerFlag & PETBATTLE_CAST_TRIGGER_IGNORE_COOLDOWN))
{
int32 abilitySlot = -1;
for (uint32 i = 0; i < MAX_PETBATTLE_ABILITIES; i++)
{
if (Pets[casterPetID]->Abilities[i] == abilityID)
{
abilitySlot = i;
break;
}
}
if (abilitySlot != -1)
Pets[casterPetID]->Cooldowns[abilitySlot] = abilityInfo->Cooldown;
}
return PETBATTLE_CAST_OK;
}
bool PetBattle::AddAura(uint32 casterPetID, uint32 targetPetID, uint32 abilityID, int32 duration, int32 maxAllowed, uint32 fromAbilityEffectID, uint32 flags)
{
PetBattleAura* oldAura = nullptr;
uint32 auraSlot = 0;
int32 auraCount = 0;
if (!maxAllowed)
maxAllowed = 1;
// Search same auras
for (auto& aura : PetAuras)
{
auraSlot = aura->ID;
if (!aura->Expired && aura->AbilityID == abilityID && aura->TargetPetID == targetPetID)
{
++auraCount;
if (!oldAura)
{
auraSlot = aura->ID;
oldAura = aura;
}
}
else if (!auraCount && aura->ID > auraSlot)
auraSlot = aura->ID;
}
if (!auraCount)
++auraSlot;
// Event
PetBattleEvent battleEvent(auraCount ? PETBATTLE_EVENT_BUFF_CHANGE : PETBATTLE_EVENT_BUFF_APPLY, casterPetID, flags, fromAbilityEffectID, RoundTurn++, 0, 1);
battleEvent.UpdateBuff(targetPetID, auraSlot, abilityID, duration, 0);
RoundEvents.push_back(battleEvent);
if (flags)
return false;
// Remove old aura if at max allowed count
if (!flags && maxAllowed && auraCount >= maxAllowed)
{
auto remainingAuraToRemove = 1 + auraCount - maxAllowed;
for (auto itr = PetAuras.begin(); itr != PetAuras.end() && remainingAuraToRemove > 0; ++itr)
{
if (!(*itr)->Expired && (*itr)->AbilityID == abilityID && (*itr)->TargetPetID == targetPetID)
{
(*itr)->Expire(this);
--remainingAuraToRemove;
}
}
}
// Create aura
auto aura = new PetBattleAura();
aura->AbilityID = abilityID;
aura->TriggerId = fromAbilityEffectID;
aura->CasterPetID = casterPetID;
aura->Duration = duration;
aura->MaxDuration = duration;
aura->Turn = 1;
aura->TargetPetID = targetPetID;
aura->ID = auraSlot;
aura->Expired = false;
PetAuras.push_back(aura);
aura->Apply(this);
return true;
}
void PetBattle::SetPetState(uint32 sourcePetID, uint32 targetPetID, uint32 fromAbilityEffectID, uint32 state, int32 value, bool fromCapture, uint32 flags)
{
// TC_LOG_DEBUG(LOG_FILTER_BATTLEPET, "PetBattleEvent::SetPetState state %u value %u States %u", state, value, Pets[targetPetID]->States[state]);
if (state >= NUM_BATTLEPET_STATES)
return;
if (Pets[targetPetID]->States[state] == value)
return;
Pets[targetPetID]->States[state] = value;
auto petStateEntry = sBattlePetStateStore.LookupEntry(state);
if (petStateEntry && petStateEntry->Flags && !fromCapture)
{
PetBattleEvent battleEvent(PETBATTLE_EVENT_SET_STATE, sourcePetID, flags, fromAbilityEffectID, RoundTurn++, 0, 1);
battleEvent.UpdateState(targetPetID, state, value);
RoundEvents.push_back(battleEvent);
}
}
void PetBattle::Kill(int8 killer, int8 target, uint32 killerAbibilityEffectID, bool fromCapture, uint32 flags)
{
if (Teams[Pets[target]->TeamID]->ActivePetID == target)
{
Teams[Pets[target]->TeamID]->ActiveAbilityId = 0;
Teams[Pets[target]->TeamID]->activeAbilityTurn = 0;
Teams[Pets[target]->TeamID]->activeAbilityTurnMax = 0;
}
for (auto& aura : PetAuras)
if (aura->TargetPetID == target)
aura->Expire(this);
// TC_LOG_DEBUG(LOG_FILTER_BATTLEPET, "PetBattle::Kill BATTLEPET_STATE_Special_ConsumedCorpse");
SetPetState(killer, target, killerAbibilityEffectID, BATTLEPET_STATE_Is_Dead, 1, fromCapture, flags);
RoundResult = PETBATTLE_ROUND_RESULT_CATCH_OR_KILL;
}
void PetBattle::Catch(int8 catcher, int8 catchedTarget, uint32 fromAbilityEffectID)
{
Kill(catcher, catchedTarget, fromAbilityEffectID);
Teams[Pets[catcher]->TeamID]->CapturedPet = catchedTarget;
Pets[catchedTarget]->Caged = true;
Pets[catchedTarget]->Captured = true;
CatchedPetId = catchedTarget;
PetXDied.push_back(catchedTarget);
RoundResult = PETBATTLE_ROUND_RESULT_CATCH_OR_KILL;
}
uint32 PetBattle::GetFirstAttackingTeam()
{
std::shared_ptr<BattlePetInstance> activePets[2];
activePets[0] = Pets[Teams[0]->ActivePetID];
activePets[1] = Pets[Teams[1]->ActivePetID];
/// Deduce the first caster (based on front pets speed)
auto pet0Speed = activePets[0]->GetSpeed();
auto pet1Speed = activePets[1]->GetSpeed();
return pet0Speed == pet1Speed ? rand() & 1 : pet1Speed > pet0Speed;
}
int32 PetBattle::GetForfeitHealthPenalityPct()
{
if (BattleType != PETBATTLE_TYPE_PVE)
return 0;
return 10;
}