mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
## About The Pull Request This PR adds a new unit test for all organs, a new unit test for lungs, and includes improvements for the existing breath and organ_set_bonus tests. Using the tests, I was able to root out bugs in the organs. This PR includes an advanced refactor of several developer-facing functions. This PR certainly represents a "quality pass" for organs which will make them easier to develop from now on. ### Synopsis of changes: 1. Fixed many fundamental bugs in organ code, especially in `Insert()`/`Remove()` and their overrides. 2. Added two new procs to `/obj/item/organ` named `on_insert` and `on_remove`, each being called after `Insert()`/`Remove()`. 3. Added `organ_effects` lazylist to `/obj/item/organ`. Converted `organ_traits` to lazylist. 2x less empty lists per organ. 4. Adding `SHOULD_CALL_PARENT(TRUE)` to `Insert()`/`Remove()` was very beneficial to stability and overall code health. 5. Created unit test `organ_sanity` for all usable organs in the game. Tests insertion and removal. 6. Created unit test `lungs_sanity` for `/obj/item/organ/internal/lungs`. 7. Improved `breath_sanity` unit tests with additional tests and conditions. 8. Improved `organ_set_bonus_sanity` unit tests with better documentation and maintainable code. --- ### Granular bug/fix list: - A lot of organs are overriding `Insert()` to apply unique side-effects, but aren't checking the return value of the parent proc which causes the activation of side-effects even if the insertion technically fails. I noticed the use-case of applying "unique side-effects" is repeated across a lot of organs in the game, and by overriding `Insert()` the potential for bugs is very high; I solved this problem with inversion-of-control by adding two new procs to `/obj/item/organ` named `on_insert` and `on_remove`, each being called after `Insert()` and `Remove()` succeed. - Many organs, such as abductor "glands", cursed heart, demon heart, alien hive-node, alien plasma-vessel, etc, were not returning their parent's `Insert()` proc return value at all, and as a result those organs `Insert()`s were always returning `null`. I have been mopping those bugs up in my last few PRs, and now the unit test reveals it all. Functions such as those in surgery expect a truthy value to be returned from `Insert()` to represent insertion success, and otherwise it force-moves the organ out of the mob. - Fixed abductor "glands" which had a hard-del bug due to their `Remove()` not calling the parent proc. - Fixed cybernetic arm implants which had a hard-del bug due to `Remove()` not resetting their `hand` variable to `null`. - Fixed lungs gas exchange implementation, which was allowing exhaled gases to feedback into the inhaled gases, which caused Humans to inhale much more gas than intended and not exhale expected gases. ### Overview of the `organ_sanity` unit test: - The new `organ_sanity` unit test gathers all "usable" organs in the game and tests to see if their `Insert()` and `Remove()` functions behave as we expect them to. - Some organs, such as the Nightmare Brain, cause the mob's species to change which subsequently swaps out all of their organs; the unit test accounts for these organs via the typecache `species_changing_organs`. - Some organs are not usable in-game and can't be unit tested, so the unit test accounts for them via the typecache `test_organ_blacklist`. ### Overview of the `lungs_sanity` unit test: - This unit test focuses on `/obj/item/organ/internal/lungs` including Plasmaman and Ashwalker lungs. The test focuses on testing the lungs' `check_breath()` proc. - The tests are composed of calling `check_breath` with different gas mixes to test breathing and suffocation. - Includes gas exchange test for inhaled/exhaled gases, such as O2 to CO2. ### Improvements to the `breath_sanity` unit tests: - Added additional tests for suffocation with empty internals, pure Nitrogen internals, and a gas-less turf. - Includes slightly more reliable tests for internals tanks. ## Why It's Good For The Game **Organs and Lungs were mostly untested. Too many refactors have been submitted without the addition of unit tests to prove the code works at all.** Time to stop. _Time to get some help_. Due to how bad the code health is in organs, any time we've tried to work with them some sort of bug caused them to blow up in our faces. I am trying to fix some of that by establishing some standard testing for organs. These tests have revealed and allowed me to fix lot of basic developer errors/oversights, as well as a few severe bugs.  ## Changelog 🆑 A.C.M.O. fix: Fixed lungs gas exchange implementation, so you always inhale and exhale the correct gases. fix: Fixed a large quantity of hard-deletes which were being caused by organs and cybernetic organs. fix: Fixed many organs which were applying side-effects regardless of whether or not the insertion failed. code: Added unit tests for Organs. code: Added unit tests for Lungs. code: Improved unit tests for breathing. code: Improved unit tests for DNA Infuser organs. /🆑
102 lines
6.0 KiB
Plaintext
102 lines
6.0 KiB
Plaintext
/// Tests to ensure humans, plasmamen, and ashwalkers can breath in normal situations.
|
|
/// Ensures algorithmic correctness of the "breathe()" and "toggle_internals()" procs.
|
|
/// Built to prevent regression on an issue surrounding QUANTIZE() and BREATH_VOLUME.
|
|
/// See the comment on BREATH_VOLUME for more details.
|
|
/datum/unit_test/breath
|
|
abstract_type = /datum/unit_test/breath
|
|
|
|
/// Equips the given Human with a new instance of the given tank type and a breathing mask.
|
|
/// Returns the new equipped tank.
|
|
/datum/unit_test/breath/proc/equip_labrat_internals(mob/living/carbon/human/lab_rat, tank_type)
|
|
var/obj/item/clothing/mask/breath/mask = allocate(/obj/item/clothing/mask/breath)
|
|
var/obj/item/tank/internals/source = allocate(tank_type)
|
|
lab_rat.equip_to_slot_if_possible(mask, ITEM_SLOT_MASK)
|
|
lab_rat.equip_to_slot_if_possible(source, ITEM_SLOT_HANDS)
|
|
return source
|
|
|
|
/datum/unit_test/breath/breath_sanity/Run()
|
|
// Breathing from turf.
|
|
var/mob/living/carbon/human/lab_rat = allocate(/mob/living/carbon/human/consistent)
|
|
lab_rat.forceMove(run_loc_floor_bottom_left)
|
|
var/turf/open/to_fill = run_loc_floor_bottom_left
|
|
to_fill.initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
|
to_fill.air = to_fill.create_gas_mixture()
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(!lab_rat.failed_last_breath && !lab_rat.has_alert(ALERT_NOT_ENOUGH_OXYGEN), "Humans can't get a full breath from the standard initial_gas_mix on a turf")
|
|
|
|
// Breathing from standard internals tank.
|
|
lab_rat = allocate(/mob/living/carbon/human/consistent)
|
|
var/obj/item/tank/internals/source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen)
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(!lab_rat.failed_last_breath && !lab_rat.has_alert(ALERT_NOT_ENOUGH_OXYGEN), "Humans can't get a full breath from standard o2 tanks")
|
|
if(!isnull(lab_rat.internal))
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && isnull(lab_rat.internal), "toggle_internals() failed to close internals")
|
|
|
|
// Empty internals suffocation.
|
|
lab_rat = allocate(/mob/living/carbon/human/consistent)
|
|
source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen/empty)
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals")
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(lab_rat.failed_last_breath && lab_rat.has_alert(ALERT_NOT_ENOUGH_OXYGEN), "Humans should suffocate from empty o2 tanks")
|
|
|
|
// Nitrogen internals suffocation.
|
|
lab_rat = allocate(/mob/living/carbon/human/consistent)
|
|
source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen/empty)
|
|
source.air_contents.assert_gas(/datum/gas/nitrogen)
|
|
source.air_contents.gases[/datum/gas/nitrogen][MOLES] = (10 * ONE_ATMOSPHERE) * source.volume / (R_IDEAL_GAS_EQUATION * T20C)
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals")
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(lab_rat.failed_last_breath && lab_rat.has_alert(ALERT_NOT_ENOUGH_OXYGEN), "Humans should suffocate from pure n2 tanks")
|
|
|
|
/datum/unit_test/breath/breath_sanity/Destroy()
|
|
//Reset initial_gas_mix to avoid future issues on other tests
|
|
var/turf/open/to_fill = run_loc_floor_bottom_left
|
|
to_fill.initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
|
return ..()
|
|
|
|
/// Tests to make sure plasmaman can breath from their internal tanks
|
|
/datum/unit_test/breath/breath_sanity_plasmamen
|
|
|
|
/datum/unit_test/breath/breath_sanity_plasmamen/Run()
|
|
// Breathing from pure Plasma internals.
|
|
var/mob/living/carbon/human/species/plasma/lab_rat = allocate(/mob/living/carbon/human/species/plasma)
|
|
var/obj/item/tank/internals/plasmaman/source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/plasmaman)
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals")
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(!lab_rat.failed_last_breath && !lab_rat.has_alert(ALERT_NOT_ENOUGH_PLASMA), "Plasmamen can't get a full breath from a standard plasma tank")
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && !lab_rat.internal, "Plasmaman toggle_internals() failed to toggle internals")
|
|
|
|
// Empty internals suffocation.
|
|
lab_rat = allocate(/mob/living/carbon/human/species/plasma)
|
|
source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen/empty)
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals")
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(lab_rat.failed_last_breath && lab_rat.has_alert(ALERT_NOT_ENOUGH_PLASMA), "Plasmamen should suffocate from empty o2 tanks")
|
|
|
|
// Nitrogen internals suffocation.
|
|
lab_rat = allocate(/mob/living/carbon/human/species/plasma)
|
|
source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen/empty)
|
|
source.air_contents.assert_gas(/datum/gas/nitrogen)
|
|
source.air_contents.gases[/datum/gas/nitrogen][MOLES] = (10 * ONE_ATMOSPHERE) * source.volume / (R_IDEAL_GAS_EQUATION * T20C)
|
|
TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals")
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(lab_rat.failed_last_breath && lab_rat.has_alert(ALERT_NOT_ENOUGH_PLASMA), "Humans should suffocate from pure n2 tanks")
|
|
|
|
/// Tests to make sure ashwalkers can breathe from the lavaland air.
|
|
/datum/unit_test/breath/breath_sanity_ashwalker
|
|
|
|
/datum/unit_test/breath/breath_sanity_ashwalker/Run()
|
|
var/mob/living/carbon/human/species/lizard/ashwalker/lab_rat = allocate(/mob/living/carbon/human/species/lizard/ashwalker)
|
|
lab_rat.forceMove(run_loc_floor_bottom_left)
|
|
var/turf/open/to_fill = run_loc_floor_bottom_left
|
|
to_fill.initial_gas_mix = LAVALAND_DEFAULT_ATMOS
|
|
to_fill.air = to_fill.create_gas_mixture()
|
|
lab_rat.breathe()
|
|
TEST_ASSERT(!lab_rat.has_alert(ALERT_NOT_ENOUGH_OXYGEN), "Ashwalkers can't get a full breath from the Lavaland's initial_gas_mix on a turf")
|
|
|
|
/datum/unit_test/breath/breath_sanity_ashwalker/Destroy()
|
|
//Reset initial_gas_mix to avoid future issues on other tests
|
|
var/turf/open/to_fill = run_loc_floor_bottom_left
|
|
to_fill.initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
|
return ..()
|