Files
Viano-Core/src/server/game/Entities/Pet/Pet.cpp
T
Ovahlord d30c811d9e Core/Creatures: reworked tempoary summon creation
* all stats and values will now be set before summons are getting added to map
* cleaned up summon spell effect handler to reduce duplicade code
* summoner guids will no longer be set for every minion and correctly handle assigning creator guids instead. These values will serve as fallback guid if there is no summoner guid
* friendly tempsummons will now inherit the guild data of their player and pass them on to further allied summons. This fixes guild specific visuals for creatures such as Guild Battle Standards and Guild Heralds.

Todo: check for regressions
2020-04-22 00:20:18 +02:00

2015 lines
68 KiB
C++

/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Common.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "WorldPacket.h"
#include "ObjectMgr.h"
#include "PhasingHandler.h"
#include "SpellMgr.h"
#include "Pet.h"
#include "Formulas.h"
#include "SpellHistory.h"
#include "SpellAuras.h"
#include "SpellAuraEffects.h"
#include "Transport.h"
#include "Unit.h"
#include "Util.h"
#include "Group.h"
#include "Opcodes.h"
#include "WorldSession.h"
#define PET_XP_FACTOR 0.05f
Pet::Pet(Player* owner, PetType type) :
Guardian(nullptr, owner, true), m_usedTalentCount(0), m_removed(false),
m_petType(type), m_duration(0), m_auraRaidUpdateMask(0), m_loading(false),
m_declinedname(nullptr)
{
ASSERT(GetOwner());
m_unitTypeMask |= UNIT_MASK_PET;
if (type == HUNTER_PET)
m_unitTypeMask |= UNIT_MASK_HUNTER_PET;
if (!(m_unitTypeMask & UNIT_MASK_CONTROLABLE_GUARDIAN))
{
m_unitTypeMask |= UNIT_MASK_CONTROLABLE_GUARDIAN;
InitCharmInfo();
}
m_name = "Pet";
m_petFocusRegenTimer = PetFocusRegenInterval;
}
Pet::~Pet()
{
delete m_declinedname;
}
void Pet::AddToWorld()
{
///- Register the pet for guid lookup
if (!IsInWorld())
{
///- Register the pet for guid lookup
GetMap()->GetObjectsStore().Insert<Pet>(GetGUID(), this);
Unit::AddToWorld();
AIM_Initialize();
}
// Prevent stuck pets when zoning. Pets default to "follow" when added to world
// so we'll reset flags and let the AI handle things
if (GetCharmInfo() && GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
{
GetCharmInfo()->SetIsCommandAttack(false);
GetCharmInfo()->SetIsCommandFollow(false);
GetCharmInfo()->SetIsAtStay(false);
GetCharmInfo()->SetIsFollowing(false);
GetCharmInfo()->SetIsReturning(false);
}
}
void Pet::RemoveFromWorld()
{
///- Remove the pet from the accessor
if (IsInWorld())
{
///- Don't call the function for Creature, normal mobs + totems go in a different storage
Unit::RemoveFromWorld();
GetMap()->GetObjectsStore().Remove<Pet>(GetGUID());
}
}
bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool current)
{
m_loading = true;
PlayerPetData* playerPetData;
if (petnumber)
{
playerPetData = owner->GetPlayerPetDataById(petnumber);
}
else if ((getPetType() == SUMMON_PET) && petEntry)
{
playerPetData = owner->GetPlayerPetDataByCreatureId(petEntry);
}
else if (getPetType() == HUNTER_PET)
{
playerPetData = owner->GetPlayerPetDataBySlot(petEntry);
}
else
{
playerPetData = owner->GetPlayerPetDataCurrent();
}
if (!playerPetData)
{
m_loading = false;
return false;
}
petEntry = playerPetData->CreatureId;
if (!petEntry)
return false;
uint32 summonSpellId = playerPetData->SummonSpellId;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summonSpellId);
bool isTemporarySummon = spellInfo && spellInfo->GetDuration() > 0;
if (current && isTemporarySummon)
return false;
PetType petType = playerPetData->Type;
if (petType == HUNTER_PET)
{
CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petEntry);
if (!creatureInfo)
{
owner->GetSession()->SendStableResult(STABLE_ERR_STABLE);
return false;
}
if (!creatureInfo->IsTameable(owner->CanTameExoticPets()))
{
owner->GetSession()->SendStableResult(STABLE_ERR_EXOTIC);
return false;
}
}
uint32 petId = playerPetData->PetId;
if (current && owner->IsPetNeedBeTemporaryUnsummoned())
{
owner->SetTemporaryUnsummonedPetNumber(petId);
return false;
}
Map* map = owner->GetMap();
ObjectGuid::LowType guid = map->GenerateLowGuid<HighGuid::Pet>();
if (!Create(guid, map, petEntry, petId))
return false;
PhasingHandler::InheritPhaseShift(this, owner);
setPetType(petType);
SetFaction(owner->GetFaction());
SetUInt32Value(UNIT_CREATED_BY_SPELL, summonSpellId);
if (IsCritter())
{
float px, py, pz;
owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, GetFollowAngle());
Relocate(px, py, pz, owner->GetOrientation());
if (!IsPositionValid())
{
TC_LOG_ERROR("entities.pet", "Pet (guidlow %d, entry %d) not loaded. Suggested coordinates isn't valid (X: %f Y: %f)",
GetGUID().GetCounter(), GetEntry(), GetPositionX(), GetPositionY());
return false;
}
Transport* transport = GetTransGUID().IsEmpty() ? owner->GetTransport() : nullptr;
if (transport)
{
float x, y, z, o;
GetPosition(x, y, z, o);
transport->CalculatePassengerOffset(x, y, z, &o);
m_movementInfo.transport.pos.Relocate(x, y, z, o);
transport->AddPassenger(this);
}
map->AddToMap(this->ToCreature());
return true;
}
m_charmInfo->SetPetNumber(petId, IsPermanentPetFor(owner));
SetDisplayId(playerPetData->DisplayId);
SetNativeDisplayId(playerPetData->DisplayId);
uint32 petlevel = playerPetData->Petlevel;
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
SetName(playerPetData->Name);
switch (getPetType())
{
case SUMMON_PET:
petlevel = owner->getLevel();
SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(CLASS_MAGE));
SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE);
break;
case HUNTER_PET:
SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_WARRIOR);
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, playerPetData->Renamed ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE);
break;
default:
if (!IsPetGhoul())
TC_LOG_ERROR("entities.pet", "Pet have incorrect type (%u) for pet loading.", getPetType());
break;
}
SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime())); // cast can't be helped here
SetCreatorGUID(owner->GetGUID());
InitStatsForLevel(petlevel);
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, playerPetData->PetExp);
SynchronizeLevelWithOwner();
// Set pet's position after setting level, its size depends on it
float px, py, pz;
owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
Relocate(px, py, pz, owner->GetOrientation());
if (!IsPositionValid())
{
TC_LOG_ERROR("entities.pet", "Pet (guidlow %d, entry %d) not loaded. Suggested coordinates isn't valid (X: %f Y: %f)",
GetGUID().GetCounter(), GetEntry(), GetPositionX(), GetPositionY());
return false;
}
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 = playerPetData->SavedHealth;
uint32 savedmana = playerPetData->SavedMana;
if (!savedhealth && getPetType() == HUNTER_PET)
setDeathState(JUST_DIED);
else
{
SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth);
SetPower(POWER_MANA, savedmana > uint32(GetMaxPower(POWER_MANA)) ? GetMaxPower(POWER_MANA) : savedmana);
}
}
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
/// @todo pets should be summoned from real cast instead of just faking it?
if (summonSpellId)
{
WorldPacket data(SMSG_SPELL_GO, (8+8+4+4+2));
data << owner->GetPackGUID();
data << owner->GetPackGUID();
data << uint8(0);
data << uint32(summonSpellId);
data << uint32(256); // CAST_FLAG_UNKNOWN3
data << uint32(0);
data << uint32(getMSTime());
owner->SendMessageToSet(&data, true);
}
Transport* transport = GetTransGUID().IsEmpty() ? owner->GetTransport() : nullptr;
if (transport)
{
float x, y, z, o;
GetPosition(x, y, z, o);
transport->CalculatePassengerOffset(x, y, z, &o);
m_movementInfo.transport.pos.Relocate(x, y, z, o);
transport->AddPassenger(this);
}
owner->SetMinion(this, true);
map->AddToMap(ToCreature());
InitTalentForLevel(); // set original talents points before spell loading
uint32 timediff = uint32(GameTime::GetGameTime() - playerPetData->Timediff);
_LoadAuras(timediff);
// load action bar, if data broken will fill later by default spells.
if (!isTemporarySummon)
{
m_charmInfo->LoadPetActionBar(playerPetData->Actionbar);
_LoadSpells();
InitTalentForLevel(); // re-init to check talent count
_LoadSpellCooldowns();
LearnPetPassives();
InitLevelupSpellsForLevel();
if (map->IsBattleArena())
RemoveArenaAuras();
CastPetAuras(current);
CastPetScalingAuras();
}
CleanupActionBar(); // remove unknown spells from action bar after load
UpdateAllStats();
SetFullHealth(); // Set full health and mana after pet scaling auras has been applied
if (IsHunterPet())
CastSpell(this, SPELL_PET_ENERGIZE, true);
else
SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
if (IsPetGhoul())
{
CastSpell(this, SPELL_PET_RISEN_GHOUL_SPAWN_IN, true);
CastSpell(this, SPELL_PET_RISEN_GHOUL_SELF_STUN, true);
}
TC_LOG_DEBUG("entities.pet", "New Pet has guid %u", GetGUID().GetCounter());
owner->PetSpellInitialize();
if (owner->GetGroup())
owner->SetGroupUpdateFlag(GROUP_UPDATE_PET);
owner->SendTalentsInfoData(true);
if (getPetType() == HUNTER_PET)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME);
stmt->setUInt32(0, owner->GetGUID().GetCounter());
stmt->setUInt32(1, GetCharmInfo()->GetPetNumber());
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (result)
{
delete m_declinedname;
m_declinedname = new DeclinedName;
Field* fields2 = result->Fetch();
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
{
m_declinedname->name[i] = fields2[i].GetString();
}
}
}
//set last used pet number (for use in BG's)
if (owner->GetTypeId() == TYPEID_PLAYER && isControlled() && !isTemporarySummoned() && (getPetType() == SUMMON_PET || getPetType() == HUNTER_PET))
owner->ToPlayer()->SetLastPetNumber(petId);
// must be after SetMinion (owner guid check)
LoadTemplateImmunities();
m_loading = false;
return true;
}
void Pet::SavePetToDB(PetSaveMode mode)
{
if (!GetEntry())
return;
// save only fully controlled creature
if (!isControlled())
return;
// not save not player pets
if (!GetOwnerOrCreatorGUID().IsPlayer())
return;
uint32 curhealth = GetHealth();
uint32 curmana = GetPower(POWER_MANA);
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
// save auras before possibly removing them
_SaveAuras(trans);
_SaveSpells(trans);
GetSpellHistory()->SaveToDB<Pet>(trans);
CharacterDatabase.CommitTransaction(trans);
PlayerPetData* playerPetData = GetOwner()->GetPlayerPetDataById(m_charmInfo->GetPetNumber());
// save as new if no data for Pet in PlayerPetDataStore
if (mode < PET_SAVE_NEW_PET && !playerPetData)
mode = PET_SAVE_NEW_PET;
if (mode == PET_SAVE_NEW_PET)
{
Optional<uint8> slot = IsHunterPet() ? GetOwner()->GetFirstUnusedActivePetSlot() : GetOwner()->GetFirstUnusedPetSlot();
if (slot)
{
SetSlot(*slot);
playerPetData = new PlayerPetData();
}
else
mode = PET_SAVE_AS_DELETED;
}
if (mode == PET_SAVE_DISMISS || mode == PET_SAVE_LOGOUT)
RemoveAllAuras();
// whole pet is saved to DB
if (mode >= PET_SAVE_CURRENT_STATE)
{
ObjectGuid::LowType ownerLowGUID = GetOwnerOrCreatorGUID().GetCounter();
std::string name = m_name;
CharacterDatabase.EscapeString(name);
trans = CharacterDatabase.BeginTransaction();
// remove current data
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
trans->Append(stmt);
uint32 petId = m_charmInfo->GetPetNumber();
// save pet
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET);
stmt->setUInt32(0, petId);
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, mode == PET_SAVE_DISMISS ? 0 : 1);
stmt->setUInt32(11, curhealth);
stmt->setUInt32(12, curmana);
stmt->setString(13, GenerateActionBarData());
stmt->setUInt32(14, GameTime::GetGameTime()); // unsure about this
stmt->setUInt32(15, GetUInt32Value(UNIT_CREATED_BY_SPELL));
stmt->setUInt8(16, getPetType());
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
if (m_petSlot > PET_SLOT_LAST)
TC_LOG_ERROR("sql.sql", "Pet::SavePetToDB: 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 = mode == PET_SAVE_DISMISS ? 0 : 1;
playerPetData->SavedHealth = curhealth;
playerPetData->SavedMana = curmana;
playerPetData->Actionbar = GenerateActionBarData();
playerPetData->Timediff = GameTime::GetGameTime();
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);
}
}
void Pet::DeleteFromDB(ObjectGuid::LowType guidlow)
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID);
stmt->setUInt32(0, guidlow);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME);
stmt->setUInt32(0, guidlow);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURAS);
stmt->setUInt32(0, guidlow);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELLS);
stmt->setUInt32(0, guidlow);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS);
stmt->setUInt32(0, guidlow);
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
}
void Pet::setDeathState(DeathState s) // overwrite virtual Creature::setDeathState and Unit::setDeathState
{
Creature::setDeathState(s);
if (getDeathState() == CORPSE)
{
if (getPetType() == HUNTER_PET)
{
// pet corpse non lootable and non skinnable
SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE);
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
//SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED);
}
}
else if (getDeathState() == ALIVE)
{
//RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED);
CastPetAuras(true);
}
}
void Pet::Update(uint32 diff)
{
if (m_removed) // pet already removed, just wait in remove queue, no updates
return;
if (m_loading)
return;
switch (m_deathState)
{
case CORPSE:
{
if (!IsHunterPet() || m_corpseRemoveTime <= GameTime::GetGameTime())
{
Remove(PET_SAVE_DISMISS); //hunters' pets never get removed because of death, NEVER!
return;
}
break;
}
case ALIVE:
{
// unsummon pet that lost owner
Player* owner = GetOwner();
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_DISMISS, true);
return;
}
if (isControlled())
{
if (owner->GetPetGUID() != GetGUID())
{
TC_LOG_ERROR("entities.pet", "Pet %u is not pet of owner %s, removed", GetEntry(), GetOwner()->GetName().c_str());
Remove(IsHunterPet() ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS);
return;
}
}
if (m_duration > 0)
{
if (uint32(m_duration) > diff)
m_duration -= diff;
else
{
Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS);
return;
}
}
// Regenerate Focus
m_petFocusRegenTimer -= diff;
if (m_petFocusRegenTimer <= 0)
{
if (getPowerType() == POWER_FOCUS)
Regenerate(POWER_FOCUS);
m_petFocusRegenTimer += PetFocusRegenInterval;
}
break;
}
default:
break;
}
Creature::Update(diff);
}
void Creature::Regenerate(Powers power)
{
uint32 curValue = GetPower(power);
uint32 maxValue = GetMaxPower(power);
if (curValue >= maxValue)
return;
float addvalue = 0.0f;
switch (power)
{
case POWER_FOCUS:
addvalue = 5 * sWorld->getRate(RATE_POWER_FOCUS);
break;
case POWER_ENERGY:
addvalue = 20;
// For Death Knight Ghouls
if (GetEntry() == 26125)
if (Unit* owner = GetOwner())
AddPct(addvalue, (1.0f / owner->ToPlayer()->GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN) - 1.0f) * 100.0f);
break;
default:
return;
}
// Apply modifiers (if any).
AddPct(addvalue, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, power));
addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * (IsHunterPet() ? PetFocusRegenInterval : REGENERATION_INTERVAL) / (5 * IN_MILLISECONDS);
ModifyPower(power, int32(addvalue));
}
void Pet::Remove(PetSaveMode mode, bool returnreagent)
{
GetOwner()->RemovePet(this, mode, returnreagent);
}
void Pet::GivePetXP(uint32 xp)
{
if (!IsHunterPet())
return;
if (xp < 1)
return;
if (!IsAlive())
return;
uint8 maxlevel = std::min((uint8)sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL), GetOwner()->getLevel());
uint8 petlevel = getLevel();
// If pet is detected to be at, or above(?) the players level, don't hand out XP
if (petlevel >= maxlevel)
return;
uint32 nextLvlXP = GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP);
uint32 curXP = GetUInt32Value(UNIT_FIELD_PETEXPERIENCE);
uint32 newXP = curXP + xp;
// Check how much XP the pet should receive, and hand off have any left from previous levelups
while (newXP >= nextLvlXP && petlevel < maxlevel)
{
// Subtract newXP from amount needed for nextlevel, and give pet the level
newXP -= nextLvlXP;
++petlevel;
GivePetLevel(petlevel);
nextLvlXP = GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP);
}
// Not affected by special conditions - give it new XP
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, petlevel < maxlevel ? newXP : 0);
}
void Pet::GivePetLevel(uint8 level)
{
if (!level || level == getLevel())
return;
if (!IsHunterPet())
{
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0);
SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(level)*PET_XP_FACTOR));
}
InitStatsForLevel(level);
InitLevelupSpellsForLevel();
InitTalentForLevel();
}
bool Pet::CreateBaseAtCreature(Creature* creature)
{
ASSERT(creature);
if (!CreateBaseAtTamed(creature->GetCreatureTemplate(), creature->GetMap()))
return false;
Relocate(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetOrientation());
if (!IsPositionValid())
{
TC_LOG_ERROR("entities.pet", "Pet (guidlow %d, entry %d) not created base at creature. Suggested coordinates isn't valid (X: %f Y: %f)",
GetGUID().GetCounter(), GetEntry(), GetPositionX(), GetPositionY());
return false;
}
CreatureTemplate const* cinfo = GetCreatureTemplate();
if (!cinfo)
{
TC_LOG_ERROR("entities.pet", "CreateBaseAtCreature() failed, creatureInfo is missing!");
return false;
}
SetDisplayId(creature->GetDisplayId());
if (CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cinfo->family))
SetName(cFamily->Name);
else
SetName(creature->GetNameForLocaleIdx(sObjectMgr->GetDBCLocaleIndex()));
Transport* transport = GetTransGUID().IsEmpty() ? creature->GetTransport() : nullptr;
if (transport)
{
float x, y, z, o;
GetPosition(x, y, z, o);
transport->CalculatePassengerOffset(x, y, z, &o);
m_movementInfo.transport.pos.Relocate(x, y, z, o);
transport->AddPassenger(this);
}
return true;
}
bool Pet::CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner)
{
if (!CreateBaseAtTamed(cinfo, owner->GetMap()))
return false;
if (CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cinfo->family))
SetName(cFamily->Name);
Relocate(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ(), owner->GetOrientation());
Transport* transport = GetTransGUID().IsEmpty() ? owner->GetTransport() : nullptr;
if (transport)
{
float x, y, z, o;
GetPosition(x, y, z, o);
transport->CalculatePassengerOffset(x, y, z, &o);
m_movementInfo.transport.pos.Relocate(x, y, z, o);
transport->AddPassenger(this);
}
return true;
}
bool Pet::CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map)
{
TC_LOG_DEBUG("entities.pet", "Pet::CreateBaseForTamed");
ObjectGuid::LowType guid = map->GenerateLowGuid<HighGuid::Pet>();
uint32 petId = sObjectMgr->GeneratePetNumber();
if (!Create(guid, map, cinfo->Entry, petId))
return false;
setPowerType(POWER_FOCUS);
SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, 0);
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0);
SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(getLevel()+1)*PET_XP_FACTOR));
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
if (cinfo->type == CREATURE_TYPE_BEAST)
{
SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100);
SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_WARRIOR);
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, UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
}
return true;
}
bool Guardian::InitStatsForLevel(uint8 petlevel)
{
CreatureTemplate const* cinfo = GetCreatureTemplate();
ASSERT(cinfo);
SetLevel(petlevel);
//Determine pet type
PetType petType = MAX_PET_TYPE;
if (IsPet() && GetOwner()->GetTypeId() == TYPEID_PLAYER)
{
switch (m_owner->getClass())
{
case CLASS_WARLOCK:
case CLASS_SHAMAN:
case CLASS_DEATH_KNIGHT:
case CLASS_MAGE:
case CLASS_PRIEST:
petType = SUMMON_PET;
break;
case CLASS_HUNTER:
petType = HUNTER_PET;
m_unitTypeMask |= UNIT_MASK_HUNTER_PET;
break;
default:
TC_LOG_ERROR("entities.pet", "Unknown type pet %u is summoned by player class %u",
GetEntry(), GetOwner()->getClass());
break;
}
}
uint32 creature_ID = (petType == HUNTER_PET) ? 1 : cinfo->Entry;
SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
SetAttackTime(BASE_ATTACK, BASE_ATTACK_TIME);
SetAttackTime(OFF_ATTACK, BASE_ATTACK_TIME);
SetAttackTime(RANGED_ATTACK, BASE_ATTACK_TIME);
SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
SetFloatValue(UNIT_MOD_CAST_HASTE, 1.0f);
//scale
CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cinfo->family);
if (cFamily && cFamily->MinScale > 0.0f && petType == HUNTER_PET)
{
float scale;
if (getLevel() >= cFamily->MaxScaleLevel)
scale = cFamily->MaxScale;
else if (getLevel() <= cFamily->MinScaleLevel)
scale = cFamily->MinScale;
else
scale = cFamily->MinScale + float(getLevel() - cFamily->MinScaleLevel) / cFamily->MaxScaleLevel * (cFamily->MaxScale - cFamily->MinScale);
SetObjectScale(scale);
}
// Resistance
// Hunters pets should not inherit resistances from creature_template, they have separate auras for that
if (!IsHunterPet())
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, float(cinfo->resistance[i]));
// Health, mana, armor and resistance
PetLevelInfo const* pInfo = sObjectMgr->GetPetLevelInfo(creature_ID, petlevel);
if (pInfo) // exist in DB
{
SetCreateHealth(pInfo->health);
if (petType != HUNTER_PET) //hunter pet use focus
SetCreateMana(pInfo->mana);
if (pInfo->armor > 0)
SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(pInfo->armor));
for (uint8 stat = 0; stat < MAX_STATS; ++stat)
SetCreateStat(Stats(stat), float(pInfo->stats[stat]));
}
else // not exist in DB, use some default fake data
{
// remove elite bonuses included in DB values
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(petlevel, cinfo->unit_class);
SetCreateHealth(stats->BaseHealth[cinfo->expansion]);
SetCreateMana(stats->BaseMana);
SetCreateStat(STAT_STRENGTH, 22);
SetCreateStat(STAT_AGILITY, 22);
SetCreateStat(STAT_STAMINA, 25);
SetCreateStat(STAT_INTELLECT, 28);
SetCreateStat(STAT_SPIRIT, 27);
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
}
SetBonusDamage(0);
switch (petType)
{
case SUMMON_PET:
{
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
}
case HUNTER_PET:
{
SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(petlevel) * PET_XP_FACTOR));
//these formula may not be correct; however, it is designed to be close to what it should be
//this makes dps 0.5 of pets level
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
//damage range is then petlevel / 2
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
//damage is increased afterwards as strength and pet scaling modify attack power
break;
}
default:
{
switch (GetEntry())
{
case ENTRY_TREANT:
{
float bonusDmg = GetOwner()->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_NATURE) * 0.15f;
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 2.5f - (petlevel / 2) + bonusDmg));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 2.5f + (petlevel / 2) + bonusDmg));
SetCreateHealth(m_owner->CountPctFromMaxHealth(10));
break;
}
case ENTRY_EARTH_ELEMENTAL:
{
if (Unit* owner = m_owner->GetOwner())
SetCreateHealth(owner->CountPctFromMaxHealth(75));
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 4 - petlevel));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 4 + petlevel));
break;
}
case ENTRY_FIRE_ELEMENTAL:
{
if (Unit* owner = m_owner->GetOwner())
{
SetCreateHealth(owner->CountPctFromMaxHealth(75));
SetBonusDamage(int32(owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE) * 0.5f));
}
SetCreateMana(28 + 10 * petlevel);
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 4 - petlevel));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 4 + petlevel));
break;
}
case ENTRY_SHADOWFIEND:
{
SetCreateMana(28 + 10 * petlevel);
SetCreateHealth(28 + 30 * petlevel);
int32 bonus_dmg = int32(GetOwner()->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_SHADOW)* 0.375f);
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float((petlevel * 4 - petlevel) + bonus_dmg));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float((petlevel * 4 + petlevel) + bonus_dmg));
break;
}
case ENTRY_VENOMOUS_SNAKE:
{
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float((petlevel / 2) - 25));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float((petlevel / 2) - 18));
break;
}
case ENTRY_VIPER:
{
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel / 2 - 10));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel / 2));
break;
}
case ENTRY_SPIRIT_WOLF:
{
SetCreateHealth(30 * petlevel);
float dmg_multiplier = 0.50f;
if (m_owner->GetAuraEffect(63271, 0)) // Glyph of Feral Spirit
dmg_multiplier = 0.80f;
SetBonusDamage(int32(m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * dmg_multiplier));
// wolf attack speed is 1.5s
SetAttackTime(BASE_ATTACK, cinfo->BaseAttackTime);
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float((petlevel * 4 - petlevel)));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float((petlevel * 4 + petlevel)));
// 14AP == 1dps, wolf's strike speed == 1.5s so dmg = AP / 14 * 1.5
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * dmg_multiplier / 14);
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * dmg_multiplier / 14);
SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(GetOwner()->GetArmor()) * 0.35f); // Bonus Armor (35% of player armor)
SetModifierValue(UNIT_MOD_STAT_STAMINA, BASE_VALUE, float(GetOwner()->GetStat(STAT_STAMINA)) * 0.3f); // Bonus Stamina (30% of player stamina)
if (!HasAura(58877)) // Spirit Hunt
AddAura(58877, this);
if (!HasAura(61783)) // Feral Pet Scaling
AddAura(61783, this);
break;
}
case ENTRY_GARGOYLE:
{
SetCreateHealth(m_owner->CountPctFromMaxHealth(70));
if (Player* owner = m_owner->ToPlayer())
{
float bonus = owner->GetRatingBonusValue(CR_HASTE_MELEE);
bonus += owner->GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE_3) +
owner->GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_RANGED_HASTE);
ApplyCastTimePercentMod(bonus, true);
}
SetBonusDamage(int32(GetOwner()->GetTotalAttackPowerValue(BASE_ATTACK) * 0.5f));
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
break;
}
case ENTRY_BLOODWORM:
{
SetCreateHealth(m_owner->CountPctFromMaxHealth(18));
SetBonusDamage(int32(m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.006f));
float minDamage = m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.05f;
float maxDamage = m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.05f;
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, minDamage);
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, maxDamage);
if (!HasAura(50453)) // Blood Siphon
CastSpell(this, 50453, true);
break;
}
case ENTRY_INFERNAL:
{
if (m_owner->GetTypeId() == TYPEID_PLAYER) // Infernal get 100% of owners spell, Immolation has his own coef.
SetBonusDamage(int32(m_owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_SPELL)));
float mod = 1;
if (petlevel < 60)
mod = 5 + (petlevel - 50) / 4;
if (petlevel < 70)
mod = 10 + (petlevel - 70) / 4;
else if (petlevel <= 80)
mod = 15 + (petlevel - 80) / 2;
else
mod = 20 + (petlevel - 85) / 2;
if (mod < 0)
mod = 0;
float minDamage = (petlevel - (petlevel / 4)) * mod;
float maxDamage = (petlevel + (petlevel / 4)) * mod;
float attackPower = ((minDamage + maxDamage) /4) * 7;
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, minDamage);
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, maxDamage);
SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, attackPower);
SetCreateHealth(m_owner->CountPctFromMaxHealth(40));
break;
}
case ENTRY_EBON_IMP:
SetBonusDamage(m_owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE));
break;
case ENTRY_ARMY_OF_THE_DEAD_GHOUL:
{
SetCreateHealth(m_owner->CountPctFromMaxHealth(30));
SetBonusDamage(int32(GetOwner()->GetTotalAttackPowerValue(BASE_ATTACK) * 0.5f));
float minDamage = m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.05f;
float maxDamage = m_owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.05f;
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, minDamage);
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, maxDamage);
if (!HasAura(62137)) // Avoidance
CastSpell(this, 62137, true);
break;
}
case ENTRY_GHOUL:
if (!HasAura(62137)) // Avoidance
CastSpell(this, 62137, true);
break;
}
break;
}
}
return true;
}
bool Pet::HaveInDiet(ItemTemplate const* item) const
{
if (!item->FoodType)
return false;
CreatureTemplate const* cInfo = GetCreatureTemplate();
if (!cInfo)
return false;
CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cInfo->family);
if (!cFamily)
return false;
uint32 diet = cFamily->PetFoodMask;
uint32 FoodMask = 1 << (item->FoodType-1);
return (diet & FoodMask) != 0;
}
uint32 Pet::GetCurrentFoodBenefitLevel(uint32 itemlevel) const
{
// -10 or greater food level
if (getLevel() <= itemlevel + 10) // possible to feed level 85 pet with ilevel 75 level food for full effect
return 50;
// -10 to -20
else if (getLevel() <= itemlevel + 20)
return 25;
// -20 to -30
else if (getLevel() <= itemlevel + 30)
return 13;
// -30 or more difference
else
return 0; //food too low level
}
void Pet::_LoadSpellCooldowns()
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
PreparedQueryResult cooldownsResult = CharacterDatabase.Query(stmt);
GetSpellHistory()->LoadFromDB<Pet>(cooldownsResult);
}
void Pet::_LoadSpells()
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (result)
{
do
{
Field* fields = result->Fetch();
addSpell(fields[0].GetUInt32(), ActiveStates(fields[1].GetUInt8()), PETSPELL_UNCHANGED);
}
while (result->NextRow());
}
}
void Pet::_SaveSpells(CharacterDatabaseTransaction& trans)
{
for (PetSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end(); itr = next)
{
++next;
// prevent saving family passives to DB
if (itr->second.type == PETSPELL_FAMILY)
continue;
CharacterDatabasePreparedStatement* stmt;
switch (itr->second.state)
{
case PETSPELL_REMOVED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_BY_SPELL);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
stmt->setUInt32(1, itr->first);
trans->Append(stmt);
m_spells.erase(itr);
continue;
case PETSPELL_CHANGED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_BY_SPELL);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
stmt->setUInt32(1, itr->first);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
stmt->setUInt32(1, itr->first);
stmt->setUInt8(2, itr->second.active);
trans->Append(stmt);
break;
case PETSPELL_NEW:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
stmt->setUInt32(1, itr->first);
stmt->setUInt8(2, itr->second.active);
trans->Append(stmt);
break;
case PETSPELL_UNCHANGED:
continue;
}
itr->second.state = PETSPELL_UNCHANGED;
}
}
void Pet::_LoadAuras(uint32 timediff)
{
TC_LOG_DEBUG("entities.pet", "Loading auras for pet %u", GetGUID().GetCounter());
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (result)
{
do
{
int32 damage[3];
int32 baseDamage[3];
Field* fields = result->Fetch();
ObjectGuid caster_guid(fields[0].GetUInt64());
// nullptr guid stored - pet is the caster of the spell - see Pet::_SaveAuras
if (!caster_guid)
caster_guid = GetGUID();
uint32 spellid = fields[1].GetUInt32();
uint8 effmask = fields[2].GetUInt8();
uint8 recalculatemask = fields[3].GetUInt8();
uint8 stackcount = fields[4].GetUInt8();
damage[0] = fields[5].GetInt32();
damage[1] = fields[6].GetInt32();
damage[2] = fields[7].GetInt32();
baseDamage[0] = fields[8].GetInt32();
baseDamage[1] = fields[9].GetInt32();
baseDamage[2] = fields[10].GetInt32();
int32 maxduration = fields[11].GetInt32();
int32 remaintime = fields[12].GetInt32();
uint8 remaincharges = fields[13].GetUInt8();
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
{
TC_LOG_ERROR("entities.pet", "Unknown aura (spellid %u), ignore.", spellid);
continue;
}
// negative effects should continue counting down after logout
if (remaintime != -1 && (!spellInfo->IsPositive() || spellInfo->HasAttribute(SPELL_ATTR4_FADES_WHILE_LOGGED_OUT)))
{
if (remaintime/IN_MILLISECONDS <= int32(timediff))
continue;
remaintime -= timediff*IN_MILLISECONDS;
}
// prevent wrong values of remaincharges
if (spellInfo->ProcCharges)
{
if (remaincharges <= 0 || remaincharges > spellInfo->ProcCharges)
remaincharges = spellInfo->ProcCharges;
}
else
remaincharges = 0;
if (Aura* aura = Aura::TryCreate(spellInfo, effmask, this, nullptr, &baseDamage[0], nullptr, caster_guid))
{
if (!aura->CanBeSaved())
{
aura->Remove();
continue;
}
aura->SetLoadedState(maxduration, remaintime, remaincharges, stackcount, recalculatemask, &damage[0]);
aura->ApplyForTargets();
TC_LOG_DEBUG("entities.pet", "Added aura spellid %u, effectmask %u", spellInfo->Id, effmask);
}
}
while (result->NextRow());
}
}
void Pet::_SaveAuras(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURAS);
stmt->setUInt32(0, m_charmInfo->GetPetNumber());
trans->Append(stmt);
for (AuraMap::const_iterator itr = m_ownedAuras.begin(); itr != m_ownedAuras.end(); ++itr)
{
// check if the aura has to be saved
if (!itr->second->CanBeSaved() || IsPetAura(itr->second))
continue;
Aura* aura = itr->second;
int32 damage[MAX_SPELL_EFFECTS];
int32 baseDamage[MAX_SPELL_EFFECTS];
uint8 effMask = 0;
uint8 recalculateMask = 0;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (aura->GetEffect(i))
{
baseDamage[i] = aura->GetEffect(i)->GetBaseAmount();
damage[i] = aura->GetEffect(i)->GetAmount();
effMask |= (1<<i);
if (aura->GetEffect(i)->CanBeRecalculated())
recalculateMask |= (1<<i);
}
else
{
baseDamage[i] = 0;
damage[i] = 0;
}
}
// don't save guid of caster in case we are caster of the spell - guid for pet is generated every pet load, so it won't match saved guid anyways
ObjectGuid casterGUID = (itr->second->GetCasterGUID() == GetGUID()) ? ObjectGuid::Empty : itr->second->GetCasterGUID();
uint8 index = 0;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_AURA);
stmt->setUInt32(index++, m_charmInfo->GetPetNumber());
stmt->setUInt64(index++, casterGUID.GetRawValue());
stmt->setUInt32(index++, itr->second->GetId());
stmt->setUInt8(index++, effMask);
stmt->setUInt8(index++, recalculateMask);
stmt->setUInt8(index++, itr->second->GetStackAmount());
stmt->setInt32(index++, damage[0]);
stmt->setInt32(index++, damage[1]);
stmt->setInt32(index++, damage[2]);
stmt->setInt32(index++, baseDamage[0]);
stmt->setInt32(index++, baseDamage[1]);
stmt->setInt32(index++, baseDamage[2]);
stmt->setInt32(index++, itr->second->GetMaxDuration());
stmt->setInt32(index++, itr->second->GetDuration());
stmt->setUInt8(index++, itr->second->GetCharges());
trans->Append(stmt);
}
}
bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpellState state /*= PETSPELL_NEW*/, PetSpellType type /*= PETSPELL_NORMAL*/)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
{
// do pet spell book cleanup
if (state == PETSPELL_UNCHANGED) // spell load case
{
TC_LOG_ERROR("entities.pet", "Pet::addSpell: Non-existed in SpellStore spell #%u request, deleting for all pets in `pet_spell`.", spellId);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_PET_SPELL);
stmt->setUInt32(0, spellId);
CharacterDatabase.Execute(stmt);
}
else
TC_LOG_ERROR("entities.pet", "Pet::addSpell: Non-existed in SpellStore spell #%u request.", spellId);
return false;
}
PetSpellMap::iterator itr = m_spells.find(spellId);
if (itr != m_spells.end())
{
if (itr->second.state == PETSPELL_REMOVED)
state = PETSPELL_CHANGED;
else
{
if (state == PETSPELL_UNCHANGED && itr->second.state != PETSPELL_UNCHANGED)
{
// can be in case spell loading but learned at some previous spell loading
itr->second.state = PETSPELL_UNCHANGED;
if (active == ACT_ENABLED)
ToggleAutocast(spellInfo, true);
else if (active == ACT_DISABLED)
ToggleAutocast(spellInfo, false);
}
return false;
}
}
PetSpell newspell;
newspell.state = state;
newspell.type = type;
if (active == ACT_DECIDE) // active was not used before, so we save it's autocast/passive state here
{
if (spellInfo->IsAutocastable())
newspell.active = ACT_ENABLED;
else
newspell.active = ACT_PASSIVE;
}
else
newspell.active = active;
// talent: unlearn all other talent ranks (high and low)
if (TalentSpellPos const* talentPos = sDBCManager.GetTalentSpellPos(spellId))
{
if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id))
{
for (uint8 i = 0; i < MAX_TALENT_RANK; ++i)
{
// skip learning spell and no rank spell case
uint32 rankSpellId = talentInfo->SpellRank[i];
if (!rankSpellId || rankSpellId == spellId)
continue;
// skip unknown ranks
if (!HasSpell(rankSpellId))
continue;
removeSpell(rankSpellId, false, false);
}
}
}
else if (spellInfo->IsRanked())
{
for (PetSpellMap::const_iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2)
{
if (itr2->second.state == PETSPELL_REMOVED)
continue;
SpellInfo const* oldRankSpellInfo = sSpellMgr->GetSpellInfo(itr2->first);
if (!oldRankSpellInfo)
continue;
if (spellInfo->IsDifferentRankOf(oldRankSpellInfo))
{
// replace by new high rank
if (spellInfo->IsHighRankOf(oldRankSpellInfo))
{
newspell.active = itr2->second.active;
if (newspell.active == ACT_ENABLED)
ToggleAutocast(oldRankSpellInfo, false);
unlearnSpell(itr2->first, false, false);
break;
}
// ignore new lesser rank
else
return false;
}
}
}
// Store pet scaling auras in a own vector to handle the updating more efficient
if (spellInfo->HasAttribute(SPELL_ATTR4_IS_PET_SCALING))
{
m_petScalingAuras.push_back(spellInfo->Id);
return true;
}
m_spells[spellId] = newspell;
// Do not go further if this Attribute exists
if (spellInfo->HasAttribute(SPELL_ATTR4_UNK15) && !IsWarlockMinion())
return true;
if (spellInfo->IsPassive() && (!spellInfo->CasterAuraState || HasAuraState(AuraStateType(spellInfo->CasterAuraState))))
CastSpell(this, spellId, true);
else
m_charmInfo->AddSpellToActionBar(spellInfo);
if (newspell.active == ACT_ENABLED)
ToggleAutocast(spellInfo, true);
uint32 talentCost = sDBCManager.GetTalentSpellCost(spellId);
if (talentCost)
{
int32 free_points = GetMaxTalentPointsForLevel(getLevel());
m_usedTalentCount += talentCost;
// update free talent points
free_points-=m_usedTalentCount;
SetFreeTalentPoints(free_points > 0 ? free_points : 0);
}
return true;
}
bool Pet::learnSpell(uint32 spell_id)
{
// prevent duplicated entires in spell book
if (!addSpell(spell_id))
return false;
if (!m_loading && getPetType() != SUMMON_PET)
{
WorldPacket data(SMSG_PET_LEARNED_SPELL, 4);
data << uint32(spell_id);
GetOwner()->SendDirectMessage(&data);
GetOwner()->PetSpellInitialize();
}
return true;
}
void Pet::InitLevelupSpellsForLevel()
{
uint8 level = getLevel();
if (PetLevelupSpellSet const* levelupSpells = GetCreatureTemplate()->family ? sSpellMgr->GetPetLevelupSpellList(GetCreatureTemplate()->family) : nullptr)
{
// PetLevelupSpellSet ordered by levels
for (PetLevelupSpellSet::const_iterator itr = levelupSpells->begin(); itr != levelupSpells->end(); ++itr)
{
// will called first if level down
if (itr->first > level)
unlearnSpell(itr->second, true); // will learn prev rank if any
// will called if level up
else
learnSpell(itr->second); // will unlearn prev rank if any
}
}
int32 petSpellsId = GetCreatureTemplate()->PetSpellDataId ? -(int32)GetCreatureTemplate()->PetSpellDataId : GetEntry();
// default spells (can be not learned if pet level (as owner level decrease result for example) less first possible in normal game)
if (PetDefaultSpellsEntry const* defSpells = sSpellMgr->GetPetDefaultSpellsEntry(petSpellsId))
{
for (uint8 i = 0; i < MAX_CREATURE_SPELL_DATA_SLOT; ++i)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(defSpells->spellid[i]);
if (!spellInfo)
continue;
// will called first if level down
if (spellInfo->SpellLevel > level)
unlearnSpell(spellInfo->Id, true);
// will called if level up
else
learnSpell(spellInfo->Id);
}
}
}
bool Pet::unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab)
{
if (removeSpell(spell_id, learn_prev, clear_ab))
{
if (!m_loading && getPetType() != SUMMON_PET)
{
WorldPacket data(SMSG_PET_REMOVED_SPELL, 4);
data << uint32(spell_id);
GetOwner()->SendDirectMessage(&data);
}
return true;
}
return false;
}
bool Pet::removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab)
{
PetSpellMap::iterator itr = m_spells.find(spell_id);
if (itr == m_spells.end())
return false;
if (itr->second.state == PETSPELL_REMOVED)
return false;
if (itr->second.state == PETSPELL_NEW)
m_spells.erase(itr);
else
itr->second.state = PETSPELL_REMOVED;
RemoveAurasDueToSpell(spell_id);
uint32 talentCost = sDBCManager.GetTalentSpellCost(spell_id);
if (talentCost > 0)
{
if (m_usedTalentCount > talentCost)
m_usedTalentCount -= talentCost;
else
m_usedTalentCount = 0;
// update free talent points
int32 free_points = GetMaxTalentPointsForLevel(getLevel()) - m_usedTalentCount;
SetFreeTalentPoints(free_points > 0 ? free_points : 0);
}
if (learn_prev)
{
if (uint32 prev_id = sSpellMgr->GetPrevSpellInChain (spell_id))
learnSpell(prev_id);
else
learn_prev = false;
}
// if remove last rank or non-ranked then update action bar at server and client if need
if (clear_ab && !learn_prev && m_charmInfo->RemoveSpellFromActionBar(spell_id))
{
if (!m_loading)
GetOwner()->PetSpellInitialize(); // need update action bar for last removed rank
}
return true;
}
void Pet::CleanupActionBar()
{
for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
if (UnitActionBarEntry const* ab = m_charmInfo->GetActionBarEntry(i))
if (ab->GetAction() && ab->IsActionBarForSpell())
{
if (!HasSpell(ab->GetAction()))
m_charmInfo->SetActionBar(i, 0, ACT_PASSIVE);
else if (ab->GetType() == ACT_ENABLED)
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(ab->GetAction()))
ToggleAutocast(spellInfo, true);
}
}
}
void Pet::InitPetCreateSpells()
{
m_charmInfo->InitPetActionBar();
m_spells.clear();
m_petScalingAuras.clear();
LearnPetPassives();
InitLevelupSpellsForLevel();
CastPetAuras(false);
CastPetScalingAuras();
}
bool Pet::resetTalents()
{
Player* player = GetOwner();
// not need after this call
if (player->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
player->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true);
CreatureTemplate const* ci = GetCreatureTemplate();
if (!ci)
return false;
// Check pet talent type
CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family);
if (!pet_family || pet_family->PetTalentType < 0)
return false;
uint8 level = getLevel();
uint32 talentPointsForLevel = GetMaxTalentPointsForLevel(level);
if (m_usedTalentCount == 0)
{
SetFreeTalentPoints(talentPointsForLevel);
return false;
}
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
if (!talentInfo)
continue;
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID);
if (!talentTabInfo)
continue;
// unlearn only talents for pets family talent type
if (!((1 << pet_family->PetTalentType) & talentTabInfo->CategoryEnumID))
continue;
for (uint8 j = 0; j < MAX_TALENT_RANK; ++j)
{
for (PetSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end();)
{
if (itr->second.state == PETSPELL_REMOVED)
{
++itr;
continue;
}
// remove learned spells (all ranks)
uint32 itrFirstId = sSpellMgr->GetFirstSpellInChain(itr->first);
// unlearn if first rank is talent or learned by talent
if (itrFirstId == talentInfo->SpellRank[j] || sSpellMgr->IsSpellLearnToSpell(talentInfo->SpellRank[j], itrFirstId))
{
unlearnSpell(itr->first, false);
itr = m_spells.begin();
continue;
}
else
++itr;
}
}
}
SetFreeTalentPoints(talentPointsForLevel);
if (!m_loading)
player->PetSpellInitialize();
return true;
}
void Pet::resetTalentsForAllPetsOf(Player* /*owner*/, Pet* /*onlinePet*/ /*= nullptr*/)
{
/*
// not need after this call
if (owner->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
owner->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true);
// reset for online
if (onlinePet)
onlinePet->resetTalents();
// now need only reset for offline pets (all pets except online case)
uint32 exceptPetNumber = onlinePet ? onlinePet->GetCharmInfo()->GetPetNumber() : 0;
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET);
stmt->setUInt32(0, owner->GetGUID().GetCounter());
stmt->setUInt32(1, exceptPetNumber);
PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
// no offline pets
if (!resultPets)
return;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_LIST);
stmt->setUInt32(0, owner->GetGUID().GetCounter());
stmt->setUInt32(1, exceptPetNumber);
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
return;
bool need_comma = false;
std::ostringstream ss;
ss << "DELETE FROM pet_spell WHERE guid IN (";
do
{
Field* fields = resultPets->Fetch();
uint32 id = fields[0].GetUInt32();
if (need_comma)
ss << ',';
ss << id;
need_comma = true;
} while (resultPets->NextRow());
ss << ") AND spell IN (";
bool need_execute = false;
do
{
Field* fields = result->Fetch();
uint32 spell = fields[0].GetUInt32();
if (!GetTalentSpellCost(spell))
continue;
if (need_execute)
ss << ',';
ss << spell;
need_execute = true;
}
while (result->NextRow());
if (!need_execute)
return;
ss << ')';
CharacterDatabase.Execute(ss.str().c_str());
*/
}
void Pet::InitTalentForLevel()
{
uint8 level = getLevel();
uint32 talentPointsForLevel = GetMaxTalentPointsForLevel(level);
// Reset talents in case low level (on level down) or wrong points for level (hunter can unlearn TP increase talent)
if (talentPointsForLevel == 0 || m_usedTalentCount > talentPointsForLevel)
resetTalents(); // Remove all talent points
SetFreeTalentPoints(talentPointsForLevel - m_usedTalentCount);
if (!m_loading)
GetOwner()->SendTalentsInfoData(true);
}
uint8 Pet::GetMaxTalentPointsForLevel(uint8 level) const
{
uint8 points = (level >= 20) ? ((level - 16) / 4) : 0;
// Mod points from owner SPELL_AURA_MOD_PET_TALENT_POINTS
points += GetOwner()->GetTotalAuraModifier(SPELL_AURA_MOD_PET_TALENT_POINTS);
return points;
}
void Pet::ToggleAutocast(SpellInfo const* spellInfo, bool apply)
{
if (!spellInfo->IsAutocastable())
return;
uint32 spellid = spellInfo->Id;
PetSpellMap::iterator itr = m_spells.find(spellid);
if (itr == m_spells.end())
return;
uint32 i;
if (apply)
{
for (i = 0; i < m_autospells.size() && m_autospells[i] != spellid; ++i)
; // just search
if (i == m_autospells.size())
{
m_autospells.push_back(spellid);
if (itr->second.active != ACT_ENABLED)
{
itr->second.active = ACT_ENABLED;
if (itr->second.state != PETSPELL_NEW)
itr->second.state = PETSPELL_CHANGED;
}
}
}
else
{
AutoSpellList::iterator itr2 = m_autospells.begin();
for (i = 0; i < m_autospells.size() && m_autospells[i] != spellid; ++i, ++itr2)
; // just search
if (i < m_autospells.size())
{
m_autospells.erase(itr2);
if (itr->second.active != ACT_DISABLED)
{
itr->second.active = ACT_DISABLED;
if (itr->second.state != PETSPELL_NEW)
itr->second.state = PETSPELL_CHANGED;
}
}
}
}
bool Pet::IsPermanentPetFor(Player* owner) const
{
switch (getPetType())
{
case SUMMON_PET:
switch (owner->getClass())
{
case CLASS_WARLOCK:
return GetCreatureTemplate()->type == CREATURE_TYPE_DEMON;
case CLASS_DEATH_KNIGHT:
return GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD;
case CLASS_MAGE:
return GetCreatureTemplate()->type == CREATURE_TYPE_ELEMENTAL;
default:
return false;
}
case HUNTER_PET:
return true;
default:
return false;
}
}
bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry, uint32 petId)
{
ASSERT(map);
SetMap(map);
Object::_Create(guidlow, petId, HighGuid::Pet);
m_spawnId = guidlow;
m_originalEntry = Entry;
if (!InitEntry(Entry))
return false;
// Force regen flag for player pets, just like we do for players themselves
SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER);
SetSheath(SHEATH_STATE_MELEE);
return true;
}
bool Pet::HasSpell(uint32 spell) const
{
PetSpellMap::const_iterator itr = m_spells.find(spell);
return itr != m_spells.end() && itr->second.state != PETSPELL_REMOVED;
}
// Get all passive spells in our skill line
void Pet::LearnPetPassives()
{
CreatureTemplate const* cInfo = GetCreatureTemplate();
if (!cInfo)
return;
CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cInfo->family);
if (!cFamily)
return;
PetFamilySpellsStore::const_iterator petStore = sPetFamilySpellsStore.find(cFamily->ID);
if (petStore != sPetFamilySpellsStore.end())
{
for (PetFamilySpellsSet::const_iterator petSet = petStore->second.begin(); petSet != petStore->second.end(); ++petSet)
addSpell(*petSet, ACT_DECIDE, PETSPELL_NEW, PETSPELL_FAMILY);
}
}
void Pet::CastPetScalingAuras()
{
for (uint32 spellId : m_petScalingAuras)
{
if (!HasAura(spellId))
CastSpell(this, spellId, true);
}
}
void Pet::CastPetAuras(bool current)
{
Player* owner = GetOwner();
if (!IsPermanentPetFor(owner))
return;
for (auto itr = owner->m_petAuras.begin(); itr != owner->m_petAuras.end();)
{
PetAura const* pa = *itr;
++itr;
if (!current && pa->IsRemovedOnChangePet())
owner->RemovePetAura(pa);
else
CastPetAura(pa);
}
}
void Pet::CastPetAura(PetAura const* aura)
{
uint32 auraId = aura->GetAura(GetEntry());
if (!auraId)
return;
if (auraId == 35696) // Demonic Knowledge
{
int32 basePoints = CalculatePct(aura->GetDamage(), GetStat(STAT_STAMINA) + GetStat(STAT_INTELLECT));
CastCustomSpell(this, auraId, &basePoints, nullptr, nullptr, true);
}
else
CastSpell(this, auraId, true);
}
bool Pet::IsPetAura(Aura const* aura)
{
Player* owner = GetOwner();
// if the owner has that pet aura, return true
for (auto itr = owner->m_petAuras.begin(); itr != owner->m_petAuras.end(); ++itr)
{
if ((*itr)->GetAura(GetEntry()) == aura->GetId())
return true;
}
return false;
}
void Pet::learnSpellHighRank(uint32 spellid)
{
learnSpell(spellid);
if (uint32 next = sSpellMgr->GetNextSpellInChain(spellid))
learnSpellHighRank(next);
}
void Pet::SynchronizeLevelWithOwner()
{
Player* owner = GetOwner();
switch (getPetType())
{
// always same level
case SUMMON_PET:
case HUNTER_PET:
GivePetLevel(owner->getLevel());
break;
default:
break;
}
}
Player* Pet::GetOwner() const
{
return Minion::GetOwner()->ToPlayer();
}
void Pet::SetDisplayId(uint32 modelId)
{
Guardian::SetDisplayId(modelId);
if (!isControlled())
return;
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();
}
void Pet::UpdatePetScalingAuras()
{
for (uint32 spellId : m_petScalingAuras)
{
if (Aura* aura = GetAura(spellId))
aura->RecalculateAmountOfEffects();
}
}