diff --git a/code/__defines/alerts.dm b/code/__defines/alerts.dm new file mode 100644 index 0000000000..3ff75d197f --- /dev/null +++ b/code/__defines/alerts.dm @@ -0,0 +1 @@ +#define ALERT_FIRE "fire" diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index 78c0a86c81..206a9a416c 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -343,7 +343,7 @@ ///from base of mob/living/resist() (/mob/living) #define COMSIG_LIVING_RESIST "living_resist" -///from base of mob/living/IgniteMob() (/mob/living) +///from base of mob/living/ignite_mob() (/mob/living) #define COMSIG_LIVING_IGNITED "living_ignite" ///from base of mob/living/ExtinguishMob() (/mob/living) #define COMSIG_LIVING_EXTINGUISHED "living_extinguished" diff --git a/code/__defines/dcs/signals/signals_atom/signals_atom_main.dm b/code/__defines/dcs/signals/signals_atom/signals_atom_main.dm index 52a5559ec0..08ea5a00bb 100644 --- a/code/__defines/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__defines/dcs/signals/signals_atom/signals_atom_main.dm @@ -7,3 +7,6 @@ ///When the transform or an atom is varedited through vv topic. #define COMSIG_ATOM_VV_MODIFY_TRANSFORM "atom_vv_modify_transform" + +/// from base of [/atom/proc/extinguish] +#define COMSIG_ATOM_EXTINGUISH "atom_extinguish" diff --git a/code/__defines/dcs/signals/signals_mob/signals_mob_main.dm b/code/__defines/dcs/signals/signals_mob/signals_mob_main.dm index 7ca76e4d5f..16e7b5a20f 100644 --- a/code/__defines/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__defines/dcs/signals/signals_mob/signals_mob_main.dm @@ -10,3 +10,14 @@ /// from /mob/living/proc/apply_damage(): (damage, damagetype, def_zone, blocked, soaked, sharp, edge, /obj/used_weapon, projectile) /// works like above but after the damage is actually inflicted #define COMSIG_MOB_AFTER_APPLY_DAMAGE "mob_after_apply_damage" +///from end of revival_healing_action(): () +#define COMSIG_LIVING_AHEAL "living_post_aheal" + +/// From /datum/status_effect/proc/on_creation() : (datum/status_effect/effect) +#define COMSIG_LIVING_STATUS_APPLIED "living_status_applied" + +/// From /datum/status_effect/proc/Destroy() : (datum/status_effect/effect) +#define COMSIG_LIVING_STATUS_REMOVED "living_status_removed" + +///from /datum/species/handle_fire. Called when the human is set on fire and burning clothes and stuff +#define COMSIG_HUMAN_BURNING "human_burning" diff --git a/code/__defines/particles.dm b/code/__defines/particles.dm new file mode 100644 index 0000000000..5657566a63 --- /dev/null +++ b/code/__defines/particles.dm @@ -0,0 +1,5 @@ +// /obj/effect/abstract/particle_holder/var/particle_flags +// Flags that effect how a particle holder displays something + +/// If we're inside something inside a mob, display off that mob too +#define PARTICLE_ATTACH_MOB (1<<0) diff --git a/code/__defines/status_effects.dm b/code/__defines/status_effects.dm new file mode 100644 index 0000000000..903a67443e --- /dev/null +++ b/code/__defines/status_effects.dm @@ -0,0 +1,202 @@ +///if it allows multiple instances of the effect +#define STATUS_EFFECT_MULTIPLE 0 +///if it allows only one, preventing new instances +#define STATUS_EFFECT_UNIQUE 1 +///if it allows only one, but new instances replace +#define STATUS_EFFECT_REPLACE 2 +/// if it only allows one, and new instances just instead refresh the timer +#define STATUS_EFFECT_REFRESH 3 + +/// Use in status effect "duration" to make it last forever +#define STATUS_EFFECT_PERMANENT -1 +/// Use in status effect "tick_interval" to prevent it from calling tick() +#define STATUS_EFFECT_NO_TICK -1 +/// Use in status effect "tick_interval" to guarantee that tick() gets called on every process() +#define STATUS_EFFECT_AUTO_TICK 0 + +/// Indicates this status effect is an abstract type, ie not instantiated +/// Doesn't actually do anything in practice, primarily just a marker / used in unit tests, +/// so don't worry if your abstract status effect doesn't actually set this +#define STATUS_EFFECT_ID_ABSTRACT "abstract" + +///Processing flags - used to define the speed at which the status will work +/// This is fast - 0.2s between ticks (I believe!) +#define STATUS_EFFECT_FAST_PROCESS 0 +/// This is slower and better for more intensive status effects - 1s between ticks +#define STATUS_EFFECT_NORMAL_PROCESS 1 +/// Similar speed to STATUS_EFFECT_FAST_PROCESS, but uses a high priority subsystem (SSpriority_effects) +#define STATUS_EFFECT_PRIORITY 2 + +//several flags for the Necropolis curse status effect +///makes the edges of the target's screen obscured +// #define CURSE_BLINDING (1<<0) +///causes gradual damage +// #define CURSE_WASTING (1<<1) +///hands reach out from the sides of the screen, doing damage and stunning if they hit the target +// #define CURSE_GRASPING (1<<2) + +//Incapacitated status effect flags +/// If the mob is normal incapacitated. Should never need this, just avoids issues if we ever overexpand this +// #define TRADITIONAL_INCAPACITATED (1<<0) +/// If the incapacitated status effect is being caused by restraints (handcuffs) +// #define INCAPABLE_RESTRAINTS (1<<1) +/// If the incapacitated status effect is being caused by stasis (stasis beds) +// #define INCAPABLE_STASIS (1<<2) +/// If the incapacitated status effect is being caused by being agressively grabbed +// #define INCAPABLE_GRAB (1<<3) + +/// Checks to see if a mob would be incapacitated even while ignoring some types +/// Does this by inverting the passed in flags and seeing if we're still incapacitated +// #define INCAPACITATED_IGNORING(mob, flags) (mob.incapacitated & ~(flags)) + +/// Max amounts of fire stacks a mob can get +#define MAX_FIRE_STACKS 20 +/// If a mob has a higher threshold than this, the icon shown will be increased to the big fire icon. +#define MOB_BIG_FIRE_STACK_THRESHOLD 3 + +// Grouped effect sources, see also code/__DEFINES/traits.dm + +#define STASIS_MACHINE_EFFECT "stasis_machine" +#define STASIS_CHEMICAL_EFFECT "stasis_chemical" +#define STASIS_SHAPECHANGE_EFFECT "stasis_shapechange" +#define STASIS_ADMIN "stasis_admin" +#define STASIS_LEGION_EATEN "stasis_eaten" +#define STASIS_SLIME_BZ "stasis_slime_bz" +#define STASIS_ELDRITCH_ETHER "stasis_eldritch_ether" + +#define STASIS_NETPOD_EFFECT "stasis_netpod" + +/// Causes the mob to become blind via the passed source +// #define become_blind(source) apply_status_effect(/datum/status_effect/grouped/blindness, source) +/// Cures the mob's blindness from the passed source, removing blindness wholesale if no sources are left +// #define cure_blind(source) remove_status_effect(/datum/status_effect/grouped/blindness, source) + +/// Is the mob blind? +// #define is_blind(...) has_status_effect(/datum/status_effect/grouped/blindness) +/// Is the mob blind from the passed source or sources? +// #define is_blind_from(sources) has_status_effect_from_source(/datum/status_effect/grouped/blindness, sources) + +/// We are not nearsighted right now. +// #define NEARSIGHTED_DISABLED 0 +/// Something is correcting our vision, but we are still a bit nearsighted. +// #define NEARSIGHTED_CORRECTED 1 +/// We are fully nearsighted. +// #define NEARSIGHTED_ENABLED 2 + +/// Simplified macro that causes the mob to become nearsighted (with correction possible) via the passed source. +// #define become_nearsighted(source) apply_status_effect(/datum/status_effect/grouped/nearsighted, source) +/// Causes the mob to become nearsighted from the passed source by a severity, which may be corrected with glasses. +// #define assign_nearsightedness(source, amount, correctable) apply_status_effect(/datum/status_effect/grouped/nearsighted, source, amount, correctable) +/// Cures the mob's nearsightedness from the passed source, removing nearsighted wholesale if no sources are left. +// #define cure_nearsighted(source) remove_status_effect(/datum/status_effect/grouped/nearsighted, source) + +/// Is the mob nearsighted? +// #define is_nearsighted(...) has_status_effect(/datum/status_effect/grouped/nearsighted) +/// Is the mob nearsigthed from the passed source or sources? +// #define is_nearsighted_from(sources) has_status_effect_from_source(/datum/status_effect/grouped/nearsighted, sources) +/// Is the mob nearsighted CURRENTLY? +/// This check fails if the mob is nearsighted but is wearing glasses, +/// While is_nearsighted will always succeed even if they are wearing glasses. +// /mob/proc/is_nearsighted_currently() +// var/datum/status_effect/grouped/nearsighted/nearsight = has_status_effect(/datum/status_effect/grouped/nearsighted) +// if(isnull(nearsight)) +// return FALSE +// return (nearsight.should_be_nearsighted() > NEARSIGHTED_CORRECTED) + +// Status effect application helpers. +// These are macros for easier use of adjust_timed_status_effect and set_timed_status_effect. +// +// adjust_x: +// - Adds duration to a status effect +// - Removes duration if a negative duration is passed. +// - Ex: adjust_stutter(10 SECONDS) adds ten seconds of stuttering. +// - Ex: adjust_jitter(-5 SECONDS) removes five seconds of jittering, or just removes jittering if less than five seconds exist. +// +// adjust_x_up_to: +// - Will only add (or remove) duration of a status effect up to the second parameter +// - If the duration will result in going beyond the second parameter, it will stop exactly at that parameter +// - The second parameter cannot be negative. +// - Ex: adjust_stutter_up_to(20 SECONDS, 10 SECONDS) adds ten seconds of stuttering. +// +// set_x: +// - Set the duration of a status effect to the exact number. +// - Setting duration to zero seconds is effectively the same as just using remove_status_effect, or qdelling the effect. +// - Ex: set_stutter(10 SECONDS) sets the stuttering to ten seconds, regardless of whether they had more or less existing stutter. +// +// set_x_if_lower: +// - Will only set the duration of that effect IF any existing duration is lower than what was passed. +// - Ex: set_stutter_if_lower(10 SECONDS) will set stuttering to ten seconds if no stuttering or less than ten seconds of stuttering exists +// - Ex: set_jitter_if_lower(20 SECONDS) will do nothing if more than twenty seconds of jittering already exists + +// #define adjust_stutter(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter) +// #define adjust_stutter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter, up_to) +// #define set_stutter(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter) +// #define set_stutter_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter, TRUE) + +// #define adjust_derpspeech(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech) +// #define adjust_derpspeech_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech, up_to) +// #define set_derpspeech(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech) +// #define set_derpspeech_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech, TRUE) + +// #define adjust_slurring(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/slurring/generic) +// #define adjust_slurring_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/slurring/generic, up_to) +// #define set_slurring(duration) set_timed_status_effect(duration, /datum/status_effect/speech/slurring/generic) +// #define set_slurring_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/slurring/generic, TRUE) + +// #define adjust_dizzy(duration) adjust_timed_status_effect(duration, /datum/status_effect/dizziness) +// #define adjust_dizzy_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/dizziness, up_to) +// #define set_dizzy(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness) +// #define set_dizzy_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness, TRUE) + +// #define adjust_staggered(duration) adjust_timed_status_effect(duration, /datum/status_effect/staggered) +// #define adjust_staggered_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/staggered, up_to) +// #define set_staggered(duration) set_timed_status_effect(duration, /datum/status_effect/staggered) +// #define set_staggered_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/staggered, TRUE) + +// #define adjust_jitter(duration) adjust_timed_status_effect(duration, /datum/status_effect/jitter) +// #define adjust_jitter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/jitter, up_to) +// #define set_jitter(duration) set_timed_status_effect(duration, /datum/status_effect/jitter) +// #define set_jitter_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/jitter, TRUE) + +// #define adjust_confusion(duration) adjust_timed_status_effect(duration, /datum/status_effect/confusion) +// #define adjust_confusion_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/confusion, up_to) +// #define set_confusion(duration) set_timed_status_effect(duration, /datum/status_effect/confusion) +// #define set_confusion_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/confusion, TRUE) + +// #define adjust_drugginess(duration) adjust_timed_status_effect(duration, /datum/status_effect/drugginess) +// #define adjust_drugginess_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/drugginess, up_to) +// #define set_drugginess(duration) set_timed_status_effect(duration, /datum/status_effect/drugginess) +// #define set_drugginess_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drugginess, TRUE) + +// #define adjust_silence(duration) adjust_timed_status_effect(duration, /datum/status_effect/silenced) +// #define adjust_silence_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/silenced, up_to) +// #define set_silence(duration) set_timed_status_effect(duration, /datum/status_effect/silenced) +// #define set_silence_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/silenced, TRUE) + +// #define adjust_hallucinations(duration) adjust_timed_status_effect(duration, /datum/status_effect/hallucination) +// #define adjust_hallucinations_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/hallucination, up_to) +// #define set_hallucinations(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination) +// #define set_hallucinations_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination, TRUE) + +// #define adjust_drowsiness(duration) adjust_timed_status_effect(duration, /datum/status_effect/drowsiness) +// #define adjust_drowsiness_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/drowsiness, up_to) +// #define set_drowsiness(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness) +// #define set_drowsiness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness, TRUE) + +// #define adjust_pacifism(duration) adjust_timed_status_effect(duration, /datum/status_effect/pacify) +// #define set_pacifism(duration) set_timed_status_effect(duration, /datum/status_effect/pacify) + +// #define adjust_eye_blur(duration) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur) +// #define adjust_eye_blur_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur, up_to) +// #define set_eye_blur(duration) set_timed_status_effect(duration, /datum/status_effect/eye_blur) +// #define set_eye_blur_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/eye_blur, TRUE) + +// #define adjust_temp_blindness(duration) adjust_timed_status_effect(duration, /datum/status_effect/temporary_blindness) +// #define adjust_temp_blindness_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/temporary_blindness, up_to) +// #define set_temp_blindness(duration) set_timed_status_effect(duration, /datum/status_effect/temporary_blindness) +// #define set_temp_blindness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/temporary_blindness, TRUE) + +// #define adjust_static_vision(duration) adjust_timed_status_effect(duration, /datum/status_effect/static_vision) +// #define adjust_static_vision_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/static_vision, up_to) +// #define set_static_vision(duration) set_timed_status_effect(duration, /datum/status_effect/static_vision) +// #define set_static_vision_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/static_vision, TRUE) diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index b018d1cccd..d5ad6ab640 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -136,6 +136,7 @@ #define FIRE_PRIORITY_DEFAULT 50 #define FIRE_PRIORITY_TICKER 60 #define FIRE_PRIORITY_PLANETS 75 +#define FIRE_PRIORITY_PRIORITY_EFFECTS 90 #define FIRE_PRIORITY_EXPLOSIONS 90 #define FIRE_PRIORITY_MACHINES 100 #define FIRE_PRIORITY_MOBS 100 diff --git a/code/__defines/traits/declarations.dm b/code/__defines/traits/declarations.dm index f95a6a5c28..d5446e206e 100644 --- a/code/__defines/traits/declarations.dm +++ b/code/__defines/traits/declarations.dm @@ -17,3 +17,18 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_ALT_CLICK_BLOCKER "no_alt_click" #define TRAIT_INCAPACITATED "incapacitated" + +#define TRAIT_NOFIRE "nonflammable" +#define TRAIT_NOFIRE_SPREAD "no_fire_spreading" +/// Mobs that have this trait cannot be extinguished +#define TRAIT_NO_EXTINGUISH "no_extinguish" +/// Tells us that the mob urrently has the fire_handler/wet_stacks status effect +#define TRAIT_IS_WET "is_wet" +/// Mobs with this trait stay wet for longer and resist fire decaying wetness +#define TRAIT_WET_FOR_LONGER "wet_for_longer" +/// Mobs with this trait will be immune to slipping while also being slippery themselves when lying on the floor +#define TRAIT_SLIPPERY_WHEN_WET "slippery_when_wet" +/// Stops the mob from slipping on water, or banana peels, or pretty much anything that doesn't have [GALOSHES_DONT_HELP] set +#define TRAIT_NO_SLIP_WATER "noslip_water" +/// Owner will ignore any fire protection when calculating fire damage +#define TRAIT_IGNORE_FIRE_PROTECTION "ignore_fire_protection" diff --git a/code/__defines/traits/macros.dm b/code/__defines/traits/macros.dm index d180b24442..6a0f9c8cc9 100644 --- a/code/__defines/traits/macros.dm +++ b/code/__defines/traits/macros.dm @@ -1,2 +1,5 @@ /// Trait applied by element #define ELEMENT_TRAIT(source) "element_trait_[source]" + +/// A trait given by a specific status effect (not sure why we need both but whatever!) +#define TRAIT_STATUS_EFFECT(effect_id) "[effect_id]-trait" diff --git a/code/_global_vars/traits/_traits.dm b/code/_global_vars/traits/_traits.dm index 8b98c1c686..85cb840de3 100644 --- a/code/_global_vars/traits/_traits.dm +++ b/code/_global_vars/traits/_traits.dm @@ -22,6 +22,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /mob = list( "TRAIT_THINKING_IN_CHARACTER" = TRAIT_THINKING_IN_CHARACTER, + "TRAIT_NOFIRE" = TRAIT_NOFIRE, + "TRAIT_NOFIRE_SPREAD" = TRAIT_NOFIRE_SPREAD, + "TRAIT_NO_EXTINGUISH" = TRAIT_NO_EXTINGUISH, ), /obj = list( "TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE, diff --git a/code/controllers/subsystems/processing/priority_effects.dm b/code/controllers/subsystems/processing/priority_effects.dm new file mode 100644 index 0000000000..ee4fb7f7f6 --- /dev/null +++ b/code/controllers/subsystems/processing/priority_effects.dm @@ -0,0 +1,6 @@ +PROCESSING_SUBSYSTEM_DEF(priority_effects) + name = "Priority Status Effects" + flags = SS_TICKER | SS_KEEP_TIMING | SS_NO_INIT + wait = 2 // Not seconds - we're running on SS_TICKER, so this is ticks. + priority = FIRE_PRIORITY_PRIORITY_EFFECTS + stat_tag = "PEFF" diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm index 037e6cdec1..e3afb0ea99 100644 --- a/code/datums/diseases/advance/symptoms/fire.dm +++ b/code/datums/diseases/advance/symptoms/fire.dm @@ -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") diff --git a/code/datums/status_effects/_status_effect.dm b/code/datums/status_effects/_status_effect.dm new file mode 100644 index 0000000000..2ac0e9fe9b --- /dev/null +++ b/code/datums/status_effects/_status_effect.dm @@ -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("[round((duration - world.time)/10, 1)]s") + +// 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 ..() diff --git a/code/datums/status_effects/_status_effect_helpers.dm b/code/datums/status_effects/_status_effect_helpers.dm new file mode 100644 index 0000000000..a5743d2e93 --- /dev/null +++ b/code/datums/status_effects/_status_effect_helpers.dm @@ -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 diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm new file mode 100644 index 0000000000..191e3aba52 --- /dev/null +++ b/code/datums/status_effects/debuffs/fire_stacks.dm @@ -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 diff --git a/code/datums/status_effects/grouped_effect.dm b/code/datums/status_effects/grouped_effect.dm new file mode 100644 index 0000000000..caddfe17fc --- /dev/null +++ b/code/datums/status_effects/grouped_effect.dm @@ -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 diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 55cdd7aab0..18898a8210 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -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) diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index 6e8fe1da9b..78302073a3 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -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 += "

