diff --git a/_maps/__MAP_DEFINES.dm b/_maps/__MAP_DEFINES.dm
index 8f55a0aea114..a275d07a744c 100644
--- a/_maps/__MAP_DEFINES.dm
+++ b/_maps/__MAP_DEFINES.dm
@@ -13,6 +13,8 @@
#define STATION_CONTACT "Station Contact"
// A level dedicated to admin use
#define ADMIN_LEVEL "Admin Level"
+ // For Z-levels dedicated to auto-spawning stuff in
+ #define Z_FLAG_RESERVED "Reserved"
// A level that can be navigated to by the crew without admin intervention or the emergency shuttle.
#define REACHABLE_BY_CREW "Reachable"
// For away missions - used by some consoles
diff --git a/code/__DEFINES/_globals.dm b/code/__DEFINES/_globals.dm
index 083abd4108ef..f80661688148 100644
--- a/code/__DEFINES/_globals.dm
+++ b/code/__DEFINES/_globals.dm
@@ -16,23 +16,41 @@
#define GLOBAL_PROTECT(X)
#endif
+/// Standard BYOND global, seriously do not use without an earthshakingly good reason
#define GLOBAL_REAL_VAR(X) var/global/##X
+
+/// Standard typed BYOND global, seriously do not use without an earthshakingly good reason
#define GLOBAL_REAL(X, Typepath) var/global##Typepath/##X
+/// Defines a global var on the controller, do not use
#define GLOBAL_RAW(X) /datum/controller/global_vars/var/global##X
+/// Create an untyped global with an initializer expression
#define GLOBAL_VAR_INIT(X, InitValue) GLOBAL_RAW(/##X); GLOBAL_MANAGED(X, InitValue)
+/// Create a global const var, do not use
#define GLOBAL_VAR_CONST(X, InitValue) GLOBAL_RAW(/const/##X) = InitValue; GLOBAL_UNMANAGED(X)
+/// Create a list global with an initializer expression
#define GLOBAL_LIST_INIT(X, InitValue) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, InitValue)
+/// Create a list global that is initialized as an empty list
#define GLOBAL_LIST_EMPTY(X) GLOBAL_LIST_INIT(X, list())
+/// Create a typed list global with an initializer expression
+#define GLOBAL_LIST_INIT_TYPED(X, Typepath, InitValue) GLOBAL_RAW(/list##Typepath/X); GLOBAL_MANAGED(X, InitValue)
+
+/// Create a typed list global that is initialized as an empty list
+#define GLOBAL_LIST_EMPTY_TYPED(X, Typepath) GLOBAL_LIST_INIT_TYPED(X, Typepath, list())
+
+/// Create a typed global with an initializer expression
#define GLOBAL_DATUM_INIT(X, Typepath, InitValue) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, InitValue)
+/// Create an untyped null global
#define GLOBAL_VAR(X) GLOBAL_RAW(/##X); GLOBAL_UNMANAGED(X)
+/// Create a null global list
#define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_UNMANAGED(X)
+/// Create a typed null global
#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X)
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 08c5087b4b09..63fddee37456 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -144,12 +144,18 @@
#define PASSTAKE (1<<9)
#define PASSBARRICADE (1<<10)
-//turf-only flags
+//turf-only flags, under the flags variable
#define BLESSED_TILE (1<<0)
#define NO_LAVA_GEN (1<<1) //Blocks lava rivers being generated on the turf
#define NO_RUINS (1<<2)
#define LAVA_BRIDGE (1<<3) //! This turf has already been reserved for a lavaland bridge placement.
+// turf flags, under the turf_flags variable
+/// If a turf is an unused reservation turf awaiting assignment
+#define UNUSED_RESERVATION_TURF (1<<4)
+/// If a turf is a reserved turf
+#define RESERVATION_TURF (1<<5)
+
//ORGAN TYPE FLAGS
#define AFFECT_ROBOTIC_ORGAN 1
#define AFFECT_ORGANIC_ORGAN 2
diff --git a/code/__DEFINES/shuttle_defines.dm b/code/__DEFINES/shuttle_defines.dm
index 43470086ce22..a656e661a9dd 100644
--- a/code/__DEFINES/shuttle_defines.dm
+++ b/code/__DEFINES/shuttle_defines.dm
@@ -14,3 +14,5 @@
#define SHUTTLE_DOCKER_LANDING_CLEAR 1
#define SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT 2
#define SHUTTLE_DOCKER_BLOCKED 3
+
+#define SHUTTLE_TRANSIT_BORDER 16
diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm
index f383b4d5540c..f3149bb42aaa 100644
--- a/code/__DEFINES/turfs.dm
+++ b/code/__DEFINES/turfs.dm
@@ -13,3 +13,10 @@
#define MINERAL_PREVENT_DIG 0 //! A mineral turf should not be changed when mined.
#define MINERAL_ALLOW_DIG 1 //! A mineral turf should be dug out when mined.
+
+/// Returns an outline (neighboring turfs) of the given block
+#define CORNER_OUTLINE(corner, width, height) ( \
+ CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, -1) + \
+ CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, height) + \
+ CORNER_BLOCK_OFFSET(corner, 1, height, -1, 0) + \
+ CORNER_BLOCK_OFFSET(corner, 1, height, width, 0))
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 47947cb245f1..8ff538f3d37f 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -740,6 +740,19 @@
LAZYINITLIST(lazy_list[key]); \
lazy_list[key] |= value;
+///Ensures the length of a list is at least I, prefilling it with V if needed. if V is a proc call, it is repeated for each new index so that list() can just make a new list for each item.
+#define LISTASSERTLEN(L, I, V...) \
+ if(length(L) < I) { \
+ var/_OLD_LENGTH = length(L); \
+ L.len = I; \
+ /* Convert the optional argument to a if check */ \
+ for(var/_USELESS_VAR in list(V)) { \
+ for(var/_INDEX_TO_ASSIGN_TO in _OLD_LENGTH+1 to I) { \
+ L[_INDEX_TO_ASSIGN_TO] = V; \
+ } \
+ } \
+ }
+
//same, but returns nothing and acts on list in place
/proc/shuffle_inplace(list/L)
if(!L)
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index e165eabdb705..3f1de6a67fde 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -112,6 +112,11 @@ DEFINE_BITFIELD(flags, list(
"NO_SCREENTIPS" = NO_SCREENTIPS,
))
+DEFINE_BITFIELD(turf_flags, list(
+ "UNUSED_RESERVATION_TURF" = UNUSED_RESERVATION_TURF,
+ "RESERVATION_TURF" = RESERVATION_TURF
+))
+
DEFINE_BITFIELD(flags_2, list(
"SLOWS_WHILE_IN_HAND_2" = SLOWS_WHILE_IN_HAND_2,
"NO_EMP_WIRES_2" = NO_EMP_WIRES_2,
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index f918fe47277f..5448b02c2e9a 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -18,7 +18,7 @@ GLOBAL_LIST_EMPTY(hierophant_walls)
GLOBAL_LIST_EMPTY(pandemics)
GLOBAL_LIST_EMPTY(all_areas)
-GLOBAL_LIST_EMPTY(all_unique_areas) // List of all unique areas. AKA areas with there_can_be_many = FALSE
+GLOBAL_LIST_EMPTY_TYPED(all_unique_areas, /area) // List of all unique areas. AKA areas with there_can_be_many = FALSE
GLOBAL_LIST_EMPTY(machines)
GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices.
diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm
index a236dabf7f7a..6cf89d96c4a8 100644
--- a/code/controllers/subsystem/non_firing/SSmapping.dm
+++ b/code/controllers/subsystem/non_firing/SSmapping.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = INIT_ORDER_MAPPING // 9
- flags = SS_NO_FIRE
+
/// What map datum are we using
var/datum/map/map_datum
/// What map will be used next round
@@ -31,6 +31,15 @@ SUBSYSTEM_DEF(mapping)
/// Ruin placement manager for lavaland levels.
var/datum/ruin_placer/lavaland/lavaland_ruins_placer
+ var/num_of_res_levels = 0
+ var/clearing_reserved_turfs = FALSE
+ var/list/datum/turf_reservations //list of turf reservations
+
+ var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
+ var/list/used_turfs = list() //list of turf = datum/turf_reservation
+ /// List of lists of turfs to reserve
+ var/list/lists_to_reserve = list()
+
// This has to be here because world/New() uses [station_name()], which looks this datum up
/datum/controller/subsystem/mapping/PreInit()
. = ..()
@@ -109,6 +118,8 @@ SUBSYSTEM_DEF(mapping)
// Makes a blank space level for the sake of randomness
GLOB.space_manager.add_new_zlevel("Empty Area", linkage = CROSSLINKED, traits = empty_z_traits)
+ // Add a reserved z-level
+ // add_reservation_zlevel() // CURRENTLY DISABLED, AS NOTHING USES IT. IF YOU WANT TO ADD LAZYLOADING TO ANYTHING, MAKE SURE TO REIMPLEMENT THIS
// Setup the Z-level linkage
GLOB.space_manager.do_transition_setup()
@@ -346,4 +357,120 @@ SUBSYSTEM_DEF(mapping)
SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency station access", "disabled"))
/datum/controller/subsystem/mapping/Recover()
- flags |= SS_NO_INIT
+ num_of_res_levels = SSmapping.num_of_res_levels
+ clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
+ turf_reservations = SSmapping.turf_reservations
+ unused_turfs = SSmapping.unused_turfs
+ used_turfs = SSmapping.used_turfs
+ lists_to_reserve = SSmapping.lists_to_reserve
+
+/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T)
+ RETURN_TYPE(/datum/turf_reservation)
+ return used_turfs[T]
+
+/// Requests a /datum/turf_reservation based on the given width, height.
+/datum/controller/subsystem/mapping/proc/request_turf_block_reservation(width, height)
+ UNTIL(!clearing_reserved_turfs)
+ log_debug("Reserving [width]x[height] turf reservation")
+ var/datum/turf_reservation/reserve = new /datum/turf_reservation
+ for(var/i in levels_by_trait(Z_FLAG_RESERVED))
+ if(reserve.reserve(width, height, i))
+ return reserve
+ //If we didn't return at this point, theres a good chance we ran out of room on the exisiting reserved z levels, so lets try a new one
+ var/z_level_num = add_reservation_zlevel()
+ if(reserve.reserve(width, height, z_level_num))
+ return reserve
+ qdel(reserve)
+
+/datum/controller/subsystem/mapping/proc/add_reservation_zlevel()
+ num_of_res_levels++
+ // . here is the z of the just added z-level
+ . = GLOB.space_manager.add_new_zlevel("Transit/Reserved #[num_of_res_levels]", traits = list(Z_FLAG_RESERVED, BLOCK_TELEPORT, IMPEDES_MAGIC))
+ initialize_reserved_level(.)
+ if(!initialized)
+ return
+ if(length(SSidlenpcpool.idle_mobs_by_zlevel) == . || !islist(SSidlenpcpool.idle_mobs_by_zlevel)) // arbitrary chosen from these lists that require the length of the z-levels
+ return
+ LISTASSERTLEN(SSidlenpcpool.idle_mobs_by_zlevel, ., list())
+ LISTASSERTLEN(SSmobs.clients_by_zlevel, ., list())
+ LISTASSERTLEN(SSmobs.dead_players_by_zlevel, ., list())
+
+///Sets up a z level as reserved
+///This is not for wiping reserved levels, use wipe_reservations() for that.
+///If this is called after SSatom init, it will call Initialize on all turfs on the passed z, as its name promises
+/datum/controller/subsystem/mapping/proc/initialize_reserved_level(z)
+ UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case.
+ log_debug("Initializing new reserved Z-level")
+ clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it.
+ if(!check_level_trait(z, Z_FLAG_RESERVED))
+ clearing_reserved_turfs = FALSE
+ CRASH("Invalid z level prepared for reservations.")
+ var/block = block(SHUTTLE_TRANSIT_BORDER, SHUTTLE_TRANSIT_BORDER, z, world.maxx - SHUTTLE_TRANSIT_BORDER, world.maxy - SHUTTLE_TRANSIT_BORDER)
+ for(var/turf/T as anything in block)
+ // No need to empty() these, because they just got created and are already /turf/space.
+ T.turf_flags |= UNUSED_RESERVATION_TURF
+ CHECK_TICK
+
+ // Gotta create these suckers if we've not done so already
+ if(SSatoms.initialized)
+ SSatoms.InitializeAtoms(block(1, 1, world.maxx, world.maxy, z), FALSE)
+
+ unused_turfs["[z]"] = block
+ clearing_reserved_turfs = FALSE
+
+/datum/controller/subsystem/mapping/fire(resumed)
+ // Cache for sonic speed
+ var/list/list/turf/unused_turfs = src.unused_turfs
+ var/list/world_contents = GLOB.all_unique_areas[world.area].contents
+ // var/list/world_turf_contents_by_z = GLOB.all_unique_areas[world.area].turfs_by_zlevel
+ var/list/lists_to_reserve = src.lists_to_reserve
+ var/index = 0
+ while(index < length(lists_to_reserve))
+ var/list/packet = lists_to_reserve[index + 1]
+ var/packetlen = length(packet)
+ while(packetlen)
+ if(MC_TICK_CHECK)
+ if(index)
+ lists_to_reserve.Cut(1, index)
+ return
+ var/turf/reserving_turf = packet[packetlen]
+ reserving_turf.empty(/turf/space)
+ LAZYINITLIST(unused_turfs["[reserving_turf.z]"])
+ if(!(reserving_turf in unused_turfs["[reserving_turf.z]"]))
+ unused_turfs["[reserving_turf.z]"].Insert(1, reserving_turf)
+ reserving_turf.turf_flags = UNUSED_RESERVATION_TURF
+
+ world_contents += reserving_turf
+ packet.len--
+ packetlen = length(packet)
+
+ index++
+ lists_to_reserve.Cut(1, index)
+
+/**
+ * Lazy loads a template on a lazy-loaded z-level
+ * If you want to use this as non-debug, make sure to uncomment add_reservation_zlevel in /datum/controller/subsystem/mapping/Initialize()
+ */
+/datum/controller/subsystem/mapping/proc/lazy_load_template(datum/map_template/template)
+ RETURN_TYPE(/datum/turf_reservation)
+
+ UNTIL(initialized)
+ var/static/lazy_loading = FALSE
+ UNTIL(!lazy_loading)
+
+ lazy_loading = TRUE
+ . = _lazy_load_template(template)
+ lazy_loading = FALSE
+
+/datum/controller/subsystem/mapping/proc/_lazy_load_template(datum/map_template/template)
+ PRIVATE_PROC(TRUE)
+ var/datum/turf_reservation/reservation = request_turf_block_reservation(template.width, template.height)
+ if(!istype(reservation))
+ return
+
+ template.load(reservation.bottom_left_turf)
+ return reservation
+
+/// Schedules a group of turfs to be handed back to the reservation system's control
+/datum/controller/subsystem/mapping/proc/unreserve_turfs(list/turfs)
+ lists_to_reserve += list(turfs)
diff --git a/code/game/area/misc_areas.dm b/code/game/area/misc_areas.dm
index e9bd5f40a6a4..d1e2b01583c7 100644
--- a/code/game/area/misc_areas.dm
+++ b/code/game/area/misc_areas.dm
@@ -63,3 +63,11 @@
/area/syndicate_mothership/jail
name = "\improper Syndicate Jail"
+
+/area/cordon
+ name = "CORDON"
+ icon_state = "cordon"
+ requires_power = FALSE
+ always_unpowered = TRUE
+ dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ valid_territory = FALSE
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index b2c02b58364d..b27a906a9720 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -34,7 +34,9 @@
var/blocks_air = FALSE
- flags = 0
+ flags = 0 // TODO, someday move all off the flags here to turf_flags
+
+ var/turf_flags = NONE
var/image/obscured //camerachunks
@@ -282,8 +284,9 @@
qdel(src) //Just get the side effects and call Destroy
var/list/old_comp_lookup = comp_lookup?.Copy()
var/list/old_signal_procs = signal_procs?.Copy()
-
+ var/carryover_turf_flags = turf_flags & (RESERVATION_TURF|UNUSED_RESERVATION_TURF)
var/turf/W = new path(src)
+ W.turf_flags |= carryover_turf_flags
if(old_comp_lookup)
LAZYOR(W.comp_lookup, old_comp_lookup)
if(old_signal_procs)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index f12dd40466da..b5a6fd1d5fcd 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -159,6 +159,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list(
/proc/machine_upgrade,
/client/proc/map_template_load,
/client/proc/map_template_upload,
+ /client/proc/map_template_load_lazy,
/client/proc/view_runtimes,
/client/proc/admin_serialize,
/client/proc/uid_log,
diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm
index 80d46e18550d..c05060131fcf 100644
--- a/code/modules/admin/verbs/map_template_loadverb.dm
+++ b/code/modules/admin/verbs/map_template_loadverb.dm
@@ -56,3 +56,23 @@
message_admins("[key_name_admin(usr)] has uploaded a map template ([map]). Took [stop_watch(timer)]s.")
else
to_chat(usr, "Map template '[map]' failed to load properly")
+
+/client/proc/map_template_load_lazy()
+ set category = "Debug"
+ set name = "Map template - Lazy Load"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ var/map = input(usr, "Choose a Map Template to place on the lazy load map level.","Place Map Template") as null|anything in GLOB.map_templates
+ if(!map)
+ return
+ var/datum/map_template/template = GLOB.map_templates[map]
+
+ message_admins("[key_name_admin(usr)] is lazyloading the map template ([template.name]).")
+ var/datum/turf_reservation/reserve = SSmapping.lazy_load_template(template)
+ if(!istype(reserve))
+ message_admins("Lazyloading [template.name] failed! You should report this as a bug.")
+ return
+ message_admins("[key_name_admin(usr)] has lazyloaded the map template ([template.name]) at [ADMIN_JMP(reserve.bottom_left_turf)]")
+
diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm
new file mode 100644
index 000000000000..4d44766ac668
--- /dev/null
+++ b/code/modules/awaymissions/cordon.dm
@@ -0,0 +1,32 @@
+/// Turf type that appears to be a world border, completely impassable and non-interactable to all physical (alive) entities.
+/turf/cordon
+ name = "cordon"
+ icon = 'icons/turf/walls.dmi'
+ icon_state = "cordon"
+ invisibility = INVISIBILITY_ABSTRACT
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ explosion_block = 50
+ rad_insulation = RAD_FULL_INSULATION
+ opacity = TRUE
+ density = TRUE
+ blocks_air = TRUE
+ baseturf = /turf/cordon
+
+// /turf/cordon/rust_heretic_act()
+// return FALSE
+
+/turf/cordon/acid_act(acidpwr, acid_volume, acid_id)
+ return FALSE
+
+/turf/cordon/singularity_act()
+ return FALSE
+
+/turf/cordon/TerraformTurf(path, list/new_baseturfs, flags)
+ return
+
+/turf/cordon/bullet_act(obj/item/projectile/hitting_projectile, def_zone, piercing_hit)
+ SHOULD_CALL_PARENT(FALSE) // Fuck you
+ return
+
+/turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover)
+ return FALSE
diff --git a/code/modules/space_management/heap_space_level.dm b/code/modules/space_management/heap_space_level.dm
deleted file mode 100644
index 666bfa9fa821..000000000000
--- a/code/modules/space_management/heap_space_level.dm
+++ /dev/null
@@ -1,62 +0,0 @@
-// This represents a level we can carve up as we please, and hand out
-// chunks of to whatever requests it
-/datum/space_level/heap
- name = "Heap level #ERROR"
- var/datum/space_chunk/top
- linkage = UNAFFECTED
-
-/datum/space_level/heap/New(z, name, transition_type, traits)
- ..(z, "Heap level #[z]", UNAFFECTED, traits)
- top = new(1, 1, zpos, world.maxx, world.maxy)
- flags = traits
-
-/datum/space_level/heap/proc/request(width, height)
- return top.can_fit_space(width, height)
-
-// Returns a space chunk datum for some nerd to work with - tells them what's safe to write into, and such
-/datum/space_level/heap/proc/allocate(width, height)
- if(!request(width, height))
- return null
-
- var/datum/space_chunk/C = top.get_optimal_chunk(width, height)
- if(!C)
- return null
- C.children.Cut()
-
- if(C.width == width && C.height == height && C.is_empty)
- C.set_occupied(TRUE)
- return C
-
- // Split the chunk into 4 pieces
- var/datum/space_chunk/return_chunk = new(C.x, C.y, C.zpos, width, height)
- C.children += return_chunk
- C.children += new /datum/space_chunk(C.x+width, C.y, C.zpos, C.width-width, height)
- C.children += new /datum/space_chunk(C.x, C.y+height, C.zpos, width, C.height-height)
- C.children += new /datum/space_chunk(C.x+width, C.y+height, C.zpos, C.width-width, C.height-height)
-
- for(var/datum/space_chunk/C2 in C.children)
- C2.parent = C
- return_chunk.set_occupied(TRUE)
- return return_chunk
-
-/datum/space_level/heap/proc/free(datum/space_chunk/C)
- if(!istype(C))
- return
- if(C.zpos != zpos)
- return
- C.set_occupied(FALSE)
- for(var/turf/T in block(C.x, C.y, C.zpos, C.x + C.width - 1, C.y + C.height - 1, C.zpos))
- for(var/atom/movable/M in T)
- if(isobserver(M))
- continue
- M.loc = null
- qdel(M, TRUE)
- T.ChangeTurf(T.baseturf)
- var/datum/space_chunk/last_empty_parent = C
- while(last_empty_parent.parent && last_empty_parent.parent.is_empty)
- last_empty_parent = last_empty_parent.parent
- // Prevent a self-qdel from killing this proc
- src = null
- for(var/datum/space_chunk/child in last_empty_parent.children)
- qdel(child)
- last_empty_parent.children.Cut()
diff --git a/code/modules/space_management/level_traits.dm b/code/modules/space_management/level_traits.dm
index 0634d9299902..aa02bdaa44c1 100644
--- a/code/modules/space_management/level_traits.dm
+++ b/code/modules/space_management/level_traits.dm
@@ -28,6 +28,8 @@
/proc/level_boosts_signal(z)
return check_level_trait(z, BOOSTS_SIGNAL)
+#define is_reserved_level(z) check_level_trait(z, ZTRAIT_RESERVED)
+
// Used for the nuke disk, or for checking if players survived through xenos
/proc/is_secure_level(z)
var/secure = check_level_trait(z, STATION_LEVEL)
diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm
new file mode 100644
index 000000000000..febe4b34bba4
--- /dev/null
+++ b/code/modules/space_management/turf_reservation.dm
@@ -0,0 +1,153 @@
+/*
+ * Turf reservations are used to reserve specific areas of the reserved z-levels for various purposes, typically for late-loading maps.
+ * It ensures that reserved turfs are properly managed and can be released when no longer needed.
+ * Reservations are not automatically released, so they must be manually released when no longer needed.
+ *
+ * Usage:
+ * - To create a new reservation, call the `request_turf_block_reservation(width, height)` method from the mapping subsystem.
+ * - This will return a new instance of /datum/turf_reservation if the reservation is successful.
+ *
+ * Releasing:
+ * - Call the `Release` method on the /datum/turf_reservation instance to release the reserved turfs and cordon turfs.
+ * - This will return the used turfs to the mapping subsystem to allow for reuse of the turfs.
+ */
+
+/datum/turf_reservation
+ /// All turfs that we've reserved
+ var/list/reserved_turfs = list()
+
+ /// Turfs around the reservation for cordoning
+ var/list/cordon_turfs = list()
+
+ /// Area of turfs next to the cordon to fill with pre_cordon_area's
+ var/list/pre_cordon_turfs = list()
+
+ /// The width of the reservation
+ var/width = 0
+
+ /// The height of the reservation
+ var/height = 0
+
+ /// Bottom left turf of the reservation
+ var/turf/bottom_left_turf
+
+ /// Top right turf of the reservation
+ var/turf/top_right_turf
+
+ /// The turf type the reservation is initially made with
+ var/turf_type = /turf/space
+
+/datum/turf_reservation/New()
+ LAZYADD(SSmapping.turf_reservations, src)
+
+/datum/turf_reservation/Destroy()
+ Release()
+ LAZYREMOVE(SSmapping.turf_reservations, src)
+ return ..()
+
+/datum/turf_reservation/proc/Release()
+ bottom_left_turf = null
+ top_right_turf = null
+
+ var/list/reserved_copy = reserved_turfs.Copy()
+ SSmapping.used_turfs -= reserved_turfs
+ reserved_turfs = list()
+
+ var/list/cordon_copy = cordon_turfs.Copy()
+ SSmapping.used_turfs -= cordon_turfs
+ cordon_turfs = list()
+
+ var/release_turfs = reserved_copy + cordon_copy
+
+ SSmapping.unreserve_turfs(release_turfs)
+
+/// Attempts to calaculate and store a list of turfs around the reservation for cordoning. Returns whether a valid cordon was calculated
+/datum/turf_reservation/proc/calculate_cordon_turfs(turf/bottom_left, turf/top_right)
+ if(bottom_left.x < 2 || bottom_left.y < 2 || top_right.x > (world.maxx - 2) || top_right.y > (world.maxy - 2))
+ return FALSE // no space for a cordon here
+
+ var/list/possible_turfs = CORNER_OUTLINE(bottom_left, width, height)
+ // if they're our cordon turfs, accept them
+ possible_turfs -= cordon_turfs
+ for(var/turf/cordon_turf as anything in possible_turfs)
+ // if we changed this to check reservation turf instead of not unused, we could have overlapping cordons.
+ // Unfortunately, that means adding logic for cordons not being removed if they have multiple edges and I'm lazy
+ if(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF))
+ return FALSE
+ cordon_turfs |= possible_turfs
+
+ return TRUE
+
+/// Actually generates the cordon around the reservation, and marking the cordon turfs as reserved
+/datum/turf_reservation/proc/generate_cordon()
+ for(var/turf/cordon_turf as anything in cordon_turfs)
+ var/area/cordon/cordon_area = GLOB.all_unique_areas[/area/cordon] || new /area/cordon
+
+ cordon_area.contents += cordon_turf
+
+ // Its no longer unused, but its also not "used"
+ cordon_turf.turf_flags &= ~UNUSED_RESERVATION_TURF
+ cordon_turf.empty(/turf/cordon)
+ SSmapping.unused_turfs["[cordon_turf.z]"] -= cordon_turf
+ // still gets linked to us though
+ SSmapping.used_turfs[cordon_turf] = src
+
+/// Internal proc which handles reserving the area for the reservation.
+/datum/turf_reservation/proc/_reserve_area(width, height, zlevel)
+ src.width = width
+ src.height = height
+ if(width > world.maxx || height > world.maxy || width < 1 || height < 1)
+ return FALSE
+ var/list/avail = SSmapping.unused_turfs["[zlevel]"]
+ var/turf/bottom_left
+ var/turf/top_right
+ var/list/turf/final_turfs = list()
+ var/passing = FALSE
+ for(var/i in avail)
+ CHECK_TICK
+ bottom_left = i
+ if(!(bottom_left.turf_flags & UNUSED_RESERVATION_TURF))
+ continue
+ if(bottom_left.x + width > world.maxx || bottom_left.y + height > world.maxy)
+ continue
+ top_right = locate(bottom_left.x + width - 1, bottom_left.y + height - 1, bottom_left.z)
+ if(!(top_right.turf_flags & UNUSED_RESERVATION_TURF))
+ continue
+ final_turfs = block(bottom_left, top_right)
+ if(!final_turfs)
+ continue
+ passing = TRUE
+ for(var/I in final_turfs)
+ var/turf/checking = I
+ if(!(checking.turf_flags & UNUSED_RESERVATION_TURF))
+ passing = FALSE
+ break
+ if(passing) // found a potentially valid area, now try to calculate its cordon
+ passing = calculate_cordon_turfs(bottom_left, top_right)
+ if(!passing)
+ continue
+ break
+ if(!passing || !istype(bottom_left) || !istype(top_right))
+ return FALSE
+ for(var/turf/T as anything in final_turfs)
+ reserved_turfs |= T
+ SSmapping.unused_turfs["[T.z]"] -= T
+ SSmapping.used_turfs[T] = src
+ T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF
+ T.empty(turf_type)
+
+ bottom_left_turf = bottom_left
+ top_right_turf = top_right
+ return TRUE
+
+/datum/turf_reservation/proc/reserve(width, height, z_reservation)
+
+ if(!_reserve_area(width, height, z_reservation))
+ log_debug("Failed turf reservation: releasing")
+ Release()
+ return FALSE
+
+ log_debug("Turf reservation successful, generating cordon")
+ generate_cordon()
+ return TRUE
+
diff --git a/code/modules/space_management/zlevel_manager.dm b/code/modules/space_management/zlevel_manager.dm
index d2364652ee6a..4887257493c9 100644
--- a/code/modules/space_management/zlevel_manager.dm
+++ b/code/modules/space_management/zlevel_manager.dm
@@ -134,42 +134,3 @@ GLOBAL_DATUM_INIT(space_manager, /datum/zlev_manager, new())
z_list.Remove(S)
qdel(S)
world.maxz--
-
-
-// An internally-used proc used for heap-zlevel management
-/datum/zlev_manager/proc/add_new_heap()
- world.maxz++
- var/our_z = world.maxz
- var/datum/space_level/yup = new /datum/space_level/heap(our_z, traits = list(BLOCK_TELEPORT, ADMIN_LEVEL))
- z_list["[our_z]"] = yup
- return yup
-
-// This is what you can call to allocate a section of space
-// Later, I'll add an argument to let you define the flags on the region
-/datum/zlev_manager/proc/allocate_space(width, height)
- if(width > world.maxx || height > world.maxy)
- throw EXCEPTION("Too much space requested! \[[width],[height]\]")
- if(!length(heaps))
- heaps.len++
- heaps[length(heaps)] = add_new_heap()
- var/datum/space_level/heap/our_heap
- var/weve_got_vacancy = 0
- for(our_heap in heaps)
- weve_got_vacancy = our_heap.request(width, height)
- if(weve_got_vacancy)
- break // We're sticking with the present value of `our_heap` - it's got room
- // This loop will also run out if no vacancies are found
-
- if(!weve_got_vacancy)
- heaps.len++
- our_heap = add_new_heap()
- heaps[length(heaps)] = our_heap
- return our_heap.allocate(width, height)
-
-/datum/zlev_manager/proc/free_space(datum/space_chunk/C)
- if(!istype(C))
- return
- var/datum/space_level/heap/heap = z_list["[C.zpos]"]
- if(!istype(heap))
- throw EXCEPTION("Attempted to free chunk at invalid z-level ([C.x],[C.y],[C.zpos]) [C.width]x[C.height]")
- heap.free(C)
diff --git a/icons/turf/areas.dmi b/icons/turf/areas.dmi
index d27c76b49de6..69aca13c76e9 100755
Binary files a/icons/turf/areas.dmi and b/icons/turf/areas.dmi differ
diff --git a/icons/turf/walls.dmi b/icons/turf/walls.dmi
index d8d4d8fdd790..fb71d66c556c 100644
Binary files a/icons/turf/walls.dmi and b/icons/turf/walls.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 94f084b0272a..2e85095448db 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -453,8 +453,8 @@
#include "code\datums\components\footstep.dm"
#include "code\datums\components\forces_doors_open.dm"
#include "code\datums\components\fullauto.dm"
-#include "code\datums\components\jetpack_component.dm"
#include "code\datums\components\ghost_direct_control.dm"
+#include "code\datums\components\jetpack_component.dm"
#include "code\datums\components\label.dm"
#include "code\datums\components\largeobjecttransparency.dm"
#include "code\datums\components\material_container.dm"
@@ -1531,8 +1531,8 @@
#include "code\modules\antagonists\_common\antag_hud.dm"
#include "code\modules\antagonists\_common\antag_spawner.dm"
#include "code\modules\antagonists\_common\antag_team.dm"
-#include "code\modules\antagonists\abductor\team_abductor.dm"
#include "code\modules\antagonists\abductor\datum_abductor.dm"
+#include "code\modules\antagonists\abductor\team_abductor.dm"
#include "code\modules\antagonists\antag_org\antag_org_datum.dm"
#include "code\modules\antagonists\antag_org\antag_org_syndicate.dm"
#include "code\modules\antagonists\changeling\changeling_power.dm"
@@ -1692,6 +1692,7 @@
#include "code\modules\atmospherics\machinery\portable\portable_atmospherics.dm"
#include "code\modules\atmospherics\machinery\portable\portable_pump.dm"
#include "code\modules\atmospherics\machinery\portable\scrubber.dm"
+#include "code\modules\awaymissions\cordon.dm"
#include "code\modules\awaymissions\mob_spawn.dm"
#include "code\modules\awaymissions\zlevel_helpers.dm"
#include "code\modules\awaymissions\maploader\dmm_suite.dm"
@@ -2874,12 +2875,12 @@
#include "code\modules\shuttle\shuttle_rotate.dm"
#include "code\modules\shuttle\supply.dm"
#include "code\modules\shuttle\syndicate_shuttles.dm"
-#include "code\modules\space_management\heap_space_level.dm"
#include "code\modules\space_management\level_check.dm"
#include "code\modules\space_management\level_traits.dm"
#include "code\modules\space_management\space_chunk.dm"
#include "code\modules\space_management\space_level.dm"
#include "code\modules\space_management\space_transition.dm"
+#include "code\modules\space_management\turf_reservation.dm"
#include "code\modules\space_management\zlevel_manager.dm"
#include "code\modules\station_goals\bluespace_tap.dm"
#include "code\modules\station_goals\bluespace_tap_events.dm"