using BumpCombat.Player; using BumpCombat.Core; using System; using System.Globalization; using UnityEngine; namespace BumpCombat.Progression { [CreateAssetMenu( fileName = "ModifierDefinition", menuName = "BumpCombat/Progression/Modifier Definition")] public sealed class ModifierDefinition : ScriptableObject { [SerializeField] private string modifierId; [SerializeField] private string displayName; [SerializeField] private CharacterStat stat; [SerializeField] private ModifierOperation operation; [SerializeField] private float value; [SerializeField, Min(1)] private int maximumStacks = 1; [SerializeField, Min(0f)] private float healOnApply; public string ModifierId => modifierId; public string DisplayName => displayName; public CharacterStat Stat => stat; public ModifierOperation Operation => operation; public float Value => value; public int MaximumStacks => maximumStacks; public float HealOnApply => healOnApply; public string GetKoreanDisplayName() { return $"{GetKoreanStatName(stat)} 강화"; } public string GetKoreanEffectDescription(float effectMultiplier) { string statName = GetKoreanStatName(stat); string effectValue = operation == ModifierOperation.Flat ? FormatSignedValue(value * effectMultiplier) : $"{FormatSignedValue(value * effectMultiplier * 100f)}%"; string description = $"{statName} {effectValue}"; if (healOnApply > 0f) { description += $"\n즉시 체력 회복 {FormatSignedValue(healOnApply * effectMultiplier)}"; } return description + "\n선택하면 즉시 적용됩니다."; } private static string GetKoreanStatName(CharacterStat targetStat) { switch (targetStat) { case CharacterStat.CollisionDamage: return "몸통박치기 피해"; case CharacterStat.MoveSpeed: return "이동 속도"; case CharacterStat.MaxHealth: return "최대 체력"; case CharacterStat.Armor: return "방어력"; case CharacterStat.LifeSteal: return "생명력 흡수"; case CharacterStat.ExperienceGain: return "경험치 획득량"; case CharacterStat.ExperiencePickupRange: return "경험치 획득 범위"; case CharacterStat.ArtifactGaugeGain: return "유물 게이지 획득량"; case CharacterStat.IncomingDamage: return "받는 피해"; case CharacterStat.AttackDamage: return "공격 피해"; default: return "능력치"; } } private static string FormatSignedValue(float amount) { if (Mathf.Approximately(amount, 0f)) { return "0"; } string formatted = amount.ToString("0.##", CultureInfo.InvariantCulture); return amount > 0f ? $"+{formatted}" : formatted; } public bool CanApply(PlayerStats playerStats) { return CanApplyToModel(playerStats); } public bool CanApplyToModel(CharacterModel model) { return model != null && model.GetStackCount(modifierId, stat) < maximumStacks; } public bool TryApply( PlayerStats playerStats, PlayerHealth playerHealth) { Action heal = playerHealth == null ? null : amount => playerHealth.Heal(amount); return TryApplyToModel(playerStats, heal); } public bool TryApplyToModel( CharacterModel model, Action heal) { return TryApplyToModel(model, heal, 1f); } public bool TryApplyToModel( CharacterModel model, Action heal, float effectMultiplier) { if (float.IsNaN(effectMultiplier) || float.IsInfinity(effectMultiplier) || effectMultiplier < 0f) { return false; } if (!CanApplyToModel(model) || !model.AddModifier(new StatModifier( modifierId, stat, operation, value * effectMultiplier, maximumStacks))) { return false; } if (healOnApply > 0f && heal != null) { heal(healOnApply * effectMultiplier); } return true; } #if UNITY_EDITOR public void Configure( string id, string label, CharacterStat targetStat, ModifierOperation modifierOperation, float modifierValue, int maxStacks, float immediateHeal = 0f) { modifierId = id; displayName = label; stat = targetStat; operation = modifierOperation; value = modifierValue; maximumStacks = Mathf.Max(1, maxStacks); healOnApply = Mathf.Max(0f, immediateHeal); } #endif } }