diff --git a/code/controllers/subsystems/turf_cascade.dm b/code/controllers/subsystems/turf_cascade.dm new file mode 100644 index 0000000000..97343b71d7 --- /dev/null +++ b/code/controllers/subsystems/turf_cascade.dm @@ -0,0 +1,98 @@ +// Defaults are tuned for bluespace cascade +#define DEFAULT_CONVERSION_RATE 350 +#define DEFAULT_CONVERSION_PROB 60 +#define DEFAULT_CONVERSION_DELAY 2.5 SECONDS + +SUBSYSTEM_DEF(turf_cascade) + name = "Turf Cascade" + wait = 2 + flags = SS_NO_INIT + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + VAR_PRIVATE/last_group_time = 0 + VAR_PRIVATE/next_group_delay = DEFAULT_CONVERSION_DELAY + + VAR_PRIVATE/list/currentrun = list() + VAR_PRIVATE/list/remaining_turf = list() + VAR_PRIVATE/turf_iterations = 0 + + VAR_PRIVATE/turf_replace_type = null // Turf path that turfs will be replaced with + VAR_PRIVATE/conversion_probability = DEFAULT_CONVERSION_PROB // Randomized rate of conversion, 0 to 100 + VAR_PRIVATE/conversion_rate = DEFAULT_CONVERSION_RATE // Maximum number of turfs converted in each batch + +/datum/controller/subsystem/turf_cascade/stat_entry(msg) + msg = "C: [length(currentrun)] | R: [length(remaining_turf)] | R: [conversion_rate] | P: [turf_replace_type]" + . = ..() + +/datum/controller/subsystem/turf_cascade/fire(resumed = FALSE) + if(!resumed) + if(world.time < (last_group_time + next_group_delay)) // Wait for next expansion + return + if(!turf_replace_type || (!length(remaining_turf) && !length(currentrun))) + stop_cascade() + return + last_group_time = world.time + + if(!length(currentrun) && length(remaining_turf) && turf_iterations <= 0) + // Create a random list of tiles to expand with instead of doing it in order + var/subtractive_rand_max = conversion_rate * (1 - (conversion_probability / 100)) + var/i = 10 // Always do at least a handful of the oldest, to avoid spots that linger unfilled + while(i-- > 0) + var/turf/next = remaining_turf[1] + remaining_turf -= next + currentrun += next + if(!length(remaining_turf)) + break + + // Now for randomized growth. If we still have any left to grow into! + if(length(remaining_turf)) + turf_iterations = max(1, conversion_rate - rand(0, subtractive_rand_max)) // Allows for slower rates and more messy growth, min 1, max conversion_rate + + while(turf_iterations-- > 0) + var/turf/next = pick(remaining_turf) + remaining_turf -= next + currentrun += next + if(!length(remaining_turf)) + break + if(MC_TICK_CHECK) + return + + while(length(currentrun)) + var/turf/changing = currentrun[1] + currentrun -= changing + + // Convert turf if we are not the replacement type already + if(changing.type != turf_replace_type) + changing.ChangeTurf(turf_replace_type) + remaining_turf += changing.conversion_cascade_act(remaining_turf) + + if(MC_TICK_CHECK) + return + +/// Starts the turf cascade and boots up the subsystem. If a cascade is already in process, it will not allow another another to start. +/datum/controller/subsystem/turf_cascade/proc/start_cascade(turf/start_turf, turf_path, max_per_fire = DEFAULT_CONVERSION_RATE, time_delay = DEFAULT_CONVERSION_DELAY, convert_probability = DEFAULT_CONVERSION_PROB) + if(turf_replace_type) + return + if(!isturf(start_turf) || !max_per_fire) + return + turf_replace_type = turf_path + remaining_turf.Add(start_turf) + conversion_rate = max_per_fire + conversion_probability = convert_probability + // Configure subsystem for the cascade... Doing it with tick delays is uglier than just making the subsystem slower or faster. Considering this will end the round anyway. + can_fire = TRUE + next_group_delay = DEFAULT_CONVERSION_DELAY + +/// Called when we have no more turfs to convert, or an admin wants to emergency stop +/datum/controller/subsystem/turf_cascade/proc/stop_cascade() + turf_replace_type = null + remaining_turf.Cut() + currentrun.Cut() + conversion_rate = DEFAULT_CONVERSION_RATE + conversion_probability = DEFAULT_CONVERSION_PROB + next_group_delay = DEFAULT_CONVERSION_DELAY + can_fire = FALSE + +#undef DEFAULT_CONVERSION_RATE +#undef DEFAULT_CONVERSION_PROB +#undef DEFAULT_CONVERSION_DELAY diff --git a/code/game/gamemodes/endgame/supermatter_cascade/blob.dm b/code/game/gamemodes/endgame/supermatter_cascade/blob.dm index 619241b306..955b872b26 100644 --- a/code/game/gamemodes/endgame/supermatter_cascade/blob.dm +++ b/code/game/gamemodes/endgame/supermatter_cascade/blob.dm @@ -10,55 +10,19 @@ //l_color="#0066FF" plane = PLANE_LIGHTING_ABOVE - var/spawned=0 // DIR mask - var/next_check=0 - var/list/avail_dirs = list(NORTH,SOUTH,EAST,WEST) - -/turf/unsimulated/wall/supermatter/Initialize(mapload) +/turf/unsimulated/wall/supermatter/conversion_cascade_act(list/already_marked_turfs) + // Do pretty fadeout animation for the new turf . = ..() - START_PROCESSING(SSturfs, src) - next_check = world.time+5 SECONDS - -/turf/unsimulated/wall/supermatter/Destroy() - STOP_PROCESSING(SSturfs, src) - return ..() - -/turf/unsimulated/wall/supermatter/process() - // Only check infrequently. - if(next_check>world.time) return - - // No more available directions? Shut down process(). - if(avail_dirs.len==0) - STOP_PROCESSING(SSobj, src) - return 1 - - // We're checking, reset the timer. - next_check = world.time+5 SECONDS - - // Choose a direction. - var/pdir = pick(avail_dirs) - avail_dirs -= pdir - var/turf/T=get_step(src,pdir) - - // EXPAND - if(!istype(T,type)) - // Do pretty fadeout animation for 1s. - new /obj/effect/overlay/bluespacify(T) - spawn(10) - // Nom. - for(var/atom/movable/A in T) - if(A) - if(isliving(A)) - qdel(A) - else if(istype(A,/mob)) // Observers, AI cameras. - continue - else - qdel(A) - T.ChangeTurf(type) - - if((spawned & (NORTH|SOUTH|EAST|WEST)) == (NORTH|SOUTH|EAST|WEST)) - STOP_PROCESSING(SSturfs, src) - return + for(var/turf/valid_turf in .) + new /obj/effect/overlay/bluespacify(valid_turf) + // Consume everything in our turf + for(var/atom/movable/A in src) + if(isliving(A)) + qdel(A) + continue + if(istype(A,/mob)) // Observers, AI cameras. + continue + qdel(A) /turf/unsimulated/wall/supermatter/attack_generic(mob/user as mob) return attack_hand(user) @@ -117,3 +81,7 @@ return qdel(user) + + +/turf/unsimulated/wall/supermatter/ex_act(severity) + return diff --git a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm index 10940eaf1a..930580fa7a 100644 --- a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm +++ b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm @@ -58,7 +58,9 @@ GLOBAL_VAR_INIT(universe_has_ended, 0) PlayerSet() - new /obj/singularity/narsie/large/exit(pick(GLOB.endgame_exits)) + if(GLOB.endgame_exits?.len) + new /obj/singularity/narsie/large/exit(pick(GLOB.endgame_exits)) + spawn(rand(30,60) SECONDS) var/txt = {" There's been a galaxy-wide electromagnetic pulse. All of our systems are heavily damaged and many personnel are dead or dying. We are seeing increasing indications of the universe itself beginning to unravel. diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 92e05cd339..07c7177584 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -517,3 +517,14 @@ if(!ismopable(movable_content)) continue movable_content.wash(clean_types) + +/// Used during turf_cascade subsystem to determine additional effects when spreading, and directions it may spread +/turf/proc/conversion_cascade_act(list/already_marked_turfs) + var/list/expanding_turfs = list() + + for(var/expand_dir in GLOB.cardinalz) + var/turf/next_turf = get_step(src, expand_dir) + if(next_turf && next_turf.type != type && !(next_turf in already_marked_turfs)) // Yes in already_marked_turfs is expensive, but less expensive than 6 dupes per turf potentially in the loop + expanding_turfs += next_turf + + return expanding_turfs diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 29a73b634a..9b5162db49 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -427,8 +427,8 @@ ADMIN_VERB(secrets, R_HOLDER, "Secrets", "Abuse harder than you ever have before if("supermatter_cascade") var/choice = tgui_alert(holder, "You sure you want to destroy the universe and create a large explosion at your location? Misuse of this could result in removal of flags or hilarity.","WARNING!", list("NO TIME TO EXPLAIN", "Cancel")) if(choice == "NO TIME TO EXPLAIN") - explosion(get_turf(holder), 8, 16, 24, 32, 1) - new /turf/unsimulated/wall/supermatter(get_turf(holder)) + explosion(get_turf(holder.mob), 8, 16, 24, 32, 1) + SSturf_cascade.start_cascade(get_turf(holder.mob), /turf/unsimulated/wall/supermatter) SetUniversalState(/datum/universal_state/supermatter_cascade) message_admins("[key_name_admin(holder)] has managed to destroy the universe with a supermatter cascade. Good job, [key_name_admin(holder)]") diff --git a/code/modules/power/singularity/act.dm b/code/modules/power/singularity/act.dm index 5d68684064..890f4ffd91 100644 --- a/code/modules/power/singularity/act.dm +++ b/code/modules/power/singularity/act.dm @@ -77,6 +77,7 @@ if(forensic_data?.get_hiddenprints()) prints = ", all touchers : " + forensic_data?.get_hiddenprints() + SSturf_cascade.start_cascade(get_turf(src), /turf/unsimulated/wall/supermatter) SetUniversalState(/datum/universal_state/supermatter_cascade) log_admin("New super singularity made by eating a SM crystal [prints]. Last touched by [forensic_data?.get_lastprint()].") message_admins("New super singularity made by eating a SM crystal [prints]. Last touched by [forensic_data?.get_lastprint()].") diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts b/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts index accad50341..889402080e 100644 --- a/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts +++ b/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts @@ -42,7 +42,7 @@ export const SORTING_TYPES: readonly SortType[] = [ }, { label: 'Subsystem Overtime', - propName: 'overtime', + propName: 'tick_overrun', inDeciseconds: true, }, ]; diff --git a/vorestation.dme b/vorestation.dme index df3bf81a9b..621cfe809d 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -614,6 +614,7 @@ #include "code\controllers\subsystems\time_track.dm" #include "code\controllers\subsystems\timer.dm" #include "code\controllers\subsystems\transcore_vr.dm" +#include "code\controllers\subsystems\turf_cascade.dm" #include "code\controllers\subsystems\verb_manager.dm" #include "code\controllers\subsystems\vis_overlays.dm" #include "code\controllers\subsystems\vote.dm"