diff --git a/client_files/export/main.cpp b/client_files/export/main.cpp index 08e203722..9e85ad24d 100644 --- a/client_files/export/main.cpp +++ b/client_files/export/main.cpp @@ -29,6 +29,8 @@ #include "../../common/content/world_content_service.h" #include "../../common/zone_store.h" #include "../../common/path_manager.h" +#include "../../common/repositories/skill_caps_repository.h" +#include "../../common/file.h" EQEmuLogSys LogSys; WorldContentService content_service; @@ -164,81 +166,76 @@ void ExportSpells(SharedDatabase *db) fclose(f); } -bool SkillUsable(SharedDatabase *db, int skill_id, int class_id) +bool SkillUsable(SharedDatabase* db, int skill_id, int class_id) { - - bool res = false; - - std::string query = StringFormat( - "SELECT max(cap) FROM skill_caps WHERE class=%d AND skillID=%d", - class_id, skill_id + const auto& l = SkillCapsRepository::GetWhere( + *db, + fmt::format( + "`class_id` = {} AND `skill_id` = {} ORDER BY `cap` DESC LIMIT 1", + class_id, + skill_id + ) ); - auto results = db->QueryDatabase(query); - if (!results.Success()) { - return false; - } - if (results.RowCount() == 0) { - return false; - } - - auto row = results.begin(); - if (row[0] && Strings::ToInt(row[0]) > 0) { - return true; - } - - return false; + return !l.empty(); } -int GetSkill(SharedDatabase *db, int skill_id, int class_id, int level) +uint32 GetSkill(SharedDatabase* db, int skill_id, int class_id, int level) { - - std::string query = StringFormat( - "SELECT cap FROM skill_caps WHERE class=%d AND skillID=%d AND level=%d", - class_id, skill_id, level + const auto& l = SkillCapsRepository::GetWhere( + *db, + fmt::format( + "`class_id` = {} AND `skill_id` = {} AND `level` = {}", + class_id, + skill_id, + level + ) ); - auto results = db->QueryDatabase(query); - if (!results.Success()) { + + if (l.empty()) { return 0; } - if (results.RowCount() == 0) { - return 0; - } + auto e = l.front(); - auto row = results.begin(); - return Strings::ToInt(row[0]); + return e.cap; } -void ExportSkillCaps(SharedDatabase *db) +void ExportSkillCaps(SharedDatabase* db) { LogInfo("Exporting Skill Caps"); - std::string file = fmt::format("{}/export/SkillCaps.txt", path.GetServerPath()); - FILE *f = fopen(file.c_str(), "w"); - if (!f) { + std::ofstream file(fmt::format("{}/export/SkillCaps.txt", path.GetServerPath())); + if (!file || !file.is_open()) { LogError("Unable to open export/SkillCaps.txt to write, skipping."); return; } - for (int cl = 1; cl <= 16; ++cl) { - for (int skill = 0; skill <= 77; ++skill) { - if (SkillUsable(db, skill, cl)) { - int previous_cap = 0; - for (int level = 1; level <= 100; ++level) { - int cap = GetSkill(db, skill, cl, level); + const uint8 skill_cap_max_level = ( + RuleI(Character, SkillCapMaxLevel) > 0 ? + RuleI(Character, SkillCapMaxLevel) : + RuleI(Character, MaxLevel) + ); + + for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) { + for (uint8 skill_id = EQ::skills::Skill1HBlunt; skill_id <= EQ::skills::Skill2HPiercing; skill_id++) { + if (SkillUsable(db, skill_id, class_id)) { + uint32 previous_cap = 0; + for (uint8 level = 1; level <= skill_cap_max_level; level++) { + uint32 cap = GetSkill(db, skill_id, class_id, level); if (cap < previous_cap) { cap = previous_cap; } - fprintf(f, "%d^%d^%d^%d^0\n", cl, skill, level, cap); + file << fmt::format("{}^{}^{}^{}^0", class_id, skill_id, level, cap) << std::endl; + previous_cap = cap; } } } } - fclose(f); + file.close(); } void ExportBaseData(SharedDatabase *db) diff --git a/common/database.cpp b/common/database.cpp index 322908e00..7bdbcc1cc 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -1150,56 +1150,6 @@ uint8 Database::GetRaceSkill(uint8 skillid, uint8 in_race) return Strings::ToUnsignedInt(row[0]); } -uint8 Database::GetSkillCap(uint8 skillid, uint8 in_race, uint8 in_class, uint16 in_level) -{ - uint8 skill_level = 0, skill_formula = 0; - uint16 base_cap = 0, skill_cap = 0, skill_cap2 = 0, skill_cap3 = 0; - - - //Fetch the data from DB. - std::string query = StringFormat("SELECT level, formula, pre50cap, post50cap, post60cap from skillcaps where skill = %i && class = %i", skillid, in_class); - auto results = QueryDatabase(query); - - if (results.Success() && results.RowsAffected() != 0) - { - auto row = results.begin(); - skill_level = Strings::ToUnsignedInt(row[0]); - skill_formula = Strings::ToUnsignedInt(row[1]); - skill_cap = Strings::ToUnsignedInt(row[2]); - if (Strings::ToUnsignedInt(row[3]) > skill_cap) - skill_cap2 = (Strings::ToUnsignedInt(row[3])-skill_cap)/10; //Split the post-50 skill cap into difference between pre-50 cap and post-50 cap / 10 to determine amount of points per level. - skill_cap3 = Strings::ToUnsignedInt(row[4]); - } - - int race_skill = GetRaceSkill(skillid,in_race); - - if (race_skill > 0 && (race_skill > skill_cap || skill_cap == 0 || in_level < skill_level)) - return race_skill; - - if (skill_cap == 0) //Can't train this skill at all. - return 255; //Untrainable - - if (in_level < skill_level) - return 254; //Untrained - - //Determine pre-51 level-based cap - if (skill_formula > 0) - base_cap = in_level*skill_formula+skill_formula; - if (base_cap > skill_cap || skill_formula == 0) - base_cap = skill_cap; - - //If post 50, add post 50 cap to base cap. - if (in_level > 50 && skill_cap2 > 0) - base_cap += skill_cap2*(in_level-50); - - //No cap should ever go above its post50cap - if (skill_cap3 > 0 && base_cap > skill_cap3) - base_cap = skill_cap3; - - //Base cap is now the max value at the person's level, return it! - return base_cap; -} - uint32 Database::GetCharacterInfo(std::string character_name, uint32 *account_id, uint32 *zone_id, uint32 *instance_id) { auto query = fmt::format( diff --git a/common/database.h b/common/database.h index 81f92c97a..857ef07bb 100644 --- a/common/database.h +++ b/common/database.h @@ -244,7 +244,6 @@ public: uint8 GetMinStatus(uint32 zone_id, uint32 instance_version); uint8 GetRaceSkill(uint8 skillid, uint8 in_race); uint8 GetServerType(); - uint8 GetSkillCap(uint8 skillid, uint8 in_race, uint8 in_class, uint16 in_level); void AddReport(std::string who, std::string against, std::string lines); struct TimeOfDay_Struct LoadTime(time_t &realtime); diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index a674afbef..ae7304192 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5451,6 +5451,22 @@ DROP PRIMARY KEY, ADD PRIMARY KEY (`group_id`, `character_id`, `bot_id`, `merc_id`) USING BTREE; ALTER TABLE `group_id` MODIFY COLUMN `character_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `name`; +)" + }, + ManifestEntry{ + .version = 9268, + .description = "2024_03_23_skill_caps.sql", + .check = "SHOW COLUMNS FROM `skill_caps` LIKE 'skill_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `skill_caps` +CHANGE COLUMN `skillID` `skill_id` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 FIRST, +CHANGE COLUMN `class` `class_id` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `skill_id`, +ADD COLUMN `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, +DROP PRIMARY KEY, +ADD PRIMARY KEY (`id`) USING BTREE, +ADD INDEX `level_skill_cap`(`skill_id`, `class_id`, `level`, `cap`); )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/repositories/base/base_skill_caps_repository.h b/common/repositories/base/base_skill_caps_repository.h index 29223c2d3..4b3c86e62 100644 --- a/common/repositories/base/base_skill_caps_repository.h +++ b/common/repositories/base/base_skill_caps_repository.h @@ -19,8 +19,9 @@ class BaseSkillCapsRepository { public: struct SkillCaps { - uint8_t skillID; - uint8_t class_; + uint32_t id; + uint8_t skill_id; + uint8_t class_id; uint8_t level; uint32_t cap; uint8_t class_; @@ -28,14 +29,15 @@ public: static std::string PrimaryKey() { - return std::string("skillID"); + return std::string("id"); } static std::vector Columns() { return { - "skillID", - "`class`", + "id", + "skill_id", + "class_id", "level", "cap", "class_", @@ -45,8 +47,9 @@ public: static std::vector SelectColumns() { return { - "skillID", - "`class`", + "id", + "skill_id", + "class_id", "level", "cap", "class_", @@ -90,11 +93,12 @@ public: { SkillCaps e{}; - e.skillID = 0; - e.class_ = 0; - e.level = 0; - e.cap = 0; - e.class_ = 0; + e.id = 0; + e.skill_id = 0; + e.class_id = 0; + e.level = 0; + e.cap = 0; + e.class_ = 0; return e; } @@ -105,7 +109,7 @@ public: ) { for (auto &skill_caps : skill_capss) { - if (skill_caps.skillID == skill_caps_id) { + if (skill_caps.id == skill_caps_id) { return skill_caps; } } @@ -131,11 +135,12 @@ public: if (results.RowCount() == 1) { SkillCaps e{}; - e.skillID = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.class_ = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.cap = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.class_ = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.skill_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.class_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.level = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.cap = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.class_ = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; return e; } @@ -169,11 +174,11 @@ public: auto columns = Columns(); - v.push_back(columns[0] + " = " + std::to_string(e.skillID)); - v.push_back(columns[1] + " = " + std::to_string(e.class_)); - v.push_back(columns[2] + " = " + std::to_string(e.level)); - v.push_back(columns[3] + " = " + std::to_string(e.cap)); - v.push_back(columns[4] + " = " + std::to_string(e.class_)); + v.push_back(columns[1] + " = " + std::to_string(e.skill_id)); + v.push_back(columns[2] + " = " + std::to_string(e.class_id)); + v.push_back(columns[3] + " = " + std::to_string(e.level)); + v.push_back(columns[4] + " = " + std::to_string(e.cap)); + v.push_back(columns[5] + " = " + std::to_string(e.class_)); auto results = db.QueryDatabase( fmt::format( @@ -181,7 +186,7 @@ public: TableName(), Strings::Implode(", ", v), PrimaryKey(), - e.skillID + e.id ) ); @@ -195,8 +200,9 @@ public: { std::vector v; - v.push_back(std::to_string(e.skillID)); - v.push_back(std::to_string(e.class_)); + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.skill_id)); + v.push_back(std::to_string(e.class_id)); v.push_back(std::to_string(e.level)); v.push_back(std::to_string(e.cap)); v.push_back(std::to_string(e.class_)); @@ -210,7 +216,7 @@ public: ); if (results.Success()) { - e.skillID = results.LastInsertedID(); + e.id = results.LastInsertedID(); return e; } @@ -229,8 +235,9 @@ public: for (auto &e: entries) { std::vector v; - v.push_back(std::to_string(e.skillID)); - v.push_back(std::to_string(e.class_)); + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.skill_id)); + v.push_back(std::to_string(e.class_id)); v.push_back(std::to_string(e.level)); v.push_back(std::to_string(e.cap)); v.push_back(std::to_string(e.class_)); @@ -267,11 +274,12 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { SkillCaps e{}; - e.skillID = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.class_ = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.cap = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.class_ = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.skill_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.class_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.level = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.cap = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.class_ = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -296,11 +304,12 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { SkillCaps e{}; - e.skillID = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.class_ = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.cap = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.class_ = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.skill_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.class_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.level = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.cap = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.class_ = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -375,8 +384,9 @@ public: { std::vector v; - v.push_back(std::to_string(e.skillID)); - v.push_back(std::to_string(e.class_)); + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.skill_id)); + v.push_back(std::to_string(e.class_id)); v.push_back(std::to_string(e.level)); v.push_back(std::to_string(e.cap)); v.push_back(std::to_string(e.class_)); @@ -402,8 +412,9 @@ public: for (auto &e: entries) { std::vector v; - v.push_back(std::to_string(e.skillID)); - v.push_back(std::to_string(e.class_)); + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.skill_id)); + v.push_back(std::to_string(e.class_id)); v.push_back(std::to_string(e.level)); v.push_back(std::to_string(e.cap)); v.push_back(std::to_string(e.class_)); diff --git a/common/servertalk.h b/common/servertalk.h index 7eedbe893..2a8621a70 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -273,6 +273,7 @@ #define ServerOP_ReloadFactions 0x4126 #define ServerOP_ReloadLoot 0x4127 #define ServerOP_ReloadBaseData 0x4128 +#define ServerOP_ReloadSkillCaps 0x4129 #define ServerOP_CZDialogueWindow 0x4500 #define ServerOP_CZLDoNUpdate 0x4501 diff --git a/common/shareddb.cpp b/common/shareddb.cpp index c7eccbce0..d20d520d8 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -45,6 +45,7 @@ #include "repositories/loottable_repository.h" #include "repositories/character_item_recast_repository.h" #include "repositories/character_corpses_repository.h" +#include "repositories/skill_caps_repository.h" namespace ItemField { @@ -1626,136 +1627,6 @@ bool SharedDatabase::GetCommandSubSettings(std::vector(file_name); - - LogInfo("Loaded skill caps via shared memory"); - - mutex.Unlock(); - } catch(std::exception &ex) { - LogError("Error loading skill caps: {}", ex.what()); - return false; - } - - return true; -} - -void SharedDatabase::LoadSkillCaps(void *data) { - const uint32 class_count = Class::PLAYER_CLASS_COUNT; - const uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1; - const uint32 level_count = HARD_LEVEL_CAP + 1; - uint16 *skill_caps_table = static_cast(data); - - const std::string query = "SELECT skillID, class, level, cap FROM skill_caps ORDER BY skillID, class, level"; - auto results = QueryDatabase(query); - if (!results.Success()) { - LogError("Error loading skill caps from database: {}", results.ErrorMessage().c_str()); - return; - } - - for(auto& row = results.begin(); row != results.end(); ++row) { - const uint8 skillID = Strings::ToUnsignedInt(row[0]); - const uint8 class_ = Strings::ToUnsignedInt(row[1]) - 1; - const uint8 level = Strings::ToUnsignedInt(row[2]); - const uint16 cap = Strings::ToUnsignedInt(row[3]); - - if(skillID >= skill_count || class_ >= class_count || level >= level_count) - continue; - - const uint32 index = (((class_ * skill_count) + skillID) * level_count) + level; - skill_caps_table[index] = cap; - } -} - -uint16 SharedDatabase::GetSkillCap(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const -{ - if(!skill_caps_mmf) { - return 0; - } - - if(Class_ == 0) - return 0; - - int SkillMaxLevel = RuleI(Character, SkillCapMaxLevel); - if(SkillMaxLevel < 1) { - SkillMaxLevel = RuleI(Character, MaxLevel); - } - - const uint32 class_count = Class::PLAYER_CLASS_COUNT; - const uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1; - const uint32 level_count = HARD_LEVEL_CAP + 1; - if(Class_ > class_count || static_cast(Skill) > skill_count || Level > level_count) { - return 0; - } - - if(Level > static_cast(SkillMaxLevel)){ - Level = static_cast(SkillMaxLevel); - } - - const uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count) + Level; - const uint16 *skill_caps_table = static_cast(skill_caps_mmf->Get()); - return skill_caps_table[index]; -} - -uint8 SharedDatabase::GetTrainLevel(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const -{ - if(!skill_caps_mmf) { - return 0; - } - - if(Class_ == 0) - return 0; - - int SkillMaxLevel = RuleI(Character, SkillCapMaxLevel); - if (SkillMaxLevel < 1) { - SkillMaxLevel = RuleI(Character, MaxLevel); - } - - const uint32 class_count = Class::PLAYER_CLASS_COUNT; - const uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1; - const uint32 level_count = HARD_LEVEL_CAP + 1; - if(Class_ > class_count || static_cast(Skill) > skill_count || Level > level_count) { - return 0; - } - - uint8 ret = 0; - if(Level > static_cast(SkillMaxLevel)) { - const uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count); - const uint16 *skill_caps_table = static_cast(skill_caps_mmf->Get()); - for(uint8 x = 0; x < Level; x++){ - if(skill_caps_table[index + x]){ - ret = x; - break; - } - } - } - else - { - const uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count); - const uint16 *skill_caps_table = static_cast(skill_caps_mmf->Get()); - for(int x = 0; x < SkillMaxLevel; x++){ - if(skill_caps_table[index + x]){ - ret = x; - break; - } - } - } - - if(ret > GetSkillCap(Class_, Skill, Level)) - ret = static_cast(GetSkillCap(Class_, Skill, Level)); - - return ret; -} - void SharedDatabase::LoadDamageShieldTypes(SPDat_Spell_Struct* sp, int32 iMaxSpellID) { const std::string query = StringFormat("SELECT `spellid`, `type` FROM `damageshieldtypes` WHERE `spellid` > 0 " "AND `spellid` <= %i", iMaxSpellID); @@ -2044,3 +1915,24 @@ void SharedDatabase::SetSharedSpellsCount(uint32 shared_spells_count) { SharedDatabase::m_shared_spells_count = shared_spells_count; } + +uint16 SharedDatabase::GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level) +{ + const auto& l = SkillCapsRepository::GetWhere( + *this, + fmt::format( + "`class_id` = {} AND `skill_id` = {} AND `level` = {} LIMIT 1", + class_id, + static_cast(skill_id), + level + ) + ); + + if (l.empty()) { + return 0; + } + + const auto& e = l.front(); + + return e.cap; +} diff --git a/common/shareddb.h b/common/shareddb.h index e525f9aca..36aec680f 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -160,13 +160,7 @@ public: uint32 GetSharedItemsCount() { return m_shared_items_count; } uint32 GetItemsCount(); - /** - * skills - */ - void LoadSkillCaps(void *data); - bool LoadSkillCaps(const std::string &prefix); - uint16 GetSkillCap(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const; - uint8 GetTrainLevel(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const; + uint16 GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level); /** * spells diff --git a/common/version.h b/common/version.h index 790d43932..102887b44 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9267 +#define CURRENT_BINARY_DATABASE_VERSION 9268 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9043 #endif diff --git a/shared_memory/CMakeLists.txt b/shared_memory/CMakeLists.txt index ba8e2f033..c192e6540 100644 --- a/shared_memory/CMakeLists.txt +++ b/shared_memory/CMakeLists.txt @@ -4,13 +4,11 @@ SET(shared_memory_sources items.cpp main.cpp spells.cpp - skill_caps.cpp ) SET(shared_memory_headers items.h spells.h - skill_caps.h ) ADD_EXECUTABLE(shared_memory ${shared_memory_sources} ${shared_memory_headers}) diff --git a/shared_memory/main.cpp b/shared_memory/main.cpp index 9c4e84f40..b9293fbe1 100644 --- a/shared_memory/main.cpp +++ b/shared_memory/main.cpp @@ -28,7 +28,6 @@ #include "../common/eqemu_exception.h" #include "../common/strings.h" #include "items.h" -#include "skill_caps.h" #include "spells.h" #include "../common/content/world_content_service.h" #include "../common/zone_store.h" @@ -183,7 +182,6 @@ int main(int argc, char **argv) bool load_all = true; bool load_items = false; bool load_loot = false; - bool load_skill_caps = false; bool load_spells = false; if (argc > 1) { @@ -197,11 +195,7 @@ int main(int argc, char **argv) break; case 's': - if (strcasecmp("skill_caps", argv[i]) == 0) { - load_skill_caps = true; - load_all = false; - } - else if (strcasecmp("spells", argv[i]) == 0) { + if (strcasecmp("spells", argv[i]) == 0) { load_spells = true; load_all = false; } @@ -236,16 +230,6 @@ int main(int argc, char **argv) } } - if (load_all || load_skill_caps) { - LogInfo("Loading skill caps"); - try { - LoadSkillCaps(&content_db, hotfix_name); - } catch (std::exception &ex) { - LogError("{}", ex.what()); - return 1; - } - } - if (load_all || load_spells) { LogInfo("Loading spells"); try { diff --git a/shared_memory/skill_caps.cpp b/shared_memory/skill_caps.cpp deleted file mode 100644 index 648a0cfb5..000000000 --- a/shared_memory/skill_caps.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2013 EQEMu Development Team (http://eqemulator.net) - - 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; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - 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, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "skill_caps.h" -#include "../common/global_define.h" -#include "../common/shareddb.h" -#include "../common/ipc_mutex.h" -#include "../common/memory_mapped_file.h" -#include "../common/eqemu_exception.h" -#include "../common/classes.h" -#include "../common/features.h" - -void LoadSkillCaps(SharedDatabase *database, const std::string &prefix) { - EQ::IPCMutex mutex("skill_caps"); - mutex.Lock(); - - uint32 class_count = Class::PLAYER_CLASS_COUNT; - uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1; - uint32 level_count = HARD_LEVEL_CAP + 1; - uint32 size = (class_count * skill_count * level_count * sizeof(uint16)); - - auto Config = EQEmuConfig::get(); - std::string file_name = Config->SharedMemDir + prefix + std::string("skill_caps"); - EQ::MemoryMappedFile mmf(file_name, size); - mmf.ZeroFile(); - - void *ptr = mmf.Get(); - database->LoadSkillCaps(ptr); - mutex.Unlock(); -} diff --git a/shared_memory/skill_caps.h b/shared_memory/skill_caps.h deleted file mode 100644 index da2cebc51..000000000 --- a/shared_memory/skill_caps.h +++ /dev/null @@ -1,29 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2013 EQEMu Development Team (http://eqemulator.net) - - 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; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - 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, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __EQEMU_SHARED_MEMORY_SKILL_CAPS_H -#define __EQEMU_SHARED_MEMORY_SKILL_CAPS_H - -#include -#include "../common/eqemu_config.h" - -class SharedDatabase; -void LoadSkillCaps(SharedDatabase *database, const std::string &prefix); - -#endif - diff --git a/world/eqemu_api_world_data_service.cpp b/world/eqemu_api_world_data_service.cpp index c3013a4de..adbb873aa 100644 --- a/world/eqemu_api_world_data_service.cpp +++ b/world/eqemu_api_world_data_service.cpp @@ -156,6 +156,7 @@ std::vector reload_types = { Reload{.command = "opcodes", .opcode = ServerOP_ReloadOpcodes, .desc = "Opcodes"}, Reload{.command = "perl_export", .opcode = ServerOP_ReloadPerlExportSettings, .desc = "Perl Event Export Settings"}, Reload{.command = "rules", .opcode = ServerOP_ReloadRules, .desc = "Rules"}, + Reload{.command = "skill_caps", .opcode = ServerOP_ReloadSkillCaps, .desc = "Skill Caps"}, Reload{.command = "static", .opcode = ServerOP_ReloadStaticZoneData, .desc = "Static Zone Data"}, Reload{.command = "tasks", .opcode = ServerOP_ReloadTasks, .desc = "Tasks"}, Reload{.command = "titles", .opcode = ServerOP_ReloadTitles, .desc = "Titles"}, diff --git a/world/world_boot.cpp b/world/world_boot.cpp index 86ae466ec..4eeb49b38 100644 --- a/world/world_boot.cpp +++ b/world/world_boot.cpp @@ -292,10 +292,6 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) LogError("Error: Could not load item data. But ignoring"); } - if (!content_db.LoadSkillCaps(std::string(hotfix_name))) { - LogError("Error: Could not load skill cap data. But ignoring"); - } - guild_mgr.LoadGuilds(); guild_mgr.LoadTributes(); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 6ea739d3c..1450dd7e5 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1411,6 +1411,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ReloadNPCEmotes: case ServerOP_ReloadObjects: case ServerOP_ReloadPerlExportSettings: + case ServerOP_ReloadSkillCaps: case ServerOP_ReloadStaticZoneData: case ServerOP_ReloadTitles: case ServerOP_ReloadTraps: @@ -1496,11 +1497,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { LogInfo("Error: Could not load item data. But ignoring"); } - LogInfo("Loading skill caps"); - if (!content_db.LoadSkillCaps(hotfix_name)) { - LogInfo("Error: Could not load skill cap data. But ignoring"); - } - zoneserver_list.SendPacket(pack); break; } diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 662c35e0f..a8a1fc8c3 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -166,6 +166,7 @@ SET(zone_sources zone_event_scheduler.cpp zone_npc_factions.cpp zone_reload.cpp + zone_skill_caps.cpp zoning.cpp ) diff --git a/zone/client.cpp b/zone/client.cpp index fabf9bf32..7dcace56d 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2756,31 +2756,48 @@ void Client::CheckLanguageSkillIncrease(uint8 language_id, uint8 teacher_skill) } } -bool Client::HasSkill(EQ::skills::SkillType skill_id) const { - return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id)); -} - -bool Client::CanHaveSkill(EQ::skills::SkillType skill_id) const { - if (ClientVersion() < EQ::versions::ClientVersion::RoF2 && class_ == Class::Berserker && skill_id == EQ::skills::Skill1HPiercing) - skill_id = EQ::skills::Skill2HPiercing; - - return(content_db.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)) > 0); - //if you don't have it by max level, then odds are you never will? -} - -uint16 Client::MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const { - if (ClientVersion() < EQ::versions::ClientVersion::RoF2 && class_ == Class::Berserker && skillid == EQ::skills::Skill1HPiercing) - skillid = EQ::skills::Skill2HPiercing; - - return(content_db.GetSkillCap(class_, skillid, level)); -} - -uint8 Client::SkillTrainLevel(EQ::skills::SkillType skillid, uint16 class_) +bool Client::HasSkill(EQ::skills::SkillType skill_id) const { - if (ClientVersion() < EQ::versions::ClientVersion::RoF2 && class_ == Class::Berserker && skillid == EQ::skills::Skill1HPiercing) - skillid = EQ::skills::Skill2HPiercing; + return GetSkill(skill_id) > 0 && CanHaveSkill(skill_id); +} - return(content_db.GetTrainLevel(class_, skillid, RuleI(Character, MaxLevel))); +bool Client::CanHaveSkill(EQ::skills::SkillType skill_id) const +{ + if ( + ClientVersion() < EQ::versions::ClientVersion::RoF2 && + class_ == Class::Berserker && + skill_id == EQ::skills::Skill1HPiercing + ) { + skill_id = EQ::skills::Skill2HPiercing; + } + + return zone->GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)).cap > 0; +} + +uint16 Client::MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const +{ + if ( + ClientVersion() < EQ::versions::ClientVersion::RoF2 && + class_id == Class::Berserker && + skill_id == EQ::skills::Skill1HPiercing + ) { + skill_id = EQ::skills::Skill2HPiercing; + } + + return(content_db.GetSkillCap(class_id, skill_id, level)); +} + +uint8 Client::SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id) +{ + if ( + ClientVersion() < EQ::versions::ClientVersion::RoF2 && + class_id == Class::Berserker && + skill_id == EQ::skills::Skill1HPiercing + ) { + skill_id = EQ::skills::Skill2HPiercing; + } + + return zone->GetTrainLevel(class_id, skill_id, RuleI(Character, MaxLevel)); } uint16 Client::GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill) @@ -9226,6 +9243,7 @@ void Client::ShowDevToolsMenu() menu_reload_six += " | " + Saylink::Silent("#reload quest", "Quests"); menu_reload_seven += Saylink::Silent("#reload rules", "Rules"); + menu_reload_seven += " | " + Saylink::Silent("#reload skill_caps", "Skill Caps"); menu_reload_seven += " | " + Saylink::Silent("#reload static", "Static Zone Data"); menu_reload_seven += " | " + Saylink::Silent("#reload tasks", "Tasks"); @@ -11353,6 +11371,16 @@ void Client::SendReloadCommandMessages() { ).c_str() ); + auto skill_caps_link = Saylink::Silent("#reload skill_caps"); + + Message( + Chat::White, + fmt::format( + "Usage: {} - Reloads Skill Caps globally", + skill_caps_link + ).c_str() + ); + auto static_link = Saylink::Silent("#reload static"); Message( diff --git a/zone/client.h b/zone/client.h index dfddcf07d..6b54c202a 100644 --- a/zone/client.h +++ b/zone/client.h @@ -815,9 +815,9 @@ public: void SetHoTT(uint32 mobid); void ShowSkillsWindow(); - uint16 MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const; - inline uint16 MaxSkill(EQ::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); } - uint8 SkillTrainLevel(EQ::skills::SkillType skillid, uint16 class_); + uint16 MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const; + inline uint16 MaxSkill(EQ::skills::SkillType skill_id) const { return MaxSkill(skill_id, GetClass(), GetLevel()); } + uint8 SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id); void MaxSkills(); void SendTradeskillSearchResults(const std::string &query, unsigned long objtype, unsigned long someid); diff --git a/zone/gm_commands/reload.cpp b/zone/gm_commands/reload.cpp index 0c0a73410..2566d3ba0 100644 --- a/zone/gm_commands/reload.cpp +++ b/zone/gm_commands/reload.cpp @@ -34,6 +34,7 @@ void command_reload(Client *c, const Seperator *sep) bool is_perl_export = !strcasecmp(sep->arg[1], "perl_export"); bool is_quest = !strcasecmp(sep->arg[1], "quest") || (is_rq_alias); bool is_rules = !strcasecmp(sep->arg[1], "rules"); + bool is_skill_caps = !strcasecmp(sep->arg[1], "skill_caps"); bool is_static = !strcasecmp(sep->arg[1], "static"); bool is_tasks = !strcasecmp(sep->arg[1], "tasks"); bool is_titles = !strcasecmp(sep->arg[1], "titles"); @@ -66,6 +67,7 @@ void command_reload(Client *c, const Seperator *sep) !is_perl_export && !is_quest && !is_rules && + !is_skill_caps && !is_static && !is_tasks && !is_titles && @@ -161,6 +163,9 @@ void command_reload(Client *c, const Seperator *sep) } else if (is_rules) { c->Message(Chat::White, "Attempting to reload Rules globally."); pack = new ServerPacket(ServerOP_ReloadRules, 0); + } else if (is_skill_caps) { + c->Message(Chat::White, "Attempting to reload Skill Caps globally."); + pack = new ServerPacket(ServerOP_ReloadSkillCaps, 0); } else if (is_static) { c->Message(Chat::White, "Attempting to reload Static Zone Data globally."); pack = new ServerPacket(ServerOP_ReloadStaticZoneData, 0); diff --git a/zone/main.cpp b/zone/main.cpp index 7d6a17ac7..e8e841f47 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -372,10 +372,6 @@ int main(int argc, char **argv) LogError("Failed. But ignoring error and going on.."); } - if (!content_db.LoadSkillCaps(std::string(hotfix_name))) { - LogError("Loading skill caps failed!"); - return 1; - } if (!database.LoadSpells(hotfix_name, &SPDAT_RECORDS, &spells)) { LogError("Loading spells failed!"); return 1; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index d63cd14bb..49f260b06 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2097,6 +2097,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); break; } + case ServerOP_ReloadSkillCaps: + { + if (zone && zone->IsLoaded()) { + zone->SendReloadMessage("Skill Caps"); + zone->ReloadSkillCaps(); + } + + break; + } case ServerOP_ReloadDataBucketsCache: { zone->SendReloadMessage("Data buckets cache"); @@ -3563,11 +3572,6 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) LogError("Loading items failed!"); } - LogInfo("Loading skill caps"); - if (!content_db.LoadSkillCaps(std::string(hotfix_name))) { - LogError("Loading skill caps failed!"); - } - LogInfo("Loading spells"); if (!content_db.LoadSpells(hotfix_name, &SPDAT_RECORDS, &spells)) { LogError("Loading spells failed!"); diff --git a/zone/zone.cpp b/zone/zone.cpp index 826b33bf5..0e08f0dc5 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1177,6 +1177,8 @@ bool Zone::Init(bool is_static) { LoadBaseData(); + LoadSkillCaps(); + //Load merchant data LoadMerchants(); diff --git a/zone/zone.h b/zone/zone.h index aa00653e8..0cbd050e1 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -46,6 +46,7 @@ #include "../common/repositories/lootdrop_repository.h" #include "../common/repositories/lootdrop_entries_repository.h" #include "../common/repositories/base_data_repository.h" +#include "../common/repositories/skill_caps_repository.h" struct EXPModifier { @@ -452,6 +453,13 @@ public: void LoadBaseData(); void ReloadBaseData(); + // Skill Caps + inline void ClearSkillCaps() { m_skill_caps.clear(); } + SkillCapsRepository::SkillCaps GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level); + uint8 GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level); + void LoadSkillCaps(); + void ReloadSkillCaps(); + private: bool allow_mercs; bool can_bind; @@ -515,6 +523,9 @@ private: // Base Data std::vector m_base_data = { }; + + // Skill Caps + std::vector m_skill_caps = { }; }; #endif diff --git a/zone/zone_skill_caps.cpp b/zone/zone_skill_caps.cpp new file mode 100644 index 000000000..b09de1649 --- /dev/null +++ b/zone/zone_skill_caps.cpp @@ -0,0 +1,84 @@ +#include "zone.h" + +SkillCapsRepository::SkillCaps Zone::GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level) +{ + if (!IsPlayerClass(class_id)) { + return SkillCapsRepository::NewEntity(); + } + + for (const auto& e : m_skill_caps) { + if ( + e.class_id == class_id && + e.level == level && + static_cast(e.skill_id) == skill_id + ) { + return e; + } + } + + return SkillCapsRepository::NewEntity(); +} + +uint8 Zone::GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level) +{ + if ( + !IsPlayerClass(class_id) || + class_id > Class::PLAYER_CLASS_COUNT || + static_cast(skill_id) > (EQ::skills::HIGHEST_SKILL + 1) + ) { + return 0; + } + + const uint8 skill_cap_max_level = ( + RuleI(Character, SkillCapMaxLevel) > 0 ? + RuleI(Character, SkillCapMaxLevel) : + RuleI(Character, MaxLevel) + ); + + const uint8 max_level = level > skill_cap_max_level ? level : skill_cap_max_level; + + for (const auto& e : m_skill_caps) { + for (uint8 current_level = 1; current_level <= max_level; current_level++) { + if ( + e.class_id == class_id && + static_cast(e.skill_id) == skill_id && + e.level == current_level + ) { + return current_level; + } + } + } + + return 0; +} + +void Zone::LoadSkillCaps() +{ + const auto& l = SkillCapsRepository::All(content_db); + + m_skill_caps.reserve(l.size()); + + for (const auto& e : l) { + if ( + e.level < 1 || + !IsPlayerClass(e.class_id) || + static_cast(e.skill_id) >= EQ::skills::SkillCount + ) { + continue; + } + + m_skill_caps.emplace_back(e); + } + + LogInfo( + "Loaded [{}] Skill Cap Entr{}", + l.size(), + l.size() != 1 ? "ies" : "y" + ); +} + +void Zone::ReloadSkillCaps() +{ + ClearSkillCaps(); + LoadSkillCaps(); +}