Skip to content

Commit

Permalink
Automatic Input Redirection (#458)
Browse files Browse the repository at this point in the history
* Automatic Input Redirection
The map element will now yield to the command bar for Classic input.
The game will inform you of any macros not applied.
It will also report extra stuff, I'll fix it later.

* Tighten things up
Player Panel reports input mode

* Protected Macros

* More macro magic

* hate, let me tell you about hate
let me tell you how much I've come to hate SSInput and TG's decisions
since I began to stare into this code. There are roughly five hundred
lines of code split across 15 files that power the keyloop. If the word
hate were to replace every mere bit of every character of these hundreds
of lines, it would not equal one one-billionth of the hate I feel for this system
at this micro-instant. Hate. Hate.

* Allows silencing the bad hotkey warning
The code is inherently imperfect.

* Proper multimacro

* Update code/_compile_options.dm

* Prevent Doublestriking keys

* Remove unnecessary failsafe
  • Loading branch information
francinum authored Sep 16, 2023
1 parent 6ac143f commit 69d71e7
Show file tree
Hide file tree
Showing 24 changed files with 299 additions and 36 deletions.
5 changes: 4 additions & 1 deletion code/_compile_options.dm
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@
///~~Requires VERB_STRESS_TEST to be defined~~
// #define FORCE_VERB_OVERTIME


///Uncomment this to enable a set of debugging verbs for client macros
///This ability is given to all clients, and I'm not going to bother vetting how safe this is.
///This will generate a compile warning.
//#define MACRO_TEST

/////////////////////// REFERENCE TRACKING

Expand Down
113 changes: 108 additions & 5 deletions code/controllers/subsystem/input.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ VERB_MANAGER_SUBSYSTEM_DEF(input)

use_default_stats = FALSE

/// Standard macroset *ALL* players get
var/list/macro_set
/// Macros applied only to hotkey users
var/list/hotkey_only_set
/// Macros applied onlt to classic users
var/list/classic_only_set
/// Typecache of all unprintable keys that are safe for classic to bind
var/list/unprintables_cache
/// Macro IDs we shouldn't clear during client.clear_macros()
var/list/protected_macro_ids

///running average of how many clicks inputted by a player the server processes every second. used for the subsystem stat entry
var/clicks_per_second = 0
Expand All @@ -34,11 +43,105 @@ VERB_MANAGER_SUBSYSTEM_DEF(input)
// This is for when macro sets are eventualy datumized
/datum/controller/subsystem/verb_manager/input/proc/setup_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" = "Reset-Held-Keys",
// These could probably just be put in the skin. I actually don't understand WHY they aren't just in the skin. Besides the use of defines for Tab.
"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" = "Reset-Held-Keys",
)
hotkey_only_set = list(
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
)
classic_only_set = list(
//We need to force these to capture them for macro modifiers.
//Did I mention I fucking despise the way this system works at a base, almost reptilian-barely-understands-consciousness level?
//Because I do.
"Alt" = "\"KeyDown Alt\"",
"Alt+UP" = "\"KeyUp Alt\"",
"Ctrl" = "\"KeyDown Ctrl\"",
"Ctrl+UP" = "\"KeyUp Ctrl\"",
)
// This list may be out of date, and may include keys not actually legal to bind? The only full list is from 2008. http://www.byond.com/docs/notes/macro.html
unprintables_cache = list(
// Arrow Keys
"North" = TRUE,
"West" = TRUE,
"East" = TRUE,
"South" = TRUE,
// Numpad-Lock Disabled
"Northwest" = TRUE, // KP_Home
"Northeast" = TRUE, // KP_PgUp
"Center" = TRUE,
"Southwest" = TRUE, // KP_End
"Southeast" = TRUE, // KP_PgDn
// Keys you really shouldn't touch, but are technically unprintable
"Return" = TRUE,
"Escape" = TRUE,
"Delete" = TRUE,
// Things I'm not sure BYOND actually supports anymore.
"Select" = TRUE,
"Execute" = TRUE,
"Snapshot" = TRUE,
"Attn" = TRUE,
"CrSel" = TRUE,
"ExSel" = TRUE,
"ErEOF" = TRUE,
"Zoom" = TRUE,
"PA1" = TRUE,
"OEMClear" = TRUE,
// Things the modern ref says is okay
"Pause" = TRUE,
"Play" = TRUE,
"Insert" = TRUE,
"Help" = TRUE,
"LWin" = TRUE,
"RWin" = TRUE,
"Apps" = TRUE,
"Numpad0" = TRUE,
"Numpad1" = TRUE,
"Numpad2" = TRUE,
"Numpad3" = TRUE,
"Numpad4" = TRUE,
"Numpad5" = TRUE,
"Numpad6" = TRUE,
"Numpad7" = TRUE,
"Numpad8" = TRUE,
"Numpad9" = TRUE,
"Multiply" = TRUE,
"Add" = TRUE,
"Separator" = TRUE,
"Subtract" = TRUE,
"Decimal" = TRUE,
"Divide" = TRUE,
"F1" = TRUE,
"F2" = TRUE,
"F3" = TRUE,
"F4" = TRUE,
"F5" = TRUE,
"F6" = TRUE,
"F7" = TRUE,
"F8" = TRUE,
"F9" = TRUE,
"F10" = TRUE,
"F11" = TRUE,
"F12" = TRUE,
"F13" = TRUE,
"F14" = TRUE,
"F15" = TRUE,
"F16" = TRUE,
"F17" = TRUE,
"F18" = TRUE,
"F19" = TRUE,
"F20" = TRUE,
"F21" = TRUE,
"F22" = TRUE,
"F23" = TRUE,
"F24" = TRUE,
)
// Macro IDs we don't delete on wipe, Usually stuff baked into the skin, or that we have to be more careful with.
protected_macro_ids = list(
"PROTECTED-Shift",
"PROTECTED-ShiftUp"
)

