diff --git a/baystation12.dme b/baystation12.dme
index 018a92b64d..02fd56baec 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -219,6 +219,7 @@
#include "code\controllers\subsystems\goals.dm"
#include "code\controllers\subsystems\graphs.dm"
#include "code\controllers\subsystems\inactivity.dm"
+#include "code\controllers\subsystems\input.dm"
#include "code\controllers\subsystems\jobs.dm"
#include "code\controllers\subsystems\kv.dm"
#include "code\controllers\subsystems\lighting.dm"
@@ -1967,6 +1968,17 @@
#include "code\modules\item_worth\item_worth.dm"
#include "code\modules\item_worth\value_procs.dm"
#include "code\modules\item_worth\worths_list.dm"
+#include "code\modules\keybindings\bindings_admin.dm"
+#include "code\modules\keybindings\bindings_ai.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\library\lib_items.dm"
#include "code\modules\library\lib_machines.dm"
#include "code\modules\library\lib_readme.dm"
diff --git a/code/__defines/admin.dm b/code/__defines/admin.dm
index 4d8aa8573b..db7da1ec7b 100644
--- a/code/__defines/admin.dm
+++ b/code/__defines/admin.dm
@@ -46,4 +46,11 @@
#define TICKET_ASSIGNED 2 // An admin has assigned themself to the ticket and will respond
#define LAST_CKEY(M) (M.ckey || M.last_ckey)
-#define LAST_KEY(M) (M.key || M.last_ckey)
\ No newline at end of file
+#define LAST_KEY(M) (M.key || M.last_ckey)
+
+///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 50
+///Length of held key rolling buffer
+#define HELD_KEY_BUFFER_LENGTH 15
diff --git a/code/__defines/colors.dm b/code/__defines/colors.dm
index 5d5cf0d51b..6f573306f4 100644
--- a/code/__defines/colors.dm
+++ b/code/__defines/colors.dm
@@ -181,3 +181,6 @@
#define COLOR_DARKMODE_BACKGROUND "#202020"
#define COLOR_DARKMODE_DARKBACKGROUND "#171717"
#define COLOR_DARKMODE_TEXT "#a4bad6"
+
+#define COLOR_INPUT_DISABLED "#F0F0F0"
+#define COLOR_INPUT_ENABLED "#D3B5B5"
diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index cfebd1d9f9..5c9fabe6c4 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -293,3 +293,12 @@
#define INIT_MACHINERY_PROCESS_ALL 0x3
//--
+//intent defines
+#define INTENT_HELP "help"
+#define INTENT_GRAB "grab"
+#define INTENT_DISARM "disarm"
+#define INTENT_HARM "harm"
+//NOTE: INTENT_HOTKEY_* defines are not actual intents!
+//they are here to support hotkeys
+#define INTENT_HOTKEY_LEFT "left"
+#define INTENT_HOTKEY_RIGHT "right"
diff --git a/code/__defines/subsystem-priority.dm b/code/__defines/subsystem-priority.dm
index 995b9713e7..482a2f040e 100644
--- a/code/__defines/subsystem-priority.dm
+++ b/code/__defines/subsystem-priority.dm
@@ -10,6 +10,7 @@
#define SS_PRIORITY_ICON_UPDATE 20 // Queued icon updates. Mostly used by APCs and tables.
// Normal
+#define SS_PRIORITY_INPUT 1000
#define SS_PRIORITY_TICKER 100 // Gameticker.
#define SS_PRIORITY_MOB 95 // Mob Life().
#define SS_PRIORITY_MACHINERY 95 // Machinery + powernet ticks.
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index edf610841e..9a7e945ff2 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -31,6 +31,7 @@ return;\
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
+#define SS_INIT_INPUT 19
#define SS_INIT_EARLY 18
#define SS_INIT_GARBAGE 17
#define SS_INIT_CHEMISTRY 16
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index b25711aa87..75536ea2e1 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -1118,3 +1118,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
M.start_pulling(t)
else
step(user.pulling, get_dir(user.pulling.loc, A))
+
+/proc/REF(input)
+ return "\ref[input]"
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 454df24071..800e8035ff 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -65,6 +65,9 @@
if(modifiers["middle"])
MiddleClickOn(A)
return 1
+ if(modifiers["middle"] && modifiers["alt"])
+ AltMiddleClickOn(A)
+ return 1
if(modifiers["shift"])
ShiftClickOn(A)
return 0
@@ -219,7 +222,10 @@
Only used for swapping hands
*/
/mob/proc/MiddleClickOn(var/atom/A)
- swap_hand()
+ pointed(A)
+ return
+
+/mob/proc/AltMiddleClickOn(var/atom/A)
return
// In case of use break glass
@@ -302,11 +308,8 @@
/mob/proc/CtrlAltClickOn(var/atom/A)
if(A.CtrlAltClick(src))
return
- pointed(A)
/atom/proc/CtrlAltClick(var/mob/user)
- if(user.client && user.client.eye == user)
- user.pointed(src)
return
/*
diff --git a/code/_onclick/click_inf.dm b/code/_onclick/click_inf.dm
index 1c48c063c0..a782eac521 100644
--- a/code/_onclick/click_inf.dm
+++ b/code/_onclick/click_inf.dm
@@ -1,4 +1,4 @@
-/mob/living/carbon/MiddleClickOn(atom/A)
+/mob/living/carbon/AltMiddleClickOn(atom/A)
if(!stat && mind && iscarbon(A) && A != src)
var/datum/changeling/C = mind.changeling
if(C?.chosen_sting)
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index 9868ec3d01..8eea7ccccc 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -96,8 +96,9 @@
return
return
-//Middle click cycles through selected modules.
-/mob/living/silicon/robot/MiddleClickOn(var/atom/A)
+//Alt Middle click cycles through selected modules.
+//Though we have "X" marcos for it and humans now can't swap hands using middle click, I'll let it be here
+/mob/living/silicon/robot/AltMiddleClickOn(var/atom/A)
cycle_modules()
return
diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm
index 5d7604a6f5..a185fb4bc2 100644
--- a/code/_onclick/hud/ability_screen_objects.dm
+++ b/code/_onclick/hud/ability_screen_objects.dm
@@ -353,6 +353,7 @@
toggle_open(2) //forces the icons to refresh on screen
/mob/Life()
+ set waitfor = FALSE
UNLINT(..())
if(ability_master)
ability_master.update_spells(0)
@@ -410,4 +411,4 @@
for(var/obj/screen/ability/spell/spell in spell_objects)
spell.spell.silenced = amount
spell.spell.process()
- spell.update_charge(1)
\ No newline at end of file
+ spell.update_charge(1)
diff --git a/code/_onclick/rig.dm b/code/_onclick/rig.dm
index 583f4e59c7..4850217649 100644
--- a/code/_onclick/rig.dm
+++ b/code/_onclick/rig.dm
@@ -1,4 +1,4 @@
-/mob/living/MiddleClickOn(atom/A)
+/mob/living/AltMiddleClickOn(atom/A)
if(get_preference_value(/datum/client_preference/hardsuit_activation) == GLOB.PREF_MIDDLE_CLICK)
if(HardsuitClickOn(A))
return
@@ -51,4 +51,4 @@
if(ismob(A)) // No instant mob attacking - though modules have their own cooldowns
setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
return 1
- return 0
\ No newline at end of file
+ return 0
diff --git a/code/controllers/subsystems/input.dm b/code/controllers/subsystems/input.dm
new file mode 100644
index 0000000000..ea8e6d7bb4
--- /dev/null
+++ b/code/controllers/subsystems/input.dm
@@ -0,0 +1,124 @@
+SUBSYSTEM_DEF(input)
+ name = "Input"
+ wait = 1 //SS_TICKER means this runs every tick
+ init_order = SS_INIT_INPUT
+ flags = SS_TICKER
+ priority = SS_PRIORITY_INPUT
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+
+ var/list/macro_sets
+ var/list/movement_keys
+ var/list/alt_movement_keys
+
+/datum/controller/subsystem/input/Initialize()
+ setup_default_macro_sets()
+
+ setup_default_movement_keys()
+
+ initialized = TRUE
+
+ refresh_client_macro_sets()
+
+ 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]\\\"\"",
+ "Back" = "\".winset \\\"input.focus=true 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]\\\"\"",
+ ),
+ "old_hotkeys" = list(
+ "Tab" = "\".winset \\\"mainwindow.macro=old_default input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
+ "Back" = "\".winset \\\"input.focus=true 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", "Shift",
+ "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, rest
+ "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
+ "T" = "T", // say, whisper
+ "M" = "M", // me
+ "O" = "O", // ooc
+ "L" = "L", // looc
+ "C" = "C", // stop pulling
+ )
+
+ 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
+ )
+ var/static/list/azerty_movement_keys = list(
+ "Z" = NORTH, "Q" = WEST, "S" = SOUTH, "D" = EAST, // WASD
+ "North" = NORTH, "West" = WEST, "South" = SOUTH, "East" = EAST, // Arrow keys & Numpad
+ )
+ movement_keys = default_movement_keys.Copy()
+ alt_movement_keys = azerty_movement_keys.Copy()
+
+// Badmins just wanna have fun ♪
+/datum/controller/subsystem/input/proc/refresh_client_macro_sets()
+ var/list/clients = GLOB.clients
+ for(var/i in 1 to clients.len)
+ var/client/user = clients[i]
+ user.set_macros()
+
+/datum/controller/subsystem/input/fire()
+ var/list/clients = GLOB.clients // Let's sing the list cache song
+ if(listclearnulls(clients)) // clear nulls before we run keyloop
+ log_world("Found a null in clients list!")
+ for(var/i in 1 to clients.len)
+ var/client/C = clients[i]
+ C.keyLoop()
+
+/datum/controller/subsystem/input/Recover()
+ macro_sets = SSinput.macro_sets
+ movement_keys = SSinput.movement_keys
+ alt_movement_keys = SSinput.alt_movement_keys
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index e5500d99c0..3a7e8493f1 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -1,10 +1,13 @@
-/client/verb/ooc(message as text)
+/client/verb/ooc(message = "" as text)
set name = "OOC"
set category = "OOC"
+ if(!message)
+ message = input(src.mob, "", "ooc \"text\"") as text|null
+
sanitize_and_communicate(/decl/communication_channel/ooc, src, message)
-/client/verb/looc(message as text)
+/client/verb/looc(message = "" as text)
set name = "LOOC"
set desc = "Local OOC, seen only by those in view. Remember: Just because you see someone that doesn't mean they see you."
set category = "OOC"
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index aa0b8ef7bb..019ea7cdb1 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -254,6 +254,9 @@
prefs?.apply_post_login_preferences()
//[/INF]
+ if(SSinput.initialized)
+ set_macros()
+
//////////////
//DISCONNECT//
//////////////
diff --git a/code/modules/keybindings/bindings_admin.dm b/code/modules/keybindings/bindings_admin.dm
new file mode 100644
index 0000000000..673bdbcf16
--- /dev/null
+++ b/code/modules/keybindings/bindings_admin.dm
@@ -0,0 +1,32 @@
+/datum/admins/key_down(_key, client/user)
+ switch(_key)
+ if("F5")
+// if(user.keys_held["Shift"])
+// user.get_mentor_say()
+// else
+ user.get_admin_say()
+ return
+ if("F6")
+ user.admin_ghost()
+ return
+ if("F7")
+ player_panel()
+ return
+ if("F8")
+ user.cmd_admin_pm_panel()
+ return
+ if("F9")
+ user.invisimin()
+ return
+ if("F10")
+ user.get_dead_say()
+ return
+ ..()
+
+/client/proc/get_admin_say()
+ var/msg = input(src, null, "asay \"text\"") as text|null
+ cmd_admin_say(msg)
+
+/client/proc/get_dead_say()
+ var/msg = input(src, null, "dsay \"text\"") as text
+ dsay(msg)
diff --git a/code/modules/keybindings/bindings_ai.dm b/code/modules/keybindings/bindings_ai.dm
new file mode 100644
index 0000000000..975b5ba81a
--- /dev/null
+++ b/code/modules/keybindings/bindings_ai.dm
@@ -0,0 +1,6 @@
+/mob/living/silicon/ai/key_down(_key, client/user)
+ switch(_key)
+ if("4")
+ a_intent_change(INTENT_HOTKEY_LEFT)
+ return
+ return ..()
diff --git a/code/modules/keybindings/bindings_atom.dm b/code/modules/keybindings/bindings_atom.dm
new file mode 100644
index 0000000000..2b7fafbe20
--- /dev/null
+++ b/code/modules/keybindings/bindings_atom.dm
@@ -0,0 +1,19 @@
+// You might be wondering why this isn't client level. If focus is null, we don't want you to move.
+// Only way to do that is to tie the behavior into the focus's keyLoop().
+
+/atom/movable/keyLoop(client/user)
+ if(!user.keys_held["Ctrl"])
+ var/movement_dir = null
+ var/list/movement = SSinput.movement_keys
+ for(var/_key in user.keys_held)
+ movement_dir = movement_dir | movement[_key]
+ if(user.next_move_dir_add)
+ movement_dir |= user.next_move_dir_add
+ if(user.next_move_dir_sub)
+ movement_dir &= ~user.next_move_dir_sub
+ // Sanity checks in case you hold left and right and up to make sure you only go up
+ if((movement_dir & NORTH) && (movement_dir & SOUTH))
+ movement_dir &= ~(NORTH|SOUTH)
+ if((movement_dir & EAST) && (movement_dir & WEST))
+ movement_dir &= ~(EAST|WEST)
+ user.Move(get_step(src, movement_dir), movement_dir)
diff --git a/code/modules/keybindings/bindings_carbon.dm b/code/modules/keybindings/bindings_carbon.dm
new file mode 100644
index 0000000000..a2cb4e7219
--- /dev/null
+++ b/code/modules/keybindings/bindings_carbon.dm
@@ -0,0 +1,21 @@
+
+/mob/living/carbon/key_down(_key, client/user)
+ user.keys_held[_key] = world.time
+ if(!user.keys_held["Shift"])
+ switch(_key)
+ if("R", "Southwest") // Southwest is End
+ toggle_throw_mode()
+ return
+ if("1")
+ a_intent_change(I_HELP)
+ return
+ if("2")
+ a_intent_change(I_DISARM)
+ return
+ if("3")
+ a_intent_change(I_GRAB)
+ return
+ if("4")
+ a_intent_change(I_HURT)
+ return
+ return ..()
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
new file mode 100644
index 0000000000..ea99bd1659
--- /dev/null
+++ b/code/modules/keybindings/bindings_client.dm
@@ -0,0 +1,112 @@
+// Clients aren't datums so we have to define these procs indpendently.
+// These verbs are called for all key press and release events
+/client/verb/keyDown(_key as text)
+ set instant = TRUE
+ set hidden = TRUE
+
+ client_keysend_amount += 1
+
+ var/cache = client_keysend_amount
+
+ if(keysend_tripped && next_keysend_trip_reset <= world.time)
+ keysend_tripped = FALSE
+
+ if(next_keysend_reset <= world.time)
+ client_keysend_amount = 0
+ next_keysend_reset = world.time + (1 SECONDS)
+
+ //The "tripped" system is to confirm that flooding is still happening after one spike
+ //not entirely sure how byond commands interact in relation to lag
+ //don't want to kick people if a lag spike results in a huge flood of commands being sent
+ if(cache >= MAX_KEYPRESS_AUTOKICK)
+ if(!keysend_tripped)
+ keysend_tripped = TRUE
+ next_keysend_trip_reset = world.time + (2 SECONDS)
+ else
+ log_admin("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.")
+ message_admins("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.")
+ qdel(src)
+ return
+
+ ///Check if the key is short enough to even be a real key
+ if(LAZYLEN(_key) > MAX_KEYPRESS_COMMANDLENGTH)
+ 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(src)
+ 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
+
+ keys_held[_key] = world.time
+
+ current_key_address = ((current_key_address + 1) % HELD_KEY_BUFFER_LENGTH)
+
+ var/movement = SSinput.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.
+
+ switch(_key)
+ if("F1")
+ if(keys_held["Ctrl"] && keys_held["Shift"]) // Is this command ever used?
+ winset(src, null, "command=.options")
+ else
+ adminhelp()
+ return
+ if("F2", "O") // Screenshot. Hold shift to choose a name and location to save in
+ ooc()
+ return
+ if("F3", "T")
+ if(keys_held["Shift"])
+ mob.whisper_wrapper()
+ else
+ mob.say_wrapper()
+ return
+ if("F4", "M")
+ mob.me_wrapper()
+ return
+ if("L")
+ looc()
+ return
+ if("F11") // Toggles Fullscreen or Fits Viewport
+ if(keys_held["Ctrl"])
+ fit_viewport()
+ else
+ toggle_fullscreen()
+ return
+ if("F12") // Toggles minimal HUD
+ mob.button_pressed_F12()
+ return
+
+ if(holder)
+ holder.key_down(_key, src)
+ if(mob.focus)
+ mob.focus.key_down(_key, src)
+
+/client/verb/keyUp(_key as text)
+ set instant = TRUE
+ set hidden = TRUE
+ //Can't just do a remove because it would alter the length of the rolling buffer, instead search for the key then null it out if it exists
+ for(var/i in 1 to HELD_KEY_BUFFER_LENGTH)
+ if(keys_held[i] == _key)
+ keys_held[i] = null
+ break
+ var/movement = SSinput.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)
+
+// Called every game tick
+/client/keyLoop()
+ if(holder)
+ holder.keyLoop(src)
+ if(mob.focus)
+ mob.focus.keyLoop(src)
diff --git a/code/modules/keybindings/bindings_human.dm b/code/modules/keybindings/bindings_human.dm
new file mode 100644
index 0000000000..cfec33a158
--- /dev/null
+++ b/code/modules/keybindings/bindings_human.dm
@@ -0,0 +1,83 @@
+/mob/living/carbon/human/key_down(_key, client/user)
+/*
+ if(_key == "H")
+ var/obj/item/clothing/accessory/holster/H = null
+ if(istype(w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/S = w_uniform
+ if(S.accessories.len)
+ H = locate() in S.accessories
+ if (!H)
+ return
+ if(!H.holstered)
+ if(!istype(get_active_hand(), /obj/item/gun))
+ to_chat(usr, "You need your gun equiped to holster it.")
+ return
+ var/obj/item/gun/W = get_active_hand()
+ H.holster(W, usr)
+ else
+ H.unholster(usr)
+*/
+ if(client.keys_held["Shift"])
+ switch(_key)
+ if("E") // Put held thing in belt or take out most recent thing from belt
+ quick_equip() // Implementing the storage component is going to take way too long
+ return
+ // var/obj/item/thing = get_active_hand()
+ // 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_r_hand()
+ // update_inv_l_hand()
+ // 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
+ var/obj/item/thing = get_active_hand()
+ 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_r_hand()
+ update_inv_l_hand()
+ 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
new file mode 100644
index 0000000000..88c14a51a1
--- /dev/null
+++ b/code/modules/keybindings/bindings_living.dm
@@ -0,0 +1,10 @@
+/mob/living/key_down(_key, client/user)
+ switch(_key)
+ if("B")
+ if(user.keys_held["Shift"])
+ lay_down()
+ else
+ resist()
+ return
+
+ return ..()
diff --git a/code/modules/keybindings/bindings_mob.dm b/code/modules/keybindings/bindings_mob.dm
new file mode 100644
index 0000000000..5d3dde1a30
--- /dev/null
+++ b/code/modules/keybindings/bindings_mob.dm
@@ -0,0 +1,90 @@
+// 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", "C")
+ 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_hand()
+ if(!I)
+ to_chat(src, "You have nothing to drop in your hand!")
+ else
+ drop_item(I)
+ return
+ if("E")
+ quick_equip()
+ return
+ if("Shift")
+ set_moving_quickly()
+ return
+ if(",")
+ move_up()
+ return
+ if(".")
+ SelfMove(DOWN)
+ return
+ if("j")
+ toggle_gun_mode()
+ 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("Shift")
+ set_moving_slowly()
+ return ..()
diff --git a/code/modules/keybindings/bindings_robot.dm b/code/modules/keybindings/bindings_robot.dm
new file mode 100644
index 0000000000..db455f82c0
--- /dev/null
+++ b/code/modules/keybindings/bindings_robot.dm
@@ -0,0 +1,14 @@
+/mob/living/silicon/robot/key_down(_key, client/user)
+ switch(_key)
+ if("1", "2", "3")
+ cmd_toggle_module(text2num(_key))
+ return
+ if("4")
+ a_intent_change(INTENT_HOTKEY_LEFT)
+ return
+ if("X")
+ cycle_modules()
+ return
+ if("Q")
+ cmd_unequip_module() // User is in QWERTY hotkey mode.
+ return ..()
diff --git a/code/modules/keybindings/focus.dm b/code/modules/keybindings/focus.dm
new file mode 100644
index 0000000000..651b50cc45
--- /dev/null
+++ b/code/modules/keybindings/focus.dm
@@ -0,0 +1,8 @@
+/mob
+ var/datum/focus //What receives our keyboard inputs. src by default
+
+/mob/proc/set_focus(datum/new_focus)
+ if(focus == new_focus)
+ return
+ focus = new_focus
+ reset_view(focus) //Maybe this should be done manually? You figure it out, reader
diff --git a/code/modules/keybindings/readme.dm b/code/modules/keybindings/readme.dm
new file mode 100644
index 0000000000..3f8b992b93
--- /dev/null
+++ b/code/modules/keybindings/readme.dm
@@ -0,0 +1,42 @@
+# In-code keypress handling system
+
+This whole system is heavily based off of forum_account's keyboard library.
+Thanks to forum_account for saving the day, the library can be found
+[here](https://secure.byond.com/developer/Forum_account/Keyboard)!
+
+.dmf macros have some very serious shortcomings. For example, they do not allow reusing parts
+of one macro in another, so giving cyborgs their own shortcuts to swap active module couldn't
+inherit the movement that all mobs should have anyways. The webclient only supports one macro,
+so having more than one was problematic. Additionally each keybind has to call an actual
+verb, which meant a lot of hidden verbs that just call one other proc. Also our existing
+macro was really bad and tied unrelated behavior into `Northeast()`, `Southeast()`, `Northwest()`,
+and `Southwest()`.
+
+The basic premise of this system is to not screw with .dmf macro setup at all and handle
+pressing those keys in the code instead. We have every key call `client.keyDown()`
+or `client.keyUp()` with the pressed key as an argument. Certain keys get processed
+directly by the client because they should be doable at any time, then we call
+`keyDown()` or `keyUp()` on the client's holder and the client's mob's focus.
+By default `mob.focus` is the mob itself, but you can set it to any datum to give control of a
+client's keypresses to another object. This would be a good way to handle a menu or driving
+a mech. You can also set it to null to disregard input from a certain user.
+
+Movement is handled by having each client call `client.keyLoop()` every game tick.
+As above, this calls holder and `focus.keyLoop()`. `atom/movable/keyLoop()` handles movement
+Try to keep the calculations in this proc light. It runs every tick for every client after all!
+
+You can also tell which keys are being held down now. Each client a list of keys pressed called
+`keys_held`. Each entry is a key as a text string associated with the world.time when it was
+pressed.
+
+No client-set keybindings at this time, but it shouldn't be too hard if someone wants.
+
+Notes about certain keys:
+
+* `Tab` has client-sided behavior but acts normally
+* `T`, `O`, and `M` move focus to the input when pressed. This fires the keyUp macro right away.
+* `\` needs to be escaped in the dmf so any usage is `\\`
+
+You cannot `TICK_CHECK` or check `world.tick_usage` inside of procs called by key down and up
+events. They happen outside of a byond tick and have no meaning there. Key looping
+works correctly since it's part of a subsystem, not direct input.
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
new file mode 100644
index 0000000000..dcc1df09ec
--- /dev/null
+++ b/code/modules/keybindings/setup.dm
@@ -0,0 +1,61 @@
+/client
+ /// A rolling buffer of any keys held currently
+ var/list/keys_held = list()
+ ///used to keep track of the current rolling buffer position
+ var/current_key_address = 0
+
+ var/client_keysend_amount = 0
+ var/next_keysend_reset = 0
+ var/next_keysend_trip_reset = 0
+ var/keysend_tripped = FALSE
+ /// 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"
+ winset(src, null, erase_output)
+
+/client/proc/set_macros()
+ set waitfor = FALSE
+
+ //Reset and populate the rolling buffer
+ keys_held.Cut()
+ for(var/i in 1 to HELD_KEY_BUFFER_LENGTH)
+ keys_held += null
+
+ 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]")
+ winset(src, null, "map.focus=true input.background-color=[COLOR_INPUT_DISABLED] mainwindow.macro=default")
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index c3433f3e94..ee686a528f 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -3,10 +3,12 @@ Add fingerprints to items when we put them in our hands.
This saves us from having to call add_fingerprint() any time something is put in a human's hands programmatically.
*/
-/mob/living/carbon/human/verb/quick_equip()
+/mob/verb/quick_equip()
set name = "quick-equip"
set hidden = 1
+ return
+/mob/living/carbon/human/quick_equip()
if(ishuman(src))
var/mob/living/carbon/human/H = src
var/obj/item/I = H.get_active_hand()
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 074945ad40..e7c4fe6233 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -107,6 +107,9 @@
//set macro to normal incase it was overriden (like cyborg currently does)
//INF winset(src, null, "mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true input.background-color=#d3b5b5")
+ if(client && SSinput.initialized)
+ client.set_macros()
+
/mob/living/carbon/Login()
. = ..()
if(internals && internal)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index dcef99ee55..11ea1ffbba 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -52,6 +52,7 @@
move_intent = move_intents[1]
if(ispath(move_intent))
move_intent = decls_repository.get_decl(move_intent)
+ set_focus(src)
START_PROCESSING(SSmobs, src)
/mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
@@ -226,6 +227,7 @@
return log(2, mob_size / MOB_MEDIUM)
/mob/proc/Life()
+ set waitfor = FALSE
// if(organStructure)
// organStructure.ProcessOrgans()
return
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index e1b89d16d9..cd47557835 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -414,7 +414,7 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT)
else return I_HURT
//change a mob's act-intent. Input the intent as a string such as "help" or use "right"/"left
-/mob/verb/a_intent_change(input as text)
+/mob/proc/a_intent_change(input)
set name = "a-intent"
set hidden = 1
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index d0bc16c97a..e179a8f3b6 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -180,7 +180,10 @@
return
if(!mob)
return // Moved here to avoid nullrefs below
- return mob.SelfMove(direction)
+ . = mob.SelfMove(direction)
+ if(.)
+ next_move_dir_add = 0
+ next_move_dir_sub = 0
// Checks whether this mob is allowed to move in space
// Return 1 for movement, 0 for none,
diff --git a/code/modules/mob/new_player/login.dm b/code/modules/mob/new_player/login.dm
index 952ee59b5b..53d6f81196 100644
--- a/code/modules/mob/new_player/login.dm
+++ b/code/modules/mob/new_player/login.dm
@@ -24,6 +24,8 @@
if(client)
new_player_panel()
+ if(SSinput.initialized)
+ client.set_macros()
if(!SScharacter_setup.initialized)
SScharacter_setup.newplayers_requiring_init += src
diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm
index 4996d5d526..bf15cb8833 100644
--- a/code/modules/mob/typing_indicator.dm
+++ b/code/modules/mob/typing_indicator.dm
@@ -62,6 +62,16 @@ I IS TYPIN'!'
if(message)
say_verb(message)
+/mob/verb/whisper_wrapper()
+ set name = ".Whisper"
+ set hidden = 1
+
+ create_typing_indicator()
+ var/message = input(src, "", "whisper (text)") as text|null
+ remove_typing_indicator()
+ if(message)
+ whisper(message)
+
/mob/verb/me_wrapper()
set name = ".Me"
set hidden = 1
diff --git a/interface/interface.dm b/interface/interface.dm
index 10a758ab2c..737b3cc538 100644
--- a/interface/interface.dm
+++ b/interface/interface.dm
@@ -69,10 +69,11 @@
var/admin = {"
Admin:
-\tF5 = стать призраком/вернуться в тело
-\tF6 = панель игроков
-\tF7 = отправить ПМ
-\tF8 = невидимость
+\tF5 = Админ чат
+\tF6 = стать призраком/вернуться в тело
+\tF7 = панель игроков
+\tF8 = отправить ПМ
+\tF9 = невидимость
"}
var/hotkey_mode = {"
@@ -88,8 +89,9 @@ Hotkey-Mode: (Режим хоткеев включен)
\te = надеть
\tr = метнуть
\tt = сказать
-\t5 = эмоция
+\tm = эмоция
\tx = поменять руку
+\tc = перестать тащить
\tz = активировать объект в руке (или y)
\tj = переключить режим прицеливания
\tf = переключить-взаимодействие-влево
diff --git a/interface/skin.dmf b/interface/skin.dmf
index e1813b9894..3da6c13eb7 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -1,870 +1,36 @@
-macro "borghotkeymode"
- elem
- name = "Tab"
- command = ".winset \"mainwindow.macro=borgmacro hotkey_toggle.is-checked=false input.focus=true input.background-color=#d3b5b5\""
- elem
- name = "Center+REP"
- command = ".center"
- elem
- name = "ALT+Return"
- command = "Toggle-Fullscreen"
- elem
- name = "Northeast"
- command = ".northeast"
- elem
- name = "Southeast"
- command = ".southeast"
- elem
- name = "Southwest"
- command = ".southwest"
- elem
- name = "Northwest"
- command = ".northwest"
- elem
- name = "ALT+West"
- command = "westfaceperm"
- elem
- name = "CTRL+West"
- command = "westface"
- elem
- name = "West+REP"
- command = ".moveleft"
- elem
- name = "ALT+North"
- command = "northfaceperm"
- elem
- name = "CTRL+North"
- command = "northface"
- elem
- name = "North+REP"
- command = ".moveup"
- elem
- name = "ALT+East"
- command = "eastfaceperm"
- elem
- name = "CTRL+East"
- command = "eastface"
- elem
- name = "East+REP"
- command = ".moveright"
- elem
- name = "ALT+South"
- command = "southfaceperm"
- elem
- name = "CTRL+South"
- command = "southface"
- elem
- name = "South+REP"
- command = ".movedown"
- elem
- name = "Insert"
- command = "a-intent right"
- elem
- name = "Delete"
- command = "delete-key-pressed"
- elem
- name = "1"
- command = "toggle-module 1"
- elem
- name = "CTRL+1"
- command = "toggle-module 1"
- elem
- name = "2"
- command = "toggle-module 2"
- elem
- name = "CTRL+2"
- command = "toggle-module 2"
- elem
- name = "3"
- command = "toggle-module 3"
- elem
- name = "CTRL+3"
- command = "toggle-module 3"
- elem
- name = "4"
- command = "a-intent left"
- elem
- name = "CTRL+4"
- command = "a-intent left"
- elem
- name = "5"
- command = ".me"
- elem
- name = "A+REP"
- command = ".moveleft"
- elem
- name = "CTRL+A+REP"
- command = ".moveleft"
- elem
- name = "D+REP"
- command = ".moveright"
- elem
- name = "CTRL+D+REP"
- command = ".moveright"
- elem
- name = "F"
- command = "a-intent left"
- elem
- name = "CTRL+F"
- command = "a-intent left"
- elem
- name = "G"
- command = "a-intent right"
- elem
- name = "CTRL+G"
- command = "a-intent right"
- elem
- name = "J"
- command = "toggle-gun-mode"
- elem
- name = "CTRL+J"
- command = "toggle-gun-mode"
- elem
- name = "Q"
- command = "unequip-module"
- elem
- name = "CTRL+Q"
- command = "unequip-module"
- elem
- name = "R"
- command = ".southwest"
- elem
- name = "CTRL+R"
- command = ".southwest"
- elem "s_key"
- name = "S+REP"
- command = ".movedown"
- elem
- name = "CTRL+S+REP"
- command = ".movedown"
- elem
- name = "T"
- command = ".say"
- elem "w_key"
- name = "W+REP"
- command = ".moveup"
- elem
- name = "CTRL+W+REP"
- command = ".moveup"
- elem
- name = "X"
- command = ".northeast"
- elem
- name = "CTRL+X"
- command = ".northeast"
- elem
- name = "Y"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Y"
- command = "Activate-Held-Object"
- elem
- name = "Z"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Z"
- command = "Activate-Held-Object"
- elem
- name = "Numpad1"
- command = "body-r-leg"
- elem
- name = "Numpad2"
- command = "body-groin"
- elem
- name = "Numpad3"
- command = "body-l-leg"
- elem
- name = "Numpad4"
- command = "body-r-arm"
- elem
- name = "Numpad5"
- command = "body-chest"
- elem
- name = "Numpad6"
- command = "body-l-arm"
- elem
- name = "Numpad8"
- command = "body-toggle-head"
- elem
- name = "F1"
- command = "adminhelp"
- elem
- name = "CTRL+SHIFT+F1+REP"
- command = ".options"
- elem
- name = "F2"
- command = "ooc"
- elem
- name = "F2+REP"
- command = ".screenshot auto"
- elem
- name = "SHIFT+F2+REP"
- command = ".screenshot"
- elem
- name = "F3"
- command = ".say"
- elem
- name = "F4"
- command = ".me"
- elem
- name = "F5"
- command = "asay"
- elem
- name = "F6"
- command = "Player-Panel"
- elem
- name = "F7"
- command = "ssay"
- elem
- name = "F8"
- command = "Invisimin"
- elem
- name = "F12"
- command = "F12"
- elem
- name = ","
- command = "move-upwards"
- elem
- name = "."
- command = "move-down"
-
-macro "macro"
- elem
- name = "Tab"
- command = ".winset \"mainwindow.macro=hotkeymode hotkey_toggle.is-checked=true mapwindow.map.focus=true input.background-color=#f0f0f0\""
- elem
- name = "Center+REP"
- command = ".center"
- elem
- name = "ALT+Return"
- command = "Toggle-Fullscreen"
- elem
- name = "Northeast"
- command = ".northeast"
- elem
- name = "Southeast"
- command = ".southeast"
- elem
- name = "Southwest"
- command = ".southwest"
- elem
- name = "Northwest"
- command = ".northwest"
- elem
- name = "ALT+West"
- command = "westfaceperm"
- elem
- name = "CTRL+West"
- command = "westface"
- elem
- name = "West+REP"
- command = ".moveleft"
- elem
- name = "ALT+North"
- command = "northfaceperm"
- elem
- name = "CTRL+North"
- command = "northface"
- elem
- name = "North+REP"
- command = ".moveup"
- elem
- name = "ALT+East"
- command = "eastfaceperm"
- elem
- name = "CTRL+East"
- command = "eastface"
- elem
- name = "East+REP"
- command = ".moveright"
- elem
- name = "ALT+South"
- command = "southfaceperm"
- elem
- name = "CTRL+South"
- command = "southface"
- elem
- name = "South+REP"
- command = ".movedown"
- elem
- name = "Insert"
- command = "a-intent right"
- elem
- name = "Delete"
- command = "delete-key-pressed"
- elem
- name = "CTRL+1"
- command = "a-intent help"
- elem
- name = "CTRL+2"
- command = "a-intent disarm"
- elem
- name = "CTRL+3"
- command = "a-intent grab"
- elem
- name = "CTRL+4"
- command = "a-intent harm"
- elem
- name = "CTRL+A+REP"
- command = ".moveleft"
- elem
- name = "CTRL+D+REP"
- command = ".moveright"
- elem
- name = "CTRL+E"
- command = "quick-equip"
- elem
- name = "CTRL+F"
- command = "a-intent left"
- elem
- name = "CTRL+G"
- command = "a-intent right"
- elem
- name = "CTRL+SHIFT+G"
- command = ".configure graphics-hwmode on"
- elem
- name = "CTRL+Q"
- command = ".northwest"
- elem
- name = "CTRL+R"
- command = ".southwest"
- elem
- name = "CTRL+S+REP"
- command = ".movedown"
- elem
- name = "CTRL+W+REP"
- command = ".moveup"
- elem
- name = "CTRL+X"
- command = ".northeast"
- elem
- name = "CTRL+Y"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Z"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Numpad1"
- command = "body-r-leg"
- elem
- name = "CTRL+Numpad2"
- command = "body-groin"
- elem
- name = "CTRL+Numpad3"
- command = "body-l-leg"
- elem
- name = "CTRL+Numpad4"
- command = "body-r-arm"
- elem
- name = "CTRL+Numpad5"
- command = "body-chest"
- elem
- name = "CTRL+Numpad6"
- command = "body-l-arm"
- elem
- name = "CTRL+Numpad8"
- command = "body-toggle-head"
- elem
- name = "CTRL+Add"
- command = "move-upwards"
- elem
- name = "CTRL+Subtract"
- command = "move-down"
- elem
- name = "F1"
- command = "adminhelp"
- elem
- name = "CTRL+SHIFT+F1+REP"
- command = ".options"
- elem
- name = "F2"
- command = "ooc"
- elem
- name = "F2+REP"
- command = ".screenshot auto"
- elem
- name = "SHIFT+F2+REP"
- command = ".screenshot"
- elem
- name = "F3"
- command = ".say"
- elem
- name = "F4"
- command = ".me"
- elem
- name = "F5"
- command = "asay"
- elem
- name = "F6"
- command = "Player-Panel"
- elem
- name = "F7"
- command = "ssay"
- elem
- name = "F8"
- command = "Invisimin"
- elem
- name = "F12"
- command = "F12"
-
-macro "hotkeymode"
- elem
- name = "Tab"
- command = ".winset \"mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true input.background-color=#d3b5b5\""
- elem
- name = "Center+REP"
- command = ".center"
- elem
- name = "ALT+Return"
- command = "Toggle-Fullscreen"
- elem
- name = "Northeast"
- command = ".northeast"
- elem
- name = "Southeast"
- command = ".southeast"
- elem
- name = "Southwest"
- command = ".southwest"
- elem
- name = "Northwest"
- command = ".northwest"
- elem
- name = "ALT+West"
- command = "westfaceperm"
- elem
- name = "CTRL+West"
- command = "westface"
- elem
- name = "West+REP"
- command = ".moveleft"
- elem
- name = "ALT+North"
- command = "northfaceperm"
- elem
- name = "CTRL+North"
- command = "northface"
- elem
- name = "North+REP"
- command = ".moveup"
- elem
- name = "ALT+East"
- command = "eastfaceperm"
- elem
- name = "CTRL+East"
- command = "eastface"
- elem
- name = "East+REP"
- command = ".moveright"
- elem
- name = "ALT+South"
- command = "southfaceperm"
- elem
- name = "CTRL+South"
- command = "southface"
- elem
- name = "South+REP"
- command = ".movedown"
- elem
- name = "Insert"
- command = "a-intent right"
- elem
- name = "Delete"
- command = "delete-key-pressed"
- elem
- name = "1"
- command = "a-intent help"
- elem
- name = "CTRL+1"
- command = "a-intent help"
- elem
- name = "2"
- command = "a-intent disarm"
- elem
- name = "CTRL+2"
- command = "a-intent disarm"
- elem
- name = "3"
- command = "a-intent grab"
- elem
- name = "CTRL+3"
- command = "a-intent grab"
- elem
- name = "4"
- command = "a-intent harm"
- elem
- name = "CTRL+4"
- command = "a-intent harm"
- elem
- name = "5"
- command = ".me"
- elem
- name = "A+REP"
- command = ".moveleft"
- elem
- name = "CTRL+A+REP"
- command = ".moveleft"
- elem
- name = "D+REP"
- command = ".moveright"
- elem
- name = "CTRL+D+REP"
- command = ".moveright"
- elem
- name = "E"
- command = "quick-equip"
- elem
- name = "CTRL+E"
- command = "quick-equip"
- elem
- name = "F"
- command = "a-intent left"
- elem
- name = "CTRL+F"
- command = "a-intent left"
- elem
- name = "G"
- command = "a-intent right"
- elem
- name = "CTRL+G"
- command = "a-intent right"
- elem
- name = "H"
- command = "holster"
- elem
- name = "CTRL+H"
- command = "holster"
- elem
- name = "J"
- command = "toggle-gun-mode"
- elem
- name = "CTRL+J"
- command = "toggle-gun-mode"
- elem
- name = "Q"
- command = ".northwest"
- elem
- name = "CTRL+Q"
- command = ".northwest"
- elem
- name = "R"
- command = ".southwest"
- elem
- name = "CTRL+R"
- command = ".southwest"
- elem "s_key"
- name = "S+REP"
- command = ".movedown"
- elem
- name = "CTRL+S+REP"
- command = ".movedown"
- elem
- name = "T"
- command = ".say"
- elem "w_key"
- name = "W+REP"
- command = ".moveup"
- elem
- name = "CTRL+W+REP"
- command = ".moveup"
- elem
- name = "X"
- command = ".northeast"
- elem
- name = "CTRL+X"
- command = ".northeast"
- elem
- name = "Y"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Y"
- command = "Activate-Held-Object"
- elem
- name = "Z"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Z"
- command = "Activate-Held-Object"
- elem
- name = "Numpad1"
- command = "body-r-leg"
- elem
- name = "Numpad2"
- command = "body-groin"
- elem
- name = "Numpad3"
- command = "body-l-leg"
- elem
- name = "Numpad4"
- command = "body-r-arm"
- elem
- name = "Numpad5"
- command = "body-chest"
- elem
- name = "Numpad6"
- command = "body-l-arm"
- elem
- name = "Numpad8"
- command = "body-toggle-head"
- elem
- name = "F1"
- command = "adminhelp"
- elem
- name = "CTRL+SHIFT+F1+REP"
- command = ".options"
- elem
- name = "F2"
- command = "ooc"
- elem
- name = "F2+REP"
- command = ".screenshot auto"
- elem
- name = "SHIFT+F2+REP"
- command = ".screenshot"
- elem
- name = "F3"
- command = ".say"
- elem
- name = "F4"
- command = ".me"
- elem
- name = "F5"
- command = "asay"
- elem
- name = "F6"
- command = "Player-Panel"
- elem
- name = "F7"
- command = "ssay"
- elem
- name = "F8"
- command = "Invisimin"
- elem
- name = "F12"
- command = "F12"
- elem
- name = ","
- command = "move-upwards"
- elem
- name = "."
- command = "move-down"
- elem
- name = "SHIFT"
- command = "setmovingquickly"
- elem
- name = "SHIFT+UP"
- command = "setmovingslowly"
-
-macro "borgmacro"
- elem
- name = "Tab"
- command = ".winset \"mainwindow.macro=borghotkeymode hotkey_toggle.is-checked=true mapwindow.map.focus=true input.background-color=#f0f0f0\""
- elem
- name = "Center+REP"
- command = ".center"
- elem
- name = "ALT+Return"
- command = "Toggle-Fullscreen"
- elem
- name = "Northeast"
- command = ".northeast"
- elem
- name = "Southeast"
- command = ".southeast"
- elem
- name = "Southwest"
- command = ".southwest"
- elem
- name = "Northwest"
- command = ".northwest"
- elem
- name = "ALT+West"
- command = "westfaceperm"
- elem
- name = "CTRL+West"
- command = "westface"
- elem
- name = "West+REP"
- command = ".moveleft"
- elem
- name = "ALT+North"
- command = "northfaceperm"
- elem
- name = "CTRL+North"
- command = "northface"
- elem
- name = "North+REP"
- command = ".moveup"
- elem
- name = "ALT+East"
- command = "eastfaceperm"
- elem
- name = "CTRL+East"
- command = "eastface"
- elem
- name = "East+REP"
- command = ".moveright"
- elem
- name = "ALT+South"
- command = "southfaceperm"
- elem
- name = "CTRL+South"
- command = "southface"
- elem
- name = "South+REP"
- command = ".movedown"
- elem
- name = "Insert"
- command = "a-intent right"
- elem
- name = "Delete"
- command = "delete-key-pressed"
- elem
- name = "CTRL+1"
- command = "toggle-module 1"
- elem
- name = "CTRL+2"
- command = "toggle-module 2"
- elem
- name = "CTRL+3"
- command = "toggle-module 3"
- elem
- name = "CTRL+4"
- command = "a-intent left"
- elem
- name = "CTRL+A+REP"
- command = ".moveleft"
- elem
- name = "CTRL+D+REP"
- command = ".moveright"
- elem
- name = "CTRL+F"
- command = "a-intent left"
- elem
- name = "CTRL+G"
- command = "a-intent right"
- elem
- name = "CTRL+Q"
- command = ".northwest"
- elem
- name = "CTRL+R"
- command = ".southwest"
- elem
- name = "CTRL+S+REP"
- command = ".movedown"
- elem
- name = "CTRL+W+REP"
- command = ".moveup"
- elem
- name = "CTRL+X"
- command = ".northeast"
- elem
- name = "CTRL+Y"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Z"
- command = "Activate-Held-Object"
- elem
- name = "CTRL+Numpad1"
- command = "body-r-leg"
- elem
- name = "CTRL+Numpad2"
- command = "body-groin"
- elem
- name = "CTRL+Numpad3"
- command = "body-l-leg"
- elem
- name = "CTRL+Numpad4"
- command = "body-r-arm"
- elem
- name = "CTRL+Numpad5"
- command = "body-chest"
- elem
- name = "CTRL+Numpad6"
- command = "body-l-arm"
- elem
- name = "CTRL+Numpad8"
- command = "body-toggle-head"
- elem
- name = "CTRL+Add"
- command = "move-upwards"
- elem
- name = "CTRL+Subtract"
- command = "move-down"
- elem
- name = "F1"
- command = "adminhelp"
- elem
- name = "CTRL+SHIFT+F1+REP"
- command = ".options"
- elem
- name = "F2"
- command = "ooc"
- elem
- name = "F2+REP"
- command = ".screenshot auto"
- elem
- name = "SHIFT+F2+REP"
- command = ".screenshot"
- elem
- name = "F3"
- command = ".say"
- elem
- name = "F4"
- command = ".me"
- elem
- name = "F5"
- command = "asay"
- elem
- name = "F6"
- command = "Player-Panel"
- elem
- name = "F7"
- command = "ssay"
- elem
- name = "F8"
- command = "Invisimin"
- elem
- name = "F12"
- command = "F12"
-
+macro "default"
menu "menu"
- elem
+ elem
name = "&File"
command = ""
saved-params = "is-checked"
- elem
- name = "&Quick screenshot\tF2"
- command = ".screenshot auto"
- category = "&File"
- saved-params = "is-checked"
- elem
+ elem
name = "&Save screenshot as...\tShift+F2"
command = ".screenshot"
category = "&File"
saved-params = "is-checked"
- elem
+ elem
name = ""
command = ""
category = "&File"
saved-params = "is-checked"
- elem
+ elem
name = "&Reconnect"
command = ".reconnect"
category = "&File"
saved-params = "is-checked"
- elem
+ elem
name = "&Check ping"
command = ".ping"
category = "&File"
saved-params = "is-checked"
- elem
+ elem
name = "&Quit"
command = ".quit"
category = "&File"
saved-params = "is-checked"
- elem
+ elem
name = "&Size"
command = ""
saved-params = "is-checked"
@@ -911,7 +77,7 @@ menu "menu"
can-check = true
group = "size"
saved-params = "is-checked"
- elem
+ elem
name = "&Scaling"
command = ""
saved-params = "is-checked"
@@ -936,16 +102,16 @@ menu "menu"
can-check = true
group = "scale"
saved-params = "is-checked"
- elem
+ elem
name = "&Help"
command = ""
saved-params = "is-checked"
- elem
+ elem
name = "&Admin help\tF1"
command = "adminhelp"
category = "&Help"
saved-params = "is-checked"
- elem
+ elem
name = "&Hotkeys"
command = "hotkeys-help"
category = "&Help"
@@ -973,16 +139,6 @@ window "mainwindow"
anchor2 = none
is-visible = false
saved-params = ""
- elem "hotkey_toggle"
- type = BUTTON
- pos = 560,420
- size = 80x20
- anchor1 = 100,100
- anchor2 = none
- saved-params = ""
- text = "Hotkey Toggle"
- command = ".winset \"mainwindow.macro!=macro ? mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true : mainwindow.macro=hotkeymode hotkey_toggle.is-checked=true mapwindow.map.focus=true\""
- button-type = pushbox
elem "mainvsplit"
type = CHILD
pos = 3,0
@@ -995,7 +151,7 @@ window "mainwindow"
elem "input"
type = INPUT
pos = 3,420
- size = 517x20
+ size = 600x20
anchor1 = 0,100
anchor2 = 100,100
background-color = #d3b5b5
@@ -1004,7 +160,7 @@ window "mainwindow"
saved-params = "command"
elem "saybutton"
type = BUTTON
- pos = 520,420
+ pos = 600,420
size = 40x20
anchor1 = 100,100
anchor2 = none
@@ -1308,4 +464,3 @@ window "TelecommsIDE"
command = "cancel"
multi-line = true
no-command = true
-