From c2b8ef1d091beee8954e6ca273df7af061e9648d Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:36:56 -0500 Subject: [PATCH] Allow localhosts to set "dev override preferences" to load a specific preference savefile for guests (#92770) ## About The Pull Request A verb is now available on localhost called `"Export Save as Dev Preferences"` This exports your current savefile to `/config/dev_preferences.json` If you then connect to your localhost as a guest, it will load `dev_preferences.json` as your preference datum This allows for devs testing the game locally to load preferences for guests. (Guests connecting to live servers are completely unaffected.) ## Why It's Good For The Game Initially I only did this because the recent keybinding changes have destroyed my muscle memory when testing w/o logging in. But as I worked on it I thought of a few other usecases, like when implementing preference version migration - the dev preference is never saved which means you can re-compile as much as you want without needing to revert your save manually. --- .gitignore | 1 + code/__DEFINES/admin_verb.dm | 1 + code/__DEFINES/preferences.dm | 3 +++ code/controllers/subsystem/admin_verbs.dm | 2 ++ code/datums/json_savefile.dm | 4 ++++ code/modules/admin/verbs/debug.dm | 18 ++++++++++++++++++ code/modules/client/preferences.dm | 9 +++++++-- code/modules/client/preferences_savefile.dm | 4 ++++ 8 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 87c5006457e..640ac9e61d8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Local overrides--you can use this to change the config for your dev environment (like setting up SQL) without worrying about committing it /config/dev_overrides.txt +/config/dev_preferences.json #Ignore everything in datafolder and subdirectories /data/**/* diff --git a/code/__DEFINES/admin_verb.dm b/code/__DEFINES/admin_verb.dm index ae8c75b0588..71abb07f8a0 100644 --- a/code/__DEFINES/admin_verb.dm +++ b/code/__DEFINES/admin_verb.dm @@ -92,3 +92,4 @@ _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_categor // Visibility flags #define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug" +#define ADMIN_VERB_VISIBLITY_FLAG_LOCALHOST "Localhost" diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 8d9e911e9ae..ff375d5b770 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -38,6 +38,9 @@ #define TOGGLES_DEFAULT_CHAT (CHAT_OOC|CHAT_DEAD|CHAT_PRAYER|CHAT_PULLR|CHAT_GHOSTPDA|CHAT_GHOSTRADIO|CHAT_BANKCARD|CHAT_GHOSTLAWS|CHAT_LOGIN_LOGOUT) +/// File path to the dev preference json file, which is loaded by guests while localhosting. +#define DEV_PREFS_PATH "config/dev_preferences.json" + #define PARALLAX_INSANE "Insane" #define PARALLAX_HIGH "High" #define PARALLAX_MED "Medium" diff --git a/code/controllers/subsystem/admin_verbs.dm b/code/controllers/subsystem/admin_verbs.dm index 74b86a3c5cb..732508f0f8a 100644 --- a/code/controllers/subsystem/admin_verbs.dm +++ b/code/controllers/subsystem/admin_verbs.dm @@ -132,6 +132,8 @@ SUBSYSTEM_DEF(admin_verbs) // refresh their verbs admin_visibility_flags[admin.ckey] ||= list() + if(admin.is_localhost()) + admin_visibility_flags[admin.ckey] |= list(ADMIN_VERB_VISIBLITY_FLAG_LOCALHOST) for(var/datum/admin_verb/verb_singleton as anything in get_valid_verbs_for_admin(admin)) verb_singleton.assign_to_client(admin) admin.init_verbs() diff --git a/code/datums/json_savefile.dm b/code/datums/json_savefile.dm index f68efd432d1..1897c5a8301 100644 --- a/code/datums/json_savefile.dm +++ b/code/datums/json_savefile.dm @@ -118,3 +118,7 @@ GENERAL_PROTECT_DATUM(/datum/json_savefile) return TRUE return FALSE + +/// Copies the entire tree to another json savefile datum, overwriting whatever was in the other datum before. +/datum/json_savefile/proc/copy_to_savefile(datum/json_savefile/other_savefile) + other_savefile.tree = tree.Copy() diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 3a6667af6d1..a981a5c3e74 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -1031,3 +1031,21 @@ ADMIN_VERB(count_instances, R_DEBUG, "Count Atoms/Datums", "Count how many atom . = list() CRASH("count_datums not supported on OpenDream") #endif + +ADMIN_VERB_VISIBILITY(export_save_to_dev_preference, ADMIN_VERB_VISIBLITY_FLAG_LOCALHOST) +ADMIN_VERB(export_save_to_dev_preference, R_DEBUG, "Export Save as Dev Preferences", "Exports your savefile to be used by any guests that connect to your localost.", ADMIN_CATEGORY_SERVER) + if(!user.is_localhost()) + tgui_alert(user, "You shouldn't be using this right now!", "Export Failed", list("OK")) + log_admin("[key_name(user)] attempted to export preferences to [DEV_PREFS_PATH] - this is normally locked to localhost only!") + stack_trace("Export Save as Dev Preferences was called by a non-localhost user!") + return + if(is_guest_key(user.key)) + tgui_alert(user, "Guests don't have preferences to export.", "Export Failed", list("OK")) + return + var/datum/preferences/user_prefs = user.prefs + var/datum/json_savefile/dev_save = new(DEV_PREFS_PATH) + user_prefs.save_preferences() + user_prefs.savefile.copy_to_savefile(dev_save) + dev_save.save() + tgui_alert(user, "Exported preferences to [DEV_PREFS_PATH]. \ + Next time you localhost as a guest it will use this savefile as-is.", "Export Complete", list("OK thanks")) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index aef4119d151..740d99c1f21 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -101,8 +101,13 @@ GLOBAL_LIST_EMPTY(preferences_datums) middleware += new middleware_type(src) if(IS_CLIENT_OR_MOCK(parent)) - load_and_save = !is_guest_key(parent.key) - load_path(parent.ckey) + if(is_guest_key(parent.key)) + if(parent.is_localhost()) + path = DEV_PREFS_PATH // guest + locallost = dev instance, load dev preferences if possible + else + load_and_save = FALSE // guest + not localhost = guest on live, don't save anything + else + load_path(parent.ckey) // not guest = load their actual savefile if(load_and_save && !fexists(path)) try_savefile_type_migration() diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 562ceae9c59..97801af313a 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -275,6 +275,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car /datum/preferences/proc/save_preferences() if(!savefile) CRASH("Attempted to save the preferences of [parent] without a savefile. This should have been handled by load_preferences()") + if(path == DEV_PREFS_PATH) + // Don't save over dev preferences + return TRUE + 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 for (var/preference_type in GLOB.preference_entries)