From 66c341c25006dcf72a5f94fa262a9c1a09e80fb1 Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Thu, 13 Jun 2024 12:36:08 +0300 Subject: [PATCH] [MIRROR] Incoming stamina damage while in stamcrit has diminishing returns applied (#3788) * [MIRROR] Incoming stamina damage while in stamcrit has diminishing returns applied [MDB IGNORE] (#2987) * Incoming stamina damage while in stamcrit has diminishing returns applied * This was not even used * Update death_consequences_trauma.dm --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Mal <13398309+vinylspiders@users.noreply.github.com> * Update combat_stamina.dm --------- Co-authored-by: NovaBot <154629622+NovaBot13@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Mal <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: Iajret --- code/__DEFINES/mobs.dm | 2 - code/__DEFINES/~nova_defines/combat.dm | 2 - code/datums/status_effects/debuffs/debuffs.dm | 8 +- .../datums/status_effects/debuffs/stamcrit.dm | 75 +++++++++++++++++++ .../mob/living/basic/health_adjustment.dm | 3 + code/modules/mob/living/carbon/carbon.dm | 14 ---- .../mob/living/carbon/carbon_defines.dm | 3 - .../modules/mob/living/carbon/damage_procs.dm | 6 +- code/modules/mob/living/carbon/life.dm | 6 +- code/modules/mob/living/damage_procs.dm | 34 ++++++--- code/modules/mob/living/living.dm | 2 +- code/modules/mob/living/living_defines.dm | 3 + .../mob/living/silicon/damage_procs.dm | 3 + .../mob/living/simple_animal/damage_procs.dm | 3 + code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/combat_stamina.dm | 24 ++++++ .../death_consequences_trauma.dm | 2 +- tgstation.dme | 1 + 18 files changed, 149 insertions(+), 43 deletions(-) create mode 100644 code/datums/status_effects/debuffs/stamcrit.dm create mode 100644 code/modules/unit_tests/combat_stamina.dm diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index c50653275b7..89fe0a5db2e 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -171,8 +171,6 @@ #define HUMAN_MAX_OXYLOSS 3 #define HUMAN_CRIT_MAX_OXYLOSS (SSMOBS_DT/3) -#define STAMINA_REGEN_BLOCK_TIME (10 SECONDS) - #define HEAT_DAMAGE_LEVEL_1 1 //Amount of damage applied when your body temperature just passes the 360.15k safety point #define HEAT_DAMAGE_LEVEL_2 1.5 //Amount of damage applied when your body temperature passes the 400K point #define HEAT_DAMAGE_LEVEL_3 4 //Amount of damage applied when your body temperature passes the 460K point and you are on fire diff --git a/code/__DEFINES/~nova_defines/combat.dm b/code/__DEFINES/~nova_defines/combat.dm index 05474bb21d8..72f1e1cd7e0 100644 --- a/code/__DEFINES/~nova_defines/combat.dm +++ b/code/__DEFINES/~nova_defines/combat.dm @@ -25,8 +25,6 @@ #define OVERSIZED_HARM_DAMAGE_BONUS 5 /// Those with the oversized trait do 5 more damage. #define OVERSIZED_KICK_EFFECTIVENESS_BONUS 5 /// Increased unarmed_effectiveness/stun threshold on oversized kicks. -#define FILTER_STAMINACRIT filter(type="drop_shadow", x=0, y=0, size=-3, color="#04080F") - //Force mob to rest, does NOT do stamina damage. //It's really not recommended to use this proc to give feedback, hence why silent is defaulting to true. /mob/living/carbon/proc/KnockToFloor(silent = TRUE, ignore_canknockdown = FALSE, knockdown_amt = 1) diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index 20a043447cb..e560e9a0ac7 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -18,10 +18,12 @@ /datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration) if(isnum(set_duration)) duration = set_duration - . = ..() - if(. && (needs_update_stat || issilicon(owner))) - owner.update_stat() + return ..() +/datum/status_effect/incapacitating/on_apply() + if(needs_update_stat || issilicon(owner)) + owner.update_stat() + return TRUE /datum/status_effect/incapacitating/on_remove() if(needs_update_stat || issilicon(owner)) //silicons need stat updates in addition to normal canmove updates diff --git a/code/datums/status_effects/debuffs/stamcrit.dm b/code/datums/status_effects/debuffs/stamcrit.dm new file mode 100644 index 00000000000..05433244df0 --- /dev/null +++ b/code/datums/status_effects/debuffs/stamcrit.dm @@ -0,0 +1,75 @@ +/datum/status_effect/incapacitating/stamcrit + status_type = STATUS_EFFECT_UNIQUE + // Lasts until we go back to 0 stamina, which is handled by the mob + duration = -1 + tick_interval = -1 + /// Cooldown between displaying warning messages that we hit diminishing returns + COOLDOWN_DECLARE(warn_cd) + /// A counter that tracks every time we've taken enough damage to trigger diminishing returns + var/diminishing_return_counter = 0 + +/datum/status_effect/incapacitating/stamcrit/on_creation(mob/living/new_owner, set_duration) + . = ..() + if(!.) + return . + + // This should be in on apply but we need it to happen AFTER being added to the mob + // (Because we need to wait until the status effect is in their status effect list, or we'll add two) + if(owner.getStaminaLoss() < 120) + // Puts you a little further into the initial stamcrit, makes stamcrit harder to outright counter with chems. + owner.adjustStaminaLoss(30, FALSE) + + // Same + RegisterSignal(owner, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, PROC_REF(update_diminishing_return)) + RegisterSignal(owner, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(check_remove)) + +/datum/status_effect/incapacitating/stamcrit/on_apply() + if(owner.stat == DEAD) + return FALSE + if(owner.check_stun_immunity(CANKNOCKDOWN)) + return FALSE + + . = ..() + if(!.) + return . + + if(owner.stat == CONSCIOUS) + to_chat(owner, span_notice("You're too exhausted to keep going...")) + owner.add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED), STAMINA) + return . + +/datum/status_effect/incapacitating/stamcrit/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_HEALTH_UPDATE) + UnregisterSignal(owner, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE) + owner.remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED), STAMINA) + return ..() + +/datum/status_effect/incapacitating/stamcrit/proc/update_diminishing_return(datum/source, type, amount, forced) + SIGNAL_HANDLER + if(amount <= 0 || forced) + return NONE + // Here we fake the effect of having diminishing returns + // We don't actually decrease incoming stamina damage because that would be pointless, the mob is at stam damage cap anyways + // Instead we just "ignore" the damage if we have a sufficiently high diminishing return counter + var/mod_amount = ceil(sqrt(amount) / 2) - diminishing_return_counter + // We check base amount not mod_amount because we still want to up tick it even if we've already got a high counter + // We also only uptick it after calculating damage so we start ticking up after the damage and not before + switch(amount) + if(5 to INFINITY) + diminishing_return_counter += 1 + if(2 to 5) // Prevent chems from skyrockting DR + diminishing_return_counter += 0.05 + if(mod_amount > 0) + return NONE + + if(COOLDOWN_FINISHED(src, warn_cd) && owner.stat == CONSCIOUS) + to_chat(owner, span_notice("You start to recover from the exhaustion!")) + owner.visible_message(span_warning("[owner] starts to recover from the exhaustion!"), ignored_mobs = owner) + COOLDOWN_START(src, warn_cd, 2.5 SECONDS) + + return COMPONENT_IGNORE_CHANGE + +/datum/status_effect/incapacitating/stamcrit/proc/check_remove(datum/source, ...) + SIGNAL_HANDLER + if(owner.maxHealth - owner.getStaminaLoss() > owner.crit_threshold) + qdel(src) diff --git a/code/modules/mob/living/basic/health_adjustment.dm b/code/modules/mob/living/basic/health_adjustment.dm index 6e5f4cc95af..bae9d7b9e57 100644 --- a/code/modules/mob/living/basic/health_adjustment.dm +++ b/code/modules/mob/living/basic/health_adjustment.dm @@ -66,3 +66,6 @@ if(updating_stamina) update_stamina() . -= staminaloss + +/mob/living/basic/received_stamina_damage(current_level, amount_actual, amount) + return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 39e94dac2d4..c0db54b9e12 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -576,20 +576,6 @@ remove_movespeed_modifier(/datum/movespeed_modifier/carbon_softcrit) SEND_SIGNAL(src, COMSIG_LIVING_HEALTH_UPDATE) -/mob/living/carbon/update_stamina() - var/stam = getStaminaLoss() - if(stam > DAMAGE_PRECISION && (maxHealth - stam) <= crit_threshold) - if (!stat) - enter_stamcrit() - else if(HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) - REMOVE_TRAIT(src, TRAIT_INCAPACITATED, STAMINA) - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, STAMINA) - REMOVE_TRAIT(src, TRAIT_FLOORED, STAMINA) - filters -= FILTER_STAMINACRIT - else - return - update_stamina_hud() - /mob/living/carbon/update_sight() if(!client) return diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 2e2168eca9d..bef6a1fea87 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -86,9 +86,6 @@ /// This number is also reset to 0 every tick of carbon Life(). Pain. var/damageoverlaytemp = 0 - ///used to halt stamina regen temporarily - var/stam_regen_start_time = 0 - /// Protection (insulation) from the heat, Value 0-1 corresponding to the percentage of protection var/heat_protection = 0 // No heat protection /// Protection (insulation) from the cold, Value 0-1 corresponding to the percentage of protection diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index aeba381703b..b781b296bc8 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -144,10 +144,10 @@ if(AT_TOXIN_VOMIT_THRESHOLD(src)) apply_status_effect(/datum/status_effect/tox_vomit) -/mob/living/carbon/adjustStaminaLoss(amount, updating_stamina, forced, required_biotype = ALL) +/mob/living/carbon/received_stamina_damage(current_level, amount_actual, amount) . = ..() - if(amount > 0) - stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME + if((maxHealth - current_level) <= crit_threshold && stat != DEAD) + apply_status_effect(/datum/status_effect/incapacitating/stamcrit) /** * If an organ exists in the slot requested, and we are capable of taking damage (we don't have [GODMODE] on), call the damage proc on that organ. diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index e309fcd78a4..2908c35a54e 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -31,11 +31,7 @@ if(stat != DEAD) handle_brain_damage(seconds_per_tick, times_fired) - if(stat == DEAD) - stop_sound_channel(CHANNEL_HEARTBEAT) - else - if(getStaminaLoss() > 0 && stam_regen_start_time <= world.time) - adjustStaminaLoss(-INFINITY) + if(stat != DEAD) handle_bodyparts(seconds_per_tick, times_fired) if(. && mind) //. == not dead diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 6e8e2d5531e..5382b1aebf6 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -453,26 +453,42 @@ /mob/living/proc/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype = ALL) if(!can_adjust_stamina_loss(amount, forced, required_biotype)) return 0 - . = staminaloss + var/old_amount = staminaloss staminaloss = clamp((staminaloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, max_stamina) - . -= staminaloss - if(. == 0) // no change, no need to update + var/delta = old_amount - staminaloss + if(delta <= 0) + // need to check for stamcrit AFTER canadjust but BEFORE early return here + received_stamina_damage(staminaloss, -1 * delta) + if(delta == 0) // no change, no need to update return 0 if(updating_stamina) updatehealth() + return delta /mob/living/proc/setStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype = ALL) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 if(!forced && !(mob_biotypes & required_biotype)) - return FALSE - . = staminaloss + return 0 + var/old_amount = staminaloss staminaloss = amount - . -= staminaloss - if(!.) // no change, no need to update - return FALSE + var/delta = old_amount - staminaloss + if(delta <= 0 && amount >= DAMAGE_PRECISION) + received_stamina_damage(staminaloss, -1 * delta, amount) + if(delta == 0) // no change, no need to update + return 0 if(updating_stamina) updatehealth() + return delta + +/// The mob has received stamina damage +/// +/// - current_level: The mob's current stamina damage amount (to save unnecessary getStaminaLoss() calls) +/// - amount_actual: The amount of stamina damage received, in actuality +/// For example, if you are taking 50 stamina damage but are at 90, you would actually only receive 30 stamina damage (due to the cap) +/// - amount: The amount of stamina damage received, raw +/mob/living/proc/received_stamina_damage(current_level, amount_actual, amount) + addtimer(CALLBACK(src, PROC_REF(setStaminaLoss), 0, TRUE, TRUE), stamina_regen_time, TIMER_UNIQUE|TIMER_OVERRIDE) /** * heal ONE external organ, organ gets randomly selected from damaged ones. diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index b4350191b6f..6ac0f3811ac 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1454,7 +1454,7 @@ return TRUE /mob/living/proc/update_stamina() - return + update_stamina_hud() /mob/living/carbon/alien/update_stamina() return diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index a11c87deccf..b4df58b1d5a 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -226,3 +226,6 @@ /// What our current gravity state is. Used to avoid duplicate animates and such var/gravity_state = null + + /// How long it takes to return to 0 stam + var/stamina_regen_time = 10 SECONDS diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm index 58e5a244eaf..90a41f1f2bf 100644 --- a/code/modules/mob/living/silicon/damage_procs.dm +++ b/code/modules/mob/living/silicon/damage_procs.dm @@ -13,6 +13,9 @@ /mob/living/silicon/setStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype) return FALSE +/mob/living/silicon/received_stamina_damage(current_level, amount_actual, amount) + return + /mob/living/silicon/adjustOrganLoss(slot, amount, maximum = 500, required_organ_flag) //immune to organ damage (no organs, duh) return FALSE diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm index 3e49ac30559..7a8a2fcb63b 100644 --- a/code/modules/mob/living/simple_animal/damage_procs.dm +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -65,3 +65,6 @@ staminaloss = max(0, min(max_staminaloss, staminaloss + (amount * damage_coeff[STAMINA]))) if(updating_stamina) update_stamina() + +/mob/living/simple_animal/received_stamina_damage(current_level, amount_actual, amount) + return diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index d386fbef356..9a51045e2fc 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -119,6 +119,7 @@ #include "closets.dm" #include "clothing_under_armor_subtype_check.dm" #include "combat.dm" +#include "combat_stamina.dm" #include "combat_welder.dm" #include "component_tests.dm" #include "confusion.dm" diff --git a/code/modules/unit_tests/combat_stamina.dm b/code/modules/unit_tests/combat_stamina.dm new file mode 100644 index 00000000000..d82e2624dc5 --- /dev/null +++ b/code/modules/unit_tests/combat_stamina.dm @@ -0,0 +1,24 @@ +/// Tests 100 stamina damage = stamcrit +/datum/unit_test/stamcrit + priority = TEST_LONGER + +/datum/unit_test/stamcrit/Run() + var/mob/living/carbon/human/consistent/tider = allocate(__IMPLIED_TYPE__) + tider.stamina_regen_time = 0.2 SECONDS + tider.adjustStaminaLoss(tider.maxHealth-1) // NOVA EDIT CHANGE - ORIGINAL: tider.adjustStaminaLoss(99) + TEST_ASSERT(!tider.has_status_effect(/datum/status_effect/incapacitating/stamcrit), "Stamcrit should not be applied at 99 stamina damage") + tider.adjustStaminaLoss(1) + TEST_ASSERT(tider.has_status_effect(/datum/status_effect/incapacitating/stamcrit), "Stamcrit should be applied at 100 stamina damage") + sleep(tider.stamina_regen_time * 2) + TEST_ASSERT(!tider.has_status_effect(/datum/status_effect/incapacitating/stamcrit), "Stamcrit should be removed after regen time") + +/// Tests stamina regen after the set time +/datum/unit_test/stam_regen + priority = TEST_LONGER + +/datum/unit_test/stam_regen/Run() + var/mob/living/carbon/human/consistent/tider = allocate(__IMPLIED_TYPE__) + tider.stamina_regen_time = 0.2 SECONDS + tider.adjustStaminaLoss(50) + sleep(tider.stamina_regen_time * 2) + TEST_ASSERT_EQUAL(tider.getStaminaLoss(), 0, "Stamina should be fully regenerated after regen time") diff --git a/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm b/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm index 000d3b3d875..0057217dfce 100644 --- a/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm +++ b/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm @@ -338,7 +338,7 @@ if (owner_staminaloss > (minimum_stamina_damage + 1)) return else if ((owner_staminaloss >= (minimum_stamina_damage - 1)) && (owner_staminaloss <= (minimum_stamina_damage + 1))) - owner.stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME + owner.apply_status_effect(/datum/status_effect/incapacitating/stamcrit) return var/final_adjustment = (minimum_stamina_damage - owner_staminaloss) diff --git a/tgstation.dme b/tgstation.dme index 6a42485e1c5..29588e3a33b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1945,6 +1945,7 @@ #include "code\datums\status_effects\debuffs\spacer.dm" #include "code\datums\status_effects\debuffs\speech_debuffs.dm" #include "code\datums\status_effects\debuffs\staggered.dm" +#include "code\datums\status_effects\debuffs\stamcrit.dm" #include "code\datums\status_effects\debuffs\static_vision.dm" #include "code\datums\status_effects\debuffs\strandling.dm" #include "code\datums\status_effects\debuffs\terrified.dm"