From 1b5c0489a4088dca925ab061a389d95005c95820 Mon Sep 17 00:00:00 2001 From: san7890 Date: Wed, 3 May 2023 08:56:46 -0600 Subject: [PATCH] `ex_act()` will work on basic mobs again (lol) + Unit Test (#74953) basically ex_act's implementation on basic mobs would call parent and then react to it's value, this is presumably to do the first check about space vine mutations and whatever. the problem is that the `/mob/living` implementation would itself also call parent, and that would always return null because `/atom/proc/ex_act` doesn't have a set return value. So, this simply would _always_ early return, with ex_act presumably *never* working on basic mobs for at least four months now. I decided to then change up the return values for pretty much all implementations of `ex_act()` since there was no rhyme or reason to returning null/FALSE/TRUE, and documenting why it's like that. Just to make sure I wasn't breaking anything doing this (at least on base implementations), I wrote a unit test for all of the three major physical types in game (objs, mobs, turfs) because i am a paranoid fuckar. we should be good to go now though. ## Why It's Good For The Game i noticed this because placing c4's on sargeant araneus wouldn't actually damage it whatsoever. now it actually does the stated 30 damage, but araneus has like 250 health so it doesn't actually matter in the long run. whatever at least it does the damn 30 now. also adds a unit test for this specific case as well as a range of other cases to ensure this stuff doesn't silently break in this way anymore --- code/game/atoms.dm | 7 + code/game/machinery/syndicatebomb.dm | 5 +- .../objects/effects/anomalies/_anomalies.dm | 3 + .../objects/effects/decals/cleanable/misc.dm | 4 + code/game/objects/effects/decals/decal.dm | 1 + .../objects/items/devices/chameleonproj.dm | 1 + .../items/devices/portable_chem_mixer.dm | 2 + code/game/objects/items/latexballoon.dm | 2 + code/game/objects/items/melee/misc.dm | 1 + code/game/objects/obj_defense.dm | 8 +- .../crates_lockers/closets/secure/freezer.dm | 1 + code/game/objects/structures/safe.dm | 4 + .../transit_tubes/transit_tube_pod.dm | 7 +- code/game/turfs/closed/minerals.dm | 4 +- code/game/turfs/closed/walls.dm | 9 +- code/game/turfs/open/asteroid.dm | 2 +- code/game/turfs/open/floor.dm | 2 + code/game/turfs/open/floor/plating.dm | 1 + .../game/turfs/open/floor/reinforced_floor.dm | 2 + code/game/turfs/open/lava.dm | 2 +- code/game/turfs/open/misc.dm | 1 + code/game/turfs/open/sand.dm | 2 +- .../antagonists/blob/structures/core.dm | 1 + .../events/space_vines/vine_structure.dm | 2 + code/modules/mining/ores_coins.dm | 7 + .../modules/mob/living/basic/basic_defense.dm | 7 +- .../mob/living/carbon/alien/alien_defense.dm | 11 +- .../mob/living/carbon/human/human_defense.dm | 10 +- .../mob/living/silicon/ai/ai_defense.dm | 2 +- .../mob/living/silicon/robot/robot_defense.dm | 2 + .../living/simple_animal/animal_defense.dm | 8 +- .../mob/living/simple_animal/bot/mulebot.dm | 2 + .../living/simple_animal/guardian/guardian.dm | 2 + .../simple_animal/guardian/types/protector.dm | 2 + .../hostile/megafauna/_megafauna.dm | 2 + .../hostile/megafauna/colossus.dm | 3 +- .../hostile/megafauna/demonic_frost_miner.dm | 1 + .../hostile/mining_mobs/basilisk.dm | 2 + .../simple_animal/hostile/slaughter_demon.dm | 2 + code/modules/pai/defense.dm | 2 + code/modules/power/cell.dm | 4 +- code/modules/power/gravitygenerator.dm | 3 + code/modules/power/rtg.dm | 2 + .../singularity/dark_matter_singularity.dm | 2 +- code/modules/power/singularity/singularity.dm | 2 + code/modules/reagents/reagent_dispenser.dm | 1 + code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/explosion_action.dm | 210 ++++++++++++++++++ 48 files changed, 330 insertions(+), 34 deletions(-) create mode 100644 code/modules/unit_tests/explosion_action.dm diff --git a/code/game/atoms.dm b/code/game/atoms.dm index e45e49ef466..2cf13ab998c 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -915,9 +915,16 @@ * Should be called through the [EX_ACT] wrapper macro. * The wrapper takes care of the [COMSIG_ATOM_EX_ACT] signal. * as well as calling [/atom/proc/contents_explosion]. + * + * Returns TRUE by default, and behavior should be implemented on children procs on a per-atom basis. Should only return FALSE if we resist the explosion for any reason. + * We assume that the default is TRUE because all atoms should be considered destructible in some manner unless they explicitly opt out (in our current framework). + * However, the return value itself doesn't have any external consumers, it's only so children procs can listen to the value from their parent procs (due to the nature of the [EX_ACT] macro). + * Thus, the return value only matters on overrides of this proc, and the only thing that truly matters is the code that is executed (applying damage, calling damage procs, etc.) + * */ /atom/proc/ex_act(severity, target) set waitfor = FALSE + return TRUE /** * React to a hit by a blob objecd diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm index 89af7550428..4f57d5961ec 100644 --- a/code/game/machinery/syndicatebomb.dm +++ b/code/game/machinery/syndicatebomb.dm @@ -61,7 +61,7 @@ ..() /obj/machinery/syndicatebomb/ex_act(severity, target) - return + return FALSE /obj/machinery/syndicatebomb/process() if(!active) @@ -321,6 +321,7 @@ /obj/item/bombcore/ex_act(severity, target) // Little boom can chain a big boom. detonate() + return TRUE /obj/item/bombcore/burn() detonate() @@ -346,7 +347,7 @@ desc = "After a string of unwanted detonations, this payload has been specifically redesigned to not explode unless triggered electronically by a bomb shell." /obj/item/bombcore/syndicate/ex_act(severity, target) - return + return FALSE /obj/item/bombcore/syndicate/burn() return ..() diff --git a/code/game/objects/effects/anomalies/_anomalies.dm b/code/game/objects/effects/anomalies/_anomalies.dm index 16c98365edd..49198ced59c 100644 --- a/code/game/objects/effects/anomalies/_anomalies.dm +++ b/code/game/objects/effects/anomalies/_anomalies.dm @@ -88,6 +88,9 @@ /obj/effect/anomaly/ex_act(severity, target) if(severity >= EXPLODE_DEVASTATE) qdel(src) + return TRUE + + return FALSE /obj/effect/anomaly/proc/anomalyNeutralize() new /obj/effect/particle_effect/fluid/smoke/bad(loc) diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 500f84cc757..bad573c0089 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -44,6 +44,7 @@ /obj/effect/decal/cleanable/glass/ex_act() qdel(src) + return TRUE /obj/effect/decal/cleanable/glass/plasma icon_state = "plasmatiny" @@ -191,6 +192,9 @@ /obj/effect/decal/cleanable/shreds/ex_act(severity, target) if(severity >= EXPLODE_DEVASTATE) //so shreds created during an explosion aren't deleted by the explosion. qdel(src) + return TRUE + + return FALSE /obj/effect/decal/cleanable/shreds/Initialize(mapload, oldname) pixel_x = rand(-10, 10) diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 1f2193e9506..6b0678ab068 100644 --- a/code/game/objects/effects/decals/decal.dm +++ b/code/game/objects/effects/decals/decal.dm @@ -23,6 +23,7 @@ /obj/effect/decal/ex_act(severity, target) qdel(src) + return TRUE /obj/effect/decal/fire_act(exposed_temperature, exposed_volume) if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index 534871bdda6..3ece9837b1a 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -145,6 +145,7 @@ /obj/effect/dummy/chameleon/ex_act(S, T) master.disrupt() + return TRUE /obj/effect/dummy/chameleon/bullet_act() . = ..() diff --git a/code/game/objects/items/devices/portable_chem_mixer.dm b/code/game/objects/items/devices/portable_chem_mixer.dm index adb2da30582..ebd2ee31641 100644 --- a/code/game/objects/items/devices/portable_chem_mixer.dm +++ b/code/game/objects/items/devices/portable_chem_mixer.dm @@ -37,6 +37,8 @@ if(severity > EXPLODE_LIGHT) return ..() + return FALSE + /obj/item/storage/portable_chem_mixer/attackby(obj/item/I, mob/user, params) if (is_reagent_container(I) && !(I.item_flags & ABSTRACT) && I.is_open_container() && atom_storage.locked) var/obj/item/reagent_containers/B = I diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm index ac4d2874e57..c055aa2702e 100644 --- a/code/game/objects/items/latexballoon.dm +++ b/code/game/objects/items/latexballoon.dm @@ -93,6 +93,8 @@ if (prob(50)) qdel(src) + return TRUE + /obj/item/latexballoon/bullet_act(obj/projectile/projectile) if(projectile.damage > 0) burst() diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 95d9fba9b73..1c6f37b6589 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -247,6 +247,7 @@ span_hear("You hear a loud crack as you are washed with a wave of heat.") ) consume_everything() + return TRUE /obj/item/melee/supermatter_sword/acid_act() visible_message(span_danger("The acid smacks into [src] and rapidly flashes to ash."),\ diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index fd7d0fab5f5..e7ab18cc44b 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -5,14 +5,14 @@ /obj/ex_act(severity, target) if(resistance_flags & INDESTRUCTIBLE) - return + return FALSE . = ..() //contents explosion if(QDELETED(src)) - return + return TRUE if(target == src) take_damage(INFINITY, BRUTE, BOMB, 0) - return + return TRUE switch(severity) if(EXPLODE_DEVASTATE) take_damage(INFINITY, BRUTE, BOMB, 0) @@ -21,6 +21,8 @@ if(EXPLODE_LIGHT) take_damage(rand(10, 90), BRUTE, BOMB, 0) + return TRUE + /obj/bullet_act(obj/projectile/P) . = ..() playsound(src, P.hitsound, 50, TRUE) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm index fc4b2c03321..ae076da40cd 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -31,6 +31,7 @@ return ..() jones = TRUE flags_1 &= ~PREVENT_CONTENTS_EXPLOSION_1 + return FALSE /obj/structure/closet/secure_closet/freezer/atom_destruction(damage_flag) new /obj/item/stack/sheet/iron(drop_location(), 1) diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index c14c29cc62b..5d42331b5df 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -93,6 +93,10 @@ FLOOR SAFES if(3) desc = initial(desc) + "\nThe lock seems to be broken." + return TRUE + + return FALSE + /obj/structure/safe/ui_assets(mob/user) return list( get_asset_datum(/datum/asset/simple/safe), diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm index 7d8808dc3de..f94e03dc36a 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm @@ -54,8 +54,11 @@ /obj/structure/transit_tube_pod/ex_act(severity, target) . = ..() - if(!QDELETED(src)) - empty_pod() + if(QDELETED(src)) + return TRUE + + empty_pod() + return TRUE /obj/structure/transit_tube_pod/contents_explosion(severity, target) switch(severity) diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm index f286e1b86a1..27cfa04b461 100644 --- a/code/game/turfs/closed/minerals.dm +++ b/code/game/turfs/closed/minerals.dm @@ -178,7 +178,7 @@ . = ..() if(target == src) gets_drilled(null, FALSE) - return + return TRUE switch(severity) if(EXPLODE_DEVASTATE) gets_drilled(null, FALSE) @@ -189,6 +189,8 @@ if(prob(75)) gets_drilled(null, FALSE) + return TRUE + /turf/closed/mineral/blob_act(obj/structure/blob/B) if(prob(50)) gets_drilled(give_exp = FALSE) diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index b9b1ec2a487..789cae54939 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -101,21 +101,24 @@ /turf/closed/wall/ex_act(severity, target) if(target == src) dismantle_wall(1,1) - return + return TRUE switch(severity) if(EXPLODE_DEVASTATE) //SN src = null var/turf/NT = ScrapeAway() NT.contents_explosion(severity, target) - return + return TRUE if(EXPLODE_HEAVY) dismantle_wall(prob(50), TRUE) if(EXPLODE_LIGHT) if (prob(hardness)) dismantle_wall(0,1) + if(!density) - ..() + return ..() + + return TRUE /turf/closed/wall/blob_act(obj/structure/blob/B) diff --git a/code/game/turfs/open/asteroid.dm b/code/game/turfs/open/asteroid.dm index a9d346cbf32..af5340c2da2 100644 --- a/code/game/turfs/open/asteroid.dm +++ b/code/game/turfs/open/asteroid.dm @@ -63,7 +63,7 @@ return /turf/open/misc/asteroid/ex_act(severity, target) - return + return FALSE /turf/open/misc/asteroid/attackby(obj/item/W, mob/user, params) . = ..() diff --git a/code/game/turfs/open/floor.dm b/code/game/turfs/open/floor.dm index a78ebe225e7..a1f31099c5a 100644 --- a/code/game/turfs/open/floor.dm +++ b/code/game/turfs/open/floor.dm @@ -105,6 +105,8 @@ src.break_tile() src.hotspot_expose(1000,CELL_VOLUME) + return FALSE + /turf/open/floor/is_shielded() for(var/obj/structure/A in contents) return 1 diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm index 56bd7010730..b9349f87d86 100644 --- a/code/game/turfs/open/floor/plating.dm +++ b/code/game/turfs/open/floor/plating.dm @@ -176,6 +176,7 @@ /turf/open/floor/plating/foam/ex_act() . = ..() ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + return TRUE /turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/I, tool_type) return diff --git a/code/game/turfs/open/floor/reinforced_floor.dm b/code/game/turfs/open/floor/reinforced_floor.dm index 3d70823a3a1..1912f4dc872 100644 --- a/code/game/turfs/open/floor/reinforced_floor.dm +++ b/code/game/turfs/open/floor/reinforced_floor.dm @@ -76,6 +76,8 @@ if(prob(50)) ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + return TRUE + /turf/open/floor/engine/singularity_pull(S, current_size) ..() if(current_size >= STAGE_FIVE) diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm index 403d029e7c7..5e4bebc6eb5 100644 --- a/code/game/turfs/open/lava.dm +++ b/code/game/turfs/open/lava.dm @@ -109,7 +109,7 @@ update_appearance(~UPDATE_SMOOTHING) /turf/open/lava/ex_act(severity, target) - return + return FALSE /turf/open/lava/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) return diff --git a/code/game/turfs/open/misc.dm b/code/game/turfs/open/misc.dm index 55e6ca05b13..fa3a65f44c5 100644 --- a/code/game/turfs/open/misc.dm +++ b/code/game/turfs/open/misc.dm @@ -67,6 +67,7 @@ break_tile() hotspot_expose(1000,CELL_VOLUME) + return TRUE /turf/open/misc/is_shielded() for(var/obj/structure/A in contents) diff --git a/code/game/turfs/open/sand.dm b/code/game/turfs/open/sand.dm index 4b03a0b7ac9..8835cbdb03c 100644 --- a/code/game/turfs/open/sand.dm +++ b/code/game/turfs/open/sand.dm @@ -10,7 +10,7 @@ heavyfootstep = FOOTSTEP_GENERIC_HEAVY /turf/open/misc/beach/ex_act(severity, target) - return + return FALSE /turf/open/misc/beach/sand gender = PLURAL diff --git a/code/modules/antagonists/blob/structures/core.dm b/code/modules/antagonists/blob/structures/core.dm index 2f62ca42785..1f0b761651b 100644 --- a/code/modules/antagonists/blob/structures/core.dm +++ b/code/modules/antagonists/blob/structures/core.dm @@ -61,6 +61,7 @@ /obj/structure/blob/special/core/ex_act(severity, target) var/damage = 10 * (severity + 1) //remember, the core takes half brute damage, so this is 20/15/10 damage based on severity take_damage(damage, BRUTE, BOMB, 0) + return TRUE /obj/structure/blob/special/core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, overmind_reagent_trigger = 1) . = ..() diff --git a/code/modules/events/space_vines/vine_structure.dm b/code/modules/events/space_vines/vine_structure.dm index 21616ae4f65..9ba68136ef7 100644 --- a/code/modules/events/space_vines/vine_structure.dm +++ b/code/modules/events/space_vines/vine_structure.dm @@ -183,6 +183,8 @@ if(!index && prob(34 * severity)) qdel(src) + return TRUE + /obj/structure/spacevine/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return (always_atmos_process || exposed_temperature > FIRE_MINIMUM_TEMPERATURE_TO_SPREAD || exposed_temperature < VINE_FREEZING_POINT || !can_spread)//if you're room temperature you're safe diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 824dede7fb7..17101142db3 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -132,6 +132,9 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ /obj/item/stack/ore/glass/ex_act(severity, target) if(severity) qdel(src) + return TRUE + + return FALSE /obj/item/stack/ore/glass/basalt name = "volcanic ash" @@ -284,6 +287,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ /obj/item/gibtonite/ex_act() GibtoniteReaction(null, 1) + return TRUE /obj/item/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) if(!primed) @@ -327,6 +331,9 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ /obj/item/stack/ore/ex_act(severity, target) if(severity >= EXPLODE_DEVASTATE) qdel(src) + return TRUE + + return FALSE /*****************************Coin********************************/ diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm index 7d00dbda288..cd1ded2ed6f 100644 --- a/code/modules/mob/living/basic/basic_defense.dm +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -139,7 +139,8 @@ /mob/living/basic/ex_act(severity, target, origin) . = ..() if(!. || QDELETED(src)) - return + return FALSE + var/bomb_armor = getarmor(null, BOMB) switch(severity) if (EXPLODE_DEVASTATE) @@ -148,7 +149,7 @@ else investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS) gib() - return + if (EXPLODE_HEAVY) var/bloss = 60 if(prob(bomb_armor)) @@ -161,6 +162,8 @@ bloss = bloss / 1.5 apply_damage(bloss, damagetype = BRUTE) + return TRUE + /mob/living/basic/blob_act(obj/structure/blob/attacking_blob) apply_damage(20, damagetype = BRUTE) diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index b12f4e71e6e..60f40f03709 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -96,18 +96,14 @@ In all, this is a lot like the monkey code. /N updatehealth() /mob/living/carbon/alien/ex_act(severity, target, origin) - if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) - return FALSE - . = ..() - if(QDELETED(src)) - return + if(!. || QDELETED(src)) + return FALSE var/obj/item/organ/internal/ears/ears = get_organ_slot(ORGAN_SLOT_EARS) switch (severity) if (EXPLODE_DEVASTATE) gib() - return if (EXPLODE_HEAVY) take_overall_damage(60, 60) @@ -121,6 +117,9 @@ In all, this is a lot like the monkey code. /N if(ears) ears.adjustEarDamage(15,60) + return TRUE + + /mob/living/carbon/alien/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15) return 0 diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index cf0d3317f3b..a183dec51c5 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -372,11 +372,11 @@ /mob/living/carbon/human/ex_act(severity, target, origin) if(HAS_TRAIT(src, TRAIT_BOMBIMMUNE)) - return + return FALSE . = ..() - if (!severity || QDELETED(src)) - return + if (!. || !severity || QDELETED(src)) + return FALSE var/brute_loss = 0 var/burn_loss = 0 var/bomb_armor = getarmor(null, BOMB) @@ -398,7 +398,7 @@ SSexplosions.low_mov_atom += thing investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS) gib() - return + return TRUE else brute_loss = 500 var/atom/throw_target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) @@ -454,6 +454,8 @@ if(!max_limb_loss) break + return TRUE + /mob/living/carbon/human/blob_act(obj/structure/blob/B) if(stat == DEAD) diff --git a/code/modules/mob/living/silicon/ai/ai_defense.dm b/code/modules/mob/living/silicon/ai/ai_defense.dm index 2cd8244aa18..34d0f3c0ed4 100644 --- a/code/modules/mob/living/silicon/ai/ai_defense.dm +++ b/code/modules/mob/living/silicon/ai/ai_defense.dm @@ -52,7 +52,7 @@ if (stat != DEAD) adjustBruteLoss(30) - + return TRUE /mob/living/silicon/ai/bullet_act(obj/projectile/Proj) . = ..(Proj) diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 787e128e646..499ad7530e8 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -417,6 +417,8 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real if (stat != DEAD) adjustBruteLoss(30) + return TRUE + /mob/living/silicon/robot/bullet_act(obj/projectile/Proj, def_zone) . = ..() updatehealth() diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 4f2cd0f19ee..2ee3598c79c 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -131,12 +131,10 @@ return TRUE /mob/living/simple_animal/ex_act(severity, target, origin) - if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + . = ..() + if(!. || QDELETED(src)) return FALSE - . = ..() - if(QDELETED(src)) - return switch (severity) if (EXPLODE_DEVASTATE) ex_act_devastate() @@ -145,6 +143,8 @@ if (EXPLODE_LIGHT) ex_act_light() + return TRUE + /// Called when a devastating explosive acts on this mob /mob/living/simple_animal/proc/ex_act_devastate() var/bomb_armor = getarmor(null, BOMB) diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index b52593b22c2..c3bbc842bfb 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -222,6 +222,8 @@ if(EXPLODE_LIGHT) wires.cut_random() + return TRUE + /mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/Proj) . = ..() diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index f23082407a4..c88d21ab55a 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -424,6 +424,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians if(EXPLODE_LIGHT) adjustBruteLoss(30) + return TRUE + /mob/living/simple_animal/hostile/guardian/gib() death(TRUE) diff --git a/code/modules/mob/living/simple_animal/guardian/types/protector.dm b/code/modules/mob/living/simple_animal/guardian/types/protector.dm index 1e7dc6ba54d..584041752a8 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/protector.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/protector.dm @@ -30,6 +30,8 @@ if(toggle) visible_message(span_danger("The explosion glances off [src]'s energy shielding!")) + return TRUE + /mob/living/simple_animal/hostile/guardian/protector/adjustHealth(amount, updating_health = TRUE, forced = FALSE) . = ..() if(. > 0 && toggle) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm index 4ac26d5c61e..dc2971ee3d5 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm @@ -147,6 +147,8 @@ if (EXPLODE_LIGHT) adjustBruteLoss(50) + return TRUE + /// Sets/adds the next time the megafauna can use a melee or ranged attack, in deciseconds. It is a list to allow using named args. Use the ignore_staggered var if youre setting the cooldown to ranged_cooldown_time. /mob/living/simple_animal/hostile/megafauna/proc/update_cooldowns(list/cooldown_updates, ignore_staggered = FALSE) if(!ignore_staggered && has_status_effect(/datum/status_effect/stagger)) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index ad472269f98..4830aa995bc 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -321,6 +321,7 @@ /obj/machinery/anomalous_crystal/ex_act() ActivationReaction(null, ACTIVATE_BOMB) + return TRUE /obj/machinery/anomalous_crystal/honk //Strips and equips you as a clown. I apologize for nothing observer_desc = "This crystal strips and equips its targets as clowns." @@ -628,7 +629,7 @@ return /obj/structure/closet/stasis/ex_act() - return + return FALSE /datum/action/exit_possession name = "Exit Possession" diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 0cea418cc15..6caea3efaad 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -155,6 +155,7 @@ Difficulty: Extremely Hard /mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/ex_act(severity, target) adjustBruteLoss(-30 * severity) visible_message(span_danger("[src] absorbs the explosion!"), span_userdanger("You absorb the explosion!")) + return TRUE /mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Goto(target, delay, minimum_distance) if(enraging) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm index 4d252ebabf1..e5ef6a6559d 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm @@ -77,6 +77,8 @@ if(EXPLODE_LIGHT) adjustBruteLoss(110) + return TRUE + /mob/living/simple_animal/hostile/asteroid/basilisk/AttackingTarget() . = ..() if(lava_drinker && !warmed_up && islava(target)) diff --git a/code/modules/mob/living/simple_animal/hostile/slaughter_demon.dm b/code/modules/mob/living/simple_animal/hostile/slaughter_demon.dm index 2c19e101e36..33702c95d96 100644 --- a/code/modules/mob/living/simple_animal/hostile/slaughter_demon.dm +++ b/code/modules/mob/living/simple_animal/hostile/slaughter_demon.dm @@ -235,6 +235,8 @@ if(EXPLODE_LIGHT) adjustBruteLoss(30) + return TRUE + /mob/living/simple_animal/hostile/imp/slaughter/engine_demon name = "engine demon" faction = list(FACTION_HELL, FACTION_NEUTRAL) diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm index 592ba78c5dc..8cd2bd9632c 100644 --- a/code/modules/pai/defense.dm +++ b/code/modules/pai/defense.dm @@ -39,6 +39,8 @@ fold_in(force = 1) Paralyze(200) + return TRUE + /mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user, list/modifiers) if(!user.combat_mode) visible_message(span_notice("[user] gently pats [src] on the head, eliciting an off-putting buzzing from its holographic field.")) diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 356da439554..d0e6fdb85d7 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -215,7 +215,7 @@ /obj/item/stock_parts/cell/ex_act(severity, target) . = ..() if(QDELETED(src)) - return + return FALSE switch(severity) if(EXPLODE_HEAVY) @@ -225,6 +225,8 @@ if(prob(25)) corrupt() + return TRUE + /obj/item/stock_parts/cell/attack_self(mob/user) if(ishuman(user)) var/mob/living/carbon/human/H = user diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index e8387182fad..3cf4bb74781 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -36,6 +36,9 @@ GLOBAL_LIST_EMPTY(gravity_generators) /obj/machinery/gravity_generator/ex_act(severity, target) if(severity >= EXPLODE_DEVASTATE) // Very sturdy. set_broken() + return TRUE + + return FALSE /obj/machinery/gravity_generator/blob_act(obj/structure/blob/B) if(prob(20)) diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm index 40b71db8598..effdc403fd1 100644 --- a/code/modules/power/rtg.dm +++ b/code/modules/power/rtg.dm @@ -89,6 +89,8 @@ else overload() + return TRUE + /obj/machinery/power/rtg/abductor/fire_act(exposed_temperature, exposed_volume) overload() diff --git a/code/modules/power/singularity/dark_matter_singularity.dm b/code/modules/power/singularity/dark_matter_singularity.dm index 2e81d8d00ee..bd379162b13 100644 --- a/code/modules/power/singularity/dark_matter_singularity.dm +++ b/code/modules/power/singularity/dark_matter_singularity.dm @@ -39,7 +39,7 @@ /obj/singularity/dark_matter/ex_act(severity, target) if(!COOLDOWN_FINISHED(src, initial_explosion_immunity)) - return + return FALSE return ..() /obj/singularity/dark_matter/supermatter_upgrade() diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 97299b8e0d6..56298f8d92c 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -156,6 +156,8 @@ if(EXPLODE_LIGHT) energy -= round(((energy + 1) / 4), 1) + return TRUE + /obj/singularity/process(seconds_per_tick) time_since_act += seconds_per_tick if(time_since_act < 2) diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 71e9177926c..9136b3ccc01 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -254,6 +254,7 @@ /obj/structure/reagent_dispensers/fueltank/ex_act() boom() + return TRUE /obj/structure/reagent_dispensers/fueltank/fire_act(exposed_temperature, exposed_volume) boom() diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 230f0a83fbe..aa67b372226 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -114,6 +114,7 @@ #include "dynamic_ruleset_sanity.dm" #include "egg_glands.dm" #include "emoting.dm" +#include "explosion_action.dm" #include "focus_only_tests.dm" #include "font_awesome_icons.dm" #include "food_edibility_check.dm" diff --git a/code/modules/unit_tests/explosion_action.dm b/code/modules/unit_tests/explosion_action.dm new file mode 100644 index 00000000000..6af3bc466f7 --- /dev/null +++ b/code/modules/unit_tests/explosion_action.dm @@ -0,0 +1,210 @@ +/// Tests the EX_ACT macro on several different types of atoms to ensure that it still works as expected. +/datum/unit_test/explosion_action + /// rolling var for how much brute damage the alien has taken. + var/alien_brute_loss = 0 + /// rolling var for how much burn damage the alien has taken. + var/alien_burn_loss = 0 + /// aliens get a bit of damage done to their ears when exploded, so check that too. + var/alien_ear_damage = 0 + +/datum/unit_test/explosion_action/Run() + // We split up this `Run()` into multiple parts based on the over-arching parent type. This is because all of them have different core implementations of `EX_ACT()`, and we want to test all. + // All procs also have varying levels of bulkiness to them, and it's valuable to have this level of organization because otherwise it would blend all-together and be an entangled mess. + execute_mob_tests() + execute_turf_tests() + execute_obj_tests() + +/// Tests the EX_ACT macro on several different types of mobs to ensure that it still works as expected. +/// Throughout this test, we use the "abstract" type of a `/mob/living` to ensure that the raw framework will still work and remain hardy against any `ex_act()` overrides +/// that may be done on the subtype-to-subtype basis. Any time we use an explicit subtype is to test that framework, so if you update that for some reason, you should also update this test. +/// Like, if you balance aliens to take more ear damage and this test fails, just update the test to reflect that. That's it. +/datum/unit_test/explosion_action/proc/execute_mob_tests() + // You may delete this entire section of the test when the entire `simple_animal` framework needs to be scrapped. + var/mob/living/simple_animal/test_simple_animal = allocate(/mob/living/simple_animal) + test_simple_animal.maxHealth = MAX_LIVING_HEALTH + test_simple_animal.health = MAX_LIVING_HEALTH + + EX_ACT(test_simple_animal, EXPLODE_NONE) // should do nothing. + TEST_ASSERT_EQUAL(test_simple_animal.health, MAX_LIVING_HEALTH, "EX_ACT() with EXPLODE_NONE severity should not affect the health of a simple animal! Something has gone terribly wrong!") + + EX_ACT(test_simple_animal, EXPLODE_LIGHT) // should do 30 damage. + TEST_ASSERT_EQUAL(test_simple_animal.health, MAX_LIVING_HEALTH - 30, "EX_ACT() with EXPLODE_LIGHT severity should have done 30 damage to a simple animal!") + test_simple_animal.revive(ADMIN_HEAL_ALL) + + EX_ACT(test_simple_animal, EXPLODE_HEAVY) // should do 60 damage. + TEST_ASSERT_EQUAL(test_simple_animal.health, MAX_LIVING_HEALTH - 60, "EX_ACT() with EXPLODE_HEAVY severity should have done 60 damage to a simple animal!") + test_simple_animal.revive(ADMIN_HEAL_ALL) + + EX_ACT(test_simple_animal, EXPLODE_DEVASTATE) // this should gib. + TEST_ASSERT(QDELETED(test_simple_animal), "EX_ACT() with EXPLODE_DEVASTATE severity should have gibbed a simple animal!") + // End of the simple-animal checks. No more simple animals beyond this point. + + // Now let's be safe and check basic mobs (they're the future, man) + var/mob/living/basic/test_basic_animal = allocate(/mob/living/basic) + test_basic_animal.maxHealth = MAX_LIVING_HEALTH + test_basic_animal.health = MAX_LIVING_HEALTH + + EX_ACT(test_basic_animal, EXPLODE_NONE) // should do nothing. + TEST_ASSERT_EQUAL(test_basic_animal.health, MAX_LIVING_HEALTH, "EX_ACT() with EXPLODE_NONE severity should not affect the health of a basic animal! Something has gone terribly wrong!") + + EX_ACT(test_basic_animal, EXPLODE_LIGHT) // should do 30 damage. + TEST_ASSERT_EQUAL(test_basic_animal.health, MAX_LIVING_HEALTH - 30, "EX_ACT() with EXPLODE_LIGHT severity should have done 30 damage to a basic animal!") + test_basic_animal.revive(ADMIN_HEAL_ALL) // reset the health to keep things consistent + + EX_ACT(test_basic_animal, EXPLODE_HEAVY) // should do 60 damage. + TEST_ASSERT_EQUAL(test_basic_animal.health, MAX_LIVING_HEALTH - 60, "EX_ACT() with EXPLODE_HEAVY severity should have done 60 damage to a basic animal!") + test_basic_animal.revive(ADMIN_HEAL_ALL) + + EX_ACT(test_basic_animal, EXPLODE_DEVASTATE) // this should gib. + TEST_ASSERT(QDELETED(test_basic_animal), "EX_ACT() with EXPLODE_DEVASTATE severity should have gibbed a basic animal!") + + // Aliens have their own implementation too. + var/mob/living/carbon/alien/test_alien = allocate(/mob/living/carbon/alien) + test_alien.maxHealth = MAX_LIVING_HEALTH + test_alien.health = MAX_LIVING_HEALTH + + EX_ACT(test_alien, EXPLODE_NONE) // should do nothing. + read_alien_damages(test_alien) + TEST_ASSERT_EQUAL(alien_brute_loss, 0, "EX_ACT() with EXPLODE_NONE severity should not affect the brute loss of an alien! Something has gone terribly wrong!") + TEST_ASSERT_EQUAL(alien_burn_loss, 0, "EX_ACT() with EXPLODE_NONE severity should not affect the burn loss of an alien! Something has gone terribly wrong!") + TEST_ASSERT_EQUAL(alien_ear_damage, 0, "EX_ACT() with EXPLODE_NONE severity should not affect the ear damage of an alien! Something has gone terribly wrong!") + + EX_ACT(test_alien, EXPLODE_LIGHT) // should do 30 brute overall and 15 organ damage to the ears. + read_alien_damages(test_alien) + TEST_ASSERT_EQUAL(alien_brute_loss, 30, "EX_ACT() with EXPLODE_LIGHT severity should have done 30 brute damage to an alien!") + TEST_ASSERT_EQUAL(alien_burn_loss, 0, "EX_ACT() with EXPLODE_LIGHT severity should not affect the burn loss of an alien!") + TEST_ASSERT_EQUAL(alien_ear_damage, 15, "EX_ACT() with EXPLODE_LIGHT severity should have done 15 ear damage to an alien!") + test_alien.revive(ADMIN_HEAL_ALL) + + EX_ACT(test_alien, EXPLODE_HEAVY) // should do 60 brute, 60 burn, and 30 organ damage to the ears. + read_alien_damages(test_alien) + TEST_ASSERT_EQUAL(alien_brute_loss, 60, "EX_ACT() with EXPLODE_HEAVY severity should have done 60 brute damage to an alien!") + TEST_ASSERT_EQUAL(alien_burn_loss, 60, "EX_ACT() with EXPLODE_HEAVY severity should have done 60 burn damage to an alien!") + TEST_ASSERT_EQUAL(alien_ear_damage, 30, "EX_ACT() with EXPLODE_HEAVY severity should have done 30 ear damage to an alien!") + + // Let's check to make sure the armor system works as expected. Corgi dogs are the only one that have this implemented on the basic level, so let's use that. + var/mob/living/basic/pet/dog/corgi/test_dog = set_up_test_dog() + + // those two items should give us a 100% armor rating, so let's test that to make sure it works (all ex_act checks should now be prob(100)), no room for error. + EX_ACT(test_dog, EXPLODE_LIGHT) // should do 20 damage (basic animals do a prob() check based on the armor rating, and divide the expected brute loss by 1.5). + TEST_ASSERT_EQUAL(test_dog.health, MAX_LIVING_HEALTH - 20, "EX_ACT() with EXPLODE_LIGHT severity should have done 20 damage to a corgi with an immune helmet and vest!") + test_dog.revive(ADMIN_HEAL_ALL) + + EX_ACT(test_dog, EXPLODE_HEAVY) // should do 40 damage. + TEST_ASSERT_EQUAL(test_dog.health, MAX_LIVING_HEALTH - 40, "EX_ACT() with EXPLODE_HEAVY severity should have done 40 damage to a corgi with an immune helmet and vest!") + test_dog.revive(ADMIN_HEAL_ALL) + + EX_ACT(test_dog, EXPLODE_DEVASTATE) // this should NOT gib, but should do 500 damage. 500 is a lot but we don't really need to test that exact number here to be honest + TEST_ASSERT(!QDELETED(test_dog), "EX_ACT() with EXPLODE_DEVASTATE severity should NOT have gibbed a corgi with an immune helmet and vest!") + TEST_ASSERT_EQUAL(test_dog.stat, DEAD, "EX_ACT() with EXPLODE_DEVASTATE severity should have killed a corgi with an immune helmet and vest!") + + // Humans have a lot of prob() checks and stuff (e.g. delimbing) and it's really complicated, so let's just test the basic stuff here. if you want to test this further should really go into its own unit test. + var/mob/living/carbon/human/test_human = allocate(/mob/living/carbon/human/consistent) + + ADD_TRAIT(test_human, TRAIT_BOMBIMMUNE, REF(src)) + EX_ACT(test_human, EXPLODE_DEVASTATE) // we're bomb immune, so we shouldn't gib. + TEST_ASSERT(!QDELETED(test_human), "EX_ACT() with EXPLODE_DEVASTATE severity should NOT have gibbed a human with the bomb immune trait!") + REMOVE_TRAIT(test_human, TRAIT_BOMBIMMUNE, REF(src)) + + EX_ACT(test_human, EXPLODE_DEVASTATE) // we should gib now. + TEST_ASSERT(QDELETED(test_human), "EX_ACT() with EXPLODE_DEVASTATE severity should have gibbed a human!") + +#define OPEN_FLOOR_TYPE /turf/open/floor +#define CLOSED_FLOOR_TYPE /turf/closed/wall + +/// Tests the `EX_ACT()` macro on turf subtypes to ensure some level of the underlying framework still functions. +/datum/unit_test/explosion_action/proc/execute_turf_tests() + var/turf/open/test_open_turf = run_loc_floor_bottom_left // we'll clean this up later like Create and Destroy dw + var/original_open_turf_type = run_loc_floor_bottom_left.type + var/original_open_baseturfs = islist(run_loc_floor_bottom_left.baseturfs) ? run_loc_floor_bottom_left.baseturfs.Copy() : run_loc_floor_bottom_left.baseturfs + + test_open_turf.ChangeTurf(OPEN_FLOOR_TYPE) + EX_ACT(test_open_turf, EXPLODE_NONE, test_open_turf) // regardless of severity, this should scrape away the floor + TEST_ASSERT_NOTEQUAL(test_open_turf.type, OPEN_FLOOR_TYPE, "EX_ACT() with EXPLODE_NONE severity should have scraped away the floor, but instead saw zero changes!") + test_open_turf.ChangeTurf(original_open_turf_type, original_open_baseturfs) // reset it back to original state before we go again to clear up any potential mess + + test_open_turf.ChangeTurf(OPEN_FLOOR_TYPE) + EX_ACT(test_open_turf, EXPLODE_DEVASTATE) // we should scrape away to space here, devestation severity has no probability of altering what it does. + TEST_ASSERT_NOTEQUAL(test_open_turf.type, OPEN_FLOOR_TYPE, "EX_ACT() with EXPLODE_DEVASTATE severity should have scraped away the floor, but instead saw zero changes!") + test_open_turf.ChangeTurf(original_open_turf_type, original_open_baseturfs) + + // invert the order of ourselves setting up the `/turf/closed` checks because we don't automatically start off as an open turf here + var/original_closed_turf_type = run_loc_floor_top_right.type // should just be /turf/closed/wall but lets be hardy against changes to the map template should they arise + var/original_closed_baseturfs = islist(run_loc_floor_top_right.baseturfs) ? run_loc_floor_top_right.baseturfs.Copy() : run_loc_floor_top_right.baseturfs + var/turf/closed/wall/test_closed_turf = run_loc_floor_top_right.ChangeTurf(CLOSED_FLOOR_TYPE) + + EX_ACT(test_closed_turf, EXPLODE_NONE, test_closed_turf) // regardless of severity, this should dismantle the wall + TEST_ASSERT_NOTEQUAL(test_closed_turf.type, CLOSED_FLOOR_TYPE, "EX_ACT() with EXPLODE_NONE severity (setting itself as the target) should have eviscerated the wall, but instead saw zero changes!") + test_closed_turf.ChangeTurf(original_closed_turf_type, original_closed_baseturfs) + + test_closed_turf.ChangeTurf(CLOSED_FLOOR_TYPE) + var/cached_hardness = test_closed_turf.hardness + test_closed_turf.hardness = 100 // we'll set the hardness to 100 so we don't get errant failures + EX_ACT(test_closed_turf, EXPLODE_LIGHT) + TEST_ASSERT_NOTEQUAL(test_closed_turf.type, CLOSED_FLOOR_TYPE, "EX_ACT() with EXPLODE_LIGHT severity should have dismantled the wall, but instead saw zero changes!") + test_closed_turf.ChangeTurf(original_closed_turf_type, original_closed_baseturfs) + + // just to make sure the hardness is what we wanted it to be, there's no real reason why the hardness should inherit between changeturfs but by God we should guard against it. + test_closed_turf.ChangeTurf(CLOSED_FLOOR_TYPE) + test_closed_turf.hardness = cached_hardness + EX_ACT(test_closed_turf, EXPLODE_HEAVY) // wall will be dismantled at the very least + TEST_ASSERT_NOTEQUAL(test_closed_turf.type, CLOSED_FLOOR_TYPE, "EX_ACT() with EXPLODE_HEAVY severity should have dismantled the wall, but instead saw zero changes!") + test_closed_turf.ChangeTurf(original_closed_turf_type, original_closed_baseturfs) + + test_closed_turf.ChangeTurf(CLOSED_FLOOR_TYPE) + EX_ACT(test_closed_turf, EXPLODE_DEVASTATE) // yeah we're definitely not seeing the wall anymore + TEST_ASSERT_NOTEQUAL(test_closed_turf.type, CLOSED_FLOOR_TYPE, "EX_ACT() with EXPLODE_DEVASTATE severity should have eviscerated the wall, but instead saw zero changes!") + test_closed_turf.ChangeTurf(original_closed_turf_type, original_open_baseturfs) + + // to be super duper sooper safe, clean up both turfs we changed a second time to ensure we aren't yonking downstream tests by invoking them on the actual turfs rather than what we casted + run_loc_floor_bottom_left.ChangeTurf(original_open_turf_type, original_open_baseturfs) + run_loc_floor_top_right.ChangeTurf(original_closed_turf_type, original_closed_baseturfs) + +#undef OPEN_FLOOR_TYPE +#undef CLOSED_FLOOR_TYPE + +/// Tests the `EX_ACT()` macro on objs to ensure some level of the underlying framework still functions. +/datum/unit_test/explosion_action/proc/execute_obj_tests() + // we're using the abstract type here because we don't need anything stronger for this test. + var/obj/test_object = allocate(/obj) + // cached integrity value for use throughout the proc. + var/cached_max_integrity = test_object.max_integrity // Done like this to be hardy in case of changes to integrity framework (anything is possible) - we want to get the intialize-time value, not compile-time. + + test_object.update_integrity(cached_max_integrity) + + EX_ACT(test_object, EXPLODE_NONE) + TEST_ASSERT_EQUAL(test_object.get_integrity(), cached_max_integrity, "EX_ACT() with EXPLODE_NONE severity should not have altered the integrity of the target, but instead saw a change!") + test_object.update_integrity(cached_max_integrity) // just here for cleanliness + + EX_ACT(test_object, EXPLODE_LIGHT) // can do anywhere from 10 to 90 damage, let's just care if it's not equal or not + TEST_ASSERT_NOTEQUAL(test_object.get_integrity(), cached_max_integrity, "EX_ACT() with EXPLODE_LIGHT severity should have altered the integrity of the target, but instead saw no change!") + test_object.update_integrity(cached_max_integrity) + + EX_ACT(test_object, EXPLODE_HEAVY) // can do anywhere from 100 to 250 damage + TEST_ASSERT_NOTEQUAL(test_object.get_integrity(), cached_max_integrity, "EX_ACT() with EXPLODE_HEAVY severity should have altered the integrity of the target, but instead saw no change!") + test_object.update_integrity(cached_max_integrity) + + EX_ACT(test_object, EXPLODE_DEVASTATE) // does an INFINITE amount of damage, will trigger a qdel() + TEST_ASSERT(QDELETED(test_object), "EX_ACT() with EXPLODE_DEVASTATE severity should have deleted the target, but instead saw no change!") + +/// Sets up a fully armored corgi for testing purposes. Split out into its own proc as to not clutter up the main test. +/datum/unit_test/explosion_action/proc/set_up_test_dog() + var/mob/living/basic/pet/dog/corgi/returnable_dog = allocate(/mob/living/basic/pet/dog/corgi) + returnable_dog.maxHealth = MAX_LIVING_HEALTH + returnable_dog.health = MAX_LIVING_HEALTH + + var/obj/item/clothing/head/helmet/invincible_hat = allocate(/obj/item/clothing/head/helmet) + invincible_hat.set_armor(/datum/armor/immune) + returnable_dog.inventory_head = invincible_hat + + var/obj/item/clothing/suit/armor/vest/invincible_vest = allocate(/obj/item/clothing/suit/armor/vest) + invincible_vest.set_armor(/datum/armor/immune) + returnable_dog.inventory_back = invincible_vest + + return returnable_dog + +/// Proc to lessen the amount of copypasta we do for the alien tests, simply sets the rolling vars we have. +/datum/unit_test/explosion_action/proc/read_alien_damages(mob/living/carbon/alien/subject) + alien_brute_loss = subject.getBruteLoss() + alien_burn_loss = subject.getFireLoss() + alien_ear_damage = subject.get_organ_loss(ORGAN_SLOT_EARS)