OK...

" diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index 9919c50420..cd89a98c9d 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -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 diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index b32ac7d8b7..da4223334b 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -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" diff --git a/code/game/objects/effects/particle_holder.dm b/code/game/objects/effects/particle_holder.dm new file mode 100644 index 0000000000..7e9594a6b1 --- /dev/null +++ b/code/game/objects/effects/particle_holder.dm @@ -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) diff --git a/code/game/objects/effects/particles/fire.dm b/code/game/objects/effects/particles/fire.dm new file mode 100644 index 0000000000..481849c00e --- /dev/null +++ b/code/game/objects/effects/particles/fire.dm @@ -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") diff --git a/code/game/objects/effects/particles/smoke.dm b/code/game/objects/effects/particles/smoke.dm new file mode 100644 index 0000000000..323e4ffbdc --- /dev/null +++ b/code/game/objects/effects/particles/smoke.dm @@ -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 diff --git a/code/game/objects/effects/particles/water.dm b/code/game/objects/effects/particles/water.dm new file mode 100644 index 0000000000..88e0ef542e --- /dev/null +++ b/code/game/objects/effects/particles/water.dm @@ -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) diff --git a/code/game/objects/effects/prop/columnblast.dm b/code/game/objects/effects/prop/columnblast.dm index e0698006d5..b254669a41 100644 --- a/code/game/objects/effects/prop/columnblast.dm +++ b/code/game/objects/effects/prop/columnblast.dm @@ -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 diff --git a/code/game/objects/effects/shared_particle_holder.dm b/code/game/objects/effects/shared_particle_holder.dm new file mode 100644 index 0000000000..a97812a28b --- /dev/null +++ b/code/game/objects/effects/shared_particle_holder.dm @@ -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 diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm index 152c72ffb2..e4704e766e 100644 --- a/code/game/objects/items/weapons/cigs_lighters.dm +++ b/code/game/objects/items/weapons/cigs_lighters.dm @@ -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) diff --git a/code/game/objects/items/weapons/tools/weldingtool.dm b/code/game/objects/items/weapons/tools/weldingtool.dm index 5b7b3c2866..8909ca8c42 100644 --- a/code/game/objects/items/weapons/tools/weldingtool.dm +++ b/code/game/objects/items/weapons/tools/weldingtool.dm @@ -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) diff --git a/code/game/objects/items/weapons/towels.dm b/code/game/objects/items/weapons/towels.dm index a200c49c41..a30f8d7d8d 100644 --- a/code/game/objects/items/weapons/towels.dm +++ b/code/game/objects/items/weapons/towels.dm @@ -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) . = ..() diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index 168abf2d0e..dfeecd0230 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -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() diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index ff04b13f04..cb714ddafb 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -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 diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm index 419859363d..70e046c76f 100644 --- a/code/game/turfs/simulated/water.dm +++ b/code/game/turfs/simulated/water.dm @@ -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() diff --git a/code/modules/admin/player_effects.dm b/code/modules/admin/player_effects.dm index 1a2b1286bc..7f88f92376 100644 --- a/code/modules/admin/player_effects.dm +++ b/code/modules/admin/player_effects.dm @@ -73,7 +73,7 @@ if(!istype(Tar)) return Tar.adjust_fire_stacks(10) - Tar.IgniteMob() + Tar.ignite_mob() Tar.visible_message(span_danger("[target] bursts into flames!")) if("lightning_strike") diff --git a/code/modules/admin/verbs/smite.dm b/code/modules/admin/verbs/smite.dm index 4380df9bd7..f743fe9b09 100644 --- a/code/modules/admin/verbs/smite.dm +++ b/code/modules/admin/verbs/smite.dm @@ -36,7 +36,7 @@ if(SMITE_SPONTANEOUSCOMBUSTION) target.adjust_fire_stacks(10) - target.IgniteMob() + target.ignite_mob() target.visible_message(span_danger("[target] bursts into flames!")) if(SMITE_LIGHTNINGBOLT) diff --git a/code/modules/blob2/overmind/types/pressurized_slime.dm b/code/modules/blob2/overmind/types/pressurized_slime.dm index 98a78440bf..5ecba15b56 100644 --- a/code/modules/blob2/overmind/types/pressurized_slime.dm +++ b/code/modules/blob2/overmind/types/pressurized_slime.dm @@ -52,5 +52,5 @@ /datum/blob_type/pressurized_slime/on_chunk_use(obj/item/blobcore_chunk/B, mob/living/user) // Drenches you in water. if(user) - user.ExtinguishMob() - user.fire_stacks = CLAMP(user.fire_stacks - 1, -25, 25) + user.adjust_wet_stacks(2.5) + user.extinguish_mob() diff --git a/code/modules/detectivework/tools/rag.dm b/code/modules/detectivework/tools/rag.dm index 7d92f9d73e..74161bbb62 100644 --- a/code/modules/detectivework/tools/rag.dm +++ b/code/modules/detectivework/tools/rag.dm @@ -108,7 +108,7 @@ if(on_fire) //Check if rag is on fire, if so igniting them and stopping. user.visible_message(span_danger("\The [user] hits [target] with [src]!"),) user.do_attack_animation(src) - M.IgniteMob() + M.ignite_mob() else if(user.zone_sel.selecting == O_MOUTH) //Check player target location, provided the rag is not on fire. Then check if mouth is exposed. if(ishuman(target)) //Added this since player species process reagents in majority of cases. var/mob/living/carbon/human/H = target @@ -198,7 +198,8 @@ update_name() update_icon() -/obj/item/reagent_containers/glass/rag/proc/extinguish() +/obj/item/reagent_containers/glass/rag/extinguish() + . = ..() STOP_PROCESSING(SSobj, src) set_light(0) on_fire = 0 @@ -220,7 +221,7 @@ //copied from matches if(isliving(loc)) var/mob/living/M = loc - M.IgniteMob() + M.ignite_mob() var/turf/location = get_turf(src) if(location) location.hotspot_expose(700, 5) diff --git a/code/modules/food/food/drinks/bottle.dm b/code/modules/food/food/drinks/bottle.dm index 994e14b356..3658fbc291 100644 --- a/code/modules/food/food/drinks/bottle.dm +++ b/code/modules/food/food/drinks/bottle.dm @@ -83,7 +83,7 @@ if(rag && rag.on_fire && isliving(against)) rag.forceMove(loc) var/mob/living/L = against - L.IgniteMob() + L.ignite_mob() playsound(src, "shatter", 70, 1) src.transfer_fingerprints_to(B) diff --git a/code/modules/mob/_modifiers/fire.dm b/code/modules/mob/_modifiers/fire.dm deleted file mode 100644 index 31f6a4f820..0000000000 --- a/code/modules/mob/_modifiers/fire.dm +++ /dev/null @@ -1,42 +0,0 @@ -// Ignition, but confined to the modifier system. -// This makes it more predictable and thus, easier to balance. -/datum/modifier/fire - name = "on fire" - desc = "You are on fire! You will be harmed until the fire goes out or you extinguish it with water." - mob_overlay_state = "on_fire" - - on_created_text = span_danger("You burst into flames!") - on_expired_text = span_warning("The fire starts to fade.") - stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot. - var/damage_per_tick = 5 - -/datum/modifier/fire/intense - mob_overlay_state = "on_fire_intense" - damage_per_tick = 10 - -/datum/modifier/fire/tick() - holder.inflict_heat_damage(damage_per_tick) - -/datum/modifier/fire/weak - damage_per_tick = 1 - -/* - * Modifier used by projectiles, like the flamethrower, that rely heavily on fire_stacks to persist. - */ - -/datum/modifier/fire/stack_managed/tick() - ..() - - if(!holder.fire_stacks || holder.fire_stacks < 0) - if(prob(10)) - expire() - - else if(holder.fire_stacks > 0) - holder.fire_stacks -= 0.5 - -/datum/modifier/fire/stack_managed/intense - mob_overlay_state = "on_fire_intense" - damage_per_tick = 10 - -/datum/modifier/fire/stack_managed/weak - damage_per_tick = 1 diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index 8640e65de7..83d272d31c 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -156,8 +156,5 @@ else clear_alert("alien_fire") -/mob/living/carbon/alien/handle_fire() - if(..()) - return - bodytemperature += BODYTEMP_HEATING_MAX //If you're on fire, you heat up! - return +/mob/living/carbon/alien/on_fire_stack(seconds_per_tick, datum/status_effect/fire_handler/fire_stacks/fire_handler) + bodytemperature += BODYTEMP_HEATING_MAX diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 362f4b897c..311ecc6a6e 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -244,7 +244,7 @@ src.adjust_fire_stacks(-0.5) if (prob(10) && (M.fire_stacks <= 0)) M.adjust_fire_stacks(1) - M.IgniteMob() + M.ignite_mob() if (M.on_fire) M.visible_message(span_danger("The fire spreads from [src] to [M]!"), span_danger("The fire spreads to you as well!")) @@ -253,8 +253,7 @@ if (src.fire_stacks <= 0) M.visible_message(span_warning("[M] successfully pats out [src]'s flames."), span_warning("You successfully pat out [src]'s flames.")) - src.ExtinguishMob() - src.fire_stacks = 0 + src.extinguish_mob() else if (ishuman(src) && src:w_uniform) var/mob/living/carbon/human/H = src @@ -284,7 +283,7 @@ src.adjust_fire_stacks(1) M.adjust_fire_stacks(-1) if(M.on_fire) - src.IgniteMob() + src.ignite_mob() M.resting = 0 //Hoist yourself up up off the ground. No para/stunned/weakened removal. update_canmove() else if(istype(hugger)) @@ -296,7 +295,7 @@ src.adjust_fire_stacks(1) M.adjust_fire_stacks(-1) if(M.on_fire) - src.IgniteMob() + src.ignite_mob() AdjustParalysis(-3) AdjustStunned(-3) AdjustWeakened(-3) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 62cf2755a9..1083054f89 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -280,7 +280,7 @@ if(AM.is_incorporeal()) return - spread_fire(AM) + spreadFire(AM) ..() // call parent because we moved behavior to parent diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 42900a4424..4651198e68 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -652,10 +652,9 @@ emp_act return CLAMP(1-converted_protection, 0, 1) /mob/living/carbon/human/water_act(amount) - 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) species.handle_water_damage(src, amount) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 4d364b1b57..1b027a45ce 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -2236,26 +2236,13 @@ hud_updateflag = 0 -/mob/living/carbon/human/handle_fire() - if(..()) - return - - var/thermal_protection = get_heat_protection(fire_stacks * 1500) // Arbitrary but below firesuit max temp when below 20 stacks. - - if(thermal_protection == 1) // Immune. - return - else - var/fire_temp_add = (BODYTEMP_HEATING_MAX + (fire_stacks * 15)) * (1-thermal_protection) - //This is to prevent humans from heating up indefinitely. A human being on fire (fat burns at 250C) can't magically - // increase your body temperature beyond 250C, but it's possible something else (atmos) has heated us up beyond it, - // so don't worry about the firestacks at that point. Really, we should be cooling the room down, because it has - // to expend energy to heat our body up! But let's not worry about that. - - // This whole section above is ABSOLUTELY STUPID and makes no sense and this would prevent too-high-heat from even being able to hurt someone. No. We will heat up for as long as needed. - //if((bodytemperature + fire_temp_add) > HUMAN_COMBUSTION_TEMP) - // return - - bodytemperature += fire_temp_add +/mob/living/carbon/human/on_fire_stack(seconds_per_tick, datum/status_effect/fire_handler/fire_stacks/fire_handler) + SEND_SIGNAL(src, COMSIG_HUMAN_BURNING) + // burn_clothing(seconds_per_tick, fire_handler.stacks) + var/no_protection = FALSE + if(HAS_TRAIT(src, TRAIT_IGNORE_FIRE_PROTECTION)) + no_protection = TRUE + fire_handler.harm_human(seconds_per_tick, no_protection) /mob/living/carbon/human/rejuvenate() restore_blood() diff --git a/code/modules/mob/living/carbon/resist.dm b/code/modules/mob/living/carbon/resist.dm index c3e1a0ee65..da0e0e8444 100644 --- a/code/modules/mob/living/carbon/resist.dm +++ b/code/modules/mob/living/carbon/resist.dm @@ -12,7 +12,7 @@ span_danger("[src] has successfully extinguished themselves!"), span_notice("You extinguish yourself.") ) - ExtinguishMob() + extinguish_mob() return TRUE /mob/living/carbon/resist_restraints() diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index c6ade8d909..2a9047fd94 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -192,8 +192,6 @@ if(agony) apply_effect(agony, AGONY, blocked) if(flammable) adjust_fire_stacks(flammable) if(ignite) - if(ignite >= 3) - add_modifier(/datum/modifier/fire/stack_managed/intense, 60 SECONDS) - else - add_modifier(/datum/modifier/fire/stack_managed, 45 * ignite SECONDS) + adjust_fire_stacks(ignite) + ignite_mob() return 1 diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 788c33bb8c..dc87a27d33 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -59,9 +59,6 @@ if(environment) handle_environment(environment) - //Check if we're on fire - handle_fire() - if(client) // Handle re-running ambience to mobs if they've remained in an area, AND have an active client assigned to them, and do not have repeating ambience disabled. handle_ambience() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index bb86d8738e..d10e7e3037 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -725,12 +725,11 @@ BITSET(hud_updateflag, HEALTH_HUD) BITSET(hud_updateflag, STATUS_HUD) BITSET(hud_updateflag, LIFE_HUD) - ExtinguishMob() - fire_stacks = 0 if(ai_holder) // AI gets told to sleep when killed. Since they're not dead anymore, wake it up. ai_holder.go_wake() SEND_SIGNAL(src, COMSIG_HUMAN_DNA_FINALIZED) + SEND_SIGNAL(src, COMSIG_LIVING_AHEAL) /mob/living/proc/rejuvenate() if(reagents) @@ -1176,10 +1175,6 @@ return FALSE return TRUE -// Gets the correct icon_state for being on fire. See OnFire.dmi for the icons. -/mob/living/proc/get_fire_icon_state() - return "generic" - // Called by job_controller. /mob/living/proc/equip_post_job() return diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 7617cfc70f..de33f3d1bd 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -419,85 +419,6 @@ spawn(1) updatehealth() return 1 -/mob/living/proc/IgniteMob() - if(fire_stacks > 0 && !on_fire) - on_fire = 1 - new/obj/effect/dummy/lighting_obj/moblight/fire(src) - throw_alert("fire", /obj/screen/alert/fire) - update_fire() - firesoundloop.start() - -/mob/living/proc/ExtinguishMob() - if(on_fire) - on_fire = 0 - fire_stacks = 0 - for(var/obj/effect/dummy/lighting_obj/moblight/fire/F in src) - qdel(F) - clear_alert("fire") - update_fire() - firesoundloop.stop() - - if(has_modifier_of_type(/datum/modifier/fire)) - remove_modifiers_of_type(/datum/modifier/fire) - -/mob/living/proc/update_fire() - return - -/mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person - fire_stacks = CLAMP(fire_stacks + add_fire_stacks, FIRE_MIN_STACKS, FIRE_MAX_STACKS) - -/mob/living/proc/handle_fire() - if(fire_stacks < 0) - fire_stacks = min(0, ++fire_stacks) //If we've doused ourselves in water to avoid fire, dry off slowly - - if(fire_stacks > 0) - fire_stacks = max(0, (fire_stacks-0.1)) //Should slowly burn out - - if(!on_fire) - return 1 - else if(fire_stacks <= 0) - ExtinguishMob() //Fire's been put out. - return 1 - - var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment - if(G.gas[GAS_O2] < 1) - ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire - return 1 - - var/turf/location = get_turf(src) - location.hotspot_expose(fire_burn_temperature(), 50, 1) - -//altered this to cap at the temperature of the fire causing it, using the same 1:1500 value as /mob/living/carbon/human/handle_fire() in human/life.dm -/mob/living/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature) - if(fire_stacks < exposed_temperature/1500) // Subject to balance - adjust_fire_stacks(2) - else - adjust_fire_stacks(2) - IgniteMob() - -//Share fire evenly between the two mobs -//Called in MobCollide() and Crossed() -/mob/living/proc/spread_fire(mob/living/L) - return -// This is commented out pending discussion on Polaris. If you're a downsteam and you want people to spread fire by touching each other, feel free to uncomment this. -/* - if(!istype(L)) - return - var/L_old_on_fire = L.on_fire - - if(on_fire) //Only spread fire stacks if we're on fire - fire_stacks /= 2 - L.fire_stacks += fire_stacks - if(L.IgniteMob()) - message_admins("[key_name(src)] bumped into [key_name(L)] and set them on fire.") - - if(L_old_on_fire) //Only ignite us and gain their stacks if they were onfire before we bumped them - L.fire_stacks /= 2 - fire_stacks += L.fire_stacks - IgniteMob() -*/ - /mob/living/proc/get_cold_protection() return 0 @@ -534,8 +455,7 @@ // Called when touching a lava tile. // Does roughly 70 damage (30 instantly, up to ~40 over time) to unprotected mobs, and 10 to fully protected mobs. /mob/living/lava_act() - adjust_fire_stacks(1) - add_modifier(/datum/modifier/fire/stack_managed/intense, 8 SECONDS) // Around 40 total if left to burn and without fire protection per stack. + adjust_fire_stacks(4) inflict_heat_damage(20) // Another 20, however this is instantly applied to unprotected mobs. adjustFireLoss(10) // Lava cannot be 100% resisted with fire protection. diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 5dcaa13342..e40f60544c 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -42,8 +42,12 @@ var/tod = null // Time of death var/update_slimes = 1 var/silent = null // Can't talk. Value goes down every life proc. - var/on_fire = 0 //The "Are we on fire?" var + + /// Helper vars for quick access to firestacks, these should be updated every time firestacks are adjusted + var/on_fire = 0 var/fire_stacks + /// Rate at which fire stacks should decay from this mob + var/fire_stack_decay_rate = -0.05 var/failed_last_breath = 0 //This is used to determine if the mob failed a breath. If they did fail a brath, they will attempt to breathe each tick, otherwise just once per 4 ticks. var/lastpuke = 0 @@ -115,3 +119,6 @@ var/ooc_notes_favs = null var/ooc_notes_maybes = null var/ooc_notes_style = FALSE + + ///a list of all status effects the mob has + var/list/status_effects diff --git a/code/modules/mob/living/living_fire.dm b/code/modules/mob/living/living_fire.dm new file mode 100644 index 0000000000..b8cf958297 --- /dev/null +++ b/code/modules/mob/living/living_fire.dm @@ -0,0 +1,179 @@ +//Mobs on Fire + +//altered this to cap at the temperature of the fire causing it, using the same 1:1500 value as /mob/living/carbon/human/handle_fire() in human/life.dm +/mob/living/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature) + if(fire_stacks < exposed_temperature/1500) // Subject to balance + adjust_fire_stacks(2) + else + adjust_fire_stacks(2) + ignite_mob() + + +/// Global list that containes cached fire overlays for mobs +GLOBAL_LIST_EMPTY(fire_appearances) + +/mob/living/proc/ignite_mob(silent) + if(fire_stacks <= 0) + return FALSE + + var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks) + if(!fire_status || fire_status.on_fire) + return FALSE + + return fire_status.ignite(silent) + +/** + * Extinguish all fire on the mob + * + * This removes all fire stacks, fire effects, alerts, and moods + * Signals the extinguishing. + */ +/mob/living/proc/extinguish_mob() + if(HAS_TRAIT(src, TRAIT_NO_EXTINGUISH)) //The everlasting flames will not be extinguished + return + var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks) + if(!fire_status || !fire_status.on_fire) + return + remove_status_effect(/datum/status_effect/fire_handler/fire_stacks) + +/mob/living/proc/update_fire() + return + +/** + * Adjust the amount of fire stacks on a mob + * + * This modifies the fire stacks on a mob. + * + * Vars: + * * stacks: int The amount to modify the fire stacks + * * fire_type: type Type of fire status effect that we apply, should be subtype of /datum/status_effect/fire_handler/fire_stacks + */ + +/mob/living/proc/adjust_fire_stacks(stacks, fire_type = /datum/status_effect/fire_handler/fire_stacks) + if(stacks < 0) + if(HAS_TRAIT(src, TRAIT_NO_EXTINGUISH)) //You can't reduce fire stacks of the everlasting flames + return + stacks = max(-fire_stacks, stacks) + apply_status_effect(fire_type, stacks) + +/mob/living/proc/adjust_wet_stacks(stacks, wet_type = /datum/status_effect/fire_handler/wet_stacks) + if(HAS_TRAIT(src, TRAIT_NO_EXTINGUISH)) //The everlasting flames will not be extinguished + return + if(stacks < 0) + stacks = max(fire_stacks, stacks) + apply_status_effect(wet_type, stacks) + +/** + * Set the fire stacks on a mob + * + * This sets the fire stacks on a mob, stacks are clamped between -20 and 20. + * If the fire stacks are reduced to 0 then we will extinguish the mob. + * + * Vars: + * * stacks: int The amount to set fire_stacks to + * * fire_type: type Type of fire status effect that we apply, should be subtype of /datum/status_effect/fire_handler/fire_stacks + * * remove_wet_stacks: bool If we remove all wet stacks upon doing this + */ + +/mob/living/proc/set_fire_stacks(stacks, fire_type = /datum/status_effect/fire_handler/fire_stacks, remove_wet_stacks = TRUE) + if(stacks < 0) //Shouldn't happen, ever + CRASH("set_fire_stacks received negative [stacks] fire stacks") + + if(remove_wet_stacks) + remove_status_effect(/datum/status_effect/fire_handler/wet_stacks) + + if(stacks == 0) + remove_status_effect(fire_type) + return + + apply_status_effect(fire_type, stacks, TRUE) + +/mob/living/proc/set_wet_stacks(stacks, wet_type = /datum/status_effect/fire_handler/wet_stacks, remove_fire_stacks = TRUE) + if(stacks < 0) + CRASH("set_wet_stacks received negative [stacks] wet stacks") + + if(remove_fire_stacks) + remove_status_effect(/datum/status_effect/fire_handler/fire_stacks) + + if(stacks == 0) + remove_status_effect(wet_type) + return + + apply_status_effect(wet_type, stacks, TRUE) + +//Share fire evenly between the two mobs +//Called in MobBump() and Crossed() +/mob/living/proc/spreadFire(mob/living/spread_to) + if(!istype(spread_to)) + return + + // can't spread fire to mobs that don't catch on fire + if(HAS_TRAIT(spread_to, TRAIT_NOFIRE_SPREAD) || HAS_TRAIT(src, TRAIT_NOFIRE_SPREAD)) + return + + var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks) + var/datum/status_effect/fire_handler/fire_stacks/their_fire_status = spread_to.has_status_effect(/datum/status_effect/fire_handler/fire_stacks) + if(fire_status && fire_status.on_fire) + if(their_fire_status && their_fire_status.on_fire) + var/firesplit = (fire_stacks + spread_to.fire_stacks) / 2 + var/fire_type = (spread_to.fire_stacks > fire_stacks) ? their_fire_status.type : fire_status.type + set_fire_stacks(firesplit, fire_type) + spread_to.set_fire_stacks(firesplit, fire_type) + return + + adjust_fire_stacks(-fire_stacks / 2, fire_status.type) + spread_to.adjust_fire_stacks(fire_stacks, fire_status.type) + if(spread_to.ignite_mob()) + message_admins("[key_name(src)] bumped into [key_name(spread_to)] and set them on fire.") + return + + if(!their_fire_status || !their_fire_status.on_fire) + return + + spread_to.adjust_fire_stacks(-spread_to.fire_stacks / 2, their_fire_status.type) + adjust_fire_stacks(spread_to.fire_stacks, their_fire_status.type) + ignite_mob() + +/** + * Gets the fire overlay to use for this mob + * + * Args: + * * stacks: Current amount of fire_stacks + * * on_fire: If we're lit on fire + * + * Return a mutable appearance, the overlay that will be applied. + */ + +// Gets the correct icon_state for being on fire. See OnFire.dmi for the icons. +/mob/living/proc/get_fire_icon_state() + return "generic" + +/mob/living/proc/get_fire_overlay(stacks, on_fire) + RETURN_TYPE(/mutable_appearance) + var/fire_icon = get_fire_icon_state() + + if(!GLOB.fire_appearances[fire_icon]) + var/mutable_appearance/new_fire_overlay = mutable_appearance( + 'icons/mob/OnFire.dmi', + fire_icon, + FIRE_LAYER, + appearance_flags = RESET_COLOR|KEEP_APART, + ) + GLOB.fire_appearances[fire_icon] = new_fire_overlay + + return GLOB.fire_appearances[fire_icon] + +/** + * Handles effects happening when mob is on normal fire + * + * Vars: + * * seconds_per_tick + * * times_fired + * * fire_handler: Current fire status effect that called the proc + */ + +/mob/living/proc/on_fire_stack(seconds_per_tick, datum/status_effect/fire_handler/fire_stacks/fire_handler) + return + +//Mobs on Fire end diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index bcf2fc1b69..10b1d3ea0e 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -55,7 +55,7 @@ default behaviour is: var/mob/living/tmob = AM //Even if we don't push/swap places, we "touched" them, so spread fire - spread_fire(tmob) + spreadFire(tmob) for(var/mob/living/M in range(tmob, 1)) if(tmob.pinned.len || ((M.pulling == tmob && ( tmob.restrained() && !( M.restrained() ) && M.stat == 0)) || locate(/obj/item/grab, tmob.grabbed_by.len)) ) diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 181ea0b478..8392fb1707 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -372,15 +372,15 @@ canmove = FALSE return canmove +/mob/living/silicon/robot/fire_act() + if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them + ignite_mob() + /mob/living/silicon/robot/update_fire() cut_overlay(image(icon = 'icons/mob/OnFire.dmi', icon_state = get_fire_icon_state())) if(on_fire) add_overlay(image(icon = 'icons/mob/OnFire.dmi', icon_state = get_fire_icon_state())) -/mob/living/silicon/robot/fire_act() - if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them - IgniteMob() - /mob/living/silicon/robot/handle_light() if(lights_on) set_light(integrated_light_power, 1, robot_light_col) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index b063d114ff..ffeffd679c 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -28,6 +28,7 @@ var/sensor_type = 0 //VOREStation add - silicon omni "is sensor on or nah" var/hudmode = null + fire_stack_decay_rate = -0.55 /mob/living/silicon/Initialize(mapload, is_decoy = FALSE) . = ..() diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm index d5d6bc8d1a..45ced044d8 100644 --- a/code/modules/mob/living/simple_mob/defense.dm +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -176,17 +176,6 @@ . = 1 - . . = min(., 1.0) - -// Fire stuff. Not really exciting at the moment. -/mob/living/simple_mob/handle_fire() - return -/mob/living/simple_mob/update_fire() - return -/mob/living/simple_mob/IgniteMob() - return -/mob/living/simple_mob/ExtinguishMob() - return - /mob/living/simple_mob/get_heat_protection() . = heat_resist . = 1 - . // Invert from 1 = immunity to 0 = immunity. diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm index 8add800d27..d7577bda91 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm @@ -184,7 +184,7 @@ visible_message(span_critical("\The [src]'s tank ruptures!")) ruptured = 1 adjust_fire_stacks(2) - IgniteMob() + ignite_mob() return ..() //Ammo Mouse diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm index 854e58ae12..956537ffc0 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm @@ -79,15 +79,13 @@ name = "ember" icon = 'icons/effects/effects.dmi' icon_state = "explosion_particle" - modifier_type_to_apply = /datum/modifier/fire - modifier_duration = 6 SECONDS // About 15 damage per stack, as Life() ticks every two seconds. + incendiary = 3 damage = 0 nodamage = TRUE impact_effect_type = /obj/effect/temp_visual/impact_effect hitsound_wall = 'sound/weapons/effects/searwall.ogg' - // Close to mid-ranged shooter that arcs over other things, ideal if allies are in front of it. // Difference from siege hivebots is that siege hivebots have limited charges for their attacks, are very long range, and // the projectiles have an AoE component, where as backline hivebots do not. diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm index eab5013eb9..dd866959c2 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm @@ -50,7 +50,7 @@ to_chat(L, span_danger("You've been burned by \the [src]!")) L.adjust_fire_stacks(1) if(prob(12)) - L.IgniteMob() + L.ignite_mob() /mob/living/simple_mob/slime/xenobio/blue desc = "This slime produces 'cryotoxin' and uses it against their foes. Very deadly to other slimes." diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm index b8b9466b28..a1f343133f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm @@ -887,7 +887,7 @@ I think I covered everything. if(iscarbon(target)) var/mob/living/carbon/M = target M.adjust_fire_stacks(fire_stacks) - M.IgniteMob() + M.ignite_mob() else . = ..() @@ -1010,7 +1010,7 @@ I think I covered everything. for(var/RG in to_inject) if(!P.reagents.has_reagent(RG)) P.reagents.add_reagent(RG, 10) - L.ExtinguishMob() + L.extinguish_mob() return //Don't attack people if we're on help intent return .=..() diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 56766feb5f..bb8d46bd28 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -68,7 +68,7 @@ span_userdanger("You miss the [src] and accidentally light yourself on fire!")) user.unEquip(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index f08afa01bb..9190a5b912 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -351,10 +351,8 @@ /obj/item/projectile/bullet/incendiary/flamethrower/tiny damage = 2 - incendiary = 0 + incendiary = 10 flammability = 2 - modifier_type_to_apply = /datum/modifier/fire/stack_managed/weak - modifier_duration = 20 SECONDS range = 6 agony = 0 hud_state = "flame" diff --git a/code/modules/reagents/reagents/core.dm b/code/modules/reagents/reagents/core.dm index df0934458d..3a3fd8b381 100644 --- a/code/modules/reagents/reagents/core.dm +++ b/code/modules/reagents/reagents/core.dm @@ -309,7 +309,7 @@ // Then extinguish people on fire. var/needed = max(0,L.fire_stacks) * 5 if(amount > needed) - L.ExtinguishMob() + L.extinguish_mob() L.water_act(amount / 25) // Div by 25, as water_act multiplies it by 5 in order to calculate firestack modification. remove_self(needed) // Put out cigarettes if splashed. diff --git a/code/modules/reagents/reagents/other.dm b/code/modules/reagents/reagents/other.dm index f5fe6fa8f5..fb18605be9 100644 --- a/code/modules/reagents/reagents/other.dm +++ b/code/modules/reagents/reagents/other.dm @@ -212,7 +212,7 @@ M.SetConfused(0) M.SetSleeping(0) M.radiation = 0 - M.ExtinguishMob() + M.extinguish_mob() M.fire_stacks = 0 M.add_chemical_effect(CE_ANTIBIOTIC, ANTIBIO_SUPER) M.add_chemical_effect(CE_STABLE, 15) diff --git a/code/modules/reagents/reagents/other_vr.dm b/code/modules/reagents/reagents/other_vr.dm index f1cfa6ec97..4140b57a48 100644 --- a/code/modules/reagents/reagents/other_vr.dm +++ b/code/modules/reagents/reagents/other_vr.dm @@ -102,7 +102,7 @@ S.visible_message(span_warning("[S]'s flesh sizzles where the foam touches it!"), span_danger("Your flesh burns in the foam!")) if(istype(M)) M.adjust_fire_stacks(-reac_volume) - M.ExtinguishMob() + M.extinguish_mob() /datum/reagent/liquid_protean name = REAGENT_LIQUIDPROTEAN diff --git a/code/modules/reagents/reagents/toxins.dm b/code/modules/reagents/reagents/toxins.dm index 6cd6725165..c7e80c3b3b 100644 --- a/code/modules/reagents/reagents/toxins.dm +++ b/code/modules/reagents/reagents/toxins.dm @@ -135,7 +135,7 @@ if(prob(10)) to_chat(M, span_critical("You feel something boiling within you!")) spawn(rand(30, 60)) - M.IgniteMob() + M.ignite_mob() /datum/reagent/toxin/lead name = REAGENT_LEAD @@ -584,7 +584,7 @@ to_chat(M, span_warning("Your veins feel like they're on fire!")) M.adjust_fire_stacks(0.1) else if(prob(5)) - M.IgniteMob() + M.ignite_mob() to_chat(M, span_critical("Some of your veins rupture, the exposed blood igniting!")) /datum/reagent/condensedcapsaicin/venom diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm index 09b9a633df..705845e7e0 100644 --- a/code/modules/spells/targeted/ethereal_jaunt.dm +++ b/code/modules/spells/targeted/ethereal_jaunt.dm @@ -30,7 +30,7 @@ animation.plane = MOB_PLANE animation.layer = ABOVE_MOB_LAYER animation.master = holder - target.ExtinguishMob() + target.extinguish_mob() if(target.buckled) target.buckled.unbuckle_mob( target, TRUE) jaunt_disappear(animation, target) diff --git a/icons/effects/particles/bonfire.dmi b/icons/effects/particles/bonfire.dmi new file mode 100644 index 0000000000..e8e2e36346 Binary files /dev/null and b/icons/effects/particles/bonfire.dmi differ diff --git a/icons/effects/particles/echo.dmi b/icons/effects/particles/echo.dmi new file mode 100644 index 0000000000..60a243a8a7 Binary files /dev/null and b/icons/effects/particles/echo.dmi differ diff --git a/icons/effects/particles/generic.dmi b/icons/effects/particles/generic.dmi new file mode 100644 index 0000000000..41776efdbf Binary files /dev/null and b/icons/effects/particles/generic.dmi differ diff --git a/icons/effects/particles/goop.dmi b/icons/effects/particles/goop.dmi new file mode 100644 index 0000000000..673c1a7ad5 Binary files /dev/null and b/icons/effects/particles/goop.dmi differ diff --git a/icons/effects/particles/notes/note.dmi b/icons/effects/particles/notes/note.dmi new file mode 100644 index 0000000000..766a29e6a8 Binary files /dev/null and b/icons/effects/particles/notes/note.dmi differ diff --git a/icons/effects/particles/notes/note_harm.dmi b/icons/effects/particles/notes/note_harm.dmi new file mode 100644 index 0000000000..0fe386e1ed Binary files /dev/null and b/icons/effects/particles/notes/note_harm.dmi differ diff --git a/icons/effects/particles/notes/note_holy.dmi b/icons/effects/particles/notes/note_holy.dmi new file mode 100644 index 0000000000..f817e8253d Binary files /dev/null and b/icons/effects/particles/notes/note_holy.dmi differ diff --git a/icons/effects/particles/notes/note_light.dmi b/icons/effects/particles/notes/note_light.dmi new file mode 100644 index 0000000000..3474edebc2 Binary files /dev/null and b/icons/effects/particles/notes/note_light.dmi differ diff --git a/icons/effects/particles/notes/note_null.dmi b/icons/effects/particles/notes/note_null.dmi new file mode 100644 index 0000000000..14c941f9e4 Binary files /dev/null and b/icons/effects/particles/notes/note_null.dmi differ diff --git a/icons/effects/particles/notes/note_sleepy.dmi b/icons/effects/particles/notes/note_sleepy.dmi new file mode 100644 index 0000000000..21030914a9 Binary files /dev/null and b/icons/effects/particles/notes/note_sleepy.dmi differ diff --git a/icons/effects/particles/pollen.dmi b/icons/effects/particles/pollen.dmi new file mode 100644 index 0000000000..559c4d1846 Binary files /dev/null and b/icons/effects/particles/pollen.dmi differ diff --git a/icons/effects/particles/smoke.dmi b/icons/effects/particles/smoke.dmi new file mode 100644 index 0000000000..7e271e51f0 Binary files /dev/null and b/icons/effects/particles/smoke.dmi differ diff --git a/icons/effects/particles/stink.dmi b/icons/effects/particles/stink.dmi new file mode 100644 index 0000000000..29b92acbe6 Binary files /dev/null and b/icons/effects/particles/stink.dmi differ diff --git a/icons/effects/particles/voidwalker.dmi b/icons/effects/particles/voidwalker.dmi new file mode 100644 index 0000000000..621e468ac9 Binary files /dev/null and b/icons/effects/particles/voidwalker.dmi differ diff --git a/modular_chomp/code/datums/crafting/items.dm b/modular_chomp/code/datums/crafting/items.dm index 7390d425ce..45a6c2c0d8 100644 --- a/modular_chomp/code/datums/crafting/items.dm +++ b/modular_chomp/code/datums/crafting/items.dm @@ -173,7 +173,8 @@ /obj/item/material/sword/rapier/solar/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone) . = ..() - target.add_modifier(/datum/modifier/fire/weak, 12 SECONDS) //should be 12 damage? + target.adjust_fire_stacks(10) + target.ignite_mob() //Icicle /obj/item/gun/energy/icelauncher diff --git a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/ants.dm b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/ants.dm index 8dbee90bfe..fe4192d94c 100644 --- a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/ants.dm +++ b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/ants.dm @@ -360,7 +360,11 @@ ANT STRUCTURES /obj/effect/ant_structure/trap/burn icon_state = "burn_trap" - modifiertype = /datum/modifier/fire/weak + //No modifier. + +/obj/effect/ant_structure/trap/burn/attack_mob(mob/living/L) + L.adjust_fire_stacks(5) + L.ignite_mob() /obj/effect/ant_structure/trap/slowdown icon_state = "slow_trap" diff --git a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/oddities.dm b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/oddities.dm index 84017a664a..895462a4ee 100644 --- a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/oddities.dm +++ b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/animal/tyr/oddities.dm @@ -175,7 +175,8 @@ /mob/living/simple_mob/animal/tyr/explode_beetle/apply_melee_effects(var/atom/A) if(isliving(A)) var/mob/living/L = A - L.add_modifier(/datum/modifier/fire, 3 SECONDS) + L.adjust_fire_stacks(3) + L.ignite_mob() /mob/living/simple_mob/animal/tyr/glowing_beetle name = "glowing beetle" diff --git a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/humanoid/eclipse/heads.dm b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/humanoid/eclipse/heads.dm index baac16eb8a..78ce5c7886 100644 --- a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/humanoid/eclipse/heads.dm +++ b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/humanoid/eclipse/heads.dm @@ -53,11 +53,16 @@ armor_penetration = 40 //Large pointy crystal damage_type = BRUTE check_armour = "bullet" - modifier_type_to_apply = /datum/modifier/fire/weak - modifier_duration = 0.05 MINUTE range = 12 hud_state = "laser_sniper" +/obj/item/projectile/energy/flamecrystal/on_hit(atom/target, blocked = 0, def_zone) + . = ..() + if(isliving(target)) + var/mob/living/L = target + L.adjust_fire_stacks(5) + L.ignite_mob() + /obj/item/projectile/bullet/flamegun use_submunitions = 1 only_submunitions = 1 diff --git a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/feral.dm b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/feral.dm index 6fc0d2b184..8d26b98f9e 100644 --- a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/feral.dm +++ b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/feral.dm @@ -32,7 +32,8 @@ /mob/living/simple_mob/slime/feral/orange/apply_melee_effects(var/atom/A) if(isliving(A)) var/mob/living/L = A - L.add_modifier(/datum/modifier/fire, 5 SECONDS) + L.adjust_fire_stacks(5) + L.ignite_mob() /mob/living/simple_mob/slime/feral/blue diff --git a/modular_chomp/code/modules/projectiles/mob.dm b/modular_chomp/code/modules/projectiles/mob.dm index ec5d2598a7..70a65a1712 100644 --- a/modular_chomp/code/modules/projectiles/mob.dm +++ b/modular_chomp/code/modules/projectiles/mob.dm @@ -74,11 +74,16 @@ armor_penetration = 15 my_chems = list(REAGENT_ID_FUEL, REAGENT_ID_MOLD) flammability = 0.25 - modifier_type_to_apply = /datum/modifier/fire - modifier_duration = 6 SECONDS color = "#38b9ff" speed = 3.2 +/obj/item/projectile/energy/blob/moth/on_hit(atom/target, blocked = 0, def_zone) + . = ..() + if(isliving(target)) + var/mob/living/L = target + L.adjust_fire_stacks(10) + L.ignite_mob() + /obj/item/projectile/bullet/pistol/medium/ap/eclipse ricochets = 1 ricochets_max = 8 diff --git a/modular_chomp/code/modules/reagents/reagents/food_drinks.dm b/modular_chomp/code/modules/reagents/reagents/food_drinks.dm index 0b396ba5c6..267f60a96e 100644 --- a/modular_chomp/code/modules/reagents/reagents/food_drinks.dm +++ b/modular_chomp/code/modules/reagents/reagents/food_drinks.dm @@ -650,7 +650,7 @@ /datum/reagent/ethanol/mauna_loa/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) if(prob(10)) M.adjust_fire_stacks(5*removed) - M.IgniteMob() + M.ignite_mob() /datum/reagent/ethanol/hiveminderaser name = REAGENT_HIVEMINDERASER diff --git a/modular_chomp/code/modules/reagents/reagents/medicine.dm b/modular_chomp/code/modules/reagents/reagents/medicine.dm index 0b870fed48..b35048a50c 100644 --- a/modular_chomp/code/modules/reagents/reagents/medicine.dm +++ b/modular_chomp/code/modules/reagents/reagents/medicine.dm @@ -339,7 +339,7 @@ M.remove_a_modifier_of_type(/datum/modifier/grievous_wounds) M.remove_a_modifier_of_type(/datum/modifier/deep_wounds) M.remove_a_modifier_of_type(/datum/modifier/hivebot_weaken) - M.remove_a_modifier_of_type(/datum/modifier/fire) + M.extinguish_mob() M.remove_a_modifier_of_type(/datum/modifier/berserk_exhaustion) M.remove_a_modifier_of_type(/datum/modifier/entangled) M.remove_a_modifier_of_type(/datum/modifier/wizfire) diff --git a/vorestation.dme b/vorestation.dme index a4aea84647..6a1f84bbee 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -50,6 +50,7 @@ #include "code\__defines\admin_ch.dm" #include "code\__defines\admin_verb.dm" #include "code\__defines\airlock_control.dm" +#include "code\__defines\alerts.dm" #include "code\__defines\ammunition.dm" #include "code\__defines\appearance.dm" #include "code\__defines\assemblies.dm" @@ -141,6 +142,7 @@ #include "code\__defines\overlay_ch.dm" #include "code\__defines\overmap.dm" #include "code\__defines\paicard.dm" +#include "code\__defines\particles.dm" #include "code\__defines\pda.dm" #include "code\__defines\persistence.dm" #include "code\__defines\phobias.dm" @@ -188,6 +190,7 @@ #include "code\__defines\stack_trace.dm" #include "code\__defines\stat_tracking.dm" #include "code\__defines\statpanel.dm" +#include "code\__defines\status_effects.dm" #include "code\__defines\subsystems.dm" #include "code\__defines\supply.dm" #include "code\__defines\talksounds.dm" @@ -525,6 +528,7 @@ #include "code\controllers\subsystems\processing\instruments.dm" #include "code\controllers\subsystems\processing\obj.dm" #include "code\controllers\subsystems\processing\obj_tab_items.dm" +#include "code\controllers\subsystems\processing\priority_effects.dm" #include "code\controllers\subsystems\processing\processing.dm" #include "code\controllers\subsystems\processing\projectiles.dm" #include "code\controllers\subsystems\processing\turfs.dm" @@ -833,6 +837,10 @@ #include "code\datums\repositories\repository.dm" #include "code\datums\repositories\unique.dm" #include "code\datums\roundstats\_defines_local.dm" +#include "code\datums\status_effects\_status_effect.dm" +#include "code\datums\status_effects\_status_effect_helpers.dm" +#include "code\datums\status_effects\grouped_effect.dm" +#include "code\datums\status_effects\debuffs\fire_stacks.dm" #include "code\datums\supplypacks\atmospherics.dm" #include "code\datums\supplypacks\contraband.dm" #include "code\datums\supplypacks\costumes.dm" @@ -1468,8 +1476,10 @@ #include "code\game\objects\effects\misc.dm" #include "code\game\objects\effects\motion_echo.dm" #include "code\game\objects\effects\overlays.dm" +#include "code\game\objects\effects\particle_holder.dm" #include "code\game\objects\effects\portals.dm" #include "code\game\objects\effects\semirandom_mobs_vr.dm" +#include "code\game\objects\effects\shared_particle_holder.dm" #include "code\game\objects\effects\spiders.dm" #include "code\game\objects\effects\spiders_vr.dm" #include "code\game\objects\effects\step_triggers.dm" @@ -1510,6 +1520,9 @@ #include "code\game\objects\effects\map_effects\radiation_emitter.dm" #include "code\game\objects\effects\map_effects\screen_shaker.dm" #include "code\game\objects\effects\map_effects\sound_emitter.dm" +#include "code\game\objects\effects\particles\fire.dm" +#include "code\game\objects\effects\particles\smoke.dm" +#include "code\game\objects\effects\particles\water.dm" #include "code\game\objects\effects\prop\columnblast.dm" #include "code\game\objects\effects\prop\snake.dm" #include "code\game\objects\effects\spawners\bombspawner.dm" @@ -3278,7 +3291,6 @@ #include "code\modules\mob\_modifiers\cloning.dm" #include "code\modules\mob\_modifiers\crusher_mark.dm" #include "code\modules\mob\_modifiers\feysight.dm" -#include "code\modules\mob\_modifiers\fire.dm" #include "code\modules\mob\_modifiers\horror.dm" #include "code\modules\mob\_modifiers\medical.dm" #include "code\modules\mob\_modifiers\modifiers.dm" @@ -3332,6 +3344,7 @@ #include "code\modules\mob\living\living_defense.dm" #include "code\modules\mob\living\living_defines.dm" #include "code\modules\mob\living\living_defines_vr.dm" +#include "code\modules\mob\living\living_fire.dm" #include "code\modules\mob\living\living_movement.dm" #include "code\modules\mob\living\living_powers.dm" #include "code\modules\mob\living\living_vr.dm"