diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index a1b75ae494f..c3c0ea77ddb 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -41,11 +41,13 @@ var/global/pipe_processing_killed = 0 /datum/controller/game_controller/proc/setup() world.tick_lag = config.Ticklag + zlevels.initialize() + preloadTemplates() if(!config.disable_away_missions) createRandomZlevel() if(!config.disable_space_ruins) - seedRuins(7, rand(10, 13), /area/space, space_ruins_templates) + seedRuins(7, rand(0, 3), /area/space, space_ruins_templates) setup_objects() setupgenetics() diff --git a/code/datums/helper_datums/map_template.dm b/code/datums/helper_datums/map_template.dm index 1e755f64b09..c1b9312b734 100644 --- a/code/datums/helper_datums/map_template.dm +++ b/code/datums/helper_datums/map_template.dm @@ -23,7 +23,7 @@ height = bounds[MAP_MAXY] return bounds -/datum/map_template/proc/load(turf/T, centered = 0) +/datum/map_template/proc/load(turf/T, centered = 0, delay_init = 0) var/turf/placement = T var/min_x = placement.x var/min_y = placement.y @@ -43,7 +43,7 @@ // 1 bigger, to update the turf smoothing var/turf/ST_bot_left = locate(max(1, min_x-1), max(1, min_y-1), placement.z) var/turf/ST_top_right = locate(min(world.maxx, max_x+1), min(world.maxy, max_y+1), placement.z) - var/list/bounds = maploader.load_map(get_file(), min_x, min_y, placement.z, cropMap = 1) + var/list/bounds = maploader.load_map(get_file(), min_x, min_y, placement.z, cropMap = 1, delay_init = delay_init) if(!bounds) return 0 if(bot_left == null || top_right == null) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 477e1ff9972..f4fe506229a 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -31,7 +31,7 @@ var/list/dirty_z_levels = list() // If you're wondering what goofery this is, this is for things that need the environment // around them set up - like `air_update_turf` and the like - if(!(T && T.z in dirty_z_levels) && auto_init && ticker && ticker.current_state == GAME_STATE_PLAYING) + if(!(T && zlevels.is_zlevel_dirty(T.z)) && auto_init && ticker && ticker.current_state == GAME_STATE_PLAYING) initialize() /atom/movable/Destroy() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 3e8bd44f127..c531162e22b 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -192,15 +192,16 @@ return W -/turf/proc/AfterChange() //called after a turf has been replaced in ChangeTurf() +// I'm including `ignore_air` because BYOND lacks positional-only arguments +/turf/proc/AfterChange(ignore_air, but_its_not_the_saaaaaame = FALSE) //called after a turf has been replaced in ChangeTurf() levelupdate() CalculateAdjacentTurfs() - if(!can_have_cabling()) + if(!but_its_not_the_saaaaaame && !can_have_cabling()) for(var/obj/structure/cable/C in contents) qdel(C) -/turf/simulated/AfterChange(ignore_air) +/turf/simulated/AfterChange(ignore_air, but_its_not_the_saaaaaame = FALSE) ..() RemoveLattice() if(!ignore_air) diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm index f447810d860..f37ad73a1dc 100644 --- a/code/modules/admin/verbs/map_template_loadverb.dm +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -19,6 +19,10 @@ to_chat(usr, "Map is too large to fit in bounds. Map's dimensions: ([template.width], [template.height])") return + var/delay_init = 0 + if(alert(usr,"Delay initialization? (Needed for large atmos-containing maps)","Delay","Yes","No") == "Yes") + delay_init = 1 + var/list/preview = list() for(var/S in template.get_affected_turfs(T,centered = TRUE)) preview += image('icons/turf/overlays.dmi',S,"greenOverlay") @@ -26,7 +30,7 @@ if(alert(usr,"Confirm location.","Template Confirm","Yes","No") == "Yes") var/timer = start_watch() message_admins("[key_name_admin(usr)] has started to place the map template ([template.name]) at (JMP)") - if(template.load(T, centered = TRUE)) + if(template.load(T, centered = TRUE, delay_init = delay_init)) message_admins("[key_name_admin(usr)] has placed a map template ([template.name]) at (JMP). Took [stop_watch(timer)]s.") else to_chat(usr, "Failed to place map") diff --git a/code/modules/awaymissions/maploader/reader.dm b/code/modules/awaymissions/maploader/reader.dm index 6f5dc2837a4..104c7fcbb93 100644 --- a/code/modules/awaymissions/maploader/reader.dm +++ b/code/modules/awaymissions/maploader/reader.dm @@ -27,8 +27,13 @@ var/global/dmm_suite/preloader/_preloader = new * * If `measureOnly` is set, then no atoms will be created, and all this will do * is return the bounds after parsing the file + * + * If delay_init is set, then all initializations while the map is loading will + * be deferred until later + * You'll need it for large initializations like loading the cyberiad, where + * glass and similar can cause atmos to initialize before it's ready */ -/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) +/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, delay_init as num) var/tfile = dmm_file//the map file we're creating if(isfile(tfile)) tfile = file2text(tfile) @@ -43,14 +48,12 @@ var/global/dmm_suite/preloader/_preloader = new 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 - // This ain't re-entrant - // Meaning - don't load more than one map at once, using `spawn` or whatever - var/list/old_dirty_z_levels = deepCopyList(dirty_z_levels) + var/list/touched_z_levels = list() var/dmm_suite/loaded_map/LM = new - // This try-catch is used as a budget "Finally" clause, as the "touched_z_levels" - // var needs to be reset + // This try-catch is used as a budget "Finally" clause, as the dirt count + // needs to be reset try dmmRegex.next = 1 while(dmmRegex.Find(tfile, dmmRegex.next)) @@ -78,14 +81,15 @@ var/global/dmm_suite/preloader/_preloader = new var/xcrd var/ycrd = text2num(dmmRegex.group[4]) + y_offset - 1 var/zcrd = text2num(dmmRegex.group[5]) + z_offset - 1 - if(!measureOnly) - dirty_z_levels |= zcrd + if(!measureOnly && delay_init) + zlevels.add_dirt(zcrd) + touched_z_levels |= zcrd if(zcrd > world.maxz) if(cropMap) continue else - world.maxz = zcrd //create a new z_level if needed + zlevels.increase_max_zlevel_to(zcrd) //create a new z_level if needed bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrdStart) bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) @@ -144,27 +148,28 @@ var/global/dmm_suite/preloader/_preloader = new CHECK_TICK catch(var/exception/e) - if(!measureOnly) - dirty_z_levels = old_dirty_z_levels + if(!measureOnly && delay_init) + for(var/i in touched_z_levels) + zlevels.remove_dirt(i) throw e - // This *might* leak memory? Whatever it's a trivial quantity - if(!measureOnly) - dirty_z_levels = old_dirty_z_levels + if(!measureOnly && delay_init) + for(var/i in touched_z_levels) + zlevels.remove_dirt(i) qdel(LM) if(bounds[MAP_MINX] == 1.#INF) // Shouldn't need to check every item - log_startup_progress("Min x: bounds[MAP_MINX]") - log_startup_progress("Min y: bounds[MAP_MINY]") - log_startup_progress("Min z: bounds[MAP_MINZ]") - log_startup_progress("Max x: bounds[MAP_MAXX]") - log_startup_progress("Max y: bounds[MAP_MAXY]") - log_startup_progress("Max z: bounds[MAP_MAXZ]") + log_debug("Min x: bounds[MAP_MINX]") + log_debug("Min y: bounds[MAP_MINY]") + log_debug("Min z: bounds[MAP_MINZ]") + log_debug("Max x: bounds[MAP_MAXX]") + log_debug("Max y: bounds[MAP_MAXY]") + log_debug("Max z: bounds[MAP_MAXZ]") return null else 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(1) + T.AfterChange(1,but_its_not_the_saaaaaame = TRUE) // are you happy now return bounds /** diff --git a/code/modules/awaymissions/maploader/swapmaps.dm b/code/modules/awaymissions/maploader/swapmaps.dm index 8dc9f9f6c99..7115cfcbf13 100644 --- a/code/modules/awaymissions/maploader/swapmaps.dm +++ b/code/modules/awaymissions/maploader/swapmaps.dm @@ -326,7 +326,7 @@ swapmap x2+=x1-1 y2+=y1-1 z2+=z1-1 - world.maxz=max(z2,world.maxz) // stretch z if necessary + zlevels.increase_max_zlevel_to(z2) // stretch z if necessary if(!ischunk) swapmaps_loaded[src]=null swapmaps_byname[id]=src @@ -373,7 +373,7 @@ swapmap mz=max(mz,M.z2) world.maxx=mx world.maxy=my - world.maxz=mz + zlevels.cut_levels_downto(mz) // save and delete proc/Unload() diff --git a/code/modules/spacial_allocator/zlevel.dm b/code/modules/spacial_allocator/zlevel.dm new file mode 100644 index 00000000000..45be707c15a --- /dev/null +++ b/code/modules/spacial_allocator/zlevel.dm @@ -0,0 +1,7 @@ +/datum/zlevel + var/flags = 0 + var/dirt_count = 0 + var/zpos + +/datum/zlevel/New(z) + zpos = z diff --git a/code/modules/spacial_allocator/zlevel_manager.dm b/code/modules/spacial_allocator/zlevel_manager.dm new file mode 100644 index 00000000000..4a70c1c4d6c --- /dev/null +++ b/code/modules/spacial_allocator/zlevel_manager.dm @@ -0,0 +1,69 @@ +var/global/datum/zlev_manager/zlevels = new + +/datum/zlev_manager + // A list of z-levels + var/list/z_list = list() + + +// Populate our z level list +/datum/zlev_manager/proc/initialize() + for(var/i = 1, i < world.maxz, i++) + z_list.Add(new /datum/zlevel(i)) + + +// For when you need the z-level to be at a certain point +/datum/zlev_manager/proc/increase_max_zlevel_to(new_maxz) + if(world.maxz>=new_maxz) + return + while(world.maxznew_maxz) + kill_topmost_zlevel() + +// Decrements the max z-level by one +// not normally used, but hey the swapmap loader wanted it +/datum/zlev_manager/proc/kill_topmost_zlevel() + var/our_z = world.maxz + qdel(z_list[our_z]) + z_list.Remove(our_z) + world.maxz-- + +/* +* "Dirt" management +* "Dirt" is used to keep track of whether a z level should automatically have +* stuff on it initialize or not - the map loaders, when set to defer init, place +* a freeze on the z levels they touch so as to prevent atmos from exploding, +* among other things +*/ + + +// Returns whether the given z level has a freeze on initialization +/datum/zlev_manager/proc/is_zlevel_dirty(z) + if(z > z_list.len) + return 0 // It's not dirty if it's not being tracked + var/datum/zlevel/our_z = z_list[z] + return (our_z.dirt_count > 0) + + +// Increases the dirt count on a z level +/datum/zlev_manager/proc/add_dirt(z) + var/datum/zlevel/our_z = z_list[z] + our_z.dirt_count++ + + +// Decreases the dirt count on a z level +/datum/zlev_manager/proc/remove_dirt(z) + var/datum/zlevel/our_z = z_list[z] + our_z.dirt_count-- diff --git a/paradise.dme b/paradise.dme index 0d40ed8e7c9..beeb2bb3f0f 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1990,6 +1990,8 @@ #include "code\modules\spacepods\parts.dm" #include "code\modules\spacepods\pod_fabricator.dm" #include "code\modules\spacepods\spacepod.dm" +#include "code\modules\spacial_allocator\zlevel.dm" +#include "code\modules\spacial_allocator\zlevel_manager.dm" #include "code\modules\store\items.dm" #include "code\modules\store\store.dm" #include "code\modules\surgery\bones.dm"