Project_WL/Assets/WL/PartSkill/WLPartStats.cs

185 lines
9.6 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLPartStats.cs — 장비 부위 기본 능력치 중 「배율 · 조건」 몫 (PD 10-16 원문 #879)
//
// PD: 무기 = 무기 타입별 공격력 · 갑옷 = 방어력 · 장갑 = 생명력 · 신발(부츠) = 이동속도 · 반지 = 근접 공격력과 원거리 공격력 중 랜덤 · 목걸이 = 랜덤 스텟
// - 평평한 값(공격 · 생명 · 방어)과 반지의 근접/원거리 공격력 = WLEquipStats(기존 식 · 부위 표 WLGradeTable.json parts) 가 더한다.
// - 여기 = 신발 이동 속도 · 목걸이 랜덤 스탯 · 반지 근접/원거리 판정 · 파츠 패시브 스탯을 「훅 1」(ActorStatInfo.Reset 안 · 최종 공식 전)에 더한다.
// - 반지 종류 · 목걸이 스탯 = 아이템 고유 번호(_id)로 정한다(같은 아이템은 늘 같은 값 · 새로 얻으면 다시 뽑힘).
// - 벨트 → 반지 이름 = WLPartStats.json renameItems(FR 원본 파일 무수정 · 로드 뒤 표시 이름만).
// 값 = Resources/WL/PartSkill/WLPartStats.json(C45) · 제안값.
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using UnityEngine;
using WL.Equip;
namespace WL.PartSkill
{
public static class WLPartStats
{
public static string LastHook1 { get; private set; } = "";
public static int Hook1Count;
static int Hash(Item it)
{
if (it == null || it.rawItem == null) return 0;
unchecked
{
int h = it.rawItem._id * 73856093 ^ it.rawItem.index * 19349663;
return h & 0x7fffffff;
}
}
public static int TierOf(Item it)
{
WLGradeTier t; int plus;
return it != null && it.itemJson != null && WLEquipGrades.TryMap(it.itemJson, out t, out plus) ? t.tier : 1;
}
static float At(float[] arr, int i) { return arr == null || arr.Length == 0 ? 0f : arr[Mathf.Clamp(i, 0, arr.Length - 1)]; }
// ── 반지 ────────────────────────────────────────────────────────────
public static WLPartRingKind RingKind(Item it)
{
var k = WLPartSkillData.Stats.ringKinds;
if (k == null || k.Count == 0 || it == null) return null;
return k[Hash(it) % k.Count];
}
/// <summary>장착 무기의 계열(WLWeaponAttackTable family) — Sword · Hammer · Bow …</summary>
public static string EquippedWeaponFamily()
{
var w = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
if (w == null || w.itemJson == null) return "";
var f = WL.Combat.Weapon.WLWeaponAttackTable.First(w.itemJson.GroupNo);
return f != null ? f.family : "";
}
public static string WeaponReach(string family)
{
var S = WLPartSkillData.Stats;
if (S.meleeFamilies != null && S.meleeFamilies.Contains(family)) return "melee";
if (S.rangedFamilies != null && S.rangedFamilies.Contains(family)) return "ranged";
return "";
}
/// <summary>반지 효과가 지금 무기와 맞는가(근접 무기 = 근접 공격력 · 원거리 무기 = 원거리 공격력).</summary>
public static bool RingMatches(Item ring, out string ringName)
{
var k = RingKind(ring);
ringName = k != null ? k.name : "공격력";
if (k == null) return true;
return WeaponReach(EquippedWeaponFamily()) == k.id;
}
// ── 목걸이 · 신발 ────────────────────────────────────────────────────
public static WLPartStatPool NecklaceStat(Item it)
{
var pool = WLPartSkillData.Stats.necklacePool;
if (pool == null || pool.Count == 0 || it == null) return null;
return pool[Hash(it) % pool.Count];
}
public static float NecklaceValue(Item it, out WLPartStatPool p)
{
p = NecklaceStat(it);
return p != null ? At(p.perTier, TierOf(it)) : 0f;
}
public static float ShoesMove(Item it) { return it == null ? 0f : At(WLPartSkillData.Stats.shoesMovePerTier, TierOf(it)); }
/// <summary>표시 문자열 — 「이동 속도 +3%」.</summary>
public static string PoolText(WLPartStatPool p, float v)
{
if (p == null) return "";
return p.name + " +" + (v * p.pct).ToString(p.pct >= 100f ? "0.#" : "0.##") + "%";
}
/// <summary>WLPartSkillState.Rebuild 가 부른다 — 목걸이 랜덤 스탯 · 신발 이동 속도를 구성에 더한다.</summary>
public static void AddPartStats(WLPartLoadout L)
{
WLPartSlot s;
if (L.byPart.TryGetValue(eFREquipmentType.Shose, out s) && s.item != null) L.moveBonus += ShoesMove(s.item);
if (L.byPart.TryGetValue(eFREquipmentType.Necklace, out s) && s.item != null)
{
WLPartStatPool p; float v = NecklaceValue(s.item, out p);
if (p != null && v != 0f)
{
switch (p.stat)
{
case "MOVE": L.moveBonus += v; break;
case "PART_SKILL_DMG": L.dmgMul += v; break;
case "PART_SKILL_COOL": L.coolMul *= Mathf.Max(0.1f, 1f - v); break;
default:
eStat e;
if (System.Enum.TryParse(p.stat, out e)) L.AddStat(e, v);
break;
}
}
}
}
/// <summary>훅 1 — WLEquipStats.Apply(ActorStatInfo.Reset 안 · PC 만)에서 부른다. 최종 공식(ATK × 배율 등) 전에 배율을 더한다.</summary>
public static void ApplyHook1(ActorStatInfo info)
{
if (info == null || !WLPartSkillData.Enabled) return;
WLPartSkillState.EnsureHooked();
if (WLPartSkillState.Version == 0) WLPartSkillState.Rebuild("hook1");
var L = WLPartSkillState.Current;
var sb = new System.Text.StringBuilder();
foreach (var kv in L.statAdds)
{
if (kv.Value == 0d) continue;
info.Set_Stat(true, kv.Key, kv.Value);
sb.Append(kv.Key).Append('+').Append(kv.Value.ToString("0.###")).Append(' ');
}
Hook1Count++;
LastHook1 = sb.ToString();
}
// ── 이름 바꾸기(벨트 → 반지) ─────────────────────────────────────────
public static int RenameItems(IList<ItemJson> items)
{
if (items == null || !WLPartSkillData.EnsureLoaded()) return 0;
var rules = WLPartSkillData.Stats.renameItems;
if (rules == null || rules.Count == 0) return 0;
int n = 0;
for (int i = 0; i < items.Count; i++)
{
var j = items[i]; if (j == null || string.IsNullOrEmpty(j.Item_name)) continue;
for (int r = 0; r < rules.Count; r++)
{
var rule = rules[r];
if (rule == null || (int)j.EquipmentType != rule.equipType || string.IsNullOrEmpty(rule.from) || !j.Item_name.Contains(rule.from)) continue;
j.Item_name = j.Item_name.Replace(rule.from, rule.to ?? "");
n++;
}
}
return n;
}
// ── UI 한 줄 ────────────────────────────────────────────────────────
/// <summary>장비 1개의 기본 능력치 한 줄(슬롯 아래 · 상세 팝업). 평평한 값은 WLEquipStats.ItemValues 를 쓴다.</summary>
public static string ItemStatLine(Item it, bool shortForm)
{
if (it == null || it.itemJson == null || !it.itemJson.IsEquipment) return "";
var type = it.itemJson.EquipmentType;
if (type == eFREquipmentType.Shose) return "이동 속도 +" + (ShoesMove(it) * 100f).ToString("0.#") + "%";
if (type == eFREquipmentType.Necklace)
{
WLPartStatPool p; float v = NecklaceValue(it, out p);
return p != null ? PoolText(p, v) : "";
}
eStat o; double bv, bonus; string line;
if (!WLEquipStats.ItemValues(it, WLEquipStats.CurrentMainStat(), out o, out bv, out bonus, out line)) return "";
if (type == eFREquipmentType.Belt)
{
string rn; bool ok = RingMatches(it, out rn);
if (shortForm) return ok ? rn + " +" + (bv + bonus).ToString("F1") : "<color=#FF8080>" + rn + " +" + (bv + bonus).ToString("F1") + "</color>";
return rn + " +" + (bv + bonus).ToString("F1") + (ok ? "" : " <color=#FF8080>(지금 무기와 맞지 않아 효과 없음)</color>");
}
return WLEquipGrades.OutputName(o) + " +" + (bv + bonus).ToString("F1");
}
}
}