feat: 허수아비 계산 시뮬레이터 추가
- 빌드 입력, 룰셋, 회전 정책, 결과/리포트 모델을 포함한 데미지 계산 시뮬레이터 기반을 추가 - 단일 실행 창과 배치 전수 조사 창, 플레이어 데미지 스윕 메뉴를 추가 - DamageEffect 계산값 접근자를 열어 기존 전투 공식을 시뮬레이터에서 재사용하도록 정리
This commit is contained in:
176
Assets/_Game/Scripts/Editor/BuildSimulationBatchCommands.cs
Normal file
176
Assets/_Game/Scripts/Editor/BuildSimulationBatchCommands.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 허수아비 계산 시뮬레이터의 배치 조사 실행 메뉴입니다.
|
||||
/// </summary>
|
||||
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<string> DisabledPlayerSkillPaths = new HashSet<string>
|
||||
{
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_회전베기.asset",
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_돌진.asset",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 현재 기준 플레이어 단일 슬롯 데미지 전수 조사를 실행합니다.
|
||||
/// </summary>
|
||||
[MenuItem("Tools/Colosseum/Simulation/Run Player Damage Sweep")]
|
||||
private static void RunPlayerDamageSweep()
|
||||
{
|
||||
PassiveTreeData passiveTree = AssetDatabase.LoadAssetAtPath<PassiveTreeData>(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<string> warnings = new List<string>();
|
||||
List<SkillData> skills = LoadPlayerSkills(warnings);
|
||||
List<SkillGemData> gems = LoadAssetsInFolder<SkillGemData>(PlayerGemFolder);
|
||||
List<PassiveNodeData> passiveNodes = LoadAssetsInFolder<PassiveNodeData>(PlayerPassiveFolder);
|
||||
|
||||
List<BuildSimulationInput> 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<SkillData> LoadPlayerSkills(List<string> warnings)
|
||||
{
|
||||
List<SkillData> skills = LoadAssetsInFolder<SkillData>(PlayerSkillFolder);
|
||||
List<SkillData> filtered = new List<SkillData>();
|
||||
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<T> LoadAssetsInFolder<T>(string folderPath) where T : Object
|
||||
{
|
||||
List<T> assets = new List<T>();
|
||||
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<T>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user