[MIRROR] Port /datum/status_effect and convert wetness and fire stacks to it (#11666)

Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-09-17 05:21:49 -07:00
committed by GitHub
parent 0000fce008
commit 9f292671ae
88 changed files with 1775 additions and 249 deletions

View File

@@ -260,9 +260,23 @@
/atom/proc/emag_act(var/remaining_charges, var/mob/user, var/emag_source)
return -1
/atom/proc/fire_act()
return
/**
* Respond to fire being used on our atom
*
* Default behaviour is to send [COMSIG_ATOM_FIRE_ACT] and return
*/
/atom/proc/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, air, exposed_temperature, exposed_volume)
return FALSE
/**
* Sends [COMSIG_ATOM_EXTINGUISH] signal, which properly removes burning component if it is present.
*
* Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return
*/
/atom/proc/extinguish()
SHOULD_CALL_PARENT(TRUE)
return SEND_SIGNAL(src, COMSIG_ATOM_EXTINGUISH)
// Returns an assoc list of RCD information.
// Example would be: list(RCD_VALUE_MODE = RCD_DECONSTRUCT, RCD_VALUE_DELAY = 50, RCD_VALUE_COST = RCD_SHEETS_PER_MATTER_UNIT * 4)

View File

@@ -417,7 +417,7 @@
if(emagged)
var/mob/living/M = user
M.adjust_fire_stacks(5)
M.IgniteMob() //flew into a star, so you're on fire
M.ignite_mob() //flew into a star, so you're on fire
to_chat(user,span_danger(span_large("You feel an immense wave of heat emanate from \the [src]. Your skin bursts into flames.")))
dat += "<br><P ALIGN=Right><a href='byond://?src=\ref[src];menu=1'>OK...</a></P>"

View File

@@ -301,7 +301,7 @@
M.client.eye = src
M.stop_pulling()
M.loc = src
M.ExtinguishMob()
M.extinguish_mob()
if(M.health > -100 && (M.health < 0 || M.sleeping))
to_chat(M, span_boldnotice("You feel a cold liquid surround you. Your skin starts to freeze up."))
occupant = M

View File

@@ -318,7 +318,8 @@ steam.start() -- spawns the effect
/obj/effect/effect/smoke/elemental/fire/affect(mob/living/L)
L.inflict_heat_damage(strength)
L.add_modifier(/datum/modifier/fire, 6 SECONDS) // Around 15 damage per stack.
L.adjust_fire_stacks(10)
L.ignite_mob()
/obj/effect/effect/smoke/elemental/frost
name = "freezing cloud"

View File

@@ -0,0 +1,73 @@
///objects can only have one particle on them at a time, so we use these abstract effects to hold and display the effects. You know, so multiple particle effects can exist at once.
///also because some objects do not display particles due to how their visuals are built
/obj/effect/abstract/particle_holder
name = "particle holder"
desc = "How are you reading this? Please make a bug report :)"
appearance_flags = KEEP_APART|KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE|RESET_COLOR //movable appearance_flags plus KEEP_APART and KEEP_TOGETHER
vis_flags = VIS_INHERIT_PLANE
layer = ABOVE_MOB_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
/// Holds info about how this particle emitter works
/// See \code\__DEFINES\particles.dm
var/particle_flags = NONE
var/atom/parent
/obj/effect/abstract/particle_holder/Initialize(mapload, particle_path = /particles/smoke, particle_flags = NONE)
. = ..()
if(!loc)
stack_trace("particle holder was created with no loc!")
return INITIALIZE_HINT_QDEL
if(loc.plane == TURF_PLANE)
vis_flags &= ~VIS_INHERIT_PLANE // don't yoink the floor plane. we'll just sit on game plane, it's fine
// We nullspace ourselves because some objects use their contents (e.g. storage) and some items may drop everything in their contents on deconstruct.
parent = loc
loc = null
// Mouse opacity can get set to opaque by some objects when placed into the object's contents (storage containers).
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
src.particle_flags = particle_flags
particles = new particle_path()
// /atom doesn't have vis_contents, /turf and /atom/movable do
var/atom/movable/lie_about_areas = parent
lie_about_areas.vis_contents += src
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(parent_deleted))
if(particle_flags & PARTICLE_ATTACH_MOB)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
on_move(parent, null, NORTH)
/obj/effect/abstract/particle_holder/Destroy(force)
QDEL_NULL(particles)
parent = null
return ..()
/// Non movables don't delete contents on destroy, so we gotta do this
/obj/effect/abstract/particle_holder/proc/parent_deleted(datum/source)
SIGNAL_HANDLER
qdel(src)
/// signal called when a parent that's been hooked into this moves
/// does a variety of checks to ensure overrides work out properly
/obj/effect/abstract/particle_holder/proc/on_move(atom/movable/attached, atom/oldloc, direction)
SIGNAL_HANDLER
if(!(particle_flags & PARTICLE_ATTACH_MOB))
return
//remove old
if(ismob(oldloc))
var/mob/particle_mob = oldloc
particle_mob.vis_contents -= src
// If we're sitting in a mob, we want to emit from it too, for vibes and shit
if(ismob(attached.loc))
var/mob/particle_mob = attached.loc
particle_mob.vis_contents += src
/// Sets the particles position to the passed coordinates
/obj/effect/abstract/particle_holder/proc/set_particle_position(x = 0, y = 0, z = 0)
particles.position = list(x, y, z)

