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
R*uT`_Xg=3btyZ>){vW$T=)wR1
literal 10717
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
From a11454084cd720b62e7b0210eec2e279620823e9 Mon Sep 17 00:00:00 2001
From: cm13-github <128137806+cm13-github@users.noreply.github.com>
Date: Mon, 18 Dec 2023 20:03:43 +0000
Subject: [PATCH 08/23] Automatic changelog for PR #5214 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5214.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5214.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5214.yml b/html/changelogs/AutoChangeLog-pr-5214.yml
new file mode 100644
index 000000000000..5613e788b043
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5214.yml
@@ -0,0 +1,4 @@
+author: "NessiePendragon"
+delete-after: True
+changes:
+ - rscadd: "Added new sprites for Warrior."
\ No newline at end of file
From 553fa38db201d7669c454a75f0e7c83d4dc541db Mon Sep 17 00:00:00 2001
From: Changelogs
Date: Tue, 19 Dec 2023 01:11:24 +0000
Subject: [PATCH 09/23] Automatic changelog compile [ci skip]
---
html/changelogs/AutoChangeLog-pr-4960.yml | 8 -------
html/changelogs/AutoChangeLog-pr-5137.yml | 4 ----
html/changelogs/AutoChangeLog-pr-5170.yml | 4 ----
html/changelogs/AutoChangeLog-pr-5199.yml | 4 ----
html/changelogs/AutoChangeLog-pr-5201.yml | 5 ----
html/changelogs/AutoChangeLog-pr-5203.yml | 5 ----
html/changelogs/AutoChangeLog-pr-5204.yml | 4 ----
html/changelogs/AutoChangeLog-pr-5214.yml | 4 ----
html/changelogs/AutoChangeLog-pr-5239.yml | 4 ----
html/changelogs/archive/2023-12.yml | 28 +++++++++++++++++++++++
10 files changed, 28 insertions(+), 42 deletions(-)
delete mode 100644 html/changelogs/AutoChangeLog-pr-4960.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5137.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5170.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5199.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5201.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5203.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5204.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5214.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5239.yml
diff --git a/html/changelogs/AutoChangeLog-pr-4960.yml b/html/changelogs/AutoChangeLog-pr-4960.yml
deleted file mode 100644
index b59deca2ee14..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4960.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-author: "realforest2001"
-delete-after: True
-changes:
- - rscadd: "Added a proc for comparing the registered name of an ID, to the real name of a mob. Also checks registered_ref if one exists."
- - rscadd: "Evacuation can no longer be cancelled without passing above check."
- - rscadd: "People forced into escape-pod stasis bays against their wishes can now eject themselves."
- - bugfix: "Xenos can no longer force humans into escape-pod stasis bays."
- - bugfix: "Escape-pod stasis bays no longer accept corpses."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5137.yml b/html/changelogs/AutoChangeLog-pr-5137.yml
deleted file mode 100644
index b67685a48b1a..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5137.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Drathek"
-delete-after: True
-changes:
- - balance: "Added the possibility of surgery steps failing based on tool and surface suitability compensated by surgery skill."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5170.yml b/html/changelogs/AutoChangeLog-pr-5170.yml
deleted file mode 100644
index 2b2965b83eb7..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5170.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "SabreML"
-delete-after: True
-changes:
- - qol: "Added a 'Ghost' button for dead xenomorphs."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5199.yml b/html/changelogs/AutoChangeLog-pr-5199.yml
deleted file mode 100644
index 6e061baf9d11..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5199.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "stalkerino"
-delete-after: True
-changes:
- - balance: "fixes the balance of the game by making hair gradient trait free"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5201.yml b/html/changelogs/AutoChangeLog-pr-5201.yml
deleted file mode 100644
index 7a974cdea4bd..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5201.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "SabreML"
-delete-after: True
-changes:
- - rscadd: "Updated the 'help' message for xeno special structure construction."
- - qol: "Added a 'remaining' counter when constructing special structures to let players know how many can still be built."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5203.yml b/html/changelogs/AutoChangeLog-pr-5203.yml
deleted file mode 100644
index 7f28fb942289..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5203.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "PurpleCIoud"
-delete-after: True
-changes:
- - imageadd: "added chocolate bar new sprite"
- - imagedel: "deleted old chocolate bar sprite"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5204.yml b/html/changelogs/AutoChangeLog-pr-5204.yml
deleted file mode 100644
index 93eb9f0d773a..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5204.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "NateDross"
-delete-after: True
-changes:
- - bugfix: "Requisitions elevator lighting fix"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5214.yml b/html/changelogs/AutoChangeLog-pr-5214.yml
deleted file mode 100644
index 5613e788b043..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5214.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "NessiePendragon"
-delete-after: True
-changes:
- - rscadd: "Added new sprites for Warrior."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5239.yml b/html/changelogs/AutoChangeLog-pr-5239.yml
deleted file mode 100644
index 8bda529cf5e6..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5239.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Huffie56"
-delete-after: True
-changes:
- - bugfix: "fix a nightmare insert that had a wall and a door on same tile."
\ No newline at end of file
diff --git a/html/changelogs/archive/2023-12.yml b/html/changelogs/archive/2023-12.yml
index 4239bcc1ab53..4a882e76a8d1 100644
--- a/html/changelogs/archive/2023-12.yml
+++ b/html/changelogs/archive/2023-12.yml
@@ -346,3 +346,31 @@
- bugfix: fixed landing zone camera on all map in rotation.
sleepynecrons:
- imageadd: marine snow uniforms and armors given a new look
+2023-12-19:
+ Drathek:
+ - balance: Added the possibility of surgery steps failing based on tool and surface
+ suitability compensated by surgery skill.
+ Huffie56:
+ - bugfix: fix a nightmare insert that had a wall and a door on same tile.
+ NateDross:
+ - bugfix: Requisitions elevator lighting fix
+ NessiePendragon:
+ - rscadd: Added new sprites for Warrior.
+ PurpleCIoud:
+ - imageadd: added chocolate bar new sprite
+ - imagedel: deleted old chocolate bar sprite
+ SabreML:
+ - rscadd: Updated the 'help' message for xeno special structure construction.
+ - qol: Added a 'remaining' counter when constructing special structures to let players
+ know how many can still be built.
+ - qol: Added a 'Ghost' button for dead xenomorphs.
+ realforest2001:
+ - rscadd: Added a proc for comparing the registered name of an ID, to the real name
+ of a mob. Also checks registered_ref if one exists.
+ - rscadd: Evacuation can no longer be cancelled without passing above check.
+ - rscadd: People forced into escape-pod stasis bays against their wishes can now
+ eject themselves.
+ - bugfix: Xenos can no longer force humans into escape-pod stasis bays.
+ - bugfix: Escape-pod stasis bays no longer accept corpses.
+ stalkerino:
+ - balance: fixes the balance of the game by making hair gradient trait free
From 55f9dd8d39bcdd3a1e6c8c72f128c6f4447111dc Mon Sep 17 00:00:00 2001
From: fira
Date: Tue, 19 Dec 2023 06:39:07 +0100
Subject: [PATCH 10/23] /tg/ Status Effects Part 2 - datum, KD, KO, Stuns
(#4842)
# About the pull request
Part 2 - this includes porting the actual status_effect datum, modifying
it to fit our purposes by backing it with timers similarly to old
system, and finally implementing KD, KO and Stun with it.
This contains Part 1 PR (#4828) so if you want to take a look at it I'd
advise checking the last commits or setting up a compare between both
branches.
# Explain why it's good for the game
Predictable status timers. Current ones are bogus in their handling of
"life tick correction" and will "stack" time even when they're not
supposed to.
Also provides a more robust backend for general effects, and integrates
status effects into it.
# Testing Photographs and Procedure
Summary testing of buckling interactions, explosion knock times,
crawling, resting. Will have to be expanded once part 1 is ready
# Changelog
:cl:
add: Added Buckled, Handcuffed and Legcuffed screen alerts
code: Ported /tg/ status effects backend, modified with timers to let
effects end at appropriate time
code: Stun, Knockdown and Knockout now use the new effects backend
balance: Due to backend change, all KO/KD/Stuns may behave differently
timing wise. This is of course subject to adjustments.
balance: Endurance is now set at 8% effect duration reduction per level
above 1. However it now compounds with species bonus. Feel free to
adjust.
balance: Knockdowns are not inherently incapacitating anymore and many
sources of it have been updated to also stun to make up for it.
fix: KO/KD/Stuns do not artificially and randomly ''stack'' due to
incorrect timer offset calculation anymore.
fix: Stuns now correctly apply Stun reduction values instead of
Knockdown reductions.
fix: Crawling can now be interrupted by a normal move, if you are fit
enough to do so.
/:cl:
---------
Co-authored-by: forest2001 <41653574+realforest2001@users.noreply.github.com>
---
code/__DEFINES/alerts.dm | 7 +
.../signals/atom/mob/living/signals_living.dm | 5 -
.../dcs/signals/atom/mob/signals_mob.dm | 3 +
code/__DEFINES/mobs.dm | 3 +
code/__DEFINES/status_effects.dm | 25 ++
code/__DEFINES/subsystems.dm | 4 +-
code/_onclick/hud/hud.dm | 1 +
.../subsystem/processing/effects.dm | 1 -
.../subsystem/processing/fasteffects.dm | 4 +
.../subsystem/processing/oldeffects.dm | 5 +
code/datums/ammo/ammo.dm | 3 +-
code/datums/ammo/bullet/rifle.dm | 3 +-
code/datums/ammo/bullet/shotgun.dm | 12 +-
code/datums/ammo/energy.dm | 17 +-
code/datums/ammo/rocket.dm | 2 -
code/datums/ammo/xeno.dm | 16 +-
code/datums/effects/_effects.dm | 4 +-
code/datums/status_effects/_status_effect.dm | 277 +++++++++++++++++
.../status_effects/_status_effect_helpers.dm | 136 ++++++++
code/datums/status_effects/debuffs/debuffs.dm | 104 +++++++
code/datums/status_effects/grouped_effect.dm | 20 ++
code/datums/status_effects/limited_effect.dm | 20 ++
code/datums/status_effects/stacking_effect.dm | 101 ++++++
.../gamemodes/colonialmarines/huntergames.dm | 3 +-
code/game/machinery/doors/airlock.dm | 3 +-
code/game/machinery/flasher.dm | 6 +-
.../game/machinery/medical_pod/bodyscanner.dm | 4 +-
code/game/machinery/medical_pod/sleeper.dm | 2 +-
code/game/objects/effects/aliens.dm | 4 +-
code/game/objects/objs.dm | 2 +
code/game/objects/structures/tables_racks.dm | 7 +-
code/modules/cm_aliens/Ovipositor.dm | 8 +-
.../modules/cm_marines/NonLethalRestraints.dm | 5 +-
code/modules/maptext_alerts/screen_alerts.dm | 65 ++++
code/modules/mob/living/carbon/human/human.dm | 4 +-
.../living/carbon/human/human_attackhand.dm | 9 +-
.../mob/living/carbon/human/human_damage.dm | 8 -
code/modules/mob/living/carbon/human/life.dm | 7 +
.../life/handle_regular_status_updates.dm | 2 +-
.../carbon/human/life/handle_stasis_bag.dm | 8 +-
.../living/carbon/human/life/life_helpers.dm | 77 +----
.../living/carbon/human/species/species.dm | 9 +-
code/modules/mob/living/carbon/inventory.dm | 7 +
.../mob/living/carbon/xenomorph/XenoProcs.dm | 12 +-
.../mob/living/carbon/xenomorph/Xenomorph.dm | 9 -
.../xenomorph/abilities/general_abilities.dm | 2 +
.../xenomorph/abilities/general_powers.dm | 7 +
.../abilities/warrior/warrior_abilities.dm | 2 +-
.../abilities/warrior/warrior_powers.dm | 4 +-
.../living/carbon/xenomorph/attack_alien.dm | 6 +-
.../carbon/xenomorph/castes/Sentinel.dm | 3 +-
.../living/carbon/xenomorph/castes/Warrior.dm | 4 +-
.../living/carbon/xenomorph/damage_procs.dm | 1 +
.../mob/living/carbon/xenomorph/life.dm | 65 ++--
.../living/carbon/xenomorph/update_icons.dm | 13 +
code/modules/mob/living/damage_procs.dm | 11 +-
code/modules/mob/living/living.dm | 25 +-
code/modules/mob/living/living_defines.dm | 7 +-
.../modules/mob/living/living_health_procs.dm | 292 +++++++++---------
code/modules/mob/living/living_verbs.dm | 7 +
code/modules/mob/living/silicon/ai/life.dm | 3 -
code/modules/mob/living/silicon/robot/life.dm | 2 +-
.../mob/living/simple_animal/simple_animal.dm | 4 -
code/modules/mob/mob.dm | 25 +-
code/modules/mob/mob_helpers.dm | 3 +-
code/modules/mob/mob_movement.dm | 18 +-
.../projectiles/guns/specialist/sniper.dm | 3 +-
.../reagents/chemistry_reagents/drink.dm | 3 +-
.../reagents/chemistry_reagents/toxin.dm | 3 +-
code/modules/shuttle/helpers.dm | 15 +-
code/modules/shuttle/shuttles/ert.dm | 15 +-
code/modules/shuttles/marine_ferry.dm | 15 +-
code/modules/shuttles/shuttle.dm | 9 +-
colonialmarines.dme | 10 +
icons/mob/screen_alert.dmi | Bin 765 -> 2304 bytes
75 files changed, 1172 insertions(+), 439 deletions(-)
create mode 100644 code/__DEFINES/alerts.dm
create mode 100644 code/__DEFINES/status_effects.dm
create mode 100644 code/controllers/subsystem/processing/fasteffects.dm
create mode 100644 code/controllers/subsystem/processing/oldeffects.dm
create mode 100644 code/datums/status_effects/_status_effect.dm
create mode 100644 code/datums/status_effects/_status_effect_helpers.dm
create mode 100644 code/datums/status_effects/debuffs/debuffs.dm
create mode 100644 code/datums/status_effects/grouped_effect.dm
create mode 100644 code/datums/status_effects/limited_effect.dm
create mode 100644 code/datums/status_effects/stacking_effect.dm
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 af61a47aa8858a88e45cb178d498046ee629321a..21cc40876fbc8c7b8928bf947b9963ba6c0a894f 100644
GIT binary patch
delta 2185
zcmV;42zK}V1%MKe7Y;xO1^@s6s%dfF0001wktKV7LDHF2nw*`Jnu1HGvVyCh3)t-d
z?|L^ZDnA};000OqNkl1TW=f36+W}OBzJfhFS=2dXj!Qx8%-@ck`WsU+Bk8W
zzy{J5FzUJ}QuHM+&L7A_-vjia^<&Vtrit^^r#`i)&?0G+z=(~=@>RAR$ChPN7fRwq
z+~sb6<_xvW@*Y-#X*ILEvpajvd~@c^oDrFxo`y4Le!U4Dz$c&l&D)83&5%&|
zXtb{*m1EtVR)S#9cyX5mLhUZ}ec#O>REw~FtmrGyd&I2^8QGMTIpuvI`sVI3@M
ziD`!L@pzo4!CgRYe_!qn?=%Er*}A{Sd}2oia3`n<*oFb^g6$d5ENsPqay!6hfK!!ate`{z
z*gDTb0n5uzEI(%!+;Dgdhsf*z8RhDKRfH`VP;ULpJ;9w6QI8UAZ3A{%5wZeXFu)9>
zwGF}17wG!yB6o-Jp4It)Ef`>?vLPVO1yFeO1B|(VS@`G12H?srE^b!y1gnh#2t)XK
zR)s4@0Yp2O#SvCV0kaaUJW~%!6fi%3OWr9kVnr#nS{VZ)gwzby6oCN^BREojuRzt_
zQR{wD0T`*lhz)CUDqL(%Q=*sDq$YB
zNyqb=&VDtB4TK<-(lVX=*mgj`)|X|U90)|DNTe4c;U0*Fdm+ryFsAHUG|~erD=NHo
zYLfo;+#3}46SU&!6!XmB3?MpxYjf7xiNq4i>R_-y&uhH(31~1l6a(t^KwjBudLO}V
zvNc5`h{Ah)UI>T6I^n|@qkcZm6rjMPN6T<
zgm8n1D$qrNbpe5$vumn<1`|K(r#BZ;mWRbgSA^Py5>HS8FUSsw^?wmug2MWH!4L`}
zQvvJ$$&{eZ&fZn9?6D(z=*0K{{pa*mOG?lxBP+i|_!ra599J}r)FISQxs^**8`rC3;WdOFmx#f5|
zw+vrjV?ELWv5_+tVVB+DlLcxk2Fw5yE%_)wiDfT8$GJhy
zEfa|MLVTbbfO(DY6HWMo;M71D;KQMQ;k}^&4Q6k^jG}rD++V@xcFk;%ba(gj{}{nJ
z`ta~b)@n?D!0UBN@xh1`bi0Wt=$uo9=@b4H`CO`C2Sr%lOv4wKufX^Z50g)4X49-9s_@j
z#dM$Sg4jSGZL4&3pn?IDn#TL~c3VQVydV(b5^|1_zFuK#V}-oXy${?Dt*9v;`60aR
zODeM
zD=0vJ14`Yv;s{S$Tf>5L^E#wO;Ovr!&c9{Yi*&i@6J_b%v1T~ii
z$q@plWC%qHbN}+t<~9K^-jL6$4Ssg(P~iuE;66GWMd<#yQv?hPM+?lA!y0|fZ^f&5bpBBgTy+7nG(29U^0MaCCIXe0*lJR1U0@KkC;S6Elb=M?z)+23`P0VN7>
zXa+|PkJ5EERSreG;1VSWx(Qc;Zi+M`i!_#|vpM+c@|=F>-ct7CiywB>6(+6x_xXv5
zqcjlkLYN)6@Z)tAEFSirysmP4xRgl3mtS6nZx$bCE?(^X{($cQ?9&s)>T3Pe00000
LNkvXXu0mjfz`q|&
delta 634
zcmV-=0)_p668!~`7Y-l@1^@s6qMd$(0001nktKV7G2~KKaP@Nmy9ogPWh=C6aoaZl
z00J^eL_t(|oaI@~Y63A3&WchG^{1eP_9WEadoK7C1*xU4VBe+n6-q5Wg;40NPoRQ5
z2~}H#9z0o{z$~-tZZLrihBL-S0sQ==jAvFyA)|~zFa;_
z#5mZ0PMU-?EsV#b>^_XH318^eZ8UEGY;3jLua4@wO8_{Z&*w#{bQ(Fh<#OpMr&1~H
z0Zk@fS+J!;0(HJxz0|e===XbWA1V?&3dm%R7W4Uh!@*qvhyokypGHbU=PQ+pd>4zw
zz+>Sbq0S^X5GyuYSx?L`=8U}D?Y46
UY!<_skpKVy07*qoM6N<$g6GR38~^|S
From 6103fdee3a4af966070f129dc2f0c353479659b9 Mon Sep 17 00:00:00 2001
From: cm13-github <128137806+cm13-github@users.noreply.github.com>
Date: Tue, 19 Dec 2023 05:47:08 +0000
Subject: [PATCH 11/23] Automatic changelog for PR #4842 [ci skip]
---
html/changelogs/AutoChangeLog-pr-4842.yml | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-4842.yml
diff --git a/html/changelogs/AutoChangeLog-pr-4842.yml b/html/changelogs/AutoChangeLog-pr-4842.yml
new file mode 100644
index 000000000000..6c1c16a996b3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4842.yml
@@ -0,0 +1,12 @@
+author: "fira"
+delete-after: True
+changes:
+ - rscadd: "Added Buckled, Handcuffed and Legcuffed screen alerts"
+ - code_imp: "Ported /tg/ status effects backend, modified with timers to let effects end at appropriate time"
+ - code_imp: "Stun, Knockdown and Knockout now use the new effects backend"
+ - balance: "Due to backend change, all KO/KD/Stuns may behave differently timing wise. This is of course subject to adjustments."
+ - balance: "Endurance is now set at 8% effect duration reduction per level above 1. However it now compounds with species bonus. Feel free to adjust."
+ - balance: "Knockdowns are not inherently incapacitating anymore and many sources of it have been updated to also stun to make up for it."
+ - bugfix: "KO/KD/Stuns do not artificially and randomly ''stack'' due to incorrect timer offset calculation anymore."
+ - bugfix: "Stuns now correctly apply Stun reduction values instead of Knockdown reductions."
+ - bugfix: "Crawling can now be interrupted by a normal move, if you are fit enough to do so."
\ No newline at end of file
From 9cdddd4210a6ab1ade6ecfcc1530adb889609b67 Mon Sep 17 00:00:00 2001
From: Doubleumc
Date: Tue, 19 Dec 2023 07:57:11 -0500
Subject: [PATCH 12/23] Projectile ref clears (#5164)
# About the pull request
Forgot to clear a reference I set in
https://github.com/cmss13-devs/cmss13/pull/4986 and cleared some others
I saw.
Not familiar enough with the Destroy/QDEL pipeline to know what else, if
anything, should be done with `weapon_cause_data` and `bullet_traits`
since they hold references themselves.
# Explain why it's good for the game
I will clean up after myself.
# Testing Photographs and Procedure
Screenshots & Videos
Put screenshots and videos here with an empty line between the
screenshots and the `` tags.
# Changelog
No player-facing changes.
---
code/modules/projectiles/projectile.dm | 3 +++
1 file changed, 3 insertions(+)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index ef265e0b8095..d6191898c8c2 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -96,7 +96,10 @@
starting = null
permutated = null
path = null
+ vis_source = null
+ process_start_turf = null
weapon_cause_data = null
+ bullet_traits = null
firer = null
QDEL_NULL(bound_beam)
SSprojectiles.stop_projectile(src)
From 8e5ff1efb1f35c7aae38b31eaa1dc6db0c5f7a18 Mon Sep 17 00:00:00 2001
From: Contrabang <91113370+Contrabang@users.noreply.github.com>
Date: Tue, 19 Dec 2023 13:39:17 -0500
Subject: [PATCH 13/23] Fixes matches doing brute instead of burn when
accidently burning yourself (#5243)
# About the pull request
Burning yourself when trying to start a match now does burn damage,
instead of brute.
# Explain why it's good for the game
If the text says it burns ya, it should probably burn ya.
# Testing Photographs and Procedure
Screenshots & Videos
ouch ow my fingers, burnt
# Changelog
:cl:
fix: Matches now do burn damage instead of brute, when you accidently
burn your own hand.
/:cl:
---
code/game/objects/items/storage/fancy.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm
index 9afa0dfd1851..d12f09c2042e 100644
--- a/code/game/objects/items/storage/fancy.dm
+++ b/code/game/objects/items/storage/fancy.dm
@@ -302,7 +302,7 @@
if(istype(W) && !W.heat_source && !W.burnt)
if(prob(burn_chance))
to_chat(user, SPAN_WARNING("\The [W] lights, but you burn your hand in the process! Ouch!"))
- user.apply_damage(3, BRUTE, pick("r_hand", "l_hand"))
+ user.apply_damage(3, BURN, pick("r_hand", "l_hand"))
if((user.pain.feels_pain) && prob(25))
user.emote("scream")
W.light_match()
From 308396378a15a6bc5f956245395db0aa52ed446b Mon Sep 17 00:00:00 2001
From: cm13-github <128137806+cm13-github@users.noreply.github.com>
Date: Tue, 19 Dec 2023 18:50:17 +0000
Subject: [PATCH 14/23] Automatic changelog for PR #5243 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5243.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5243.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5243.yml b/html/changelogs/AutoChangeLog-pr-5243.yml
new file mode 100644
index 000000000000..07bc1c732124
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5243.yml
@@ -0,0 +1,4 @@
+author: "Contrabang"
+delete-after: True
+changes:
+ - bugfix: "Matches now do burn damage instead of brute, when you accidently burn your own hand."
\ No newline at end of file
From 99d2d6535acfb27a10b20f5af883c3ac2e87662c Mon Sep 17 00:00:00 2001
From: forest2001 <41653574+realforest2001@users.noreply.github.com>
Date: Tue, 19 Dec 2023 18:39:54 +0000
Subject: [PATCH 15/23] Adds closeother to USS Almayer doors. (#5235)
# About the pull request
As title. Turns out we've had the ability for a long time and never used
it. Particularly useful in the Brig/CIC lobby.
# Explain why it's good for the game
# Testing Photographs and Procedure
Screenshots & Videos
Put screenshots and videos here with an empty line between the
screenshots and the `` tags.
# Changelog
:cl:
maptweak: Various doors around the Almayer will now close their
opposites for better security.
/:cl:
---
maps/map_files/USS_Almayer/USS_Almayer.dmm | 89 ++++++++++++++++------
1 file changed, 66 insertions(+), 23 deletions(-)
diff --git a/maps/map_files/USS_Almayer/USS_Almayer.dmm b/maps/map_files/USS_Almayer/USS_Almayer.dmm
index bf5663120e60..1c56dc0400ee 100644
--- a/maps/map_files/USS_Almayer/USS_Almayer.dmm
+++ b/maps/map_files/USS_Almayer/USS_Almayer.dmm
@@ -3320,7 +3320,8 @@
},
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
dir = 1;
- name = "\improper Brig Maintenance"
+ name = "\improper Brig Maintenance";
+ closeOtherId = "brigmaint_s"
},
/obj/structure/machinery/door/poddoor/almayer/open{
id = "perma_lockdown_2";
@@ -7522,7 +7523,8 @@
name = "\improper Combat Information Center Blast Door"
},
/obj/structure/machinery/door/airlock/almayer/command/reinforced{
- name = "\improper Combat Information Center"
+ name = "\improper Combat Information Center";
+ closeOtherId = "ciclobby_n"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -8116,7 +8118,8 @@
},
/obj/structure/machinery/door/airlock/almayer/command/reinforced{
id_tag = "cic_exterior";
- name = "\improper Combat Information Center"
+ name = "\improper Combat Information Center";
+ closeOtherId = "ciclobby_n"
},
/obj/structure/machinery/door/poddoor/almayer/open{
dir = 4;
@@ -24843,6 +24846,27 @@
icon_state = "red"
},
/area/almayer/living/cryo_cells)
+"ccc" = (
+/obj/effect/decal/warning_stripes{
+ icon_state = "E";
+ pixel_x = 1
+ },
+/obj/effect/decal/warning_stripes{
+ icon_state = "W";
+ pixel_x = -1
+ },
+/obj/structure/machinery/door/airlock/almayer/research/reinforced{
+ dir = 8;
+ name = "\improper Containment Airlock";
+ closeOtherId = "containment_n"
+ },
+/obj/structure/machinery/door/poddoor/almayer/biohazard/white{
+ dir = 4
+ },
+/turf/open/floor/almayer{
+ icon_state = "test_floor4"
+ },
+/area/almayer/medical/containment)
"ccd" = (
/obj/structure/machinery/cm_vending/sorted/cargo_guns/squad_prep,
/turf/open/floor/almayer{
@@ -26844,7 +26868,8 @@
access_modified = 1;
name = "\improper Astronavigational Deck";
req_access = null;
- req_one_access_txt = "3;19"
+ req_one_access_txt = "3;19";
+ closeOtherId = "astroladder_n"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -26856,7 +26881,8 @@
access_modified = 1;
name = "\improper Astronavigational Deck";
req_access = null;
- req_one_access_txt = "3;19"
+ req_one_access_txt = "3;19";
+ closeOtherId = "astroladder_s"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -29462,7 +29488,8 @@
dir = 1
},
/obj/structure/machinery/door/airlock/multi_tile/almayer/secdoor/glass/reinforced{
- name = "\improper Brig Lobby"
+ name = "\improper Brig Lobby";
+ closeOtherId = "brignorth"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -29998,7 +30025,8 @@
},
/obj/structure/machinery/door/airlock/almayer/command/reinforced{
id_tag = "cic_exterior";
- name = "\improper Combat Information Center"
+ name = "\improper Combat Information Center";
+ closeOtherId = "ciclobby_s"
},
/obj/structure/machinery/door/poddoor/almayer/open{
dir = 4;
@@ -33360,7 +33388,8 @@
"eKa" = (
/obj/structure/machinery/door/airlock/multi_tile/almayer/secdoor/glass/reinforced{
dir = 2;
- name = "\improper Brig Lobby"
+ name = "\improper Brig Lobby";
+ closeOtherId = "briglobby"
},
/obj/structure/machinery/door/firedoor/border_only/almayer,
/turf/open/floor/almayer{
@@ -39939,7 +39968,8 @@
dir = 4
},
/obj/structure/machinery/door/airlock/almayer/command/reinforced{
- name = "\improper Combat Information Center"
+ name = "\improper Combat Information Center";
+ closeOtherId = "ciclobby_s"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -41546,7 +41576,8 @@
},
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
dir = 1;
- name = "\improper Brig Prison Yard And Offices"
+ name = "\improper Brig Prison Yard And Offices";
+ closeOtherId = "brigcells"
},
/obj/structure/machinery/door/firedoor/border_only/almayer{
dir = 2
@@ -50405,7 +50436,8 @@
/area/almayer/hull/lower_hull/l_m_p)
"lFJ" = (
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
- name = "\improper Brig Prisoner Yard"
+ name = "\improper Brig Prisoner Yard";
+ closeOtherId = "brigcells"
},
/obj/structure/disposalpipe/segment{
dir = 8
@@ -51047,7 +51079,8 @@
/area/almayer/hull/upper_hull/u_f_p)
"lUm" = (
/obj/structure/machinery/door/airlock/multi_tile/almayer/secdoor/glass/reinforced{
- name = "\improper Brig Cells"
+ name = "\improper Brig Cells";
+ closeOtherId = "briglobby"
},
/obj/structure/machinery/door/firedoor/border_only/almayer{
dir = 1
@@ -52501,7 +52534,8 @@
access_modified = 1;
name = "\improper Astronavigational Deck";
req_access = null;
- req_one_access_txt = "3;19"
+ req_one_access_txt = "3;19";
+ closeOtherId = "astroladder_n"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -57833,7 +57867,8 @@
},
/obj/structure/machinery/door/airlock/almayer/research/reinforced{
dir = 8;
- name = "\improper Containment Airlock"
+ name = "\improper Containment Airlock";
+ closeOtherId = "containment_n"
},
/obj/structure/machinery/door/poddoor/almayer/biohazard/white{
dir = 4
@@ -61539,7 +61574,8 @@
},
/obj/structure/machinery/door/firedoor/border_only/almayer,
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
- name = "\improper Brig"
+ name = "\improper Brig";
+ closeOtherId = "brigmaint_n"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -64845,7 +64881,8 @@
/obj/structure/pipes/standard/simple/hidden/supply,
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
dir = 1;
- name = "\improper Warden's Office"
+ name = "\improper Warden's Office";
+ closeOtherId = "brigwarden"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -66928,7 +66965,8 @@
dir = 2;
name = "\improper Brig Armoury";
req_access = null;
- req_one_access_txt = "1;3"
+ req_one_access_txt = "1;3";
+ closeOtherId = "brignorth"
},
/turf/open/floor/almayer{
icon_state = "test_floor4"
@@ -70316,7 +70354,8 @@
/area/almayer/hallways/aft_hallway)
"tMH" = (
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
- name = "\improper Warden's Office"
+ name = "\improper Warden's Office";
+ closeOtherId = "brigwarden"
},
/obj/structure/machinery/door/poddoor/shutters/almayer/open{
dir = 4;
@@ -76333,7 +76372,8 @@
"wdo" = (
/obj/structure/machinery/door/airlock/almayer/research/reinforced{
dir = 8;
- name = "\improper Containment Airlock"
+ name = "\improper Containment Airlock";
+ closeOtherId = "containment_s"
},
/obj/effect/decal/warning_stripes{
icon_state = "S"
@@ -77179,7 +77219,8 @@
name = "\improper Brig Medbay";
req_access = null;
req_one_access = null;
- req_one_access_txt = "20;3"
+ req_one_access_txt = "20;3";
+ closeOtherId = "brigmed"
},
/obj/structure/machinery/door/firedoor/border_only/almayer,
/obj/structure/pipes/standard/simple/hidden/supply{
@@ -79122,7 +79163,8 @@
"xhM" = (
/obj/structure/machinery/door/airlock/almayer/security/glass/reinforced{
dir = 1;
- name = "\improper Brig"
+ name = "\improper Brig";
+ closeOtherId = "brigmaint_n"
},
/obj/structure/machinery/door/poddoor/almayer/open{
id = "Brig Lockdown Shutters";
@@ -81974,7 +82016,8 @@
},
/obj/structure/machinery/door/airlock/almayer/research/reinforced{
dir = 8;
- name = "\improper Containment Airlock"
+ name = "\improper Containment Airlock";
+ closeOtherId = "containment_s"
},
/obj/structure/machinery/door/poddoor/almayer/biohazard/white{
dir = 4
@@ -115166,7 +115209,7 @@ mSK
mSK
vOy
vOy
-ylc
+ccc
wKP
ylc
vOy
From a6b67c5693f2aeb9e4ecd373f679a48483edb3b5 Mon Sep 17 00:00:00 2001
From: cm13-github <128137806+cm13-github@users.noreply.github.com>
Date: Tue, 19 Dec 2023 19:05:21 +0000
Subject: [PATCH 16/23] Automatic changelog for PR #5235 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5235.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5235.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5235.yml b/html/changelogs/AutoChangeLog-pr-5235.yml
new file mode 100644
index 000000000000..1442f3b2437e
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5235.yml
@@ -0,0 +1,4 @@
+author: "realforest2001"
+delete-after: True
+changes:
+ - maptweak: "Various doors around the Almayer will now close their opposites for better security."
\ No newline at end of file
From 2263280f518327b03dae7dcedfe1db3592147a1e Mon Sep 17 00:00:00 2001
From: ItsVyzo <46250991+ItsVyzo@users.noreply.github.com>
Date: Tue, 19 Dec 2023 10:40:15 -0800
Subject: [PATCH 17/23] suspect sec records qol fix (#5230)
# About the pull request
Missed another code line for making suspects nardo gray instead of green
# Explain why it's good for the game
Makes it easier to distinguish suspects from innocents on sec records
# Testing Photographs and Procedure
Screenshots & Videos
Put screenshots and videos here with an empty line between the
screenshots and the `` tags.
# Changelog
:cl: LTNTS
qol: makes suspect nardo gray
/:cl:
---
code/game/machinery/computer/security.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 9a08ab7bd566..e7626938549a 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -95,7 +95,7 @@
if("Released")
background = "'background-color:#2981b3;'"
if("Suspect")
- background = "'background-color:#008743;'"
+ background = "'background-color:#686A6C;'"
if("NJP")
background = "'background-color:#faa20a;'"
if("None")
From c8baa27f0e4b3f01cbcb9290f5d116f1ad9f5047 Mon Sep 17 00:00:00 2001
From: cm13-github <128137806+cm13-github@users.noreply.github.com>
Date: Tue, 19 Dec 2023 19:19:14 +0000
Subject: [PATCH 18/23] Automatic changelog for PR #5230 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5230.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5230.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5230.yml b/html/changelogs/AutoChangeLog-pr-5230.yml
new file mode 100644
index 000000000000..5c3ba51b1edb
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5230.yml
@@ -0,0 +1,4 @@
+author: "LTNTS"
+delete-after: True
+changes:
+ - qol: "makes suspect nardo gray"
\ No newline at end of file
From f733a560483b92712551438b3f0cf9512e307a12 Mon Sep 17 00:00:00 2001
From: Birdtalon
Date: Tue, 19 Dec 2023 21:09:54 +0000
Subject: [PATCH 19/23] Hive UI pylon crash fix (#5249)
# About the pull request
This is a ref not a new list so we remove huggers/lessers etc from the
hive and crash the UI every time the pylon calls `give_larva()` Big
credit to Fira for doing most of the work on finding this.
![Discord_2023-12-18_21-31-58](https://github.com/cmss13-devs/cmss13/assets/25027759/afb25591-1bef-4ef2-9c04-3ddaec945fde)
# Explain why it's good for the game
# Testing Photographs and Procedure
Screenshots & Videos
Put screenshots and videos here with an empty line between the
screenshots and the `` tags.
# Changelog
:cl: Birdtalon, Fira
fix: Fixes hive UI crash upon pylon giving new larva.
/:cl:
---
code/modules/cm_aliens/structures/special/pylon_core.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm
index d62db4947b95..2350ecfa4462 100644
--- a/code/modules/cm_aliens/structures/special/pylon_core.dm
+++ b/code/modules/cm_aliens/structures/special/pylon_core.dm
@@ -190,7 +190,7 @@
if(!linked_hive.hive_location || !linked_hive.living_xeno_queen)
return
- var/list/hive_xenos = linked_hive.totalXenos
+ var/list/hive_xenos = linked_hive.totalXenos.Copy()
for(var/mob/living/carbon/xenomorph/xeno in hive_xenos)
if(!xeno.counts_for_slots)
From 34ced12830f031c1f36695b5e3e65266ce54fa2a Mon Sep 17 00:00:00 2001
From: cm13-github <128137806+cm13-github@users.noreply.github.com>
Date: Tue, 19 Dec 2023 21:18:23 +0000
Subject: [PATCH 20/23] Automatic changelog for PR #5249 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5249.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5249.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5249.yml b/html/changelogs/AutoChangeLog-pr-5249.yml
new file mode 100644
index 000000000000..ec0404cb997b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5249.yml
@@ -0,0 +1,4 @@
+author: "Birdtalon, Fira"
+delete-after: True
+changes:
+ - bugfix: "Fixes hive UI crash upon pylon giving new larva."
\ No newline at end of file
From 03292967e4c0ce138e3e04e202c85190326f70cc Mon Sep 17 00:00:00 2001
From: InsaneRed <47158596+InsaneRed@users.noreply.github.com>
Date: Wed, 20 Dec 2023 01:21:44 +0300
Subject: [PATCH 21/23] You/We fixes (#5240)
# About the pull request
Fixes up a few overlooked things
# Explain why it's good for the game
consistency
# Testing Photographs and Procedure
Screenshots & Videos
it ran on my pc
# Changelog
:cl:
spellcheck: Converted more "YOU" to "WE" for xenomorphs.
/:cl:
---------
Co-authored-by: InsaneRed
---
code/modules/cm_aliens/structures/egg.dm | 10 ++++----
code/modules/cm_aliens/structures/tunnel.dm | 24 +++++++++----------
.../living/carbon/xenomorph/castes/Crusher.dm | 16 ++++++-------
.../strains/hivelord/resin_whisperer.dm | 6 ++---
4 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/code/modules/cm_aliens/structures/egg.dm b/code/modules/cm_aliens/structures/egg.dm
index edb86c204558..5b0654d05b55 100644
--- a/code/modules/cm_aliens/structures/egg.dm
+++ b/code/modules/cm_aliens/structures/egg.dm
@@ -49,7 +49,7 @@
if(status == EGG_BURST || status == EGG_DESTROYED)
M.animation_attack_on(src)
M.visible_message(SPAN_XENONOTICE("[M] clears the hatched egg."), \
- SPAN_XENONOTICE("You clear the hatched egg."))
+ SPAN_XENONOTICE("We clear the hatched egg."))
playsound(src.loc, "alien_resin_break", 25)
qdel(src)
return XENO_NONCOMBAT_ACTION
@@ -57,7 +57,7 @@
if(M.hivenumber != hivenumber)
M.animation_attack_on(src)
M.visible_message(SPAN_XENOWARNING("[M] crushes \the [src]"),
- SPAN_XENOWARNING("You crush \the [src]"))
+ SPAN_XENOWARNING("We crush \the [src]"))
Burst(TRUE)
return XENO_ATTACK_ACTION
@@ -70,9 +70,9 @@
return XENO_NO_DELAY_ACTION
if(EGG_GROWN)
if(islarva(M))
- to_chat(M, SPAN_XENOWARNING("You nudge the egg, but nothing happens."))
+ to_chat(M, SPAN_XENOWARNING("We nudge the egg, but nothing happens."))
return
- to_chat(M, SPAN_XENONOTICE("You retrieve the child."))
+ to_chat(M, SPAN_XENONOTICE("We retrieve the child."))
Burst(FALSE)
return XENO_NONCOMBAT_ACTION
@@ -186,7 +186,7 @@
if(EGG_BURST)
if(user)
visible_message(SPAN_XENOWARNING("[user] slides [F] back into [src]."), \
- SPAN_XENONOTICE("You place the child back in to [src]."))
+ SPAN_XENONOTICE("We place the child back in to [src]."))
user.temp_drop_inv_item(F)
else
visible_message(SPAN_XENOWARNING("[F] crawls back into [src]!")) //Not sure how, but let's roll with it for now.
diff --git a/code/modules/cm_aliens/structures/tunnel.dm b/code/modules/cm_aliens/structures/tunnel.dm
index 8c467be695b4..1f0f98c14361 100644
--- a/code/modules/cm_aliens/structures/tunnel.dm
+++ b/code/modules/cm_aliens/structures/tunnel.dm
@@ -147,7 +147,7 @@
//No teleporting!
return FALSE
- to_chat(X, SPAN_XENONOTICE("You begin moving to your destination."))
+ to_chat(X, SPAN_XENONOTICE("We begin moving to our destination."))
var/tunnel_time = TUNNEL_MOVEMENT_XENO_DELAY
@@ -165,11 +165,11 @@
to_chat(X, SPAN_WARNING("The tunnel is too crowded, wait for others to exit!"))
return FALSE
if(!T.loc)
- to_chat(X, SPAN_WARNING("The tunnel has collapsed before you reached its exit!"))
+ to_chat(X, SPAN_WARNING("The tunnel has collapsed before we reached its exit!"))
return FALSE
X.forceMove(T)
- to_chat(X, SPAN_XENONOTICE("You have reached your destination."))
+ to_chat(X, SPAN_XENONOTICE("We have reached our destination."))
return TRUE
/obj/structure/tunnel/proc/exit_tunnel(mob/living/carbon/xenomorph/X)
@@ -177,7 +177,7 @@
if(X in contents)
X.forceMove(loc)
visible_message(SPAN_XENONOTICE("\The [X] pops out of the tunnel!"), \
- SPAN_XENONOTICE("You pop out through the other side!"))
+ SPAN_XENONOTICE("We pop out through the other side!"))
return TRUE
//Used for controling tunnel exiting and returning
@@ -200,15 +200,15 @@
if(!isfriendly(M))
if(M.mob_size < MOB_SIZE_BIG)
- to_chat(M, SPAN_XENOWARNING("You aren't large enough to collapse this tunnel!"))
+ to_chat(M, SPAN_XENOWARNING("We aren't large enough to collapse this tunnel!"))
return XENO_NO_DELAY_ACTION
M.visible_message(SPAN_XENODANGER("[M] begins to fill [src] with dirt."),\
- SPAN_XENONOTICE("You begin to fill [src] with dirt using your massive claws."), max_distance = 3)
+ SPAN_XENONOTICE("We begin to fill [src] with dirt using our massive claws."), max_distance = 3)
xeno_attack_delay(M)
if(!do_after(M, 10 SECONDS, INTERRUPT_ALL, BUSY_ICON_HOSTILE, src, INTERRUPT_ALL_OUT_OF_RANGE, max_dist = 1))
- to_chat(M, SPAN_XENOWARNING("You decide not to cave the tunnel in."))
+ to_chat(M, SPAN_XENOWARNING("We decide not to cave the tunnel in."))
return XENO_NO_DELAY_ACTION
src.visible_message(SPAN_XENODANGER("[src] caves in!"), max_distance = 3)
@@ -217,7 +217,7 @@
return XENO_NO_DELAY_ACTION
if(M.anchored)
- to_chat(M, SPAN_XENOWARNING("You can't climb through a tunnel while immobile."))
+ to_chat(M, SPAN_XENOWARNING("We can't climb through a tunnel while immobile."))
return XENO_NO_DELAY_ACTION
if(!hive.tunnels.len)
@@ -237,14 +237,14 @@
if(M.mob_size >= MOB_SIZE_BIG)
M.visible_message(SPAN_XENONOTICE("[M] begins heaving their huge bulk down into \the [src]."), \
- SPAN_XENONOTICE("You begin heaving your monstrous bulk into \the [src] ."))
+ SPAN_XENONOTICE("We begin heaving our monstrous bulk into \the [src]."))
else
M.visible_message(SPAN_XENONOTICE("\The [M] begins crawling down into \the [src]."), \
- SPAN_XENONOTICE("You begin crawling down into \the [src]."))
+ SPAN_XENONOTICE("We begin crawling down into \the [src]