Project_WL/Assets/WL/PartSkill/WLPartSkillCombat.cs

146 lines
7.6 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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;
}
}
}