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 ""
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
-
- . += "