When a pet ability was cast, PetHandler reset all CharmInfo flags before calling PetAI methods, setting the attack flag to true every time. This commands the pet to go and chase the target which is not correct when the state is COMMAND_STAY. PetAI already handles the scenario, it just wasn't getting the actual pet attack state.
2570 lines
92 KiB
C++
2570 lines
92 KiB
C++
/*
|
|
* This file is part of the AzerothCore 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 Affero General Public License as published by the
|
|
* Free Software Foundation; either version 3 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 Affero 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 "Pet.h"
|
|
#include "ArenaSpectator.h"
|
|
#include "CharmInfo.h"
|
|
#include "Common.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "GameTime.h"
|
|
#include "Group.h"
|
|
#include "InstanceScript.h"
|
|
#include "Log.h"
|
|
#include "ObjectMgr.h"
|
|
#include "PetPackets.h"
|
|
#include "Player.h"
|
|
#include "QueryHolder.h"
|
|
#include "ScriptMgr.h"
|
|
#include "SpellAuraEffects.h"
|
|
#include "SpellAuras.h"
|
|
#include "SpellMgr.h"
|
|
#include "Unit.h"
|
|
#include "Util.h"
|
|
#include "WorldPacket.h"
|
|
#include "WorldSession.h"
|
|
|
|
Pet::Pet(Player* owner, PetType type) : Guardian(nullptr, owner ? owner->GetGUID() : ObjectGuid::Empty, true),
|
|
m_usedTalentCount(0),
|
|
m_removed(false),
|
|
m_owner(owner),
|
|
m_happinessTimer(PET_LOSE_HAPPINES_INTERVAL),
|
|
m_petType(type),
|
|
m_duration(0),
|
|
m_auraRaidUpdateMask(0),
|
|
m_loading(false),
|
|
m_petRegenTimer(PET_FOCUS_REGEN_INTERVAL),
|
|
m_tempspellTarget(nullptr),
|
|
m_tempoldTarget(),
|
|
m_tempspellIsPositive(false),
|
|
m_tempspell(0)
|
|
{
|
|
ASSERT(m_owner && m_owner->GetTypeId() == TYPEID_PLAYER);
|
|
|
|
m_unitTypeMask |= UNIT_MASK_PET;
|
|
|
|
if (type == HUNTER_PET)
|
|
m_unitTypeMask |= UNIT_MASK_HUNTER_PET;
|
|
|
|
if (!(m_unitTypeMask & UNIT_MASK_CONTROLABLE_GUARDIAN))
|
|
{
|
|
m_unitTypeMask |= UNIT_MASK_CONTROLABLE_GUARDIAN;
|
|
InitCharmInfo();
|
|
}
|
|
|
|
m_name = "Pet";
|
|
}
|
|
|
|
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();
|
|
Motion_Initialize();
|
|
AIM_Initialize();
|
|
}
|
|
|
|
// pussywizard: apply ICC buff to pets
|
|
if (GetOwnerGUID().IsPlayer() && GetMapId() == 631 && FindMap() && FindMap()->ToInstanceMap() && FindMap()->ToInstanceMap()->GetInstanceScript() && FindMap()->ToInstanceMap()->GetInstanceScript()->GetData(251 /*DATA_BUFF_AVAILABLE*/))
|
|
if (Unit* owner = GetOwner())
|
|
if (Player* plr = owner->ToPlayer())
|
|
{
|
|
SpellAreaForAreaMapBounds saBounds = sSpellMgr->GetSpellAreaForAreaMapBounds(4812);
|
|
for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
|
|
if ((itr->second->raceMask & plr->getRaceMask()) && !HasAura(itr->second->spellId))
|
|
if (SpellInfo const* si = sSpellMgr->GetSpellInfo(itr->second->spellId))
|
|
if (si->HasAura(SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT))
|
|
AddAura(itr->second->spellId, this);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (GetOwnerGUID().IsPlayer())
|
|
{
|
|
if (Player* owner = GetOwner())
|
|
{
|
|
if (getPetType() == SUMMON_PET && owner->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_PET))
|
|
{
|
|
owner->SetLastPetSpell(GetUInt32Value(UNIT_CREATED_BY_SPELL));
|
|
}
|
|
}
|
|
|
|
sScriptMgr->OnPetAddToWorld(this);
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
class PetLoadQueryHolder : public CharacterDatabaseQueryHolder
|
|
{
|
|
public:
|
|
enum
|
|
{
|
|
DECLINED_NAMES,
|
|
AURAS,
|
|
SPELLS,
|
|
COOLDOWNS,
|
|
|
|
MAX
|
|
};
|
|
|
|
PetLoadQueryHolder(ObjectGuid::LowType ownerGuid, uint32 petNumber)
|
|
{
|
|
SetSize(MAX);
|
|
|
|
CharacterDatabasePreparedStatement* stmt = nullptr;
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_DECLINED_NAME);
|
|
stmt->SetData(0, ownerGuid);
|
|
stmt->SetData(1, petNumber);
|
|
SetPreparedQuery(DECLINED_NAMES, stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA);
|
|
stmt->SetData(0, petNumber);
|
|
SetPreparedQuery(AURAS, stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL);
|
|
stmt->SetData(0, petNumber);
|
|
SetPreparedQuery(SPELLS, stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN);
|
|
stmt->SetData(0, petNumber);
|
|
SetPreparedQuery(COOLDOWNS, stmt);
|
|
}
|
|
};
|
|
|
|
std::pair<PetStable::PetInfo const*, PetSaveMode> Pet::GetLoadPetInfo(PetStable const& stable, uint32 petEntry, uint32 petnumber, bool current)
|
|
{
|
|
if (petnumber)
|
|
{
|
|
// Known petnumber entry
|
|
if (stable.CurrentPet && stable.CurrentPet->PetNumber == petnumber)
|
|
return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
|
|
|
|
for (std::size_t stableSlot = 0; stableSlot < stable.StabledPets.size(); ++stableSlot)
|
|
if (stable.StabledPets[stableSlot] && stable.StabledPets[stableSlot]->PetNumber == petnumber)
|
|
return { &stable.StabledPets[stableSlot].value(), PetSaveMode(PET_SAVE_FIRST_STABLE_SLOT + stableSlot) };
|
|
|
|
for (PetStable::PetInfo const& pet : stable.UnslottedPets)
|
|
if (pet.PetNumber == petnumber)
|
|
return { &pet, PET_SAVE_NOT_IN_SLOT };
|
|
}
|
|
else if (current)
|
|
{
|
|
// Current pet (slot 0)
|
|
if (stable.CurrentPet)
|
|
return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
|
|
}
|
|
else if (petEntry)
|
|
{
|
|
// known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets)
|
|
if (stable.CurrentPet && stable.CurrentPet->CreatureId == petEntry)
|
|
return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
|
|
|
|
for (PetStable::PetInfo const& pet : stable.UnslottedPets)
|
|
if (pet.CreatureId == petEntry)
|
|
return { &pet, PET_SAVE_NOT_IN_SLOT };
|
|
}
|
|
else
|
|
{
|
|
// Any current or other non-stabled pet (for hunter "call pet")
|
|
if (stable.CurrentPet)
|
|
return { &stable.CurrentPet.value(), PET_SAVE_AS_CURRENT };
|
|
|
|
if (!stable.UnslottedPets.empty())
|
|
return { &stable.UnslottedPets.front(), PET_SAVE_NOT_IN_SLOT };
|
|
}
|
|
|
|
return { nullptr, PET_SAVE_AS_DELETED };
|
|
}
|
|
|
|
bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool current, uint32 healthPct /*= 0*/, bool fullMana /*= false*/)
|
|
{
|
|
m_loading = true;
|
|
|
|
PetStable* petStable = ASSERT_NOTNULL(owner->GetPetStable());
|
|
|
|
ObjectGuid::LowType ownerid = owner->GetGUID().GetCounter();
|
|
std::pair<PetStable::PetInfo const*, PetSaveMode> info = GetLoadPetInfo(*petStable, petEntry, petnumber, current);
|
|
PetStable::PetInfo const* petInfo = info.first;
|
|
PetSaveMode slot = info.second;
|
|
if (!petInfo)
|
|
{
|
|
m_loading = false;
|
|
return false;
|
|
}
|
|
|
|
// Don't try to reload the current pet
|
|
if (petStable->CurrentPet && owner->GetPet() && petStable->CurrentPet.value().PetNumber == petInfo->PetNumber)
|
|
return false;
|
|
|
|
// we are loading pet at that moment
|
|
if (owner->IsSpectator() || owner->GetPet() || !owner->IsInWorld() || !owner->FindMap())
|
|
return false;
|
|
|
|
bool forceLoadFromDB = false;
|
|
sScriptMgr->OnBeforeLoadPetFromDB(owner, petEntry, petnumber, current, forceLoadFromDB);
|
|
|
|
if (!forceLoadFromDB && (owner->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_PET) && !owner->CanSeeDKPet())) // DK Pet exception
|
|
return false;
|
|
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(petInfo->CreatedBySpellId);
|
|
|
|
bool isTemporarySummon = spellInfo && spellInfo->GetDuration() > 0;
|
|
if (current && isTemporarySummon)
|
|
return false;
|
|
|
|
if (petInfo->Type == HUNTER_PET)
|
|
{
|
|
CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petInfo->CreatureId);
|
|
if (!creatureInfo || !creatureInfo->IsTameable(owner->CanTameExoticPets()))
|
|
return false;
|
|
}
|
|
|
|
if (current && owner->IsPetNeedBeTemporaryUnsummoned())
|
|
{
|
|
owner->SetLastPetSpell(petInfo->CreatedBySpellId);
|
|
owner->SetTemporaryUnsummonedPetNumber(petInfo->PetNumber);
|
|
return false;
|
|
}
|
|
|
|
Map* map = owner->GetMap();
|
|
ObjectGuid::LowType guid = map->GenerateLowGuid<HighGuid::Pet>();
|
|
|
|
if (!Create(guid, map, owner->GetPhaseMask(), petInfo->CreatureId, petInfo->PetNumber))
|
|
return false;
|
|
|
|
setPetType(petInfo->Type);
|
|
SetFaction(owner->GetFaction());
|
|
SetUInt32Value(UNIT_CREATED_BY_SPELL, petInfo->CreatedBySpellId);
|
|
|
|
if (IsCritter())
|
|
{
|
|
float px, py, pz;
|
|
owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, GetFollowAngle());
|
|
Relocate(px, py, pz, owner->GetOrientation());
|
|
|
|
if (!IsPositionValid())
|
|
{
|
|
LOG_ERROR("entities.pet", "Pet{} not loaded. Suggested coordinates isn't valid (X: {} Y: {})",
|
|
GetGUID().ToString(), GetPositionX(), GetPositionY());
|
|
return false;
|
|
}
|
|
|
|
UpdatePositionData();
|
|
map->AddToMap(ToCreature(), true);
|
|
return true;
|
|
}
|
|
|
|
if (getPetType() == HUNTER_PET || GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD)
|
|
GetCharmInfo()->SetPetNumber(petInfo->PetNumber, IsPermanentPetFor(owner)); // Show pet details tab (Shift+P) only for hunter pets, demons or undead
|
|
else
|
|
GetCharmInfo()->SetPetNumber(petInfo->PetNumber, false);
|
|
|
|
SetDisplayId(petInfo->DisplayId);
|
|
SetNativeDisplayId(petInfo->DisplayId);
|
|
UpdatePositionData();
|
|
uint8 petlevel = petInfo->Level;
|
|
ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
|
|
SetName(petInfo->Name);
|
|
|
|
switch (getPetType())
|
|
{
|
|
case SUMMON_PET:
|
|
petlevel = owner->GetLevel();
|
|
|
|
if (IsPetGhoul())
|
|
SetUInt32Value(UNIT_FIELD_BYTES_0, 0x400); // class = rogue
|
|
else
|
|
SetUInt32Value(UNIT_FIELD_BYTES_0, 0x800); // class = mage
|
|
|
|
ReplaceAllUnitFlags(UNIT_FLAG_PLAYER_CONTROLLED); // this enables popup window (pet dismiss, cancel)
|
|
break;
|
|
case HUNTER_PET:
|
|
SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100); // class = warrior, gender = none, power = focus
|
|
SetSheath(SHEATH_STATE_MELEE);
|
|
SetByteFlag(UNIT_FIELD_BYTES_2, 2, petInfo->WasRenamed ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
|
|
|
|
ReplaceAllUnitFlags(UNIT_FLAG_PLAYER_CONTROLLED);
|
|
// this enables popup window (pet abandon, cancel)
|
|
SetMaxPower(POWER_HAPPINESS, GetCreatePowers(POWER_HAPPINESS));
|
|
SetPower(POWER_HAPPINESS, petInfo->Happiness);
|
|
setPowerType(POWER_FOCUS);
|
|
break;
|
|
default:
|
|
if (!IsPetGhoul())
|
|
LOG_ERROR("entities.pet", "Pet have incorrect type ({}) for pet loading.", getPetType());
|
|
break;
|
|
}
|
|
|
|
SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime().count())); // cast can't be helped here
|
|
SetCreatorGUID(owner->GetGUID());
|
|
|
|
InitStatsForLevel(petlevel);
|
|
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, petInfo->Experience);
|
|
|
|
SynchronizeLevelWithOwner();
|
|
|
|
// Set pet's position after setting level, its size depends on it
|
|
float px, py, pz;
|
|
owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, GetFollowAngle());
|
|
Relocate(px, py, pz, owner->GetOrientation());
|
|
if (!IsPositionValid())
|
|
{
|
|
LOG_ERROR("entities.pet", "Pet {} not loaded. Suggested coordinates isn't valid (X: {} Y: {})",
|
|
GetGUID().ToString(), GetPositionX(), GetPositionY());
|
|
return false;
|
|
}
|
|
|
|
SetReactState(petInfo->ReactState);
|
|
SetCanModifyStats(true);
|
|
|
|
// set current pet as current
|
|
// 0=current
|
|
// 1..MAX_PET_STABLES in stable slot
|
|
// PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning))
|
|
if (slot == PET_SAVE_NOT_IN_SLOT)
|
|
{
|
|
uint32 petInfoNumber = petInfo->PetNumber;
|
|
if (petStable->CurrentPet)
|
|
owner->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
|
|
|
auto unslottedPetItr = std::find_if(petStable->UnslottedPets.begin(), petStable->UnslottedPets.end(), [&](PetStable::PetInfo const& unslottedPet)
|
|
{
|
|
return unslottedPet.PetNumber == petInfoNumber;
|
|
});
|
|
|
|
ASSERT(!petStable->CurrentPet);
|
|
ASSERT(unslottedPetItr != petStable->UnslottedPets.end());
|
|
|
|
petStable->CurrentPet = std::move(*unslottedPetItr);
|
|
petStable->UnslottedPets.erase(unslottedPetItr);
|
|
|
|
// old petInfo pointer is no longer valid, refresh it
|
|
petInfo = &petStable->CurrentPet.value();
|
|
}
|
|
else if (PET_SAVE_FIRST_STABLE_SLOT <= slot && slot <= PET_SAVE_LAST_STABLE_SLOT)
|
|
{
|
|
auto stabledPet = std::find_if(petStable->StabledPets.begin(), petStable->StabledPets.end(), [petnumber](Optional<PetStable::PetInfo> const& pet)
|
|
{
|
|
return pet && pet->PetNumber == petnumber;
|
|
});
|
|
|
|
ASSERT(stabledPet != petStable->StabledPets.end());
|
|
|
|
std::swap(*stabledPet, petStable->CurrentPet);
|
|
|
|
// old petInfo pointer is no longer valid, refresh it
|
|
petInfo = &petStable->CurrentPet.value();
|
|
}
|
|
|
|
// Send fake summon spell cast - this is needed for correct cooldown application for spells
|
|
// Example: 46584 - without this cooldown (which should be set always when pet is loaded) isn't set clientside
|
|
/// @todo pets should be summoned from real cast instead of just faking it?
|
|
if (petInfo->CreatedBySpellId && spellInfo && (spellInfo->CategoryRecoveryTime > 0 || spellInfo->RecoveryTime > 0))
|
|
{
|
|
WorldPacket data(SMSG_SPELL_GO, (8 + 8 + 4 + 4 + 2));
|
|
data << owner->GetPackGUID();
|
|
data << owner->GetPackGUID();
|
|
data << uint8(0);
|
|
data << uint32(petInfo->CreatedBySpellId);
|
|
data << uint32(256); // CAST_FLAG_UNKNOWN3
|
|
data << uint32(0);
|
|
owner->SendMessageToSet(&data, true);
|
|
}
|
|
|
|
owner->SetMinion(this, true);
|
|
|
|
if (!isTemporarySummon)
|
|
m_charmInfo->LoadPetActionBar(petInfo->ActionBar);
|
|
|
|
map->AddToMap(ToCreature(), true);
|
|
|
|
//set last used pet number (for use in BG's)
|
|
if (owner->GetTypeId() == TYPEID_PLAYER && isControlled() && !isTemporarySummoned() && (getPetType() == SUMMON_PET || getPetType() == HUNTER_PET))
|
|
owner->ToPlayer()->SetLastPetNumber(petInfo->PetNumber);
|
|
|
|
owner->GetSession()->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(std::make_shared<PetLoadQueryHolder>(ownerid, petInfo->PetNumber)))
|
|
.AfterComplete([this, owner, session = owner->GetSession(), isTemporarySummon, current, lastSaveTime = petInfo->LastSaveTime, savedhealth = petInfo->Health, savedmana = petInfo->Mana, healthPct, fullMana]
|
|
(SQLQueryHolderBase const& holder)
|
|
{
|
|
if (session->GetPlayer() != owner || owner->GetPet() != this)
|
|
return;
|
|
|
|
// passing previous checks ensure that 'this' is still valid
|
|
if (m_removed)
|
|
return;
|
|
|
|
InitTalentForLevel(); // set original talents points before spell loading
|
|
|
|
uint32 timediff = uint32(GameTime::GetGameTime().count() - lastSaveTime);
|
|
_LoadAuras(holder.GetPreparedResult(PetLoadQueryHolder::AURAS), timediff);
|
|
|
|
// load action bar, if data broken will fill later by default spells.
|
|
if (!isTemporarySummon)
|
|
{
|
|
_LoadSpells(holder.GetPreparedResult(PetLoadQueryHolder::SPELLS));
|
|
InitTalentForLevel(); // re-init to check talent count
|
|
_LoadSpellCooldowns(holder.GetPreparedResult(PetLoadQueryHolder::COOLDOWNS));
|
|
LearnPetPassives();
|
|
InitLevelupSpellsForLevel();
|
|
if (GetMap()->IsBattleArena())
|
|
RemoveArenaAuras();
|
|
|
|
CastPetAuras(current);
|
|
}
|
|
|
|
CleanupActionBar(); // remove unknown spells from action bar after load
|
|
|
|
LOG_DEBUG("entities.pet", "New Pet has {}", GetGUID().ToString());
|
|
|
|
owner->PetSpellInitialize();
|
|
owner->SendTalentsInfoData(true);
|
|
|
|
if (owner->GetGroup())
|
|
owner->SetGroupUpdateFlag(GROUP_UPDATE_PET);
|
|
|
|
if (getPetType() == HUNTER_PET)
|
|
{
|
|
if (PreparedQueryResult result = holder.GetPreparedResult(PetLoadQueryHolder::DECLINED_NAMES))
|
|
{
|
|
m_declinedname = std::make_unique<DeclinedName>();
|
|
Field* fields = result->Fetch();
|
|
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
|
|
m_declinedname->name[i] = fields[i].Get<std::string>();
|
|
}
|
|
}
|
|
|
|
uint32 curHealth = savedhealth;
|
|
if (healthPct)
|
|
{
|
|
curHealth = CountPctFromMaxHealth(healthPct);
|
|
}
|
|
|
|
uint32 curMana = savedmana;
|
|
if (fullMana)
|
|
curMana = GetMaxPower(POWER_MANA);
|
|
|
|
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));
|
|
SetFullHealth();
|
|
}
|
|
else
|
|
{
|
|
if (!curHealth && getPetType() == HUNTER_PET)
|
|
setDeathState(DeathState::JustDied);
|
|
else
|
|
{
|
|
SetHealth(curHealth > GetMaxHealth() ? GetMaxHealth() : curHealth);
|
|
SetPower(POWER_MANA, curMana > GetMaxPower(POWER_MANA) ? GetMaxPower(POWER_MANA) : curMana);
|
|
}
|
|
}
|
|
|
|
// must be after SetMinion (owner guid check)
|
|
//LoadTemplateImmunities();
|
|
//LoadMechanicTemplateImmunity();
|
|
m_loading = false;
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
void Pet::SavePetToDB(PetSaveMode mode)
|
|
{
|
|
// not save not player pets
|
|
if (!GetOwnerGUID().IsPlayer())
|
|
return;
|
|
|
|
// dont allow to save pet when it is loaded, possibly bugs action bar!, save only fully controlled creature
|
|
Player* owner = GetOwner()->ToPlayer();
|
|
if (!owner || m_loading || !GetEntry() || !isControlled())
|
|
return;
|
|
|
|
// 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);
|
|
|
|
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
|
// save auras before possibly removing them
|
|
_SaveAuras(trans);
|
|
|
|
// stable and not in slot saves
|
|
if (mode > PET_SAVE_AS_CURRENT)
|
|
RemoveAllAuras();
|
|
|
|
_SaveSpells(trans);
|
|
_SaveSpellCooldowns(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
// current/stable/not_in_slot
|
|
if (mode >= PET_SAVE_AS_CURRENT)
|
|
{
|
|
ObjectGuid::LowType ownerLowGUID = GetOwnerGUID().GetCounter();
|
|
trans = CharacterDatabase.BeginTransaction();
|
|
// remove current data
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
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->SetData(0, ownerLowGUID);
|
|
stmt->SetData(1, uint8(PET_SAVE_AS_CURRENT));
|
|
stmt->SetData(2, uint8(PET_SAVE_LAST_STABLE_SLOT));
|
|
trans->Append(stmt);
|
|
}
|
|
|
|
// save pet
|
|
std::string actionBar = GenerateActionBarData();
|
|
|
|
if (owner->GetPetStable()->CurrentPet && owner->GetPetStable()->CurrentPet->PetNumber == m_charmInfo->GetPetNumber())
|
|
{
|
|
FillPetInfo(&owner->GetPetStable()->CurrentPet.value());
|
|
}
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_PET);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
stmt->SetData(1, GetEntry());
|
|
stmt->SetData(2, ownerLowGUID);
|
|
stmt->SetData(3, GetNativeDisplayId());
|
|
stmt->SetData(4, GetUInt32Value(UNIT_CREATED_BY_SPELL));
|
|
stmt->SetData(5, uint8(getPetType()));
|
|
stmt->SetData(6, GetLevel());
|
|
stmt->SetData(7, GetUInt32Value(UNIT_FIELD_PETEXPERIENCE));
|
|
stmt->SetData(8, uint8(GetReactState()));
|
|
stmt->SetData(9, GetName());
|
|
stmt->SetData(10, uint8(HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) ? 0 : 1));
|
|
stmt->SetData(11, uint8(mode));
|
|
stmt->SetData(12, curhealth);
|
|
stmt->SetData(13, curmana);
|
|
stmt->SetData(14, GetPower(POWER_HAPPINESS));
|
|
stmt->SetData(15, GameTime::GetGameTime().count());
|
|
stmt->SetData(16, actionBar);
|
|
|
|
trans->Append(stmt);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
}
|
|
// delete
|
|
else
|
|
{
|
|
RemoveAllAuras();
|
|
DeleteFromDB(m_charmInfo->GetPetNumber());
|
|
}
|
|
}
|
|
|
|
void Pet::DeleteFromDB(ObjectGuid::LowType guidlow)
|
|
{
|
|
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID);
|
|
stmt->SetData(0, guidlow);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME);
|
|
stmt->SetData(0, guidlow);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURAS);
|
|
stmt->SetData(0, guidlow);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELLS);
|
|
stmt->SetData(0, guidlow);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS);
|
|
stmt->SetData(0, guidlow);
|
|
trans->Append(stmt);
|
|
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
void Pet::setDeathState(DeathState s, bool /*despawn = false*/) // overwrite virtual Creature::setDeathState and Unit::setDeathState
|
|
{
|
|
Creature::setDeathState(s);
|
|
if (getDeathState() == DeathState::Corpse)
|
|
{
|
|
if (getPetType() == HUNTER_PET)
|
|
{
|
|
// pet corpse non lootable and non skinnable
|
|
ReplaceAllDynamicFlags(UNIT_DYNFLAG_NONE);
|
|
RemoveUnitFlag(UNIT_FLAG_SKINNABLE);
|
|
|
|
//lose happiness when died and not in BG/Arena
|
|
MapEntry const* mapEntry = sMapStore.LookupEntry(GetMapId());
|
|
if (!mapEntry || (mapEntry->map_type != MAP_ARENA && mapEntry->map_type != MAP_BATTLEGROUND))
|
|
ModifyPower(POWER_HAPPINESS, -HAPPINESS_LEVEL_SIZE);
|
|
|
|
//SetUnitFlag(UNIT_FLAG_STUNNED);
|
|
}
|
|
}
|
|
else if (getDeathState() == DeathState::Alive)
|
|
{
|
|
//RemoveUnitFlag(UNIT_FLAG_STUNNED);
|
|
CastPetAuras(true);
|
|
}
|
|
}
|
|
|
|
void Pet::Update(uint32 diff)
|
|
{
|
|
auto _diff = Milliseconds(diff);
|
|
|
|
if (m_removed) // pet already removed, just wait in remove queue, no updates
|
|
return;
|
|
|
|
if (m_loading)
|
|
return;
|
|
|
|
switch (m_deathState)
|
|
{
|
|
case DeathState::Corpse:
|
|
{
|
|
if (getPetType() != HUNTER_PET || m_corpseRemoveTime <= GameTime::GetGameTime().count())
|
|
{
|
|
Remove(PET_SAVE_NOT_IN_SLOT); //hunters' pets never get removed because of death, NEVER!
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case DeathState::Alive:
|
|
{
|
|
// unsummon pet that lost owner
|
|
Player* owner = GetOwner();
|
|
if (!owner || (!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);
|
|
return;
|
|
}
|
|
|
|
if (isControlled())
|
|
{
|
|
if (owner->GetPetGUID() != GetGUID())
|
|
{
|
|
LOG_ERROR("entities.pet", "Pet {} is not pet of owner {}, removed", GetEntry(), GetOwner()->GetName());
|
|
ASSERT(getPetType() != HUNTER_PET, "Unexpected unlinked pet found for owner {}", owner->GetSession()->GetPlayerInfo());
|
|
Remove(PET_SAVE_NOT_IN_SLOT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_duration > 0s)
|
|
{
|
|
if (m_duration > _diff)
|
|
m_duration -= _diff;
|
|
else
|
|
{
|
|
Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// xinef: m_regenTimer is decrased in Creature::Update()
|
|
// xinef: just check if we can update focus in current period
|
|
if (getPowerType() == POWER_FOCUS)
|
|
{
|
|
m_petRegenTimer -= _diff;
|
|
if (m_petRegenTimer <= 0s)
|
|
{
|
|
m_petRegenTimer += PET_FOCUS_REGEN_INTERVAL;
|
|
Regenerate(POWER_FOCUS);
|
|
}
|
|
}
|
|
|
|
if (m_tempspell)
|
|
{
|
|
Unit* tempspellTarget = m_tempspellTarget;
|
|
Unit* tempoldTarget = nullptr;
|
|
|
|
if (!m_tempoldTarget.IsEmpty())
|
|
tempoldTarget = ObjectAccessor::GetUnit(*this, m_tempoldTarget);
|
|
|
|
bool tempspellIsPositive = m_tempspellIsPositive;
|
|
uint32 tempspell = m_tempspell;
|
|
Unit* charmer = GetCharmerOrOwner();
|
|
if (!charmer)
|
|
return;
|
|
|
|
if (!GetCharmInfo())
|
|
return;
|
|
|
|
if (tempspellTarget && tempspellTarget->IsAlive())
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tempspell);
|
|
if (!spellInfo)
|
|
return;
|
|
float max_range = GetSpellMaxRangeForTarget(tempspellTarget, spellInfo);
|
|
if (spellInfo->RangeEntry->Flags == SPELL_RANGE_MELEE)
|
|
max_range -= 2 * MIN_MELEE_REACH;
|
|
|
|
if (IsWithinLOSInMap(tempspellTarget) && GetDistance(tempspellTarget) < max_range)
|
|
{
|
|
if (!GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo) && !HasSpellCooldown(tempspell))
|
|
{
|
|
StopMoving();
|
|
GetMotionMaster()->Clear(false);
|
|
GetMotionMaster()->MoveIdle();
|
|
|
|
GetCharmInfo()->SetIsAtStay(true);
|
|
GetCharmInfo()->SetIsCommandFollow(false);
|
|
GetCharmInfo()->SetIsFollowing(false);
|
|
GetCharmInfo()->SetIsReturning(false);
|
|
GetCharmInfo()->SaveStayPosition(true);
|
|
|
|
AddSpellCooldown(tempspell, 0, spellInfo->IsCooldownStartedOnEvent() ? infinityCooldownDelay : 0);
|
|
|
|
CastSpell(tempspellTarget, tempspell, false);
|
|
m_tempspell = 0;
|
|
m_tempspellTarget = nullptr;
|
|
|
|
if (tempspellIsPositive)
|
|
{
|
|
if (tempoldTarget && tempoldTarget->IsAlive())
|
|
{
|
|
GetCharmInfo()->SetIsCommandAttack(true);
|
|
GetCharmInfo()->SetIsAtStay(false);
|
|
GetCharmInfo()->SetIsFollowing(false);
|
|
GetCharmInfo()->SetIsCommandFollow(false);
|
|
GetCharmInfo()->SetIsReturning(false);
|
|
|
|
if (ToCreature() && ToCreature()->IsAIEnabled)
|
|
ToCreature()->AI()->AttackStart(tempoldTarget);
|
|
}
|
|
else
|
|
{
|
|
if (IsAIEnabled)
|
|
AI()->PetStopAttack();
|
|
else
|
|
{
|
|
GetCharmInfo()->SetCommandState(COMMAND_FOLLOW);
|
|
GetCharmInfo()->SetIsCommandAttack(false);
|
|
GetCharmInfo()->SetIsAtStay(false);
|
|
GetCharmInfo()->SetIsReturning(true);
|
|
GetCharmInfo()->SetIsCommandFollow(true);
|
|
GetCharmInfo()->SetIsFollowing(false);
|
|
GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, GetFollowAngle());
|
|
}
|
|
}
|
|
|
|
m_tempoldTarget = ObjectGuid::Empty;
|
|
m_tempspellIsPositive = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_tempspell = 0;
|
|
m_tempspellTarget = nullptr;
|
|
m_tempoldTarget = ObjectGuid::Empty;
|
|
m_tempspellIsPositive = false;
|
|
|
|
Unit* victim = charmer->GetVictim();
|
|
if (victim && victim->IsAlive())
|
|
{
|
|
StopMoving();
|
|
GetMotionMaster()->Clear(false);
|
|
GetMotionMaster()->MoveIdle();
|
|
|
|
GetCharmInfo()->SetIsCommandAttack(true);
|
|
GetCharmInfo()->SetIsAtStay(false);
|
|
GetCharmInfo()->SetIsFollowing(false);
|
|
GetCharmInfo()->SetIsCommandFollow(false);
|
|
GetCharmInfo()->SetIsReturning(false);
|
|
|
|
if (ToCreature() && ToCreature()->IsAIEnabled)
|
|
ToCreature()->AI()->AttackStart(victim);
|
|
}
|
|
else
|
|
{
|
|
StopMoving();
|
|
GetMotionMaster()->Clear(false);
|
|
GetMotionMaster()->MoveIdle();
|
|
|
|
GetCharmInfo()->SetCommandState(COMMAND_FOLLOW);
|
|
GetCharmInfo()->SetIsCommandAttack(false);
|
|
GetCharmInfo()->SetIsAtStay(false);
|
|
GetCharmInfo()->SetIsReturning(true);
|
|
GetCharmInfo()->SetIsCommandFollow(true);
|
|
GetCharmInfo()->SetIsFollowing(false);
|
|
GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, GetFollowAngle());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (getPetType() == HUNTER_PET)
|
|
{
|
|
m_happinessTimer -= diff;
|
|
if (m_happinessTimer <= int32(0))
|
|
{
|
|
LoseHappiness();
|
|
m_happinessTimer += PET_LOSE_HAPPINES_INTERVAL;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Creature::Update(diff);
|
|
}
|
|
|
|
void Pet::LoseHappiness()
|
|
{
|
|
uint32 curValue = GetPower(POWER_HAPPINESS);
|
|
if (curValue <= 0)
|
|
return;
|
|
int32 addvalue = 670; //value is 70/35/17/8/4 (per min) * 1000 / 8 (timer 7.5 secs)
|
|
if (IsInCombat()) //we know in combat happiness fades faster, multiplier guess
|
|
addvalue = int32(addvalue * 1.5f);
|
|
ModifyPower(POWER_HAPPINESS, -addvalue);
|
|
}
|
|
|
|
HappinessState Pet::GetHappinessState()
|
|
{
|
|
if (GetPower(POWER_HAPPINESS) < HAPPINESS_LEVEL_SIZE)
|
|
return UNHAPPY;
|
|
else if (GetPower(POWER_HAPPINESS) >= HAPPINESS_LEVEL_SIZE * 2)
|
|
return HAPPY;
|
|
else
|
|
return CONTENT;
|
|
}
|
|
|
|
void Pet::Remove(PetSaveMode mode, bool returnreagent)
|
|
{
|
|
GetOwner()->RemovePet(this, mode, returnreagent);
|
|
}
|
|
|
|
void Pet::GivePetXP(uint32 xp)
|
|
{
|
|
if (getPetType() != HUNTER_PET)
|
|
return;
|
|
|
|
xp *= sWorld->getRate(RATE_XP_PET);
|
|
|
|
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 (getPetType() == HUNTER_PET)
|
|
{
|
|
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0);
|
|
SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(level)*sWorld->getRate(RATE_XP_PET_NEXT_LEVEL)));
|
|
}
|
|
|
|
InitStatsForLevel(level);
|
|
InitLevelupSpellsForLevel();
|
|
InitTalentForLevel();
|
|
}
|
|
|
|
bool Pet::CreateBaseAtCreature(Creature* creature)
|
|
{
|
|
ASSERT(creature);
|
|
|
|
if (!CreateBaseAtTamed(creature->GetCreatureTemplate(), creature->GetMap(), creature->GetPhaseMask()))
|
|
return false;
|
|
|
|
Relocate(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetOrientation());
|
|
|
|
if (!IsPositionValid())
|
|
{
|
|
LOG_ERROR("entities.pet", "Pet {} not created base at creature. Suggested coordinates isn't valid (X: {} Y: {})",
|
|
GetGUID().ToString(), GetPositionX(), GetPositionY());
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
if (!cinfo)
|
|
{
|
|
LOG_ERROR("entities.pet", "CreateBaseAtCreature() failed, creatureInfo is missing!");
|
|
return false;
|
|
}
|
|
|
|
SetDisplayId(creature->GetDisplayId());
|
|
|
|
UpdatePositionData();
|
|
|
|
if (CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cinfo->family))
|
|
SetName(cFamily->Name[sWorld->GetDefaultDbcLocale()]);
|
|
else
|
|
SetName(creature->GetNameForLocaleIdx(sObjectMgr->GetDBCLocaleIndex()));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Pet::CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner)
|
|
{
|
|
if (!CreateBaseAtTamed(cinfo, owner->GetMap(), owner->GetPhaseMask()))
|
|
return false;
|
|
|
|
if (CreatureFamilyEntry const* cFamily = sCreatureFamilyStore.LookupEntry(cinfo->family))
|
|
SetName(cFamily->Name[sWorld->GetDefaultDbcLocale()]);
|
|
|
|
Relocate(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ(), owner->GetOrientation());
|
|
|
|
UpdatePositionData();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Pet::CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map, uint32 phaseMask)
|
|
{
|
|
LOG_DEBUG("entities.pet", "Pet::CreateBaseForTamed");
|
|
ObjectGuid::LowType guid = map->GenerateLowGuid<HighGuid::Pet>();
|
|
uint32 pet_number = sObjectMgr->GeneratePetNumber();
|
|
if (!Create(guid, map, phaseMask, cinfo->Entry, pet_number))
|
|
return false;
|
|
|
|
SetMaxPower(POWER_HAPPINESS, GetCreatePowers(POWER_HAPPINESS));
|
|
SetPower(POWER_HAPPINESS, 166500);
|
|
setPowerType(POWER_FOCUS);
|
|
SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, 0);
|
|
SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0);
|
|
SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(GetLevel() + 1)* sWorld->getRate(RATE_XP_PET_NEXT_LEVEL)));
|
|
ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
|
|
|
|
if (cinfo->type == CREATURE_TYPE_BEAST)
|
|
{
|
|
SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100);
|
|
SetSheath(SHEATH_STATE_MELEE);
|
|
SetByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// @todo: Move stat mods code to pet passive auras
|
|
bool Guardian::InitStatsForLevel(uint8 petlevel)
|
|
{
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
ASSERT(cinfo);
|
|
|
|
SetLevel(petlevel);
|
|
SetCanModifyStats(true);
|
|
|
|
Unit* owner = GetOwner();
|
|
if (!owner) // just to be sure, asynchronous now
|
|
{
|
|
DespawnOrUnsummon(1000);
|
|
return false;
|
|
}
|
|
|
|
//Determine pet type
|
|
PetType petType = MAX_PET_TYPE;
|
|
if (owner->GetTypeId() == TYPEID_PLAYER)
|
|
{
|
|
sScriptMgr->OnBeforeGuardianInitStatsForLevel(owner->ToPlayer(), this, cinfo, petType);
|
|
|
|
if (IsPet())
|
|
{
|
|
if (petType == MAX_PET_TYPE)
|
|
{
|
|
// The petType was not overwritten by the hook, continue with default initialization
|
|
if (owner->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_PET) ||
|
|
owner->IsClass(CLASS_SHAMAN, CLASS_CONTEXT_PET) || // Fire Elemental
|
|
owner->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_PET) || // Risen Ghoul
|
|
owner->IsClass(CLASS_MAGE, CLASS_CONTEXT_PET)) // Water Elemental with glyph
|
|
petType = SUMMON_PET;
|
|
else if (owner->IsClass(CLASS_HUNTER, CLASS_CONTEXT_PET))
|
|
{
|
|
petType = HUNTER_PET;
|
|
}
|
|
}
|
|
|
|
if (petType == HUNTER_PET)
|
|
m_unitTypeMask |= UNIT_MASK_HUNTER_PET;
|
|
else if (petType != SUMMON_PET)
|
|
LOG_ERROR("entities.pet", "Unknown type pet {} is summoned by player class {}", GetEntry(), owner->getClass());
|
|
}
|
|
|
|
if (petType == HUNTER_PET || petType == SUMMON_PET)
|
|
{
|
|
SetSpeed(MOVE_RUN, 1.15f);
|
|
}
|
|
}
|
|
|
|
uint32 creature_ID = (petType == HUNTER_PET) ? 1 : cinfo->Entry;
|
|
|
|
if (petType == HUNTER_PET)
|
|
{
|
|
SetMeleeDamageSchool(SPELL_SCHOOL_NORMAL);
|
|
}
|
|
else
|
|
{
|
|
SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
|
|
}
|
|
|
|
SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(petlevel * 50));
|
|
|
|
uint32 attackTime = BASE_ATTACK_TIME;
|
|
if (!owner->IsClass(CLASS_HUNTER, CLASS_CONTEXT_PET) && cinfo->BaseAttackTime >= 1000)
|
|
attackTime = cinfo->BaseAttackTime;
|
|
|
|
SetAttackTime(BASE_ATTACK, attackTime);
|
|
SetAttackTime(OFF_ATTACK, attackTime);
|
|
SetAttackTime(RANGED_ATTACK, BASE_ATTACK_TIME);
|
|
|
|
SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
|
|
|
|
// scale
|
|
SetObjectScale(GetNativeObjectScale());
|
|
|
|
// Resistance
|
|
// xinef: hunter pets should not inherit template resistances
|
|
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
|
|
{
|
|
// Default scale value of 1 to use if Pet.RankMod.Health = 0
|
|
float factorHealth = 1;
|
|
// If config is set to allow pets to use health modifiers, apply it to creatures with a DB entry
|
|
// Pet.RankMod.Health = 1 use the factor value based on the rank of the pet, most pets have a rank of 0 and so use
|
|
// the Elite rank which is set as the default in Creature::_GetHealthMod(int32 Rank)
|
|
if (sWorld->getBoolConfig(CONFIG_ALLOWS_RANK_MOD_FOR_PET_HEALTH))
|
|
{
|
|
factorHealth *= _GetHealthMod(cinfo->rank);
|
|
}
|
|
|
|
SetCreateHealth(pInfo->health*factorHealth);
|
|
SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)pInfo->health);
|
|
if (petType != HUNTER_PET) //hunter pet use focus
|
|
{
|
|
SetCreateMana(pInfo->mana);
|
|
SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)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);
|
|
// xinef: multiply base values by creature_template factors!
|
|
float factorHealth = owner->GetTypeId() == TYPEID_PLAYER ? std::min(1.0f, cinfo->ModHealth) : cinfo->ModHealth;
|
|
float factorMana = owner->GetTypeId() == TYPEID_PLAYER ? std::min(1.0f, cinfo->ModMana) : cinfo->ModMana;
|
|
|
|
if (sWorld->getBoolConfig(CONFIG_ALLOWS_RANK_MOD_FOR_PET_HEALTH))
|
|
{
|
|
factorHealth *= _GetHealthMod(cinfo->rank);
|
|
}
|
|
|
|
SetCreateHealth(std::max<uint32>(1, stats->BaseHealth[cinfo->expansion]*factorHealth));
|
|
SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, GetCreateHealth());
|
|
SetCreateMana(stats->BaseMana * factorMana);
|
|
SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, GetCreateMana());
|
|
|
|
// xinef: added some multipliers so debuffs can affect pets in any way...
|
|
SetCreateStat(STAT_STRENGTH, 22);
|
|
SetCreateStat(STAT_AGILITY, 22);
|
|
SetCreateStat(STAT_STAMINA, 25);
|
|
SetCreateStat(STAT_INTELLECT, 28);
|
|
SetCreateStat(STAT_SPIRIT, 27);
|
|
}
|
|
|
|
switch (petType)
|
|
{
|
|
case HUNTER_PET:
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
|
|
SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(petlevel)* sWorld->getRate(RATE_XP_PET_NEXT_LEVEL)));
|
|
break;
|
|
}
|
|
case SUMMON_PET:
|
|
{
|
|
if (pInfo)
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(pInfo->min_dmg));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(pInfo->max_dmg));
|
|
}
|
|
else
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
|
|
}
|
|
|
|
switch(GetEntry())
|
|
{
|
|
case NPC_FELGUARD:
|
|
{
|
|
// xinef: Glyph of Felguard, so ugly im crying... no appropriate spell
|
|
if (AuraEffect* aurEff = owner->GetAuraEffectDummy(SPELL_GLYPH_OF_FELGUARD))
|
|
{
|
|
HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, aurEff->GetAmount(), true);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NPC_VOIDWALKER:
|
|
{
|
|
if (AuraEffect* aurEff = owner->GetAuraEffectDummy(SPELL_GLYPH_OF_VOIDWALKER))
|
|
{
|
|
HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_PCT, aurEff->GetAmount(), true);
|
|
}
|
|
break;
|
|
}
|
|
case NPC_WATER_ELEMENTAL_PERM:
|
|
{
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_01, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_02, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_03, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_04, this);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
switch (GetEntry())
|
|
{
|
|
case NPC_FIRE_ELEMENTAL:
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 3.5f - petlevel));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 3.5f + petlevel));
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_FIRE_ELEMENTAL_SCALING_01, this);
|
|
AddAura(SPELL_FIRE_ELEMENTAL_SCALING_02, this);
|
|
AddAura(SPELL_FIRE_ELEMENTAL_SCALING_03, this);
|
|
AddAura(SPELL_FIRE_ELEMENTAL_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_EARTH_ELEMENTAL:
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 2.0f - petlevel));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 2.0f + petlevel));
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_EARTH_ELEMENTAL_SCALING_01, this);
|
|
AddAura(SPELL_EARTH_ELEMENTAL_SCALING_02, this);
|
|
AddAura(SPELL_EARTH_ELEMENTAL_SCALING_03, this);
|
|
AddAura(SPELL_EARTH_ELEMENTAL_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_INFERNAL:
|
|
{
|
|
if (pInfo)
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(pInfo->min_dmg));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(pInfo->max_dmg));
|
|
}
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_WARLOCK_PET_SCALING_05, this);
|
|
AddAura(SPELL_INFERNAL_SCALING_01, this);
|
|
AddAura(SPELL_INFERNAL_SCALING_02, this);
|
|
AddAura(SPELL_INFERNAL_SCALING_03, this);
|
|
AddAura(SPELL_INFERNAL_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_DOOMGUARD:
|
|
{
|
|
float highAmt = petlevel / 11.0f;
|
|
float lowAmt = petlevel / 12.0f;
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, lowAmt * lowAmt * lowAmt);
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, highAmt * highAmt * highAmt);
|
|
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_WARLOCK_PET_SCALING_01, this);
|
|
AddAura(SPELL_WARLOCK_PET_SCALING_02, this);
|
|
AddAura(SPELL_WARLOCK_PET_SCALING_03, this);
|
|
AddAura(SPELL_WARLOCK_PET_SCALING_04, this);
|
|
AddAura(SPELL_WARLOCK_PET_SCALING_05, this);
|
|
break;
|
|
}
|
|
case NPC_WATER_ELEMENTAL_TEMP:
|
|
{
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_01, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_02, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_03, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_TREANT: //force of nature
|
|
{
|
|
if (!pInfo)
|
|
SetCreateHealth(30 + 30 * petlevel);
|
|
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 2.5f - petlevel));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 2.5f + petlevel));
|
|
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_TREANT_SCALING_01, this);
|
|
AddAura(SPELL_TREANT_SCALING_02, this);
|
|
AddAura(SPELL_TREANT_SCALING_03, this);
|
|
AddAura(SPELL_TREANT_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_SHADOWFIEND:
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 2.5f - petlevel));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 2.5f + petlevel));
|
|
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_SHADOWFIEND_SCALING_01, this);
|
|
AddAura(SPELL_SHADOWFIEND_SCALING_02, this);
|
|
AddAura(SPELL_SHADOWFIEND_SCALING_03, this);
|
|
AddAura(SPELL_SHADOWFIEND_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_FERAL_SPIRIT:
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 4.0f - petlevel));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 4.0f + petlevel));
|
|
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_FERAL_SPIRIT_SPIRIT_HUNT, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_FERAL_SPIRIT_SCALING_01, this);
|
|
AddAura(SPELL_FERAL_SPIRIT_SCALING_02, this);
|
|
AddAura(SPELL_FERAL_SPIRIT_SCALING_03, this);
|
|
|
|
if (owner->getRace() == RACE_ORC)
|
|
{
|
|
CastSpell(this, SPELL_ORC_RACIAL_COMMAND_SHAMAN, true, nullptr, nullptr, owner->GetGUID());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NPC_MIRROR_IMAGE: // Mirror Image
|
|
{
|
|
SetDisplayId(owner->GetDisplayId());
|
|
if (!pInfo)
|
|
{
|
|
SetCreateMana(28 + 30 * petlevel);
|
|
SetCreateHealth(28 + 10 * petlevel);
|
|
}
|
|
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_01, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_02, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_03, this);
|
|
AddAura(SPELL_MAGE_PET_SCALING_04, this);
|
|
break;
|
|
}
|
|
case NPC_EBON_GARGOYLE: // Ebon Gargoyle
|
|
{
|
|
if (!pInfo)
|
|
{
|
|
SetCreateMana(28 + 10 * petlevel);
|
|
SetCreateHealth(28 + 30 * petlevel);
|
|
}
|
|
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_DK_PET_SCALING_01, this);
|
|
AddAura(SPELL_DK_PET_SCALING_02, this);
|
|
AddAura(SPELL_DK_PET_SCALING_03, this);
|
|
break;
|
|
}
|
|
case NPC_BLOODWORM:
|
|
{
|
|
// Xinef: Hit / Expertise scaling
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
SetCreateHealth(4 * petlevel);
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - 30 - (petlevel / 4) + owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.006f));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel - 30 + (petlevel / 4) + owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.006f));
|
|
SetReactState(REACT_DEFENSIVE);
|
|
break;
|
|
}
|
|
case NPC_ARMY_OF_THE_DEAD:
|
|
{
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
AddAura(SPELL_DK_PET_SCALING_01, this);
|
|
AddAura(SPELL_DK_PET_SCALING_02, this);
|
|
AddAura(SPELL_DK_PET_SCALING_03, this);
|
|
AddAura(SPELL_PET_AVOIDANCE, this);
|
|
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
|
|
break;
|
|
}
|
|
case NPC_VENOMOUS_SNAKE:
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel * 0.7 - 38));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel * 0.8 - 40));
|
|
break;
|
|
case NPC_VIPER:
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(1.3 * petlevel - 64));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(1.5 * petlevel - 68));
|
|
break;
|
|
case NPC_GENERIC_IMP:
|
|
case NPC_GENERIC_VOIDWALKER:
|
|
{
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(petlevel - (petlevel / 4)));
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(petlevel + (petlevel / 4)));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Can be summon and guardian
|
|
if (GetEntry() == NPC_RISEN_GHOUL)
|
|
{
|
|
// 100% energy after summon
|
|
SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
|
|
|
|
// xinef: fixes orc death knight command racial
|
|
if (owner->getRace() == RACE_ORC)
|
|
CastSpell(this, SPELL_ORC_RACIAL_COMMAND_DK, true, nullptr, nullptr, owner->GetGUID());
|
|
|
|
// Avoidance, Night of the Dead
|
|
if (Aura* aur = AddAura(SPELL_NIGHT_OF_THE_DEAD_AVOIDANCE, this))
|
|
if (AuraEffect* aurEff = owner->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_DEATHKNIGHT, 2718, 0))
|
|
if (aur->GetEffect(0))
|
|
aur->GetEffect(0)->SetAmount(-aurEff->GetSpellInfo()->Effects[EFFECT_2].CalcValue());
|
|
|
|
AddAura(SPELL_HUNTER_PET_SCALING_04, this);
|
|
// Added to perm ghoul by default
|
|
if (!IsPet())
|
|
{
|
|
AddAura(SPELL_DK_PET_SCALING_01, this);
|
|
AddAura(SPELL_DK_PET_SCALING_02, this);
|
|
}
|
|
}
|
|
|
|
sScriptMgr->OnInitStatsForLevel(this, petlevel);
|
|
|
|
UpdateAllStats();
|
|
|
|
SetFullHealth();
|
|
SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
|
|
|
|
if (owner->GetTypeId() == TYPEID_PLAYER)
|
|
sScriptMgr->OnAfterGuardianInitStatsForLevel(owner->ToPlayer(), this);
|
|
|
|
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;
|
|
}
|
|
|
|
uint32 Pet::GetCurrentFoodBenefitLevel(uint32 itemlevel) const
|
|
{
|
|
// -5 or greater food level
|
|
if (GetLevel() <= itemlevel + 5) //possible to feed level 60 pet with level 55 level food for full effect
|
|
return 35000;
|
|
// -10..-6
|
|
else if (GetLevel() <= itemlevel + 10) //pure guess, but sounds good
|
|
return 17000;
|
|
// -14..-11
|
|
else if (GetLevel() <= itemlevel + 14) //level 55 food gets green on 70, makes sense to me
|
|
return 8000;
|
|
// -15 or less
|
|
else
|
|
return 0; //food too low level
|
|
}
|
|
|
|
void Pet::_LoadSpellCooldowns(PreparedQueryResult result)
|
|
{
|
|
m_CreatureSpellCooldowns.clear();
|
|
|
|
if (result)
|
|
{
|
|
time_t curTime = GameTime::GetGameTime().count();
|
|
|
|
PacketCooldowns cooldowns;
|
|
WorldPacket data;
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32 spell_id = fields[0].Get<uint32>();
|
|
uint16 category = fields[1].Get<uint16>();
|
|
time_t db_time = time_t(fields[2].Get<uint32>());
|
|
|
|
if (!sSpellMgr->GetSpellInfo(spell_id))
|
|
{
|
|
LOG_ERROR("entities.pet", "Pet {} have unknown spell {} in `pet_spell_cooldown`, skipping.", m_charmInfo->GetPetNumber(), spell_id);
|
|
continue;
|
|
}
|
|
|
|
// skip outdated cooldown
|
|
if (db_time <= curTime)
|
|
continue;
|
|
|
|
uint32 cooldown = (db_time - curTime) * IN_MILLISECONDS;
|
|
cooldowns[spell_id] = cooldown;
|
|
_AddCreatureSpellCooldown(spell_id, category, cooldown);
|
|
|
|
LOG_DEBUG("entities.pet", "Pet (Number: {}) spell {} cooldown loaded ({} secs).", m_charmInfo->GetPetNumber(), spell_id, uint32(db_time - curTime));
|
|
} while (result->NextRow());
|
|
|
|
if (!cooldowns.empty() && GetOwner())
|
|
{
|
|
BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, cooldowns);
|
|
GetOwner()->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Pet::_SaveSpellCooldowns(CharacterDatabaseTransaction trans)
|
|
{
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
trans->Append(stmt);
|
|
|
|
time_t curTime = GameTime::GetGameTime().count();
|
|
uint32 curMSTime = GameTime::GetGameTimeMS().count();
|
|
uint32 infTime = curMSTime + infinityCooldownDelayCheck;
|
|
|
|
// remove oudated and save active
|
|
CreatureSpellCooldowns::iterator itr, itr2;
|
|
for (itr = m_CreatureSpellCooldowns.begin(); itr != m_CreatureSpellCooldowns.end();)
|
|
{
|
|
itr2 = itr;
|
|
++itr;
|
|
|
|
if (itr2->second.end <= curMSTime + 1000)
|
|
{
|
|
m_CreatureSpellCooldowns.erase(itr2);
|
|
}
|
|
else if (itr2->second.end <= infTime)
|
|
{
|
|
uint32 cooldown = ((itr2->second.end - curMSTime) / IN_MILLISECONDS) + curTime;
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL_COOLDOWN);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
stmt->SetData(1, itr2->first);
|
|
stmt->SetData(2, itr2->second.category);
|
|
stmt->SetData(3, cooldown);
|
|
trans->Append(stmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Pet::_LoadSpells(PreparedQueryResult result)
|
|
{
|
|
if (result)
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
addSpell(fields[0].Get<uint32>(), ActiveStates(fields[1].Get<uint8>()), 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->SetData(0, m_charmInfo->GetPetNumber());
|
|
stmt->SetData(1, itr->first);
|
|
trans->Append(stmt);
|
|
|
|
m_spells.erase(itr);
|
|
continue;
|
|
case PETSPELL_CHANGED:
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_BY_SPELL);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
stmt->SetData(1, itr->first);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
stmt->SetData(1, itr->first);
|
|
stmt->SetData(2, itr->second.active);
|
|
trans->Append(stmt);
|
|
|
|
break;
|
|
case PETSPELL_NEW:
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL);
|
|
stmt->SetData(0, m_charmInfo->GetPetNumber());
|
|
stmt->SetData(1, itr->first);
|
|
stmt->SetData(2, itr->second.active);
|
|
trans->Append(stmt);
|
|
break;
|
|
case PETSPELL_UNCHANGED:
|
|
continue;
|
|
}
|
|
itr->second.state = PETSPELL_UNCHANGED;
|
|
}
|
|
}
|
|
|
|
void Pet::_LoadAuras(PreparedQueryResult result, uint32 timediff)
|
|
{
|
|
LOG_DEBUG("entities.pet", "Loading auras for pet {}", GetGUID().ToString());
|
|
|
|
if (result)
|
|
{
|
|
do
|
|
{
|
|
int32 damage[3];
|
|
int32 baseDamage[3];
|
|
Field* fields = result->Fetch();
|
|
ObjectGuid caster_guid = ObjectGuid(fields[0].Get<uint64>());
|
|
// nullptr guid stored - pet is the caster of the spell - see Pet::_SaveAuras
|
|
if (!caster_guid)
|
|
caster_guid = GetGUID();
|
|
uint32 spellid = fields[1].Get<uint32>();
|
|
uint8 effmask = fields[2].Get<uint8>();
|
|
uint8 recalculatemask = fields[3].Get<uint8>();
|
|
uint8 stackcount = fields[4].Get<uint8>();
|
|
damage[0] = fields[5].Get<int32>();
|
|
damage[1] = fields[6].Get<int32>();
|
|
damage[2] = fields[7].Get<int32>();
|
|
baseDamage[0] = fields[8].Get<int32>();
|
|
baseDamage[1] = fields[9].Get<int32>();
|
|
baseDamage[2] = fields[10].Get<int32>();
|
|
int32 maxduration = fields[11].Get<int32>();
|
|
int32 remaintime = fields[12].Get<int32>();
|
|
uint8 remaincharges = fields[13].Get<uint8>();
|
|
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
|
|
if (!spellInfo)
|
|
{
|
|
LOG_ERROR("entities.pet", "Unknown aura (spellid {}), ignore.", spellid);
|
|
continue;
|
|
}
|
|
|
|
// avoid higher level auras if any, and adjust
|
|
SpellInfo const* scaledSpellInfo = spellInfo->GetAuraRankForLevel(GetLevel());
|
|
if (scaledSpellInfo != spellInfo)
|
|
spellInfo = scaledSpellInfo;
|
|
|
|
// again after level check
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
// negative effects should continue counting down after logout
|
|
if (remaintime != -1 && (!spellInfo->IsPositive() || spellInfo->HasAttribute(SPELL_ATTR4_AURA_EXPIRES_OFFLINE)))
|
|
{
|
|
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();
|
|
LOG_DEBUG("entities.pet", "Added aura spellid {}, effectmask {}", spellInfo->Id, effmask);
|
|
}
|
|
} while (result->NextRow());
|
|
}
|
|
}
|
|
|
|
void Pet::_SaveAuras(CharacterDatabaseTransaction trans)
|
|
{
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_AURAS);
|
|
stmt->SetData(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;
|
|
if (aura->GetDuration() < 60 * IN_MILLISECONDS)
|
|
continue;
|
|
|
|
// dont save infinite negative auras! (lavas, transformations etc)
|
|
if (aura->IsPermanent() && !aura->GetSpellInfo()->IsPositive())
|
|
continue;
|
|
|
|
// pussywizard: don't save auras that cannot be cancelled (needed for ICC buff on pets/summons)
|
|
if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_AURA_CANCEL))
|
|
continue;
|
|
|
|
// xinef: don't save hidden auras
|
|
if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_NO_AURA_ICON))
|
|
continue;
|
|
|
|
// Xinef: Dont save auras with model change
|
|
if (aura->GetSpellInfo()->HasAura(SPELL_AURA_TRANSFORM))
|
|
continue;
|
|
|
|
// xinef: don's save auras with interrupt flags on map change
|
|
if (aura->GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_CHANGE_MAP)
|
|
continue;
|
|
|
|
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;
|
|
|
|
CharacterDatabasePreparedStatement* stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_AURA);
|
|
stmt2->SetData(index++, m_charmInfo->GetPetNumber());
|
|
stmt2->SetData(index++, casterGUID.GetRawValue());
|
|
stmt2->SetData(index++, itr->second->GetId());
|
|
stmt2->SetData(index++, effMask);
|
|
stmt2->SetData(index++, recalculateMask);
|
|
stmt2->SetData(index++, itr->second->GetStackAmount());
|
|
stmt2->SetData(index++, damage[0]);
|
|
stmt2->SetData(index++, damage[1]);
|
|
stmt2->SetData(index++, damage[2]);
|
|
stmt2->SetData(index++, baseDamage[0]);
|
|
stmt2->SetData(index++, baseDamage[1]);
|
|
stmt2->SetData(index++, baseDamage[2]);
|
|
stmt2->SetData(index++, itr->second->GetMaxDuration());
|
|
stmt2->SetData(index++, itr->second->GetDuration());
|
|
stmt2->SetData(index++, itr->second->GetCharges());
|
|
trans->Append(stmt2);
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
LOG_ERROR("entities.pet", "Pet::addSpell: Non-existed in SpellStore spell #{} request, deleting for all pets in `pet_spell`.", spellId);
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_PET_SPELL);
|
|
stmt->SetData(0, spellId);
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
else
|
|
LOG_ERROR("entities.pet", "Pet::addSpell: Non-existed in SpellStore spell #{} request.", spellId);
|
|
|
|
return false;
|
|
}
|
|
|
|
auto const& itr = m_spells.find(spellId);
|
|
if (itr != m_spells.end())
|
|
{
|
|
if (itr->second.state == PETSPELL_REMOVED)
|
|
{
|
|
m_spells.erase(itr);
|
|
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;
|
|
|
|
ToggleAutocast(spellInfo, active == ACT_ENABLED);
|
|
return false;
|
|
}
|
|
else
|
|
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_DISABLED;
|
|
else
|
|
newspell.active = ACT_PASSIVE;
|
|
}
|
|
else
|
|
newspell.active = active;
|
|
|
|
// talent: unlearn all other talent ranks (high and low)
|
|
if (TalentSpellPos const* talentPos = GetTalentSpellPos(spellId))
|
|
{
|
|
if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id))
|
|
{
|
|
for (uint32 rankSpellId : talentInfo->RankID)
|
|
{
|
|
// skip learning spell and no rank spell case
|
|
if (!rankSpellId || rankSpellId == spellId)
|
|
continue;
|
|
|
|
// skip unknown ranks
|
|
if (!HasSpell(rankSpellId))
|
|
continue;
|
|
removeSpell(rankSpellId, false, false);
|
|
}
|
|
}
|
|
}
|
|
else if (spellInfo->IsRanked())
|
|
{
|
|
for (auto const& [spellID, petSpell] : m_spells)
|
|
{
|
|
if (petSpell.state == PETSPELL_REMOVED)
|
|
continue;
|
|
|
|
SpellInfo const* oldRankSpellInfo = sSpellMgr->GetSpellInfo(spellID);
|
|
|
|
if (!oldRankSpellInfo)
|
|
continue;
|
|
|
|
if (spellInfo->IsDifferentRankOf(oldRankSpellInfo))
|
|
{
|
|
// replace by new high rank
|
|
if (spellInfo->IsHighRankOf(oldRankSpellInfo))
|
|
{
|
|
newspell.active = petSpell.active;
|
|
|
|
if (newspell.active == ACT_ENABLED)
|
|
ToggleAutocast(oldRankSpellInfo, false);
|
|
|
|
unlearnSpell(spellID, false, false);
|
|
break;
|
|
}
|
|
// ignore new lesser rank
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_spells[spellId] = newspell;
|
|
|
|
if (spellInfo->IsPassive())
|
|
CastSpell(this, spellId, true);
|
|
else
|
|
m_charmInfo->AddSpellToActionBar(spellInfo);
|
|
|
|
// unapply aura stats if dont meet requirements
|
|
if (Aura* aura = GetAura(spellId))
|
|
{
|
|
if (aura->GetSpellInfo()->CasterAuraState == AURA_STATE_HEALTHLESS_35_PERCENT ||
|
|
aura->GetSpellInfo()->CasterAuraState == AURA_STATE_HEALTH_ABOVE_75_PERCENT ||
|
|
aura->GetSpellInfo()->CasterAuraState == AURA_STATE_HEALTHLESS_20_PERCENT )
|
|
if (!HasAuraState((AuraStateType)aura->GetSpellInfo()->CasterAuraState))
|
|
{
|
|
aura->HandleAllEffects(aura->GetApplicationOfTarget(GetGUID()), AURA_EFFECT_HANDLE_REAL, false);
|
|
}
|
|
}
|
|
|
|
ToggleAutocast(spellInfo, (newspell.active == ACT_ENABLED));
|
|
|
|
uint32 talentCost = 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)
|
|
{
|
|
WorldPackets::Pet::PetLearnedSpell packet;
|
|
packet.SpellID = spell_id;
|
|
m_owner->SendDirectMessage(packet.Write());
|
|
m_owner->PetSpellInitialize();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Pet::InitLevelupSpellsForLevel()
|
|
{
|
|
uint8 level = GetLevel();
|
|
|
|
if (PetLevelupSpellSet const* levelupSpells = GetCreatureTemplate()->family ? sSpellMgr->GetPetLevelupSpellList(GetCreatureTemplate()->family) : nullptr)
|
|
{
|
|
// PetLevelupSpellSet ordered by levels, process in reversed order
|
|
for (PetLevelupSpellSet::const_reverse_iterator itr = levelupSpells->rbegin(); itr != levelupSpells->rend(); ++itr)
|
|
{
|
|
// will called first if level down
|
|
if (itr->first > level && sScriptMgr->CanUnlearnSpellSet(this, itr->first, itr->second))
|
|
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 (uint32 spellId : defSpells->spellid)
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
// will called first if level down
|
|
if (spellInfo->SpellLevel > level && sScriptMgr->CanUnlearnSpellDefault(this, spellInfo))
|
|
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)
|
|
{
|
|
WorldPackets::Pet::PetUnlearnedSpell packet;
|
|
packet.SpellID = spell_id;
|
|
m_owner->SendDirectMessage(packet.Write());
|
|
}
|
|
|
|
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 = 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)
|
|
{
|
|
// need update action bar for last removed rank
|
|
if (Unit* owner = GetOwner())
|
|
if (owner->GetTypeId() == TYPEID_PLAYER)
|
|
owner->ToPlayer()->PetSpellInitialize();
|
|
}
|
|
}
|
|
|
|
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 (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(ab->GetAction()))
|
|
ToggleAutocast(spellInfo, ab->GetType() == ACT_ENABLED);
|
|
}
|
|
}
|
|
|
|
void Pet::InitPetCreateSpells()
|
|
{
|
|
m_charmInfo->InitPetActionBar();
|
|
m_spells.clear();
|
|
|
|
LearnPetPassives();
|
|
InitLevelupSpellsForLevel();
|
|
|
|
CastPetAuras(false);
|
|
}
|
|
|
|
bool Pet::resetTalents()
|
|
{
|
|
Unit* owner = GetOwner();
|
|
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
|
|
return false;
|
|
|
|
if (!sScriptMgr->CanResetTalents(this))
|
|
return false;
|
|
|
|
// not need after this call
|
|
if (owner->ToPlayer()->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
|
|
owner->ToPlayer()->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;
|
|
|
|
Player* player = owner->ToPlayer();
|
|
|
|
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->TalentTab);
|
|
if (!talentTabInfo)
|
|
continue;
|
|
|
|
// unlearn only talents for pets family talent type
|
|
if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask))
|
|
continue;
|
|
|
|
for (uint32 talentSpellId : talentInfo->RankID)
|
|
{
|
|
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 == talentSpellId)
|
|
{
|
|
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->ToPlayer()->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
|
|
{
|
|
owner->ToPlayer()->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true);
|
|
}
|
|
|
|
// reset for online
|
|
if (onlinePet)
|
|
{
|
|
onlinePet->resetTalents();
|
|
}
|
|
|
|
PetStable* petStable = owner->GetPetStable();
|
|
if (!petStable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::unordered_set<uint32> petIds;
|
|
if (petStable->CurrentPet)
|
|
{
|
|
petIds.insert(petStable->CurrentPet->PetNumber);
|
|
}
|
|
|
|
for (Optional<PetStable::PetInfo> const& stabledPet : petStable->StabledPets)
|
|
{
|
|
if (stabledPet)
|
|
{
|
|
petIds.insert(stabledPet->PetNumber);
|
|
}
|
|
}
|
|
|
|
for (PetStable::PetInfo const& unslottedPet : petStable->UnslottedPets)
|
|
{
|
|
petIds.insert(unslottedPet.PetNumber);
|
|
}
|
|
|
|
// now need only reset for offline pets (all pets except online case)
|
|
if (onlinePet)
|
|
{
|
|
petIds.erase(onlinePet->GetCharmInfo()->GetPetNumber());
|
|
}
|
|
|
|
// no offline pets
|
|
if (petIds.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool need_comma = false;
|
|
std::ostringstream ss;
|
|
ss << "DELETE FROM pet_spell WHERE guid IN (";
|
|
|
|
for (uint32 id : petIds)
|
|
{
|
|
if (need_comma)
|
|
{
|
|
ss << ',';
|
|
}
|
|
|
|
ss << id;
|
|
|
|
need_comma = true;
|
|
}
|
|
|
|
ss << ") AND spell IN (";
|
|
|
|
need_comma = false;
|
|
for (uint32 spell : sPetTalentSpells)
|
|
{
|
|
if (need_comma)
|
|
{
|
|
ss << ',';
|
|
}
|
|
|
|
ss << spell;
|
|
|
|
need_comma = true;
|
|
}
|
|
|
|
ss << ')';
|
|
|
|
CharacterDatabase.Execute(ss.str().c_str());
|
|
}
|
|
|
|
void Pet::InitTalentForLevel()
|
|
{
|
|
uint8 level = GetLevel();
|
|
uint32 talentPointsForLevel = GetMaxTalentPointsForLevel(level);
|
|
|
|
Unit* owner = GetOwner();
|
|
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
// 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)
|
|
owner->ToPlayer()->SendTalentsInfoData(true);
|
|
}
|
|
|
|
uint8 Pet::GetMaxTalentPointsForLevel(uint8 level)
|
|
{
|
|
uint8 points = (level >= 20) ? ((level - 16) / 4) : 0;
|
|
// Mod points from owner SPELL_AURA_MOD_PET_TALENT_POINTS
|
|
if (Unit* owner = GetOwner())
|
|
points += owner->GetTotalAuraModifier(SPELL_AURA_MOD_PET_TALENT_POINTS);
|
|
|
|
sScriptMgr->OnCalculateMaxTalentPointsForLevel(this, level, points);
|
|
|
|
return uint8(points * sWorld->getRate(RATE_TALENT_PET));
|
|
}
|
|
|
|
void Pet::ToggleAutocast(SpellInfo const* spellInfo, bool apply)
|
|
{
|
|
ASSERT(spellInfo);
|
|
|
|
if (!spellInfo->IsAutocastable())
|
|
return;
|
|
|
|
PetSpellMap::iterator itr = m_spells.find(spellInfo->Id);
|
|
if (itr == m_spells.end())
|
|
return;
|
|
|
|
auto autospellItr = std::find(m_autospells.begin(), m_autospells.end(), spellInfo->Id);
|
|
|
|
if (apply)
|
|
{
|
|
if (autospellItr == m_autospells.end())
|
|
{
|
|
m_autospells.push_back(spellInfo->Id);
|
|
|
|
if (itr->second.active != ACT_ENABLED)
|
|
{
|
|
itr->second.active = ACT_ENABLED;
|
|
if (itr->second.state != PETSPELL_NEW)
|
|
itr->second.state = PETSPELL_CHANGED;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (autospellItr != m_autospells.end())
|
|
{
|
|
m_autospells.erase(autospellItr);
|
|
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:
|
|
if (owner->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_PET))
|
|
return GetCreatureTemplate()->type == CREATURE_TYPE_DEMON;
|
|
else if (owner->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_PET))
|
|
return GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD;
|
|
else if (owner->IsClass(CLASS_MAGE, CLASS_CONTEXT_PET))
|
|
return GetEntry() == 37994;
|
|
else
|
|
return false;
|
|
case HUNTER_PET:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 Entry, uint32 pet_number)
|
|
{
|
|
ASSERT(map);
|
|
SetMap(map);
|
|
|
|
SetPhaseMask(phaseMask, false);
|
|
|
|
Object::_Create(guidlow, pet_number, 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
|
|
SetUnitFlag2(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 general hunter pets skill 270
|
|
// Passive 01~10, Passive 00 (20782, not used), Ferocious Inspiration (34457)
|
|
// Scale 01~03 (34902~34904, bonus from owner, not used)
|
|
for (uint32 spellId : petStore->second)
|
|
addSpell(spellId, ACT_DECIDE, PETSPELL_NEW, PETSPELL_FAMILY);
|
|
}
|
|
}
|
|
|
|
void Pet::CastPetAuras(bool current)
|
|
{
|
|
Unit* owner = GetOwner();
|
|
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
if (!IsPermanentPetFor(owner->ToPlayer()))
|
|
return;
|
|
|
|
for (PetAuraSet::const_iterator 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::learnSpellHighRank(uint32 spellid)
|
|
{
|
|
learnSpell(spellid);
|
|
|
|
if (uint32 next = sSpellMgr->GetNextSpellInChain(spellid))
|
|
learnSpellHighRank(next);
|
|
}
|
|
|
|
void Pet::SynchronizeLevelWithOwner()
|
|
{
|
|
Unit* owner = GetOwner();
|
|
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
switch (getPetType())
|
|
{
|
|
// always same level
|
|
case SUMMON_PET:
|
|
GivePetLevel(owner->GetLevel());
|
|
break;
|
|
// can't be greater owner level
|
|
case HUNTER_PET:
|
|
if (GetLevel() > owner->GetLevel())
|
|
GivePetLevel(owner->GetLevel());
|
|
else if (GetLevel() + 5 < owner->GetLevel())
|
|
GivePetLevel(owner->GetLevel() - 5);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Pet::SetDisplayId(uint32 modelId, float displayScale /*= 1.f*/)
|
|
{
|
|
Guardian::SetDisplayId(modelId, displayScale);
|
|
|
|
if (!isControlled())
|
|
return;
|
|
|
|
if (Unit* owner = GetOwner())
|
|
if (Player* player = owner->ToPlayer())
|
|
if (player->GetGroup())
|
|
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MODEL_ID);
|
|
}
|
|
|
|
void Pet::CastWhenWillAvailable(uint32 spellid, Unit* spellTarget, ObjectGuid oldTarget, bool spellIsPositive)
|
|
{
|
|
if (!spellid)
|
|
return;
|
|
|
|
if (!spellTarget)
|
|
return;
|
|
|
|
m_tempspellTarget = spellTarget;
|
|
m_tempspell = spellid;
|
|
m_tempspellIsPositive = spellIsPositive;
|
|
|
|
if (!oldTarget.IsEmpty())
|
|
m_tempoldTarget = oldTarget;
|
|
}
|
|
|
|
void Pet::ClearCastWhenWillAvailable()
|
|
{
|
|
m_tempspellIsPositive = false;
|
|
m_tempspell = 0;
|
|
m_tempspellTarget = nullptr;
|
|
m_tempoldTarget = ObjectGuid::Empty;
|
|
}
|
|
|
|
void Pet::RemoveSpellCooldown(uint32 spell_id, bool update /* = false */)
|
|
{
|
|
m_CreatureSpellCooldowns.erase(spell_id);
|
|
|
|
if (update)
|
|
{
|
|
if (Player* playerOwner = GetCharmerOrOwnerPlayerOrPlayerItself())
|
|
{
|
|
WorldPacket data(SMSG_CLEAR_COOLDOWN, 4 + 8);
|
|
data << uint32(spell_id);
|
|
data << GetGUID();
|
|
playerOwner->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Pet::FillPetInfo(PetStable::PetInfo* petInfo) const
|
|
{
|
|
petInfo->PetNumber = m_charmInfo->GetPetNumber();
|
|
petInfo->CreatureId = GetEntry();
|
|
petInfo->DisplayId = GetNativeDisplayId();
|
|
petInfo->Level = GetLevel();
|
|
petInfo->Experience = GetUInt32Value(UNIT_FIELD_PETEXPERIENCE);
|
|
petInfo->ReactState = GetReactState();
|
|
petInfo->Name = GetName();
|
|
petInfo->WasRenamed = !HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED);
|
|
petInfo->Health = GetHealth();
|
|
petInfo->Mana = GetPower(POWER_MANA);
|
|
petInfo->Happiness = GetPower(POWER_HAPPINESS);
|
|
petInfo->ActionBar = GenerateActionBarData();
|
|
petInfo->LastSaveTime = GameTime::GetGameTime().count();
|
|
petInfo->CreatedBySpellId = GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
|
petInfo->Type = getPetType();
|
|
}
|
|
|
|
Player* Pet::GetOwner() const
|
|
{
|
|
return m_owner;
|
|
}
|
|
|
|
float Pet::GetNativeObjectScale() const
|
|
{
|
|
uint8 ctFamily = GetCreatureTemplate()->family;
|
|
|
|
CreatureFamilyEntry const* creatureFamily = sCreatureFamilyStore.LookupEntry(ctFamily);
|
|
if (creatureFamily && creatureFamily->minScale > 0.0f && getPetType() & HUNTER_PET)
|
|
{
|
|
float minScaleLevel = creatureFamily->minScaleLevel;
|
|
uint8 level = GetLevel();
|
|
|
|
float minLevelScaleMod = level >= minScaleLevel ? (level / minScaleLevel) : 0.0f;
|
|
float maxScaleMod = creatureFamily->maxScaleLevel - minScaleLevel;
|
|
|
|
if (minLevelScaleMod > maxScaleMod)
|
|
minLevelScaleMod = maxScaleMod;
|
|
|
|
float scaleMod = creatureFamily->maxScaleLevel != minScaleLevel ? minLevelScaleMod / maxScaleMod : 0.f;
|
|
|
|
float maxScale = creatureFamily->maxScale;
|
|
|
|
// override maxScale
|
|
switch (ctFamily)
|
|
{
|
|
case CREATURE_FAMILY_CHIMAERA:
|
|
case CREATURE_FAMILY_CORE_HOUND:
|
|
case CREATURE_FAMILY_CRAB:
|
|
case CREATURE_FAMILY_DEVILSAUR:
|
|
case CREATURE_FAMILY_NETHER_RAY:
|
|
case CREATURE_FAMILY_RHINO:
|
|
case CREATURE_FAMILY_SPIDER:
|
|
case CREATURE_FAMILY_TURTLE:
|
|
case CREATURE_FAMILY_WARP_STALKER:
|
|
case CREATURE_FAMILY_WASP:
|
|
case CREATURE_FAMILY_WIND_SERPENT:
|
|
maxScale = 1.0f;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
float scale = (maxScale - creatureFamily->minScale) * scaleMod + creatureFamily->minScale;
|
|
|
|
scale = std::min(scale, maxScale);
|
|
|
|
return scale;
|
|
}
|
|
|
|
// take value for non-hunter pets from DB
|
|
return Guardian::GetNativeObjectScale();
|
|
}
|
|
|
|
std::string Pet::GenerateActionBarData() const
|
|
{
|
|
std::ostringstream oss;
|
|
|
|
for (uint32 i = ACTION_BAR_INDEX_START; i < ACTION_BAR_INDEX_END; ++i)
|
|
{
|
|
oss << uint32(m_charmInfo->GetActionBarEntry(i)->GetType()) << ' '
|
|
<< uint32(m_charmInfo->GetActionBarEntry(i)->GetAction()) << ' ';
|
|
}
|
|
|
|
return oss.str();
|
|
}
|
|
|
|
std::string Pet::GetDebugInfo() const
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << Guardian::GetDebugInfo() << "\n"
|
|
<< std::boolalpha
|
|
<< "PetType: " << std::to_string(getPetType()) << " "
|
|
<< "PetNumber: " << m_charmInfo->GetPetNumber();
|
|
return sstr.str();
|
|
}
|