185 lines
9.6 KiB
C#
185 lines
9.6 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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");
|
||
}
|
||
}
|
||
}
|