package rules import ( "fmt" "log" "sync" ) // RuleManager manages all rule sets and provides rule lookup functionality // Converted from C++ RuleManager class type RuleManager struct { // Core rule storage rules map[RuleCategory]map[RuleType]*Rule // Default rules from code ruleSets map[int32]*RuleSet // Rule sets loaded from database globalRuleSet *RuleSet // Active global rule set zoneRuleSets map[int32]*RuleSet // Zone-specific rule sets blankRule *Rule // Default blank rule // Thread safety rulesMutex sync.RWMutex ruleSetsMutex sync.RWMutex globalRuleSetMutex sync.RWMutex zoneRuleSetsMutex sync.RWMutex // Statistics stats RuleManagerStats // Configuration initialized bool } // NewRuleManager creates a new rule manager instance func NewRuleManager() *RuleManager { rm := &RuleManager{ rules: make(map[RuleCategory]map[RuleType]*Rule), ruleSets: make(map[int32]*RuleSet), globalRuleSet: NewRuleSet(), zoneRuleSets: make(map[int32]*RuleSet), blankRule: NewRule(), initialized: false, } rm.Init() // Load coded defaults into global rule set rm.LoadCodedDefaultsIntoRuleSet(rm.globalRuleSet) return rm } // Init initializes the rule manager with default rules from code // Equivalent to C++ RuleManager::Init() func (rm *RuleManager) Init() { rm.rulesMutex.Lock() defer rm.rulesMutex.Unlock() // Clear existing rules rm.rules = make(map[RuleCategory]map[RuleType]*Rule) // Initialize all default rules with their coded values // This is a direct conversion of the RULE_INIT macro usage in C++ // CLIENT RULES rm.initRule(CategoryClient, ClientShowWelcomeScreen, "0", "Client:ShowWelcomeScreen") rm.initRule(CategoryClient, ClientGroupSpellsTimer, "1000", "Client:GroupSpellsTimer") rm.initRule(CategoryClient, ClientQuestQueueTimer, "50", "Client:QuestQueueTimer") // FACTION RULES rm.initRule(CategoryFaction, FactionAllowBasedCombat, "1", "Faction:AllowFactionBasedCombat") // GUILD RULES rm.initRule(CategoryGuild, GuildMaxLevel, "50", "Guild:MaxLevel") rm.initRule(CategoryGuild, GuildMaxPlayers, "-1", "Guild:MaxPlayers") // PLAYER RULES rm.initRule(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") rm.initRule(CategoryPlayer, PlayerMaxLevelOverrideStatus, "100", "Player:MaxLevelOverrideStatus") rm.initRule(CategoryPlayer, PlayerVitalityAmount, ".5", "Player:VitalityAmount") rm.initRule(CategoryPlayer, PlayerVitalityFrequency, "3600", "Player:VitalityFrequency") rm.initRule(CategoryPlayer, PlayerMaxAA, "320", "Player:MaxAA") rm.initRule(CategoryPlayer, PlayerMaxClassAA, "100", "Player:MaxClassAA") rm.initRule(CategoryPlayer, PlayerMaxSubclassAA, "100", "Player:MaxSubclassAA") rm.initRule(CategoryPlayer, PlayerMaxShadowsAA, "70", "Player:MaxShadowsAA") rm.initRule(CategoryPlayer, PlayerMaxHeroicAA, "50", "Player:MaxHeroicAA") rm.initRule(CategoryPlayer, PlayerMaxTradeskillAA, "40", "Player:MaxTradeskillAA") rm.initRule(CategoryPlayer, PlayerMaxPrestigeAA, "25", "Player:MaxPrestigeAA") rm.initRule(CategoryPlayer, PlayerMaxTradeskillPrestigeAA, "25", "Player:MaxTradeskillPrestigeAA") rm.initRule(CategoryPlayer, PlayerMinLastNameLevel, "20", "Player:MinLastNameLevel") rm.initRule(CategoryPlayer, PlayerMaxLastNameLength, "20", "Player:MaxLastNameLength") rm.initRule(CategoryPlayer, PlayerMinLastNameLength, "4", "Player:MinLastNameLength") rm.initRule(CategoryPlayer, PlayerDisableHouseAlignmentRequirement, "1", "Player:DisableHouseAlignmentRequirement") rm.initRule(CategoryPlayer, PlayerMentorItemDecayRate, ".05", "Player:MentorItemDecayRate") rm.initRule(CategoryPlayer, PlayerTemporaryItemLogoutTime, "1800.0", "Player:TemporaryItemLogoutTime") rm.initRule(CategoryPlayer, PlayerHeirloomItemShareExpiration, "172800.0", "Player:HeirloomItemShareExpiration") rm.initRule(CategoryPlayer, PlayerSwimmingSkillMinSpeed, "20", "Player:SwimmingSkillMinSpeed") rm.initRule(CategoryPlayer, PlayerSwimmingSkillMaxSpeed, "200", "Player:SwimmingSkillMaxSpeed") rm.initRule(CategoryPlayer, PlayerSwimmingSkillMinBreathLength, "30", "Player:SwimmingSkillMinBreathLength") rm.initRule(CategoryPlayer, PlayerSwimmingSkillMaxBreathLength, "1000", "Player:SwimmingSkillMaxBreathLength") rm.initRule(CategoryPlayer, PlayerAutoSkillUpBaseSkills, "0", "Player:AutoSkillUpBaseSkills") rm.initRule(CategoryPlayer, PlayerMaxWeightStrengthMultiplier, "2.0", "Player:MaxWeightStrengthMultiplier") rm.initRule(CategoryPlayer, PlayerBaseWeight, "50", "Player:BaseWeight") rm.initRule(CategoryPlayer, PlayerWeightPercentImpact, "0.01", "Player:WeightPercentImpact") rm.initRule(CategoryPlayer, PlayerWeightPercentCap, "0.95", "Player:WeightPercentCap") rm.initRule(CategoryPlayer, PlayerCoinWeightPerStone, "40.0", "Player:CoinWeightPerStone") rm.initRule(CategoryPlayer, PlayerWeightInflictsSpeed, "1", "Player:WeightInflictsSpeed") rm.initRule(CategoryPlayer, PlayerLevelMasterySkillMultiplier, "5", "Player:LevelMasterySkillMultiplier") rm.initRule(CategoryPlayer, PlayerTraitTieringSelection, "1", "Player:TraitTieringSelection") rm.initRule(CategoryPlayer, PlayerClassicTraitLevelTable, "1", "Player:ClassicTraitLevelTable") rm.initRule(CategoryPlayer, PlayerTraitFocusSelectLevel, "9", "Player:TraitFocusSelectLevel") rm.initRule(CategoryPlayer, PlayerTraitTrainingSelectLevel, "10", "Player:TraitTrainingSelectLevel") rm.initRule(CategoryPlayer, PlayerTraitRaceSelectLevel, "10", "Player:TraitRaceSelectLevel") rm.initRule(CategoryPlayer, PlayerTraitCharacterSelectLevel, "10", "Player:TraitCharacterSelectLevel") rm.initRule(CategoryPlayer, PlayerStartHPBase, "40", "Player:StartHPBase") rm.initRule(CategoryPlayer, PlayerStartPowerBase, "45", "Player:StartPowerBase") rm.initRule(CategoryPlayer, PlayerStartHPLevelMod, "2.0", "Player:StartHPLevelMod") rm.initRule(CategoryPlayer, PlayerStartPowerLevelMod, "2.1", "Player:StartPowerLevelMod") rm.initRule(CategoryPlayer, PlayerAllowEquipCombat, "1", "Player:AllowPlayerEquipCombat") rm.initRule(CategoryPlayer, PlayerMaxTargetCommandDistance, "50.0", "Player:MaxTargetCommandDistance") rm.initRule(CategoryPlayer, PlayerMinSkillMultiplierValue, "30", "Player:MinSkillMultiplierValue") rm.initRule(CategoryPlayer, PlayerHarvestSkillUpMultiplier, "2.0", "Player:HarvestSkillUpMultiplier") rm.initRule(CategoryPlayer, PlayerMiniDingPercentage, "10", "Player:MiniDingPercentage") // PVP RULES rm.initRule(CategoryPVP, PVPAllowPVP, "0", "PVP:AllowPVP") rm.initRule(CategoryPVP, PVPLevelRange, "4", "PVP:LevelRange") rm.initRule(CategoryPVP, PVPInvisPlayerDiscoveryRange, "20", "PVP:InvisPlayerDiscoveryRange") rm.initRule(CategoryPVP, PVPMitigationModByLevel, "25", "PVP:PVPMitigationModByLevel") rm.initRule(CategoryPVP, PVPType, "0", "PVP:PVPType") // COMBAT RULES rm.initRule(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") rm.initRule(CategoryCombat, CombatDeathExperienceDebt, "50.00", "Combat:DeathExperienceDebt") rm.initRule(CategoryCombat, CombatPVPDeathExperienceDebt, "25.00", "Combat:PVPDeathExperienceDebt") rm.initRule(CategoryCombat, CombatGroupExperienceDebt, "0", "Combat:GroupExperienceDebt") rm.initRule(CategoryCombat, CombatExperienceToDebt, "50.00", "Combat:ExperienceToDebt") rm.initRule(CategoryCombat, CombatExperienceDebtRecoveryPercent, "5.00", "Combat:ExperienceDebtRecoveryPercent") rm.initRule(CategoryCombat, CombatExperienceDebtRecoveryPeriod, "600", "Combat:ExperienceDebtRecoveryPeriod") rm.initRule(CategoryCombat, CombatEnableSpiritShards, "1", "Combat:EnableSpiritShards") rm.initRule(CategoryCombat, CombatSpiritShardSpawnScript, "SpawnScripts/Generic/SpiritShard.lua", "Combat:SpiritShardSpawnScript") rm.initRule(CategoryCombat, CombatShardDebtRecoveryPercent, "25.00", "Combat:ShardDebtRecoveryPercent") rm.initRule(CategoryCombat, CombatShardRecoveryByRadius, "1", "Combat:ShardRecoveryByRadius") rm.initRule(CategoryCombat, CombatShardLifetime, "86400", "Combat:ShardLifetime") rm.initRule(CategoryCombat, CombatEffectiveMitigationCapLevel, "80", "Combat:EffectiveMitigationCapLevel") rm.initRule(CategoryCombat, CombatCalculatedMitigationCapLevel, "100", "Combat:CalculatedMitigationCapLevel") rm.initRule(CategoryCombat, CombatMitigationLevelEffectivenessMax, "1.5", "Combat:MitigationLevelEffectivenessMax") rm.initRule(CategoryCombat, CombatMitigationLevelEffectivenessMin, ".5", "Combat:MitigationLevelEffectivenessMin") rm.initRule(CategoryCombat, CombatMaxMitigationAllowed, ".75", "Combat:MaxMitigationAllowed") rm.initRule(CategoryCombat, CombatMaxMitigationAllowedPVP, ".75", "Combat:MaxMitigationAllowedPVP") rm.initRule(CategoryCombat, CombatStrengthNPC, "10", "Combat:StrengthNPC") rm.initRule(CategoryCombat, CombatStrengthOther, "25", "Combat:StrengthOther") rm.initRule(CategoryCombat, CombatMaxSkillBonusByLevel, "1.5", "Combat:MaxSkillBonusByLevel") rm.initRule(CategoryCombat, CombatLockedEncounterNoAttack, "1", "Combat:LockedEncounterNoAttack") rm.initRule(CategoryCombat, CombatMaxChaseDistance, "0.0", "Combat:MaxChaseDistance") // SPAWN RULES rm.initRule(CategorySpawn, SpawnSpeedMultiplier, "300", "Spawn:SpeedMultiplier") rm.initRule(CategorySpawn, SpawnClassicRegen, "0", "Spawn:ClassicRegen") rm.initRule(CategorySpawn, SpawnHailMovementPause, "5000", "Spawn:HailMovementPause") rm.initRule(CategorySpawn, SpawnHailDistance, "5", "Spawn:HailDistance") rm.initRule(CategorySpawn, SpawnUseHardCodeWaterModelType, "1", "Spawn:UseHardCodeWaterModelType") rm.initRule(CategorySpawn, SpawnUseHardCodeFlyingModelType, "1", "Spawn:UseHardCodeFlyingModelType") // UI RULES rm.initRule(CategoryUI, UIMaxWhoResults, "20", "UI:MaxWhoResults") rm.initRule(CategoryUI, UIMaxWhoOverrideStatus, "200", "UI:MaxWhoOverrideStatus") // WORLD RULES rm.initRule(CategoryWorld, WorldDefaultStartingZoneID, "1", "World:DefaultStartingZoneID") rm.initRule(CategoryWorld, WorldEnablePOIDiscovery, "0", "World:EnablePOIDiscovery") rm.initRule(CategoryWorld, WorldGamblingTokenItemID, "2", "World:GamblingTokenItemID") rm.initRule(CategoryWorld, WorldGuildAutoJoin, "0", "World:GuildAutoJoin") rm.initRule(CategoryWorld, WorldGuildAutoJoinID, "1", "World:GuildAutoJoinID") rm.initRule(CategoryWorld, WorldGuildAutoJoinDefaultRankID, "7", "World:GuildAutoJoinDefaultRankID") rm.initRule(CategoryWorld, PlayerMaxPlayers, "-1", "World:MaxPlayers") rm.initRule(CategoryWorld, PlayerMaxPlayersOverrideStatus, "100", "World:MaxPlayersOverrideStatus") rm.initRule(CategoryWorld, WorldServerLocked, "0", "World:ServerLocked") rm.initRule(CategoryWorld, WorldServerLockedOverrideStatus, "10", "World:ServerLockedOverrideStatus") rm.initRule(CategoryWorld, WorldSyncZonesWithLogin, "1", "World:SyncZonesWithLogin") rm.initRule(CategoryWorld, WorldSyncEquipWithLogin, "1", "World:SyncEquipWithLogin") rm.initRule(CategoryWorld, WorldUseBannedIPsTable, "0", "World:UseBannedIPsTable") rm.initRule(CategoryWorld, WorldLinkDeadTimer, "120000", "World:LinkDeadTimer") rm.initRule(CategoryWorld, WorldRemoveDisconnectedClientsTimer, "30000", "World:RemoveDisconnectedClientsTimer") rm.initRule(CategoryWorld, WorldPlayerCampTimer, "20", "World:PlayerCampTimer") rm.initRule(CategoryWorld, WorldGMCampTimer, "1", "World:GMCampTimer") rm.initRule(CategoryWorld, WorldAutoAdminPlayers, "0", "World:AutoAdminPlayers") rm.initRule(CategoryWorld, WorldAutoAdminGMs, "0", "World:AutoAdminGMs") rm.initRule(CategoryWorld, WorldAutoAdminStatusValue, "10", "World:AutoAdminStatusValue") rm.initRule(CategoryWorld, WorldDuskTime, "20:00", "World:DuskTime") rm.initRule(CategoryWorld, WorldDawnTime, "8:00", "World:DawnTime") rm.initRule(CategoryWorld, WorldThreadedLoad, "0", "World:ThreadedLoad") rm.initRule(CategoryWorld, WorldTradeskillSuccessChance, "87.0", "World:TradeskillSuccessChance") rm.initRule(CategoryWorld, WorldTradeskillCritSuccessChance, "2.0", "World:TradeskillCritSuccessChance") rm.initRule(CategoryWorld, WorldTradeskillFailChance, "10.0", "World:TradeskillFailChance") rm.initRule(CategoryWorld, WorldTradeskillCritFailChance, "1.0", "World:TradeskillCritFailChance") rm.initRule(CategoryWorld, WorldTradeskillEventChance, "15.0", "World:TradeskillEventChance") rm.initRule(CategoryWorld, WorldEditorURL, "www.eq2emulator.net", "World:EditorURL") rm.initRule(CategoryWorld, WorldEditorIncludeID, "0", "World:EditorIncludeID") rm.initRule(CategoryWorld, WorldEditorOfficialServer, "0", "World:EditorOfficialServer") rm.initRule(CategoryWorld, WorldSavePaperdollImage, "1", "World:SavePaperdollImage") rm.initRule(CategoryWorld, WorldSaveHeadshotImage, "1", "World:SaveHeadshotImage") rm.initRule(CategoryWorld, WorldSendPaperdollImagesToLogin, "1", "World:SendPaperdollImagesToLogin") rm.initRule(CategoryWorld, WorldTreasureChestDisabled, "0", "World:TreasureChestDisabled") rm.initRule(CategoryWorld, WorldStartingZoneLanguages, "0", "World:StartingZoneLanguages") rm.initRule(CategoryWorld, WorldStartingZoneRuleFlag, "0", "World:StartingZoneRuleFlag") rm.initRule(CategoryWorld, WorldEnforceRacialAlignment, "1", "World:EnforceRacialAlignment") rm.initRule(CategoryWorld, WorldMemoryCacheZoneMaps, "0", "World:MemoryCacheZoneMaps") rm.initRule(CategoryWorld, WorldAutoLockEncounter, "0", "World:AutoLockEncounter") rm.initRule(CategoryWorld, WorldDisplayItemTiers, "1", "World:DisplayItemTiers") rm.initRule(CategoryWorld, WorldLoreAndLegendAccept, "0", "World:LoreAndLegendAccept") // ZONE RULES rm.initRule(CategoryZone, PlayerMaxPlayers, "100", "Zone:MaxPlayers") rm.initRule(CategoryZone, ZoneMinLevelOverrideStatus, "1", "Zone:MinZoneLevelOverrideStatus") rm.initRule(CategoryZone, ZoneMinAccessOverrideStatus, "100", "Zone:MinZoneAccessOverrideStatus") rm.initRule(CategoryZone, ZoneWeatherEnabled, "1", "Zone:WeatherEnabled") rm.initRule(CategoryZone, ZoneWeatherType, "0", "Zone:WeatherType") rm.initRule(CategoryZone, ZoneMinWeatherSeverity, "0.0", "Zone:MinWeatherSeverity") rm.initRule(CategoryZone, ZoneMaxWeatherSeverity, "1.0", "Zone:MaxWeatherSeverity") rm.initRule(CategoryZone, ZoneWeatherChangeFrequency, "300", "Zone:WeatherChangeFrequency") rm.initRule(CategoryZone, ZoneWeatherChangePerInterval, "0.02", "Zone:WeatherChangePerInterval") rm.initRule(CategoryZone, ZoneWeatherChangeChance, "20", "Zone:WeatherChangeChance") rm.initRule(CategoryZone, ZoneWeatherDynamicMaxOffset, "0.08", "Zone:WeatherDynamicMaxOffset") rm.initRule(CategoryZone, ZoneSpawnUpdateTimer, "50", "Zone:SpawnUpdateTimer") rm.initRule(CategoryZone, ZoneCheckAttackNPC, "2000", "Zone:CheckAttackNPC") rm.initRule(CategoryZone, ZoneCheckAttackPlayer, "2000", "Zone:CheckAttackPlayer") rm.initRule(CategoryZone, ZoneHOTime, "10.0", "Zone:HOTime") rm.initRule(CategoryZone, ZoneRegenTimer, "6000", "Zone:RegenTimer") rm.initRule(CategoryZone, ZoneClientSaveTimer, "60000", "Zone:ClientSaveTimer") rm.initRule(CategoryZone, ZoneShutdownDelayTimer, "120000", "Zone:ShutdownDelayTimer") rm.initRule(CategoryZone, ZoneWeatherTimer, "60000", "Zone:WeatherTimer") rm.initRule(CategoryZone, ZoneSpawnDeleteTimer, "30000", "Zone:SpawnDeleteTimer") rm.initRule(CategoryZone, ZoneUseMapUnderworldCoords, "1", "Zone:UseMapUnderworldCoords") rm.initRule(CategoryZone, ZoneMapUnderworldCoordOffset, "-200.0", "Zone:MapUnderworldCoordOffset") rm.initRule(CategoryZone, ZoneSharedMaxPlayers, "30", "Zone:SharedZoneMaxPlayers") // LOOT RULES rm.initRule(CategoryLoot, LootRadius, "5.0", "Loot:LootRadius") rm.initRule(CategoryLoot, LootAutoDisarmChest, "1", "Loot:AutoDisarmChest") rm.initRule(CategoryLoot, LootChestTriggerRadiusGroup, "10.0", "Loot:ChestTriggerRadiusGroup") rm.initRule(CategoryLoot, LootChestUnlockedTimeDrop, "1200", "Loot:ChestUnlockedTimeDrop") rm.initRule(CategoryLoot, LootAllowChestUnlockByDropTime, "1", "Loot:AllowChestUnlockByDropTime") rm.initRule(CategoryLoot, LootChestUnlockedTimeTrap, "600", "Loot:ChestUnlockedTimeTrap") rm.initRule(CategoryLoot, LootAllowChestUnlockByTrapTime, "1", "Loot:AllowChestUnlockByTrapTime") rm.initRule(CategoryLoot, LootSkipGrayMob, "1", "Loot:SkipLootGrayMob") rm.initRule(CategoryLoot, LootDistributionTime, "120", "Loot:LootDistributionTime") // SPELLS RULES rm.initRule(CategorySpells, SpellsNoInterruptBaseChance, "50", "Spells:NoInterruptBaseChance") rm.initRule(CategorySpells, SpellsEnableFizzleSpells, "1", "Spells:EnableFizzleSpells") rm.initRule(CategorySpells, SpellsDefaultFizzleChance, "10.0", "Spells:DefaultFizzleChance") rm.initRule(CategorySpells, SpellsFizzleMaxSkill, "1.2", "Spells:FizzleMaxSkill") rm.initRule(CategorySpells, SpellsFizzleDefaultSkill, ".2", "Spells:FizzleDefaultSkill") rm.initRule(CategorySpells, SpellsEnableCrossZoneGroupBuffs, "0", "Spells:EnableCrossZoneGroupBuffs") rm.initRule(CategorySpells, SpellsEnableCrossZoneTargetBuffs, "0", "Spells:EnableCrossZoneTargetBuffs") rm.initRule(CategorySpells, SpellsPlayerSpellSaveStateWaitInterval, "100", "Spells:PlayerSpellSaveStateWaitInterval") rm.initRule(CategorySpells, SpellsPlayerSpellSaveStateCap, "1000", "Spells:PlayerSpellSaveStateCap") rm.initRule(CategorySpells, SpellsRequirePreviousTierScribe, "0", "Spells:RequirePreviousTierScribe") rm.initRule(CategorySpells, SpellsCureSpellID, "110003", "Spells:CureSpellID") rm.initRule(CategorySpells, SpellsCureCurseSpellID, "110004", "Spells:CureCurseSpellID") rm.initRule(CategorySpells, SpellsCureNoxiousSpellID, "110005", "Spells:CureNoxiousSpellID") rm.initRule(CategorySpells, SpellsCureMagicSpellID, "210006", "Spells:CureMagicSpellID") rm.initRule(CategorySpells, SpellsCureTraumaSpellID, "0", "Spells:CureTraumaSpellID") rm.initRule(CategorySpells, SpellsCureArcaneSpellID, "0", "Spells:CureArcaneSpellID") rm.initRule(CategorySpells, SpellsMinistrationSkillID, "366253016", "Spells:MinistrationSkillID") rm.initRule(CategorySpells, SpellsMinistrationPowerReductionMax, "15.0", "Spells:MinistrationPowerReductionMax") rm.initRule(CategorySpells, SpellsMinistrationPowerReductionSkill, "25", "Spells:MinistrationPowerReductionSkill") rm.initRule(CategorySpells, SpellsMasterSkillReduceSpellResist, "25", "Spells:MasterSkillReduceSpellResist") rm.initRule(CategorySpells, SpellsUseClassicSpellLevel, "0", "Spells:UseClassicSpellLevel") // EXPANSION RULES rm.initRule(CategoryExpansion, ExpansionGlobalFlag, "0", "Expansion:GlobalExpansionFlag") rm.initRule(CategoryExpansion, ExpansionHolidayFlag, "0", "Expansion:GlobalHolidayFlag") // DISCORD RULES rm.initRule(CategoryDiscord, DiscordEnabled, "0", "Discord:DiscordEnabled") rm.initRule(CategoryDiscord, DiscordWebhookURL, "None", "Discord:DiscordWebhookURL") rm.initRule(CategoryDiscord, DiscordBotToken, "None", "Discord:DiscordBotToken") rm.initRule(CategoryDiscord, DiscordChannel, "Discord", "Discord:DiscordChannel") rm.initRule(CategoryDiscord, DiscordListenChan, "0", "Discord:DiscordListenChan") rm.initialized = true log.Printf("[Rules] Initialized rule manager with %d categories", len(rm.rules)) } // initRule is a helper function equivalent to the RULE_INIT macro func (rm *RuleManager) initRule(category RuleCategory, ruleType RuleType, value string, combined string) { if rm.rules[category] == nil { rm.rules[category] = make(map[RuleType]*Rule) } rm.rules[category][ruleType] = NewRuleWithValues(category, ruleType, value, combined) } // Flush clears all rule sets and optionally reinitializes with defaults func (rm *RuleManager) Flush(reinit bool) { // Clear default rules rm.rulesMutex.Lock() rm.rules = make(map[RuleCategory]map[RuleType]*Rule) rm.rulesMutex.Unlock() // Clear rule sets rm.ClearRuleSets() rm.ClearZoneRuleSets() if reinit { rm.Init() } rm.initialized = reinit } // LoadCodedDefaultsIntoRuleSet loads the coded default rules into a rule set func (rm *RuleManager) LoadCodedDefaultsIntoRuleSet(ruleSet *RuleSet) { if ruleSet == nil { return } rm.rulesMutex.RLock() defer rm.rulesMutex.RUnlock() for _, typeMap := range rm.rules { for _, rule := range typeMap { ruleSet.AddRule(NewRuleFromRule(rule)) } } } // AddRuleSet adds a rule set to the manager func (rm *RuleManager) AddRuleSet(ruleSet *RuleSet) bool { if ruleSet == nil { return false } id := ruleSet.GetID() rm.ruleSetsMutex.Lock() defer rm.ruleSetsMutex.Unlock() if _, exists := rm.ruleSets[id]; exists { return false // Rule set with this ID already exists } rm.ruleSets[id] = ruleSet rm.stats.IncrementRuleSetOperations() // Update stats rm.stats.mutex.Lock() rm.stats.TotalRuleSets = int32(len(rm.ruleSets)) totalRules := int32(0) for _, rs := range rm.ruleSets { totalRules += int32(rs.Size()) } rm.stats.TotalRules = totalRules rm.stats.mutex.Unlock() return true } // GetNumRuleSets returns the number of rule sets func (rm *RuleManager) GetNumRuleSets() int32 { rm.ruleSetsMutex.RLock() defer rm.ruleSetsMutex.RUnlock() return int32(len(rm.ruleSets)) } // ClearRuleSets removes all rule sets func (rm *RuleManager) ClearRuleSets() { rm.ruleSetsMutex.Lock() defer rm.ruleSetsMutex.Unlock() rm.ruleSets = make(map[int32]*RuleSet) // Update stats rm.stats.mutex.Lock() rm.stats.TotalRuleSets = 0 rm.stats.TotalRules = 0 rm.stats.mutex.Unlock() } // SetGlobalRuleSet sets the global rule set by copying from an existing rule set func (rm *RuleManager) SetGlobalRuleSet(ruleSetID int32) bool { rm.ruleSetsMutex.RLock() sourceRuleSet, exists := rm.ruleSets[ruleSetID] rm.ruleSetsMutex.RUnlock() if !exists { return false } rm.globalRuleSetMutex.Lock() defer rm.globalRuleSetMutex.Unlock() rm.globalRuleSet.CopyRulesInto(sourceRuleSet) // Update stats rm.stats.mutex.Lock() rm.stats.GlobalRuleSetID = ruleSetID rm.stats.mutex.Unlock() return true } // GetGlobalRule gets a rule from the global rule set by category and type func (rm *RuleManager) GetGlobalRule(category RuleCategory, ruleType RuleType) *Rule { rm.globalRuleSetMutex.RLock() defer rm.globalRuleSetMutex.RUnlock() rule := rm.globalRuleSet.GetRule(category, ruleType) if rule != nil { rm.stats.IncrementRuleGetOperations() log.Printf("[Rules] Rule: %s, Value: %s", rule.GetCombined(), rule.GetValue()) return rule } // Return blank rule if not found (matching C++ behavior) return rm.blankRule } // GetGlobalRuleByName gets a rule from the global rule set by name func (rm *RuleManager) GetGlobalRuleByName(categoryName string, typeName string) *Rule { rm.globalRuleSetMutex.RLock() defer rm.globalRuleSetMutex.RUnlock() rule := rm.globalRuleSet.GetRuleByName(categoryName, typeName) if rule != nil { rm.stats.IncrementRuleGetOperations() return rule } return rm.blankRule } // SetZoneRuleSet sets a zone-specific rule set func (rm *RuleManager) SetZoneRuleSet(zoneID int32, ruleSetID int32) bool { rm.ruleSetsMutex.RLock() ruleSet, exists := rm.ruleSets[ruleSetID] rm.ruleSetsMutex.RUnlock() if !exists { return false } rm.zoneRuleSetsMutex.Lock() defer rm.zoneRuleSetsMutex.Unlock() rm.zoneRuleSets[zoneID] = ruleSet // Update stats rm.stats.mutex.Lock() rm.stats.ZoneRuleSets = int32(len(rm.zoneRuleSets)) rm.stats.mutex.Unlock() return true } // GetZoneRule gets a rule for a specific zone, falling back to global rules func (rm *RuleManager) GetZoneRule(zoneID int32, category RuleCategory, ruleType RuleType) *Rule { var rule *Rule // First try to get the zone-specific rule if zoneID != 0 { rm.zoneRuleSetsMutex.RLock() if zoneRuleSet, exists := rm.zoneRuleSets[zoneID]; exists { rule = zoneRuleSet.GetRule(category, ruleType) } rm.zoneRuleSetsMutex.RUnlock() } // Fall back to global rule if zone rule not found if rule == nil { rule = rm.GetGlobalRule(category, ruleType) } return rule } // ClearZoneRuleSets removes all zone-specific rule sets func (rm *RuleManager) ClearZoneRuleSets() { rm.zoneRuleSetsMutex.Lock() defer rm.zoneRuleSetsMutex.Unlock() rm.zoneRuleSets = make(map[int32]*RuleSet) // Update stats rm.stats.mutex.Lock() rm.stats.ZoneRuleSets = 0 rm.stats.mutex.Unlock() } // GetBlankRule returns the blank rule for missing rules func (rm *RuleManager) GetBlankRule() *Rule { return rm.blankRule } // GetGlobalRuleSet returns the global rule set func (rm *RuleManager) GetGlobalRuleSet() *RuleSet { rm.globalRuleSetMutex.RLock() defer rm.globalRuleSetMutex.RUnlock() return rm.globalRuleSet } // GetRules returns the default rules map func (rm *RuleManager) GetRules() map[RuleCategory]map[RuleType]*Rule { rm.rulesMutex.RLock() defer rm.rulesMutex.RUnlock() // Return a deep copy to prevent external modification rulesCopy := make(map[RuleCategory]map[RuleType]*Rule) for category, typeMap := range rm.rules { rulesCopy[category] = make(map[RuleType]*Rule) for ruleType, rule := range typeMap { rulesCopy[category][ruleType] = NewRuleFromRule(rule) } } return rulesCopy } // GetRuleSet returns a rule set by ID func (rm *RuleManager) GetRuleSet(id int32) *RuleSet { rm.ruleSetsMutex.RLock() defer rm.ruleSetsMutex.RUnlock() if ruleSet, exists := rm.ruleSets[id]; exists { return ruleSet } return nil } // GetAllRuleSets returns all rule sets func (rm *RuleManager) GetAllRuleSets() map[int32]*RuleSet { rm.ruleSetsMutex.RLock() defer rm.ruleSetsMutex.RUnlock() // Return a copy of the map ruleSetsCopy := make(map[int32]*RuleSet) for id, ruleSet := range rm.ruleSets { ruleSetsCopy[id] = ruleSet } return ruleSetsCopy } // GetStats returns current rule manager statistics func (rm *RuleManager) GetStats() RuleManagerStats { return rm.stats.GetSnapshot() } // ResetStats resets all statistics counters func (rm *RuleManager) ResetStats() { rm.stats.Reset() } // IsInitialized returns whether the rule manager has been initialized func (rm *RuleManager) IsInitialized() bool { return rm.initialized } // ValidateRule validates a rule's category, type, and value func (rm *RuleManager) ValidateRule(category RuleCategory, ruleType RuleType, value string) error { if len(value) > MaxRuleValueLength { return ErrRuleValueTooLong } // Additional validation can be added here for specific rule types return nil } // GetRuleInfo returns information about a specific rule func (rm *RuleManager) GetRuleInfo(category RuleCategory, ruleType RuleType) string { rule := rm.GetGlobalRule(category, ruleType) if rule == nil || !rule.IsValid() { return "Rule not found" } return fmt.Sprintf("Rule: %s = %s", rule.GetCombined(), rule.GetValue()) } // String returns a string representation of the rule manager func (rm *RuleManager) String() string { stats := rm.GetStats() return fmt.Sprintf("RuleManager{RuleSets: %d, Rules: %d, GlobalID: %d, ZoneRuleSets: %d}", stats.TotalRuleSets, stats.TotalRules, stats.GlobalRuleSetID, stats.ZoneRuleSets) }