Stat & equipment definition continued (WIP)

This commit is contained in:
Pedro Gomes 2025-09-25 21:04:11 +01:00
parent 415e29576b
commit 7f0d4ec7f3
8 changed files with 440 additions and 810 deletions

View File

@ -118,7 +118,7 @@ namespace Kryz.CharacterStats
public class CharacterStat public class CharacterStat
{ {
public GameTag statTag; public GameTag statTag;
public CharacterStatType statType; public StatDefinition statDefinition;
public float BaseValue; public float BaseValue;

View File

@ -1,197 +1,252 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using Kryz.CharacterStats;
namespace Kryz.CharacterStats.Examples namespace Kryz.CharacterStats.Examples
{ {
public class CharacterStats : MonoBehaviour public class CharacterStats : MonoBehaviour
{ {
[Header("---------------------------------------------------------------------------------------------")] [Header("Runtime Stats (Auto-Generated)")]
[Header("Primary Stats:")] [SerializeField] private bool showDebugStats = false;
public CharacterStat Cunning;
public CharacterStat Flow;
public CharacterStat Presence;
//Secondary // Main stats dictionary - all stats live here
[Space] private Dictionary<string, CharacterStat> allStats = new Dictionary<string, CharacterStat>();
[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
// Compatibility dictionaries for existing systems
public Dictionary<string, CharacterStat> primaryStatsDictionary = new Dictionary<string, CharacterStat>(); public Dictionary<string, CharacterStat> primaryStatsDictionary = new Dictionary<string, CharacterStat>();
public Dictionary<string, CharacterStat> secondaryStatsDictionary = new Dictionary<string, CharacterStat>(); public Dictionary<string, CharacterStat> secondaryStatsDictionary = new Dictionary<string, CharacterStat>();
// Events
public UnityEvent onUpdateStatValues = new UnityEvent(); public UnityEvent onUpdateStatValues = new UnityEvent();
public UnityEvent onAllStatsUpdated = 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() protected virtual void Awake()
{ {
Cunning.statType = CharacterStatType.Cunning; InitializeStatsFromRegistry();
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);
onUpdateStatValues.AddListener(UpdateSecondaryStatsBasedOnPrimaryStats); 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) public void IncreaseAllStatPoints(int amount)
{ {
onUpdateStatValues.Invoke(); onUpdateStatValues.Invoke();
} }
public void UpdateSecondaryStatsBasedOnPrimaryStats() public void UpdateSecondaryStatsBasedOnPrimaryStats()
{ {
//Remove previous // Get primary stats
CritChance.RemoveAllModifiersFromSource(GameConstants.ObjectSources.CunningSource); var cunning = GetStat("cunning");
CritDamage.RemoveAllModifiersFromSource(GameConstants.ObjectSources.CunningSource); var flow = GetStat("flow");
MovementSpeed.RemoveAllModifiersFromSource(GameConstants.ObjectSources.CunningSource); var presence = GetStat("presence");
//HealthRegen.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); if (cunning == null || flow == null || presence == null)
MaxMana.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); {
ManaRegen.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); Debug.LogError("CharacterStats: Primary stats not found! Make sure cunning, flow, and presence StatDefinitions exist.");
CooldownReduction.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); return;
AttackSpeed.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource); }
//ResourceCostReduction.RemoveAllModifiersFromSource(GameConstants.ObjectSources.FlowSource);
AreaEffectiveness.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource); // Remove previous modifiers from all affected stats
ReputationGainIncrease.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource); RemovePrimaryStatInfluences();
GoldCostReduction.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource);
AuraPower.RemoveAllModifiersFromSource(GameConstants.ObjectSources.PresenceSource);
//Add refreshed values // Apply Cunning influences
CritChance.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatsBalancing.CritChanceIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); ApplyStatInfluence("critChance", cunning.Value * GameConstants.CharacterStatsBalancing.CritChanceIncreasePerCunning, GameConstants.ObjectSources.CunningSource);
CritDamage.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatsBalancing.CritDamageIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); ApplyStatInfluence("critDamage", cunning.Value * GameConstants.CharacterStatsBalancing.CritDamageIncreasePerCunning, GameConstants.ObjectSources.CunningSource);
//DodgeChance.AddModifier(new StatModifier(Cunning.Value * GameConstants.CharacterStatInfluence.DodgeChanceIncreasePerCunning, StatModType.Flat, GameConstants.ObjectSources.CunningSource)); ApplyStatInfluence("movementSpeed", cunning.Value * GameConstants.CharacterStatsBalancing.MovementSpeedIncreasePerCunning, 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 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()); Debug.Log(this.gameObject.name + " has relative power level of: " + GetRelativePowerLevel());
onAllStatsUpdated.Invoke(); 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();
}
} }
} }

