580 lines
26 KiB
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)
|
|
} |