using System.Collections.Generic;
using UnityEngine;
namespace TKS
{
///
/// 적군 슬링샷 결정: 교전 범위 안의 가장 가까운 아군을 향해 당기고, 정확도에 따라 흔들린다. 범위 밖이면 대기(null).
/// 궁병·책사(PD 2026-11-05 #1087 ⑤⑥ · cfg 를 넘기면): 공격 가능 범위 안에서 사선이 빈 대상 우선 · 책사는 맞을 자리 광역에 자기 편이 없는 대상을 그보다 먼저 · 없으면 가장 가까운 대상(그냥 쏜다).
///
public static class EnemyAI
{
public static Vector2? Decide(Unit e, IReadOnlyList targets, float accuracy, float aggroRange, System.Random rng, Board board, BalanceConfig cfg = null)
{
Unit nearest = null;
float best = float.MaxValue;
for (int i = 0; i < targets.Count; i++)
{
var t = targets[i];
if (!t.IsAlive) continue;
float d = Vector2.Distance(e.Position, t.Position);
if (d < best)
{
best = d;
nearest = t;
}
}
if (nearest == null) return null;
if (best > aggroRange) return null; // 넓은 전장: 교전 범위 밖이면 자리 지킴
if (cfg != null && UnitTypeInfo.IsRanged(e.Type)) nearest = PickRangedTarget(e, targets, nearest, cfg);
Vector2 toT = nearest.Position - e.Position;
float dist = toT.magnitude;
Vector2 dir = dist > 0.001f ? toT / dist : Vector2.up;
float sling = SkillMods.SlingMax(e); // 적 무장 패시브(적토마 등 · PD #1093 B) 반영
Vector2 target;
if (dist <= sling)
{
target = nearest.Position; // 최종 목표점 명중 시도
}
else
{
target = e.Position + dir * sling; // 접근(궁병은 사거리 안이면 이동 후 사격)
}
if (rng.NextDouble() > accuracy)
{
float ang = ((float)rng.NextDouble() * 2f - 1f) * 22f * Mathf.Deg2Rad;
float cs = Mathf.Cos(ang);
float sn = Mathf.Sin(ang);
Vector2 rd = new Vector2(dir.x * cs - dir.y * sn, dir.x * sn + dir.y * cs);
float len = (target - e.Position).magnitude * (0.75f + 0.5f * (float)rng.NextDouble());
target = e.Position + rd * Mathf.Min(len, sling);
}
Vector2 v = target - e.Position;
if (v.magnitude > sling) v = v.normalized * sling;
return board.ClampInside(e.Position + v, e.Radius);
}
///
/// 원거리 대상 고르기: 공격 가능 범위(사거리 + 접근 허용 · 가장자리 간격) 안의 대상만 본다.
/// 등급 0 = 사선이 빔(화살이 처음 맞힐 유닛 = 그 대상) + (책사) 맞을 자리 광역에 자기 편 없음 · 1 = 사선만 빔(책사) · 2 = 막힘 → 등급 낮은 순 · 같으면 가까운 순.
/// 범위 안에 아무도 없으면 가장 가까운 대상(접근).
///
static Unit PickRangedTarget(Unit e, IReadOnlyList targets, Unit nearest, BalanceConfig cfg)
{
Unit pick = null;
int pickTier = int.MaxValue;
float pickD = float.MaxValue;
float reach = SkillMods.Range(e) + e.ArcherMoveAllow;
for (int i = 0; i < targets.Count; i++)
{
var t = targets[i];
if (!t.IsAlive) continue;
float d = Vector2.Distance(e.Position, t.Position);
if (d - e.Radius * Combat.CastRadiusScale - t.Radius > reach || d < 0.001f) continue;
float travel;
var first = Combat.FirstUnitOnArrow(e, e.Position, (t.Position - e.Position) / d, d, cfg.arrowRadius, out travel);
bool clear = first == t;
bool safe = true;
if (clear && e.Type == UnitType.Strategist)
{
foreach (var s in Combat.UnitsInCircle(t.Position, cfg.strategistSplashRadius, t, e))
{
if (s.Faction == e.Faction) safe = false;
}
}
int tier = clear ? (safe ? 0 : 1) : 2;
if (tier < pickTier || (tier == pickTier && d < pickD))
{
pick = t;
pickTier = tier;
pickD = d;
}
}
return pick ?? nearest;
}
}
}