diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index c7bf018a..f9427409 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -81,3 +81,10 @@
#define SPAM_TRIGGER_WARNING 5 //Number of identical messages required before the spam-prevention will warn you to stfu
#define SPAM_TRIGGER_AUTOMUTE 10 //Number of identical messages required before the spam-prevention will automute you
+
+///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 100
+///Length of held key rolling buffer
+#define HELD_KEY_BUFFER_LENGTH 15
\ No newline at end of file
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 42094a4b..52f3515d 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -86,7 +86,7 @@
var/widescreenlayout = FALSE //CIT CHANGE - adds support for different hud layouts depending on widescreen pref
if(owner.client && owner.client.prefs && owner.client.prefs.widescreenpref) //CIT CHANGE - ditto
- widescreenlayout = TRUE // CIT CHANGE - ditto
+ widescreenlayout = FALSE // CIT CHANGE - ditto
var/obj/screen/using
var/obj/screen/inventory/inv_box
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 4106862c..4e7ec45b 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -75,3 +75,9 @@
var/datum/player_details/player_details //these persist between logins/logouts during the same round.
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
+
+
+ var/client_keysend_amount = 0
+ var/next_keysend_reset = 0
+ var/next_keysend_trip_reset = 0
+ var/keysend_tripped = FALSE
\ No newline at end of file
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index 4ffc10f9..c7719dcb 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -111,6 +111,11 @@
if(A)
load_program(A)
if("safety")
+ if(!issilicon(usr) && !IsAdminGhost(usr))
+ var/msg = "[key_name(usr)] attempted to emag the holodeck using a href they shouldn't have!"
+ message_admins(msg)
+ log_admin(msg)
+ return
obj_flags ^= EMAGGED
if((obj_flags & EMAGGED) && program && emag_programs[program.name])
emergency_shutdown()
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 548a734f..0b1e526d 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -3,8 +3,42 @@
/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_IN(src, 1)
+ 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_IN(src, 1)
+ return
+ //offset by 1 because the buffer address is 0 indexed because the math was simpler
+ keys_held[current_key_address + 1] = _key
+ //the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
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
@@ -35,7 +69,11 @@
set instant = TRUE
set hidden = TRUE
- keys_held -= _key
+ //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
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index 54df252f..a83e7241 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -1,9 +1,14 @@
/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
+ /// 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
+ /// 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
@@ -31,6 +36,12 @@
/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
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index a0e73c4e..47fd657c 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -141,6 +141,13 @@
ViewManifest()
if(href_list["SelectedJob"])
+ if(!SSticker || !SSticker.IsRoundInProgress())
+ var/msg = "[key_name(usr)] attempted to join the round using a href that shouldn't be available at this moment!"
+ log_admin(msg)
+ message_admins(msg)
+ to_chat(usr, "The round is either not ready, or has already finished...")
+ return
+
if(!GLOB.enter_allowed)
to_chat(usr, "There is an administrative lock on entering the game!")
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index b7d013c8..1bfff1a9 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -163,6 +163,8 @@
missing -= BP.body_zone
for(var/obj/item/I in BP.embedded_objects)
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n"
+ if(BP.broken)
+ msg += "[t_He] [t_has] \a [I] broken [BP.name]!\n"
for(var/X in disabled)
var/obj/item/bodypart/BP = X
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 2a9a60f3..82a241ef 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -673,6 +673,7 @@
var/status = ""
var/brutedamage = LB.brute_dam
var/burndamage = LB.burn_dam
+ var/broken = LB.broken
if(hallucination)
if(prob(30))
brutedamage += rand(30,40)
@@ -700,7 +701,8 @@
status += LB.medium_burn_msg
else if(burndamage > 0)
status += LB.light_burn_msg
-
+ if(broken == 1)
+ status = "broken"
if(status == "")
status = "OK"
var/no_damage
@@ -711,6 +713,7 @@
for(var/obj/item/I in LB.embedded_objects)
to_send += "\t There is \a [I] embedded in your [LB.name]!\n"
+ for(var/t in missing)
for(var/t in missing)
to_send += "Your [parse_zone(t)] is missing!\n"
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 1ccdf1a1..b0826b86 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -807,11 +807,16 @@ Pass a positive integer as an argument to override a bot's default speed.
switch(href_list["operation"])
if("patrol")
- auto_patrol = !auto_patrol
- bot_reset()
+ if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked))
+ return TRUE
if("remote")
remote_disabled = !remote_disabled
if("hack")
+ if(!issilicon(usr) && !IsAdminGhost(usr))
+ var/msg = "[key_name(usr)] attempted to hack a bot with a href that shouldn't be available!"
+ message_admins(msg)
+ log_admin(msg)
+ return TRUE
if(emagged != 2)
emagged = 2
hacked = TRUE
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index 2f4e1eea..4af79c1f 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -132,6 +132,9 @@ Auto Patrol: []"},
if(..())
return 1
+ if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked))
+ return TRUE
+
switch(href_list["operation"])
if("idcheck")
idcheck = !idcheck
diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm
index 81b491e6..7c6d8e08 100644
--- a/code/modules/surgery/bodyparts/bodyparts.dm
+++ b/code/modules/surgery/bodyparts/bodyparts.dm
@@ -21,6 +21,8 @@
var/held_index = 0 //are we a hand? if so, which one!
var/is_pseudopart = FALSE //For limbs that don't really exist, eg chainsaws
+ var/broken = FALSE //Broken bones
+
var/disabled = BODYPART_NOT_DISABLED //If disabled, limb is as good as missing
var/body_damage_coeff = 1 //Multiplier of the limb's damage that gets applied to the mob
var/stam_damage_coeff = 0.5
@@ -75,6 +77,8 @@
to_chat(user, "This limb has [brute_dam > 30 ? "severe" : "minor"] bruising.")
if(burn_dam > DAMAGE_PRECISION)
to_chat(user, "This limb has [burn_dam > 30 ? "severe" : "minor"] burns.")
+ if(broken == TRUE)
+ to_chat(user, "This limb is broken.")
/obj/item/bodypart/blob_act()
take_damage(max_damage)
diff --git a/icons/mob/human_parts_greyscale.dmi b/icons/mob/human_parts_greyscale.dmi
index 1bdc7e3c..97aeea4e 100644
Binary files a/icons/mob/human_parts_greyscale.dmi and b/icons/mob/human_parts_greyscale.dmi differ