View File

@ -1,4 +1,6 @@
using System.Collections.Generic; using Kryz.CharacterStats;
using System.Collections.Generic;
using System.Linq;
using UnityEngine; using UnityEngine;
public enum WeaponType public enum WeaponType
@ -43,6 +45,8 @@ public static class WeaponTypeExtensions
} }
} }
namespace Kryz.CharacterStats.Examples namespace Kryz.CharacterStats.Examples
{ {
public enum EquipmentType public enum EquipmentType
@ -55,248 +59,9 @@ namespace Kryz.CharacterStats.Examples
Bracers, Bracers,
Gloves, Gloves,
Boots, Boots,
Weapon1, Weapon1,
Weapon2, 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);
}
}
} }

View File

@ -1,244 +1,190 @@
using Kryz.CharacterStats; using Kryz.CharacterStats;
using Kryz.CharacterStats.Examples; using Kryz.CharacterStats.Examples;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEngine; 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] [System.Serializable]
public class EquippableItemInstance : ItemInstance public class EquippableItemInstance : ItemInstance
{ {
public int AttackDamageBonus; [Header("Equipment Identity")]
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]
public EquipmentType EquipmentType; public EquipmentType EquipmentType;
[Space]
// NEW: Add WeaponType for weapon items
public WeaponType WeaponType; public WeaponType WeaponType;
[Space] public string IconPath; // Store the icon path for serialization
[Header("Dynamic Stat System")]
public List<ItemStatBonus> statBonuses = new List<ItemStatBonus>();
[Header("Crafting System")]
public bool CraftableBase = false; public bool CraftableBase = false;
[Space]
/// <summary>
/// Can only contain up to this number of unique stats in a single item instance.
/// </summary>
[Space]
[Tooltip("Can only contain up to this number of unique stats in a single item instance.")]
public int MaxTotalUniqueStatsIncreasedByStones; public int MaxTotalUniqueStatsIncreasedByStones;
public List<string> AddedStoneStats = new List<string>(); public List<string> AddedStoneStats = new List<string>();
// Updated EquippableItemInstance helper properties // Helper properties
public bool IsWeapon => EquipmentType == EquipmentType.Weapon1 || EquipmentType == EquipmentType.Weapon2; 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(); public bool IsTwoHandedWeapon => IsWeapon && WeaponType.IsTwoHanded();
// NEW: Helper property to check if this weapon is one-handed
public bool IsOneHandedWeapon => IsWeapon && WeaponType.IsOneHanded(); public bool IsOneHandedWeapon => IsWeapon && WeaponType.IsOneHanded();
public void Equip(PlayerCharacterStats c) public void Equip(PlayerCharacterStats characterStats)
{ {
if (AttackDamageBonus != 0) foreach (var bonus in statBonuses)
c.AttackDamage.AddModifier(new StatModifier(AttackDamageBonus, StatModType.Flat, this)); {
if (SpellDamageBonus != 0) if (bonus.statDefinition == null || !bonus.HasAnyBonus) continue;
c.SpellDamage.AddModifier(new StatModifier(SpellDamageBonus, StatModType.Flat, this));
if (CritChanceBonus != 0) var stat = characterStats.GetStat(bonus.statDefinition.StatKey);
c.CritChance.AddModifier(new StatModifier(CritChanceBonus, StatModType.Flat, this)); if (stat != null)
if (CritDamageBonus != 0) {
c.CritDamage.AddModifier(new StatModifier(CritDamageBonus, StatModType.Flat, this)); // Apply flat bonus
if (bonus.HasFlatBonus)
{
stat.AddModifier(new StatModifier(bonus.flatValue, StatModType.Flat, this));
}
if (MaxHealthBonus != 0) // Apply percent bonus
c.MaxHealth.AddModifier(new StatModifier(MaxHealthBonus, StatModType.Flat, this)); if (bonus.HasPercentBonus)
if (HealthRegenBonus != 0) {
c.HealthRegen.AddModifier(new StatModifier(HealthRegenBonus, StatModType.Flat, this)); stat.AddModifier(new StatModifier(bonus.percentValue, StatModType.PercentAdd, this));
}
if (MaxManaBonus != 0) }
c.MaxMana.AddModifier(new StatModifier(MaxManaBonus, StatModType.Flat, this)); else
if (ManaRegenBonus != 0) {
c.ManaRegen.AddModifier(new StatModifier(ManaRegenBonus, StatModType.Flat, this)); Debug.LogWarning($"EquippableItemInstance: Stat '{bonus.statDefinition.StatKey}' not found in character stats!");
}
}
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) public void Unequip(PlayerCharacterStats characterStats)
{ {
c.AttackDamage.RemoveAllModifiersFromSource(this); // Remove all modifiers from this item source across all stats
c.SpellDamage.RemoveAllModifiersFromSource(this); var allStats = characterStats.GetAllStats();
foreach (var kvp in allStats)
c.AttackSpeed.RemoveAllModifiersFromSource(this); {
kvp.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);
} }
// 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<ItemStatBonus> 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) public bool CanAddCraftingStone(CraftingStatStone stone)
{ {
if (!CraftableBase) return false; if (!CraftableBase) return false;
var stoneStats = GetNonZeroStats(stone); var stoneStats = GetNonZeroStatsFromStone(stone);
var itemNonZeroStats = GetNonZeroStats(this); 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 // Ensure we don't exceed the max unique stats cap
if (AddedStoneStats.Count + newUniqueStats > MaxTotalUniqueStatsIncreasedByStones) if (AddedStoneStats.Count + newUniqueStats > MaxTotalUniqueStatsIncreasedByStones)
{ {
return false; // Adding the crafting stone would exceed the cap return false;
} }
return true; return true;
} }
public bool TryAddCraftingStone(CraftingStatStone stone) public bool TryAddCraftingStone(CraftingStatStone stone)
{ {
if (!CanAddCraftingStone(stone)) return false; if (!CanAddCraftingStone(stone)) return false;
var stoneStats = GetNonZeroStats(stone); var stoneStats = GetNonZeroStatsFromStone(stone);
var itemStats = GetNonZeroStats(this); 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])) if (!currentStats.Contains(statKey) && !AddedStoneStats.Contains(statKey))
AddedStoneStats.Add(stoneStats[i]); {
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; return true;
} }
private List<string> GetNonZeroStats(object stats) private List<string> GetNonZeroStatsFromStone(CraftingStatStone stone)
{ {
var nonZeroStats = new List<string>(); var nonZeroStats = new List<string>();
var fields = stats.GetType().GetFields(); var fields = stone.GetType().GetFields();
foreach (var field in fields) 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); 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); nonZeroStats.Add(field.Name);
} }
@ -247,216 +193,89 @@ public class EquippableItemInstance : ItemInstance
return nonZeroStats; return nonZeroStats;
} }
//TODO: Add new stats to stones too // This method needs to be updated based on your CraftingStatStone structure
private void AddStats(CraftingStatStone stone) private void AddStatsFromStone(CraftingStatStone stone)
{ {
AttackDamageBonus += stone.AttackDamageBonus; // TODO: Update this to work with the new stat system
SpellDamageBonus += stone.SpellDamageBonus; // You'll need to map stone properties to StatDefinition references
CritChanceBonus += stone.CritChanceBonus; // For now, keeping the old structure as a placeholder
CritDamageBonus += stone.CritDamageBonus; Debug.LogWarning("AddStatsFromStone needs to be updated for the new stat system");
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;
} }
// Constructors
public EquippableItemInstance() 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; EquipmentType = EquipmentType.Helmet;
WeaponType = WeaponType.Sword; // Default weapon type WeaponType = WeaponType.Sword;
CraftableBase = false; CraftableBase = false;
MaxTotalUniqueStatsIncreasedByStones = 0; MaxTotalUniqueStatsIncreasedByStones = 0;
statBonuses = new List<ItemStatBonus>();
AddedStoneStats = new List<string>();
templateIndex = -1; 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; ItemName = itemName;
Icon = template.Icon; Icon = icon;
sellPricePlayer = template.sellPricePlayer; IconPath = iconPath ?? ""; // Store the icon path
sellPriceVendor = template.sellPriceVendor; EquipmentType = equipmentTypeDef.EquipmentType;
description = template.description; WeaponType = equipmentTypeDef.WeaponType;
templateIndex = ItemIndexer.Instance.Items.IndexOf(template); CraftableBase = false; // Set based on your needs
MaxTotalUniqueStatsIncreasedByStones = 6; // Default value
statBonuses = new List<ItemStatBonus>();
AddedStoneStats = new List<string>();
templateIndex = -1;
// Copy all int stats // You can set default sell prices here or pass them as parameters
AttackDamageBonus = template.AttackDamageBonus; sellPricePlayer = 100;
SpellDamageBonus = template.SpellDamageBonus; sellPriceVendor = 50;
CritChanceBonus = template.CritChanceBonus; description = $"A {equipmentTypeDef.GetDisplayName()}";
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;
} }
public EquippableItemInstance(EquippableItem template, bool randomizeStats) // Helper method to set icon with path
public void SetIcon(Sprite icon, string iconPath)
{ {
ItemName = template.ItemName; Icon = icon;
Icon = template.Icon; IconPath = iconPath;
sellPricePlayer = template.sellPricePlayer; }
sellPriceVendor = template.sellPriceVendor;
description = template.description;
templateIndex = ItemIndexer.Instance.Items.IndexOf(template);
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 return $"{equipmentTypeDef.ResourcesPath}/{icon.name}";
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;
} }
else return ""; // Manual icons don't have paths
{ }
// 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;
AttackDamagePercentBonus = template.AttackDamagePercentBonus;
SpellDamagePercentBonus = template.SpellDamagePercentBonus;
AttackSpeedPercentBonus = template.AttackSpeedPercentBonus; // Utility methods for debugging and UI
CritChancePercentBonus = template.CritChancePercentBonus; public string GetStatSummary()
CritDamagePercentBonus = template.CritDamagePercentBonus; {
MaxHealthPercentBonus = template.MaxHealthPercentBonus; if (statBonuses.Count == 0) return "No stat bonuses";
HealthRegenPercentBonus = template.HealthRegenPercentBonus;
MaxManaPercentBonus = template.MaxManaPercentBonus; var summary = new List<string>();
ManaRegenPercentBonus = template.ManaRegenPercentBonus; foreach (var bonus in GetAllNonZeroBonuses())
ArmorPercentBonus = template.ArmorPercentBonus; {
MagicResistancePercentBonus = template.MagicResistancePercentBonus; var parts = new List<string>();
DodgeChancePercentBonus = template.DodgeChancePercentBonus; if (bonus.HasFlatBonus)
BlockChancePercentBonus = template.BlockChancePercentBonus; parts.Add($"+{bonus.flatValue:F0}");
BlockEffectivenessPercentBonus = template.BlockEffectivenessPercentBonus; if (bonus.HasPercentBonus)
AreaEffectivenessPercentBonus = template.AreaEffectivenessPercentBonus; parts.Add($"+{bonus.percentValue:P1}");
CooldownReductionPercentBonus = template.CooldownReductionPercentBonus;
MovementSpeedPercentBonus = template.MovementSpeedPercentBonus; summary.Add($"{bonus.statDefinition.DisplayName}: {string.Join(" ", parts)}");
ReputationGainIncreasePercentBonus = template.ReputationGainIncreasePercentBonus;
GoldCostReductionPercentBonus = template.GoldCostReductionPercentBonus;
} }
EquipmentType = template.EquipmentType; return string.Join("\n", summary);
WeaponType = template.WeaponType; }
CraftableBase = template.CraftableBase; [ContextMenu("Log Stat Summary")]
MaxTotalUniqueStatsIncreasedByStones = template.MaxTotalUniqueStatsIncreasedByStones; private void LogStatSummary()
{
Debug.Log($"=== {ItemName} Stats ===\n{GetStatSummary()}");
} }
} }

