diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 1fbd794856e..c71232b6d69 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -40,6 +40,8 @@
#define LINKED_UP (1<<0)
/// an obj/item is created! (obj/item/created_item)
#define COMSIG_GLOB_NEW_ITEM "!new_item"
+/// a client (re)connected, after all /client/New() checks have passed : (client/connected_client)
+#define COMSIG_GLOB_CLIENT_CONNECT "!client_connect"
/// a weather event of some kind occured
#define COMSIG_WEATHER_TELEGRAPH(event_type) "!weather_telegraph [event_type]"
#define COMSIG_WEATHER_START(event_type) "!weather_start [event_type]"
diff --git a/code/__DEFINES/lag_switch.dm b/code/__DEFINES/lag_switch.dm
new file mode 100644
index 00000000000..fd1fc579a63
--- /dev/null
+++ b/code/__DEFINES/lag_switch.dm
@@ -0,0 +1,10 @@
+// All of the possible Lag Switch lag mitigation measures
+// If you add more do not forget to update MEASURES_AMOUNT accordingly
+#define DISABLE_DEAD_KEYLOOP 1 // Stops ghosts flying around freely, they can still jump and orbit, staff exempted
+#define DISABLE_GHOST_ZOOM_TRAY 2 // Stops ghosts using zoom/t-ray verbs and resets their view if zoomed out, staff exempted
+#define DISABLE_RUNECHAT 3 // Disable runechat and enable the bubbles, speaking mobs with TRAIT_BYPASS_MEASURES exempted
+#define DISABLE_USR_ICON2HTML 4 // Disable icon2html procs from verbs like examine, mobs calling with TRAIT_BYPASS_MEASURES exempted
+#define DISABLE_NON_OBSJOBS 5 // Prevents anyone from joining the game as anything but observer
+#define SLOWMODE_SAY 6 // Limit IC/dchat spam to one message every x seconds per client, TRAIT_BYPASS_MEASURES exempted
+
+#define MEASURES_AMOUNT 6 // The total number of switches defined above
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 8971cfc322f..74d1ec23f83 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -323,6 +323,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_BLOODSHOT_EYES "bloodshot_eyes"
/// This mob should never close UI even if it doesn't have a client
#define TRAIT_PRESERVE_UI_WITHOUT_CLIENT "preserve_ui_without_client"
+/// This mob overrides certian SSlag_switch measures with this special trait
+#define TRAIT_BYPASS_MEASURES "bypass_lagswitch_measures"
#define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed
/// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 0b78028f036..5d7f87ade1a 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1124,6 +1124,8 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
/proc/icon2html(thing, target, icon_state, dir = SOUTH, frame = 1, moving = FALSE, sourceonly = FALSE, extra_classes = null)
if (!thing)
return
+ if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES))
+ return
var/key
var/icon/I = thing
@@ -1225,6 +1227,8 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
/proc/costly_icon2html(thing, target, sourceonly = FALSE)
if (!thing)
return
+ if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES))
+ return
if (isicon(thing))
return icon2html(thing, target)
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 77fec9e6c88..ff6f2b79b25 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -17,6 +17,7 @@ GLOBAL_LIST_INIT(dangerous_turfs, typecacheof(list(
//This is for procs to replace all the goddamn 'in world's that are chilling around the code
GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**.
+GLOBAL_LIST_EMPTY(keyloop_list) //as above but can be limited to boost performance
GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless
GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob
GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index bd2fa7f736a..57fb14ae2d7 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -88,6 +88,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_CANNOT_OPEN_PRESENTS" = TRAIT_CANNOT_OPEN_PRESENTS,
"TRAIT_PRESENT_VISION" = TRAIT_PRESENT_VISION,
"TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER,
+ "TRAIT_BYPASS_MEASURES" = TRAIT_BYPASS_MEASURES,
"TRAIT_NOMOBSWAP" = TRAIT_NOMOBSWAP,
"TRAIT_XRAY_VISION" = TRAIT_XRAY_VISION,
"TRAIT_WEB_WEAVER" = TRAIT_WEB_WEAVER,
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index e0812de3637..c690e7e9efe 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -299,6 +299,10 @@
/datum/config_entry/flag/maprotation
+/datum/config_entry/number/auto_lag_switch_pop //Number of clients at which drastic lag mitigation measures kick in
+ config_entry_value = null
+ min_val = 0
+
/datum/config_entry/number/soft_popcap
default = null
min_val = 0
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 311eec6e684..dd389e0dfbc 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -35,5 +35,5 @@ SUBSYSTEM_DEF(input)
user.set_macros()
/datum/controller/subsystem/input/fire()
- for(var/mob/user as anything in GLOB.player_list)
+ for(var/mob/user as anything in GLOB.keyloop_list)
user.focus?.keyLoop(user.client)
diff --git a/code/controllers/subsystem/lag_switch.dm b/code/controllers/subsystem/lag_switch.dm
new file mode 100644
index 00000000000..f8bbd13e297
--- /dev/null
+++ b/code/controllers/subsystem/lag_switch.dm
@@ -0,0 +1,133 @@
+/// The subsystem for controlling drastic performance enhancements aimed at reducing server load for a smoother albeit slightly duller gaming experience
+SUBSYSTEM_DEF(lag_switch)
+ name = "Lag Switch"
+ flags = SS_NO_FIRE
+
+ /// If the lag switch measures should attempt to trigger automatically, TRUE if a config value exists
+ var/auto_switch = FALSE
+ /// Amount of connected clients at which the Lag Switch should engage, set via config or admin panel
+ var/trigger_pop = INFINITY - 1337
+ /// List of bools corresponding to code/__DEFINES/lag_switch.dm
+ var/static/list/measures[MEASURES_AMOUNT]
+ /// List of measures that toggle automatically
+ var/list/auto_measures = list(DISABLE_GHOST_ZOOM_TRAY, DISABLE_RUNECHAT, DISABLE_USR_ICON2HTML)
+ /// Timer ID for the automatic veto period
+ var/veto_timer_id
+ /// Cooldown between say verb uses when slowmode is enabled
+ var/slowmode_cooldown = 3 SECONDS
+
+/datum/controller/subsystem/lag_switch/Initialize(start_timeofday)
+ for(var/i = 1, i <= measures.len, i++)
+ measures[i] = FALSE
+ var/auto_switch_pop = CONFIG_GET(number/auto_lag_switch_pop)
+ if(auto_switch_pop)
+ auto_switch = TRUE
+ trigger_pop = auto_switch_pop
+ RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, .proc/client_connected)
+ return ..()
+
+/datum/controller/subsystem/lag_switch/proc/client_connected(datum/source, client/connected)
+ SIGNAL_HANDLER
+ if(TGS_CLIENT_COUNT < trigger_pop)
+ return
+
+ auto_switch = FALSE
+ UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT)
+ veto_timer_id = addtimer(CALLBACK(src, .proc/set_all_measures, TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE)
+ message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)")
+ log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.")
+
+/// (En/Dis)able automatic triggering of switches based on client count
+/datum/controller/subsystem/lag_switch/proc/toggle_auto_enable()
+ auto_switch = !auto_switch
+ if(auto_switch)
+ RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, .proc/client_connected)
+ else
+ UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT)
+
+/// Called from an admin chat link
+/datum/controller/subsystem/lag_switch/proc/cancel_auto_enable_in_progress()
+ if(!veto_timer_id)
+ return FALSE
+
+ deltimer(veto_timer_id)
+ veto_timer_id = null
+ return TRUE
+
+/// Update the slowmode timer length and clear existing ones if reduced
+/datum/controller/subsystem/lag_switch/proc/change_slowmode_cooldown(length)
+ if(!length)
+ return FALSE
+
+ var/length_secs = length SECONDS
+ if(length_secs <= 0)
+ length_secs = 1 // one tick because cooldowns do not like 0
+
+ if(length_secs < slowmode_cooldown)
+ for(var/client/C as anything in GLOB.clients)
+ COOLDOWN_RESET(C, say_slowmode)
+
+ slowmode_cooldown = length_secs
+ if(measures[SLOWMODE_SAY])
+ to_chat(world, span_boldannounce("Slowmode timer has been changed to [length] seconds by an admin."))
+ return TRUE
+
+/// Handle the state change for individual measures
+/datum/controller/subsystem/lag_switch/proc/set_measure(measure_key, state)
+ if(isnull(measure_key) || isnull(state))
+ stack_trace("SSlag_switch.set_measure() was called with a null arg")
+ return FALSE
+ if(isnull(LAZYACCESS(measures, measure_key)))
+ stack_trace("SSlag_switch.set_measure() was called with a measure_key not in the list of measures")
+ return FALSE
+ if(measures[measure_key] == state)
+ return TRUE
+
+ measures[measure_key] = state
+
+ switch(measure_key)
+ if(DISABLE_DEAD_KEYLOOP)
+ if(state)
+ for(var/mob/user as anything in GLOB.player_list)
+ if(user.stat == DEAD && !user.client?.holder)
+ GLOB.keyloop_list -= user
+ deadchat_broadcast(span_big("To increase performance Observer freelook is now disabled. Please use Orbit, Teleport, and Jump to look around."), message_type = DEADCHAT_ANNOUNCEMENT)
+ else
+ GLOB.keyloop_list |= GLOB.player_list
+ deadchat_broadcast("Observer freelook has been re-enabled. Enjoy your wooshing.", message_type = DEADCHAT_ANNOUNCEMENT)
+ if(DISABLE_GHOST_ZOOM_TRAY)
+ if(state) // if enabling make sure current ghosts are updated
+ for(var/mob/dead/observer/ghost in GLOB.dead_mob_list)
+ if(!ghost.client)
+ continue
+ if(!ghost.client.holder && ghost.client.view_size.getView() != ghost.client.view_size.default)
+ ghost.client.view_size.resetToDefault()
+ if(SLOWMODE_SAY)
+ if(state)
+ to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been enabled with [slowmode_cooldown/10] seconds between messages."))
+ else
+ for(var/client/C as anything in GLOB.clients)
+ COOLDOWN_RESET(C, say_slowmode)
+ to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been disabled by an admin."))
+ if(DISABLE_NON_OBSJOBS)
+ world.update_status()
+
+ return TRUE
+
+/// Helper to loop over all measures for mass changes
+/datum/controller/subsystem/lag_switch/proc/set_all_measures(state, automatic = FALSE)
+ if(isnull(state))
+ stack_trace("SSlag_switch.set_all_measures() was called with a null state arg")
+ return FALSE
+
+ if(automatic)
+ message_admins("Lag Switch enabling automatic measures now.")
+ log_admin("Lag Switch enabling automatic measures now.")
+ veto_timer_id = null
+ for(var/i = 1, i <= auto_measures.len, i++)
+ set_measure(auto_measures[i], state)
+ return TRUE
+
+ for(var/i = 1, i <= measures.len, i++)
+ set_measure(i, state)
+ return TRUE
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 31dda2fc6cd..81add481df7 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -43,7 +43,6 @@ SUBSYSTEM_DEF(ticker)
var/news_report
- var/late_join_disabled
var/roundend_check_paused = FALSE
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 78152337492..eeccb32aaff 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -232,6 +232,8 @@
* * spans - Additional classes to be added to the message
*/
/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE)
+ if(SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(speaker, TRAIT_BYPASS_MEASURES))
+ return
// Ensure the list we are using, if present, is a copy so we don't modify the list provided to us
spans = spans ? spans.Copy() : list()
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 6663f223ce3..63dcd56a542 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -142,7 +142,7 @@
. = list()
.["version"] = GLOB.game_version
.["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE
- .["enter"] = GLOB.enter_allowed
+ .["enter"] = !LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS)
.["ai"] = CONFIG_GET(flag/allow_ai)
.["host"] = world.host ? world.host : null
.["round_id"] = GLOB.round_id
diff --git a/code/game/world.dm b/code/game/world.dm
index de4a2a02af5..1bf6d27fc6c 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -276,7 +276,7 @@ GLOBAL_VAR(restart_counter)
var/list/features = list()
- if (!GLOB.enter_allowed)
+ if(LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS))
features += "closed"
var/s = ""
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index c9bd7b74d7a..1138cba4f4e 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -587,15 +587,12 @@
set category = "Server"
set desc="People can't enter"
set name="Toggle Entering"
- GLOB.enter_allowed = !( GLOB.enter_allowed )
- if (!( GLOB.enter_allowed ))
- to_chat(world, "New players may no longer enter the game.", confidential = TRUE)
- else
- to_chat(world, "New players may now enter the game.", confidential = TRUE)
- log_admin("[key_name(usr)] toggled new player game entering.")
- message_admins(span_adminnotice("[key_name_admin(usr)] toggled new player game entering."))
- world.update_status()
- SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ if(!SSlag_switch.initialized)
+ return
+ SSlag_switch.set_measure(DISABLE_NON_OBSJOBS, !SSlag_switch.measures[DISABLE_NON_OBSJOBS])
+ log_admin("[key_name(usr)] toggled new player game entering. Lag Switch at index ([DISABLE_NON_OBSJOBS])")
+ message_admins("[key_name_admin(usr)] toggled new player game entering [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "OFF" : "ON"].")
+ SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[!SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleAI()
set category = "Server"
@@ -825,6 +822,9 @@
tgui_alert(usr, "You cannot manage jobs before the job subsystem is initialized!")
return
+ if(SSlag_switch.measures[DISABLE_NON_OBSJOBS])
+ dat += "
Lag Switch \"Disable non-observer late joining\" is ON. Only Observers may join!
"
+
dat += ""
for(var/j in SSjob.occupations)
@@ -957,3 +957,31 @@
"Admin login: [key_name(src)]")
if(string)
message_admins("[string]")
+
+/datum/admins/proc/show_lag_switch_panel()
+ set category = "Admin.Game"
+ set name = "Show Lag Switches"
+ set desc="Display the controls for drastic lag mitigation measures."
+
+ if(!SSlag_switch.initialized)
+ to_chat(usr, span_notice("The Lag Switch subsystem has not yet been initialized."))
+ return
+ if(!check_rights())
+ return
+
+ var/list/dat = list("Lag SwitchesLag (Reduction) Switches
")
+ dat += "Automatic Trigger: [SSlag_switch.auto_switch ? "On" : "Off"]
"
+ dat += "Population Threshold: [SSlag_switch.trigger_pop]
"
+ dat += "Slowmode Cooldown (toggle On/Off below): [SSlag_switch.slowmode_cooldown/10] seconds
"
+ dat += "
SET ALL MEASURES: ON | OFF
"
+ dat += "
Disable ghosts zoom and t-ray verbs (except staff): [SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] ? "On" : "Off"]
"
+ dat += "Disable late joining: [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "On" : "Off"]
"
+ dat += "
============! MAD GHOSTS ZONE !============
"
+ dat += "Disable deadmob keyLoop (except staff, informs dchat): [SSlag_switch.measures[DISABLE_DEAD_KEYLOOP] ? "On" : "Off"]
"
+ dat += "==========================================
"
+ dat += "
Measures below can be bypassed with a special trait
"
+ dat += "Slowmode say verb (informs world): [SSlag_switch.measures[SLOWMODE_SAY] ? "On" : "Off"]
"
+ dat += "Disable runechat: [SSlag_switch.measures[DISABLE_RUNECHAT] ? "On" : "Off"] - trait applies to speaker
"
+ dat += "Disable examine icons: [SSlag_switch.measures[DISABLE_USR_ICON2HTML] ? "On" : "Off"] - trait applies to examiner
"
+ dat += ""
+ usr << browse(dat.Join(), "window=lag_switch_panel;size=420x420")
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 2f2f7fe92fa..9258e2f3cf6 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -28,6 +28,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
return list(
/client/proc/invisimin, /*allows our mob to go invisible/visible*/
// /datum/admins/proc/show_traitor_panel, /*interface which shows a mob's mind*/ -Removed due to rare practical use. Moved to debug verbs ~Errorage
+ /datum/admins/proc/show_lag_switch_panel,
/datum/admins/proc/show_player_panel, /*shows an interface for individual players, with various links (links require additional flags*/
/datum/verbs/menu/Admin/verb/playerpanel,
/client/proc/game_panel, /*game panel, allows to change game-mode etc*/
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 94e8ba104fb..17088f47a49 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1674,6 +1674,58 @@
GLOB.station_goals += G
modify_goals()
+ else if(href_list["change_lag_switch"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ switch(href_list["change_lag_switch"])
+ if("ALL_ON")
+ SSlag_switch.set_all_measures(TRUE)
+ log_admin("[key_name(usr)] turned all Lag Switch measures ON.")
+ message_admins("[key_name_admin(usr)] turned all Lag Switch measures ON.")
+ if("ALL_OFF")
+ SSlag_switch.set_all_measures(FALSE)
+ log_admin("[key_name(usr)] turned all Lag Switch measures OFF.")
+ message_admins("[key_name_admin(usr)] turned all Lag Switch measures OFF.")
+ else
+ var/switch_index = text2num(href_list["change_lag_switch"])
+ if(!SSlag_switch.set_measure(switch_index, !LAZYACCESS(SSlag_switch.measures, switch_index)))
+ to_chat(src, span_danger("Something went wrong when trying to toggle that Lag Switch. Check runtimes for more info."), confidential = TRUE)
+ else
+ log_admin("[key_name(usr)] turned a Lag Switch measure at index ([switch_index]) [LAZYACCESS(SSlag_switch.measures, switch_index) ? "ON" : "OFF"]")
+ message_admins("[key_name_admin(usr)] turned a Lag Switch measure [LAZYACCESS(SSlag_switch.measures, switch_index) ? "ON" : "OFF"]")
+
+ src.show_lag_switch_panel()
+
+ else if(href_list["change_lag_switch_option"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ switch(href_list["change_lag_switch_option"])
+ if("CANCEL")
+ if(SSlag_switch.cancel_auto_enable_in_progress())
+ log_admin("[key_name(usr)] canceled the automatic Lag Switch activation in progress.")
+ message_admins("[key_name_admin(usr)] canceled the automatic Lag Switch activation in progress.")
+ return // return here to avoid (re)rendering the panel for this case
+ if("TOGGLE_AUTO")
+ SSlag_switch.toggle_auto_enable()
+ log_admin("[key_name(usr)] toggled automatic Lag Switch activation [SSlag_switch.auto_switch ? "ON" : "OFF"].")
+ message_admins("[key_name_admin(usr)] toggled automatic Lag Switch activation [SSlag_switch.auto_switch ? "ON" : "OFF"].")
+ if("NUM")
+ var/new_num = input("Enter new threshold value:", "Num") as null|num
+ if(!isnull(new_num))
+ SSlag_switch.trigger_pop = new_num
+ log_admin("[key_name(usr)] set the Lag Switch automatic trigger pop to [new_num].")
+ message_admins("[key_name_admin(usr)] set the Lag Switch automatic trigger pop to [new_num].")
+ if("SLOWCOOL")
+ var/new_num = input("Enter new cooldown in seconds:", "Num") as null|num
+ if(!isnull(new_num))
+ SSlag_switch.change_slowmode_cooldown(new_num)
+ log_admin("[key_name(usr)] set the Lag Switch slowmode cooldown to [new_num] seconds.")
+ message_admins("[key_name_admin(usr)] set the Lag Switch slowmode cooldown to [new_num] seconds.")
+
+ src.show_lag_switch_panel()
+
else if(href_list["viewruntime"])
var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"])
if(!istype(error_viewer))
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index ebe6e5d96d4..408fe9b4d52 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -474,7 +474,7 @@ GLOBAL_VAR(station_nuke_source)
SSticker.roundend_check_paused = FALSE
return
- GLOB.enter_allowed = FALSE
+ SSlag_switch.set_measure(DISABLE_NON_OBSJOBS, TRUE)
var/off_station = 0
var/turf/bomb_location = get_turf(src)
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 748de9c50d4..b32b1936975 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -32,6 +32,8 @@
var/externalreplyamount = 0
///When was the last time we warned them about not cryoing without an ahelp, set to -5 minutes so that rounstart cryo still warns
COOLDOWN_DECLARE(cryo_warned)
+ ///Tracks say() usage for ic/dchat while slowmode is enabled
+ COOLDOWN_DECLARE(say_slowmode)
/////////
//OTHER//
/////////
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 40d25049c18..d26025f8b66 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -448,6 +448,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
view_size.setZoomMode()
fit_viewport()
Master.UpdateTickRate()
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CLIENT_CONNECT, src)
//////////////
//DISCONNECT//
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index a0f3a10d651..7a276f4b212 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -181,7 +181,7 @@
to_chat(usr, span_danger("The round is either not ready, or has already finished..."))
return
- if(!GLOB.enter_allowed)
+ if(SSlag_switch.measures[DISABLE_NON_OBSJOBS])
to_chat(usr, span_notice("There is an administrative lock on entering the game!"))
return
@@ -214,7 +214,10 @@
ready = PLAYER_NOT_READY
return FALSE
- var/this_is_like_playing_right = tgui_alert(usr, "Are you sure you wish to observe? You will not be able to play this round!","Player Setup",list("Yes","No"))
+ var/less_input_message
+ if(SSlag_switch.measures[DISABLE_DEAD_KEYLOOP])
+ less_input_message = " - Notice: Observer freelook is currently disabled."
+ var/this_is_like_playing_right = tgui_alert(usr, "Are you sure you wish to observe? You will not be able to play this round![less_input_message]","Player Setup",list("Yes","No"))
if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes")
ready = PLAYER_NOT_READY
@@ -295,10 +298,6 @@
tgui_alert(usr, get_job_unavailable_error_message(error, rank))
return FALSE
- if(SSticker.late_join_disabled)
- tgui_alert(usr, "An administrator has disabled late join spawning.")
- return FALSE
-
var/arrivals_docked = TRUE
if(SSshuttle.arrivals)
close_spawn_windows() //In case we get held up
@@ -388,7 +387,10 @@
/mob/dead/new_player/proc/LateChoices()
- var/list/dat = list("Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
")
+ var/list/dat = list()
+ if(SSlag_switch.measures[DISABLE_NON_OBSJOBS])
+ dat += "Only Observers may join at this time.
"
+ dat += "Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
"
if(SSshuttle.emergency)
switch(SSshuttle.emergency.mode)
if(SHUTTLE_ESCAPE)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index a4d64d5c9a8..9500f17f546 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -365,6 +365,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
client.view_size.setDefault(getScreenSize(client.prefs.widescreenpref))//Let's reset so people can't become allseeing gods
SStgui.on_transfer(src, mind.current) // Transfer NanoUIs.
+ if(mind.current.stat == DEAD && SSlag_switch.measures[DISABLE_DEAD_KEYLOOP])
+ to_chat(src, span_warning("To leave your body again use the Ghost verb."))
mind.current.key = key
mind.current.client.init_verbs()
return TRUE
@@ -520,6 +522,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "View Range"
set desc = "Change your view range."
+ if(SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] && !client?.holder)
+ to_chat(usr, span_notice("That verb is currently globally disabled."))
+ return
+
var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT
if(client.view_size.getView() == client.view_size.default)
var/list/views = list()
@@ -534,6 +540,11 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/verb/add_view_range(input as num)
set name = "Add View Range"
set hidden = TRUE
+
+ if(SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] && !client?.holder)
+ to_chat(usr, span_notice("That verb is currently globally disabled."))
+ return
+
var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT
if(input)
client.rescale_view(input, 0, ((max_view*2)+1) - 15)
@@ -954,6 +965,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set desc = "Toggles a view of sub-floor objects"
var/static/t_ray_view = FALSE
+ if(SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] && !client?.holder && !t_ray_view)
+ to_chat(usr, span_notice("That verb is currently globally disabled."))
+ return
t_ray_view = !t_ray_view
var/list/t_ray_images = list()
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index f17219adc58..551a7ddff03 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -76,6 +76,9 @@
var/turf/T = get_turf(src)
if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf))
deadchat_broadcast(" has died at [get_area_name(T)].", "[mind.name]", follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
+ if(SSlag_switch.measures[DISABLE_DEAD_KEYLOOP] && !client?.holder)
+ to_chat(src, span_deadsay(span_big("Observer freelook is disabled.\nPlease use Orbit, Teleport, and Jump to look around.")))
+ ghostize(TRUE)
if(mind)
mind.store_memory("Time of death: [tod]", 0)
set_drugginess(0)
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index 5e5111b0b1d..4ac954fd6e2 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -148,6 +148,12 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
say_dead(original_message)
return
+ if(client && SSlag_switch.measures[SLOWMODE_SAY] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES) && !forced && src == usr)
+ if(!COOLDOWN_FINISHED(client, say_slowmode))
+ to_chat(src, span_warning("Message not sent due to slowmode. Please wait [SSlag_switch.slowmode_cooldown/10] seconds between messages.\n\"[message]\""))
+ return
+ COOLDOWN_START(client, say_slowmode, SSlag_switch.slowmode_cooldown)
+
if(!can_speak_basic(original_message, ignore_spam, forced))
return
@@ -345,7 +351,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
//speech bubble
var/list/speech_bubble_recipients = list()
for(var/mob/M in listening)
- if(M.client && !M.client.prefs.chat_on_map)
+ if(M.client && (!M.client.prefs.chat_on_map || (SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES))))
speech_bubble_recipients.Add(M.client)
var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER)
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm
index a89739544b0..f6babd95831 100644
--- a/code/modules/mob/mob_lists.dm
+++ b/code/modules/mob/mob_lists.dm
@@ -49,6 +49,10 @@
/mob/proc/add_to_player_list()
SHOULD_CALL_PARENT(TRUE)
GLOB.player_list |= src
+ if(client.holder)
+ GLOB.keyloop_list |= src
+ else if(stat != DEAD || !SSlag_switch?.measures[DISABLE_DEAD_KEYLOOP])
+ GLOB.keyloop_list |= src
if(!SSticker?.mode)
return
if(stat == DEAD)
@@ -60,6 +64,7 @@
/mob/proc/remove_from_player_list()
SHOULD_CALL_PARENT(TRUE)
GLOB.player_list -= src
+ GLOB.keyloop_list -= src
if(!SSticker?.mode)
return
if(stat == DEAD)
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 37a42f4bb17..76f44ab130d 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -60,6 +60,12 @@
to_chat(src, span_danger("You cannot talk in deadchat (muted)."))
return
+ if(SSlag_switch.measures[SLOWMODE_SAY] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES) && src == usr)
+ if(!COOLDOWN_FINISHED(client, say_slowmode))
+ to_chat(src, span_warning("Message not sent due to slowmode. Please wait [SSlag_switch.slowmode_cooldown/10] seconds between messages.\n\"[message]\""))
+ return
+ COOLDOWN_START(client, say_slowmode, SSlag_switch.slowmode_cooldown)
+
if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT))
return
diff --git a/config/admins.txt b/config/admins.txt
index 3d3885d0d16..70293c3880b 100644
--- a/config/admins.txt
+++ b/config/admins.txt
@@ -144,3 +144,4 @@ StyleMistake = Game Master
actioninja = Game Master
bobbahbrown = Game Master
Jaredfogle = Game Master
+WaylandSmithy = Game Master
diff --git a/config/config.txt b/config/config.txt
index 4cb4daeeddd..23787ed4604 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -316,6 +316,9 @@ NOTE_FRESH_DAYS 91.31055
## Notes older then this will be completely faded out.
NOTE_STALE_DAYS 365.2422
+## Uncomment to allow drastic performence enhancemet measures to turn on automatically once there are equal or more clients than the configured amount (will also prompt admin for veto)
+#AUTO_LAG_SWITCH_POP 75
+
##Note: all population caps can be used with each other if desired.
## Uncomment for 'soft' population caps, players will be warned while joining if the living crew exceeds the listed number.
diff --git a/tgstation.dme b/tgstation.dme
index 272ffd2578c..b35027c7f76 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -78,6 +78,7 @@
#include "code\__DEFINES\is_helpers.dm"
#include "code\__DEFINES\jobs.dm"
#include "code\__DEFINES\keybinding.dm"
+#include "code\__DEFINES\lag_switch.dm"
#include "code\__DEFINES\language.dm"
#include "code\__DEFINES\layers.dm"
#include "code\__DEFINES\lighting.dm"
@@ -329,6 +330,7 @@
#include "code\controllers\subsystem\input.dm"
#include "code\controllers\subsystem\ipintel.dm"
#include "code\controllers\subsystem\job.dm"
+#include "code\controllers\subsystem\lag_switch.dm"
#include "code\controllers\subsystem\language.dm"
#include "code\controllers\subsystem\lighting.dm"
#include "code\controllers\subsystem\machines.dm"