using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Events; using Kryz.CharacterStats; namespace Kryz.CharacterStats.Examples { public class CharacterStats : MonoBehaviour { [Header("Runtime Stats (Auto-Generated)")] [SerializeField] private bool showDebugStats = false; // Main stats dictionary - all stats live here private Dictionary allStats = new Dictionary(); // Compatibility dictionaries for existing systems public Dictionary primaryStatsDictionary = new Dictionary(); public Dictionary secondaryStatsDictionary = new Dictionary(); // Events public UnityEvent onUpdateStatValues = new UnityEvent(); public UnityEvent onAllStatsUpdated = new UnityEvent(); // Quick access properties for primary stats (for compatibility) public CharacterStat Cunning => GetStat("cunning"); public CharacterStat Flow => GetStat("flow"); public CharacterStat Presence => GetStat("presence"); protected virtual void Awake() { InitializeStatsFromRegistry(); onUpdateStatValues.AddListener(UpdateSecondaryStatsBasedOnPrimaryStats); } private void InitializeStatsFromRegistry() { // Clear existing stats allStats.Clear(); primaryStatsDictionary.Clear(); secondaryStatsDictionary.Clear(); // Get all stat definitions from registry var statDefinitions = StatRegistry.Instance.GetAllStats(); if (statDefinitions.Length == 0) { Debug.LogWarning($"CharacterStats on {gameObject.name}: No stat definitions found in StatRegistry!"); return; } // Create CharacterStat for each definition foreach (var statDef in statDefinitions) { var characterStat = new CharacterStat(statDef.DefaultBaseValue); characterStat.statDefinition = statDef; // Store reference to definition allStats[statDef.StatKey] = characterStat; // Populate compatibility dictionaries string lowerKey = statDef.StatKey.ToLower(); if (statDef.IsPrimary) { primaryStatsDictionary[lowerKey] = characterStat; } else { secondaryStatsDictionary[lowerKey] = characterStat; } if (showDebugStats) { Debug.Log($"Initialized stat: {statDef.StatKey} (Base: {statDef.DefaultBaseValue}, Category: {statDef.Category})"); } } Debug.Log($"CharacterStats on {gameObject.name}: Initialized {allStats.Count} stats"); } // Main stat access method public CharacterStat GetStat(string statKey) { if (allStats.TryGetValue(statKey, out CharacterStat stat)) { return stat; } Debug.LogWarning($"CharacterStats: Stat '{statKey}' not found!"); return null; } // Safe stat access that won't return null public CharacterStat GetStatSafe(string statKey) { var stat = GetStat(statKey); if (stat == null) { // Return a dummy stat to prevent null reference exceptions return new CharacterStat(0f); } return stat; } // Get stats by category public CharacterStat[] GetStatsByCategory(StatCategory category) { return allStats.Values .Where(stat => stat.statDefinition.Category == category) .ToArray(); } // Get all primary stats public CharacterStat[] GetPrimaryStats() { return allStats.Values .Where(stat => stat.statDefinition.IsPrimary) .ToArray(); } public CharacterStat[] GetAllStats() { return allStats.Values.ToArray(); } // Get all UI-visible stats public CharacterStat[] GetUIVisibleStats() { return allStats.Values .Where(stat => stat.statDefinition.ShowInUI) .ToArray(); } public void IncreaseAllStatPoints(int amount) { onUpdateStatValues.Invoke(); } public void UpdateSecondaryStatsBasedOnPrimaryStats() { // Get primary stats var cunning = GetStat("cunning"); var flow = GetStat("flow"); var presence = GetStat("presence"); if (cunning == null || flow == null || presence == null) { Debug.LogError("CharacterStats: Primary stats not found! Make sure cunning, flow, and presence StatDefinitions exist."); return; } // Remove previous modifiers from all affected stats RemovePrimaryStatInfluences(); // Apply Cunning influences ApplyStatInfluence("critChance", cunning.Value * GameConstants.CharacterStatsBalancing.CritChanceIncreasePerCunning, GameConstants.ObjectSources.CunningSource); ApplyStatInfluence("critDamage", cunning.Value * GameConstants.CharacterStatsBalancing.CritDamageIncreasePerCunning, GameConstants.ObjectSources.CunningSource); ApplyStatInfluence("movementSpeed", cunning.Value * GameConstants.CharacterStatsBalancing.MovementSpeedIncreasePerCunning, GameConstants.ObjectSources.CunningSource); // Apply Flow influences ApplyStatInfluence("maxMana", flow.Value * GameConstants.CharacterStatsBalancing.MaxManaIncreasePerFlow, GameConstants.ObjectSources.FlowSource); ApplyStatInfluence("manaRegen", flow.Value * GameConstants.CharacterStatsBalancing.ManaRegenIncreasePerFlow, GameConstants.ObjectSources.FlowSource); ApplyStatInfluence("cooldownReduction", flow.Value * GameConstants.CharacterStatsBalancing.CooldownReductionIncreasePerFlow, GameConstants.ObjectSources.FlowSource); ApplyStatInfluence("attackSpeed", flow.Value * GameConstants.CharacterStatsBalancing.AttackSpeedIncreasePerFlow, GameConstants.ObjectSources.FlowSource); // Apply Presence influences ApplyStatInfluence("areaEffectiveness", presence.Value * GameConstants.CharacterStatsBalancing.AreaEffectivenessIncreasePerPresence, GameConstants.ObjectSources.PresenceSource); ApplyStatInfluence("reputationGainIncrease", presence.Value * GameConstants.CharacterStatsBalancing.ReputationGainIncreasePerPresence, GameConstants.ObjectSources.PresenceSource); ApplyStatInfluence("goldCostReduction", presence.Value * GameConstants.CharacterStatsBalancing.GoldCostReductionPerPresence, GameConstants.ObjectSources.PresenceSource); ApplyStatInfluence("auraPower", presence.Value * GameConstants.CharacterStatsBalancing.AuraPowerPerPresence, GameConstants.ObjectSources.PresenceSource); Debug.Log(this.gameObject.name + " has relative power level of: " + GetRelativePowerLevel()); onAllStatsUpdated.Invoke(); } private void RemovePrimaryStatInfluences() { // Remove Cunning influences RemoveStatInfluence("critChance", GameConstants.ObjectSources.CunningSource); RemoveStatInfluence("critDamage", GameConstants.ObjectSources.CunningSource); RemoveStatInfluence("movementSpeed", GameConstants.ObjectSources.CunningSource); // Remove Flow influences RemoveStatInfluence("maxMana", GameConstants.ObjectSources.FlowSource); RemoveStatInfluence("manaRegen", GameConstants.ObjectSources.FlowSource); RemoveStatInfluence("cooldownReduction", GameConstants.ObjectSources.FlowSource); RemoveStatInfluence("attackSpeed", GameConstants.ObjectSources.FlowSource); // Remove Presence influences RemoveStatInfluence("areaEffectiveness", GameConstants.ObjectSources.PresenceSource); RemoveStatInfluence("reputationGainIncrease", GameConstants.ObjectSources.PresenceSource); RemoveStatInfluence("goldCostReduction", GameConstants.ObjectSources.PresenceSource); RemoveStatInfluence("auraPower", GameConstants.ObjectSources.PresenceSource); } private void ApplyStatInfluence(string statKey, float value, object source) { var stat = GetStat(statKey); if (stat != null) { stat.AddModifier(new StatModifier(value, StatModType.Flat, source)); } else { Debug.LogWarning($"CharacterStats: Could not apply influence to stat '{statKey}' - stat not found!"); } } private void RemoveStatInfluence(string statKey, object source) { var stat = GetStat(statKey); stat?.RemoveAllModifiersFromSource(source); } public float GetRelativePowerLevel() { var attackDamage = GetStatSafe("attackDamage"); var spellDamage = GetStatSafe("spellDamage"); var critDamage = GetStatSafe("critDamage"); var critChance = GetStatSafe("critChance"); var maxHealth = GetStatSafe("maxHealth"); var armor = GetStatSafe("armor"); var magicResistance = GetStatSafe("magicResistance"); return attackDamage.Value * 1.1f + spellDamage.Value * 1.1f + (critDamage.Value * (critChance.Value / 100f)) + maxHealth.Value * 1f + armor.Value * 1f + magicResistance.Value * 1f; } // Debug/Editor methods [ContextMenu("Log All Stats")] private void LogAllStats() { Debug.Log($"=== CharacterStats on {gameObject.name} ==="); foreach (var kvp in allStats) { var stat = kvp.Value; var def = stat.statDefinition; Debug.Log($"{def.DisplayName} ({kvp.Key}): {stat.Value} (Base: {stat.BaseValue}, Category: {def.Category})"); } } [ContextMenu("Refresh Stats From Registry")] private void RefreshStatsFromRegistry() { InitializeStatsFromRegistry(); onUpdateStatValues.Invoke(); } } }