// Badmins just wanna have fun ♪
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/_keybindings.dm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
var/full_name
var/description = ""
var/category = CATEGORY_MISC
var/weight = WEIGHT_LOWEST
var/keybind_signal

/datum/keybinding/New()
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/admin.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/admin
category = CATEGORY_ADMIN
weight = WEIGHT_ADMIN

/datum/keybinding/admin/can_use(client/user)
return user.holder ? TRUE : FALSE
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/carbon.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/carbon
category = CATEGORY_CARBON
weight = WEIGHT_MOB

/datum/keybinding/carbon/can_use(client/user)
return iscarbon(user.mob)
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/client.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/client
category = CATEGORY_CLIENT
weight = WEIGHT_HIGHEST


/datum/keybinding/client/admin_help
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/emote.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/emote
category = CATEGORY_EMOTE
weight = WEIGHT_EMOTE
keybind_signal = COMSIG_KB_EMOTE
var/emote_key

Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/human.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/human
category = CATEGORY_HUMAN
weight = WEIGHT_MOB

/datum/keybinding/human/can_use(client/user)
return ishuman(user.mob)
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/living.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/living
category = CATEGORY_HUMAN
weight = WEIGHT_MOB

/datum/keybinding/living/can_use(client/user)
return isliving(user.mob)
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/mob.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/mob
category = CATEGORY_HUMAN
weight = WEIGHT_MOB

/datum/keybinding/mob/stop_pulling
hotkey_keys = list("H", "Delete")
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/movement.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/movement
category = CATEGORY_MOVEMENT
weight = WEIGHT_HIGHEST

/datum/keybinding/movement/north
hotkey_keys = list("W", "North")
Expand Down
1 change: 0 additions & 1 deletion code/datums/keybinding/robot.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/datum/keybinding/robot
category = CATEGORY_ROBOT
weight = WEIGHT_ROBOT

/datum/keybinding/robot/can_use(client/user)
return iscyborg(user.mob)
Expand Down
1 change: 1 addition & 0 deletions code/modules/admin/verbs/admingame.dm
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
if(M.client.byond_version)
full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]"
body += "<br>\[<b>Byond version:</b> [full_version]\]<br>"
body += "<br><b>Input Mode:</b> [M.client.hotkeys ? "Using Hotkeys" : "Using Classic Input"]<br>"


