SSInput and Diagonal Movement

This commit is contained in:
Chompstation Bot
2021-07-07 18:35:44 +00:00
parent 7e10238662
commit 1513fbe0cb
20 changed files with 4445 additions and 113 deletions

View File

@@ -0,0 +1,45 @@
// 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().
// THE TRADITIONAL STYLE FROM /TG (modified)
/atom/movable/keyLoop(client/user)
// Bail out if the user is holding the "face direction" key (Maybe?)
// TODO - I think this breaks non-hotkeys WASD movement, so maybe adopt the /tg solution)
if(user.mod_keys_held & CTRL_KEY)
return
var/must_call_move = FALSE
var/movement_dir = MOVEMENT_KEYS_TO_DIR(user.move_keys_held)
if(user.next_move_dir_add)
must_call_move = TRUE // So that next_move_dir_add gets cleared if its time.
movement_dir |= user.next_move_dir_add
if(user.next_move_dir_sub)
DEBUG_INPUT("[(user.next_move_dir_sub & movement_dir) ? "Actually" : "Want to"] subtract [dirs2text(user.next_move_dir_sub)] from [dirs2text(movement_dir)]")
must_call_move = TRUE // So that next_move_dir_sub gets cleared if its time.
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|SOUTH)) == (NORTH|SOUTH))
movement_dir &= ~(NORTH|SOUTH)
if((movement_dir & (EAST|WEST)) == (EAST|WEST))
movement_dir &= ~(EAST|WEST)
#ifdef CARDINAL_INPUT_ONLY
if(movement_dir & user.last_move_dir_pressed)
movement_dir = user.last_move_dir_pressed
else if (movement_dir == NORTHEAST || movement_dir == NORTHWEST)
DEBUG_INPUT("overriding to NORTH: movement_dir=[dirs2text(movement_dir)] last=[dirs2text(user.last_move_dir_pressed)]")
movement_dir = NORTH
else if (movement_dir == SOUTHEAST || movement_dir == SOUTHWEST)
DEBUG_INPUT("overriding to SOUTH: movement_dir=[dirs2text(movement_dir)] last=[dirs2text(user.last_move_dir_pressed)]")
movement_dir = SOUTH
#endif
// Compensate for client camera spinning (client.dir) so our movement still makes sense I guess.
if(movement_dir) // Only compensate if non-zero, as byond will auto-fill dir otherwise
movement_dir = turn(movement_dir, -dir2angle(user.dir))
// Move, but only if we actually are planing to move, or we need to clear the next move vars
if(movement_dir || must_call_move)
user.Move(get_step(src, movement_dir), movement_dir)

View File

@@ -0,0 +1,77 @@
// TODO - Optimize this into numerics if this ends up working out
var/global/list/MOVE_KEY_MAPPINGS = list(
"North" = NORTH_KEY,
"South" = SOUTH_KEY,
"East" = EAST_KEY,
"West" = WEST_KEY,
"W" = W_KEY,
"A" = A_KEY,
"S" = S_KEY,
"D" = D_KEY,
"Shift" = SHIFT_KEY,
"Ctrl" = CTRL_KEY,
"Alt" = ALT_KEY,
)
// These verbs are called for all movemen key press and release events
/client/verb/moveKeyDown(movekeyName as text)
set instant = TRUE
set hidden = TRUE
// set name = ".movekeydown"
set name = "KeyDown"
// Map text sent by skin.dmf to our numeric codes. (This can be optimized away once we update skin.dmf)
var/movekey = MOVE_KEY_MAPPINGS[movekeyName]
// Validate input. Must be one (and only one) of the key codes)
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
log_debug("Client [ckey] sent an illegal movement key down: [movekeyName] ([movekey])")
return
// Record that we are now holding the key!
move_keys_held |= (movekey & 0x0FF)
mod_keys_held |= (movekey & 0xF00)
// If we were NOT holding at the start of this move cycle and pressed it, remember that.
var/movement = MOVEMENT_KEYS_TO_DIR(movekey)
if(movement && !(next_move_dir_sub & movement) && !(mod_keys_held & CTRL_KEY)) // TODO-LESHANA - Possibly not holding Alt either
DEBUG_INPUT("Saving [dirs2text(movement)] into next_move_dir_ADD")
next_move_dir_add |= movement
#ifdef CARDINAL_INPUT_ONLY
if(movement)
DEBUG_INPUT("set last=[dirs2text(movement)]")
last_move_dir_pressed = movement
#endif
mob.focus?.key_down(movekey, src)
/client/verb/moveKeyUp(movekeyName as text)
set instant = TRUE
set hidden = TRUE
// set name = ".movekeyup"
set name = "KeyUp"
// Map text sent by skin.dmf to our numeric codes. (This can be optimized away once we update skin.dmf)
var/movekey = MOVE_KEY_MAPPINGS[movekeyName]
// Validate input. Must be one (and only one) of the key codes)
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
log_debug("Client [ckey] sent an illegal movement key up: [movekeyName] ([movekey])")
return
// Clear bit indicating we were holding the key
move_keys_held &= ~movekey
mod_keys_held &= ~movekey
// If we were holding at the start of this move cycle and now relased it, remember that.
var/movement = MOVEMENT_KEYS_TO_DIR(movekey)
if(movement && !(next_move_dir_add & movement))
DEBUG_INPUT("Saving [dirs2text(movement)] into next_move_dir_SUB")
next_move_dir_sub |= movement
mob.focus?.key_up(movekey, src)
// Called every game tick
/client/keyLoop()
mob.focus?.keyLoop(src)

View File

@@ -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.

View File

@@ -0,0 +1,34 @@
// 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 server tick
set waitfor = FALSE
return
/// Set mob's focus
/// TODO - Do we even need this concept?
/mob/proc/set_focus(datum/new_focus)
if(focus == new_focus)
return
focus = new_focus
/// Turns a keys bitfield into text showing all bits set
/proc/keys2text(keys)
if(!keys)
return ""
var/list/out = list()
if(keys & NORTH_KEY) out += "NORTH"
if(keys & SOUTH_KEY) out += "SOUTH"
if(keys & EAST_KEY) out += "EAST"
if(keys & WEST_KEY) out += "WEST"
if(keys & W_KEY) out += "W"
if(keys & A_KEY) out += "A"
if(keys & S_KEY) out += "S"
if(keys & D_KEY) out += "D"
if(keys & CTRL_KEY) out += "CTRL"
if(keys & SHIFT_KEY) out += "SHIFT"
if(keys & ALT_KEY) out += "ALT"
return out.Join(" ")