diff --git a/sql/updates/characters/4.3.4/custom_2018_01_07_00_characters.sql b/sql/updates/characters/4.3.4/custom_2018_01_07_00_characters.sql new file mode 100644 index 0000000000..ded04aa9c4 --- /dev/null +++ b/sql/updates/characters/4.3.4/custom_2018_01_07_00_characters.sql @@ -0,0 +1 @@ +ALTER TABLE `character_pet` ADD COLUMN `active` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `renamed`; diff --git a/sql/updates/world/4.3.4/custom_2018_01_07_00_world.sql b/sql/updates/world/4.3.4/custom_2018_01_07_00_world.sql new file mode 100644 index 0000000000..c637c41f3b --- /dev/null +++ b/sql/updates/world/4.3.4/custom_2018_01_07_00_world.sql @@ -0,0 +1,4 @@ +DELETE FROM `npc_trainer` WHERE `SpellID` IN (93321, 93375, 79682, 80388); +INSERT INTO `npc_trainer` (`ID`, `SpellID`, `MoneyCost`, `ReqLevel`) VALUES +(200003, 93321, 110, 10), +(200009, 93375, 680, 10); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 882a1de63f..ac2d0a2047 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -43,11 +43,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_MAIL_LIST_ITEMS, "SELECT itemEntry,count FROM item_instance WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, " "gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.slot " - "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid " + "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.active = 1 LEFT JOIN guild_member AS gm ON c.guid = gm.guid " "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, " "c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, " - "cb.guid, c.slot, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? " + "cb.guid, c.slot, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.active = 1 " "LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid " "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_FREE_NAME, "SELECT guid, name, at_login FROM characters WHERE guid = ? AND account = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)", CONNECTION_ASYNC); @@ -600,11 +600,10 @@ void CharacterDatabaseConnection::DoPrepareStatements() // 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_SLOTS_DETAIL, "SELECT slot, id, entry, modelid, level, name FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_PET_ENTRY, "SELECT entry FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SLOT_BY_ID, "SELECT slot, entry FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_PET_SLOT_BY_ID, "SELECT slot, id, 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_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); @@ -621,17 +620,16 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_BOTH); 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_BOTH); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) ", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ? AND id <> ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, active, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_PET_ID_BY_SLOT, "SELECT id, owner, slot FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_UNUSED_ACTIVE_PET_SLOT, "SELECT cp.slot+1 FROM character_pet cp LEFT JOIN character_pet cpa ON cp.slot+1 = cpa.slot WHERE cpa.slot IS NULL AND cp.slot + 1 <= 4 ORDER BY cp.slot LIMIT 1", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_ALL_PETS_DETAIL, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, active, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? ORDER BY slot", CONNECTION_ASYNC); // PvPstats PrepareStatement(CHAR_SEL_PVPSTATS_MAXID, "SELECT MAX(id) FROM pvpstats_battlegrounds", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 77f964cd85..e63aa75058 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -522,25 +522,22 @@ enum CharacterDatabaseStatements CHAR_DEL_PET_SPELLS, CHAR_DEL_CHAR_PET_BY_OWNER, CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, - CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, CHAR_SEL_PET_SLOTS, + CHAR_SEL_PET_ID_BY_SLOT, + CHAR_SEL_UNUSED_ACTIVE_PET_SLOT, 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_DEL_CHAR_PET_DECLINEDNAME, CHAR_INS_CHAR_PET_DECLINEDNAME, CHAR_UPD_CHAR_PET_NAME, - CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, CHAR_UPD_CHAR_PET_SLOT_BY_SLOT, CHAR_UPD_CHAR_PET_SLOT_BY_ID, CHAR_DEL_CHAR_PET_BY_ID, - CHAR_DEL_CHAR_PET_BY_SLOT, + CHAR_INS_PET, + CHAR_SEL_CHAR_ALL_PETS_DETAIL, CHAR_SEL_ITEMCONTAINER_ITEMS, CHAR_DEL_ITEMCONTAINER_ITEMS, diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index 2283616df7..fc1c9ec3f6 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -347,8 +347,8 @@ void PetAI::OwnerAttackedBy(Unit* attacker) if (!attacker || !me->IsAlive()) return; - // Passive pets don't do anything - if (me->HasReactState(REACT_PASSIVE)) + // Passive and assist pets don't do anything + if (me->HasReactState(REACT_PASSIVE) || me->HasReactState(REACT_ASSIST)) return; // Prevent pet from disengaging from current target @@ -368,8 +368,8 @@ void PetAI::OwnerAttacked(Unit* target) if (!target || !me->IsAlive()) return; - // Passive pets don't do anything - if (me->HasReactState(REACT_PASSIVE)) + // Passive and Defesnive pets don't do anything + if (me->HasReactState(REACT_PASSIVE) || me->HasReactState(REACT_DEFENSIVE)) return; // Prevent pet from disengaging from current target @@ -388,8 +388,8 @@ Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const // The parameter: allowAutoSelect lets us disable aggressive pet auto targeting for certain situations // Passive pets don't do next target selection - if (me->HasReactState(REACT_PASSIVE)) - return NULL; + if (me->HasReactState(REACT_PASSIVE) || me->HasReactState(REACT_ASSIST)) + return nullptr; // Check pet attackers first so we don't drag a bunch of targets to the owner if (Unit* myAttacker = me->getAttackerForHelper()) @@ -398,7 +398,7 @@ Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const // Not sure why we wouldn't have an owner but just in case... if (!me->GetCharmerOrOwner()) - return NULL; + return nullptr; // Check owner attackers if (Unit* ownerAttacker = me->GetCharmerOrOwner()->getAttackerForHelper()) @@ -421,7 +421,7 @@ Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const } // Default - no valid targets - return NULL; + return nullptr; } void PetAI::HandleReturnMovement() @@ -433,7 +433,7 @@ void PetAI::HandleReturnMovement() if (me->IsCharmed()) return; - if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY)) + if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->GetCharmInfo()->HasCommandState(COMMAND_MOVE_TO)) { if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning()) { @@ -447,7 +447,7 @@ void PetAI::HandleReturnMovement() me->GetMotionMaster()->MovePoint(me->GetGUID().GetCounter(), x, y, z); } } - else if (!me->GetCharmInfo()->HasCommandState(COMMAND_MOVE_TO)) + if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW)) { if (!me->GetCharmInfo()->IsFollowing() && !me->GetCharmInfo()->IsReturning()) { @@ -562,8 +562,8 @@ bool PetAI::CanAttack(Unit* target) if (me->GetCharmInfo()->IsReturning()) return !me->GetCharmInfo()->IsCommandFollow(); - // Stay - can attack if target is within range or commanded to - if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY)) + // Stay/Move To - can attack if target is within range or commanded to + if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->GetCharmInfo()->HasCommandState(COMMAND_MOVE_TO)) return (me->IsWithinMeleeRange(target) || me->GetCharmInfo()->IsCommandAttack()); // Pets attacking something (or chasing) should only switch targets if owner tells them to diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index f79aeec076..e659efd01f 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -975,7 +975,7 @@ void Battleground::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool Sen bgTypeId = BATTLEGROUND_AA; // set the bg type to all arenas (it will be used for queue refreshing) // unsummon current and summon old pet if there was one and there isn't a current pet - player->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT); + player->RemovePet(nullptr, PET_SAVE_DISMISS); player->ResummonPetTemporaryUnSummonedIfAny(); } diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index b6cfb855f1..d113e14749 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -251,7 +251,7 @@ void TempSummon::UnSummon(uint32 msTime) //ASSERT(!IsPet()); if (IsPet()) { - ((Pet*)this)->Remove(PET_SAVE_NOT_IN_SLOT); + ((Pet*)this)->Remove(PET_SAVE_DISMISS); ASSERT(!IsInWorld()); return; } @@ -325,6 +325,21 @@ bool Minion::IsGuardianPet() const return IsPet() || (m_Properties && m_Properties->Category == SUMMON_CATEGORY_PET); } +bool Minion::IsWarlockMinion() const +{ + switch (GetEntry()) + { + case ENTRY_IMP: + case ENTRY_VOIDWALKER: + case ENTRY_SUCCUBUS: + case ENTRY_FELHUNTER: + case ENTRY_FELGUARD: + return true; + default: + return false; + } +} + Guardian::Guardian(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject) : Minion(properties, owner, isWorldObject) , m_bonusSpellDamage(0) { diff --git a/src/server/game/Entities/Creature/TemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon.h index 40d2ffc090..887537f1c7 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.h +++ b/src/server/game/Entities/Creature/TemporarySummon.h @@ -37,6 +37,33 @@ struct TempSummonData uint32 time; ///< Despawn time, usable only with certain temp summon types }; +enum PetEntries +{ + // Warlock Pet/Minions + ENTRY_IMP = 416, + ENTRY_VOIDWALKER = 1860, + ENTRY_SUCCUBUS = 1863, + ENTRY_FELHUNTER = 417, + ENTRY_FELGUARD = 17252, + + // Mage Pet + ENTRY_WATER_ELEMENTAL = 510, + + // Druid Minion + ENTRY_TREANT = 1964, + + // Shaman Minions + ENTRY_FIRE_ELEMENTAL = 15438, + ENTRY_SPIRIT_WOLF = 29264, + + // Death Knight Pet/Minions + ENTRY_GHOUL = 26125, + ENTRY_BLOODWORM = 28017, + ENTRY_RUNIC_WEAPON = 27893, + ENTRY_GARGOYLE = 27829, + ENTRY_SHADOWFIEND = 19668 +}; + class TC_GAME_API TempSummon : public Creature { public: @@ -76,6 +103,7 @@ class TC_GAME_API Minion : public TempSummon bool IsPetGhoul() const { return GetEntry() == 26125; } // Ghoul may be guardian or pet bool IsSpiritWolf() const { return GetEntry() == 29264; } // Spirit wolf from feral spirits bool IsGuardianPet() const; + bool IsWarlockMinion() const; protected: Unit* const m_owner; float m_followAngle; diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 16b405310b..239d6507e0 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -95,78 +95,58 @@ void Pet::RemoveFromWorld() } } -bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current) +bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool current) { m_loading = true; - ObjectGuid::LowType ownerid = owner->GetGUID().GetCounter(); - - PreparedStatement* stmt; - PreparedQueryResult result; + PlayerPetData* playerPetData; if (petnumber) { - // Known petnumber entry - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY); - stmt->setUInt32(0, ownerid); - stmt->setUInt32(1, petnumber); + playerPetData = owner->GetPlayerPetDataById(petnumber); } - else if (current) + else if ((getPetType() == SUMMON_PET) && petEntry) { - // Current pet (slot 0) - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT); - stmt->setUInt32(0, ownerid); - stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT)); + playerPetData = owner->GetPlayerPetDataByCreatureId(petEntry); } - else if (petEntry) + else if (getPetType() == HUNTER_PET) { - // known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets) - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2); - stmt->setUInt32(0, ownerid); - stmt->setUInt32(1, petEntry); - stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt8(3, uint8(PET_SAVE_LAST_STABLE_SLOT)); + playerPetData = owner->GetPlayerPetDataBySlot(petEntry); } else { - // Any current or other non-stabled pet (for hunter "call pet") - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_BY_SLOT); - stmt->setUInt32(0, ownerid); - stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt8(2, uint8(PET_SAVE_LAST_STABLE_SLOT)); + playerPetData = owner->GetPlayerPetDataCurrent(); } - result = CharacterDatabase.Query(stmt); - - if (!result) + if (!playerPetData) { m_loading = false; return false; } - Field* fields = result->Fetch(); - - // update for case of current pet "slot = 0" - petEntry = fields[1].GetUInt32(); + petEntry = playerPetData->CreatureId; if (!petEntry) return false; - uint32 summonSpellId = fields[14].GetUInt32(); + uint32 summonSpellId = playerPetData->SummonSpellId; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summonSpellId); bool isTemporarySummon = spellInfo && spellInfo->GetDuration() > 0; if (current && isTemporarySummon) return false; - PetType petType = PetType(fields[15].GetUInt8()); + PetType petType = playerPetData->Type; if (petType == HUNTER_PET) { CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry); if (!creatureInfo || !creatureInfo->IsTameable(owner->CanTameExoticPets())) + { + owner->GetSession()->SendStableResult(STABLE_ERR_EXOTIC); return false; + } } - uint32 petId = fields[0].GetUInt32(); + uint32 petId = playerPetData->PetId; if (current && owner->IsPetNeedBeTemporaryUnsummoned()) { @@ -204,11 +184,11 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c m_charmInfo->SetPetNumber(petId, IsPermanentPetFor(owner)); - SetDisplayId(fields[3].GetUInt32()); - SetNativeDisplayId(fields[3].GetUInt32()); - uint32 petlevel = fields[4].GetUInt16(); + SetDisplayId(playerPetData->DisplayId); + SetNativeDisplayId(playerPetData->DisplayId); + uint32 petlevel = playerPetData->Petlevel; SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); - SetName(fields[8].GetString()); + SetName(playerPetData->Name); switch (getPetType()) { @@ -224,7 +204,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, GENDER_NONE); SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, POWER_FOCUS); SetSheath(SHEATH_STATE_MELEE); - SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, fields[9].GetBool() ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED); + SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, playerPetData->Renamed ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED); SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); // this enables popup window (pet abandon, cancel) @@ -235,11 +215,11 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c break; } - SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(NULL))); // cast can't be helped here + SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(nullptr))); // cast can't be helped here SetCreatorGUID(owner->GetGUID()); InitStatsForLevel(petlevel); - SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, fields[5].GetUInt32()); + SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, playerPetData->PetExp); SynchronizeLevelWithOwner(); @@ -254,15 +234,15 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c return false; } - SetReactState(ReactStates(fields[6].GetUInt8())); + SetReactState(ReactStates(playerPetData->Reactstate)); SetCanModifyStats(true); if (getPetType() == SUMMON_PET && !current) //all (?) summon pets come with full health when called, but not when they are current SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); else { - uint32 savedhealth = fields[10].GetUInt32(); - uint32 savedmana = fields[11].GetUInt32(); + uint32 savedhealth = playerPetData->SavedHealth; + uint32 savedmana = playerPetData->SavedMana; if (!savedhealth && getPetType() == HUNTER_PET) setDeathState(JUST_DIED); else @@ -272,29 +252,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c } } - // set current pet as current - // 0=current - // 1..MAX_PET_STABLES in stable slot - // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning)) - if (fields[7].GetUInt8()) - { - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID); - stmt->setUInt8(0, uint8(PET_SAVE_NOT_IN_SLOT)); - stmt->setUInt32(1, ownerid); - stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt32(3, m_charmInfo->GetPetNumber()); - trans->Append(stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); - stmt->setUInt8(0, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt32(1, ownerid); - stmt->setUInt32(2, m_charmInfo->GetPetNumber()); - trans->Append(stmt); - - CharacterDatabase.CommitTransaction(trans); - } + SetSlot(playerPetData->Slot); // 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 @@ -317,13 +275,13 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c InitTalentForLevel(); // set original talents points before spell loading - uint32 timediff = uint32(time(NULL) - fields[13].GetUInt32()); + uint32 timediff = uint32(time(nullptr) - playerPetData->Timediff); _LoadAuras(timediff); // load action bar, if data broken will fill later by default spells. if (!isTemporarySummon) { - m_charmInfo->LoadPetActionBar(fields[12].GetString()); + m_charmInfo->LoadPetActionBar(playerPetData->Actionbar); _LoadSpells(); InitTalentForLevel(); // re-init to check talent count @@ -349,10 +307,10 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c if (getPetType() == HUNTER_PET) { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME); stmt->setUInt32(0, owner->GetGUID().GetCounter()); stmt->setUInt32(1, GetCharmInfo()->GetPetNumber()); - result = CharacterDatabase.Query(stmt); + PreparedQueryResult result = CharacterDatabase.Query(stmt); if (result) { @@ -374,6 +332,8 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c LoadTemplateImmunities(); m_loading = false; + SetActive(true); + return true; } @@ -390,20 +350,6 @@ void Pet::SavePetToDB(PetSaveMode mode) if (!GetOwnerGUID().IsPlayer()) return; - Player* owner = GetOwner(); - - // not save pet as current if another pet temporary unsummoned - if (mode == PET_SAVE_AS_CURRENT && owner->GetTemporaryUnsummonedPetNumber() && - owner->GetTemporaryUnsummonedPetNumber() != m_charmInfo->GetPetNumber()) - { - // pet will lost anyway at restore temporary unsummoned - if (getPetType() == HUNTER_PET) - return; - - // for warlock case - mode = PET_SAVE_NOT_IN_SLOT; - } - uint32 curhealth = GetHealth(); uint32 curmana = GetPower(POWER_MANA); @@ -411,16 +357,33 @@ void Pet::SavePetToDB(PetSaveMode mode) // save auras before possibly removing them _SaveAuras(trans); - // stable and not in slot saves - if (mode > PET_SAVE_AS_CURRENT) - RemoveAllAuras(); - _SaveSpells(trans); GetSpellHistory()->SaveToDB(trans); CharacterDatabase.CommitTransaction(trans); - // current/stable/not_in_slot - if (mode >= PET_SAVE_AS_CURRENT) + if (mode == PET_SAVE_NEW_PET) + { + Optional slot = IsHunterPet() ? GetOwner()->GetFirstUnusedActivePetSlot() : GetOwner()->GetFirstUnusedPetSlot(); + + if (slot) + SetSlot(*slot); + else + mode = PET_SAVE_AS_DELETED; + } + + if (mode == PET_SAVE_DISMISS) + { + RemoveAllAuras(); + SetActive(false); + } + + if (mode == PET_SAVE_LOGOUT) + { + RemoveAllAuras(); + } + + // whole pet is saved to DB + if (mode >= PET_SAVE_CURRENT_STATE) { ObjectGuid::LowType ownerLowGUID = GetOwnerGUID().GetCounter(); std::string name = m_name; @@ -432,62 +395,77 @@ void Pet::SavePetToDB(PetSaveMode mode) stmt->setUInt32(0, m_charmInfo->GetPetNumber()); trans->Append(stmt); - // prevent duplicate using slot (except PET_SAVE_NOT_IN_SLOT) - if (mode <= PET_SAVE_LAST_STABLE_SLOT) - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt8(0, uint8(PET_SAVE_NOT_IN_SLOT)); - stmt->setUInt32(1, ownerLowGUID); - stmt->setUInt8(2, uint8(mode)); - trans->Append(stmt); - } - - // prevent existence another hunter pet in PET_SAVE_AS_CURRENT and PET_SAVE_NOT_IN_SLOT - if (getPetType() == HUNTER_PET && (mode == PET_SAVE_AS_CURRENT || mode > PET_SAVE_LAST_STABLE_SLOT)) - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_SLOT); - stmt->setUInt32(0, ownerLowGUID); - stmt->setUInt8(1, uint8(PET_SAVE_AS_CURRENT)); - stmt->setUInt8(2, uint8(PET_SAVE_LAST_STABLE_SLOT)); - trans->Append(stmt); - } - // save pet - std::ostringstream ss; - ss << "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType) " - << "VALUES (" - << m_charmInfo->GetPetNumber() << ',' - << GetEntry() << ',' - << ownerLowGUID << ',' - << GetNativeDisplayId() << ',' - << uint32(getLevel()) << ',' - << GetUInt32Value(UNIT_FIELD_PETEXPERIENCE) << ',' - << uint32(GetReactState()) << ',' - << uint32(mode) << ", '" - << name.c_str() << "', " - << uint32(HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) ? 0 : 1) << ',' - << curhealth << ',' - << curmana << ", '"; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET); + stmt->setUInt32(0, m_charmInfo->GetPetNumber()); + stmt->setUInt32(1, GetEntry()); + stmt->setUInt64(2, ownerLowGUID); + stmt->setUInt32(3, GetNativeDisplayId()); + stmt->setUInt8(4, getLevel()); + stmt->setUInt32(5, GetUInt32Value(UNIT_FIELD_PETEXPERIENCE)); + stmt->setUInt8(6, GetReactState()); + stmt->setInt16(7, m_petSlot); + stmt->setString(8, m_name); + stmt->setUInt8(9, HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) ? 0 : 1); + stmt->setUInt8(10, IsActive() ? 1 : 0); + stmt->setUInt32(11, curhealth); + stmt->setUInt32(12, curmana); - 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(13, GenerateActionBarData()); - ss << "', " - << time(NULL) << ',' - << GetUInt32Value(UNIT_CREATED_BY_SPELL) << ',' - << uint32(getPetType()) << ')'; + stmt->setUInt32(14, time(nullptr)); // unsure about this + stmt->setUInt32(15, GetUInt32Value(UNIT_CREATED_BY_SPELL)); + stmt->setUInt8(16, getPetType()); + trans->Append(stmt); - trans->Append(ss.str().c_str()); CharacterDatabase.CommitTransaction(trans); + + PlayerPetData* playerPetData = (mode < PET_SAVE_NEW_PET) ? GetOwner()->GetPlayerPetDataById(m_charmInfo->GetPetNumber()) : new PlayerPetData(); + + // save as new if no data for Pet in PlayerPetDataStore + if (mode < PET_SAVE_NEW_PET && !playerPetData) + { + mode = PET_SAVE_NEW_PET; + playerPetData = new PlayerPetData(); + } + + uint32 petId = m_charmInfo->GetPetNumber(); + + if (m_petSlot > PET_SLOT_LAST) + { + TC_LOG_ERROR("sql.sql", "Player::LoadPetsFromDB: bad slot %u for pet %u!", m_petSlot, petId); + } + + playerPetData->PetId = petId; + playerPetData->CreatureId = GetEntry(); + playerPetData->Owner = ownerLowGUID; + playerPetData->DisplayId = GetNativeDisplayId(); + playerPetData->Petlevel = getLevel(); + playerPetData->PetExp = GetUInt32Value(UNIT_FIELD_PETEXPERIENCE); + playerPetData->Reactstate = GetReactState(); + playerPetData->Slot = m_petSlot; + playerPetData->Name = m_name; + playerPetData->Renamed = HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) ? 0 : 1; + playerPetData->Active = IsActive() ? 1 : 0; + playerPetData->SavedHealth = curhealth; + playerPetData->SavedMana = curmana; + playerPetData->Actionbar = GenerateActionBarData(); + playerPetData->Timediff = time(nullptr); + playerPetData->SummonSpellId = GetUInt32Value(UNIT_CREATED_BY_SPELL); + playerPetData->Type = getPetType(); + + if (mode == PET_SAVE_NEW_PET) + GetOwner()->AddToPlayerPetDataStore(playerPetData); + } // delete else { RemoveAllAuras(); DeleteFromDB(m_charmInfo->GetPetNumber()); + + GetOwner()->DeleteFromPlayerPetDataStore(m_charmInfo->GetPetNumber()); + GetOwner()->GetSession()->SendStablePet(ObjectGuid::Empty); } } @@ -551,9 +529,9 @@ void Pet::Update(uint32 diff) { case CORPSE: { - if (getPetType() != HUNTER_PET || m_corpseRemoveTime <= time(NULL)) + if (!IsHunterPet() || m_corpseRemoveTime <= time(nullptr)) { - Remove(PET_SAVE_NOT_IN_SLOT); //hunters' pets never get removed because of death, NEVER! + Remove(PET_SAVE_DISMISS); //hunters' pets never get removed because of death, NEVER! return; } break; @@ -565,7 +543,7 @@ void Pet::Update(uint32 diff) if ((!IsWithinDistInMap(owner, GetMap()->GetVisibilityRange()) && !isPossessed()) || (isControlled() && !owner->GetPetGUID())) //if (!owner || (!IsWithinDistInMap(owner, GetMap()->GetVisibilityDistance()) && (owner->GetCharmGUID() && (owner->GetCharmGUID() != GetGUID()))) || (isControlled() && !owner->GetPetGUID())) { - Remove(PET_SAVE_NOT_IN_SLOT, true); + Remove(PET_SAVE_DISMISS, true); return; } @@ -574,7 +552,7 @@ void Pet::Update(uint32 diff) if (owner->GetPetGUID() != GetGUID()) { TC_LOG_ERROR("entities.pet", "Pet %u is not pet of owner %s, removed", GetEntry(), GetOwner()->GetName().c_str()); - Remove(getPetType() == HUNTER_PET?PET_SAVE_AS_DELETED:PET_SAVE_NOT_IN_SLOT); + Remove(IsHunterPet() ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS); return; } } @@ -585,7 +563,7 @@ void Pet::Update(uint32 diff) m_duration -= diff; else { - Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED:PET_SAVE_NOT_IN_SLOT); + Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS); return; } } @@ -676,7 +654,7 @@ void Pet::Remove(PetSaveMode mode, bool returnreagent) void Pet::GivePetXP(uint32 xp) { - if (getPetType() != HUNTER_PET) + if (!IsHunterPet()) return; if (xp < 1) @@ -716,7 +694,7 @@ void Pet::GivePetLevel(uint8 level) if (!level || level == getLevel()) return; - if (getPetType() == HUNTER_PET) + if (!IsHunterPet()) { SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(level)*PET_XP_FACTOR)); @@ -1341,7 +1319,7 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel if (active == ACT_DECIDE) // active was not used before, so we save it's autocast/passive state here { if (spellInfo->IsAutocastable()) - newspell.active = ACT_DISABLED; + newspell.active = ACT_ENABLED; else newspell.active = ACT_PASSIVE; } @@ -1639,6 +1617,7 @@ bool Pet::resetTalents() void Pet::resetTalentsForAllPetsOf(Player* owner, Pet* onlinePet /*= NULL*/) { + /* // not need after this call if (owner->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS)) owner->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true); @@ -1712,6 +1691,7 @@ void Pet::resetTalentsForAllPetsOf(Player* owner, Pet* onlinePet /*= NULL*/) ss << ')'; CharacterDatabase.Execute(ss.str().c_str()); + */ } void Pet::InitTalentForLevel() @@ -1948,3 +1928,16 @@ void Pet::SetDisplayId(uint32 modelId) if (GetOwner()->GetGroup()) GetOwner()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MODEL_ID); } + +std::string Pet::GenerateActionBarData() const +{ + 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()) << ' '; + } + + return ss.str(); +} diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index 45796d5793..94572df765 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -23,7 +23,23 @@ #include "TemporarySummon.h" #define PET_FOCUS_REGEN_INTERVAL 4 * IN_MILLISECONDS -#define HAPPINESS_LEVEL_SIZE 333000 + +enum StableResultCode +{ + STABLE_ERR_MONEY = 0x01, // "you don't have enough money" + STABLE_ERR_INVALID_SLOT = 0x03, // "That slot is locked" + STABLE_SUCCESS_STABLE = 0x08, // stable success + STABLE_SUCCESS_UNSTABLE = 0x09, // unstable/swap success + STABLE_SUCCESS_BUY_SLOT = 0x0A, // buy slot success + STABLE_ERR_EXOTIC = 0x0B, // "you are unable to control exotic creatures" + STABLE_ERR_STABLE = 0x0C // "Internal pet error" +}; + +enum PetStableInfo +{ + PET_STABLE_ACTIVE = 1, + PET_STABLE_INACTIVE = 2 +}; struct PetSpell { @@ -60,7 +76,7 @@ class TC_GAME_API Pet : public Guardian bool CreateBaseAtCreature(Creature* creature); bool CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner); bool CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map); - bool LoadPetFromDB(Player* owner, uint32 petentry = 0, uint32 petnumber = 0, bool current = false); + bool LoadPetData(Player* owner, uint32 petentry = 0, uint32 petnumber = 0, bool current = false); bool IsLoading() const override { return m_loading;} void SavePetToDB(PetSaveMode mode); void Remove(PetSaveMode mode, bool returnreagent = false); @@ -119,6 +135,7 @@ class TC_GAME_API 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; @@ -145,6 +162,12 @@ class TC_GAME_API Pet : public Guardian Player* GetOwner() const; + uint32 GetSlot() { return m_petSlot; } + void SetSlot(uint32 newPetSlot) { m_petSlot = newPetSlot; } // use only together with DB update + + bool IsActive() { return m_petActive; } + void SetActive(bool active) { m_petActive = active; } + protected: PetType m_petType; int32 m_duration; // time until unsummon (used mostly for summoned guardians and not used for controlled pets) @@ -153,6 +176,8 @@ class TC_GAME_API Pet : public Guardian uint32 m_focusRegenTimer; DeclinedName *m_declinedname; + uint32 m_petSlot; + bool m_petActive = false; 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 dd359dc6ee..42b248dcf7 100644 --- a/src/server/game/Entities/Pet/PetDefines.h +++ b/src/server/game/Entities/Pet/PetDefines.h @@ -26,23 +26,27 @@ enum PetType MAX_PET_TYPE = 4 }; -#define MAX_PET_STABLES 4 +#define MAX_PET_STABLES 20 // stored in character_pet.slot enum PetSaveMode { - PET_SAVE_AS_DELETED = -1, // not saved in fact - PET_SAVE_AS_CURRENT = 0, // in current slot (with player) - PET_SAVE_FIRST_STABLE_SLOT = 1, - PET_SAVE_LAST_STABLE_SLOT = MAX_PET_STABLES, // last in DB stable slot index (including), all higher have same meaning as PET_SAVE_NOT_IN_SLOT - PET_SAVE_NOT_IN_SLOT = 100 // for avoid conflict with stable size grow will use 100 + PET_SAVE_AS_DELETED = -1, // not saved in fact + PET_SAVE_UPADTE_SLOT = 0, // not used yet + PET_SAVE_CURRENT_STATE = 1, // Saves everything like it is atm + PET_SAVE_DISMISS = 2, // Saves everything like it is atm, but removes auras and sets unactive + PET_SAVE_LOGOUT = 3, // Saves everything like it is atm, but removes auras + PET_SAVE_NEW_PET = 4 }; -enum HappinessState +enum PetStableSlot { - UNHAPPY = 1, - CONTENT = 2, - HAPPY = 3 + PET_SLOT_FIRST = 0, + PET_SLOT_LAST = 20, + PET_SLOT_FIRST_ACTIVE_SLOT = PET_SLOT_FIRST, + PET_SLOT_LAST_ACTIVE_SLOT = 4, + PET_SLOT_FIRST_STABLE_SLOT = 5, + PET_SLOT_LAST_STABLE_SLOT = PET_SLOT_LAST }; enum PetSpellState diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index e3ea1e2669..195e4ed9e5 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -583,6 +583,9 @@ Player::~Player() for (uint8 i = 0; i < MAX_CUF_PROFILES; ++i) delete _CUFProfiles[i]; + for (uint8 i = 0; i < PlayerPetDataStore.size(); i++) + delete PlayerPetDataStore[i]; + ClearResurrectRequestData(); sWorld->DecreasePlayerCount(); @@ -1498,7 +1501,7 @@ void Player::Update(uint32 p_time) Pet* pet = GetPet(); if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityRange()) && !pet->isPossessed()) //if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGUID() && (pet->GetGUID() != GetCharmGUID()))) - RemovePet(pet, PET_SAVE_NOT_IN_SLOT, true); + RemovePet(pet, PET_SAVE_DISMISS, true); if (IsAlive()) { @@ -1541,7 +1544,7 @@ void Player::setDeathState(DeathState s) ClearResurrectRequestData(); //FIXME: is pet dismissed at dying or releasing spirit? if second, add setDeathState(DEAD) to HandleRepopRequestOpcode and define pet unsummon here with (s == DEAD) - RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); + RemovePet(nullptr, PET_SAVE_DISMISS, true); // save value before aura remove in Unit::setDeathState ressSpellId = GetUInt32Value(PLAYER_SELF_RES_SPELL); @@ -3727,6 +3730,9 @@ void Player::LearnSpell(uint32 spell_id, bool dependent, uint32 fromSkill /*= 0* LearnSpell(itr2->second, false, fromSkill); } } + + if (CanControlPet(spell_id) && GetPet()) + PetSpellInitialize(); } void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank) @@ -3995,7 +4001,7 @@ bool Player::ResetTalents(bool no_cost) } } - RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); + RemovePet(nullptr, PET_SAVE_DISMISS, true); for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) { @@ -4072,7 +4078,7 @@ bool Player::ResetTalents(bool no_cost) if (Pet* pet = GetPet()) { if (pet->getPetType() == HUNTER_PET && !pet->GetCreatureTemplate()->IsTameable(CanTameExoticPets())) - RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); + RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); } */ @@ -18613,11 +18619,138 @@ void Player::LoadPet() if (IsInWorld()) { Pet* pet = new Pet(this); - if (!pet->LoadPetFromDB(this, 0, 0, true)) + if (!pet->LoadPetData(this, 0, 0, true)) delete pet; } } +void Player::LoadPetsFromDB(PreparedQueryResult result) +{ + if (!result) + return; + + do + { + Field* fields = result->Fetch(); + + PlayerPetData* playerPetData = new PlayerPetData(); + + uint8 slot = fields[7].GetUInt8(); + uint32 petId = fields[0].GetUInt32(); + + if (slot < PET_SLOT_FIRST || slot > PET_SLOT_LAST) + { + TC_LOG_ERROR("sql.sql", "Player::LoadPetsFromDB: bad slot %u for pet %u!", slot, petId); + continue; + } + + playerPetData->PetId = petId; + playerPetData->CreatureId = fields[1].GetUInt32(); + playerPetData->Owner = fields[2].GetUInt64(); + playerPetData->DisplayId = fields[3].GetUInt32(); + playerPetData->Petlevel = fields[4].GetUInt16(); + playerPetData->PetExp = fields[5].GetUInt32(); + playerPetData->Reactstate = ReactStates(fields[6].GetUInt8()); + playerPetData->Slot = slot; + playerPetData->Name = fields[8].GetString(); + playerPetData->Renamed = fields[9].GetBool(); + playerPetData->Active = fields[10].GetBool(); + playerPetData->SavedHealth = fields[11].GetUInt32(); + playerPetData->SavedMana = fields[12].GetUInt32(); + playerPetData->Actionbar = fields[13].GetString(); + playerPetData->Timediff = fields[14].GetUInt32(); + playerPetData->SummonSpellId = fields[15].GetUInt32(); + playerPetData->Type = PetType(fields[16].GetUInt8()); + + PlayerPetDataStore.push_back(playerPetData); + + } while (result->NextRow()); +} + +PlayerPetData* Player::GetPlayerPetDataById(uint32 petId) +{ + for (PlayerPetData* p : PlayerPetDataStore) + if (p->PetId == petId) + return p; + + return nullptr; +} + +PlayerPetData* Player::GetPlayerPetDataBySlot(uint8 slot) +{ + for (PlayerPetData* p : PlayerPetDataStore) + if (p->Slot == slot) + return p; + + return nullptr; +} + +PlayerPetData* Player::GetPlayerPetDataByCreatureId(uint32 creatureId) +{ + for (PlayerPetData* p : PlayerPetDataStore) + if (p->CreatureId == creatureId) + return p; + + return nullptr; +} + +PlayerPetData* Player::GetPlayerPetDataCurrent() +{ + for (PlayerPetData* p : PlayerPetDataStore) + if (p->Active == true) + return p; + + return nullptr; +} + +Optional Player::GetFirstUnusedActivePetSlot() +{ + std::set unusedActiveSlot = { 0, 1, 2, 3, 4 }; //unfiltered + + for (PlayerPetData* p : PlayerPetDataStore) + if (unusedActiveSlot.find(p->Slot) != unusedActiveSlot.end()) + unusedActiveSlot.erase(p->Slot); + + if (!unusedActiveSlot.empty()) + return *unusedActiveSlot.begin(); + + return Optional{}; +} + +Optional Player::GetFirstUnusedPetSlot() +{ + std::set unusedSlot; + + for (uint8 i = 0; i < PET_SLOT_LAST; i++) // 4 MAX StableSlots (256 theoretically) + unusedSlot.insert(i); + + for (PlayerPetData* p : PlayerPetDataStore) + if (unusedSlot.find(p->Slot) != unusedSlot.end()) + unusedSlot.erase(p->Slot); + + if (!unusedSlot.empty()) + return *unusedSlot.begin(); + + return Optional{}; +} + +void Player::DeleteFromPlayerPetDataStore(uint32 petNumber) +{ + for (uint8 i = 0; i < PlayerPetDataStore.size(); ++i) + { + if (PlayerPetDataStore[i]->PetId == petNumber) + { + delete PlayerPetDataStore[i]; + PlayerPetDataStore.erase(PlayerPetDataStore.begin() + (i--)); + } + } +} + +void Player::AddToPlayerPetDataStore(PlayerPetData* playerPetData) +{ + PlayerPetDataStore.push_back(playerPetData); +} + void Player::_LoadQuestStatus(PreparedQueryResult result) { uint16 slot = 0; @@ -19783,7 +19916,7 @@ void Player::SaveToDB(bool create /*=false*/) // save pet (hunter pet level and experience and all type pets health/mana). if (Pet* pet = GetPet()) - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_CURRENT_STATE); } // fast save function for item/money cheating preventing - save only inventory and money state @@ -20883,12 +21016,11 @@ void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent) case 1863: case 417: case 17252: - mode = PET_SAVE_NOT_IN_SLOT; + mode = PET_SAVE_DISMISS; break; } } - // only if current pet in slot pet->SavePetToDB(mode); SetMinion(pet, false); @@ -21086,8 +21218,31 @@ void Player::SendOnCancelExpectedVehicleRideAura() const GetSession()->SendPacket(&data); } +bool Player::CanControlPet(uint32 spellId) const +{ + // Hunters and Warlocks cannot control their pets until they learned their spell + if (spellId) + { + switch (spellId) + { + case 93321: // Control Pet + return getClass() == CLASS_HUNTER; + case 93375: // Control Demon + return getClass() == CLASS_WARLOCK; + } + } + else + return (HasAura(93321) && getClass() == CLASS_HUNTER || HasAura(93375) && getClass() == CLASS_WARLOCK); + + return false; +} + void Player::PetSpellInitialize() { + // Do not send the pet action bar when Hunters or Warlocks cannot control their pets + if (!CanControlPet()) + return; + Pet* pet = GetPet(); if (!pet) @@ -21095,7 +21250,7 @@ void Player::PetSpellInitialize() CharmInfo* charmInfo = pet->GetCharmInfo(); - WorldPacket data(SMSG_PET_SPELLS, 8+2+4+4+4*MAX_UNIT_ACTION_BAR_INDEX+1+1); + WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1); data << uint64(pet->GetGUID()); data << uint16(pet->GetCreatureTemplate()->family); // creature family (required for pet talents) data << uint32(pet->GetDuration()); @@ -22871,7 +23026,7 @@ template<> inline void BeforeVisibilityDestroy(Creature* t, Player* p) { if (p->GetPetGUID() == t->GetGUID() && t->IsPet()) - t->ToPet()->Remove(PET_SAVE_NOT_IN_SLOT, true); + t->ToPet()->Remove(PET_SAVE_DISMISS, true); } void Player::UpdateVisibilityOf(WorldObject* target) @@ -25979,7 +26134,7 @@ void Player::UnsummonPetTemporaryIfAny() m_oldpetspell = pet->GetUInt32Value(UNIT_CREATED_BY_SPELL); } - RemovePet(pet, PET_SAVE_AS_CURRENT); + RemovePet(pet, PET_SAVE_DISMISS); } void Player::ResummonPetTemporaryUnSummonedIfAny() @@ -25995,7 +26150,7 @@ void Player::ResummonPetTemporaryUnSummonedIfAny() return; Pet* NewPet = new Pet(this); - if (!NewPet->LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true)) + if (!NewPet->LoadPetData(this, 0, m_temporaryUnsummonedPetNumber, false)) delete NewPet; m_temporaryUnsummonedPetNumber = 0; @@ -26572,7 +26727,7 @@ void Player::ActivateSpec(uint8 spec) // TO-DO: We need more research to know what happens with warlock's reagent if (Pet* pet = GetPet()) - RemovePet(pet, PET_SAVE_NOT_IN_SLOT); + RemovePet(pet, PET_SAVE_DISMISS); ClearAllReactives(); UnsummonAllTotems(); @@ -27373,34 +27528,6 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy { Pet* pet = new Pet(this, petType); - if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry)) - { - // 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 > 0) - pet->SetDuration(duration); - - 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()) { @@ -27409,70 +27536,70 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy return nullptr; } - Map* map = GetMap(); - uint32 pet_number = sObjectMgr->GeneratePetNumber(); - if (!pet->Create(map->GenerateLowGuid(), map, entry, pet_number)) + bool hasPetData = pet->LoadPetData(this, entry); + + if (!hasPetData && petType == HUNTER_PET) { - TC_LOG_ERROR("misc", "Player::SummonPet: No such creature entry %u", entry); delete pet; return nullptr; } - pet->CopyPhaseFrom(this); - - 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) + if (!hasPetData) { - case SUMMON_PET: - // this enables pet details window (Shift+P) - pet->GetCharmInfo()->SetPetNumber(pet_number, true); - pet->SetUInt32Value(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS); - 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()); - - switch (petType) - { - case SUMMON_PET: - pet->InitPetCreateSpells(); - pet->InitTalentForLevel(); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); - PetSpellInitialize(); - break; - default: - break; - } - - if (petType == SUMMON_PET) - { - // 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();) + Map* map = GetMap(); + uint32 pet_number = sObjectMgr->GeneratePetNumber(); + if (!pet->Create(map->GenerateLowGuid(), map, entry, pet_number)) { - if ((*itr)->GetMiscValue() == 2228) - { - RemoveAurasDueToSpell((*itr)->GetId()); - itr = auraClassScripts.begin(); - } - else - ++itr; + TC_LOG_ERROR("misc", "Player::SummonPet: No such creature entry %u", entry); + delete pet; + return nullptr; + } + + // generate new name for summon pet + std::string new_name = sObjectMgr->GeneratePetName(entry); + if (!new_name.empty()) + pet->SetName(new_name); + + pet->CopyPhaseFrom(this); + + pet->SetCreatorGUID(GetGUID()); + pet->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, GetFaction()); + + pet->setPowerType(POWER_MANA); + pet->SetUInt64Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + pet->SetUInt32Value(UNIT_FIELD_BYTES_1, 0); + pet->InitStatsForLevel(getLevel()); + pet->SetReactState(REACT_ASSIST); + + SetMinion(pet, true); + + switch (petType) + { + case SUMMON_PET: + pet->GetCharmInfo()->SetPetNumber(pet_number, true); + pet->SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_MAGE); + 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()); + + switch (petType) + { + case SUMMON_PET: + pet->InitPetCreateSpells(); + pet->SavePetToDB(PET_SAVE_NEW_PET); + PetSpellInitialize(); + GetSession()->SendPetAdded(pet->GetSlot(), pet->GetCharmInfo()->GetPetNumber(), pet->GetEntry(), pet->GetDisplayId(), pet->getLevel(), pet->GetName()); + break; + default: + break; } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 22d1a29b6e..4d88d7c5f6 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -943,6 +943,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_CURRENCY = 34, PLAYER_LOGIN_QUERY_LOAD_CUF_PROFILES = 35, PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION = 36, + PLAYER_LOGIN_QUERY_LOAD_ALL_PETS = 37, MAX_PLAYER_LOGIN_QUERY }; @@ -1094,6 +1095,27 @@ class TC_GAME_API PlayerTaxi std::ostringstream& operator << (std::ostringstream& ss, PlayerTaxi const& taxi); +struct PlayerPetData +{ + uint32 PetId; + uint32 CreatureId; + uint64 Owner; + uint32 DisplayId; + uint32 Petlevel; + uint32 PetExp; + ReactStates Reactstate; + uint8 Slot; + std::string Name; + bool Renamed; + bool Active; + uint32 SavedHealth; + uint32 SavedMana; + std::string Actionbar; + uint32 Timediff; + uint32 SummonSpellId; + PetType Type; +}; + class Player; /// Holder for Battleground data @@ -1519,6 +1541,7 @@ class TC_GAME_API Player : public Unit, public GridObject void SendItemDurations(); void LoadCorpse(PreparedQueryResult result); void LoadPet(); + void LoadPetsFromDB(PreparedQueryResult result); bool AddItem(uint32 itemId, uint32 count); @@ -1768,6 +1791,7 @@ class TC_GAME_API Player : public Unit, public GridObject bool RemoveMItem(uint32 id); void SendOnCancelExpectedVehicleRideAura() const; + bool CanControlPet(uint32 spellId = 0) const; void PetSpellInitialize(); void CharmSpellInitialize(); void PossessSpellInitialize(); @@ -2551,9 +2575,20 @@ class TC_GAME_API Player : public Unit, public GridObject VoidStorageItem* GetVoidStorageItem(uint8 slot) const; VoidStorageItem* GetVoidStorageItem(uint64 id, uint8& slot) const; + // LFG Call to Arms void SetTempCallToArmsRoles(uint8 roles) { _tmpLfgRolesCheck = roles; } uint8 GetCallToArmsTempRoles() { return _tmpLfgRolesCheck; } + // Pets + PlayerPetData* GetPlayerPetDataById(uint32 petId); + PlayerPetData* GetPlayerPetDataBySlot(uint8 slot); + PlayerPetData* GetPlayerPetDataByCreatureId(uint32 creatureId); + PlayerPetData* GetPlayerPetDataCurrent(); + Optional GetFirstUnusedActivePetSlot(); + Optional GetFirstUnusedPetSlot(); + void DeleteFromPlayerPetDataStore(uint32 petNumber); + void AddToPlayerPetDataStore(PlayerPetData* playerPetData); + protected: // Gamemaster whisper whitelist GuidList WhisperList; @@ -2915,6 +2950,8 @@ class TC_GAME_API Player : public Unit, public GridObject Archaeology _archaeology; uint8 _tmpLfgRolesCheck; + + std::vector PlayerPetDataStore; }; TC_GAME_API void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index f26cf8a5ef..09e619aec2 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -960,17 +960,6 @@ void Creature::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, ######## ######## #######################################*/ -#define ENTRY_IMP 416 -#define ENTRY_VOIDWALKER 1860 -#define ENTRY_SUCCUBUS 1863 -#define ENTRY_FELHUNTER 417 -#define ENTRY_FELGUARD 17252 -#define ENTRY_WATER_ELEMENTAL 510 -#define ENTRY_TREANT 1964 -#define ENTRY_FIRE_ELEMENTAL 15438 -#define ENTRY_GHOUL 26125 -#define ENTRY_BLOODWORM 28017 - bool Guardian::UpdateStats(Stats stat) { if (stat >= MAX_STATS) @@ -1102,13 +1091,12 @@ void Guardian::UpdateMaxHealth() float multiplicator; switch (GetEntry()) { - case ENTRY_IMP: multiplicator = 8.4f; break; - case ENTRY_VOIDWALKER: multiplicator = 11.0f; break; - case ENTRY_SUCCUBUS: multiplicator = 9.1f; break; - case ENTRY_FELHUNTER: multiplicator = 9.5f; break; - case ENTRY_FELGUARD: multiplicator = 11.0f; break; - case ENTRY_BLOODWORM: multiplicator = 1.0f; break; - default: multiplicator = 10.0f; break; + case ENTRY_BLOODWORM: + multiplicator = 1.0f; + break; + default: + multiplicator = 14.0f; + break; } float value = GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); @@ -1116,6 +1104,37 @@ void Guardian::UpdateMaxHealth() value += GetModifierValue(unitMod, TOTAL_VALUE) + stamina * multiplicator; value *= GetModifierValue(unitMod, TOTAL_PCT); + if (Unit* owner = GetOwner()) + { + switch (GetEntry()) + { + case ENTRY_IMP: + value = (7.7 * getLevel() / 80) * GetOwner()->GetStat(STAT_STAMINA) + GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + break; + case ENTRY_VOIDWALKER: + value = (15.0 * getLevel() / 80) * GetOwner()->GetStat(STAT_STAMINA) + GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + break; + case ENTRY_FELHUNTER: + case ENTRY_FELGUARD: + case ENTRY_SUCCUBUS: + value = (9.1 * getLevel() / 80) * GetOwner()->GetStat(STAT_STAMINA) + GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + break; + case ENTRY_WATER_ELEMENTAL: + value = (7.0 * getLevel() / 80) * GetOwner()->GetStat(STAT_STAMINA) + GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + break; + case ENTRY_SPIRIT_WOLF: + case ENTRY_GARGOYLE: + case ENTRY_SHADOWFIEND: + value = (4.2 * getLevel() / 80) * GetOwner()->GetStat(STAT_STAMINA) + GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + break; + case ENTRY_TREANT: + value = (1.9 * getLevel() / 80) * GetOwner()->GetStat(STAT_STAMINA) + GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + break; + default: + break; + } + } + SetMaxHealth((uint32)value); } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 310d1e515b..d3bfbc994e 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -5943,7 +5943,7 @@ void Unit::SetMinion(Minion *minion, bool apply) { // remove existing minion pet if (oldPet->IsPet()) - ((Pet*)oldPet)->Remove(PET_SAVE_AS_CURRENT); + ((Pet*)oldPet)->Remove(PET_SAVE_DISMISS); else oldPet->UnSummon(); SetPetGUID(minion->GetGUID()); @@ -10041,7 +10041,7 @@ uint32 Unit::GetPowerIndex(uint32 powerType) const uint32 classId = getClass(); if (Pet const* pet = ToPet()) { - if (pet->getPetType() == HUNTER_PET) + if (pet->IsHunterPet()) classId = CLASS_HUNTER; else if (pet->IsPetGhoul()) classId = CLASS_ROGUE; @@ -10290,17 +10290,19 @@ void CharmInfo::RestoreState() void CharmInfo::InitPetActionBar() { - // the first 3 SpellOrActions are attack, follow and stay - for (uint32 i = 0; i < ACTION_BAR_INDEX_PET_SPELL_START - ACTION_BAR_INDEX_START; ++i) - SetActionBar(ACTION_BAR_INDEX_START + i, COMMAND_ATTACK - i, ACT_COMMAND); + // the first 3 SpellOrActions are attack, follow and moveTo + SetActionBar(ACTION_BAR_INDEX_ATTACK, COMMAND_ATTACK, ACT_COMMAND); + SetActionBar(ACTION_BAR_INDEX_FOLLOW, COMMAND_FOLLOW, ACT_COMMAND); + SetActionBar(ACTION_BAR_INDEX_MOVE_TO, COMMAND_MOVE_TO, ACT_COMMAND); // middle 4 SpellOrActions are spells/special attacks/abilities for (uint32 i = 0; i < ACTION_BAR_INDEX_PET_SPELL_END-ACTION_BAR_INDEX_PET_SPELL_START; ++i) SetActionBar(ACTION_BAR_INDEX_PET_SPELL_START + i, 0, ACT_PASSIVE); // last 3 SpellOrActions are reactions - for (uint32 i = 0; i < ACTION_BAR_INDEX_END - ACTION_BAR_INDEX_PET_SPELL_END; ++i) - SetActionBar(ACTION_BAR_INDEX_PET_SPELL_END + i, COMMAND_ATTACK - i, ACT_REACTION); + SetActionBar(ACTION_BAR_INDEX_ASSIST, REACT_ASSIST, ACT_REACTION); + SetActionBar(ACTION_BAR_INDEX_DEFENSIVE, REACT_DEFENSIVE, ACT_REACTION); + SetActionBar(ACTION_BAR_INDEX_PASSIVE, REACT_PASSIVE, ACT_REACTION); } void CharmInfo::InitEmptyActionBar(bool withAttack) @@ -10423,7 +10425,7 @@ bool CharmInfo::AddSpellToActionBar(SpellInfo const* spellInfo, ActiveStates new uint8 j = (preferredSlot + i) % MAX_UNIT_ACTION_BAR_INDEX; if (!PetActionBar[j].GetAction() && PetActionBar[j].IsActionBarForSpell()) { - SetActionBar(j, spell_id, newstate == ACT_DECIDE ? spellInfo->IsAutocastable() ? ACT_DISABLED : ACT_PASSIVE : newstate); + SetActionBar(j, spell_id, newstate == ACT_DECIDE ? spellInfo->IsAutocastable() ? ACT_ENABLED : ACT_PASSIVE : newstate); return true; } } @@ -11359,17 +11361,17 @@ void Unit::SetContestedPvP(Player* attackedPlayer) Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) { if (GetTypeId() != TYPEID_PLAYER) - return NULL; + return nullptr; Pet* pet = new Pet(ToPlayer(), HUNTER_PET); if (!pet->CreateBaseAtCreature(creatureTarget)) { delete pet; - return NULL; + return nullptr; } - uint8 level = creatureTarget->getLevel() + 5 < getLevel() ? (getLevel() - 5) : creatureTarget->getLevel(); + uint8 level = getLevel(); InitTamedPet(pet, level, spell_id); @@ -11379,18 +11381,18 @@ Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id) { if (GetTypeId() != TYPEID_PLAYER) - return NULL; + return nullptr; CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry); if (!creatureInfo) - return NULL; + return nullptr; Pet* pet = new Pet(ToPlayer(), HUNTER_PET); if (!pet->CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id)) { delete pet; - return NULL; + return nullptr; } return pet; @@ -11418,6 +11420,7 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) pet->InitPetCreateSpells(); //pet->InitLevelupSpellsForLevel(); pet->SetFullHealth(); + pet->SetReactState(REACT_ASSIST); return true; } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 408fb5743c..865d3f9389 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1109,7 +1109,8 @@ enum ReactStates { REACT_PASSIVE = 0, REACT_DEFENSIVE = 1, - REACT_AGGRESSIVE = 2 + REACT_AGGRESSIVE = 2, + REACT_ASSIST = 3 }; enum CommandStates : uint8 @@ -1171,8 +1172,14 @@ typedef UnitActionBarEntry CharmSpellInfo; enum ActionBarIndex { ACTION_BAR_INDEX_START = 0, + ACTION_BAR_INDEX_ATTACK = ACTION_BAR_INDEX_START, + ACTION_BAR_INDEX_FOLLOW = 1, + ACTION_BAR_INDEX_MOVE_TO = 2, ACTION_BAR_INDEX_PET_SPELL_START = 3, ACTION_BAR_INDEX_PET_SPELL_END = 7, + ACTION_BAR_INDEX_ASSIST = ACTION_BAR_INDEX_PET_SPELL_END, + ACTION_BAR_INDEX_DEFENSIVE = 8, + ACTION_BAR_INDEX_PASSIVE = 9, ACTION_BAR_INDEX_END = 10 }; diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 0e9d1a8ead..a08636d7d5 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -225,6 +225,10 @@ bool LoginQueryHolder::Initialize() stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_ALL_PETS_DETAIL); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ALL_PETS, stmt); + return res; } @@ -289,8 +293,7 @@ void WorldSession::HandleCharEnumOpcode(WorldPacket& /*recvData*/) else stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ENUM); - stmt->setUInt8(0, PET_SAVE_AS_CURRENT); - stmt->setUInt32(1, GetAccountId()); + stmt->setUInt32(0, GetAccountId()); _queryProcessor.AddQuery(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleCharEnum, this, std::placeholders::_1))); } @@ -993,6 +996,10 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS)) Pet::resetTalentsForAllPetsOf(pCurrChar); + pCurrChar->LoadPetsFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ALL_PETS)); + if (pCurrChar->getClass() == CLASS_HUNTER) + pCurrChar->GetSession()->SendStablePet(ObjectGuid::Empty); + // Load pet if any (if player not alive and in taxi flight or another then pet will remember as temporary unsummoned) pCurrChar->LoadPet(); diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index b7b0c0dede..72bfb8aea6 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -74,7 +74,7 @@ void WorldSession::HandleRepopRequestOpcode(WorldPacket& recvData) } //this is spirit release confirm? - GetPlayer()->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); + GetPlayer()->RemovePet(nullptr, PET_SAVE_DISMISS, true); GetPlayer()->BuildPlayerRepop(); GetPlayer()->RepopAtGraveyard(); } diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 41bbeea955..a201b50dad 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -37,17 +37,6 @@ #include "CreatureAI.h" #include "SpellInfo.h" -enum StableResultCode -{ - STABLE_ERR_MONEY = 0x01, // "you don't have enough money" - STABLE_ERR_INVALID_SLOT = 0x03, // "That slot is locked" - STABLE_SUCCESS_STABLE = 0x08, // stable success - STABLE_SUCCESS_UNSTABLE = 0x09, // unstable/swap success - STABLE_SUCCESS_BUY_SLOT = 0x0A, // buy slot success - STABLE_ERR_EXOTIC = 0x0B, // "you are unable to control exotic creatures" - STABLE_ERR_STABLE = 0x0C, // "Internal pet error" -}; - void WorldSession::HandleTabardVendorActivateOpcode(WorldPacket& recvData) { ObjectGuid guid; @@ -486,68 +475,29 @@ void WorldSession::HandleListStabledPetsOpcode(WorldPacket& recvData) void WorldSession::SendStablePet(ObjectGuid guid) { - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS_DETAIL); + WorldPacket data(MSG_LIST_STABLED_PETS, 200); - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt8(1, PET_SAVE_FIRST_STABLE_SLOT); - stmt->setUInt8(2, PET_SAVE_LAST_STABLE_SLOT); + data << uint64(guid); // Stablemaster + data << uint8(_player->PlayerPetDataStore.size()); + data << uint8(PET_SLOT_LAST_STABLE_SLOT); // Stable Slots - _queryProcessor.AddQuery(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::SendStablePetCallback, this, guid, std::placeholders::_1))); -} - -void WorldSession::SendStablePetCallback(ObjectGuid guid, PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - - TC_LOG_DEBUG("network", "WORLD: Recv MSG_LIST_STABLED_PETS Send."); - - WorldPacket data(MSG_LIST_STABLED_PETS, 200); // guess size - - data << uint64(guid); - - Pet* pet = _player->GetPet(); - - size_t wpos = data.wpos(); - data << uint8(0); // place holder for slot show number - - data << uint8(GetPlayer()->m_stableSlots); - - uint8 num = 0; // counter for place holder - - // not let move dead pet in slot - if (pet && pet->IsAlive() && pet->getPetType() == HUNTER_PET) + for (PlayerPetData* p : _player->PlayerPetDataStore) { - data << uint32(num); // 4.x unknown, some kind of order? - 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) - ++num; + uint32 petSlot = p->Slot; + uint8 flags = PET_STABLE_ACTIVE; + + if (petSlot > PET_SLOT_LAST_ACTIVE_SLOT) + flags |= PET_STABLE_INACTIVE; + + data << int32(petSlot); + data << uint32(p->PetId); + data << uint32(p->CreatureId); + data << uint32(p->Petlevel); + data << p->Name; + data << uint8(flags); } - if (result) - { - do - { - Field* fields = result->Fetch(); - - data << uint32(num); - 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) - - ++num; - } - while (result->NextRow()); - } - - data.put(wpos, num); // set real data to placeholder SendPacket(&data); - } void WorldSession::SendStableResult(uint8 res) @@ -557,12 +507,32 @@ void WorldSession::SendStableResult(uint8 res) SendPacket(&data); } -void WorldSession::HandleStablePet(WorldPacket& recvData) +void WorldSession::HandleSetPetSlot(WorldPacket& recvData) { TC_LOG_DEBUG("network", "WORLD: Recv CMSG_STABLE_PET"); - ObjectGuid npcGUID; + ObjectGuid guid; + uint32 petId; + uint8 new_slot; - recvData >> npcGUID; + recvData >> petId >> new_slot; + + guid[3] = recvData.ReadBit(); + guid[2] = recvData.ReadBit(); + guid[0] = recvData.ReadBit(); + guid[7] = recvData.ReadBit(); + guid[5] = recvData.ReadBit(); + guid[6] = recvData.ReadBit(); + guid[1] = recvData.ReadBit(); + guid[4] = recvData.ReadBit(); + + recvData.ReadByteSeq(guid[5]); + recvData.ReadByteSeq(guid[3]); + recvData.ReadByteSeq(guid[1]); + recvData.ReadByteSeq(guid[7]); + recvData.ReadByteSeq(guid[4]); + recvData.ReadByteSeq(guid[0]); + recvData.ReadByteSeq(guid[6]); + recvData.ReadByteSeq(guid[2]); if (!GetPlayer()->IsAlive()) { @@ -570,276 +540,63 @@ void WorldSession::HandleStablePet(WorldPacket& recvData) return; } - if (!CheckStableMaster(npcGUID)) + if (!CheckStableMaster(guid)) { SendStableResult(STABLE_ERR_STABLE); return; } - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - Pet* pet = _player->GetPet(); - - // can't place in stable dead pet - if (!pet || !pet->IsAlive() || pet->getPetType() != HUNTER_PET) + if (new_slot > PET_SLOT_LAST_STABLE_SLOT || new_slot < PET_SLOT_FIRST_ACTIVE_SLOT) { SendStableResult(STABLE_ERR_STABLE); return; } + PlayerPetData* playerPetData = _player->GetPlayerPetDataById(petId); + CreatureTemplate const* creatureInfo = nullptr; - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOTS); + if (playerPetData) + creatureInfo = sObjectMgr->GetCreatureTemplate(playerPetData->CreatureId); - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt8(1, PET_SAVE_FIRST_STABLE_SLOT); - stmt->setUInt8(2, PET_SAVE_LAST_STABLE_SLOT); - - _queryProcessor.AddQuery(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleStablePetCallback, this, std::placeholders::_1))); -} - -void WorldSession::HandleStablePetCallback(PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - - uint8 freeSlot = 1; - if (result) - { - do - { - Field* fields = result->Fetch(); - - uint8 slot = fields[1].GetUInt8(); - - // slots ordered in query, and if not equal then free - if (slot != freeSlot) - break; - - // this slot not free, skip - ++freeSlot; - } - while (result->NextRow()); - } - - if (freeSlot > 0 && freeSlot <= GetPlayer()->m_stableSlots) - { - _player->RemovePet(_player->GetPet(), PetSaveMode(freeSlot)); - SendStableResult(STABLE_SUCCESS_STABLE); - } - else - SendStableResult(STABLE_ERR_INVALID_SLOT); -} - -void WorldSession::HandleUnstablePet(WorldPacket& recvData) -{ - TC_LOG_DEBUG("network", "WORLD: Recv CMSG_UNSTABLE_PET."); - ObjectGuid npcGUID; - uint32 petnumber; - - recvData >> npcGUID >> petnumber; - - if (!CheckStableMaster(npcGUID)) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - PreparedStatement* 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.AddQuery(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSession::HandleUnstablePetCallback, this, petnumber, std::placeholders::_1))); -} - -void WorldSession::HandleUnstablePetCallback(uint32 petId, PreparedQueryResult result) -{ - if (!GetPlayer()) - return; - - uint32 petEntry = 0; - if (result) - { - Field* fields = result->Fetch(); - petEntry = fields[0].GetUInt32(); - } - - if (!petEntry) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry); - if (!creatureInfo || !creatureInfo->IsTameable(_player->CanTameExoticPets())) - { - // if problem in exotic pet - if (creatureInfo && creatureInfo->IsTameable(true)) - 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) - _player->RemovePet(pet, PET_SAVE_AS_DELETED); - - Pet* newPet = new Pet(_player, HUNTER_PET); - if (!newPet->LoadPetFromDB(_player, petEntry, petId)) - { - delete newPet; - newPet = NULL; - SendStableResult(STABLE_ERR_STABLE); - return; - } - - SendStableResult(STABLE_SUCCESS_UNSTABLE); -} - -void WorldSession::HandleBuyStableSlot(WorldPacket& recvData) -{ - TC_LOG_DEBUG("network", "WORLD: Recv CMSG_BUY_STABLE_SLOT."); - ObjectGuid npcGUID; - - recvData >> npcGUID; - - if (!CheckStableMaster(npcGUID)) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - if (GetPlayer()->m_stableSlots < MAX_PET_STABLES) - { - /*StableSlotPricesEntry const* SlotPrice = sStableSlotPricesStore.LookupEntry(GetPlayer()->m_stableSlots+1); - if (_player->HasEnoughMoney(SlotPrice->Price)) - { - ++GetPlayer()->m_stableSlots; - _player->ModifyMoney(-int32(SlotPrice->Price)); - SendStableResult(STABLE_SUCCESS_BUY_SLOT); - } - else - SendStableResult(STABLE_ERR_MONEY);*/ - } - else - SendStableResult(STABLE_ERR_STABLE); -} - -void WorldSession::HandleStableRevivePet(WorldPacket &/* recvData */) -{ - TC_LOG_DEBUG("network", "HandleStableRevivePet: Not implemented"); -} - -void WorldSession::HandleStableSwapPet(WorldPacket& recvData) -{ - TC_LOG_DEBUG("network", "WORLD: Recv CMSG_STABLE_SWAP_PET."); - ObjectGuid npcGUID; - uint32 petId; - - recvData >> npcGUID >> petId; - - if (!CheckStableMaster(npcGUID)) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - Pet* pet = _player->GetPet(); - - if (!pet || pet->getPetType() != HUNTER_PET) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - // Find swapped pet slot in stable - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SLOT_BY_ID); - - stmt->setUInt32(0, _player->GetGUID().GetCounter()); - stmt->setUInt32(1, petId); - - _queryProcessor.AddQuery(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) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - Field* fields = result->Fetch(); - - uint32 slot = fields[0].GetUInt8(); - uint32 petEntry = fields[1].GetUInt32(); - - if (!petEntry) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry); if (!creatureInfo || !creatureInfo->IsTameable(true)) { SendStableResult(STABLE_ERR_STABLE); return; } - if (!creatureInfo->IsTameable(_player->CanTameExoticPets())) + if (!creatureInfo->IsTameable(_player->CanTameExoticPets()) && new_slot <= PET_SLOT_LAST_ACTIVE_SLOT) { SendStableResult(STABLE_ERR_EXOTIC); return; } + // remove fake death 2 + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + Pet* pet = _player->GetPet(); - // The player's pet could have been removed during the delay of the DB callback - if (!pet) + + // can't place in stable dead pet + if (pet) { - SendStableResult(STABLE_ERR_STABLE); - return; + if (pet->GetCharmInfo()->GetPetNumber() == petId) + { + if (!pet->IsAlive() || !pet->IsHunterPet()) + { + SendStableResult(STABLE_ERR_STABLE); + return; + } + } } - // move alive pet to slot or delete dead pet - _player->RemovePet(pet, pet->IsAlive() ? PetSaveMode(slot) : PET_SAVE_AS_DELETED); - - // summon unstabled pet - Pet* newPet = new Pet(_player); - if (!newPet->LoadPetFromDB(_player, petEntry, petId)) + if (playerPetData) { - delete newPet; - SendStableResult(STABLE_ERR_STABLE); + UpdatePetSlot(petId, playerPetData->Slot, new_slot); } - else - SendStableResult(STABLE_SUCCESS_UNSTABLE); +} + +void WorldSession::HandleStableRevivePet(WorldPacket &/* recvData */) +{ + TC_LOG_DEBUG("network", "HandleStableRevivePet: Not implemented"); } void WorldSession::HandleRepairItemOpcode(WorldPacket& recvData) diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index 3592ec07e4..c459ba9a3e 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -259,8 +259,10 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe ASSERT(pet->GetTypeId() == TYPEID_UNIT); if (pet->IsPet()) { - if (((Pet*)pet)->getPetType() == HUNTER_PET) + if (((Pet*)pet)->IsHunterPet()) + { GetPlayer()->RemovePet((Pet*)pet, PET_SAVE_AS_DELETED); + } else //dismissing a summoned pet is like killing them (this prevents returning a soulshard...) pet->setDeathState(CORPSE); @@ -279,6 +281,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetIsCommandAttack(false); charmInfo->SetIsAtStay(true); + charmInfo->SetIsCommandFollow(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsReturning(false); charmInfo->SaveStayPosition(); @@ -296,6 +299,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe // no break; case REACT_DEFENSIVE: //recovery case REACT_AGGRESSIVE: //activete + case REACT_ASSIST: if (pet->GetTypeId() == TYPEID_UNIT) pet->ToCreature()->SetReactState(ReactStates(spellid)); break; @@ -621,7 +625,7 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) Pet* pet = ObjectAccessor::GetPet(*_player, petguid); // check it! - if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET || + if (!pet || !pet->IsPet() || !((Pet*)pet)->IsHunterPet() || !pet->HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) || pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo()) return; @@ -887,3 +891,88 @@ void WorldSession::HandleLearnPreviewTalentsPet(WorldPacket& recvData) recvData.rfinish(); } + +void WorldSession::UpdatePetSlot(uint32 petNumberA, uint8 oldPetSlot, uint8 newPetSlot) +{ + uint32 petNumberB = 0; + Pet* pet = _player->GetPet(); + + // first check check new PetSlot if another pet already exists there + PlayerPetData* playerPetDataA = _player->GetPlayerPetDataById(petNumberA); + PlayerPetData* playerPetDataB = _player->GetPlayerPetDataBySlot(newPetSlot); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + + // If Slot is already in use + if (playerPetDataB) + { + petNumberB = playerPetDataB->PetId; + + // Check if current pet is the pet to swap + if (pet && pet->GetCharmInfo()->GetPetNumber() == petNumberB) + { + pet->SetSlot(oldPetSlot); + _player->RemovePet(pet, PET_SAVE_DISMISS); + } + else + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT); + stmt->setUInt32(0, oldPetSlot); + stmt->setUInt64(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, newPetSlot); + trans->Append(stmt); + + } + + playerPetDataB->Slot = oldPetSlot; + } + + // Check if current pet is the pet to swap + if (pet && pet->GetCharmInfo()->GetPetNumber() == petNumberA) + { + pet->SetSlot(newPetSlot); + _player->RemovePet(pet, PET_SAVE_DISMISS); + } + else + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); + stmt->setUInt32(0, newPetSlot); + stmt->setUInt64(1, _player->GetGUID().GetCounter()); + stmt->setUInt32(2, petNumberA); + trans->Append(stmt); + } + + playerPetDataA->Slot = newPetSlot; + + CharacterDatabase.CommitTransaction(trans); + + SendPetSlotUpdated(petNumberA, newPetSlot, petNumberB, oldPetSlot); + SendStableResult(STABLE_SUCCESS_STABLE); +} + +void WorldSession::SendPetSlotUpdated(int32 petNumberA, int32 petSlotA, int32 petNumberB, int32 petSlotB) +{ + WorldPacket data(SMSG_PET_SLOT_UPDATED, 4 + 4 + 4 + 4); + + data << uint32(petNumberA); + data << uint32(petSlotA); + data << uint32(petNumberB); + data << uint32(petSlotB); + + SendPacket(&data); +} + +void WorldSession::SendPetAdded(int32 petSlot, int32 petNumber, int32 creatureID, int32 displayID, int32 level, std::string name) +{ + WorldPacket data(SMSG_PET_ADDED, 4 + 4 + 1 + 4 + 4 + name.size()); + + data << uint32(level); + data << uint32(petSlot); + data << uint8(PET_STABLE_ACTIVE | (petSlot > PET_SLOT_LAST_ACTIVE_SLOT ? PET_STABLE_INACTIVE : 0)); + data << uint32(creatureID); + data << uint32(petNumber); + data.WriteBits(uint32(name.length()), 8); + data << name; + + SendPacket(&data); +} diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 22a7bc098d..51a8de322f 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -1201,7 +1201,7 @@ void Map::MoveAllCreaturesInMoveList() /// @todo pets will disappear if this is outside CreatureRespawnRelocation //need to check why pet is frequently relocated to an unloaded cell if (c->IsPet()) - ((Pet*)c)->Remove(PET_SAVE_NOT_IN_SLOT, true); + ((Pet*)c)->Remove(PET_SAVE_DISMISS, true); else AddObjectToRemoveList(c); } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 22a0880c29..705220e14a 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -490,7 +490,7 @@ void OpcodeTable::Initialize() DEFINE_OPCODE_HANDLER(CMSG_SET_FACTION_ATWAR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetFactionAtWar ); DEFINE_OPCODE_HANDLER(CMSG_SET_FACTION_INACTIVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetFactionInactiveOpcode ); DEFINE_OPCODE_HANDLER(CMSG_SET_GUILD_BANK_TEXT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetGuildBankTabText ); - DEFINE_OPCODE_HANDLER(CMSG_SET_PET_SLOT, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + DEFINE_OPCODE_HANDLER(CMSG_SET_PET_SLOT, STATUS_LOGGEDIN , PROCESS_THREADUNSAFE, &WorldSession::HandleSetPetSlot ); DEFINE_OPCODE_HANDLER(CMSG_SET_PLAYER_DECLINED_NAMES, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleSetPlayerDeclinedNames ); DEFINE_OPCODE_HANDLER(CMSG_SET_PREFERED_CEMETERY, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); DEFINE_OPCODE_HANDLER(CMSG_SET_PRIMARY_TALENT_TREE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); @@ -1056,7 +1056,7 @@ void OpcodeTable::Initialize() DEFINE_OPCODE_HANDLER(SMSG_PETITION_SIGN_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_ACTION_FEEDBACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_ACTION_SOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - DEFINE_OPCODE_HANDLER(SMSG_PET_ADDED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + DEFINE_OPCODE_HANDLER(SMSG_PET_ADDED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_BROKEN, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_CAST_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_DISMISS_SOUND, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); @@ -1067,7 +1067,7 @@ void OpcodeTable::Initialize() DEFINE_OPCODE_HANDLER(SMSG_PET_NAME_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_REMOVED_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_RENAMEABLE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - DEFINE_OPCODE_HANDLER(SMSG_PET_SLOT_UPDATED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + DEFINE_OPCODE_HANDLER(SMSG_PET_SLOT_UPDATED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_SPELLS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_TAME_FAILURE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); DEFINE_OPCODE_HANDLER(SMSG_PET_UPDATE_COMBO_POINTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 384ea3ee40..40e1da137f 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -536,7 +536,7 @@ void WorldSession::LogoutPlayer(bool save) guild->HandleMemberLogout(this); ///- Remove pet - _player->RemovePet(NULL, PET_SAVE_AS_CURRENT, true); + _player->RemovePet(nullptr, PET_SAVE_LOGOUT, true); ///- Clear whisper whitelist _player->ClearWhisperWhiteList(); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index f7b34b5e24..d468b69136 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -383,9 +383,11 @@ class TC_GAME_API WorldSession // Pet void SendPetNameQuery(ObjectGuid guid, uint32 petnumber); void SendStablePet(ObjectGuid guid); - void SendStablePetCallback(ObjectGuid guid, PreparedQueryResult result); void SendStableResult(uint8 guid); bool CheckStableMaster(ObjectGuid guid); + void UpdatePetSlot(uint32 petNumber, uint8 oldPetSlot, uint8 newPetSlot); + void SendPetSlotUpdated(int32 petNumberA, int32 petSlotA, int32 petNumberB, int32 petSlotB); + void SendPetAdded(int32 petSlot, int32 petNumber, int32 creatureID, int32 displayID, int32 level, std::string name); // Account Data AccountData* GetAccountData(AccountDataType type) { return &m_accountData[type]; } @@ -719,14 +721,8 @@ class TC_GAME_API WorldSession void HandleNpcTextQueryOpcode(WorldPacket& recvPacket); 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 HandleSetPetSlot(WorldPacket& recvPacket); void HandleStableRevivePet(WorldPacket& recvPacket); - void HandleStableSwapPet(WorldPacket& recvPacket); - void HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result); void SendTrainerBuyFailed(uint64 guid, uint32 spellId, uint32 reason); void HandleDuelAcceptedOpcode(WorldPacket& recvPacket); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index e43da4160f..42bb1c58c6 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2676,10 +2676,7 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/) if (!pet) // in very specific state like near world end/etc. return; - // "kill" original creature - creatureTarget->DespawnOrUnsummon(); - - uint8 level = (creatureTarget->getLevel() < (m_caster->getLevel() - 5)) ? (m_caster->getLevel() - 5) : creatureTarget->getLevel(); + uint8 level = m_caster->getLevel(); // prepare visual effect for levelup pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1); @@ -2687,9 +2684,6 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/) // add to world pet->GetMap()->AddToMap(pet->ToCreature()); - // visual effect for levelup - pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); - // caster have pet now m_caster->SetMinion(pet, true); @@ -2697,9 +2691,23 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/) if (m_caster->GetTypeId() == TYPEID_PLAYER) { - pet->SavePetToDB(PET_SAVE_AS_CURRENT); - m_caster->ToPlayer()->PetSpellInitialize(); + pet->SavePetToDB(PET_SAVE_NEW_PET); + if (uint8 slot = pet->GetSlot() <= PET_SLOT_LAST_ACTIVE_SLOT) + { + m_caster->ToPlayer()->GetSession()->SendPetAdded(pet->GetSlot(), pet->GetCharmInfo()->GetPetNumber(), creatureTarget->GetEntry(), creatureTarget->GetDisplayId(), creatureTarget->getLevel(), pet->GetName()); + m_caster->ToPlayer()->PetSpellInitialize(); + } + else + { + pet->Remove(PET_SAVE_AS_DELETED); + return; + } } + // "kill" original creature if everything worked + creatureTarget->DespawnOrUnsummon(); + + // visual effect for levelup + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); } void Spell::EffectSummonPet(SpellEffIndex effIndex) @@ -2707,15 +2715,18 @@ void Spell::EffectSummonPet(SpellEffIndex effIndex) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) return; - Player* owner = NULL; + Player* owner = nullptr; if (m_originalCaster) { owner = m_originalCaster->ToPlayer(); if (!owner && m_originalCaster->IsTotem()) owner = m_originalCaster->GetCharmerOrOwnerPlayerOrPlayerItself(); } + // SUMMON_PET SummonPet's entries are at MiscValue, HunterPetSlot at BasePoints + uint32 petentry = (m_spellInfo->Effects[effIndex].MiscValue == 0 && m_spellInfo->Effects[effIndex].BasePoints <= PET_SLOT_LAST_ACTIVE_SLOT) ? m_spellInfo->Effects[effIndex].BasePoints : m_spellInfo->Effects[effIndex].MiscValue; + + PetType petType = (m_spellInfo->Effects[effIndex].MiscValue == 0 && m_spellInfo->Effects[effIndex].BasePoints <= PET_SLOT_LAST_ACTIVE_SLOT) ? HUNTER_PET : SUMMON_PET; - uint32 petentry = m_spellInfo->Effects[effIndex].MiscValue; if (!owner) { @@ -2761,14 +2772,14 @@ 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, (OldSummon->getPetType() == HUNTER_PET ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS), false); else return; } float x, y, z; owner->GetClosePoint(x, y, z, owner->GetObjectSize()); - Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), SUMMON_PET, 0); + Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), petType, 0); if (!pet) return; @@ -2782,11 +2793,6 @@ void Spell::EffectSummonPet(SpellEffIndex effIndex) pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); - // generate new name for summon pet - std::string new_name=sObjectMgr->GeneratePetName(petentry); - if (!new_name.empty()) - pet->SetName(new_name); - ExecuteLogEffectSummonObject(effIndex, pet); } @@ -2812,7 +2818,7 @@ void Spell::EffectLearnPetSpell(SpellEffIndex effIndex) return; pet->learnSpell(learn_spellproto->Id); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_CURRENT_STATE); pet->GetOwner()->PetSpellInitialize(); } @@ -4097,7 +4103,7 @@ void Spell::EffectDismissPet(SpellEffIndex effIndex) Pet* pet = unitTarget->ToPet(); ExecuteLogEffectUnsummonObject(effIndex, pet); - pet->GetOwner()->RemovePet(pet, PET_SAVE_NOT_IN_SLOT); + pet->GetOwner()->RemovePet(pet, PET_SAVE_DISMISS); } void Spell::EffectSummonObject(SpellEffIndex effIndex) @@ -4661,7 +4667,7 @@ void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) if (!player->GetPet()) { // Position passed to SummonPet is irrelevant with current implementation, - // pet will be relocated without using these coords in Pet::LoadPetFromDB + // pet will be relocated without using these coords in Pet::LoadPetData player->SummonPet(0, 0.0f, 0.0f, 0.0f, 0.0f, SUMMON_PET, 0); hadPet = false; } @@ -4702,7 +4708,7 @@ void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) ci->SetIsReturning(false); } - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_CURRENT_STATE); } void Spell::EffectDestroyAllTotems(SpellEffIndex /*effIndex*/) @@ -5284,7 +5290,7 @@ void Spell::EffectCreateTamedPet(SpellEffIndex effIndex) if (unitTarget->GetTypeId() == TYPEID_PLAYER) { - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_NEW_PET); unitTarget->ToPlayer()->PetSpellInitialize(); } } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index febfea8855..e767460529 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -1452,7 +1452,7 @@ public: // caster have pet now player->SetMinion(pet, true); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_NEW_PET); player->PetSpellInitialize(); return true; diff --git a/src/server/scripts/Commands/cs_pet.cpp b/src/server/scripts/Commands/cs_pet.cpp index d893ce6338..339cc5a457 100644 --- a/src/server/scripts/Commands/cs_pet.cpp +++ b/src/server/scripts/Commands/cs_pet.cpp @@ -122,7 +122,7 @@ public: pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->getLevel()); player->SetMinion(pet, true); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_NEW_PET); player->PetSpellInitialize(); return true; diff --git a/src/server/scripts/Kalimdor/LostCityOfTheTolvir/boss_siamat.cpp b/src/server/scripts/Kalimdor/LostCityOfTheTolvir/boss_siamat.cpp index 6cdfd8787e..d40099d950 100644 --- a/src/server/scripts/Kalimdor/LostCityOfTheTolvir/boss_siamat.cpp +++ b/src/server/scripts/Kalimdor/LostCityOfTheTolvir/boss_siamat.cpp @@ -555,6 +555,11 @@ class spell_siamat_wailing_winds : public SpellScriptLoader { PrepareAuraScript(spell_siamat_wailing_winds_AuraScript); + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WAILING_WINDS_DAMAGE }); + } + void OnPeriodic(AuraEffect const* /*aurEff*/) { if (Unit* caster = GetCaster()) diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 6872c00b39..2f643088eb 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -2580,7 +2580,7 @@ class spell_gen_pet_summoned : public SpellScriptLoader { PetType newPetType = (player->getClass() == CLASS_HUNTER) ? HUNTER_PET : SUMMON_PET; Pet* newPet = new Pet(player, newPetType); - if (newPet->LoadPetFromDB(player, 0, player->GetLastPetNumber(), true)) + if (newPet->LoadPetData(player, 0, player->GetLastPetNumber(), true)) { // revive the pet if it is dead if (newPet->getDeathState() == DEAD) @@ -3105,7 +3105,7 @@ class spell_gen_summon_elemental : public SpellScriptLoader if (GetCaster()) if (Unit* owner = GetCaster()->GetOwner()) if (owner->GetTypeId() == TYPEID_PLAYER) /// @todo this check is maybe wrong - owner->ToPlayer()->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); + owner->ToPlayer()->RemovePet(nullptr, PET_SAVE_DISMISS, true); } void Register() override @@ -3693,10 +3693,10 @@ class spell_gen_gm_freeze : public SpellScriptLoader { if (Pet* pet = player->GetPet()) { - pet->SavePetToDB(PET_SAVE_AS_CURRENT); + pet->SavePetToDB(PET_SAVE_CURRENT_STATE); // not let dismiss dead pet if (pet->IsAlive()) - player->RemovePet(pet, PET_SAVE_NOT_IN_SLOT); + player->RemovePet(pet, PET_SAVE_DISMISS); } } } diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index e0fc96e487..d8728fb00b 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -1129,6 +1129,9 @@ class spell_hun_tame_beast : public SpellScriptLoader if (!GetExplTargetUnit()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + if (!caster->ToPlayer()->GetFirstUnusedActivePetSlot()) + return SPELL_FAILED_DONT_REPORT; + if (Creature* target = GetExplTargetUnit()->ToCreature()) { if (target->getLevel() > caster->getLevel()) diff --git a/src/server/scripts/Spells/spell_pet.cpp b/src/server/scripts/Spells/spell_pet.cpp index a7b515fd79..77c0e1466a 100644 --- a/src/server/scripts/Spells/spell_pet.cpp +++ b/src/server/scripts/Spells/spell_pet.cpp @@ -51,11 +51,6 @@ enum WarlockPetCalculate SPELL_WARLOCK_PET_SCALING_03 = 34957, SPELL_WARLOCK_PET_SCALING_04 = 34958, SPELL_WARLOCK_PET_SCALING_05 = 61013, - ENTRY_FELGUARD = 17252, - ENTRY_VOIDWALKER = 1860, - ENTRY_FELHUNTER = 417, - ENTRY_SUCCUBUS = 1863, - ENTRY_IMP = 416, SPELL_WARLOCK_GLYPH_OF_VOIDWALKER = 56247, }; diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 8ef373bbd7..88195ccd75 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -2565,6 +2565,8 @@ class npc_stable_master : public CreatureScript bool GossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override { + player->GetSession()->SendStablePet(me->GetGUID()); + SmartAI::GossipSelect(player, menuId, gossipListId); if (menuId != STABLE_MASTER_GOSSIP_SUB_MENU) return true;