diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm new file mode 100644 index 000000000000..b4fc5e04c9c7 --- /dev/null +++ b/code/__DEFINES/alerts.dm @@ -0,0 +1,7 @@ +#define ALERT_BUCKLED "buckled" +#define ALERT_HANDCUFFED "handcuffed" +#define ALERT_LEGCUFFED "legcuffed" +#define ALERT_FLOORED "floored" +#define ALERT_INCAPACITATED "incapacitated" +#define ALERT_KNOCKEDOUT "knockedout" +#define ALERT_IMMOBILIZED "immobilized" diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_living.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_living.dm index 56cd4dd8cd8e..89f3951e7c99 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_living.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_living.dm @@ -23,11 +23,6 @@ #define COMSIG_LIVING_SPEAK "living_speak" #define COMPONENT_OVERRIDE_SPEAK (1<<0) -#define COMSIG_LIVING_APPLY_EFFECT "living_apply_effect" -#define COMSIG_LIVING_ADJUST_EFFECT "living_adjust_effect" -#define COMSIG_LIVING_SET_EFFECT "living_set_effect" - #define COMPONENT_CANCEL_EFFECT (1<<0) - /// From /obj/structure/proc/do_climb(var/mob/living/user, mods) #define COMSIG_LIVING_CLIMB_STRUCTURE "climb_over_structure" /// From /mob/living/Collide(): (atom/A) diff --git a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm index f4beec321c9e..bfb62c2bcf6e 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm @@ -95,6 +95,9 @@ #define COMSIG_MOB_MOVE_OR_LOOK "mob_move_or_look" #define COMPONENT_OVERRIDE_MOB_MOVE_OR_LOOK (1<<0) +///from rejuv +#define COMSIG_LIVING_POST_FULLY_HEAL "living_post_fully_heal" + ///from /mob/living/emote(): () #define COMSIG_MOB_EMOTE "mob_emote" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 0dcd26de3e3a..e50d9e72497c 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -1,3 +1,6 @@ +/// Multiplier for Stun/KD/KO/etc durations in new backend, due to old system being based on life ticks +#define GLOBAL_STATUS_MULTIPLIER 20 // each in-code unit is worth 20ds of duration + #define HEALTH_THRESHOLD_DEAD -100 #define HEALTH_THRESHOLD_CRIT -50 diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm new file mode 100644 index 000000000000..ecccbd40abeb --- /dev/null +++ b/code/__DEFINES/status_effects.dm @@ -0,0 +1,25 @@ +///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 + +///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 + +//Incapacitated status effect flags +/// If the incapacitated status effect will ignore a mob in restraints (handcuffs) +#define IGNORE_RESTRAINTS (1<<0) +/// If the incapacitated status effect will ignore a mob in stasis (stasis beds) +#define IGNORE_STASIS (1<<1) +/// If the incapacitated status effect will ignore a mob being agressively grabbed +#define IGNORE_GRAB (1<<2) + +/// Time threshold after which we launch ending timer - this should be higher than the slowest processing rate +#define STATUS_EFFECT_TIME_THRESHOLD (2 SECONDS) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 6af4a3585e29..9cb67e1e0de1 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -179,9 +179,11 @@ #define SS_PRIORITY_FAST_OBJECTS 105 #define SS_PRIORITY_OBJECTS 104 #define SS_PRIORITY_DECORATOR 99 +#define SS_PRIORITY_EFFECTS 97 +#define SS_PRIORITY_FASTEFFECTS 96 #define SS_PRIORITY_HIJACK 97 #define SS_PRIORITY_POWER 95 -#define SS_PRIORITY_EFFECTS 92 +#define SS_PRIORITY_OLDEFFECTS 92 #define SS_PRIORITY_MACHINERY 90 #define SS_PRIORITY_FZ_TRANSITIONS 88 #define SS_PRIORITY_ROUND_RECORDING 83 diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 215e228fdd9d..f5f61424daac 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -230,6 +230,7 @@ hud_version = display_hud_version persistent_inventory_update(screenmob) mymob.update_action_buttons(TRUE) + reorganize_alerts(screenmob) mymob.reload_fullscreens() // ensure observers get an accurate and up-to-date view diff --git a/code/controllers/subsystem/processing/effects.dm b/code/controllers/subsystem/processing/effects.dm index 5dc9c5f7b9c2..095d557c1ad3 100644 --- a/code/controllers/subsystem/processing/effects.dm +++ b/code/controllers/subsystem/processing/effects.dm @@ -1,5 +1,4 @@ PROCESSING_SUBSYSTEM_DEF(effects) name = "Effects" wait = 1 SECONDS - flags = SS_NO_INIT | SS_KEEP_TIMING priority = SS_PRIORITY_EFFECTS diff --git a/code/controllers/subsystem/processing/fasteffects.dm b/code/controllers/subsystem/processing/fasteffects.dm new file mode 100644 index 000000000000..29d3857916f9 --- /dev/null +++ b/code/controllers/subsystem/processing/fasteffects.dm @@ -0,0 +1,4 @@ +PROCESSING_SUBSYSTEM_DEF(fasteffects) + name = "Fast Effects" + wait = 0.2 SECONDS + priority = SS_PRIORITY_FASTEFFECTS diff --git a/code/controllers/subsystem/processing/oldeffects.dm b/code/controllers/subsystem/processing/oldeffects.dm new file mode 100644 index 000000000000..d2b217f5fc9d --- /dev/null +++ b/code/controllers/subsystem/processing/oldeffects.dm @@ -0,0 +1,5 @@ +PROCESSING_SUBSYSTEM_DEF(oldeffects) + name = "Old Effects" + wait = 1 SECONDS + flags = SS_NO_INIT | SS_KEEP_TIMING + priority = SS_PRIORITY_OLDEFFECTS diff --git a/code/datums/ammo/ammo.dm b/code/datums/ammo/ammo.dm index cff78f5fb553..48a387e54d20 100644 --- a/code/datums/ammo/ammo.dm +++ b/code/datums/ammo/ammo.dm @@ -164,7 +164,8 @@ /datum/ammo/proc/knockback_effects(mob/living/living_mob, obj/projectile/fired_projectile) if(iscarbonsizexeno(living_mob)) var/mob/living/carbon/xenomorph/target = living_mob - target.apply_effect(0.7, WEAKEN) // 0.9 seconds of stun, per agreement from Balance Team when switched from MC stuns to exact stuns + target.Stun(0.7) // Previous comment said they believed 0.7 was 0.9s and that the balance team approved this. Geez... + target.KnockDown(0.7) target.apply_effect(1, SUPERSLOW) target.apply_effect(2, SLOW) to_chat(target, SPAN_XENODANGER("You are shaken by the sudden impact!")) diff --git a/code/datums/ammo/bullet/rifle.dm b/code/datums/ammo/bullet/rifle.dm index b6085572e3b9..0be6f1db8ff4 100644 --- a/code/datums/ammo/bullet/rifle.dm +++ b/code/datums/ammo/bullet/rifle.dm @@ -175,7 +175,8 @@ if(iscarbonsizexeno(living_mob)) var/mob/living/carbon/xenomorph/target = living_mob to_chat(target, SPAN_XENODANGER("You are shaken and slowed by the sudden impact!")) - target.apply_effect(0.5, WEAKEN) + target.KnockDown(0.5) // purely for visual effect, noone actually cares + target.Stun(0.5) target.apply_effect(2, SUPERSLOW) target.apply_effect(5, SLOW) else diff --git a/code/datums/ammo/bullet/shotgun.dm b/code/datums/ammo/bullet/shotgun.dm index 77e1e6401472..96ac4cb6ba04 100644 --- a/code/datums/ammo/bullet/shotgun.dm +++ b/code/datums/ammo/bullet/shotgun.dm @@ -25,7 +25,8 @@ if(iscarbonsizexeno(living_mob)) var/mob/living/carbon/xenomorph/target = living_mob to_chat(target, SPAN_XENODANGER("You are shaken and slowed by the sudden impact!")) - target.apply_effect(0.5, WEAKEN) + target.KnockDown(0.5) // If you ask me the KD should be left out, but players like their visual cues + target.Stun(0.5) target.apply_effect(1, SUPERSLOW) target.apply_effect(3, SLOW) else @@ -249,7 +250,8 @@ if(iscarbonsizexeno(living_mob)) var/mob/living/carbon/xenomorph/target = living_mob to_chat(target, SPAN_XENODANGER("You are shaken and slowed by the sudden impact!")) - target.apply_effect(0.5, WEAKEN) + target.KnockDown(0.5) // If you ask me the KD should be left out, but players like their visual cues + target.Stun(0.5) target.apply_effect(2, SUPERSLOW) target.apply_effect(5, SLOW) else @@ -338,7 +340,8 @@ return shake_camera(M, 3, 4) - M.apply_effect(2, WEAKEN) + M.KnockDown(2) // If you ask me the KD should be left out, but players like their visual cues + M.Stun(2) M.apply_effect(4, SLOW) if(iscarbonsizexeno(M)) to_chat(M, SPAN_XENODANGER("The impact knocks you off your feet!")) @@ -351,7 +354,8 @@ if(iscarbonsizexeno(living_mob)) var/mob/living/carbon/xenomorph/target = living_mob to_chat(target, SPAN_XENODANGER("You are shaken and slowed by the sudden impact!")) - target.apply_effect(0.5, WEAKEN) + target.KnockDown(0.5) // If you ask me the KD should be left out, but players like their visual cues + target.Stun(0.5) target.apply_effect(2, SUPERSLOW) target.apply_effect(5, SLOW) else diff --git a/code/datums/ammo/energy.dm b/code/datums/ammo/energy.dm index 01c69ffa0015..27d2b7d4e0c5 100644 --- a/code/datums/ammo/energy.dm +++ b/code/datums/ammo/energy.dm @@ -103,8 +103,8 @@ icon_state = "shrapnel_plasma" damage_type = BURN -/datum/ammo/bullet/shrapnel/plasma/on_hit_mob(mob/hit_mob, obj/projectile/hit_projectile) - hit_mob.apply_effect(2, WEAKEN) +/datum/ammo/bullet/shrapnel/plasma/on_hit_mob(mob/living/hit_mob, obj/projectile/hit_projectile) + hit_mob.Stun(2) /datum/ammo/energy/yautja/caster name = "root caster bolt" @@ -141,12 +141,8 @@ log_attack("[key_name(C)] was stunned by a high power stun bolt from [key_name(P.firer)] at [get_area(P)]") if(ishuman(C)) - var/mob/living/carbon/human/H = C stun_time++ - H.apply_effect(stun_time, WEAKEN) - else - M.apply_effect(stun_time, WEAKEN) - + C.apply_effect(stun_time, WEAKEN) C.apply_effect(stun_time, STUN) ..() @@ -217,12 +213,7 @@ continue to_chat(M, SPAN_DANGER("A powerful electric shock ripples through your body, freezing you in place!")) M.apply_effect(stun_time, STUN) - - if (ishuman(M)) - var/mob/living/carbon/human/H = M - H.apply_effect(stun_time, WEAKEN) - else - M.apply_effect(stun_time, WEAKEN) + M.apply_effect(stun_time, WEAKEN) /datum/ammo/energy/yautja/rifle/bolt name = "plasma rifle bolt" diff --git a/code/datums/ammo/rocket.dm b/code/datums/ammo/rocket.dm index 52914f745110..66a9f65bdcdd 100644 --- a/code/datums/ammo/rocket.dm +++ b/code/datums/ammo/rocket.dm @@ -65,7 +65,6 @@ /datum/ammo/rocket/ap/on_hit_mob(mob/M, obj/projectile/P) var/turf/T = get_turf(M) M.ex_act(150, P.dir, P.weapon_cause_data, 100) - M.apply_effect(2, WEAKEN) M.apply_effect(2, PARALYZE) if(ishuman_strict(M)) // No yautya or synths. Makes humans gib on direct hit. M.ex_act(300, P.dir, P.weapon_cause_data, 100) @@ -84,7 +83,6 @@ var/hit_something = 0 for(var/mob/M in T) M.ex_act(150, P.dir, P.weapon_cause_data, 100) - M.apply_effect(4, WEAKEN) M.apply_effect(4, PARALYZE) hit_something = 1 continue diff --git a/code/datums/ammo/xeno.dm b/code/datums/ammo/xeno.dm index 9ecc9ebf9321..654ab88c7abc 100644 --- a/code/datums/ammo/xeno.dm +++ b/code/datums/ammo/xeno.dm @@ -49,8 +49,9 @@ if(!isxeno(M)) if(insta_neuro) - if(M.GetKnockDownValueNotADurationDoNotUse() < 3) // If they have less than somewhere random between 4 and 6 seconds KD left and assuming it doesnt get refreshed itnernally - M.adjust_effect(1 * power, WEAKEN) + if(M.GetKnockDownDuration() < 3) // Why are you not using KnockDown(3) ? Do you even know 3 is SIX seconds ? So many questions left unanswered. + M.KnockDown(power) + M.Stun(power) return if(ishuman(M)) @@ -65,8 +66,9 @@ no_clothes_neuro = TRUE if(no_clothes_neuro) - if(M.GetKnockDownValueNotADurationDoNotUse() < 5) // If they have less than somewhere random between 8 and 10 seconds KD left and assuming it doesnt get refreshed itnernally - M.adjust_effect(1 * power, WEAKEN) // KD them a bit more + if(M.GetKnockDownDuration() < 5) // Nobody actually knows what this means. Supposedly it means less than 10 seconds. Frankly if you get locked into 10s of knockdown to begin with there are bigger issues. + M.KnockDown(power) + M.Stun(power) M.visible_message(SPAN_DANGER("[M] falls prone.")) /proc/apply_scatter_neuro(mob/living/M) @@ -79,9 +81,9 @@ H.visible_message(SPAN_DANGER("[M] shrugs off the neurotoxin!")) return - if(M.GetKnockDownValueNotADurationDoNotUse() < 0.7) // basically (knocked_down && prob(90)) - M.apply_effect(0.7, WEAKEN) - M.visible_message(SPAN_DANGER("[M] falls prone.")) + M.KnockDown(0.7) // Completely arbitrary values from another time where stun timers incorrectly stacked. Kill as needed. + M.Stun(0.7) + M.visible_message(SPAN_DANGER("[M] falls prone.")) /datum/ammo/xeno/toxin/on_hit_mob(mob/M,obj/projectile/P) if(ishuman(M)) diff --git a/code/datums/effects/_effects.dm b/code/datums/effects/_effects.dm index 932dc44954fc..ea6823574f54 100644 --- a/code/datums/effects/_effects.dm +++ b/code/datums/effects/_effects.dm @@ -41,7 +41,7 @@ if(!validate_atom(thing) || QDELETED(thing)) qdel(src) return - START_PROCESSING(SSeffects, src) + START_PROCESSING(SSoldeffects, src) affected_atom = thing LAZYADD(affected_atom.effects_list, src) @@ -118,7 +118,7 @@ if(affected_atom) LAZYREMOVE(affected_atom.effects_list, src) affected_atom = null - STOP_PROCESSING(SSeffects, src) + STOP_PROCESSING(SSoldeffects, src) . = ..() diff --git a/code/datums/status_effects/_status_effect.dm b/code/datums/status_effects/_status_effect.dm new file mode 100644 index 000000000000..ddbd6366d98c --- /dev/null +++ b/code/datums/status_effects/_status_effect.dm @@ -0,0 +1,277 @@ +/// 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_PROTECTED/duration = -1 + /// Truthy once duration is initialized + VAR_PRIVATE/duration_set = FALSE + /// 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 + /// 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 SSfasteffects or SSeffects + var/processing_speed = STATUS_EFFECT_FAST_PROCESS + /// Do we self-terminate when a fullheal is called? // CM note: this is rejuvenate + var/remove_on_fullheal = FALSE + + /* Unimplemented feature: Our Rejuv needs refactoring to work with this + /// If remove_on_fullheal is TRUE, what flag do we need to be removed? + var/heal_flag_necessary = HEAL_STATUS + */ + + /* Particle effects feature was cut due to lacking backend, feel free to add when we have backend */ + + /// Timer ID for triggering the effect end precisely + var/timerid + +/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, ...) + SHOULD_NOT_SLEEP(TRUE) // Don't sleep between duration_set and update_timer + 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_REJUVENATED, PROC_REF(remove_effect_on_heal)) + + if(duration != -1) + duration = world.time + duration + duration_set = TRUE + if(tick_interval != -1) + 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 > world.time || tick_interval > world.time) //don't process if we don't care + switch(processing_speed) + if(STATUS_EFFECT_FAST_PROCESS) + START_PROCESSING(SSfasteffects, src) + if(STATUS_EFFECT_NORMAL_PROCESS) + START_PROCESSING(SSeffects, src) + update_timer() + + update_particles() + + return TRUE + +/datum/status_effect/Destroy() + if(timerid) + deltimer(timerid) + switch(processing_speed) + if(STATUS_EFFECT_FAST_PROCESS) + STOP_PROCESSING(SSfasteffects, src) + if(STATUS_EFFECT_NORMAL_PROCESS) + STOP_PROCESSING(SSeffects, src) + if(owner) + linked_alert = null + owner.clear_alert(id) + LAZYREMOVE(owner.status_effects, src) + on_remove() + UnregisterSignal(owner, COMSIG_LIVING_REJUVENATED) + owner = null + 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(seconds_per_tick) + SHOULD_NOT_OVERRIDE(TRUE) + if(QDELETED(owner)) + qdel(src) + return + if(tick_interval != -1 && tick_interval < world.time) + var/tick_length = 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 + + // Timer and update procs should basically always handle this, it's a safety net + if(!timerid && duration != -1 && duration < world.time) + qdel(src) + else + update_timer() // Attempt to start up end timer + +/// Updates the timer used for precisely ending the effect +/// We force_refresh if the duration changed otherwise than ticking down +/datum/status_effect/proc/update_timer(force_refresh = FALSE) + if(duration == -1 || duration <= world.time) // infinite or expired + return + else if(duration - world.time <= STATUS_EFFECT_TIME_THRESHOLD) + if(!timerid || force_refresh) + timerid = addtimer(CALLBACK(src, PROC_REF(timer_callback)), duration - world.time, TIMER_OVERRIDE|TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_NO_HASH_WAIT) + else if(timerid) + deltimer(timerid) + timerid = null + +/// Timer invocation callback to end the effect +/datum/status_effect/proc/timer_callback() + if(timerid) + timerid = null + qdel(src) // shrimple as that + +/// Called when 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 == -1) + return + duration = world.time + original_duration + update_timer(force_refresh = TRUE) + +/// 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_REJUVENATED] 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) + qdel(src) + +/// Updates the duration of the status effect to the given [amount] of deciseconds from now, qdeling / ending if we eclipse the current world time. +/// If increment is truthy, we only update if the resulting amount is higher. +/datum/status_effect/proc/update_duration(amount, increment = FALSE) + if(!duration_set) // Barebones setter for before we start everything up + if(increment) + duration = max(duration, amount) + else + duration = amount + return FALSE + if(duration == -1) // Infinite duration + return FALSE + var/new_duration = world.time + amount + if(increment && duration >= new_duration) + return FALSE + duration = new_duration + if(duration <= world.time) + qdel(src) + return TRUE + update_timer(force_refresh = TRUE) + return FALSE + +/// Updates the duration of the status effect to the given [amount] of deciseconds from its current set ending +/datum/status_effect/proc/adjust_duration(amount) + if(!duration_set) + duration += amount + return FALSE + if(duration == -1) + return FALSE + var/remaining = duration - world.time + remaining += amount + return update_duration(remaining) + +/// Remove [amount] of duration (in deciseconds) from the status effect. Compatibility handler with /tg/. +/datum/status_effect/proc/remove_duration(amount) + adjust_duration(-amount) + +/// Get duration left on the effect +/datum/status_effect/proc/get_duration_left() + if(!duration_set) + return -1 + var/remaining = duration - world.time + if(remaining < 0) + return -1 + return remaining + + +/** + * Updates the particles for the status effects + * Should be handled by subtypes! + */ + +/datum/status_effect/proc/update_particles() + SHOULD_CALL_PARENT(FALSE) + +/// 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 000000000000..0ee952200610 --- /dev/null +++ b/code/datums/status_effects/_status_effect_helpers.dm @@ -0,0 +1,136 @@ + +// 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(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 + +/** + * 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 it's 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 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 diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm new file mode 100644 index 000000000000..a36b7b91e4c6 --- /dev/null +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -0,0 +1,104 @@ +//Largely negative status effects go here, even if they have small benificial effects +//STUN EFFECTS +/datum/status_effect/incapacitating + tick_interval = -1 + status_type = STATUS_EFFECT_REPLACE + alert_type = null + remove_on_fullheal = TRUE +// heal_flag_necessary = HEAL_CC_STATUS + var/needs_update_stat = FALSE + +/datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration) + if(isnum(set_duration)) + update_duration(set_duration) + . = ..() + if(. && needs_update_stat) + owner.update_stat() + + +/datum/status_effect/incapacitating/on_remove() + if(needs_update_stat ) //silicons need stat updates in addition to normal canmove updates + owner.update_stat() + return ..() + +//STUN +/datum/status_effect/incapacitating/stun + id = "stun" +// alert_type = /atom/movable/screen/alert/status_effect/stun + +/datum/status_effect/incapacitating/stun/on_apply() + . = ..() + if(!.) + return + owner.add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED /*, TRAIT_HANDS_BLOCKED*/), TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/stun/on_remove() + owner.remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED /*, TRAIT_HANDS_BLOCKED*/), TRAIT_STATUS_EFFECT(id)) + return ..() + +/atom/movable/screen/alert/status_effect/stun + name = "Stunned" + desc = "You are incapacitated. You may not move or act." + icon_state = ALERT_INCAPACITATED + + +//KNOCKDOWN +/datum/status_effect/incapacitating/knockdown + id = "knockdown" +// alert_type = /atom/movable/screen/alert/status_effect/knockdown + +/datum/status_effect/incapacitating/knockdown/on_apply() + . = ..() + if(!.) + return + owner.add_traits(list(TRAIT_FLOORED, TRAIT_IMMOBILIZED), TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/knockdown/on_remove() + owner.remove_traits(list(TRAIT_FLOORED, TRAIT_IMMOBILIZED), TRAIT_STATUS_EFFECT(id)) + return ..() + +/atom/movable/screen/alert/status_effect/knockdown + name = "Floored" + desc = "You can't stand up!" + icon_state = ALERT_FLOORED + +//IMMOBILIZED +/datum/status_effect/incapacitating/immobilized + id = "immobilized" +// alert_type = /atom/movable/screen/alert/status_effect/immobilized + +/datum/status_effect/incapacitating/immobilized/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/immobilized/on_remove() + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + return ..() + +/atom/movable/screen/alert/status_effect/immobilized + name = "Immobilized" + desc = "You can't move." + icon_state = ALERT_IMMOBILIZED + +//UNCONSCIOUS +/datum/status_effect/incapacitating/unconscious + id = "unconscious" + needs_update_stat = TRUE +// alert_type = /atom/movable/screen/alert/status_effect/unconscious + +/datum/status_effect/incapacitating/unconscious/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/unconscious/on_remove() + REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + return ..() + +/atom/movable/screen/alert/status_effect/unconscious + name = "Unconscious" + desc = "You've been knocked out." + icon_state = ALERT_KNOCKEDOUT diff --git a/code/datums/status_effects/grouped_effect.dm b/code/datums/status_effects/grouped_effect.dm new file mode 100644 index 000000000000..ade0a187e0db --- /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 000000000000..0f56e72da52f --- /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/stacking_effect.dm b/code/datums/status_effects/stacking_effect.dm new file mode 100644 index 000000000000..3ef5855938f7 --- /dev/null +++ b/code/datums/status_effects/stacking_effect.dm @@ -0,0 +1,101 @@ +/// 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 + +/* This implementation is missing effects overlays because we did not have + /tg/ overlays backend available at the time. Feel free to add them when we do! */ + +/// 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(seconds_between_ticks) + 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 + 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) + 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 + return ..() + diff --git a/code/game/gamemodes/colonialmarines/huntergames.dm b/code/game/gamemodes/colonialmarines/huntergames.dm index aad0f9ba5787..310785070458 100644 --- a/code/game/gamemodes/colonialmarines/huntergames.dm +++ b/code/game/gamemodes/colonialmarines/huntergames.dm @@ -244,7 +244,8 @@ H.skills = null //no restriction on what the contestants can do - H.apply_effect(15, WEAKEN) + H.KnockDown(15) + H.Stun(15) H.nutrition = NUTRITION_NORMAL var/randjob = rand(0,10) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index b03ba1e8e195..332d9b96bd44 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -858,7 +858,8 @@ GLOBAL_LIST_INIT(airlock_wire_descriptions, list( var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread sparks.set_up(5, 1, src) sparks.start() - xeno.apply_effect(1, WEAKEN) + xeno.KnockDown(1) + xeno.Stun(1) playsound(src, 'sound/effects/metalhit.ogg', 50, TRUE) xeno.visible_message(SPAN_XENOWARNING("\The [xeno] strikes \the [src] with its tail!"), SPAN_XENOWARNING("You strike \the [src] with your tail!")) diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm index fc2cf5a6320c..437ef7b067ea 100644 --- a/code/game/machinery/flasher.dm +++ b/code/game/machinery/flasher.dm @@ -60,7 +60,7 @@ src.last_flash = world.time use_power(1500) - for (var/mob/O in viewers(src, null)) + for (var/mob/living/O in viewers(src, null)) if (get_dist(src, O) > src.range) continue @@ -72,7 +72,9 @@ if (istype(O, /mob/living/carbon/xenomorph))//So aliens don't get flashed (they have no external eyes)/N continue - O.apply_effect(strength, WEAKEN) + O.KnockDown(strength) + O.Stun(strength) + if (istype(O, /mob/living/carbon/human)) var/mob/living/carbon/human/H = O var/datum/internal_organ/eyes/E = H.internal_organs_by_name["eyes"] diff --git a/code/game/machinery/medical_pod/bodyscanner.dm b/code/game/machinery/medical_pod/bodyscanner.dm index bbc3be7d5aae..732ff1ba97b9 100644 --- a/code/game/machinery/medical_pod/bodyscanner.dm +++ b/code/game/machinery/medical_pod/bodyscanner.dm @@ -204,7 +204,7 @@ "toxloss" = H.getToxLoss(), "cloneloss" = H.getCloneLoss(), "brainloss" = H.getBrainLoss(), - "knocked_out" = H.GetKnockOutValueNotADurationDoNotUse(), + "knocked_out" = H.GetKnockOutDuration(), "bodytemp" = H.bodytemperature, "inaprovaline_amount" = H.reagents.get_reagent_amount("inaprovaline"), "dexalin_amount" = H.reagents.get_reagent_amount("dexalin"), @@ -263,7 +263,7 @@ s_class = occ["brainloss"] < 1 ? INTERFACE_GOOD : INTERFACE_BAD dat += "[SET_CLASS("  Approx. Brain Damage:", INTERFACE_PINK)] [SET_CLASS("[occ["brainloss"]]%", s_class)]

