Kills /obj/shapeshift_holder, replaces it with /datum/status_effect/shapechange_mob, also does a lot of Wabbajack refactoring (#69091)

About The Pull Request

    Deletes /obj/shapeshift_holder, replaces it with /datum/status_effect/shapechange_mob
    Refactors Heretic worm form into a shapeshift spell
    Refactors Wabbajack, and associated code

Fixes #69117
Fixes #65653
Fixes #59127
Fixes #52786
Why It's Good For The Game

/obj/shapeshift_holder was one of the worst remaining abuses of /obj direct subtypes, so I replaced it with a cool fancy datum.

This also decouples the shapeshifting behavior entirely from the shapeshifting spell. So we have support for shapeshifted mobs not sourced from a spell. Which is neat, we could technically swap Wabbajack to use this in the future.
Changelog

cl Melbert
fix: Wabbajacking a shapeshifted mob no longer runtimes horribly. When a shapeshifted mob is wabbajacked, they'll now be removed from their shapeshift and stunned.
fix: Transforming via a shapeshift should no longer rob you of your hearing / runechat awareness.
fix: Shapeshifting plays nicer with holoparasites.
fix: Being polymorphed from a xeno to a non-xeno correctly makes you a non-xeno
refactor: Refactored shapeshifting, the shapeshift holder is now a status effect instead of an object.
refactor: Heretic worm form is a shapeshift spell now, this might have some minor behavioral changes but should overall be the same.
refactor: Refactored Wabbajack (+ cursed pool). Overall a bit more clean / consistent behavior.
/cl
This commit is contained in:
MrMelbert
2022-09-02 09:44:41 +12:00
committed by GitHub
parent c7cf23ae7e
commit de04b3be80
31 changed files with 710 additions and 346 deletions
@@ -19,6 +19,10 @@
/// If TRUE, we cannot mindswap into mobs with minds if they do not currently have a key / player.
var/target_requires_key = TRUE
/// If TRUE, we cannot mindswap into people without a mind.
/// You may be wondering "What's the point of mindswap if the target has no mind"?
/// Primarily for debugging - targets hit with this set to FALSE will init a mind, then do the swap.
var/target_requires_mind = TRUE
/// For how long is the caster stunned for after the spell
var/unconscious_amount_caster = 40 SECONDS
/// For how long is the victim stunned for after the spell
@@ -64,7 +68,7 @@
if(living_target.stat == DEAD)
to_chat(owner, span_warning("You don't particularly want to be dead!"))
return FALSE
if(!living_target.mind)
if(!living_target.mind && target_requires_mind)
to_chat(owner, span_warning("[living_target.p_theyve(TRUE)] doesn't appear to have a mind to swap into!"))
return FALSE
if(!living_target.key && target_requires_key)
@@ -86,6 +90,10 @@
if(stand.summoner)
to_swap = stand.summoner
// Gives the target a mind if we don't require one and they don't have one
if(!to_swap.mind && !target_requires_mind)
to_swap.mind_initialize()
var/datum/mind/mind_to_swap = to_swap.mind
if(to_swap.can_block_magic(antimagic_flags) \
|| mind_to_swap.has_antag_datum(/datum/antagonist/wizard) \
@@ -0,0 +1,236 @@
// A status effect for having a mob temporarily (usually) change form into that of another mob.
// When the status effect is removed, the mob is reverted back to their human form in most cases.
// If you want a more permanent polymorph, see [/proc/wabbajack].
/datum/status_effect/shapechange_mob
id = "shapechange_mob"
alert_type = /atom/movable/screen/alert/status_effect/shapeshifted
// When the mob's deleted on_remove() will handle our references
on_remove_on_mob_delete = TRUE
/// The caster's mob. Who has transformed into us
/// This reference is handled in [/proc/restore_caster], which is always called if we delete
var/mob/living/caster_mob
/// Whether we're currently undoing the change
var/already_restored = FALSE
/datum/status_effect/shapechange_mob/on_creation(mob/living/new_owner, mob/living/caster)
// If any type or subtype of shapeshift mob is on the new_owner already throw an error and self-delete
if(locate(type) in new_owner.status_effects)
stack_trace("Mob shapechange status effect applied to a mob which already was shapechanged, which will definitely cause issues.")
qdel(src)
return
// No caster mob, no one to put in the mob. Self-delete
if(!istype(caster))
stack_trace("Mob shapechange status effect applied without a proper caster.")
qdel(src)
return
src.caster_mob = caster
return ..()
/datum/status_effect/shapechange_mob/on_apply()
caster_mob.mind?.transfer_to(owner)
caster_mob.forceMove(owner)
caster_mob.notransform = TRUE
caster_mob.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT)
RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, .proc/on_wabbajacked)
RegisterSignal(owner, COMSIG_LIVING_DEATH, .proc/on_shape_death)
RegisterSignal(caster_mob, COMSIG_LIVING_DEATH, .proc/on_caster_death)
RegisterSignal(caster_mob, COMSIG_PARENT_QDELETING, .proc/on_caster_deleted)
SEND_SIGNAL(caster_mob, COMSIG_LIVING_SHAPESHIFTED, owner)
return TRUE
/datum/status_effect/shapechange_mob/on_remove()
// If the owner was straight up deleted we shouldn't restore anyone
// (the caster's stored in the owner's contents so if it's qdel'd so is the caster)
if(!QDELETED(owner))
restore_caster()
// Either restore_caster() or other signals should have handled this reference by now,
// but juuust in case make sure nothing sticks around.
caster_mob = null
/// Signal proc for [COMSIG_LIVING_PRE_WABBAJACKED] to prevent us from being Wabbajacked and messed up.
/datum/status_effect/shapechange_mob/proc/on_wabbajacked(mob/living/source, randomized)
SIGNAL_HANDLER
var/mob/living/revealed_mob = caster_mob
source.visible_message(span_warning("[revealed_mob] gets pulled back to their normal form!"))
restore_caster()
revealed_mob.Paralyze(10 SECONDS, ignore_canstun = TRUE)
return STOP_WABBAJACK
/// Restores the caster back to their human form.
/// if kill_caster_after is TRUE, the caster will have death() called on them after restoring.
/datum/status_effect/shapechange_mob/proc/restore_caster(kill_caster_after = FALSE)
if(already_restored || !caster_mob)
return
if(QDELETED(owner))
CRASH("Mob shapechange effect called restore_caster while the owner was qdeleted, this shouldn't happen.")
already_restored = TRUE
UnregisterSignal(owner, list(COMSIG_LIVING_PRE_WABBAJACKED, COMSIG_LIVING_DEATH))
UnregisterSignal(caster_mob, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH))
caster_mob.forceMove(owner.loc)
caster_mob.notransform = FALSE
caster_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT)
owner.mind?.transfer_to(caster_mob)
if(kill_caster_after)
caster_mob.death()
after_unchange()
caster_mob = null
// 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)
/// Effects done after the casting mob has reverted to their human form.
/datum/status_effect/shapechange_mob/proc/after_unchange()
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(owner, COMSIG_LIVING_UNSHAPESHIFTED, caster_mob)
/// Signal proc for [COMSIG_LIVING_DEATH] from our owner.
/// If our owner mob is killed, we should revert back to normal.
/datum/status_effect/shapechange_mob/proc/on_shape_death(datum/source, gibbed)
SIGNAL_HANDLER
// gibbed = deleted = nothing to restore
if(gibbed)
return
restore_caster()
/// Signal proc for [COMSIG_LIVING_DEATH] from our caster.
/// If our internal caster is killed, kill our owner, too (which causes the above signal).
/// This should very rarely end up being called but you never know
/datum/status_effect/shapechange_mob/proc/on_caster_death(datum/source, gibbed)
SIGNAL_HANDLER
// Our caster inside was gibbed, mirror the gib to our mob
if(gibbed)
owner.gib()
// Otherwise our caster died, just make our mob die
else
owner.death()
/// Signal proc for [COMSIG_PARENT_QDELETING] from our caster, delete us / our owner if we get deleted
/datum/status_effect/shapechange_mob/proc/on_caster_deleted(datum/source)
SIGNAL_HANDLER
caster_mob = null
if(QDELETED(owner))
return
qdel(owner)
// A subtype for a shapechange sourced from a shapeshift spell.
/datum/status_effect/shapechange_mob/from_spell
id = "shapechange_from_spell"
/// The shapechange spell that's caused our change
var/datum/weakref/source_weakref
/datum/status_effect/shapechange_mob/from_spell/on_creation(mob/living/new_owner, mob/living/caster, datum/action/cooldown/spell/shapeshift/source_spell)
if(!istype(source_spell))
stack_trace("Mob shapechange \"from spell\" status effect applied without a source spell.")
qdel(src)
return
source_weakref = WEAKREF(source_spell)
return ..()
/datum/status_effect/shapechange_mob/from_spell/on_apply()
var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve()
if(source_spell.owner == caster_mob)
// Assuming the spell is owned by the caster, give it over to the shapeshifted mob
// so they can actually transform back to their original form
source_spell.Grant(owner)
if(source_spell.convert_damage)
var/damage_to_apply = owner.maxHealth * ((caster_mob.maxHealth - caster_mob.health) / caster_mob.maxHealth)
owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, wound_bonus = CANT_WOUND)
owner.blood_volume = caster_mob.blood_volume
for(var/datum/action/bodybound_action as anything in caster_mob.actions)
if(bodybound_action.target != caster_mob)
continue
bodybound_action.Grant(owner)
return ..()
/datum/status_effect/shapechange_mob/from_spell/restore_caster(kill_caster_after)
var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve()
// The owner = owner check here is specifically for edge cases in which the owner of the spell
// is no longer in control of the shapeshifted mob, such as mindswapping out of a shapeshift
if(!QDELETED(source_spell) && source_spell.owner == owner)
source_spell.Grant(caster_mob)
for(var/datum/action/bodybound_action as anything in owner.actions)
if(bodybound_action.target != caster_mob)
continue
bodybound_action.Grant(caster_mob)
return ..()
/datum/status_effect/shapechange_mob/from_spell/after_unchange()
. = ..()
var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref?.resolve()
if(QDELETED(source_spell) || !source_spell.convert_damage)
return
if(caster_mob.stat != DEAD)
caster_mob.revive(full_heal = TRUE, admin_revive = FALSE)
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.blood_volume = owner.blood_volume
/datum/status_effect/shapechange_mob/from_spell/on_shape_death(datum/source, gibbed)
var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve()
// If our spell dictates our wizard dies when our shape dies, we won't restore by default
if(!QDELETED(source_spell) && source_spell.die_with_shapeshifted_form)
// (But if our spell says we should revert on death anyways, we'll also do that)
if(source_spell.revert_on_death)
restore_caster(kill_caster_after = TRUE)
// Otherwise, we just do nothing - we dead
return
return ..() // Restore like normal
/datum/status_effect/shapechange_mob/from_spell/on_caster_death(datum/source)
var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve()
// If our spell does not have revert_on_death, don't do anything when our caster dies
if(!QDELETED(source_spell) && !source_spell.revert_on_death)
return
return ..() // Kill our owner and revert, like normal
/atom/movable/screen/alert/status_effect/shapeshifted
name = "Shapeshifted"
desc = "Your form is not your own... you're shapeshifted into another creature! \
A wizard could turn you back - or maybe you're stuck like this for good?"
icon_state = "shapeshifted"
/atom/movable/screen/alert/status_effect/shapeshifted/Click(location, control, params)
. = ..()
if(!.)
return
var/mob/living/living_user = usr
if(!istype(living_user))
return
// Clicking the action will try to cast whatever spell shifted us in the first place
for(var/datum/action/cooldown/spell/shapeshift/shift_spell in living_user.actions)
if(istype(living_user, shift_spell.shapeshift_type))
shift_spell.Trigger()
return TRUE
@@ -1,9 +1,20 @@
/// Helper for checking of someone's shapeshifted currently.
#define is_shifted(mob) mob.has_status_effect(/datum/status_effect/shapechange_mob/from_spell)
/**
* Shapeshift spells.
*
* Allows the caster to transform to and from a different mob type.
*/
/datum/action/cooldown/spell/shapeshift
button_icon_state = "shapeshift"
school = SCHOOL_TRANSMUTATION
cooldown_time = 10 SECONDS
/// Whehter we revert to our human form on death.
/// Our spell's requrements before we shapeshifted. Stored on shapeshift so we can restore them after unshifting.
var/pre_shift_requirements
/// Whether we revert to our human form on death.
var/revert_on_death = TRUE
/// Whether we die when our shapeshifted form is killed
var/die_with_shapeshifted_form = TRUE
@@ -12,29 +23,35 @@
/// If convert damage is true, the damage type we deal when converting damage back and forth
var/convert_damage_type = BRUTE
/// Our chosen type
/// Our chosen type.
var/mob/living/shapeshift_type
/// All possible types we can become
/// All possible types we can become.
/// This should be implemented even if there is only one choice.
var/list/atom/possible_shapes
/datum/action/cooldown/spell/shapeshift/is_valid_target(atom/cast_on)
return isliving(cast_on)
/datum/action/cooldown/spell/shapeshift/proc/is_shifted(mob/living/cast_on)
return locate(/obj/shapeshift_holder) in cast_on
/datum/action/cooldown/spell/shapeshift/before_cast(atom/cast_on)
/datum/action/cooldown/spell/shapeshift/before_cast(mob/living/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
if(shapeshift_type)
// If another shapeshift spell was casted while we're already shifted, they could technically go to do_unshapeshift().
// However, we don't really want people casting shapeshift A to un-shapeshift from shapeshift B,
// as it could cause bugs or unintended behavior. So we'll just stop them here.
if(is_shifted(cast_on) && !is_type_in_list(cast_on, possible_shapes))
to_chat(cast_on, span_warning("This spell won't un-shapeshift you from this form!"))
return . | SPELL_CANCEL_CAST
return
if(length(possible_shapes) == 1)
shapeshift_type = possible_shapes[1]
return
// Not bothering with caching these as they're only ever shown once
var/list/shape_names_to_types = list()
var/list/shape_names_to_image = list()
if(!length(shape_names_to_types) || !length(shape_names_to_image))
@@ -67,24 +84,25 @@
cast_on.buckled?.unbuckle_mob(cast_on, force = TRUE)
var/currently_ventcrawling = (cast_on.movement_type & VENTCRAWLING)
var/mob/living/resulting_mob
// Do the shift back or forth
if(is_shifted(cast_on))
restore_form(cast_on)
resulting_mob = do_unshapeshift(cast_on)
else
do_shapeshift(cast_on)
resulting_mob = do_shapeshift(cast_on)
// The shift is done, let's make sure they're in a valid state now
// If we're not ventcrawling, we don't need to mind
if(!currently_ventcrawling)
if(!currently_ventcrawling || !resulting_mob)
return
// We are ventcrawling - can our new form support ventcrawling?
if(HAS_TRAIT(cast_on, TRAIT_VENTCRAWLER_ALWAYS) || HAS_TRAIT(cast_on, TRAIT_VENTCRAWLER_NUDE))
if(HAS_TRAIT(resulting_mob, TRAIT_VENTCRAWLER_ALWAYS) || HAS_TRAIT(resulting_mob, TRAIT_VENTCRAWLER_NUDE))
return
// Uh oh. You've shapeshifted into something that can't fit into a vent, while ventcrawling.
eject_from_vents(cast_on)
eject_from_vents(resulting_mob)
/// Whenever someone shapeshifts within a vent,
/// and enters a state in which they are no longer a ventcrawler,
@@ -107,9 +125,11 @@
playsound(possible_vent, 'sound/effects/reee.ogg', 75, TRUE)
priority_announce("We detected a pipe blockage around [get_area(get_turf(cast_on))], please dispatch someone to investigate.", "Central Command")
cast_on.death()
qdel(cast_on)
// Gib our caster, and make sure to leave nothing behind
// (If we leave something behind, it'll drop on the turf of the pipe, which is kinda wrong.)
cast_on.gib(TRUE, TRUE, TRUE)
/// Callback for the radial that allows the user to choose their species.
/datum/action/cooldown/spell/shapeshift/proc/check_menu(mob/living/caster)
if(QDELETED(src))
return FALSE
@@ -118,153 +138,43 @@
return !caster.incapacitated()
/// Actually does the shapeshift, for the caster.
/datum/action/cooldown/spell/shapeshift/proc/do_shapeshift(mob/living/caster)
if(is_shifted(caster))
to_chat(caster, span_warning("You're already shapeshifted!"))
CRASH("[type] called do_shapeshift while shapeshifted.")
var/mob/living/new_shape = new shapeshift_type(caster.loc)
var/obj/shapeshift_holder/new_shape_holder = new(new_shape, src, caster)
spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB)
return new_shape_holder
/datum/action/cooldown/spell/shapeshift/proc/restore_form(mob/living/caster)
var/obj/shapeshift_holder/current_shift = is_shifted(caster)
if(QDELETED(current_shift))
var/mob/living/new_shape = create_shapeshift_mob(caster.loc)
var/datum/status_effect/shapechange_mob/shapechange = new_shape.apply_status_effect(/datum/status_effect/shapechange_mob/from_spell, caster, src)
if(!shapechange)
// We failed to shift, maybe because we were already shapeshifted?
// Whatver the case, this shouldn't happen, so throw a stack trace.
to_chat(caster, span_warning("You can't shapeshift in this form!"))
stack_trace("[type] do_shapeshift was called when the mob was already shapeshifted (from a spell).")
return
var/mob/living/restored_player = current_shift.stored
// Make sure it's castable even in their new form.
pre_shift_requirements = spell_requirements
spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB)
current_shift.restore()
spell_requirements = initial(spell_requirements) // Miiight mess with admin stuff.
return new_shape
return restored_player
/// Actually does the un-shapeshift, from the caster. (Caster is a shapeshifted mob.)
/datum/action/cooldown/spell/shapeshift/proc/do_unshapeshift(mob/living/caster)
var/datum/status_effect/shapechange_mob/shapechange = caster.has_status_effect(/datum/status_effect/shapechange_mob/from_spell)
if(!shapechange)
// We made it to do_unshapeshift without having a shapeshift status effect, this shouldn't happen.
to_chat(caster, span_warning("You can't un-shapeshift from this form!"))
stack_trace("[type] do_unshapeshift was called when the mob wasn't even shapeshifted (from a spell).")
return
// Maybe one day, this can be a component or something
// Until then, this is what holds data between wizard and shapeshift form whenever shapeshift is cast.
/obj/shapeshift_holder
name = "Shapeshift holder"
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
var/mob/living/stored
var/mob/living/shape
var/restoring = FALSE
var/datum/action/cooldown/spell/shapeshift/source
// Restore the requirements.
spell_requirements = pre_shift_requirements
pre_shift_requirements = null
/obj/shapeshift_holder/Initialize(mapload, datum/action/cooldown/spell/shapeshift/_source, mob/living/caster)
. = ..()
source = _source
shape = loc
if(!istype(shape))
stack_trace("shapeshift holder created outside mob/living")
return INITIALIZE_HINT_QDEL
stored = caster
var/mob/living/unshapeshifted_mob = shapechange.caster_mob
caster.remove_status_effect(/datum/status_effect/shapechange_mob/from_spell)
return unshapeshifted_mob
// Transfer the Shapeshift spell over, if we actually own it
if(source.owner == caster)
source.Grant(shape)
/// Helper proc that instantiates the mob we shapeshift into.
/// Returns an instance of a living mob. Can be overridden.
/datum/action/cooldown/spell/shapeshift/proc/create_shapeshift_mob(atom/loc)
return new shapeshift_type(loc)
// Also transfer over any actions bound to them specifically - this leaves behind item actions and similar
// (Mindbound actions are automatically tranferred over, so we don't need to worry about it)
for(var/datum/action/bodybound_action as anything in caster.actions)
if(bodybound_action.target != caster)
continue
bodybound_action.Grant(shape)
if(stored.mind)
stored.mind.transfer_to(shape)
stored.forceMove(src)
stored.notransform = TRUE
if(source.convert_damage)
var/damage_percent = (stored.maxHealth - stored.health) / stored.maxHealth;
var/damapply = damage_percent * shape.maxHealth;
shape.apply_damage(damapply, source.convert_damage_type, forced = TRUE, wound_bonus = CANT_WOUND);
shape.blood_volume = stored.blood_volume;
RegisterSignal(shape, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), .proc/shape_death)
RegisterSignal(stored, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), .proc/caster_death)
/obj/shapeshift_holder/Destroy()
// restore_form manages signal unregistering. If restoring is TRUE, we've already unregistered the signals and we're here
// because restore() qdel'd src.
if(!restoring)
restore()
stored = null
shape = null
return ..()
/obj/shapeshift_holder/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(!restoring && !QDELETED(src))
restore()
/obj/shapeshift_holder/handle_atom_del(atom/A)
if(A == stored && !restoring)
restore()
/obj/shapeshift_holder/Exited(atom/movable/gone, direction)
if(stored == gone && !restoring)
restore()
/obj/shapeshift_holder/proc/caster_death()
SIGNAL_HANDLER
//Something kills the stored caster through direct damage.
if(source.revert_on_death)
restore(death = TRUE)
else
shape.death()
/obj/shapeshift_holder/proc/shape_death()
SIGNAL_HANDLER
//Shape dies.
if(source.die_with_shapeshifted_form)
if(source.revert_on_death)
restore(death = TRUE)
else
restore()
/obj/shapeshift_holder/proc/restore(death=FALSE)
// Destroy() calls this proc if it hasn't been called. Unregistering here prevents multiple qdel loops
// when caster and shape both die at the same time.
UnregisterSignal(shape, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH))
UnregisterSignal(stored, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH))
restoring = TRUE
// Give Shapeshift back to the OG, if we actually own it
// (Shapeshift into mindswap may change the action's owner)
if(!QDELETED(source) && source.owner == shape)
source.Grant(stored)
// Also transfer their bodybound actions back
for(var/datum/action/bodybound_action as anything in shape.actions)
if(bodybound_action.target != stored)
continue
bodybound_action.Grant(stored)
stored.forceMove(shape.loc)
stored.notransform = FALSE
if(shape.mind)
shape.mind.transfer_to(stored)
if(death)
stored.death()
else if(source.convert_damage)
stored.revive(full_heal = TRUE, admin_revive = FALSE)
var/damage_percent = (shape.maxHealth - shape.health)/shape.maxHealth;
var/damapply = stored.maxHealth * damage_percent
stored.apply_damage(damapply, source.convert_damage_type, forced = TRUE, wound_bonus=CANT_WOUND)
if(source.convert_damage)
stored.blood_volume = shape.blood_volume;
// This guard is important because restore() can also be called on COMSIG_PARENT_QDELETING for shape, as well as on death.
// This can happen in, for example, [/proc/wabbajack] where the mob hit is qdel'd.
if(!QDELETED(shape))
QDEL_NULL(shape)
qdel(src)
return stored
#undef is_shifted
@@ -3,6 +3,7 @@
name = "Dragon Form"
desc = "Take on the shape a lesser ash drake."
invocation = "RAAAAAAAAWR!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
possible_shapes = list(/mob/living/simple_animal/hostile/megafauna/dragon/lesser)
@@ -2,6 +2,7 @@
name = "Polar Bear Form"
desc = "Take on the shape of a polar bear."
invocation = "RAAAAAAAAWR!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
possible_shapes = list(/mob/living/simple_animal/hostile/asteroid/polarbear/lesser)