diff --git a/code/_onclick/autoclick.dm b/code/_onclick/autoclick.dm
deleted file mode 100644
index bd978a921d..0000000000
--- a/code/_onclick/autoclick.dm
+++ /dev/null
@@ -1,98 +0,0 @@
-/client
- var/list/atom/selected_target[2]
- var/obj/item/active_mousedown_item = null
- var/mouseParams = ""
- var/mouseLocation = null
- var/mouseObject = null
- var/mouseControlObject = null
-
-/client/MouseDown(object, location, control, params)
- var/delay = mob.CanMobAutoclick(object, location, params)
- if(delay)
- selected_target[1] = object
- selected_target[2] = params
- while(selected_target[1])
- Click(selected_target[1], location, control, selected_target[2])
- sleep(delay)
- active_mousedown_item = mob.canMobMousedown(object, location, params)
- if(active_mousedown_item)
- active_mousedown_item.onMouseDown(object, location, params, mob)
-
-/client/MouseUp(object, location, control, params)
- selected_target[1] = null
- if(active_mousedown_item)
- active_mousedown_item.onMouseUp(object, location, params, mob)
- active_mousedown_item = null
-
-/mob/proc/CanMobAutoclick(object, location, params)
-
-/mob/living/carbon/CanMobAutoclick(atom/object, location, params)
- if(!object.IsAutoclickable())
- return
- var/obj/item/h = get_active_held_item()
- if(h)
- . = h.CanItemAutoclick(object, location, params)
-
-/mob/proc/canMobMousedown(object, location, params)
-
-/mob/living/carbon/canMobMousedown(atom/object, location, params)
- var/obj/item/H = get_active_held_item()
- if(H)
- . = H.canItemMouseDown(object, location, params)
-
-/obj/item/proc/CanItemAutoclick(object, location, params)
-
-/obj/item/proc/canItemMouseDown(object, location, params)
- if(canMouseDown)
- return src
-
-/obj/item/proc/onMouseDown(object, location, params, mob)
- return
-
-/obj/item/proc/onMouseUp(object, location, params, mob)
- return
-
-/obj/item
- var/canMouseDown = FALSE
-
-/obj/item/gun
- var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
-
-/obj/item/gun/CanItemAutoclick(object, location, params)
- . = automatic
-
-/atom/proc/IsAutoclickable()
- . = 1
-
-/obj/screen/IsAutoclickable()
- . = 0
-
-/obj/screen/click_catcher/IsAutoclickable()
- . = 1
-
-//Please don't roast me too hard
-/client/MouseMove(object,location,control,params)
- mouseParams = params
- mouseLocation = location
- mouseObject = object
- mouseControlObject = control
- if(mob && LAZYLEN(mob.mousemove_intercept_objects))
- for(var/datum/D in mob.mousemove_intercept_objects)
- D.onMouseMove(object, location, control, params)
-
-/datum/proc/onMouseMove(object, location, control, params)
- return
-
-/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
- mouseParams = params
- mouseLocation = over_location
- mouseObject = over_object
- mouseControlObject = over_control
- if(selected_target[1] && over_object && over_object.IsAutoclickable())
- selected_target[1] = over_object
- selected_target[2] = params
- if(active_mousedown_item)
- active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
-
-/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
- return
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index 3d62cdd0ac..03bff92596 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -19,3 +19,120 @@
// recieve a mousedrop
/atom/proc/MouseDrop_T(atom/dropping, mob/user)
return
+
+
+/client
+ var/list/atom/selected_target[2]
+ var/obj/item/active_mousedown_item = null
+ var/mouseParams = ""
+ var/mouseLocation = null
+ var/mouseObject = null
+ var/mouseControlObject = null
+ var/middragtime = 0
+ var/atom/middragatom
+
+/client/MouseDown(object, location, control, params)
+ var/delay = mob.CanMobAutoclick(object, location, params)
+ if(delay)
+ selected_target[1] = object
+ selected_target[2] = params
+ while(selected_target[1])
+ Click(selected_target[1], location, control, selected_target[2])
+ sleep(delay)
+ active_mousedown_item = mob.canMobMousedown(object, location, params)
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseDown(object, location, params, mob)
+
+/client/MouseUp(object, location, control, params)
+ selected_target[1] = null
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseUp(object, location, params, mob)
+ active_mousedown_item = null
+
+/mob/proc/CanMobAutoclick(object, location, params)
+
+/mob/living/carbon/CanMobAutoclick(atom/object, location, params)
+ if(!object.IsAutoclickable())
+ return
+ var/obj/item/h = get_active_held_item()
+ if(h)
+ . = h.CanItemAutoclick(object, location, params)
+
+/mob/proc/canMobMousedown(object, location, params)
+
+/mob/living/carbon/canMobMousedown(atom/object, location, params)
+ var/obj/item/H = get_active_held_item()
+ if(H)
+ . = H.canItemMouseDown(object, location, params)
+
+/obj/item/proc/CanItemAutoclick(object, location, params)
+
+/obj/item/proc/canItemMouseDown(object, location, params)
+ if(canMouseDown)
+ return src
+
+/obj/item/proc/onMouseDown(object, location, params, mob)
+ return
+
+/obj/item/proc/onMouseUp(object, location, params, mob)
+ return
+
+/obj/item
+ var/canMouseDown = FALSE
+
+/obj/item/gun
+ var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
+
+/obj/item/gun/CanItemAutoclick(object, location, params)
+ . = automatic
+
+/atom/proc/IsAutoclickable()
+ . = 1
+
+/obj/screen/IsAutoclickable()
+ . = 0
+
+/obj/screen/click_catcher/IsAutoclickable()
+ . = 1
+
+//Please don't roast me too hard
+/client/MouseMove(object,location,control,params)
+ mouseParams = params
+ mouseLocation = location
+ mouseObject = object
+ mouseControlObject = control
+ if(mob && LAZYLEN(mob.mousemove_intercept_objects))
+ for(var/datum/D in mob.mousemove_intercept_objects)
+ D.onMouseMove(object, location, control, params)
+
+/datum/proc/onMouseMove(object, location, control, params)
+ return
+
+/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
+ var/list/L = params2list(params)
+ if (L["middle"])
+ if (src_object && src_location != over_location)
+ middragtime = world.time
+ middragatom = src_object
+ else
+ middragtime = 0
+ middragatom = null
+ mouseParams = params
+ mouseLocation = over_location
+ mouseObject = over_object
+ mouseControlObject = over_control
+ if(selected_target[1] && over_object && over_object.IsAutoclickable())
+ selected_target[1] = over_object
+ selected_target[2] = params
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+
+
+/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+ return
+
+/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params)
+ if (middragatom == src_object)
+ middragtime = 0
+ middragatom = null
+ ..()
\ No newline at end of file
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index a0fb41ed12..258a695a25 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -344,6 +344,14 @@
config_entry_value = null
min_val = 0
+/datum/config_entry/number/minute_click_limit
+ config_entry_value = 400
+ min_val = 0
+
+/datum/config_entry/number/second_click_limit
+ config_entry_value = 15
+ min_val = 0
+
/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error
config_entry_value = 600
min_val = 0
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 193d23e7bf..30e61dba1c 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -62,6 +62,7 @@
var/inprefs = FALSE
var/list/topiclimiter
+ var/list/clicklimiter
var/datum/chatOutput/chatOutput
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index ff2333cdb4..b036e826c9 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -650,6 +650,49 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.")
ip_intel = res.intel
+/client/Click(atom/object, atom/location, control, params)
+ var/ab = FALSE
+ var/list/L = params2list(params)
+ if (object && object == middragatom && L["left"])
+ ab = max(0, 5 SECONDS-(world.time-middragtime)*0.1)
+ var/mcl = CONFIG_GET(number/minute_click_limit)
+ if (!holder && mcl)
+ var/minute = round(world.time, 600)
+ if (!clicklimiter)
+ clicklimiter = new(LIMITER_SIZE)
+ if (minute != clicklimiter[CURRENT_MINUTE])
+ clicklimiter[CURRENT_MINUTE] = minute
+ clicklimiter[MINUTE_COUNT] = 0
+ clicklimiter[MINUTE_COUNT] += 1+(ab)
+ if (clicklimiter[MINUTE_COUNT] > mcl)
+ var/msg = "Your previous click was ignored because you've done too many in a minute."
+ if (minute != clicklimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them)
+ clicklimiter[ADMINSWARNED_AT] = minute
+
+ msg += " Administrators have been informed."
+ if (ab)
+ log_game("[key_name(src)] is using the middle click aimbot exploit")
+ message_admins("[key_name_admin(src)] [ADMIN_FLW(usr)] [ADMIN_KICK(usr)] is using the middle click aimbot exploit")
+ add_system_note("aimbot", "Is using the middle click aimbot exploit")
+
+ log_game("[key_name(src)] Has hit the per-minute click limit of [mcl] clicks in a given game minute")
+ message_admins("[key_name_admin(src)] [ADMIN_FLW(usr)] [ADMIN_KICK(usr)] Has hit the per-minute click limit of [mcl] clicks in a given game minute")
+ to_chat(src, "[msg]")
+ return
+
+ var/scl = CONFIG_GET(number/second_click_limit)
+ if (!holder && scl)
+ var/second = round(world.time, 10)
+ if (!clicklimiter)
+ clicklimiter = new(LIMITER_SIZE)
+ if (second != clicklimiter[CURRENT_SECOND])
+ clicklimiter[CURRENT_SECOND] = second
+ clicklimiter[SECOND_COUNT] = 0
+ clicklimiter[SECOND_COUNT] += 1+(!!ab)
+ if (clicklimiter[SECOND_COUNT] > scl)
+ to_chat(src, "Your previous click was ignored because you've done too many in a second")
+ return
+ ..()
/client/proc/add_verbs_from_config()
if(CONFIG_GET(flag/see_own_notes))
diff --git a/config/config.txt b/config/config.txt
index 290052308a..ac3cf9b3ab 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -386,6 +386,17 @@ SECOND_TOPIC_LIMIT 10
MINUTE_TOPIC_LIMIT 100
+
+## CLICK RATE LIMITING
+## Same as above, but applies to clicking on objects in the game window.
+## This should be a higher then the interface limit to allow for the spam clickly nature of most battles.
+## Admins are exempt from these limits.
+## Hitting the minute limit notifies admins.
+## Set to 0 to disable.
+SECOND_CLICK_LIMIT 15
+
+MINUTE_CLICK_LIMIT 400
+
##Error handling related options
## The "cooldown" time for each occurence of a unique error
#ERROR_COOLDOWN 600
diff --git a/tgstation.dme b/tgstation.dme
index f5a2ea4268..0e8718303b 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -149,7 +149,6 @@
#include "code\_js\menus.dm"
#include "code\_onclick\adjacent.dm"
#include "code\_onclick\ai.dm"
-#include "code\_onclick\autoclick.dm"
#include "code\_onclick\click.dm"
#include "code\_onclick\cyborg.dm"
#include "code\_onclick\drag_drop.dm"