From 7f0d4ec7f32e1050a68987c48b0b82045242de76 Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Thu, 25 Sep 2025 21:04:11 +0100 Subject: [PATCH] Stat & equipment definition continued (WIP) --- Assets/Character Stats/CharacterStat.cs | 2 +- .../Scripts/CharacterStats.cs | 361 ++++++----- .../Scripts/EquippableItem.cs | 247 +------- .../Scripts/EquippableItemInstance.cs | 563 ++++++------------ .../Items & Inventory/Scripts/Item.cs | 10 +- .../Scripts/PlayerCharacterStats.cs | 9 +- .../Items & Inventory/Scripts/StatPanel.cs | 43 +- Assets/Scripts/Drops/DropTable.cs | 15 +- 8 files changed, 440 insertions(+), 810 deletions(-) diff --git a/Assets/Character Stats/CharacterStat.cs b/Assets/Character Stats/CharacterStat.cs index 5c5019c2..8e880012 100644 --- a/Assets/Character Stats/CharacterStat.cs +++ b/Assets/Character Stats/CharacterStat.cs @@ -118,7 +118,7 @@ namespace Kryz.CharacterStats public class CharacterStat { public GameTag statTag; - public CharacterStatType statType; + public StatDefinition statDefinition; public float BaseValue; diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs index 3fec893a..ae522355 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs @@ -1,197 +1,252 @@ -using System.Collections; using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.Events; +using Kryz.CharacterStats; namespace Kryz.CharacterStats.Examples { - public class CharacterStats : MonoBehaviour { - [Header("---------------------------------------------------------------------------------------------")] - [Header("Primary Stats:")] - public CharacterStat Cunning; - public CharacterStat Flow; - public CharacterStat Presence; + [Header("Runtime Stats (Auto-Generated)")] + [SerializeField] private bool showDebugStats = false; - //Secondary - [Space] - [Header("---------------------------------------------------------------------------------------------")] - [Header("Offensive Stats:")] - [Space] - public CharacterStat AttackDamage; - public CharacterStat SpellDamage; - - public CharacterStat AttackSpeed; - - - public CharacterStat CritChance; - public CharacterStat CritDamage; - - public CharacterStat AuraPower; - - [Space] - [Header("---------------------------------------------------------------------------------------------")] - [Header("Resource Stats:")] - [Space] - public CharacterStat MaxHealth; - public CharacterStat HealthRegen; - public CharacterStat MaxMana; - public CharacterStat ManaRegen; - - [Space] - [Header("---------------------------------------------------------------------------------------------")] - [Header("Defensive Stats:")] - [Space] - public CharacterStat Armor; - public CharacterStat MagicResistance; - public CharacterStat DodgeChance; - public CharacterStat BlockChance; - public CharacterStat BlockEffectiveness; - - [Space] - [Header("---------------------------------------------------------------------------------------------")] - [Header("Misc Stats:")] - [Space] - public CharacterStat AreaEffectiveness; - public CharacterStat CooldownReduction; - public CharacterStat MovementSpeed; - public CharacterStat ReputationGainIncrease; - public CharacterStat GoldCostReduction; - - - //Awakening Related - //public CharacterStat + // 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() { - Cunning.statType = CharacterStatType.Cunning; - Flow.statType = CharacterStatType.Flow; - Presence.statType = CharacterStatType.Presence; - - AttackDamage.statType = CharacterStatType.AttackDamage; - SpellDamage.statType = CharacterStatType.SpellDamage; - AttackSpeed.statType = CharacterStatType.AttackSpeed; - CritChance.statType = CharacterStatType.CritChance; - CritDamage.statType = CharacterStatType.CritDamage; - AuraPower.statType = CharacterStatType.AuraPower; - - MaxHealth.statType = CharacterStatType.MaxHealth; - HealthRegen.statType = CharacterStatType.HealthRegen; - MaxMana.statType = CharacterStatType.MaxMana; - ManaRegen.statType = CharacterStatType.ManaRegen; - - Armor.statType = CharacterStatType.Armor; - MagicResistance.statType = CharacterStatType.MagicResistance; - DodgeChance.statType = CharacterStatType.DodgeChance; - BlockChance.statType = CharacterStatType.BlockChance; - BlockEffectiveness.statType = CharacterStatType.BlockEffectiveness; - - AreaEffectiveness.statType = CharacterStatType.AreaEffectiveness; - CooldownReduction.statType = CharacterStatType.CooldownReduction; - MovementSpeed.statType = CharacterStatType.MovementSpeed; - - ReputationGainIncrease.statType = CharacterStatType.ReputationGainIncrease; - GoldCostReduction.statType = CharacterStatType.GoldCostReduction; - - - primaryStatsDictionary.Add(nameof(Cunning).ToLower(), Cunning); - primaryStatsDictionary.Add(nameof(Flow).ToLower(), Flow); - primaryStatsDictionary.Add(nameof(Presence).ToLower(), Presence); - - - - secondaryStatsDictionary.Add(nameof(AttackDamage).ToLower(), AttackDamage); - secondaryStatsDictionary.Add(nameof(SpellDamage).ToLower(), SpellDamage); - secondaryStatsDictionary.Add(nameof(AttackSpeed).ToLower(), AttackSpeed); - - secondaryStatsDictionary.Add(nameof(CritChance).ToLower(), CritChance); - secondaryStatsDictionary.Add(nameof(CritDamage).ToLower(), CritDamage); - secondaryStatsDictionary.Add(nameof(AuraPower).ToLower(), AuraPower); - - secondaryStatsDictionary.Add(nameof(HealthRegen).ToLower(), HealthRegen); - secondaryStatsDictionary.Add(nameof(ManaRegen).ToLower(), ManaRegen); - secondaryStatsDictionary.Add(nameof(MaxHealth).ToLower(), MaxHealth); - secondaryStatsDictionary.Add(nameof(MaxMana).ToLower(), MaxMana); - - secondaryStatsDictionary.Add(nameof(Armor).ToLower(), Armor); - secondaryStatsDictionary.Add(nameof(MagicResistance).ToLower(), MagicResistance); - secondaryStatsDictionary.Add(nameof(DodgeChance).ToLower(), DodgeChance); - secondaryStatsDictionary.Add(nameof(BlockChance).ToLower(), BlockChance); - secondaryStatsDictionary.Add(nameof(BlockEffectiveness).ToLower(), BlockEffectiveness); - - secondaryStatsDictionary.Add(nameof(AreaEffectiveness).ToLower(), AreaEffectiveness); - secondaryStatsDictionary.Add(nameof(CooldownReduction).ToLower(), CooldownReduction); - secondaryStatsDictionary.Add(nameof(MovementSpeed).ToLower(), MovementSpeed); - secondaryStatsDictionary.Add(nameof(ReputationGainIncrease).ToLower(), ReputationGainIncrease); - secondaryStatsDictionary.Add(nameof(GoldCostReduction).ToLower(), GoldCostReduction); - + 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() { - //Remove previous - CritChance.RemoveAllModifiersFromSource(GameConstants.ObjectSources.CunningSource); - CritDamage.RemoveAllModifiersFromSource(GameConstants.ObjectSources.CunningSource); - MovementSpeed.RemoveAllModifiersFromSource(GameConstants.ObjectSources.CunningSource); + // Get primary stats + var cunning = GetStat("cunning"); + var flow = GetStat("flow"); + var presence = GetStat("presence"); - //HealthRegen.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); - MaxMana.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); - ManaRegen.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); - CooldownReduction.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); - AttackSpeed.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); - //ResourceCostReduction.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); + if (cunning == null || flow == null || presence == null) + { + Debug.LogError("CharacterStats: Primary stats not found! Make sure cunning, flow, and presence StatDefinitions exist."); + return; + } - AreaEffectiveness.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource); - ReputationGainIncrease.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource); - GoldCostReduction.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource); - AuraPower.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource); + // Remove previous modifiers from all affected stats + RemovePrimaryStatInfluences(); - //Add refreshed values - CritChance.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatsBalancing.CritChanceIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); - CritDamage.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatsBalancing.CritDamageIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); - //DodgeChance.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatInfluence.DodgeChanceIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); - MovementSpeed.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatsBalancing.MovementSpeedIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); - - //HealthRegen.AddModifier(new StatModifier(Flow.Value * GameConstants.CharacterStatInfluence.HealthRegenIncreasePerFlow, StatModType.Flat, GameConstants.ObjectSources.FlowSource)); - MaxMana.AddModifier(new StatModifier(Flow.Value * GameConstants.CharacterStatsBalancing.MaxManaIncreasePerFlow, StatModType.Flat, GameConstants.ObjectSources.FlowSource)); - ManaRegen.AddModifier(new StatModifier(Flow.Value * GameConstants.CharacterStatsBalancing.ManaRegenIncreasePerFlow, StatModType.Flat, GameConstants.ObjectSources.FlowSource)); - CooldownReduction.AddModifier(new StatModifier(Flow.Value * GameConstants.CharacterStatsBalancing.CooldownReductionIncreasePerFlow, StatModType.Flat, GameConstants.ObjectSources.FlowSource)); - AttackSpeed.AddModifier(new StatModifier(Flow.Value * GameConstants.CharacterStatsBalancing.AttackSpeedIncreasePerFlow, StatModType.Flat, GameConstants.ObjectSources.FlowSource)); - //ResourceCostReduction.AddModifier(new StatModifier(Flow.Value * GameConstants.CharacterStatInfluence.ResourceCostReductionPerFlow, StatModType.Flat, GameConstants.ObjectSources.FlowSource)); - - AreaEffectiveness.AddModifier(new StatModifier(Presence.Value * GameConstants.CharacterStatsBalancing.AreaEffectivenessIncreasePerPresence, StatModType.Flat, GameConstants.ObjectSources.PresenceSource)); - - ReputationGainIncrease.AddModifier(new StatModifier(Presence.Value * GameConstants.CharacterStatsBalancing.ReputationGainIncreasePerPresence, StatModType.Flat, GameConstants.ObjectSources.PresenceSource)); - GoldCostReduction.AddModifier(new StatModifier(Presence.Value * GameConstants.CharacterStatsBalancing.GoldCostReductionPerPresence, StatModType.Flat, GameConstants.ObjectSources.PresenceSource)); - AuraPower.AddModifier(new StatModifier(Presence.Value * GameConstants.CharacterStatsBalancing.AuraPowerPerPresence, StatModType.Flat, GameConstants.ObjectSources.PresenceSource)); - - + // 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(); } - public float GetRelativePowerLevel() + private void RemovePrimaryStatInfluences() { - return AttackDamage.Value * 1.1f + SpellDamage.Value * 1.1f + (CritDamage.Value * (CritChance.Value / 100f)) + MaxHealth.Value * 1f + Armor.Value * 1f + MagicResistance.Value * 1f; + // 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(); + } } } \ No newline at end of file diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs index 41cc482d..076ae218 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Kryz.CharacterStats; +using System.Collections.Generic; +using System.Linq; using UnityEngine; public enum WeaponType @@ -43,6 +45,8 @@ public static class WeaponTypeExtensions } } + + namespace Kryz.CharacterStats.Examples { public enum EquipmentType @@ -55,248 +59,9 @@ namespace Kryz.CharacterStats.Examples Bracers, Gloves, Boots, - Weapon1, Weapon2, - - //Accessory, - //Amulet, } - - [CreateAssetMenu] - public class EquippableItem : Item - { - public int AttackDamageBonus; - public int SpellDamageBonus; - //no flat attack speed bonus, only % - public int CritChanceBonus; - public int CritDamageBonus; - - public int MaxHealthBonus; - public int HealthRegenBonus; - public int MaxManaBonus; - public int ManaRegenBonus; - - public int ArmorBonus; - public int MagicResistanceBonus; - //no flat dodge - //no flat block chance - //no flat block effectiveness - - //no flat area - //no flat cdr - //no flat movespeed - - //no flat rep gains - //no flat gold cost reduction - - [Space] - public float AttackDamagePercentBonus; - public float SpellDamagePercentBonus; - - public float AttackSpeedPercentBonus; - - public float CritChancePercentBonus; - public float CritDamagePercentBonus; - - public float MaxHealthPercentBonus; - public float HealthRegenPercentBonus; - public float MaxManaPercentBonus; - public float ManaRegenPercentBonus; - - - public float ArmorPercentBonus; - public float MagicResistancePercentBonus; - public float DodgeChancePercentBonus; - public float BlockChancePercentBonus; - public float BlockEffectivenessPercentBonus; - - - public float AreaEffectivenessPercentBonus; - public float CooldownReductionPercentBonus; - public float MovementSpeedPercentBonus; - public float ReputationGainIncreasePercentBonus; - public float GoldCostReductionPercentBonus; - - - public EquipmentType EquipmentType; - [Space] - // NEW: Add WeaponType for weapon items - public WeaponType WeaponType; - - // Updated EquippableItemInstance helper properties - public bool IsWeapon => EquipmentType == EquipmentType.Weapon1 || EquipmentType == EquipmentType.Weapon2; - // NEW: Helper property to check if this weapon is two-handed - public bool IsTwoHandedWeapon => IsWeapon && WeaponType.IsTwoHanded(); - - // NEW: Helper property to check if this weapon is one-handed - public bool IsOneHandedWeapon => IsWeapon && WeaponType.IsOneHanded(); - - [Space] - public bool CraftableBase = false; - [Space(20f)] - [Header("Crafting-ish")] - public int MinAttackDamageBonus; - public int MaxAttackDamageBonus; - [Space] - public int MinSpellDamageBonus; - public int MaxSpellDamageBonus; - [Space] - public int MinCritChanceBonus; - public int MaxCritChanceBonus; - [Space] - public int MinCritDamageBonus; - public int MaxCritDamageBonus; - [Space] - public int MinMaxHealthBonus; - public int MaxMaxHealthBonus; - [Space] - public int MinArmorBonus; - public int MaxArmorBonus; - [Space] - public int MinMagicResistanceBonus; - public int MaxMagicResistanceBonus; - [Space] - [Space] - public float MinAttackDamagePercentBonus; - public float MaxAttackDamagePercentBonus; - [Space] - public float MinSpellDamagePercentBonus; - public float MaxSpellDamagePercentBonus; - [Space] - public float MinCritChancePercentBonus; - public float MaxCritChancePercentBonus; - [Space] - public float MinCritDamagePercentBonus; - public float MaxCritDamagePercentBonus; - [Space] - public float MinMaxHealthPercentBonus; - public float MaxMaxHealthPercentBonus; - [Space] - public float MinArmorPercentBonus; - public float MaxArmorPercentBonus; - [Space] - public float MinMagicResistancePercentBonus; - public float MaxMagicResistancePercentBonus; - [Space] - [Space] - [Tooltip("Can only increase up to this number of unique stats in a single item instance.")] - public int MaxTotalUniqueStatsIncreasedByStones; - - - public void Equip(PlayerCharacterStats c) - { - - if (AttackDamageBonus != 0) - c.AttackDamage.AddModifier(new StatModifier(AttackDamageBonus, StatModType.Flat, this)); - if (SpellDamageBonus != 0) - c.SpellDamage.AddModifier(new StatModifier(SpellDamageBonus, StatModType.Flat, this)); - - if (CritChanceBonus != 0) - c.CritChance.AddModifier(new StatModifier(CritChanceBonus, StatModType.Flat, this)); - if (CritDamageBonus != 0) - c.CritDamage.AddModifier(new StatModifier(CritDamageBonus, StatModType.Flat, this)); - - if (MaxHealthBonus != 0) - c.MaxHealth.AddModifier(new StatModifier(MaxHealthBonus, StatModType.Flat, this)); - if (HealthRegenBonus != 0) - c.HealthRegen.AddModifier(new StatModifier(HealthRegenBonus, StatModType.Flat, this)); - - if (MaxManaBonus != 0) - c.MaxMana.AddModifier(new StatModifier(MaxManaBonus, StatModType.Flat, this)); - if (ManaRegenBonus != 0) - c.ManaRegen.AddModifier(new StatModifier(ManaRegenBonus, StatModType.Flat, this)); - - - if (ArmorBonus != 0) - c.Armor.AddModifier(new StatModifier(ArmorBonus, StatModType.Flat, this)); - if (MagicResistanceBonus != 0) - c.MagicResistance.AddModifier(new StatModifier(MagicResistanceBonus, StatModType.Flat, this)); - - - - - - - if (AttackDamagePercentBonus != 0) - c.AttackDamage.AddModifier(new StatModifier(AttackDamagePercentBonus, StatModType.PercentAdd, this)); - if (SpellDamagePercentBonus != 0) - c.SpellDamage.AddModifier(new StatModifier(SpellDamagePercentBonus, StatModType.PercentAdd, this)); - - if (AttackSpeedPercentBonus != 0) - c.AttackSpeed.AddModifier(new StatModifier(AttackSpeedPercentBonus, StatModType.PercentAdd, this)); - - if (CritChancePercentBonus != 0) - c.CritChance.AddModifier(new StatModifier(CritChancePercentBonus, StatModType.PercentAdd, this)); - if (CritDamagePercentBonus != 0) - c.CritDamage.AddModifier(new StatModifier(CritDamagePercentBonus, StatModType.PercentAdd, this)); - - - - if (MaxHealthPercentBonus != 0) - c.MaxHealth.AddModifier(new StatModifier(MaxHealthPercentBonus, StatModType.PercentAdd, this)); - if (HealthRegenPercentBonus != 0) - c.HealthRegen.AddModifier(new StatModifier(HealthRegenPercentBonus, StatModType.PercentAdd, this)); - - if (MaxManaPercentBonus != 0) - c.MaxMana.AddModifier(new StatModifier(MaxManaPercentBonus, StatModType.PercentAdd, this)); - if (ManaRegenPercentBonus != 0) - c.ManaRegen.AddModifier(new StatModifier(ManaRegenPercentBonus, StatModType.PercentAdd, this)); - - - if (ArmorPercentBonus != 0) - c.Armor.AddModifier(new StatModifier(ArmorPercentBonus, StatModType.PercentAdd, this)); - if (MagicResistancePercentBonus != 0) - c.MagicResistance.AddModifier(new StatModifier(MagicResistancePercentBonus, StatModType.PercentAdd, this)); - if (DodgeChancePercentBonus != 0) - c.DodgeChance.AddModifier(new StatModifier(DodgeChancePercentBonus, StatModType.PercentAdd, this)); - if (BlockChancePercentBonus != 0) - c.BlockChance.AddModifier(new StatModifier(BlockChancePercentBonus, StatModType.PercentAdd, this)); - if (BlockEffectivenessPercentBonus != 0) - c.BlockEffectiveness.AddModifier(new StatModifier(BlockEffectivenessPercentBonus, StatModType.PercentAdd, this)); - - - if (AreaEffectivenessPercentBonus != 0) - c.AreaEffectiveness.AddModifier(new StatModifier(AreaEffectivenessPercentBonus, StatModType.PercentAdd, this)); - if (CooldownReductionPercentBonus != 0) - c.CooldownReduction.AddModifier(new StatModifier(CooldownReductionPercentBonus, StatModType.PercentAdd, this)); - if (MovementSpeedPercentBonus != 0) - c.MovementSpeed.AddModifier(new StatModifier(MovementSpeedPercentBonus, StatModType.PercentAdd, this)); - if (ReputationGainIncreasePercentBonus != 0) - c.ReputationGainIncrease.AddModifier(new StatModifier(ReputationGainIncreasePercentBonus, StatModType.PercentAdd, this)); - if (GoldCostReductionPercentBonus != 0) - c.GoldCostReduction.AddModifier(new StatModifier(GoldCostReductionPercentBonus, StatModType.PercentAdd, this)); - } - - public void Unequip(PlayerCharacterStats c) - { - - c.AttackDamage.RemoveAllModifiersFromSource(this); - c.SpellDamage.RemoveAllModifiersFromSource(this); - - c.AttackSpeed.RemoveAllModifiersFromSource(this); - - c.CritChance.RemoveAllModifiersFromSource(this); - c.CritDamage.RemoveAllModifiersFromSource(this); - - c.MaxHealth.RemoveAllModifiersFromSource(this); - c.HealthRegen.RemoveAllModifiersFromSource(this); - c.MaxMana.RemoveAllModifiersFromSource(this); - c.ManaRegen.RemoveAllModifiersFromSource(this); - - c.Armor.RemoveAllModifiersFromSource(this); - c.MagicResistance.RemoveAllModifiersFromSource(this); - c.DodgeChance.RemoveAllModifiersFromSource(this); - c.BlockChance.RemoveAllModifiersFromSource(this); - c.BlockEffectiveness.RemoveAllModifiersFromSource(this); - - c.AreaEffectiveness.RemoveAllModifiersFromSource(this); - c.CooldownReduction.RemoveAllModifiersFromSource(this); - c.MovementSpeed.RemoveAllModifiersFromSource(this); - c.ReputationGainIncrease.RemoveAllModifiersFromSource(this); - c.GoldCostReduction.RemoveAllModifiersFromSource(this); - } - - } + } \ No newline at end of file diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs index e5e5a1e0..c6700b0d 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs @@ -1,244 +1,190 @@ using Kryz.CharacterStats; using Kryz.CharacterStats.Examples; -using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; +[System.Serializable] +public class ItemStatBonus +{ + public StatDefinition statDefinition; + public float flatValue; + public float percentValue; + + public bool HasFlatBonus => flatValue != 0; + public bool HasPercentBonus => percentValue != 0; + public bool HasAnyBonus => HasFlatBonus || HasPercentBonus; + + public ItemStatBonus() { } + + public ItemStatBonus(StatDefinition stat, float flat = 0f, float percent = 0f) + { + statDefinition = stat; + flatValue = flat; + percentValue = percent; + } +} + [System.Serializable] public class EquippableItemInstance : ItemInstance { - public int AttackDamageBonus; - public int SpellDamageBonus; - //no flat attack speed bonus, only % - public int CritChanceBonus; - public int CritDamageBonus; - - public int MaxHealthBonus; - public int HealthRegenBonus; - public int MaxManaBonus; - public int ManaRegenBonus; - - public int ArmorBonus; - public int MagicResistanceBonus; - //no flat dodge - //no flat block chance - //no flat block effectiveness - - //no flat area - //no flat cdr - //no flat movespeed - - //no flat rep gains - //no flat gold cost reduction - - [Space] - public float AttackDamagePercentBonus; - public float SpellDamagePercentBonus; - - public float AttackSpeedPercentBonus; - - public float CritChancePercentBonus; - public float CritDamagePercentBonus; - - public float MaxHealthPercentBonus; - public float HealthRegenPercentBonus; - public float MaxManaPercentBonus; - public float ManaRegenPercentBonus; - - - public float ArmorPercentBonus; - public float MagicResistancePercentBonus; - public float DodgeChancePercentBonus; - public float BlockChancePercentBonus; - public float BlockEffectivenessPercentBonus; - - - public float AreaEffectivenessPercentBonus; - public float CooldownReductionPercentBonus; - public float MovementSpeedPercentBonus; - public float ReputationGainIncreasePercentBonus; - public float GoldCostReductionPercentBonus; - [Space] + [Header("Equipment Identity")] public EquipmentType EquipmentType; - [Space] - // NEW: Add WeaponType for weapon items public WeaponType WeaponType; - [Space] + public string IconPath; // Store the icon path for serialization + + [Header("Dynamic Stat System")] + public List statBonuses = new List(); + + [Header("Crafting System")] public bool CraftableBase = false; - [Space] - /// - /// Can only contain up to this number of unique stats in a single item instance. - /// - [Space] - [Tooltip("Can only contain up to this number of unique stats in a single item instance.")] public int MaxTotalUniqueStatsIncreasedByStones; public List AddedStoneStats = new List(); - // Updated EquippableItemInstance helper properties + // Helper properties public bool IsWeapon => EquipmentType == EquipmentType.Weapon1 || EquipmentType == EquipmentType.Weapon2; - // NEW: Helper property to check if this weapon is two-handed public bool IsTwoHandedWeapon => IsWeapon && WeaponType.IsTwoHanded(); - - // NEW: Helper property to check if this weapon is one-handed public bool IsOneHandedWeapon => IsWeapon && WeaponType.IsOneHanded(); - public void Equip(PlayerCharacterStats c) + public void Equip(PlayerCharacterStats characterStats) { - if (AttackDamageBonus != 0) - c.AttackDamage.AddModifier(new StatModifier(AttackDamageBonus, StatModType.Flat, this)); - if (SpellDamageBonus != 0) - c.SpellDamage.AddModifier(new StatModifier(SpellDamageBonus, StatModType.Flat, this)); + foreach (var bonus in statBonuses) + { + if (bonus.statDefinition == null || !bonus.HasAnyBonus) continue; - if (CritChanceBonus != 0) - c.CritChance.AddModifier(new StatModifier(CritChanceBonus, StatModType.Flat, this)); - if (CritDamageBonus != 0) - c.CritDamage.AddModifier(new StatModifier(CritDamageBonus, StatModType.Flat, this)); + var stat = characterStats.GetStat(bonus.statDefinition.StatKey); + if (stat != null) + { + // Apply flat bonus + if (bonus.HasFlatBonus) + { + stat.AddModifier(new StatModifier(bonus.flatValue, StatModType.Flat, this)); + } - if (MaxHealthBonus != 0) - c.MaxHealth.AddModifier(new StatModifier(MaxHealthBonus, StatModType.Flat, this)); - if (HealthRegenBonus != 0) - c.HealthRegen.AddModifier(new StatModifier(HealthRegenBonus, StatModType.Flat, this)); - - if (MaxManaBonus != 0) - c.MaxMana.AddModifier(new StatModifier(MaxManaBonus, StatModType.Flat, this)); - if (ManaRegenBonus != 0) - c.ManaRegen.AddModifier(new StatModifier(ManaRegenBonus, StatModType.Flat, this)); - - - if (ArmorBonus != 0) - c.Armor.AddModifier(new StatModifier(ArmorBonus, StatModType.Flat, this)); - if (MagicResistanceBonus != 0) - c.MagicResistance.AddModifier(new StatModifier(MagicResistanceBonus, StatModType.Flat, this)); - - - - - - - if (AttackDamagePercentBonus != 0) - c.AttackDamage.AddModifier(new StatModifier(AttackDamagePercentBonus, StatModType.PercentAdd, this)); - if (SpellDamagePercentBonus != 0) - c.SpellDamage.AddModifier(new StatModifier(SpellDamagePercentBonus, StatModType.PercentAdd, this)); - - if (AttackSpeedPercentBonus != 0) - c.AttackSpeed.AddModifier(new StatModifier(AttackSpeedPercentBonus, StatModType.PercentAdd, this)); - - if (CritChancePercentBonus != 0) - c.CritChance.AddModifier(new StatModifier(CritChancePercentBonus, StatModType.PercentAdd, this)); - if (CritDamagePercentBonus != 0) - c.CritDamage.AddModifier(new StatModifier(CritDamagePercentBonus, StatModType.PercentAdd, this)); - - - - if (MaxHealthPercentBonus != 0) - c.MaxHealth.AddModifier(new StatModifier(MaxHealthPercentBonus, StatModType.PercentAdd, this)); - if (HealthRegenPercentBonus != 0) - c.HealthRegen.AddModifier(new StatModifier(HealthRegenPercentBonus, StatModType.PercentAdd, this)); - - if (MaxManaPercentBonus != 0) - c.MaxMana.AddModifier(new StatModifier(MaxManaPercentBonus, StatModType.PercentAdd, this)); - if (ManaRegenPercentBonus != 0) - c.ManaRegen.AddModifier(new StatModifier(ManaRegenPercentBonus, StatModType.PercentAdd, this)); - - - if (ArmorPercentBonus != 0) - c.Armor.AddModifier(new StatModifier(ArmorPercentBonus, StatModType.PercentAdd, this)); - if (MagicResistancePercentBonus != 0) - c.MagicResistance.AddModifier(new StatModifier(MagicResistancePercentBonus, StatModType.PercentAdd, this)); - if (DodgeChancePercentBonus != 0) - c.DodgeChance.AddModifier(new StatModifier(DodgeChancePercentBonus, StatModType.PercentAdd, this)); - if (BlockChancePercentBonus != 0) - c.BlockChance.AddModifier(new StatModifier(BlockChancePercentBonus, StatModType.PercentAdd, this)); - if (BlockEffectivenessPercentBonus != 0) - c.BlockEffectiveness.AddModifier(new StatModifier(BlockEffectivenessPercentBonus, StatModType.PercentAdd, this)); - - - if (AreaEffectivenessPercentBonus != 0) - c.AreaEffectiveness.AddModifier(new StatModifier(AreaEffectivenessPercentBonus, StatModType.PercentAdd, this)); - if (CooldownReductionPercentBonus != 0) - c.CooldownReduction.AddModifier(new StatModifier(CooldownReductionPercentBonus, StatModType.PercentAdd, this)); - if (MovementSpeedPercentBonus != 0) - c.MovementSpeed.AddModifier(new StatModifier(MovementSpeedPercentBonus, StatModType.PercentAdd, this)); - if (ReputationGainIncreasePercentBonus != 0) - c.ReputationGainIncrease.AddModifier(new StatModifier(ReputationGainIncreasePercentBonus, StatModType.PercentAdd, this)); - if (GoldCostReductionPercentBonus != 0) - c.GoldCostReduction.AddModifier(new StatModifier(GoldCostReductionPercentBonus, StatModType.PercentAdd, this)); + // Apply percent bonus + if (bonus.HasPercentBonus) + { + stat.AddModifier(new StatModifier(bonus.percentValue, StatModType.PercentAdd, this)); + } + } + else + { + Debug.LogWarning($"EquippableItemInstance: Stat '{bonus.statDefinition.StatKey}' not found in character stats!"); + } + } } - public void Unequip(PlayerCharacterStats c) + public void Unequip(PlayerCharacterStats characterStats) { - c.AttackDamage.RemoveAllModifiersFromSource(this); - c.SpellDamage.RemoveAllModifiersFromSource(this); - - c.AttackSpeed.RemoveAllModifiersFromSource(this); - - c.CritChance.RemoveAllModifiersFromSource(this); - c.CritDamage.RemoveAllModifiersFromSource(this); - - c.MaxHealth.RemoveAllModifiersFromSource(this); - c.HealthRegen.RemoveAllModifiersFromSource(this); - c.MaxMana.RemoveAllModifiersFromSource(this); - c.ManaRegen.RemoveAllModifiersFromSource(this); - - c.Armor.RemoveAllModifiersFromSource(this); - c.MagicResistance.RemoveAllModifiersFromSource(this); - c.DodgeChance.RemoveAllModifiersFromSource(this); - c.BlockChance.RemoveAllModifiersFromSource(this); - c.BlockEffectiveness.RemoveAllModifiersFromSource(this); - - c.AreaEffectiveness.RemoveAllModifiersFromSource(this); - c.CooldownReduction.RemoveAllModifiersFromSource(this); - c.MovementSpeed.RemoveAllModifiersFromSource(this); - c.ReputationGainIncrease.RemoveAllModifiersFromSource(this); - c.GoldCostReduction.RemoveAllModifiersFromSource(this); + // Remove all modifiers from this item source across all stats + var allStats = characterStats.GetAllStats(); + foreach (var kvp in allStats) + { + kvp.RemoveAllModifiersFromSource(this); + } } + // Stat management methods + public void AddStatBonus(StatDefinition statDef, float flatValue = 0f, float percentValue = 0f) + { + var existingBonus = statBonuses.FirstOrDefault(b => b.statDefinition == statDef); + if (existingBonus != null) + { + existingBonus.flatValue += flatValue; + existingBonus.percentValue += percentValue; + } + else + { + statBonuses.Add(new ItemStatBonus(statDef, flatValue, percentValue)); + } + } + + public void SetStatBonus(StatDefinition statDef, float flatValue = 0f, float percentValue = 0f) + { + var existingBonus = statBonuses.FirstOrDefault(b => b.statDefinition == statDef); + if (existingBonus != null) + { + existingBonus.flatValue = flatValue; + existingBonus.percentValue = percentValue; + } + else + { + statBonuses.Add(new ItemStatBonus(statDef, flatValue, percentValue)); + } + } + + public ItemStatBonus GetStatBonus(StatDefinition statDef) + { + return statBonuses.FirstOrDefault(b => b.statDefinition == statDef); + } + + public bool HasStat(StatDefinition statDef) + { + var bonus = GetStatBonus(statDef); + return bonus != null && bonus.HasAnyBonus; + } + + public List GetAllNonZeroBonuses() + { + return statBonuses.Where(b => b.HasAnyBonus).ToList(); + } + + public int GetUniqueStatCount() + { + return GetAllNonZeroBonuses().Count; + } + + // Crafting stone system (updated to use new stat system) public bool CanAddCraftingStone(CraftingStatStone stone) { if (!CraftableBase) return false; - var stoneStats = GetNonZeroStats(stone); - var itemNonZeroStats = GetNonZeroStats(this); + var stoneStats = GetNonZeroStatsFromStone(stone); + var currentStats = GetAllNonZeroBonuses().Select(b => b.statDefinition.StatKey).ToList(); - int newUniqueStats = stoneStats.Except(itemNonZeroStats).Count(); + int newUniqueStats = stoneStats.Except(currentStats).Count(); // Ensure we don't exceed the max unique stats cap if (AddedStoneStats.Count + newUniqueStats > MaxTotalUniqueStatsIncreasedByStones) { - return false; // Adding the crafting stone would exceed the cap + return false; } return true; } + public bool TryAddCraftingStone(CraftingStatStone stone) { if (!CanAddCraftingStone(stone)) return false; - var stoneStats = GetNonZeroStats(stone); - var itemStats = GetNonZeroStats(this); + var stoneStats = GetNonZeroStatsFromStone(stone); + var currentStats = GetAllNonZeroBonuses().Select(b => b.statDefinition.StatKey).ToList(); - for (int i = 0; i < stoneStats.Count; i++) + // Track new stats being added + foreach (var statKey in stoneStats) { - if (!itemStats.Contains(stoneStats[i]) && !AddedStoneStats.Contains(stoneStats[i])) - AddedStoneStats.Add(stoneStats[i]); + if (!currentStats.Contains(statKey) && !AddedStoneStats.Contains(statKey)) + { + AddedStoneStats.Add(statKey); + } } - // Add stats from the stone to the item - AddStats(stone); + + // Add stats from the stone to the item (this needs to be updated based on your stone structure) + AddStatsFromStone(stone); return true; } - private List GetNonZeroStats(object stats) + private List GetNonZeroStatsFromStone(CraftingStatStone stone) { var nonZeroStats = new List(); - var fields = stats.GetType().GetFields(); + var fields = stone.GetType().GetFields(); foreach (var field in fields) { - if (field.GetValue(stats) is int intValue && intValue != 0 && field.Name.ToLower().Contains("bonus")) + if (field.GetValue(stone) is int intValue && intValue != 0 && field.Name.ToLower().Contains("bonus")) { nonZeroStats.Add(field.Name); } - else if (field.GetValue(stats) is float floatValue && !Mathf.Approximately(floatValue, 0f) && field.Name.ToLower().Contains("bonus")) + else if (field.GetValue(stone) is float floatValue && !Mathf.Approximately(floatValue, 0f) && field.Name.ToLower().Contains("bonus")) { nonZeroStats.Add(field.Name); } @@ -247,216 +193,89 @@ public class EquippableItemInstance : ItemInstance return nonZeroStats; } - //TODO: Add new stats to stones too - private void AddStats(CraftingStatStone stone) + // This method needs to be updated based on your CraftingStatStone structure + private void AddStatsFromStone(CraftingStatStone stone) { - AttackDamageBonus += stone.AttackDamageBonus; - SpellDamageBonus += stone.SpellDamageBonus; - CritChanceBonus += stone.CritChanceBonus; - CritDamageBonus += stone.CritDamageBonus; - MaxHealthBonus += stone.MaxHealthBonus; - //HealthRegenBonus += stone.HealthRegenBonus; - //MaxManaBonus += stone.MaxManaBonus; - //ManaRegenBonus += stone.ManaRegenBonus; - ArmorBonus += stone.ArmorBonus; - MagicResistanceBonus += stone.MagicResistanceBonus; - - AttackDamagePercentBonus += stone.AttackDamagePercentBonus; - SpellDamagePercentBonus += stone.SpellDamagePercentBonus; - //AttackSpeedPercentBonus += stone.AttackSpeedPercentBonus; - CritChancePercentBonus += stone.CritChancePercentBonus; - CritDamagePercentBonus += stone.CritDamagePercentBonus; - MaxHealthPercentBonus += stone.MaxHealthPercentBonus; - //HealthRegenPercentBonus += stone.HealthRegenPercentBonus; - //MaxManaPercentBonus += stone.MaxManaPercentBonus; - //ManaRegenPercentBonus += stone.ManaRegenPercentBonus; - ArmorPercentBonus += stone.ArmorPercentBonus; - MagicResistancePercentBonus += stone.MagicResistancePercentBonus; - //DodgeChancePercentBonus += stone.DodgeChancePercentBonus; - //BlockChancePercentBonus += stone.BlockChancePercentBonus; - //BlockEffectivenessPercentBonus += stone.BlockEffectivenessPercentBonus; - //AreaEffectivenessPercentBonus += stone.AreaEffectivenessPercentBonus; - //CooldownReductionPercentBonus += stone.CooldownReductionPercentBonus; - //MovementSpeedPercentBonus += stone.MovementSpeedPercentBonus; - //ReputationGainIncreasePercentBonus += stone.ReputationGainIncreasePercentBonus; - //GoldCostReductionPercentBonus += stone.GoldCostReductionPercentBonus; + // TODO: Update this to work with the new stat system + // You'll need to map stone properties to StatDefinition references + // For now, keeping the old structure as a placeholder + Debug.LogWarning("AddStatsFromStone needs to be updated for the new stat system"); } + // Constructors public EquippableItemInstance() { - // Initialize all int stats to 0 - AttackDamageBonus = 0; - SpellDamageBonus = 0; - CritChanceBonus = 0; - CritDamageBonus = 0; - MaxHealthBonus = 0; - HealthRegenBonus = 0; - MaxManaBonus = 0; - ManaRegenBonus = 0; - ArmorBonus = 0; - MagicResistanceBonus = 0; - - // Initialize all float stats to 0 - AttackDamagePercentBonus = 0; - SpellDamagePercentBonus = 0; - AttackSpeedPercentBonus = 0; - CritChancePercentBonus = 0; - CritDamagePercentBonus = 0; - MaxHealthPercentBonus = 0; - HealthRegenPercentBonus = 0; - MaxManaPercentBonus = 0; - ManaRegenPercentBonus = 0; - ArmorPercentBonus = 0; - MagicResistancePercentBonus = 0; - DodgeChancePercentBonus = 0; - BlockChancePercentBonus = 0; - BlockEffectivenessPercentBonus = 0; - AreaEffectivenessPercentBonus = 0; - CooldownReductionPercentBonus = 0; - MovementSpeedPercentBonus = 0; - ReputationGainIncreasePercentBonus = 0; - GoldCostReductionPercentBonus = 0; - EquipmentType = EquipmentType.Helmet; - WeaponType = WeaponType.Sword; // Default weapon type - + WeaponType = WeaponType.Sword; CraftableBase = false; MaxTotalUniqueStatsIncreasedByStones = 0; + statBonuses = new List(); + AddedStoneStats = new List(); templateIndex = -1; } - public EquippableItemInstance(EquippableItem template) + // Constructor for generating from EquipmentTypeDefinition (recommended approach) + public EquippableItemInstance(EquippableItemTypeDefinition equipmentTypeDef, string itemName, Sprite icon, string iconPath = null) { - ItemName = template.ItemName; - Icon = template.Icon; - sellPricePlayer = template.sellPricePlayer; - sellPriceVendor = template.sellPriceVendor; - description = template.description; - templateIndex = ItemIndexer.Instance.Items.IndexOf(template); + ItemName = itemName; + Icon = icon; + IconPath = iconPath ?? ""; // Store the icon path + EquipmentType = equipmentTypeDef.EquipmentType; + WeaponType = equipmentTypeDef.WeaponType; + CraftableBase = false; // Set based on your needs + MaxTotalUniqueStatsIncreasedByStones = 6; // Default value + statBonuses = new List(); + AddedStoneStats = new List(); + templateIndex = -1; - // Copy all int stats - AttackDamageBonus = template.AttackDamageBonus; - SpellDamageBonus = template.SpellDamageBonus; - CritChanceBonus = template.CritChanceBonus; - CritDamageBonus = template.CritDamageBonus; - MaxHealthBonus = template.MaxHealthBonus; - HealthRegenBonus = template.HealthRegenBonus; - MaxManaBonus = template.MaxManaBonus; - ManaRegenBonus = template.ManaRegenBonus; - ArmorBonus = template.ArmorBonus; - MagicResistanceBonus = template.MagicResistanceBonus; - - // Copy all float stats - AttackDamagePercentBonus = template.AttackDamagePercentBonus; - SpellDamagePercentBonus = template.SpellDamagePercentBonus; - AttackSpeedPercentBonus = template.AttackSpeedPercentBonus; - CritChancePercentBonus = template.CritChancePercentBonus; - CritDamagePercentBonus = template.CritDamagePercentBonus; - MaxHealthPercentBonus = template.MaxHealthPercentBonus; - HealthRegenPercentBonus = template.HealthRegenPercentBonus; - MaxManaPercentBonus = template.MaxManaPercentBonus; - ManaRegenPercentBonus = template.ManaRegenPercentBonus; - ArmorPercentBonus = template.ArmorPercentBonus; - MagicResistancePercentBonus = template.MagicResistancePercentBonus; - DodgeChancePercentBonus = template.DodgeChancePercentBonus; - BlockChancePercentBonus = template.BlockChancePercentBonus; - BlockEffectivenessPercentBonus = template.BlockEffectivenessPercentBonus; - AreaEffectivenessPercentBonus = template.AreaEffectivenessPercentBonus; - CooldownReductionPercentBonus = template.CooldownReductionPercentBonus; - MovementSpeedPercentBonus = template.MovementSpeedPercentBonus; - ReputationGainIncreasePercentBonus = template.ReputationGainIncreasePercentBonus; - GoldCostReductionPercentBonus = template.GoldCostReductionPercentBonus; - - EquipmentType = template.EquipmentType; - WeaponType = template.WeaponType; - - CraftableBase = template.CraftableBase; - MaxTotalUniqueStatsIncreasedByStones = template.MaxTotalUniqueStatsIncreasedByStones; + // You can set default sell prices here or pass them as parameters + sellPricePlayer = 100; + sellPriceVendor = 50; + description = $"A {equipmentTypeDef.GetDisplayName()}"; } - public EquippableItemInstance(EquippableItem template, bool randomizeStats) + // Helper method to set icon with path + public void SetIcon(Sprite icon, string iconPath) { - ItemName = template.ItemName; - Icon = template.Icon; - sellPricePlayer = template.sellPricePlayer; - sellPriceVendor = template.sellPriceVendor; - description = template.description; - templateIndex = ItemIndexer.Instance.Items.IndexOf(template); + Icon = icon; + IconPath = iconPath; + } - if (randomizeStats) + // Helper method to get icon path from EquipmentTypeDefinition + public static string GetIconPathFromEquipmentType(EquippableItemTypeDefinition equipmentTypeDef, Sprite icon) + { + if (equipmentTypeDef.UseResourcesFolder && !string.IsNullOrEmpty(equipmentTypeDef.ResourcesPath)) { - // Randomize int stats - AttackDamageBonus = Random.Range(template.MinAttackDamageBonus, template.MaxAttackDamageBonus); - SpellDamageBonus = Random.Range(template.MinSpellDamageBonus, template.MaxSpellDamageBonus); - CritChanceBonus = Random.Range(template.MinCritChanceBonus, template.MaxCritChanceBonus); - CritDamageBonus = Random.Range(template.MinCritDamageBonus, template.MaxCritDamageBonus); - MaxHealthBonus = Random.Range(template.MinMaxHealthBonus, template.MaxMaxHealthBonus); - //HealthRegenBonus = Random.Range(template.MinHealthRegenBonus, template.MaxHealthRegenBonus); - //MaxManaBonus = Random.Range(template.MinMaxManaBonus, template.MaxMaxManaBonus); - //ManaRegenBonus = Random.Range(template.MinManaRegenBonus, template.MaxManaRegenBonus); - ArmorBonus = Random.Range(template.MinArmorBonus, template.MaxArmorBonus); - MagicResistanceBonus = Random.Range(template.MinMagicResistanceBonus, template.MaxMagicResistanceBonus); - - // Randomize float stats with rounding - AttackDamagePercentBonus = Mathf.Round(Random.Range(template.MinAttackDamagePercentBonus, template.MaxAttackDamagePercentBonus) * 100f) / 100f; - SpellDamagePercentBonus = Mathf.Round(Random.Range(template.MinSpellDamagePercentBonus, template.MaxSpellDamagePercentBonus) * 100f) / 100f; - //AttackSpeedPercentBonus = Mathf.Round(Random.Range(template.MinAttackSpeedPercentBonus, template.MaxAttackSpeedPercentBonus) * 100f) / 100f; - CritChancePercentBonus = Mathf.Round(Random.Range(template.MinCritChancePercentBonus, template.MaxCritChancePercentBonus) * 100f) / 100f; - CritDamagePercentBonus = Mathf.Round(Random.Range(template.MinCritDamagePercentBonus, template.MaxCritDamagePercentBonus) * 100f) / 100f; - MaxHealthPercentBonus = Mathf.Round(Random.Range(template.MinMaxHealthPercentBonus, template.MaxMaxHealthPercentBonus) * 100f) / 100f; - //HealthRegenPercentBonus = Mathf.Round(Random.Range(template.MinHealthRegenPercentBonus, template.MaxHealthRegenPercentBonus) * 100f) / 100f; - //MaxManaPercentBonus = Mathf.Round(Random.Range(template.MinMaxManaPercentBonus, template.MaxMaxManaPercentBonus) * 100f) / 100f; - //ManaRegenPercentBonus = Mathf.Round(Random.Range(template.MinManaRegenPercentBonus, template.MaxManaRegenPercentBonus) * 100f) / 100f; - ArmorPercentBonus = Mathf.Round(Random.Range(template.MinArmorPercentBonus, template.MaxArmorPercentBonus) * 100f) / 100f; - MagicResistancePercentBonus = Mathf.Round(Random.Range(template.MinMagicResistancePercentBonus, template.MaxMagicResistancePercentBonus) * 100f) / 100f; - //DodgeChancePercentBonus = Mathf.Round(Random.Range(template.MinDodgeChancePercentBonus, template.MaxDodgeChancePercentBonus) * 100f) / 100f; - //BlockChancePercentBonus = Mathf.Round(Random.Range(template.MinBlockChancePercentBonus, template.MaxBlockChancePercentBonus) * 100f) / 100f; - //BlockEffectivenessPercentBonus = Mathf.Round(Random.Range(template.MinBlockEffectivenessPercentBonus, template.MaxBlockEffectivenessPercentBonus) * 100f) / 100f; - //AreaEffectivenessPercentBonus = Mathf.Round(Random.Range(template.MinAreaEffectivenessPercentBonus, template.MaxAreaEffectivenessPercentBonus) * 100f) / 100f; - //CooldownReductionPercentBonus = Mathf.Round(Random.Range(template.MinCooldownReductionPercentBonus, template.MaxCooldownReductionPercentBonus) * 100f) / 100f; - //MovementSpeedPercentBonus = Mathf.Round(Random.Range(template.MinMovementSpeedPercentBonus, template.MaxMovementSpeedPercentBonus) * 100f) / 100f; - //ReputationGainIncreasePercentBonus = Mathf.Round(Random.Range(template.MinReputationGainIncreasePercentBonus, template.MaxReputationGainIncreasePercentBonus) * 100f) / 100f; - //GoldCostReductionPercentBonus = Mathf.Round(Random.Range(template.MinGoldCostReductionPercentBonus, template.MaxGoldCostReductionPercentBonus) * 100f) / 100f; + return $"{equipmentTypeDef.ResourcesPath}/{icon.name}"; } - else - { - // Copy exact values from template - AttackDamageBonus = template.AttackDamageBonus; - SpellDamageBonus = template.SpellDamageBonus; - CritChanceBonus = template.CritChanceBonus; - CritDamageBonus = template.CritDamageBonus; - MaxHealthBonus = template.MaxHealthBonus; - HealthRegenBonus = template.HealthRegenBonus; - MaxManaBonus = template.MaxManaBonus; - ManaRegenBonus = template.ManaRegenBonus; - ArmorBonus = template.ArmorBonus; - MagicResistanceBonus = template.MagicResistanceBonus; + return ""; // Manual icons don't have paths + } - AttackDamagePercentBonus = template.AttackDamagePercentBonus; - SpellDamagePercentBonus = template.SpellDamagePercentBonus; - AttackSpeedPercentBonus = template.AttackSpeedPercentBonus; - CritChancePercentBonus = template.CritChancePercentBonus; - CritDamagePercentBonus = template.CritDamagePercentBonus; - MaxHealthPercentBonus = template.MaxHealthPercentBonus; - HealthRegenPercentBonus = template.HealthRegenPercentBonus; - MaxManaPercentBonus = template.MaxManaPercentBonus; - ManaRegenPercentBonus = template.ManaRegenPercentBonus; - ArmorPercentBonus = template.ArmorPercentBonus; - MagicResistancePercentBonus = template.MagicResistancePercentBonus; - DodgeChancePercentBonus = template.DodgeChancePercentBonus; - BlockChancePercentBonus = template.BlockChancePercentBonus; - BlockEffectivenessPercentBonus = template.BlockEffectivenessPercentBonus; - AreaEffectivenessPercentBonus = template.AreaEffectivenessPercentBonus; - CooldownReductionPercentBonus = template.CooldownReductionPercentBonus; - MovementSpeedPercentBonus = template.MovementSpeedPercentBonus; - ReputationGainIncreasePercentBonus = template.ReputationGainIncreasePercentBonus; - GoldCostReductionPercentBonus = template.GoldCostReductionPercentBonus; + + + // Utility methods for debugging and UI + public string GetStatSummary() + { + if (statBonuses.Count == 0) return "No stat bonuses"; + + var summary = new List(); + foreach (var bonus in GetAllNonZeroBonuses()) + { + var parts = new List(); + if (bonus.HasFlatBonus) + parts.Add($"+{bonus.flatValue:F0}"); + if (bonus.HasPercentBonus) + parts.Add($"+{bonus.percentValue:P1}"); + + summary.Add($"{bonus.statDefinition.DisplayName}: {string.Join(" ", parts)}"); } - EquipmentType = template.EquipmentType; - WeaponType = template.WeaponType; + return string.Join("\n", summary); + } - CraftableBase = template.CraftableBase; - MaxTotalUniqueStatsIncreasedByStones = template.MaxTotalUniqueStatsIncreasedByStones; + [ContextMenu("Log Stat Summary")] + private void LogStatSummary() + { + Debug.Log($"=== {ItemName} Stats ===\n{GetStatSummary()}"); } } \ No newline at end of file diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/Item.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/Item.cs index 4949387e..78ebf91b 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/Item.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/Item.cs @@ -14,11 +14,7 @@ namespace Kryz.CharacterStats.Examples public static ItemInstance ConvertTemplateIntoInstance(Item template) { - if(template is EquippableItem item) - { - return new EquippableItemInstance(item, item.CraftableBase); - } - else if(template is HiddenMap map) + if(template is HiddenMap map) { return new HiddenMapInstance(map); } @@ -30,10 +26,6 @@ namespace Kryz.CharacterStats.Examples } public static ItemInstance ConvertTemplateIntoInstance(Item template, int templateIndex) { - if (template is EquippableItem item) - { - return new EquippableItemInstance(item, item.CraftableBase); - } return new ItemInstance(template, templateIndex); } } diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs index d5110301..4812e18a 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs @@ -107,8 +107,8 @@ namespace Kryz.CharacterStats.Examples statPanel = FindObjectOfType(); statPanel.SetPlayerStats(this); - statPanel.SetStats(Cunning, Flow, Presence); - statPanel.SetSecondaryStats(AttackDamage, SpellDamage, AttackSpeed, CritChance, CritDamage, AuraPower, MaxHealth, HealthRegen, MaxMana, ManaRegen, Armor, MagicResistance, DodgeChance, BlockChance, BlockEffectiveness, AreaEffectiveness, CooldownReduction, MovementSpeed, ReputationGainIncrease, GoldCostReduction); + statPanel.SetStats(primaryStatsDictionary); + statPanel.SetSecondaryStats(secondaryStatsDictionary); //TODO: statPanel set misc stats (area, cdr, movespeed, rep, gold statPanel.UpdateStatValues(); statPanel.SetCharacterInfo(PlayerDataHandler.Instance.currentCharacterName.Value, level.currentLevel.ToString(), reputationLevel.currentLevel.ToString()); @@ -294,11 +294,10 @@ namespace Kryz.CharacterStats.Examples public void UpdateStatsBasedOnLevel() { - - MaxHealth.RemoveAllModifiersFromSource(GameConstants.ObjectSources.LevelSource); + GetStat("MaxHealth").RemoveAllModifiersFromSource(GameConstants.ObjectSources.LevelSource); //Debug.Log("MAX HEALTH BASE VALUE: " + MaxHealth.BaseValue); - MaxHealth.AddModifier(new StatModifier(MaxHealth.BaseValue * (level.currentLevel - 1) * GameConstants.CharacterStatsBalancing.BaseMaxHealthGrowthPerLevel, StatModType.Flat, GameConstants.ObjectSources.LevelSource)); + GetStat("MaxHealth").AddModifier(new StatModifier(GetStat("MaxHealth").BaseValue * (level.currentLevel - 1) * GameConstants.CharacterStatsBalancing.BaseMaxHealthGrowthPerLevel, StatModType.Flat, GameConstants.ObjectSources.LevelSource)); onUpdateStatValues.Invoke(); diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/StatPanel.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/StatPanel.cs index 2cc3d4b9..50120282 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/StatPanel.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/StatPanel.cs @@ -1,4 +1,6 @@ -using TMPro; +using System.Collections.Generic; +using System.Linq; +using TMPro; using UnityEngine; using UnityEngine.UI; @@ -52,20 +54,23 @@ namespace Kryz.CharacterStats.Examples this.playerStats = playerStats; } - public void SetStats(params CharacterStat[] charStats) + public void SetStats(Dictionary primaryStats) { - stats = charStats; + // Store both dictionary values as array (for compatibility with existing methods) + stats = primaryStats.Values.ToArray(); - if (stats.Length > statDisplays.Length) + for (int i = 0; i < statDisplays.Length && i < primaryStats.Count; i++) { - Debug.LogError("Not Enough Stat Displays!"); - return; + var stat = primaryStats.Values.ElementAt(i); + statDisplays[i].Stat = stat; + statDisplays[i].NameText.text = stat.statDefinition.DisplayName; // This requires updating CharacterStat first + statDisplays[i].gameObject.SetActive(true); } - for (int i = 0; i < statDisplays.Length; i++) + // Hide unused displays + for (int i = primaryStats.Count; i < statDisplays.Length; i++) { - statDisplays[i].Stat = i < stats.Length ? stats[i] : null; - statDisplays[i].gameObject.SetActive(i < stats.Length); + statDisplays[i].gameObject.SetActive(false); } unallocated.ValueText.text = playerStats.AvailablePointsToAllocate.ToString(); @@ -79,19 +84,25 @@ namespace Kryz.CharacterStats.Examples addStatButtons[2].onClick.AddListener(() => AllocateStat(2)); resetAllocated.onClick.AddListener(ResetAllocatedStats); } - public void SetSecondaryStats(params CharacterStat[] secondaryStats) + public void SetSecondaryStats(Dictionary secondaryStats) { - this.secondaryStats = secondaryStats; + // Store as array for compatibility + this.secondaryStats = secondaryStats.Values.ToArray(); - if (secondaryStats.Length > secondaryStatDisplays.Length) + int index = 0; + foreach (var kvp in secondaryStats) { - Debug.LogError("Not Enough Stat Displays!"); - return; + if (index >= secondaryStatDisplays.Length) break; + + secondaryStatDisplays[index].Stat = kvp.Value; + secondaryStatDisplays[index].NameText.text = kvp.Value.statDefinition.DisplayName; + index++; } - for (int i = 0; i < secondaryStatDisplays.Length; i++) + // Hide unused displays + for (int i = index; i < secondaryStatDisplays.Length; i++) { - secondaryStatDisplays[i].Stat = secondaryStats[i]; + secondaryStatDisplays[i].gameObject.SetActive(false); } } diff --git a/Assets/Scripts/Drops/DropTable.cs b/Assets/Scripts/Drops/DropTable.cs index 82e678e5..1ddd82b3 100644 --- a/Assets/Scripts/Drops/DropTable.cs +++ b/Assets/Scripts/Drops/DropTable.cs @@ -13,10 +13,8 @@ public class DropTable : MonoBehaviour [SerializeField] private GameEvent_Int onCoinDrop; - public List guaranteedItemDrop = new List(); [Header("Only one of the following list is guaranteed to drop.")] public List guaranteedOnlyOnePerKill = new List(); - public List extraDrops = new List(); public List nonEquippablesDrops = new List(); [Space] @@ -66,17 +64,11 @@ public class DropTable : MonoBehaviour onCoinDrop.Raise(finalCoinAmount); - for (int i = 0; i < guaranteedItemDrop.Count; i++) - { - instantiatedDrop = Instantiate(interactableDropPrefab, spawnPosition, this.transform.rotation); - itemDrop = instantiatedDrop.GetComponent(); - itemDrop.itemDrop = Item.ConvertTemplateIntoInstance(guaranteedItemDrop[i]); - } if (guaranteedOnlyOnePerKill.Count > 0 && WeightedDrop.HaveDropsForDifficulty(weightedDropLootTable, GameDifficultyController.Instance.GetCurrentDifficultyLevel())) { possibleItem = WeightedDrop.GetRandomDrop(guaranteedOnlyOnePerKill, GameDifficultyController.Instance.GetCurrentDifficultyLevel()); - spawnPosition = guaranteedItemDrop.Count > 0 ? this.transform.position + Vector3.one * 0.3f : this.transform.position; + //spawnPosition = guaranteedItemDrop.Count > 0 ? this.transform.position + Vector3.one * 0.3f : this.transform.position; spawnPosition.y = 0f; instantiatedDrop = Instantiate(interactableDropPrefab, spawnPosition, this.transform.rotation); itemDrop = instantiatedDrop.GetComponent(); @@ -109,10 +101,7 @@ public class DropTable : MonoBehaviour if (possibleItem == null) return; spawnPosition = this.transform.position; - if (guaranteedItemDrop.Count > 0) - { - spawnPosition += Vector3.one * 0.15f; - } + if(guaranteedOnlyOnePerKill.Count > 0) { spawnPosition += Vector3.one * 0.15f;