Files
Colosseum/Assets/_Game/Scripts/Combat/Simulation/SimulationBatchReportUtility.cs
dal4segno 285da31047 feat: 허수아비 계산 시뮬레이터 추가
- 빌드 입력, 룰셋, 회전 정책, 결과/리포트 모델을 포함한 데미지 계산 시뮬레이터 기반을 추가
- 단일 실행 창과 배치 전수 조사 창, 플레이어 데미지 스윕 메뉴를 추가
- DamageEffect 계산값 접근자를 열어 기존 전투 공식을 시뮬레이터에서 재사용하도록 정리
2026-03-28 15:07:09 +09:00

163 lines
6.6 KiB
C#

using System.Text;
namespace Colosseum.Combat.Simulation
{
/// <summary>
/// 배치 시뮬레이션 결과를 추출용 문자열로 변환합니다.
/// </summary>
public static class SimulationBatchReportUtility
{
/// <summary>
/// Markdown 리포트를 생성합니다.
/// </summary>
public static string BuildMarkdown(SimulationBatchResult result)
{
if (result == null)
return string.Empty;
StringBuilder builder = new StringBuilder();
builder.Append("# 허수아비 배치 시뮬레이션 결과");
builder.AppendLine();
builder.AppendLine();
builder.Append("- Batch: ");
builder.Append(result.BatchName);
builder.AppendLine();
builder.Append("- Generated Builds: ");
builder.Append(result.GeneratedBuildCount);
builder.AppendLine();
builder.Append("- Truncated: ");
builder.Append(result.Truncated ? "Yes" : "No");
builder.AppendLine();
builder.AppendLine();
builder.AppendLine("| 순위 | 빌드 | DPS | 총 피해 | 총 마나 | 첫 사이클 |");
builder.AppendLine("| --- | --- | ---: | ---: | ---: | ---: |");
for (int i = 0; i < result.Entries.Count; i++)
{
SimulationBatchEntry entry = result.Entries[i];
SimulationResult simulation = entry != null ? entry.Result : null;
if (simulation == null)
continue;
builder.Append("| ");
builder.Append(i + 1);
builder.Append(" | ");
builder.Append(entry.BuildLabel);
builder.Append(" | ");
builder.Append(simulation.AverageDps.ToString("0.##"));
builder.Append(" | ");
builder.Append(simulation.TotalDamage.ToString("0.##"));
builder.Append(" | ");
builder.Append(simulation.TotalManaUsed.ToString("0.##"));
builder.Append(" | ");
builder.Append(simulation.FirstCycleEndTime >= 0f ? simulation.FirstCycleEndTime.ToString("0.##") + "s" : "미완료");
builder.AppendLine(" |");
}
if (result.Warnings.Count > 0)
{
builder.AppendLine();
builder.AppendLine("## 경고");
builder.AppendLine();
for (int i = 0; i < result.Warnings.Count; i++)
{
builder.Append("- ");
builder.Append(result.Warnings[i]);
builder.AppendLine();
}
}
return builder.ToString().TrimEnd();
}
/// <summary>
/// CSV 리포트를 생성합니다.
/// </summary>
public static string BuildCsv(SimulationBatchResult result)
{
if (result == null)
return string.Empty;
StringBuilder builder = new StringBuilder();
builder.AppendLine("Rank,BuildLabel,RuleName,RotationName,DurationSeconds,TotalDamage,AverageDps,TotalManaUsed,AverageManaPerSecond,FirstCycleEndTime,TopSkill,TopSkillDamage,Warnings");
for (int i = 0; i < result.Entries.Count; i++)
{
SimulationBatchEntry entry = result.Entries[i];
SimulationResult simulation = entry != null ? entry.Result : null;
if (simulation == null)
continue;
SimulationSkillBreakdown topSkill = simulation.SkillBreakdowns.Count > 0 ? simulation.SkillBreakdowns[0] : null;
string warnings = string.Join(" / ", simulation.Warnings);
builder.Append(i + 1);
builder.Append(',');
builder.Append(Escape(entry.BuildLabel));
builder.Append(',');
builder.Append(Escape(simulation.RuleName));
builder.Append(',');
builder.Append(Escape(simulation.RotationName));
builder.Append(',');
builder.Append(simulation.DurationSeconds.ToString("0.##"));
builder.Append(',');
builder.Append(simulation.TotalDamage.ToString("0.##"));
builder.Append(',');
builder.Append(simulation.AverageDps.ToString("0.##"));
builder.Append(',');
builder.Append(simulation.TotalManaUsed.ToString("0.##"));
builder.Append(',');
builder.Append(simulation.AverageManaPerSecond.ToString("0.##"));
builder.Append(',');
builder.Append(simulation.FirstCycleEndTime >= 0f ? simulation.FirstCycleEndTime.ToString("0.##") : string.Empty);
builder.Append(',');
builder.Append(Escape(topSkill != null ? topSkill.SkillName : string.Empty));
builder.Append(',');
builder.Append(topSkill != null ? topSkill.TotalDamage.ToString("0.##") : string.Empty);
builder.Append(',');
builder.Append(Escape(warnings));
builder.AppendLine();
}
return builder.ToString().TrimEnd();
}
/// <summary>
/// 기본 파일 이름을 생성합니다.
/// </summary>
public static string BuildDefaultFileName(SimulationBatchResult result, bool csv)
{
string batchName = result != null && !string.IsNullOrWhiteSpace(result.BatchName) ? result.BatchName : "BuildSimulationBatch";
string extension = csv ? "csv" : "md";
return $"{Sanitize(batchName)}.{extension}";
}
private static string Escape(string value)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
bool needsQuotes = value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r");
if (!needsQuotes)
return value;
return $"\"{value.Replace("\"", "\"\"")}\"";
}
private static string Sanitize(string value)
{
if (string.IsNullOrWhiteSpace(value))
return "BuildSimulationBatch";
string sanitized = value;
char[] invalidChars = System.IO.Path.GetInvalidFileNameChars();
for (int i = 0; i < invalidChars.Length; i++)
{
sanitized = sanitized.Replace(invalidChars[i], '_');
}
return sanitized.Replace(' ', '_');
}
}
}