View File

@@ -0,0 +1,56 @@
// Fire related particles.
/particles/bonfire
icon = 'icons/effects/particles/bonfire.dmi'
icon_state = "bonfire"
width = 100
height = 100
count = 1000
spawning = 4
lifespan = 0.7 SECONDS
fade = 1 SECONDS
grow = -0.01
velocity = list(0, 0)
position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND)
drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2))
gravity = list(0, 0.95)
scale = generator(GEN_VECTOR, list(0.3, 0.3), list(1,1), NORMAL_RAND)
rotation = 30
spin = generator(GEN_NUM, -20, 20)
/particles/embers
icon = 'icons/effects/particles/generic.dmi'
icon_state = list("dot" = 4,"cross" = 1,"curl" = 1)
width = 64
height = 96
count = 500
spawning = 5
lifespan = 3 SECONDS
fade = 1 SECONDS
color = 0
color_change = 0.05
gradient = list("#FBAF4D", "#FCE6B6", "#FD481C")
position = generator(GEN_BOX, list(-12,-16,0), list(12,16,0), NORMAL_RAND)
drift = generator(GEN_VECTOR, list(-0.1,0), list(0.1,0.025), UNIFORM_RAND)
spin = generator(GEN_NUM, list(-15,15), NORMAL_RAND)
scale = generator(GEN_VECTOR, list(0.5,0.5), list(2,2), NORMAL_RAND)
/particles/embers/minor
spawning = 1
/particles/embers/spark
count = 3
spawning = 2
gradient = list("#FBAF4D", "#FCE6B6", "#FFFFFF")
lifespan = 1.5 SECONDS
fade = 1 SECONDS
fadein = 0.1 SECONDS
grow = -0.1
velocity = generator(GEN_CIRCLE, 3, 3, SQUARE_RAND)
position = generator(GEN_SPHERE, 0, 0, LINEAR_RAND)
scale = generator(GEN_VECTOR, list(0.5, 0.5), list(1,1), NORMAL_RAND)
drift = list(0)
/particles/embers/spark/severe
count = 10
spawning = 5
gradient = list("#FCE6B6", "#FFFFFF")

View File