body += "<br><br>\[ "
Expand Down
2 changes: 2 additions & 0 deletions code/modules/client/client_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@
/// rate limiting for the crew manifest
var/crew_manifest_delay

/// Semaphore for macro updates, so that they all complete and don't stomp over each other.
var/updating_macros = FALSE
/// A buffer of currently held keys.
var/list/keys_held = list()
/// A buffer for combinations such of modifiers + keys (ex: CtrlD, AltE, ShiftT). Format: `"key"` -> `"combo"` (ex: `"D"` -> `"CtrlD"`)
Expand Down
12 changes: 7 additions & 5 deletions code/modules/client/client_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
winset(src, null, "input.background-color=[COLOR_INPUT_DISABLED]")

else
winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED]")
winset(src, null, "input.background-color=[COLOR_INPUT_ENABLED]")

SEND_SIGNAL(src, COMSIG_CLIENT_CLICK, object, location, control, params, usr)

Expand Down Expand Up @@ -1110,6 +1110,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
movement_keys = list()
for(var/kb_name in D.key_bindings)
for(var/key in D.key_bindings[kb_name])
if(!hotkeys && !SSinput.unprintables_cache[key])
continue
switch(kb_name)
if("North")
movement_keys[key] = NORTH
Expand Down Expand Up @@ -1233,12 +1235,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
/client/proc/set_right_click_menu_mode(shift_only)
if(shift_only)
winset(src, "mapwindow.map", "right-click=true")
winset(src, "ShiftUp", "is-disabled=false")
winset(src, "Shift", "is-disabled=false")
winset(src, "default.PROTECTED-Shift", "command=\".winset :map.right-click=false\nKeyDown Shift\"")
winset(src, "default.PROTECTED-ShiftUp", "command=\".winset :map.right-click=true\nKeyUp Shift\"")
else
winset(src, "mapwindow.map", "right-click=false")
winset(src, "default.Shift", "is-disabled=true")
winset(src, "default.ShiftUp", "is-disabled=true")
winset(src, "default.PROTECTED-Shift", "command=\"KeyDown Shift\"")
winset(src, "default.PROTECTED-ShiftUp", "command=\"KeyUp Shift\"")

/client/proc/update_ambience_pref()
if(prefs.toggles & SOUND_AMBIENCE)
Expand Down
7 changes: 7 additions & 0 deletions code/modules/client/preferences/hotkeys.dm
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@

/datum/preference/toggle/hotkeys/apply_to_client(client/client, value)
client.hotkeys = value
if(SSinput.initialized)
client.set_macros() //They've changed their preferences, We need to rewrite the macro set again.

/datum/preference/toggle/hotkeys_silence
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "hotkeys_silence"
savefile_identifier = PREFERENCE_PLAYER
1 change: 1 addition & 0 deletions code/modules/client/preferences/middleware/keybindings.dm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
preferences.key_bindings = deep_copy_list(GLOB.default_hotkeys)
preferences.key_bindings_by_key = preferences.get_key_bindings_by_key(preferences.key_bindings)
preferences.update_static_data(user)
preferences.parent.set_macros()

return TRUE

