From 190904b5bbb047a9e941195c7a53be807cfc8123 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Thu, 8 Jan 2026 04:00:41 +0000 Subject: [PATCH] Polymorph belt plays nicely with mob evolution (#94723) ## About The Pull Request Fixes #82319 Fixes #94129 If your mob type changes while you are transformed via the polymorph belt, it will transfer the polymorph status to the new mob. This means that turning into a Bileworm after Bileworms have evolved will no longer ghost you, and instead you will just keep playing as a vileworm. Additionally it means if you copy a chick, spiderling, juvenile lobster, and walk around as one of those for long enough to grow up, it will seamlessly move you over. Or if you turn into a mouse and a nearby regal rat uses their riot ability it will transform you into a rat, etc. If you change back into a human and then change back into your mob you will return to the original state though. I decided to put this on a new subtype of the status effect rather than the base type because I think it's more of a programming issue if this happens for an ability with a fixed transform type (and we wouldn't want to mutate the possible shapes list in that case), so in those cases we should keep the existing behaviour of just untransforming you. Additionally, I made the evolutionary leap component always wait a minimum of three seconds instead of being instant before transforming a mob. This means that other things have time to register signals and react to the transformation, so you should never be deleted by bileworm evolution any more. ## Why It's Good For The Game This fixes a few long-standing bugs and seems more fun than untransforming you. ## Changelog :cl: fix: If you use the polymorph belt to turn into a mob which transforms into another mob, it won't ghost you or kick you out upon transformation. /:cl: --- code/datums/components/evolutionary_leap.dm | 9 ++++----- code/modules/clothing/belts/polymorph_belt.dm | 20 +++++++++++++++++++ .../spell_types/shapeshift/_shapeshift.dm | 9 ++++++--- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/code/datums/components/evolutionary_leap.dm b/code/datums/components/evolutionary_leap.dm index 3a47dbbdcf0..c9176f4b39f 100644 --- a/code/datums/components/evolutionary_leap.dm +++ b/code/datums/components/evolutionary_leap.dm @@ -23,9 +23,9 @@ RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(comp_on_round_start)) return - //if the round has already taken long enough, just leap right away. + //if the round has already taken long enough, evolve in three seconds. if((world.time - SSticker.round_start_time) > evolve_mark) - leap(silent = TRUE) + addtimer(CALLBACK(src, PROC_REF(leap)), 3 SECONDS, TIMER_DELETE_ME) return setup_timer() @@ -49,12 +49,11 @@ var/mark = evolve_mark - sum timer_id = addtimer(CALLBACK(src, PROC_REF(leap), FALSE), mark, TIMER_STOPPABLE) -/datum/component/evolutionary_leap/proc/leap(silent) +/datum/component/evolutionary_leap/proc/leap() var/mob/living/old_mob = parent if (old_mob.stat == DEAD) return var/mob/living/new_mob = evolve_path var/new_mob_name = initial(new_mob.name) - if(!silent) - old_mob.visible_message(span_warning("[old_mob] evolves into \a [new_mob_name]!")) + old_mob.visible_message(span_warning("[old_mob] evolves into \a [new_mob_name]!")) old_mob.change_mob_type(evolve_path, old_mob.loc, new_name = new_mob_name, delete_old_mob = TRUE) diff --git a/code/modules/clothing/belts/polymorph_belt.dm b/code/modules/clothing/belts/polymorph_belt.dm index 07740888a9a..4177a24b28b 100644 --- a/code/modules/clothing/belts/polymorph_belt.dm +++ b/code/modules/clothing/belts/polymorph_belt.dm @@ -115,6 +115,7 @@ spell_requirements = NONE possible_shapes = list(/mob/living/basic/cockroach) can_be_shared = FALSE + shapechange_type = /datum/status_effect/shapechange_mob/from_spell/polymorph_belt /// Amount of time it takes us to transform back or forth var/channel_time = 3 SECONDS @@ -172,3 +173,22 @@ var/mob/living/will_become = transform_type desc = "Assume your [initial(will_become.name)] form!" build_all_button_icons(update_flags = UPDATE_BUTTON_NAME) + +/// Subtype of the polymorph status effect which tracks arbitrary mob transformation +/datum/status_effect/shapechange_mob/from_spell/polymorph_belt + +/datum/status_effect/shapechange_mob/from_spell/polymorph_belt/on_apply() + . = ..() + RegisterSignal(owner, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_type_change)) + +/datum/status_effect/shapechange_mob/from_spell/polymorph_belt/on_pre_type_change(mob/living/source) + return // Stub out base effect because we don't want to cancel if they transform + +/// If our mob transforms (via aging usually) then move the status effect across to the new mob +/datum/status_effect/shapechange_mob/from_spell/polymorph_belt/proc/on_type_change(mob/living/source, mob/living/new_mob) + SIGNAL_HANDLER + var/caster = caster_mob // Will be unset when the mob is unshifted + var/datum/action/cooldown/spell/shapeshift/transform_action = source_weakref?.resolve() + transform_action.possible_shapes |= new_mob.type + restore_caster() + new_mob.apply_status_effect(type, caster, transform_action) diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm index 205b6f49f80..06b04198e6b 100644 --- a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -29,6 +29,9 @@ /// This should be implemented even if there is only one choice. var/list/atom/possible_shapes + /// The shapechange status effect to apply to our mob, it should be a subtype of this if you change it + var/shapechange_type = /datum/status_effect/shapechange_mob/from_spell + /datum/action/cooldown/spell/shapeshift/Remove(mob/remove_from) unshift_owner() return ..() @@ -146,7 +149,7 @@ /// Actually does the shapeshift, for the caster. /datum/action/cooldown/spell/shapeshift/proc/do_shapeshift(mob/living/caster) 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) + var/datum/status_effect/shapechange_mob/shapechange = new_shape.apply_status_effect(shapechange_type, 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. @@ -169,7 +172,7 @@ /// 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) + var/datum/status_effect/shapechange_mob/shapechange = caster.has_status_effect(shapechange_type) 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!")) @@ -181,7 +184,7 @@ pre_shift_requirements = null var/mob/living/unshapeshifted_mob = shapechange.caster_mob - caster.remove_status_effect(/datum/status_effect/shapechange_mob/from_spell) + caster.remove_status_effect(shapechange_type) return unshapeshifted_mob /// Helper proc that instantiates the mob we shapeshift into.