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 GameTag statTag;
public CharacterStatType statType;
public StatDefinition statDefinition;
public float BaseValue;

View File

@ -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<string, CharacterStat> allStats = new Dictionary<string, CharacterStat>();
// Compatibility dictionaries for existing systems
public Dictionary<string, CharacterStat> primaryStatsDictionary = new Dictionary<string, CharacterStat>();
public Dictionary<string, CharacterStat> secondaryStatsDictionary = new Dictionary<string, CharacterStat>();
// 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();
}
}
}

View File

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

View File

@ -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<ItemStatBonus> statBonuses = new List<ItemStatBonus>();
[Header("Crafting System")]
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 List<string> AddedStoneStats = new List<string>();
// 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<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)
{
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<string> GetNonZeroStats(object stats)
private List<string> GetNonZeroStatsFromStone(CraftingStatStone stone)
{
var nonZeroStats = new List<string>();
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<ItemStatBonus>();
AddedStoneStats = new List<string>();
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<ItemStatBonus>();
AddedStoneStats = new List<string>();
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<string>();
foreach (var bonus in GetAllNonZeroBonuses())
{
var parts = new List<string>();
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()}");
}
}

View File

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

View File

@ -107,8 +107,8 @@ namespace Kryz.CharacterStats.Examples
statPanel = FindObjectOfType<StatPanel>();
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();

View File

@ -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<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!");
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<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!");
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);
}
}

View File

@ -13,10 +13,8 @@ public class DropTable : MonoBehaviour
[SerializeField] private GameEvent_Int onCoinDrop;
public List<EquippableItem> guaranteedItemDrop = new List<EquippableItem>();
[Header("Only one of the following list is guaranteed to drop.")]
public List<WeightedDrop> guaranteedOnlyOnePerKill = new List<WeightedDrop>();
public List<EquippableItem> extraDrops = new List<EquippableItem>();
public List<Item> nonEquippablesDrops = new List<Item>();
[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<EquippableItemDrop>();
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<EquippableItemDrop>();
@ -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;