Skip to content

Commit

Permalink
Ports SSfoam and SSsmoke (#17742)
Browse files Browse the repository at this point in the history
* Ports SSfoam and SSsmoke

* Remove dupe define
  • Loading branch information
ThatLing committed Feb 8, 2023
1 parent 9bb63a7 commit f83723c
Show file tree
Hide file tree
Showing 78 changed files with 1,708 additions and 1,114 deletions.
8 changes: 8 additions & 0 deletions code/__DEFINES/MC.dm
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,11 @@
PreInit();\
}\
/datum/controller/subsystem/processing/##X

#define FLUID_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/fluids/##X);\
/datum/controller/subsystem/fluids/##X/New(){\
NEW_SS_GLOBAL(SS##X);\
PreInit();\
}\
/datum/controller/subsystem/fluids/##X/fire() {..() /*just so it shows up on the profiler*/} \
/datum/controller/subsystem/fluids/##X
6 changes: 6 additions & 0 deletions code/__DEFINES/maths.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
// Similar to clamp but the bottom rolls around to the top and vice versa. min is inclusive, max is exclusive
#define WRAP(val, min, max) ( min == max ? min : (val) - (round(((val) - (min))/((max) - (min))) * ((max) - (min))) )

/// Increments a value and wraps it if it exceeds some value. Can be used to circularly iterate through a list through `idx = WRAP_UP(idx, length_of_list)`.
#define WRAP_UP(val, max) (((val) % (max)) + 1)

// Real modulus that handles decimals
#define MODULUS(x, y) ( (x) - (y) * round((x) / (y)) )

Expand Down Expand Up @@ -220,3 +223,6 @@
/// Like DT_PROB_RATE but easier to use, simply put `if(DT_PROB(10, 5))`
#define DT_PROB(prob_per_second_percent, delta_time) (prob(100*DT_PROB_RATE(prob_per_second_percent/100, delta_time)))
// )

/// The number of cells in a taxicab circle (rasterized diamond) of radius X.
#define DIAMOND_AREA(X) (1 + 2*(X)*((X)+1))
3 changes: 2 additions & 1 deletion code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
#define FIRE_PRIORITY_VIS 10
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_WET_FLOORS 20
#define FIRE_PRIORITY_FLUIDS 20
#define FIRE_PRIORITY_AIR 20
#define FIRE_PRIORITY_NPC 20
#define FIRE_PRIORITY_PROCESS 25
Expand Down Expand Up @@ -261,7 +262,7 @@

// Subsystem delta times or tickrates, in seconds. I.e, how many seconds in between each process() call for objects being processed by that subsystem.
// Only use these defines if you want to access some other objects processing delta_time, otherwise use the delta_time that is sent as a parameter to process()
#define SSFLUIDS_DT (SSfluids.wait/10)
#define SSFLUIDS_DT (SSplumbing.wait/10)
#define SSMACHINES_DT (SSmachines.wait/10)
#define SSMOBS_DT (SSmobs.wait/10)
#define SSOBJ_DT (SSobj.wait/10)
Expand Down
10 changes: 9 additions & 1 deletion code/__HELPERS/_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
#define LAZYCLEARLIST(L) if(L) L.Cut()
///Accesses an associative list, returns null if nothing is found
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
///Qdel every item in the list before setting the list to null
#define QDEL_LAZYLIST(L) for(var/I in L) qdel(I); L = null;
//These methods don't null the list
///Use LAZYLISTDUPLICATE instead if you want it to null with no entries
#define LAZYCOPY(L) (L ? L.Copy() : list() )
/// Consider LAZYNULL instead
#define LAZYCLEARLIST(L) if(L) L.Cut()
///Returns the list if it's actually a valid list, otherwise will initialize it
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V);
Expand Down
14 changes: 14 additions & 0 deletions code/__HELPERS/reagents.dm
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,17 @@
if(!GLOB.chemical_reactions_list[primary_reagent])
GLOB.chemical_reactions_list[primary_reagent] = list()
GLOB.chemical_reactions_list[primary_reagent] += R

