GLOBAL_LIST_EMPTY(preferences_datums)
/datum/preferences
var/client/parent
//doohickeys for savefiles
var/path
var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used
var/max_save_slots = 3
//non-preference stuff
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 = "#c43b23"
var/asaycolor = "#ff4500" //This won't change the color for current admins, only incoming ones.
/// If we spawn an ERT as an admin and choose to spawn as the briefing officer, we'll be given this outfit
var/brief_outfit = /datum/outfit/centcom/commander
var/enable_tips = TRUE
var/tip_delay = 500 //tip delay in milliseconds
//Antag preferences
var/list/be_special = list() //Special role selection
var/tmp/old_be_special = 0 //Bitflag version of be_special, used to update old savefiles and nothing more
//If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were,
//autocorrected this round, not that you'd need to check that.
var/UI_style = null
var/buttons_locked = FALSE
var/hotkeys = TRUE
///Runechat preference. If true, certain messages will be displayed on the map, not ust on the chat area. Boolean.
var/chat_on_map = TRUE
///Limit preference on the size of the message. Requires chat_on_map to have effect.
var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH
///Whether non-mob messages will be displayed, such as machine vendor announcements. Requires chat_on_map to have effect. Boolean.
var/see_chat_non_mob = TRUE
///Whether emotes will be displayed on runechat. Requires chat_on_map to have effect. Boolean.
var/see_rc_emotes = TRUE
// Custom Keybindings
var/list/key_bindings = list()
var/tgui_fancy = TRUE
var/tgui_lock = FALSE
var/windowflashing = TRUE
var/toggles = TOGGLES_DEFAULT
var/db_flags
var/chat_toggles = TOGGLES_DEFAULT_CHAT
var/ghost_form = "ghost"
var/ghost_orbit = GHOST_ORBIT_CIRCLE
var/ghost_accs = GHOST_ACCS_DEFAULT_OPTION
var/ghost_others = GHOST_OTHERS_DEFAULT_OPTION
var/ghost_hud = 1
var/inquisitive_ghost = 1
var/allow_midround_antag = 1
var/preferred_map = null
var/pda_style = MONO
var/pda_color = "#808000"
var/uses_glasses_colour = 0
//character preferences
var/slot_randomized //keeps track of round-to-round randomization of the character slot, prevents overwriting
var/real_name //our character's name
var/gender = MALE //gender of character (well duh)
var/age = 30 //age of character
var/underwear = "Nude" //underwear type
var/underwear_color = "000" //underwear color
var/undershirt = "Nude" //undershirt type
var/socks = "Nude" //socks type
var/backpack = DBACKPACK //backpack type
var/jumpsuit_style = PREF_SUIT //suit/skirt
var/hairstyle = "Bald" //Hair type
var/hair_color = "000" //Hair color
var/facial_hairstyle = "Shaved" //Face hair type
var/facial_hair_color = "000" //Facial hair color
var/skin_tone = "caucasian1" //Skin color
var/eye_color = "000" //Eye color
var/datum/species/pref_species = new /datum/species/human() //Mutant race
var/list/features = list("mcolor" = "FFF", "ethcolor" = "9c3030", "tail_lizard" = "Smooth", "tail_human" = "None", "snout" = "Round", "horns" = "None", "ears" = "None", "wings" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "moth_wings" = "Plain", "moth_antennae" = "Plain", "moth_markings" = "None")
var/list/randomise = list(
RANDOM_UNDERWEAR = TRUE,
RANDOM_UNDERWEAR_COLOR = TRUE,
RANDOM_UNDERSHIRT = TRUE,
RANDOM_SOCKS = TRUE,
RANDOM_BACKPACK = TRUE,
RANDOM_JUMPSUIT_STYLE = TRUE,
RANDOM_HAIRSTYLE = TRUE,
RANDOM_HAIR_COLOR = TRUE,
RANDOM_FACIAL_HAIRSTYLE = TRUE,
RANDOM_FACIAL_HAIR_COLOR = TRUE,
RANDOM_SKIN_TONE = TRUE,
RANDOM_EYE_COLOR = TRUE,
)
var/phobia = "spiders"
var/list/custom_names = list()
var/preferred_ai_core_display = "Blue"
var/prefered_security_department = SEC_DEPT_NONE
//Quirk list
var/list/all_quirks = list()
//Job preferences 2.0 - indexed by job title , no key or value implies never
var/list/job_preferences = list()
// Want randomjob if preferences already filled - Donkie
var/joblessrole = BERANDOMJOB //defaults to 1 for fewer assistants
// 0 = character settings, 1 = game preferences
var/current_tab = 0
var/unlock_content = 0
var/list/ignoring = list()
var/clientfps = -1
var/parallax
///Do we show screentips, if so, how big?
var/screentip_pref = TRUE
///Color of screentips at top of screen
var/screentip_color = "#ffd391"
///Do we show item hover outlines?
var/itemoutline_pref = TRUE
var/ambientocclusion = TRUE
///Should we automatically fit the viewport?
var/auto_fit_viewport = FALSE
///Should we be in the widescreen mode set by the config?
var/widescreenpref = TRUE
///What size should pixels be displayed as? 0 is strech to fit
var/pixel_size = 0
///What scaling method should we use? Distort means nearest neighbor
var/scaling_method = SCALING_METHOD_DISTORT
var/uplink_spawn_loc = UPLINK_PDA
///The playtime_reward_cloak variable can be set to TRUE from the prefs menu only once the user has gained over 5K playtime hours. If true, it allows the user to get a cool looking roundstart cloak.
var/playtime_reward_cloak = FALSE
var/list/exp = list()
var/list/menuoptions
var/action_buttons_screen_locs = list()
///Someone thought we were nice! We get a little heart in OOC until we join the server past the below time (we can keep it until the end of the round otherwise)
var/hearted
///If we have a hearted commendations, we honor it every time the player loads preferences until this time has been passed
var/hearted_until
/// Agendered spessmen can choose whether to have a male or female bodytype
var/body_type
/// If we have persistent scars enabled
var/persistent_scars = TRUE
///If we want to broadcast deadchat connect/disconnect messages
var/broadcast_login_logout = TRUE
///What outfit typepaths we've favorited in the SelectEquipment menu
var/list/favorite_outfits = list()
///If TRUE, we replace the flash effect from flashes with a solid black screen
var/darkened_flash = FALSE
/datum/preferences/New(client/C)
parent = C
for(var/custom_name_id in GLOB.preferences_custom_names)
custom_names[custom_name_id] = get_default_name(custom_name_id)
UI_style = GLOB.available_ui_styles[1]
if(istype(C))
if(!IsGuestKey(C.key))
load_path(C.ckey)
unlock_content = C.IsByondMember()
if(unlock_content)
max_save_slots = 8
var/loaded_preferences_successfully = load_preferences()
if(loaded_preferences_successfully)
if(load_character())
return
//we couldn't load character data so just randomize the character appearance + name
randomise_appearance_prefs() //let's create a random character then - rather than a fat, bald and naked man.
key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys
C?.set_macros()
real_name = pref_species.random_name(gender,1)
if(!loaded_preferences_successfully)
save_preferences()
save_character() //let's save this new random character so it doesn't keep generating new ones.
menuoptions = list()
return
#define APPEARANCE_CATEGORY_COLUMN "
"
#define MAX_MUTANT_ROWS 4
/datum/preferences/proc/ShowChoices(mob/user)
if(!user || !user.client)
return
if(slot_randomized)
load_character(default_slot) // Reloads the character slot. Prevents random features from overwriting the slot if saved.
slot_randomized = FALSE
update_preview_icon()
var/list/dat = list("
"
if (user.client.get_exp_living(TRUE) >= PLAYTIME_VETERAN)
dat += " Don The Ultimate Gamer Cloak?: [(playtime_reward_cloak) ? "Enabled" : "Disabled"] "
var/use_skintones = pref_species.use_skintones
if(use_skintones)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Skin Tone
"
dat += "[skin_tone]"
dat += "[(randomise[RANDOM_SKIN_TONE]) ? "Lock" : "Unlock"]"
dat += " "
var/mutant_colors
if((MUTCOLORS in pref_species.species_traits) || (MUTCOLORS_PARTSONLY in pref_species.species_traits))
if(!use_skintones)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Mutant Color
"
dat += "Change "
mutant_colors = TRUE
if(istype(pref_species, /datum/species/ethereal)) //not the best thing to do tbf but I dont know whats better.
if(!use_skintones)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Ethereal Color
"
dat += "Change "
if((EYECOLOR in pref_species.species_traits) && !(NOEYESPRITES in pref_species.species_traits))
if(!use_skintones && !mutant_colors)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
"
dat += "[features["tail_lizard"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.external_organs[/obj/item/organ/external/snout])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Snout
"
dat += "[features["snout"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.external_organs[/obj/item/organ/external/horns])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Horns
"
dat += "[features["horns"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.external_organs[/obj/item/organ/external/frills])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Frills
"
dat += "[features["frills"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.mutant_bodyparts["spines"])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Spines
"
dat += "[features["spines"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.mutant_bodyparts["body_markings"])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Body Markings
"
dat += "[features["body_markings"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.mutant_bodyparts["legs"])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Legs
"
dat += "[features["legs"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.external_organs[/obj/item/organ/external/wings/moth])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Moth wings
"
dat += "[features["moth_wings"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.external_organs[/obj/item/organ/external/antennae])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Moth antennae
"
dat += "[features["moth_antennae"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.mutant_bodyparts["moth_markings"])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Moth markings
"
dat += "[features["moth_markings"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.mutant_bodyparts["tail_human"])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Tail
"
dat += "[features["tail_human"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(pref_species.mutant_bodyparts["ears"])
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Ears
"
dat += "[features["ears"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
//Adds a thing to select which phobia because I can't be assed to put that in the quirks window
if("Phobia" in all_quirks)
dat += "
Phobia
"
dat += "[phobia] "
if(CONFIG_GET(flag/join_with_mutant_humans))
if(pref_species.mutant_bodyparts["wings"] && GLOB.r_wings_list.len >1)
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
dat += "
Wings
"
dat += "[features["wings"]] "
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
if(mutant_category)
dat += ""
mutant_category = 0
dat += "
"
if(is_banned_from(user.ckey, ROLE_SYNDICATE))
dat += "You are banned from antagonist roles. "
src.be_special = list()
for (var/special_role in GLOB.special_roles)
if(is_banned_from(user.ckey, special_role))
dat += "Be [capitalize(special_role)]:BANNED "
else
var/days_remaining = null
if(CONFIG_GET(flag/use_age_restriction_for_jobs)) //If it's a game mode antag, check if the player meets the minimum age
var/days_needed = GLOB.special_roles[special_role]
days_remaining = user.client?.get_remaining_days(days_needed)
if(days_remaining)
dat += "Be [capitalize(special_role)]: \[IN [days_remaining] DAYS] "
else
dat += "Be [capitalize(special_role)]:[(special_role in be_special) ? "Enabled" : "Disabled"] "
dat += " "
dat += "Midround Antagonist:[(toggles & MIDROUND_ANTAG) ? "Enabled" : "Disabled"] "
dat += "
"
var/timegate = CONFIG_GET(number/auto_deadmin_timegate)
if(timegate)
dat += "Noted roles will automatically deadmin during the first [FLOOR(timegate / 600, 1)] minutes of the round, and will defer to individual preferences after. "
if(CONFIG_GET(flag/auto_deadmin_players) && !timegate)
dat += "Always Deadmin: FORCED "
else
dat += "Always Deadmin: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_ALWAYS)?"Enabled":"Disabled"] "
if(!(toggles & DEADMIN_ALWAYS))
dat += " "
if(!CONFIG_GET(flag/auto_deadmin_antagonists) || (CONFIG_GET(flag/auto_deadmin_antagonists) && !timegate))
dat += "As Antag: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_ANTAGONIST)?"Deadmin":"Keep Admin"] "
else
dat += "As Antag: FORCED "
if(!CONFIG_GET(flag/auto_deadmin_heads) || (CONFIG_GET(flag/auto_deadmin_heads) && !timegate))
dat += "As Command: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_HEAD)?"Deadmin":"Keep Admin"] "
else
dat += "As Command: FORCED "
if(!CONFIG_GET(flag/auto_deadmin_security) || (CONFIG_GET(flag/auto_deadmin_security) && !timegate))
dat += "As Security: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_SECURITY)?"Deadmin":"Keep Admin"] "
else
dat += "As Security: FORCED "
if(!CONFIG_GET(flag/auto_deadmin_silicons) || (CONFIG_GET(flag/auto_deadmin_silicons) && !timegate))
dat += "As Silicon: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_SILICON)?"Deadmin":"Keep Admin"] "
else
dat += "As Silicon: FORCED "
dat += "
"
dat += "
"
if(3) // Custom keybindings
// Create an inverted list of keybindings -> key
var/list/user_binds = list()
for (var/key in key_bindings)
for(var/kb_name in key_bindings[key])
user_binds[kb_name] += list(key)
var/list/kb_categories = list()
// Group keybinds by category
for (var/name in GLOB.keybindings_by_name)
var/datum/keybinding/kb = GLOB.keybindings_by_name[name]
kb_categories[kb.category] += list(kb)
dat += ""
for (var/category in kb_categories)
dat += "
[category]
"
for (var/i in kb_categories[category])
var/datum/keybinding/kb = i
if(!length(user_binds[kb.name]) || user_binds[kb.name][1] == "Unbound")
dat += " Unbound"
var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
dat += " "
else
var/bound_key = user_binds[kb.name][1]
dat += " [bound_key]"
for(var/bound_key_index in 2 to length(user_binds[kb.name]))
bound_key = user_binds[kb.name][bound_key_index]
dat += " | [bound_key]"
if(length(user_binds[kb.name]) < MAX_KEYS_PER_KEYBIND)
dat += "| Add Secondary"
var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
dat += " "
dat += "
" // Easier to press up here.
HTML += ""
HTML += "
" // Table within a table for alignment, also allows you to easily add more colomns.
HTML += "
"
var/index = -1
//The job before the current job. I only use this to get the previous jobs color when I'm filling in blank rows.
var/datum/job/lastJob
var/datum/job/overflow_role = SSjob.GetJobType(SSjob.overflow_role)
for(var/datum/job/job as anything in SSjob.joinable_occupations)
index += 1
if(index >= limit)
width += widthPerColumn
if((index < limit) && (lastJob != null))
// Fills the rest of the cells with the last job's selection color.
for(var/i = 0, i < (limit - index), i += 1)
HTML += "
"
continue
if(job.job_flags & JOB_BOLD_SELECT_TEXT)//Bold head jobs
HTML += "[rank]"
else
HTML += "[rank]"
HTML += "
"
var/prefLevelLabel = "ERROR"
var/prefLevelColor = "pink"
var/prefUpperLevel = -1 // level to assign on left click
var/prefLowerLevel = -1 // level to assign on right click
switch(job_preferences[job.title])
if(JP_HIGH)
prefLevelLabel = "High"
prefLevelColor = "slateblue"
prefUpperLevel = 4
prefLowerLevel = 2
if(JP_MEDIUM)
prefLevelLabel = "Medium"
prefLevelColor = "green"
prefUpperLevel = 1
prefLowerLevel = 3
if(JP_LOW)
prefLevelLabel = "Low"
prefLevelColor = "orange"
prefUpperLevel = 2
prefLowerLevel = 4
else
prefLevelLabel = "NEVER"
prefLevelColor = "red"
prefUpperLevel = 3
prefLowerLevel = 1
HTML += ""
if(rank == overflow_role.title)//Overflow is special
if(job_preferences[overflow_role.title] == JP_LOW)
HTML += "Yes"
else
HTML += "No"
HTML += "
"
continue
HTML += "[prefLevelLabel]"
HTML += ""
for(var/i = 1, i < (limit - index), i += 1) // Finish the column so it is even
HTML += "
 
 
"
HTML += "
"
HTML += "
"
var/message = "Be an [overflow_role.title] if preferences unavailable"
if(joblessrole == BERANDOMJOB)
message = "Get random job if preferences unavailable"
else if(joblessrole == RETURNTOLOBBY)
message = "Return to lobby if preferences unavailable"
HTML += "