" - dat += "[SET_CLASS("Knocked Out Summary:", "#40628a")] [occ["knocked_out"]]% (approximately [round(occ["knocked_out"] / 5)] seconds left!)
" + dat += "[SET_CLASS("Knocked Out Summary:", "#40628a")] [occ["knocked_out"]]% (approximately [round(occ["knocked_out"] * GLOBAL_STATUS_MULTIPLIER / (1 SECONDS))] seconds left!)
" dat += "[SET_CLASS("Body Temperature:", "#40628a")] [occ["bodytemp"]-T0C]°C ([occ["bodytemp"]*1.8-459.67]°F)

" s_class = occ["blood_amount"] > 448 ? INTERFACE_OKAY : INTERFACE_BAD diff --git a/code/game/machinery/medical_pod/sleeper.dm b/code/game/machinery/medical_pod/sleeper.dm index fe2b698caed0..84ef2f579ba1 100644 --- a/code/game/machinery/medical_pod/sleeper.dm +++ b/code/game/machinery/medical_pod/sleeper.dm @@ -391,7 +391,7 @@ to_chat(user, "[]\t -Toxin Content %: []", (occupant.getToxLoss() < 60 ? SPAN_NOTICE("") : SPAN_DANGER("")), occupant.getToxLoss()) to_chat(user, "[]\t -Burn Severity %: []", (occupant.getFireLoss() < 60 ? SPAN_NOTICE("") : SPAN_DANGER("")), occupant.getFireLoss()) to_chat(user, SPAN_NOTICE(" Expected time till occupant can safely awake: (note: These times are always inaccurate)")) - to_chat(user, SPAN_NOTICE(" \t [occupant.GetKnockOutValueNotADurationDoNotUse() / 5] second\s (if around 1 or 2 the sleeper is keeping them asleep.)")) + to_chat(user, SPAN_NOTICE(" \t [occupant.GetKnockOutDuration() * GLOBAL_STATUS_MULTIPLIER / (1 SECONDS)] second\s (if around 1 or 2 the sleeper is keeping them asleep.)")) else to_chat(user, SPAN_NOTICE(" There is no one inside!")) return diff --git a/code/game/objects/effects/aliens.dm b/code/game/objects/effects/aliens.dm index d328bb958643..41adfdd9581d 100644 --- a/code/game/objects/effects/aliens.dm +++ b/code/game/objects/effects/aliens.dm @@ -326,11 +326,11 @@ handle_weather() RegisterSignal(SSdcs, COMSIG_GLOB_WEATHER_CHANGE, PROC_REF(handle_weather)) RegisterSignal(acid_t, COMSIG_PARENT_QDELETING, PROC_REF(cleanup)) - START_PROCESSING(SSeffects, src) + START_PROCESSING(SSoldeffects, src) /obj/effect/xenomorph/acid/Destroy() acid_t = null - STOP_PROCESSING(SSeffects, src) + STOP_PROCESSING(SSoldeffects, src) . = ..() /obj/effect/xenomorph/acid/proc/cleanup() diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 9d730c71970b..cc9f1fe53fea 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -236,6 +236,7 @@ /obj/proc/unbuckle() SIGNAL_HANDLER if(buckled_mob && buckled_mob.buckled == src) + buckled_mob.clear_alert(ALERT_BUCKLED) buckled_mob.set_buckled(null) buckled_mob.anchored = initial(buckled_mob.anchored) @@ -302,6 +303,7 @@ /obj/proc/do_buckle(mob/living/target, mob/user) send_buckling_message(target, user) if (src && src.loc) + target.throw_alert(ALERT_BUCKLED, /atom/movable/screen/alert/buckled) target.set_buckled(src) target.forceMove(src.loc) target.setDir(dir) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 736427606683..2ed19485acc9 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -274,7 +274,9 @@ var/mob/living/M = G.grabbed_thing if(user.a_intent == INTENT_HARM) if(user.grab_level > GRAB_AGGRESSIVE) - if (prob(15)) M.apply_effect(5, WEAKEN) + if (prob(15)) + M.KnockDown(5) + M.Stun(5) M.apply_damage(8, def_zone = "head") user.visible_message(SPAN_DANGER("[user] slams [M]'s face against [src]!"), SPAN_DANGER("You slam [M]'s face against [src]!")) @@ -284,7 +286,8 @@ return else if(user.grab_level >= GRAB_AGGRESSIVE) M.forceMove(loc) - M.apply_effect(5, WEAKEN) + M.KnockDown(5) + M.Stun(5) playsound(loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7) user.visible_message(SPAN_DANGER("[user] throws [M] on [src], stunning them!"), SPAN_DANGER("You throw [M] on [src], stunning them!")) diff --git a/code/modules/cm_aliens/Ovipositor.dm b/code/modules/cm_aliens/Ovipositor.dm index 07d3466a279d..b8983f37cc7a 100644 --- a/code/modules/cm_aliens/Ovipositor.dm +++ b/code/modules/cm_aliens/Ovipositor.dm @@ -14,11 +14,11 @@ . = ..() begin_decay_time = world.timeofday + QUEEN_OVIPOSITOR_DECAY_TIME - START_PROCESSING(SSeffects, src) // Process every second + START_PROCESSING(SSoldeffects, src) // Process every second /obj/ovipositor/Destroy() if(!decayed && !destroyed) - STOP_PROCESSING(SSeffects, src) + STOP_PROCESSING(SSoldeffects, src) return ..() @@ -32,7 +32,7 @@ /obj/ovipositor/proc/do_decay() decayed = TRUE - STOP_PROCESSING(SSeffects, src) + STOP_PROCESSING(SSoldeffects, src) icon_state = "ovipositor_molted" flick("ovipositor_decay", src) @@ -46,7 +46,7 @@ /obj/ovipositor/proc/explode() destroyed = TRUE - STOP_PROCESSING(SSeffects, src) + STOP_PROCESSING(SSoldeffects, src) icon_state = "ovipositor_gibbed" flick("ovipositor_explosion", src) diff --git a/code/modules/cm_marines/NonLethalRestraints.dm b/code/modules/cm_marines/NonLethalRestraints.dm index 3b2439a22a82..e1eb7ec60a77 100644 --- a/code/modules/cm_marines/NonLethalRestraints.dm +++ b/code/modules/cm_marines/NonLethalRestraints.dm @@ -31,7 +31,7 @@ to_chat(user, SPAN_WARNING("\The [src] is out of charge.")) add_fingerprint(user) -/obj/item/weapon/stunprod/attack(mob/M, mob/user) +/obj/item/weapon/stunprod/attack(mob/living/M, mob/user) if(isrobot(M)) ..() return @@ -43,7 +43,8 @@ return if(status) - M.apply_effect(6, WEAKEN) + M.KnockDown(6) + M.Stun(6) charges -= 2 M.visible_message(SPAN_DANGER("[M] has been prodded with [src] by [user]!")) diff --git a/code/modules/maptext_alerts/screen_alerts.dm b/code/modules/maptext_alerts/screen_alerts.dm index 0b923f7dc753..b096d3b3718f 100644 --- a/code/modules/maptext_alerts/screen_alerts.dm +++ b/code/modules/maptext_alerts/screen_alerts.dm @@ -214,6 +214,9 @@ /// Alert owner var/mob/owner + /// Boolean. If TRUE, the Click() proc will attempt to Click() on the master first if there is a master. + var/click_master = TRUE + /atom/movable/screen/alert/MouseEntered(location,control,params) . = ..() if(!QDELETED(src)) @@ -251,3 +254,65 @@ if(NOTIFY_XENO_TACMAP) GLOB.xeno_tacmap_status.tgui_interact(ghost_user) +/atom/movable/screen/alert/buckled + name = "Buckled" + desc = "You've been buckled to something. Click the alert to unbuckle unless you're handcuffed." + icon_state = ALERT_BUCKLED + +/atom/movable/screen/alert/restrained/handcuffed + name = "Handcuffed" + desc = "You're handcuffed and can't act. If anyone drags you, you won't be able to move. Click the alert to free yourself." + click_master = FALSE + +/atom/movable/screen/alert/restrained/legcuffed + name = "Legcuffed" + desc = "You're legcuffed, which slows you down considerably. Click the alert to free yourself." + click_master = FALSE + +/atom/movable/screen/alert/restrained/clicked() + . = ..() + if(!.) + return + + var/mob/living/living_owner = owner + + if(!living_owner.can_resist()) + return + +// living_owner.changeNext_move(CLICK_CD_RESIST) // handled in resist proc + if((living_owner.mobility_flags & MOBILITY_MOVE) && (living_owner.last_special <= world.time)) + return living_owner.resist_restraints() + +/atom/movable/screen/alert/buckled/clicked() + . = ..() + if(!.) + return + + var/mob/living/living_owner = owner + + if(!living_owner.can_resist()) + return +// living_owner.changeNext_move(CLICK_CD_RESIST) // handled in resist proc + if(living_owner.last_special <= world.time) + return living_owner.resist_buckle() + +/atom/movable/screen/alert/clicked(location, control, params) + if(!usr || !usr.client) + return FALSE + if(usr != owner) + return FALSE + var/list/modifiers = params2list(params) + if(LAZYACCESS(modifiers, SHIFT_CLICK)) // screen objects don't do the normal Click() stuff so we'll cheat + to_chat(usr, SPAN_BOLDNOTICE("[name] - [desc]")) + return FALSE + if(master && click_master) + return usr.client.Click(master, location, control, params) + + return TRUE + +/atom/movable/screen/alert/Destroy() + . = ..() + severity = 0 + master = null + owner = null + screen_loc = "" diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 6170aec3031c..b523cef08eec 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1098,9 +1098,9 @@ for(var/datum/effects/bleeding/internal/internal_bleed in effects_list) msg += "They have bloating and discoloration on their [internal_bleed.limb.display_name]\n" - if(knocked_out && stat != DEAD) + if(stat == UNCONSCIOUS) msg += "They seem to be unconscious\n" - if(stat == DEAD) + else if(stat == DEAD) if(src.check_tod() && is_revivable()) msg += "They're not breathing" else diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index dfbc2c971a8c..8f032288065b 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -161,7 +161,9 @@ disarm_chance += 5 * defender_skill_level if(disarm_chance <= 25) - apply_effect(2 + max((attacker_skill_level - defender_skill_level), 0), WEAKEN) + var/strength = 2 + max((attacker_skill_level - defender_skill_level), 0) + KnockDown(strength) + Stun(strength) playsound(loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7) var/shove_text = attacker_skill_level > 1 ? "tackled" : pick("pushed", "shoved") visible_message(SPAN_DANGER("[attacking_mob] has [shove_text] [src]!"), null, null, 5) @@ -205,15 +207,14 @@ if (w_uniform) w_uniform.add_fingerprint(M) - - if(body_position == LYING_DOWN || sleeping) + if(HAS_TRAIT(src, TRAIT_FLOORED) || HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || body_position == LYING_DOWN || sleeping) if(client) sleeping = max(0,src.sleeping-5) if(!sleeping) set_resting(FALSE) M.visible_message(SPAN_NOTICE("[M] shakes [src] trying to wake [t_him] up!"), \ SPAN_NOTICE("You shake [src] trying to wake [t_him] up!"), null, 4) - else if(stunned) + else if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) M.visible_message(SPAN_NOTICE("[M] shakes [src], trying to shake [t_him] out of his stupor!"), \ SPAN_NOTICE("You shake [src], trying to shake [t_him] out of his stupor!"), null, 4) else diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm index 90a4d7bca4ab..e09e9e2ebb7b 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human_damage.dm @@ -514,11 +514,3 @@ This function restores all limbs. damage_to_deal *= 0.25 // Massively reduced effectiveness stamina.apply_damage(damage_to_deal) - -/mob/living/carbon/human/knocked_out_start() - ..() - sound_environment_override = SOUND_ENVIRONMENT_PSYCHOTIC - -/mob/living/carbon/human/knocked_out_callback() - . = ..() - sound_environment_override = SOUND_ENVIRONMENT_NONE diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index be1c7833c5c1..1a43138421e4 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -88,3 +88,10 @@ if(!client && !mind && species) species.handle_npc(src) + +/mob/living/carbon/human/set_stat(new_stat) + . = ..() + // Temporarily force triggering HUD updates so they apply immediately rather than on Life tick. + // Remove this once effects have been ported to trait signals (blinded, dazed, etc) + if(stat != .) + handle_regular_hud_updates() diff --git a/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm b/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm index 41554f056744..e9bb307d7335 100644 --- a/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm +++ b/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm @@ -53,7 +53,7 @@ if(!already_in_crit) new /datum/effects/crit/human(src) - if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT)) + if(IsKnockOut()) blinded = TRUE if(regular_update && halloss > 0) apply_damage(-3, HALLOSS) diff --git a/code/modules/mob/living/carbon/human/life/handle_stasis_bag.dm b/code/modules/mob/living/carbon/human/life/handle_stasis_bag.dm index 16d9955395b0..43c757fabb3e 100644 --- a/code/modules/mob/living/carbon/human/life/handle_stasis_bag.dm +++ b/code/modules/mob/living/carbon/human/life/handle_stasis_bag.dm @@ -4,9 +4,9 @@ //Handle side effects from stasis switch(in_stasis) if(STASIS_IN_BAG) - // I hate whoever wrote this and statuses with a passion - knocked_down = knocked_down? --knocked_down : knocked_down + 10 //knocked_down set. - if(knocked_down <= 0) - knocked_down_callback() + // At least 6 seconds, but reduce by 2s every time - IN ADDITION to normal recovery + // Don't ask me why and feel free to change it + KnockDown(3) + AdjustKnockDown(-1) if(STASIS_IN_CRYO_CELL) if(sleeping < 10) sleeping += 10 //Puts the mob to sleep indefinitely. diff --git a/code/modules/mob/living/carbon/human/life/life_helpers.dm b/code/modules/mob/living/carbon/human/life/life_helpers.dm index 25f020a9f8b6..bf254b9da1ed 100644 --- a/code/modules/mob/living/carbon/human/life/life_helpers.dm +++ b/code/modules/mob/living/carbon/human/life/life_helpers.dm @@ -197,12 +197,6 @@ speech_problem_flag = TRUE return slurring -/mob/living/carbon/human/handle_stunned() - if(stunned) - adjust_effect(-species.stun_reduction, STUN, EFFECT_FLAG_LIFE) - speech_problem_flag = TRUE - return stunned - /mob/living/carbon/human/handle_dazed() if(dazed) var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.1 : 0 @@ -213,26 +207,6 @@ speech_problem_flag = TRUE return dazed -/mob/living/carbon/human/handle_knocked_down() - if(knocked_down) - var/species_resistance = species.knock_down_reduction - var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.1 : 0 - - var/final_reduction = species_resistance + skill_resistance - adjust_effect(-final_reduction, WEAKEN, EFFECT_FLAG_LIFE) - knocked_down_callback_check() - return knocked_down - -/mob/living/carbon/human/handle_knocked_out() - if(knocked_out) - var/species_resistance = species.knock_out_reduction - var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.1 : 0 - - var/final_reduction = species_resistance + skill_resistance - adjust_effect(-final_reduction, PARALYZE, EFFECT_FLAG_LIFE) - knocked_out_callback_check() - return knocked_out - /mob/living/carbon/human/handle_stuttering() if(..()) speech_problem_flag = TRUE @@ -240,40 +214,23 @@ #define HUMAN_TIMER_TO_EFFECT_CONVERSION (0.05) //(1/20) //once per 2 seconds, with effect equal to endurance, which is used later -// This is here because sometimes our stun comes too early and tick is about to start, so we need to compensate -// this is the best place to do it, tho name might be a bit misleading I guess -/mob/living/carbon/human/stun_clock_adjustment() - var/species_resistance = species.knock_down_reduction - var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.1 : 0 - - var/final_reduction = species_resistance + skill_resistance - var/shift_left = (SShuman.next_fire - world.time) * HUMAN_TIMER_TO_EFFECT_CONVERSION * final_reduction - if(stunned > shift_left) - stunned += SShuman.wait * HUMAN_TIMER_TO_EFFECT_CONVERSION * final_reduction - shift_left - -/mob/living/carbon/human/knockdown_clock_adjustment() - if(!species) - return FALSE - - var/species_resistance = species.knock_down_reduction - var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.1 : 0 - - var/final_reduction = species_resistance + skill_resistance - var/shift_left = (SShuman.next_fire - world.time) * HUMAN_TIMER_TO_EFFECT_CONVERSION * final_reduction - if(knocked_down > shift_left) - knocked_down += SShuman.wait * HUMAN_TIMER_TO_EFFECT_CONVERSION * final_reduction - shift_left - -/mob/living/carbon/human/knockout_clock_adjustment() - if(!species) - return FALSE - - var/species_resistance = species.knock_out_reduction - var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.1 : 0 - - var/final_reduction = species_resistance + skill_resistance - var/shift_left = (SShuman.next_fire - world.time) * HUMAN_TIMER_TO_EFFECT_CONVERSION * final_reduction - if(knocked_out > shift_left) - knocked_out += SShuman.wait * HUMAN_TIMER_TO_EFFECT_CONVERSION * final_reduction - shift_left +/mob/living/carbon/human/GetStunDuration(amount) + . = ..() + var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.08 : 0 + var/final_reduction = (1 - skill_resistance) / species.stun_reduction + return . * final_reduction + +/mob/living/carbon/human/GetKnockDownDuration(amount) + . = ..() + var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.08 : 0 + var/final_reduction = (1 - skill_resistance) / species.knock_down_reduction + return . * final_reduction + +/mob/living/carbon/human/GetKnockOutDuration(amount) + . = ..() + var/skill_resistance = skills ? (skills.get_skill_level(SKILL_ENDURANCE)-1)*0.08 : 0 + var/final_reduction = (1 - skill_resistance) / species.knock_out_reduction + return . * final_reduction /mob/living/carbon/human/proc/handle_revive() SEND_SIGNAL(src, COMSIG_HUMAN_REVIVED) diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index 397a478a2779..4392c3359596 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -92,9 +92,12 @@ "eyes" = /datum/internal_organ/eyes ) - var/knock_down_reduction = 1 //how much the knocked_down effect is reduced per Life call. - var/stun_reduction = 1 //how much the stunned effect is reduced per Life call. - var/knock_out_reduction = 1 //same thing + /// Factor of reduction of KnockDown duration. + var/knock_down_reduction = 1 + /// Factor of reduction of Stun duration. + var/stun_reduction = 1 + /// Factor of reduction of KnockOut duration. + var/knock_out_reduction = 1 /// If different from 1, a signal is registered on post_spawn(). var/weed_slowdown_mult = 1 diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 97bf25e08932..e5673817b277 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -4,11 +4,18 @@ if(handcuffed) drop_held_items() stop_pulling() + throw_alert(ALERT_HANDCUFFED, /atom/movable/screen/alert/restrained/handcuffed, new_master = handcuffed) + else + clear_alert(ALERT_HANDCUFFED) update_inv_handcuffed() + /mob/living/carbon/proc/legcuff_update() if(legcuffed) set_movement_intent(MOVE_INTENT_WALK) + throw_alert(ALERT_LEGCUFFED, /atom/movable/screen/alert/restrained/legcuffed, new_master = handcuffed) + else + clear_alert(ALERT_LEGCUFFED) update_inv_legcuffed() diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index b4287309bb3c..46b6c857d481 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -284,7 +284,8 @@ if(H.check_shields(15, "the pounce")) //Human shield block. visible_message(SPAN_DANGER("[src] slams into [H]!"), SPAN_XENODANGER("We slam into [H]!"), null, 5) - apply_effect(1, WEAKEN) + KnockDown(1) + Stun(1) throwing = FALSE //Reset throwing manually. playsound(H, "bonk", 75, FALSE) //bonk return @@ -299,13 +300,15 @@ else if(prob(75)) //Body slam the fuck out of xenos jumping at your front. visible_message(SPAN_DANGER("[H] body slams [src]!"), SPAN_XENODANGER("[H] body slams us!"), null, 5) - apply_effect(3, WEAKEN) + KnockDown(3) + Stun(3) throwing = FALSE return if(iscolonysynthetic(H) && prob(60)) visible_message(SPAN_DANGER("[H] withstands being pounced and slams down [src]!"), SPAN_XENODANGER("[H] throws us down after withstanding the pounce!"), null, 5) - apply_effect(1.5, WEAKEN) + KnockDown(1.5) + Stun(1.5) throwing = FALSE return @@ -313,7 +316,8 @@ visible_message(SPAN_DANGER("[src] [pounceAction.ability_name] onto [M]!"), SPAN_XENODANGER("We [pounceAction.ability_name] onto [M]!"), null, 5) if (pounceAction.knockdown) - M.apply_effect(pounceAction.knockdown_duration, WEAKEN) + M.KnockDown(pounceAction.knockdown_duration) + M.Stun(pounceAction.knockdown_duration) // To replicate legacy behavior. Otherwise M39 Armbrace users for example can still shoot step_to(src, M) if (pounceAction.freeze_self) diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index f77d7d99c1a3..3095e805be6a 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -252,13 +252,9 @@ var/pounce_distance = 0 // Life reduction variables. - var/life_stun_reduction = -1.5 - var/life_knockdown_reduction = -1.5 - var/life_knockout_reduction = -1.5 var/life_daze_reduction = -1.5 var/life_slow_reduction = -1.5 - ////////////////////////////////////////////////////////////////// // // Misc. State - poorly modularized @@ -1101,11 +1097,6 @@ forceMove(current_structure.loc) return TRUE -/mob/living/carbon/xenomorph/knocked_down_callback() - . = ..() - if(!resting) // !resting because we dont wanna prematurely update wounds if they're just trying to rest - update_wounds() - ///Generate a new unused nicknumber for the current hive, if hive doesn't exist return 0 /mob/living/carbon/xenomorph/proc/generate_and_set_nicknumber() if(!hive) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm index 6834f7b2e165..80e4130fccb9 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm @@ -394,10 +394,12 @@ /// remove hide and apply modified attack cooldown /datum/action/xeno_action/onclick/xenohide/proc/post_attack() var/mob/living/carbon/xenomorph/xeno = owner + UnregisterSignal(xeno, COMSIG_MOB_STATCHANGE) if(xeno.layer == XENO_HIDING_LAYER) xeno.layer = initial(xeno.layer) button.icon_state = "template" xeno.update_wounds() + xeno.update_layer() apply_cooldown(4) //2 second cooldown after attacking /datum/action/xeno_action/onclick/xenohide/give_to(mob/living/living_mob) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm index 06f584fda032..81f500245a41 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -500,14 +500,21 @@ xeno.layer = XENO_HIDING_LAYER to_chat(xeno, SPAN_NOTICE("We are now hiding.")) button.icon_state = "template_active" + RegisterSignal(xeno, COMSIG_MOB_STATCHANGE, PROC_REF(unhide_on_stat)) else xeno.layer = initial(xeno.layer) to_chat(xeno, SPAN_NOTICE("We have stopped hiding.")) button.icon_state = "template" + UnregisterSignal(xeno, COMSIG_MOB_STATCHANGE) xeno.update_wounds() apply_cooldown() return ..() +/datum/action/xeno_action/onclick/xenohide/proc/unhide_on_stat(mob/living/carbon/xenomorph/source, new_stat, old_stat) + SIGNAL_HANDLER + if(new_stat >= UNCONSCIOUS && old_stat <= UNCONSCIOUS) + post_attack() + /datum/action/xeno_action/onclick/place_trap/use_ability(atom/A) var/mob/living/carbon/xenomorph/X = owner if(!X.check_state()) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm index 5127ca6fe4ed..2c16477c1414 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm @@ -10,7 +10,7 @@ // Configurables var/fling_distance = 4 - var/stun_power = 0 + var/stun_power = 0.5 var/weaken_power = 0.5 var/slowdown = 2 diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_powers.dm index 4bb9e63ebaf2..c25b7e5fc49f 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_powers.dm @@ -77,9 +77,9 @@ xeno.visible_message(SPAN_XENOWARNING("[xeno] effortlessly flings [carbon] to the side!"), SPAN_XENOWARNING("We effortlessly fling [carbon] to the side!")) playsound(carbon,'sound/weapons/alien_claw_block.ogg', 75, 1) if(stun_power) - carbon.apply_effect(get_xeno_stun_duration(carbon, stun_power), STUN) + carbon.Stun(get_xeno_stun_duration(carbon, stun_power)) if(weaken_power) - carbon.apply_effect(weaken_power, WEAKEN) + carbon.KnockDown(get_xeno_stun_duration(carbon, weaken_power)) if(slowdown) if(carbon.slowed < slowdown) carbon.apply_effect(slowdown, SLOW) diff --git a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm index a038974b608b..d57df232cda4 100644 --- a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm +++ b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm @@ -204,12 +204,14 @@ if(M.attempt_tackle(src, tackle_mult, tackle_min_offset, tackle_max_offset)) playsound(loc, 'sound/weapons/alien_knockdown.ogg', 25, 1) - apply_effect(rand(M.tacklestrength_min, M.tacklestrength_max), WEAKEN) + var/strength = rand(M.tacklestrength_min, M.tacklestrength_max) + Stun(strength) + KnockDown(strength) // Purely for knockdown visuals. All the heavy lifting is done by Stun M.visible_message(SPAN_DANGER("[M] tackles down [src]!"), \ SPAN_DANGER("We tackle down [src]!"), null, 5, CHAT_TYPE_XENO_COMBAT) else playsound(loc, 'sound/weapons/alien_claw_swipe.ogg', 25, 1) - if (HAS_TRAIT(src, TRAIT_FLOORED)) + if (body_position == LYING_DOWN) M.visible_message(SPAN_DANGER("[M] tries to tackle [src], but they are already down!"), \ SPAN_DANGER("We try to tackle [src], but they are already down!"), null, 5, CHAT_TYPE_XENO_COMBAT) else diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm index af7aa7224f0c..a568a093b3a4 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm @@ -103,5 +103,6 @@ #undef NEURO_TOUCH_DELAY /datum/behavior_delegate/sentinel_base/proc/paralyzing_slash(mob/living/carbon/human/human_target) - human_target.apply_effect(2, WEAKEN) + human_target.KnockDown(2) + human_target.Stun(2) to_chat(human_target, SPAN_XENOHIGHDANGER("You fall over, paralyzed by the toxin!")) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm index 3bcea7d91d60..04996af8f8db 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm @@ -101,7 +101,9 @@ if(should_neckgrab && living_mob.mob_size < MOB_SIZE_BIG) living_mob.drop_held_items() - living_mob.apply_effect(get_xeno_stun_duration(living_mob, 2), WEAKEN) + var/duration = get_xeno_stun_duration(living_mob, 2) + living_mob.KnockDown(duration) + living_mob.Stun(duration) if(living_mob.pulledby != src) return // Grab was broken, probably as Stun side effect (eg. target getting knocked away from a manned M56D) visible_message(SPAN_XENOWARNING("[src] grabs [living_mob] by the throat!"), \ diff --git a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm index b6ceb2043458..e372b03e68d9 100644 --- a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm +++ b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm @@ -81,6 +81,7 @@ powerfactor_value = min(powerfactor_value,20) if(powerfactor_value > 0 && small_explosives_stun) KnockDown(powerfactor_value/5) + Stun(powerfactor_value/5) // Due to legacy knockdown being considered an impairement if(mob_size < MOB_SIZE_BIG) Slow(powerfactor_value) Superslow(powerfactor_value/2) diff --git a/code/modules/mob/living/carbon/xenomorph/life.dm b/code/modules/mob/living/carbon/xenomorph/life.dm index 70c92dad076d..07efb5be2ff8 100644 --- a/code/modules/mob/living/carbon/xenomorph/life.dm +++ b/code/modules/mob/living/carbon/xenomorph/life.dm @@ -516,25 +516,24 @@ Make sure their actual health updates immediately.*/ else if(world.time > next_grace_time && stat == CONSCIOUS) var/grace_time = crit_grace_time > 0 ? crit_grace_time + (1 SECONDS * max(round(warding_aura - 1), 0)) : 0 if(grace_time) - sound_environment_override = SOUND_ENVIRONMENT_PSYCHOTIC addtimer(CALLBACK(src, PROC_REF(handle_crit)), grace_time) else handle_crit() next_grace_time = world.time + grace_time + blinded = stat == UNCONSCIOUS // Xenos do not go blind from other sources - still, replace that by a status_effect or trait when able if(!gibbing) med_hud_set_health() /mob/living/carbon/xenomorph/proc/handle_crit() - if(stat == DEAD || gibbing) - return + if(stat <= CONSCIOUS && !gibbing) + set_stat(UNCONSCIOUS) - sound_environment_override = SOUND_ENVIRONMENT_NONE - set_stat(UNCONSCIOUS) - blinded = TRUE - see_in_dark = 5 - if(layer != initial(layer)) //Unhide - layer = initial(layer) - recalculate_move_delay = TRUE +/mob/living/carbon/xenomorph/set_stat(new_stat) + . = ..() + // Temporarily force triggering HUD updates so they apply immediately rather than on Life tick. + // Remove this once effects have been ported to trait signals (blinded, dazed, etc) + if(stat != .) + handle_regular_hud_updates() /mob/living/carbon/xenomorph/proc/handle_luminosity() var/new_luminosity = 0 @@ -548,12 +547,15 @@ Make sure their actual health updates immediately.*/ else set_light_on(FALSE) -/mob/living/carbon/xenomorph/handle_stunned() - if(stunned) - adjust_effect(life_stun_reduction, STUN, EFFECT_FLAG_LIFE) - stun_callback_check() - - return stunned +/mob/living/carbon/xenomorph/GetStunDuration(amount) + amount *= 2 / 3 + return ..() +/mob/living/carbon/xenomorph/GetKnockDownDuration(amount) + amount *= 2 / 3 + return ..() +/mob/living/carbon/xenomorph/GetKnockOutDuration(amount) + amount *= 2 / 3 + return ..() /mob/living/carbon/xenomorph/proc/handle_interference() if(interference) @@ -579,16 +581,6 @@ Make sure their actual health updates immediately.*/ adjust_effect(life_slow_reduction, SUPERSLOW, EFFECT_FLAG_LIFE) return superslowed -/mob/living/carbon/xenomorph/handle_knocked_down() - if(HAS_TRAIT(src, TRAIT_FLOORED)) - adjust_effect(life_knockdown_reduction, WEAKEN, EFFECT_FLAG_LIFE) - knocked_down_callback_check() - -/mob/living/carbon/xenomorph/handle_knocked_out() - if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT)) - adjust_effect(life_knockout_reduction, PARALYZE, EFFECT_FLAG_LIFE) - knocked_out_callback_check() - //Returns TRUE if xeno is on weeds //Returns TRUE if xeno is off weeds AND doesn't need weeds for healing AND is not on Almayer UNLESS Queen is also on Almayer (aka - no solo Lurker Almayer hero) /mob/living/carbon/xenomorph/proc/check_weeds_for_healing() @@ -603,24 +595,3 @@ Make sure their actual health updates immediately.*/ if(hive && hive.living_xeno_queen && !is_mainship_level(hive.living_xeno_queen.loc.z) && is_mainship_level(loc.z)) return FALSE //We are on the ship, but the Queen isn't return TRUE //we have off-weed healing, and either we're on Almayer with the Queen, or we're on non-Almayer, or the Queen is dead, good enough! - - -#define XENO_TIMER_TO_EFFECT_CONVERSION (0.075) // (1.5/20) //once per 2 seconds, with 1.5 effect per that once - -// This is here because sometimes our stun comes too early and tick is about to start, so we need to compensate -// this is the best place to do it, tho name might be a bit misleading I guess -/mob/living/carbon/xenomorph/stun_clock_adjustment() - var/shift_left = (SSxeno.next_fire - world.time) * XENO_TIMER_TO_EFFECT_CONVERSION - if(stunned > shift_left) - stunned += SSxeno.wait * XENO_TIMER_TO_EFFECT_CONVERSION - shift_left - -/mob/living/carbon/xenomorph/knockdown_clock_adjustment() - var/shift_left = (SSxeno.next_fire - world.time) * XENO_TIMER_TO_EFFECT_CONVERSION - if(knocked_down > shift_left) - knocked_down += SSxeno.wait * XENO_TIMER_TO_EFFECT_CONVERSION - shift_left - -/mob/living/carbon/xenomorph/knockout_clock_adjustment() - var/shift_left = (SSxeno.next_fire - world.time) * XENO_TIMER_TO_EFFECT_CONVERSION - if(knocked_out > shift_left) - knocked_out += SSxeno.wait * XENO_TIMER_TO_EFFECT_CONVERSION - shift_left - diff --git a/code/modules/mob/living/carbon/xenomorph/update_icons.dm b/code/modules/mob/living/carbon/xenomorph/update_icons.dm index e576b23e2855..7b048bdf2f58 100644 --- a/code/modules/mob/living/carbon/xenomorph/update_icons.dm +++ b/code/modules/mob/living/carbon/xenomorph/update_icons.dm @@ -99,19 +99,32 @@ . = ..() if(. != new_value) update_icons() // Snowflake handler for xeno resting icons + update_wounds() /mob/living/carbon/xenomorph/on_floored_start() . = ..() update_icons() + update_wounds() /mob/living/carbon/xenomorph/on_floored_end() . = ..() update_icons() + update_wounds() /mob/living/carbon/xenomorph/on_incapacitated_trait_gain() . = ..() update_icons() + update_wounds() /mob/living/carbon/xenomorph/on_incapacitated_trait_loss() . = ..() update_icons() + update_wounds() +/mob/living/carbon/xenomorph/on_knockedout_trait_gain() + . = ..() + update_icons() + update_wounds() +/mob/living/carbon/xenomorph/on_knockedout_trait_loss() + . = ..() + update_icons() + update_wounds() /* ^^^^^^^^^^^^^^ End Icon updates */ diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 9fade66e44c6..7c4eff0e2c15 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -65,14 +65,13 @@ //#define EFFECT_FLAG_XENOMORPH //#define EFFECT_FLAG_CHEMICAL +/// Legacy wrapper for effects, DO NOT USE and migrate all code to USING THE STATUS PROCS DIRECTLY /mob/proc/apply_effect() return FALSE +// Legacy wrapper for effects, DO NOT USE and migrate all code to USING THE BELOW PROCS DIRECTLY /mob/living/apply_effect(effect = 0, effect_type = STUN, effect_flags = EFFECT_FLAG_DEFAULT) - if(SEND_SIGNAL(src, COMSIG_LIVING_APPLY_EFFECT, effect, effect_type, effect_flags) & COMPONENT_CANCEL_EFFECT) - return - if(!effect) return FALSE @@ -106,9 +105,6 @@ /mob/living/adjust_effect(effect = 0, effect_type = STUN, effect_flags = EFFECT_FLAG_DEFAULT) - if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_EFFECT, effect, effect_type, effect_flags) & COMPONENT_CANCEL_EFFECT) - return - if(!effect) return FALSE @@ -142,9 +138,6 @@ /mob/living/set_effect(effect = 0, effect_type = STUN, effect_flags = EFFECT_FLAG_DEFAULT) - if(SEND_SIGNAL(src, COMSIG_LIVING_SET_EFFECT, effect, effect_type, effect_flags) & COMPONENT_CANCEL_EFFECT) - return - switch(effect_type) if(STUN) SetStun(effect) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 6205c4f919a4..64c851310823 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -24,6 +24,7 @@ /mob/living/Destroy() GLOB.living_mob_list -= src + cleanup_status_effects() pipes_shown = null . = ..() @@ -33,6 +34,17 @@ QDEL_NULL(pain) QDEL_NULL(stamina) QDEL_NULL(hallucinations) + status_effects = null + +/// Clear all running status effects assuming deletion +/mob/living/proc/cleanup_status_effects() + PROTECTED_PROC(TRUE) + if(length(status_effects)) + for(var/datum/status_effect/S as anything in status_effects) + if(S?.on_remove_on_mob_delete) //the status effect calls on_remove when its mob is deleted + qdel(S) + else + S?.be_replaced() /mob/living/proc/initialize_pain() pain = new /datum/pain(src) @@ -473,10 +485,12 @@ if(CONSCIOUS) if(stat >= UNCONSCIOUS) ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) + sound_environment_override = SOUND_ENVIRONMENT_PSYCHOTIC add_traits(list(/*TRAIT_HANDS_BLOCKED, */ TRAIT_INCAPACITATED, TRAIT_FLOORED), STAT_TRAIT) if(UNCONSCIOUS) if(stat >= UNCONSCIOUS) ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) //adding trait sources should come before removing to avoid unnecessary updates + sound_environment_override = SOUND_ENVIRONMENT_PSYCHOTIC if(DEAD) SEND_SIGNAL(src, COMSIG_MOB_STAT_SET_ALIVE) // remove_from_dead_mob_list() @@ -486,15 +500,18 @@ if(CONSCIOUS) if(. >= UNCONSCIOUS) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) + sound_environment_override = SOUND_ENVIRONMENT_NONE remove_traits(list(/*TRAIT_HANDS_BLOCKED, */ TRAIT_INCAPACITATED, TRAIT_FLOORED, /*TRAIT_CRITICAL_CONDITION*/), STAT_TRAIT) if(UNCONSCIOUS) if(. >= UNCONSCIOUS) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) + sound_environment_override = SOUND_ENVIRONMENT_NONE if(DEAD) SEND_SIGNAL(src, COMSIG_MOB_STAT_SET_DEAD) // REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) // remove_from_alive_mob_list() // add_to_dead_mob_list() + update_layer() // Force update layers so that lying down works as intended upon death. This is redundant otherwise. Replace this by trait signals /** * Changes the inclination angle of a mob, used by humans and others to differentiate between standing up and prone positions. @@ -577,12 +594,16 @@ drop_l_hand() drop_r_hand() add_temp_pass_flags(PASS_MOB_THRU) + update_layer() + +/// Updates the layer the mob is on based on its current status. This can result in redundant updates. Replace by trait signals eventually +/mob/living/proc/update_layer() //so mob lying always appear behind standing mobs, but dead ones appear behind living ones if(pulledby && pulledby.grab_level == GRAB_CARRY) layer = ABOVE_MOB_LAYER - else if (stat == DEAD) + else if (body_position == LYING_DOWN && stat == DEAD) layer = LYING_DEAD_MOB_LAYER // Dead mobs should layer under living ones - else if(layer == initial(layer)) //to avoid things like hiding larvas. + else if(body_position == LYING_DOWN && layer == initial(layer)) //to avoid things like hiding larvas. //i have no idea what this means layer = LYING_LIVING_MOB_LAYER /// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment. diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 43e94361676d..88bd8e09c386 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -14,15 +14,14 @@ var/brainloss = 0 //'Retardation' damage caused by someone hitting you in the head with a bible or being infected with brainrot. var/halloss = 0 //Hallucination damage. 'Fake' damage obtained through hallucinating or the holodeck. Sleeping should cause it to wear off. - // please don't use these - VAR_PROTECTED/knocked_out = 0 - VAR_PROTECTED/knocked_down = 0 - VAR_PROTECTED/stunned = 0 + // please don't use these directly, use the procs var/dazed = 0 var/slowed = 0 // X_SLOW_AMOUNT var/superslowed = 0 // X_SUPERSLOW_AMOUNT var/sleeping = 0 + ///a list of all status effects the mob has + var/list/status_effects /// Cooldown for manually toggling resting to avoid spamming COOLDOWN_DECLARE(rest_cooldown) diff --git a/code/modules/mob/living/living_health_procs.dm b/code/modules/mob/living/living_health_procs.dm index e4c9659db827..50e59622f132 100644 --- a/code/modules/mob/living/living_health_procs.dm +++ b/code/modules/mob/living/living_health_procs.dm @@ -81,55 +81,51 @@ /mob/living/proc/setMaxHealth(newMaxHealth) maxHealth = newMaxHealth - -/mob/living - VAR_PROTECTED/stun_timer = TIMER_ID_NULL - -/mob/living/proc/stun_callback() - REMOVE_TRAIT(src, TRAIT_INCAPACITATED, STUNNED_TRAIT) - stunned = 0 - handle_regular_status_updates(FALSE) - if(stun_timer != TIMER_ID_NULL) - deltimer(stun_timer) - stun_timer = TIMER_ID_NULL - -/mob/living/proc/stun_callback_check() - if(stunned) - ADD_TRAIT(src, TRAIT_INCAPACITATED, STUNNED_TRAIT) - if(stunned && stunned < recovery_constant) - stun_timer = addtimer(CALLBACK(src, PROC_REF(stun_callback)), (stunned/recovery_constant) * 2 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE|TIMER_STOPPABLE) +/* STUN (Incapacitation) */ +/// Overridable handler to adjust the numerical value of status effects. Expand as needed +/mob/living/proc/GetStunDuration(amount) + return amount * GLOBAL_STATUS_MULTIPLIER +/mob/living/proc/IsStun() //If we're stunned + return has_status_effect(/datum/status_effect/incapacitating/stun) +/mob/living/proc/AmountStun() //How much time remain in our stun - scaled by GLOBAL_STATUS_MULTIPLIER (normally in multiples of legacy 2 seconds) + var/datum/status_effect/incapacitating/stun/S = IsStun() + if(S) + return S.get_duration_left() / GLOBAL_STATUS_MULTIPLIER + return 0 +/mob/living/proc/Stun(amount) + if(!(status_flags & CANSTUN)) return - if(!stunned) // Force reset since the timer wasn't called - stun_callback() + amount = GetStunDuration(amount) + var/datum/status_effect/incapacitating/stun/S = IsStun() + if(S) + S.update_duration(amount, increment = TRUE) + else if(amount > 0) + S = apply_status_effect(/datum/status_effect/incapacitating/stun, amount) + return S +/mob/living/proc/SetStun(amount, ignore_canstun = FALSE) //Sets remaining duration + if(!(status_flags & CANSTUN)) return - - if(stun_timer != TIMER_ID_NULL) - deltimer(stun_timer) - stun_timer = TIMER_ID_NULL - -// adjust stun if needed, do not call it in adjust stunned -/mob/living/proc/stun_clock_adjustment() - return - -/mob/living/proc/Stun(amount) - if(status_flags & CANSTUN) - stunned = max(max(stunned,amount),0) //can't go below 0, getting a low amount of stun doesn't lower your current stun - stun_clock_adjustment() - stun_callback_check() - return - -/mob/living/proc/SetStun(amount) //if you REALLY need to set stun to a set amount without the whole "can't go below current stunned" - if(status_flags & CANSTUN) - stunned = max(amount,0) - stun_clock_adjustment() - stun_callback_check() - return - -/mob/living/proc/AdjustStun(amount) - if(status_flags & CANSTUN) - stunned = max(stunned + amount,0) - stun_callback_check() - return + amount = GetStunDuration(amount) + var/datum/status_effect/incapacitating/stun/S = IsStun() + if(amount <= 0) + if(S) + qdel(S) + else + if(S) + S.update_duration(amount) + else + S = apply_status_effect(/datum/status_effect/incapacitating/stun, amount) + return S +/mob/living/proc/AdjustStun(amount, ignore_canstun = FALSE) //Adds to remaining duration + if(!(status_flags & CANSTUN)) + return + amount = GetStunDuration(amount) + var/datum/status_effect/incapacitating/stun/S = IsStun() + if(S) + S.adjust_duration(amount) + else if(amount > 0) + S = apply_status_effect(/datum/status_effect/incapacitating/stun, amount) + return S /mob/living/proc/Daze(amount) if(status_flags & CANDAZE) @@ -174,103 +170,113 @@ SetSuperslow(superslowed + amount) return -/mob/living - VAR_PRIVATE/knocked_down_timer +/* KnockDown (Flooring) */ +/// Overridable handler to adjust the numerical value of status effects. Expand as needed +/mob/living/proc/GetKnockDownDuration(amount) + return amount * GLOBAL_STATUS_MULTIPLIER -/mob/living/proc/knocked_down_callback() - remove_traits(list(TRAIT_FLOORED, TRAIT_INCAPACITATED), KNOCKEDDOWN_TRAIT) - knocked_down = 0 - handle_regular_status_updates(FALSE) - knocked_down_timer = null +/mob/living/proc/IsKnockDown() + return has_status_effect(/datum/status_effect/incapacitating/knockdown) -/mob/living/proc/knocked_down_callback_check() - if(knocked_down) - add_traits(list(TRAIT_FLOORED, TRAIT_INCAPACITATED), KNOCKEDDOWN_TRAIT) +///How much time remains - scaled by GLOBAL_STATUS_MULTIPLIER (normally in multiples of legacy 2 seconds) +/mob/living/proc/AmountKnockDown() + var/datum/status_effect/incapacitating/knockdown/S = IsKnockDown() + if(S) + return S.get_duration_left() / GLOBAL_STATUS_MULTIPLIER + return 0 - if(knocked_down && knocked_down < recovery_constant) - knocked_down_timer = addtimer(CALLBACK(src, PROC_REF(knocked_down_callback)), (knocked_down/recovery_constant) * 2 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE|TIMER_STOPPABLE) // times whatever amount we have per tick +/mob/living/proc/KnockDown(amount) + if(!(status_flags & CANKNOCKDOWN)) return - - if(!knocked_down) // Force reset since the timer wasn't called - knocked_down_callback() + amount = GetKnockDownDuration(amount) + var/datum/status_effect/incapacitating/knockdown/S = IsKnockDown() + if(S) + S.update_duration(amount, increment = TRUE) + else if(amount > 0) + S = apply_status_effect(/datum/status_effect/incapacitating/knockdown, amount) + return S + +///Sets exact remaining KnockDown duration +/mob/living/proc/SetKnockDown(amount, ignore_canstun = FALSE) + if(!(status_flags & CANKNOCKDOWN)) return - - if(knocked_down_timer) - deltimer(knocked_down_timer) - knocked_down_timer = null - -/mob/living - VAR_PRIVATE/knocked_out_timer - -/mob/living/proc/knocked_out_start() - return - -/mob/living/proc/knocked_out_callback() - REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, KNOCKEDOUT_TRAIT) - knocked_out = 0 - handle_regular_status_updates(FALSE) - knocked_out_timer = null - -/mob/living/proc/knocked_out_callback_check() - if(knocked_out) - ADD_TRAIT(src, TRAIT_KNOCKEDOUT, KNOCKEDOUT_TRAIT) - if(knocked_out && knocked_out < recovery_constant) - knocked_out_timer = addtimer(CALLBACK(src, PROC_REF(knocked_out_callback)), (knocked_out/recovery_constant) * 2 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE|TIMER_STOPPABLE) // times whatever amount we have per tick + amount = GetKnockDownDuration(amount) + var/datum/status_effect/incapacitating/knockdown/S = IsKnockDown() + if(amount <= 0) + if(S) + qdel(S) + else + if(S) + S.update_duration(amount) + else + S = apply_status_effect(/datum/status_effect/incapacitating/knockdown, amount) + return S + +///Adds to remaining Knockdown duration +/mob/living/proc/AdjustKnockDown(amount, ignore_canstun = FALSE) + if(!(status_flags & CANKNOCKDOWN)) return - else if(!knocked_out) - //It's been called, and we're probably inconscious, so fix that. - knocked_out_callback() - - if(knocked_out_timer) - deltimer(knocked_out_timer) - knocked_out_timer = null - -// adjust knockdown if needed, do not call it in adjust knockdown -/mob/living/proc/knockdown_clock_adjustment() - return - -/mob/living/proc/KnockDown(amount, force) - if((status_flags & CANKNOCKDOWN) || force) - knocked_down = max(max(knocked_down,amount),0) - knockdown_clock_adjustment() - knocked_down_callback_check() - return - -/mob/living/proc/SetKnockDown(amount) - if(status_flags & CANKNOCKDOWN) - knocked_down = max(amount,0) - knockdown_clock_adjustment() - knocked_down_callback_check() - return - -/mob/living/proc/AdjustKnockDown(amount) - if(status_flags & CANKNOCKDOWN) - knocked_down = max(knocked_down + amount,0) - knocked_down_callback_check() - return - -/mob/living/proc/knockout_clock_adjustment() - return - + amount = GetKnockDownDuration(amount) + var/datum/status_effect/incapacitating/knockdown/S = IsKnockDown() + if(S) + S.adjust_duration(amount) + else if(amount > 0) + S = apply_status_effect(/datum/status_effect/incapacitating/knockdown, amount) + return S + +/* KnockOut (Unconscious) */ +/// Overridable handler to adjust the numerical value of status effects. Expand as needed +/mob/living/proc/GetKnockOutDuration(amount) + return amount * GLOBAL_STATUS_MULTIPLIER + +/mob/living/proc/IsKnockOut() + return has_status_effect(/datum/status_effect/incapacitating/unconscious) + +/mob/living/proc/AmountKnockOut() //How much time remains - scaled by GLOBAL_STATUS_MULTIPLIER (normally in multiples of legacy 2 seconds) + var/datum/status_effect/incapacitating/unconscious/S = IsKnockOut() + if(S) + return S.get_duration_left() / GLOBAL_STATUS_MULTIPLIER + return 0 + +/// Sets Knockout duration to at least the amount provided /mob/living/proc/KnockOut(amount) - if(status_flags & CANKNOCKOUT) - knocked_out = max(max(knocked_out,amount),0) - knockout_clock_adjustment() - knocked_out_callback_check() - return - -/mob/living/proc/SetKnockOut(amount) - if(status_flags & CANKNOCKOUT) - knocked_out = max(amount,0) - knockout_clock_adjustment() - knocked_out_callback_check() - return - -/mob/living/proc/AdjustKnockOut(amount) - if(status_flags & CANKNOCKOUT) - knocked_out = max(knocked_out + amount,0) - knocked_out_callback_check() - return + if(!(status_flags & CANKNOCKOUT)) + return + amount = GetKnockOutDuration(amount) + var/datum/status_effect/incapacitating/unconscious/S = IsKnockOut() + if(S) + S.update_duration(amount, increment = TRUE) + else if(amount > 0) + S = apply_status_effect(/datum/status_effect/incapacitating/unconscious, amount) + return S + +/// Sets exact remaining Knockout duration +/mob/living/proc/SetKnockOut(amount, ignore_canstun = FALSE) + if(!(status_flags & CANKNOCKOUT)) + return + amount = GetKnockOutDuration(amount) + var/datum/status_effect/incapacitating/unconscious/S = IsKnockOut() + if(amount <= 0) + if(S) + qdel(S) + else + if(S) + S.update_duration(amount) + else + S = apply_status_effect(/datum/status_effect/incapacitating/unconscious, amount) + return S + +/// Adds to remaining Knockout duration +/mob/living/proc/AdjustKnockOut(amount, ignore_canstun = FALSE) + if(!(status_flags & CANKNOCKOUT)) + return + amount = GetKnockOutDuration(amount) + var/datum/status_effect/incapacitating/unconscious/S = IsKnockOut() + if(S) + S.adjust_duration(amount) + else if(amount > 0) + S = apply_status_effect(/datum/status_effect/incapacitating/unconscious, amount) + return S /mob/living/proc/Sleeping(amount) sleeping = max(max(sleeping,amount),0) @@ -416,8 +422,6 @@ /mob/living/proc/rejuvenate() heal_all_damage() - SEND_SIGNAL(src, COMSIG_LIVING_REJUVENATED) - // shut down ongoing problems status_flags &= ~PERMANENTLY_DEAD nutrition = NUTRITION_NORMAL @@ -458,6 +462,8 @@ set_stat(CONSCIOUS) regenerate_all_icons() + SEND_SIGNAL(src, COMSIG_LIVING_REJUVENATED) + /mob/living/proc/heal_all_damage() // shut down various types of badness @@ -503,11 +509,3 @@ return face_dir(direction) return ..() - -// Transition handlers. do NOT use this. I mean seriously don't. It's broken. Players love their broken behaviors. -/mob/living/proc/GetStunValueNotADurationDoNotUse() - return stunned -/mob/living/proc/GetKnockDownValueNotADurationDoNotUse() - return knocked_down -/mob/living/proc/GetKnockOutValueNotADurationDoNotUse() - return knocked_out diff --git a/code/modules/mob/living/living_verbs.dm b/code/modules/mob/living/living_verbs.dm index 88167feda3c0..3a97725a6fc4 100644 --- a/code/modules/mob/living/living_verbs.dm +++ b/code/modules/mob/living/living_verbs.dm @@ -1,3 +1,10 @@ +/mob/living/can_resist() + if(next_move > world.time) + return FALSE + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + return FALSE + return TRUE + /mob/living/verb/resist() set name = "Resist" set category = "IC" diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index 5b190143f5bc..7b0ee6869e80 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -34,9 +34,6 @@ // Gain Power apply_damage(-1, OXY) - // Handle EMP-stun - handle_stunned() - //stage = 1 //if (isRemoteControlling(src)) // Are we not sure what we are? var/blind = 0 diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index b4a6e59e52e2..ac031e74e11f 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -22,7 +22,7 @@ /mob/living/silicon/robot/proc/clamp_values() // set_effect(min(stunned, 30), STUN) - set_effect(min(knocked_out, 30), PARALYZE) +// set_effect(min(knocked_out, 30), PARALYZE) // set_effect(min(knocked_down, 20), WEAKEN) sleeping = 0 apply_damage(0, BRUTE) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 13d7c6d9b2ca..a1ef9032e435 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -93,10 +93,6 @@ if(health > maxHealth) health = maxHealth - handle_stunned() - handle_knocked_down(TRUE) - handle_knocked_out(TRUE) - //Movement if(!client && !stop_automated_movement && wander && !anchored) if(isturf(src.loc) && !resting && !buckled && (mobility_flags & MOBILITY_MOVE)) //This is so it only moves if it's not inside a closet, gentics machine, etc. diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c256f05e74b4..2ed1ee5e126c 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -833,10 +833,11 @@ note dizziness decrements automatically in the mob's Life() proc. selection.forceMove(get_turf(src)) return TRUE +///Can this mob resist (default FALSE) +/mob/proc/can_resist() + return FALSE + /mob/living/proc/handle_statuses() - handle_stunned() - handle_knocked_down() - handle_knocked_out() handle_stuttering() handle_silent() handle_drugged() @@ -845,11 +846,6 @@ note dizziness decrements automatically in the mob's Life() proc. handle_slowed() handle_superslowed() -/mob/living/proc/handle_stunned() - if(stunned) - adjust_effect(-1, STUN) - return stunned - /mob/living/proc/handle_dazed() if(dazed) adjust_effect(-1, DAZE) @@ -865,19 +861,6 @@ note dizziness decrements automatically in the mob's Life() proc. adjust_effect(-1, SUPERSLOW) return superslowed - -/mob/living/proc/handle_knocked_down(bypass_client_check = FALSE) - if(knocked_down && (bypass_client_check || client)) - knocked_down = max(knocked_down-1,0) - knocked_down_callback_check() - return knocked_down - -/mob/living/proc/handle_knocked_out(bypass_client_check = FALSE) - if(knocked_out && (bypass_client_check || client)) - knocked_out = max(knocked_out-1,0) - knocked_out_callback_check() - return knocked_out - /mob/living/proc/handle_stuttering() if(stuttering) stuttering = max(stuttering-1, 0) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 5d1baac3a534..6abe12eee9b1 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -313,9 +313,8 @@ GLOBAL_LIST_INIT(limb_types_by_name, list( /// Returns if the mob is incapacitated and unable to perform general actions /mob/proc/is_mob_incapacitated(ignore_restrained) - // note that stat includes knockout via unconscious - // TODO: re-re-re-figure out if we need TRAIT_FLOORED here or using TRAIT_INCAPACITATED only is acceptable deviance from legacy behavior return (stat || (!ignore_restrained && is_mob_restrained()) || (status_flags & FAKEDEATH) || HAS_TRAIT(src, TRAIT_INCAPACITATED)) + /mob/proc/get_eye_protection() return EYE_PROTECTION_NONE diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 65489944211a..8e9a513fdc88 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -182,10 +182,12 @@ move_delay += MOVE_REDUCTION_DIRECTION_LOCKED // by Geeves mob.cur_speed = Clamp(10/(move_delay + 0.5), MIN_SPEED, MAX_SPEED) - //We are now going to move - moving = TRUE - mob.move_intentionally = TRUE + next_movement = world.time + MINIMAL_MOVEMENT_INTERVAL // We pre-set this now for the crawling case. If crawling do_after fails, next_movement would be set after the attempt end instead of now. + + //Try to crawl first if(living_mob && living_mob.body_position == LYING_DOWN) + if(mob.crawling) + return // Already doing it. //check for them not being a limbless blob (only humans have limbs) if(ishuman(mob)) var/mob/living/carbon/human/human = mob @@ -194,19 +196,17 @@ if(!(human.get_limb(zone))) extremities.Remove(zone) if(extremities.len < 4) - next_movement = world.time + MINIMAL_MOVEMENT_INTERVAL - mob.move_intentionally = FALSE - moving = FALSE return //now crawl mob.crawling = TRUE if(!do_after(mob, 1 SECONDS, INTERRUPT_MOVED|INTERRUPT_UNCONSCIOUS|INTERRUPT_STUNNED|INTERRUPT_RESIST|INTERRUPT_CHANGED_LYING, NO_BUSY_ICON)) mob.crawling = FALSE - next_movement = world.time + MINIMAL_MOVEMENT_INTERVAL - mob.move_intentionally = FALSE - moving = FALSE return + if(!mob.crawling) + return // Crawling interrupted by a "real" move. Do nothing. In theory INTERRUPT_MOVED|INTERRUPT_CHANGED_LYING catches this in do_after. mob.crawling = FALSE + mob.move_intentionally = TRUE + moving = TRUE if(mob.confused) mob.Move(get_step(mob, pick(GLOB.cardinals))) else diff --git a/code/modules/projectiles/guns/specialist/sniper.dm b/code/modules/projectiles/guns/specialist/sniper.dm index 0cd9d8dd16c8..673de1a59602 100644 --- a/code/modules/projectiles/guns/specialist/sniper.dm +++ b/code/modules/projectiles/guns/specialist/sniper.dm @@ -453,7 +453,8 @@ if(PMC_sniper.body_position == STANDING_UP && !istype(PMC_sniper.wear_suit,/obj/item/clothing/suit/storage/marine/smartgunner/veteran/pmc) && !istype(PMC_sniper.wear_suit,/obj/item/clothing/suit/storage/marine/veteran)) PMC_sniper.visible_message(SPAN_WARNING("[PMC_sniper] is blown backwards from the recoil of the [src.name]!"),SPAN_HIGHDANGER("You are knocked prone by the blowback!")) step(PMC_sniper,turn(PMC_sniper.dir,180)) - PMC_sniper.apply_effect(5, WEAKEN) + PMC_sniper.KnockDown(5) + PMC_sniper.Stun(5) //Type 88 //Based on the actual Dragunov DMR rifle. diff --git a/code/modules/reagents/chemistry_reagents/drink.dm b/code/modules/reagents/chemistry_reagents/drink.dm index 66ce0844556b..9739687dec20 100644 --- a/code/modules/reagents/chemistry_reagents/drink.dm +++ b/code/modules/reagents/chemistry_reagents/drink.dm @@ -555,8 +555,7 @@ /datum/reagent/neurotoxin/on_mob_life(mob/living/carbon/M) . = ..() if(!.) return - if(!HAS_TRAIT(src, TRAIT_FLOORED)) - M.apply_effect(5, WEAKEN) + M.KnockDown(5) if(!data) data = 1 data++ M.dizziness +=6 diff --git a/code/modules/reagents/chemistry_reagents/toxin.dm b/code/modules/reagents/chemistry_reagents/toxin.dm index d9be565a85b2..445918ef284d 100644 --- a/code/modules/reagents/chemistry_reagents/toxin.dm +++ b/code/modules/reagents/chemistry_reagents/toxin.dm @@ -115,7 +115,8 @@ M.status_flags |= FAKEDEATH ADD_TRAIT(M, TRAIT_IMMOBILIZED, FAKEDEATH_TRAIT) M.apply_damage(0.5*REM, OXY) - M.apply_effect(2, WEAKEN) + M.KnockDown(2) + M.Stun(2) M.silent = max(M.silent, 10) /datum/reagent/toxin/zombiepowder/on_delete() diff --git a/code/modules/shuttle/helpers.dm b/code/modules/shuttle/helpers.dm index 6ab5d88da1b7..6b29f155582e 100644 --- a/code/modules/shuttle/helpers.dm +++ b/code/modules/shuttle/helpers.dm @@ -124,14 +124,13 @@ lockdown_door(air) /datum/door_controller/single/proc/bump_at_turf(turf/door_turf) - for(var/mob/blocking_mob in door_turf) - if(isliving(blocking_mob)) - to_chat(blocking_mob, SPAN_HIGHDANGER("You get thrown back as the [label] doors slam shut!")) - blocking_mob.apply_effect(4, WEAKEN) - for(var/turf/target_turf in orange(1, door_turf)) // Forcemove to a non shuttle turf - if(!istype(target_turf, /turf/open/shuttle) && !istype(target_turf, /turf/closed/shuttle)) - blocking_mob.forceMove(target_turf) - break + for(var/mob/living/blocking_mob in door_turf) + to_chat(blocking_mob, SPAN_HIGHDANGER("You get thrown back as the [label] doors slam shut!")) + blocking_mob.KnockDown(4) + for(var/turf/target_turf in orange(1, door_turf)) // Forcemove to a non shuttle turf + if(!istype(target_turf, /turf/open/shuttle) && !istype(target_turf, /turf/closed/shuttle)) + blocking_mob.forceMove(target_turf) + break /datum/door_controller/proc/lockdown_door(obj/structure/machinery/door/target) if(istype(target, /obj/structure/machinery/door/airlock)) diff --git a/code/modules/shuttle/shuttles/ert.dm b/code/modules/shuttle/shuttles/ert.dm index 1760caf3d87c..b619645c501c 100644 --- a/code/modules/shuttle/shuttles/ert.dm +++ b/code/modules/shuttle/shuttles/ert.dm @@ -61,14 +61,13 @@ INVOKE_ASYNC(src, PROC_REF(lockdown_door_launch), door) /obj/docking_port/mobile/emergency_response/proc/lockdown_door_launch(obj/structure/machinery/door/airlock/air) - for(var/mob/blocking_mob in air.loc) // Bump all mobs outta the way for outside airlocks of shuttles - if(isliving(blocking_mob)) - to_chat(blocking_mob, SPAN_HIGHDANGER("You get thrown back as the dropship doors slam shut!")) - blocking_mob.apply_effect(4, WEAKEN) - for(var/turf/target_turf in orange(1, air)) // Forcemove to a non shuttle turf - if(!istype(target_turf, /turf/open/shuttle) && !istype(target_turf, /turf/closed/shuttle)) - blocking_mob.forceMove(target_turf) - break + for(var/mob/living/blocking_mob in air.loc) // Bump all mobs outta the way for outside airlocks of shuttles + to_chat(blocking_mob, SPAN_HIGHDANGER("You get thrown back as the dropship doors slam shut!")) + blocking_mob.KnockDown(4) + for(var/turf/target_turf in orange(1, air)) // Forcemove to a non shuttle turf + if(!istype(target_turf, /turf/open/shuttle) && !istype(target_turf, /turf/closed/shuttle)) + blocking_mob.forceMove(target_turf) + break lockdown_door(air) /obj/docking_port/mobile/emergency_response/proc/lockdown_door(obj/structure/machinery/door/airlock/air) diff --git a/code/modules/shuttles/marine_ferry.dm b/code/modules/shuttles/marine_ferry.dm index 364d74824099..82c5b8e4403d 100644 --- a/code/modules/shuttles/marine_ferry.dm +++ b/code/modules/shuttles/marine_ferry.dm @@ -590,14 +590,13 @@ /datum/shuttle/ferry/marine/force_close_launch(obj/structure/machinery/door/AL) if(!iselevator) - for(var/mob/M in AL.loc) // Bump all mobs outta the way for outside airlocks of shuttles - if(isliving(M)) - to_chat(M, SPAN_HIGHDANGER("You get thrown back as the dropship doors slam shut!")) - M.apply_effect(4, WEAKEN) - for(var/turf/T in orange(1, AL)) // Forcemove to a non shuttle turf - if(!istype(T, /turf/open/shuttle) && !istype(T, /turf/closed/shuttle)) - M.forceMove(T) - break + for(var/mob/living/M in AL.loc) // Bump all mobs outta the way for outside airlocks of shuttles + to_chat(M, SPAN_HIGHDANGER("You get thrown back as the dropship doors slam shut!")) + M.KnockDown(4) + for(var/turf/T in orange(1, AL)) // Forcemove to a non shuttle turf + if(!istype(T, /turf/open/shuttle) && !istype(T, /turf/closed/shuttle)) + M.forceMove(T) + break return ..() // Sleeps /datum/shuttle/ferry/marine/open_doors(list/L) diff --git a/code/modules/shuttles/shuttle.dm b/code/modules/shuttles/shuttle.dm index 319d75e344c3..dc6f3a682b24 100644 --- a/code/modules/shuttles/shuttle.dm +++ b/code/modules/shuttles/shuttle.dm @@ -29,8 +29,6 @@ var/iselevator = 0 //Used to remove some shuttle related procs and texts to make it compatible with elevators var/almayerelevator = 0 //elevators on the almayer without limitations - var/list/last_passangers = list() //list of living creatures that were our last passengers - var/require_link = FALSE var/linked = FALSE var/ambience_muffle = MUFFLE_HIGH @@ -202,9 +200,7 @@ origin.move_contents_to(destination, direction=direction) - last_passangers.Cut() - for(var/mob/M in destination) - last_passangers += M + for(var/mob/living/M in destination) if(M.client) spawn(0) if(M.buckled && !iselevator) @@ -215,7 +211,8 @@ shake_camera(M, iselevator? 2 : 10, 1) if(istype(M, /mob/living/carbon) && !iselevator) if(!M.buckled) - M.apply_effect(3, WEAKEN) + M.Stun(3) + M.KnockDown(3) for(var/turf/T in origin) // WOW so hacky - who cares. Abby if(iselevator) diff --git a/colonialmarines.dme b/colonialmarines.dme index 27acc51dc015..64f1338244b4 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -31,6 +31,7 @@ #include "code\__DEFINES\_tick.dm" #include "code\__DEFINES\access.dm" #include "code\__DEFINES\admin.dm" +#include "code\__DEFINES\alerts.dm" #include "code\__DEFINES\ARES.dm" #include "code\__DEFINES\assert.dm" #include "code\__DEFINES\atmospherics.dm" @@ -98,6 +99,7 @@ #include "code\__DEFINES\speech_channels.dm" #include "code\__DEFINES\stamina.dm" #include "code\__DEFINES\stats.dm" +#include "code\__DEFINES\status_effects.dm" #include "code\__DEFINES\STUI.dm" #include "code\__DEFINES\subsystems.dm" #include "code\__DEFINES\supply.dm" @@ -307,10 +309,12 @@ #include "code\controllers\subsystem\init\lobby_art.dm" #include "code\controllers\subsystem\processing\defprocess.dm" #include "code\controllers\subsystem\processing\effects.dm" +#include "code\controllers\subsystem\processing\fasteffects.dm" #include "code\controllers\subsystem\processing\fastobj.dm" #include "code\controllers\subsystem\processing\hive_status.dm" #include "code\controllers\subsystem\processing\obj_tab_items.dm" #include "code\controllers\subsystem\processing\objects.dm" +#include "code\controllers\subsystem\processing\oldeffects.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\shield_pillar.dm" #include "code\controllers\subsystem\processing\slowobj.dm" @@ -634,6 +638,12 @@ #include "code\datums\statistics\random_facts\kills_fact.dm" #include "code\datums\statistics\random_facts\random_fact.dm" #include "code\datums\statistics\random_facts\revives_fact.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\limited_effect.dm" +#include "code\datums\status_effects\stacking_effect.dm" +#include "code\datums\status_effects\debuffs\debuffs.dm" #include "code\datums\supply_packs\_supply_packs.dm" #include "code\datums\supply_packs\ammo.dm" #include "code\datums\supply_packs\attachments.dm" diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index af61a47aa885..21cc40876fbc 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