diff options
author | horn <pankrac.ja@seznam.cz> | 2015-09-09 14:52:32 +0200 |
---|---|---|
committer | horn <pankrac.ja@seznam.cz> | 2015-09-09 14:52:32 +0200 |
commit | e8b1faa1562c85a9e7d63af68d750cd574c0e396 (patch) | |
tree | bedeb266d1114666983e6b7ce947e87d65446308 /src/server | |
parent | e0fcb410b4d08119b7e72f154d7eed8cd2bd1e0f (diff) |
Core/BattlePets: Basics for Battle Pets
Diffstat (limited to 'src/server')
32 files changed, 1420 insertions, 46 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index f79fb8faf41..2de1dc0645b 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -22,7 +22,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() if (!m_reconnecting) m_stmts.resize(MAX_CHARACTERDATABASE_STATEMENTS); -#define SelectItemInstanceContent "ii.guid, ii.itemEntry, ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.transmogrification, ii.upgradeId, ii.enchantIllusion, ii.bonusListIDs" +#define SelectItemInstanceContent "ii.guid, ii.itemEntry, ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.transmogrification, ii.upgradeId, ii.enchantIllusion, ii.battlePetSpeciesId, ii.battlePetBreedData, ii.battlePetLevel, ii.battlePetDisplayId, ii.bonusListIDs" PrepareStatement(CHAR_DEL_QUEST_POOL_SAVE, "DELETE FROM pool_quest_save WHERE pool_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_QUEST_POOL_SAVE, "INSERT INTO pool_quest_save (pool_id, quest_id) VALUES (?, ?)", CONNECTION_ASYNC); @@ -159,8 +159,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_ITEM_BOP_TRADE, "DELETE FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_ITEM_BOP_TRADE, "INSERT INTO item_soulbound_trade_data VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_REP_INVENTORY_ITEM, "REPLACE INTO character_inventory (guid, bag, slot, item) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_REP_ITEM_INSTANCE, "REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, transmogrification, upgradeId, enchantIllusion, bonusListIDs, guid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_ITEM_INSTANCE, "UPDATE item_instance SET itemEntry = ?, owner_guid = ?, creatorGuid = ?, giftCreatorGuid = ?, count = ?, duration = ?, charges = ?, flags = ?, enchantments = ?, randomPropertyId = ?, durability = ?, playedTime = ?, text = ?, transmogrification = ?, upgradeId = ?, enchantIllusion = ?, bonusListIDs = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_REP_ITEM_INSTANCE, "REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, transmogrification, upgradeId, enchantIllusion, battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, bonusListIDs, guid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_ITEM_INSTANCE, "UPDATE item_instance SET itemEntry = ?, owner_guid = ?, creatorGuid = ?, giftCreatorGuid = ?, count = ?, duration = ?, charges = ?, flags = ?, enchantments = ?, randomPropertyId = ?, durability = ?, playedTime = ?, text = ?, transmogrification = ?, upgradeId = ?, enchantIllusion = ?, battlePetSpeciesId = ?, battlePetBreedData = ?, battlePetLevel = ?, battlePetDisplayId = ?, bonusListIDs = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ITEM_INSTANCE_ON_LOAD, "UPDATE item_instance SET duration = ?, flags = ?, durability = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_ITEM_INSTANCE, "DELETE FROM item_instance WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER, "DELETE FROM item_instance WHERE owner_guid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp index f2eb88589e1..104b5e2bbc7 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.cpp +++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp @@ -47,6 +47,20 @@ void HotfixDatabaseConnection::DoPrepareStatements() " ORDER BY ID DESC", CONNECTION_SYNCH); PREPARE_LOCALE_STMT(HOTFIX_SEL_BARBER_SHOP_STYLE, "SELECT ID, DisplayName_lang, Description_lang FROM barber_shop_style_locale WHERE locale = ?", CONNECTION_SYNCH); + // BattlePetBreedQuality.db2 + PrepareStatement(HOTFIX_SEL_BATTLE_PET_BREED_QUALITY, "SELECT ID, Quality, Modifier FROM battle_pet_breed_quality ORDER BY ID DESC", CONNECTION_SYNCH); + + // BattlePetBreedState.db2 + PrepareStatement(HOTFIX_SEL_BATTLE_PET_BREED_STATE, "SELECT ID, BreedID, State, Value FROM battle_pet_breed_state ORDER BY ID DESC", CONNECTION_SYNCH); + + // BattlePetSpecies.db2 + PrepareStatement(HOTFIX_SEL_BATTLE_PET_SPECIES, "SELECT ID, CreatureID, IconFileID, SummonSpellID, PetType, Source, Flags, " + "SourceText, Description FROM battle_pet_species ORDER BY ID DESC", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_BATTLE_PET_SPECIES, "SELECT ID, SourceText, Description FROM battle_pet_species_locale WHERE locale = ?", CONNECTION_SYNCH); + + // BattlePetSpeciesState.db2 + PrepareStatement(HOTFIX_SEL_BATTLE_PET_SPECIES_STATE, "SELECT ID, SpeciesID, State, Value FROM battle_pet_species_state ORDER BY ID DESC", CONNECTION_SYNCH); + // BroadcastText.db2 PrepareStatement(HOTFIX_SEL_BROADCAST_TEXT, "SELECT ID, Language, MaleText, FemaleText, EmoteID1, EmoteID2, EmoteID3, EmoteDelay1, EmoteDelay2, " "EmoteDelay3, SoundID, UnkEmoteID, Type FROM broadcast_text ORDER BY ID DESC", CONNECTION_SYNCH); @@ -277,6 +291,9 @@ void HotfixDatabaseConnection::DoPrepareStatements() // ItemSpecOverride.db2 PrepareStatement(HOTFIX_SEL_ITEM_SPEC_OVERRIDE, "SELECT ID, ItemID, SpecID FROM item_spec_override ORDER BY ID DESC", CONNECTION_SYNCH); + // ItemToBattlePetSpecies.db2 + PrepareStatement(HOTFIX_SEL_ITEM_TO_BATTLE_PET_SPECIES, "SELECT ID, BattlePetSpeciesID FROM item_to_battle_pet_species ORDER BY ID DESC", CONNECTION_SYNCH); + // ItemXBonusTree.db2 PrepareStatement(HOTFIX_SEL_ITEM_X_BONUS_TREE, "SELECT ID, ItemID, BonusTreeID FROM item_x_bonus_tree ORDER BY ID DESC", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h index cbff13a4fbf..196d7602da7 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.h +++ b/src/server/database/Database/Implementation/HotfixDatabase.h @@ -42,6 +42,15 @@ enum HotfixDatabaseStatements HOTFIX_SEL_BARBER_SHOP_STYLE, HOTFIX_SEL_BARBER_SHOP_STYLE_LOCALE, + HOTFIX_SEL_BATTLE_PET_BREED_QUALITY, + + HOTFIX_SEL_BATTLE_PET_BREED_STATE, + + HOTFIX_SEL_BATTLE_PET_SPECIES, + HOTFIX_SEL_BATTLE_PET_SPECIES_LOCALE, + + HOTFIX_SEL_BATTLE_PET_SPECIES_STATE, + HOTFIX_SEL_BROADCAST_TEXT, HOTFIX_SEL_BROADCAST_TEXT_LOCALE, @@ -159,6 +168,8 @@ enum HotfixDatabaseStatements HOTFIX_SEL_ITEM_SPEC_OVERRIDE, + HOTFIX_SEL_ITEM_TO_BATTLE_PET_SPECIES, + HOTFIX_SEL_ITEM_X_BONUS_TREE, HOTFIX_SEL_KEY_CHAIN, diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 869dc617246..9526e012fa2 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -144,4 +144,13 @@ void LoginDatabaseConnection::DoPrepareStatements() // Account wide toys PrepareStatement(LOGIN_SEL_ACCOUNT_TOYS, "SELECT itemId, isFavourite FROM battlenet_account_toys WHERE accountId = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_REP_ACCOUNT_TOYS, "REPLACE INTO battlenet_account_toys (accountId, itemId, isFavourite) VALUES (?, ?, ?)", CONNECTION_ASYNC); + + // Battle Pets + PrepareStatement(LOGIN_SEL_BATTLE_PETS, "SELECT guid, species, breed, level, exp, health, quality, flags, name FROM battle_pets WHERE battlenetAccountId = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_INS_BATTLE_PETS, "INSERT INTO battle_pets (guid, battlenetAccountId, species, breed, level, exp, health, quality, flags, name) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(LOGIN_DEL_BATTLE_PETS, "DELETE FROM battle_pets WHERE battlenetAccountId = ? AND guid = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_BATTLE_PETS, "UPDATE battle_pets SET level = ?, exp = ?, health = ?, quality = ?, flags = ?, name = ? WHERE battlenetAccountId = ? AND guid = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_SEL_BATTLE_PET_SLOTS, "SELECT id, battlePetGuid, locked FROM battle_pet_slots WHERE battlenetAccountId = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_INS_BATTLE_PET_SLOTS, "INSERT INTO battle_pet_slots (id, battlenetAccountId, battlePetGuid, locked) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(LOGIN_DEL_BATTLE_PET_SLOTS, "DELETE FROM battle_pet_slots WHERE battlenetAccountId = ?", CONNECTION_ASYNC); } diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index cd66be205f5..9ef214a3120 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -133,6 +133,14 @@ enum LoginDatabaseStatements LOGIN_SEL_ACCOUNT_TOYS, LOGIN_REP_ACCOUNT_TOYS, + LOGIN_SEL_BATTLE_PETS, + LOGIN_INS_BATTLE_PETS, + LOGIN_DEL_BATTLE_PETS, + LOGIN_UPD_BATTLE_PETS, + LOGIN_SEL_BATTLE_PET_SLOTS, + LOGIN_INS_BATTLE_PET_SLOTS, + LOGIN_DEL_BATTLE_PET_SLOTS, + MAX_LOGINDATABASE_STATEMENTS }; diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp index 68d76272e22..91d97746ae1 100644 --- a/src/server/game/Achievements/AchievementMgr.cpp +++ b/src/server/game/Achievements/AchievementMgr.cpp @@ -1057,6 +1057,7 @@ void AchievementMgr<T>::UpdateAchievementCriteria(AchievementCriteriaTypes type, case ACHIEVEMENT_CRITERIA_TYPE_WIN_ARENA: // This also behaves like ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA case ACHIEVEMENT_CRITERIA_TYPE_ON_LOGIN: case ACHIEVEMENT_CRITERIA_TYPE_PLACE_GARRISON_BUILDING: + case ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET_COUNT: SetCriteriaProgress(achievementCriteria, 1, referencePlayer, PROGRESS_ACCUMULATE); break; // std case: increment at miscValue1 @@ -1164,6 +1165,7 @@ void AchievementMgr<T>::UpdateAchievementCriteria(AchievementCriteriaTypes type, case ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM: case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT: case ACHIEVEMENT_CRITERIA_TYPE_RECRUIT_GARRISON_FOLLOWER: + case ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET: SetCriteriaProgress(achievementCriteria, 1, referencePlayer); break; case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT: @@ -1280,8 +1282,6 @@ void AchievementMgr<T>::UpdateAchievementCriteria(AchievementCriteriaTypes type, case ACHIEVEMENT_CRITERIA_TYPE_COUNT_OF_LFR_QUEUE_BOOSTS_BY_TANK: case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_SCENARIO_COUNT: case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_SCENARIO: - case ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET: - case ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET_COUNT: case ACHIEVEMENT_CRITERIA_TYPE_CAPTURE_BATTLE_PET: case ACHIEVEMENT_CRITERIA_TYPE_WIN_PET_BATTLE: case ACHIEVEMENT_CRITERIA_TYPE_LEVEL_BATTLE_PET: @@ -1444,12 +1444,14 @@ bool AchievementMgr<T>::IsCompletedCriteria(AchievementCriteria const* achieveme case ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS: case ACHIEVEMENT_CRITERIA_TYPE_CURRENCY: case ACHIEVEMENT_CRITERIA_TYPE_PLACE_GARRISON_BUILDING: + case ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET_COUNT: return progress->counter >= requiredAmount; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT: case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST: case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL: case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA: case ACHIEVEMENT_CRITERIA_TYPE_RECRUIT_GARRISON_FOLLOWER: + case ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET: return progress->counter >= 1; case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL: return progress->counter >= (requiredAmount * 75); @@ -2533,6 +2535,10 @@ bool AchievementMgr<T>::AdditionalRequirementsSatisfied(ModifierTreeNode const* if (!unit || unit->GetHealthPct() >= reqValue) return false; break; + case ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_BATTLE_PET_SPECIES: // 91 + if (miscValue1 != reqValue) + return false; + break; case ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_GARRISON_FOLLOWER_QUALITY: // 145 { if (!referencePlayer) diff --git a/src/server/game/BattlePets/BattlePetMgr.cpp b/src/server/game/BattlePets/BattlePetMgr.cpp new file mode 100644 index 00000000000..b65302f4050 --- /dev/null +++ b/src/server/game/BattlePets/BattlePetMgr.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "BattlePetMgr.h" +#include "Containers.h" +#include "Player.h" +#include "WorldSession.h" + +void BattlePetMgr::BattlePet::CalculateStats() +{ + float health = 0.0f; + float power = 0.0f; + float speed = 0.0f; + + // get base breed stats + auto breedState = _battlePetBreedStates.find(PacketInfo.Breed); + if (breedState == _battlePetBreedStates.end()) // non existing breed id + return; + + health = breedState->second[STATE_STAT_STAMINA]; + power = breedState->second[STATE_STAT_POWER]; + speed = breedState->second[STATE_STAT_SPEED]; + + // modify stats depending on species - not all pets have this + auto speciesState = _battlePetSpeciesStates.find(PacketInfo.Species); + if (speciesState != _battlePetSpeciesStates.end()) + { + health += speciesState->second[STATE_STAT_STAMINA]; + power += speciesState->second[STATE_STAT_POWER]; + speed += speciesState->second[STATE_STAT_SPEED]; + } + + // modify stats by quality + for (auto itr : sBattlePetBreedQualityStore) + { + if (itr->Quality == PacketInfo.Quality) + { + health *= itr->Modifier; + power *= itr->Modifier; + speed *= itr->Modifier; + break; + } + // TOOD: add check if pet has existing quality + } + + // scale stats depending on level + health *= PacketInfo.Level; + power *= PacketInfo.Level; + speed *= PacketInfo.Level; + + // set stats + // round, ceil or floor? verify this + PacketInfo.MaxHealth = uint32((round(health / 20) + 100)); + PacketInfo.Power = uint32(round(power / 100)); + PacketInfo.Speed = uint32(round(speed / 100)); +} + +std::unordered_map<uint16 /*BreedID*/, std::unordered_map<BattlePetState /*state*/, int32 /*value*/, std::hash<std::underlying_type<BattlePetState>::type> >> BattlePetMgr::_battlePetBreedStates; +std::unordered_map<uint32 /*SpeciesID*/, std::unordered_map<BattlePetState /*state*/, int32 /*value*/, std::hash<std::underlying_type<BattlePetState>::type> >> BattlePetMgr::_battlePetSpeciesStates; +std::unordered_map<uint32 /*SpeciesID*/, std::unordered_set<uint8 /*breed*/>> BattlePetMgr::_availableBreedsPerSpecies; +std::unordered_map<uint32 /*SpeciesID*/, uint8 /*quality*/> BattlePetMgr::_defaultQualityPerSpecies; + +void BattlePetMgr::Initialize() +{ + if (QueryResult result = LoginDatabase.Query("SELECT MAX(guid) FROM battle_pets")) + sObjectMgr->GetGenerator<HighGuid::BattlePet>().Set((*result)[0].GetUInt64() + 1); + + for (auto itr : sBattlePetBreedStateStore) + _battlePetBreedStates[itr->BreedID][BattlePetState(itr->State)] = itr->Value; + + for (auto itr : sBattlePetSpeciesStateStore) + _battlePetSpeciesStates[itr->SpeciesID][BattlePetState(itr->State)] = itr->Value; + + LoadAvailablePetBreeds(); + LoadDefaultPetQualities(); +} + +void BattlePetMgr::LoadAvailablePetBreeds() +{ + QueryResult result = WorldDatabase.Query("SELECT speciesId, breedId FROM battle_pet_breeds"); + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 battle pet breeds. DB table `battle_pet_breeds` is empty."); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + uint32 speciesId = fields[0].GetUInt32(); + uint16 breedId = fields[1].GetUInt16(); + + if (!sBattlePetSpeciesStore.LookupEntry(speciesId)) + { + TC_LOG_ERROR("sql.sql", "Non-existing BattlePetSpecies.db2 entry %u was referenced in `battle_pet_breeds` by row (%u, %u).", speciesId, speciesId, breedId); + continue; + } + + // TODO: verify breed id (3 - 12 (male) or 3 - 22 (male and female)) if needed + + _availableBreedsPerSpecies[speciesId].insert(breedId); + ++count; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u battle pet breeds.", count); +} + +void BattlePetMgr::LoadDefaultPetQualities() +{ + QueryResult result = WorldDatabase.Query("SELECT speciesId, quality FROM battle_pet_quality"); + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 battle pet qualities. DB table `battle_pet_quality` is empty."); + return; + } + + do + { + Field* fields = result->Fetch(); + uint32 speciesId = fields[0].GetUInt32(); + uint8 quality = fields[1].GetUInt8(); + + if (!sBattlePetSpeciesStore.LookupEntry(speciesId)) + { + TC_LOG_ERROR("sql.sql", "Non-existing BattlePetSpecies.db2 entry %u was referenced in `battle_pet_quality` by row (%u, %u).", speciesId, speciesId, quality); + continue; + } + + // TODO: verify quality (0 - 3 for player pets or 0 - 5 for both player and tamer pets) if needed + + _defaultQualityPerSpecies[speciesId] = quality; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u battle pet qualities.", uint32(_defaultQualityPerSpecies.size())); +} + +uint16 BattlePetMgr::RollPetBreed(uint32 species) +{ + auto itr = _availableBreedsPerSpecies.find(species); + if (itr == _availableBreedsPerSpecies.end()) + return 3; // default B/B + + return Trinity::Containers::SelectRandomContainerElement(itr->second); +} + +uint8 BattlePetMgr::GetDefaultPetQuality(uint32 species) +{ + auto itr = _defaultQualityPerSpecies.find(species); + if (itr == _defaultQualityPerSpecies.end()) + return 0; // default poor + + return itr->second; +} + +BattlePetMgr::BattlePetMgr(WorldSession* owner) +{ + _owner = owner; + for (uint8 i = 0; i < MAX_PET_BATTLE_SLOTS; ++i) + { + WorldPackets::BattlePet::BattlePetSlot slot; + slot.Index = i; + _slots.push_back(slot); + } +} + +void BattlePetMgr::LoadFromDB(PreparedQueryResult pets, PreparedQueryResult slots) +{ + if (pets) + { + do + { + Field* fields = pets->Fetch(); + uint32 species = fields[1].GetUInt32(); + + if (BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(species)) + { + if (GetPetCount(species) >= MAX_BATTLE_PETS_PER_SPECIES) + { + TC_LOG_ERROR("misc", "Battlenet account with id %u has more than 3 battle pets of species %u", _owner->GetBattlenetAccountId(), species); + continue; + } + + BattlePet pet; + pet.PacketInfo.Guid = ObjectGuid::Create<HighGuid::BattlePet>(fields[0].GetUInt64()); + pet.PacketInfo.Species = species; + pet.PacketInfo.Breed = fields[2].GetUInt16(); + pet.PacketInfo.Level = fields[3].GetUInt16(); + pet.PacketInfo.Exp = fields[4].GetUInt16(); + pet.PacketInfo.Health = fields[5].GetUInt32(); + pet.PacketInfo.Quality = fields[6].GetUInt8(); + pet.PacketInfo.Flags = fields[7].GetUInt16(); + pet.PacketInfo.Name = fields[8].GetString(); + pet.PacketInfo.CreatureID = speciesEntry->CreatureID; + pet.SaveInfo = BATTLE_PET_UNCHANGED; + pet.CalculateStats(); + _pets[pet.PacketInfo.Guid.GetCounter()] = pet; + } + } while (pets->NextRow()); + } + + if (slots) + { + uint8 i = 0; // slots->GetRowCount() should equal MAX_BATTLE_PET_SLOTS + + do + { + Field* fields = slots->Fetch(); + _slots[i].Index = fields[0].GetUInt8(); + auto itr = _pets.find(fields[1].GetUInt64()); + if (itr != _pets.end()) + _slots[i].Pet = itr->second.PacketInfo; + _slots[i].Locked = fields[2].GetBool(); + i++; + } while (slots->NextRow()); + } +} + +void BattlePetMgr::SaveToDB(SQLTransaction& trans) +{ + PreparedStatement* stmt = nullptr; + + for (auto itr = _pets.begin(); itr != _pets.end();) + { + switch (itr->second.SaveInfo) + { + case BATTLE_PET_NEW: + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BATTLE_PETS); + stmt->setUInt64(0, itr->first); + stmt->setUInt32(1, _owner->GetBattlenetAccountId()); + stmt->setUInt32(2, itr->second.PacketInfo.Species); + stmt->setUInt16(3, itr->second.PacketInfo.Breed); + stmt->setUInt16(4, itr->second.PacketInfo.Level); + stmt->setUInt16(5, itr->second.PacketInfo.Exp); + stmt->setUInt32(6, itr->second.PacketInfo.Health); + stmt->setUInt8(7, itr->second.PacketInfo.Quality); + stmt->setUInt16(8, itr->second.PacketInfo.Flags); + stmt->setString(9, itr->second.PacketInfo.Name); + trans->Append(stmt); + itr->second.SaveInfo = BATTLE_PET_UNCHANGED; + ++itr; + break; + case BATTLE_PET_CHANGED: + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BATTLE_PETS); + stmt->setUInt16(0, itr->second.PacketInfo.Level); + stmt->setUInt16(1, itr->second.PacketInfo.Exp); + stmt->setUInt32(2, itr->second.PacketInfo.Health); + stmt->setUInt8(3, itr->second.PacketInfo.Quality); + stmt->setUInt16(4, itr->second.PacketInfo.Flags); + stmt->setString(5, itr->second.PacketInfo.Name); + stmt->setUInt32(6, _owner->GetBattlenetAccountId()); + stmt->setUInt64(7, itr->first); + trans->Append(stmt); + itr->second.SaveInfo = BATTLE_PET_UNCHANGED; + ++itr; + break; + case BATTLE_PET_REMOVED: + stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PETS); + stmt->setUInt32(0, _owner->GetBattlenetAccountId()); + stmt->setUInt64(1, itr->first); + trans->Append(stmt); + itr = _pets.erase(itr); + break; + default: + ++itr; + break; + } + } + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PET_SLOTS); + stmt->setUInt32(0, _owner->GetBattlenetAccountId()); + trans->Append(stmt); + + for (WorldPackets::BattlePet::BattlePetSlot const& slot : _slots) + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BATTLE_PET_SLOTS); + stmt->setUInt8(0, slot.Index); + stmt->setUInt32(1, _owner->GetBattlenetAccountId()); + stmt->setUInt64(2, slot.Pet.Guid.GetCounter()); + stmt->setBool(3, slot.Locked); + trans->Append(stmt); + } +} + +BattlePetMgr::BattlePet* BattlePetMgr::GetPet(ObjectGuid guid) +{ + auto itr = _pets.find(guid.GetCounter()); + if (itr != _pets.end()) + return &itr->second; + + return nullptr; +} + +void BattlePetMgr::AddPet(uint32 species, uint32 creatureId, uint16 breed, uint8 quality, uint16 level /*= 1*/) +{ + BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(species); + if (!battlePetSpecies) // should never happen + return; + + BattlePet pet; + pet.PacketInfo.Guid = ObjectGuid::Create<HighGuid::BattlePet>(sObjectMgr->GetGenerator<HighGuid::BattlePet>().Generate()); + pet.PacketInfo.Species = species; + pet.PacketInfo.CreatureID = creatureId; + pet.PacketInfo.Level = level; + pet.PacketInfo.Exp = 0; + pet.PacketInfo.Flags = 0; + pet.PacketInfo.Breed = breed; + pet.PacketInfo.Quality = quality; + pet.PacketInfo.Name = ""; + pet.CalculateStats(); + pet.PacketInfo.Health = pet.PacketInfo.MaxHealth; + pet.SaveInfo = BATTLE_PET_NEW; + + _pets[pet.PacketInfo.Guid.GetCounter()] = pet; + + std::vector<BattlePet> updates; + updates.push_back(pet); + SendUpdates(updates, true); + + _owner->GetPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET, species); +} + +void BattlePetMgr::RemovePet(ObjectGuid guid) +{ + BattlePet* pet = GetPet(guid); + if (!pet) + return; + + pet->SaveInfo = BATTLE_PET_REMOVED; + + // spell is not unlearned on retail + /*if (GetPetCount(pet->PacketInfo.Species) == 0) + if (BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species)) + _owner->GetPlayer()->RemoveSpell(speciesEntry->SummonSpellID);*/ +} + +uint8 BattlePetMgr::GetPetCount(uint32 species) const +{ + uint8 count = 0; + for (auto& itr : _pets) + if (itr.second.PacketInfo.Species == species && itr.second.SaveInfo != BATTLE_PET_REMOVED) + count++; + + return count; +} + +void BattlePetMgr::UnlockSlot(uint8 slot) +{ + if (!_slots[slot].Locked) + return; + + _slots[slot].Locked = false; + + WorldPackets::BattlePet::PetBattleSlotUpdates updates; + updates.Slots.push_back(_slots[slot]); + updates.AutoSlotted = false; // what's this? + updates.NewSlot = true; // causes the "new slot unlocked" bubble to appear + _owner->SendPacket(updates.Write()); +} + +std::vector<BattlePetMgr::BattlePet> BattlePetMgr::GetLearnedPets() const +{ + std::vector<BattlePet> pets; + for (auto& pet : _pets) + if (pet.second.SaveInfo != BATTLE_PET_REMOVED) + pets.push_back(pet.second); + + return pets; +} + +void BattlePetMgr::CageBattlePet(ObjectGuid guid) +{ + BattlePet* pet = GetPet(guid); + if (!pet) + return; + + ItemPosCountVec dest; + + if (_owner->GetPlayer()->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, BATTLE_PET_CAGE_ITEM_ID, 1) != EQUIP_ERR_OK) + return; + + Item* item = _owner->GetPlayer()->StoreNewItem(dest, BATTLE_PET_CAGE_ITEM_ID, true); + if (!item) + return; + + item->SetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID, pet->PacketInfo.Species); + item->SetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA, pet->PacketInfo.Breed | (pet->PacketInfo.Quality << 24)); + item->SetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL, pet->PacketInfo.Level); + item->SetModifier(ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID, pet->PacketInfo.CreatureID); + + // FIXME: "You create: ." - item name missing in chat + _owner->GetPlayer()->SendNewItem(item, 1, true, true); + + RemovePet(guid); + + WorldPackets::BattlePet::BattlePetDeleted deletePet; + deletePet.PetGuid = guid; + _owner->SendPacket(deletePet.Write()); +} + +void BattlePetMgr::HealBattlePetsPct(uint8 pct) +{ + // TODO: After each Pet Battle, any injured companion will automatically + // regain 50 % of the damage that was taken during combat + std::vector<BattlePet> updates; + + for (auto& pet : _pets) + if (pet.second.PacketInfo.Health != pet.second.PacketInfo.MaxHealth) + { + pet.second.PacketInfo.Health += CalculatePct(pet.second.PacketInfo.MaxHealth, pct); + // don't allow Health to be greater than MaxHealth + pet.second.PacketInfo.Health = std::min(pet.second.PacketInfo.Health, pet.second.PacketInfo.MaxHealth); + if (pet.second.SaveInfo != BATTLE_PET_NEW) + pet.second.SaveInfo = BATTLE_PET_CHANGED; + updates.push_back(pet.second); + } + + SendUpdates(updates, false); +} + +void BattlePetMgr::SummonPet(ObjectGuid guid) +{ + BattlePet* pet = GetPet(guid); + if (!pet) + return; + + BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(pet->PacketInfo.Species); + if (!speciesEntry) + return; + + // TODO: set proper CreatureID for spell DEFAULT_SUMMON_BATTLE_PET_SPELL (default EffectMiscValueA is 40721 - Murkimus the Gladiator) + _owner->GetPlayer()->CastSpell(_owner->GetPlayer(), speciesEntry->SummonSpellID ? speciesEntry->SummonSpellID : DEFAULT_SUMMON_BATTLE_PET_SPELL); + + // TODO: set pet level, quality... update fields +} + +void BattlePetMgr::SendUpdates(std::vector<BattlePet> pets, bool petAdded) +{ + WorldPackets::BattlePet::BattlePetUpdates updates; + for (auto pet : pets) + updates.Pets.push_back(pet.PacketInfo); + + updates.PetAdded = petAdded; + _owner->SendPacket(updates.Write()); +} + +void BattlePetMgr::SendError(BattlePetError error, uint32 creatureId) +{ + WorldPackets::BattlePet::BattlePetError battlePetError; + battlePetError.Result = error; + battlePetError.CreatureID = creatureId; + _owner->SendPacket(battlePetError.Write()); +} diff --git a/src/server/game/BattlePets/BattlePetMgr.h b/src/server/game/BattlePets/BattlePetMgr.h new file mode 100644 index 00000000000..97b1b34c13c --- /dev/null +++ b/src/server/game/BattlePets/BattlePetMgr.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BattlePetMgr_h__ +#define BattlePetMgr_h__ + +#include "DB2Stores.h" +#include "BattlePetPackets.h" + +enum BattlePetMisc +{ + MAX_PET_BATTLE_SLOTS = 3, + MAX_BATTLE_PETS_PER_SPECIES = 3, + BATTLE_PET_CAGE_ITEM_ID = 82800, + DEFAULT_SUMMON_BATTLE_PET_SPELL = 118301 +}; + +// TODO: fix values in this enum +enum BattlePetError +{ + BATTLEPETRESULT_CANT_HAVE_MORE_PETS_OF_THAT_TYPE = 9, + BATTLEPETRESULT_TOO_HIGH_LEVEL_TO_UNCAGE = 12, + BATTLEPETRESULT_CANT_HAVE_MORE_PETS = 13, + + // wrong order + BATTLEPETRESULT_DUPLICATE_CONVERTED_PET, + BATTLEPETRESULT_NEED_TO_UNLOCK, + BATTLEPETRESULT_BAD_PARAM, + BATTLEPETRESULT_LOCKED_PET_ALREADY_EXISTS, + BATTLEPETRESULT_OK, + BATTLEPETRESULT_UNCAPTURABLE, + BATTLEPETRESULT_CANT_INVALID_CHARACTER_GUID +}; + +// taken from BattlePetState.db2 - it seems to store some initial values for battle pets +// there are only values used in BattlePetSpeciesState.db2 and BattlePetBreedState.db2 +// TODO: expand this enum if needed +enum BattlePetState +{ + STATE_MAX_HEALTH_BONUS = 2, + STATE_INTERNAL_INITIAL_LEVEL = 17, + STATE_STAT_POWER = 18, + STATE_STAT_STAMINA = 19, + STATE_STAT_SPEED = 20, + STATE_MOD_DAMAGE_DEALT_PERCENT = 23, + STATE_GENDER = 78, // 1 - male, 2 - female + STATE_COSMETIC_WATER_BUBBLED = 85, + STATE_SPECIAL_IS_COCKROACH = 93, + STATE_COSMETIC_FLY_TIER = 128, + STATE_COSMETIC_BIGGLESWORTH = 144, + STATE_PASSIVE_ELITE = 153, + STATE_PASSIVE_BOSS = 162, + STATE_COSMETIC_TREASURE_GOBLIN = 176, + // these are not in BattlePetState.db2 but are used in BattlePetSpeciesState.db2 + STATE_START_WITH_BUFF = 183, + STATE_START_WITH_BUFF_2 = 184 +}; + +enum BattlePetSaveInfo +{ + BATTLE_PET_UNCHANGED = 0, + BATTLE_PET_CHANGED = 1, + BATTLE_PET_NEW = 2, + BATTLE_PET_REMOVED = 3 +}; + +class BattlePetMgr +{ +public: + struct BattlePet + { + void CalculateStats(); + + WorldPackets::BattlePet::BattlePet PacketInfo; + BattlePetSaveInfo SaveInfo; + }; + + explicit BattlePetMgr(WorldSession* owner); + + static void Initialize(); + + static uint16 RollPetBreed(uint32 species); + static uint8 GetDefaultPetQuality(uint32 species); + + void LoadFromDB(PreparedQueryResult pets, PreparedQueryResult slots); + void SaveToDB(SQLTransaction& trans); + + BattlePet* GetPet(ObjectGuid guid); + void AddPet(uint32 species, uint32 creatureId, uint16 breed, uint8 quality, uint16 level = 1); + void RemovePet(ObjectGuid guid); + + uint8 GetPetCount(uint32 species) const; + + WorldPackets::BattlePet::BattlePetSlot* GetSlot(uint8 slot) { return &_slots[slot]; } + void UnlockSlot(uint8 slot); + + WorldSession* GetOwner() const { return _owner; } + + uint16 GetTrapLevel() const { return _trapLevel; } + std::vector<BattlePet> GetLearnedPets() const; + std::vector<WorldPackets::BattlePet::BattlePetSlot> GetSlots() const { return _slots; } + + void CageBattlePet(ObjectGuid guid); + void HealBattlePetsPct(uint8 pct); + + void SummonPet(ObjectGuid guid); + + void SendUpdates(std::vector<BattlePet> pets, bool petAdded); + void SendError(BattlePetError error, uint32 creatureId); + +private: + WorldSession* _owner; + uint16 _trapLevel = 0; + std::unordered_map<uint64 /*battlePetGuid*/, BattlePet> _pets; + std::vector<WorldPackets::BattlePet::BattlePetSlot> _slots; + + static void LoadAvailablePetBreeds(); + static void LoadDefaultPetQualities(); + + // hash no longer required in C++14 + static std::unordered_map<uint16 /*BreedID*/, std::unordered_map<BattlePetState /*state*/, int32 /*value*/, std::hash<std::underlying_type<BattlePetState>::type> >> _battlePetBreedStates; + static std::unordered_map<uint32 /*SpeciesID*/, std::unordered_map<BattlePetState /*state*/, int32 /*value*/, std::hash<std::underlying_type<BattlePetState>::type> >> _battlePetSpeciesStates; + static std::unordered_map<uint32 /*SpeciesID*/, std::unordered_set<uint8 /*breed*/>> _availableBreedsPerSpecies; + static std::unordered_map<uint32 /*SpeciesID*/, uint8 /*quality*/> _defaultQualityPerSpecies; +}; + +#endif // BattlePetMgr_h__ diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt index 0ed6b78d580..14bb8f25389 100644 --- a/src/server/game/CMakeLists.txt +++ b/src/server/game/CMakeLists.txt @@ -16,6 +16,7 @@ file(GLOB_RECURSE sources_AuctionHouse AuctionHouse/*.cpp AuctionHouse/*.h) file(GLOB_RECURSE sources_AuctionHouseBot AuctionHouseBot/*.cpp AuctionHouseBot/*.h) file(GLOB_RECURSE sources_Battlefield Battlefield/*.cpp Battlefield/*.h) file(GLOB_RECURSE sources_Battlegrounds Battlegrounds/*.cpp Battlegrounds/*.h) +file(GLOB_RECURSE sources_BattlePets BattlePets/*.cpp BattlePets/*.h) file(GLOB_RECURSE sources_Calendar Calendar/*.cpp Calendar/*.h) file(GLOB_RECURSE sources_Chat Chat/*.cpp Chat/*.h) file(GLOB_RECURSE sources_Combat Combat/*.cpp Combat/*.h) @@ -68,6 +69,7 @@ set(game_STAT_SRCS ${sources_AuctionHouseBot} ${sources_Battlefield} ${sources_Battlegrounds} + ${sources_BattlePets} ${sources_Calendar} ${sources_Chat} ${sources_Combat} @@ -118,6 +120,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/Battlefield/Zones ${CMAKE_CURRENT_SOURCE_DIR}/Battlegrounds ${CMAKE_CURRENT_SOURCE_DIR}/Battlegrounds/Zones + ${CMAKE_CURRENT_SOURCE_DIR}/BattlePets ${CMAKE_CURRENT_SOURCE_DIR}/Calendar ${CMAKE_CURRENT_SOURCE_DIR}/Chat ${CMAKE_CURRENT_SOURCE_DIR}/Chat/Channels diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp index 0485715f287..ae26119986c 100644 --- a/src/server/game/DataStores/DB2Stores.cpp +++ b/src/server/game/DataStores/DB2Stores.cpp @@ -29,6 +29,10 @@ DB2Storage<AreaGroupEntry> sAreaGroupStore("AreaGroup.db2", DB2Storage<AreaGroupMemberEntry> sAreaGroupMemberStore("AreaGroupMember.db2", AreaGroupMemberFormat, HOTFIX_SEL_AREA_GROUP_MEMBER); DB2Storage<AuctionHouseEntry> sAuctionHouseStore("AuctionHouse.db2", AuctionHouseFormat, HOTFIX_SEL_AUCTION_HOUSE); DB2Storage<BarberShopStyleEntry> sBarberShopStyleStore("BarberShopStyle.db2", BarberShopStyleFormat, HOTFIX_SEL_BARBER_SHOP_STYLE); +DB2Storage<BattlePetBreedQualityEntry> sBattlePetBreedQualityStore("BattlePetBreedQuality.db2", BattlePetBreedQualityFormat, HOTFIX_SEL_BATTLE_PET_BREED_QUALITY); +DB2Storage<BattlePetBreedStateEntry> sBattlePetBreedStateStore("BattlePetBreedState.db2", BattlePetBreedStateFormat, HOTFIX_SEL_BATTLE_PET_BREED_STATE); +DB2Storage<BattlePetSpeciesEntry> sBattlePetSpeciesStore("BattlePetSpecies.db2", BattlePetSpeciesFormat, HOTFIX_SEL_BATTLE_PET_SPECIES); +DB2Storage<BattlePetSpeciesStateEntry> sBattlePetSpeciesStateStore("BattlePetSpeciesState.db2", BattlePetSpeciesStateFormat, HOTFIX_SEL_BATTLE_PET_SPECIES_STATE); DB2Storage<BroadcastTextEntry> sBroadcastTextStore("BroadcastText.db2", BroadcastTextFormat, HOTFIX_SEL_BROADCAST_TEXT); DB2Storage<CharStartOutfitEntry> sCharStartOutfitStore("CharStartOutfit.db2", CharStartOutfitFormat, HOTFIX_SEL_CHAR_START_OUTFIT); DB2Storage<ChrClassesXPowerTypesEntry> sChrClassesXPowerTypesStore("ChrClassesXPowerTypes.db2", ChrClassesXPowerTypesFormat, HOTFIX_SEL_CHR_CLASSES_X_POWER_TYPES); @@ -78,6 +82,7 @@ DB2Storage<ItemRandomSuffixEntry> sItemRandomSuffixStore("ItemRand DB2Storage<ItemSparseEntry> sItemSparseStore("Item-sparse.db2", ItemSparseFormat, HOTFIX_SEL_ITEM_SPARSE); DB2Storage<ItemSpecEntry> sItemSpecStore("ItemSpec.db2", ItemSpecFormat, HOTFIX_SEL_ITEM_SPEC); DB2Storage<ItemSpecOverrideEntry> sItemSpecOverrideStore("ItemSpecOverride.db2", ItemSpecOverrideFormat, HOTFIX_SEL_ITEM_SPEC_OVERRIDE); +DB2Storage<ItemToBattlePetSpeciesEntry> sItemToBattlePetSpeciesStore("ItemToBattlePetSpecies.db2", ItemToBattlePetSpeciesFormat, HOTFIX_SEL_ITEM_TO_BATTLE_PET_SPECIES); DB2Storage<ItemXBonusTreeEntry> sItemXBonusTreeStore("ItemXBonusTree.db2", ItemXBonusTreeFormat, HOTFIX_SEL_ITEM_X_BONUS_TREE); DB2Storage<KeyChainEntry> sKeyChainStore("KeyChain.db2", KeyChainFormat, HOTFIX_SEL_KEY_CHAIN); DB2Storage<MailTemplateEntry> sMailTemplateStore("MailTemplate.db2", MailTemplateFormat, HOTFIX_SEL_MAIL_TEMPLATE); @@ -196,8 +201,12 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale) LOAD_DB2(sAchievementStore); LOAD_DB2(sAreaGroupMemberStore); LOAD_DB2(sAreaGroupStore); + LOAD_DB2(sBattlePetBreedQualityStore); + LOAD_DB2(sBattlePetBreedStateStore); LOAD_DB2(sAuctionHouseStore); LOAD_DB2(sBarberShopStyleStore); + LOAD_DB2(sBattlePetSpeciesStore); + LOAD_DB2(sBattlePetSpeciesStateStore); LOAD_DB2(sBroadcastTextStore); LOAD_DB2(sCharStartOutfitStore); LOAD_DB2(sChrClassesXPowerTypesStore); @@ -247,6 +256,7 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale) LOAD_DB2(sItemSpecOverrideStore); LOAD_DB2(sItemSpecStore); LOAD_DB2(sItemStore); + LOAD_DB2(sItemToBattlePetSpeciesStore); LOAD_DB2(sItemXBonusTreeStore); LOAD_DB2(sKeyChainStore); LOAD_DB2(sMailTemplateStore); diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h index 685d251f685..4184ab5b5d4 100644 --- a/src/server/game/DataStores/DB2Stores.h +++ b/src/server/game/DataStores/DB2Stores.h @@ -27,6 +27,10 @@ extern DB2Storage<AchievementEntry> sAchievementStore; extern DB2Storage<AuctionHouseEntry> sAuctionHouseStore; extern DB2Storage<BarberShopStyleEntry> sBarberShopStyleStore; +extern DB2Storage<BattlePetBreedQualityEntry> sBattlePetBreedQualityStore; +extern DB2Storage<BattlePetBreedStateEntry> sBattlePetBreedStateStore; +extern DB2Storage<BattlePetSpeciesEntry> sBattlePetSpeciesStore; +extern DB2Storage<BattlePetSpeciesStateEntry> sBattlePetSpeciesStateStore; extern DB2Storage<BroadcastTextEntry> sBroadcastTextStore; extern DB2Storage<CharStartOutfitEntry> sCharStartOutfitStore; extern DB2Storage<CinematicSequencesEntry> sCinematicSequencesStore; @@ -70,6 +74,7 @@ extern DB2Storage<ItemRandomSuffixEntry> sItemRandomSuffixStore; extern DB2Storage<ItemSparseEntry> sItemSparseStore; extern DB2Storage<ItemSpecEntry> sItemSpecStore; extern DB2Storage<ItemSpecOverrideEntry> sItemSpecOverrideStore; +extern DB2Storage<ItemToBattlePetSpeciesEntry> sItemToBattlePetSpeciesStore; extern DB2Storage<MailTemplateEntry> sMailTemplateStore; extern DB2Storage<ModifierTreeEntry> sModifierTreeStore; extern DB2Storage<MountCapabilityEntry> sMountCapabilityStore; diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h index 289d895c024..f2f8bec2b68 100644 --- a/src/server/game/DataStores/DB2Structure.h +++ b/src/server/game/DataStores/DB2Structure.h @@ -77,6 +77,42 @@ struct BarberShopStyleEntry uint32 Data; // 7 (real ID to hair/facial hair) }; +struct BattlePetBreedQualityEntry +{ + uint32 ID; // 0 + uint32 Quality; // 1 + float Modifier; // 2 +}; + +struct BattlePetBreedStateEntry +{ + uint32 ID; // 0 + uint32 BreedID; // 1 + uint32 State; // 2 + int32 Value; // 3 +}; + +struct BattlePetSpeciesEntry +{ + uint32 ID; // 0 + uint32 CreatureID; // 1 + uint32 IconFileID; // 2 + uint32 SummonSpellID; // 3 + uint32 PetType; // 4 + int32 Source; // 5 + uint32 Flags; // 6 + LocalizedString* SourceText; // 7 + LocalizedString* Description; // 8 +}; + +struct BattlePetSpeciesStateEntry +{ + uint32 ID; // 0 + uint32 SpeciesID; // 1 + uint32 State; // 2 + int32 Value; // 3 +}; + #define MAX_BROADCAST_TEXT_EMOTES 3 struct BroadcastTextEntry @@ -830,6 +866,12 @@ struct ItemSpecOverrideEntry uint32 SpecID; // 2 }; +struct ItemToBattlePetSpeciesEntry +{ + uint32 ID; // 0 + uint32 BattlePetSpeciesID; // 1 +}; + struct ItemXBonusTreeEntry { uint32 ID; // 0 diff --git a/src/server/game/DataStores/DB2fmt.h b/src/server/game/DataStores/DB2fmt.h index 0ebf8d2063e..ab9e2e0ae4e 100644 --- a/src/server/game/DataStores/DB2fmt.h +++ b/src/server/game/DataStores/DB2fmt.h @@ -23,6 +23,10 @@ char const AreaGroupFormat[] = "n"; char const AreaGroupMemberFormat[] = "nii"; char const AuctionHouseFormat[] = "niiis"; char const BarberShopStyleFormat[] = "nissfiii"; +char const BattlePetBreedQualityFormat[] = "nif"; +char const BattlePetBreedStateFormat[] = "niii"; +char const BattlePetSpeciesFormat[] = "niiiiiiss"; +char const BattlePetSpeciesStateFormat[] = "niii"; char const BroadcastTextFormat[] = "nissiiiiiiiii"; char const CharStartOutfitFormat[] = "nbbbbiiiiiiiiiiiiiiiiiiiiiiiiii"; char const ChrClassesXPowerTypesFormat[] = "iii"; @@ -72,6 +76,7 @@ char const ItemRandomSuffixFormat[] = "nssiiiiiiiiii"; char const ItemSparseFormat[] = "niiiiffiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiffffffffffiiifisssssiiiiiiiiiiiiiiiiiiifiiifiii"; char const ItemSpecFormat[] = "niiiiii"; char const ItemSpecOverrideFormat[] = "nii"; +char const ItemToBattlePetSpeciesFormat[] = "ni"; char const ItemXBonusTreeFormat[] = "nii"; char const KeyChainFormat[] = "nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; char const MailTemplateFormat[] = "ns"; diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index d7125293671..ed1a1470e91 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -159,6 +159,7 @@ enum AchievementCriteriaAdditionalCondition ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_RATED_BATTLEGROUND = 63, // NYI ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_PROJECT_RARITY = 65, ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_PROJECT_RACE = 66, + ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_BATTLE_PET_SPECIES = 91, ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_GARRISON_FOLLOWER_QUALITY = 145, ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_GARRISON_FOLLOWER_LEVEL = 146, ACHIEVEMENT_CRITERIA_ADDITIONAL_CONDITION_GARRISON_RARE_MISSION = 147, // NYI diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 4d65598c298..6c7d390c069 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -351,6 +351,10 @@ void Item::SaveToDB(SQLTransaction& trans) stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_TRANSMOG_ITEM_ID) | (GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_MOD) << 24)); stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_UPGRADE_ID)); stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION)); + stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)); + stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA)); + stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL)); + stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID)); std::ostringstream bonusListIDs; for (uint32 bonusListID : GetDynamicValues(ITEM_DYNAMIC_FIELD_BONUSLIST_IDS)) @@ -405,8 +409,10 @@ void Item::SaveToDB(SQLTransaction& trans) bool Item::LoadFromDB(ObjectGuid::LowType guid, ObjectGuid ownerGuid, Field* fields, uint32 entry) { - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - //result = CharacterDatabase.PQuery("SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, transmogrification, upgradeId, enchantIllusion, bonusListIDs FROM item_instance WHERE guid = '%u'", guid); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + //result = CharacterDatabase.PQuery("SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, + // 13 14 15 16 17 18 19 20 + // transmogrification, upgradeId, enchantIllusion, battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, bonusListIDs FROM item_instance WHERE guid = '%u'", guid); // create item before any checks for store correct guid // and allow use "FSetState(ITEM_REMOVED); SaveToDB();" for deleting item from DB @@ -482,8 +488,12 @@ bool Item::LoadFromDB(ObjectGuid::LowType guid, ObjectGuid ownerGuid, Field* fie } SetModifier(ITEM_MODIFIER_UPGRADE_ID, fields[14].GetUInt32()); SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION, fields[15].GetUInt32()); + SetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID, fields[16].GetUInt32()); + SetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA, fields[17].GetUInt32()); + SetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL, fields[18].GetUInt16()); + SetModifier(ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID, fields[19].GetUInt32()); - Tokenizer bonusListIDs(fields[16].GetString(), ' '); + Tokenizer bonusListIDs(fields[20].GetString(), ' '); for (char const* token : bonusListIDs) { uint32 bonusListID = atoul(token); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 3b62f9f59a1..901bd81ecba 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -27,6 +27,7 @@ #include "Battleground.h" #include "BattlegroundMgr.h" #include "BattlegroundScore.h" +#include "BattlePetMgr.h" #include "CellImpl.h" #include "ChannelMgr.h" #include "CharacterDatabaseCleaner.h" @@ -13699,10 +13700,10 @@ void Player::SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, packet.Quantity = quantity; packet.QuantityInInventory = GetItemCount(item->GetEntry()); //packet.DungeonEncounterID; - //packet.BattlePetBreedID; - //packet.BattlePetBreedQuality; - //packet.BattlePetSpeciesID; - //packet.BattlePetLevel; + packet.BattlePetBreedID = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) & 0xFFFFFF; + packet.BattlePetBreedQuality = (item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF; + packet.BattlePetSpeciesID = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID); + packet.BattlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL); packet.ItemGUID = item->GetGUID(); @@ -17650,8 +17651,10 @@ void Player::LoadCorpse() void Player::_LoadInventory(PreparedQueryResult result, uint32 timeDiff) { - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - // SELECT ii.guid, ii.itemEntry, ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.transmogrification, ii.upgradeId, ii.enchantIllusion, ii.bonusListIDs, bag, slot FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid WHERE ci.guid = ? ORDER BY bag, slot + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + // SELECT ii.guid, ii.itemEntry, ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.transmogrification, ii.upgradeId + // 15 16 17 18 19 20 21 22 + // ii.enchantIllusion, battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, ii.bonusListIDs, bag, slot FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid WHERE ci.guid = ? ORDER BY bag, slot //NOTE: the "order by `bag`" is important because it makes sure //the bagMap is filled before items in the bags are loaded //NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?) @@ -17673,8 +17676,8 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timeDiff) Field* fields = result->Fetch(); if (Item* item = _LoadItem(trans, zoneId, timeDiff, fields)) { - ObjectGuid bagGuid = fields[17].GetUInt64() ? ObjectGuid::Create<HighGuid::Item>(fields[17].GetUInt64()) : ObjectGuid::Empty; - uint8 slot = fields[18].GetUInt8(); + ObjectGuid bagGuid = fields[21].GetUInt64() ? ObjectGuid::Create<HighGuid::Item>(fields[21].GetUInt64()) : ObjectGuid::Empty; + uint8 slot = fields[22].GetUInt8(); uint8 err = EQUIP_ERR_OK; // Item is not in bag @@ -17995,7 +17998,7 @@ void Player::_LoadMailedItems(Mail* mail) Item* item = NewItemOrBag(proto); - ObjectGuid ownerGuid = fields[17].GetUInt64() ? ObjectGuid::Create<HighGuid::Player>(fields[17].GetUInt64()) : ObjectGuid::Empty; + ObjectGuid ownerGuid = fields[21].GetUInt64() ? ObjectGuid::Create<HighGuid::Player>(fields[21].GetUInt64()) : ObjectGuid::Empty; if (!item->LoadFromDB(itemGuid, ownerGuid, fields, itemEntry)) { TC_LOG_ERROR("entities.player", "Player::_LoadMailedItems - Item in mail (%u) doesn't exist !!!! - item guid: " UI64FMTD ", deleted from mail", mail->messageID, itemGuid); @@ -19188,9 +19191,11 @@ void Player::SaveToDB(bool create /*=false*/) CharacterDatabase.CommitTransaction(trans); - SQLTransaction transLogin = LoginDatabase.BeginTransaction(); - GetSession()->SaveAccountToys(transLogin); - LoginDatabase.CommitTransaction(transLogin); + // TODO: Move this out + trans = LoginDatabase.BeginTransaction(); + GetSession()->SaveAccountToys(trans); + GetSession()->GetBattlePetMgr()->SaveToDB(trans); + LoginDatabase.CommitTransaction(trans); // save pet (hunter pet level and experience and all type pets health/mana). if (Pet* pet = GetPet()) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index cddcfd1ef80..d4973e0f332 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -532,7 +532,7 @@ enum PlayerFlags PLAYER_FLAGS_UNK21 = 0x00200000, PLAYER_FLAGS_COMMENTATOR2 = 0x00400000, PLAYER_ALLOW_ONLY_ABILITY = 0x00800000, // used by bladestorm and killing spree, allowed only spells with SPELL_ATTR0_REQ_AMMO, SPELL_EFFECT_ATTACK, checked only for active player - PLAYER_FLAGS_UNK24 = 0x01000000, // disabled all melee ability on tab include autoattack + PLAYER_FLAGS_PET_BATTLES_UNLOCKED = 0x01000000, // enables pet battles PLAYER_FLAGS_NO_XP_GAIN = 0x02000000, PLAYER_FLAGS_UNK26 = 0x04000000, PLAYER_FLAGS_AUTO_DECLINE_GUILD = 0x08000000, // Automatically declines guild invites diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index e93a1da83b3..e7347a00732 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -368,7 +368,7 @@ void Guild::BankTab::LoadFromDB(Field* fields) bool Guild::BankTab::LoadItemFromDB(Field* fields) { - uint8 slotId = fields[19].GetUInt8(); + uint8 slotId = fields[23].GetUInt8(); ObjectGuid::LowType itemGuid = fields[0].GetUInt64(); uint32 itemEntry = fields[1].GetUInt32(); if (slotId >= GUILD_BANK_MAX_SLOTS) @@ -2395,7 +2395,7 @@ void Guild::LoadBankTabFromDB(Field* fields) bool Guild::LoadBankItemFromDB(Field* fields) { - uint8 tabId = fields[18].GetUInt8(); + uint8 tabId = fields[22].GetUInt8(); if (tabId >= _GetPurchasedTabsSize()) { TC_LOG_ERROR("guild", "Invalid tab for item (GUID: %u, id: #%u) in guild bank, skipped.", diff --git a/src/server/game/Guilds/GuildMgr.cpp b/src/server/game/Guilds/GuildMgr.cpp index 44816f5fd67..6db5b84a696 100644 --- a/src/server/game/Guilds/GuildMgr.cpp +++ b/src/server/game/Guilds/GuildMgr.cpp @@ -395,10 +395,10 @@ void GuildMgr::LoadGuilds() // Delete orphan guild bank items CharacterDatabase.DirectExecute("DELETE gbi FROM guild_bank_item gbi LEFT JOIN guild g ON gbi.guildId = g.guildId WHERE g.guildId IS NULL"); - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - // SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, transmogrification, upgradeId, enchantIllusion, bonusListIDs, - // 17 18 19 - // guildid, TabId, SlotId FROM guild_bank_item gbi INNER JOIN item_instance ii ON gbi.item_guid = ii.guid + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + // SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, transmogrification, upgradeId, enchantIllusion, + // 17 18 19 20 21 22 23 + // battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, guildid, TabId, SlotId FROM guild_bank_item gbi INNER JOIN item_instance ii ON gbi.item_guid = ii.guid PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_GUILD_BANK_ITEMS)); if (!result) @@ -411,7 +411,7 @@ void GuildMgr::LoadGuilds() do { Field* fields = result->Fetch(); - uint64 guildId = fields[17].GetUInt64(); + uint64 guildId = fields[21].GetUInt64(); if (Guild* guild = GetGuildById(guildId)) guild->LoadBankItemFromDB(fields); diff --git a/src/server/game/Handlers/BattlePetHandler.cpp b/src/server/game/Handlers/BattlePetHandler.cpp new file mode 100644 index 00000000000..bd44ab73791 --- /dev/null +++ b/src/server/game/Handlers/BattlePetHandler.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "WorldSession.h" +#include "BattlePetMgr.h" +#include "BattlePetPackets.h" +#include "Player.h" + +void WorldSession::HandleBattlePetRequestJournal(WorldPackets::BattlePet::BattlePetRequestJournal& /*battlePetRequestJournal*/) +{ + // TODO: Move this to BattlePetMgr::SendJournal() just to have all packets in one file + WorldPackets::BattlePet::BattlePetJournal battlePetJournal; + battlePetJournal.Trap = GetBattlePetMgr()->GetTrapLevel(); + + for (auto itr : GetBattlePetMgr()->GetLearnedPets()) + battlePetJournal.Pets.push_back(itr.PacketInfo); + + battlePetJournal.Slots = GetBattlePetMgr()->GetSlots(); + SendPacket(battlePetJournal.Write()); +} + +void WorldSession::HandleBattlePetSetBattleSlot(WorldPackets::BattlePet::BattlePetSetBattleSlot& battlePetSetBattleSlot) +{ + if (BattlePetMgr::BattlePet* pet = GetBattlePetMgr()->GetPet(battlePetSetBattleSlot.PetGuid)) + GetBattlePetMgr()->GetSlot(battlePetSetBattleSlot.Slot)->Pet = pet->PacketInfo; +} + +void WorldSession::HandleBattlePetModifyName(WorldPackets::BattlePet::BattlePetModifyName& battlePetModifyName) +{ + if (BattlePetMgr::BattlePet* pet = GetBattlePetMgr()->GetPet(battlePetModifyName.PetGuid)) + { + pet->PacketInfo.Name = battlePetModifyName.Name; + + if (pet->SaveInfo != BATTLE_PET_NEW) + pet->SaveInfo = BATTLE_PET_CHANGED; + } +} + +void WorldSession::HandleBattlePetDeletePet(WorldPackets::BattlePet::BattlePetDeletePet& battlePetDeletePet) +{ + GetBattlePetMgr()->RemovePet(battlePetDeletePet.PetGuid); +} + +void WorldSession::HandleBattlePetSetFlags(WorldPackets::BattlePet::BattlePetSetFlags& battlePetSetFlags) +{ + if (BattlePetMgr::BattlePet* pet = GetBattlePetMgr()->GetPet(battlePetSetFlags.PetGuid)) + { + if (battlePetSetFlags.ControlType == 2) // 2 - apply + pet->PacketInfo.Flags |= battlePetSetFlags.Flags; + else // 3 - remove + pet->PacketInfo.Flags &= ~battlePetSetFlags.Flags; + + if (pet->SaveInfo != BATTLE_PET_NEW) + pet->SaveInfo = BATTLE_PET_CHANGED; + } +} + +void WorldSession::HandleCageBattlePet(WorldPackets::BattlePet::CageBattlePet& cageBattlePet) +{ + GetBattlePetMgr()->CageBattlePet(cageBattlePet.PetGuid); +} + +void WorldSession::HandleBattlePetSummon(WorldPackets::BattlePet::BattlePetSummon& battlePetSummon) +{ + GetBattlePetMgr()->SummonPet(battlePetSummon.PetGuid); +} diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index dfbd48f0dec..d50e721fb1d 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -22,6 +22,7 @@ #include "AuthenticationPackets.h" #include "Battleground.h" #include "BattlenetServerManager.h" +#include "BattlePetPackets.h" #include "CalendarMgr.h" #include "CharacterPackets.h" #include "Chat.h" @@ -985,6 +986,10 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) hotfixInfo.Hotfixes = sDB2Manager.GetHotfixData(); SendPacket(hotfixInfo.Write()); + // TODO: Move this to BattlePetMgr::SendJournalLock() just to have all packets in one file + WorldPackets::BattlePet::BattlePetJournalLockAcquired lock; + SendPacket(lock.Write()); + pCurrChar->SendInitialPacketsBeforeAddToMap(); //Show cinematic at the first time that player login diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index 425f021ed22..ccd3dd77109 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -27,6 +27,7 @@ #include "DB2Stores.h" #include "NPCPackets.h" #include "ItemPackets.h" +#include "BattlePetMgr.h" void WorldSession::HandleSplitItemOpcode(WorldPackets::Item::SplitItem& splitItem) { @@ -1264,3 +1265,21 @@ bool WorldSession::CanUseBank(ObjectGuid bankerGUID) const return true; } + +void WorldSession::HandleUseCritterItem(WorldPackets::Item::UseCritterItem& useCritterItem) +{ + Item* item = _player->GetItemByGuid(useCritterItem.ItemGuid); + if (!item) + return; + + ItemToBattlePetSpeciesEntry const* itemToBattlePetSpecies = sItemToBattlePetSpeciesStore.LookupEntry(item->GetEntry()); + if (!itemToBattlePetSpecies) + return; + + BattlePetSpeciesEntry const* battlePetSpecies = sBattlePetSpeciesStore.LookupEntry(itemToBattlePetSpecies->BattlePetSpeciesID); + if (!battlePetSpecies) + return; + + GetBattlePetMgr()->AddPet(battlePetSpecies->ID, battlePetSpecies->CreatureID, BattlePetMgr::RollPetBreed(battlePetSpecies->ID), BattlePetMgr::GetDefaultPetQuality(battlePetSpecies->ID)); + _player->DestroyItem(item->GetBagSlot(), item->GetSlot(), true); +} diff --git a/src/server/game/Server/Packets/BattlePetPackets.cpp b/src/server/game/Server/Packets/BattlePetPackets.cpp new file mode 100644 index 00000000000..435b9f54338 --- /dev/null +++ b/src/server/game/Server/Packets/BattlePetPackets.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "BattlePetPackets.h" +#include "World.h" + +ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::BattlePet::BattlePetSlot const& slot) +{ + data << (slot.Pet.Guid.IsEmpty() ? ObjectGuid::Create<HighGuid::BattlePet>(0) : slot.Pet.Guid); + data << slot.CollarID; + data << slot.Index; + data.WriteBit(slot.Locked); + data.FlushBits(); + + return data; +} + +ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::BattlePet::BattlePet const& pet) +{ + data << pet.Guid; + data << pet.Species; + data << pet.CreatureID; + data << pet.CollarID; + data << pet.Breed; + data << pet.Level; + data << pet.Exp; + data << pet.Flags; + data << pet.Power; + data << pet.Health; + data << pet.MaxHealth; + data << pet.Speed; + data << pet.Quality; + data.WriteBits(pet.Name.size(), 7); + data.WriteBit(!pet.Owner.IsEmpty()); // HasOwnerInfo + data.WriteBit(pet.Name.empty()); // NoRename + data.FlushBits(); + + if (!pet.Owner.IsEmpty()) + { + data << pet.Owner; + data << GetVirtualRealmAddress(); // Virtual + data << GetVirtualRealmAddress(); // Native + } + + data.WriteString(pet.Name); + + return data; +} + +WorldPacket const* WorldPackets::BattlePet::BattlePetJournal::Write() +{ + _worldPacket << Trap; + _worldPacket << Slots.size(); + _worldPacket << Pets.size(); + + for (auto const& slot : Slots) + _worldPacket << slot; + + for (auto const& pet : Pets) + _worldPacket << pet; + + _worldPacket.WriteBit(1); // HasJournalLock + _worldPacket.FlushBits(); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::BattlePet::BattlePetUpdates::Write() +{ + _worldPacket << Pets.size(); + + for (auto const& pet : Pets) + _worldPacket << pet; + + _worldPacket.WriteBit(PetAdded); + _worldPacket.FlushBits(); + + return &_worldPacket; +} + + +WorldPacket const* WorldPackets::BattlePet::PetBattleSlotUpdates::Write() +{ + _worldPacket << Slots.size(); + + for (auto const& slot : Slots) + _worldPacket << slot; + + _worldPacket.WriteBit(NewSlot); + _worldPacket.WriteBit(AutoSlotted); + _worldPacket.FlushBits(); + + return &_worldPacket; +} + +void WorldPackets::BattlePet::BattlePetSetBattleSlot::Read() +{ + _worldPacket >> PetGuid; + _worldPacket >> Slot; +} + +void WorldPackets::BattlePet::BattlePetModifyName::Read() +{ + _worldPacket >> PetGuid; + uint32 nameLength = _worldPacket.ReadBits(7); + bool hasDeclinedNames = _worldPacket.ReadBit(); + Name = _worldPacket.ReadString(nameLength); + + if (hasDeclinedNames) + { + uint8 declinedNameLengths[MAX_DECLINED_NAME_CASES]; + + for (uint8 i = 0; i < 5; ++i) + declinedNameLengths[i] = _worldPacket.ReadBits(7); + + for (uint8 i = 0; i < 5; ++i) + Declined.name[i] = _worldPacket.ReadString(declinedNameLengths[i]); + } +} + +void WorldPackets::BattlePet::BattlePetDeletePet::Read() +{ + _worldPacket >> PetGuid; +} + +void WorldPackets::BattlePet::BattlePetSetFlags::Read() +{ + _worldPacket >> PetGuid; + _worldPacket >> Flags; + ControlType = _worldPacket.ReadBits(2); +} + +void WorldPackets::BattlePet::CageBattlePet::Read() +{ + _worldPacket >> PetGuid; +} + +WorldPacket const* WorldPackets::BattlePet::BattlePetDeleted::Write() +{ + _worldPacket << PetGuid; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::BattlePet::BattlePetError::Write() +{ + _worldPacket.WriteBits(Result, 4); + _worldPacket.FlushBits(); + _worldPacket << CreatureID; + + return &_worldPacket; +} + +void WorldPackets::BattlePet::BattlePetSummon::Read() +{ + _worldPacket >> PetGuid; +} diff --git a/src/server/game/Server/Packets/BattlePetPackets.h b/src/server/game/Server/Packets/BattlePetPackets.h new file mode 100644 index 00000000000..844cb855464 --- /dev/null +++ b/src/server/game/Server/Packets/BattlePetPackets.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BattlePetPackets_h__ +#define BattlePetPackets_h__ + +#include "Packet.h" +#include "ObjectGuid.h" + +namespace WorldPackets +{ + namespace BattlePet + { + struct BattlePet + { + ObjectGuid Guid; + uint32 Species = 0; + uint32 CreatureID = 0; + uint32 CollarID = 0; // what's this? + uint16 Breed = 0; + uint16 Level = 0; + uint16 Exp = 0; + uint16 Flags = 0; + uint32 Power = 0; + uint32 Health = 0; + uint32 MaxHealth = 0; + uint32 Speed = 0; + uint8 Quality = 0; + ObjectGuid Owner; // for non-account wide pets only? (Guild Page, Guild Herald) + std::string Name; + }; + + struct BattlePetSlot + { + BattlePet Pet; + uint32 CollarID = 0; // what's this? + uint8 Index = 0; + bool Locked = true; + }; + + class BattlePetJournal final : public ServerPacket + { + public: + BattlePetJournal() : ServerPacket(SMSG_BATTLE_PET_JOURNAL) { } + + WorldPacket const* Write() override; + + uint16 Trap = 0; + std::vector<BattlePetSlot> Slots; + std::vector<BattlePet> Pets; + }; + + class BattlePetJournalLockAcquired final : public ServerPacket + { + public: + BattlePetJournalLockAcquired() : ServerPacket(SMSG_BATTLE_PET_JOURNAL_LOCK_ACQUIRED, 0) { } + + WorldPacket const* Write() override { return &_worldPacket; } + }; + + class BattlePetRequestJournal final : public ClientPacket + { + public: + BattlePetRequestJournal(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_REQUEST_JOURNAL, std::move(packet)) { } + + void Read() override { } + }; + + class BattlePetUpdates final : public ServerPacket + { + public: + BattlePetUpdates() : ServerPacket(SMSG_BATTLE_PET_UPDATES) { } + + WorldPacket const* Write() override; + + std::vector<BattlePet> Pets; + bool PetAdded = false; + }; + + class PetBattleSlotUpdates final : public ServerPacket + { + public: + PetBattleSlotUpdates() : ServerPacket(SMSG_PET_BATTLE_SLOT_UPDATES) { } + + WorldPacket const* Write() override; + + std::vector<BattlePetSlot> Slots; + bool AutoSlotted = false; + bool NewSlot = false; + }; + + class BattlePetSetBattleSlot final : public ClientPacket + { + public: + BattlePetSetBattleSlot(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_SET_BATTLE_SLOT, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGuid; + uint8 Slot = 0; + }; + + class BattlePetModifyName final : public ClientPacket + { + public: + BattlePetModifyName(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_MODIFY_NAME, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGuid; + std::string Name; + DeclinedName Declined; + }; + + class BattlePetDeletePet final : public ClientPacket + { + public: + BattlePetDeletePet(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_DELETE_PET, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGuid; + }; + + class BattlePetSetFlags final : public ClientPacket + { + public: + BattlePetSetFlags(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_SET_FLAGS, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGuid; + uint32 Flags = 0; + uint8 ControlType = 0; + }; + + class CageBattlePet final : public ClientPacket + { + public: + CageBattlePet(WorldPacket&& packet) : ClientPacket(CMSG_CAGE_BATTLE_PET, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGuid; + }; + + class BattlePetDeleted final : public ServerPacket + { + public: + BattlePetDeleted() : ServerPacket(SMSG_BATTLE_PET_DELETED, 16) { } + + WorldPacket const* Write() override; + + ObjectGuid PetGuid; + }; + + class BattlePetError final : public ServerPacket + { + public: + BattlePetError() : ServerPacket(SMSG_BATTLE_PET_ERROR, 5) { } + + WorldPacket const* Write() override; + + uint8 Result = 0; + uint32 CreatureID = 0; + }; + + class BattlePetSummon final : public ClientPacket + { + public: + BattlePetSummon(WorldPacket&& packet) : ClientPacket(CMSG_BATTLE_PET_SUMMON, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGuid; + }; + } +} + +#endif // BattlePetPackets_h__ diff --git a/src/server/game/Server/Packets/ItemPackets.cpp b/src/server/game/Server/Packets/ItemPackets.cpp index 80bafbba104..b6b69b84eac 100644 --- a/src/server/game/Server/Packets/ItemPackets.cpp +++ b/src/server/game/Server/Packets/ItemPackets.cpp @@ -461,3 +461,8 @@ void WorldPackets::Item::TransmogrifyItems::Read() for (TransmogrifyItem& item : Items) _worldPacket >> item; } + +void WorldPackets::Item::UseCritterItem::Read() +{ + _worldPacket >> ItemGuid; +} diff --git a/src/server/game/Server/Packets/ItemPackets.h b/src/server/game/Server/Packets/ItemPackets.h index 943a681edbe..db7e9a01c0a 100644 --- a/src/server/game/Server/Packets/ItemPackets.h +++ b/src/server/game/Server/Packets/ItemPackets.h @@ -428,6 +428,16 @@ namespace WorldPackets Array<TransmogrifyItem, MAX_TRANSMOGRIFY_ITEMS> Items; }; + class UseCritterItem final : public ClientPacket + { + public: + UseCritterItem(WorldPacket&& packet) : ClientPacket(CMSG_USE_CRITTER_ITEM, std::move(packet)) { } + + void Read() override; + + ObjectGuid ItemGuid; + }; + ByteBuffer& operator>>(ByteBuffer& data, InvUpdate& invUpdate); } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 24a01eb9057..a214fd44556 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -22,6 +22,7 @@ #include "Packets/AuctionHousePackets.h" #include "Packets/BankPackets.h" #include "Packets/BattlegroundPackets.h" +#include "Packets/BattlePetPackets.h" #include "Packets/BlackMarketPackets.h" #include "Packets/CalendarPackets.h" #include "Packets/ChannelPackets.h" @@ -205,14 +206,14 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_BATTLE_PAY_GET_PURCHASE_LIST, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_BATTLE_PAY_START_PURCHASE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_BATTLE_PAY_START_VAS_PURCHASE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::BattlePet::BattlePetDeletePet, &WorldSession::HandleBattlePetDeletePet); DEFINE_HANDLER(CMSG_BATTLE_PET_DELETE_PET_CHEAT, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_BATTLE_PET_MODIFY_NAME, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_BATTLE_PET_MODIFY_NAME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::BattlePet::BattlePetModifyName, &WorldSession::HandleBattlePetModifyName); + DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::BattlePet::BattlePetRequestJournal, &WorldSession::HandleBattlePetRequestJournal); DEFINE_HANDLER(CMSG_BATTLE_PET_REQUEST_JOURNAL_LOCK, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_BATTLE_PET_SET_BATTLE_SLOT, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_BATTLE_PET_SET_FLAGS, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_BATTLE_PET_SUMMON, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_BATTLE_PET_SET_BATTLE_SLOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::BattlePet::BattlePetSetBattleSlot, &WorldSession::HandleBattlePetSetBattleSlot); + DEFINE_HANDLER(CMSG_BATTLE_PET_SET_FLAGS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::BattlePet::BattlePetSetFlags, &WorldSession::HandleBattlePetSetFlags); + DEFINE_HANDLER(CMSG_BATTLE_PET_SUMMON, STATUS_LOGGEDIN, PROCESS_INPLACE, WorldPackets::BattlePet::BattlePetSummon, &WorldSession::HandleBattlePetSummon); DEFINE_HANDLER(CMSG_BATTLE_PET_UPDATE_NOTIFY, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_BEGIN_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Trade::BeginTrade, &WorldSession::HandleBeginTradeOpcode); DEFINE_OPCODE_HANDLER_OLD(CMSG_BF_MGR_ENTRY_INVITE_RESPONSE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::HandleBfEntryInviteResponse ); @@ -231,7 +232,7 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_BUY_REAGENT_BANK, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_BUY_WOW_TOKEN_CONFIRM, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_BUY_WOW_TOKEN_START, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_CAGE_BATTLE_PET, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_CAGE_BATTLE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::BattlePet::CageBattlePet, &WorldSession::HandleCageBattlePet); DEFINE_HANDLER(CMSG_CALENDAR_ADD_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Calendar::CalendarAddEvent, &WorldSession::HandleCalendarAddEvent); DEFINE_OPCODE_HANDLER_OLD(CMSG_CALENDAR_COMPLAIN, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarComplain ); DEFINE_HANDLER(CMSG_CALENDAR_COPY_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Calendar::CalendarCopyEvent, &WorldSession::HandleCalendarCopyEvent); @@ -813,7 +814,7 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_UPGRADE_GARRISON, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_UPGRADE_ITEM, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_USED_FOLLOW, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_USE_CRITTER_ITEM, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_USE_CRITTER_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Item::UseCritterItem, &WorldSession::HandleUseCritterItem); DEFINE_HANDLER(CMSG_USE_EQUIPMENT_SET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::EquipmentSet::UseEquipmentSet, &WorldSession::HandleUseEquipmentSet); DEFINE_HANDLER(CMSG_USE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Spells::UseItem, &WorldSession::HandleUseItemOpcode); DEFINE_HANDLER(CMSG_USE_TOY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Toy::UseToy, &WorldSession::HandleUseToy); @@ -916,16 +917,16 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PAY_START_PURCHASE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PETS_HEALED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_CAGE_DATE_ERROR, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_DELETED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_ERROR, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_JOURNAL, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_JOURNAL_LOCK_ACQUIRED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_DELETED, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_ERROR, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_JOURNAL, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_JOURNAL_LOCK_ACQUIRED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_JOURNAL_LOCK_DENIED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_LICENSE_CHANGED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_RESTORED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_REVOKED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_TRAP_LEVEL, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_UPDATES, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_BATTLE_PET_UPDATES, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BF_MGR_DROP_TIMER_CANCELLED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BF_MGR_DROP_TIMER_STARTED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_BF_MGR_EJECTED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); @@ -1458,7 +1459,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_BATTLE_REPLACEMENTS_MADE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_BATTLE_REQUEST_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_BATTLE_ROUND_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_BATTLE_SLOT_UPDATES, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_BATTLE_SLOT_UPDATES, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_CAST_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_CLEAR_SPELLS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_PET_DISMISS_SOUND, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 37f2e2899fc..4e60c366a66 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -48,6 +48,7 @@ #include "ClientConfigPackets.h" #include "MiscPackets.h" #include "ChatPackets.h" +#include "BattlePetMgr.h" #include "PacketUtilities.h" #include <zlib.h> @@ -130,7 +131,8 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 battlenetAccoun _RBACData(NULL), expireTime(60000), // 1 min after socket loss, session is deleted forceExit(false), - m_currentBankerGUID() + m_currentBankerGUID(), + _battlePetMgr(Trinity::make_unique<BattlePetMgr>(this)) { memset(_tutorials, 0, sizeof(_tutorials)); @@ -1191,6 +1193,8 @@ public: enum { GLOBAL_ACCOUNT_TOYS = 0, + BATTLE_PETS, + BATTLE_PET_SLOTS, MAX_QUERIES }; @@ -1205,6 +1209,14 @@ public: stmt->setUInt32(0, battlenetAccountId); ok = SetPreparedQuery(GLOBAL_ACCOUNT_TOYS, stmt) && ok; + stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BATTLE_PETS); + stmt->setUInt32(0, battlenetAccountId); + ok = SetPreparedQuery(BATTLE_PETS, stmt) && ok; + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BATTLE_PET_SLOTS); + stmt->setUInt32(0, battlenetAccountId); + ok = SetPreparedQuery(BATTLE_PET_SLOTS, stmt) && ok; + return ok; } }; @@ -1252,6 +1264,9 @@ void WorldSession::InitializeSessionCallback(SQLQueryHolder* realmHolder, SQLQue SendClientCacheVersion(sWorld->getIntConfig(CONFIG_CLIENTCACHE_VERSION)); SendTutorialsData(); + _battlePetMgr->LoadFromDB(holder->GetPreparedResult(AccountInfoQueryHolder::BATTLE_PETS), + holder->GetPreparedResult(AccountInfoQueryHolder::BATTLE_PET_SLOTS)); + delete realmHolder; delete holder; } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index bc1faf778df..54c794ad0ca 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -33,6 +33,7 @@ #include "AccountMgr.h" #include <unordered_set> +class BattlePetMgr; class Channel; class Creature; class GameObject; @@ -117,6 +118,17 @@ namespace WorldPackets class ReportPvPPlayerAFK; } + namespace BattlePet + { + class BattlePetRequestJournal; + class BattlePetSetBattleSlot; + class BattlePetModifyName; + class BattlePetDeletePet; + class BattlePetSetFlags; + class BattlePetSummon; + class CageBattlePet; + } + namespace BlackMarket { class BlackMarketOpen; @@ -320,6 +332,7 @@ namespace WorldPackets class WrapItem; class CancelTempEnchantment; class TransmogrifyItems; + class UseCritterItem; } namespace Loot @@ -987,6 +1000,9 @@ class WorldSession uint32 GetRecruiterId() const { return recruiterId; } bool IsARecruiter() const { return isRecruiter; } + // Battle Pets + BattlePetMgr* GetBattlePetMgr() const { return _battlePetMgr.get(); } + public: // opcodes handlers void Handle_NULL(WorldPackets::Null& null); // not used @@ -1306,6 +1322,7 @@ class WorldSession void HandleSwapItem(WorldPackets::Item::SwapItem& swapItem); void HandleBuybackItem(WorldPackets::Item::BuyBackItem& packet); void HandleWrapItem(WorldPackets::Item::WrapItem& packet); + void HandleUseCritterItem(WorldPackets::Item::UseCritterItem& packet); void HandleAttackSwingOpcode(WorldPackets::Combat::AttackSwing& packet); void HandleAttackStopOpcode(WorldPackets::Combat::AttackStop& packet); @@ -1581,6 +1598,15 @@ class WorldSession void HandleGarrisonRequestBlueprintAndSpecializationData(WorldPackets::Garrison::GarrisonRequestBlueprintAndSpecializationData& garrisonRequestBlueprintAndSpecializationData); void HandleGarrisonGetBuildingLandmarks(WorldPackets::Garrison::GarrisonGetBuildingLandmarks& garrisonGetBuildingLandmarks); + // Battle Pets + void HandleBattlePetRequestJournal(WorldPackets::BattlePet::BattlePetRequestJournal& battlePetRequestJournal); + void HandleBattlePetSetBattleSlot(WorldPackets::BattlePet::BattlePetSetBattleSlot& battlePetSetBattleSlot); + void HandleBattlePetModifyName(WorldPackets::BattlePet::BattlePetModifyName& battlePetModifyName); + void HandleBattlePetDeletePet(WorldPackets::BattlePet::BattlePetDeletePet& battlePetDeletePet); + void HandleBattlePetSetFlags(WorldPackets::BattlePet::BattlePetSetFlags& battlePetSetFlags); + void HandleBattlePetSummon(WorldPackets::BattlePet::BattlePetSummon& battlePetSummon); + void HandleCageBattlePet(WorldPackets::BattlePet::CageBattlePet& cageBattlePet); + private: void InitializeQueryCallbackParameters(); void ProcessQueryCallbacks(); @@ -1694,6 +1720,8 @@ class WorldSession ObjectGuid m_currentBankerGUID; ToyBoxContainer _toys; + std::unique_ptr<BattlePetMgr> _battlePetMgr; + WorldSession(WorldSession const& right) = delete; WorldSession& operator=(WorldSession const& right) = delete; }; diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index c894c53aaf2..d5ed0a3c0a8 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -434,6 +434,9 @@ class Spell void EffectCreateGarrison(SpellEffIndex effIndex); void EffectAddGarrisonFollower(SpellEffIndex effIndex); void EffectActivateGarrisonBuilding(SpellEffIndex effIndex); + void EffectHealBattlePetPct(SpellEffIndex effIndex); + void EffectEnableBattlePets(SpellEffIndex effIndex); + void EffectUncageBattlePet(SpellEffIndex effIndex); typedef std::set<Aura*> UsedSpellMods; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index d10f76b76d3..1cb344da5aa 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -56,6 +56,7 @@ #include "Guild.h" #include "ReputationMgr.h" #include "AreaTrigger.h" +#include "BattlePetMgr.h" #include "Garrison.h" #include "CombatLogPackets.h" #include "DuelPackets.h" @@ -256,7 +257,7 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS]= &Spell::EffectNULL, //189 SPELL_EFFECT_LOOT &Spell::EffectNULL, //190 SPELL_EFFECT_190 &Spell::EffectNULL, //191 SPELL_EFFECT_TELEPORT_TO_DIGSITE - &Spell::EffectNULL, //192 SPELL_EFFECT_UNCAGE_BATTLEPET + &Spell::EffectUncageBattlePet, //192 SPELL_EFFECT_UNCAGE_BATTLEPET &Spell::EffectNULL, //193 SPELL_EFFECT_START_PET_BATTLE &Spell::EffectNULL, //194 SPELL_EFFECT_194 &Spell::EffectNULL, //195 SPELL_EFFECT_195 @@ -264,8 +265,8 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS]= &Spell::EffectNULL, //197 SPELL_EFFECT_197 &Spell::EffectNULL, //198 SPELL_EFFECT_198 &Spell::EffectNULL, //199 SPELL_EFFECT_199 - &Spell::EffectNULL, //200 SPELL_EFFECT_HEAL_BATTLEPET_PCT - &Spell::EffectNULL, //201 SPELL_EFFECT_ENABLE_BATTLE_PETS + &Spell::EffectHealBattlePetPct, //200 SPELL_EFFECT_HEAL_BATTLEPET_PCT + &Spell::EffectEnableBattlePets, //201 SPELL_EFFECT_ENABLE_BATTLE_PETS &Spell::EffectNULL, //202 SPELL_EFFECT_202 &Spell::EffectNULL, //203 SPELL_EFFECT_203 &Spell::EffectNULL, //204 SPELL_EFFECT_CHANGE_BATTLEPET_QUALITY @@ -2259,6 +2260,22 @@ void Spell::EffectLearnSpell(SpellEffIndex effIndex) uint32 spellToLearn = (m_spellInfo->Id == 483 || m_spellInfo->Id == 55884) ? damage : effectInfo->TriggerSpell; player->LearnSpell(spellToLearn, false); + if (m_spellInfo->Id == 55884) + { + if (BattlePetMgr* battlePetMgr = player->GetSession()->GetBattlePetMgr()) + { + for (auto entry : sBattlePetSpeciesStore) + { + if (entry->SummonSpellID == spellToLearn) + { + battlePetMgr->AddPet(entry->ID, entry->CreatureID, BattlePetMgr::RollPetBreed(entry->ID), BattlePetMgr::GetDefaultPetQuality(entry->ID)); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_BATTLE_PET_COUNT); + break; + } + } + } + } + TC_LOG_DEBUG("spells", "Spell: %s has learned spell %u from %s", player->GetGUID().ToString().c_str(), spellToLearn, m_caster->GetGUID().ToString().c_str()); } @@ -5861,3 +5878,85 @@ void Spell::EffectActivateGarrisonBuilding(SpellEffIndex effIndex) if (Garrison* garrison = unitTarget->ToPlayer()->GetGarrison()) garrison->ActivateBuilding(GetEffect(effIndex)->MiscValue); } + +void Spell::EffectHealBattlePetPct(SpellEffIndex effIndex) +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (BattlePetMgr* battlePetMgr = unitTarget->ToPlayer()->GetSession()->GetBattlePetMgr()) + battlePetMgr->HealBattlePetsPct(GetEffect(effIndex)->BasePoints); +} + +void Spell::EffectEnableBattlePets(SpellEffIndex /*effIndex*/) +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* plr = unitTarget->ToPlayer(); + plr->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_PET_BATTLES_UNLOCKED); + plr->GetSession()->GetBattlePetMgr()->UnlockSlot(0); +} + +void Spell::EffectUncageBattlePet(SpellEffIndex /*effIndex*/) +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) + return; + + if (!m_CastItem || !m_caster || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* plr = m_caster->ToPlayer(); + + // are we allowed to learn battle pets without it? + /*if (plr->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_PET_BATTLES_UNLOCKED)) + return; // send some error*/ + + uint32 speciesId = m_CastItem->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID); + uint16 breed = m_CastItem->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) & 0xFFFFFF; + uint8 quality = (m_CastItem->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF; + uint16 level = m_CastItem->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL); + uint32 creatureId = m_CastItem->GetModifier(ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID); + + BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(speciesId); + if (!speciesEntry) + return; + + BattlePetMgr* battlePetMgr = plr->GetSession()->GetBattlePetMgr(); + if (!battlePetMgr) + return; + + uint16 maxLearnedLevel = 0; + + for (auto pet : battlePetMgr->GetLearnedPets()) + maxLearnedLevel = std::max(pet.PacketInfo.Level, maxLearnedLevel); + + // TODO: This means if you put your highest lvl pet into cage, you won't be able to uncage it again which is probably wrong. + // We will need to store maxLearnedLevel somewhere to avoid this behaviour. + if (maxLearnedLevel < level) + { + battlePetMgr->SendError(BATTLEPETRESULT_TOO_HIGH_LEVEL_TO_UNCAGE, creatureId); // or speciesEntry.CreatureID + SendCastResult(SPELL_FAILED_CANT_ADD_BATTLE_PET); + return; + } + + if (battlePetMgr->GetPetCount(speciesId) >= MAX_BATTLE_PETS_PER_SPECIES) + { + battlePetMgr->SendError(BATTLEPETRESULT_CANT_HAVE_MORE_PETS_OF_THAT_TYPE, creatureId); // or speciesEntry.CreatureID + SendCastResult(SPELL_FAILED_CANT_ADD_BATTLE_PET); + return; + } + + if (!plr->HasSpell(speciesEntry->SummonSpellID)) + plr->LearnSpell(speciesEntry->SummonSpellID, false); + + battlePetMgr->AddPet(speciesId, creatureId, breed, quality, level); + plr->DestroyItem(m_CastItem->GetBagSlot(), m_CastItem->GetSlot(), true); + m_CastItem = nullptr; +} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index af1d7134663..d6cceac46fb 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -27,6 +27,7 @@ #include "AuctionHouseMgr.h" #include "BattlefieldMgr.h" #include "BattlegroundMgr.h" +#include "BattlePetMgr.h" #include "CalendarMgr.h" #include "Channel.h" #include "CharacterDatabaseCleaner.h" @@ -2026,6 +2027,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading realm names..."); sObjectMgr->LoadRealmNames(); + TC_LOG_INFO("server.loading", "Loading battle pets info..."); + BattlePetMgr::Initialize(); + uint32 startupDuration = GetMSTimeDiffToNow(startupBegin); TC_LOG_INFO("server.worldserver", "World initialized in %u minutes %u seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000)); |