[MIRROR] Turf Cascade Subsystem (#12348)

Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2026-02-01 10:30:41 -07:00
committed by GitHub
parent 153c095b14
commit a49b18dacc
8 changed files with 133 additions and 52 deletions
@@ -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
@@ -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
@@ -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.
+11
View File
@@ -531,3 +531,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
+2 -2
View File
@@ -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)]")
+1
View File
@@ -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()].")
@@ -42,7 +42,7 @@ export const SORTING_TYPES: readonly SortType[] = [
},
{
label: 'Subsystem Overtime',
propName: 'overtime',
propName: 'tick_overrun',
inDeciseconds: true,
},
];
+1
View File
@@ -636,6 +636,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"