diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index 18add6f93e1..793d131faaa 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -89,6 +89,8 @@
///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
///Max amount of keypress messages per second over two seconds before client is autokicked
#define MAX_KEYPRESS_AUTOKICK 50
///Length of held key rolling buffer
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 165dae58383..85b2768bfdc 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -23,6 +23,19 @@ GLOBAL_VAR_INIT(cmp_field, "name")
/proc/cmp_records_dsc(datum/data/record/a, datum/data/record/b)
return sorttext(a.fields[GLOB.cmp_field], b.fields[GLOB.cmp_field])
+// Datum cmp with vars is always slower than a specialist cmp proc, use your judgement.
+/proc/cmp_datum_numeric_asc(datum/a, datum/b, variable)
+ return cmp_numeric_asc(a.vars[variable], b.vars[variable])
+
+/proc/cmp_datum_numeric_dsc(datum/a, datum/b, variable)
+ return cmp_numeric_dsc(a.vars[variable], b.vars[variable])
+
+/proc/cmp_datum_text_asc(datum/a, datum/b, variable)
+ return sorttext(b.vars[variable], a.vars[variable])
+
+/proc/cmp_datum_text_dsc(datum/a, datum/b, variable)
+ return sorttext(a.vars[variable], b.vars[variable])
+
/proc/cmp_ckey_asc(client/a, client/b)
return sorttext(b.ckey, a.ckey)
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index e33cc754baf..3166e4e26fa 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -50,6 +50,20 @@
GLOB.materials_list[D.id] = D
sortList(GLOB.materials_list)
+ // Keybindings
+ for(var/KB in subtypesof(/datum/keybinding))
+ var/datum/keybinding/keybinding = KB
+ if(!initial(keybinding.key))
+ continue
+ var/datum/keybinding/instance = new keybinding
+ GLOB.keybindings_by_name[initial(instance.name)] = instance
+ if (!GLOB.keybinding_list_by_key[initial(instance.key)])
+ GLOB.keybinding_list_by_key[initial(instance.key)] = list()
+ GLOB.keybinding_list_by_key[initial(instance.key)] += instance.name
+ // Sort all the keybindings by their weight
+ for(var/key in GLOB.keybinding_list_by_key)
+ GLOB.keybinding_list_by_key[key] = sortList(GLOB.keybinding_list_by_key[key])
+
GLOB.emote_list = init_emote_list()
init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes)
@@ -71,3 +85,4 @@
for(var/path in subtypesof(prototype))
L+= path
return L
+
diff --git a/code/__HELPERS/sanitize_values.dm b/code/__HELPERS/sanitize_values.dm
index 81cb61fe4ba..467deee6e37 100644
--- a/code/__HELPERS/sanitize_values.dm
+++ b/code/__HELPERS/sanitize_values.dm
@@ -11,6 +11,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 00000000000..b37c2a81101
--- /dev/null
+++ b/code/_globalvars/lists/client.dm
@@ -0,0 +1,2 @@
+GLOBAL_LIST_EMPTY(keybinding_list_by_key)
+GLOBAL_LIST_EMPTY(keybindings_by_name)
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 70604bfba0d..cca0358f9fc 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -6,7 +6,7 @@ SUBSYSTEM_DEF(input)
priority = FIRE_PRIORITY_INPUT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
- var/list/macro_sets
+ var/list/macro_set
var/list/movement_keys
/datum/controller/subsystem/input/Initialize()
@@ -22,77 +22,13 @@ SUBSYSTEM_DEF(input)
// This is for when macro sets are eventualy datumized
/datum/controller/subsystem/input/proc/setup_default_macro_sets()
- var/list/static/default_macro_sets
+ macro_set = list(
+ "Any" = "\"KeyDown \[\[*\]\]\"",
+ "Any+UP" = "\"KeyUp \[\[*\]\]\"",
+ "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
+ "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=\\\"\\\"\\\"\"")
- 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",
- "T" = "say",
- "M" = "me",
- "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",
- "T" = "say",
- "M" = "me",
- "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(
- "North", "East", "South", "West",
- "Northeast", "Southeast", "Northwest", "Southwest",
- "Insert", "Delete", "Ctrl", "Alt",
- "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
- )
-
- 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]\""
-
- 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
diff --git a/code/datums/keybinding/_defines.dm b/code/datums/keybinding/_defines.dm
new file mode 100644
index 00000000000..b531336ef04
--- /dev/null
+++ b/code/datums/keybinding/_defines.dm
@@ -0,0 +1,16 @@
+#define CATEGORY_CLIENT "CLIENT"
+#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 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_LOWEST 999
diff --git a/code/datums/keybinding/_keybindings.dm b/code/datums/keybinding/_keybindings.dm
new file mode 100644
index 00000000000..eeb5eed7853
--- /dev/null
+++ b/code/datums/keybinding/_keybindings.dm
@@ -0,0 +1,13 @@
+/datum/keybinding
+ var/key
+ var/name
+ var/full_name
+ var/description = ""
+ var/category = CATEGORY_MISC
+ var/weight = WEIGHT_LOWEST
+
+/datum/keybinding/proc/down(client/user)
+ return FALSE
+
+/datum/keybinding/proc/up(client/user)
+ return FALSE
diff --git a/code/datums/keybinding/admin.dm b/code/datums/keybinding/admin.dm
new file mode 100644
index 00000000000..07deb6a7817
--- /dev/null
+++ b/code/datums/keybinding/admin.dm
@@ -0,0 +1,74 @@
+/datum/keybinding/admin
+ category = CATEGORY_ADMIN
+ weight = WEIGHT_ADMIN
+
+
+/datum/keybinding/admin/admin_say
+ key = "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
+ key = "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
+ key = "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
+ key = "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
+ key = "Ctrl-F8"
+ 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
+ key = "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
+ key = "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
diff --git a/code/datums/keybinding/carbon.dm b/code/datums/keybinding/carbon.dm
new file mode 100644
index 00000000000..98bfff54c71
--- /dev/null
+++ b/code/datums/keybinding/carbon.dm
@@ -0,0 +1,66 @@
+/datum/keybinding/carbon
+ category = CATEGORY_CARBON
+ weight = WEIGHT_MOB
+
+
+/datum/keybinding/carbon/toggle_throw_mode
+ key = "R"
+ 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)
+ if (!iscarbon(user.mob))
+ return FALSE
+ var/mob/living/carbon/C = user.mob
+ C.toggle_throw_mode()
+ return TRUE
+
+
+/datum/keybinding/carbon/select_help_intent
+ key = "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
+ key = "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
+ key = "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
+ key = "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/datums/keybinding/client.dm b/code/datums/keybinding/client.dm
new file mode 100644
index 00000000000..5d96cead8ae
--- /dev/null
+++ b/code/datums/keybinding/client.dm
@@ -0,0 +1,45 @@
+/datum/keybinding/client
+ category = CATEGORY_CLIENT
+ weight = WEIGHT_HIGHEST
+
+
+/datum/keybinding/client/admin_help
+ key = "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
+ key = "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
+ key = "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
+
+/datum/keybinding/client/ooc
+ key = "O"
+ name = "ooc"
+ full_name = "OOC"
+ description = ""
+
+/datum/keybinding/client/ooc/down(client/user)
+ user.ooc_wrapper()
+ return TRUE
diff --git a/code/datums/keybinding/human.dm b/code/datums/keybinding/human.dm
new file mode 100644
index 00000000000..9bdeb506082
--- /dev/null
+++ b/code/datums/keybinding/human.dm
@@ -0,0 +1,37 @@
+/datum/keybinding/human
+ category = CATEGORY_HUMAN
+ weight = WEIGHT_MOB
+
+
+/datum/keybinding/human/quick_equip
+ key = "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
+ key = "Shift-E"
+ 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
+ key = "Shift-B"
+ 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/datums/keybinding/living.dm b/code/datums/keybinding/living.dm
new file mode 100644
index 00000000000..94082224a7f
--- /dev/null
+++ b/code/datums/keybinding/living.dm
@@ -0,0 +1,15 @@
+/datum/keybinding/living
+ category = CATEGORY_HUMAN
+ weight = WEIGHT_MOB
+
+
+/datum/keybinding/living/resist
+ key = "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
diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm
new file mode 100644
index 00000000000..5a1fb904ab2
--- /dev/null
+++ b/code/datums/keybinding/mob.dm
@@ -0,0 +1,233 @@
+/datum/keybinding/mob
+ category = CATEGORY_HUMAN
+ weight = WEIGHT_MOB
+
+
+/datum/keybinding/mob/face_north
+ key = "Ctrl-W"
+ name = "face_north"
+ full_name = "Face North"
+ description = ""
+
+/datum/keybinding/mob/face_north/down(client/user)
+ var/mob/M = user.mob
+ M.northface()
+ return TRUE
+
+
+/datum/keybinding/mob/face_east
+ key = "Ctrl-D"
+ name = "face_east"
+ full_name = "Face East"
+ description = ""
+
+/datum/keybinding/mob/face_east/down(client/user)
+ var/mob/M = user.mob
+ M.eastface()
+ return TRUE
+
+
+/datum/keybinding/mob/face_south
+ key = "Ctrl-S"
+ name = "face_south"
+ full_name = "Face South"
+ description = ""
+
+/datum/keybinding/mob/face_south/down(client/user)
+ var/mob/M = user.mob
+ M.southface()
+ return TRUE
+
+/datum/keybinding/mob/face_west
+ key = "Ctrl-A"
+ name = "face_west"
+ full_name = "Face West"
+ description = ""
+
+/datum/keybinding/mob/face_west/down(client/user)
+ var/mob/M = user.mob
+ M.westface()
+ return TRUE
+
+/datum/keybinding/mob/stop_pulling
+ key = "H"
+ 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
+ key = "Home"
+ name = "cycle_intent_right"
+ full_name = "cycle 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
+ key = "Insert"
+ name = "cycle_intent_left"
+ full_name = "cycle 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
+ key = "X"
+ 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/say
+ key = "T"
+ name = "say"
+ full_name = "Say"
+ description = ""
+
+/datum/keybinding/mob/say/down(client/user)
+ var/mob/M = user.mob
+ M.say_wrapper()
+ return TRUE
+
+/datum/keybinding/mob/me
+ key = "M"
+ name = "me"
+ full_name = "Me"
+ description = ""
+
+/datum/keybinding/mob/me/down(client/user)
+ var/mob/M = user.mob
+ M.me_verb()
+ return TRUE
+
+/datum/keybinding/mob/activate_inhand
+ key = "Z"
+ 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
+ key = "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/toggle_move_intent
+ key = "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"
+
+/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/target_head_cycle
+ key = "Numpad8"
+ name = "target_head_cycle"
+ full_name = "Target: Cycle head"
+ description = ""
+
+/datum/keybinding/mob/target_head_cycle/down(client/user)
+ user.body_toggle_head()
+ return TRUE
+
+/datum/keybinding/mob/target_r_arm
+ key = "Numpad4"
+ name = "target_r_arm"
+ full_name = "Target: right arm"
+ description = ""
+
+/datum/keybinding/mob/target_r_arm/down(client/user)
+ user.body_r_arm()
+ return TRUE
+
+/datum/keybinding/mob/target_body_chest
+ key = "Numpad5"
+ name = "target_body_chest"
+ full_name = "Target: Body"
+ description = ""
+
+/datum/keybinding/mob/target_body_chest/down(client/user)
+ user.body_chest()
+ return TRUE
+
+/datum/keybinding/mob/target_left_arm
+ key = "Numpad6"
+ name = "target_left_arm"
+ full_name = "Target: left arm"
+ description = ""
+
+/datum/keybinding/mob/target_left_arm/down(client/user)
+ user.body_l_arm()
+ return TRUE
+
+/datum/keybinding/mob/target_right_leg
+ key = "Numpad1"
+ name = "target_right_leg"
+ full_name = "Target: Right leg"
+ description = ""
+
+/datum/keybinding/mob/target_right_leg/down(client/user)
+ user.body_r_leg()
+ return TRUE
+
+/datum/keybinding/mob/target_body_groin
+ key = "Numpad2"
+ name = "target_body_groin"
+ full_name = "Target: Groin"
+ description = ""
+
+/datum/keybinding/mob/target_body_groin/down(client/user)
+ user.body_groin()
+ return TRUE
+
+/datum/keybinding/mob/target_left_leg
+ key = "Numpad3"
+ name = "target_left_leg"
+ full_name = "Target: left leg"
+ description = ""
+
+/datum/keybinding/mob/target_left_leg/down(client/user)
+ user.body_l_leg()
+ return TRUE
diff --git a/code/datums/keybinding/robot.dm b/code/datums/keybinding/robot.dm
new file mode 100644
index 00000000000..98bfd9b6b23
--- /dev/null
+++ b/code/datums/keybinding/robot.dm
@@ -0,0 +1,69 @@
+/datum/keybinding/robot
+ category = CATEGORY_ROBOT
+ weight = WEIGHT_ROBOT
+
+
+/datum/keybinding/robot/moduleone
+ key = "1"
+ name = "module_one"
+ full_name = "Toggle module 1"
+ description = "Equips or unequips the first module"
+
+/datum/keybinding/robot/moduleone/down(client/user)
+ if(!iscyborg(user.mob))
+ return FALSE
+ var/mob/living/silicon/robot/R = user.mob
+ R.toggle_module(1)
+ return TRUE
+
+/datum/keybinding/robot/moduletwo
+ key = "2"
+ name = "module_two"
+ full_name = "Toggle module 2"
+ description = "Equips or unequips the second module"
+
+/datum/keybinding/robot/moduletwo/down(client/user)
+ if(!iscyborg(user.mob))
+ return FALSE
+ var/mob/living/silicon/robot/R = user.mob
+ R.toggle_module(2)
+ return TRUE
+
+/datum/keybinding/robot/modulethree
+ key = "3"
+ name = "module_three"
+ full_name = "Toggle module 3"
+ description = "Equips or unequips the third module"
+
+/datum/keybinding/robot/modulethree/down(client/user)
+ if(!iscyborg(user.mob))
+ return FALSE
+ var/mob/living/silicon/robot/R = user.mob
+ R.toggle_module(3)
+ return TRUE
+
+/datum/keybinding/robot/intent_cycle
+ key = "4"
+ name = "cycle_intent"
+ full_name = "Cycle intent left"
+ description = "Cycles the intent left"
+
+/datum/keybinding/robot/intent_cycle/down(client/user)
+ if(!iscyborg(user.mob))
+ return FALSE
+ var/mob/living/silicon/robot/R = user.mob
+ R.a_intent_change(INTENT_HOTKEY_LEFT)
+ return TRUE
+
+/datum/keybinding/robot/unequip_module
+ key = "Q"
+ name = "unequip_module"
+ full_name = "Unequip module"
+ description = "Unequips the active module"
+
+/datum/keybinding/robot/unequip_module/down(client/user)
+ if(!iscyborg(user.mob))
+ return FALSE
+ var/mob/living/silicon/robot/R = user.mob
+ R.uneq_active()
+ return TRUE
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index a8f9a5d9c05..b1cffd47dbb 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -28,6 +28,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/UI_style = null
var/buttons_locked = FALSE
var/hotkeys = FALSE
+
+ // Custom Keybindings
+ var/list/key_bindings = list()
+
var/tgui_fancy = TRUE
var/tgui_lock = TRUE
var/windowflashing = TRUE
@@ -125,6 +129,7 @@ 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.keybinding_list_by_key) // give them default keybinds too
real_name = pref_species.random_name(gender,1)
if(!loaded_preferences_successfully)
save_preferences()
@@ -147,6 +152,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Character Settings "
dat += "Game Preferences "
dat += "OOC Preferences "
+ dat += "Custom Keybindings "
if(!path)
dat += "
Please create an account to save your preferences
"
@@ -512,7 +518,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"] "
dat += " "
dat += "Action Buttons: [(buttons_locked) ? "Locked In Place" : "Unlocked"] "
- dat += "Keybindings: [(hotkeys) ? "Hotkeys" : "Default"] "
+ dat += "Hotkey mode: [(hotkeys) ? "Hotkeys" : "Default"] "
dat += " "
dat += "PDA Color: Change "
dat += "PDA Style: [pda_style] "
@@ -616,9 +622,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Be [capitalize(i)]: [(i in be_special) ? "Enabled" : "Disabled"] "
dat += " "
dat += "Midround Antagonist: [(toggles & MIDROUND_ANTAG) ? "Enabled" : "Disabled"] "
-
dat += ""
-
if(2) //OOC Preferences
dat += ""
dat += "OOC Settings "
@@ -687,7 +691,36 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += " "
dat += "
"
+ if(3) // Custom keybindings
+ // 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] = 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]
+ if (!(kb.category in kb_categories))
+ kb_categories[kb.category] = list()
+ 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
+ var/bound_key = user_binds[kb.name]
+ bound_key = (bound_key) ? bound_key : "Unbound"
+
+ dat += "[kb.full_name] [bound_key] Default: ( [kb.key] ) "
+ dat += " "
+
+ dat += " "
+ dat += "\[Reset to default\] "
+ dat += ""
dat += ""
if(!IsGuestKey(user.key))
@@ -706,6 +739,28 @@ 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
@@ -1438,9 +1493,78 @@ 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")
+ winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED]")
else
- winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default")
+ winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_DISABLED]")
+
+ 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(old_key != "Unbound") // if it was already set
+ key_bindings[old_key] -= kb_name
+ key_bindings["Unbound"] += list(kb_name)
+ user << browse(null, "window=capturekeypress")
+ save_preferences()
+ ShowChoices(user)
+ return
+
+ var/key = href_list["key"]
+ var/numpad = text2num(href_list["numpad"])
+ var/AltMod = text2num(href_list["alt"]) ? "Alt-" : ""
+ var/CtrlMod = text2num(href_list["ctrl"]) ? "Ctrl-" : ""
+ var/ShiftMod = text2num(href_list["shift"]) ? "Shift-" : ""
+ // var/key_code = text2num(href_list["key_code"])
+
+ var/new_key = uppertext(key)
+
+ // This is a mapping from JS keys to Byond - ref: https://keycode.info/
+ var/static/list/_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"
+ )
+ new_key = _kbMap[new_key] ? _kbMap[new_key] : new_key
+
+ if (numpad)
+ new_key = "Numpad[new_key]"
+
+ var/full_key = "[AltMod][CtrlMod][ShiftMod][new_key]"
+ if(!key_bindings[old_key])
+ key_bindings[old_key] = list()
+ key_bindings[old_key] -= kb_name
+ key_bindings[full_key] += list(kb_name)
+ key_bindings[full_key] = sortList(key_bindings[full_key])
+
+ user << browse(null, "window=capturekeypress")
+ save_preferences()
+
+ if("keybindings_reset")
+ key_bindings = deepCopyList(GLOB.keybinding_list_by_key)
+
if("action_buttons")
buttons_locked = !buttons_locked
if("tgui_fancy")
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index d1650b8304d..2da689e2e47 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 25
+#define SAVEFILE_VERSION_MAX 26
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -126,6 +126,14 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["hair_style_name"] >> hairstyle
if(S["facial_style_name"])
S["facial_style_name"] >> facial_hairstyle
+
+ if(current_version < 26)
+ key_bindings = sanitize_islist(key_bindings, deepCopyList(GLOB.keybinding_list_by_key))
+ key_bindings["T"] = list(1 = "say")
+ key_bindings["M"] = list(1 = "me")
+ key_bindings["O"] = list(1 = "ooc")
+ key_bindings["L"] = list(1 = "looc")
+ WRITE_FILE(S["key_bindings"], key_bindings)
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
@@ -183,6 +191,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["pda_style"] >> pda_style
S["pda_color"] >> pda_color
+ // Custom hotkeys
+ S["key_bindings"] >> key_bindings
+
//try to fix any outdated data if necessary
if(needs_update >= 0)
update_preferences(needs_update, S) //needs_update = savefile_version if we need an update (positive integer)
@@ -212,7 +223,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
be_special = SANITIZE_LIST(be_special)
pda_style = sanitize_inlist(pda_style, GLOB.pda_styles, initial(pda_style))
pda_color = sanitize_hexcolor(pda_color, 6, 1, initial(pda_color))
-
+ key_bindings = sanitize_islist(key_bindings, deepCopyList(GLOB.keybinding_list_by_key))
return TRUE
/datum/preferences/proc/save_preferences()
@@ -258,7 +269,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["tip_delay"], tip_delay)
WRITE_FILE(S["pda_style"], pda_style)
WRITE_FILE(S["pda_color"], pda_color)
-
+ WRITE_FILE(S["key_bindings"], key_bindings)
return TRUE
/datum/preferences/proc/load_character(slot)
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index 1a82cf204fc..0bf2c47c070 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -1,6 +1,11 @@
GLOBAL_VAR_INIT(OOC_COLOR, null)//If this is null, use the CSS for OOC. Otherwise, use a custom colour.
GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
+/client/verb/ooc_wrapper()
+ set hidden = TRUE
+ var/message = input("", "OOC") as text
+ ooc(message)
+
/client/verb/ooc(msg as text)
set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite
set category = "OOC"
diff --git a/code/modules/keybindings/bindings_admin.dm b/code/modules/keybindings/bindings_admin.dm
deleted file mode 100644
index ca232adbe0e..00000000000
--- 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_carbon.dm b/code/modules/keybindings/bindings_carbon.dm
deleted file mode 100644
index cea93b0da32..00000000000
--- a/code/modules/keybindings/bindings_carbon.dm
+++ /dev/null
@@ -1,18 +0,0 @@
-/mob/living/carbon/key_down(_key, client/user)
- switch(_key)
- if("R", "Southwest") // Southwest is End
- toggle_throw_mode()
- return
- if("1")
- a_intent_change("help")
- return
- if("2")
- a_intent_change("disarm")
- return
- if("3")
- a_intent_change("grab")
- return
- if("4")
- a_intent_change("harm")
- return
- return ..()
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 0a971dca9ca..9b54ceb2034 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -35,6 +35,7 @@
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)
return
+
//offset by 1 because the buffer address is 0 indexed because the math was simpler
keys_held[current_key_address + 1] = _key
//the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
@@ -47,24 +48,19 @@
// Client-level keybindings are ones anyone should be able to do at any time
// Things like taking screenshots, hitting tab, and adminhelps.
- 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
+ var/AltMod = keys_held["Alt"] ? "Alt-" : ""
+ var/CtrlMod = keys_held["Ctrl"] ? "Ctrl-" : ""
+ var/ShiftMod = keys_held["Shift"] ? "Shift-" : ""
+ var/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.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(full_key, src)
+ mob.focus?.key_down(full_key, src)
/client/verb/keyUp(_key as text)
set instant = TRUE
@@ -79,14 +75,16 @@
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.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)
+ 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 c404acfcb7c..00000000000
--- a/code/modules/keybindings/bindings_human.dm
+++ /dev/null
@@ -1,65 +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
- 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(user, "You have no belt to take something out of! ")
- return
- if(equip_to_slot_if_possible(thing, 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(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
- 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(user, "You have no backpack to take something out of! ")
- return
- if(equip_to_slot_if_possible(thing, 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(user, "You can't fit anything in! ")
- return
- if(thing) // put thing in backpack
- if(!SEND_SIGNAL(equipped_back, COMSIG_TRY_STORAGE_INSERT, thing, user.mob))
- to_chat(user, "You can't fit anything in! ")
- return
- if(!equipped_back.contents.len) // nothing to take out
- to_chat(user, "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
- return ..()
diff --git a/code/modules/keybindings/bindings_living.dm b/code/modules/keybindings/bindings_living.dm
deleted file mode 100644
index 241bc15b608..00000000000
--- a/code/modules/keybindings/bindings_living.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-/mob/living/key_down(_key, client/user)
- switch(_key)
- if("B")
- resist()
- 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 0b7fb66fdde..00000000000
--- a/code/modules/keybindings/bindings_mob.dm
+++ /dev/null
@@ -1,82 +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)
- northface()
- return
- if(SOUTH)
- southface()
- return
- if(WEST)
- westface()
- return
- if(EAST)
- 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 ad10b2bd9aa..00000000000
--- a/code/modules/keybindings/bindings_robot.dm
+++ /dev/null
@@ -1,12 +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
- return ..()
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index 8433c9bf5a3..78e06132311 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -22,15 +22,12 @@
// 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()
@@ -43,18 +40,13 @@
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]")
+ var/list/macro_set = SSinput.macro_set
+ for(var/k in 1 to length(macro_set))
+ var/key = macro_set[k]
+ var/command = macro_set[key]
+ winset(src, "default-[REF(key)]", "parent=default;name=[key];command=[command]")
if(prefs.hotkeys)
- winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=default")
+ winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED]")
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_DISABLED]")
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 04daf73122f..6083f1b2407 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -270,3 +270,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, 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, 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/say.dm b/code/modules/mob/say.dm
index 250aefefa82..9b8c1e08a4b 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -10,6 +10,11 @@
if(message)
say(message)
+/mob/verb/say_wrapper()
+ set hidden = TRUE
+ var/message = input("", "Say") as text
+ say_verb(message)
+
///Whisper verb
/mob/verb/whisper_verb(message as text)
set name = "Whisper"
@@ -22,7 +27,7 @@
///whisper a message
/mob/proc/whisper(message, datum/language/language=null)
say(message, language) //only living mobs actually whisper, everything else just talks
-
+
///The me emote verb
/mob/verb/me_verb(message as text)
set name = "Me"
diff --git a/tgstation.dme b/tgstation.dme
index 6a93e8f232a..11218221279 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -165,6 +165,7 @@
#include "code\_globalvars\traits.dm"
#include "code\_globalvars\lists\achievements.dm"
#include "code\_globalvars\lists\admin.dm"
+#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
#include "code\_globalvars\lists\mapping.dm"
@@ -512,6 +513,15 @@
#include "code\datums\helper_datums\getrev.dm"
#include "code\datums\helper_datums\icon_snapshot.dm"
#include "code\datums\helper_datums\teleport.dm"
+#include "code\datums\keybinding\_defines.dm"
+#include "code\datums\keybinding\_keybindings.dm"
+#include "code\datums\keybinding\admin.dm"
+#include "code\datums\keybinding\carbon.dm"
+#include "code\datums\keybinding\client.dm"
+#include "code\datums\keybinding\human.dm"
+#include "code\datums\keybinding\living.dm"
+#include "code\datums\keybinding\mob.dm"
+#include "code\datums\keybinding\robot.dm"
#include "code\datums\looping_sounds\_looping_sound.dm"
#include "code\datums\looping_sounds\item_sounds.dm"
#include "code\datums\looping_sounds\machinery_sounds.dm"
@@ -1885,14 +1895,8 @@
#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\language\aphasia.dm"