Compare commits
3
Commits
d2838f7162
...
fdf2335ab1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdf2335ab1 | ||
|
|
cb66441cae | ||
|
|
56d6639fb2 |
File diff suppressed because it is too large
Load Diff
@@ -52,9 +52,11 @@ namespace BattlePets
|
||||
if (breedState == _battlePetBreedStates.end()) // non existing breed id
|
||||
return;
|
||||
|
||||
float health = breedState->second[STATE_STAT_STAMINA];
|
||||
float power = breedState->second[STATE_STAT_POWER];
|
||||
float speed = breedState->second[STATE_STAT_SPEED];
|
||||
// double: the float32 DB2 quality multiplier (0.65f = 0.6499999761) must not be re-rounded
|
||||
// (retail: species 1959 breed 16 q3 L1 = 171 HP, float locals give 172)
|
||||
double health = breedState->second[STATE_STAT_STAMINA];
|
||||
double power = breedState->second[STATE_STAT_POWER];
|
||||
double speed = breedState->second[STATE_STAT_SPEED];
|
||||
|
||||
// modify stats depending on species - not all pets have this
|
||||
auto speciesState = _battlePetSpeciesStates.find(PacketInfo.Species);
|
||||
|
||||
@@ -27,222 +27,222 @@ struct BattlePetSpeciesEntry;
|
||||
|
||||
namespace BattlePets
|
||||
{
|
||||
enum BattlePetMisc
|
||||
{
|
||||
DEFAULT_MAX_BATTLE_PETS_PER_SPECIES = 3,
|
||||
BATTLE_PET_CAGE_ITEM_ID = 82800,
|
||||
SPELL_VISUAL_UNCAGE_PET = 222,
|
||||
enum BattlePetMisc
|
||||
{
|
||||
DEFAULT_MAX_BATTLE_PETS_PER_SPECIES = 3,
|
||||
BATTLE_PET_CAGE_ITEM_ID = 82800,
|
||||
SPELL_VISUAL_UNCAGE_PET = 222,
|
||||
|
||||
SPELL_BATTLE_PET_TRAINING = 125610,
|
||||
SPELL_REVIVE_BATTLE_PETS = 125439,
|
||||
SPELL_SUMMON_BATTLE_PET = 118301
|
||||
};
|
||||
SPELL_BATTLE_PET_TRAINING = 125610,
|
||||
SPELL_REVIVE_BATTLE_PETS = 125439,
|
||||
SPELL_SUMMON_BATTLE_PET = 118301
|
||||
};
|
||||
|
||||
static constexpr uint16 MAX_BATTLE_PET_LEVEL = 25;
|
||||
static constexpr uint16 MAX_BATTLE_PET_LEVEL = 25;
|
||||
|
||||
enum class BattlePetBreedQuality : uint8
|
||||
{
|
||||
Poor = 0,
|
||||
Common = 1,
|
||||
Uncommon = 2,
|
||||
Rare = 3,
|
||||
Epic = 4,
|
||||
Legendary = 5,
|
||||
enum class BattlePetBreedQuality : uint8
|
||||
{
|
||||
Poor = 0,
|
||||
Common = 1,
|
||||
Uncommon = 2,
|
||||
Rare = 3,
|
||||
Epic = 4,
|
||||
Legendary = 5,
|
||||
|
||||
Count
|
||||
};
|
||||
Count
|
||||
};
|
||||
|
||||
enum class BattlePetDbFlags : uint16
|
||||
{
|
||||
None = 0x000,
|
||||
Favorite = 0x001,
|
||||
Converted = 0x002,
|
||||
Revoked = 0x004,
|
||||
LockedForConvert = 0x008,
|
||||
Ability0Selection = 0x010,
|
||||
Ability1Selection = 0x020,
|
||||
Ability2Selection = 0x040,
|
||||
FanfareNeeded = 0x080,
|
||||
DisplayOverridden = 0x100
|
||||
};
|
||||
enum class BattlePetDbFlags : uint16
|
||||
{
|
||||
None = 0x000,
|
||||
Favorite = 0x001,
|
||||
Converted = 0x002,
|
||||
Revoked = 0x004,
|
||||
LockedForConvert = 0x008,
|
||||
Ability0Selection = 0x010,
|
||||
Ability1Selection = 0x020,
|
||||
Ability2Selection = 0x040,
|
||||
FanfareNeeded = 0x080,
|
||||
DisplayOverridden = 0x100
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG(BattlePetDbFlags);
|
||||
DEFINE_ENUM_FLAG(BattlePetDbFlags);
|
||||
|
||||
enum class BattlePetError : uint8
|
||||
{
|
||||
CantHaveMorePetsOfType = 3, // You can't have any more pets of that type.
|
||||
CantHaveMorePets = 4, // You can't have any more pets.
|
||||
TooHighLevelToUncage = 7 // This pet is too high level for you to uncage.
|
||||
};
|
||||
enum class BattlePetError : uint8
|
||||
{
|
||||
CantHaveMorePetsOfType = 3, // You can't have any more pets of that type.
|
||||
CantHaveMorePets = 4, // You can't have any more pets.
|
||||
TooHighLevelToUncage = 7 // This pet is too high level for you to uncage.
|
||||
};
|
||||
|
||||
enum class BattlePetSlot : uint8
|
||||
{
|
||||
Slot0 = 0,
|
||||
Slot1 = 1,
|
||||
Slot2 = 2,
|
||||
enum class BattlePetSlot : uint8
|
||||
{
|
||||
Slot0 = 0,
|
||||
Slot1 = 1,
|
||||
Slot2 = 2,
|
||||
|
||||
Count
|
||||
};
|
||||
Count
|
||||
};
|
||||
|
||||
enum class BattlePetXpSource : uint8
|
||||
{
|
||||
PetBattle = 0,
|
||||
SpellEffect = 1,
|
||||
enum class BattlePetXpSource : uint8
|
||||
{
|
||||
PetBattle = 0,
|
||||
SpellEffect = 1,
|
||||
|
||||
Count
|
||||
};
|
||||
Count
|
||||
};
|
||||
|
||||
// 6.2.4
|
||||
enum FlagsControlType
|
||||
{
|
||||
FLAGS_CONTROL_TYPE_APPLY = 1,
|
||||
FLAGS_CONTROL_TYPE_REMOVE = 2
|
||||
};
|
||||
// 6.2.4
|
||||
enum FlagsControlType
|
||||
{
|
||||
FLAGS_CONTROL_TYPE_APPLY = 1,
|
||||
FLAGS_CONTROL_TYPE_REMOVE = 2
|
||||
};
|
||||
|
||||
// BattlePetState.db2 — LuaName-based IDs (verified via wago.tools/WoWDBDefs)
|
||||
enum BattlePetState
|
||||
{
|
||||
STATE_IS_DEAD = 1,
|
||||
STATE_MAX_HEALTH_BONUS = 2,
|
||||
STATE_INTERNAL_INITIAL_LEVEL = 17,
|
||||
STATE_STAT_POWER = 18,
|
||||
STATE_STAT_STAMINA = 19,
|
||||
STATE_STAT_SPEED = 20,
|
||||
STATE_MECHANIC_IS_POISONED = 21,
|
||||
STATE_MECHANIC_IS_STUNNED = 22,
|
||||
STATE_MOD_DAMAGE_DEALT_PERCENT = 23,
|
||||
STATE_MOD_DAMAGE_TAKEN_PERCENT = 24,
|
||||
STATE_MOD_SPEED_PERCENT = 25,
|
||||
STATE_STAT_CRIT_CHANCE = 40,
|
||||
STATE_STAT_ACCURACY = 41,
|
||||
STATE_WEATHER_BURNT_EARTH = 53,
|
||||
STATE_WEATHER_ARCANE_STORM = 54,
|
||||
STATE_WEATHER_MOONLIGHT = 55,
|
||||
STATE_WEATHER_DARKNESS = 56,
|
||||
STATE_WEATHER_SANDSTORM = 57,
|
||||
STATE_WEATHER_BLIZZARD = 58,
|
||||
STATE_WEATHER_MUD = 59,
|
||||
STATE_WEATHER_RAIN = 60,
|
||||
STATE_WEATHER_SUNLIGHT = 61,
|
||||
STATE_WEATHER_LIGHTNING_STORM = 62,
|
||||
STATE_WEATHER_WINDY = 63,
|
||||
STATE_MOD_HEALING_DEALT_PERCENT = 65,
|
||||
STATE_MOD_HEALING_TAKEN_PERCENT = 66,
|
||||
STATE_ADD_FLAT_DAMAGE_TAKEN = 71,
|
||||
STATE_ADD_FLAT_DAMAGE_DEALT = 72,
|
||||
STATE_STAT_DODGE = 73,
|
||||
STATE_GENDER = 78,
|
||||
STATE_MECHANIC_IS_BLIND = 82,
|
||||
STATE_COSMETIC_WATER_BUBBLED = 85,
|
||||
STATE_MOD_PET_TYPE_DAMAGE_DEALT_PCT = 87,
|
||||
STATE_MOD_PET_TYPE_DAMAGE_TAKEN_PCT = 88,
|
||||
STATE_MOD_PET_TYPE_ID = 89,
|
||||
STATE_SPECIAL_IS_COCKROACH = 93,
|
||||
STATE_MOD_MAX_HEALTH_PERCENT = 99,
|
||||
STATE_COSMETIC_FLY_TIER = 128,
|
||||
STATE_SPECIAL_IS_CLEANSING = 141,
|
||||
STATE_COSMETIC_BIGGLESWORTH = 144,
|
||||
STATE_PASSIVE_ELITE = 153,
|
||||
STATE_PASSIVE_BOSS = 162,
|
||||
STATE_COSMETIC_TREASURE_GOBLIN = 176,
|
||||
// these are not in BattlePetState.db2 but are used in BattlePetSpeciesState.db2
|
||||
STATE_START_WITH_BUFF = 183,
|
||||
STATE_START_WITH_BUFF_2 = 184,
|
||||
STATE_COSMETIC_SPECTRAL_BLUE = 196,
|
||||
STATE_WEATHER_TOXIC_GAS = 316,
|
||||
};
|
||||
// BattlePetState.db2 ? LuaName-based IDs (verified via wago.tools/WoWDBDefs)
|
||||
enum BattlePetState
|
||||
{
|
||||
STATE_IS_DEAD = 1,
|
||||
STATE_MAX_HEALTH_BONUS = 2,
|
||||
STATE_INTERNAL_INITIAL_LEVEL = 17,
|
||||
STATE_STAT_POWER = 18,
|
||||
STATE_STAT_STAMINA = 19,
|
||||
STATE_STAT_SPEED = 20,
|
||||
STATE_MECHANIC_IS_POISONED = 21,
|
||||
STATE_MECHANIC_IS_STUNNED = 22,
|
||||
STATE_MOD_DAMAGE_DEALT_PERCENT = 23,
|
||||
STATE_MOD_DAMAGE_TAKEN_PERCENT = 24,
|
||||
STATE_MOD_SPEED_PERCENT = 25,
|
||||
STATE_STAT_CRIT_CHANCE = 40,
|
||||
STATE_STAT_ACCURACY = 41,
|
||||
STATE_WEATHER_BURNT_EARTH = 53,
|
||||
STATE_WEATHER_ARCANE_STORM = 54,
|
||||
STATE_WEATHER_MOONLIGHT = 55,
|
||||
STATE_WEATHER_DARKNESS = 56,
|
||||
STATE_WEATHER_SANDSTORM = 57,
|
||||
STATE_WEATHER_BLIZZARD = 58,
|
||||
STATE_WEATHER_MUD = 59,
|
||||
STATE_WEATHER_RAIN = 60,
|
||||
STATE_WEATHER_SUNLIGHT = 61,
|
||||
STATE_WEATHER_LIGHTNING_STORM = 62,
|
||||
STATE_WEATHER_WINDY = 63,
|
||||
STATE_MOD_HEALING_DEALT_PERCENT = 65,
|
||||
STATE_MOD_HEALING_TAKEN_PERCENT = 66,
|
||||
STATE_ADD_FLAT_DAMAGE_TAKEN = 71,
|
||||
STATE_ADD_FLAT_DAMAGE_DEALT = 72,
|
||||
STATE_STAT_DODGE = 73,
|
||||
STATE_GENDER = 78,
|
||||
STATE_MECHANIC_IS_BLIND = 82,
|
||||
STATE_COSMETIC_WATER_BUBBLED = 85,
|
||||
STATE_MOD_PET_TYPE_DAMAGE_DEALT_PCT = 87,
|
||||
STATE_MOD_PET_TYPE_DAMAGE_TAKEN_PCT = 88,
|
||||
STATE_MOD_PET_TYPE_ID = 89,
|
||||
STATE_SPECIAL_IS_COCKROACH = 93,
|
||||
STATE_MOD_MAX_HEALTH_PERCENT = 99,
|
||||
STATE_COSMETIC_FLY_TIER = 128,
|
||||
STATE_SPECIAL_IS_CLEANSING = 141,
|
||||
STATE_COSMETIC_BIGGLESWORTH = 144,
|
||||
STATE_PASSIVE_ELITE = 153,
|
||||
STATE_PASSIVE_BOSS = 162,
|
||||
STATE_COSMETIC_TREASURE_GOBLIN = 176,
|
||||
// these are not in BattlePetState.db2 but are used in BattlePetSpeciesState.db2
|
||||
STATE_START_WITH_BUFF = 183,
|
||||
STATE_START_WITH_BUFF_2 = 184,
|
||||
STATE_COSMETIC_SPECTRAL_BLUE = 196,
|
||||
STATE_WEATHER_TOXIC_GAS = 316,
|
||||
};
|
||||
|
||||
enum BattlePetSaveInfo
|
||||
{
|
||||
BATTLE_PET_UNCHANGED = 0,
|
||||
BATTLE_PET_CHANGED = 1,
|
||||
BATTLE_PET_NEW = 2,
|
||||
BATTLE_PET_REMOVED = 3
|
||||
};
|
||||
enum BattlePetSaveInfo
|
||||
{
|
||||
BATTLE_PET_UNCHANGED = 0,
|
||||
BATTLE_PET_CHANGED = 1,
|
||||
BATTLE_PET_NEW = 2,
|
||||
BATTLE_PET_REMOVED = 3
|
||||
};
|
||||
|
||||
struct BattlePet
|
||||
{
|
||||
void CalculateStats();
|
||||
struct BattlePet
|
||||
{
|
||||
void CalculateStats();
|
||||
|
||||
WorldPackets::BattlePet::BattlePet PacketInfo;
|
||||
time_t NameTimestamp = time_t(0);
|
||||
std::unique_ptr<::DeclinedName> DeclinedName;
|
||||
BattlePetSaveInfo SaveInfo = BATTLE_PET_UNCHANGED;
|
||||
};
|
||||
WorldPackets::BattlePet::BattlePet PacketInfo;
|
||||
time_t NameTimestamp = time_t(0);
|
||||
std::unique_ptr<::DeclinedName> DeclinedName;
|
||||
BattlePetSaveInfo SaveInfo = BATTLE_PET_UNCHANGED;
|
||||
};
|
||||
|
||||
class TC_GAME_API BattlePetMgr
|
||||
{
|
||||
public:
|
||||
explicit BattlePetMgr(WorldSession* owner);
|
||||
BattlePetMgr(BattlePetMgr const& right) = delete;
|
||||
BattlePetMgr(BattlePetMgr&& right) = delete;
|
||||
class TC_GAME_API BattlePetMgr
|
||||
{
|
||||
public:
|
||||
explicit BattlePetMgr(WorldSession* owner);
|
||||
BattlePetMgr(BattlePetMgr const& right) = delete;
|
||||
BattlePetMgr(BattlePetMgr&& right) = delete;
|
||||
|
||||
static void Initialize();
|
||||
static void Initialize();
|
||||
|
||||
static void AddBattlePetSpeciesBySpell(uint32 spellId, BattlePetSpeciesEntry const* speciesEntry);
|
||||
static BattlePetSpeciesEntry const* GetBattlePetSpeciesByCreature(uint32 creatureId);
|
||||
static BattlePetSpeciesEntry const* GetBattlePetSpeciesBySpell(uint32 spellId);
|
||||
static uint16 RollPetBreed(uint32 species);
|
||||
static BattlePetBreedQuality GetDefaultPetQuality(uint32 species);
|
||||
static void GetBaseStats(uint32 speciesId, uint16 breedId, int32& outPower, int32& outStamina, int32& outSpeed);
|
||||
static uint32 SelectPetDisplay(BattlePetSpeciesEntry const* speciesEntry);
|
||||
static void AddBattlePetSpeciesBySpell(uint32 spellId, BattlePetSpeciesEntry const* speciesEntry);
|
||||
static BattlePetSpeciesEntry const* GetBattlePetSpeciesByCreature(uint32 creatureId);
|
||||
static BattlePetSpeciesEntry const* GetBattlePetSpeciesBySpell(uint32 spellId);
|
||||
static uint16 RollPetBreed(uint32 species);
|
||||
static BattlePetBreedQuality GetDefaultPetQuality(uint32 species);
|
||||
static void GetBaseStats(uint32 speciesId, uint16 breedId, int32& outPower, int32& outStamina, int32& outSpeed);
|
||||
static uint32 SelectPetDisplay(BattlePetSpeciesEntry const* speciesEntry);
|
||||
|
||||
void LoadFromDB(PreparedQueryResult pets, PreparedQueryResult slots);
|
||||
void SaveToDB(LoginDatabaseTransaction trans);
|
||||
void LoadFromDB(PreparedQueryResult pets, PreparedQueryResult slots);
|
||||
void SaveToDB(LoginDatabaseTransaction trans);
|
||||
|
||||
BattlePet* GetPet(ObjectGuid guid);
|
||||
void AddPet(uint32 species, uint32 display, uint16 breed, BattlePetBreedQuality quality, uint16 level = 1);
|
||||
void RemovePet(ObjectGuid guid);
|
||||
void ClearFanfare(ObjectGuid guid);
|
||||
void ModifyName(ObjectGuid guid, std::string const& name, std::unique_ptr<DeclinedName> declinedName);
|
||||
bool IsPetInSlot(ObjectGuid guid) const;
|
||||
BattlePet* GetPet(ObjectGuid guid);
|
||||
void AddPet(uint32 species, uint32 display, uint16 breed, BattlePetBreedQuality quality, uint16 level = 1);
|
||||
void RemovePet(ObjectGuid guid);
|
||||
void ClearFanfare(ObjectGuid guid);
|
||||
void ModifyName(ObjectGuid guid, std::string const& name, std::unique_ptr<DeclinedName> declinedName);
|
||||
bool IsPetInSlot(ObjectGuid guid) const;
|
||||
|
||||
uint8 GetPetCount(BattlePetSpeciesEntry const* battlePetSpecies, ObjectGuid ownerGuid) const;
|
||||
bool HasMaxPetCount(BattlePetSpeciesEntry const* battlePetSpecies, ObjectGuid ownerGuid) const;
|
||||
uint32 GetPetUniqueSpeciesCount() const;
|
||||
uint8 GetPetCount(BattlePetSpeciesEntry const* battlePetSpecies, ObjectGuid ownerGuid) const;
|
||||
bool HasMaxPetCount(BattlePetSpeciesEntry const* battlePetSpecies, ObjectGuid ownerGuid) const;
|
||||
uint32 GetPetUniqueSpeciesCount() const;
|
||||
|
||||
WorldPackets::BattlePet::BattlePetSlot* GetSlot(BattlePetSlot slot) { return slot < BattlePetSlot::Count ? &_slots[size_t(slot)] : nullptr; }
|
||||
void UnlockSlot(BattlePetSlot slot);
|
||||
WorldPackets::BattlePet::BattlePetSlot* GetSlot(BattlePetSlot slot) { return slot < BattlePetSlot::Count ? &_slots[size_t(slot)] : nullptr; }
|
||||
void UnlockSlot(BattlePetSlot slot);
|
||||
|
||||
WorldSession* GetOwner() const { return _owner; }
|
||||
WorldSession* GetOwner() const { return _owner; }
|
||||
|
||||
uint16 GetTrapLevel() const { return _trapLevel; }
|
||||
uint16 GetMaxPetLevel() const;
|
||||
std::vector<WorldPackets::BattlePet::BattlePetSlot> const& GetSlots() const { return _slots; }
|
||||
uint16 GetTrapLevel() const { return _trapLevel; }
|
||||
uint16 GetMaxPetLevel() const;
|
||||
std::vector<WorldPackets::BattlePet::BattlePetSlot> const& GetSlots() const { return _slots; }
|
||||
|
||||
void CageBattlePet(ObjectGuid guid);
|
||||
void ChangeBattlePetQuality(ObjectGuid guid, BattlePetBreedQuality quality);
|
||||
void GrantBattlePetExperience(ObjectGuid guid, uint16 xp, BattlePetXpSource xpSource);
|
||||
void GrantBattlePetLevel(ObjectGuid guid, uint16 grantedLevels);
|
||||
void HealBattlePetsPct(uint8 pct);
|
||||
void SyncBattlePetHealth(ObjectGuid guid, int32 newHealth);
|
||||
void UpdateBattlePetData(ObjectGuid guid);
|
||||
void CageBattlePet(ObjectGuid guid);
|
||||
void ChangeBattlePetQuality(ObjectGuid guid, BattlePetBreedQuality quality);
|
||||
void GrantBattlePetExperience(ObjectGuid guid, uint16 xp, BattlePetXpSource xpSource);
|
||||
void GrantBattlePetLevel(ObjectGuid guid, uint16 grantedLevels);
|
||||
void HealBattlePetsPct(uint8 pct);
|
||||
void SyncBattlePetHealth(ObjectGuid guid, int32 newHealth);
|
||||
void UpdateBattlePetData(ObjectGuid guid);
|
||||
|
||||
void SummonPet(ObjectGuid guid);
|
||||
void DismissPet();
|
||||
void SummonPet(ObjectGuid guid);
|
||||
void DismissPet();
|
||||
|
||||
void SendJournal();
|
||||
void SendUpdates(std::span<std::reference_wrapper<BattlePet const> const> pets, bool petAdded);
|
||||
void SendError(BattlePetError error, uint32 creatureId);
|
||||
void SendJournal();
|
||||
void SendUpdates(std::span<std::reference_wrapper<BattlePet const> const> pets, bool petAdded);
|
||||
void SendError(BattlePetError error, uint32 creatureId);
|
||||
|
||||
void SendJournalLockStatus();
|
||||
bool IsJournalLockAcquired() const;
|
||||
bool HasJournalLock() const { return _hasJournalLock; }
|
||||
void ToggleJournalLock(bool lock) { _hasJournalLock = lock; }
|
||||
void SendJournalLockStatus();
|
||||
bool IsJournalLockAcquired() const;
|
||||
bool HasJournalLock() const { return _hasJournalLock; }
|
||||
void ToggleJournalLock(bool lock) { _hasJournalLock = lock; }
|
||||
|
||||
bool IsBattlePetSystemEnabled() { return GetSlot(BattlePetSlot::Slot0)->Locked != true; }
|
||||
bool IsBattlePetSystemEnabled() { return GetSlot(BattlePetSlot::Slot0)->Locked != true; }
|
||||
|
||||
private:
|
||||
void UpdateSlotPetData(ObjectGuid guid, WorldPackets::BattlePet::BattlePet const& packetInfo);
|
||||
private:
|
||||
void UpdateSlotPetData(ObjectGuid guid, WorldPackets::BattlePet::BattlePet const& packetInfo);
|
||||
|
||||
WorldSession* _owner;
|
||||
bool _hasJournalLock = false;
|
||||
uint16 _trapLevel = 0;
|
||||
std::unordered_map<uint64 /*battlePetGuid*/, BattlePet> _pets;
|
||||
std::vector<WorldPackets::BattlePet::BattlePetSlot> _slots;
|
||||
WorldSession* _owner;
|
||||
bool _hasJournalLock = false;
|
||||
uint16 _trapLevel = 0;
|
||||
std::unordered_map<uint64 /*battlePetGuid*/, BattlePet> _pets;
|
||||
std::vector<WorldPackets::BattlePet::BattlePetSlot> _slots;
|
||||
|
||||
static void LoadAvailablePetBreeds();
|
||||
static void LoadDefaultPetQualities();
|
||||
};
|
||||
static void LoadAvailablePetBreeds();
|
||||
static void LoadDefaultPetQualities();
|
||||
};
|
||||
}
|
||||
#endif // BattlePetMgr_h__
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#include "PetBattleMgr.h"
|
||||
#include "Player.h"
|
||||
#include "Random.h"
|
||||
#include "SceneObject.h"
|
||||
#include "Util.h"
|
||||
#include "WorldSession.h"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
namespace PetBattles
|
||||
{
|
||||
@@ -278,14 +278,14 @@ namespace PetBattles
|
||||
wildPet.BaseSpeed = baseSpeed;
|
||||
|
||||
// Use same formula as BattlePet::CalculateStats (DB2 values are scaled ~1000-2000)
|
||||
float health = float(baseHP) * qualityMultiplier * wildPet.Level;
|
||||
float power = float(basePower) * qualityMultiplier * wildPet.Level;
|
||||
float speed = float(baseSpeed) * qualityMultiplier * wildPet.Level;
|
||||
double health = double(baseHP) * qualityMultiplier * wildPet.Level;
|
||||
double power = double(basePower) * qualityMultiplier * wildPet.Level;
|
||||
double speed = double(baseSpeed) * qualityMultiplier * wildPet.Level;
|
||||
|
||||
wildPet.MaxHealth = int32(round(health / 20.0f) + 100);
|
||||
wildPet.MaxHealth = int32(round(health / 20.0) + 100);
|
||||
wildPet.Health = wildPet.MaxHealth;
|
||||
wildPet.Power = int32(round(power / 100.0f));
|
||||
wildPet.Speed = int32(round(speed / 100.0f));
|
||||
wildPet.Power = int32(round(power / 100.0));
|
||||
wildPet.Speed = int32(round(speed / 100.0));
|
||||
wildPet.EffectivePower = wildPet.Power;
|
||||
wildPet.EffectiveSpeed = wildPet.Speed;
|
||||
|
||||
@@ -338,14 +338,14 @@ namespace PetBattles
|
||||
|
||||
pet.Level = level;
|
||||
|
||||
float health = float(pet.BaseStamina) * qualityMultiplier * level;
|
||||
float power = float(pet.BasePower) * qualityMultiplier * level;
|
||||
float speed = float(pet.BaseSpeed) * qualityMultiplier * level;
|
||||
double health = double(pet.BaseStamina) * qualityMultiplier * level;
|
||||
double power = double(pet.BasePower) * qualityMultiplier * level;
|
||||
double speed = double(pet.BaseSpeed) * qualityMultiplier * level;
|
||||
|
||||
pet.MaxHealth = int32(round(health / 20.0f) + 100);
|
||||
pet.MaxHealth = int32(round(health / 20.0) + 100);
|
||||
pet.Health = pet.MaxHealth; // PvP teams enter at full health
|
||||
pet.Power = std::max(1, int32(round(power / 100.0f)));
|
||||
pet.Speed = std::max(1, int32(round(speed / 100.0f)));
|
||||
pet.Power = std::max(1, int32(round(power / 100.0)));
|
||||
pet.Speed = std::max(1, int32(round(speed / 100.0)));
|
||||
pet.EffectivePower = pet.Power;
|
||||
pet.EffectiveSpeed = pet.Speed;
|
||||
}
|
||||
@@ -654,6 +654,7 @@ namespace PetBattles
|
||||
{
|
||||
PetBattleTeamData& team = _teams[teamIdx];
|
||||
PetBattlePetData& activePet = team.Pets[team.FrontPetIndex];
|
||||
activePet.SeenAction = true;
|
||||
|
||||
// Stunned pets skip their turn
|
||||
if (activePet.IsStunned)
|
||||
@@ -1685,8 +1686,8 @@ namespace PetBattles
|
||||
{
|
||||
DamageResult result;
|
||||
|
||||
// Formula: rawDamage = abilityPower * (attackerPower / 20.0f)
|
||||
float rawDamage = abilityPower * (attackerPower / 20.0f);
|
||||
// Formula (12.1.0.69587 wire, 25/25 hits): rawDamage = abilityPower * (1 + attackerPower / 20)
|
||||
float rawDamage = abilityPower * (1.0f + attackerPower / 20.0f);
|
||||
|
||||
// Type effectiveness modifier
|
||||
result.TypeMod = GetTypeEffectiveness(abilityType, PetBattlePetType(defender.PetType));
|
||||
@@ -1742,13 +1743,15 @@ namespace PetBattles
|
||||
if (attacker.PetType != PET_TYPE_ELEMENTAL)
|
||||
critChance += _environments[PET_BATTLE_WEATHER_ENV_SLOT].GetState(BattlePets::STATE_STAT_CRIT_CHANCE) / 100.0f;
|
||||
critChance = std::clamp(critChance, 0.0f, 1.0f);
|
||||
// Retail truncates the modified damage, then applies the crit multiplier and truncates again
|
||||
// (wire: 16.5 -> 16, 475.5 -> 475; crit 5 -> 7, 35 -> 52)
|
||||
int32 damage = int32(std::floor(rawDamage));
|
||||
if (frand(0.0f, 1.0f) < critChance)
|
||||
{
|
||||
rawDamage *= PET_BATTLE_CRIT_MULTIPLIER;
|
||||
damage = int32(std::floor(damage * PET_BATTLE_CRIT_MULTIPLIER));
|
||||
result.IsCrit = true;
|
||||
}
|
||||
|
||||
int32 damage = std::max(1, int32(std::round(rawDamage)));
|
||||
damage = std::max(1, damage);
|
||||
|
||||
// Flat damage taken modifier from environment (e.g. Sandstorm damage shield)
|
||||
if (defender.PetType != PET_TYPE_ELEMENTAL)
|
||||
@@ -1773,7 +1776,7 @@ namespace PetBattles
|
||||
|
||||
int32 PetBattle::CalculateAbilityHealing(int32 healPower, int32 attackerPower, PetBattlePetData const& healer)
|
||||
{
|
||||
float rawHealing = healPower * (attackerPower / 20.0f);
|
||||
float rawHealing = healPower * (1.0f + attackerPower / 20.0f);
|
||||
|
||||
// Weather healing modifier from environment states (Elemental passive: ignores weather)
|
||||
if (healer.PetType != PET_TYPE_ELEMENTAL)
|
||||
@@ -1788,7 +1791,7 @@ namespace PetBattles
|
||||
if (stateID == BattlePets::STATE_MOD_HEALING_DEALT_PERCENT && stateValue != 0)
|
||||
rawHealing *= (1.0f + stateValue / 100.0f);
|
||||
|
||||
return std::max(1, int32(std::round(rawHealing)));
|
||||
return std::max(1, int32(std::floor(rawHealing)));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -2258,13 +2261,14 @@ namespace PetBattles
|
||||
for (uint8 i = 0; i < loserTeam.PetCount; ++i)
|
||||
maxOpponentLevel = std::max(maxOpponentLevel, loserTeam.Pets[i].Level);
|
||||
|
||||
// Award XP to all surviving pets on the winning team
|
||||
// Award XP to every pet of the winning team that took the field (retail: a pet that died
|
||||
// during the battle still gets XP, a pet that never fought gets none -- FINAL_ROUND SeenAction)
|
||||
BattlePets::BattlePetMgr* petMgr = player->GetSession()->GetBattlePetMgr();
|
||||
|
||||
for (uint8 i = 0; i < winnerTeam.PetCount; ++i)
|
||||
{
|
||||
PetBattlePetData& pet = winnerTeam.Pets[i];
|
||||
if (!pet.IsAlive() || pet.Level >= BattlePets::MAX_BATTLE_PET_LEVEL)
|
||||
if (!pet.SeenAction || pet.Level >= BattlePets::MAX_BATTLE_PET_LEVEL)
|
||||
continue;
|
||||
|
||||
if (pet.BattlePetGUID.IsEmpty())
|
||||
@@ -2390,7 +2394,6 @@ namespace PetBattles
|
||||
void PetBattle::SendFinalRoundPacket(bool abandoned)
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleFinalRound finalRound;
|
||||
finalRound.SceneObjectGUID = _sceneObjectGUID;
|
||||
finalRound.Abandoned = abandoned;
|
||||
finalRound.PvpBattle = (_battleType == PET_BATTLE_TYPE_PVP || _battleType == PET_BATTLE_TYPE_LFPB);
|
||||
// 12.0.7 (sniff-verified vs b_pets, 5 battles): the winner is the per-team flag pair in the FinalRound
|
||||
@@ -2421,8 +2424,8 @@ namespace PetBattles
|
||||
pet.Pboid = t * MAX_PET_BATTLE_TEAM_SIZE + i;
|
||||
pet.Captured = team.Pets[i].IsCaptured;
|
||||
pet.Caged = false;
|
||||
pet.SeenAction = false;
|
||||
pet.AwardedXP = _canAwardXP;
|
||||
pet.SeenAction = team.Pets[i].SeenAction;
|
||||
pet.AwardedXP = _canAwardXP && team.Pets[i].SeenAction && team.Pets[i].Level < BattlePets::MAX_BATTLE_PET_LEVEL;
|
||||
finalRound.Pets.push_back(pet);
|
||||
}
|
||||
}
|
||||
@@ -2478,7 +2481,6 @@ namespace PetBattles
|
||||
|
||||
// Send finished notification and sync pet health to journal
|
||||
WorldPackets::BattlePet::PetBattleFinished finished;
|
||||
finished.SceneObjectGUID = _sceneObjectGUID;
|
||||
for (uint8 t = 0; t < MAX_PET_BATTLE_PLAYERS; ++t)
|
||||
{
|
||||
Player* teamPlayer = GetPlayerForTeam(t);
|
||||
@@ -2489,12 +2491,20 @@ namespace PetBattles
|
||||
|
||||
BattlePets::BattlePetMgr* petMgr = teamPlayer->GetSession()->GetBattlePetMgr();
|
||||
PetBattleTeamData const& team = _teams[t];
|
||||
std::vector<std::reference_wrapper<BattlePets::BattlePet const>> changed;
|
||||
for (uint8 p = 0; p < team.PetCount; ++p)
|
||||
{
|
||||
if (!team.Pets[p].BattlePetGUID.IsEmpty())
|
||||
petMgr->SyncBattlePetHealth(team.Pets[p].BattlePetGUID, team.Pets[p].Health);
|
||||
if (team.Pets[p].BattlePetGUID.IsEmpty())
|
||||
continue;
|
||||
petMgr->SyncBattlePetHealth(team.Pets[p].BattlePetGUID, team.Pets[p].Health);
|
||||
if (BattlePets::BattlePet const* journalPet = petMgr->GetPet(team.Pets[p].BattlePetGUID))
|
||||
changed.emplace_back(*journalPet);
|
||||
}
|
||||
petMgr->SendJournal();
|
||||
// Retail (12.1.0.69587): after SMSG_PET_BATTLE_FINISHED the client receives
|
||||
// SMSG_BATTLE_PET_UPDATES carrying only the pets whose health/xp/level changed --
|
||||
// never a full SMSG_BATTLE_PET_JOURNAL resend (10 KB) at this point.
|
||||
if (!changed.empty())
|
||||
petMgr->SendUpdates(changed, false);
|
||||
}
|
||||
|
||||
// NPC trainer post-battle: restore movement and play cry emote on loss
|
||||
@@ -2699,24 +2709,6 @@ namespace PetBattles
|
||||
return ObjectAccessor::FindPlayer(_teams[teamIdx].PlayerGUID);
|
||||
}
|
||||
|
||||
void PetBattle::DespawnSceneObject()
|
||||
{
|
||||
if (_sceneObjectGUID.IsEmpty())
|
||||
return;
|
||||
|
||||
for (uint8 t = 0; t < MAX_PET_BATTLE_PLAYERS; ++t)
|
||||
{
|
||||
if (Player* player = GetPlayerForTeam(t))
|
||||
{
|
||||
if (SceneObject* sceneObject = ObjectAccessor::GetSceneObject(*player, _sceneObjectGUID))
|
||||
sceneObject->Remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_sceneObjectGUID.Clear();
|
||||
}
|
||||
|
||||
uint32 PetBattle::GetOpponentCreatureID(uint8 teamIdx) const
|
||||
{
|
||||
uint8 opponentTeam = 1 - teamIdx;
|
||||
|
||||
@@ -33,131 +33,132 @@ struct BattlePetAbilityEffectEntry;
|
||||
namespace PetBattles
|
||||
{
|
||||
|
||||
struct NPCTeamPetInfo;
|
||||
struct NPCTeamPetInfo;
|
||||
|
||||
struct DamageResult
|
||||
{
|
||||
int32 Damage = 0;
|
||||
bool IsCrit = false;
|
||||
float TypeMod = 1.0f;
|
||||
};
|
||||
|
||||
struct PetBattleAura
|
||||
{
|
||||
uint32 AbilityID = 0;
|
||||
uint32 EffectID = 0; // BattlePetAbilityEffectEntry::ID that triggers each tick
|
||||
int32 Duration = 0; // total duration in rounds
|
||||
int32 RemainingRounds = 0; // rounds until expiry
|
||||
uint32 AuraInstanceID = 0; // unique ID for this aura instance
|
||||
int32 CurrentRound = 0; // round when aura was applied
|
||||
uint8 CasterTeam = 0;
|
||||
uint8 CasterPet = 0;
|
||||
PetBattleAuraType AuraType = PET_BATTLE_AURA_BUFF;
|
||||
int32 DamagePerTick = 0; // damage or healing per tick
|
||||
int8 PetType = -1; // pet type of the caster for type effectiveness on DoTs
|
||||
uint8 StateFlags = PET_BATTLE_AURA_STATE_NONE;
|
||||
};
|
||||
|
||||
struct PetBattleEnvironment
|
||||
{
|
||||
uint32 AbilityID = 0;
|
||||
int8 RemainingRounds = 0;
|
||||
uint8 CasterTeam = 0;
|
||||
uint32 AuraInstanceID = 0; // Unique ID for the environment aura (sent to client)
|
||||
int32 CurrentRound = 0; // Round when weather was applied
|
||||
std::unordered_map<uint32, int32> States; // BattlePetState modifiers from DB2
|
||||
|
||||
// States flagged with IsPeriodic=1 in BattlePetEffectProperties param[4].
|
||||
// Each round their SET_STATE effect is re-emitted to the client so the
|
||||
// visual / counter on the environment slot refreshes; the stored value
|
||||
// itself doesn't change between ticks.
|
||||
std::unordered_set<uint32> PeriodicStateIDs;
|
||||
|
||||
bool IsActive() const { return AbilityID != 0 && AuraInstanceID != 0; }
|
||||
int32 GetState(uint32 stateID) const
|
||||
struct DamageResult
|
||||
{
|
||||
auto it = States.find(stateID);
|
||||
return it != States.end() ? it->second : 0;
|
||||
}
|
||||
};
|
||||
int32 Damage = 0;
|
||||
bool IsCrit = false;
|
||||
float TypeMod = 1.0f;
|
||||
};
|
||||
|
||||
struct PetBattlePetData
|
||||
{
|
||||
ObjectGuid BattlePetGUID;
|
||||
uint32 Species = 0;
|
||||
uint32 CreatureID = 0;
|
||||
uint32 DisplayID = 0;
|
||||
int32 NpcTeamMemberID = 0;
|
||||
uint16 Breed = 0;
|
||||
uint16 Level = 0;
|
||||
uint16 Xp = 0;
|
||||
uint8 Quality = 0;
|
||||
int8 PetType = 0;
|
||||
uint32 Flags = 0;
|
||||
std::string CustomName;
|
||||
|
||||
// Base breed+species stats from DB2 (sent in States, used by client)
|
||||
int32 BasePower = 0; // STATE_STAT_POWER (18) raw base value
|
||||
int32 BaseStamina = 0; // STATE_STAT_STAMINA (19) raw base value
|
||||
int32 BaseSpeed = 0; // STATE_STAT_SPEED (20) raw base value
|
||||
|
||||
// Combat stats (computed from base stats + level + quality)
|
||||
int32 Health = 0;
|
||||
int32 MaxHealth = 0;
|
||||
int32 Power = 0;
|
||||
int32 Speed = 0;
|
||||
|
||||
// Effective stats (base + aura modifiers, recalculated each round)
|
||||
int32 EffectivePower = 0;
|
||||
int32 EffectiveSpeed = 0;
|
||||
|
||||
// Ability IDs (up to 3 per pet)
|
||||
std::array<uint32, MAX_PET_BATTLE_ABILITIES> AbilityIDs = {};
|
||||
std::array<int8, MAX_PET_BATTLE_ABILITIES> AbilityCooldowns = {};
|
||||
std::array<int8, MAX_PET_BATTLE_ABILITIES> AbilityLockdowns = {};
|
||||
|
||||
// Multi-turn ability state
|
||||
int32 MultiTurnAbilityID = 0; // currently executing multi-turn ability (0 = none)
|
||||
int8 MultiTurnCurrentIndex = 0; // which turn index we're on (0-based)
|
||||
int8 MultiTurnTotalTurns = 0; // total turns in the sequence
|
||||
bool IsLockedByMultiTurn = false; // cannot use other abilities during multi-turn
|
||||
|
||||
// Battle state
|
||||
bool IsAlive() const { return Health > 0; }
|
||||
int32 GetState(uint32 stateID) const
|
||||
struct PetBattleAura
|
||||
{
|
||||
for (auto const& [id, val] : States)
|
||||
if (id == stateID)
|
||||
return val;
|
||||
return 0;
|
||||
}
|
||||
bool IsCaptured = false;
|
||||
bool IsStunned = false; // skips next turn
|
||||
uint32 AbilityID = 0;
|
||||
uint32 EffectID = 0; // BattlePetAbilityEffectEntry::ID that triggers each tick
|
||||
int32 Duration = 0; // total duration in rounds
|
||||
int32 RemainingRounds = 0; // rounds until expiry
|
||||
uint32 AuraInstanceID = 0; // unique ID for this aura instance
|
||||
int32 CurrentRound = 0; // round when aura was applied
|
||||
uint8 CasterTeam = 0;
|
||||
uint8 CasterPet = 0;
|
||||
PetBattleAuraType AuraType = PET_BATTLE_AURA_BUFF;
|
||||
int32 DamagePerTick = 0; // damage or healing per tick
|
||||
int8 PetType = -1; // pet type of the caster for type effectiveness on DoTs
|
||||
uint8 StateFlags = PET_BATTLE_AURA_STATE_NONE;
|
||||
};
|
||||
|
||||
// Passive ability state
|
||||
bool UndeadReviveUsed = false; // Undead: already used revive
|
||||
bool MechanicalReviveUsed = false; // Mechanical: already used revive
|
||||
bool IsUndeadReviving = false; // Undead: currently in revive round
|
||||
bool DragonkinDamageBonus = false; // Dragonkin: active for current round
|
||||
|
||||
// Auras applied to this pet
|
||||
std::vector<PetBattleAura> Auras;
|
||||
|
||||
// State values for the pet
|
||||
std::vector<std::pair<uint32, int32>> States;
|
||||
|
||||
void RecalculateEffectiveStats(std::unordered_map<uint32, int32> const* envStates = nullptr)
|
||||
struct PetBattleEnvironment
|
||||
{
|
||||
EffectivePower = Power;
|
||||
EffectiveSpeed = Speed;
|
||||
uint32 AbilityID = 0;
|
||||
int8 RemainingRounds = 0;
|
||||
uint8 CasterTeam = 0;
|
||||
uint32 AuraInstanceID = 0; // Unique ID for the environment aura (sent to client)
|
||||
int32 CurrentRound = 0; // Round when weather was applied
|
||||
std::unordered_map<uint32, int32> States; // BattlePetState modifiers from DB2
|
||||
|
||||
// Apply percentage modifiers from pet states (set by abilities via effectCategory 3/6)
|
||||
float powerModPct = 0.0f;
|
||||
float speedModPct = 0.0f;
|
||||
for (auto const& [stateID, stateValue] : States)
|
||||
// States flagged with IsPeriodic=1 in BattlePetEffectProperties param[4].
|
||||
// Each round their SET_STATE effect is re-emitted to the client so the
|
||||
// visual / counter on the environment slot refreshes; the stored value
|
||||
// itself doesn't change between ticks.
|
||||
std::unordered_set<uint32> PeriodicStateIDs;
|
||||
|
||||
bool IsActive() const { return AbilityID != 0 && AuraInstanceID != 0; }
|
||||
int32 GetState(uint32 stateID) const
|
||||
{
|
||||
switch (stateID)
|
||||
auto it = States.find(stateID);
|
||||
return it != States.end() ? it->second : 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct PetBattlePetData
|
||||
{
|
||||
ObjectGuid BattlePetGUID;
|
||||
uint32 Species = 0;
|
||||
uint32 CreatureID = 0;
|
||||
uint32 DisplayID = 0;
|
||||
int32 NpcTeamMemberID = 0;
|
||||
uint16 Breed = 0;
|
||||
uint16 Level = 0;
|
||||
uint16 Xp = 0;
|
||||
uint8 Quality = 0;
|
||||
int8 PetType = 0;
|
||||
uint32 Flags = 0;
|
||||
std::string CustomName;
|
||||
|
||||
// Base breed+species stats from DB2 (sent in States, used by client)
|
||||
int32 BasePower = 0; // STATE_STAT_POWER (18) raw base value
|
||||
int32 BaseStamina = 0; // STATE_STAT_STAMINA (19) raw base value
|
||||
int32 BaseSpeed = 0; // STATE_STAT_SPEED (20) raw base value
|
||||
|
||||
// Combat stats (computed from base stats + level + quality)
|
||||
int32 Health = 0;
|
||||
int32 MaxHealth = 0;
|
||||
int32 Power = 0;
|
||||
int32 Speed = 0;
|
||||
|
||||
// Effective stats (base + aura modifiers, recalculated each round)
|
||||
int32 EffectivePower = 0;
|
||||
int32 EffectiveSpeed = 0;
|
||||
|
||||
// Ability IDs (up to 3 per pet)
|
||||
std::array<uint32, MAX_PET_BATTLE_ABILITIES> AbilityIDs = {};
|
||||
std::array<int8, MAX_PET_BATTLE_ABILITIES> AbilityCooldowns = {};
|
||||
std::array<int8, MAX_PET_BATTLE_ABILITIES> AbilityLockdowns = {};
|
||||
|
||||
// Multi-turn ability state
|
||||
int32 MultiTurnAbilityID = 0; // currently executing multi-turn ability (0 = none)
|
||||
int8 MultiTurnCurrentIndex = 0; // which turn index we're on (0-based)
|
||||
int8 MultiTurnTotalTurns = 0; // total turns in the sequence
|
||||
bool IsLockedByMultiTurn = false; // cannot use other abilities during multi-turn
|
||||
|
||||
// Battle state
|
||||
bool IsAlive() const { return Health > 0; }
|
||||
bool SeenAction = false; // was on the field for at least one turn (FINAL_ROUND SeenAction, XP eligibility)
|
||||
int32 GetState(uint32 stateID) const
|
||||
{
|
||||
for (auto const& [id, val] : States)
|
||||
if (id == stateID)
|
||||
return val;
|
||||
return 0;
|
||||
}
|
||||
bool IsCaptured = false;
|
||||
bool IsStunned = false; // skips next turn
|
||||
|
||||
// Passive ability state
|
||||
bool UndeadReviveUsed = false; // Undead: already used revive
|
||||
bool MechanicalReviveUsed = false; // Mechanical: already used revive
|
||||
bool IsUndeadReviving = false; // Undead: currently in revive round
|
||||
bool DragonkinDamageBonus = false; // Dragonkin: active for current round
|
||||
|
||||
// Auras applied to this pet
|
||||
std::vector<PetBattleAura> Auras;
|
||||
|
||||
// State values for the pet
|
||||
std::vector<std::pair<uint32, int32>> States;
|
||||
|
||||
void RecalculateEffectiveStats(std::unordered_map<uint32, int32> const* envStates = nullptr)
|
||||
{
|
||||
EffectivePower = Power;
|
||||
EffectiveSpeed = Speed;
|
||||
|
||||
// Apply percentage modifiers from pet states (set by abilities via effectCategory 3/6)
|
||||
float powerModPct = 0.0f;
|
||||
float speedModPct = 0.0f;
|
||||
for (auto const& [stateID, stateValue] : States)
|
||||
{
|
||||
switch (stateID)
|
||||
{
|
||||
case BattlePets::STATE_MOD_DAMAGE_DEALT_PERCENT:
|
||||
powerModPct += stateValue / 100.0f;
|
||||
break;
|
||||
@@ -166,223 +167,218 @@ struct PetBattlePetData
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply stat modifiers from BUFF/DEBUFF auras
|
||||
for (PetBattleAura const& aura : Auras)
|
||||
{
|
||||
if (aura.AuraType == PET_BATTLE_AURA_BUFF)
|
||||
powerModPct += aura.DamagePerTick / 100.0f;
|
||||
else if (aura.AuraType == PET_BATTLE_AURA_DEBUFF)
|
||||
powerModPct -= aura.DamagePerTick / 100.0f;
|
||||
}
|
||||
|
||||
EffectivePower = int32(EffectivePower * (1.0f + powerModPct));
|
||||
EffectiveSpeed = int32(EffectiveSpeed * (1.0f + speedModPct));
|
||||
|
||||
// Flying passive: +50% speed while above 50% HP
|
||||
if (PetType == PET_TYPE_FLYING && Health > MaxHealth / 2)
|
||||
EffectiveSpeed = int32(EffectiveSpeed * (1.0f + PASSIVE_FLYING_SPEED_BONUS));
|
||||
|
||||
// Weather speed modifier from environment states (Elemental passive: ignores weather)
|
||||
if (PetType != PET_TYPE_ELEMENTAL && envStates)
|
||||
{
|
||||
auto it = envStates->find(BattlePets::STATE_MOD_SPEED_PERCENT);
|
||||
if (it != envStates->end() && it->second != 0)
|
||||
EffectiveSpeed = int32(EffectiveSpeed * (1.0f + it->second / 100.0f));
|
||||
}
|
||||
|
||||
if (EffectivePower < 1) EffectivePower = 1;
|
||||
if (EffectiveSpeed < 1) EffectiveSpeed = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Apply stat modifiers from BUFF/DEBUFF auras
|
||||
for (PetBattleAura const& aura : Auras)
|
||||
{
|
||||
if (aura.AuraType == PET_BATTLE_AURA_BUFF)
|
||||
powerModPct += aura.DamagePerTick / 100.0f;
|
||||
else if (aura.AuraType == PET_BATTLE_AURA_DEBUFF)
|
||||
powerModPct -= aura.DamagePerTick / 100.0f;
|
||||
}
|
||||
|
||||
EffectivePower = int32(EffectivePower * (1.0f + powerModPct));
|
||||
EffectiveSpeed = int32(EffectiveSpeed * (1.0f + speedModPct));
|
||||
|
||||
// Flying passive: +50% speed while above 50% HP
|
||||
if (PetType == PET_TYPE_FLYING && Health > MaxHealth / 2)
|
||||
EffectiveSpeed = int32(EffectiveSpeed * (1.0f + PASSIVE_FLYING_SPEED_BONUS));
|
||||
|
||||
// Weather speed modifier from environment states (Elemental passive: ignores weather)
|
||||
if (PetType != PET_TYPE_ELEMENTAL && envStates)
|
||||
{
|
||||
auto it = envStates->find(BattlePets::STATE_MOD_SPEED_PERCENT);
|
||||
if (it != envStates->end() && it->second != 0)
|
||||
EffectiveSpeed = int32(EffectiveSpeed * (1.0f + it->second / 100.0f));
|
||||
}
|
||||
|
||||
if (EffectivePower < 1) EffectivePower = 1;
|
||||
if (EffectiveSpeed < 1) EffectiveSpeed = 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct PetBattleTeamData
|
||||
{
|
||||
ObjectGuid PlayerGUID;
|
||||
uint32 TrapAbilityID = 0;
|
||||
uint16 TrapStatus = 0; // 0 = not used, 1 = used, 2+ = cooldown
|
||||
int8 FrontPetIndex = 0; // index of current active pet [0..2]
|
||||
uint8 InputFlags = 0;
|
||||
bool HasInputThisRound = false;
|
||||
|
||||
std::array<PetBattlePetData, MAX_PET_BATTLE_TEAM_SIZE> Pets;
|
||||
uint8 PetCount = 0;
|
||||
|
||||
// Pending input for this round
|
||||
PetBattleMoveType PendingMoveType = PET_BATTLE_MOVE_PASS;
|
||||
uint32 PendingAbilityID = 0;
|
||||
int8 PendingNewFrontPet = -1;
|
||||
|
||||
bool HasAlivePets() const
|
||||
struct PetBattleTeamData
|
||||
{
|
||||
for (uint8 i = 0; i < PetCount; ++i)
|
||||
if (Pets[i].IsAlive() && !Pets[i].IsCaptured)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
ObjectGuid PlayerGUID;
|
||||
uint32 TrapAbilityID = 0;
|
||||
uint16 TrapStatus = 0; // 0 = not used, 1 = used, 2+ = cooldown
|
||||
int8 FrontPetIndex = 0; // index of current active pet [0..2]
|
||||
uint8 InputFlags = 0;
|
||||
bool HasInputThisRound = false;
|
||||
|
||||
int8 GetFirstAlivePetIndex() const
|
||||
std::array<PetBattlePetData, MAX_PET_BATTLE_TEAM_SIZE> Pets;
|
||||
uint8 PetCount = 0;
|
||||
|
||||
// Pending input for this round
|
||||
PetBattleMoveType PendingMoveType = PET_BATTLE_MOVE_PASS;
|
||||
uint32 PendingAbilityID = 0;
|
||||
int8 PendingNewFrontPet = -1;
|
||||
|
||||
bool HasAlivePets() const
|
||||
{
|
||||
for (uint8 i = 0; i < PetCount; ++i)
|
||||
if (Pets[i].IsAlive() && !Pets[i].IsCaptured)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int8 GetFirstAlivePetIndex() const
|
||||
{
|
||||
for (uint8 i = 0; i < PetCount; ++i)
|
||||
if (Pets[i].IsAlive() && !Pets[i].IsCaptured)
|
||||
return static_cast<int8>(i);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
// Represents a single effect that happened during round resolution
|
||||
struct PetBattleRoundEffect
|
||||
{
|
||||
for (uint8 i = 0; i < PetCount; ++i)
|
||||
if (Pets[i].IsAlive() && !Pets[i].IsCaptured)
|
||||
return static_cast<int8>(i);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
uint32 AbilityEffectID = 0;
|
||||
uint16 Flags = 0;
|
||||
uint8 EffectType = 0;
|
||||
uint8 SourceTeam = 0;
|
||||
uint8 SourcePet = 0;
|
||||
uint8 TargetTeam = 0;
|
||||
uint8 TargetPet = 0;
|
||||
int32 Param1 = 0;
|
||||
int32 Param2 = 0;
|
||||
int32 Param3 = 0; // For aura effects: RoundsRemaining (captured at creation time)
|
||||
int32 Param4 = 0; // For aura effects: CurrentRound (captured at creation time)
|
||||
int8 TargetEnvSlot = -1; // >= 0: target is environment slot (PBOID = PBOID_ENVIRONMENT_BASE + slot)
|
||||
};
|
||||
|
||||
// Represents a single effect that happened during round resolution
|
||||
struct PetBattleRoundEffect
|
||||
{
|
||||
uint32 AbilityEffectID = 0;
|
||||
uint16 Flags = 0;
|
||||
uint8 EffectType = 0;
|
||||
uint8 SourceTeam = 0;
|
||||
uint8 SourcePet = 0;
|
||||
uint8 TargetTeam = 0;
|
||||
uint8 TargetPet = 0;
|
||||
int32 Param1 = 0;
|
||||
int32 Param2 = 0;
|
||||
int32 Param3 = 0; // For aura effects: RoundsRemaining (captured at creation time)
|
||||
int32 Param4 = 0; // For aura effects: CurrentRound (captured at creation time)
|
||||
int8 TargetEnvSlot = -1; // >= 0: target is environment slot (PBOID = PBOID_ENVIRONMENT_BASE + slot)
|
||||
};
|
||||
class TC_GAME_API PetBattle
|
||||
{
|
||||
public:
|
||||
PetBattle();
|
||||
~PetBattle();
|
||||
|
||||
class TC_GAME_API PetBattle
|
||||
{
|
||||
public:
|
||||
PetBattle();
|
||||
~PetBattle();
|
||||
uint32 GetBattleID() const { return _battleID; }
|
||||
void SetBattleID(uint32 id) { _battleID = id; }
|
||||
|
||||
uint32 GetBattleID() const { return _battleID; }
|
||||
void SetBattleID(uint32 id) { _battleID = id; }
|
||||
PetBattleType GetBattleType() const { return _battleType; }
|
||||
PetBattleState GetBattleState() const { return _state; }
|
||||
void SetBattleState(PetBattleState state) { _state = state; }
|
||||
uint32 GetCurrentRound() const { return _currentRound; }
|
||||
uint32 GetElapsedTime() const { return _elapsedSecs; }
|
||||
bool CanAwardXP() const { return _canAwardXP; }
|
||||
|
||||
PetBattleType GetBattleType() const { return _battleType; }
|
||||
PetBattleState GetBattleState() const { return _state; }
|
||||
void SetBattleState(PetBattleState state) { _state = state; }
|
||||
uint32 GetCurrentRound() const { return _currentRound; }
|
||||
uint32 GetElapsedTime() const { return _elapsedSecs; }
|
||||
bool CanAwardXP() const { return _canAwardXP; }
|
||||
PetBattleTeamData& GetTeam(uint8 teamIdx) { return _teams[teamIdx]; }
|
||||
PetBattleTeamData const& GetTeam(uint8 teamIdx) const { return _teams[teamIdx]; }
|
||||
|
||||
PetBattleTeamData& GetTeam(uint8 teamIdx) { return _teams[teamIdx]; }
|
||||
PetBattleTeamData const& GetTeam(uint8 teamIdx) const { return _teams[teamIdx]; }
|
||||
PetBattleEnvironment const& GetEnvironment(uint8 slot) const { return _environments[slot]; }
|
||||
|
||||
PetBattleEnvironment const& GetEnvironment(uint8 slot) const { return _environments[slot]; }
|
||||
// Setup
|
||||
void InitWildBattle(Player* player, ObjectGuid wildCreatureGUID);
|
||||
void InitPvPBattle(Player* player1, Player* player2);
|
||||
void InitNPCBattle(Player* player, Creature* trainer, std::vector<NPCTeamPetInfo> const& npcTeam);
|
||||
|
||||
// Setup
|
||||
void InitWildBattle(Player* player, ObjectGuid wildCreatureGUID);
|
||||
void InitPvPBattle(Player* player1, Player* player2);
|
||||
void InitNPCBattle(Player* player, Creature* trainer, std::vector<NPCTeamPetInfo> const& npcTeam);
|
||||
// State machine
|
||||
void Start();
|
||||
void Update(uint32 diff);
|
||||
void SubmitInput(uint8 teamIdx, PetBattleMoveType moveType, uint32 abilityID, int8 newFrontPet);
|
||||
void ProcessRound();
|
||||
void FinishBattle(PetBattleResult result);
|
||||
void Forfeit(uint8 teamIdx);
|
||||
|
||||
// State machine
|
||||
void Start();
|
||||
void Update(uint32 diff);
|
||||
void SubmitInput(uint8 teamIdx, PetBattleMoveType moveType, uint32 abilityID, int8 newFrontPet);
|
||||
void ProcessRound();
|
||||
void FinishBattle(PetBattleResult result);
|
||||
void Forfeit(uint8 teamIdx);
|
||||
// Queries
|
||||
bool IsFinished() const { return _state == PET_BATTLE_STATE_FINISHED; }
|
||||
bool IsFinalRound() const { return _state == PET_BATTLE_STATE_FINAL_ROUND; }
|
||||
bool BothTeamsReady() const;
|
||||
ObjectGuid GetNpcTrainerGUID() const { return _npcTrainerGUID; }
|
||||
void CompleteBattle();
|
||||
uint8 GetWinnerTeam() const { return _winnerTeam; }
|
||||
Player* GetPlayerForTeam(uint8 teamIdx) const;
|
||||
|
||||
// Queries
|
||||
bool IsFinished() const { return _state == PET_BATTLE_STATE_FINISHED; }
|
||||
bool IsFinalRound() const { return _state == PET_BATTLE_STATE_FINAL_ROUND; }
|
||||
bool BothTeamsReady() const;
|
||||
ObjectGuid GetNpcTrainerGUID() const { return _npcTrainerGUID; }
|
||||
void CompleteBattle();
|
||||
uint8 GetWinnerTeam() const { return _winnerTeam; }
|
||||
Player* GetPlayerForTeam(uint8 teamIdx) const;
|
||||
// Trap validation
|
||||
uint8 GetTrapStatus(uint8 playerTeam) const;
|
||||
|
||||
ObjectGuid GetSceneObjectGUID() const { return _sceneObjectGUID; }
|
||||
void SetSceneObjectGUID(ObjectGuid guid) { _sceneObjectGUID = guid; }
|
||||
void DespawnSceneObject();
|
||||
// Final round delay ? true if FinalRound packet is pending (death animation playing)
|
||||
bool HasPendingFinishDelay() const { return _finishDelayMs > 0; }
|
||||
void SendFinalRoundPacket(bool abandoned);
|
||||
|
||||
// Trap validation
|
||||
uint8 GetTrapStatus(uint8 playerTeam) const;
|
||||
// AI for wild/NPC teams
|
||||
void GenerateWildTeamInput();
|
||||
void GenerateNPCTeamInput();
|
||||
|
||||
// Final round delay ? true if FinalRound packet is pending (death animation playing)
|
||||
bool HasPendingFinishDelay() const { return _finishDelayMs > 0; }
|
||||
void SendFinalRoundPacket(bool abandoned);
|
||||
// Round resolution results
|
||||
std::vector<PetBattleRoundEffect> const& GetRoundEffects() const { return _roundEffects; }
|
||||
bool WasPetKilledThisRound(uint8 teamIdx, uint8 petIdx) const;
|
||||
bool NeedsFrontPetSwap(uint8 teamIdx) const;
|
||||
void ClearNeedsFrontPetSwap(uint8 teamIdx) { if (teamIdx < MAX_PET_BATTLE_PLAYERS) _needsFrontPetSwap[teamIdx] = false; }
|
||||
|
||||
// AI for wild/NPC teams
|
||||
void GenerateWildTeamInput();
|
||||
void GenerateNPCTeamInput();
|
||||
// Criteria helpers ? last ability used per team (for ModifierTreeType::PetBattleLastAbility/Type)
|
||||
uint32 GetLastAbilityID(uint8 teamIdx) const { return teamIdx < MAX_PET_BATTLE_PLAYERS ? _lastAbilityID[teamIdx] : 0; }
|
||||
// Opponent creature ID for criteria evaluation
|
||||
uint32 GetOpponentCreatureID(uint8 teamIdx) const;
|
||||
|
||||
// Round resolution results
|
||||
std::vector<PetBattleRoundEffect> const& GetRoundEffects() const { return _roundEffects; }
|
||||
bool WasPetKilledThisRound(uint8 teamIdx, uint8 petIdx) const;
|
||||
bool NeedsFrontPetSwap(uint8 teamIdx) const;
|
||||
void ClearNeedsFrontPetSwap(uint8 teamIdx) { if (teamIdx < MAX_PET_BATTLE_PLAYERS) _needsFrontPetSwap[teamIdx] = false; }
|
||||
private:
|
||||
// DB2-driven ability effect chain
|
||||
void ApplyAbilityEffects(uint8 attackerTeam, uint8 attackerPet, uint32 abilityID);
|
||||
void ProcessEffect(BattlePetAbilityEffectEntry const* effect, uint8 attackerTeam, uint8 attackerPet,
|
||||
uint8 defenderTeam, uint8 defenderPet, uint32 abilityID);
|
||||
|
||||
// Criteria helpers ? last ability used per team (for ModifierTreeType::PetBattleLastAbility/Type)
|
||||
uint32 GetLastAbilityID(uint8 teamIdx) const { return teamIdx < MAX_PET_BATTLE_PLAYERS ? _lastAbilityID[teamIdx] : 0; }
|
||||
// Opponent creature ID for criteria evaluation
|
||||
uint32 GetOpponentCreatureID(uint8 teamIdx) const;
|
||||
// Damage/healing with passive family abilities applied
|
||||
DamageResult CalculateAbilityDamage(int32 abilityPower, int32 attackerPower, PetBattlePetType abilityType,
|
||||
PetBattlePetData const& attacker, PetBattlePetData& defender);
|
||||
int32 CalculateAbilityHealing(int32 healPower, int32 attackerPower, PetBattlePetData const& healer);
|
||||
|
||||
private:
|
||||
// DB2-driven ability effect chain
|
||||
void ApplyAbilityEffects(uint8 attackerTeam, uint8 attackerPet, uint32 abilityID);
|
||||
void ProcessEffect(BattlePetAbilityEffectEntry const* effect, uint8 attackerTeam, uint8 attackerPet,
|
||||
uint8 defenderTeam, uint8 defenderPet, uint32 abilityID);
|
||||
// Aura system
|
||||
void AddAura(uint8 targetTeam, uint8 targetPet, uint32 abilityID, uint32 effectID,
|
||||
PetBattleAuraType auraType, int32 duration, int32 damagePerTick, int8 petType,
|
||||
uint8 casterTeam, uint8 casterPet);
|
||||
void RemoveAura(uint8 targetTeam, uint8 targetPet, uint32 abilityID);
|
||||
void TickAuras();
|
||||
|
||||
// Damage/healing with passive family abilities applied
|
||||
DamageResult CalculateAbilityDamage(int32 abilityPower, int32 attackerPower, PetBattlePetType abilityType,
|
||||
PetBattlePetData const& attacker, PetBattlePetData& defender);
|
||||
int32 CalculateAbilityHealing(int32 healPower, int32 attackerPower, PetBattlePetData const& healer);
|
||||
// Environment/weather (state-driven from BattlePetAbilityState DB2)
|
||||
void ApplyWeatherStates(uint32 abilityID);
|
||||
void ClearWeatherStates();
|
||||
void TickWeather();
|
||||
|
||||
// Aura system
|
||||
void AddAura(uint8 targetTeam, uint8 targetPet, uint32 abilityID, uint32 effectID,
|
||||
PetBattleAuraType auraType, int32 duration, int32 damagePerTick, int8 petType,
|
||||
uint8 casterTeam, uint8 casterPet);
|
||||
void RemoveAura(uint8 targetTeam, uint8 targetPet, uint32 abilityID);
|
||||
void TickAuras();
|
||||
// Passive family abilities
|
||||
void ApplyPassiveRoundStart();
|
||||
void ApplyPassiveOnDamageDealt(uint8 attackerTeam, uint8 attackerPet, uint8 defenderTeam, uint8 defenderPet, int32 damage);
|
||||
bool ApplyPassiveOnDeath(uint8 teamIdx, uint8 petIdx);
|
||||
|
||||
// Environment/weather (state-driven from BattlePetAbilityState DB2)
|
||||
void ApplyWeatherStates(uint32 abilityID);
|
||||
void ClearWeatherStates();
|
||||
void TickWeather();
|
||||
void ProcessTurnForTeam(uint8 teamIdx);
|
||||
void CheckDeaths();
|
||||
void AwardExperience();
|
||||
|
||||
// Passive family abilities
|
||||
void ApplyPassiveRoundStart();
|
||||
void ApplyPassiveOnDamageDealt(uint8 attackerTeam, uint8 attackerPet, uint8 defenderTeam, uint8 defenderPet, int32 damage);
|
||||
bool ApplyPassiveOnDeath(uint8 teamIdx, uint8 petIdx);
|
||||
// Helper to load player's battle pet team
|
||||
void LoadPlayerTeam(Player* player, PetBattleTeamData& team);
|
||||
void LoadWildPetAbilities(PetBattlePetData& pet);
|
||||
void GenerateWildTeam(Player* player, ObjectGuid wildCreatureGUID);
|
||||
|
||||
void ProcessTurnForTeam(uint8 teamIdx);
|
||||
void CheckDeaths();
|
||||
void AwardExperience();
|
||||
uint32 _battleID = 0;
|
||||
PetBattleType _battleType = PET_BATTLE_TYPE_PVE;
|
||||
PetBattleState _state = PET_BATTLE_STATE_CREATED;
|
||||
uint32 _currentRound = 0;
|
||||
uint32 _elapsedSecs = 0;
|
||||
uint32 _updateTimer = 0;
|
||||
uint8 _winnerTeam = 0;
|
||||
bool _canAwardXP = true;
|
||||
bool _maxLengthWarningSent = false;
|
||||
uint32 _roundTimerSecs = 0; // seconds since last input was accepted
|
||||
uint32 _nextAuraInstanceID = 1;
|
||||
|
||||
// Helper to load player's battle pet team
|
||||
void LoadPlayerTeam(Player* player, PetBattleTeamData& team);
|
||||
void LoadWildPetAbilities(PetBattlePetData& pet);
|
||||
void GenerateWildTeam(Player* player, ObjectGuid wildCreatureGUID);
|
||||
std::array<PetBattleTeamData, MAX_PET_BATTLE_PLAYERS> _teams;
|
||||
std::array<PetBattleEnvironment, MAX_PET_BATTLE_ENVIRONMENTS> _environments;
|
||||
|
||||
uint32 _battleID = 0;
|
||||
PetBattleType _battleType = PET_BATTLE_TYPE_PVE;
|
||||
PetBattleState _state = PET_BATTLE_STATE_CREATED;
|
||||
uint32 _currentRound = 0;
|
||||
uint32 _elapsedSecs = 0;
|
||||
uint32 _updateTimer = 0;
|
||||
uint8 _winnerTeam = 0;
|
||||
bool _canAwardXP = true;
|
||||
bool _maxLengthWarningSent = false;
|
||||
uint32 _roundTimerSecs = 0; // seconds since last input was accepted
|
||||
uint32 _nextAuraInstanceID = 1;
|
||||
// Round resolution data
|
||||
std::vector<PetBattleRoundEffect> _roundEffects;
|
||||
std::array<bool, MAX_PET_BATTLE_TEAM_SIZE* MAX_PET_BATTLE_PLAYERS> _petKilledThisRound = {};
|
||||
std::array<bool, MAX_PET_BATTLE_PLAYERS> _needsFrontPetSwap = {};
|
||||
|
||||
std::array<PetBattleTeamData, MAX_PET_BATTLE_PLAYERS> _teams;
|
||||
std::array<PetBattleEnvironment, MAX_PET_BATTLE_ENVIRONMENTS> _environments;
|
||||
float _trapFailBonus = 0.0f; // +20% per failed trap attempt
|
||||
uint32 _finishDelayMs = 0; // delay before sending FinalRound packet (ms)
|
||||
std::array<uint32, MAX_PET_BATTLE_PLAYERS> _lastAbilityID = {}; // last ability used per team
|
||||
|
||||
// Round resolution data
|
||||
std::vector<PetBattleRoundEffect> _roundEffects;
|
||||
std::array<bool, MAX_PET_BATTLE_TEAM_SIZE * MAX_PET_BATTLE_PLAYERS> _petKilledThisRound = {};
|
||||
std::array<bool, MAX_PET_BATTLE_PLAYERS> _needsFrontPetSwap = {};
|
||||
|
||||
float _trapFailBonus = 0.0f; // +20% per failed trap attempt
|
||||
uint32 _finishDelayMs = 0; // delay before sending FinalRound packet (ms)
|
||||
std::array<uint32, MAX_PET_BATTLE_PLAYERS> _lastAbilityID = {}; // last ability used per team
|
||||
|
||||
ObjectGuid _wildCreatureGUID;
|
||||
ObjectGuid _npcTrainerGUID;
|
||||
ObjectGuid _sceneObjectGUID;
|
||||
};
|
||||
ObjectGuid _wildCreatureGUID;
|
||||
ObjectGuid _npcTrainerGUID;
|
||||
};
|
||||
|
||||
} // namespace PetBattles
|
||||
|
||||
|
||||
@@ -21,13 +21,18 @@
|
||||
#include "Creature.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "GameTime.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "SceneObject.h"
|
||||
#include "WorldSession.h"
|
||||
#include <algorithm>
|
||||
|
||||
// Observed on retail 12.1.0.69587: QUEUED resent 7.3 s after the join; AvgWaitTime 60 s in both frames.
|
||||
static constexpr uint32 PET_BATTLE_QUEUE_STATUS_UPDATE_INTERVAL = 7000;
|
||||
static constexpr uint64 PET_BATTLE_QUEUE_AVG_WAIT_SECS = 60;
|
||||
|
||||
|
||||
namespace PetBattles
|
||||
{
|
||||
|
||||
@@ -568,16 +573,26 @@ namespace PetBattles
|
||||
{
|
||||
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus timeoutStatus;
|
||||
timeoutStatus.Status = PET_BATTLE_QUEUE_STATUS_PROPOSAL_TIMED_OUT;
|
||||
player->SendDirectMessage(timeoutStatus.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_PROPOSAL_TIMED_OUT);
|
||||
}
|
||||
_pvpQueue.push_back({ guid, 0 });
|
||||
Enqueue(guid);
|
||||
}
|
||||
_pendingProposal.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Retail (12.1.0.69587 capture): a queued client receives SMSG_PET_BATTLE_QUEUE_STATUS again
|
||||
// while it waits -- QUEUED with the elapsed ClientWaitTime (observed once, 7.3 s after the join).
|
||||
for (PvPQueueEntry& entry : _pvpQueue)
|
||||
{
|
||||
entry.UpdateTimer += diff;
|
||||
if (entry.UpdateTimer < PET_BATTLE_QUEUE_STATUS_UPDATE_INTERVAL)
|
||||
continue;
|
||||
entry.UpdateTimer = 0;
|
||||
if (Player* player = ObjectAccessor::FindPlayer(entry.PlayerGUID))
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_QUEUED, &entry);
|
||||
}
|
||||
|
||||
// Try to match PvP queue players periodically (every 5 seconds)
|
||||
_queueMatchTimer += diff;
|
||||
if (_queueMatchTimer >= 5000)
|
||||
@@ -601,9 +616,6 @@ namespace PetBattles
|
||||
battle->SetBattleID(battleID);
|
||||
battle->InitWildBattle(player, wildCreatureGUID);
|
||||
|
||||
if (SceneObject* sceneObject = SceneObject::CreatePetBattleSceneObject(player, player->GetPosition(), player->GetGUID()))
|
||||
battle->SetSceneObjectGUID(sceneObject->GetGUID());
|
||||
|
||||
PetBattle* ptr = battle.get();
|
||||
_activeBattles[battleID] = std::move(battle);
|
||||
_playerToBattle[player->GetGUID()] = battleID;
|
||||
@@ -627,9 +639,6 @@ namespace PetBattles
|
||||
if (battle->IsFinished())
|
||||
return nullptr; // Validation failed in InitPvPBattle
|
||||
|
||||
if (SceneObject* sceneObject = SceneObject::CreatePetBattleSceneObject(player1, player1->GetPosition(), ObjectGuid::Empty))
|
||||
battle->SetSceneObjectGUID(sceneObject->GetGUID());
|
||||
|
||||
PetBattle* ptr = battle.get();
|
||||
_activeBattles[battleID] = std::move(battle);
|
||||
_playerToBattle[player1->GetGUID()] = battleID;
|
||||
@@ -655,9 +664,6 @@ namespace PetBattles
|
||||
battle->SetBattleID(battleID);
|
||||
battle->InitNPCBattle(player, trainer, *npcTeam);
|
||||
|
||||
if (SceneObject* sceneObject = SceneObject::CreatePetBattleSceneObject(player, player->GetPosition(), player->GetGUID()))
|
||||
battle->SetSceneObjectGUID(sceneObject->GetGUID());
|
||||
|
||||
PetBattle* ptr = battle.get();
|
||||
_activeBattles[battleID] = std::move(battle);
|
||||
_playerToBattle[player->GetGUID()] = battleID;
|
||||
@@ -676,8 +682,6 @@ namespace PetBattles
|
||||
|
||||
PetBattle* battle = it->second.get();
|
||||
|
||||
battle->DespawnSceneObject();
|
||||
|
||||
// Remove player mappings
|
||||
for (uint8 i = 0; i < MAX_PET_BATTLE_PLAYERS; ++i)
|
||||
{
|
||||
@@ -822,6 +826,36 @@ namespace PetBattles
|
||||
// PvP Queue
|
||||
// ============================================================================
|
||||
|
||||
void PetBattleMgr::Enqueue(ObjectGuid playerGUID)
|
||||
{
|
||||
PvPQueueEntry entry;
|
||||
entry.PlayerGUID = playerGUID;
|
||||
entry.TicketId = _nextQueueTicketId++;
|
||||
entry.EnqueueTime = GameTime::GetGameTime();
|
||||
_pvpQueue.push_back(entry);
|
||||
}
|
||||
|
||||
// One packet shape for every queue outcome: the ticket names the requester (and the queue entry
|
||||
// when there is one); the wait times ride along only while the player is actually queued.
|
||||
void PetBattleMgr::SendQueueStatus(Player* player, uint32 status, PvPQueueEntry const* entry) const
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus packet;
|
||||
packet.Status = status;
|
||||
packet.Ticket.RequesterGuid = player->GetGUID();
|
||||
packet.Ticket.Type = WorldPackets::LFG::RideType::PetBattle;
|
||||
if (entry)
|
||||
{
|
||||
packet.Ticket.Id = entry->TicketId;
|
||||
packet.Ticket.Time = entry->EnqueueTime;
|
||||
packet.ClientWaitTime = uint64(std::max<time_t>(0, GameTime::GetGameTime() - entry->EnqueueTime));
|
||||
packet.AvgWaitTime = PET_BATTLE_QUEUE_AVG_WAIT_SECS;
|
||||
}
|
||||
else
|
||||
packet.Ticket.Time = GameTime::GetGameTime();
|
||||
|
||||
player->SendDirectMessage(packet.Write());
|
||||
}
|
||||
|
||||
void PetBattleMgr::JoinQueue(ObjectGuid playerGUID)
|
||||
{
|
||||
// Don't add if already in queue
|
||||
@@ -831,9 +865,7 @@ namespace PetBattles
|
||||
{
|
||||
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_ALREADY_QUEUED;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_ALREADY_QUEUED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -844,9 +876,7 @@ namespace PetBattles
|
||||
{
|
||||
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_JOIN_FAILED;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_JOIN_FAILED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -857,9 +887,7 @@ namespace PetBattles
|
||||
BattlePets::BattlePetMgr* petMgr = player->GetSession()->GetBattlePetMgr();
|
||||
if (!petMgr->HasJournalLock())
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_JOURNAL_LOCK;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_JOURNAL_LOCK);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -880,24 +908,15 @@ namespace PetBattles
|
||||
|
||||
if (!hasPet)
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_SLOTS;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_JOIN_FAILED_SLOTS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PvPQueueEntry entry;
|
||||
entry.PlayerGUID = playerGUID;
|
||||
entry.EnqueueTime = 0;
|
||||
_pvpQueue.push_back(entry);
|
||||
Enqueue(playerGUID);
|
||||
|
||||
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_QUEUED;
|
||||
player->SendDirectMessage(status.Write());
|
||||
}
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_QUEUED, &_pvpQueue.back());
|
||||
|
||||
TC_LOG_DEBUG("server.loading", "PetBattleMgr: Player {} joined PvP pet battle queue (queue size: {})",
|
||||
playerGUID.ToString(), _pvpQueue.size());
|
||||
@@ -921,15 +940,11 @@ namespace PetBattles
|
||||
|
||||
if (Player* other = ObjectAccessor::FindPlayer(otherGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED;
|
||||
other->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(other, PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED);
|
||||
}
|
||||
|
||||
// Re-add the other player to the queue
|
||||
PvPQueueEntry requeue;
|
||||
requeue.PlayerGUID = otherGUID;
|
||||
_pvpQueue.push_back(requeue);
|
||||
Enqueue(otherGUID);
|
||||
|
||||
_pendingProposal.reset();
|
||||
}
|
||||
@@ -937,9 +952,7 @@ namespace PetBattles
|
||||
|
||||
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_REMOVED;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_REMOVED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,24 +973,18 @@ namespace PetBattles
|
||||
// Send declined status to the declining player
|
||||
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_DECLINED;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_MATCH_DECLINED);
|
||||
}
|
||||
|
||||
// Notify the other player and re-queue them
|
||||
ObjectGuid otherGUID = (_pendingProposal->Player1 == playerGUID) ?
|
||||
_pendingProposal->Player2 : _pendingProposal->Player1;
|
||||
|
||||
PvPQueueEntry requeue;
|
||||
requeue.PlayerGUID = otherGUID;
|
||||
_pvpQueue.push_back(requeue);
|
||||
Enqueue(otherGUID);
|
||||
|
||||
if (Player* other = ObjectAccessor::FindPlayer(otherGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED;
|
||||
other->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(other, PET_BATTLE_QUEUE_STATUS_MATCH_OPPONENT_DECLINED);
|
||||
}
|
||||
|
||||
_pendingProposal.reset();
|
||||
@@ -987,9 +994,7 @@ namespace PetBattles
|
||||
// Send accepted status to this player
|
||||
if (Player* player = ObjectAccessor::FindPlayer(playerGUID))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = PET_BATTLE_QUEUE_STATUS_MATCH_ACCEPTED;
|
||||
player->SendDirectMessage(status.Write());
|
||||
SendQueueStatus(player, PET_BATTLE_QUEUE_STATUS_MATCH_ACCEPTED);
|
||||
}
|
||||
|
||||
// Check if both accepted
|
||||
@@ -1074,9 +1079,7 @@ namespace PetBattles
|
||||
// Send matchmaking then proposal status to both players
|
||||
for (Player* matchPlayer : { player1, player2 })
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus matchStatus;
|
||||
matchStatus.Status = PET_BATTLE_QUEUE_STATUS_MATCHMAKING;
|
||||
matchPlayer->SendDirectMessage(matchStatus.Write());
|
||||
SendQueueStatus(matchPlayer, PET_BATTLE_QUEUE_STATUS_MATCHMAKING);
|
||||
}
|
||||
|
||||
WorldPackets::BattlePet::PetBattleQueueProposeMatch proposeMatch;
|
||||
|
||||
@@ -47,7 +47,9 @@ namespace PetBattles
|
||||
struct PvPQueueEntry
|
||||
{
|
||||
ObjectGuid PlayerGUID;
|
||||
uint32 EnqueueTime = 0;
|
||||
uint32 TicketId = 0; // RideTicket.Id sent in every SMSG_PET_BATTLE_QUEUE_STATUS
|
||||
time_t EnqueueTime = 0; // RideTicket.Time; ClientWaitTime = now - EnqueueTime
|
||||
uint32 UpdateTimer = 0; // ms since the last QUEUED status resend
|
||||
};
|
||||
|
||||
// Inferred targeting for ability auras based on the BattlePetAbilityState
|
||||
@@ -167,6 +169,10 @@ namespace PetBattles
|
||||
std::vector<PvPQueueEntry> _pvpQueue;
|
||||
std::unique_ptr<PvPMatchProposal> _pendingProposal;
|
||||
uint32 _queueMatchTimer = 0;
|
||||
uint32 _nextQueueTicketId = 1;
|
||||
|
||||
void Enqueue(ObjectGuid playerGUID);
|
||||
void SendQueueStatus(Player* player, uint32 status, PvPQueueEntry const* entry = nullptr) const;
|
||||
};
|
||||
|
||||
} // namespace PetBattles
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "WorldSession.h"
|
||||
#include "BattlePetMgr.h"
|
||||
#include "BattlePetPackets.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "Creature.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "Log.h"
|
||||
@@ -35,6 +34,12 @@ void WorldSession::HandleBattlePetRequestJournal(WorldPackets::BattlePet::Battle
|
||||
|
||||
void WorldSession::HandleBattlePetRequestJournalLock(WorldPackets::BattlePet::BattlePetRequestJournalLock& /*battlePetRequestJournalLock*/)
|
||||
{
|
||||
// Retail 12.1.0.69587 (three captures): the client sends this whenever the journal UI opens;
|
||||
// a session that already holds the lock (SMSG_BATTLE_PET_JOURNAL_LOCK_ACQUIRED was pushed at
|
||||
// login) gets NO reply -- no lock packet and no 10 KB journal resend.
|
||||
if (GetBattlePetMgr()->HasJournalLock())
|
||||
return;
|
||||
|
||||
GetBattlePetMgr()->SendJournalLockStatus();
|
||||
|
||||
if (GetBattlePetMgr()->HasJournalLock())
|
||||
@@ -129,17 +134,6 @@ void WorldSession::HandleBattlePetDeletePet(WorldPackets::BattlePet::BattlePetDe
|
||||
GetBattlePetMgr()->RemovePet(battlePetDeletePet.PetGuid);
|
||||
}
|
||||
|
||||
void WorldSession::HandleBattlePetDeletePetCheat(WorldPackets::BattlePet::BattlePetDeletePetCheat& battlePetDeletePetCheat)
|
||||
{
|
||||
// Developer/GM cheat variant of CMSG_BATTLE_PET_DELETE_PET - the client only emits it in developer mode.
|
||||
// Gate it on account security so a normal player cannot reach the cheat path; the effect is identical to
|
||||
// the non-cheat handler (remove the pet from the journal).
|
||||
if (AccountMgr::IsPlayerAccount(GetSecurity()))
|
||||
return;
|
||||
|
||||
GetBattlePetMgr()->RemovePet(battlePetDeletePetCheat.PetGuid);
|
||||
}
|
||||
|
||||
void WorldSession::HandleBattlePetSetFlags(WorldPackets::BattlePet::BattlePetSetFlags& battlePetSetFlags)
|
||||
{
|
||||
if (!GetBattlePetMgr()->HasJournalLock())
|
||||
@@ -193,7 +187,7 @@ static WorldPackets::BattlePet::PetBattlePetUpdateInfo BuildPetUpdateInfo(
|
||||
PetBattles::PetBattlePetData const& petData, uint8 teamIdx, uint8 petIdx);
|
||||
|
||||
static void BuildPetBattlePlayerUpdate(WorldPackets::BattlePet::PetBattlePlayerUpdateInfo& update,
|
||||
PetBattles::PetBattleTeamData const& team, PetBattles::PetBattle const* battle, uint8 teamIdx)
|
||||
PetBattles::PetBattleTeamData const& team, bool isWildTeam, PetBattles::PetBattle const* battle, uint8 teamIdx)
|
||||
{
|
||||
update.CharacterGUID = team.PlayerGUID;
|
||||
update.TrapAbilityID = team.TrapAbilityID;
|
||||
@@ -205,6 +199,8 @@ static void BuildPetBattlePlayerUpdate(WorldPackets::BattlePet::PetBattlePlayerU
|
||||
for (uint8 i = 0; i < team.PetCount; ++i)
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattlePetUpdateInfo petInfo = BuildPetUpdateInfo(team.Pets[i], teamIdx, i);
|
||||
if (isWildTeam)
|
||||
petInfo.DisplayID = 0;
|
||||
update.Pets.push_back(std::move(petInfo));
|
||||
}
|
||||
}
|
||||
@@ -361,12 +357,28 @@ static WorldPackets::BattlePet::PetBattlePetUpdateInfo BuildPetUpdateInfo(
|
||||
petInfo.Auras.push_back(auraInfo);
|
||||
}
|
||||
|
||||
petInfo.States.push_back({ BattlePets::STATE_STAT_POWER, petData.BasePower });
|
||||
petInfo.States.push_back({ BattlePets::STATE_STAT_POWER, petData.Power }); // wire: final power, not the raw base
|
||||
petInfo.States.push_back({ BattlePets::STATE_STAT_STAMINA, petData.BaseStamina });
|
||||
petInfo.States.push_back({ BattlePets::STATE_STAT_SPEED, petData.BaseSpeed });
|
||||
petInfo.States.push_back({ 40, 5 }); // CritChance = 5%
|
||||
if (petData.PetType >= 0 && petData.PetType < PetBattles::PET_TYPE_COUNT)
|
||||
petInfo.States.push_back({ uint32(44 + petData.PetType), 1 }); // Family passive flag
|
||||
{
|
||||
// BattlePetState passive ids (Passive_Critter 42 .. Passive_Aquatic 51) are not in PetType order
|
||||
static constexpr uint32 passiveStateByPetType[PetBattles::PET_TYPE_COUNT] =
|
||||
{
|
||||
44, // Humanoid
|
||||
46, // Dragonkin
|
||||
45, // Flying
|
||||
50, // Undead
|
||||
42, // Critter
|
||||
49, // Magic
|
||||
47, // Elemental
|
||||
43, // Beast
|
||||
51, // Aquatic
|
||||
48 // Mechanical
|
||||
};
|
||||
petInfo.States.push_back({ passiveStateByPetType[petData.PetType], 1 });
|
||||
}
|
||||
|
||||
return petInfo;
|
||||
}
|
||||
@@ -408,12 +420,11 @@ static void BuildRoundEffects(std::vector<WorldPackets::BattlePet::PetBattleEffe
|
||||
case PetBattles::PET_BATTLE_EFFECT_AURA_CANCEL:
|
||||
case PetBattles::PET_BATTLE_EFFECT_AURA_CHANGE:
|
||||
{
|
||||
// Sniff-verified retail wire order is [AbilityID, InstanceID, RoundsRemaining, CurrentRound]
|
||||
// (we historically stored Param1=InstanceID, Param2=AbilityID ? swap on the wire so
|
||||
// existing call sites keep their semantic naming)
|
||||
// 12.1.0.69587 wire order (three captures, WPP ReadPetBattleEffectTarget agrees):
|
||||
// [AuraInstanceID, AuraAbilityID, RoundsRemaining, CurrentRound]
|
||||
target.Type = 1; // Aura: 4 params
|
||||
target.Params.push_back(roundEffect.Param2); // AuraAbilityID
|
||||
target.Params.push_back(roundEffect.Param1); // AuraInstanceID
|
||||
target.Params.push_back(roundEffect.Param2); // AuraAbilityID
|
||||
target.Params.push_back(roundEffect.Param3); // RoundsRemaining
|
||||
target.Params.push_back(roundEffect.Param4); // CurrentRound
|
||||
break;
|
||||
@@ -468,11 +479,6 @@ static void BuildRoundEffects(std::vector<WorldPackets::BattlePet::PetBattleEffe
|
||||
}
|
||||
|
||||
void WorldSession::HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattleRequestWild& petBattleRequestWild)
|
||||
{
|
||||
StartWildPetBattle(petBattleRequestWild.TargetGUID);
|
||||
}
|
||||
|
||||
void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
|
||||
{
|
||||
Player* player = GetPlayer();
|
||||
if (!player)
|
||||
@@ -529,7 +535,7 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
|
||||
}
|
||||
|
||||
// Validate target creature
|
||||
Creature* creature = ObjectAccessor::GetCreature(*player, targetGUID);
|
||||
Creature* creature = ObjectAccessor::GetCreature(*player, petBattleRequestWild.TargetGUID);
|
||||
if (!creature || !creature->IsWildBattlePet())
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleRequestFailed failed;
|
||||
@@ -559,7 +565,7 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
|
||||
}
|
||||
|
||||
// Create the battle
|
||||
PetBattles::PetBattle* battle = sPetBattleMgr->CreateWildBattle(player, targetGUID);
|
||||
PetBattles::PetBattle* battle = sPetBattleMgr->CreateWildBattle(player, petBattleRequestWild.TargetGUID);
|
||||
if (!battle)
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleRequestFailed failed;
|
||||
@@ -604,17 +610,16 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
|
||||
|
||||
// Build and send initial update
|
||||
WorldPackets::BattlePet::PetBattleInitialUpdate initialUpdate;
|
||||
initialUpdate.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), battle, PetBattles::PET_BATTLE_TEAM_1);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), battle, PetBattles::PET_BATTLE_TEAM_2);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), false, battle, PetBattles::PET_BATTLE_TEAM_1);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), true, battle, PetBattles::PET_BATTLE_TEAM_2);
|
||||
BuildPetBattleEnviros(initialUpdate.Enviros, battle);
|
||||
|
||||
initialUpdate.CurRound = battle->GetCurrentRound();
|
||||
initialUpdate.CurPetBattleState = static_cast<int8>(battle->GetBattleState());
|
||||
initialUpdate.NpcCreatureID = 0; // Wild battles have no NPC trainer; creature identified by InitialWildPetGUID
|
||||
initialUpdate.NpcDisplayID = 0;
|
||||
initialUpdate.InitialWildPetGUID = targetGUID;
|
||||
initialUpdate.InitialWildPetGUID = petBattleRequestWild.TargetGUID;
|
||||
initialUpdate.IsPVP = false;
|
||||
initialUpdate.CanAwardXP = true;
|
||||
initialUpdate.WaitingForFrontPetsMaxSecs = 30;
|
||||
@@ -657,7 +662,6 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
|
||||
// we don't have intro animations. This packet tells the client abilities are available.
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleFirstRound firstRound;
|
||||
firstRound.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
firstRound.CurRound = 0;
|
||||
firstRound.NextPetBattleState = static_cast<int8>(PetBattles::PET_BATTLE_STATE_ROUND_IN_PROGRESS);
|
||||
|
||||
@@ -678,7 +682,7 @@ void WorldSession::StartWildPetBattle(ObjectGuid targetGUID)
|
||||
// its input when the player submits theirs (in HandlePetBattleInput).
|
||||
|
||||
TC_LOG_DEBUG("server.loading", "PetBattleHandler: Player {} started wild battle against {}",
|
||||
player->GetGUID().ToString(), targetGUID.ToString());
|
||||
player->GetGUID().ToString(), petBattleRequestWild.TargetGUID.ToString());
|
||||
}
|
||||
|
||||
void WorldSession::StartNPCPetBattle(Creature* trainer)
|
||||
@@ -783,10 +787,9 @@ void WorldSession::StartNPCPetBattle(Creature* trainer)
|
||||
|
||||
// Build and send initial update
|
||||
WorldPackets::BattlePet::PetBattleInitialUpdate initialUpdate;
|
||||
initialUpdate.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), battle, PetBattles::PET_BATTLE_TEAM_1);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), battle, PetBattles::PET_BATTLE_TEAM_2);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), false, battle, PetBattles::PET_BATTLE_TEAM_1);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), false, battle, PetBattles::PET_BATTLE_TEAM_2);
|
||||
BuildPetBattleEnviros(initialUpdate.Enviros, battle);
|
||||
|
||||
initialUpdate.CurRound = battle->GetCurrentRound();
|
||||
@@ -807,7 +810,6 @@ void WorldSession::StartNPCPetBattle(Creature* trainer)
|
||||
// Send first round to unlock abilities
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleFirstRound firstRound;
|
||||
firstRound.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
firstRound.CurRound = 0;
|
||||
firstRound.NextPetBattleState = static_cast<int8>(PetBattles::PET_BATTLE_STATE_ROUND_IN_PROGRESS);
|
||||
|
||||
@@ -875,7 +877,6 @@ void WorldSession::HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput&
|
||||
// after a delay by PetBattle::Update() when the battle is ending.
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleRoundResult roundResult;
|
||||
roundResult.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
roundResult.CurRound = battle->GetCurrentRound();
|
||||
roundResult.NextPetBattleState = static_cast<int8>(battle->GetBattleState());
|
||||
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
|
||||
@@ -966,7 +967,6 @@ void WorldSession::HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBa
|
||||
|
||||
// Send REPLACEMENTS_MADE to acknowledge the swap
|
||||
WorldPackets::BattlePet::PetBattleReplacementsMade replacements;
|
||||
replacements.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
replacements.CurRound = battle->GetCurrentRound();
|
||||
replacements.NextPetBattleState = static_cast<int8>(PetBattles::PET_BATTLE_STATE_ROUND_IN_PROGRESS);
|
||||
for (uint8 i = 0; i < PetBattles::MAX_PET_BATTLE_PLAYERS; ++i)
|
||||
@@ -1144,45 +1144,10 @@ void WorldSession::HandleJoinPetBattleQueue(WorldPackets::BattlePet::JoinPetBatt
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
if (sPetBattleMgr->IsPlayerInBattle(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = 0;
|
||||
SendPacket(status.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that player has at least one alive pet in battle slots
|
||||
{
|
||||
BattlePets::BattlePetMgr* petMgr = GetBattlePetMgr();
|
||||
bool hasPet = false;
|
||||
for (uint8 i = 0; i < uint8(BattlePets::BattlePetSlot::Count); ++i)
|
||||
{
|
||||
WorldPackets::BattlePet::BattlePetSlot* slot = petMgr->GetSlot(BattlePets::BattlePetSlot(i));
|
||||
if (slot && !slot->Locked && !slot->Pet.Guid.IsEmpty())
|
||||
{
|
||||
BattlePets::BattlePet* pet = petMgr->GetPet(slot->Pet.Guid);
|
||||
if (pet && pet->PacketInfo.Health > 0)
|
||||
{
|
||||
hasPet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasPet)
|
||||
{
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = 0;
|
||||
SendPacket(status.Write());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// PetBattleMgr::JoinQueue validates (in battle, journal lock, alive slotted pets) and answers
|
||||
// with exactly one SMSG_PET_BATTLE_QUEUE_STATUS; retail sends a single QUEUED at join. The old
|
||||
// handler sent a second copy (and status 0 on failure), which is not what the client expects.
|
||||
sPetBattleMgr->JoinQueue(player->GetGUID());
|
||||
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = 1; // Queued
|
||||
SendPacket(status.Write());
|
||||
}
|
||||
|
||||
void WorldSession::HandleLeavePetBattleQueue(WorldPackets::BattlePet::LeavePetBattleQueue& /*leavePetBattleQueue*/)
|
||||
@@ -1191,11 +1156,7 @@ void WorldSession::HandleLeavePetBattleQueue(WorldPackets::BattlePet::LeavePetBa
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
sPetBattleMgr->LeaveQueue(player->GetGUID());
|
||||
|
||||
WorldPackets::BattlePet::PetBattleQueueStatus status;
|
||||
status.Status = 0; // Not in queue
|
||||
SendPacket(status.Write());
|
||||
sPetBattleMgr->LeaveQueue(player->GetGUID()); // LeaveQueue sends REMOVED itself
|
||||
}
|
||||
|
||||
void WorldSession::HandlePetBattleQueueProposeMatchResult(WorldPackets::BattlePet::PetBattleQueueProposeMatchResult& petBattleQueueProposeMatchResult)
|
||||
@@ -1232,9 +1193,8 @@ void WorldSession::HandlePetBattleRequestUpdate(WorldPackets::BattlePet::PetBatt
|
||||
|
||||
// Send initial update to both players
|
||||
WorldPackets::BattlePet::PetBattleInitialUpdate initialUpdate;
|
||||
initialUpdate.SceneObjectGUID = battle->GetSceneObjectGUID();
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), battle, PetBattles::PET_BATTLE_TEAM_1);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), battle, PetBattles::PET_BATTLE_TEAM_2);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[0], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_1), false, battle, PetBattles::PET_BATTLE_TEAM_1);
|
||||
BuildPetBattlePlayerUpdate(initialUpdate.Players[1], battle->GetTeam(PetBattles::PET_BATTLE_TEAM_2), false, battle, PetBattles::PET_BATTLE_TEAM_2);
|
||||
BuildPetBattleEnviros(initialUpdate.Enviros, battle);
|
||||
initialUpdate.CurRound = battle->GetCurrentRound();
|
||||
initialUpdate.CurPetBattleState = static_cast<int8>(battle->GetBattleState());
|
||||
|
||||
@@ -332,12 +332,13 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
ByteBuffer& operator<<(ByteBuffer& data, PetBattleEffectTargetInfo const& target)
|
||||
{
|
||||
data << uint8(target.Type << 4); // Upper nibble (client reads uint8 >> 4)
|
||||
data << uint8(target.Petx);
|
||||
data << uint8(target.Type << 4); // Upper nibble (IDA-verified 12.0: client reads uint8 >> 4)
|
||||
|
||||
if (target.Type == 8 && target.EmbeddedPetUpdate)
|
||||
data << *target.EmbeddedPetUpdate;
|
||||
|
||||
data << int32(target.Petx);
|
||||
|
||||
for (int32 param : target.Params)
|
||||
data << int32(param);
|
||||
|
||||
@@ -347,11 +348,11 @@ namespace WorldPackets::BattlePet
|
||||
ByteBuffer& operator<<(ByteBuffer& data, PetBattleEffectInfo const& effect)
|
||||
{
|
||||
data << int32(effect.AbilityEffectID);
|
||||
data << uint16(effect.Flags);
|
||||
data << int32(effect.Flags);
|
||||
data << int16(effect.SourceAuraInstanceID);
|
||||
data << int16(effect.TurnInstanceID);
|
||||
data << int8(effect.PetBattleEffectType);
|
||||
data << uint8(effect.CasterPBOID);
|
||||
data << int32(effect.PetBattleEffectType);
|
||||
data << int32(effect.CasterPBOID);
|
||||
data << uint8(effect.StackDepth);
|
||||
|
||||
data << uint32(effect.Targets.size());
|
||||
@@ -393,17 +394,18 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
data << uint32(cooldowns.size());
|
||||
|
||||
// V12 wire order: Cooldowns, PetXDied count (3 bits), Effects, PetXDied data
|
||||
// (V6 had Effects before Cooldowns, but V12 client expects this order)
|
||||
// 12.1.0.69587 wire order (every FIRST_ROUND / ROUND_RESULT / REPLACEMENTS_MADE of two retail
|
||||
// captures, verified byte-exact): Cooldowns, Effects, PetXDied count (3 bits) + flush, PetXDied.
|
||||
// The count used to be written BEFORE the effects, which shifted every effect by one byte.
|
||||
for (PetBattleCooldownInfo const& cd : cooldowns)
|
||||
data << cd;
|
||||
|
||||
data << Bits<3>(petXDied.size());
|
||||
data.FlushBits();
|
||||
|
||||
for (PetBattleEffectInfo const& effect : effects)
|
||||
data << effect;
|
||||
|
||||
data << Bits<3>(petXDied.size());
|
||||
data.FlushBits();
|
||||
|
||||
for (int8 pboid : petXDied)
|
||||
data << int8(pboid);
|
||||
}
|
||||
@@ -501,9 +503,6 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
WorldPacket const* PetBattleInitialUpdate::Write()
|
||||
{
|
||||
// SceneObjectGUID wrapper (see BEFUND/PLAN_B5) precedes the existing body, unchanged otherwise.
|
||||
_worldPacket << SceneObjectGUID;
|
||||
|
||||
// Wire format matches BaseEntity.cpp:494-588
|
||||
for (std::size_t i = 0; i < 2; ++i)
|
||||
{
|
||||
@@ -541,7 +540,6 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
WorldPacket const* PetBattleFirstRound::Write()
|
||||
{
|
||||
_worldPacket << SceneObjectGUID;
|
||||
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
|
||||
|
||||
return &_worldPacket;
|
||||
@@ -549,7 +547,6 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
WorldPacket const* PetBattleRoundResult::Write()
|
||||
{
|
||||
_worldPacket << SceneObjectGUID;
|
||||
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
|
||||
|
||||
return &_worldPacket;
|
||||
@@ -557,7 +554,6 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
WorldPacket const* PetBattleReplacementsMade::Write()
|
||||
{
|
||||
_worldPacket << SceneObjectGUID;
|
||||
WriteRoundResult(_worldPacket, CurRound, NextPetBattleState, Players, Effects, Cooldowns, PetXDied);
|
||||
|
||||
return &_worldPacket;
|
||||
@@ -584,8 +580,6 @@ namespace WorldPackets::BattlePet
|
||||
|
||||
WorldPacket const* PetBattleFinalRound::Write()
|
||||
{
|
||||
_worldPacket << SceneObjectGUID;
|
||||
|
||||
// 12.0.7 (68275) wire (sniff-verified vs b_pets, 5 battles): flags_byte, u32(=0), npcCreatureID(u32), pets.count, pets[].
|
||||
// flags_byte = MSB-first bitfield{4}: bit7=Abandoned, bit6=PvpBattle, bit5=Winners[0] (team0), bit4=Winners[1] (team1).
|
||||
_worldPacket << Bits<1>(Abandoned);
|
||||
@@ -623,16 +617,20 @@ namespace WorldPackets::BattlePet
|
||||
WorldPacket const* PetBattleQueueStatus::Write()
|
||||
{
|
||||
_worldPacket << uint32(Status);
|
||||
_worldPacket << uint32(SlotResult[0]);
|
||||
_worldPacket << uint32(SlotResult[1]);
|
||||
_worldPacket << Size<uint32>(SlotResult);
|
||||
for (uint32 result : SlotResult)
|
||||
_worldPacket << uint32(result);
|
||||
|
||||
_worldPacket << Ticket;
|
||||
|
||||
_worldPacket << OptionalInit(ClientWaitTime);
|
||||
_worldPacket << OptionalInit(AvgWaitTime);
|
||||
_worldPacket.FlushBits();
|
||||
|
||||
if (ClientWaitTime)
|
||||
_worldPacket << uint32(*ClientWaitTime);
|
||||
_worldPacket << uint64(*ClientWaitTime);
|
||||
if (AvgWaitTime)
|
||||
_worldPacket << uint32(*AvgWaitTime);
|
||||
_worldPacket << uint64(*AvgWaitTime);
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
@@ -649,18 +647,4 @@ namespace WorldPackets::BattlePet
|
||||
_worldPacket << uint32(RoundsRemaining);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
void BattlePetDeletePetCheat::Read()
|
||||
{
|
||||
_worldPacket >> PetGuid;
|
||||
}
|
||||
|
||||
WorldPacket const* PetBattleFinished::Write()
|
||||
{
|
||||
// Body is disasm-verified to be exactly this one ObjectGuid (petbattle_wire_FULL_68275.json
|
||||
// 0x42008a, read_count 1) - see BEFUND/PLAN_B5.
|
||||
_worldPacket << SceneObjectGUID;
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
|
||||
#include "Packet.h"
|
||||
#include "PacketUtilities.h"
|
||||
#include "LFGPacketsCommon.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "Optional.h"
|
||||
#include "Position.h"
|
||||
#include "UnitDefines.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
@@ -194,18 +196,6 @@ namespace WorldPackets
|
||||
ObjectGuid PetGuid;
|
||||
};
|
||||
|
||||
// Developer/GM cheat variant of DELETE_PET (opcode DELETE_PET+1). Same wire (one pet GUID); the handler
|
||||
// is gated on GM security since the client only emits this in developer mode.
|
||||
class BattlePetDeletePetCheat final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
explicit BattlePetDeletePetCheat(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_DELETE_PET_CHEAT, std::move(packet)) {}
|
||||
|
||||
void Read() override;
|
||||
|
||||
ObjectGuid PetGuid;
|
||||
};
|
||||
|
||||
class BattlePetSetFlags final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
@@ -366,7 +356,7 @@ namespace WorldPackets
|
||||
struct PetBattleEffectTargetInfo
|
||||
{
|
||||
uint8 Type = 0; // JamPetBattleEffectTarget field 0 (wire: uint8(Type << 4); client reads byte>>4)
|
||||
int32 Petx = 0; // field 1 (petx@4) - target pet index / PBOID, always read after the discriminator
|
||||
int32 Petx = 0; // field 1 (petx@4) ? target pet index / PBOID, always read after the discriminator
|
||||
// Params carry the type-specific union members (ascending reflection offset):
|
||||
// 1 aura: auraInstanceID, auraAbilityID, roundsRemaining, currentRound (fields 2-5)
|
||||
// 2 state: stateID, stateValue (fields 6-7)
|
||||
@@ -375,7 +365,7 @@ namespace WorldPackets
|
||||
// 5 trigger: triggerAbilityID (field 10)
|
||||
// 6 cooldown: changedAbilityID, cooldownRemaining, lockdownRemaining (fields 11-13)
|
||||
// 7 broadcast:broadcastTextID (field 14)
|
||||
// 8 pet: EmbeddedPetUpdate (field 15 'pet'); slot/newAbilityID (16-17) not serialized - see report
|
||||
// 8 pet: EmbeddedPetUpdate (field 15 'pet'); slot/newAbilityID (16-17) not serialized ? see report
|
||||
std::vector<int32> Params;
|
||||
Optional<PetBattlePetUpdateInfo> EmbeddedPetUpdate; // Only when Type == 8
|
||||
};
|
||||
@@ -559,14 +549,10 @@ namespace WorldPackets
|
||||
class PetBattleInitialUpdate final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
// 12.1 (69382, PLAN_B5): the client only listens for the round-broadcast body on the
|
||||
// (SceneType::PetBattle). SMSG_PET_BATTLE_INITIAL_UPDATE is dead (TC-declared STATUS_NEVER,
|
||||
// no longer dispatched by the retail client) - this class now targets the live opcode.
|
||||
PetBattleInitialUpdate() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_INITIAL_UPDATE) {}
|
||||
PetBattleInitialUpdate() : ServerPacket(SMSG_PET_BATTLE_INITIAL_UPDATE) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
ObjectGuid SceneObjectGUID;
|
||||
std::array<PetBattlePlayerUpdateInfo, 2> Players;
|
||||
std::array<PetBattleEnviroInfo, 3> Enviros;
|
||||
uint16 WaitingForFrontPetsMaxSecs = 30;
|
||||
@@ -584,14 +570,10 @@ namespace WorldPackets
|
||||
class PetBattleFirstRound final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
// See PetBattleInitialUpdate above: FIRST_ROUND/ROUND_RESULT/REPLACEMENTS_MADE share one
|
||||
// client parser (JamPetBattleRoundResult, PLAN_B5) and only differ by which
|
||||
// SMSG_SCENE_OBJECT_PET_BATTLE_* opcode wraps the identical body.
|
||||
PetBattleFirstRound() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_FIRST_ROUND) {}
|
||||
PetBattleFirstRound() : ServerPacket(SMSG_PET_BATTLE_FIRST_ROUND) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
ObjectGuid SceneObjectGUID;
|
||||
uint32 CurRound = 0;
|
||||
int8 NextPetBattleState = 0;
|
||||
std::array<PetBattleRoundPlayerData, 2> Players;
|
||||
@@ -603,11 +585,10 @@ namespace WorldPackets
|
||||
class PetBattleRoundResult final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
PetBattleRoundResult() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_ROUND_RESULT) {}
|
||||
PetBattleRoundResult() : ServerPacket(SMSG_PET_BATTLE_ROUND_RESULT) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
ObjectGuid SceneObjectGUID;
|
||||
uint32 CurRound = 0;
|
||||
int8 NextPetBattleState = 0;
|
||||
std::array<PetBattleRoundPlayerData, 2> Players;
|
||||
@@ -619,11 +600,10 @@ namespace WorldPackets
|
||||
class PetBattleReplacementsMade final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
PetBattleReplacementsMade() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_REPLACEMENTS_MADE) {}
|
||||
PetBattleReplacementsMade() : ServerPacket(SMSG_PET_BATTLE_REPLACEMENTS_MADE) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
ObjectGuid SceneObjectGUID;
|
||||
uint32 CurRound = 0;
|
||||
int8 NextPetBattleState = 0;
|
||||
std::array<PetBattleRoundPlayerData, 2> Players;
|
||||
@@ -650,21 +630,14 @@ namespace WorldPackets
|
||||
class PetBattleFinalRound final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
// 12.1 (69382, PLAN_B5): live opcode is SMSG_SCENE_OBJECT_PET_BATTLE_FINAL_ROUND; the send
|
||||
// site (PetBattle::SendFinalRoundPacket) and the body already existed, only the opcode +
|
||||
// SceneObjectGUID wrapper were missing (rank #2, "lowest fruit" in the cluster).
|
||||
PetBattleFinalRound() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_FINAL_ROUND) {}
|
||||
PetBattleFinalRound() : ServerPacket(SMSG_PET_BATTLE_FINAL_ROUND) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// UNVERIFIED (PLAN_B5): whether FINAL_ROUND really carries a SceneObjectGUID prefix is only
|
||||
// confirmed by pattern-matching against FIRST_ROUND/ROUND_RESULT (both sniff-verified); no
|
||||
// direct sniff of FINAL_ROUND itself exists yet.
|
||||
ObjectGuid SceneObjectGUID;
|
||||
bool Abandoned = false;
|
||||
bool PvpBattle = false;
|
||||
// 12.0.7 (68275) JamPetBattleFinalRound (sniff-verified vs b_pets, 5 battles): the winner is the
|
||||
// per-team flag pair in the flag byte - bit5=Winners[0] (team0), bit4=Winners[1] (team1). The two
|
||||
// per-team flag pair in the flag byte ? bit5=Winners[0] (team0), bit4=Winners[1] (team1). The two
|
||||
// flat uint32s after the flush are field#1 (0 in every capture, role unknown, NOT winners) and
|
||||
// NpcCreatureID (0 for wild battles, the trainer entry for NPC battles).
|
||||
std::array<bool, 2> Winners = {};
|
||||
@@ -675,14 +648,9 @@ namespace WorldPackets
|
||||
class PetBattleFinished final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
// 12.1 (69382, PLAN_B5): live opcode is SMSG_SCENE_OBJECT_PET_BATTLE_FINISHED; body is
|
||||
// disasm-verified to be exactly one ObjectGuid (petbattle_wire_FULL_68275.json 0x42008a,
|
||||
// read_count 1) - the best-belegte opcode in the whole cluster.
|
||||
PetBattleFinished() : ServerPacket(SMSG_SCENE_OBJECT_PET_BATTLE_FINISHED, 20) {}
|
||||
PetBattleFinished() : ServerPacket(SMSG_PET_BATTLE_FINISHED, 0) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
ObjectGuid SceneObjectGUID;
|
||||
WorldPacket const* Write() override { return &_worldPacket; }
|
||||
};
|
||||
|
||||
class PetBattleRequestFailed final : public ServerPacket
|
||||
@@ -713,10 +681,16 @@ namespace WorldPackets
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// 12.1.0.69587 wire (friend capture, CMSG_JOIN_PET_BATTLE_QUEUE -> two QUEUED statuses):
|
||||
// Status u32, SlotResult (sized, empty on QUEUED), RideTicket{RequesterGuid = player,
|
||||
// Id, Type = 3 (PetBattle), Time = enqueue time, IsCrossFaction}, then the two optional
|
||||
// wait times as 64-bit seconds (ClientWaitTime 0 -> 8 across a 7.3 s gap, AvgWaitTime 60).
|
||||
// The previous layout (fixed SlotResult pair, no ticket, 32-bit times) was 32 bytes short.
|
||||
uint32 Status = 0;
|
||||
std::array<uint32, 2> SlotResult = {};
|
||||
Optional<uint32> ClientWaitTime;
|
||||
Optional<uint32> AvgWaitTime;
|
||||
std::vector<uint32> SlotResult;
|
||||
WorldPackets::LFG::RideTicket Ticket;
|
||||
Optional<uint64> ClientWaitTime;
|
||||
Optional<uint64> AvgWaitTime;
|
||||
};
|
||||
|
||||
class PetBattleQueueProposeMatch final : public ServerPacket
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace WorldPackets
|
||||
None = 0,
|
||||
Battlegrounds = 1,
|
||||
Lfg = 2,
|
||||
PetBattle = 3, // SMSG_PET_BATTLE_QUEUE_STATUS ticket (12.1.0.69587 capture)
|
||||
LfgListListing = 4, // premade group listing tickets (sniff-verified, 68275)
|
||||
LfgListApplication = 6 // premade group application tickets (sniff-verified, 68275)
|
||||
};
|
||||
|
||||
@@ -232,7 +232,7 @@ void OpcodeTable::InitializeClientOpcodes()
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PAY_START_VAS_PURCHASE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePayStartVasPurchase);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_CLEAR_FANFARE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetClearFanfare);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetDeletePet);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET_CHEAT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetDeletePetCheat);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET_CHEAT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_MODIFY_NAME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetModifyName);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetRequestJournal);
|
||||
DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL_LOCK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlePetRequestJournalLock);
|
||||
|
||||
@@ -2586,7 +2586,6 @@ public:
|
||||
void HandleBattlePetModifyName(WorldPackets::BattlePet::BattlePetModifyName& battlePetModifyName);
|
||||
void HandleQueryBattlePetName(WorldPackets::BattlePet::QueryBattlePetName& queryBattlePetName);
|
||||
void HandleBattlePetDeletePet(WorldPackets::BattlePet::BattlePetDeletePet& battlePetDeletePet);
|
||||
void HandleBattlePetDeletePetCheat(WorldPackets::BattlePet::BattlePetDeletePetCheat& battlePetDeletePetCheat);
|
||||
void HandleBattlePetSetFlags(WorldPackets::BattlePet::BattlePetSetFlags& battlePetSetFlags);
|
||||
void HandleBattlePetClearFanfare(WorldPackets::BattlePet::BattlePetClearFanfare& battlePetClearFanfare);
|
||||
void HandleBattlePetSummon(WorldPackets::BattlePet::BattlePetSummon& battlePetSummon);
|
||||
@@ -2596,7 +2595,6 @@ public:
|
||||
// Pet Battle combat
|
||||
void HandlePetBattleRequestWild(WorldPackets::BattlePet::PetBattleRequestWild& petBattleRequestWild);
|
||||
void StartNPCPetBattle(Creature* trainer);
|
||||
void StartWildPetBattle(ObjectGuid targetGUID);
|
||||
void HandlePetBattleInput(WorldPackets::BattlePet::PetBattleInput& petBattleInput);
|
||||
void HandlePetBattleReplaceFrontPet(WorldPackets::BattlePet::PetBattleReplaceFrontPet& petBattleReplaceFrontPet);
|
||||
void HandlePetBattleQuitNotify(WorldPackets::BattlePet::PetBattleQuitNotify& petBattleQuitNotify);
|
||||
|
||||
@@ -6361,11 +6361,12 @@ void Spell::EffectStartPetBattle()
|
||||
Player* player = caster->ToPlayer();
|
||||
Creature* creature = unitTarget->ToCreature();
|
||||
|
||||
if (creature->IsWildBattlePet())
|
||||
/* if (creature->IsWildBattlePet())
|
||||
{
|
||||
player->GetSession()->StartWildPetBattle(creature->GetGUID());
|
||||
}
|
||||
else if (sPetBattleMgr->GetNPCTeam(creature->GetEntry()))
|
||||
}*/
|
||||
|
||||
/*else*/ if (sPetBattleMgr->GetNPCTeam(creature->GetEntry()))
|
||||
{
|
||||
player->GetSession()->StartNPCPetBattle(creature);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "InstanceScript.h"
|
||||
#include "Map.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "kings_rest.h"
|
||||
|
||||
// Sniff-derived (Map 1762, build 12.1.0.69587)
|
||||
enum KingDazarSpells
|
||||
{
|
||||
// King Dazar
|
||||
SPELL_AERIAL_SMASH = 1303101, // leap at a player, 4s
|
||||
SPELL_AERIAL_SMASH_DUMMY = 1303105,
|
||||
SPELL_AERIAL_SMASH_IMPACT = 1303115, // landing shockwaves
|
||||
|
||||
SPELL_BLADE_COMBO_1 = 269503, // telegraph on a player
|
||||
SPELL_BLADE_COMBO_2 = 268586, // 1.5s channel
|
||||
SPELL_BLADE_COMBO_3 = 268587,
|
||||
SPELL_BLADE_COMBO_4 = 268589,
|
||||
SPELL_BLADE_COMBO_5 = 268590,
|
||||
SPELL_BLADE_COMBO_6 = 268591,
|
||||
|
||||
SPELL_GILDED_DESTRUCTION = 1303267, // 6s, floods the arena via Gold Pool Stalkers
|
||||
SPELL_GILDED_DESTRUCTION_DOT = 1303288,
|
||||
SPELL_ETERNAL_BOND = 1303523, // empower T'zala
|
||||
SPELL_COPY_THREAT_TO_VEHICLE = 173531,
|
||||
SPELL_IMPALING_SPEAR = 1302944, // Dazar ground spears
|
||||
SPELL_IMPALING_SPEAR_B = 1302945,
|
||||
|
||||
// Reban (raptor)
|
||||
SPELL_RAPTOR_SPAWN_SPLASH = 269023, // summons raptor adds
|
||||
SPELL_HUNTING_LEAP = 269231,
|
||||
SPELL_HUNTING_LEAP_THROW = 269232,
|
||||
SPELL_HUNTING_LEAP_TELEGRAPH = 1303039, // ground telegraph
|
||||
SPELL_HUNTING_LEAP_LAND = 1310473, // landing impact
|
||||
SPELL_DEATHLY_ROAR = 269369, // 4s fear howl
|
||||
|
||||
// T'zala (vehicle)
|
||||
SPELL_QUAKING_LEAP = 1303323, // 3s, roots the raid
|
||||
SPELL_QUAKING_LEAP_SMALL = 1303324,
|
||||
SPELL_QUAKING_LEAP_IMPACT = 1303326, // landing damage
|
||||
SPELL_QUAKING_LEAP_IMPACT_2 = 1303327,
|
||||
SPELL_QUAKING_LEAP_IMPACT_3 = 1303328,
|
||||
SPELL_AERIAL_SMASH_TZALA = 1303120,
|
||||
SPELL_SAVAGE_MAUL = 1303481,
|
||||
SPELL_SAVAGE_MAUL_HEAVY = 1303488,
|
||||
SPELL_SAVAGE_MAUL_CAST = 1303490,
|
||||
SPELL_ETERNAL_BOND_TZALA = 1303519
|
||||
};
|
||||
|
||||
enum KingDazarEvents
|
||||
{
|
||||
EVENT_AERIAL_SMASH = 1,
|
||||
EVENT_BLADE_COMBO,
|
||||
EVENT_BLADE_COMBO_NEXT,
|
||||
EVENT_GILDED_DESTRUCTION,
|
||||
EVENT_ETERNAL_BOND,
|
||||
EVENT_IMPALING_SPEARS,
|
||||
EVENT_TZALA_EMPOWER,
|
||||
|
||||
EVENT_LEAP,
|
||||
EVENT_ROAR,
|
||||
EVENT_RAPTOR_SPLASH,
|
||||
|
||||
EVENT_QUAKE,
|
||||
EVENT_MAUL,
|
||||
EVENT_SMASH_TZALA,
|
||||
EVENT_POOL_DOT
|
||||
};
|
||||
|
||||
enum KingDazarTexts
|
||||
{
|
||||
SAY_DAZAR_AGGRO = 0, // You will pay for desecrating my temple!
|
||||
SAY_DAZAR_BOND = 1, // Even death cannot break de bond between us, my pets. Now feast, T'zala... FEAST!
|
||||
SAY_DAZAR_GILDED = 2, // Dis is why I am king... and you are nothing.
|
||||
SAY_DAZAR_HUNT = 3, // Come... come, my dear Reban. It is feeding time!
|
||||
SAY_DAZAR_SLASH = 4, // None can withstand my blade!
|
||||
SAY_DAZAR_ROAR_MELEE = 5, // I will shred your flesh!
|
||||
SAY_DAZAR_KILL = 6, // Run! Send word to de Zandalari! ... Run!
|
||||
SAY_DAZAR_DEATH = 7 // I... surrender... to de sands of time...
|
||||
};
|
||||
|
||||
// 136160 - King Dazar
|
||||
struct boss_king_dazar : public BossAI
|
||||
{
|
||||
boss_king_dazar(Creature* creature) : BossAI(creature, DATA_KING_DAZAR), _comboChain(0) { }
|
||||
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
BossAI::JustEngagedWith(who);
|
||||
Talk(SAY_DAZAR_AGGRO);
|
||||
|
||||
// The raptor and its mount join the fray alongside the king
|
||||
for (Creature* pet : GetPets())
|
||||
if (pet)
|
||||
{
|
||||
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, pet, 2);
|
||||
pet->AI()->DoZoneInCombat();
|
||||
}
|
||||
|
||||
events.ScheduleEvent(EVENT_AERIAL_SMASH, 15s);
|
||||
events.ScheduleEvent(EVENT_BLADE_COMBO, 23s);
|
||||
events.ScheduleEvent(EVENT_GILDED_DESTRUCTION, 30s);
|
||||
events.ScheduleEvent(EVENT_ETERNAL_BOND, 40s);
|
||||
events.ScheduleEvent(EVENT_IMPALING_SPEARS, 46s);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
Talk(SAY_DAZAR_DEATH);
|
||||
|
||||
for (Creature* pet : GetPets())
|
||||
if (pet)
|
||||
{
|
||||
instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, pet, 2);
|
||||
pet->DespawnOrUnsummon(3s);
|
||||
}
|
||||
|
||||
summons.DespawnAll();
|
||||
_JustDied();
|
||||
}
|
||||
|
||||
void KilledUnit(Unit* victim) override
|
||||
{
|
||||
if (victim->IsPlayer())
|
||||
Talk(SAY_DAZAR_KILL);
|
||||
}
|
||||
|
||||
void DoAerialSmash()
|
||||
{
|
||||
Talk(SAY_DAZAR_SLASH);
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 80.0f, true))
|
||||
DoCast(target, SPELL_AERIAL_SMASH);
|
||||
events.ScheduleEvent(EVENT_AERIAL_SMASH, 28s);
|
||||
}
|
||||
|
||||
void DoBladeCombo()
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 80.0f, true))
|
||||
{
|
||||
DoCast(target, SPELL_BLADE_COMBO_1);
|
||||
DoCast(target, SPELL_BLADE_COMBO_2);
|
||||
}
|
||||
_comboChain = 0;
|
||||
events.ScheduleEvent(EVENT_BLADE_COMBO_NEXT, 1s);
|
||||
}
|
||||
|
||||
void DoBladeComboNext()
|
||||
{
|
||||
static uint32 const chain[] = { SPELL_BLADE_COMBO_3, SPELL_BLADE_COMBO_4, SPELL_BLADE_COMBO_5, SPELL_BLADE_COMBO_6 };
|
||||
|
||||
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 80.0f, true);
|
||||
if (!target)
|
||||
{
|
||||
_comboChain = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
DoCast(target, chain[_comboChain]);
|
||||
|
||||
if (++_comboChain < 4)
|
||||
events.ScheduleEvent(EVENT_BLADE_COMBO_NEXT, 800ms);
|
||||
else
|
||||
{
|
||||
_comboChain = 0;
|
||||
events.ScheduleEvent(EVENT_BLADE_COMBO, 42s);
|
||||
}
|
||||
}
|
||||
|
||||
void DoGildedDestruction()
|
||||
{
|
||||
Talk(SAY_DAZAR_GILDED);
|
||||
DoCastSelf(SPELL_GILDED_DESTRUCTION);
|
||||
events.ScheduleEvent(EVENT_GILDED_DESTRUCTION, 55s);
|
||||
}
|
||||
|
||||
void DoEternalBond()
|
||||
{
|
||||
Talk(SAY_DAZAR_BOND);
|
||||
DoCastSelf(SPELL_ETERNAL_BOND);
|
||||
|
||||
if (Creature* tzala = me->FindNearestCreature(NPC_TZALA, 150.0f, true))
|
||||
tzala->AI()->SetData(DATA_KING_DAZAR, 0);
|
||||
|
||||
events.ScheduleEvent(EVENT_ETERNAL_BOND, 80s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_AERIAL_SMASH:
|
||||
DoAerialSmash();
|
||||
break;
|
||||
case EVENT_BLADE_COMBO:
|
||||
DoBladeCombo();
|
||||
break;
|
||||
case EVENT_BLADE_COMBO_NEXT:
|
||||
DoBladeComboNext();
|
||||
break;
|
||||
case EVENT_GILDED_DESTRUCTION:
|
||||
DoGildedDestruction();
|
||||
break;
|
||||
case EVENT_ETERNAL_BOND:
|
||||
DoEternalBond();
|
||||
break;
|
||||
case EVENT_IMPALING_SPEARS:
|
||||
DoCastSelf(SPELL_IMPALING_SPEAR);
|
||||
events.ScheduleEvent(EVENT_IMPALING_SPEARS, 45s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Creature*> GetPets() const
|
||||
{
|
||||
std::vector<Creature*> pets;
|
||||
pets.reserve(2);
|
||||
if (Creature* tzala = me->FindNearestCreature(NPC_TZALA, 150.0f, true))
|
||||
pets.push_back(tzala);
|
||||
if (Creature* reban = me->FindNearestCreature(NPC_REBAN, 150.0f, true))
|
||||
pets.push_back(reban);
|
||||
return pets;
|
||||
}
|
||||
|
||||
uint8 _comboChain;
|
||||
};
|
||||
|
||||
// 136984 - Reban (King Dazar's raptor)
|
||||
struct npc_kings_rest_reban : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_reban(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
if (InstanceScript* instance = me->GetInstanceScript())
|
||||
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 2);
|
||||
|
||||
_events.ScheduleEvent(EVENT_RAPTOR_SPLASH, 5s);
|
||||
_events.ScheduleEvent(EVENT_LEAP, 8s);
|
||||
_events.ScheduleEvent(EVENT_ROAR, 14s);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
_events.Reset();
|
||||
}
|
||||
|
||||
void DoHuntingLeap()
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 80.0f, true))
|
||||
{
|
||||
DoCast(target, SPELL_HUNTING_LEAP);
|
||||
DoCast(target, SPELL_HUNTING_LEAP_TELEGRAPH);
|
||||
}
|
||||
|
||||
_events.ScheduleEvent(EVENT_LEAP, 10s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_RAPTOR_SPLASH:
|
||||
DoCastSelf(SPELL_RAPTOR_SPAWN_SPLASH);
|
||||
_events.ScheduleEvent(EVENT_RAPTOR_SPLASH, 33s);
|
||||
break;
|
||||
case EVENT_LEAP:
|
||||
DoHuntingLeap();
|
||||
break;
|
||||
case EVENT_ROAR:
|
||||
DoCastSelf(SPELL_DEATHLY_ROAR);
|
||||
_events.ScheduleEvent(EVENT_ROAR, 10s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
// 136976 - T'zala (the raptor mount)
|
||||
struct npc_kings_rest_tzala : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_tzala(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
if (InstanceScript* instance = me->GetInstanceScript())
|
||||
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 2);
|
||||
|
||||
// Emulates the retail mount moment ~37s into the fight
|
||||
_events.ScheduleEvent(EVENT_TZALA_EMPOWER, 37s);
|
||||
_events.ScheduleEvent(EVENT_SMASH_TZALA, 54s);
|
||||
_events.ScheduleEvent(EVENT_QUAKE, 49s);
|
||||
_events.ScheduleEvent(EVENT_MAUL, 76s);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
_events.Reset();
|
||||
}
|
||||
|
||||
void SetData(uint32 /*type*/, uint32 /*value*/) override
|
||||
{
|
||||
// Eternal Bond from the boss empowers the mount
|
||||
DoCastSelf(SPELL_ETERNAL_BOND_TZALA);
|
||||
DoCastSelf(SPELL_COPY_THREAT_TO_VEHICLE);
|
||||
me->SetHealth(me->GetMaxHealth());
|
||||
}
|
||||
|
||||
void DoQuakingLeap()
|
||||
{
|
||||
for (MapReference const& ref : me->GetMap()->GetPlayers())
|
||||
{
|
||||
Player* player = ref.GetSource();
|
||||
if (!player || !player->IsAlive() || !me->IsWithinDistInMap(player, 100.0f))
|
||||
continue;
|
||||
|
||||
DoCast(player, SPELL_QUAKING_LEAP);
|
||||
}
|
||||
|
||||
DoCastSelf(SPELL_QUAKING_LEAP_IMPACT);
|
||||
_events.ScheduleEvent(EVENT_QUAKE, 22s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TZALA_EMPOWER:
|
||||
SetData(0, 0);
|
||||
break;
|
||||
case EVENT_SMASH_TZALA:
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 80.0f, true))
|
||||
DoCast(target, SPELL_AERIAL_SMASH_TZALA);
|
||||
_events.ScheduleEvent(EVENT_SMASH_TZALA, 28s);
|
||||
break;
|
||||
case EVENT_QUAKE:
|
||||
DoQuakingLeap();
|
||||
break;
|
||||
case EVENT_MAUL:
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true))
|
||||
DoCast(target, SPELL_SAVAGE_MAUL);
|
||||
_events.ScheduleEvent(EVENT_MAUL, 12s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
// 269148 - Gold Pool Stalker
|
||||
struct npc_kings_rest_gold_pool_stalker : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_gold_pool_stalker(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustAppeared() override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
_events.ScheduleEvent(EVENT_POOL_DOT, 1s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_POOL_DOT)
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
DoCast(target, SPELL_GILDED_DESTRUCTION_DOT);
|
||||
_events.ScheduleEvent(EVENT_POOL_DOT, 3s + 500ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
void AddSC_boss_king_dazar()
|
||||
{
|
||||
RegisterKingsRestCreatureAI(boss_king_dazar);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_reban);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_tzala);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_gold_pool_stalker);
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "InstanceScript.h"
|
||||
#include "Map.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "kings_rest.h"
|
||||
|
||||
// Sniff-derived (Map 1762, build 12.1.0.69587)
|
||||
enum MchimbaSpells
|
||||
{
|
||||
SPELL_COMMAND_CONSTRUCTS = 1312911, // engage, summons Interment Construct
|
||||
SPELL_DRAIN_FLUIDS = 267618, // pull players, 2s cast
|
||||
SPELL_EXPLOSIVE_ACIDS = 1312143, // floor pools during Drain Fluids
|
||||
SPELL_BURN_CORRUPTION = 267639, // dot on random player, 4s
|
||||
SPELL_BURN_CORRUPTION_DUMMY = 1311956,
|
||||
SPELL_AWAKENING_SLAM = 1312146, // frontal, 3s
|
||||
SPELL_BURNING_GROUND = 267874, // floor fire at random player
|
||||
SPELL_ENTOMB = 267702, // lock player into a coffin, 3s
|
||||
SPELL_OPEN_COFFIN = 271290, // finish the entombed player, 6s
|
||||
SPELL_EMBALM = 1312569, // Interment Construct dot
|
||||
SPELL_WRETCHED_DISCHARGE = 267763, // mummy floor pools, 4s
|
||||
SPELL_SUMMON_HALF_MUMMY = 1312147, // coffin -> Half-Finished Mummy
|
||||
SPELL_STRUGGLE = 267764, // entombed player tries to break free
|
||||
SPELL_CHECK_FOR_BADDIES = 267844,
|
||||
SPELL_MARK_AS_BAD = 1316988,
|
||||
SPELL_CANCEL_BAD_MARKINGS = 272532
|
||||
};
|
||||
|
||||
enum MchimbaEvents
|
||||
{
|
||||
EVENT_MCHIMBA_SLAM = 1,
|
||||
EVENT_MCHIMBA_BURN,
|
||||
EVENT_MCHIMBA_DRAIN,
|
||||
EVENT_MCHIMBA_ACIDS,
|
||||
EVENT_MCHIMBA_BURNING_GROUND,
|
||||
EVENT_MCHIMBA_ENTOMB,
|
||||
EVENT_MCHIMBA_OPEN_COFFIN,
|
||||
EVENT_MCHIMBA_CONSTRUCT_THROW
|
||||
};
|
||||
|
||||
enum MchimbaTexts
|
||||
{
|
||||
SAY_MCHIMBA_AGGRO = 0, // Your bodies bear de infection!
|
||||
SAY_MCHIMBA_BURN = 1, // De blood curse must be purged!
|
||||
SAY_MCHIMBA_SLAM = 2, // Prevent infection! Remove de brain!
|
||||
SAY_MCHIMBA_ENTOMB = 3, // Prepare de body!
|
||||
SAY_MCHIMBA_KILL = 4, // You have disturbed de dead!
|
||||
SAY_MCHIMBA_DEATH = 5 // De dead... must be... preserved...
|
||||
};
|
||||
|
||||
// 134993 - Mchimba the Embalmer
|
||||
struct boss_mchimba_the_embalmer : public BossAI
|
||||
{
|
||||
boss_mchimba_the_embalmer(Creature* creature) : BossAI(creature, DATA_MCHIMBA_THE_EMBALMER), _acidPulses(0) { }
|
||||
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
BossAI::JustEngagedWith(who);
|
||||
Talk(SAY_MCHIMBA_AGGRO);
|
||||
DoCastSelf(SPELL_COMMAND_CONSTRUCTS);
|
||||
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_DRAIN, 6s);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_BURN, 20s);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_SLAM, 30s);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_BURNING_GROUND, 55s);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_ENTOMB, 60s);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
Talk(SAY_MCHIMBA_DEATH);
|
||||
DoCastSelf(SPELL_CANCEL_BAD_MARKINGS);
|
||||
summons.DespawnAll();
|
||||
_JustDied();
|
||||
}
|
||||
|
||||
void KilledUnit(Unit* victim) override
|
||||
{
|
||||
if (victim->IsPlayer())
|
||||
Talk(SAY_MCHIMBA_KILL);
|
||||
}
|
||||
|
||||
void DoSlam()
|
||||
{
|
||||
Talk(SAY_MCHIMBA_SLAM);
|
||||
DoCastSelf(SPELL_AWAKENING_SLAM);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_SLAM, 90s);
|
||||
}
|
||||
|
||||
void DoBurn()
|
||||
{
|
||||
Talk(SAY_MCHIMBA_BURN);
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 150.0f, true))
|
||||
DoCast(target, SPELL_BURN_CORRUPTION);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_BURN, 30s);
|
||||
}
|
||||
|
||||
void DoDrain()
|
||||
{
|
||||
DoCastSelf(SPELL_DRAIN_FLUIDS);
|
||||
_acidPulses = 0;
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_ACIDS, 5s);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_DRAIN, 33s);
|
||||
}
|
||||
|
||||
void DoAcidsPulse()
|
||||
{
|
||||
// The whole raid is showered by Explosive Acids while the pool erupts
|
||||
for (MapReference const& ref : me->GetMap()->GetPlayers())
|
||||
if (Player* player = ref.GetSource())
|
||||
if (player->IsAlive() && me->IsWithinDistInMap(player, 150.0f))
|
||||
DoCast(player, SPELL_EXPLOSIVE_ACIDS);
|
||||
|
||||
if (++_acidPulses < 5)
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_ACIDS, 2s);
|
||||
}
|
||||
|
||||
void DoEntomb()
|
||||
{
|
||||
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 150.0f, true);
|
||||
if (!target)
|
||||
{
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_ENTOMB, 2s);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock onto the most embalmed player if any
|
||||
Unit* best = nullptr;
|
||||
int32 bestStacks = 0;
|
||||
for (MapReference const& ref : me->GetMap()->GetPlayers())
|
||||
{
|
||||
Player* player = ref.GetSource();
|
||||
if (!player || !player->IsAlive() || !me->IsWithinDistInMap(player, 150.0f))
|
||||
continue;
|
||||
|
||||
int32 stacks = player->GetAuraCount(SPELL_EMBALM);
|
||||
if (stacks > bestStacks)
|
||||
{
|
||||
bestStacks = stacks;
|
||||
best = player;
|
||||
}
|
||||
}
|
||||
if (best)
|
||||
target = best;
|
||||
|
||||
Talk(SAY_MCHIMBA_ENTOMB, target);
|
||||
DoCast(target, SPELL_ENTOMB);
|
||||
_entombTargetGUID = target->GetGUID();
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_OPEN_COFFIN, 8s);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_ENTOMB, 30s);
|
||||
}
|
||||
|
||||
void DoOpenCoffin()
|
||||
{
|
||||
if (Unit* entombed = ObjectAccessor::GetUnit(*me, _entombTargetGUID))
|
||||
if (entombed->IsAlive() && entombed->HasAura(SPELL_ENTOMB))
|
||||
DoCast(entombed, SPELL_OPEN_COFFIN);
|
||||
|
||||
_entombTargetGUID.Clear();
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_MCHIMBA_SLAM:
|
||||
DoSlam();
|
||||
break;
|
||||
case EVENT_MCHIMBA_BURN:
|
||||
DoBurn();
|
||||
break;
|
||||
case EVENT_MCHIMBA_DRAIN:
|
||||
DoDrain();
|
||||
break;
|
||||
case EVENT_MCHIMBA_ACIDS:
|
||||
DoAcidsPulse();
|
||||
break;
|
||||
case EVENT_MCHIMBA_BURNING_GROUND:
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
DoCast(target, SPELL_BURNING_GROUND);
|
||||
events.ScheduleEvent(EVENT_MCHIMBA_BURNING_GROUND, 70s);
|
||||
break;
|
||||
case EVENT_MCHIMBA_ENTOMB:
|
||||
DoEntomb();
|
||||
break;
|
||||
case EVENT_MCHIMBA_OPEN_COFFIN:
|
||||
DoOpenCoffin();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid _entombTargetGUID;
|
||||
uint8 _acidPulses;
|
||||
};
|
||||
|
||||
enum MummyEvents
|
||||
{
|
||||
EVENT_MUMMY_EMBALM = 1,
|
||||
EVENT_MUMMY_DISCHARGE
|
||||
};
|
||||
|
||||
// 137969 - Interment Construct
|
||||
struct npc_kings_rest_interment_construct : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_interment_construct(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustAppeared() override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
_events.ScheduleEvent(EVENT_MUMMY_EMBALM, 1s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_MUMMY_EMBALM)
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 150.0f, true))
|
||||
DoCast(target, SPELL_EMBALM);
|
||||
_events.ScheduleEvent(EVENT_MUMMY_EMBALM, 7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
// 273050 / 136264 - Half-Finished Mummy / Finished Mummy
|
||||
struct npc_kings_rest_wretched_mummy : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_wretched_mummy(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustAppeared() override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
_events.ScheduleEvent(EVENT_MUMMY_DISCHARGE, 4s);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
_events.Reset();
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_MUMMY_DISCHARGE)
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
DoCast(target, SPELL_WRETCHED_DISCHARGE);
|
||||
_events.ScheduleEvent(EVENT_MUMMY_DISCHARGE, 9s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
void AddSC_boss_mchimba_the_embalmer()
|
||||
{
|
||||
RegisterKingsRestCreatureAI(boss_mchimba_the_embalmer);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_interment_construct);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_wretched_mummy);
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "InstanceScript.h"
|
||||
#include "Map.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "kings_rest.h"
|
||||
|
||||
// Sniff-derived (Map 1762, build 12.1.0.69587)
|
||||
enum CouncilSpells
|
||||
{
|
||||
// Zanazal the Wise
|
||||
SPELL_ASSIST_CALL = 1305716, // buffs all allies
|
||||
SPELL_ARC_LIGHTNING = 1305810, // chain lightning channel, ~1s
|
||||
SPELL_POISON_NOVA = 267273, // 4s wind-up nova
|
||||
SPELL_CALL_OF_THE_ELEMENTS = 267060, // 2.5s, summons the 3 totems
|
||||
|
||||
// Kula the Butcher
|
||||
SPELL_WHIRLING_AXE = 266191, // toss axe at a player
|
||||
|
||||
// Aka'ali the Conqueror
|
||||
SPELL_BERSERKER_ASSIST_TELEPORT = 1305707, // reposition near an ally
|
||||
SPELL_BARREL_THROUGH = 266951, // 6s telegraph charge
|
||||
|
||||
// Totems
|
||||
SPELL_REINFORCED = 1309499,
|
||||
SPELL_TORRENT = 267096, // Torrent Totem
|
||||
SPELL_DISRUPTION = 267257, // Thundering Totem
|
||||
SPELL_EXPLODE = 267077 // Explosive Totem, 9s
|
||||
};
|
||||
|
||||
enum CouncilEvents
|
||||
{
|
||||
// Zanazal
|
||||
EVENT_Z_ASSIST = 1,
|
||||
EVENT_Z_ARC_LIGHTNING,
|
||||
EVENT_Z_POISON_NOVA,
|
||||
EVENT_Z_CALL_ELEMENTS,
|
||||
|
||||
// Kula
|
||||
EVENT_K_WHIRLING_AXE,
|
||||
|
||||
// Aka'ali
|
||||
EVENT_A_ASSIST_TELEPORT,
|
||||
EVENT_A_BARREL_THROUGH,
|
||||
|
||||
// Totems
|
||||
EVENT_TOTEM_TORRENT,
|
||||
EVENT_TOTEM_DISRUPTION,
|
||||
EVENT_TOTEM_EXPLODE
|
||||
};
|
||||
|
||||
enum CouncilTexts
|
||||
{
|
||||
SAY_KULA_AGGRO = 0, // Time to get de axe!
|
||||
SAY_KULA_WHIRLING_AXE = 1, // Somebody is losing a head!
|
||||
SAY_ZANAZAL_POISON = 0, // Dere is no antidote for dis poison!
|
||||
SAY_ZANAZAL_ELEMENTS = 1, // De elements answer my call!
|
||||
SAY_AKAALI_BARREL = 0, // Here comes de pain!
|
||||
SAY_AKAALI_DEATH = 1, // My rage... be... undying...
|
||||
SAY_KULA_DEATH = 2, // My head... will make... a fine trophy...
|
||||
SAY_ZANAZAL_DEATH = 2 // Please... bury me... far from dese... imbeciles...
|
||||
};
|
||||
|
||||
// Base AI shared by the three council members: manages the shared encounter state,
|
||||
// the boss frame, chaining the arena into combat and encounter completion.
|
||||
class boss_council_tribes_member : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
boss_council_tribes_member(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { }
|
||||
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
ScriptedAI::JustEngagedWith(who);
|
||||
|
||||
// The whole council chamber answers the pull
|
||||
for (Creature* member : GetMembers())
|
||||
if (member)
|
||||
member->AI()->DoZoneInCombat();
|
||||
|
||||
if (_instance && _instance->GetBossState(DATA_COUNCIL_OF_TRIBES) != IN_PROGRESS)
|
||||
{
|
||||
_instance->SetBossState(DATA_COUNCIL_OF_TRIBES, IN_PROGRESS);
|
||||
for (Creature* member : GetMembers())
|
||||
if (member)
|
||||
_instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, member);
|
||||
}
|
||||
}
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
ScriptedAI::EnterEvadeMode(why);
|
||||
|
||||
if (!_instance || _instance->GetBossState(DATA_COUNCIL_OF_TRIBES) == DONE)
|
||||
return;
|
||||
|
||||
bool anyInCombat = false;
|
||||
for (Creature* member : GetMembers())
|
||||
if (member && member->IsInCombat())
|
||||
anyInCombat = true;
|
||||
|
||||
if (!anyInCombat && _instance->GetBossState(DATA_COUNCIL_OF_TRIBES) == IN_PROGRESS)
|
||||
{
|
||||
for (Creature* member : GetMembers())
|
||||
if (member)
|
||||
_instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, member);
|
||||
_instance->SetBossState(DATA_COUNCIL_OF_TRIBES, FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
Talk(GetDeathText());
|
||||
|
||||
if (!_instance)
|
||||
return;
|
||||
|
||||
bool allDead = true;
|
||||
for (Creature* member : GetMembers())
|
||||
if (member && member->IsAlive())
|
||||
allDead = false;
|
||||
|
||||
if (allDead && _instance->GetBossState(DATA_COUNCIL_OF_TRIBES) == IN_PROGRESS)
|
||||
{
|
||||
for (Creature* member : GetMembers())
|
||||
if (member)
|
||||
_instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, member);
|
||||
_instance->SetBossState(DATA_COUNCIL_OF_TRIBES, DONE);
|
||||
// summoned totems despawn themselves once the encounter leaves IN_PROGRESS
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
InstanceScript* _instance;
|
||||
|
||||
std::vector<Creature*> GetMembers() const
|
||||
{
|
||||
std::vector<Creature*> members;
|
||||
members.reserve(3);
|
||||
if (!_instance)
|
||||
return members;
|
||||
if (Creature* member = _instance->GetCreature(DATA_AKAALI_THE_CONQUEROR))
|
||||
members.push_back(member);
|
||||
if (Creature* member = _instance->GetCreature(DATA_ZANAZAL_THE_WISE))
|
||||
members.push_back(member);
|
||||
if (Creature* member = _instance->GetCreature(DATA_KULA_THE_BUTCHER))
|
||||
members.push_back(member);
|
||||
return members;
|
||||
}
|
||||
|
||||
virtual uint8 GetDeathText() const = 0;
|
||||
};
|
||||
|
||||
// 269810 - Zanazal the Wise
|
||||
struct boss_zanazal_the_wise : public boss_council_tribes_member
|
||||
{
|
||||
boss_zanazal_the_wise(Creature* creature) : boss_council_tribes_member(creature) { }
|
||||
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
boss_council_tribes_member::JustEngagedWith(who);
|
||||
|
||||
events.ScheduleEvent(EVENT_Z_ASSIST, 2s);
|
||||
events.ScheduleEvent(EVENT_Z_ARC_LIGHTNING, 2s);
|
||||
events.ScheduleEvent(EVENT_Z_POISON_NOVA, 10s + 500ms);
|
||||
events.ScheduleEvent(EVENT_Z_CALL_ELEMENTS, 20s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_Z_ASSIST:
|
||||
DoCastSelf(SPELL_ASSIST_CALL);
|
||||
events.ScheduleEvent(EVENT_Z_ASSIST, 90s);
|
||||
break;
|
||||
case EVENT_Z_ARC_LIGHTNING:
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
DoCast(target, SPELL_ARC_LIGHTNING);
|
||||
events.ScheduleEvent(EVENT_Z_ARC_LIGHTNING, 8s + 500ms);
|
||||
break;
|
||||
case EVENT_Z_POISON_NOVA:
|
||||
Talk(SAY_ZANAZAL_POISON);
|
||||
DoCastSelf(SPELL_POISON_NOVA);
|
||||
events.ScheduleEvent(EVENT_Z_POISON_NOVA, 18s);
|
||||
break;
|
||||
case EVENT_Z_CALL_ELEMENTS:
|
||||
Talk(SAY_ZANAZAL_ELEMENTS);
|
||||
DoCastSelf(SPELL_CALL_OF_THE_ELEMENTS);
|
||||
events.ScheduleEvent(EVENT_Z_CALL_ELEMENTS, 30s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8 GetDeathText() const override
|
||||
{
|
||||
return SAY_ZANAZAL_DEATH;
|
||||
}
|
||||
};
|
||||
|
||||
// 269811 - Kula the Butcher
|
||||
struct boss_kula_the_butcher : public boss_council_tribes_member
|
||||
{
|
||||
boss_kula_the_butcher(Creature* creature) : boss_council_tribes_member(creature) { }
|
||||
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
boss_council_tribes_member::JustEngagedWith(who);
|
||||
|
||||
Talk(SAY_KULA_AGGRO);
|
||||
events.ScheduleEvent(EVENT_K_WHIRLING_AXE, 24s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_K_WHIRLING_AXE)
|
||||
{
|
||||
Talk(SAY_KULA_WHIRLING_AXE);
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true))
|
||||
DoCast(target, SPELL_WHIRLING_AXE);
|
||||
events.ScheduleEvent(EVENT_K_WHIRLING_AXE, 20s);
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8 GetDeathText() const override
|
||||
{
|
||||
return SAY_KULA_DEATH;
|
||||
}
|
||||
};
|
||||
|
||||
// 269808 - Aka'ali the Conqueror
|
||||
struct boss_akaali_the_conqueror : public boss_council_tribes_member
|
||||
{
|
||||
boss_akaali_the_conqueror(Creature* creature) : boss_council_tribes_member(creature) { }
|
||||
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
boss_council_tribes_member::JustEngagedWith(who);
|
||||
|
||||
events.ScheduleEvent(EVENT_A_ASSIST_TELEPORT, 22s);
|
||||
events.ScheduleEvent(EVENT_A_BARREL_THROUGH, 27s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_A_ASSIST_TELEPORT:
|
||||
DoCastSelf(SPELL_BERSERKER_ASSIST_TELEPORT);
|
||||
events.ScheduleEvent(EVENT_A_ASSIST_TELEPORT, 30s);
|
||||
break;
|
||||
case EVENT_A_BARREL_THROUGH:
|
||||
Talk(SAY_AKAALI_BARREL);
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true))
|
||||
DoCast(target, SPELL_BARREL_THROUGH);
|
||||
events.ScheduleEvent(EVENT_A_BARREL_THROUGH, 24s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8 GetDeathText() const override
|
||||
{
|
||||
return SAY_AKAALI_DEATH;
|
||||
}
|
||||
};
|
||||
|
||||
// 135765 - Torrent Totem
|
||||
struct npc_kings_rest_torrent_totem : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_torrent_totem(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustAppeared() override
|
||||
{
|
||||
DoCastSelf(SPELL_REINFORCED);
|
||||
DoZoneInCombat();
|
||||
_events.ScheduleEvent(EVENT_TOTEM_TORRENT, 1s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (DespawnIfEncounterEnded())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_TOTEM_TORRENT)
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
DoCast(target, SPELL_TORRENT);
|
||||
_events.ScheduleEvent(EVENT_TOTEM_TORRENT, 6s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
|
||||
bool DespawnIfEncounterEnded()
|
||||
{
|
||||
if (InstanceScript* instance = me->GetInstanceScript())
|
||||
if (instance->GetBossState(DATA_COUNCIL_OF_TRIBES) == IN_PROGRESS)
|
||||
return false;
|
||||
|
||||
me->DespawnOrUnsummon(1s);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 135761 - Thundering Totem
|
||||
struct npc_kings_rest_thundering_totem : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_thundering_totem(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustAppeared() override
|
||||
{
|
||||
DoCastSelf(SPELL_REINFORCED);
|
||||
DoZoneInCombat();
|
||||
_events.ScheduleEvent(EVENT_TOTEM_DISRUPTION, 1s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (InstanceScript* instance = me->GetInstanceScript())
|
||||
if (instance->GetBossState(DATA_COUNCIL_OF_TRIBES) != IN_PROGRESS)
|
||||
{
|
||||
me->DespawnOrUnsummon(1s);
|
||||
return;
|
||||
}
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_TOTEM_DISRUPTION)
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
DoCast(target, SPELL_DISRUPTION);
|
||||
_events.ScheduleEvent(EVENT_TOTEM_DISRUPTION, 8s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
// 135764 - Explosive Totem
|
||||
struct npc_kings_rest_explosive_totem : public ScriptedAI
|
||||
{
|
||||
npc_kings_rest_explosive_totem(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void JustAppeared() override
|
||||
{
|
||||
DoCastSelf(SPELL_REINFORCED);
|
||||
DoZoneInCombat();
|
||||
_events.ScheduleEvent(EVENT_TOTEM_EXPLODE, 9s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (InstanceScript* instance = me->GetInstanceScript())
|
||||
if (instance->GetBossState(DATA_COUNCIL_OF_TRIBES) != IN_PROGRESS)
|
||||
{
|
||||
me->DespawnOrUnsummon(1s);
|
||||
return;
|
||||
}
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
if (eventId == EVENT_TOTEM_EXPLODE)
|
||||
{
|
||||
DoCastSelf(SPELL_EXPLODE);
|
||||
me->DespawnOrUnsummon(1s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
void AddSC_boss_the_council_of_tribes()
|
||||
{
|
||||
RegisterKingsRestCreatureAI(boss_zanazal_the_wise);
|
||||
RegisterKingsRestCreatureAI(boss_kula_the_butcher);
|
||||
RegisterKingsRestCreatureAI(boss_akaali_the_conqueror);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_torrent_totem);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_thundering_totem);
|
||||
RegisterKingsRestCreatureAI(npc_kings_rest_explosive_totem);
|
||||
}
|
||||
@@ -47,11 +47,29 @@ enum KingsRestCreatureIds
|
||||
{
|
||||
// Bosses
|
||||
BOSS_GOLDEN_SERPENT = 135322,
|
||||
BOSS_AKAALI_THE_CONQUEROR = 135470,
|
||||
BOSS_ZANAZAL_THE_WISE = 135472,
|
||||
BOSS_KULA_THE_BUTCHER = 135475,
|
||||
BOSS_AKAALI_THE_CONQUEROR = 269808,
|
||||
BOSS_ZANAZAL_THE_WISE = 269810,
|
||||
BOSS_KULA_THE_BUTCHER = 269811,
|
||||
BOSS_MCHIMBA_THE_EMBALMER = 134993,
|
||||
BOSS_KING_DAZAR = 136160
|
||||
BOSS_KING_DAZAR = 136160,
|
||||
|
||||
// Mchimba the Embalmer
|
||||
NPC_INTERMENT_CONSTRUCT = 137969,
|
||||
NPC_FINISHED_MUMMY = 136264,
|
||||
NPC_HALF_FINISHED_MUMMY = 273050,
|
||||
NPC_EMBALMING_COFFIN = 136256,
|
||||
NPC_HUNTING_LEAP = 270502,
|
||||
|
||||
// Council of Tribes
|
||||
NPC_TORRENT_TOTEM = 135765,
|
||||
NPC_EXPLOSIVE_TOTEM = 135764,
|
||||
NPC_THUNDERING_TOTEM = 135761,
|
||||
|
||||
// King Dazar
|
||||
NPC_TZALA = 136976,
|
||||
NPC_REBAN = 136984,
|
||||
NPC_GOLD_POOL_STALKER = 269148,
|
||||
NPC_WAYSTONE = 247012,
|
||||
};
|
||||
|
||||
enum KingsRestGameObjectIds
|
||||
|
||||
@@ -33,6 +33,9 @@ void AddSC_boss_sporecaller_zancha();
|
||||
void AddSC_instance_kings_rest();
|
||||
void AddSC_kings_rest();
|
||||
void AddSC_boss_golden_serpent();
|
||||
void AddSC_boss_mchimba_the_embalmer();
|
||||
void AddSC_boss_the_council_of_tribes();
|
||||
void AddSC_boss_king_dazar();
|
||||
|
||||
// The name of this function should match:
|
||||
// void Add${NameOfDirectory}Scripts()
|
||||
@@ -54,4 +57,7 @@ void AddZandalarScripts()
|
||||
AddSC_instance_kings_rest();
|
||||
AddSC_kings_rest();
|
||||
AddSC_boss_golden_serpent();
|
||||
AddSC_boss_mchimba_the_embalmer();
|
||||
AddSC_boss_the_council_of_tribes();
|
||||
AddSC_boss_king_dazar();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user