local Input = CS.UnityEngine.Input local KeyCode = CS.UnityEngine.KeyCode local Vector3 = CS.UnityEngine.Vector3 local Time = CS.UnityEngine.Time local Camera = CS.UnityEngine.Camera local GameObject = CS.UnityEngine.GameObject _G.__FREECAM_ENABLED = _G.__FREECAM_ENABLED or false _G.__FREECAM_SPEED = _G.__FREECAM_SPEED or 15.0 local RAINBOW_TEXT = "นี่คือเวอร์ชั่นทดสอบของเกย์ Honkai Star Gay" local function show_ui(msg) pcall(function() local obj = GameObject.Find("VersionText") if obj then local textComp = obj:GetComponentInChildren(typeof(CS.RPG.Client.LocalizedText)) if textComp then textComp.text = msg end end end) end local function get_active_cam() local cam = Camera.main if cam and cam.gameObject.activeInHierarchy then return cam end local all = Camera.allCameras if all and all.Length > 0 then for i = 0, all.Length - 1 do local c = all[i] if c and c.gameObject.activeInHierarchy and c.name ~= "UICamera" and not string.find(c.name, "UI") then return c end end return all[0] end return nil end -- Detach only the active camera brain and restore its recorded state. Virtual -- cameras continue updating animation/effect state; do not scan/disable every -- virtual camera on each render frame. _G.__FC_BRAINS = _G.__FC_BRAINS or {} local function set_cinemachine_enabled(cam, enabled) if enabled then for id, state in pairs(_G.__FC_BRAINS) do pcall(function() if state.brain then state.brain.enabled = state.enabled end end) _G.__FC_BRAINS[id] = nil end return end pcall(function() if not cam then return end local brain = cam:GetComponent(typeof(CS.Cinemachine.CinemachineBrain)) if not brain then return end local id = brain:GetInstanceID() if not _G.__FC_BRAINS[id] then _G.__FC_BRAINS[id] = {brain=brain, enabled=brain.enabled} end brain.enabled = false 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 if not gate.phaseField then local flags = CS.System.Enum.ToObject(typeof(CS.System.Reflection.BindingFlags), 60) gate.phaseField = typeof(CS.RPG.Client.GamePhaseManager):GetField("_CurrentPhase", flags) end local manager = CS.RPG.Client.GlobalVars.s_GamePhaseManager local phase = manager and gate.phaseField:GetValue(manager) local inBattle = phase and typeof(CS.RPG.Client.BattleGamePhase):IsInstanceOfType(phase) local wanted = _G.__FREECAM_ENABLED and inBattle -- 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 -- key-down events and integrate movement only once per Unity frame. local frame = Time.frameCount if _G.__FC_INPUT_FRAME == frame then if _G.__FREECAM_ENABLED then local cam = get_active_cam() if cam and _G.__FC_POS then cam.transform.position = _G.__FC_POS cam.transform.eulerAngles = _G.__FC_EULER end end return end _G.__FC_INPUT_FRAME = frame if Input.GetKeyDown(KeyCode.Tab) then _G.__FREECAM_ENABLED = not _G.__FREECAM_ENABLED _G.__FC_LAST_TIME = nil local cam = get_active_cam() if _G.__FREECAM_ENABLED then if cam then _G.__FC_POS = cam.transform.position _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 | RMB: Look") else if cam then set_cinemachine_enabled(cam, true) end show_ui(RAINBOW_TEXT) 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 set_cinemachine_enabled(cam, false) if not _G.__FC_POS then _G.__FC_POS = cam.transform.position _G.__FC_EULER = cam.transform.eulerAngles end local now = Time.unscaledTime or 0 local dt = now - (_G.__FC_LAST_TIME or now) if dt <= 0 or dt > 0.1 then dt = 0.016 end _G.__FC_LAST_TIME = now -- 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 | 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 | RMB: Look") end -- 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) local radX = _G.__FC_EULER.x * (math.pi / 180.0) local cosX = math.cos(radX) local fwd = Vector3(math.sin(radY) * cosX, -math.sin(radX), math.cos(radY) * cosX) local right = Vector3(math.cos(radY), 0, -math.sin(radY)) local move = Vector3(0, 0, 0) -- Continuous flying via Arrow Keys if Input.GetKey(KeyCode.UpArrow) then move = move + fwd end if Input.GetKey(KeyCode.DownArrow) then move = move - fwd end if Input.GetKey(KeyCode.RightArrow) then move = move + right end if Input.GetKey(KeyCode.LeftArrow) then move = move - right end if Input.GetKey(KeyCode.PageUp) then move = move + Vector3(0, 1, 0) end if Input.GetKey(KeyCode.PageDown) then move = move - Vector3(0, 1, 0) end if move.sqrMagnitude > 0.0001 then _G.__FC_POS = _G.__FC_POS + move.normalized * (_G.__FREECAM_SPEED * dt) end -- Enforce locked free camera transform EVERY frame cam.transform.position = _G.__FC_POS cam.transform.eulerAngles = _G.__FC_EULER end end end) end -- Multiple hooks for complete Unity & xLua render pipeline coverage if not _G.__FREECAM_REGISTERED then _G.__FREECAM_REGISTERED = true local function hook(target, evt) pcall(function() target[evt]('+', function(c) _G.__FREECAM_TICK() end) end) pcall(function() target[evt] = target[evt] + function(c) _G.__FREECAM_TICK() end end) end hook(Camera, "onPreRender") hook(Camera, "onPreCull") hook(Camera, "onPostRender") pcall(function() CS.UnityEngine.Application.onBeforeRender('+', function() _G.__FREECAM_TICK() end) end) pcall(function() CS.UnityEngine.Application.onBeforeRender = CS.UnityEngine.Application.onBeforeRender + function() _G.__FREECAM_TICK() end end) 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