Merge pull request #32751 from ninjanomnom/keybindings

In-code key handling system, Take 2
This commit is contained in:
oranges
2017-12-05 22:15:13 +13:00
committed by GitHub
44 changed files with 421 additions and 1648 deletions
+6 -5
View File
@@ -21,25 +21,26 @@
usr.loc = O
usr.real_name = O.name
usr.name = O.name
usr.client.eye = O
usr.reset_perspective(O)
usr.control_object = O
SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/release(obj/O in world)
/proc/release()
set name = "Release Obj"
set category = "Object"
//usr.loc = get_turf(usr)
if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object
usr.real_name = usr.name_archive
usr.name_archive = ""
usr.name = usr.real_name
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.name = H.get_visible_name()
// usr.regenerate_icons() //So the name is updated properly
usr.loc = O.loc
usr.client.eye = usr
usr.loc = get_turf(usr.control_object)
usr.reset_perspective()
usr.control_object = null
SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+1 -2
View File
@@ -68,11 +68,10 @@
a_right.on_found(finder)
/obj/item/device/assembly_holder/Move()
..()
. = ..()
if(a_left && a_right)
a_left.holder_movement()
a_right.holder_movement()
return
/obj/item/device/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess
if(a_left && a_right)
+1 -2
View File
@@ -83,10 +83,9 @@
/obj/item/device/assembly/infra/Move()
var/t = dir
..()
. = ..()
setDir(t)
qdel(first)
return
/obj/item/device/assembly/infra/holder_movement()
if(!holder)
+2 -1
View File
@@ -195,7 +195,6 @@ GLOBAL_LIST(external_rsc_urls)
prefs.last_id = computer_id //these are gonna be used for banning
if(world.byond_version >= 511 && byond_version >= 511 && prefs.clientfps)
vars["fps"] = prefs.clientfps
sethotkeys(1) //set hoykeys from preferences (from_pref = 1)
log_access("Login: [key_name(src)] from [address ? address : "localhost"]-[computer_id] || BYOND v[byond_version]")
var/alert_mob_dupe_login = FALSE
@@ -223,6 +222,8 @@ GLOBAL_LIST(external_rsc_urls)
. = ..() //calls mob.Login()
set_macros()
chatOutput.start() // Starts the chat
if(alert_mob_dupe_login)
-25
View File
@@ -1,25 +0,0 @@
/client/verb/sethotkeys(from_pref = 0 as num)
set name = "Set Hotkeys"
set hidden = TRUE
set waitfor = FALSE
set desc = "Used to set mob-specific hotkeys or load hoykey mode from preferences"
var/hotkey_default = "default"
var/hotkey_macro = "hotkeys"
var/current_setting
var/list/default_macros = list("default", "robot-default")
if(from_pref)
current_setting = (prefs.hotkeys ? hotkey_macro : hotkey_default)
else
current_setting = winget(src, "mainwindow", "macro")
if(mob)
hotkey_macro = mob.macro_hotkeys
hotkey_default = mob.macro_default
if(current_setting in default_macros)
winset(src, null, "mainwindow.macro=[hotkey_default] input.focus=true input.background-color=#d3b5b5")
else
winset(src, null, "mainwindow.macro=[hotkey_macro] mapwindow.map.focus=true input.background-color=#e0e0e0")
@@ -0,0 +1,15 @@
/datum/admins/key_down(_key, client/user)
switch(_key)
if("F5")
user.admin_ghost()
return
if("F6")
player_panel_new()
return
if("F7")
user.cmd_admin_pm_panel()
return
if("F8")
user.invisimin()
return
..()
+18
View File
@@ -0,0 +1,18 @@
// 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 = NONE
for(var/_key in user.keys_held)
movement_dir |= GLOB.movement_keys[_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)
@@ -0,0 +1,18 @@
/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 ..()
@@ -0,0 +1,50 @@
// 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
keys_held[_key] = world.time
var/movement = GLOB.movement_keys[_key]
if(!(next_move_dir_sub & movement))
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()
if("F2") // Screenshot. Hold shift to choose a name and location to save in
winset(src, null, "command=.screenshot [!keys_held["shift"] ? "auto" : ""]")
if("F12") // Toggles minimal HUD
mob.button_pressed_F12()
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
keys_held -= _key
var/movement = GLOB.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)
@@ -0,0 +1,6 @@
/mob/living/carbon/human/key_down(_key, client/user)
switch(_key)
if("E")
quick_equip()
return
return ..()
@@ -0,0 +1,7 @@
/mob/living/key_down(_key, client/user)
switch(_key)
if("B")
resist()
return
return ..()
+79
View File
@@ -0,0 +1,79 @@
// 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, "<span class='notice'>You are not pulling anything.</span>")
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, "<span class='warning'>You have nothing to drop in your hand!</span>")
else
dropItemToGround(I)
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(GLOB.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 ..()
@@ -0,0 +1,12 @@
/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 ..()
+20
View File
@@ -0,0 +1,20 @@
/datum
var/list/focusers //Only initialized when needed. Contains a list of mobs focusing on this.
/mob
var/datum/focus //What receives our keyboard inputs. src by default
/mob/proc/set_focus(datum/new_focus)
if(focus == new_focus)
return
if(new_focus)
if(!new_focus.focusers) //Set up the new focus
new_focus.focusers = list()
new_focus.focusers += src
if(focus)
focus.focusers -= src //Tell the old focus we're done with it
focus = new_focus
reset_perspective(focus) //Maybe this should be done manually? You figure it out, reader
+40
View File
@@ -0,0 +1,40 @@
# 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](http://www.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.
+62
View File
@@ -0,0 +1,62 @@
/client
var/list/keys_held = list() // A list of any keys held currently
// 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.
var/next_move_dir_add // On next move, add this dir to the move that would otherwise be done
var/next_move_dir_sub // On next move, subtract this dir from the move that would otherwise be done
// 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
return
// Keys used for movement
GLOBAL_LIST_INIT(movement_keys, list(
"W" = NORTH, "A" = WEST, "S" = SOUTH, "D" = EAST, // WASD
"North" = NORTH, "West" = WEST, "South" = SOUTH, "East" = EAST, // Arrow keys & Numpad
))
/*
A horrific battle against shitcode was fought here to find out some use details of winset
Aparently you need to wrap the entire proc + args in quotes if you intend on using args
But you don't need the quote wrappings to just call on a proc with no args
ex. winset(src, "default-Any", "command=keyDown \[\[*\]\]") fail: command = keyDown
ex. winset(src, "default-Any", "command=keyDown \"\[\[*\]\]\"") fail: same
ex. winset(src, "default-T", "command=say") works fine
ex. winset(src, "default-Any", "command=\"keyDown \[\[*\]\]\"") works fine
Thanks for the useful errors lummox ~ninjanomnom
*/
GLOBAL_LIST_INIT(default_macros, list(
"Tab" = "\".winset \\\"input.focus=true?map.focus=true input.background-color=#F0F0F0:input.focus=true input.background-color=#D3B5B5\\\"\"",
"O" = "ooc",
"T" = "say",
"M" = "me",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
))
/client/proc/set_macros()
set waitfor = FALSE
winset(src, null, "reset=true")
winset(src, null, "mainwindow.macro=default")
var/list/default = params2list(winget(src, "default.*", "command"))
for(var/i in 1 to length(default))
var/id = default[i]
winset(src, id, "parent=none")
var/list/default_macros = GLOB.default_macros
for(var/i in 1 to length(default_macros))
var/input = default_macros[i]
var/output = default_macros[input]
winset(src, "default-[input]", "parent=default;name=[input];command=[output]")
if(prefs.hotkeys)
winset(src, null, "mapwindow.map.focus=true input.background-color=#e0e0e0")
else
winset(src, null, "input.focus=true input.background-color=#d3b5b5")
+1
View File
@@ -16,6 +16,7 @@ INITIALIZE_IMMEDIATE(/mob/dead)
if(CONFIG_GET(string/cross_server_address))
verbs += /mob/dead/proc/server_hop
set_focus(src)
return INITIALIZE_HINT_NORMAL
/mob/dead/dust() //ghosts can't be vaporised.
@@ -11,7 +11,7 @@
..()
/mob/living/carbon/alien/humanoid/hunter/movement_delay()
. = -1 //hunters are sanic
. = -1 //hunters are sanic
. += ..() //but they still need to slow down on stun
@@ -5,8 +5,6 @@
icon_state = "robot"
maxHealth = 100
health = 100
macro_default = "robot-default"
macro_hotkeys = "robot-hotkeys"
bubble_icon = "robot"
designation = "Default" //used for displaying the prefix & getting the current module of cyborg
has_limbs = 1
@@ -247,17 +245,6 @@
return //won't work if dead
robot_alerts()
//for borg hotkeys, here module refers to borg inv slot, not core module
/mob/living/silicon/robot/verb/cmd_toggle_module(module as num)
set name = "Toggle Module"
set hidden = 1
toggle_module(module)
/mob/living/silicon/robot/verb/cmd_unequip_module()
set name = "Unequip Module"
set hidden = 1
uneq_active()
/mob/living/silicon/robot/proc/robot_alerts()
var/dat = ""
for (var/cat in alarms)
@@ -64,7 +64,7 @@
src.visible_message("<span class='danger'>[src] gets an evil-looking gleam in [p_their()] eye.</span>")
/mob/living/simple_animal/hostile/retaliate/goat/Move()
..()
. = ..()
if(!stat)
eat_plants()
@@ -283,9 +283,8 @@
/mob/living/simple_animal/hostile/syndicate/mecha_pilot/Move(NewLoc,Dir=0,step_x=0,step_y=0)
if(mecha && loc == mecha)
mecha.relaymove(src, Dir)
return
..()
return mecha.relaymove(src, Dir)
return ..()
/mob/living/simple_animal/hostile/syndicate/mecha_pilot/Goto(target, delay, minimum_distance)
-2
View File
@@ -33,8 +33,6 @@
sync_mind()
client.sethotkeys() //set mob specific hotkeys
update_client_colour()
if(client)
client.click_intercept = null
+1
View File
@@ -30,6 +30,7 @@
GLOB.dead_mob_list += src
else
GLOB.alive_mob_list += src
set_focus(src)
prepare_huds()
for(var/v in GLOB.active_alternate_appearances)
if(!v)
-3
View File
@@ -21,9 +21,6 @@
*/
var/zone_selected = null
var/macro_default = "default"
var/macro_hotkeys = "hotkeys"
var/damageoverlaytemp = 0
var/computer_id = null
var/lastattacker = null
+25 -97
View File
@@ -11,94 +11,12 @@
return (!mover.density || !density || lying)
//The byond version of these verbs wait for the next tick before acting.
// instant verbs however can run mid tick or even during the time between ticks.
/client/verb/moveup()
set name = ".moveup"
set instant = 1
Move(get_step(mob, NORTH), NORTH)
/client/verb/movedown()
set name = ".movedown"
set instant = 1
Move(get_step(mob, SOUTH), SOUTH)
/client/verb/moveright()
set name = ".moveright"
set instant = 1
Move(get_step(mob, EAST), EAST)
/client/verb/moveleft()
set name = ".moveleft"
set instant = 1
Move(get_step(mob, WEST), WEST)
/client/Northeast()
swap_hand()
return
/client/Southeast()
attack_self()
return
/client/Southwest()
if(iscarbon(usr))
var/mob/living/carbon/C = usr
C.toggle_throw_mode()
else
to_chat(usr, "<span class='danger'>This mob type cannot throw items.</span>")
return
/client/Northwest()
var/obj/item/I = usr.get_active_held_item()
if(!I)
to_chat(usr, "<span class='warning'>You have nothing to drop in your hand!</span>")
return
usr.dropItemToGround(I)
//This gets called when you press the delete button.
/client/verb/delete_key_pressed()
set hidden = 1
if(!isliving(usr))
return
if(!usr.pulling)
to_chat(usr, "<span class='notice'>You are not pulling anything.</span>")
return
usr.stop_pulling()
/client/verb/swap_hand()
set category = "IC"
set name = "Swap hands"
if(mob)
mob.swap_hand()
/client/verb/attack_self()
set hidden = 1
if(mob)
mob.mode()
return
/client/verb/drop_item()
set hidden = 1
if(!iscyborg(mob) && mob.stat == CONSCIOUS)
mob.dropItemToGround(mob.get_active_held_item())
return
/client/Center()
if(isobj(mob.loc))
var/obj/O = mob.loc
if(mob.canmove)
return O.relaymove(mob, 0)
return
/client/proc/Move_object(direct)
if(mob && mob.control_object)
if(mob.control_object.density)
@@ -114,19 +32,23 @@
#define MOVEMENT_DELAY_BUFFER_DELTA 1.25
/client/Move(n, direct)
if(world.time < move_delay)
if(world.time < move_delay) //do not move anything ahead of this check please
return FALSE
else
next_move_dir_add = 0
next_move_dir_sub = 0
var/old_move_delay = move_delay
move_delay = world.time+world.tick_lag //this is here because Move() can now be called mutiple times per tick
if(!mob || !mob.loc)
return FALSE
var/oldloc = mob.loc
if(!n || !direct)
return FALSE
if(mob.notransform)
return FALSE //This is sota the goto stop mobs from moving var
if(mob.control_object)
return Move_object(direct)
if(!isliving(mob))
return mob.Move(n,direct)
return mob.Move(n, direct)
if(mob.stat == DEAD)
mob.ghostize()
return FALSE
@@ -159,26 +81,32 @@
if(!mob.Process_Spacemove(direct))
return FALSE
//We are now going to move
var/delay = mob.movement_delay()
if(old_move_delay + (delay*MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time)
move_delay = old_move_delay + delay
var/add_delay = mob.movement_delay()
if(old_move_delay + (add_delay*MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time)
move_delay = old_move_delay
else
move_delay = delay + world.time
move_delay = world.time
var/oldloc = mob.loc
if(mob.confused)
var/newdir = 0
if(mob.confused > 40)
step(mob, pick(GLOB.cardinals))
newdir = pick(GLOB.alldirs)
else if(prob(mob.confused * 1.5))
step(mob, angle2dir(dir2angle(direct) + pick(90, -90)))
newdir = angle2dir(dir2angle(direct) + pick(90, -90))
else if(prob(mob.confused * 3))
step(mob, angle2dir(dir2angle(direct) + pick(45, -45)))
else
step(mob, direct)
else
. = ..()
newdir = angle2dir(dir2angle(direct) + pick(45, -45))
if(newdir)
direct = newdir
n = get_step(mob, direct)
. = ..()
if((direct & (direct - 1)) && mob.loc == n) //moved diagonally successfully
add_delay *= 2
if(mob.loc != oldloc)
move_delay += add_delay
if(.) // If mob is null here, we deserve the runtime
if(mob.throwing)
mob.throwing.finalize(FALSE)
+4 -1
View File
@@ -5,7 +5,10 @@
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
return
usr.say(message)
if(!message)
message = input(src, "What do you want to say?", "Speech") as null|text
if(message)
say(message)
/mob/verb/whisper_verb(message as text)
+1 -1
View File
@@ -53,7 +53,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
// You aren't allowed to move.
/obj/machinery/gravity_generator/Move()
..()
. = ..()
qdel(src)
/obj/machinery/gravity_generator/proc/set_broken()
@@ -76,6 +76,7 @@
/obj/machinery/field/containment/Move()
qdel(src)
return FALSE
// Abstract Field Class
@@ -152,7 +152,7 @@
qdel(src)
/obj/structure/particle_accelerator/Move()
..()
. = ..()
if(master && master.active)
master.toggle_power()
investigate_log("was moved whilst active; it <font color='red'>powered down</font>.", INVESTIGATE_SINGULO)