mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
* Makes TTS blips/enabled into one pref (#76558) ## About The Pull Request  I actually do not know how to test this locally as I don't know how to set TTS up, hope it works. ## Why It's Good For The Game It's 2 buttons, one only works depending on the other, I don't think it makes sense. ## Changelog 🆑 qol: The TTS and TTS Blips option is now one choiced preference. /🆑 * Makes TTS blips/enabled into one pref --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
387 lines
14 KiB
Plaintext
387 lines
14 KiB
Plaintext
//This is the lowest supported version, anything below this is completely obsolete and the entire savefile will be wiped.
|
|
#define SAVEFILE_VERSION_MIN 32
|
|
|
|
//This is the current version, anything below this will attempt to update (if it's not obsolete)
|
|
// You do not need to raise this if you are adding new values that have sane defaults.
|
|
// Only raise this value when changing the meaning/format/name/layout of an existing value
|
|
// where you would want the updater procs below to run
|
|
#define SAVEFILE_VERSION_MAX 44
|
|
|
|
/*
|
|
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 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.)
|
|
|
|
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"] < 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)
|
|
if(current_version < 34)
|
|
write_preference(/datum/preference/toggle/auto_fit_viewport, TRUE)
|
|
|
|
if(current_version < 35) //makes old keybinds compatible with #52040, sets the new default
|
|
var/newkey = FALSE
|
|
for(var/list/key in key_bindings)
|
|
for(var/bind in key)
|
|
if(bind == "quick_equipbelt")
|
|
key -= "quick_equipbelt"
|
|
key |= "quick_equip_belt"
|
|
|
|
if(bind == "bag_equip")
|
|
key -= "bag_equip"
|
|
key |= "quick_equip_bag"
|
|
|
|
if(bind == "quick_equip_suit_storage")
|
|
newkey = TRUE
|
|
if(!newkey && !key_bindings["ShiftQ"])
|
|
key_bindings["ShiftQ"] = list("quick_equip_suit_storage")
|
|
|
|
if(current_version < 36)
|
|
if(key_bindings["ShiftQ"] == "quick_equip_suit_storage")
|
|
key_bindings["ShiftQ"] = list("quick_equip_suit_storage")
|
|
|
|
if(current_version < 37)
|
|
if(read_preference(/datum/preference/numeric/fps) == 0)
|
|
write_preference(GLOB.preference_entries[/datum/preference/numeric/fps], -1)
|
|
|
|
if (current_version < 38)
|
|
var/found_block_movement = FALSE
|
|
|
|
for (var/list/key in key_bindings)
|
|
for (var/bind in key)
|
|
if (bind == "block_movement")
|
|
found_block_movement = TRUE
|
|
break
|
|
if (found_block_movement)
|
|
break
|
|
|
|
if (!found_block_movement)
|
|
LAZYADD(key_bindings["Ctrl"], "block_movement")
|
|
|
|
if (current_version < 39)
|
|
LAZYADD(key_bindings["F"], "toggle_combat_mode")
|
|
LAZYADD(key_bindings["4"], "toggle_combat_mode")
|
|
if (current_version < 40)
|
|
LAZYADD(key_bindings["Space"], "hold_throw_mode")
|
|
|
|
if (current_version < 41)
|
|
migrate_preferences_to_tgui_prefs_menu()
|
|
|
|
if (current_version < 44)
|
|
update_tts_blip_prefs()
|
|
|
|
/datum/preferences/proc/update_character(current_version, list/save_data)
|
|
if (current_version < 41)
|
|
migrate_character_to_tgui_prefs_menu()
|
|
|
|
if (current_version < 42)
|
|
// migrate_body_types(save_data) // SKYRAT EDIT - This'll fuck up savefiles
|
|
migrate_mentor() // SKYRAT EDIT - Make mentors alive again
|
|
|
|
if (current_version < 43)
|
|
migrate_legacy_sound_toggles(savefile)
|
|
|
|
/// checks through keybindings for outdated unbound keys and updates them
|
|
/datum/preferences/proc/check_keybindings()
|
|
if(!parent)
|
|
return
|
|
var/list/binds_by_key = get_key_bindings_by_key(key_bindings)
|
|
var/list/notadded = list()
|
|
for (var/name in GLOB.keybindings_by_name)
|
|
var/datum/keybinding/kb = GLOB.keybindings_by_name[name]
|
|
if(kb.name in key_bindings)
|
|
continue // key is unbound and or bound to something
|
|
|
|
var/addedbind = FALSE
|
|
key_bindings[kb.name] = list()
|
|
|
|
if(parent.hotkeys)
|
|
for(var/hotkeytobind in kb.hotkey_keys)
|
|
if(hotkeytobind == "Unbound")
|
|
addedbind = TRUE
|
|
else if(!length(binds_by_key[hotkeytobind])) //Only bind to the key if nothing else is bound
|
|
key_bindings[kb.name] |= hotkeytobind
|
|
addedbind = TRUE
|
|
else
|
|
for(var/classickeytobind in kb.classic_keys)
|
|
if(classickeytobind == "Unbound")
|
|
addedbind = TRUE
|
|
else if(!length(binds_by_key[classickeytobind])) //Only bind to the key if nothing else is bound
|
|
key_bindings[kb.name] |= classickeytobind
|
|
addedbind = TRUE
|
|
|
|
if(!addedbind)
|
|
notadded += kb
|
|
save_preferences() //Save the players pref so that new keys that were set to Unbound as default are permanently stored
|
|
if(length(notadded))
|
|
addtimer(CALLBACK(src, PROC_REF(announce_conflict), notadded), 5 SECONDS)
|
|
|
|
/datum/preferences/proc/announce_conflict(list/notadded)
|
|
to_chat(parent, "<span class='warningplain'><b><u>Keybinding Conflict</u></b></span>\n\
|
|
<span class='warningplain'><b>There are new <a href='?src=[REF(src)];open_keybindings=1'>keybindings</a> that default to keys you've already bound. The new ones will be unbound.</b></span>")
|
|
for(var/item in notadded)
|
|
var/datum/keybinding/conflicted = item
|
|
to_chat(parent, span_danger("[conflicted.category]: [conflicted.full_name] needs updating"))
|
|
|
|
/datum/preferences/proc/load_path(ckey, filename="preferences.json")
|
|
if(!ckey || !load_and_save)
|
|
return
|
|
path = "data/player_saves/[ckey[1]]/[ckey]/[filename]"
|
|
|
|
/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(!savefile)
|
|
stack_trace("Attempted to load the preferences of [parent] without a savefile; did you forget to call load_savefile?")
|
|
load_savefile()
|
|
if(!savefile)
|
|
stack_trace("Failed to load the savefile for [parent] after manually calling load_savefile; something is very wrong.")
|
|
return FALSE
|
|
|
|
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
|
|
|
|
apply_all_client_preferences()
|
|
|
|
//general preferences
|
|
lastchangelog = savefile.get_entry("lastchangelog")
|
|
be_special = savefile.get_entry("be_special")
|
|
default_slot = savefile.get_entry("default_slot")
|
|
chat_toggles = savefile.get_entry("chat_toggles")
|
|
toggles = savefile.get_entry("toggles")
|
|
ignoring = savefile.get_entry("ignoring")
|
|
|
|
// OOC commendations
|
|
hearted_until = savefile.get_entry("hearted_until")
|
|
if(hearted_until > world.realtime)
|
|
hearted = TRUE
|
|
//favorite outfits
|
|
favorite_outfits = savefile.get_entry("favorite_outfits")
|
|
|
|
var/list/parsed_favs = list()
|
|
for(var/typetext in favorite_outfits)
|
|
var/datum/outfit/path = text2path(typetext)
|
|
if(ispath(path)) //whatever typepath fails this check probably doesn't exist anymore
|
|
parsed_favs += path
|
|
favorite_outfits = unique_list(parsed_favs)
|
|
|
|
// Custom hotkeys
|
|
key_bindings = savefile.get_entry("key_bindings")
|
|
|
|
//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)
|
|
|
|
check_keybindings() // this apparently fails every time and overwrites any unloaded prefs with the default values, so don't load anything after this line or it won't actually save
|
|
key_bindings_by_key = get_key_bindings_by_key(key_bindings)
|
|
|
|
//Sanitize
|
|
lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog))
|
|
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
|
|
toggles = sanitize_integer(toggles, 0, (2**24)-1, initial(toggles))
|
|
be_special = sanitize_be_special(SANITIZE_LIST(be_special))
|
|
key_bindings = sanitize_keybindings(key_bindings)
|
|
favorite_outfits = SANITIZE_LIST(favorite_outfits)
|
|
|
|
if(needs_update >= 0) //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()
|
|
|
|
return TRUE
|
|
|
|
/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()")
|
|
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)
|
|
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.serialize(value_cache[preference_type]))
|
|
|
|
savefile.set_entry("lastchangelog", lastchangelog)
|
|
savefile.set_entry("be_special", be_special)
|
|
savefile.set_entry("default_slot", default_slot)
|
|
savefile.set_entry("toggles", toggles)
|
|
savefile.set_entry("chat_toggles", chat_toggles)
|
|
savefile.set_entry("ignoring", ignoring)
|
|
savefile.set_entry("key_bindings", key_bindings)
|
|
savefile.set_entry("hearted_until", (hearted_until > world.realtime ? hearted_until : null))
|
|
savefile.set_entry("favorite_outfits", favorite_outfits)
|
|
savefile.save()
|
|
return TRUE
|
|
|
|
/datum/preferences/proc/load_character(slot)
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
if(!slot)
|
|
slot = default_slot
|
|
slot = sanitize_integer(slot, 1, max_save_slots, initial(default_slot))
|
|
if(slot != default_slot)
|
|
default_slot = slot
|
|
savefile.set_entry("default_slot", slot)
|
|
|
|
var/tree_key = "character[slot]"
|
|
var/list/save_data = savefile.get_entry(tree_key)
|
|
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
|
|
for (var/preference_type in GLOB.preference_entries)
|
|
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
|
if (preference.savefile_identifier != PREFERENCE_CHARACTER)
|
|
continue
|
|
|
|
value_cache -= preference_type
|
|
read_preference(preference_type)
|
|
|
|
//Character
|
|
randomise = save_data?["randomise"]
|
|
|
|
//Load prefs
|
|
job_preferences = save_data?["job_preferences"]
|
|
|
|
//Quirks
|
|
all_quirks = save_data?["all_quirks"]
|
|
|
|
load_character_skyrat(save_data) //SKYRAT EDIT ADDITION
|
|
|
|
//try to fix any outdated data if necessary
|
|
//preference updating will handle saving the updated data for us.
|
|
if(needs_update >= 0)
|
|
update_character(needs_update, save_data) //needs_update == savefile_version if we need an update (positive integer)
|
|
|
|
//Sanitize
|
|
randomise = SANITIZE_LIST(randomise)
|
|
job_preferences = SANITIZE_LIST(job_preferences)
|
|
all_quirks = SANITIZE_LIST(all_quirks)
|
|
|
|
//Validate job prefs
|
|
for(var/j in job_preferences)
|
|
if(job_preferences[j] != JP_LOW && job_preferences[j] != JP_MEDIUM && job_preferences[j] != JP_HIGH)
|
|
job_preferences -= j
|
|
|
|
all_quirks = SSquirks.filter_invalid_quirks(SANITIZE_LIST(all_quirks), augments)// SKYRAT EDIT - AUGMENTS+
|
|
validate_quirks()
|
|
|
|
return TRUE
|
|
|
|
/datum/preferences/proc/save_character()
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
if(!path)
|
|
return FALSE
|
|
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)
|
|
|
|
for (var/datum/preference/preference as anything in get_preferences_in_priority_order())
|
|
if (preference.savefile_identifier != PREFERENCE_CHARACTER)
|
|
continue
|
|
|
|
if (!(preference.type in recently_updated_keys))
|
|
continue
|
|
|
|
recently_updated_keys -= preference.type
|
|
|
|
if (preference.type in value_cache)
|
|
write_preference(preference, preference.serialize(value_cache[preference.type]))
|
|
|
|
save_data["version"] = SAVEFILE_VERSION_MAX //load_character will sanitize any bad data, so assume up-to-date.
|
|
|
|
// This is the version when the random security department was removed.
|
|
// When the minimum is higher than that version, it's impossible for someone to have the "Random" department.
|
|
#if SAVEFILE_VERSION_MIN > 40
|
|
#warn The prefered_security_department check in code/modules/client/preferences/security_department.dm is no longer necessary.
|
|
#endif
|
|
|
|
//Character
|
|
save_data["randomise"] = randomise
|
|
|
|
//Write prefs
|
|
save_data["job_preferences"] = job_preferences
|
|
|
|
//Quirks
|
|
save_data["all_quirks"] = all_quirks
|
|
|
|
save_character_skyrat(save_data) //SKYRAT EDIT ADDITION
|
|
|
|
return TRUE
|
|
|
|
/datum/preferences/proc/sanitize_be_special(list/input_be_special)
|
|
var/list/output = list()
|
|
|
|
for (var/role in input_be_special)
|
|
if (role in GLOB.special_roles)
|
|
output += role
|
|
|
|
return output.len == input_be_special.len ? input_be_special : output
|
|
|
|
/proc/sanitize_keybindings(value)
|
|
var/list/base_bindings = sanitize_islist(value,list())
|
|
for(var/keybind_name in base_bindings)
|
|
if (!(keybind_name in GLOB.keybindings_by_name))
|
|
base_bindings -= keybind_name
|
|
return base_bindings
|
|
|
|
#undef SAVEFILE_VERSION_MAX
|
|
#undef SAVEFILE_VERSION_MIN
|