diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 2f9ab4112e..a792ee280f 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -1,6 +1,12 @@ /////////////////////////////////////////////////////////////// //SS13 Optimized Map loader ////////////////////////////////////////////////////////////// +/* + * Notes: + * This does NOT support map files where more than one Z is in each grid set, as defined in the file. + * MultiZ map files ARE supported (however very much discouraged), as long as each gridset only contains one z. + * This assumes that for the most part, map files are properly formed, either DMM or TGM standard formats. If you feed it bad data, expect to crash. + */ #define SPACE_KEY "space" /datum/grid_set @@ -9,6 +15,12 @@ var/zcrd var/gridLines +/datum/grid_set/proc/height(key_len) + return length(gridLines) + +/datum/grid_set/proc/width(key_len) + return gridLines[1] / key_len + /datum/parsed_map var/original_path var/key_len = 0 @@ -43,7 +55,7 @@ /// - `cropMap`: When true, the map will be cropped to fit the existing world dimensions (Optional). /// - `measureOnly`: When true, no changes will be made to the world (Optional). /// - `no_changeturf`: When true, [turf/AfterChange] won't be called on loaded turfs -/// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional). +/// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the game world) to crop to (Optional). /// - `placeOnTop`: Whether to use [turf/PlaceOnTop] rather than [turf/ChangeTurf] (Optional). /proc/load_map( dmm_file as file, @@ -60,25 +72,37 @@ placeOnTop = FALSE as num, orientation = SOUTH as num, annihilate_tiles = FALSE, - z_lower = -INFINITY as num, - z_upper = INFINITY as num + crop_relative_to_game_world = TRUE ) - var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly) + var/datum/parsed_map/parsed = new(dmm_file, measureOnly = measureOnly) if(parsed.bounds && !measureOnly) parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles) return parsed -/// Parse a map, possibly cropping it. -//WHY THE HECK DO WE EVEN SUPPORT NEGATIVE COORDINATES, ALL IT IS IS A WASTE OF TIME AND CPU!!!??? -//DO NOT USE THIS TO TRIM MAPS UNLESS STRICTLY NEEDED! IT IS EXTREMELY EXPENSIVE TO DO SO! +/** + * Parse a map, possibly cropping it. + * Do not use the crop function unless strictly necessary. + * WARNING: Crop function crops based on the tiles you'd see in the map editor. If you're planning to load it in in a different orientation later, you better have done the math. + * It's recommended that you do not crop using this at all. + */ /datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, measureOnly = FALSE) + _parse(tfile, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly) + +/datum/parsed_map/proc/_parse(tfile, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly) + var/static/parsing = FALSE + UNTIL(!parsing) + // do not multithread this or bad things happen + parsing = TRUE + _do_parse(tfile, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly) + parsing = FALSE + +/datum/parsed_map/proc/_do_parse(tfile, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly) if(isfile(tfile)) original_path = "[tfile]" tfile = file2text(tfile) else if(isnull(tfile)) // create a new datum without loading a map return - bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) ASSERT(x_upper >= x_lower) ASSERT(y_upper >= y_lower) @@ -107,12 +131,13 @@ if(!key_len) CRASH("Coords before model definition in DMM") + // NOTE: We are assuming each coordset only contains one z. + var/curr_z = text2num(dmmRegex.group[5]) + if(curr_z < z_lower || curr_z > z_upper) + continue + var/curr_x = text2num(dmmRegex.group[3]) var/curr_y = text2num(dmmRegex.group[4]) - var/curr_z = text2num(dmmRegex.group[5]) - - if(curr_x < x_lower || curr_y < y_lower || curr_z < z_lower || curr_z > z_upper) - continue var/datum/grid_set/gridSet = new @@ -120,12 +145,6 @@ gridSet.ycrd = curr_y gridSet.zcrd = curr_z - bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x) //since down is up for y/gridlines, we now know the lower left corner. - bounds[MAP_MINY] = min(bounds[MAP_MINY], curr_y) - bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_z) - - bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z) //we know max z now - var/list/gridLines = splittext(dmmRegex.group[6], "\n") gridSet.gridLines = gridLines @@ -134,31 +153,36 @@ if(leadingBlanks > 1) gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines. - gridSets += gridSet - var/lines = length(gridLines) - if(lines) - if(gridLines[gridLines.len] == "") - gridLines.Cut(gridLines.len) // Remove only one blank line at the end. - var/right_length = y_upper - curr_y + 1 - if(lines > right_length) - gridLines.len = right_length //this can't be negative due to our ASSERTions above, hopefully. - - if(!gridLines.len) // Skip it if there's no content. + if(lines && gridLines[lines] == "") + // remove one trailing blank line + gridLines.len-- + lines-- + // y crop + var/right_length = y_upper - curr_y + 1 + if(lines > right_length) + lines = gridLines.len = right_length + if(!lines) // blank continue - //do not use curr_y after this point, ycrd has changed. use it before because local var. - gridSet.ycrd += gridLines.len - 1 // Start at the top and work down - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd) //we know max y now + var/width = length(gridLines[1]) / key_len + // x crop + var/right_width = x_upper - curr_x + 1 + if(width > right_width) + for(var/i in 1 to lines) + gridLines[i] = copytext(gridLines[i], 1, key_len * right_width) + + // during the actual load we're starting at the top and working our way down + gridSet.ycrd += lines - 1 - var/linelength = length(gridLines[1]) //yes it only samples the first line, this is why you use TGM instead of DMM! - var/xlength = linelength / key_len - - var/maxx = gridSet.xcrd + xlength - 1 - if(maxx > x_upper) - for(var/i in 1 to length(gridLines)) - gridLines[i] = copytext(gridLines[i], 1, key_len * (x_upper - curr_x + 1)) - bounds[MAP_MAXX] = max(bounds[MAP_MAXX], maxx) + // Safe to proceed, commit gridset to list and record coords. + gridSets += gridSet + bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], curr_x + width - 1) + bounds[MAP_MINY] = min(bounds[MAP_MINY], curr_y) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], curr_y + lines - 1) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_z) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z) CHECK_TICK // Indicate failure to parse any coordinates by nulling bounds @@ -172,7 +196,8 @@ /datum/parsed_map/Destroy() if(template_host && template_host.cached_map == src) template_host.cached_map = null - return ..() + . = ..() + return QDEL_HINT_HARDDEL_NOW /// Load the parsed map into the world. See [/proc/load_map] for arguments. /datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles, datum/map_orientation_pattern/forced_pattern) @@ -197,6 +222,8 @@ var/yi = mode.yi var/turn_angle = round(SIMPLIFY_DEGREES(mode.turn_angle), 90) var/delta_swap = x_offset - y_offset + // less checks later + var/do_crop = x_lower > -INFINITY || x_upper < INFINITY || y_lower > -INFINITY || y_upper < INFINITY for(var/__I in gridSets) var/datum/grid_set/gridset = __I @@ -221,6 +248,8 @@ for(var/pos = 1 to (length(line) - key_len + 1) step key_len) var/placement_x = swap_xy? (actual_y + delta_swap) : actual_x var/placement_y = swap_xy? (actual_x - delta_swap) : actual_y + if(do_crop && ((placement_x < x_lower) || (placement_x > x_upper) || (placement_y < y_lower) || (placement_y > y_upper))) + continue if(placement_x > world.maxx) if(cropMap) actual_x += xi @@ -532,6 +561,7 @@ // fallback: string return text -/datum/parsed_map/Destroy() - ..() - return QDEL_HINT_HARDDEL_NOW +/datum/parsed_map/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, dmmRegex) || var_name == NAMEOF(src, trimQuotesRegex) || var_name == NAMEOF(src, trimRegex)) + return FALSE + return ..() diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm index 20ba10b0e1..d9380a40dd 100644 --- a/code/modules/procedural_mapping/mapGenerators/repair.dm +++ b/code/modules/procedural_mapping/mapGenerators/repair.dm @@ -26,8 +26,8 @@ var/z_offset = SSmapping.station_start var/list/bounds for (var/path in SSmapping.config.GetFullMapPaths()) - var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, measureOnly = FALSE, no_changeturf = FALSE, cropMap=TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high) - bounds = parsed?.bounds + var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, orientation = SSmapping.config.orientation, cropMap = TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high) + bounds = parsed.bounds z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1 var/list/obj/machinery/atmospherics/atmos_machines = list()