Skip to content

Commit

Permalink
[MIRROR] Incoming stamina damage while in stamcrit has diminishing re…
Browse files Browse the repository at this point in the history
…turns 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 <[email protected]>
Co-authored-by: Mal <[email protected]>

* Update combat_stamina.dm

---------

Co-authored-by: NovaBot <[email protected]>
Co-authored-by: MrMelbert <[email protected]>
Co-authored-by: Mal <[email protected]>
Co-authored-by: Iajret <[email protected]>
  • Loading branch information
5 people authored Jun 13, 2024
1 parent 3eff499 commit 66c341c
Show file tree
Hide file tree
Showing 18 changed files with 149 additions and 43 deletions.
2 changes: 0 additions & 2 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions code/__DEFINES/~nova_defines/combat.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions code/datums/status_effects/debuffs/debuffs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions code/datums/status_effects/debuffs/stamcrit.dm
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions code/modules/mob/living/basic/health_adjustment.dm
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@
if(updating_stamina)
update_stamina()
. -= staminaloss

/mob/living/basic/received_stamina_damage(current_level, amount_actual, amount)
return
14 changes: 0 additions & 14 deletions code/modules/mob/living/carbon/carbon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions code/modules/mob/living/carbon/carbon_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions code/modules/mob/living/carbon/damage_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 1 addition & 5 deletions code/modules/mob/living/carbon/life.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 25 additions & 9 deletions code/modules/mob/living/damage_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1454,7 +1454,7 @@
return TRUE

/mob/living/proc/update_stamina()
return
update_stamina_hud()

/mob/living/carbon/alien/update_stamina()
return
Expand Down
3 changes: 3 additions & 0 deletions code/modules/mob/living/living_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions code/modules/mob/living/silicon/damage_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions code/modules/mob/living/simple_animal/damage_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions code/modules/unit_tests/_unit_tests.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 24 additions & 0 deletions code/modules/unit_tests/combat_stamina.dm
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 66c341c

Please sign in to comment.