game: disable keyboard input by default, give users a way to enable it via the imgui menu (#3295)

It was narrowed down recently that a lot of people have issues with the
controller input because of Steam Input working as intended. Steam Input
can be configured to replicate controller inputs as keyboard inputs (for
example, pressing X on your controller presses Enter on the keyboard).

This results in the problem of "jumping pauses the game" and similar
issues. This is a consequence of the intended behaviour of the game
listening to all input sources at the same time.

Since the vast majority of players are using controllers over keyboards,
it makes sense to disable the keyboard input by default to solve this
problem. However that makes things awkward for users that want to use
the keyboard (how do they enable the setting). The solution is a new
imgui option in the settings menu:
![Screenshot 2024-01-07
141224](https://github.com/open-goal/jak-project/assets/13153231/6f9ffa2d-be7a-433d-b698-15b70210e97e)

**Known issue that I don't care about** -- in Jak 1's menu code, since
the flags are controlled by pointers to values instead of a lambda like
in jak 2, the menu won't update live with the imgui option. This has no
functional impact and I don't care enough to fix it.

I also made the pc-settings.gc file persist on first load if the file
wasn't found. Hopefully this helps diagnose the support issues related
to the black screen.

# Why not just ignore the keyboard inputs for a period of time?

This won't work, the keyboard is polled every frame. Therefore if you
hold down the X button on your controller, steam is continuously
signaling that `Enter` is held down on the keyboard.

Yes it would be possible to completely disable the keyboard while the
controller is being used, but this defeats the purpose of creating an
input system that allows multiple input sources at the same time.

With an explicit option, not only can the user decide the behaviour they
want (do they want the keyboard ignored or simultaneously listened to)
but we avoid breaking strange edge-cases in usage leading to never
ending complexity:
- ie. imagine steam input sends events to the mouse, well you can't
disable the mouse while using the keyboard because most times people are
using mouse and keyboard
- ie. a user that wants to hold a direction with the keyboard and press
buttons on the controller in tandem (something i frequently do while
TAS'ing, to move in a perfect straight line)
This commit is contained in:
Tyler Wilding
2024-02-23 18:19:07 -05:00
committed by GitHub
parent d5f92efcda
commit d1a6c60eb8
15 changed files with 75 additions and 52 deletions
@@ -5,6 +5,7 @@
#include "common/global_profiler/GlobalProfiler.h"
#include "game/graphics/display.h"
#include "game/graphics/gfx.h"
#include "game/system/hid/sdl_util.h"
@@ -151,6 +152,12 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) {
ImGui::TreePop();
}
ImGui::Checkbox("Treat Pad0 as Pad1", &Gfx::g_debug_settings.treat_pad0_as_pad1);
auto is_keyboard_enabled =
Display::GetMainDisplay()->get_input_manager()->is_keyboard_enabled();
if (ImGui::Checkbox("Enable Keyboard (forced on if no controllers detected)",
&is_keyboard_enabled)) {
Display::GetMainDisplay()->get_input_manager()->enable_keyboard(is_keyboard_enabled);
}
ImGui::EndMenu();
}
+8
View File
@@ -676,6 +676,13 @@ void pc_set_controller(u32 controller_id, u32 port) {
}
}
u32 pc_get_keyboard_enabled() {
if (Display::GetMainDisplay()) {
return bool_to_symbol(Display::GetMainDisplay()->get_input_manager()->is_keyboard_enabled());
}
return bool_to_symbol(false);
}
void pc_set_keyboard_enabled(u32 sym_val) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->enable_keyboard(symbol_to_bool(sym_val));
@@ -921,6 +928,7 @@ void init_common_pc_port_functions(
make_func_symbol_func("pc-get-controller-count", (void*)pc_get_controller_count);
make_func_symbol_func("pc-get-controller-index", (void*)pc_get_controller_index);
make_func_symbol_func("pc-set-controller!", (void*)pc_set_controller);
make_func_symbol_func("pc-get-keyboard-enabled?", (void*)pc_get_keyboard_enabled);
make_func_symbol_func("pc-set-keyboard-enabled!", (void*)pc_set_keyboard_enabled);
make_func_symbol_func("pc-set-mouse-options!", (void*)pc_set_mouse_options);
make_func_symbol_func("pc-set-mouse-camera-sens!", (void*)pc_set_mouse_camera_sens);
+2
View File
@@ -3,6 +3,8 @@
namespace jak2 {
#define VOICE_BIT(voice) (1 << ((voice) >> 1))
// Our CI is on an ancient version of clang-format, manually format this
// so we don't have to worry about the version locally
// clang-format off
#define CORE_BIT(voice) ((voice)&1)
// clang-format on
+12 -9
View File
@@ -59,9 +59,10 @@ void DebugSettings::save_settings() {
}
void to_json(json& j, const DisplaySettings& obj) {
j = json{{"display_id", obj.display_id},
{"window_xpos", obj.window_xpos},
{"window_ypos", obj.window_ypos}};
json_serialize(version);
json_serialize(display_id);
json_serialize(window_xpos);
json_serialize(window_ypos);
}
void from_json(const json& j, DisplaySettings& obj) {
json_deserialize_if_exists(version);
@@ -94,12 +95,13 @@ void DisplaySettings::save_settings() {
}
void to_json(json& j, const InputSettings& obj) {
j = json{{"version", obj.version},
{"last_selected_controller_guid", obj.last_selected_controller_guid},
{"controller_port_mapping", obj.controller_port_mapping},
{"controller_binds", obj.controller_binds},
{"keyboard_binds", obj.keyboard_binds},
{"mouse_binds", obj.mouse_binds}};
json_serialize(version);
json_serialize(last_selected_controller_guid);
json_serialize(controller_port_mapping);
json_serialize(controller_binds);
json_serialize(keyboard_binds);
json_serialize(mouse_binds);
json_serialize(keyboard_enabled);
}
void from_json(const json& j, InputSettings& obj) {
@@ -109,6 +111,7 @@ void from_json(const json& j, InputSettings& obj) {
json_deserialize_if_exists(controller_binds);
json_deserialize_if_exists(keyboard_binds);
json_deserialize_if_exists(mouse_binds);
json_deserialize_if_exists(keyboard_enabled);
}
InputSettings::InputSettings() {
+3
View File
@@ -56,6 +56,9 @@ struct InputSettings {
std::unordered_map<std::string, InputBindingGroups> controller_binds;
InputBindingGroups keyboard_binds;
InputBindingGroups mouse_binds;
bool keyboard_enabled = false;
bool keyboard_temp_enabled =
false; // not saved or restored, flips on if no controllers are detected
void save_settings();
};
+6 -4
View File
@@ -128,8 +128,10 @@ void InputManager::refresh_device_list() {
lg::warn(
"No active game controllers could be found or loaded successfully - inputs will not "
"work!");
m_settings->keyboard_temp_enabled = true;
} else {
lg::info("Found {} controllers", m_available_controllers.size());
m_settings->keyboard_temp_enabled = false;
}
}
}
@@ -203,7 +205,7 @@ void InputManager::process_sdl_event(const SDL_Event& event) {
}
void InputManager::poll_keyboard_data() {
if (m_keyboard_enabled && m_skip_polling_for_n_frames <= 0 && !m_waiting_for_bind) {
if (is_keyboard_enabled() && m_skip_polling_for_n_frames <= 0 && !m_waiting_for_bind) {
if (m_data.find(m_keyboard_and_mouse_port) != m_data.end()) {
m_keyboard.poll_state(m_data.at(m_keyboard_and_mouse_port));
}
@@ -211,7 +213,7 @@ void InputManager::poll_keyboard_data() {
}
void InputManager::clear_keyboard_actions() {
if (m_keyboard_enabled) {
if (is_keyboard_enabled()) {
if (m_data.find(m_keyboard_and_mouse_port) != m_data.end()) {
m_keyboard.clear_actions(m_data.at(m_keyboard_and_mouse_port));
}
@@ -427,8 +429,8 @@ int InputManager::update_rumble(int port, u8 low_intensity, u8 high_intensity) {
}
void InputManager::enable_keyboard(const bool enabled) {
m_keyboard_enabled = enabled;
if (!m_keyboard_enabled) {
m_settings->keyboard_enabled = enabled;
if (!m_settings->keyboard_enabled) {
// Reset inputs as this device won't be able to be read from again!
clear_inputs();
}
+3 -1
View File
@@ -101,6 +101,9 @@ class InputManager {
void reset_input_bindings_to_defaults(const int port, const InputDeviceType device_type);
bool auto_hiding_cursor() { return m_auto_hide_mouse || m_mouse.is_camera_being_controlled(); }
void hide_cursor(const bool hide_cursor);
bool is_keyboard_enabled() {
return m_settings->keyboard_enabled || m_settings->keyboard_temp_enabled;
}
private:
std::mutex m_event_queue_mtx;
@@ -128,7 +131,6 @@ class InputManager {
/// Collection of arbitrary commands to run on user actions
CommandBindingGroups m_command_binds;
bool m_keyboard_enabled = true;
bool m_mouse_enabled = false;
int m_skip_polling_for_n_frames = 0;
bool m_auto_hide_mouse = true;
+1
View File
@@ -346,6 +346,7 @@
(define-extern pc-set-waiting-for-bind! (function int symbol symbol int none))
(define-extern pc-stop-waiting-for-bind! (function none))
(define-extern pc-set-controller! (function int int none))
(define-extern pc-get-keyboard-enabled? (function symbol))
(define-extern pc-set-keyboard-enabled! (function symbol none))
(define-extern pc-set-mouse-options! (function symbol symbol symbol none))
(define-extern pc-set-mouse-camera-sens! (function float float none))
+4 -8
View File
@@ -375,12 +375,6 @@
(pc-ignore-background-controller-events! val)
(none))
(defmethod set-enable-keyboard! pc-settings ((obj pc-settings) (val symbol))
"sets whether to ignore keyboard input events"
(set! (-> obj keyboard-enabled?) val)
(pc-set-keyboard-enabled! val)
(none))
(defmethod update-mouse-controls! pc-settings ((obj pc-settings))
"Uses whatever is set on the [[pc-settings]] to update the runtime on how it should interpret mouse events"
(pc-set-mouse-options! (-> obj mouse-enabled?) (-> obj mouse-camera?) (-> obj mouse-movement?))
@@ -668,7 +662,9 @@
(("controller-eco-led?") (set! (-> obj controller-led-eco?) (file-stream-read-symbol file)))
(("controller-heat-led?") (set! (-> obj controller-led-heat?) (file-stream-read-symbol file)))
(("stick-deadzone") (set! (-> obj stick-deadzone) (file-stream-read-float file)))
(("keyboard-enabled?") (set-enable-keyboard! obj (file-stream-read-symbol file)))
;; TODO - remove this eventually, setting moved into json input-settings
;; has to stay here or the settings parsing code explodes
(("keyboard-enabled?") (file-stream-read-symbol file))
(("mouse-enabled?") (set! (-> obj mouse-enabled?) (file-stream-read-symbol file)))
(("mouse-camera?") (set! (-> obj mouse-camera?) (file-stream-read-symbol file)))
(("mouse-xsens") (set! (-> obj mouse-xsens) (file-stream-read-float file)))
@@ -754,7 +750,6 @@
(format file " (controller-led-min-brightness ~f)~%" (-> obj controller-led-min-brightness))
(format file " (controller-led-max-brightness ~f)~%" (-> obj controller-led-max-brightness))
(format file " (stick-deadzone ~f)~%" (-> obj stick-deadzone))
(format file " (keyboard-enabled? ~A)~%" (-> obj keyboard-enabled?))
(format file " (mouse-enabled? ~A)~%" (-> obj mouse-enabled?))
(format file " (mouse-camera? ~A)~%" (-> obj mouse-camera?))
(format file " (mouse-xsens ~f)~%" (-> obj mouse-xsens))
@@ -834,6 +829,7 @@
;; if saved settings are corrupted or not found, the object will be fully reset to use all defaults
(load-settings obj)
;; any post-operations that need to be done after loading
(set! (-> obj keyboard-enabled?) (pc-get-keyboard-enabled?))
(update-mouse-controls! obj)
obj)
+3 -4
View File
@@ -152,7 +152,7 @@
(controller-led-max-brightness float)
(controller-led-color rgbaf :inline)
(stick-deadzone float) ;; analog stick deadzone. 0-1
(keyboard-enabled? symbol)
(keyboard-enabled? symbol) ;; NOTE - this is here solely to satisfy jak 1's menu design, the setting has been moved into `input-settings.json`
(mouse-enabled? symbol)
(mouse-camera? symbol)
(mouse-xsens float)
@@ -258,7 +258,6 @@
(commit-to-file (_type_) none)
(load-settings (_type_) int)
(set-ignore-controller-in-bg! (_type_ symbol) none)
(set-enable-keyboard! (_type_ symbol) none)
(update-mouse-controls! (_type_) none)
(debug-font-scale-factor (_type_) float)
)
@@ -389,9 +388,9 @@
(set! (-> obj controller-led-min-brightness) 0.25)
(set! (-> obj controller-led-max-brightness) 1.0)
)
(when (or (= device 'all) (= device 'input))
(set! (-> obj keyboard-enabled?) #t)
(set! (-> obj keyboard-enabled?) #f)
(set! (-> obj mouse-enabled?) #f)
(set! (-> obj auto-hide-cursor?) #t)
)
+2 -2
View File
@@ -43,7 +43,7 @@
(big-head-npc)
(oh-my-goodness)
)
;; pc enum for languages. this is the game's languages + custom ones.
(defenum pc-language
:type uint16
@@ -184,7 +184,7 @@
(set! (-> obj cheats) (pc-cheats))
(set! (-> obj cheats-known) (pc-cheats))
(dotimes (i PC_SPOOL_LOG_LENGTH)
(set! (-> obj scenes-seen i) 0)
)
+4 -3
View File
@@ -245,7 +245,7 @@
(new 'static 'game-option :option-type (game-option-type menu) :name (text-id input-opts-binds-controller) :scale #t :param3 (game-option-menu controller-binds)
:option-disabled-func (lambda () (<= (pc-get-controller-count) 0)))
(new 'static 'game-option :option-type (game-option-type menu) :name (text-id input-opts-binds-keyboard) :scale #t :param3 (game-option-menu keyboard-binds)
:option-disabled-func (lambda () (not (-> *pc-settings* keyboard-enabled?))))
:option-disabled-func (lambda () (not (pc-get-keyboard-enabled?))))
(new 'static 'game-option :option-type (game-option-type menu) :name (text-id input-opts-binds-mouse) :scale #t :param3 (game-option-menu mouse-binds)
:option-disabled-func (lambda () (not (-> *pc-settings* mouse-enabled?))))
(new 'static 'game-option :option-type (game-option-type button) :name (text-id back) :scale #t)))
@@ -257,7 +257,8 @@
(new 'static 'game-option :option-type (game-option-type on-off) :name (text-id input-opts-enable-kb) :scale #t
:on-change (lambda ((val object))
(let ((val-sym (the-as symbol val)))
(set-enable-keyboard! *pc-settings* val-sym)
(pc-set-keyboard-enabled! val-sym)
(set! (-> *pc-settings* keyboard-enabled?) val-sym)
(none))))
(new 'static 'game-option :option-type (game-option-type on-off) :name (text-id input-opts-enable-mouse) :scale #t
:on-change (lambda ((val object))
@@ -277,7 +278,7 @@
(new 'static 'game-option :option-type (game-option-type confirmation) :name (text-id restore-defaults) :scale #t
:on-confirm (lambda ()
(reset-input *pc-settings* 'input #t)
(set-enable-keyboard! *pc-settings* (-> *pc-settings* keyboard-enabled?))
(pc-set-keyboard-enabled! (-> *pc-settings* keyboard-enabled?))
(update-mouse-controls! *pc-settings*)
(none)))
(new 'static 'game-option :option-type (game-option-type button) :name (text-id back) :scale #t)))
+1
View File
@@ -183,6 +183,7 @@
(define-extern pc-stop-waiting-for-bind! (function none))
(define-extern pc-get-controller-index (function int int))
(define-extern pc-set-controller! (function int int none))
(define-extern pc-get-keyboard-enabled? (function symbol))
(define-extern pc-set-keyboard-enabled! (function symbol none))
(define-extern pc-set-mouse-options! (function symbol symbol symbol none))
(define-extern pc-set-mouse-camera-sens! (function float float none))
+15 -15
View File
@@ -60,7 +60,7 @@
(when (= (get-base-height *ocean-map*) -216498.17)
(set! (-> *pc-cheat-state* sewer-valve-start-time) (get-current-time))
)
;; no cheating!
(cond
((task-node-open? (game-task-node sewer-board-drain))
@@ -85,7 +85,7 @@
(else
(let (
(time-taken (- (-> *pc-cheat-state* sewer-valve-end-time) (-> *pc-cheat-state* sewer-valve-start-time))))
(and (< 0 time-taken)
(< time-taken (seconds (-> *pc-cheat-state* sewer-valve-target-seconds)))
(= #b111 (-> *pc-cheat-state* sewer-valve-on)))
@@ -236,7 +236,7 @@
(defmethod enable ((this led-fader-state) (start-from vector))
"begin transition."
(when (-> this enable?)
(disable this))
@@ -247,7 +247,7 @@
(defmethod disable ((this led-fader-state))
"disable transition."
(set! (-> this amount) 0.0)
(update this 0.0 0.1)
(false! (-> this enable?))
@@ -291,7 +291,7 @@
(defmethod update ((this led-fader-state) (to float) (duration float))
"disable transition."
(when (-> this enable?)
(seek! (-> this amount) to (/ (-> *target* clock seconds-per-frame) duration))
(vector4-lerp! (-> this cur-color) (-> this start-color) (-> this end-color) (-> this amount))
@@ -363,7 +363,7 @@
"Set the default misc settings"
((method-of-type pc-settings update) obj)
(set! *hires-sky* (-> obj hires-clouds?))
(when (not (led-enabled? obj))
@@ -448,7 +448,7 @@
(defmethod update-led ((obj pc-settings-jak2))
"set the controller led color by modifying the controller-led-color vector"
;; default color is just blue.
(set-vector-xyz! (-> obj controller-led-color) 0.0 0.0 1.0)
@@ -533,7 +533,7 @@
(set-vector-xyz! (-> obj controller-led-color) 0.6875 0.6 0.78125))
)
)
;; darkjak
((and (nonzero? (-> *target* darkjak)) (focus-test? *target* dark))
(vector-copy! (-> *led-fader-state* end-color) *led-darkjak-color*)
@@ -545,23 +545,23 @@
(update *led-fader-state* 1.0 0.3))
(vector3-copy!! (-> obj controller-led-color) (-> *led-fader-state* cur-color))
)
;; indax
((focus-test? *target* indax)
(set-vector-xyz! (-> obj controller-led-color) 1.0 0.5 0.0)
)
;; mech
((focus-test? *target* mech)
(set-vector-xyz! (-> obj controller-led-color) 1.0 1.0 0.0)
)
;; board
((focus-test? *target* board)
(set-vector-xyz! (-> obj controller-led-color) 0.0 1.0 1.0)
)
)
;; wanted flash
(awhen (the hud-map (process-by-name "hud-map" *active-pool*))
(when (not (hidden? it))
@@ -641,7 +641,7 @@
;; check unlocked cheats
;;;;;;;;;;;;;;;;;;;;;;;;
(dotimes (i (-> *pc-cheats-list* length))
;; reveals cheats if they have been purchased, purchases cheats if they have been unlocked, unlocks cheats if they have been enabled.
;; the cheat process requires the steps to be filled in this order, see sequential checking below
(logior! (-> *pc-settings* cheats-revealed) (logior! (-> *pc-settings* cheats-purchased) (logior! (-> *pc-settings* cheats-unlocked) (-> *pc-settings* cheats))))
@@ -649,7 +649,7 @@
(let* ((cheat (-> *pc-cheats-list* i))
(cost (-> cheat skill))
(unlock-func (the (function symbol) (-> cheat unlock-func value))))
(when (if (logtest? (-> *game-info* secrets) (game-secrets hero-mode))
(task-node-closed? (-> cheat avail-after-hero))
(task-node-closed? (-> cheat avail-after)))
@@ -657,7 +657,7 @@
(when (>= (-> *game-info* skill-total) cost)
(logior! (-> obj cheats-purchased) (-> cheat flag))
(when (or (zero? unlock-func)
(not unlock-func)
(unlock-func))
@@ -188,10 +188,8 @@ This gives us more freedom to write code how we want.
:name (text-id progress-input-options-enable-keyboard)
:truthy-text (text-id progress-on)
:falsey-text (text-id progress-off)
:get-value-fn (lambda () (-> *pc-settings* keyboard-enabled?))
:on-confirm (lambda ((val symbol))
(set! (-> *pc-settings* keyboard-enabled?) val)
(pc-settings-save)))
:get-value-fn (lambda () (pc-get-keyboard-enabled?))
:on-confirm (lambda ((val symbol)) (pc-set-keyboard-enabled! val)))
(new 'static 'menu-generic-boolean-option
:name (text-id progress-input-options-enable-mouse)
:truthy-text (text-id progress-on)
@@ -265,7 +263,7 @@ This gives us more freedom to write code how we want.
:entries (select l3 r3 start dpad-up dpad-right dpad-down dpad-left l2 r2 l1 r1 triangle circle cross square)
:confirm ((text-id progress-restore-defaults) (lambda () (pc-reset-bindings-to-defaults! 0 0))))
(progress-new-generic-link-to-keybind-details-page (text-id progress-reassign-binds-keyboard)
:should-disable? (lambda () (not (-> *pc-settings* keyboard-enabled?)))
:should-disable? (lambda () (not (pc-get-keyboard-enabled?)))
:device-type keyboard
:entries (l-analog-up l-analog-down l-analog-left l-analog-right r-analog-up r-analog-down r-analog-left r-analog-left r-analog-right
select l3 r3 start dpad-up dpad-right dpad-down dpad-left l2 r2 l1 r1 triangle circle cross square)
@@ -279,7 +277,7 @@ This gives us more freedom to write code how we want.
:name (text-id progress-restore-defaults)
:on-confirm (lambda ((val symbol))
(reset-input *pc-settings* 'input #t)
(set-enable-keyboard! *pc-settings* (-> *pc-settings* keyboard-enabled?))
(pc-set-keyboard-enabled! (-> *pc-settings* keyboard-enabled?))
(update-mouse-controls! *pc-settings*)
(pc-settings-save)))))