mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 07:46:20 +00:00
## 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.
125 lines
4.9 KiB
Plaintext
125 lines
4.9 KiB
Plaintext
/**
|
|
* 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)
|
|
|
|
/datum/json_savefile/serialize_list(list/options, list/semvers)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
SET_SERIALIZATION_SEMVER(semvers, "1.0.0")
|
|
return tree.Copy()
|
|
|
|
/// 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)))
|
|
var/file_name = "[account_name ? "[account_name]_" : ""]preferences_[time2text(world.timeofday, "MMM_DD_YYYY_hh-mm-ss", TIMEZONE_UTC)].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
|
|
|
|
/// 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()
|