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" = , "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. // 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 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) save_character() save_preferences() load_preferences(TRUE) load_character() 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, var/apply_bloodtype = TRUE) //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(GLOB.last_names)]" else if(firstspace == name_length) real_name += "[pick(GLOB.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 if(apply_bloodtype) character.dna.b_type= b_type //This actually just straight up kills whoever uses it if the blood types aren't compatible in TF 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(GLOB.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(GLOB.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(GLOB.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 = GLOB.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()