Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MIRROR] Incoming stamina damage while in stamcrit has diminishing returns applied #3788

Merged
merged 2 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading