ThreeKingdomsSlingshot/Assets/Scripts/Runtime/AI/Sim.cs

143 lines
5.5 KiB
C#
Raw Normal View History

지형 기믹 레이어(PD #1097 세션 D) + 병종 규칙 개정 2건 · 예측 시뮬레이션 적 AI(PD #1098) - 데이터: Assets/Data/Gimmicks/{gimmicks,stage_gimmicks,tutorial_tips}.csv + Assets/Data/AI/ai_weights.csv(기획 tables/ 사본) → 메뉴 TKS/Import Gimmicks(검증 · 오류 시 자산 불변) → Resources/Gimmicks/GimmickDb(16)·StageGimmickTable(16 + 팁 3) · Resources/AI/AiWeights(32) - 레이어: GimmickLayer(전투 시작 = 스테이지 배치 · 배경 위 유닛 아래 · 원/사각/다각 · 색 도형 + 글자 · 내구 핍 · 강 콜라이더 = 다리 구간 제외 · 겹친 유닛 보정) · 장애물 = 병종별 내구 피해·튕김·창 목책 밀기·바위 굴림·기병 정지 · 투사체 pass/block/destroy · 폭약 광역(아군 포함)+밀기+불 · 책사 광역 덤불 제거 · 강 낙하 - 지대: 경로 스윕(독 1→2→3 % · 늪 ×2 · 불 10 % · 빙판 미끄러짐 · 고지 · 안개 · 군기 · 함정) · 양 진영 턴 시작 훅 - 규칙 개정: 보병 튕김 중 다른 적과 연쇄 1회 · 창병 반작용(spearRecoilRatio · 밀며 전진 폐기) · 기존 테스트 기대값 갱신 - 적 AI: Runtime/AI(Sim 가상 실행 · TacticalAI 후보·점수·난이도·턴 운영 · 1부 보스 액티브) · 조준선 = 가상 실행 예상 궤적·기믹 반응·지대 띠 · TKS/Gimmick Painter · 캡처 GimmickImporter.Capture - 검증: 컴파일 0 · 플레이모드 113/113(신규 26) · console error 0 · 적 10기 턴 결정 62 ms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:20:07 +00:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TKS
{
/// <summary>
/// 가상 실행(dry-run · PD #1098 적 AI 예측 시뮬레이션): Active 동안 ActionResolver 가 연출 없이 한 프레임 안에 끝난다 —
/// Fx 이동·대기·투사체 = 즉시 · 팝업·광역 터짐 = 없음 · 유닛 표시(UnitView) 갱신 없음 · 피해 난수 = 분산 0 · 전투 이벤트 카운터 없음.
/// 상태는 Snapshot 으로 떠 두고 Restore 로 되돌린다(유닛 · 스킬 상태 · 기믹 레이어).
/// </summary>
public static class Sim
{
public static bool Active;
/// <summary>코루틴을 이 프레임 안에서 끝까지 돌린다(중첩 IEnumerator 포함 · null/기타 yield = 즉시 진행).</summary>
public static void Run(IEnumerator routine)
{
if (routine == null) return;
var stack = new Stack<IEnumerator>();
stack.Push(routine);
int guard = 0;
while (stack.Count > 0)
{
if (++guard > 200000)
{
Debug.LogError("[TKS] 가상 실행 루프 상한 초과 — 중단");
return;
}
var top = stack.Peek();
if (!top.MoveNext())
{
stack.Pop();
continue;
}
var cur = top.Current as IEnumerator;
if (cur != null) stack.Push(cur);
}
}
public class UnitSnap
{
public Unit U;
public Vector2 Pos;
public int Hp, MaxHp, Atk, Def;
public bool Acted, Bonus, Overwatch, Collider, Active;
}
public class SkillSnap
{
public UnitSkills S;
public int ReadyTurn, BonusMovesLeft, PierceHits;
public bool Reserved, Armed, BonusAttack, OwUnlimited;
public float OwMult, OwAdd;
}
public class Snapshot
{
public readonly List<UnitSnap> Units = new List<UnitSnap>();
public readonly List<SkillSnap> Skills = new List<SkillSnap>();
public List<SkillSystem.Timed> Timed;
public int LogCount;
public GimmickLayer.Snapshot Gimmicks;
public string Message;
}
public static Snapshot Take(BattleManager bm)
{
var s = new Snapshot { Message = bm.LastMessage };
foreach (var u in bm.Units)
{
if (u == null) continue;
s.Units.Add(new UnitSnap
{
U = u, Pos = u.Position, Hp = u.Hp, MaxHp = u.MaxHp, Atk = u.Atk, Def = u.Def, Acted = u.ActedThisTurn, Bonus = u.BonusMoveAvailable,
Overwatch = u.Overwatch, Collider = u.Collider != null && u.Collider.enabled, Active = u.gameObject.activeSelf
});
}
var sys = bm.Skills;
if (sys != null)
{
foreach (var e in sys.Entries)
{
s.Skills.Add(new SkillSnap
{
S = e, ReadyTurn = e.ReadyTurn, BonusMovesLeft = e.BonusMovesLeft, PierceHits = e.PierceHits, Reserved = e.Reserved, Armed = e.Armed,
BonusAttack = e.BonusAttack, OwUnlimited = e.OwUnlimited, OwMult = e.OwMult, OwAdd = e.OwAdd
});
}
s.Timed = new List<SkillSystem.Timed>(sys.TimedEffects);
s.LogCount = sys.Log.Count;
}
if (GimmickLayer.Current != null) s.Gimmicks = GimmickLayer.Current.Take();
return s;
}
public static void Restore(BattleManager bm, Snapshot s)
{
foreach (var x in s.Units)
{
var u = x.U;
if (u == null) continue;
u.Position = x.Pos;
u.Hp = x.Hp;
u.MaxHp = x.MaxHp;
u.Atk = x.Atk;
u.Def = x.Def;
u.ActedThisTurn = x.Acted;
u.BonusMoveAvailable = x.Bonus;
u.Overwatch = x.Overwatch;
if (u.Collider != null) u.Collider.enabled = x.Collider;
if (u.gameObject.activeSelf != x.Active) u.gameObject.SetActive(x.Active);
}
var sys = bm.Skills;
if (sys != null)
{
foreach (var k in s.Skills)
{
var e = k.S;
e.ReadyTurn = k.ReadyTurn;
e.BonusMovesLeft = k.BonusMovesLeft;
e.PierceHits = k.PierceHits;
e.Reserved = k.Reserved;
e.Armed = k.Armed;
e.BonusAttack = k.BonusAttack;
e.OwUnlimited = k.OwUnlimited;
e.OwMult = k.OwMult;
e.OwAdd = k.OwAdd;
}
if (s.Timed != null)
{
sys.TimedEffects.Clear();
sys.TimedEffects.AddRange(s.Timed);
}
if (sys.Log.Count > s.LogCount) sys.Log.RemoveRange(s.LogCount, sys.Log.Count - s.LogCount);
}
if (GimmickLayer.Current != null && s.Gimmicks != null) GimmickLayer.Current.Restore(s.Gimmicks);
bm.LastMessage = s.Message;
Physics2D.SyncTransforms();
}
}
}