diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index c194e578c9..5c54843df2 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -47,6 +47,7 @@ // Subsystems shutdown in the reverse of the order they initialize in // The numbers just define the ordering, they are meaningless otherwise. +#define INIT_ORDER_FAIL2TOPIC 22 #define INIT_ORDER_TITLE 20 #define INIT_ORDER_GARBAGE 19 #define INIT_ORDER_DBCORE 18 diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 3ee77d3edc..4dd590d9a1 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -78,7 +78,6 @@ if (CONFIG_GET(flag/log_manifest)) WRITE_LOG(GLOB.world_manifest_log, "[ckey] \\ [body.real_name] \\ [mind.assigned_role] \\ [mind.special_role ? mind.special_role : "NONE"] \\ [latejoin ? "LATEJOIN":"ROUNDSTART"]") - /proc/log_say(text) if (CONFIG_GET(flag/log_say)) WRITE_LOG(GLOB.world_game_log, "SAY: [text]") @@ -121,7 +120,6 @@ if (CONFIG_GET(flag/log_vote)) WRITE_LOG(GLOB.world_game_log, "VOTE: [text]") - /proc/log_topic(text) WRITE_LOG(GLOB.world_game_log, "TOPIC: [text]") @@ -141,6 +139,9 @@ if (CONFIG_GET(flag/log_job_debug)) WRITE_LOG(GLOB.world_job_debug_log, "JOB: [text]") +/proc/log_subsystem(subsystem, text) + WRITE_LOG(GLOB.subsystem_log, "[subsystem]: [text]") + /* Log to both DD and the logfile. */ /proc/log_world(text) #ifdef USE_CUSTOM_ERROR_HANDLER diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 5ca3513e66..01d5051dba 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -30,6 +30,8 @@ GLOBAL_VAR(world_virus_log) GLOBAL_PROTECT(world_virus_log) GLOBAL_VAR(world_map_error_log) GLOBAL_PROTECT(world_map_error_log) +GLOBAL_VAR(subsystem_log) +GLOBAL_PROTECT(subsystem_log) GLOBAL_LIST_EMPTY(bombers) GLOBAL_PROTECT(bombers) diff --git a/code/controllers/configuration/entries/fail2topic.dm b/code/controllers/configuration/entries/fail2topic.dm new file mode 100644 index 0000000000..7ed09b378a --- /dev/null +++ b/code/controllers/configuration/entries/fail2topic.dm @@ -0,0 +1,19 @@ +/datum/config_entry/number/fail2topic_rate_limit + config_entry_value = 10 //deciseconds + +/datum/config_entry/number/fail2topic_max_fails + config_entry_value = 5 + +/datum/config_entry/string/fail2topic_rule_name + config_entry_value = "_dd_fail2topic" + protection = CONFIG_ENTRY_LOCKED //affects physical server configuration, no touchies!! + +/datum/config_entry/flag/fail2topic_enabled + config_entry_value = TRUE + +/datum/config_entry/number/topic_max_size + config_entry_value = 8192 + +/datum/config_entry/keyed_list/topic_rate_limit_whitelist + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 125da84a30..7244212630 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -54,7 +54,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/restart_clear = 0 var/static/restart_timeout = 0 var/static/restart_count = 0 - + var/static/random_seed //current tick limit, assigned before running a subsystem. @@ -69,7 +69,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(!random_seed) random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9) rand_seed(random_seed) - + var/list/_subsystems = list() subsystems = _subsystems if (Master != src) diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 4fe0812c56..3be4f36270 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -155,6 +155,8 @@ if(SS_SLEEPING) state = SS_PAUSING +/datum/controller/subsystem/proc/subsystem_log(msg) + return log_subsystem(name, msg) //used to initialize the subsystem AFTER the map has loaded /datum/controller/subsystem/Initialize(start_timeofday) @@ -162,7 +164,7 @@ var/time = (REALTIMEOFDAY - start_timeofday) / 10 var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" to_chat(world, "[msg]") - log_world(msg) + log_subsystem("INIT", msg) return time //hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. diff --git a/code/controllers/subsystem/fail2topic.dm b/code/controllers/subsystem/fail2topic.dm new file mode 100644 index 0000000000..a589ae2462 --- /dev/null +++ b/code/controllers/subsystem/fail2topic.dm @@ -0,0 +1,113 @@ +SUBSYSTEM_DEF(fail2topic) + name = "Fail2Topic" + init_order = INIT_ORDER_FAIL2TOPIC + flags = SS_BACKGROUND + runlevels = ALL + + var/list/rate_limiting = list() + var/list/fail_counts = list() + var/list/active_bans = list() + + var/rate_limit + var/max_fails + var/rule_name + var/enabled = FALSE + +/datum/controller/subsystem/fail2topic/Initialize(timeofday) + rate_limit = CONFIG_GET(number/fail2topic_rate_limit) + max_fails = CONFIG_GET(number/fail2topic_max_fails) + rule_name = CONFIG_GET(string/fail2topic_rule_name) + enabled = CONFIG_GET(flag/fail2topic_enabled) + + DropFirewallRule() // Clear the old bans if any still remain + + if (world.system_type == UNIX && enabled) + enabled = FALSE + subsystem_log("DISABLED - UNIX systems are not supported.") + if(!enabled) + flags |= SS_NO_FIRE + can_fire = FALSE + + return ..() + +/datum/controller/subsystem/fail2topic/fire() + while (rate_limiting.len) + var/ip = rate_limiting[1] + var/last_attempt = rate_limiting[ip] + + if (world.time - last_attempt > rate_limit) + rate_limiting -= ip + fail_counts -= ip + + if (MC_TICK_CHECK) + return + +/datum/controller/subsystem/fail2topic/Shutdown() + DropFirewallRule() + +/datum/controller/subsystem/fail2topic/proc/IsRateLimited(ip) + var/last_attempt = rate_limiting[ip] + + var/static/datum/config_entry/keyed_list/topic_rate_limit_whitelist/cached_whitelist_entry + if(!istype(cached_whitelist_entry)) + cached_whitelist_entry = CONFIG_GET(keyed_list/topic_rate_limit_whitelist) + + if(istype(cached_whitelist_entry)) + if(cached_whitelist_entry.config_entry_value[ip]) + return FALSE + + if (active_bans[ip]) + return TRUE + + rate_limiting[ip] = world.time + + if (isnull(last_attempt)) + return FALSE + + if (world.time - last_attempt > rate_limit) + fail_counts -= ip + return FALSE + else + var/failures = fail_counts[ip] + + if (isnull(failures)) + fail_counts[ip] = 1 + return TRUE + else if (failures > max_fails) + BanFromFirewall(ip) + return TRUE + else + fail_counts[ip] = failures + 1 + return TRUE + +/datum/controller/subsystem/fail2topic/proc/BanFromFirewall(ip) + if (!enabled) + return + + active_bans[ip] = world.time + fail_counts -= ip + rate_limiting -= ip + + . = shell("netsh advfirewall firewall add rule name=\"[rule_name]\" dir=in interface=any action=block remoteip=[ip]") + + if (.) + subsystem_log("Failed to ban [ip]. Exit code: [.].") + else if (isnull(.)) + subsystem_log("Failed to invoke shell to ban [ip].") + else + subsystem_log("Banned [ip].") + +/datum/controller/subsystem/fail2topic/proc/DropFirewallRule() + if (!enabled) + return + + active_bans = list() + + . = shell("netsh advfirewall firewall delete rule name=\"[rule_name]\"") + + if (.) + subsystem_log("Failed to drop firewall rule. Exit code: [.].") + else if (isnull(.)) + subsystem_log("Failed to invoke shell for firewall rule drop.") + else + subsystem_log("Firewall rule dropped.") diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 612c86bc11..9e2efd1d4e 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -1,189 +1,189 @@ -// SETUP - -/proc/TopicHandlers() - . = list() - var/list/all_handlers = subtypesof(/datum/world_topic) - for(var/I in all_handlers) - var/datum/world_topic/WT = I - var/keyword = initial(WT.keyword) - if(!keyword) - warning("[WT] has no keyword! Ignoring...") - continue - var/existing_path = .[keyword] - if(existing_path) - warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") - else if(keyword == "key") - warning("[WT] has keyword 'key'! Ignoring...") - else - .[keyword] = WT - -// DATUM - -/datum/world_topic - var/keyword - var/log = TRUE - var/key_valid - var/require_comms_key = FALSE - -/datum/world_topic/proc/TryRun(list/input) - key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) - if(require_comms_key && !key_valid) - return "Bad Key" - input -= "key" - . = Run(input) - if(islist(.)) - . = list2params(.) - -/datum/world_topic/proc/Run(list/input) - CRASH("Run() not implemented for [type]!") - -// TOPICS - -/datum/world_topic/ping - keyword = "ping" - log = FALSE - -/datum/world_topic/ping/Run(list/input) - . = 0 - for (var/client/C in GLOB.clients) - ++. - -/datum/world_topic/playing - keyword = "playing" - log = FALSE - -/datum/world_topic/playing/Run(list/input) - return GLOB.player_list.len - -/datum/world_topic/pr_announce - keyword = "announce" - require_comms_key = TRUE - var/static/list/PRcounts = list() //PR id -> number of times announced this round - -/datum/world_topic/pr_announce/Run(list/input) - var/list/payload = json_decode(input["payload"]) - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [input[keyword]]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/datum/world_topic/ahelp_relay - keyword = "Ahelp" - require_comms_key = TRUE - -/datum/world_topic/ahelp_relay/Run(list/input) - relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") - -/datum/world_topic/comms_console - keyword = "Comms_Console" - require_comms_key = TRUE - -/datum/world_topic/comms_console/Run(list/input) - minor_announce(input["message"], "Incoming message from [input["message_sender"]]") - for(var/obj/machinery/computer/communications/CM in GLOB.machines) - CM.overrideCooldown() - -/datum/world_topic/news_report - keyword = "News_Report" - require_comms_key = TRUE - -/datum/world_topic/news_report/Run(list/input) - minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") - -/datum/world_topic/server_hop - keyword = "server_hop" - -/datum/world_topic/server_hop/Run(list/input) - var/expected_key = input[keyword] - for(var/mob/dead/observer/O in GLOB.player_list) - if(O.key == expected_key) - if(O.client) - new /obj/screen/splash(O.client, TRUE) - break - -/datum/world_topic/adminmsg - keyword = "adminmsg" - require_comms_key = TRUE - -/datum/world_topic/adminmsg/Run(list/input) - return IrcPm(input[keyword], input["msg"], input["sender"]) - -/datum/world_topic/namecheck - keyword = "namecheck" - require_comms_key = TRUE - -/datum/world_topic/namecheck/Run(list/input) - //Oh this is a hack, someone refactor the functionality out of the chat command PLS - var/datum/tgs_chat_command/namecheck/NC = new - var/datum/tgs_chat_user/user = new - user.friendly_name = input["sender"] - user.mention = user.friendly_name - return NC.Run(user, input["namecheck"]) - -/datum/world_topic/adminwho - keyword = "adminwho" - require_comms_key = TRUE - -/datum/world_topic/adminwho/Run(list/input) - return ircadminwho() - -/datum/world_topic/status - keyword = "status" - -/datum/world_topic/status/Run(list/input) - if(!key_valid) //If we have a key, then it's safe to trust that this isn't a malicious packet. Also prevents the extra info from leaking - if(GLOB.topic_status_lastcache <= world.time + 5) - return GLOB.topic_status_cache - GLOB.topic_status_lastcache = world.time - . = list() - .["version"] = GLOB.game_version - .["mode"] = "hidden" //CIT CHANGE - hides the gamemode in topic() calls to prevent meta'ing the gamemode - .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE - .["enter"] = GLOB.enter_allowed - .["vote"] = CONFIG_GET(flag/allow_vote_mode) - .["ai"] = CONFIG_GET(flag/allow_ai) - .["host"] = world.host ? world.host : null - .["round_id"] = GLOB.round_id - .["players"] = GLOB.clients.len - .["revision"] = GLOB.revdata.commit - .["revision_date"] = GLOB.revdata.date - - var/list/adm = get_admin_counts() - var/list/presentmins = adm["present"] - var/list/afkmins = adm["afk"] - .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho - .["gamestate"] = SSticker.current_state - - .["map_name"] = SSmapping.config?.map_name || "Loading..." - - if(key_valid) - .["active_players"] = get_active_player_count() - if(SSticker.HasRoundStarted()) - .["real_mode"] = SSticker.mode.name - // Key-authed callers may know the truth behind the "secret" - - .["security_level"] = get_security_level() - .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 - // Amount of world's ticks in seconds, useful for calculating round duration - - //Time dilation stats. - .["time_dilation_current"] = SStime_track.time_dilation_current - .["time_dilation_avg"] = SStime_track.time_dilation_avg - .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow - .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast - - if(SSshuttle && SSshuttle.emergency) - .["shuttle_mode"] = SSshuttle.emergency.mode - // Shuttle status, see /__DEFINES/stat.dm - .["shuttle_timer"] = SSshuttle.emergency.timeLeft() - // Shuttle timer, in seconds - - if(!key_valid) - GLOB.topic_status_cache = . +// SETUP + +/proc/TopicHandlers() + . = list() + var/list/all_handlers = subtypesof(/datum/world_topic) + for(var/I in all_handlers) + var/datum/world_topic/WT = I + var/keyword = initial(WT.keyword) + if(!keyword) + warning("[WT] has no keyword! Ignoring...") + continue + var/existing_path = .[keyword] + if(existing_path) + warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") + else if(keyword == "key") + warning("[WT] has keyword 'key'! Ignoring...") + else + .[keyword] = WT + +// DATUM + +/datum/world_topic + var/keyword + var/log = TRUE + var/key_valid + var/require_comms_key = FALSE + +/datum/world_topic/proc/TryRun(list/input, addr) + key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) + if(require_comms_key && !key_valid) + return "Bad Key" + input -= "key" + . = Run(input, addr) + if(islist(.)) + . = list2params(.) + +/datum/world_topic/proc/Run(list/input, addr) + CRASH("Run() not implemented for [type]!") + +// TOPICS + +/datum/world_topic/ping + keyword = "ping" + log = FALSE + +/datum/world_topic/ping/Run(list/input, addr) + . = 0 + for (var/client/C in GLOB.clients) + ++. + +/datum/world_topic/playing + keyword = "playing" + log = FALSE + +/datum/world_topic/playing/Run(list/input, addr) + return GLOB.player_list.len + +/datum/world_topic/pr_announce + keyword = "announce" + require_comms_key = TRUE + var/static/list/PRcounts = list() //PR id -> number of times announced this round + +/datum/world_topic/pr_announce/Run(list/input, addr) + var/list/payload = json_decode(input["payload"]) + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [input[keyword]]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/datum/world_topic/ahelp_relay + keyword = "Ahelp" + require_comms_key = TRUE + +/datum/world_topic/ahelp_relay/Run(list/input, addr) + relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") + +/datum/world_topic/comms_console + keyword = "Comms_Console" + require_comms_key = TRUE + +/datum/world_topic/comms_console/Run(list/input, addr) + minor_announce(input["message"], "Incoming message from [input["message_sender"]]") + for(var/obj/machinery/computer/communications/CM in GLOB.machines) + CM.overrideCooldown() + +/datum/world_topic/news_report + keyword = "News_Report" + require_comms_key = TRUE + +/datum/world_topic/news_report/Run(list/input, addr) + minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") + +/datum/world_topic/server_hop + keyword = "server_hop" + +/datum/world_topic/server_hop/Run(list/input, addr) + var/expected_key = input[keyword] + for(var/mob/dead/observer/O in GLOB.player_list) + if(O.key == expected_key) + if(O.client?.address == addr) + new /obj/screen/splash(O.client, TRUE) + break + +/datum/world_topic/adminmsg + keyword = "adminmsg" + require_comms_key = TRUE + +/datum/world_topic/adminmsg/Run(list/input, addr) + return IrcPm(input[keyword], input["msg"], input["sender"]) + +/datum/world_topic/namecheck + keyword = "namecheck" + require_comms_key = TRUE + +/datum/world_topic/namecheck/Run(list/input, addr) + //Oh this is a hack, someone refactor the functionality out of the chat command PLS + var/datum/tgs_chat_command/namecheck/NC = new + var/datum/tgs_chat_user/user = new + user.friendly_name = input["sender"] + user.mention = user.friendly_name + return NC.Run(user, input["namecheck"]) + +/datum/world_topic/adminwho + keyword = "adminwho" + require_comms_key = TRUE + +/datum/world_topic/adminwho/Run(list/input, addr) + return ircadminwho() + +/datum/world_topic/status + keyword = "status" + +/datum/world_topic/status/Run(list/input, addr) + if(!key_valid) //If we have a key, then it's safe to trust that this isn't a malicious packet. Also prevents the extra info from leaking + if(GLOB.topic_status_lastcache <= world.time + 5) + return GLOB.topic_status_cache + GLOB.topic_status_lastcache = world.time + . = list() + .["version"] = GLOB.game_version + .["mode"] = "hidden" //CIT CHANGE - hides the gamemode in topic() calls to prevent meta'ing the gamemode + .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE + .["enter"] = GLOB.enter_allowed + .["vote"] = CONFIG_GET(flag/allow_vote_mode) + .["ai"] = CONFIG_GET(flag/allow_ai) + .["host"] = world.host ? world.host : null + .["round_id"] = GLOB.round_id + .["players"] = GLOB.clients.len + .["revision"] = GLOB.revdata.commit + .["revision_date"] = GLOB.revdata.date + + var/list/adm = get_admin_counts() + var/list/presentmins = adm["present"] + var/list/afkmins = adm["afk"] + .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho + .["gamestate"] = SSticker.current_state + + .["map_name"] = SSmapping.config?.map_name || "Loading..." + + if(key_valid) + .["active_players"] = get_active_player_count() + if(SSticker.HasRoundStarted()) + .["real_mode"] = SSticker.mode.name + // Key-authed callers may know the truth behind the "secret" + + .["security_level"] = get_security_level() + .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 + // Amount of world's ticks in seconds, useful for calculating round duration + + //Time dilation stats. + .["time_dilation_current"] = SStime_track.time_dilation_current + .["time_dilation_avg"] = SStime_track.time_dilation_avg + .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow + .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast + + if(SSshuttle && SSshuttle.emergency) + .["shuttle_mode"] = SSshuttle.emergency.mode + // Shuttle status, see /__DEFINES/stat.dm + .["shuttle_timer"] = SSshuttle.emergency.timeLeft() + // Shuttle timer, in seconds + + if(!key_valid) + GLOB.topic_status_cache = . diff --git a/code/game/world.dm b/code/game/world.dm index 691a60ce18..e68372d768 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -115,6 +115,7 @@ GLOBAL_LIST(topic_status_cache) GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + GLOB.subsystem_log = "[GLOB.log_directory]/subsystem.log" #ifdef UNIT_TESTS GLOB.test_log = file("[GLOB.log_directory]/tests.log") @@ -129,6 +130,7 @@ GLOBAL_LIST(topic_status_cache) start_log(GLOB.world_qdel_log) start_log(GLOB.world_runtime_log) start_log(GLOB.world_job_debug_log) + start_log(GLOB.subsystem_log) GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently if(fexists(GLOB.config_error_log)) @@ -146,6 +148,14 @@ GLOBAL_LIST(topic_status_cache) /world/Topic(T, addr, master, key) TGS_TOPIC //redirect to server tools if necessary + if(!SSfail2topic) + return "Server not initialized." + else if(SSfail2topic.IsRateLimited(addr)) + return "Rate limited." + + if(length(T) > CONFIG_GET(number/topic_max_size)) + return "Payload too large!" + var/static/list/topic_handlers = TopicHandlers() var/list/input = params2list(T) @@ -162,7 +172,7 @@ GLOBAL_LIST(topic_status_cache) return handler = new handler() - return handler.TryRun(input) + return handler.TryRun(input, addr) /world/proc/AnnouncePR(announcement, list/payload) var/static/list/PRcounts = list() //PR id -> number of times announced this round diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm index a1b74942e0..63a392bf06 100644 --- a/code/modules/surgery/bodyparts/bodyparts.dm +++ b/code/modules/surgery/bodyparts/bodyparts.dm @@ -172,9 +172,8 @@ var/total_damage = brute + burn if(total_damage > can_inflict) - var/excess = total_damage - can_inflict - brute = round(brute * (excess / total_damage),DAMAGE_PRECISION) - burn = round(burn * (excess / total_damage),DAMAGE_PRECISION) + brute = round(brute * (max_damage / total_damage),DAMAGE_PRECISION) + burn = round(burn * (max_damage / total_damage),DAMAGE_PRECISION) brute_dam += brute burn_dam += burn diff --git a/code/modules/tgs/v3210/commands.dm b/code/modules/tgs/v3210/commands.dm index 71d7e32366..e674fd4e78 100644 --- a/code/modules/tgs/v3210/commands.dm +++ b/code/modules/tgs/v3210/commands.dm @@ -19,7 +19,7 @@ TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!") warned_command_names[command_name] = TRUE continue - + if(command_name_types[command_name]) if(warnings_only) TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") @@ -55,24 +55,24 @@ The MIT License Copyright (c) 2017 Jordan Brown -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ diff --git a/config/config.txt b/config/config.txt index e71c2587b7..fafb3e5791 100644 --- a/config/config.txt +++ b/config/config.txt @@ -473,3 +473,15 @@ DISABLE_HIGH_POP_MC_MODE_AMOUNT 60 ## For reference, Goonstation uses a resolution of 21x15 for it's widescreen mode. ## Do note that changing this value will affect the title screen. The title screen will have to be updated manually if this is changed. DEFAULT_VIEW 21x15 + +### FAIL2TOPIC: +### Automated IP bans for world/Topic() spammers +## Enabled +FAIL2TOPIC_ENABLED +## Minimum wait time in deciseconds between valid requests +FAIL2TOPIC_RATE_LIMIT 10 +## Number of requests after breaching rate limit that triggers a ban +FAIL2TOPIC_MAX_FAILS 5 +## Firewall rule name used on physical server +FAIL2TOPIC_RULE_NAME _dd_fail2topic + diff --git a/html/changelogs/AutoChangeLog-pr-10473.yml b/html/changelogs/AutoChangeLog-pr-10473.yml new file mode 100644 index 0000000000..f1821a3cce --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10473.yml @@ -0,0 +1,4 @@ +author: "Bhijn" +delete-after: True +changes: + - bugfix: "server_hop can no longer be used to remotely lobotomize a spaceman" diff --git a/html/changelogs/AutoChangeLog-pr-10483.yml b/html/changelogs/AutoChangeLog-pr-10483.yml new file mode 100644 index 0000000000..1f016b1591 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10483.yml @@ -0,0 +1,4 @@ +author: "Putnam3145" +delete-after: True +changes: + - bugfix: "Limb damage works now" diff --git a/tgstation.dme b/tgstation.dme index a167c130b0..f52892fce3 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -229,6 +229,7 @@ #include "code\controllers\configuration\entries\dbconfig.dm" #include "code\controllers\configuration\entries\donator.dm" #include "code\controllers\configuration\entries\dynamic.dm" +#include "code\controllers\configuration\entries\fail2topic.dm" #include "code\controllers\configuration\entries\game_options.dm" #include "code\controllers\configuration\entries\general.dm" #include "code\controllers\subsystem\acid.dm" @@ -245,6 +246,7 @@ #include "code\controllers\subsystem\dcs.dm" #include "code\controllers\subsystem\disease.dm" #include "code\controllers\subsystem\events.dm" +#include "code\controllers\subsystem\fail2topic.dm" #include "code\controllers\subsystem\fire_burning.dm" #include "code\controllers\subsystem\garbage.dm" #include "code\controllers\subsystem\icon_smooth.dm" @@ -470,8 +472,8 @@ #include "code\datums\elements\_element.dm" #include "code\datums\elements\cleaning.dm" #include "code\datums\elements\earhealing.dm" -#include "code\datums\elements\wuv.dm" #include "code\datums\elements\ghost_role_eligibility.dm" +#include "code\datums\elements\wuv.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" #include "code\datums\helper_datums\icon_snapshot.dm"