Core/Pets: merged funjoker's new Pet Slot system
* fixed packets and handling for stable master * fixed Control Pet / Demon handling * corrected wrong trainer spell for the Control spells * Added base support for Assist behaivior * corrected action bar layout * Hunter Pets will now have the same level as their tamer (patch 4.1 change) * corrected health stats for pets
This commit is contained in:
1 parent
b2945a5c58
commit
b7c15093a6
32 files changed
+803
-685
No files matched your search
@@ -0,0 +1 @@
|
||||
ALTER TABLE `character_pet` ADD COLUMN `active` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `renamed`;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Pet>(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
// current/stable/not_in_slot
|
||||
if (mode >= PET_SAVE_AS_CURRENT)
|
||||
if (mode == PET_SAVE_NEW_PET)
|
||||
{
|
||||
Optional<uint8> 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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<uint8> Player::GetFirstUnusedActivePetSlot()
|
||||
{
|
||||
std::set<uint8> 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<uint8>{};
|
||||
}
|
||||
|
||||
Optional<uint8> Player::GetFirstUnusedPetSlot()
|
||||
{
|
||||
std::set<uint8> 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<uint8>{};
|
||||
}
|
||||
|
||||
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>(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<HighGuid::Pet>(), 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<HighGuid::Pet>(), 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Player>
|
||||
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<Player>
|
||||
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<Player>
|
||||
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<uint8> GetFirstUnusedActivePetSlot();
|
||||
Optional<uint8> 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<Player>
|
||||
Archaeology _archaeology;
|
||||
|
||||
uint8 _tmpLfgRolesCheck;
|
||||
|
||||
std::vector<PlayerPetData*> PlayerPetDataStore;
|
||||
};
|
||||
|
||||
TC_GAME_API void AddItemsSetItem(Player* player, Item* item);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<uint8>(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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user