aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2020-08-16 21:39:25 +0200
committerGitHub <noreply@github.com>2020-08-16 21:39:25 +0200
commit5c1fc5e3876549f5ed2b9051fffb6f3d94a67d7a (patch)
tree7f432455e2be060cb25ba8d3b971fe866a5efc6b /src
parent7edad0d601a7ae925cba850c5a23019f99be2a1e (diff)
Core/Pets: Pet management refactoring (#25191)
* Core/Pets: Pet management refactoring * Preload basic pet data on character login with async query * Load additional pet data (declined names/auras/spells/cooldowns) using async query after we are sure pet loading will succeed * Remove all select queries related to pet stable/unstable * Remove all silent pet deletions except explicit UI-triggered abandons * Fixed displaying stable master content when current pet is not summoned * Allow to stable/swap unsummoned current pet Closes #3610 Closes #21266
Diffstat (limited to 'src')
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp23
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h13
-rw-r--r--src/server/game/DataStores/DBCStores.cpp9
-rw-r--r--src/server/game/DataStores/DBCStores.h2
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp417
-rw-r--r--src/server/game/Entities/Pet/Pet.h13
-rw-r--r--src/server/game/Entities/Pet/PetDefines.h47
-rw-r--r--src/server/game/Entities/Player/Player.cpp102
-rw-r--r--src/server/game/Entities/Player/Player.h12
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp13
-rw-r--r--src/server/game/Entities/Unit/UnitDefines.h3
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp6
-rw-r--r--src/server/game/Handlers/NPCHandler.cpp337
-rw-r--r--src/server/game/Handlers/PetHandler.cpp7
-rw-r--r--src/server/game/Server/WorldSession.h4
-rw-r--r--src/server/game/Spells/Spell.cpp30
-rw-r--r--src/server/game/Spells/SpellEffects.cpp4
-rw-r--r--src/server/scripts/Commands/cs_pet.cpp35
-rw-r--r--src/server/scripts/Spells/spell_hunter.cpp20
19 files changed, 653 insertions, 444 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index ab0d938d434..bf5ea294626 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -555,20 +555,14 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE id = ?", CONNECTION_ASYNC);
// Pet
- PrepareStatement(CHAR_SEL_PET_SLOTS, "SELECT owner, slot FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC);
- PrepareStatement(CHAR_SEL_PET_SLOTS_DETAIL, "SELECT owner, id, entry, level, name FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC);
- PrepareStatement(CHAR_SEL_PET_ENTRY, "SELECT entry FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_SEL_PET_SLOT_BY_ID, "SELECT slot, entry FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_SEL_PET_SPELL_LIST, "SELECT DISTINCT pet_spell.spell FROM pet_spell, character_pet WHERE character_pet.owner = ? AND character_pet.id = pet_spell.guid AND character_pet.id <> ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET, "SELECT id FROM character_pet WHERE owner = ? AND id <> ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_CHAR_PET_IDS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
- PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience FROM pet_aura WHERE guid = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, time, categoryId, categoryEnd FROM pet_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience FROM pet_aura WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, time, categoryId, categoryEnd FROM pet_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_AURAS, "DELETE FROM pet_aura WHERE guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_DEL_PET_SPELLS, "DELETE FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_SPELL_COOLDOWNS, "DELETE FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_BOTH);
@@ -577,14 +571,9 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_BOTH);
PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, "
"base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) ", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ? AND id <> ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index 98a40671671..7404a615c61 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -480,22 +480,11 @@ enum CharacterDatabaseStatements : uint32
CHAR_DEL_PET_SPELLS,
CHAR_DEL_CHAR_PET_BY_OWNER,
CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER,
- CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT,
- CHAR_SEL_PET_SLOTS,
- CHAR_SEL_PET_SLOTS_DETAIL,
- CHAR_SEL_PET_ENTRY,
- CHAR_SEL_PET_SLOT_BY_ID,
- CHAR_SEL_PET_SPELL_LIST,
- CHAR_SEL_CHAR_PET,
+ CHAR_SEL_CHAR_PET_IDS,
CHAR_SEL_CHAR_PETS,
- CHAR_SEL_CHAR_PET_BY_ENTRY,
- CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2,
- CHAR_SEL_CHAR_PET_BY_SLOT,
CHAR_DEL_CHAR_PET_DECLINEDNAME,
CHAR_INS_CHAR_PET_DECLINEDNAME,
CHAR_UPD_CHAR_PET_NAME,
- CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID,
- CHAR_UPD_CHAR_PET_SLOT_BY_SLOT,
CHAR_UPD_CHAR_PET_SLOT_BY_ID,
CHAR_DEL_CHAR_PET_BY_ID,
CHAR_DEL_CHAR_PET_BY_SLOT,
diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp
index c0d404246b7..d62d2c2b12f 100644
--- a/src/server/game/DataStores/DBCStores.cpp
+++ b/src/server/game/DataStores/DBCStores.cpp
@@ -180,6 +180,7 @@ DBCStorage <StableSlotPricesEntry> sStableSlotPricesStore(StableSlotPricesfmt);
DBCStorage <SummonPropertiesEntry> sSummonPropertiesStore(SummonPropertiesfmt);
DBCStorage <TalentEntry> sTalentStore(TalentEntryfmt);
TalentSpellPosMap sTalentSpellPosMap;
+std::unordered_set<uint32> sPetTalentSpells;
DBCStorage <TalentTabEntry> sTalentTabStore(TalentTabEntryfmt);
// store absolute bit position for first rank for talent inspect
@@ -525,12 +526,18 @@ void LoadDBCStores(const std::string& dataPath)
// create talent spells set
for (TalentEntry const* talentInfo : sTalentStore)
{
+ TalentTabEntry const* talentTab = sTalentTabStore.LookupEntry(talentInfo->TabID);
for (uint8 j = 0; j < MAX_TALENT_RANK; ++j)
+ {
if (talentInfo->SpellRank[j])
+ {
sTalentSpellPosMap[talentInfo->SpellRank[j]] = TalentSpellPos(talentInfo->ID, j);
+ if (talentTab && talentTab->PetTalentMask)
+ sPetTalentSpells.insert(talentInfo->SpellRank[j]);
+ }
+ }
}
-
// prepare fast data access to bit pos of talent ranks for use at inspecting
{
// now have all max ranks (and then bit amount used for store talent ranks in inspect)
diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h
index aa7d1c95f32..a273460f434 100644
--- a/src/server/game/DataStores/DBCStores.h
+++ b/src/server/game/DataStores/DBCStores.h
@@ -24,6 +24,7 @@
#include <list>
#include <map>
#include <unordered_map>
+#include <unordered_set>
enum LocaleConstant : uint8;
@@ -178,6 +179,7 @@ TC_GAME_API extern DBCStorage <SpellFocusObjectEntry> sSpellFocusObjectSt
TC_GAME_API extern DBCStorage <SpellItemEnchantmentEntry> sSpellItemEnchantmentStore;
TC_GAME_API extern DBCStorage <SpellItemEnchantmentConditionEntry> sSpellItemEnchantmentConditionStore;
TC_GAME_API extern PetFamilySpellsStore sPetFamilySpellsStore;
+TC_GAME_API extern std::unordered_set<uint32> sPetTalentSpells;
TC_GAME_API extern DBCStorage <SpellRadiusEntry> sSpellRadiusStore;
TC_GAME_API extern DBCStorage <SpellRangeEntry> sSpellRangeStore;
TC_GAME_API extern DBCStorage <SpellRuneCostEntry> sSpellRuneCostStore;
diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp
index 0909e7e04f9..30df47ddb28 100644
--- a/src/server/game/Entities/Pet/Pet.cpp
+++ b/src/server/game/Entities/Pet/Pet.cpp
@@ -25,6 +25,7 @@
#include "ObjectMgr.h"
#include "PetPackets.h"
#include "Player.h"
+#include "QueryHolder.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
@@ -40,8 +41,7 @@
Pet::Pet(Player* owner, PetType type) :
Guardian(nullptr, owner, true), m_usedTalentCount(0), m_removed(false),
- m_happinessTimer(7500), m_petType(type), m_duration(0), m_auraRaidUpdateMask(0), m_loading(false),
- m_declinedname(nullptr)
+ m_happinessTimer(7500), m_petType(type), m_duration(0), m_auraRaidUpdateMask(0), m_loading(false)
{
ASSERT(GetOwner());
@@ -59,10 +59,7 @@ Pet::Pet(Player* owner, PetType type) :
m_focusRegenTimer = PET_FOCUS_REGEN_INTERVAL;
}
-Pet::~Pet()
-{
- delete m_declinedname;
-}
+Pet::~Pet() = default;
void Pet::AddToWorld()
{
@@ -100,94 +97,133 @@ void Pet::RemoveFromWorld()
}
}
-bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current)
+std::pair<PetStable::PetInfo const*, PetSaveMode> Pet::GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, bool current)
{
- m_loading = true;
-
- ObjectGuid::LowType ownerid = owner->GetGUID().GetCounter();
-
- CharacterDatabasePreparedStatement* stmt;
- PreparedQueryResult result;
-
if (petnumber)
{
// Known petnumber entry
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY);
- stmt->setUInt32(0, ownerid);
- stmt->setUInt32(1, petnumber);
+ if (stable.CurrentPet && stable.CurrentPet->PetNumber == petnumber)
+ return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
+
+ for (std::size_t stableSlot = 0; stableSlot < stable.StabledPets.size(); ++stableSlot)
+ if (stable.StabledPets[stableSlot] && stable.StabledPets[stableSlot]->PetNumber == petnumber)
+ return { &stable.StabledPets[stableSlot].value(), PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + stableSlot) };
+
+ for (PetStable::PetInfo const& pet : stable.UnslottedPets)
+ if (pet.CreatureId == petEntry)
+ return { &pet, PET_SAVE_NOT_IN_SLOT };
}
else if (current)
{
// Current pet (slot 0)
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT);
- stmt->setUInt32(0, ownerid);
- stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT));
+ if (stable.CurrentPet)
+ return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
}
else if (petEntry)
{
// known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets)
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2);
- stmt->setUInt32(0, ownerid);
- stmt->setUInt32(1, petEntry);
- stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT));
- stmt->setUInt8(3, uint8(PET_SAVE_LAST_STABLE_SLOT));
+ if (stable.CurrentPet && stable.CurrentPet->CreatureId == petEntry)
+ return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
+
+ for (PetStable::PetInfo const& pet : stable.UnslottedPets)
+ if (pet.CreatureId == petEntry)
+ return { &pet, PET_SAVE_NOT_IN_SLOT };
}
else
{
// Any current or other non-stabled pet (for hunter "call pet")
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_SLOT);
- stmt->setUInt32(0, ownerid);
- stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT));
- stmt->setUInt8(2, uint8(PET_SAVE_LAST_STABLE_SLOT));
+ if (stable.CurrentPet)
+ return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
+
+ if (!stable.UnslottedPets.empty())
+ return { &stable.UnslottedPets.front(), PET_SAVE_NOT_IN_SLOT };
}
- result = CharacterDatabase.Query(stmt);
+ return { nullptr, PET_SAVE_AS_DELETED };
+}
+
+class PetLoadQueryHolder : public CharacterDatabaseQueryHolder
+{
+public:
+ enum
+ {
+ DECLINED_NAMES,
+ AURAS,
+ SPELLS,
+ COOLDOWNS,
+
+ MAX
+ };
- if (!result)
+ PetLoadQueryHolder(ObjectGuid::LowType ownerGuid, uint32 petNumber)
{
- m_loading = false;
- return false;
+ SetSize(MAX);
+
+ CharacterDatabasePreparedStatement* stmt;
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME);
+ stmt->setUInt32(0, ownerGuid);
+ stmt->setUInt32(1, petNumber);
+ SetPreparedQuery(DECLINED_NAMES, stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA);
+ stmt->setUInt32(0, petNumber);
+ SetPreparedQuery(AURAS, stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL);
+ stmt->setUInt32(0, petNumber);
+ SetPreparedQuery(SPELLS, stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN);
+ stmt->setUInt32(0, petNumber);
+ SetPreparedQuery(COOLDOWNS, stmt);
}
+};
- Field* fields = result->Fetch();
+bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current)
+{
+ m_loading = true;
+
+ PetStable* petStable = ASSERT_NOTNULL(owner->GetPetStable());
- // update for case of current pet "slot = 0"
- petEntry = fields[1].GetUInt32();
- if (!petEntry)
+ ObjectGuid::LowType ownerid = owner->GetGUID().GetCounter();
+ std::pair<PetStable::PetInfo const*, PetSaveMode> info = GetLoadPetInfo(*petStable, petEntry, petnumber, current);
+ PetStable::PetInfo const* petInfo = info.first;
+ PetSaveMode slot = info.second;
+ if (!petInfo)
+ {
+ m_loading = false;
return false;
+ }
- uint32 summonSpellId = fields[15].GetUInt32();
- SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summonSpellId);
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(petInfo->CreatedBySpellId);
bool isTemporarySummon = spellInfo && spellInfo->GetDuration() > 0;
if (current && isTemporarySummon)
return false;
- PetType petType = PetType(fields[16].GetUInt8());
- if (petType == HUNTER_PET)
+ if (petInfo->Type == HUNTER_PET)
{
- CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry);
+ CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petInfo->CreatureId);
if (!creatureInfo || !creatureInfo->IsTameable(owner->CanTameExoticPets()))
return false;
}
- uint32 petId = fields[0].GetUInt32();
-
if (current && owner->IsPetNeedBeTemporaryUnsummoned())
{
- owner->SetTemporaryUnsummonedPetNumber(petId);
+ owner->SetTemporaryUnsummonedPetNumber(petInfo->PetNumber);
return false;
}
Map* map = owner->GetMap();
ObjectGuid::LowType guid = map->GenerateLowGuid<HighGuid::Pet>();
- if (!Create(guid, map, owner->GetPhaseMask(), petEntry, petId))
+ if (!Create(guid, map, owner->GetPhaseMask(), petInfo->CreatureId, petInfo->PetNumber))
return false;
- setPetType(petType);
+ setPetType(petInfo->Type);
SetFaction(owner->GetFaction());
- SetUInt32Value(UNIT_CREATED_BY_SPELL, summonSpellId);
+ SetUInt32Value(UNIT_CREATED_BY_SPELL, petInfo->CreatedBySpellId);
if (IsCritter())
{
@@ -206,13 +242,13 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
return true;
}
- m_charmInfo->SetPetNumber(petId, IsPermanentPetFor(owner));
+ m_charmInfo->SetPetNumber(petInfo->PetNumber, IsPermanentPetFor(owner));
- SetDisplayId(fields[3].GetUInt32());
- SetNativeDisplayId(fields[3].GetUInt32());
- uint32 petlevel = fields[4].GetUInt16();
+ SetDisplayId(petInfo->DisplayId);
+ SetNativeDisplayId(petInfo->DisplayId);
+ uint8 petlevel = petInfo->Level;
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
- SetName(fields[8].GetString());
+ SetName(petInfo->Name);
switch (getPetType())
{
@@ -227,12 +263,12 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
SetClass(CLASS_WARRIOR);
SetGender(GENDER_NONE);
SetSheath(SHEATH_STATE_MELEE);
- SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, fields[9].GetBool() ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
+ SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, petInfo->WasRenamed ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
// this enables popup window (pet abandon, cancel)
SetMaxPower(POWER_HAPPINESS, GetCreatePowerValue(POWER_HAPPINESS));
- SetPower(POWER_HAPPINESS, fields[12].GetUInt32());
+ SetPower(POWER_HAPPINESS, petInfo->Happiness);
break;
default:
if (!IsPetGhoul())
@@ -244,7 +280,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
SetCreatorGUID(owner->GetGUID());
InitStatsForLevel(petlevel);
- SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, fields[5].GetUInt32());
+ SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, petInfo->Experience);
SynchronizeLevelWithOwner();
@@ -259,15 +295,15 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
return false;
}
- SetReactState(ReactStates(fields[6].GetUInt8()));
+ SetReactState(petInfo->ReactState);
SetCanModifyStats(true);
if (getPetType() == SUMMON_PET && !current) //all (?) summon pets come with full health when called, but not when they are current
SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
else
{
- uint32 savedhealth = fields[10].GetUInt32();
- uint32 savedmana = fields[11].GetUInt32();
+ uint32 savedhealth = petInfo->Health;
+ uint32 savedmana = petInfo->Mana;
if (!savedhealth && getPetType() == HUNTER_PET)
setDeathState(JUST_DIED);
else
@@ -281,102 +317,100 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
// 0=current
// 1..MAX_PET_STABLES in stable slot
// PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning))
- if (fields[7].GetUInt8())
+ if (slot == PET_SAVE_NOT_IN_SLOT)
{
- CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
-
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID);
- stmt->setUInt8(0, uint8(PET_SAVE_NOT_IN_SLOT));
- stmt->setUInt32(1, ownerid);
- stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT));
- stmt->setUInt32(3, m_charmInfo->GetPetNumber());
- trans->Append(stmt);
-
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
- stmt->setUInt8(0, uint8(PET_SAVE_AS_CURRENT));
- stmt->setUInt32(1, ownerid);
- stmt->setUInt32(2, m_charmInfo->GetPetNumber());
- trans->Append(stmt);
+ auto unslottedPetItr = std::find_if(petStable->UnslottedPets.begin(), petStable->UnslottedPets.end(), [&](PetStable::PetInfo const& unslottedPet)
+ {
+ return unslottedPet.PetNumber == petInfo->PetNumber;
+ });
+ ASSERT(!petStable->CurrentPet);
+ ASSERT(unslottedPetItr != petStable->UnslottedPets.end());
- CharacterDatabase.CommitTransaction(trans);
+ petStable->CurrentPet = std::move(*unslottedPetItr);
+ petStable->UnslottedPets.erase(unslottedPetItr);
}
// Send fake summon spell cast - this is needed for correct cooldown application for spells
// Example: 46584 - without this cooldown (which should be set always when pet is loaded) isn't set clientside
/// @todo pets should be summoned from real cast instead of just faking it?
- if (summonSpellId)
+ if (petInfo->CreatedBySpellId)
{
WorldPacket data(SMSG_SPELL_GO, (8+8+4+4+2));
data << owner->GetPackGUID();
data << owner->GetPackGUID();
data << uint8(0);
- data << uint32(summonSpellId);
+ data << uint32(petInfo->CreatedBySpellId);
data << uint32(256); // CAST_FLAG_UNKNOWN3
data << uint32(0);
owner->SendMessageToSet(&data, true);
}
owner->SetMinion(this, true);
- map->AddToMap(ToCreature());
- InitTalentForLevel(); // set original talents points before spell loading
+ if (!isTemporarySummon)
+ m_charmInfo->LoadPetActionBar(petInfo->ActionBar);
- uint32 timediff = uint32(GameTime::GetGameTime() - fields[14].GetUInt32());
- _LoadAuras(timediff);
+ map->AddToMap(ToCreature());
- // load action bar, if data broken will fill later by default spells.
- if (!isTemporarySummon)
+ //set last used pet number (for use in BG's)
+ if (owner->GetTypeId() == TYPEID_PLAYER && isControlled() && !isTemporarySummoned() && (getPetType() == SUMMON_PET || getPetType() == HUNTER_PET))
+ owner->ToPlayer()->SetLastPetNumber(petInfo->PetNumber);
+
+ owner->GetSession()->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(new PetLoadQueryHolder(ownerid, petInfo->PetNumber)))
+ .AfterComplete([this, owner, session = owner->GetSession(), isTemporarySummon, current, lastSaveTime = petInfo->LastSaveTime](SQLQueryHolderBase* holder)
{
- m_charmInfo->LoadPetActionBar(fields[13].GetString());
+ if (session->GetPlayer() != owner || owner->GetPet() != this)
+ return;
- _LoadSpells();
- InitTalentForLevel(); // re-init to check talent count
- _LoadSpellCooldowns();
- LearnPetPassives();
- InitLevelupSpellsForLevel();
- if (map->IsBattleArena())
- RemoveArenaAuras();
+ // passing previous checks ensure that 'this' is still valid
+ if (m_removed)
+ return;
- CastPetAuras(current);
- }
+ InitTalentForLevel(); // set original talents points before spell loading
- CleanupActionBar(); // remove unknown spells from action bar after load
+ uint32 timediff = uint32(GameTime::GetGameTime() - lastSaveTime);
+ _LoadAuras(holder->GetPreparedResult(PetLoadQueryHolder::AURAS), timediff);
- TC_LOG_DEBUG("entities.pet", "New Pet has %s", GetGUID().ToString().c_str());
+ // load action bar, if data broken will fill later by default spells.
+ if (!isTemporarySummon)
+ {
+ _LoadSpells(holder->GetPreparedResult(PetLoadQueryHolder::SPELLS));
+ InitTalentForLevel(); // re-init to check talent count
+ GetSpellHistory()->LoadFromDB<Pet>(holder->GetPreparedResult(PetLoadQueryHolder::COOLDOWNS));
+ LearnPetPassives();
+ InitLevelupSpellsForLevel();
+ if (GetMap()->IsBattleArena())
+ RemoveArenaAuras();
+
+ CastPetAuras(current);
+ }
- owner->PetSpellInitialize();
+ CleanupActionBar(); // remove unknown spells from action bar after load
- if (owner->GetGroup())
- owner->SetGroupUpdateFlag(GROUP_UPDATE_PET);
+ TC_LOG_DEBUG("entities.pet", "New Pet has %s", GetGUID().ToString().c_str());
- owner->SendTalentsInfoData(true);
+ owner->PetSpellInitialize();
- if (getPetType() == HUNTER_PET)
- {
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME);
- stmt->setUInt32(0, owner->GetGUID().GetCounter());
- stmt->setUInt32(1, GetCharmInfo()->GetPetNumber());
- result = CharacterDatabase.Query(stmt);
+ if (owner->GetGroup())
+ owner->SetGroupUpdateFlag(GROUP_UPDATE_PET);
- if (result)
+ owner->SendTalentsInfoData(true);
+
+ if (getPetType() == HUNTER_PET)
{
- delete m_declinedname;
- m_declinedname = new DeclinedName;
- Field* fields2 = result->Fetch();
- for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
+ if (PreparedQueryResult result = holder->GetPreparedResult(PetLoadQueryHolder::DECLINED_NAMES))
{
- m_declinedname->name[i] = fields2[i].GetString();
+ m_declinedname = std::make_unique<DeclinedName>();
+ Field* fields = result->Fetch();
+ for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
+ m_declinedname->name[i] = fields[i].GetString();
}
}
- }
- //set last used pet number (for use in BG's)
- if (owner->GetTypeId() == TYPEID_PLAYER && isControlled() && !isTemporarySummoned() && (getPetType() == SUMMON_PET || getPetType() == HUNTER_PET))
- owner->ToPlayer()->SetLastPetNumber(petId);
-
- // must be after SetMinion (owner guid check)
- LoadTemplateImmunities();
- m_loading = false;
+ // must be after SetMinion (owner guid check)
+ LoadTemplateImmunities();
+ m_loading = false;
+ });
return true;
}
@@ -434,16 +468,6 @@ void Pet::SavePetToDB(PetSaveMode mode)
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
trans->Append(stmt);
- // prevent duplicate using slot (except PET_SAVE_NOT_IN_SLOT)
- if (mode <= PET_SAVE_LAST_STABLE_SLOT)
- {
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT);
- stmt->setUInt8(0, uint8(PET_SAVE_NOT_IN_SLOT));
- stmt->setUInt32(1, ownerLowGUID);
- stmt->setUInt8(2, uint8(mode));
- trans->Append(stmt);
- }
-
// prevent existence another hunter pet in PET_SAVE_AS_CURRENT and PET_SAVE_NOT_IN_SLOT
if (getPetType() == HUNTER_PET && (mode == PET_SAVE_AS_CURRENT || mode > PET_SAVE_LAST_STABLE_SLOT))
{
@@ -455,6 +479,11 @@ void Pet::SavePetToDB(PetSaveMode mode)
}
// save pet
+ std::string actionBar = GenerateActionBarData();
+
+ ASSERT(owner->GetPetStable()->CurrentPet && owner->GetPetStable()->CurrentPet->PetNumber == m_charmInfo->GetPetNumber());
+ FillPetInfo(&owner->GetPetStable()->CurrentPet.value());
+
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
stmt->setUInt32(1, GetEntry());
@@ -469,7 +498,7 @@ void Pet::SavePetToDB(PetSaveMode mode)
stmt->setUInt32(10, curhealth);
stmt->setUInt32(11, curmana);
stmt->setUInt32(12, GetPower(POWER_HAPPINESS));
- stmt->setString(13, GenerateActionBarData());
+ stmt->setString(13, actionBar);
stmt->setUInt32(14, GameTime::GetGameTime());
stmt->setUInt32(15, GetUInt32Value(UNIT_CREATED_BY_SPELL));
stmt->setUInt8(16, getPetType());
@@ -485,6 +514,25 @@ void Pet::SavePetToDB(PetSaveMode mode)
}
}
+void Pet::FillPetInfo(PetStable::PetInfo* petInfo) const
+{
+ petInfo->PetNumber = m_charmInfo->GetPetNumber();
+ petInfo->CreatureId = GetEntry();
+ petInfo->DisplayId = GetNativeDisplayId();
+ petInfo->Level = GetLevel();
+ petInfo->Experience = GetUInt32Value(UNIT_FIELD_PETEXPERIENCE);
+ petInfo->ReactState = GetReactState();
+ petInfo->Name = GetName();
+ petInfo->WasRenamed = !HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED);
+ petInfo->Health = GetHealth();
+ petInfo->Mana = GetPower(POWER_MANA);
+ petInfo->Happiness = GetPower(POWER_HAPPINESS);
+ petInfo->ActionBar = GenerateActionBarData();
+ petInfo->LastSaveTime = GameTime::GetGameTime();
+ petInfo->CreatedBySpellId = GetUInt32Value(UNIT_CREATED_BY_SPELL);
+ petInfo->Type = getPetType();
+}
+
void Pet::DeleteFromDB(ObjectGuid::LowType guidlow)
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
@@ -572,7 +620,8 @@ void Pet::Update(uint32 diff)
if (owner->GetPetGUID() != GetGUID())
{
TC_LOG_ERROR("entities.pet", "Pet %u is not pet of owner %s, removed", GetEntry(), GetOwner()->GetName().c_str());
- Remove(getPetType() == HUNTER_PET?PET_SAVE_AS_DELETED:PET_SAVE_NOT_IN_SLOT);
+ ASSERT(getPetType() != HUNTER_PET, "Unexpected unlinked pet found for owner %s", owner->GetSession()->GetPlayerInfo().c_str());
+ Remove(PET_SAVE_NOT_IN_SLOT);
return;
}
}
@@ -583,7 +632,7 @@ void Pet::Update(uint32 diff)
m_duration -= diff;
else
{
- Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED:PET_SAVE_NOT_IN_SLOT);
+ Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT);
return;
}
}
@@ -1098,21 +1147,8 @@ uint32 Pet::GetCurrentFoodBenefitLevel(uint32 itemlevel) const
return 0; //food too low level
}
-void Pet::_LoadSpellCooldowns()
+void Pet::_LoadSpells(PreparedQueryResult result)
{
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN);
- stmt->setUInt32(0, m_charmInfo->GetPetNumber());
- PreparedQueryResult cooldownsResult = CharacterDatabase.Query(stmt);
-
- GetSpellHistory()->LoadFromDB<Pet>(cooldownsResult);
-}
-
-void Pet::_LoadSpells()
-{
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL);
- stmt->setUInt32(0, m_charmInfo->GetPetNumber());
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
-
if (result)
{
do
@@ -1174,14 +1210,10 @@ void Pet::_SaveSpells(CharacterDatabaseTransaction& trans)
}
}
-void Pet::_LoadAuras(uint32 timediff)
+void Pet::_LoadAuras(PreparedQueryResult result, uint32 timediff)
{
TC_LOG_DEBUG("entities.pet", "Loading auras for pet %s", GetGUID().ToString().c_str());
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA);
- stmt->setUInt32(0, m_charmInfo->GetPetNumber());
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
-
if (result)
{
do
@@ -1381,10 +1413,9 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel
{
if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id))
{
- for (uint8 i = 0; i < MAX_TALENT_RANK; ++i)
+ for (uint32 rankSpellId : talentInfo->SpellRank)
{
// skip learning spell and no rank spell case
- uint32 rankSpellId = talentInfo->SpellRank[i];
if (!rankSpellId || rankSpellId == spellId)
continue;
@@ -1488,9 +1519,9 @@ void Pet::InitLevelupSpellsForLevel()
// default spells (can be not learned if pet level (as owner level decrease result for example) less first possible in normal game)
if (PetDefaultSpellsEntry const* defSpells = sSpellMgr->GetPetDefaultSpellsEntry(petSpellsId))
{
- for (uint8 i = 0; i < MAX_CREATURE_SPELL_DATA_SLOT; ++i)
+ for (uint32 spellId : defSpells->spellid)
{
- SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(defSpells->spellid[i]);
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
continue;
@@ -1633,7 +1664,7 @@ bool Pet::resetTalents()
if (!((1 << pet_family->PetTalentType) & talentTabInfo->PetTalentMask))
continue;
- for (uint8 j = 0; j < MAX_TALENT_RANK; ++j)
+ for (uint32 talentSpellId : talentInfo->SpellRank)
{
for (PetSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end();)
{
@@ -1646,7 +1677,7 @@ bool Pet::resetTalents()
uint32 itrFirstId = sSpellMgr->GetFirstSpellInChain(itr->first);
// unlearn if first rank is talent or learned by talent
- if (itrFirstId == talentInfo->SpellRank[j] || sSpellMgr->IsSpellLearnToSpell(talentInfo->SpellRank[j], itrFirstId))
+ if (itrFirstId == talentSpellId || sSpellMgr->IsSpellLearnToSpell(talentSpellId, itrFirstId))
{
unlearnSpell(itr->first, false);
itr = m_spells.begin();
@@ -1675,67 +1706,55 @@ void Pet::resetTalentsForAllPetsOf(Player* owner, Pet* onlinePet /*= nullptr*/)
if (onlinePet)
onlinePet->resetTalents();
- // now need only reset for offline pets (all pets except online case)
- uint32 exceptPetNumber = onlinePet ? onlinePet->GetCharmInfo()->GetPetNumber() : 0;
+ PetStable* petStable = owner->GetPetStable();
+ if (!petStable)
+ return;
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET);
- stmt->setUInt32(0, owner->GetGUID().GetCounter());
- stmt->setUInt32(1, exceptPetNumber);
- PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
+ std::unordered_set<uint32> petIds;
+ if (petStable->CurrentPet)
+ petIds.insert(petStable->CurrentPet->PetNumber);
- // no offline pets
- if (!resultPets)
- return;
+ for (Optional<PetStable::PetInfo> const& stabledPet : petStable->StabledPets)
+ if (stabledPet)
+ petIds.insert(stabledPet->PetNumber);
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_LIST);
- stmt->setUInt32(0, owner->GetGUID().GetCounter());
- stmt->setUInt32(1, exceptPetNumber);
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
+ for (PetStable::PetInfo const& unslottedPet : petStable->UnslottedPets)
+ petIds.insert(unslottedPet.PetNumber);
- if (!result)
+ // now need only reset for offline pets (all pets except online case)
+ if (onlinePet)
+ petIds.erase(onlinePet->GetCharmInfo()->GetPetNumber());
+
+ // no offline pets
+ if (!petIds.empty())
return;
bool need_comma = false;
std::ostringstream ss;
ss << "DELETE FROM pet_spell WHERE guid IN (";
- do
+ for (uint32 id : petIds)
{
- Field* fields = resultPets->Fetch();
-
- uint32 id = fields[0].GetUInt32();
-
if (need_comma)
ss << ',';
ss << id;
need_comma = true;
- } while (resultPets->NextRow());
+ }
ss << ") AND spell IN (";
- bool need_execute = false;
- do
+ need_comma = false;
+ for (uint32 spell : sPetTalentSpells)
{
- Field* fields = result->Fetch();
-
- uint32 spell = fields[0].GetUInt32();
-
- if (!GetTalentSpellCost(spell))
- continue;
-
- if (need_execute)
+ if (need_comma)
ss << ',';
ss << spell;
- need_execute = true;
+ need_comma = true;
}
- while (result->NextRow());
-
- if (!need_execute)
- return;
ss << ')';
@@ -1874,8 +1893,8 @@ void Pet::LearnPetPassives()
// For general hunter pets skill 270
// Passive 01~10, Passive 00 (20782, not used), Ferocious Inspiration (34457)
// Scale 01~03 (34902~34904, bonus from owner, not used)
- for (PetFamilySpellsSet::const_iterator petSet = petStore->second.begin(); petSet != petStore->second.end(); ++petSet)
- addSpell(*petSet, ACT_DECIDE, PETSPELL_NEW, PETSPELL_FAMILY);
+ for (uint32 spellId : petStore->second)
+ addSpell(spellId, ACT_DECIDE, PETSPELL_NEW, PETSPELL_FAMILY);
}
}
@@ -1918,11 +1937,10 @@ bool Pet::IsPetAura(Aura const* aura)
Player* owner = GetOwner();
// if the owner has that pet aura, return true
- for (auto itr = owner->m_petAuras.begin(); itr != owner->m_petAuras.end(); ++itr)
- {
- if ((*itr)->GetAura(GetEntry()) == aura->GetId())
+ for (PetAura const* petAura : owner->m_petAuras)
+ if (petAura->GetAura(GetEntry()) == aura->GetId())
return true;
- }
+
return false;
}
@@ -2009,6 +2027,7 @@ std::string Pet::GetDebugInfo() const
std::stringstream sstr;
sstr << Guardian::GetDebugInfo() << "\n"
<< std::boolalpha
- << "PetType: " << std::to_string(getPetType());
+ << "PetType: " << std::to_string(getPetType())
+ << "PetNumber: " << m_charmInfo->GetPetNumber();
return sstr.str();
}
diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h
index 93cfe6a7db4..a17b2644798 100644
--- a/src/server/game/Entities/Pet/Pet.h
+++ b/src/server/game/Entities/Pet/Pet.h
@@ -59,9 +59,11 @@ class TC_GAME_API Pet : public Guardian
bool CreateBaseAtCreature(Creature* creature);
bool CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner);
bool CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map, uint32 phaseMask);
- bool LoadPetFromDB(Player* owner, uint32 petentry = 0, uint32 petnumber = 0, bool current = false);
+ static std::pair<PetStable::PetInfo const*, PetSaveMode> GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, bool current);
+ bool LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current);
bool IsLoading() const override { return m_loading;}
void SavePetToDB(PetSaveMode mode);
+ void FillPetInfo(PetStable::PetInfo* petInfo) const;
void Remove(PetSaveMode mode, bool returnreagent = false);
static void DeleteFromDB(ObjectGuid::LowType guidlow);
@@ -107,10 +109,9 @@ class TC_GAME_API Pet : public Guardian
void CastPetAura(PetAura const* aura);
bool IsPetAura(Aura const* aura);
- void _LoadSpellCooldowns();
- void _LoadAuras(uint32 timediff);
+ void _LoadAuras(PreparedQueryResult result, uint32 timediff);
void _SaveAuras(CharacterDatabaseTransaction& trans);
- void _LoadSpells();
+ void _LoadSpells(PreparedQueryResult result);
void _SaveSpells(CharacterDatabaseTransaction& trans);
bool addSpell(uint32 spellId, ActiveStates active = ACT_DECIDE, PetSpellState state = PETSPELL_NEW, PetSpellType type = PETSPELL_NORMAL);
@@ -141,7 +142,7 @@ class TC_GAME_API Pet : public Guardian
void SetAuraUpdateMaskForRaid(uint8 slot) { m_auraRaidUpdateMask |= (uint64(1) << slot); }
void ResetAuraUpdateMaskForRaid() { m_auraRaidUpdateMask = 0; }
- DeclinedName const* GetDeclinedNames() const { return m_declinedname; }
+ DeclinedName const* GetDeclinedNames() const { return m_declinedname.get(); }
bool m_removed; // prevent overwrite pet state in DB at next Pet::Update if pet already removed(saved)
@@ -157,7 +158,7 @@ class TC_GAME_API Pet : public Guardian
bool m_loading;
uint32 m_focusRegenTimer;
- DeclinedName *m_declinedname;
+ std::unique_ptr<DeclinedName> m_declinedname;
private:
void SaveToDB(uint32, uint8, uint32) override // override of Creature::SaveToDB - must not be called
diff --git a/src/server/game/Entities/Pet/PetDefines.h b/src/server/game/Entities/Pet/PetDefines.h
index ad9f56bac50..9f4683ef283 100644
--- a/src/server/game/Entities/Pet/PetDefines.h
+++ b/src/server/game/Entities/Pet/PetDefines.h
@@ -18,7 +18,15 @@
#ifndef TRINITYCORE_PET_DEFINES_H
#define TRINITYCORE_PET_DEFINES_H
-enum PetType
+#include "Define.h"
+#include "Optional.h"
+#include <array>
+#include <string>
+#include <vector>
+
+enum ReactStates : uint8;
+
+enum PetType : uint8
{
SUMMON_PET = 0,
HUNTER_PET = 1,
@@ -28,7 +36,7 @@ enum PetType
#define MAX_PET_STABLES 4
// stored in character_pet.slot
-enum PetSaveMode
+enum PetSaveMode : int8
{
PET_SAVE_AS_DELETED = -1, // not saved in fact
PET_SAVE_AS_CURRENT = 0, // in current slot (with player)
@@ -76,4 +84,39 @@ enum PetTalk
#define PET_FOLLOW_DIST 1.0f
#define PET_FOLLOW_ANGLE float(M_PI/2)
+class PetStable
+{
+public:
+ struct PetInfo
+ {
+ PetInfo() { }
+
+ std::string Name;
+ std::string ActionBar;
+ uint32 PetNumber = 0;
+ uint32 CreatureId = 0;
+ uint32 DisplayId = 0;
+ uint32 Experience = 0;
+ uint32 Health = 0;
+ uint32 Mana = 0;
+ uint32 Happiness = 0;
+ uint32 LastSaveTime = 0;
+ uint32 CreatedBySpellId = 0;
+ uint8 Level = 0;
+ ReactStates ReactState = ReactStates(0);
+ PetType Type = MAX_PET_TYPE;
+ bool WasRenamed = false;
+ };
+
+ Optional<PetInfo> CurrentPet; // PET_SAVE_AS_CURRENT
+ std::array<Optional<PetInfo>, MAX_PET_STABLES> StabledPets; // PET_SAVE_FIRST_STABLE_SLOT - PET_SAVE_LAST_STABLE_SLOT
+ uint32 MaxStabledPets = 0;
+ std::vector<PetInfo> UnslottedPets; // PET_SAVE_NOT_IN_SLOT
+
+ PetInfo const* GetUnslottedHunterPet() const
+ {
+ return UnslottedPets.size() == 1 && UnslottedPets[0].Type == HUNTER_PET ? &UnslottedPets[0] : nullptr;
+ }
+};
+
#endif
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index aaf028a7355..005d58ea90c 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -308,8 +308,6 @@ Player::Player(WorldSession* session): Unit(true)
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
m_forced_speed_changes[i] = 0;
- m_stableSlots = 0;
-
/////////////////// Instance System /////////////////////
m_HomebindTimer = 0;
@@ -4213,7 +4211,7 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe
// Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet.
// NOW we can finally clear other DB data related to character
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_IDS);
stmt->setUInt32(0, guid);
PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
@@ -17671,13 +17669,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder* holder)
uint32 extraflags = fields[36].GetUInt16();
- m_stableSlots = fields[37].GetUInt8();
- if (m_stableSlots > MAX_PET_STABLES)
- {
- TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) can't have more stable slots than %u, but has %u in DB",
- GetGUID().ToString().c_str(), MAX_PET_STABLES, uint32(m_stableSlots));
- m_stableSlots = MAX_PET_STABLES;
- }
+ _LoadPetStable(fields[37].GetUInt8(), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS));
m_atLoginFlags = fields[38].GetUInt16();
@@ -18495,7 +18487,7 @@ void Player::LoadPet()
{
//fixme: the pet should still be loaded if the player is not in world
// just not added to the map
- if (IsInWorld())
+ if (m_petStable && IsInWorld())
{
Pet* pet = new Pet(this);
if (!pet->LoadPetFromDB(this, 0, 0, true))
@@ -19394,7 +19386,7 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create /* = false
stmt->setUInt32(index++, m_resetTalentsCost);
stmt->setUInt32(index++, uint32(m_resetTalentsTime));
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
- stmt->setUInt8(index++, m_stableSlots);
+ stmt->setUInt8(index++, m_petStable ? m_petStable->MaxStabledPets : 0);
stmt->setUInt16(index++, (uint16)m_atLoginFlags);
stmt->setUInt16(index++, GetZoneId());
stmt->setUInt32(index++, uint32(m_deathExpireTime));
@@ -19519,7 +19511,7 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create /* = false
stmt->setUInt32(index++, m_resetTalentsCost);
stmt->setUInt32(index++, uint32(m_resetTalentsTime));
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
- stmt->setUInt8(index++, m_stableSlots);
+ stmt->setUInt8(index++, m_petStable ? m_petStable->MaxStabledPets : 0);
stmt->setUInt16(index++, (uint16)m_atLoginFlags);
stmt->setUInt16(index++, GetZoneId());
stmt->setUInt32(index++, uint32(m_deathExpireTime));
@@ -20699,6 +20691,17 @@ void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent)
// only if current pet in slot
pet->SavePetToDB(mode);
+ ASSERT(m_petStable->CurrentPet && m_petStable->CurrentPet->PetNumber == pet->GetCharmInfo()->GetPetNumber());
+ if (mode == PET_SAVE_NOT_IN_SLOT)
+ {
+ m_petStable->UnslottedPets.push_back(std::move(*m_petStable->CurrentPet));
+ m_petStable->CurrentPet.reset();
+ }
+ else if (mode == PET_SAVE_AS_DELETED)
+ m_petStable->CurrentPet.reset();
+ // else if (stable slots) handled in opcode handlers due to required swaps
+ // else (current pet) doesnt need to do anything
+
SetMinion(pet, false);
pet->AddObjectToRemoveList();
@@ -26315,6 +26318,14 @@ bool Player::AddItem(uint32 itemId, uint32 count)
return true;
}
+PetStable& Player::GetOrInitPetStable()
+{
+ if (!m_petStable)
+ m_petStable = std::make_unique<PetStable>();
+
+ return *m_petStable;
+}
+
void Player::RefundItem(Item* item)
{
if (!item->IsRefundable())
@@ -26499,6 +26510,55 @@ void Player::_LoadInstanceTimeRestrictions(PreparedQueryResult result)
} while (result->NextRow());
}
+void Player::_LoadPetStable(uint8 petStableSlots, PreparedQueryResult result)
+{
+ if (!petStableSlots && !result)
+ return;
+
+ m_petStable = std::make_unique<PetStable>();
+ m_petStable->MaxStabledPets = petStableSlots;
+ if (m_petStable->MaxStabledPets > MAX_PET_STABLES)
+ {
+ TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) can't have more stable slots than %u, but has %u in DB",
+ GetGUID().ToString().c_str(), MAX_PET_STABLES, m_petStable->MaxStabledPets);
+ m_petStable->MaxStabledPets = MAX_PET_STABLES;
+ }
+
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ // SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ?
+ if (result)
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ PetStable::PetInfo petInfo;
+ petInfo.PetNumber = fields[0].GetUInt32();
+ petInfo.CreatureId = fields[1].GetUInt32();
+ petInfo.DisplayId = fields[2].GetUInt32();
+ petInfo.Level = fields[3].GetUInt16();
+ petInfo.Experience = fields[4].GetUInt32();
+ petInfo.ReactState = ReactStates(fields[5].GetUInt8());
+ PetSaveMode slot = PetSaveMode(fields[6].GetUInt8());
+ petInfo.Name = fields[7].GetString();
+ petInfo.WasRenamed = fields[8].GetBool();
+ petInfo.Health = fields[9].GetUInt32();
+ petInfo.Mana = fields[10].GetUInt32();
+ petInfo.Happiness = fields[11].GetUInt32();
+ petInfo.ActionBar = fields[12].GetString();
+ petInfo.LastSaveTime = fields[13].GetUInt32();
+ petInfo.CreatedBySpellId = fields[14].GetUInt32();
+ petInfo.Type = PetType(fields[15].GetUInt8());
+ if (slot == PET_SAVE_AS_CURRENT)
+ m_petStable->CurrentPet = std::move(petInfo);
+ else if (slot >= PET_SAVE_FIRST_STABLE_SLOT && slot <= PET_SAVE_LAST_STABLE_SLOT)
+ m_petStable->StabledPets[slot - 1] = std::move(petInfo);
+ else if (slot == PET_SAVE_NOT_IN_SLOT)
+ m_petStable->UnslottedPets.push_back(std::move(petInfo));
+
+ } while (result->NextRow());
+ }
+}
+
void Player::_SaveInstanceTimeRestrictions(CharacterDatabaseTransaction& trans)
{
if (_instanceResetTimes.empty())
@@ -26654,19 +26714,14 @@ Guild* Player::GetGuild()
return guildId ? sGuildMgr->GetGuildById(guildId) : nullptr;
}
-Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 duration, bool aliveOnly)
+Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 duration)
{
+ PetStable& petStable = GetOrInitPetStable();
+
Pet* pet = new Pet(this, petType);
- if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry))
+ if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry, 0, false))
{
- if (aliveOnly && !pet->IsAlive())
- {
- pet->DespawnOrUnsummon();
- SendTameFailure(PETTAME_DEAD);
- return nullptr;
- }
-
// Remove Demonic Sacrifice auras (known pet)
Unit::AuraEffectList const& auraClassScripts = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();)
@@ -26745,6 +26800,9 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy
map->AddToMap(pet->ToCreature());
+ ASSERT(!petStable.CurrentPet && (petType != HUNTER_PET || !petStable.GetUnslottedHunterPet()));
+ pet->FillPetInfo(&petStable.CurrentPet.emplace());
+
switch (petType)
{
case SUMMON_PET:
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 1c3978a8367..4595cdb8f66 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -733,6 +733,7 @@ enum PlayerLoginQueryIndex
PLAYER_LOGIN_QUERY_LOAD_SEASONAL_QUEST_STATUS = 31,
PLAYER_LOGIN_QUERY_LOAD_MONTHLY_QUEST_STATUS = 32,
PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION = 33,
+ PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS = 34,
MAX_PLAYER_LOGIN_QUERY
};
@@ -1013,8 +1014,12 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 GetXPRestBonus(uint32 xp);
uint32 GetInnTriggerId() const { return inn_triggerId; }
+ PetStable* GetPetStable() { return m_petStable.get(); }
+ PetStable& GetOrInitPetStable();
+ PetStable const* GetPetStable() const { return m_petStable.get(); }
+
Pet* GetPet() const;
- Pet* SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 despwtime, bool aliveOnly = false);
+ Pet* SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 despwtime);
void RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent = false);
uint32 GetPhaseMaskForSpawn() const; // used for proper set phase for DB at GM-mode creature/GO spawn
@@ -1179,8 +1184,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool AddItem(uint32 itemId, uint32 count);
- uint32 m_stableSlots;
-
/*********************************************************/
/*** GOSSIP SYSTEM ***/
/*********************************************************/
@@ -2245,6 +2248,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void _LoadGlyphs(PreparedQueryResult result);
void _LoadTalents(PreparedQueryResult result);
void _LoadInstanceTimeRestrictions(PreparedQueryResult result);
+ void _LoadPetStable(uint8 petStableSlots, PreparedQueryResult result);
/*********************************************************/
/*** SAVE SYSTEM ***/
@@ -2478,6 +2482,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool m_bCanDelayTeleport;
bool m_bHasDelayedTeleport;
+ std::unique_ptr<PetStable> m_petStable;
+
// Temporary removed pet cache
uint32 m_temporaryUnsummonedPetNumber;
uint32 m_oldpetspell;
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 5d71cd12499..81c169be596 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -10770,7 +10770,11 @@ Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id)
uint8 level = creatureTarget->GetLevel() + 5 < GetLevel() ? (GetLevel() - 5) : creatureTarget->GetLevel();
- InitTamedPet(pet, level, spell_id);
+ if (!InitTamedPet(pet, level, spell_id))
+ {
+ delete pet;
+ return nullptr;
+ }
return pet;
}
@@ -10797,6 +10801,11 @@ Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id)
bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id)
{
+ Player* player = ToPlayer();
+ PetStable& petStable = player->GetOrInitPetStable();
+ if (petStable.CurrentPet || petStable.GetUnslottedHunterPet())
+ return false;
+
pet->SetCreatorGUID(GetGUID());
pet->SetFaction(GetFaction());
pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, spell_id);
@@ -10815,6 +10824,8 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id)
pet->InitPetCreateSpells();
//pet->InitLevelupSpellsForLevel();
pet->SetFullHealth();
+
+ pet->FillPetInfo(&petStable.CurrentPet.emplace());
return true;
}
diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h
index 0c420304f77..c6115217f1d 100644
--- a/src/server/game/Entities/Unit/UnitDefines.h
+++ b/src/server/game/Entities/Unit/UnitDefines.h
@@ -334,12 +334,13 @@ enum ActiveStates
ACT_DECIDE = 0x00 // custom
};
-enum ReactStates
+enum ReactStates : uint8
{
REACT_PASSIVE = 0,
REACT_DEFENSIVE = 1,
REACT_AGGRESSIVE = 2
};
+
inline char const* DescribeReactState(ReactStates state)
{
switch (state)
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index e5d8ae3e7ca..5a56cb150e0 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -208,9 +208,13 @@ bool LoginQueryHolder::Initialize()
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_LOCATION);
- stmt->setUInt64(0, lowGuid);
+ stmt->setUInt32(0, lowGuid);
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS);
+ stmt->setUInt32(0, lowGuid);
+ res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, stmt);
+
return res;
}
diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp
index 97781beabc5..a82a5f184af 100644
--- a/src/server/game/Handlers/NPCHandler.cpp
+++ b/src/server/game/Handlers/NPCHandler.cpp
@@ -301,66 +301,66 @@ void WorldSession::HandleRequestStabledPets(WorldPacket& recvData)
void WorldSession::SendStablePet(ObjectGuid guid)
{
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS_DETAIL);
-
- stmt->setUInt32(0, _player->GetGUID().GetCounter());
- stmt->setUInt8(1, PET_SAVE_FIRST_STABLE_SLOT);
- stmt->setUInt8(2, PET_SAVE_LAST_STABLE_SLOT);
-
- _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::SendStablePetCallback, this, guid, std::placeholders::_1)));
-}
-
-void WorldSession::SendStablePetCallback(ObjectGuid guid, PreparedQueryResult result)
-{
- if (!GetPlayer())
- return;
-
TC_LOG_DEBUG("network", "WORLD: Recv MSG_LIST_STABLED_PETS Send.");
WorldPacket data(MSG_LIST_STABLED_PETS, 200); // guess size
data << uint64(guid);
- Pet* pet = _player->GetPet();
-
size_t wpos = data.wpos();
data << uint8(0); // place holder for slot show number
- data << uint8(GetPlayer()->m_stableSlots);
+ PetStable* petStable = GetPlayer()->GetPetStable();
+ if (!petStable)
+ {
+ data << uint8(0); // stable slots
+ SendPacket(&data);
+ return;
+ }
+
+ data << uint8(petStable->MaxStabledPets);
uint8 num = 0; // counter for place holder
- // not let move dead pet in slot
- if (pet && pet->IsAlive() && pet->getPetType() == HUNTER_PET)
+ if (petStable->CurrentPet)
{
- data << uint32(pet->GetCharmInfo()->GetPetNumber());
- data << uint32(pet->GetEntry());
- data << uint32(pet->GetLevel());
- data << pet->GetName(); // petname
- data << uint8(1); // 1 = current, 2/3 = in stable (any from 4, 5, ... create problems with proper show)
+ PetStable::PetInfo const& pet = *petStable->CurrentPet;
+ data << uint32(pet.PetNumber);
+ data << uint32(pet.CreatureId);
+ data << uint32(pet.Level);
+ data << pet.Name; // petname
+ data << uint8(1); // flags: 1 active, 2 inactive
++num;
}
-
- if (result)
+ else
{
- do
+ if (PetStable::PetInfo const* pet = petStable->GetUnslottedHunterPet())
{
- Field* fields = result->Fetch();
-
- data << uint32(fields[1].GetUInt32()); // petnumber
- data << uint32(fields[2].GetUInt32()); // creature entry
- data << uint32(fields[3].GetUInt16()); // level
- data << fields[4].GetString(); // name
- data << uint8(2); // 1 = current, 2/3 = in stable (any from 4, 5, ... create problems with proper show)
+ data << uint32(pet->PetNumber);
+ data << uint32(pet->CreatureId);
+ data << uint32(pet->Level);
+ data << pet->Name; // petname
+ data << uint8(1); // flags: 1 active, 2 inactive
+ ++num;
+ }
+ }
+ for (Optional<PetStable::PetInfo> const& stabledSlot : petStable->StabledPets)
+ {
+ if (stabledSlot)
+ {
+ PetStable::PetInfo const& pet = *stabledSlot;
+ data << uint32(pet.PetNumber);
+ data << uint32(pet.CreatureId);
+ data << uint32(pet.Level);
+ data << pet.Name; // petname
+ data << uint8(2); // flags: 1 active, 2 inactive
++num;
}
- while (result->NextRow());
}
data.put<uint8>(wpos, num); // set real data to placeholder
SendPacket(&data);
-
}
void WorldSession::SendPetStableResult(uint8 res)
@@ -393,56 +393,49 @@ void WorldSession::HandleStablePet(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
+ PetStable* petStable = GetPlayer()->GetPetStable();
+ if (!petStable)
+ return;
+
Pet* pet = _player->GetPet();
// can't place in stable dead pet
- if (!pet || !pet->IsAlive() || pet->getPetType() != HUNTER_PET)
+ if ((pet && (!pet->IsAlive() || pet->getPetType() != HUNTER_PET))
+ || (!pet && (petStable->UnslottedPets.size() != 1 || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET)))
{
SendPetStableResult(STABLE_ERR_STABLE);
return;
}
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS);
-
- stmt->setUInt32(0, _player->GetGUID().GetCounter());
- stmt->setUInt8(1, PET_SAVE_FIRST_STABLE_SLOT);
- stmt->setUInt8(2, PET_SAVE_LAST_STABLE_SLOT);
-
- _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleStablePetCallback, this, std::placeholders::_1)));
-}
-
-void WorldSession::HandleStablePetCallback(PreparedQueryResult result)
-{
- if (!GetPlayer())
- return;
-
- uint8 freeSlot = 1;
- if (result)
+ for (uint32 freeSlot = 0; freeSlot < petStable->MaxStabledPets; ++freeSlot)
{
- do
+ if (!petStable->StabledPets[freeSlot])
{
- Field* fields = result->Fetch();
-
- uint8 slot = fields[1].GetUInt8();
-
- // slots ordered in query, and if not equal then free
- if (slot != freeSlot)
- break;
-
- // this slot not free, skip
- ++freeSlot;
+ if (pet)
+ {
+ // stable summoned pet
+ _player->RemovePet(pet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + freeSlot));
+ std::swap(petStable->StabledPets[freeSlot], petStable->CurrentPet);
+ SendPetStableResult(STABLE_SUCCESS_STABLE);
+ return;
+ }
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, freeSlot);
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petStable->UnslottedPets[0].PetNumber);
+ CharacterDatabase.Execute(stmt);
+
+ // stable unsummoned pet
+ petStable->StabledPets[freeSlot] = std::move(petStable->UnslottedPets.back());
+ petStable->UnslottedPets.pop_back();
+ SendPetStableResult(STABLE_SUCCESS_STABLE);
+ return;
}
- while (result->NextRow());
}
- WorldPacket data(SMSG_STABLE_RESULT, 1);
- if (freeSlot > 0 && freeSlot <= GetPlayer()->m_stableSlots)
- {
- _player->RemovePet(_player->GetPet(), PetSaveMode(freeSlot));
- SendPetStableResult(STABLE_SUCCESS_STABLE);
- }
- else
- SendPetStableResult(STABLE_ERR_STABLE);
+ // not free stable slot
+ SendPetStableResult(STABLE_ERR_STABLE);
}
void WorldSession::HandleUnstablePet(WorldPacket& recvData)
@@ -463,35 +456,25 @@ void WorldSession::HandleUnstablePet(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_ENTRY);
-
- stmt->setUInt32(0, _player->GetGUID().GetCounter());
- stmt->setUInt32(1, petnumber);
- stmt->setUInt8(2, PET_SAVE_FIRST_STABLE_SLOT);
- stmt->setUInt8(3, PET_SAVE_LAST_STABLE_SLOT);
-
- _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleUnstablePetCallback, this, petnumber, std::placeholders::_1)));
-}
-
-void WorldSession::HandleUnstablePetCallback(uint32 petId, PreparedQueryResult result)
-{
- if (!GetPlayer())
+ PetStable* petStable = GetPlayer()->GetPetStable();
+ if (!petStable)
+ {
+ SendPetStableResult(STABLE_ERR_STABLE);
return;
+ }
- uint32 petEntry = 0;
- if (result)
+ auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional<PetStable::PetInfo> const& pet)
{
- Field* fields = result->Fetch();
- petEntry = fields[0].GetUInt32();
- }
+ return pet && pet->PetNumber == petnumber;
+ });
- if (!petEntry)
+ if (stabledPet == petStable->StabledPets.end())
{
SendPetStableResult(STABLE_ERR_STABLE);
return;
}
- CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry);
+ CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId);
if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets()))
{
// if problem in exotic pet
@@ -502,27 +485,72 @@ void WorldSession::HandleUnstablePetCallback(uint32 petId, PreparedQueryResult r
return;
}
- Pet* pet = _player->GetPet();
- if (pet && pet->IsAlive())
+ Pet* oldPet = _player->GetPet();
+ if (oldPet)
+ {
+ // try performing a swap, client sends this packet instead of swap when starting from stabled slot
+ if (!oldPet->IsAlive() || !oldPet->IsHunterPet())
+ {
+ SendPetStableResult(STABLE_ERR_STABLE);
+ return;
+ }
+
+ _player->RemovePet(oldPet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet)));
+ }
+ else if (petStable->UnslottedPets.size() == 1)
+ {
+ if (petStable->CurrentPet || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET)
+ {
+ SendPetStableResult(STABLE_ERR_STABLE);
+ return;
+ }
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet)));
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petStable->UnslottedPets[0].PetNumber);
+ CharacterDatabase.Execute(stmt);
+
+ // move unsummoned pet into CurrentPet slot so that it gets moved into stable slot later
+ petStable->CurrentPet = std::move(petStable->UnslottedPets.back());
+ petStable->UnslottedPets.pop_back();
+ }
+ else if (petStable->CurrentPet || !petStable->UnslottedPets.empty())
{
SendPetStableResult(STABLE_ERR_STABLE);
return;
}
- // delete dead pet
- if (pet)
- _player->RemovePet(pet, PET_SAVE_AS_DELETED);
-
Pet* newPet = new Pet(_player, HUNTER_PET);
- if (!newPet->LoadPetFromDB(_player, petEntry, petId))
+ if (!newPet->LoadPetFromDB(_player, 0, petnumber, false))
{
delete newPet;
- newPet = nullptr;
+
+ petStable->UnslottedPets.push_back(std::move(*petStable->CurrentPet));
+ petStable->CurrentPet.reset();
+
+ // update current pet slot in db immediately to maintain slot consistency, dismissed pet was already saved
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, PET_SAVE_NOT_IN_SLOT);
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petnumber);
+ CharacterDatabase.Execute(stmt);
+
SendPetStableResult(STABLE_ERR_STABLE);
- return;
}
+ else
+ {
+ std::swap(*stabledPet, petStable->CurrentPet);
- SendPetStableResult(STABLE_SUCCESS_UNSTABLE);
+ // update current pet slot in db immediately to maintain slot consistency, dismissed pet was already saved
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, PET_SAVE_AS_CURRENT);
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petnumber);
+ CharacterDatabase.Execute(stmt);
+
+ SendPetStableResult(STABLE_SUCCESS_UNSTABLE);
+ }
}
void WorldSession::HandleBuyStableSlot(WorldPacket& recvData)
@@ -542,12 +570,13 @@ void WorldSession::HandleBuyStableSlot(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- if (GetPlayer()->m_stableSlots < MAX_PET_STABLES)
+ PetStable& petStable = GetPlayer()->GetOrInitPetStable();
+ if (petStable.MaxStabledPets < MAX_PET_STABLES)
{
- StableSlotPricesEntry const* SlotPrice = sStableSlotPricesStore.LookupEntry(GetPlayer()->m_stableSlots+1);
+ StableSlotPricesEntry const* SlotPrice = sStableSlotPricesStore.LookupEntry(petStable.MaxStabledPets + 1);
if (_player->HasEnoughMoney(SlotPrice->Cost))
{
- ++GetPlayer()->m_stableSlots;
+ ++petStable.MaxStabledPets;
_player->ModifyMoney(-int32(SlotPrice->Cost));
SendPetStableResult(STABLE_SUCCESS_BUY_SLOT);
}
@@ -581,47 +610,26 @@ void WorldSession::HandleStableSwapPet(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- Pet* pet = _player->GetPet();
-
- if (!pet || pet->getPetType() != HUNTER_PET)
+ PetStable* petStable = GetPlayer()->GetPetStable();
+ if (!petStable)
{
SendPetStableResult(STABLE_ERR_STABLE);
return;
}
// Find swapped pet slot in stable
-
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOT_BY_ID);
-
- stmt->setUInt32(0, _player->GetGUID().GetCounter());
- stmt->setUInt32(1, petId);
-
- _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleStableSwapPetCallback, this, petId, std::placeholders::_1)));
-}
-
-void WorldSession::HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result)
-{
- if (!GetPlayer())
- return;
-
- if (!result)
+ auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petId](Optional<PetStable::PetInfo> const& pet)
{
- SendPetStableResult(STABLE_ERR_STABLE);
- return;
- }
-
- Field* fields = result->Fetch();
-
- uint32 slot = fields[0].GetUInt8();
- uint32 petEntry = fields[1].GetUInt32();
+ return pet && pet->PetNumber == petId;
+ });
- if (!petEntry)
+ if (stabledPet == petStable->StabledPets.end())
{
SendPetStableResult(STABLE_ERR_STABLE);
return;
}
- CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry);
+ CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId);
if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets()))
{
// if problem in exotic pet
@@ -632,26 +640,71 @@ void WorldSession::HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult
return;
}
- Pet* pet = _player->GetPet();
- // The player's pet could have been removed during the delay of the DB callback
- if (!pet)
+ Pet* oldPet = _player->GetPet();
+ if (oldPet)
+ {
+ if (!oldPet->IsAlive() || !oldPet->IsHunterPet())
+ {
+ SendPetStableResult(STABLE_ERR_STABLE);
+ return;
+ }
+
+ _player->RemovePet(oldPet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet)));
+ }
+ else if (petStable->UnslottedPets.size() == 1)
+ {
+ if (petStable->CurrentPet || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET)
+ {
+ SendPetStableResult(STABLE_ERR_STABLE);
+ return;
+ }
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet)));
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petStable->UnslottedPets[0].PetNumber);
+ CharacterDatabase.Execute(stmt);
+
+ // move unsummoned pet into CurrentPet slot so that it gets moved into stable slot later
+ petStable->CurrentPet = std::move(petStable->UnslottedPets.back());
+ petStable->UnslottedPets.pop_back();
+ }
+ else if (petStable->CurrentPet || !petStable->UnslottedPets.empty())
{
SendPetStableResult(STABLE_ERR_STABLE);
return;
}
- // move alive pet to slot or delete dead pet
- _player->RemovePet(pet, pet->IsAlive() ? PetSaveMode(slot) : PET_SAVE_AS_DELETED);
-
// summon unstabled pet
- Pet* newPet = new Pet(_player);
- if (!newPet->LoadPetFromDB(_player, petEntry, petId))
+ Pet* newPet = new Pet(_player, HUNTER_PET);
+ if (!newPet->LoadPetFromDB(_player, 0, petId, false))
{
delete newPet;
SendPetStableResult(STABLE_ERR_STABLE);
+
+ petStable->UnslottedPets.push_back(std::move(*petStable->CurrentPet));
+ petStable->CurrentPet.reset();
+
+ // update current pet slot in db immediately to maintain slot consistency, dismissed pet was already saved
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, PET_SAVE_NOT_IN_SLOT);
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petId);
+ CharacterDatabase.Execute(stmt);
}
else
+ {
+ std::swap(*stabledPet, petStable->CurrentPet);
+
+ // update current pet slot in db immediately to maintain slot consistency, dismissed pet was already saved
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->setUInt8(0, PET_SAVE_AS_CURRENT);
+ stmt->setUInt32(1, _player->GetGUID().GetCounter());
+ stmt->setUInt32(2, petId);
+ CharacterDatabase.Execute(stmt);
+
SendPetStableResult(STABLE_SUCCESS_UNSTABLE);
+ }
}
void WorldSession::HandleRepairItemOpcode(WorldPacket& recvData)
diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp
index 56d12210529..de4f5f13078 100644
--- a/src/server/game/Handlers/PetHandler.cpp
+++ b/src/server/game/Handlers/PetHandler.cpp
@@ -596,9 +596,11 @@ void WorldSession::HandlePetRename(WorldPacket& recvData)
recvData >> name;
recvData >> isdeclined;
+ PetStable* petStable = _player->GetPetStable();
Pet* pet = ObjectAccessor::GetPet(*_player, petguid);
if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET || !pet->HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) ||
- pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo())
+ pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() ||
+ !petStable || !petStable->CurrentPet || petStable->CurrentPet->PetNumber != pet->GetCharmInfo()->GetPetNumber())
return;
PetNameInvalidReason res = ObjectMgr::CheckPetName(name, GetSessionDbcLocale());
@@ -621,6 +623,9 @@ void WorldSession::HandlePetRename(WorldPacket& recvData)
pet->RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED);
+ petStable->CurrentPet->Name = name;
+ petStable->CurrentPet->WasRenamed = true;
+
if (isdeclined)
{
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 33e863b15c5..9c132c39e0d 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -492,7 +492,6 @@ class TC_GAME_API WorldSession
// Pet
void SendQueryPetNameResponse(ObjectGuid guid, uint32 petnumber);
void SendStablePet(ObjectGuid guid);
- void SendStablePetCallback(ObjectGuid guid, PreparedQueryResult result);
void SendPetStableResult(uint8 guid);
bool CheckStableMaster(ObjectGuid guid);
@@ -780,13 +779,10 @@ class TC_GAME_API WorldSession
void HandleBinderActivateOpcode(WorldPacket& recvPacket);
void HandleRequestStabledPets(WorldPacket& recvPacket);
void HandleStablePet(WorldPacket& recvPacket);
- void HandleStablePetCallback(PreparedQueryResult result);
void HandleUnstablePet(WorldPacket& recvPacket);
- void HandleUnstablePetCallback(uint32 petId, PreparedQueryResult result);
void HandleBuyStableSlot(WorldPacket& recvPacket);
void HandleStableRevivePet(WorldPacket& recvPacket);
void HandleStableSwapPet(WorldPacket& recvPacket);
- void HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result);
void HandleDuelAcceptedOpcode(WorldPacket& recvPacket);
void HandleDuelCancelledOpcode(WorldPacket& recvPacket);
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 6fcbc5b75a5..85d3def3648 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -5758,6 +5758,36 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint
if (unitCaster->GetCharmedGUID())
return SPELL_FAILED_ALREADY_HAVE_CHARM;
+
+ Player* playerCaster = unitCaster->ToPlayer();
+ if (playerCaster && playerCaster->GetPetStable())
+ {
+ std::pair<PetStable::PetInfo const*, PetSaveMode> info = Pet::GetLoadPetInfo(*playerCaster->GetPetStable(), m_spellInfo->Effects[i].MiscValue, 0, false);
+ if (!info.first)
+ {
+ playerCaster->SendTameFailure(PETTAME_NOPETAVAILABLE);
+ return SPELL_FAILED_DONT_REPORT;
+ }
+
+ if (info.first->Type == HUNTER_PET && !info.first->Health)
+ {
+ playerCaster->SendTameFailure(PETTAME_DEAD);
+ return SPELL_FAILED_DONT_REPORT;
+ }
+
+ CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(info.first->CreatureId);
+ if (!creatureInfo || !creatureInfo->IsTameable(playerCaster->CanTameExoticPets()))
+ {
+ // if problem in exotic pet
+ if (creatureInfo && creatureInfo->IsTameable(true))
+ playerCaster->SendTameFailure(PETTAME_CANTCONTROLEXOTIC);
+ else
+ playerCaster->SendTameFailure(PETTAME_NOPETAVAILABLE);
+
+ return SPELL_FAILED_DONT_REPORT;
+ }
+ }
+
break;
}
case SPELL_EFFECT_SUMMON_PLAYER:
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 3c064f6344f..99813bfd327 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -2887,14 +2887,14 @@ void Spell::EffectSummonPet(SpellEffIndex effIndex)
}
if (owner->GetTypeId() == TYPEID_PLAYER)
- owner->ToPlayer()->RemovePet(OldSummon, (OldSummon->getPetType() == HUNTER_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT), false);
+ owner->ToPlayer()->RemovePet(OldSummon, PET_SAVE_NOT_IN_SLOT, false);
else
return;
}
float x, y, z;
owner->GetClosePoint(x, y, z, owner->GetCombatReach());
- Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), SUMMON_PET, 0, true);
+ Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), SUMMON_PET, 0);
if (!pet)
return;
diff --git a/src/server/scripts/Commands/cs_pet.cpp b/src/server/scripts/Commands/cs_pet.cpp
index c1fae85621b..417eaebfffa 100644
--- a/src/server/scripts/Commands/cs_pet.cpp
+++ b/src/server/scripts/Commands/cs_pet.cpp
@@ -91,42 +91,27 @@ public:
}
// Everything looks OK, create new pet
- Pet* pet = new Pet(player, HUNTER_PET);
- if (!pet->CreateBaseAtCreature(creatureTarget))
- {
- delete pet;
- handler->PSendSysMessage("Error 1");
- return false;
- }
+ Pet* pet = player->CreateTamedPetFrom(creatureTarget);
+ // "kill" original creature
creatureTarget->DespawnOrUnsummon();
- creatureTarget->SetHealth(0); // just for nice GM-mode view
- pet->SetGuidValue(UNIT_FIELD_CREATEDBY, player->GetGUID());
- pet->SetFaction(player->GetFaction());
-
- if (!pet->InitStatsForLevel(creatureTarget->GetLevel()))
- {
- TC_LOG_ERROR("misc", "InitStatsForLevel() in EffectTameCreature failed! Pet deleted.");
- handler->PSendSysMessage("Error 2");
- delete pet;
- return false;
- }
+ uint8 level = (creatureTarget->GetLevel() < (player->GetLevel() - 5)) ? (player->GetLevel() - 5) : player->GetLevel();
// prepare visual effect for levelup
- pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->GetLevel()-1);
-
- pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true);
- // this enables pet details window (Shift+P)
- pet->InitPetCreateSpells();
- pet->SetFullHealth();
+ pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1);
+ // add to world
pet->GetMap()->AddToMap(pet->ToCreature());
// visual effect for levelup
- pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->GetLevel());
+ pet->SetUInt32Value(UNIT_FIELD_LEVEL, level);
+ // caster have pet now
player->SetMinion(pet, true);
+
+ pet->InitTalentForLevel();
+
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
player->PetSpellInitialize();
diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp
index e1278a77828..48abad416b7 100644
--- a/src/server/scripts/Spells/spell_hunter.cpp
+++ b/src/server/scripts/Spells/spell_hunter.cpp
@@ -1415,8 +1415,8 @@ class spell_hun_tame_beast : public SpellScriptLoader
SpellCastResult CheckCast()
{
- Unit* caster = GetCaster();
- if (caster->GetTypeId() != TYPEID_PLAYER)
+ Player* caster = GetCaster()->ToPlayer();
+ if (!caster)
return SPELL_FAILED_DONT_REPORT;
if (!GetExplTargetUnit())
@@ -1428,11 +1428,21 @@ class spell_hun_tame_beast : public SpellScriptLoader
return SPELL_FAILED_HIGHLEVEL;
// use SMSG_PET_TAME_FAILURE?
- if (!target->GetCreatureTemplate()->IsTameable(caster->ToPlayer()->CanTameExoticPets()))
+ if (!target->GetCreatureTemplate()->IsTameable(caster->CanTameExoticPets()))
return SPELL_FAILED_BAD_TARGETS;
- if (caster->GetPetGUID())
- return SPELL_FAILED_ALREADY_HAVE_SUMMON;
+ PetStable const* petStable = caster->GetPetStable();
+ if (petStable)
+ {
+ if (petStable->CurrentPet)
+ return SPELL_FAILED_ALREADY_HAVE_SUMMON;
+
+ if (petStable->GetUnslottedHunterPet())
+ {
+ caster->SendTameFailure(PETTAME_TOOMANY);
+ return SPELL_FAILED_DONT_REPORT;
+ }
+ }
if (caster->GetCharmedGUID())
return SPELL_FAILED_ALREADY_HAVE_CHARM;