Revert "Merge pull request #16240 from ShadowLarkens/revert"

This reverts commit 885171f985, reversing
changes made to 3e314f8064.
This commit is contained in:
ShadowLarkens
2024-09-07 14:11:15 -07:00
parent 49dda94177
commit f9052dbb60
186 changed files with 5029 additions and 2255 deletions
+215 -92
View File
@@ -1,121 +1,244 @@
#define SAVEFILE_VERSION_MIN 8
#define SAVEFILE_VERSION_MAX 11
#define SAVEFILE_VERSION_MAX 13
//handles converting savefiles to new formats
//MAKE SURE YOU KEEP THIS UP TO DATE!
//If the sanity checks are capable of handling any issues. Only increase SAVEFILE_VERSION_MAX,
//this will mean that savefile_version will still be over SAVEFILE_VERSION_MIN, meaning
//this savefile update doesn't run everytime we load from the savefile.
//This is mainly for format changes, such as the bitflags in toggles changing order or something.
//if a file can't be updated, return 0 to delete it and start again
//if a file was updated, return 1
/datum/preferences/proc/savefile_update()
if(savefile_version < 8) //lazily delete everything + additional files so they can be saved in the new format
for(var/ckey in preferences_datums)
var/datum/preferences/D = preferences_datums[ckey]
if(D == src)
var/delpath = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/"
if(delpath && fexists(delpath))
fdel(delpath)
break
return 0
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
This proc checks if the current directory of the savefile S needs updating
It is to be used by the load_character and load_preferences procs.
(S.cd == "/" is preferences, S.cd == "/character[integer]" is a character slot, etc)
if(savefile_version == SAVEFILE_VERSION_MAX) //update successful.
save_preferences()
save_character()
return 1
return 0
if the current directory's version is below SAVEFILE_VERSION_MIN it will simply wipe everything in that directory
(if we're at root "/" then it'll just wipe the entire savefile, for instance.)
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey) return
if its version is below SAVEFILE_VERSION_MAX but above the minimum, it will load data but later call the
respective update_preferences() or update_character() proc.
Those procs allow coders to specify format changes so users do not lose their setups and have to redo them again.
Failing all that, the standard sanity checks are performed. They simply check the data is suitable, reverting to
initial() values if necessary.
*/
/datum/preferences/proc/save_data_needs_update(list/save_data)
if(!save_data) // empty list, either savefile isnt loaded or its a new char
return -1
if(!save_data["version"]) // special case: if there is no version key, such as in character slots before v12
return -3
if(save_data["version"] < SAVEFILE_VERSION_MIN)
return -2
if(save_data["version"] < SAVEFILE_VERSION_MAX)
return save_data["version"]
return -1
//should these procs get fairly long
//just increase SAVEFILE_VERSION_MIN so it's not as far behind
//SAVEFILE_VERSION_MAX and then delete any obsolete if clauses
//from these procs.
//This only really meant to avoid annoying frequent players
//if your savefile is 3 months out of date, then 'tough shit'.
/datum/preferences/proc/update_preferences(current_version, datum/json_savefile/S)
// Migration from BYOND savefiles to JSON: Important milemark.
// if(current_version < 11)
// Migration for client preferences
if(current_version < 13)
migration_13_preferences(S)
/datum/preferences/proc/update_character(current_version, list/save_data)
// Migration from BYOND savefiles to JSON: Important milemark.
if(current_version == -3)
// Add a version field inside each character
save_data["version"] = SAVEFILE_VERSION_MAX
/// Migrates from byond savefile to json savefile
/datum/preferences/proc/try_savefile_type_migration()
load_path(client.ckey, "preferences.sav") // old save file
var/old_path = path
load_path(client.ckey)
if(!fexists(old_path))
return
var/datum/json_savefile/json_savefile = new(path)
json_savefile.import_byond_savefile(new /savefile(old_path))
json_savefile.save()
return TRUE
/datum/preferences/proc/load_path(ckey, filename = "preferences.json")
if(!ckey || !load_and_save)
return
path = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/[filename]"
savefile_version = SAVEFILE_VERSION_MAX
/datum/preferences/proc/load_savefile()
if(load_and_save && !path)
CRASH("Attempted to load savefile without first loading a path!")
savefile = new /datum/json_savefile(load_and_save ? path : null)
/datum/preferences/proc/load_preferences()
if(!path) return 0
if(!fexists(path)) return 0
var/savefile/S = new /savefile(path)
if(!S) return 0
S.cd = "/"
if(!savefile)
stack_trace("Attempted to load the preferences of [client] without a savefile; did you forget to call load_savefile?")
load_savefile()
if(!savefile)
stack_trace("Failed to load the savefile for [client] after manually calling load_savefile; something is very wrong.")
return FALSE
S["version"] >> savefile_version
//Conversion
if(!savefile_version || !isnum(savefile_version) || savefile_version < SAVEFILE_VERSION_MIN || savefile_version > SAVEFILE_VERSION_MAX)
if(!savefile_update()) //handles updates
savefile_version = SAVEFILE_VERSION_MAX
save_preferences()
save_character()
return 0
var/needs_update = save_data_needs_update(savefile.get_entry())
if(load_and_save && (needs_update <= -2)) //fatal, can't load any data
var/bacpath = "[path].updatebac" //todo: if the savefile version is higher then the server, check the backup, and give the player a prompt to load the backup
if(fexists(bacpath))
fdel(bacpath) //only keep 1 version of backup
fcopy(savefile.path, bacpath) //byond helpfully lets you use a savefile for the first arg.
return FALSE
player_setup.load_preferences(S)
return 1
apply_all_client_preferences()
//try to fix any outdated data if necessary
if(needs_update >= 0)
var/bacpath = "[path].updatebac" //todo: if the savefile version is higher then the server, check the backup, and give the player a prompt to load the backup
if(fexists(bacpath))
fdel(bacpath) //only keep 1 version of backup
fcopy(savefile.path, bacpath) //byond helpfully lets you use a savefile for the first arg.
update_preferences(needs_update, savefile) //needs_update = savefile_version if we need an update (positive integer)
// Load general prefs after applying migrations
player_setup.load_preferences(savefile)
//save the updated version
var/old_default_slot = default_slot
// var/old_max_save_slots = max_save_slots
for(var/slot in savefile.get_entry()) //but first, update all current character slots.
if (copytext(slot, 1, 10) != "character")
continue
var/slotnum = text2num(copytext(slot, 10))
if (!slotnum)
continue
// max_save_slots = max(max_save_slots, slotnum) //so we can still update byond member slots after they lose memeber status
default_slot = slotnum
if(load_character())
save_character()
default_slot = old_default_slot
// max_save_slots = old_max_save_slots
save_preferences()
else
// Load general prefs
player_setup.load_preferences(savefile)
return TRUE
/datum/preferences/proc/save_preferences()
if(!path) return 0
var/savefile/S = new /savefile(path)
if(!S) return 0
S.cd = "/"
if(!savefile)
CRASH("Attempted to save the preferences of [client] without a savefile. This should have been handled by load_preferences()")
savefile.set_entry("version", SAVEFILE_VERSION_MAX) //updates (or failing that the sanity checks) will ensure data is not invalid at load. Assume up-to-date
S["version"] << savefile_version
player_setup.save_preferences(S)
return 1
player_setup.save_preferences(savefile)
for(var/preference_type in GLOB.preference_entries)
var/datum/preference/preference = GLOB.preference_entries[preference_type]
if(preference.savefile_identifier != PREFERENCE_PLAYER)
continue
if(!(preference.type in recently_updated_keys))
continue
recently_updated_keys -= preference.type
if(preference_type in value_cache)
write_preference(preference, preference.pref_serialize(value_cache[preference_type]))
savefile.save()
return TRUE
/datum/preferences/proc/reset_slot()
var/bacpath = "[path].resetbac"
if(fexists(bacpath))
fdel(bacpath) //only keep 1 version of backup
fcopy(savefile.path, bacpath) //byond helpfully lets you use a savefile for the first arg.
savefile.remove_entry("character[default_slot]")
default_slot = 1
clear_character_previews()
// Load slot 1 character
load_character()
// And save them immediately, in case we load an empty slot
save_character()
save_preferences()
return TRUE
/datum/preferences/proc/load_character(slot)
if(!path) return 0
if(!fexists(path)) return 0
var/savefile/S = new /savefile(path)
if(!S) return 0
S.cd = "/"
if(!slot) slot = default_slot
if(slot != SAVE_RESET) // SAVE_RESET will reset the slot as though it does not exist, but keep the current slot for saving purposes.
slot = sanitize_integer(slot, 1, config.character_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
S["default_slot"] << slot
else
S["default_slot"] << default_slot
SHOULD_NOT_SLEEP(TRUE)
if(!slot)
slot = default_slot
if(slot != SAVE_RESET)
S.cd = "/character[slot]"
player_setup.load_character(S)
else
player_setup.load_character(S)
S.cd = "/character[default_slot]"
player_setup.save_character(S)
slot = sanitize_integer(slot, 1, config.character_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
savefile.set_entry("default_slot", slot)
clear_character_previews() // VOREStation Edit
return 1
var/list/save_data = savefile.get_entry("character[slot]") // This is allowed to be null and will give a -1 in needs_update
var/needs_update = save_data_needs_update(save_data)
if(needs_update == -2) //fatal, can't load any data
return FALSE
// Read everything into cache (pre-migrations, as migrations should have access to deserialized data)
// Uses priority order as some values may rely on others for creating default values
for(var/datum/preference/preference as anything in get_preferences_in_priority_order())
if(preference.savefile_identifier != PREFERENCE_CHARACTER)
continue
value_cache -= preference.type
read_preference(preference.type)
// It has to be a list or load_character freaks out
if(!save_data)
player_setup.load_character(list())
else
player_setup.load_character(save_data)
//try to fix any outdated data if necessary
//preference updating will handle saving the updated data for us.
if(needs_update >= 0 || needs_update == -3)
update_character(needs_update, save_data) //needs_update == savefile_version if we need an update (positive integer
clear_character_previews()
return TRUE
/datum/preferences/proc/save_character()
if(!path) return 0
var/savefile/S = new /savefile(path)
if(!S) return 0
S.cd = "/character[default_slot]"
SHOULD_NOT_SLEEP(TRUE)
if(!savefile)
return FALSE
player_setup.save_character(S)
return 1
var/tree_key = "character[default_slot]"
if(!(tree_key in savefile.get_entry()))
savefile.set_entry(tree_key, list())
var/save_data = savefile.get_entry(tree_key)
save_data["version"] = SAVEFILE_VERSION_MAX //load_character will sanitize any bad data, so assume up-to-date.
player_setup.save_character(save_data)
return TRUE
/datum/preferences/proc/overwrite_character(slot)
if(!path) return 0
if(!fexists(path)) return 0
var/savefile/S = new /savefile(path)
if(!S) return 0
if(!slot) slot = default_slot
if(slot != SAVE_RESET)
slot = sanitize_integer(slot, 1, config.character_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
nif_path = nif_durability = nif_savedata = null //VOREStation Add - Don't copy NIF
S["default_slot"] << slot
if(!savefile)
return FALSE
if(!slot)
slot = default_slot
else
S["default_slot"] << default_slot
// This basically just changes default_slot without loading the correct data, so the next save call will overwrite
// the slot
slot = sanitize_integer(slot, 1, config.character_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
nif_path = nif_durability = nif_savedata = null //VOREStation Add - Don't copy NIF
savefile.set_entry("default_slot", slot)
return 1
return TRUE
/datum/preferences/proc/sanitize_preferences()
player_setup.sanitize_setup()
return 1
return TRUE
#undef SAVEFILE_VERSION_MAX
#undef SAVEFILE_VERSION_MIN