/// 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 /// 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 /// 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 ///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 /// A particle effect, for things like embers - Should be set on update_particles() var/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_POST_FULLY_HEAL, PROC_REF(remove_effect_on_heal)) 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) update_particles() 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() UnregisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL) owner = null if(particle_effect) QDEL_NULL(particle_effect) return ..() // 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(delta_time, times_fired) SHOULD_NOT_OVERRIDE(TRUE) if(QDELETED(owner)) qdel(src) return if(tick_interval <= world.time) tick(delta_time, times_fired) 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 /// 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(). /datum/status_effect/proc/tick(delta_time, times_fired) 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 == -1) return duration = world.time + original_duration /// Adds nextmove modifier multiplicatively to the owner while applied /datum/status_effect/proc/nextmove_modifier() return 1 /datum/status_effect/proc/interact_speed_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 == -1) // Infinite duration return FALSE duration -= seconds if(duration <= world.time) qdel(src) return TRUE return FALSE /** * Updates the particles for the status effects * Should be handled by subtypes! */ /datum/status_effect/proc/update_particles() SHOULD_CALL_PARENT(FALSE) //////////////// // 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 ////////////////// // HELPER PROCS // ////////////////// /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 /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/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 /** * 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 it's 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