From a65db13ec33cc258bd4e66e6eea9d3aeefecf853 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Tue, 17 Jun 2025 02:32:22 -0500 Subject: [PATCH] [Rules] Implement Targeted Rule Sets --- common/content/world_content_service.cpp | 69 ++++++++++++ common/content/world_content_service.h | 8 ++ common/database/database_update_manifest.cpp | 18 ++++ .../base/base_rule_sets_repository.h | 100 ++++++++++++++++-- common/rulesys.cpp | 8 +- common/rulesys.h | 6 ++ common/version.h | 2 +- zone/main.cpp | 5 + zone/zone.cpp | 5 +- 9 files changed, 209 insertions(+), 12 deletions(-) diff --git a/common/content/world_content_service.cpp b/common/content/world_content_service.cpp index d6511e710..4242f201d 100644 --- a/common/content/world_content_service.cpp +++ b/common/content/world_content_service.cpp @@ -6,6 +6,8 @@ #include "../rulesys.h" #include "../eqemu_logsys.h" #include "../repositories/instance_list_repository.h" +#include "../repositories/rule_sets_repository.h" +#include "../repositories/rule_values_repository.h" #include "../zone_store.h" @@ -347,3 +349,70 @@ bool WorldContentService::DoesZonePassContentFiltering(const ZoneRepository::Zon return DoesPassContentFiltering(f); } + +void WorldContentService::LoadTargetedRulesets(Database* db) +{ + if (!m_zone_id) { + LogError("Zone ID is not set. Cannot load targeted rulesets."); + return; + } + + LogInfo("Zone ID [{}] Instance Version [{}] - Loading targeted rulesets", m_zone_id, m_instance_version); + + constexpr int8 EXPANSION_ZERO_VALUE = -2; + + auto rules = RuleValuesRepository::GetWhere(*db, "TRUE ORDER BY ruleset_id, rule_name"); + auto inst = RuleManager::Instance(); + auto sets = RuleSetsRepository::GetWhere(*db, "TRUE ORDER BY ruleset_id"); + for (auto& e : sets) { + bool has_filters = + !e.zone_ids.empty() || + !e.instance_versions.empty() || + !e.content_flags.empty() || + !e.content_flags_disabled.empty() || + e.min_expansion != EXPANSION_ZERO_VALUE || + e.max_expansion != EXPANSION_ZERO_VALUE; + if (!has_filters) { + continue; // not a targeted ruleset + } + + auto zone_id = std::to_string(m_zone_id); + if (!e.zone_ids.empty() && !Strings::Contains(e.zone_ids, zone_id)) { + continue; + } + + auto instance_id = std::to_string(m_instance_version); + if (!e.instance_versions.empty() && !Strings::Contains(e.instance_versions, instance_id)) { + continue; + } + + if (!DoesPassContentFiltering( + ContentFlags{ + .min_expansion = e.min_expansion, + .max_expansion = e.max_expansion, + .content_flags = e.content_flags, + .content_flags_disabled = e.content_flags_disabled + } + )) { + continue; + } + + for (auto &r : rules) { + if (r.ruleset_id != e.ruleset_id) { + continue; + } + + inst->SetRule(r.rule_name, r.rule_value); + + LogInfo( + "Loading targeted rule from ruleset [{}] ruleset_name [{}] rule_name [{}] rule_value [{}]", + e.ruleset_id, + e.name, + r.rule_name, + r.rule_value + ); + } + } + + return; +} \ No newline at end of file diff --git a/common/content/world_content_service.h b/common/content/world_content_service.h index 0f84682d7..ddab25270 100644 --- a/common/content/world_content_service.h +++ b/common/content/world_content_service.h @@ -187,6 +187,11 @@ public: return &instance; } + // targeted rulesets + void LoadTargetedRulesets(Database* db); + inline void SetZoneId(int zone_id) { m_zone_id = zone_id; } + inline void SetInstanceVersion(int instance_version) { m_instance_version = instance_version; } + private: int current_expansion{}; std::vector content_flags; @@ -195,6 +200,9 @@ private: Database *m_database; Database *m_content_database; + int m_zone_id = 0; + int m_instance_version = 0; + // holds a record of the zone table from the database WorldContentService *LoadStaticGlobalZoneInstances(); std::vector m_zone_static_instances; diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 05984036e..37c1925ea 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7133,6 +7133,24 @@ CREATE INDEX idx_event_type_char_id ON player_event_logs (event_type_id, charact .sql = R"( ALTER TABLE `character_corpses` ADD COLUMN `entity_variables` TEXT DEFAULT NULL AFTER `rezzable`; +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9326, + .description = "2025_06_16_rule_set_expansion.sql", + .check = "SHOW COLUMNS FROM `rule_sets` LIKE 'zone_ids'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `rule_sets` + ADD COLUMN `zone_ids` TEXT NOT NULL DEFAULT '', + ADD COLUMN `instance_versions` TEXT NOT NULL DEFAULT '', + ADD COLUMN `content_flags` TEXT NOT NULL DEFAULT '', + ADD COLUMN `content_flags_disabled` TEXT NOT NULL DEFAULT '', + ADD COLUMN `min_expansion` TINYINT NOT NULL DEFAULT -2, + ADD COLUMN `max_expansion` TINYINT NOT NULL DEFAULT -2, + ADD COLUMN `notes` VARCHAR(255) NOT NULL DEFAULT ''; )", .content_schema_update = false }, diff --git a/common/repositories/base/base_rule_sets_repository.h b/common/repositories/base/base_rule_sets_repository.h index e4f51f69d..128ec89c8 100644 --- a/common/repositories/base/base_rule_sets_repository.h +++ b/common/repositories/base/base_rule_sets_repository.h @@ -21,6 +21,13 @@ public: struct RuleSets { uint8_t ruleset_id; std::string name; + std::string zone_ids; + std::string instance_versions; + std::string content_flags; + std::string content_flags_disabled; + int8_t min_expansion; + int8_t max_expansion; + std::string notes; }; static std::string PrimaryKey() @@ -33,6 +40,13 @@ public: return { "ruleset_id", "name", + "zone_ids", + "instance_versions", + "content_flags", + "content_flags_disabled", + "min_expansion", + "max_expansion", + "notes", }; } @@ -41,6 +55,13 @@ public: return { "ruleset_id", "name", + "zone_ids", + "instance_versions", + "content_flags", + "content_flags_disabled", + "min_expansion", + "max_expansion", + "notes", }; } @@ -81,8 +102,15 @@ public: { RuleSets e{}; - e.ruleset_id = 0; - e.name = ""; + e.ruleset_id = 0; + e.name = ""; + e.zone_ids = ""; + e.instance_versions = ""; + e.content_flags = ""; + e.content_flags_disabled = ""; + e.min_expansion = -2; + e.max_expansion = -2; + e.notes = ""; return e; } @@ -119,8 +147,15 @@ public: if (results.RowCount() == 1) { RuleSets e{}; - e.ruleset_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.name = row[1] ? row[1] : ""; + e.ruleset_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.name = row[1] ? row[1] : ""; + e.zone_ids = row[2] ? row[2] : ""; + e.instance_versions = row[3] ? row[3] : ""; + e.content_flags = row[4] ? row[4] : ""; + e.content_flags_disabled = row[5] ? row[5] : ""; + e.min_expansion = row[6] ? static_cast(atoi(row[6])) : -2; + e.max_expansion = row[7] ? static_cast(atoi(row[7])) : -2; + e.notes = row[8] ? row[8] : ""; return e; } @@ -155,6 +190,13 @@ public: auto columns = Columns(); v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'"); + v.push_back(columns[2] + " = '" + Strings::Escape(e.zone_ids) + "'"); + v.push_back(columns[3] + " = '" + Strings::Escape(e.instance_versions) + "'"); + v.push_back(columns[4] + " = '" + Strings::Escape(e.content_flags) + "'"); + v.push_back(columns[5] + " = '" + Strings::Escape(e.content_flags_disabled) + "'"); + v.push_back(columns[6] + " = " + std::to_string(e.min_expansion)); + v.push_back(columns[7] + " = " + std::to_string(e.max_expansion)); + v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -178,6 +220,13 @@ public: v.push_back(std::to_string(e.ruleset_id)); v.push_back("'" + Strings::Escape(e.name) + "'"); + v.push_back("'" + Strings::Escape(e.zone_ids) + "'"); + v.push_back("'" + Strings::Escape(e.instance_versions) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags_disabled) + "'"); + v.push_back(std::to_string(e.min_expansion)); + v.push_back(std::to_string(e.max_expansion)); + v.push_back("'" + Strings::Escape(e.notes) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -209,6 +258,13 @@ public: v.push_back(std::to_string(e.ruleset_id)); v.push_back("'" + Strings::Escape(e.name) + "'"); + v.push_back("'" + Strings::Escape(e.zone_ids) + "'"); + v.push_back("'" + Strings::Escape(e.instance_versions) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags_disabled) + "'"); + v.push_back(std::to_string(e.min_expansion)); + v.push_back(std::to_string(e.max_expansion)); + v.push_back("'" + Strings::Escape(e.notes) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -242,8 +298,15 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { RuleSets e{}; - e.ruleset_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.name = row[1] ? row[1] : ""; + e.ruleset_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.name = row[1] ? row[1] : ""; + e.zone_ids = row[2] ? row[2] : ""; + e.instance_versions = row[3] ? row[3] : ""; + e.content_flags = row[4] ? row[4] : ""; + e.content_flags_disabled = row[5] ? row[5] : ""; + e.min_expansion = row[6] ? static_cast(atoi(row[6])) : -2; + e.max_expansion = row[7] ? static_cast(atoi(row[7])) : -2; + e.notes = row[8] ? row[8] : ""; all_entries.push_back(e); } @@ -268,8 +331,15 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { RuleSets e{}; - e.ruleset_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.name = row[1] ? row[1] : ""; + e.ruleset_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.name = row[1] ? row[1] : ""; + e.zone_ids = row[2] ? row[2] : ""; + e.instance_versions = row[3] ? row[3] : ""; + e.content_flags = row[4] ? row[4] : ""; + e.content_flags_disabled = row[5] ? row[5] : ""; + e.min_expansion = row[6] ? static_cast(atoi(row[6])) : -2; + e.max_expansion = row[7] ? static_cast(atoi(row[7])) : -2; + e.notes = row[8] ? row[8] : ""; all_entries.push_back(e); } @@ -346,6 +416,13 @@ public: v.push_back(std::to_string(e.ruleset_id)); v.push_back("'" + Strings::Escape(e.name) + "'"); + v.push_back("'" + Strings::Escape(e.zone_ids) + "'"); + v.push_back("'" + Strings::Escape(e.instance_versions) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags_disabled) + "'"); + v.push_back(std::to_string(e.min_expansion)); + v.push_back(std::to_string(e.max_expansion)); + v.push_back("'" + Strings::Escape(e.notes) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -370,6 +447,13 @@ public: v.push_back(std::to_string(e.ruleset_id)); v.push_back("'" + Strings::Escape(e.name) + "'"); + v.push_back("'" + Strings::Escape(e.zone_ids) + "'"); + v.push_back("'" + Strings::Escape(e.instance_versions) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags) + "'"); + v.push_back("'" + Strings::Escape(e.content_flags_disabled) + "'"); + v.push_back(std::to_string(e.min_expansion)); + v.push_back(std::to_string(e.max_expansion)); + v.push_back("'" + Strings::Escape(e.notes) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/rulesys.cpp b/common/rulesys.cpp index f73112b1b..3fa0ec925 100644 --- a/common/rulesys.cpp +++ b/common/rulesys.cpp @@ -26,6 +26,7 @@ #include "../common/repositories/rule_sets_repository.h" #include "../common/repositories/rule_values_repository.h" +#include "../common/content/world_content_service.h" const char *RuleManager::s_categoryNames[_CatCount + 1] = { #define RULE_CATEGORY(category_name) \ @@ -268,7 +269,6 @@ bool RuleManager::LoadRules(Database *db, const std::string &rule_set_name, bool const std::string default_ruleset_name = "default"; bool is_default = rule_set_name == default_ruleset_name; - if (!is_default) { const auto default_rule_set_id = RuleSetsRepository::GetRuleSetID(*db, default_ruleset_name); @@ -327,6 +327,10 @@ bool RuleManager::LoadRules(Database *db, const std::string &rule_set_name, bool rule_set_id ); + if (m_post_load_callback) { + m_post_load_callback(db); + } + return true; } @@ -661,4 +665,4 @@ bool RuleManager::GetBoolRule(RuleManager::BoolType t) const std::string RuleManager::GetStringRule(RuleManager::StringType t) const { return m_RuleStringValues[t]; -} +} \ No newline at end of file diff --git a/common/rulesys.h b/common/rulesys.h index eab3af6b8..1ee5f4939 100644 --- a/common/rulesys.h +++ b/common/rulesys.h @@ -40,6 +40,7 @@ RuleManager::Instance()->GetStringRule( RuleManager::String__##rule_name ) +#include #include #include #include @@ -156,6 +157,9 @@ public: bool UpdateInjectedRules(Database* db, const std::string& rule_set_name, bool quiet_update = false); bool UpdateOrphanedRules(Database* db, bool quiet_update = false); bool RestoreRuleNotes(Database* db); + void SetPostLoadCallback(std::function cb) { + m_post_load_callback = std::move(cb); + } private: RuleManager(); @@ -170,6 +174,8 @@ private: uint32 m_RuleBoolValues[BoolRuleCount]; std::string m_RuleStringValues[StringRuleCount]; + std::function m_post_load_callback; + typedef enum { IntRule, RealRule, diff --git a/common/version.h b/common/version.h index 82382af86..0fb38f1af 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 9325 +#define CURRENT_BINARY_DATABASE_VERSION 9326 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #define CUSTOM_BINARY_DATABASE_VERSION 0 diff --git a/zone/main.cpp b/zone/main.cpp index 6177ce562..0f60a746d 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -413,6 +413,11 @@ int main(int argc, char **argv) ->ReloadContentFlags(); ZoneEventScheduler::Instance()->SetDatabase(&database)->LoadScheduledEvents(); + RuleManager::Instance()->SetPostLoadCallback( + [&](Database* db) { + WorldContentService::Instance()->LoadTargetedRulesets(db); + } + ); EQ::SayLinkEngine::LoadCachedSaylinks(); diff --git a/zone/zone.cpp b/zone/zone.cpp index 29ee85867..917f84c89 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1151,6 +1151,10 @@ bool Zone::Init(bool is_static) { ); } // if that fails, try the file name, then load defaults + content_service.SetZoneId(GetZoneID()); + content_service.SetInstanceVersion(GetInstanceVersion()); + RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); + if (RuleManager::Instance()->GetActiveRulesetID() != default_ruleset) { std::string r_name = RuleSetsRepository::GetRuleSetName(database, default_ruleset); if (r_name.size() > 0) { @@ -1293,7 +1297,6 @@ void Zone::ReloadStaticData() { WorldContentService::Instance()->SetExpansionContext()->ReloadContentFlags(); - LogInfo("Zone Static Data Reloaded"); }