Files
nikothedudeandGitHub de91f5826b 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
/🆑
2026-02-12 10:45:55 -05:00

193 lines
8.6 KiB
Plaintext

#define MAX_MUTANT_ROWS 4
/datum/preferences
/// Associative list, keyed by language typepath, pointing to UNDERSTOOD_LANGUAGE, or UNDERSTOOD_LANGUAGE | SPOKEN_LANGUAGE, for whether we understand or speak the language
var/list/languages = list()
/// List of chosen augmentations. It's an associative list with key name of the slot, pointing to a typepath of an augment define
var/augments = list()
/// List of chosen preferred styles for limb replacements
var/augment_limb_styles = list()
/// Which augment slot we currently have chosen, this is for UI display
var/chosen_augment_slot
/// Has to include all information that extra organs from mutant bodyparts would need. (so far only genitals now)
var/list/features = MANDATORY_FEATURE_LIST
/// A list containing all of our mutant bodparts
var/list/list/mutant_bodyparts = list()
/// A list of all bodymarkings
var/list/list/body_markings = list()
/// Will the person see accessories not meant for their species to choose from
var/mismatched_customization = FALSE
/// Allows the user to freely color his body markings and mutant parts.
var/allow_advanced_colors = FALSE
/// Preference of how the preview should show the character.
var/preview_pref = PREVIEW_PREF_JOB
var/needs_update = TRUE
var/arousal_preview = AROUSAL_NONE
var/datum/species/pref_species
// BACKGROUND STUFF
var/general_record = ""
var/security_record = ""
var/medical_record = ""
var/background_info = ""
var/exploitable_info = ""
/// Whether the user wants to see body size being shown in the preview
var/show_body_size = FALSE
/// Alternative job titles stored in preferences. Assoc list, ie. alt_job_titles["Scientist"] = "Cytologist"
var/list/alt_job_titles = list()
// Determines if the player has undergone TGUI preferences migration, if so, this will prevent constant loading.
var/tgui_prefs_migration = TRUE
/// An assoc list of food types to liked or dislike values. If null or empty, default species tastes are used instead on application.
/// If a food doesn't exist in this list, it uses the default value.
var/list/food_preferences = list()
/datum/preferences/proc/species_updated(species_type)
all_quirks = list()
// Reset cultural stuff
languages[try_get_common_language()] = UNDERSTOOD_LANGUAGE | SPOKEN_LANGUAGE
save_character()
/datum/preferences/proc/print_bodypart_change_line(key)
var/acc_name = mutant_bodyparts[key][MUTANT_INDEX_NAME]
var/shown_colors = 0
var/datum/sprite_accessory/SA = SSaccessories.sprite_accessories[key][acc_name]
var/dat = ""
if(SA.color_src == USE_MATRIXED_COLORS)
shown_colors = 3
else if (SA.color_src == USE_ONE_COLOR)
shown_colors = 1
if((allow_advanced_colors || SA.always_color_customizable) && shown_colors)
dat += "<a href='byond://?src=[REF(src)];key=[key];preference=reset_color;task=change_bodypart'>R</a>"
dat += "<a href='byond://?src=[REF(src)];key=[key];preference=change_name;task=change_bodypart'>[acc_name]</a>"
if(allow_advanced_colors || SA.always_color_customizable)
if(shown_colors)
dat += "<BR>"
var/list/colorlist = mutant_bodyparts[key][MUTANT_INDEX_COLOR_LIST]
for(var/i in 1 to shown_colors)
dat += " <a href='byond://?src=[REF(src)];key=[key];color_index=[i];preference=change_color;task=change_bodypart'><span class='color_holder_box' style='background-color:["#[colorlist[i]]"]'></span></a>"
return dat
/datum/preferences/proc/reset_colors()
for(var/key in mutant_bodyparts)
var/datum/sprite_accessory/SA = SSaccessories.sprite_accessories[key][mutant_bodyparts[key][MUTANT_INDEX_NAME]]
if(SA.always_color_customizable)
continue
mutant_bodyparts[key][MUTANT_INDEX_COLOR_LIST] = SA.get_default_color(features, pref_species)
for(var/zone in body_markings)
var/list/bml = body_markings[zone]
for(var/key in bml)
var/datum/body_marking/BM = GLOB.body_markings[key]
bml[key] = BM.get_default_color(features, pref_species)
/// This helper proc gets the current species language holder and does any post-processing that's required in one easy to track place.
/// This proc should *always* be edited or used when modifying or getting the default languages of a player controlled, unrestricted species, to prevent any errant conflicts.
/datum/preferences/proc/get_adjusted_language_holder()
var/datum/species/species = read_preference(/datum/preference/choiced/species)
species = new species()
var/datum/language_holder/language_holder = new species.species_language_holder()
// Do language post procesing here. Used to house our foreigner functionality.
// I saw little reason to remove this proc, considering it makes code using this a little easier to read.
return language_holder
/// Tries to get the topmost language of the language holder. Should be the species' native language, and if it isn't, you should pester a coder.
/datum/preferences/proc/try_get_common_language()
var/datum/language_holder/language_holder = get_adjusted_language_holder()
var/language = language_holder.spoken_languages[1]
return language
/datum/preferences/proc/validate_species_parts()
var/list/default_bodyparts = GLOB.default_mutant_bodyparts[pref_species.name]
var/list/target_bodyparts = default_bodyparts.Copy()
// Remove all "extra" accessories
for(var/key in mutant_bodyparts)
if(!SSaccessories.sprite_accessories[key]) // That accessory no longer exists, remove it
mutant_bodyparts -= key
continue
if(!GLOB.default_mutant_bodyparts[pref_species.name][key])
mutant_bodyparts -= key
continue
if(!SSaccessories.sprite_accessories[key][mutant_bodyparts[key][MUTANT_INDEX_NAME]]) // The individual accessory no longer exists
mutant_bodyparts[key][MUTANT_INDEX_NAME] = GLOB.default_mutant_bodyparts[pref_species.name[key][MUTANTPART_NAME]]
validate_color_keys_for_part(key) // Validate the color count of each accessory that wasnt removed
// Add any missing accessories
for(var/key in target_bodyparts)
if(!mutant_bodyparts[key])
var/datum/sprite_accessory/SA
if(target_bodyparts[key][MUTANTPART_CAN_RANDOMIZE])
SA = random_accessory_of_key_for_species(key, pref_species)
else
SA = SSaccessories.sprite_accessories[key][target_bodyparts[key][MUTANTPART_NAME]]
var/final_list = list()
final_list[MUTANT_INDEX_NAME] = SA.name
final_list[MUTANT_INDEX_COLOR_LIST] = SA.get_default_color(features, pref_species)
mutant_bodyparts[key] = final_list
if(!allow_advanced_colors)
reset_colors()
/datum/preferences/proc/validate_color_keys_for_part(key)
var/datum/sprite_accessory/SA = SSaccessories.sprite_accessories[key][mutant_bodyparts[key][MUTANT_INDEX_NAME]]
var/list/colorlist = mutant_bodyparts[key][MUTANT_INDEX_COLOR_LIST]
if(SA.color_src == USE_MATRIXED_COLORS && colorlist.len != 3)
mutant_bodyparts[key][MUTANT_INDEX_COLOR_LIST] = SA.get_default_color(features, pref_species)
else if (SA.color_src == USE_ONE_COLOR && colorlist.len != 1)
mutant_bodyparts[key][MUTANT_INDEX_COLOR_LIST] = SA.get_default_color(features, pref_species)
/datum/preferences/proc/CanBuyAugment(datum/augment_item/target_aug, datum/augment_item/current_aug)
// Check biotypes
if(!(pref_species.inherent_biotypes & target_aug.allowed_biotypes))
return
var/quirk_points = GetQuirkBalance()
var/leverage = 0
if(current_aug)
leverage += current_aug.cost
if((quirk_points + leverage)>= target_aug.cost)
return TRUE
else
return FALSE
/// This proc saves the damage currently on `character` (human) and reapplies it after `safe_transfer_prefs()` is applied to the `character`.
/datum/preferences/proc/safe_transfer_prefs_to_with_damage(mob/living/carbon/human/character, icon_updates = TRUE, is_antag = FALSE, visuals_only = FALSE)
if(!istype(character))
return FALSE
var/datum/component/damage_tracker/human/added_tracker = character.AddComponent(/datum/component/damage_tracker/human)
if(!added_tracker)
return FALSE
safe_transfer_prefs_to(character, icon_updates, is_antag, visuals_only = visuals_only)
qdel(added_tracker)
// 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
#undef MAX_MUTANT_ROWS