diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 75720a7c2..f74a464af 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -540,16 +540,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE id = ?", CONNECTION_ASYNC); // Pet - PrepareStatement(CHAR_SEL_PET_SLOTS, "SELECT owner, slot FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SLOTS_DETAIL, "SELECT owner, id, entry, level, name FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_ENTRY, "SELECT entry, slot FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SLOT_BY_ID, "SELECT slot, entry FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SPELL_LIST, "SELECT DISTINCT pet_spell.spell FROM pet_spell, character_pet WHERE character_pet.owner = ? AND character_pet.id = pet_spell.guid AND character_pet.id <> ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_PET, "SELECT id FROM character_pet WHERE owner = ? AND id <> ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_PET_IDS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_ADD_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, category, time FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC); @@ -561,15 +556,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, " "base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) ", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_SYNS, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ? AND id <> ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index ca39783c9..cf286e723 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -468,23 +468,12 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_PET_SPELLS, CHAR_DEL_CHAR_PET_BY_OWNER, CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, - CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, - CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_SYNS, - CHAR_SEL_PET_SLOTS, - CHAR_SEL_PET_SLOTS_DETAIL, - CHAR_SEL_PET_ENTRY, - CHAR_SEL_PET_SLOT_BY_ID, - CHAR_SEL_PET_SPELL_LIST, - CHAR_SEL_CHAR_PET, CHAR_SEL_CHAR_PETS, - CHAR_SEL_CHAR_PET_BY_ENTRY, - CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, - CHAR_SEL_CHAR_PET_BY_SLOT, + CHAR_SEL_CHAR_PET_IDS, CHAR_DEL_CHAR_PET_DECLINEDNAME, CHAR_ADD_CHAR_PET_DECLINEDNAME, + CHAR_SEL_PET_DECLINED_NAME, CHAR_UPD_CHAR_PET_NAME, - CHAR_UDP_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, - CHAR_UDP_CHAR_PET_SLOT_BY_SLOT, CHAR_UPD_CHAR_PET_SLOT_BY_ID, CHAR_DEL_CHAR_PET_BY_ID, CHAR_DEL_CHAR_PET_BY_SLOT, diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 34f52cd3c..e0af58158 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -159,6 +159,7 @@ DBCStorage sStableSlotPricesStore(StableSlotPricesfmt); DBCStorage sSummonPropertiesStore(SummonPropertiesfmt); DBCStorage sTalentStore(TalentEntryfmt); TalentSpellPosMap sTalentSpellPosMap; +std::unordered_set sPetTalentSpells; DBCStorage sTalentTabStore(TalentTabEntryfmt); // store absolute bit position for first rank for talent inspect @@ -474,10 +475,23 @@ void LoadDBCStores(const std::string& dataPath) // create talent spells set for (TalentEntry const* talentInfo : sTalentStore) + { + TalentTabEntry const* talentTab = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + for (uint8 j = 0; j < MAX_TALENT_RANK; ++j) + { if (talentInfo->RankID[j]) + { sTalentSpellPosMap[talentInfo->RankID[j]] = TalentSpellPos(talentInfo->TalentID, j); + if (talentTab && talentTab->petTalentMask) + { + sPetTalentSpells.insert(talentInfo->RankID[j]); + } + } + } + } + // prepare fast data access to bit pos of talent ranks for use at inspecting { // now have all max ranks (and then bit amount used for store talent ranks in inspect) diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index 2ff299c99..7294c32d5 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -23,6 +23,7 @@ #include "DBCStructure.h" #include #include +#include typedef std::list SimpleFactionsList; typedef std::vector FlyByCameraCollection; @@ -155,6 +156,7 @@ extern DBCStorage sSpellItemEnchantmentStore; extern DBCStorage sSpellItemEnchantmentConditionStore; extern SpellCategoryStore sSpellsByCategoryStore; extern PetFamilySpellsStore sPetFamilySpellsStore; +extern std::unordered_set sPetTalentSpells; extern DBCStorage sSpellRadiusStore; extern DBCStorage sSpellRangeStore; extern DBCStorage sSpellRuneCostStore; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 712a5b751..918e889b5 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -48,6 +48,7 @@ #include "WaypointMovementGenerator.h" #include "World.h" #include "WorldPacket.h" +#include "Pet.h" // TODO: this import is not necessary for compilation and marked as unused by the IDE // however, for some reasons removing it would cause a damn linking issue @@ -854,7 +855,7 @@ void Creature::Regenerate(Powers power) if (Powers((*i)->GetMiscValue()) == power) AddPct(addvalue, (*i)->GetAmount()); - addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * (power == POWER_FOCUS ? PET_FOCUS_REGEN_INTERVAL : CREATURE_REGEN_INTERVAL) / (5 * IN_MILLISECONDS); + addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * (power == POWER_FOCUS ? PET_FOCUS_REGEN_INTERVAL.count() : CREATURE_REGEN_INTERVAL) / (5 * IN_MILLISECONDS); ModifyPower(power, int32(addvalue)); } diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 114e73798..c6b8f3aeb 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -33,12 +33,15 @@ #define MAX_KILL_CREDIT 2 #define CREATURE_REGEN_INTERVAL 2 * IN_MILLISECONDS -#define PET_FOCUS_REGEN_INTERVAL 4 * IN_MILLISECONDS #define MAX_CREATURE_QUEST_ITEMS 6 #define MAX_EQUIPMENT_ITEMS 3 + +constexpr Milliseconds PET_FOCUS_REGEN_INTERVAL = 4s; + enum class VisibilityDistanceType : uint8; + // TODO: Implement missing flags from TC in places that custom flags from xinef&pussywizzard use flag values. // EnumUtils: DESCRIBE THIS enum CreatureFlagsExtra : uint32 diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 29bcc7442..6b11b4566 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -26,6 +26,7 @@ #include "Log.h" #include "ObjectMgr.h" #include "Player.h" +#include "QueryHolder.h" #include "ScriptMgr.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" @@ -36,11 +37,24 @@ #include "WorldSession.h" Pet::Pet(Player* owner, PetType type) : Guardian(nullptr, owner ? owner->GetGUID() : ObjectGuid::Empty, true), - m_usedTalentCount(0), m_removed(false), m_owner(owner), - m_happinessTimer(PET_LOSE_HAPPINES_INTERVAL), m_petType(type), m_duration(0), - m_auraRaidUpdateMask(0), m_loading(false), m_petRegenTimer(PET_FOCUS_REGEN_INTERVAL), m_declinedname(nullptr), m_tempspellTarget(nullptr), m_tempoldTarget(nullptr), m_tempspellIsPositive(false), m_tempspell(0), asynchLoadType(PET_LOAD_DEFAULT) + m_usedTalentCount(0), + m_removed(false), + m_owner(owner), + m_happinessTimer(PET_LOSE_HAPPINES_INTERVAL), + m_petType(type), + m_duration(0), + m_auraRaidUpdateMask(0), + m_loading(false), + m_petRegenTimer(PET_FOCUS_REGEN_INTERVAL), + m_tempspellTarget(nullptr), + m_tempoldTarget(nullptr), + m_tempspellIsPositive(false), + m_tempspell(0) { + ASSERT(m_owner && m_owner->GetTypeId() == TYPEID_PLAYER); + m_unitTypeMask |= UNIT_MASK_PET; + if (type == HUNTER_PET) m_unitTypeMask |= UNIT_MASK_HUNTER_PET; @@ -53,11 +67,6 @@ Pet::Pet(Player* owner, PetType type) : Guardian(nullptr, owner ? owner->GetGUID m_name = "Pet"; } -Pet::~Pet() -{ - delete m_declinedname; -} - void Pet::AddToWorld() { ///- Register the pet for guid lookup @@ -111,128 +120,372 @@ void Pet::RemoveFromWorld() } } -SpellCastResult Pet::TryLoadFromDB(Player* owner, bool current /*= false*/, PetType mandatoryPetType /*= MAX_PET_TYPE*/, bool checkDead /*= false*/) +class PetLoadQueryHolder : public CharacterDatabaseQueryHolder { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_SYNS); - stmt->setUInt32(0, owner->GetGUID().GetCounter()); - stmt->setUInt8(1, uint8(current ? PET_SAVE_AS_CURRENT : PET_SAVE_NOT_IN_SLOT)); - - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (!result) - return SPELL_FAILED_NO_PET; - - Field* fields = result->Fetch(); - - uint32 petentry = fields[1].GetUInt32(); - uint32 savedHealth = fields[10].GetUInt32(); - uint32 summon_spell_id = fields[15].GetUInt32(); - auto petType = PetType(fields[16].GetUInt8()); - - // update for case of current pet "slot = 0" - if (!petentry) - return SPELL_FAILED_NO_PET; - - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petentry); - if (!creatureInfo) +public: + enum { - LOG_ERROR("entities.pet", "Pet entry %u does not exist but used at pet load (owner: %s).", petentry, owner->GetName().c_str()); - return SPELL_FAILED_NO_PET; + DECLINED_NAMES, + AURAS, + SPELLS, + COOLDOWNS, + + MAX + }; + + PetLoadQueryHolder(ObjectGuid::LowType ownerGuid, uint32 petNumber) + { + SetSize(MAX); + + CharacterDatabasePreparedStatement* stmt = nullptr; + + 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); + } +}; + +std::pair Pet::GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, bool current) +{ + if (petnumber) + { + // Known petnumber entry + 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.PetNumber == petnumber) + return { &pet, PET_SAVE_NOT_IN_SLOT }; + } + else if (current) + { + // Current pet (slot 0) + 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) + 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") + if (stable.CurrentPet) + return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT }; + + if (!stable.UnslottedPets.empty()) + return { &stable.UnslottedPets.front(), PET_SAVE_NOT_IN_SLOT }; } - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon_spell_id); - - bool isTemporarySummoned = spellInfo && spellInfo->GetDuration() > 0; - - // check temporary summoned pets like mage water elemental - if (current && isTemporarySummoned) - return SPELL_FAILED_NO_PET; - - if (!checkDead) - { - if (!savedHealth) - { - owner->ToPlayer()->SendTameFailure(PET_TAME_DEAD); - return SPELL_FAILED_TARGETS_DEAD; - } - } - else if (savedHealth) - { - return SPELL_FAILED_TARGET_NOT_DEAD; - } - - if (mandatoryPetType != MAX_PET_TYPE && petType != mandatoryPetType) - return SPELL_FAILED_BAD_TARGETS; - - return SPELL_CAST_OK; + return { nullptr, PET_SAVE_AS_DELETED }; } -bool Pet::LoadPetFromDB(Player* owner, uint8 asynchLoadType, uint32 petentry, uint32 petnumber, bool current, AsynchPetSummon* info) +bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current) { + m_loading = true; + + PetStable* petStable = ASSERT_NOTNULL(owner->GetPetStable()); + + ObjectGuid::LowType ownerid = owner->GetGUID().GetCounter(); + std::pair info = GetLoadPetInfo(*petStable, petEntry, petnumber, current); + PetStable::PetInfo const* petInfo = info.first; + PetSaveMode slot = info.second; + if (!petInfo) + { + m_loading = false; + return false; + } + + // Don't try to reload the current pet + if (petStable->CurrentPet && owner->GetPet() && petStable->CurrentPet.value().PetNumber == petInfo->PetNumber) + return false; + // we are loading pet at that moment if (owner->IsSpectator() || owner->GetPet() || !owner->IsInWorld() || !owner->FindMap()) return false; bool forceLoadFromDB = false; - sScriptMgr->OnBeforeLoadPetFromDB(owner, petentry, petnumber, current, forceLoadFromDB); + sScriptMgr->OnBeforeLoadPetFromDB(owner, petEntry, petnumber, current, forceLoadFromDB); - if (!forceLoadFromDB) - if (owner->getClass() == CLASS_DEATH_KNIGHT && !owner->CanSeeDKPet()) // DK Pet exception + if (!forceLoadFromDB && (owner->getClass() == CLASS_DEATH_KNIGHT && !owner->CanSeeDKPet())) // DK Pet exception + return false; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(petInfo->CreatedBySpellId); + + bool isTemporarySummon = spellInfo && spellInfo->GetDuration() > 0; + if (current && isTemporarySummon) + return false; + + if (petInfo->Type == HUNTER_PET) + { + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petInfo->CreatureId); + if (!creatureInfo || !creatureInfo->IsTameable(owner->CanTameExoticPets())) return false; - - ObjectGuid::LowType ownerGuid = owner->GetGUID().GetCounter(); - CharacterDatabasePreparedStatement* stmt; - - if (petnumber) - { - // Known petnumber entry - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY); - stmt->setUInt32(0, ownerGuid); - stmt->setUInt32(1, petnumber); } - else if (current) + + if (current && owner->IsPetNeedBeTemporaryUnsummoned()) { - // Current pet (slot 0) - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT); - stmt->setUInt32(0, ownerGuid); - stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT)); + owner->SetTemporaryUnsummonedPetNumber(petInfo->PetNumber); + return false; } - else if (petentry) + + Map* map = owner->GetMap(); + ObjectGuid::LowType guid = map->GenerateLowGuid(); + + if (!Create(guid, map, owner->GetPhaseMask(), petInfo->CreatureId, petInfo->PetNumber)) + return false; + + setPetType(petInfo->Type); + SetFaction(owner->GetFaction()); + SetUInt32Value(UNIT_CREATED_BY_SPELL, petInfo->CreatedBySpellId); + + if (IsCritter()) { - // 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, ownerGuid); - stmt->setUInt32(1, petentry); - stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt8(3, uint8(PET_SAVE_LAST_STABLE_SLOT)); + float px, py, pz; + owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, GetFollowAngle()); + Relocate(px, py, pz, owner->GetOrientation()); + + if (!IsPositionValid()) + { + LOG_ERROR("entities.pet", "Pet%s not loaded. Suggested coordinates isn't valid (X: %f Y: %f)", + GetGUID().ToString().c_str(), GetPositionX(), GetPositionY()); + return false; + } + + UpdatePositionData(); + map->AddToMap(ToCreature(), true); + return true; + } + + if (getPetType() == HUNTER_PET || GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD) + GetCharmInfo()->SetPetNumber(petInfo->PetNumber, IsPermanentPetFor(owner)); // Show pet details tab (Shift+P) only for hunter pets, demons or undead + else + GetCharmInfo()->SetPetNumber(petInfo->PetNumber, false); + + SetDisplayId(petInfo->DisplayId); + SetNativeDisplayId(petInfo->DisplayId); + UpdatePositionData(); + uint8 petlevel = petInfo->Level; + SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + SetName(petInfo->Name); + + switch (getPetType()) + { + case SUMMON_PET: + petlevel = owner->getLevel(); + + if (IsPetGhoul()) + SetUInt32Value(UNIT_FIELD_BYTES_0, 0x400); // class = rogue + else + SetUInt32Value(UNIT_FIELD_BYTES_0, 0x800); // class = mage + + SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); // this enables popup window (pet dismiss, cancel) + break; + case HUNTER_PET: + SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100); // class = warrior, gender = none, power = focus + SetSheath(SHEATH_STATE_MELEE); + SetByteFlag(UNIT_FIELD_BYTES_2, 2, 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, GetCreatePowers(POWER_HAPPINESS)); + SetPower(POWER_HAPPINESS, petInfo->Happiness); + setPowerType(POWER_FOCUS); + break; + default: + if (!IsPetGhoul()) + LOG_ERROR("entities.pet", "Pet have incorrect type (%u) for pet loading.", getPetType()); + break; + } + + SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(nullptr))); // cast can't be helped here + SetCreatorGUID(owner->GetGUID()); + + InitStatsForLevel(petlevel); + SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, petInfo->Experience); + + SynchronizeLevelWithOwner(); + + // Set pet's position after setting level, its size depends on it + float px, py, pz; + owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, GetFollowAngle()); + Relocate(px, py, pz, owner->GetOrientation()); + if (!IsPositionValid()) + { + LOG_ERROR("entities.pet", "Pet %s not loaded. Suggested coordinates isn't valid (X: %f Y: %f)", + GetGUID().ToString().c_str(), GetPositionX(), GetPositionY()); + return false; + } + + 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)); + //SetHealth(GetMaxHealth()); } else { - // Any current or other non-stabled pet (for hunter "call pet") - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_SLOT); - stmt->setUInt32(0, ownerGuid); - stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt8(2, uint8(PET_SAVE_LAST_STABLE_SLOT)); - } - - // load them asynchronously - { - WorldSession* mySess = owner->GetSession(); - mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt) - .WithPreparedCallback([mySess, asynchLoadType, info, owner](PreparedQueryResult result) + uint32 savedhealth = petInfo->Health; + uint32 savedmana = petInfo->Mana; + if (!savedhealth && getPetType() == HUNTER_PET) + setDeathState(JUST_DIED); + else { - // process only if player is in world (teleport crashes?) - // otherwise wait with result till he logs in - uint8 loadResult = mySess->HandleLoadPetFromDBFirstCallback(result, asynchLoadType, info); - - if (loadResult != PET_LOAD_OK) - Pet::HandleAsynchLoadFailed(info, owner, asynchLoadType, loadResult); - })); + SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth); + SetPower(POWER_MANA, savedmana > GetMaxPower(POWER_MANA) ? GetMaxPower(POWER_MANA) : savedmana); + } } + // set current pet as current + // 0=current + // 1..MAX_PET_STABLES in stable slot + // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning)) + if (slot == PET_SAVE_NOT_IN_SLOT) + { + uint32 petInfoNumber = petInfo->PetNumber; + if (petStable->CurrentPet) + 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(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(); + } + else if (PET_SAVE_FIRST_STABLE_SLOT <= slot && slot <= PET_SAVE_LAST_STABLE_SLOT) + { + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional const& pet) + { + return pet && pet->PetNumber == petnumber; + }); + + ASSERT(stabledPet != petStable->StabledPets.end()); + + std::swap(*stabledPet, petStable->CurrentPet); + + // old petInfo pointer is no longer valid, refresh it + petInfo = &petStable->CurrentPet.value(); + } + + // 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 (petInfo->CreatedBySpellId) + { + WorldPacket data(SMSG_SPELL_GO, (8 + 8 + 4 + 4 + 2)); + data << owner->GetPackGUID(); + data << owner->GetPackGUID(); + data << uint8(0); + data << uint32(petInfo->CreatedBySpellId); + data << uint32(256); // CAST_FLAG_UNKNOWN3 + data << uint32(0); + owner->SendMessageToSet(&data, true); + } + + owner->SetMinion(this, true); + + if (!isTemporarySummon) + m_charmInfo->LoadPetActionBar(petInfo->ActionBar); + + map->AddToMap(ToCreature(), true); + + //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(std::make_shared(ownerid, petInfo->PetNumber))) + .AfterComplete([this, owner, session = owner->GetSession(), isTemporarySummon, current, lastSaveTime = petInfo->LastSaveTime](SQLQueryHolderBase const& holder) + { + if (session->GetPlayer() != owner || owner->GetPet() != this) + return; + + // passing previous checks ensure that 'this' is still valid + if (m_removed) + return; + + InitTalentForLevel(); // set original talents points before spell loading + + uint32 timediff = uint32(time(nullptr) - lastSaveTime); + _LoadAuras(holder.GetPreparedResult(PetLoadQueryHolder::AURAS), timediff); + + // 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 + _LoadSpellCooldowns(holder.GetPreparedResult(PetLoadQueryHolder::COOLDOWNS)); + LearnPetPassives(); + InitLevelupSpellsForLevel(); + if (GetMap()->IsBattleArena()) + RemoveArenaAuras(); + + CastPetAuras(current); + } + + CleanupActionBar(); // remove unknown spells from action bar after load + + LOG_DEBUG("entities.pet", "New Pet has %s", GetGUID().ToString().c_str()); + + owner->PetSpellInitialize(); + owner->SendTalentsInfoData(true); + + if (owner->GetGroup()) + owner->SetGroupUpdateFlag(GROUP_UPDATE_PET); + + if (getPetType() == HUNTER_PET) + { + if (PreparedQueryResult result = holder.GetPreparedResult(PetLoadQueryHolder::DECLINED_NAMES)) + { + m_declinedname = std::make_unique(); + Field* fields = result->Fetch(); + for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + m_declinedname->name[i] = fields[i].GetString(); + } + } + + // must be after SetMinion (owner guid check) + //LoadTemplateImmunities(); + //LoadMechanicTemplateImmunity(); + m_loading = false; + }); + return true; } -void Pet::SavePetToDB(PetSaveMode mode, bool logout) +void Pet::SavePetToDB(PetSaveMode mode) { // not save not player pets if (!GetOwnerGUID().IsPlayer()) @@ -260,14 +513,14 @@ void Pet::SavePetToDB(PetSaveMode mode, bool logout) CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); // save auras before possibly removing them - _SaveAuras(trans, logout); + _SaveAuras(trans); // stable and not in slot saves if (mode > PET_SAVE_AS_CURRENT) RemoveAllAuras(); _SaveSpells(trans); - _SaveSpellCooldowns(trans, logout); + _SaveSpellCooldowns(trans); CharacterDatabase.CommitTransaction(trans); // current/stable/not_in_slot @@ -281,16 +534,6 @@ void Pet::SavePetToDB(PetSaveMode mode, bool logout) 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_UDP_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)) { @@ -302,6 +545,11 @@ void Pet::SavePetToDB(PetSaveMode mode, bool logout) } // 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_REP_CHAR_PET); stmt->setUInt32(0, m_charmInfo->GetPetNumber()); stmt->setUInt32(1, GetEntry()); @@ -319,12 +567,7 @@ void Pet::SavePetToDB(PetSaveMode mode, bool logout) stmt->setUInt32(13, curmana); stmt->setUInt32(14, GetPower(POWER_HAPPINESS)); stmt->setUInt32(15, time(nullptr)); - - std::ostringstream ss; - for (uint32 i = ACTION_BAR_INDEX_START; i < ACTION_BAR_INDEX_END; ++i) - ss << uint32(m_charmInfo->GetActionBarEntry(i)->GetType()) << ' ' << uint32(m_charmInfo->GetActionBarEntry(i)->GetAction()) << ' '; - - stmt->setString(16, ss.str()); + stmt->setString(16, actionBar); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); @@ -392,6 +635,8 @@ void Pet::setDeathState(DeathState s, bool /*despawn = false*/) void Pet::Update(uint32 diff) { + auto _diff = Milliseconds(diff); + if (m_removed) // pet already removed, just wait in remove queue, no updates return; @@ -424,16 +669,17 @@ void Pet::Update(uint32 diff) { if (owner->GetPetGUID() != GetGUID()) { - LOG_ERROR("entities.pet", "Pet %u is not pet of owner %s, removed", GetEntry(), m_owner->GetName().c_str()); - Remove(getPetType() == HUNTER_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT); + LOG_ERROR("entities.pet", "Pet %u is not pet of owner %s, removed", GetEntry(), GetOwner()->GetName().c_str()); + ASSERT(getPetType() != HUNTER_PET, "Unexpected unlinked pet found for owner %s", owner->GetSession()->GetPlayerInfo().c_str()); + Remove(PET_SAVE_NOT_IN_SLOT); return; } } - if (m_duration > 0) + if (m_duration > 0s) { - if (uint32(m_duration) > diff) - m_duration -= diff; + if (m_duration > _diff) + m_duration -= _diff; else { Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT); @@ -445,15 +691,15 @@ void Pet::Update(uint32 diff) // xinef: just check if we can update focus in current period if (getPowerType() == POWER_FOCUS) { - m_petRegenTimer -= diff; - if (m_petRegenTimer <= int32(0)) + m_petRegenTimer -= _diff; + if (m_petRegenTimer <= 0s) { m_petRegenTimer += PET_FOCUS_REGEN_INTERVAL; Regenerate(POWER_FOCUS); } } - if (m_tempspell != 0) + if (m_tempspell) { Unit* tempspellTarget = m_tempspellTarget; Unit* tempoldTarget = m_tempoldTarget; @@ -613,7 +859,7 @@ HappinessState Pet::GetHappinessState() void Pet::Remove(PetSaveMode mode, bool returnreagent) { - m_owner->RemovePet(this, mode, returnreagent); + GetOwner()->RemovePet(this, mode, returnreagent); } void Pet::GivePetXP(uint32 xp) @@ -1205,7 +1451,7 @@ void Pet::_LoadSpellCooldowns(PreparedQueryResult result) } } -void Pet::_SaveSpellCooldowns(CharacterDatabaseTransaction trans, bool logout) +void Pet::_SaveSpellCooldowns(CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS); stmt->setUInt32(0, m_charmInfo->GetPetNumber()); @@ -1226,7 +1472,7 @@ void Pet::_SaveSpellCooldowns(CharacterDatabaseTransaction trans, bool logout) { m_CreatureSpellCooldowns.erase(itr2); } - else if (itr2->second.end <= infTime && (logout || itr2->second.end > (curMSTime + 30 * IN_MILLISECONDS))) + else if (itr2->second.end <= infTime && (itr2->second.end > (curMSTime + 30 * IN_MILLISECONDS))) { uint32 cooldown = ((itr2->second.end - curMSTime) / IN_MILLISECONDS) + curTime; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL_COOLDOWN); @@ -1379,7 +1625,7 @@ void Pet::_LoadAuras(PreparedQueryResult result, uint32 timediff) } } -void Pet::_SaveAuras(CharacterDatabaseTransaction trans, bool logout) +void Pet::_SaveAuras(CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURAS); stmt->setUInt32(0, m_charmInfo->GetPetNumber()); @@ -1392,7 +1638,7 @@ void Pet::_SaveAuras(CharacterDatabaseTransaction trans, bool logout) continue; Aura* aura = itr->second; - if (!logout && aura->GetDuration() < 60 * IN_MILLISECONDS) + if (aura->GetDuration() < 60 * IN_MILLISECONDS) continue; // dont save infinite negative auras! (lavas, transformations etc) @@ -1472,9 +1718,7 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel LOG_ERROR("entities.pet", "Pet::addSpell: Non-existed in SpellStore spell #%u request, deleting for all pets in `pet_spell`.", spellId); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_PET_SPELL); - stmt->setUInt32(0, spellId); - CharacterDatabase.Execute(stmt); } else @@ -1483,7 +1727,7 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel return false; } - PetSpellMap::iterator itr = m_spells.find(spellId); + auto const& itr = m_spells.find(spellId); if (itr != m_spells.end()) { if (itr->second.state == PETSPELL_REMOVED) @@ -1522,10 +1766,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->RankID) { // skip learning spell and no rank spell case - uint32 rankSpellId = talentInfo->RankID[i]; if (!rankSpellId || rankSpellId == spellId) continue; @@ -1538,12 +1781,12 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel } else if (spellInfo->IsRanked()) { - for (PetSpellMap::const_iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2) + for (auto const& [spellID, petSpell] : m_spells) { - if (itr2->second.state == PETSPELL_REMOVED) + if (petSpell.state == PETSPELL_REMOVED) continue; - SpellInfo const* oldRankSpellInfo = sSpellMgr->GetSpellInfo(itr2->first); + SpellInfo const* oldRankSpellInfo = sSpellMgr->GetSpellInfo(spellID); if (!oldRankSpellInfo) continue; @@ -1553,12 +1796,12 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel // replace by new high rank if (spellInfo->IsHighRankOf(oldRankSpellInfo)) { - newspell.active = itr2->second.active; + newspell.active = petSpell.active; if (newspell.active == ACT_ENABLED) ToggleAutocast(oldRankSpellInfo, false); - unlearnSpell(itr2->first, false, false); + unlearnSpell(spellID, false, false); break; } // ignore new lesser rank @@ -1611,9 +1854,10 @@ bool Pet::learnSpell(uint32 spell_id) { WorldPacket data(SMSG_PET_LEARNED_SPELL, 4); data << uint32(spell_id); - m_owner->GetSession()->SendPacket(&data); - m_owner->PetSpellInitialize(); + GetOwner()->GetSession()->SendPacket(&data); + GetOwner()->PetSpellInitialize(); } + return true; } @@ -1640,9 +1884,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* spellEntry = sSpellMgr->GetSpellInfo(defSpells->spellid[i]); + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spellId); if (!spellEntry) continue; @@ -1664,10 +1908,12 @@ bool Pet::unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab) { WorldPacket data(SMSG_PET_REMOVED_SPELL, 4); data << uint32(spell_id); - m_owner->GetSession()->SendPacket(&data); + GetOwner()->GetSession()->SendPacket(&data); } + return true; } + return false; } @@ -1781,12 +2027,10 @@ bool Pet::resetTalents() for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i) { TalentEntry const* talentInfo = sTalentStore.LookupEntry(i); - if (!talentInfo) continue; TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); - if (!talentTabInfo) continue; @@ -1794,7 +2038,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->RankID) { for (PetSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end();) { @@ -1807,7 +2051,7 @@ bool Pet::resetTalents() uint32 itrFirstId = sSpellMgr->GetFirstSpellInChain(itr->first); // unlearn if first rank is talent or learned by talent - if (itrFirstId == talentInfo->RankID[j]) + if (itrFirstId == talentSpellId) { unlearnSpell(itr->first, false); itr = m_spells.begin(); @@ -1826,78 +2070,65 @@ bool Pet::resetTalents() return true; } -void Pet::resetTalentsForAllPetsOf(Player* owner, Pet* online_pet /*= nullptr*/) +void Pet::resetTalentsForAllPetsOf(Player* owner, Pet* onlinePet /*= nullptr*/) { // not need after this call if (owner->ToPlayer()->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS)) owner->ToPlayer()->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true); // reset for online - if (online_pet) - online_pet->resetTalents(); + if (onlinePet) + onlinePet->resetTalents(); - // now need only reset for offline pets (all pets except online case) - uint32 except_petnumber = online_pet ? online_pet->GetCharmInfo()->GetPetNumber() : 0; - - // xinef: sync query - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET); - stmt->setUInt32(0, owner->GetGUID().GetCounter()); - stmt->setUInt32(1, except_petnumber); - PreparedQueryResult resultPets = CharacterDatabase.Query(stmt); - - // no offline pets - if (!resultPets) + PetStable* petStable = owner->GetPetStable(); + if (!petStable) return; - // xinef: sync query - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_LIST); - stmt->setUInt32(0, owner->GetGUID().GetCounter()); - stmt->setUInt32(1, except_petnumber); - PreparedQueryResult result = CharacterDatabase.Query(stmt); + std::unordered_set petIds; + if (petStable->CurrentPet) + petIds.insert(petStable->CurrentPet->PetNumber); - if (!result) + for (Optional const& stabledPet : petStable->StabledPets) + if (stabledPet) + petIds.insert(stabledPet->PetNumber); + + for (PetStable::PetInfo const& unslottedPet : petStable->UnslottedPets) + petIds.insert(unslottedPet.PetNumber); + + // 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; - } while (result->NextRow()); - - if (!need_execute) - return; + need_comma = true; + } ss << ')'; @@ -2050,8 +2281,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); } } @@ -2108,234 +2339,6 @@ void Pet::SynchronizeLevelWithOwner() } } -void Pet::HandleAsynchLoadSucceed() -{ - Player* owner = GetOwner(); - if (!owner) - return; - - if (GetAsynchLoadType() == PET_LOAD_HANDLE_UNSTABLE_CALLBACK) - { - if (Player* player = owner->ToPlayer()) - player->GetSession()->SendStableResult(0x09 /*STABLE_SUCCESS_UNSTABLE*/); - } - else// if (GetAsynchLoadType() == PET_LOAD_BG_RESURRECT || GetAsynchLoadType() == PET_LOAD_SUMMON_PET || GetAsynchLoadType() == PET_LOAD_SUMMON_DEAD_PET) - { - // Remove Demonic Sacrifice auras (known pet) - Unit::AuraEffectList const& auraClassScripts = owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); - for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();) - { - if ((*itr)->GetMiscValue() == 2228) - { - owner->RemoveAurasDueToSpell((*itr)->GetId()); - itr = auraClassScripts.begin(); - } - else - ++itr; - } - - SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); - if (GetAsynchLoadType() == PET_LOAD_SUMMON_DEAD_PET) - { - setDeathState(ALIVE); - ClearUnitState(uint32(UNIT_STATE_ALL_STATE & ~(UNIT_STATE_POSSESSED))); // xinef: added just in case... some linked auras and so on - SetHealth(CountPctFromMaxHealth(15)); // Xinef: well, only two pet reviving spells exist and both revive with 15% - SavePetToDB(PET_SAVE_AS_CURRENT, false); - } - } - - // xinef: resurrect with full health if resurrected by BG / BF spirit - if (GetAsynchLoadType() == PET_LOAD_BG_RESURRECT) - SetHealth(GetMaxHealth()); - - // xinef: We are summoned in arena / battleground, remove all positive auras - // xinef: and set health to full if in preparation phase - if (GetMap()->IsBattlegroundOrArena()) - { - if (GetMap()->IsBattleArena()) - RemoveArenaAuras(); - - if (Player* player = owner->ToPlayer()) - if (Battleground* bg = player->GetBattleground()) - if (bg->GetStatus() == STATUS_WAIT_JOIN) - { - if (IsAlive()) - SetHealth(GetMaxHealth()); - - if (GetMap()->IsBattleground()) - CastSpell(this, SPELL_PREPARATION, true); - } - } - - // Fix aurastate auras, depending on health! - // Set aurastate manualy, prevents aura switching - if (HealthBelowPct(20)) - SetFlag(UNIT_FIELD_AURASTATE, 1 << (AURA_STATE_HEALTHLESS_20_PERCENT - 1)); - if (HealthBelowPct(35)) - SetFlag(UNIT_FIELD_AURASTATE, 1 << (AURA_STATE_HEALTHLESS_35_PERCENT - 1)); - if (HealthAbovePct(75)) - SetFlag(UNIT_FIELD_AURASTATE, 1 << (AURA_STATE_HEALTH_ABOVE_75_PERCENT - 1)); - - // unapply aura stats if dont meet requirements - AuraApplicationMap const& Auras = GetAppliedAuras(); - for (AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) - { - // we assume that all auras are applied now, aurastate was modfied MANUALY preventing any apply/unapply state switching - Aura* aura = itr->second->GetBase(); - SpellInfo const* m_spellInfo = aura->GetSpellInfo(); - if (m_spellInfo->CasterAuraState != AURA_STATE_HEALTHLESS_20_PERCENT && - m_spellInfo->CasterAuraState != AURA_STATE_HEALTHLESS_35_PERCENT && - m_spellInfo->CasterAuraState != AURA_STATE_HEALTH_ABOVE_75_PERCENT) - continue; - - if (!HasAuraState((AuraStateType)m_spellInfo->CasterAuraState)) - aura->HandleAllEffects(itr->second, AURA_EFFECT_HANDLE_REAL, false); - } - - SetAsynchLoadType(PET_LOAD_DEFAULT); - - // Warlock pet exception, check if owner is casting new summon spell - if (Spell* spell = owner->GetCurrentSpell(CURRENT_GENERIC_SPELL)) - if (spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_SUMMON_PET)) - CastSpell(this, 32752, true, nullptr, nullptr, GetGUID()); - - if (owner->NeedSendSpectatorData() && GetCreatureTemplate()->family) - { - ArenaSpectator::SendCommand_UInt32Value(owner->FindMap(), owner->GetGUID(), "PHP", (uint32)GetHealthPct()); - ArenaSpectator::SendCommand_UInt32Value(owner->FindMap(), owner->GetGUID(), "PET", GetCreatureTemplate()->family); - } -} - -void Pet::HandleAsynchLoadFailed(AsynchPetSummon* info, Player* player, uint8 asynchLoadType, uint8 loadResult) -{ - if (loadResult == PET_LOAD_ERROR && asynchLoadType == PET_LOAD_HANDLE_UNSTABLE_CALLBACK) - { - player->GetSession()->SendStableResult(0x06 /*STABLE_ERR_STABLE*/); - } - else if (loadResult == PET_LOAD_NO_RESULT && info && (asynchLoadType == PET_LOAD_SUMMON_PET || asynchLoadType == PET_LOAD_SUMMON_DEAD_PET)) - { - // xinef: petentry == 0 for hunter "call pet" (current pet summoned if any) - if (!info->m_entry || !player) - return; - - Pet* pet = new Pet(player, info->m_petType); - pet->Relocate(info->pos); // already validated (IsPositionValid) - - Map* map = player->GetMap(); - uint32 pet_number = sObjectMgr->GeneratePetNumber(); - if (!pet->Create(map->GenerateLowGuid(), map, player->GetPhaseMask(), info->m_entry, pet_number)) - { - LOG_ERROR("entities.pet", "no such creature entry %u", info->m_entry); - delete pet; - return; - } - - if (info->m_createdBySpell) - pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, info->m_createdBySpell); - pet->SetCreatorGUID(player->GetGUID()); - pet->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, player->GetFaction()); - - pet->setPowerType(POWER_MANA); - pet->SetUInt32Value(UNIT_NPC_FLAGS, 0); - pet->SetUInt32Value(UNIT_FIELD_BYTES_1, 0); - pet->InitStatsForLevel(player->getLevel()); - - player->SetMinion(pet, true); - - if (info->m_petType == SUMMON_PET) - { - if (pet->GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || pet->GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD) - pet->GetCharmInfo()->SetPetNumber(pet_number, true); // Show pet details tab (Shift+P) only for demons & undead - else - pet->GetCharmInfo()->SetPetNumber(pet_number, false); - - pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 2048); - pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); - pet->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); - pet->SetFullHealth(); - pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA)); - pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(nullptr))); // cast can't be helped in this case - } - - map->AddToMap(pet->ToCreature(), true); - - if (info->m_petType == SUMMON_PET) - { - pet->InitPetCreateSpells(); - pet->InitTalentForLevel(); - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); - player->PetSpellInitialize(); // no need to check, no other possibility - - // Remove Demonic Sacrifice auras (known pet) - Unit::AuraEffectList const& auraClassScripts = player->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); - for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();) - { - if ((*itr)->GetMiscValue() == 2228) - { - player->RemoveAurasDueToSpell((*itr)->GetId()); - itr = auraClassScripts.begin(); - } - else - ++itr; - } - } - - if (info->m_duration > 0) - pet->SetDuration(info->m_duration); - - // we are almost at home... - if (asynchLoadType == PET_LOAD_SUMMON_PET) - { - Unit* caster = ObjectAccessor::GetUnit(*player, info->m_casterGUID); - if (!caster) - caster = player; - - if (caster->GetTypeId() == TYPEID_UNIT) - { - if (caster->ToCreature()->IsTotem()) - pet->SetReactState(REACT_AGGRESSIVE); - else - pet->SetReactState(REACT_DEFENSIVE); - } - - // Reset cooldowns - if (player->getClass() != CLASS_HUNTER) - { - pet->m_CreatureSpellCooldowns.clear(); - player->ToPlayer()->PetSpellInitialize(); - } - - // Set health to max if new pet is summoned - // in this function old pet is saved with current health eg. 20% and new one is loaded from db with same amount - // pet should have full health - pet->SetHealth(pet->GetMaxHealth()); - - // generate new name for summon pet - std::string new_name = sObjectMgr->GeneratePetName(info->m_entry); - if (!new_name.empty()) - pet->SetName(new_name); - } - else // if GetAsynchLoad() == PET_LOAD_SUMMON_DEAD_PET - { - pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); - pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); - pet->setDeathState(ALIVE); - pet->ClearUnitState(uint32(UNIT_STATE_ALL_STATE & ~(UNIT_STATE_POSSESSED))); // xinef: just in case - pet->SetHealth(pet->CountPctFromMaxHealth(uint32(info->m_casterGUID.GetRawValue()))); // damage for this effect is saved in caster guid, dont create next variable... - - //AIM_Initialize(); - //owner->PetSpellInitialize(); - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); - } - - if (info->m_healthPct) - { - pet->SetHealth(pet->CountPctFromMaxHealth(info->m_healthPct)); - } - } -} - void Pet::SetDisplayId(uint32 modelId) { Guardian::SetDisplayId(modelId); @@ -2388,3 +2391,40 @@ void Pet::RemoveSpellCooldown(uint32 spell_id, bool update /* = false */) } } } + +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, 2, UNIT_CAN_BE_RENAMED); + petInfo->Health = GetHealth(); + petInfo->Mana = GetPower(POWER_MANA); + petInfo->Happiness = GetPower(POWER_HAPPINESS); + petInfo->ActionBar = GenerateActionBarData(); + petInfo->LastSaveTime = time(nullptr); + petInfo->CreatedBySpellId = GetUInt32Value(UNIT_CREATED_BY_SPELL); + petInfo->Type = getPetType(); +} + +Player* Pet::GetOwner() const +{ + return m_owner; +} + +std::string Pet::GenerateActionBarData() const +{ + std::ostringstream oss; + + for (uint32 i = ACTION_BAR_INDEX_START; i < ACTION_BAR_INDEX_END; ++i) + { + oss << uint32(m_charmInfo->GetActionBarEntry(i)->GetType()) << ' ' + << uint32(m_charmInfo->GetActionBarEntry(i)->GetAction()) << ' '; + } + + return oss.str(); +} diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index ca7127cc8..7c7181800 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -21,9 +21,8 @@ #include "PetDefines.h" #include "TemporarySummon.h" -#define PET_FOCUS_REGEN_INTERVAL 4 * IN_MILLISECONDS -#define PET_LOSE_HAPPINES_INTERVAL 7500 -#define HAPPINESS_LEVEL_SIZE 333000 +constexpr auto PET_LOSE_HAPPINES_INTERVAL = 7500; +constexpr auto HAPPINESS_LEVEL_SIZE = 333000; struct PetSpell { @@ -32,20 +31,6 @@ struct PetSpell PetSpellType type; }; -class AsynchPetSummon -{ -public: - AsynchPetSummon(uint32 entry, Position position, PetType petType, uint32 duration, uint32 createdBySpell, ObjectGuid casterGUID, int32 healthPct = 0) : - m_entry(entry), pos(position), m_petType(petType), m_duration(duration), m_createdBySpell(createdBySpell), m_casterGUID(casterGUID), m_healthPct(healthPct) { } - - uint32 m_entry; - Position pos; - PetType m_petType; - uint32 m_duration, m_createdBySpell; - ObjectGuid m_casterGUID; - int32 m_healthPct; -}; - typedef std::unordered_map PetSpellMap; typedef std::vector AutoSpellList; @@ -55,7 +40,7 @@ class Pet : public Guardian { public: explicit Pet(Player* owner, PetType type = MAX_PET_TYPE); - ~Pet() override; + ~Pet() override = default; void AddToWorld() override; void RemoveFromWorld() override; @@ -65,7 +50,7 @@ public: PetType getPetType() const { return m_petType; } void setPetType(PetType type) { m_petType = type; } bool isControlled() const { return getPetType() == SUMMON_PET || getPetType() == HUNTER_PET; } - bool isTemporarySummoned() const { return m_duration > 0; } + bool isTemporarySummoned() const { return m_duration > 0s; } bool IsPermanentPetFor(Player* owner) const; // pet have tab in character windows and set UNIT_FIELD_PETNUMBER @@ -73,10 +58,11 @@ public: bool CreateBaseAtCreature(Creature* creature); bool CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner); bool CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map, uint32 phaseMask); - static SpellCastResult TryLoadFromDB(Player* owner, bool current = false, PetType mandatoryPetType = MAX_PET_TYPE, bool checkDead = false); - static bool LoadPetFromDB(Player* owner, uint8 asynchLoadType, uint32 petentry = 0, uint32 petnumber = 0, bool current = false, AsynchPetSummon* info = nullptr); - bool isBeingLoaded() const override { return m_loading;} - void SavePetToDB(PetSaveMode mode, bool logout); + static std::pair GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, bool current); + bool LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current); + bool isBeingLoaded() 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); @@ -99,19 +85,8 @@ public: void SynchronizeLevelWithOwner(); bool HaveInDiet(ItemTemplate const* item) const; uint32 GetCurrentFoodBenefitLevel(uint32 itemlevel) const; - void SetDuration(int32 dur) { m_duration = dur; } - int32 GetDuration() const { return m_duration; } - - /* - bool UpdateStats(Stats stat); - bool UpdateAllStats(); - void UpdateResistances(uint32 school); - void UpdateArmor(); - void UpdateMaxHealth(); - void UpdateMaxPower(Powers power); - void UpdateAttackPowerAndDamage(bool ranged = false); - void UpdateDamagePhysical(WeaponAttackType attType); - */ + void SetDuration(Milliseconds dur) { m_duration = dur; } + Milliseconds GetDuration() const { return m_duration; } void ToggleAutocast(SpellInfo const* spellInfo, bool apply); @@ -124,8 +99,8 @@ public: void ClearCastWhenWillAvailable(); void RemoveSpellCooldown(uint32 spell_id, bool update /* = false */); - void _SaveSpellCooldowns(CharacterDatabaseTransaction trans, bool logout); - void _SaveAuras(CharacterDatabaseTransaction trans, bool logout); + void _SaveSpellCooldowns(CharacterDatabaseTransaction trans); + void _SaveAuras(CharacterDatabaseTransaction trans); void _SaveSpells(CharacterDatabaseTransaction trans); void _LoadSpellCooldowns(PreparedQueryResult result); @@ -139,6 +114,7 @@ public: bool unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab = true); bool removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab = true); void CleanupActionBar(); + std::string GenerateActionBarData() const; PetSpellMap m_spells; AutoSpellList m_autospells; @@ -159,37 +135,30 @@ public: 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) + bool m_removed; // prevent overwrite pet state in DB at next Pet::Update if pet already removed(saved) - Player* GetOwner() const { return m_owner; } + Player* GetOwner() const; void SetLoading(bool load) { m_loading = load; } - void HandleAsynchLoadSucceed(); - static void HandleAsynchLoadFailed(AsynchPetSummon* info, Player* player, uint8 asynchLoadType, uint8 loadResult); - uint8 GetAsynchLoadType() const { return asynchLoadType; } - void SetAsynchLoadType(uint8 type) { asynchLoadType = type; } [[nodiscard]] bool HasTempSpell() const { return m_tempspell != 0; } - protected: Player* m_owner; int32 m_happinessTimer; PetType m_petType; - int32 m_duration; // time until unsummon (used mostly for summoned guardians and not used for controlled pets) + Milliseconds m_duration; // time until unsummon (used mostly for summoned guardians and not used for controlled pets) uint64 m_auraRaidUpdateMask; bool m_loading; - int32 m_petRegenTimer; // xinef: used for focus regeneration + Milliseconds m_petRegenTimer; // xinef: used for focus regeneration - DeclinedName* m_declinedname; + std::unique_ptr m_declinedname; Unit* m_tempspellTarget; Unit* m_tempoldTarget; bool m_tempspellIsPositive; uint32 m_tempspell; - uint8 asynchLoadType; - 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 61332b565..b183be610 100644 --- a/src/server/game/Entities/Pet/PetDefines.h +++ b/src/server/game/Entities/Pet/PetDefines.h @@ -18,17 +18,25 @@ #ifndef AZEROTHCORE_PET_DEFINES_H #define AZEROTHCORE_PET_DEFINES_H -enum PetType +#include "Define.h" +#include "Optional.h" +#include +#include +#include + +enum ReactStates : uint8; + +enum PetType : uint8 { SUMMON_PET = 0, HUNTER_PET = 1, MAX_PET_TYPE = 4 }; -#define MAX_PET_STABLES 4 +constexpr auto 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) @@ -73,24 +81,6 @@ enum PetTalk PET_TALK_ATTACK = 1 }; -// used at pet loading query list preparing, and later result selection -enum PetLoadQueryIndex -{ - PET_LOAD_QUERY_LOADAURAS = 0, - PET_LOAD_QUERY_LOADSPELLS = 1, - PET_LOAD_QUERY_LOADSPELLCOOLDOWN = 2, - MAX_PET_LOAD_QUERY, -}; - -enum PetLoadStage -{ - PET_LOAD_DEFAULT = 0, - PET_LOAD_HANDLE_UNSTABLE_CALLBACK = 1, // used also in HandleStableSwapPetCallback, uses same error / ok messages - PET_LOAD_BG_RESURRECT = 2, - PET_LOAD_SUMMON_PET = 3, - PET_LOAD_SUMMON_DEAD_PET = 4 -}; - enum PetLoadState { PET_LOAD_OK = 0, @@ -202,4 +192,39 @@ enum PetScalingSpells #define PET_FOLLOW_DIST 1.0f #define PET_FOLLOW_ANGLE (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 CurrentPet; // PET_SAVE_AS_CURRENT + std::array, MAX_PET_STABLES> StabledPets; // PET_SAVE_FIRST_STABLE_SLOT - PET_SAVE_LAST_STABLE_SLOT + uint32 MaxStabledPets = 0; + std::vector UnslottedPets; // PET_SAVE_NOT_IN_SLOT + + PetInfo const* GetUnslottedHunterPet() const + { + return UnslottedPets.size() == 1 && UnslottedPets[0].Type == HUNTER_PET ? &UnslottedPets[0] : nullptr; + } +}; + #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index a7bfcf4f2..c90efc3eb 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -282,8 +282,6 @@ Player::Player(WorldSession* session): Unit(true), m_mover(this) for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) m_forced_speed_changes[i] = 0; - m_stableSlots = 0; - /////////////////// Instance System ///////////////////// m_HomebindTimer = 0; @@ -3969,7 +3967,7 @@ void Player::DeleteFromDB(ObjectGuid::LowType lowGuid, uint32 accountId, bool up // 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, lowGuid); PreparedQueryResult resultPets = CharacterDatabase.Query(stmt); @@ -8703,7 +8701,18 @@ void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent) pet->CombatStop(); // only if current pet in slot - pet->SavePetToDB(mode, true); + 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); @@ -8898,7 +8907,7 @@ void Player::PetSpellInitialize() WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1); data << pet->GetGUID(); data << uint16(pet->GetCreatureTemplate()->family); // creature family (required for pet talents) - data << uint32(pet->GetDuration()); + data << uint32(pet->GetDuration().count()); data << uint8(pet->GetReactState()); data << uint8(charmInfo->GetCommandState()); data << uint16(0); // Flags, mostly unknown @@ -13396,8 +13405,11 @@ void Player::ResummonPetTemporaryUnSummonedIfAny() if (!CanResummonPet(GetLastPetSpell())) return; - Pet::LoadPetFromDB(this, PET_LOAD_SUMMON_PET, 0, m_temporaryUnsummonedPetNumber, true); - //m_temporaryUnsummonedPetNumber = 0; + Pet* newPet = new Pet(this); + if (!newPet->LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true)) + delete newPet; + + m_temporaryUnsummonedPetNumber = 0; } bool Player::CanResummonPet(uint32 spellid) @@ -13878,7 +13890,7 @@ void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans) 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)); @@ -14017,7 +14029,7 @@ void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans) 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)); @@ -14468,6 +14480,7 @@ void Player::SetReputation(uint32 factionentry, uint32 value) { GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value); } + uint32 Player::GetReputation(uint32 factionentry) const { return GetReputationMgr().GetReputation(sFactionStore.LookupEntry(factionentry)); @@ -14701,6 +14714,14 @@ bool Player::AddItem(uint32 itemId, uint32 count) return true; } +PetStable& Player::GetOrInitPetStable() +{ + if (!m_petStable) + m_petStable = std::make_unique(); + + return *m_petStable; +} + void Player::RefundItem(Item* item) { if (!item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE)) @@ -14933,6 +14954,58 @@ void Player::_LoadBrewOfTheMonth(PreparedQueryResult result) } } +void Player::_LoadPetStable(uint8 petStableSlots, PreparedQueryResult result) +{ + if (!petStableSlots && !result) + return; + + m_petStable = std::make_unique(); + m_petStable->MaxStabledPets = petStableSlots; + + if (m_petStable->MaxStabledPets > MAX_PET_STABLES) + { + FMT_LOG_ERROR("entities.player", "Player::LoadFromDB: Player ({}) can't have more stable slots than {}, but has {} in DB", + GetGUID().ToString(), 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()) @@ -15119,30 +15192,134 @@ Guild* Player::GetGuild() const return guildId ? sGuildMgr->GetGuildById(guildId) : nullptr; } -void Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 duration, uint32 createdBySpell, ObjectGuid casterGUID, uint8 asynchLoadType, int32 healthPct /*= 0*/) +Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, Milliseconds duration /*= 0s*/) { - Position pos = {x, y, z, ang}; - if (!pos.IsPositionValid()) - return; + PetStable& petStable = GetOrInitPetStable(); - AsynchPetSummon* asynchPetInfo = new AsynchPetSummon(entry, pos, petType, duration, createdBySpell, casterGUID, healthPct); - Pet::LoadPetFromDB(this, asynchLoadType, entry, 0, false, asynchPetInfo); -} + Pet* pet = new Pet(this, petType); -bool Player::IsPetDismissed() -{ - /* - * Check PET_SAVE_NOT_IN_SLOT means the pet is dismissed. If someone ever - * Changes the slot flag, they will break this validation. - */ - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_SYNS); - stmt->setUInt32(0, GetGUID().GetCounter()); - stmt->setUInt8(1, uint8(PET_SAVE_NOT_IN_SLOT)); + if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry, 0, false)) + { + // 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();) + { + if ((*itr)->GetMiscValue() == 2228) + { + RemoveAurasDueToSpell((*itr)->GetId()); + itr = auraClassScripts.begin(); + } + else + ++itr; + } - if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) - return true; + if (duration > 0s) + pet->SetDuration(duration); - return false; + // Generate a new name for the newly summoned ghoul + if (pet->IsPetGhoul()) + { + std::string new_name = sObjectMgr->GeneratePetName(entry); + if (!new_name.empty()) + pet->SetName(new_name); + } + + return nullptr; + } + + // petentry == 0 for hunter "call pet" (current pet summoned if any) + if (!entry) + { + delete pet; + return nullptr; + } + + pet->Relocate(x, y, z, ang); + if (!pet->IsPositionValid()) + { + LOG_ERROR("misc", "Player::SummonPet: Pet (%s, Entry: %d) not summoned. Suggested coordinates aren't valid (X: %f Y: %f)", pet->GetGUID().ToString().c_str(), pet->GetEntry(), pet->GetPositionX(), pet->GetPositionY()); + delete pet; + return nullptr; + } + + Map* map = GetMap(); + uint32 pet_number = sObjectMgr->GeneratePetNumber(); + if (!pet->Create(map->GenerateLowGuid(), map, GetPhaseMask(), entry, pet_number)) + { + LOG_ERROR("misc", "Player::SummonPet: No such creature entry %u", entry); + delete pet; + return nullptr; + } + + if (petType == SUMMON_PET && petStable.CurrentPet) + RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); + + pet->SetCreatorGUID(GetGUID()); + pet->SetFaction(GetFaction()); + pet->setPowerType(POWER_MANA); + pet->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + pet->SetUInt32Value(UNIT_FIELD_BYTES_1, 0); + pet->InitStatsForLevel(getLevel()); + + SetMinion(pet, true); + + switch (petType) + { + case SUMMON_PET: + { + if (pet->GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || pet->GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD) + pet->GetCharmInfo()->SetPetNumber(pet_number, true); // Show pet details tab (Shift+P) only for demons & undead + else + pet->GetCharmInfo()->SetPetNumber(pet_number, false); + + pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 2048); + pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); + pet->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); + pet->SetFullHealth(); + pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA)); + pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(nullptr))); // cast can't be helped in this case + break; + } + default: + break; + } + + map->AddToMap(pet->ToCreature(), true); + + ASSERT(!petStable.CurrentPet && (petType != HUNTER_PET || !petStable.GetUnslottedHunterPet())); + pet->FillPetInfo(&petStable.CurrentPet.emplace()); + + if (petType == SUMMON_PET) + { + pet->InitPetCreateSpells(); + pet->InitTalentForLevel(); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + PetSpellInitialize(); + + // 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();) + { + if ((*itr)->GetMiscValue() == 2228) + { + RemoveAurasDueToSpell((*itr)->GetId()); + itr = auraClassScripts.begin(); + } + else + ++itr; + } + } + + if (duration > 0s) + pet->SetDuration(duration); + + if (NeedSendSpectatorData() && pet->GetCreatureTemplate()->family) + { + ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PHP", (uint32)pet->GetHealthPct()); + ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PET", pet->GetCreatureTemplate()->family); + } + + return pet; } uint32 Player::GetSpec(int8 spec) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index f6c4c40bb..e55108e0c 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -877,6 +877,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_BREW_OF_THE_MONTH = 34, PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION = 35, PLAYER_LOGIN_QUERY_LOAD_CHARACTER_SETTINGS = 36, + PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS = 37, MAX_PLAYER_LOGIN_QUERY }; @@ -1162,9 +1163,12 @@ public: void RemoveRestFlag(RestFlag restFlag); [[nodiscard]] uint32 GetInnTriggerId() const { return _innTriggerId; } + PetStable* GetPetStable() { return m_petStable.get(); } + PetStable& GetOrInitPetStable(); + PetStable const* GetPetStable() const { return m_petStable.get(); } + [[nodiscard]] Pet* GetPet() const; - bool IsPetDismissed(); - void SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 despwtime, uint32 createdBySpell, ObjectGuid casterGUID, uint8 asynchLoadType, int32 healthPct = 0); + Pet* SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, Milliseconds duration = 0s); void RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent = false); [[nodiscard]] uint32 GetPhaseMaskForSpawn() const; // used for proper set phase for DB at GM-mode creature/GO spawn @@ -1345,8 +1349,6 @@ public: bool AddItem(uint32 itemId, uint32 count); - uint32 m_stableSlots; - /*********************************************************/ /*** GOSSIP SYSTEM ***/ /*********************************************************/ @@ -2684,6 +2686,7 @@ public: void _LoadInstanceTimeRestrictions(PreparedQueryResult result); void _LoadBrewOfTheMonth(PreparedQueryResult result); void _LoadCharacterSettings(PreparedQueryResult result); + void _LoadPetStable(uint8 petStableSlots, PreparedQueryResult result); /*********************************************************/ /*** SAVE SYSTEM ***/ @@ -2923,6 +2926,8 @@ private: bool m_bMustDelayTeleport; bool m_bHasDelayedTeleport; + std::unique_ptr m_petStable; + // Temporary removed pet cache uint32 m_temporaryUnsummonedPetNumber; uint32 m_oldpetspell; diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 2e62c84d8..b345bed3d 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -5375,12 +5375,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons uint32 extraflags = fields[36].GetUInt16(); - m_stableSlots = fields[37].GetUInt8(); - if (m_stableSlots > MAX_PET_STABLES) - { - LOG_ERROR("entities.player", "Player can have not more %u stable slots, but have in DB %u", 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(); @@ -6293,9 +6288,11 @@ 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::LoadPetFromDB(this, PET_LOAD_SUMMON_PET, 0, 0, true); + Pet* pet = new Pet(this); + if (!pet->LoadPetFromDB(this, 0, 0, true)) + delete pet; } } @@ -7177,7 +7174,7 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create, bool logo // save pet (hunter pet level and experience and all type pets health/mana). if (Pet* pet = GetPet()) - pet->SavePetToDB(PET_SAVE_AS_CURRENT, logout); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); } // fast save function for item/money cheating preventing - save only inventory and money state diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 638eea1c9..69bf56db1 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -16609,11 +16609,10 @@ bool Unit::IsPetAura(Aura const* aura) return false; // if the owner has that pet aura, return true - for (PetAuraSet::const_iterator 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; } @@ -16632,7 +16631,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; } @@ -16659,6 +16662,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); @@ -16676,6 +16684,7 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) // this enables pet details window (Shift+P) pet->InitPetCreateSpells(); pet->SetFullHealth(); + pet->FillPetInfo(&petStable.CurrentPet.emplace()); return true; } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index aa3ccf16d..f86823ebb 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1146,7 +1146,7 @@ enum ActiveStates ACT_DECIDE = 0x00 // custom }; -enum ReactStates +enum ReactStates : uint8 { REACT_PASSIVE = 0, REACT_DEFENSIVE = 1, diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 65c49c984..c6c48167f 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -202,13 +202,17 @@ bool LoginQueryHolder::Initialize() res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_LOCATION); - stmt->setUInt64(0, lowGuid); + stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SETTINGS); - stmt->setUInt64(0, lowGuid); + stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CHARACTER_SETTINGS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, stmt); + return res; } diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index fef384af0..e75be5b54 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -488,74 +488,61 @@ void WorldSession::HandleListStabledPetsOpcode(WorldPacket& recvData) void WorldSession::SendStablePet(ObjectGuid guid) { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS_DETAIL); - - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt8(1, PET_SAVE_FIRST_STABLE_SLOT); - stmt->setUInt8(2, PET_SAVE_LAST_STABLE_SLOT); - - _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::SendStablePetCallback, this, guid, std::placeholders::_1))); -} - -void WorldSession::SendStablePetCallback(ObjectGuid guid, PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - LOG_DEBUG("network", "WORLD: Recv MSG_LIST_STABLED_PETS Send."); WorldPacket data(MSG_LIST_STABLED_PETS, 200); // guess size data << guid; size_t wpos = data.wpos(); data << uint8(0); // place holder for slot show number - data << uint8(GetPlayer()->m_stableSlots); - Pet* pet = _player->GetPet(); + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) + { + data << uint8(0); // stable slots + SendPacket(&data); + return; + } + + data << uint8(petStable->MaxStabledPets); + uint8 num = 0; // counter for place holder // not let move dead pet in slot - if (pet && pet->IsAlive() && pet->getPetType() == HUNTER_PET) + if (petStable->CurrentPet) { - data << uint32(pet->GetCharmInfo()->GetPetNumber()); - data << uint32(pet->GetEntry()); - data << uint32(pet->getLevel()); - data << pet->GetName(); // petname - data << uint8(1); // 1 = current, 2/3 = in stable (any from 4, 5, ... create problems with proper show) + PetStable::PetInfo const& pet = *petStable->CurrentPet; + data << uint32(pet.PetNumber); + data << uint32(pet.CreatureId); + data << uint32(pet.Level); + data << pet.Name; // petname + data << uint8(1); // flags: 1 active, 2 inactive ++num; } - else if (_player->IsPetDismissed() || _player->GetTemporaryUnsummonedPetNumber()) + else { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_SYNS); - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt8(1, uint8(_player->GetTemporaryUnsummonedPetNumber() ? PET_SAVE_AS_CURRENT : PET_SAVE_NOT_IN_SLOT)); - - if (PreparedQueryResult _result = CharacterDatabase.Query(stmt)) + if (PetStable::PetInfo const* pet = petStable->GetUnslottedHunterPet()) { - Field* fields = _result->Fetch(); - - data << uint32(fields[0].GetUInt32()); // id - data << uint32(fields[1].GetUInt32()); // entry - data << uint32(fields[4].GetUInt16()); // level - data << fields[8].GetString(); // petname - data << uint8(1); + data << uint32(pet->PetNumber); + data << uint32(pet->CreatureId); + data << uint32(pet->Level); + data << pet->Name; // petname + data << uint8(1); // flags: 1 active, 2 inactive ++num; } } - if (result) + for (Optional const& stabledSlot : petStable->StabledPets) { - do + if (stabledSlot) { - Field* fields = result->Fetch(); - - data << uint32(fields[1].GetUInt32()); // petnumber - data << uint32(fields[2].GetUInt32()); // creature entry - data << uint32(fields[3].GetUInt16()); // level - data << fields[4].GetString(); // name - data << uint8(2); // 1 = current, 2/3 = in stable (any from 4, 5, ... create problems with proper show) - + PetStable::PetInfo const& pet = *stabledSlot; + data << uint32(pet.PetNumber); + data << uint32(pet.CreatureId); + data << uint32(pet.Level); + data << pet.Name; // petname + data << uint8(2); // flags: 1 active, 2 inactive ++num; - } while (result->NextRow()); + } } data.put(wpos, num); // set real data to placeholder @@ -592,81 +579,49 @@ void WorldSession::HandleStablePet(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) + return; + Pet* pet = _player->GetPet(); // can't place in stable dead pet - if (pet) + if ((pet && (!pet->IsAlive() || pet->getPetType() != HUNTER_PET)) + || (!pet && (petStable->UnslottedPets.size() != 1 || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET))) { - if (!pet->IsAlive() || pet->getPetType() != HUNTER_PET) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - } - else - { - SpellCastResult loadResult = Pet::TryLoadFromDB(_player, _player->GetTemporaryUnsummonedPetNumber() != 0, HUNTER_PET); - if (loadResult != SPELL_CAST_OK) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - } - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS); - - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt8(1, PET_SAVE_FIRST_STABLE_SLOT); - stmt->setUInt8(2, PET_SAVE_LAST_STABLE_SLOT); - - _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleStablePetCallback, this, std::placeholders::_1))); -} - -void WorldSession::HandleStablePetCallback(PreparedQueryResult result) -{ - if (!GetPlayer()) + SendStableResult(STABLE_ERR_STABLE); return; - - uint8 freeSlot = 1; - if (result) - { - do - { - Field* fields = result->Fetch(); - - uint8 slot = fields[1].GetUInt8(); - - // slots ordered in query, and if not equal then free - if (slot != freeSlot) - break; - - // this slot not free, skip - ++freeSlot; - } while (result->NextRow()); } - WorldPacket data(SMSG_STABLE_RESULT, 1); - if (freeSlot > 0 && freeSlot <= GetPlayer()->m_stableSlots) + for (uint32 freeSlot = 0; freeSlot < petStable->MaxStabledPets; ++freeSlot) { - if (_player->GetPetGUID()) + if (!petStable->StabledPets[freeSlot]) { - _player->RemovePet(_player->GetPet(), PetSaveMode(freeSlot)); + if (pet) + { + // stable summoned pet + _player->RemovePet(pet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + freeSlot)); + std::swap(petStable->StabledPets[freeSlot], petStable->CurrentPet); + SendStableResult(STABLE_SUCCESS_STABLE); + return; + } + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt8(0, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + freeSlot)); + stmt->setUInt32(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petStable->UnslottedPets[0].PetNumber); + CharacterDatabase.Execute(stmt); + + // stable unsummoned pet + petStable->StabledPets[freeSlot] = std::move(petStable->UnslottedPets.back()); + petStable->UnslottedPets.pop_back(); SendStableResult(STABLE_SUCCESS_STABLE); return; } - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt8(0, freeSlot); - stmt->setUInt32(1, _player->GetGUID().GetCounter()); - stmt->setUInt8(2, uint8(_player->GetTemporaryUnsummonedPetNumber() ? PET_SAVE_AS_CURRENT : PET_SAVE_NOT_IN_SLOT)); - CharacterDatabase.Execute(stmt); - - _player->SetTemporaryUnsummonedPetNumber(0); - SendStableResult(STABLE_SUCCESS_STABLE); - return; } - else - SendStableResult(STABLE_ERR_STABLE); + + // not free stable slot + SendStableResult(STABLE_ERR_STABLE); } void WorldSession::HandleUnstablePet(WorldPacket& recvData) @@ -687,37 +642,25 @@ void WorldSession::HandleUnstablePet(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_ENTRY); - - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt32(1, petnumber); - stmt->setUInt8(2, PET_SAVE_FIRST_STABLE_SLOT); - stmt->setUInt8(3, PET_SAVE_LAST_STABLE_SLOT); - - _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleUnstablePetCallback, this, petnumber, std::placeholders::_1))); -} - -void WorldSession::HandleUnstablePetCallback(uint32 petId, PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - - uint32 petEntry = 0; - uint32 slot = 0; - if (result) - { - Field* fields = result->Fetch(); - petEntry = fields[0].GetUInt32(); - slot = fields[1].GetUInt32(); - } - - if (!petEntry) + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) { SendStableResult(STABLE_ERR_STABLE); return; } - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry); + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional const& pet) + { + return pet && pet->PetNumber == petnumber; + }); + + if (stabledPet == petStable->StabledPets.end()) + { + SendStableResult(STABLE_ERR_STABLE); + return; + } + + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId); if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets())) { // if problem in exotic pet @@ -725,45 +668,74 @@ void WorldSession::HandleUnstablePetCallback(uint32 petId, PreparedQueryResult r SendStableResult(STABLE_ERR_EXOTIC); else SendStableResult(STABLE_ERR_STABLE); + return; } - Pet* pet = _player->GetPet(); - if (pet && pet->IsAlive()) + Pet* oldPet = _player->GetPet(); + if (oldPet) { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - // delete dead pet - if (pet) - { - _player->RemovePet(pet, PET_SAVE_AS_DELETED); - } - else if (_player->IsPetDismissed() || _player->GetTemporaryUnsummonedPetNumber()) - { - // try to find if pet is actually temporary unsummoned and alive - SpellCastResult loadResult = Pet::TryLoadFromDB(_player, _player->GetTemporaryUnsummonedPetNumber() != 0, HUNTER_PET); - if (loadResult != SPELL_CAST_OK) + // try performing a swap, client sends this packet instead of swap when starting from stabled slot + if (!oldPet->IsAlive() || !oldPet->IsHunterPet()) { SendStableResult(STABLE_ERR_STABLE); return; } - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt8(0, 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) + { + SendStableResult(STABLE_ERR_STABLE); + return; + } + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt8(0, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet))); stmt->setUInt32(1, _player->GetGUID().GetCounter()); - stmt->setUInt8(2, uint8(_player->GetTemporaryUnsummonedPetNumber() ? PET_SAVE_AS_CURRENT : PET_SAVE_NOT_IN_SLOT)); + stmt->setUInt32(2, petStable->UnslottedPets[0].PetNumber); CharacterDatabase.Execute(stmt); - _player->SetTemporaryUnsummonedPetNumber(0); + // 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(); } - - if (!Pet::LoadPetFromDB(_player, PET_LOAD_HANDLE_UNSTABLE_CALLBACK, petEntry, petId)) + else if (petStable->CurrentPet || !petStable->UnslottedPets.empty()) { SendStableResult(STABLE_ERR_STABLE); 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->setUInt32(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petnumber); + CharacterDatabase.Execute(stmt); + + SendStableResult(STABLE_ERR_STABLE); + } + 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->setUInt32(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petnumber); + CharacterDatabase.Execute(stmt); + + SendStableResult(STABLE_SUCCESS_UNSTABLE); + } } void WorldSession::HandleBuyStableSlot(WorldPacket& recvData) @@ -783,12 +755,13 @@ void WorldSession::HandleBuyStableSlot(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - if (GetPlayer()->m_stableSlots < MAX_PET_STABLES) + PetStable& petStable = GetPlayer()->GetOrInitPetStable(); + if (petStable.MaxStabledPets < MAX_PET_STABLES) { - StableSlotPricesEntry const* SlotPrice = sStableSlotPricesStore.LookupEntry(GetPlayer()->m_stableSlots + 1); + StableSlotPricesEntry const* SlotPrice = sStableSlotPricesStore.LookupEntry(petStable.MaxStabledPets + 1); if (_player->HasEnoughMoney(SlotPrice->Price)) { - ++GetPlayer()->m_stableSlots; + ++petStable.MaxStabledPets; _player->ModifyMoney(-int32(SlotPrice->Price)); SendStableResult(STABLE_SUCCESS_BUY_SLOT); } @@ -822,58 +795,26 @@ void WorldSession::HandleStableSwapPet(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - Pet* pet = _player->GetPet(); - - if (pet) + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) { - if (pet->getPetType() != HUNTER_PET) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - } - else if (_player->IsPetDismissed() || _player->GetTemporaryUnsummonedPetNumber()) - { - // try to find if pet is actually temporary unsummoned and alive - SpellCastResult loadResult = Pet::TryLoadFromDB(_player, _player->GetTemporaryUnsummonedPetNumber() != 0, HUNTER_PET); - if (loadResult != SPELL_CAST_OK) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } + SendStableResult(STABLE_ERR_STABLE); + return; } // Find swapped pet slot in stable - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOT_BY_ID); + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petId](Optional const& pet) + { + return pet && pet->PetNumber == petId; + }); - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt32(1, petId); - - _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleStableSwapPetCallback, this, petId, std::placeholders::_1))); -} - -void WorldSession::HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - - if (!result) + if (stabledPet == petStable->StabledPets.end()) { SendStableResult(STABLE_ERR_STABLE); return; } - Field* fields = result->Fetch(); - uint32 slot = fields[0].GetUInt8(); - uint32 petEntry = fields[1].GetUInt32(); - - if (!petEntry) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry); + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate((*stabledPet)->CreatureId); if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets())) { // if problem in exotic pet @@ -884,29 +825,69 @@ void WorldSession::HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult return; } - Pet* pet = _player->GetPet(); - - // move alive pet to slot or delete dead pet - if (pet) - _player->RemovePet(pet, pet->IsAlive() ? PetSaveMode(slot) : PET_SAVE_AS_DELETED); - else if (_player->IsPetDismissed() || _player->GetTemporaryUnsummonedPetNumber()) + Pet* oldPet = _player->GetPet(); + if (oldPet) { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt8(0, slot); + if (!oldPet->IsAlive() || !oldPet->IsHunterPet()) + { + SendStableResult(STABLE_ERR_STABLE); + return; + } + + _player->RemovePet(oldPet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet))); + } + else if (petStable->UnslottedPets.size() == 1) + { + if (petStable->CurrentPet || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET) + { + SendStableResult(STABLE_ERR_STABLE); + return; + } + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt8(0, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet))); stmt->setUInt32(1, _player->GetGUID().GetCounter()); - stmt->setUInt8(2, uint8(_player->GetTemporaryUnsummonedPetNumber() ? PET_SAVE_AS_CURRENT : PET_SAVE_NOT_IN_SLOT)); + stmt->setUInt32(2, petStable->UnslottedPets[0].PetNumber); CharacterDatabase.Execute(stmt); - _player->SetTemporaryUnsummonedPetNumber(0); + // 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()) + { + SendStableResult(STABLE_ERR_STABLE); + return; } // summon unstabled pet - if (!Pet::LoadPetFromDB(_player, PET_LOAD_HANDLE_UNSTABLE_CALLBACK, petEntry, petId)) + Pet* newPet = new Pet(_player, HUNTER_PET); + if (!newPet->LoadPetFromDB(_player, 0, petId, false)) { + delete newPet; SendStableResult(STABLE_ERR_STABLE); + + petStable->UnslottedPets.push_back(std::move(*petStable->CurrentPet)); + petStable->CurrentPet.reset(); + + // update current pet slot in db immediately to maintain slot consistency, dismissed pet was already saved + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt8(0, PET_SAVE_NOT_IN_SLOT); + stmt->setUInt32(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petId); + CharacterDatabase.Execute(stmt); } else + { + // update current pet slot in db immediately to maintain slot consistency, dismissed pet was already saved + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt8(0, PET_SAVE_AS_CURRENT); + stmt->setUInt32(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petId); + CharacterDatabase.Execute(stmt); + SendStableResult(STABLE_SUCCESS_UNSTABLE); + } } void WorldSession::HandleRepairItemOpcode(WorldPacket& recvData) diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index e7096282a..6add902a4 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -35,355 +35,6 @@ #include "WorldPacket.h" #include "WorldSession.h" -class LoadPetFromDBQueryHolder : public CharacterDatabaseQueryHolder -{ -public: - LoadPetFromDBQueryHolder(uint32 petNumber, bool current, uint32 diffTime, std::string&& actionBar, uint32 health, uint32 mana) - : _petNumber(petNumber), - _current(current), - _diffTime(diffTime), - _actionBar(std::move(actionBar)), - _savedHealth(health), - _savedMana(mana) { } - - uint32 GetPetNumber() const { return _petNumber; } - uint32 GetDiffTime() const { return _diffTime; } - bool GetCurrent() const { return _current; } - uint32 GetSavedHealth() const { return _savedHealth; } - uint32 GetSavedMana() const { return _savedMana; } - std::string GetActionBar() const { return _actionBar; } - - bool Initialize(); -private: - enum - { - AURAS, - SPELLS, - COOLDOWNS, - - MAX - }; - - const uint32 _petNumber; - const bool _current; - const uint32 _diffTime; - const std::string _actionBar; - const uint32 _savedHealth; - const uint32 _savedMana; -}; - -bool LoadPetFromDBQueryHolder::Initialize() -{ - SetSize(MAX); - - bool res = true; - CharacterDatabasePreparedStatement* stmt = nullptr; - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA); - stmt->setUInt32(0, _petNumber); - res &= SetPreparedQuery(AURAS, stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL); - stmt->setUInt32(0, _petNumber); - res &= SetPreparedQuery(SPELLS, stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN); - stmt->setUInt32(0, _petNumber); - res &= SetPreparedQuery(COOLDOWNS, stmt); - - return res; -} - -uint8 WorldSession::HandleLoadPetFromDBFirstCallback(PreparedQueryResult result, uint8 asynchLoadType, AsynchPetSummon* info) -{ - if (!result) - return PET_LOAD_NO_RESULT; - - Player* owner = GetPlayer(); - if (!owner || owner->GetPet() || owner->GetVehicle() || owner->IsSpectator() || owner->IsBeingTeleportedFar()) - { - return PET_LOAD_ERROR; - } - - Field* fields = result->Fetch(); - - // Xinef: this can happen if fetch is called twice, impossibru. - if (!fields) - return PET_LOAD_ERROR; - - // update for case of current pet "slot = 0" - uint32 petentry = fields[1].GetUInt32(); - if (!petentry) - return PET_LOAD_NO_RESULT; - - uint8 petSlot = fields[7].GetUInt8(); - bool current = petSlot == PET_SAVE_AS_CURRENT; - uint32 summon_spell_id = fields[15].GetUInt32(); - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon_spell_id); // CANT BE nullptr - bool is_temporary_summoned = spellInfo && spellInfo->GetDuration() > 0; - uint32 pet_number = fields[0].GetUInt32(); - uint32 savedhealth = fields[10].GetUInt32(); - uint32 savedmana = fields[11].GetUInt32(); - PetType pet_type = PetType(fields[16].GetUInt8()); - - // xinef: BG resurrect, overwrite saved value - if (asynchLoadType == PET_LOAD_BG_RESURRECT) - savedhealth = 1; - - if (pet_type == HUNTER_PET && savedhealth == 0 && asynchLoadType != PET_LOAD_SUMMON_DEAD_PET) - { - WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1 + 4); - data << uint8(0); - data << uint32(883); - data << uint8(SPELL_FAILED_TARGETS_DEAD); - SendPacket(&data); - owner->RemoveSpellCooldown(883, false); - return PET_LOAD_ERROR; - } - - // check temporary summoned pets like mage water elemental - if (current && is_temporary_summoned) - return PET_LOAD_ERROR; - - if (pet_type == HUNTER_PET) - { - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petentry); - if (!creatureInfo || !creatureInfo->IsTameable(owner->CanTameExoticPets())) - return PET_LOAD_ERROR; - } - - Map* map = owner->GetMap(); - ObjectGuid::LowType guid = map->GenerateLowGuid(); - Pet* pet = new Pet(owner, pet_type); - if (!pet->Create(guid, map, owner->GetPhaseMask(), petentry, pet_number)) - { - delete pet; - return PET_LOAD_ERROR; - } - - std::shared_ptr holder = std::make_shared(pet_number, current, uint32(time(nullptr) - fields[14].GetUInt32()), fields[13].GetString(), savedhealth, savedmana); - if (!holder->Initialize()) - { - delete pet; - return PET_LOAD_ERROR; - } - - float px, py, pz; - owner->GetClosePoint(px, py, pz, pet->GetObjectSize(), PET_FOLLOW_DIST, pet->GetFollowAngle()); - if (!pet->IsPositionValid()) - { - LOG_ERROR("network.opcode", "Pet (%s, entry %d) not loaded. Suggested coordinates isn't valid (X: %f Y: %f)", - pet->GetGUID().ToString().c_str(), pet->GetEntry(), pet->GetPositionX(), pet->GetPositionY()); - delete pet; - return PET_LOAD_ERROR; - } - - pet->SetLoading(true); - pet->Relocate(px, py, pz, owner->GetOrientation()); - pet->setPetType(pet_type); - pet->SetFaction(owner->GetFaction()); - pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, summon_spell_id); - - if (pet->IsCritter()) - { - pet->UpdatePositionData(); - map->AddToMap(pet->ToCreature(), true); - pet->SetLoading(false); // xinef, mine - return PET_LOAD_OK; - } - - if (pet->getPetType() == HUNTER_PET || pet->GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || pet->GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD) - pet->GetCharmInfo()->SetPetNumber(pet_number, pet->IsPermanentPetFor(owner)); // Show pet details tab (Shift+P) only for hunter pets, demons or undead - else - pet->GetCharmInfo()->SetPetNumber(pet_number, false); - - pet->SetDisplayId(fields[3].GetUInt32()); - pet->UpdatePositionData(); - pet->SetNativeDisplayId(fields[3].GetUInt32()); - pet->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); - pet->SetName(fields[8].GetString()); - uint32 petlevel = fields[4].GetUInt16(); - - switch (pet->getPetType()) - { - case SUMMON_PET: - petlevel = owner->getLevel(); - - if (pet->IsPetGhoul()) - pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 0x400); // class = rogue - else - pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 0x800); // class = mage - - pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); - // this enables popup window (pet dismiss, cancel) - break; - case HUNTER_PET: - pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100); // class = warrior, gender = none, power = focus - pet->SetSheath(SHEATH_STATE_MELEE); - pet->SetByteFlag(UNIT_FIELD_BYTES_2, 2, fields[9].GetBool() ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED); - pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); - // this enables popup window (pet abandon, cancel) - pet->SetMaxPower(POWER_HAPPINESS, pet->GetCreatePowers(POWER_HAPPINESS)); - pet->SetPower(POWER_HAPPINESS, fields[12].GetUInt32()); - pet->setPowerType(POWER_FOCUS); - break; - default: - if (!pet->IsPetGhoul()) - LOG_ERROR("network.opcode", "Pet have incorrect type (%u) for pet loading.", pet->getPetType()); - break; - } - - pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(nullptr))); // cast can't be helped here - pet->SetCreatorGUID(owner->GetGUID()); - owner->SetMinion(pet, true); - - pet->InitStatsForLevel(petlevel); - pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, fields[5].GetUInt32()); - pet->SynchronizeLevelWithOwner(); - pet->SetReactState(ReactStates(fields[6].GetUInt8())); - pet->SetCanModifyStats(true); - - // set current pet as current - // 0=current - // 1..MAX_PET_STABLES in stable slot - // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning)) - if (petSlot) - { - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID); - stmt->setUInt8(0, uint8(PET_SAVE_NOT_IN_SLOT)); - stmt->setUInt32(1, owner->GetGUID().GetCounter()); - stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt32(3, pet_number); - trans->Append(stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); - stmt->setUInt8(0, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt32(1, owner->GetGUID().GetCounter()); - stmt->setUInt32(2, pet_number); - trans->Append(stmt); - - CharacterDatabase.CommitTransaction(trans); - } - - // 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 (summon_spell_id) - { - WorldPacket data(SMSG_SPELL_GO, (8 + 8 + 4 + 4 + 2)); - data << owner->GetPackGUID(); - data << owner->GetPackGUID(); - data << uint8(0); - data << uint32(summon_spell_id); - data << uint32(256); // CAST_FLAG_UNKNOWN3 - data << uint32(0); - owner->SendMessageToSet(&data, true); - } - - // do it as early as possible! - pet->InitTalentForLevel(); // set original talents points before spell loading - - if (!is_temporary_summoned) - pet->GetCharmInfo()->InitPetActionBar(); - - map->AddToMap(pet->ToCreature(), true); - - if (pet->getPetType() == SUMMON_PET && !current) //all (?) summon pets come with full health when called, but not when they are current - pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA)); - else - { - pet->SetHealth(savedhealth > pet->GetMaxHealth() ? pet->GetMaxHealth() : savedhealth); - pet->SetPower(POWER_MANA, savedmana > pet->GetMaxPower(POWER_MANA) ? pet->GetMaxPower(POWER_MANA) : savedmana); - } - - pet->SetAsynchLoadType(asynchLoadType); - - AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)).AfterComplete([this, info](SQLQueryHolderBase const& holder) - { - HandleLoadPetFromDBSecondCallback(static_cast(holder), info); - }); - - return PET_LOAD_OK; -} - -void WorldSession::HandleLoadPetFromDBSecondCallback(LoadPetFromDBQueryHolder const& holder, AsynchPetSummon* info) -{ - if (!GetPlayer()) - return; - - Player* owner = GetPlayer(); - Pet* pet = owner->GetPet(); - if (!pet) - return; - - pet->_LoadAuras(holder.GetPreparedResult(PET_LOAD_QUERY_LOADAURAS), holder.GetDiffTime()); - bool current = holder.GetCurrent(); - uint32 summon_spell_id = pet->GetUInt32Value(UNIT_CREATED_BY_SPELL); - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon_spell_id); // CANT BE nullptr - bool is_temporary_summoned = spellInfo && spellInfo->GetDuration() > 0; - - // load action bar, if data broken will fill later by default spells. - if (!is_temporary_summoned) - { - pet->GetCharmInfo()->LoadPetActionBar(holder.GetActionBar()); // action bar stored in already read string - pet->_LoadSpells(holder.GetPreparedResult(PET_LOAD_QUERY_LOADSPELLS)); - pet->InitTalentForLevel(); // re-init to check talent count - pet->_LoadSpellCooldowns(holder.GetPreparedResult(PET_LOAD_QUERY_LOADSPELLCOOLDOWN)); - pet->LearnPetPassives(); - pet->InitLevelupSpellsForLevel(); - pet->CastPetAuras(current); - } - - pet->CleanupActionBar(); // remove unknown spells from action bar after load - owner->PetSpellInitialize(); - owner->SendTalentsInfoData(true); - - if (owner->GetGroup()) - owner->SetGroupUpdateFlag(GROUP_UPDATE_PET); - - //set last used pet number (for use in BG's) - if (owner->GetTypeId() == TYPEID_PLAYER && pet->isControlled() && !pet->isTemporarySummoned() && (pet->getPetType() == SUMMON_PET || pet->getPetType() == HUNTER_PET)) - { - owner->ToPlayer()->SetLastPetNumber(holder.GetPetNumber()); - owner->SetLastPetSpell(pet->GetUInt32Value(UNIT_CREATED_BY_SPELL)); - } - - if (pet->getPetType() == SUMMON_PET && !current) //all (?) summon pets come with full health when called, but not when they are current - { - pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA)); - pet->SetHealth(pet->GetMaxHealth()); - } - else - { - if (!holder.GetSavedHealth() && pet->getPetType() == HUNTER_PET && pet->GetAsynchLoadType() != PET_LOAD_SUMMON_DEAD_PET) - pet->setDeathState(JUST_DIED); - else - { - pet->SetHealth(holder.GetSavedHealth() > pet->GetMaxHealth() ? pet->GetMaxHealth() : holder.GetSavedHealth()); - pet->SetPower(POWER_MANA, holder.GetSavedMana() > pet->GetMaxPower(POWER_MANA) ? pet->GetMaxPower(POWER_MANA) : holder.GetSavedMana()); - } - } - - pet->SetLoading(false); - owner->SetTemporaryUnsummonedPetNumber(0); // clear this only if pet is loaded successfuly - - // current - if (current && owner->IsPetNeedBeTemporaryUnsummoned()) - { - owner->UnsummonPetTemporaryIfAny(); - return; - } - - pet->HandleAsynchLoadSucceed(); - - if (info && info->m_healthPct) - { - pet->SetHealth(pet->CountPctFromMaxHealth(info->m_healthPct)); - } -} - void WorldSession::HandleDismissCritter(WorldPacket& recvData) { ObjectGuid guid; @@ -1193,12 +844,18 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) recvData >> name; recvData >> isdeclined; + PetStable* petStable = _player->GetPetStable(); + Pet* pet = ObjectAccessor::GetPet(*_player, petguid); + // check it! if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET || - !pet->HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) || - pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo()) + !pet->HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) || + pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() || + !petStable || !petStable->CurrentPet || petStable->CurrentPet->PetNumber != pet->GetCharmInfo()->GetPetNumber()) + { return; + } PetNameInvalidReason res = ObjectMgr::CheckPetName(name); if (res != PET_NAME_SUCCESS) @@ -1221,6 +878,9 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) pet->RemoveByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED); + petStable->CurrentPet->Name = name; + petStable->CurrentPet->WasRenamed = true; + if (isdeclined) { for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i) diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 26650ecad..8ac0a6e6c 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -687,18 +687,13 @@ public: // opcodes handlers void HandleBinderActivateOpcode(WorldPacket& recvPacket); void HandleListStabledPetsOpcode(WorldPacket& recvPacket); void HandleStablePet(WorldPacket& recvPacket); - void HandleStablePetCallback(PreparedQueryResult result); void HandleUnstablePet(WorldPacket& recvPacket); - void HandleUnstablePetCallback(uint32 petId, PreparedQueryResult result); void HandleBuyStableSlot(WorldPacket& recvPacket); void HandleStableRevivePet(WorldPacket& recvPacket); void HandleStableSwapPet(WorldPacket& recvPacket); - void HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result); void HandleOpenWrappedItemCallback(uint8 bagIndex, uint8 slot, ObjectGuid::LowType itemLowGUID, PreparedQueryResult result); void HandleLoadActionsSwitchSpec(PreparedQueryResult result); void HandleCharacterAuraFrozen(PreparedQueryResult result); - uint8 HandleLoadPetFromDBFirstCallback(PreparedQueryResult result, uint8 asynchLoadType, AsynchPetSummon* info); - void HandleLoadPetFromDBSecondCallback(LoadPetFromDBQueryHolder const& holder, AsynchPetSummon* info); void HandleDuelAcceptedOpcode(WorldPacket& recvPacket); void HandleDuelCancelledOpcode(WorldPacket& recvPacket); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index f04cb4a91..60ab5cba2 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -5922,19 +5922,22 @@ SpellCastResult Spell::CheckCast(bool strict) } case SPELL_EFFECT_RESURRECT_PET: { - if (Creature* pet = m_caster->GetGuardianPet()) + Unit* unitCaster = m_caster->ToUnit(); + if (!unitCaster) + return SPELL_FAILED_BAD_TARGETS; + + Creature* pet = unitCaster->GetGuardianPet(); + if (pet && pet->IsAlive()) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + Player* player = unitCaster->ToPlayer(); + if (player) { - if (pet->IsAlive()) + // Check pet before resurrect + auto [petStable, petSlot] = Pet::GetLoadPetInfo(*player->GetPetStable(), 0, 0, false); + if (!petStable) { - return SPELL_FAILED_ALREADY_HAVE_SUMMON; - } - } - else if (Player* player = m_caster->ToPlayer()) - { - SpellCastResult loadResult = Pet::TryLoadFromDB(player, false, MAX_PET_TYPE, true); - if (loadResult != SPELL_CAST_OK) - { - return loadResult; + return SPELL_FAILED_NO_PET; } } @@ -5972,6 +5975,10 @@ SpellCastResult Spell::CheckCast(bool strict) } case SPELL_EFFECT_SUMMON_PET: { + Unit* unitCaster = m_caster->ToUnit(); + if (!unitCaster) + return SPELL_FAILED_BAD_TARGETS; + if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST)) { if (m_caster->GetPetGUID()) @@ -5983,6 +5990,40 @@ SpellCastResult Spell::CheckCast(bool strict) if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->getClass() == CLASS_WARLOCK && strict) if (Pet* pet = m_caster->ToPlayer()->GetPet()) pet->CastSpell(pet, 32752, true, nullptr, nullptr, pet->GetGUID()); //starting cast, trigger pet stun (cast by pet so it doesn't attack player) + + Player* playerCaster = unitCaster->ToPlayer(); + if (playerCaster && playerCaster->GetPetStable()) + { + std::pair info = Pet::GetLoadPetInfo(*playerCaster->GetPetStable(), m_spellInfo->Effects[i].MiscValue, 0, false); + if (info.first) + { + if (info.first->Type == HUNTER_PET) + { + if (!info.first->Health) + { + playerCaster->SendTameFailure(PET_TAME_DEAD); + return SPELL_FAILED_DONT_REPORT; + } + + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(info.first->CreatureId); + if (!creatureInfo || !creatureInfo->IsTameable(playerCaster->CanTameExoticPets())) + { + // if problem in exotic pet + if (creatureInfo && creatureInfo->IsTameable(true)) + playerCaster->SendTameFailure(PET_TAME_CANT_CONTROL_EXOTIC); + else + playerCaster->SendTameFailure(PET_TAME_NOPET_AVAILABLE); + + return SPELL_FAILED_DONT_REPORT; + } + } + } + else if (!m_spellInfo->Effects[i].MiscValue) // when miscvalue is present it is allowed to create new pets + { + playerCaster->SendTameFailure(PET_TAME_NOPET_AVAILABLE); + return SPELL_FAILED_DONT_REPORT; + } + } break; } case SPELL_EFFECT_SUMMON_PLAYER: diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 420c1a5e5..8f247518b 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -3141,7 +3141,7 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/) if (m_caster->GetTypeId() == TYPEID_PLAYER) { - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); m_caster->ToPlayer()->PetSpellInitialize(); } } @@ -3219,19 +3219,45 @@ void Spell::EffectSummonPet(SpellEffIndex effIndex) } if (owner->GetTypeId() == TYPEID_PLAYER) - owner->ToPlayer()->RemovePet(OldSummon, (OldSummon->getPetType() == HUNTER_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT), false); + owner->ToPlayer()->RemovePet(OldSummon, PET_SAVE_NOT_IN_SLOT, false); else return; } float x, y, z; owner->GetClosePoint(x, y, z, owner->GetObjectSize()); - owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), SUMMON_PET, 0, m_spellInfo->Id, m_caster->GetGUID(), PET_LOAD_SUMMON_PET); - //if (!pet) - // return; + Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), SUMMON_PET); + if (!pet) + return; - // xinef: cant execute this... :( hope nothing relevant gets bugged - //ExecuteLogEffectSummonObject(effIndex, pet); + if (m_caster->GetTypeId() == TYPEID_UNIT) + { + if (m_caster->ToCreature()->IsTotem()) + pet->SetReactState(REACT_AGGRESSIVE); + else + pet->SetReactState(REACT_DEFENSIVE); + } + + pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + // Reset cooldowns + if (owner->getClass() != CLASS_HUNTER) + { + pet->m_CreatureSpellCooldowns.clear(); + owner->PetSpellInitialize(); + } + + // Set health to max if new pet is summoned + // in this function old pet is saved with current health eg. 20% and new one is loaded from db with same amount + // pet should have full health + pet->SetHealth(pet->GetMaxHealth()); + + // generate new name for summon pet + std::string new_name = sObjectMgr->GeneratePetName(petentry); + if (!new_name.empty()) + pet->SetName(new_name); + + // ExecuteLogEffectSummonObject(effectInfo->EffectIndex, pet); } void Spell::EffectLearnPetSpell(SpellEffIndex effIndex) @@ -3256,7 +3282,7 @@ void Spell::EffectLearnPetSpell(SpellEffIndex effIndex) return; pet->learnSpell(learn_spellproto->Id); - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); pet->GetOwner()->PetSpellInitialize(); } @@ -5362,41 +5388,55 @@ void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) return; + if (damage < 0) + return; + Player* player = m_caster->ToPlayer(); if (!player) return; - Pet* pet = player->GetPet(); - if (pet && pet->IsAlive()) - return; + // Maybe player dismissed dead pet or pet despawned? + bool hadPet = true; - if (damage < 0) - return; - - float x, y, z; - player->GetPosition(x, y, z); - if (!pet) + if (!player->GetPet()) { - player->SummonPet(0, x, y, z, player->GetOrientation(), SUMMON_PET, 0, 0, ObjectGuid((uint64)damage), PET_LOAD_SUMMON_DEAD_PET, damage); - return; + // 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); + hadPet = false; } - pet->SetPosition(x, y, z, player->GetOrientation()); + // TODO: Better to fail Hunter's "Revive Pet" at cast instead of here when casting ends + Pet* pet = player->GetPet(); // Attempt to get current pet + if (!pet || pet->IsAlive()) + return; + + // If player did have a pet before reviving, teleport it + if (hadPet) + { + // Reposition the pet's corpse before reviving so as not to grab aggro + // We can use a different, more accurate version of GetClosePoint() since we have a pet + float x, y, z; // Will be used later to reposition the pet if we have one + player->GetClosePoint(x, y, z, pet->GetCombatReach(), PET_FOLLOW_DIST, pet->GetFollowAngle()); + pet->NearTeleportTo(x, y, z, player->GetOrientation()); + pet->Relocate(x, y, z, player->GetOrientation()); // This is needed so SaveStayPosition() will get the proper coords. + } pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); pet->setDeathState(ALIVE); pet->ClearUnitState(uint32(UNIT_STATE_ALL_STATE & ~(UNIT_STATE_POSSESSED))); // xinef: just in case pet->SetHealth(pet->CountPctFromMaxHealth(damage)); + pet->SetDisplayId(pet->GetNativeDisplayId()); // xinef: restore movement - if (pet->GetCharmInfo()) + if (auto ci = pet->GetCharmInfo()) { - pet->GetCharmInfo()->SetIsAtStay(false); - pet->GetCharmInfo()->SetIsFollowing(false); + ci->SetIsAtStay(false); + ci->SetIsFollowing(false); } - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); } void Spell::EffectDestroyAllTotems(SpellEffIndex /*effIndex*/) @@ -6015,7 +6055,7 @@ void Spell::EffectCreateTamedPet(SpellEffIndex effIndex) if (unitTarget->GetTypeId() == TYPEID_PLAYER) { - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); unitTarget->ToPlayer()->PetSpellInitialize(); } } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 59a284ae9..c3ccc9855 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -1195,7 +1195,7 @@ public: // caster have pet now player->SetMinion(pet, true); - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); player->PetSpellInitialize(); return true; diff --git a/src/server/scripts/Commands/cs_pet.cpp b/src/server/scripts/Commands/cs_pet.cpp index 18e7a8b4c..533a3a89d 100644 --- a/src/server/scripts/Commands/cs_pet.cpp +++ b/src/server/scripts/Commands/cs_pet.cpp @@ -25,6 +25,7 @@ #include "SpellMgr.h" #include "SpellInfo.h" #include "WorldSession.h" +#include "ObjectMgr.h" using namespace Acore::ChatCommands; @@ -79,42 +80,28 @@ public: } // Everything looks OK, create new pet - Pet* pet = new Pet(player, HUNTER_PET); - if (!pet->CreateBaseAtCreature(creatureTarget)) - { - delete pet; - handler->PSendSysMessage("Error 1"); - return false; - } + Pet* pet = player->CreateTamedPetFrom(creatureTarget); - creatureTarget->setDeathState(JUST_DIED); - creatureTarget->RemoveCorpse(); - creatureTarget->SetHealth(0); // just for nice GM-mode view + // "kill" original creature + creatureTarget->DespawnOrUnsummon(); - pet->SetGuidValue(UNIT_FIELD_CREATEDBY, player->GetGUID()); - pet->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, player->GetFaction()); - - if (!pet->InitStatsForLevel(creatureTarget->getLevel())) - { - LOG_ERROR("misc", "InitStatsForLevel() in EffectTameCreature failed! Pet deleted."); - handler->PSendSysMessage("Error 2"); - delete pet; - return false; - } + uint8 level = (creatureTarget->getLevel() < (player->getLevel() - 5)) ? (player->getLevel() - 5) : player->getLevel(); // prepare visual effect for levelup - pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->getLevel() - 1); - pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true); + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1); - // this enables pet details window (Shift+P) - pet->InitPetCreateSpells(); - pet->SetFullHealth(); + // add to world pet->GetMap()->AddToMap(pet->ToCreature()); // visual effect for levelup - pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->getLevel()); + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); + + // caster have pet now player->SetMinion(pet, true); - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + + pet->InitTalentForLevel(); + + pet->SavePetToDB(PET_SAVE_AS_CURRENT); player->PetSpellInitialize(); return true; diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index bb95d8c13..942bec478 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -1679,7 +1679,31 @@ class spell_gen_pet_summoned : public SpellScript { Player* player = GetCaster()->ToPlayer(); if (player->GetLastPetNumber() && player->CanResummonPet(player->GetLastPetSpell())) - Pet::LoadPetFromDB(player, PET_LOAD_BG_RESURRECT, 0, player->GetLastPetNumber(), true); + { + PetType newPetType = (player->getClass() == CLASS_HUNTER) ? HUNTER_PET : SUMMON_PET; + Pet* newPet = new Pet(player, newPetType); + if (newPet->LoadPetFromDB(player, 0, player->GetLastPetNumber(), true)) + { + // revive the pet if it is dead + if (newPet->getDeathState() != ALIVE && newPet->getDeathState() != JUST_RESPAWNED) + newPet->setDeathState(JUST_RESPAWNED); + + newPet->SetFullHealth(); + newPet->SetPower(newPet->getPowerType(), newPet->GetMaxPower(newPet->getPowerType())); + + switch (newPet->GetEntry()) + { + case NPC_DOOMGUARD: + case NPC_INFERNAL: + newPet->SetEntry(NPC_IMP); + break; + default: + break; + } + } + else + delete newPet; + } } void Register() override diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index 9e6c9a7a9..5b8c5b9bb 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -948,7 +948,20 @@ class spell_hun_tame_beast : public SpellScript return SPELL_FAILED_DONT_REPORT; } - if (caster->GetPetGUID() || player->GetTemporaryUnsummonedPetNumber() || player->IsPetDismissed() || player->GetCharmGUID()) + PetStable const* petStable = player->GetPetStable(); + if (petStable) + { + if (petStable->CurrentPet) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + if (petStable->GetUnslottedHunterPet()) + { + caster->SendTameFailure(PET_TAME_TOO_MANY); + return SPELL_FAILED_DONT_REPORT; + } + } + + if (player->GetCharmGUID()) { player->SendTameFailure(PET_TAME_ANOTHER_SUMMON_ACTIVE); return SPELL_FAILED_DONT_REPORT;