From 1ed0fb73f91c5e96110dc9d22faa5872fda3399c Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Sat, 17 Oct 2026 23:49:51 +0700 Subject: [PATCH] Restore freecam mouse look and block battle mouse actions --- README.md | 13 +++-- scripts/freecam.lua | 122 ++++++++++++++++++++++++++++++++++++++------ scripts/hide_ui.lua | 8 +++ 3 files changed, 123 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9093c5f..b827f7b 100644 --- a/README.md +++ b/README.md @@ -284,13 +284,16 @@ python tools/audit_protocol_handlers.py "" - เมื่อมีการเซฟ preset การสู้ทั่วไป/Calyx ใช้สมาชิกทีมที่เลือก แต่คง stage/waves จากข้อมูลเดิม ห้อง Endgame ใช้ทีมประจำห้องของ ChallengeRun - Build ผ่านยังไม่ยืนยันว่า UI ของ Client แสดงครบ 20 ช่องและใช้งาน Quick Lineup ได้ทุกหน้า ต้องตรวจเพิ่มในเกม การจัดทีม Endgame โดย request ExtraLineupType ยังคงเป็นคนละเส้นทางกับการแก้ preset ทีมเดินโลก -### Freecam: คีย์บอร์ด +### Freecam -- `Tab`: เปิด/ปิด Freecam; ลูกศร: เลื่อนกล้อง; `I/K`: ก้มเงย; `J/L`: หันซ้ายขวา -- `Page Up` / `Page Down`: ขึ้น/ลง; `+` / `-`: เพิ่ม/ลดความเร็ว -- Freecam ไม่อ่านเมาส์และไม่ใช้ `E/Q` คุมกล้อง เพื่อให้กดสกิลระหว่างสู้ได้โดยกล้องไม่ลอยขึ้นลง ปุ่มเมาส์ยังทำงานตามคำสั่งของเกมตามปกติ -- สคริปต์อัปเดตผ่าน heartbeat โดยไม่ต้อง build หรือรีสตาร์ตเซิร์ฟเวอร์ การใช้งานใน Fight ยังรอยืนยันจาก Client +- `Tab`: เปิด/ปิด Freecam; ลูกศร: เลื่อนกล้อง; กดเมาส์ขวาค้างแล้วขยับเมาส์เพื่อหันกล้อง +- `Page Up` / `Page Down`: ขึ้น/ลง; `+` / `-`: เพิ่ม/ลดความเร็ว ไม่ใช้ E/Q หรือ I/J/K/L คุมกล้อง +- ขณะเปิด Freecam ใน Battle ซ่อนการรับคลิก UI ด้วย `InControlInputModule.allowMouseInput` และพักเฉพาะ mouse binding ซ้าย/ขวาของคำสั่งเกมในหน่วยความจำ คีย์บอร์ดยังกดสกิลได้ ส่วนกล้องอ่านเมาส์ผ่าน Unity Input โดยตรง +- คืนค่าตอนปิด Freecam/ออก Battle หลังปล่อยปุ่มเมาส์ และก่อนปิด Client ไม่มีการลบ/เพิ่ม binding ผ่าน OnBindingsChanged ชื่อ field ตรวจจาก Client โดยตรง ผลป้องกันคลิกใน Battle ยังรอยืนยันการเล่นจริง +- สคริปต์อัปเดตผ่าน heartbeat โดยไม่ต้อง build หรือรีสตาร์ตเซิร์ฟเวอร์ ### Hide UI `Backslash` ซ่อนเฉพาะ HUD ที่ระบุชื่อไว้ใน `scripts/hide_ui.lua` เช่น HUD เดินโลก/ต่อสู้ ตัวเลขลอยและ waypoint โดยไม่ปิด UIRoot, กล้อง UI หรือ Canvas ทั้งฉาก จึงเหลือหน้าตัวละคร หน้ารายละเอียด EX Eidolon และหน้าต่างรางวัลไว้ ปิดโหมดแล้วคืนค่า CanvasGroup เดิมของ HUD ที่แก้ ถ้าเคยใช้สคริปต์ซ่อนทั้ง UI รุ่นเก่า แนะนำเปิดเกมใหม่หนึ่งครั้ง เพราะรุ่นเก่าไม่ได้เก็บค่า alpha/interactable เดิมทั้งหมดไว้ ผลหน้า EX ยังรอยืนยันจาก Client + +Hide UI ซ่อนเมนู รายละเอียด รายชื่อตัวละคร และปุ่มปิด/แถบบนที่ตรวจพบในหน้า Character เพิ่มด้วย โดยเว้น AvatarRankPanel และ Dialog ไว้เพื่อรักษาภาพ Eidolon; ส่วนควบคุมภายในหน้า Rank ยังไม่ได้แยกซ่อนและรอตรวจ Client diff --git a/scripts/freecam.lua b/scripts/freecam.lua index bd2c06c..e3a71b9 100644 --- a/scripts/freecam.lua +++ b/scripts/freecam.lua @@ -5,8 +5,6 @@ local Time = CS.UnityEngine.Time local Camera = CS.UnityEngine.Camera local GameObject = CS.UnityEngine.GameObject --- Camera control tuning, in degrees per second (independent of frame rate). -local KEYBOARD_TURN_SPEED = 90.0 _G.__FREECAM_ENABLED = _G.__FREECAM_ENABLED or false _G.__FREECAM_SPEED = _G.__FREECAM_SPEED or 15.0 @@ -72,6 +70,88 @@ local function set_cinemachine_enabled(cam, enabled) end end +-- Client inspection: InControlInputModule.allowMouseInput controls UI +-- clicks. Battle shortcuts also read MouseBindingSource independently. +-- Mask only mouse button controls in memory; leave keyboard bindings intact. +-- Do not use RemoveBinding/AddBinding (which emit OnBindingsChanged). +_G.__FC_MOUSE_GATE = _G.__FC_MOUSE_GATE or {modules={}, bindings={}} +local function restore_mouse_gate() + local gate = _G.__FC_MOUSE_GATE + for _, state in ipairs(gate.modules) do + pcall(function() state.module.allowMouseInput = state.allowed end) + end + for _, state in ipairs(gate.bindings) do + pcall(function() + if state.binding.Control == CS.InControl.Mouse.None then + gate.controlField:SetValue(state.binding, state.control) + state.action:ClearInputState() + end + end) + end + gate.modules, gate.bindings, gate.sets = {}, {}, {} + gate.active = false +end + +local function update_mouse_gate() + local gate = _G.__FC_MOUSE_GATE + local battleHUD = GameObject.Find("UIRoot/Page/BattleGamePhaseUI(Clone)") + local wanted = _G.__FREECAM_ENABLED and battleHUD ~= nil + -- Releasing Freecam while holding RMB must not become a battle click. + if not wanted then + if gate.active and not Input.GetMouseButton(0) and not Input.GetMouseButton(1) then + restore_mouse_gate() + end + return + end + gate.active = true + if not gate.controlField then + local flags = CS.System.Enum.ToObject(typeof(CS.System.Reflection.BindingFlags), 60) + gate.controlField = typeof(CS.InControl.MouseBindingSource):GetField("k__BackingField", flags) + gate.setsField = typeof(CS.InControl.InputManager):GetField("playerActionSets", flags) + assert(gate.controlField and gate.setsField, "Client mouse input schema is unavailable") + end + local eventSystem = CS.UnityEngine.EventSystems.EventSystem.current + if eventSystem then + local module = eventSystem:GetComponent(typeof(CS.InControl.InControlInputModule)) + if module then + local recorded = false + for _, state in ipairs(gate.modules) do + if state.module == module then recorded = true; break end + end + if not recorded then + gate.modules[#gate.modules + 1] = {module=module, allowed=module.allowMouseInput} + end + module.allowMouseInput = false + end + end + gate.sets = gate.sets or {} + local sets = gate.setsField:GetValue(nil) + -- Scan each newly created action set once, rather than reflecting/scanning + -- the assembly or all bindings on every render callback. + for i = 0, sets.Count - 1 do + local actionSet = sets[i] + if not gate.sets[actionSet] then + gate.sets[actionSet] = true + local actions = actionSet.Actions + for j = 0, actions.Count - 1 do + local action = actions[j] + local bindings = action.Bindings + for k = 0, bindings.Count - 1 do + local binding = bindings[k] + if typeof(CS.InControl.MouseBindingSource):IsInstanceOfType(binding) then + local control = binding.Control + if control == CS.InControl.Mouse.LeftButton or control == CS.InControl.Mouse.RightButton then + gate.bindings[#gate.bindings + 1] = {binding=binding, control=control, action=action} + gate.controlField:SetValue(binding, CS.InControl.Mouse.None) + action:ClearInputState() + end + end + end + end + end + end +end + function _G.__FREECAM_TICK() pcall(function() -- Render callbacks run for several cameras/passes in one frame. Consume @@ -98,7 +178,7 @@ function _G.__FREECAM_TICK() _G.__FC_EULER = cam.transform.eulerAngles set_cinemachine_enabled(cam, false) end - show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | I/J/K/L: Look") + show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look") else if cam then set_cinemachine_enabled(cam, true) @@ -107,6 +187,12 @@ function _G.__FREECAM_TICK() end end + local gateOK, gateError = pcall(update_mouse_gate) + if not gateOK and not _G.__FC_GATE_ERROR then + _G.__FC_GATE_ERROR = true + CS.UnityEngine.Debug.Log("[FreeCam] Mouse gate error: " .. tostring(gateError)) + end + if _G.__FREECAM_ENABLED then local cam = get_active_cam() if cam then @@ -125,23 +211,21 @@ function _G.__FREECAM_TICK() -- Speed adjustment (+ / -) if Input.GetKeyDown(KeyCode.Equals) or Input.GetKeyDown(KeyCode.KeypadPlus) then _G.__FREECAM_SPEED = math.min(300.0, _G.__FREECAM_SPEED * 1.5) - show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | I/J/K/L: Look") + show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look") end if Input.GetKeyDown(KeyCode.Minus) or Input.GetKeyDown(KeyCode.KeypadMinus) then _G.__FREECAM_SPEED = math.max(0.5, _G.__FREECAM_SPEED / 1.5) - show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | I/J/K/L: Look") + show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look") end - -- Keyboard-only look: no mouse click is needed, so turning - -- the camera does not require pressing a battle action button. - local pitch, yaw = 0, 0 - if Input.GetKey(KeyCode.I) then pitch = pitch - 1 end - if Input.GetKey(KeyCode.K) then pitch = pitch + 1 end - if Input.GetKey(KeyCode.J) then yaw = yaw - 1 end - if Input.GetKey(KeyCode.L) then yaw = yaw + 1 end - _G.__FC_EULER = Vector3( - _G.__FC_EULER.x + pitch * KEYBOARD_TURN_SPEED * dt, - _G.__FC_EULER.y + yaw * KEYBOARD_TURN_SPEED * dt, 0) + -- Raw Unity mouse input remains available to the camera while + -- InControl battle clicks are gated separately below. + if Input.GetMouseButton(1) then + local rotX = Input.GetAxis("Mouse X") or 0 + local rotY = Input.GetAxis("Mouse Y") or 0 + _G.__FC_EULER = Vector3(_G.__FC_EULER.x - rotY * 2.5, + _G.__FC_EULER.y + rotX * 2.5, 0) + end -- Compute 3D direction vectors from camera Euler angles local radY = _G.__FC_EULER.y * (math.pi / 180.0) @@ -204,3 +288,11 @@ end if not _G.__FREECAM_ENABLED then show_ui(RAINBOW_TEXT) end + +-- Restore the in-memory controls before the Client tears down its settings. +if not _G.__FC_MOUSE_QUIT_REGISTERED then + local ok = pcall(function() + CS.UnityEngine.Application.quitting('+', function() restore_mouse_gate() end) + end) + _G.__FC_MOUSE_QUIT_REGISTERED = ok +end diff --git a/scripts/hide_ui.lua b/scripts/hide_ui.lua index ccd9d2b..b09ede2 100644 --- a/scripts/hide_ui.lua +++ b/scripts/hide_ui.lua @@ -15,6 +15,14 @@ local HUD_PATHS = { "UIRoot/BelowPage/MazeJumpingText(Clone)", "UIRoot/BelowPage/MazeMonsterWarningPage(Clone)", "UIRoot/AboveDialog/BetaHintDialog(Clone)", + -- Character controls observed under AvatarMainPage/Root. Keep RankPanel + -- and dialogs intact so Eidolon illustrations are not hidden with menus. + "UIRoot/Page/AvatarMainPage(Clone)/Root/AvatarSubMenuPanel_Vertical", + "UIRoot/Page/AvatarMainPage(Clone)/Root/AvatarDetailPanel", + "UIRoot/Page/AvatarMainPage(Clone)/Root/AvatarTopListPanel", + "UIRoot/Page/AvatarMainPage(Clone)/Root/AvatarListPanel", + "UIRoot/Page/AvatarMainPage(Clone)/Root/BtnClose", + "UIRoot/Page/AvatarMainPage(Clone)/Root/TopBarArea", "VersionText" }