//Creates foam from the reagent. Metaltype is for metal foam, notification is what to show people in textbox
/datum/reagents/proc/create_foam(foamtype, foam_volume, result_type = null, notification = null, log = FALSE)
var/location = get_turf(my_atom)

var/datum/effect_system/fluid_spread/foam/foam = new foamtype()
foam.set_up(amount = foam_volume, holder = my_atom, location = location, carry = src, result_type = result_type)
foam.start(log = log)

clear_reagents()
if(!notification)
return
for(var/mob/M in viewers(5, location))
to_chat(M, notification)
261 changes: 261 additions & 0 deletions code/controllers/subsystem/fluids.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// Flags indicating what parts of the fluid the subsystem processes.
/// Indicates that a fluid subsystem processes fluid spreading.
#define SS_PROCESSES_SPREADING (1<<0)
/// Indicates that a fluid subsystem processes fluid effects.
#define SS_PROCESSES_EFFECTS (1<<1)

/**
* # Fluid Subsystem
*
* A subsystem that processes the propagation and effects of a particular fluid.
*
* Both fluid spread and effect processing are handled through a carousel system.
* Fluids being spread and fluids being processed are organized into buckets.
* Each fresh (non-resumed) fire one bucket of each is selected to be processed.
* These selected buckets are then fully processed.
* The next fresh fire selects the next bucket in each set for processing.
* If this would walk off the end of a carousel list we wrap back to the first element.
* This effectively makes each set a circular list, hence a carousel.
*/
SUBSYSTEM_DEF(fluids)
name = "Fluid"
wait = 0 // Will be autoset to whatever makes the most sense given the spread and effect waits.
flags = SS_BACKGROUND|SS_KEEP_TIMING
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME

// Fluid spread processing:
/// The amount of time (in deciseconds) before a fluid node is created and when it spreads.
var/spread_wait = 1 SECONDS
/// The number of buckets in the spread carousel.
var/num_spread_buckets
/// The set of buckets containing fluid nodes to spread.
var/list/spread_carousel
/// The index of the spread carousel bucket currently being processed.
var/spread_bucket_index
/// The set of fluid nodes we are currently processing spreading for.
var/list/currently_spreading
/// Whether the subsystem has resumed spreading fluid.
var/resumed_spreading

// Fluid effect processing:
/// The amount of time (in deciseconds) between effect processing ticks for each fluid node.
var/effect_wait = 1 SECONDS
/// The number of buckets in the effect carousel.
var/num_effect_buckets
/// The set of buckets containing fluid nodes to process effects for.
var/list/effect_carousel
/// The index of the currently processing bucket on the effect carousel.
var/effect_bucket_index
/// The set of fluid nodes we are currently processing effects for.
var/list/currently_processing
/// Whether the subsystem has resumed processing fluid effects.
var/resumed_effect_processing

/datum/controller/subsystem/fluids/Initialize()
initialize_waits()
initialize_spread_carousel()
initialize_effect_carousel()
return SS_INIT_SUCCESS

/**
* Initializes the subsystem waits.
*
* Ensures that the subsystem's fire wait evenly splits the spread and effect waits.
*/
/datum/controller/subsystem/fluids/proc/initialize_waits()
if (spread_wait <= 0)
WARNING("[src] has the invalid spread wait [spread_wait].")
spread_wait = 1 SECONDS
if (effect_wait <= 0)
WARNING("[src] has the invalid effect wait [effect_wait].")
spread_wait = 1 SECONDS

// Sets the overall wait of the subsystem to evenly divide both the effect and spread waits.
var/max_wait = Gcd(spread_wait, effect_wait)
if (max_wait < wait || wait <= 0)
wait = max_wait
else
// If the wait of the subsystem overall is set to a valid value make the actual wait of the subsystem evenly divide that as well.
// Makes effect bubbling possible with identical spread and effect waits.
wait = Gcd(wait, max_wait)


