TrinityCore
Loading...
Searching...
No Matches
BattlePetMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "BattlePetMgr.h"
19#include "Containers.h"
20#include "Creature.h"
21#include "DB2Stores.h"
22#include "DatabaseEnv.h"
23#include "GameTables.h"
24#include "GameTime.h"
25#include "Item.h"
26#include "Log.h"
27#include "MapUtils.h"
28#include "ObjectAccessor.h"
29#include "ObjectMgr.h"
30#include "Player.h"
31#include "RealmList.h"
32#include "Util.h"
33#include "World.h"
34#include "WorldSession.h"
35
36namespace BattlePets
37{
38namespace
39{
40std::unordered_map<uint16 /*BreedID*/, std::unordered_map<BattlePetState /*state*/, int32 /*value*/>> _battlePetBreedStates;
41std::unordered_map<uint32 /*SpeciesID*/, std::unordered_map<BattlePetState /*state*/, int32 /*value*/>> _battlePetSpeciesStates;
42std::unordered_map<uint32 /*CreatureID*/, BattlePetSpeciesEntry const*> _battlePetSpeciesByCreature;
43std::unordered_map<uint32 /*SpellID*/, BattlePetSpeciesEntry const*> _battlePetSpeciesBySpell;
44std::unordered_map<uint32 /*SpeciesID*/, std::unordered_set<uint8 /*breed*/>> _availableBreedsPerSpecies;
45std::unordered_map<uint32 /*SpeciesID*/, uint8 /*quality*/> _defaultQualityPerSpecies;
46}
47
49{
50 // get base breed stats
51 auto breedState = _battlePetBreedStates.find(PacketInfo.Breed);
52 if (breedState == _battlePetBreedStates.end()) // non existing breed id
53 return;
54
55 float health = breedState->second[STATE_STAT_STAMINA];
56 float power = breedState->second[STATE_STAT_POWER];
57 float speed = breedState->second[STATE_STAT_SPEED];
58
59 // modify stats depending on species - not all pets have this
60 auto speciesState = _battlePetSpeciesStates.find(PacketInfo.Species);
61 if (speciesState != _battlePetSpeciesStates.end())
62 {
63 health += speciesState->second[STATE_STAT_STAMINA];
64 power += speciesState->second[STATE_STAT_POWER];
65 speed += speciesState->second[STATE_STAT_SPEED];
66 }
67
68 // modify stats by quality
69 for (BattlePetBreedQualityEntry const* battlePetBreedQuality : sBattlePetBreedQualityStore)
70 {
71 if (battlePetBreedQuality->QualityEnum == PacketInfo.Quality)
72 {
73 health *= battlePetBreedQuality->StateMultiplier;
74 power *= battlePetBreedQuality->StateMultiplier;
75 speed *= battlePetBreedQuality->StateMultiplier;
76 break;
77 }
78 // TOOD: add check if pet has existing quality
79 }
80
81 // scale stats depending on level
82 health *= PacketInfo.Level;
83 power *= PacketInfo.Level;
84 speed *= PacketInfo.Level;
85
86 // set stats
87 // round, ceil or floor? verify this
88 PacketInfo.MaxHealth = uint32((round(health / 20) + 100));
89 PacketInfo.Power = uint32(round(power / 100));
90 PacketInfo.Speed = uint32(round(speed / 100));
91}
92
94{
95 if (QueryResult result = LoginDatabase.Query("SELECT MAX(guid) FROM battle_pets"))
96 sObjectMgr->GetGenerator<HighGuid::BattlePet>().Set((*result)[0].GetUInt64() + 1);
97
98 for (BattlePetSpeciesEntry const* battlePetSpecies : sBattlePetSpeciesStore)
99 if (uint32 creatureId = battlePetSpecies->CreatureID)
100 _battlePetSpeciesByCreature[creatureId] = battlePetSpecies;
101
102 for (BattlePetBreedStateEntry const* battlePetBreedState : sBattlePetBreedStateStore)
103 _battlePetBreedStates[battlePetBreedState->BattlePetBreedID][BattlePetState(battlePetBreedState->BattlePetStateID)] = battlePetBreedState->Value;
104
105 for (BattlePetSpeciesStateEntry const* battlePetSpeciesState : sBattlePetSpeciesStateStore)
106 _battlePetSpeciesStates[battlePetSpeciesState->BattlePetSpeciesID][BattlePetState(battlePetSpeciesState->BattlePetStateID)] = battlePetSpeciesState->Value;
107
110}
111
113{
114 QueryResult result = WorldDatabase.Query("SELECT speciesId, breedId FROM battle_pet_breeds");
115 if (!result)
116 {
117 TC_LOG_INFO("server.loading", ">> Loaded 0 battle pet breeds. DB table `battle_pet_breeds` is empty.");
118 return;
119 }
120
121 uint32 count = 0;
122 do
123 {
124 Field* fields = result->Fetch();
125 uint32 speciesId = fields[0].GetUInt32();
126 uint16 breedId = fields[1].GetUInt16();
127
128 if (!sBattlePetSpeciesStore.LookupEntry(speciesId))
129 {
130 TC_LOG_ERROR("sql.sql", "Non-existing BattlePetSpecies.db2 entry {} was referenced in `battle_pet_breeds` by row ({}, {}).", speciesId, speciesId, breedId);
131 continue;
132 }
133
134 // TODO: verify breed id (3 - 12 (male) or 3 - 22 (male and female)) if needed
135
136 _availableBreedsPerSpecies[speciesId].insert(breedId);
137 ++count;
138 } while (result->NextRow());
139
140 TC_LOG_INFO("server.loading", ">> Loaded {} battle pet breeds.", count);
141}
142
144{
145 QueryResult result = WorldDatabase.Query("SELECT speciesId, quality FROM battle_pet_quality");
146 if (!result)
147 {
148 TC_LOG_INFO("server.loading", ">> Loaded 0 battle pet qualities. DB table `battle_pet_quality` is empty.");
149 return;
150 }
151
152 do
153 {
154 Field* fields = result->Fetch();
155 uint32 speciesId = fields[0].GetUInt32();
156 uint8 quality = fields[1].GetUInt8();
157
158 BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(speciesId);
159 if (!battlePetSpecies)
160 {
161 TC_LOG_ERROR("sql.sql", "Non-existing BattlePetSpecies.db2 entry {} was referenced in `battle_pet_quality` by row ({}, {}).", speciesId, speciesId, quality);
162 continue;
163 }
164
166 {
167 TC_LOG_ERROR("sql.sql", "BattlePetSpecies.db2 entry {} was referenced in `battle_pet_quality` with non-existing quality {}).", speciesId, quality);
168 continue;
169 }
170
171 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::WellKnown) && quality > AsUnderlyingType(BattlePetBreedQuality::Rare))
172 {
173 TC_LOG_ERROR("sql.sql", "Learnable BattlePetSpecies.db2 entry {} was referenced in `battle_pet_quality` with invalid quality {}. Maximum allowed quality is BattlePetBreedQuality::Rare.", speciesId, quality);
174 continue;
175 }
176
177 _defaultQualityPerSpecies[speciesId] = quality;
178 } while (result->NextRow());
179
180 TC_LOG_INFO("server.loading", ">> Loaded {} battle pet qualities.", uint32(_defaultQualityPerSpecies.size()));
181}
182
184{
185 _battlePetSpeciesBySpell[spellId] = speciesEntry;
186}
187
189{
190 return Trinity::Containers::MapGetValuePtr(_battlePetSpeciesByCreature, creatureId);
191}
192
194{
195 return Trinity::Containers::MapGetValuePtr(_battlePetSpeciesBySpell, spellId);
196}
197
199{
200 auto itr = _availableBreedsPerSpecies.find(species);
201 if (itr == _availableBreedsPerSpecies.end())
202 return 3; // default B/B
203
205}
206
208{
209 auto itr = _defaultQualityPerSpecies.find(species);
210 if (itr == _defaultQualityPerSpecies.end())
211 return BattlePetBreedQuality::Poor; // Default
212
213 return BattlePetBreedQuality(itr->second);
214}
215
217{
218 if (CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(speciesEntry->CreatureID))
219 if (!speciesEntry->GetFlags().HasFlag(BattlePetSpeciesFlags::RandomDisplay))
220 if (CreatureModel const* creatureModel = creatureTemplate->GetRandomValidModel())
221 return creatureModel->CreatureDisplayID;
222
223 return 0;
224}
225
227{
228 _owner = owner;
229 for (uint8 i = 0; i < AsUnderlyingType(BattlePetSlot::Count); ++i)
230 {
232 slot.Index = i;
233 _slots.push_back(slot);
234 }
235}
236
238{
239 if (pets)
240 {
241 do
242 {
243 Field* fields = pets->Fetch();
244 uint32 species = fields[1].GetUInt32();
245 ObjectGuid ownerGuid = !fields[11].IsNull() ? ObjectGuid::Create<HighGuid::Player>(fields[11].GetInt64()) : ObjectGuid::Empty;
246
247 if (BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(species))
248 {
249 if (speciesEntry->GetFlags().HasFlag(BattlePetSpeciesFlags::NotAccountWide))
250 {
251 if (ownerGuid.IsEmpty())
252 {
253 TC_LOG_ERROR("misc", "Battlenet account with id {} has battle pet of species {} with BattlePetSpeciesFlags::NotAccountWide but no owner", _owner->GetBattlenetAccountId(), species);
254 continue;
255 }
256 }
257 else
258 {
259 if (!ownerGuid.IsEmpty())
260 {
261 TC_LOG_ERROR("misc", "Battlenet account with id {} has battle pet of species {} without BattlePetSpeciesFlags::NotAccountWide but with owner", _owner->GetBattlenetAccountId(), species);
262 continue;
263 }
264 }
265
266 if (HasMaxPetCount(speciesEntry, ownerGuid))
267 {
268 if (ownerGuid.IsEmpty())
269 TC_LOG_ERROR("misc", "Battlenet account with id {} has more than maximum battle pets of species {}", _owner->GetBattlenetAccountId(), species);
270 else
271 TC_LOG_ERROR("misc", "Battlenet account with id {} has more than maximum battle pets of species {} for player {}", _owner->GetBattlenetAccountId(), species, ownerGuid.ToString());
272
273 continue;
274 }
275
276 BattlePet pet;
277 pet.PacketInfo.Guid = ObjectGuid::Create<HighGuid::BattlePet>(fields[0].GetUInt64());
278 pet.PacketInfo.Species = species;
279 pet.PacketInfo.Breed = fields[2].GetUInt16();
280 pet.PacketInfo.DisplayID = fields[3].GetUInt32();
281 pet.PacketInfo.Level = fields[4].GetUInt16();
282 pet.PacketInfo.Exp = fields[5].GetUInt16();
283 pet.PacketInfo.Health = fields[6].GetUInt32();
284 pet.PacketInfo.Quality = fields[7].GetUInt8();
285 pet.PacketInfo.Flags = fields[8].GetUInt16();
286 pet.PacketInfo.Name = fields[9].GetString();
287 pet.NameTimestamp = fields[10].GetInt64();
288 pet.PacketInfo.CreatureID = speciesEntry->CreatureID;
289
290 if (!fields[13].IsNull())
291 {
292 pet.DeclinedName = std::make_unique<DeclinedName>();
293 for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
294 pet.DeclinedName->name[i] = fields[13 + i].GetString();
295 }
296
297 if (!ownerGuid.IsEmpty())
298 {
299 pet.PacketInfo.OwnerInfo.emplace();
300 pet.PacketInfo.OwnerInfo->Guid = ownerGuid;
301 if (std::shared_ptr<Realm const> ownerRealm = sRealmList->GetRealm(fields[12].GetInt32()))
302 pet.PacketInfo.OwnerInfo->PlayerVirtualRealm = pet.PacketInfo.OwnerInfo->PlayerNativeRealm = ownerRealm->Id.GetAddress();
303 }
304
306 pet.CalculateStats();
307 _pets[pet.PacketInfo.Guid.GetCounter()] = std::move(pet);
308 }
309 } while (pets->NextRow());
310 }
311
312 if (slots)
313 {
314 uint8 i = 0; // slots->GetRowCount() should equal MAX_BATTLE_PET_SLOTS
315
316 do
317 {
318 Field* fields = slots->Fetch();
319 _slots[i].Index = fields[0].GetUInt8();
320 auto itr = _pets.find(fields[1].GetUInt64());
321 if (itr != _pets.end())
322 _slots[i].Pet = itr->second.PacketInfo;
323 _slots[i].Locked = fields[2].GetBool();
324 i++;
325 } while (slots->NextRow());
326 }
327}
328
330{
331 LoginDatabasePreparedStatement* stmt = nullptr;
332
333 for (auto itr = _pets.begin(); itr != _pets.end();)
334 {
335 switch (itr->second.SaveInfo)
336 {
337 case BATTLE_PET_NEW:
338 stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BATTLE_PETS);
339 stmt->setUInt64(0, itr->first);
341 stmt->setUInt32(2, itr->second.PacketInfo.Species);
342 stmt->setUInt16(3, itr->second.PacketInfo.Breed);
343 stmt->setUInt32(4, itr->second.PacketInfo.DisplayID);
344 stmt->setUInt16(5, itr->second.PacketInfo.Level);
345 stmt->setUInt16(6, itr->second.PacketInfo.Exp);
346 stmt->setUInt32(7, itr->second.PacketInfo.Health);
347 stmt->setUInt8(8, itr->second.PacketInfo.Quality);
348 stmt->setUInt16(9, itr->second.PacketInfo.Flags);
349 stmt->setString(10, itr->second.PacketInfo.Name);
350 stmt->setInt64(11, itr->second.NameTimestamp);
351 if (itr->second.PacketInfo.OwnerInfo)
352 {
353 stmt->setInt64(12, itr->second.PacketInfo.OwnerInfo->Guid.GetCounter());
354 stmt->setInt32(13, sRealmList->GetCurrentRealmId().Realm);
355 }
356 else
357 {
358 stmt->setNull(12);
359 stmt->setNull(13);
360 }
361
362 trans->Append(stmt);
363
364 if (itr->second.DeclinedName)
365 {
366 stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BATTLE_PET_DECLINED_NAME);
367 stmt->setUInt64(0, itr->first);
368
369 for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; i++)
370 stmt->setString(i + 1, itr->second.DeclinedName->name[i]);
371
372 trans->Append(stmt);
373 }
374
375 itr->second.SaveInfo = BATTLE_PET_UNCHANGED;
376 ++itr;
377 break;
379 stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BATTLE_PETS);
380 stmt->setUInt16(0, itr->second.PacketInfo.Level);
381 stmt->setUInt16(1, itr->second.PacketInfo.Exp);
382 stmt->setUInt32(2, itr->second.PacketInfo.Health);
383 stmt->setUInt8(3, itr->second.PacketInfo.Quality);
384 stmt->setUInt16(4, itr->second.PacketInfo.Flags);
385 stmt->setString(5, itr->second.PacketInfo.Name);
386 stmt->setInt64(6, itr->second.NameTimestamp);
388 stmt->setUInt64(8, itr->first);
389 trans->Append(stmt);
390
391 stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PET_DECLINED_NAME);
392 stmt->setUInt64(0, itr->first);
393 trans->Append(stmt);
394
395 if (itr->second.DeclinedName)
396 {
397 stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BATTLE_PET_DECLINED_NAME);
398 stmt->setUInt64(0, itr->first);
399
400 for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; i++)
401 stmt->setString(i + 1, itr->second.DeclinedName->name[i]);
402
403 trans->Append(stmt);
404 }
405
406 itr->second.SaveInfo = BATTLE_PET_UNCHANGED;
407 ++itr;
408 break;
410 stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PET_DECLINED_NAME);
411 stmt->setUInt64(0, itr->first);
412 trans->Append(stmt);
413
414 stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PETS);
416 stmt->setUInt64(1, itr->first);
417 trans->Append(stmt);
418
419 itr = _pets.erase(itr);
420 break;
421 default:
422 ++itr;
423 break;
424 }
425 }
426
427 stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PET_SLOTS);
429 trans->Append(stmt);
430
432 {
433 stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BATTLE_PET_SLOTS);
434 stmt->setUInt8(0, slot.Index);
436 stmt->setUInt64(2, slot.Pet.Guid.GetCounter());
437 stmt->setBool(3, slot.Locked);
438 trans->Append(stmt);
439 }
440}
441
446
447void BattlePetMgr::AddPet(uint32 species, uint32 display, uint16 breed, BattlePetBreedQuality quality, uint16 level /*= 1*/)
448{
449 BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(species);
450 if (!battlePetSpecies) // should never happen
451 return;
452
453 if (!battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::WellKnown)) // Not learnable
454 return;
455
456 ObjectGuid guid = ObjectGuid::Create<HighGuid::BattlePet>(sObjectMgr->GetGenerator<HighGuid::BattlePet>().Generate());
457
458 BattlePet& pet = _pets[guid.GetCounter()];
459 pet.PacketInfo.Guid = guid;
460 pet.PacketInfo.Species = species;
461 pet.PacketInfo.CreatureID = battlePetSpecies->CreatureID;
462 pet.PacketInfo.DisplayID = display;
463 pet.PacketInfo.Level = level;
464 pet.PacketInfo.Breed = breed;
465 pet.PacketInfo.Quality = AsUnderlyingType(quality);
466 pet.CalculateStats();
468
469 Player* player = _owner->GetPlayer();
470 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::NotAccountWide))
471 {
472 pet.PacketInfo.OwnerInfo.emplace();
473 pet.PacketInfo.OwnerInfo->Guid = player->GetGUID();
474 pet.PacketInfo.OwnerInfo->PlayerVirtualRealm = pet.PacketInfo.OwnerInfo->PlayerNativeRealm = player->m_playerData->VirtualPlayerRealm;
475 }
476
478
479 std::array<std::reference_wrapper<BattlePet const>, 1> updates = { pet };
480 SendUpdates(updates, true);
481
484}
485
487{
488 if (!HasJournalLock())
489 return;
490
491 BattlePet* pet = GetPet(guid);
492 if (!pet)
493 return;
494
496}
497
499{
500 BattlePet* pet = GetPet(guid);
501 if (!pet)
502 return;
503
504 pet->PacketInfo.Flags &= ~AsUnderlyingType(BattlePetDbFlags::FanfareNeeded);
505
506 if (pet->SaveInfo != BATTLE_PET_NEW)
508}
509
510void BattlePetMgr::ModifyName(ObjectGuid guid, std::string const& name, std::unique_ptr<DeclinedName> declinedName)
511{
512 if (!HasJournalLock())
513 return;
514
515 BattlePet* pet = GetPet(guid);
516 if (!pet)
517 return;
518
519 pet->PacketInfo.Name = name;
521
522 pet->DeclinedName = std::move(declinedName);
523
524 if (pet->SaveInfo != BATTLE_PET_NEW)
526
527 // Update the timestamp if the battle pet is summoned
528 if (Creature* summonedBattlePet = _owner->GetPlayer()->GetSummonedBattlePet())
529 if (summonedBattlePet->GetBattlePetCompanionGUID() == guid)
530 summonedBattlePet->SetBattlePetCompanionNameTimestamp(pet->NameTimestamp);
531}
532
534{
536 if (slot.Pet.Guid == guid)
537 return true;
538
539 return false;
540}
541
542uint8 BattlePetMgr::GetPetCount(BattlePetSpeciesEntry const* battlePetSpecies, ObjectGuid ownerGuid) const
543{
544 return uint8(std::ranges::count_if(_pets, [battlePetSpecies, ownerGuid](BattlePet const& pet)
545 {
546 if (pet.PacketInfo.Species != battlePetSpecies->ID)
547 return false;
548
549 if (pet.SaveInfo == BATTLE_PET_REMOVED)
550 return false;
551
552 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::NotAccountWide))
553 if (!ownerGuid.IsEmpty() && pet.PacketInfo.OwnerInfo)
554 if (pet.PacketInfo.OwnerInfo->Guid != ownerGuid)
555 return false;
556
557 return true;
559}
560
561bool BattlePetMgr::HasMaxPetCount(BattlePetSpeciesEntry const* battlePetSpecies, ObjectGuid ownerGuid) const
562{
563 uint8 maxPetsPerSpecies = battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::LegacyAccountUnique) ? 1 : DEFAULT_MAX_BATTLE_PETS_PER_SPECIES;
564
565 return GetPetCount(battlePetSpecies, ownerGuid) >= maxPetsPerSpecies;
566}
567
569{
570 std::set<uint32> speciesIds;
571 std::ranges::transform(_pets, std::inserter(speciesIds, speciesIds.end()), [](BattlePet const& pet)
572 {
573 return pet.PacketInfo.Species;
575 return speciesIds.size();
576}
577
579{
580 if (slot >= BattlePetSlot::Count)
581 return;
582
583 uint8 slotIndex = AsUnderlyingType(slot);
584 if (!_slots[slotIndex].Locked)
585 return;
586
587 _slots[slotIndex].Locked = false;
588
590 updates.Slots.push_back(_slots[slotIndex]);
591 updates.AutoSlotted = false; // what's this?
592 updates.NewSlot = true; // causes the "new slot unlocked" bubble to appear
593 _owner->SendPacket(updates.Write());
594}
595
597{
598 uint16 level = 0;
599 for (auto& pet : _pets)
600 if (pet.second.SaveInfo != BATTLE_PET_REMOVED)
601 level = std::max(level, pet.second.PacketInfo.Level);
602
603 return level;
604}
605
607{
608 if (!HasJournalLock())
609 return;
610
611 BattlePet* pet = GetPet(guid);
612 if (!pet)
613 return;
614
615 if (BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species))
616 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::NotTradable))
617 return;
618
619 if (IsPetInSlot(guid))
620 return;
621
622 if (pet->PacketInfo.Health < pet->PacketInfo.MaxHealth)
623 return;
624
625 ItemPosCountVec dest;
626
628 return;
629
631 if (!item)
632 return;
633
638
639 _owner->GetPlayer()->SendNewItem(item, 1, true, false);
640
641 RemovePet(guid);
642
644 deletePet.PetGuid = guid;
645 _owner->SendPacket(deletePet.Write());
646
647 // Battle pet despawns if it's summoned
648 Player* player = _owner->GetPlayer();
649 if (Creature* summonedBattlePet = player->GetSummonedBattlePet())
650 {
651 if (summonedBattlePet->GetBattlePetCompanionGUID() == guid)
652 {
653 summonedBattlePet->DespawnOrUnsummon();
654 player->SetBattlePetData(nullptr);
655 }
656 }
657}
658
660{
661 if (!HasJournalLock())
662 return;
663
664 BattlePet* pet = GetPet(guid);
665 if (!pet)
666 return;
667
668 if (quality > BattlePetBreedQuality::Rare)
669 return;
670
671 if (BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species))
672 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::CantBattle))
673 return;
674
675 uint8 qualityValue = AsUnderlyingType(quality);
676 if (pet->PacketInfo.Quality >= qualityValue)
677 return;
678
679 pet->PacketInfo.Quality = qualityValue;
680 pet->CalculateStats();
682
683 if (pet->SaveInfo != BATTLE_PET_NEW)
685
686 std::array<std::reference_wrapper<BattlePet const>, 1> updates = { *pet };
687 SendUpdates(updates, false);
688
689 // UF::PlayerData::CurrentBattlePetBreedQuality isn't updated (Intended)
690 // _owner->GetPlayer()->SetCurrentBattlePetBreedQuality(qualityValue);
691}
692
694{
695 if (!HasJournalLock())
696 return;
697
698 BattlePet* pet = GetPet(guid);
699 if (!pet)
700 return;
701
702 if (xp <= 0 || xpSource >= BattlePetXpSource::Count)
703 return;
704
705 if (BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species))
706 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::CantBattle))
707 return;
708
709 uint16 level = pet->PacketInfo.Level;
710 if (level >= MAX_BATTLE_PET_LEVEL)
711 return;
712
713 GtBattlePetXPEntry const* xpEntry = sBattlePetXPGameTable.GetRow(level);
714 if (!xpEntry)
715 return;
716
717 Player* player = _owner->GetPlayer();
718 uint16 nextLevelXp = uint16(GetBattlePetXPPerLevel(xpEntry));
719
720 if (xpSource == BattlePetXpSource::PetBattle)
722
723 xp += pet->PacketInfo.Exp;
724
725 while (xp >= nextLevelXp && level < MAX_BATTLE_PET_LEVEL)
726 {
727 xp -= nextLevelXp;
728
729 xpEntry = sBattlePetXPGameTable.GetRow(++level);
730 if (!xpEntry)
731 return;
732
733 nextLevelXp = uint16(GetBattlePetXPPerLevel(xpEntry));
734
736 if (xpSource == BattlePetXpSource::PetBattle)
738 }
739
740 pet->PacketInfo.Level = level;
741 pet->PacketInfo.Exp = level < MAX_BATTLE_PET_LEVEL ? xp : 0;
742 pet->CalculateStats();
744
745 if (pet->SaveInfo != BATTLE_PET_NEW)
747
748 std::array<std::reference_wrapper<BattlePet const>, 1> updates = { *pet };
749 SendUpdates(updates, false);
750}
751
753{
754 if (!HasJournalLock())
755 return;
756
757 BattlePet* pet = GetPet(guid);
758 if (!pet)
759 return;
760
761 if (BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species))
762 if (battlePetSpecies->GetFlags().HasFlag(BattlePetSpeciesFlags::CantBattle))
763 return;
764
765 uint16 level = pet->PacketInfo.Level;
766 if (level >= MAX_BATTLE_PET_LEVEL)
767 return;
768
769 while (grantedLevels > 0 && level < MAX_BATTLE_PET_LEVEL)
770 {
771 ++level;
772 --grantedLevels;
773
775 }
776
777 pet->PacketInfo.Level = level;
778 if (level >= MAX_BATTLE_PET_LEVEL)
779 pet->PacketInfo.Exp = 0;
780 pet->CalculateStats();
782
783 if (pet->SaveInfo != BATTLE_PET_NEW)
785
786 std::array<std::reference_wrapper<BattlePet const>, 1> updates = { *pet };
787 SendUpdates(updates, false);
788}
789
791{
792 // TODO: After each Pet Battle, any injured companion will automatically
793 // regain 50 % of the damage that was taken during combat
794 std::vector<std::reference_wrapper<BattlePet const>> updates;
795
796 for (auto& [_, pet] : _pets)
797 {
798 if (pet.PacketInfo.Health == pet.PacketInfo.MaxHealth)
799 continue;
800
801 pet.PacketInfo.Health += CalculatePct(pet.PacketInfo.MaxHealth, pct);
802 // don't allow Health to be greater than MaxHealth
803 pet.PacketInfo.Health = std::min(pet.PacketInfo.Health, pet.PacketInfo.MaxHealth);
804 if (pet.SaveInfo != BATTLE_PET_NEW)
805 pet.SaveInfo = BATTLE_PET_CHANGED;
806
807 updates.push_back(pet);
808 }
809
810 SendUpdates(updates, false);
811}
812
814{
815 BattlePet* pet = GetPet(guid);
816 if (!pet)
817 return;
818
819 Player* player = _owner->GetPlayer();
820
821 // Update battle pet related update fields
822 if (Creature* summonedBattlePet = player->GetSummonedBattlePet())
823 {
824 if (summonedBattlePet->GetBattlePetCompanionGUID() == guid)
825 {
826 summonedBattlePet->SetWildBattlePetLevel(pet->PacketInfo.Level);
827 player->SetBattlePetData(pet);
828 }
829 }
830}
831
833{
834 BattlePet* pet = GetPet(guid);
835 if (!pet)
836 return;
837
838 BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species);
839 if (!speciesEntry)
840 return;
841
842 Player* player = _owner->GetPlayer();
843 player->SetBattlePetData(pet);
844
846 uint32 summonSpellId = speciesEntry->SummonSpellID;
847 if (!summonSpellId)
848 {
849 summonSpellId = uint32(SPELL_SUMMON_BATTLE_PET);
850 args.AddSpellBP0(speciesEntry->CreatureID);
851 }
852 player->CastSpell(_owner->GetPlayer(), summonSpellId, args);
853}
854
856{
857 Player* player = _owner->GetPlayer();
858 if (Creature* summonedBattlePet = player->GetSummonedBattlePet())
859 {
860 summonedBattlePet->DespawnOrUnsummon();
861 player->SetBattlePetData(nullptr);
862 }
863}
864
866{
867 if (!HasJournalLock())
869
871 battlePetJournal.Trap = _trapLevel;
872 battlePetJournal.HasJournalLock = _hasJournalLock;
873
874 for (auto& pet : _pets)
875 if (pet.second.SaveInfo != BATTLE_PET_REMOVED)
876 if (!pet.second.PacketInfo.OwnerInfo || pet.second.PacketInfo.OwnerInfo->Guid == _owner->GetPlayer()->GetGUID())
877 battlePetJournal.Pets.push_back(std::ref(pet.second.PacketInfo));
878
879 battlePetJournal.Slots.reserve(_slots.size());
880 std::ranges::transform(_slots, std::back_inserter(battlePetJournal.Slots), [](WorldPackets::BattlePet::BattlePetSlot& slot) { return std::ref(slot); });
881 _owner->SendPacket(battlePetJournal.Write());
882}
883
884void BattlePetMgr::SendUpdates(std::span<std::reference_wrapper<BattlePet const> const> pets, bool petAdded)
885{
887 std::ranges::transform(pets, std::back_inserter(updates.Pets), &BattlePet::PacketInfo);
888 updates.PetAdded = petAdded;
889 _owner->SendPacket(updates.Write());
890}
891
893{
895 battlePetError.Result = AsUnderlyingType(error);
896 battlePetError.CreatureID = creatureId;
897 _owner->SendPacket(battlePetError.Write());
898}
899
901{
903 ToggleJournalLock(true);
904
905 if (HasJournalLock())
906 {
908 _owner->SendPacket(battlePetJournalLockAcquired.Write());
909 }
910 else
911 {
913 _owner->SendPacket(BattlePetJournalLockDenied.Write());
914 }
915}
916
918{
919 return sWorld->IsBattlePetJournalLockAcquired(_owner->GetBattlenetAccountGUID());
920}
921}
DB2Storage< BattlePetBreedQualityEntry > sBattlePetBreedQualityStore("BattlePetBreedQuality.db2", &BattlePetBreedQualityLoadInfo::Instance)
DB2Storage< BattlePetBreedStateEntry > sBattlePetBreedStateStore("BattlePetBreedState.db2", &BattlePetBreedStateLoadInfo::Instance)
DB2Storage< BattlePetSpeciesStateEntry > sBattlePetSpeciesStateStore("BattlePetSpeciesState.db2", &BattlePetSpeciesStateLoadInfo::Instance)
DB2Storage< BattlePetSpeciesEntry > sBattlePetSpeciesStore("BattlePetSpecies.db2", &BattlePetSpeciesLoadInfo::Instance)
SQLTransaction< LoginDatabaseConnection > LoginDatabaseTransaction
std::shared_ptr< ResultSet > QueryResult
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
uint8_t uint8
Definition Define.h:156
int32_t int32
Definition Define.h:150
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
GameTable< GtBattlePetXPEntry > sBattlePetXPGameTable
float GetBattlePetXPPerLevel(GtBattlePetXPEntry const *row)
Definition GameTables.h:316
@ EQUIP_ERR_OK
Definition ItemDefines.h:26
@ ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID
@ ITEM_MODIFIER_BATTLE_PET_BREED_DATA
@ ITEM_MODIFIER_BATTLE_PET_SPECIES_ID
@ ITEM_MODIFIER_BATTLE_PET_LEVEL
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
@ LOGIN_INS_BATTLE_PET_SLOTS
@ LOGIN_INS_BATTLE_PETS
@ LOGIN_DEL_BATTLE_PETS
@ LOGIN_DEL_BATTLE_PET_DECLINED_NAME
@ LOGIN_INS_BATTLE_PET_DECLINED_NAME
@ LOGIN_UPD_BATTLE_PETS
@ LOGIN_DEL_BATTLE_PET_SLOTS
#define sObjectMgr
Definition ObjectMgr.h:1885
std::vector< ItemPosCount > ItemPosCountVec
Definition Player.h:841
#define sRealmList
Definition RealmList.h:93
@ SPELL_AURA_MOD_BATTLE_PET_XP_PCT
#define MAX_DECLINED_NAME_CASES
@ NULL_BAG
Definition Unit.h:63
@ NULL_SLOT
Definition Unit.h:64
constexpr std::underlying_type< E >::type AsUnderlyingType(E enumValue)
Definition Util.h:565
T CalculatePct(T base, U pct)
Definition Util.h:72
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
std::vector< WorldPackets::BattlePet::BattlePetSlot > _slots
void GrantBattlePetExperience(ObjectGuid guid, uint16 xp, BattlePetXpSource xpSource)
void HealBattlePetsPct(uint8 pct)
static uint16 RollPetBreed(uint32 species)
void RemovePet(ObjectGuid guid)
uint32 GetPetUniqueSpeciesCount() const
void ToggleJournalLock(bool lock)
static void LoadAvailablePetBreeds()
bool IsJournalLockAcquired() const
static void AddBattlePetSpeciesBySpell(uint32 spellId, BattlePetSpeciesEntry const *speciesEntry)
uint16 GetMaxPetLevel() const
void ClearFanfare(ObjectGuid guid)
static BattlePetSpeciesEntry const * GetBattlePetSpeciesByCreature(uint32 creatureId)
void SummonPet(ObjectGuid guid)
BattlePetMgr(WorldSession *owner)
void ModifyName(ObjectGuid guid, std::string const &name, std::unique_ptr< DeclinedName > declinedName)
static uint32 SelectPetDisplay(BattlePetSpeciesEntry const *speciesEntry)
void UnlockSlot(BattlePetSlot slot)
uint8 GetPetCount(BattlePetSpeciesEntry const *battlePetSpecies, ObjectGuid ownerGuid) const
void SendUpdates(std::span< std::reference_wrapper< BattlePet const > const > pets, bool petAdded)
static BattlePetSpeciesEntry const * GetBattlePetSpeciesBySpell(uint32 spellId)
void AddPet(uint32 species, uint32 display, uint16 breed, BattlePetBreedQuality quality, uint16 level=1)
std::unordered_map< uint64, BattlePet > _pets
void UpdateBattlePetData(ObjectGuid guid)
BattlePet * GetPet(ObjectGuid guid)
bool IsPetInSlot(ObjectGuid guid) const
static void LoadDefaultPetQualities()
void CageBattlePet(ObjectGuid guid)
void GrantBattlePetLevel(ObjectGuid guid, uint16 grantedLevels)
bool HasMaxPetCount(BattlePetSpeciesEntry const *battlePetSpecies, ObjectGuid ownerGuid) const
void SendError(BattlePetError error, uint32 creatureId)
void ChangeBattlePetQuality(ObjectGuid guid, BattlePetBreedQuality quality)
static BattlePetBreedQuality GetDefaultPetQuality(uint32 species)
void LoadFromDB(PreparedQueryResult pets, PreparedQueryResult slots)
void SaveToDB(LoginDatabaseTransaction trans)
Class used to access individual fields of database query result.
Definition Field.h:94
bool GetBool() const noexcept
Definition Field.h:102
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
uint16 GetUInt16() const noexcept
Definition Field.cpp:43
bool IsNull() const noexcept
Definition Field.h:131
uint8 GetUInt8() const noexcept
Definition Field.cpp:29
int32 GetInt32() const noexcept
Definition Field.cpp:64
std::string GetString() const noexcept
Definition Field.cpp:113
int64 GetInt64() const noexcept
Definition Field.cpp:78
Definition Item.h:179
void SetModifier(ItemModifier modifier, uint32 value)
Definition Item.cpp:2483
LowType GetCounter() const
Definition ObjectGuid.h:336
static ObjectGuid const Empty
Definition ObjectGuid.h:314
bool IsEmpty() const
Definition ObjectGuid.h:362
std::string ToString() const
UF::UpdateField< UF::PlayerData, int32(WowCS::EntityFragment::CGObject), TYPEID_PLAYER > m_playerData
Definition Player.h:3061
Item * StoreNewItem(ItemPosCountVec const &pos, uint32 itemId, bool update, ItemRandomBonusListId randomBonusListId=0, GuidSet const &allowedLooters=GuidSet(), ItemContext context=ItemContext::NONE, std::vector< int32 > const *bonusListIDs=nullptr, bool addToCollection=true)
Definition Player.cpp:11370
void UpdateCriteria(CriteriaType type, uint64 miscValue1=0, uint64 miscValue2=0, uint64 miscValue3=0, WorldObject *ref=nullptr)
Definition Player.cpp:27588
void SetBattlePetData(BattlePets::BattlePet const *pet=nullptr)
Definition Player.cpp:22251
Creature * GetSummonedBattlePet() const
Definition Player.cpp:22242
InventoryResult CanStoreNewItem(uint8 bag, uint8 slot, ItemPosCountVec &dest, uint32 item, uint32 count, uint32 *no_space_count=nullptr) const
Definition Player.cpp:10050
void SendNewItem(Item *item, uint32 quantity, bool received, bool created, bool broadcast=false, uint32 dungeonEncounterId=0)
Definition Player.cpp:13878
void setUInt16(uint8 index, uint16 value)
void setString(uint8 index, std::string &&value)
void setUInt32(uint8 index, uint32 value)
void setInt64(uint8 index, int64 value)
void setBool(uint8 index, bool value)
void setUInt64(uint8 index, uint64 value)
void setInt32(uint8 index, int32 value)
void setUInt8(uint8 index, uint8 value)
float GetTotalAuraMultiplier(AuraType auraType) const
Definition Unit.cpp:5074
void SetWildBattlePetLevel(uint32 wildBattlePetLevel)
Definition Unit.h:1271
SpellCastResult CastSpell(CastSpellTargetArg const &targets, uint32 spellId, CastSpellExtraArgs const &args={ })
Definition Object.cpp:2217
WorldPacket const * Write() override
std::vector< std::reference_wrapper< BattlePet > > Pets
std::vector< std::reference_wrapper< BattlePetSlot > > Slots
std::vector< std::reference_wrapper< BattlePet const > > Pets
Player session in the World.
ObjectGuid GetBattlenetAccountGUID() const
Player * GetPlayer() const
void SendPacket(WorldPacket const *packet, bool forced=false)
Send a packet to the client.
uint32 GetBattlenetAccountId() const
#define sWorld
Definition World.h:916
@ SPELL_SUMMON_BATTLE_PET
@ DEFAULT_MAX_BATTLE_PETS_PER_SPECIES
@ BATTLE_PET_CAGE_ITEM_ID
static constexpr uint16 MAX_BATTLE_PET_LEVEL
time_t GetGameTime()
Definition GameTime.cpp:52
auto SelectRandomContainerElement(C const &container) -> std::add_const_t< decltype(*std::ranges::begin(container))> &
Definition Containers.h:110
constexpr auto MapValue
Definition MapUtils.h:77
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
EnumFlag< BattlePetSpeciesFlags > GetFlags() const
WorldPackets::BattlePet::BattlePet PacketInfo
std::unique_ptr<::DeclinedName > DeclinedName
BattlePetSaveInfo SaveInfo
CastSpellExtraArgs & AddSpellBP0(SpellEffectValue val)
Optional< BattlePetOwnerInfo > OwnerInfo