View File

@ -14,11 +14,7 @@ namespace Kryz.CharacterStats.Examples
public static ItemInstance ConvertTemplateIntoInstance(Item template) public static ItemInstance ConvertTemplateIntoInstance(Item template)
{ {
if(template is EquippableItem item) if(template is HiddenMap map)
{
return new EquippableItemInstance(item, item.CraftableBase);
}
else if(template is HiddenMap map)
{ {
return new HiddenMapInstance(map); return new HiddenMapInstance(map);
} }
@ -30,10 +26,6 @@ namespace Kryz.CharacterStats.Examples
} }
public static ItemInstance ConvertTemplateIntoInstance(Item template, int templateIndex) public static ItemInstance ConvertTemplateIntoInstance(Item template, int templateIndex)
{ {
if (template is EquippableItem item)
{
return new EquippableItemInstance(item, item.CraftableBase);
}
return new ItemInstance(template, templateIndex); return new ItemInstance(template, templateIndex);
} }
} }

View File

@ -107,8 +107,8 @@ namespace Kryz.CharacterStats.Examples
statPanel = FindObjectOfType<StatPanel>(); statPanel = FindObjectOfType<StatPanel>();
statPanel.SetPlayerStats(this); statPanel.SetPlayerStats(this);
statPanel.SetStats(Cunning, Flow, Presence); statPanel.SetStats(primaryStatsDictionary);
statPanel.SetSecondaryStats(AttackDamage, SpellDamage, AttackSpeed, CritChance, CritDamage, AuraPower, MaxHealth, HealthRegen, MaxMana, ManaRegen, Armor, MagicResistance, DodgeChance, BlockChance, BlockEffectiveness, AreaEffectiveness, CooldownReduction, MovementSpeed, ReputationGainIncrease, GoldCostReduction); statPanel.SetSecondaryStats(secondaryStatsDictionary);
//TODO: statPanel set misc stats (area, cdr, movespeed, rep, gold //TODO: statPanel set misc stats (area, cdr, movespeed, rep, gold
statPanel.UpdateStatValues(); statPanel.UpdateStatValues();
statPanel.SetCharacterInfo(PlayerDataHandler.Instance.currentCharacterName.Value, level.currentLevel.ToString(), reputationLevel.currentLevel.ToString()); statPanel.SetCharacterInfo(PlayerDataHandler.Instance.currentCharacterName.Value, level.currentLevel.ToString(), reputationLevel.currentLevel.ToString());
@ -294,11 +294,10 @@ namespace Kryz.CharacterStats.Examples
public void UpdateStatsBasedOnLevel() public void UpdateStatsBasedOnLevel()
{ {
GetStat("MaxHealth").RemoveAllModifiersFromSource(GameConstants.ObjectSources.LevelSource);
MaxHealth.RemoveAllModifiersFromSource(GameConstants.ObjectSources.LevelSource);
//Debug.Log("MAX HEALTH BASE VALUE: " + MaxHealth.BaseValue); //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(); onUpdateStatValues.Invoke();

View File

@ -1,4 +1,6 @@
using TMPro; using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@ -52,20 +54,23 @@ namespace Kryz.CharacterStats.Examples
this.playerStats = playerStats; this.playerStats = playerStats;
} }
public void SetStats(params CharacterStat[] charStats) public void SetStats(Dictionary<string, CharacterStat> 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!"); var stat = primaryStats.Values.ElementAt(i);
return; 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(false);
statDisplays[i].gameObject.SetActive(i < stats.Length);
} }
unallocated.ValueText.text = playerStats.AvailablePointsToAllocate.ToString(); unallocated.ValueText.text = playerStats.AvailablePointsToAllocate.ToString();
@ -79,19 +84,25 @@ namespace Kryz.CharacterStats.Examples
addStatButtons[2].onClick.AddListener(() => AllocateStat(2)); addStatButtons[2].onClick.AddListener(() => AllocateStat(2));
resetAllocated.onClick.AddListener(ResetAllocatedStats); resetAllocated.onClick.AddListener(ResetAllocatedStats);
} }
public void SetSecondaryStats(params CharacterStat[] secondaryStats) public void SetSecondaryStats(Dictionary<string, CharacterStat> 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!"); if (index >= secondaryStatDisplays.Length) break;
return;
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);
} }
} }