/**
* Initializes the carousel used to process fluid spreading.
*
* Synchronizes the spread delta time with the actual target spread tick rate.
* Builds the carousel buckets used to queue spreads.
*/
/datum/controller/subsystem/fluids/proc/initialize_spread_carousel()
// Make absolutely certain that the spread wait is in sync with the target spread tick rate.
num_spread_buckets = round(spread_wait / wait)
spread_wait = wait * num_spread_buckets

spread_carousel = list()
spread_carousel.len = num_spread_buckets
for(var/i in 1 to num_spread_buckets)
spread_carousel[i] = list()
currently_spreading = list()
spread_bucket_index = 1

/**
* Initializes the carousel used to process fluid effects.
*
* Synchronizes the spread delta time with the actual target spread tick rate.
* Builds the carousel buckets used to bubble processing.
*/
/datum/controller/subsystem/fluids/proc/initialize_effect_carousel()
// Make absolutely certain that the effect wait is in sync with the target effect tick rate.
num_effect_buckets = round(effect_wait / wait)
effect_wait = wait * num_effect_buckets

effect_carousel = list()
effect_carousel.len = num_effect_buckets
for(var/i in 1 to num_effect_buckets)
effect_carousel[i] = list()
currently_processing = list()
effect_bucket_index = 1


/datum/controller/subsystem/fluids/fire(resumed)
var/delta_time
var/cached_bucket_index
var/list/obj/effect/particle_effect/fluid/currentrun
MC_SPLIT_TICK_INIT(2)

MC_SPLIT_TICK // Start processing fluid spread:
if(!resumed_spreading)
spread_bucket_index = WRAP_UP(spread_bucket_index, num_spread_buckets)
currently_spreading = spread_carousel[spread_bucket_index]
spread_carousel[spread_bucket_index] = list() // Reset the bucket so we don't process an _entire station's worth of foam_ spreading every 2 ticks when the foam flood event happens.
resumed_spreading = TRUE

delta_time = spread_wait / (1 SECONDS)
currentrun = currently_spreading
while(currentrun.len)
var/obj/effect/particle_effect/fluid/to_spread = currentrun[currentrun.len]
currentrun.len--

if(!QDELETED(to_spread))
to_spread.spread(delta_time)
to_spread.spread_bucket = null

if (MC_TICK_CHECK)
break

if(!currentrun.len)
resumed_spreading = FALSE

MC_SPLIT_TICK // Start processing fluid effects:
if(!resumed_effect_processing)
effect_bucket_index = WRAP_UP(effect_bucket_index, num_effect_buckets)
var/list/tmp_list = effect_carousel[effect_bucket_index]
currently_processing = tmp_list.Copy()
resumed_effect_processing = TRUE

delta_time = effect_wait / (1 SECONDS)
cached_bucket_index = effect_bucket_index
currentrun = currently_processing
while(currentrun.len)
var/obj/effect/particle_effect/fluid/to_process = currentrun[currentrun.len]
currentrun.len--

if (QDELETED(to_process) || to_process.process(delta_time) == PROCESS_KILL)
effect_carousel[cached_bucket_index] -= to_process
to_process.effect_bucket = null
to_process.datum_flags &= ~DF_ISPROCESSING

if (MC_TICK_CHECK)
break

if(!currentrun.len)
resumed_effect_processing = FALSE


/**
* Queues a fluid node to spread later after one full carousel rotation.
*
* Arguments:
* - [node][/obj/effect/particle_effect/fluid]: The node to queue to spread.
*/
/datum/controller/subsystem/fluids/proc/queue_spread(obj/effect/particle_effect/fluid/node)
if (node.spread_bucket)
return

spread_carousel[spread_bucket_index] += node
node.spread_bucket = spread_bucket_index

/**
* Cancels a queued spread of a fluid node.
*
* Arguments:
* - [node][/obj/effect/particle_effect/fluid]: The node to cancel the spread of.
*/
/datum/controller/subsystem/fluids/proc/cancel_spread(obj/effect/particle_effect/fluid/node)
if(!node.spread_bucket)
return

var/bucket_index = node.spread_bucket
spread_carousel[bucket_index] -= node
if (bucket_index == spread_bucket_index)
currently_spreading -= node

