ThreeKingdomsSlingshot/Assets/Scripts/Runtime/Gimmicks/GimmickLayer.cs

984 lines
40 KiB
C#
Raw Permalink 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>유닛 1기의 기믹 상태(중독 · 행동 불가 · 상태 글자).</summary>
public class GimmickUnitState
{
public int PoisonTurns;
public int PoisonTick;
public float PoisonPct;
public float PoisonStep;
/// <summary>남은 행동 불가 턴(함정 · 강) — 그 진영 턴 시작에 1 소모하고 그 턴을 쉰다.</summary>
public int Stun;
/// <summary>이번 턴 행동 불가(턴 시작에 정함).</summary>
public bool Skip;
public TextMesh Badge;
public GimmickUnitState Copy()
{
return (GimmickUnitState)MemberwiseClone();
}
}
/// <summary>
/// 지형 기믹 레이어(PD #1097 · 기획서 지형기믹_기획서.md) — 스테이지와 독립된 레이어. 전투 시작마다 그 스테이지의 배치(stage_gimmicks.csv)로
/// 장애물(콜라이더 + 내구)·지대(모양 · 경로 스윕)를 만든다(배경 위 · 유닛 아래). 전투 코드는 이 클래스의 질의만 부른다:
/// 이동 막기(Combat.FreeTravel) · 함정 정지 · 늪·고지 이동 비용 · 고지 사거리·근접 피해 · 군기 공격 · 안개 표적 · 광역 막힘 · 턴 시작(중독·불·행동 불가·군기).
/// 충돌 반응(튕김·밀기·파괴·폭발·미끄러짐)은 ActionResolver.Gimmicks.cs.
/// </summary>
public class GimmickLayer : MonoBehaviour
{
public static GimmickLayer Current { get; private set; }
/// <summary>조준 미리보기 계산 중(아군 = 숨은 함정을 모른다).</summary>
public static bool PreviewMode;
BattleManager battle;
readonly List<GimmickInstance> items = new List<GimmickInstance>();
readonly Dictionary<Unit, GimmickUnitState> states = new Dictionary<Unit, GimmickUnitState>();
GimmickInstance splashTemplate;
/// <summary>지금 처리 중인 행동의 결과(기믹 효과가 Displaced·Notes 를 더한다).</summary>
public ActionOutcome Outcome;
public struct ForcedMove
{
public Unit Unit;
public Vector2 From;
public Vector2 To;
지형 기믹 레이어(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
}
/// <summary>이번 행동의 강제 이동(밀림·튕김·반작용·넉백) 구간 — 강가·다리에서 밀려 강에 들어가면 빠짐(PD 2026-11-06 개정).</summary>
지형 기믹 레이어(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
public readonly List<ForcedMove> Forced = new List<ForcedMove>();
/// <summary>책사 광역이 걷어 낸 안개 자리(x, y, 반지름 · PD 2026-11-06).</summary>
readonly List<Vector3> fogHoles = new List<Vector3>();
readonly List<GameObject> fogMasks = new List<GameObject>();
지형 기믹 레이어(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
public struct Affect
{
public Unit Unit;
public GimmickZone Kind;
/// <summary>앞으로 받을 피해 환산(중독 = 남은 턴 합계 · 그 밖 = 0 · 즉시 피해는 HP 로 따로 센다).</summary>
public float Value;
}
/// <summary>이번 행동에서 지대 효과를 받은 유닛(독·불·함정·강) — 적 AI 점수(목적지 기믹 가치 · PD #1098).</summary>
public readonly List<Affect> Affected = new List<Affect>();
public GimmickPreview Preview { get; private set; }
public IReadOnlyList<GimmickInstance> Items
{
get { return items; }
}
public BattleManager Battle
{
get { return battle; }
}
public bool HasAny
{
get { return items.Count > 0; }
}
public static GimmickLayer Ensure(BattleManager bm)
{
if (Current != null && Current.battle == bm) return Current;
var go = new GameObject("GimmickLayer");
var layer = go.AddComponent<GimmickLayer>();
layer.battle = bm;
layer.Preview = new GimmickPreview(layer);
Current = layer;
return layer;
}
void OnDestroy()
{
if (Current == this) Current = null;
}
// ---------- 생성 ----------
/// <summary>전투 시작: 그 스테이지(장-번호)의 배치를 만든다(정의·배치 = 임포트 자산 · 없으면 빈 레이어).</summary>
public void Build(BalanceConfig.StageDef stage)
{
Clear();
GimmickTables.Invalidate();
var db = GimmickTables.Db;
var table = GimmickTables.Table;
if (db == null || table == null || stage == null) return;
foreach (var p in table.For(stage.chapter, stage.stage))
{
var def = db.Get(p.gimmickId);
if (def == null) continue;
Spawn(def, p.Clone(), false);
}
ResolveOverlaps();
}
/// <summary>배치(편성 규칙 좌표)가 장애물 기믹과 겹친 유닛은 가장 가까운 빈 자리로 옮긴다(겹친 채면 캐스트가 거리 0 으로 막힘).</summary>
public int ResolveOverlaps()
{
if (battle == null) return 0;
Physics2D.SyncTransforms();
int moved = 0;
foreach (var u in battle.Units)
{
if (u == null || !u.IsAlive || Free(u, u.Position)) continue;
bool done = false;
for (float r = 0.2f; r <= 4f && !done; r += 0.2f)
{
for (int k = 0; k < 16 && !done; k++)
{
float a = k * Mathf.PI / 8f;
Vector2 p = battle.Board.ClampInside(u.Position + new Vector2(Mathf.Cos(a), Mathf.Sin(a)) * r, u.Radius);
if (!Free(u, p)) continue;
u.Position = p;
done = true;
moved++;
}
}
}
if (moved > 0) Physics2D.SyncTransforms();
return moved;
}
bool Free(Unit u, Vector2 p)
{
foreach (var g in items)
{
if (g.BlocksMove && g.Shape.SignedDistance(p) < u.Radius + 0.05f) return false;
}
foreach (var o in battle.Units)
{
if (o == null || o == u || !o.IsAlive) continue;
if (Vector2.Distance(o.Position, p) < o.Radius + u.Radius + 0.02f) return false;
}
return true;
}
public void Clear()
{
foreach (var g in items)
{
if (g.Go != null)
{
g.Go.SetActive(false);
Destroy(g.Go);
}
}
items.Clear();
foreach (var kv in states)
{
if (kv.Value.Badge != null) Destroy(kv.Value.Badge.gameObject);
}
states.Clear();
splashTemplate = null;
Forced.Clear();
fogHoles.Clear();
foreach (var m in fogMasks)
{
if (m != null) Destroy(m);
}
fogMasks.Clear();
지형 기믹 레이어(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
Outcome = null;
if (Preview != null) Preview.Hide();
}
/// <summary>정의 + 배치로 1개 생성(전투 중 생성 · 테스트 · 페인터 공용). 불 spawn_on_splash 배치는 지대 없이 생성 규칙으로만 둔다.</summary>
public GimmickInstance Spawn(GimmickDef def, GimmickPlacement src, bool spawned)
{
var g = new GimmickInstance { Def = def, Src = src, Shape = GimmickShape.From(src), Spawned = spawned };
g.MaxHp = def.durable ? (src.hpOverride > 0 ? src.hpOverride : def.hp) : 0;
g.Hp = g.MaxHp;
if (def.zone == GimmickZone.Burn)
{
float turns = g.P(2, 0f);
g.BurnLeft = turns > 0f ? Mathf.RoundToInt(turns) : -1;
if (!spawned && g.P(3, 0f) >= 0.5f)
{
g.Template = true; // 8-15 화공: 책사 광역이 떨어진 자리에 생긴다
splashTemplate = g;
}
}
items.Add(g);
if (!g.Template && !Sim.Active) BuildObject(g);
return g;
}
/// <summary>테스트·페인터: id 와 모양으로 생성(params = 「키=값;…」).</summary>
public GimmickInstance Spawn(string id, GimmickShape shape, string param = null, int hpOverride = -1)
{
var def = GimmickTables.Db != null ? GimmickTables.Db.Get(id) : null;
if (def == null)
{
Debug.LogError("[TKS] 기믹 정의 없음: " + id + " (TKS/Import Gimmicks)");
return null;
}
var p = new GimmickPlacement
{
stage = "", gimmickId = id, shape = shape.Type, cx = shape.Center.x, cy = shape.Center.y,
wOrR = shape.Type == GimmickShapeType.Circle ? shape.R : shape.Size.x, h = shape.Size.y, rot = shape.Rot,
points = shape.Points != null ? (Vector2[])shape.Points.Clone() : new Vector2[0], hpOverride = hpOverride, paramsRaw = param ?? ""
};
if (!string.IsNullOrEmpty(param))
{
foreach (var kv in param.Split(';'))
{
var f = kv.Split('=');
float v;
if (f.Length != 2 || !float.TryParse(f[1], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out v)) continue;
string k = f[0].Trim();
if (k == "p1" || k == "slide_max") p.pOver1 = v;
else if (k == "p2" || k == "burn_turns") p.pOver2 = v;
else if (k == "p3" || k == "spawn_on_splash") p.pOver3 = v;
}
}
return Spawn(def, p, false);
지형 기믹 레이어(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
}
void BuildObject(GimmickInstance g)
{
g.Go = new GameObject("G_" + g.Def.id);
g.Go.transform.SetParent(transform, false);
g.Go.transform.position = new Vector3(g.Shape.Center.x, g.Shape.Center.y, 0f);
if (g.IsBlock && g.Zone != GimmickZone.River) AddCollider(g, g.Shape, g.Go.transform);
g.View = new GimmickView(g);
}
static void AddCollider(GimmickInstance g, GimmickShape s, Transform parent)
{
var cgo = new GameObject("Collider");
cgo.transform.SetParent(parent, false);
cgo.transform.position = new Vector3(s.Center.x, s.Center.y, 0f);
Collider2D col;
switch (s.Type)
{
case GimmickShapeType.Circle:
{
var c = cgo.AddComponent<CircleCollider2D>();
c.radius = s.R;
col = c;
break;
}
case GimmickShapeType.Rect:
{
cgo.transform.rotation = Quaternion.Euler(0f, 0f, s.Rot);
var b = cgo.AddComponent<BoxCollider2D>();
b.size = s.Size;
col = b;
break;
}
default:
{
var pc = cgo.AddComponent<PolygonCollider2D>();
var pts = new Vector2[s.Points.Length];
for (int i = 0; i < pts.Length; i++) pts[i] = s.Points[i] - s.Center;
pc.points = pts;
col = pc;
break;
}
}
col.isTrigger = false;
cgo.AddComponent<GimmickBody>().Inst = g;
g.Colliders.Add(col);
}
// ---------- 정적 질의(레이어가 없으면 기본값) ----------
public static GimmickInstance InstOf(Collider2D c)
{
if (c == null) return null;
var b = c.GetComponent<GimmickBody>();
return b != null ? b.Inst : null;
}
/// <summary>이 콜라이더가 이동을 막는 기믹인가(부서진 잔해·제거된 덤불 = 아님).</summary>
public static bool BlocksMove(Collider2D c)
{
var g = InstOf(c);
return g != null && g.BlocksMove;
}
public static float AttackMultOf(Unit a)
{
var l = Current;
return l == null ? 1f : l.AttackMult(a);
}
public static float MeleeTakenMultOf(Unit a, Unit d)
{
var l = Current;
return l == null ? 1f : l.MeleeTakenMult(a, d);
}
public static float RangeBonusOf(Unit u)
{
var l = Current;
return l == null ? 0f : l.RangeBonus(u);
}
public static bool IsFogged(Unit u)
{
var l = Current;
return l != null && l.Fogged(u);
}
public static float RangedTakenMultOf(Unit d)
{
var l = Current;
return l == null ? 1f : l.RangedTakenMult(d);
}
지형 기믹 레이어(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
public static bool Skips(Unit u)
{
var l = Current;
if (l == null || u == null) return false;
GimmickUnitState s;
return l.states.TryGetValue(u, out s) && s.Skip;
}
// ---------- 위치 질의 ----------
public GimmickUnitState StateOf(Unit u, bool create = false)
{
GimmickUnitState s;
if (u == null) return null;
if (!states.TryGetValue(u, out s) && create)
{
s = new GimmickUnitState();
states[u] = s;
}
return s;
}
public GimmickInstance ZoneAt(Vector2 p, GimmickZone z)
{
foreach (var g in items)
{
if (g.ZoneActive && g.Zone == z && g.Shape.Contains(p)) return g;
}
return null;
}
public bool OnBridge(Vector2 p)
{
foreach (var g in items)
{
if (g.IsBridge && !g.Removed && g.Shape.Contains(p)) return true;
}
return false;
}
/// <summary>강물 안(강 지대 · 다리 구간 제외)이면 그 강.</summary>
public GimmickInstance RiverAt(Vector2 p)
{
var r = ZoneAt(p, GimmickZone.River);
return r != null && !OnBridge(p) ? r : null;
}
/// <summary>안개 안(책사 광역이 걷어 낸 자리 제외)이면 그 안개.</summary>
public GimmickInstance FogAt(Vector2 p)
{
var f = ZoneAt(p, GimmickZone.Fog);
if (f == null) return null;
foreach (var h in fogHoles)
{
if (Vector2.Distance(p, h) <= h.z) return null;
}
return f;
}
public int FogHoleCount
{
get { return fogHoles.Count; }
}
/// <summary>책사 광역 = 그 반경만큼 안개를 걷는다(겹친 안개가 있을 때만 · 반환 = 걷었다 · PD 2026-11-06).</summary>
public bool ClearFog(Vector2 c, float r)
{
bool any = false;
foreach (var g in items)
{
if (g.Zone == GimmickZone.Fog && g.ZoneActive && g.Shape.SignedDistance(c) <= r) any = true;
}
if (!any || r <= 0f) return false;
fogHoles.Add(new Vector3(c.x, c.y, r));
if (!Sim.Active)
{
var m = new GameObject("FogCleared");
m.transform.SetParent(transform, false);
m.transform.position = new Vector3(c.x, c.y, 0f);
m.transform.localScale = new Vector3(r * 2f, r * 2f, 1f);
m.AddComponent<SpriteMask>().sprite = ProceduralSprites.Circle;
fogMasks.Add(m);
Fx.Popup(c, "안개 걷힘", new Color(0.9f, 0.95f, 1f));
}
return true;
}
지형 기믹 레이어(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
/// <summary>숨은 함정: origin → dir 로 dist 안에서 중심이 처음 함정에 드는 거리(+0.06 = 안으로 살짝) · 없으면 dist. 조준 미리보기(아군)는 모른다.</summary>
public float TrapStop(Unit actor, Vector2 origin, Vector2 dir, float dist)
{
if (PreviewMode) return dist;
float best = dist;
foreach (var g in items)
{
if (g.Zone != GimmickZone.Trap || !g.ZoneActive || g.Shape.Contains(origin)) continue;
float t = g.Shape.EnterDistance(origin, dir, dist);
if (t >= 0f) best = Mathf.Min(best, t + 0.06f);
}
return best;
}
/// <summary>
/// 이동 비용(늪 = 안 구간 ×p1 · 고지 = 밖에서 오르는 구간 ×p3): budget(슬링샷 이동량)으로 실제로 갈 수 있는 거리.
/// 늪에서 멈추면 다음 행동도 늪 안 구간만큼 반감(출발점이 늪이어도 같은 계산).
/// </summary>
public float CostReach(Unit u, Vector2 origin, Vector2 dir, float budget)
{
if (budget <= 0f) return 0f;
bool any = false;
foreach (var g in items)
{
if (!g.ZoneActive || (g.Zone != GimmickZone.Slow && g.Zone != GimmickZone.High && g.Zone != GimmickZone.River)) continue;
지형 기믹 레이어(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
if (Combat.SegmentPointDistance(origin, origin + dir * budget, g.Shape.Center) > g.Shape.BoundingRadius + 0.01f) continue;
any = true;
break;
}
if (!any) return budget;
const float step = GimmickShape.SweepStep;
float used = 0f, t = 0f;
int guard = 0;
while (used < budget - 1e-4f && guard++ < 4000)
{
Vector2 p = origin + dir * (t + step * 0.5f);
float m = 1f;
foreach (var g in items)
{
if (!g.ZoneActive || !g.Shape.Contains(p)) continue;
if (g.Zone == GimmickZone.Slow) m *= Mathf.Max(1f, g.P(1, 1f));
else if (g.Zone == GimmickZone.High && !g.Shape.Contains(origin)) m *= Mathf.Max(1f, g.P(3, 1f));
else if (g.Zone == GimmickZone.River && !OnBridge(p)) m *= Mathf.Max(1f, g.P(3, 1f)); // 강 = 늪과 같은 판정(다리 구간 제외 · PD 2026-11-06)
지형 기믹 레이어(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
}
float cost = step * m;
if (used + cost >= budget)
{
t += (budget - used) / m;
return t;
}
used += cost;
t += step;
}
return t;
}
/// <summary>고지 위의 궁·책 사거리 +p1.</summary>
public float RangeBonus(Unit u)
{
if (u == null || !UnitTypeInfo.IsRanged(u.Type)) return 0f;
var g = ZoneAt(u.Position, GimmickZone.High);
return g != null ? g.P(1, 0f) : 0f;
}
/// <summary>고지 위 유닛이 아래(고지 밖)에서 오는 근접 피해 × (1 − p2).</summary>
public float MeleeTakenMult(Unit a, Unit d)
{
if (a == null || d == null) return 1f;
var g = ZoneAt(d.Position, GimmickZone.High);
if (g == null || ZoneAt(a.Position, GimmickZone.High) != null) return 1f;
return Mathf.Max(0f, 1f - g.P(2, 0f));
}
/// <summary>군기 점령 측 전원 공 × (1 + Σp2).</summary>
public float AttackMult(Unit a)
{
if (a == null) return 1f;
float add = 0f;
foreach (var g in items)
{
if (g.Zone == GimmickZone.Capture && g.ZoneActive && g.Owner == (int)a.Faction) add += g.P(2, 0f);
}
return 1f + add;
}
/// <summary>안개 안 유닛(걷힌 자리 제외) — 받는 원거리 피해 ×p1(PD 2026-11-06 개정 · 표적·사선은 정상).</summary>
지형 기믹 레이어(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
public bool Fogged(Unit u)
{
return u != null && FogAt(u.Position) != null;
지형 기믹 레이어(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
}
/// <summary>원거리 피해(화살 · 책사 중심·광역) 배율 = 안개 안이면 p1.</summary>
public float RangedTakenMult(Unit d)
지형 기믹 레이어(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
{
var f = d != null ? FogAt(d.Position) : null;
return f != null ? Mathf.Max(0f, f.P(1, 1f)) : 1f;
지형 기믹 레이어(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
}
/// <summary>광역이 중심 → p 사이에서 막히는가(성문·성벽·바위 = 투사체 막힘 + 책사 제거 불가).</summary>
public bool SplashBlocked(Vector2 c, Vector2 p)
{
if (items.Count == 0 || (p - c).sqrMagnitude < 1e-4f) return false;
Physics2D.SyncTransforms();
foreach (var h in Physics2D.LinecastAll(c, p))
{
var g = InstOf(h.collider);
if (g == null || !g.BlocksMove) continue;
if (g.Def.projectile == GimmickProjectile.Block && !g.Def.strategistClear && !g.Shape.Contains(c)) return true;
}
return false;
}
/// <summary>기병 관통 옆 밀기 배율: 늪 안 = 0 · 빙판 위 = ×p2.</summary>
public float KnockMult(Unit e)
{
if (e == null) return 1f;
if (ZoneAt(e.Position, GimmickZone.Slow) != null || RiverAt(e.Position) != null) return 0f; // 늪·강 안 = 밀기 0
지형 기믹 레이어(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
var ice = ZoneAt(e.Position, GimmickZone.Slide);
return ice != null ? Mathf.Max(0f, ice.P(2, 1f)) : 1f;
}
/// <summary>정지점이 빙판이면 같은 방향으로 빙판이 끝나는 곳까지(최대 p1) 미끄러질 거리 · 아니면 0.</summary>
public float SlideDistance(Vector2 pos, Vector2 dir)
{
var ice = ZoneAt(pos, GimmickZone.Slide);
if (ice == null || dir.sqrMagnitude < 1e-6f) return 0f;
float max = Mathf.Max(0f, ice.P(1, 0f));
return Mathf.Min(max, ice.Shape.ExitDistance(pos, dir.normalized, max));
}
/// <summary>책사 광역 반경 안의 제거 가능한 장애물(strategist_clear = 덤불 제거 · 폭약 수레 파괴).</summary>
public List<GimmickInstance> ClearableIn(Vector2 c, float r)
{
var res = new List<GimmickInstance>();
foreach (var g in items)
{
if (!g.BlocksMove || !g.Def.strategistClear) continue;
if (g.Shape.SignedDistance(c) <= r) res.Add(g);
}
return res;
}
/// <summary>책사 광역이 떨어진 자리에 불(스테이지에 spawn_on_splash 불 배치가 있을 때 · 반경 = 광역 반경 · 지속 = 그 배치 burn_turns).</summary>
public GimmickInstance OnSplash(Vector2 c, float r)
{
if (splashTemplate == null) return null;
return SpawnBurning(c, r, splashTemplate.P(2, 0f));
}
/// <summary>폭약 폭발 자리 불(반경 = 폭발 반경 · 지속 = 스테이지 화공 배치 burn_turns · 없으면 정의 p2 · 0 = 영구).</summary>
public GimmickInstance SpawnFire(Vector2 c, float r)
{
float turns = 0f;
if (splashTemplate != null) turns = splashTemplate.P(2, 0f);
else
{
var def = GimmickTables.Db != null ? GimmickTables.Db.Get(GimmickIds.BurningDebris) : null;
if (def != null && !float.IsNaN(def.p2)) turns = def.p2;
}
return SpawnBurning(c, r, turns);
}
public GimmickInstance SpawnBurning(Vector2 c, float r, float turns)
{
var db = GimmickTables.Db;
var def = db != null ? db.Get(GimmickIds.BurningDebris) : null;
if (def == null) return null;
var p = new GimmickPlacement { gimmickId = def.id, shape = GimmickShapeType.Circle, cx = c.x, cy = c.y, wOrR = Mathf.Max(0.3f, r), pOver2 = turns, pOver3 = 0f };
return Spawn(def, p, true);
}
public void Remove(GimmickInstance g)
{
if (g == null || g.Removed) return;
g.Removed = true;
g.SetCollidersEnabled(false);
if (!Sim.Active && g.View != null) g.View.Vanish(g.Zone == GimmickZone.Trap);
}
/// <summary>내구 피해(흔들림 · 핍) → 0 이면 잔해. 반환 = 이번에 부서졌다.</summary>
public bool Hit(GimmickInstance g, int dmg)
{
if (g == null || !g.BlocksMove || !g.Def.durable || dmg <= 0) return false;
g.Hp = Mathf.Max(0, g.Hp - dmg);
bool broke = g.Hp <= 0;
if (broke)
{
g.Broken = true;
g.SetCollidersEnabled(false);
}
if (!Sim.Active && g.View != null)
{
g.View.Shake();
if (broke) g.View.Shatter();
else g.View.Refresh();
Fx.Popup(g.Shape.Center, broke ? g.Def.nameKo + " 파괴" : g.Def.nameKo + " −" + dmg, new Color(1f, 0.8f, 0.4f));
}
return broke;
}
// ---------- 상태(중독 · 행동 불가) ----------
public void Poison(Unit u, GimmickInstance g)
{
var s = StateOf(u, true);
if (s.PoisonTurns <= 0) s.PoisonTick = 0; // 재진입 = 지속만 갱신(중첩 없음)
s.PoisonTurns = Mathf.Max(1, Mathf.RoundToInt(g.P(1, 1f)));
s.PoisonPct = g.P(2, 0f);
s.PoisonStep = g.P(3, 0f);
Text(u, "중독", new Color(0.75f, 0.45f, 1f));
RefreshBadge(u);
}
장수 스킬 144개 새 기획 구현(#1112 · PD 2026-11-10 엑셀 「장수 목록」) — 효과 표 skill_effects.csv(효과 1줄 · 한글 주석 열) · 이름 56개 새로 - 수치 SOT = Assets/Data/OutGame/skill_effects.csv(skill_id·key·target·cond·dur·p1~p3 + effect_ko·p1_ko~p3_ko 주석) · skills.csv effect_key = 키 목록 · heroes.csv gender(m|f) - 처리기 121종(SkillKeys) · 대상 칸(자신·같은 편·진영·반경·행동 반경) · 조건 칸(치명타·상대 상태 이상·병종·성별·턴·장착 중) · 지속 라운드 - 상태 이상 = 혼란·출혈(이동 거리 비례)·중독(중첩)·화상 + 기절(기믹 행동 불가 재사용) · 정화 · 회복·흡혈·부활·무적·무효화·호위(반격) - 재행동(유비·위연·여몽·손견·곽가·우금·맹획·감녕·고순·장합) · 치명타 확률·피해·무조건 - 이동 변형: 관통(손견·안량·손책 아군 통과) · 핀볼 튕김(손책·동탁) · 복귀(조운·손책·여포·문추·공손찬·순우경 · 하후돈 탭 지점·쓸어 감) · 위치 교환(위연) · 끌어오기(장료) · 아군 통과+탭 방향 밀치기(조조) · 반복(정보) · 난동(동탁) - 특수 사격: 거대 화살 · 직선 관통 · 폭발(화염구·독구름·운석·화살비) · 부채꼴 · 멀티샷 · 도탄 · 회오리 · 목책 · 불 장벽·불꽃·독 지대 · 궁병식 비도 · 광역 회복 - 즉시형: 광역 타격·밀기 · 흡수 · 일제 사격 · 미인계 끌어오기 · 범위 회복·정화 · 재행동 부여 · 변진 - 가상 실행(적 AI) 스킬 상태 사본(TakeState/RestoreState) · 옛 견제 스킬 확장 제거 - 테스트(고친 부분 클래스만): SkillTests 19(새로) · BattleFlowTests 42 · TacticalAITests 7 · GimmickTests 19 · OutGameTests 15 · OutGameCTests 12 전부 통과 · console error 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 10:00:18 +00:00
/// <summary>스킬 정화(제갈량·방통·간옹 · 2026-11-10 #1112): 중독·행동 불가 해제. 반환 = 지운 것이 있었다.</summary>
public bool Cleanse(Unit u)
{
var s = StateOf(u);
if (s == null) return false;
bool any = s.PoisonTurns > 0 || s.Stun > 0 || s.Skip;
s.PoisonTurns = 0;
s.Stun = 0;
s.Skip = false;
RefreshBadge(u);
return any;
}
지형 기믹 레이어(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
public void Stun(Unit u, int turns, string why)
{
if (u == null || turns <= 0) return;
var s = StateOf(u, true);
s.Stun = Mathf.Max(s.Stun, turns);
Text(u, why, new Color(1f, 0.85f, 0.3f));
RefreshBadge(u);
}
/// <summary>최대 HP 비율 피해(지대 · 튕김 자기 피해) — 반격·사망과 같은 경로(SkillSystem.Deal). 반환 = 준 피해.</summary>
public int PctDamage(Unit u, float pct, string label)
{
if (u == null || !u.IsAlive || pct <= 0f) return 0;
int dmg = Mathf.Max(1, Mathf.RoundToInt(u.MaxHp * pct));
dmg = SkillSystem.Deal(u, dmg);
Text(u, (string.IsNullOrEmpty(label) ? "" : label + " ") + "−" + dmg, new Color(1f, 0.55f, 0.3f));
return dmg;
}
public void Text(Unit u, string text, Color c)
{
if (Sim.Active || u == null) return;
Fx.Popup(u.Position + new Vector2(0f, 0.35f), text, c);
}
void RefreshBadge(Unit u)
{
if (Sim.Active || u == null) return;
var s = StateOf(u);
if (s == null) return;
string t = "";
if (s.PoisonTurns > 0) t += "독" + s.PoisonTurns;
if (s.Stun > 0 || s.Skip) t += (t.Length > 0 ? " " : "") + "행동불가";
if (s.Badge == null)
{
if (t.Length == 0) return;
s.Badge = TextUtil.CreateWorldText(u.transform, t, 0.24f, new Color(0.85f, 0.6f, 1f), 6);
s.Badge.gameObject.name = "GimmickStatus";
float top = u.View != null && u.View.HasArt ? -u.Radius * 0.55f + u.ArtHeight + 0.12f : u.Radius + 0.22f;
s.Badge.transform.localPosition = new Vector3(0f, top + 0.78f, 0f);
}
s.Badge.text = t;
s.Badge.gameObject.SetActive(t.Length > 0 && u.IsAlive);
}
public string BadgeText(Unit u)
{
var s = StateOf(u);
return s != null && s.Badge != null && s.Badge.gameObject.activeSelf ? s.Badge.text : "";
}
// ---------- 턴 시작 ----------
/// <summary>
/// side 진영 턴 시작: 군기 점령 판정 → (아군 턴 = 라운드 시작) 불 지속 −1 → 그 진영 유닛의 중독(최대 HP p2 + p3 × 지난 턴 수) ·
/// 불 위에서 멈춘 유닛(p1) · 행동 불가(남은 턴 1 소모 · 아군 = 행동 끝 표시).
/// </summary>
public IEnumerator TurnStart(Faction side)
{
if (items.Count == 0 && states.Count == 0) yield break;
bool any = false;
foreach (var g in items)
{
if (g.Zone != GimmickZone.Capture || !g.ZoneActive) continue;
float r = g.P(1, 0f);
bool p = false, e = false;
foreach (var u in battle.Units)
{
if (u == null || !u.IsAlive) continue;
if (Vector2.Distance(u.Position, g.Shape.Center) - u.Radius > r) continue;
if (u.Faction == Faction.Player) p = true;
else e = true;
}
int owner = p && e ? -1 : p ? (int)Faction.Player : e ? (int)Faction.Enemy : g.Owner;
if (owner != g.Owner)
{
g.Owner = owner;
if (!Sim.Active && g.View != null)
{
g.View.Refresh();
Fx.Popup(g.Shape.Center, owner < 0 ? "군기 중립" : owner == 0 ? "아군 군기 점령" : "적군 군기 점령", new Color(1f, 0.9f, 0.4f));
}
any = true;
}
}
if (side == Faction.Player)
{
foreach (var g in items)
{
if (g.Zone != GimmickZone.Burn || !g.ZoneActive || g.BurnLeft <= 0) continue;
g.BurnLeft--;
if (g.BurnLeft == 0) g.Out = true;
if (!Sim.Active && g.View != null) g.View.Refresh();
}
}
foreach (var u in battle.AliveUnits(side))
{
var s = StateOf(u);
if (s != null && s.PoisonTurns > 0)
{
PctDamage(u, s.PoisonPct + s.PoisonStep * s.PoisonTick, "중독");
s.PoisonTick++;
s.PoisonTurns--;
any = true;
}
if (u.IsAlive)
{
var burn = ZoneAt(u.Position, GimmickZone.Burn);
if (burn != null)
{
PctDamage(u, burn.P(1, 0f), "불");
any = true;
}
}
if (s != null)
{
s.Skip = false;
if (s.Stun > 0 && u.IsAlive)
{
s.Stun--;
s.Skip = true;
if (side == Faction.Player) u.MarkActed();
Text(u, "행동 불가", new Color(1f, 0.85f, 0.3f));
any = true;
}
RefreshBadge(u);
}
}
if (any)
{
battle.NotifyChanged();
yield return Fx.Wait(0.35f);
}
}
// ---------- 행동 뒤 지대(경로 스윕) ----------
/// <summary>
/// 이 행동에서 움직인 모든 유닛(행동 유닛 경로 + 밀림·넉백·튕김 구간)의 중심 경로가 지대와 겹치면 효과:
/// 독늪 = 중독(재진입 갱신) · 불 = 최대 HP p1(행동당 1회) · 함정 = 그 자리 정지(이동 계산에서 이미 멈춤) + p1 턴 행동 불가 + 제거.
/// 그 뒤 다리 위에서 옆으로 밀려 강에 막힌 유닛 = 최대 HP p1 + p2 턴 행동 불가.
/// </summary>
public void ApplyZones(Unit mover, ActionOutcome o)
{
if (items.Count == 0 || o == null) return;
var segs = new Dictionary<Unit, List<Vector2>>();
if (mover != null && o.Path.Count > 0) segs[mover] = new List<Vector2>(o.Path);
foreach (var d in o.Displaced)
{
if (d.Unit == null) continue;
List<Vector2> l;
if (!segs.TryGetValue(d.Unit, out l))
{
l = new List<Vector2>();
segs[d.Unit] = l;
}
l.Add(d.From);
l.Add(d.To);
l.Add(new Vector2(float.NaN, float.NaN)); // 구간 끊김
}
foreach (var kv in segs)
{
var u = kv.Key;
var pts = kv.Value;
bool burned = false;
foreach (var g in items)
{
if (!g.ZoneActive || !u.IsAlive) continue;
if (g.Zone != GimmickZone.Poison && g.Zone != GimmickZone.Burn && g.Zone != GimmickZone.Trap) continue;
if (!Touches(g.Shape, pts, u.Position)) continue;
switch (g.Zone)
{
case GimmickZone.Poison:
{
Poison(u, g);
var s = StateOf(u);
float future = 0f;
for (int k = 0; k < s.PoisonTurns; k++) future += (s.PoisonPct + s.PoisonStep * (s.PoisonTick + k)) * u.MaxHp;
Affected.Add(new Affect { Unit = u, Kind = g.Zone, Value = future });
break;
}
case GimmickZone.Burn:
if (burned) break;
burned = true;
PctDamage(u, g.P(1, 0f), "불");
Affected.Add(new Affect { Unit = u, Kind = g.Zone });
break;
case GimmickZone.Trap:
Remove(g);
g.Revealed = true;
Stun(u, Mathf.Max(1, Mathf.RoundToInt(g.P(1, 1f))), "함정!");
o.Notes.Add(u.DisplayName + " 함정");
Affected.Add(new Affect { Unit = u, Kind = g.Zone });
break;
}
}
}
// 강(PD 2026-11-06 개정): 강가·다리에서 밀려(밀림·튕김·반작용·넉백) 강물에 들어가면 최대 HP p1 + p2 턴 행동 불가 · 행동당 1회 · 걸어 들어가면 느려지기만
var fell = new HashSet<Unit>();
지형 기믹 레이어(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
foreach (var f in Forced)
{
var u = f.Unit;
if (u == null || !u.IsAlive || fell.Contains(u) || RiverAt(f.From) != null) continue;
var river = RiverEntered(f.From, f.To);
지형 기믹 레이어(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
if (river == null) continue;
fell.Add(u);
지형 기믹 레이어(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
PctDamage(u, river.P(1, 0f), "강에 빠짐");
Stun(u, Mathf.Max(1, Mathf.RoundToInt(river.P(2, 1f))), "강에 빠짐");
o.Notes.Add(u.DisplayName + " 강에 빠짐");
Affected.Add(new Affect { Unit = u, Kind = GimmickZone.River });
}
Forced.Clear();
}
static bool Touches(GimmickShape s, List<Vector2> pts, Vector2 end)
{
if (pts.Count == 1) return s.Contains(pts[0]);
for (int i = 0; i + 1 < pts.Count; i++)
{
if (float.IsNaN(pts[i].x) || float.IsNaN(pts[i + 1].x)) continue;
if (s.SegmentTouches(pts[i], pts[i + 1])) return true;
}
return s.Contains(end);
}
/// <summary>선분 a→b 가 강물(다리 제외)에 들어가는 첫 강 · 없으면 null.</summary>
GimmickInstance RiverEntered(Vector2 a, Vector2 b)
지형 기믹 레이어(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
{
float len = Vector2.Distance(a, b);
if (len < 0.01f) return null;
int n = Mathf.Max(1, Mathf.CeilToInt(len / GimmickShape.SweepStep));
for (int i = 1; i <= n; i++)
지형 기믹 레이어(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
{
var r = RiverAt(Vector2.Lerp(a, b, (float)i / n));
if (r != null) return r;
지형 기믹 레이어(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
}
return null;
}
public void NoteForced(Unit u, Vector2 from, Vector2 to)
지형 기믹 레이어(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
{
if (u == null || items.Count == 0 || (to - from).sqrMagnitude < 1e-6f) return;
Forced.Add(new ForcedMove { Unit = u, From = from, To = to });
지형 기믹 레이어(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
}
// ---------- 가상 실행(적 AI) 스냅숏 ----------
public class Snapshot
{
public int Count;
public readonly List<GimmickInstance> Refs = new List<GimmickInstance>();
public readonly List<int> Hp = new List<int>();
public readonly List<int> Flags = new List<int>();
public readonly List<int> BurnLeft = new List<int>();
public readonly List<int> Owner = new List<int>();
public readonly List<Vector2> Center = new List<Vector2>();
public readonly Dictionary<Unit, GimmickUnitState> States = new Dictionary<Unit, GimmickUnitState>();
public GimmickInstance Template;
public int FogHoles;
지형 기믹 레이어(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
}
public Snapshot Take()
{
var s = new Snapshot { Count = items.Count, Template = splashTemplate, FogHoles = fogHoles.Count };
지형 기믹 레이어(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
foreach (var g in items)
{
s.Refs.Add(g);
s.Hp.Add(g.Hp);
s.Flags.Add((g.Broken ? 1 : 0) | (g.Removed ? 2 : 0) | (g.Revealed ? 4 : 0) | (g.Out ? 8 : 0));
s.BurnLeft.Add(g.BurnLeft);
s.Owner.Add(g.Owner);
s.Center.Add(g.Shape.Center);
}
foreach (var kv in states) s.States[kv.Key] = kv.Value.Copy();
return s;
}
public void Restore(Snapshot s)
{
if (s == null) return;
for (int i = items.Count - 1; i >= s.Count; i--)
{
if (items[i].Go != null) Destroy(items[i].Go);
items.RemoveAt(i);
}
for (int i = 0; i < s.Count; i++)
{
var g = s.Refs[i];
items[i] = g;
g.Hp = s.Hp[i];
int f = s.Flags[i];
g.Broken = (f & 1) != 0;
g.Removed = (f & 2) != 0;
g.Revealed = (f & 4) != 0;
g.Out = (f & 8) != 0;
g.BurnLeft = s.BurnLeft[i];
g.Owner = s.Owner[i];
if ((g.Shape.Center - s.Center[i]).sqrMagnitude > 1e-8f) g.MoveBy(s.Center[i] - g.Shape.Center);
g.SetCollidersEnabled(g.BlocksMove);
}
splashTemplate = s.Template;
if (fogHoles.Count > s.FogHoles) fogHoles.RemoveRange(s.FogHoles, fogHoles.Count - s.FogHoles);
지형 기믹 레이어(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
foreach (var kv in s.States)
{
GimmickUnitState cur;
if (states.TryGetValue(kv.Key, out cur))
{
var badge = cur.Badge;
states[kv.Key] = kv.Value.Copy();
states[kv.Key].Badge = badge;
}
else states[kv.Key] = kv.Value.Copy();
}
var extra = new List<Unit>();
foreach (var kv in states)
{
if (!s.States.ContainsKey(kv.Key)) extra.Add(kv.Key);
}
foreach (var u in extra)
{
if (states[u].Badge == null) states.Remove(u);
else
{
var b = states[u].Badge;
states[u] = new GimmickUnitState { Badge = b };
}
}
Forced.Clear();
Affected.Clear();
Outcome = null;
}
}
}