From 6e8299cdf3dfa510df7b2ede7cdd810e81d8fb24 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 22 Apr 2022 01:03:52 +0200 Subject: [PATCH] [MIRROR] Auto-docs + code improvements + splits up status_effect.dm [MDB IGNORE] (#12983) * Auto-docs + code improvements + splits up status_effect.dm (#66362) * Code improvements for status effects in general * Does this for now * Throws in a qdeleted check * A return * comment tweak * Missed some ref()s * Wrong var * Comment clarifications * Some more comment clarifications * Auto-docs + code improvements + splits up status_effect.dm Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/_onclick/click.dm | 7 +- code/datums/components/gunpoint.dm | 6 +- code/datums/status_effects/_status_effect.dm | 146 +++++++++ .../status_effects/_status_effect_helpers.dm | 95 ++++++ code/datums/status_effects/debuffs/debuffs.dm | 1 - code/datums/status_effects/grouped_effect.dm | 20 ++ code/datums/status_effects/limited_effect.dm | 20 ++ code/datums/status_effects/neutral.dm | 4 +- code/datums/status_effects/stacking_effect.dm | 139 ++++++++ code/datums/status_effects/status_effect.dm | 299 ------------------ code/modules/mob/living/living.dm | 15 +- tgstation.dme | 6 +- 12 files changed, 441 insertions(+), 317 deletions(-) create mode 100644 code/datums/status_effects/_status_effect.dm create mode 100644 code/datums/status_effects/_status_effect_helpers.dm create mode 100644 code/datums/status_effects/grouped_effect.dm create mode 100644 code/datums/status_effects/limited_effect.dm create mode 100644 code/datums/status_effects/stacking_effect.dm delete mode 100644 code/datums/status_effects/status_effect.dm diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 7126a3f8b21..325df268dd3 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -23,10 +23,9 @@ /mob/living/changeNext_move(num) var/mod = next_move_modifier var/adj = next_move_adjust - for(var/i in status_effects) - var/datum/status_effect/S = i - mod *= S.nextmove_modifier() - adj += S.nextmove_adjust() + for(var/datum/status_effect/effect as anything in status_effects) + mod *= effect.nextmove_modifier() + adj += effect.nextmove_adjust() next_move = world.time + ((num + adj)*mod) /** diff --git a/code/datums/components/gunpoint.dm b/code/datums/components/gunpoint.dm index 6b49174636a..9dbce3f879a 100644 --- a/code/datums/components/gunpoint.dm +++ b/code/datums/components/gunpoint.dm @@ -43,7 +43,7 @@ add_memory_in_range(target, 7, MEMORY_GUNPOINT, list(DETAIL_PROTAGONIST = target, DETAIL_DEUTERAGONIST = shooter, DETAIL_WHAT_BY = weapon), story_value = STORY_VALUE_OKAY, memory_flags = MEMORY_CHECK_BLINDNESS) shooter.apply_status_effect(/datum/status_effect/holdup, shooter) - target.apply_status_effect(/datum/status_effect/grouped/heldup, shooter) + target.apply_status_effect(/datum/status_effect/grouped/heldup, REF(shooter)) if(istype(weapon, /obj/item/gun/ballistic/rocketlauncher) && weapon.chambered) if(target.stat == CONSCIOUS && IS_NUKE_OP(shooter) && !IS_NUKE_OP(target) && (locate(/obj/item/disk/nuclear) in target.get_contents()) && shooter.client) @@ -58,7 +58,7 @@ /datum/component/gunpoint/Destroy(force, silent) var/mob/living/shooter = parent shooter.remove_status_effect(/datum/status_effect/holdup) - target.remove_status_effect(/datum/status_effect/grouped/heldup, shooter) + target.remove_status_effect(/datum/status_effect/grouped/heldup, REF(shooter)) SEND_SIGNAL(target, COMSIG_CLEAR_MOOD_EVENT, "gunpoint") return ..() @@ -125,7 +125,7 @@ /datum/component/gunpoint/proc/async_trigger_reaction() var/mob/living/shooter = parent shooter.remove_status_effect(/datum/status_effect/holdup) // try doing these before the trigger gets pulled since the target (or shooter even) may not exist after pulling the trigger, dig? - target.remove_status_effect(/datum/status_effect/grouped/heldup, shooter) + target.remove_status_effect(/datum/status_effect/grouped/heldup, REF(shooter)) SEND_SIGNAL(target, COMSIG_CLEAR_MOOD_EVENT, "gunpoint") if(point_of_no_return) diff --git a/code/datums/status_effects/_status_effect.dm b/code/datums/status_effects/_status_effect.dm new file mode 100644 index 00000000000..f760f106130 --- /dev/null +++ b/code/datums/status_effects/_status_effect.dm @@ -0,0 +1,146 @@ +/// 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 = -1 + /// When set initially / in on_creation, this is how long between [proc/tick] calls in deciseconds. + /// While processing, this becomes the world.time when the next tick will occur. + /// -1 = will stop processing, if duration is also unlimited (-1). + var/tick_interval = 1 SECONDS + /// The mob affected by the status effect. + var/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 + /// If defined, this text will appear when the mob is examined + /// To use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves + var/examine_text + /// 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 = /atom/movable/screen/alert/status_effect + /// The alert itself, created in [proc/on_creation] (if alert_type is specified). + var/atom/movable/screen/alert/status_effect/linked_alert + /// Used to define if the status effect should be using SSfastprocess or SSprocessing + var/processing_speed = STATUS_EFFECT_FAST_PROCESS + +/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) + + if(duration != -1) + duration = world.time + duration + tick_interval = world.time + tick_interval + + if(alert_type) + var/atom/movable/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 + + if(duration > 0 || initial(tick_interval) > 0) //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) + + 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(owner) + linked_alert = null + owner.clear_alert(id) + LAZYREMOVE(owner.status_effects, src) + on_remove() + owner = null + return ..() + +// Status effect process. Handles adjusting it's duration and ticks. +// If you're adding processed effects, put them in [proc/tick] +// instead of extending / overriding ththe process() proc. +/datum/status_effect/process(delta_time) + if(QDELETED(owner)) + qdel(src) + return + if(tick_interval < world.time) + tick() + tick_interval = world.time + initial(tick_interval) + if(duration != -1 && duration < world.time) + qdel(src) + +/// 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 + +/// Called every tick from process(). +/datum/status_effect/proc/tick() + 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() + 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 == -1) + 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 + +/// Alert base type for status effect alerts +/atom/movable/screen/alert/status_effect + name = "Curse of Mundanity" + desc = "You don't feel any different..." + /// The status effect we're linked to + var/datum/status_effect/attached_effect + +/atom/movable/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 00000000000..fdeffe89aeb --- /dev/null +++ b/code/datums/status_effects/_status_effect_helpers.dm @@ -0,0 +1,95 @@ + +// 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) + 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(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 it's typepath + * + * Returns an instance of a status effect, or NULL if none were found. + */ +/mob/living/proc/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 + +/** + * Reteurns 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 it's typepath + * + * Returns a list + */ +/mob/living/proc/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/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index e931b3b7b08..34d1ee854e5 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -212,7 +212,6 @@ /datum/status_effect/grouped/stasis id = "stasis" duration = -1 - tick_interval = 10 alert_type = /atom/movable/screen/alert/status_effect/stasis var/last_dead_time diff --git a/code/datums/status_effects/grouped_effect.dm b/code/datums/status_effects/grouped_effect.dm new file mode 100644 index 00000000000..ade0a187e0d --- /dev/null +++ b/code/datums/status_effects/grouped_effect.dm @@ -0,0 +1,20 @@ +/// Status effect from multiple sources, when all sources are removed, so is the effect +/datum/status_effect/grouped + // 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) + var/datum/status_effect/grouped/existing = new_owner.has_status_effect(type) + if(existing) + existing.sources |= source + qdel(src) + return FALSE + + sources |= source + return ..() + +/datum/status_effect/grouped/before_remove(source) + sources -= source + return !length(sources) diff --git a/code/datums/status_effects/limited_effect.dm b/code/datums/status_effects/limited_effect.dm new file mode 100644 index 00000000000..0f56e72da52 --- /dev/null +++ b/code/datums/status_effects/limited_effect.dm @@ -0,0 +1,20 @@ +/// These effects reapply their on_apply() effect when refreshed while stacks < max_stacks. +/datum/status_effect/limited_buff + id = "limited_buff" + duration = -1 + status_type = STATUS_EFFECT_REFRESH + ///How many stacks we currently have + var/stacks = 1 + ///How many stacks we can have maximum + var/max_stacks = 3 + +/datum/status_effect/limited_buff/refresh(effect) + if(stacks < max_stacks) + on_apply() + stacks++ + else + maxed_out() + +/// Called whenever the buff is refreshed when there are more stacks than max_stacks. +/datum/status_effect/limited_buff/proc/maxed_out() + return diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 11446b931c7..dfc8ed396b2 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -130,11 +130,11 @@ icon_state = "aimed" /datum/status_effect/grouped/heldup/on_apply() - owner.apply_status_effect(/datum/status_effect/grouped/surrender, src) + owner.apply_status_effect(/datum/status_effect/grouped/surrender, REF(src)) return ..() /datum/status_effect/grouped/heldup/on_remove() - owner.remove_status_effect(/datum/status_effect/grouped/surrender, src) + owner.remove_status_effect(/datum/status_effect/grouped/surrender, REF(src)) return ..() // holdup is for the person aiming diff --git a/code/datums/status_effects/stacking_effect.dm b/code/datums/status_effects/stacking_effect.dm new file mode 100644 index 00000000000..9896ef5ec70 --- /dev/null +++ b/code/datums/status_effects/stacking_effect.dm @@ -0,0 +1,139 @@ + +/// Status effects that can stack. +/datum/status_effect/stacking + id = "stacking_base" + duration = -1 // Only removed under specific conditions. + tick_interval = 1 SECONDS // Deciseconds between decays, once decay starts + alert_type = null + /// How many stacks are currently accumulated. + /// Also, the default stacks number given on application. + var/stacks = 0 + // Deciseconds until ticks start occuring, which removes stacks + /// (first stack will be removed at this time plus tick_interval) + var/delay_before_decay + /// How many stacks are lost per tick (decay trigger) + var/stack_decay = 1 + /// The threshold for having special effects occur when a certain stack number is reached + var/stack_threshold + /// The maximum number of stacks that can be applied + var/max_stacks + /// If TRUE, the status effect is consumed / removed when stack_threshold is met + var/consumed_on_threshold = TRUE + /// Set to true once the stack_threshold is crossed, and false once it falls back below + var/threshold_crossed = FALSE + + /// Icon file for overlays applied when the status effect is applied + var/overlay_file + /// Icon file for underlays applied when the status effect is applied + var/underlay_file + /// Icon state for overlays applied when the status effect is applied + /// States in the file must be given a name, followed by a number which corresponds to a number of stacks. + /// Put the state name without the number in these state vars + var/overlay_state + /// Icon state for underlays applied when the status effect is applied + /// The number is concatonated onto the string based on the number of stacks to get the correct state name. + var/underlay_state + /// A reference to our overlay appearance + var/mutable_appearance/status_overlay + /// A referenceto our underlay appearance + var/mutable_appearance/status_underlay + +/// Effects that occur when the stack count crosses stack_threshold +/datum/status_effect/stacking/proc/threshold_cross_effect() + return + +/// Effects that occur if the status effect is removed due to the stack_threshold being crossed +/datum/status_effect/stacking/proc/stacks_consumed_effect() + return + +/// Effects that occur if the status is removed due to being under 1 remaining stack +/datum/status_effect/stacking/proc/fadeout_effect() + return + +/// Runs every time tick(), causes stacks to decay over time +/datum/status_effect/stacking/proc/stack_decay_effect() + return + +/// Called when the stack_threshold is crossed (stacks go over the threshold) +/datum/status_effect/stacking/proc/on_threshold_cross() + threshold_cross_effect() + if(consumed_on_threshold) + stacks_consumed_effect() + qdel(src) + +/// Called when the stack_threshold is uncrossed / dropped (stacks go under the threshold after being over it) +/datum/status_effect/stacking/proc/on_threshold_drop() + return + +/// Whether the owner can have the status effect. +/// Return FALSE if the owner is not in a valid state (self-deletes the effect), or TRUE otherwise +/datum/status_effect/stacking/proc/can_have_status() + return owner.stat != DEAD + +/// Whether the owner can currently gain stacks or not +/// Return FALSE if the owner is not in a valid state, or TRUE otherwise +/datum/status_effect/stacking/proc/can_gain_stacks() + return owner.stat != DEAD + +/datum/status_effect/stacking/tick() + if(!can_have_status()) + qdel(src) + else + add_stacks(-stack_decay) + stack_decay_effect() + +/// Add (or remove) [stacks_added] stacks to our current stack count. +/datum/status_effect/stacking/proc/add_stacks(stacks_added) + if(stacks_added > 0 && !can_gain_stacks()) + return FALSE + owner.cut_overlay(status_overlay) + owner.underlays -= status_underlay + stacks += stacks_added + if(stacks > 0) + if(stacks >= stack_threshold && !threshold_crossed) //threshold_crossed check prevents threshold effect from occuring if changing from above threshold to still above threshold + threshold_crossed = TRUE + on_threshold_cross() + if(consumed_on_threshold) + return + else if(stacks < stack_threshold && threshold_crossed) + threshold_crossed = FALSE //resets threshold effect if we fall below threshold so threshold effect can trigger again + on_threshold_drop() + if(stacks_added > 0) + tick_interval += delay_before_decay //refreshes time until decay + stacks = min(stacks, max_stacks) + status_overlay.icon_state = "[overlay_state][stacks]" + status_underlay.icon_state = "[underlay_state][stacks]" + owner.add_overlay(status_overlay) + owner.underlays += status_underlay + else + fadeout_effect() + qdel(src) //deletes status if stacks fall under one + +/datum/status_effect/stacking/on_creation(mob/living/new_owner, stacks_to_apply) + . = ..() + if(.) + add_stacks(stacks_to_apply) + +/datum/status_effect/stacking/on_apply() + if(!can_have_status()) + return FALSE + status_overlay = mutable_appearance(overlay_file, "[overlay_state][stacks]") + status_underlay = mutable_appearance(underlay_file, "[underlay_state][stacks]") + var/icon/I = icon(owner.icon, owner.icon_state, owner.dir) + var/icon_height = I.Height() + status_overlay.pixel_x = -owner.pixel_x + status_overlay.pixel_y = FLOOR(icon_height * 0.25, 1) + status_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the status's overlay size based on the target's icon size + status_underlay.pixel_x = -owner.pixel_x + status_underlay.transform = matrix() * (icon_height/world.icon_size) * 3 + status_underlay.alpha = 40 + owner.add_overlay(status_overlay) + owner.underlays += status_underlay + return ..() + +/datum/status_effect/stacking/Destroy() + if(owner) + owner.cut_overlay(status_overlay) + owner.underlays -= status_underlay + QDEL_NULL(status_overlay) + return ..() diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm deleted file mode 100644 index eeedc36f999..00000000000 --- a/code/datums/status_effects/status_effect.dm +++ /dev/null @@ -1,299 +0,0 @@ -//Status effects are used to apply temporary or permanent effects to mobs. Mobs are aware of their status effects at all times. -//This file contains their code, plus code for applying and removing them. -//When making a new status effect, add a define to status_effects.dm in __DEFINES for ease of use! - -/datum/status_effect - var/id = "effect" //Used for screen alerts. - var/duration = -1 //How long the status effect lasts in DECISECONDS. Enter -1 for an effect that never ends unless removed through some means. - var/tick_interval = 10 //How many deciseconds between ticks, approximately. Leave at 10 for every second. Setting this to -1 will stop processing if duration is also unlimited. - var/mob/living/owner //The mob affected by the status effect. - var/status_type = STATUS_EFFECT_UNIQUE //How many of the effect can be on one mob, and what happens when you try to add another - var/on_remove_on_mob_delete = FALSE //if we call on_remove() when the mob is deleted - var/examine_text //If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves - var/alert_type = /atom/movable/screen/alert/status_effect //the alert thrown by the status effect, contains name and description - var/atom/movable/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists - ///Processing speed - used to define if the status effect should be using SSfastprocess or SSprocessing - var/processing_speed = STATUS_EFFECT_FAST_PROCESS - -/datum/status_effect/New(list/arguments) - on_creation(arglist(arguments)) - -/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) - if(duration != -1) - duration = world.time + duration - tick_interval = world.time + tick_interval - if(alert_type) - var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type) - A.attached_effect = src //so the alert can reference us, if it needs to - linked_alert = A //so we can reference the alert, if we need to - if(duration > 0 || initial(tick_interval) > 0) //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) - 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(owner) - linked_alert = null - owner.clear_alert(id) - LAZYREMOVE(owner.status_effects, src) - on_remove() - owner = null - return ..() - -/datum/status_effect/process() - if(!owner) - qdel(src) - return - if(tick_interval < world.time) - tick() - tick_interval = world.time + initial(tick_interval) - if(duration != -1 && duration < world.time) - qdel(src) - -/datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself. - return TRUE -/datum/status_effect/proc/tick() //Called every tick. -/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null -/datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted - owner.clear_alert(id) - LAZYREMOVE(owner.status_effects, src) - owner = null - qdel(src) - -/datum/status_effect/proc/before_remove() //! Called before being removed; returning FALSE will cancel removal - return TRUE - -/datum/status_effect/proc/refresh(effect, ...) - var/original_duration = initial(duration) - if(original_duration == -1) - return - duration = world.time + original_duration - -//clickdelay/nextmove modifiers! -/datum/status_effect/proc/nextmove_modifier() - return 1 - -/datum/status_effect/proc/nextmove_adjust() - return 0 - -//////////////// -// ALERT HOOK // -//////////////// - -/atom/movable/screen/alert/status_effect - name = "Curse of Mundanity" - desc = "You don't feel any different..." - var/datum/status_effect/attached_effect - -/atom/movable/screen/alert/status_effect/Destroy() - attached_effect = null //Don't keep a ref now - return ..() - -////////////////// -// HELPER PROCS // -////////////////// - -/mob/living/proc/apply_status_effect(effect, ...) //applies a given status effect to this mob, returning the effect if it was successful - . = FALSE - var/datum/status_effect/S1 = effect - LAZYINITLIST(status_effects) - var/list/arguments = args.Copy() - arguments[1] = src - for(var/datum/status_effect/S in status_effects) - if(S.id == initial(S1.id) && S.status_type) - if(S.status_type == STATUS_EFFECT_REPLACE) - S.be_replaced() - else if(S.status_type == STATUS_EFFECT_REFRESH) - S.refresh(arglist(arguments)) - return - else - return - S1 = new effect(arguments) - . = S1 - -/mob/living/proc/remove_status_effect(effect, ...) //removes all of a given status effect from this mob, returning TRUE if at least one was removed - . = FALSE - var/list/arguments = args.Copy(2) - if(status_effects) - var/datum/status_effect/S1 = effect - for(var/datum/status_effect/S in status_effects) - if(initial(S1.id) == S.id && S.before_remove(arguments)) - qdel(S) - . = TRUE - -/mob/living/proc/has_status_effect(effect) //returns the effect if the mob calling the proc owns the given status effect - . = FALSE - if(status_effects) - var/datum/status_effect/S1 = effect - for(var/datum/status_effect/S in status_effects) - if(initial(S1.id) == S.id) - return S - -/mob/living/proc/has_status_effect_list(effect) //returns a list of effects with matching IDs that the mod owns; use for effects there can be multiple of - . = list() - if(status_effects) - var/datum/status_effect/S1 = effect - for(var/datum/status_effect/S in status_effects) - if(initial(S1.id) == S.id) - . += S - -////////////////////// -// STACKING EFFECTS // -////////////////////// - -/datum/status_effect/stacking - id = "stacking_base" - duration = -1 //removed under specific conditions - alert_type = null - var/stacks = 0 //how many stacks are accumulated, also is # of stacks that target will have when first applied - var/delay_before_decay //deciseconds until ticks start occuring, which removes stacks (first stack will be removed at this time plus tick_interval) - tick_interval = 10 //deciseconds between decays once decay starts - var/stack_decay = 1 //how many stacks are lost per tick (decay trigger) - var/stack_threshold //special effects trigger when stacks reach this amount - var/max_stacks //stacks cannot exceed this amount - var/consumed_on_threshold = TRUE //if status should be removed once threshold is crossed - var/threshold_crossed = FALSE //set to true once the threshold is crossed, false once it falls back below - var/overlay_file - var/underlay_file - var/overlay_state // states in .dmi must be given a name followed by a number which corresponds to a number of stacks. put the state name without the number in these state vars - var/underlay_state // the number is concatonated onto the string based on the number of stacks to get the correct state name - var/mutable_appearance/status_overlay - var/mutable_appearance/status_underlay - -/datum/status_effect/stacking/proc/threshold_cross_effect() //what happens when threshold is crossed - -/datum/status_effect/stacking/proc/stacks_consumed_effect() //runs if status is deleted due to threshold being crossed - -/datum/status_effect/stacking/proc/fadeout_effect() //runs if status is deleted due to being under one stack - -/datum/status_effect/stacking/proc/stack_decay_effect() //runs every time tick() causes stacks to decay - -/datum/status_effect/stacking/proc/on_threshold_cross() - threshold_cross_effect() - if(consumed_on_threshold) - stacks_consumed_effect() - qdel(src) - -/datum/status_effect/stacking/proc/on_threshold_drop() - -/datum/status_effect/stacking/proc/can_have_status() - return owner.stat != DEAD - -/datum/status_effect/stacking/proc/can_gain_stacks() - return owner.stat != DEAD - -/datum/status_effect/stacking/tick() - if(!can_have_status()) - qdel(src) - else - add_stacks(-stack_decay) - stack_decay_effect() - -/datum/status_effect/stacking/proc/add_stacks(stacks_added) - if(stacks_added > 0 && !can_gain_stacks()) - return FALSE - owner.cut_overlay(status_overlay) - owner.underlays -= status_underlay - stacks += stacks_added - if(stacks > 0) - if(stacks >= stack_threshold && !threshold_crossed) //threshold_crossed check prevents threshold effect from occuring if changing from above threshold to still above threshold - threshold_crossed = TRUE - on_threshold_cross() - if(consumed_on_threshold) - return - else if(stacks < stack_threshold && threshold_crossed) - threshold_crossed = FALSE //resets threshold effect if we fall below threshold so threshold effect can trigger again - on_threshold_drop() - if(stacks_added > 0) - tick_interval += delay_before_decay //refreshes time until decay - stacks = min(stacks, max_stacks) - status_overlay.icon_state = "[overlay_state][stacks]" - status_underlay.icon_state = "[underlay_state][stacks]" - owner.add_overlay(status_overlay) - owner.underlays += status_underlay - else - fadeout_effect() - qdel(src) //deletes status if stacks fall under one - -/datum/status_effect/stacking/on_creation(mob/living/new_owner, stacks_to_apply) - . = ..() - if(.) - add_stacks(stacks_to_apply) - -/datum/status_effect/stacking/on_apply() - if(!can_have_status()) - return FALSE - status_overlay = mutable_appearance(overlay_file, "[overlay_state][stacks]") - status_underlay = mutable_appearance(underlay_file, "[underlay_state][stacks]") - var/icon/I = icon(owner.icon, owner.icon_state, owner.dir) - var/icon_height = I.Height() - status_overlay.pixel_x = -owner.pixel_x - status_overlay.pixel_y = FLOOR(icon_height * 0.25, 1) - status_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the status's overlay size based on the target's icon size - status_underlay.pixel_x = -owner.pixel_x - status_underlay.transform = matrix() * (icon_height/world.icon_size) * 3 - status_underlay.alpha = 40 - owner.add_overlay(status_overlay) - owner.underlays += status_underlay - return ..() - -/datum/status_effect/stacking/Destroy() - if(owner) - owner.cut_overlay(status_overlay) - owner.underlays -= status_underlay - QDEL_NULL(status_overlay) - return ..() - -/// Status effect from multiple sources, when all sources are removed, so is the effect -/datum/status_effect/grouped - status_type = STATUS_EFFECT_MULTIPLE //! Adds itself to sources and destroys itself if one exists already, there are never multiple - var/list/sources = list() - -/datum/status_effect/grouped/on_creation(mob/living/new_owner, source) - var/datum/status_effect/grouped/existing = new_owner.has_status_effect(type) - if(existing) - existing.sources |= source - qdel(src) - return FALSE - else - sources |= source - return ..() - -/datum/status_effect/grouped/before_remove(source) - sources -= source - return !length(sources) - -//These effects reapplies their on_apply() effect when refreshed while stacks < max_stacks -/datum/status_effect/limited_buff - id = "limited_buff" - duration = -1 - status_type = STATUS_EFFECT_REFRESH - ///How many stacks we currently have - var/stacks = 1 - ///How many stacks we can have maximum - var/max_stacks = 3 - -/datum/status_effect/limited_buff/refresh(effect) - if(stacks < max_stacks) - on_apply() - stacks++ - else - maxed_out() - -/datum/status_effect/limited_buff/proc/maxed_out() - return diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index a0ea323e3af..d5cb96d830f 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -25,13 +25,14 @@ med_hud_set_status() /mob/living/Destroy() - if(LAZYLEN(status_effects)) - for(var/s in status_effects) - var/datum/status_effect/S = s - if(S.on_remove_on_mob_delete) //the status effect calls on_remove when its mob is deleted - qdel(S) - else - S.be_replaced() + for(var/datum/status_effect/effect as anything in status_effects) + // The status effect calls on_remove when its mob is deleted + if(effect.on_remove_on_mob_delete) + qdel(effect) + + else + effect.be_replaced() + if(ranged_ability) ranged_ability.remove_ranged_ability(src) if(buckled) diff --git a/tgstation.dme b/tgstation.dme index dfd398fa9dc..0ace8e55b6f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1164,12 +1164,16 @@ #include "code\datums\station_traits\negative_traits.dm" #include "code\datums\station_traits\neutral_traits.dm" #include "code\datums\station_traits\positive_traits.dm" +#include "code\datums\status_effects\_status_effect.dm" +#include "code\datums\status_effects\_status_effect_helpers.dm" #include "code\datums\status_effects\agent_pinpointer.dm" #include "code\datums\status_effects\buffs.dm" #include "code\datums\status_effects\drug_effects.dm" #include "code\datums\status_effects\gas.dm" +#include "code\datums\status_effects\grouped_effect.dm" +#include "code\datums\status_effects\limited_effect.dm" #include "code\datums\status_effects\neutral.dm" -#include "code\datums\status_effects\status_effect.dm" +#include "code\datums\status_effects\stacking_effect.dm" #include "code\datums\status_effects\wound_effects.dm" #include "code\datums\status_effects\debuffs\debuffs.dm" #include "code\datums\status_effects\debuffs\speech_debuffs.dm"