node.spread_bucket = null

/**
* Starts processing the effects of a fluid node.
*
* The fluid node will next process after one full bucket rotation.
*
* Arguments:
* - [node][/obj/effect/particle_effect/fluid]: The node to start processing.
*/
/datum/controller/subsystem/fluids/proc/start_processing(obj/effect/particle_effect/fluid/node)
if (node.datum_flags & DF_ISPROCESSING || node.effect_bucket)
return

// Edit this value to make all fluids process effects (at the same time|offset by when they started processing| -> offset by a random amount <- )
var/bucket_index = rand(1, num_effect_buckets)
effect_carousel[bucket_index] += node
node.effect_bucket = bucket_index
node.datum_flags |= DF_ISPROCESSING

/**
* Stops processing the effects of a fluid node.
*
* Arguments:
* - [node][/obj/effect/particle_effect/fluid]: The node to stop processing.
*/
/datum/controller/subsystem/fluids/proc/stop_processing(obj/effect/particle_effect/fluid/node)
if(!(node.datum_flags & DF_ISPROCESSING))
return

var/bucket_index = node.effect_bucket
if(!bucket_index)
return

effect_carousel[bucket_index] -= node
if (bucket_index == effect_bucket_index)
currently_processing -= node

node.effect_bucket = null
node.datum_flags &= ~DF_ISPROCESSING

#undef SS_PROCESSES_SPREADING
#undef SS_PROCESSES_EFFECTS


// Subtypes:

/// The subsystem responsible for processing smoke propagation and effects.
FLUID_SUBSYSTEM_DEF(smoke)
name = "Smoke"
spread_wait = 0.1 SECONDS
effect_wait = 2.0 SECONDS

/// The subsystem responsible for processing foam propagation and effects.
FLUID_SUBSYSTEM_DEF(foam)
name = "Foam"
wait = 0.1 SECONDS // Makes effect bubbling work with foam.
spread_wait = 0.2 SECONDS
effect_wait = 0.2 SECONDS
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PROCESSING_SUBSYSTEM_DEF(fluids)
name = "Fluids"
PROCESSING_SUBSYSTEM_DEF(plumbing)
name = "Plumbing"
wait = 10
stat_tag = "FD" //its actually Fluid Ducts
flags = SS_NO_INIT
6 changes: 3 additions & 3 deletions code/datums/components/plumbing/plumbing.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

/datum/component/plumbing/process()
if(!demand_connects || !reagents)
STOP_PROCESSING(SSfluids, src)
STOP_PROCESSING(SSplumbing, src)
return
if(reagents.total_volume < reagents.maximum_volume)
for(var/D in GLOB.cardinals)
Expand Down Expand Up @@ -117,7 +117,7 @@
/datum/component/plumbing/proc/disable() //we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
if(!active)
return
STOP_PROCESSING(SSfluids, src)
STOP_PROCESSING(SSplumbing, src)
for(var/A in ducts)
var/datum/ductnet/D = ducts[A]
D.remove_plumber(src)
Expand All @@ -131,7 +131,7 @@
active = TRUE

if(demand_connects)
START_PROCESSING(SSfluids, src)
START_PROCESSING(SSplumbing, src)

for(var/D in GLOB.cardinals)
if(D & (demand_connects + supply_connects))
Expand Down
4 changes: 3 additions & 1 deletion code/game/machinery/ai_slipper.dm
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
if(cooldown_time > world.time)
to_chat(user, span_danger("[src] cannot be activated for <b>[DisplayTimeText(world.time - cooldown_time)]</b>."))
return
new /obj/effect/particle_effect/foam(loc)
var/datum/effect_system/fluid_spread/foam/foam = new
foam.set_up(4, holder = src, location = loc)
foam.start()
uses--
to_chat(user, span_notice("You activate [src]. It now has <b>[uses]</b> uses of foam remaining."))
cooldown = world.time + cooldown_time
Expand Down

0 comments on commit f83723c

Please sign in to comment.