diff --git a/common/repositories/base/base_character_spells_repository.h b/common/repositories/base/base_character_spells_repository.h index 11514f84b..c02b1290a 100644 --- a/common/repositories/base/base_character_spells_repository.h +++ b/common/repositories/base/base_character_spells_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_SPELLS_REPOSITORY_H @@ -16,6 +16,7 @@ #include "../../strings.h" #include + class BaseCharacterSpellsRepository { public: struct CharacterSpells { @@ -112,8 +113,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), character_spells_id ) ); @@ -337,6 +339,66 @@ 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 CharacterSpells &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.spell_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.id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.spell_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_SPELLS_REPOSITORY_H diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index b2de38eeb..d4daad7f6 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5771,7 +5771,7 @@ void Client::Handle_OP_DeleteSpell(const EQApplicationPacket *app) if (m_pp.spell_book[dss->spell_slot] != SPELLBOOK_UNKNOWN) { m_pp.spell_book[dss->spell_slot] = SPELLBOOK_UNKNOWN; - database.DeleteCharacterSpell(CharacterID(), m_pp.spell_book[dss->spell_slot], dss->spell_slot); + database.DeleteCharacterSpell(CharacterID(), dss->spell_slot); dss->success = 1; } else @@ -14550,10 +14550,10 @@ void Client::Handle_OP_SwapSpell(const EQApplicationPacket *app) /* Save Spell Swaps */ if (!database.SaveCharacterSpell(CharacterID(), m_pp.spell_book[swapspell->from_slot], swapspell->from_slot)) { - database.DeleteCharacterSpell(CharacterID(), m_pp.spell_book[swapspell->from_slot], swapspell->from_slot); + database.DeleteCharacterSpell(CharacterID(), swapspell->from_slot); } if (!database.SaveCharacterSpell(CharacterID(), swapspelltemp, swapspell->to_slot)) { - database.DeleteCharacterSpell(CharacterID(), swapspelltemp, swapspell->to_slot); + database.DeleteCharacterSpell(CharacterID(), swapspell->to_slot); } QueuePacket(app); diff --git a/zone/spells.cpp b/zone/spells.cpp index 1d70dbe72..04d1a9397 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5707,7 +5707,7 @@ void Client::UnscribeSpell(int slot, bool update_client, bool defer_save) LogSpells("Spell [{}] erased from spell book slot [{}]", m_pp.spell_book[slot], slot); if (!defer_save) { - database.DeleteCharacterSpell(CharacterID(), m_pp.spell_book[slot], slot); + database.DeleteCharacterSpell(CharacterID(), slot); } if (update_client && slot < EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize) { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index bc5f8e607..fa2d0eaee 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -27,6 +27,7 @@ #include "../common/repositories/character_leadership_abilities_repository.h" #include "../common/repositories/character_material_repository.h" #include "../common/repositories/character_memmed_spells_repository.h" +#include "../common/repositories/character_spells_repository.h" #include #include @@ -682,35 +683,33 @@ bool ZoneDatabase::LoadCharacterMemmedSpells(uint32 character_id, PlayerProfile_ return true; } -bool ZoneDatabase::LoadCharacterSpellBook(uint32 character_id, PlayerProfile_Struct* pp){ - std::string query = StringFormat( - "SELECT " - "slot_id, " - "`spell_id` " - "FROM " - "`character_spells` " - "WHERE `id` = %u ORDER BY `slot_id`", character_id); - auto results = database.QueryDatabase(query); +bool ZoneDatabase::LoadCharacterSpellBook(uint32 character_id, PlayerProfile_Struct* pp) +{ + const auto& l = CharacterSpellsRepository::GetWhere( + database, + fmt::format( + "`id` = {} ORDER BY `slot_id`", + character_id + ) + ); - /* Initialize Spells */ - - memset(pp->spell_book, 0xFF, (sizeof(uint32) * EQ::spells::SPELLBOOK_SIZE)); + memset(pp->spell_book, UINT8_MAX, (sizeof(uint32) * EQ::spells::SPELLBOOK_SIZE)); // We have the ability to block loaded spells by max id on a per-client basis.. // but, we do not have to ability to keep players from using older clients after // they have scribed spells on a newer one that exceeds the older one's limit. // Load them all so that server actions are valid..but, nix them in translators. - for (auto& row = results.begin(); row != results.end(); ++row) { - int idx = Strings::ToInt(row[0]); - int id = Strings::ToInt(row[1]); - - if (idx < 0 || idx >= EQ::spells::SPELLBOOK_SIZE) - continue; - if (id < 3 || id > SPDAT_RECORDS) // 3 ("Summon Corpse") is the first scribable spell in spells_us.txt + for (const auto& e : l) { + if (!EQ::ValueWithin(e.slot_id, 0, EQ::spells::SPELLBOOK_SIZE)) { continue; + } - pp->spell_book[idx] = id; + if (!IsValidSpell(e.spell_id)) { + continue; + } + + pp->spell_book[e.slot_id] = e.spell_id; } return true; @@ -1282,17 +1281,32 @@ bool ZoneDatabase::SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell ); } -bool ZoneDatabase::SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ - if (spell_id > SPDAT_RECORDS){ return false; } - std::string query = StringFormat("REPLACE INTO `character_spells` (id, slot_id, spell_id) VALUES (%u, %u, %u)", character_id, slot_id, spell_id); - QueryDatabase(query); - return true; +bool ZoneDatabase::SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + return CharacterSpellsRepository::ReplaceOne( + *this, + CharacterSpellsRepository::CharacterSpells{ + .id = character_id, + .slot_id = static_cast(slot_id), + .spell_id = static_cast(spell_id) + } + ); } -bool ZoneDatabase::DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ - std::string query = StringFormat("DELETE FROM `character_spells` WHERE `slot_id` = %u AND `id` = %u", slot_id, character_id); - QueryDatabase(query); - return true; +bool ZoneDatabase::DeleteCharacterSpell(uint32 character_id, uint32 slot_id) +{ + return CharacterSpellsRepository::DeleteWhere( + *this, + fmt::format( + "`id` = {} AND `slot_id` = {}", + character_id, + slot_id + ) + ); } bool ZoneDatabase::DeleteCharacterDisc(uint32 character_id, uint32 slot_id){ diff --git a/zone/zonedb.h b/zone/zonedb.h index 2c0b28244..b586765f0 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -428,7 +428,7 @@ public: bool DeleteCharacterMaterialColor(uint32 character_id); bool DeleteCharacterLeadershipAbilities(uint32 character_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 slot_id); - bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); + bool DeleteCharacterSpell(uint32 character_id, uint32 slot_id); bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Struct* pp);