@@ -0,0 +1,129 @@
// All the smoke variant particles.
/particles/smoke
icon = 'icons/effects/particles/smoke.dmi'
icon_state = list("smoke_1" = 1, "smoke_2" = 1, "smoke_3" = 2)
width = 100
height = 100
count = 1000
spawning = 4
lifespan = 1.5 SECONDS
fade = 1 SECONDS
velocity = list(0, 0.4, 0)
position = list(6, 0, 0)
drift = generator(GEN_SPHERE, 0, 2, NORMAL_RAND)
friction = 0.2
gravity = list(0, 0.95)
grow = 0.05
/particles/smoke/burning
position = list(0, 0, 0)
/particles/smoke/burning/small
spawning = 1
scale = list(0.8, 0.8)
velocity = list(0, 0.4, 0)
/particles/smoke/steam
icon_state = list("steam_1" = 1, "steam_2" = 1, "steam_3" = 2)
fade = 1.5 SECONDS
/particles/smoke/steam/mild
spawning = 1
velocity = list(0, 0.3, 0)
friction = 0.25
/particles/smoke/steam/bad
icon_state = list("steam_1" = 1, "smoke_1" = 1, "smoke_2" = 1, "smoke_3" = 1)
spawning = 2
velocity = list(0, 0.25, 0)
/particles/smoke/steam/mald
icon_state = list("steam_1" = 1, "steam_2" = 1, "steam_3" = 2)
velocity = list(0, 0.25, 0)
lifespan = 1 SECONDS
fade = 0.5 SECONDS
position = list(-1, 12, 0)
/particles/smoke/cig
icon_state = list("steam_1" = 2, "steam_2" = 1, "steam_3" = 1)
count = 1
spawning = 0.05 // used to pace it out roughly in time with breath ticks
position = list(-6, -2, 0)
gravity = list(0, 0.75, 0)
lifespan = 0.75 SECONDS
fade = 0.75 SECONDS
velocity = list(0, 0.2, 0)
scale = 0.5
grow = 0.01
friction = 0.5
color = "#d0d0d09d"
/particles/smoke/cig/big
icon_state = list("steam_1" = 1, "steam_2" = 2, "steam_3" = 2)
gravity = list(0, 0.5, 0)
velocity = list(0, 0.1, 0)
lifespan = 1 SECONDS
fade = 1 SECONDS
grow = 0.1
scale = 0.75
spawning = 1
friction = 0.75
/particles/smoke/ash
icon_state = list("ash_1" = 2, "ash_2" = 2, "ash_3" = 1, "smoke_1" = 3, "smoke_2" = 2)
count = 500
spawning = 1
lifespan = 1 SECONDS
fade = 0.2 SECONDS
fadein = 0.7 SECONDS
position = generator(GEN_VECTOR, list(-3, 5, 0), list(3, 6.5, 0), NORMAL_RAND)
velocity = generator(GEN_VECTOR, list(-0.1, 0.4, 0), list(0.1, 0.5, 0), NORMAL_RAND)
/particles/fog
icon = 'icons/effects/particles/smoke.dmi'
icon_state = list("chill_1" = 2, "chill_2" = 2, "chill_3" = 1)
/particles/fog/breath
count = 1
spawning = 1
lifespan = 1 SECONDS
fade = 0.5 SECONDS
grow = 0.05
spin = 2
color = "#fcffff77"
/particles/smoke/cyborg
count = 5
spawning = 1
lifespan = 1 SECONDS
fade = 1.8 SECONDS
position = list(0, 0, 0)
scale = list(0.5, 0.5)
grow = 0.1
/particles/smoke/cyborg/heavy_damage
lifespan = 0.8 SECONDS
fade = 0.8 SECONDS
/particles/hotspring_steam
icon = 'icons/effects/particles/smoke.dmi'
icon_state = list(
"steam_cloud_1" = 1,
"steam_cloud_2" = 1,
"steam_cloud_3" = 1,
"steam_cloud_4" = 1,
"steam_cloud_5" = 1,
)
color = "#FFFFFF8A"
count = 5
spawning = 0.3
lifespan = 3 SECONDS
fade = 1.2 SECONDS
fadein = 0.4 SECONDS
position = generator(GEN_BOX, list(-17,-15,0), list(24,15,0), NORMAL_RAND)
scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND)
drift = generator(GEN_SPHERE, list(-0.01,0), list(0.01,0.01), UNIFORM_RAND)
spin = generator(GEN_NUM, list(-2,2), NORMAL_RAND)
gravity = list(0.05, 0.28)
friction = 0.3
grow = 0.037

View File

@@ -0,0 +1,14 @@
// Water related particles.
/particles/droplets
icon = 'icons/effects/particles/generic.dmi'
icon_state = list("dot"=2,"drop"=1)
width = 32
height = 36
count = 5
spawning = 0.2
lifespan = 1 SECONDS
fade = 0.5 SECONDS
color = "#549EFF"
position = generator(GEN_BOX, list(-9,-9,0), list(9,18,0), NORMAL_RAND)
scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND)
gravity = list(0, -0.9)

View File

@@ -42,7 +42,7 @@
Target.hotspot_expose(1000, 50, 1)
for(var/mob/living/L in Target)
L.fire_stacks += 2
L.add_modifier(/datum/modifier/fire/stack_managed/intense, 30 SECONDS)
L.adjust_fire_stacks(2)
L.ignite_mob()
return TRUE

