diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index a50bf74d557..c85e06eb616 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -44,6 +44,7 @@
#define ADMIN_SM(user) "(SM)"
#define ADMIN_TP(user) "(TP)"
#define ADMIN_BSA(user) "(BSA)"
+#define ADMIN_KICK(user) "(KICK)"
#define ADMIN_CENTCOM_REPLY(user) "(RPLY)"
#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)"
#define ADMIN_SC(user) "(SC)"
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 7b8f3d2b722..4cd2df43e1c 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -232,6 +232,9 @@
var/list/gamemode_cache = null
+ var/minutetopiclimit
+ var/secondtopiclimit
+
/datum/configuration/New()
gamemode_cache = typecacheof(/datum/game_mode,TRUE)
for(var/T in gamemode_cache)
@@ -482,7 +485,10 @@
config.client_error_version = text2num(value)
if("client_error_message")
config.client_error_message = value
-
+ if("minute_topic_limit")
+ config.minutetopiclimit = text2num(value)
+ if("second_topic_limit")
+ config.secondtopiclimit = text2num(value)
else
diary << "Unknown setting in configuration: '[name]'"
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 7c18b69d356..9b9adff580b 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -59,4 +59,7 @@
var/avgping = 0
var/connection_time //world.time they connected
var/connection_realtime //world.realtime they connected
- var/connection_timeofday //world.timeofday they connected
\ No newline at end of file
+ var/connection_timeofday //world.timeofday they connected
+
+ var/inprefs = FALSE
+ var/list/topiclimiter
\ No newline at end of file
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index c292196e069..cd5d28757fb 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -3,6 +3,12 @@
////////////
#define UPLOAD_LIMIT 1048576 //Restricts client uploads to the server to 1MB //Could probably do with being lower.
+#define LIMITER_SIZE 5
+#define CURRENT_SECOND 1
+#define SECOND_COUNT 2
+#define CURRENT_MINUTE 3
+#define MINUTE_COUNT 4
+#define ADMINSWARNED_AT 5
/*
When somebody clicks a link in game, this Topic is called first.
It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever]
@@ -18,16 +24,55 @@
- If so, is there any protection against somebody spam-clicking a link?
If you have any questions about this stuff feel free to ask. ~Carn
*/
-/client/var/inprefs = FALSE
+
/client/Topic(href, href_list, hsrc)
if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null
return
+
// asset_cache
if(href_list["asset_cache_confirm_arrival"])
//src << "ASSET JOB [href_list["asset_cache_confirm_arrival"]] ARRIVED."
var/job = text2num(href_list["asset_cache_confirm_arrival"])
- completed_asset_jobs += job
- return
+ //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us
+ // into letting append to a list without limit.
+ if (job && job <= last_asset_job && !(job in completed_asset_jobs))
+ completed_asset_jobs += job
+ return
+
+ if (!holder && config.minutetopiclimit)
+ var/minute = round(world.time, 600)
+ if (!topiclimiter)
+ topiclimiter = new(LIMITER_SIZE)
+ if (minute != topiclimiter[CURRENT_MINUTE])
+ topiclimiter[CURRENT_MINUTE] = minute
+ topiclimiter[MINUTE_COUNT] = 0
+ topiclimiter[MINUTE_COUNT] += 1
+ if (topiclimiter[MINUTE_COUNT] > config.minutetopiclimit)
+ var/msg = "Your previous action was ignored because you've done too many in a minute."
+ if (minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them)
+ topiclimiter[ADMINSWARNED_AT] = minute
+ msg += " Administrators have been informed."
+ log_game("[key_name(src)] Has hit the per-minute topic limit of [config.minutetopiclimit] topic calls in a given game minute")
+ message_admins("[key_name_admin(src)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [config.minutetopiclimit] topic calls in a given game minute")
+ src << "[msg]"
+ return
+
+ if (!holder && config.secondtopiclimit)
+ var/second = round(world.time, 10)
+ if (!topiclimiter)
+ topiclimiter = new(LIMITER_SIZE)
+ if (second != topiclimiter[CURRENT_SECOND])
+ topiclimiter[CURRENT_SECOND] = second
+ topiclimiter[SECOND_COUNT] = 0
+ topiclimiter[SECOND_COUNT] += 1
+ if (topiclimiter[SECOND_COUNT] > config.secondtopiclimit)
+ src << "Your previous action was ignored because you've done too many in a second"
+ return
+
+ //Logs all hrefs
+ if(config && config.log_hrefs && href_logfile)
+ href_logfile << "[time2text(world.timeofday,"hh:mm")] [src] (usr:[usr]) || [hsrc ? "[hsrc] " : ""][href]
"
+
// Admin PM
if(href_list["priv_msg"])
if (href_list["ahelp_reply"])
@@ -36,10 +81,6 @@
cmd_admin_pm(href_list["priv_msg"],null)
return
- //Logs all hrefs
- if(config && config.log_hrefs && href_logfile)
- href_logfile << "[time2text(world.timeofday,"hh:mm")] [src] (usr:[usr]) || [hsrc ? "[hsrc] " : ""][href]
"
-
switch(href_list["_src_"])
if("holder")
hsrc = holder
diff --git a/config/config.txt b/config/config.txt
index a0b7c6854b2..fe8764358c5 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -297,3 +297,13 @@ ANNOUNCE_ADMIN_LOGOUT
#CLIENT_WARN_MESSAGE Byond is really close to releasing 510 beta as the stable release, please take this time to try it out. Reports are that the client preforms better then the version you are using, and also handles network lag better. Shortly after it's release we will end up using 510 client features and you will be forced to update.
#CLIENT_ERROR_VERSION 509
#CLIENT_ERROR_MESSAGE Your version of byond is not supported. Please upgrade.
+
+## TOPIC RATE LIMITING
+## This allows you to limit how many topic calls (clicking on a interface window) the client can do in any given game second and/or game minute.
+## Admins are exempt from these limits.
+## Hitting the minute limit notifies admins.
+## Set to 0 or comment out to disable.
+SECOND_TOPIC_LIMIT 10
+
+MINUTE_TOPIC_LIMIT 100
+