using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace TKS
{
///
/// 자동 플레이 실측: 아군도 AI 로 조작해 몇 턴을 돌리며 스크린샷·로그를 남긴다.
/// 에디터에서 ProjectSetup.AutoPlayCapture 가 SessionState 로 출력 폴더를 넘기면 생성된다.
/// 비활성 에디터 창은 프레임이 느리므로 timeScale 을 올려 실시간 기준으로 빨리 끝낸다.
///
public class AutoPlayDriver : MonoBehaviour
{
public string OutputDir;
public int MaxTurns = 3;
public int Width = 720;
public int Height = 1280;
readonly List log = new List();
int shotIndex = 1;
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void AutoSpawn()
{
string dir = UnityEditor.SessionState.GetString("TKS_AUTOPLAY_DIR", "");
if (string.IsNullOrEmpty(dir)) return;
UnityEditor.SessionState.EraseString("TKS_AUTOPLAY_DIR");
var go = new GameObject("AutoPlayDriver");
var d = go.AddComponent();
d.OutputDir = dir;
d.MaxTurns = UnityEditor.SessionState.GetInt("TKS_AUTOPLAY_TURNS", 3);
}
#endif
IEnumerator Start()
{
Directory.CreateDirectory(OutputDir);
Time.timeScale = 4f;
Time.maximumDeltaTime = 1f;
float t0 = Time.realtimeSinceStartup;
// 시작 화면 = 본영 로비(2026-11-05 #1093) → 출정 = 챕터 월드맵 → 캡처 · 1-1 팝업 캡처 → 출전(PD 2026-11-05)
yield return null;
var og = TKS.OutGame.OutGameRoot.Instance;
if (og != null && og.LobbyVisible)
{
og.OpenWorldMap();
yield return null;
}
var wm = WorldMapScreen.Instance;
if (wm != null && wm.IsVisible && Camera.main != null)
{
yield return new WaitForSecondsRealtime(0.4f);
yield return Shot(Camera.main, "worldmap");
wm.OpenPopup(0);
yield return null;
yield return Shot(Camera.main, "worldmap_popup");
wm.ConfirmPopup();
}
while ((BattleManager.Instance == null || BattleManager.Instance.Phase != BattlePhase.PlayerTurn)
&& Time.realtimeSinceStartup - t0 < 20f)
{
yield return null;
}
var bm = BattleManager.Instance;
var cam = Camera.main;
if (bm == null || cam == null)
{
Log("FAIL: BattleManager 또는 카메라 없음");
Finish(1);
yield break;
}
var camCtrl = cam.GetComponent();
Log("stage=" + bm.Stage.title + " players=" + bm.AliveCount(Faction.Player) + " enemies=" + bm.AliveCount(Faction.Enemy) + " board=" + bm.Config.boardSize);
yield return Fx.Wait(0.6f);
yield return Shot(cam, "start");
// 전장 전체 조감(최대 줌아웃) → 원래 줌
if (camCtrl != null)
{
float prev = camCtrl.ViewHeight;
camCtrl.SetViewHeight(camCtrl.MaxViewHeight);
camCtrl.SnapTo(Vector2.zero);
yield return null;
yield return Shot(cam, "overview_zoom_out");
camCtrl.SetViewHeight(prev);
camCtrl.PanBy(new Vector2(0f, 6f)); // 스크롤 확인
yield return null;
yield return Shot(cam, "scrolled_up");
camCtrl.FocusOn(bm.AliveUnits(Faction.Player)[0].Position + new Vector2(0f, 3f));
yield return Fx.Wait(0.5f);
}
var first = FirstActable(bm);
if (first != null)
{
var tgt = EnemyAI.Decide(first, bm.AliveUnits(Faction.Enemy), 1f, float.PositiveInfinity, bm.Rng, bm.Board, bm.Config);
if (tgt.HasValue)
{
Vector2 pointer = first.Position - (tgt.Value - first.Position) * 0.5f;
bm.Sling.Select(first); // 선택 링 + 사거리 원(궁병이면 도넛) + HUD 정보
if (first.View != null) first.View.SetAiming(true);
bm.Sling.ShowPreview(first, tgt.Value, pointer);
yield return null;
yield return Shot(cam, "aim_" + first.DisplayName);
bm.Sling.HidePreview();
if (first.View != null) first.View.SetAiming(false);
bm.Sling.Select(null);
}
}
// 사거리 표시 캡처: 궁병 선택(얇은 원 + 공격 가능 도넛) · 적 선택(적의 사거리)
foreach (var u in bm.AliveUnits(Faction.Player))
{
if (!UnitTypeInfo.IsRanged(u.Type)) continue;
if (camCtrl != null) camCtrl.FocusOn(u.Position + new Vector2(0f, 2f));
yield return Fx.Wait(0.5f);
bm.Sling.Select(u);
yield return null;
yield return Shot(cam, "range_archer_" + u.DisplayName);
bm.Sling.Select(null);
break;
}
var enemies0 = bm.AliveUnits(Faction.Enemy);
if (enemies0.Count > 0)
{
var e = enemies0[0];
if (camCtrl != null) camCtrl.FocusOn(e.Position);
yield return Fx.Wait(0.5f);
bm.Sling.Select(e);
yield return null;
yield return Shot(cam, "range_enemy_" + e.DisplayName);
bm.Sling.Select(null);
if (camCtrl != null) camCtrl.FocusOn(bm.AliveUnits(Faction.Player)[0].Position + new Vector2(0f, 3f));
yield return Fx.Wait(0.5f);
}
bool firstActionShot = false;
for (int turn = 1; turn <= MaxTurns; turn++)
{
int guard = 0;
while (bm.Phase == BattlePhase.PlayerTurn && guard++ < 24)
{
var u = FirstActable(bm);
if (u == null)
{
bm.OnEmptyTap();
break;
}
var target = EnemyAI.Decide(u, bm.AliveUnits(Faction.Enemy), 1f, float.PositiveInfinity, bm.Rng, bm.Board, bm.Config);
if (!target.HasValue) break;
if (!bm.RequestPlayerAction(u, target.Value)) break;
while (bm.Phase == BattlePhase.Resolving) yield return null;
Log("turn " + turn + " | " + bm.LastMessage);
if (!firstActionShot)
{
firstActionShot = true;
yield return Shot(cam, "after_first_action");
}
if (bm.Phase == BattlePhase.StageClear || bm.Phase == BattlePhase.Defeat) break;
}
while (bm.Phase == BattlePhase.EnemyTurn || bm.Phase == BattlePhase.Resolving) yield return null;
Log("turn " + turn + " end | phase=" + bm.Phase + " players=" + bm.AliveCount(Faction.Player) + " enemies=" + bm.AliveCount(Faction.Enemy) + " | " + bm.LastMessage);
yield return Fx.Wait(0.4f);
yield return Shot(cam, "turn" + turn + "_end");
if (bm.Phase == BattlePhase.StageClear || bm.Phase == BattlePhase.Defeat) break;
}
Finish(0);
}
static Unit FirstActable(BattleManager bm)
{
foreach (var u in bm.AliveUnits(Faction.Player))
{
if (!u.ActedThisTurn) return u;
}
return null;
}
IEnumerator Shot(Camera cam, string name)
{
string file = Path.Combine(OutputDir, shotIndex.ToString("00") + "_" + name + ".png");
shotIndex++;
yield return CaptureUtil.Capture(cam, Width, Height, file);
Log("shot " + file);
}
void Log(string s)
{
log.Add(s);
Debug.Log("[AutoPlay] " + s);
try
{
File.WriteAllLines(Path.Combine(OutputDir, "autoplay_log.txt"), log.ToArray());
}
catch
{
}
}
void Finish(int code)
{
Log("finish code=" + code);
Time.timeScale = 1f;
#if UNITY_EDITOR
UnityEditor.EditorApplication.Exit(code);
#else
Application.Quit(code);
#endif
}
}
}