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