diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 32b2ff04868f5c..9078fd1d8e4e7c 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -540,13 +540,7 @@ 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); @@ -561,15 +555,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 2d675c667067dd..fbd437ef54cc41 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -468,23 +468,11 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_PET_SPELLS, CHAR_DEL_CHAR_PET_BY_OWNER, CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, - CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, - CHAR_SEL_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_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 6afc5f746596a8..59d836aced62ae 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -160,6 +160,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 @@ -475,10 +476,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 2ff299c991c75e..7294c32d5e1742 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 5a3ebd7d44f5e6..7871680e6e412f 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 @@ -853,7 +854,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 114e737983a1e6..c6b8f3aeb0e8c7 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 4dfcc143f4ae92..f2e0717a0ed616 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,376 @@ 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)); +public: + enum + { + DECLINED_NAMES, + AURAS, + SPELLS, + COOLDOWNS, - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (!result) - return SPELL_FAILED_NO_PET; + MAX + }; - Field* fields = result->Fetch(); + PetLoadQueryHolder([[maybe_unused]] ObjectGuid::LowType ownerGuid, uint32 petNumber) + { + SetSize(MAX); - uint32 petentry = fields[1].GetUInt32(); - uint32 savedHealth = fields[10].GetUInt32(); - uint32 summon_spell_id = fields[15].GetUInt32(); - auto petType = PetType(fields[16].GetUInt8()); + CharacterDatabasePreparedStatement* stmt = nullptr; - // update for case of current pet "slot = 0" - if (!petentry) - return SPELL_FAILED_NO_PET; + /*stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME); + stmt->setUInt32(0, ownerGuid); + stmt->setUInt32(1, petNumber); + SetPreparedQuery(DECLINED_NAMES, stmt);*/ - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petentry); - if (!creatureInfo) - { - 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; - } + 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); - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon_spell_id); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN); + stmt->setUInt32(0, petNumber); + SetPreparedQuery(COOLDOWNS, stmt); + } +}; - bool isTemporarySummoned = spellInfo && spellInfo->GetDuration() > 0; +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 }; - // check temporary summoned pets like mage water elemental - if (current && isTemporarySummoned) - return SPELL_FAILED_NO_PET; + 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) }; - if (!checkDead) + for (PetStable::PetInfo const& pet : stable.UnslottedPets) + if (pet.PetNumber == petnumber) + return { &pet, PET_SAVE_NOT_IN_SLOT }; + } + else if (current) { - if (!savedHealth) - { - owner->ToPlayer()->SendTameFailure(PET_TAME_DEAD); - return SPELL_FAILED_TARGETS_DEAD; - } + // Current pet (slot 0) + if (stable.CurrentPet) + return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT }; } - else if (savedHealth) + else if (petEntry) { - return SPELL_FAILED_TARGET_NOT_DEAD; + // 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 (mandatoryPetType != MAX_PET_TYPE && petType != mandatoryPetType) - return SPELL_FAILED_BAD_TARGETS; + if (!stable.UnslottedPets.empty()) + return { &stable.UnslottedPets.front(), PET_SAVE_NOT_IN_SLOT }; + } - 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) { - // we are loading pet at that moment - if (owner->IsSpectator() || owner->GetPet() || !owner->IsInWorld() || !owner->FindMap()) + 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; 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; + + // we are loading pet at that moment + // if (owner->IsSpectator() || owner->GetPet() || !owner->IsInWorld() || !owner->FindMap()) + // return false; + + // Don't try to reload the current pet + if (petStable->CurrentPet && owner->GetPet() && petStable->CurrentPet.value().PetNumber == petInfo->PetNumber) + 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; + } + + if (current && owner->IsPetNeedBeTemporaryUnsummoned()) + { + owner->SetTemporaryUnsummonedPetNumber(petInfo->PetNumber); + return false; + } - ObjectGuid::LowType ownerGuid = owner->GetGUID().GetCounter(); - CharacterDatabasePreparedStatement* stmt; + Map* map = owner->GetMap(); + ObjectGuid::LowType guid = map->GenerateLowGuid(); - if (petnumber) + 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 petnumber entry - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY); - stmt->setUInt32(0, ownerGuid); - stmt->setUInt32(1, petnumber); + 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; } - else if (current) + + 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()) { - // 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)); + 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; } - else if (petentry) + + 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()) { - // 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)); + 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)); + uint32 savedhealth = petInfo->Health; + uint32 savedmana = petInfo->Mana; + if (!savedhealth && getPetType() == HUNTER_PET) + setDeathState(JUST_DIED); + else + { + SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth); + SetPower(POWER_MANA, savedmana > GetMaxPower(POWER_MANA) ? GetMaxPower(POWER_MANA) : savedmana); + } } - // load them asynchronously + // 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) { - WorldSession* mySess = owner->GetSession(); - mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt) - .WithPreparedCallback([mySess, asynchLoadType, info, owner](PreparedQueryResult result) + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional const& pet) { - // process only if player is in world (teleport crashes?) - // otherwise wait with result till he logs in - uint8 loadResult = mySess->HandleLoadPetFromDBFirstCallback(result, asynchLoadType); + return pet && pet->PetNumber == petnumber; + }); + + ASSERT(stabledPet != petStable->StabledPets.end()); - if (loadResult != PET_LOAD_OK) - Pet::HandleAsynchLoadFailed(info, owner, asynchLoadType, loadResult); - })); + 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 +517,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 +538,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 +549,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 +571,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 +639,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 +673,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 +695,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; @@ -490,6 +740,8 @@ void Pet::Update(uint32 diff) GetCharmInfo()->SetIsReturning(false); GetCharmInfo()->SaveStayPosition(true); + AddSpellCooldown(tempspell, 0, spellInfo->IsCooldownStartedOnEvent() ? infinityCooldownDelay : 0); + CastSpell(tempspellTarget, tempspell, false); m_tempspell = 0; m_tempspellTarget = nullptr; @@ -611,7 +863,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) @@ -1203,7 +1455,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()); @@ -1224,7 +1476,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); @@ -1377,7 +1629,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()); @@ -1390,7 +1642,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) @@ -1470,9 +1722,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 @@ -1481,7 +1731,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) @@ -1520,10 +1770,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; @@ -1536,12 +1785,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; @@ -1551,12 +1800,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 @@ -1609,9 +1858,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; } @@ -1638,9 +1888,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; @@ -1662,10 +1912,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; } @@ -1779,12 +2031,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; @@ -1792,7 +2042,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();) { @@ -1805,7 +2055,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(); @@ -1824,78 +2074,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; + PetStable* petStable = owner->GetPetStable(); + if (!petStable) + return; - // 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); + std::unordered_set petIds; + if (petStable->CurrentPet) + petIds.insert(petStable->CurrentPet->PetNumber); - // no offline pets - if (!resultPets) - return; + for (Optional const& stabledPet : petStable->StabledPets) + if (stabledPet) + petIds.insert(stabledPet->PetNumber); - // 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); + for (PetStable::PetInfo const& unslottedPet : petStable->UnslottedPets) + petIds.insert(unslottedPet.PetNumber); - if (!result) + // now need only reset for offline pets (all pets except online case) + if (onlinePet) + petIds.erase(onlinePet->GetCharmInfo()->GetPetNumber()); + + // no offline pets + if (!petIds.empty()) return; bool need_comma = false; std::ostringstream ss; ss << "DELETE FROM pet_spell WHERE guid IN ("; - do + for (uint32 id : petIds) { - Field* fields = resultPets->Fetch(); - - uint32 id = fields[0].GetUInt32(); - if (need_comma) ss << ','; ss << id; need_comma = true; - } while (resultPets->NextRow()); + } ss << ") AND spell IN ("; - bool need_execute = false; - do + need_comma = false; + for (uint32 spell : sPetTalentSpells) { - Field* fields = result->Fetch(); - - uint32 spell = fields[0].GetUInt32(); - - if (!GetTalentSpellCost(spell)) - continue; - - if (need_execute) + if (need_comma) ss << ','; ss << spell; - need_execute = true; - } while (result->NextRow()); - - if (!need_execute) - return; + need_comma = true; + } ss << ')'; @@ -2048,8 +2285,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); } } @@ -2106,229 +2343,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); - } - } -} - void Pet::SetDisplayId(uint32 modelId) { Guardian::SetDisplayId(modelId); @@ -2381,3 +2395,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 e09910b17d38f4..7c718180013a95 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) : - m_entry(entry), pos(position), m_petType(petType), - m_duration(duration), m_createdBySpell(createdBySpell), m_casterGUID(casterGUID) { } - - uint32 m_entry; - Position pos; - PetType m_petType; - uint32 m_duration, m_createdBySpell; - ObjectGuid m_casterGUID; -}; - 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 @@ class Pet : public Guardian 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 @@ class Pet : public Guardian 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 @@ class Pet : public Guardian 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 @@ class Pet : public Guardian 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 @@ class Pet : public Guardian 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 @@ class Pet : public Guardian void SetAuraUpdateMaskForRaid(uint8 slot) { m_auraRaidUpdateMask |= (uint64(1) << slot); } void ResetAuraUpdateMaskForRaid() { m_auraRaidUpdateMask = 0; } - DeclinedName const* GetDeclinedNames() const { return m_declinedname; } + DeclinedName const* GetDeclinedNames() const { return m_declinedname.get(); } - bool m_removed; // prevent overwrite pet state in DB at next Pet::Update if pet already removed(saved) + 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 61332b565fde30..b183be610d7b73 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 f8e35a7bc65730..47f23add8e6436 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -190,6 +190,8 @@ Player::Player(WorldSession* session): Unit(true), m_mover(this) m_areaUpdateId = 0; m_team = TEAM_NEUTRAL; + m_needZoneUpdate = false; + m_nextSave = SavingSystemMgr::IncreaseSavingMaxValue(1); m_additionalSaveTimer = 0; m_additionalSaveMask = 0; @@ -283,8 +285,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; @@ -1362,6 +1362,8 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati RemoveAurasByType(SPELL_AURA_MOD_FEAR); RemoveAurasByType(SPELL_AURA_MOD_CONFUSE); RemoveAurasByType(SPELL_AURA_MOD_ROOT); + // remove auras that should be removed when being teleported + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED); } if (m_transport) @@ -3986,7 +3988,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); @@ -5522,7 +5524,7 @@ void Player::SendMessageToSetInRange_OwnTeam(WorldPacket* data, float dist, bool Cell::VisitWorldObjects(this, notifier, dist); } -void Player::SendDirectMessage(WorldPacket* data) +void Player::SendDirectMessage(WorldPacket const* data) const { m_session->SendPacket(data); } @@ -8716,7 +8718,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); @@ -8842,7 +8855,7 @@ void Player::Whisper(std::string_view text, Language language, Player* target, b std::string _text(text); - if (!sScriptMgr->CanPlayerUseChat(this, CHAT_MSG_EMOTE, LANG_UNIVERSAL, _text, target)) + if (!sScriptMgr->CanPlayerUseChat(this, CHAT_MSG_WHISPER, language, _text, target)) { return; } @@ -8911,7 +8924,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 @@ -13409,8 +13422,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) @@ -13891,7 +13907,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)); @@ -14030,7 +14046,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)); @@ -14481,6 +14497,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)); @@ -14714,6 +14731,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)) @@ -14946,6 +14971,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()) @@ -15015,8 +15092,12 @@ bool Player::SetCanFly(bool apply, bool packetOnly /*= false*/) bool Player::SetHover(bool apply, bool packetOnly /*= false*/) { - if (!packetOnly && !Unit::SetHover(apply)) - return false; + // moved inside, flag can be removed on landing and wont send appropriate packet to client when aura is removed + if (!packetOnly /* && !Unit::SetHover(apply)*/) + { + Unit::SetHover(apply); + // return false; + } WorldPacket data(apply ? SMSG_MOVE_SET_HOVER : SMSG_MOVE_UNSET_HOVER, 12); data << GetPackGUID(); @@ -15032,8 +15113,12 @@ bool Player::SetHover(bool apply, bool packetOnly /*= false*/) bool Player::SetWaterWalking(bool apply, bool packetOnly /*= false*/) { - if (!packetOnly && !Unit::SetWaterWalking(apply)) - return false; + // moved inside, flag can be removed on landing and wont send appropriate packet to client when aura is removed + if (!packetOnly /* && !Unit::SetWaterWalking(apply)*/) + { + Unit::SetWaterWalking(apply); + // return false; + } WorldPacket data(apply ? SMSG_MOVE_WATER_WALK : SMSG_MOVE_LAND_WALK, 12); data << GetPackGUID(); @@ -15124,30 +15209,128 @@ 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) +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); - 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); + + 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 34000cee01a7fb..ed930e4f3d21f0 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -875,6 +875,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_MONTHLY_QUEST_STATUS = 32, PLAYER_LOGIN_QUERY_LOAD_BREW_OF_THE_MONTH = 34, PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION = 35, + PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS = 36, MAX_PLAYER_LOGIN_QUERY }; @@ -1163,9 +1164,12 @@ class Player : public Unit, public GridObject 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); + 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 @@ -1346,8 +1350,6 @@ class Player : public Unit, public GridObject bool AddItem(uint32 itemId, uint32 count); - uint32 m_stableSlots; - /*********************************************************/ /*** GOSSIP SYSTEM ***/ /*********************************************************/ @@ -1473,7 +1475,7 @@ class Player : public Unit, public GridObject [[nodiscard]] bool HasQuestForItem(uint32 itemId, uint32 excludeQuestId = 0, bool turnIn = false, bool* showInLoot = nullptr) const; [[nodiscard]] bool HasQuestForGO(int32 GOId) const; [[nodiscard]] bool HasQuest(uint32 questId) const; - void UpdateVisibleGameobjectsOrSpellClicks(); + void UpdateForQuestWorldObjects(); [[nodiscard]] bool CanShareQuest(uint32 quest_id) const; void SendQuestComplete(uint32 quest_id); @@ -1806,6 +1808,7 @@ class Player : public Unit, public GridObject void UpdatePvP(bool state, bool _override = false); void UpdateZone(uint32 newZone, uint32 newArea); void UpdateArea(uint32 newArea); + void SetNeedZoneUpdate(bool needUpdate) { m_needZoneUpdate = needUpdate; } void UpdateZoneDependentAuras(uint32 zone_id); // zones void UpdateAreaDependentAuras(uint32 area_id); // subzones @@ -2174,7 +2177,7 @@ class Player : public Unit, public GridObject void SendInitWorldStates(uint32 zone, uint32 area); void SendUpdateWorldState(uint32 Field, uint32 Value); - void SendDirectMessage(WorldPacket* data); + void SendDirectMessage(WorldPacket const* data) const; void SendBGWeekendWorldStates(); void SendBattlefieldWorldStates(); @@ -2679,6 +2682,7 @@ class Player : public Unit, public GridObject void _LoadTalents(PreparedQueryResult result); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); void _LoadBrewOfTheMonth(PreparedQueryResult result); + void _LoadPetStable(uint8 petStableSlots, PreparedQueryResult result); /*********************************************************/ /*** SAVE SYSTEM ***/ @@ -2869,6 +2873,8 @@ class Player : public Unit, public GridObject uint8 m_grantableLevels; + bool m_needZoneUpdate; + [[nodiscard]] AchievementMgr* GetAchievementMgr() const { return m_achievementMgr; } private: @@ -2916,6 +2922,8 @@ class Player : public Unit, public GridObject 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 253a13d5c58709..1b93c7b702559b 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -5371,12 +5371,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(); @@ -6287,9 +6282,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; } } @@ -7170,7 +7167,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); // our: saving system if (!create && !logout) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index db9f98d7b6355c..cbe3a303103913 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -16569,11 +16569,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; } @@ -16592,7 +16591,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; } @@ -16619,6 +16622,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); @@ -16636,6 +16644,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 9d44db1ab8f71e..fdd9491468058d 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 c33f15243dc28f..9018560ee8fb4b 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -203,9 +203,13 @@ bool LoginQueryHolder::Initialize() res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_LOCATION); - stmt->setUInt64(0, lowGuid); + stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, stmt); + return res; } diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 81f12473390305..0be2122dafc28a 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -519,74 +519,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) - { - 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) + if (petStable->CurrentPet) + { + 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 @@ -623,81 +610,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->IsAlive() || pet->getPetType() != HUNTER_PET) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - } - else + if ((pet && (!pet->IsAlive() || pet->getPetType() != HUNTER_PET)) + || (!pet && (petStable->UnslottedPets.size() != 1 || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET))) { - 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) + for (uint32 freeSlot = 0; freeSlot < petStable->MaxStabledPets; ++freeSlot) { - do + if (!petStable->StabledPets[freeSlot]) { - Field* fields = result->Fetch(); - - uint8 slot = fields[1].GetUInt8(); - - // slots ordered in query, and if not equal then free - if (slot != freeSlot) - break; + 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; + } - // this slot not free, skip - ++freeSlot; - } while (result->NextRow()); - } + 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); - WorldPacket data(SMSG_STABLE_RESULT, 1); - if (freeSlot > 0 && freeSlot <= GetPlayer()->m_stableSlots) - { - if (_player->GetPetGUID()) - { - _player->RemovePet(_player->GetPet(), PetSaveMode(freeSlot)); + // 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) @@ -718,37 +673,25 @@ void WorldSession::HandleUnstablePet(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_ENTRY); - - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt32(1, petnumber); - stmt->setUInt8(2, PET_SAVE_FIRST_STABLE_SLOT); - stmt->setUInt8(3, PET_SAVE_LAST_STABLE_SLOT); - - _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleUnstablePetCallback, this, petnumber, std::placeholders::_1))); -} - -void WorldSession::HandleUnstablePetCallback(uint32 petId, PreparedQueryResult result) -{ - if (!GetPlayer()) + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) + { + SendStableResult(STABLE_ERR_STABLE); return; + } - uint32 petEntry = 0; - uint32 slot = 0; - if (result) + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional const& pet) { - Field* fields = result->Fetch(); - petEntry = fields[0].GetUInt32(); - slot = fields[1].GetUInt32(); - } + return pet && pet->PetNumber == petnumber; + }); - if (!petEntry) + if (stabledPet == petStable->StabledPets.end()) { 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 @@ -756,45 +699,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()) - { - SendStableResult(STABLE_ERR_STABLE); return; } - // delete dead pet - if (pet) + Pet* oldPet = _player->GetPet(); + if (oldPet) { - _player->RemovePet(pet, PET_SAVE_AS_DELETED); + // 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; + } + + _player->RemovePet(oldPet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet))); } - else if (_player->IsPetDismissed() || _player->GetTemporaryUnsummonedPetNumber()) + else if (petStable->UnslottedPets.size() == 1) { - // 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) + if (petStable->CurrentPet || !petStable->UnslottedPets[0].Health || petStable->UnslottedPets[0].Type != HUNTER_PET) { SendStableResult(STABLE_ERR_STABLE); return; } - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt8(0, slot); + 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) @@ -814,12 +786,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); } @@ -853,58 +826,26 @@ void WorldSession::HandleStableSwapPet(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - Pet* pet = _player->GetPet(); - - if (pet) - { - 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; - } - } - - // Find swapped pet slot in stable - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOT_BY_ID); - - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt32(1, petId); - - _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleStableSwapPetCallback, this, petId, std::placeholders::_1))); -} - -void WorldSession::HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - - if (!result) + PetStable* petStable = GetPlayer()->GetPetStable(); + if (!petStable) { SendStableResult(STABLE_ERR_STABLE); return; } - Field* fields = result->Fetch(); - uint32 slot = fields[0].GetUInt8(); - uint32 petEntry = fields[1].GetUInt32(); + // Find swapped pet slot in stable + auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petId](Optional const& pet) + { + return pet && pet->PetNumber == petId; + }); - if (!petEntry) + if (stabledPet == petStable->StabledPets.end()) { 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 @@ -915,29 +856,69 @@ void WorldSession::HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult return; } - Pet* pet = _player->GetPet(); + Pet* oldPet = _player->GetPet(); + if (oldPet) + { + if (!oldPet->IsAlive() || !oldPet->IsHunterPet()) + { + SendStableResult(STABLE_ERR_STABLE); + return; + } - // 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()) + _player->RemovePet(oldPet, PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + std::distance(petStable->StabledPets.begin(), stabledPet))); + } + else if (petStable->UnslottedPets.size() == 1) { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt8(0, slot); + 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 a148d015db37b0..6add902a4e4457 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -35,350 +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) -{ - 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](SQLQueryHolderBase const& holder) - { - HandleLoadPetFromDBSecondCallback(static_cast(holder)); - }); - - return PET_LOAD_OK; -} - -void WorldSession::HandleLoadPetFromDBSecondCallback(LoadPetFromDBQueryHolder const& holder) -{ - 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(); -} - void WorldSession::HandleDismissCritter(WorldPacket& recvData) { ObjectGuid guid; @@ -411,12 +67,12 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) recvData >> data; recvData >> guid2; //tag guid - uint32 spellid = UNIT_ACTION_BUTTON_ACTION(data); + uint32 spellId = UNIT_ACTION_BUTTON_ACTION(data); uint8 flag = UNIT_ACTION_BUTTON_TYPE(data); //delete = 0x07 CastSpell = C1 // used also for charmed creature Unit* pet = ObjectAccessor::GetUnit(*_player, guid1); - LOG_DEBUG("network.opcode", "HandlePetAction: Pet %s - flag: %u, spellid: %u, target: %s.", guid1.ToString().c_str(), uint32(flag), spellid, guid2.ToString().c_str()); + LOG_DEBUG("network.opcode", "HandlePetAction: Pet %s - flag: %u, spellId: %u, target: %s.", guid1.ToString().c_str(), uint32(flag), spellId, guid2.ToString().c_str()); if (!pet) { @@ -433,8 +89,8 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) if (!pet->IsAlive()) { // xinef: allow dissmis dead pets - SpellInfo const* spell = (flag == ACT_ENABLED || flag == ACT_PASSIVE) ? sSpellMgr->GetSpellInfo(spellid) : nullptr; - if ((flag != ACT_COMMAND || spellid != COMMAND_ABANDON) && (!spell || !spell->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD))) + SpellInfo const* spell = (flag == ACT_ENABLED || flag == ACT_PASSIVE) ? sSpellMgr->GetSpellInfo(spellId) : nullptr; + if ((flag != ACT_COMMAND || spellId != COMMAND_ABANDON) && (!spell || !spell->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD))) return; } @@ -443,13 +99,13 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) return; // Do not follow itself vehicle - if (spellid == COMMAND_FOLLOW && _player->IsOnVehicle(pet)) + if (spellId == COMMAND_FOLLOW && _player->IsOnVehicle(pet)) { return; } if (GetPlayer()->m_Controlled.size() == 1) - HandlePetActionHelper(pet, guid1, spellid, flag, guid2); + HandlePetActionHelper(pet, guid1, spellId, flag, guid2); else { //If a pet is dismissed, m_Controlled will change @@ -457,10 +113,10 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) for (Unit::ControlSet::iterator itr = GetPlayer()->m_Controlled.begin(); itr != GetPlayer()->m_Controlled.end(); ++itr) { // xinef: allow to dissmis dead pets - if ((*itr)->GetEntry() == pet->GetEntry() && ((*itr)->IsAlive() || (flag == ACT_COMMAND && spellid == COMMAND_ABANDON))) + if ((*itr)->GetEntry() == pet->GetEntry() && ((*itr)->IsAlive() || (flag == ACT_COMMAND && spellId == COMMAND_ABANDON))) controlled.push_back(*itr); // xinef: mirror image blizzard crappness - else if ((*itr)->GetEntry() == NPC_MIRROR_IMAGE && flag == ACT_COMMAND && spellid == COMMAND_FOLLOW) + else if ((*itr)->GetEntry() == NPC_MIRROR_IMAGE && flag == ACT_COMMAND && spellId == COMMAND_FOLLOW) { (*itr)->InterruptNonMeleeSpells(false); } @@ -468,7 +124,7 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) for (Unit* pet : controlled) if (pet && pet->IsInWorld() && pet->GetMap() == _player->GetMap()) - HandlePetActionHelper(pet, guid1, spellid, flag, guid2); + HandlePetActionHelper(pet, guid1, spellId, flag, guid2); } } @@ -500,20 +156,20 @@ void WorldSession::HandlePetStopAttack(WorldPacket& recvData) pet->ClearInPetCombat(); } -void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2) +void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellId, uint16 flag, ObjectGuid guid2) { CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) { LOG_ERROR("network.opcode", "WorldSession::HandlePetAction(petGuid: %s, tagGuid: %s, spellId: %u, flag: %u): object (%s) is considered pet-like but doesn't have a charminfo!", - guid1.ToString().c_str(), guid2.ToString().c_str(), spellid, flag, pet->GetGUID().ToString().c_str()); + guid1.ToString().c_str(), guid2.ToString().c_str(), spellId, flag, pet->GetGUID().ToString().c_str()); return; } switch (flag) { case ACT_COMMAND: //0x07 - switch (spellid) + switch (spellId) { case COMMAND_STAY: //flat=1792 //STAY { @@ -539,7 +195,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetForcedTargetGUID(); break; } - case COMMAND_FOLLOW: //spellid=1792 //FOLLOW + case COMMAND_FOLLOW: //spellId=1792 //FOLLOW { pet->AttackStop(); pet->InterruptNonMeleeSpells(false); @@ -559,12 +215,12 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetForcedTargetGUID(); break; } - case COMMAND_ATTACK: //spellid=1792 //ATTACK + case COMMAND_ATTACK: //spellId=1792 //ATTACK { // Can't attack if owner is pacified if (_player->HasAuraType(SPELL_AURA_MOD_PACIFY)) { - //pet->SendPetCastFail(spellid, SPELL_FAILED_PACIFIED); + //pet->SendPetCastFail(spellId, SPELL_FAILED_PACIFIED); //TODO: Send proper error message to client return; } @@ -663,11 +319,11 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe } break; default: - LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); + LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellId %i.", uint32(flag), spellId); } break; case ACT_REACTION: // 0x6 - switch (spellid) + switch (spellId) { case REACT_PASSIVE: //passive pet->AttackStop(); @@ -679,9 +335,9 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe case REACT_DEFENSIVE: //recovery case REACT_AGGRESSIVE: //activete if (pet->GetTypeId() == TYPEID_UNIT) - pet->ToCreature()->SetReactState(ReactStates(spellid)); + pet->ToCreature()->SetReactState(ReactStates(spellId)); else - charmInfo->SetPlayerReactState(ReactStates(spellid)); + charmInfo->SetPlayerReactState(ReactStates(spellId)); break; } break; @@ -692,10 +348,10 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe Unit* unit_target = nullptr; // do not cast unknown spells - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { - LOG_ERROR("network.opcode", "WORLD: unknown PET spell id %i", spellid); + LOG_ERROR("network.opcode", "WORLD: unknown PET spell id %i", spellId); return; } @@ -710,10 +366,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe return; } - // do not cast not learned spells - if (!pet->HasSpell(spellid) || spellInfo->IsPassive()) - return; - // Clear the flags as if owner clicked 'attack'. AI will reset them // after AttackStart, even if spell failed charmInfo->SetIsAtStay(false); @@ -721,7 +373,28 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetIsReturning(false); charmInfo->SetIsFollowing(false); - Spell* spell = new Spell(pet, spellInfo, TRIGGERED_NONE); + TriggerCastFlags triggerCastFlags = TRIGGERED_NONE; + + if (spellInfo->IsPassive()) + return; + + // cast only learned spells + if (!pet->HasSpell(spellId)) + { + bool allow = false; + + // allow casting of spells triggered by clientside periodic trigger auras + if (pet->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellId)) + { + allow = true; + triggerCastFlags = TRIGGERED_FULL_MASK; + } + + if (!allow) + return; + } + + Spell* spell = new Spell(pet, spellInfo, triggerCastFlags); spell->LoadScripts(); // xinef: load for CheckPetCast SpellCastResult result = spell->CheckPetCast(unit_target); @@ -752,7 +425,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe { if (!spellInfo->IsCooldownStartedOnEvent()) { - pet->ToCreature()->AddSpellCooldown(spellid, 0, 0); + pet->ToCreature()->AddSpellCooldown(spellId, 0, 0); } unit_target = spell->m_targets.GetUnitTarget(); @@ -806,9 +479,9 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe else spell->SendPetCastResult(SPELL_FAILED_DONT_REPORT); - if (!pet->HasSpellCooldown(spellid)) + if (!pet->HasSpellCooldown(spellId)) if(pet->ToPet()) - pet->ToPet()->RemoveSpellCooldown(spellid, true); + pet->ToPet()->RemoveSpellCooldown(spellId, true); spell->finish(false); delete spell; @@ -866,7 +539,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe pet->SendPetAIReaction(guid1); } - pet->ToPet()->CastWhenWillAvailable(spellid, unit_target, nullptr, tempspellIsPositive); + pet->ToPet()->CastWhenWillAvailable(spellId, unit_target, nullptr, tempspellIsPositive); } } else if (haspositiveeffect) @@ -902,7 +575,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe pet->SendPetAIReaction(guid1); } - pet->ToPet()->CastWhenWillAvailable(spellid, unit_target, victim, tmpSpellIsPositive); + pet->ToPet()->CastWhenWillAvailable(spellId, unit_target, victim, tmpSpellIsPositive); } } } @@ -917,8 +590,8 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe spell->SendPetCastResult(result); } - if (!pet->ToCreature()->HasSpellCooldown(spellid)) - GetPlayer()->SendClearCooldown(spellid, pet); + if (!pet->ToCreature()->HasSpellCooldown(spellId)) + GetPlayer()->SendClearCooldown(spellId, pet); spell->finish(false); delete spell; @@ -929,7 +602,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe break; } default: - LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); + LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellId %i.", uint32(flag), spellId); } } @@ -1171,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) @@ -1199,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) @@ -1277,9 +959,9 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) { LOG_DEBUG("network.opcode", "CMSG_PET_SPELL_AUTOCAST"); ObjectGuid guid; - uint32 spellid; + uint32 spellId; uint8 state; //1 for on, 0 for off - recvPacket >> guid >> spellid >> state; + recvPacket >> guid >> spellId >> state; if (!_player->GetGuardianPet() && !_player->GetCharm()) return; @@ -1287,7 +969,7 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) if (guid.IsPlayer()) return; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return; @@ -1312,7 +994,7 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) continue; // do not add not learned spells/ passive spells - if (!pet->HasSpell(spellid) || !spellInfo->IsAutocastable()) + if (!pet->HasSpell(spellId) || !spellInfo->IsAutocastable()) continue; CharmInfo* charmInfo = pet->GetCharmInfo(); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 62b0c9a14cfb9d..bfd2393b4406e0 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -69,22 +69,43 @@ namespace lfg struct LfgUpdateData; } -namespace rbac -{ - class RBACData; -} - namespace WorldPackets { - - namespace Character + namespace Guild { - class LogoutCancel; - class LogoutRequest; - class ShowingCloak; - class ShowingHelm; - class PlayerLogout; - class PlayedTimeClient; + class QueryGuildInfo; + class GuildCreate; + class GuildInviteByName; + class AcceptGuildInvite; + class GuildDeclineInvitation; + class GuildGetInfo; + class GuildGetRoster; + class GuildPromoteMember; + class GuildDemoteMember; + class GuildOfficerRemoveMember; + class GuildLeave; + class GuildDelete; + class GuildUpdateMotdText; + class GuildAddRank; + class GuildDeleteRank; + class GuildUpdateInfoText; + class GuildSetMemberNote; + class GuildEventLogQuery; + class GuildBankRemainingWithdrawMoneyQuery; + class GuildPermissionsQuery; + class GuildSetRankPermissions; + class GuildBankActivate; + class GuildBankQueryTab; + class GuildBankDepositMoney; + class GuildBankWithdrawMoney; + class GuildBankSwapItems; + class GuildBankBuyTab; + class GuildBankUpdateTab; + class GuildBankLogQuery; + class GuildBankTextQuery; + class GuildBankSetTabText; + class GuildSetGuildMaster; + class SaveGuildEmblem; } } @@ -281,16 +302,9 @@ class WorldSession void SendAuthResponse(uint8 code, bool shortForm, uint32 queuePos = 0); void SendClientCacheVersion(uint32 version); - rbac::RBACData* GetRBACData(); - bool HasPermission(uint32 permissionId); - void LoadPermissions(); - QueryCallback LoadPermissionsAsync(); - void InvalidateRBACData(); // Used to force LoadPermissions at next HasPermission check - AccountTypes GetSecurity() const { return _security; } bool CanSkipQueue() const { return _skipQueue; } uint32 GetAccountId() const { return _accountId; } - std::string const& GetAccountName() const { return _accountName; } Player* GetPlayer() const { return _player; } std::string const& GetPlayerName() const; std::string GetPlayerInfo() const; @@ -316,7 +330,7 @@ class WorldSession bool isLogingOut() const { return _logoutTime || m_playerLogout; } /// Engage the logout process for the user - void SetLogoutStartTime(time_t requestTime) + void LogoutRequest(time_t requestTime) { _logoutTime = requestTime; } @@ -489,7 +503,7 @@ class WorldSession void SendSetPlayerDeclinedNamesResult(DeclinedNameResult result, ObjectGuid guid); // played time - void HandlePlayedTime(WorldPackets::Character::PlayedTimeClient& packet); + void HandlePlayedTime(WorldPacket& recvPacket); // new void HandleMoveUnRootAck(WorldPacket& recvPacket); @@ -509,8 +523,8 @@ class WorldSession void HandleMountSpecialAnimOpcode(WorldPacket& recvdata); // character view - void HandleShowingHelmOpcode(WorldPackets::Character::ShowingHelm& packet); - void HandleShowingCloakOpcode(WorldPackets::Character::ShowingCloak& packet); + void HandleShowingHelmOpcode(WorldPacket& recvData); + void HandleShowingCloakOpcode(WorldPacket& recvData); // repair void HandleRepairItemOpcode(WorldPacket& recvPacket); @@ -528,9 +542,9 @@ class WorldSession void HandleLootReleaseOpcode(WorldPacket& recvPacket); void HandleLootMasterGiveOpcode(WorldPacket& recvPacket); void HandleWhoOpcode(WorldPacket& recvPacket); - void HandleLogoutRequestOpcode(WorldPackets::Character::LogoutRequest& logoutRequest); - void HandlePlayerLogoutOpcode(WorldPackets::Character::PlayerLogout& playerLogout); - void HandleLogoutCancelOpcode(WorldPackets::Character::LogoutCancel& logoutCancel); + void HandleLogoutRequestOpcode(WorldPacket& recvPacket); + void HandlePlayerLogoutOpcode(WorldPacket& recvPacket); + void HandleLogoutCancelOpcode(WorldPacket& recvPacket); // GM Ticket opcodes void HandleGMTicketCreateOpcode(WorldPacket& recvPacket); @@ -624,28 +638,28 @@ class WorldSession void HandleOfferPetitionOpcode(WorldPacket& recvData); void HandleTurnInPetitionOpcode(WorldPacket& recvData); - void HandleGuildQueryOpcode(WorldPacket& recvPacket); - void HandleGuildCreateOpcode(WorldPacket& recvPacket); - void HandleGuildInviteOpcode(WorldPacket& recvPacket); - void HandleGuildRemoveOpcode(WorldPacket& recvPacket); - void HandleGuildAcceptOpcode(WorldPacket& recvPacket); - void HandleGuildDeclineOpcode(WorldPacket& recvPacket); - void HandleGuildInfoOpcode(WorldPacket& recvPacket); - void HandleGuildEventLogQueryOpcode(WorldPacket& recvPacket); - void HandleGuildRosterOpcode(WorldPacket& recvPacket); - void HandleGuildPromoteOpcode(WorldPacket& recvPacket); - void HandleGuildDemoteOpcode(WorldPacket& recvPacket); - void HandleGuildLeaveOpcode(WorldPacket& recvPacket); - void HandleGuildDisbandOpcode(WorldPacket& recvPacket); - void HandleGuildLeaderOpcode(WorldPacket& recvPacket); - void HandleGuildMOTDOpcode(WorldPacket& recvPacket); - void HandleGuildSetPublicNoteOpcode(WorldPacket& recvPacket); - void HandleGuildSetOfficerNoteOpcode(WorldPacket& recvPacket); - void HandleGuildRankOpcode(WorldPacket& recvPacket); - void HandleGuildAddRankOpcode(WorldPacket& recvPacket); - void HandleGuildDelRankOpcode(WorldPacket& recvPacket); - void HandleGuildChangeInfoTextOpcode(WorldPacket& recvPacket); - void HandleSaveGuildEmblemOpcode(WorldPacket& recvPacket); + void HandleGuildQueryOpcode(WorldPackets::Guild::QueryGuildInfo& query); + void HandleGuildCreateOpcode(WorldPackets::Guild::GuildCreate& packet); + void HandleGuildInviteOpcode(WorldPackets::Guild::GuildInviteByName& packet); + void HandleGuildRemoveOpcode(WorldPackets::Guild::GuildOfficerRemoveMember& packet); + void HandleGuildAcceptOpcode(WorldPackets::Guild::AcceptGuildInvite& invite); + void HandleGuildDeclineOpcode(WorldPackets::Guild::GuildDeclineInvitation& decline); + void HandleGuildInfoOpcode(WorldPackets::Guild::GuildGetInfo& packet); + void HandleGuildEventLogQueryOpcode(WorldPackets::Guild::GuildEventLogQuery& packet); + void HandleGuildRosterOpcode(WorldPackets::Guild::GuildGetRoster& packet); + void HandleGuildPromoteOpcode(WorldPackets::Guild::GuildPromoteMember& promote); + void HandleGuildDemoteOpcode(WorldPackets::Guild::GuildDemoteMember& demote); + void HandleGuildLeaveOpcode(WorldPackets::Guild::GuildLeave& leave); + void HandleGuildDisbandOpcode(WorldPackets::Guild::GuildDelete& packet); + void HandleGuildLeaderOpcode(WorldPackets::Guild::GuildSetGuildMaster& packet); + void HandleGuildMOTDOpcode(WorldPackets::Guild::GuildUpdateMotdText& packet); + void HandleGuildSetPublicNoteOpcode(WorldPackets::Guild::GuildSetMemberNote& packet); + void HandleGuildSetOfficerNoteOpcode(WorldPackets::Guild::GuildSetMemberNote& packet); + void HandleGuildRankOpcode(WorldPackets::Guild::GuildSetRankPermissions& packet); + void HandleGuildAddRankOpcode(WorldPackets::Guild::GuildAddRank& packet); + void HandleGuildDelRankOpcode(WorldPackets::Guild::GuildDeleteRank& packet); + void HandleGuildChangeInfoTextOpcode(WorldPackets::Guild::GuildUpdateInfoText& packet); + void HandleSaveGuildEmblemOpcode(WorldPackets::Guild::SaveGuildEmblem& packet); void HandleTaxiNodeStatusQueryOpcode(WorldPacket& recvPacket); void HandleTaxiQueryAvailableNodes(WorldPacket& recvPacket); @@ -667,18 +681,13 @@ class WorldSession 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); - void HandleLoadPetFromDBSecondCallback(LoadPetFromDBQueryHolder const& holder); void HandleDuelAcceptedOpcode(WorldPacket& recvPacket); void HandleDuelCancelledOpcode(WorldPacket& recvPacket); @@ -935,19 +944,19 @@ class WorldSession void HandleSetTaxiBenchmarkOpcode(WorldPacket& recvData); // Guild Bank - void HandleGuildPermissions(WorldPacket& recvData); - void HandleGuildBankMoneyWithdrawn(WorldPacket& recvData); - void HandleGuildBankerActivate(WorldPacket& recvData); - void HandleGuildBankQueryTab(WorldPacket& recvData); - void HandleGuildBankLogQuery(WorldPacket& recvData); - void HandleGuildBankDepositMoney(WorldPacket& recvData); - void HandleGuildBankWithdrawMoney(WorldPacket& recvData); - void HandleGuildBankSwapItems(WorldPacket& recvData); - - void HandleGuildBankUpdateTab(WorldPacket& recvData); - void HandleGuildBankBuyTab(WorldPacket& recvData); - void HandleQueryGuildBankTabText(WorldPacket& recvData); - void HandleSetGuildBankTabText(WorldPacket& recvData); + void HandleGuildPermissions(WorldPackets::Guild::GuildPermissionsQuery& packet); + void HandleGuildBankMoneyWithdrawn(WorldPackets::Guild::GuildBankRemainingWithdrawMoneyQuery& packet); + void HandleGuildBankerActivate(WorldPackets::Guild::GuildBankActivate& packet); + void HandleGuildBankQueryTab(WorldPackets::Guild::GuildBankQueryTab& packet); + void HandleGuildBankLogQuery(WorldPackets::Guild::GuildBankLogQuery& packet); + void HandleGuildBankDepositMoney(WorldPackets::Guild::GuildBankDepositMoney& packet); + void HandleGuildBankWithdrawMoney(WorldPackets::Guild::GuildBankWithdrawMoney& packet); + void HandleGuildBankSwapItems(WorldPackets::Guild::GuildBankSwapItems& packet); + + void HandleGuildBankUpdateTab(WorldPackets::Guild::GuildBankUpdateTab& packet); + void HandleGuildBankBuyTab(WorldPackets::Guild::GuildBankBuyTab& packet); + void HandleQueryGuildBankTabText(WorldPackets::Guild::GuildBankTextQuery& packet); + void HandleSetGuildBankTabText(WorldPackets::Guild::GuildBankSetTabText& packet); // Refer-a-Friend void HandleGrantLevel(WorldPacket& recvData); @@ -1105,7 +1114,6 @@ class WorldSession uint32 recruiterId; bool isRecruiter; LockedQueue _recvQueue; - rbac::RBACData* _RBACData; uint32 m_currentVendorEntry; ObjectGuid m_currentBankerGUID; uint32 _offlineTime; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 6598a2af976703..499b9809032216 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -5922,21 +5922,13 @@ SpellCastResult Spell::CheckCast(bool strict) } case SPELL_EFFECT_RESURRECT_PET: { - if (Creature* pet = m_caster->GetGuardianPet()) - { - if (pet->IsAlive()) - { - 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; - } - } + 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; break; } @@ -5972,6 +5964,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 +5979,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 b29e122058fdcb..1420d8a3a8bc55 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -224,7 +224,7 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS] = &Spell::EffectCreateItem2, //157 SPELL_EFFECT_CREATE_ITEM_2 create item or create item template and replace by some randon spell loot item &Spell::EffectMilling, //158 SPELL_EFFECT_MILLING milling &Spell::EffectRenamePet, //159 SPELL_EFFECT_ALLOW_RENAME_PET allow rename pet once again - &Spell::EffectNULL, //160 SPELL_EFFECT_160 1 spell - 45534 + &Spell::EffectForceCast, //160 SPELL_EFFECT_FORCE_CAST_2 &Spell::EffectSpecCount, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert) &Spell::EffectActivateSpec, //162 SPELL_EFFECT_TALENT_SPEC_SELECT activate primary/secondary spec &Spell::EffectNULL, //163 unused @@ -3167,7 +3167,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(); } } @@ -3245,19 +3245,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; + + 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); - // xinef: cant execute this... :( hope nothing relevant gets bugged - //ExecuteLogEffectSummonObject(effIndex, pet); + // ExecuteLogEffectSummonObject(effectInfo->EffectIndex, pet); } void Spell::EffectLearnPetSpell(SpellEffIndex effIndex) @@ -3282,7 +3308,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(); } @@ -5403,7 +5429,7 @@ void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) player->GetPosition(x, y, z); if (!pet) { - player->SummonPet(0, x, y, z, player->GetOrientation(), SUMMON_PET, 0, 0, ObjectGuid((uint64)damage), PET_LOAD_SUMMON_DEAD_PET); + player->SummonPet(0, x, y, z, player->GetOrientation(), SUMMON_PET); return; } @@ -5422,7 +5448,7 @@ void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) pet->GetCharmInfo()->SetIsFollowing(false); } - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); } void Spell::EffectDestroyAllTotems(SpellEffIndex /*effIndex*/) @@ -6041,7 +6067,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_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index beecd919a032d8..bf46ac10c959dd 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -2948,44 +2948,28 @@ class misc_commandscript : public CommandScript } // 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); - // this enables pet details window (Shift+P) - pet->InitPetCreateSpells(); - pet->SetFullHealth(); + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1); + // add to world pet->GetMap()->AddToMap(pet->ToCreature()); // visual effect for levelup - pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->getLevel()); + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); + // caster have pet now player->SetMinion(pet, true); - pet->SavePetToDB(PET_SAVE_AS_CURRENT, false); + + pet->InitTalentForLevel(); + + pet->SavePetToDB(PET_SAVE_AS_CURRENT); player->PetSpellInitialize(); return true; diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 59a284ae9b851b..c3ccc98557fb34 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -1195,7 +1195,7 @@ class npc_commandscript : public CommandScript // 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/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 8d88183bf767c8..4d1bc67e76d451 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -1678,7 +1678,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 bc659bdd1c8e27..0a212a268dc5d2 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -947,7 +947,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;