diff --git a/code/modules/persistence/persistence.dm b/code/modules/persistence/persistence.dm new file mode 100644 index 0000000000..050348378b --- /dev/null +++ b/code/modules/persistence/persistence.dm @@ -0,0 +1,90 @@ +/* +* Returns a byond list that can be passed to the "deserialize" proc +* to bring a new instance of this atom to its original state +* +* If we want to store this info, we can pass it to `json_encode` or some other +* interface that suits our fancy, to make it into an easily-handled string +*/ +/datum/proc/serialize() + var/data = list("type" = "[type]") + return data + +/* +* This is given the byond list from above, to bring this atom to the state +* described in the list. +* This will be called after `New` but before `initialize`, so linking and stuff +* would probably be handled in `initialize` +* +* Also, this should only be called by `list_to_object` in persistence.dm - at least +* with current plans - that way it can actually initialize the type from the list +*/ +/datum/proc/deserialize(var/list/data) + return + +/atom + // This var isn't actually used for anything, but is present so that + // DM's map reader doesn't forfeit on reading a JSON-serialized map + var/map_json_data + +// This is so specific atoms can override these, and ignore certain ones +/atom/proc/vars_to_save() + return list("color","dir","icon","icon_state","name","pixel_x","pixel_y") + +/atom/proc/map_important_vars() + // A list of important things to save in the map editor + return list("color","dir","icon","icon_state","layer","name","pixel_x","pixel_y") + +/area/map_important_vars() + // Keep the area default icons, to keep things nice and legible + return list("name") + +// No need to save any state of an area by default +/area/vars_to_save() + return list("name") + +/atom/serialize() + var/list/data = ..() + for(var/thing in vars_to_save()) + if(vars[thing] != initial(vars[thing])) + data[thing] = vars[thing] + return data + + +/atom/deserialize(var/list/data) + for(var/thing in vars_to_save()) + if(thing in data) + vars[thing] = data[thing] + ..() + + +/* +Whoops, forgot to put documentation here. +What this does, is take a JSON string produced by running +BYOND's native `json_encode` on a list from `serialize` above, and +turns that string into a new instance of that object. + +You can also easily get an instance of this string by calling "Serialize Marked Datum" +in the "Debug" tab. + +If you're clever, you can do neat things with SDQL and this, though be careful - +some objects, like humans, are dependent that certain extra things are defined +in their list +*/ +/proc/object_to_json(var/atom/movable/thing) + return json_encode(thing.serialize()) + +/proc/json_to_object(var/json_data, var/loc) + return list_to_object(json_decode(json_data), loc) + +/proc/list_to_object(var/list/data, var/loc) + if(!islist(data)) + throw EXCEPTION("You didn't give me a list, bucko") + if(!("type" in data)) + throw EXCEPTION("No 'type' field in the data") + var/path = text2path(data["type"]) + if(!path) + throw EXCEPTION("Path not found: [path]") + + var/atom/movable/thing = new path(loc) + thing.deserialize(data) + return thing diff --git a/code/modules/vore/eating/belly_dat_vr.dm b/code/modules/vore/eating/belly_dat_vr.dm index 19befd3035..9453edc66f 100644 --- a/code/modules/vore/eating/belly_dat_vr.dm +++ b/code/modules/vore/eating/belly_dat_vr.dm @@ -9,7 +9,7 @@ /datum/belly var/name // Name of this location var/inside_flavor // Flavor text description of inside sight/sound/smells/feels. - var/vore_sound = 'sound/vore/gulp.ogg' // Sound when ingesting someone + var/vore_sound = "Gulp" // Sound when ingesting someone var/vore_verb = "ingest" // Verb for eating with this in messages var/human_prey_swallow_time = 100 // Time in deciseconds to swallow /mob/living/carbon/human var/nonhuman_prey_swallow_time = 30 // Time in deciseconds to swallow anything else @@ -107,7 +107,6 @@ new_belly.emote_time = emoteTime new_belly.digest_brute = digest_brute new_belly.digest_burn = digest_burn - new_belly.digest_tickrate = digest_tickrate new_belly.immutable = immutable new_belly.can_taste = can_taste new_belly.escapable = escapable diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm index 01f21b4124..3b48c3b1c2 100644 --- a/code/modules/vore/eating/belly_obj_vr.dm +++ b/code/modules/vore/eating/belly_obj_vr.dm @@ -13,14 +13,13 @@ /obj/belly name = "belly" // Name of this location desc = "It's a belly! You're in it!" // Flavor text description of inside sight/sound/smells/feels. - var/vore_sound = 'sound/vore/gulp.ogg' // Sound when ingesting someone + var/vore_sound = "Gulp" // Sound when ingesting someone var/vore_verb = "ingest" // Verb for eating with this in messages var/human_prey_swallow_time = 100 // Time in deciseconds to swallow /mob/living/carbon/human var/nonhuman_prey_swallow_time = 30 // Time in deciseconds to swallow anything else var/emote_time = 60 SECONDS // How long between stomach emotes at prey var/digest_brute = 2 // Brute damage per tick in digestion mode var/digest_burn = 2 // Burn damage per tick in digestion mode - var/digest_tickrate = 3 // Modulus this of air controller tick number to iterate gurgles on var/immutable = 0 // Prevents this belly from being deleted var/escapable = 0 // Belly can be resisted out of at any time var/escapetime = 60 SECONDS // Deciseconds, how long to escape this belly @@ -100,6 +99,37 @@ //List has indexes that are the digestion mode strings, and keys that are lists of strings. var/tmp/list/emote_lists = list() +//For serialization, keep this updated, required for bellies to save correctly. +/obj/belly/vars_to_save() + return ..() + list( + "name", + "desc", + "vore_sound", + "vore_verb", + "human_prey_swallow_time", + "nonhuman_prey_swallow_time", + "emote_time", + "digest_brute", + "digest_burn", + "immutable", + "can_taste", + "escapable", + "escapetime", + "digestchance", + "absorbchance", + "escapechance", + "transferchance", + "transferlocation", + "bulge_size", + "shrink_grow_size", + "struggle_messages_outside", + "struggle_messages_inside", + "digest_messages_owner", + "digest_messages_prey", + "examine_messages", + "emote_lists" + ) + /obj/belly/initialize() . = ..() //If not, we're probably just in a prefs list or something. @@ -125,8 +155,10 @@ //Sound w/ antispam flag setting if(vore_sound && !recent_sound) - playsound(src, vore_sound, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/client_preference/eating_noises) - recent_sound = TRUE + var/soundfile = vore_sounds[vore_sound] + if(soundfile) + playsound(src, soundfile, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/client_preference/eating_noises) + recent_sound = TRUE //Messages if it's a mob if(isliving(thing)) @@ -503,8 +535,10 @@ if(!(content in src) || !istype(target)) return content.forceMove(target) - if(!silent) - playsound(src, target.vore_sound, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/client_preference/digestion_noises) + if(!silent && target.vore_sound && !recent_sound) + var/soundfile = vore_sounds[target.vore_sound] + if(soundfile) + playsound(src, soundfile, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/client_preference/digestion_noises) owner.updateVRPanel() for(var/mob/living/M in contents) M.updateVRPanel() @@ -524,7 +558,6 @@ dupe.emote_time = emote_time dupe.digest_brute = digest_brute dupe.digest_burn = digest_burn - dupe.digest_tickrate = digest_tickrate dupe.immutable = immutable dupe.can_taste = can_taste dupe.escapable = escapable diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index 4b7fabc1d1..8ea2a05dc1 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -184,11 +184,17 @@ P.digestable = src.digestable P.allowmobvore = src.allowmobvore - P.belly_prefs = src.vore_organs P.vore_taste = src.vore_taste P.can_be_drop_prey = src.can_be_drop_prey P.can_be_drop_pred = src.can_be_drop_pred + var/list/serialized = list() + for(var/belly in src.vore_organs) + var/obj/belly/B = belly + serialized += list(B.serialize()) //Can't add a list as an object to another list in Byond. Thanks. + + P.belly_prefs = serialized + return 1 // @@ -201,27 +207,15 @@ var/datum/vore_preferences/P = client.prefs_vr - vore_organs = list() digestable = P.digestable allowmobvore = P.allowmobvore vore_taste = P.vore_taste can_be_drop_prey = P.can_be_drop_prey can_be_drop_pred = P.can_be_drop_pred - var/force_save = FALSE - for(var/I in P.belly_prefs) - if(isbelly(I)) //Belly system 2.0 - var/obj/belly/saved = I - saved.copy(src) - else if(istext(I)) //Belly name for old datum system - force_save = TRUE - log_debug("[src] had legacy belly [I], converting.") - var/datum/belly/Bp = P.belly_prefs[I] - var/obj/belly/new_belly = new(src) - Bp.copy(new_belly) //Rewritten to convert to a 2.0 belly - - if(force_save) - save_vore_prefs() + vore_organs.Cut() + for(var/entry in P.belly_prefs) + list_to_object(entry,src) return 1 diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm index dcef2a4577..2205e54432 100644 --- a/code/modules/vore/eating/vore_vr.dm +++ b/code/modules/vore/eating/vore_vr.dm @@ -41,12 +41,12 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE /datum/vore_preferences //Actual preferences - var/digestable = 1 - var/allowmobvore = 1 + var/digestable = TRUE + var/allowmobvore = TRUE var/list/belly_prefs = list() - var/vore_taste - var/can_be_drop_prey - var/can_be_drop_pred + var/vore_taste = "nothing in particular" + var/can_be_drop_prey = FALSE + var/can_be_drop_pred = FALSE //Mechanically required var/path @@ -58,7 +58,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE if(istype(C)) client = C client_ckey = C.ckey - load_vore(C) + load_vore() // // Check if an object is capable of eating things, based on vore_organs @@ -80,34 +80,49 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE // // Save/Load Vore Preferences // +/datum/vore_preferences/proc/load_path(ckey,slot,filename="character",ext="json") + if(!ckey || !slot) return + path = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/vore/[filename][slot].[ext]" + + /datum/vore_preferences/proc/load_vore() - if(!client || !client_ckey) return 0 //No client, how can we save? + if(!client || !client_ckey) + return 0 //No client, how can we save? + if(!client.prefs || !client.prefs.default_slot) + return 0 //Need to know what character to load! slot = client.prefs.default_slot - - path = client.prefs.path - + + load_path(client_ckey,slot) + if(!path) return 0 //Path couldn't be set? if(!fexists(path)) //Never saved before save_vore() //Make the file first return 1 - var/savefile/S = new /savefile(path) - if(!S) return 0 //Savefile object couldn't be created? + var/list/json_from_file = json_decode(file2text(path)) + if(!json_from_file) + return 0 //My concern grows - S.cd = "/character[slot]" + var/version = json_from_file["version"] + json_from_file = patch_version(json_from_file,version) - S["digestable"] >> digestable - S["allowmobvore"] >> allowmobvore - S["belly_prefs"] >> belly_prefs - S["vore_taste"] >> vore_taste - S["can_be_drop_prey"] >> can_be_drop_prey - S["can_be_drop_pred"] >> can_be_drop_pred + digestable = json_from_file["digestable"] + allowmobvore = json_from_file["allowmobvore"] + vore_taste = json_from_file["vore_taste"] + can_be_drop_prey = json_from_file["can_be_drop_prey"] + can_be_drop_prey = json_from_file["can_be_drop_pred"] + belly_prefs = json_from_file["belly_prefs"] + //Quick sanitize if(isnull(digestable)) - digestable = 1 + digestable = TRUE if(isnull(allowmobvore)) - allowmobvore = 1 + allowmobvore = TRUE + if(isnull(can_be_drop_prey)) + allowmobvore = FALSE + if(isnull(can_be_drop_pred)) + allowmobvore = FALSE if(isnull(belly_prefs)) belly_prefs = list() @@ -115,16 +130,34 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE /datum/vore_preferences/proc/save_vore() if(!path) return 0 - if(!slot) return 0 - var/savefile/S = new /savefile(path) - if(!S) return 0 - S.cd = "/character[slot]" + + var/version = 1 //For "good times" use in the future + var/list/settings_list = list( + "version" = version, + "digestable" = digestable, + "allowmobvore" = allowmobvore, + "vore_taste" = vore_taste, + "can_be_drop_prey" = can_be_drop_prey, + "can_be_drop_pred" = can_be_drop_pred, + "belly_prefs" = belly_prefs, + ) - S["digestable"] << digestable - S["allowmobvore"] << allowmobvore - S["belly_prefs"] << belly_prefs - S["vore_taste"] << vore_taste - S["can_be_drop_prey"] << can_be_drop_prey - S["can_be_drop_pred"] << can_be_drop_pred + //List to JSON + var/json_to_file = json_encode(settings_list) + if(!json_to_file) + log_debug("Saving: [path] failed jsonencode") + return 0 + + //Write it out + if(fexists(path)) + fdel(path) //Byond only supports APPENDING to files, not replacing. + text2file(json_to_file,path) + if(!fexists(path)) + log_debug("Saving: [path] failed file write") + return 0 return 1 + +//Can do conversions here +/datum/vore_preferences/proc/patch_version(var/list/json_from_file,var/version) + return json_from_file diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index f0f564f87f..89c40daf92 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -525,12 +525,11 @@ "Struggle Message (outside)", "Struggle Message (inside)", "Examine Message (when full)", - "Reset All To Default", - "Cancel - No Changes" + "Reset All To Default" ) alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message, max 10 messages per topic.","Really, don't.") - var/choice = input(user,"Select a type to modify. Messages from each topic are pulled at random when needed.","Pick Type") in messages + var/choice = input(user,"Select a type to modify. Messages from each topic are pulled at random when needed.","Pick Type") as null|anything in messages var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name." switch(choice) @@ -567,9 +566,6 @@ selected.struggle_messages_outside = initial(selected.struggle_messages_outside) selected.struggle_messages_inside = initial(selected.struggle_messages_inside) - if("Cancel - No Changes") - return 0 - if(href_list["b_verb"]) var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null) @@ -580,15 +576,16 @@ selected.vore_verb = new_verb if(href_list["b_sound"]) - var/choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") in vore_sounds + "Cancel - No Changes" - - if(choice == "Cancel") + var/choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") as null|anything in vore_sounds + if(!choice) return 0 - selected.vore_sound = vore_sounds[choice] + selected.vore_sound = choice if(href_list["b_soundtest"]) - user << selected.vore_sound + var/soundfile = vore_sounds[selected.vore_sound] + if(soundfile) + user << soundfile if(href_list["b_tastes"]) selected.can_taste = !selected.can_taste diff --git a/vorestation.dme b/vorestation.dme index 0f1f8fd84a..060a65a44e 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -2295,6 +2295,7 @@ #include "code\modules\paperwork\photography.dm" #include "code\modules\paperwork\silicon_photography.dm" #include "code\modules\paperwork\stamps.dm" +#include "code\modules\persistence\persistence.dm" #include "code\modules\planet\planet.dm" #include "code\modules\planet\time.dm" #include "code\modules\planet\weather.dm"