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