View File

@@ -0,0 +1,100 @@
#define SHARED_PARTICLE_HOLDER_INDEX 1
#define SHARED_PARTICLE_USER_NUM_INDEX 2
// Assoc list of particle type/key -> list(list of particle holders, number of particle users)
GLOBAL_LIST_EMPTY(shared_particles)
//A more abstract version of particle holder not bound to a specific object
/obj/effect/abstract/shared_particle_holder
name = "shared particle holder"
desc = "How are you reading this? Please make a bug report :)"
appearance_flags = KEEP_APART|KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE|RESET_COLOR
vis_flags = VIS_INHERIT_PLANE
layer = ABOVE_MOB_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
/// Holds info about how this particle emitter works
/// See \code\__DEFINES\particles.dm
var/particle_flags = NONE
/obj/effect/abstract/shared_particle_holder/Initialize(mapload, particle_path = /particles/smoke, particle_flags = NONE)
. = ..()
// Shouldn't exist outside of nullspace
loc = null
src.particle_flags = particle_flags
particles = new particle_path()
/obj/effect/abstract/shared_particle_holder/Destroy(force)
QDEL_NULL(particles)
return ..()
/* Adds (or creates and adds) a shared particle holder
* Shared particle holders are held in nullspace and added to vis_contents of all atoms using it
* in order to save clientside performance by making clients only render 3-5 particle holders
* for 400 objects using them. This should be prioritized over normal particles when possible if it is known
* that there will be a lot of objects using certain particles.
* custom_key can be used to create a new pool of already existing particle type in case you're planning to edit holder's color or properties
* pool_size controls how many particle holders per type are created. Any objects over this cap will pick an existing holder from the pool.
*
* Now, this code seems fucked up, that's because this is meant to support both objects (and mobs) and turfs, *however* areas are special
* and don't have vis_contents, so to avoid copypaste code we do this weirdness
*/
/atom/proc/add_shared_particles(particle_type, custom_key = null, particle_flags = NONE, pool_size = 3)
var/atom/movable/play_pretend = src
var/particle_key = custom_key || "[particle_type]"
if (!GLOB.shared_particles[particle_key])
GLOB.shared_particles[particle_key] = list(list(new /obj/effect/abstract/shared_particle_holder(null, particle_type, particle_flags)), 1)
play_pretend.vis_contents += GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX][1]
return GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX][1]
var/list/type_holders = GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX]
for (var/obj/effect/abstract/shared_particle_holder/particle_holder as anything in type_holders)
if (particle_holder in play_pretend.vis_contents)
return particle_holder
if (length(type_holders) < pool_size)
var/obj/effect/abstract/shared_particle_holder/new_holder = new(null, particle_type, particle_flags)
type_holders += new_holder
play_pretend.vis_contents += new_holder
GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] += 1
return new_holder
var/obj/effect/abstract/shared_particle_holder/particle_holder = pick(type_holders)
play_pretend.vis_contents += particle_holder
GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] += 1
return particle_holder
/area/add_shared_particles(particle_type, custom_key = null, particle_flags = NONE, pool_size = 3)
CRASH("add_shared_particles was called on an area [src] ([type]) trying to add [particle_type]! Only turfs and movables support shared particles.")
/* Removes shared particles from object's vis_contents and disposes of it if nothing uses that type/key of particle
* particle_key can be either a type (if no custom_key was passed) or said custom_key
*/
/atom/proc/remove_shared_particles(particle_key, delete_on_empty = TRUE)
if (!particle_key)
return
if (ispath(particle_key))
particle_key = "[particle_key]"
if (!GLOB.shared_particles[particle_key])
return
var/atom/movable/play_pretend = src
var/list/type_holders = GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX]
for (var/obj/effect/abstract/shared_particle_holder/particle_holder as anything in type_holders)
if (!(particle_holder in play_pretend.vis_contents))
continue
play_pretend.vis_contents -= particle_holder
GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] -= 1
if (delete_on_empty && GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] <= 0)
QDEL_LIST(type_holders)
GLOB.shared_particles -= particle_key
return
/area/remove_shared_particles(particle_key, delete_on_empty = TRUE)
CRASH("remove_shared_particles was called on an area [src] ([type]) trying to add [particle_key]! Only turfs and movables support shared particles.")
#undef SHARED_PARTICLE_HOLDER_INDEX
#undef SHARED_PARTICLE_USER_NUM_INDEX

View File

@@ -38,7 +38,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
/obj/item/flame/match/process()
if(isliving(loc))
var/mob/living/M = loc
M.IgniteMob()
M.ignite_mob()
var/turf/location = get_turf(src)
smoketime--
if(smoketime < 1)
@@ -650,7 +650,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
return
if(lit == 1)
M.IgniteMob()
M.ignite_mob()
add_attack_logs(user,M,"Lit on fire with [src]")
if(istype(M.wear_mask, /obj/item/clothing/mask/smokable/cigarette) && user.zone_sel.selecting == O_MOUTH && lit)
@@ -850,7 +850,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
return
if(lit == 1)
M.IgniteMob()
M.ignite_mob()
add_attack_logs(user,M,"Lit on fire with [src]")
if(istype(M.wear_mask, /obj/item/clothing/mask/smokable/cigarette) && user.zone_sel.selecting == O_MOUTH && lit)
@@ -921,7 +921,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
return
if(lit == 1)
M.IgniteMob()
M.ignite_mob()
add_attack_logs(user,M,"Lit on fire with [src]")
if(istype(M.wear_mask, /obj/item/clothing/mask/smokable/cigarette) && user.zone_sel.selecting == O_MOUTH && lit)
@@ -1087,7 +1087,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
return
if (lit == 1)
M.IgniteMob()
M.ignite_mob()
add_attack_logs(user, M, "Lit on fire with [src]")
if (istype(M.wear_mask, /obj/item/clothing/mask/smokable/cigarette) && user.zone_sel.selecting == O_MOUTH && lit)

View File

@@ -166,7 +166,7 @@
var/turf/location = get_turf(user)
if(isliving(O))
var/mob/living/L = O
L.IgniteMob()
L.ignite_mob()
if (istype(location, /turf))
location.hotspot_expose(700, 50, 1)
/obj/item/weldingtool/attack_self(mob/user)

View File

@@ -25,9 +25,7 @@
user.visible_message(span_notice("[user] uses [src] to towel themselves off."))
playsound(src, 'sound/weapons/towelwipe.ogg', 25, 1)
if(user.fire_stacks > 0)
user.fire_stacks = (max(0, user.fire_stacks - 1.5))
else if(user.fire_stacks < 0)
user.fire_stacks = (min(0, user.fire_stacks + 1.5))
user.adjust_fire_stacks(-1.5)
/obj/item/towel/random/Initialize(mapload)
. = ..()

View File

@@ -166,7 +166,8 @@
return TRUE
/obj/structure/bonfire/proc/extinguish()
/obj/structure/bonfire/extinguish()
. = ..()
if(burning)
burning = FALSE
update_icon()
@@ -193,7 +194,7 @@
var/mob/living/L = A
if(!(L.is_incorporeal()))
L.adjust_fire_stacks(get_fuel_amount() / 4)
L.IgniteMob()
L.ignite_mob()
/obj/structure/bonfire/update_icon()
cut_overlays()
@@ -360,7 +361,8 @@
return FALSE
return TRUE
/obj/structure/fireplace/proc/extinguish()
/obj/structure/fireplace/extinguish()
. = ..()
if(burning)
burning = FALSE
update_icon()

View File

@@ -253,8 +253,8 @@
if(isliving(O))
var/mob/living/L = O
L.ExtinguishMob()
L.fire_stacks = -20 //Douse ourselves with water to avoid fire more easily
L.extinguish_mob()
L.adjust_fire_stacks(-20) //Douse ourselves with water to avoid fire more easily
if(iscarbon(O))
//flush away reagents on the skin

View File

@@ -184,10 +184,10 @@
return
/mob/living/water_act(amount)
adjust_fire_stacks(-amount * 5)
// adjust_fire_stacks(-amount * 5)
adjust_wet_stacks(amount * 5)
for(var/atom/movable/AM in contents)
AM.water_act(amount)
remove_modifiers_of_type(/datum/modifier/fire)
inflict_water_damage(20 * amount) // Only things vulnerable to water will actually be harmed (slimes/prommies).
var/list/shoreline_icon_cache = list()