Adds a new button to polymorph - Character (#5184)

## About The Pull Request

Title.

This new button allows you to change your character entirely, using
either your or the targets character slots. Basically a pocket SAD. You
can target anyone in view, including yourself.
## Why It's Good For The Game

Saucy shit. Seriously though, like, all the TF stuff we have ingame
kinda sucks. Having the ability to just on demand transform someone into
whatever you want it kinda awesome.
## Proof Of Testing
<details>
<summary>Screenshots/Videos</summary>


https://github.com/user-attachments/assets/91654b75-0f99-44d2-b8af-ba0a14bdb625

The dying bit just happens with slimes cause they have no heart, cant
think of a fix

</details>

## Changelog
🆑
fix: SAD can now remove organs, such as a taur body, or breasts
add: A new polymorph mode: Character, which allows you to switch your
character at your whim
/🆑
This commit is contained in:
nikothedude
2026-02-12 10:45:55 -05:00
committed by GitHub
parent 34a297b32a
commit de91f5826b
3 changed files with 181 additions and 4 deletions
@@ -176,6 +176,16 @@
// Updates the mob's chat color in the global cache
/datum/preferences/safe_transfer_prefs_to(mob/living/carbon/human/character, icon_updates = TRUE, is_antag = FALSE, visuals_only = FALSE)
// clear organs that might not be replaced
for (var/obj/item/organ/iter_organ as anything in character.organs)
var/feature_key = iter_organ.bodypart_overlay?.feature_key
if (isnull(feature_key))
continue
if(character.dna.mutant_bodyparts[feature_key] && character.dna.mutant_bodyparts[feature_key][MUTANT_INDEX_NAME] != SPRITE_ACCESSORY_NONE)
qdel(iter_organ)
character.dna.mutant_bodyparts.Cut()
. = ..()
GLOB.chat_colors_by_mob_name[character.name] = list(character.chat_color, character.chat_color_darkened) // by now the mob has had its prefs applied to it
@@ -11,8 +11,7 @@
ui_icon = "paintbrush"
action_to_grant = /datum/action/innate/alter_form/nif
/// The NIF version of alter form. This lacks the ability to change body color.
/// The NIF version of alter form.
/datum/action/innate/alter_form/nif
name = "Polymorph"
slime_restricted = FALSE
@@ -31,6 +30,7 @@
"DNA" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "dna"),
"Hair" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "scissors"),
"Markings" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "rainbow_spraycan"),
"Character" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "alter_form")
),
tooltips = TRUE,
)
@@ -45,4 +45,5 @@
alter_hair(alterer)
if("Markings")
alter_markings(alterer)
if("Character")
begin_character_alteration(alterer)
@@ -139,6 +139,7 @@
"DNA" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "dna"),
"Hair" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "scissors"),
"Markings" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "rainbow_spraycan"),
"Character" = image(icon = 'modular_skyrat/master_files/icons/mob/actions/actions_slime.dmi', icon_state = "alter_form"),
),
tooltips = TRUE,
)
@@ -151,7 +152,8 @@
alter_hair(alterer)
if("Markings")
alter_markings(alterer)
if("Character")
begin_character_alteration(alterer)
/**
* Alter colours handles the changing of mutant colours
* This affects skin tone primarily, though has the option to change hair, markings, and mutant body parts to match
@@ -547,6 +549,170 @@
alterer.dna.features["balls_size"] = avocados.balls_description_to_size(new_size)
avocados.set_size(alterer.dna.features["balls_size"])
/**
* The beginning for character alteration. Handles all the settings and targetting. Leads into [do_char_alteration].
*
* Args:
* * mob/living/carbon/human/alterer: The mob doing the transforming.
*
*/
/datum/action/innate/alter_form/proc/begin_character_alteration(mob/living/carbon/human/alterer)
var/list/mob/living/carbon/viable_targets = list()
to_chat(alterer, span_userdanger("This ability is not meant to be used for mechanical advantage."))
to_chat(alterer, span_warning("Your use of character mode will be admin logged! Don't mess about!"))
for (var/mob/living/carbon/human/iter_carbon in view(alterer))
if (!is_valid_char_alteration_target(iter_carbon))
continue
viable_targets += iter_carbon
var/mob/living/carbon/target = tgui_input_list(
alterer,
"Who do you want to transform?",
"Visible mobs",
viable_targets,
alterer,
5 SECONDS,
)
if (isnull(target) || !is_valid_char_alteration_target(target))
alterer.balloon_alert(alterer, "invalid selection!")
return
var/mode = tgui_alert(
alterer,
"Do you want to use your characters, or theirs?",
"Character Source",
list("Yours", "Theirs", "Cancel"),
5 SECONDS,
FALSE
)
var/client/target_client
var/mob/target_mob
switch (mode)
if ("Yours")
target_client = alterer.client
target_mob = alterer
if ("Theirs")
target_client = target.client
target_mob = target
if ("Cancel")
return
if (isnull(target_client) || isnull(target_mob))
alterer.balloon_alert(alterer, "invalid selection!")
return
var/list/prefdata_names = target_client.prefs.create_character_profiles()
if (isnull(prefdata_names))
return
var/target_char_tgui_title = ((mode == "Theirs") ? "[alterer] wants to transform you... which character?" : "Which character?")
var/target_char_name = tgui_input_list(
target_mob,
target_char_tgui_title,
"Character",
prefdata_names,
timeout = 5 SECONDS
)
if (isnull(target_char_name))
alterer.balloon_alert(alterer, "no selection!")
return
if (!is_valid_char_alteration_target(target))
return
var/allowed = tgui_alert(
target,
"[alterer.get_visible_name()] wants to transform you into [target_char_name]. Do you consent?",
"Transformation",
list("No", "Yes"),
10 SECONDS,
FALSE
)
if (allowed != "Yes")
alterer.balloon_alert(alterer, "transformation rejected")
return
var/datum/preferences/prefs = target_client?.prefs
if (isnull(prefs))
return
if (!is_valid_char_alteration_target(target))
return
var/old_slot = prefs.savefile.get_entry("default_slot")
prefs.load_character(prefdata_names.Find(target_char_name))
do_char_alteration(alterer, target, prefs)
prefs.load_character(old_slot)
/**
* The second and final step in character alteration. Actually sets the target to the new character.
*
* Args:
* * mob/living/carbon/human/alterer: The mob doing the transforming.
* * mob/living/carbon/human/target: The target to be transformed. Must have passed [is_valid_char_alteration_target].
* * datum/preferences/char_source: The source of the character. Generally either target's or alterer's preference datum.
*/
/datum/action/innate/alter_form/proc/do_char_alteration(mob/living/carbon/human/alterer, mob/living/carbon/human/target, datum/preferences/char_source)
target.visible_message(
span_warning("[target.get_visible_name()] unnervingly twitches, [target.p_their()] body distorting... until eventually transforming into something new."),
span_warning("Your body sears and tears, taking a new form!")
)
var/original_name = target.dna.real_name
// Once added this status effect never goes away, and always remembers the !first! original name
target.apply_status_effect(/datum/status_effect/shapeshift_transformed, original_name)
char_source.safe_transfer_prefs_to_with_damage(target)
target.dna.update_dna_identity()
SSquirks.OverrideQuirks(target, char_source.parent)
var/output = "[key_name(target)] has been transformed by [key_name(alterer)] using polymorph, at [loc_name(target)]. Original Name: [original_name], New Name: [target.dna.real_name]."
message_admins(output)
log_game(output)
/datum/status_effect/shapeshift_transformed
id = "shapeshift_transformed"
alert_type = null
var/orig_name
/datum/status_effect/shapeshift_transformed/on_creation(mob/living/new_owner, orig_name)
. = ..()
if (!.)
return
src.orig_name = orig_name
/datum/status_effect/shapeshift_transformed/get_examine_text()
var/mob/living/carbon/human/human_owner = owner
if (!istype(owner))
return
var/curr_name = human_owner.dna.real_name
if (curr_name == orig_name)
return
return span_warning("This character has been transformed via Shapeshift, originally being [orig_name].")
/**
* Validates if the target can be transformed.
*
* Args:
* * mob/living/carbon/human/target: The check target.
*
* Returns:
* * FALSE if the target has no client or is dead. TRUE otherwise.
*/
/datum/action/innate/alter_form/proc/is_valid_char_alteration_target(mob/living/carbon/target)
var/client/client = target.client
if (isnull(client))
return FALSE
if (target.stat == DEAD)
return FALSE
return TRUE
/datum/species/jelly/on_bloodsucker_gain(mob/living/carbon/human/target)
humanize_organs(target)