diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 19ed2c06383..3fe80b245e9 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -401,7 +401,9 @@ proc/checkhtml(var/t) var/keylength = length(char) while(index) log_debug("Bad string given to dmm encoder! [text]") - text = copytext(text, 1, index) + copytext(text, index+keylength) + // 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 diff --git a/code/modules/awaymissions/maploader/reader.dm b/code/modules/awaymissions/maploader/reader.dm index 55125186da9..9cb75d4a22b 100644 --- a/code/modules/awaymissions/maploader/reader.dm +++ b/code/modules/awaymissions/maploader/reader.dm @@ -385,7 +385,10 @@ var/global/dmm_suite/preloader/_preloader = new var/endquote = findtext(trim_right,quote,-1) if(!endquote) log_debug("Terminating quote not found!") - trim_right = copytext(trim_right,2,endquote) + // 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))) @@ -450,18 +453,18 @@ var/global/dmm_suite/preloader/_preloader = new /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 - else - for(var/attribute in attributes) - var/value = attributes[attribute] - if(islist(value)) - value = deepCopyList(value) - what.vars[attribute] = value + for(var/attribute in attributes) + var/value = attributes[attribute] + if(islist(value)) + value = deepCopyList(value) + what.vars[attribute] = value use_preloader = FALSE // If the map loader fails, make this safe diff --git a/code/modules/awaymissions/maploader/writer.dm b/code/modules/awaymissions/maploader/writer.dm index 3de76ccefa7..c5cac261c46 100644 --- a/code/modules/awaymissions/maploader/writer.dm +++ b/code/modules/awaymissions/maploader/writer.dm @@ -5,10 +5,9 @@ #define DMM_IGNORE_PLAYERS 16 #define DMM_IGNORE_MOBS 24 #define DMM_USE_JSON 32 -dmm_suite{ - var{ - quote = "\"" - list/letter_digits = list( +/dmm_suite + var/quote = "\"" + var/list/letter_digits = list( "a","b","c","d","e", "f","g","h","i","j", "k","l","m","n","o", @@ -22,180 +21,191 @@ 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/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) // We give the map writer the file directly - // Because repeated string appending is super murder for performance - var/map_text = write_map(t1,t2,flags,saved_map) - saved_map << map_text - return saved_map - } - write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num, var/map_file as file){ - //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/template_buffer = {""} - var/buffer_line = {""} - var/dmm_text = {""} - for(var/pos_z in sw.z to ne.z){ - for(var/pos_y = ne.y, pos_y >= sw.y, pos_y--){ // 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 - } - buffer_line += "[template_number]," - CHECK_TICK - } - template_buffer += "[buffer_line];" - buffer_line = "" - } - template_buffer += "." - } - 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] - 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] - buffer_line += temp_key - CHECK_TICK - } - dmm_text += {"[buffer_line]\n"} - buffer_line = "" - } - dmm_text += {"\"}"} - } - return dmm_text - } - 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/obj_template = "" - var/mob_template = "" - var/turf_template = "" - if(!(flags & DMM_IGNORE_TURFS)){ - turf_template = "[model.type][check_attributes(model,use_json=use_json)]," - } else{ turf_template = "[world.turf],"} - var/area_template = "" - if(!(flags & DMM_IGNORE_OBJS)){ - for(var/obj/O in model.contents){ - if(O.dont_save) - continue - obj_template += "[O.type][check_attributes(O,use_json=use_json)]," - } - } - for(var/mob/M in model.contents){ - if(M.dont_save) - 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)]," - } - } - } - 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 = "[obj_template][mob_template][turf_template][area_template]" - return template - } - check_attributes(var/atom/A,use_json=0){ - var/attributes_text = {"{"} - if(!use_json){ - 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+={"; "} - } - } - } else { - var/list/yeah = A.serialize() - // Remove useless info - yeah -= "type" - if(yeah.len) { - var/json_stuff = json_encode(yeah) - json_stuff = dmm_encode(json_stuff) - attributes_text += {"map_json_data = "[json_stuff]""} - } - } - 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 - } - } - } +/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/template_buffer = "" + var/buffer_line = "" + 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 = ne.y, pos_y >= sw.y, pos_y--) // 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 + buffer_line += "[template_number]," + CHECK_TICK + + template_buffer += "[buffer_line];" + buffer_line = "" + + 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...") + 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" + 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...") + 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)) + // 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] + buffer_line += temp_key + CHECK_TICK + dmm_text += "[buffer_line]\n" + buffer_line = "" + dmm_text += "\"}" + 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/obj_template = "" + var/mob_template = "" + 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 = "[obj_template][mob_template][turf_template][area_template]" + return template + +/dmm_suite/proc/check_attributes(var/atom/A,use_json=0) + var/attributes_text = "{" + if(!use_json) + for(var/V in A.vars) + sleep(-1) + if((!issaved(A.vars[V])) || (A.vars[V]==initial(A.vars[V]))) continue + attributes_text += 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_text += var_to_dmm(A.vars[thing],thing) + + // Remove useless info + yeah -= "type" + if(yeah.len) + var/json_stuff = json_encode(yeah) + attributes_text += var_to_dmm(json_stuff, "map_json_data") + if(attributes_text == "{") + 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 += "}" + return attributes_text + + +/dmm_suite/proc/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 + + +/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)) + return "[name] = '[attr]'; " + else + return "" diff --git a/code/modules/persistence/persistence.dm b/code/modules/persistence/persistence.dm index a803d6b2eb4..e6de3f5d138 100644 --- a/code/modules/persistence/persistence.dm +++ b/code/modules/persistence/persistence.dm @@ -21,20 +21,37 @@ /datum/proc/deserialize(var/list/data) return - /atom - // This is so specific atoms can override these, and ignore certain ones - var/list/vars_to_save = list("dir","name","color","icon","icon_state", "pixel_x", "pixel_y") + // 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("dir","name","color","icon","icon_state", "pixel_x", "pixel_y") + +/atom/proc/map_important_vars() + // A list of important things to save in the map editor + return list("dir","name","color","icon","icon_state", "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) + 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) + for(var/thing in vars_to_save()) if(thing in data) vars[thing] = data[thing] ..()