View File

@ -13,10 +13,8 @@ public class DropTable : MonoBehaviour
[SerializeField] private GameEvent_Int onCoinDrop; [SerializeField] private GameEvent_Int onCoinDrop;
public List<EquippableItem> guaranteedItemDrop = new List<EquippableItem>();
[Header("Only one of the following list is guaranteed to drop.")] [Header("Only one of the following list is guaranteed to drop.")]
public List<WeightedDrop> guaranteedOnlyOnePerKill = new List<WeightedDrop>(); public List<WeightedDrop> guaranteedOnlyOnePerKill = new List<WeightedDrop>();
public List<EquippableItem> extraDrops = new List<EquippableItem>();
public List<Item> nonEquippablesDrops = new List<Item>(); public List<Item> nonEquippablesDrops = new List<Item>();
[Space] [Space]
@ -66,17 +64,11 @@ public class DropTable : MonoBehaviour
onCoinDrop.Raise(finalCoinAmount); onCoinDrop.Raise(finalCoinAmount);
for (int i = 0; i < guaranteedItemDrop.Count; i++)
{
instantiatedDrop = Instantiate(interactableDropPrefab, spawnPosition, this.transform.rotation);
itemDrop = instantiatedDrop.GetComponent<EquippableItemDrop>();
itemDrop.itemDrop = Item.ConvertTemplateIntoInstance(guaranteedItemDrop[i]);
}
if (guaranteedOnlyOnePerKill.Count > 0 && WeightedDrop.HaveDropsForDifficulty(weightedDropLootTable, GameDifficultyController.Instance.GetCurrentDifficultyLevel())) if (guaranteedOnlyOnePerKill.Count > 0 && WeightedDrop.HaveDropsForDifficulty(weightedDropLootTable, GameDifficultyController.Instance.GetCurrentDifficultyLevel()))
{ {
possibleItem = WeightedDrop.GetRandomDrop(guaranteedOnlyOnePerKill, 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; spawnPosition.y = 0f;
instantiatedDrop = Instantiate(interactableDropPrefab, spawnPosition, this.transform.rotation); instantiatedDrop = Instantiate(interactableDropPrefab, spawnPosition, this.transform.rotation);
itemDrop = instantiatedDrop.GetComponent<EquippableItemDrop>(); itemDrop = instantiatedDrop.GetComponent<EquippableItemDrop>();
@ -109,10 +101,7 @@ public class DropTable : MonoBehaviour
if (possibleItem == null) return; if (possibleItem == null) return;
spawnPosition = this.transform.position; spawnPosition = this.transform.position;
if (guaranteedItemDrop.Count > 0)
{
spawnPosition += Vector3.one * 0.15f;
}
if(guaranteedOnlyOnePerKill.Count > 0) if(guaranteedOnlyOnePerKill.Count > 0)
{ {
spawnPosition += Vector3.one * 0.15f; spawnPosition += Vector3.one * 0.15f;