diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm index 281d7e48f7..487499f697 100644 --- a/code/ATMOSPHERICS/components/unary/vent_pump.dm +++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm @@ -165,14 +165,14 @@ if(welded) vent_icon += "weld" - playsound(src, stop_sound, 25, ignore_walls = FALSE, preference = /datum/client_preference/air_pump_noise) + playsound(src, stop_sound, 25, ignore_walls = FALSE, preference = /datum/preference/toggle/air_pump_noise) else if(!use_power || !node || (stat & (NOPOWER|BROKEN))) vent_icon += "off" - playsound(src, stop_sound, 25, ignore_walls = FALSE, preference = /datum/client_preference/air_pump_noise) + playsound(src, stop_sound, 25, ignore_walls = FALSE, preference = /datum/preference/toggle/air_pump_noise) else vent_icon += "[pump_direction ? "out" : "in"]" - playsound(src, start_sound, 25, ignore_walls = FALSE, preference = /datum/client_preference/air_pump_noise) + playsound(src, start_sound, 25, ignore_walls = FALSE, preference = /datum/preference/toggle/air_pump_noise) add_overlay(icon_manager.get_atmos_icon("device", , , vent_icon)) diff --git a/code/__defines/logging.dm b/code/__defines/logging.dm index 07a121d9bb..c0e2ce9977 100644 --- a/code/__defines/logging.dm +++ b/code/__defines/logging.dm @@ -1,2 +1,3 @@ #define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text) #define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text) +#define READ_FILE(file, text) DIRECT_INPUT(file, text) diff --git a/code/__defines/preferences.dm b/code/__defines/preferences.dm index ba0abca8cf..14adeb62bb 100644 --- a/code/__defines/preferences.dm +++ b/code/__defines/preferences.dm @@ -15,3 +15,38 @@ #define MULTILINGUAL_MODE_MAX 4 #define SAVE_RESET -1 + +// Values for /datum/preference/savefile_identifier +/// This preference is character specific. +#define PREFERENCE_CHARACTER "character" +/// This preference is account specific. +#define PREFERENCE_PLAYER "player" + +// Values for /datum/preferences/current_tab +/// Open the character preference window +#define PREFERENCE_TAB_CHARACTER_PREFERENCES 0 + +/// Open the game preferences window +#define PREFERENCE_TAB_GAME_PREFERENCES 1 + +/// These will be shown in the character sidebar, but at the bottom. +#define PREFERENCE_CATEGORY_FEATURES "features" + +/// Any preferences that will show to the sides of the character in the setup menu. +#define PREFERENCE_CATEGORY_CLOTHING "clothing" + +/// Preferences that will be put into the 3rd list, and are not contextual. +#define PREFERENCE_CATEGORY_NON_CONTEXTUAL "non_contextual" + +/// Will be put under the game preferences window. +#define PREFERENCE_CATEGORY_GAME_PREFERENCES "game_preferences" + +/// These will show in the list to the right of the character preview. +#define PREFERENCE_CATEGORY_SECONDARY_FEATURES "secondary_features" + +/// These are preferences that are supplementary for main features, +/// such as hair color being affixed to hair. +#define PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES "supplemental_features" + +/// These preferences will not be rendered on the preferences page, and are practically invisible unless specifically rendered. Used for quirks, currently. +#define PREFERENCE_CATEGORY_MANUALLY_RENDERED "manually_rendered_features" diff --git a/code/__defines/text.dm b/code/__defines/text.dm index 4b520e5771..b48754dd32 100644 --- a/code/__defines/text.dm +++ b/code/__defines/text.dm @@ -8,3 +8,6 @@ * because DM does it so it copies until the char BEFORE the `end` arg, so we need to bump `end` by 1 in these cases. */ #define PREVENT_CHARACTER_TRIM_LOSS(integer) (integer + 1) + +/// Simply removes the < and > characters, and limits the length of the message. +#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), "")) diff --git a/code/_global_vars/_regexes.dm b/code/_global_vars/_regexes.dm index 46e78a28fa..1d92bc8c52 100644 --- a/code/_global_vars/_regexes.dm +++ b/code/_global_vars/_regexes.dm @@ -2,3 +2,8 @@ GLOBAL_DATUM_INIT(is_http_protocol, /regex, regex("^https?://")) GLOBAL_DATUM_INIT(is_valid_url, /regex, regex("((?:https://)\[-a-zA-Z0-9@:%._+~#=]{1,256}.\[-a-zA-Z0-9@:%._+~#=]{1,256}\\b(?:\[-a-zA-Z0-9@():%_+.,~#?&/=]*\[^.,!?:; ()<>{}\\[]\n\"'ยด`]))", "gm")) + +//All < and > characters +GLOBAL_DATUM_INIT(angular_brackets, /regex, regex(@"[<>]", "g")) + +GLOBAL_DATUM_INIT(is_color, /regex, regex("^#\[0-9a-fA-F]{6}$")) diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm index 5b947eab4d..22cb789e21 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -257,7 +257,7 @@ return R // radio, true, false, what's the difference /mob/observer/dead/can_hear_radio(var/list/hearturfs) - return is_preference_enabled(/datum/client_preference/ghost_radio) + return client?.prefs?.read_preference(/datum/preference/toggle/ghost_radio) //Uses dview to quickly return mobs and objects in view, @@ -311,10 +311,10 @@ if(M && M.stat == DEAD && remote_ghosts && !M.forbid_seeing_deadchat) switch(type) if(1) //Audio messages use ghost_ears - if(M.is_preference_enabled(/datum/client_preference/ghost_ears)) + if(M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) mobs |= M if(2) //Visual messages use ghost_sight - if(M.is_preference_enabled(/datum/client_preference/ghost_sight)) + if(M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_sight)) mobs |= M //For objects below the top level who still want to hear diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm index f2b4818ca8..a7274f0d09 100644 --- a/code/_helpers/logging.dm +++ b/code/_helpers/logging.dm @@ -47,7 +47,7 @@ WRITE_LOG(debug_log, "DEBUG: [sanitize(text)]") for(var/client/C in GLOB.admins) - if(C.is_preference_enabled(/datum/client_preference/debug/show_debug_logs)) + if(C.prefs?.read_preference(/datum/preference/toggle/show_debug_logs)) to_chat(C, type = MESSAGE_TYPE_DEBUG, html = "DEBUG: [text]") diff --git a/code/_helpers/sanitize_values.dm b/code/_helpers/sanitize_values.dm index c74176043c..7cc687d7ce 100644 --- a/code/_helpers/sanitize_values.dm +++ b/code/_helpers/sanitize_values.dm @@ -6,6 +6,13 @@ return number return default +/proc/sanitize_float(number, min=0, max=1, accuracy=1, default=0) + if(isnum(number)) + number = round(number, accuracy) + if(round(min, accuracy) <= number && number <= round(max, accuracy)) + return number + return default + // Checks if the given input is a valid list index; returns true/false and doesn't change anything. /proc/is_valid_index(input, list/given_list) if(!isnum(input)) diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index cb2d51a710..24f8d323a8 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -353,7 +353,7 @@ GLOBAL_LIST_EMPTY(text_tag_cache) /proc/create_text_tag(var/tagname, var/tagdesc = tagname, var/client/C = null) - if(!(C && C.is_preference_enabled(/datum/client_preference/chat_tags))) + if(!(C && C.prefs?.read_preference(/datum/preference/toggle/chat_tags))) return tagdesc if(!GLOB.text_tag_cache[tagname]) var/datum/asset/spritesheet/chatassets = get_asset_datum(/datum/asset/spritesheet/chat) @@ -363,7 +363,7 @@ GLOBAL_LIST_EMPTY(text_tag_cache) return GLOB.text_tag_cache[tagname] /proc/create_text_tag_old(var/tagname, var/tagdesc = tagname, var/client/C = null) - if(!(C && C.is_preference_enabled(/datum/client_preference/chat_tags))) + if(!(C && C.prefs?.read_preference(/datum/preference/toggle/chat_tags))) return tagdesc return "[tagdesc]" diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm index 407d4368f7..69632d1cfd 100644 --- a/code/_helpers/type2type.dm +++ b/code/_helpers/type2type.dm @@ -590,3 +590,20 @@ /// for use inside of browse() calls to html assets that might be loaded on a cdn. /proc/url2htmlloader(url) return {""} + +/// Returns a list with all keys turned into paths +/proc/text2path_list(list/L) + . = list() + for(var/key in L) + var/path = key + if(!ispath(path)) + path = text2path(key) + if(!isnull(L[path])) + .[path] = L[path] + continue + if(!isnull(L[key])) + .[path] = L[key] + continue + if(!isnull(path)) + . += path + diff --git a/code/_macros.dm b/code/_macros.dm index b37d24fa0d..2ff9da5e76 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -26,6 +26,7 @@ // From TG, might be useful to have. // Didn't port SEND_TEXT() since to_chat() appears to serve the same purpose. #define DIRECT_OUTPUT(A, B) A << B +#define DIRECT_INPUT(A, B) A >> B #define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image) #define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound) //#define WRITE_LOG is in logging.dm diff --git a/code/controllers/subsystems/ping.dm b/code/controllers/subsystems/ping.dm index 59651caa36..ebdc23e3e1 100644 --- a/code/controllers/subsystems/ping.dm +++ b/code/controllers/subsystems/ping.dm @@ -30,19 +30,18 @@ SUBSYSTEM_DEF(ping) var/client/client = currentrun[currentrun.len] currentrun.len-- - if(client) - if(!client.is_preference_enabled(/datum/client_preference/vchat_enable)) - winset(client, "output", "on-show=&is-disabled=0&is-visible=1") - winset(client, "browseroutput", "is-disabled=1;is-visible=0") - client.tgui_panel.oldchat = TRUE + if(!client?.prefs?.read_preference(/datum/preference/toggle/vchat_enable)) + winset(client, "output", "on-show=&is-disabled=0&is-visible=1") + winset(client, "browseroutput", "is-disabled=1;is-visible=0") + client.tgui_panel.oldchat = TRUE - if (client?.tgui_panel?.is_ready()) - // Send a soft ping - client.tgui_panel.window.send_message("ping/soft", list( - // Slightly less than the subsystem timer (somewhat arbitrary) - // to prevent incoming pings from resetting the afk state - "afk" = client.is_afk(3.5 SECONDS), - )) + if (client?.tgui_panel?.is_ready()) + // Send a soft ping + client.tgui_panel.window.send_message("ping/soft", list( + // Slightly less than the subsystem timer (somewhat arbitrary) + // to prevent incoming pings from resetting the afk state + "afk" = client.is_afk(3.5 SECONDS), + )) if (MC_TICK_CHECK) return diff --git a/code/datums/browser.dm b/code/datums/browser.dm index e38b78ab4d..4aac042789 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -27,7 +27,7 @@ if (nref) ref = nref // If a client exists, but they have disabled fancy windowing, disable it! - if(user && user.client && !user.client.is_preference_enabled(/datum/client_preference/browser_style)) + if(!user?.client?.prefs?.read_preference(/datum/preference/toggle/browser_style)) return add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm index 0af253b885..bf33ea8f6b 100644 --- a/code/datums/chat_message.dm +++ b/code/datums/chat_message.dm @@ -104,7 +104,7 @@ var/list/runechat_image_cache = list() owned_by = owner.client RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(unregister_qdel_self)) // this should only call owned_by if the client is destroyed - var/extra_length = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) + var/extra_length = owned_by.prefs?.read_preference(/datum/preference/toggle/runechat_long_messages) var/maxlen = extra_length ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH var/msgwidth = extra_length ? CHAT_MESSAGE_EXT_WIDTH : CHAT_MESSAGE_WIDTH @@ -244,10 +244,10 @@ var/list/runechat_image_cache = list() if(QDELETED(speaker)) return // Doesn't want to hear - if(ismob(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_mob)) + if(ismob(speaker) && !client.prefs?.read_preference(/datum/preference/toggle/runechat_mob)) return // I know the pref is 'obj' but people dunno what turfs are - else if(!client.is_preference_enabled(/datum/client_preference/runechat_obj)) + else if(!client.prefs?.read_preference(/datum/preference/toggle/runechat_obj)) return // Incapable of receiving @@ -270,7 +270,7 @@ var/list/runechat_image_cache = list() if(italics) extra_classes |= "italics" - if(client.is_preference_enabled(/datum/client_preference/runechat_border)) + if(client.prefs?.read_preference(/datum/preference/toggle/runechat_border)) extra_classes |= "black_outline" var/dist = get_dist(src, speaker) diff --git a/code/datums/json_savefile.dm b/code/datums/json_savefile.dm new file mode 100644 index 0000000000..f93c885687 --- /dev/null +++ b/code/datums/json_savefile.dm @@ -0,0 +1,116 @@ +/** + * A savefile implementation that handles all data using json. + * Also saves it using JSON too, fancy. + * If you pass in a null path, it simply acts as a memory tree instead, and cannot be saved. + */ +/datum/json_savefile + var/path = "" + VAR_PRIVATE/list/tree + /// If this is set to true, calling set_entry or remove_entry will automatically call save(), this does not catch modifying a sub-tree, nor do I know how to do that + var/auto_save = FALSE + /// Cooldown that tracks the time between attempts to download the savefile. + COOLDOWN_DECLARE(download_cooldown) + +GENERAL_PROTECT_DATUM(/datum/json_savefile) + +/datum/json_savefile/New(path) + src.path = path + tree = list() + if(path && fexists(path)) + load() + +/** + * Gets an entry from the json tree, with an optional default value. + * If no key is specified it throws the entire tree at you instead + */ +/datum/json_savefile/proc/get_entry(key, default_value) + if(!key) + return tree + return (key in tree) ? tree[key] : default_value + +/// Sets an entry in the tree to the given value +/datum/json_savefile/proc/set_entry(key, value) + tree[key] = value + if(auto_save) + save() + +/// Removes the given key from the tree +/datum/json_savefile/proc/remove_entry(key) + if(key) + tree -= key + if(auto_save) + save() + +/// Wipes the entire tree +/datum/json_savefile/proc/wipe() + tree?.Cut() + +/datum/json_savefile/proc/load() + if(!path || !fexists(path)) + return FALSE + try + tree = json_decode(rustg_file_read(path)) + return TRUE + catch(var/exception/err) + stack_trace("failed to load json savefile at '[path]': [err]") + return FALSE + +/datum/json_savefile/proc/save() + if(path) + rustg_file_write(json_encode(tree, JSON_PRETTY_PRINT), path) + +/// Traverses the entire dir tree of the given savefile and dynamically assembles the tree from it +/datum/json_savefile/proc/import_byond_savefile(savefile/savefile) + tree.Cut() + var/list/dirs_to_go = list("/" = tree) + while(length(dirs_to_go)) + var/dir = dirs_to_go[1] + var/list/region = dirs_to_go[dir] + dirs_to_go.Cut(1, 2) + savefile.cd = dir + for(var/entry in savefile.dir) + var/entry_value + savefile.cd = "[dir]/[entry]" + //eof refers to the path you are cd'ed into, not the savefile as a whole. being false right after cding into an entry means this entry has no buffer, which only happens with nested save file directories + if (savefile.eof) + region[entry] = list() + dirs_to_go["[dir]/[entry]"] = region[entry] + continue + READ_FILE(savefile, entry_value) //we are cd'ed to the entry, so we don't need to specify a path to read from + region[entry] = entry_value + +/// Proc that handles generating a JSON file (prettified if 515 and over!) of a user's preferences and showing it to them. +/// Requester is passed in to the ftp() and tgui_alert() procs, and account_name is just used to generate the filename. +/// We don't _need_ to pass in account_name since this is reliant on the json_savefile datum already knowing what we correspond to, but it's here to help people keep track of their stuff. +/datum/json_savefile/proc/export_json_to_client(mob/requester, account_name) + if(!istype(requester) || !path) + return + + if(!json_export_checks(requester)) + return + + // COOLDOWN_START(src, download_cooldown, (CONFIG_GET(number/seconds_cooldown_for_preferences_export) * (1 SECONDS))) + COOLDOWN_START(src, download_cooldown, (10 SECONDS)) + var/file_name = "[account_name ? "[account_name]_" : ""]preferences_[time2text(world.timeofday, "MMM_DD_YYYY_hh-mm-ss")].json" + var/temporary_file_storage = "data/preferences_export_working_directory/[file_name]" + + if(!text2file(json_encode(tree, JSON_PRETTY_PRINT), temporary_file_storage)) + tgui_alert(requester, "Failed to export preferences to JSON! You might need to try again later.", "Export Preferences JSON") + return + + var/exportable_json = file(temporary_file_storage) + + DIRECT_OUTPUT(requester, ftp(exportable_json, file_name)) + fdel(temporary_file_storage) + +/// Proc that just handles all of the checks for exporting a preferences file, returns TRUE if all checks are passed, FALSE otherwise. +/// Just done like this to make the code in the export_json_to_client() proc a bit cleaner. +/datum/json_savefile/proc/json_export_checks(mob/requester) + if(!COOLDOWN_FINISHED(src, download_cooldown)) + tgui_alert(requester, "You must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, download_cooldown))] before exporting your preferences again!", "Export Preferences JSON") + return FALSE + + if(tgui_alert(requester, "Are you sure you want to export your preferences as a JSON file? This will save to a file on your computer.", "Export Preferences JSON", list("Cancel", "Yes")) == "Yes") + return TRUE + + return FALSE diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm index 74189128e3..91a28a713a 100644 --- a/code/datums/looping_sounds/_looping_sound.dm +++ b/code/datums/looping_sounds/_looping_sound.dm @@ -105,7 +105,7 @@ if(direct) if(ismob(thing)) var/mob/M = thing - if(pref_check && !M.is_preference_enabled(pref_check)) + if(M.check_sound_preference(pref_check)) continue SEND_SOUND(thing, S) else diff --git a/code/datums/looping_sounds/alarm_sounds.dm b/code/datums/looping_sounds/alarm_sounds.dm index 4453de6de9..35d4599e23 100644 --- a/code/datums/looping_sounds/alarm_sounds.dm +++ b/code/datums/looping_sounds/alarm_sounds.dm @@ -2,7 +2,7 @@ /datum/looping_sound/alarm volume_chan = VOLUME_CHANNEL_ALARMS - pref_check = /datum/client_preference/looping_alarms + pref_check = /datum/preference/toggle/looping_alarms /datum/looping_sound/alarm/fire_alarm // Commented out start/end as I don't feel they're very fitting // start_sound = 'sound/effects/alarms/fire_alarm/fire_alarm_start.ogg' diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index 85cf2c4adb..613d54fef6 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -13,7 +13,7 @@ mid_length = 60 volume = 40 extra_range = 10 - pref_check = /datum/client_preference/supermatter_hum + pref_check = /datum/preference/toggle/supermatter_hum /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -104,7 +104,7 @@ mid_length = 70 end_sound = 'sound/machines/air_pump/airpumpshutdown.ogg' volume = 15 - pref_check = /datum/client_preference/air_pump_noise + pref_check = /datum/preference/toggle/air_pump_noise //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -124,7 +124,7 @@ mid_length = 60 volume = 10 extra_range = -1 // Short-range - pref_check = /datum/client_preference/fridge_hum + pref_check = /datum/preference/toggle/fridge_hum volume_chan = VOLUME_CHANNEL_MACHINERY_IDLE //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/datums/looping_sounds/mob_sounds.dm b/code/datums/looping_sounds/mob_sounds.dm index eb65eed926..9e27f7b9b1 100644 --- a/code/datums/looping_sounds/mob_sounds.dm +++ b/code/datums/looping_sounds/mob_sounds.dm @@ -26,7 +26,7 @@ mid_sounds = list('modular_chomp/sound/misc/cozy.ogg'=1) mid_length = 21 SECONDS volume = 40 - pref_check = /datum/client_preference/sleep_music + pref_check = /datum/preference/toggle/sleep_music direct = TRUE // We send this sound directly to the mob, bc they only hear it when they're asleep. exclusive = TRUE // This should only occur once, because we only want one music loop running while we snooze. volume_chan = VOLUME_CHANNEL_HUD_WARNINGS diff --git a/code/datums/looping_sounds/weather_sounds.dm b/code/datums/looping_sounds/weather_sounds.dm index e8ab567bda..57477b8a65 100644 --- a/code/datums/looping_sounds/weather_sounds.dm +++ b/code/datums/looping_sounds/weather_sounds.dm @@ -1,5 +1,5 @@ /datum/looping_sound/weather - pref_check = /datum/client_preference/weather_sounds + pref_check = /datum/preference/toggle/weather_sounds volume_chan = VOLUME_CHANNEL_WEATHER // CHOMPEdit - Weather Volume Channel // CHOMPEdit: Blanket replace all wind with this, for now, in lieue of a snowstorm-specific wind @@ -124,3 +124,6 @@ mid_sounds = 'sound/effects/weather/downpour/rainindoors.ogg' mid_length = 24 SECONDS //CHOMPEDIT end + +/datum/looping_sound/weather/rain/indoors/heavy + volume = 40 diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm index bffcf2ff0b..25e1ecfc4d 100644 --- a/code/datums/progressbar.dm +++ b/code/datums/progressbar.dm @@ -41,7 +41,7 @@ progress = CLAMP(progress, 0, goal) bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]" - if(!shown && user.is_preference_enabled(/datum/client_preference/show_progress_bar)) + if(!shown && user.client?.prefs?.read_preference(/datum/preference/toggle/show_progress_bar)) user.client.images += bar shown = 1 diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index d2e09bb449..a5c58f207f 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -394,7 +394,7 @@ var/list/mob/living/forced_ambiance_list = new /area/proc/play_ambience(var/mob/living/L, initial = TRUE) // Ambience goes down here -- make sure to list each area seperately for ease of adding things in later, thanks! Note: areas adjacent to each other should have the same sounds to prevent cutoff when possible.- LastyScratch - if(!(L && L.is_preference_enabled(/datum/client_preference/play_ambiance))) + if(!L?.read_preference(/datum/preference/toggle/play_ambience)) return var/volume_mod = L.get_preference_volume_channel(VOLUME_CHANNEL_AMBIENCE) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 221dae6f32..644609657e 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1286,8 +1286,8 @@ About the new airlock wires panel: for(var/mob/M as anything in player_list) if(!M || !M.client) continue - var/old_sounds = M.client.is_preference_enabled(/datum/client_preference/old_door_sounds) - var/department_door_sounds = M.client.is_preference_enabled(/datum/client_preference/department_door_sounds) + var/old_sounds = M.read_preference(/datum/preference/toggle/old_door_sounds) + var/department_door_sounds = M.read_preference(/datum/preference/toggle/department_door_sounds) var/sound var/volume if(old_sounds) // Do we have old sounds enabled? Play these even if we have department door sounds enabled. @@ -1414,8 +1414,8 @@ About the new airlock wires panel: for(var/mob/M as anything in player_list) if(!M || !M.client) continue - var/old_sounds = M.client.is_preference_enabled(/datum/client_preference/old_door_sounds) - var/department_door_sounds = M.client.is_preference_enabled(/datum/client_preference/department_door_sounds) + var/old_sounds = M.read_preference(/datum/preference/toggle/old_door_sounds) + var/department_door_sounds = M.read_preference(/datum/preference/toggle/department_door_sounds) var/sound var/volume if(old_sounds) diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm index 844552a08a..5ffbf39c35 100644 --- a/code/game/machinery/telecomms/broadcaster.dm +++ b/code/game/machinery/telecomms/broadcaster.dm @@ -406,19 +406,21 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept for (var/mob/R in receive) /* --- Loop through the receivers and categorize them --- */ - if(!R.is_preference_enabled(/datum/client_preference/holder/hear_radio)) - continue + // Allows admins to disable radio + if(R?.client?.holder) + if(!R.client?.prefs?.read_preference(/datum/preference/toggle/holder/hear_radio)) + continue if(istype(R, /mob/new_player)) // we don't want new players to hear messages. rare but generates runtimes. continue // Ghosts hearing all radio chat don't want to hear syndicate intercepts, they're duplicates - if(data == DATA_ANTAG && istype(R, /mob/observer/dead) && R.is_preference_enabled(/datum/client_preference/ghost_radio)) + if(data == DATA_ANTAG && istype(R, /mob/observer/dead) && R.client?.prefs?.read_preference(/datum/preference/toggle/ghost_radio)) continue // ChompEDIT START - Ghost blacklist for certain spammy radio channels var/list/ghostradio_freq_blacklist = list(ENT_FREQ, BDCM_FREQ) - if(istype(R, /mob/observer/dead) && R.is_preference_enabled(/datum/client_preference/ghost_radio) && (connection.frequency in ghostradio_freq_blacklist)) + if(istype(R, /mob/observer/dead) && R.client?.prefs?.read_preference(/datum/preference/toggle/ghost_radio) && (connection.frequency in ghostradio_freq_blacklist)) continue // ChompEDIT END @@ -519,21 +521,21 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept if(length(heard_masked)) for (var/mob/R in heard_masked) R.hear_radio(message_pieces, verbage, part_a, part_b, part_c, part_d, part_e, M, 0, name) - if(R.is_preference_enabled(/datum/client_preference/radio_sounds)) + if(R.read_preference(/datum/preference/toggle/radio_sounds)) R << 'sound/effects/radio_common_quieter.ogg' /* --- Process all the mobs that heard the voice normally (understood) --- */ if(length(heard_normal)) for (var/mob/R in heard_normal) R.hear_radio(message_pieces, verbage, part_a, part_b, part_c, part_d, part_e, M, 0, realname) - if(R.is_preference_enabled(/datum/client_preference/radio_sounds)) + if(R.read_preference(/datum/preference/toggle/radio_sounds)) R << 'sound/effects/radio_common_quieter.ogg' /* --- Process all the mobs that heard the voice normally (did not understand) --- */ if(length(heard_voice)) for (var/mob/R in heard_voice) R.hear_radio(message_pieces, verbage, part_a, part_b, part_c, part_d, part_e, M,0, vname) - if(R.is_preference_enabled(/datum/client_preference/radio_sounds)) + if(R.read_preference(/datum/preference/toggle/radio_sounds)) R << 'sound/effects/radio_common_quieter.ogg' /* --- Process all the mobs that heard a garbled voice (did not understand) --- */ @@ -541,14 +543,14 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept if(length(heard_garbled)) for (var/mob/R in heard_garbled) R.hear_radio(message_pieces, verbage, part_a, part_b, part_c, part_d, part_e, M, 1, vname) - if(R.is_preference_enabled(/datum/client_preference/radio_sounds)) + if(R.read_preference(/datum/preference/toggle/radio_sounds)) R << 'sound/effects/radio_common_quieter.ogg' /* --- Complete gibberish. Usually happens when there's a compressed message --- */ if(length(heard_gibberish)) for (var/mob/R in heard_gibberish) R.hear_radio(message_pieces, verbage, part_a, part_b, part_c, part_d, part_e, M, 1) - if(R.is_preference_enabled(/datum/client_preference/radio_sounds)) + if(R.read_preference(/datum/preference/toggle/radio_sounds)) R << 'sound/effects/radio_common_quieter.ogg' return 1 @@ -623,10 +625,10 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept for (var/mob/R in receive) /* --- Loop through the receivers and categorize them --- */ - - if(!R.is_preference_enabled(/datum/client_preference/holder/hear_radio)) //Adminning with 80 people on can be fun when you're trying to talk and all you can hear is radios. - continue - + // Allow admins to disable radios completely + if(R?.client?.holder) + if(!R.client?.prefs?.read_preference(/datum/preference/toggle/holder/hear_radio)) + continue // --- Check for compression --- if(compression > 0) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 2457190abe..555e3d1171 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -329,7 +329,7 @@ else playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) else - playsound(src, drop_sound, 30, preference = /datum/client_preference/drop_sounds) + playsound(src, drop_sound, 30, preference = /datum/preference/toggle/drop_sounds) // apparently called whenever an item is removed from a slot, container, or anything else. /obj/item/proc/dropped(mob/user as mob) @@ -367,12 +367,12 @@ if(user.pulling == src) user.stop_pulling() if(("[slot]" in slot_flags_enumeration) && (slot_flags & slot_flags_enumeration["[slot]"])) if(equip_sound && !muffled_by_belly(user)) //ChompEDIT - playsound(src, equip_sound, 20, preference = /datum/client_preference/pickup_sounds) + playsound(src, equip_sound, 20, preference = /datum/preference/toggle/pickup_sounds) else if(!muffled_by_belly(user)) //ChompEDIT - playsound(src, drop_sound, 20, preference = /datum/client_preference/pickup_sounds) + playsound(src, drop_sound, 20, preference = /datum/preference/toggle/pickup_sounds) else if(slot == slot_l_hand || slot == slot_r_hand) if(!muffled_by_belly(user)) //ChompEDIT - playsound(src, pickup_sound, 20, preference = /datum/client_preference/pickup_sounds) + playsound(src, pickup_sound, 20, preference = /datum/preference/toggle/pickup_sounds) return // As above but for items being equipped to an active module on a robot. @@ -952,7 +952,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. /obj/item/MouseEntered(location,control,params) . = ..() - if(usr.is_preference_enabled(/datum/client_preference/inv_tooltips) && ((src in usr) || isstorage(loc))) // If in inventory or in storage we're looking at + if(usr?.read_preference(/datum/preference/toggle/inv_tooltips) && ((src in usr) || isstorage(loc))) // If in inventory or in storage we're looking at var/user = usr tip_timer = addtimer(CALLBACK(src, PROC_REF(openTip), location, control, params, user), 5, TIMER_STOPPABLE) diff --git a/code/game/objects/items/devices/communicator/UI_tgui.dm b/code/game/objects/items/devices/communicator/UI_tgui.dm index fb59fd2a32..e6c099ad45 100644 --- a/code/game/objects/items/devices/communicator/UI_tgui.dm +++ b/code/game/objects/items/devices/communicator/UI_tgui.dm @@ -384,7 +384,7 @@ var/obj/item/device/communicator/comm = exonet.get_atom_from_address(their_address) to_chat(usr, "[icon2html(src, usr.client)] Sent message to [istype(comm, /obj/item/device/communicator) ? comm.owner : comm.name], \"[text]\" (Reply)") for(var/mob/M in player_list) - if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + if(M.stat == DEAD && M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat) continue if(exonet.get_atom_from_address(their_address) == M) diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm index c198afcd9f..6b0c8ad601 100644 --- a/code/game/objects/items/devices/communicator/messaging.dm +++ b/code/game/objects/items/devices/communicator/messaging.dm @@ -161,7 +161,7 @@ exonet_messages.Add("To [chosen_communicator]:
[text_message]") log_pda("(DCOMM: [src]) sent \"[text_message]\" to [chosen_communicator]", src) for(var/mob/M in player_list) - if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + if(M.stat == DEAD && M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat) continue if(M == src) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index ebc6532843..52a7ca212c 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -64,12 +64,12 @@ if(!ishuman(src.loc)) //CHOMP Addition, this IF block. return ..(freq, level) //CHOMP Addition end if (aiOverride) - playsound(loc, 'sound/effects/radio_common.ogg', 20, 1, 1, preference = /datum/client_preference/radio_sounds) + playsound(loc, 'sound/effects/radio_common.ogg', 20, 1, 1, preference = /datum/preference/toggle/radio_sounds) //CHOMPAdd return ..(freq, level) if(ishuman(src.loc)) var/mob/living/carbon/human/H = src.loc if(H.l_ear == src || H.r_ear == src) - playsound(loc, 'sound/effects/radio_common.ogg', 20, 1, 1, preference = /datum/client_preference/radio_sounds) + playsound(loc, 'sound/effects/radio_common.ogg', 20, 1, 1, preference = /datum/preference/toggle/radio_sounds) //CHOMPAdd return ..(freq, level) return -1 diff --git a/code/game/objects/structures/low_wall.dm b/code/game/objects/structures/low_wall.dm index d65abcbad5..1a9dc0e6c4 100644 --- a/code/game/objects/structures/low_wall.dm +++ b/code/game/objects/structures/low_wall.dm @@ -108,7 +108,7 @@ if(W.loc != user) // This should stop mounted modules ending up outside the module. return - if(can_place_items() && user.unEquip(W, 0, src.loc) && user.is_preference_enabled(/datum/client_preference/precision_placement)) + if(can_place_items() && user.unEquip(W, 0, src.loc) && user.client?.prefs?.read_preference(/datum/preference/toggle/precision_placement)) auto_align(W, click_parameters) return 1 diff --git a/code/game/sound.dm b/code/game/sound.dm index 4c12c8bb23..e587e4b577 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -41,10 +41,23 @@ M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, is_global, channel, pressure_affected, S, preference, volume_channel) +/mob/proc/check_sound_preference(list/preference) + if(!islist(preference)) + preference = list(preference) + + for(var/p in preference) + // Ignore nulls + if(p) + if(!read_preference(p)) + return FALSE + + return TRUE + /mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global, channel = 0, pressure_affected = TRUE, sound/S, preference, volume_channel = null) if(!client || ear_deaf > 0) return - if(preference && !client.is_preference_enabled(preference)) + + if(!check_sound_preference(preference)) return if(!S) @@ -130,7 +143,7 @@ /client/proc/playtitlemusic() if(!ticker || !SSmedia_tracks.lobby_tracks.len || !media) return - if(is_preference_enabled(/datum/client_preference/play_lobby_music)) + if(prefs?.read_preference(/datum/preference/toggle/play_lobby_music)) var/datum/track/T = pick(SSmedia_tracks.lobby_tracks) media.push_music(T.url, world.time, 0.85) to_chat(src,"Lobby music: [T.title] by [T.artist].") diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 409968a57c..777527714a 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -17,7 +17,7 @@ var/global/floorIsLava = 0 var/rendered = "ATTACK: [text]" for(var/client/C in GLOB.admins) if((R_ADMIN|R_MOD) & C.holder.rights) - if(C.is_preference_enabled(/datum/client_preference/mod/show_attack_logs)) + if(C.prefs?.read_preference(/datum/preference/toggle/show_attack_logs)) var/msg = rendered to_chat(C, type = MESSAGE_TYPE_ATTACKLOG, diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm index 7257641016..7a5b8245d1 100644 --- a/code/modules/admin/admin_verb_lists_vr.dm +++ b/code/modules/admin/admin_verb_lists_vr.dm @@ -18,8 +18,6 @@ var/list/admin_verbs_default = list( // /client/proc/cmd_mod_say, // /client/proc/deadchat //toggles deadchat on/off, // /client/proc/toggle_ahelp_sound, - /client/proc/toggle_admin_global_looc, - /client/proc/toggle_admin_deadchat ) var/list/admin_verbs_admin = list( @@ -118,8 +116,6 @@ var/list/admin_verbs_admin = list( /client/proc/change_security_level, /client/proc/view_chemical_reaction_logs, /client/proc/makepAI, - /client/proc/toggle_debug_logs, - /client/proc/toggle_attack_logs, /datum/admins/proc/paralyze_mob, /client/proc/fixatmos, /datum/admins/proc/quick_nif, //VOREStation Add, @@ -273,7 +269,6 @@ var/list/admin_verbs_debug = list( /client/proc/jumptomob, /client/proc/jumptocoord, /client/proc/dsay, - /client/proc/toggle_debug_logs, /client/proc/admin_ghost, //allows us to ghost/reenter body at will, /datum/admins/proc/show_player_panel, //shows an interface for individual players, with various links (links require additional flags, //VOREStation Add, /client/proc/player_panel_new, //shows an interface for all players, with links to various panels, //VOREStation Add, @@ -421,7 +416,6 @@ var/list/admin_verbs_mod = list( /client/proc/check_antagonists, /client/proc/aooc, /client/proc/jobbans, - /client/proc/toggle_attack_logs, /client/proc/cmd_admin_subtle_message, //send an message to somebody as a 'voice in their head', /datum/admins/proc/paralyze_mob, /client/proc/cmd_admin_direct_narrate, @@ -560,8 +554,6 @@ var/list/admin_verbs_event_manager = list( /client/proc/change_human_appearance_self, // Allows the human-based mob itself change its basic appearance , /client/proc/change_security_level, /client/proc/makepAI, - /client/proc/toggle_debug_logs, - /client/proc/toggle_attack_logs, /datum/admins/proc/paralyze_mob, /client/proc/fixatmos, /datum/admins/proc/sendFax, diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 1f62a98dc3..df0815a642 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -299,7 +299,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) for(var/client/X in GLOB.admins) // if(!check_rights(R_ADMIN, 0, X)) //CHOMP Remove let everyone hear the ahelp // continue //CHOMP Remove let everyone hear the ahelp - if(X.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) X << 'sound/effects/adminhelp.ogg' window_flash(X) to_chat(X, chat_msg) @@ -410,7 +410,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) return if(initiator) - if(initiator.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(initiator.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) initiator << 'sound/effects/adminhelp.ogg' to_chat(initiator, "[span_red("- AdminHelp Rejected! -")]
\ diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index fab2d9a81c..80b1ab0935 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -165,7 +165,7 @@ to_chat(src, "PM to-Admins: [msg]") //play the recieving admin the adminhelp sound (if they have them enabled) - if(recipient.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(recipient.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) recipient << 'sound/effects/adminhelp.ogg' else diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm index 0209b7c5ec..11c0d7f8dc 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -11,7 +11,7 @@ to_chat(src, "You cannot send DSAY messages (muted).") return - if(!is_preference_enabled(/datum/client_preference/show_dsay)) + if(!prefs?.read_preference(/datum/preference/toggle/show_dsay)) to_chat(src, "You have deadchat muted.") return diff --git a/code/modules/admin/verbs/lightning_strike.dm b/code/modules/admin/verbs/lightning_strike.dm index a7eb744d72..8213a9a9c5 100644 --- a/code/modules/admin/verbs/lightning_strike.dm +++ b/code/modules/admin/verbs/lightning_strike.dm @@ -65,7 +65,7 @@ var/sound = get_sfx("thunder") for(var/mob/M in player_list) if( (P && (M.z in P.expected_z_levels)) || M.z == T.z) - if(M.is_preference_enabled(/datum/client_preference/weather_sounds)) + if(M.check_sound_preference(/datum/preference/toggle/weather_sounds)) M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE) if(cosmetic) // Everything beyond here involves potentially damaging things. If we don't want to do that, stop now. diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index b824aa38b5..c3c02d5d0b 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -42,7 +42,7 @@ var/list/sounds_cache = list() message_admins("[key_name_admin(src)] played sound [S]", 1) for(var/mob/M in player_list) - if(M.is_preference_enabled(/datum/client_preference/play_admin_midis)) + if(M.read_preference(/datum/preference/toggle/play_admin_midis)) admin_sound.volume = vol * M.client.admin_music_volume SEND_SOUND(M, admin_sound) admin_sound.volume = vol @@ -91,7 +91,7 @@ var/list/sounds_cache = list() log_admin("[key_name(src)] played sound [S] on Z[target_z]") message_admins("[key_name_admin(src)] played sound [S] on Z[target_z]", 1) for(var/mob/M in player_list) - if(M.is_preference_enabled(/datum/client_preference/play_admin_midis) && M.z == target_z) + if(M.read_preference(/datum/preference/toggle/play_admin_midis) && M.z == target_z) M << uploaded_sound feedback_add_details("admin_verb", "Play Z Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -204,7 +204,7 @@ var/list/sounds_cache = list() for(var/m in player_list) var/mob/M = m var/client/C = M.client - if(C.is_preference_enabled(/datum/client_preference/play_admin_midis)) + if(C.prefs?.read_preference(/datum/preference/toggle/play_admin_midis)) if(!stop_web_sounds) C.tgui_panel?.play_music(web_sound_url, music_extra_data) else diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index cea02a731f..9eaad6d43b 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -21,7 +21,7 @@ for(var/client/C in GLOB.admins) if(R_ADMIN|R_EVENT & C.holder.rights) - if(C.is_preference_enabled(/datum/client_preference/admin/show_chat_prayers)) + if(C.prefs?.read_preference(/datum/preference/toggle/show_chat_prayers)) to_chat(C, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE) C << 'sound/effects/ding.ogg' to_chat(usr, "Your prayers have been received by the gods.", confidential = TRUE) diff --git a/code/modules/asset_cache/assets/preferences.dm b/code/modules/asset_cache/assets/preferences.dm new file mode 100644 index 0000000000..3d01471a38 --- /dev/null +++ b/code/modules/asset_cache/assets/preferences.dm @@ -0,0 +1,22 @@ +/// Sends information needed for shared details on individual preferences +/datum/asset/json/preferences + name = "preferences" + +/datum/asset/json/preferences/generate() + var/list/preference_data = list() + + for(var/middleware_type in subtypesof(/datum/preference_middleware)) + var/datum/preference_middleware/middleware = new middleware_type + var/data = middleware.get_constant_data() + if(!isnull(data)) + preference_data[middleware.key] = data + + qdel(middleware) + + for(var/preference_type in GLOB.preference_entries) + var/datum/preference/preference_entry = GLOB.preference_entries[preference_type] + var/data = preference_entry.compile_constant_data() + if(!isnull(data)) + preference_data[preference_entry.savefile_key] = data + + return preference_data diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 56ae29d7cb..3239633ff2 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -225,12 +225,15 @@ //preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum) prefs = preferences_datums[ckey] - if(!prefs) + if(prefs) + prefs.client = src + prefs.load_savefile() // just to make sure we have the latest data + prefs.apply_all_client_preferences() + else prefs = new /datum/preferences(src) preferences_datums[ckey] = prefs prefs.last_ip = address //these are gonna be used for banning prefs.last_id = computer_id //these are gonna be used for banning - prefs.client = src // Only relevant if we reloaded it from the global list, otherwise prefs/New sets it hook_vr("client_new",list(src)) //VOREStation Code. For now this only loads vore prefs, so better put before mob.Login() call but after normal prefs are loaded. @@ -302,7 +305,7 @@ alert = TRUE if(alert) for(var/client/X in GLOB.admins) - if(X.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) X << 'sound/voice/bcriminal.ogg' //ChompEDIT - back to beepsky window_flash(X) //VOREStation Edit end. @@ -522,6 +525,12 @@ if(prefs) prefs.ShowChoices(usr) +/client/verb/game_options() + set name = "Game Options" + set category = "Preferences" + if(prefs) + prefs.tgui_interact(usr) + /client/proc/findJoinDate() var/list/http = world.Export("http://byond.com/members/[ckey]?format=text") if(!http) diff --git a/code/modules/client/preference_setup/antagonism/01_basic.dm b/code/modules/client/preference_setup/antagonism/01_basic.dm index 8dc760a00f..3572a2fd62 100644 --- a/code/modules/client/preference_setup/antagonism/01_basic.dm +++ b/code/modules/client/preference_setup/antagonism/01_basic.dm @@ -4,17 +4,17 @@ var/global/list/uplink_locations = list("PDA", "Headset", "None") name = "Basic" sort_order = 1 -/datum/category_item/player_setup_item/antagonism/basic/load_character(var/savefile/S) - S["uplinklocation"] >> pref.uplinklocation - S["exploit_record"] >> pref.exploit_record - S["antag_faction"] >> pref.antag_faction - S["antag_vis"] >> pref.antag_vis +/datum/category_item/player_setup_item/antagonism/basic/load_character(list/save_data) + pref.uplinklocation = save_data["uplinklocation"] + pref.exploit_record = save_data["exploit_record"] + pref.antag_faction = save_data["antag_faction"] + pref.antag_vis = save_data["antag_vis"] -/datum/category_item/player_setup_item/antagonism/basic/save_character(var/savefile/S) - S["uplinklocation"] << pref.uplinklocation - S["exploit_record"] << pref.exploit_record - S["antag_faction"] << pref.antag_faction - S["antag_vis"] << pref.antag_vis +/datum/category_item/player_setup_item/antagonism/basic/save_character(list/save_data) + save_data["uplinklocation"] = pref.uplinklocation + save_data["exploit_record"] = pref.exploit_record + save_data["antag_faction"] = pref.antag_faction + save_data["antag_vis"] = pref.antag_vis /datum/category_item/player_setup_item/antagonism/basic/sanitize_character() pref.uplinklocation = sanitize_inlist(pref.uplinklocation, uplink_locations, initial(pref.uplinklocation)) diff --git a/code/modules/client/preference_setup/antagonism/02_candidacy.dm b/code/modules/client/preference_setup/antagonism/02_candidacy.dm index bc5aff7ec0..a515ad1f4e 100644 --- a/code/modules/client/preference_setup/antagonism/02_candidacy.dm +++ b/code/modules/client/preference_setup/antagonism/02_candidacy.dm @@ -32,11 +32,11 @@ var/global/list/special_roles = list( //keep synced with the defines BE_* in set name = "Candidacy" sort_order = 2 -/datum/category_item/player_setup_item/antagonism/candidacy/load_character(var/savefile/S) - S["be_special"] >> pref.be_special +/datum/category_item/player_setup_item/antagonism/candidacy/load_character(list/save_data) + pref.be_special = save_data["be_special"] -/datum/category_item/player_setup_item/antagonism/candidacy/save_character(var/savefile/S) - S["be_special"] << pref.be_special +/datum/category_item/player_setup_item/antagonism/candidacy/save_character(list/save_data) + save_data["be_special"] = pref.be_special /datum/category_item/player_setup_item/antagonism/candidacy/sanitize_character() pref.be_special = sanitize_integer(pref.be_special, 0, 16777215, initial(pref.be_special)) //VOREStation Edit - 24 bits of support diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index 5ae2986a72..57023a3ab9 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -10,47 +10,47 @@ name = "Basic" sort_order = 1 -/datum/category_item/player_setup_item/general/basic/load_character(var/savefile/S) - S["real_name"] >> pref.real_name - S["nickname"] >> pref.nickname - S["name_is_always_random"] >> pref.be_random_name - S["gender"] >> pref.biological_gender - S["id_gender"] >> pref.identifying_gender - S["age"] >> pref.age - S["bday_month"] >> pref.bday_month - S["bday_day"] >> pref.bday_day - S["last_bday_note"] >> pref.last_birthday_notification - S["bday_announce"] >> pref.bday_announce - S["spawnpoint"] >> pref.spawnpoint - S["OOC_Notes"] >> pref.metadata - S["OOC_Notes_Likes"] >> pref.metadata_likes - S["OOC_Notes_Disikes"] >> pref.metadata_dislikes - //CHOMPEdit Start - S["OOC_Notes_Maybes"] >> pref.metadata_maybes - S["OOC_Notes_Favs"] >> pref.metadata_favs - S["OOC_Notes_System"] >> pref.matadata_ooc_style - //CHOMPEdit End +/datum/category_item/player_setup_item/general/basic/load_character(list/save_data) + pref.real_name = save_data["real_name"] + pref.nickname = save_data["nickname"] + pref.be_random_name = save_data["name_is_always_random"] + pref.biological_gender = save_data["gender"] + pref.identifying_gender = save_data["id_gender"] + pref.age = save_data["age"] + pref.bday_month = save_data["bday_month"] + pref.bday_day = save_data["bday_day"] + pref.last_birthday_notification = save_data["last_bday_note"] + pref.bday_announce = save_data["bday_announce"] + pref.spawnpoint = save_data["spawnpoint"] + pref.metadata = save_data["OOC_Notes"] + pref.metadata_likes = save_data["OOC_Notes_Likes"] + pref.metadata_dislikes = save_data["OOC_Notes_Disikes"] + //CHOMPAdd Start + pref.metadata_maybes = save_data["OOC_Notes_Maybes"] + pref.metadata_favs = save_data["OOC_Notes_Favs"] + pref.matadata_ooc_style = save_data["OOC_Notes_System"] + //CHOMPAdd End -/datum/category_item/player_setup_item/general/basic/save_character(var/savefile/S) - S["real_name"] << pref.real_name - S["nickname"] << pref.nickname - S["name_is_always_random"] << pref.be_random_name - S["gender"] << pref.biological_gender - S["id_gender"] << pref.identifying_gender - S["age"] << pref.age - S["bday_month"] << pref.bday_month - S["bday_day"] << pref.bday_day - S["last_bday_note"] << pref.last_birthday_notification - S["bday_announce"] << pref.bday_announce - S["spawnpoint"] << pref.spawnpoint - S["OOC_Notes"] << pref.metadata - S["OOC_Notes_Likes"] << pref.metadata_likes - S["OOC_Notes_Disikes"] << pref.metadata_dislikes - //CHOMPEdit Start - S["OOC_Notes_Favs"] << pref.metadata_favs - S["OOC_Notes_Maybes"] << pref.metadata_maybes - S["OOC_Notes_System"] << pref.matadata_ooc_style - //CHOMPEdit End +/datum/category_item/player_setup_item/general/basic/save_character(list/save_data) + save_data["real_name"] = pref.real_name + save_data["nickname"] = pref.nickname + save_data["name_is_always_random"] = pref.be_random_name + save_data["gender"] = pref.biological_gender + save_data["id_gender"] = pref.identifying_gender + save_data["age"] = pref.age + save_data["bday_month"] = pref.bday_month + save_data["bday_day"] = pref.bday_day + save_data["last_bday_note"] = pref.last_birthday_notification + save_data["bday_announce"] = pref.bday_announce + save_data["spawnpoint"] = pref.spawnpoint + save_data["OOC_Notes"] = pref.metadata + save_data["OOC_Notes_Likes"] = pref.metadata_likes + save_data["OOC_Notes_Disikes"] = pref.metadata_dislikes + //CHOMPAdd Start + save_data["OOC_Notes_Maybes"] = pref.metadata_maybes + save_data["OOC_Notes_Favs"] = pref.metadata_favs + save_data["OOC_Notes_System"] = pref.matadata_ooc_style + //CHOMPAdd End /datum/category_item/player_setup_item/general/basic/sanitize_character() pref.age = sanitize_integer(pref.age, get_min_age(), get_max_age(), initial(pref.age)) diff --git a/code/modules/client/preference_setup/general/02_language.dm b/code/modules/client/preference_setup/general/02_language.dm index fa18822e1b..44abf32a24 100644 --- a/code/modules/client/preference_setup/general/02_language.dm +++ b/code/modules/client/preference_setup/general/02_language.dm @@ -7,28 +7,20 @@ sort_order = 2 var/static/list/forbidden_prefixes = list(";", ":", ".", "!", "*", "^", "-") -/datum/category_item/player_setup_item/general/language/load_character(var/savefile/S) - S["language"] >> pref.alternate_languages - S["extra_languages"] >> pref.extra_languages - if(islist(pref.alternate_languages)) // Because aparently it may not be? - testing("LANGSANI: Loaded from [pref.client]'s character [pref.real_name || "-name not yet loaded-"] savefile: [english_list(pref.alternate_languages || list())]") - S["language_prefixes"] >> pref.language_prefixes - //CHOMPEdit Begin - S["species"] >> pref.species - //CHOMPEdit End - //VORE Edit Begin - S["preflang"] >> pref.preferred_language - //VORE Edit End - S["language_custom_keys"] >> pref.language_custom_keys +/datum/category_item/player_setup_item/general/language/load_character(list/save_data) + pref.alternate_languages = save_data["language"] + pref.extra_languages = save_data["extra_languages"] + pref.language_prefixes = save_data["language_prefixes"] + pref.species = save_data["species"] //CHOMPAdd + pref.preferred_language = save_data["preflang"] + pref.language_custom_keys = save_data["language_custom_keys"] -/datum/category_item/player_setup_item/general/language/save_character(var/savefile/S) - S["language"] << pref.alternate_languages - S["extra_languages"] << pref.extra_languages - if(islist(pref.alternate_languages)) // Because aparently it may not be? - testing("LANGSANI: Loaded from [pref.client]'s character [pref.real_name || "-name not yet loaded-"] savefile: [english_list(pref.alternate_languages || list())]") - S["language_prefixes"] << pref.language_prefixes - S["language_custom_keys"] << pref.language_custom_keys - S["preflang"] << pref.preferred_language // VOREStation Edit +/datum/category_item/player_setup_item/general/language/save_character(list/save_data) + save_data["language"] = pref.alternate_languages + save_data["extra_languages"] = pref.extra_languages + save_data["language_prefixes"] = pref.language_prefixes + save_data["language_custom_keys"] = pref.language_custom_keys + save_data["preflang"] = pref.preferred_language /datum/category_item/player_setup_item/general/language/sanitize_character() if(!islist(pref.alternate_languages)) pref.alternate_languages = list() diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index a0da720fde..0358f4cddb 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -90,152 +90,151 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O name = "Body" sort_order = 3 -/datum/category_item/player_setup_item/general/body/load_character(var/savefile/S) - S["species"] >> pref.species - S["hair_red"] >> pref.r_hair - S["hair_green"] >> pref.g_hair - S["hair_blue"] >> pref.b_hair - S["grad_red"] >> pref.r_grad - S["grad_green"] >> pref.g_grad - S["grad_blue"] >> pref.b_grad - S["facial_red"] >> pref.r_facial - S["grad_red"] >> pref.r_grad - S["grad_green"] >> pref.g_grad - S["grad_blue"] >> pref.b_grad - S["facial_green"] >> pref.g_facial - S["facial_blue"] >> pref.b_facial - S["skin_tone"] >> pref.s_tone - S["skin_red"] >> pref.r_skin - S["skin_green"] >> pref.g_skin - S["skin_blue"] >> pref.b_skin - S["hair_style_name"] >> pref.h_style - S["grad_style_name"] >> pref.grad_style - S["facial_style_name"] >> pref.f_style - S["grad_style_name"] >> pref.grad_style - S["eyes_red"] >> pref.r_eyes - S["eyes_green"] >> pref.g_eyes - S["eyes_blue"] >> pref.b_eyes - S["b_type"] >> pref.b_type - S["disabilities"] >> pref.disabilities - S["organ_data"] >> pref.organ_data - S["rlimb_data"] >> pref.rlimb_data - S["body_markings"] >> pref.body_markings - S["synth_color"] >> pref.synth_color - S["synth_red"] >> pref.r_synth - S["synth_green"] >> pref.g_synth - S["synth_blue"] >> pref.b_synth - S["synth_markings"] >> pref.synth_markings - S["bgstate"] >> pref.bgstate - S["body_descriptors"] >> pref.body_descriptors - S["Wingdings"] >> pref.wingdings //YWadd start - S["colorblind_mono"] >> pref.colorblind_mono - S["colorblind_vulp"] >> pref.colorblind_vulp - S["colorblind_taj"] >> pref.colorblind_taj - S["haemophilia"] >> pref.haemophilia //YWadd end - S["ear_style"] >> pref.ear_style - S["r_ears"] >> pref.r_ears - S["g_ears"] >> pref.g_ears - S["b_ears"] >> pref.b_ears - S["r_ears2"] >> pref.r_ears2 - S["g_ears2"] >> pref.g_ears2 - S["b_ears2"] >> pref.b_ears2 - S["r_ears3"] >> pref.r_ears3 - S["g_ears3"] >> pref.g_ears3 - S["b_ears3"] >> pref.b_ears3 - S["tail_style"] >> pref.tail_style - S["r_tail"] >> pref.r_tail - S["g_tail"] >> pref.g_tail - S["b_tail"] >> pref.b_tail - S["r_tail2"] >> pref.r_tail2 - S["g_tail2"] >> pref.g_tail2 - S["b_tail2"] >> pref.b_tail2 - S["r_tail3"] >> pref.r_tail3 - S["g_tail3"] >> pref.g_tail3 - S["b_tail3"] >> pref.b_tail3 - S["wing_style"] >> pref.wing_style - S["r_wing"] >> pref.r_wing - S["g_wing"] >> pref.g_wing - S["b_wing"] >> pref.b_wing - S["r_wing2"] >> pref.r_wing2 - S["g_wing2"] >> pref.g_wing2 - S["b_wing2"] >> pref.b_wing2 - S["r_wing3"] >> pref.r_wing3 - S["g_wing3"] >> pref.g_wing3 - S["b_wing3"] >> pref.b_wing3 - S["digitigrade"] >> pref.digitigrade +/datum/category_item/player_setup_item/general/body/load_character(list/save_data) + pref.species = save_data["species"] + pref.r_hair = save_data["hair_red"] + pref.g_hair = save_data["hair_green"] + pref.b_hair = save_data["hair_blue"] + pref.r_facial = save_data["facial_red"] + pref.r_grad = save_data["grad_red"] + pref.g_grad = save_data["grad_green"] + pref.b_grad = save_data["grad_blue"] + pref.g_facial = save_data["facial_green"] + pref.b_facial = save_data["facial_blue"] + pref.s_tone = save_data["skin_tone"] + pref.r_skin = save_data["skin_red"] + pref.g_skin = save_data["skin_green"] + pref.b_skin = save_data["skin_blue"] + pref.h_style = save_data["hair_style_name"] + pref.f_style = save_data["facial_style_name"] + pref.grad_style = save_data["grad_style_name"] + pref.r_eyes = save_data["eyes_red"] + pref.g_eyes = save_data["eyes_green"] + pref.b_eyes = save_data["eyes_blue"] + pref.b_type = save_data["b_type"] + pref.disabilities = save_data["disabilities"] + pref.organ_data = save_data["organ_data"] + pref.rlimb_data = save_data["rlimb_data"] + pref.body_markings = save_data["body_markings"] + pref.synth_color = save_data["synth_color"] + pref.r_synth = save_data["synth_red"] + pref.g_synth = save_data["synth_green"] + pref.b_synth = save_data["synth_blue"] + pref.synth_markings = save_data["synth_markings"] + pref.bgstate = save_data["bgstate"] + pref.body_descriptors = save_data["body_descriptors"] + //YWadd start + pref.wingdings = save_data["Wingdings"] + pref.colorblind_mono = save_data["colorblind_mono"] + pref.colorblind_vulp = save_data["colorblind_vulp"] + pref.colorblind_taj = save_data["colorblind_taj"] + pref.haemophilia = save_data["haemophilia"] + //YWadd end + pref.ear_style = save_data["ear_style"] + pref.r_ears = save_data["r_ears"] + pref.g_ears = save_data["g_ears"] + pref.b_ears = save_data["b_ears"] + pref.r_ears2 = save_data["r_ears2"] + pref.g_ears2 = save_data["g_ears2"] + pref.b_ears2 = save_data["b_ears2"] + pref.r_ears3 = save_data["r_ears3"] + pref.g_ears3 = save_data["g_ears3"] + pref.b_ears3 = save_data["b_ears3"] + pref.tail_style = save_data["tail_style"] + pref.r_tail = save_data["r_tail"] + pref.g_tail = save_data["g_tail"] + pref.b_tail = save_data["b_tail"] + pref.r_tail2 = save_data["r_tail2"] + pref.g_tail2 = save_data["g_tail2"] + pref.b_tail2 = save_data["b_tail2"] + pref.r_tail3 = save_data["r_tail3"] + pref.g_tail3 = save_data["g_tail3"] + pref.b_tail3 = save_data["b_tail3"] + pref.wing_style = save_data["wing_style"] + pref.r_wing = save_data["r_wing"] + pref.g_wing = save_data["g_wing"] + pref.b_wing = save_data["b_wing"] + pref.r_wing2 = save_data["r_wing2"] + pref.g_wing2 = save_data["g_wing2"] + pref.b_wing2 = save_data["b_wing2"] + pref.r_wing3 = save_data["r_wing3"] + pref.g_wing3 = save_data["g_wing3"] + pref.b_wing3 = save_data["b_wing3"] + pref.digitigrade = save_data["digitigrade"] -/datum/category_item/player_setup_item/general/body/save_character(var/savefile/S) - S["species"] << pref.species - S["hair_red"] << pref.r_hair - S["hair_green"] << pref.g_hair - S["hair_blue"] << pref.b_hair - S["grad_red"] << pref.r_grad - S["grad_green"] << pref.g_grad - S["grad_blue"] << pref.b_grad - S["facial_red"] << pref.r_facial - S["facial_green"] << pref.g_facial - S["facial_blue"] << pref.b_facial - S["skin_tone"] << pref.s_tone - S["skin_red"] << pref.r_skin - S["skin_green"] << pref.g_skin - S["skin_blue"] << pref.b_skin - S["hair_style_name"] << pref.h_style - S["grad_style_name"] << pref.grad_style - S["facial_style_name"] << pref.f_style - S["grad_style_name"] << pref.grad_style - S["eyes_red"] << pref.r_eyes - S["eyes_green"] << pref.g_eyes - S["eyes_blue"] << pref.b_eyes - S["b_type"] << pref.b_type - S["disabilities"] << pref.disabilities - S["organ_data"] << pref.organ_data - S["rlimb_data"] << pref.rlimb_data - S["body_markings"] << pref.body_markings - S["synth_color"] << pref.synth_color - S["synth_red"] << pref.r_synth - S["synth_green"] << pref.g_synth - S["synth_blue"] << pref.b_synth - S["synth_markings"] << pref.synth_markings - S["bgstate"] << pref.bgstate - S["body_descriptors"] << pref.body_descriptors - S["Wingdings"] << pref.wingdings //YWadd start - S["colorblind_mono"] << pref.colorblind_mono - S["colorblind_vulp"] << pref.colorblind_vulp - S["colorblind_taj"] << pref.colorblind_taj - S["haemophilia"] << pref.haemophilia //YWadd end - S["ear_style"] << pref.ear_style - S["r_ears"] << pref.r_ears - S["g_ears"] << pref.g_ears - S["b_ears"] << pref.b_ears - S["r_ears2"] << pref.r_ears2 - S["g_ears2"] << pref.g_ears2 - S["b_ears2"] << pref.b_ears2 - S["r_ears3"] << pref.r_ears3 - S["g_ears3"] << pref.g_ears3 - S["b_ears3"] << pref.b_ears3 - S["tail_style"] << pref.tail_style - S["r_tail"] << pref.r_tail - S["g_tail"] << pref.g_tail - S["b_tail"] << pref.b_tail - S["r_tail2"] << pref.r_tail2 - S["g_tail2"] << pref.g_tail2 - S["b_tail2"] << pref.b_tail2 - S["r_tail3"] << pref.r_tail3 - S["g_tail3"] << pref.g_tail3 - S["b_tail3"] << pref.b_tail3 - S["wing_style"] << pref.wing_style - S["r_wing"] << pref.r_wing - S["g_wing"] << pref.g_wing - S["b_wing"] << pref.b_wing - S["r_wing2"] << pref.r_wing2 - S["g_wing2"] << pref.g_wing2 - S["b_wing2"] << pref.b_wing2 - S["r_wing3"] << pref.r_wing3 - S["g_wing3"] << pref.g_wing3 - S["b_wing3"] << pref.b_wing3 - S["digitigrade"] << pref.digitigrade +/datum/category_item/player_setup_item/general/body/save_character(list/save_data) + save_data["species"] = pref.species + save_data["hair_red"] = pref.r_hair + save_data["hair_green"] = pref.g_hair + save_data["hair_blue"] = pref.b_hair + save_data["grad_red"] = pref.r_grad + save_data["grad_green"] = pref.g_grad + save_data["grad_blue"] = pref.b_grad + save_data["facial_red"] = pref.r_facial + save_data["facial_green"] = pref.g_facial + save_data["facial_blue"] = pref.b_facial + save_data["skin_tone"] = pref.s_tone + save_data["skin_red"] = pref.r_skin + save_data["skin_green"] = pref.g_skin + save_data["skin_blue"] = pref.b_skin + save_data["hair_style_name"] = pref.h_style + save_data["facial_style_name"] = pref.f_style + save_data["grad_style_name"] = pref.grad_style + save_data["eyes_red"] = pref.r_eyes + save_data["eyes_green"] = pref.g_eyes + save_data["eyes_blue"] = pref.b_eyes + save_data["b_type"] = pref.b_type + save_data["disabilities"] = pref.disabilities + save_data["organ_data"] = pref.organ_data + save_data["rlimb_data"] = pref.rlimb_data + save_data["body_markings"] = pref.body_markings + save_data["synth_color"] = pref.synth_color + save_data["synth_red"] = pref.r_synth + save_data["synth_green"] = pref.g_synth + save_data["synth_blue"] = pref.b_synth + save_data["synth_markings"] = pref.synth_markings + save_data["bgstate"] = pref.bgstate + save_data["body_descriptors"] = pref.body_descriptors + //YWadd start + save_data["Wingdings"] = pref.wingdings + save_data["colorblind_mono"] = pref.colorblind_mono + save_data["colorblind_vulp"] = pref.colorblind_vulp + save_data["colorblind_taj"] = pref.colorblind_taj + save_data["haemophilia"] = pref.haemophilia + //YWadd end + save_data["ear_style"] = pref.ear_style + save_data["r_ears"] = pref.r_ears + save_data["g_ears"] = pref.g_ears + save_data["b_ears"] = pref.b_ears + save_data["r_ears2"] = pref.r_ears2 + save_data["g_ears2"] = pref.g_ears2 + save_data["b_ears2"] = pref.b_ears2 + save_data["r_ears3"] = pref.r_ears3 + save_data["g_ears3"] = pref.g_ears3 + save_data["b_ears3"] = pref.b_ears3 + save_data["tail_style"] = pref.tail_style + save_data["r_tail"] = pref.r_tail + save_data["g_tail"] = pref.g_tail + save_data["b_tail"] = pref.b_tail + save_data["r_tail2"] = pref.r_tail2 + save_data["g_tail2"] = pref.g_tail2 + save_data["b_tail2"] = pref.b_tail2 + save_data["r_tail3"] = pref.r_tail3 + save_data["g_tail3"] = pref.g_tail3 + save_data["b_tail3"] = pref.b_tail3 + save_data["wing_style"] = pref.wing_style + save_data["r_wing"] = pref.r_wing + save_data["g_wing"] = pref.g_wing + save_data["b_wing"] = pref.b_wing + save_data["r_wing2"] = pref.r_wing2 + save_data["g_wing2"] = pref.g_wing2 + save_data["b_wing2"] = pref.b_wing2 + save_data["r_wing3"] = pref.r_wing3 + save_data["g_wing3"] = pref.g_wing3 + save_data["b_wing3"] = pref.b_wing3 + save_data["digitigrade"] = pref.digitigrade -/datum/category_item/player_setup_item/general/body/sanitize_character(var/savefile/S) +/datum/category_item/player_setup_item/general/body/sanitize_character() if(!pref.species || !(pref.species in GLOB.playable_species)) pref.species = SPECIES_HUMAN pref.r_hair = sanitize_integer(pref.r_hair, 0, 255, initial(pref.r_hair)) @@ -539,11 +538,17 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O ++ind if(ind > 1) . += ", " - var/datum/robolimb/R - if(pref.rlimb_data[name] && all_robolimbs[pref.rlimb_data[name]]) - R = all_robolimbs[pref.rlimb_data[name]] + + var/datum/robolimb/R = basic_robolimb + var/key = pref.rlimb_data[name] + if(!istext(key)) + log_debug("Bad rlimb_data for [key_name(pref.client)], [name] was set to [key]") + to_chat(usr, span_warning("Error loading robot limb data for `[name]`, clearing pref.")) + pref.rlimb_data -= name else - R = basic_robolimb + R = LAZYACCESS(all_robolimbs, key) + if(!istype(R)) + R = basic_robolimb . += "\t[R.company] [organ_name] prosthesis" else if(status == "amputated") ++ind diff --git a/code/modules/client/preference_setup/general/04_equipment.dm b/code/modules/client/preference_setup/general/04_equipment.dm index c40eecdc48..9746bcc81a 100644 --- a/code/modules/client/preference_setup/general/04_equipment.dm +++ b/code/modules/client/preference_setup/general/04_equipment.dm @@ -6,23 +6,23 @@ name = "Clothing" sort_order = 4 -/datum/category_item/player_setup_item/general/equipment/load_character(var/savefile/S) - S["all_underwear"] >> pref.all_underwear - S["all_underwear_metadata"] >> pref.all_underwear_metadata - S["backbag"] >> pref.backbag - S["pdachoice"] >> pref.pdachoice - S["communicator_visibility"] >> pref.communicator_visibility - S["ttone"] >> pref.ringtone // CHOMPEdit - We use ttone in the pref so that it doesnt get reset - //S["shoe_hater"] >> pref.shoe_hater //RS ADD //CHOMPRemove, remove RS No shoes +/datum/category_item/player_setup_item/general/equipment/load_character(list/save_data) + pref.all_underwear = save_data["all_underwear"] + pref.all_underwear_metadata = save_data["all_underwear_metadata"] + pref.backbag = save_data["backbag"] + pref.pdachoice = save_data["pdachoice"] + pref.communicator_visibility = save_data["communicator_visibility"] + pref.ringtone = save_data["ttone"] // CHOMPEdit - We use ttone in the pref so that it doesnt get reset + //pref.shoe_hater = save_data["shoe_hater"] //CHOMPRemove, remove RS No shoes -/datum/category_item/player_setup_item/general/equipment/save_character(var/savefile/S) - S["all_underwear"] << pref.all_underwear - S["all_underwear_metadata"] << pref.all_underwear_metadata - S["backbag"] << pref.backbag - S["pdachoice"] << pref.pdachoice - S["communicator_visibility"] << pref.communicator_visibility - S["ttone"] << pref.ringtone // CHOMPEdit - We use ttone in the pref so that it doesnt get reset - //S["shoe_hater"] << pref.shoe_hater //RS ADD //CHOMPRemove, remove RS No shoes +/datum/category_item/player_setup_item/general/equipment/save_character(list/save_data) + save_data["all_underwear"] = pref.all_underwear + save_data["all_underwear_metadata"] = pref.all_underwear_metadata + save_data["backbag"] = pref.backbag + save_data["pdachoice"] = pref.pdachoice + save_data["communicator_visibility"] = pref.communicator_visibility + save_data["ttone"] = pref.ringtone // CHOMPEdit - We use ttone in the pref so that it doesnt get reset + //save_data["shoe_hater"] = pref.shoe_hater //CHOMPRemove, remove RS No shoes var/global/list/valid_ringtones = list( "beep", diff --git a/code/modules/client/preference_setup/general/05_background.dm b/code/modules/client/preference_setup/general/05_background.dm index 5a8f945e10..f52c928cef 100644 --- a/code/modules/client/preference_setup/general/05_background.dm +++ b/code/modules/client/preference_setup/general/05_background.dm @@ -1,28 +1,27 @@ /datum/category_item/player_setup_item/general/background name = "Background" sort_order = 5 +/datum/category_item/player_setup_item/general/background/load_character(list/save_data) + pref.med_record = save_data["med_record"] + pref.sec_record = save_data["sec_record"] + pref.gen_record = save_data["gen_record"] + pref.home_system = save_data["home_system"] + pref.birthplace = save_data["birthplace"] + pref.citizenship = save_data["citizenship"] + pref.faction = save_data["faction"] + pref.religion = save_data["religion"] + pref.economic_status = save_data["economic_status"] -/datum/category_item/player_setup_item/general/background/load_character(var/savefile/S) - S["med_record"] >> pref.med_record - S["sec_record"] >> pref.sec_record - S["gen_record"] >> pref.gen_record - S["home_system"] >> pref.home_system - S["birthplace"] >> pref.birthplace - S["citizenship"] >> pref.citizenship - S["faction"] >> pref.faction - S["religion"] >> pref.religion - S["economic_status"] >> pref.economic_status - -/datum/category_item/player_setup_item/general/background/save_character(var/savefile/S) - S["med_record"] << pref.med_record - S["sec_record"] << pref.sec_record - S["gen_record"] << pref.gen_record - S["home_system"] << pref.home_system - S["birthplace"] << pref.birthplace - S["citizenship"] << pref.citizenship - S["faction"] << pref.faction - S["religion"] << pref.religion - S["economic_status"] << pref.economic_status +/datum/category_item/player_setup_item/general/background/save_character(list/save_data) + save_data["med_record"] = pref.med_record + save_data["sec_record"] = pref.sec_record + save_data["gen_record"] = pref.gen_record + save_data["home_system"] = pref.home_system + save_data["birthplace"] = pref.birthplace + save_data["citizenship"] = pref.citizenship + save_data["faction"] = pref.faction + save_data["religion"] = pref.religion + save_data["economic_status"] = pref.economic_status /datum/category_item/player_setup_item/general/background/sanitize_character() if(!pref.home_system) pref.home_system = "Unset" diff --git a/code/modules/client/preference_setup/general/06_flavor.dm b/code/modules/client/preference_setup/general/06_flavor.dm index 8a7f5fcd12..3f07eb65c3 100644 --- a/code/modules/client/preference_setup/general/06_flavor.dm +++ b/code/modules/client/preference_setup/general/06_flavor.dm @@ -2,37 +2,37 @@ name = "Flavor" sort_order = 6 -/datum/category_item/player_setup_item/general/flavor/load_character(var/savefile/S) - S["flavor_texts_general"] >> pref.flavor_texts["general"] - S["flavor_texts_head"] >> pref.flavor_texts["head"] - S["flavor_texts_face"] >> pref.flavor_texts["face"] - S["flavor_texts_eyes"] >> pref.flavor_texts["eyes"] - S["flavor_texts_torso"] >> pref.flavor_texts["torso"] - S["flavor_texts_arms"] >> pref.flavor_texts["arms"] - S["flavor_texts_hands"] >> pref.flavor_texts["hands"] - S["flavor_texts_legs"] >> pref.flavor_texts["legs"] - S["flavor_texts_feet"] >> pref.flavor_texts["feet"] - S["custom_link"] >> pref.custom_link +/datum/category_item/player_setup_item/general/flavor/load_character(list/save_data) + pref.flavor_texts["general"] = save_data["flavor_texts_general"] + pref.flavor_texts["head"] = save_data["flavor_texts_head"] + pref.flavor_texts["face"] = save_data["flavor_texts_face"] + pref.flavor_texts["eyes"] = save_data["flavor_texts_eyes"] + pref.flavor_texts["torso"] = save_data["flavor_texts_torso"] + pref.flavor_texts["arms"] = save_data["flavor_texts_arms"] + pref.flavor_texts["hands"] = save_data["flavor_texts_hands"] + pref.flavor_texts["legs"] = save_data["flavor_texts_legs"] + pref.flavor_texts["feet"] = save_data["flavor_texts_feet"] + pref.custom_link = save_data["custom_link"] //Flavour text for robots. - S["flavour_texts_robot_Default"] >> pref.flavour_texts_robot["Default"] + pref.flavour_texts_robot["Default"] = save_data["flavour_texts_robot_Default"] for(var/module in robot_module_types) - S["flavour_texts_robot_[module]"] >> pref.flavour_texts_robot[module] + pref.flavour_texts_robot[module] = save_data["flavour_texts_robot_[module]"] -/datum/category_item/player_setup_item/general/flavor/save_character(var/savefile/S) - S["flavor_texts_general"] << pref.flavor_texts["general"] - S["flavor_texts_head"] << pref.flavor_texts["head"] - S["flavor_texts_face"] << pref.flavor_texts["face"] - S["flavor_texts_eyes"] << pref.flavor_texts["eyes"] - S["flavor_texts_torso"] << pref.flavor_texts["torso"] - S["flavor_texts_arms"] << pref.flavor_texts["arms"] - S["flavor_texts_hands"] << pref.flavor_texts["hands"] - S["flavor_texts_legs"] << pref.flavor_texts["legs"] - S["flavor_texts_feet"] << pref.flavor_texts["feet"] - S["custom_link"] << pref.custom_link +/datum/category_item/player_setup_item/general/flavor/save_character(list/save_data) + save_data["flavor_texts_general"] = pref.flavor_texts["general"] + save_data["flavor_texts_head"] = pref.flavor_texts["head"] + save_data["flavor_texts_face"] = pref.flavor_texts["face"] + save_data["flavor_texts_eyes"] = pref.flavor_texts["eyes"] + save_data["flavor_texts_torso"] = pref.flavor_texts["torso"] + save_data["flavor_texts_arms"] = pref.flavor_texts["arms"] + save_data["flavor_texts_hands"] = pref.flavor_texts["hands"] + save_data["flavor_texts_legs"] = pref.flavor_texts["legs"] + save_data["flavor_texts_feet"] = pref.flavor_texts["feet"] + save_data["custom_link"] = pref.custom_link - S["flavour_texts_robot_Default"] << pref.flavour_texts_robot["Default"] + save_data["flavour_texts_robot_Default"] = pref.flavour_texts_robot["Default"] for(var/module in robot_module_types) - S["flavour_texts_robot_[module]"] << pref.flavour_texts_robot[module] + save_data["flavour_texts_robot_[module]"] = pref.flavour_texts_robot[module] /datum/category_item/player_setup_item/general/flavor/sanitize_character() return diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm index 161fd00270..c0e16237a3 100644 --- a/code/modules/client/preference_setup/global/01_ui.dm +++ b/code/modules/client/preference_setup/global/01_ui.dm @@ -2,41 +2,41 @@ name = "UI" sort_order = 1 -/datum/category_item/player_setup_item/player_global/ui/load_preferences(var/savefile/S) - S["UI_style"] >> pref.UI_style - S["UI_style_color"] >> pref.UI_style_color - S["UI_style_alpha"] >> pref.UI_style_alpha - S["ooccolor"] >> pref.ooccolor - S["tooltipstyle"] >> pref.tooltipstyle - S["client_fps"] >> pref.client_fps - S["ambience_freq"] >> pref.ambience_freq - S["ambience_chance"] >> pref.ambience_chance - S["tgui_fancy"] >> pref.tgui_fancy - S["tgui_lock"] >> pref.tgui_lock - S["tgui_input_mode"] >> pref.tgui_input_mode - S["tgui_large_buttons"] >> pref.tgui_large_buttons - S["tgui_swapped_buttons"] >> pref.tgui_swapped_buttons - S["obfuscate_key"] >> pref.obfuscate_key - S["obfuscate_job"] >> pref.obfuscate_job - S["chat_timestamp"] >> pref.chat_timestamp +/datum/category_item/player_setup_item/player_global/ui/load_preferences(datum/json_savefile/savefile) + pref.UI_style = savefile.get_entry("UI_style") + pref.UI_style_color = savefile.get_entry("UI_style_color") + pref.UI_style_alpha = savefile.get_entry("UI_style_alpha") + pref.ooccolor = savefile.get_entry("ooccolor") + pref.tooltipstyle = savefile.get_entry("tooltipstyle") + pref.client_fps = savefile.get_entry("client_fps") + pref.ambience_freq = savefile.get_entry("ambience_freq") + pref.ambience_chance = savefile.get_entry("ambience_chance") + pref.tgui_fancy = savefile.get_entry("tgui_fancy") + pref.tgui_lock = savefile.get_entry("tgui_lock") + pref.tgui_input_mode = savefile.get_entry("tgui_input_mode") + pref.tgui_large_buttons = savefile.get_entry("tgui_large_buttons") + pref.tgui_swapped_buttons = savefile.get_entry("tgui_swapped_buttons") + pref.obfuscate_key = savefile.get_entry("obfuscate_key") + pref.obfuscate_job = savefile.get_entry("obfuscate_job") + pref.chat_timestamp = savefile.get_entry("chat_timestamp") -/datum/category_item/player_setup_item/player_global/ui/save_preferences(var/savefile/S) - S["UI_style"] << pref.UI_style - S["UI_style_color"] << pref.UI_style_color - S["UI_style_alpha"] << pref.UI_style_alpha - S["ooccolor"] << pref.ooccolor - S["tooltipstyle"] << pref.tooltipstyle - S["client_fps"] << pref.client_fps - S["ambience_freq"] << pref.ambience_freq - S["ambience_chance"] << pref.ambience_chance - S["tgui_fancy"] << pref.tgui_fancy - S["tgui_lock"] << pref.tgui_lock - S["tgui_input_mode"] << pref.tgui_input_mode - S["tgui_large_buttons"] << pref.tgui_large_buttons - S["tgui_swapped_buttons"] << pref.tgui_swapped_buttons - S["obfuscate_key"] << pref.obfuscate_key - S["obfuscate_job"] << pref.obfuscate_job - S["chat_timestamp"] << pref.chat_timestamp +/datum/category_item/player_setup_item/player_global/ui/save_preferences(datum/json_savefile/savefile) + savefile.set_entry("UI_style", pref.UI_style) + savefile.set_entry("UI_style_color", pref.UI_style_color) + savefile.set_entry("UI_style_alpha", pref.UI_style_alpha) + savefile.set_entry("ooccolor", pref.ooccolor) + savefile.set_entry("tooltipstyle", pref.tooltipstyle) + savefile.set_entry("client_fps", pref.client_fps) + savefile.set_entry("ambience_freq", pref.ambience_freq) + savefile.set_entry("ambience_chance", pref.ambience_chance) + savefile.set_entry("tgui_fancy", pref.tgui_fancy) + savefile.set_entry("tgui_lock", pref.tgui_lock) + savefile.set_entry("tgui_input_mode", pref.tgui_input_mode) + savefile.set_entry("tgui_large_buttons", pref.tgui_large_buttons) + savefile.set_entry("tgui_swapped_buttons", pref.tgui_swapped_buttons) + savefile.set_entry("obfuscate_key", pref.obfuscate_key) + savefile.set_entry("obfuscate_job", pref.obfuscate_job) + savefile.set_entry("chat_timestamp", pref.chat_timestamp) /datum/category_item/player_setup_item/player_global/ui/sanitize_preferences() pref.UI_style = sanitize_inlist(pref.UI_style, all_ui_styles, initial(pref.UI_style)) diff --git a/code/modules/client/preference_setup/global/02_settings.dm b/code/modules/client/preference_setup/global/02_settings.dm index b39caf0a42..9ccd7b1c21 100644 --- a/code/modules/client/preference_setup/global/02_settings.dm +++ b/code/modules/client/preference_setup/global/02_settings.dm @@ -1,141 +1,20 @@ -/datum/preferences - var/preferences_enabled = null - var/preferences_disabled = null - /datum/category_item/player_setup_item/player_global/settings name = "Settings" sort_order = 2 -/datum/category_item/player_setup_item/player_global/settings/load_preferences(var/savefile/S) - S["lastchangelog"] >> pref.lastchangelog - S["lastnews"] >> pref.lastnews - S["lastlorenews"] >> pref.lastlorenews - S["default_slot"] >> pref.default_slot - S["preferences"] >> pref.preferences_enabled - S["preferences_disabled"] >> pref.preferences_disabled +/datum/category_item/player_setup_item/player_global/settings/load_preferences(datum/json_savefile/savefile) + pref.lastchangelog = savefile.get_entry("lastchangelog") + pref.lastnews = savefile.get_entry("lastnews") + pref.lastlorenews = savefile.get_entry("lastlorenews") + pref.default_slot = savefile.get_entry("default_slot") -/datum/category_item/player_setup_item/player_global/settings/save_preferences(var/savefile/S) - S["lastchangelog"] << pref.lastchangelog - S["lastnews"] << pref.lastnews - S["lastlorenews"] << pref.lastlorenews - S["default_slot"] << pref.default_slot - S["preferences"] << pref.preferences_enabled - S["preferences_disabled"] << pref.preferences_disabled +/datum/category_item/player_setup_item/player_global/settings/save_preferences(datum/json_savefile/savefile) + savefile.set_entry("lastchangelog", pref.lastchangelog) + savefile.set_entry("lastnews", pref.lastnews) + savefile.set_entry("lastlorenews", pref.lastlorenews) + savefile.set_entry("default_slot", pref.default_slot) /datum/category_item/player_setup_item/player_global/settings/sanitize_preferences() - // Ensure our preferences are lists. - if(!istype(pref.preferences_enabled, /list)) - pref.preferences_enabled = list() - if(!istype(pref.preferences_disabled, /list)) - pref.preferences_disabled = list() - - // Arrange preferences that have never been enabled/disabled. - var/list/client_preference_keys = list() - for(var/datum/client_preference/client_pref as anything in get_client_preferences()) - client_preference_keys += client_pref.key - if((client_pref.key in pref.preferences_enabled) || (client_pref.key in pref.preferences_disabled)) - continue - - if(client_pref.enabled_by_default) - pref.preferences_enabled += client_pref.key - else - pref.preferences_disabled += client_pref.key - - // Clean out preferences that no longer exist. - for(var/key in pref.preferences_enabled) - if(!(key in client_preference_keys)) - pref.preferences_enabled -= key - for(var/key in pref.preferences_disabled) - if(!(key in client_preference_keys)) - pref.preferences_disabled -= key - pref.lastchangelog = sanitize_text(pref.lastchangelog, initial(pref.lastchangelog)) pref.lastnews = sanitize_text(pref.lastnews, initial(pref.lastnews)) pref.default_slot = sanitize_integer(pref.default_slot, 1, CONFIG_GET(number/character_slots), initial(pref.default_slot)) // CHOMPEdit - -/datum/category_item/player_setup_item/player_global/settings/content(var/mob/user) - . = list() - . += "Preferences
" - . += "" - var/mob/pref_mob = preference_mob() - for(var/datum/client_preference/client_pref as anything in get_client_preferences()) - if(!client_pref.may_toggle(pref_mob)) - continue - - . += "" - if(pref_mob.is_preference_enabled(client_pref.key)) - . += "" - else - . += "" - . += "" - - . += "
[client_pref.description]: [client_pref.enabled_description] [client_pref.disabled_description][client_pref.enabled_description] [client_pref.disabled_description]
" - return jointext(., "") - -/datum/category_item/player_setup_item/player_global/settings/OnTopic(var/href,var/list/href_list, var/mob/user) - var/mob/pref_mob = preference_mob() - if(href_list["toggle_on"]) - . = pref_mob.set_preference(href_list["toggle_on"], TRUE) - else if(href_list["toggle_off"]) - . = pref_mob.set_preference(href_list["toggle_off"], FALSE) - if(.) - return TOPIC_REFRESH - - return ..() - -/** - * This can take either a single preference datum or a list of preferences, and will return true if *all* preferences in the arguments are enabled. - */ -/client/proc/is_preference_enabled(var/preference) - if(!islist(preference)) - preference = list(preference) - for(var/p in preference) - var/datum/client_preference/cp = get_client_preference(p) - if(!prefs || !cp || !istype(cp, /datum/client_preference) || !(cp.key in prefs.preferences_enabled)) - return FALSE - return TRUE - -/client/proc/set_preference(var/preference, var/set_preference) - var/datum/client_preference/cp = get_client_preference(preference) - if(!cp) - return FALSE - preference = cp.key - - if(set_preference && !(preference in prefs.preferences_enabled)) - return toggle_preference(cp) - else if(!set_preference && (preference in prefs.preferences_enabled)) - return toggle_preference(cp) - -/client/proc/toggle_preference(var/preference, var/set_preference) - var/datum/client_preference/cp = get_client_preference(preference) - if(!cp) - return FALSE - preference = cp.key - - var/enabled - if(preference in prefs.preferences_disabled) - prefs.preferences_enabled |= preference - prefs.preferences_disabled -= preference - enabled = TRUE - . = TRUE - else if(preference in prefs.preferences_enabled) - prefs.preferences_enabled -= preference - prefs.preferences_disabled |= preference - enabled = FALSE - . = TRUE - if(.) - cp.toggled(mob, enabled) - -/mob/proc/is_preference_enabled(var/preference) - if(!client) - return FALSE - return client.is_preference_enabled(preference) - -/mob/proc/set_preference(var/preference, var/set_preference) - if(!client) - return FALSE - if(!client.prefs) - log_debug("Client prefs found to be null for mob [src] and client [ckey], this should be investigated.") - return FALSE - - return client.set_preference(preference, set_preference) diff --git a/code/modules/client/preference_setup/global/03_pai.dm b/code/modules/client/preference_setup/global/03_pai.dm index c4d81e552b..d9b20873d8 100644 --- a/code/modules/client/preference_setup/global/03_pai.dm +++ b/code/modules/client/preference_setup/global/03_pai.dm @@ -4,7 +4,7 @@ var/datum/paiCandidate/candidate -/datum/category_item/player_setup_item/player_global/pai/load_preferences(var/savefile/S) +/datum/category_item/player_setup_item/player_global/pai/load_preferences(datum/json_savefile/savefile) if(!candidate) candidate = new() var/preference_mob = preference_mob() @@ -18,7 +18,7 @@ candidate.savefile_load(preference_mob) -/datum/category_item/player_setup_item/player_global/pai/save_preferences(var/savefile/S) +/datum/category_item/player_setup_item/player_global/pai/save_preferences(datum/json_savefile/savefile) if(!candidate) return diff --git a/code/modules/client/preference_setup/global/04_ooc.dm b/code/modules/client/preference_setup/global/04_ooc.dm index e695bee677..c375107754 100644 --- a/code/modules/client/preference_setup/global/04_ooc.dm +++ b/code/modules/client/preference_setup/global/04_ooc.dm @@ -2,12 +2,12 @@ name = "OOC" sort_order = 4 -/datum/category_item/player_setup_item/player_global/ooc/load_preferences(var/savefile/S) - S["ignored_players"] >> pref.ignored_players +/datum/category_item/player_setup_item/player_global/ooc/load_preferences(datum/json_savefile/savefile) + pref.ignored_players = savefile.get_entry("ignored_players") -/datum/category_item/player_setup_item/player_global/ooc/save_preferences(var/savefile/S) - S["ignored_players"] << pref.ignored_players +/datum/category_item/player_setup_item/player_global/ooc/save_preferences(datum/json_savefile/savefile) + savefile.set_entry("ignored_players", pref.ignored_players) /* /datum/category_item/player_setup_item/player_global/ooc/sanitize_preferences() @@ -39,4 +39,4 @@ return TOPIC_REFRESH return ..() -*/ \ No newline at end of file +*/ diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm deleted file mode 100644 index 912dd36460..0000000000 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ /dev/null @@ -1,503 +0,0 @@ -var/list/_client_preferences -var/list/_client_preferences_by_key -var/list/_client_preferences_by_type - -/proc/get_client_preferences() - if(!_client_preferences) - _client_preferences = list() - for(var/datum/client_preference/client_type as anything in subtypesof(/datum/client_preference)) - if(initial(client_type.description)) - _client_preferences += new client_type() - return _client_preferences - -/proc/get_client_preference(var/datum/client_preference/preference) - if(istype(preference)) - return preference - if(ispath(preference)) - return get_client_preference_by_type(preference) - return get_client_preference_by_key(preference) - -/proc/get_client_preference_by_key(var/preference) - if(!_client_preferences_by_key) - _client_preferences_by_key = list() - for(var/datum/client_preference/client_pref as anything in get_client_preferences()) - _client_preferences_by_key[client_pref.key] = client_pref - return _client_preferences_by_key[preference] - -/proc/get_client_preference_by_type(var/preference) - if(!_client_preferences_by_type) - _client_preferences_by_type = list() - for(var/datum/client_preference/client_pref as anything in get_client_preferences()) - _client_preferences_by_type[client_pref.type] = client_pref - return _client_preferences_by_type[preference] - -/datum/client_preference - var/description - var/key - var/enabled_by_default = TRUE - var/enabled_description = "Yes" - var/disabled_description = "No" - -/datum/client_preference/proc/may_toggle(var/mob/preference_mob) - return TRUE - -/datum/client_preference/proc/toggled(var/mob/preference_mob, var/enabled) - return - -/********************* -* Player Preferences * -*********************/ - -/datum/client_preference/play_admin_midis - description ="Play admin midis" - key = "SOUND_MIDI" - -/datum/client_preference/play_lobby_music - description ="Play lobby music" - key = "SOUND_LOBBY" - -/datum/client_preference/play_lobby_music/toggled(var/mob/preference_mob, var/enabled) - if(!preference_mob.client || !preference_mob.client.media) - return - - if(enabled) - preference_mob.client.playtitlemusic() - else - preference_mob.client.media.stop_music() - -/datum/client_preference/play_ambiance - description ="Play ambience" - key = "SOUND_AMBIENCE" - -/datum/client_preference/play_ambiance/toggled(var/mob/preference_mob, var/enabled) - if(!enabled) - preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 1) - preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2) -//VOREStation Add - Need to put it here because it should be ordered riiiight here. -/datum/client_preference/play_jukebox - description ="Play jukebox music" - key = "SOUND_JUKEBOX" - -/datum/client_preference/play_jukebox/toggled(var/mob/preference_mob, var/enabled) - if(!enabled) - preference_mob.stop_all_music() - else - preference_mob.update_music() - -/datum/client_preference/eating_noises - description = "Eating Noises" - key = "EATING_NOISES" - enabled_description = "Noisy" - disabled_description = "Silent" - -/datum/client_preference/digestion_noises - description = "Digestion Noises" - key = "DIGEST_NOISES" - enabled_description = "Noisy" - disabled_description = "Silent" - -/datum/client_preference/belch_noises // Belching noises - pref toggle for 'em - description = "Burping" - key = "BELCH_NOISES" - enabled_description = "Noisy" - disabled_description = "Silent" - enabled_by_default = FALSE //CHOMPedit - -/datum/client_preference/emote_noises - description = "Emote Noises" //MERP - key = "EMOTE_NOISES" - enabled_description = "Noisy" - disabled_description = "Silent" -/datum/client_preference/whisubtle_vis - description = "Whi/Subtles Ghost Visible" - key = "WHISUBTLE_VIS" - enabled_description = "Visible" - disabled_description = "Hidden" - enabled_by_default = FALSE - -/datum/client_preference/ghost_see_whisubtle - description = "See subtles/whispers as ghost" - key = "GHOST_SEE_WHISUBTLE" - enabled_description = "Visible" - disabled_description = "Hidden" - enabled_by_default = TRUE -//VOREStation Add End -/datum/client_preference/weather_sounds - description ="Weather sounds" - key = "SOUND_WEATHER" - enabled_description = "Audible" - disabled_description = "Silent" - -/datum/client_preference/supermatter_hum - description ="Supermatter hum" - key = "SOUND_SUPERMATTER" - enabled_description = "Audible" - disabled_description = "Silent" - -/datum/client_preference/ghost_ears - description ="Ghost ears" - key = "CHAT_GHOSTEARS" - enabled_description = "All Speech" - disabled_description = "Nearby" - -/datum/client_preference/ghost_sight - description ="Ghost sight" - key = "CHAT_GHOSTSIGHT" - enabled_description = "All Emotes" - disabled_description = "Nearby" - -/datum/client_preference/ghost_radio - description ="Ghost radio" - key = "CHAT_GHOSTRADIO" - enabled_description = "All Chatter" - disabled_description = "Nearby" - -/datum/client_preference/chat_tags - description ="Chat tags" - key = "CHAT_SHOWICONS" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/air_pump_noise - description ="Air Pump Ambient Noise" - key = "SOUND_AIRPUMP" - enabled_description = "Audible" - disabled_description = "Silent" - -/datum/client_preference/looping_alarms // CHOMPStation Add: Looping Alarms - description ="Looping Alarm Sounds" - key = "SOUND_ALARMLOOP" - enabled_description = "Audible" - disabled_description = "Silent" - -/datum/client_preference/fridge_hum // CHOMPStation Add: Misc Sounds - description ="Fridge Humming" - key = "SOUND_FRIDGEHUM" - enabled_description = "Audible" - disabled_description = "Silent" - -/datum/client_preference/old_door_sounds - description ="Old Door Sounds" - key = "SOUND_OLDDOORS" - enabled_description = "Old" - disabled_description = "New" - enabled_by_default = FALSE - -/datum/client_preference/department_door_sounds - description ="Department-Specific Door Sounds" - key = "SOUND_DEPARTMENTDOORS" - enabled_description = "Enabled" - disabled_description = "Disabled" - -/datum/client_preference/pickup_sounds - description = "Picked Up Item Sounds" - key = "SOUND_PICKED" - enabled_description = "Enabled" - disabled_description = "Disabled" - -/datum/client_preference/drop_sounds - description = "Dropped Item Sounds" - key = "SOUND_DROPPED" - enabled_description = "Enabled" - disabled_description = "Disabled" - -/datum/client_preference/mob_tooltips - description ="Mob tooltips" - key = "MOB_TOOLTIPS" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/inv_tooltips - description ="Inventory tooltips" - key = "INV_TOOLTIPS" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/attack_icons - description ="Attack icons" - key = "ATTACK_ICONS" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/precision_placement - description ="Precision Placement" - key = "PRECISE_PLACEMENT" - enabled_description = "Active" - disabled_description = "Inactive" - -/datum/client_preference/hotkeys_default - description ="Hotkeys Default" - key = "HUD_HOTKEYS" - enabled_description = "Enabled" - disabled_description = "Disabled" - enabled_by_default = TRUE // Backwards compatibility //CHOMP Edit: It's 2020, use your WASD keys by default. Flipped to True. - -/datum/client_preference/show_typing_indicator - description ="Typing indicator" - key = "SHOW_TYPING" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/show_typing_indicator/toggled(var/mob/preference_mob, var/enabled) - if(!enabled) - preference_mob.client?.stop_thinking() - -/datum/client_preference/show_typing_indicator_subtle - description ="Typing indicator (subtle)" - key = "SHOW_TYPING_SUBTLE" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/show_ooc - description ="OOC chat" - key = "CHAT_OOC" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/show_looc - description ="LOOC chat" - key = "CHAT_LOOC" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/show_dsay - description ="Dead chat" - key = "CHAT_DEAD" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/check_mention - description ="Emphasize Name Mention" - key = "CHAT_MENTION" - enabled_description = "Emphasize" - disabled_description = "Normal" - -/datum/client_preference/show_progress_bar - description ="Progress Bar" - key = "SHOW_PROGRESS" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/safefiring - description = "Gun Firing Intent Requirement" - key = "SAFE_FIRING" - enabled_description = "Safe" - disabled_description = "Dangerous" - -/datum/client_preference/browser_style - description = "Fake NanoUI Browser Style" - key = "BROWSER_STYLED" - enabled_description = "Fancy" - disabled_description = "Plain" - -/datum/client_preference/ambient_occlusion - description = "Fake Ambient Occlusion" - key = "AMBIENT_OCCLUSION_PREF" - enabled_by_default = FALSE - enabled_description = "On" - disabled_description = "Off" - -/datum/client_preference/ambient_occlusion/toggled(var/mob/preference_mob, var/enabled) - . = ..() - if(preference_mob && preference_mob.plane_holder) - var/datum/plane_holder/PH = preference_mob.plane_holder - PH.set_ao(VIS_OBJS, enabled) - PH.set_ao(VIS_MOBS, enabled) - -/datum/client_preference/instrument_toggle - description ="Hear In-game Instruments" - key = "SOUND_INSTRUMENT" - -/datum/client_preference/vchat_enable - description = "Enable/Disable TGChat" - key = "VCHAT_ENABLE" - enabled_description = "Enabled" - disabled_description = "Disabled" - -/datum/client_preference/status_indicators - description = "Status Indicators" - key = "SHOW_STATUS" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/radio_sounds - description = "Radio Sounds" - key = "RADIO_SOUNDS" - enabled_description = "On" - disabled_description = "Off" - -/datum/client_preference/say_sounds - description = "Say Sounds" - key = "SAY_SOUNDS" - enabled_description = "On" - disabled_description = "Off" - -/datum/client_preference/emote_sounds - description = "Me Sounds" - key = "EMOTE_SOUNDS" - enabled_description = "On" - disabled_description = "Off" - -/datum/client_preference/whisper_sounds - description = "Whisper Sounds" - key = "WHISPER_SOUNDS" - enabled_description = "On" - disabled_description = "Off" - -/datum/client_preference/subtle_sounds - description = "Subtle Sounds" - key = "SUBTLE_SOUNDS" - enabled_description = "On" - disabled_description = "Off" - -/datum/client_preference/vore_health_bars - description = "Vore Health Bars" - key = "VORE_HEALTH_BARS" - enabled_description = "Enabled" - disabled_description = "Disabled" - -/datum/client_preference/runechat_mob - description = "Runechat (Mobs)" - key = "RUNECHAT_MOB" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/runechat_obj - description = "Runechat (Objs)" - key = "RUNECHAT_OBJ" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/runechat_border - description = "Runechat Message Border" - key = "RUNECHAT_BORDER" - enabled_description = "Show" - disabled_description = "Hide" - enabled_by_default = TRUE - -/datum/client_preference/runechat_long_messages - description = "Runechat Message Length" - key = "RUNECHAT_LONG" - enabled_description = "Long" - disabled_description = "Short" - enabled_by_default = FALSE - -/datum/client_preference/status_indicators/toggled(mob/preference_mob, enabled) - . = ..() - if(preference_mob && preference_mob.plane_holder) - var/datum/plane_holder/PH = preference_mob.plane_holder - PH.set_vis(VIS_STATUS, enabled) - -/datum/client_preference/show_lore_news - description = "Lore News Popup" - key = "NEWS_POPUP" - enabled_by_default = TRUE - enabled_description = "Popup New On Login" - disabled_description = "Do Nothing" - -/datum/client_preference/play_mentorhelp_ping - description = "Mentorhelps" - key = "SOUND_MENTORHELP" - enabled_description = "Hear" - disabled_description = "Silent" - -/datum/client_preference/player_tips - description = "Receive Tips Periodically" - key = "RECEIVE_TIPS" - enabled_description = "Enabled" - disabled_description = "Disabled" - -/datum/client_preference/pain_frequency - description = "Pain Messages Cooldown" - key = "PAIN_FREQUENCY" - enabled_by_default = FALSE - enabled_description = "Extended" - disabled_description = "Default" - -// CHOMPAdd -/datum/client_preference/sleep_music - description = "Sleeping Music" - key = "SLEEP_MUSIC" - enabled_description = "Audible" - disabled_description = "Silent" -// CHOMPAdd End - -/datum/client_preference/auto_afk - description = "Automatic AFK Status" - key = "AUTO_AFK" - enabled_by_default = TRUE - enabled_description = "Automatic" - disabled_description = "Manual Only" - -/datum/client_preference/tgui_say - description = "TGUI Say: Use TGUI For Say Input" - key = "TGUI_SAY" - enabled_by_default = TRUE - enabled_description = "Yes" - disabled_description = "No" - -/datum/client_preference/tgui_say_light - description = "TGUI Say: Use Light Mode" - key = "TGUI_SAY_LIGHT_MODE" - enabled_by_default = FALSE - enabled_description = "Yes" - disabled_description = "No" - -/******************** -* Staff Preferences * -********************/ -/datum/client_preference/admin/may_toggle(var/mob/preference_mob) - return check_rights(R_ADMIN|R_EVENT, 0, preference_mob) - -/datum/client_preference/mod/may_toggle(var/mob/preference_mob) - return check_rights(R_MOD|R_ADMIN, 0, preference_mob) - -/datum/client_preference/debug/may_toggle(var/mob/preference_mob) - return check_rights(R_DEBUG|R_ADMIN, 0, preference_mob) - -/datum/client_preference/mod/show_attack_logs - description = "Attack Log Messages" - key = "CHAT_ATTACKLOGS" - enabled_description = "Show" - disabled_description = "Hide" - enabled_by_default = FALSE - -/datum/client_preference/debug/show_debug_logs - description = "Debug Log Messages" - key = "CHAT_DEBUGLOGS" - enabled_description = "Show" - disabled_description = "Hide" - enabled_by_default = FALSE - -/datum/client_preference/admin/show_chat_prayers - description = "Chat Prayers" - key = "CHAT_PRAYER" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/holder/may_toggle(var/mob/preference_mob) - return preference_mob && preference_mob.client && preference_mob.client.holder - -/datum/client_preference/holder/play_adminhelp_ping - description = "Adminhelps" - key = "SOUND_ADMINHELP" - enabled_description = "Hear" - disabled_description = "Silent" - -/datum/client_preference/holder/hear_radio - description = "Radio chatter" - key = "CHAT_RADIO" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/holder/show_rlooc - description ="Remote LOOC chat" - key = "CHAT_RLOOC" - enabled_description = "Show" - disabled_description = "Hide" - -/datum/client_preference/holder/show_staff_dsay - description ="Staff Deadchat" - key = "CHAT_ADSAY" - enabled_description = "Show" - disabled_description = "Hide" diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm index dbb424b877..a5c6fd1fdb 100644 --- a/code/modules/client/preference_setup/loadout/loadout.dm +++ b/code/modules/client/preference_setup/loadout/loadout.dm @@ -45,19 +45,19 @@ var/list/gear_datums = list() sort_order = 1 var/current_tab = "General" -/datum/category_item/player_setup_item/loadout/load_character(var/savefile/S) - from_file(S["gear_list"], pref.gear_list) - from_file(S["gear_slot"], pref.gear_slot) +/datum/category_item/player_setup_item/loadout/load_character(list/save_data) + pref.gear_list = save_data["gear_list"] + pref.gear_slot = save_data["gear_slot"] if(pref.gear_list!=null && pref.gear_slot!=null) pref.gear = pref.gear_list["[pref.gear_slot]"] else - from_file(S["gear"], pref.gear) + pref.gear = save_data["gear"] pref.gear_slot = 1 -/datum/category_item/player_setup_item/loadout/save_character(var/savefile/S) +/datum/category_item/player_setup_item/loadout/save_character(list/save_data) pref.gear_list["[pref.gear_slot]"] = pref.gear - to_file(S["gear_list"], pref.gear_list) - to_file(S["gear_slot"], pref.gear_slot) + save_data["gear_list"] = pref.gear_list + save_data["gear_slot"] = pref.gear_slot /datum/category_item/player_setup_item/loadout/proc/valid_gear_choices(var/max_cost) . = list() diff --git a/code/modules/client/preference_setup/occupation/occupation.dm b/code/modules/client/preference_setup/occupation/occupation.dm index fdcd7e875e..6b912f30ca 100644 --- a/code/modules/client/preference_setup/occupation/occupation.dm +++ b/code/modules/client/preference_setup/occupation/occupation.dm @@ -2,51 +2,51 @@ name = "Occupation" sort_order = 1 -/datum/category_item/player_setup_item/occupation/load_character(var/savefile/S) - S["alternate_option"] >> pref.alternate_option - S["job_civilian_high"] >> pref.job_civilian_high - S["job_civilian_med"] >> pref.job_civilian_med - S["job_civilian_low"] >> pref.job_civilian_low - S["job_medsci_high"] >> pref.job_medsci_high - S["job_medsci_med"] >> pref.job_medsci_med - S["job_medsci_low"] >> pref.job_medsci_low - S["job_engsec_high"] >> pref.job_engsec_high - S["job_engsec_med"] >> pref.job_engsec_med - S["job_engsec_low"] >> pref.job_engsec_low +/datum/category_item/player_setup_item/occupation/load_character(list/save_data) + pref.alternate_option = save_data["alternate_option"] + pref.job_civilian_high = save_data["job_civilian_high"] + pref.job_civilian_med = save_data["job_civilian_med"] + pref.job_civilian_low = save_data["job_civilian_low"] + pref.job_medsci_high = save_data["job_medsci_high"] + pref.job_medsci_med = save_data["job_medsci_med"] + pref.job_medsci_low = save_data["job_medsci_low"] + pref.job_engsec_high = save_data["job_engsec_high"] + pref.job_engsec_med = save_data["job_engsec_med"] + pref.job_engsec_low = save_data["job_engsec_low"] //VOREStation Add - S["job_talon_low"] >> pref.job_talon_low - S["job_talon_med"] >> pref.job_talon_med - S["job_talon_high"] >> pref.job_talon_high + pref.job_talon_low = save_data["job_talon_low"] + pref.job_talon_med = save_data["job_talon_med"] + pref.job_talon_high = save_data["job_talon_high"] //VOREStation Add End - S["player_alt_titles"] >> pref.player_alt_titles + pref.player_alt_titles = save_data["player_alt_titles"] //CHOMPStation Add - S["job_other_low"] >> pref.job_other_low - S["job_other_med"] >> pref.job_other_med - S["job_other_high"] >> pref.job_other_high + pref.job_other_low = save_data["job_other_low"] + pref.job_other_med = save_data["job_other_med"] + pref.job_other_high = save_data["job_other_high"] //CHOMPStation Add End -/datum/category_item/player_setup_item/occupation/save_character(var/savefile/S) - S["alternate_option"] << pref.alternate_option - S["job_civilian_high"] << pref.job_civilian_high - S["job_civilian_med"] << pref.job_civilian_med - S["job_civilian_low"] << pref.job_civilian_low - S["job_medsci_high"] << pref.job_medsci_high - S["job_medsci_med"] << pref.job_medsci_med - S["job_medsci_low"] << pref.job_medsci_low - S["job_engsec_high"] << pref.job_engsec_high - S["job_engsec_med"] << pref.job_engsec_med - S["job_engsec_low"] << pref.job_engsec_low +/datum/category_item/player_setup_item/occupation/save_character(list/save_data) + save_data["alternate_option"] = pref.alternate_option + save_data["job_civilian_high"] = pref.job_civilian_high + save_data["job_civilian_med"] = pref.job_civilian_med + save_data["job_civilian_low"] = pref.job_civilian_low + save_data["job_medsci_high"] = pref.job_medsci_high + save_data["job_medsci_med"] = pref.job_medsci_med + save_data["job_medsci_low"] = pref.job_medsci_low + save_data["job_engsec_high"] = pref.job_engsec_high + save_data["job_engsec_med"] = pref.job_engsec_med + save_data["job_engsec_low"] = pref.job_engsec_low //VOREStation Add - S["job_talon_low"] << pref.job_talon_low - S["job_talon_med"] << pref.job_talon_med - S["job_talon_high"] << pref.job_talon_high + save_data["job_talon_low"] = pref.job_talon_low + save_data["job_talon_med"] = pref.job_talon_med + save_data["job_talon_high"] = pref.job_talon_high //VOREStation Add End - S["player_alt_titles"] << pref.player_alt_titles + save_data["player_alt_titles"] = pref.player_alt_titles //CHOMPStation Add - S["job_other_low"] << pref.job_other_low - S["job_other_med"] << pref.job_other_med - S["job_other_high"] << pref.job_other_high - //CHOMPStation Add End + save_data["job_other_low"] = pref.job_other_low + save_data["job_other_med"] = pref.job_other_med + save_data["job_other_high"] = pref.job_other_high + //CHOMPStation Add Endarkens/revert-16279-revert-16253-reprefs /datum/category_item/player_setup_item/occupation/sanitize_character() pref.alternate_option = sanitize_integer(pref.alternate_option, 0, 2, initial(pref.alternate_option)) diff --git a/code/modules/client/preference_setup/preference_setup.dm b/code/modules/client/preference_setup/preference_setup.dm index 5a8e31c324..1aa899e744 100644 --- a/code/modules/client/preference_setup/preference_setup.dm +++ b/code/modules/client/preference_setup/preference_setup.dm @@ -59,21 +59,21 @@ for(var/datum/category_group/player_setup_category/PS in categories) PS.sanitize_setup() -/datum/category_collection/player_setup_collection/proc/load_character(var/savefile/S) +/datum/category_collection/player_setup_collection/proc/load_character(list/save_data) for(var/datum/category_group/player_setup_category/PS in categories) - PS.load_character(S) + PS.load_character(save_data) -/datum/category_collection/player_setup_collection/proc/save_character(var/savefile/S) +/datum/category_collection/player_setup_collection/proc/save_character(list/save_data) for(var/datum/category_group/player_setup_category/PS in categories) - PS.save_character(S) + PS.save_character(save_data) -/datum/category_collection/player_setup_collection/proc/load_preferences(var/savefile/S) +/datum/category_collection/player_setup_collection/proc/load_preferences(datum/json_savefile/savefile) for(var/datum/category_group/player_setup_category/PS in categories) - PS.load_preferences(S) + PS.load_preferences(savefile) -/datum/category_collection/player_setup_collection/proc/save_preferences(var/savefile/S) +/datum/category_collection/player_setup_collection/proc/save_preferences(datum/json_savefile/savefile) for(var/datum/category_group/player_setup_category/PS in categories) - PS.save_preferences(S) + PS.save_preferences(savefile) /datum/category_collection/player_setup_collection/proc/copy_to_mob(var/mob/living/carbon/human/C) for(var/datum/category_group/player_setup_category/PS in categories) @@ -86,6 +86,7 @@ dat += "[PS.name] " // TODO: Check how to properly mark a href/button selected in a classic browser window else dat += "[PS.name] " + dat += "Game Options" return dat /datum/category_collection/player_setup_collection/proc/content(var/mob/user) @@ -105,6 +106,9 @@ selected_category = category . = 1 + else if(href_list["game_prefs"]) + user.client.prefs.tgui_interact(user) + if(.) user.client.prefs.ShowChoices(user) @@ -123,29 +127,29 @@ for(var/datum/category_item/player_setup_item/PI in items) PI.sanitize_character() -/datum/category_group/player_setup_category/proc/load_character(var/savefile/S) +/datum/category_group/player_setup_category/proc/load_character(list/save_data) // Load all data, then sanitize it. // Need due to, for example, the 01_basic module relying on species having been loaded to sanitize correctly but that isn't loaded until module 03_body. for(var/datum/category_item/player_setup_item/PI in items) - PI.load_character(S) + PI.load_character(save_data) -/datum/category_group/player_setup_category/proc/save_character(var/savefile/S) +/datum/category_group/player_setup_category/proc/save_character(list/save_data) // Sanitize all data, then save it for(var/datum/category_item/player_setup_item/PI in items) PI.sanitize_character() for(var/datum/category_item/player_setup_item/PI in items) - PI.save_character(S) + PI.save_character(save_data) -/datum/category_group/player_setup_category/proc/load_preferences(var/savefile/S) +/datum/category_group/player_setup_category/proc/load_preferences(datum/json_savefile/savefile) for(var/datum/category_item/player_setup_item/PI in items) - PI.load_preferences(S) + PI.load_preferences(savefile) -/datum/category_group/player_setup_category/proc/save_preferences(var/savefile/S) +/datum/category_group/player_setup_category/proc/save_preferences(datum/json_savefile/savefile) for(var/datum/category_item/player_setup_item/PI in items) PI.sanitize_preferences() for(var/datum/category_item/player_setup_item/PI in items) - PI.save_preferences(S) + PI.save_preferences(savefile) /datum/category_group/player_setup_category/proc/copy_to_mob(var/mob/living/carbon/human/C) for(var/datum/category_item/player_setup_item/PI in items) @@ -188,25 +192,25 @@ /* * Called when the item is asked to load per character settings */ -/datum/category_item/player_setup_item/proc/load_character(var/savefile/S) +/datum/category_item/player_setup_item/proc/load_character(list/save_data) return /* * Called when the item is asked to save per character settings */ -/datum/category_item/player_setup_item/proc/save_character(var/savefile/S) +/datum/category_item/player_setup_item/proc/save_character(list/save_data) return /* * Called when the item is asked to load user/global settings */ -/datum/category_item/player_setup_item/proc/load_preferences(var/savefile/S) +/datum/category_item/player_setup_item/proc/load_preferences(datum/json_savefile/savefile) return /* * Called when the item is asked to save user/global settings */ -/datum/category_item/player_setup_item/proc/save_preferences(var/savefile/S) +/datum/category_item/player_setup_item/proc/save_preferences(datum/json_savefile/savefile) return /* diff --git a/code/modules/client/preference_setup/skills/skills.dm b/code/modules/client/preference_setup/skills/skills.dm index 645314ab59..eef0512acc 100644 --- a/code/modules/client/preference_setup/skills/skills.dm +++ b/code/modules/client/preference_setup/skills/skills.dm @@ -2,15 +2,15 @@ name = "Skills" sort_order = 1 -/datum/category_item/player_setup_item/skills/load_character(var/savefile/S) - S["skills"] >> pref.skills - S["used_skillpoints"] >> pref.used_skillpoints - S["skill_specialization"] >> pref.skill_specialization +/datum/category_item/player_setup_item/skills/load_character(list/save_data) + pref.skills = save_data["skills"] + pref.used_skillpoints = save_data["used_skillpoints"] + pref.skill_specialization = save_data["skill_specialization"] -/datum/category_item/player_setup_item/skills/save_character(var/savefile/S) - S["skills"] << pref.skills - S["used_skillpoints"] << pref.used_skillpoints - S["skill_specialization"] << pref.skill_specialization +/datum/category_item/player_setup_item/skills/save_character(list/save_data) + save_data["skills"] = pref.skills + save_data["used_skillpoints"] = pref.used_skillpoints + save_data["skill_specialization"] = pref.skill_specialization /datum/category_item/player_setup_item/skills/sanitize_character() if(SKILLS == null) setup_skills() diff --git a/code/modules/client/preference_setup/traits/traits.dm b/code/modules/client/preference_setup/traits/traits.dm index 4102a04f74..60369ff0c0 100644 --- a/code/modules/client/preference_setup/traits/traits.dm +++ b/code/modules/client/preference_setup/traits/traits.dm @@ -35,11 +35,11 @@ var/list/trait_categories = list() // The categories available for the trait men sort_order = 1 var/current_tab = "Physical" -/datum/category_item/player_setup_item/traits/load_character(var/savefile/S) - S["traits"] >> pref.traits +/datum/category_item/player_setup_item/traits/load_character(list/save_data) + pref.traits = save_data["traits"] -/datum/category_item/player_setup_item/traits/save_character(var/savefile/S) - S["traits"] << pref.traits +/datum/category_item/player_setup_item/traits/save_character(list/save_data) + save_data["traits"] = pref.traits /datum/category_item/player_setup_item/traits/content() diff --git a/code/modules/client/preference_setup/volume_sliders/01_volume.dm b/code/modules/client/preference_setup/volume_sliders/01_volume.dm index 1d5910a56e..db790d14a2 100644 --- a/code/modules/client/preference_setup/volume_sliders/01_volume.dm +++ b/code/modules/client/preference_setup/volume_sliders/01_volume.dm @@ -7,11 +7,11 @@ name = "General Volume" sort_order = 1 -/datum/category_item/player_setup_item/volume_sliders/volume/load_preferences(var/savefile/S) - S["volume_channels"] >> pref.volume_channels +/datum/category_item/player_setup_item/volume_sliders/volume/load_preferences(datum/json_savefile/savefile) + pref.volume_channels = savefile.get_entry("volume_channels") -/datum/category_item/player_setup_item/volume_sliders/volume/save_preferences(var/savefile/S) - S["volume_channels"] << pref.volume_channels +/datum/category_item/player_setup_item/volume_sliders/volume/save_preferences(datum/json_savefile/savefile) + savefile.set_entry("volume_channels", pref.volume_channels) /datum/category_item/player_setup_item/volume_sliders/volume/sanitize_preferences() if(isnull(pref.volume_channels)) diff --git a/code/modules/client/preference_setup/volume_sliders/02_media.dm b/code/modules/client/preference_setup/volume_sliders/02_media.dm index 1f04d0d289..039c656452 100644 --- a/code/modules/client/preference_setup/volume_sliders/02_media.dm +++ b/code/modules/client/preference_setup/volume_sliders/02_media.dm @@ -6,13 +6,13 @@ name = "Media" sort_order = 2 -/datum/category_item/player_setup_item/volume_sliders/media/load_preferences(var/savefile/S) - S["media_volume"] >> pref.media_volume - S["media_player"] >> pref.media_player +/datum/category_item/player_setup_item/volume_sliders/media/load_preferences(datum/json_savefile/savefile) + pref.media_volume = savefile.get_entry("media_volume") + pref.media_player = savefile.get_entry("media_player") -/datum/category_item/player_setup_item/volume_sliders/media/save_preferences(var/savefile/S) - S["media_volume"] << pref.media_volume - S["media_player"] << pref.media_player +/datum/category_item/player_setup_item/volume_sliders/media/save_preferences(datum/json_savefile/savefile) + savefile.set_entry("media_volume", pref.media_volume) + savefile.set_entry("media_player", pref.media_player) /datum/category_item/player_setup_item/volume_sliders/media/sanitize_preferences() pref.media_volume = isnum(pref.media_volume) ? CLAMP(pref.media_volume, 0, 1) : initial(pref.media_volume) diff --git a/code/modules/client/preference_setup/vore/02_size.dm b/code/modules/client/preference_setup/vore/02_size.dm index 8d835b263e..5da84d5631 100644 --- a/code/modules/client/preference_setup/vore/02_size.dm +++ b/code/modules/client/preference_setup/vore/02_size.dm @@ -24,32 +24,35 @@ name = "Size" sort_order = 2 -/datum/category_item/player_setup_item/vore/size/load_character(var/savefile/S) - S["size_multiplier"] >> pref.size_multiplier - S["weight_vr"] >> pref.weight_vr - S["weight_gain"] >> pref.weight_gain - S["weight_loss"] >> pref.weight_loss - S["fuzzy"] >> pref.fuzzy - S["offset_override"] >> pref.offset_override - S["voice_freq"] >> pref.voice_freq - S["voice_sound"] >> pref.voice_sound - S["custom_speech_bubble"] >> pref.custom_speech_bubble - S["custom_footstep"] >> pref.custom_footstep // CHOMPEdit - S["species_sound"] >> pref.species_sound // CHOMPEdit - -/datum/category_item/player_setup_item/vore/size/save_character(var/savefile/S) - S["size_multiplier"] << pref.size_multiplier - S["weight_vr"] << pref.weight_vr - S["weight_gain"] << pref.weight_gain - S["weight_loss"] << pref.weight_loss - S["fuzzy"] << pref.fuzzy - S["offset_override"] << pref.offset_override - S["voice_freq"] << pref.voice_freq - S["voice_sound"] << pref.voice_sound - S["custom_speech_bubble"] << pref.custom_speech_bubble - S["custom_footstep"] << pref.custom_footstep // CHOMPEdit - S["species_sound"] << pref.species_sound // CHOMPEdit +/datum/category_item/player_setup_item/vore/size/load_character(list/save_data) + pref.size_multiplier = save_data["size_multiplier"] + pref.weight_vr = save_data["weight_vr"] + pref.weight_gain = save_data["weight_gain"] + pref.weight_loss = save_data["weight_loss"] + pref.fuzzy = save_data["fuzzy"] + pref.offset_override = save_data["offset_override"] + pref.voice_freq = save_data["voice_freq"] + pref.voice_sound = save_data["voice_sound"] + pref.custom_speech_bubble = save_data["custom_speech_bubble"] + //CHOMPAdd Start + pref.custom_footstep = save_data["custom_footstep"] + pref.species_sound = save_data["species_sound"] + //CHOMPAdd End +/datum/category_item/player_setup_item/vore/size/save_character(list/save_data) + save_data["size_multiplier"] = pref.size_multiplier + save_data["weight_vr"] = pref.weight_vr + save_data["weight_gain"] = pref.weight_gain + save_data["weight_loss"] = pref.weight_loss + save_data["fuzzy"] = pref.fuzzy + save_data["offset_override"] = pref.offset_override + save_data["voice_freq"] = pref.voice_freq + save_data["voice_sound"] = pref.voice_sound + save_data["custom_speech_bubble"] = pref.custom_speech_bubble + //CHOMPAdd Start + save_data["custom_footstep"] = pref.custom_footstep + save_data["species_sound"] = pref.species_sound + //CHOMPAdd End /datum/category_item/player_setup_item/vore/size/sanitize_character() pref.weight_vr = sanitize_integer(pref.weight_vr, WEIGHT_MIN, WEIGHT_MAX, initial(pref.weight_vr)) diff --git a/code/modules/client/preference_setup/vore/03_egg.dm b/code/modules/client/preference_setup/vore/03_egg.dm index 5ad651fbf9..f330d419ce 100644 --- a/code/modules/client/preference_setup/vore/03_egg.dm +++ b/code/modules/client/preference_setup/vore/03_egg.dm @@ -14,13 +14,13 @@ name = "Egg appearance." sort_order = 3 -/datum/category_item/player_setup_item/vore/egg/load_character(var/savefile/S) - S["vore_egg_type"] >> pref.vore_egg_type - S["autohiss"] >> pref.autohiss // VOREStation Add +/datum/category_item/player_setup_item/vore/egg/load_character(list/save_data) + pref.vore_egg_type = save_data["vore_egg_type"] + pref.autohiss = save_data["autohiss"] -/datum/category_item/player_setup_item/vore/egg/save_character(var/savefile/S) - S["vore_egg_type"] << pref.vore_egg_type - S["autohiss"] << pref.autohiss // VOREStation Add +/datum/category_item/player_setup_item/vore/egg/save_character(list/save_data) + save_data["vore_egg_type"] = pref.vore_egg_type + save_data["autohiss"] = pref.autohiss /datum/category_item/player_setup_item/vore/egg/sanitize_character() pref.vore_egg_type = sanitize_inlist(pref.vore_egg_type, global_vore_egg_types, initial(pref.vore_egg_type)) diff --git a/code/modules/client/preference_setup/vore/04_resleeving.dm b/code/modules/client/preference_setup/vore/04_resleeving.dm index b953ef56ad..60c20fe257 100644 --- a/code/modules/client/preference_setup/vore/04_resleeving.dm +++ b/code/modules/client/preference_setup/vore/04_resleeving.dm @@ -9,16 +9,15 @@ name = "Resleeving" sort_order = 4 -/datum/category_item/player_setup_item/vore/resleeve/load_character(var/savefile/S) - S["resleeve_lock"] >> pref.resleeve_lock - S["resleeve_scan"] >> pref.resleeve_scan - S["mind_scan"] >> pref.mind_scan +/datum/category_item/player_setup_item/vore/resleeve/load_character(list/save_data) + pref.resleeve_lock = save_data["resleeve_lock"] + pref.resleeve_scan = save_data["resleeve_scan"] + pref.mind_scan = save_data["mind_scan"] - -/datum/category_item/player_setup_item/vore/resleeve/save_character(var/savefile/S) - S["resleeve_lock"] << pref.resleeve_lock - S["resleeve_scan"] << pref.resleeve_scan - S["mind_scan"] << pref.mind_scan +/datum/category_item/player_setup_item/vore/resleeve/save_character(list/save_data) + save_data["resleeve_lock"] = pref.resleeve_lock + save_data["resleeve_scan"] = pref.resleeve_scan + save_data["mind_scan"] = pref.mind_scan /datum/category_item/player_setup_item/vore/resleeve/sanitize_character() pref.resleeve_lock = sanitize_integer(pref.resleeve_lock, 0, 1, initial(pref.resleeve_lock)) diff --git a/code/modules/client/preference_setup/vore/05_persistence.dm b/code/modules/client/preference_setup/vore/05_persistence.dm index 330a21a73b..bb2535f44b 100644 --- a/code/modules/client/preference_setup/vore/05_persistence.dm +++ b/code/modules/client/preference_setup/vore/05_persistence.dm @@ -7,12 +7,12 @@ name = "Persistence" sort_order = 5 -/datum/category_item/player_setup_item/vore/persistence/load_character(var/savefile/S) - S["persistence_settings"] >> pref.persistence_settings +/datum/category_item/player_setup_item/vore/persistence/load_character(list/save_data) + pref.persistence_settings = save_data["persistence_settings"] sanitize_character() // Don't let new characters start off with nulls -/datum/category_item/player_setup_item/vore/persistence/save_character(var/savefile/S) - S["persistence_settings"] << pref.persistence_settings +/datum/category_item/player_setup_item/vore/persistence/save_character(list/save_data) + save_data["persistence_settings"] = pref.persistence_settings /datum/category_item/player_setup_item/vore/persistence/sanitize_character() pref.persistence_settings = sanitize_integer(pref.persistence_settings, 0, (1<<(PERSIST_COUNT+1)-1), initial(pref.persistence_settings)) diff --git a/code/modules/client/preference_setup/vore/06_vantag.dm b/code/modules/client/preference_setup/vore/06_vantag.dm index c402f1f277..78d801a75e 100644 --- a/code/modules/client/preference_setup/vore/06_vantag.dm +++ b/code/modules/client/preference_setup/vore/06_vantag.dm @@ -8,13 +8,13 @@ name = "VS Events" sort_order = 6 -/datum/category_item/player_setup_item/vore/vantag/load_character(var/savefile/S) - S["vantag_volunteer"] >> pref.vantag_volunteer - S["vantag_preference"] >> pref.vantag_preference +/datum/category_item/player_setup_item/vore/vantag/load_character(list/save_data) + pref.vantag_volunteer = save_data["vantag_volunteer"] + pref.vantag_preference = save_data["vantag_preference"] -/datum/category_item/player_setup_item/vore/vantag/save_character(var/savefile/S) - S["vantag_volunteer"] << pref.vantag_volunteer - S["vantag_preference"] << pref.vantag_preference +/datum/category_item/player_setup_item/vore/vantag/save_character(list/save_data) + save_data["vantag_volunteer"] = pref.vantag_volunteer + save_data["vantag_preference"] = pref.vantag_preference /datum/category_item/player_setup_item/vore/vantag/sanitize_character() pref.vantag_volunteer = sanitize_integer(pref.vantag_volunteer, 0, 1, initial(pref.vantag_volunteer)) diff --git a/code/modules/client/preference_setup/vore/07_traits.dm b/code/modules/client/preference_setup/vore/07_traits.dm index f7ac96d096..3e9d2afefc 100644 --- a/code/modules/client/preference_setup/vore/07_traits.dm +++ b/code/modules/client/preference_setup/vore/07_traits.dm @@ -113,47 +113,47 @@ var/global/list/valid_bloodreagents = list("default","iron","copper","phoron","s name = "Traits" sort_order = 7 -/datum/category_item/player_setup_item/vore/traits/load_character(var/savefile/S) - S["custom_species"] >> pref.custom_species - S["custom_base"] >> pref.custom_base - S["pos_traits"] >> pref.pos_traits - S["neu_traits"] >> pref.neu_traits - S["neg_traits"] >> pref.neg_traits - S["blood_color"] >> pref.blood_color - S["blood_reagents"] >> pref.blood_reagents +/datum/category_item/player_setup_item/vore/traits/load_character(list/save_data) + pref.custom_species = save_data["custom_species"] + pref.custom_base = save_data["custom_base"] + pref.pos_traits = text2path_list(save_data["pos_traits"]) + pref.neu_traits = text2path_list(save_data["neu_traits"]) + pref.neg_traits = text2path_list(save_data["neg_traits"]) + pref.blood_color = save_data["blood_color"] + pref.blood_reagents = save_data["blood_reagents"] - S["traits_cheating"] >> pref.traits_cheating - S["max_traits"] >> pref.max_traits - S["trait_points"] >> pref.starting_trait_points + pref.traits_cheating = save_data["traits_cheating"] + pref.max_traits = save_data["max_traits"] + pref.starting_trait_points = save_data["trait_points"] - S["custom_say"] >> pref.custom_say - S["custom_whisper"] >> pref.custom_whisper - S["custom_ask"] >> pref.custom_ask - S["custom_exclaim"] >> pref.custom_exclaim + pref.custom_say = save_data["custom_say"] + pref.custom_whisper = save_data["custom_whisper"] + pref.custom_ask = save_data["custom_ask"] + pref.custom_exclaim = save_data["custom_exclaim"] - S["custom_heat"] >> pref.custom_heat - S["custom_cold"] >> pref.custom_cold + pref.custom_heat = save_data["custom_heat"] + pref.custom_cold = save_data["custom_cold"] -/datum/category_item/player_setup_item/vore/traits/save_character(var/savefile/S) - S["custom_species"] << pref.custom_species - S["custom_base"] << pref.custom_base - S["pos_traits"] << pref.pos_traits - S["neu_traits"] << pref.neu_traits - S["neg_traits"] << pref.neg_traits - S["blood_color"] << pref.blood_color - S["blood_reagents"] << pref.blood_reagents +/datum/category_item/player_setup_item/vore/traits/save_character(list/save_data) + save_data["custom_species"] = pref.custom_species + save_data["custom_base"] = pref.custom_base + save_data["pos_traits"] = pref.pos_traits + save_data["neu_traits"] = pref.neu_traits + save_data["neg_traits"] = pref.neg_traits + save_data["blood_color"] = pref.blood_color + save_data["blood_reagents"] = pref.blood_reagents - S["traits_cheating"] << pref.traits_cheating - S["max_traits"] << pref.max_traits - S["trait_points"] << pref.starting_trait_points + save_data["traits_cheating"] = pref.traits_cheating + save_data["max_traits"] = pref.max_traits + save_data["trait_points"] = pref.starting_trait_points - S["custom_say"] << pref.custom_say - S["custom_whisper"] << pref.custom_whisper - S["custom_ask"] << pref.custom_ask - S["custom_exclaim"] << pref.custom_exclaim + save_data["custom_say"] = pref.custom_say + save_data["custom_whisper"] = pref.custom_whisper + save_data["custom_ask"] = pref.custom_ask + save_data["custom_exclaim"] = pref.custom_exclaim - S["custom_heat"] << pref.custom_heat - S["custom_cold"] << pref.custom_cold + save_data["custom_heat"] = pref.custom_heat + save_data["custom_cold"] = pref.custom_cold /datum/category_item/player_setup_item/vore/traits/sanitize_character() if(!pref.pos_traits) pref.pos_traits = list() @@ -192,13 +192,16 @@ var/global/list/valid_bloodreagents = list("default","iron","copper","phoron","s //Neutral traits for(var/datum/trait/path as anything in pref.neu_traits) if(!(path in neutral_traits)) + to_world_log("removing [path] for not being in neutral_traits") pref.neu_traits -= path continue if(!(pref.species == SPECIES_CUSTOM) && !(path in everyone_traits_neutral)) + to_world_log("removing [path] for not being a custom species") pref.neu_traits -= path continue var/take_flags = initial(path.can_take) if((pref.dirty_synth && !(take_flags & SYNTHETICS)) || (pref.gross_meatbag && !(take_flags & ORGANICS))) + to_world_log("removing [path] for being a dirty synth") pref.neu_traits -= path //Negative traits for(var/datum/trait/path as anything in pref.neg_traits) diff --git a/code/modules/client/preference_setup/vore/08_nif.dm b/code/modules/client/preference_setup/vore/08_nif.dm index d502fb447b..e4a760be83 100644 --- a/code/modules/client/preference_setup/vore/08_nif.dm +++ b/code/modules/client/preference_setup/vore/08_nif.dm @@ -5,19 +5,34 @@ var/list/nif_savedata // Definition of the stuff for NIFs +// Magic bullshit, they're stored separately from everything else /datum/category_item/player_setup_item/vore/nif name = "NIF Data" sort_order = 8 -/datum/category_item/player_setup_item/vore/nif/load_character(var/savefile/S) - S["nif_path"] >> pref.nif_path - S["nif_durability"] >> pref.nif_durability - S["nif_savedata"] >> pref.nif_savedata +/proc/nif_savefile_path(ckey) + if(!ckey) + return + return "data/player_saves/[copytext(ckey,1,2)]/[ckey]/nif.json" -/datum/category_item/player_setup_item/vore/nif/save_character(var/savefile/S) - S["nif_path"] << pref.nif_path - S["nif_durability"] << pref.nif_durability - S["nif_savedata"] << pref.nif_savedata +/datum/category_item/player_setup_item/vore/nif/load_character() + var/datum/json_savefile/savefile = new /datum/json_savefile(nif_savefile_path(pref.client_ckey)) + var/list/save_data_file = savefile.get_entry("character[pref.default_slot]", list()) + + pref.nif_path = save_data_file["nif_path"] + pref.nif_durability = save_data_file["nif_durability"] + pref.nif_savedata = save_data_file["nif_savedata"] + +/datum/category_item/player_setup_item/vore/nif/save_character() + var/datum/json_savefile/savefile = new /datum/json_savefile(nif_savefile_path(pref.client_ckey)) + var/list/save_data_file = savefile.get_entry("character[pref.default_slot]", list()) + + save_data_file["nif_path"] = pref.nif_path + save_data_file["nif_durability"] = pref.nif_durability + save_data_file["nif_savedata"] = pref.nif_savedata + + savefile.set_entry("character[pref.default_slot]", save_data_file) + savefile.save() /datum/category_item/player_setup_item/vore/nif/sanitize_character() if(pref.nif_path && !ispath(pref.nif_path)) //We have at least a text string that should be a path. @@ -38,23 +53,8 @@ /datum/category_item/player_setup_item/vore/nif/copy_to_mob(var/mob/living/carbon/human/character) //If you had a NIF... - if((character.type == /mob/living/carbon/human) && ispath(pref.nif_path) && pref.nif_durability) - new pref.nif_path(character,pref.nif_durability,pref.nif_savedata) - - /* - //And now here's the trick. We wipe these so that if they die, they lose the NIF. - //Backup implants will start saving this again periodically, and so will cryo'ing out. - pref.nif_path = null - pref.nif_durability = null - pref.nif_savedata = null - */ - //No we do not, that's lame and admins have to re-NIF them later. - //If they leave round after they get their NIF extracted, it will save as 'gone' anyway - //The NIF will save automatically every time durability changes too now. - var/savefile/S = new /savefile(pref.path) - if(!S) WARNING ("Couldn't load NIF save savefile? [pref.real_name]") - S.cd = "/character[pref.default_slot]" - save_character(S) + if(istype(character) && ispath(pref.nif_path) && pref.nif_durability) + new pref.nif_path(character, pref.nif_durability, pref.nif_savedata) /datum/category_item/player_setup_item/vore/nif/content(var/mob/user) . += "NIF: [ispath(pref.nif_path) ? "Present" : "None"]" diff --git a/code/modules/client/preference_setup/vore/09_misc.dm b/code/modules/client/preference_setup/vore/09_misc.dm index 2cc2cce649..849ee96ad0 100644 --- a/code/modules/client/preference_setup/vore/09_misc.dm +++ b/code/modules/client/preference_setup/vore/09_misc.dm @@ -2,31 +2,35 @@ name = "Misc Settings" sort_order = 9 -/datum/category_item/player_setup_item/vore/misc/load_character(var/savefile/S) - S["show_in_directory"] >> pref.show_in_directory - S["directory_tag"] >> pref.directory_tag - S["directory_gendertag"] >> pref.directory_gendertag // CHOMPStation Edit: Character Directory Update - S["directory_sexualitytag"] >> pref.directory_sexualitytag // CHOMPStation Edit: Character Directory Update - S["directory_erptag"] >> pref.directory_erptag - S["directory_ad"] >> pref.directory_ad - S["sensorpref"] >> pref.sensorpref - S["capture_crystal"] >> pref.capture_crystal - S["auto_backup_implant"] >> pref.auto_backup_implant - S["borg_petting"] >> pref.borg_petting - S["stomach_vision"] >> pref.stomach_vision +/datum/category_item/player_setup_item/vore/misc/load_character(list/save_data) + pref.show_in_directory = save_data["show_in_directory"] + pref.directory_tag = save_data["directory_tag"] + //CHOMPAdd Start + pref.directory_gendertag = save_data["directory_gendertag"] + pref.directory_sexualitytag = save_data["directory_sexualitytag"] + //CHOMPAdd End + pref.directory_erptag = save_data["directory_erptag"] + pref.directory_ad = save_data["directory_ad"] + pref.sensorpref = save_data["sensorpref"] + pref.capture_crystal = save_data["capture_crystal"] + pref.auto_backup_implant = save_data["auto_backup_implant"] + pref.borg_petting = save_data["borg_petting"] + pref.stomach_vision = save_data["stomach_vision"] -/datum/category_item/player_setup_item/vore/misc/save_character(var/savefile/S) - S["show_in_directory"] << pref.show_in_directory - S["directory_tag"] << pref.directory_tag - S["directory_gendertag"] << pref.directory_gendertag // CHOMPStation Edit: Character Directory Update - S["directory_sexualitytag"] << pref.directory_sexualitytag // CHOMPStation Edit: Character Directory Update - S["directory_erptag"] << pref.directory_erptag - S["directory_ad"] << pref.directory_ad - S["sensorpref"] << pref.sensorpref - S["capture_crystal"] << pref.capture_crystal - S["auto_backup_implant"] << pref.auto_backup_implant - S["borg_petting"] << pref.borg_petting - S["stomach_vision"] << pref.stomach_vision +/datum/category_item/player_setup_item/vore/misc/save_character(list/save_data) + save_data["show_in_directory"] = pref.show_in_directory + save_data["directory_tag"] = pref.directory_tag + //CHOMPAdd Start + save_data["directory_gendertag"] = pref.directory_gendertag + save_data["directory_sexualitytag"] = pref.directory_sexualitytag + //CHOMPAdd End + save_data["directory_erptag"] = pref.directory_erptag + save_data["directory_ad"] = pref.directory_ad + save_data["sensorpref"] = pref.sensorpref + save_data["capture_crystal"] = pref.capture_crystal + save_data["auto_backup_implant"] = pref.auto_backup_implant + save_data["borg_petting"] = pref.borg_petting + save_data["stomach_vision"] = pref.stomach_vision /datum/category_item/player_setup_item/vore/misc/copy_to_mob(var/mob/living/carbon/human/character) if(pref.sensorpref > 5 || pref.sensorpref < 1) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 9af067b09a..86d2409551 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -1,10 +1,12 @@ var/list/preferences_datums = list() /datum/preferences - //doohickeys for savefiles + /// The path to the general savefile for this datum var/path - var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used - var/savefile_version = 0 + /// 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 @@ -176,29 +178,55 @@ var/list/preferences_datums = 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 - gear = list() - gear_list = list() - gear_slot = 1 - - if(istype(C)) - client = C - client_ckey = C.ckey - if(!IsGuestKey(C.key)) - load_path(C.ckey) - if(load_preferences()) - load_character() + 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]) @@ -357,8 +385,8 @@ var/list/preferences_datums = list() return 1 if(href_list["save"]) - save_preferences() save_character() + save_preferences() else if(href_list["reload"]) load_preferences() load_character() @@ -373,7 +401,7 @@ var/list/preferences_datums = list() 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 - load_character(SAVE_RESET) + reset_slot() sanitize_preferences() else if(href_list["copy"]) if(!IsGuestKey(usr.key)) @@ -402,6 +430,12 @@ var/list/preferences_datums = list() // 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() @@ -420,26 +454,23 @@ var/list/preferences_datums = list() if(selecting_slots) to_chat(user, "You already have a slot selection dialog open!") return - var/savefile/S = new /savefile(path) - if(!S) - error("Somehow missing savefile path?! [path]") + if(!savefile) return - var/name - var/nickname //vorestation edit - This set appends nicknames to the save slot + var/default var/list/charlist = list() - var/default //VOREStation edit - for(var/i = 1, i <= CONFIG_GET(number/character_slots), i++) // CHOMPEdit - S.cd = "/character[i]" - S["real_name"] >> name - S["nickname"] >> nickname //vorestation edit + + for(var/i in 1 to CONFIG_GET(number/character_slots)) //CHOMPEdit + 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) //VOREStation edit + if(i == default_slot) default = "[name][nickname ? " ([nickname])" : ""]" charlist["[name][nickname ? " ([nickname])" : ""]"] = i @@ -454,33 +485,34 @@ var/list/preferences_datums = list() 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, "You already have a slot selection dialog open!") return - var/savefile/S = new /savefile(path) - if(!S) - error("Somehow missing savefile path?! [path]") + if(!savefile) return - var/name - var/nickname //vorestation edit - This set appends nicknames to the save slot var/list/charlist = list() - for(var/i = 1, i <= CONFIG_GET(number/character_slots), i++) // CHOMPEdit - S.cd = "/character[i]" - S["real_name"] >> name - S["nickname"] >> nickname //vorestation edit + + for(var/i in 1 to CONFIG_GET(number/character_slots)) //CHOMPEdit + 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\]" - if(i == default_slot) + else if(i == default_slot) name = "โ–บ[i] - [name]" else name = "[i] - [name]" + charlist["[name][nickname ? " ([nickname])" : ""]"] = i selecting_slots = TRUE @@ -494,10 +526,10 @@ var/list/preferences_datums = list() 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], [name][nickname ? " ([nickname])" : ""]'s savedata?", "Confirm Override", list("No", "Yes")) == "Yes") + 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_preferences() save_character() + save_preferences() attempt_vr(user.client?.prefs_vr,"load_vore","") ShowChoices(user) diff --git a/code/modules/client/preferences/README.md b/code/modules/client/preferences/README.md new file mode 100644 index 0000000000..fabfb779c9 --- /dev/null +++ b/code/modules/client/preferences/README.md @@ -0,0 +1,456 @@ +# Preferences (by Mothblocks) + +This does not contain all the information on specific values--you can find those as doc-comments in relevant paths, such as `/datum/preference`. Rather, this gives you an overview for creating *most* preferences, and getting your foot in the door to create more advanced ones. + +## Anatomy of a preference (A.K.A. how do I make one?) + +Most preferences consist of two parts: + +1. A `/datum/preference` type. +2. A tgui representation in a TypeScript file. + +Every `/datum/preference` requires these three values be set: +1. `category` - See [Categories](#Categories). +2. `savefile_key` - The value which will be saved in the savefile. This will also be the identifier for tgui. +3. `savefile_identifier` - Whether or not this is a character specific preference (`PREFERENCE_CHARACTER`) or one that affects the player (`PREFERENCE_PLAYER`). As an example: hair color is `PREFERENCE_CHARACTER` while your UI settings are `PREFERENCE_PLAYER`, since they do not change between characters. + +For the tgui representation, most preferences will create a `.tsx` file in `tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/`. If your preference is a character preference, make a new file in `character_preferences`. Otherwise, put it in `game_preferences`. The filename does not matter, and this file can hold multiple relevant preferences if you would like. + +From here, you will want to write code resembling: + +```ts +import { Feature } from "../base"; + +export const savefile_key_here: Feature = { + name: "Preference Name Here", + component: Component, + + // Necessary for game preferences, unused for others + category: "CATEGORY", + + // Optional, shown as a tooltip + description: "This preference will blow your mind!", +} +``` + +`T` and `Component` depend on the type of preference you're making. Here are all common examples... + +## Numeric preferences + +Examples include age and FPS. + +A numeric preference derives from `/datum/preference/numeric`. + +```dm +/datum/preference/numeric/legs + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "legs" + + minimum = 1 + maximum = 8 +``` + +You can optionally provide a `step` field. This value is 1 by default, meaning only integers are accepted. + +Your `.tsx` file would look like: + +```ts +import { Feature, FeatureNumberInput } from "../base"; + +export const legs: Feature = { + name: "Legs", + component: FeatureNumberInput, +} +``` + +## Toggle preferences + +Examples include enabling tooltips. + +```dm +/datum/preference/toggle/enable_breathing + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "enable_breathing" + + // Optional, TRUE by default + default_value = FALSE +``` + +Your `.tsx` file would look like: + +```ts +import { CheckboxInput, FeatureToggle } from "../base"; + +export const enable_breathing: FeatureToggle = { + name: "Enable breathing", + component: CheckboxInput, +} +``` + +## Choiced preferences +A choiced preference is one where the only options are in a distinct few amount of choices. Examples include skin tone, shirt, and UI style. + +To create one, derive from `/datum/preference/choiced`. + +```dm +/datum/preference/choiced/favorite_drink + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "favorite_drink" +``` + +Now we need to tell the game what the choices are. We do this by overriding `init_possible_values()`. This will return a list of possible options. + +```dm +/datum/preference/choiced/favorite_drink/init_possible_values() + return list( + "Milk", + "Cola", + "Water", + ) +``` + +Your `.tsx` file would then look like: + +```tsx +import { FeatureChoiced, FeatureDropdownInput } from "../base"; + +export const favorite_drink: FeatureChoiced = { + name: "Favorite drink", + component: FeatureDropdownInput, +}; +``` + +This will create a dropdown input for your preference. + +### Choiced preferences - Icons +Choiced preferences can generate icons. This is how the clothing/species preferences work, for instance. However, if we just want a basic dropdown input with icons, it would look like this: + +```dm +/datum/preference/choiced/favorite_drink + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "favorite_drink" + should_generate_icons = TRUE // NEW! This is necessary. + +/datum/preference/choiced/favorite_drink/init_possible_values() + return list("Milk", "Cola", "Water") + +// New! This proc will get called for every value. +/datum/preference/choiced/favorite_drink/icon_for(value) + switch (value) + if ("Milk") + return icon('drinks.dmi', "milk") + if ("Cola") + return icon('drinks.dmi', "cola") + if ("Water") + return icon('drinks.dmi', "water") +``` + +Then, change your `.tsx` file to look like: + +```tsx +import { FeatureChoiced, FeatureIconnedDropdownInput } from "../base"; + +export const favorite_drink: FeatureChoiced = { + name: "Favorite drink", + component: FeatureIconnedDropdownInput, +}; +``` + +### Choiced preferences - Display names +Sometimes the values you want to save in code aren't the same as the ones you want to display. You can specify display names to change this. + +The only thing you will add is "compiled data". + +```dm +/datum/preference/choiced/favorite_drink/compile_constant_data() + var/list/data = ..() + + // An assoc list of values to display names + data[CHOICED_PREFERENCE_DISPLAY_NAMES] = list( + "Milk" = "Delicious Milk", + "Cola" = "Crisp Cola", + "Water" = "Plain Ol' Water", + ) + + return data +``` + +Your `.tsx` file does not change. The UI will figure it out for you! + +## Color preferences +These refer to colors, such as your OOC color. When read, these values will be given as 6 hex digits, *without* the pound sign. + +```dm +/datum/preference/color/eyeliner_color + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "eyeliner_color" +``` + +Your `.tsx` file would look like: + +```ts +import { FeatureColorInput, Feature } from "../base"; + +export const eyeliner_color: Feature = { + name: "Eyeliner color", + component: FeatureColorInput, +}; +``` + +## Name preferences +These refer to an alternative name. Examples include AI names and backup human names. + +These exist in `code/modules/client/preferences/names.dm`. + +These do not need a `.ts` file, and will be created in the UI automatically. + +```dm +/datum/preference/name/doctor + savefile_key = "doctor_name" + + // The name on the UI + explanation = "Doctor name" + + // This groups together with anything else with the same group + group = "medicine" + + // Optional, if specified the UI will show this name actively + // when the player is a medical doctor. + relevant_job = /datum/job/medical_doctor +``` + +## Making your preference do stuff + +There are a handful of procs preferences can use to act on their own: + +```dm +/// Apply this preference onto the given client. +/// Called when the savefile_identifier == PREFERENCE_PLAYER. +/datum/preference/proc/apply_to_client(client/client, value) + +/// Fired when the preference is updated. +/// Calls apply_to_client by default, but can be overridden. +/datum/preference/proc/apply_to_client_updated(client/client, value) + +/// Apply this preference onto the given human. +/// Must be overriden by subtypes. +/// Called when the savefile_identifier == PREFERENCE_CHARACTER. +/datum/preference/proc/apply_to_human(mob/living/carbon/human/target, value) +``` + +For example, `/datum/preference/numeric/age` contains: + +```dm +/datum/preference/numeric/age/apply_to_human(mob/living/carbon/human/target, value) + target.age = value +``` + +If your preference is `PREFERENCE_CHARACTER`, it MUST override `apply_to_human`, even if just to immediately `return`. + +You can also read preferences directly with `prefs.read_preference(/datum/preference/type/here)`, which will return the stored value. + +## Categories +Every preference needs to be in a `category`. These can be found in `code/__DEFINES/preferences.dm`. + +```dm +/// These will be shown in the character sidebar, but at the bottom. +#define PREFERENCE_CATEGORY_FEATURES "features" + +/// Any preferences that will show to the sides of the character in the setup menu. +#define PREFERENCE_CATEGORY_CLOTHING "clothing" + +/// Preferences that will be put into the 3rd list, and are not contextual. +#define PREFERENCE_CATEGORY_NON_CONTEXTUAL "non_contextual" + +/// Will be put under the game preferences window. +#define PREFERENCE_CATEGORY_GAME_PREFERENCES "game_preferences" + +/// These will show in the list to the right of the character preview. +#define PREFERENCE_CATEGORY_SECONDARY_FEATURES "secondary_features" + +/// These are preferences that are supplementary for main features, +/// such as hair color being affixed to hair. +#define PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES "supplemental_features" +``` + +![Preference categories for the main page](https://raw.githubusercontent.com/tgstation/documentation-assets/main/preferences/preference_categories.png) + +> SECONDARY_FEATURES or NON_CONTEXTUAL? + +Secondary features tend to be species specific. Non contextual features shouldn't change much from character to character. + +## Default values and randomization + +There are three procs to be aware of in regards to this topic: + +- `create_default_value()`. This is used when a value deserializes improperly or when a new character is created. +- `create_informed_default_value(datum/preferences/preferences)` - Used for more complicated default values, like how names require the gender. Will call `create_default_value()` by default. +- `create_random_value(datum/preferences/preferences)` - Explicitly used for random values, such as when a character is being randomized. + +`create_default_value()` in most preferences will create a random value. If this is a problem (like how default characters should always be human), you can override `create_default_value()`. By default (without overriding `create_random_value`), random values are just default values. + +## Advanced - Server data + +As previewed in [the display names implementation](#Choiced-preferences---Display-names), there exists a `compile_constant_data()` proc you can override. + +Compiled data is used wherever the server needs to give the client some value it can't figure out on its own. Skin tones use this to tell the client what colors they represent, for example. + +Compiled data is sent to the `serverData` field in the `FeatureValueProps`. + +## Advanced - Creating your own tgui component + +If you have good knowledge with tgui (especially TypeScript), you'll be able to create your own component to represent preferences. + +The `component` field in a feature accepts __any__ component that accepts `FeatureValueProps`. + +This will give you the fields: + +```ts +act: typeof sendAct, +featureId: string, +handleSetValue: (newValue: TSending) => void, +serverData: TServerData | undefined, +shrink?: boolean, +value: TReceiving, +``` + +`act` is the same as the one you get from `useBackend`. + +`featureId` is the savefile_key of the feature. + +`handleSetValue` is a function that, when called, will tell the server the new value, as well as changing the value immediately locally. + +`serverData` is the [server data](#Advanced---Server-data), if it has been fetched yet (and exists). + +`shrink` is whether or not the UI should appear smaller. This is only used for supplementary features. + +`value` is the current value, could be predicted (meaning that the value was changed locally, but has not yet reached the server). + +For a basic example of how this can look, observe `CheckboxInput`: + +```tsx +export const CheckboxInput = ( + props: FeatureValueProps +) => { + return ( { + props.handleSetValue(!props.value); + }} + />); +}; +``` + +## Advanced - Middleware +A `/datum/preference_middleware` is a way to inject your own data at specific points, as well as hijack actions. + +Middleware can hijack actions by specifying `action_delegations`: + +```dm +/datum/preference_middleware/congratulations + action_delegations = list( + "congratulate_me" = PROC_REF(congratulate_me), + ) + +/datum/preference_middleware/congratulations/proc/congratulate_me(list/params, mob/user) + to_chat(user, span_notice("Wow, you did a great job learning about middleware!")) + + return TRUE +``` + +Middleware can inject its own data at several points, such as providing new UI assets, compiled data (used by middleware such as quirks to tell the client what quirks exist), etc. Look at `code/modules/client/preferences/middleware/_middleware.dm` for full information. + +--- + +## Antagonists + +In order to make an antagonist selectable, you must do a few things: + +1. Your antagonist needs an icon. +2. Your antagonist must be in a Dynamic ruleset. The ruleset must specify the antagonist as its `antag_flag`. +3. Your antagonist needs a file in `tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/filename.ts`. This file name MUST be the `antag_flag` of your ruleset, with nothing but letters remaining (e.g. "Nuclear Operative" -> `nuclearoperative`). +4. Add it to `special_roles`. + +## Creating icons + +If you are satisfied with your icon just being a dude with some clothes, then you can specify `preview_outfit` in your `/datum/antagonist`. + +Space Ninja, for example, looks like: + +```dm +/datum/antagonist/ninja + preview_outift = /datum/outfit/ninja +``` + +However, if you want to get creative, you can override `/get_preview_icon()`. This proc should return an icon of size `ANTAGONIST_PREVIEW_ICON_SIZE`x`ANTAGONIST_PREVIEW_ICON_SIZE`. + +There are some helper procs you can use as well. `render_preview_outfit(outfit_type)` will take an outfit and give you an icon of someone wearing those clothes. `finish_preview_outfit` will, given an icon, resize it appropriately and zoom in on the head. Note that this will look bad on anything that isn't a human, so if you have a non-human antagonist (such as sentient disease), just run `icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)`. + +For inspiration, here is changeling's: + +```dm +/datum/antagonist/changeling/get_preview_icon() + var/icon/final_icon = render_preview_outfit(/datum/outfit/changeling) + var/icon/split_icon = render_preview_outfit(/datum/outfit/job/engineer) + + final_icon.Shift(WEST, world.icon_size / 2) + final_icon.Shift(EAST, world.icon_size / 2) + + split_icon.Shift(EAST, world.icon_size / 2) + split_icon.Shift(WEST, world.icon_size / 2) + + final_icon.Blend(split_icon, ICON_OVERLAY) + + return finish_preview_icon(final_icon) +``` + +...which creates: + +![Changeling icon](https://raw.githubusercontent.com/tgstation/documentation-assets/main/preferences/changeling.png) + +## Creating the tgui representation + +In the `.ts` file you created earlier, you must now give the information of your antagonist. For reference, this is the changeling's: + +```ts +import { Antagonist, Category } from "../base"; +import { multiline } from "common/string"; + +const Changeling: Antagonist = { + key: "changeling", // This must be the same as your filename + name: "Changeling", + description: [ + multiline` + A highly intelligent alien predator that is capable of altering their + shape to flawlessly resemble a human. + `, + + multiline` + Transform yourself or others into different identities, and buy from an + arsenal of biological weaponry with the DNA you collect. + `, + ], + category: Category.Roundstart, // Category.Roundstart, Category.Midround, or Category.Latejoin +}; + +export default Changeling; +``` + +## Readying the Dynamic ruleset + +You already need to create a Dynamic ruleset, so in order to get your antagonist recognized, you just need to specify `antag_flag`. This must be unique per ruleset. + +Two other values to note are `antag_flag_override` and `antag_preference`. + +`antag_flag_override` exists for cases where you want the banned antagonist to be separate from `antag_flag`. As an example: roundstart, midround, and latejoin traitors have separate `antag_flag`, but all have `antag_flag_override = ROLE_TRAITOR`. This is because admins want to ban a player from Traitor altogether, not specific rulesets. + +If `antag_preference` is set, it will refer to that preference instead of `antag_flag`. This is used for clown operatives, which we want to be on the same preference as standard nuke ops, but must specify a unique `antag_flag` for. + +## Updating special_roles + +In `code/__DEFINES/role_preferences.dm` (the same place you'll need to make your ROLE_\* defined), simply add your antagonist to the `special_roles` assoc list. The key is your ROLE, the value is the number of days since your first game in order to play as that antagonist. diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm new file mode 100644 index 0000000000..d3ad859249 --- /dev/null +++ b/code/modules/client/preferences/_preference.dm @@ -0,0 +1,546 @@ +// Priorities must be in order! +/// The default priority level +#define PREFERENCE_PRIORITY_DEFAULT 1 + +/// The priority at which species runs, needed for external organs to apply properly. +#define PREFERENCE_PRIORITY_SPECIES 2 + +/** + * Some preferences get applied directly to bodyparts (anything head_flags related right now). + * These must apply after species, as species gaining might replace the bodyparts of the human. + */ +#define PREFERENCE_PRIORITY_BODYPARTS 3 + +/// The priority at which gender is determined, needed for proper randomization. +#define PREFERENCE_PRIORITY_GENDER 4 + +/// The priority at which body type is decided, applied after gender so we can +/// support the "use gender" option. +#define PREFERENCE_PRIORITY_BODY_TYPE 5 + +/// Used for preferences that rely on body setup being finalized. +#define PREFERENCE_PRORITY_LATE_BODY_TYPE 6 + +/// Equpping items based on preferences. +/// Should happen after species and body type to make sure it looks right. +/// Mostly redundant, but a safety net for saving/loading. +#define PREFERENCE_PRIORITY_LOADOUT 7 + +/// The priority at which names are decided, needed for proper randomization. +#define PREFERENCE_PRIORITY_NAMES 8 + +/// Preferences that aren't names, but change the name changes set by PREFERENCE_PRIORITY_NAMES. +#define PREFERENCE_PRIORITY_NAME_MODIFICATIONS 9 + +/// The maximum preference priority, keep this updated, but don't use it for `priority`. +#define MAX_PREFERENCE_PRIORITY PREFERENCE_PRIORITY_NAME_MODIFICATIONS + +/// For choiced preferences, this key will be used to set display names in constant data. +#define CHOICED_PREFERENCE_DISPLAY_NAMES "display_names" + +/// For main feature preferences, this key refers to a feature considered supplemental. +/// For instance, hair color being supplemental to hair. +#define SUPPLEMENTAL_FEATURE_KEY "supplemental_feature" + +/// An assoc list list of types to instantiated `/datum/preference` instances +GLOBAL_LIST_INIT(preference_entries, init_preference_entries()) + +/// An assoc list of preference entries by their `savefile_key` +GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) + +/proc/init_preference_entries() + var/list/output = list() + for(var/datum/preference/preference_type as anything in subtypesof(/datum/preference)) + if(is_abstract(preference_type)) + continue + output[preference_type] = new preference_type + return output + +/proc/init_preference_entries_by_key() + var/list/output = list() + for(var/datum/preference/preference_type as anything in subtypesof(/datum/preference)) + if(is_abstract(preference_type)) + continue + output[initial(preference_type.savefile_key)] = GLOB.preference_entries[preference_type] + return output + +/// Returns a flat list of preferences in order of their priority +/proc/get_preferences_in_priority_order() + var/list/preferences[MAX_PREFERENCE_PRIORITY] + + for(var/preference_type in GLOB.preference_entries) + var/datum/preference/preference = GLOB.preference_entries[preference_type] + LAZYADD(preferences[preference.priority], preference) + + var/list/flattened = list() + for(var/index in 1 to MAX_PREFERENCE_PRIORITY) + // Don't add nulls to the list if there's no preferences in a given priority level + if(LAZYLEN(preferences[index])) + flattened += preferences[index] + return flattened + +/// Represents an individual preference. +/datum/preference + /// The key inside the savefile to use. + /// This is also sent to the UI. + /// Once you pick this, don't change it. + var/savefile_key + + /// The category of preference, for use by the PreferencesMenu. + /// This isn't used for anything other than as a key for UI data. + /// It is up to the PreferencesMenu UI itself to interpret it. + var/category = "misc" + + /// Do not instantiate if type matches this. + abstract_type = /datum/preference + + /// What savefile should this preference be read from? + /// Valid values are PREFERENCE_CHARACTER and PREFERENCE_PLAYER. + /// See the documentation in [code/__DEFINES/preferences.dm]. + var/savefile_identifier + + /// The priority of when to apply this preference. + /// Used for when you need to rely on another preference. + var/priority = PREFERENCE_PRIORITY_DEFAULT + + /// If set, will be available to randomize, but only if the preference + /// is for PREFERENCE_CHARACTER. + var/can_randomize = TRUE + + /// If randomizable (PREFERENCE_CHARACTER and can_randomize), whether + /// or not to enable randomization by default. + /// This doesn't mean it'll always be random, but rather if a player + /// DOES have random body on, will this already be randomized? + var/randomize_by_default = TRUE + + /// If the selected species has this in its /datum/species/mutant_bodyparts, + /// will show the feature as selectable. + var/relevant_mutant_bodypart = null + + /// If the selected species has this in its /datum/species/body_markings, + /// will show the feature as selectable. + var/relevant_body_markings = null + + /// If the selected species has this in its /datum/species/inherent_traits, + /// will show the feature as selectable. + var/relevant_inherent_trait = null + + /// If the selected species has this in its /datum/species/var/external_organs, + /// will show the feature as selectable. + var/relevant_external_organ = null + + /// If the selected species has this head_flag by default, + /// will show the feature as selectable. + var/relevant_head_flag = null + +/// Called on the saved input when retrieving. +/// Also called by the value sent from the user through UI. Do not trust it. +/// Input is the value inside the savefile, output is to tell other code +/// what the value is. +/// This is useful either for more optimal data saving or for migrating +/// older data. +/// Must be overridden by subtypes. +/// Can return null if no value was found. +/datum/preference/proc/pref_deserialize(input, datum/preferences/preferences) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("`pref_deserialize()` was not implemented on [type]!") + +/// Called on the input while saving. +/// Input is the current value, output is what to save in the savefile. +/datum/preference/proc/pref_serialize(input) + SHOULD_NOT_SLEEP(TRUE) + return input + +/// Produce a default, potentially random value for when no value for this +/// preference is found in the savefile. +/// Either this or create_informed_default_value must be overriden by subtypes. +/datum/preference/proc/create_default_value() + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("`create_default_value()` was not implemented on [type]!") + +/// Produce a default, potentially random value for when no value for this +/// preference is found in the savefile. +/// Unlike create_default_value(), will provide the preferences object if you +/// need to use it. +/// If not overriden, will call create_default_value() instead. +/datum/preference/proc/create_informed_default_value(datum/preferences/preferences) + return create_default_value() + +/// Produce a random value for the purposes of character randomization. +/// Will just create a default value by default. +/datum/preference/proc/create_random_value(datum/preferences/preferences) + return create_informed_default_value(preferences) + +/// Returns whether or not a preference can be randomized. +/datum/preference/proc/is_randomizable() + SHOULD_NOT_OVERRIDE(TRUE) + return savefile_identifier == PREFERENCE_CHARACTER && can_randomize + +/// Given a savefile, return either the saved data or an acceptable default. +/// This will write to the savefile if a value was not found with the new value. +/datum/preference/proc/read(list/save_data, datum/preferences/preferences) + SHOULD_NOT_OVERRIDE(TRUE) + + var/value + + if(!isnull(save_data)) + value = save_data[savefile_key] + + if(isnull(value)) + return null + else + return pref_deserialize(value, preferences) + +/// Given a savefile, writes the inputted value. +/// Returns TRUE for a successful application. +/// Return FALSE if it is invalid. +/datum/preference/proc/write(list/save_data, value) + SHOULD_NOT_OVERRIDE(TRUE) + + if(!is_valid(value)) + return FALSE + + if(!isnull(save_data)) + save_data[savefile_key] = pref_serialize(value) + + return TRUE + +/// Apply this preference onto the given client. +/// Called when the savefile_identifier == PREFERENCE_PLAYER. +/datum/preference/proc/apply_to_client(client/client, value) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(FALSE) + return + +/// Fired when the preference is updated. +/// Calls apply_to_client by default, but can be overridden. +/datum/preference/proc/apply_to_client_updated(client/client, value) + SHOULD_NOT_SLEEP(TRUE) + apply_to_client(client, value) + +/// Apply this preference onto the given human. +/// Must be overriden by subtypes. +/// Called when the savefile_identifier == PREFERENCE_CHARACTER. +/datum/preference/proc/apply_to_human(mob/living/carbon/human/target, value) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("`apply_to_human()` was not implemented for [type]!") + +/// Returns which savefile to use for a given savefile identifier +/datum/preferences/proc/get_save_data_for_savefile_identifier(savefile_identifier) + RETURN_TYPE(/list) + + if(!client) + return null + if(!savefile) + CRASH("Attempted to get the savedata for [savefile_identifier] of [client] without a savefile. This should have been handled by load_preferences()") + + // Both of these will cache savefiles, but only for a tick. + // This is because storing a savefile will lock it, causing later issues down the line. + // Do not change them to addtimer, since the timer SS might not be running at this time. + switch (savefile_identifier) + if(PREFERENCE_CHARACTER) + return savefile.get_entry("character[default_slot]") + if(PREFERENCE_PLAYER) + return savefile.get_entry() + else + CRASH("Unknown savefile identifier [savefile_identifier]") + +/// Read a /datum/preference type and return its value. +/// This will write to the savefile if a value was not found with the new value. +/datum/preferences/proc/read_preference(preference_type) + var/datum/preference/preference_entry = GLOB.preference_entries[preference_type] + if(isnull(preference_entry)) + var/extra_info = "" + + // Current initializing subsystem is important to know because it might be a problem with + // things running pre-assets-initialization. + // if(!isnull(Master.current_initializing_subsystem)) + // extra_info = "Info was attempted to be retrieved while [Master.current_initializing_subsystem] was initializing." + // else if(!MC_RUNNING()) + // extra_info = "Info was attempted to be retrieved before the MC started, but not while it was actively initializing a subsystem" + + CRASH("Preference type `[preference_type]` is invalid! [extra_info]") + + if(preference_type in value_cache) + return value_cache[preference_type] + + var/value = preference_entry.read(get_save_data_for_savefile_identifier(preference_entry.savefile_identifier), src) + if(isnull(value)) + value = preference_entry.create_informed_default_value(src) + if(write_preference(preference_entry, value)) + return value + else + CRASH("Couldn't write the default value for [preference_type] (received [value])") + value_cache[preference_type] = value + return value + +/// Read a /datum/preference type and return its value. +/mob/proc/read_preference(preference_type) + return client?.prefs?.read_preference(preference_type) + +/// Set a /datum/preference entry. +/// Returns TRUE for a successful preference application. +/// Returns FALSE if it is invalid. +/datum/preferences/proc/write_preference(datum/preference/preference, preference_value) + var/save_data = get_save_data_for_savefile_identifier(preference.savefile_identifier) + var/new_value = preference.pref_deserialize(preference_value, src) + var/success = preference.write(save_data, new_value) + if(success) + value_cache[preference.type] = new_value + return success + +/// Will perform an update on the preference, but not write to the savefile. +/// This will, for instance, update the character preference view. +/// Performs sanity checks. +/datum/preferences/proc/update_preference(datum/preference/preference, preference_value) + if(!preference.is_accessible(src)) + return FALSE + + var/new_value = preference.pref_deserialize(preference_value, src) + var/success = preference.write(null, new_value) + + if(!success) + return FALSE + + recently_updated_keys |= preference.type + value_cache[preference.type] = new_value + + if(preference.savefile_identifier == PREFERENCE_PLAYER) + preference.apply_to_client_updated(client, read_preference(preference.type)) + else + update_preview_icon() + + return TRUE + +/// Checks that a given value is valid. +/// Must be overriden by subtypes. +/// Any type can be passed through. +/datum/preference/proc/is_valid(value) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("`is_valid()` was not implemented for [type]!") + +/// Returns data to be sent to users in the menu +/datum/preference/proc/compile_ui_data(mob/user, value) + SHOULD_NOT_SLEEP(TRUE) + + return pref_serialize(value) + +/// Returns data compiled into the preferences JSON asset +/datum/preference/proc/compile_constant_data() + SHOULD_NOT_SLEEP(TRUE) + + return null + +/// Returns whether or not this preference is accessible. +/// If FALSE, will not show in the UI and will not be editable (by update_preference). +/datum/preference/proc/is_accessible(datum/preferences/preferences) + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + + // if( + // !isnull(relevant_mutant_bodypart) + // || !isnull(relevant_inherent_trait) + // || !isnull(relevant_external_organ) + // || !isnull(relevant_head_flag) + // || !isnull(relevant_body_markings) + // ) + // var/species_type = preferences.read_preference(/datum/preference/choiced/species) + + // var/datum/species/species = GLOB.species_prototypes[species_type] + // if(!(savefile_key in species.get_features())) + // return FALSE + + if(!should_show_on_page(preferences.current_window)) + return FALSE + + return TRUE + +/// Returns whether or not, given the PREFERENCE_TAB_*, this preference should +/// appear. +/datum/preference/proc/should_show_on_page(preference_tab) + var/is_on_character_page = preference_tab == PREFERENCE_TAB_CHARACTER_PREFERENCES + var/is_character_preference = savefile_identifier == PREFERENCE_CHARACTER + return is_on_character_page == is_character_preference + +/// A preference that is a choice of one option among a fixed set. +/// Used for preferences such as clothing. +/datum/preference/choiced + /// If this is TRUE, an icon will be generated for every value. + /// If you implement this, you must implement `icon_for(value)` for every possible value. + var/should_generate_icons = FALSE + + var/list/cached_values + + /// If the preference is a main feature (PREFERENCE_CATEGORY_FEATURES or PREFERENCE_CATEGORY_CLOTHING) + /// this is the name of the feature that will be presented. + var/main_feature_name + + abstract_type = /datum/preference/choiced + +/// Returns a list of every possible value. +/// The first time this is called, will run `init_values()`. +/// Return value can be in the form of: +/// - A flat list of raw values, such as list(MALE, FEMALE, PLURAL). +/// - An assoc list of raw values to atoms/icons. +/datum/preference/choiced/proc/get_choices() + // Override `init_values()` instead. + SHOULD_NOT_OVERRIDE(TRUE) + + if(isnull(cached_values)) + cached_values = init_possible_values() + ASSERT(cached_values.len) + + return cached_values + +/// Returns a list of every possible value, serialized. +/datum/preference/choiced/proc/get_choices_serialized() + // Override `init_values()` instead. + SHOULD_NOT_OVERRIDE(TRUE) + + var/list/serialized_choices = list() + + for(var/choice in get_choices()) + serialized_choices += pref_serialize(choice) + + return serialized_choices + +/// Returns a list of every possible value. +/// This must be overriden by `/datum/preference/choiced` subtypes. +/// If `should_generate_icons` is TRUE, then you will also need to implement `icon_for(value)` +/// for every possible value. +/datum/preference/choiced/proc/init_possible_values() + CRASH("`init_possible_values()` was not implemented for [type]!") + +/// When `should_generate_icons` is TRUE, this proc is called for every value. +/// It can return either an icon or a typepath to an atom to create. +/datum/preference/choiced/proc/icon_for(value) + SHOULD_CALL_PARENT(FALSE) + SHOULD_NOT_SLEEP(TRUE) + CRASH("`icon_for()` was not implemented for [type], even though should_generate_icons = TRUE!") + +/datum/preference/choiced/is_valid(value) + return value in get_choices() + +/datum/preference/choiced/pref_deserialize(input, datum/preferences/preferences) + return sanitize_inlist(input, get_choices(), create_default_value()) + +/datum/preference/choiced/create_default_value() + return pick(get_choices()) + +/datum/preference/choiced/compile_constant_data() + var/list/data = list() + + var/list/choices = list() + + for(var/choice in get_choices()) + choices += choice + + data["choices"] = choices + + if(should_generate_icons) + var/list/icons = list() + + // for(var/choice in choices) // TODO: Pref spritesheet asset + // icons[choice] = get_spritesheet_key(choice) + + data["icons"] = icons + + if(!isnull(main_feature_name)) + data["name"] = main_feature_name + + return data + +/// A preference that represents an RGB color of something. +/// Will give the value as 6 hex digits, without a hash. +/datum/preference/color + abstract_type = /datum/preference/color + +/datum/preference/color/pref_deserialize(input, datum/preferences/preferences) + return sanitize_hexcolor(input) + +/datum/preference/color/create_default_value() + return random_color() + +/datum/preference/color/pref_serialize(input) + return sanitize_hexcolor(input) + +/datum/preference/color/is_valid(value) + return findtext(value, GLOB.is_color) + +/// A numeric preference with a minimum and maximum value +/datum/preference/numeric + /// The minimum value + var/minimum + + /// The maximum value + var/maximum + + /// The step of the number, such as 1 for integers or 0.5 for half-steps. + var/step = 1 + + abstract_type = /datum/preference/numeric + +/datum/preference/numeric/pref_deserialize(input, datum/preferences/preferences) + if(istext(input)) // Sometimes TGUI will return a string instead of a number, so we take that into account. + input = text2num(input) // Worst case, it's null, it'll just use create_default_value() + return sanitize_float(input, minimum, maximum, step, create_default_value()) + +/datum/preference/numeric/pref_serialize(input) + return sanitize_float(input, minimum, maximum, step, create_default_value()) + +/datum/preference/numeric/create_default_value() + return rand(minimum, maximum) + +/datum/preference/numeric/is_valid(value) + return isnum(value) && value >= round(minimum, step) && value <= round(maximum, step) + +/datum/preference/numeric/compile_constant_data() + return list( + "minimum" = minimum, + "maximum" = maximum, + "step" = step, + ) + +/// A preference whose value is always TRUE or FALSE +/datum/preference/toggle + abstract_type = /datum/preference/toggle + + /// The default value of the toggle, if create_default_value is not specified + var/default_value = TRUE + +/datum/preference/toggle/create_default_value() + return default_value + +/datum/preference/toggle/pref_deserialize(input, datum/preferences/preferences) + return !!input + +/datum/preference/toggle/is_valid(value) + return value == TRUE || value == FALSE + + +/// A string-based preference accepting arbitrary string values entered by the user, with a maximum length. +/datum/preference/text + abstract_type = /datum/preference/text + + /// What is the maximum length of the value allowed in this field? + var/maximum_value_length = 256 + + /// Should we strip HTML the input or simply restrict it to the maximum_value_length? + var/should_strip_html = TRUE + + +/datum/preference/text/pref_deserialize(input, datum/preferences/preferences) + return should_strip_html ? STRIP_HTML_SIMPLE(input, maximum_value_length) : copytext(input, 1, maximum_value_length) + +/datum/preference/text/create_default_value() + return "" + +/datum/preference/text/is_valid(value) + return istext(value) && length(value) < maximum_value_length + +/datum/preference/text/compile_constant_data() + return list("maximum_length" = maximum_value_length) diff --git a/code/modules/client/preferences/middleware/_middleware.dm b/code/modules/client/preferences/middleware/_middleware.dm new file mode 100644 index 0000000000..8f47f73642 --- /dev/null +++ b/code/modules/client/preferences/middleware/_middleware.dm @@ -0,0 +1,52 @@ +/// Preference middleware is code that helps to decentralize complicated preference features. +/datum/preference_middleware + /// The preferences datum + var/datum/preferences/preferences + + /// The key that will be used for get_constant_data(). + /// If null, will use the typepath minus /datum/preference_middleware. + var/key = null + + /// Map of ui_act actions -> proc paths to call. + /// Signature is `(list/params, mob/user) -> TRUE/FALSE. + /// Return output is the same as ui_act--TRUE if it should update, FALSE if it should not + var/list/action_delegations = list() + +/datum/preference_middleware/New(datum/preferences) + src.preferences = preferences + + if (isnull(key)) + // + 2 coming from the off-by-one of copytext, and then another from the slash + key = copytext("[type]", length("[parent_type]") + 2) + +/datum/preference_middleware/Destroy() + preferences = null + return ..() + +/// Append all of these into ui_data +/datum/preference_middleware/proc/get_ui_data(mob/user) + return list() + +/// Append all of these into ui_static_data +/datum/preference_middleware/proc/get_ui_static_data(mob/user) + return list() + +/// Append all of these into ui_assets +/datum/preference_middleware/proc/get_ui_assets() + return list() + +/// Append all of these into /datum/asset/json/preferences. +/datum/preference_middleware/proc/get_constant_data() + return null + +/// Merge this into the result of compile_character_preferences. +/datum/preference_middleware/proc/get_character_preferences(mob/user) + return null + +/// Called every set_preference, returns TRUE if this handled it. +/datum/preference_middleware/proc/pre_set_preference(mob/user, preference, value) + return FALSE + +/// Called when a character is changed. +/datum/preference_middleware/proc/on_new_character(mob/user) + return diff --git a/code/modules/client/preferences/migrations/13_preferences.dm b/code/modules/client/preferences/migrations/13_preferences.dm new file mode 100644 index 0000000000..1c6eb47d31 --- /dev/null +++ b/code/modules/client/preferences/migrations/13_preferences.dm @@ -0,0 +1,12 @@ +/// Transforms the bay style `"preferences": ["SOUND_MIDI", ...]` to `"SOUND_MIDI": 1` and `"preferences_disabled": [...]` to `0`. +/datum/preferences/proc/migration_13_preferences(datum/json_savefile/S) + var/list/preferences_enabled = S.get_entry("preferences") + for(var/key in preferences_enabled) + S.set_entry("[key]", TRUE) + + var/list/preferences_disabled = S.get_entry("preferences_disabled") + for(var/key in preferences_disabled) + S.set_entry("[key]", FALSE) + + // Force a reload of the value cache + apply_all_client_preferences() diff --git a/code/modules/client/preferences/migrations/14_nifs.dm b/code/modules/client/preferences/migrations/14_nifs.dm new file mode 100644 index 0000000000..da2a25a71e --- /dev/null +++ b/code/modules/client/preferences/migrations/14_nifs.dm @@ -0,0 +1,17 @@ +/// Moves nif stuff to it's own file +/datum/preferences/proc/migration_14_nifs(datum/json_savefile/S) + var/datum/json_savefile/new_savefile = new /datum/json_savefile(nif_savefile_path(client_ckey)) + + for(var/slot in 1 to CONFIG_GET(number/character_slots)) //CHOMPEdit + var/list/prefs = S.get_entry("character[slot]", null) + if(!islist(prefs)) + continue + + var/list/new_data = list() + new_data["nif_path"] = prefs["nif_path"] + new_data["nif_durability"] = prefs["nif_durability"] + new_data["nif_savedata"] = prefs["nif_savedata"] + + new_savefile.set_entry("character[slot]", new_data) + + new_savefile.save() diff --git a/code/modules/client/preferences/preferences_tg.dm b/code/modules/client/preferences/preferences_tg.dm new file mode 100644 index 0000000000..944bffc039 --- /dev/null +++ b/code/modules/client/preferences/preferences_tg.dm @@ -0,0 +1,28 @@ +// Contains all of the variables and such to make tg prefs work +/datum/preferences + /// The savefile relating to character preferences, PREFERENCE_CHARACTER + var/list/character_data + + /// A list of keys that have been updated since the last save. + var/list/recently_updated_keys = list() + + /// A cache of preference entries to values. + /// Used to avoid expensive READ_FILE every time a preference is retrieved. + var/value_cache = list() + + /// If set to TRUE, will update character_profiles on the next ui_data tick. + var/tainted_character_profiles = FALSE + + var/current_window = PREFERENCE_TAB_GAME_PREFERENCES + + /// A list of instantiated middleware + var/list/datum/preference_middleware/middleware = list() + +/// Applies all PREFERENCE_PLAYER preferences +/datum/preferences/proc/apply_all_client_preferences() + for(var/datum/preference/preference as anything in get_preferences_in_priority_order()) + if(preference.savefile_identifier != PREFERENCE_PLAYER) + continue + + value_cache -= preference.type + preference.apply_to_client(client, read_preference(preference.type)) diff --git a/code/modules/client/preferences/types/admin.dm b/code/modules/client/preferences/types/admin.dm new file mode 100644 index 0000000000..f066bc041e --- /dev/null +++ b/code/modules/client/preferences/types/admin.dm @@ -0,0 +1,73 @@ +/datum/preference/toggle/show_attack_logs + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_ATTACKLOGS" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_attack_logs/is_accessible(datum/preferences/preferences) + . = ..() + if(!.) + return + + return check_rights(R_MOD|R_ADMIN, FALSE, preferences.client) + +/datum/preference/toggle/show_debug_logs + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_DEBUGLOGS" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_debug_logs/is_accessible(datum/preferences/preferences) + . = ..() + if(!.) + return + + return check_rights(R_DEBUG|R_ADMIN, FALSE, preferences.client) + +/datum/preference/toggle/show_chat_prayers + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_PRAYER" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_chat_prayers/is_accessible(datum/preferences/preferences) + . = ..() + if(!.) + return + + return check_rights(R_EVENT|R_ADMIN, FALSE, preferences.client) + +// General holder prefs +/datum/preference/toggle/holder + abstract_type = /datum/preference/toggle/holder + +/datum/preference/toggle/holder/is_accessible(datum/preferences/preferences) + . = ..(preferences) + if(!.) + return + + return preferences.client.holder + +/datum/preference/toggle/holder/play_adminhelp_ping + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_ADMINHELP" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/holder/hear_radio + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_RADIO" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/holder/show_rlooc + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_RLOOC" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/holder/show_staff_dsay + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_ADSAY" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/preferences/types/chat.dm b/code/modules/client/preferences/types/chat.dm new file mode 100644 index 0000000000..4bf527a1cf --- /dev/null +++ b/code/modules/client/preferences/types/chat.dm @@ -0,0 +1,69 @@ +/datum/preference/toggle/chat_tags + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_SHOWICONS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_typing_indicator + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SHOW_TYPING" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_typing_indicator/apply_to_client(client/client, value) + if(!value) + client.stop_thinking() + +/datum/preference/toggle/show_typing_indicator_subtle + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SHOW_TYPING_SUBTLE" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_ooc + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_OOC" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_looc + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_LOOC" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_dsay + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_DEAD" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/check_mention + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_MENTION" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/vore_health_bars + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "VORE_HEALTH_BARS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_lore_news + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "NEWS_POPUP" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/player_tips + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "RECEIVE_TIPS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/pain_frequency + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "PAIN_FREQUENCY" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/preferences/types/ghost.dm b/code/modules/client/preferences/types/ghost.dm new file mode 100644 index 0000000000..8ae51631ed --- /dev/null +++ b/code/modules/client/preferences/types/ghost.dm @@ -0,0 +1,29 @@ +/datum/preference/toggle/whisubtle_vis + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "WHISUBTLE_VIS" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/ghost_see_whisubtle + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "GHOST_SEE_WHISUBTLE" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/ghost_ears + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_GHOSTEARS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/ghost_sight + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_GHOSTSIGHT" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/ghost_radio + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "CHAT_GHOSTRADIO" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/preferences/types/misc.dm b/code/modules/client/preferences/types/misc.dm new file mode 100644 index 0000000000..b1ec621671 --- /dev/null +++ b/code/modules/client/preferences/types/misc.dm @@ -0,0 +1,71 @@ +/// Whether or not to toggle ambient occlusion, the shadows around people +/datum/preference/toggle/ambient_occlusion + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "AMBIENT_OCCLUSION_PREF" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value) + var/datum/plane_holder/PH = client?.mob?.plane_holder + if(PH) + PH.set_ao(VIS_OBJS, value) + PH.set_ao(VIS_MOBS, value) + +/datum/preference/toggle/mob_tooltips + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "MOB_TOOLTIPS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/inv_tooltips + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "INV_TOOLTIPS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/attack_icons + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "ATTACK_ICONS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/precision_placement + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "PRECISE_PLACEMENT" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/hotkeys_default + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "HUD_HOTKEYS" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/show_progress_bar + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SHOW_PROGRESS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/safefiring + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SAFE_FIRING" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/status_indicators + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SHOW_STATUS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/status_indicators/apply_to_client(client/client, value) + var/datum/plane_holder/PH = client?.mob?.plane_holder + if(PH) + PH.set_vis(VIS_STATUS, value) + +/datum/preference/toggle/auto_afk + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "AUTO_AFK" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/preferences/types/runechat.dm b/code/modules/client/preferences/types/runechat.dm new file mode 100644 index 0000000000..1c6ea10fb3 --- /dev/null +++ b/code/modules/client/preferences/types/runechat.dm @@ -0,0 +1,23 @@ +/datum/preference/toggle/runechat_mob + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "RUNECHAT_MOB" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/runechat_obj + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "RUNECHAT_OBJ" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/runechat_border + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "RUNECHAT_BORDER" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/runechat_long_messages + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "RUNECHAT_LONG" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/preferences/types/sound.dm b/code/modules/client/preferences/types/sound.dm new file mode 100644 index 0000000000..5dc787d259 --- /dev/null +++ b/code/modules/client/preferences/types/sound.dm @@ -0,0 +1,149 @@ +/datum/preference/toggle/play_admin_midis + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_MIDI" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/play_lobby_music + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_LOBBY" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/play_lobby_music/apply_to_client(client/client, value) + if(value) + client?.playtitlemusic() + else + client?.media?.stop_music() + +/datum/preference/toggle/play_ambience + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_AMBIENCE" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/play_ambience/apply_to_client(client/client, value) + if(!value) + client << sound(null, repeat = 0, wait = 0, volume = 0, channel = CHANNEL_AMBIENCE_FORCED) + client << sound(null, repeat = 0, wait = 0, volume = 0, channel = CHANNEL_AMBIENCE) + +/datum/preference/toggle/play_jukebox + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_JUKEBOX" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/play_jukebox/apply_to_client(client/client, value) + if(value) + client?.mob?.update_music() + else + client?.mob?.stop_all_music() + +/datum/preference/toggle/instrument_toggle + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_INSTRUMENT" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/emote_noises + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "EMOTE_NOISES" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/radio_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "RADIO_SOUNDS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/say_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SAY_SOUNDS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/emote_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "EMOTE_SOUNDS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/whisper_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "WHISPER_SOUNDS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/subtle_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SUBTLE_SOUNDS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/air_pump_noise + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_AIRPUMP" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/old_door_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_OLDDOORS" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/department_door_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_DEPARTMENTDOORS" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/pickup_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_PICKED" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/drop_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_DROPPED" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/weather_sounds + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_WEATHER" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/supermatter_hum + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_SUPERMATTER" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/play_mentorhelp_ping + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_MENTORHELP" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +// Vorey sounds +/datum/preference/toggle/belch_noises // Belching noises - pref toggle for 'em + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "BELCH_NOISES" + default_value = FALSE //CHOMPEdit + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/eating_noises + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "EATING_NOISES" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/digestion_noises + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "DIGEST_NOISES" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/preferences/types/ui.dm b/code/modules/client/preferences/types/ui.dm new file mode 100644 index 0000000000..006c15527b --- /dev/null +++ b/code/modules/client/preferences/types/ui.dm @@ -0,0 +1,37 @@ +/datum/preference/toggle/browser_style + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "BROWSER_STYLED" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/vchat_enable + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "VCHAT_ENABLE" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/vchat_enable/apply_to_client(client/client, value) + var/before = client.tgui_panel.oldchat + if(value) + client.tgui_panel.oldchat = FALSE + else + client.tgui_panel.oldchat = TRUE + + // If something actually changed, reload chat + if(before != client.tgui_panel.oldchat) + client.nuke_chat() + +/datum/preference/toggle/tgui_say + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "TGUI_SAY" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/tgui_say_light + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "TGUI_SAY_LIGHT_MODE" + default_value = FALSE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/tgui_say_light/apply_to_client(client/client, value) + client.tgui_say?.load() diff --git a/code/modules/client/preferences_ch.dm b/code/modules/client/preferences_ch.dm deleted file mode 100644 index 58e9bb61e7..0000000000 --- a/code/modules/client/preferences_ch.dm +++ /dev/null @@ -1,67 +0,0 @@ -/client/verb/toggle_looping_alarms() - set name = "Looping Alarms" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles alarm sound loops." - - var/pref_path = /datum/client_preference/looping_alarms - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear alarm sounds looping.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAlarmLoops") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_sleep_music() - set name = "Toggle Sleeping Music" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "When enabled, you will hear cozy music played during surgery, cryo, and sleeper pod usage." - - var/pref_path = /datum/client_preference/sleep_music - - toggle_preference(pref_path) - - to_chat(src, "You are [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hearing sleeping music while in cryo/surgery/sleeper.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb", "TSleepMusic") - -/* // Up-ported to Virgo, disabling here -/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]"] -*/ diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 26afa27045..8c5e58c139 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -1,121 +1,267 @@ #define SAVEFILE_VERSION_MIN 8 -#define SAVEFILE_VERSION_MAX 11 +#define SAVEFILE_VERSION_MAX 14 -//handles converting savefiles to new formats -//MAKE SURE YOU KEEP THIS UP TO DATE! -//If the sanity checks are capable of handling any issues. Only increase SAVEFILE_VERSION_MAX, -//this will mean that savefile_version will still be over SAVEFILE_VERSION_MIN, meaning -//this savefile update doesn't run everytime we load from the savefile. -//This is mainly for format changes, such as the bitflags in toggles changing order or something. -//if a file can't be updated, return 0 to delete it and start again -//if a file was updated, return 1 -/datum/preferences/proc/savefile_update() - if(savefile_version < 8) //lazily delete everything + additional files so they can be saved in the new format - for(var/ckey in preferences_datums) - var/datum/preferences/D = preferences_datums[ckey] - if(D == src) - var/delpath = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/" - if(delpath && fexists(delpath)) - fdel(delpath) - break - return 0 +/* +SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn + This proc checks if the current directory of the savefile S needs updating + It is to be used by the load_character and load_preferences procs. + (S.cd == "/" is preferences, S.cd == "/character[integer]" is a character slot, etc) - if(savefile_version == SAVEFILE_VERSION_MAX) //update successful. - save_preferences() - save_character() - return 1 - return 0 + if the current directory's version is below SAVEFILE_VERSION_MIN it will simply wipe everything in that directory + (if we're at root "/" then it'll just wipe the entire savefile, for instance.) -/datum/preferences/proc/load_path(ckey,filename="preferences.sav") - if(!ckey) return + if its version is below SAVEFILE_VERSION_MAX but above the minimum, it will load data but later call the + respective update_preferences() or update_character() proc. + Those procs allow coders to specify format changes so users do not lose their setups and have to redo them again. + + Failing all that, the standard sanity checks are performed. They simply check the data is suitable, reverting to + initial() values if necessary. +*/ +/datum/preferences/proc/save_data_needs_update(list/save_data) + if(!save_data) // empty list, either savefile isnt loaded or its a new char + return -1 + if(!save_data["version"]) // special case: if there is no version key, such as in character slots before v12 + return -3 + if(save_data["version"] < SAVEFILE_VERSION_MIN) + return -2 + if(save_data["version"] < SAVEFILE_VERSION_MAX) + return save_data["version"] + return -1 + +//should these procs get fairly long +//just increase SAVEFILE_VERSION_MIN so it's not as far behind +//SAVEFILE_VERSION_MAX and then delete any obsolete if clauses +//from these procs. +//This only really meant to avoid annoying frequent players +//if your savefile is 3 months out of date, then 'tough shit'. + +/datum/preferences/proc/update_preferences(current_version, datum/json_savefile/S) + // Migration from BYOND savefiles to JSON: Important milemark. + // if(current_version < 11) + + // Migration for client preferences + if(current_version < 13) + log_debug("[client_ckey] preferences migrating from [current_version] to v13....") + to_chat(client, span_danger("Migrating savefile from version [current_version] to v13...")) + + migration_13_preferences(S) + + log_debug("[client_ckey] preferences successfully migrated from [current_version] to v13.") + to_chat(client, span_danger("v13 savefile migration complete.")) + + // Migration for nifs + if(current_version < 14) + log_debug("[client_ckey] preferences migrating from [current_version] to v14....") + to_chat(client, span_danger("Migrating savefile from version [current_version] to v14...")) + + migration_14_nifs(S) + + log_debug("[client_ckey] preferences successfully migrated from [current_version] to v14.") + to_chat(client, span_danger("v14 savefile migration complete.")) + + +/datum/preferences/proc/update_character(current_version, list/save_data) + // Migration from BYOND savefiles to JSON: Important milemark. + if(current_version == -3) + // Add a version field inside each character + save_data["version"] = SAVEFILE_VERSION_MAX + +/// Migrates from byond savefile to json savefile +/datum/preferences/proc/try_savefile_type_migration() + log_debug("[client_ckey] preferences migrating from savefile to JSON...") + to_chat(client, span_danger("Savefile migration to JSON in progress...")) + + load_path(client.ckey, "preferences.sav") // old save file + var/old_path = path + load_path(client.ckey) + if(!fexists(old_path)) + return + var/datum/json_savefile/json_savefile = new(path) + json_savefile.import_byond_savefile(new /savefile(old_path)) + json_savefile.save() + + log_debug("[client_ckey] preferences successfully migrated from savefile to JSON.") + to_chat(client, span_danger("Savefile migration to JSON is complete.")) + + return TRUE + +/datum/preferences/proc/load_path(ckey, filename = "preferences.json") + if(!ckey || !load_and_save) + return path = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/[filename]" - savefile_version = SAVEFILE_VERSION_MAX + +/datum/preferences/proc/load_savefile() + if(load_and_save && !path) + CRASH("Attempted to load savefile without first loading a path!") + savefile = new /datum/json_savefile(load_and_save ? path : null) /datum/preferences/proc/load_preferences() - if(!path) return 0 - if(!fexists(path)) return 0 - var/savefile/S = new /savefile(path) - if(!S) return 0 - S.cd = "/" + if(!savefile) + stack_trace("Attempted to load the preferences of [client] without a savefile; did you forget to call load_savefile?") + load_savefile() + if(!savefile) + stack_trace("Failed to load the savefile for [client] after manually calling load_savefile; something is very wrong.") + return FALSE - S["version"] >> savefile_version - //Conversion - if(!savefile_version || !isnum(savefile_version) || savefile_version < SAVEFILE_VERSION_MIN || savefile_version > SAVEFILE_VERSION_MAX) - if(!savefile_update()) //handles updates - savefile_version = SAVEFILE_VERSION_MAX - save_preferences() - save_character() - return 0 + var/needs_update = save_data_needs_update(savefile.get_entry()) + if(load_and_save && (needs_update <= -2)) //fatal, can't load any data + var/bacpath = "[path].updatebac" //todo: if the savefile version is higher then the server, check the backup, and give the player a prompt to load the backup + if(fexists(bacpath)) + fdel(bacpath) //only keep 1 version of backup + fcopy(savefile.path, bacpath) //byond helpfully lets you use a savefile for the first arg. + return FALSE - player_setup.load_preferences(S) - return 1 + apply_all_client_preferences() + + //try to fix any outdated data if necessary + if(needs_update >= 0) + var/bacpath = "[path].updatebac" //todo: if the savefile version is higher then the server, check the backup, and give the player a prompt to load the backup + if(fexists(bacpath)) + fdel(bacpath) //only keep 1 version of backup + fcopy(savefile.path, bacpath) //byond helpfully lets you use a savefile for the first arg. + update_preferences(needs_update, savefile) //needs_update = savefile_version if we need an update (positive integer) + + // Load general prefs after applying migrations + player_setup.load_preferences(savefile) + + //save the updated version + var/old_default_slot = default_slot + // var/old_max_save_slots = max_save_slots + + for(var/slot in savefile.get_entry()) //but first, update all current character slots. + if (copytext(slot, 1, 10) != "character") + continue + var/slotnum = text2num(copytext(slot, 10)) + if (!slotnum) + continue + // max_save_slots = max(max_save_slots, slotnum) //so we can still update byond member slots after they lose memeber status + default_slot = slotnum + if(load_character()) + save_character() + default_slot = old_default_slot + // max_save_slots = old_max_save_slots + save_preferences() + else + // Load general prefs + player_setup.load_preferences(savefile) + + return TRUE /datum/preferences/proc/save_preferences() - if(!path) return 0 - var/savefile/S = new /savefile(path) - if(!S) return 0 - S.cd = "/" + if(!savefile) + CRASH("Attempted to save the preferences of [client] without a savefile. This should have been handled by load_preferences()") + savefile.set_entry("version", SAVEFILE_VERSION_MAX) //updates (or failing that the sanity checks) will ensure data is not invalid at load. Assume up-to-date - S["version"] << savefile_version - player_setup.save_preferences(S) - return 1 + player_setup.save_preferences(savefile) + + for(var/preference_type in GLOB.preference_entries) + var/datum/preference/preference = GLOB.preference_entries[preference_type] + if(preference.savefile_identifier != PREFERENCE_PLAYER) + continue + + if(!(preference.type in recently_updated_keys)) + continue + + recently_updated_keys -= preference.type + + if(preference_type in value_cache) + write_preference(preference, preference.pref_serialize(value_cache[preference_type])) + + savefile.save() + + return TRUE + +/datum/preferences/proc/reset_slot() + var/bacpath = "[path].resetbac" + if(fexists(bacpath)) + fdel(bacpath) //only keep 1 version of backup + fcopy(savefile.path, bacpath) //byond helpfully lets you use a savefile for the first arg. + + savefile.remove_entry("character[default_slot]") + default_slot = 1 + + clear_character_previews() + + // Load slot 1 character + load_character() + // And save them immediately, in case we load an empty slot + save_character() + save_preferences() + return TRUE /datum/preferences/proc/load_character(slot) - if(!path) return 0 - if(!fexists(path)) return 0 - var/savefile/S = new /savefile(path) - if(!S) return 0 - S.cd = "/" - if(!slot) slot = default_slot - if(slot != SAVE_RESET) // SAVE_RESET will reset the slot as though it does not exist, but keep the current slot for saving purposes. - slot = sanitize_integer(slot, 1, CONFIG_GET(number/character_slots), initial(default_slot)) // CHOMPEdit - if(slot != default_slot) - default_slot = slot - S["default_slot"] << slot - else - S["default_slot"] << default_slot + SHOULD_NOT_SLEEP(TRUE) + if(!slot) + slot = default_slot - if(slot != SAVE_RESET) - S.cd = "/character[slot]" - player_setup.load_character(S) - else - player_setup.load_character(S) - S.cd = "/character[default_slot]" - player_setup.save_character(S) + slot = sanitize_integer(slot, 1, CONFIG_GET(number/character_slots), initial(default_slot)) // CHOMPEdit + if(slot != default_slot) + default_slot = slot + savefile.set_entry("default_slot", slot) - clear_character_previews() // VOREStation Edit - return 1 + var/list/save_data = savefile.get_entry("character[slot]") // This is allowed to be null and will give a -1 in needs_update + + var/needs_update = save_data_needs_update(save_data) + if(needs_update == -2) //fatal, can't load any data + return FALSE + + // Read everything into cache (pre-migrations, as migrations should have access to deserialized data) + // Uses priority order as some values may rely on others for creating default values + for(var/datum/preference/preference as anything in get_preferences_in_priority_order()) + if(preference.savefile_identifier != PREFERENCE_CHARACTER) + continue + + value_cache -= preference.type + read_preference(preference.type) + + // It has to be a list or load_character freaks out + if(!save_data) + player_setup.load_character(list()) + else + player_setup.load_character(save_data) + + //try to fix any outdated data if necessary + //preference updating will handle saving the updated data for us. + if(needs_update >= 0 || needs_update == -3) + update_character(needs_update, save_data) //needs_update == savefile_version if we need an update (positive integer + + clear_character_previews() + return TRUE /datum/preferences/proc/save_character() - if(!path) return 0 - var/savefile/S = new /savefile(path) - if(!S) return 0 - S.cd = "/character[default_slot]" + SHOULD_NOT_SLEEP(TRUE) + if(!savefile) + return FALSE - player_setup.save_character(S) - return 1 + var/tree_key = "character[default_slot]" + if(!(tree_key in savefile.get_entry())) + savefile.set_entry(tree_key, list()) + var/save_data = savefile.get_entry(tree_key) + + save_data["version"] = SAVEFILE_VERSION_MAX //load_character will sanitize any bad data, so assume up-to-date. + player_setup.save_character(save_data) + + return TRUE /datum/preferences/proc/overwrite_character(slot) - if(!path) return 0 - if(!fexists(path)) return 0 - var/savefile/S = new /savefile(path) - if(!S) return 0 - if(!slot) slot = default_slot - if(slot != SAVE_RESET) - slot = sanitize_integer(slot, 1, CONFIG_GET(number/character_slots), initial(default_slot)) // CHOMPEdit - if(slot != default_slot) - default_slot = slot - nif_path = nif_durability = nif_savedata = null //VOREStation Add - Don't copy NIF - S["default_slot"] << slot + if(!savefile) + return FALSE + if(!slot) + slot = default_slot - else - S["default_slot"] << default_slot + // This basically just changes default_slot without loading the correct data, so the next save call will overwrite + // the slot + slot = sanitize_integer(slot, 1, CONFIG_GET(number/character_slots), initial(default_slot)) // CHOMPEdit + if(slot != default_slot) + default_slot = slot + nif_path = nif_durability = nif_savedata = null //VOREStation Add - Don't copy NIF + savefile.set_entry("default_slot", slot) - return 1 + return TRUE /datum/preferences/proc/sanitize_preferences() player_setup.sanitize_setup() - return 1 + return TRUE #undef SAVEFILE_VERSION_MAX #undef SAVEFILE_VERSION_MIN diff --git a/code/modules/client/preferences_tgui.dm b/code/modules/client/preferences_tgui.dm new file mode 100644 index 0000000000..b5086632b8 --- /dev/null +++ b/code/modules/client/preferences_tgui.dm @@ -0,0 +1,138 @@ +/datum/preferences/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PreferencesMenu", "Preferences") + ui.set_autoupdate(FALSE) + ui.open() + +/datum/preferences/tgui_state(mob/user) + return GLOB.tgui_always_state + +/datum/preferences/tgui_status(mob/user, datum/tgui_state/state) + return user.client == client ? STATUS_INTERACTIVE : STATUS_CLOSE + +/datum/preferences/ui_assets(mob/user) + var/list/assets = list( + // get_asset_datum(/datum/asset/spritesheet/preferences), + get_asset_datum(/datum/asset/json/preferences), + ) + + for (var/datum/preference_middleware/preference_middleware as anything in middleware) + assets += preference_middleware.get_ui_assets() + + return assets + +/datum/preferences/tgui_data(mob/user) + var/list/data = list() + + if(tainted_character_profiles) + data["character_profiles"] = create_character_profiles() + tainted_character_profiles = FALSE + + data["character_preferences"] = compile_character_preferences(user) + + data["active_slot"] = default_slot + + for(var/datum/preference_middleware/preference_middleware as anything in middleware) + data += preference_middleware.get_ui_data(user) + + return data + +/datum/preferences/tgui_static_data(mob/user) + var/list/data = list() + + data["character_profiles"] = create_character_profiles() + + // data["character_preview_view"] = character_preview_view.assigned_map + // data["overflow_role"] = SSjob.GetJobType(SSjob.overflow_role).title + data["window"] = current_window + + for(var/datum/preference_middleware/preference_middleware as anything in middleware) + data += preference_middleware.get_ui_static_data(user) + + return data + +/datum/preferences/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + . = ..() + if(.) + return + + switch(action) + if("set_preference") + var/requested_preference_key = params["preference"] + var/value = params["value"] + + for(var/datum/preference_middleware/preference_middleware as anything in middleware) + if(preference_middleware.pre_set_preference(usr, requested_preference_key, value)) + return TRUE + + var/datum/preference/requested_preference = GLOB.preference_entries_by_key[requested_preference_key] + if(isnull(requested_preference)) + return FALSE + + // SAFETY: `update_preference` performs validation checks + if(!update_preference(requested_preference, value)) + return FALSE + + // if(istype(requested_preference, /datum/preference/name)) // TODO: do this + // tainted_character_profiles = TRUE + + return TRUE + + for(var/datum/preference_middleware/preference_middleware as anything in middleware) + var/delegation = preference_middleware.action_delegations[action] + if(!isnull(delegation)) + return call(preference_middleware, delegation)(params, usr) + + return FALSE + +/datum/preferences/tgui_close(mob/user) + save_character() + save_preferences() + +/datum/preferences/proc/create_character_profiles() + var/list/profiles = list() + + for(var/index in 1 to CONFIG_GET(number/character_slots)) //CHOMPEdit + // TODO: It won't be updated in the savefile yet, so just read the name directly + // if(index == default_slot) + // profiles += read_preference(/datum/preference/name/real_name) + // continue + + var/tree_key = "character[index]" + var/save_data = savefile.get_entry(tree_key) + var/name = save_data?["real_name"] + + if(isnull(name)) + profiles += null + continue + + profiles += name + + return profiles + +/datum/preferences/proc/compile_character_preferences(mob/user) + var/list/preferences = list() + + for(var/datum/preference/preference as anything in get_preferences_in_priority_order()) + if(!preference.is_accessible(src)) + continue + + var/value = read_preference(preference.type) + var/data = preference.compile_ui_data(user, value) + + LAZYINITLIST(preferences[preference.category]) + preferences[preference.category][preference.savefile_key] = data + + for(var/datum/preference_middleware/preference_middleware as anything in middleware) + var/list/append_character_preferences = preference_middleware.get_character_preferences(user) + if(isnull(append_character_preferences)) + continue + + for(var/category in append_character_preferences) + if(category in preferences) + preferences[category] += append_character_preferences[category] + else + preferences[category] = append_character_preferences[category] + + return preferences diff --git a/code/modules/client/preferences_toggle_procs.dm b/code/modules/client/preferences_toggle_procs.dm index 1869d88b1d..684b92a4ad 100644 --- a/code/modules/client/preferences_toggle_procs.dm +++ b/code/modules/client/preferences_toggle_procs.dm @@ -1,229 +1,3 @@ -//Toggles for preferences, normal clients -/client/verb/toggle_ghost_ears() - set name = "Toggle Ghost Ears" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles between seeing all mob speech and only nearby mob speech as an observer." - - var/pref_path = /datum/client_preference/ghost_ears - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear all mob speech as a ghost.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TGEars") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_vision() - set name = "Toggle Ghost Sight" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles between seeing all mob emotes and only nearby mob emotes as an observer." - - var/pref_path = /datum/client_preference/ghost_sight - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] see all emotes as a ghost.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TGVision") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_radio() - set name = "Toggle Ghost Radio" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles between seeing all radio chat and only nearby radio chatter as an observer." - - var/pref_path = /datum/client_preference/ghost_radio - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear all radios as a ghost.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TGRadio") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_deadchat() - set name = "Toggle Deadchat" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles visibility of dead chat." - - var/pref_path = /datum/client_preference/show_dsay - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear dead chat as a ghost.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TDeadChat") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ooc() - set name = "Toggle OOC" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles visibility of global out of character chat." - - var/pref_path = /datum/client_preference/show_ooc - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(/datum/client_preference/show_ooc)) ? "now" : "no longer"] hear global out of character chat.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_looc() - set name = "Toggle LOOC" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles visibility of local out of character chat." - - var/pref_path = /datum/client_preference/show_looc - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear local out of character chat.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_precision_placement() - set name = "Toggle Precision Placement" - set category = "Preferences.Game" //CHOMPEdit - set desc = "Toggles whether objects placed on table will be on cursor position or centered." - - var/pref_path = /datum/client_preference/precision_placement - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] place items where your cursor is on the table.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TPIP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_typing() - set name = "Toggle Typing Indicator" - set category = "Preferences.Game" //CHOMPEdit - set desc = "Toggles you having the speech bubble typing indicator." - - var/pref_path = /datum/client_preference/show_typing_indicator - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] have the speech indicator.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TTIND") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ahelp_sound() - set name = "Toggle Admin Help Sound" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear a noise broadcasted when you get an admin message." - - var/pref_path = /datum/client_preference/holder/play_adminhelp_ping - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] receive noise from admin messages.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAHelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_lobby_music() - set name = "Toggle Lobby Music" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear the music in the lobby." - - var/pref_path = /datum/client_preference/play_lobby_music - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear music in the lobby.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TLobMusic") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_admin_midis() - set name = "Toggle Admin Music" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear music played by admins." - - var/pref_path = /datum/client_preference/play_admin_midis - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear music from admins.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAMidis") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ambience() - set name = "Toggle Ambience" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear local ambience." - - var/pref_path = /datum/client_preference/play_ambiance - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear ambient noise.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAmbience") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_weather_sounds() - set name = "Toggle Weather Sounds" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear weather sounds while on a planet." - - var/pref_path = /datum/client_preference/weather_sounds - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear weather sounds.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TWeatherSounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_supermatter_hum() - set name = "Toggle SM Hum" // Avoiding using the full 'Supermatter' name to not conflict with the Setup-Supermatter adminverb. - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear supermatter hums." - - var/pref_path = /datum/client_preference/supermatter_hum - - toggle_preference(pref_path) - - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear a hum from the supermatter.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TSupermatterHum") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_jukebox() - set name = "Toggle Jukebox" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear jukebox music." - - var/pref_path = /datum/client_preference/play_jukebox - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear jukebox music.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TJukebox") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /client/verb/toggle_be_special(role in be_special_flags) set name = "Toggle Special Role Candidacy" set category = "Preferences.Character" //CHOMPEdit @@ -239,146 +13,6 @@ feedback_add_details("admin_verb","TBeSpecial") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/client/verb/toggle_air_pump_hum() - set name = "Toggle Air Vent Noise" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear air vent humming." - - var/pref_path = /datum/client_preference/air_pump_noise - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear air vents hum, start, and stop.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAirPumpNoise") - -/client/verb/toggle_old_door_sounds() - set name = "Toggle Door: Old Sounds" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles door sounds between old and new." - - var/pref_path = /datum/client_preference/old_door_sounds - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear the legacy door sounds.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TOldDoorSounds") - -/client/verb/toggle_department_door_sounds() - set name = "Toggle Door: Department Sounds" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles hearing of department-specific door sounds." - - var/pref_path = /datum/client_preference/department_door_sounds - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear per-department door sounds.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TDepartmentDoorSounds") - -/client/verb/toggle_pickup_sounds() - set name = "Toggle Item: Picked up Sounds" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear sounds when items are picked up." - - var/pref_path = /datum/client_preference/pickup_sounds - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear sounds when items are picked up.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb", "TPickupSounds") - -/client/verb/toggle_drop_sounds() - set name = "Toggle Item: Dropped Sounds" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear sounds when items are dropped or thrown." - - var/pref_path = /datum/client_preference/drop_sounds - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear sounds when items are dropped or thrown.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb", "TDropSounds") - -/client/verb/toggle_safe_firing() - set name = "Toggle Gun Firing Intent Requirement" - set category = "Preferences.Game" //CHOMPEdit - set desc = "Toggles between safe and dangerous firing. Safe requires a non-help intent to fire, dangerous can be fired on help intent." - - var/pref_path = /datum/client_preference/safefiring - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src,"You will now use [(is_preference_enabled(/datum/client_preference/safefiring)) ? "safe" : "dangerous"] firearms firing.") - - feedback_add_details("admin_verb","TFiringMode") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_mob_tooltips() - set name = "Toggle Mob Tooltips" - set category = "Preferences.Game" //CHOMPEdit - set desc = "Toggles displaying name/species over mobs when they are moused over." - - var/pref_path = /datum/client_preference/mob_tooltips - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src,"You will now [(is_preference_enabled(/datum/client_preference/mob_tooltips)) ? "see" : "not see"] mob tooltips.") - - feedback_add_details("admin_verb","TMobTooltips") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_inv_tooltips() - set name = "Toggle Item Tooltips" - set category = "Preferences.Game" //CHOMPEdit - set desc = "Toggles displaying name/desc over items when they are moused over (only applies in inventory)." - - var/pref_path = /datum/client_preference/inv_tooltips - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src,"You will now [(is_preference_enabled(/datum/client_preference/inv_tooltips)) ? "see" : "not see"] inventory tooltips.") - - feedback_add_details("admin_verb","TInvTooltips") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_hear_instruments() - set name = "Toggle Hear/Ignore Instruments" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles the ability to hear instruments playing." - - var/pref_path = /datum/client_preference/instrument_toggle - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/instrument_toggle)) ? "hear" : "not hear"] instruments being played.") - - feedback_add_details("admin_verb","THInstm") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_vchat() - set name = "Toggle TGChat" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles TGChat. Reloading TGChat and/or reconnecting required to affect changes." - - var/pref_path = /datum/client_preference/vchat_enable - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You have toggled TGChat [is_preference_enabled(pref_path) ? "on" : "off"]. \ - You will have to reload TGChat and/or reconnect to the server for these changes to take place. \ - TGChat message persistence is not guaranteed if you change this again before the start of the next round.") - /client/verb/toggle_chat_timestamps() set name = "Toggle Chat Timestamps" set category = "Preferences.Chat" //CHOMPEdit @@ -389,101 +23,6 @@ to_chat(src, span_notice("You have toggled chat timestamps: [prefs.chat_timestamp ? "ON" : "OFF"].")) -/client/verb/toggle_status_indicators() - set name = "Toggle Status Indicators" - set category = "Preferences.Game" //CHOMPEdit - set desc = "Toggles seeing status indicators over peoples' heads." - - var/pref_path = /datum/client_preference/status_indicators - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/status_indicators)) ? "see" : "not see"] status indicators.") - - feedback_add_details("admin_verb","TStatusIndicators") - - -/client/verb/toggle_radio_sounds() - set name = "Toggle Radio Sounds" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggle hearing a sound when somebody speaks over your headset." - - var/pref_path = /datum/client_preference/radio_sounds - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/radio_sounds)) ? "hear" : "not hear"] radio sounds.") - - feedback_add_details("admin_verb","TRadioSounds") - -/client/verb/toggle_say_sounds() - set name = "Toggle Voice Sounds" //CHOMPEdit - changed name to one that doesn't interfere with say autofill - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggle hearing a sound when somebody speaks or emotes." - - var/pref_path = /datum/client_preference/say_sounds - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/say_sounds)) ? "hear" : "not hear"] say sounds.") - - feedback_add_details("admin_verb","TSaySounds") -/* -CHOMPRemove. Bundled voice sounds into emote/whisper/subtle. Going this extra length to babyproof prefs wasn't necessary and got in the way of quick whisper/say autofill. - -/client/verb/toggle_emote_sounds() - set name = "Sound-Toggle-Me" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggle hearing a sound when somebody speaks using me ." - - var/pref_path = /datum/client_preference/emote_sounds - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/emote_sounds)) ? "hear" : "not hear"] me sounds.") - - feedback_add_details("admin_verb","TMeSounds") - -/client/verb/toggle_whisper_sounds() - set name = "Sound-Toggle-Whisper" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggle hearing a sound when somebody speaks using whisper." - - var/pref_path = /datum/client_preference/whisper_sounds - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/whisper_sounds)) ? "hear" : "not hear"] whisper sounds.") - - feedback_add_details("admin_verb","TWhisperSounds") - -/client/verb/toggle_subtle_sounds() - set name = "Sound-Toggle-Subtle" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggle hearing a sound when somebody uses subtle." - - var/pref_path = /datum/client_preference/subtle_sounds - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/subtle_sounds)) ? "hear" : "not hear"] subtle sounds.") - - feedback_add_details("admin_verb","TSubtleSounds") -*/ - -/client/verb/toggle_vore_health_bars() - set name = "Toggle Vore Health Bars" - set category = "Preferences.Vore" //CHOMPEdit - set desc = "Toggle the display of vore related health bars" - - var/pref_path = /datum/client_preference/vore_health_bars - toggle_preference(pref_path) - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, "Vore related health bars - [(is_preference_enabled(/datum/client_preference/vore_health_bars)) ? "Enabled" : "Disabled"]") - - feedback_add_details("admin_verb","TVoreHealthBars") - // Not attached to a pref datum because those are strict binary toggles /client/verb/toggle_examine_mode() set name = "Toggle Examine Mode" @@ -518,65 +57,3 @@ CHOMPRemove. Bundled voice sounds into emote/whisper/subtle. Going this extra le to_chat(src, "Multilingual parsing will enforce the a language delimiter after the delimiter-key combination (,0,galcom -2 still galcom). The extra delimiter will be consumed by the pattern-matching.") if(MULTILINGUAL_OFF) to_chat(src, "Multilingual parsing is now disabled. Entire messages will be in the language specified at the start of the message.") - - -//Toggles for Staff -//Developers - -/client/proc/toggle_debug_logs() - set name = "Toggle Debug Logs" - set category = "Preferences.Admin" //CHOMPEdit - set desc = "Toggles seeing debug logs." - - var/pref_path = /datum/client_preference/debug/show_debug_logs - - if(check_rights(R_ADMIN|R_DEBUG)) - toggle_preference(pref_path) - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] receive debug logs.") - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TADebugLogs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -//Mods -/client/proc/toggle_attack_logs() - set name = "Toggle Attack Logs" - set category = "Preferences.Admin" //CHOMPEdit - set desc = "Toggles seeing attack logs." - - var/pref_path = /datum/client_preference/mod/show_attack_logs - - if(check_rights(R_ADMIN|R_MOD)) - toggle_preference(pref_path) - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] receive attack logs.") - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAAttackLogs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -//General -/client/proc/toggle_admin_global_looc() - set name = "Toggle Admin Global LOOC Visibility" - set category = "Preferences.Admin" //CHOMPEdit - set desc = "Toggles seeing LOOC messages outside your actual LOOC range." - - var/pref_path = /datum/client_preference/holder/show_rlooc - - if(holder) - toggle_preference(pref_path) - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear global LOOC.") - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAGlobalLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_admin_deadchat() - set name = "Toggle Admin Living Deadchat" - set category = "Preferences.Admin" //CHOMPEdit - set desc = "Toggles seeing deadchat while not observing." - - var/pref_path = /datum/client_preference/holder/show_staff_dsay - - if(holder) - toggle_preference(pref_path) - to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear deadchat while not observing.") - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TADeadchat") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/client/preferences_vr.dm b/code/modules/client/preferences_vr.dm index 8aac560416..c09da3bce5 100644 --- a/code/modules/client/preferences_vr.dm +++ b/code/modules/client/preferences_vr.dm @@ -18,97 +18,6 @@ var/job_talon_low = 0 //Why weren't these in game toggles already? -/client/verb/toggle_eating_noises() - set name = "Toggle Eating Noises" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles hearing Vore Eating noises." - - var/pref_path = /datum/client_preference/eating_noises - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear eating related vore noises.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TEatNoise") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/verb/toggle_digestion_noises() - set name = "Toggle Digestion Noises" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles hearing Vore Digestion noises." - - var/pref_path = /datum/client_preference/digestion_noises - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear digestion related vore noises.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TDigestNoise") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_belch_noises() - set name = "Toggle Audible Belching" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles hearing audible belches." - - var/pref_path = /datum/client_preference/belch_noises - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear belching.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TBelchNoise") - -/client/verb/toggle_emote_noises() - set name = "Toggle Emote Noises" - set category = "Preferences.Sounds" //CHOMPEdit - set desc = "Toggles hearing emote noises." - - var/pref_path = /datum/client_preference/emote_noises - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear emote-related noises.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TEmoteNoise") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_quiets() - set name = "Toggle Ghost Privacy" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "Toggles ghosts being able to see your subtles/whispers." - - var/pref_path = /datum/client_preference/whisubtle_vis - - toggle_preference(pref_path) - - to_chat(src, "Ghosts will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear subtles/whispers made by you.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TWhisubtleVis") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_privacyvision() - set name = "Toggle Ghost Private Eyes/ears" - set category = "Preferences.Admin" //CHOMPEdit - set desc = "Toggles your ability to see subtles/whispers. Overrides admin status. Respects Ghost Privacy" - - var/pref_path = /datum/client_preference/ghost_see_whisubtle - - toggle_preference(pref_path) - - to_chat(src, "As a ghost, you will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear subtles/whispers made by players.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TGhostSeeWhisSubtle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /client/verb/toggle_capture_crystal() set name = "Toggle Catchable" set category = "Preferences.Character" //CHOMPEdit @@ -127,55 +36,3 @@ SScharacter_setup.queue_preferences_save(prefs) feedback_add_details("admin_verb","TCaptureCrystal") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_mentorhelp_ping() - set name = "Toggle Mentorhelp Ping" - set category = "Preferences.Admin" //CHOMPEdit - set desc = "Toggles the mentorhelp ping" - - var/pref_path = /datum/client_preference/play_mentorhelp_ping - - toggle_preference(pref_path) - - to_chat(src, "Mentorhelp pings are now [ is_preference_enabled(pref_path) ? "enabled" : "disabled"]") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb", "TSoundMentorhelps") - -/client/verb/toggle_player_tips() - set name = "Toggle Receiving Player Tips" - set category = "Preferences.Chat" //CHOMPEdit - set desc = "When toggled on, you receive tips periodically on roleplay and gameplay." - - var/pref_path = /datum/client_preference/player_tips - - toggle_preference(pref_path) - - to_chat(src, "You are [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] periodically receiving advice on gameplay and roleplay.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb", "TReceivePlayerTips") - -/client/verb/toggle_pain_frequency() - set name = "Toggle Pain Frequency" - set category = "Preferences.Game" //CHOMPEdit - set desc = "When toggled on, increases the cooldown of pain messages sent to chat for minor injuries" - - var/pref_path = /datum/client_preference/pain_frequency - - toggle_preference(pref_path) - - to_chat(src, "The cooldown between pain messages for minor (under 20/5 injury. Multi-limb injuries are still faster) is now [ (is_preference_enabled(pref_path)) ? "extended" : "default"].") - -/client/verb/toggle_automatic_afk() - set name = "Toggle Automatic AFK" - set category = "Preferences.Game" //CHOMPEdit - set desc = "When enabled, causes you to be automatically marked as AFK if you are idle for too long." - - var/pref_path = /datum/client_preference/auto_afk - - toggle_preference(pref_path) - - to_chat(src, "You will [ (is_preference_enabled(pref_path)) ? "now" : "not"] be automatically marked as AFK if you are idle for ten minutes or more.") diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index 339b71ac23..94c4714f2f 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -15,7 +15,7 @@ msg = sanitize(msg) if(!msg) return - if(!is_preference_enabled(/datum/client_preference/show_ooc)) + if(!prefs?.read_preference(/datum/preference/toggle/show_ooc)) to_chat(src, "You have OOC muted.") return @@ -66,7 +66,7 @@ msg = GLOB.is_valid_url.Replace(msg,"$1") for(var/client/target in GLOB.clients) - if(target.is_preference_enabled(/datum/client_preference/show_ooc)) + if(target.prefs?.read_preference(/datum/preference/toggle/show_ooc)) if(target.is_key_ignored(key)) // If we're ignored by this person, then do nothing. continue var/display_name = src.key @@ -101,7 +101,7 @@ if(!msg) return - if(!is_preference_enabled(/datum/client_preference/show_looc)) + if(!prefs?.read_preference(/datum/preference/toggle/show_looc)) to_chat(src, "You have LOOC muted.") return @@ -159,7 +159,7 @@ // Everyone in normal viewing range of the LOOC for(var/mob/viewer in m_viewers) - if(viewer.client && viewer.client.is_preference_enabled(/datum/client_preference/show_looc)) + if(viewer.client && viewer.client.prefs?.read_preference(/datum/preference/toggle/show_looc)) receivers |= viewer.client else if(istype(viewer,/mob/observer/eye)) // For AI eyes and the like var/mob/observer/eye/E = viewer @@ -168,7 +168,7 @@ // Admins with RLOOC displayed who weren't already in for(var/client/admin in GLOB.admins) - if(!(admin in receivers) && admin.is_preference_enabled(/datum/client_preference/holder/show_rlooc)) + if(!(admin in receivers) && admin.prefs?.read_preference(/datum/preference/toggle/holder/show_rlooc)) if(check_rights(R_ADMIN|R_SERVER, FALSE, admin)) //Stop rLOOC showing for retired staff //CHOMPEdit, admins should see LOOC r_receivers |= admin diff --git a/code/modules/client/verbs/typing.dm b/code/modules/client/verbs/typing.dm index 4845a40e32..50704f6c93 100644 --- a/code/modules/client/verbs/typing.dm +++ b/code/modules/client/verbs/typing.dm @@ -7,7 +7,7 @@ src << output('html/typing_indicator.html', "commandbar_spy") /client/proc/handle_commandbar_typing(href_list) - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator)) return if(length(href_list["verb"]) < 1 || !(lowertext(href_list["verb"]) in IC_VERBS) || text2num(href_list["argument_length"]) < 1) @@ -31,10 +31,10 @@ /** Sets the mob as "thinking" - with indicator and the TRAIT_THINKING_IN_CHARACTER trait */ /client/proc/start_thinking(channel) - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator)) return FALSE if(channel == "Whis" || channel == "Subtle" || channel == "whisper" || channel == "subtle") - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator_subtle)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator_subtle)) return FALSE ADD_TRAIT(mob, TRAIT_THINKING_IN_CHARACTER, CURRENTLY_TYPING_TRAIT) mob.create_thinking_indicator() @@ -50,10 +50,10 @@ /client/proc/start_typing(channel) var/mob/client_mob = mob client_mob.remove_thinking_indicator() - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator) || !HAS_TRAIT(client_mob, TRAIT_THINKING_IN_CHARACTER)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator) || !HAS_TRAIT(client_mob, TRAIT_THINKING_IN_CHARACTER)) return FALSE if(channel == "Whis" || channel == "Subtle" || channel == "whisper" || channel == "subtle") - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator_subtle)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator_subtle)) return FALSE client_mob.create_typing_indicator() addtimer(CALLBACK(src, PROC_REF(stop_typing), channel), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE) @@ -67,10 +67,10 @@ return FALSE var/mob/client_mob = mob client_mob.remove_typing_indicator() - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator) || !HAS_TRAIT(client_mob, TRAIT_THINKING_IN_CHARACTER)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator) || !HAS_TRAIT(client_mob, TRAIT_THINKING_IN_CHARACTER)) return FALSE if(channel == "Whis" || channel == "Subtle" || channel == "whisper" || channel == "subtle") - if(!is_preference_enabled(/datum/client_preference/show_typing_indicator_subtle)) + if(!prefs?.read_preference(/datum/preference/toggle/show_typing_indicator_subtle)) return FALSE client_mob.create_thinking_indicator() diff --git a/code/modules/emotes/definitions/audible_belch.dm b/code/modules/emotes/definitions/audible_belch.dm index 3bb3b6a17c..25db0333cb 100644 --- a/code/modules/emotes/definitions/audible_belch.dm +++ b/code/modules/emotes/definitions/audible_belch.dm @@ -2,7 +2,7 @@ key = "belch" emote_message_3p = "belches." message_type = AUDIBLE_MESSAGE - sound_preferences = list(/datum/client_preference/emote_noises,/datum/client_preference/belch_noises) + sound_preferences = list(/datum/preference/toggle/emote_noises, /datum/preference/toggle/belch_noises) /decl/emote/audible/belch/get_emote_sound(var/atom/user) return list( diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm index e8303d0b29..a29d8f4ecd 100644 --- a/code/modules/emotes/emote_define.dm +++ b/code/modules/emotes/emote_define.dm @@ -43,7 +43,7 @@ var/global/list/emotes_by_key var/conscious = TRUE // Do we need to be awake to emote this? var/emote_range = 0 // If >0, restricts emote visibility to viewers within range. - var/sound_preferences = list(/datum/client_preference/emote_noises) // Default emote sound_preferences is just emote_noises. Belch emote overrides this list for pref-checks. + var/sound_preferences = list(/datum/preference/toggle/emote_noises) // Default emote sound_preferences is just emote_noises. Belch emote overrides this list for pref-checks. var/sound_vary = FALSE /decl/emote/Initialize() @@ -190,7 +190,7 @@ var/global/list/emotes_by_key //CHOMPEdit Add - Preference for variable pitch + Extra range argument if(istype(user, /mob)) var/mob/u = user - playsound(user.loc, sound_to_play, use_sound["vol"], u.is_preference_enabled(/datum/client_preference/random_emote_pitch) && sound_vary, extrarange = use_sound["exr"], frequency = u.voice_freq, preference = sound_preferences, volume_channel = use_sound["volchannel"]) //CHOMPEdit + playsound(user.loc, sound_to_play, use_sound["vol"], u.read_preference(/datum/preference/toggle/random_emote_pitch) && sound_vary, extrarange = use_sound["exr"], frequency = u.voice_freq, preference = sound_preferences, volume_channel = use_sound["volchannel"]) //CHOMPEdit else playsound(user.loc, sound_to_play, use_sound["vol"], sound_vary, extrarange = use_sound["exr"], frequency = null, preference = sound_preferences, volume_channel = use_sound["volchannel"]) //VOREStation Add - Preference // CHOMPEdit: volume channel + range //CHOMPEdit End - Previous line used to be outside an if/else before the edit. diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index c8e18b217e..cc13f2d0d2 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -214,7 +214,7 @@ if(!T) return if(client) - playsound(T, pick(voice_sounds_list), 75, TRUE, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/client_preference/say_sounds) //CHOMPEdit - use say prefs instead //ChompEDIT - also ignore walls + playsound(T, pick(voice_sounds_list), 75, TRUE, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/preference/toggle/emote_sounds) //CHOMPEdit - use say prefs instead //ChompEDIT - also ignore walls var/list/in_range = get_mobs_and_objs_in_view_fast(T,range,2,remote_ghosts = client ? TRUE : FALSE) var/list/m_viewers = in_range["mobs"] var/list/o_viewers = in_range["objs"] diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm index bd964765cb..13c3b74453 100644 --- a/code/modules/instruments/songs/play_legacy.dm +++ b/code/modules/instruments/songs/play_legacy.dm @@ -89,5 +89,5 @@ if(!M) hearing_mobs -= M continue - M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played, preference = /datum/client_preference/instrument_toggle, volume_channel = VOLUME_CHANNEL_INSTRUMENTS) + M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played, preference = /datum/preference/toggle/instrument_toggle, volume_channel = VOLUME_CHANNEL_INSTRUMENTS) // Could do environment and echo later but not for now diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm index bf6444b025..dcfd5edc94 100644 --- a/code/modules/instruments/songs/play_synthesized.dm +++ b/code/modules/instruments/songs/play_synthesized.dm @@ -79,7 +79,7 @@ channel = channel, pressure_affected = null, S = copy, - preference = /datum/client_preference/instrument_toggle, + preference = /datum/preference/toggle/instrument_toggle, volume_channel = VOLUME_CHANNEL_INSTRUMENTS) // Could do environment and echo later but not for now diff --git a/code/modules/media/mediamanager.dm b/code/modules/media/mediamanager.dm index 5faf06318d..172796395e 100644 --- a/code/modules/media/mediamanager.dm +++ b/code/modules/media/mediamanager.dm @@ -137,9 +137,9 @@ // Tell the player to play something via JS. /datum/media_manager/proc/send_update() - if(!(owner.prefs)) + if(!owner.prefs) return - if(!owner.is_preference_enabled(/datum/client_preference/play_jukebox) && url != "") + if(!owner.prefs.read_preference(/datum/preference/toggle/play_jukebox) && url != "") return // Don't send anything other than a cancel to people with SOUND_STREAMING pref disabled MP_DEBUG("Sending update to mediapanel ([url], [(world.time - start_time) / 10], [volume * source_volume])...") owner << output(list2params(list(url, (world.time - start_time) / 10, volume * source_volume)), "[WINDOW_ID]:SetMusic") diff --git a/code/modules/mentor/mentor.dm b/code/modules/mentor/mentor.dm index 5f9e2e13fd..2905ae689c 100644 --- a/code/modules/mentor/mentor.dm +++ b/code/modules/mentor/mentor.dm @@ -269,7 +269,7 @@ var/list/mentor_verbs_default = list( log_admin("[key_name(src)]->[key_name(recipient)]: [msg]") - if(recipient.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping)) + if(recipient.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) recipient << 'sound/effects/mentorhelp.mp3' for(var/client/C in GLOB.mentors) diff --git a/code/modules/mentor/mentorhelp.dm b/code/modules/mentor/mentorhelp.dm index 54c8009818..585f15b539 100644 --- a/code/modules/mentor/mentorhelp.dm +++ b/code/modules/mentor/mentorhelp.dm @@ -199,10 +199,10 @@ GLOBAL_DATUM_INIT(mhelp_tickets, /datum/mentor_help_tickets, new) var/chat_msg = "(ESCALATE) Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)]: [msg]" AddInteraction("[LinkedReplyName(ref_src)]: [msg]") for (var/client/C in GLOB.mentors) - if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping)) + if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) C << 'sound/effects/mentorhelp.mp3' for (var/client/C in GLOB.admins) - if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping)) + if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) C << 'sound/effects/mentorhelp.mp3' message_mentors(chat_msg) diff --git a/code/modules/mob/animations.dm b/code/modules/mob/animations.dm index 9e6efaed9c..3e1d2b5045 100644 --- a/code/modules/mob/animations.dm +++ b/code/modules/mob/animations.dm @@ -238,8 +238,7 @@ note dizziness decrements automatically in the mob's Life() proc. //Check for clients with pref enabled var/list/viewing = list() for(var/mob/M as anything in viewers(A)) - var/client/C = M.client - if(C && C.is_preference_enabled(/datum/client_preference/attack_icons)) + if(M.client?.prefs?.read_preference(/datum/preference/toggle/attack_icons)) viewing += M.client //Animals attacking each other in the distance, probably. Forgeddaboutit. diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index 6bf217e1ec..5d2f5dbfe6 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -11,7 +11,7 @@ to_chat(src, "You cannot send deadchat emotes (muted).") return - if(!is_preference_enabled(/datum/client_preference/show_dsay)) + if(!client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) to_chat(src, "You have deadchat muted.") return diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index f14f18a42e..528a01c6a6 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -66,7 +66,7 @@ if(!client && !teleop) return FALSE - if(isobserver(src) && is_preference_enabled(/datum/client_preference/ghost_ears)) + if(isobserver(src) && client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) if(speaker && !speaker.client && !(speaker in view(src))) //Does the speaker have a client? It's either random stuff that observers won't care about (Experiment 97B says, 'EHEHEHEHEHEHEHE') //Or someone snoring. So we make it where they won't hear it. @@ -108,7 +108,7 @@ if(speaker_name != speaker.real_name && speaker.real_name) speaker_name = "[speaker.real_name] ([speaker_name])" track = "([ghost_follow_link(speaker, src)]) " - if(is_preference_enabled(/datum/client_preference/ghost_ears) && (speaker in view(src))) + if(client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears) && (speaker in view(src))) message = "[message]" if(is_deaf() && stat != DEAD) //CHOMPEdit - Dead people should be able to hear stuff like ghosts can @@ -119,7 +119,7 @@ else var/message_to_send = null message_to_send = "[speaker_name][speaker.GetAltName()] [track][message]" - if(check_mentioned(multilingual_to_message(message_pieces)) && is_preference_enabled(/datum/client_preference/check_mention)) + if(check_mentioned(multilingual_to_message(message_pieces)) && client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) message_to_send = "[message_to_send]" on_hear_say(message_to_send, speaker) @@ -227,7 +227,7 @@ if(client.prefs.chat_timestamp) time = say_timestamp() var/final_message = "[part_b][speaker_name][part_c][formatted][part_d]" - if(check_mentioned(formatted) && is_preference_enabled(/datum/client_preference/check_mention)) + if(check_mentioned(formatted) && client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) final_message = "[time][part_a][final_message][part_e]" else final_message = "[time][part_a][final_message][part_e]" @@ -238,7 +238,7 @@ if(client.prefs.chat_timestamp) time = say_timestamp() var/final_message = "[part_b][track][part_c][formatted][part_d]" - if(check_mentioned(formatted) && is_preference_enabled(/datum/client_preference/check_mention)) + if(check_mentioned(formatted) && client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) final_message = "[time][part_a][final_message][part_e]" else final_message = "[time][part_a][final_message][part_e]" @@ -249,7 +249,7 @@ if(client.prefs.chat_timestamp) time = say_timestamp() var/final_message = "[part_b][speaker_name][part_c][formatted][part_d]" - if(check_mentioned(formatted) && is_preference_enabled(/datum/client_preference/check_mention)) + if(check_mentioned(formatted) && client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) final_message = "[time][part_a][final_message][part_e]" else final_message = "[time][part_a][final_message][part_e]" @@ -260,7 +260,7 @@ if(client.prefs.chat_timestamp) time = say_timestamp() var/final_message = "[part_b][track][part_c][formatted][part_d]" - if(check_mentioned(formatted) && is_preference_enabled(/datum/client_preference/check_mention)) + if(check_mentioned(formatted) && client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) final_message = "[time][part_a][final_message][part_e]" else final_message = "[time][part_a][final_message][part_e]" diff --git a/code/modules/mob/language/synthetic.dm b/code/modules/mob/language/synthetic.dm index 6a4ccd494d..0d301117bb 100644 --- a/code/modules/mob/language/synthetic.dm +++ b/code/modules/mob/language/synthetic.dm @@ -26,7 +26,7 @@ for (var/mob/M in dead_mob_list) if(!istype(M,/mob/new_player) && !istype(M,/mob/living/carbon/brain)) //No meta-evesdropping var/message_to_send = span_binary("[message_start] ([ghost_follow_link(speaker, M)]) [message_body]") - if(M.check_mentioned(message) && M.is_preference_enabled(/datum/client_preference/check_mention)) + if(M.check_mentioned(message) && M.client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) message_to_send = "[message_to_send]" M.show_message(message_to_send, 2) @@ -39,7 +39,7 @@ continue var/message_to_send = span_binary("[message_start] [message_body]") - if(S.check_mentioned(message) && S.is_preference_enabled(/datum/client_preference/check_mention)) + if(S.check_mentioned(message) && S.client?.prefs?.read_preference(/datum/preference/toggle/check_mention)) message_to_send = "[message_to_send]" S.show_message(message_to_send, 2) diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 37eb33c452..67433d6311 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -92,7 +92,7 @@ if(mind) // SSgame_master.adjust_danger(gibbed ? 40 : 20) // VOREStation Edit - We don't use SSgame_master yet. for(var/mob/observer/dead/O in mob_list) - if(O.client && O.client.is_preference_enabled(/datum/client_preference/show_dsay)) + if(O.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) to_chat(O, "[src] has died in [get_area(src)]. [ghost_follow_link(src, O)] ") /* // CHOMPEdit Start: Replacing this with our own death sounds. :3 diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 9538d6e04f..59e903b969 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1221,7 +1221,7 @@ if(noisy == TRUE && nutrition < 250 && prob(10)) //VOREStation edit for hunger noises. var/sound/growlsound = sound(get_sfx("hunger_sounds")) var/growlmultiplier = 100 - (nutrition / 250 * 100) - playsound(src, growlsound, vol = growlmultiplier, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/client_preference/digestion_noises) + playsound(src, growlsound, vol = growlmultiplier, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/preference/toggle/digestion_noises) // VOREStation Edit End //CHOMPEdit Begin if(nutrition > 500 && noisy_full == TRUE) @@ -1312,7 +1312,7 @@ if(fear) fear = (fear - 1) - if(fear >= 80 && is_preference_enabled(/datum/client_preference/play_ambiance)) + if(fear >= 80 && client?.prefs?.read_preference(/datum/preference/toggle/play_ambience)) if(last_fear_sound + 51 SECONDS <= world.time) src << sound('sound/effects/Heart Beat.ogg',0,0,0,25) last_fear_sound = world.time @@ -2070,7 +2070,7 @@ if(!H || (H.robotic >= ORGAN_ROBOT)) return - if(pulse >= PULSE_2FAST || shock_stage >= 10 || (istype(get_turf(src), /turf/space) && is_preference_enabled(/datum/client_preference/play_ambiance))) + if(pulse >= PULSE_2FAST || shock_stage >= 10 || (istype(get_turf(src), /turf/space) && read_preference(/datum/preference/toggle/play_ambience))) //PULSE_THREADY - maximum value for pulse, currently it 5. //High pulse value corresponds to a fast rate of heartbeat. //Divided by 2, otherwise it is too slow. diff --git a/code/modules/mob/living/inventory.dm b/code/modules/mob/living/inventory.dm index 4f56531065..132ae10072 100644 --- a/code/modules/mob/living/inventory.dm +++ b/code/modules/mob/living/inventory.dm @@ -60,7 +60,7 @@ item_dropped = r_hand . = drop_r_hand(Target) - if (istype(item_dropped) && !QDELETED(item_dropped) && is_preference_enabled(/datum/client_preference/drop_sounds)) + if (istype(item_dropped) && !QDELETED(item_dropped) && check_sound_preference(/datum/preference/toggle/drop_sounds)) addtimer(CALLBACK(src, PROC_REF(make_item_drop_sound), item_dropped), 1) /mob/proc/make_item_drop_sound(obj/item/I) @@ -68,7 +68,7 @@ return if(I.drop_sound) - playsound(I, I.drop_sound, 25, 0, preference = /datum/client_preference/drop_sounds) + playsound(I, I.drop_sound, 25, 0, preference = /datum/preference/toggle/drop_sounds) //Drops the item in our left hand diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index aac36198a1..0c5cf388b2 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -39,7 +39,7 @@ if(client) var/idle_limit = 10 MINUTES - if(client.inactivity >= idle_limit && !away_from_keyboard && src.is_preference_enabled(/datum/client_preference/auto_afk)) //if we're not already afk and we've been idle too long, and we have automarking enabled... then automark it + if(client.inactivity >= idle_limit && !away_from_keyboard && client.prefs?.read_preference(/datum/preference/toggle/auto_afk)) //if we're not already afk and we've been idle too long, and we have automarking enabled... then automark it add_status_indicator("afk") to_chat(src, "You have been idle for too long, and automatically marked as AFK.") away_from_keyboard = TRUE diff --git a/code/modules/mob/living/living_vr.dm b/code/modules/mob/living/living_vr.dm index 2a80b1557a..c001d1966a 100644 --- a/code/modules/mob/living/living_vr.dm +++ b/code/modules/mob/living/living_vr.dm @@ -98,7 +98,7 @@ return var/msg = ooc_notes //CHOMPEdit Start - if(ooc_notes_style && (ooc_notes_favs || ooc_notes_likes || ooc_notes_maybes || ooc_notes_dislikes) && usr.client.is_preference_enabled(/datum/client_preference/vchat_enable)) // Oldchat hates proper formatting + if(ooc_notes_style && (ooc_notes_favs || ooc_notes_likes || ooc_notes_maybes || ooc_notes_dislikes) && !user.client?.prefs?.read_preference(/datum/preference/toggle/vchat_enable)) // Oldchat hates proper formatting msg += "

" msg += "" if(ooc_notes_favs) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 3a939ae02b..cc2bfb0ce8 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -368,14 +368,14 @@ var/list/channel_to_radio_key = new if(M && src) //If we still exist, when the spawn processes //VOREStation Add - Ghosts don't hear whispers - if(whispering && isobserver(M) && (!M.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle) || \ - (!(is_preference_enabled(/datum/client_preference/whisubtle_vis) || (isbelly(M.loc) && src == M.loc:owner)) && !M.client?.holder))) // CHOMPedit + if(whispering && isobserver(M) && (!M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || \ + (!(client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || (isbelly(M.loc) && src == M.loc:owner)) && !M.client?.holder))) // CHOMPedit M.show_message("[src.name] [w_not_heard].", 2) return //VOREStation Add End var/dst = get_dist(get_turf(M),get_turf(src)) - var/runechat_enabled = M.client?.is_preference_enabled(/datum/client_preference/runechat_mob) + var/runechat_enabled = M.client?.prefs?.read_preference(/datum/preference/toggle/runechat_mob) if(dst <= message_range || (M.stat == DEAD && !forbid_seeing_deadchat)) //Inside normal message range, or dead with ears (handled in the view proc) if(M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol)) @@ -420,12 +420,12 @@ var/list/channel_to_radio_key = new message = "([message_mode == "headset" ? "Common" : capitalize(message_mode)]) [message]" //Adds radio keys used if available if(whispering) if(do_sound && message) - playsound(T, pick(voice_sounds_list), 25, TRUE, extrarange = -6, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/client_preference/say_sounds) //CHOMPEdit - Use say sound prefs + playsound(T, pick(voice_sounds_list), 25, TRUE, extrarange = -6, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/preference/toggle/whisper_sounds) //CHOMPEdit - Use say sound prefs log_whisper(message, src) else if(do_sound && message) - playsound(T, pick(voice_sounds_list), 75, TRUE, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/client_preference/say_sounds) //CHOMPEdit - tiny fix + playsound(T, pick(voice_sounds_list), 75, TRUE, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/preference/toggle/say_sounds) //CHOMPEdit - tiny fix log_say(message, src) return 1 diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm index 81f8755178..0c0ec96348 100644 --- a/code/modules/mob/living/silicon/pai/pai_vr.dm +++ b/code/modules/mob/living/silicon/pai/pai_vr.dm @@ -546,9 +546,9 @@ for (var/mob/G in player_list) if (istype(G, /mob/new_player)) continue - else if(isobserver(G) && G.is_preference_enabled(/datum/client_preference/ghost_ears)) - if((is_preference_enabled(/datum/client_preference/whisubtle_vis) || G.client.holder) && \ - G.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle)) + else if(isobserver(G) && G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) + if((client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || G.client.holder) && \ + G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle)) to_chat(G, "[src.name]'s screen prints, \"[message]\"") /mob/living/silicon/pai/proc/touch_window(soft_name) //This lets us touch TGUI procs and windows that may be nested behind other TGUI procs and windows diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm index dc14e60105..d9489f48c8 100644 --- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm +++ b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm @@ -95,7 +95,7 @@ if(do_after(user, 30, target) && length(contents) < max_item_count) target.forceMove(src) user.visible_message("[hound.name]'s [src.name] groans lightly as [target.name] slips inside.", "Your [src.name] groans lightly as [target] slips inside.") - playsound(src, gulpsound, vol = 60, vary = 1, falloff = 0.1, preference = /datum/client_preference/eating_noises) + playsound(src, gulpsound, vol = 60, vary = 1, falloff = 0.1, preference = /datum/preference/toggle/eating_noises) if(analyzer && istype(target,/obj/item)) var/obj/item/tech_item = target var/list/tech_levels = list() @@ -115,7 +115,7 @@ trashmouse.forceMove(src) trashmouse.reset_view(src) user.visible_message("[hound.name]'s [src.name] groans lightly as [trashmouse] slips inside.", "Your [src.name] groans lightly as [trashmouse] slips inside.") - playsound(src, gulpsound, vol = 60, vary = 1, falloff = 0.1, preference = /datum/client_preference/eating_noises) + playsound(src, gulpsound, vol = 60, vary = 1, falloff = 0.1, preference = /datum/preference/toggle/eating_noises) if(delivery) if(islist(deliverylists[delivery_tag])) deliverylists[delivery_tag] |= trashmouse @@ -137,7 +137,7 @@ START_PROCESSING(SSobj, src) user.visible_message("[hound.name]'s [src.name] groans lightly as [trashman] slips inside.", "Your [src.name] groans lightly as [trashman] slips inside.") log_attack("[key_name(hound)] has eaten [key_name(patient)] with a cyborg belly. ([hound ? "JMP" : "null"])") - playsound(src, gulpsound, vol = 100, vary = 1, falloff = 0.1, preference = /datum/client_preference/eating_noises) + playsound(src, gulpsound, vol = 100, vary = 1, falloff = 0.1, preference = /datum/preference/toggle/eating_noises) if(delivery) if(islist(deliverylists[delivery_tag])) deliverylists[delivery_tag] |= trashman @@ -168,7 +168,7 @@ START_PROCESSING(SSobj, src) user.visible_message("[hound.name]'s [src.name] lights up as [H.name] slips inside.", "Your [src] lights up as [H] slips inside. Life support functions engaged.") log_admin("[key_name(hound)] has eaten [key_name(patient)] with a cyborg belly. ([hound ? "JMP" : "null"])") - playsound(src, gulpsound, vol = 100, vary = 1, falloff = 0.1, preference = /datum/client_preference/eating_noises) + playsound(src, gulpsound, vol = 100, vary = 1, falloff = 0.1, preference = /datum/preference/toggle/eating_noises) /obj/item/device/dogborg/sleeper/proc/ingest_atom(var/atom/ingesting) if (!ingesting || ingesting == hound) @@ -558,11 +558,11 @@ 'sound/vore/death8.ogg', 'sound/vore/death9.ogg', 'sound/vore/death10.ogg') - playsound(src, finisher, vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/client_preference/digestion_noises) + playsound(src, finisher, vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/preference/toggle/digestion_noises) to_chat(hound, "Your [src.name] is now clean. Ending self-cleaning cycle.") cleaning = 0 update_patient() - playsound(src, 'sound/machines/ding.ogg', vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/client_preference/digestion_noises) + playsound(src, 'sound/machines/ding.ogg', vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/preference/toggle/digestion_noises) return if(prob(20)) @@ -579,7 +579,7 @@ 'sound/vore/digest10.ogg', 'sound/vore/digest11.ogg', 'sound/vore/digest12.ogg') - playsound(src, churnsound, vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/client_preference/digestion_noises) + playsound(src, churnsound, vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/preference/toggle/digestion_noises) //If the timing is right, and there are items to be touched if(air_master.current_cycle%3==1 && length(touchable_items)) @@ -618,7 +618,7 @@ 'sound/vore/death8.ogg', 'sound/vore/death9.ogg', 'sound/vore/death10.ogg') - playsound(src, deathsound, vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/client_preference/digestion_noises) + playsound(src, deathsound, vol = 100, vary = 1, falloff = 0.1, ignore_walls = TRUE, preference = /datum/preference/toggle/digestion_noises) if(is_vore_predator(T)) for(var/obj/belly/B as anything in T.vore_organs) for(var/atom/movable/thing in B) diff --git a/code/modules/mob/living/silicon/robot/drone/drone_say.dm b/code/modules/mob/living/silicon/robot/drone/drone_say.dm index 5e8dd6dc74..d35e87fff6 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_say.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_say.dm @@ -32,8 +32,8 @@ for (var/mob/M in player_list) if (istype(M, /mob/new_player)) continue - else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + else if(M.stat == DEAD && M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) if(M.client) to_chat(M, "[src] transmits, \"[message]\"") return 1 - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm index 1ad860e839..6c5118e8c7 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm @@ -500,14 +500,14 @@ for(var/mob/M as anything in vis_mobs) if(isnewplayer(M)) continue - if(isobserver(M) && (!M.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle) || \ - !L.is_preference_enabled(/datum/client_preference/whisubtle_vis) && !M.client?.holder)) + if(isobserver(M) && (!M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || \ + !L.client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) && !M.client?.holder)) spawn(0) M.show_message(undisplayed_message, 2) else spawn(0) M.show_message(message, 2) - if(M.is_preference_enabled(/datum/client_preference/subtle_sounds)) + if(M.read_preference(/datum/preference/toggle/subtle_sounds)) M << sound('sound/talksounds/subtle_sound.ogg', volume = 50) /decl/flooring/fur @@ -930,7 +930,7 @@ spawnstuff = FALSE /area/redgate/stardog/flesh_abyss/play_ambience(var/mob/living/L, initial = TRUE) - if(!L.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(!L.check_sound_preference(/datum/preference/toggle/digestion_noises)) return ..() @@ -1172,14 +1172,14 @@ for(var/mob/M as anything in vis_mobs) if(isnewplayer(M)) continue - if(isobserver(M) && (!M.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle) || \ - !L.is_preference_enabled(/datum/client_preference/whisubtle_vis) && !M.client?.holder)) + if(isobserver(M) && (!M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || \ + !L.client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) && !M.client?.holder)) spawn(0) M.show_message(undisplayed_message, 2) else spawn(0) M.show_message(message, 2) - if(M.is_preference_enabled(/datum/client_preference/subtle_sounds)) + if(M.read_preference(/datum/preference/toggle/subtle_sounds)) M << sound('sound/talksounds/subtle_sound.ogg', volume = 50) /area/redgate/stardog/eyes @@ -1335,8 +1335,8 @@ var/go = FALSE if(isobserver(AM)) return - playsound(src, teleport_sound, vol = 100, vary = 1, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) - playsound(target, teleport_sound, vol = 100, vary = 1, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) + playsound(src, teleport_sound, vol = 100, vary = 1, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) + playsound(target, teleport_sound, vol = 100, vary = 1, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) if(isliving(AM)) var/mob/living/L = AM if(teleport_message && L.client) @@ -1374,7 +1374,7 @@ var/mob/living/simple_mob/vore/overmap/stardog/dog = s.parent dog.adjust_nutrition(I.reagents.total_volume) dog.adjust_affinity(25) - playsound(src, teleport_sound, vol = 100, vary = 1, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) + playsound(src, teleport_sound, vol = 100, vary = 1, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) visible_message("The dog gobbles up \the [I]!") if(dog.client) to_chat(dog, "[I.thrower ? "\The [I.thrower]" : "Someone"] feeds \the [I] to you!") @@ -1699,7 +1699,7 @@ /obj/structure/auto_flesh_door/proc/Open() isSwitchingStates = 1 var/oursound = pick(open_sounds) - playsound(src, oursound, 100, 1, preference = /datum/client_preference/digestion_noises , volume_channel = VOLUME_CHANNEL_VORE) + playsound(src, oursound, 100, 1, preference = /datum/preference/toggle/digestion_noises , volume_channel = VOLUME_CHANNEL_VORE) flick("flesh-opening",src) sleep(8) density = FALSE @@ -1715,7 +1715,7 @@ /obj/structure/auto_flesh_door/proc/Close() isSwitchingStates = 1 var/oursound = pick(open_sounds) - playsound(src, oursound, 100, 1, preference = /datum/client_preference/digestion_noises , volume_channel = VOLUME_CHANNEL_VORE) + playsound(src, oursound, 100, 1, preference = /datum/preference/toggle/digestion_noises , volume_channel = VOLUME_CHANNEL_VORE) flick("flesh-closing",src) sleep(8) density = TRUE diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm index 70b8750301..14ba10397c 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm @@ -268,7 +268,7 @@ for(var/mob/M in player_list) if(istype(M, /mob/new_player)) continue - else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + else if(M.stat == DEAD && M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) to_chat(M, "[src.true_name] whispers to [host], \"[message]\"") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm index 81207c7fb9..904efb8bb2 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm @@ -28,7 +28,7 @@ for (var/mob/M in player_list) if (istype(M, /mob/new_player)) continue - else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + else if(M.stat == DEAD && M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears)) to_chat(M, "The captive mind of [src] whispers, \"[message]\"") /mob/living/captive_brain/me_verb(message as text) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index abfbc7343e..e80a0b01c5 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -69,12 +69,12 @@ recalculate_vis() // AO support - var/ao_enabled = client.is_preference_enabled(/datum/client_preference/ambient_occlusion) + var/ao_enabled = client.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion) plane_holder.set_ao(VIS_OBJS, ao_enabled) plane_holder.set_ao(VIS_MOBS, ao_enabled) // Status indicators - var/status_enabled = client.is_preference_enabled(/datum/client_preference/status_indicators) + var/status_enabled = client.prefs?.read_preference(/datum/preference/toggle/status_indicators) plane_holder.set_vis(VIS_STATUS, status_enabled) //set macro to normal incase it was overriden (like cyborg currently does) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 181786ef77..5cbaabe59e 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1328,20 +1328,18 @@ ChompEdit removal end*/ return TRUE /mob/MouseEntered(location, control, params) - if(usr != src && usr.is_preference_enabled(/datum/client_preference/mob_tooltips) && src.will_show_tooltip()) - openToolTip(user = usr, tip_src = src, params = params, title = get_nametag_name(usr), content = get_nametag_desc(usr)) - - ..() + if(usr != src && will_show_tooltip()) + if(usr?.read_preference(/datum/preference/toggle/mob_tooltips)) + openToolTip(usr, src, params, title = get_nametag_name(usr), content = get_nametag_desc(usr)) + . = ..() /mob/MouseDown() closeToolTip(usr) //No reason not to, really - - ..() + . = ..() /mob/MouseExited() closeToolTip(usr) //No reason not to, really - - ..() + . = ..() // Manages a global list of mobs with clients attached, indexed by z-level. /mob/proc/update_client_z(new_z) // +1 to register, null to unregister. diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 4f797a087b..8698570fa7 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -411,7 +411,7 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT) return // Can't talk in deadchat if you can't see it. for(var/mob/M in player_list) - if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.is_preference_enabled(/datum/client_preference/holder/show_staff_dsay))) && M.is_preference_enabled(/datum/client_preference/show_dsay)) + if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.client?.prefs?.read_preference(/datum/preference/toggle/holder/show_staff_dsay))) && M.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) var/follow var/lname if(M.forbid_seeing_deadchat && !M.client.holder) @@ -441,7 +441,7 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT) /proc/say_dead_object(var/message, var/obj/subject = null) for(var/mob/M in player_list) - if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.is_preference_enabled(/datum/client_preference/holder/show_staff_dsay))) && M.is_preference_enabled(/datum/client_preference/show_dsay)) + if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.client?.prefs?.read_preference(/datum/preference/toggle/holder/show_staff_dsay))) && M.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) var/follow var/lname = "Game Master" if(M.forbid_seeing_deadchat && !M.client.holder) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index d4d0c6c393..5784c00ad5 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -117,7 +117,7 @@ if (client.prefs.lastlorenews == GLOB.news_data.newsindex) client.seen_news = 1 - if(GLOB.news_data.station_newspaper && !client.seen_news && client.is_preference_enabled(/datum/client_preference/show_lore_news)) + if(GLOB.news_data.station_newspaper && !client.seen_news && client.prefs?.read_preference(/datum/preference/toggle/show_lore_news)) show_latest_news(GLOB.news_data.station_newspaper) client.prefs.lastlorenews = GLOB.news_data.newsindex SScharacter_setup.queue_preferences_save(client.prefs) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index acaaaae972..dc79904f92 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -74,7 +74,7 @@ to_chat(src, "Deadchat is globally muted.") return - if(!is_preference_enabled(/datum/client_preference/show_dsay)) + if(!client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) to_chat(usr, "You have deadchat muted.") return diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm index 914ecf8eef..7113657541 100644 --- a/code/modules/mob/say_vr.dm +++ b/code/modules/mob/say_vr.dm @@ -203,14 +203,14 @@ continue if(src.client && M && !(get_z(src) == get_z(M))) message = "[message]" - if(isobserver(M) && (!(is_preference_enabled(/datum/client_preference/whisubtle_vis) || (isbelly(M.loc) && src == M.loc:owner)) || \ - !is_preference_enabled(/datum/client_preference/whisubtle_vis) && !M.client?.holder)) //CHOMPEdit - Added the belly check so that ghosts in bellies can still see their pred's messages. + if(isobserver(M) && (!(M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || (isbelly(M.loc) && src == M.loc:owner)) || \ + !client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) && !M.client?.holder)) //CHOMPEdit - Added the belly check so that ghosts in bellies can still see their pred's messages. spawn(0) M.show_message(undisplayed_message, 2) else spawn(0) M.show_message(message, 2) - if(M.Adjacent(src) && M.is_preference_enabled(/datum/client_preference/say_sounds)) //CHOMPEdit - makes it so the sounds only play for ghosts when adjacent to the person making them + if(M.Adjacent(src) && M.read_preference(/datum/preference/toggle/subtle_sounds)) //CHOMPEdit - makes it so the sounds only play for ghosts when adjacent to the person making them if(voice_sounds_list) //CHOMPEdit, changes to subtle emotes to use mob voice instead M << sound(pick(voice_sounds_list), volume = 25) @@ -292,14 +292,14 @@ else pb = db.pred_body to_chat(pb, "The captive mind of \the [M] thinks, \"[message]\"") //To our pred if dominated brain - if(pb.is_preference_enabled(/datum/client_preference/say_sounds)) + if(pb.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead pb << sound(pick(voice_sounds_list), volume = 25) f = TRUE else if(M.absorbed && isbelly(M.loc)) pb = M.loc.loc to_chat(pb, "\The [M] thinks, \"[message]\"") //To our pred if absorbed - if(pb.is_preference_enabled(/datum/client_preference/say_sounds)) + if(pb.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead pb << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -310,7 +310,7 @@ if(istype(I, /mob/living/dominated_brain) && I != M) var/mob/living/dominated_brain/db = I to_chat(db, "The captive mind of \the [M] thinks, \"[message]\"") //To any dominated brains in the pred - if(db.is_preference_enabled(/datum/client_preference/say_sounds)) + if(db.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead db << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -318,7 +318,7 @@ for(var/mob/living/L in B) if(L.absorbed && L != M && L.ckey) to_chat(L, "\The [M] thinks, \"[message]\"") //To any absorbed people in the pred - if(L.is_preference_enabled(/datum/client_preference/say_sounds)) + if(L.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead L << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -328,7 +328,7 @@ if(istype(I, /mob/living/dominated_brain)) var/mob/living/dominated_brain/db = I to_chat(db, "\The [M] thinks, \"[message]\"") //To any dominated brains inside us - if(db.is_preference_enabled(/datum/client_preference/say_sounds)) + if(db.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead db << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -336,7 +336,7 @@ for(var/mob/living/L in B) if(L.absorbed) to_chat(L, "\The [M] thinks, \"[message]\"") //To any absorbed people inside us - if(L.is_preference_enabled(/datum/client_preference/say_sounds)) + if(L.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead L << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -344,20 +344,20 @@ if(f) //We found someone to send the message to if(pb) to_chat(M, "You think \"[message]\"") //To us if we are the prey - if(M.is_preference_enabled(/datum/client_preference/say_sounds)) + if(M.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead M << sound(pick(voice_sounds_list), volume = 25) else to_chat(M, "You think \"[message]\"") //To us if we are the pred - if(M.is_preference_enabled(/datum/client_preference/say_sounds)) + if(M.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead M << sound(pick(voice_sounds_list), volume = 25) for (var/mob/G in player_list) if (istype(G, /mob/new_player)) continue - else if(isobserver(G) && G.is_preference_enabled(/datum/client_preference/ghost_ears) && \ - G.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle)) - if(is_preference_enabled(/datum/client_preference/whisubtle_vis) || G.client.holder) + else if(isobserver(G) && G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears) && \ + G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle)) + if(client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || G.client.holder) to_chat(G, "\The [M] thinks, \"[message]\"") log_say(message,M) else //There wasn't anyone to send the message to, pred or prey, so let's just say it instead and correct our psay just in case. @@ -397,7 +397,7 @@ else pb = db.pred_body to_chat(pb, "\The [M] [message]") //To our pred if dominated brain - if(pb.is_preference_enabled(/datum/client_preference/say_sounds)) + if(pb.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead pb << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -405,7 +405,7 @@ else if(M.absorbed && isbelly(M.loc)) pb = M.loc.loc to_chat(pb, "\The [M] [message]") //To our pred if absorbed - if(pb.is_preference_enabled(/datum/client_preference/say_sounds)) + if(pb.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead pb << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -416,7 +416,7 @@ if(istype(I, /mob/living/dominated_brain) && I != M) var/mob/living/dominated_brain/db = I to_chat(db, "\The [M] [message]") //To any dominated brains in the pred - if(pb.is_preference_enabled(/datum/client_preference/say_sounds)) + if(db.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead pb << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -424,7 +424,7 @@ for(var/mob/living/L in B) if(L.absorbed && L != M && L.ckey) to_chat(L, "\The [M] [message]") //To any absorbed people in the pred - if(L.is_preference_enabled(/datum/client_preference/say_sounds)) + if(L.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead L << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -434,7 +434,7 @@ if(istype(I, /mob/living/dominated_brain)) var/mob/living/dominated_brain/db = I to_chat(db, "\The [M] [message]") //To any dominated brains inside us - if(db.is_preference_enabled(/datum/client_preference/say_sounds)) + if(db.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead db << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -442,7 +442,7 @@ for(var/mob/living/L in B) if(L.absorbed) to_chat(L, "\The [M] [message]") //To any absorbed people inside us - if(L.is_preference_enabled(/datum/client_preference/say_sounds)) + if(L.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead L << sound(pick(voice_sounds_list), volume = 25) f = TRUE @@ -450,20 +450,20 @@ if(f) //We found someone to send the message to if(pb) to_chat(M, "\The [M] [message]") //To us if we are the prey - if(M.is_preference_enabled(/datum/client_preference/say_sounds)) + if(M.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead M << sound(pick(voice_sounds_list), volume = 25) else to_chat(M, "\The [M] [message]") //To us if we are the pred - if(M.is_preference_enabled(/datum/client_preference/say_sounds)) + if(M.read_preference(/datum/preference/toggle/subtle_sounds)) if(voice_sounds_list) //CHOMPEdit, changes subtle emote sound to use mob voice instead M << sound(pick(voice_sounds_list), volume = 25) for (var/mob/G in player_list) if (istype(G, /mob/new_player)) continue - else if(isobserver(G) && G.is_preference_enabled(/datum/client_preference/ghost_ears && \ - G.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle))) - if(is_preference_enabled(/datum/client_preference/whisubtle_vis) || G.client.holder) + else if(isobserver(G) && G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears) && \ + G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle)) + if(client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || G.client.holder) to_chat(G, "\The [M] [message]") log_say(message,M) else //There wasn't anyone to send the message to, pred or prey, so let's just emote it instead and correct our psay just in case. @@ -501,7 +501,7 @@ ourfreq = voice_freq if(client) - playsound(T, pick(emote_sound), 25, TRUE, falloff = 1 , is_global = TRUE, frequency = ourfreq, ignore_walls = TRUE, preference = /datum/client_preference/emote_sounds) //ChompEDIT - ignore walls + playsound(T, pick(emote_sound), 25, TRUE, falloff = 1 , is_global = TRUE, frequency = ourfreq, ignore_walls = TRUE, preference = /datum/preference/toggle/emote_sounds) //ChompEDIT - ignore walls var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,2,remote_ghosts = client ? TRUE : FALSE) var/list/m_viewers = in_range["mobs"] diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm index f79506eaf8..93310c5beb 100644 --- a/code/modules/mob/typing_indicator.dm +++ b/code/modules/mob/typing_indicator.dm @@ -15,7 +15,7 @@ set name = "Say verb" set category = "IC.TGUI Say" //CHOMPEdit - if(is_preference_enabled(/datum/client_preference/tgui_say)) + if(client?.prefs?.read_preference(/datum/preference/toggle/tgui_say)) winset(src, null, "command=[client.tgui_say_create_open_command(SAY_CHANNEL)]") return @@ -31,7 +31,7 @@ set name = "Me verb" set category = "IC.TGUI Say" //CHOMPEdit - if(is_preference_enabled(/datum/client_preference/tgui_say)) + if(client?.prefs?.read_preference(/datum/preference/toggle/tgui_say)) winset(src, null, "command=[client.tgui_say_create_open_command(ME_CHANNEL)]") return @@ -47,11 +47,11 @@ set name = "Whisper verb" set category = "IC.TGUI Say" //CHOMPEdit - if(is_preference_enabled(/datum/client_preference/tgui_say)) + if(client?.prefs?.read_preference(/datum/preference/toggle/tgui_say)) winset(src, null, "command=[client.tgui_say_create_open_command(WHIS_CHANNEL)]") return - if(is_preference_enabled(/datum/client_preference/show_typing_indicator_subtle)) + if(client?.prefs?.read_preference(/datum/preference/toggle/show_typing_indicator_subtle)) client?.start_thinking() client?.start_typing() var/message = tgui_input_text(usr, "Type your message:", "Whisper") @@ -65,11 +65,11 @@ set category = "IC.TGUI Say" //CHOMPEdit set desc = "Emote to nearby people (and your pred/prey)" - if(is_preference_enabled(/datum/client_preference/tgui_say)) + if(client?.prefs?.read_preference(/datum/preference/toggle/tgui_say)) winset(src, null, "command=[client.tgui_say_create_open_command(SUBTLE_CHANNEL)]") return - if(is_preference_enabled(/datum/client_preference/show_typing_indicator_subtle)) + if(client?.prefs?.read_preference(/datum/preference/toggle/show_typing_indicator_subtle)) client?.start_thinking() client?.start_typing() var/message = tgui_input_text(usr, "Type your message:", "Subtle", multiline = TRUE) diff --git a/code/modules/organs/pain.dm b/code/modules/organs/pain.dm index 5348b69f06..7376150af1 100644 --- a/code/modules/organs/pain.dm +++ b/code/modules/organs/pain.dm @@ -19,7 +19,7 @@ // Anti message spam checks // If multiple limbs are injured, cooldown is ignored to print all injuries until all limbs are iterated over - if(src.is_preference_enabled(/datum/client_preference/pain_frequency)) + if(client?.prefs?.read_preference(/datum/preference/toggle/pain_frequency)) switch(power) if(0 to 5) force = 0 diff --git a/code/modules/player_tips_vr/player_tips_controller_vr.dm b/code/modules/player_tips_vr/player_tips_controller_vr.dm index 90940b0b49..12cc14f0fe 100644 --- a/code/modules/player_tips_vr/player_tips_controller_vr.dm +++ b/code/modules/player_tips_vr/player_tips_controller_vr.dm @@ -27,7 +27,7 @@ Controlled by the player_tips subsystem under code/controllers/subsystems/player break last_tip = tip for(var/mob/M in player_list) - if(M.is_preference_enabled(/datum/client_preference/player_tips)) + if(M.client?.prefs?.read_preference(/datum/preference/toggle/player_tips)) if(!M.key && !(M.key in HasReceived)) to_chat(M, SPAN_WARNING("You have periodic player tips enabled. You may turn them off at any time with the Toggle Receiving Player Tips verb in Preferences, or in character set up under the OOC tab!\n Player tips appear every 45-75 minutes.")) HasReceived.Add(M.key) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 37a873b5b2..38ce8a59e4 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -230,7 +230,7 @@ PreFire(A,user,params) //They're using the new gun system, locate what they're aiming at. return - if(user && user.a_intent == I_HELP && user.is_preference_enabled(/datum/client_preference/safefiring)) //regardless of what happens, refuse to shoot if help intent is on + if(user && user.a_intent == I_HELP && user.client?.prefs?.read_preference(/datum/preference/toggle/safefiring)) //regardless of what happens, refuse to shoot if help intent is on to_chat(user, "You refrain from firing your [src] as your intent is set to help.") return diff --git a/code/modules/projectiles/guns/projectile_ch.dm b/code/modules/projectiles/guns/projectile_ch.dm index e419213057..d29a69f27a 100644 --- a/code/modules/projectiles/guns/projectile_ch.dm +++ b/code/modules/projectiles/guns/projectile_ch.dm @@ -410,7 +410,7 @@ PreFire(A,user,params) //They're using the new gun system, locate what they're aiming at. return - if(user && user.a_intent == I_HELP && user.is_preference_enabled(/datum/client_preference/safefiring)) //regardless of what happens, refuse to shoot if help intent is on + if(user && user.a_intent == I_HELP && user.read_preference(/datum/preference/toggle/safefiring)) //regardless of what happens, refuse to shoot if help intent is on to_chat(user, "You refrain from firing your [src] as your intent is set to help.") return diff --git a/code/modules/projectiles/targeting/targeting_triggers.dm b/code/modules/projectiles/targeting/targeting_triggers.dm index 1b8f3fcadd..d327ba8e8f 100644 --- a/code/modules/projectiles/targeting/targeting_triggers.dm +++ b/code/modules/projectiles/targeting/targeting_triggers.dm @@ -20,7 +20,7 @@ if(!owner.checkClickCooldown()) return owner.setClickCooldown(5) // Spam prevention, essentially. - if(owner.a_intent == I_HELP && owner.is_preference_enabled(/datum/client_preference/safefiring)) + if(owner.a_intent == I_HELP && owner.client?.prefs?.read_preference(/datum/preference/toggle/safefiring)) to_chat(owner, "You refrain from firing \the [aiming_with] as your intent is set to help.") return owner.visible_message("\The [owner] pulls the trigger reflexively!") diff --git a/code/modules/resleeving/infocore_records.dm b/code/modules/resleeving/infocore_records.dm index 24ac6ecfd1..841e54069a 100644 --- a/code/modules/resleeving/infocore_records.dm +++ b/code/modules/resleeving/infocore_records.dm @@ -67,7 +67,7 @@ nif_savedata = M.nif.save_data.Copy() //CHOMPEdit Start - Preference for Automatic transcore notifications - if(istype(M,/mob) && !M.is_preference_enabled(/datum/client_preference/autotranscore)) + if(istype(M,/mob) && !M.read_preference(/datum/preference/toggle/autotranscore)) do_notify = FALSE //CHOMPEdit End diff --git a/code/modules/tables/interactions.dm b/code/modules/tables/interactions.dm index d1aae5fc71..711373cfe3 100644 --- a/code/modules/tables/interactions.dm +++ b/code/modules/tables/interactions.dm @@ -155,7 +155,7 @@ return // Placing stuff on tables - if(user.unEquip(W, 0, src.loc) && user.is_preference_enabled(/datum/client_preference/precision_placement)) + if(user.unEquip(W, 0, src.loc) && user.client?.prefs?.read_preference(/datum/preference/toggle/precision_placement)) auto_align(W, click_parameters) return 1 diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm index 725f4321bf..84e9906499 100644 --- a/code/modules/tgui_input/say_modal/modal.dm +++ b/code/modules/tgui_input/say_modal/modal.dm @@ -67,7 +67,7 @@ winset(client, "tgui_say", "pos=410,400;size=360,30;is-visible=0;") window.send_message("props", list( - lightMode = client.is_preference_enabled(/datum/client_preference/tgui_say_light), + lightMode = client?.prefs?.read_preference(/datum/preference/toggle/tgui_say_light), maxLength = max_length, )) diff --git a/code/modules/vore/chat_healthbars.dm b/code/modules/vore/chat_healthbars.dm index 6e9a69e5b2..fb3cbc0b75 100644 --- a/code/modules/vore/chat_healthbars.dm +++ b/code/modules/vore/chat_healthbars.dm @@ -6,7 +6,7 @@ if(!reciever.client) //No one is home, don't bother return if(!override) //Did the person push the verb? Ignore the pref - if(!reciever.client.is_preference_enabled(/datum/client_preference/vore_health_bars)) + if(!reciever.client.prefs?.read_preference(/datum/preference/toggle/vore_health_bars)) return if(!istype(src.loc, /obj/belly)) // not in a belly? don't bother return diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm index f93c1fb087..4543d9bedc 100644 --- a/code/modules/vore/eating/belly_obj_vr.dm +++ b/code/modules/vore/eating/belly_obj_vr.dm @@ -569,7 +569,7 @@ // var/turf/simulated/T = owner.loc // CHOMPEdit var/S = pick(GLOB.slosh) //ChompEDIT if(S) - playsound(owner.loc, S, sound_volume * (reagents.total_volume / 100), FALSE, frequency = noise_freq, preference = /datum/client_preference/digestion_noises) //CHOMPEdit + playsound(owner.loc, S, sound_volume * (reagents.total_volume / 100), FALSE, frequency = noise_freq, preference = /datum/preference/toggle/digestion_noises) //CHOMPEdit cycle_sloshed = TRUE thing.belly_cycles = 0 //reset cycle count if(istype(thing, /mob/observer)) //Ports CHOMPStation PR#3072 @@ -602,7 +602,7 @@ if(special_entrance_sound) //CHOMPEdit: Custom sound set by mob's init_vore or ingame varedits. soundfile = special_entrance_sound if(soundfile) - playsound(src, soundfile, vol = sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, soundfile, vol = sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit recent_sound = TRUE if(reagents.total_volume >= 5 && !isliving(thing) && (item_digest_mode == IM_DIGEST || item_digest_mode == IM_DIGEST_PARALLEL)) //CHOMPAdd @@ -1118,7 +1118,7 @@ else soundfile = fancy_release_sounds[release_sound] if(soundfile) - playsound(src, soundfile, vol = sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, soundfile, vol = sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit return count @@ -1206,7 +1206,7 @@ else soundfile = fancy_release_sounds[release_sound] if(soundfile) - playsound(src, soundfile, vol = sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, soundfile, vol = sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit //Should fix your view not following you out of mobs sometimes! if(ismob(M)) var/mob/ourmob = M @@ -1986,9 +1986,9 @@ struggle_snuggle = sound(get_sfx("classic_struggle_sounds")) else struggle_snuggle = sound(get_sfx("fancy_prey_struggle")) - playsound(src, struggle_snuggle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, struggle_snuggle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit else - playsound(src, struggle_rustle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, struggle_rustle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit //CHOMPEdit End if(escapable) //If the stomach has escapable enabled. @@ -2279,9 +2279,9 @@ struggle_snuggle = sound(get_sfx("classic_struggle_sounds")) else struggle_snuggle = sound(get_sfx("fancy_prey_struggle")) - playsound(src, struggle_snuggle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, struggle_snuggle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit else - playsound(src, struggle_rustle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, struggle_rustle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit //CHOMPEdit End //absorb resists diff --git a/code/modules/vore/eating/bellymodes_datum_vr.dm b/code/modules/vore/eating/bellymodes_datum_vr.dm index a859f05780..4be67ac8f2 100644 --- a/code/modules/vore/eating/bellymodes_datum_vr.dm +++ b/code/modules/vore/eating/bellymodes_datum_vr.dm @@ -30,7 +30,7 @@ GLOBAL_LIST_INIT(digest_modes, list()) //Person just died in guts! if(L.stat == DEAD) if(!L.digestion_in_progress) //CHOMPEdit Start - if(L.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(L.check_sound_preference(/datum/preference/toggle/digestion_noises)) if(!B.fancy_vore) SEND_SOUND(L, sound(get_sfx("classic_death_sounds"))) else diff --git a/code/modules/vore/eating/bellymodes_vr.dm b/code/modules/vore/eating/bellymodes_vr.dm index 395a54383f..6ed9674199 100644 --- a/code/modules/vore/eating/bellymodes_vr.dm +++ b/code/modules/vore/eating/bellymodes_vr.dm @@ -91,7 +91,7 @@ /////////////////////////// Make any noise /////////////////////////// if(digestion_noise_chance && prob(digestion_noise_chance)) for(var/mob/M in contents) - if(M && M.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(M && M.check_sound_preference(/datum/preference/toggle/digestion_noises)) SEND_SOUND(M, prey_digest) play_sound = pred_digest @@ -100,7 +100,7 @@ updateVRPanels() if(play_sound) for(var/mob/M in hearers(VORE_SOUND_RANGE, get_turf(owner))) //so we don't fill the whole room with the sound effect - if(!M.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(!M.check_sound_preference(/datum/preference/toggle/digestion_noises)) continue if(isturf(M.loc) || (M.loc != src)) //to avoid people on the inside getting the outside sounds and their direct sounds + built in sound pref check if(fancy_vore) @@ -127,7 +127,7 @@ if(play_sound) for(var/mob/M in hearers(VORE_SOUND_RANGE, get_turf(owner))) //so we don't fill the whole room with the sound effect - if(!M.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(!M.check_sound_preference(/datum/preference/toggle/digestion_noises)) continue if(isturf(M.loc) || (M.loc != src)) //to avoid people on the inside getting the outside sounds and their direct sounds + built in sound pref check if(fancy_vore) @@ -271,7 +271,7 @@ /obj/belly/proc/prey_loop() for(var/mob/living/M in belly_surrounding) //CHOMPEdit - contents changed to belly_surrounding to loop sound for indirect viewers too //We don't bother executing any other code if the prey doesn't want to hear the noises. - if(!M.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(!M.check_sound_preference(/datum/preference/toggle/digestion_noises)) M.stop_sound_channel(CHANNEL_PREYLOOP) // sanity just in case, because byond is whack and you can't trust it continue diff --git a/code/modules/vore/eating/digest_act_vr.dm b/code/modules/vore/eating/digest_act_vr.dm index d294f63afe..47e26ee463 100644 --- a/code/modules/vore/eating/digest_act_vr.dm +++ b/code/modules/vore/eating/digest_act_vr.dm @@ -111,7 +111,7 @@ soundfile = pick('sound/vore/shortgurgles/gurgle_M1.ogg', 'sound/vore/shortgurgles/gurgle_M2.ogg', 'sound/vore/shortgurgles/gurgle_M3.ogg') else soundfile = pick('sound/vore/shortgurgles/gurgle_S1.ogg', 'sound/vore/shortgurgles/gurgle_S2.ogg', 'sound/vore/shortgurgles/gurgle_S3.ogg') - playsound(src, soundfile, vol = g_sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit + playsound(src, soundfile, vol = g_sound_volume, vary = 1, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) //CHOMPEdit //CHOMPEdit Start - Allow those turned into items to become the recycled item var/recycled = B.recycle(src) if(!recycled) diff --git a/code/modules/vore/eating/living_ch.dm b/code/modules/vore/eating/living_ch.dm index eaeb7315a3..2d2861a990 100644 --- a/code/modules/vore/eating/living_ch.dm +++ b/code/modules/vore/eating/living_ch.dm @@ -286,7 +286,7 @@ else soundfile = fancy_release_sounds[RTB.release_sound] if(soundfile) - playsound(src, soundfile, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/client_preference/eating_noises) + playsound(src, soundfile, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/preference/toggle/eating_noises) /mob/living/proc/vore_bellyrub(var/mob/living/T in view(1,src)) set name = "Give Bellyrubs" diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index ba7e7a14f1..06fb05b42e 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -418,27 +418,25 @@ if(selecting_slots) to_chat(user, "You already have a slot selection dialog open!") return - var/savefile/S = new /savefile(path) - if(!S) - error("Somehow missing savefile path?! [path]") + if(!savefile) return - var/name - var/nickname //vorestation edit - This set appends nicknames to the save slot var/list/charlist = list() - var/default //VOREStation edit - for(var/i=1, i<= CONFIG_GET(number/character_slots), i++) //CHOMPEdit - S.cd = "/character[i]" - S["real_name"] >> name - S["nickname"] >> nickname //vorestation edit + + var/default + for(var/i in 1 to CONFIG_GET(number/character_slots)) //CHOMPEdit + 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]" + default = "[name][nickname ? " ([nickname])" : ""]" else name = "[i] - [name]" - if (i == default_slot) //VOREStation edit - default = "[name][nickname ? " ([nickname])" : ""]" + charlist["[name][nickname ? " ([nickname])" : ""]"] = i var/remember_default = default_slot @@ -461,11 +459,6 @@ return remember_default /datum/preferences/proc/return_to_character_slot(mob/user, var/remembered_default) - var/savefile/S = new /savefile(path) - if(!S) - error("Somehow missing savefile path?! [path]") - return - load_character(remembered_default) attempt_vr(user.client?.prefs_vr,"load_vore","") //VOREStation Edit sanitize_preferences() @@ -1331,11 +1324,10 @@ if(!user) CRASH("display_voreprefs() was called without an associated user.") var/dispvoreprefs = "[src]'s vore preferences


" - if(client && client.prefs) - if("CHAT_OOC" in client.prefs.preferences_disabled) - dispvoreprefs += "OOC DISABLED
" - if("CHAT_LOOC" in client.prefs.preferences_disabled) - dispvoreprefs += "LOOC DISABLED
" + if(!client?.prefs?.read_preference(/datum/preference/toggle/show_ooc)) + dispvoreprefs += "OOC DISABLED
" + if(!client?.prefs?.read_preference(/datum/preference/toggle/show_looc)) + dispvoreprefs += "LOOC DISABLED
" //CHOMPEdit Start dispvoreprefs += "Devourable: [devourable ? "Enabled" : "Disabled"]
" if(devourable) diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index fc4741a561..c4793f827b 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -2791,7 +2791,7 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", l.adjust_nutrition(thismuch) ourtarget.death() // To make sure all on-death procs get properly called if(ourtarget) - if(ourtarget.is_preference_enabled(/datum/client_preference/digestion_noises)) + if(ourtarget.check_sound_preference(/datum/preference/toggle/digestion_noises)) if(!b.fancy_vore) SEND_SOUND(ourtarget, sound(get_sfx("classic_death_sounds"))) else diff --git a/code/modules/vore/persist/persist_vr.dm b/code/modules/vore/persist/persist_vr.dm index a3e0a93f06..6d4a91a19a 100644 --- a/code/modules/vore/persist/persist_vr.dm +++ b/code/modules/vore/persist/persist_vr.dm @@ -109,6 +109,7 @@ prefs.size_multiplier = H.size_multiplier prefs.save_character() + prefs.save_preferences() // Saves mob's current coloration state to prefs // This basically needs to be the reverse of /datum/category_item/player_setup_item/general/body/copy_to_mob() ~Leshana @@ -233,40 +234,49 @@ * towards future shenanigans such as upgradable NIFs or different types or things of that nature, * without invoking the need for a bunch of different save file variables. */ -/proc/persist_nif_data(var/mob/living/carbon/human/H,var/datum/preferences/prefs) +/proc/persist_nif_data(mob/living/carbon/human/H) if(!istype(H)) stack_trace("Persist (NIF): Given a nonhuman: [H]") return - if(!prefs) - prefs = prep_for_persist(H) - - if(!prefs) - warning("Persist (NIF): [H] has no prefs datum, skipping") - return - var/obj/item/device/nif/nif = H.nif if(nif && H.ckey != nif.owner_key) return + var/slot = H?.mind?.loaded_from_slot + if(isnull(slot)) + warning("Persist (NIF): [H] has no mind slot, skipping") + return + + var/datum/json_savefile/savefile = new /datum/json_savefile(nif_savefile_path(H.ckey)) + var/list/save_data = savefile.get_entry("character[slot]", list()) + //If they have one, and if it's not installing without an owner, because //Someone who joins and immediately leaves again (wrong job choice, maybe) //should keep it even though it was probably doing the quick-calibrate, and their //owner will have been pre-set during the constructor. + var/nif_path + var/nif_durability + var/nif_savedata if(nif && !(nif.stat == NIF_INSTALLING && !nif.owner)) - prefs.nif_path = nif.type - prefs.nif_durability = nif.durability - prefs.nif_savedata = nif.save_data.Copy() + nif_path = nif.type + nif_durability = nif.durability + nif_savedata = nif.save_data.Copy() else - prefs.nif_path = null - prefs.nif_durability = null - prefs.nif_savedata = null + nif_path = null + nif_durability = null + nif_savedata = null - var/datum/category_group/player_setup_category/vore_cat = prefs.player_setup.categories_by_name["VORE"] - var/datum/category_item/player_setup_item/vore/nif/nif_prefs = vore_cat.items_by_name["NIF Data"] + save_data["nif_path"] = nif_path + save_data["nif_durability"] = nif_durability + save_data["nif_savedata"] = nif_savedata - var/savefile/S = new /savefile(prefs.path) - if(!S) warning("Persist (NIF): Couldn't load NIF save savefile? [prefs.real_name]") - S.cd = "/character[prefs.default_slot]" - nif_prefs.save_character(S) + savefile.set_entry("character[slot]", save_data) + savefile.save() + + // If they still have the same character loaded, update prefs + if(H?.client?.prefs?.default_slot == slot) + var/datum/category_group/player_setup_category/vore_cat = H.client.prefs.player_setup.categories_by_name["VORE"] + var/datum/category_item/player_setup_item/vore/nif/nif_prefs = vore_cat.items_by_name["NIF Data"] + nif_prefs.load_character() diff --git a/interface/interface.dm b/interface/interface.dm index 74f8ca539d..546a259741 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -223,7 +223,7 @@ Any-Mode: (hotkey doesn't need to be on) /client/proc/set_hotkeys_macro(macro_name = "macro", hotkey_macro_name = "hotkeymode", hotkeys_enabled = null) // If hotkeys mode was not specified, fall back to choice of default in client preferences. if(isnull(hotkeys_enabled)) - hotkeys_enabled = is_preference_enabled(/datum/client_preference/hotkeys_default) + hotkeys_enabled = prefs?.read_preference(/datum/preference/toggle/hotkeys_default) if(hotkeys_enabled) winset(src, null, "mainwindow.macro=[hotkey_macro_name] hotkey_toggle.is-checked=true mapwindow.map.focus=true") diff --git a/interface/skin.dmf b/interface/skin.dmf index 57d976f70d..a28a6708b1 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1154,6 +1154,20 @@ menu "menu" command = "hotkeys-help" category = "&Help" saved-params = "is-checked" + elem + name = "&Preferences" + command = "" + saved-params = "is-checked" + elem + name = "&Character Setup" + command = "character-setup" + category = "&Preferences" + saved-params = "is-checked" + elem + name = "&Game Options" + command = "game-options" + category = "&Preferences" + saved-params = "is-checked" window "mainwindow" elem "mainwindow" diff --git a/modular_chomp/code/_HELPERS/announcements.dm b/modular_chomp/code/_HELPERS/announcements.dm index d41b2b76ac..aaafa4732d 100644 --- a/modular_chomp/code/_HELPERS/announcements.dm +++ b/modular_chomp/code/_HELPERS/announcements.dm @@ -51,7 +51,7 @@ for(var/mob/target in players) to_chat(target, finalized_announcement) //if(play_sound && target.client?.prefs.read_preference(/datum/preference/toggle/sound_announcements)) - if(play_sound && target.client?.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(play_sound && target.client?.prefs.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) SEND_SOUND(target, sound(sound_override)) else to_chat(world, finalized_announcement) @@ -61,7 +61,7 @@ for(var/mob/player in player_list) //if(player.client?.prefs.read_preference(/datum/preference/toggle/sound_announcements)) - if(player.client?.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(player.client?.prefs.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) SEND_SOUND(player, sound(sound_override)) /** diff --git a/modular_chomp/code/datums/elements/slosh.dm b/modular_chomp/code/datums/elements/slosh.dm index b0032a3b93..7445ae59b6 100644 --- a/modular_chomp/code/datums/elements/slosh.dm +++ b/modular_chomp/code/datums/elements/slosh.dm @@ -89,5 +89,5 @@ if(!has_gravity(source) && prob(75)) return - playsound(source.loc, S, volume, FALSE, preference = /datum/client_preference/digestion_noises) + playsound(source.loc, S, volume, FALSE, preference = /datum/preference/toggle/digestion_noises) return diff --git a/modular_chomp/code/modules/balloon_alert/balloon_alert.dm b/modular_chomp/code/modules/balloon_alert/balloon_alert.dm index 0d05097807..801db06a35 100644 --- a/modular_chomp/code/modules/balloon_alert/balloon_alert.dm +++ b/modular_chomp/code/modules/balloon_alert/balloon_alert.dm @@ -22,7 +22,7 @@ for(var/mob/M in hearers) - runechat_enabled = M.client?.is_preference_enabled(/datum/client_preference/runechat_mob) + runechat_enabled = M.client.prefs?.read_preference(/datum/preference/toggle/runechat_mob) if(M.client && !runechat_enabled) continue diff --git a/modular_chomp/code/modules/client/preference_setup/global/setting_datums.dm b/modular_chomp/code/modules/client/preference_setup/global/setting_datums.dm deleted file mode 100644 index 416ac2ea70..0000000000 --- a/modular_chomp/code/modules/client/preference_setup/global/setting_datums.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/client_preference/random_emote_pitch - description ="Random emote pitch" - key = "EMOTE_VARY" - enabled_description = "Will be Random" - disabled_description = "Will not be Random" - -/datum/client_preference/autotranscore - description = "Automatic Transcore Notification" - key = "AUTOTRANSCORE" - enabled_description = "Automatic notification" - disabled_description = "No automatic notification" diff --git a/modular_chomp/code/modules/client/preferences.dm b/modular_chomp/code/modules/client/preferences.dm index 2df9cfb052..56dff7a2aa 100644 --- a/modular_chomp/code/modules/client/preferences.dm +++ b/modular_chomp/code/modules/client/preferences.dm @@ -8,36 +8,6 @@ var/job_other_med = 0 var/job_other_high = 0 -/client/verb/toggle_random_emote_pitch() - set name = "Toggle Random Emote Pitch" - set category = "Preferences.Character" - set desc = "Toggles whether or not emotes with sound you make will have random pitch." - - var/pref_path = /datum/client_preference/random_emote_pitch - - toggle_preference(pref_path) - - to_chat(src, "Audible emotes you make will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] have a random pitch applied to them.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TRandomEmotePitch") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_autotranscore() - set name = "Toggle Automatic Transcore Notification" - set category = "Preferences.Character" - set desc = "Toggles whether or not your death with a backup implant will automatically trigger a transcore notification after a few minutes." - - var/pref_path = /datum/client_preference/autotranscore - - toggle_preference(pref_path) - - to_chat(src, "Your death with a backup implant will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] trigger an automatic transcore notification.") - - SScharacter_setup.queue_preferences_save(prefs) - - feedback_add_details("admin_verb","TAutoTranscore") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /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 diff --git a/modular_chomp/code/modules/client/preferences/types/misc.dm b/modular_chomp/code/modules/client/preferences/types/misc.dm new file mode 100644 index 0000000000..58b2f812af --- /dev/null +++ b/modular_chomp/code/modules/client/preferences/types/misc.dm @@ -0,0 +1,9 @@ +/datum/preference/toggle/random_emote_pitch + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "EMOTE_VARY" + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/autotranscore + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "AUTOTRANSCORE" + savefile_identifier = PREFERENCE_PLAYER diff --git a/modular_chomp/code/modules/client/preferences/types/sound.dm b/modular_chomp/code/modules/client/preferences/types/sound.dm new file mode 100644 index 0000000000..2b7ce3ecc8 --- /dev/null +++ b/modular_chomp/code/modules/client/preferences/types/sound.dm @@ -0,0 +1,14 @@ +/datum/preference/toggle/looping_alarms + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_ALARMLOOP" + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/fridge_hum + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SOUND_FRIDGEHUM" + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/sleep_music + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "SLEEP_MUSIC" + savefile_identifier = PREFERENCE_PLAYER diff --git a/modular_chomp/code/modules/tickets/tickets.dm b/modular_chomp/code/modules/tickets/tickets.dm index 9100fbcf0e..9b4369f761 100644 --- a/modular_chomp/code/modules/tickets/tickets.dm +++ b/modular_chomp/code/modules/tickets/tickets.dm @@ -375,15 +375,15 @@ GLOBAL_DATUM_INIT(tickets, /datum/tickets, new) if(level == 1) for (var/client/C in GLOB.mentors) - if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping)) + if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) C << 'sound/effects/mentorhelp.mp3' for (var/client/C in GLOB.admins) - if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping)) + if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) C << 'sound/effects/mentorhelp.mp3' message_mentors(chat_msg) else if(level == 0) for(var/client/X in GLOB.admins) - if(X.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) X << 'sound/effects/adminhelp.ogg' window_flash(X) to_chat(X, chat_msg) @@ -515,7 +515,7 @@ GLOBAL_DATUM_INIT(tickets, /datum/tickets, new) return if(initiator) - if(initiator.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping)) + if(initiator.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) initiator << 'sound/effects/adminhelp.ogg' to_chat(initiator, "[span_red("- AdminHelp Rejected! -")]
\ diff --git a/prof.dll b/prof.dll new file mode 100644 index 0000000000..4721a5c187 Binary files /dev/null and b/prof.dll differ diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferenceWindow.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferenceWindow.tsx new file mode 100644 index 0000000000..58d863c5e6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferenceWindow.tsx @@ -0,0 +1,70 @@ +// import { exhaustiveCheck } from 'common/exhaustive'; +import { useState } from 'react'; + +import { useBackend } from '../../backend'; +import { Stack } from '../../components'; +import { Window } from '../../layouts'; +import { GamePreferencesSelectedPage, PreferencesMenuData } from './data'; +import { GamePreferencesPage } from './GamePreferencesPage'; +// import { KeybindingsPage } from './KeybindingsPage'; + +export const GamePreferenceWindow = (props: { + startingPage?: GamePreferencesSelectedPage; +}) => { + const { act, data } = useBackend(); + + const [currentPage, setCurrentPage] = useState( + props.startingPage ?? GamePreferencesSelectedPage.Settings, + ); + + let pageContents; + + switch (currentPage) { + // case GamePreferencesSelectedPage.Keybindings: + // pageContents = ; + // break; + case GamePreferencesSelectedPage.Settings: + pageContents = ; + break; + // default: + // exhaustiveCheck(currentPage); + } + + return ( + + + + {/* + + + + Settings + + + + + + Keybindings + + + + */} + + {/* */} + + + {pageContents} + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferencesPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferencesPage.tsx new file mode 100644 index 0000000000..35f279b428 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferencesPage.tsx @@ -0,0 +1,149 @@ +import { binaryInsertWith, sortBy } from 'common/collections'; +import { ReactNode, useState } from 'react'; + +import { useBackend } from '../../backend'; +import { Box, Button, Flex, Input, Section, Tooltip } from '../../components'; +import { PreferencesMenuData } from './data'; +import features from './preferences/features'; +import { FeatureValueInput } from './preferences/features/base'; +import { TabbedMenu } from './TabbedMenu'; + +type PreferenceChild = { + name: string; + children: ReactNode; +}; + +const binaryInsertPreference = ( + collection: PreferenceChild[], + value: PreferenceChild, +) => binaryInsertWith(collection, value, (child) => child.name); + +const sortByName = (array: [string, PreferenceChild[]][]) => + sortBy(array, ([name]) => name); + +export const GamePreferencesPage = (props) => { + const { act, data } = useBackend(); + + const gamePreferences: Record = {}; + + for (const [featureId, value] of Object.entries( + data.character_preferences.game_preferences, + )) { + const feature = features[featureId]; + + let nameInner: ReactNode = feature?.name || featureId; + + if (feature?.description) { + nameInner = ( + + {nameInner} + + ); + } + + let name: ReactNode = ( + + {nameInner} + + ); + + if (feature?.description) { + name = ( + + {name} + + ); + } + + const child = ( + + {name} + + + {(feature && ( + + )) || ( + + ...is not filled out properly!!! + + )} + + + ); + + const entry = { + name: feature?.name || featureId, + children: child, + }; + + const category = feature?.category || 'ERROR'; + + gamePreferences[category] = binaryInsertPreference( + gamePreferences[category] || [], + entry, + ); + } + + const [search, setSearch] = useState(''); + const [searchVisible, setSearchVisible] = useState(false); + + // For some reason, typescript thinks that this call to filter() can change the shape of the array + const gamePreferenceEntries: any = sortByName(Object.entries(gamePreferences)) + .map(([category, preferences]) => { + return [ + category, + preferences + .filter( + (entry) => + !search || + entry.name.toLowerCase().includes(search.toLowerCase()), + ) + .map((entry) => entry.children), + ]; + }) + .filter(([category, prefs]) => prefs.length !== 0); + + return ( + <> + {!gamePreferenceEntries.length && ( +
No results found.
+ )} + + {searchVisible && ( + setSearch(val)} + onChange={(e, val) => setSearch(val)} + /> + )} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/ServerPreferencesFetcher.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/ServerPreferencesFetcher.tsx new file mode 100644 index 0000000000..0b90fd855c --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/ServerPreferencesFetcher.tsx @@ -0,0 +1,43 @@ +import { Component, ReactNode } from 'react'; + +import { resolveAsset } from '../../assets'; +import { fetchRetry } from '../../http'; +import { ServerData } from './data'; + +// Cache response so it's only sent once +let fetchServerData: Promise | undefined; + +export class ServerPreferencesFetcher extends Component< + { + render: (serverData: ServerData | undefined) => ReactNode; + }, + { + serverData?: ServerData; + } +> { + state = { + serverData: undefined, + }; + + componentDidMount() { + this.populateServerData(); + } + + async populateServerData() { + if (!fetchServerData) { + fetchServerData = fetchRetry(resolveAsset('preferences.json')).then( + (response) => response.json(), + ); + } + + const preferencesData: ServerData = await fetchServerData; + + this.setState({ + serverData: preferencesData, + }); + } + + render() { + return this.props?.render?.(this.state.serverData); + } +} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/TabbedMenu.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/TabbedMenu.tsx new file mode 100644 index 0000000000..13c572ebc8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/TabbedMenu.tsx @@ -0,0 +1,90 @@ +import { Component, createRef, ReactNode, RefObject } from 'react'; + +import { Button, Section, Stack } from '../../components'; +import { FlexProps } from '../../components/Flex'; + +type TabbedMenuProps = { + categoryEntries: [string, ReactNode][]; + contentProps?: FlexProps; +}; + +export class TabbedMenu extends Component { + categoryRefs: Record> = {}; + sectionRef: RefObject = createRef(); + + getCategoryRef(category: string): RefObject { + if (!this.categoryRefs[category]) { + this.categoryRefs[category] = createRef(); + } + + return this.categoryRefs[category]; + } + + render() { + return ( + + + + {this.props.categoryEntries.map(([category]) => { + return ( + + + + ); + })} + + + + + + {this.props.categoryEntries.map(([category, children]) => { + return ( + +
+ {children} +
+
+ ); + })} +
+
+
+ ); + } +} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/abductor.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/abductor.ts new file mode 100644 index 0000000000..789e5e9823 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/abductor.ts @@ -0,0 +1,23 @@ +import { Antagonist, Category } from '../base'; + +const Abductor: Antagonist = { + key: 'abductor', + name: 'Abductor', + description: [ + ` + Abductors are technologically advanced alien society set on cataloging + all species in the system. Unfortunately for their subjects their methods + are quite invasive. + `, + + ` + You and a partner will become the abductor scientist and agent duo. + As an agent, abduct unassuming victims and bring them back to your UFO. + As a scientist, scout out victims for your agent, keep them safe, and + operate on whoever they bring back. + `, + ], + category: Category.Midround, +}; + +export default Abductor; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/blob.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/blob.ts new file mode 100644 index 0000000000..92cd5bdc44 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/blob.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; + +export const BLOB_MECHANICAL_DESCRIPTION = ` + The blob infests the station and destroys everything in its path, including + hull, fixtures, and creatures. Spread your mass, collect resources, and + consume the entire station. Make sure to prepare your defenses, because the + crew will be alerted to your presence! +`; + +const Blob: Antagonist = { + key: 'blob', + name: 'Blob', + description: [BLOB_MECHANICAL_DESCRIPTION], + category: Category.Midround, +}; + +export default Blob; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/blobinfection.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/blobinfection.ts new file mode 100644 index 0000000000..d8a5f135fe --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/blobinfection.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { BLOB_MECHANICAL_DESCRIPTION } from './blob'; + +const BlobInfection: Antagonist = { + key: 'blobinfection', + name: 'Blob Infection', + description: [ + ` + At any point in the middle of the shift, be strucken with an infection + that will turn you into the terrifying blob. + `, + BLOB_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, +}; + +export default BlobInfection; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/bloodbrother.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/bloodbrother.ts new file mode 100644 index 0000000000..56cbcbfc97 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/bloodbrother.ts @@ -0,0 +1,16 @@ +import { Antagonist, Category } from '../base'; + +const BloodBrother: Antagonist = { + key: 'bloodbrother', + name: 'Blood Brother', + description: [ + ` + Team up with other crew members as blood brothers to combine the strengths + of your departments, break each other out of prison, and overwhelm the + station. + `, + ], + category: Category.Roundstart, +}; + +export default BloodBrother; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/changeling.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/changeling.ts new file mode 100644 index 0000000000..9124a07063 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/changeling.ts @@ -0,0 +1,21 @@ +import { Antagonist, Category } from '../base'; + +export const CHANGELING_MECHANICAL_DESCRIPTION = ` +Transform yourself or others into different identities, and buy from an +arsenal of biological weaponry with the DNA you collect. +`; + +const Changeling: Antagonist = { + key: 'changeling', + name: 'Changeling', + description: [ + ` + A highly intelligent alien predator that is capable of altering their + shape to flawlessly resemble a human. + `, + CHANGELING_MECHANICAL_DESCRIPTION, + ], + category: Category.Roundstart, +}; + +export default Changeling; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/changelingmidround.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/changelingmidround.ts new file mode 100644 index 0000000000..8324bdb858 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/changelingmidround.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { CHANGELING_MECHANICAL_DESCRIPTION } from './changeling'; + +const ChangelingMidround: Antagonist = { + key: 'changelingmidround', + name: 'Space Changeling', + description: [ + ` + A midround changeling does not receive a crew identity, instead arriving + from space. This will be more difficult than being a round-start changeling! + `, + CHANGELING_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, +}; + +export default ChangelingMidround; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/clownoperative.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/clownoperative.ts new file mode 100644 index 0000000000..61f874a2e9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/clownoperative.ts @@ -0,0 +1,20 @@ +import { Antagonist, Category } from '../base'; +import { OPERATIVE_MECHANICAL_DESCRIPTION } from './operative'; + +const ClownOperative: Antagonist = { + key: 'clownoperative', + name: 'Clown Operative', + description: [ + ` + Honk! You have been chosen, for better or worse to join the Syndicate + Clown Operative strike team. Your mission, whether or not you choose + to tickle it, is to honk Nanotrasen's most advanced research facility! + That's right, you're going to Clown Station 13. + `, + + OPERATIVE_MECHANICAL_DESCRIPTION, + ], + category: Category.Roundstart, +}; + +export default ClownOperative; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/cultist.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/cultist.ts new file mode 100644 index 0000000000..c5cfaf61c2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/cultist.ts @@ -0,0 +1,22 @@ +import { Antagonist, Category } from '../base'; + +const Cultist: Antagonist = { + key: 'cultist', + name: 'Cultist', + description: [ + ` + The Geometer of Blood, Nar-Sie, has sent a number of her followers to + Space Station 13. As a cultist, you have an abundance of cult magics at + your disposal, something for all situations. You must work with your + brethren to summon an avatar of your eldritch goddess! + `, + + ` + Armed with blood magic, convert crew members to the Blood Cult, sacrifice + those who get in the way, and summon Nar-Sie. + `, + ], + category: Category.Roundstart, +}; + +export default Cultist; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/fugitive.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/fugitive.ts new file mode 100644 index 0000000000..f05a96c2ec --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/fugitive.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +const Fugitive: Antagonist = { + key: 'fugitive', + name: 'Fugitive', + description: [ + ` + Wherever you come from, you're being hunted. You have 10 minutes to prepare + before fugitive hunters arrive and start hunting you and your friends down! + `, + ], + category: Category.Midround, +}; + +export default Fugitive; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/glitch.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/glitch.ts new file mode 100644 index 0000000000..3d269c54ca --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/glitch.ts @@ -0,0 +1,19 @@ +import { Antagonist, Category } from '../base'; + +const Glitch: Antagonist = { + key: 'glitch', + name: 'Glitch', + description: [ + ` + The virtual domain is a dangerous place for bitrunners. Make it so. + `, + + ` + You are a short-term antagonist, a glitch in the system. Use martial arts \ + and lethal weaponry to terminate organics. + `, + ], + category: Category.Midround, +}; + +export default Glitch; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/headrevolutionary.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/headrevolutionary.ts new file mode 100644 index 0000000000..0a1d644266 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/headrevolutionary.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +export const REVOLUTIONARY_MECHANICAL_DESCRIPTION = ` + Armed with a flash, convert as many people to the revolution as you can. + Kill or exile all heads of staff on the station. + `; + +const HeadRevolutionary: Antagonist = { + key: 'headrevolutionary', + name: 'Head Revolutionary', + description: ['VIVA LA REVOLUTION!', REVOLUTIONARY_MECHANICAL_DESCRIPTION], + category: Category.Roundstart, +}; + +export default HeadRevolutionary; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/heretic.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/heretic.ts new file mode 100644 index 0000000000..8c2d631bd5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/heretic.ts @@ -0,0 +1,22 @@ +import { Antagonist, Category } from '../base'; + +export const HERETIC_MECHANICAL_DESCRIPTION = ` + Find hidden influences and sacrifice crew members to gain magical + powers and ascend as one of several paths. + `; + +const Heretic: Antagonist = { + key: 'heretic', + name: 'Heretic', + description: [ + ` + Forgotten, devoured, gutted. Humanity has forgotten the eldritch forces + of decay, but the mansus veil has weakened. We will make them taste fear + again... + `, + HERETIC_MECHANICAL_DESCRIPTION, + ], + category: Category.Roundstart, +}; + +export default Heretic; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/hereticsmuggler.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/hereticsmuggler.ts new file mode 100644 index 0000000000..6ce90a3552 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/hereticsmuggler.ts @@ -0,0 +1,14 @@ +import { Antagonist, Category } from '../base'; +import { HERETIC_MECHANICAL_DESCRIPTION } from './heretic'; + +const HereticSmuggler: Antagonist = { + key: 'hereticsmuggler', + name: 'Heretic Smuggler', + description: [ + 'A form of heretic that can activate when joining an ongoing shift.', + HERETIC_MECHANICAL_DESCRIPTION, + ], + category: Category.Latejoin, +}; + +export default HereticSmuggler; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/loneoperative.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/loneoperative.ts new file mode 100644 index 0000000000..3f8ac5c5b7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/loneoperative.ts @@ -0,0 +1,18 @@ +import { Antagonist, Category } from '../base'; +import { OPERATIVE_MECHANICAL_DESCRIPTION } from './operative'; + +const LoneOperative: Antagonist = { + key: 'loneoperative', + name: 'Lone Operative', + description: [ + ` + A solo nuclear operative that has a higher chance of spawning the longer + the nuclear authentication disk stays in one place. + `, + + OPERATIVE_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, +}; + +export default LoneOperative; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/malfai.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/malfai.ts new file mode 100644 index 0000000000..03aadd752c --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/malfai.ts @@ -0,0 +1,16 @@ +import { Antagonist, Category } from '../base'; + +export const MALF_AI_MECHANICAL_DESCRIPTION = ` + With a law zero to complete your objectives at all costs, combine your + omnipotence and malfunction modules to wreak havoc across the station. + Go delta to destroy the station and all those who opposed you. + `; + +const MalfAI: Antagonist = { + key: 'malfai', + name: 'Malfunctioning AI', + description: [MALF_AI_MECHANICAL_DESCRIPTION], + category: Category.Roundstart, +}; + +export default MalfAI; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/malfaimidround.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/malfaimidround.ts new file mode 100644 index 0000000000..84e52b43dc --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/malfaimidround.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { MALF_AI_MECHANICAL_DESCRIPTION } from './malfai'; + +const MalfAIMidround: Antagonist = { + key: 'malfaimidround', + name: 'Value Drifted AI', + description: [ + ` + A form of malfunctioning AI that is given to existing AIs in the middle + of the shift. + `, + MALF_AI_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, +}; + +export default MalfAIMidround; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/nightmare.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/nightmare.ts new file mode 100644 index 0000000000..90046dd625 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/nightmare.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +const Nightmare: Antagonist = { + key: 'nightmare', + name: 'Nightmare', + description: [ + ` + Use your light eater to break sources of light to survive and thrive. + Jaunt through the darkness and seek your prey with night vision. + `, + ], + category: Category.Midround, +}; + +export default Nightmare; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/obsessed.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/obsessed.ts new file mode 100644 index 0000000000..8d4c981398 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/obsessed.ts @@ -0,0 +1,16 @@ +import { Antagonist, Category } from '../base'; + +const Obsessed: Antagonist = { + key: 'obsessed', + name: 'Obsessed', + description: [ + ` + You're obsessed with someone! Your obsession may begin to notice their + personal items are stolen and their coworkers have gone missing, + but will they realize they are your next victim in time? + `, + ], + category: Category.Midround, +}; + +export default Obsessed; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/operative.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/operative.ts new file mode 100644 index 0000000000..a8182de9e0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/operative.ts @@ -0,0 +1,24 @@ +import { Antagonist, Category } from '../base'; + +export const OPERATIVE_MECHANICAL_DESCRIPTION = ` + Retrieve the nuclear authentication disk, use it to activate the nuclear + fission explosive, and destroy the station. +`; + +const Operative: Antagonist = { + key: 'operative', + name: 'Nuclear Operative', + description: [ + ` + Congratulations, agent. You have been chosen to join the Syndicate + Nuclear Operative strike team. Your mission, whether or not you choose + to accept it, is to destroy Nanotrasen's most advanced research facility! + That's right, you're going to Space Station 13. + `, + + OPERATIVE_MECHANICAL_DESCRIPTION, + ], + category: Category.Roundstart, +}; + +export default Operative; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/operativemidround.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/operativemidround.ts new file mode 100644 index 0000000000..7497c6442f --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/operativemidround.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { OPERATIVE_MECHANICAL_DESCRIPTION } from './operative'; + +const OperativeMidround: Antagonist = { + key: 'operativemidround', + name: 'Nuclear Assailant', + description: [ + ` + A form of nuclear operative that is offered to ghosts in the middle + of the shift. + `, + OPERATIVE_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, +}; + +export default OperativeMidround; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/paradoxclone.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/paradoxclone.ts new file mode 100644 index 0000000000..1456df3ec6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/paradoxclone.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +const ParadoxClone: Antagonist = { + key: 'paradoxclone', + name: 'Paradox Clone', + description: [ + ` + A freak time-space anomaly has teleported you into another reality! + Now you have to find your counterpart and kill and replace them. + `, + ], + category: Category.Midround, +}; + +export default ParadoxClone; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/provocateur.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/provocateur.ts new file mode 100644 index 0000000000..1d2879c867 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/provocateur.ts @@ -0,0 +1,18 @@ +import { Antagonist, Category } from '../base'; +import { REVOLUTIONARY_MECHANICAL_DESCRIPTION } from './headrevolutionary'; + +const Provocateur: Antagonist = { + key: 'provocateur', + name: 'Provocateur', + description: [ + ` + A form of head revolutionary that can activate when joining an ongoing + shift. + `, + + REVOLUTIONARY_MECHANICAL_DESCRIPTION, + ], + category: Category.Latejoin, +}; + +export default Provocateur; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/revenant.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/revenant.ts new file mode 100644 index 0000000000..241b52b3e4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/revenant.ts @@ -0,0 +1,16 @@ +import { Antagonist, Category } from '../base'; + +const Revenant: Antagonist = { + key: 'revenant', + name: 'Revenant', + description: [ + ` + Become the mysterious revenant. Break windows, overload lights, and eat + the crew's life force, all while talking to your old community of + disgruntled ghosts. + `, + ], + category: Category.Midround, +}; + +export default Revenant; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/sentiencepotionspawn.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/sentiencepotionspawn.ts new file mode 100644 index 0000000000..e69db3bec3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/sentiencepotionspawn.ts @@ -0,0 +1,22 @@ +import { Antagonist, Category } from '../base'; + +const SentientCreature: Antagonist = { + key: 'sentiencepotionspawn', + name: 'Sentient Creature', + description: [ + ` + Either by cosmic happenstance, or due to crew's shenanigans, you have been + given sentience! + `, + + ` + This is a blanket preference. The more benign ones include random human + level intelligence events, the cargorilla, and creatures uplifted via sentience + potions. The less friendly ones include the regal rat, and the boosted + mining elite mobs. + `, + ], + category: Category.Midround, +}; + +export default SentientCreature; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spacedragon.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spacedragon.ts new file mode 100644 index 0000000000..4a11cc94d1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spacedragon.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +const SpaceDragon: Antagonist = { + key: 'spacedragon', + name: 'Space Dragon', + description: [ + ` + Become a ferocious space dragon. Breathe fire, summon an army of space + carps, crush walls, and terrorize the station. + `, + ], + category: Category.Midround, +}; + +export default SpaceDragon; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spaceninja.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spaceninja.ts new file mode 100644 index 0000000000..e6db1b96f9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spaceninja.ts @@ -0,0 +1,23 @@ +import { Antagonist, Category } from '../base'; + +const SpaceNinja: Antagonist = { + key: 'spaceninja', + name: 'Space Ninja', + description: [ + ` + The Spider Clan practice a sort of augmentation of human flesh in order to + achieve a more perfect state of being and follow Postmodern Space Bushido. + `, + + ` + Become a conniving space ninja, equipped with a katana, gloves to hack + into airlocks and APCs, a suit to make you go near-invisible, + as well as a variety of abilities in your kit. Hack into arrest consoles + to mark everyone as arrest, and even hack into communication consoles to + summon more threats to cause chaos on the station! + `, + ], + category: Category.Midround, +}; + +export default SpaceNinja; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spy.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spy.ts new file mode 100644 index 0000000000..b004b972d4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spy.ts @@ -0,0 +1,22 @@ +import { Antagonist, Category } from '../base'; + +const Spy: Antagonist = { + key: 'spy', + name: 'Spy', + description: [ + ` + Your mission, should you choose to accept it: Infiltrate Space Station 13. + Disguise yourself as a member of their crew and steal vital equipment. + Should you be caught or killed, your employer will disavow any knowledge + of your actions. Good luck agent. + `, + + ` + Complete Spy Bounties to earn rewards from your employer. + Use these rewards to sow chaos and mischief! + `, + ], + category: Category.Roundstart, +}; + +export default Spy; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/stowawaychangeling.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/stowawaychangeling.ts new file mode 100644 index 0000000000..20ba22ee07 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/stowawaychangeling.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { CHANGELING_MECHANICAL_DESCRIPTION } from './changeling'; + +const Stowaway_Changeling: Antagonist = { + key: 'stowawaychangeling', + name: 'Stowaway Changeling', + description: [ + ` + A Changeling that found its way onto the shuttle + unbeknownst to the crewmembers on board. + `, + CHANGELING_MECHANICAL_DESCRIPTION, + ], + category: Category.Latejoin, +}; + +export default Stowaway_Changeling; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/syndicateinfiltrator.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/syndicateinfiltrator.ts new file mode 100644 index 0000000000..45ad29292b --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/syndicateinfiltrator.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; +import { TRAITOR_MECHANICAL_DESCRIPTION } from './traitor'; + +const SyndicateInfiltrator: Antagonist = { + key: 'syndicateinfiltrator', + name: 'Syndicate Infiltrator', + description: [ + 'A form of traitor that can activate when joining an ongoing shift.', + TRAITOR_MECHANICAL_DESCRIPTION, + ], + category: Category.Latejoin, + priority: -1, +}; + +export default SyndicateInfiltrator; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/syndicatesleeperagent.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/syndicatesleeperagent.ts new file mode 100644 index 0000000000..4a680e5d34 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/syndicatesleeperagent.ts @@ -0,0 +1,18 @@ +import { Antagonist, Category } from '../base'; +import { TRAITOR_MECHANICAL_DESCRIPTION } from './traitor'; + +const SyndicateSleeperAgent: Antagonist = { + key: 'syndicatesleeperagent', + name: 'Syndicate Sleeper Agent', + description: [ + ` + A form of traitor that can activate at any point in the middle + of the shift. + `, + TRAITOR_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, + priority: -1, +}; + +export default SyndicateSleeperAgent; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/traitor.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/traitor.ts new file mode 100644 index 0000000000..ccb6391998 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/traitor.ts @@ -0,0 +1,23 @@ +import { Antagonist, Category } from '../base'; + +export const TRAITOR_MECHANICAL_DESCRIPTION = ` + Start with an uplink to purchase your gear and take on your sinister + objectives. Ascend through the ranks and become an infamous legend. + `; + +const Traitor: Antagonist = { + key: 'traitor', + name: 'Traitor', + description: [ + ` + An unpaid debt. A score to be settled. Maybe you were just in the wrong + place at the wrong time. Whatever the reasons, you were selected to + infiltrate Space Station 13. + `, + TRAITOR_MECHANICAL_DESCRIPTION, + ], + category: Category.Roundstart, + priority: -1, +}; + +export default Traitor; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wizard.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wizard.ts new file mode 100644 index 0000000000..62530a26f6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wizard.ts @@ -0,0 +1,18 @@ +import { Antagonist, Category } from '../base'; + +export const WIZARD_MECHANICAL_DESCRIPTION = ` + Choose between a variety of powerful spells in order to cause chaos + among Space Station 13. + `; + +const Wizard: Antagonist = { + key: 'wizard', + name: 'Wizard', + description: [ + `"GREETINGS. WE'RE THE WIZARDS OF THE WIZARD'S FEDERATION."`, + WIZARD_MECHANICAL_DESCRIPTION, + ], + category: Category.Roundstart, +}; + +export default Wizard; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wizardmidround.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wizardmidround.ts new file mode 100644 index 0000000000..b36a9f5170 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wizardmidround.ts @@ -0,0 +1,14 @@ +import { Antagonist, Category } from '../base'; +import { WIZARD_MECHANICAL_DESCRIPTION } from './wizard'; + +const WizardMidround: Antagonist = { + key: 'wizardmidround', + name: 'Wizard (Midround)', + description: [ + 'A form of wizard that is offered to ghosts in the middle of the shift.', + WIZARD_MECHANICAL_DESCRIPTION, + ], + category: Category.Midround, +}; + +export default WizardMidround; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/xenomorph.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/xenomorph.ts new file mode 100644 index 0000000000..a9a1960f3b --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/xenomorph.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +const Xenomorph: Antagonist = { + key: 'xenomorph', + name: 'Xenomorph', + description: [ + ` + Become the extraterrestrial xenomorph. Start as a larva, and progress + your way up the caste, including even the Queen! + `, + ], + category: Category.Midround, +}; + +export default Xenomorph; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/base.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/base.ts new file mode 100644 index 0000000000..a91b6af878 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/base.ts @@ -0,0 +1,35 @@ +/** + * This folder represents the antagonists you can choose in the preferences + * menu. + * + * Every file in this folder represents one antagonist. + * + * For example "Syndicate Sleeper Agent" -> syndicatesleeperagent.ts + * + * "Antagonist" in this context actually means ruleset. + * This is an important distinction--it means that players can choose to be + * a roundstart traitor, but not a latejoin traitor. + * + * Icons are generated from the antag datums themselves, provided by the + * `antag_datum` variable on the /datum/dynamic_ruleset. + * + * The icon used is whatever the return value of get_preview_icon() is. + * Most antagonists, unless they want an especially cool effect, can simply + * set preview_outfit to some typepath representing their character. + */ + +export type Antagonist = { + // the antag_flag, made lowercase, and with non-alphanumerics removed. + key: string; + + name: string; + description: string[]; + category: Category; + priority?: number; +}; + +export enum Category { + Roundstart, + Midround, + Latejoin, +} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts new file mode 100644 index 0000000000..ff8d1b112a --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts @@ -0,0 +1,36 @@ +import { sendAct } from '../../backend'; + +export enum GamePreferencesSelectedPage { + Settings, + Keybindings, +} + +export const createSetPreference = + (act: typeof sendAct, preference: string) => (value: unknown) => { + act('set_preference', { + preference, + value, + }); + }; + +export enum Window { + Character = 0, + Game = 1, + Keybindings = 2, +} + +export type PreferencesMenuData = { + character_profiles: (string | null)[]; + + character_preferences: { + game_preferences: Record; + }; + + active_slot: number; + + window: Window; +}; + +export type ServerData = { + [otheyKey: string]: unknown; +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/index.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/index.tsx new file mode 100644 index 0000000000..e64e62f8ff --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/index.tsx @@ -0,0 +1,31 @@ +// import { exhaustiveCheck } from 'common/exhaustive'; + +import { useBackend } from '../../backend'; +// import { CharacterPreferenceWindow } from './CharacterPreferenceWindow'; +import { + GamePreferencesSelectedPage, + PreferencesMenuData, + Window, +} from './data'; +import { GamePreferenceWindow } from './GamePreferenceWindow'; + +export const PreferencesMenu = (props) => { + const { data } = useBackend(); + + const window = data.window; + + switch (window) { + // case Window.Character: + // return ; + case Window.Game: + return ; + case Window.Keybindings: + return ( + + ); + // default: + // exhaustiveCheck(window); + } +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx new file mode 100644 index 0000000000..452eb67751 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx @@ -0,0 +1,267 @@ +import { sortBy } from 'common/collections'; +import { BooleanLike } from 'common/react'; +import { + ComponentType, + createElement, + ReactNode, + useEffect, + useState, +} from 'react'; + +import { sendAct, useBackend } from '../../../../backend'; +import { + Box, + Button, + Dropdown, + Input, + NumberInput, + Slider, + Stack, +} from '../../../../components'; +import { createSetPreference, PreferencesMenuData } from '../../data'; +import { ServerPreferencesFetcher } from '../../ServerPreferencesFetcher'; + +export const sortChoices = (array: [string, ReactNode][]) => + sortBy(array, ([name]) => name); + +export type Feature< + TReceiving, + TSending = TReceiving, + TServerData = undefined, +> = { + name: string; + component: FeatureValue; + category?: string; + description?: string; +}; + +/** + * Represents a preference. + * TReceiving = The type you will be receiving + * TSending = The type you will be sending + * TServerData = The data the server sends through preferences.json + */ +type FeatureValue< + TReceiving, + TSending = TReceiving, + TServerData = undefined, +> = ComponentType>; + +export type FeatureValueProps< + TReceiving, + TSending = TReceiving, + TServerData = undefined, +> = Readonly<{ + act: typeof sendAct; + featureId: string; + handleSetValue: (newValue: TSending) => void; + serverData: TServerData | undefined; + shrink?: boolean; + value: TReceiving; +}>; + +export const FeatureColorInput = (props: FeatureValueProps) => { + return ( + + ); +}; + +export type FeatureToggle = Feature; + +export const CheckboxInput = ( + props: FeatureValueProps, +) => { + return ( + { + props.handleSetValue(!props.value); + }} + /> + ); +}; + +export const CheckboxInputInverse = ( + props: FeatureValueProps, +) => { + return ( + { + props.handleSetValue(!props.value); + }} + /> + ); +}; + +export function createDropdownInput( + // Map of value to display texts + choices: Record, + dropdownProps?: Record, +): FeatureValue { + return (props: FeatureValueProps) => { + return ( + { + return { + displayText: label, + value: dataValue, + }; + }, + )} + {...dropdownProps} + /> + ); + }; +} + +export type FeatureChoicedServerData = { + choices: string[]; + display_names?: Record; + icons?: Record; +}; + +export type FeatureChoiced = Feature; + +export type FeatureNumericData = { + minimum: number; + maximum: number; + step: number; +}; + +export type FeatureNumeric = Feature; + +export const FeatureNumberInput = ( + props: FeatureValueProps, +) => { + if (!props.serverData) { + return Loading...; + } + + return ( + { + props.handleSetValue(value); + }} + minValue={props.serverData.minimum} + maxValue={props.serverData.maximum} + step={props.serverData.step} + value={props.value} + /> + ); +}; + +export const FeatureSliderInput = ( + props: FeatureValueProps, +) => { + if (!props.serverData) { + return Loading...; + } + + return ( + { + props.handleSetValue(value); + }} + minValue={props.serverData.minimum} + maxValue={props.serverData.maximum} + step={props.serverData.step} + value={props.value} + stepPixelSize={10} + /> + ); +}; + +export const FeatureValueInput = (props: { + feature: Feature; + featureId: string; + shrink?: boolean; + value: unknown; + + act: typeof sendAct; +}) => { + const { data } = useBackend(); + + const feature = props.feature; + + const [predictedValue, setPredictedValue] = useState(props.value); + + const changeValue = (newValue: unknown) => { + setPredictedValue(newValue); + createSetPreference(props.act, props.featureId)(newValue); + }; + + useEffect(() => { + setPredictedValue(props.value); + }, [data.active_slot, props.value]); + + return ( + { + return createElement(feature.component, { + act: props.act, + featureId: props.featureId, + serverData: serverData?.[props.featureId] as any, + shrink: props.shrink, + + handleSetValue: changeValue, + value: predictedValue, + }); + }} + /> + ); +}; + +export type FeatureShortTextData = { + maximum_length: number; +}; + +export const FeatureShortTextInput = ( + props: FeatureValueProps, +) => { + if (!props.serverData) { + return Loading...; + } + + return ( + props.handleSetValue(value)} + /> + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx new file mode 100644 index 0000000000..32e1161e63 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx @@ -0,0 +1,113 @@ +import { classes } from 'common/react'; +import { capitalizeFirst } from 'common/string'; +import { ReactNode } from 'react'; + +import { Box, Dropdown, Stack } from '../../../../components'; +import { Feature, FeatureChoicedServerData, FeatureValueProps } from './base'; + +type DropdownInputProps = FeatureValueProps< + string, + string, + FeatureChoicedServerData +> & + Partial<{ + disabled: boolean; + buttons: boolean; + }>; + +type IconnedDropdownInputProps = FeatureValueProps< + string, + string, + FeatureChoicedServerData +>; + +export type FeatureWithIcons = Feature; + +export function FeatureDropdownInput(props: DropdownInputProps) { + const { serverData, disabled, buttons, handleSetValue, value } = props; + + if (!serverData) { + return null; + } + + const { choices, display_names } = serverData; + + const dropdownOptions = choices.map((choice) => { + let displayText: ReactNode = display_names + ? display_names[choice] + : capitalizeFirst(choice); + + return { + displayText, + value: choice, + }; + }); + + let display_text = value; + if (display_names) { + display_text = display_names[value]; + } + + return ( + + ); +} + +export function FeatureIconnedDropdownInput(props: IconnedDropdownInputProps) { + const { serverData, handleSetValue, value } = props; + + if (!serverData) { + return null; + } + + const { choices, display_names, icons } = serverData; + + const dropdownOptions = choices.map((choice) => { + let displayText: ReactNode = display_names + ? display_names[choice] + : capitalizeFirst(choice); + + if (icons?.[choice]) { + displayText = ( + + + + + {displayText} + + ); + } + + return { + displayText, + value: choice, + }; + }); + + let display_text = value; + if (display_names) { + display_text = display_names[value]; + } + + return ( + + ); +} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx new file mode 100644 index 0000000000..f9f8fec97b --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx @@ -0,0 +1,50 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const CHAT_ATTACKLOGS: FeatureToggle = { + name: 'Attack Log Messages', + category: 'ADMIN', + description: 'Show attack logs.', + component: CheckboxInput, +}; + +export const CHAT_DEBUGLOGS: FeatureToggle = { + name: 'Debug Logs', + category: 'ADMIN', + description: 'Show debug logs.', + component: CheckboxInput, +}; + +export const CHAT_PRAYER: FeatureToggle = { + name: 'Chat Prayers', + category: 'ADMIN', + description: 'Show prayers.', + component: CheckboxInput, +}; + +export const SOUND_ADMINHELP: FeatureToggle = { + name: 'Adminhelp Sound', + category: 'ADMIN', + description: 'Enables playing the bwoink when a new adminhelp is sent.', + component: CheckboxInput, +}; + +export const CHAT_RADIO: FeatureToggle = { + name: 'Radio Chatter', + category: 'ADMIN', + description: 'Completely enable/disable hearing any radio anywhere.', + component: CheckboxInput, +}; + +export const CHAT_RLOOC: FeatureToggle = { + name: 'Remote LOOC Chat', + category: 'ADMIN', + description: 'Hear LOOC from anywhere.', + component: CheckboxInput, +}; + +export const CHAT_ADSAY: FeatureToggle = { + name: 'Living Deadchat', + category: 'ADMIN', + description: 'Enables seeing deadchat when not observing.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/chat.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/chat.tsx new file mode 100644 index 0000000000..10c081f060 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/chat.tsx @@ -0,0 +1,81 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const CHAT_SHOWICONS: FeatureToggle = { + name: 'Chat Tags', + category: 'CHAT', + description: 'Show tags/badges on special channels like OOC.', + component: CheckboxInput, +}; + +export const SHOW_TYPING: FeatureToggle = { + name: 'Typing Indicator', + category: 'CHAT', + description: 'Show a typing indicator when you are typing ingame.', + component: CheckboxInput, +}; + +export const SHOW_TYPING_SUBTLE: FeatureToggle = { + name: 'Typing Indicator: Subtle', + category: 'CHAT', + description: 'Show typing indicator for subtle and whisper messages.', + component: CheckboxInput, +}; + +export const CHAT_OOC: FeatureToggle = { + name: 'OOC Chat', + category: 'CHAT', + description: 'Enables OOC chat.', + component: CheckboxInput, +}; + +export const CHAT_LOOC: FeatureToggle = { + name: 'LOOC Chat', + category: 'CHAT', + description: 'Enables L(ocal)OOC chat.', + component: CheckboxInput, +}; + +export const CHAT_DEAD: FeatureToggle = { + name: 'Dead Chat', + category: 'CHAT', + description: 'Enables observer/dead/ghost chat.', + component: CheckboxInput, +}; + +export const CHAT_MENTION: FeatureToggle = { + name: 'Emphasize Name Mention', + category: 'CHAT', + description: + 'Makes messages containing your name or nickname appear larger to get your attention.', + component: CheckboxInput, +}; + +export const VORE_HEALTH_BARS: FeatureToggle = { + name: 'Vore Health Bars', + category: 'CHAT', + description: + 'Periodically shows status health bars in chat occasionally during vore absorption/digestion.', + component: CheckboxInput, +}; + +export const NEWS_POPUP: FeatureToggle = { + name: 'Lore News Popups', + category: 'CHAT', + description: 'Show new lore news on login.', + component: CheckboxInput, +}; + +export const RECEIVE_TIPS: FeatureToggle = { + name: 'Receive Tips Periodically', + category: 'CHAT', + description: 'Show helpful tips for new players periodically.', + component: CheckboxInput, +}; + +export const PAIN_FREQUENCY: FeatureToggle = { + name: 'Pain Message Cooldown', + category: 'CHAT', + description: + 'When enabled, reduces the amount of pain messages for minor wounds that you see.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ghost.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ghost.tsx new file mode 100644 index 0000000000..10cc2297af --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ghost.tsx @@ -0,0 +1,36 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const WHISUBTLE_VIS: FeatureToggle = { + name: 'Allow ghosts to see whispers/subtles', + category: 'GHOST', + description: 'Enables ghosts to see your whispers and subtle emotes.', + component: CheckboxInput, +}; + +export const GHOST_SEE_WHISUBTLE: FeatureToggle = { + name: 'See whispers/subtles as ghost', + category: 'GHOST', + description: 'As a ghost, see whispers and subtles.', + component: CheckboxInput, +}; + +export const CHAT_GHOSTEARS: FeatureToggle = { + name: 'Ghost Ears', + category: 'GHOST', + description: 'When enabled, hear all speech; otherwise, only hear nearby.', + component: CheckboxInput, +}; + +export const CHAT_GHOSTSIGHT: FeatureToggle = { + name: 'Ghost Sight', + category: 'GHOST', + description: 'When enabled, hear all emotes; otherwise, only hear nearby.', + component: CheckboxInput, +}; + +export const CHAT_GHOSTRADIO: FeatureToggle = { + name: 'Ghost Radio', + category: 'GHOST', + description: 'When enabled, hear all radio; otherwise, only hear nearby.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/misc.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/misc.tsx new file mode 100644 index 0000000000..33e63600a4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/misc.tsx @@ -0,0 +1,74 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const AMBIENT_OCCLUSION_PREF: FeatureToggle = { + name: 'Enable ambient occlusion', + category: 'GAMEPLAY', + description: 'Enable ambient occlusion, light shadows around characters.', + component: CheckboxInput, +}; + +export const MOB_TOOLTIPS: FeatureToggle = { + name: 'Enable mob tooltips', + category: 'GAMEPLAY', + description: 'Enable tooltips when hovering over mobs.', + component: CheckboxInput, +}; + +export const INV_TOOLTIPS: FeatureToggle = { + name: 'Enable inventory tooltips', + category: 'GAMEPLAY', + description: 'Enable tooltips when hovering over inventory items.', + component: CheckboxInput, +}; + +export const ATTACK_ICONS: FeatureToggle = { + name: 'Attack Icons', + category: 'GAMEPLAY', + description: + 'Enable showing an overlay of what a mob was hit with during the attack animation.', + component: CheckboxInput, +}; + +export const PRECISE_PLACEMENT: FeatureToggle = { + name: 'Precision Placement', + category: 'GAMEPLAY', + description: + 'Objects placed on table will be on cursor position when enabled, or centered when disabled.', + component: CheckboxInput, +}; + +export const HUD_HOTKEYS: FeatureToggle = { + name: 'Hotkeys Default', + category: 'GAMEPLAY', + description: 'Enables turning hotkey mode on by default.', + component: CheckboxInput, +}; + +export const SHOW_PROGRESS: FeatureToggle = { + name: 'Progress Bar', + category: 'GAMEPLAY', + description: 'Enables seeing progress bars for various actions.', + component: CheckboxInput, +}; + +export const SAFE_FIRING: FeatureToggle = { + name: 'Gun Firing Intent Requirement', + category: 'GAMEPLAY', + description: 'When enabled, firing a gun requires a non-help intent to fire.', + component: CheckboxInput, +}; + +export const SHOW_STATUS: FeatureToggle = { + name: 'Status Indicators', + category: 'GAMEPLAY', + description: "Enables seeing status indicators over people's heads.", + component: CheckboxInput, +}; + +export const AUTO_AFK: FeatureToggle = { + name: 'Automatic AFK Status', + category: 'GAMEPLAY', + description: + 'When enabled, you will automatically be marked as AFK if you are idle for too long.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/runechat.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/runechat.tsx new file mode 100644 index 0000000000..832c3d79fe --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/runechat.tsx @@ -0,0 +1,29 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const RUNECHAT_MOB: FeatureToggle = { + name: 'Runechat: Mobs', + category: 'RUNECHAT', + description: 'Chat messages will show above heads.', + component: CheckboxInput, +}; + +export const RUNECHAT_OBJ: FeatureToggle = { + name: 'Runechat: Objects', + category: 'RUNECHAT', + description: 'Chat messages will show above objects when they speak.', + component: CheckboxInput, +}; + +export const RUNECHAT_BORDER: FeatureToggle = { + name: 'Runechat: Letter Borders', + category: 'RUNECHAT', + description: 'Enables a border around each letter in a runechat message.', + component: CheckboxInput, +}; + +export const RUNECHAT_LONG: FeatureToggle = { + name: 'Runechat: Long Messages', + category: 'RUNECHAT', + description: 'Sets runechat to show more characters.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/sound.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/sound.tsx new file mode 100644 index 0000000000..937fe35486 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/sound.tsx @@ -0,0 +1,156 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const SOUND_MIDI: FeatureToggle = { + name: 'Play Admin MIDIs', + category: 'SOUNDS', + description: 'Enable hearing admin played sounds.', + component: CheckboxInput, +}; + +export const SOUND_LOBBY: FeatureToggle = { + name: 'Play Lobby Music', + category: 'SOUNDS', + description: 'Enable hearing lobby music.', + component: CheckboxInput, +}; + +export const SOUND_AMBIENCE: FeatureToggle = { + name: 'Play Ambience', + category: 'SOUNDS', + description: 'Enable hearing ambient sounds and music.', + component: CheckboxInput, +}; + +export const SOUND_JUKEBOX: FeatureToggle = { + name: 'Play Jukebox Music', + category: 'SOUNDS', + description: 'Enable hearing music from the jukebox.', + component: CheckboxInput, +}; + +export const SOUND_INSTRUMENT: FeatureToggle = { + name: 'Hear In-game Instruments', + category: 'SOUNDS', + description: 'Enable hearing instruments playing.', + component: CheckboxInput, +}; + +export const EMOTE_NOISES: FeatureToggle = { + name: 'Emote Noises', + category: 'SOUNDS', + description: 'Enable hearing noises from emotes.', + component: CheckboxInput, +}; + +export const RADIO_SOUNDS: FeatureToggle = { + name: 'Radio Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when somebody speaks over your headset.', + component: CheckboxInput, +}; + +export const SAY_SOUNDS: FeatureToggle = { + name: 'Say Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when somebody speaks using say.', + component: CheckboxInput, +}; + +export const EMOTE_SOUNDS: FeatureToggle = { + name: 'Me Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when somebody uses me.', + component: CheckboxInput, +}; + +export const WHISPER_SOUNDS: FeatureToggle = { + name: 'Whisper Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when somebody speaks using whisper.', + component: CheckboxInput, +}; + +export const SUBTLE_SOUNDS: FeatureToggle = { + name: 'Subtle Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when somebody uses subtle.', + component: CheckboxInput, +}; + +export const SOUND_AIRPUMP: FeatureToggle = { + name: 'Air Pump Ambient Noise', + category: 'SOUNDS', + description: 'Enable hearing air vent humming.', + component: CheckboxInput, +}; + +export const SOUND_OLDDOORS: FeatureToggle = { + name: 'Old Door Sounds', + category: 'SOUNDS', + description: 'Switch to old door sounds.', + component: CheckboxInput, +}; + +export const SOUND_DEPARTMENTDOORS: FeatureToggle = { + name: 'Department Door Sounds', + category: 'SOUNDS', + description: 'Enable hearing department-specific door sounds.', + component: CheckboxInput, +}; + +export const SOUND_PICKED: FeatureToggle = { + name: 'Picked-Up Item Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when items are picked up.', + component: CheckboxInput, +}; + +export const SOUND_DROPPED: FeatureToggle = { + name: 'Dropped Item Sounds', + category: 'SOUNDS', + description: 'Enable hearing a sound when items are dropped.', + component: CheckboxInput, +}; + +export const SOUND_WEATHER: FeatureToggle = { + name: 'Weather Sounds', + category: 'SOUNDS', + description: 'Enable hearing weather sounds while on a planet.', + component: CheckboxInput, +}; + +export const SOUND_SUPERMATTER: FeatureToggle = { + name: 'Supermatter Hum', + category: 'SOUNDS', + description: 'Enable hearing supermatter hums.', + component: CheckboxInput, +}; + +export const SOUND_MENTORHELP: FeatureToggle = { + name: 'Mentorhelp Pings', + category: 'SOUNDS', + description: 'Enable hearing mentorhelp pings.', + component: CheckboxInput, +}; + +// Vorey sounds +export const BELCH_NOISES: FeatureToggle = { + name: 'Belch Noises', + category: 'SOUNDS', + description: 'Enable hearing burping noises.', + component: CheckboxInput, +}; + +export const EATING_NOISES: FeatureToggle = { + name: 'Eating Noises', + category: 'SOUNDS', + description: 'Enable hearing vore eating noises.', + component: CheckboxInput, +}; + +export const DIGEST_NOISES: FeatureToggle = { + name: 'Digestion Noises', + category: 'SOUNDS', + description: 'Enable hearing vore digestion noises.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx new file mode 100644 index 0000000000..7292b65de7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx @@ -0,0 +1,29 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const BROWSER_STYLED: FeatureToggle = { + name: 'Use Fake NanoUI Browser Style', + category: 'UI', + description: 'Enable a dark fake NanoUI browser style for older UIs.', + component: CheckboxInput, +}; + +export const VCHAT_ENABLE: FeatureToggle = { + name: 'Enable TGChat', + category: 'UI', + description: 'Enable the TGChat chat panel.', + component: CheckboxInput, +}; + +export const TGUI_SAY: FeatureToggle = { + name: 'Say: Use TGUI', + category: 'UI', + description: 'Use TGUI for Say input.', + component: CheckboxInput, +}; + +export const TGUI_SAY_LIGHT_MODE: FeatureToggle = { + name: 'Say: Light mode', + category: 'UI', + description: 'Sets TGUI Say to use a light mode.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts new file mode 100644 index 0000000000..8b49759534 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts @@ -0,0 +1,37 @@ +// Unlike species and others, feature files export arrays of features +// rather than individual ones. This is because a lot of features are +// extremely small, and so it's easier for everyone to just combine them +// together. +// This still helps to prevent the server from needing to send client UI data +import { Feature } from './base'; + +// while also preventing downstreams from needing to mutate existing files. +const features: Record> = {}; + +const requireFeature = require.context('./', true, /.tsx$/); + +for (const key of requireFeature.keys()) { + if (key === 'index' || key === 'base') { + continue; + } + + for (const [featureKey, feature] of Object.entries(requireFeature(key))) { + features[featureKey] = feature as Feature; + } +} + +// CHOMPAdd start for modularity +const requireAdditions = require.context( + '../../../chompstation/PreferencesMenu/preferences/features', + true, + /.tsx$/, +); + +for (const key of requireAdditions.keys()) { + for (const [featureKey, feature] of Object.entries(requireAdditions(key))) { + features[featureKey] = feature as Feature; + } +} +// CHOMPAdd end + +export default features; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/gender.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/gender.ts new file mode 100644 index 0000000000..baac865559 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/gender.ts @@ -0,0 +1,28 @@ +export enum Gender { + Male = 'male', + Female = 'female', + Other = 'plural', + Other2 = 'neuter', +} + +export const GENDERS = { + [Gender.Male]: { + icon: 'mars', + text: 'He/Him', + }, + + [Gender.Female]: { + icon: 'venus', + text: 'She/Her', + }, + + [Gender.Other]: { + icon: 'transgender', + text: 'They/Them', + }, + + [Gender.Other2]: { + icon: 'neuter', + text: 'It/Its', + }, +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/PreferencesMenu/preferences/features/game_preferences/misc.tsx b/tgui/packages/tgui/interfaces/chompstation/PreferencesMenu/preferences/features/game_preferences/misc.tsx new file mode 100644 index 0000000000..01ed68f1e7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/PreferencesMenu/preferences/features/game_preferences/misc.tsx @@ -0,0 +1,18 @@ +import { + CheckboxInput, + FeatureToggle, +} from '../../../../../PreferencesMenu/preferences/features/base'; + +export const EMOTE_VARY: FeatureToggle = { + name: 'Random emote pitch', + category: 'GAMEPLAY', + description: 'Enable random emote pitch.', + component: CheckboxInput, +}; + +export const AUTOTRANSCORE: FeatureToggle = { + name: 'Automatic transcore notification', + category: 'GAMEPLAY', + description: 'Enable automatic transcore notification.', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/PreferencesMenu/preferences/features/game_preferences/sound.tsx b/tgui/packages/tgui/interfaces/chompstation/PreferencesMenu/preferences/features/game_preferences/sound.tsx new file mode 100644 index 0000000000..3e0f8e78ad --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/PreferencesMenu/preferences/features/game_preferences/sound.tsx @@ -0,0 +1,25 @@ +import { + CheckboxInput, + FeatureToggle, +} from '../../../../../PreferencesMenu/preferences/features/base'; + +export const SOUND_ALARMLOOP: FeatureToggle = { + name: 'Looping Alarm Sounds', + category: 'SOUNDS', + description: 'Enable looping alarm sounds.', + component: CheckboxInput, +}; + +export const SOUND_FRIDGEHUM: FeatureToggle = { + name: 'Fridge Humming', + category: 'SOUNDS', + description: 'Enable fridge humming.', + component: CheckboxInput, +}; + +export const SLEEP_MUSIC: FeatureToggle = { + name: 'Sleeping Music', + category: 'SOUNDS', + description: 'Enable sleeping music.', + component: CheckboxInput, +}; diff --git a/vorestation.dme b/vorestation.dme index 8389ad70f6..8da7391b35 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -442,6 +442,7 @@ #include "code\datums\ghost_query.dm" #include "code\datums\ghost_query_vr.dm" #include "code\datums\hierarchy.dm" +#include "code\datums\json_savefile.dm" #include "code\datums\mind.dm" #include "code\datums\mind_vr.dm" #include "code\datums\mixed.dm" @@ -2034,6 +2035,7 @@ #include "code\modules\asset_cache\assets\fontawesome.dm" #include "code\modules\asset_cache\assets\icon_ref_map.dm" #include "code\modules\asset_cache\assets\jquery.dm" +#include "code\modules\asset_cache\assets\preferences.dm" #include "code\modules\asset_cache\assets\tgfont.dm" #include "code\modules\asset_cache\assets\tgui.dm" #include "code\modules\asset_cache\transports\asset_transport.dm" @@ -2104,10 +2106,10 @@ #include "code\modules\client\client procs_vr.dm" #include "code\modules\client\movement.dm" #include "code\modules\client\preferences.dm" -#include "code\modules\client\preferences_ch.dm" #include "code\modules\client\preferences_factions.dm" #include "code\modules\client\preferences_savefile.dm" #include "code\modules\client\preferences_spawnpoints.dm" +#include "code\modules\client\preferences_tgui.dm" #include "code\modules\client\preferences_toggle_procs.dm" #include "code\modules\client\preferences_vr.dm" #include "code\modules\client\preferences_yw.dm" @@ -2129,7 +2131,6 @@ #include "code\modules\client\preference_setup\global\02_settings.dm" #include "code\modules\client\preference_setup\global\03_pai.dm" #include "code\modules\client\preference_setup\global\04_ooc.dm" -#include "code\modules\client\preference_setup\global\setting_datums.dm" #include "code\modules\client\preference_setup\loadout\gear_tweaks.dm" #include "code\modules\client\preference_setup\loadout\gear_tweaks_vr.dm" #include "code\modules\client\preference_setup\loadout\loadout.dm" @@ -2185,6 +2186,18 @@ #include "code\modules\client\preference_setup\vore\07_traits.dm" #include "code\modules\client\preference_setup\vore\08_nif.dm" #include "code\modules\client\preference_setup\vore\09_misc.dm" +#include "code\modules\client\preferences\_preference.dm" +#include "code\modules\client\preferences\preferences_tg.dm" +#include "code\modules\client\preferences\middleware\_middleware.dm" +#include "code\modules\client\preferences\migrations\13_preferences.dm" +#include "code\modules\client\preferences\migrations\14_nifs.dm" +#include "code\modules\client\preferences\types\admin.dm" +#include "code\modules\client\preferences\types\chat.dm" +#include "code\modules\client\preferences\types\ghost.dm" +#include "code\modules\client\preferences\types\misc.dm" +#include "code\modules\client\preferences\types\runechat.dm" +#include "code\modules\client\preferences\types\sound.dm" +#include "code\modules\client\preferences\types\ui.dm" #include "code\modules\client\verbs\advanced_who.dm" #include "code\modules\client\verbs\character_directory.dm" #include "code\modules\client\verbs\ignore.dm" @@ -4793,7 +4806,6 @@ #include "modular_chomp\code\modules\casino\casino_map_atoms.dm" #include "modular_chomp\code\modules\client\preferences.dm" #include "modular_chomp\code\modules\client\preferences_spawnpoints.dm" -#include "modular_chomp\code\modules\client\preference_setup\global\setting_datums.dm" #include "modular_chomp\code\modules\client\preference_setup\loadout\gear_tweaks.dm" #include "modular_chomp\code\modules\client\preference_setup\loadout\loadout_accessories.dm" #include "modular_chomp\code\modules\client\preference_setup\loadout\loadout_general.dm" @@ -4802,6 +4814,8 @@ #include "modular_chomp\code\modules\client\preference_setup\loadout\loadout_shoes.dm" #include "modular_chomp\code\modules\client\preference_setup\loadout\loadout_suit.dm" #include "modular_chomp\code\modules\client\preference_setup\loadout\loadout_xeno.dm" +#include "modular_chomp\code\modules\client\preferences\types\misc.dm" +#include "modular_chomp\code\modules\client\preferences\types\sound.dm" #include "modular_chomp\code\modules\client\verbs\ping.dm" #include "modular_chomp\code\modules\clothing\clothing.dm" #include "modular_chomp\code\modules\clothing\clothing_icons.dm"