diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.cpp | 1 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.h | 1 | ||||
-rw-r--r-- | src/server/game/Entities/Pet/Pet.cpp | 92 | ||||
-rw-r--r-- | src/server/game/Entities/Pet/Pet.h | 4 | ||||
-rw-r--r-- | src/server/game/Entities/Pet/PetDefines.h | 48 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 118 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 4 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 10 | ||||
-rw-r--r-- | src/server/game/Handlers/CharacterHandler.cpp | 4 | ||||
-rw-r--r-- | src/server/game/Handlers/NPCHandler.cpp | 377 | ||||
-rw-r--r-- | src/server/game/Handlers/PetHandler.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Server/Packets/NPCPackets.cpp | 7 | ||||
-rw-r--r-- | src/server/game/Server/Packets/NPCPackets.h | 12 | ||||
-rw-r--r-- | src/server/game/Server/Protocol/Opcodes.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Server/WorldSession.h | 5 | ||||
-rw-r--r-- | src/server/game/Spells/Spell.cpp | 49 | ||||
-rw-r--r-- | src/server/game/Spells/SpellEffects.cpp | 42 | ||||
-rw-r--r-- | src/server/scripts/Spells/spell_hunter.cpp | 29 |
18 files changed, 374 insertions, 437 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index e68e46854d1..8e323f3c849 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -732,7 +732,6 @@ void CharacterDatabaseConnection::DoPrepareStatements() 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_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 IN (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_ALL_PET_SPELLS_BY_OWNER, "DELETE FROM pet_spell WHERE guid in (SELECT id FROM character_pet WHERE owner=?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_PET_SPECS_BY_OWNER, "UPDATE character_pet SET specialization = 0 WHERE owner=?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization) " diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 5fe79e83112..ccae9b1f8f2 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -599,7 +599,6 @@ enum CharacterDatabaseStatements : uint32 CHAR_UPD_CHAR_PET_NAME, CHAR_UPD_CHAR_PET_SLOT_BY_ID, CHAR_DEL_CHAR_PET_BY_ID, - CHAR_DEL_CHAR_PET_BY_SLOT, CHAR_DEL_ALL_PET_SPELLS_BY_OWNER, CHAR_UPD_PET_SPECS_BY_OWNER, CHAR_INS_PET, diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 8a2ffc81a07..88de7a45c84 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -101,13 +101,14 @@ void Pet::RemoveFromWorld() } } -std::pair<PetStable::PetInfo const*, PetSaveMode> Pet::GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, bool current) +std::pair<PetStable::PetInfo const*, PetSaveMode> Pet::GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, Optional<PetSaveMode> slot) { if (petnumber) { // Known petnumber entry - if (stable.CurrentPet && stable.CurrentPet->PetNumber == petnumber) - return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT }; + for (std::size_t activeSlot = 0; activeSlot < stable.ActivePets.size(); ++activeSlot) + if (stable.ActivePets[activeSlot] && stable.ActivePets[activeSlot]->PetNumber == petnumber) + return { &stable.ActivePets[activeSlot].value(), PetSaveMode(PET_SAVE_FIRST_ACTIVE_SLOT + activeSlot) }; for (std::size_t stableSlot = 0; stableSlot < stable.StabledPets.size(); ++stableSlot) if (stable.StabledPets[stableSlot] && stable.StabledPets[stableSlot]->PetNumber == petnumber) @@ -117,18 +118,24 @@ std::pair<PetStable::PetInfo const*, PetSaveMode> Pet::GetLoadPetInfo(PetStable if (pet.PetNumber == petnumber) return { &pet, PET_SAVE_NOT_IN_SLOT }; } - else if (current) + else if (slot) { - // Current pet (slot 0) - if (stable.CurrentPet) - return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT }; + // Current pet + if (slot == PET_SAVE_AS_CURRENT) + if (stable.GetCurrentActivePetIndex() && stable.ActivePets[*stable.GetCurrentActivePetIndex()]) + return { &stable.ActivePets[*stable.GetCurrentActivePetIndex()].value(), PetSaveMode(*stable.GetCurrentActivePetIndex()) }; + + if (slot >= PET_SAVE_FIRST_ACTIVE_SLOT && slot < PET_SAVE_LAST_ACTIVE_SLOT) + if (stable.ActivePets[*slot]) + return { &stable.ActivePets[*slot].value(), *slot }; + + if (slot >= PET_SAVE_FIRST_STABLE_SLOT && slot < PET_SAVE_LAST_STABLE_SLOT) + if (stable.StabledPets[*slot]) + return { &stable.StabledPets[*slot].value(), *slot }; } else if (petEntry) { // known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets) - 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 }; @@ -136,8 +143,8 @@ std::pair<PetStable::PetInfo const*, PetSaveMode> Pet::GetLoadPetInfo(PetStable else { // Any current or other non-stabled pet (for hunter "call pet") - if (stable.CurrentPet) - return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT }; + if (stable.ActivePets[0]) + return { &stable.ActivePets[0].value(), PET_SAVE_FIRST_ACTIVE_SLOT }; if (!stable.UnslottedPets.empty()) return { &stable.UnslottedPets.front(), PET_SAVE_NOT_IN_SLOT }; @@ -194,24 +201,24 @@ public: } }; -bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current) +bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current, Optional<PetSaveMode> forcedSlot /*= {}*/) { m_loading = true; PetStable* petStable = ASSERT_NOTNULL(owner->GetPetStable()); ObjectGuid::LowType ownerid = owner->GetGUID().GetCounter(); - std::pair<PetStable::PetInfo const*, PetSaveMode> info = GetLoadPetInfo(*petStable, petEntry, petnumber, current); + std::pair<PetStable::PetInfo const*, PetSaveMode> info = GetLoadPetInfo(*petStable, petEntry, petnumber, forcedSlot); PetStable::PetInfo const* petInfo = info.first; PetSaveMode slot = info.second; - if (!petInfo) + if (!petInfo || (slot >= PET_SAVE_FIRST_STABLE_SLOT && slot < PET_SAVE_LAST_STABLE_SLOT)) { m_loading = false; return false; } // Don't try to reload the current pet - if (petStable->CurrentPet && owner->GetPet() && petStable->CurrentPet.value().PetNumber == petInfo->PetNumber) + if (petStable->GetCurrentPet() && owner->GetPet() && petStable->GetCurrentPet()->PetNumber == petInfo->PetNumber) return false; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(petInfo->CreatedBySpellId, owner->GetMap()->GetDifficultyID()); @@ -329,40 +336,33 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c } // set current pet as current - // 0=current - // 1..MAX_PET_STABLES in stable slot - // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning)) + // 0-4=current + // PET_SAVE_NOT_IN_SLOT(-1) = not stable slot (summoning)) if (slot == PET_SAVE_NOT_IN_SLOT) { uint32 petInfoNumber = petInfo->PetNumber; - if (petStable->CurrentPet) + if (petStable->CurrentPetIndex) owner->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); auto unslottedPetItr = std::find_if(petStable->UnslottedPets.begin(), petStable->UnslottedPets.end(), [&](PetStable::PetInfo const& unslottedPet) { return unslottedPet.PetNumber == petInfoNumber; }); - ASSERT(!petStable->CurrentPet); + ASSERT(!petStable->CurrentPetIndex); ASSERT(unslottedPetItr != petStable->UnslottedPets.end()); - petStable->CurrentPet = std::move(*unslottedPetItr); - petStable->UnslottedPets.erase(unslottedPetItr); - - // old petInfo pointer is no longer valid, refresh it - petInfo = &petStable->CurrentPet.value(); + petStable->SetCurrentUnslottedPetIndex(std::distance(petStable->UnslottedPets.begin(), unslottedPetItr)); } - else if (PET_SAVE_FIRST_STABLE_SLOT <= slot && slot <= PET_SAVE_LAST_STABLE_SLOT) + else if (PET_SAVE_FIRST_ACTIVE_SLOT <= slot && slot <= PET_SAVE_LAST_ACTIVE_SLOT) { - auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional<PetStable::PetInfo> const& pet) + auto activePetItr = std::find_if(petStable->ActivePets.begin(), petStable->ActivePets.end(), [&](Optional<PetStable::PetInfo> const& pet) { - return pet && pet->PetNumber == petnumber; + return pet && pet->PetNumber == petInfo->PetNumber; }); - ASSERT(stabledPet != petStable->StabledPets.end()); - - std::swap(*stabledPet, petStable->CurrentPet); + ASSERT(!petStable->CurrentPetIndex); + ASSERT(activePetItr != petStable->ActivePets.end()); - // old petInfo pointer is no longer valid, refresh it - petInfo = &petStable->CurrentPet.value(); + petStable->SetCurrentActivePetIndex(std::distance(petStable->ActivePets.begin(), activePetItr)); } // Send fake summon spell cast - this is needed for correct cooldown application for spells @@ -491,8 +491,12 @@ void Pet::SavePetToDB(PetSaveMode mode) // save auras before possibly removing them _SaveAuras(trans); + if (mode == PET_SAVE_AS_CURRENT) + if (Optional<uint32> activeSlot = owner->GetPetStable()->GetCurrentActivePetIndex()) + mode = PetSaveMode(*activeSlot); + // stable and not in slot saves - if (mode > PET_SAVE_AS_CURRENT) + if (mode < PET_SAVE_FIRST_ACTIVE_SLOT || mode >= PET_SAVE_LAST_ACTIVE_SLOT) RemoveAllAuras(); _SaveSpells(trans); @@ -500,7 +504,7 @@ void Pet::SavePetToDB(PetSaveMode mode) CharacterDatabase.CommitTransaction(trans); // current/stable/not_in_slot - if (mode >= PET_SAVE_AS_CURRENT) + if (mode != PET_SAVE_AS_DELETED) { ObjectGuid::LowType ownerLowGUID = GetOwnerGUID().GetCounter(); trans = CharacterDatabase.BeginTransaction(); @@ -510,21 +514,11 @@ void Pet::SavePetToDB(PetSaveMode mode) stmt->setUInt32(0, m_charmInfo->GetPetNumber()); 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_NOT_IN_SLOT)) - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_SLOT); - stmt->setUInt64(0, ownerLowGUID); - stmt->setInt16(1, mode); - stmt->setInt16(2, PET_SAVE_NOT_IN_SLOT); - trans->Append(stmt); - } - // save pet std::string actionBar = GenerateActionBarData(); - ASSERT(owner->GetPetStable()->CurrentPet && owner->GetPetStable()->CurrentPet->PetNumber == m_charmInfo->GetPetNumber()); - FillPetInfo(&owner->GetPetStable()->CurrentPet.value()); + ASSERT(owner->GetPetStable()->GetCurrentPet() && owner->GetPetStable()->GetCurrentPet()->PetNumber == m_charmInfo->GetPetNumber()); + FillPetInfo(owner->GetPetStable()->GetCurrentPet()); stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET); stmt->setUInt32(0, m_charmInfo->GetPetNumber()); @@ -534,7 +528,7 @@ void Pet::SavePetToDB(PetSaveMode mode) stmt->setUInt8(4, GetLevel()); stmt->setUInt32(5, m_unitData->PetExperience); stmt->setUInt8(6, GetReactState()); - stmt->setInt16(7, mode); + stmt->setInt16(7, owner->GetPetStable()->GetCurrentActivePetIndex().value_or(PET_SAVE_NOT_IN_SLOT)); stmt->setString(8, m_name); stmt->setUInt8(9, HasPetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED) ? 0 : 1); stmt->setUInt32(10, curhealth); diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index 9456dab4448..8dc3da1d1ea 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -65,8 +65,8 @@ 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); - 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); + static std::pair<PetStable::PetInfo const*, PetSaveMode> GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, Optional<PetSaveMode> slot); + bool LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current, Optional<PetSaveMode> forcedSlot = {}); bool IsLoading() const override { return m_loading;} void SavePetToDB(PetSaveMode mode); void FillPetInfo(PetStable::PetInfo* petInfo) const; diff --git a/src/server/game/Entities/Pet/PetDefines.h b/src/server/game/Entities/Pet/PetDefines.h index 5bdcc1ee67c..0b1048e79d0 100644 --- a/src/server/game/Entities/Pet/PetDefines.h +++ b/src/server/game/Entities/Pet/PetDefines.h @@ -33,18 +33,31 @@ enum PetType : uint8 MAX_PET_TYPE = 4 }; -#define MAX_PET_STABLES 4 +#define MAX_ACTIVE_PETS 5 +#define MAX_PET_STABLES 200 // stored in character_pet.slot enum PetSaveMode : int16 { PET_SAVE_AS_DELETED = -2, // not saved in fact - PET_SAVE_AS_CURRENT = 0, // in current slot (with player) - PET_SAVE_FIRST_STABLE_SLOT = 1, - PET_SAVE_LAST_STABLE_SLOT = MAX_PET_STABLES, // last in DB stable slot index (including), all higher have same meaning as PET_SAVE_NOT_IN_SLOT + PET_SAVE_AS_CURRENT = -3, // in current slot (with player) + PET_SAVE_FIRST_ACTIVE_SLOT = 0, + PET_SAVE_LAST_ACTIVE_SLOT = PET_SAVE_FIRST_ACTIVE_SLOT + MAX_ACTIVE_PETS, + PET_SAVE_FIRST_STABLE_SLOT = 5, + PET_SAVE_LAST_STABLE_SLOT = PET_SAVE_FIRST_STABLE_SLOT + MAX_PET_STABLES, // last in DB stable slot index PET_SAVE_NOT_IN_SLOT = -1 // for avoid conflict with stable size grow will use negative value }; +constexpr bool IsActivePetSlot(PetSaveMode slot) +{ + return slot >= PET_SAVE_FIRST_ACTIVE_SLOT && slot < PET_SAVE_LAST_ACTIVE_SLOT; +} + +constexpr bool IsStabledPetSlot(PetSaveMode slot) +{ + return slot >= PET_SAVE_FIRST_STABLE_SLOT && slot < PET_SAVE_LAST_STABLE_SLOT; +} + enum PetSpellState { PETSPELL_UNCHANGED = 0, @@ -97,6 +110,8 @@ enum class PetTameResult : uint8 EliteTooHighLevel = 14 }; +constexpr uint32 CALL_PET_SPELL_ID = 883; + class PetStable { public: @@ -121,14 +136,33 @@ public: bool WasRenamed = false; }; - Optional<PetInfo> CurrentPet; // PET_SAVE_AS_CURRENT + Optional<uint32> CurrentPetIndex; // index into ActivePets or UnslottedPets if highest bit is set + std::array<Optional<PetInfo>, MAX_ACTIVE_PETS> ActivePets; // PET_SAVE_FIRST_ACTIVE_SLOT - PET_SAVE_LAST_ACTIVE_SLOT std::array<Optional<PetInfo>, MAX_PET_STABLES> StabledPets; // PET_SAVE_FIRST_STABLE_SLOT - PET_SAVE_LAST_STABLE_SLOT std::vector<PetInfo> UnslottedPets; // PET_SAVE_NOT_IN_SLOT - PetInfo const* GetUnslottedHunterPet() const + PetInfo* GetCurrentPet() { return const_cast<PetInfo*>(const_cast<PetStable const*>(this)->GetCurrentPet()); } + PetInfo const* GetCurrentPet() const { - return UnslottedPets.size() == 1 && UnslottedPets[0].Type == HUNTER_PET ? &UnslottedPets[0] : nullptr; + if (!CurrentPetIndex) + return nullptr; + + if (Optional<uint32> activePetIndex = GetCurrentActivePetIndex()) + return ActivePets[*activePetIndex] ? &ActivePets[*activePetIndex].value() : nullptr; + + if (Optional<uint32> unslottedPetIndex = GetCurrentUnslottedPetIndex()) + return *unslottedPetIndex < UnslottedPets.size() ? &UnslottedPets[*unslottedPetIndex] : nullptr; + + return nullptr; } + + Optional<uint32> GetCurrentActivePetIndex() const { return CurrentPetIndex && ((*CurrentPetIndex & UnslottedPetIndexMask) == 0) ? CurrentPetIndex : std::nullopt; } + void SetCurrentActivePetIndex(uint32 index) { CurrentPetIndex = index; } + Optional<uint32> GetCurrentUnslottedPetIndex() const { return CurrentPetIndex && ((*CurrentPetIndex & UnslottedPetIndexMask) != 0) ? Optional<uint32>(*CurrentPetIndex & ~UnslottedPetIndexMask) : std::nullopt; } + void SetCurrentUnslottedPetIndex(uint32 index) { CurrentPetIndex = index | UnslottedPetIndexMask; } + +private: + static constexpr uint32 UnslottedPetIndexMask = 0x80000000; }; #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index e26ecd7528d..ed97075d7e1 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -18413,8 +18413,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder* holder) uint32 extraflags = fields.extra_flags; - _LoadPetStable(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS)); - m_temporaryUnsummonedPetNumber = fields.summonedPetNumber; + _LoadPetStable(fields.summonedPetNumber, holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS)); if (HasAtLoginFlag(AT_LOGIN_RENAME)) { @@ -20611,7 +20610,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba stmt->setUInt32(index++, GetPrimarySpecialization()); stmt->setUInt16(index++, (uint16)m_ExtraFlags); if (PetStable const* petStable = GetPetStable()) - stmt->setUInt32(index++, petStable->CurrentPet && petStable->CurrentPet->Health > 0 ? petStable->CurrentPet->PetNumber : 0); // summonedPetNumber + stmt->setUInt32(index++, petStable->GetCurrentPet() && petStable->GetCurrentPet()->Health > 0 ? petStable->GetCurrentPet()->PetNumber : 0); // summonedPetNumber else stmt->setUInt32(index++, 0); // summonedPetNumber stmt->setUInt16(index++, (uint16)m_atLoginFlags); @@ -21955,49 +21954,22 @@ void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent) if (!pet) { - if (mode == PET_SAVE_NOT_IN_SLOT && m_petStable && m_petStable->CurrentPet) - { - // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); - stmt->setInt16(0, PET_SAVE_NOT_IN_SLOT); - stmt->setUInt64(1, GetGUID().GetCounter()); - stmt->setUInt32(2, m_petStable->CurrentPet->PetNumber); - CharacterDatabase.Execute(stmt); - - m_petStable->UnslottedPets.push_back(std::move(*m_petStable->CurrentPet)); - m_petStable->CurrentPet.reset(); - } + // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount + if (mode == PET_SAVE_NOT_IN_SLOT && m_petStable && m_petStable->CurrentPetIndex) + m_petStable->CurrentPetIndex.reset(); return; } pet->CombatStop(); - if (returnreagent) - { - switch (pet->GetEntry()) - { - //warlock pets except imp are removed(?) when logging out - case 1860: - case 1863: - case 417: - case 17252: - mode = PET_SAVE_NOT_IN_SLOT; - break; - } - } - // 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(); + PetStable::PetInfo const* currentPet = m_petStable->GetCurrentPet(); + ASSERT(currentPet && currentPet->PetNumber == pet->GetCharmInfo()->GetPetNumber()); + if (mode == PET_SAVE_NOT_IN_SLOT || mode == PET_SAVE_AS_DELETED) + m_petStable->CurrentPetIndex.reset(); // else if (stable slots) handled in opcode handlers due to required swaps // else (current pet) doesnt need to do anything @@ -28288,7 +28260,7 @@ void Player::_LoadInstanceTimeRestrictions(PreparedQueryResult result) } while (result->NextRow()); } -void Player::_LoadPetStable(PreparedQueryResult result) +void Player::_LoadPetStable(uint32 summonedPetNumber, PreparedQueryResult result) { if (!result) return; @@ -28319,15 +28291,18 @@ void Player::_LoadPetStable(PreparedQueryResult result) 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); + if (slot >= PET_SAVE_FIRST_ACTIVE_SLOT && slot < PET_SAVE_LAST_ACTIVE_SLOT) + m_petStable->ActivePets[slot] = std::move(petInfo); + else if (slot >= PET_SAVE_FIRST_STABLE_SLOT && slot < PET_SAVE_LAST_STABLE_SLOT) + m_petStable->StabledPets[slot - PET_SAVE_FIRST_STABLE_SLOT] = std::move(petInfo); else if (slot == PET_SAVE_NOT_IN_SLOT) m_petStable->UnslottedPets.push_back(std::move(petInfo)); } while (result->NextRow()); } + + if (Pet::GetLoadPetInfo(*m_petStable, 0, summonedPetNumber, {}).first) + m_temporaryUnsummonedPetNumber = summonedPetNumber; } void Player::_SaveInstanceTimeRestrictions(CharacterDatabaseTransaction& trans) @@ -28665,18 +28640,21 @@ 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) +Pet* Player::SummonPet(uint32 entry, Optional<PetSaveMode> slot, float x, float y, float z, float ang, uint32 duration, bool* isNew /*= nullptr*/) { PetStable& petStable = GetOrInitPetStable(); - Pet* pet = new Pet(this, petType); + Pet* pet = new Pet(this, SUMMON_PET); - if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry, 0, false)) + if (pet->LoadPetFromDB(this, entry, 0, false, slot)) { if (duration > 0) pet->SetDuration(duration); - return nullptr; + if (isNew) + *isNew = false; + + return pet; } // petentry == 0 for hunter "call pet" (current pet summoned if any) @@ -28686,6 +28664,8 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy return nullptr; } + // only SUMMON_PET are handled here + pet->Relocate(x, y, z, ang); if (!pet->IsPositionValid()) { @@ -28703,7 +28683,7 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy return nullptr; } - if (petType == SUMMON_PET && petStable.CurrentPet) + if (petStable.GetCurrentPet()) RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); PhasingHandler::InheritPhaseShift(pet, this); @@ -28717,43 +28697,33 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy SetMinion(pet, true); - switch (petType) - { - case SUMMON_PET: - // this enables pet details window (Shift+P) - pet->GetCharmInfo()->SetPetNumber(pet_number, true); - pet->SetClass(CLASS_MAGE); - pet->SetPetExperience(0); - pet->SetPetNextLevelExperience(1000); - pet->SetFullHealth(); - pet->SetFullPower(POWER_MANA); - pet->SetPetNameTimestamp(uint32(GameTime::GetGameTime())); - break; - default: - break; - } + // this enables pet details window (Shift+P) + pet->GetCharmInfo()->SetPetNumber(pet_number, true); + pet->SetClass(CLASS_MAGE); + pet->SetPetExperience(0); + pet->SetPetNextLevelExperience(1000); + pet->SetFullHealth(); + pet->SetFullPower(POWER_MANA); + pet->SetPetNameTimestamp(uint32(GameTime::GetGameTime())); map->AddToMap(pet->ToCreature()); - ASSERT(!petStable.CurrentPet && (petType != HUNTER_PET || !petStable.GetUnslottedHunterPet())); - pet->FillPetInfo(&petStable.CurrentPet.emplace()); + ASSERT(!petStable.CurrentPetIndex); + petStable.SetCurrentUnslottedPetIndex(petStable.UnslottedPets.size()); + pet->FillPetInfo(&petStable.UnslottedPets.emplace_back()); - switch (petType) - { - case SUMMON_PET: - pet->InitPetCreateSpells(); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); - PetSpellInitialize(); - break; - default: - break; - } + pet->InitPetCreateSpells(); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + PetSpellInitialize(); if (duration > 0) pet->SetDuration(duration); //ObjectAccessor::UpdateObjectVisibility(pet); + if (isNew) + *isNew = true; + return pet; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 471720327f7..666db7ca213 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1211,7 +1211,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> 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); + Pet* SummonPet(uint32 entry, Optional<PetSaveMode> slot, float x, float y, float z, float ang, uint32 despwtime, bool* isNew = nullptr); void RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent = false); void SendTameFailure(PetTameResult result); @@ -2835,7 +2835,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void _LoadTalents(PreparedQueryResult result); void _LoadPvpTalents(PreparedQueryResult result); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); - void _LoadPetStable(PreparedQueryResult result); + void _LoadPetStable(uint32 summonedPetNumber, PreparedQueryResult result); void _LoadCurrency(PreparedQueryResult result); void _LoadCUFProfiles(PreparedQueryResult result); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index c62211a1a1d..b339b55e7d1 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -10316,7 +10316,12 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) { Player* player = ToPlayer(); PetStable& petStable = player->GetOrInitPetStable(); - if (petStable.CurrentPet || petStable.GetUnslottedHunterPet()) + auto freeActiveSlotItr = std::find_if(petStable.ActivePets.begin(), petStable.ActivePets.end(), [](Optional<PetStable::PetInfo> const& petInfo) + { + return !petInfo.has_value(); + }); + + if (freeActiveSlotItr == petStable.ActivePets.end()) return false; pet->SetCreatorGUID(GetGUID()); @@ -10340,7 +10345,8 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) //pet->InitLevelupSpellsForLevel(); pet->SetFullHealth(); - pet->FillPetInfo(&petStable.CurrentPet.emplace()); + petStable.SetCurrentActivePetIndex(std::distance(petStable.ActivePets.begin(), freeActiveSlotItr)); + pet->FillPetInfo(&freeActiveSlotItr->emplace()); return true; } diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index c80cc034336..7e44225f8da 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -1146,6 +1146,10 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) pCurrChar->SetGuildLevel(0); } + // Send stable contents to display icons on Call Pet spells + if (pCurrChar->HasSpell(CALL_PET_SPELL_ID)) + SendStablePet(ObjectGuid::Empty); + pCurrChar->GetSession()->GetBattlePetMgr()->SendJournalLockStatus(); pCurrChar->SendInitialPacketsBeforeAddToMap(); diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 0607103ad6c..43ac2e581d8 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -32,14 +32,11 @@ #include "MailPackets.h" #include "Map.h" #include "NPCPackets.h" -#include "ObjectAccessor.h" #include "ObjectMgr.h" -#include "Opcodes.h" #include "Pet.h" #include "PetPackets.h" #include "Player.h" #include "ReputationMgr.h" -#include "ScriptMgr.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "Trainer.h" @@ -373,51 +370,36 @@ void WorldSession::SendStablePet(ObjectGuid guid) return; } - int32 petSlot = 0; - if (petStable->CurrentPet) + for (uint32 petSlot = 0; petSlot < petStable->ActivePets.size(); ++petSlot) { - PetStable::PetInfo const& pet = *petStable->CurrentPet; + if (!petStable->ActivePets[petSlot]) + continue; + + PetStable::PetInfo const& pet = *petStable->ActivePets[petSlot]; WorldPackets::Pet::PetStableInfo& stableEntry = packet.Pets.emplace_back(); - stableEntry.PetSlot = petSlot; + stableEntry.PetSlot = petSlot + PET_SAVE_FIRST_ACTIVE_SLOT; 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; - } - else - { - if (PetStable::PetInfo const* pet = petStable->GetUnslottedHunterPet()) - { - 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<PetStable::PetInfo> const& stabledSlot : petStable->StabledPets) + for (uint32 petSlot = 0; petSlot < petStable->StabledPets.size(); ++petSlot) { - if (stabledSlot) - { - PetStable::PetInfo const& pet = *stabledSlot; - 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_INACTIVE; - stableEntry.PetName = pet.Name; - ++petSlot; - } + if (!petStable->StabledPets[petSlot]) + continue; + + PetStable::PetInfo const& pet = *petStable->StabledPets[petSlot]; + WorldPackets::Pet::PetStableInfo& stableEntry = packet.Pets.emplace_back(); + stableEntry.PetSlot = petSlot + PET_SAVE_FIRST_STABLE_SLOT; + stableEntry.PetNumber = pet.PetNumber; + stableEntry.CreatureID = pet.CreatureId; + stableEntry.DisplayID = pet.DisplayId; + stableEntry.ExperienceLevel = pet.Level; + stableEntry.PetFlags = PET_STABLE_INACTIVE; + stableEntry.PetName = pet.Name; } SendPacket(packet.Write()); @@ -430,291 +412,150 @@ void WorldSession::SendPetStableResult(StableResult result) SendPacket(petStableResult.Write()); } -void WorldSession::HandleStablePet(WorldPacket& recvData) +void WorldSession::HandleSetPetSlot(WorldPackets::NPC::SetPetSlot& setPetSlot) { - ObjectGuid npcGUID; - - recvData >> npcGUID; - - if (!GetPlayer()->IsAlive()) + if (!CheckStableMaster(setPetSlot.StableMaster) || setPetSlot.DestSlot >= PET_SAVE_LAST_STABLE_SLOT) { SendPetStableResult(StableResult::InternalError); return; } - if (!CheckStableMaster(npcGUID)) - { - SendPetStableResult(StableResult::InternalError); - return; - } - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + GetPlayer()->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); 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)) - || (!pet && (petStable->UnslottedPets.size() != 1 || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET))) { SendPetStableResult(StableResult::InternalError); return; } - for (uint32 freeSlot = 0; freeSlot < petStable->StabledPets.size(); ++freeSlot) - { - if (!petStable->StabledPets[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; - } + auto [srcPet, srcPetSlot] = Pet::GetLoadPetInfo(*petStable, 0, setPetSlot.PetNumber, {}); + PetSaveMode dstPetSlot = PetSaveMode(setPetSlot.DestSlot); + PetStable::PetInfo const* dstPet = Pet::GetLoadPetInfo(*petStable, 0, 0, dstPetSlot).first; - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); - stmt->setUInt8(0, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + 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; - } - } - - // not free stable slot - SendPetStableResult(StableResult::InvalidSlot); -} - -void WorldSession::HandleUnstablePet(WorldPacket& recvData) -{ - ObjectGuid npcGUID; - uint32 petnumber; - - recvData >> npcGUID >> petnumber; - - if (!CheckStableMaster(npcGUID)) + if (!srcPet || srcPet->Type != HUNTER_PET) { SendPetStableResult(StableResult::InternalError); return; } - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - PetStable* petStable = GetPlayer()->GetPetStable(); - if (!petStable) + if (dstPet && dstPet->Type != HUNTER_PET) { SendPetStableResult(StableResult::InternalError); return; } - auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional<PetStable::PetInfo> const& pet) - { - return pet && pet->PetNumber == petnumber; - }); + Optional<PetStable::PetInfo>* src = nullptr; + Optional<PetStable::PetInfo>* dst = nullptr; + Optional<int16> newActivePetIndex; - if (stabledPet == petStable->StabledPets.end()) + if (IsActivePetSlot(srcPetSlot) && IsActivePetSlot(dstPetSlot)) { - SendPetStableResult(StableResult::InternalError); - return; - } + // active<->active: only swap ActivePets and CurrentPetIndex (do not despawn pets) + src = &petStable->ActivePets[srcPetSlot - PET_SAVE_FIRST_ACTIVE_SLOT]; + dst = &petStable->ActivePets[dstPetSlot - PET_SAVE_FIRST_ACTIVE_SLOT]; - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId); - if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets())) + if (petStable->GetCurrentActivePetIndex() == srcPetSlot) + newActivePetIndex = dstPetSlot; + else if (petStable->GetCurrentActivePetIndex() == dstPetSlot) + newActivePetIndex = srcPetSlot; + } + else if (IsStabledPetSlot(srcPetSlot) && IsStabledPetSlot(dstPetSlot)) { - // if problem in exotic pet - if (creatureInfo && creatureInfo->IsTameable(true)) - SendPetStableResult(StableResult::CantControlExotic); - else - SendPetStableResult(StableResult::InternalError); - return; + // stabled<->stabled: only swap StabledPets + src = &petStable->StabledPets[srcPetSlot - PET_SAVE_FIRST_STABLE_SLOT]; + dst = &petStable->StabledPets[dstPetSlot - PET_SAVE_FIRST_STABLE_SLOT]; } - - Pet* oldPet = _player->GetPet(); - if (oldPet) + else if (IsActivePetSlot(srcPetSlot) && IsStabledPetSlot(dstPetSlot)) { - // try performing a swap, client sends this packet instead of swap when starting from stabled slot - if (!oldPet->IsAlive() || !oldPet->IsHunterPet()) + // active<->stabled: swap petStable contents and despawn active pet if it is involved in swap + if (petStable->CurrentPetIndex == srcPetSlot) { - SendPetStableResult(StableResult::InternalError); - return; + Pet* oldPet = _player->GetPet(); + if (oldPet && !oldPet->IsAlive()) + { + SendPetStableResult(StableResult::InternalError); + return; + } + + _player->RemovePet(oldPet, PET_SAVE_NOT_IN_SLOT); } - _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) + if (dstPet) { - SendPetStableResult(StableResult::InternalError); - return; + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(dstPet->CreatureId); + if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets())) + { + SendPetStableResult(StableResult::CantControlExotic); + 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; - } - - Pet* newPet = new Pet(_player, HUNTER_PET); - if (!newPet->LoadPetFromDB(_player, 0, petnumber, false)) - { - delete newPet; - - 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); - } - else - { - // 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); + src = &petStable->ActivePets[srcPetSlot - PET_SAVE_FIRST_ACTIVE_SLOT]; + dst = &petStable->StabledPets[dstPetSlot - PET_SAVE_FIRST_STABLE_SLOT]; } -} - -void WorldSession::HandleStableSwapPet(WorldPacket& recvData) -{ - ObjectGuid npcGUID; - uint32 petId; - - recvData >> npcGUID >> petId; - - if (!CheckStableMaster(npcGUID)) + else if (IsStabledPetSlot(srcPetSlot) && IsActivePetSlot(dstPetSlot)) { - SendPetStableResult(StableResult::InternalError); - return; - } - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - PetStable* petStable = GetPlayer()->GetPetStable(); - if (!petStable) - { - SendPetStableResult(StableResult::InternalError); - return; - } - - // Find swapped pet slot in stable - auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petId](Optional<PetStable::PetInfo> const& pet) - { - return pet && pet->PetNumber == petId; - }); - - if (stabledPet == petStable->StabledPets.end()) - { - SendPetStableResult(StableResult::InternalError); - return; - } - - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId); - if (!creatureInfo || !creatureInfo->IsTameable(true)) - { - SendPetStableResult(StableResult::InternalError); - return; - } - - Pet* oldPet = _player->GetPet(); - if (oldPet) - { - if (!oldPet->IsAlive() || !oldPet->IsHunterPet()) + // stabled<->active: swap petStable contents and despawn active pet if it is involved in swap + if (petStable->CurrentPetIndex == dstPetSlot) { - SendPetStableResult(StableResult::InternalError); - return; + Pet* oldPet = _player->GetPet(); + if (oldPet && !oldPet->IsAlive()) + { + SendPetStableResult(StableResult::InternalError); + return; + } + + _player->RemovePet(oldPet, PET_SAVE_NOT_IN_SLOT); } - _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) + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(srcPet->CreatureId); + if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets())) { - SendPetStableResult(StableResult::InternalError); + SendPetStableResult(StableResult::CantControlExotic); 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; + src = &petStable->StabledPets[srcPetSlot - PET_SAVE_FIRST_STABLE_SLOT]; + dst = &petStable->ActivePets[dstPetSlot - PET_SAVE_FIRST_ACTIVE_SLOT]; } - // summon unstabled pet - Pet* newPet = new Pet(_player, HUNTER_PET); - if (!newPet->LoadPetFromDB(_player, 0, petId, false)) - { - delete newPet; - SendPetStableResult(StableResult::InternalError); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - petStable->UnslottedPets.push_back(std::move(*petStable->CurrentPet)); - petStable->CurrentPet.reset(); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setInt16(0, dstPetSlot); + stmt->setUInt64(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, srcPet->PetNumber); + trans->Append(stmt); - // 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); + if (dstPet) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setInt16(0, srcPetSlot); stmt->setUInt64(1, _player->GetGUID().GetCounter()); - stmt->setUInt32(2, petId); - CharacterDatabase.Execute(stmt); + stmt->setUInt32(2, dstPet->PetNumber); + trans->Append(stmt); } - else + + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete( + [this, currentPlayerGuid = _player->GetGUID(), src, dst, newActivePetIndex](bool success) { - // 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); + if (_player && _player->GetGUID() == currentPlayerGuid) + { + if (success) + { + std::swap(*src, *dst); + if (newActivePetIndex) + GetPlayer()->GetPetStable()->SetCurrentActivePetIndex(*newActivePetIndex); - SendPetStableResult(StableResult::UnstableSuccess); - } + SendPetStableResult(StableResult::StableSuccess); + } + else + { + SendPetStableResult(StableResult::InternalError); + } + } + }); } void WorldSession::HandleRepairItemOpcode(WorldPackets::Item::RepairItem& packet) diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index ecb60b8770d..69f104d7abe 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -538,7 +538,7 @@ void WorldSession::HandlePetRename(WorldPackets::Pet::PetRename& packet) if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET || !pet->HasPetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED) || pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() || - !petStable || !petStable->CurrentPet || petStable->CurrentPet->PetNumber != pet->GetCharmInfo()->GetPetNumber()) + !petStable || !petStable->GetCurrentPet() || petStable->GetCurrentPet()->PetNumber != pet->GetCharmInfo()->GetPetNumber()) return; PetNameInvalidReason res = ObjectMgr::CheckPetName(name); @@ -560,8 +560,8 @@ void WorldSession::HandlePetRename(WorldPackets::Pet::PetRename& packet) pet->RemovePetFlag(UNIT_PET_FLAG_CAN_BE_RENAMED); - petStable->CurrentPet->Name = name; - petStable->CurrentPet->WasRenamed = true; + petStable->GetCurrentPet()->Name = name; + petStable->GetCurrentPet()->WasRenamed = true; if (declinedname) { diff --git a/src/server/game/Server/Packets/NPCPackets.cpp b/src/server/game/Server/Packets/NPCPackets.cpp index fc7bd406588..1ddf5378c4e 100644 --- a/src/server/game/Server/Packets/NPCPackets.cpp +++ b/src/server/game/Server/Packets/NPCPackets.cpp @@ -224,5 +224,12 @@ void RequestStabledPets::Read() { _worldPacket >> StableMaster; } + +void SetPetSlot::Read() +{ + _worldPacket >> PetNumber; + _worldPacket >> DestSlot; + _worldPacket >> StableMaster; +} } } diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h index bf16a355b42..320594ffd2f 100644 --- a/src/server/game/Server/Packets/NPCPackets.h +++ b/src/server/game/Server/Packets/NPCPackets.h @@ -264,6 +264,18 @@ namespace WorldPackets ObjectGuid StableMaster; }; + + class SetPetSlot final : public ClientPacket + { + public: + SetPetSlot(WorldPacket&& packet) : ClientPacket(CMSG_SET_PET_SLOT, std::move(packet)) { } + + void Read() override; + + ObjectGuid StableMaster; + uint32 PetNumber = 0; + uint8 DestSlot = 0; + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 22b2b2bef06..6be93de627c 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -808,7 +808,7 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_SET_LOOT_SPECIALIZATION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetLootSpecialization); DEFINE_HANDLER(CMSG_SET_PARTY_ASSIGNMENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetPartyAssignment); DEFINE_HANDLER(CMSG_SET_PARTY_LEADER, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSetPartyLeaderOpcode); - DEFINE_HANDLER(CMSG_SET_PET_SLOT, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_SET_PET_SLOT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSetPetSlot); DEFINE_HANDLER(CMSG_SET_PLAYER_DECLINED_NAMES, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleSetPlayerDeclinedNames); DEFINE_HANDLER(CMSG_SET_PREFERRED_CEMETERY, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_SET_PVP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetPvP); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index ace0b3931bb..3e71131fff0 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -547,6 +547,7 @@ namespace WorldPackets class SpiritHealerActivate; class TrainerBuySpell; class RequestStabledPets; + class SetPetSlot; } namespace Party @@ -1403,9 +1404,7 @@ class TC_GAME_API WorldSession void HandleNpcTextQueryOpcode(WorldPackets::Query::QueryNPCText& packet); void HandleBinderActivateOpcode(WorldPackets::NPC::Hello& packet); void HandleRequestStabledPets(WorldPackets::NPC::RequestStabledPets& packet); - void HandleStablePet(WorldPacket& recvPacket); - void HandleUnstablePet(WorldPacket& recvPacket); - void HandleStableSwapPet(WorldPacket& recvPacket); + void HandleSetPetSlot(WorldPackets::NPC::SetPetSlot& setPetSlot); 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 95fd1f1a61d..03c1fc92a9e 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -5928,13 +5928,23 @@ SpellCastResult Spell::CheckCast(bool strict, int32* param1 /*= nullptr*/, int32 } case SPELL_EFFECT_RESURRECT_PET: { - Unit* unitCaster = m_caster->ToUnit(); - if (!unitCaster) + Player* playerCaster = m_caster->ToPlayer(); + if (!playerCaster || !playerCaster->GetPetStable()) return SPELL_FAILED_BAD_TARGETS; - Creature* pet = unitCaster->GetGuardianPet(); + Pet* pet = playerCaster->GetPet(); if (pet && pet->IsAlive()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + PetStable const* petStable = playerCaster->GetPetStable(); + auto deadPetItr = std::find_if(petStable->ActivePets.begin(), petStable->ActivePets.end(), [](Optional<PetStable::PetInfo> const& petInfo) + { + return petInfo && !petInfo->Health; + }); + + if (deadPetItr == petStable->ActivePets.end()) + return SPELL_FAILED_BAD_TARGETS; + break; } // This is generic summon effect @@ -5998,17 +6008,27 @@ SpellCastResult Spell::CheckCast(bool strict, int32* param1 /*= nullptr*/, int32 Player* playerCaster = unitCaster->ToPlayer(); if (playerCaster && playerCaster->GetPetStable()) { - std::pair<PetStable::PetInfo const*, PetSaveMode> info = Pet::GetLoadPetInfo(*playerCaster->GetPetStable(), spellEffectInfo.MiscValue, 0, false); - if (info.first) + Optional<PetSaveMode> petSlot; + if (!spellEffectInfo.MiscValue) { - if (info.first->Type == HUNTER_PET) + petSlot = PetSaveMode(spellEffectInfo.CalcValue()); + + // No pet can be summoned if any pet is dead + for (Optional<PetStable::PetInfo> const& activePet : playerCaster->GetPetStable()->ActivePets) { - if (!info.first->Health) + if (activePet && !activePet->Health) { playerCaster->SendTameFailure(PetTameResult::Dead); return SPELL_FAILED_DONT_REPORT; } + } + } + std::pair<PetStable::PetInfo const*, PetSaveMode> info = Pet::GetLoadPetInfo(*playerCaster->GetPetStable(), spellEffectInfo.MiscValue, 0, petSlot); + if (info.first) + { + if (info.first->Type == HUNTER_PET) + { CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(info.first->CreatureId); if (!creatureInfo || !creatureInfo->IsTameable(playerCaster->CanTameExoticPets())) { @@ -6031,6 +6051,21 @@ SpellCastResult Spell::CheckCast(bool strict, int32* param1 /*= nullptr*/, int32 break; } + case SPELL_EFFECT_DISMISS_PET: + { + Player* playerCaster = m_caster->ToPlayer(); + if (!playerCaster) + return SPELL_FAILED_BAD_TARGETS; + + Pet* pet = playerCaster->GetPet(); + if (!pet) + return SPELL_FAILED_NO_PET; + + if (!pet->IsAlive()) + return SPELL_FAILED_TARGETS_DEAD; + + break; + } case SPELL_EFFECT_SUMMON_PLAYER: { if (m_caster->GetTypeId() != TYPEID_PLAYER) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 2cdf9006079..edd59647a76 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2626,26 +2626,34 @@ void Spell::EffectSummonPet() return; } + Optional<PetSaveMode> petSlot; + if (!petentry) + petSlot = PetSaveMode(damage); + float x, y, z; owner->GetClosePoint(x, y, z, owner->GetCombatReach()); - Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), SUMMON_PET, 0); + bool isNew = false; + Pet* pet = owner->SummonPet(petentry, petSlot, x, y, z, owner->GetOrientation(), 0, &isNew); if (!pet) return; - if (m_caster->GetTypeId() == TYPEID_UNIT) + if (isNew) { - if (m_caster->ToCreature()->IsTotem()) - pet->SetReactState(REACT_AGGRESSIVE); - else - pet->SetReactState(REACT_DEFENSIVE); - } + if (m_caster->GetTypeId() == TYPEID_UNIT) + { + if (m_caster->ToCreature()->IsTotem()) + pet->SetReactState(REACT_AGGRESSIVE); + else + pet->SetReactState(REACT_DEFENSIVE); + } - pet->SetCreatedBySpell(m_spellInfo->Id); + pet->SetCreatedBySpell(m_spellInfo->Id); - // generate new name for summon pet - std::string new_name = sObjectMgr->GeneratePetName(petentry); - if (!new_name.empty()) - pet->SetName(new_name); + // generate new name for summon pet + std::string new_name = sObjectMgr->GeneratePetName(petentry); + if (!new_name.empty()) + pet->SetName(new_name); + } ExecuteLogEffectSummonObject(SpellEffectName(effectInfo->Effect), pet); } @@ -4252,9 +4260,17 @@ void Spell::EffectResurrectPet() if (!player->GetPet()) { + PetStable const* petStable = player->GetPetStable(); + auto deadPetItr = std::find_if(petStable->ActivePets.begin(), petStable->ActivePets.end(), [](Optional<PetStable::PetInfo> const& petInfo) + { + return petInfo && !petInfo->Health; + }); + + PetSaveMode slot = PetSaveMode(std::distance(petStable->ActivePets.begin(), deadPetItr)); + // Position passed to SummonPet is irrelevant with current implementation, // pet will be relocated without using these coords in Pet::LoadPetFromDB - player->SummonPet(0, 0.0f, 0.0f, 0.0f, 0.0f, SUMMON_PET, 0); + player->SummonPet(0, slot, 0.0f, 0.0f, 0.0f, 0.0f, 0); hadPet = false; } diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index ef3197d7119..29cd6f890b8 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -615,6 +615,15 @@ class spell_hun_tame_beast : public SpellScriptLoader { PrepareSpellScript(spell_hun_tame_beast_SpellScript); + static constexpr uint32 CallPetSpellIds[MAX_ACTIVE_PETS] = + { + 883, + 83242, + 83243, + 83244, + 83245, + }; + SpellCastResult CheckCast() { Player* caster = GetCaster()->ToPlayer(); @@ -633,13 +642,25 @@ class spell_hun_tame_beast : public SpellScriptLoader if (!target->GetCreatureTemplate()->IsTameable(caster->CanTameExoticPets())) return SPELL_FAILED_BAD_TARGETS; - PetStable const* petStable = caster->GetPetStable(); - if (petStable) + if (PetStable const* petStable = caster->GetPetStable()) { - if (petStable->CurrentPet) + if (petStable->CurrentPetIndex) return SPELL_FAILED_ALREADY_HAVE_SUMMON; - if (petStable->GetUnslottedHunterPet()) + auto freeSlotItr = std::find_if(petStable->ActivePets.begin(), petStable->ActivePets.end(), [](Optional<PetStable::PetInfo> const& petInfo) + { + return !petInfo.has_value(); + }); + + if (freeSlotItr == petStable->ActivePets.end()) + { + caster->SendTameFailure(PetTameResult::TooMany); + return SPELL_FAILED_DONT_REPORT; + } + + // Check for known Call Pet X spells + std::size_t freeSlotIndex = std::distance(petStable->ActivePets.begin(), freeSlotItr); + if (!caster->HasSpell(CallPetSpellIds[freeSlotIndex])) { caster->SendTameFailure(PetTameResult::TooMany); return SPELL_FAILED_DONT_REPORT; |