From 9650cf5eef2499a755f97e5408ef2e253da69a58 Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+AnArgonianLizardThatStealsPRs@users.noreply.github.com> Date: Fri, 27 Oct 2023 10:25:02 +0300 Subject: [PATCH] [MIRROR] Shapechange health transfer tweaks [MDB IGNORE] (#24590) (#298) * Shapechange health transfer tweaks * Fix diffs --------- Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Jacquerel Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com> --- code/datums/actions/action.dm | 59 +++++++++--------- code/datums/components/leash.dm | 8 +++ code/datums/elements/weather_listener.dm | 2 +- .../heretic/magic/ascended_shapeshift.dm | 1 + .../heretic/magic/flesh_ascension.dm | 1 + .../mob/living/carbon/human/_species.dm | 16 ++--- code/modules/mob/living/damage_procs.dm | 4 ++ .../living/simple_animal/guardian/guardian.dm | 19 +++--- .../simple_animal/hostile/megafauna/drake.dm | 2 +- code/modules/mob/mob_helpers.dm | 1 - .../spell_types/shapeshift/_shape_status.dm | 27 +++++---- code/modules/surgery/bodyparts/_bodyparts.dm | 2 +- code/modules/unit_tests/spell_shapeshift.dm | 60 ++++++++++++++++--- 13 files changed, 135 insertions(+), 67 deletions(-) diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm index d81c72cc920..69f8fd42dcf 100644 --- a/code/datums/actions/action.dm +++ b/code/datums/actions/action.dm @@ -79,16 +79,17 @@ /// Grants the action to the passed mob, making it the owner /datum/action/proc/Grant(mob/grant_to) - if(!grant_to) + if(isnull(grant_to)) Remove(owner) return - if(owner) - if(owner == grant_to) - return - Remove(owner) - SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, grant_to) - SEND_SIGNAL(grant_to, COMSIG_MOB_GRANTED_ACTION, src) + if(grant_to == owner) + return // We already have it + var/mob/previous_owner = owner owner = grant_to + if(!isnull(previous_owner)) + Remove(previous_owner) + SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, owner) + SEND_SIGNAL(owner, COMSIG_MOB_GRANTED_ACTION, src) RegisterSignal(owner, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE) // Register some signals based on our check_flags @@ -120,27 +121,29 @@ LAZYREMOVE(remove_from?.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared viewers = list() - if(owner) - SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner) - SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src) - UnregisterSignal(owner, COMSIG_QDELETING) - - // Clean up our check_flag signals - UnregisterSignal(owner, list( - COMSIG_LIVING_SET_BODY_POSITION, - COMSIG_MOB_STATCHANGE, - SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), - SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), - SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), - SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_PHASED), - SIGNAL_REMOVETRAIT(TRAIT_HANDS_BLOCKED), - SIGNAL_REMOVETRAIT(TRAIT_IMMOBILIZED), - SIGNAL_REMOVETRAIT(TRAIT_INCAPACITATED), - SIGNAL_REMOVETRAIT(TRAIT_MAGICALLY_PHASED), - )) - - if(target == owner) - RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref)) + if(isnull(owner)) + return + SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner) + SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src) + UnregisterSignal(owner, COMSIG_QDELETING) + + // Clean up our check_flag signals + UnregisterSignal(owner, list( + COMSIG_LIVING_SET_BODY_POSITION, + COMSIG_MOB_STATCHANGE, + SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), + SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), + SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), + SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_PHASED), + SIGNAL_REMOVETRAIT(TRAIT_HANDS_BLOCKED), + SIGNAL_REMOVETRAIT(TRAIT_IMMOBILIZED), + SIGNAL_REMOVETRAIT(TRAIT_INCAPACITATED), + SIGNAL_REMOVETRAIT(TRAIT_MAGICALLY_PHASED), + )) + + if(target == owner) + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref)) + if (owner == remove_from) owner = null /// Actually triggers the effects of the action. diff --git a/code/datums/components/leash.dm b/code/datums/components/leash.dm index d04a482dbf6..ef0b278c799 100644 --- a/code/datums/components/leash.dm +++ b/code/datums/components/leash.dm @@ -109,6 +109,14 @@ if (get_dist(parent, owner) <= distance) return + var/atom/movable/atom_parent = parent + if (isnull(owner.loc)) + atom_parent.moveToNullspace() // If our parent is in nullspace I guess we gotta go there too + return + if (isnull(atom_parent.loc)) + force_teleport_back("in nullspace") // If we're in nullspace, get outta there + return + SEND_SIGNAL(parent, COMSIG_LEASH_PATH_STARTED) current_path_tick += 1 diff --git a/code/datums/elements/weather_listener.dm b/code/datums/elements/weather_listener.dm index 61778e2a186..0028f57ff3d 100644 --- a/code/datums/elements/weather_listener.dm +++ b/code/datums/elements/weather_listener.dm @@ -35,7 +35,7 @@ /datum/element/weather_listener/proc/handle_z_level_change(datum/source, turf/old_loc, turf/new_loc) SIGNAL_HANDLER var/list/fitting_z_levels = SSmapping.levels_by_trait(weather_trait) - if(!(new_loc.z in fitting_z_levels)) + if(!(new_loc?.z in fitting_z_levels)) return var/datum/component/our_comp = source.AddComponent(\ /datum/component/area_sound_manager, \ diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm index f1d6de56e39..e11053fd3e3 100644 --- a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm @@ -4,6 +4,7 @@ desc = "A spell that allows you to take on the form of another eldritch creature, gaining their abilities. \ You can change your choice at any time, and if your form dies, you dont die." cooldown_time = 20 SECONDS + convert_damage = FALSE die_with_shapeshifted_form = FALSE possible_shapes = list( /mob/living/basic/heretic_summon/ash_spirit, diff --git a/code/modules/antagonists/heretic/magic/flesh_ascension.dm b/code/modules/antagonists/heretic/magic/flesh_ascension.dm index d086c1127fc..a2d792080e0 100644 --- a/code/modules/antagonists/heretic/magic/flesh_ascension.dm +++ b/code/modules/antagonists/heretic/magic/flesh_ascension.dm @@ -13,6 +13,7 @@ invocation_type = INVOCATION_SHOUT spell_requirements = NONE + convert_damage = FALSE // Functionally meaningless on Armsy, we track how many segments it had instead possible_shapes = list(/mob/living/basic/heretic_summon/armsy) /// The length of our new wormy when we shed. diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 2ba24884ded..d92dde594dd 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1422,32 +1422,32 @@ GLOBAL_LIST_EMPTY(features_by_species) H.damageoverlaytemp = 20 var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.brute_mod if(BP) - if(BP.receive_damage(damage_amount, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction, damage_source = attacking_item)) + if(BP.receive_damage(damage_amount, 0, forced = forced, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction, damage_source = attacking_item)) H.update_damage_overlays() else//no bodypart, we deal damage with a more general method. - H.adjustBruteLoss(damage_amount) + H.adjustBruteLoss(damage_amount, forced = forced) INVOKE_ASYNC(H, TYPE_PROC_REF(/mob/living/carbon/human, adjust_pain), damage_amount) // SKYRAT EDIT ADDITION - ERP Pain if(BURN) H.damageoverlaytemp = 20 var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.burn_mod if(BP) - if(BP.receive_damage(0, damage_amount, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction, damage_source = attacking_item)) + if(BP.receive_damage(0, damage_amount, forced = forced, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction, damage_source = attacking_item)) H.update_damage_overlays() else - H.adjustFireLoss(damage_amount) + H.adjustFireLoss(damage_amount, forced = forced) INVOKE_ASYNC(H, TYPE_PROC_REF(/mob/living/carbon/human, adjust_pain), damage_amount) // SKYRAT EDIT ADDITION - ERP Pain if(TOX) var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.tox_mod - H.adjustToxLoss(damage_amount) + H.adjustToxLoss(damage_amount, forced = forced) if(OXY) var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.oxy_mod - H.adjustOxyLoss(damage_amount) + H.adjustOxyLoss(damage_amount, forced = forced) if(CLONE) var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.clone_mod - H.adjustCloneLoss(damage_amount) + H.adjustCloneLoss(damage_amount, forced = forced) if(STAMINA) var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.stamina_mod - H.adjustStaminaLoss(damage_amount) + H.adjustStaminaLoss(damage_amount, forced = forced) if(BRAIN) var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.brain_mod H.adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_amount) diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index a58d92ab7e9..c92262733c1 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -68,6 +68,10 @@ if(STAMINA) return getStaminaLoss() +/// return the total damage of all types which update your health +/mob/living/proc/get_total_damage(precision = DAMAGE_PRECISION) + return round(getBruteLoss() + getFireLoss() + getToxLoss() + getOxyLoss() + getCloneLoss(), precision) + /// applies multiple damages at once via [/mob/living/proc/apply_damage] /mob/living/proc/apply_damages(brute = 0, burn = 0, tox = 0, oxy = 0, clone = 0, def_zone = null, blocked = FALSE, stamina = 0, brain = 0) if(blocked >= 100) diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index 4ca35a67357..21286787755 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -138,7 +138,9 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians /mob/living/simple_animal/hostile/guardian/proc/cut_summoner(different_person = FALSE) if(is_deployed()) recall_effects() - forceMove(get_turf(src)) + var/summoner_turf = get_turf(src) + if (!isnull(summoner_turf)) + forceMove(summoner_turf) UnregisterSignal(summoner, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_LIVING_DEATH, COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED)) if(different_person) summoner.faction -= "[REF(src)]" @@ -311,7 +313,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians SIGNAL_HANDLER cut_summoner() - forceMove(source.loc) + if (!isnull(source.loc)) + forceMove(source.loc) to_chat(src, span_danger("Your summoner has died!")) visible_message(span_bolddanger("\The [src] dies along with its user!")) source.visible_message(span_bolddanger("[source]'s body is completely consumed by the strain of sustaining [src]!")) @@ -346,12 +349,12 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians return to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) visible_message(span_danger("\The [src] jumps back to its user.")) - if(istype(summoner.loc, /obj/effect)) + new /obj/effect/temp_visual/guardian/phase/out(loc) + if(istype(summoner.loc, /obj/effect) || isnull(summoner.loc)) recall(forced = TRUE) - else - new /obj/effect/temp_visual/guardian/phase/out(loc) - forceMove(summoner.loc) - new /obj/effect/temp_visual/guardian/phase(loc) + return + forceMove(summoner.loc) + new /obj/effect/temp_visual/guardian/phase(loc) /mob/living/simple_animal/hostile/guardian/can_suicide() return FALSE @@ -469,7 +472,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians //MANIFEST, RECALL, TOGGLE MODE/LIGHT, SHOW TYPE /mob/living/simple_animal/hostile/guardian/proc/manifest(forced) - if(is_deployed() || istype(summoner.loc, /obj/effect) || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) + if(is_deployed() || isnull(summoner.loc) || istype(summoner.loc, /obj/effect) || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) return FALSE forceMove(summoner.loc) new /obj/effect/temp_visual/guardian/phase(loc) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index 1aadefd4153..5008d4b1748 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -322,7 +322,7 @@ /mob/living/simple_animal/hostile/megafauna/dragon/lesser/adjustHealth(amount, updating_health = TRUE, forced = FALSE) . = ..() - lava_swoop.enraged = FALSE + lava_swoop?.enraged = FALSE // In case taking damage caused us to start deleting ourselves /mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype) return diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index cb041a6ab35..5f52e79aaab 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -84,7 +84,6 @@ CRASH("limbs is empty and the chest is blacklisted. this may not be intended!") return (((chest_blacklisted && !base_zone) || even_weights) ? pick_weight(limbs) : ran_zone(base_zone, base_probability, limbs)) - ///Would this zone be above the neck /proc/above_neck(zone) var/list/zones = list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES) diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm index 94e1d549af2..ba8269108a8 100644 --- a/code/modules/spells/spell_types/shapeshift/_shape_status.dm +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -76,20 +76,27 @@ UnregisterSignal(owner, list(COMSIG_LIVING_PRE_WABBAJACKED, COMSIG_LIVING_DEATH)) UnregisterSignal(caster_mob, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) - caster_mob.forceMove(owner.loc) REMOVE_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, REF(src)) caster_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT) - owner.mind?.transfer_to(caster_mob) + + var/atom/former_loc = owner.loc + owner.moveToNullspace() + caster_mob.forceMove(former_loc) // This is to avoid crushing our former cockroach body if(kill_caster_after) caster_mob.death() after_unchange() - caster_mob = null + + // We're about to remove the status effect and clear owner so we need to cache this + var/mob/living/former_body = owner + + // Do this late as it will destroy the status effect we are in and null a bunch of values we are trying to use + owner.mind?.transfer_to(caster_mob) // Destroy the owner after all's said and done, this will also destroy our status effect (src) // retore_caster() should never reach this point while either the owner or the effect is being qdeleted - qdel(owner) + qdel(former_body) /// Effects done after the casting mob has reverted to their human form. /datum/status_effect/shapechange_mob/proc/after_unchange() @@ -154,9 +161,9 @@ source_spell.Grant(owner) if(source_spell.convert_damage) - var/damage_to_apply = owner.maxHealth * ((caster_mob.maxHealth - caster_mob.health) / caster_mob.maxHealth) + var/damage_to_apply = owner.maxHealth * (caster_mob.get_total_damage() / caster_mob.maxHealth) - owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, wound_bonus = CANT_WOUND) + owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE, wound_bonus = CANT_WOUND) owner.blood_volume = caster_mob.blood_volume for(var/datum/action/bodybound_action as anything in caster_mob.actions) @@ -186,11 +193,9 @@ if(QDELETED(source_spell) || !source_spell.convert_damage) return - if(caster_mob.stat != DEAD) - caster_mob.revive(HEAL_DAMAGE) - - var/damage_to_apply = caster_mob.maxHealth * ((owner.maxHealth - owner.health) / owner.maxHealth) - caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, wound_bonus = CANT_WOUND) + caster_mob.fully_heal(HEAL_DAMAGE) // Remove all of our damage before setting our health to a proportion of the former transformed mob's health + var/damage_to_apply = caster_mob.maxHealth * (owner.get_total_damage() / owner.maxHealth) + caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE, wound_bonus = CANT_WOUND) caster_mob.blood_volume = owner.blood_volume diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 407ac67a43a..a8f35aaa968 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -457,7 +457,7 @@ /obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, blocked = 0, updating_health = TRUE, forced = FALSE, required_bodytype = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, damage_source) SHOULD_CALL_PARENT(TRUE) - var/hit_percent = (100-blocked)/100 + var/hit_percent = forced ? 1 : (100-blocked)/100 if((!brute && !burn) || hit_percent <= 0) return FALSE if (!forced) diff --git a/code/modules/unit_tests/spell_shapeshift.dm b/code/modules/unit_tests/spell_shapeshift.dm index 4e2ce659003..9b5804e3520 100644 --- a/code/modules/unit_tests/spell_shapeshift.dm +++ b/code/modules/unit_tests/spell_shapeshift.dm @@ -18,6 +18,8 @@ qdel(shift) +#define TRIGGER_RESET_COOLDOWN(spell) spell.next_use_time = 0; spell.Trigger(); + /** * Validates that shapeshift spells put the mob in another mob, as they should. */ @@ -25,7 +27,7 @@ /datum/unit_test/shapeshift_spell/Run() - var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent, run_loc_floor_bottom_left) dummy.mind_initialize() for(var/spell_type in subtypesof(/datum/action/cooldown/spell/shapeshift)) @@ -57,8 +59,7 @@ if(forced_shape) shift.shapeshift_type = forced_shape - shift.next_use_time = 0 - shift.Trigger() + TRIGGER_RESET_COOLDOWN(shift) var/mob/expected_shape = shift.shapeshift_type if(!istype(dummy.loc, expected_shape)) return TEST_FAIL("Shapeshift spell: [shift.name] failed to transform the dummy into the shape [initial(expected_shape.name)]. \ @@ -68,8 +69,7 @@ if(!(shift in shape.actions)) return TEST_FAIL("Shapeshift spell: [shift.name] failed to grant the spell to the dummy's shape.") - shift.next_use_time = 0 - shift.Trigger() + TRIGGER_RESET_COOLDOWN(shift) if(istype(dummy.loc, shift.shapeshift_type)) return TEST_FAIL("Shapeshift spell: [shift.name] failed to transform the dummy back into a human.") @@ -81,7 +81,7 @@ /datum/unit_test/shapeshift_holoparasites/Run() - var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent, run_loc_floor_bottom_left) var/datum/action/cooldown/spell/shapeshift/wizard/shift = new(dummy) shift.shapeshift_type = shift.possible_shapes[1] @@ -99,9 +99,53 @@ TEST_ASSERT_EQUAL(test_stand.summoner, dummy.loc, "Shapeshift spell failed to transfer the holoparasite to the dummy's shape.") // Dummy casts shapeshfit back, the stand's summoner should become the dummy again. - shift.next_use_time = 0 - shift.Trigger() + TRIGGER_RESET_COOLDOWN(shift) TEST_ASSERT(!istype(dummy.loc, shift.shapeshift_type), "Shapeshift spell failed to transform the dummy back into human form.") TEST_ASSERT_EQUAL(test_stand.summoner, dummy, "Shapeshift spell failed to transfer the holoparasite back to the dummy's human form.") qdel(shift) + +#define EXPECTED_HEALTH_RATIO 0.5 + +/// Validates that shapeshifting carries health or death between forms properly, if it is supposed to +/datum/unit_test/shapeshift_health + +/datum/unit_test/shapeshift_health/Run() + for(var/spell_type in subtypesof(/datum/action/cooldown/spell/shapeshift)) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent, run_loc_floor_bottom_left) + var/datum/action/cooldown/spell/shapeshift/shift_spell = new spell_type(dummy) + shift_spell.Grant(dummy) + shift_spell.shapeshift_type = shift_spell.possible_shapes[1] + + if (istype(shift_spell, /datum/action/cooldown/spell/shapeshift/polymorph_belt)) + var/datum/action/cooldown/spell/shapeshift/polymorph_belt/belt_spell = shift_spell + belt_spell.channel_time = 0 SECONDS // No do-afters + + if (shift_spell.convert_damage) + shift_spell.Trigger() + TEST_ASSERT(istype(dummy.loc, shift_spell.shapeshift_type), "Failed to transform into [shift_spell.shapeshift_type]using [shift_spell.name].") + var/mob/living/shifted_mob = dummy.loc + shifted_mob.apply_damage(shifted_mob.maxHealth * EXPECTED_HEALTH_RATIO, BRUTE, forced = TRUE) + TRIGGER_RESET_COOLDOWN(shift_spell) + TEST_ASSERT(!istype(dummy.loc, shift_spell.shapeshift_type), "Failed to unfransform from [shift_spell.shapeshift_type] using [shift_spell.name].") + TEST_ASSERT_EQUAL(dummy.get_total_damage(), dummy.maxHealth * EXPECTED_HEALTH_RATIO, "Failed to transfer damage from [shift_spell.shapeshift_type] to original form using [shift_spell.name].") + TRIGGER_RESET_COOLDOWN(shift_spell) + TEST_ASSERT(istype(dummy.loc, shift_spell.shapeshift_type), "Failed to transform into [shift_spell.shapeshift_type] after taking damage using [shift_spell.name].") + shifted_mob = dummy.loc + TEST_ASSERT_EQUAL(shifted_mob.get_total_damage(), shifted_mob.maxHealth * EXPECTED_HEALTH_RATIO, "Failed to transfer damage from original form to [shift_spell.shapeshift_type] using [shift_spell.name].") + TRIGGER_RESET_COOLDOWN(shift_spell) + + if (shift_spell.die_with_shapeshifted_form) + TRIGGER_RESET_COOLDOWN(shift_spell) + TEST_ASSERT(istype(dummy.loc, shift_spell.shapeshift_type), "Failed to transform into [shift_spell.shapeshift_type]") + var/mob/living/shifted_mob = dummy.loc + shifted_mob.health = 0 // Fucking megafauna + shifted_mob.death() + if (shift_spell.revert_on_death) + TEST_ASSERT(!istype(dummy.loc, shift_spell.shapeshift_type), "Failed to untransform after death using [shift_spell.name].") + TEST_ASSERT_EQUAL(dummy.stat, DEAD, "Failed to kill original mob when transformed mob died using [shift_spell.name].") + + qdel(shift_spell) + +#undef EXPECTED_HEALTH_RATIO +#undef TRIGGER_RESET_COOLDOWN