From b57e1c1e93afd0af4a7de9c673a49b792993be89 Mon Sep 17 00:00:00 2001
From: kevinz000 <2003111+kevinz000@users.noreply.github.com>
Date: Wed, 20 May 2020 14:40:20 -0700
Subject: [PATCH] Rebindable Hotkeys (#12138)
* demodularize interface
* keybindings
* binds
* prefs
* loose ends
* globals
* changes
* s
* datum ref lists
* ok
* fixes
* fixes
* fix
* ok
* sigh
* sigh
* indicators
* let's play the move code around game
* let's play the i didn't comma my lists game
* let's play the indent game
* let's play hte spelling bee
* let's fail the spelling bee
* LET'S PLAY THe HOW HARd IS IT TO SPELL A PROC GAME
* let's play the bugfix game
* bugfixes
* improvements
* Update bindings_client.dm
* pixel shift
* A
* wups
---
code/__DEFINES/admin.dm | 7 -
code/__DEFINES/admin/keybindings.dm | 18 +
code/__HELPERS/global_lists.dm | 2 +
code/__HELPERS/sanitize_values.dm | 6 +
code/_globalvars/lists/client.dm | 21 ++
code/_globalvars/lists/keybindings.dm | 31 ++
code/controllers/subsystem/input.dm | 158 ++++-----
code/modules/admin/secrets.dm | 59 ----
code/modules/client/client_defines.dm | 14 +
code/modules/client/client_procs.dm | 23 +-
code/modules/client/preferences.dm | 179 +++++++++-
code/modules/client/preferences_savefile.dm | 16 +-
code/modules/keybindings/bindings_admin.dm | 24 --
code/modules/keybindings/bindings_atom.dm | 2 +-
code/modules/keybindings/bindings_carbon.dm | 9 -
code/modules/keybindings/bindings_client.dm | 70 ++--
code/modules/keybindings/bindings_human.dm | 80 -----
code/modules/keybindings/bindings_living.dm | 26 --
code/modules/keybindings/bindings_mob.dm | 94 ------
code/modules/keybindings/bindings_robot.dm | 22 --
code/modules/keybindings/focus.dm | 3 -
code/modules/keybindings/keybind/__defines.dm | 20 ++
code/modules/keybindings/keybind/_keybind.dm | 24 ++
code/modules/keybindings/keybind/admin.dm | 96 ++++++
code/modules/keybindings/keybind/carbon.dm | 62 ++++
code/modules/keybindings/keybind/client.dm | 33 ++
code/modules/keybindings/keybind/emote.dm | 15 +
code/modules/keybindings/keybind/human.dm | 39 +++
code/modules/keybindings/keybind/living.dm | 41 +++
code/modules/keybindings/keybind/mob.dm | 123 +++++++
code/modules/keybindings/keybind/movement.dm | 175 ++++++++++
code/modules/keybindings/keybind/robot.dm | 61 ++++
code/modules/keybindings/keybind/targeting.dm | 76 +++++
code/modules/keybindings/setup.dm | 60 ++--
.../mob/living/carbon/human/inventory.dm | 62 ++++
code/modules/mob/mob_defines.dm | 3 +
code/modules/mob/say.dm | 6 +
code/modules/mob/say_vr.dm | 6 +
interface/skin.dmf | 118 ++++---
modular_citadel/interface/skin.dmf | 319 ------------------
tgstation.dme | 22 +-
41 files changed, 1346 insertions(+), 879 deletions(-)
create mode 100644 code/__DEFINES/admin/keybindings.dm
create mode 100644 code/_globalvars/lists/client.dm
create mode 100644 code/_globalvars/lists/keybindings.dm
delete mode 100644 code/modules/keybindings/bindings_admin.dm
delete mode 100644 code/modules/keybindings/bindings_carbon.dm
delete mode 100644 code/modules/keybindings/bindings_human.dm
delete mode 100644 code/modules/keybindings/bindings_living.dm
delete mode 100644 code/modules/keybindings/bindings_mob.dm
delete mode 100644 code/modules/keybindings/bindings_robot.dm
create mode 100644 code/modules/keybindings/keybind/__defines.dm
create mode 100644 code/modules/keybindings/keybind/_keybind.dm
create mode 100644 code/modules/keybindings/keybind/admin.dm
create mode 100644 code/modules/keybindings/keybind/carbon.dm
create mode 100644 code/modules/keybindings/keybind/client.dm
create mode 100644 code/modules/keybindings/keybind/emote.dm
create mode 100644 code/modules/keybindings/keybind/human.dm
create mode 100644 code/modules/keybindings/keybind/living.dm
create mode 100644 code/modules/keybindings/keybind/mob.dm
create mode 100644 code/modules/keybindings/keybind/movement.dm
create mode 100644 code/modules/keybindings/keybind/robot.dm
create mode 100644 code/modules/keybindings/keybind/targeting.dm
delete mode 100644 modular_citadel/interface/skin.dmf
diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index 0b46f21a97..e8e75c132a 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -83,10 +83,3 @@
#define SPAM_TRIGGER_WARNING 5 //Number of identical messages required before the spam-prevention will warn you to stfu
#define SPAM_TRIGGER_AUTOMUTE 10 //Number of identical messages required before the spam-prevention will automute you
-
-///Max length of a keypress command before it's considered to be a forged packet/bogus command
-#define MAX_KEYPRESS_COMMANDLENGTH 16
-///Max amount of keypress messages per second over two seconds before client is autokicked
-#define MAX_KEYPRESS_AUTOKICK 100
-///Length of max held keys
-#define MAX_HELD_KEYS 15
diff --git a/code/__DEFINES/admin/keybindings.dm b/code/__DEFINES/admin/keybindings.dm
new file mode 100644
index 0000000000..3efc26c7d5
--- /dev/null
+++ b/code/__DEFINES/admin/keybindings.dm
@@ -0,0 +1,18 @@
+// Defines for managed input/keybinding system.
+/// Max length of a keypress command before it's considered to be a forged packet/bogus command
+#define MAX_KEYPRESS_COMMANDLENGTH 16
+/// Maximum keys that can be bound to one button
+#define MAX_COMMANDS_PER_KEY 5
+/// Maximum keys per keybind
+#define MAX_KEYS_PER_KEYBIND 3
+/// Max amount of keypress messages per second over two seconds before client is autokicked
+#define MAX_KEYPRESS_AUTOKICK 100
+/// Max keys that can be held down at once by a client
+#define MAX_HELD_KEYS 15
+
+/// Macroset name of hotkeys/keybind only/modern mode
+#define SKIN_MACROSET_HOTKEYS "hotkeys"
+/// Macroset name of classic hotkey mode
+#define SKIN_MACROSET_CLASSIC_HOTKEYS "oldhotkeys"
+/// Macroset name of classic input mode
+#define SKIN_MACROSET_CLASSIC_INPUT "oldinput"
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index bcfaa1bed2..0b5c60a95a 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -75,6 +75,8 @@
var/datum/emote/E = new path()
E.emote_list[E.key] = E
+ init_keybindings()
+
//Uplink Items
for(var/path in subtypesof(/datum/uplink_item))
var/datum/uplink_item/I = path
diff --git a/code/__HELPERS/sanitize_values.dm b/code/__HELPERS/sanitize_values.dm
index dadd4322fb..cbe1817233 100644
--- a/code/__HELPERS/sanitize_values.dm
+++ b/code/__HELPERS/sanitize_values.dm
@@ -18,6 +18,12 @@
return text
return default
+/proc/sanitize_islist(value, default)
+ if(islist(value) && length(value))
+ return value
+ if(default)
+ return default
+
/proc/sanitize_inlist(value, list/List, default)
if(value in List)
return value
diff --git a/code/_globalvars/lists/client.dm b/code/_globalvars/lists/client.dm
new file mode 100644
index 0000000000..5181d870c4
--- /dev/null
+++ b/code/_globalvars/lists/client.dm
@@ -0,0 +1,21 @@
+GLOBAL_LIST_EMPTY(classic_keybinding_list_by_key)
+GLOBAL_LIST_EMPTY(hotkey_keybinding_list_by_key)
+GLOBAL_LIST_EMPTY(keybindings_by_name)
+
+// This is a mapping from JS keys to Byond - ref: https://keycode.info/
+GLOBAL_LIST_INIT(_kbMap, list(
+ "UP" = "North",
+ "RIGHT" = "East",
+ "DOWN" = "South",
+ "LEFT" = "West",
+ "INSERT" = "Insert",
+ "HOME" = "Northwest",
+ "PAGEUP" = "Northeast",
+ "DEL" = "Delete",
+ "END" = "Southwest",
+ "PAGEDOWN" = "Southeast",
+ "SPACEBAR" = "Space",
+ "ALT" = "Alt",
+ "SHIFT" = "Shift",
+ "CONTROL" = "Ctrl"
+ ))
diff --git a/code/_globalvars/lists/keybindings.dm b/code/_globalvars/lists/keybindings.dm
new file mode 100644
index 0000000000..7e5b016a95
--- /dev/null
+++ b/code/_globalvars/lists/keybindings.dm
@@ -0,0 +1,31 @@
+/// Creates and sorts all the keybinding datums
+/proc/init_keybindings()
+ for(var/KB in subtypesof(/datum/keybinding))
+ var/datum/keybinding/keybinding = KB
+ if(!initial(keybinding.hotkey_keys))
+ continue
+ add_keybinding(new keybinding)
+ // init_emote_keybinds() - Disabled - I don't particularly want this.
+
+/// Adds an instanced keybinding to the global tracker
+/proc/add_keybinding(datum/keybinding/instance)
+ GLOB.keybindings_by_name[instance.name] = instance
+
+ // Classic
+ if(LAZYLEN(instance.classic_keys))
+ for(var/bound_key in instance.classic_keys)
+ LAZYADD(GLOB.classic_keybinding_list_by_key[bound_key], list(instance.name))
+
+ // Hotkey
+ if(LAZYLEN(instance.hotkey_keys))
+ for(var/bound_key in instance.hotkey_keys)
+ LAZYADD(GLOB.hotkey_keybinding_list_by_key[bound_key], list(instance.name))
+
+/proc/init_emote_keybinds()
+ for(var/i in subtypesof(/datum/emote))
+ var/datum/emote/faketype = i
+ if(!initial(faketype.key))
+ continue
+ var/datum/keybinding/emote/emote_kb = new
+ emote_kb.link_to_emote(faketype)
+ add_keybinding(emote_kb)
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 1f8a03b3e7..812c0a00ca 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -6,13 +6,20 @@ SUBSYSTEM_DEF(input)
priority = FIRE_PRIORITY_INPUT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
- var/list/macro_sets
- var/list/movement_keys
+ /// Classic mode input focused macro set. Manually set because we can't define ANY or ANY+UP for classic.
+ var/static/list/macroset_classic_input
+ /// Classic mode map focused macro set. Manually set because it needs to be clientside and go to macroset_classic_input.
+ var/static/list/macroset_classic_hotkey
+ /// New hotkey mode macro set. All input goes into map, game keeps incessently setting your focus to map, we can use ANY all we want here; we don't care about the input bar, the user has to force the input bar every time they want to type.
+ var/static/list/macroset_hotkey
+
+ /// Macro set for hotkeys
+ var/list/hotkey_mode_macros
+ /// Macro set for classic.
+ var/list/input_mode_macros
/datum/controller/subsystem/input/Initialize()
- setup_default_macro_sets()
-
- setup_default_movement_keys()
+ setup_macrosets()
initialized = TRUE
@@ -20,94 +27,68 @@ SUBSYSTEM_DEF(input)
return ..()
-// This is for when macro sets are eventualy datumized
-/datum/controller/subsystem/input/proc/setup_default_macro_sets()
- var/list/static/default_macro_sets
-
- if(default_macro_sets)
- macro_sets = default_macro_sets
- return
-
- default_macro_sets = list(
- "default" = list(
- "Tab" = "\".winset \\\"input.focus=true?map.focus=true input.background-color=[COLOR_INPUT_DISABLED]:input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
- "O" = "ooc",
- "Ctrl+O" = "looc",
- "T" = "say",
- "Ctrl+T" = "say_indicator",
- "Y" = "whisper",
- "M" = "me",
- "Ctrl+M" = "me_indicator",
- "5" = "subtle",
- "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
- "Any" = "\"KeyDown \[\[*\]\]\"",
- "Any+UP" = "\"KeyUp \[\[*\]\]\"",
- ),
- "old_default" = list(
- "Tab" = "\".winset \\\"mainwindow.macro=old_hotkeys map.focus=true input.background-color=[COLOR_INPUT_DISABLED]\\\"\"",
- "Ctrl+T" = "say",
- "Ctrl+O" = "ooc",
- ),
- "old_hotkeys" = list(
- "Tab" = "\".winset \\\"mainwindow.macro=old_default input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
- "O" = "ooc",
- "L" = "looc",
- "T" = "say",
- "Ctrl+T" = "say_indicator",
- "M" = "me",
- "Ctrl+M" = "me_indicator",
- "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
- "Any" = "\"KeyDown \[\[*\]\]\"",
- "Any+UP" = "\"KeyUp \[\[*\]\]\"",
- ),
- )
-
- // Because i'm lazy and don't want to type all these out twice
- var/list/old_default = default_macro_sets["old_default"]
-
- var/list/static/oldmode_keys = list(
+/// Sets up the key list for classic mode for when badmins screw up vv's.
+/datum/controller/subsystem/input/proc/setup_macrosets()
+ // First, let's do the snowflake keyset!
+ macroset_classic_input = list()
+ var/list/classic_mode_keys = list(
"North", "East", "South", "West",
"Northeast", "Southeast", "Northwest", "Southwest",
- "Insert", "Delete", "Ctrl", "Alt",
+ "Insert", "Delete", "Ctrl", "Alt", "Shift",
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
)
+ for(var/key in classic_mode_keys)
+ macroset_classic_input[key] = "\"KeyDown [key]\""
+ macroset_classic_input["[key]+UP"] = "\"KeyUp [key]\""
+ // LET'S PLAY THE BIND EVERY KEY GAME!
+ // oh except for Backspace and Enter; if you want to use those you shouldn't have used oldmode!
+ var/list/classic_ctrl_override_keys = list(
+ "\[", "\]", "\\", ";", "'", ",", ".", "/", "-", "\\=", "`"
+ )
+ // i'm lazy let's play the list iteration game of numbers
+ for(var/i in 0 to 9)
+ classic_ctrl_override_keys += "[i]"
+ // let's play the ascii game of A to Z (UPPERCASE)
+ for(var/i in 65 to 90)
+ classic_ctrl_override_keys += ascii2text(i)
+ // let's play the game of clientside bind overrides!
+ classic_ctrl_override_keys -= list("T", "O", "M", "L")
+ macroset_classic_input["Ctrl+T"] = "say"
+ macroset_classic_input["Ctrl+O"] = "ooc"
+ macroset_classic_input["Ctrl+L"] = "looc"
+ // let's play the list iteration game x2
+ for(var/key in classic_ctrl_override_keys)
+ macroset_classic_input["Ctrl+[key]"] = "\"KeyDown [key]\""
+ macroset_classic_input["Ctrl+[key]+UP"] = "\"KeyUp [key]\""
+ // Misc
+ macroset_classic_input["Tab"] = "\".winset \\\"mainwindow.macro=[SKIN_MACROSET_CLASSIC_HOTKEYS] map.focus=true input.background-color=[COLOR_INPUT_DISABLED]\\\"\""
+ macroset_classic_input["Escape"] = "\".winset \\\"input.text=\\\"\\\"\\\"\""
- for(var/i in 1 to oldmode_keys.len)
- var/key = oldmode_keys[i]
- old_default[key] = "\"KeyDown [key]\""
- old_default["[key]+UP"] = "\"KeyUp [key]\""
+ // FINALLY, WE CAN DO SOMETHING MORE NORMAL FOR THE SNOWFLAKE-BUT-LESS KEYSET.
+ macroset_classic_hotkey = list(
+ "Any" = "\"KeyDown \[\[*\]\]\"",
+ "Any+UP" = "\"KeyUp \[\[*\]\]\"",
+ "Tab" = "\".winset \\\"mainwindow.macro=[SKIN_MACROSET_CLASSIC_INPUT] input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
+ "Escape" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
+ "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
+ "O" = "ooc",
+ "T" = "say",
+ "L" = "looc",
+ "M" = "me"
+ )
- var/list/static/oldmode_ctrl_override_keys = list(
- "W" = "W", "A" = "A", "S" = "S", "D" = "D", // movement
- "1" = "1", "2" = "2", "3" = "3", "4" = "4", // intent
- "B" = "B", // resist
- "E" = "E", // quick equip
- "F" = "F", // intent left
- "G" = "G", // intent right
- "H" = "H", // stop pulling
- "Q" = "Q", // drop
- "R" = "R", // throw
- "X" = "X", // switch hands
- "Y" = "Y", // activate item
- "Z" = "Z", // activate item
- )
-
- for(var/i in 1 to oldmode_ctrl_override_keys.len)
- var/key = oldmode_ctrl_override_keys[i]
- var/override = oldmode_ctrl_override_keys[key]
- old_default["Ctrl+[key]"] = "\"KeyDown [override]\""
- old_default["Ctrl+[key]+UP"] = "\"KeyUp [override]\""
-
- macro_sets = default_macro_sets
-
-// For initially setting up or resetting to default the movement keys
-/datum/controller/subsystem/input/proc/setup_default_movement_keys()
- var/static/list/default_movement_keys = list(
- "W" = NORTH, "A" = WEST, "S" = SOUTH, "D" = EAST, // WASD
- "North" = NORTH, "West" = WEST, "South" = SOUTH, "East" = EAST, // Arrow keys & Numpad
- )
-
- movement_keys = default_movement_keys.Copy()
+ // And finally, the modern set.
+ macroset_hotkey = list(
+ "Any" = "\"KeyDown \[\[*\]\]\"",
+ "Any+UP" = "\"KeyUp \[\[*\]\]\"",
+ "Tab" = "\".winset \\\"input.focus=true?map.focus=true input.background-color=[COLOR_INPUT_DISABLED]:input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
+ "Escape" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
+ "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
+ "O" = "ooc",
+ "T" = "say",
+ "L" = "looc",
+ "M" = "me"
+ )
// Badmins just wanna have fun ♪
/datum/controller/subsystem/input/proc/refresh_client_macro_sets()
@@ -115,9 +96,10 @@ SUBSYSTEM_DEF(input)
for(var/i in 1 to clients.len)
var/client/user = clients[i]
user.set_macros()
+ user.update_movement_keys()
/datum/controller/subsystem/input/fire()
var/list/clients = GLOB.clients // Let's sing the list cache song
- for(var/i in 1 to length(clients))
+ for(var/i in 1 to clients.len)
var/client/C = clients[i]
C.keyLoop()
diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm
index a06b674e42..41662c1cad 100644
--- a/code/modules/admin/secrets.dm
+++ b/code/modules/admin/secrets.dm
@@ -69,11 +69,6 @@
The floor is lava! (DANGEROUS: extremely lame) Spawn a custom portal storm
- Flip client movement directions
- Randomize client movement directions
- Set each movement direction manually
- Reset movement directions to default
- Change bomb cap Mass Purrbation Mass Remove Purrbation
@@ -603,60 +598,6 @@
purrbation.")
log_admin("[key_name(usr)] has removed everyone from purrbation.")
- if("flipmovement")
- if(!check_rights(R_FUN))
- return
- if(alert("Flip all movement controls?","Confirm","Yes","Cancel") == "Cancel")
- return
- var/list/movement_keys = SSinput.movement_keys
- for(var/i in 1 to movement_keys.len)
- var/key = movement_keys[i]
- movement_keys[key] = turn(movement_keys[key], 180)
- message_admins("[key_name_admin(usr)] has flipped all movement directions.")
- log_admin("[key_name(usr)] has flipped all movement directions.")
-
- if("randommovement")
- if(!check_rights(R_FUN))
- return
- if(alert("Randomize all movement controls?","Confirm","Yes","Cancel") == "Cancel")
- return
- var/list/movement_keys = SSinput.movement_keys
- for(var/i in 1 to movement_keys.len)
- var/key = movement_keys[i]
- movement_keys[key] = turn(movement_keys[key], 45 * rand(1, 8))
- message_admins("[key_name_admin(usr)] has randomized all movement directions.")
- log_admin("[key_name(usr)] has randomized all movement directions.")
-
- if("custommovement")
- if(!check_rights(R_FUN))
- return
- if(alert("Are you sure you want to change every movement key?","Confirm","Yes","Cancel") == "Cancel")
- return
- var/list/movement_keys = SSinput.movement_keys
- var/list/new_movement = list()
- for(var/i in 1 to movement_keys.len)
- var/key = movement_keys[i]
-
- var/msg = "Please input the new movement direction when the user presses [key]. Ex. northeast"
- var/title = "New direction for [key]"
- var/new_direction = text2dir(input(usr, msg, title) as text|null)
- if(!new_direction)
- new_direction = movement_keys[key]
-
- new_movement[key] = new_direction
- SSinput.movement_keys = new_movement
- message_admins("[key_name_admin(usr)] has configured all movement directions.")
- log_admin("[key_name(usr)] has configured all movement directions.")
-
- if("resetmovement")
- if(!check_rights(R_FUN))
- return
- if(alert("Are you sure you want to reset movement keys to default?","Confirm","Yes","Cancel") == "Cancel")
- return
- SSinput.setup_default_movement_keys()
- message_admins("[key_name_admin(usr)] has reset all movement keys.")
- log_admin("[key_name(usr)] has reset all movement keys.")
-
if("customportal")
if(!check_rights(R_FUN))
return
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index f865cfddbb..47255d9535 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -80,10 +80,24 @@
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
+ /// Keys currently held
+ var/list/keys_held = list()
+ /// These next two vars are to apply movement for keypresses and releases made while move delayed.
+ /// Because discarding that input makes the game less responsive.
+ /// On next move, add this dir to the move that would otherwise be done
+ var/next_move_dir_add
+ /// On next move, subtract this dir from the move that would otherwise be done
+ var/next_move_dir_sub
+ /// Amount of keydowns in the last keysend checking interval
var/client_keysend_amount = 0
+ /// World tick time where client_keysend_amount will reset
var/next_keysend_reset = 0
+ /// World tick time where keysend_tripped will reset back to false
var/next_keysend_trip_reset = 0
+ /// When set to true, user will be autokicked if they trip the keysends in a second limit again
var/keysend_tripped = FALSE
+ /// custom movement keys for this client
+ var/list/movement_keys = list()
/// Messages currently seen by this client
var/list/seen_messages
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index ab1db47db6..a66d804b55 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -265,6 +265,9 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
else
prefs = new /datum/preferences(src)
GLOB.preferences_datums[ckey] = prefs
+ if(SSinput.initialized)
+ set_macros()
+ update_movement_keys(prefs)
prefs.last_ip = address //these are gonna be used for banning
prefs.last_id = computer_id //these are gonna be used for banning
@@ -328,9 +331,6 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
qdel(src)
return
- if(SSinput.initialized)
- set_macros()
-
chatOutput.start() // Starts the chat
if(alert_mob_dupe_login)
@@ -892,6 +892,23 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
y = clamp(y+change, min,max)
change_view("[x]x[y]")
+/client/proc/update_movement_keys(datum/preferences/direct_prefs)
+ var/datum/preferences/D = prefs || direct_prefs
+ if(!D?.key_bindings)
+ return
+ movement_keys = list()
+ for(var/key in D.key_bindings)
+ for(var/kb_name in D.key_bindings[key])
+ switch(kb_name)
+ if("North")
+ movement_keys[key] = NORTH
+ if("East")
+ movement_keys[key] = EAST
+ if("West")
+ movement_keys[key] = WEST
+ if("South")
+ movement_keys[key] = SOUTH
+
/client/proc/change_view(new_size)
if (isnull(new_size))
CRASH("change_view called without argument.")
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 65fd5e2186..77bcbacfcf 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -46,6 +46,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/chat_on_map = TRUE
var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH
var/see_chat_non_mob = TRUE
+
+ /// Custom Keybindings
+ var/list/key_bindings = list()
+
+
var/tgui_fancy = TRUE
var/tgui_lock = TRUE
var/windowflashing = TRUE
@@ -184,9 +189,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/uplink_spawn_loc = UPLINK_PDA
- var/sprint_spacebar = FALSE
- var/sprint_toggle = FALSE
-
var/hud_toggle_flash = TRUE
var/hud_toggle_color = "#ffffff"
@@ -245,6 +247,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
//we couldn't load character data so just randomize the character appearance + name
random_character() //let's create a random character then - rather than a fat, bald and naked man.
+ key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys
+ C?.update_movement_keys(src)
real_name = pref_species.random_name(gender,1)
if(!loaded_preferences_successfully)
save_preferences()
@@ -266,6 +270,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Loadout"
dat += "Game Preferences"
dat += "Content Preferences"
+ dat += "Keybindings"
if(!path)
dat += "
Please create an account to save your preferences
"
@@ -855,7 +860,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "See Runechat for non-mobs:[see_chat_non_mob ? "Enabled" : "Disabled"] "
dat += " "
dat += "Action Buttons:[(buttons_locked) ? "Locked In Place" : "Unlocked"] "
- dat += "Keybindings:[(hotkeys) ? "Hotkeys" : "Default"] "
dat += " "
dat += "PDA Color:Change "
dat += "PDA Style:[pda_style] "
@@ -951,8 +955,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += " "
dat += "Ambient Occlusion:[ambientocclusion ? "Enabled" : "Disabled"] "
dat += "Fit Viewport:[auto_fit_viewport ? "Auto" : "Manual"] "
- dat += "Sprint Key:[sprint_spacebar ? "Space" : "Shift"] "
- dat += "Toggle Sprint:[sprint_toggle ? "Enabled" : "Disabled"] "
dat += "HUD Button Flashes:[hud_toggle_flash ? "Enabled" : "Disabled"] "
dat += "HUD Button Flash Color:Change "
@@ -1075,6 +1077,56 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Ass Slapping:[(cit_toggles & NO_ASS_SLAP) ? "Disallowed" : "Allowed"] "
dat += ""
dat += " "
+ if(5) // Custom keybindings
+ dat += "Keybindings:[(hotkeys) ? "Hotkeys" : "Input"] "
+ dat += "Keybindings mode controls how the game behaves with tab and map/input focus. If it is on Hotkeys, the game will always attempt to force you to map focus, meaning keypresses are sent \
+ directly to the map instead of the input. You will still be able to use the command bar, but you need to tab to do it every time you click on the game map. \
+ If it is on Input, the game will not force focus away from the input bar, and you can switch focus using TAB between these two modes: If the input bar is pink, that means that you are in non-hotkey mode, sending all keypresses of the normal \
+ alphanumeric characters, punctuation, spacebar, backspace, enter, etc, typing keys into the input bar. If the input bar is white, you are in hotkey mode, meaning all keypresses go into the game's keybind handling system unless you \
+ manually click on the input bar to shift focus there. \
+ Input mode is the closest thing to the old input system. \
+ IMPORTANT: While in input mode's non hotkey setting (tab toggled), Ctrl + KEY will send KEY to the keybind system as the key itself, not as Ctrl + KEY. This means Ctrl + T/W/A/S/D/all your familiar stuff still works, but you \
+ won't be able to access any regular Ctrl binds. "
+ // Create an inverted list of keybindings -> key
+ var/list/user_binds = list()
+ for (var/key in key_bindings)
+ for(var/kb_name in key_bindings[key])
+ user_binds[kb_name] += list(key)
+
+ var/list/kb_categories = list()
+ // Group keybinds by category
+ for (var/name in GLOB.keybindings_by_name)
+ var/datum/keybinding/kb = GLOB.keybindings_by_name[name]
+ kb_categories[kb.category] += list(kb)
+
+ dat += ""
+
+ for (var/category in kb_categories)
+ dat += "
[category]
"
+ for (var/i in kb_categories[category])
+ var/datum/keybinding/kb = i
+ if(!length(user_binds[kb.name]))
+ dat += " Unbound"
+ var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys
+ if(LAZYLEN(default_keys))
+ dat += "| Default: [default_keys.Join(", ")]"
+ dat += " "
+ else
+ var/bound_key = user_binds[kb.name][1]
+ dat += " [bound_key]"
+ for(var/bound_key_index in 2 to length(user_binds[kb.name]))
+ bound_key = user_binds[kb.name][bound_key_index]
+ dat += " | [bound_key]"
+ if(length(user_binds[kb.name]) < MAX_KEYS_PER_KEYBIND)
+ dat += "| Add Secondary"
+ var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys
+ if(LAZYLEN(default_keys))
+ dat += "| Default: [default_keys.Join(", ")]"
+ dat += " "
+
+ dat += "