using System.Collections.Generic; using System.IO; using System.Text; using UnityEditor; using UnityEngine; using Colosseum.Combat.Simulation; using Colosseum.Passives; using Colosseum.Skills; namespace Colosseum.Editor { /// /// 허수아비 계산 시뮬레이터의 배치 조사 실행 메뉴입니다. /// public static class BuildSimulationBatchCommands { private const string PlayerSkillFolder = "Assets/_Game/Data/Skills"; private const string PlayerGemFolder = "Assets/_Game/Data/SkillGems"; private const string PlayerPassiveFolder = "Assets/_Game/Data/Passives/Nodes"; private const string PlayerPassiveTreePath = "Assets/_Game/Data/Passives/Data_PassiveTree_Player_Prototype.asset"; private const string ReportFolder = "BuildSimulationReports"; private static readonly HashSet DisabledPlayerSkillPaths = new HashSet { "Assets/_Game/Data/Skills/Data_Skill_Player_회전베기.asset", "Assets/_Game/Data/Skills/Data_Skill_Player_돌진.asset", }; /// /// 현재 기준 플레이어 단일 슬롯 데미지 전수 조사를 실행합니다. /// [MenuItem("Tools/Colosseum/Simulation/Run Player Damage Sweep")] private static void RunPlayerDamageSweep() { PassiveTreeData passiveTree = AssetDatabase.LoadAssetAtPath(PlayerPassiveTreePath); if (passiveTree == null) { Debug.LogError($"[BuildSimulationBatch] 패시브 트리를 찾지 못했습니다. | Path={PlayerPassiveTreePath}"); return; } BuildSimulationInput template = new BuildSimulationInput(); template.SetBuildName("플레이어_단일슬롯_데미지전수"); SimulationRuleSet ruleSet = new SimulationRuleSet(); ruleSet.Configure("PlayerDamageSweep10s", 10f, 1, 0f, 0f); RotationPolicy rotationPolicy = new RotationPolicy(); rotationPolicy.Configure("Slot0Only", new[] { 0 }, false, 0, false, 5, 0f); SimulationCombinationSpec combinationSpec = new SimulationCombinationSpec(); combinationSpec.Configure( "PlayerDamageSweep", combineSkills: true, combineGems: true, combinePassives: true, activeSlotIndices: new[] { 0 }, allowDuplicateSkills: false, includeEmptyGemSet: true, passiveTree: passiveTree, includeEmptyPassiveSelection: true, maxPassiveNodeCount: 0, maxBuildCount: 50000); List warnings = new List(); List skills = LoadPlayerSkills(warnings); List gems = LoadAssetsInFolder(PlayerGemFolder); List passiveNodes = LoadAssetsInFolder(PlayerPassiveFolder); List builds = SimulationCombinationGenerator.GenerateBuilds( template, combinationSpec, skills, gems, passiveNodes, warnings, out bool truncated); SimulationBatchResult result = SimulationBatchRunner.Run( combinationSpec.BatchName, builds, ruleSet, rotationPolicy, warnings, truncated); string reportDirectory = Path.Combine(Path.GetDirectoryName(Application.dataPath) ?? Application.dataPath, ReportFolder); Directory.CreateDirectory(reportDirectory); string timestamp = System.DateTime.Now.ToString("yyyyMMdd_HHmmss"); string markdownPath = Path.Combine(reportDirectory, $"PlayerDamageSweep_{timestamp}.md"); string csvPath = Path.Combine(reportDirectory, $"PlayerDamageSweep_{timestamp}.csv"); File.WriteAllText(markdownPath, SimulationBatchReportUtility.BuildMarkdown(result), Encoding.UTF8); File.WriteAllText(csvPath, SimulationBatchReportUtility.BuildCsv(result), Encoding.UTF8); Debug.Log(BuildSummary(result, markdownPath, csvPath)); } private static List LoadPlayerSkills(List warnings) { List skills = LoadAssetsInFolder(PlayerSkillFolder); List filtered = new List(); for (int i = 0; i < skills.Count; i++) { SkillData skill = skills[i]; if (skill == null || !skill.name.StartsWith("Data_Skill_Player_", System.StringComparison.Ordinal)) continue; string assetPath = AssetDatabase.GetAssetPath(skill); if (DisabledPlayerSkillPaths.Contains(assetPath)) { warnings?.Add($"애니메이션 미구현 스킬 제외: {skill.SkillName}"); continue; } filtered.Add(skill); } return filtered; } private static List LoadAssetsInFolder(string folderPath) where T : Object { List assets = new List(); string[] guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}", new[] { folderPath }); for (int i = 0; i < guids.Length; i++) { string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]); T asset = AssetDatabase.LoadAssetAtPath(assetPath); if (asset != null) assets.Add(asset); } return assets; } private static string BuildSummary(SimulationBatchResult result, string markdownPath, string csvPath) { StringBuilder builder = new StringBuilder(); builder.Append("[BuildSimulationBatch] 플레이어 단일 슬롯 데미지 전수 조사 완료"); builder.Append(" | Builds="); builder.Append(result.GeneratedBuildCount); builder.Append(" | Truncated="); builder.Append(result.Truncated); builder.Append(" | Markdown="); builder.Append(markdownPath); builder.Append(" | CSV="); builder.Append(csvPath); int topCount = Mathf.Min(10, result.Entries.Count); for (int i = 0; i < topCount; i++) { SimulationBatchEntry entry = result.Entries[i]; SimulationResult simulation = entry != null ? entry.Result : null; if (simulation == null) continue; builder.AppendLine(); builder.Append('#'); builder.Append(i + 1); builder.Append(' '); builder.Append(entry.BuildLabel); builder.Append(" | DPS="); builder.Append(simulation.AverageDps.ToString("0.##")); builder.Append(" | Dmg="); builder.Append(simulation.TotalDamage.ToString("0.##")); builder.Append(" | Mana="); builder.Append(simulation.TotalManaUsed.ToString("0.##")); } return builder.ToString(); } } }