using UnityEngine;
namespace TKS
{
/// 지형 기믹 질의(PD #1097) — 장애물 콜라이더 · 투사체 pass/block/destroy · 안개 · 기믹 피해 공식.
public static partial class Combat
{
/// true 인 동안 투사체가 장애물 기믹을 통과(황충 노익장 · 스킬 관통 사격 · 호출 쪽이 켜고 끔).
public static bool ArrowIgnoreGimmicks;
/// 전장 가장자리 벽까지만(기믹·유닛 무시) 나아갈 수 있는 거리.
public static float WallTravel(Unit actor, Vector2 origin, Vector2 dir, float dist)
{
float min = dist;
foreach (var h in Cast(actor, origin, dir, dist))
{
if (h.collider.GetComponent() != null) min = Mathf.Min(min, h.distance);
}
return Mathf.Max(0f, min - Epsilon);
}
/// 경로(원 캐스트) 위 첫 장애물 기믹. hitDist = 닿기까지 중심 이동 거리 · normal = 닿은 면의 바깥 방향(튕김 방향).
public static GimmickInstance FirstGimmickOnPath(Unit actor, Vector2 origin, Vector2 dir, float dist, out float hitDist, out Vector2 normal)
{
hitDist = 0f;
normal = -dir;
if (GimmickLayer.Current == null || !GimmickLayer.Current.HasAny) return null;
foreach (var h in Cast(actor, origin, dir, dist))
{
var g = GimmickLayer.InstOf(h.collider);
if (g == null || !g.BlocksMove) continue;
hitDist = h.distance;
normal = h.normal.sqrMagnitude > 1e-6f ? h.normal.normalized : -dir;
if (Vector2.Dot(normal, dir) > 0f) normal = -dir; // 출발부터 겹친 경우(법선 미정)
return g;
}
return null;
}
///
/// 투사체(PD 2026-11-05 #1087 ⑥ + 기믹 #1097): 첫 유닛 또는 막는 기믹. 목책·강 = 통과(pass) · 덤불·바위·성문·성벽 = 막힘(gimmick 에 담고 null) ·
/// 폭약 수레 = 맞으면 파괴(gimmick 에 담고 null) · 안개 안 유닛도 정상 피격(피해만 ×p1 · PD 2026-11-06 개정).
///
public static Unit FirstUnitOnArrow(Unit shooter, Vector2 from, Vector2 dir, float dist, float arrowRadius, Faction? passFaction, out float travel, out GimmickInstance gimmick)
{
travel = dist;
gimmick = null;
Physics2D.SyncTransforms();
var hits = Physics2D.CircleCastAll(from, arrowRadius, dir, dist);
System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
foreach (var h in hits)
{
if (h.collider.GetComponent() != null)
{
travel = h.distance;
return null;
}
var g = GimmickLayer.InstOf(h.collider);
if (g != null)
{
if (!g.BlocksMove || g.Def.projectile == GimmickProjectile.Pass || ArrowIgnoreGimmicks) continue; // 황충 노익장 = 장애물 통과(2026-11-10 #1112)
travel = h.distance;
gimmick = g;
return null;
}
var u = UnitOf(h.collider);
if (u == null || u == shooter || !u.IsAlive) continue;
if (passFaction.HasValue && u.Faction == passFaction.Value) continue;
travel = h.distance;
return u;
}
return null;
}
///
/// 기믹 피해(바위 굴림 · 폭약 폭발) = (공격 × 스킬 공 배율 × 군기 × mult − 방어 × defenseFactor) × 난수 · 상성·크리 없음(「공 ×p」 · 해석 = PD 확인 대상).
///
public static int GimmickDamage(BalanceConfig cfg, Unit attacker, Unit defender, float mult, System.Random rng)
{
if (attacker == null || defender == null) return 0;
float atk = attacker.Atk * SkillMods.AttackMult(attacker, true) * GimmickLayer.AttackMultOf(attacker);
float raw = atk * mult - defender.Def * cfg.defenseFactor;
return Mathf.Max(1, Mathf.RoundToInt(raw * Variance(cfg, rng)));
}
/// 피해 난수(가상 실행 = 분산 0 · 적 AI 예측 · PD #1098).
public static float Variance(BalanceConfig cfg, System.Random rng)
{
if (Sim.Active || rng == null) return 1f;
return 1f + ((float)rng.NextDouble() * 2f - 1f) * cfg.damageVariance;
}
/// 함정 정지(숨은 함정에 중심이 들면 그 자리) — FreeTravel 이 쓴다.
static float TrapStop(Unit actor, Vector2 origin, Vector2 dir, float dist)
{
var l = GimmickLayer.Current;
return l == null || !l.HasAny ? dist : l.TrapStop(actor, origin, dir, dist);
}
}
}