2 * Copyright (C) 2005-2008 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 "WorldPacket.h"
21 #include "WorldSession.h"
23 #include "ObjectMgr.h"
28 #include "ObjectAccessor.h"
29 #include "MapManager.h"
30 #include "CreatureAI.h"
35 void WorldSession::HandlePetAction( WorldPacket
& recv_data
)
37 CHECK_PACKET_SIZE(recv_data
, 8+2+2+8);
43 recv_data
>> guid1
; //pet guid
45 recv_data
>> flag
; //delete = 0x0700 CastSpell = C100
46 recv_data
>> guid2
; //tag guid
48 // used also for charmed creature
49 Unit
* pet
= ObjectAccessor::GetUnit(*_player
, guid1
);
50 sLog
.outDetail("HandlePetAction.Pet %u flag is %u, spellid is %u, target %u.\n", uint32(GUID_LOPART(guid1
)), flag
, spellid
, uint32(GUID_LOPART(guid2
)) );
53 sLog
.outError( "Pet %u not exist.\n", uint32(GUID_LOPART(guid1
)) );
57 if(pet
!= GetPlayer()->GetPet() && pet
!= GetPlayer()->GetCharm())
59 sLog
.outError("HandlePetAction.Pet %u isn't pet of player %s.\n", uint32(GUID_LOPART(guid1
)), GetPlayer()->GetName() );
66 if(pet
->GetTypeId() == TYPEID_PLAYER
&& !(flag
== ACT_COMMAND
&& spellid
== COMMAND_ATTACK
))
69 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
72 sLog
.outError("WorldSession::HandlePetAction: object "I64FMTD
" is considered pet-like but doesn't have a charminfo!", pet
->GetGUID());
78 case ACT_COMMAND
: //0x0700
81 case COMMAND_STAY
: //flat=1792 //STAY
83 pet
->GetMotionMaster()->Clear();
84 pet
->GetMotionMaster()->MoveIdle();
85 charmInfo
->SetCommandState( COMMAND_STAY
);
87 case COMMAND_FOLLOW
: //spellid=1792 //FOLLOW
89 pet
->GetMotionMaster()->MoveFollow(_player
,PET_FOLLOW_DIST
,PET_FOLLOW_ANGLE
);
90 charmInfo
->SetCommandState( COMMAND_FOLLOW
);
92 case COMMAND_ATTACK
: //spellid=1792 //ATTACK
94 // only place where pet can be player
95 pet
->clearUnitState(UNIT_STAT_FOLLOW
);
96 uint64 selguid
= _player
->GetSelection();
97 Unit
*TargetUnit
= ObjectAccessor::GetUnit(*_player
, selguid
);
101 // not let attack friendly units.
102 if(GetPlayer()->IsFriendlyTo(TargetUnit
))
104 // Not let attack through obstructions
105 if(!pet
->IsWithinLOSInMap(TargetUnit
))
111 if(pet
->GetTypeId() != TYPEID_PLAYER
)
113 pet
->GetMotionMaster()->Clear();
114 if (((Creature
*)pet
)->AI())
115 ((Creature
*)pet
)->AI()->AttackStart(TargetUnit
);
117 //10% chance to play special pet attack talk, else growl
118 if(((Creature
*)pet
)->isPet() && ((Pet
*)pet
)->getPetType() == SUMMON_PET
&& pet
!= TargetUnit
&& urand(0, 100) < 10)
119 pet
->SendPetTalk((uint32
)PET_TALK_ATTACK
);
122 // 90% chance for pet and 100% chance for charmed creature
123 pet
->SendPetAIReaction(guid1
);
126 else // charmed player
128 pet
->Attack(TargetUnit
,true);
129 pet
->SendPetAIReaction(guid1
);
133 case COMMAND_ABANDON
: // abandon (hunter pet) or dismiss (summoned pet)
134 if(((Creature
*)pet
)->isPet())
137 if(p
->getPetType() == HUNTER_PET
)
138 _player
->RemovePet(p
,PET_SAVE_AS_DELETED
);
140 //dismissing a summoned pet is like killing them (this prevents returning a soulshard...)
141 p
->setDeathState(CORPSE
);
147 sLog
.outError("WORLD: unknown PET flag Action %i and spellid %i.\n", flag
, spellid
);
150 case ACT_REACTION
: // 0x600
153 case REACT_PASSIVE
: //passive
154 case REACT_DEFENSIVE
: //recovery
155 case REACT_AGGRESSIVE
: //activete
156 charmInfo
->SetReactState( ReactStates(spellid
) );
160 case ACT_DISABLED
: // 0x8100 spell (disabled), ignore
161 case ACT_PASSIVE
: // 0x0100
162 case ACT_ENABLED
: // 0xC100 spell
166 unit_target
= ObjectAccessor::GetUnit(*_player
,guid2
);
170 if (((Creature
*)pet
)->GetGlobalCooldown() > 0)
173 // do not cast unknown spells
174 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spellid
);
177 sLog
.outError("WORLD: unknown PET spell id %i\n", spellid
);
181 for(uint32 i
= 0; i
< 3;i
++)
183 if(spellInfo
->EffectImplicitTargetA
[i
] == TARGET_ALL_ENEMY_IN_AREA
|| spellInfo
->EffectImplicitTargetA
[i
] == TARGET_ALL_ENEMY_IN_AREA_INSTANT
|| spellInfo
->EffectImplicitTargetA
[i
] == TARGET_ALL_ENEMY_IN_AREA_CHANNELED
)
187 // do not cast not learned spells
188 if(!pet
->HasSpell(spellid
) || IsPassiveSpell(spellid
))
191 pet
->clearUnitState(UNIT_STAT_FOLLOW
);
193 Spell
*spell
= new Spell(pet
, spellInfo
, false);
195 int16 result
= spell
->PetCanCast(unit_target
);
197 //auto turn to target unless possessed
198 if(result
== SPELL_FAILED_UNIT_NOT_INFRONT
&& !pet
->HasAuraType(SPELL_AURA_MOD_POSSESS
))
200 pet
->SetInFront(unit_target
);
201 if( unit_target
->GetTypeId() == TYPEID_PLAYER
)
202 pet
->SendUpdateToPlayer( (Player
*)unit_target
);
203 if(Unit
* powner
= pet
->GetCharmerOrOwner())
204 if(powner
->GetTypeId() == TYPEID_PLAYER
)
205 pet
->SendUpdateToPlayer((Player
*)powner
);
211 ((Creature
*)pet
)->AddCreatureSpellCooldown(spellid
);
212 if (((Creature
*)pet
)->isPet())
213 ((Pet
*)pet
)->CheckLearning(spellid
);
215 unit_target
= spell
->m_targets
.getUnitTarget();
217 //10% chance to play special pet attack talk, else growl
218 //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell
219 if(((Creature
*)pet
)->isPet() && (((Pet
*)pet
)->getPetType() == SUMMON_PET
) && (pet
!= unit_target
) && (urand(0, 100) < 10))
220 pet
->SendPetTalk((uint32
)PET_TALK_SPECIAL_SPELL
);
223 pet
->SendPetAIReaction(guid1
);
226 if( unit_target
&& !GetPlayer()->IsFriendlyTo(unit_target
) && !pet
->HasAuraType(SPELL_AURA_MOD_POSSESS
))
228 pet
->clearUnitState(UNIT_STAT_FOLLOW
);
231 pet
->GetMotionMaster()->Clear();
232 if (((Creature
*)pet
)->AI())
233 ((Creature
*)pet
)->AI()->AttackStart(unit_target
);
236 spell
->prepare(&(spell
->m_targets
));
240 if(pet
->HasAuraType(SPELL_AURA_MOD_POSSESS
))
242 WorldPacket
data(SMSG_CAST_FAILED
, (4+1+1));
243 data
<< uint8(0) << uint32(spellid
) << uint8(result
);
246 case SPELL_FAILED_REQUIRES_SPELL_FOCUS
:
247 data
<< uint32(spellInfo
->RequiresSpellFocus
);
253 pet
->SendPetCastFail(spellid
, result
);
255 if(!((Creature
*)pet
)->HasSpellCooldown(spellid
))
256 pet
->SendPetClearCooldown(spellid
);
258 spell
->finish(false);
264 sLog
.outError("WORLD: unknown PET flag Action %i and spellid %i.\n", flag
, spellid
);
268 void WorldSession::HandlePetNameQuery( WorldPacket
& recv_data
)
270 CHECK_PACKET_SIZE(recv_data
,4+8);
272 sLog
.outDetail( "HandlePetNameQuery. CMSG_PET_NAME_QUERY\n" );
277 recv_data
>> petnumber
;
278 recv_data
>> petguid
;
280 SendPetNameQuery(petguid
,petnumber
);
283 void WorldSession::SendPetNameQuery( uint64 petguid
, uint32 petnumber
)
285 Creature
* pet
= ObjectAccessor::GetCreatureOrPet(*_player
, petguid
);
286 if(!pet
|| !pet
->GetCharmInfo() || pet
->GetCharmInfo()->GetPetNumber() != petnumber
)
289 std::string name
= pet
->GetName();
291 WorldPacket
data(SMSG_PET_NAME_QUERY_RESPONSE
, (4+4+name
.size()+1));
292 data
<< uint32(petnumber
);
293 data
<< name
.c_str();
294 data
<< uint32(pet
->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP
));
296 if( pet
->isPet() && ((Pet
*)pet
)->GetDeclinedNames() )
299 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
300 data
<< ((Pet
*)pet
)->GetDeclinedNames()->name
[i
];
305 _player
->GetSession()->SendPacket(&data
);
308 void WorldSession::HandlePetSetAction( WorldPacket
& recv_data
)
310 CHECK_PACKET_SIZE(recv_data
, 8+4+2+2);
312 sLog
.outDetail( "HandlePetSetAction. CMSG_PET_SET_ACTION\n" );
320 recv_data
>> petguid
;
322 // FIXME: charmed case
323 //Pet* pet = ObjectAccessor::Instance().GetPet(petguid);
324 if(ObjectAccessor::FindPlayer(petguid
))
327 Creature
* pet
= ObjectAccessor::GetCreatureOrPet(*_player
, petguid
);
329 if(!pet
|| (pet
!= _player
->GetPet() && pet
!= _player
->GetCharm()))
331 sLog
.outError( "HandlePetSetAction: Unknown pet or pet owner.\n" );
335 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
338 sLog
.outError("WorldSession::HandlePetSetAction: object "I64FMTD
" is considered pet-like but doesn't have a charminfo!", pet
->GetGUID());
342 count
= (recv_data
.size() == 24) ? 2 : 1;
343 for(uint8 i
= 0; i
< count
; i
++)
345 recv_data
>> position
;
346 recv_data
>> spell_id
;
347 recv_data
>> act_state
;
349 sLog
.outDetail( "Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X\n", _player
->GetName(), position
, spell_id
, act_state
);
351 //if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add
352 if(!((act_state
== ACT_ENABLED
|| act_state
== ACT_DISABLED
|| act_state
== ACT_PASSIVE
) && spell_id
&& !pet
->HasSpell(spell_id
)))
355 if(act_state
== ACT_ENABLED
&& spell_id
)
358 charmInfo
->ToggleCreatureAutocast(spell_id
, true);
360 ((Pet
*)pet
)->ToggleAutocast(spell_id
, true);
362 //sign for no/turn off autocast
363 else if(act_state
== ACT_DISABLED
&& spell_id
)
366 charmInfo
->ToggleCreatureAutocast(spell_id
, false);
368 ((Pet
*)pet
)->ToggleAutocast(spell_id
, false);
371 charmInfo
->GetActionBarEntry(position
)->Type
= act_state
;
372 charmInfo
->GetActionBarEntry(position
)->SpellOrAction
= spell_id
;
377 void WorldSession::HandlePetRename( WorldPacket
& recv_data
)
379 CHECK_PACKET_SIZE(recv_data
, 8+1);
381 sLog
.outDetail( "HandlePetRename. CMSG_PET_RENAME\n" );
387 DeclinedName declinedname
;
389 recv_data
>> petguid
;
391 CHECK_PACKET_SIZE(recv_data
, recv_data
.rpos() + 1);
392 recv_data
>> isdeclined
;
394 Pet
* pet
= ObjectAccessor::GetPet(petguid
);
396 if( !pet
|| !pet
->isPet() || ((Pet
*)pet
)->getPetType()!= HUNTER_PET
||
397 pet
->GetByteValue(UNIT_FIELD_BYTES_2
, 2) != UNIT_RENAME_ALLOWED
||
398 pet
->GetOwnerGUID() != _player
->GetGUID() || !pet
->GetCharmInfo() )
401 if(!ObjectMgr::IsValidPetName(name
))
403 SendPetNameInvalid(PET_NAME_INVALID
, name
, NULL
);
407 if(objmgr
.IsReservedName(name
))
409 SendPetNameInvalid(PET_NAME_RESERVED
, name
, NULL
);
415 Unit
*owner
= pet
->GetOwner();
416 if(owner
&& (owner
->GetTypeId() == TYPEID_PLAYER
) && ((Player
*)owner
)->GetGroup())
417 ((Player
*)owner
)->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_NAME
);
419 pet
->SetByteValue(UNIT_FIELD_BYTES_2
, 2, UNIT_RENAME_NOT_ALLOWED
);
423 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
425 CHECK_PACKET_SIZE(recv_data
, recv_data
.rpos() + 1);
426 recv_data
>> declinedname
.name
[i
];
430 Utf8toWStr(name
, wname
);
431 if(!ObjectMgr::CheckDeclinedNames(GetMainPartOfName(wname
,0),declinedname
))
433 SendPetNameInvalid(PET_NAME_DECLENSION_DOESNT_MATCH_BASE_NAME
, name
, &declinedname
);
438 CharacterDatabase
.BeginTransaction();
441 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
442 CharacterDatabase
.escape_string(declinedname
.name
[i
]);
443 CharacterDatabase
.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u' AND id = '%u'", _player
->GetGUIDLow(), pet
->GetCharmInfo()->GetPetNumber());
444 CharacterDatabase
.PExecute("INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u','%u','%s','%s','%s','%s','%s')",
445 pet
->GetCharmInfo()->GetPetNumber(), _player
->GetGUIDLow(), declinedname
.name
[0].c_str(), declinedname
.name
[1].c_str(), declinedname
.name
[2].c_str(), declinedname
.name
[3].c_str(), declinedname
.name
[4].c_str());
448 CharacterDatabase
.escape_string(name
);
449 CharacterDatabase
.PExecute("UPDATE character_pet SET name = '%s', renamed = '1' WHERE owner = '%u' AND id = '%u'", name
.c_str(), _player
->GetGUIDLow(), pet
->GetCharmInfo()->GetPetNumber());
450 CharacterDatabase
.CommitTransaction();
452 pet
->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP
, time(NULL
));
455 void WorldSession::HandlePetAbandon( WorldPacket
& recv_data
)
457 CHECK_PACKET_SIZE(recv_data
, 8);
460 recv_data
>> guid
; //pet guid
461 sLog
.outDetail( "HandlePetAbandon. CMSG_PET_ABANDON pet guid is %u", GUID_LOPART(guid
) );
464 Creature
* pet
= ObjectAccessor::GetCreatureOrPet(*_player
, guid
);
469 if(pet
->GetGUID() == _player
->GetPetGUID())
471 uint32 feelty
= pet
->GetPower(POWER_HAPPINESS
);
472 pet
->SetPower(POWER_HAPPINESS
,(feelty
-50000) > 0 ?(feelty
-50000) : 0);
475 _player
->RemovePet((Pet
*)pet
,PET_SAVE_AS_DELETED
);
477 else if(pet
->GetGUID() == _player
->GetCharmGUID())
484 void WorldSession::HandlePetUnlearnOpcode(WorldPacket
& recvPacket
)
486 CHECK_PACKET_SIZE(recvPacket
,8);
488 sLog
.outDetail("CMSG_PET_UNLEARN");
492 Pet
* pet
= _player
->GetPet();
494 if(!pet
|| pet
->getPetType() != HUNTER_PET
|| pet
->m_spells
.size() <= 1)
497 if(guid
!= pet
->GetGUID())
499 sLog
.outError( "HandlePetUnlearnOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid
)),GetPlayer()->GetName() );
503 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
506 sLog
.outError("WorldSession::HandlePetUnlearnOpcode: object "I64FMTD
" is considered pet-like but doesn't have a charminfo!", pet
->GetGUID());
510 uint32 cost
= pet
->resetTalentsCost();
512 if (GetPlayer()->GetMoney() < cost
)
514 GetPlayer()->SendBuyError( BUY_ERR_NOT_ENOUGHT_MONEY
, 0, 0, 0);
518 for(PetSpellMap::iterator itr
= pet
->m_spells
.begin(); itr
!= pet
->m_spells
.end();)
520 uint32 spell_id
= itr
->first
; // Pet::removeSpell can invalidate iterator at erase NEW spell
522 //pet->removeSpell(spell_id);
523 pet
->unlearnSpell(spell_id
);
526 for(uint8 i
= 0; i
< 10; i
++)
528 if(charmInfo
->GetActionBarEntry(i
)->SpellOrAction
&& charmInfo
->GetActionBarEntry(i
)->Type
== ACT_ENABLED
|| charmInfo
->GetActionBarEntry(i
)->Type
== ACT_DISABLED
)
529 charmInfo
->GetActionBarEntry(i
)->SpellOrAction
= 0;
532 // relearn pet passives
533 pet
->LearnPetPassives();
535 pet
->m_resetTalentsTime
= time(NULL
);
536 pet
->m_resetTalentsCost
= cost
;
537 GetPlayer()->ModifyMoney(-(int32
)cost
);
539 GetPlayer()->PetSpellInitialize();
542 void WorldSession::HandlePetSpellAutocastOpcode( WorldPacket
& recvPacket
)
544 CHECK_PACKET_SIZE(recvPacket
,8+2+2+1);
546 sLog
.outDetail("CMSG_PET_SPELL_AUTOCAST");
549 uint16 spellid2
; //maybe second spell, automatically toggled off when first toggled on?
550 uint8 state
; //1 for on, 0 for off
551 recvPacket
>> guid
>> spellid
>> spellid2
>> state
;
553 if(!_player
->GetPet() && !_player
->GetCharm())
556 if(ObjectAccessor::FindPlayer(guid
))
559 Creature
* pet
=ObjectAccessor::GetCreatureOrPet(*_player
,guid
);
561 if(!pet
|| (pet
!= _player
->GetPet() && pet
!= _player
->GetCharm()))
563 sLog
.outError( "HandlePetSpellAutocastOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid
)),GetPlayer()->GetName() );
567 // do not add not learned spells/ passive spells
568 if(!pet
->HasSpell(spellid
) || IsPassiveSpell(spellid
))
571 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
574 sLog
.outError("WorldSession::HandlePetSpellAutocastOpcod: object "I64FMTD
" is considered pet-like but doesn't have a charminfo!", pet
->GetGUID());
579 //state can be used as boolean
580 pet
->GetCharmInfo()->ToggleCreatureAutocast(spellid
, state
);
582 ((Pet
*)pet
)->ToggleAutocast(spellid
, state
);
584 for(uint8 i
= 0; i
< 10; ++i
)
586 if((charmInfo
->GetActionBarEntry(i
)->Type
== ACT_ENABLED
|| charmInfo
->GetActionBarEntry(i
)->Type
== ACT_DISABLED
) && spellid
== charmInfo
->GetActionBarEntry(i
)->SpellOrAction
)
587 charmInfo
->GetActionBarEntry(i
)->Type
= state
? ACT_ENABLED
: ACT_DISABLED
;
591 void WorldSession::HandlePetCastSpellOpcode( WorldPacket
& recvPacket
)
593 sLog
.outDetail("WORLD: CMSG_PET_CAST_SPELL");
595 CHECK_PACKET_SIZE(recvPacket
,8+1+4+1);
599 uint8 unk_flags
; // flags (if 0x02 - some additional data are received)
601 recvPacket
>> guid
>> cast_count
>> spellid
>> unk_flags
;
603 sLog
.outDebug("WORLD: CMSG_PET_CAST_SPELL, cast_count: %u, spellid %u, unk_flags %u", cast_count
, spellid
, unk_flags
);
605 if(!_player
->GetPet() && !_player
->GetCharm())
608 if(ObjectAccessor::FindPlayer(guid
))
611 Creature
* pet
=ObjectAccessor::GetCreatureOrPet(*_player
,guid
);
613 if(!pet
|| (pet
!= _player
->GetPet() && pet
!= _player
->GetCharm()))
615 sLog
.outError( "HandlePetCastSpellOpcode: Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid
)),GetPlayer()->GetName() );
619 if (pet
->GetGlobalCooldown() > 0)
622 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spellid
);
625 sLog
.outError("WORLD: unknown PET spell id %i\n", spellid
);
629 // do not cast not learned spells
630 if(!pet
->HasSpell(spellid
) || IsPassiveSpell(spellid
))
633 SpellCastTargets targets
;
634 if(!targets
.read(&recvPacket
,pet
))
637 pet
->clearUnitState(UNIT_STAT_FOLLOW
);
639 Spell
*spell
= new Spell(pet
, spellInfo
, false);
640 spell
->m_cast_count
= cast_count
; // probably pending spell cast
641 spell
->m_targets
= targets
;
643 int16 result
= spell
->PetCanCast(NULL
);
646 pet
->AddCreatureSpellCooldown(spellid
);
650 p
->CheckLearning(spellid
);
651 //10% chance to play special pet attack talk, else growl
652 //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell
653 if(p
->getPetType() == SUMMON_PET
&& (urand(0, 100) < 10))
654 pet
->SendPetTalk((uint32
)PET_TALK_SPECIAL_SPELL
);
656 pet
->SendPetAIReaction(guid
);
659 spell
->prepare(&(spell
->m_targets
));
663 pet
->SendPetCastFail(spellid
, result
);
664 if(!pet
->HasSpellCooldown(spellid
))
665 pet
->SendPetClearCooldown(spellid
);
667 spell
->finish(false);
672 void WorldSession::SendPetNameInvalid(uint32 error
, const std::string
& name
, DeclinedName
*declinedName
)
674 WorldPacket
data(SMSG_PET_NAME_INVALID
, 4 + name
.size() + 1 + 1);
675 data
<< uint32(error
);
680 for(uint32 i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
681 data
<< declinedName
->name
[i
];
688 void WorldSession::HandlePetLearnTalent( WorldPacket
& recv_data
)
690 sLog
.outDebug("WORLD: CMSG_PET_LEARN_TALENT");
693 CHECK_PACKET_SIZE(recv_data
, 8+4+4);
696 uint32 talent_id
, requested_rank
;
697 recv_data
>> guid
>> talent_id
>> requested_rank
;
699 Pet
*pet
= _player
->GetPet();
704 if(guid
!= pet
->GetGUID())
707 uint32 CurTalentPoints
= pet
->GetFreeTalentPoints();
709 if(CurTalentPoints
== 0)
712 if (requested_rank
> 4)
715 TalentEntry
const *talentInfo
= sTalentStore
.LookupEntry(talent_id
);
720 TalentTabEntry
const *talentTabInfo
= sTalentTabStore
.LookupEntry(talentInfo
->TalentTab
);
725 CreatureInfo
const *ci
= pet
->GetCreatureInfo();
730 CreatureFamilyEntry
const *pet_family
= sCreatureFamilyStore
.LookupEntry(ci
->family
);
735 if(pet_family
->petTalentType
< 0) // not hunter pet
738 // prevent learn talent for different family (cheating)
739 if(!((1 << pet_family
->petTalentType
) & talentTabInfo
->petTalentMask
))
742 // prevent skip talent ranks (cheating)
743 if(requested_rank
> 0 && !pet
->HasSpell(talentInfo
->RankID
[requested_rank
-1]))
746 // Check if it requires another talent
747 if (talentInfo
->DependsOn
> 0)
749 if(TalentEntry
const *depTalentInfo
= sTalentStore
.LookupEntry(talentInfo
->DependsOn
))
751 bool hasEnoughRank
= false;
752 for (int i
= talentInfo
->DependsOnRank
; i
<= 4; i
++)
754 if (depTalentInfo
->RankID
[i
] != 0)
755 if (pet
->HasSpell(depTalentInfo
->RankID
[i
]))
756 hasEnoughRank
= true;
763 // Find out how many points we have in this field
764 uint32 spentPoints
= 0;
766 uint32 tTab
= talentInfo
->TalentTab
;
767 if (talentInfo
->Row
> 0)
769 unsigned int numRows
= sTalentStore
.GetNumRows();
770 for (unsigned int i
= 0; i
< numRows
; i
++) // Loop through all talents.
772 // Someday, someone needs to revamp
773 const TalentEntry
*tmpTalent
= sTalentStore
.LookupEntry(i
);
774 if (tmpTalent
) // the way talents are tracked
776 if (tmpTalent
->TalentTab
== tTab
)
778 for (int j
= 0; j
<= 4; j
++)
780 if (tmpTalent
->RankID
[j
] != 0)
782 if (pet
->HasSpell(tmpTalent
->RankID
[j
]))
784 spentPoints
+= j
+ 1;
793 // not have required min points spent in talent tree
794 if(spentPoints
< (talentInfo
->Row
* 3))
797 // spell not set in talent.dbc
798 uint32 spellid
= talentInfo
->RankID
[requested_rank
];
801 sLog
.outError("Talent.dbc have for talent: %u Rank: %u spell id = 0", talent_id
, requested_rank
);
806 if(pet
->HasSpell(spellid
))
809 // learn! (other talent ranks will unlearned at learning)
810 pet
->learnSpell(spellid
);
811 sLog
.outDetail("TalentID: %u Rank: %u Spell: %u\n", talent_id
, requested_rank
, spellid
);
813 // update free talent points
814 pet
->SetFreeTalentPoints(CurTalentPoints
- 1);