eq2go/internal/rules/manager.go

580 lines
26 KiB
Go

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)
}