2 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include "Database/DatabaseEnv.h"
22 #include "WorldPacket.h"
23 #include "ObjectMgr.h"
27 #include "SpellAuras.h"
28 #include "CreatureAI.h"
32 char const* petTypeSuffix
[MAX_PET_TYPE
] =
34 "'s Minion", // SUMMON_PET
35 "'s Pet", // HUNTER_PET
36 "'s Guardian", // GUARDIAN_PET
37 "'s Companion" // MINI_PET
40 Pet::Pet(PetType type
) :
41 Creature(CREATURE_SUBTYPE_PET
), m_removed(false), m_petType(type
), m_happinessTimer(7500), m_duration(0), m_resetTalentsCost(0),
42 m_bonusdamage(0), m_resetTalentsTime(0), m_usedTalentCount(0), m_auraUpdateMask(0), m_loading(false),
43 m_declinedname(NULL
), m_petModeFlags(PET_MODE_DEFAULT
)
48 // pets always have a charminfo, even if they are not actually charmed
49 CharmInfo
* charmInfo
= InitCharmInfo(this);
51 if(type
== MINI_PET
) // always passive
52 charmInfo
->SetReactState(REACT_PASSIVE
);
53 else if(type
== GUARDIAN_PET
) // always aggressive
54 charmInfo
->SetReactState(REACT_AGGRESSIVE
);
59 delete m_declinedname
;
62 void Pet::AddToWorld()
64 ///- Register the pet for guid lookup
66 GetMap()->GetObjectsStore().insert
<Pet
>(GetGUID(), (Pet
*)this);
71 void Pet::RemoveFromWorld()
73 ///- Remove the pet from the accessor
75 GetMap()->GetObjectsStore().erase
<Pet
>(GetGUID(), (Pet
*)NULL
);
77 ///- Don't call the function for Creature, normal mobs + totems go in a different storage
78 Unit::RemoveFromWorld();
81 bool Pet::LoadPetFromDB( Player
* owner
, uint32 petentry
, uint32 petnumber
, bool current
)
85 uint32 ownerid
= owner
->GetGUIDLow();
90 // known petnumber entry 0 1 2(?) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
91 result
= CharacterDatabase
.PQuery("SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, resettalents_cost, resettalents_time, CreatedBySpell, PetType "
92 "FROM character_pet WHERE owner = '%u' AND id = '%u'",
95 // current pet (slot 0) 0 1 2(?) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
96 result
= CharacterDatabase
.PQuery("SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, resettalents_cost, resettalents_time, CreatedBySpell, PetType "
97 "FROM character_pet WHERE owner = '%u' AND slot = '%u'",
98 ownerid
, PET_SAVE_AS_CURRENT
);
100 // known petentry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets)
101 // 0 1 2(?) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
102 result
= CharacterDatabase
.PQuery("SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, resettalents_cost, resettalents_time, CreatedBySpell, PetType "
103 "FROM character_pet WHERE owner = '%u' AND entry = '%u' AND (slot = '%u' OR slot > '%u') ",
104 ownerid
, petentry
,PET_SAVE_AS_CURRENT
,PET_SAVE_LAST_STABLE_SLOT
);
106 // any current or other non-stabled pet (for hunter "call pet")
107 // 0 1 2(?) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
108 result
= CharacterDatabase
.PQuery("SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, resettalents_cost, resettalents_time, CreatedBySpell, PetType "
109 "FROM character_pet WHERE owner = '%u' AND (slot = '%u' OR slot > '%u') ",
110 ownerid
,PET_SAVE_AS_CURRENT
,PET_SAVE_LAST_STABLE_SLOT
);
115 Field
*fields
= result
->Fetch();
117 // update for case of current pet "slot = 0"
118 petentry
= fields
[1].GetUInt32();
125 uint32 summon_spell_id
= fields
[17].GetUInt32();
126 SpellEntry
const* spellInfo
= sSpellStore
.LookupEntry(summon_spell_id
);
128 bool is_temporary_summoned
= spellInfo
&& GetSpellDuration(spellInfo
) > 0;
130 // check temporary summoned pets like mage water elemental
131 if (current
&& is_temporary_summoned
)
137 PetType pet_type
= PetType(fields
[18].GetUInt8());
138 if(pet_type
== HUNTER_PET
)
140 CreatureInfo
const* creatureInfo
= ObjectMgr::GetCreatureTemplate(petentry
);
141 if(!creatureInfo
|| !creatureInfo
->isTameable(owner
->CanTameExoticPets()))
148 uint32 pet_number
= fields
[0].GetUInt32();
150 if (current
&& owner
->IsPetNeedBeTemporaryUnsummoned())
152 owner
->SetTemporaryUnsummonedPetNumber(pet_number
);
157 Map
*map
= owner
->GetMap();
158 uint32 guid
= map
->GenerateLocalLowGuid(HIGHGUID_PET
);
159 if (!Create(guid
, map
, owner
->GetPhaseMask(), petentry
, pet_number
))
166 owner
->GetClosePoint(px
, py
, pz
, GetObjectSize(), PET_FOLLOW_DIST
, PET_FOLLOW_ANGLE
);
168 Relocate(px
, py
, pz
, owner
->GetOrientation());
170 if (!IsPositionValid())
172 sLog
.outError("Pet (guidlow %d, entry %d) not loaded. Suggested coordinates isn't valid (X: %f Y: %f)",
173 GetGUIDLow(), GetEntry(), GetPositionX(), GetPositionY());
178 setPetType(pet_type
);
179 setFaction(owner
->getFaction());
180 SetUInt32Value(UNIT_CREATED_BY_SPELL
, summon_spell_id
);
182 CreatureInfo
const *cinfo
= GetCreatureInfo();
183 if (cinfo
->type
== CREATURE_TYPE_CRITTER
)
186 map
->Add((Creature
*)this);
191 m_charmInfo
->SetPetNumber(pet_number
, IsPermanentPetFor(owner
));
193 SetOwnerGUID(owner
->GetGUID());
194 SetDisplayId(fields
[3].GetUInt32());
195 SetNativeDisplayId(fields
[3].GetUInt32());
196 uint32 petlevel
= fields
[4].GetUInt32();
197 SetUInt32Value(UNIT_NPC_FLAGS
, UNIT_NPC_FLAG_NONE
);
198 SetName(fields
[8].GetString());
200 switch (getPetType())
203 petlevel
=owner
->getLevel();
205 SetUInt32Value(UNIT_FIELD_BYTES_0
, 2048);
206 SetUInt32Value(UNIT_FIELD_FLAGS
, UNIT_FLAG_PVP_ATTACKABLE
);
207 // this enables popup window (pet dismiss, cancel)
210 SetUInt32Value(UNIT_FIELD_BYTES_0
, 0x02020100);
211 SetSheath(SHEATH_STATE_MELEE
);
212 SetByteFlag(UNIT_FIELD_BYTES_2
, 2, fields
[9].GetBool() ? UNIT_CAN_BE_ABANDONED
: UNIT_CAN_BE_RENAMED
| UNIT_CAN_BE_ABANDONED
);
214 SetUInt32Value(UNIT_FIELD_FLAGS
, UNIT_FLAG_PVP_ATTACKABLE
);
215 // this enables popup window (pet abandon, cancel)
216 SetMaxPower(POWER_HAPPINESS
, GetCreatePowers(POWER_HAPPINESS
));
217 SetPower(POWER_HAPPINESS
, fields
[12].GetUInt32());
218 setPowerType(POWER_FOCUS
);
221 sLog
.outError("Pet have incorrect type (%u) for pet loading.", getPetType());
227 if(owner
->IsFFAPvP())
230 SetCanModifyStats(true);
231 InitStatsForLevel(petlevel
);
232 InitTalentForLevel(); // set original talents points before spell loading
234 SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP
, uint32(time(NULL
)));
235 SetUInt32Value(UNIT_FIELD_PETEXPERIENCE
, fields
[5].GetUInt32());
236 SetCreatorGUID(owner
->GetGUID());
238 m_charmInfo
->SetReactState(ReactStates(fields
[6].GetUInt8()));
240 uint32 savedhealth
= fields
[10].GetUInt32();
241 uint32 savedmana
= fields
[11].GetUInt32();
243 // set current pet as current
245 // 1..MAX_PET_STABLES in stable slot
246 // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning))
247 if (fields
[7].GetUInt32() != 0)
249 CharacterDatabase
.BeginTransaction();
250 CharacterDatabase
.PExecute("UPDATE character_pet SET slot = '%u' WHERE owner = '%u' AND slot = '%u' AND id <> '%u'",
251 PET_SAVE_NOT_IN_SLOT
, ownerid
, PET_SAVE_AS_CURRENT
, m_charmInfo
->GetPetNumber());
252 CharacterDatabase
.PExecute("UPDATE character_pet SET slot = '%u' WHERE owner = '%u' AND id = '%u'",
253 PET_SAVE_AS_CURRENT
, ownerid
, m_charmInfo
->GetPetNumber());
254 CharacterDatabase
.CommitTransaction();
257 // load action bar, if data broken will fill later by default spells.
258 if (!is_temporary_summoned
)
259 m_charmInfo
->LoadPetActionBar(fields
[13].GetCppString());
261 // since last save (in seconds)
262 uint32 timediff
= uint32(time(NULL
) - fields
[14].GetUInt64());
264 m_resetTalentsCost
= fields
[15].GetUInt32();
265 m_resetTalentsTime
= fields
[16].GetUInt64();
269 //load spells/cooldowns/auras
270 _LoadAuras(timediff
);
273 if (is_temporary_summoned
)
275 // Temporary summoned pets always have initial spell list at load
276 InitPetCreateSpells();
281 CastPetAuras(current
);
284 if (getPetType() == SUMMON_PET
&& !current
) //all (?) summon pets come with full health when called, but not when they are current
286 SetHealth(GetMaxHealth());
287 SetPower(POWER_MANA
, GetMaxPower(POWER_MANA
));
291 SetHealth(savedhealth
> GetMaxHealth() ? GetMaxHealth() : savedhealth
);
292 SetPower(POWER_MANA
, savedmana
> GetMaxPower(POWER_MANA
) ? GetMaxPower(POWER_MANA
) : savedmana
);
295 UpdateWalkMode(owner
);
298 map
->Add((Creature
*)this);
300 // Spells should be loaded after pet is added to map, because in CheckCast is check on it
302 InitLevelupSpellsForLevel();
304 CleanupActionBar(); // remove unknown spells from action bar after load
306 _LoadSpellCooldowns();
308 owner
->SetPet(this); // in DB stored only full controlled creature
309 sLog
.outDebug("New Pet has guid %u", GetGUIDLow());
311 if (owner
->GetTypeId() == TYPEID_PLAYER
)
313 ((Player
*)owner
)->PetSpellInitialize();
314 if(((Player
*)owner
)->GetGroup())
315 ((Player
*)owner
)->SetGroupUpdateFlag(GROUP_UPDATE_PET
);
317 ((Player
*)owner
)->SendTalentsInfoData(true);
320 if (owner
->GetTypeId() == TYPEID_PLAYER
&& getPetType() == HUNTER_PET
)
322 result
= CharacterDatabase
.PQuery("SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = '%u' AND id = '%u'", owner
->GetGUIDLow(), GetCharmInfo()->GetPetNumber());
327 delete m_declinedname
;
329 m_declinedname
= new DeclinedName
;
330 Field
*fields2
= result
->Fetch();
331 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
332 m_declinedname
->name
[i
] = fields2
[i
].GetCppString();
340 SynchronizeLevelWithOwner();
344 void Pet::SavePetToDB(PetSaveMode mode
)
349 // save only fully controlled creature
353 // not save not player pets
354 if(!IS_PLAYER_GUID(GetOwnerGUID()))
357 Player
* pOwner
= (Player
*)GetOwner();
361 // current/stable/not_in_slot
362 if (mode
>= PET_SAVE_AS_CURRENT
)
364 // not save pet as current if another pet temporary unsummoned
365 if (mode
== PET_SAVE_AS_CURRENT
&& pOwner
->GetTemporaryUnsummonedPetNumber() &&
366 pOwner
->GetTemporaryUnsummonedPetNumber() != m_charmInfo
->GetPetNumber())
368 // pet will lost anyway at restore temporary unsummoned
369 if(getPetType()==HUNTER_PET
)
373 mode
= PET_SAVE_NOT_IN_SLOT
;
376 uint32 curhealth
= GetHealth();
377 uint32 curmana
= GetPower(POWER_MANA
);
379 // stable and not in slot saves
380 if (mode
!= PET_SAVE_AS_CURRENT
)
384 _SaveSpellCooldowns();
387 uint32 owner
= GUID_LOPART(GetOwnerGUID());
388 std::string name
= m_name
;
389 CharacterDatabase
.escape_string(name
);
390 CharacterDatabase
.BeginTransaction();
391 // remove current data
392 CharacterDatabase
.PExecute("DELETE FROM character_pet WHERE owner = '%u' AND id = '%u'", owner
,m_charmInfo
->GetPetNumber() );
394 // prevent duplicate using slot (except PET_SAVE_NOT_IN_SLOT)
395 if(mode
<= PET_SAVE_LAST_STABLE_SLOT
)
396 CharacterDatabase
.PExecute("UPDATE character_pet SET slot = '%u' WHERE owner = '%u' AND slot = '%u'",
397 PET_SAVE_NOT_IN_SLOT
, owner
, uint32(mode
) );
399 // prevent existence another hunter pet in PET_SAVE_AS_CURRENT and PET_SAVE_NOT_IN_SLOT
400 if(getPetType()==HUNTER_PET
&& (mode
==PET_SAVE_AS_CURRENT
||mode
> PET_SAVE_LAST_STABLE_SLOT
))
401 CharacterDatabase
.PExecute("DELETE FROM character_pet WHERE owner = '%u' AND (slot = '%u' OR slot > '%u')",
402 owner
,PET_SAVE_AS_CURRENT
,PET_SAVE_LAST_STABLE_SLOT
);
404 std::ostringstream ss
;
405 ss
<< "INSERT INTO character_pet ( id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, resettalents_cost, resettalents_time, CreatedBySpell, PetType) "
407 << m_charmInfo
->GetPetNumber() << ", "
408 << GetEntry() << ", "
410 << GetNativeDisplayId() << ", "
411 << getLevel() << ", "
412 << GetUInt32Value(UNIT_FIELD_PETEXPERIENCE
) << ", "
413 << uint32(m_charmInfo
->GetReactState()) << ", "
414 << uint32(mode
) << ", '"
415 << name
.c_str() << "', "
416 << uint32(HasByteFlag(UNIT_FIELD_BYTES_2
, 2, UNIT_CAN_BE_RENAMED
) ? 0 : 1) << ", "
417 << (curhealth
< 1 ? 1 : curhealth
) << ", "
419 << GetPower(POWER_HAPPINESS
) << ", '";
421 for(uint32 i
= ACTION_BAR_INDEX_START
; i
< ACTION_BAR_INDEX_END
; ++i
)
423 ss
<< uint32(m_charmInfo
->GetActionBarEntry(i
)->GetType()) << " "
424 << uint32(m_charmInfo
->GetActionBarEntry(i
)->GetAction()) << " ";
428 << time(NULL
) << ", "
429 << uint32(m_resetTalentsCost
) << ", "
430 << uint64(m_resetTalentsTime
) << ", "
431 << GetUInt32Value(UNIT_CREATED_BY_SPELL
) << ", "
432 << uint32(getPetType()) << ")";
434 CharacterDatabase
.Execute( ss
.str().c_str() );
435 CharacterDatabase
.CommitTransaction();
440 RemoveAllAuras(AURA_REMOVE_BY_DELETE
);
441 DeleteFromDB(m_charmInfo
->GetPetNumber());
445 void Pet::DeleteFromDB(uint32 guidlow
)
447 CharacterDatabase
.PExecute("DELETE FROM character_pet WHERE id = '%u'", guidlow
);
448 CharacterDatabase
.PExecute("DELETE FROM character_pet_declinedname WHERE id = '%u'", guidlow
);
449 CharacterDatabase
.PExecute("DELETE FROM pet_aura WHERE guid = '%u'", guidlow
);
450 CharacterDatabase
.PExecute("DELETE FROM pet_spell WHERE guid = '%u'", guidlow
);
451 CharacterDatabase
.PExecute("DELETE FROM pet_spell_cooldown WHERE guid = '%u'", guidlow
);
454 void Pet::setDeathState(DeathState s
) // overwrite virtual Creature::setDeathState and Unit::setDeathState
456 Creature::setDeathState(s
);
457 if(getDeathState()==CORPSE
)
459 //remove summoned pet (no corpse)
460 if(getPetType()==SUMMON_PET
)
461 Remove(PET_SAVE_NOT_IN_SLOT
);
462 // other will despawn at corpse desppawning (Pet::Update code)
465 // pet corpse non lootable and non skinnable
466 SetUInt32Value( UNIT_DYNAMIC_FLAGS
, 0x00 );
467 RemoveFlag (UNIT_FIELD_FLAGS
, UNIT_FLAG_SKINNABLE
);
469 //lose happiness when died and not in BG/Arena
470 MapEntry
const* mapEntry
= sMapStore
.LookupEntry(GetMapId());
471 if(!mapEntry
|| (mapEntry
->map_type
!= MAP_ARENA
&& mapEntry
->map_type
!= MAP_BATTLEGROUND
))
472 ModifyPower(POWER_HAPPINESS
, -HAPPINESS_LEVEL_SIZE
);
474 SetFlag(UNIT_FIELD_FLAGS
, UNIT_FLAG_STUNNED
);
477 else if(getDeathState()==ALIVE
)
479 RemoveFlag(UNIT_FIELD_FLAGS
, UNIT_FLAG_STUNNED
);
484 void Pet::Update(uint32 diff
)
486 if(m_removed
) // pet already removed, just wait in remove queue, no updates
489 switch( m_deathState
)
493 if( m_deathTimer
<= diff
)
495 assert(getPetType()!=SUMMON_PET
&& "Must be already removed.");
496 Remove(PET_SAVE_NOT_IN_SLOT
); //hunters' pets never get removed because of death, NEVER!
503 // unsummon pet that lost owner
504 Unit
* owner
= GetOwner();
505 if(!owner
|| (!IsWithinDistInMap(owner
, GetMap()->GetVisibilityDistance()) && (owner
->GetCharmGUID() && (owner
->GetCharmGUID() != GetGUID()))) || (isControlled() && !owner
->GetPetGUID()))
507 Remove(PET_SAVE_NOT_IN_SLOT
, true);
513 if( owner
->GetPetGUID() != GetGUID() )
515 Remove(getPetType()==HUNTER_PET
?PET_SAVE_AS_DELETED
:PET_SAVE_NOT_IN_SLOT
);
522 if(m_duration
> (int32
)diff
)
523 m_duration
-= (int32
)diff
;
526 Remove(getPetType() != SUMMON_PET
? PET_SAVE_AS_DELETED
:PET_SAVE_NOT_IN_SLOT
);
531 //regenerate focus for hunter pets or energy for deathknight's ghoul
532 if(m_regenTimer
<= diff
)
534 switch (getPowerType())
538 Regenerate(getPowerType());
546 m_regenTimer
-= diff
;
548 if(getPetType() != HUNTER_PET
)
551 if(m_happinessTimer
<= diff
)
554 m_happinessTimer
= 7500;
557 m_happinessTimer
-= diff
;
564 Creature::Update(diff
);
567 void Pet::Regenerate(Powers power
)
569 uint32 curValue
= GetPower(power
);
570 uint32 maxValue
= GetMaxPower(power
);
572 if (curValue
>= maxValue
)
575 float addvalue
= 0.0f
;
582 addvalue
= 24 * sWorld
.getConfig(CONFIG_FLOAT_RATE_POWER_FOCUS
);
587 // For deathknight's ghoul.
595 // Apply modifiers (if any).
596 AuraList
const& ModPowerRegenPCTAuras
= GetAurasByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT
);
597 for(AuraList::const_iterator i
= ModPowerRegenPCTAuras
.begin(); i
!= ModPowerRegenPCTAuras
.end(); ++i
)
598 if ((*i
)->GetModifier()->m_miscvalue
== power
)
599 addvalue
*= ((*i
)->GetModifier()->m_amount
+ 100) / 100.0f
;
601 ModifyPower(power
, (int32
)addvalue
);
604 void Pet::LooseHappiness()
606 uint32 curValue
= GetPower(POWER_HAPPINESS
);
609 int32 addvalue
= 670; //value is 70/35/17/8/4 (per min) * 1000 / 8 (timer 7.5 secs)
610 if(isInCombat()) //we know in combat happiness fades faster, multiplier guess
611 addvalue
= int32(addvalue
* 1.5);
612 ModifyPower(POWER_HAPPINESS
, -addvalue
);
615 HappinessState
Pet::GetHappinessState()
617 if(GetPower(POWER_HAPPINESS
) < HAPPINESS_LEVEL_SIZE
)
619 else if(GetPower(POWER_HAPPINESS
) >= HAPPINESS_LEVEL_SIZE
* 2)
625 bool Pet::CanTakeMoreActiveSpells(uint32 spellid
)
627 uint8 activecount
= 1;
628 uint32 chainstartstore
[ACTIVE_SPELLS_MAX
];
630 if(IsPassiveSpell(spellid
))
633 chainstartstore
[0] = sSpellMgr
.GetFirstSpellInChain(spellid
);
635 for (PetSpellMap::const_iterator itr
= m_spells
.begin(); itr
!= m_spells
.end(); ++itr
)
637 if(itr
->second
.state
== PETSPELL_REMOVED
)
640 if(IsPassiveSpell(itr
->first
))
643 uint32 chainstart
= sSpellMgr
.GetFirstSpellInChain(itr
->first
);
647 for(x
= 0; x
< activecount
; x
++)
649 if(chainstart
== chainstartstore
[x
])
653 if(x
== activecount
) //spellchain not yet saved -> add active count
656 if(activecount
> ACTIVE_SPELLS_MAX
)
658 chainstartstore
[x
] = chainstart
;
664 void Pet::Remove(PetSaveMode mode
, bool returnreagent
)
666 Unit
* owner
= GetOwner();
670 if(owner
->GetTypeId()==TYPEID_PLAYER
)
672 ((Player
*)owner
)->RemovePet(this,mode
,returnreagent
);
676 // only if current pet in slot
677 if(owner
->GetPetGUID()==GetGUID())
681 AddObjectToRemoveList();
685 void Pet::GivePetXP(uint32 xp
)
687 if(getPetType() != HUNTER_PET
)
696 uint32 level
= getLevel();
698 // XP to money conversion processed in Player::RewardQuest
699 if(level
>= sWorld
.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL
))
702 uint32 curXP
= GetUInt32Value(UNIT_FIELD_PETEXPERIENCE
);
703 uint32 nextLvlXP
= GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
);
704 uint32 newXP
= curXP
+ xp
;
706 if(newXP
>= nextLvlXP
&& level
+1 > GetOwner()->getLevel())
708 SetUInt32Value(UNIT_FIELD_PETEXPERIENCE
, nextLvlXP
-1);
712 while( newXP
>= nextLvlXP
&& level
< sWorld
.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL
) )
716 GivePetLevel(level
+1);
717 SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
, sObjectMgr
.GetXPForPetLevel(level
+1));
720 nextLvlXP
= GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
);
723 SetUInt32Value(UNIT_FIELD_PETEXPERIENCE
, newXP
);
726 void Pet::GivePetLevel(uint32 level
)
731 InitStatsForLevel(level
);
732 InitLevelupSpellsForLevel();
733 InitTalentForLevel();
736 bool Pet::CreateBaseAtCreature(Creature
* creature
)
740 sLog
.outError("CRITICAL: NULL pointer parsed into CreateBaseAtCreature()");
744 uint32 guid
= creature
->GetMap()->GenerateLocalLowGuid(HIGHGUID_PET
);
746 sLog
.outBasic("Create pet");
747 uint32 pet_number
= sObjectMgr
.GeneratePetNumber();
748 if(!Create(guid
, creature
->GetMap(), creature
->GetPhaseMask(), creature
->GetEntry(), pet_number
))
751 Relocate(creature
->GetPositionX(), creature
->GetPositionY(), creature
->GetPositionZ(), creature
->GetOrientation());
753 if(!IsPositionValid())
755 sLog
.outError("Pet (guidlow %d, entry %d) not created base at creature. Suggested coordinates isn't valid (X: %f Y: %f)",
756 GetGUIDLow(), GetEntry(), GetPositionX(), GetPositionY());
760 CreatureInfo
const *cinfo
= GetCreatureInfo();
763 sLog
.outError("CreateBaseAtCreature() failed, creatureInfo is missing!");
767 if(cinfo
->type
== CREATURE_TYPE_CRITTER
)
769 setPetType(MINI_PET
);
772 SetDisplayId(creature
->GetDisplayId());
773 SetNativeDisplayId(creature
->GetNativeDisplayId());
774 SetMaxPower(POWER_HAPPINESS
, GetCreatePowers(POWER_HAPPINESS
));
775 SetPower(POWER_HAPPINESS
, 166500);
776 setPowerType(POWER_FOCUS
);
777 SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP
, 0);
778 SetUInt32Value(UNIT_FIELD_PETEXPERIENCE
, 0);
779 SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
, sObjectMgr
.GetXPForPetLevel(creature
->getLevel()));
780 SetUInt32Value(UNIT_NPC_FLAGS
, UNIT_NPC_FLAG_NONE
);
782 if(CreatureFamilyEntry
const* cFamily
= sCreatureFamilyStore
.LookupEntry(cinfo
->family
))
783 SetName(cFamily
->Name
[sWorld
.GetDefaultDbcLocale()]);
785 SetName(creature
->GetNameForLocaleIdx(sObjectMgr
.GetDBCLocaleIndex()));
787 if(cinfo
->type
== CREATURE_TYPE_BEAST
)
789 SetUInt32Value(UNIT_FIELD_BYTES_0
, 0x02020100);
790 SetSheath(SHEATH_STATE_MELEE
);
791 SetByteFlag(UNIT_FIELD_BYTES_2
, 2, UNIT_CAN_BE_RENAMED
| UNIT_CAN_BE_ABANDONED
);
792 SetUInt32Value(UNIT_MOD_CAST_SPEED
, creature
->GetUInt32Value(UNIT_MOD_CAST_SPEED
));
797 bool Pet::InitStatsForLevel(uint32 petlevel
, Unit
* owner
)
799 CreatureInfo
const *cinfo
= GetCreatureInfo();
807 sLog
.outError("attempt to summon pet (Entry %u) without owner! Attempt terminated.", cinfo
->Entry
);
812 uint32 creature_ID
= (getPetType() == HUNTER_PET
) ? 1 : cinfo
->Entry
;
816 SetMeleeDamageSchool(SpellSchools(cinfo
->dmgschool
));
818 SetModifierValue(UNIT_MOD_ARMOR
, BASE_VALUE
, float(petlevel
*50));
820 SetAttackTime(BASE_ATTACK
, BASE_ATTACK_TIME
);
821 SetAttackTime(OFF_ATTACK
, BASE_ATTACK_TIME
);
822 SetAttackTime(RANGED_ATTACK
, BASE_ATTACK_TIME
);
824 SetFloatValue(UNIT_MOD_CAST_SPEED
, 1.0);
826 CreatureFamilyEntry
const* cFamily
= sCreatureFamilyStore
.LookupEntry(cinfo
->family
);
827 if(cFamily
&& cFamily
->minScale
> 0.0f
&& getPetType()==HUNTER_PET
)
830 if (getLevel() >= cFamily
->maxScaleLevel
)
831 scale
= cFamily
->maxScale
;
832 else if (getLevel() <= cFamily
->minScaleLevel
)
833 scale
= cFamily
->minScale
;
835 scale
= cFamily
->minScale
+ float(getLevel() - cFamily
->minScaleLevel
) / cFamily
->maxScaleLevel
* (cFamily
->maxScale
- cFamily
->minScale
);
837 SetFloatValue(OBJECT_FIELD_SCALE_X
, scale
);
841 int32 createResistance
[MAX_SPELL_SCHOOL
] = {0,0,0,0,0,0,0};
843 if(cinfo
&& getPetType() != HUNTER_PET
)
845 createResistance
[SPELL_SCHOOL_HOLY
] = cinfo
->resistance1
;
846 createResistance
[SPELL_SCHOOL_FIRE
] = cinfo
->resistance2
;
847 createResistance
[SPELL_SCHOOL_NATURE
] = cinfo
->resistance3
;
848 createResistance
[SPELL_SCHOOL_FROST
] = cinfo
->resistance4
;
849 createResistance
[SPELL_SCHOOL_SHADOW
] = cinfo
->resistance5
;
850 createResistance
[SPELL_SCHOOL_ARCANE
] = cinfo
->resistance6
;
857 if(owner
->GetTypeId() == TYPEID_PLAYER
)
859 switch(owner
->getClass())
864 //the damage bonus used for pets is either fire or shadow damage, whatever is higher
865 uint32 fire
= owner
->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS
+ SPELL_SCHOOL_FIRE
);
866 uint32 shadow
= owner
->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS
+ SPELL_SCHOOL_SHADOW
);
867 uint32 val
= (fire
> shadow
) ? fire
: shadow
;
869 SetBonusDamage(int32 (val
* 0.15f
));
870 //bonusAP += val * 0.57;
875 //40% damage bonus of mage's frost damage
876 float val
= owner
->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS
+ SPELL_SCHOOL_FROST
) * 0.4f
;
879 SetBonusDamage( int32(val
));
887 SetBaseWeaponDamage(BASE_ATTACK
, MINDAMAGE
, float(petlevel
- (petlevel
/ 4)) );
888 SetBaseWeaponDamage(BASE_ATTACK
, MAXDAMAGE
, float(petlevel
+ (petlevel
/ 4)) );
890 //SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, float(cinfo->attackpower));
892 PetLevelInfo
const* pInfo
= sObjectMgr
.GetPetLevelInfo(creature_ID
, petlevel
);
893 if(pInfo
) // exist in DB
895 SetCreateHealth(pInfo
->health
);
896 SetCreateMana(pInfo
->mana
);
899 SetModifierValue(UNIT_MOD_ARMOR
, BASE_VALUE
, float(pInfo
->armor
));
901 for(int stat
= 0; stat
< MAX_STATS
; ++stat
)
903 SetCreateStat(Stats(stat
), float(pInfo
->stats
[stat
]));
906 else // not exist in DB, use some default fake data
908 sLog
.outErrorDb("Summoned pet (Entry: %u) not have pet stats data in DB",cinfo
->Entry
);
910 // remove elite bonuses included in DB values
911 SetCreateHealth(uint32(((float(cinfo
->maxhealth
) / cinfo
->maxlevel
) / (1 + 2 * cinfo
->rank
)) * petlevel
) );
912 SetCreateMana( uint32(((float(cinfo
->maxmana
) / cinfo
->maxlevel
) / (1 + 2 * cinfo
->rank
)) * petlevel
) );
914 SetCreateStat(STAT_STRENGTH
, 22);
915 SetCreateStat(STAT_AGILITY
, 22);
916 SetCreateStat(STAT_STAMINA
, 25);
917 SetCreateStat(STAT_INTELLECT
, 28);
918 SetCreateStat(STAT_SPIRIT
, 27);
924 SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
, sObjectMgr
.GetXPForPetLevel(petlevel
));
925 //these formula may not be correct; however, it is designed to be close to what it should be
926 //this makes dps 0.5 of pets level
927 SetBaseWeaponDamage(BASE_ATTACK
, MINDAMAGE
, float(petlevel
- (petlevel
/ 4)) );
928 //damage range is then petlevel / 2
929 SetBaseWeaponDamage(BASE_ATTACK
, MAXDAMAGE
, float(petlevel
+ (petlevel
/ 4)) );
930 //damage is increased afterwards as strength and pet scaling modify attack power
932 //stored standard pet stats are entry 1 in pet_levelinfo
933 PetLevelInfo
const* pInfo
= sObjectMgr
.GetPetLevelInfo(creature_ID
, petlevel
);
934 if(pInfo
) // exist in DB
936 SetCreateHealth(pInfo
->health
);
937 SetModifierValue(UNIT_MOD_ARMOR
, BASE_VALUE
, float(pInfo
->armor
));
938 //SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, float(cinfo->attackpower));
940 for( int i
= STAT_STRENGTH
; i
< MAX_STATS
; ++i
)
942 SetCreateStat(Stats(i
), float(pInfo
->stats
[i
]));
945 else // not exist in DB, use some default fake data
947 sLog
.outErrorDb("Hunter pet levelstats missing in DB");
949 // remove elite bonuses included in DB values
950 SetCreateHealth( uint32(((float(cinfo
->maxhealth
) / cinfo
->maxlevel
) / (1 + 2 * cinfo
->rank
)) * petlevel
) );
952 SetCreateStat(STAT_STRENGTH
, 22);
953 SetCreateStat(STAT_AGILITY
, 22);
954 SetCreateStat(STAT_STAMINA
, 25);
955 SetCreateStat(STAT_INTELLECT
, 28);
956 SetCreateStat(STAT_SPIRIT
, 27);
961 SetUInt32Value(UNIT_FIELD_PETEXPERIENCE
, 0);
962 SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
, 1000);
964 SetCreateMana(28 + 10*petlevel
);
965 SetCreateHealth(28 + 30*petlevel
);
967 // FIXME: this is wrong formula, possible each guardian pet have own damage formula
968 //these formula may not be correct; however, it is designed to be close to what it should be
969 //this makes dps 0.5 of pets level
970 SetBaseWeaponDamage(BASE_ATTACK
, MINDAMAGE
, float(petlevel
- (petlevel
/ 4)));
971 //damage range is then petlevel / 2
972 SetBaseWeaponDamage(BASE_ATTACK
, MAXDAMAGE
, float(petlevel
+ (petlevel
/ 4)));
975 sLog
.outError("Pet have incorrect type (%u) for levelup.", getPetType());
979 for (int i
= SPELL_SCHOOL_HOLY
; i
< MAX_SPELL_SCHOOL
; ++i
)
980 SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START
+ i
), BASE_VALUE
, float(createResistance
[i
]));
984 SetHealth(GetMaxHealth());
985 SetPower(POWER_MANA
, GetMaxPower(POWER_MANA
));
990 bool Pet::HaveInDiet(ItemPrototype
const* item
) const
995 CreatureInfo
const* cInfo
= GetCreatureInfo();
999 CreatureFamilyEntry
const* cFamily
= sCreatureFamilyStore
.LookupEntry(cInfo
->family
);
1003 uint32 diet
= cFamily
->petFoodMask
;
1004 uint32 FoodMask
= 1 << (item
->FoodType
-1);
1005 return diet
& FoodMask
;
1008 uint32
Pet::GetCurrentFoodBenefitLevel(uint32 itemlevel
)
1010 // -5 or greater food level
1011 if(getLevel() <= itemlevel
+ 5) //possible to feed level 60 pet with level 55 level food for full effect
1014 else if(getLevel() <= itemlevel
+ 10) //pure guess, but sounds good
1017 else if(getLevel() <= itemlevel
+ 14) //level 55 food gets green on 70, makes sense to me
1021 return 0; //food too low level
1024 void Pet::_LoadSpellCooldowns()
1026 m_CreatureSpellCooldowns
.clear();
1027 m_CreatureCategoryCooldowns
.clear();
1029 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT spell,time FROM pet_spell_cooldown WHERE guid = '%u'",m_charmInfo
->GetPetNumber());
1033 time_t curTime
= time(NULL
);
1035 WorldPacket
data(SMSG_SPELL_COOLDOWN
, (8+1+size_t(result
->GetRowCount())*8));
1037 data
<< uint8(0x0); // flags (0x1, 0x2)
1041 Field
*fields
= result
->Fetch();
1043 uint32 spell_id
= fields
[0].GetUInt32();
1044 time_t db_time
= (time_t)fields
[1].GetUInt64();
1046 if(!sSpellStore
.LookupEntry(spell_id
))
1048 sLog
.outError("Pet %u have unknown spell %u in `pet_spell_cooldown`, skipping.",m_charmInfo
->GetPetNumber(),spell_id
);
1052 // skip outdated cooldown
1053 if(db_time
<= curTime
)
1056 data
<< uint32(spell_id
);
1057 data
<< uint32(uint32(db_time
-curTime
)*IN_MILISECONDS
);
1059 _AddCreatureSpellCooldown(spell_id
,db_time
);
1061 sLog
.outDebug("Pet (Number: %u) spell %u cooldown loaded (%u secs).", m_charmInfo
->GetPetNumber(), spell_id
, uint32(db_time
-curTime
));
1063 while( result
->NextRow() );
1067 if(!m_CreatureSpellCooldowns
.empty() && GetOwner())
1069 ((Player
*)GetOwner())->GetSession()->SendPacket(&data
);
1074 void Pet::_SaveSpellCooldowns()
1076 CharacterDatabase
.PExecute("DELETE FROM pet_spell_cooldown WHERE guid = '%u'", m_charmInfo
->GetPetNumber());
1078 time_t curTime
= time(NULL
);
1080 // remove oudated and save active
1081 for(CreatureSpellCooldowns::iterator itr
= m_CreatureSpellCooldowns
.begin();itr
!= m_CreatureSpellCooldowns
.end();)
1083 if(itr
->second
<= curTime
)
1084 m_CreatureSpellCooldowns
.erase(itr
++);
1087 CharacterDatabase
.PExecute("INSERT INTO pet_spell_cooldown (guid,spell,time) VALUES ('%u', '%u', '" UI64FMTD
"')", m_charmInfo
->GetPetNumber(), itr
->first
, uint64(itr
->second
));
1093 void Pet::_LoadSpells()
1095 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT spell,active FROM pet_spell WHERE guid = '%u'",m_charmInfo
->GetPetNumber());
1101 Field
*fields
= result
->Fetch();
1103 addSpell(fields
[0].GetUInt32(), ActiveStates(fields
[1].GetUInt8()), PETSPELL_UNCHANGED
);
1105 while( result
->NextRow() );
1111 void Pet::_SaveSpells()
1113 for (PetSpellMap::iterator itr
= m_spells
.begin(), next
= m_spells
.begin(); itr
!= m_spells
.end(); itr
= next
)
1117 // prevent saving family passives to DB
1118 if (itr
->second
.type
== PETSPELL_FAMILY
)
1121 switch(itr
->second
.state
)
1123 case PETSPELL_REMOVED
:
1124 CharacterDatabase
.PExecute("DELETE FROM pet_spell WHERE guid = '%u' and spell = '%u'", m_charmInfo
->GetPetNumber(), itr
->first
);
1125 m_spells
.erase(itr
);
1127 case PETSPELL_CHANGED
:
1128 CharacterDatabase
.PExecute("DELETE FROM pet_spell WHERE guid = '%u' and spell = '%u'", m_charmInfo
->GetPetNumber(), itr
->first
);
1129 CharacterDatabase
.PExecute("INSERT INTO pet_spell (guid,spell,active) VALUES ('%u', '%u', '%u')", m_charmInfo
->GetPetNumber(), itr
->first
, itr
->second
.active
);
1132 CharacterDatabase
.PExecute("INSERT INTO pet_spell (guid,spell,active) VALUES ('%u', '%u', '%u')", m_charmInfo
->GetPetNumber(), itr
->first
, itr
->second
.active
);
1134 case PETSPELL_UNCHANGED
:
1138 itr
->second
.state
= PETSPELL_UNCHANGED
;
1142 void Pet::_LoadAuras(uint32 timediff
)
1146 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT caster_guid,spell,effect_index,stackcount,amount,maxduration,remaintime,remaincharges FROM pet_aura WHERE guid = '%u'",m_charmInfo
->GetPetNumber());
1152 Field
*fields
= result
->Fetch();
1153 uint64 caster_guid
= fields
[0].GetUInt64();
1154 uint32 spellid
= fields
[1].GetUInt32();
1155 SpellEffectIndex effindex
= SpellEffectIndex(fields
[2].GetUInt32());
1156 uint32 stackcount
= fields
[3].GetUInt32();
1157 int32 damage
= (int32
)fields
[4].GetUInt32();
1158 int32 maxduration
= (int32
)fields
[5].GetUInt32();
1159 int32 remaintime
= (int32
)fields
[6].GetUInt32();
1160 int32 remaincharges
= (int32
)fields
[7].GetUInt32();
1162 SpellEntry
const* spellproto
= sSpellStore
.LookupEntry(spellid
);
1165 sLog
.outError("Unknown aura (spellid %u, effindex %u), ignore.",spellid
,effindex
);
1169 if(effindex
>= MAX_EFFECT_INDEX
)
1171 sLog
.outError("Invalid effect index (spellid %u, effindex %u), ignore.",spellid
,effindex
);
1175 // negative effects should continue counting down after logout
1176 if (remaintime
!= -1 && !IsPositiveEffect(spellid
, effindex
))
1178 if (remaintime
/IN_MILISECONDS
<= int32(timediff
))
1181 remaintime
-= timediff
*IN_MILISECONDS
;
1184 // prevent wrong values of remaincharges
1185 if(spellproto
->procCharges
)
1187 if(remaincharges
<= 0 || remaincharges
> (int32
)spellproto
->procCharges
)
1188 remaincharges
= spellproto
->procCharges
;
1193 /// do not load single target auras (unless they were cast by the player)
1194 if (caster_guid
!= GetGUID() && IsSingleTargetSpell(spellproto
))
1197 for(uint32 i
=0; i
< stackcount
; ++i
)
1199 Aura
* aura
= CreateAura(spellproto
, effindex
, NULL
, this, NULL
);
1202 damage
= aura
->GetModifier()->m_amount
;
1203 aura
->SetLoadedState(caster_guid
,damage
,maxduration
,remaintime
,remaincharges
);
1207 while( result
->NextRow() );
1213 void Pet::_SaveAuras()
1215 CharacterDatabase
.PExecute("DELETE FROM pet_aura WHERE guid = '%u'", m_charmInfo
->GetPetNumber());
1217 AuraMap
const& auras
= GetAuras();
1221 spellEffectPair lastEffectPair
= auras
.begin()->first
;
1222 uint32 stackCounter
= 1;
1224 for(AuraMap::const_iterator itr
= auras
.begin(); ; ++itr
)
1226 if(itr
== auras
.end() || lastEffectPair
!= itr
->first
)
1228 AuraMap::const_iterator itr2
= itr
;
1229 // save previous spellEffectPair to db
1231 SpellEntry
const *spellInfo
= itr2
->second
->GetSpellProto();
1232 /// do not save single target auras (unless they were cast by the player)
1233 if (!(itr2
->second
->GetCasterGUID() != GetGUID() && IsSingleTargetSpell(spellInfo
)))
1235 if(!itr2
->second
->IsPassive())
1237 // skip all auras from spell that apply at cast SPELL_AURA_MOD_SHAPESHIFT or pet area auras.
1239 for (i
= 0; i
< MAX_EFFECT_INDEX
; ++i
)
1240 if (spellInfo
->EffectApplyAuraName
[i
] == SPELL_AURA_MOD_STEALTH
||
1241 spellInfo
->Effect
[i
] == SPELL_EFFECT_APPLY_AREA_AURA_OWNER
||
1242 spellInfo
->Effect
[i
] == SPELL_EFFECT_APPLY_AREA_AURA_PET
)
1247 CharacterDatabase
.PExecute("INSERT INTO pet_aura (guid,caster_guid,spell,effect_index,stackcount,amount,maxduration,remaintime,remaincharges) "
1248 "VALUES ('%u', '" UI64FMTD
"', '%u', '%u', '%u', '%d', '%d', '%d', '%d')",
1249 m_charmInfo
->GetPetNumber(), itr2
->second
->GetCasterGUID(),(uint32
)itr2
->second
->GetId(), (uint32
)itr2
->second
->GetEffIndex(), stackCounter
, itr2
->second
->GetModifier()->m_amount
,int(itr2
->second
->GetAuraMaxDuration()),int(itr2
->second
->GetAuraDuration()),int(itr2
->second
->GetAuraCharges()));
1253 if(itr
== auras
.end())
1257 if (lastEffectPair
== itr
->first
)
1261 lastEffectPair
= itr
->first
;
1267 bool Pet::addSpell(uint32 spell_id
,ActiveStates active
/*= ACT_DECIDE*/, PetSpellState state
/*= PETSPELL_NEW*/, PetSpellType type
/*= PETSPELL_NORMAL*/)
1269 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spell_id
);
1272 // do pet spell book cleanup
1273 if(state
== PETSPELL_UNCHANGED
) // spell load case
1275 sLog
.outError("Pet::addSpell: Non-existed in SpellStore spell #%u request, deleting for all pets in `pet_spell`.",spell_id
);
1276 CharacterDatabase
.PExecute("DELETE FROM pet_spell WHERE spell = '%u'",spell_id
);
1279 sLog
.outError("Pet::addSpell: Non-existed in SpellStore spell #%u request.",spell_id
);
1284 PetSpellMap::iterator itr
= m_spells
.find(spell_id
);
1285 if (itr
!= m_spells
.end())
1287 if (itr
->second
.state
== PETSPELL_REMOVED
)
1289 m_spells
.erase(itr
);
1290 state
= PETSPELL_CHANGED
;
1292 else if (state
== PETSPELL_UNCHANGED
&& itr
->second
.state
!= PETSPELL_UNCHANGED
)
1294 // can be in case spell loading but learned at some previous spell loading
1295 itr
->second
.state
= PETSPELL_UNCHANGED
;
1297 if(active
== ACT_ENABLED
)
1298 ToggleAutocast(spell_id
, true);
1299 else if(active
== ACT_DISABLED
)
1300 ToggleAutocast(spell_id
, false);
1308 uint32 oldspell_id
= 0;
1311 newspell
.state
= state
;
1312 newspell
.type
= type
;
1314 if(active
== ACT_DECIDE
) //active was not used before, so we save it's autocast/passive state here
1316 if(IsPassiveSpell(spell_id
))
1317 newspell
.active
= ACT_PASSIVE
;
1319 newspell
.active
= ACT_DISABLED
;
1322 newspell
.active
= active
;
1324 // talent: unlearn all other talent ranks (high and low)
1325 if(TalentSpellPos
const* talentPos
= GetTalentSpellPos(spell_id
))
1327 if(TalentEntry
const *talentInfo
= sTalentStore
.LookupEntry( talentPos
->talent_id
))
1329 for(int i
=0; i
< MAX_TALENT_RANK
; ++i
)
1331 // skip learning spell and no rank spell case
1332 uint32 rankSpellId
= talentInfo
->RankID
[i
];
1333 if(!rankSpellId
|| rankSpellId
==spell_id
)
1336 // skip unknown ranks
1337 if(!HasSpell(rankSpellId
))
1339 removeSpell(rankSpellId
,false,false);
1343 else if(sSpellMgr
.GetSpellRank(spell_id
)!=0)
1345 for (PetSpellMap::const_iterator itr2
= m_spells
.begin(); itr2
!= m_spells
.end(); ++itr2
)
1347 if(itr2
->second
.state
== PETSPELL_REMOVED
) continue;
1349 if( sSpellMgr
.IsRankSpellDueToSpell(spellInfo
,itr2
->first
) )
1351 // replace by new high rank
1352 if(sSpellMgr
.IsHighRankOfSpell(spell_id
,itr2
->first
))
1354 newspell
.active
= itr2
->second
.active
;
1356 if(newspell
.active
== ACT_ENABLED
)
1357 ToggleAutocast(itr2
->first
, false);
1359 oldspell_id
= itr2
->first
;
1360 unlearnSpell(itr2
->first
,false,false);
1363 // ignore new lesser rank
1364 else if(sSpellMgr
.IsHighRankOfSpell(itr2
->first
,spell_id
))
1370 m_spells
[spell_id
] = newspell
;
1372 if (IsPassiveSpell(spell_id
))
1373 CastSpell(this, spell_id
, true);
1375 m_charmInfo
->AddSpellToActionBar(spell_id
);
1377 if(newspell
.active
== ACT_ENABLED
)
1378 ToggleAutocast(spell_id
, true);
1380 uint32 talentCost
= GetTalentSpellCost(spell_id
);
1383 int32 free_points
= GetMaxTalentPointsForLevel(getLevel());
1384 m_usedTalentCount
+=talentCost
;
1385 // update free talent points
1386 free_points
-=m_usedTalentCount
;
1387 SetFreeTalentPoints(free_points
> 0 ? free_points
: 0);
1392 bool Pet::learnSpell(uint32 spell_id
)
1394 // prevent duplicated entires in spell book
1395 if (!addSpell(spell_id
))
1400 Unit
* owner
= GetOwner();
1401 if(owner
&& owner
->GetTypeId() == TYPEID_PLAYER
)
1403 WorldPacket
data(SMSG_PET_LEARNED_SPELL
, 4);
1404 data
<< uint32(spell_id
);
1405 ((Player
*)owner
)->GetSession()->SendPacket(&data
);
1407 ((Player
*)owner
)->PetSpellInitialize();
1413 void Pet::InitLevelupSpellsForLevel()
1415 uint32 level
= getLevel();
1417 if(PetLevelupSpellSet
const *levelupSpells
= GetCreatureInfo()->family
? sSpellMgr
.GetPetLevelupSpellList(GetCreatureInfo()->family
) : NULL
)
1419 // PetLevelupSpellSet ordered by levels, process in reversed order
1420 for(PetLevelupSpellSet::const_reverse_iterator itr
= levelupSpells
->rbegin(); itr
!= levelupSpells
->rend(); ++itr
)
1422 // will called first if level down
1423 if(itr
->first
> level
)
1424 unlearnSpell(itr
->second
,true); // will learn prev rank if any
1425 // will called if level up
1427 learnSpell(itr
->second
); // will unlearn prev rank if any
1431 int32 petSpellsId
= GetCreatureInfo()->PetSpellDataId
? -(int32
)GetCreatureInfo()->PetSpellDataId
: GetEntry();
1433 // default spells (can be not learned if pet level (as owner level decrease result for example) less first possible in normal game)
1434 if(PetDefaultSpellsEntry
const *defSpells
= sSpellMgr
.GetPetDefaultSpellsEntry(petSpellsId
))
1436 for(int i
= 0; i
< MAX_CREATURE_SPELL_DATA_SLOT
; ++i
)
1438 SpellEntry
const* spellEntry
= sSpellStore
.LookupEntry(defSpells
->spellid
[i
]);
1442 // will called first if level down
1443 if(spellEntry
->spellLevel
> level
)
1444 unlearnSpell(spellEntry
->Id
,true);
1445 // will called if level up
1447 learnSpell(spellEntry
->Id
);
1452 bool Pet::unlearnSpell(uint32 spell_id
, bool learn_prev
, bool clear_ab
)
1454 if(removeSpell(spell_id
,learn_prev
,clear_ab
))
1458 if (Unit
* owner
= GetOwner())
1460 if(owner
->GetTypeId() == TYPEID_PLAYER
)
1462 WorldPacket
data(SMSG_PET_REMOVED_SPELL
, 4);
1463 data
<< uint32(spell_id
);
1464 ((Player
*)owner
)->GetSession()->SendPacket(&data
);
1473 bool Pet::removeSpell(uint32 spell_id
, bool learn_prev
, bool clear_ab
)
1475 PetSpellMap::iterator itr
= m_spells
.find(spell_id
);
1476 if (itr
== m_spells
.end())
1479 if(itr
->second
.state
== PETSPELL_REMOVED
)
1482 if(itr
->second
.state
== PETSPELL_NEW
)
1483 m_spells
.erase(itr
);
1485 itr
->second
.state
= PETSPELL_REMOVED
;
1487 RemoveAurasDueToSpell(spell_id
);
1489 uint32 talentCost
= GetTalentSpellCost(spell_id
);
1492 if (m_usedTalentCount
> talentCost
)
1493 m_usedTalentCount
-=talentCost
;
1495 m_usedTalentCount
= 0;
1496 // update free talent points
1497 int32 free_points
= GetMaxTalentPointsForLevel(getLevel()) - m_usedTalentCount
;
1498 SetFreeTalentPoints(free_points
> 0 ? free_points
: 0);
1503 if (uint32 prev_id
= sSpellMgr
.GetPrevSpellInChain (spell_id
))
1504 learnSpell(prev_id
);
1509 // if remove last rank or non-ranked then update action bar at server and client if need
1510 if (clear_ab
&& !learn_prev
&& m_charmInfo
->RemoveSpellFromActionBar(spell_id
))
1514 // need update action bar for last removed rank
1515 if (Unit
* owner
= GetOwner())
1516 if (owner
->GetTypeId() == TYPEID_PLAYER
)
1517 ((Player
*)owner
)->PetSpellInitialize();
1525 void Pet::CleanupActionBar()
1527 for(int i
= 0; i
< MAX_UNIT_ACTION_BAR_INDEX
; ++i
)
1528 if(UnitActionBarEntry
const* ab
= m_charmInfo
->GetActionBarEntry(i
))
1529 if(uint32 action
= ab
->GetAction())
1530 if(ab
->IsActionBarForSpell() && !HasSpell(action
))
1531 m_charmInfo
->SetActionBar(i
,0,ACT_DISABLED
);
1534 void Pet::InitPetCreateSpells()
1536 m_charmInfo
->InitPetActionBar();
1541 CastPetAuras(false);
1544 bool Pet::resetTalents(bool no_cost
)
1546 Unit
*owner
= GetOwner();
1547 if (!owner
|| owner
->GetTypeId()!=TYPEID_PLAYER
)
1550 // not need after this call
1551 if(((Player
*)owner
)->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS
))
1552 ((Player
*)owner
)->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS
,true);
1554 CreatureInfo
const * ci
= GetCreatureInfo();
1557 // Check pet talent type
1558 CreatureFamilyEntry
const *pet_family
= sCreatureFamilyStore
.LookupEntry(ci
->family
);
1559 if(!pet_family
|| pet_family
->petTalentType
< 0)
1562 Player
*player
= (Player
*)owner
;
1564 uint32 level
= getLevel();
1565 uint32 talentPointsForLevel
= GetMaxTalentPointsForLevel(level
);
1567 if (m_usedTalentCount
== 0)
1569 SetFreeTalentPoints(talentPointsForLevel
);
1577 cost
= resetTalentsCost();
1579 if (player
->GetMoney() < cost
)
1581 player
->SendBuyError( BUY_ERR_NOT_ENOUGHT_MONEY
, 0, 0, 0);
1586 for (unsigned int i
= 0; i
< sTalentStore
.GetNumRows(); ++i
)
1588 TalentEntry
const *talentInfo
= sTalentStore
.LookupEntry(i
);
1590 if (!talentInfo
) continue;
1592 TalentTabEntry
const *talentTabInfo
= sTalentTabStore
.LookupEntry( talentInfo
->TalentTab
);
1597 // unlearn only talents for pets family talent type
1598 if(!((1 << pet_family
->petTalentType
) & talentTabInfo
->petTalentMask
))
1601 for (int j
= 0; j
< MAX_TALENT_RANK
; j
++)
1603 for(PetSpellMap::const_iterator itr
= m_spells
.begin(); itr
!= m_spells
.end();)
1605 if(itr
->second
.state
== PETSPELL_REMOVED
)
1610 // remove learned spells (all ranks)
1611 uint32 itrFirstId
= sSpellMgr
.GetFirstSpellInChain(itr
->first
);
1613 // unlearn if first rank is talent or learned by talent
1614 if (itrFirstId
== talentInfo
->RankID
[j
] || sSpellMgr
.IsSpellLearnToSpell(talentInfo
->RankID
[j
],itrFirstId
))
1616 removeSpell(itr
->first
,false);
1617 itr
= m_spells
.begin();
1626 SetFreeTalentPoints(talentPointsForLevel
);
1630 player
->ModifyMoney(-(int32
)cost
);
1632 m_resetTalentsCost
= cost
;
1633 m_resetTalentsTime
= time(NULL
);
1635 player
->PetSpellInitialize();
1639 void Pet::resetTalentsForAllPetsOf(Player
* owner
, Pet
* online_pet
/*= NULL*/)
1641 // not need after this call
1642 if(((Player
*)owner
)->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS
))
1643 ((Player
*)owner
)->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS
,true);
1647 online_pet
->resetTalents(true);
1649 // now need only reset for offline pets (all pets except online case)
1650 uint32 except_petnumber
= online_pet
? online_pet
->GetCharmInfo()->GetPetNumber() : 0;
1652 QueryResult
*resultPets
= CharacterDatabase
.PQuery(
1653 "SELECT id FROM character_pet WHERE owner = '%u' AND id <> '%u'",
1654 owner
->GetGUIDLow(),except_petnumber
);
1660 QueryResult
*result
= CharacterDatabase
.PQuery(
1661 "SELECT DISTINCT pet_spell.spell FROM pet_spell, character_pet "
1662 "WHERE character_pet.owner = '%u' AND character_pet.id = pet_spell.guid AND character_pet.id <> %u",
1663 owner
->GetGUIDLow(),except_petnumber
);
1671 bool need_comma
= false;
1672 std::ostringstream ss
;
1673 ss
<< "DELETE FROM pet_spell WHERE guid IN (";
1677 Field
*fields
= resultPets
->Fetch();
1679 uint32 id
= fields
[0].GetUInt32();
1688 while( resultPets
->NextRow() );
1692 ss
<< ") AND spell IN (";
1694 bool need_execute
= false;
1697 Field
*fields
= result
->Fetch();
1699 uint32 spell
= fields
[0].GetUInt32();
1701 if(!GetTalentSpellCost(spell
))
1709 need_execute
= true;
1711 while( result
->NextRow() );
1720 CharacterDatabase
.Execute(ss
.str().c_str());
1723 void Pet::InitTalentForLevel()
1725 uint32 level
= getLevel();
1726 uint32 talentPointsForLevel
= GetMaxTalentPointsForLevel(level
);
1727 // Reset talents in case low level (on level down) or wrong points for level (hunter can unlearn TP increase talent)
1728 if(talentPointsForLevel
== 0 || m_usedTalentCount
> talentPointsForLevel
)
1730 // Remove all talent points
1733 SetFreeTalentPoints(talentPointsForLevel
- m_usedTalentCount
);
1735 Unit
*owner
= GetOwner();
1736 if (!owner
|| owner
->GetTypeId() != TYPEID_PLAYER
)
1740 ((Player
*)owner
)->SendTalentsInfoData(true);
1743 uint32
Pet::resetTalentsCost() const
1745 uint32 days
= uint32(sWorld
.GetGameTime() - m_resetTalentsTime
)/DAY
;
1747 // The first time reset costs 10 silver; after 1 day cost is reset to 10 silver
1748 if(m_resetTalentsCost
< 10*SILVER
|| days
> 0)
1751 else if(m_resetTalentsCost
< 50*SILVER
)
1754 else if(m_resetTalentsCost
< 1*GOLD
)
1756 // then increasing at a rate of 1 gold; cap 10 gold
1758 return (m_resetTalentsCost
+ 1*GOLD
> 10*GOLD
? 10*GOLD
: m_resetTalentsCost
+ 1*GOLD
);
1761 uint8
Pet::GetMaxTalentPointsForLevel(uint32 level
)
1763 uint8 points
= (level
>= 20) ? ((level
- 16) / 4) : 0;
1764 // Mod points from owner SPELL_AURA_MOD_PET_TALENT_POINTS
1765 if (Unit
*owner
= GetOwner())
1766 points
+=owner
->GetTotalAuraModifier(SPELL_AURA_MOD_PET_TALENT_POINTS
);
1770 void Pet::ToggleAutocast(uint32 spellid
, bool apply
)
1772 if(IsPassiveSpell(spellid
))
1775 PetSpellMap::iterator itr
= m_spells
.find(spellid
);
1781 for (i
= 0; i
< m_autospells
.size() && m_autospells
[i
] != spellid
; ++i
)
1784 if (i
== m_autospells
.size())
1786 m_autospells
.push_back(spellid
);
1788 if(itr
->second
.active
!= ACT_ENABLED
)
1790 itr
->second
.active
= ACT_ENABLED
;
1791 if(itr
->second
.state
!= PETSPELL_NEW
)
1792 itr
->second
.state
= PETSPELL_CHANGED
;
1798 AutoSpellList::iterator itr2
= m_autospells
.begin();
1799 for (i
= 0; i
< m_autospells
.size() && m_autospells
[i
] != spellid
; ++i
, itr2
++)
1802 if (i
< m_autospells
.size())
1804 m_autospells
.erase(itr2
);
1805 if(itr
->second
.active
!= ACT_DISABLED
)
1807 itr
->second
.active
= ACT_DISABLED
;
1808 if(itr
->second
.state
!= PETSPELL_NEW
)
1809 itr
->second
.state
= PETSPELL_CHANGED
;
1815 bool Pet::IsPermanentPetFor(Player
* owner
)
1817 switch(getPetType())
1820 switch(owner
->getClass())
1823 return GetCreatureInfo()->type
== CREATURE_TYPE_DEMON
;
1824 case CLASS_DEATH_KNIGHT
:
1825 return GetCreatureInfo()->type
== CREATURE_TYPE_UNDEAD
;
1836 bool Pet::Create(uint32 guidlow
, Map
*map
, uint32 phaseMask
, uint32 Entry
, uint32 pet_number
)
1839 SetPhaseMask(phaseMask
,false);
1841 Object::_Create(guidlow
, pet_number
, HIGHGUID_PET
);
1843 m_DBTableGuid
= guidlow
;
1844 m_originalEntry
= Entry
;
1846 if(!InitEntry(Entry
))
1849 SetSheath(SHEATH_STATE_MELEE
);
1851 if(getPetType() == MINI_PET
) // always non-attackable
1852 SetFlag(UNIT_FIELD_FLAGS
, UNIT_FLAG_NON_ATTACKABLE
);
1857 bool Pet::HasSpell(uint32 spell
) const
1859 PetSpellMap::const_iterator itr
= m_spells
.find(spell
);
1860 return (itr
!= m_spells
.end() && itr
->second
.state
!= PETSPELL_REMOVED
);
1863 // Get all passive spells in our skill line
1864 void Pet::LearnPetPassives()
1866 CreatureInfo
const* cInfo
= GetCreatureInfo();
1870 CreatureFamilyEntry
const* cFamily
= sCreatureFamilyStore
.LookupEntry(cInfo
->family
);
1874 PetFamilySpellsStore::const_iterator petStore
= sPetFamilySpellsStore
.find(cFamily
->ID
);
1875 if(petStore
!= sPetFamilySpellsStore
.end())
1877 for(PetFamilySpellsSet::const_iterator petSet
= petStore
->second
.begin(); petSet
!= petStore
->second
.end(); ++petSet
)
1878 addSpell(*petSet
, ACT_DECIDE
, PETSPELL_NEW
, PETSPELL_FAMILY
);
1882 void Pet::CastPetAuras(bool current
)
1884 Unit
* owner
= GetOwner();
1885 if(!owner
|| owner
->GetTypeId()!=TYPEID_PLAYER
)
1888 for(PetAuraSet::const_iterator itr
= owner
->m_petAuras
.begin(); itr
!= owner
->m_petAuras
.end();)
1890 PetAura
const* pa
= *itr
;
1893 if(!current
&& pa
->IsRemovedOnChangePet())
1894 owner
->RemovePetAura(pa
);
1900 void Pet::CastPetAura(PetAura
const* aura
)
1902 uint32 auraId
= aura
->GetAura(GetEntry());
1906 if(auraId
== 35696) // Demonic Knowledge
1908 int32 basePoints
= int32(aura
->GetDamage() * (GetStat(STAT_STAMINA
) + GetStat(STAT_INTELLECT
)) / 100);
1909 CastCustomSpell(this, auraId
, &basePoints
, NULL
, NULL
, true);
1912 CastSpell(this, auraId
, true);
1915 struct DoPetLearnSpell
1917 DoPetLearnSpell(Pet
& _pet
) : pet(_pet
) {}
1918 void operator() (uint32 spell_id
) { pet
.learnSpell(spell_id
); }
1922 void Pet::learnSpellHighRank(uint32 spellid
)
1924 learnSpell(spellid
);
1926 DoPetLearnSpell
worker(*this);
1927 sSpellMgr
.doForHighRanks(spellid
,worker
);
1930 void Pet::SynchronizeLevelWithOwner()
1932 Unit
* owner
= GetOwner();
1933 if (!owner
|| owner
->GetTypeId() != TYPEID_PLAYER
)
1936 switch(getPetType())
1938 // always same level
1940 GivePetLevel(owner
->getLevel());
1942 // can't be greater owner level
1944 if(getLevel() > owner
->getLevel())
1946 GivePetLevel(owner
->getLevel());
1947 SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
, sObjectMgr
.GetXPForPetLevel(owner
->getLevel()));
1948 SetUInt32Value(UNIT_FIELD_PETEXPERIENCE
, GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP
)-1);
1956 void Pet::ApplyModeFlags(PetModeFlags mode
, bool apply
)
1959 m_petModeFlags
= PetModeFlags(m_petModeFlags
| mode
);
1961 m_petModeFlags
= PetModeFlags(m_petModeFlags
& ~mode
);
1963 Unit
* owner
= GetOwner();
1964 if(!owner
|| owner
->GetTypeId()!=TYPEID_PLAYER
)
1967 WorldPacket
data(SMSG_PET_MODE
, 12);
1968 data
<< uint64(GetGUID());
1969 data
<< uint32(m_petModeFlags
);
1970 ((Player
*)owner
)->GetSession()->SendPacket(&data
);