From ca92686b44cc8c53a8991bf02d6e8534797fc115 Mon Sep 17 00:00:00 2001 From: Shauren Date: Sun, 16 Aug 2020 21:39:25 +0200 Subject: 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 (cherry picked from commit 5c1fc5e3876549f5ed2b9051fffb6f3d94a67d7a) --- .../Database/Implementation/CharacterDatabase.cpp | 27 +- .../Database/Implementation/CharacterDatabase.h | 13 +- src/server/game/Entities/Pet/Pet.cpp | 411 +++++++++++---------- src/server/game/Entities/Pet/Pet.h | 19 +- src/server/game/Entities/Pet/PetDefines.h | 45 ++- src/server/game/Entities/Player/Player.cpp | 104 ++++-- src/server/game/Entities/Player/Player.h | 12 +- src/server/game/Entities/Unit/Unit.cpp | 13 +- src/server/game/Entities/Unit/UnitDefines.h | 2 +- src/server/game/Handlers/CharacterHandler.cpp | 4 + src/server/game/Handlers/NPCHandler.cpp | 340 +++++++++-------- src/server/game/Handlers/PetHandler.cpp | 7 +- src/server/game/Server/WorldSession.h | 4 - src/server/game/Spells/Spell.cpp | 30 ++ src/server/game/Spells/SpellEffects.cpp | 4 +- src/server/scripts/Commands/cs_pet.cpp | 31 +- src/server/scripts/Spells/spell_hunter.cpp | 20 +- 17 files changed, 648 insertions(+), 438 deletions(-) (limited to 'src') diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 6003983276b..37b59e6cc3a 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -704,27 +704,21 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE InviteID = ?", 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, modelid 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, difficulty, stackCount, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_PET_AURA_EFFECT, "SELECT casterGuid, spell, effectMask, effectIndex, amount, baseAmount FROM pet_aura_effect 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, difficulty, stackCount, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_PET_AURA_EFFECT, "SELECT casterGuid, spell, effectMask, effectIndex, amount, baseAmount FROM pet_aura_effect 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_AURA_EFFECTS, "DELETE FROM pet_aura_effect 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); PrepareStatement(CHAR_INS_PET_SPELL_COOLDOWN, "INSERT INTO pet_spell_cooldown (guid, spell, time, categoryId, categoryEnd) VALUES (?, ?, ?, ?, ?)", CONNECTION_BOTH); - PrepareStatement(CHAR_SEL_PET_SPELL_CHARGES, "SELECT categoryId, rechargeStart, rechargeEnd FROM pet_spell_charges WHERE guid = ? AND rechargeEnd > UNIX_TIMESTAMP() ORDER BY rechargeEnd", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_PET_SPELL_CHARGES, "SELECT categoryId, rechargeStart, rechargeEnd FROM pet_spell_charges WHERE guid = ? AND rechargeEnd > UNIX_TIMESTAMP() ORDER BY rechargeEnd", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PET_SPELL_CHARGES, "DELETE FROM pet_spell_charges WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PET_SPELL_CHARGES, "INSERT INTO pet_spell_charges (guid, categoryId, rechargeStart, rechargeEnd) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PET_SPELL_BY_SPELL, "DELETE FROM pet_spell WHERE guid = ? and spell = ?", CONNECTION_ASYNC); @@ -733,14 +727,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH); PrepareStatement(CHAR_INS_PET_AURA_EFFECT, "INSERT INTO pet_aura_effect (guid, casterGuid, spell, effectMask, effectIndex, amount, baseAmount) " "VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization 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, abdata, savetime, CreatedBySpell, PetType, specialization 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, abdata, savetime, CreatedBySpell, PetType, specialization 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, abdata, savetime, CreatedBySpell, PetType, specialization 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, abdata, savetime, CreatedBySpell, PetType, specialization 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 b83ed38531d..5fe79e83112 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -592,22 +592,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/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 25029227159..489d7c96aa6 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -27,6 +27,7 @@ #include "PetPackets.h" #include "PhasingHandler.h" #include "Player.h" +#include "QueryHolder.h" #include "Spell.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" @@ -44,7 +45,7 @@ Pet::Pet(Player* owner, PetType type) : Guardian(nullptr, owner, true), m_removed(false), m_petType(type), m_duration(0), m_loading(false), m_groupUpdateMask(0), - m_declinedname(nullptr), m_petSpecialization(0) + m_petSpecialization(0) { ASSERT(GetOwner()); @@ -62,10 +63,7 @@ Pet::Pet(Player* owner, PetType type) : m_focusRegenTimer = PET_FOCUS_REGEN_INTERVAL; } -Pet::~Pet() -{ - delete m_declinedname; -} +Pet::~Pet() = default; void Pet::AddToWorld() { @@ -103,94 +101,145 @@ void Pet::RemoveFromWorld() } } -bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current) +std::pair 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->setUInt64(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->setUInt64(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->setUInt64(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->setUInt64(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, + AURA_EFFECTS, + SPELLS, + COOLDOWNS, + CHARGES, + + 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->setUInt64(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_AURA_EFFECT); + stmt->setUInt32(0, petNumber); + SetPreparedQuery(AURA_EFFECTS, 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); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_CHARGES); + stmt->setUInt32(0, petNumber); + SetPreparedQuery(CHARGES, 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 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[14].GetUInt32(); - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summonSpellId, owner->GetMap()->GetDifficultyID()); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(petInfo->CreatedBySpellId, owner->GetMap()->GetDifficultyID()); bool isTemporarySummon = spellInfo && spellInfo->GetDuration() > 0; if (current && isTemporarySummon) return false; - PetType petType = PetType(fields[15].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(); - if (!Create(map->GenerateLowGuid(), map, petEntry)) + ObjectGuid::LowType guid = map->GenerateLowGuid(); + + if (!Create(guid, map, petInfo->CreatureId, petInfo->PetNumber)) return false; PhasingHandler::InheritPhaseShift(this, owner); - setPetType(petType); + setPetType(petInfo->Type); SetFaction(owner->GetFaction()); - SetCreatedBySpell(summonSpellId); + SetCreatedBySpell(petInfo->CreatedBySpellId); if (IsCritter()) { @@ -209,14 +258,14 @@ 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; SetNpcFlags(UNIT_NPC_FLAG_NONE); SetNpcFlags2(UNIT_NPC_FLAG_2_NONE); - SetName(fields[8].GetString()); + SetName(petInfo->Name); switch (getPetType()) { @@ -229,7 +278,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c SetClass(CLASS_WARRIOR); SetGender(GENDER_NONE); SetSheath(SHEATH_STATE_MELEE); - SetPetFlags(fields[9].GetBool() ? UNIT_PET_FLAG_CAN_BE_ABANDONED : UnitPetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED | UNIT_PET_FLAG_CAN_BE_ABANDONED)); + SetPetFlags(petInfo->WasRenamed ? UNIT_PET_FLAG_CAN_BE_ABANDONED : UnitPetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED | UNIT_PET_FLAG_CAN_BE_ABANDONED)); SetUnitFlags(UNIT_FLAG_PLAYER_CONTROLLED); // this enables popup window (pet abandon, cancel) break; default: @@ -242,7 +291,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c SetCreatorGUID(owner->GetGUID()); InitStatsForLevel(petlevel); - SetPetExperience(fields[5].GetUInt32()); + SetPetExperience(petInfo->Experience); SynchronizeLevelWithOwner(); @@ -257,15 +306,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 SetFullPower(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 @@ -279,38 +328,31 @@ 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->setUInt64(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->setUInt64(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) { WorldPackets::Spells::SpellGo spellGo; WorldPackets::Spells::SpellCastData& castData = spellGo.Cast; castData.CasterGUID = owner->GetGUID(); castData.CasterUnit = owner->GetGUID(); - castData.CastID = ObjectGuid::Create(SPELL_CAST_SOURCE_NORMAL, summonSpellId, map->GenerateLowGuid()); - castData.SpellID = summonSpellId; + castData.CastID = ObjectGuid::Create(SPELL_CAST_SOURCE_NORMAL, petInfo->CreatedBySpellId, map->GenerateLowGuid()); + castData.SpellID = petInfo->CreatedBySpellId; castData.CastFlags = CAST_FLAG_UNKNOWN_9; castData.CastTime = getMSTime(); @@ -318,69 +360,75 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c } owner->SetMinion(this, true); + + if (!isTemporarySummon) + m_charmInfo->LoadPetActionBar(petInfo->ActionBar); + map->AddToMap(ToCreature()); - uint32 timediff = uint32(GameTime::GetGameTime() - fields[13].GetUInt32()); - _LoadAuras(timediff); + //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); - // load action bar, if data broken will fill later by default spells. - if (!isTemporarySummon) + owner->GetSession()->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(new PetLoadQueryHolder(ownerid, petInfo->PetNumber))) + .AfterComplete([this, owner, session = owner->GetSession(), isTemporarySummon, current, lastSaveTime = petInfo->LastSaveTime, specializationId = petInfo->SpecializationId](SQLQueryHolderBase* holder) { - m_charmInfo->LoadPetActionBar(fields[12].GetString()); + if (session->GetPlayer() != owner || owner->GetPet() != this) + return; - _LoadSpells(); - _LoadSpellCooldowns(); - LearnPetPassives(); - InitLevelupSpellsForLevel(); - if (map->IsBattleArena()) - RemoveArenaAuras(); + // passing previous checks ensure that 'this' is still valid + if (m_removed) + return; - CastPetAuras(current); - } + uint32 timediff = uint32(GameTime::GetGameTime() - lastSaveTime); + _LoadAuras(holder->GetPreparedResult(PetLoadQueryHolder::AURAS), holder->GetPreparedResult(PetLoadQueryHolder::AURA_EFFECTS), 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)); + GetSpellHistory()->LoadFromDB(holder->GetPreparedResult(PetLoadQueryHolder::COOLDOWNS), holder->GetPreparedResult(PetLoadQueryHolder::CHARGES)); + LearnPetPassives(); + InitLevelupSpellsForLevel(); + if (GetMap()->IsBattleArena()) + RemoveArenaAuras(); + + CastPetAuras(current); + } - uint16 specId = fields[16].GetUInt16(); - if (ChrSpecializationEntry const* petSpec = sChrSpecializationStore.LookupEntry(specId)) - specId = sDB2Manager.GetChrSpecializationByIndex(owner->HasAuraType(SPELL_AURA_OVERRIDE_PET_SPECS) ? PET_SPEC_OVERRIDE_CLASS_INDEX : 0, petSpec->OrderIndex)->ID; + TC_LOG_DEBUG("entities.pet", "New Pet has %s", GetGUID().ToString().c_str()); - SetSpecialization(specId); + uint16 specId = specializationId; + if (ChrSpecializationEntry const* petSpec = sChrSpecializationStore.LookupEntry(specId)) + specId = sDB2Manager.GetChrSpecializationByIndex(owner->HasAuraType(SPELL_AURA_OVERRIDE_PET_SPECS) ? PET_SPEC_OVERRIDE_CLASS_INDEX : 0, petSpec->OrderIndex)->ID; - // The SetSpecialization function will run these functions if the pet's spec is not 0 - if (!GetSpecialization()) - { - CleanupActionBar(); // remove unknown spells from action bar after load - owner->PetSpellInitialize(); - } + SetSpecialization(specId); - SetGroupUpdateFlag(GROUP_UPDATE_PET_FULL); + // The SetSpecialization function will run these functions if the pet's spec is not 0 + if (!GetSpecialization()) + { + CleanupActionBar(); // remove unknown spells from action bar after load - if (getPetType() == HUNTER_PET) - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME); - stmt->setUInt64(0, owner->GetGUID().GetCounter()); - stmt->setUInt32(1, GetCharmInfo()->GetPetNumber()); - result = CharacterDatabase.Query(stmt); + owner->PetSpellInitialize(); + } + + SetGroupUpdateFlag(GROUP_UPDATE_PET_FULL); - if (result) + 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(); + 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; } @@ -438,16 +486,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->setUInt64(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)) { @@ -459,6 +497,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()); @@ -472,13 +515,11 @@ void Pet::SavePetToDB(PetSaveMode mode) stmt->setUInt8(9, HasPetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED) ? 0 : 1); stmt->setUInt32(10, curhealth); stmt->setUInt32(11, curmana); - - stmt->setString(12, GenerateActionBarData()); - + stmt->setString(12, actionBar); stmt->setUInt32(13, GameTime::GetGameTime()); stmt->setUInt32(14, m_unitData->CreatedBySpell); stmt->setUInt8(15, getPetType()); - stmt->setUInt16(16, m_petSpecialization); + stmt->setUInt16(16, GetSpecialization()); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); @@ -491,36 +532,55 @@ void Pet::SavePetToDB(PetSaveMode mode) } } -void Pet::DeleteFromDB(uint32 guidlow) +void Pet::FillPetInfo(PetStable::PetInfo* petInfo) const +{ + petInfo->PetNumber = m_charmInfo->GetPetNumber(); + petInfo->CreatureId = GetEntry(); + petInfo->DisplayId = GetNativeDisplayId(); + petInfo->Level = GetLevel(); + petInfo->Experience = m_unitData->PetExperience; + petInfo->ReactState = GetReactState(); + petInfo->Name = GetName(); + petInfo->WasRenamed = !HasPetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED); + petInfo->Health = GetHealth(); + petInfo->Mana = GetPower(POWER_MANA); + petInfo->ActionBar = GenerateActionBarData(); + petInfo->LastSaveTime = GameTime::GetGameTime(); + petInfo->CreatedBySpellId = m_unitData->CreatedBySpell; + petInfo->Type = getPetType(); + petInfo->SpecializationId = GetSpecialization(); +} + +void Pet::DeleteFromDB(uint32 petNumber) { CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURA_EFFECTS); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURAS); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELLS); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_CHARGES); - stmt->setUInt32(0, guidlow); + stmt->setUInt32(0, petNumber); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); @@ -582,7 +642,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; } } @@ -593,7 +654,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; } } @@ -744,7 +805,7 @@ bool Pet::CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner) bool Pet::CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map) { TC_LOG_DEBUG("entities.pet", "Pet::CreateBaseForTamed"); - if (!Create(map->GenerateLowGuid(), map, cinfo->Entry)) + if (!Create(map->GenerateLowGuid(), map, cinfo->Entry, sObjectMgr->GeneratePetNumber())) return false; SetPetNameTimestamp(0); @@ -1042,25 +1103,8 @@ bool Pet::HaveInDiet(ItemTemplate const* item) const return (diet & FoodMask) != 0; } -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); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_CHARGES); - stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - PreparedQueryResult chargesResult = CharacterDatabase.Query(stmt); - - GetSpellHistory()->LoadFromDB(cooldownsResult, chargesResult); -} - -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 @@ -1122,21 +1166,18 @@ void Pet::_SaveSpells(CharacterDatabaseTransaction& trans) } } -void Pet::_LoadAuras(uint32 timediff) +void Pet::_LoadAuras(PreparedQueryResult auraResult, PreparedQueryResult effectResult, uint32 timediff) { TC_LOG_DEBUG("entities.pet", "Loading auras for pet %s", GetGUID().ToString().c_str()); + ObjectGuid casterGuid, itemGuid; + std::map effectInfo; + /* 0 1 2 3 4 5 SELECT casterGuid, spell, effectMask, effectIndex, amount, baseAmount FROM pet_aura_effect WHERE guid = ? */ - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA_EFFECT); - stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - - ObjectGuid casterGuid, itemGuid; - std::map effectInfo; - if (PreparedQueryResult effectResult = CharacterDatabase.Query(stmt)) + if (effectResult) { do { @@ -1156,14 +1197,11 @@ void Pet::_LoadAuras(uint32 timediff) } while (effectResult->NextRow()); } - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA); - stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - /* 0 1 2 3 4 5 6 7 8 SELECT casterGuid, spell, effectMask, recalculateMask, difficulty, stackCount, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ? */ - if (PreparedQueryResult auraResult = CharacterDatabase.Query(stmt)) + if (auraResult) { do { @@ -1453,9 +1491,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(int32(GetEntry()))) { - for (uint8 i = 0; i < MAX_CREATURE_SPELL_DATA_SLOT; ++i) + for (uint32 spellId : defSpells->spellid) { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(defSpells->spellid[i], DIFFICULTY_NONE); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); if (!spellInfo) continue; @@ -1627,11 +1665,12 @@ bool Pet::IsPermanentPetFor(Player* owner) const } } -bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry) +bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry, uint32 /*petId*/) { ASSERT(map); SetMap(map); + // TODO: counter should be constructed as (summon_count << 32) | petNumber Object::_Create(ObjectGuid::Create(map->GetId(), Entry, guidlow)); m_spawnId = guidlow; @@ -1672,8 +1711,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); } } @@ -1716,11 +1755,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; } @@ -1896,6 +1934,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 88880884ed6..9456dab4448 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -61,15 +61,17 @@ class TC_GAME_API Pet : public Guardian bool IsPermanentPetFor(Player* owner) const; // pet have tab in character windows and set UNIT_FIELD_PETNUMBER - bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry); + bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry, uint32 pet_number); bool CreateBaseAtCreature(Creature* creature); bool CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner); bool CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map); - bool LoadPetFromDB(Player* owner, uint32 petentry = 0, uint32 petnumber = 0, bool current = false); + static std::pair 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(uint32 guidlow); + static void DeleteFromDB(uint32 petNumber); void setDeathState(DeathState s) override; // overwrite virtual Creature::setDeathState and Unit::setDeathState void Update(uint32 diff) override; // overwrite virtual Creature::Update and Unit::Update @@ -112,10 +114,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 auraResult, PreparedQueryResult effectResult, 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); @@ -134,7 +135,7 @@ class TC_GAME_API Pet : public Guardian void InitPetCreateSpells(); - uint16 GetSpecialization() { return m_petSpecialization; } + uint16 GetSpecialization() const { return m_petSpecialization; } void SetSpecialization(uint16 spec); void LearnSpecializationSpells(); void RemoveSpecializationSpells(bool clearActionBar); @@ -143,7 +144,7 @@ class TC_GAME_API Pet : public Guardian void SetGroupUpdateFlag(uint32 flag); void ResetGroupUpdateFlag(); - 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) @@ -158,7 +159,7 @@ class TC_GAME_API Pet : public Guardian uint32 m_focusRegenTimer; uint32 m_groupUpdateMask; - DeclinedName *m_declinedname; + std::unique_ptr m_declinedname; uint16 m_petSpecialization; diff --git a/src/server/game/Entities/Pet/PetDefines.h b/src/server/game/Entities/Pet/PetDefines.h index 83607eb0bc1..f69b6ff8af9 100644 --- a/src/server/game/Entities/Pet/PetDefines.h +++ b/src/server/game/Entities/Pet/PetDefines.h @@ -19,8 +19,14 @@ #define TRINITYCORE_PET_DEFINES_H #include "Define.h" +#include "Optional.h" +#include +#include +#include -enum PetType +enum ReactStates : uint8; + +enum PetType : uint8 { SUMMON_PET = 0, HUNTER_PET = 1, @@ -30,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) @@ -98,4 +104,39 @@ enum class PetTameResult : uint8 EliteTooHighLevel = 14 }; +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 LastSaveTime = 0; + uint32 CreatedBySpellId = 0; + uint16 SpecializationId = 0; + uint8 Level = 0; + ReactStates ReactState = ReactStates(0); + PetType Type = MAX_PET_TYPE; + bool WasRenamed = false; + }; + + Optional CurrentPet; // PET_SAVE_AS_CURRENT + std::array, MAX_PET_STABLES> StabledPets; // PET_SAVE_FIRST_STABLE_SLOT - PET_SAVE_LAST_STABLE_SLOT + uint32 MaxStabledPets = 0; + std::vector 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 d50efffe822..d579d970426 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -258,8 +258,6 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this) m_movementForceModMagnitudeChanges = 0; - m_stableSlots = 0; - /////////////////// Instance System ///////////////////// m_HomebindTimer = 0; @@ -3909,7 +3907,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->setUInt64(0, guid); PreparedQueryResult resultPets = CharacterDatabase.Query(stmt); @@ -18415,13 +18413,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder* holder) uint32 extraflags = fields.extra_flags; - m_stableSlots = fields.stable_slots; - 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.stable_slots, holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS)); if (HasAtLoginFlag(AT_LOGIN_RENAME)) { @@ -19467,7 +19459,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)) @@ -20485,7 +20477,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba stmt->setInt64(index++, GetTalentResetTime()); stmt->setUInt32(index++, GetPrimarySpecialization()); 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->setInt64(index++, m_deathExpireTime); @@ -20629,7 +20621,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba stmt->setUInt8(index++, GetNumRespecs()); stmt->setUInt32(index++, GetPrimarySpecialization()); 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->setInt64(index++, m_deathExpireTime); @@ -21991,6 +21983,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(); @@ -28070,6 +28073,14 @@ bool Player::AddItem(uint32 itemId, uint32 count) return true; } +PetStable& Player::GetOrInitPetStable() +{ + if (!m_petStable) + m_petStable = std::make_unique(); + + return *m_petStable; +} + void Player::SendItemRefundResult(Item* item, ItemExtendedCostEntry const* iece, uint8 error) const { WorldPackets::Item::ItemPurchaseRefundResult itemPurchaseRefundResult; @@ -28270,6 +28281,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(); + 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, abdata, savetime, CreatedBySpell, PetType, specialization 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.ActionBar = fields[11].GetString(); + petInfo.LastSaveTime = fields[12].GetUInt32(); + petInfo.CreatedBySpellId = fields[13].GetUInt32(); + petInfo.Type = PetType(fields[14].GetUInt8()); + petInfo.SpecializationId = fields[15].GetUInt16(); + 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()) @@ -28605,19 +28665,14 @@ Guild const* Player::GetGuild() const 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(PetTameResult::Dead); - return nullptr; - } - if (duration > 0) pet->SetDuration(duration); @@ -28641,7 +28696,7 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy Map* map = GetMap(); uint32 pet_number = sObjectMgr->GeneratePetNumber(); - if (!pet->Create(map->GenerateLowGuid(), map, entry)) + if (!pet->Create(map->GenerateLowGuid(), map, entry, pet_number)) { TC_LOG_ERROR("misc", "Player::SummonPet: No such creature entry %u", entry); delete pet; @@ -28677,6 +28732,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 d7ef33b7f73..05a106e2ef1 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -889,6 +889,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_CURRENCY, PLAYER_LOGIN_QUERY_LOAD_CUF_PROFILES, PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, + PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, PLAYER_LOGIN_QUERY_LOAD_GARRISON, PLAYER_LOGIN_QUERY_LOAD_GARRISON_BLUEPRINTS, PLAYER_LOGIN_QUERY_LOAD_GARRISON_BUILDINGS, @@ -1205,8 +1206,12 @@ class TC_GAME_API Player : public Unit, public GridObject void setDeathState(DeathState s) override; // overwrite Unit::setDeathState + 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); void SendTameFailure(PetTameResult result); @@ -1490,8 +1495,6 @@ class TC_GAME_API Player : public Unit, public GridObject bool AddItem(uint32 itemId, uint32 count); - uint32 m_stableSlots; - /*********************************************************/ /*** GOSSIP SYSTEM ***/ /*********************************************************/ @@ -2833,6 +2836,7 @@ class TC_GAME_API Player : public Unit, public GridObject void _LoadTalents(PreparedQueryResult result); void _LoadPvpTalents(PreparedQueryResult result); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); + void _LoadPetStable(uint8 petStableSlots, PreparedQueryResult result); void _LoadCurrency(PreparedQueryResult result); void _LoadCUFProfiles(PreparedQueryResult result); @@ -3073,6 +3077,8 @@ class TC_GAME_API Player : public Unit, public GridObject bool m_bCanDelayTeleport; bool m_bHasDelayedTeleport; + std::unique_ptr 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 65d4f4f5165..d037047e9de 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -10283,7 +10283,11 @@ Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) uint8 level = creatureTarget->GetLevelForTarget(this) + 5 < GetLevel() ? (GetLevel() - 5) : creatureTarget->GetLevelForTarget(this); - InitTamedPet(pet, level, spell_id); + if (!InitTamedPet(pet, level, spell_id)) + { + delete pet; + return nullptr; + } return pet; } @@ -10310,6 +10314,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->SetCreatedBySpell(spell_id); @@ -10330,6 +10339,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 b852ebdc02e..60babf771db 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -380,7 +380,7 @@ enum ActiveStates ACT_DECIDE = 0x00 // custom }; -enum ReactStates +enum ReactStates : uint8 { REACT_PASSIVE = 0, REACT_DEFENSIVE = 1, diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index befda8ece15..ae75f958c8e 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -310,6 +310,10 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_GARRISON); stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_GARRISON, stmt); diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 09dceb99ec2..9854aaa6710 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -362,62 +362,62 @@ void WorldSession::HandleRequestStabledPets(WorldPackets::NPC::RequestStabledPet void WorldSession::SendStablePet(ObjectGuid guid) { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS_DETAIL); - - stmt->setUInt64(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; - WorldPackets::Pet::PetStableList packet; packet.StableMaster = guid; - Pet* pet = _player->GetPet(); + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) + { + SendPacket(packet.Write()); + return; + } int32 petSlot = 0; - // not let move dead pet in slot - if (pet && pet->IsAlive() && pet->getPetType() == HUNTER_PET) + if (petStable->CurrentPet) { - WorldPackets::Pet::PetStableInfo stableEntry; + PetStable::PetInfo const& pet = *petStable->CurrentPet; + WorldPackets::Pet::PetStableInfo& stableEntry = packet.Pets.emplace_back(); stableEntry.PetSlot = petSlot; - stableEntry.PetNumber = pet->GetCharmInfo()->GetPetNumber(); - stableEntry.CreatureID = pet->GetEntry(); - stableEntry.DisplayID = pet->GetDisplayId(); - stableEntry.ExperienceLevel = pet->GetLevel(); + stableEntry.PetNumber = pet.PetNumber; + stableEntry.CreatureID = pet.CreatureId; + stableEntry.DisplayID = pet.DisplayId; + stableEntry.ExperienceLevel = pet.Level; stableEntry.PetFlags = PET_STABLE_ACTIVE; - stableEntry.PetName = pet->GetName(); + stableEntry.PetName = pet.Name; ++petSlot; - - packet.Pets.push_back(stableEntry); } - - if (result) + else { - do + if (PetStable::PetInfo const* pet = petStable->GetUnslottedHunterPet()) { - Field* fields = result->Fetch(); - WorldPackets::Pet::PetStableInfo stableEntry; + WorldPackets::Pet::PetStableInfo& stableEntry = packet.Pets.emplace_back(); + stableEntry.PetSlot = petSlot; + stableEntry.PetNumber = pet->PetNumber; + stableEntry.CreatureID = pet->CreatureId; + stableEntry.DisplayID = pet->DisplayId; + stableEntry.ExperienceLevel = pet->Level; + stableEntry.PetFlags = PET_STABLE_ACTIVE; + stableEntry.PetName = pet->Name; + ++petSlot; + } + } + for (Optional const& stabledSlot : petStable->StabledPets) + { + if (stabledSlot) + { + PetStable::PetInfo const& pet = *stabledSlot; + WorldPackets::Pet::PetStableInfo& stableEntry = packet.Pets.emplace_back(); stableEntry.PetSlot = petSlot; - stableEntry.PetNumber = fields[1].GetUInt32(); // petnumber - stableEntry.CreatureID = fields[2].GetUInt32(); // creature entry - stableEntry.DisplayID = fields[5].GetUInt32(); // creature displayid - stableEntry.ExperienceLevel = fields[3].GetUInt16(); // level + stableEntry.PetNumber = pet.PetNumber; + stableEntry.CreatureID = pet.CreatureId; + stableEntry.DisplayID = pet.DisplayId; + stableEntry.ExperienceLevel = pet.Level; stableEntry.PetFlags = PET_STABLE_INACTIVE; - stableEntry.PetName = fields[4].GetString(); // Name - + stableEntry.PetName = pet.Name; ++petSlot; - packet.Pets.push_back(stableEntry); } - while (result->NextRow()); } SendPacket(packet.Write()); @@ -452,55 +452,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(StableResult::InternalError); return; } - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS); - - stmt->setUInt64(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(StableResult::StableSuccess); + return; + } + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt8(0, freeSlot); + stmt->setUInt64(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(StableResult::StableSuccess); + return; } - while (result->NextRow()); } - if (freeSlot > 0 && freeSlot <= GetPlayer()->m_stableSlots) - { - _player->RemovePet(_player->GetPet(), PetSaveMode(freeSlot)); - SendPetStableResult(StableResult::StableSuccess); - } - else - SendPetStableResult(StableResult::InvalidSlot); + // not free stable slot + SendPetStableResult(StableResult::InvalidSlot); } void WorldSession::HandleUnstablePet(WorldPacket& recvData) @@ -520,35 +514,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->setUInt64(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(StableResult::InternalError); return; + } - uint32 petEntry = 0; - if (result) + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional const& pet) { - Field* fields = result->Fetch(); - petEntry = fields[0].GetUInt32(); - } + return pet && pet->PetNumber == petnumber; + }); - if (!petEntry) + if (stabledPet == petStable->StabledPets.end()) { SendPetStableResult(StableResult::InternalError); 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 @@ -559,27 +543,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(StableResult::InternalError); + 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(StableResult::InternalError); + 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->setUInt64(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(StableResult::InternalError); 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->setUInt64(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petnumber); + CharacterDatabase.Execute(stmt); + SendPetStableResult(StableResult::InternalError); - return; } + else + { + std::swap(*stabledPet, petStable->CurrentPet); - SendPetStableResult(StableResult::UnstableSuccess); + // 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->setUInt64(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petnumber); + CharacterDatabase.Execute(stmt); + + SendPetStableResult(StableResult::UnstableSuccess); + } } void WorldSession::HandleBuyStableSlot(WorldPacket& recvData) @@ -598,12 +627,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(StableResult::BuySlotSuccess); } @@ -636,79 +666,97 @@ 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(StableResult::InternalError); return; } // Find swapped pet slot in stable - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOT_BY_ID); - - stmt->setUInt64(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 const& pet) { - SendPetStableResult(StableResult::InternalError); - return; - } - - Field* fields = result->Fetch(); + return pet && pet->PetNumber == petId; + }); - uint32 slot = fields[0].GetUInt8(); - uint32 petEntry = fields[1].GetUInt32(); - - if (!petEntry) + if (stabledPet == petStable->StabledPets.end()) { SendPetStableResult(StableResult::InternalError); return; } - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry); + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId); if (!creatureInfo || !creatureInfo->IsTameable(true)) { SendPetStableResult(StableResult::InternalError); return; } - if (!creatureInfo->IsTameable(_player->CanTameExoticPets())) + Pet* oldPet = _player->GetPet(); + if (oldPet) { - SendPetStableResult(StableResult::CantControlExotic); - return; + if (!oldPet->IsAlive() || !oldPet->IsHunterPet()) + { + SendPetStableResult(StableResult::InternalError); + 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(StableResult::InternalError); + return; + } - Pet* pet = _player->GetPet(); - // The player's pet could have been removed during the delay of the DB callback - if (!pet) + 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->setUInt64(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(StableResult::InternalError); 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(StableResult::InternalError); + + 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->setUInt64(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->setUInt64(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petId); + CharacterDatabase.Execute(stmt); + SendPetStableResult(StableResult::UnstableSuccess); + } } void WorldSession::HandleRepairItemOpcode(WorldPackets::Item::RepairItem& packet) diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index c57920c7691..ecb60b8770d 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -532,11 +532,13 @@ void WorldSession::HandlePetRename(WorldPackets::Pet::PetRename& packet) std::string name = packet.RenameData.NewName; Optional const& declinedname = packet.RenameData.DeclinedNames; + PetStable* petStable = _player->GetPetStable(); Pet* pet = ObjectAccessor::GetPet(*_player, petguid); // check it! if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET || !pet->HasPetFlag(UNIT_PET_FLAG_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); @@ -558,6 +560,9 @@ void WorldSession::HandlePetRename(WorldPackets::Pet::PetRename& packet) pet->RemovePetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED); + petStable->CurrentPet->Name = name; + petStable->CurrentPet->WasRenamed = true; + if (declinedname) { std::wstring wname; diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 193dea6100c..a32395fe64f 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1065,7 +1065,6 @@ class TC_GAME_API WorldSession // Pet void SendQueryPetNameResponse(ObjectGuid guid); void SendStablePet(ObjectGuid guid); - void SendStablePetCallback(ObjectGuid guid, PreparedQueryResult result); void SendPetStableResult(StableResult result); bool CheckStableMaster(ObjectGuid guid); @@ -1405,13 +1404,10 @@ class TC_GAME_API WorldSession void HandleBinderActivateOpcode(WorldPackets::NPC::Hello& packet); void HandleRequestStabledPets(WorldPackets::NPC::RequestStabledPets& packet); 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 HandleCanDuel(WorldPackets::Duel::CanDuel& packet); void HandleDuelResponseOpcode(WorldPackets::Duel::DuelResponse& duelResponse); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index d1415153575..4fd9582b1e8 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -5994,6 +5994,36 @@ SpellCastResult Spell::CheckCast(bool strict, int32* param1 /*= nullptr*/, int32 if (!unitCaster->GetCharmedGUID().IsEmpty()) return SPELL_FAILED_ALREADY_HAVE_CHARM; + + Player* playerCaster = unitCaster->ToPlayer(); + if (playerCaster && playerCaster->GetPetStable()) + { + std::pair info = Pet::GetLoadPetInfo(*playerCaster->GetPetStable(), spellEffectInfo.MiscValue, 0, false); + if (!info.first) + { + playerCaster->SendTameFailure(PetTameResult::NoPetAvailable); + return SPELL_FAILED_DONT_REPORT; + } + + if (info.first->Type == HUNTER_PET && !info.first->Health) + { + playerCaster->SendTameFailure(PetTameResult::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(PetTameResult::CantControlExotic); + else + playerCaster->SendTameFailure(PetTameResult::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 95d509b2c58..beef7e3c9c8 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2621,14 +2621,14 @@ void Spell::EffectSummonPet() } 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 7ca9de2c249..b23a71780af 100644 --- a/src/server/scripts/Commands/cs_pet.cpp +++ b/src/server/scripts/Commands/cs_pet.cpp @@ -91,42 +91,25 @@ 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->SetCreatorGUID(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; - } // prepare visual effect for levelup - pet->SetLevel(creatureTarget->GetLevel() - 1); - - pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true); - // this enables pet details window (Shift+P) - pet->InitPetCreateSpells(); - pet->SetFullHealth(); + pet->SetLevel(player->GetLevel() - 1); + // add to world pet->GetMap()->AddToMap(pet->ToCreature()); // visual effect for levelup - pet->SetLevel(creatureTarget->GetLevel()); + pet->SetLevel(player->GetLevel()); + // caster have pet now player->SetMinion(pet, true); + 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 e3be7cf78ed..ef3197d7119 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -617,8 +617,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()) @@ -630,11 +630,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().IsEmpty()) - 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(PetTameResult::TooMany); + return SPELL_FAILED_DONT_REPORT; + } + } if (!caster->GetCharmedGUID().IsEmpty()) return SPELL_FAILED_ALREADY_HAVE_CHARM; -- cgit v1.2.3