diff --git a/config/bandastation/bandastation_config.txt b/config/bandastation/bandastation_config.txt index ac8597da67126..deddf40ab09c8 100644 --- a/config/bandastation/bandastation_config.txt +++ b/config/bandastation/bandastation_config.txt @@ -38,3 +38,11 @@ ROUNDSTART_RACES vulpkanin ## Time in deciseconds the mob must be clientless for to be despawned by cryopod. 30 minutes by default #CRYO_MIN_SSD_TIME 18000 + +## Speech filter for players + +## If enabled, some words will be removed from player's messages. +# ENABLE_SPEECH_FILTER +## List of ckeys, that bypass speech filter. +# SPEECH_FILTER_BYPASS ckey +# SPEECH_FILTER_BYPASS ckey diff --git a/config/bandastation/brainrot_filter.json b/config/bandastation/brainrot_filter.json new file mode 100644 index 0000000000000..17c843639b99b --- /dev/null +++ b/config/bandastation/brainrot_filter.json @@ -0,0 +1,3 @@ +{ + "brainrot_filter": [""] +} diff --git a/modular_bandastation/modular_bandastation.dme b/modular_bandastation/modular_bandastation.dme index 410f0d8f64290..8f204d8399b94 100644 --- a/modular_bandastation/modular_bandastation.dme +++ b/modular_bandastation/modular_bandastation.dme @@ -47,6 +47,7 @@ #include "ru_jobs/_ru_jobs.dme" #include "security_levels/_security_levels.dme" #include "species/_species.dme" +#include "speech_filter/_speech_filter.dme" #include "title_screen/_title_screen.dme" #include "translations/_translations.dme" #include "tts/_tts.dme" diff --git a/modular_bandastation/speech_filter/_speech_filter.dm b/modular_bandastation/speech_filter/_speech_filter.dm new file mode 100644 index 0000000000000..1126a11c14489 --- /dev/null +++ b/modular_bandastation/speech_filter/_speech_filter.dm @@ -0,0 +1,4 @@ +/datum/modpack/speech_filter + name = "Фильтр речи" + desc = "Фильтрация речи игроков." + author = "gaxeer" diff --git a/modular_bandastation/speech_filter/_speech_filter.dme b/modular_bandastation/speech_filter/_speech_filter.dme new file mode 100644 index 0000000000000..dedff6323165e --- /dev/null +++ b/modular_bandastation/speech_filter/_speech_filter.dme @@ -0,0 +1,5 @@ +#include "_speech_filter.dm" + +#include "code/configuration.dm" +#include "code/mob.dm" +#include "code/speech_filter.dm" diff --git a/modular_bandastation/speech_filter/code/configuration.dm b/modular_bandastation/speech_filter/code/configuration.dm new file mode 100644 index 0000000000000..baeac19b3aaaf --- /dev/null +++ b/modular_bandastation/speech_filter/code/configuration.dm @@ -0,0 +1,3 @@ +/datum/config_entry/flag/enable_speech_filter + +/datum/config_entry/str_list/speech_filter_bypass diff --git a/modular_bandastation/speech_filter/code/mob.dm b/modular_bandastation/speech_filter/code/mob.dm new file mode 100644 index 0000000000000..c21c26edaf777 --- /dev/null +++ b/modular_bandastation/speech_filter/code/mob.dm @@ -0,0 +1,3 @@ +/mob/living/Login() + . = ..() + AddElement(/datum/element/speech_filter, src) diff --git a/modular_bandastation/speech_filter/code/speech_filter.dm b/modular_bandastation/speech_filter/code/speech_filter.dm new file mode 100644 index 0000000000000..db3b5dedb96e2 --- /dev/null +++ b/modular_bandastation/speech_filter/code/speech_filter.dm @@ -0,0 +1,85 @@ +#define BRAINROT_FILTER_FILE "config/bandastation/brainrot_filter.json" + +/datum/element/speech_filter + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + /// what is displayed to the speaker on replace + var/static/list/brainrot_notifications = list( + "Почему у меня такой скудный словарный запас? Стоит сходить в библиотеку и прочесть книгу...", + "Что, черт побери, я несу?", + "Я в своём уме? Надо следить за языком.", + "Неужели я не могу подобрать нужных слов? Позор мне..." + ) + +/datum/element/speech_filter/Attach(datum/target) + . = ..() + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + + var/mob/mob_to_censor = target + if(!mob_to_censor.client) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(mob_to_censor, COMSIG_MOB_SAY, PROC_REF(filter_speech), TRUE) + RegisterSignal(mob_to_censor, COMSIG_MOB_LOGOUT, PROC_REF(Detach), TRUE) + +/datum/element/speech_filter/Detach(datum/source, force) + . = ..() + UnregisterSignal(source, COMSIG_MOB_SAY) + UnregisterSignal(source, COMSIG_MOB_LOGOUT) + +/datum/element/speech_filter/proc/filter_speech(mob/speaker, list/speech_args) + if(!CONFIG_GET(flag/enable_speech_filter) || can_bypass_filter(speaker)) + return + + var/message = speech_args[SPEECH_MESSAGE] + if(!length(message)) + return + + if(message[1] == "*") + return + + var/brainrot_regex = get_brainrot_filter_regex() + if(!brainrot_regex) + return + + var/original_message = copytext(message, 1) + message = rustutils_regex_replace(message, brainrot_regex, "i", "цветочек") + if(original_message == message) + return + + speech_args[SPEECH_MESSAGE] = message + addtimer(CALLBACK(speaker, TYPE_PROC_REF(/mob, emote), "drool"), 0.3 SECONDS) + to_chat(speaker, span_priorityalert(pick(brainrot_notifications))) + message_admins("[ADMIN_LOOKUPFLW(speaker)] has attempted to say forbidden word. His message was: [original_message]") + log_game("[key_name(speaker)] has attempted to say forbidden word. His message was: [original_message]") + +/datum/element/speech_filter/proc/get_brainrot_filter_regex() + if(!fexists(BRAINROT_FILTER_FILE)) + return + + var/static/list/filters + + if(!length(filters)) + var/raw_filter = file2text(BRAINROT_FILTER_FILE) + if(raw_filter) + var/list/parsed_filter = safe_json_decode(raw_filter) + if(isnull(parsed_filter)) + log_config("JSON parsing failure for [BRAINROT_FILTER_FILE]") + else + filters = parsed_filter["brainrot_filter"] + + if(!length(filters)) + return list() + + var/static/brainrot_regex + if(!brainrot_regex) + var/list/unique_filters = list() + unique_filters |= filters + brainrot_regex = unique_filters.Join("|") + + return brainrot_regex + +/datum/element/speech_filter/proc/can_bypass_filter(mob/mob_to_check) + return mob_to_check.client.ckey in CONFIG_GET(str_list/speech_filter_bypass) + +#undef BRAINROT_FILTER_FILE