101 lines
5.0 KiB
C#
101 lines
5.0 KiB
C#
using UnityEngine;
|
||
|
||
namespace TKS
|
||
{
|
||
/// <summary>지형 기믹 질의(PD #1097) — 장애물 콜라이더 · 투사체 pass/block/destroy · 안개 · 기믹 피해 공식.</summary>
|
||
public static partial class Combat
|
||
{
|
||
/// <summary>true 인 동안 투사체가 장애물 기믹을 통과(황충 노익장 · 스킬 관통 사격 · 호출 쪽이 켜고 끔).</summary>
|
||
public static bool ArrowIgnoreGimmicks;
|
||
|
||
/// <summary>전장 가장자리 벽까지만(기믹·유닛 무시) 나아갈 수 있는 거리.</summary>
|
||
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<BoardWall>() != null) min = Mathf.Min(min, h.distance);
|
||
}
|
||
return Mathf.Max(0f, min - Epsilon);
|
||
}
|
||
|
||
/// <summary>경로(원 캐스트) 위 첫 장애물 기믹. hitDist = 닿기까지 중심 이동 거리 · normal = 닿은 면의 바깥 방향(튕김 방향).</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 투사체(PD 2026-11-05 #1087 ⑥ + 기믹 #1097): 첫 유닛 또는 막는 기믹. 목책·강 = 통과(pass) · 덤불·바위·성문·성벽 = 막힘(gimmick 에 담고 null) ·
|
||
/// 폭약 수레 = 맞으면 파괴(gimmick 에 담고 null) · 안개 안 유닛도 정상 피격(피해만 ×p1 · PD 2026-11-06 개정).
|
||
/// </summary>
|
||
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<BoardWall>() != 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 기믹 피해(바위 굴림 · 폭약 폭발) = (공격 × 스킬 공 배율 × 군기 × mult − 방어 × defenseFactor) × 난수 · 상성·크리 없음(「공 ×p」 · 해석 = PD 확인 대상).
|
||
/// </summary>
|
||
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)));
|
||
}
|
||
|
||
/// <summary>피해 난수(가상 실행 = 분산 0 · 적 AI 예측 · PD #1098).</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>함정 정지(숨은 함정에 중심이 들면 그 자리) — FreeTravel 이 쓴다.</summary>
|
||
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);
|
||
}
|
||
}
|
||
}
|