diff --git a/.gitignore b/.gitignore
index 02f0c8cf790..9a9bbe0aafd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ data/
/_maps/map_files/backup/*
/_maps/map_files/**/backup/*
/_maps/map_files/**/*.dmm.backup
+/_maps/quicksave/*
/nano/debug.html
*.db
stddef.dm
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index b17cda4e560..e56ed3a4310 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -389,4 +389,41 @@ proc/checkhtml(var/t)
/proc/macro2html(text)
var/static/regex/text_macro = new("(\\xFF.)(.*)$")
- return text_macro.Replace(text, /proc/replace_text_macro)
\ No newline at end of file
+ return text_macro.Replace(text, /proc/replace_text_macro)
+
+/proc/dmm_encode(text)
+ // First, go through and nix out any of our escape sequences so we don't leave ourselves open to some escape sequence attack
+ // Some coder will probably despise me for this, years down the line
+
+ var/list/repl_chars = list("#?qt;", "#?lbr;", "#?rbr;")
+ for(var/char in repl_chars)
+ var/index = findtext(text, char)
+ var/keylength = length(char)
+ while(index)
+ log_debug("Bad string given to dmm encoder! [text]")
+ // Replace w/ underscore to prevent "{4;" from cheesing the radar
+ // Should probably also use canon text replacing procs
+ text = copytext(text, 1, index) + "_" + copytext(text, index+keylength)
+ index = findtext(text, char)
+
+ // Then, replace characters as normal
+ var/list/repl_chars_2 = list("\"" = "#?qt;", "{" = "#?lbr;", "}" = "#?rbr;")
+ for(var/char in repl_chars_2)
+ var/index = findtext(text, char)
+ var/keylength = length(char)
+ while(index)
+ text = copytext(text, 1, index) + repl_chars_2[char] + copytext(text, index+keylength)
+ index = findtext(text, char)
+ return text
+
+
+/proc/dmm_decode(text)
+ // Replace what we extracted above
+ var/list/repl_chars = list("#?qt;" = "\"", "#?lbr;" = "{", "#?rbr;" = "}")
+ for(var/char in repl_chars)
+ var/index = findtext(text, char)
+ var/keylength = length(char)
+ while(index)
+ text = copytext(text, 1, index) + repl_chars[char] + copytext(text, index+keylength)
+ index = findtext(text, char)
+ return text
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 158ca06480b..0435ead41b2 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -13,6 +13,7 @@
var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom.
var/simulated = 1 //filter for actions - used by lighting overlays
var/atom_say_verb = "says"
+ var/dont_save = 0 // For atoms that are temporary by necessity - like lighting overlays
///Chemistry.
var/datum/reagents/reagents = null
diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm
index a8a33784263..4224ad1aa5e 100644
--- a/code/game/dna/dna2.dm
+++ b/code/game/dna/dna2.dm
@@ -421,7 +421,7 @@ var/global/list/bad_blocks[0]
reg_dna[unique_enzymes] = character.real_name
// Hmm, I wonder how to go about this without a huge convention break
-/datum/dna/proc/serialize()
+/datum/dna/serialize()
var/data = list()
data["UE"] = unique_enzymes
data["SE"] = SE.Copy() // This is probably too lazy for my own good
@@ -432,7 +432,7 @@ var/global/list/bad_blocks[0]
data["real_name"] = real_name
return data
-/datum/dna/proc/deserialize(data)
+/datum/dna/deserialize(data)
unique_enzymes = data["UE"]
// The de-serializer is unlikely to tamper with the lists
SE = data["SE"]
@@ -441,4 +441,4 @@ var/global/list/bad_blocks[0]
UpdateSE()
species = data["species"]
b_type = data["b_type"]
- real_name = data["real_name"]
\ No newline at end of file
+ real_name = data["real_name"]
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index 5b6cfb5e73d..3d980e769e7 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -26,11 +26,13 @@
var/turf/T = loc
hide(T.intact)
- if(codes["patrol"])
+ if(!codes || !codes.len)
+ log_debug("Empty codes datum at ([x],[y],[z])")
+ if("patrol" in codes)
if(!navbeacons["[z]"])
navbeacons["[z]"] = list()
navbeacons["[z]"] += src //Register with the patrol list!
- if(codes["delivery"])
+ if("delivery" in codes)
deliverybeacons += src
deliverybeacontags += location
@@ -39,6 +41,15 @@
deliverybeacons -= src
return ..()
+/obj/machinery/navbeacon/serialize()
+ var/list/data = ..()
+ data["codes"] = codes
+ return data
+
+/obj/machinery/navbeacon/deserialize(list/data)
+ codes = data["codes"]
+ ..()
+
// set the transponder codes assoc list from codes_txt
/obj/machinery/navbeacon/proc/set_codes()
if(!codes_txt)
@@ -201,4 +212,4 @@ Transponder Codes:
"}
codes[newkey] = newval
- updateDialog()
\ No newline at end of file
+ updateDialog()
diff --git a/code/modules/admin/buildmode.dm b/code/modules/admin/buildmode.dm
index ed48b2dc6d0..f08121634d3 100644
--- a/code/modules/admin/buildmode.dm
+++ b/code/modules/admin/buildmode.dm
@@ -8,7 +8,8 @@
#define FILL_BUILDMODE 8
#define LINK_BUILDMODE 9
#define BOOM_BUILDMODE 10
-#define NUM_BUILDMODES 10
+#define SAVE_BUILDMODE 11
+#define NUM_BUILDMODES 11
/obj/screen/buildmode
icon = 'icons/misc/buildmode.dmi'
@@ -81,8 +82,11 @@
/obj/effect/buildmode_reticule/New(var/turf/t, var/client/c)
loc = t
I = image('icons/mob/blob.dmi', t, "marker",19.0,2) // Sprite reuse wooo
- cl = c
- cl.images += I
+ if(c)
+ cl = c
+ cl.images += I
+ else
+ log_debug("Buildmode reticule created without a client!")
/obj/effect/buildmode_reticule/proc/deselect()
qdel(src)
@@ -174,6 +178,8 @@
var/light = -1
var/flash = -1
var/flames = -1
+ // Saving mode
+ var/use_json = TRUE
/datum/click_intercept/buildmode/New(client/c)
..()
@@ -261,6 +267,13 @@
to_chat(user, "***********************************************************")
to_chat(user, "Mouse Button on obj = Kaboom")
to_chat(user, "***********************************************************")
+ if(SAVE_BUILDMODE)
+ to_chat(user, "***********************************************************")
+ to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner")
+ to_chat(user, "***********************************************************")
+
+
+
/datum/click_intercept/buildmode/proc/change_settings(mob/user)
switch(mode)
@@ -345,6 +358,9 @@
var/flames = input("Range of flames. -1 to none", text("Input")) as num|null
if(flames == null) flames = -1
+ if(SAVE_BUILDMODE)
+ use_json = (alert("Would you like to use json (Default is \"Yes\")?",,"Yes","No") == "Yes")
+
/datum/click_intercept/buildmode/proc/change_dir()
switch(build_dir)
if(NORTH)
@@ -625,3 +641,27 @@
link_lines += L2
if(BOOM_BUILDMODE)
explosion(object, devastation, heavy, light, flash, null, null,flames)
+ if(SAVE_BUILDMODE)
+ if(!cornerA)
+ cornerA = select_tile(get_turf(object))
+ return
+ if(!cornerB)
+ cornerB = select_tile(get_turf(object))
+ if(left_click)
+ if(cornerA && cornerB)
+ var/turf/A = get_turf(cornerA)
+ var/turf/B = get_turf(cornerB)
+ deselect_region() // So we don't read our own reticules
+ var/map_name = input(holder, "Please select a name for your map", "Buildmode", "")
+ if(map_name == "")
+ return
+ var/map_flags = 0
+ if(use_json)
+ map_flags = 32 // Magic number defined in `writer.dm` that I can't use directly
+ // because #defines are for some reason our coding standard
+ var/our_map = maploader.save_map(A,B,map_name,map_flags)
+ holder << ftp(our_map) // send the map they've made! Or are stealing, whatever
+ to_chat(holder, "Map saving complete! [our_map]")
+ return
+
+ deselect_region()
diff --git a/code/modules/awaymissions/maploader/reader.dm b/code/modules/awaymissions/maploader/reader.dm
index ea59dd1d518..9cb75d4a22b 100644
--- a/code/modules/awaymissions/maploader/reader.dm
+++ b/code/modules/awaymissions/maploader/reader.dm
@@ -228,10 +228,12 @@ var/global/dmm_suite/preloader/_preloader = new
var/full_def = trim_text(copytext(model, old_position, dpos)) //full definition, e.g : /obj/foo/bar{variables=derp}
var/variables_start = findtext(full_def, "{")
- var/atom_def = text2path(trim_text(copytext(full_def, 1, variables_start))) //path definition, e.g /obj/foo/bar
+ var/atom_text = trim_text(copytext(full_def, 1, variables_start))
+ var/atom_def = text2path(atom_text) //path definition, e.g /obj/foo/bar
old_position = dpos + 1
if(!atom_def) // Skip the item if the path does not exist. Fix your crap, mappers!
+ log_debug("Bad path: [atom_text]")
continue
members.Add(atom_def)
@@ -378,8 +380,15 @@ var/global/dmm_suite/preloader/_preloader = new
var/trim_right = trim_text(copytext(text,equal_position+1,position))//the content of the variable
//Check for string
+ // Make it read to the next delimiter, instead of the quote
if(findtext(trim_right,quote,1,2))
- trim_right = copytext(trim_right,2,findtext(trim_right,quote,3,0))
+ var/endquote = findtext(trim_right,quote,-1)
+ if(!endquote)
+ log_debug("Terminating quote not found!")
+ // Our map writer escapes quotes and curly brackets to avoid
+ // letting our simple parser choke on meanly-crafted names/etc
+ // - so we decode it here so it's back to good ol' legibility
+ trim_right = dmm_decode(copytext(trim_right,2,endquote))
//Check for number
else if(isnum(text2num(trim_right)))
@@ -430,14 +439,27 @@ var/global/dmm_suite/preloader/_preloader = new
parent_type = /datum
var/list/attributes
var/target_path
+ var/json_ready = 0
/dmm_suite/preloader/proc/setup(list/the_attributes, path)
if(the_attributes.len)
+ json_ready = 0
+ if("map_json_data" in the_attributes)
+ json_ready = 1
use_preloader = TRUE
attributes = the_attributes
target_path = path
/dmm_suite/preloader/proc/load(atom/what)
+ if(json_ready)
+ var/json_data = attributes["map_json_data"]
+ attributes -= "map_json_data"
+ json_data = dmm_decode(json_data)
+ try
+ what.deserialize(json_decode(json_data))
+ catch(var/exception/e)
+ log_debug("Bad json data: '[json_data]'")
+ throw e
for(var/attribute in attributes)
var/value = attributes[attribute]
if(islist(value))
diff --git a/code/modules/awaymissions/maploader/writer.dm b/code/modules/awaymissions/maploader/writer.dm
index 208d0935a32..842a51fbce5 100644
--- a/code/modules/awaymissions/maploader/writer.dm
+++ b/code/modules/awaymissions/maploader/writer.dm
@@ -4,10 +4,10 @@
#define DMM_IGNORE_NPCS 8
#define DMM_IGNORE_PLAYERS 16
#define DMM_IGNORE_MOBS 24
-dmm_suite{
- var{
- quote = "\""
- list/letter_digits = list(
+#define DMM_USE_JSON 32
+/dmm_suite
+ var/quote = "\""
+ var/list/letter_digits = list(
"a","b","c","d","e",
"f","g","h","i","j",
"k","l","m","n","o",
@@ -21,154 +21,202 @@ dmm_suite{
"U","V","W","X","Y",
"Z"
)
- }
- save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){
- //Check for illegal characters in file name... in a cheap way.
- if(!((ckeyEx(map_name)==map_name) && ckeyEx(map_name))){
- CRASH("Invalid text supplied to proc save_map, invalid characters or empty string.")
- }
- //Check for valid turfs.
- if(!isturf(t1) || !isturf(t2)){
- CRASH("Invalid arguments supplied to proc save_map, arguments were not turfs.")
- }
- var/file_text = write_map(t1,t2,flags)
- if(fexists("[map_name].dmm")){
- fdel("[map_name].dmm")
- }
- var/saved_map = file("[map_name].dmm")
- saved_map << file_text
- return saved_map
- }
- write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){
- //Check for valid turfs.
- if(!isturf(t1) || !isturf(t2)){
- CRASH("Invalid arguments supplied to proc write_map, arguments were not turfs.")
- }
- var/turf/nw = locate(min(t1.x,t2.x),max(t1.y,t2.y),min(t1.z,t2.z))
- var/turf/se = locate(max(t1.x,t2.x),min(t1.y,t2.y),max(t1.z,t2.z))
- var/list/templates[0]
- var/template_buffer = {""}
- var/dmm_text = {""}
- for(var/pos_z in nw.z to se.z){
- for(var/pos_y in nw.y to se.y){
- for(var/pos_x in nw.x to se.x){
- var/turf/test_turf = locate(pos_x,pos_y,pos_z)
- var/test_template = make_template(test_turf, flags)
- var/template_number = templates.Find(test_template)
- if(!template_number){
- templates.Add(test_template)
- template_number = templates.len
- }
- template_buffer += "[template_number],"
- }
- template_buffer += ";"
- }
- template_buffer += "."
- }
- var/key_length = round/*floor*/(log(letter_digits.len,templates.len-1)+1)
- var/list/keys[templates.len]
- for(var/key_pos in 1 to templates.len){
- keys[key_pos] = get_model_key(key_pos,key_length)
- dmm_text += {""[keys[key_pos]]" = ([templates[key_pos]])\n"}
- }
- var/z_level = 0
- for(var/z_pos=1;TRUE;z_pos=findtext(template_buffer,".",z_pos)+1){
- if(z_pos>=length(template_buffer)){break}
- if(z_level){dmm_text+={"\n"}}
- dmm_text += {"\n(1,1,[++z_level]) = {"\n"}
- var/z_block = copytext(template_buffer,z_pos,findtext(template_buffer,".",z_pos))
- for(var/y_pos=1;TRUE;y_pos=findtext(z_block,";",y_pos)+1){
- if(y_pos>=length(z_block)){break}
- var/y_block = copytext(z_block,y_pos,findtext(z_block,";",y_pos))
- for(var/x_pos=1;TRUE;x_pos=findtext(y_block,",",x_pos)+1){
- if(x_pos>=length(y_block)){break}
- var/x_block = copytext(y_block,x_pos,findtext(y_block,",",x_pos))
- var/key_number = text2num(x_block)
- var/temp_key = keys[key_number]
- dmm_text += temp_key
- sleep(-1)
- }
- dmm_text += {"\n"}
- sleep(-1)
- }
- dmm_text += {"\"}"}
- sleep(-1)
- }
- return dmm_text
- }
- proc{
- make_template(var/turf/model as turf, var/flags as num){
- var/template = ""
- var/obj_template = ""
- var/mob_template = ""
- var/turf_template = ""
- if(!(flags & DMM_IGNORE_TURFS)){
- turf_template = "[model.type][check_attributes(model)],"
- } else{ turf_template = "[world.turf],"}
- var/area_template = ""
- if(!(flags & DMM_IGNORE_OBJS)){
- for(var/obj/O in model.contents){
- obj_template += "[O.type][check_attributes(O)],"
- }
- }
- for(var/mob/M in model.contents){
- if(M.client){
- if(!(flags & DMM_IGNORE_PLAYERS)){
- mob_template += "[M.type][check_attributes(M)],"
- }
- }
- else{
- if(!(flags & DMM_IGNORE_NPCS)){
- mob_template += "[M.type][check_attributes(M)],"
- }
- }
- }
- if(!(flags & DMM_IGNORE_AREAS)){
- var/area/m_area = model.loc
- area_template = "[m_area.type][check_attributes(m_area)]"
- } else{ area_template = "[world.area]"}
- template = "[obj_template][mob_template][turf_template][area_template]"
- return template
- }
- check_attributes(var/atom/A){
- var/attributes_text = {"{"}
- for(var/V in A.vars){
- sleep(-1)
- if((!issaved(A.vars[V])) || (A.vars[V]==initial(A.vars[V]))){continue}
- if(istext(A.vars[V])){
- attributes_text += {"[V] = "[A.vars[V]]""}
- }
- else if(isnum(A.vars[V])||ispath(A.vars[V])){
- attributes_text += {"[V] = [A.vars[V]]"}
- }
- else if(isicon(A.vars[V])||isfile(A.vars[V])){
- attributes_text += {"[V] = '[A.vars[V]]'"}
- }
- else{
- continue
- }
- if(attributes_text != {"{"}){
- attributes_text+={"; "}
- }
- }
- if(attributes_text=={"{"}){
- return
- }
- if(copytext(attributes_text, length(attributes_text)-1, 0) == {"; "}){
- attributes_text = copytext(attributes_text, 1, length(attributes_text)-1)
- }
- attributes_text += {"}"}
- return attributes_text
- }
- get_model_key(var/which as num, var/key_length as num){
- var/key = ""
- var/working_digit = which-1
- for(var/digit_pos in key_length to 1 step -1){
- var/place_value = round/*floor*/(working_digit/(letter_digits.len**(digit_pos-1)))
- working_digit-=place_value*(letter_digits.len**(digit_pos-1))
- key = "[key][letter_digits[place_value+1]]"
- }
- return key
- }
- }
- }
\ No newline at end of file
+
+/dmm_suite/save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num)
+ //Check for illegal characters in file name... in a cheap way.
+ if(!((ckeyEx(map_name)==map_name) && ckeyEx(map_name)))
+ CRASH("Invalid text supplied to proc save_map, invalid characters or empty string.")
+ //Check for valid turfs.
+ if(!isturf(t1) || !isturf(t2))
+ CRASH("Invalid arguments supplied to proc save_map, arguments were not turfs.")
+
+ var/map_prefix = "_maps/quicksave/"
+ var/map_path = "[map_prefix][map_name].dmm"
+ if(fexists(map_path))
+ fdel(map_path)
+ var/saved_map = file(map_path)
+ var/map_text = write_map(t1,t2,flags,saved_map)
+ saved_map << map_text
+ return saved_map
+
+/dmm_suite/write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num)
+ //Check for valid turfs.
+ if(!isturf(t1) || !isturf(t2))
+ CRASH("Invalid arguments supplied to proc write_map, arguments were not turfs.")
+
+ var/turf/ne = locate(max(t1.x,t2.x),max(t1.y,t2.y),max(t1.z,t2.z)) // Outer corner
+ var/turf/sw = locate(min(t1.x,t2.x),min(t1.y,t2.y),min(t1.z,t2.z)) // Inner corner
+ var/list/templates[0]
+ var/list/template_buffer = list()
+ var/template_buffer_text
+ var/dmm_text = ""
+
+ var/total_timer = start_watch()
+ var/timer = start_watch()
+ log_debug("Reading turfs...")
+ // Read the contents of all the turfs we were given
+ for(var/pos_z in sw.z to ne.z)
+ for(var/pos_y in ne.y to sw.y step -1) // We're reversing this because the map format is silly
+ for(var/pos_x in sw.x to ne.x)
+ var/turf/test_turf = locate(pos_x,pos_y,pos_z)
+ var/test_template = make_template(test_turf, flags)
+ var/template_number = templates.Find(test_template)
+ if(!template_number)
+ templates.Add(test_template)
+ template_number = templates.len
+ template_buffer += "[template_number],"
+ CHECK_TICK
+
+ template_buffer += ";"
+
+ template_buffer += "."
+ template_buffer_text = jointext(template_buffer,"")
+ log_debug("Reading turfs took [stop_watch(timer)]s.")
+
+ if(templates.len == 0)
+ CRASH("No templates found!")
+ var/key_length = round/*floor*/(log(letter_digits.len,templates.len-1)+1)
+ var/list/keys[templates.len]
+ // Write the list of key/model pairs to the file
+ timer = start_watch()
+ log_debug("Writing out key/model pairs to file header...")
+ var/list/key_models = list()
+ for(var/key_pos in 1 to templates.len)
+ keys[key_pos] = get_model_key(key_pos,key_length)
+ key_models += "\"[keys[key_pos]]\" = ([templates[key_pos]])\n"
+ CHECK_TICK
+ dmm_text += jointext(key_models,"")
+ log_debug("Writing key/model pairs complete, took [stop_watch(timer)]s.")
+
+ var/z_level = 0
+ // Loop over all z in our zone
+ timer = start_watch()
+ log_debug("Writing out key map...")
+ var/list/key_map = list()
+ for(var/z_pos=1;TRUE;z_pos=findtext(template_buffer_text,".",z_pos)+1)
+ if(z_pos>=length(template_buffer_text)) break
+ if(z_level) key_map += "\n"
+ key_map += "\n(1,1,[++z_level]) = {\"\n"
+ var/z_block = copytext(template_buffer_text,z_pos,findtext(template_buffer_text,".",z_pos))
+ for(var/y_pos=1;TRUE;y_pos=findtext(z_block,";",y_pos)+1)
+ if(y_pos>=length(z_block)) break
+ var/y_block = copytext(z_block,y_pos,findtext(z_block,";",y_pos))
+ // A row of keys
+ for(var/x_pos=1;TRUE;x_pos=findtext(y_block,",",x_pos)+1)
+ if(x_pos>=length(y_block)) break
+ var/x_block = copytext(y_block,x_pos,findtext(y_block,",",x_pos))
+ var/key_number = text2num(x_block)
+ var/temp_key = keys[key_number]
+ key_map += temp_key
+ CHECK_TICK
+ key_map += "\n"
+ key_map += "\"}"
+ dmm_text += jointext(key_map,"")
+ log_debug("Writing key map complete, took [stop_watch(timer)]s.")
+ log_debug("TOTAL TIME: [stop_watch(total_timer)]s.")
+ return dmm_text
+
+/dmm_suite/proc/make_template(var/turf/model as turf, var/flags as num)
+ var/use_json = 0
+ if(flags & DMM_USE_JSON)
+ use_json = 1
+ var/template = ""
+ var/turf_template = ""
+ var/list/obj_template = list()
+ var/list/mob_template = list()
+ var/area_template = ""
+
+
+
+ // Turf
+ if(!(flags & DMM_IGNORE_TURFS))
+ turf_template = "[model.type][check_attributes(model,use_json=use_json)],"
+ else turf_template = "[world.turf],"
+
+ // Objects loop
+ if(!(flags & DMM_IGNORE_OBJS))
+ for(var/obj/O in model.contents)
+ if(O.dont_save || !isnull(O.gcDestroyed))
+ continue
+ obj_template += "[O.type][check_attributes(O,use_json=use_json)],"
+
+ // Mobs Loop
+ for(var/mob/M in model.contents)
+ if(M.dont_save || !isnull(M.gcDestroyed))
+ continue
+ if(M.client)
+ if(!(flags & DMM_IGNORE_PLAYERS))
+ mob_template += "[M.type][check_attributes(M,use_json=use_json)],"
+ else
+ if(!(flags & DMM_IGNORE_NPCS))
+ mob_template += "[M.type][check_attributes(M,use_json=use_json)],"
+
+ // Area
+ if(!(flags & DMM_IGNORE_AREAS))
+ var/area/m_area = model.loc
+ area_template = "[m_area.type][check_attributes(m_area,use_json=use_json)]"
+ else area_template = "[world.area]"
+
+ template = "[jointext(obj_template,"")][jointext(mob_template,"")][turf_template][area_template]"
+ return template
+
+/dmm_suite/proc/check_attributes(var/atom/A,use_json=0)
+ var/attributes_text = "{"
+ var/list/attributes = list()
+ if(!use_json)
+ for(var/V in A.vars)
+ CHECK_TICK
+ if((!issaved(A.vars[V])) || (A.vars[V]==initial(A.vars[V]))) continue
+
+ attributes += var_to_dmm(A.vars[V], V)
+ else
+ var/list/yeah = A.serialize()
+ // We'll want to write out vars that are important to the editor
+ // So that the map is legible as before
+ for(var/thing in A.map_important_vars())
+ // Save vars that are important for the map editor, so that
+ // json-encoded maps are legible for standard editors
+ if(A.vars[thing] != initial(A.vars[thing]))
+ yeah -= thing
+ attributes += var_to_dmm(A.vars[thing],thing)
+
+ // Remove useless info
+ yeah -= "type"
+ if(yeah.len)
+ var/json_stuff = json_encode(yeah)
+ attributes += var_to_dmm(json_stuff, "map_json_data")
+ if(attributes.len == 0)
+ return
+
+ // Trim a trailing semicolon - `var_to_dmm` always appends a semicolon,
+ // so the last one will be trailing.
+ if(copytext(attributes_text, length(attributes_text)-1, 0) == "; ")
+ attributes_text = copytext(attributes_text, 1, length(attributes_text)-1)
+ attributes_text = "{[jointext(attributes,"; ")]}"
+ return attributes_text
+
+
+/dmm_suite/proc/get_model_key(var/which as num, var/key_length as num)
+ var/list/key = list()
+ var/working_digit = which-1
+ for(var/digit_pos in key_length to 1 step -1)
+ var/place_value = round/*floor*/(working_digit/(letter_digits.len**(digit_pos-1)))
+ working_digit-=place_value*(letter_digits.len**(digit_pos-1))
+ key += letter_digits[place_value+1]
+ return jointext(key,"")
+
+
+/dmm_suite/proc/var_to_dmm(attr, name)
+ if(istext(attr))
+ // dmm_encode will strip out characters that would be capable of disrupting
+ // parsing - namely, quotes and curly braces
+ return "[name] = \"[dmm_encode(attr)]\""
+ else if(isnum(attr)||ispath(attr))
+ return "[name] = [attr]"
+ else if(isicon(attr)||isfile(attr))
+ if(length("[attr]") == 0)
+ // The DM map reader is unable to read files that have a '' file/icon entry
+ return
+ return "[name] = '[attr]'"
+ else
+ return ""
diff --git a/code/modules/persistence/persistence.dm b/code/modules/persistence/persistence.dm
index cc5a7684947..4c1b40eaa98 100644
--- a/code/modules/persistence/persistence.dm
+++ b/code/modules/persistence/persistence.dm
@@ -5,8 +5,9 @@
* 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]")
+/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
@@ -17,9 +18,45 @@
* 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)
+/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]
+ ..()
+
+
/proc/json_to_object(var/json_data, var/loc)
var/data = json_decode(json_data)
return list_to_object(data, loc)
diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi
index 062d5b404c8..cc54e5aec0f 100644
Binary files a/icons/misc/buildmode.dmi and b/icons/misc/buildmode.dmi differ