first commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
public enum ModifierType
|
||||
{
|
||||
FlatAdd,
|
||||
PercentAdd,
|
||||
PercentMultiply
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17d0bd4f5e38d384e85abdd7bb8a32db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerStats : MonoBehaviour
|
||||
{
|
||||
[Header("Base Values")]
|
||||
[SerializeField] private float baseMaxHealth = 100f;
|
||||
[SerializeField] private float baseMaxMana = 100f;
|
||||
[SerializeField] private float baseDamage = 10f;
|
||||
[SerializeField] private float baseMoveSpeed = 5f;
|
||||
[SerializeField] private float baseAbilityCooldownReduction = 0f;
|
||||
[SerializeField] private float baseCriticalChance = 0f;
|
||||
|
||||
[Header("Runtime Modifiers")]
|
||||
[SerializeField] private List<StatModifier> activeModifiers = new List<StatModifier>();
|
||||
|
||||
private readonly Dictionary<StatType, float> baseValues = new Dictionary<StatType, float>();
|
||||
private readonly Dictionary<StatType, float> currentValues = new Dictionary<StatType, float>();
|
||||
|
||||
public event Action OnStatsChanged;
|
||||
|
||||
public float MaxHealth { get { return GetStat(StatType.MaxHealth); } }
|
||||
public float MaxMana { get { return GetStat(StatType.MaxMana); } }
|
||||
public float Damage { get { return GetStat(StatType.Damage); } }
|
||||
public float MoveSpeed { get { return GetStat(StatType.MoveSpeed); } }
|
||||
public float AbilityCooldownReduction { get { return GetStat(StatType.AbilityCooldownReduction); } }
|
||||
public float CriticalChance { get { return GetStat(StatType.CriticalChance); } }
|
||||
|
||||
// Compatibility-friendly alias for lab wording: player speed is driven by PlayerStats.speed.
|
||||
public float speed { get { return MoveSpeed; } }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ClampBaseValues();
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
public IReadOnlyList<StatModifier> GetActiveModifiers()
|
||||
{
|
||||
return activeModifiers;
|
||||
}
|
||||
|
||||
public float GetBaseStat(StatType statType)
|
||||
{
|
||||
EnsureBaseValues();
|
||||
return baseValues.TryGetValue(statType, out float value) ? value : 0f;
|
||||
}
|
||||
|
||||
public float GetStat(StatType statType)
|
||||
{
|
||||
if (currentValues.Count == 0)
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
return currentValues.TryGetValue(statType, out float value) ? value : GetBaseStat(statType);
|
||||
}
|
||||
|
||||
public void AddModifier(StatModifier modifier)
|
||||
{
|
||||
if (!IsValidNumber(modifier.value))
|
||||
{
|
||||
Debug.LogWarning($"Ignored invalid modifier value for {modifier.statType}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(modifier.sourceId))
|
||||
{
|
||||
modifier.sourceId = "Runtime";
|
||||
}
|
||||
|
||||
activeModifiers.Add(modifier);
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
public int RemoveModifiersBySource(string sourceId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = activeModifiers.RemoveAll(modifier => modifier.sourceId == sourceId);
|
||||
if (removed > 0)
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public int RemoveModifiersBySourcePrefix(string sourcePrefix)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourcePrefix))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = activeModifiers.RemoveAll(modifier =>
|
||||
!string.IsNullOrEmpty(modifier.sourceId) &&
|
||||
modifier.sourceId.StartsWith(sourcePrefix, StringComparison.Ordinal));
|
||||
|
||||
if (removed > 0)
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void ClearModifiers()
|
||||
{
|
||||
if (activeModifiers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeModifiers.Clear();
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
public void RecalculateStats()
|
||||
{
|
||||
ClampBaseValues();
|
||||
EnsureBaseValues();
|
||||
currentValues.Clear();
|
||||
|
||||
foreach (StatType statType in Enum.GetValues(typeof(StatType)))
|
||||
{
|
||||
float baseValue = GetBaseStat(statType);
|
||||
float flatAdd = 0f;
|
||||
float percentAdd = 0f;
|
||||
float percentMultiply = 1f;
|
||||
|
||||
for (int i = 0; i < activeModifiers.Count; i++)
|
||||
{
|
||||
StatModifier modifier = activeModifiers[i];
|
||||
if (modifier.statType != statType || !IsValidNumber(modifier.value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (modifier.modifierType)
|
||||
{
|
||||
case ModifierType.FlatAdd:
|
||||
flatAdd += modifier.value;
|
||||
break;
|
||||
case ModifierType.PercentAdd:
|
||||
percentAdd += modifier.value;
|
||||
break;
|
||||
case ModifierType.PercentMultiply:
|
||||
percentMultiply *= Mathf.Max(0f, 1f + modifier.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float finalValue = (baseValue + flatAdd) * Mathf.Max(0f, 1f + percentAdd) * percentMultiply;
|
||||
currentValues[statType] = ClampFinalValue(statType, finalValue);
|
||||
}
|
||||
|
||||
OnStatsChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void EnsureBaseValues()
|
||||
{
|
||||
baseValues[StatType.MaxHealth] = Mathf.Max(0f, baseMaxHealth);
|
||||
baseValues[StatType.MaxMana] = Mathf.Max(0f, baseMaxMana);
|
||||
baseValues[StatType.Damage] = Mathf.Max(0f, baseDamage);
|
||||
baseValues[StatType.MoveSpeed] = Mathf.Max(0.1f, baseMoveSpeed);
|
||||
baseValues[StatType.AbilityCooldownReduction] = Mathf.Clamp(baseAbilityCooldownReduction, 0f, 100f);
|
||||
baseValues[StatType.CriticalChance] = Mathf.Clamp(baseCriticalChance, 0f, 100f);
|
||||
}
|
||||
|
||||
private void ClampBaseValues()
|
||||
{
|
||||
baseMaxHealth = Mathf.Max(0f, baseMaxHealth);
|
||||
baseMaxMana = Mathf.Max(0f, baseMaxMana);
|
||||
baseDamage = Mathf.Max(0f, baseDamage);
|
||||
baseMoveSpeed = Mathf.Max(0.1f, baseMoveSpeed);
|
||||
baseAbilityCooldownReduction = Mathf.Clamp(baseAbilityCooldownReduction, 0f, 100f);
|
||||
baseCriticalChance = Mathf.Clamp(baseCriticalChance, 0f, 100f);
|
||||
}
|
||||
|
||||
private float ClampFinalValue(StatType statType, float value)
|
||||
{
|
||||
if (!IsValidNumber(value))
|
||||
{
|
||||
return GetBaseStat(statType);
|
||||
}
|
||||
|
||||
switch (statType)
|
||||
{
|
||||
case StatType.MoveSpeed:
|
||||
return Mathf.Max(0.1f, value);
|
||||
case StatType.AbilityCooldownReduction:
|
||||
case StatType.CriticalChance:
|
||||
return Mathf.Clamp(value, 0f, 100f);
|
||||
default:
|
||||
return Mathf.Max(0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidNumber(float value)
|
||||
{
|
||||
return !float.IsNaN(value) && !float.IsInfinity(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bea8fb1929119353ea53e65980138a53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
[Serializable]
|
||||
public struct StatModifier
|
||||
{
|
||||
public StatType statType;
|
||||
public ModifierType modifierType;
|
||||
public float value;
|
||||
public string sourceId;
|
||||
|
||||
public StatModifier(StatType statType, ModifierType modifierType, float value, string sourceId = "")
|
||||
{
|
||||
this.statType = statType;
|
||||
this.modifierType = modifierType;
|
||||
this.value = value;
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48d0d2852923565a596a441444ff0153
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
public enum StatType
|
||||
{
|
||||
MaxHealth,
|
||||
MaxMana,
|
||||
Damage,
|
||||
MoveSpeed,
|
||||
AbilityCooldownReduction,
|
||||
CriticalChance
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f45916503d6cc198badac0de1d5845f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user