2 * Copyright (C) 2005-2009 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"
22 #include "ObjectMgr.h"
27 #include "ObjectAccessor.h"
28 #include "CreatureAI.h"
32 void WorldSession::HandlePetAction( WorldPacket
& recv_data
)
37 recv_data
>> guid1
; //pet guid
39 recv_data
>> guid2
; //tag guid
41 uint32 spellid
= UNIT_ACTION_BUTTON_ACTION(data
);
42 uint8 flag
= UNIT_ACTION_BUTTON_TYPE(data
); //delete = 0x07 CastSpell = C1
44 // used also for charmed creature
45 Unit
* pet
= ObjectAccessor::GetUnit(*_player
, guid1
);
46 sLog
.outDetail("HandlePetAction.Pet %u flag is %u, spellid is %u, target %u.", uint32(GUID_LOPART(guid1
)), uint32(flag
), spellid
, uint32(GUID_LOPART(guid2
)) );
49 sLog
.outError( "Pet %u not exist.", uint32(GUID_LOPART(guid1
)) );
53 if(pet
!= GetPlayer()->GetPet() && pet
!= GetPlayer()->GetCharm())
55 sLog
.outError("HandlePetAction.Pet %u isn't pet of player %s.", uint32(GUID_LOPART(guid1
)), GetPlayer()->GetName() );
62 if(pet
->GetTypeId() == TYPEID_PLAYER
&& !(flag
== ACT_COMMAND
&& spellid
== COMMAND_ATTACK
))
65 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
68 sLog
.outError("WorldSession::HandlePetAction: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", pet
->GetGUIDLow(), pet
->GetTypeId());
74 case ACT_COMMAND
: //0x07
77 case COMMAND_STAY
: //flat=1792 //STAY
79 pet
->GetMotionMaster()->Clear();
80 pet
->GetMotionMaster()->MoveIdle();
81 charmInfo
->SetCommandState( COMMAND_STAY
);
83 case COMMAND_FOLLOW
: //spellid=1792 //FOLLOW
85 pet
->GetMotionMaster()->MoveFollow(_player
,PET_FOLLOW_DIST
,PET_FOLLOW_ANGLE
);
86 charmInfo
->SetCommandState( COMMAND_FOLLOW
);
88 case COMMAND_ATTACK
: //spellid=1792 //ATTACK
90 const uint64
& selguid
= _player
->GetSelection();
91 Unit
*TargetUnit
= ObjectAccessor::GetUnit(*_player
, selguid
);
95 // not let attack friendly units.
96 if(GetPlayer()->IsFriendlyTo(TargetUnit
))
98 // Not let attack through obstructions
99 if(!pet
->IsWithinLOSInMap(TargetUnit
))
102 // This is true if pet has no target or has target but targets differs.
103 if(pet
->getVictim() != TargetUnit
)
105 if (pet
->getVictim())
108 if(pet
->GetTypeId() != TYPEID_PLAYER
)
110 pet
->GetMotionMaster()->Clear();
111 if (((Creature
*)pet
)->AI())
112 ((Creature
*)pet
)->AI()->AttackStart(TargetUnit
);
114 //10% chance to play special pet attack talk, else growl
115 if(((Creature
*)pet
)->isPet() && ((Pet
*)pet
)->getPetType() == SUMMON_PET
&& pet
!= TargetUnit
&& urand(0, 100) < 10)
116 pet
->SendPetTalk((uint32
)PET_TALK_ATTACK
);
119 // 90% chance for pet and 100% chance for charmed creature
120 pet
->SendPetAIReaction(guid1
);
123 else // charmed player
125 pet
->Attack(TargetUnit
,true);
126 pet
->SendPetAIReaction(guid1
);
131 case COMMAND_ABANDON
: // abandon (hunter pet) or dismiss (summoned pet)
132 if(((Creature
*)pet
)->isPet())
135 if(p
->getPetType() == HUNTER_PET
)
136 _player
->RemovePet(p
,PET_SAVE_AS_DELETED
);
138 //dismissing a summoned pet is like killing them (this prevents returning a soulshard...)
139 p
->setDeathState(CORPSE
);
145 sLog
.outError("WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag
), spellid
);
148 case ACT_REACTION
: // 0x6
151 case REACT_PASSIVE
: //passive
152 case REACT_DEFENSIVE
: //recovery
153 case REACT_AGGRESSIVE
: //activete
154 charmInfo
->SetReactState( ReactStates(spellid
) );
158 case ACT_DISABLED
: // 0x81 spell (disabled), ignore
159 case ACT_PASSIVE
: // 0x01
160 case ACT_ENABLED
: // 0xC1 spell
162 Unit
* unit_target
= NULL
;
163 if (((Creature
*)pet
)->GetGlobalCooldown() > 0)
167 unit_target
= ObjectAccessor::GetUnit(*_player
,guid2
);
169 // do not cast unknown spells
170 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spellid
);
173 sLog
.outError("WORLD: unknown PET spell id %i", spellid
);
177 for(uint32 i
= 0; i
< 3;++i
)
179 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
)
183 // do not cast not learned spells
184 if(!pet
->HasSpell(spellid
) || IsPassiveSpell(spellid
))
187 pet
->clearUnitState(UNIT_STAT_FOLLOW
);
189 Spell
*spell
= new Spell(pet
, spellInfo
, false);
191 SpellCastResult result
= spell
->CheckPetCast(unit_target
);
193 //auto turn to target unless possessed
194 if(result
== SPELL_FAILED_UNIT_NOT_INFRONT
&& !pet
->HasAuraType(SPELL_AURA_MOD_POSSESS
))
198 pet
->SetInFront(unit_target
);
199 if (unit_target
->GetTypeId() == TYPEID_PLAYER
)
200 pet
->SendCreateUpdateToPlayer( (Player
*)unit_target
);
202 else if(Unit
*unit_target2
= spell
->m_targets
.getUnitTarget())
204 pet
->SetInFront(unit_target2
);
205 if (unit_target2
->GetTypeId() == TYPEID_PLAYER
)
206 pet
->SendCreateUpdateToPlayer( (Player
*)unit_target2
);
208 if (Unit
* powner
= pet
->GetCharmerOrOwner())
209 if(powner
->GetTypeId() == TYPEID_PLAYER
)
210 pet
->SendCreateUpdateToPlayer((Player
*)powner
);
211 result
= SPELL_CAST_OK
;
214 if(result
== SPELL_CAST_OK
)
216 ((Creature
*)pet
)->AddCreatureSpellCooldown(spellid
);
218 unit_target
= spell
->m_targets
.getUnitTarget();
220 //10% chance to play special pet attack talk, else growl
221 //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell
222 if(((Creature
*)pet
)->isPet() && (((Pet
*)pet
)->getPetType() == SUMMON_PET
) && (pet
!= unit_target
) && (urand(0, 100) < 10))
223 pet
->SendPetTalk((uint32
)PET_TALK_SPECIAL_SPELL
);
226 pet
->SendPetAIReaction(guid1
);
229 if( unit_target
&& !GetPlayer()->IsFriendlyTo(unit_target
) && !pet
->HasAuraType(SPELL_AURA_MOD_POSSESS
))
231 // This is true if pet has no target or has target but targets differs.
232 if (pet
->getVictim() != unit_target
)
234 if (pet
->getVictim())
236 pet
->GetMotionMaster()->Clear();
237 if (((Creature
*)pet
)->AI())
238 ((Creature
*)pet
)->AI()->AttackStart(unit_target
);
242 spell
->prepare(&(spell
->m_targets
));
246 if(pet
->HasAuraType(SPELL_AURA_MOD_POSSESS
))
247 Spell::SendCastResult(GetPlayer(),spellInfo
,0,result
);
249 pet
->SendPetCastFail(spellid
, result
);
251 if (!((Creature
*)pet
)->HasSpellCooldown(spellid
))
252 GetPlayer()->SendClearCooldown(spellid
, pet
);
254 spell
->finish(false);
260 sLog
.outError("WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag
), spellid
);
264 void WorldSession::HandlePetNameQuery( WorldPacket
& recv_data
)
266 sLog
.outDetail( "HandlePetNameQuery. CMSG_PET_NAME_QUERY" );
271 recv_data
>> petnumber
;
272 recv_data
>> petguid
;
274 SendPetNameQuery(petguid
,petnumber
);
277 void WorldSession::SendPetNameQuery( uint64 petguid
, uint32 petnumber
)
279 Creature
* pet
= _player
->GetMap()->GetCreatureOrPetOrVehicle(petguid
);
280 if(!pet
|| !pet
->GetCharmInfo() || pet
->GetCharmInfo()->GetPetNumber() != petnumber
)
283 std::string name
= pet
->GetName();
285 WorldPacket
data(SMSG_PET_NAME_QUERY_RESPONSE
, (4+4+name
.size()+1));
286 data
<< uint32(petnumber
);
287 data
<< name
.c_str();
288 data
<< uint32(pet
->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP
));
290 if( pet
->isPet() && ((Pet
*)pet
)->GetDeclinedNames() )
293 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
294 data
<< ((Pet
*)pet
)->GetDeclinedNames()->name
[i
];
299 _player
->GetSession()->SendPacket(&data
);
302 void WorldSession::HandlePetSetAction( WorldPacket
& recv_data
)
304 sLog
.outDetail( "HandlePetSetAction. CMSG_PET_SET_ACTION" );
309 recv_data
>> petguid
;
311 Creature
* pet
= _player
->GetMap()->GetCreatureOrPetOrVehicle(petguid
);
313 if(!pet
|| (pet
!= _player
->GetPet() && pet
!= _player
->GetCharm()))
315 sLog
.outError( "HandlePetSetAction: Unknown pet or pet owner." );
319 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
322 sLog
.outError("WorldSession::HandlePetSetAction: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", pet
->GetGUIDLow(), pet
->GetTypeId());
326 count
= (recv_data
.size() == 24) ? 2 : 1;
330 bool move_command
= false;
332 for(uint8 i
= 0; i
< count
; ++i
)
334 recv_data
>> position
[i
];
335 recv_data
>> data
[i
];
337 uint8 act_state
= UNIT_ACTION_BUTTON_TYPE(data
[i
]);
339 //ignore invalid position
340 if(position
[i
] >= MAX_UNIT_ACTION_BAR_INDEX
)
343 // in the normal case, command and reaction buttons can only be moved, not removed
344 // at moving count ==2, at removing count == 1
345 // ignore attempt to remove command|reaction buttons (not possible at normal case)
346 if (act_state
== ACT_COMMAND
|| act_state
== ACT_REACTION
)
355 // check swap (at command->spell swap client remove spell first in another packet, so check only command move correctness)
358 uint8 act_state_0
= UNIT_ACTION_BUTTON_TYPE(data
[0]);
359 if(act_state_0
== ACT_COMMAND
|| act_state_0
== ACT_REACTION
)
361 uint32 spell_id_0
= UNIT_ACTION_BUTTON_ACTION(data
[0]);
362 UnitActionBarEntry
const* actionEntry_1
= charmInfo
->GetActionBarEntry(position
[1]);
363 if (!actionEntry_1
|| spell_id_0
!= actionEntry_1
->GetAction() ||
364 act_state_0
!= actionEntry_1
->GetType())
368 uint8 act_state_1
= UNIT_ACTION_BUTTON_TYPE(data
[1]);
369 if(act_state_1
== ACT_COMMAND
|| act_state_1
== ACT_REACTION
)
371 uint32 spell_id_1
= UNIT_ACTION_BUTTON_ACTION(data
[1]);
372 UnitActionBarEntry
const* actionEntry_0
= charmInfo
->GetActionBarEntry(position
[0]);
373 if (!actionEntry_0
|| spell_id_1
!= actionEntry_0
->GetAction() ||
374 act_state_1
!= actionEntry_0
->GetType())
379 for(uint8 i
= 0; i
< count
; ++i
)
381 uint32 spell_id
= UNIT_ACTION_BUTTON_ACTION(data
[i
]);
382 uint8 act_state
= UNIT_ACTION_BUTTON_TYPE(data
[i
]);
384 sLog
.outDetail( "Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X", _player
->GetName(), position
[i
], spell_id
, uint32(act_state
));
386 //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
387 if(!((act_state
== ACT_ENABLED
|| act_state
== ACT_DISABLED
|| act_state
== ACT_PASSIVE
) && spell_id
&& !pet
->HasSpell(spell_id
)))
390 if(act_state
== ACT_ENABLED
&& spell_id
)
393 charmInfo
->ToggleCreatureAutocast(spell_id
, true);
395 ((Pet
*)pet
)->ToggleAutocast(spell_id
, true);
397 //sign for no/turn off autocast
398 else if(act_state
== ACT_DISABLED
&& spell_id
)
401 charmInfo
->ToggleCreatureAutocast(spell_id
, false);
403 ((Pet
*)pet
)->ToggleAutocast(spell_id
, false);
406 charmInfo
->SetActionBar(position
[i
],spell_id
,ActiveStates(act_state
));
411 void WorldSession::HandlePetRename( WorldPacket
& recv_data
)
413 sLog
.outDetail( "HandlePetRename. CMSG_PET_RENAME" );
419 DeclinedName declinedname
;
421 recv_data
>> petguid
;
423 recv_data
>> isdeclined
;
425 Pet
* pet
= _player
->GetMap()->GetPet(petguid
);
427 if( !pet
|| pet
->getPetType() != HUNTER_PET
||
428 pet
->GetByteValue(UNIT_FIELD_BYTES_2
, 2) != UNIT_RENAME_ALLOWED
||
429 pet
->GetOwnerGUID() != _player
->GetGUID() || !pet
->GetCharmInfo() )
432 PetNameInvalidReason res
= ObjectMgr::CheckPetName(name
);
433 if(res
!= PET_NAME_SUCCESS
)
435 SendPetNameInvalid(res
, name
, NULL
);
439 if(sObjectMgr
.IsReservedName(name
))
441 SendPetNameInvalid(PET_NAME_RESERVED
, name
, NULL
);
447 if(_player
->GetGroup())
448 _player
->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_NAME
);
450 pet
->SetByteValue(UNIT_FIELD_BYTES_2
, 2, UNIT_RENAME_NOT_ALLOWED
);
454 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
456 recv_data
>> declinedname
.name
[i
];
460 Utf8toWStr(name
, wname
);
461 if(!ObjectMgr::CheckDeclinedNames(GetMainPartOfName(wname
,0),declinedname
))
463 SendPetNameInvalid(PET_NAME_DECLENSION_DOESNT_MATCH_BASE_NAME
, name
, &declinedname
);
468 CharacterDatabase
.BeginTransaction();
471 for(int i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
472 CharacterDatabase
.escape_string(declinedname
.name
[i
]);
473 CharacterDatabase
.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u' AND id = '%u'", _player
->GetGUIDLow(), pet
->GetCharmInfo()->GetPetNumber());
474 CharacterDatabase
.PExecute("INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u','%u','%s','%s','%s','%s','%s')",
475 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());
478 CharacterDatabase
.escape_string(name
);
479 CharacterDatabase
.PExecute("UPDATE character_pet SET name = '%s', renamed = '1' WHERE owner = '%u' AND id = '%u'", name
.c_str(), _player
->GetGUIDLow(), pet
->GetCharmInfo()->GetPetNumber());
480 CharacterDatabase
.CommitTransaction();
482 pet
->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP
, time(NULL
));
485 void WorldSession::HandlePetAbandon( WorldPacket
& recv_data
)
488 recv_data
>> guid
; //pet guid
489 sLog
.outDetail( "HandlePetAbandon. CMSG_PET_ABANDON pet guid is %u", GUID_LOPART(guid
) );
491 if(!_player
->IsInWorld())
495 if (Creature
* pet
= _player
->GetMap()->GetCreatureOrPetOrVehicle(guid
))
499 if(pet
->GetGUID() == _player
->GetPetGUID())
501 uint32 feelty
= pet
->GetPower(POWER_HAPPINESS
);
502 pet
->SetPower(POWER_HAPPINESS
,(feelty
-50000) > 0 ?(feelty
-50000) : 0);
505 _player
->RemovePet((Pet
*)pet
,PET_SAVE_AS_DELETED
);
507 else if(pet
->GetGUID() == _player
->GetCharmGUID())
514 void WorldSession::HandlePetUnlearnOpcode(WorldPacket
& recvPacket
)
516 sLog
.outDetail("CMSG_PET_UNLEARN");
518 recvPacket
>> guid
; // Pet guid
520 Pet
* pet
= _player
->GetPet();
522 if(!pet
|| pet
->getPetType() != HUNTER_PET
|| pet
->m_usedTalentCount
== 0)
525 if(guid
!= pet
->GetGUID())
527 sLog
.outError( "HandlePetUnlearnOpcode.Pet %u isn't pet of player %s .", uint32(GUID_LOPART(guid
)),GetPlayer()->GetName() );
531 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
534 sLog
.outError("WorldSession::HandlePetUnlearnOpcode: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", pet
->GetGUIDLow(), pet
->GetTypeId());
538 _player
->SendTalentsInfoData(true);
541 void WorldSession::HandlePetSpellAutocastOpcode( WorldPacket
& recvPacket
)
543 sLog
.outDetail("CMSG_PET_SPELL_AUTOCAST");
546 uint8 state
; //1 for on, 0 for off
547 recvPacket
>> guid
>> spellid
>> state
;
549 if(!_player
->GetPet() && !_player
->GetCharm())
552 Creature
* pet
= _player
->GetMap()->GetCreatureOrPetOrVehicle(guid
);
554 if(!pet
|| (pet
!= _player
->GetPet() && pet
!= _player
->GetCharm()))
556 sLog
.outError( "HandlePetSpellAutocastOpcode.Pet %u isn't pet of player %s .", uint32(GUID_LOPART(guid
)),GetPlayer()->GetName() );
560 // do not add not learned spells/ passive spells
561 if(!pet
->HasSpell(spellid
) || IsPassiveSpell(spellid
))
564 CharmInfo
*charmInfo
= pet
->GetCharmInfo();
567 sLog
.outError("WorldSession::HandlePetSpellAutocastOpcod: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", pet
->GetGUIDLow(), pet
->GetTypeId());
572 //state can be used as boolean
573 pet
->GetCharmInfo()->ToggleCreatureAutocast(spellid
, state
);
575 ((Pet
*)pet
)->ToggleAutocast(spellid
, state
);
577 charmInfo
->SetSpellAutocast(spellid
,state
);
580 void WorldSession::HandlePetCastSpellOpcode( WorldPacket
& recvPacket
)
582 sLog
.outDetail("WORLD: CMSG_PET_CAST_SPELL");
587 uint8 unk_flags
; // flags (if 0x02 - some additional data are received)
589 recvPacket
>> guid
>> cast_count
>> spellid
>> unk_flags
;
591 sLog
.outDebug("WORLD: CMSG_PET_CAST_SPELL, cast_count: %u, spellid %u, unk_flags %u", cast_count
, spellid
, unk_flags
);
593 if (!_player
->GetPet() && !_player
->GetCharm())
596 Creature
* pet
= _player
->GetMap()->GetCreatureOrPetOrVehicle(guid
);
598 if (!pet
|| (pet
!= _player
->GetPet() && pet
!= _player
->GetCharm()))
600 sLog
.outError( "HandlePetCastSpellOpcode: Pet %u isn't pet of player %s .", uint32(GUID_LOPART(guid
)),GetPlayer()->GetName() );
604 if (pet
->GetGlobalCooldown() > 0)
607 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spellid
);
610 sLog
.outError("WORLD: unknown PET spell id %i", spellid
);
614 // do not cast not learned spells
615 if (!pet
->HasSpell(spellid
) || IsPassiveSpell(spellid
))
618 SpellCastTargets targets
;
619 if (!targets
.read(&recvPacket
,pet
))
622 pet
->clearUnitState(UNIT_STAT_FOLLOW
);
624 Spell
*spell
= new Spell(pet
, spellInfo
, false);
625 spell
->m_cast_count
= cast_count
; // probably pending spell cast
626 spell
->m_targets
= targets
;
628 SpellCastResult result
= spell
->CheckPetCast(NULL
);
629 if (result
== SPELL_CAST_OK
)
631 pet
->AddCreatureSpellCooldown(spellid
);
634 //10% chance to play special pet attack talk, else growl
635 //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell
636 if(((Pet
*)pet
)->getPetType() == SUMMON_PET
&& (urand(0, 100) < 10))
637 pet
->SendPetTalk((uint32
)PET_TALK_SPECIAL_SPELL
);
639 pet
->SendPetAIReaction(guid
);
642 spell
->prepare(&(spell
->m_targets
));
646 pet
->SendPetCastFail(spellid
, result
);
647 if (!pet
->HasSpellCooldown(spellid
))
648 GetPlayer()->SendClearCooldown(spellid
, pet
);
650 spell
->finish(false);
655 void WorldSession::SendPetNameInvalid(uint32 error
, const std::string
& name
, DeclinedName
*declinedName
)
657 WorldPacket
data(SMSG_PET_NAME_INVALID
, 4 + name
.size() + 1 + 1);
658 data
<< uint32(error
);
663 for(uint32 i
= 0; i
< MAX_DECLINED_NAME_CASES
; ++i
)
664 data
<< declinedName
->name
[i
];
671 void WorldSession::HandlePetLearnTalent( WorldPacket
& recv_data
)
673 sLog
.outDebug("WORLD: CMSG_PET_LEARN_TALENT");
676 uint32 talent_id
, requested_rank
;
677 recv_data
>> guid
>> talent_id
>> requested_rank
;
679 _player
->LearnPetTalent(guid
, talent_id
, requested_rank
);
680 _player
->SendTalentsInfoData(true);
683 void WorldSession::HandleLearnPreviewTalentsPet( WorldPacket
& recv_data
)
685 sLog
.outDebug("CMSG_LEARN_PREVIEW_TALENTS_PET");
691 recv_data
>> talentsCount
;
693 uint32 talentId
, talentRank
;
695 for(uint32 i
= 0; i
< talentsCount
; ++i
)
697 recv_data
>> talentId
>> talentRank
;
699 _player
->LearnPetTalent(guid
, talentId
, talentRank
);
702 _player
->SendTalentsInfoData(true);