From cc784f338eff3006eea42f41115bed1e17df203b Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 27 Jul 2016 13:31:19 -0700 Subject: [PATCH 1/3] Adds bare bones for a persistence system Admins can toy around with converting a character to text, and placing them back elsewhere, independent of the round. It's got a kink, though: The text given to you directly needs to be printed once to remove all the escapes - otherwise the thing will choke. I'd like advice on resolving that --- code/game/dna/dna2.dm | 23 ++++++ code/modules/admin/admin_verbs.dm | 7 +- code/modules/admin/verbs/serialization.dm | 26 +++++++ code/modules/mob/living/carbon/human/human.dm | 74 ++++++++++++++++--- code/modules/persistence/persistence.dm | 38 ++++++++++ code/modules/surgery/organs/organ.dm | 29 ++++++++ code/modules/surgery/organs/organ_external.dm | 33 +++++++-- paradise.dme | 2 + 8 files changed, 215 insertions(+), 17 deletions(-) create mode 100644 code/modules/admin/verbs/serialization.dm create mode 100644 code/modules/persistence/persistence.dm diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm index 76254b13c44..a8a33784263 100644 --- a/code/game/dna/dna2.dm +++ b/code/game/dna/dna2.dm @@ -419,3 +419,26 @@ var/global/list/bad_blocks[0] unique_enzymes = md5(character.real_name) reg_dna[unique_enzymes] = character.real_name + +// Hmm, I wonder how to go about this without a huge convention break +/datum/dna/proc/serialize() + var/data = list() + data["UE"] = unique_enzymes + data["SE"] = SE.Copy() // This is probably too lazy for my own good + data["UI"] = UI.Copy() + data["species"] = species // This works because `species` is a string, not a datum + // Because old DNA coders were insane or something + data["b_type"] = b_type + data["real_name"] = real_name + return data + +/datum/dna/proc/deserialize(data) + unique_enzymes = data["UE"] + // The de-serializer is unlikely to tamper with the lists + SE = data["SE"] + UI = data["UI"] + UpdateUI() + UpdateSE() + species = data["species"] + b_type = data["b_type"] + real_name = data["real_name"] \ No newline at end of file diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 90ad26039db..aed9270bf57 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -113,7 +113,8 @@ var/list/admin_verbs_event = list( var/list/admin_verbs_spawn = list( /datum/admins/proc/spawn_atom, /*allows us to spawn instances*/ - /client/proc/respawn_character + /client/proc/respawn_character, + /client/proc/admin_deserialize ) var/list/admin_verbs_server = list( /client/proc/ToRban, @@ -161,7 +162,9 @@ var/list/admin_verbs_debug = list( /proc/machine_upgrade, /client/proc/map_template_load, /client/proc/map_template_upload, - /client/proc/view_runtimes + /client/proc/view_runtimes, + /client/proc/admin_serialize, + /client/proc/admin_deserialize ) var/list/admin_verbs_possess = list( /proc/possess, diff --git a/code/modules/admin/verbs/serialization.dm b/code/modules/admin/verbs/serialization.dm new file mode 100644 index 00000000000..41ba5d11aa1 --- /dev/null +++ b/code/modules/admin/verbs/serialization.dm @@ -0,0 +1,26 @@ +/client/proc/admin_serialize() + set name = "Serialize Marked Datum" + set desc = "Turns your marked object into a JSON string you can later use to re-create the object" + set category = "Debug" + + if(!check_rights(R_ADMIN)) + return + + if(!istype(holder.marked_datum, /atom/movable)) + to_chat(src, "The marked datum is not an atom/movable!") + return + + var/atom/movable/AM = holder.marked_datum + to_chat(src, json_encode(AM.serialize())) + +/client/proc/admin_deserialize() + set name = "Deserialize JSON datum" + set desc = "Creates an object from a JSON string" + set category = "Debug" + + if(!check_rights(R_ADMIN|R_DEBUG)) + return + + var/json_text = input("Enter the JSON code:","Text") as message|null + if(json_text) + json_to_object(json_text, get_turf(usr)) \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index f9c448df5e3..12cabc68a5f 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -8,7 +8,7 @@ //why are these here and not in human_defines.dm //var/list/hud_list[10] var/datum/species/species //Contains icon generation and language information, set during New(). - var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us. + var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us. var/obj/item/weapon/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call. /mob/living/carbon/human/New(var/new_loc, var/new_species = null, var/delay_ready_dna = 0) @@ -482,8 +482,8 @@ if(3 to 4) stunprob = 30 if(5 to 6) stunprob = 40 if(7 to 8) stunprob = 60 - if(9) stunprob = 70 - if(10) stunprob = 95 + if(9) stunprob = 70 + if(10) stunprob = 95 if(prob(stunprob)) M.powerlevel -= 3 @@ -944,7 +944,7 @@ for(var/datum/data/record/R in data_core.security) if(R.fields["id"] == E.fields["id"]) if(hasHUD(usr,"security")) - var/t1 = sanitize(copytext(input("Add Comment:", "Sec. records", null, null) as message,1,MAX_MESSAGE_LEN)) + var/t1 = sanitize(copytext(input("Add Comment:", "Sec. records", null, null) as message,1,MAX_MESSAGE_LEN)) if( !(t1) || usr.stat || usr.restrained() || !(hasHUD(usr,"security")) ) return var/counter = 1 @@ -1075,7 +1075,7 @@ for(var/datum/data/record/R in data_core.medical) if(R.fields["id"] == E.fields["id"]) if(hasHUD(usr,"medical")) - var/t1 = sanitize(copytext(input("Add Comment:", "Med. records", null, null) as message,1,MAX_MESSAGE_LEN)) + var/t1 = sanitize(copytext(input("Add Comment:", "Med. records", null, null) as message,1,MAX_MESSAGE_LEN)) if( !(t1) || usr.stat || usr.restrained() || !(hasHUD(usr,"medical")) ) return var/counter = 1 @@ -1285,8 +1285,8 @@ var/limb_path = organ_data["path"] var/obj/item/organ/external/O = new limb_path(temp_holder) if(H.get_limb_by_name(O.name)) //Check to see if the user already has an limb with the same name as the 'missing limb'. If they do, skip regrowth. - continue //In an example, this will prevent duplication of the mob's right arm if the mob is a Human and they have a Diona right arm, since, - //while the limb with the name 'right_arm' the mob has may not be listed in their species' bodyparts definition, it is still viable and has the appropriate limb name. + continue //In an example, this will prevent duplication of the mob's right arm if the mob is a Human and they have a Diona right arm, since, + //while the limb with the name 'right_arm' the mob has may not be listed in their species' bodyparts definition, it is still viable and has the appropriate limb name. else O = new limb_path(H) //Create the limb on the player. O.owner = H @@ -1704,7 +1704,7 @@ if((head_organ.species.name in tmp_hair.species_allowed) && (robohead.company in tmp_hair.models_allowed)) //Populate the list of available monitor styles only with styles that the monitor-head is allowed to use. hair += i - var/new_style = input(src, "Select a monitor display", "Monitor Display", head_organ.h_style) as null|anything in hair + var/new_style = input(src, "Select a monitor display", "Monitor Display", head_organ.h_style) as null|anything in hair if(incapacitated()) to_chat(src, "You were interrupted while changing your monitor display.") return @@ -1909,7 +1909,7 @@ if(current_size >= STAGE_THREE) var/list/handlist = list(l_hand, r_hand) for(var/obj/item/hand in handlist) - if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && unEquip(hand)) + if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && unEquip(hand)) step_towards(hand, src) to_chat(src, "\The [S] pulls \the [hand] from your grip!") apply_effect(current_size * 3, IRRADIATE) @@ -2031,3 +2031,59 @@ return 0 return . + +/mob/living/carbon/human/serialize() + // Currently: Limbs/organs only + var/list/data = ..() + var/list/limbs_list = list() + var/list/organs_list = list() + data["limbs"] = limbs_list + data["iorgans"] = organs_list + data["dna"] = dna.serialize() + data["ushirt"] = undershirt + for(var/limb in organs_by_name) + var/obj/item/organ/O = organs_by_name[limb] + if(!O) + limbs_list[limb] = "missing" + continue + + limbs_list[limb] = O.serialize() + + for(var/organ in internal_organs) + var/obj/item/organ/O = organ + organs_list[O.name] = O.serialize() + + return json_encode(data) + +/mob/living/carbon/human/deserialize(list/data) + var/list/limbs_list = data["limbs"] + var/list/organs_list = data["iorgans"] + if(!islist(data["limbs"])) + throw EXCEPTION("Expected a limbs list, but found none") + + if(islist(data["dna"])) + dna.deserialize(data["dna"]) + real_name = dna.real_name + name = real_name + UpdateAppearance() + set_species(dna.species) + undershirt = data["ushirt"] + for(var/obj/item/organ/internal/iorgan in internal_organs) + qdel(iorgan) + + for(var/obj/item/organ/external/organ in organs) + qdel(organ) + + for(var/limb in limbs_list) + // Missing means skip this part - it's missing + if(limbs_list[limb] == "missing") + continue + // "New" code handles insertion and DNA sync'ing + var/obj/item/organ/external/E = list_to_object(limbs_list[limb], src) + E.sync_colour_to_dna() + + for(var/organ in organs_list) + // As above, "New" code handles insertion, DNA sync + list_to_object(organs_list[organ], src) + update_icons() + ..() diff --git a/code/modules/persistence/persistence.dm b/code/modules/persistence/persistence.dm new file mode 100644 index 00000000000..cc5a7684947 --- /dev/null +++ b/code/modules/persistence/persistence.dm @@ -0,0 +1,38 @@ +/* +* 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 +*/ +/atom/movable/proc/serialize() + return list("type" = "[type]") + +/* +* 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 `json_to_object` in persistence.dm - at least +* with current plans - that way it can actually initialize the type from the list +*/ +/atom/movable/proc/deserialize(var/data) + return + +/proc/json_to_object(var/json_data, var/loc) + var/data = json_decode(json_data) + return list_to_object(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/surgery/organs/organ.dm b/code/modules/surgery/organs/organ.dm index 5e49a97fc59..b49ccc6a838 100644 --- a/code/modules/surgery/organs/organ.dm +++ b/code/modules/surgery/organs/organ.dm @@ -343,3 +343,32 @@ I use this so that this can be made better once the organ overhaul rolls out -- if(!istype(owner)) // You're not the primary organ of ANYTHING, bucko return 0 return src == O.get_int_organ(organ_tag) + +/obj/item/organ/serialize() + var/data = ..() + if(status != 0) + data["status"] = status + if(robotic > 0) + data["robotic"] = robotic + + // Save the DNA datum if: The owner doesn't exist, or the dna doesn't match + // the owner + if(!(owner && dna.unique_enzymes == owner.dna.unique_enzymes)) + data["dna"] = dna.serialize() + return data + +/obj/item/organ/deserialize(var/data) + switch(data["robotic"]) + if(1) + mechassist() + if(2) + robotize() + else + // Nothing + if(isnum(data["status"])) + status = data["status"] + if(islist(data["dna"])) + // The only thing the official proc does is + //instantiate the list and call this proc + dna.deserialize(data["dna"]) + ..() diff --git a/code/modules/surgery/organs/organ_external.dm b/code/modules/surgery/organs/organ_external.dm index 10d7fe724ea..1c3f05f5816 100644 --- a/code/modules/surgery/organs/organ_external.dm +++ b/code/modules/surgery/organs/organ_external.dm @@ -836,12 +836,7 @@ Note that amputating the affected organ does in fact remove the infection from t ..() if(company && istext(company)) - model = company - var/datum/robolimb/R = all_robolimbs[company] - if(R) - force_icon = R.icon - name = "[R.company] [initial(name)]" - desc = "[R.desc]" + set_company(company) cannot_break = 1 get_icon() @@ -849,6 +844,16 @@ Note that amputating the affected organ does in fact remove the infection from t if(T) T.robotize() + + +/obj/item/organ/external/proc/set_company(var/company) + model = company + var/datum/robolimb/R = all_robolimbs[company] + if(R) + force_icon = R.icon + name = "[R.company] [initial(name)]" + desc = "[R.desc]" + /obj/item/organ/external/proc/mutate() src.status |= ORGAN_MUTATED if(owner) owner.update_body() @@ -969,3 +974,19 @@ Note that amputating the affected organ does in fact remove the infection from t continue wounds -= W qdel(W) + + +/obj/item/organ/external/serialize() + var/list/data = ..() + if(robotic == 2) + data["company"] = model + // If we wanted to store wound information, here is where it would go + return data + +/obj/item/organ/external/deserialize(list/data) + var/company = data["company"] + if(company && istext(company)) + set_company(company) + ..() // Parent call loads in the DNA + if(data["dna"]) + sync_colour_to_dna() diff --git a/paradise.dme b/paradise.dme index d79069d135d..5c184d781ea 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1043,6 +1043,7 @@ #include "code\modules\admin\verbs\possess.dm" #include "code\modules\admin\verbs\pray.dm" #include "code\modules\admin\verbs\randomverbs.dm" +#include "code\modules\admin\verbs\serialization.dm" #include "code\modules\admin\verbs\striketeam.dm" #include "code\modules\admin\verbs\striketeam_syndicate.dm" #include "code\modules\admin\verbs\ticklag.dm" @@ -1739,6 +1740,7 @@ #include "code\modules\pda\pdas.dm" #include "code\modules\pda\radio.dm" #include "code\modules\pda\utilities.dm" +#include "code\modules\persistence\persistence.dm" #include "code\modules\pooling\pool.dm" #include "code\modules\power\apc.dm" #include "code\modules\power\cable.dm" From 7719b58cf0c7b07c60761b5bf882090e18ece4a7 Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 27 Jul 2016 22:26:40 -0700 Subject: [PATCH 2/3] Improves serialization: Now with clothes (tm) --- code/game/objects/items/weapons/cards_ids.dm | 41 +++++++++++++++-- .../objects/items/weapons/storage/belt.dm | 4 +- .../objects/items/weapons/storage/storage.dm | 31 +++++++++++++ code/modules/clothing/suits/storage.dm | 9 ++++ code/modules/mob/living/carbon/human/human.dm | 46 ++++++++++++++++++- 5 files changed, 124 insertions(+), 7 deletions(-) diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index 0ee843fe85c..dd51696c118 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -105,13 +105,13 @@ var/photo var/dat var/stamped = 0 - + var/obj/item/weapon/card/id/guest/guest_pass = null // Guest pass attached to the ID /obj/item/weapon/card/id/New() ..() spawn(30) - if(ishuman(loc)) + if(ishuman(loc) && blood_type == "\[UNSET\]") var/mob/living/carbon/human/H = loc SetOwnerInfo(H) @@ -242,10 +242,10 @@ set name = "Remove Guest Pass" set category = "Object" set src in range(0) - + if(usr.stat || !usr.canmove || usr.restrained()) return - + if(guest_pass) to_chat(usr, "You remove the guest pass from this ID.") guest_pass.forceMove(get_turf(src)) @@ -253,6 +253,37 @@ else to_chat(usr, "There is no guest pass attached to this ID") +/obj/item/weapon/card/id/serialize() + var/list/data = ..() + + data["sex"] = sex + data["age"] = age + data["btype"] = blood_type + data["dna_hash"] = dna_hash + data["fprint_hash"] = fingerprint_hash + data["access"] = access + data["job"] = assignment + data["account"] = associated_account_number + data["owner"] = registered_name + data["mining"] = mining_points + return data + +/obj/item/weapon/card/id/deserialize(list/data) + sex = data["sex"] + age = data["age"] + blood_type = data["btype"] + dna_hash = data["dna_hash"] + fingerprint_hash = data["fprint_hash"] + access = data["access"] // No need for a copy, the list isn't getting touched + assignment = data["job"] + associated_account_number = data["account"] + registered_name = data["owner"] + mining_points = data["mining"] + // We'd need to use icon serialization(b64) to save the photo, and I don't feel like i + UpdateName() + RebuildHTML()t + ..() + /obj/item/weapon/card/id/silver name = "identification card" desc = "A silver card which shows honour and dedication." @@ -767,4 +798,4 @@ if("TDgreen") return "Thunderdome Green" else - return capitalize(skin) \ No newline at end of file + return capitalize(skin) diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index a0e3d4dd69e..23f8f9ea685 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -37,7 +37,9 @@ src.add_fingerprint(usr) return - +/obj/item/weapon/storage/belt/deserialize(list/data) + ..() + update_icon() /obj/item/weapon/storage/belt/utility name = "tool-belt" //Carn: utility belt is nicer, but it bamboozles the text parsing. diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm index e2324fcb434..e792d38afd7 100644 --- a/code/game/objects/items/weapons/storage/storage.dm +++ b/code/game/objects/items/weapons/storage/storage.dm @@ -541,3 +541,34 @@ return depth +/obj/item/weapon/storage/serialize() + var data = ..() + var/list/content_list = list() + data["content"] = content_list + data["slots"] = storage_slots + data["max_w_class"] = max_w_class + data["max_c_w_class"] = max_combined_w_class + for(var/thing in contents) + var/atom/movable/AM = thing + // This code does not watch out for infinite loops + // But then again a tesseract would destroy the server anyways + // Also I wish I could just insert a list instead of it reading it the wrong way + content_list.len++ + content_list[content_list.len] = AM.serialize() + return data + +/obj/item/weapon/storage/deserialize(list/data) + if(isnum(data["slots"])) + storage_slots = data["slots"] + if(isnum(data["max_w_class"])) + max_w_class = data["max_w_class"] + if(isnum(data["max_c_w_class"])) + max_combined_w_class = data["max_c_w_class"] + for(var/thing in contents) + qdel(thing) // out with the old + for(var/thing in data["content"]) + if(islist(thing)) + list_to_object(thing, src) + else + log_debug("Non-list thing: [thing]. We are a [name]") + ..() diff --git a/code/modules/clothing/suits/storage.dm b/code/modules/clothing/suits/storage.dm index 360c9b2915f..faf5f389e9d 100644 --- a/code/modules/clothing/suits/storage.dm +++ b/code/modules/clothing/suits/storage.dm @@ -50,3 +50,12 @@ if(istype(G.gift, /obj/item/weapon/storage)) L += G.gift:return_inv() return L + +/obj/item/clothing/suit/storage/serialize() + var/list/data = ..() + data["pockets"] = pockets.serialize() + return data + +/obj/item/clothing/suit/storage/deserialize(list/data) + qdel(pockets) + pockets = list_to_object(data["pockets"], src) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 12cabc68a5f..c63c03d8321 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -2037,10 +2037,20 @@ var/list/data = ..() var/list/limbs_list = list() var/list/organs_list = list() + var/list/equip_list = list() data["limbs"] = limbs_list data["iorgans"] = organs_list + data["equip"] = equip_list + data["dna"] = dna.serialize() + data["age"] = age + + // No being naked data["ushirt"] = undershirt + data["socks"] = socks + data["uwear"] = underwear + + // Limbs for(var/limb in organs_by_name) var/obj/item/organ/O = organs_by_name[limb] if(!O) @@ -2049,15 +2059,25 @@ limbs_list[limb] = O.serialize() + // Internal organs/augments for(var/organ in internal_organs) var/obj/item/organ/O = organ organs_list[O.name] = O.serialize() - return json_encode(data) + // Equipment + equip_list.len = slots_amt + for(var/i = 1, i < slots_amt, i++) + var/obj/item/thing = get_item_by_slot(i) + if(thing != null) + equip_list[i] = thing.serialize() + + return data /mob/living/carbon/human/deserialize(list/data) var/list/limbs_list = data["limbs"] var/list/organs_list = data["iorgans"] + var/list/equip_list = data["equip"] + var/turf/T = get_turf(src) if(!islist(data["limbs"])) throw EXCEPTION("Expected a limbs list, but found none") @@ -2067,7 +2087,10 @@ name = real_name UpdateAppearance() set_species(dna.species) + age = data["age"] undershirt = data["ushirt"] + underwear = data["uwear"] + socks = data["socks"] for(var/obj/item/organ/internal/iorgan in internal_organs) qdel(iorgan) @@ -2085,5 +2108,26 @@ for(var/organ in organs_list) // As above, "New" code handles insertion, DNA sync list_to_object(organs_list[organ], src) + + + // De-serialize equipment + // #1: Jumpsuit + // #2: Outer suit + // #3+: Everything else + if(islist(equip_list[slot_w_uniform])) + var/obj/item/clothing/C = list_to_object(equip_list[slot_w_uniform], T) + equip_to_slot_if_possible(C, slot_w_uniform) + + if(islist(equip_list[slot_wear_suit])) + var/obj/item/clothing/C = list_to_object(equip_list[slot_wear_suit], T) + equip_to_slot_if_possible(C, slot_wear_suit) + + for(var/i = 1, i < slots_amt, i++) + if(i == slot_w_uniform || i == slot_wear_suit) + continue + if(islist(equip_list[i])) + var/obj/item/clothing/C = list_to_object(equip_list[i], T) + equip_to_slot_if_possible(C, i) update_icons() + ..() From 11707edff80ff47314dc3e8f511c8782fcedcaf1 Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 27 Jul 2016 22:49:45 -0700 Subject: [PATCH 3/3] Now with less "t" --- code/game/objects/items/weapons/cards_ids.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index dd51696c118..d1721c5c247 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -281,7 +281,7 @@ mining_points = data["mining"] // We'd need to use icon serialization(b64) to save the photo, and I don't feel like i UpdateName() - RebuildHTML()t + RebuildHTML() ..() /obj/item/weapon/card/id/silver