diff options
Diffstat (limited to 'src/server/game/Entities')
| -rw-r--r-- | src/server/game/Entities/Pet/Pet.cpp | 417 | ||||
| -rw-r--r-- | src/server/game/Entities/Pet/Pet.h | 13 | ||||
| -rw-r--r-- | src/server/game/Entities/Pet/PetDefines.h | 47 | ||||
| -rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 102 | ||||
| -rw-r--r-- | src/server/game/Entities/Player/Player.h | 12 | ||||
| -rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 13 | ||||
| -rw-r--r-- | src/server/game/Entities/Unit/UnitDefines.h | 3 |
7 files changed, 373 insertions, 234 deletions
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) |
