From 553fa38db201d7669c454a75f0e7c83d4dc541db Mon Sep 17 00:00:00 2001 From: Changelogs Date: Tue, 19 Dec 2023 01:11:24 +0000 Subject: [PATCH 01/20] 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 02/20] /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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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].")) xeno_attack_delay(M) if(!do_after(M, tunnel_time, INTERRUPT_NO_NEEDHAND, BUSY_ICON_GENERIC)) - to_chat(M, SPAN_WARNING("Your crawling was interrupted!")) + to_chat(M, SPAN_WARNING("Our crawling was interrupted!")) return XENO_NO_DELAY_ACTION if(hive.tunnels.len) //Make sure other tunnels exist @@ -252,7 +252,7 @@ to_chat(M, SPAN_HIGHDANGER("Alt + Click the tunnel to exit, Ctrl + Click to choose a destination.")) pick_tunnel(M) else - to_chat(M, SPAN_WARNING("\The [src] ended unexpectedly, so you return back up.")) + to_chat(M, SPAN_WARNING("\The [src] ended unexpectedly, so we return back up.")) return XENO_NO_DELAY_ACTION /obj/structure/tunnel/maint_tunnel diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm index 64dfd021f894..d2c0b0e40e59 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm @@ -81,21 +81,21 @@ //Barricade collision else if (istype(target, /obj/structure/barricade)) var/obj/structure/barricade/B = target - visible_message(SPAN_DANGER("[src] rams into [B] and skids to a halt!"), SPAN_XENOWARNING("You ram into [B] and skid to a halt!")) + visible_message(SPAN_DANGER("[src] rams into [B] and skids to a halt!"), SPAN_XENOWARNING("We ram into [B] and skid to a halt!")) B.Collided(src) . = FALSE else if (istype(target, /obj/vehicle/multitile)) var/obj/vehicle/multitile/M = target - visible_message(SPAN_DANGER("[src] rams into [M] and skids to a halt!"), SPAN_XENOWARNING("You ram into [M] and skid to a halt!")) + visible_message(SPAN_DANGER("[src] rams into [M] and skids to a halt!"), SPAN_XENOWARNING("We ram into [M] and skid to a halt!")) M.Collided(src) . = FALSE else if (istype(target, /obj/structure/machinery/m56d_hmg)) var/obj/structure/machinery/m56d_hmg/HMG = target - visible_message(SPAN_DANGER("[src] rams [HMG]!"), SPAN_XENODANGER("You ram [HMG]!")) + visible_message(SPAN_DANGER("[src] rams [HMG]!"), SPAN_XENODANGER("We ram [HMG]!")) playsound(loc, "punch", 25, 1) HMG.CrusherImpact() . = FALSE @@ -132,7 +132,7 @@ else if (istype(target, /obj/structure/machinery/defenses)) var/obj/structure/machinery/defenses/DF = target - visible_message(SPAN_DANGER("[src] rams [DF]!"), SPAN_XENODANGER("You ram [DF]!")) + visible_message(SPAN_DANGER("[src] rams [DF]!"), SPAN_XENODANGER("We ram [DF]!")) if (!DF.unacidable) playsound(loc, "punch", 25, 1) @@ -148,7 +148,7 @@ if (V.unslashable) . = FALSE else - visible_message(SPAN_DANGER("[src] smashes straight into [V]!"), SPAN_XENODANGER("You smash straight into [V]!")) + visible_message(SPAN_DANGER("[src] smashes straight into [V]!"), SPAN_XENODANGER("We smash straight into [V]!")) playsound(loc, "punch", 25, 1) V.tip_over() @@ -165,7 +165,7 @@ if (V.unslashable) . = FALSE else - visible_message(SPAN_DANGER("[src] smashes straight into [V]!"), SPAN_XENODANGER("You smash straight into [V]!")) + visible_message(SPAN_DANGER("[src] smashes straight into [V]!"), SPAN_XENODANGER("We smash straight into [V]!")) playsound(loc, "punch", 25, 1) V.tip_over() @@ -184,7 +184,7 @@ if (O.unacidable) . = FALSE else if (O.anchored) - visible_message(SPAN_DANGER("[src] crushes [O]!"), SPAN_XENODANGER("You crush [O]!")) + visible_message(SPAN_DANGER("[src] crushes [O]!"), SPAN_XENODANGER("We crush [O]!")) if(O.contents.len) //Hopefully won't auto-delete things inside crushed stuff. var/turf/T = get_turf(src) for(var/atom/movable/S in T.contents) S.forceMove(T) @@ -195,7 +195,7 @@ else if(O.buckled_mob) O.unbuckle() - visible_message(SPAN_WARNING("[src] knocks [O] aside!"), SPAN_XENOWARNING("You knock [O] aside.")) //Canisters, crates etc. go flying. + visible_message(SPAN_WARNING("[src] knocks [O] aside!"), SPAN_XENOWARNING("We knock [O] aside.")) //Canisters, crates etc. go flying. playsound(loc, "punch", 25, 1) var/impact_range = 2 diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm b/code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm index 3200fd0da4a1..3653209b78f2 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm +++ b/code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm @@ -90,9 +90,9 @@ var/obj/structure/mineral_door/resin/resin_door = target_atom resin_door.TryToSwitchState(owner) if(resin_door.state) - to_chat(owner, SPAN_XENONOTICE("You focus your connection to the resin and remotely close the resin door.")) + to_chat(owner, SPAN_XENONOTICE("We focus our connection to the resin and remotely close the resin door.")) else - to_chat(owner, SPAN_XENONOTICE("You focus your connection to the resin and remotely open the resin door.")) + to_chat(owner, SPAN_XENONOTICE("We focus our connection to the resin and remotely open the resin door.")) return // since actions are instanced per hivelord, and only one construction can be made at a time, tweaking the datum on the fly here is fine. you're going to have to figure something out if these conditions change, though @@ -111,7 +111,7 @@ var/datum/resin_construction/resing_construction = GLOB.resin_constructions_list[hivelord.selected_resin] target_turf.visible_message(SPAN_XENONOTICE("The weeds begin pulsating wildly and secrete resin in the shape of \a [resing_construction.construction_name]!"), null, 5) - to_chat(owner, SPAN_XENONOTICE("You focus your plasma into the weeds below you and force the weeds to secrete resin in the shape of \a [resing_construction.construction_name].")) + to_chat(owner, SPAN_XENONOTICE("We focus our plasma into the weeds below us and force the weeds to secrete resin in the shape of \a [resing_construction.construction_name].")) playsound(target_turf, "alien_resin_build", 25) return TRUE From f28e9294cb573a5a8a19c3b620a33ee78c775e0e Mon Sep 17 00:00:00 2001 From: cm13-github <128137806+cm13-github@users.noreply.github.com> Date: Tue, 19 Dec 2023 22:30:22 +0000 Subject: [PATCH 14/20] Automatic changelog for PR #5240 [ci skip] --- html/changelogs/AutoChangeLog-pr-5240.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-5240.yml diff --git a/html/changelogs/AutoChangeLog-pr-5240.yml b/html/changelogs/AutoChangeLog-pr-5240.yml new file mode 100644 index 000000000000..d600d5d9acf2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-5240.yml @@ -0,0 +1,4 @@ +author: "InsaneRed" +delete-after: True +changes: + - spellcheck: "Converted more \"YOU\" to \"WE\" for xenomorphs." \ No newline at end of file From 92c1793b233955ef8bcd0eaee139736d340c374e Mon Sep 17 00:00:00 2001 From: Changelogs Date: Wed, 20 Dec 2023 00:58:35 +0000 Subject: [PATCH 15/20] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-4842.yml | 12 ---------- html/changelogs/AutoChangeLog-pr-5230.yml | 4 ---- html/changelogs/AutoChangeLog-pr-5235.yml | 4 ---- html/changelogs/AutoChangeLog-pr-5240.yml | 4 ---- html/changelogs/AutoChangeLog-pr-5243.yml | 4 ---- html/changelogs/AutoChangeLog-pr-5249.yml | 4 ---- html/changelogs/archive/2023-12.yml | 29 +++++++++++++++++++++++ 7 files changed, 29 insertions(+), 32 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-4842.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-5230.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-5235.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-5240.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-5243.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-5249.yml diff --git a/html/changelogs/AutoChangeLog-pr-4842.yml b/html/changelogs/AutoChangeLog-pr-4842.yml deleted file mode 100644 index 6c1c16a996b3..000000000000 --- a/html/changelogs/AutoChangeLog-pr-4842.yml +++ /dev/null @@ -1,12 +0,0 @@ -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 diff --git a/html/changelogs/AutoChangeLog-pr-5230.yml b/html/changelogs/AutoChangeLog-pr-5230.yml deleted file mode 100644 index 5c3ba51b1edb..000000000000 --- a/html/changelogs/AutoChangeLog-pr-5230.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "LTNTS" -delete-after: True -changes: - - qol: "makes suspect nardo gray" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-5235.yml b/html/changelogs/AutoChangeLog-pr-5235.yml deleted file mode 100644 index 1442f3b2437e..000000000000 --- a/html/changelogs/AutoChangeLog-pr-5235.yml +++ /dev/null @@ -1,4 +0,0 @@ -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 diff --git a/html/changelogs/AutoChangeLog-pr-5240.yml b/html/changelogs/AutoChangeLog-pr-5240.yml deleted file mode 100644 index d600d5d9acf2..000000000000 --- a/html/changelogs/AutoChangeLog-pr-5240.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "InsaneRed" -delete-after: True -changes: - - spellcheck: "Converted more \"YOU\" to \"WE\" for xenomorphs." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-5243.yml b/html/changelogs/AutoChangeLog-pr-5243.yml deleted file mode 100644 index 07bc1c732124..000000000000 --- a/html/changelogs/AutoChangeLog-pr-5243.yml +++ /dev/null @@ -1,4 +0,0 @@ -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 diff --git a/html/changelogs/AutoChangeLog-pr-5249.yml b/html/changelogs/AutoChangeLog-pr-5249.yml deleted file mode 100644 index ec0404cb997b..000000000000 --- a/html/changelogs/AutoChangeLog-pr-5249.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Birdtalon, Fira" -delete-after: True -changes: - - bugfix: "Fixes hive UI crash upon pylon giving new larva." \ No newline at end of file diff --git a/html/changelogs/archive/2023-12.yml b/html/changelogs/archive/2023-12.yml index 4a882e76a8d1..b0bde6fe0753 100644 --- a/html/changelogs/archive/2023-12.yml +++ b/html/changelogs/archive/2023-12.yml @@ -374,3 +374,32 @@ - bugfix: Escape-pod stasis bays no longer accept corpses. stalkerino: - balance: fixes the balance of the game by making hair gradient trait free +2023-12-20: + Birdtalon, Fira: + - bugfix: Fixes hive UI crash upon pylon giving new larva. + Contrabang: + - bugfix: Matches now do burn damage instead of brute, when you accidently burn + your own hand. + InsaneRed: + - spellcheck: Converted more "YOU" to "WE" for xenomorphs. + LTNTS: + - qol: makes suspect nardo gray + fira: + - 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. + realforest2001: + - maptweak: Various doors around the Almayer will now close their opposites for + better security. From 60ddbd4d1f17b0adef4c1023877a74b753b43dcc Mon Sep 17 00:00:00 2001 From: Birdtalon Date: Wed, 20 Dec 2023 12:02:58 +0000 Subject: [PATCH 16/20] Fixes lurker tail stab over ledges and window frames (#5160) # About the pull request Fixes lurker tail stab not being usable over window frames and ledges. # 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: fix: Lurker can tail jab over ledges and window frames. /:cl: --- .../living/carbon/xenomorph/abilities/lurker/lurker_powers.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm index 69f869387068..4ec301a17819 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm @@ -208,7 +208,7 @@ xeno.visible_message(SPAN_XENOWARNING("\The [xeno] strikes the window with their tail!"), SPAN_XENOWARNING("We strike the window with our tail!")) apply_cooldown(cooldown_modifier = 0.5) return - if(current_structure.density) + if(current_structure.density && !current_structure.throwpass) to_chat(xeno, SPAN_WARNING("There's something blocking us from striking!")) return From 7917dec240aaeb8085877e55f009d17d8e07069a Mon Sep 17 00:00:00 2001 From: cm13-github <128137806+cm13-github@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:14:59 +0000 Subject: [PATCH 17/20] Automatic changelog for PR #5160 [ci skip] --- html/changelogs/AutoChangeLog-pr-5160.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-5160.yml diff --git a/html/changelogs/AutoChangeLog-pr-5160.yml b/html/changelogs/AutoChangeLog-pr-5160.yml new file mode 100644 index 000000000000..730a2bc41181 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-5160.yml @@ -0,0 +1,4 @@ +author: "Birdtalon" +delete-after: True +changes: + - bugfix: "Lurker can tail jab over ledges and window frames." \ No newline at end of file From 4a29849b40aa24457f77034bd345016f597158e2 Mon Sep 17 00:00:00 2001 From: Drathek <76988376+Drulikar@users.noreply.github.com> Date: Wed, 20 Dec 2023 06:12:28 -0800 Subject: [PATCH 18/20] Update weeded warrior sprites (#5250) # About the pull request This PR is a followup to #5214 to fix the weeded warrior sprites that didn't get updated. # Explain why it's good for the game Fixes this: ![image](https://github.com/cmss13-devs/cmss13/assets/76988376/78a98ca3-7d6d-4a5d-a57a-e67e69e6ee58) # Testing Photographs and Procedure ![warrior](https://github.com/cmss13-devs/cmss13/assets/76988376/f7b7f547-66c0-447a-b290-c37b96f67f2b) # Changelog :cl: Drathek imageadd: Update weeded warrior sprites to be compatible with knight strain sprite /:cl: --- icons/mob/xenos/weeds_64x64.dmi | Bin 28551 -> 32998 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/xenos/weeds_64x64.dmi b/icons/mob/xenos/weeds_64x64.dmi index 43d92fc4a8ac5403159d8cdbba093b3916f4f8b5..26a31eb0fc607feebfb0992994b8cfe308a8cc54 100644 GIT binary patch literal 32998 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 literal 28551 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

