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

101 lines
5.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}