Files
Bubberstation/code/modules/client/preferences_savefile.dm
Xhuis 527dddd95d Complete Revenant Rewrite (#18522)
I'm unhappy with the way revenants are right now, and my code for them is pretty unsatisfactory in comparison to what I know now. Although revenants will still fill the same role of just being spookier ghosts, they'll be a bit more passive - incapable, for instance, of giving diseases to people. The new revenants will be called umbras and will use vitae instead of essence.

Total change list:

    Revenants have been renamed to umbras. Essence has been renamed to vitae. This may be temporary.
    Umbra spawn events are now weighted higher and spawn an unoccupied umbra. Ghosts are alerted to the umbra's position and may interact with it to take control of it.
    Umbras' health is not based on vitae but has a hard cap at 100.
    Umbras have a passive vitae drain each tick, defaulting at 0.01. If the umbra runs out of vitae, they will die irrevocably. They also slowly regenerate health by doing this.
    When an umbra dies, they leave behind umbral ashes that reform after one minute. They're difficult to see and can be scattered by activating them, although they also have high research levels if you're fast enough.
    Harvesting vitae from critical targets no longer kills them. Harvesting a target in general prohibits them from being harvested until five minutes later, but they can be drained again after that.
    EMPs revitalize umbras and give them hefty amounts of vitae due to their physical nature.
    Umbras have four abilities: Toggle Nightvision, Discordant Whisper, Possess, and Thoughtsteal.
        Toggle Nightvision is self-explanatory.
        Discordant Whisper is identical to the original revenant's transmit.
        Possess allows the umbra to slip into a human's body unnoticed. While in their body, umbras will slowly drain vitae from the human at a tiny rate - not enough to cause harm, but enough to induce adverse effects in the clueless human. These effects intensify over time and eventually lead to the umbra being forced out of their host.
        Thoughtsteal paralyzes a living human for several seconds while the umbra steals their memories. After several seconds, the umbra copies the notes of the target's memories and turns invisible - the hapless victim is stunned for several seconds afterwards and can't be Thoughtstolen by the same umbra again. Umbras have an objective to steal the memories of 25% of the station's population.
    Salt piles have been added, created by salt shaker or just by splashing salt. These piles will prevent an umbra from passing and reveal them briefly if they try.
2016-06-17 10:58:07 +12:00

490 lines
17 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 8
//This is the current version, anything below this will attempt to update (if it's not obsolete)
#define SAVEFILE_VERSION_MAX 15
/*
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/savefile_needs_update(savefile/S)
var/savefile_version
S["version"] >> savefile_version
if(savefile_version < SAVEFILE_VERSION_MIN)
S.dir.Cut()
return -2
if(savefile_version < SAVEFILE_VERSION_MAX)
return savefile_version
return -1
/datum/preferences/proc/update_antagchoices(current_version)
if((!islist(be_special) || old_be_special ) && current_version < 12)
//Archived values of when antag pref defines were a bitfield+fitflags
var/B_traitor = 1
var/B_operative = 2
var/B_changeling = 4
var/B_wizard = 8
var/B_malf = 16
var/B_rev = 32
var/B_alien = 64
var/B_pai = 128
var/B_cultist = 256
var/B_blob = 512
var/B_ninja = 1024
var/B_monkey = 2048
var/B_gang = 4096
var/B_shadowling = 8192
var/B_abductor = 16384
var/list/archived = list(B_traitor,B_operative,B_changeling,B_wizard,B_malf,B_rev,B_alien,B_pai,B_cultist,B_blob,B_ninja,B_monkey,B_gang,B_shadowling,B_abductor)
be_special = list()
for(var/flag in archived)
if(old_be_special & flag)
//this is shitty, but this proc should only be run once per player and then never again for the rest of eternity,
switch(flag)
if(1) //why aren't these the variables above? Good question, it's because byond complains the expression isn't constant, when it is.
be_special += ROLE_TRAITOR
if(2)
be_special += ROLE_OPERATIVE
if(4)
be_special += ROLE_CHANGELING
if(8)
be_special += ROLE_WIZARD
if(16)
be_special += ROLE_MALF
if(32)
be_special += ROLE_REV
if(64)
be_special += ROLE_ALIEN
if(128)
be_special += ROLE_PAI
if(256)
be_special += ROLE_CULTIST
if(512)
be_special += ROLE_BLOB
if(1024)
be_special += ROLE_NINJA
if(2048)
be_special += ROLE_MONKEY
if(4096)
be_special += ROLE_GANG
if(8192)
be_special += ROLE_SHADOWLING
if(16384)
be_special += ROLE_ABDUCTOR
/datum/preferences/proc/update_preferences(current_version)
if(current_version < 10)
toggles |= MEMBER_PUBLIC
if(current_version < 11)
chat_toggles = TOGGLES_DEFAULT_CHAT
toggles = TOGGLES_DEFAULT
if(current_version < 12)
ignoring = list()
if(current_version < 15)
toggles |= SOUND_ANNOUNCEMENTS
//should this proc get fairly long (say 3 versions long),
//just increase SAVEFILE_VERSION_MIN so it's not as far behind
//SAVEFILE_VERSION_MAX and then delete any obsolete if clauses
//from this proc.
//It's only really meant to avoid annoying frequent players
//if your savefile is 3 months out of date, then 'tough shit'.
/datum/preferences/proc/update_character(current_version)
if(current_version < 9) //an example, underwear were an index for a hardcoded list, converting to a string
if(gender == MALE)
switch(underwear)
if(1)
underwear = "Mens White"
if(2)
underwear = "Mens Grey"
if(3)
underwear = "Mens Green"
if(4)
underwear = "Mens Blue"
if(5)
underwear = "Mens Black"
if(6)
underwear = "Mankini"
if(7)
underwear = "Mens Hearts Boxer"
if(8)
underwear = "Mens Black Boxer"
if(9)
underwear = "Mens Grey Boxer"
if(10)
underwear = "Mens Striped Boxer"
if(11)
underwear = "Mens Kinky"
if(12)
underwear = "Mens Red"
if(13)
underwear = "Nude"
else
switch(underwear)
if(1)
underwear = "Ladies Red"
if(2)
underwear = "Ladies White"
if(3)
underwear = "Ladies Yellow"
if(4)
underwear = "Ladies Blue"
if(5)
underwear = "Ladies Black"
if(6)
underwear = "Ladies Thong"
if(7)
underwear = "Babydoll"
if(8)
underwear = "Ladies Baby-Blue"
if(9)
underwear = "Ladies Green"
if(10)
underwear = "Ladies Pink"
if(11)
underwear = "Ladies Kinky"
if(12)
underwear = "Tankini"
if(13)
underwear = "Nude"
if(pref_species && !(pref_species.id in roundstart_species))
var/rando_race = pick(config.roundstart_races)
pref_species = new rando_race()
if(current_version < 13 || !istext(backbag))
switch(backbag)
if(2)
backbag = DSATCHEL
else
backbag = DBACKPACK
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
path = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/[filename]"
/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 = "/"
var/needs_update = savefile_needs_update(S)
if(needs_update == -2) //fatal, can't load any data
return 0
//general preferences
S["ooccolor"] >> ooccolor
S["lastchangelog"] >> lastchangelog
S["UI_style"] >> UI_style
S["hotkeys"] >> hotkeys
S["tgui_fancy"] >> tgui_fancy
S["tgui_lock"] >> tgui_lock
if(islist(S["be_special"]))
S["be_special"] >> be_special
else //force update and store the old bitflag version of be_special
needs_update = 11
S["be_special"] >> old_be_special
S["default_slot"] >> default_slot
S["chat_toggles"] >> chat_toggles
S["toggles"] >> toggles
S["ghost_form"] >> ghost_form
S["ghost_orbit"] >> ghost_orbit
S["ghost_accs"] >> ghost_accs
S["ghost_others"] >> ghost_others
S["preferred_map"] >> preferred_map
S["ignoring"] >> ignoring
S["ghost_hud"] >> ghost_hud
S["inquisitive_ghost"] >> inquisitive_ghost
//try to fix any outdated data if necessary
if(needs_update >= 0)
update_preferences(needs_update) //needs_update = savefile_version if we need an update (positive integer)
update_antagchoices(needs_update)
//Sanitize
ooccolor = sanitize_ooccolor(sanitize_hexcolor(ooccolor, 6, 1, initial(ooccolor)))
lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog))
UI_style = sanitize_inlist(UI_style, list("Midnight", "Plasmafire", "Retro", "Slimecore", "Operative"), initial(UI_style))
hotkeys = sanitize_integer(hotkeys, 0, 1, initial(hotkeys))
tgui_fancy = sanitize_integer(tgui_fancy, 0, 1, initial(tgui_fancy))
tgui_lock = sanitize_integer(tgui_lock, 0, 1, initial(tgui_lock))
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
toggles = sanitize_integer(toggles, 0, 65535, initial(toggles))
ghost_form = sanitize_inlist(ghost_form, ghost_forms, initial(ghost_form))
ghost_orbit = sanitize_inlist(ghost_orbit, ghost_orbits, initial(ghost_orbit))
ghost_accs = sanitize_inlist(ghost_accs, ghost_accs_options, GHOST_ACCS_DEFAULT_OPTION)
ghost_others = sanitize_inlist(ghost_others, ghost_others_options, GHOST_OTHERS_DEFAULT_OPTION)
return 1
/datum/preferences/proc/save_preferences()
if(!path)
return 0
var/savefile/S = new /savefile(path)
if(!S)
return 0
S.cd = "/"
S["version"] << SAVEFILE_VERSION_MAX //updates (or failing that the sanity checks) will ensure data is not invalid at load. Assume up-to-date
//general preferences
S["ooccolor"] << ooccolor
S["lastchangelog"] << lastchangelog
S["UI_style"] << UI_style
S["hotkeys"] << hotkeys
S["tgui_fancy"] << tgui_fancy
S["tgui_lock"] << tgui_lock
S["be_special"] << be_special
S["default_slot"] << default_slot
S["toggles"] << toggles
S["chat_toggles"] << chat_toggles
S["ghost_form"] << ghost_form
S["ghost_orbit"] << ghost_orbit
S["ghost_accs"] << ghost_accs
S["ghost_others"] << ghost_others
S["preferred_map"] << preferred_map
S["ignoring"] << ignoring
S["ghost_hud"] << ghost_hud
S["inquisitive_ghost"] << inquisitive_ghost
return 1
/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
slot = sanitize_integer(slot, 1, max_save_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
S["default_slot"] << slot
S.cd = "/character[slot]"
var/needs_update = savefile_needs_update(S)
if(needs_update == -2) //fatal, can't load any data
return 0
//Species
var/species_id
S["species"] >> species_id
if(config.mutant_races && species_id && (species_id in roundstart_species))
var/newtype = roundstart_species[species_id]
pref_species = new newtype()
else
var/rando_race = pick(config.roundstart_races)
pref_species = new rando_race()
if(!S["features["mcolor"]"] || S["features["mcolor"]"] == "#000")
S["features["mcolor"]"] << "#FFF"
//Character
S["OOC_Notes"] >> metadata
S["real_name"] >> real_name
S["name_is_always_random"] >> be_random_name
S["body_is_always_random"] >> be_random_body
S["gender"] >> gender
S["age"] >> age
S["hair_color"] >> hair_color
S["facial_hair_color"] >> facial_hair_color
S["eye_color"] >> eye_color
S["skin_tone"] >> skin_tone
S["hair_style_name"] >> hair_style
S["facial_style_name"] >> facial_hair_style
S["underwear"] >> underwear
S["undershirt"] >> undershirt
S["socks"] >> socks
S["backbag"] >> backbag
S["feature_mcolor"] >> features["mcolor"]
S["feature_lizard_tail"] >> features["tail_lizard"]
S["feature_lizard_snout"] >> features["snout"]
S["feature_lizard_horns"] >> features["horns"]
S["feature_lizard_frills"] >> features["frills"]
S["feature_lizard_spines"] >> features["spines"]
S["feature_lizard_body_markings"] >> features["body_markings"]
if(!config.mutant_humans)
features["tail_human"] = "none"
features["ears"] = "none"
else
S["feature_human_tail"] >> features["tail_human"]
S["feature_human_ears"] >> features["ears"]
S["clown_name"] >> custom_names["clown"]
S["mime_name"] >> custom_names["mime"]
S["ai_name"] >> custom_names["ai"]
S["cyborg_name"] >> custom_names["cyborg"]
S["religion_name"] >> custom_names["religion"]
S["deity_name"] >> custom_names["deity"]
//Jobs
S["userandomjob"] >> userandomjob
S["job_civilian_high"] >> job_civilian_high
S["job_civilian_med"] >> job_civilian_med
S["job_civilian_low"] >> job_civilian_low
S["job_medsci_high"] >> job_medsci_high
S["job_medsci_med"] >> job_medsci_med
S["job_medsci_low"] >> job_medsci_low
S["job_engsec_high"] >> job_engsec_high
S["job_engsec_med"] >> job_engsec_med
S["job_engsec_low"] >> job_engsec_low
//try to fix any outdated data if necessary
if(needs_update >= 0)
update_character(needs_update) //needs_update == savefile_version if we need an update (positive integer)
//Sanitize
metadata = sanitize_text(metadata, initial(metadata))
real_name = reject_bad_name(real_name)
if(!features["mcolor"] || features["mcolor"] == "#000")
features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
if(!real_name)
real_name = random_unique_name(gender)
be_random_name = sanitize_integer(be_random_name, 0, 1, initial(be_random_name))
be_random_body = sanitize_integer(be_random_body, 0, 1, initial(be_random_body))
gender = sanitize_gender(gender)
if(gender == MALE)
hair_style = sanitize_inlist(hair_style, hair_styles_male_list)
facial_hair_style = sanitize_inlist(facial_hair_style, facial_hair_styles_male_list)
underwear = sanitize_inlist(underwear, underwear_m)
undershirt = sanitize_inlist(undershirt, undershirt_m)
else
hair_style = sanitize_inlist(hair_style, hair_styles_female_list)
facial_hair_style = sanitize_inlist(facial_hair_style, facial_hair_styles_female_list)
underwear = sanitize_inlist(underwear, underwear_f)
undershirt = sanitize_inlist(undershirt, undershirt_f)
socks = sanitize_inlist(socks, socks_list)
age = sanitize_integer(age, AGE_MIN, AGE_MAX, initial(age))
hair_color = sanitize_hexcolor(hair_color, 3, 0)
facial_hair_color = sanitize_hexcolor(facial_hair_color, 3, 0)
eye_color = sanitize_hexcolor(eye_color, 3, 0)
skin_tone = sanitize_inlist(skin_tone, skin_tones)
backbag = sanitize_inlist(backbag, backbaglist, initial(backbag))
features["mcolor"] = sanitize_hexcolor(features["mcolor"], 3, 0)
features["tail_lizard"] = sanitize_inlist(features["tail_lizard"], tails_list_lizard)
features["tail_human"] = sanitize_inlist(features["tail_human"], tails_list_human, "None")
features["snout"] = sanitize_inlist(features["snout"], snouts_list)
features["horns"] = sanitize_inlist(features["horns"], horns_list)
features["ears"] = sanitize_inlist(features["ears"], ears_list, "None")
features["frills"] = sanitize_inlist(features["frills"], frills_list)
features["spines"] = sanitize_inlist(features["spines"], spines_list)
features["body_markings"] = sanitize_inlist(features["body_markings"], body_markings_list)
userandomjob = sanitize_integer(userandomjob, 0, 1, initial(userandomjob))
job_civilian_high = sanitize_integer(job_civilian_high, 0, 65535, initial(job_civilian_high))
job_civilian_med = sanitize_integer(job_civilian_med, 0, 65535, initial(job_civilian_med))
job_civilian_low = sanitize_integer(job_civilian_low, 0, 65535, initial(job_civilian_low))
job_medsci_high = sanitize_integer(job_medsci_high, 0, 65535, initial(job_medsci_high))
job_medsci_med = sanitize_integer(job_medsci_med, 0, 65535, initial(job_medsci_med))
job_medsci_low = sanitize_integer(job_medsci_low, 0, 65535, initial(job_medsci_low))
job_engsec_high = sanitize_integer(job_engsec_high, 0, 65535, initial(job_engsec_high))
job_engsec_med = sanitize_integer(job_engsec_med, 0, 65535, initial(job_engsec_med))
job_engsec_low = sanitize_integer(job_engsec_low, 0, 65535, initial(job_engsec_low))
return 1
/datum/preferences/proc/save_character()
if(!path)
return 0
var/savefile/S = new /savefile(path)
if(!S)
return 0
S.cd = "/character[default_slot]"
S["version"] << SAVEFILE_VERSION_MAX //load_character will sanitize any bad data, so assume up-to-date.
//Character
S["OOC_Notes"] << metadata
S["real_name"] << real_name
S["name_is_always_random"] << be_random_name
S["body_is_always_random"] << be_random_body
S["gender"] << gender
S["age"] << age
S["hair_color"] << hair_color
S["facial_hair_color"] << facial_hair_color
S["eye_color"] << eye_color
S["skin_tone"] << skin_tone
S["hair_style_name"] << hair_style
S["facial_style_name"] << facial_hair_style
S["underwear"] << underwear
S["undershirt"] << undershirt
S["socks"] << socks
S["backbag"] << backbag
S["species"] << pref_species.id
S["feature_mcolor"] << features["mcolor"]
S["feature_lizard_tail"] << features["tail_lizard"]
S["feature_human_tail"] << features["tail_human"]
S["feature_lizard_snout"] << features["snout"]
S["feature_lizard_horns"] << features["horns"]
S["feature_human_ears"] << features["ears"]
S["feature_lizard_frills"] << features["frills"]
S["feature_lizard_spines"] << features["spines"]
S["feature_lizard_body_markings"] << features["body_markings"]
S["clown_name"] << custom_names["clown"]
S["mime_name"] << custom_names["mime"]
S["ai_name"] << custom_names["ai"]
S["cyborg_name"] << custom_names["cyborg"]
S["religion_name"] << custom_names["religion"]
S["deity_name"] << custom_names["deity"]
//Jobs
S["userandomjob"] << userandomjob
S["job_civilian_high"] << job_civilian_high
S["job_civilian_med"] << job_civilian_med
S["job_civilian_low"] << job_civilian_low
S["job_medsci_high"] << job_medsci_high
S["job_medsci_med"] << job_medsci_med
S["job_medsci_low"] << job_medsci_low
S["job_engsec_high"] << job_engsec_high
S["job_engsec_med"] << job_engsec_med
S["job_engsec_low"] << job_engsec_low
return 1
#undef SAVEFILE_VERSION_MAX
#undef SAVEFILE_VERSION_MIN
/*
//DEBUG
//Some crude tools for testing savefiles
//path is the savefile path
/client/verb/savefile_export(path as text)
var/savefile/S = new /savefile(path)
S.ExportText("/",file("[path].txt"))
//path is the savefile path
/client/verb/savefile_import(path as text)
var/savefile/S = new /savefile(path)
S.ImportText("/",file("[path].txt"))
*/