GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts to play these roles ROLE_PAI = 0, ROLE_GUARDIAN = 0, ROLE_TRAITOR = 7, ROLE_CHANGELING = 14, ROLE_WIZARD = 14, ROLE_REV = 14, ROLE_VAMPIRE = 14, ROLE_BLOB = 14, ROLE_REVENANT = 14, ROLE_OPERATIVE = 21, ROLE_CULTIST = 21, ROLE_ALIEN = 21, ROLE_DEMON = 21, ROLE_SENTIENT = 21, ROLE_ELITE = 21, // ROLE_GANG = 21, ROLE_ABDUCTOR = 30 )) /proc/player_old_enough_antag(client/C, role) if(available_in_days_antag(C, role)) return 0 //available_in_days>0 = still some days required = player not old enough if(role_available_in_playtime(C, role)) return 0 //available_in_playtime>0 = still some more playtime required = they are not eligible return 1 /proc/available_in_days_antag(client/C, role) if(!C) return 0 if(!role) return 0 if(!GLOB.configuration.gamemode.antag_account_age_restriction) return 0 if(!isnum(C.player_age)) return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced var/minimal_player_age_antag = GLOB.special_role_times[num2text(role)] if(!isnum(minimal_player_age_antag)) return 0 return max(0, minimal_player_age_antag - C.player_age) /proc/check_client_age(client/C, days) // If days isn't provided, returns the age of the client. If it is provided, it returns the days until the player_age is equal to or greater than the days variable if(!days) return C.player_age else return max(0, days - C.player_age) /datum/preferences var/client/parent //doohickeys for savefiles var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used var/max_save_slots = MAX_SAVE_SLOTS var/max_gear_slots = 0 //game-preferences var/lastchangelog = "1" //Saved changlog timestamp (unix epoch) to detect if there was a change. Dont set this to 0 unless you want the last changelog date to be 4x longer than the expected lifespan of the universe. var/lastchangelog_2 = "1" // Clone of the above var for viewing changes since last connection. This is never overriden. Yes it needs to exist. var/exp var/ooccolor = "#b82e00" var/list/be_special = list() //Special role selection var/UI_style = "Midnight" var/toggles = TOGGLES_DEFAULT var/toggles2 = TOGGLES_2_DEFAULT // Created because 1 column has a bitflag limit of 24 (BYOND limitation not MySQL) var/sound = SOUND_DEFAULT var/UI_style_color = "#ffffff" var/UI_style_alpha = 255 var/clientfps = 63 var/atklog = ATKLOG_ALL var/fuid // forum userid /// Volume mixer, indexed by channel as TEXT (numerical indexes will not work). Volume goes from 0 to 100. var/list/volume_mixer = list( "1012" = 100, // CHANNEL_GENERAL //Note: This should stay on top because order in this list defines order of sliders in mixer's interface. "1024" = 100, // CHANNEL_LOBBYMUSIC "1023" = 100, // CHANNEL_ADMIN "1022" = 100, // CHANNEL_VOX "1021" = 100, // CHANNEL_JUKEBOX "1020" = 100, // CHANNEL_HEARTBEAT "1019" = 100, // CHANNEL_BUZZ "1018" = 100, // CHANNEL_AMBIENCE "1017" = 100, // CHANNEL_ENGINE "1016" = 100, // CHANNEL_FIREALARM "1015" = 100, // CHANNEL_ASH_STORM "1014" = 100, // CHANNEL_RADIO_NOISE "1013" = 100 // CHANNEL_BOSS_MUSIC ) /// The volume mixer save timer handle. Used to debounce the DB call to save, to avoid spamming. var/volume_mixer_saving = null // BYOND membership var/unlock_content = 0 var/gear_tab = "General" // Parallax var/parallax = PARALLAX_HIGH /// 2FA status var/_2fa_status = _2FA_DISABLED ///Screentip Mode, in pixels. 8 is small, 15 is mega big, 0 is off. var/screentip_mode = 8 ///Color of screentips at top of screen var/screentip_color = "#ffd391" /// Did we load successfully? var/successful_load = FALSE /// Have we loaded characters already? var/characters_loaded = FALSE // 0 = character settings, 1 = game preferences, 2 = loadout var/current_tab = TAB_CHAR /// List of all character saves we have. This is indexed based on the slot number var/list/datum/character_save/character_saves = list() /// The current active character var/datum/character_save/active_character /// How dark things are if client is a ghost, 0-255 var/ghost_darkness_level = LIGHTING_PLANE_ALPHA_VISIBLE /// Colourblind mode var/colourblind_mode = COLOURBLIND_MODE_NONE /// Active keybinds (currently useable by the mob/client) var/list/datum/keybindings = list() /// Keybinding overrides ("name" => ["key"...]) var/list/keybindings_overrides = null /// Player's region override for routing optimisation var/server_region = null /// List of admin ckeys this player wont hear sounds from var/list/admin_sound_ckey_ignore = list() /// View range preference for this client var/viewrange = DEFAULT_CLIENT_VIEWSIZE /datum/preferences/New(client/C, datum/db_query/Q) // Process our query parent = C max_gear_slots = GLOB.configuration.general.base_loadout_points parent?.set_macros() if(!SSdbcore.IsConnected()) init_keybindings() //we want default keybinds, even if DB is not connected return // Bail if(istype(C)) if(!IsGuestKey(C.key)) unlock_content = C.IsByondMember() if(unlock_content) max_save_slots = MAX_SAVE_SLOTS_MEMBER successful_load = load_preferences(Q) if(!successful_load) to_chat(C, "Your preferences failed to load. Please inform the server host immediately.") /datum/preferences/proc/color_square(colour) return "___" // Hello I am a proc full of snowflake species checks how are you /datum/preferences/proc/ShowChoices(mob/user) if(!user || !user.client) return if(SSatoms.initialized) // DNA won't be set up on our dummy mob if it's not ready active_character.update_preview_icon() user << browse_rsc(active_character.preview_icon_front, "previewicon.png") user << browse_rsc(active_character.preview_icon_side, "previewicon2.png") var/list/dat = list() dat += "
| "
dat += "Name: "
dat += "[active_character.real_name]"
dat += "(Randomize)"
dat += "(Always Randomize) " dat += " | "
dat += " " dat += "Load slot - " dat += "Save slot" if(active_character.from_db) dat += "- Clear slot" dat += " |
"
dat += "Identity" dat += "Gender: [active_character.gender == MALE ? "Male" : (active_character.gender == FEMALE ? "Female" : "Genderless")]" dat += "" dat += "Age: [active_character.age] " dat += "Body: (®) " dat += "Species: [active_character.species] " if(active_character.species == "Vox") // Purge these bastards dat += "N2 Tank: [active_character.speciesprefs ? "Large N2 Tank" : "Specialized N2 Tank"] " if(active_character.species == "Plasmaman") dat += "Plasma Tank: [active_character.speciesprefs ? "Large Plasma Tank" : "Specialized Plasma Tank"] " if(active_character.species == "Grey") dat += "Wingdings: Set in disabilities " dat += "Voice Translator: [active_character.speciesprefs ? "Yes" : "No"] " dat += "Secondary Language: [active_character.language] " if(S.autohiss_basic_map) dat += "Auto-accent: [active_character.autohiss_mode == AUTOHISS_FULL ? "Full" : (active_character.autohiss_mode == AUTOHISS_BASIC ? "Basic" : "Off")] " if(NO_BLOOD in S.species_traits) // unique blood type for species with no_blood/unique_blood active_character.b_type = "None" else if(active_character.species == "Slime People") active_character.b_type = "Slime Jelly" else if(active_character.b_type == "None" || active_character.b_type == "Slime Jelly") active_character.b_type = pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") dat += "Blood Type: [active_character.b_type] " if(S.bodyflags & (HAS_SKIN_TONE|HAS_ICON_SKIN_TONE)) dat += "Skin Tone: [S.bodyflags & HAS_ICON_SKIN_TONE ? "[active_character.s_tone]" : "[-active_character.s_tone + 35]/220"] " dat += "Disabilities: \[Set\] " dat += "Nanotrasen Relation: [active_character.nanotrasen_relation] " dat += "Physique: [active_character.physique] " dat += "Height: [active_character.height] " dat += "Set Flavor Text " if(length(active_character.flavor_text) <= 40) if(!length(active_character.flavor_text)) dat += "\[...\] " else dat += "[active_character.flavor_text] " else dat += "[TextPreview(active_character.flavor_text)]... " dat += " Hair & Accessories" if(S.bodyflags & HAS_HEAD_ACCESSORY) //Species that have head accessories. var/headaccessoryname = "Head Accessory: " if(active_character.species == "Unathi") headaccessoryname = "Horns: " dat += "[headaccessoryname]" dat += "[active_character.ha_style] " dat += "Color [color_square(active_character.hacc_colour)]" if(S.bodyflags & HAS_HEAD_MARKINGS) //Species with head markings. dat += "Head Markings: " dat += "[active_character.m_styles["head"]]" dat += "Color [color_square(active_character.m_colours["head"])] " if(S.bodyflags & HAS_BODY_MARKINGS) //Species with body markings/tattoos. dat += "Body Markings: " dat += "[active_character.m_styles["body"]]" dat += "Color [color_square(active_character.m_colours["body"])] " if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings. dat += "Tail Markings: " dat += "[active_character.m_styles["tail"]]" dat += "Color [color_square(active_character.m_colours["tail"])] " if(!(S.bodyflags & BALD)) dat += "Hair: " dat += "[active_character.h_style]" dat += "Color [color_square(active_character.h_colour)]" var/datum/sprite_accessory/temp_hair_style = GLOB.hair_styles_public_list[active_character.h_style] if(temp_hair_style && temp_hair_style.secondary_theme && !temp_hair_style.no_sec_colour) dat += " Color #2 [color_square(active_character.h_sec_colour)]" // Hair gradient dat += " " dat += "- Gradient:" dat += " [active_character.h_grad_style]" dat += " Color [color_square(active_character.h_grad_colour)]" dat += " [active_character.h_grad_alpha]" dat += " " dat += "- Gradient Offset: [active_character.h_grad_offset_x],[active_character.h_grad_offset_y]" dat += " " else active_character.h_style = "Bald" if(!(S.bodyflags & SHAVED)) dat += "Facial Hair: " dat += "[active_character.f_style ? "[active_character.f_style]" : "Shaved"]" dat += "Color [color_square(active_character.f_colour)]" var/datum/sprite_accessory/temp_facial_hair_style = GLOB.facial_hair_styles_list[active_character.f_style] if(temp_facial_hair_style && temp_facial_hair_style.secondary_theme && !temp_facial_hair_style.no_sec_colour) dat += " Color #2 [color_square(active_character.f_sec_colour)]" dat += " " else active_character.f_style = "Shaved" if(!(S.bodyflags & ALL_RPARTS)) dat += "Eyes: " dat += "Color [color_square(active_character.e_colour)] " if((S.bodyflags & HAS_SKIN_COLOR) || ((S.bodyflags & HAS_BODYACC_COLOR) && GLOB.body_accessory_by_species[active_character.species]) || check_rights(R_ADMIN, 0, user)) //admins can always fuck with this, because they are admins dat += "Body Color: " dat += "Color [color_square(active_character.s_colour)] " if(GLOB.body_accessory_by_species[active_character.species] || check_rights(R_ADMIN, 0, user)) dat += "Body Accessory: " dat += "[active_character.body_accessory ? "[active_character.body_accessory]" : "None"] " dat += " | "
dat += "Occupation Choices" dat += "Set Occupation Preferences" if(jobban_isbanned(user, ROLEBAN_RECORDS)) dat += "You are banned from using character records. " else dat += "Character Records " dat += " Limbs" if(S.bodyflags & HAS_ALT_HEADS) //Species with alt heads. dat += "Alternate Head: " dat += "[active_character.alt_head]" dat += "Limbs and Parts: Adjust " if(active_character.species != "Slime People" && active_character.species != "Machine") dat += "Internal Organs: Adjust " //display limbs below var/ind = 0 for(var/name in active_character.organ_data) var/status = active_character.organ_data[name] var/organ_name = null switch(name) if("chest") organ_name = "torso" if("groin") organ_name = "lower body" if("head") organ_name = "head" if("l_arm") organ_name = "left arm" if("r_arm") organ_name = "right arm" if("l_leg") organ_name = "left leg" if("r_leg") organ_name = "right leg" if("l_foot") organ_name = "left foot" if("r_foot") organ_name = "right foot" if("l_hand") organ_name = "left hand" if("r_hand") organ_name = "right hand" if("eyes") organ_name = "eyes" if("ears") organ_name = "ears" if("heart") organ_name = "heart" if("lungs") organ_name = "lungs" if("liver") organ_name = "liver" if("kidneys") organ_name = "kidneys" if(status in list("cyborg", "amputated", "cybernetic")) ++ind if(ind > 1) dat += ", " switch(status) if("cyborg") var/datum/robolimb/R if(active_character.rlimb_data[name] && GLOB.all_robolimbs[active_character.rlimb_data[name]]) R = GLOB.all_robolimbs[active_character.rlimb_data[name]] else R = GLOB.basic_robolimb dat += "\t[R.company] [organ_name] prosthesis" if("amputated") dat += "\tAmputated [organ_name]" if("cybernetic") dat += "\tCybernetic [organ_name]" if(!ind) dat += "\[...\] " else dat += " " dat += " Clothing" if(S.clothing_flags & HAS_UNDERWEAR) dat += "Underwear: [active_character.underwear]" if(S.clothing_flags & HAS_UNDERSHIRT) dat += "Undershirt: [active_character.undershirt] " if(S.clothing_flags & HAS_SOCKS) dat += "Socks: [active_character.socks] " dat += "Backpack Type: [active_character.backbag] " var/datum/species/myspecies = GLOB.all_species[active_character.species] if(!isnull(myspecies)) dat += " Species Information" dat += "Species Description: [myspecies.blurb] " dat += " |
"
dat += "Special Role Settings" if(jobban_isbanned(user, ROLE_SYNDICATE)) dat += "You are banned from special roles." be_special = list() else for(var/i in GLOB.special_roles) if(jobban_isbanned(user, i)) dat += "Be [capitalize(i)]: \[BANNED]" else if(!player_old_enough_antag(user.client, i)) var/available_in_days_antag = available_in_days_antag(user.client, i) var/role_available_in_playtime = get_exp_format(role_available_in_playtime(user.client, i)) if(available_in_days_antag) dat += "Be [capitalize(i)]: \[IN [(available_in_days_antag)] DAYS] " else if(role_available_in_playtime) dat += "Be [capitalize(i)]: \[IN [(role_available_in_playtime)]] " else dat += "Be [capitalize(i)]: \[ERROR] " else var/is_special = (i in src.be_special) dat += "Be [capitalize(i)]:[(is_special) ? "Yes" : "No"] " dat += " |
| [G.display_name] | " else dat += "|||
| [G.display_name] | " dat += "[G.cost] | " if(G.allowed_roles) dat += "Restrictions: " for(var/role in G.allowed_roles) dat += role + " " dat += "" dat += " | [G.description] |
| " for(var/datum/gear_tweak/tweak in G.gear_tweaks) . += " [tweak.get_contents(active_character.get_tweak_metadata(G, tweak))]" . += " | |||
[cat] | |||
| [KB.name] | " dat += "[keys_buttons][(length(keys) < 5) ? "+ | " : ""]" dat += "Reset to Default Clear | " if(KB.category == KB_CATEGORY_EMOTE_CUSTOM) var/datum/keybinding/custom/custom_emote_keybind = kb if(custom_emote_keybind.donor_exclusive && !(user.client.donator_level || user.client.holder || unlock_content)) dat += "|
| The use of this emote is restricted to patrons and byond members. | " dat += "|||
| [custom_emote_keybind.default_emote_text] | " else dat += "\"[active_character.real_name] [emote_text]\" | " dat += "Change Text | " dat += "Reset to Default | " dat += "