diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index b6c1c00339c..984e8418a60 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -27,9 +27,13 @@
else if (!dooc_allowed && !src.client.holder && (src.client.deadchat != 0))
usr << "\red OOC for dead mobs has been turned off."
return
- else if (src.client && (src.client.muted || src.client.muted_complete))
- src << "You are muted."
- return
+ else if (src.client)
+ if(src.client.muted_ooc)
+ src << "\red You cannot use OOC (muted by admins)."
+ return
+
+ if (src.client.handle_spam_prevention(msg,MUTE_OOC))
+ return
else if (findtext(msg, "byond://") && !src.client.holder)
src << "Advertising other servers is not allowed."
log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index f81c58ffb7e..7c86e0ac061 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -752,34 +752,23 @@ var/global/BSACooldown = 0
message_admins("\blue [key_name_admin(usr)] removed [t] from the goonlist.")
remove_goon(t)
*/
- if (href_list["mute2"])
- if ((src.rank in list( "Moderator", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" )))
- var/mob/M = locate(href_list["mute2"])
+ if (href_list["mute"])
+ if ((src.rank in list( "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" )))
+ var/mob/M = locate(href_list["mute"])
+ var/mute_type = href_list["mute_type"]
+ if(istext(mute_type))
+ mute_type = text2num(mute_type)
+ if(!isnum(mute_type))
+ return
if (ismob(M))
- if ((M.client && M.client.holder && (M.client.holder.level >= src.level)))
- alert("You cannot perform this action. You must be of a higher administrative rank!", null, null, null, null, null)
- return
if(!M.client)
src << "This mob doesn't have a client tied to it."
return
- M.client.muted = !M.client.muted
- log_admin("[key_name(usr)] has [(M.client.muted ? "muted" : "voiced")] [key_name(M)].")
- message_admins("\blue [key_name_admin(usr)] has [(M.client.muted ? "muted" : "voiced")] [key_name_admin(M)].", 1)
- M << "You have been [(M.client.muted ? "muted" : "voiced")]. Please resolve this in adminhelp."
- if (href_list["mute_complete"])
- if ((src.rank in list( "Moderator", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" )))
- var/mob/M = locate(href_list["mute_complete"])
- if (ismob(M))
if ((M.client && M.client.holder && (M.client.holder.level >= src.level)))
alert("You cannot perform this action. You must be of a higher administrative rank!", null, null, null, null, null)
return
- if(!M.client)
- src << "This mob doesn't have a client tied to it."
- return
- M.client.muted_complete = !M.client.muted_complete
- log_admin("[key_name(usr)] has [(M.client.muted_complete ? "completely muted" : "voiced (complete)")] [key_name(M)].")
- message_admins("\blue [key_name_admin(usr)] has [(M.client.muted_complete ? "completely muted" : "voiced (complete)")] [key_name_admin(M)].", 1)
- M << "You have been [(M.client.muted_complete ? "completely muted" : "voiced (complete)")]. You are unable to speak or even adminhelp"
+
+ cmd_admin_mute(M, mute_type)
if (href_list["c_mode"])
if ((src.rank in list( "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" )))
@@ -1234,6 +1223,10 @@ var/global/BSACooldown = 0
if (href_list["adminmoreinfo"])
var/mob/M = locate(href_list["adminmoreinfo"])
+ if(!M)
+ usr << "\blue The mob no longer exists."
+ return
+
if(src && src.owner)
var/location_description = ""
var/special_role_description = ""
@@ -2279,14 +2272,13 @@ var/global/BSACooldown = 0
if(M.client)
body += "| Prison | "
- body += "Mute: "
- if(M.client.muted_complete)
- body += "Completely Muted: (Allow adminhelp)"
- else
- if(M.client.muted)
- body += "Soft Mute: (Unmute) (Mute adminhelps)"
- else
- body += "Voiced: (Mute)"
+ body += "
Mute: "
+ body += "\[IC | "
+ body += "OOC | "
+ body += "PRAY | "
+ body += "ADMINHELP | "
+ body += "DEADCHAT\]"
+ body += "(toggle all)"
body += "
"
body += "Jump to | "
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 0fbe6e98981..ef915f3698a 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -1,14 +1,17 @@
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
-var/list/adminhelp_ignored_words = list("unknown","the","a","an", "monkey", "alien")
+var/list/adminhelp_ignored_words = list("unknown","the","a","an", "monkey", "alien", "as")
/client/verb/adminhelp(msg as text)
set category = "Admin"
set name = "Adminhelp"
- if (muted_complete)
- src << "Error: Admin-PM: You are completely muted."
+ if (muted_adminhelp)
+ src << "Error: Admin-PM: You cannot send adminhelps (Muted by admins)."
+ return
+
+ if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
return
if(!msg) return
@@ -80,7 +83,7 @@ var/list/adminhelp_ignored_words = list("unknown","the","a","an", "monkey", "ali
if (X.holder)
if(X.sound_adminhelp)
X << 'adminhelp.ogg'
- var/msg_to_send = "\blue HELP: [key_name(src, X)] (PP) (VV) (SM) (JMP) (CA): [msg]"
+ var/msg_to_send = "\blue HELP: [key_name(src, X)] (?) (PP) (VV) (SM) (JMP) (CA): [msg]"
msg_to_send = dd_replaceText(msg_to_send, "HOLDERREF", "\ref[X.holder]")
msg_to_send = dd_replaceText(msg_to_send, "ADMINREF", "\ref[X]")
X << msg_to_send
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index 8411ad31577..3894a31bed2 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -36,8 +36,11 @@
//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM.
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_admin_pm(var/client/C, var/msg)
- if(src.muted_complete)
- src << "Error: Admin-PM: You are completely muted."
+ if(src.muted_adminhelp)
+ src << "Error: Admin-PM: You are unable to use admin PM-s (muted by admins)."
+ return
+
+ if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
return
if( !C || !istype(C,/client) )
diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm
index 40f4ee1a588..f82d6ef0456 100644
--- a/code/modules/admin/verbs/adminsay.dm
+++ b/code/modules/admin/verbs/adminsay.dm
@@ -7,8 +7,11 @@
src << "Only administrators may use this command."
return
- if (src.muted || src.muted_complete)
- src << "You are muted."
+ if (src.muted_adminhelp)
+ src << "You cannot send ASAY messages (muted by admins)."
+ return
+
+ if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm
index 4ef4d3e72c7..9e4d5dc876a 100644
--- a/code/modules/admin/verbs/deadsay.dm
+++ b/code/modules/admin/verbs/deadsay.dm
@@ -7,7 +7,11 @@
return
if(!src.mob)
return
- if(src.muted || src.muted_complete)
+ if(src.muted_deadchat)
+ src << "\red You cannot send DSAY messages (muted by admins)."
+ return
+
+ if (src.handle_spam_prevention(msg,MUTE_DEADCHAT))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index f6f9cfa87dd..68d02a9afd6 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -7,14 +7,18 @@
if (!msg)
return
- if (usr.client && (usr.client.muted || usr.client.muted_complete))
- return
+ if (usr.client)
+ if(usr.client.muted_pray)
+ return
+
+ if (src.client.handle_spam_prevention(msg,MUTE_PRAY))
+ return
var/icon/cross = icon('storage.dmi',"bible")
for (var/mob/M in world)
if (M.client && M.client.holder && M.client.seeprayers)
- M << "\blue \icon[cross] PRAY: [key_name(src, M)] (PP) (VV) (SM) (JMP) (CA) (SC): [msg]"
+ M << "\blue \icon[cross] PRAY: [key_name(src, M)] (?) (PP) (VV) (SM) (JMP) (CA) (SC): [msg]"
usr << "Your prayers have been received by the gods."
feedback_add_details("admin_verb","PR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 7aa60025c2d..dff13da6bc8 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -123,25 +123,73 @@
message_admins("[key_name_admin(usr)] has toggled [key_name_admin(M)]'s nodamage to [(M.nodamage ? "On" : "Off")]", 1)
feedback_add_details("admin_verb","GOD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/cmd_admin_mute(mob/M as mob in world)
- set category = "Special Verbs"
- set name = "Admin Mute"
- if(!holder)
- src << "Only administrators may use this command."
- return
- if (M.client && M.client.holder && (M.client.holder.level >= holder.level))
- alert("You cannot perform this action. You must be of a higher administrative rank!", null, null, null, null, null)
- return
+proc/cmd_admin_mute(mob/M as mob, mute_type, automute = 0)
+ if(!automute)
+ if(usr && usr.client)
+ if(!usr.client.holder)
+ src << "Only administrators may use this command."
+ return
+ if (M.client && M.client.holder && (M.client.holder.level >= usr.client.holder.level))
+ alert("You cannot perform this action. You must be of a higher administrative rank!", null, null, null, null, null)
+ return
if(!M.client)
src << "This mob doesn't have a client tied to it."
return
- M.client.muted = !M.client.muted
- log_admin("[key_name(src)] has [(M.client.muted ? "muted" : "voiced")] [key_name(M)].")
- message_admins("[key_name_admin(src)] has [(M.client.muted ? "muted" : "voiced")] [key_name_admin(M)].", 1)
+ var/muteunmute = 0 //0 = unmuted; 1 = muted
+ var/mute_string = "unknown"
- M << "You have been [(M.client.muted ? "muted" : "voiced")]."
- feedback_add_details("admin_verb","MUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ //The '| automute' thing ensures that if an automute is being applied by code, it always mutes to prevent any potential for automute to unmute someone who was muted.
+ switch(mute_type)
+ if(MUTE_IC)
+ M.client.muted_ic = !M.client.muted_ic | automute
+ muteunmute = M.client.muted_ic
+ mute_string = "IC (say and emote)"
+ if(MUTE_OOC)
+ M.client.muted_ooc = !M.client.muted_ooc | automute
+ muteunmute = M.client.muted_ooc
+ mute_string = "OOC"
+ if(MUTE_PRAY)
+ M.client.muted_pray = !M.client.muted_pray | automute
+ muteunmute = M.client.muted_pray
+ mute_string = "pray"
+ if(MUTE_ADMINHELP)
+ M.client.muted_adminhelp = !M.client.muted_adminhelp | automute
+ muteunmute = M.client.muted_adminhelp
+ mute_string = "adminhelp, admin PM and ASAY"
+ if(MUTE_DEADCHAT)
+ M.client.muted_deadchat = !M.client.muted_deadchat | automute
+ muteunmute = M.client.muted_deadchat
+ mute_string = "deadchat and DSAY"
+ if(MUTE_ALL)
+ mute_string = "everything"
+ if( M.client.muted_ic )
+ M.client.muted_ic = 1
+ M.client.muted_ooc = 1
+ M.client.muted_pray = 1
+ M.client.muted_adminhelp = 1
+ M.client.muted_deadchat = 1
+ muteunmute = 1
+ else
+ M.client.muted_ic = 0
+ M.client.muted_ooc = 0
+ M.client.muted_pray = 0
+ M.client.muted_adminhelp = 0
+ M.client.muted_deadchat = 0
+ muteunmute = 0
+
+ if(!automute)
+ log_admin("[key_name(usr)] has [(muteunmute ? "muted" : "voiced")] [key_name(M)] from [mute_string]")
+ message_admins("[key_name_admin(usr)] has [(muteunmute ? "muted" : "voiced")] [key_name_admin(M)] from [mute_string].", 1)
+
+ M << "You have been [(muteunmute ? "muted" : "voiced")] from [mute_string] by [(usr.client.stealth)?"an admin":"[usr.client]"]."
+ feedback_add_details("admin_verb","MUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ else
+ log_admin("SPAM AUTOMUTE: [(muteunmute ? "muted" : "voiced")] [key_name(M)] from [mute_string]")
+ message_admins("SPAM AUTOMUTE: [(muteunmute ? "muted" : "voiced")] [key_name_admin(M)] from [mute_string].", 1)
+
+ M << "You have been [(muteunmute ? "muted" : "voiced")] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin."
+ feedback_add_details("admin_verb","AUTOMUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_add_random_ai_law()
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 07f851d6580..f267a6bd766 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -9,8 +9,16 @@
var/fakekey = null
var/seeprayers = 0
var/ooccolor = "#b82e00"
- var/muted = null //Can't talk in OOC, say, whisper, emote... anything except for adminhelp and admin-pm. An admin punishment
- var/muted_complete = null //Can't talk in any way shape or form (muted + can't adminhelp or respond to admin pm-s). An admin punishment
+
+ var/muted_ic //can't use 'say' while alive or emotes.
+ var/muted_ooc //can't speak in ooc
+ var/muted_deadchat //can't use 'say' while dead or DSAY
+ var/muted_pray //can't send prayers
+ var/muted_adminhelp //can't send adminhelps, PM-s or use ASAY
+
+ var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming.
+ var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent.
+
var/warned = 0
var/sound_adminhelp = 0 //If set to 1 this will play a sound when adminhelps are received.
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index ec367a706fc..90d1c2b6bd4 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -52,6 +52,20 @@
..() //redirect to [locate(hsrc)]/Topic()
+/client/proc/handle_spam_prevention(var/message, var/mute_type)
+ if(src.last_message == message)
+ src.last_message_count++
+ if(src.last_message_count >= SPAM_TRIGGER_AUTOMUTE)
+ src << "\red You have exceeded the spam filter limit for identical messages. An auto-mute was applied."
+ cmd_admin_mute(src.mob, mute_type, 1)
+ return 1
+ if(src.last_message_count >= SPAM_TRIGGER_WARNING)
+ src << "\red You are nearing the spam filter limit for identical messages."
+ return 0
+ else
+ last_message = message
+ src.last_message_count = 0
+ return 0
//This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc.
/client/AllowUpload(filename, filelength)
diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm
index 9c015e7e880..4a39d8f0fb5 100644
--- a/code/modules/mob/dead/observer/say.dm
+++ b/code/modules/mob/dead/observer/say.dm
@@ -9,9 +9,13 @@
log_say("Ghost/[src.key] : [message]")
- if (src.client && (src.client.muted || src.client.muted_complete))
- src << "You are muted."
- return
+ if (src.client)
+ if(src.client.muted_deadchat)
+ src << "\red You cannot talk in deadchat (muted by admins)."
+ return
+
+ if (src.client.handle_spam_prevention(message,MUTE_DEADCHAT))
+ return
. = src.say_dead(message)
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index 1da2e94e0f0..194f609a780 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -65,9 +65,12 @@
if ("me")
if(silent)
return
- if (src.client && (client.muted || client.muted_complete))
- src << "You are muted."
- return
+ if (src.client)
+ if (client.muted_ic)
+ src << "\red You cannot send IC messages (muted by admins)."
+ return
+ if (src.client.handle_spam_prevention(message,MUTE_IC))
+ return
if (stat)
return
if(!(message))
diff --git a/code/modules/mob/living/carbon/human/whisper.dm b/code/modules/mob/living/carbon/human/whisper.dm
index 3de25582982..0c79f8e1af3 100644
--- a/code/modules/mob/living/carbon/human/whisper.dm
+++ b/code/modules/mob/living/carbon/human/whisper.dm
@@ -10,9 +10,14 @@
log_whisper("[src.name]/[src.key] : [message]")
- if (src.client && (src.client.muted || src.client.muted_complete))
- src << "You are muted."
- return
+ if (src.client)
+ if (src.client.muted_ic)
+ src << "\red You cannot whisper (muted by admins)."
+ return
+
+ if (src.client.handle_spam_prevention(message,MUTE_IC))
+ return
+
if (src.stat == 2)
return src.say_dead(message)
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 42dbffffd4c..fac157c90df 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -92,9 +92,12 @@ var/list/department_radio_keys = list(
if (silent)
return
- if (src.client && (client.muted || src.client.muted_complete))
- src << "You are muted."
- return
+ if (src.client)
+ if(client.muted_ic)
+ src << "\red You cannot speak in IC (muted by admins)."
+ return
+ if (src.client.handle_spam_prevention(message,MUTE_IC))
+ return
// stat == 2 is handled above, so this stops transmission of uncontious messages
if (stat)
diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm
index 89571eff031..db694473ff9 100644
--- a/code/modules/mob/living/silicon/robot/emote.dm
+++ b/code/modules/mob/living/silicon/robot/emote.dm
@@ -70,9 +70,12 @@
if ("me")
if(silent)
return
- if (src.client && (client.muted || client.muted_complete))
- src << "You are muted."
- return
+ if (src.client)
+ if(client.muted_ic)
+ src << "You cannot send IC messages (muted by admins)."
+ return
+ if (src.client.handle_spam_prevention(message,MUTE_IC))
+ return
if (stat)
return
if(!(message))
diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm
index 069cb12d227..96369dc814d 100644
--- a/code/modules/mob/living/silicon/say.dm
+++ b/code/modules/mob/living/silicon/say.dm
@@ -2,9 +2,12 @@
if (!message)
return
- if (src.client && (client.muted || src.client.muted_complete))
- src << "You are muted."
- return
+ if (src.client)
+ if(client.muted_ic)
+ src << "You cannot send IC messages (muted by admins)."
+ return
+ if (src.client.handle_spam_prevention(message,MUTE_IC))
+ return
if (stat == 2)
message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
diff --git a/code/setup.dm b/code/setup.dm
index 889878a2ad0..9dfa29e1947 100644
--- a/code/setup.dm
+++ b/code/setup.dm
@@ -361,3 +361,14 @@ var/list/liftable_structures = list(\
/obj/structure/filingcabinet, \
/obj/structure/reagent_dispensers, \
/obj/machinery/portable_atmospherics/canister)
+
+//A set of constants used to determine which type of mute an admin wishes to apply:
+#define MUTE_IC 1
+#define MUTE_OOC 2
+#define MUTE_PRAY 3
+#define MUTE_ADMINHELP 4
+#define MUTE_DEADCHAT 5
+#define MUTE_ALL 6
+
+#define SPAM_TRIGGER_WARNING 5
+#define SPAM_TRIGGER_AUTOMUTE 10
\ No newline at end of file