Project_WL/Assets/WL/PartSkill/WLPartStats.cs

185 lines
9.6 KiB
C#
Raw 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.

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