diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 8e5ae046c7c..93e50f68107 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -33,7 +33,7 @@ SUBSYSTEM_DEF(mapping) var/list/z_list var/datum/space_level/transit var/datum/space_level/empty_space - var/dmm_suite/loader + var/datum/maploader/loader /datum/controller/subsystem/mapping/PreInit() if(!config) @@ -178,7 +178,8 @@ SUBSYSTEM_DEF(mapping) var/total_z = 0 for (var/file in files) var/full_path = "_maps/[path]/[file]" - var/bounds = loader.load_map(file(full_path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE) + var/datum/parsed_map/pm = loader.load_map(file(full_path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE) + var/bounds = pm?.bounds files[file] = total_z // save the start Z of this file total_z += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1 @@ -202,7 +203,8 @@ SUBSYSTEM_DEF(mapping) // load the maps for (var/file in files) var/full_path = "_maps/[path]/[file]" - if(!loader.load_map(file(full_path), 0, 0, start_z + files[file], no_changeturf = TRUE)) + var/datum/parsed_map/parsed = loader.load_map(file(full_path), 0, 0, start_z + files[file], no_changeturf = TRUE) + if(!parsed?.bounds) errorList |= full_path if(!silent) INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") diff --git a/code/modules/mapping/dmm_suite.dm b/code/modules/mapping/dmm_suite.dm index 2d2f8888930..625350b3ad9 100644 --- a/code/modules/mapping/dmm_suite.dm +++ b/code/modules/mapping/dmm_suite.dm @@ -1,4 +1,4 @@ -dmm_suite{ +/datum/maploader{ /* dmm_suite version 1.0 diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 2cc5a74b6ba..295efe339f4 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -4,7 +4,7 @@ var/height = 0 var/mappath = null var/loaded = 0 // Times loaded this round - var/static/dmm_suite/maploader = new + var/static/datum/maploader/maploader = new /datum/map_template/New(path = null, rename = null) if(path) @@ -15,7 +15,8 @@ name = rename /datum/map_template/proc/preload_size(path) - var/bounds = maploader.load_map(file(path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE) + var/datum/parsed_map/parsed = maploader.load_map(file(path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE) + var/bounds = parsed?.bounds if(bounds) width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1 height = bounds[MAP_MAXY] @@ -53,7 +54,8 @@ var/y = round((world.maxy - height)/2) var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(ZTRAIT_AWAY = TRUE)) - var/list/bounds = maploader.load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + var/datum/parsed_map/parsed = maploader.load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + var/list/bounds = parsed.bounds if(!bounds) return FALSE @@ -76,7 +78,8 @@ if(T.y+height > world.maxy) return - var/list/bounds = maploader.load_map(file(mappath), T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + var/datum/parsed_map/parsed = maploader.load_map(file(mappath), T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + var/list/bounds = parsed?.bounds if(!bounds) return diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index dfc75c32ee4..10b84d1109a 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -1,22 +1,33 @@ /////////////////////////////////////////////////////////////// //SS13 Optimized Map loader ////////////////////////////////////////////////////////////// - +#define SPACE_KEY "space" //global datum that will preload variables on atoms instanciation GLOBAL_VAR_INIT(use_preloader, FALSE) -GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) +GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) -/dmm_suite +/datum/grid_set + var/xcrd + var/ycrd + var/zcrd + var/xcrdStart + var/gridLines + +/datum/parsed_map + var/list/grid_models = list() + var/list/gridSets = list() + var/list/bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + var/key_len = 0 + +/datum/maploader // /"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}/g var/static/regex/dmmRegex = new/regex({""(\[a-zA-Z]+)" = \\(((?:.|\n)*?)\\)\n(?!\t)|\\((\\d+),(\\d+),(\\d+)\\) = \\{"(\[a-zA-Z\n]*)"\\}"}, "g") // /^[\s\n]+"?|"?[\s\n]+$|^"|"$/g var/static/regex/trimQuotesRegex = new/regex({"^\[\\s\n]+"?|"?\[\\s\n]+$|^"|"$"}, "g") // /^[\s\n]+|[\s\n]+$/ var/static/regex/trimRegex = new/regex("^\[\\s\n]+|\[\\s\n]+$", "g") - var/static/list/modelCache = list() - var/static/space_key #ifdef TESTING - var/static/turfsSkipped + var/turfsSkipped #endif /** @@ -29,10 +40,9 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) * 2) Read the map line by line, parsing the result (using parse_grid) * */ -/dmm_suite/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, lower_crop_x as num, lower_crop_y as num, upper_crop_x as num, upper_crop_y as num, placeOnTop as num) +/datum/maploader/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, lower_crop_x as num, lower_crop_y as num, upper_crop_x as num, upper_crop_y as num, placeOnTop as num) //How I wish for RAII Master.StartLoadingMap() - space_key = null #ifdef TESTING turfsSkipped = 0 #endif @@ -43,24 +53,10 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) #endif Master.StopLoadingMap() -/dmm_suite/proc/load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE) - var/tfile = dmm_file//the map file we're creating - if(isfile(tfile)) - tfile = file2text(tfile) - - if(!x_offset) - x_offset = 1 - if(!y_offset) - y_offset = 1 - if(!z_offset) - z_offset = world.maxz + 1 - - var/list/bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) - var/list/grid_models = list() - var/key_len = 0 - +/datum/parsed_map/New(tfile, x_offset, y_offset, z_offset, x_lower, x_upper, y_lower, y_upper, measureOnly, regex/dmmRegex, cropMap) var/stored_index = 1 + //multiz lool while(dmmRegex.Find(tfile, stored_index)) stored_index = dmmRegex.next @@ -73,41 +69,33 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) if(!key_len) key_len = length(key) else - throw EXCEPTION("Inconsistent key length in DMM") + CRASH("Inconsistent key length in DMM") if(!measureOnly) grid_models[key] = dmmRegex.group[2] // (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} else if(dmmRegex.group[3]) // Coords if(!key_len) - throw EXCEPTION("Coords before model definition in DMM") + CRASH("Coords before model definition in DMM") var/curr_x = text2num(dmmRegex.group[3]) if(curr_x < x_lower || curr_x > x_upper) continue - var/xcrdStart = curr_x + x_offset - 1 + var/datum/grid_set/gridSet = new + + gridSet.xcrdStart = curr_x + x_offset - 1 //position of the currently processed square - var/xcrd - var/ycrd = text2num(dmmRegex.group[4]) + y_offset - 1 - var/zcrd = text2num(dmmRegex.group[5]) + z_offset - 1 + gridSet.ycrd = text2num(dmmRegex.group[4]) + y_offset - 1 + gridSet.zcrd = text2num(dmmRegex.group[5]) + z_offset - 1 - var/zexpansion = zcrd > world.maxz - if(zexpansion && !measureOnly) - if(cropMap) - continue - else - while (zcrd > world.maxz) //create a new z_level if needed - world.incrementMaxZ() - if(!no_changeturf) - WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") - - bounds[MAP_MINX] = min(bounds[MAP_MINX], CLAMP(xcrdStart, x_lower, x_upper)) - bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) - bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) + bounds[MAP_MINX] = min(bounds[MAP_MINX], CLAMP(gridSet.xcrdStart, x_lower, x_upper)) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd) var/list/gridLines = splittext(dmmRegex.group[6], "\n") + gridSet.gridLines = gridLines var/leadingBlanks = 0 while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "") @@ -116,113 +104,122 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) if(!gridLines.len) // Skip it if only blank lines exist. continue + + gridSets += gridSet if(gridLines.len && gridLines[gridLines.len] == "") gridLines.Cut(gridLines.len) // Remove only one blank line at the end. - bounds[MAP_MINY] = min(bounds[MAP_MINY], CLAMP(ycrd, y_lower, y_upper)) - ycrd += gridLines.len - 1 // Start at the top and work down + bounds[MAP_MINY] = min(bounds[MAP_MINY], CLAMP(gridSet.ycrd, y_lower, y_upper)) + gridSet.ycrd += gridLines.len - 1 // Start at the top and work down - if(!cropMap && ycrd > world.maxy) - if(!measureOnly) - world.maxy = ycrd // Expand Y here. X is expanded in the loop below - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], CLAMP(ycrd, y_lower, y_upper)) + if(!cropMap && gridSet.ycrd > world.maxy) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], CLAMP(gridSet.ycrd, y_lower, y_upper)) else - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], CLAMP(min(ycrd, world.maxy), y_lower, y_upper)) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], CLAMP(min(gridSet.ycrd, world.maxy), y_lower, y_upper)) - var/maxx = xcrdStart + var/maxx = gridSet.xcrdStart if(measureOnly) for(var/line in gridLines) - maxx = max(maxx, xcrdStart + length(line) / key_len - 1) - else - for(var/line in gridLines) - if((ycrd - y_offset + 1) < y_lower || (ycrd - y_offset + 1) > y_upper) //Reverse operation and check if it is out of bounds of cropping. - --ycrd - continue - if(ycrd <= world.maxy && ycrd >= 1) - xcrd = xcrdStart - for(var/tpos = 1 to length(line) - key_len + 1 step key_len) - if((xcrd - x_offset + 1) < x_lower || (xcrd - x_offset + 1) > x_upper) //Same as above. - ++xcrd - continue //X cropping. - if(xcrd > world.maxx) - if(cropMap) - break - else - world.maxx = xcrd - - if(xcrd >= 1) - var/model_key = copytext(line, tpos, tpos + key_len) - var/no_afterchange = no_changeturf || zexpansion - if(!no_afterchange || (model_key != space_key)) - if(!grid_models[model_key]) - throw EXCEPTION("Undefined model key in DMM.") - parse_grid(grid_models[model_key], model_key, xcrd, ycrd, zcrd, no_changeturf || zexpansion, placeOnTop) - #ifdef TESTING - else - ++turfsSkipped - #endif - CHECK_TICK - maxx = max(maxx, xcrd) - ++xcrd - --ycrd + maxx = max(maxx, gridSet.xcrdStart + length(line) / key_len - 1) bounds[MAP_MAXX] = CLAMP(max(bounds[MAP_MAXX], cropMap ? min(maxx, world.maxx) : maxx), x_lower, x_upper) + CHECK_TICK + +/datum/maploader/proc/load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE) + var/tfile = dmm_file//the map file we're creating + if(isfile(tfile)) + tfile = file2text(tfile) + + if(!x_offset) + x_offset = 1 + if(!y_offset) + y_offset = 1 + if(!z_offset) + z_offset = world.maxz + 1 + + var/datum/parsed_map/parsed = new(tfile, x_offset, y_offset, z_offset, x_lower, x_upper, y_lower, y_upper, measureOnly, dmmRegex, cropMap) + + var/list/modelCache + var/space_key + if(!measureOnly) + modelCache = build_cache(parsed, no_changeturf) + space_key = modelCache[SPACE_KEY] + + for(var/I in parsed.gridSets) + var/datum/grid_set/gset = I + if(!cropMap && !measureOnly && gset.ycrd > world.maxy) + world.maxy = gset.ycrd // Expand Y here. X is expanded in the loop below + var/zexpansion = gset.zcrd > world.maxz + if(zexpansion && !measureOnly) + if(cropMap) + continue + else + while (gset.zcrd > world.maxz) //create a new z_level if needed + world.incrementMaxZ() + if(!no_changeturf) + WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") + + var/maxx = gset.xcrdStart + if(!measureOnly) + for(var/line in gset.gridLines) + if((gset.ycrd - y_offset + 1) < y_lower || (gset.ycrd - y_offset + 1) > y_upper) //Reverse operation and check if it is out of bounds of cropping. + --gset.ycrd + continue + if(gset.ycrd <= world.maxy && gset.ycrd >= 1) + gset.xcrd = gset.xcrdStart + for(var/tpos = 1 to length(line) - parsed.key_len + 1 step parsed.key_len) + if((gset.xcrd - x_offset + 1) < x_lower || (gset.xcrd - x_offset + 1) > x_upper) //Same as above. + ++gset.xcrd + continue //X cropping. + if(gset.xcrd > world.maxx) + if(cropMap) + break + else + world.maxx = gset.xcrd + + if(gset.xcrd >= 1) + var/model_key = copytext(line, tpos, tpos + parsed.key_len) + var/no_afterchange = no_changeturf || zexpansion + if(!no_afterchange || (model_key != space_key)) + var/list/cache = modelCache[model_key] + if(!cache) + CRASH("Undefined model key in DMM: [model_key]") + build_coordinate(cache, gset.xcrd, gset.ycrd, gset.zcrd, no_afterchange, placeOnTop) + #ifdef TESTING + else + ++turfsSkipped + #endif + CHECK_TICK + maxx = max(maxx, gset.xcrd) + ++gset.xcrd + --gset.ycrd CHECK_TICK + var/list/bounds = parsed.bounds if(bounds[1] == 1.#INF) // Shouldn't need to check every item - return null - else - if(!measureOnly) - if(!no_changeturf) - for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) - var/turf/T = t - //we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs - T.AfterChange(CHANGETURF_IGNORE_AIR) - return bounds + parsed.bounds = null + else if(!measureOnly && !no_changeturf) + for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) + var/turf/T = t + //we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs + T.AfterChange(CHANGETURF_IGNORE_AIR) + return parsed -/** - * Fill a given tile with its area/turf/objects/mobs - * Variable model is one full map line (e.g /turf/unsimulated/wall{icon_state = "rock"}, /area/mine/explored) - * - * WORKING : - * - * 1) Read the model string, member by member (delimiter is ',') - * - * 2) Get the path of the atom and store it into a list - * - * 3) a) Check if the member has variables (text within '{' and '}') - * - * 3) b) Construct an associative list with found variables, if any (the atom index in members is the same as its variables in members_attributes) - * - * 4) Instanciates the atom with its variables - * - */ -/dmm_suite/proc/parse_grid(model as text, model_key as text, xcrd as num,ycrd as num,zcrd as num, no_changeturf as num, placeOnTop as num) - /*Method parse_grid() - - Accepts a text string containing a comma separated list of type paths of the - same construction as those contained in a .dmm file, and instantiates them. - */ - - var/list/members //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored) - var/list/members_attributes //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) - var/list/cached = modelCache[model] - var/index - - if(cached) - members = cached[1] - members_attributes = cached[2] - else +/datum/maploader/proc/build_cache(datum/parsed_map/parsed, no_changeturf) + . = list() + var/list/grid_models = parsed.grid_models + for(var/model_key in grid_models) + var/model = grid_models[model_key] + var/list/members = list() //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored) + var/list/members_attributes = list() //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) ///////////////////////////////////////////////////////// //Constructing members and corresponding variables lists //////////////////////////////////////////////////////// - members = list() - members_attributes = list() - index = 1 - + var/index = 1 var/old_position = 1 var/dpos @@ -269,13 +266,25 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) // 5. and the members are world.turf and world.area // Basically, if we find an entry like this: "XXX" = (/turf/default, /area/default) // We can skip calling this proc every time we see XXX - if(no_changeturf && !space_key && members.len == 2 && members_attributes.len == 2 && length(members_attributes[1]) == 0 && length(members_attributes[2]) == 0 && (world.area in members) && (world.turf in members)) - space_key = model_key - return + if(no_changeturf \ + && !(.[SPACE_KEY]) \ + && members.len == 2 \ + && members_attributes.len == 2 \ + && length(members_attributes[1]) == 0 \ + && length(members_attributes[2]) == 0 \ + && (world.area in members) \ + && (world.turf in members)) + + .[SPACE_KEY] = model_key + continue - modelCache[model] = list(members, members_attributes) + .[model_key] = list(members, members_attributes) +/datum/maploader/proc/build_coordinate(list/model, xcrd as num, ycrd as num, zcrd as num, no_changeturf as num, placeOnTop as num) + var/index + var/list/members = model[1] + var/list/members_attributes = model[2] //////////////// //Instanciation @@ -335,7 +344,7 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) //////////////// //Instance an atom at (x,y,z) and gives it the variables in attributes -/dmm_suite/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop) +/datum/maploader/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop) GLOB._preloader.setup(attributes, path) if(crds) @@ -358,13 +367,13 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) stoplag() SSatoms.map_loader_begin() -/dmm_suite/proc/create_atom(path, crds) +/datum/maploader/proc/create_atom(path, crds) set waitfor = FALSE . = new path (crds) //text trimming (both directions) helper proc //optionally removes quotes before and after the text (for variable name) -/dmm_suite/proc/trim_text(what as text,trim_quotes=0) +/datum/maploader/proc/trim_text(what as text,trim_quotes=0) if(trim_quotes) return trimQuotesRegex.Replace(what, "") else @@ -373,7 +382,7 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) //find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape //returns 0 if reached the last delimiter -/dmm_suite/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"") +/datum/maploader/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"") var/position = initial_position var/next_delimiter = findtext(text,delimiter,position,0) var/next_opening = findtext(text,opening_escape,position,0) @@ -388,7 +397,7 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) //build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) //return the filled list -/dmm_suite/proc/readlist(text as text, delimiter=",") +/datum/maploader/proc/readlist(text as text, delimiter=",") var/list/to_return = list() @@ -441,7 +450,7 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) return to_return -/dmm_suite/Destroy() +/datum/maploader/Destroy() ..() return QDEL_HINT_HARDDEL_NOW @@ -449,18 +458,18 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) //Preloader datum ////////////////// -/dmm_suite/preloader +/datum/map_preloader parent_type = /datum var/list/attributes var/target_path -/dmm_suite/preloader/proc/setup(list/the_attributes, path) +/datum/map_preloader/proc/setup(list/the_attributes, path) if(the_attributes.len) GLOB.use_preloader = TRUE attributes = the_attributes target_path = path -/dmm_suite/preloader/proc/load(atom/what) +/datum/map_preloader/proc/load(atom/what) for(var/attribute in attributes) var/value = attributes[attribute] if(islist(value)) diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm index 4b541e8a792..99750ed9d09 100644 --- a/code/modules/procedural_mapping/mapGenerators/repair.dm +++ b/code/modules/procedural_mapping/mapGenerators/repair.dm @@ -21,13 +21,14 @@ return var/datum/mapGenerator/repair/reload_station_map/mother1 = mother GLOB.reloading_map = TRUE - var/static/dmm_suite/reloader = new + var/static/datum/maploader/reloader = new // This is kind of finicky on multi-Z maps but the reader would need to be // changed to allow Z cropping and that's a mess var/z_offset = SSmapping.station_start var/list/bounds for (var/path in SSmapping.config.GetFullMapPaths()) - bounds = reloader.load_map(file(path), measureOnly = FALSE, no_changeturf = FALSE,x_offset = 0, y_offset = 0, z_offset = z_offset, cropMap=TRUE, lower_crop_x = mother1.x_low, lower_crop_y = mother1.y_low, upper_crop_x = mother1.x_high, upper_crop_y = mother1.y_high) + var/datum/parsed_map/parsed = reloader.load_map(file(path), measureOnly = FALSE, no_changeturf = FALSE,x_offset = 0, y_offset = 0, z_offset = z_offset, cropMap=TRUE, lower_crop_x = mother1.x_low, lower_crop_y = mother1.y_low, upper_crop_x = mother1.x_high, upper_crop_y = mother1.y_high) + bounds = parsed?.bounds z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1 var/list/obj/machinery/atmospherics/atmos_machines = list()