Files
CHOMPStation2/code/modules/client/preferences.dm
CHOMPStation2StaffMirrorBot 75e167a92f [MIRROR] Dna, Bodyrecord, Xenochi Revive Refactor (#11038)
Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
2025-06-11 03:41:08 +02:00

646 lines
23 KiB
Plaintext

var/list/preferences_datums = list()
/datum/preferences
/// The path to the general savefile for this datum
var/path
/// Whether or not we allow saving/loading. Used for guests, if they're enabled
var/load_and_save = TRUE
/// Ensures that we always load the last used save, QOL
var/default_slot = 1
//non-preference stuff
var/warns = 0
var/muted = 0
var/last_ip
var/last_id
var/saved_notification = FALSE
//game-preferences
var/lastchangelog = "" //Saved changlog filesize to detect if there was a change // CHOMPAdd
var/be_special = 0 //Special role selection
//character preferences
var/real_name //our character's name
var/nickname //our character's nickname
var/b_type = DEFAULT_BLOOD_TYPE //blood type (not-chooseable)
var/blood_reagents = "default" //blood restoration reagents
var/headset = 1 //headset type
var/backbag = 2 //backpack type
var/pdachoice = 1 //PDA type
//var/shoe_hater = FALSE //If true, will spawn with no shoes //CHOMPRemove, remove RS No shoes
var/no_jacket = FALSE //if true, will not spawn with outfit's jacket/outer layer
var/h_style = "Bald" //Hair type
var/grad_style = "none" //Gradient style
var/f_style = "Shaved" //Face hair type
var/s_tone = -75 //Skin tone
var/species = SPECIES_HUMAN //Species datum to use.
var/species_preview //Used for the species selection window.
var/list/alternate_languages = list() //Secondary language(s)
var/list/language_prefixes = list() //Language prefix keys
var/list/language_custom_keys = list() //Language custom call keys
var/list/gear_list = list() //Custom/fluff item loadouts.
var/gear_slot = 1 //The current gear save slot
var/list/traits //Traits which modifier characters for better or worse (mostly worse).
var/synth_color = 0 //Lets normally uncolorable synth parts be colorable.
var/synth_markings = 1 //Enable/disable markings on synth parts.
var/digitigrade = 0
//Some faction information.
var/home_system = "Unset" //Current home or residence.
var/birthplace = "Unset" //Location of birth.
var/citizenship = "None" //Government or similar entity with which you hold citizenship.
var/faction = "None" //General associated faction.
var/religion = "None" //Religious association.
var/antag_faction = "None" //Antag associated faction.
var/antag_vis = "Hidden" //How visible antag association is to others.
//Mob preview
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
var/static/list/preview_screen_locs = list(
"1" = "character_preview_map:2,7",
"2" = "character_preview_map:2,5",
"4" = "character_preview_map:2,3",
"8" = "character_preview_map:2,1",
"BG" = "character_preview_map:1,1 to 3,8",
"PMH" = "character_preview_map:2,7",
"PMHjiggle" = "character_preview_map:102,7:107",
)
//Jobs, uses bitflags
var/job_civilian_high = 0
var/job_civilian_med = 0
var/job_civilian_low = 0
var/job_medsci_high = 0
var/job_medsci_med = 0
var/job_medsci_low = 0
var/job_engsec_high = 0
var/job_engsec_med = 0
var/job_engsec_low = 0
//Keeps track of preferrence for not getting any wanted jobs
var/alternate_option = 1
//character preferences
var/slot_randomized //keeps track of round-to-round randomization of the character slot, prevents overwriting
var/list/randomise = list()
// maps each organ to either null(intact), "cyborg" or "amputated"
// will probably not be able to do this for head and torso ;)
var/list/organ_data = list()
var/list/rlimb_data = list()
var/list/player_alt_titles = new() // the default name of a job like JOB_MEDICAL_DOCTOR
var/list/body_markings = list() // "name" = "#rgbcolor" //VOREStation Edit: "name" = list(BP_HEAD = list("on" = <enabled>, "color" = "#rgbcolor"), BP_TORSO = ...)
var/list/flavor_texts = list()
var/list/flavour_texts_robot = list()
var/custom_link = null
var/med_record = ""
var/sec_record = ""
var/gen_record = ""
var/exploit_record = ""
var/economic_status = "Average"
var/client/client = null
var/client_ckey = null
// Communicator identity data
var/communicator_visibility = 0
/// Default ringtone for character; if blank, use job default.
var/ringtone = null
var/datum/category_collection/player_setup_collection/player_setup
var/datum/browser/panel
var/lastnews // Hash of last seen lobby news content.
var/lastlorenews //ID of last seen lore news article.
var/examine_text_mode = 0 // Just examine text, include usage (description_info), switch to examine panel.
var/multilingual_mode = 0 // Default behaviour, delimiter-key-space, delimiter-key-delimiter, off
// THIS IS NOT SAVED
// WE JUST HAVE NOWHERE ELSE TO STORE IT
var/list/action_button_screen_locs
///If they are currently in the process of swapping slots, don't let them open 999 windows for it and get confused
var/selecting_slots = FALSE
/// The json savefile for this datum
var/datum/json_savefile/savefile
/datum/preferences/New(client/C)
client = C
for(var/middleware_type in subtypesof(/datum/preference_middleware))
middleware += new middleware_type(src)
if(istype(C)) // IS_CLIENT_OR_MOCK
client_ckey = C.ckey
load_and_save = !IsGuestKey(C.key)
load_path(C.ckey)
if(load_and_save && !fexists(path))
try_savefile_type_migration()
else
CRASH("attempted to create a preferences datum without a client or mock!")
load_savefile()
// Legacy code
gear_list = list()
gear_slot = 1
// End legacy code
player_setup = new(src)
var/loaded_preferences_successfully = load_preferences()
if(loaded_preferences_successfully)
if(load_character())
return
// Didn't load a character, so let's randomize
set_biological_gender(pick(MALE, FEMALE))
real_name = random_name(identifying_gender,species)
b_type = RANDOM_BLOOD_TYPE
if(client)
apply_all_client_preferences()
if(!loaded_preferences_successfully)
save_preferences()
save_character() // Save random character
/datum/preferences/Destroy()
QDEL_LIST_ASSOC_VAL(char_render_holders)
QDEL_NULL(middleware)
value_cache = null
return ..()
/datum/preferences/proc/ShowChoices(mob/user)
if(!user || !user.client) return
if(!get_mob_by_key(client_ckey))
to_chat(user, span_danger("No mob exists for the given client!"))
return
if(!char_render_holders)
update_preview_icon()
show_character_previews()
current_window = PREFERENCE_TAB_CHARACTER_PREFERENCES
update_tgui_static_data(user)
tgui_interact(user)
/datum/preferences/proc/update_character_previews(var/mob/living/carbon/human/mannequin)
if(!client)
return
var/obj/screen/setup_preview/pm_helper/PMH = LAZYACCESS(char_render_holders, "PMH")
if(!PMH)
PMH = new
LAZYSET(char_render_holders, "PMH", PMH)
client.screen |= PMH
PMH.screen_loc = preview_screen_locs["PMH"]
var/obj/screen/setup_preview/bg/BG = LAZYACCESS(char_render_holders, "BG")
if(!BG)
BG = new
BG.plane = TURF_PLANE
BG.icon = 'icons/effects/setup_backgrounds_vr.dmi'
BG.pref = src
LAZYSET(char_render_holders, "BG", BG)
client.screen |= BG
BG.icon_state = bgstate
BG.screen_loc = preview_screen_locs["BG"]
for(var/D in global.GLOB.cardinal)
var/obj/screen/setup_preview/O = LAZYACCESS(char_render_holders, "[D]")
if(!O)
O = new
O.pref = src
LAZYSET(char_render_holders, "[D]", O)
client.screen |= O
mannequin.set_dir(D)
mannequin.update_tail_showing()
mannequin.ImmediateOverlayUpdate()
var/mutable_appearance/MA = new(mannequin)
O.appearance = MA
O.screen_loc = preview_screen_locs["[D]"]
/datum/preferences/proc/show_character_previews()
if(!client || !char_render_holders)
return
for(var/render_holder in char_render_holders)
client.screen |= char_render_holders[render_holder]
/datum/preferences/proc/clear_character_previews()
for(var/index in char_render_holders)
var/obj/screen/S = char_render_holders[index]
client?.screen -= S
qdel(S)
char_render_holders = null
/datum/preferences/proc/process_link(mob/user, list/href_list)
if(!user) return
if(!isnewplayer(user)) return
if(href_list["preference"] == "open_whitelist_forum")
if(CONFIG_GET(string/forumurl))
user << link(CONFIG_GET(string/forumurl))
else
to_chat(user, span_danger("The forum URL is not set in the server configuration."))
return
ShowChoices(user)
return 1
/datum/preferences/Topic(href, list/href_list)
if(..())
return 1
if(href_list["save"])
if(save_character())
to_chat(usr,span_notice("Character [player_setup?.preferences?.real_name] saved!"))
save_preferences()
else if(href_list["reload"])
load_preferences(TRUE)
load_character()
attempt_vr(client.prefs_vr,"load_vore","")
sanitize_preferences()
else if(href_list["load"])
if(!IsGuestKey(usr.key))
open_load_dialog(usr)
return 1
else if(href_list["resetslot"])
if("Yes" != tgui_alert(usr, "This will reset the current slot. Continue?", "Reset current slot?", list("No", "Yes")))
return 0
if("Yes" != tgui_alert(usr, "Are you completely sure that you want to reset this character slot?", "Reset current slot?", list("No", "Yes")))
return 0
reset_slot()
sanitize_preferences()
else if(href_list["copy"])
if(!IsGuestKey(usr.key))
open_copy_dialog(usr)
return 1
else if(href_list["close"])
// User closed preferences window, cleanup anything we need to.
clear_character_previews()
//Mannequin removal code needed here...For the far future once harddels are solved.
return 1
else
return 0
ShowChoices(usr)
return 1
/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = TRUE)
// Sanitizing rather than saving as someone might still be editing when copy_to occurs.
player_setup.sanitize_setup()
// This needs to happen before anything else becuase it sets some variables.
character.set_species(species)
// Special Case: This references variables owned by two different datums, so do it here.
if(read_preference(/datum/preference/toggle/human/name_is_always_random))
real_name = random_name(identifying_gender,species)
// Ask the preferences datums to apply their own settings to the new mob
player_setup.copy_to_mob(character)
for(var/datum/preference/preference as anything in get_preferences_in_priority_order())
if(preference.savefile_identifier != PREFERENCE_CHARACTER)
continue
preference.apply_pref_to(character, read_preference(preference.type))
// Sync up all their organs and species one final time
character.force_update_organs()
if(icon_updates)
character.force_update_limbs()
character.update_icons_body()
character.update_mutations()
character.update_underwear()
character.update_hair()
/datum/preferences/proc/open_load_dialog(mob/user)
if(selecting_slots)
to_chat(user, span_warning("You already have a slot selection dialog open!"))
return
if(!savefile)
return
var/default
var/list/charlist = list()
for(var/i in 1 to CONFIG_GET(number/character_slots))
var/list/save_data = savefile.get_entry("character[i]", list())
var/name = save_data["real_name"]
var/nickname = save_data["nickname"]
if(!name)
name = "[i] - \[Unused Slot\]"
else if(i == default_slot)
name = "►[i] - [name]"
else
name = "[i] - [name]"
if(i == default_slot)
default = "[name][nickname ? " ([nickname])" : ""]"
charlist["[name][nickname ? " ([nickname])" : ""]"] = i
selecting_slots = TRUE
var/choice = tgui_input_list(user, "Select a character to load:", "Load Slot", charlist, default)
selecting_slots = FALSE
if(!choice)
return
var/slotnum = charlist[choice]
if(!slotnum)
error("Player picked [choice] slot to load, but that wasn't one we sent.")
return
load_preferences(TRUE)
load_character(slotnum)
attempt_vr(user.client?.prefs_vr,"load_vore","")
sanitize_preferences()
save_preferences()
ShowChoices(user)
/datum/preferences/proc/open_copy_dialog(mob/user)
if(selecting_slots)
to_chat(user, span_warning("You already have a slot selection dialog open!"))
return
if(!savefile)
return
var/list/charlist = list()
for(var/i in 1 to CONFIG_GET(number/character_slots))
var/list/save_data = savefile.get_entry("character[i]", list())
var/name = save_data["real_name"]
var/nickname = save_data["nickname"]
if(!name)
name = "[i] - \[Unused Slot\]"
else if(i == default_slot)
name = "►[i] - [name]"
else
name = "[i] - [name]"
charlist["[name][nickname ? " ([nickname])" : ""]"] = i
selecting_slots = TRUE
var/choice = tgui_input_list(user, "Select a character to COPY TO:", "Copy Slot", charlist)
selecting_slots = FALSE
if(!choice)
return
var/slotnum = charlist[choice]
if(!slotnum)
error("Player picked [choice] slot to copy to, but that wasn't one we sent.")
return
if(tgui_alert(user, "Are you sure you want to override slot [slotnum], [choice]'s savedata?", "Confirm Override", list("No", "Yes")) == "Yes")
overwrite_character(slotnum)
sanitize_preferences()
save_character()
save_preferences()
attempt_vr(user.client?.prefs_vr,"load_vore","")
ShowChoices(user)
/datum/preferences/proc/vanity_copy_to(var/mob/living/carbon/human/character, var/copy_name, var/copy_flavour = TRUE, var/copy_ooc_notes = FALSE, var/convert_to_prosthetics = FALSE)
//snowflake copy_to, does not copy anything but the vanity things
//does not check if the name is the same, do that in any proc that calls this proc
/*
name, nickname, flavour, OOC notes
gender, sex
custom species name, custom bodytype, weight, scale, scaling center, sound type, sound freq
custom say verbs
ears, wings, tail, hair, facial hair
ears colors, wings colors, tail colors
body color, prosthetics (if they're a protean) (convert to DSI if protean and not prosthetic), eye color, hair color etc
markings
custom synth markings toggle, custom synth color toggle
digitigrade
blood color
*/
if (copy_name)
if(CONFIG_GET(flag/humans_need_surnames))
var/firstspace = findtext(real_name, " ")
var/name_length = length(real_name)
if(!firstspace) //we need a surname
real_name += " [pick(last_names)]"
else if(firstspace == name_length)
real_name += "[pick(last_names)]"
character.real_name = real_name
character.name = character.real_name
if(character.dna)
character.dna.real_name = character.real_name
character.nickname = nickname
character.gender = biological_gender
character.identifying_gender = identifying_gender
character.h_style = h_style
var/datum/preference/color/hair_color = GLOB.preference_entries[/datum/preference/color/human/hair_color]
hair_color.apply_pref_to(character, read_preference(/datum/preference/color/human/hair_color))
var/datum/preference/color/grad_color = GLOB.preference_entries[/datum/preference/color/human/grad_color]
grad_color.apply_pref_to(character, read_preference(/datum/preference/color/human/grad_color))
character.f_style = f_style
character.s_tone = s_tone
character.h_style = h_style
character.grad_style= grad_style
character.f_style = f_style
character.grad_style= grad_style
character.dna.b_type= b_type
character.synth_color = synth_color
var/datum/preference/color/synth_color_color = GLOB.preference_entries[/datum/preference/color/human/synth_color]
synth_color_color.apply_pref_to(character, read_preference(/datum/preference/color/human/synth_color))
character.synth_markings = synth_markings
var/list/ear_styles = get_available_styles(global.ear_styles_list)
character.ear_style = ear_styles[ear_style]
var/datum/preference/color/ears_color1 = GLOB.preference_entries[/datum/preference/color/human/ears_color1]
ears_color1.apply_pref_to(character, read_preference(/datum/preference/color/human/ears_color1))
var/datum/preference/color/ears_color2 = GLOB.preference_entries[/datum/preference/color/human/ears_color2]
ears_color2.apply_pref_to(character, read_preference(/datum/preference/color/human/ears_color2))
var/datum/preference/color/ears_color3 = GLOB.preference_entries[/datum/preference/color/human/ears_color3]
ears_color3.apply_pref_to(character, read_preference(/datum/preference/color/human/ears_color3))
character.ear_secondary_style = ear_styles[ear_secondary_style]
character.ear_secondary_colors = SANITIZE_LIST(ear_secondary_colors)
var/list/tail_styles = get_available_styles(global.tail_styles_list)
character.tail_style = tail_styles[tail_style]
var/datum/preference/color/tail_color1 = GLOB.preference_entries[/datum/preference/color/human/tail_color1]
tail_color1.apply_pref_to(character, read_preference(/datum/preference/color/human/tail_color1))
var/datum/preference/color/tail_color2 = GLOB.preference_entries[/datum/preference/color/human/tail_color2]
tail_color2.apply_pref_to(character, read_preference(/datum/preference/color/human/tail_color2))
var/datum/preference/color/tail_color3 = GLOB.preference_entries[/datum/preference/color/human/tail_color3]
tail_color3.apply_pref_to(character, read_preference(/datum/preference/color/human/tail_color3))
var/list/wing_styles = get_available_styles(global.wing_styles_list)
character.wing_style = wing_styles[wing_style]
var/datum/preference/color/wing_color1 = GLOB.preference_entries[/datum/preference/color/human/wing_color1]
wing_color1.apply_pref_to(character, read_preference(/datum/preference/color/human/wing_color1))
var/datum/preference/color/wing_color2 = GLOB.preference_entries[/datum/preference/color/human/wing_color2]
wing_color2.apply_pref_to(character, read_preference(/datum/preference/color/human/wing_color2))
var/datum/preference/color/wing_color3 = GLOB.preference_entries[/datum/preference/color/human/wing_color3]
wing_color3.apply_pref_to(character, read_preference(/datum/preference/color/human/wing_color3))
var/datum/preference/numeric/wing_alpha = GLOB.preference_entries[/datum/preference/numeric/human/wing_alpha]
wing_alpha.apply_pref_to(character,read_preference(/datum/preference/numeric/human/wing_alpha))
var/datum/preference/numeric/skin_color = GLOB.preference_entries[/datum/preference/color/human/skin_color]
skin_color.apply_pref_to(character,read_preference(/datum/preference/color/human/skin_color))
character.set_gender(biological_gender)
// Destroy/cyborgize organs and limbs.
if (convert_to_prosthetics) //should only really be run for proteans
var/list/organs_to_edit = list()
for (var/name in list(BP_TORSO, BP_HEAD, BP_GROIN, BP_L_ARM, BP_R_ARM, BP_L_HAND, BP_R_HAND, BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT))
var/obj/item/organ/external/O = character.organs_by_name[name]
if (O)
var/x = organs_to_edit.Find(O.parent_organ)
if (x == 0)
organs_to_edit += name
else
organs_to_edit.Insert(x+(O.robotic == ORGAN_NANOFORM ? 1 : 0), name)
for(var/name in organs_to_edit)
var/status = organ_data[name]
var/obj/item/organ/external/O = character.organs_by_name[name]
if(O)
if(status == "amputated")
continue
else if(status == "cyborg")
O.robotize(rlimb_data[name])
else
var/bodytype
var/datum/species/selected_species = GLOB.all_species[species]
if(selected_species.selects_bodytype && custom_base) //Everyone technically has custom_base set to HUMAN, but only some species actually select it.
bodytype = custom_base
else
bodytype = selected_species.get_bodytype()
var/dsi_company = GLOB.dsi_to_species[bodytype]
if (!dsi_company)
dsi_company = "DSI - Adaptive"
O.robotize(dsi_company)
for(var/N in character.organs_by_name)
var/obj/item/organ/external/O = character.organs_by_name[N]
if(O)
O.markings.Cut()
var/priority = 0
for(var/M in body_markings)
priority += 1
var/datum/sprite_accessory/marking/mark_datum = body_marking_styles_list[M]
for(var/BP in mark_datum.body_parts)
var/obj/item/organ/external/O = character.organs_by_name[BP]
if(O)
if(!islist(body_markings[M][BP])) continue
O.markings[M] = list("color" = body_markings[M][BP]["color"], "datum" = mark_datum, "priority" = priority, "on" = body_markings[M][BP]["on"])
character.markings_len = priority
if (copy_flavour)
character.flavor_texts["general"] = flavor_texts["general"]
character.flavor_texts["head"] = flavor_texts["head"]
character.flavor_texts["face"] = flavor_texts["face"]
character.flavor_texts["eyes"] = flavor_texts["eyes"]
character.flavor_texts["torso"] = flavor_texts["torso"]
character.flavor_texts["arms"] = flavor_texts["arms"]
character.flavor_texts["hands"] = flavor_texts["hands"]
character.flavor_texts["legs"] = flavor_texts["legs"]
character.flavor_texts["feet"] = flavor_texts["feet"]
if (copy_ooc_notes)
character.ooc_notes = read_preference(/datum/preference/text/living/ooc_notes)
character.ooc_notes_dislikes = read_preference(/datum/preference/text/living/ooc_notes_dislikes)
character.ooc_notes_likes = read_preference(/datum/preference/text/living/ooc_notes_likes)
character.ooc_notes_favs = read_preference(/datum/preference/text/living/ooc_notes_favs)
character.ooc_notes_maybes = read_preference(/datum/preference/text/living/ooc_notes_maybes)
character.ooc_notes_style = read_preference(/datum/preference/toggle/living/ooc_notes_style)
character.weight = weight_vr
character.weight_gain = weight_gain
character.weight_loss = weight_loss
character.fuzzy = fuzzy
character.offset_override = offset_override
character.voice_freq = voice_freq
character.resize(size_multiplier, animate = FALSE, ignore_prefs = TRUE)
var/list/traits_to_copy = list(/datum/trait/neutral/tall,
/datum/trait/neutral/taller,
/datum/trait/neutral/short,
/datum/trait/neutral/shorter,
/datum/trait/neutral/obese,
/datum/trait/neutral/fat,
/datum/trait/neutral/thin,
/datum/trait/neutral/thinner,
/datum/trait/neutral/micro_size_down,
/datum/trait/neutral/micro_size_up)
//reset all the above trait vars
if (character.species)
character.species.micro_size_mod = 0
character.species.icon_scale_x = 1
character.species.icon_scale_y = 1
for (var/trait in neu_traits)
if (trait in traits_to_copy)
var/datum/trait/instance = GLOB.all_traits[trait]
if (!instance)
continue
for (var/to_edit in instance.var_changes)
character.species.vars[to_edit] = instance.var_changes[to_edit]
character.update_transform()
if(!voice_sound)
character.voice_sounds_list = DEFAULT_TALK_SOUNDS
else
character.voice_sounds_list = get_talk_sound(voice_sound)
character.species?.blood_color = blood_color
var/datum/species/selected_species = GLOB.all_species[species]
var/bodytype_selected
if(selected_species.selects_bodytype && custom_base)
bodytype_selected = custom_base
else
bodytype_selected = selected_species.get_bodytype(character)
character.dna.base_species = bodytype_selected
character.species.base_species = bodytype_selected
character.species.icobase = character.species.get_icobase()
character.species.deform = character.species.get_icobase(get_deform = TRUE)
character.species.vanity_base_fit = bodytype_selected
if(istype(character.species, /datum/species/shapeshifter))
wrapped_species_by_ref["\ref[character]"] = bodytype_selected
character.custom_species = custom_species
character.custom_say = lowertext(trim(custom_say))
character.custom_ask = lowertext(trim(custom_ask))
character.custom_whisper = lowertext(trim(custom_whisper))
character.custom_exclaim = lowertext(trim(custom_exclaim))
character.digitigrade = digitigrade
for(var/obj/item/clothing/O in character.contents)
O.handle_digitigrade(character)
character.dna.ResetUIFrom(character)
character.force_update_limbs()
character.regenerate_icons()