lZh~wu;FK4 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 B`cnV^ From f94b1387dac12e62d722b1ce926692ee79fa59d6 Mon Sep 17 00:00:00 2001 From: cm13-github <128137806+cm13-github@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:23:14 +0000 Subject: [PATCH 19/20] Automatic changelog for PR #5250 [ci skip] --- html/changelogs/AutoChangeLog-pr-5250.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-5250.yml diff --git a/html/changelogs/AutoChangeLog-pr-5250.yml b/html/changelogs/AutoChangeLog-pr-5250.yml new file mode 100644 index 000000000000..a2229e1fa453 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-5250.yml @@ -0,0 +1,4 @@ +author: "Drathek" +delete-after: True +changes: + - imageadd: "Update weeded warrior sprites to be compatible with knight strain sprite" \ No newline at end of file From 72282fbe47e217181c185f2de52ab898db22df1f Mon Sep 17 00:00:00 2001 From: Drathek <76988376+Drulikar@users.noreply.github.com> Date: Wed, 20 Dec 2023 06:18:15 -0800 Subject: [PATCH 20/20] Eslint Setting Change (#5256) # About the pull request This is merely to address VSC's ESlint extension being so aggressive about forcing this setting change. # Explain why it's good for the game N/A # Testing Photographs and Procedure N/A # Changelog No player facing changes. --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 201562aaf7c7..c7b218b77591 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,7 @@ "**/.pnp.*": true }, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "files.eol": "\n", "files.insertFinalNewline": true,