using System.Collections.Generic; using System.Text; using UnityEngine; using Colosseum.Skills; using Colosseum.Stats; namespace Colosseum.Passives { /// /// 패시브 노드와 효과를 UI 친화적인 문자열로 변환합니다. /// public static class PassivePresentationUtility { public static string GetBranchLabel(PassiveNodeBranch branch) { return branch switch { PassiveNodeBranch.Common => "공통", PassiveNodeBranch.Attack => "공격", PassiveNodeBranch.Defense => "방어", PassiveNodeBranch.Support => "지원", PassiveNodeBranch.Bridge => "연결", _ => "미분류", }; } public static string GetNodeKindLabel(PassiveNodeKind nodeKind) { return nodeKind switch { PassiveNodeKind.Hub => "허브", PassiveNodeKind.Axis => "축 노드", PassiveNodeKind.Bridge => "브릿지", PassiveNodeKind.Capstone => "완성 노드", _ => "노드", }; } public static string GetAxisSummary(PassiveAxisMask axisMask) { if (axisMask == PassiveAxisMask.None) return "중립"; if (axisMask == PassiveAxisMask.All) return "공격 / 방어 / 지원"; List labels = new(); if ((axisMask & PassiveAxisMask.Attack) != 0) labels.Add("공격"); if ((axisMask & PassiveAxisMask.Defense) != 0) labels.Add("방어"); if ((axisMask & PassiveAxisMask.Support) != 0) labels.Add("지원"); return labels.Count > 0 ? string.Join(" / ", labels) : "중립"; } public static string GetStatLabel(StatType statType) { return statType switch { StatType.Strength => "근력 (STR)", StatType.Dexterity => "민첩 (DEX)", StatType.Intelligence => "지능 (INT)", StatType.Vitality => "활력 (VIT)", StatType.Wisdom => "지혜 (WIS)", StatType.Spirit => "정신 (SPI)", _ => "알 수 없는 스탯", }; } public static string BuildNodeSummary(PassiveNodeData node) { if (node == null) return string.Empty; StringBuilder builder = new StringBuilder(); builder.Append(node.DisplayName); builder.Append('\n'); builder.Append(GetAxisSummary(node.AxisMask)); builder.Append(" | 비용 "); builder.Append(node.Cost); string effectSummary = BuildEffectSummary(node); if (!string.IsNullOrWhiteSpace(effectSummary)) { builder.Append('\n'); builder.Append(effectSummary); } return builder.ToString(); } public static string BuildNodeDetail(PassiveNodeData node) { if (node == null) return "노드를 선택하면 설명을 표시합니다."; StringBuilder builder = new StringBuilder(); builder.AppendLine($"{node.DisplayName} [{GetBranchLabel(node.Branch)}]"); builder.AppendLine($"{GetNodeKindLabel(node.NodeKind)} | 축: {GetAxisSummary(node.AxisMask)}"); builder.AppendLine($"티어 {node.Tier} | 비용 {node.Cost}"); if (!string.IsNullOrWhiteSpace(node.Description)) { builder.AppendLine(); builder.AppendLine(node.Description.Trim()); } if (node.Effects != null && node.Effects.Count > 0) { builder.AppendLine(); builder.AppendLine("효과"); for (int i = 0; i < node.Effects.Count; i++) { PassiveEffectEntry effect = node.Effects[i]; if (effect == null) continue; builder.Append("• "); builder.AppendLine(GetEffectLabel(effect)); } } if (node.PrerequisiteNodes != null && node.PrerequisiteNodes.Count > 0) { builder.AppendLine(); builder.AppendLine("선행 노드"); for (int i = 0; i < node.PrerequisiteNodes.Count; i++) { PassiveNodeData prerequisiteNode = node.PrerequisiteNodes[i]; if (prerequisiteNode == null) continue; builder.Append("• "); builder.AppendLine(prerequisiteNode.DisplayName); } } return builder.ToString().TrimEnd(); } public static string BuildEffectSummary(PassiveNodeData node) { if (node?.Effects == null || node.Effects.Count <= 0) return "효과 없음"; StringBuilder builder = new StringBuilder(); int writtenCount = 0; for (int i = 0; i < node.Effects.Count; i++) { PassiveEffectEntry effect = node.Effects[i]; if (effect == null) continue; if (writtenCount > 0) builder.Append(" / "); builder.Append(GetEffectLabel(effect)); writtenCount++; } return writtenCount > 0 ? builder.ToString() : "효과 없음"; } public static string GetEffectLabel(PassiveEffectEntry effect) { if (effect == null) return string.Empty; string label = effect.EffectType switch { PassiveEffectType.StatModifier => BuildStatModifierLabel(effect), PassiveEffectType.DamageMultiplier => $"공격 피해 {FormatMultiplierDelta(effect.Value)}", PassiveEffectType.HealMultiplier => $"회복량 {FormatMultiplierDelta(effect.Value)}", PassiveEffectType.ShieldDoneMultiplier => $"보호막 부여량 {FormatMultiplierDelta(effect.Value)}", PassiveEffectType.ShieldReceivedMultiplier => $"보호막 수혜량 {FormatMultiplierDelta(effect.Value)}", PassiveEffectType.ThreatGeneratedMultiplier => $"위협 생성 {FormatMultiplierDelta(effect.Value)}", PassiveEffectType.IncomingDamageMultiplier => $"받는 피해 {FormatMultiplierDelta(effect.Value)}", PassiveEffectType.ManaCostMultiplier => $"마나 비용 {FormatMultiplierDelta(effect.Value)}", _ => $"알 수 없는 효과 ({effect.EffectType})", }; if (effect.SkillRoleMask != SkillRoleType.None && effect.SkillRoleMask != SkillRoleType.All) { label += $" ({SkillClassificationUtility.GetAllowedRoleSummary(effect.SkillRoleMask)} 스킬 한정)"; } return label; } private static string BuildStatModifierLabel(PassiveEffectEntry effect) { string statLabel = GetStatLabel(effect.StatType); return effect.ModType switch { StatModType.Flat => $"{statLabel} {FormatFlatValue(effect.Value)}", StatModType.PercentAdd => $"{statLabel} {FormatSignedPercent(effect.Value * 100f)}", StatModType.PercentMult => $"{statLabel} {FormatSignedPercent((effect.Value - 1f) * 100f)}", _ => $"{statLabel} {effect.Value:0.##}", }; } private static string FormatMultiplierDelta(float multiplier) { return FormatSignedPercent((multiplier - 1f) * 100f); } private static string FormatSignedPercent(float percentValue) { string sign = percentValue >= 0f ? "+" : string.Empty; return $"{sign}{percentValue:0.##}%"; } private static string FormatFlatValue(float value) { string sign = value >= 0f ? "+" : string.Empty; return $"{sign}{value:0.##}"; } } }