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 //game-preferences var/lastchangelog = "" //Saved changlog filesize to detect if there was a change var/ooccolor = "#010000" //Whatever this is set to acts as 'reset' color and is thus unusable as an actual custom color var/be_special = 0 //Special role selection var/UI_style = "Midnight" var/UI_style_color = "#ffffff" var/UI_style_alpha = 255 var/tooltipstyle = "Midnight" //Style for popup tooltips var/client_fps = 40 var/ambience_freq = 5 // How often we're playing repeating ambience to a client. var/ambience_chance = 35 // What's the % chance we'll play ambience (in conjunction with the above frequency) var/tgui_fancy = TRUE var/tgui_lock = FALSE var/tgui_input_mode = FALSE // All the Input Boxes (Text,Number,List,Alert) var/tgui_large_buttons = TRUE var/tgui_swapped_buttons = FALSE var/obfuscate_key = FALSE var/obfuscate_job = FALSE var/chat_timestamp = FALSE //character preferences var/real_name //our character's name var/be_random_name = 0 //whether we are a random name every round var/nickname //our character's nickname var/age = 30 //age of character var/bday_month = 0 //Birthday month var/bday_day = 0 //Birthday day var/last_birthday_notification = 0 //The last year we were notified about our birthday var/bday_announce = FALSE //Public announcement for birthdays var/spawnpoint = "Arrivals Shuttle" //where this character will spawn (0-2). var/b_type = "A+" //blood type (not-chooseable) var/blood_reagents = "default" //blood restoration reagents var/backbag = 2 //backpack type var/pdachoice = 1 //PDA type //var/shoe_hater = FALSE //RS ADD - if true, will spawn with no shoes //CHOMPRemove, remove RS No shoes var/h_style = "Bald" //Hair type var/r_hair = 0 //Hair color var/g_hair = 0 //Hair color var/b_hair = 0 //Hair color var/grad_style = "none" //Gradient style var/r_grad = 0 //Gradient color var/g_grad = 0 //Gradient color var/b_grad = 0 //Gradient color var/f_style = "Shaved" //Face hair type var/r_facial = 0 //Face hair color var/g_facial = 0 //Face hair color var/b_facial = 0 //Face hair color var/s_tone = -75 //Skin tone var/r_skin = 238 //Skin color // Vorestation edit, so color multi sprites can aren't BLACK AS THE VOID by default. var/g_skin = 206 //Skin color // Vorestation edit, so color multi sprites can aren't BLACK AS THE VOID by default. var/b_skin = 179 //Skin color // Vorestation edit, so color multi sprites can aren't BLACK AS THE VOID by default. var/r_eyes = 0 //Eye color var/g_eyes = 0 //Eye color var/b_eyes = 0 //Eye color 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 //Left in for Legacy reasons, will no longer save. 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/r_synth //Used with synth_color to color synth parts that normaly can't be colored. var/g_synth //Same as above var/b_synth //Same as above var/synth_markings = 1 //Enable/disable markings on synth parts. //VOREStation Edit - 1 by default 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" ) //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 var/used_skillpoints = 0 var/skill_specialization = null var/list/skills = list() // skills can range from 0 to 3 // 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/list/body_descriptors = list() var/med_record = "" var/sec_record = "" var/gen_record = "" var/exploit_record = "" var/disabilities = 0 var/economic_status = "Average" var/uplinklocation = "PDA" // OOC Metadata: var/metadata = "" var/metadata_likes = "" var/metadata_dislikes = "" var/list/ignored_players = list() 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 var/list/volume_channels = list() ///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() 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/ZeroSkills(var/forced = 0) for(var/V in SKILLS) for(var/datum/skill/S in SKILLS[V]) if(!skills.Find(S.ID) || forced) skills[S.ID] = SKILL_NONE /datum/preferences/proc/CalculateSkillPoints() used_skillpoints = 0 for(var/V in SKILLS) for(var/datum/skill/S in SKILLS[V]) var/multiplier = 1 switch(skills[S.ID]) if(SKILL_NONE) used_skillpoints += 0 * multiplier if(SKILL_BASIC) used_skillpoints += 1 * multiplier if(SKILL_ADEPT) // secondary skills cost less if(S.secondary) used_skillpoints += 1 * multiplier else used_skillpoints += 3 * multiplier if(SKILL_EXPERT) // secondary skills cost less if(S.secondary) used_skillpoints += 3 * multiplier else used_skillpoints += 6 * multiplier /datum/preferences/proc/GetSkillClass(points) return CalculateSkillClass(points, age) /proc/CalculateSkillClass(points, age) if(points <= 0) return "Unconfigured" // skill classes describe how your character compares in total points points -= min(round((age - 20) / 2.5), 4) // every 2.5 years after 20, one extra skillpoint if(age > 30) points -= round((age - 30) / 5) // every 5 years after 30, one extra skillpoint switch(points) if(-1000 to 3) return "Terrifying" if(4 to 6) return "Below Average" if(7 to 10) return "Average" if(11 to 14) return "Above Average" if(15 to 18) return "Exceptional" if(19 to 24) return "Genius" if(24 to 1000) return "God" /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() var/dat = "
" if(path) dat += "Slot - " dat += "Load slot - " dat += "Save slot - " dat += "Reload slot - " dat += "Reset slot - " dat += "Copy slot" else dat += "Please create an account to save your preferences." dat += "
" dat += player_setup.header() dat += "

" dat += player_setup.content(user) dat += "" //user << browse(dat, "window=preferences;size=635x736") winshow(user, "preferences_window", TRUE) var/datum/browser/popup = new(user, "preferences_browser", "Character Setup", 800, 800) popup.set_content(dat) popup.open(FALSE) // Skip registring onclose on the browser pane onclose(user, "preferences_window", src) // We want to register on the window itself /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.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(!istype(user, /mob/new_player)) 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) //ChompEDIT - usr removal return 1 /datum/preferences/Topic(href, list/href_list) if(..()) return 1 if(href_list["save"]) save_character() save_preferences() else if(href_list["reload"]) load_preferences() load_character() attempt_vr(client.prefs_vr,"load_vore","") //VOREStation Edit 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() 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(be_random_name) 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_to_human(character, read_preference(preference.type)) // VOREStation Edit - 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() if(LAZYLEN(character.descriptors)) for(var/entry in body_descriptors) character.descriptors[entry] = body_descriptors[entry] /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() load_character(slotnum) attempt_vr(user.client?.prefs_vr,"load_vore","") //VOREStation Edit 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)