153 lines
10 KiB
C#
153 lines
10 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLWeaponClassApplier.cs — 장착 무기 → 힘민지 직업(모션 세트) 전환 (#825 · 2026-10-09 · 기획 v1 판단 ① 권고 A)
|
|
//
|
|
// 장착한 무기(FR GroupNo)의 표 class_id 로 `PCActor.Change_Class(cd, true)` 를 부른다 → 애니메이터·무기 모델·사거리·탄(Projectile_{CID})이
|
|
// 함께 바뀐다. `_ui = true` 라 서버 세이브(Equip[PC])는 건드리지 않는다(힘민지 ServerInfo.Write 오염 방지 · 코드 조사 2026-10-09).
|
|
// - 켜고 끄기 = WLWeaponSettings.classSwitch (PD 판단 ① 확정 전 기본 0)
|
|
// - 섬(로비 맵)에서는 Set_Obj 가 로비 애니메이터를 끼우므로 WLIslandAvatar.ApplyCombatForm 과 같은 방식으로 호출 동안만 Stage 로 바꾼다
|
|
// - 공격·스킬 모션 중에는 바꾸지 않고, 전환 뒤 settleSec 동안은 다시 바꾸지 않는다(컨트롤러 비동기 로드)
|
|
// - 전환 뒤 WLEquipStats.RefreshPc 로 장비 스탯을 다시 반영한다
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using UnityEngine;
|
|
using WL.Equip;
|
|
|
|
namespace WL.Combat.Weapon
|
|
{
|
|
// WLWeaponSettings 는 WLWeaponSettings.cs 로 옮김(#883 · 파일 이름 = 클래스 이름이어야 에셋이 스크립트를 참조한다)
|
|
|
|
public class WLWeaponClassApplier : MonoBehaviour
|
|
{
|
|
public static WLWeaponClassApplier Instance { get; private set; }
|
|
public static int Applied, Skipped, Forced;
|
|
public static string LastLog { get; private set; } = "";
|
|
public static int LastClassId, DesiredClassId;
|
|
|
|
float _t; float _settleUntil; float _pendingSince = -1f;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
|
static void Boot()
|
|
{
|
|
var cfg = WLWeaponSettings.Instance;
|
|
if (cfg == null || cfg.enabled_ == 0 || Instance != null) return;
|
|
var go = new GameObject("WL_WeaponClassApplier");
|
|
DontDestroyOnLoad(go);
|
|
Instance = go.AddComponent<WLWeaponClassApplier>();
|
|
}
|
|
|
|
void OnEnable() { WLInventory.Changed += OnInventoryChanged; }
|
|
void OnDisable() { WLInventory.Changed -= OnInventoryChanged; }
|
|
void OnDestroy() { if (Instance == this) Instance = null; }
|
|
void OnInventoryChanged() { _t = 999f; } // 다음 Update 에서 바로 점검
|
|
|
|
void Update()
|
|
{
|
|
var cfg = WLWeaponSettings.Instance;
|
|
if (cfg == null || cfg.enabled_ == 0 || cfg.classSwitch == 0) return;
|
|
_t += Time.unscaledDeltaTime;
|
|
if (_t < cfg.pollSec) return;
|
|
_t = 0f;
|
|
TryApply(cfg, "poll");
|
|
}
|
|
|
|
/// <summary>
|
|
/// #851 PD 「던전에 들어갈 때 멋대로 무기가 바뀌면 안돼」 — 씬마다 새로 만들어지는 내 PC 를 **처음부터** 장착 무기의 직업으로 만든다.
|
|
/// 원본은 서버 데이터의 장착 직업(Equip[PC] · 10501)으로 PC 를 만들고, 이 적용기가 폴링으로 뒤늦게 바꿔 「해머 → 검」 교체가 보였다.
|
|
/// 원본 훅 = PCActor.Set 1줄. 무기가 없거나 표에 없으면 받은 값 그대로(원본 동작).
|
|
/// </summary>
|
|
public static int ResolveSpawnClass(int baseClassId)
|
|
{
|
|
var cfg = WLWeaponSettings.Instance;
|
|
if (cfg == null || cfg.enabled_ == 0 || cfg.classSwitch == 0) return baseClassId;
|
|
try
|
|
{
|
|
if (!WLInventory.EnsureLoaded()) return baseClassId;
|
|
WLStarterKit.Ensure(cfg); // 🔴 임시 시작 장비(검 장착)를 출생 전에 — 시작 직후 「해머 → 검」 교체도 없앤다(세이브 객체당 1회 · 멱등)
|
|
var w = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
|
|
int desired = w != null && w.itemJson != null ? WLWeaponAttackTable.ClassIdFor(w.itemJson.GroupNo) : 0;
|
|
if (desired <= 0 || desired == baseClassId) return baseClassId;
|
|
WLWeaponClasses.Ensure(); // WL 추가 직업(창·대검) 행을 ClassConfig 에 보탠다(멱등)
|
|
var cd = table_classconfig.Ins != null ? table_classconfig.Ins.Get_Data_orNull(desired) : null;
|
|
if (cd == null) return baseClassId;
|
|
SpawnResolved++;
|
|
LastSpawnLog = "spawn: 직업 " + baseClassId + " → " + desired + " (" + w.itemJson.Item_name + ")";
|
|
if (cfg.verboseLog != 0) Debug.Log("[WL-825] " + LastSpawnLog);
|
|
return desired;
|
|
}
|
|
catch (System.Exception) { return baseClassId; } // 원본은 없는 ID 에 예외를 던진다 — 원래 값으로
|
|
}
|
|
public static int SpawnResolved;
|
|
public static string LastSpawnLog = "";
|
|
|
|
static int CurrentClassId(PCActor pc)
|
|
{
|
|
if (pc == null) return 0;
|
|
var f = typeof(Actor).GetField("m_ID", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
|
|
object v = f != null ? f.GetValue(pc) : null;
|
|
return v is int ? (int)v : 0;
|
|
}
|
|
|
|
/// <summary>장착 무기의 직업으로 전환을 시도한다. 돌려주는 값 = 결과 한 줄(프로브·로그용).</summary>
|
|
public string TryApply(WLWeaponSettings cfg, string why)
|
|
{
|
|
if (!MyValue.bMyPC || MyValue.MyPC == null) return Note("PC 없음");
|
|
var pc = MyValue.MyPC;
|
|
if (!WLInventory.EnsureLoaded()) return Note("인벤토리 미로드");
|
|
WLStarterKit.Ensure(cfg); // 🔴 임시(PD 2026-10-10) — 무기 6계열 1개씩 + 검 장착 · 1회
|
|
var w = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
|
|
int desired = w != null && w.itemJson != null ? WLWeaponAttackTable.ClassIdFor(w.itemJson.GroupNo) : 0;
|
|
DesiredClassId = desired;
|
|
int cur = CurrentClassId(pc);
|
|
LastClassId = cur;
|
|
if (desired <= 0) { Skipped++; return Note(w == null ? "무기 미장착" : "표에 없는 무기 그룹 " + w.itemJson.GroupNo); }
|
|
WLWeaponClasses.Ensure(); // WL 추가 직업(창) 행을 ClassConfig 에 보탠다(멱등)
|
|
WLWeaponClasses.EnsureWeaponOrient(pc); // #834 활 등 무기 인스턴스 방향 보정(표식 1회 · 재장착 때 재적용)
|
|
if (cur == desired)
|
|
{
|
|
_pendingSince = -1f;
|
|
bool ev = WLWeaponClasses.EnsureClipEvents(pc, cur); // 추가 직업 = 클립 타격 이벤트 보강(컨트롤러 로드 뒤)
|
|
bool rm = pc.Get_CurAnim() < eAnim.Attack && WLWeaponClasses.RefreshModelIfNeeded(pc, cur); // 수정 행(망치) = 살아 있는 PC 의 무기 모델 재부착 1회
|
|
return Note("이미 직업 " + desired + " (" + w.itemJson.Item_name + ")" + (ev ? "" : " · 클립 이벤트 대기") + (rm ? " · 모델 재부착" : ""));
|
|
}
|
|
if (Time.unscaledTime < _settleUntil) { Skipped++; return Note("안정화 대기"); }
|
|
if (pc.Get_CurAnim() >= eAnim.Attack)
|
|
{
|
|
// 연속 전투에서는 공격 끝 프레임을 폴링으로 못 잡는다(2026-10-09 스모크: 6종 전부 「보류」) → 일정 시간 뒤 공격을 끊고 바꾼다
|
|
if (_pendingSince < 0f) _pendingSince = Time.unscaledTime;
|
|
if (cfg.forceSwitchAfterSec <= 0f || Time.unscaledTime - _pendingSince < cfg.forceSwitchAfterSec) { Skipped++; return Note("공격 중 — 보류"); }
|
|
try { pc.Play_Idle(); } catch (System.Exception) { }
|
|
Forced++;
|
|
}
|
|
_pendingSince = -1f;
|
|
ClassConfigTableData cd = null;
|
|
try { cd = table_classconfig.Ins != null ? table_classconfig.Ins.Get_Data_orNull(desired) : null; } catch (System.Exception) { cd = null; } // 원본은 없는 ID 에 예외를 던진다
|
|
if (cd == null) { Skipped++; return Note("ClassConfig 없음 " + desired); }
|
|
|
|
// 섬(로비 맵)은 Set_Obj 가 로비 애니메이터를 끼운다 → 호출 동안만 Stage 로(WLIslandAvatar.ApplyCombatForm 과 같은 방식)
|
|
var info = InGameInfo.Ins;
|
|
var gf = info != null ? typeof(InGameInfo).GetField("m_GameMode", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) : null;
|
|
object prev = gf != null ? gf.GetValue(info) : null;
|
|
bool flipped = false;
|
|
try
|
|
{
|
|
if (gf != null && prev is eGameMode && (eGameMode)prev == eGameMode.Lobby) { gf.SetValue(info, eGameMode.Stage); flipped = true; }
|
|
pc.Change_Class(cd, true);
|
|
}
|
|
catch (System.Exception ex) { return Note("전환 실패 — " + ex.Message); }
|
|
finally { if (flipped) gf.SetValue(info, prev); }
|
|
try { WLEquipStats.RefreshPc(); } catch (System.Exception ex) { Debug.LogWarning("[WL-825] 스탯 재반영 실패 — " + ex.Message); }
|
|
Applied++;
|
|
_settleUntil = Time.unscaledTime + cfg.settleSec;
|
|
return Note(why + ": 직업 " + cur + " → " + desired + " (" + w.itemJson.Item_name + " · " + WLWeaponAttackTable.Describe(w.itemJson.GroupNo) + ")" + (flipped ? " · 로비 모드 우회" : ""));
|
|
}
|
|
|
|
string Note(string s)
|
|
{
|
|
LastLog = s;
|
|
var cfg = WLWeaponSettings.Instance;
|
|
if (cfg != null && cfg.verboseLog != 0 && s.Contains("→")) Debug.Log("[WL-825] " + s);
|
|
return s;
|
|
}
|
|
}
|
|
}
|