Files
Bubberstation/code/modules/unit_tests/mob_damage.dm
distributivgesetz 274eb2a52e Removes Clone Damage (#80109)
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

Does what it says on the tin. We don't have any "special" sources of
clone damage left in the game, most of them are rather trivial so I
bunched them together into this PR.

Notable things removed:
- Clonexadone, because its entire thing was centered around clone damage
- Decloner gun, it's also centered around cloning damage, I couldn't
think of a replacement mechanic and nobody uses it anyways
- Everything else already dealt clone damage as a side (rainbow knife
deals a random damage type for example), so these sources were removed

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game

Consider the four sources of normal damage that you can get: Brute,
Burn, Toxins and Oxygen. These four horsemen of the apocalypse are very
well put together and it's no surprise that they are in the game, as you
can fit any way of damaging a mob into them. Getting beaten to death by
a security officer? Brute damage. Running around on fire? Burn damage.
Poisoned or irradiated? Toxin damage. Suffocating in space? Brute, burn
and oxygen damage. Technically there's also stamina damage but that's
its own ballpark and it also makes sense why we have a damage number for
it.

Picture this now: We have this cool mechanic called "clone pods" where
you can magically revive dead people with absolute ease. We don't want
it to be for free though, it comes at a cost. This cost is clone damage,
and it serves to restrain people from abusing cloning.

Fast forward time a bit and cloning is now removed from the game. What
stays with us is a damage number that is intrinsically tied to the
context of a removed feature. It was a good idea that we had it for that
feature at the time, but now it just sits there. It's the odd one out
from all the other damage types. You can easily explain why your blade
dealt brute damage, but how are you going to fit clone damage into any
context without also becoming extremely specific?

My point is: **clone damage is conceptually a flawed mechanic because it
is too specific**. That is the major issue why no one uses it, and why
that makes it unworthy of being a damage stat.
Don't take my word for it though, because a while ago we only had a
handful of sources for this damage type in the game. And in most of the
rounds where you saw this damage, it came from only one department. It's
not worthwhile to keep it around as a damage number. People also didn't
know what to do with this damage type, so we currently have two ways of
healing clone damage: Cryotubes as a roundstart way of healing clone
damage and Rezadone, which instantly sets your clone damage to 0 on the
first tick. As a medical doctor, when was the last time you saw someone
come in with clone damage and thought to yourself, "Oh, this person has
clone damage, I cannot wait to heal them!" ?

Now we have replacements for these clone damage sources. Slimes? Slime
status effect that deals brute instead of clone. Cosmic heretics? Random
organ damage, because their mechanics are already pretty fleshed out.
Decloning virus? The virus operated as a "ticking timebomb" which used
cloning damage as the timer, so it has been reworked to not use clone
damage. What remains after all this is now a basically unused damage
type. Every specific situation that used clone damage is now relying on
another damage type. Now it's time to put clone damage to rest once and
for all.

Sure, you can technically add some form of cellular degradation in the
future, but it shouldn't be a damage number. The idea of your cells
being degraded is a cool concept, don't get me wrong, but make it a
status effect or maybe even a wound for that matter.

<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and it's effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
del: Removed clone damage.
del: Removed the decloner gun.
del: Removed clonexadone.
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->
2023-12-04 14:42:43 -08:00

583 lines
30 KiB
Plaintext

/// Tests to make sure mob damage procs are working correctly
/datum/unit_test/mob_damage
priority = TEST_LONGER
/datum/unit_test/mob_damage/Destroy()
SSmobs.ignite()
return ..()
/datum/unit_test/mob_damage/Run()
SSmobs.pause()
var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent)
dummy.maxHealth = 200 // tank mode
/* The sanity tests: here we make sure that:
1) That damage procs are returning the expected values. They should be returning the actual amount of damage taken/healed.
(Negative values mean damage was taken, positive mean healing)
2) Verifying that the damage has been accurately applied to the mob afterwards. */
test_sanity_simple(dummy)
test_sanity_complex(dummy)
// Testing if biotypes are working as intended
test_biotypes(dummy)
// Testing whether or not TRAIT_NOBREATH is working as intended
test_nobreath(dummy)
// Testing whether or not TRAIT_TOXINLOVER and TRAIT_TOXIMMUNE are working as intended
test_toxintraits(dummy)
// Testing the proc ordered_healing()
test_ordered_healing(dummy)
// testing with godmode enabled
test_godmode(dummy)
/**
* Test whether the adjust damage procs return the correct values and that the mob's health is the expected value afterwards.
*
* By default this calls apply_damage(amount) followed by verify_damage(amount_after) and returns TRUE if both succeeded.
* amount_after defaults to the mob's current stamina loss but can be overridden as needed.
*
* Arguments:
* * testing_mob - the mob to apply the damage to
* * amount - the amount of damage to apply to the mob
* * expected - what the expected return value of the damage proc is
* * amount_after - in case you want to specify what the damage amount on the mob should be afterwards
* * included_types - Bitflag of damage types to apply
* * biotypes - the biotypes of damage to apply
* * bodytypes - the bodytypes of damage to apply
* * forced - whether or not this is forced damage
*/
/datum/unit_test/mob_damage/proc/test_apply_damage(mob/living/testing_mob, amount, expected = -amount, amount_after, included_types, biotypes, bodytypes, forced)
if(isnull(amount_after))
amount_after = testing_mob.getStaminaLoss() - expected // stamina loss applies to both carbon and basic mobs the same way, so that's why we're using it here
if(!apply_damage(testing_mob, amount, expected, included_types, biotypes, bodytypes, forced))
return FALSE
if(!verify_damage(testing_mob, amount_after, included_types))
return FALSE
return TRUE
/**
* Test whether the set damage procs return the correct values and that the mob's health is the expected value afterwards.
*
* By default this calls set_damage(amount) followed by verify_damage(amount_after) and returns TRUE if both succeeded.
* amount_after defaults to the mob's current stamina loss but can be overridden as needed.
*
* Arguments:
* * testing_mob - the mob to apply the damage to
* * amount - the amount of damage to apply to the mob
* * expected - what the expected return value of the damage proc is
* * amount_after - in case you want to specify what the damage amount on the mob should be afterwards
* * included_types - Bitflag of damage types to apply
* * biotypes - the biotypes of damage to apply
* * bodytypes - the bodytypes of damage to apply
* * forced - whether or not this is forced damage
*/
/datum/unit_test/mob_damage/proc/test_set_damage(mob/living/testing_mob, amount, expected, amount_after, included_types, biotypes, bodytypes, forced)
if(isnull(amount_after))
amount_after = testing_mob.getStaminaLoss() - expected
if(!set_damage(testing_mob, amount, expected, included_types, biotypes, bodytypes, forced))
return FALSE
if(!verify_damage(testing_mob, amount_after, included_types))
return FALSE
return TRUE
/**
* Check that the mob has a specific amount of damage
*
* By default this checks that the mob has <amount> of every type of damage.
* Arguments:
* * testing_mob - the mob to check the damage of
* * amount - the amount of damage to verify that the mob has
* * included_types - Bitflag of damage types to check.
*/
/datum/unit_test/mob_damage/proc/verify_damage(mob/living/testing_mob, amount, included_types = ALL)
if(included_types & TOXLOSS)
TEST_ASSERT_EQUAL(testing_mob.getToxLoss(), amount, \
"[testing_mob] should have [amount] toxin damage, instead they have [testing_mob.getToxLoss()]!")
if(included_types & BRUTELOSS)
TEST_ASSERT_EQUAL(round(testing_mob.getBruteLoss(), 1), amount, \
"[testing_mob] should have [amount] brute damage, instead they have [testing_mob.getBruteLoss()]!")
if(included_types & FIRELOSS)
TEST_ASSERT_EQUAL(round(testing_mob.getFireLoss(), 1), amount, \
"[testing_mob] should have [amount] burn damage, instead they have [testing_mob.getFireLoss()]!")
if(included_types & OXYLOSS)
TEST_ASSERT_EQUAL(testing_mob.getOxyLoss(), amount, \
"[testing_mob] should have [amount] oxy damage, instead they have [testing_mob.getOxyLoss()]!")
if(included_types & STAMINALOSS)
TEST_ASSERT_EQUAL(testing_mob.getStaminaLoss(), amount, \
"[testing_mob] should have [amount] stamina damage, instead they have [testing_mob.getStaminaLoss()]!")
return TRUE
/**
* Apply a specific amount of damage to the mob using adjustBruteLoss(), adjustToxLoss(), etc.
*
* By default this applies <amount> damage of every type to the mob, and checks that the damage procs return the <expected> value
* Arguments:
* * testing_mob - the mob to apply the damage to
* * amount - the amount of damage to apply to the mob
* * expected - what the expected return value of the damage proc is
* * included_types - Bitflag of damage types to apply
* * biotypes - the biotypes of damage to apply
* * bodytypes - the bodytypes of damage to apply
* * forced - whether or not this is forced damage
*/
/datum/unit_test/mob_damage/proc/apply_damage(mob/living/testing_mob, amount, expected = -amount, included_types = ALL, biotypes = ALL, bodytypes = ALL, forced = FALSE)
var/damage_returned
if(included_types & TOXLOSS)
damage_returned = testing_mob.adjustToxLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"adjustToxLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & BRUTELOSS)
damage_returned = round(testing_mob.adjustBruteLoss(amount, updating_health = FALSE, forced = forced, required_bodytype = bodytypes), 1)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"adjustBruteLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & FIRELOSS)
damage_returned = round(testing_mob.adjustFireLoss(amount, updating_health = FALSE, forced = forced, required_bodytype = bodytypes), 1)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"adjustFireLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & OXYLOSS)
damage_returned = testing_mob.adjustOxyLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"adjustOxyLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & STAMINALOSS)
damage_returned = testing_mob.adjustStaminaLoss(amount, updating_stamina = FALSE, forced = forced, required_biotype = biotypes)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"adjustStaminaLoss() should have returned [expected], but returned [damage_returned] instead!")
return TRUE
/**
* Set a specific amount of damage for the mob using setBruteLoss(), setToxLoss(), etc.
*
* By default this sets every type of damage to <amount> for the mob, and checks that the damage procs return the <expected> value
* Arguments:
* * testing_mob - the mob to apply the damage to
* * amount - the amount of damage to apply to the mob
* * expected - what the expected return value of the damage proc is
* * included_types - Bitflag of damage types to apply
* * biotypes - the biotypes of damage to apply
* * bodytypes - the bodytypes of damage to apply
* * forced - whether or not this is forced damage
*/
/datum/unit_test/mob_damage/proc/set_damage(mob/living/testing_mob, amount, expected = -amount, included_types = ALL, biotypes = ALL, bodytypes = ALL, forced = FALSE)
var/damage_returned
if(included_types & TOXLOSS)
damage_returned = testing_mob.setToxLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"setToxLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & BRUTELOSS)
damage_returned = round(testing_mob.setBruteLoss(amount, updating_health = FALSE, forced = forced), 1)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"setBruteLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & FIRELOSS)
damage_returned = round(testing_mob.setFireLoss(amount, updating_health = FALSE, forced = forced), 1)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"setFireLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & OXYLOSS)
damage_returned = testing_mob.setOxyLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"setOxyLoss() should have returned [expected], but returned [damage_returned] instead!")
if(included_types & STAMINALOSS)
damage_returned = testing_mob.setStaminaLoss(amount, updating_stamina = FALSE, forced = forced, required_biotype = biotypes)
TEST_ASSERT_EQUAL(damage_returned, expected, \
"setStaminaLoss() should have returned [expected], but returned [damage_returned] instead!")
return TRUE
/// Sanity tests damage and healing using adjustToxLoss, adjustBruteLoss, etc
/datum/unit_test/mob_damage/proc/test_sanity_simple(mob/living/carbon/human/consistent/dummy)
// Apply 5 damage and then heal it
if(!test_apply_damage(dummy, amount = 5))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly")
if(!test_apply_damage(dummy, amount = -5))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly")
// Apply 15 damage and heal 3
if(!test_apply_damage(dummy, amount = 15))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly")
if(!test_apply_damage(dummy, amount = -3))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! underhealing was not applied correctly")
// Now overheal by 666. It should heal for 12.
if(!test_apply_damage(dummy, amount = -666, expected = 12))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! overhealing was not applied correctly")
// Now test the damage setter procs
// set all types of damage to 5
if(!test_set_damage(dummy, amount = 5, expected = -5))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to set damage to 5")
// now try healing 5
if(!test_set_damage(dummy, amount = 0, expected = 5))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to set damage to 0")
/// Sanity tests damage and healing using the more complex procs like take_overall_damage(), heal_overall_damage(), etc
/datum/unit_test/mob_damage/proc/test_sanity_complex(mob/living/carbon/human/consistent/dummy)
// Heal up, so that errors from the previous tests we won't cause this one to fail
dummy.fully_heal(HEAL_DAMAGE)
var/damage_returned
// take 5 brute, 2 burn
damage_returned = round(dummy.take_bodypart_damage(5, 2, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, -7, \
"take_bodypart_damage() should have returned -7, but returned [damage_returned] instead!")
TEST_ASSERT_EQUAL(round(dummy.getBruteLoss(), 1), 5, \
"Dummy should have 5 brute damage, instead they have [dummy.getBruteLoss()]!")
TEST_ASSERT_EQUAL(round(dummy.getFireLoss(), 1), 2, \
"Dummy should have 2 burn damage, instead they have [dummy.getFireLoss()]!")
// heal 4 brute, 1 burn
damage_returned = round(dummy.heal_bodypart_damage(4, 1, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, 5, \
"heal_bodypart_damage() should have returned 5, but returned [damage_returned] instead!")
if(!verify_damage(dummy, 1, included_types = BRUTELOSS|FIRELOSS))
TEST_FAIL("heal_bodypart_damage did not apply its healing correctly on the mob!")
// heal 1 brute, 1 burn
damage_returned = round(dummy.heal_overall_damage(1, 1, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, 2, \
"heal_overall_damage() should have returned 2, but returned [damage_returned] instead!")
if(!verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS))
TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mob!")
// take 50 brute, 50 burn
damage_returned = round(dummy.take_overall_damage(50, 50, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, -100, \
"take_overall_damage() should have returned -100, but returned [damage_returned] instead!")
if(!verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS))
TEST_FAIL("take_overall_damage did not apply its damage correctly on the mob!")
// testing negative damage amount args with the overall damage procs - the sign should be ignored for these procs
damage_returned = round(dummy.take_bodypart_damage(-5, -5, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, -10, \
"take_bodypart_damage() should have returned -10, but returned [damage_returned] instead!")
damage_returned = round(dummy.heal_bodypart_damage(-5, -5, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, 10, \
"heal_bodypart_damage() should have returned 10, but returned [damage_returned] instead!")
damage_returned = round(dummy.take_overall_damage(-5, -5, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, -10, \
"take_overall_damage() should have returned -10, but returned [damage_returned] instead!")
damage_returned = round(dummy.heal_overall_damage(-5, -5, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, 10, \
"heal_overall_damage() should have returned 10, but returned [damage_returned] instead!")
if(!verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS))
TEST_FAIL("heal_overall_damage did not apply its healingcorrectly on the mob!")
// testing overhealing
damage_returned = round(dummy.heal_overall_damage(75, 99, updating_health = FALSE), 1)
TEST_ASSERT_EQUAL(damage_returned, 100, \
"heal_overall_damage() should have returned 100, but returned [damage_returned] instead!")
if(!verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS))
TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mob!")
/// Tests damage procs with godmode on
/datum/unit_test/mob_damage/proc/test_godmode(mob/living/carbon/human/consistent/dummy)
// Heal up, so that errors from the previous tests we won't cause this one to fail
dummy.fully_heal(HEAL_DAMAGE)
// flip godmode bit to 1
dummy.status_flags ^= GODMODE
// Apply 9 damage and then heal it
if(!test_apply_damage(dummy, amount = 9, expected = 0))
TEST_FAIL("ABOVE FAILURE: failed test_godmode! mob took damage despite having godmode enabled.")
if(!test_apply_damage(dummy, amount = -9, expected = 0))
TEST_FAIL("ABOVE FAILURE: failed test_godmode! mob healed when they should've been at full health.")
// Apply 11 damage and then heal it, this time with forced enabled. The damage should go through regardless of godmode.
if(!test_apply_damage(dummy, amount = 11, forced = TRUE))
TEST_FAIL("ABOVE FAILURE: failed test_godmode! godmode did not respect forced = TRUE")
if(!test_apply_damage(dummy, amount = -11, forced = TRUE))
TEST_FAIL("ABOVE FAILURE: failed test_godmode! godmode did not respect forced = TRUE")
// flip godmode bit back to 0
dummy.status_flags ^= GODMODE
/// Testing biotypes
/datum/unit_test/mob_damage/proc/test_biotypes(mob/living/carbon/human/consistent/dummy)
// Heal up, so that errors from the previous tests we won't cause this one to fail
dummy.fully_heal(HEAL_DAMAGE)
// Testing biotypes using a plasmaman, who is MOB_MINERAL and MOB_HUMANOID
dummy.set_species(/datum/species/plasmaman)
// argumentless default: should default to required_biotype = ALL. The damage should be applied in that case.
if(!test_apply_damage(dummy, 1, included_types = TOXLOSS|STAMINALOSS))
TEST_FAIL("ABOVE FAILURE: plasmaman did not take damage with biotypes = ALL")
// If we specify MOB_ORGANIC, the damage should not get applied because plasmamen lack that biotype.
if(!test_apply_damage(dummy, 1, expected = 0, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_ORGANIC))
TEST_FAIL("ABOVE FAILURE: plasmaman took damage with biotypes = MOB_ORGANIC")
// Now if we specify MOB_MINERAL the damage should get applied.
if(!test_apply_damage(dummy, 1, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_MINERAL))
TEST_FAIL("ABOVE FAILURE: plasmaman did not take damage with biotypes = MOB_MINERAL")
// Transform back to human
dummy.set_species(/datum/species/human)
// We have 2 damage presently.
// Try to heal it; let's specify MOB_MINERAL, which should no longer work because we have changed back to a human.
if(!test_apply_damage(dummy, -2, expected = 0, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_MINERAL))
TEST_FAIL("ABOVE FAILURE: human took damage with biotypes = MOB_MINERAL")
// Force heal some of the damage. When forced = TRUE the damage/healing gets applied no matter what.
if(!test_apply_damage(dummy, -1, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_MINERAL, forced = TRUE))
TEST_FAIL("ABOVE FAILURE: human did not get healed when biotypes = MOB_MINERAL and forced = TRUE")
// Now heal the rest of it with the correct biotype. Make sure that this works. We should have 0 damage afterwards.
if(!test_apply_damage(dummy, -1, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_ORGANIC))
TEST_FAIL("ABOVE FAILURE: human did not get healed with biotypes = MOB_ORGANIC")
/// Testing oxyloss with the TRAIT_NOBREATH
/datum/unit_test/mob_damage/proc/test_nobreath(mob/living/carbon/human/consistent/dummy)
// Heal up, so that errors from the previous tests we won't cause this one to fail
dummy.fully_heal(HEAL_DAMAGE)
// TRAIT_NOBREATH is supposed to prevent oxyloss damage (but not healing). Let's make sure that's the case.
ADD_TRAIT(dummy, TRAIT_NOBREATH, TRAIT_SOURCE_UNIT_TESTS)
// force some oxyloss here
dummy.setOxyLoss(2, updating_health = FALSE, forced = TRUE)
// Try to take more oxyloss damage with TRAIT_NOBREATH. It should not work.
if(!test_apply_damage(dummy, 2, expected = 0, amount_after = dummy.getOxyLoss(), included_types = OXYLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_nobreath! mob took oxyloss damage while having TRAIT_NOBREATH")
// Make sure we are still be able to heal the oxyloss. This should work.
if(!test_apply_damage(dummy, -2, amount_after = dummy.getOxyLoss()-2, included_types = OXYLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_nobreath! mob could not heal oxyloss damage while having TRAIT_NOBREATH")
REMOVE_TRAIT(dummy, TRAIT_NOBREATH, TRAIT_SOURCE_UNIT_TESTS)
/// Testing toxloss with TRAIT_TOXINLOVER and TRAIT_TOXIMMUNE
/datum/unit_test/mob_damage/proc/test_toxintraits(mob/living/carbon/human/consistent/dummy)
// Heal up, so that errors from the previous tests we won't cause this one to fail
dummy.fully_heal(HEAL_DAMAGE)
// TRAIT_TOXINLOVER is supposed to invert toxin damage and healing. Things that would normally cause toxloss now heal it, and vice versa.
ADD_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS)
// force some toxloss here
dummy.setToxLoss(2, updating_health = FALSE, forced = TRUE)
// Try to take more toxloss damage with TRAIT_TOXINLOVER. It should heal instead.
if(!test_apply_damage(dummy, 2, expected = 2, amount_after = dummy.getToxLoss()-2, included_types = TOXLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not heal from toxin damage with TRAIT_TOXINLOVER")
// If we try to heal the toxloss we should take damage instead
if(!test_apply_damage(dummy, -2, expected = -2, amount_after = dummy.getToxLoss()+2, included_types = TOXLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not take damage from toxin healing with TRAIT_TOXINLOVER")
// TOXIMMUNE trait should prevent the damage you get from being healed by toxins medicines while having TRAIT_TOXINLOVER
ADD_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS)
// need to force apply some toxin damage since the TOXIMUNNE trait sets toxloss to 0 upon being added
dummy.setToxLoss(2, updating_health = FALSE, forced = TRUE)
// try to 'heal' again - this time it should just do nothing because we should be immune to any sort of toxin damage - including from inverted healing
if(!test_apply_damage(dummy, -2, expected = 0, amount_after = dummy.getToxLoss(), included_types = TOXLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob should not have taken any damage or healing with TRAIT_TOXINLOVER + TRAIT_TOXIMMUNE")
// ok, let's try taking 'damage'. The inverted damage should still heal mobs with the TOXIMMUNE trait.
if(!test_apply_damage(dummy, 2, expected = 2, amount_after = dummy.getToxLoss()-2, included_types = TOXLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not heal from taking toxin damage with TRAIT_TOXINLOVER + TRAIT_TOXIMMUNE")
REMOVE_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS)
REMOVE_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS)
/// Testing heal_ordered_damage()
/datum/unit_test/mob_damage/proc/test_ordered_healing(mob/living/carbon/human/consistent/dummy)
// Heal up, so that errors from the previous tests we won't cause this one to fail
dummy.fully_heal(HEAL_DAMAGE)
var/damage_returned
// We apply 20 brute, 20 burn, and 20 toxin damage. 60 damage total
apply_damage(dummy, 20, included_types = TOXLOSS|BRUTELOSS|FIRELOSS)
// Heal 30 damage of that, starting from brute
damage_returned = round(dummy.heal_ordered_damage(30, list(BRUTE, BURN, TOX)), 1)
TEST_ASSERT_EQUAL(damage_returned, 30, \
"heal_ordered_damage() should have returned 30, but returned [damage_returned] instead!")
// Should have 10 burn damage and 20 toxins damage remaining, let's check
TEST_ASSERT_EQUAL(dummy.getBruteLoss(), 0, \
"[src] should have 0 brute damage, but has [dummy.getBruteLoss()] instead!")
TEST_ASSERT_EQUAL(dummy.getFireLoss(), 10, \
"[src] should have 10 burn damage, but has [dummy.getFireLoss()] instead!")
TEST_ASSERT_EQUAL(dummy.getToxLoss(), 20, \
"[src] should have 20 toxin damage, but has [dummy.getToxLoss()] instead!")
// Now heal the remaining 30, overhealing by 5.
damage_returned = round(dummy.heal_ordered_damage(35, list(BRUTE, BURN, TOX)), 1)
TEST_ASSERT_EQUAL(damage_returned, 30, \
"heal_ordered_damage() should have returned 30, but returned [damage_returned] instead!")
// Should have no damage remaining
TEST_ASSERT_EQUAL(dummy.getBruteLoss(), 0, \
"[src] should have 0 brute damage, but has [dummy.getBruteLoss()] instead!")
TEST_ASSERT_EQUAL(dummy.getFireLoss(), 0, \
"[src] should have 0 burn damage, but has [dummy.getFireLoss()] instead!")
TEST_ASSERT_EQUAL(dummy.getToxLoss(), 0, \
"[src] should have 0 toxin damage, but has [dummy.getToxLoss()] instead!")
/// Tests that mob damage procs are working as intended for basic mobs
/datum/unit_test/mob_damage/basic
/datum/unit_test/mob_damage/basic/Run()
SSmobs.pause()
var/mob/living/basic/mouse/gray/gusgus = allocate(/mob/living/basic/mouse/gray)
// give gusgus a damage_coeff of 1 for this test
gusgus.damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 1, OXY = 1)
// tank mouse
gusgus.maxHealth = 200
test_sanity_simple(gusgus)
test_sanity_complex(gusgus)
/**
* Check that the mob has a specific amount of damage. Note: basic mobs have all incoming damage types besides stam converted into brute damage.
*
* By default this checks that the mob has <amount> of every type of damage.
* Arguments:
* * testing_mob - the mob to check the damage of
* * amount - the amount of damage to verify that the mob has
* * expected - the expected return value of the damage procs, if it differs from the default of (amount * 4)
* * included_types - Bitflag of damage types to check.
*/
/datum/unit_test/mob_damage/basic/verify_damage(mob/living/testing_mob, amount, expected, included_types = ALL)
if(included_types & TOXLOSS)
TEST_ASSERT_EQUAL(testing_mob.getToxLoss(), 0, \
"[testing_mob] should have [0] toxin damage, instead they have [testing_mob.getToxLoss()]!")
if(included_types & BRUTELOSS)
TEST_ASSERT_EQUAL(round(testing_mob.getBruteLoss(), 1), expected || amount * 4, \
"[testing_mob] should have [expected || amount * 4] brute damage, instead they have [testing_mob.getBruteLoss()]!")
if(included_types & FIRELOSS)
TEST_ASSERT_EQUAL(round(testing_mob.getFireLoss(), 1), 0, \
"[testing_mob] should have [0] burn damage, instead they have [testing_mob.getFireLoss()]!")
if(included_types & OXYLOSS)
TEST_ASSERT_EQUAL(testing_mob.getOxyLoss(), 0, \
"[testing_mob] should have [0] oxy damage, instead they have [testing_mob.getOxyLoss()]!")
if(included_types & STAMINALOSS)
TEST_ASSERT_EQUAL(testing_mob.getStaminaLoss(), amount, \
"[testing_mob] should have [amount] stamina damage, instead they have [testing_mob.getStaminaLoss()]!")
return TRUE
/datum/unit_test/mob_damage/basic/test_sanity_simple(mob/living/basic/mouse/gray/gusgus)
// check to see if basic mob damage works
// Simple damage and healing
// Take 1 damage, heal for 1
if(!test_apply_damage(gusgus, amount = 1))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly")
if(!test_apply_damage(gusgus, amount = -1))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly")
// Give 2 damage of every time (translates to 8 brute, 2 staminaloss)
if(!test_apply_damage(gusgus, amount = 2))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly")
// underhealing: heal 1 damage of every type (translates to 4 brute, 1 staminaloss)
if(!test_apply_damage(gusgus, amount = -1))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly")
// overhealing
// heal 11 points of toxloss (should take care of all 4 brute damage remaining)
if(!apply_damage(gusgus, -11, expected = 4, included_types = TOXLOSS))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! toxloss was not applied correctly")
// heal the remaining point of staminaloss
if(!apply_damage(gusgus, -11, expected = 1, included_types = STAMINALOSS))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to heal staminaloss correctly")
// heal 35 points of each type, we should already be at full health so nothing should happen
if(!test_apply_damage(gusgus, amount = -35, expected = 0))
TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! overhealing was not applied correctly")
/datum/unit_test/mob_damage/basic/test_sanity_complex(mob/living/basic/mouse/gray/gusgus)
// Heal up, so that errors from the previous tests we won't cause this one to fail
gusgus.fully_heal(HEAL_DAMAGE)
var/damage_returned
// overall damage procs
// take 5 brute, 2 burn
damage_returned = gusgus.take_bodypart_damage(5, 2, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, -7, \
"take_bodypart_damage() should have returned -7, but returned [damage_returned] instead!")
TEST_ASSERT_EQUAL(gusgus.bruteloss, 7, \
"Mouse should have 7 brute damage, instead they have [gusgus.bruteloss]!")
TEST_ASSERT_EQUAL(gusgus.fireloss, 0, \
"Mouse should have 0 burn damage, instead they have [gusgus.fireloss]!")
// heal 4 brute, 1 burn
damage_returned = gusgus.heal_bodypart_damage(4, 1, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, 5, \
"heal_bodypart_damage() should have returned 5, but returned [damage_returned] instead!")
TEST_ASSERT_EQUAL(gusgus.bruteloss, 2, \
"Mouse should have 2 brute damage, instead they have [gusgus.bruteloss]!")
TEST_ASSERT_EQUAL(gusgus.fireloss, 0, \
"Mouse should have 0 burn damage, instead they have [gusgus.fireloss]!")
// heal 1 brute, 1 burn
damage_returned = gusgus.heal_overall_damage(1, 1, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, 2, \
"heal_overall_damage() should have returned 2, but returned [damage_returned] instead!")
TEST_ASSERT_EQUAL(gusgus.bruteloss, 0, \
"Mouse should have 0 brute damage, instead they have [gusgus.bruteloss]!")
TEST_ASSERT_EQUAL(gusgus.fireloss, 0, \
"Mouse should have 0 burn damage, instead they have [gusgus.fireloss]!")
// take 50 brute, 50 burn
damage_returned = gusgus.take_overall_damage(3, 3, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, -6, \
"take_overall_damage() should have returned -6, but returned [damage_returned] instead!")
if(!verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS))
TEST_FAIL("take_overall_damage did not apply its damage correctly on the mouse!")
// testing negative args with the overall damage procs
damage_returned = gusgus.take_bodypart_damage(-1, -1, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, -2, \
"take_bodypart_damage() should have returned -2, but returned [damage_returned] instead!")
damage_returned = gusgus.heal_bodypart_damage(-1, -1, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, 2, \
"heal_bodypart_damage() should have returned 2, but returned [damage_returned] instead!")
damage_returned = gusgus.take_overall_damage(-1, -1, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, -2, \
"take_overall_damage() should have returned -2, but returned [damage_returned] instead!")
damage_returned = gusgus.heal_overall_damage(-1, -1, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, 2, \
"heal_overall_damage() should have returned 2, but returned [damage_returned] instead!")
if(!verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS))
TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mouse!")
// testing overhealing
damage_returned = gusgus.heal_overall_damage(75, 99, updating_health = FALSE)
TEST_ASSERT_EQUAL(damage_returned, 6, \
"heal_overall_damage() should have returned 6, but returned [damage_returned] instead!")
if(!verify_damage(gusgus, 0, included_types = BRUTELOSS))
TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mouse!")