Port /datum/status_effect and convert wetness and fire stacks to it (#18180)

* Port /datum/status_effect system

* Port fire stacks to status_effects

* Fixes and adjustments to wetness

* One last little thing

* Fixes these compile errors

A few things on the backend got updated...Adjusts them here.

---------

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
ShadowLarkens
2025-09-17 01:50:37 -04:00
committed by GitHub
co-authored by Kashargul Cameron Lennox
parent bd8893cbdb
commit a1322afa05
80 changed files with 1748 additions and 237 deletions
@@ -64,12 +64,12 @@ Bonus
to_chat(M, span_warning(pick("You feel hot.", "You hear a crackling noise.", "You smell smoke.")))
if(4)
Firestacks_stage_4(M, A)
M.IgniteMob()
M.ignite_mob()
to_chat(M, span_userdanger("Your skin bursts into flames!"))
M.emote("scream")
if(5)
Firestacks_stage_5(M, A)
M.IgniteMob()
M.ignite_mob()
if(M.stat != DEAD)
to_chat(M, span_userdanger("Your skin erupts into an inferno!"))
M.emote("scream")
@@ -0,0 +1,254 @@
/// Status effects are used to apply temporary or permanent effects to mobs.
/// This file contains their code, plus code for applying and removing them.
/datum/status_effect
/// The ID of the effect. ID is used in adding and removing effects to check for duplicates, among other things.
var/id = "effect"
/// When set initially / in on_creation, this is how long the status effect lasts in deciseconds.
/// While processing, this becomes the world.time when the status effect will expire.
/// -1 = infinite duration.
var/duration = STATUS_EFFECT_PERMANENT
/// When set initially / in on_creation, this is how long between [proc/tick] calls in deciseconds.
/// Note that this cannot be faster than the processing subsystem you choose to fire the effect on. (See: [var/processing_speed])
/// While processing, this becomes the world.time when the next tick will occur.
/// -1 = will prevent ticks, and if duration is also unlimited (-1), stop processing wholesale.
var/tick_interval = 1 SECONDS
///If our tick intervals are set to be a dynamic value within a range, the lowerbound of said range
var/tick_interval_lowerbound
///If our tick intervals are set to be a dynamic value within a range, the upperbound of said range
var/tick_interval_upperbound
/// The mob affected by the status effect.
VAR_FINAL/mob/living/owner
/// How many of the effect can be on one mob, and/or what happens when you try to add a duplicate.
var/status_type = STATUS_EFFECT_UNIQUE
/// If TRUE, we call [proc/on_remove] when owner is deleted. Otherwise, we call [proc/be_replaced].
var/on_remove_on_mob_delete = FALSE
/// The typepath to the alert thrown by the status effect when created.
/// Status effect "name"s and "description"s are shown to the owner here.
var/alert_type = /obj/screen/alert/status_effect
/// The alert itself, created in [proc/on_creation] (if alert_type is specified).
VAR_FINAL/obj/screen/alert/status_effect/linked_alert
/// If TRUE, and we have an alert, we will show a duration on the alert
var/show_duration = FALSE
/// Used to define if the status effect should be using SSfastprocess or SSprocessing
var/processing_speed = STATUS_EFFECT_FAST_PROCESS
/// Do we self-terminate when a fullheal is called?
var/remove_on_fullheal = FALSE
/// If remove_on_fullheal is TRUE, what flag do we need to be removed?
// var/heal_flag_necessary = HEAL_STATUS
/// A particle effect, for things like embers - Should be set on update_particles()
VAR_FINAL/obj/effect/abstract/particle_holder/particle_effect
/datum/status_effect/New(list/arguments)
on_creation(arglist(arguments))
/// Called from New() with any supplied status effect arguments.
/// Not guaranteed to exist by the end.
/// Returning FALSE from on_apply will stop on_creation and self-delete the effect.
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner)
owner = new_owner
if(QDELETED(owner) || !on_apply())
qdel(src)
return
if(owner)
LAZYADD(owner.status_effects, src)
RegisterSignal(owner, COMSIG_LIVING_AHEAL, PROC_REF(remove_effect_on_heal))
if(duration == INFINITY)
// we will optionally allow INFINITY, because i imagine it'll be convenient in some places,
// but we'll still set it to -1 / STATUS_EFFECT_PERMANENT for proper unified handling
duration = STATUS_EFFECT_PERMANENT
if(duration != STATUS_EFFECT_PERMANENT)
duration = world.time + duration
if(tick_interval != STATUS_EFFECT_NO_TICK)
tick_interval = world.time + tick_interval
if(alert_type)
var/obj/screen/alert/status_effect/new_alert = owner.throw_alert(id, alert_type)
new_alert.attached_effect = src //so the alert can reference us, if it needs to
linked_alert = new_alert //so we can reference the alert, if we need to
update_shown_duration()
if(duration > world.time || tick_interval > world.time) //don't process if we don't care
switch(processing_speed)
if(STATUS_EFFECT_FAST_PROCESS)
START_PROCESSING(SSfastprocess, src)
if(STATUS_EFFECT_NORMAL_PROCESS)
START_PROCESSING(SSprocessing, src)
if(STATUS_EFFECT_PRIORITY)
START_PROCESSING(SSpriority_effects, src)
update_particles()
SEND_SIGNAL(owner, COMSIG_LIVING_STATUS_APPLIED, src)
return TRUE
/datum/status_effect/Destroy()
switch(processing_speed)
if(STATUS_EFFECT_FAST_PROCESS)
STOP_PROCESSING(SSfastprocess, src)
if(STATUS_EFFECT_NORMAL_PROCESS)
STOP_PROCESSING(SSprocessing, src)
if(STATUS_EFFECT_PRIORITY)
STOP_PROCESSING(SSpriority_effects, src)
if(owner)
linked_alert = null
owner.clear_alert(id)
LAZYREMOVE(owner.status_effects, src)
on_remove()
UnregisterSignal(owner, COMSIG_LIVING_AHEAL)
SEND_SIGNAL(owner, COMSIG_LIVING_STATUS_REMOVED, src)
owner = null
if(particle_effect)
QDEL_NULL(particle_effect)
return ..()
/// Updates the status effect alert's maptext (if possible)
/datum/status_effect/proc/update_shown_duration()
PRIVATE_PROC(TRUE)
if(!linked_alert || !show_duration)
return
linked_alert.maptext = MAPTEXT("<span style='text-align:center'>[round((duration - world.time)/10, 1)]s</span>")
// Status effect process. Handles adjusting its duration and ticks.
// If you're adding processed effects, put them in [proc/tick]
// instead of extending / overriding the process() proc.
/datum/status_effect/process(seconds_per_tick)
SHOULD_NOT_OVERRIDE(TRUE)
if(QDELETED(owner))
qdel(src)
return
if(tick_interval == STATUS_EFFECT_AUTO_TICK)
tick(seconds_per_tick)
else if(tick_interval != STATUS_EFFECT_NO_TICK && tick_interval < world.time)
var/tick_length = (tick_interval_upperbound && tick_interval_lowerbound) ? rand(tick_interval_lowerbound, tick_interval_upperbound) : initial(tick_interval)
tick(tick_length / (1 SECONDS))
tick_interval = world.time + tick_length
if(QDELING(src))
// tick deleted us, no need to continue
return
if(duration != STATUS_EFFECT_PERMANENT)
if(duration < world.time)
qdel(src)
return
update_shown_duration()
/// Called whenever the effect is applied in on_created
/// Returning FALSE will cause it to delete itself during creation instead.
/datum/status_effect/proc/on_apply()
return TRUE
/// Gets and formats examine text associated with our status effect.
/// Return 'null' to have no examine text appear (default behavior).
/datum/status_effect/proc/get_examine_text()
return null
/**
* Called every tick from process().
* This is only called of tick_interval is not -1.
*
* Note that every tick =/= every processing cycle.
*
* * seconds_between_ticks = This is how many SECONDS that elapse between ticks.
* This is a constant value based upon the initial tick interval set on the status effect.
* It is similar to seconds_per_tick, from processing itself, but adjusted to the status effect's tick interval.
*/
/datum/status_effect/proc/tick(seconds_between_ticks)
return
/// Called whenever the buff expires or is removed (qdeleted)
/// Note that at the point this is called, it is out of the
/// owner's status_effects list, but owner is not yet null
/datum/status_effect/proc/on_remove()
return
/// Called instead of on_remove when a status effect
/// of status_type STATUS_EFFECT_REPLACE is replaced by itself,
/// or when a status effect with on_remove_on_mob_delete
/// set to FALSE has its mob deleted
/datum/status_effect/proc/be_replaced()
linked_alert = null
owner.clear_alert(id)
LAZYREMOVE(owner.status_effects, src)
owner = null
qdel(src)
/// Called before being fully removed (before on_remove)
/// Returning FALSE will cancel removal
/datum/status_effect/proc/before_remove()
return TRUE
/// Called when a status effect of status_type STATUS_EFFECT_REFRESH
/// has its duration refreshed in apply_status_effect - is passed New() args
/datum/status_effect/proc/refresh(effect, ...)
var/original_duration = initial(duration)
if(original_duration == STATUS_EFFECT_PERMANENT)
return
duration = world.time + original_duration
/// Adds nextmove modifier multiplicatively to the owner while applied
/datum/status_effect/proc/nextmove_modifier()
return 1
/// Adds nextmove adjustment additiviely to the owner while applied
/datum/status_effect/proc/nextmove_adjust()
return 0
/// Signal proc for [COMSIG_LIVING_POST_FULLY_HEAL] to remove us on fullheal
/datum/status_effect/proc/remove_effect_on_heal(datum/source) //, heal_flags)
SIGNAL_HANDLER
if(!remove_on_fullheal)
return
// if(!heal_flag_necessary || (heal_flags & heal_flag_necessary))
qdel(src)
/// Remove [seconds] of duration from the status effect, qdeling / ending if we eclipse the current world time.
/datum/status_effect/proc/remove_duration(seconds)
if(duration == STATUS_EFFECT_PERMANENT) // Infinite duration
return FALSE
duration -= seconds
if(duration <= world.time)
qdel(src)
return TRUE
update_shown_duration()
return FALSE
/**
* Updates the particles for the status effects
* Should be handled by subtypes!
*/
/datum/status_effect/proc/update_particles()
SHOULD_CALL_PARENT(FALSE)
return
/datum/status_effect/vv_edit_var(var_name, var_value)
. = ..()
if(!.)
return
if(var_name == NAMEOF(src, duration))
if(var_value == INFINITY)
duration = STATUS_EFFECT_PERMANENT
update_shown_duration()
if(var_name == NAMEOF(src, show_duration))
update_shown_duration()
/// Alert base type for status effect alerts
/obj/screen/alert/status_effect
name = "Curse of Mundanity"
desc = "You don't feel any different..."
// maptext_y = 2
/// The status effect we're linked to
var/datum/status_effect/attached_effect
/obj/screen/alert/status_effect/Destroy()
attached_effect = null //Don't keep a ref now
return ..()
@@ -0,0 +1,147 @@
// Status effect helpers for living mobs
/**
* Applies a given status effect to this mob.
*
* new_effect - TYPEPATH of a status effect to apply.
* Additional status effect arguments can be passed.
*
* Returns the instance of the created effected, if successful.
* Returns 'null' if unsuccessful.
*/
/mob/living/proc/apply_status_effect(datum/status_effect/new_effect, ...)
RETURN_TYPE(/datum/status_effect)
// The arguments we pass to the start effect. The 1st argument is this mob.
var/list/arguments = args.Copy()
arguments[1] = src
// If the status effect we're applying doesn't allow multiple effects, we need to handle it
if(initial(new_effect.status_type) != STATUS_EFFECT_MULTIPLE)
for(var/datum/status_effect/existing_effect as anything in status_effects)
if(existing_effect.id != initial(new_effect.id))
continue
switch(existing_effect.status_type)
// Multiple are allowed, continue as normal. (Not normally reachable)
if(STATUS_EFFECT_MULTIPLE)
break
// Only one is allowed of this type - early return
if(STATUS_EFFECT_UNIQUE)
return
// Replace the existing instance (deletes it).
if(STATUS_EFFECT_REPLACE)
existing_effect.be_replaced()
// Refresh the existing type, then early return
if(STATUS_EFFECT_REFRESH)
existing_effect.refresh(arglist(arguments))
return
// Create the status effect with our mob + our arguments
var/datum/status_effect/new_instance = new new_effect(arguments)
if(!QDELETED(new_instance))
return new_instance
/**
* Removes all instances of a given status effect from this mob
*
* removed_effect - TYPEPATH of a status effect to remove.
* Additional status effect arguments can be passed - these are passed into before_remove.
*
* Returns TRUE if at least one was removed.
*/
/mob/living/proc/remove_status_effect(datum/status_effect/removed_effect, ...)
var/list/arguments = args.Copy(2)
. = FALSE
for(var/datum/status_effect/existing_effect as anything in status_effects)
if(existing_effect.id == initial(removed_effect.id) && existing_effect.before_remove(arglist(arguments)))
qdel(existing_effect)
. = TRUE
return .
/**
* Checks if this mob has a status effect that shares the passed effect's ID
*
* checked_effect - TYPEPATH of a status effect to check for. Checks for its ID, not its typepath
*
* Returns an instance of a status effect, or NULL if none were found.
*/
/mob/proc/has_status_effect(datum/status_effect/checked_effect)
// Yes I'm being cringe and putting this on the mob level even though status effects only apply to the living level
// There's quite a few places (namely examine and, bleh, cult code) where it's easier to not need to cast to living before checking
// for an effect such as blindness
return null
/mob/living/has_status_effect(datum/status_effect/checked_effect)
RETURN_TYPE(/datum/status_effect)
for(var/datum/status_effect/present_effect as anything in status_effects)
if(present_effect.id == initial(checked_effect.id))
return present_effect
return null
///Gets every status effect of an ID and returns all of them in a list, rather than the individual 'has_status_effect'
/mob/living/proc/get_all_status_effect_of_id(datum/status_effect/checked_effect)
RETURN_TYPE(/list/datum/status_effect)
var/list/all_effects_of_type = list()
for(var/datum/status_effect/present_effect as anything in status_effects)
if(present_effect.id == initial(checked_effect.id))
all_effects_of_type += present_effect
return all_effects_of_type
/**
* Checks if this mob has a status effect that shares the passed effect's ID
* and has the passed sources are in its list of sources (ONLY works for grouped efects!)
*
* checked_effect - TYPEPATH of a status effect to check for. Checks for its ID, not its typepath
*
* Returns an instance of a status effect, or NULL if none were found.
*/
/mob/proc/has_status_effect_from_source(datum/status_effect/grouped/checked_effect, sources)
// See [/mob/proc/has_status_effect] for reason behind having this on the mob level
return null
/mob/living/has_status_effect_from_source(datum/status_effect/grouped/checked_effect, sources)
RETURN_TYPE(/datum/status_effect)
if(!ispath(checked_effect))
CRASH("has_status_effect_from_source passed with an improper status effect path.")
if(!islist(sources))
sources = list(sources)
for(var/datum/status_effect/grouped/present_effect in status_effects)
if(present_effect.id != initial(checked_effect.id))
continue
var/list/matching_sources = present_effect.sources & sources
if(length(matching_sources))
return present_effect
return null
/**
* Returns a list of all status effects that share the passed effect type's ID
*
* checked_effect - TYPEPATH of a status effect to check for. Checks for its ID, not its typepath
*
* Returns a list
*/
/mob/proc/has_status_effect_list(datum/status_effect/checked_effect)
// See [/mob/proc/has_status_effect] for reason behind having this on the mob level
return null
/mob/living/has_status_effect_list(datum/status_effect/checked_effect)
RETURN_TYPE(/list)
var/list/effects_found = list()
for(var/datum/status_effect/present_effect as anything in status_effects)
if(present_effect.id == initial(checked_effect.id))
effects_found += present_effect
return effects_found
@@ -0,0 +1,388 @@
/////////// BUBBER EDIT THIS WILL BE FORCED TO CONFLICT READ THIS
/*
RESET THIS FILE BACK TO TG ONCE YOU GET FISH INFUSION
RESET THIS FILE BACK TO TG ONCE YOU GET FISH INFUSION
RESET THIS FILE BACK TO TG ONCE YOU GET FISH INFUSION
RESET THIS FILE BACK TO TG ONCE YOU GET FISH INFUSION
*/
/datum/status_effect/fire_handler
duration = STATUS_EFFECT_PERMANENT
id = STATUS_EFFECT_ID_ABSTRACT
alert_type = null
status_type = STATUS_EFFECT_REFRESH //Custom code
on_remove_on_mob_delete = TRUE
tick_interval = 2 SECONDS
processing_speed = STATUS_EFFECT_PRIORITY
/// Current amount of stacks we have
var/stacks
/// Maximum of stacks that we could possibly get
var/stack_limit = MAX_FIRE_STACKS
/// What status effect types do we remove uppon being applied. These are just deleted without any deduction from our or their stacks when forced.
var/list/enemy_types
/// What status effect types do we merge into if they exist. Ignored when forced.
var/list/merge_types
/// What status effect types do we override if they exist. These are simply deleted when forced.
var/list/override_types
/// For how much firestacks does one our stack count
var/stack_modifier = 1
/datum/status_effect/fire_handler/refresh(mob/living/new_owner, new_stacks, forced = FALSE)
if(forced)
set_stacks(new_stacks)
else
adjust_stacks(new_stacks)
/datum/status_effect/fire_handler/on_creation(mob/living/new_owner, new_stacks, forced = FALSE)
. = ..()
if(isanimal(owner))
qdel(src)
return
// if(isbasicmob(owner))
// if(!check_basic_mob_immunity(owner))
// qdel(src)
// return
owner = new_owner
set_stacks(new_stacks)
for(var/enemy_type in enemy_types)
var/datum/status_effect/fire_handler/enemy_effect = owner.has_status_effect(enemy_type)
if(enemy_effect)
if(forced)
qdel(enemy_effect)
continue
var/cur_stacks = stacks
adjust_stacks(-abs(enemy_effect.stacks * enemy_effect.stack_modifier / stack_modifier))
enemy_effect.adjust_stacks(-abs(cur_stacks * stack_modifier / enemy_effect.stack_modifier))
if(enemy_effect.stacks <= 0)
qdel(enemy_effect)
if(stacks <= 0)
qdel(src)
return
if(!forced)
var/list/merge_effects = list()
for(var/merge_type in merge_types)
var/datum/status_effect/fire_handler/merge_effect = owner.has_status_effect(merge_type)
if(merge_effect)
merge_effects += merge_effects
if(LAZYLEN(merge_effects))
for(var/datum/status_effect/fire_handler/merge_effect in merge_effects)
merge_effect.adjust_stacks(stacks * stack_modifier / merge_effect.stack_modifier / LAZYLEN(merge_effects))
qdel(src)
return
for(var/override_type in override_types)
var/datum/status_effect/fire_handler/override_effect = owner.has_status_effect(override_type)
if(override_effect)
if(forced)
qdel(override_effect)
continue
adjust_stacks(override_effect.stacks)
qdel(override_effect)
/**
* Setter and adjuster procs for firestacks
*
* Arguments:
* - new_stacks
*
*/
/datum/status_effect/fire_handler/proc/set_stacks(new_stacks)
stacks = max(0, min(stack_limit, new_stacks))
cache_stacks()
/datum/status_effect/fire_handler/proc/adjust_stacks(new_stacks)
stacks = max(0, min(stack_limit, stacks + new_stacks))
cache_stacks()
/// Checks if the applicable basic mob is immune to the status effect we're trying to apply. Returns TRUE if it is, FALSE if it isn't.
// /datum/status_effect/fire_handler/proc/check_basic_mob_immunity(mob/living/basic/basic_owner)
// return (basic_owner.basic_mob_flags & FLAMMABLE_MOB)
/**
* Refresher for mob's fire_stacks
*/
/datum/status_effect/fire_handler/proc/cache_stacks()
owner.fire_stacks = 0
var/was_on_fire = owner.on_fire
owner.on_fire = FALSE
for(var/datum/status_effect/fire_handler/possible_fire in owner.status_effects)
owner.fire_stacks += possible_fire.stacks * possible_fire.stack_modifier
if(!istype(possible_fire, /datum/status_effect/fire_handler/fire_stacks))
continue
var/datum/status_effect/fire_handler/fire_stacks/our_fire = possible_fire
if(our_fire.on_fire)
owner.on_fire = TRUE
if(was_on_fire && !owner.on_fire)
owner.clear_alert(ALERT_FIRE)
else if(!was_on_fire && owner.on_fire)
owner.throw_alert(ALERT_FIRE, /obj/screen/alert/fire)
// owner.update_appearance(UPDATE_OVERLAYS)
owner.update_fire()
update_particles()
/datum/status_effect/fire_handler/fire_stacks
id = "fire_stacks" //fire_stacks and wet_stacks should have different IDs or else has_status_effect won't work
remove_on_fullheal = TRUE
enemy_types = list(/datum/status_effect/fire_handler/wet_stacks)
stack_modifier = 1
/// If we're on fire
var/on_fire = FALSE
/// Reference to the mob light emitter itself
var/obj/effect/dummy/lighting_obj/moblight
/// Type of mob light emitter we use when on fire
var/moblight_type = /obj/effect/dummy/lighting_obj/moblight/fire
/// Cached particle type
var/cached_state
/datum/status_effect/fire_handler/fire_stacks/get_examine_text()
if(owner.on_fire)
return
var/datum/gender/T = GLOB.gender_datums[owner.get_visible_gender()]
return "[T.He] [T.is] covered in something flammable."
// /datum/status_effect/fire_handler/fire_stacks/proc/owner_touched_sparks()
// SIGNAL_HANDLER
// ignite()
/datum/status_effect/fire_handler/fire_stacks/on_creation(mob/living/new_owner, new_stacks, forced = FALSE)
. = ..()
// RegisterSignal(owner, COMSIG_ATOM_TOUCHED_SPARKS, PROC_REF(owner_touched_sparks))
/datum/status_effect/fire_handler/fire_stacks/on_remove()
// UnregisterSignal(owner, COMSIG_ATOM_TOUCHED_SPARKS)
if (cached_state)
owner.remove_shared_particles(cached_state)
/datum/status_effect/fire_handler/fire_stacks/tick(seconds_between_ticks)
if(stacks <= 0)
qdel(src)
return TRUE
if(!on_fire)
return TRUE
var/decay_multiplier = 1 // HAS_TRAIT(owner, TRAIT_HUSK) ? 2 : 1 // husks decay twice as fast
adjust_stacks(owner.fire_stack_decay_rate * decay_multiplier * seconds_between_ticks)
if(stacks <= 0)
qdel(src)
return TRUE
var/datum/gas_mixture/air = owner.loc.return_air()
if(air.gas[GAS_O2] < 1)
qdel(src)
return TRUE
deal_damage(seconds_between_ticks)
/datum/status_effect/fire_handler/fire_stacks/update_particles()
if (!on_fire)
if (cached_state)
owner.remove_shared_particles(cached_state)
cached_state = null
return
var/particle_type = /particles/embers/minor
if(stacks > MOB_BIG_FIRE_STACK_THRESHOLD)
particle_type = /particles/embers
if (cached_state == particle_type)
return
if (cached_state)
owner.remove_shared_particles(cached_state)
owner.add_shared_particles(particle_type)
cached_state = particle_type
/**
* Proc that handles damage dealing and all special effects
*
* Arguments:
* - seconds_between_ticks
*
*/
/datum/status_effect/fire_handler/fire_stacks/proc/deal_damage(seconds_per_tick)
owner.on_fire_stack(seconds_per_tick, src)
var/turf/location = get_turf(owner)
location.hotspot_expose(700, 25 * seconds_per_tick, TRUE)
/**
* Used to deal damage to humans and count their protection.
*
* Arguments:
* - seconds_between_ticks
* - no_protection: When set to TRUE, fire will ignore any possible fire protection
*
*/
/datum/status_effect/fire_handler/fire_stacks/proc/harm_human(seconds_per_tick, no_protection = FALSE)
var/mob/living/carbon/human/victim = owner
var/thermal_protection = victim.get_heat_protection(stacks)
if(!no_protection)
if(thermal_protection == 1) // IMMUNE
return
var/fire_temp_add = (BODYTEMP_HEATING_MAX + (stacks + 15)) * (1 - thermal_protection)
victim.bodytemperature += fire_temp_add
// var/mob/living/carbon/human/victim = owner
// var/thermal_protection = victim.get_heat_protection(stacks)
// if(!no_protection)
// if(thermal_protection >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
// return
// if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT)
// victim.adjust_bodytemperature(5.5 * seconds_per_tick)
// return
// var/amount_to_heat = (BODYTEMP_HEATING_MAX + (stacks * 12)) * 0.5 * seconds_per_tick
// if(owner.bodytemperature > BODYTEMP_FIRE_TEMP_SOFTCAP)
// // Apply dimishing returns upon temp beyond the soft cap
// amount_to_heat = amount_to_heat ** (BODYTEMP_FIRE_TEMP_SOFTCAP / owner.bodytemperature)
// victim.adjust_bodytemperature(amount_to_heat)
// if (!(HAS_TRAIT(victim, TRAIT_RESISTHEAT)))
// victim.add_mood_event("on_fire", /datum/mood_event/on_fire)
// victim.add_mob_memory(/datum/memory/was_burning)
/**
* Handles mob ignition, should be the only way to set on_fire to TRUE
*
* Arguments:
* - silent: When set to TRUE, no message is displayed
*
*/
/datum/status_effect/fire_handler/fire_stacks/proc/ignite(silent = FALSE)
if(HAS_TRAIT(owner, TRAIT_NOFIRE))
return FALSE
on_fire = TRUE
if(!silent)
owner.visible_message(span_warning("[owner] catches fire!"), span_userdanger("You're set on fire!"))
if(moblight_type)
if(moblight)
qdel(moblight)
moblight = new moblight_type(owner)
cache_stacks()
SEND_SIGNAL(owner, COMSIG_LIVING_IGNITED, owner)
return TRUE
/**
* Handles mob extinguishing, should be the only way to set on_fire to FALSE
*/
/datum/status_effect/fire_handler/fire_stacks/proc/extinguish()
QDEL_NULL(moblight)
on_fire = FALSE
// owner.clear_mood_event("on_fire")
SEND_SIGNAL(owner, COMSIG_LIVING_EXTINGUISHED, owner)
cache_stacks()
for(var/obj/item/equipped in (owner.get_equipped_items()))
equipped.extinguish()
/datum/status_effect/fire_handler/fire_stacks/on_remove()
if(on_fire)
extinguish()
set_stacks(0)
owner.update_fire()
// UnregisterSignal(owner, COMSIG_MOB_UPDATE_ICONS)
// owner.update_appearance(UPDATE_OVERLAYS)
return ..()
/datum/status_effect/fire_handler/fire_stacks/on_apply()
. = ..()
RegisterSignal(owner, COMSIG_ATOM_EXTINGUISH, PROC_REF(extinguish))
owner.update_fire()
// add_fire_overlay(owner)
// owner.update_appearance(UPDATE_OVERLAYS)
/datum/status_effect/fire_handler/fire_stacks/proc/add_fire_overlay(mob/living/source)
SIGNAL_HANDLER
if(stacks <= 0 || !on_fire)
return
var/mutable_appearance/created_overlay = owner.get_fire_overlay(stacks, on_fire)
if(isnull(created_overlay))
return
source.overlays |= created_overlay
// WET
/datum/status_effect/fire_handler/wet_stacks
id = "wet_stacks"
enemy_types = list(/datum/status_effect/fire_handler/fire_stacks)
stack_modifier = -1
/// If the mob has the TRAIT_SLIPPERY_WHEN_WET trait, the mob gets this component while it's wet
var/datum/component/slippery/slipperiness
/datum/status_effect/fire_handler/wet_stacks/on_apply()
. = ..()
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_WET_FOR_LONGER), SIGNAL_REMOVETRAIT(TRAIT_WET_FOR_LONGER)), PROC_REF(update_wet_stack_modifier))
update_wet_stack_modifier()
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_SLIPPERY_WHEN_WET), PROC_REF(become_slippery))
RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_SLIPPERY_WHEN_WET), PROC_REF(no_longer_slippery))
if(HAS_TRAIT(owner, TRAIT_SLIPPERY_WHEN_WET))
become_slippery()
ADD_TRAIT(owner, TRAIT_IS_WET, TRAIT_STATUS_EFFECT(id))
owner.add_shared_particles(/particles/droplets)
/datum/status_effect/fire_handler/wet_stacks/on_remove()
. = ..()
REMOVE_TRAIT(owner, TRAIT_IS_WET, TRAIT_STATUS_EFFECT(id))
if(HAS_TRAIT(owner, TRAIT_SLIPPERY_WHEN_WET))
no_longer_slippery()
owner.remove_shared_particles(/particles/droplets)
/datum/status_effect/fire_handler/wet_stacks/proc/update_wet_stack_modifier()
SIGNAL_HANDLER
stack_modifier = HAS_TRAIT(owner, TRAIT_WET_FOR_LONGER) ? -3.5 : -1
/datum/status_effect/fire_handler/wet_stacks/proc/become_slippery()
SIGNAL_HANDLER
// slipperiness = owner.AddComponent(/datum/component/slippery, 5 SECONDS, lube_flags = SLIPPERY_WHEN_LYING_DOWN|NO_SLIP_WHEN_WALKING|WEAK_SLIDE)
ADD_TRAIT(owner, TRAIT_NO_SLIP_WATER, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/fire_handler/wet_stacks/proc/no_longer_slippery()
SIGNAL_HANDLER
// QDEL_NULL(slipperiness)
REMOVE_TRAIT(owner, TRAIT_NO_SLIP_WATER, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/fire_handler/wet_stacks/get_examine_text()
var/datum/gender/T = GLOB.gender_datums[owner.get_visible_gender()]
return "[T.He] look[T.s] a little soaked."
/datum/status_effect/fire_handler/wet_stacks/tick(seconds_between_ticks)
var/decay = HAS_TRAIT(owner, TRAIT_WET_FOR_LONGER) ? -0.035 : -0.5
adjust_stacks(decay * seconds_between_ticks)
if(stacks <= 0)
qdel(src)
// /datum/status_effect/fire_handler/wet_stacks/check_basic_mob_immunity(mob/living/basic/basic_owner)
// return !(basic_owner.basic_mob_flags & IMMUNE_TO_GETTING_WET)
/// BUBBER EDIT END
@@ -0,0 +1,47 @@
/// Status effect from multiple sources, when all sources are removed, so is the effect
/datum/status_effect/grouped
id = STATUS_EFFECT_ID_ABSTRACT
alert_type = null
// Grouped effects adds itself to [var/sources] and destroys itself if one exists already, there are never actually multiple
status_type = STATUS_EFFECT_MULTIPLE
/// A list of all sources applying this status effect. Sources are a list of keys
var/list/sources = list()
/datum/status_effect/grouped/on_creation(mob/living/new_owner, source, ...)
//Get our supplied arguments, without new_owner
var/list/new_source_args = args.Copy(2)
var/datum/status_effect/grouped/existing = new_owner.has_status_effect(type)
if(existing)
existing.sources |= source
existing.source_added(arglist(new_source_args))
qdel(src)
return FALSE
/* We are the original */
. = ..()
if(.)
sources |= source
source_added(arglist(new_source_args))
/**
* Called after a source is added to the status effect,
* this includes the first source added after creation.
*/
/datum/status_effect/grouped/proc/source_added(source, ...)
return
/**
* Called after a source is removed from the status effect. \
* `removing` will be TRUE if this is the last source, which means
* the effect will be deleted.
*/
/datum/status_effect/grouped/proc/source_removed(source, removing)
return
/datum/status_effect/grouped/before_remove(source)
sources -= source
var/was_last_source = !length(sources)
source_removed(source, was_last_source)
return was_last_source