Expand Down
94 changes: 94 additions & 0 deletions code/modules/client/verbs/testing.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Macro Test Tools
* I wrote these in a fit of agony because debugging the skin is an actual feat of self-flagelation
* Some of them aren't useful, and were just part of me discovering what crimes TG does to the skin.
*
* dump_macroset_ids():
* * Dumps all elements of type macros (which are macrosets, according to the ref, but they contain macros. It's confusing, right?)
* * This isn't particularly useful. It should almost always return just "default"
*
* dump_set():
* * Dumps the contents of the "default" macroset, Which is all currently bound macros, by name, essentially listing all bound macro handles.
*
* arbitrary_winget(cmd as text):
* * A way to quickly run wingets, and get the output, since the debugger no likey sleepy procs.
*
* arbitrary_winset(cmd as text):
* * Same as above, but for winsets.
*
* focuswatch():
* * Prints the current element with focus, this can be null if something else in the OS has focus outside of BYOND. Useful for seeing where the ball is
* * in the demented juggling act TG decided was worth playing.
*
* winmon(cmd as text|null):
* * Derived from focuswatch's code, it repeatedly runs the same winset on loop, dumping the output to chat, Useful for watching for the results
* * of custom winsets.
*/

#ifdef MACRO_TEST
#warn !!UNSAFE!!: MacroTest Verbs Enabled, Do Not Merge.
/client
var/x_mt_watchingfocus = FALSE
var/x_mt_winmon_enabled = FALSE
var/list/x_mt_winmon_packet //Lazylist

/// Dumps the list of all macros. This should almost always be just default
/client/verb/dump_macroset_ids()
set name = "mt Dump Macroset IDs"
set category = "_MACRO_TEST"
to_chat(usr, (winget(src, "", "macros") || "NULL (Bad. Incredibly. Incredibly bad.)"))

/// List all children of default. Name for macros is their bound key.
/client/verb/dump_set()
set name = "mt Dump bindings"
set category = "_MACRO_TEST"
to_chat(usr, (winget(src, "default.*" , "name")|| "NULL (Bad. Real bad.)"))

/// A slightly more pleasant way to execute free wingets.
/client/verb/arbitrary_winget(cmd as text)
set name = "awing"
set desc = "Run an arbitrary Winset call, Space-separated arguments."
set category = "_MACRO_TEST"
var/list/parts = splittext(cmd, " ")
to_chat(usr, (winget(src, parts[1], parts[2]) || "NULL (Bad Call?)"))

/// A slightly more pleasant way to execute free winsets.
/client/verb/arbitrary_winset(cmd as text)
set name = "aswin"
set desc = "Run an arbitrary Winset call, Space-separated arguments."
set category = "_MACRO_TEST"
var/list/parts = splittext(cmd, " ")
winset(src, parts[1], parts[2])
to_chat(usr, ("CALLED: winset({client:[src.ckey]}, \"[parts[1]]\",\"[parts[2]]\")"))

/// Will dump the currently focused skin element to chat. Used for tracking down focus juggling issues.
/client/verb/focuswatch()
set name = "mt toggle focus watch"
set category = "_MACRO_TEST"
if(x_mt_watchingfocus)
x_mt_watchingfocus = FALSE
return
else
x_mt_watchingfocus = TRUE
while(x_mt_watchingfocus)
// Live-report the element with focus.
to_chat(usr, (winget(src, "", "focus") || "NULL (Entire game defocused?)"))
sleep(0.5 SECONDS) //Every half second

/client/verb/winmon(cmd as text|null)
set name = "winmon"
set desc = "Repeatedly run a winget to monitor it's value"
set category = "_MACRO_TEST"
if(x_mt_winmon_enabled || isnull(cmd))
x_mt_winmon_enabled = FALSE
return
else
x_mt_winmon_enabled = TRUE
var/list/parts = splittext(cmd, " ")
x_mt_winmon_packet = parts
while(x_mt_winmon_enabled)
// Repeatedly rerun the same winget to watch the value
var/winout = winget(src, x_mt_winmon_packet[1], x_mt_winmon_packet[2])
to_chat(usr, ( winout ? "WINMON:[winout]": "WINMON: NULL (Bad Call?)"))
sleep(0.5 SECONDS)
#endif
6 changes: 2 additions & 4 deletions code/modules/keybindings/bindings_client.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@
qdel(src)
return

//Focus Chat failsafe. Overrides movement checks to prevent WASD.
if(!hotkeys && length(_key) == 1 && _key != "Alt" && _key != "Ctrl" && _key != "Shift")
winset(src, null, "input.focus=true ; input.text=[url_encode(_key)]")
return
if(keys_held[_key])
return //Key is already held, prevent double-strike.

if(length(keys_held) >= HELD_KEY_BUFFER_LENGTH && !keys_held[_key])
keyUp(keys_held[1]) //We are going over the number of possible held keys, so let's remove the first one.
Expand Down
Loading

0 comments on commit 69d71e7

Please sign in to comment.