diff --git a/common/eq_constants.h b/common/eq_constants.h index 7e2142681..5f8044198 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -716,6 +716,16 @@ namespace Language { constexpr uint8 MaxValue = 100; } +namespace PetInfoType { + constexpr int Current = 0; + constexpr int Suspended = 1; +} + +namespace BuffEffectType { + constexpr uint8 None = 0; + constexpr uint8 Buff = 2; + constexpr uint8 InverseBuff = 4; +} typedef enum { FilterNone = 0, diff --git a/common/repositories/base/base_character_pet_buffs_repository.h b/common/repositories/base/base_character_pet_buffs_repository.h index 87fc3a286..6ed52f816 100644 --- a/common/repositories/base/base_character_pet_buffs_repository.h +++ b/common/repositories/base/base_character_pet_buffs_repository.h @@ -6,7 +6,7 @@ * Any modifications to base repositories are to be made by the generator only * * @generator ./utils/scripts/generators/repository-generator.pl - * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + * @docs https://docs.eqemu.io/developer/repositories */ #ifndef EQEMU_BASE_CHARACTER_PET_BUFFS_REPOSITORY_H @@ -16,6 +16,7 @@ #include "../../strings.h" #include + class BaseCharacterPetBuffsRepository { public: struct CharacterPetBuffs { @@ -144,8 +145,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), character_pet_buffs_id ) ); @@ -418,6 +420,82 @@ public: return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); } + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterPetBuffs &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.pet)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.caster_level)); + v.push_back("'" + Strings::Escape(e.castername) + "'"); + v.push_back(std::to_string(e.ticsremaining)); + v.push_back(std::to_string(e.counters)); + v.push_back(std::to_string(e.numhits)); + v.push_back(std::to_string(e.rune)); + v.push_back(std::to_string(e.instrument_mod)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.pet)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.caster_level)); + v.push_back("'" + Strings::Escape(e.castername) + "'"); + v.push_back(std::to_string(e.ticsremaining)); + v.push_back(std::to_string(e.counters)); + v.push_back(std::to_string(e.numhits)); + v.push_back(std::to_string(e.rune)); + v.push_back(std::to_string(e.instrument_mod)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_BASE_CHARACTER_PET_BUFFS_REPOSITORY_H diff --git a/common/repositories/base/base_character_pet_info_repository.h b/common/repositories/base/base_character_pet_info_repository.h index dfb5c1ea2..8d94d2292 100644 --- a/common/repositories/base/base_character_pet_info_repository.h +++ b/common/repositories/base/base_character_pet_info_repository.h @@ -6,7 +6,7 @@ * Any modifications to base repositories are to be made by the generator only * * @generator ./utils/scripts/generators/repository-generator.pl - * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + * @docs https://docs.eqemu.io/developer/repositories */ #ifndef EQEMU_BASE_CHARACTER_PET_INFO_REPOSITORY_H @@ -16,6 +16,7 @@ #include "../../strings.h" #include + class BaseCharacterPetInfoRepository { public: struct CharacterPetInfo { @@ -136,8 +137,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), character_pet_info_id ) ); @@ -398,6 +400,78 @@ public: return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); } + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterPetInfo &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.pet)); + v.push_back("'" + Strings::Escape(e.petname) + "'"); + v.push_back(std::to_string(e.petpower)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.hp)); + v.push_back(std::to_string(e.mana)); + v.push_back(std::to_string(e.size)); + v.push_back(std::to_string(e.taunting)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.pet)); + v.push_back("'" + Strings::Escape(e.petname) + "'"); + v.push_back(std::to_string(e.petpower)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.hp)); + v.push_back(std::to_string(e.mana)); + v.push_back(std::to_string(e.size)); + v.push_back(std::to_string(e.taunting)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_BASE_CHARACTER_PET_INFO_REPOSITORY_H diff --git a/common/repositories/base/base_character_pet_inventory_repository.h b/common/repositories/base/base_character_pet_inventory_repository.h index cb364d97c..4136711b9 100644 --- a/common/repositories/base/base_character_pet_inventory_repository.h +++ b/common/repositories/base/base_character_pet_inventory_repository.h @@ -6,7 +6,7 @@ * Any modifications to base repositories are to be made by the generator only * * @generator ./utils/scripts/generators/repository-generator.pl - * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + * @docs https://docs.eqemu.io/developer/repositories */ #ifndef EQEMU_BASE_CHARACTER_PET_INVENTORY_REPOSITORY_H @@ -16,6 +16,7 @@ #include "../../strings.h" #include + class BaseCharacterPetInventoryRepository { public: struct CharacterPetInventory { @@ -116,8 +117,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), character_pet_inventory_id ) ); @@ -348,6 +350,68 @@ public: return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); } + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterPetInventory &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.pet)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.pet)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_BASE_CHARACTER_PET_INVENTORY_REPOSITORY_H diff --git a/zone/client.h b/zone/client.h index 3af24cc33..b3f95db07 100644 --- a/zone/client.h +++ b/zone/client.h @@ -382,7 +382,7 @@ public: inline ExtendedProfile_Struct& GetEPP() { return m_epp; } inline EQ::InventoryProfile& GetInv() { return m_inv; } inline const EQ::InventoryProfile& GetInv() const { return m_inv; } - inline PetInfo* GetPetInfo(uint16 pet) { return (pet==1)?&m_suspendedminion:&m_petinfo; } + inline PetInfo* GetPetInfo(int pet_info_type) { return pet_info_type == PetInfoType::Suspended ? &m_suspendedminion : &m_petinfo; } inline InspectMessage_Struct& GetInspectMessage() { return m_inspect_message; } inline const InspectMessage_Struct& GetInspectMessage() const { return m_inspect_message; } void ReloadExpansionProfileSetting(); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 9d5d96019..0f44a02fa 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -3121,124 +3121,136 @@ void ZoneDatabase::LoadAuras(Client *c) void ZoneDatabase::SavePetInfo(Client *client) { - PetInfo *petinfo = nullptr; + PetInfo* p = nullptr; - // Pet Info - std::vector pet_infos = {}; - CharacterPetInfoRepository::CharacterPetInfo pet_info = {}; + std::vector pet_infos; + auto pet_info = CharacterPetInfoRepository::NewEntity(); - // Pet buffs - std::vector pet_buffs = {}; - CharacterPetBuffsRepository::CharacterPetBuffs pet_buff = {}; + std::vector pet_buffs; + auto pet_buff = CharacterPetBuffsRepository::NewEntity(); - // Pet inventory - std::vector inventory = {}; - CharacterPetInventoryRepository::CharacterPetInventory item = {}; + std::vector inventory; + auto item = CharacterPetInventoryRepository::NewEntity(); - // Loop through pet types - for (int pet = 0; pet < 2; pet++) { - petinfo = client->GetPetInfo(pet); - if (!petinfo) { + for (int pet_info_type = PetInfoType::Current; pet_info_type <= PetInfoType::Suspended; pet_info_type++) { + p = client->GetPetInfo(pet_info_type); + if (!p) { continue; } - // build pet info into struct pet_info.char_id = client->CharacterID(); - pet_info.pet = pet; - pet_info.petname = petinfo->Name; - pet_info.petpower = petinfo->petpower; - pet_info.spell_id = petinfo->SpellID; - pet_info.hp = petinfo->HP; - pet_info.mana = petinfo->Mana; - pet_info.size = petinfo->size; - pet_info.taunting = (petinfo->taunting) ? 1 : 0; + pet_info.pet = pet_info_type; + pet_info.petname = p->Name; + pet_info.petpower = p->petpower; + pet_info.spell_id = p->SpellID; + pet_info.hp = p->HP; + pet_info.mana = p->Mana; + pet_info.size = p->size; + pet_info.taunting = p->taunting ? 1 : 0; - // add pet info to vector pet_infos.push_back(pet_info); - // build pet buffs into struct - int pet_buff_count = 0; - // Guard against setting the maximum pet slots above the client allowed maximum. - int max_slots = RuleI(Spells, MaxTotalSlotsPET) > PET_BUFF_COUNT ? PET_BUFF_COUNT : RuleI(Spells, MaxTotalSlotsPET); + uint32 pet_buff_count = 0; - // count pet buffs - for (int index = 0; index < max_slots; index++) { - if (!IsValidSpell(petinfo->Buffs[index].spellid)) { + const uint32 max_slots = ( + RuleI(Spells, MaxTotalSlotsPET) > PET_BUFF_COUNT ? + PET_BUFF_COUNT : + RuleI(Spells, MaxTotalSlotsPET) + ); + + for (int slot_id = 0; slot_id < max_slots; slot_id++) { + if (!IsValidSpell(p->Buffs[slot_id].spellid)) { continue; } + pet_buff_count++; } - // reserve space for pet buffs pet_buffs.reserve(pet_buff_count); - // loop through pet buffs - for (int index = 0; index < max_slots; index++) { - if (!IsValidSpell(petinfo->Buffs[index].spellid)) { + for (int slot_id = 0; slot_id < max_slots; slot_id++) { + if (!IsValidSpell(p->Buffs[slot_id].spellid)) { continue; } pet_buff.char_id = client->CharacterID(); - pet_buff.pet = pet; - pet_buff.slot = index; - pet_buff.spell_id = petinfo->Buffs[index].spellid; - pet_buff.caster_level = petinfo->Buffs[index].level; - pet_buff.ticsremaining = petinfo->Buffs[index].duration; - pet_buff.counters = petinfo->Buffs[index].counters; - pet_buff.instrument_mod = petinfo->Buffs[index].bard_modifier; + pet_buff.pet = pet_info_type; + pet_buff.slot = slot_id; + pet_buff.spell_id = p->Buffs[slot_id].spellid; + pet_buff.caster_level = p->Buffs[slot_id].level; + pet_buff.ticsremaining = p->Buffs[slot_id].duration; + pet_buff.counters = p->Buffs[slot_id].counters; + pet_buff.instrument_mod = p->Buffs[slot_id].bard_modifier; - // add pet buffs to vector pet_buffs.push_back(pet_buff); } - // build pet inventory into struct - int pet_inventory_count = 0; - for (int index = EQ::invslot::EQUIPMENT_BEGIN; index <= EQ::invslot::EQUIPMENT_END; index++) { - if (!petinfo->Items[index]) { + uint32 pet_inventory_count = 0; + + for ( + int slot_id = EQ::invslot::EQUIPMENT_BEGIN; + slot_id <= EQ::invslot::EQUIPMENT_END; + slot_id++ + ) { + if (!p->Items[slot_id]) { continue; } + pet_inventory_count++; } - // reserve space for pet inventory inventory.reserve(pet_inventory_count); - // loop through pet inventory - for (int index = EQ::invslot::EQUIPMENT_BEGIN; index <= EQ::invslot::EQUIPMENT_END; index++) { - if (!petinfo->Items[index]) { + for ( + int slot_id = EQ::invslot::EQUIPMENT_BEGIN; + slot_id <= EQ::invslot::EQUIPMENT_END; + slot_id++ + ) { + if (!p->Items[slot_id]) { continue; } item.char_id = client->CharacterID(); - item.pet = pet; - item.slot = index; - item.item_id = petinfo->Items[index]; + item.pet = pet_info_type; + item.slot = slot_id; + item.item_id = p->Items[slot_id]; - // add pet inventory to vector inventory.push_back(item); } } - // Delete existing pet info - CharacterPetInfoRepository::DeleteWhere(database, fmt::format("char_id = {}", client->CharacterID())); + CharacterPetInfoRepository::DeleteWhere( + database, + fmt::format( + "`char_id` = {}", + client->CharacterID() + ) + ); - // insert pet info into database if (!pet_infos.empty()) { CharacterPetInfoRepository::InsertMany(database, pet_infos); } - // Delete existing pet buffs - CharacterPetBuffsRepository::DeleteWhere(database, fmt::format("char_id = {}", client->CharacterID())); + CharacterPetBuffsRepository::DeleteWhere( + database, + fmt::format( + "`char_id` = {}", + client->CharacterID() + ) + ); - // insert pet buffs into database if (!pet_buffs.empty()) { CharacterPetBuffsRepository::InsertMany(database, pet_buffs); } - // Delete existing pet inventory - CharacterPetInventoryRepository::DeleteWhere(database, fmt::format("char_id = {}", client->CharacterID())); + CharacterPetInventoryRepository::DeleteWhere( + database, + fmt::format( + "`char_id` = {}", + client->CharacterID() + ) + ); - // insert pet inventory into database if (!inventory.empty()) { CharacterPetInventoryRepository::InsertMany(database, inventory); } @@ -3275,109 +3287,106 @@ void ZoneDatabase::DeleteItemRecast(uint32 character_id, uint32 recast_type) void ZoneDatabase::LoadPetInfo(Client *client) { - // Load current pet and suspended pet - PetInfo *petinfo = client->GetPetInfo(0); - PetInfo *suspended = client->GetPetInfo(1); + auto pet_info = client->GetPetInfo(PetInfoType::Current); + auto suspended_pet_info = client->GetPetInfo(PetInfoType::Suspended); - memset(petinfo, 0, sizeof(PetInfo)); - memset(suspended, 0, sizeof(PetInfo)); + memset(pet_info, 0, sizeof(PetInfo)); + memset(suspended_pet_info, 0, sizeof(PetInfo)); - std::string query = StringFormat("SELECT `pet`, `petname`, `petpower`, `spell_id`, " - "`hp`, `mana`, `size` , `taunting` FROM `character_pet_info` " - "WHERE `char_id` = %u", - client->CharacterID()); - auto results = database.QueryDatabase(query); - if (!results.Success()) { + const auto& info = CharacterPetInfoRepository::GetWhere( + database, + fmt::format( + "`char_id` = {}", + client->CharacterID() + ) + ); + + if (info.empty()) { return; } - PetInfo *pi; - for (auto& row = results.begin(); row != results.end(); ++row) { - uint16 pet = Strings::ToInt(row[0]); + PetInfo* p; - if (pet == 0) - pi = petinfo; - else if (pet == 1) - pi = suspended; - else + for (const auto& e : info) { + if (e.pet == PetInfoType::Current) { + p = pet_info; + } else if (e.pet == PetInfoType::Suspended) { + p = suspended_pet_info; + } else { continue; + } - strncpy(pi->Name, row[1], 64); - pi->petpower = Strings::ToInt(row[2]); - pi->SpellID = Strings::ToInt(row[3]); - pi->HP = Strings::ToUnsignedInt(row[4]); - pi->Mana = Strings::ToUnsignedInt(row[5]); - pi->size = Strings::ToFloat(row[6]); - pi->taunting = (bool) Strings::ToInt(row[7]); + strn0cpy(p->Name, e.petname.c_str(), sizeof(c->Name)); + + p->petpower = e.petpower; + p->SpellID = e.spell_id; + p->HP = e.hp; + p->Mana = e.mana; + p->size = e.size; + p->taunting = e.taunting; } - query = StringFormat("SELECT `pet`, `slot`, `spell_id`, `caster_level`, `castername`, " - "`ticsremaining`, `counters`, `instrument_mod` FROM `character_pet_buffs` " - "WHERE `char_id` = %u", - client->CharacterID()); - results = QueryDatabase(query); - if (!results.Success()) { - return; + const auto& buffs = CharacterPetBuffsRepository::GetWhere( + database, + fmt::format( + "`char_id` = {}", + client->CharacterID() + ) + ); + + if (!buffs.empty()) { + for (const auto& e : buffs) { + if (e.pet == PetInfoType::Current) { + p = pet_info; + } else if (e.pet == PetInfoType::Suspended) { + p = suspended_pet_info; + } else { + continue; + } + + if (e.slot >= RuleI(Spells, MaxTotalSlotsPET)) { + continue; + } + + if (!IsValidSpell(e.spell_id)) { + continue; + } + + p->Buffs[e.slot].spellid = e.spell_id; + p->Buffs[e.slot].level = e.caster_level; + p->Buffs[e.slot].player_id = 0; + p->Buffs[e.slot].effect_type = BuffEffectType::Buff; + p->Buffs[e.slot].duration = e.ticsremaining; + p->Buffs[e.slot].counters = e.counters; + p->Buffs[e.slot].bard_modifier = e.instrument_mod; + } } - for (auto& row = results.begin(); row != results.end(); ++row) { - uint16 pet = Strings::ToInt(row[0]); - if (pet == 0) - pi = petinfo; - else if (pet == 1) - pi = suspended; - else - continue; + const auto& inventory = CharacterPetInventoryRepository::GetWhere( + database, + fmt::format( + "`char_id` = {}", + client->CharacterID() + ) + ); - uint32 slot_id = Strings::ToUnsignedInt(row[1]); - if (slot_id >= RuleI(Spells, MaxTotalSlotsPET)) - continue; + if (!inventory.empty()) { + for (const auto& e : inventory) { + if (e.pet == PetInfoType::Current) { + p = pet_info; + } else if (e.pet == PetInfoType::Suspended) { + p = suspended_pet_info; + } else { + continue; + } - uint32 spell_id = Strings::ToUnsignedInt(row[2]); - if (!IsValidSpell(spell_id)) - continue; + if (!EQ::ValueWithin(e.slot, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) { + continue; + } - uint32 caster_level = Strings::ToInt(row[3]); - int caster_id = 0; - // The castername field is currently unused - int32 ticsremaining = Strings::ToInt(row[5]); - uint32 counters = Strings::ToUnsignedInt(row[6]); - uint8 bard_mod = Strings::ToUnsignedInt(row[7]); - - pi->Buffs[slot_id].spellid = spell_id; - pi->Buffs[slot_id].level = caster_level; - pi->Buffs[slot_id].player_id = caster_id; - pi->Buffs[slot_id].effect_type = 2; // Always 2 in buffs struct for real buffs - - pi->Buffs[slot_id].duration = ticsremaining; - pi->Buffs[slot_id].counters = counters; - pi->Buffs[slot_id].bard_modifier = bard_mod; - } - - query = StringFormat("SELECT `pet`, `slot`, `item_id` " - "FROM `character_pet_inventory` " - "WHERE `char_id`=%u", - client->CharacterID()); - results = database.QueryDatabase(query); - if (!results.Success()) { - return; - } - - for (auto& row = results.begin(); row != results.end(); ++row) { - uint16 pet = Strings::ToInt(row[0]); - if (pet == 0) - pi = petinfo; - else if (pet == 1) - pi = suspended; - else - continue; - - int slot = Strings::ToInt(row[1]); - if (slot < EQ::invslot::EQUIPMENT_BEGIN || slot > EQ::invslot::EQUIPMENT_END) - continue; - - pi->Items[slot] = Strings::ToUnsignedInt(row[2]); + p->Items[e.slot] = e.item_id; + } } }