169 lines
10 KiB
C#
169 lines
10 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLSkillHotkey.cs — 키 하나로 낀 무기의 주력 스킬(1번 칸)을 즉시 발동 (PD #890 · 2026-10-17)
|
|
//
|
|
// PD 원문: 「q 버튼을 누르면 장비의 주력 스킬이 즉시 발동 되도록 해줘.」
|
|
//
|
|
// ■ 동작 — key(기본 Q)를 누르면 slot(1 = 주력) 칸 스킬을 원본 Use_Skill 로 바로 쓴다(ignoreCooldown 1 = 쿨 무시 시험 모드 · 0 = 쿨 지킴).
|
|
// 그 칸이 WL 파츠 스킬(예: 파괴자 1번 = 수호 보호막)이면 파츠 스킬 실행기에서 그 스킬을 바로 Cast(상시형은 안내만).
|
|
// 결과 한 줄을 화면 위쪽에 띄운다. 원본 Use_Skill 이 실패하는 경우 = 다른 스킬 동작 중 · 대상 없음 · 사거리 밖.
|
|
// ■ 값 = Resources/WL/WLSkillHotkey.json (C45) · 끄기 = enabled 0(오브젝트 0) · 원본 수정 0줄
|
|
// 🔴 Debug.LogError 금지(원본 ErrorLogHook 이 팝업을 띄운다).
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
using System.Reflection;
|
|
using UnityEngine;
|
|
using WL.Equip;
|
|
|
|
namespace WL.Combat.Weapon
|
|
{
|
|
[System.Serializable]
|
|
public class WLSkillHotkeyJson
|
|
{
|
|
public int enabled = 0;
|
|
public string key = "Q";
|
|
public int slot = 1; // 1 = 주력 · 2 = 각성
|
|
public string key2 = ""; // #906 두 번째 키(E = 각성) · 비우면 없음
|
|
public int slot2 = 2;
|
|
public string key3 = ""; // #973 세 번째 키(Q = 기본) · 비우면 없음
|
|
public int slot3 = 3;
|
|
public int ignoreRange = 1; // #906 1 = 사거리 밖에서도 즉시 시전(원본 Use_Skill 사거리 확인을 이 키로 쓸 때만 건너뜀)
|
|
public int ignoreCooldown = 1;
|
|
public int cancelCurrent = 1; // 1 = 다른 스킬 동작 중이면 끊고 바로 쓴다(원본 Use_Skill 은 스킬 중이면 실패)
|
|
public float messageSeconds = 1.5f;
|
|
public float messageFontScale = 0.026f;
|
|
public float messageTopRatio = 0.18f;
|
|
public string note = "";
|
|
}
|
|
|
|
public static class WLSkillHotkey
|
|
{
|
|
public const string ResourcesPath = "WL/WLSkillHotkey";
|
|
public static int Fired, Failed;
|
|
public static string LastLog = "";
|
|
internal static string Message = "";
|
|
internal static float MessageUntil;
|
|
internal static KeyCode Key = KeyCode.Q, Key2 = KeyCode.None, Key3 = KeyCode.None;
|
|
/// <summary>#906 원본 Actor.Use_Skill 사거리 확인 훅이 읽는다 — 치트키 시전 중에만 true.</summary>
|
|
public static bool IgnoreRangeNow;
|
|
|
|
static WLSkillHotkeyJson s_cfg;
|
|
static WLSkillHotkeyRunner s_runner;
|
|
static FieldInfo s_fArr;
|
|
|
|
internal static WLSkillHotkeyJson Cfg
|
|
{
|
|
get
|
|
{
|
|
if (s_cfg != null) return s_cfg;
|
|
s_cfg = new WLSkillHotkeyJson();
|
|
var ta = Resources.Load<TextAsset>(ResourcesPath);
|
|
if (ta == null) { LastLog = "설정 없음 " + ResourcesPath; return s_cfg; }
|
|
try { JsonUtility.FromJsonOverwrite(ta.text, s_cfg); } catch (System.Exception ex) { LastLog = "설정 파싱 실패 — " + ex.Message; }
|
|
KeyCode k; Key = System.Enum.TryParse(s_cfg.key, true, out k) ? k : KeyCode.Q;
|
|
Key2 = !string.IsNullOrEmpty(s_cfg.key2) && System.Enum.TryParse(s_cfg.key2, true, out k) ? k : KeyCode.None;
|
|
Key3 = !string.IsNullOrEmpty(s_cfg.key3) && System.Enum.TryParse(s_cfg.key3, true, out k) ? k : KeyCode.None;
|
|
return s_cfg;
|
|
}
|
|
}
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
|
static void Boot()
|
|
{
|
|
s_cfg = null;
|
|
if (Cfg.enabled == 0 || !DSUtil.CheckNull(s_runner)) return; // 꺼져 있으면 오브젝트 0
|
|
var go = new GameObject("[WL890] SkillHotkey");
|
|
go.hideFlags = HideFlags.HideAndDontSave;
|
|
s_runner = go.AddComponent<WLSkillHotkeyRunner>();
|
|
}
|
|
|
|
/// <summary>주력 스킬 즉시 발동(시험 도구도 부른다) · 결과 문장을 화면에 띄운다.</summary>
|
|
public static string Fire() { return Fire(Cfg.slot); }
|
|
|
|
/// <summary>#906 칸 지정 발동(1 = 주력 · 2 = 각성 · 3 = 기본 #973 · 키 = WLSkillHotkey.json).</summary>
|
|
public static string Fire(int slot)
|
|
{
|
|
string r;
|
|
try { r = FireInner(slot); } catch (System.Exception ex) { Failed++; r = "발동 실패 — " + ex.Message; }
|
|
LastLog = r; Message = r; MessageUntil = Time.unscaledTime + Cfg.messageSeconds;
|
|
return r;
|
|
}
|
|
|
|
static string FireInner(int slot)
|
|
{
|
|
var c = Cfg;
|
|
var pc = MyValue.MyPC;
|
|
if (DSUtil.CheckNull(pc) || pc.IsDead()) { Failed++; return "PC 없음"; }
|
|
int idx = Mathf.Clamp(slot - 1, 0, 5);
|
|
string nm = WLWeaponSkillTable.SlotName(slot); // #973 1 주력 · 2 각성 · 3 기본
|
|
// #906 무기 기술 표의 그 칸 — 해금(등급) 확인 · 조건 발동 칸은 표에서 스킬 데이터
|
|
var w = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
|
|
var rows = w != null && w.itemJson != null ? WLWeaponSkillTable.Rows(w.itemJson.GroupNo) : null;
|
|
WLWeaponSkillRow row = null;
|
|
if (rows != null) foreach (var rr in rows) if (rr.slot == slot) { row = rr; break; }
|
|
if (row != null && WLWeaponSkillSlots.LastTier < row.unlockTier) { Failed++; return nm + " 스킬 미해금(" + row.skillName + " · 등급 " + row.unlockTier + "부터)"; }
|
|
if (s_fArr == null) s_fArr = typeof(PCActor).GetField("arr_magicData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
|
var arr = s_fArr != null ? s_fArr.GetValue(pc) as SkillListTableData[] : null;
|
|
var data = arr != null && idx < arr.Length ? arr[idx] : null;
|
|
if (data == null && row != null && !row.IsWlSkill && row.skillId > 0) data = WLWeaponSkillSlots.SkillDataFor(pc as PCActor, row.skillId); // #906 조건 발동 칸
|
|
if (data == null) return FirePartSkill(slot);
|
|
if (pc.Get_CurAnim() >= eAnim.Skill)
|
|
{
|
|
if (c.cancelCurrent == 0) { Failed++; return "스킬 사용 중 — " + data.e_Skill; }
|
|
pc.Play_Idle(); // #890 PD 「치트키 Q 를 눌렀을 때는 쿨타임을 무시하고 주력 스킬이 발동」 — 진행 중 스킬을 끊는다
|
|
}
|
|
bool ok;
|
|
IgnoreRangeNow = c.ignoreRange != 0;
|
|
try { ok = pc.Use_Skill(data, true, idx, c.ignoreCooldown != 0); } finally { IgnoreRangeNow = false; }
|
|
if (ok) { Fired++; return nm + " 스킬 발동 · " + data.e_Skill; }
|
|
Failed++; return nm + " 발동 안 됨 · " + data.e_Skill + "(대상" + (c.ignoreRange != 0 ? "" : " · 사거리") + (c.ignoreCooldown != 0 ? "" : " · 쿨타임") + ")";
|
|
}
|
|
|
|
/// <summary>그 칸이 WL 파츠 스킬이면 실행기에서 바로 Cast.</summary>
|
|
static string FirePartSkill(int slot)
|
|
{
|
|
var w = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
|
|
var rows = w != null && w.itemJson != null ? WLWeaponSkillTable.Rows(w.itemJson.GroupNo) : null;
|
|
WLWeaponSkillRow row = null;
|
|
if (rows != null) foreach (var r in rows) if (r.slot == slot) { row = r; break; }
|
|
if (row == null || !row.IsWlSkill) { Failed++; return WLWeaponSkillTable.SlotName(slot) + " 스킬 없음(등급 미해금 · 표에 없음)"; }
|
|
var run = WL.PartSkill.WLPartSkillRunner.Instance;
|
|
if (run == null) { Failed++; return "파츠 스킬 실행기 없음"; }
|
|
foreach (var sk in run.Skills)
|
|
{
|
|
if (sk == null || sk.def == null || sk.def.key != row.wlSkill) continue;
|
|
if (sk.Persistent) { Failed++; return row.skillName + " — 상시 유지형(즉시 발동 없음)"; }
|
|
sk.Cast(); sk.cd = Mathf.Max(0.2f, sk.CoolSec);
|
|
Fired++; return WLWeaponSkillTable.SlotName(slot) + " 스킬 발동 · " + row.skillName + "(파츠 스킬)";
|
|
}
|
|
Failed++; return row.skillName + " — 실행기에 없음(등급 미해금)";
|
|
}
|
|
}
|
|
|
|
/// <summary>키 읽기 · 결과 한 줄 표시. 숨김 GameObject 1개.</summary>
|
|
internal sealed class WLSkillHotkeyRunner : MonoBehaviour
|
|
{
|
|
GUIStyle _style;
|
|
|
|
void Update()
|
|
{
|
|
var c = WLSkillHotkey.Cfg;
|
|
if (Input.GetKeyDown(WLSkillHotkey.Key)) WLSkillHotkey.Fire(c.slot);
|
|
if (WLSkillHotkey.Key2 != KeyCode.None && Input.GetKeyDown(WLSkillHotkey.Key2)) WLSkillHotkey.Fire(c.slot2); // #906 E = 각성
|
|
if (WLSkillHotkey.Key3 != KeyCode.None && Input.GetKeyDown(WLSkillHotkey.Key3)) WLSkillHotkey.Fire(c.slot3); // #973 Q = 기본
|
|
}
|
|
|
|
void OnGUI()
|
|
{
|
|
var msg = WLSkillHotkey.Message;
|
|
if (string.IsNullOrEmpty(msg) || Time.unscaledTime > WLSkillHotkey.MessageUntil) return;
|
|
var c = WLSkillHotkey.Cfg;
|
|
if (_style == null) _style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontStyle = FontStyle.Bold };
|
|
_style.fontSize = Mathf.Max(14, Mathf.RoundToInt(Screen.height * c.messageFontScale));
|
|
var r = new Rect(0f, Screen.height * c.messageTopRatio, Screen.width, _style.fontSize * 2f);
|
|
var keep = GUI.color;
|
|
GUI.color = new Color(0f, 0f, 0f, 0.75f); GUI.Label(new Rect(r.x + 2f, r.y + 2f, r.width, r.height), msg, _style);
|
|
GUI.color = new Color(0.6f, 0.95f, 1f); GUI.Label(r, msg, _style);
|
|
GUI.color = keep;
|
|
}
|
|
}
|
|
}
|