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 += "[kb.full_name] 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 += "[kb.full_name] [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 += " "
+ dat += "\[Reset to default\] "
+ dat += ""
dat += ""
@@ -1095,6 +1147,31 @@ GLOBAL_LIST_EMPTY(preferences_datums)
#undef APPEARANCE_CATEGORY_COLUMN
#undef MAX_MUTANT_ROWS
+/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, var/old_key)
+ var/HTML = {"
+ Keybinding: [kb.full_name] [kb.description]Press any key to change Press ESC to clear
+
+ "}
+ winshow(user, "capturekeypress", TRUE)
+ var/datum/browser/popup = new(user, "capturekeypress", "Keybindings
", 350, 300)
+ popup.set_content(HTML)
+ popup.open(FALSE)
+ onclose(user, "capturekeypress", src)
+
/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620)
if(!SSjob)
return
@@ -2275,10 +2352,73 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("hotkeys")
hotkeys = !hotkeys
- if(hotkeys)
- winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=default")
- else
- winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default")
+ user.client.set_macros()
+
+ if("keybindings_capture")
+ var/datum/keybinding/kb = GLOB.keybindings_by_name[href_list["keybinding"]]
+ var/old_key = href_list["old_key"]
+ CaptureKeybinding(user, kb, old_key)
+ return
+
+ if("keybindings_set")
+ var/kb_name = href_list["keybinding"]
+ if(!kb_name)
+ user << browse(null, "window=capturekeypress")
+ ShowChoices(user)
+ return
+
+ var/clear_key = text2num(href_list["clear_key"])
+ var/old_key = href_list["old_key"]
+ if(clear_key)
+ if(key_bindings[old_key])
+ key_bindings[old_key] -= kb_name
+ if(!length(key_bindings[old_key]))
+ key_bindings -= old_key
+ user << browse(null, "window=capturekeypress")
+ save_preferences()
+ ShowChoices(user)
+ return
+
+ var/new_key = uppertext(href_list["key"])
+ var/AltMod = text2num(href_list["alt"]) ? "Alt" : ""
+ var/CtrlMod = text2num(href_list["ctrl"]) ? "Ctrl" : ""
+ var/ShiftMod = text2num(href_list["shift"]) ? "Shift" : ""
+ var/numpad = text2num(href_list["numpad"]) ? "Numpad" : ""
+ // var/key_code = text2num(href_list["key_code"])
+
+ if(GLOB._kbMap[new_key])
+ new_key = GLOB._kbMap[new_key]
+
+ var/full_key
+ switch(new_key)
+ if("Alt")
+ full_key = "[new_key][CtrlMod][ShiftMod]"
+ if("Ctrl")
+ full_key = "[AltMod][new_key][ShiftMod]"
+ if("Shift")
+ full_key = "[AltMod][CtrlMod][new_key]"
+ else
+ full_key = "[AltMod][CtrlMod][ShiftMod][numpad][new_key]"
+ if(key_bindings[old_key])
+ key_bindings[old_key] -= kb_name
+ if(!length(key_bindings[old_key]))
+ key_bindings -= old_key
+ key_bindings[full_key] += list(kb_name)
+ key_bindings[full_key] = sortList(key_bindings[full_key])
+
+ user << browse(null, "window=capturekeypress")
+ user.client.update_movement_keys()
+ save_preferences()
+
+ if("keybindings_reset")
+ var/choice = tgalert(user, "Would you prefer 'hotkey' or 'classic' defaults?", "Setup keybindings", "Hotkey", "Classic", "Cancel")
+ if(choice == "Cancel")
+ ShowChoices(user)
+ return
+ hotkeys = (choice == "Hotkey")
+ key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
+ user.client.update_movement_keys()
+
if("chat_on_map")
chat_on_map = !chat_on_map
if("see_chat_non_mob")
@@ -2409,13 +2549,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(auto_fit_viewport && parent)
parent.fit_viewport()
- if("sprint_key")
- sprint_spacebar = !sprint_spacebar
-
- if("sprint_toggle")
- sprint_toggle = !sprint_toggle
-
-
if("hud_toggle_flash")
hud_toggle_flash = !hud_toggle_flash
@@ -2595,6 +2728,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
cached_holoform_icons[filter_type] = process_holoform_icon_filter(custom_holoform_icon, filter_type)
return cached_holoform_icons[filter_type]
+//Used in savefile update 32, can be removed once that is no longer relevant.
+/datum/preferences/proc/force_reset_keybindings()
+ var/choice = tgalert(parent.mob, "Your basic keybindings need to be reset, emotes will remain as before. Would you prefer 'hotkey' or 'classic' mode?", "Reset keybindings", "Hotkey", "Classic")
+ hotkeys = (choice != "Classic")
+ var/list/oldkeys = key_bindings
+ key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
+
+ for(var/key in oldkeys)
+ if(!key_bindings[key])
+ key_bindings[key] = oldkeys[key]
+ parent.update_movement_keys()
+
/datum/preferences/proc/is_loadout_slot_available(slot)
var/list/L
LAZYINITLIST(L)
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index db03cc106f..a7384ad392 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 31
+#define SAVEFILE_VERSION_MAX 32
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -42,7 +42,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//if your savefile is 3 months out of date, then 'tough shit'.
/datum/preferences/proc/update_preferences(current_version, savefile/S)
- return
+ if(current_version < 32) //If you remove this, remove force_reset_keybindings() too.
+ addtimer(CALLBACK(src, .proc/force_reset_keybindings), 30) //No mob available when this is run, timer allows user choice.
/datum/preferences/proc/update_character(current_version, savefile/S)
if(current_version < 19)
@@ -250,8 +251,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["parallax"] >> parallax
S["ambientocclusion"] >> ambientocclusion
S["auto_fit_viewport"] >> auto_fit_viewport
- S["sprint_spacebar"] >> sprint_spacebar
- S["sprint_toggle"] >> sprint_toggle
S["hud_toggle_flash"] >> hud_toggle_flash
S["hud_toggle_color"] >> hud_toggle_color
S["menuoptions"] >> menuoptions
@@ -261,6 +260,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["pda_color"] >> pda_color
S["pda_skin"] >> pda_skin
+ // Custom hotkeys
+ S["key_bindings"] >> key_bindings
+
//citadel code
S["arousable"] >> arousable
S["screenshake"] >> screenshake
@@ -295,8 +297,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
parallax = sanitize_integer(parallax, PARALLAX_INSANE, PARALLAX_DISABLE, null)
ambientocclusion = sanitize_integer(ambientocclusion, 0, 1, initial(ambientocclusion))
auto_fit_viewport = sanitize_integer(auto_fit_viewport, 0, 1, initial(auto_fit_viewport))
- sprint_spacebar = sanitize_integer(sprint_spacebar, 0, 1, initial(sprint_spacebar))
- sprint_toggle = sanitize_integer(sprint_toggle, 0, 1, initial(sprint_toggle))
hud_toggle_flash = sanitize_integer(hud_toggle_flash, 0, 1, initial(hud_toggle_flash))
hud_toggle_color = sanitize_hexcolor(hud_toggle_color, 6, 1, initial(hud_toggle_color))
ghost_form = sanitize_inlist(ghost_form, GLOB.ghost_forms, initial(ghost_form))
@@ -315,6 +315,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
cit_toggles = sanitize_integer(cit_toggles, 0, 16777215, initial(cit_toggles))
auto_ooc = sanitize_integer(auto_ooc, 0, 1, initial(auto_ooc))
no_tetris_storage = sanitize_integer(no_tetris_storage, 0, 1, initial(no_tetris_storage))
+ key_bindings = sanitize_islist(key_bindings, list())
return 1
@@ -362,8 +363,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["parallax"], parallax)
WRITE_FILE(S["ambientocclusion"], ambientocclusion)
WRITE_FILE(S["auto_fit_viewport"], auto_fit_viewport)
- WRITE_FILE(S["sprint_spacebar"], sprint_spacebar)
- WRITE_FILE(S["sprint_toggle"], sprint_toggle)
WRITE_FILE(S["hud_toggle_flash"], hud_toggle_flash)
WRITE_FILE(S["hud_toggle_color"], hud_toggle_color)
WRITE_FILE(S["menuoptions"], menuoptions)
@@ -372,6 +371,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["pda_style"], pda_style)
WRITE_FILE(S["pda_color"], pda_color)
WRITE_FILE(S["pda_skin"], pda_skin)
+ WRITE_FILE(S["key_bindings"], key_bindings)
//citadel code
WRITE_FILE(S["screenshake"], screenshake)
diff --git a/code/modules/keybindings/bindings_admin.dm b/code/modules/keybindings/bindings_admin.dm
deleted file mode 100644
index ca232adbe0..0000000000
--- a/code/modules/keybindings/bindings_admin.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-/datum/admins/key_down(_key, client/user)
- switch(_key)
- if("F3")
- user.get_admin_say()
- return
- if("F5")
- user.admin_ghost()
- return
- if("F6")
- player_panel_new()
- return
- if("F7")
- user.togglebuildmodeself()
- return
- if("F8")
- if(user.keys_held["Ctrl"])
- user.stealth()
- else
- user.invisimin()
- return
- if("F10")
- user.get_dead_say()
- return
- ..()
diff --git a/code/modules/keybindings/bindings_atom.dm b/code/modules/keybindings/bindings_atom.dm
index 5f3e879237..47d6b16feb 100644
--- a/code/modules/keybindings/bindings_atom.dm
+++ b/code/modules/keybindings/bindings_atom.dm
@@ -5,7 +5,7 @@
if(!user.keys_held["Ctrl"])
var/movement_dir = NONE
for(var/_key in user.keys_held)
- movement_dir = movement_dir | SSinput.movement_keys[_key]
+ movement_dir = movement_dir | user.movement_keys[_key]
if(user.next_move_dir_add)
movement_dir |= user.next_move_dir_add
if(user.next_move_dir_sub)
diff --git a/code/modules/keybindings/bindings_carbon.dm b/code/modules/keybindings/bindings_carbon.dm
deleted file mode 100644
index e344bc9d20..0000000000
--- a/code/modules/keybindings/bindings_carbon.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/mob/living/carbon/key_down(_key, client/user)
- switch(_key)
- if("R", "Southwest") // Southwest is End
- toggle_throw_mode()
- return
- if("C")
- user_toggle_intentional_combat_mode()
- return
- return ..()
\ No newline at end of file
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 6030362a61..a73dd9a59b 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -33,55 +33,69 @@
to_chat(src, "Invalid KeyDown detected! You have been disconnected from the server automatically. ")
log_admin("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
message_admins("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
- QDEL_IN(src, 1)
+ qdel(src)
+ return
+
+ if(_key == "Tab")
+ ForceAllKeysUp() //groan, more hacky kevcode
return
if(length(keys_held) > MAX_HELD_KEYS)
keys_held.Cut(1,2)
keys_held[_key] = TRUE
- var/movement = SSinput.movement_keys[_key]
+ var/movement = movement_keys[_key]
if(!(next_move_dir_sub & movement) && !keys_held["Ctrl"])
next_move_dir_add |= movement
// Client-level keybindings are ones anyone should be able to do at any time
// Things like taking screenshots, hitting tab, and adminhelps.
-
+ var/AltMod = keys_held["Alt"] ? "Alt" : ""
+ var/CtrlMod = keys_held["Ctrl"] ? "Ctrl" : ""
+ var/ShiftMod = keys_held["Shift"] ? "Shift" : ""
+ var/full_key
switch(_key)
- if("F1")
- if(keys_held["Ctrl"] && keys_held["Shift"]) // Is this command ever used?
- winset(src, null, "command=.options")
- else
- get_adminhelp()
- return
- if("F2") // Screenshot. Hold shift to choose a name and location to save in
- winset(src, null, "command=.screenshot [!keys_held["shift"] ? "auto" : ""]")
- return
- if("F12") // Toggles minimal HUD
- mob.button_pressed_F12()
- return
+ if("Alt", "Ctrl", "Shift")
+ full_key = "[AltMod][CtrlMod][ShiftMod]"
+ else
+ full_key = "[AltMod][CtrlMod][ShiftMod][_key]"
+ var/keycount = 0
+ for(var/kb_name in prefs.key_bindings[full_key])
+ keycount++
+ var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name]
+ if(kb.can_use(src) && kb.down(src) && keycount >= MAX_COMMANDS_PER_KEY)
+ break
- if(holder)
- holder.key_down(_key, src)
- if(mob.focus)
- mob.focus.key_down(_key, src)
+ holder?.key_down(_key, src)
+ mob.focus?.key_down(_key, src)
+
+/// Keyup's all keys held down.
+/client/proc/ForceAllKeysUp()
+ // simulate a user releasing all keys except for the mod keys. groan. i hate this. thanks, byond. why aren't keyups able to be forced to fire on macro change aoaoaoao.
+ // groan
+ for(var/key in keys_held) // all of these won't be the 3 mod keys.
+ if((key == "Ctrl") || (key == "Alt") || (key == "Shift"))
+ continue
+ keyUp("[key]")
/client/verb/keyUp(_key as text)
set instant = TRUE
set hidden = TRUE
keys_held -= _key
- var/movement = SSinput.movement_keys[_key]
+ var/movement = movement_keys[_key]
if(!(next_move_dir_add & movement))
next_move_dir_sub |= movement
- if(holder)
- holder.key_up(_key, src)
- if(mob.focus)
- mob.focus.key_up(_key, src)
+ // We don't do full key for release, because for mod keys you
+ // can hold different keys and releasing any should be handled by the key binding specifically
+ for (var/kb_name in prefs.key_bindings[_key])
+ var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name]
+ if(kb.can_use(src) && kb.up(src))
+ break
+ holder?.key_up(_key, src)
+ mob.focus?.key_up(_key, src)
// Called every game tick
/client/keyLoop()
- if(holder)
- holder.keyLoop(src)
- if(mob.focus)
- mob.focus.keyLoop(src)
\ No newline at end of file
+ holder?.keyLoop(src)
+ mob.focus?.keyLoop(src)
diff --git a/code/modules/keybindings/bindings_human.dm b/code/modules/keybindings/bindings_human.dm
deleted file mode 100644
index 9569de338c..0000000000
--- a/code/modules/keybindings/bindings_human.dm
+++ /dev/null
@@ -1,80 +0,0 @@
-/mob/living/carbon/human/key_down(_key, client/user)
- if(client.keys_held["Shift"])
- switch(_key)
- if("E") // Put held thing in belt or take out most recent thing from belt
- var/obj/item/thing = get_active_held_item()
- var/obj/item/storage/equipped_belt = get_item_by_slot(SLOT_BELT)
- if(!equipped_belt) // We also let you equip a belt like this
- if(!thing)
- to_chat(user, "You have no belt to take something out of. ")
- return
- equip_to_slot_if_possible(thing, SLOT_BELT)
- return
- if(!istype(equipped_belt)) // not a storage item
- if(!thing)
- to_chat(user, "You have no belt to take something out of. ")
- else
- to_chat(user, "You can't fit anything in. ")
- return
- if(thing) // put thing in belt
- if(!SEND_SIGNAL(equipped_belt, COMSIG_TRY_STORAGE_INSERT, thing, user.mob))
- to_chat(user, "You can't fit anything in. ")
- return
- if(!equipped_belt.contents.len) // nothing to take out
- to_chat(user, "There's nothing in your belt to take out. ")
- return
- var/obj/item/stored = equipped_belt.contents[equipped_belt.contents.len]
- if(!stored || stored.on_found(src))
- return
- stored.attack_hand(src) // take out thing from belt
- return
-
- if("B") // Put held thing in backpack or take out most recent thing from backpack
- var/obj/item/thing = get_active_held_item()
- var/obj/item/storage/equipped_backpack = get_item_by_slot(SLOT_BACK)
- if(!equipped_backpack) // We also let you equip a backpack like this
- if(!thing)
- to_chat(user, "You have no backpack to take something out of. ")
- return
- equip_to_slot_if_possible(thing, SLOT_BACK)
- return
- if(!istype(equipped_backpack)) // not a storage item
- if(!thing)
- to_chat(user, "You have no backpack to take something out of. ")
- else
- to_chat(user, "You can't fit anything in. ")
- return
- if(thing) // put thing in backpack
- if(!SEND_SIGNAL(equipped_backpack, COMSIG_TRY_STORAGE_INSERT, thing, user.mob))
- to_chat(user, "You can't fit anything in. ")
- return
- if(!equipped_backpack.contents.len) // nothing to take out
- to_chat(user, "There's nothing in your backpack to take out. ")
- return
- var/obj/item/stored = equipped_backpack.contents[equipped_backpack.contents.len]
- if(!stored || stored.on_found(src))
- return
- stored.attack_hand(src) // take out thing from backpack
- return
- switch(_key)
- if("Shift")
- if(!user.prefs.sprint_spacebar)
- user.prefs.sprint_toggle ? default_toggle_sprint() : sprint_hotkey(TRUE) //Yes, this looks hacky. Yes, this works.
- return
- if("Space")
- if(user.prefs.sprint_spacebar)
- user.prefs.sprint_toggle ? default_toggle_sprint() : sprint_hotkey(TRUE)
- return
- return ..()
-
-/mob/living/carbon/human/key_up(_key, client/user)
- switch(_key)
- if("Shift")
- if(!user.prefs.sprint_spacebar && !user.prefs.sprint_toggle)
- sprint_hotkey(FALSE)
- return
- if("Space")
- if(user.prefs.sprint_spacebar && !user.prefs.sprint_toggle)
- sprint_hotkey(FALSE)
- return
- return ..()
diff --git a/code/modules/keybindings/bindings_living.dm b/code/modules/keybindings/bindings_living.dm
deleted file mode 100644
index b338c5e899..0000000000
--- a/code/modules/keybindings/bindings_living.dm
+++ /dev/null
@@ -1,26 +0,0 @@
-/mob/living/key_down(_key, client/user)
- switch(_key)
- if("B")
- resist()
- return
- if("1")
- if(possible_a_intents)
- a_intent_change(INTENT_HELP)
- return
- if("2")
- if(possible_a_intents)
- a_intent_change(INTENT_DISARM)
- return
- if("3")
- if(possible_a_intents)
- a_intent_change(INTENT_GRAB)
- return
- if("4")
- if(possible_a_intents)
- a_intent_change(INTENT_HARM)
- return
- if ("V")
- lay_down()
- return
-
- return ..()
\ No newline at end of file
diff --git a/code/modules/keybindings/bindings_mob.dm b/code/modules/keybindings/bindings_mob.dm
deleted file mode 100644
index 36b4775c34..0000000000
--- a/code/modules/keybindings/bindings_mob.dm
+++ /dev/null
@@ -1,94 +0,0 @@
-// Technically the client argument is unncessary here since that SHOULD be src.client but let's not assume things
-// All it takes is one badmin setting their focus to someone else's client to mess things up
-// Or we can have NPC's send actual keypresses and detect that by seeing no client
-
-/mob/key_down(_key, client/user)
- switch(_key)
- if("Delete", "H")
- if(!pulling)
- to_chat(src, "You are not pulling anything. ")
- else
- stop_pulling()
- return
- if("Insert", "G")
- a_intent_change(INTENT_HOTKEY_RIGHT)
- return
- if("F")
- a_intent_change(INTENT_HOTKEY_LEFT)
- return
- if("X", "Northeast") // Northeast is Page-up
- swap_hand()
- return
- if("Y", "Z", "Southeast") // Southeast is Page-down
- mode() // attack_self(). No idea who came up with "mode()"
- return
- if("Q", "Northwest") // Northwest is Home
- var/obj/item/I = get_active_held_item()
- if(!I)
- to_chat(src, "You have nothing to drop in your hand! ")
- else
- dropItemToGround(I)
- return
- if("E")
- quick_equip()
- return
- if("Alt")
- toggle_move_intent()
- return
- //Bodypart selections
- if("Numpad8")
- user.body_toggle_head()
- return
- if("Numpad4")
- user.body_r_arm()
- return
- if("Numpad5")
- user.body_chest()
- return
- if("Numpad6")
- user.body_l_arm()
- return
- if("Numpad1")
- user.body_r_leg()
- return
- if("Numpad2")
- user.body_groin()
- return
- if("Numpad3")
- user.body_l_leg()
- return
-
- if(client.keys_held["Ctrl"])
- switch(SSinput.movement_keys[_key])
- if(NORTH)
- if(client.keys_held["Shift"])
- northshift()
- else
- northface()
- return
- if(SOUTH)
- if(client.keys_held["Shift"])
- southshift()
- else
- southface()
- return
- if(WEST)
- if(client.keys_held["Shift"])
- westshift()
- else
- westface()
- return
- if(EAST)
- if(client.keys_held["Shift"])
- eastshift()
- else
- eastface()
- return
- return ..()
-
-/mob/key_up(_key, client/user)
- switch(_key)
- if("Alt")
- toggle_move_intent()
- return
- return ..()
diff --git a/code/modules/keybindings/bindings_robot.dm b/code/modules/keybindings/bindings_robot.dm
deleted file mode 100644
index 29fb36fa65..0000000000
--- a/code/modules/keybindings/bindings_robot.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-/mob/living/silicon/robot/key_down(_key, client/user)
- switch(_key)
- if("1", "2", "3")
- toggle_module(text2num(_key))
- return
- if("4")
- a_intent_change(INTENT_HOTKEY_LEFT)
- return
- if("Q")
- uneq_active()
- return
- if("Shift")
- sprint_hotkey(TRUE)
- return
- return ..()
-
-/mob/living/silicon/robot/key_up(_key, client/user)
- switch(_key)
- if("Shift")
- sprint_hotkey(FALSE)
- return
- return ..()
diff --git a/code/modules/keybindings/focus.dm b/code/modules/keybindings/focus.dm
index 205b293e9a..d542b49267 100644
--- a/code/modules/keybindings/focus.dm
+++ b/code/modules/keybindings/focus.dm
@@ -1,6 +1,3 @@
-/mob
- var/datum/focus //What receives our keyboard inputs. src by default
-
/mob/proc/set_focus(datum/new_focus)
if(focus == new_focus)
return
diff --git a/code/modules/keybindings/keybind/__defines.dm b/code/modules/keybindings/keybind/__defines.dm
new file mode 100644
index 0000000000..baa095987c
--- /dev/null
+++ b/code/modules/keybindings/keybind/__defines.dm
@@ -0,0 +1,20 @@
+#define CATEGORY_CLIENT "CLIENT"
+#define CATEGORY_EMOTE "EMOTE"
+#define CATEGORY_ADMIN "ADMIN"
+#define CATEGORY_XENO "XENO"
+#define CATEGORY_CARBON "CARBON"
+#define CATEGORY_HUMAN "HUMAN"
+#define CATEGORY_ROBOT "ROBOT"
+#define CATEGORY_MISC "MISC"
+#define CATEGORY_MOVEMENT "MOVEMENT"
+#define CATEGORY_TARGETING "TARGETING"
+
+#define WEIGHT_HIGHEST 0
+#define WEIGHT_ADMIN 10
+#define WEIGHT_CLIENT 20
+#define WEIGHT_ROBOT 30
+#define WEIGHT_MOB 40
+#define WEIGHT_LIVING 50
+#define WEIGHT_DEAD 60
+#define WEIGHT_EMOTE 70
+#define WEIGHT_LOWEST 999
diff --git a/code/modules/keybindings/keybind/_keybind.dm b/code/modules/keybindings/keybind/_keybind.dm
new file mode 100644
index 0000000000..1d68e2d037
--- /dev/null
+++ b/code/modules/keybindings/keybind/_keybind.dm
@@ -0,0 +1,24 @@
+/datum/keybinding
+ var/list/hotkey_keys
+ var/list/classic_keys
+ var/name
+ var/full_name
+ var/description = ""
+ var/category = CATEGORY_MISC
+ var/weight = WEIGHT_LOWEST
+ var/keybind_signal
+
+/datum/keybinding/New()
+
+ // Default keys to the master "hotkey_keys"
+ if(LAZYLEN(hotkey_keys) && !LAZYLEN(classic_keys))
+ classic_keys = hotkey_keys.Copy()
+
+/datum/keybinding/proc/down(client/user)
+ return FALSE
+
+/datum/keybinding/proc/up(client/user)
+ return FALSE
+
+/datum/keybinding/proc/can_use(client/user)
+ return TRUE
diff --git a/code/modules/keybindings/keybind/admin.dm b/code/modules/keybindings/keybind/admin.dm
new file mode 100644
index 0000000000..b63221e97a
--- /dev/null
+++ b/code/modules/keybindings/keybind/admin.dm
@@ -0,0 +1,96 @@
+/datum/keybinding/admin
+ category = CATEGORY_ADMIN
+ weight = WEIGHT_ADMIN
+
+/datum/keybinding/admin/can_use(client/user)
+ return user.holder ? TRUE : FALSE
+
+/datum/keybinding/admin/admin_say
+ hotkey_keys = list("F3")
+ name = "admin_say"
+ full_name = "Admin say"
+ description = "Talk with other admins."
+
+/datum/keybinding/admin/admin_say/down(client/user)
+ user.get_admin_say()
+ return TRUE
+
+/datum/keybinding/admin/admin_ghost
+ hotkey_keys = list("F5")
+ name = "admin_ghost"
+ full_name = "Aghost"
+ description = "Go ghost"
+
+/datum/keybinding/admin/admin_ghost/down(client/user)
+ user.admin_ghost()
+ return TRUE
+
+/datum/keybinding/admin/player_panel_new
+ hotkey_keys = list("F6")
+ name = "player_panel_new"
+ full_name = "Player Panel New"
+ description = "Opens up the new player panel"
+
+/datum/keybinding/admin/player_panel_new/down(client/user)
+ user.holder.player_panel_new()
+ return TRUE
+
+/datum/keybinding/admin/toggle_buildmode_self
+ hotkey_keys = list("F7")
+ name = "toggle_buildmode_self"
+ full_name = "Toggle Buildmode Self"
+ description = "Toggles buildmode"
+
+/datum/keybinding/admin/toggle_buildmode_self/down(client/user)
+ user.togglebuildmodeself()
+ return TRUE
+
+/datum/keybinding/admin/stealthmode
+ hotkey_keys = list("CtrlF8")
+ name = "stealth_mode"
+ full_name = "Stealth mode"
+ description = "Enters stealth mode"
+
+/datum/keybinding/admin/stealthmode/down(client/user)
+ user.stealth()
+ return TRUE
+
+/datum/keybinding/admin/invisimin
+ hotkey_keys = list("F8")
+ name = "invisimin"
+ full_name = "Admin invisibility"
+ description = "Toggles ghost-like invisibility (Don't abuse this)"
+
+/datum/keybinding/admin/invisimin/down(client/user)
+ user.invisimin()
+ return TRUE
+
+/datum/keybinding/admin/deadsay
+ hotkey_keys = list("F10")
+ name = "dsay"
+ full_name = "deadsay"
+ description = "Allows you to send a message to dead chat"
+
+/datum/keybinding/admin/deadsay/down(client/user)
+ user.get_dead_say()
+ return TRUE
+
+/datum/keybinding/admin/deadmin
+ hotkey_keys = list("Unbound")
+ name = "deadmin"
+ full_name = "Deadmin"
+ description = "Shed your admin powers"
+
+/datum/keybinding/admin/deadmin/down(client/user)
+ user.deadmin()
+ return TRUE
+
+/datum/keybinding/admin/readmin
+ hotkey_keys = list("Unbound")
+ name = "readmin"
+ full_name = "Readmin"
+ description = "Regain your admin powers"
+
+/datum/keybinding/admin/readmin/down(client/user)
+ user.readmin()
+ return TRUE
diff --git a/code/modules/keybindings/keybind/carbon.dm b/code/modules/keybindings/keybind/carbon.dm
new file mode 100644
index 0000000000..46cb5cd0ac
--- /dev/null
+++ b/code/modules/keybindings/keybind/carbon.dm
@@ -0,0 +1,62 @@
+/datum/keybinding/carbon
+ category = CATEGORY_CARBON
+ weight = WEIGHT_MOB
+
+/datum/keybinding/carbon/can_use(client/user)
+ return iscarbon(user.mob)
+
+/datum/keybinding/carbon/toggle_throw_mode
+ hotkey_keys = list("R", "Southwest") // END
+ name = "toggle_throw_mode"
+ full_name = "Toggle throw mode"
+ description = "Toggle throwing the current item or not."
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/toggle_throw_mode/down(client/user)
+ var/mob/living/carbon/C = user.mob
+ C.toggle_throw_mode()
+ return TRUE
+
+/datum/keybinding/carbon/select_help_intent
+ hotkey_keys = list("1")
+ name = "select_help_intent"
+ full_name = "Select help intent"
+ description = ""
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/select_help_intent/down(client/user)
+ user.mob?.a_intent_change(INTENT_HELP)
+ return TRUE
+
+/datum/keybinding/carbon/select_disarm_intent
+ hotkey_keys = list("2")
+ name = "select_disarm_intent"
+ full_name = "Select disarm intent"
+ description = ""
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/select_disarm_intent/down(client/user)
+ user.mob?.a_intent_change(INTENT_DISARM)
+ return TRUE
+
+/datum/keybinding/carbon/select_grab_intent
+ hotkey_keys = list("3")
+ name = "select_grab_intent"
+ full_name = "Select grab intent"
+ description = ""
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/select_grab_intent/down(client/user)
+ user.mob?.a_intent_change(INTENT_GRAB)
+ return TRUE
+
+/datum/keybinding/carbon/select_harm_intent
+ hotkey_keys = list("4")
+ name = "select_harm_intent"
+ full_name = "Select harm intent"
+ description = ""
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/select_harm_intent/down(client/user)
+ user.mob?.a_intent_change(INTENT_HARM)
+ return TRUE
diff --git a/code/modules/keybindings/keybind/client.dm b/code/modules/keybindings/keybind/client.dm
new file mode 100644
index 0000000000..1d44efa063
--- /dev/null
+++ b/code/modules/keybindings/keybind/client.dm
@@ -0,0 +1,33 @@
+/datum/keybinding/client
+ category = CATEGORY_CLIENT
+ weight = WEIGHT_HIGHEST
+
+/datum/keybinding/client/admin_help
+ hotkey_keys = list("F1")
+ name = "admin_help"
+ full_name = "Admin Help"
+ description = "Ask an admin for help."
+
+/datum/keybinding/client/admin_help/down(client/user)
+ user.get_adminhelp()
+ return TRUE
+
+/datum/keybinding/client/screenshot
+ hotkey_keys = list("F2")
+ name = "screenshot"
+ full_name = "Screenshot"
+ description = "Take a screenshot."
+
+/datum/keybinding/client/screenshot/down(client/user)
+ winset(user, null, "command=.screenshot [!user.keys_held["shift"] ? "auto" : ""]")
+ return TRUE
+
+/datum/keybinding/client/minimal_hud
+ hotkey_keys = list("F12")
+ name = "minimal_hud"
+ full_name = "Minimal HUD"
+ description = "Hide most HUD features"
+
+/datum/keybinding/client/minimal_hud/down(client/user)
+ user.mob.button_pressed_F12()
+ return TRUE
diff --git a/code/modules/keybindings/keybind/emote.dm b/code/modules/keybindings/keybind/emote.dm
new file mode 100644
index 0000000000..6099826819
--- /dev/null
+++ b/code/modules/keybindings/keybind/emote.dm
@@ -0,0 +1,15 @@
+/datum/keybinding/emote
+ category = CATEGORY_EMOTE
+ weight = WEIGHT_EMOTE
+ var/emote_key
+
+/datum/keybinding/emote/proc/link_to_emote(datum/emote/faketype)
+ hotkey_keys = list("Unbound")
+ emote_key = initial(faketype.key)
+ name = initial(faketype.key)
+ full_name = capitalize(initial(faketype.key))
+ description = "Do the emote '*[emote_key]'"
+
+/datum/keybinding/emote/down(client/user)
+ . = ..()
+ return user.mob.emote(emote_key, intentional=TRUE)
diff --git a/code/modules/keybindings/keybind/human.dm b/code/modules/keybindings/keybind/human.dm
new file mode 100644
index 0000000000..43f5653472
--- /dev/null
+++ b/code/modules/keybindings/keybind/human.dm
@@ -0,0 +1,39 @@
+/datum/keybinding/human
+ category = CATEGORY_HUMAN
+ weight = WEIGHT_MOB
+
+/datum/keybinding/human/can_use(client/user)
+ return ishuman(user.mob)
+
+/datum/keybinding/human/quick_equip
+ hotkey_keys = list("E")
+ name = "quick_equip"
+ full_name = "Quick Equip"
+ description = "Quickly puts an item in the best slot available"
+
+/datum/keybinding/human/quick_equip/down(client/user)
+ var/mob/living/carbon/human/H = user.mob
+ H.quick_equip()
+ return TRUE
+
+/datum/keybinding/human/quick_equipbelt
+ hotkey_keys = list("ShiftE")
+ name = "quick_equipbelt"
+ full_name = "Quick equip belt"
+ description = "Put held thing in belt or take out most recent thing from belt"
+
+/datum/keybinding/human/quick_equipbelt/down(client/user)
+ var/mob/living/carbon/human/H = user.mob
+ H.smart_equipbelt()
+ return TRUE
+
+/datum/keybinding/human/bag_equip
+ hotkey_keys = list("ShiftB")
+ name = "bag_equip"
+ full_name = "Bag equip"
+ description = "Put held thing in backpack or take out most recent thing from backpack"
+
+/datum/keybinding/human/bag_equip/down(client/user)
+ var/mob/living/carbon/human/H = user.mob
+ H.smart_equipbag()
+ return TRUE
diff --git a/code/modules/keybindings/keybind/living.dm b/code/modules/keybindings/keybind/living.dm
new file mode 100644
index 0000000000..0408d0889b
--- /dev/null
+++ b/code/modules/keybindings/keybind/living.dm
@@ -0,0 +1,41 @@
+/datum/keybinding/living
+ category = CATEGORY_HUMAN
+ weight = WEIGHT_MOB
+
+/datum/keybinding/living/can_use(client/user)
+ return isliving(user.mob)
+
+/datum/keybinding/living/resist
+ hotkey_keys = list("B")
+ name = "resist"
+ full_name = "Resist"
+ description = "Break free of your current state. Handcuffed? on fire? Resist!"
+
+/datum/keybinding/living/resist/down(client/user)
+ var/mob/living/L = user.mob
+ L.resist()
+ return TRUE
+
+/datum/keybinding/living/toggle_combat_mode
+ hotkey_keys = list("C")
+ name = "toggle_combat_mode"
+ full_name = "Toggle combat mode"
+ description = "Toggles whether or not you're in combat mode."
+
+/datum/keybinding/living/toggle_combat_mode/can_use(client/user)
+ return iscarbon(user.mob) // for now, only carbons should be using combat mode, although all livings have combat mode implemented.
+
+/datum/keybinding/living/toggle_combat_mode/down(client/user)
+ var/mob/living/carbon/C = user.mob
+ C.user_toggle_intentional_combat_mode()
+ return TRUE
+
+/datum/keybinding/living/toggle_resting
+ hotkey_keys = list("V")
+ name = "toggle_resting"
+ full_name = "Toggle Resting"
+ description = "Toggles whether or not you are intentionally laying down."
+
+/datum/keybinding/living/toggle_resting/down(client/user)
+ var/mob/living/L = user.mob
+ L.lay_down()
diff --git a/code/modules/keybindings/keybind/mob.dm b/code/modules/keybindings/keybind/mob.dm
new file mode 100644
index 0000000000..083d4a19fd
--- /dev/null
+++ b/code/modules/keybindings/keybind/mob.dm
@@ -0,0 +1,123 @@
+/datum/keybinding/mob
+ category = CATEGORY_HUMAN
+ weight = WEIGHT_MOB
+
+/datum/keybinding/mob/stop_pulling
+ hotkey_keys = list("H", "Delete")
+ name = "stop_pulling"
+ full_name = "Stop pulling"
+ description = ""
+
+/datum/keybinding/mob/stop_pulling/down(client/user)
+ var/mob/M = user.mob
+ if(!M.pulling)
+ to_chat(user, "You are not pulling anything. ")
+ else
+ M.stop_pulling()
+ return TRUE
+
+/datum/keybinding/mob/cycle_intent_right
+ hotkey_keys = list("Northwest", "F") // HOME
+ name = "cycle_intent_right"
+ full_name = "Cycle Action Intent Right"
+ description = ""
+
+/datum/keybinding/mob/cycle_intent_right/down(client/user)
+ var/mob/M = user.mob
+ M.a_intent_change(INTENT_HOTKEY_RIGHT)
+ return TRUE
+
+/datum/keybinding/mob/cycle_intent_left
+ hotkey_keys = list("Insert", "G")
+ name = "cycle_intent_left"
+ full_name = "Cycle Action Intent Left"
+ description = ""
+
+/datum/keybinding/mob/cycle_intent_left/down(client/user)
+ var/mob/M = user.mob
+ M.a_intent_change(INTENT_HOTKEY_LEFT)
+ return TRUE
+
+/datum/keybinding/mob/swap_hands
+ hotkey_keys = list("X", "Northeast") // PAGEUP
+ name = "swap_hands"
+ full_name = "Swap hands"
+ description = ""
+
+/datum/keybinding/mob/swap_hands/down(client/user)
+ var/mob/M = user.mob
+ M.swap_hand()
+ return TRUE
+
+/datum/keybinding/mob/activate_inhand
+ hotkey_keys = list("Z", "Southeast") // PAGEDOWN
+ name = "activate_inhand"
+ full_name = "Activate in-hand"
+ description = "Uses whatever item you have inhand"
+
+/datum/keybinding/mob/activate_inhand/down(client/user)
+ var/mob/M = user.mob
+ M.mode()
+ return TRUE
+
+/datum/keybinding/mob/drop_item
+ hotkey_keys = list("Q")
+ name = "drop_item"
+ full_name = "Drop Item"
+ description = ""
+
+/datum/keybinding/mob/drop_item/down(client/user)
+ if(iscyborg(user.mob)) //cyborgs can't drop items
+ return FALSE
+ var/mob/M = user.mob
+ var/obj/item/I = M.get_active_held_item()
+ if(!I)
+ to_chat(user, "You have nothing to drop in your hand! ")
+ else
+ user.mob.dropItemToGround(I)
+ return TRUE
+
+/datum/keybinding/mob/say_with_indicator
+ hotkey_keys = list("CtrlT")
+ classic_keys = list()
+ name = "say_with_indicator"
+ full_name = "Say with Typing Indicator"
+
+/datum/keybinding/mob/say_with_indicator/down(client/user)
+ var/mob/M = user.mob
+ M.say_typing_indicator()
+ return TRUE
+
+/datum/keybinding/mob/me_with_indicator
+ hotkey_keys = list("CtrlM")
+ classic_keys = list()
+ name = "me_with_indicator"
+ full_name = "Me (emote) with Typing Indicator"
+
+/datum/keybinding/mob/me_with_indicator/down(client/user)
+ var/mob/M = user.mob
+ M.me_typing_indicator()
+ return TRUE
+
+/datum/keybinding/living/subtle
+ hotkey_keys = list("5")
+ classic_keys = list()
+ name = "subtle_emote"
+ full_name = "Subtle Emote"
+
+/datum/keybinding/living/subtle/down(client/user)
+ var/mob/living/L = user.mob
+ L.subtle_keybind()
+ return TRUE
+
+/datum/keybinding/mob/whisper
+ hotkey_keys = list("Y")
+ classic_keys = list()
+ name = "whisper"
+ full_name = "Whisper"
+
+/datum/keybinding/mob/whisper/down(client/user)
+ var/mob/M = user.mob
+ M.whisper_keybind()
+ return TRUE
+
diff --git a/code/modules/keybindings/keybind/movement.dm b/code/modules/keybindings/keybind/movement.dm
new file mode 100644
index 0000000000..23691539e9
--- /dev/null
+++ b/code/modules/keybindings/keybind/movement.dm
@@ -0,0 +1,175 @@
+/datum/keybinding/movement
+ category = CATEGORY_MOVEMENT
+ weight = WEIGHT_HIGHEST
+
+/datum/keybinding/movement/north
+ hotkey_keys = list("W", "North")
+ name = "North"
+ full_name = "Move North"
+ description = "Moves your character north"
+
+/datum/keybinding/movement/south
+ hotkey_keys = list("S", "South")
+ name = "South"
+ full_name = "Move South"
+ description = "Moves your character south"
+
+/datum/keybinding/movement/west
+ hotkey_keys = list("A", "West")
+ name = "West"
+ full_name = "Move West"
+ description = "Moves your character left"
+
+/datum/keybinding/movement/east
+ hotkey_keys = list("D", "East")
+ name = "East"
+ full_name = "Move East"
+ description = "Moves your character east"
+
+/datum/keybinding/mob/face_north
+ hotkey_keys = list("CtrlW", "CtrlNorth")
+ name = "face_north"
+ full_name = "Face North"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/face_north/down(client/user)
+ var/mob/M = user.mob
+ M.northface()
+ return TRUE
+
+/datum/keybinding/mob/face_east
+ hotkey_keys = list("CtrlD", "CtrlEast")
+ name = "face_east"
+ full_name = "Face East"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/face_east/down(client/user)
+ var/mob/M = user.mob
+ M.eastface()
+ return TRUE
+
+/datum/keybinding/mob/face_south
+ hotkey_keys = list("CtrlS", "CtrlSouth")
+ name = "face_south"
+ full_name = "Face South"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/face_south/down(client/user)
+ var/mob/M = user.mob
+ M.southface()
+ return TRUE
+
+/datum/keybinding/mob/shift_north
+ hotkey_keys = list("CtrlShiftW", "CtrlShiftNorth")
+ name = "pixel_shift_north"
+ full_name = "Pixel Shift North"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/shift_north/down(client/user)
+ var/mob/M = user.mob
+ M.northshift()
+ return TRUE
+
+/datum/keybinding/mob/shift_east
+ hotkey_keys = list("CtrlShiftD", "CtrlShiftEast")
+ name = "pixel_shift_east"
+ full_name = "Pixel Shift East"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/shift_east/down(client/user)
+ var/mob/M = user.mob
+ M.eastshift()
+ return TRUE
+
+/datum/keybinding/mob/shift_south
+ hotkey_keys = list("CtrlShiftS", "CtrlShiftSouth")
+ name = "pixel_shift_south"
+ full_name = "Pixel Shift South"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/shift_south/down(client/user)
+ var/mob/M = user.mob
+ M.southshift()
+ return TRUE
+
+/datum/keybinding/mob/shift_west
+ hotkey_keys = list("CtrlShiftA", "CtrlShiftWest")
+ name = "pixel_shift_west"
+ full_name = "Pixel Shift West"
+ description = ""
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/shift_west/down(client/user)
+ var/mob/M = user.mob
+ M.westshift()
+ return TRUE
+
+/datum/keybinding/living/hold_sprint
+ hotkey_keys = list("Shift")
+ name = "hold_sprint"
+ full_name = "Sprint (hold down)"
+ description = "Hold down to sprint"
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/living/hold_sprint/can_use(client/user)
+ return ishuman(user.mob) || iscyborg(user.mob)
+
+/datum/keybinding/living/hold_sprint/down(client/user)
+ var/mob/living/L = user.mob
+ L.sprint_hotkey(TRUE)
+ return TRUE
+
+/datum/keybinding/living/hold_sprint/up(client/user)
+ var/mob/living/L = user.mob
+ L.sprint_hotkey(FALSE)
+ return TRUE
+
+/datum/keybinding/living/toggle_sprint
+ hotkey_keys = list()
+ name = "toggle_sprint"
+ full_name = "Sprint (toggle)"
+ description = "Press to toggle sprint"
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/living/toggle_sprint/can_use(client/user)
+ return ishuman(user.mob) || iscyborg(user.mob)
+
+/datum/keybinding/living/toggle_sprint/down(client/user)
+ var/mob/living/L = user.mob
+ L.default_toggle_sprint(TRUE)
+ return TRUE
+
+/datum/keybinding/mob/toggle_move_intent
+ hotkey_keys = list("Alt")
+ name = "toggle_move_intent"
+ full_name = "Hold to toggle move intent"
+ description = "Held down to cycle to the other move intent, release to cycle back"
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/toggle_move_intent/down(client/user)
+ var/mob/M = user.mob
+ M.toggle_move_intent()
+ return TRUE
+
+/datum/keybinding/mob/toggle_move_intent/up(client/user)
+ var/mob/M = user.mob
+ M.toggle_move_intent()
+ return TRUE
+
+/datum/keybinding/mob/toggle_move_intent_alternative
+ hotkey_keys = list("Unbound")
+ name = "toggle_move_intent_alt"
+ full_name = "press to cycle move intent"
+ description = "Pressing this cycle to the opposite move intent, does not cycle back"
+ category = CATEGORY_MOVEMENT
+
+/datum/keybinding/mob/toggle_move_intent_alternative/down(client/user)
+ var/mob/M = user.mob
+ M.toggle_move_intent()
+ return TRUE
diff --git a/code/modules/keybindings/keybind/robot.dm b/code/modules/keybindings/keybind/robot.dm
new file mode 100644
index 0000000000..b9f6ce83eb
--- /dev/null
+++ b/code/modules/keybindings/keybind/robot.dm
@@ -0,0 +1,61 @@
+/datum/keybinding/robot
+ category = CATEGORY_ROBOT
+ weight = WEIGHT_ROBOT
+
+/datum/keybinding/robot/can_use(client/user)
+ return iscyborg(user.mob)
+
+/datum/keybinding/robot/moduleone
+ hotkey_keys = list("1")
+ name = "module_one"
+ full_name = "Toggle module 1"
+ description = "Equips or unequips the first module"
+
+/datum/keybinding/robot/moduleone/down(client/user)
+ var/mob/living/silicon/robot/R = user.mob
+ R.toggle_module(1)
+ return TRUE
+
+/datum/keybinding/robot/moduletwo
+ hotkey_keys = list("2")
+ name = "module_two"
+ full_name = "Toggle module 2"
+ description = "Equips or unequips the second module"
+
+/datum/keybinding/robot/moduletwo/down(client/user)
+ var/mob/living/silicon/robot/R = user.mob
+ R.toggle_module(2)
+ return TRUE
+
+/datum/keybinding/robot/modulethree
+ hotkey_keys = list("3")
+ name = "module_three"
+ full_name = "Toggle module 3"
+ description = "Equips or unequips the third module"
+
+/datum/keybinding/robot/modulethree/down(client/user)
+ var/mob/living/silicon/robot/R = user.mob
+ R.toggle_module(3)
+ return TRUE
+
+/datum/keybinding/robot/intent_cycle
+ hotkey_keys = list("4")
+ name = "cycle_intent"
+ full_name = "Cycle intent left"
+ description = "Cycles the intent left"
+
+/datum/keybinding/robot/intent_cycle/down(client/user)
+ var/mob/living/silicon/robot/R = user.mob
+ R.a_intent_change(INTENT_HOTKEY_LEFT)
+ return TRUE
+
+/datum/keybinding/robot/unequip_module
+ hotkey_keys = list("Q")
+ name = "unequip_module"
+ full_name = "Unequip module"
+ description = "Unequips the active module"
+
+/datum/keybinding/robot/unequip_module/down(client/user)
+ var/mob/living/silicon/robot/R = user.mob
+ R.uneq_active()
+ return TRUE
diff --git a/code/modules/keybindings/keybind/targeting.dm b/code/modules/keybindings/keybind/targeting.dm
new file mode 100644
index 0000000000..1548d768cf
--- /dev/null
+++ b/code/modules/keybindings/keybind/targeting.dm
@@ -0,0 +1,76 @@
+/datum/keybinding/mob/target_head_cycle
+ hotkey_keys = list("Numpad8")
+ name = "target_head_cycle"
+ full_name = "Target: Cycle head"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_head_cycle/down(client/user)
+ user.body_toggle_head()
+ return TRUE
+
+/datum/keybinding/mob/target_r_arm
+ hotkey_keys = list("Numpad4")
+ name = "target_r_arm"
+ full_name = "Target: right arm"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_r_arm/down(client/user)
+ user.body_r_arm()
+ return TRUE
+
+/datum/keybinding/mob/target_body_chest
+ hotkey_keys = list("Numpad5")
+ name = "target_body_chest"
+ full_name = "Target: Body"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_body_chest/down(client/user)
+ user.body_chest()
+ return TRUE
+
+/datum/keybinding/mob/target_left_arm
+ hotkey_keys = list("Numpad6")
+ name = "target_left_arm"
+ full_name = "Target: left arm"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_left_arm/down(client/user)
+ user.body_l_arm()
+ return TRUE
+
+/datum/keybinding/mob/target_right_leg
+ hotkey_keys = list("Numpad1")
+ name = "target_right_leg"
+ full_name = "Target: Right leg"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_right_leg/down(client/user)
+ user.body_r_leg()
+ return TRUE
+
+/datum/keybinding/mob/target_body_groin
+ hotkey_keys = list("Numpad2")
+ name = "target_body_groin"
+ full_name = "Target: Groin"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_body_groin/down(client/user)
+ user.body_groin()
+ return TRUE
+
+/datum/keybinding/mob/target_left_leg
+ hotkey_keys = list("Numpad3")
+ name = "target_left_leg"
+ full_name = "Target: left leg"
+ description = ""
+ category = CATEGORY_TARGETING
+
+/datum/keybinding/mob/target_left_leg/down(client/user)
+ user.body_l_leg()
+ return TRUE
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index 9a13bfb11c..a53820b5d2 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -1,55 +1,47 @@
-/client
- /// 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
-
-// Set a client's focus to an object and override these procs on that object to let it handle keypresses
-
/datum/proc/key_down(key, client/user) // Called when a key is pressed down initially
return
+
/datum/proc/key_up(key, client/user) // Called when a key is released
return
+
/datum/proc/keyLoop(client/user) // Called once every frame
set waitfor = FALSE
return
// removes all the existing macros
/client/proc/erase_all_macros()
- var/list/macro_sets = params2list(winget(src, null, "macros"))
var/erase_output = ""
- for(var/i in 1 to macro_sets.len)
- var/setname = macro_sets[i]
- var/list/macro_set = params2list(winget(src, "[setname].*", "command")) // The third arg doesnt matter here as we're just removing them all
- for(var/k in 1 to macro_set.len)
- var/list/split_name = splittext(macro_set[k], ".")
- var/macro_name = "[split_name[1]].[split_name[2]]" // [3] is "command"
- erase_output = "[erase_output];[macro_name].parent=null"
+ var/list/macro_set = params2list(winget(src, "default.*", "command")) // The third arg doesnt matter here as we're just removing them all
+ for(var/k in 1 to length(macro_set))
+ var/list/split_name = splittext(macro_set[k], ".")
+ var/macro_name = "[split_name[1]].[split_name[2]]" // [3] is "command"
+ erase_output = "[erase_output];[macro_name].parent=null"
winset(src, null, erase_output)
-/client/proc/set_macros()
+/client/proc/apply_macro_set(name, list/macroset)
+ ASSERT(name)
+ ASSERT(islist(macroset))
+ winclone(src, "default", name)
+ for(var/i in 1 to length(macroset))
+ var/key = macroset[i]
+ var/command = macroset[key]
+ winset(src, "[name]-[REF(key)]", "parent=[name];name=[key];command=[command]")
+
+/client/proc/set_macros(datum/preferences/prefs_override = prefs)
set waitfor = FALSE
keys_held.Cut()
erase_all_macros()
- var/list/macro_sets = SSinput.macro_sets
- for(var/i in 1 to macro_sets.len)
- var/setname = macro_sets[i]
- if(setname != "default")
- winclone(src, "default", setname)
- var/list/macro_set = macro_sets[setname]
- for(var/k in 1 to macro_set.len)
- var/key = macro_set[k]
- var/command = macro_set[key]
- winset(src, "[setname]-[REF(key)]", "parent=[setname];name=[key];command=[command]")
+ apply_macro_set(SKIN_MACROSET_HOTKEYS, SSinput.macroset_hotkey)
+ apply_macro_set(SKIN_MACROSET_CLASSIC_HOTKEYS, SSinput.macroset_classic_hotkey)
+ apply_macro_set(SKIN_MACROSET_CLASSIC_INPUT, SSinput.macroset_classic_input)
- if(prefs.hotkeys)
- winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=default")
+ set_hotkeys_preference()
+
+/client/proc/set_hotkeys_preference(datum/preferences/prefs_override = prefs)
+ if(prefs_override.hotkeys)
+ winset(src, null, "map.focus=true input.background-color=[COLOR_INPUT_DISABLED] mainwindow.macro=[SKIN_MACROSET_HOTKEYS]")
else
- winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default")
+ winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=[SKIN_MACROSET_CLASSIC_INPUT]")
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 580b0aaaf0..8de143e2bd 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -277,3 +277,65 @@
qdel(slot)
for(var/obj/item/I in held_items)
qdel(I)
+
+/mob/living/carbon/human/proc/smart_equipbag() // take most recent item out of bag or place held item in bag
+ if(incapacitated())
+ return
+ var/obj/item/thing = get_active_held_item()
+ var/obj/item/equipped_back = get_item_by_slot(SLOT_BACK)
+ if(!equipped_back) // We also let you equip a backpack like this
+ if(!thing)
+ to_chat(src, "You have no backpack to take something out of! ")
+ return
+ if(equip_to_slot_if_possible(thing, ITEM_SLOT_BACK))
+ update_inv_hands()
+ return
+ if(!SEND_SIGNAL(equipped_back, COMSIG_CONTAINS_STORAGE)) // not a storage item
+ if(!thing)
+ equipped_back.attack_hand(src)
+ else
+ to_chat(src, "You can't fit anything in! ")
+ return
+ if(thing) // put thing in backpack
+ if(!SEND_SIGNAL(equipped_back, COMSIG_TRY_STORAGE_INSERT, thing, src))
+ to_chat(src, "You can't fit anything in! ")
+ return
+ if(!equipped_back.contents.len) // nothing to take out
+ to_chat(src, "There's nothing in your backpack to take out! ")
+ return
+ var/obj/item/stored = equipped_back.contents[equipped_back.contents.len]
+ if(!stored || stored.on_found(src))
+ return
+ stored.attack_hand(src) // take out thing from backpack
+ return
+
+/mob/living/carbon/human/proc/smart_equipbelt() // put held thing in belt or take most recent item out of belt
+ if(incapacitated())
+ return
+ var/obj/item/thing = get_active_held_item()
+ var/obj/item/equipped_belt = get_item_by_slot(SLOT_BELT)
+ if(!equipped_belt) // We also let you equip a belt like this
+ if(!thing)
+ to_chat(src, "You have no belt to take something out of! ")
+ return
+ if(equip_to_slot_if_possible(thing, ITEM_SLOT_BELT))
+ update_inv_hands()
+ return
+ if(!SEND_SIGNAL(equipped_belt, COMSIG_CONTAINS_STORAGE)) // not a storage item
+ if(!thing)
+ equipped_belt.attack_hand(src)
+ else
+ to_chat(src, "You can't fit anything in! ")
+ return
+ if(thing) // put thing in belt
+ if(!SEND_SIGNAL(equipped_belt, COMSIG_TRY_STORAGE_INSERT, thing, src))
+ to_chat(src, "You can't fit anything in! ")
+ return
+ if(!equipped_belt.contents.len) // nothing to take out
+ to_chat(src, "There's nothing in your belt to take out! ")
+ return
+ var/obj/item/stored = equipped_belt.contents[equipped_belt.contents.len]
+ if(!stored || stored.on_found(src))
+ return
+ stored.attack_hand(src) // take out thing from belt
+ return
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index e551316e25..9947c8ca1e 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -12,6 +12,9 @@
vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of mob in openspace.
+ /// What receives our keyboard input. src by default.
+ var/datum/focus
+
var/lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
var/datum/mind/mind
var/list/datum/action/actions = list()
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index ac89fc2445..e2b3aee35d 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -57,6 +57,12 @@
else
return ..()
+/mob/proc/whisper_keybind()
+ var/message = input(src, "", "whisper") as text|null
+ if(!length(message))
+ return
+ return whisper_verb(message)
+
/mob/verb/whisper_verb(message as text)
set name = "Whisper"
set category = "IC"
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index ec82b41cca..44dc3e2a05 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -129,6 +129,12 @@ proc/get_top_level_mob(var/mob/S)
user.visible_message(message=message,self_message=message,vision_distance=1, ignored_mobs = GLOB.dead_mob_list)
///////////////// VERB CODE
+/mob/living/proc/subtle_keybind()
+ var/message = input(src, "", "subtle") as text|null
+ if(!length(message))
+ return
+ return subtle(message)
+
/mob/living/verb/subtle()
set name = "Subtle"
set category = "IC"
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 04a78af061..8d68336754 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -54,6 +54,7 @@ window "mainwindow"
size = 640x440
anchor1 = none
anchor2 = none
+ background-color = #242424
is-default = true
saved-params = "pos;size;is-minimized;is-maximized"
icon = 'icons\\ss13_64.png'
@@ -61,60 +62,23 @@ window "mainwindow"
menu = "menu"
elem "split"
type = CHILD
- pos = 3,0
- size = 634x417
+ pos = 0,0
+ size = 637x440
anchor1 = 0,0
anchor2 = 100,100
+ background-color = #272727
saved-params = "splitter"
left = "mapwindow"
right = "infowindow"
is-vert = true
- elem "input"
- type = INPUT
- pos = 3,420
- size = 517x20
- anchor1 = 0,100
- anchor2 = 100,100
- background-color = #d3b5b5
- is-default = true
- border = sunken
- saved-params = "command"
- elem "saybutton"
- type = BUTTON
- pos = 600,420
- size = 40x20
- anchor1 = 100,100
- anchor2 = none
- saved-params = "is-checked"
- text = "Chat"
- command = ".winset \"saybutton.is-checked=true ? input.command=\"!say \\\"\" : input.command=\"\"saybutton.is-checked=true ? mebutton.is-checked=false\"\"saybutton.is-checked=true ? oocbutton.is-checked=false\""
- button-type = pushbox
- elem "oocbutton"
- type = BUTTON
- pos = 520,420
- size = 40x20
- anchor1 = 100,100
- anchor2 = none
- saved-params = "is-checked"
- text = "OOC"
- command = ".winset \"oocbutton.is-checked=true ? input.command=\"!ooc \\\"\" : input.command=\"\"oocbutton.is-checked=true ? mebutton.is-checked=false\"\"oocbutton.is-checked=true ? saybutton.is-checked=false\""
- button-type = pushbox
- elem "mebutton"
- type = BUTTON
- pos = 560,420
- size = 40x20
- anchor1 = 100,100
- anchor2 = none
- saved-params = "is-checked"
- text = "Me"
- command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\""
- button-type = pushbox
+ splitter = 75
elem "asset_cache_browser"
type = BROWSER
pos = 0,0
size = 200x200
anchor1 = none
anchor2 = none
+ background-color = #272727
is-visible = false
saved-params = ""
elem "tooltip"
@@ -123,6 +87,7 @@ window "mainwindow"
size = 999x999
anchor1 = none
anchor2 = none
+ background-color = #272727
is-visible = false
saved-params = ""
@@ -134,11 +99,11 @@ window "mapwindow"
anchor1 = none
anchor2 = none
background-color = none
- saved-params = ""
+ saved-params = "pos;size;is-minimized;is-maximized"
statusbar = false
is-pane = true
- outer-size = 1090x1192
- inner-size = 1068x1136
+ outer-size = 684x617
+ inner-size = 662x561
elem "map"
type = MAP
pos = 0,0
@@ -159,6 +124,7 @@ window "infowindow"
size = 640x480
anchor1 = none
anchor2 = none
+ background-color = #242424
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "info"
@@ -167,6 +133,7 @@ window "infowindow"
size = 640x445
anchor1 = 0,0
anchor2 = 100,100
+ background-color = #272727
saved-params = "splitter"
left = "statwindow"
right = "outputwindow"
@@ -177,6 +144,8 @@ window "infowindow"
size = 104x20
anchor1 = 3,0
anchor2 = 19,0
+ text-color = #e0e0e0
+ background-color = #40628a
saved-params = "is-checked"
text = "Changelog"
command = "changelog"
@@ -186,6 +155,8 @@ window "infowindow"
size = 100x20
anchor1 = 19,0
anchor2 = 34,0
+ text-color = #e0e0e0
+ background-color = #40628a
saved-params = "is-checked"
text = "Rules"
command = "rules"
@@ -195,6 +166,8 @@ window "infowindow"
size = 100x20
anchor1 = 34,0
anchor2 = 50,0
+ text-color = #e0e0e0
+ background-color = #40628a
saved-params = "is-checked"
text = "Wiki"
command = "wiki"
@@ -204,6 +177,8 @@ window "infowindow"
size = 100x20
anchor1 = 50,0
anchor2 = 66,0
+ text-color = #e0e0e0
+ background-color = #40628a
saved-params = "is-checked"
text = "Forum"
command = "forum"
@@ -213,8 +188,10 @@ window "infowindow"
size = 100x20
anchor1 = 66,0
anchor2 = 81,0
+ text-color = #e0e0e0
+ background-color = #40628a
saved-params = "is-checked"
- text = "Github"
+ text = "GitHub"
command = "github"
elem "report-issue"
type = BUTTON
@@ -222,6 +199,9 @@ window "infowindow"
size = 100x20
anchor1 = 81,0
anchor2 = 97,0
+ font-size = 8
+ text-color = #e0e0e0
+ background-color = #a92c2c
saved-params = "is-checked"
text = "Report Issue"
command = "report-issue"
@@ -233,15 +213,44 @@ window "outputwindow"
size = 640x480
anchor1 = none
anchor2 = none
+ background-color = #272727
saved-params = "pos;size;is-minimized;is-maximized"
+ titlebar = false
+ statusbar = false
+ can-close = false
+ can-minimize = false
+ can-resize = false
is-pane = true
+ elem "input"
+ type = INPUT
+ pos = 2,460
+ size = 595x20
+ anchor1 = 0,100
+ anchor2 = 100,100
+ background-color = #d3b5b5
+ is-default = true
+ border = sunken
+ saved-params = "command"
+ elem "say"
+ type = BUTTON
+ pos = 600,460
+ size = 37x20
+ anchor1 = 100,100
+ anchor2 = none
+ text-color = #e0e0e0
+ background-color = #272727
+ saved-params = "is-checked"
+ text = "Chat"
+ command = ".winset \"say.is-checked=true ? input.command=\"!say \\\"\" : input.command=\""
+ is-flat = true
+ button-type = pushbox
elem "browseroutput"
type = BROWSER
pos = 0,0
- size = 640x480
+ size = 640x456
anchor1 = 0,0
anchor2 = 100,100
- background-color = #ffffff
+ background-color = #272727
is-visible = false
is-disabled = true
saved-params = ""
@@ -249,19 +258,22 @@ window "outputwindow"
elem "output"
type = OUTPUT
pos = 0,0
- size = 640x480
+ size = 640x456
anchor1 = 0,0
anchor2 = 100,100
+ text-color = #e0e0e0
+ background-color = #272727
is-default = true
saved-params = ""
window "statwindow"
elem "statwindow"
type = MAIN
- pos = 0,0
+ pos = 281,0
size = 640x480
anchor1 = none
anchor2 = none
+ background-color = #242424
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "stat"
@@ -270,8 +282,14 @@ window "statwindow"
size = 640x480
anchor1 = 0,0
anchor2 = 100,100
+ text-color = #e0e0e0
+ background-color = #272727
is-default = true
saved-params = ""
+ tab-text-color = #e0e0e0
+ tab-background-color = #242424
+ prefix-color = #e0e0e0
+ suffix-color = #e0e0e0
window "preferences_window"
elem "preferences_window"
diff --git a/modular_citadel/interface/skin.dmf b/modular_citadel/interface/skin.dmf
deleted file mode 100644
index ec9a010128..0000000000
--- a/modular_citadel/interface/skin.dmf
+++ /dev/null
@@ -1,319 +0,0 @@
-macro "default"
-
-
-menu "menu"
- elem
- name = "&File"
- command = ""
- saved-params = "is-checked"
- elem
- name = "&Quick screenshot\tF2"
- command = ".screenshot auto"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = "&Save screenshot as...\tShift+F2"
- command = ".screenshot"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = ""
- command = ""
- category = "&File"
- saved-params = "is-checked"
- elem "reconnectbutton"
- name = "&Reconnect"
- command = ".reconnect"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = "&Quit\tAlt-F4"
- command = ".quit"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = "&Help"
- command = ""
- saved-params = "is-checked"
- elem
- name = "&Admin Help\tF1"
- command = "adminhelp"
- category = "&Help"
- saved-params = "is-checked"
- elem
- name = "&Hotkeys"
- command = "hotkeys-help"
- category = "&Help"
- saved-params = "is-checked"
-
-
-window "mainwindow"
- elem "mainwindow"
- type = MAIN
- pos = 0,0
- size = 640x440
- anchor1 = none
- anchor2 = none
- background-color = #242424
- is-default = true
- saved-params = "pos;size;is-minimized;is-maximized"
- icon = 'icons\\ss13_64.png'
- macro = "default"
- menu = "menu"
- elem "split"
- type = CHILD
- pos = 0,0
- size = 637x440
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #272727
- saved-params = "splitter"
- left = "mapwindow"
- right = "infowindow"
- is-vert = true
- splitter = 75
- elem "asset_cache_browser"
- type = BROWSER
- pos = 0,0
- size = 200x200
- anchor1 = none
- anchor2 = none
- background-color = #272727
- is-visible = false
- saved-params = ""
- elem "tooltip"
- type = BROWSER
- pos = 0,0
- size = 999x999
- anchor1 = none
- anchor2 = none
- background-color = #272727
- is-visible = false
- saved-params = ""
-
-window "mapwindow"
- elem "mapwindow"
- type = MAIN
- pos = 418,0
- size = 1024x1024
- anchor1 = none
- anchor2 = none
- background-color = none
- saved-params = "pos;size;is-minimized;is-maximized"
- statusbar = false
- is-pane = true
- outer-size = 684x617
- inner-size = 662x561
- elem "map"
- type = MAP
- pos = 0,0
- size = 1024x1024
- anchor1 = 0,0
- anchor2 = 100,100
- font-family = "Arial"
- font-size = 7
- is-default = true
- saved-params = "icon-size"
- zoom-mode = distort
- style = ".center { text-align: center; }\n.maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; }\n.command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; }\n.big { font-size: 8px; }\n.reallybig { font-size: 8px; }\n.extremelybig { font-size: 8px; }\n.greentext { color: #00ff00; font-size: 7px; }\n.redtext { color: #ff0000; font-size: 7px; }\n.clown { color: #ff69bf; font-size: 7px; font-weight: bold; }\n.his_grace { color: #15d512; }\n.hypnophrase { color: #0d0d0d; font-weight: bold; }\n.yell { font-weight: bold; }\n.italics { font-size: 6px; }"
-
-window "infowindow"
- elem "infowindow"
- type = MAIN
- pos = 0,0
- size = 640x480
- anchor1 = none
- anchor2 = none
- background-color = #242424
- saved-params = "pos;size;is-minimized;is-maximized"
- is-pane = true
- elem "info"
- type = CHILD
- pos = 0,30
- size = 640x445
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #272727
- saved-params = "splitter"
- left = "statwindow"
- right = "outputwindow"
- is-vert = false
- elem "changelog"
- type = BUTTON
- pos = 16,5
- size = 104x20
- anchor1 = 3,0
- anchor2 = 19,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Changelog"
- command = "changelog"
- elem "rules"
- type = BUTTON
- pos = 120,5
- size = 100x20
- anchor1 = 19,0
- anchor2 = 34,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Rules"
- command = "rules"
- elem "wiki"
- type = BUTTON
- pos = 220,5
- size = 100x20
- anchor1 = 34,0
- anchor2 = 50,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Wiki"
- command = "wiki"
- elem "forum"
- type = BUTTON
- pos = 320,5
- size = 100x20
- anchor1 = 50,0
- anchor2 = 66,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Forum"
- command = "forum"
- elem "github"
- type = BUTTON
- pos = 420,5
- size = 100x20
- anchor1 = 66,0
- anchor2 = 81,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "GitHub"
- command = "github"
- elem "report-issue"
- type = BUTTON
- pos = 520,5
- size = 100x20
- anchor1 = 81,0
- anchor2 = 97,0
- font-size = 8
- text-color = #e0e0e0
- background-color = #a92c2c
- saved-params = "is-checked"
- text = "Report Issue"
- command = "report-issue"
-
-window "outputwindow"
- elem "outputwindow"
- type = MAIN
- pos = 0,0
- size = 640x480
- anchor1 = none
- anchor2 = none
- background-color = #272727
- saved-params = "pos;size;is-minimized;is-maximized"
- titlebar = false
- statusbar = false
- can-close = false
- can-minimize = false
- can-resize = false
- is-pane = true
- elem "input"
- type = INPUT
- pos = 2,460
- size = 595x20
- anchor1 = 0,100
- anchor2 = 100,100
- background-color = #d3b5b5
- is-default = true
- border = sunken
- saved-params = "command"
- elem "say"
- type = BUTTON
- pos = 600,460
- size = 37x20
- anchor1 = 100,100
- anchor2 = none
- text-color = #e0e0e0
- background-color = #272727
- saved-params = "is-checked"
- text = "Chat"
- command = ".winset \"say.is-checked=true ? input.command=\"!say \\\"\" : input.command=\""
- is-flat = true
- button-type = pushbox
- elem "browseroutput"
- type = BROWSER
- pos = 0,0
- size = 640x456
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #272727
- is-visible = false
- is-disabled = true
- saved-params = ""
- auto-format = false
- elem "output"
- type = OUTPUT
- pos = 0,0
- size = 640x456
- anchor1 = 0,0
- anchor2 = 100,100
- text-color = #e0e0e0
- background-color = #272727
- is-default = true
- saved-params = ""
-
-window "statwindow"
- elem "statwindow"
- type = MAIN
- pos = 281,0
- size = 640x480
- anchor1 = none
- anchor2 = none
- background-color = #242424
- saved-params = "pos;size;is-minimized;is-maximized"
- is-pane = true
- elem "stat"
- type = INFO
- pos = 0,0
- size = 640x480
- anchor1 = 0,0
- anchor2 = 100,100
- text-color = #e0e0e0
- background-color = #272727
- is-default = true
- saved-params = ""
- tab-text-color = #e0e0e0
- tab-background-color = #242424
- prefix-color = #e0e0e0
- suffix-color = #e0e0e0
-
-window "preferences_window"
- elem "preferences_window"
- type = MAIN
- pos = 372,0
- size = 1280x1000
- anchor1 = none
- anchor2 = none
- is-visible = false
- saved-params = "pos;size;is-minimized;is-maximized"
- statusbar = false
- elem "preferences_browser"
- type = BROWSER
- pos = 0,0
- size = 960x1000
- anchor1 = 0,0
- anchor2 = 75,100
- saved-params = ""
- elem "character_preview_map"
- type = MAP
- pos = 960,0
- size = 320x1000
- anchor1 = 75,0
- anchor2 = 100,100
- right-click = true
- saved-params = "zoom;letterbox;zoom-mode"
-
diff --git a/tgstation.dme b/tgstation.dme
index c39fb47815..0d14ab1254 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -120,6 +120,7 @@
#include "code\__DEFINES\vv.dm"
#include "code\__DEFINES\wall_dents.dm"
#include "code\__DEFINES\wires.dm"
+#include "code\__DEFINES\admin\keybindings.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\signals.dm"
@@ -186,7 +187,9 @@
#include "code\_globalvars\misc.dm"
#include "code\_globalvars\regexes.dm"
#include "code\_globalvars\traits.dm"
+#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
+#include "code\_globalvars\lists\keybindings.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
#include "code\_globalvars\lists\mapping.dm"
#include "code\_globalvars\lists\medals.dm"
@@ -2139,16 +2142,22 @@
#include "code\modules\jobs\job_types\virologist.dm"
#include "code\modules\jobs\job_types\warden.dm"
#include "code\modules\jobs\map_changes\map_changes.dm"
-#include "code\modules\keybindings\bindings_admin.dm"
#include "code\modules\keybindings\bindings_atom.dm"
-#include "code\modules\keybindings\bindings_carbon.dm"
#include "code\modules\keybindings\bindings_client.dm"
-#include "code\modules\keybindings\bindings_human.dm"
-#include "code\modules\keybindings\bindings_living.dm"
-#include "code\modules\keybindings\bindings_mob.dm"
-#include "code\modules\keybindings\bindings_robot.dm"
#include "code\modules\keybindings\focus.dm"
#include "code\modules\keybindings\setup.dm"
+#include "code\modules\keybindings\keybind\__defines.dm"
+#include "code\modules\keybindings\keybind\_keybind.dm"
+#include "code\modules\keybindings\keybind\admin.dm"
+#include "code\modules\keybindings\keybind\carbon.dm"
+#include "code\modules\keybindings\keybind\client.dm"
+#include "code\modules\keybindings\keybind\emote.dm"
+#include "code\modules\keybindings\keybind\human.dm"
+#include "code\modules\keybindings\keybind\living.dm"
+#include "code\modules\keybindings\keybind\mob.dm"
+#include "code\modules\keybindings\keybind\movement.dm"
+#include "code\modules\keybindings\keybind\robot.dm"
+#include "code\modules\keybindings\keybind\targeting.dm"
#include "code\modules\language\aphasia.dm"
#include "code\modules\language\beachbum.dm"
#include "code\modules\language\codespeak.dm"
@@ -3412,5 +3421,4 @@
#include "modular_citadel\code\modules\reagents\objects\clothes.dm"
#include "modular_citadel\code\modules\reagents\objects\items.dm"
#include "modular_citadel\code\modules\reagents\reagents\cit_reagents.dm"
-#include "modular_citadel\interface\skin.dmf"
// END_INCLUDE