146 lines
7.6 KiB
C#
146 lines
7.6 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLPartSkillCombat.cs — 파츠 스킬 피해 · 상태 · 넉백 · 투사체 막기 (PD 지시 #879 · 2026-10-16)
|
||
//
|
||
// 피해 = 힘민지 원본 그대로: PC.Get_DamageInfo(가짜 투사체 · 피해 배율) → 치명 판정 포함 → 대상.Cal_Damage(HP · 사망 · 피해 숫자).
|
||
// Get_Damage(방어 · HitConfirmed) 를 거치지 않는다 — 파츠 스킬 타격마다 히트스톱 · 카메라 흔들림 · WLStatus 콤보 트리거가 터지지 않게(VS 류 다단 히트).
|
||
// WLStatusRunner 도트와 같은 방식(Actor.cs Cal_Damage · Skill_Ignited 가짜 데이터 = 피격 모션 생략). 처치 = Set_Die → CombatEvents.Killed 그대로.
|
||
// 상태 = WLStatus.Apply(기절 · 감속 · 중독 · 출혈 — 기존 VFX · 문구 그대로) · 보스에는 기절 · 넉백을 걸지 않는다(원본 KnockBack 규칙과 같음).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
using WL.Combat.Status;
|
||
|
||
namespace WL.PartSkill
|
||
{
|
||
public static class WLPartSkillCombat
|
||
{
|
||
static ProjectileData s_pdHit, s_pdDot;
|
||
static readonly Collider[] s_cols = new Collider[48];
|
||
public static int Hits, Kills, Blocks, Statuses;
|
||
public static double DamageTotal;
|
||
public static readonly Dictionary<string, double> DamageByKey = new Dictionary<string, double>();
|
||
public static readonly Dictionary<string, int> HitsByKey = new Dictionary<string, int>();
|
||
|
||
public static PCActor PC { get { return MyValue.bMyPC ? MyValue.MyPC : null; } }
|
||
|
||
public static bool Valid(Actor a) { return a != null && a.gameObject.activeInHierarchy && !a.IsDead(); }
|
||
|
||
static ProjectileData Pd(bool anim)
|
||
{
|
||
if (s_pdHit == null) s_pdHit = new ProjectileData { m_SkillListTableData = new SkillListTableData() };
|
||
if (s_pdDot == null) s_pdDot = new ProjectileData { m_SkillListTableData = new SkillListTableData { e_Skill = eEffect.Skill_Ignited } };
|
||
return anim ? s_pdHit : s_pdDot;
|
||
}
|
||
|
||
/// <summary>피해 1회. ratio = 공격력 배율(FR 피해 % ÷ 100 × 등급 배율 × 설정). anim = 피격 모션(몹 움찔) 여부.</summary>
|
||
public static bool Damage(Actor victim, float ratio, bool anim, string key)
|
||
{
|
||
var pc = PC;
|
||
if (pc == null || !Valid(victim) || ratio <= 0f) return false;
|
||
var pd = Pd(anim);
|
||
pd.shooter = pc; pd.target = victim; pd.AddDmgPercent = ratio;
|
||
DamageInfo d;
|
||
try { d = pc.Get_DamageInfo(pd); }
|
||
catch (System.Exception ex) { Debug.LogWarning("[WL-879] 피해 계산 실패 — " + ex.Message); return false; }
|
||
if (d == null) return false;
|
||
d.Beater_pd = pd;
|
||
if (d.Damage < 1d) d.Damage = 1d;
|
||
double dealt = d.Damage;
|
||
victim.Cal_Damage(d);
|
||
Hits++; DamageTotal += dealt;
|
||
if (!string.IsNullOrEmpty(key))
|
||
{
|
||
double cur; DamageByKey.TryGetValue(key, out cur); DamageByKey[key] = cur + dealt;
|
||
int hc; HitsByKey.TryGetValue(key, out hc); HitsByKey[key] = hc + 1;
|
||
}
|
||
if (victim.IsDead()) Kills++;
|
||
return true;
|
||
}
|
||
|
||
public static double PcAttack()
|
||
{
|
||
var pc = PC;
|
||
try { return pc != null ? pc.Get_StatInfo().Get_Stat(eStat.BaseDmg) : 0d; } catch { return 0d; }
|
||
}
|
||
|
||
static bool IsBoss(Actor a) { return a != null && a.IsSubRole(eSubRol.Boss); }
|
||
|
||
public static void Stun(Actor v, float sec)
|
||
{
|
||
if (!Valid(v) || sec <= 0f || IsBoss(v)) return;
|
||
if (WLStatus.Apply(v, WLStatusType.Stun, sec, 0f, PC)) Statuses++;
|
||
}
|
||
|
||
public static void Slow(Actor v, float ratio, float sec)
|
||
{
|
||
if (!Valid(v) || sec <= 0f || ratio <= 0f) return;
|
||
if (WLStatus.Apply(v, WLStatusType.SpeedDown, sec, ratio, PC)) Statuses++;
|
||
}
|
||
|
||
/// <summary>중독 — 초당 고정 피해(공격력 × perSecRatio).</summary>
|
||
public static void Poison(Actor v, float perSecRatio, float sec)
|
||
{
|
||
if (!Valid(v) || sec <= 0f) return;
|
||
float perSec = (float)(PcAttack() * perSecRatio);
|
||
if (WLStatus.Apply(v, WLStatusType.Poison, sec, Mathf.Max(1f, perSec), PC)) Statuses++;
|
||
}
|
||
|
||
/// <summary>출혈 — 초당 공격력 × perSecRatio 피해(WLStatus 출혈은 최대 HP 비율이라 대상 최대 HP 로 나눠 넣는다).</summary>
|
||
public static void Bleed(Actor v, float perSecRatio, float sec)
|
||
{
|
||
if (!Valid(v) || sec <= 0f) return;
|
||
double maxHp = v.Get_MaxHP();
|
||
if (maxHp <= 0d) return;
|
||
float rate = (float)(PcAttack() * perSecRatio / maxHp);
|
||
if (WLStatus.Apply(v, WLStatusType.Bleed, sec, Mathf.Max(0.0001f, rate), PC)) Statuses++;
|
||
}
|
||
|
||
/// <summary>from 에서 멀어지게 밀어낸다(원본 Knockback_byHitter · 보스 · OBJ 는 원본이 무시).</summary>
|
||
public static void Knock(Actor v, Vector3 from, float dist, float speed)
|
||
{
|
||
if (!Valid(v) || dist <= 0f || speed <= 0f || IsBoss(v)) return;
|
||
try { v.Knockback_byHitter(from, dist / speed, speed, null, true); } catch { }
|
||
}
|
||
|
||
/// <summary>to 쪽으로 끌어당긴다(NavMeshAgent.Move · 내비 밖이면 무시).</summary>
|
||
public static void Pull(Actor v, Vector3 to, float speed, float dt)
|
||
{
|
||
if (!Valid(v) || IsBoss(v)) return;
|
||
var ag = v.GetComponent<NavMeshAgent>();
|
||
if (ag == null || !ag.enabled || !ag.isOnNavMesh) return;
|
||
var d = to - v.Get_position(); d.y = 0f;
|
||
float m = d.magnitude;
|
||
if (m < 0.35f) return;
|
||
ag.Move(d / m * Mathf.Min(m, speed * dt));
|
||
}
|
||
|
||
/// <summary>center 반경 안 적 투사체를 끈다(대지 방벽 · 거인의 손 BlockProjectile). 끈 개수.</summary>
|
||
public static int BlockProjectiles(Vector3 center, float radius, string fx)
|
||
{
|
||
var pc = PC; if (pc == null) return 0;
|
||
int n = Physics.OverlapSphereNonAlloc(center, radius, s_cols, ~0, QueryTriggerInteraction.Collide);
|
||
int blocked = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var c = s_cols[i]; if (c == null) continue;
|
||
var pb = c.GetComponentInParent<ProjectileBase>();
|
||
if (pb == null || !pb.gameObject.activeInHierarchy) continue;
|
||
var pd = pb.Get_ProjectTileData();
|
||
var shooter = pd != null ? pd.shooter : null;
|
||
if (shooter == null || shooter == (Actor)pc || shooter.IsEnemy() == pc.IsEnemy()) continue;
|
||
var at = pb.transform.position;
|
||
pb.gameObject.SetActive(false);
|
||
blocked++; Blocks++;
|
||
if (!string.IsNullOrEmpty(fx))
|
||
{
|
||
var r = WLPartSkillRunner.Instance;
|
||
WLPartSkillFx.Play(fx, at, r != null ? r.FxScale(fx, WLPartSkillData.S.hitFxScale) : WLPartSkillData.S.hitFxScale, 1f);
|
||
}
|
||
}
|
||
return blocked;
|
||
}
|
||
}
|
||
}
|