diff --git a/code/__DEFINES/__game.dm b/code/__DEFINES/__game.dm
index dfa335375b67..87c5e2255b52 100644
--- a/code/__DEFINES/__game.dm
+++ b/code/__DEFINES/__game.dm
@@ -1,9 +1,3 @@
-#define RANGE_TURFS(RADIUS, CENTER) \
-block( \
- locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \
- locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
-)
-
//Admin perms are in global.dm.
/// To make it even more clear that something is a bitfield.
diff --git a/code/__DEFINES/_math.dm b/code/__DEFINES/_math.dm
index f0281f51cedb..d7c068237987 100644
--- a/code/__DEFINES/_math.dm
+++ b/code/__DEFINES/_math.dm
@@ -14,6 +14,8 @@
#define CEILING(x, y) ( -round(-(x) / (y)) * (y) )
+#define ROUND_UP(x) ( -round(-(x)))
+
// round() acts like floor(x, 1) by default but can't handle other values
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
diff --git a/code/__DEFINES/dcs/signals/atom/signals_atom.dm b/code/__DEFINES/dcs/signals/atom/signals_atom.dm
index d9bd1202c159..4e4458232669 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_atom.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_atom.dm
@@ -1,6 +1,7 @@
/// From /atom/proc/Decorate
#define COMSIG_ATOM_DECORATED "atom_decorated"
-
+//from SSatoms InitAtom - Only if the atom was not deleted or failed initialization and has a loc
+#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON "atom_init_success_on"
///from base of atom/setDir(): (old_dir, new_dir). Called before the direction changes.
#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change"
diff --git a/code/__DEFINES/dcs/signals/atom/signals_turf.dm b/code/__DEFINES/dcs/signals/atom/signals_turf.dm
index 6a0788bcf871..881ffc3ee139 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_turf.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_turf.dm
@@ -19,3 +19,6 @@
///from /turf/closed/wall/proc/place_poster
#define COMSIG_POSTER_PLACED "poster_placed"
+
+///from base of /datum/turf_reservation/proc/Release: (datum/turf_reservation/reservation)
+#define COMSIG_TURF_RESERVATION_RELEASED "turf_reservation_released"
diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm
index 905ff1246182..8aa700f4fb3a 100644
--- a/code/__DEFINES/dcs/signals/signals_global.dm
+++ b/code/__DEFINES/dcs/signals/signals_global.dm
@@ -8,6 +8,8 @@
///from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args)
#define COMSIG_GLOB_NEW_Z "!new_z"
+/// sent after world.maxx and/or world.maxy are expanded: (has_exapnded_world_maxx, has_expanded_world_maxy)
+#define COMSIG_GLOB_EXPANDED_WORLD_BOUNDS "!expanded_world_bounds"
///from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args)
#define COMSIG_GLOB_VEHICLE_ORDERED "!vehicle_ordered"
/// from /datum/controller/subsystem/ticker/fire
diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index 155a91fa62ed..ef3d17572f0d 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -118,8 +118,5 @@ require only minor tweaks.
#define MAP_ARMOR_STYLE_JUNGLE "jungle"
#define MAP_ARMOR_STYLE_PRISON "prison"
-//turf-only flags
-#define NOJAUNT_1 (1<<0)
-#define UNUSED_RESERVATION_TURF (1<<1)
-/// If a turf can be made dirty at roundstart. This is also used in areas.
-#define CAN_BE_DIRTY_1 (1<<2)
+/// A map key that corresponds to being one exclusively for Space.
+#define SPACE_KEY "space"
diff --git a/code/__DEFINES/objects.dm b/code/__DEFINES/objects.dm
index 292b315360c5..d495c8e8c012 100644
--- a/code/__DEFINES/objects.dm
+++ b/code/__DEFINES/objects.dm
@@ -175,3 +175,8 @@ GLOBAL_LIST_INIT(RESTRICTED_CAMERA_NETWORKS, list( //Those networks can only be
#define CHECKS_PASSED 1
#define STILL_ON_COOLDOWN 2
#define NO_LIGHT_STATE_CHANGE 3
+
+//tool capabilities or something i don't know
+#define REMOVE_CROWBAR (1<<0)
+#define BREAK_CROWBAR (1<<1)
+#define REMOVE_SCREWDRIVER (1<<2)
diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm
index a3299184e4ef..dfd470a5dba3 100644
--- a/code/__DEFINES/shuttles.dm
+++ b/code/__DEFINES/shuttles.dm
@@ -41,7 +41,7 @@
#define TRANSIT_REQUEST 1
#define TRANSIT_READY 2
-#define SHUTTLE_TRANSIT_BORDER 8
+#define SHUTTLE_TRANSIT_BORDER 16
#define PARALLAX_LOOP_TIME 25
#define HYPERSPACE_END_TIME 5
diff --git a/code/__DEFINES/turf_flags.dm b/code/__DEFINES/turf_flags.dm
index d7b3e90811d8..19dc17191d7c 100644
--- a/code/__DEFINES/turf_flags.dm
+++ b/code/__DEFINES/turf_flags.dm
@@ -1,3 +1,12 @@
+//turf_flags values
+/// Marks a turf as organic. Used for alien wall and membranes.
+#define TURF_ORGANIC (1<<0)
+/// If a turf is an usused reservation turf awaiting assignment
+#define UNUSED_RESERVATION_TURF (1<<1)
+/// If a turf is a reserved turf
+#define RESERVATION_TURF (1<<2)
+
+//ChangeTurf options to change its behavior
#define CHANGETURF_DEFER_CHANGE (1<<0)
/// This flag prevents changeturf from gathering air from nearby turfs to fill the new turf with an approximation of local air
#define CHANGETURF_IGNORE_AIR (1<<1)
@@ -5,12 +14,3 @@
/// A flag for PlaceOnTop to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE
#define CHANGETURF_SKIP (1<<3)
-#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS)
-
-/// Marks a turf as organic. Used for alien wall and membranes.
-#define TURF_ORGANIC (1<<0)
-
-
-#define REMOVE_CROWBAR (1<<0)
-#define BREAK_CROWBAR (1<<1)
-#define REMOVE_SCREWDRIVER (1<<2)
diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm
new file mode 100644
index 000000000000..b9a80d4ab257
--- /dev/null
+++ b/code/__DEFINES/turfs.dm
@@ -0,0 +1,29 @@
+#define RANGE_TURFS(RADIUS, CENTER) \
+block( \
+ locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \
+ locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
+)
+
+#define RECT_TURFS(H_RADIUS, V_RADIUS, CENTER) \
+ block( \
+ locate(max((CENTER).x-(H_RADIUS),1), max((CENTER).y-(V_RADIUS),1), (CENTER).z), \
+ locate(min((CENTER).x+(H_RADIUS),world.maxx), min((CENTER).y+(V_RADIUS),world.maxy), (CENTER).z) \
+ )
+
+///Returns all turfs in a zlevel
+#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL))
+
+/// Returns a list of turfs in the rectangle specified by BOTTOM LEFT corner and height/width, checks for being outside the world border for you
+#define CORNER_BLOCK(corner, width, height) CORNER_BLOCK_OFFSET(corner, width, height, 0, 0)
+
+/// Returns a list of turfs similar to CORNER_BLOCK but with offsets
+#define CORNER_BLOCK_OFFSET(corner, width, height, offset_x, offset_y) ((block(locate(corner.x + offset_x, corner.y + offset_y, corner.z), locate(min(corner.x + (width - 1) + offset_x, world.maxx), min(corner.y + (height - 1) + offset_y, world.maxy), corner.z))))
+
+/// 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))
+
+#define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3]))
diff --git a/code/__DEFINES/typecheck/generic_types.dm b/code/__DEFINES/typecheck/generic_types.dm
index d9fa3df55430..587108d5b5e6 100644
--- a/code/__DEFINES/typecheck/generic_types.dm
+++ b/code/__DEFINES/typecheck/generic_types.dm
@@ -11,6 +11,7 @@
#define ismovableatom(A) (ismovable(A))
#define isatom(A) (isloc(A))
#define isfloorturf(A) (istype(A, /turf/open/floor))
+#define isclosedturf(A) (istype(A, /turf/closed))
#define isweakref(D) (istype(D, /datum/weakref))
#define isgenerator(A) (istype(A, /generator))
diff --git a/code/__HELPERS/lighting.dm b/code/__HELPERS/lighting.dm
index 08c360849b58..e768d9d1255c 100644
--- a/code/__HELPERS/lighting.dm
+++ b/code/__HELPERS/lighting.dm
@@ -1,3 +1,5 @@
+#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS)
+
/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EMISSIVE_COLOR].
/proc/emissive_appearance(icon, icon_state = "", layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE)
var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS)
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 30ef9428586d..9a8528aabcc3 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -534,7 +534,7 @@
//Copies a list, and all lists inside it recusively
//Does not copy any other reference type
-/proc/deepCopyList(list/L)
+/proc/deep_copy_list(list/L)
if(!islist(L))
return L
. = L.Copy()
@@ -545,10 +545,10 @@
continue
var/value = .[key]
if(islist(value))
- value = deepCopyList(value)
+ value = deep_copy_list(value)
.[key] = value
if(islist(key))
- key = deepCopyList(key)
+ key = deep_copy_list(key)
.[i] = key
.[key] = value
diff --git a/code/__HELPERS/logging.dm b/code/__HELPERS/logging.dm
index d6c18c8a93be..59e4c7710992 100644
--- a/code/__HELPERS/logging.dm
+++ b/code/__HELPERS/logging.dm
@@ -286,6 +286,16 @@ GLOBAL_PROTECT(config_error_log)
WRITE_LOG(GLOB.config_error_log, text)
SEND_TEXT(world.log, text)
+/// Logging for mapping errors
+/proc/log_mapping(text, skip_world_log)
+#ifdef UNIT_TESTS
+ GLOB.unit_test_mapping_logs += text
+#endif
+ if(skip_world_log)
+ return
+ WRITE_LOG(GLOB.mapping_log, text)
+ SEND_TEXT(world.log, text)
+
/proc/log_admin_private(text)
log_admin(text)
diff --git a/code/__HELPERS/string_lists.dm b/code/__HELPERS/string_lists.dm
new file mode 100644
index 000000000000..076bbf642756
--- /dev/null
+++ b/code/__HELPERS/string_lists.dm
@@ -0,0 +1,23 @@
+GLOBAL_LIST_EMPTY(string_lists)
+
+/**
+ * Caches lists with non-numeric stringify-able values (text or typepath).
+ */
+/proc/string_list(list/values)
+ var/string_id = values.Join("-")
+
+ . = GLOB.string_lists[string_id]
+
+ if(.)
+ return .
+
+ return GLOB.string_lists[string_id] = values
+
+///A wrapper for baseturf string lists, to offer support of non list values, and a stack_trace if we have major issues
+/proc/baseturfs_string_list(list/values, turf/baseturf_holder)
+ if(!islist(values))
+ return values //baseturf things
+ // return values
+ if(length(values) > 10)
+ return string_list(list(/turf/closed/cordon/debug))
+ return string_list(values)
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 9287197bbcc2..7396e8624ba9 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -201,6 +201,24 @@
return ""
+//Returns a string with reserved characters and spaces after the first and last letters removed
+//Like trim(), but very slightly faster. worth it for niche usecases
+/proc/trim_reduced(text)
+ var/starting_coord = 1
+ var/text_len = length(text)
+ for (var/i in 1 to text_len)
+ if (text2ascii(text, i) > 32)
+ starting_coord = i
+ break
+
+ for (var/i = text_len, i >= starting_coord, i--)
+ if (text2ascii(text, i) > 32)
+ return copytext(text, starting_coord, i + 1)
+
+ if(starting_coord > 1)
+ return copytext(text, starting_coord)
+ return ""
+
//Returns a string with reserved characters and spaces before the first word and after the last word removed.
/proc/trim(text)
return trim_left(trim_right(text))
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 2fd7ea8919df..aa23131847d7 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -820,7 +820,7 @@
animation.master = target
flick(flick_anim, animation)
-//Will return the contents of an atom recursivly to a depth of 'searchDepth'
+///Will return the contents of an atom recursivly to a depth of 'searchDepth', not including starting atom
/atom/proc/GetAllContents(searchDepth = 5, list/toReturn = list())
for(var/atom/part as anything in contents)
toReturn += part
@@ -828,6 +828,16 @@
part.GetAllContents(searchDepth - 1, toReturn)
return toReturn
+///Returns the src and all recursive contents as a list. Includes the starting atom.
+/atom/proc/get_all_contents(ignore_flag_1)
+ . = list(src)
+ var/i = 0
+ while(i < length(.))
+ var/atom/checked_atom = .[++i]
+ if(checked_atom.flags_atom & ignore_flag_1)
+ continue
+ . += checked_atom.contents
+
/// Returns list of contents of a turf recursively, much like GetAllContents
/// We only get containing atoms in the turf, excluding multitiles bordering on it
/turf/proc/GetAllTurfStrictContents(searchDepth = 5, list/toReturn = list())
@@ -1905,8 +1915,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
return list(region_x1 & region_x2, region_y1 & region_y2)
-#define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3]))
-
//Vars that will not be copied when using /DuplicateObject
GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
"tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key",
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 59d14f2e0fed..bf9330c567cc 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -154,6 +154,12 @@ DEFINE_BITFIELD(flags_atom, list(
"HTML_USE_INITAL_ICON" = HTML_USE_INITAL_ICON,
))
+DEFINE_BITFIELD(turf_flags, list(
+ "TURF_ORGANIC" = TURF_ORGANIC,
+ "UNUSED_RESERVATION_TURF" = UNUSED_RESERVATION_TURF,
+ "RESERVATION_TURF" = RESERVATION_TURF,
+))
+
DEFINE_BITFIELD(flags_item, list(
"NODROP" = NODROP,
"NOBLUDGEON" = NOBLUDGEON,
diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm
index 7d9cd3324067..4dbf46086f02 100644
--- a/code/_globalvars/global_lists.dm
+++ b/code/_globalvars/global_lists.dm
@@ -37,6 +37,9 @@ GLOBAL_LIST_EMPTY(minimap_icons)
GLOBAL_LIST_EMPTY(mainship_pipes)
+/// List of all the maps that have been cached for /proc/load_map
+GLOBAL_LIST_EMPTY(cached_maps)
+
/proc/initiate_minimap_icons()
var/list/icons = list()
for(var/iconstate in icon_states('icons/UI_icons/map_blips.dmi'))
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index ae4783e4837a..f0d5ee14363e 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -19,6 +19,9 @@ SUBSYSTEM_DEF(atoms)
var/list/BadInitializeCalls = list()
+ ///initAtom() adds the atom its creating to this list iff InitializeAtoms() has been given a list to populate as an argument
+ var/list/created_atoms
+
initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/Initialize(timeofday)
@@ -34,7 +37,7 @@ SUBSYSTEM_DEF(atoms)
populate_seed_list()
return SS_INIT_SUCCESS
-/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms)
+/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return)
if(initialized == INITIALIZATION_INSSATOMS)
return
@@ -73,7 +76,10 @@ SUBSYSTEM_DEF(atoms)
processing_late_loaders = FALSE
/// Actually creates the list of atoms. Exists soley so a runtime in the creation logic doesn't cause initalized to totally break
-/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms)
+/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms, list/atoms_to_return = null)
+ if (atoms_to_return)
+ LAZYINITLIST(created_atoms)
+
#ifdef TESTING
var/count
#endif
@@ -152,12 +158,10 @@ SUBSYSTEM_DEF(atoms)
qdeleted = TRUE
else if(!(A.flags_atom & INITIALIZED))
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
- /*
else
- SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
+ SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON)
if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables
created_atoms += A.get_all_contents()
- */
return qdeleted || QDELING(A)
diff --git a/code/controllers/subsystem/interior.dm b/code/controllers/subsystem/interior.dm
index 8abc3179f191..e2b845f833d7 100644
--- a/code/controllers/subsystem/interior.dm
+++ b/code/controllers/subsystem/interior.dm
@@ -15,11 +15,11 @@ SUBSYSTEM_DEF(interior)
var/height_to_request = template.height + INTERIOR_BORDER_SIZE
var/width_to_request = template.width + INTERIOR_BORDER_SIZE
- var/datum/turf_reservation/reserved_area = SSmapping.RequestBlockReservation(width_to_request, height_to_request, type = /datum/turf_reservation/interior)
+ var/datum/turf_reservation/reserved_area = SSmapping.request_turf_block_reservation(width_to_request, height_to_request, reservation_type = /datum/turf_reservation/interior)
- var/list/bottom_left = reserved_area.bottom_left_coords
+ var/turf/bottom_left = reserved_area.bottom_left_turfs[1]
- var/list/bounds = template.load(locate(bottom_left[1] + (INTERIOR_BORDER_SIZE / 2), bottom_left[2] + (INTERIOR_BORDER_SIZE / 2), bottom_left[3]), centered = FALSE)
+ var/list/bounds = template.load(locate(bottom_left.x + (INTERIOR_BORDER_SIZE / 2), bottom_left.y + (INTERIOR_BORDER_SIZE / 2), bottom_left.z), centered = FALSE)
var/list/turfs = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
@@ -51,12 +51,7 @@ SUBSYSTEM_DEF(interior)
if(!isturf(loc))
loc = get_turf(loc)
- var/datum/weakref/reservation_weakref = SSmapping.used_turfs[loc]
-
- if(!reservation_weakref)
- return
-
- var/datum/turf_reservation/interior/reservation = reservation_weakref.resolve()
+ var/datum/turf_reservation/interior/reservation = SSmapping.used_turfs[loc]
if(!istype(reservation))
return FALSE
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 0f4a63ff65e8..f4e4b5979618 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = SS_INIT_MAPPING
- flags = SS_NO_FIRE
+ runlevels = ALL
var/list/datum/map_config/configs
var/list/datum/map_config/next_map_configs
@@ -19,16 +19,24 @@ SUBSYSTEM_DEF(mapping)
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/datum/turf_reservations //list of turf reservations
var/list/used_turfs = list() //list of turf = datum/turf_reservation
+ /// List of lists of turfs to reserve
+ var/list/lists_to_reserve = list()
var/list/reservation_ready = list()
var/clearing_reserved_turfs = FALSE
// Z-manager stuff
var/ground_start // should only be used for maploading-related tasks
- var/list/z_list
+ ///list of all z level datums in the order of their z (z level 1 is at index 1, etc.)
+ var/list/datum/space_level/z_list
var/datum/space_level/transit
var/num_of_res_levels = 1
+ /// True when in the process of adding a new Z-level, global locking
+ var/adding_new_zlevel = FALSE
+ /// list of traits and their associated z leves
+ var/list/z_trait_levels = list()
+
//dlete dis once #39770 is resolved
/datum/controller/subsystem/mapping/proc/HACK_LoadMapConfig()
if(!configs)
@@ -52,14 +60,10 @@ SUBSYSTEM_DEF(mapping)
loadWorld()
repopulate_sorted_areas()
preloadTemplates()
- // Add the transit level
- transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE))
- initialize_reserved_level(transit.z_value)
+ // Add the first transit level
+ var/datum/space_level/base_transit = add_reservation_zlevel()
+ initialize_reserved_level(base_transit.z_value)
repopulate_sorted_areas()
- for(var/maptype as anything in configs)
- var/datum/map_config/MC = configs[maptype]
- if(MC.perf_mode)
- GLOB.perf_flags |= MC.perf_mode
if(configs[GROUND_MAP])
send2chat(new /datum/tgs_message_content("<@&[CONFIG_GET(string/new_round_alert_role_id)]> Round restarted! Map is [configs[GROUND_MAP].map_name]"), CONFIG_GET(string/new_round_alert_channel))
@@ -68,21 +72,60 @@ SUBSYSTEM_DEF(mapping)
return SS_INIT_SUCCESS
+/datum/controller/subsystem/mapping/fire(resumed)
+ // Cache for sonic speed
+ var/list/unused_turfs = src.unused_turfs
+ // CM TODO: figure out if these 2 are needed. Might be required by updated versions of map reader
+ //var/list/world_contents = GLOB.areas_by_type[world.area].contents
+ //var/list/world_turf_contents = GLOB.areas_by_type[world.area].contained_turfs
+ 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/T = packet[packetlen]
+ T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE)
+ LAZYINITLIST(unused_turfs["[T.z]"])
+ unused_turfs["[T.z]"] |= T
+ //var/area/old_area = T.loc
+ //old_area.turfs_to_uncontain += T
+ T.turf_flags = UNUSED_RESERVATION_TURF
+ //world_contents += T
+ //world_turf_contents += T
+ packet.len--
+ packetlen = length(packet)
+
+ index++
+ lists_to_reserve.Cut(1, index)
+
/datum/controller/subsystem/mapping/proc/wipe_reservations(wipe_safety_delay = 100)
if(clearing_reserved_turfs || !initialized) //in either case this is just not needed.
return
clearing_reserved_turfs = TRUE
message_admins("Clearing dynamic reservation space.")
+ // /tg/ Shuttles have extra handling here to avoid them being desallocated
do_wipe_turf_reservations()
clearing_reserved_turfs = FALSE
+/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T)
+ RETURN_TYPE(/datum/turf_reservation)
+ return used_turfs[T]
+
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT
initialized = SSmapping.initialized
map_templates = SSmapping.map_templates
+
+ shuttle_templates = SSmapping.shuttle_templates
unused_turfs = SSmapping.unused_turfs
turf_reservations = SSmapping.turf_reservations
used_turfs = SSmapping.used_turfs
+ areas_in_z = SSmapping.areas_in_z
configs = SSmapping.configs
next_map_configs = SSmapping.next_map_configs
@@ -126,18 +169,33 @@ SUBSYSTEM_DEF(mapping)
var/start_z = world.maxz + 1
var/i = 0
for (var/level in traits)
- add_new_zlevel("[name][i ? " [i + 1]" : ""]", level)
+ add_new_zlevel("[name][i ? " [i + 1]" : ""]", level, contain_turfs = FALSE)
++i
+ // ================== CM Change ==================
+ // For some reason /tg/ SSmapping attempts to center the map in new Z-Level
+ // but because it's done before loading, it's calculated before performing
+ // X/Y world expansion. When loading a map bigger than world, this results
+ // in a negative offset and the start of the map to not be loaded.
+
// load the maps
for (var/datum/parsed_map/pm as anything in parsed_maps)
- var/cur_z = start_z + parsed_maps[pm]
- if (!pm.load(1, 1, cur_z, no_changeturf = TRUE))
+ var/bounds = pm.bounds
+ var/x_offset = 1
+ var/y_offset = 1
+ if(bounds && world.maxx > bounds[MAP_MAXX])
+ x_offset = round(world.maxx / 2 - bounds[MAP_MAXX] / 2) + 1
+ if(bounds && world.maxy > bounds[MAP_MAXY])
+ y_offset = round(world.maxy / 2 - bounds[MAP_MAXY] / 2) + 1
+ if (!pm.load(x_offset, y_offset, start_z + parsed_maps[pm], no_changeturf = TRUE, new_z = TRUE))
errorList |= pm.original_path
- if(istype(z_list[cur_z], /datum/space_level))
- var/datum/space_level/cur_level = z_list[cur_z]
- cur_level.x_bounds = pm.bounds[MAP_MAXX]
- cur_level.y_bounds = pm.bounds[MAP_MAXY]
+ // CM Snowflake for Mass Screenshot dimensions auto detection
+ for(var/z in bounds[MAP_MINZ] to bounds[MAP_MAXZ])
+ var/datum/space_level/zlevel = z_list[start_z + z - 1]
+ zlevel.bounds = list(bounds[MAP_MINX], bounds[MAP_MINY], z, bounds[MAP_MAXX], bounds[MAP_MAXY], z)
+
+ // =============== END CM Change =================
+
if(!silent)
INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!")
return parsed_maps
@@ -256,66 +314,78 @@ SUBSYSTEM_DEF(mapping)
var/datum/map_template/tent/new_tent = new template()
tent_type_templates[new_tent.map_id] = new_tent
-/datum/controller/subsystem/mapping/proc/RequestBlockReservation(width, height, z, type = /datum/turf_reservation, turf_type_override)
- UNTIL(initialized && !clearing_reserved_turfs)
- var/datum/turf_reservation/reserve = new type
- if(turf_type_override)
+/// Adds a new reservation z level. A bit of space that can be handed out on request
+/// Of note, reservations default to transit turfs, to make their most common use, shuttles, faster
+/datum/controller/subsystem/mapping/proc/add_reservation_zlevel(for_shuttles)
+ num_of_res_levels++
+ return add_new_zlevel("Transit/Reserved #[num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE))
+
+/// Requests a /datum/turf_reservation based on the given width, height, and z_size. You can specify a z_reservation to use a specific z level, or leave it null to use any z level.
+/datum/controller/subsystem/mapping/proc/request_turf_block_reservation(
+ width,
+ height,
+ z_size = 1,
+ z_reservation = null,
+ reservation_type = /datum/turf_reservation,
+ turf_type_override = null,
+)
+ UNTIL((!z_reservation || reservation_ready["[z_reservation]"]) && !clearing_reserved_turfs)
+ var/datum/turf_reservation/reserve = new reservation_type
+ if(!isnull(turf_type_override))
reserve.turf_type = turf_type_override
- if(!z)
+ if(!z_reservation)
for(var/i in levels_by_trait(ZTRAIT_RESERVED))
- if(reserve.Reserve(width, height, i))
+ if(reserve.reserve(width, height, z_size, 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
- log_debug("Ran out of space in existing transit levels, adding a new one")
- num_of_res_levels++
- var/datum/space_level/newReserved = add_new_zlevel("Transit/Reserved [num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE))
+ var/datum/space_level/newReserved = add_reservation_zlevel()
initialize_reserved_level(newReserved.z_value)
- for(var/i in levels_by_trait(ZTRAIT_RESERVED))
- if(reserve.Reserve(width, height, i))
- return reserve
- CRASH("Despite adding a fresh reserved zlevel still failed to get a reservation")
+ if(reserve.reserve(width, height, z_size, newReserved.z_value))
+ return reserve
else
- if(!level_trait(z, ZTRAIT_RESERVED))
- log_debug("Cannot block reserve on a non-ZTRAIT_RESERVED level")
+ if(!level_trait(z_reservation, ZTRAIT_RESERVED))
qdel(reserve)
return
else
- if(reserve.Reserve(width, height, z))
+ if(reserve.reserve(width, height, z_size, z_reservation))
return reserve
- log_debug("unknown reservation failure")
QDEL_NULL(reserve)
-//This is not for wiping reserved levels, use wipe_reservations() for that.
+///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.
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(!level_trait(z,ZTRAIT_RESERVED))
clearing_reserved_turfs = FALSE
CRASH("Invalid z level prepared for reservations.")
- var/turf/A = get_turf(locate(8,8,z))
- var/turf/B = get_turf(locate(world.maxx - 8,world.maxy - 8,z))
+ var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,z))
+ var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,z))
var/block = block(A, B)
- for(var/t in block)
- // No need to empty() these, because it's world init and they're
- // already /turf/open/space/basic.
- var/turf/T = t
- T.flags_atom |= UNUSED_RESERVATION_TURF
+ for(var/turf/T as anything in block)
+ // No need to empty() these, because they just got created and are already /turf/open/space/basic.
+ T.turf_flags = UNUSED_RESERVATION_TURF
+ CHECK_TICK
+
+ // Gotta create these suckers if we've not done so already
+ if(SSatoms.initialized)
+ SSatoms.InitializeAtoms(Z_TURFS(z))
+
unused_turfs["[z]"] = block
reservation_ready["[z]"] = TRUE
clearing_reserved_turfs = FALSE
-/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs)
- for(var/i in turfs)
- var/turf/T = i
- T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE)
- LAZYINITLIST(unused_turfs["[T.z]"])
- unused_turfs["[T.z]"] |= T
- T.flags_atom |= UNUSED_RESERVATION_TURF
- GLOB.areas_by_type[world.area].contents += T
- CHECK_TICK
+/// Schedules a group of turfs to be handed back to the reservation system's control
+/// If await is true, will sleep until the turfs are finished work
+/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs, await = FALSE)
+ lists_to_reserve += list(turfs)
+ if(await)
+ UNTIL(!length(turfs))
//DO NOT CALL THIS PROC DIRECTLY, CALL wipe_reservations().
/datum/controller/subsystem/mapping/proc/do_wipe_turf_reservations()
+ PRIVATE_PROC(TRUE)
UNTIL(initialized) //This proc is for AFTER init, before init turf reservations won't even exist and using this will likely break things.
for(var/i in turf_reservations)
var/datum/turf_reservation/TR = i
@@ -323,19 +393,28 @@ SUBSYSTEM_DEF(mapping)
qdel(TR, TRUE)
UNSETEMPTY(turf_reservations)
var/list/clearing = list()
- for(var/l in unused_turfs) //unused_turfs is a assoc list by z = list(turfs)
+ for(var/l in unused_turfs) //unused_turfs is an assoc list by z = list(turfs)
if(islist(unused_turfs[l]))
clearing |= unused_turfs[l]
clearing |= used_turfs //used turfs is an associative list, BUT, reserve_turfs() can still handle it. If the code above works properly, this won't even be needed as the turfs would be freed already.
unused_turfs.Cut()
used_turfs.Cut()
- reserve_turfs(clearing)
+ reserve_turfs(clearing, await = TRUE)
/datum/controller/subsystem/mapping/proc/reg_in_areas_in_z(list/areas)
for(var/B in areas)
var/area/A = B
A.reg_in_areas_in_z()
+/// Takes a z level datum, and tells the mapping subsystem to manage it
+/// Also handles things like plane offset generation, and other things that happen on a z level to z level basis
+/datum/controller/subsystem/mapping/proc/manage_z_level(datum/space_level/new_z, filled_with_space, contain_turfs = TRUE)
+ // First, add the z
+ z_list += new_z
+ // Then we build our lookup lists
+ //var/z_value = new_z.z_value
+ //TODO: All the Z-plane init stuff goes below here normally, we don't have that yet
+
/// Gets a name for the marine ship as per the enabled ship map configuration
/datum/controller/subsystem/mapping/proc/get_main_ship_name()
if(!configs)
diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm
index db0d449b4dd7..439f83ceb8c0 100644
--- a/code/controllers/subsystem/shuttles.dm
+++ b/code/controllers/subsystem/shuttles.dm
@@ -39,9 +39,6 @@ SUBSYSTEM_DEF(shuttle)
var/loading_shuttle = FALSE
/datum/controller/subsystem/shuttle/Initialize(timeofday)
- if(GLOB.perf_flags & PERF_TOGGLE_SHUTTLES)
- can_fire = FALSE
- return
initial_load()
return SS_INIT_SUCCESS
@@ -52,8 +49,6 @@ SUBSYSTEM_DEF(shuttle)
CHECK_TICK
/datum/controller/subsystem/shuttle/fire(resumed = FALSE)
- if(!resumed && (GLOB.perf_flags & PERF_TOGGLE_SHUTTLES))
- return
for(var/thing in mobile)
if(!thing)
mobile.Remove(thing)
@@ -187,13 +182,19 @@ SUBSYSTEM_DEF(shuttle)
var/transit_path = M.get_transit_path_type()
- var/datum/turf_reservation/proposal = SSmapping.RequestBlockReservation(transit_width, transit_height, null, /datum/turf_reservation/transit, transit_path)
+ var/datum/turf_reservation/proposal = SSmapping.request_turf_block_reservation(
+ transit_width,
+ transit_height,
+ 1,
+ reservation_type = /datum/turf_reservation/transit,
+ turf_type_override = transit_path,
+ )
if(!istype(proposal))
log_debug("generate_transit_dock() failed to get a block reservation from mapping system")
return FALSE
- var/turf/bottomleft = locate(proposal.bottom_left_coords[1], proposal.bottom_left_coords[2], proposal.bottom_left_coords[3])
+ var/turf/bottomleft = proposal.bottom_left_turfs[1]
// Then create a transit docking port in the middle
var/coords = M.return_coords(0, 0, dock_dir)
/* 0------2
@@ -362,7 +363,7 @@ SUBSYSTEM_DEF(shuttle)
return shuttle
-/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port)
+/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE)
// Check for an existing preview
if(preview_shuttle && (loading_template != preview_template))
preview_shuttle.jumpToNullSpace()
@@ -371,8 +372,7 @@ SUBSYSTEM_DEF(shuttle)
QDEL_NULL(preview_reservation)
if(!preview_shuttle)
- if(load_template(loading_template))
- preview_shuttle.linkup(loading_template, destination_port)
+ load_template(loading_template)
preview_template = loading_template
// get the existing shuttle information, if any
@@ -407,9 +407,6 @@ SUBSYSTEM_DEF(shuttle)
for(var/area/A as anything in preview_shuttle.shuttle_areas)
for(var/turf/T as anything in A)
- // turfs inside the shuttle are not available for shuttles
- T.flags_atom &= ~UNUSED_RESERVATION_TURF
-
// update underlays
if(istype(T, /turf/closed/shuttle))
var/dx = T.x - preview_shuttle.x
@@ -418,8 +415,10 @@ SUBSYSTEM_DEF(shuttle)
T.underlays.Cut()
T.underlays += mutable_appearance(target_lz.icon, target_lz.icon_state, TURF_LAYER, FLOOR_PLANE)
+ preview_shuttle.register(replace)
var/list/force_memory = preview_shuttle.movement_force
preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
+
preview_shuttle.initiate_docking(D)
preview_shuttle.movement_force = force_memory
@@ -430,7 +429,7 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle.timer = timer
preview_shuttle.mode = mode
- preview_shuttle.register()
+ preview_shuttle.postregister(replace)
// TODO indicate to the user that success happened, rather than just
// blanking the modification tab
@@ -440,16 +439,21 @@ SUBSYSTEM_DEF(shuttle)
selected = null
QDEL_NULL(preview_reservation)
-/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/S)
+/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/loading_template)
. = FALSE
- // load shuttle template, centred at shuttle import landmark,
- preview_reservation = SSmapping.RequestBlockReservation(S.width, S.height, SSmapping.transit.z_value, /datum/turf_reservation/transit)
+ // Load shuttle template to a fresh block reservation.
+ preview_reservation = SSmapping.request_turf_block_reservation(
+ loading_template.width,
+ loading_template.height,
+ 1,
+ reservation_type = /datum/turf_reservation/transit,
+ )
if(!preview_reservation)
CRASH("failed to reserve an area for shuttle template loading")
- var/turf/BL = TURF_FROM_COORDS_LIST(preview_reservation.bottom_left_coords)
- S.load(BL, centered = FALSE, register = FALSE)
+ var/turf/bottom_left = preview_reservation.bottom_left_turfs[1]
+ loading_template.load(bottom_left, centered = FALSE, register = FALSE)
- var/affected = S.get_affected_turfs(BL, centered=FALSE)
+ var/affected = loading_template.get_affected_turfs(bottom_left, centered=FALSE)
var/found = 0
// Search the turfs for docking ports
@@ -463,13 +467,13 @@ SUBSYSTEM_DEF(shuttle)
found++
if(found > 1)
qdel(P, force=TRUE)
- log_world("Map warning: Shuttle Template [S.mappath] has multiple mobile docking ports.")
+ log_world("Map warning: Shuttle Template [loading_template.mappath] has multiple mobile docking ports.")
else
preview_shuttle = P
if(istype(P, /obj/docking_port/stationary))
- log_world("Map warning: Shuttle Template [S.mappath] has a stationary docking port.")
+ log_world("Map warning: Shuttle Template [loading_template.mappath] has a stationary docking port.")
if(!found)
- var/msg = "load_template(): Shuttle Template [S.mappath] has no mobile docking port. Aborting import."
+ var/msg = "load_template(): Shuttle Template [loading_template.mappath] has no mobile docking port. Aborting import."
for(var/T in affected)
var/turf/T0 = T
T0.empty()
@@ -478,7 +482,7 @@ SUBSYSTEM_DEF(shuttle)
WARNING(msg)
return
//Everything fine
- S.post_load(preview_shuttle)
+ loading_template.post_load(preview_shuttle)
return TRUE
/datum/controller/subsystem/shuttle/proc/unload_preview()
diff --git a/code/datums/ammo/bullet/pistol.dm b/code/datums/ammo/bullet/pistol.dm
index 8be63b0a15af..937c40d16cff 100644
--- a/code/datums/ammo/bullet/pistol.dm
+++ b/code/datums/ammo/bullet/pistol.dm
@@ -66,7 +66,7 @@
/datum/ammo/bullet/pistol/ap/toxin/on_hit_turf(turf/T, obj/projectile/P)
. = ..()
- if(T.flags_turf & TURF_ORGANIC)
+ if(T.turf_flags & TURF_ORGANIC)
P.damage *= organic_damage_mult
/datum/ammo/bullet/pistol/ap/toxin/on_hit_obj(obj/O, obj/projectile/P)
@@ -197,7 +197,7 @@
/datum/ammo/bullet/pistol/squash/toxin/on_hit_turf(turf/T, obj/projectile/P)
. = ..()
- if(T.flags_turf & TURF_ORGANIC)
+ if(T.turf_flags & TURF_ORGANIC)
P.damage *= organic_damage_mult
/datum/ammo/bullet/pistol/squash/toxin/on_hit_obj(obj/O, obj/projectile/P)
diff --git a/code/datums/ammo/bullet/revolver.dm b/code/datums/ammo/bullet/revolver.dm
index 633bf3e2f7ff..0688e615378e 100644
--- a/code/datums/ammo/bullet/revolver.dm
+++ b/code/datums/ammo/bullet/revolver.dm
@@ -52,7 +52,7 @@
/datum/ammo/bullet/revolver/marksman/toxin/on_hit_turf(turf/T, obj/projectile/P)
. = ..()
- if(T.flags_turf & TURF_ORGANIC)
+ if(T.turf_flags & TURF_ORGANIC)
P.damage *= organic_damage_mult
/datum/ammo/bullet/revolver/marksman/toxin/on_hit_obj(obj/O, obj/projectile/P)
diff --git a/code/datums/ammo/bullet/rifle.dm b/code/datums/ammo/bullet/rifle.dm
index 279a828ed786..7711e082a596 100644
--- a/code/datums/ammo/bullet/rifle.dm
+++ b/code/datums/ammo/bullet/rifle.dm
@@ -74,7 +74,7 @@
/datum/ammo/bullet/rifle/ap/toxin/on_hit_turf(turf/T, obj/projectile/P)
. = ..()
- if(T.flags_turf & TURF_ORGANIC)
+ if(T.turf_flags & TURF_ORGANIC)
P.damage *= organic_damage_mult
/datum/ammo/bullet/rifle/ap/toxin/on_hit_obj(obj/O, obj/projectile/P)
diff --git a/code/datums/ammo/bullet/smg.dm b/code/datums/ammo/bullet/smg.dm
index e24b3021da97..3fa087972fbe 100644
--- a/code/datums/ammo/bullet/smg.dm
+++ b/code/datums/ammo/bullet/smg.dm
@@ -51,7 +51,7 @@
/datum/ammo/bullet/smg/ap/toxin/on_hit_turf(turf/T, obj/projectile/P)
. = ..()
- if(T.flags_turf & TURF_ORGANIC)
+ if(T.turf_flags & TURF_ORGANIC)
P.damage *= organic_damage_mult
/datum/ammo/bullet/smg/ap/toxin/on_hit_obj(obj/O, obj/projectile/P)
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index 98bcf296755b..0eba86add45d 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -62,43 +62,30 @@
locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ]))
for(var/i in 1 to turfs.len)
var/turf/place = turfs[i]
+
+ // ================== CM Change ==================
+ // We perform atom initialization of the docking_ports BEFORE skipping space,
+ // because our lifeboats have their corners as object props and still
+ // reside on space turfs. Notably the bottom left corner, which also contains
+ // the docking port.
+
+ for(var/obj/docking_port/mobile/port in place)
+ SSatoms.InitializeAtoms(list(port))
+ if(register)
+ port.register()
+
if(istype(place, /turf/open/space)) // This assumes all shuttles are loaded in a single spot then moved to their real destination.
continue
if(length(place.baseturfs) < 2) // Some snowflake shuttle shit
continue
place.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle)
-
- for(var/obj/docking_port/mobile/port in place)
- if(register)
- port.register()
- if(isnull(port_x_offset))
- continue
- switch(port.dir) // Yeah this looks a little ugly but mappers had to do this in their head before
- if(NORTH)
- port.width = width
- port.height = height
- port.dwidth = port_x_offset - 1
- port.dheight = port_y_offset - 1
- if(EAST)
- port.width = height
- port.height = width
- port.dwidth = height - port_y_offset
- port.dheight = port_x_offset - 1
- if(SOUTH)
- port.width = width
- port.height = height
- port.dwidth = width - port_x_offset
- port.dheight = height - port_y_offset
- if(WEST)
- port.width = height
- port.height = width
- port.dwidth = port_y_offset - 1
- port.dheight = width - port_x_offset
+ // =============== END CM Change =================
//Whatever special stuff you want
-/datum/map_template/shuttle/proc/post_load(obj/docking_port/mobile/M)
+/datum/map_template/shuttle/post_load(obj/docking_port/mobile/M)
if(movement_force)
M.movement_force = movement_force.Copy()
+ M.linkup()
/datum/map_template/shuttle/vehicle
@@ -115,3 +102,9 @@
/datum/map_template/shuttle/trijent_elevator/B
elevator_network = "B"
+
+/datum/map_template/shuttle/trijent_elevator/post_load(obj/docking_port/mobile/M)
+ . = ..()
+ var/obj/docking_port/mobile/trijent_elevator/elev = M
+ elev.elevator_network = elevator_network
+ log_debug("Adding network [elevator_network] to [M.id]")
diff --git a/code/datums/tutorial/_tutorial.dm b/code/datums/tutorial/_tutorial.dm
index 7dd7ac85c04d..f6e70e33cdd9 100644
--- a/code/datums/tutorial/_tutorial.dm
+++ b/code/datums/tutorial/_tutorial.dm
@@ -50,12 +50,12 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial)
tutorial_mob = starting_mob
- reservation = SSmapping.RequestBlockReservation(initial(tutorial_template.width), initial(tutorial_template.height))
+ reservation = SSmapping.request_turf_block_reservation(initial(tutorial_template.width), initial(tutorial_template.height), 1)
if(!reservation)
abort_tutorial()
return FALSE
- var/turf/bottom_left_corner_reservation = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3])
+ var/turf/bottom_left_corner_reservation = reservation.bottom_left_turfs[1]
var/datum/map_template/tutorial/template = new tutorial_template
template.load(bottom_left_corner_reservation, FALSE, TRUE)
var/obj/landmark = locate(/obj/effect/landmark/tutorial_bottom_left) in GLOB.landmarks_list
@@ -98,11 +98,7 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial)
/// Verify the template loaded fully and without error.
/datum/tutorial/proc/verify_template_loaded()
// We subtract 1 from x and y because the bottom left corner doesn't start at the walls.
- var/turf/true_bottom_left_corner = locate(
- reservation.bottom_left_coords[1],
- reservation.bottom_left_coords[2],
- reservation.bottom_left_coords[3],
- )
+ var/turf/true_bottom_left_corner = reservation.bottom_left_turfs[1]
// We subtract 1 from x and y here because the bottom left corner counts as the first tile
var/turf/top_right_corner = locate(
true_bottom_left_corner.x + initial(tutorial_template.width) - 1,
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5f36b3b8b390..f2ae11425f42 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -503,7 +503,7 @@ Parameters are passed from New.
onclose(usr, "[name]")
///This proc is called on atoms when they are loaded into a shuttle
-/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+/atom/proc/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
return
/**
diff --git a/code/game/machinery/doors/multi_tile.dm b/code/game/machinery/doors/multi_tile.dm
index 0a179af27803..f943cd696897 100644
--- a/code/game/machinery/doors/multi_tile.dm
+++ b/code/game/machinery/doors/multi_tile.dm
@@ -336,7 +336,7 @@
continue
INVOKE_ASYNC(atom_movable, TYPE_PROC_REF(/atom/movable, throw_atom), projected, 1, SPEED_FAST, null, FALSE)
-/obj/structure/machinery/door/airlock/multi_tile/almayer/dropshiprear/lifeboat/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override)
+/obj/structure/machinery/door/airlock/multi_tile/almayer/dropshiprear/lifeboat/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
. = ..()
if(istype(port, /obj/docking_port/mobile/crashable/lifeboat))
var/obj/docking_port/mobile/crashable/lifeboat/lifeboat = port
diff --git a/code/game/objects/items/fulton.dm b/code/game/objects/items/fulton.dm
index e36d269c8b90..664c7871ba7f 100644
--- a/code/game/objects/items/fulton.dm
+++ b/code/game/objects/items/fulton.dm
@@ -140,10 +140,12 @@ GLOBAL_LIST_EMPTY(deployed_fultons)
sleep(30)
original_location = get_turf(attached_atom)
playsound(loc, 'sound/items/fulton.ogg', 50, 1)
- reservation = SSmapping.RequestBlockReservation(3, 3, turf_type_override = /turf/open/space)
- var/middle_x = reservation.bottom_left_coords[1] + Floor((reservation.top_right_coords[1] - reservation.bottom_left_coords[1]) / 2)
- var/middle_y = reservation.bottom_left_coords[2] + Floor((reservation.top_right_coords[2] - reservation.bottom_left_coords[2]) / 2)
- var/turf/space_tile = locate(middle_x, middle_y, reservation.bottom_left_coords[3])
+ reservation = SSmapping.request_turf_block_reservation(3, 3, 1, turf_type_override = /turf/open/space)
+ var/turf/bottom_left_turf = reservation.bottom_left_turfs[1]
+ var/turf/top_right_turf = reservation.top_right_turfs[1]
+ var/middle_x = bottom_left_turf.x + Floor((top_right_turf.x - bottom_left_turf.x) / 2)
+ var/middle_y = bottom_left_turf.y + Floor((top_right_turf.y - bottom_left_turf.y) / 2)
+ var/turf/space_tile = locate(middle_x, middle_y, bottom_left_turf.z)
if(!space_tile)
visible_message(SPAN_WARNING("[src] begins beeping like crazy. Something is wrong!"))
return
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm
index bf84bc04bf10..abc745dbdd45 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed.dm
@@ -15,6 +15,21 @@
icon_state = "black"
mouse_opacity = FALSE
+/// Cordon turf marking z-level boundaries and surrounding reservations
+/turf/closed/cordon
+ name = "world border"
+ icon = 'icons/turf/shuttle.dmi'
+ icon_state = "pclosed"
+ layer = ABOVE_TURF_LAYER
+ baseturfs = /turf/closed/cordon
+
+/// Used as placeholder turf when something went really wrong, as per /tg/ string lists handler
+/turf/closed/cordon/debug
+ name = "debug turf"
+ desc = "This turf shouldn't be here and probably result of incorrect turf replacement. Adminhelp about it or report it in an issue."
+ color = "#660088"
+ baseturfs = /turf/closed/cordon/debug
+
/turf/closed/mineral //mineral deposits
name = "Rock"
icon = 'icons/turf/walls/walls.dmi'
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index be58259e17ba..3d15a8c46570 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -24,7 +24,6 @@
*/
-
/turf
icon = 'icons/turf/floors/floors.dmi'
var/intact_tile = 1 //used by floors to distinguish floor with/without a floortile(e.g. plating).
@@ -47,7 +46,7 @@
var/changing_turf = FALSE
var/chemexploded = FALSE // Prevents explosion stacking
- var/flags_turf = NO_FLAGS
+ var/turf_flags = NO_FLAGS
/// Whether we've broken through the ceiling yet
var/ceiling_debrised = FALSE
@@ -57,6 +56,7 @@
///Lumcount added by sources other than lighting datum objects, such as the overlay lighting component.
var/dynamic_lumcount = 0
+
///List of light sources affecting this turf.
///Which directions does this turf block the vision of, taking into account both the turf's opacity and the movable opacity_sources.
var/directional_opacity = NONE
@@ -145,6 +145,22 @@
/turf/proc/update_icon() //Base parent. - Abby
return
+/// Call to move a turf from its current area to a new one
+/turf/proc/change_area(area/old_area, area/new_area)
+ //dont waste our time
+ if(old_area == new_area)
+ return
+
+ //move the turf
+ new_area.contents += src
+
+ //changes to make after turf has moved
+ on_change_area(old_area, new_area)
+
+/// Allows for reactions to an area change without inherently requiring change_area() be called (I hate maploading)
+/turf/proc/on_change_area(area/old_area, area/new_area)
+ transfer_area_lighting(old_area, new_area)
+
/turf/proc/add_cleanable_overlays()
for(var/cleanable_type in cleanables)
var/obj/effect/decal/cleanable/C = cleanables[cleanable_type]
@@ -435,6 +451,10 @@
W.levelupdate()
return W
+//If you modify this function, ensure it works correctly with lateloaded map templates.
+/turf/proc/AfterChange(flags, oldType) //called after a turf has been replaced in ChangeTurf()
+ return // Placeholder. This is mostly used by /tg/ code for atmos updates
+
// Take off the top layer turf and replace it with the next baseturf down
/turf/proc/ScrapeAway(amount=1, flags)
if(!amount)
@@ -767,6 +787,33 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
change_type = new_baseturfs
return ChangeTurf(change_type, null, flags)
+/// Places a turf on top - for map loading
+/turf/proc/load_on_top(turf/added_layer, flags)
+ var/area/our_area = get_area(src)
+ flags = our_area.PlaceOnTopReact(list(baseturfs), added_layer, flags)
+
+ if(flags & CHANGETURF_SKIP) // We haven't been initialized
+ if(flags_atom & INITIALIZED)
+ stack_trace("CHANGETURF_SKIP was used in a PlaceOnTop call for a turf that's initialized. This is a mistake. [src]([type])")
+ assemble_baseturfs()
+
+ var/turf/new_turf
+ if(!length(baseturfs))
+ baseturfs = list(baseturfs)
+
+ var/list/old_baseturfs = baseturfs.Copy()
+ if(!isclosedturf(src))
+ old_baseturfs += type
+
+ new_turf = ChangeTurf(added_layer, null, flags)
+ new_turf.assemble_baseturfs(initial(added_layer.baseturfs)) // The baseturfs list is created like roundstart
+ if(!length(new_turf.baseturfs))
+ new_turf.baseturfs = list(baseturfs)
+
+ // The old baseturfs are put underneath, and we sort out the unwanted ones
+ new_turf.baseturfs = baseturfs_string_list(old_baseturfs + (new_turf.baseturfs - GLOB.blacklisted_automated_baseturfs), new_turf)
+ return new_turf
+
/turf/proc/insert_self_into_baseturfs()
baseturfs += type
diff --git a/code/game/turfs/walls/wall_types.dm b/code/game/turfs/walls/wall_types.dm
index d546e7274331..a90e821fabbf 100644
--- a/code/game/turfs/walls/wall_types.dm
+++ b/code/game/turfs/walls/wall_types.dm
@@ -720,7 +720,7 @@ INITIALIZE_IMMEDIATE(/turf/closed/wall/indestructible/splashscreen)
var/hivenumber = XENO_HIVE_NORMAL
var/should_track_build = FALSE
var/datum/cause_data/construction_data
- flags_turf = TURF_ORGANIC
+ turf_flags = TURF_ORGANIC
/turf/closed/wall/resin/Initialize(mapload)
. = ..()
diff --git a/code/game/turfs/walls/walls.dm b/code/game/turfs/walls/walls.dm
index cb58ad2274a4..251b23ad9c57 100644
--- a/code/game/turfs/walls/walls.dm
+++ b/code/game/turfs/walls/walls.dm
@@ -173,7 +173,7 @@
if (acided_hole)
. += SPAN_WARNING("There's a large hole in the wall that could've been caused by some sort of acid.")
- if(flags_turf & TURF_ORGANIC)
+ if(turf_flags & TURF_ORGANIC)
return // Skip the part below. 'Organic' walls aren't deconstructable with tools.
switch(d_state)
diff --git a/code/game/world.dm b/code/game/world.dm
index 2b7dacee373a..958278042ea5 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -125,6 +125,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt"))
GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log"
GLOB.round_stats = "[GLOB.log_directory]/round_stats.log"
GLOB.scheduler_stats = "[GLOB.log_directory]/round_scheduler_stats.log"
+ GLOB.mapping_log = "[GLOB.log_directory]/mapping.log"
GLOB.strain_logs = "[GLOB.log_directory]/strain_logs.log"
start_log(GLOB.tgui_log)
@@ -134,6 +135,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt"))
start_log(GLOB.world_runtime_log)
start_log(GLOB.round_stats)
start_log(GLOB.scheduler_stats)
+ start_log(GLOB.mapping_log)
start_log(GLOB.strain_logs)
if(fexists(GLOB.config_error_log))
@@ -317,9 +319,39 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt"))
/world/proc/on_tickrate_change()
SStimer.reset_buckets()
+/**
+ * Handles incresing the world's maxx var and intializing the new turfs and assigning them to the global area.
+ * If map_load_z_cutoff is passed in, it will only load turfs up to that z level, inclusive.
+ * This is because maploading will handle the turfs it loads itself.
+ */
+/world/proc/increase_max_x(new_maxx, map_load_z_cutoff = maxz)
+ if(new_maxx <= maxx)
+ return
+// var/old_max = world.maxx
+ maxx = new_maxx
+ if(!map_load_z_cutoff)
+ return
+// var/area/global_area = GLOB.areas_by_type[world.area] // We're guaranteed to be touching the global area, so we'll just do this
+// var/list/to_add = block(
+// locate(old_max + 1, 1, 1),
+// locate(maxx, maxy, map_load_z_cutoff))
+// global_area.contained_turfs += to_add
+
+/world/proc/increase_max_y(new_maxy, map_load_z_cutoff = maxz)
+ if(new_maxy <= maxy)
+ return
+// var/old_maxy = maxy
+ maxy = new_maxy
+ if(!map_load_z_cutoff)
+ return
+// var/area/global_area = GLOB.areas_by_type[world.area] // We're guarenteed to be touching the global area, so we'll just do this
+// var/list/to_add = block(
+// locate(1, old_maxy + 1, 1),
+// locate(maxx, maxy, map_load_z_cutoff))
+// global_area.contained_turfs += to_add
+
/world/proc/incrementMaxZ()
maxz++
- //SSmobs.MaxZChanged()
/** For initializing and starting byond-tracy when BYOND_TRACY is defined
* byond-tracy is a useful profiling tool that allows the user to view the CPU usage and execution time of procs as they run.
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 535a55ca47b3..ede1cd029d9d 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -115,8 +115,10 @@
var/height
if(istype(SSmapping.z_list[cur_z], /datum/space_level))
var/datum/space_level/cur_level = SSmapping.z_list[cur_z]
- width = cur_level.x_bounds - half_chunk_size + 2
- height = cur_level.y_bounds - half_chunk_size + 2
+ cur_x += cur_level.bounds[MAP_MINX] - 1
+ cur_y += cur_level.bounds[MAP_MINY] - 1
+ width = cur_level.bounds[MAP_MAXX] - cur_level.bounds[MAP_MINX] - half_chunk_size + 1
+ height = cur_level.bounds[MAP_MAXY] - cur_level.bounds[MAP_MINY] - half_chunk_size + 1
else
width = world.maxx - half_chunk_size + 2
height = world.maxy - half_chunk_size + 2
@@ -339,3 +341,20 @@
show_browser(usr, "[str]", "Ticker Count", "tickercount")
+
+#ifdef TESTING
+GLOBAL_LIST_EMPTY(dirty_vars)
+
+/client/proc/see_dirty_varedits()
+ set category = "Debug.Mapping"
+ set name = "Dirty Varedits"
+
+ var/list/dat = list()
+ dat += "
Abandon all hope ye who enter here
"
+ for(var/thing in GLOB.dirty_vars)
+ dat += "[thing]
"
+ CHECK_TICK
+ var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", nwidth = 900, nheight = 750)
+ popup.set_content(dat.Join())
+ popup.open()
+#endif
diff --git a/code/modules/admin/verbs/load_event_level.dm b/code/modules/admin/verbs/load_event_level.dm
index 165506376b9b..72d004e03261 100644
--- a/code/modules/admin/verbs/load_event_level.dm
+++ b/code/modules/admin/verbs/load_event_level.dm
@@ -24,8 +24,6 @@
// Get dims & guesstimate center turf (in practice, current implem means min is always 1)
var/dim_x = boundaries[MAP_MAXX] - boundaries[MAP_MINX] + 1
var/dim_y = boundaries[MAP_MAXY] - boundaries[MAP_MINY] + 1
- var/center_x = boundaries[MAP_MINX] + round(dim_x / 2) // Technically off by 0.5 due to above +1. Whatever
- var/center_y = boundaries[MAP_MINY] + round(dim_y / 2)
var/prompt = alert(C, "Are you SURE you want to load this template as level ? This is SLOW and can freeze server for a bit. Dimensions are: [dim_x] x [dim_y]", "Template Confirm" ,"Yes","Nope!")
if(prompt != "Yes")
@@ -40,6 +38,9 @@
to_chat(C, "Failed to load the template to a Z-Level! Sorry!")
return
+ var/center_x = round(loaded.bounds[MAP_MAXX] / 2) // Technically off by 0.5 due to above +1. Whatever
+ var/center_y = round(loaded.bounds[MAP_MAXY] / 2)
+
// Now notify the staff of the load - this goes in addition to the generic template load game log
message_admins("Successfully loaded template as new Z-Level by ckey: [logckey], template name: [template.name]", center_x, center_y, loaded.z_value)
if(isobserver(C?.mob))
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 221736b5c84f..113d585e44ef 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(bgstate_options, list(
var/auto_observe = TRUE
/datum/preferences/New(client/C)
- key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys
+ key_bindings = deep_copy_list(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys
macros = new(C, src)
if(istype(C))
owner = C
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 2261ddf5ebfa..8f3d10c102ce 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -100,7 +100,7 @@
/proc/sanitize_keybindings(value)
var/list/base_bindings = sanitize_islist(value, list())
if(!length(base_bindings))
- base_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key)
+ base_bindings = deep_copy_list(GLOB.hotkey_keybinding_list_by_key)
for(var/key in base_bindings)
base_bindings[key] = base_bindings[key] & GLOB.keybindings_by_name
if(!length(base_bindings[key]))
diff --git a/code/modules/client/tgui_macro.dm b/code/modules/client/tgui_macro.dm
index 5159a8b7414c..cd621cebab84 100644
--- a/code/modules/client/tgui_macro.dm
+++ b/code/modules/client/tgui_macro.dm
@@ -126,7 +126,7 @@ GLOBAL_LIST_EMPTY(ui_data_keybindings)
if(choice == "Cancel")
return TRUE
prefs.hotkeys = (choice == "Hotkey")
- prefs.key_bindings = (prefs.hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
+ prefs.key_bindings = (prefs.hotkeys) ? deep_copy_list(GLOB.hotkey_keybinding_list_by_key) : deep_copy_list(GLOB.classic_keybinding_list_by_key)
INVOKE_ASYNC(owner, /client/proc/set_macros)
prefs.save_preferences()
return TRUE
diff --git a/code/modules/lighting/lighting_static/static_lighting_turf.dm b/code/modules/lighting/lighting_static/static_lighting_turf.dm
index 2fe918fa88bb..ec91e17e4fca 100644
--- a/code/modules/lighting/lighting_static/static_lighting_turf.dm
+++ b/code/modules/lighting/lighting_static/static_lighting_turf.dm
@@ -31,7 +31,8 @@
return !(luminosity || dynamic_lumcount)
-/turf/proc/change_area(area/old_area, area/new_area)
+///Transfer the lighting of one area to another
+/turf/proc/transfer_area_lighting(area/old_area, area/new_area)
if(SSlighting.initialized)
if (new_area.static_lighting != old_area.static_lighting)
if (new_area.static_lighting)
@@ -44,6 +45,8 @@
if(new_area.lighting_effect)
overlays += new_area.lighting_effect
+
+
/turf/proc/static_generate_missing_corners()
if (!lighting_corner_NE)
lighting_corner_NE = new/datum/static_lighting_corner(src, NORTH|EAST)
diff --git a/code/modules/logging/global_logs.dm b/code/modules/logging/global_logs.dm
index e0055907d67a..3fbcb70a1a8d 100644
--- a/code/modules/logging/global_logs.dm
+++ b/code/modules/logging/global_logs.dm
@@ -28,6 +28,9 @@ GLOBAL_PROTECT(scheduler_stats)
GLOBAL_VAR(strain_logs)
GLOBAL_PROTECT(strain_logs)
+GLOBAL_VAR(mapping_log)
+GLOBAL_PROTECT(mapping_log)
+
GLOBAL_VAR(round_stats)
GLOBAL_PROTECT(round_stats)
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 35f7d30a9732..50a343635de9 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -7,6 +7,20 @@
var/datum/parsed_map/cached_map
var/keep_cached_map = FALSE
+ ///Default area associated with the map template
+ var/default_area
+
+ ///if true, turfs loaded from this template are placed on top of the turfs already there, defaults to TRUE
+ var/should_place_on_top = TRUE
+
+ ///if true, creates a list of all atoms created by this template loading, defaults to FALSE
+ var/returns_created_atoms = FALSE
+
+ ///the list of atoms created by this template being loaded, only populated if returns_created_atoms is TRUE
+ var/list/created_atoms = list()
+ //make sure this list is accounted for/cleared if you request it from ssatoms!
+
+
/datum/map_template/New(path = null, rename = null, cache = FALSE)
if(path)
mappath = path
@@ -25,71 +39,129 @@
cached_map = parsed
return bounds
-/datum/parsed_map/proc/initTemplateBounds()
- var/list/obj/structure/cable/cables = list()
- var/list/atom/atoms = list()
- var/list/area/areas = list()
+/datum/map_template/proc/initTemplateBounds(list/bounds)
+ if (!bounds) //something went wrong
+ stack_trace("[name] template failed to initialize correctly!")
+ return
- var/list/turfs = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
- locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
- for(var/L in turfs)
- var/turf/B = L
- atoms += B
- areas |= B.loc
- for(var/A in B)
- atoms += A
- if(istype(A, /obj/structure/cable))
- cables += A
- continue
+ var/list/atom/movable/movables = list()
+ var/list/obj/docking_port/stationary/ports = list()
+ var/list/area/areas = list()
+ var/list/turfs = block(
+ locate(
+ bounds[MAP_MINX],
+ bounds[MAP_MINY],
+ bounds[MAP_MINZ]
+ ),
+ locate(
+ bounds[MAP_MAXX],
+ bounds[MAP_MAXY],
+ bounds[MAP_MAXZ]
+ )
+ )
+ for(var/turf/current_turf as anything in turfs)
+ var/area/current_turfs_area = current_turf.loc
+ areas |= current_turfs_area
+ if(!SSatoms.initialized)
+ continue
+
+ for(var/movable_in_turf in current_turf)
+ if(istype(movable_in_turf, /obj/docking_port/mobile))
+ continue // mobile docking ports need to be initialized after their template has finished loading, to ensure that their bounds are setup
+ movables += movable_in_turf
+ if(istype(movable_in_turf, /obj/docking_port/stationary))
+ ports += movable_in_turf
+
+ // Not sure if there is some importance here to make sure the area is in z
+ // first or not. Its defined In Initialize yet its run first in templates
+ // BEFORE so... hummm
SSmapping.reg_in_areas_in_z(areas)
- SSatoms.InitializeAtoms(atoms)
- //SSmachines.setup_template_powernets(cables) // mapping TODO:
-
-/datum/map_template/proc/load_new_z()
- var/x = round((world.maxx - width)/2)
- var/y = round((world.maxy - height)/2)
+ if(!SSatoms.initialized)
+ return
- var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(ZTRAIT_AWAY = TRUE))
- var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)
+ SSatoms.InitializeAtoms(areas + turfs + movables, returns_created_atoms ? created_atoms : null)
+
+ for(var/turf/unlit as anything in turfs)
+ var/area/loc_area = unlit.loc
+ if(!loc_area.static_lighting)
+ continue
+ unlit.static_lighting_build_overlay()
+
+/datum/map_template/proc/load_new_z(secret = FALSE)
+ var/x = round((world.maxx - width) * 0.5) + 1
+ var/y = round((world.maxy - height) * 0.5) + 1
+
+ var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(), contain_turfs = FALSE)
+ var/datum/parsed_map/parsed = load_map(
+ file(mappath),
+ x,
+ y,
+ level.z_value,
+ no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS),
+ place_on_top = should_place_on_top,
+ new_z = TRUE,
+ )
var/list/bounds = parsed.bounds
if(!bounds)
return FALSE
repopulate_sorted_areas()
-
//initialize things that are normally initialized after map load
- parsed.initTemplateBounds()
- log_game("Z-level [name] loaded at at [x],[y],[world.maxz]")
+ initTemplateBounds(bounds)
+ log_game("Z-level [name] loaded at [x],[y],[world.maxz]")
return level
-/datum/map_template/proc/load(turf/T, centered, delete)
+/datum/map_template/proc/load(turf/T, centered = FALSE, delete = FALSE)
if(centered)
T = locate(T.x - round(width/2) , T.y - round(height/2) , T.z)
if(!T)
return
- if(T.x + width > world.maxx)
+ if((T.x+width) - 1 > world.maxx)
return
- if(T.y + height > world.maxy)
+ if((T.y+height) - 1 > world.maxy)
return
// Accept cached maps, but don't save them automatically - we don't want
// ruins clogging up memory for the whole round.
var/datum/parsed_map/parsed = cached_map || new(file(mappath))
cached_map = keep_cached_map ? parsed : null
- if(!parsed.load(T.x, T.y, T.z, cropMap = TRUE, no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop = TRUE, delete = delete))
+
+ var/list/turf_blacklist = list()
+ update_blacklist(T, turf_blacklist)
+
+ UNSETEMPTY(turf_blacklist)
+ parsed.turf_blacklist = turf_blacklist
+ if(!parsed.load(
+ T.x,
+ T.y,
+ T.z,
+ crop_map = TRUE,
+ no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS),
+ place_on_top = should_place_on_top,
+ delete = delete
+ ))
return
+
var/list/bounds = parsed.bounds
if(!bounds)
return
+ repopulate_sorted_areas()
+
//initialize things that are normally initialized after map load
- parsed.initTemplateBounds()
+ initTemplateBounds(bounds)
- log_game("[name] loaded at at [T.x],[T.y],[T.z]")
+ log_game("[name] loaded at [T.x],[T.y],[T.z]")
return bounds
+/datum/map_template/proc/post_load()
+ return
+
+/datum/map_template/proc/update_blacklist(turf/T, list/input_blacklist)
+ return
+
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE)
var/turf/placement = T
if(centered)
diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm
index e8eee898a711..bd89d0e030a2 100644
--- a/code/modules/mapping/preloader.dm
+++ b/code/modules/mapping/preloader.dm
@@ -1,33 +1,42 @@
// global datum that will preload variables on atoms instanciation
GLOBAL_VAR_INIT(use_preloader, FALSE)
-GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
+GLOBAL_LIST_INIT(_preloader_attributes, null)
+GLOBAL_LIST_INIT(_preloader_path, null)
/// Preloader datum
/datum/map_preloader
- parent_type = /datum
var/list/attributes
var/target_path
-/datum/map_preloader/proc/setup(list/the_attributes, path)
+/world/proc/preloader_setup(list/the_attributes, path)
if(the_attributes.len)
GLOB.use_preloader = TRUE
- attributes = the_attributes
- target_path = path
+ GLOB._preloader_attributes = the_attributes
+ GLOB._preloader_path = path
-/datum/map_preloader/proc/load(atom/what)
+/world/proc/preloader_load(atom/what)
GLOB.use_preloader = FALSE
+ var/list/attributes = GLOB._preloader_attributes
for(var/attribute in attributes)
var/value = attributes[attribute]
if(islist(value))
- value = deepCopyList(value)
+ value = deep_copy_list(value)
+ #ifdef TESTING
+ if(what.vars[attribute] == value)
+ var/message = "[what.type] at [AREACOORD(what)] - VAR: [attribute] = [isnull(value) ? "null" : (isnum(value) ? value : "\"[value]\"")]"
+ log_mapping("DIRTY VAR: [message]")
+ GLOB.dirty_vars += message
+ #endif
what.vars[attribute] = value
-/// Area passthrough: do not instanciate a new area, reuse the current one
+/// Template noop (no operation) is used to skip a turf or area when the template is loaded this allows for template transparency
+/// ex. if a ship has gaps in it's design, you would use template_noop to fill these in so that when the ship moves z-level, any
+/// tiles these gaps land on will not be deleted and replaced with the ships (empty) tiles
/area/template_noop
name = "Area Passthrough"
icon_state = "noop"
-/// Turf passthrough: do not instanciate a new turf, reuse the current one
+/// See above explanation
/turf/template_noop
name = "Turf Passthrough"
icon_state = "noop"
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index 969dc6a795f8..424ab22ae4ac 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -1,7 +1,67 @@
///////////////////////////////////////////////////////////////
//SS13 Optimized Map loader
//////////////////////////////////////////////////////////////
-#define SPACE_KEY "space"
+// We support two different map formats
+// It is kinda possible to process them together, but if we split them up
+// I can make optimization decisions more easily
+/**
+ * DMM SPEC:
+ * DMM is split into two parts. First we have strings of text linked to lists of paths and their modifications (I will call this the cache)
+ * We call these strings "keys" and the things they point to members. Keys have a static length
+ *
+ * The second part is a list of locations matched to a string of keys. (I'll be calling this the grid)
+ * These are used to lookup the cache we built earlier.
+ * We store location lists as grid_sets. the lines represent different things depending on the spec
+ *
+ * In standard DMM (which you can treat as the base case, since it also covers weird modifications) each line
+ * represents an x file, and there's typically only one grid set per z level.
+ * The meme is you can look at a DMM formatted map and literally see what it should roughly look like
+ * This differs in TGM, and we can pull some performance from this
+ *
+ * Any restrictions here also apply to TGM
+ *
+ * /tg/ Restrictions:
+ * Paths have a specified order. First atoms in the order in which they should be loaded, then a single turf, then the area of the cell
+ * DMM technically supports turf stacking, but this is deprecated for all formats
+
+ */
+#define MAP_DMM "dmm"
+/**
+ * TGM SPEC:
+ * TGM is a derevation of DMM, with restrictions placed on it
+ * to make it easier to parse and to reduce merge conflicts/ease their resolution
+ *
+ * Requirements:
+ * Each "statement" in a key's details ends with a new line, and wrapped in (...)
+ * All paths end with either a comma or occasionally a {, then a new line
+ * Excepting the area, who is listed last and ends with a ) to mark the end of the key
+ *
+ * {} denotes a list of variable edits applied to the path that came before the first {
+ * the final } is followed by a comma, and then a new line
+ * Variable edits have the form \tname = value;\n
+ * Except the last edit, which has no final ;, and just ends in a newline
+ * No extra padding is permitted
+ * Many values are supported. See parse_constant()
+ * Strings must be wrapped in "...", files in '...', and lists in list(...)
+ * Files are kinda susy, and may not actually work. buyer beware
+ * Lists support assoc values as expected
+ * These constants can be further embedded into lists
+ *
+ * There can be no padding in front of, or behind a path
+ *
+ * Therefore:
+ * "key" = (
+ * /path,
+ * /other/path{
+ * var = list("name" = 'filepath');
+ * other_var = /path
+ * },
+ * /turf,
+ * /area)
+ *
+ */
+#define MAP_TGM "tgm"
+#define MAP_UNKNOWN "unknown"
/datum/grid_set
var/xcrd
@@ -11,9 +71,20 @@
/datum/parsed_map
var/original_path
+ var/map_format
+ /// The length of a key in this file. This is promised by the standard to be static
var/key_len = 0
+ /// The length of a line in this file. Not promised by dmm but standard dmm uses it, so we can trust it
+ var/line_len = 0
+ /// If we've expanded world.maxx
+ var/expanded_y = FALSE
+ /// If we've expanded world.maxy
+ var/expanded_x = FALSE
var/list/grid_models = list()
var/list/gridSets = list()
+ /// List of area types we've loaded AS A PART OF THIS MAP
+ /// We do this to allow non unique areas, so we'll only load one per map
+ var/list/area/loaded_areas = list()
var/list/modelCache
@@ -22,33 +93,97 @@
/// Offset bounds. Same as parsed_bounds until load().
var/list/bounds
+ ///any turf in this list is skipped inside of build_coordinate. Lazy assoc list
+ var/list/turf_blacklist
+
// raw strings used to represent regexes more accurately
// '' used to avoid confusing syntax highlighting
- var/static/regex/dmmRegex = new(@'"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g")
- var/static/regex/trimQuotesRegex = new(@'^[\s\n]+"?|"?[\s\n]+$|^"|"$', "g")
- var/static/regex/trimRegex = new(@'^[\s\n]+|[\s\n]+$', "g")
+ var/static/regex/dmm_regex = new(@'"([a-zA-Z]+)" = (?:\(\n|\()((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g")
+ /// Matches key formats in TMG (IE: newline after the \()
+ var/static/regex/matches_tgm = new(@'^"[A-z]*"[\s]*=[\s]*\([\s]*\n', "m")
+ /// Pulls out key value pairs for TGM
+ var/static/regex/var_edits_tgm = new(@'^\t([A-z]*) = (.*?);?$')
+ /// Pulls out model paths for DMM
+ var/static/regex/model_path = new(@'(\/[^\{]*?(?:\{.*?\})?)(?:,|$)', "g")
+
+ /// If we are currently loading this map
+ var/loading = FALSE
#ifdef TESTING
var/turfsSkipped = 0
#endif
-/// Shortcut function to parse a map and apply it to the world.
-///
-/// - `dmm_file`: A .dmm file to load (Required).
-/// - `x_offset`, `y_offset`, `z_offset`: Positions representign where to load the map (Optional).
-/// - `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).
-/// - `placeOnTop`: Whether to use [turf/PlaceOnTop] rather than [turf/ChangeTurf] (Optional).
-/proc/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, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num)
- var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, 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)
- return parsed
+/datum/parsed_map/proc/copy()
+ // Avoids duped work just in case
+ build_cache()
+ var/datum/parsed_map/newfriend = new()
+ newfriend.original_path = original_path
+ newfriend.map_format = map_format
+ newfriend.key_len = key_len
+ newfriend.line_len = line_len
+ newfriend.grid_models = grid_models.Copy()
+ newfriend.gridSets = gridSets.Copy()
+ newfriend.modelCache = modelCache.Copy()
+ newfriend.parsed_bounds = parsed_bounds.Copy()
+ // Copy parsed bounds to reset to initial values
+ newfriend.bounds = parsed_bounds.Copy()
+ newfriend.turf_blacklist = turf_blacklist?.Copy()
+ return newfriend
+
+//text trimming (both directions) helper macro
+#define TRIM_TEXT(text) (trim_reduced(text))
+
+/**
+ * Helper and recommened way to load a map file
+ * - dmm_file: The path to the map file
+ * - x_offset: The x offset to load the map at
+ * - y_offset: The y offset to load the map at
+ * - z_offset: The z offset to load the map at
+ * - crop_map: If true, the map will be cropped to the world bounds
+ * - measure_only: If true, the map will not be loaded, but the bounds will be calculated
+ * - no_changeturf: If true, the map will not call /turf/AfterChange
+ * - x_lower: The minimum x coordinate to load
+ * - x_upper: The maximum x coordinate to load
+ * - y_lower: The minimum y coordinate to load
+ * - y_upper: The maximum y coordinate to load
+ * - z_lower: The minimum z coordinate to load
+ * - z_upper: The maximum z coordinate to load
+ * - place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf
+ * - new_z: If true, a new z level will be created for the map
+ * - delete: CM/TGMC addition, if we need to manually clear turf contents before spawning stuff
+ */
+/proc/load_map(
+ dmm_file,
+ x_offset = 0,
+ y_offset = 0,
+ z_offset = 0,
+ crop_map = FALSE,
+ measure_only = FALSE,
+ no_changeturf = FALSE,
+ x_lower = -INFINITY,
+ x_upper = INFINITY,
+ y_lower = -INFINITY,
+ y_upper = INFINITY,
+ z_lower = -INFINITY,
+ z_upper = INFINITY,
+ place_on_top = FALSE,
+ new_z = FALSE,
+ delete = FALSE,
+)
+ if(!(dmm_file in GLOB.cached_maps))
+ GLOB.cached_maps[dmm_file] = new /datum/parsed_map(dmm_file)
+
+ var/datum/parsed_map/parsed_map = GLOB.cached_maps[dmm_file]
+ parsed_map = parsed_map.copy()
+ if(!measure_only && !isnull(parsed_map.bounds))
+ parsed_map.load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
+ return parsed_map
/// Parse a map, possibly cropping it.
-/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE)
+/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)
+ // This proc sleeps for like 6 seconds. why?
+ // Is it file accesses? if so, can those be done ahead of time, async to save on time here? I wonder.
+ // Love ya :)
if(isfile(tfile))
original_path = "[tfile]"
tfile = file2text(tfile)
@@ -56,16 +191,30 @@
// 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)
- var/stored_index = 1
+ src.bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
+
+ if(findtext(tfile, matches_tgm))
+ map_format = MAP_TGM
+ else
+ map_format = MAP_DMM // Fallback
+
+ // lists are structs don't you know :)
+ var/list/bounds = src.bounds
+ var/list/grid_models = src.grid_models
+ var/key_len = src.key_len
+ var/line_len = src.line_len
+ var/stored_index = 1
+ var/list/regexOutput
//multiz lool
- while(dmmRegex.Find(tfile, stored_index))
- stored_index = dmmRegex.next
+ while(dmm_regex.Find(tfile, stored_index))
+ stored_index = dmm_regex.next
+ // Datum var lookup is expensive, this isn't
+ regexOutput = dmm_regex.group
// "aa" = (/type{vars=blah})
- if(dmmRegex.group[1]) // Model
- var/key = dmmRegex.group[1]
+ if(regexOutput[1]) // Model
+ var/key = regexOutput[1]
if(grid_models[key]) // Duplicate model keys are ignored in DMMs
continue
if(key_len != length(key))
@@ -74,328 +223,776 @@
else
CRASH("Inconsistent key length in DMM")
if(!measureOnly)
- grid_models[key] = dmmRegex.group[2]
+ grid_models[key] = regexOutput[2]
// (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
- else if(dmmRegex.group[3]) // Coords
+ else if(regexOutput[3]) // Coords
if(!key_len)
CRASH("Coords before model definition in DMM")
- var/curr_x = text2num(dmmRegex.group[3])
-
+ var/curr_x = text2num(regexOutput[3])
if(curr_x < x_lower || curr_x > x_upper)
continue
+ var/curr_y = text2num(regexOutput[4])
+ if(curr_y < y_lower || curr_y > y_upper)
+ continue
+
+ var/curr_z = text2num(regexOutput[5])
+ if(curr_z < z_lower || curr_z > z_upper)
+ continue
+
var/datum/grid_set/gridSet = new
gridSet.xcrd = curr_x
- //position of the currently processed square
- gridSet.ycrd = text2num(dmmRegex.group[4])
- gridSet.zcrd = text2num(dmmRegex.group[5])
+ gridSet.ycrd = curr_y
+ gridSet.zcrd = curr_z
- bounds[MAP_MINX] = min(bounds[MAP_MINX], clamp(gridSet.xcrd, x_lower, x_upper))
- bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd)
- bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd)
+ bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x)
+ bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_y)
+ bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z)
- var/list/gridLines = splittext(dmmRegex.group[6], "\n")
+ var/list/gridLines = splittext(regexOutput[6], "\n")
gridSet.gridLines = gridLines
var/leadingBlanks = 0
- while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "")
+ while(leadingBlanks < length(gridLines) && gridLines[++leadingBlanks] == "")
if(leadingBlanks > 1)
gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines.
- if(!gridLines.len) // Skip it if only blank lines exist.
+ if(!length(gridLines)) // 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.
+ if(gridLines[length(gridLines)] == "")
+ gridLines.Cut(length(gridLines)) // Remove only one blank line at the end.
+
+ bounds[MAP_MINY] = min(bounds[MAP_MINY], gridSet.ycrd)
+ gridSet.ycrd += length(gridLines) - 1 // Start at the top and work down
+ bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd)
- 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
- bounds[MAP_MAXY] = max(bounds[MAP_MAXY], clamp(gridSet.ycrd, y_lower, y_upper))
+ if(!line_len)
+ line_len = length(gridLines[1])
- var/maxx = gridSet.xcrd
- if(gridLines.len) //Not an empty map
- maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1)
+ var/maxx = curr_x
+ if(length(gridLines)) //Not an empty map
+ maxx = max(maxx, curr_x + line_len / key_len - 1)
- bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper)
+ bounds[MAP_MAXX] = max(bounds[MAP_MAXX], maxx)
CHECK_TICK
// Indicate failure to parse any coordinates by nulling bounds
if(bounds[1] == 1.#INF)
- bounds = null
- parsed_bounds = bounds
+ src.bounds = null
+ else
+ // Clamp all our mins and maxes down to the proscribed limits
+ bounds[MAP_MINX] = clamp(bounds[MAP_MINX], x_lower, x_upper)
+ bounds[MAP_MAXX] = clamp(bounds[MAP_MAXX], x_lower, x_upper)
+ bounds[MAP_MINY] = clamp(bounds[MAP_MINY], y_lower, y_upper)
+ bounds[MAP_MAXY] = clamp(bounds[MAP_MAXY], y_lower, y_upper)
+ bounds[MAP_MINZ] = clamp(bounds[MAP_MINZ], z_lower, z_upper)
+ bounds[MAP_MAXZ] = clamp(bounds[MAP_MAXZ], z_lower, z_upper)
+
+ parsed_bounds = src.bounds
+ src.key_len = key_len
+ src.line_len = line_len
+
+/// Iterates over all grid sets and returns ones with z values within the given bounds. Inclusive
+/datum/parsed_map/proc/filter_grid_sets_based_on_z_bounds(lower_z, upper_z)
+ var/list/filtered_sets = list()
+ for(var/datum/grid_set/grid_set as anything in gridSets)
+ if(grid_set.zcrd < lower_z)
+ continue
+ if(grid_set.zcrd > upper_z)
+ continue
+ filtered_sets += grid_set
+ return filtered_sets
-/// 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, delete)
+/// Load the parsed map into the world. You probably want [/proc/load_map]. Keep the signature the same.
+/datum/parsed_map/proc/load(x_offset = 0, y_offset = 0, z_offset = 0, crop_map = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, place_on_top = FALSE, new_z = FALSE, delete = FALSE)
//How I wish for RAII
Master.StartLoadingMap()
- . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, delete)
+ . = _load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
Master.StopLoadingMap()
+#define MAPLOADING_CHECK_TICK \
+ if(TICK_CHECK) { \
+ if(loading) { \
+ SSatoms.map_loader_stop(REF(src)); \
+ stoplag(); \
+ SSatoms.map_loader_begin(REF(src)); \
+ } else { \
+ stoplag(); \
+ } \
+ }
+
// Do not call except via load() above.
-/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, delete = FALSE)
- var/list/areaCache = list()
+/datum/parsed_map/proc/_load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
+ PRIVATE_PROC(TRUE)
+ // Tell ss atoms that we're doing maploading
+ // We'll have to account for this in the following tick_checks so it doesn't overflow
+ loading = TRUE
+ SSatoms.map_loader_begin(REF(src))
+
+ // Loading used to be done in this proc
+ // We make the assumption that if the inner procs runtime, we WANT to do cleanup on them, but we should stil tell our parents we failed
+ // Since well, we did
+ var/sucessful = FALSE
+ switch(map_format)
+ if(MAP_TGM)
+ sucessful = _tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
+ else
+ sucessful = _dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
+
+ // And we are done lads, call it off
+ SSatoms.map_loader_stop(REF(src))
+ loading = FALSE
+
+ // CM: Disabled due to not using contained_turfs and SSarea_contents
+// if(new_z)
+// for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ])
+// SSmapping.build_area_turfs(z_index)
+
+ if(!no_changeturf)
+ var/list/turfs = block(
+ locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
+ locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
+ for(var/turf/T as anything in turfs)
+ //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)
+
+ if(expanded_x || expanded_y)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, expanded_x, expanded_y)
+
+ #ifdef TESTING
+ if(turfsSkipped)
+ testing("Skipped loading [turfsSkipped] default turfs")
+ #endif
+
+ return sucessful
+
+// Wanna clear something up about maps, talking in 255x255 here
+// In the tgm format, each gridset contains 255 lines, each line representing one tile, with 255 total gridsets
+// In the dmm format, each gridset contains 255 lines, each line representing one row of tiles, containing 255 * line length characters, with one gridset per z
+// You can think of dmm as storing maps in rows, whereas tgm stores them in columns
+/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
+ // setup
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
var/list/bounds
src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
- for(var/I in gridSets)
- var/datum/grid_set/gset = I
- var/ycrd = gset.ycrd + y_offset - 1
- var/zcrd = gset.zcrd + z_offset - 1
- if(!cropMap && ycrd > world.maxy)
- world.maxy = ycrd // Expand Y here. X is expanded in the loop below
+ // Building y coordinate ranges
+ var/y_relative_to_absolute = y_offset - 1
+ var/x_relative_to_absolute = x_offset - 1
+
+ // Ok so like. something important
+ // We talk in "relative" coords here, so the coordinate system of the map datum
+ // This is so we can do offsets, but it is NOT the same as positions in game
+ // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones
+ // TGM maps process in columns, so the starting y will always be the max size
+ // We know y starts at 1
+ var/datum/grid_set/first_column = gridSets[1]
+ var/relative_y = first_column.ycrd
+ var/highest_y = relative_y + y_relative_to_absolute
+
+ if(!crop_map && highest_y > world.maxy)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_y(highest_y, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_y(highest_y)
+ expanded_y = TRUE
+
+ // Skip Y coords that are above the smallest of the three params
+ // So maxy and y_upper get to act as thresholds, and relative_y can play
+ var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y)
+ // How many lines to skip because they'd be above the y cuttoff line
+ var/y_starting_skip = relative_y - y_skip_above
+ highest_y -= y_starting_skip
+
+ // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go
+ var/line_count = length(first_column.gridLines)
+ var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start
+ var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0)
+
+ // X setup
+ var/x_delta_with = x_upper
+ if(crop_map)
+ // Take our smaller crop threshold yes?
+ x_delta_with = min(x_delta_with, world.maxx)
+
+ // We're gonna skip all the entries above the upper x, or maxx if cropMap is set
+ // The last column is guarenteed to have the highest x value we;ll encounter
+ // Even if z scales, this still works
+ var/datum/grid_set/last_column = gridSets[length(gridSets)]
+ var/final_x = last_column.xcrd + x_relative_to_absolute
+
+ if(final_x > x_delta_with)
+ // If our relative x is greater then X upper, well then we've gotta limit our expansion
+ var/delta = max(final_x - x_delta_with, 0)
+ final_x -= delta
+ if(final_x > world.maxx && !crop_map)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_x(final_x)
+ expanded_x = TRUE
+
+ var/lowest_x = max(x_lower, 1 - x_relative_to_absolute)
+
+ // Amount we offset the grid zcrd to get the true zcrd
+ var/grid_z_offset = z_offset - 1
+ var/z_upper_set = z_upper < INFINITY
+ var/z_lower_set = z_lower > -INFINITY
+
+ // We make the assumption that the last block of turfs will have the highest embedded z in it
+ // true max zcrd
+ var/map_bounds_z_max = last_column.zcrd
+ var/z_upper_parsed = map_bounds_z_max + z_offset - 1
+ if(z_upper_set)
+ z_upper_parsed -= map_bounds_z_max - z_upper
+ if(z_lower_set)
+ var/offset_amount = z_lower - 1
+ z_upper_parsed -= offset_amount
+ grid_z_offset -= offset_amount
+
+ var/list/target_grid_sets = gridSets
+ if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want
+ target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper)
+
+ var/z_threshold = world.maxz
+ if(z_upper_parsed > z_threshold && crop_map)
+ for(var/i in z_threshold + 1 to z_upper_parsed) //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")
+
+ for(var/datum/grid_set/gset as anything in target_grid_sets)
+ var/true_xcrd = gset.xcrd + x_relative_to_absolute
+
+ // any cutoff of x means we just shouldn't iterate this gridset
+ if(final_x < true_xcrd || lowest_x > gset.xcrd)
+ continue
+
+ var/zcrd = gset.zcrd + grid_z_offset
+ // If we're using changeturf, we disable it if we load into a z level we JUST created
+ var/no_afterchange = no_changeturf || zcrd > z_threshold
+
+ // We're gonna track the first and last pairs of coords we find
+ // Since x is always incremented in steps of 1, we only need to deal in y
+ // The first x is guarenteed to be the lowest, the first y the highest, and vis versa
+ // This is faster then doing mins and maxes inside the hot loop below
+ var/first_found = FALSE
+ var/first_y = 0
+ var/last_y = 0
+
+ var/ycrd = highest_y
+ // Everything following this line is VERY hot.
+ for(var/i in 1 + y_starting_skip to line_count - y_ending_skip)
+ if(gset.gridLines[i] == space_key && no_afterchange)
+ #ifdef TESTING
+ ++turfsSkipped
+ #endif
+ ycrd--
+ MAPLOADING_CHECK_TICK
+ continue
+
+ var/list/cache = modelCache[gset.gridLines[i]]
+ if(!cache)
+ SSatoms.map_loader_stop(REF(src))
+ CRASH("Undefined model key in DMM: [gset.gridLines[i]]")
+ build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z, delete)
+
+ // only bother with bounds that actually exist
+ if(!first_found)
+ first_found = TRUE
+ first_y = ycrd
+ last_y = ycrd
+ ycrd--
+ MAPLOADING_CHECK_TICK
+
+ // The x coord never changes, so not tracking first x is safe
+ // If no ycrd is found, we assume this row is totally empty and just continue on
+ if(first_found)
+ bounds[MAP_MINX] = min(bounds[MAP_MINX], true_xcrd)
+ bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y)
+ bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
+ bounds[MAP_MAXX] = max(bounds[MAP_MAXX], true_xcrd)
+ bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y)
+ bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
+ return TRUE
+
+/// Stanrdard loading, not used in production
+/// Doesn't take advantage of any tgm optimizations, which makes it slower but also more general
+/// Use this if for some reason your map format is messy
+/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete)
+ // setup
+ var/list/modelCache = build_cache(no_changeturf)
+ var/space_key = modelCache[SPACE_KEY]
+ var/list/bounds
+ var/key_len = src.key_len
+ src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
+
+ var/y_relative_to_absolute = y_offset - 1
+ var/x_relative_to_absolute = x_offset - 1
+ var/line_len = src.line_len
+
+ // Amount we offset the grid zcrd to get the true zcrd
+ var/grid_z_offset = z_offset - 1
+ var/z_upper_set = z_upper < INFINITY
+ var/z_lower_set = z_lower > -INFINITY
+
+ // we now need to find the maximum z, fun!
+ var/map_bounds_z_max = 1
+ for(var/datum/grid_set/grid_set as anything in gridSets)
+ map_bounds_z_max = max(map_bounds_z_max, grid_set.zcrd)
+
+ var/z_upper_parsed = map_bounds_z_max + z_offset - 1
+ if(z_upper_set)
+ z_upper_parsed -= map_bounds_z_max - z_upper
+ if(z_lower_set)
+ var/offset_amount = z_lower - 1
+ z_upper_parsed -= offset_amount
+ grid_z_offset -= offset_amount
+
+ var/list/target_grid_sets = gridSets
+ if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want
+ target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper)
+
+ for(var/datum/grid_set/gset as anything in target_grid_sets)
+ var/relative_x = gset.xcrd
+ var/relative_y = gset.ycrd
+ var/true_xcrd = relative_x + x_relative_to_absolute
+ var/ycrd = relative_y + y_relative_to_absolute
+ var/zcrd = gset.zcrd + grid_z_offset
+ if(!crop_map && ycrd > world.maxy)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_y(ycrd, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_y(ycrd)
+ expanded_y = TRUE
var/zexpansion = zcrd > world.maxz
+ var/no_afterchange = no_changeturf
if(zexpansion)
- if(cropMap)
+ if(crop_map)
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")
-
- for(var/line in gset.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)
- var/xcrd = gset.xcrd + x_offset - 1
- 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))
- var/list/cache = modelCache[model_key]
- if(!cache)
- CRASH("Undefined model key in DMM: [model_key]")
- build_coordinate(areaCache, cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop, delete)
-
- // only bother with bounds that actually exist
- bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrd)
- bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd)
- bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
- bounds[MAP_MAXX] = max(bounds[MAP_MAXX], xcrd)
- bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd)
- bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
- #ifdef TESTING
- else
- ++turfsSkipped
- #endif
- CHECK_TICK
+ no_afterchange = TRUE
+ // Ok so like. something important
+ // We talk in "relative" coords here, so the coordinate system of the map datum
+ // This is so we can do offsets, but it is NOT the same as positions in game
+ // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones
+
+ // Skip Y coords that are above the smallest of the three params
+ // So maxy and y_upper get to act as thresholds, and relative_y can play
+ var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y)
+ // How many lines to skip because they'd be above the y cuttoff line
+ var/y_starting_skip = relative_y - y_skip_above
+ ycrd += y_starting_skip
+
+ // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go
+ var/line_count = length(gset.gridLines)
+ var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start
+ var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0)
+
+ // Now we're gonna precompute the x thresholds
+ // We skip all the entries below the lower x, or 1
+ var/starting_x_delta = max(max(x_lower, 1 - x_relative_to_absolute) - relative_x, 0)
+ // The x loop counts by key length, so we gotta multiply here
+ var/x_starting_skip = starting_x_delta * key_len
+ true_xcrd += starting_x_delta
+
+ // We're gonna skip all the entries above the upper x, or maxx if cropMap is set
+ var/x_target = line_len - key_len + 1
+ var/x_step_count = ROUND_UP(x_target / key_len)
+ var/final_x = relative_x + (x_step_count - 1)
+ var/x_delta_with = x_upper
+ if(crop_map)
+ // Take our smaller crop threshold yes?
+ x_delta_with = min(x_delta_with, world.maxx)
+ if(final_x > x_delta_with)
+ // If our relative x is greater then X upper, well then we've gotta limit our expansion
+ var/delta = max(final_x - x_delta_with, 0)
+ x_step_count -= delta
+ final_x -= delta
+ x_target = x_step_count * key_len
+ if(final_x > world.maxx && !crop_map)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_x(final_x)
+ expanded_x = TRUE
+
+ // We're gonna track the first and last pairs of coords we find
+ // The first x is guarenteed to be the lowest, the first y the highest, and vis versa
+ // This is faster then doing mins and maxes inside the hot loop below
+ var/first_found = FALSE
+ var/first_x = 0
+ var/first_y = 0
+ var/last_x = 0
+ var/last_y = 0
+
+ // Everything following this line is VERY hot. How hot depends on the map format
+ // (Yes this does mean dmm is technically faster to parse. shut up)
+ for(var/i in 1 + y_starting_skip to line_count - y_ending_skip)
+ var/line = gset.gridLines[i]
+
+ var/xcrd = true_xcrd
+ for(var/tpos in 1 + x_starting_skip to x_target step key_len)
+ var/model_key = copytext(line, tpos, tpos + key_len)
+ if(model_key == space_key && no_afterchange)
+ #ifdef TESTING
+ ++turfsSkipped
+ #endif
+ MAPLOADING_CHECK_TICK
++xcrd
- --ycrd
-
- CHECK_TICK
+ continue
+ var/list/cache = modelCache[model_key]
+ if(!cache)
+ SSatoms.map_loader_stop(REF(src))
+ CRASH("Undefined model key in DMM: [model_key]")
+ build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z, delete)
+
+ // only bother with bounds that actually exist
+ if(!first_found)
+ first_found = TRUE
+ first_x = xcrd
+ first_y = ycrd
+ last_x = xcrd
+ last_y = ycrd
+ MAPLOADING_CHECK_TICK
+ ++xcrd
+ ycrd--
+ MAPLOADING_CHECK_TICK
+ bounds[MAP_MINX] = min(bounds[MAP_MINX], first_x)
+ bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y)
+ bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
+ bounds[MAP_MAXX] = max(bounds[MAP_MAXX], last_x)
+ bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y)
+ bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
- //if(!no_changeturf)// mapping TODO:
- // 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 TRUE
- #ifdef TESTING
- if(turfsSkipped)
- testing("Skipped loading [turfsSkipped] default turfs")
- #endif
+GLOBAL_LIST_EMPTY(map_model_default)
- return TRUE
+/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths)
+ if(map_format == MAP_TGM)
+ return tgm_build_cache(no_changeturf, bad_paths)
+ return dmm_build_cache(no_changeturf, bad_paths)
-/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths=null)
+/datum/parsed_map/proc/tgm_build_cache(no_changeturf, bad_paths=null)
if(modelCache && !bad_paths)
return modelCache
. = modelCache = list()
var/list/grid_models = src.grid_models
+ var/set_space = FALSE
+ // Use where a list is needed, but where it will not be modified
+ // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later
+ var/static/list/default_list = GLOB.map_model_default // It's stupid, but it saves += list(list)
+ var/static/list/wrapped_default_list = list(default_list) // It's stupid, but it saves += list(list)
+ var/static/regex/var_edits = var_edits_tgm
+
+ var/path_to_init = ""
+ // Reference to the attributes list we're currently filling, if any
+ var/list/current_attributes
+ // If we are currently editing a path or not
+ var/editing = FALSE
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())
+ // We're going to split models by newline
+ // This guarentees that each entry will be of interest to us
+ // Then we'll process them step by step
+ // Hopefully this reduces the cost from read_list that we'd otherwise have
+ var/list/lines = splittext(grid_models[model_key], "\n")
+ // Builds list of path/edits for later
+ // Of note: we cannot preallocate them to save time in list expansion later
+ // But fortunately lists allocate at least 8 entries normally anyway, and
+ // We are unlikely to have more then that many members
+ //will contain all members (paths) in model (in our example : /turf/unsimulated/wall)
+ var/list/members = list()
+ //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())
+ var/list/members_attributes = list()
/////////////////////////////////////////////////////////
//Constructing members and corresponding variables lists
////////////////////////////////////////////////////////
+ // string representation of the path to init
+ for(var/line in lines)
+ // We do this here to avoid needing to check at each return statement
+ // No harm in it anyway
+ MAPLOADING_CHECK_TICK
+
+ switch(line[length(line)])
+ if(";") // Var edit, we'll apply it
+ // Var edits look like \tname = value;
+ // I'm gonna try capturing them with regex, since it ought to be the fastest here
+ // Should hand back key = value
+ var_edits.Find(line)
+ var/value = parse_constant(var_edits.group[2])
+ if(istext(value))
+ value = apply_text_macros(value)
+ current_attributes[var_edits.group[1]] = value
+ continue // Keep on keeping on brother
+ if("{") // Start of an edit, and so also the start of a path
+ editing = TRUE
+ current_attributes = list() // Init the list we'll be filling
+ members_attributes += list(current_attributes)
+ path_to_init = copytext(line, 1, -1)
+ if(",") // Either the end of a path, or the end of an edit
+ if(editing) // it was the end of a path
+ editing = FALSE
+ continue
+ members_attributes += wrapped_default_list // We know this is a path, and we also know it has no vv's. so we'll just set this to the default list
+ // Drop the last char mind
+ path_to_init = copytext(line, 1, -1)
+ if("}") // Gotta be the end of an area edit, let's check to be sure
+ if(editing) // it was the end of an area edit (shouldn't do those anyhow)
+ editing = FALSE
+ continue
+ stack_trace("ended a line on JUST a }, with no ongoing edit. What? Area shit?")
+ else // If we're editing, this is a var edit entry. the last one in a stack, cause god hates me. Otherwise, it's an area
+ if(editing) // I want inline I want inline I want inline
+ // Var edits look like \tname = value;
+ // I'm gonna try capturing them with regex, since it ought to be the fastest here
+ // Should hand back key = value
+ var_edits.Find(line)
+ var/value = parse_constant(var_edits.group[2])
+ if(istext(value))
+ value = apply_text_macros(value)
+ current_attributes[var_edits.group[1]] = value
+ continue // Keep on keeping on brother
- var/index = 1
- var/old_position = 1
- var/dpos
+ members_attributes += wrapped_default_list // We know this is a path, and we also know it has no vv's. so we'll just set this to the default list
+ path_to_init = line
- while(dpos != 0)
- //finding next member (e.g /turf/unsimulated/wall{icon_state = "rock"} or /area/mine/explored)
- dpos = find_next_delimiter_position(model, old_position, ",", "{", "}") //find next delimiter (comma here) that's not within {...}
- var/full_def = trim_text(copytext(model, old_position, dpos)) //full definition, e.g : /obj/foo/bar{variables=derp}
- var/variables_start = findtext(full_def, "{")
- var/path_text = trim_text(copytext(full_def, 1, variables_start))
+ // Alright, if we've gotten to this point, our string is a path
+ // Oh and we don't trim it, because we require no padding for these
+ // Saves like 1.5 deciseconds
+ var/atom_def = text2path(path_to_init) //path definition, e.g /obj/foo/bar
+
+ if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers!
+ if(bad_paths)
+ // Rare case, avoid the var to save time most of the time
+ LAZYOR(bad_paths[copytext(line, 1, -1)], model_key)
+ continue
+ // Index is already incremented either way, just gotta set the path and all
+ members += atom_def
+
+ //check and see if we can just skip this turf
+ //So you don't have to understand this horrid statement, we can do this if
+ // 1. the space_key isn't set yet
+ // 2. no_changeturf is set
+ // 3. there are exactly 2 members
+ // 4. with no attributes
+ // 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(!set_space \
+ && no_changeturf \
+ && members_attributes.len == 2 \
+ && members.len == 2 \
+ && members_attributes[1] == default_list \
+ && members_attributes[2] == default_list \
+ && members[2] == world.area \
+ && members[1] == world.turf
+ )
+ set_space = TRUE
+ .[SPACE_KEY] = model_key
+ continue
+
+ .[model_key] = list(members, members_attributes)
+ return .
+
+/// Builds key caches for general formats
+/// Slower then the proc above, tho it could still be optimized slightly. it's just not a priority
+/// Since we don't run DMM maps, ever.
+/datum/parsed_map/proc/dmm_build_cache(no_changeturf, bad_paths=null)
+ if(modelCache && !bad_paths)
+ return modelCache
+ . = modelCache = list()
+ var/list/grid_models = src.grid_models
+ var/set_space = FALSE
+ // Use where a list is needed, but where it will not be modified
+ // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later
+ var/static/list/default_list = list(GLOB.map_model_default)
+ for(var/model_key in grid_models)
+ //will contain all members (paths) in model (in our example : /turf/unsimulated/wall)
+ var/list/members = list()
+ //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())
+ var/list/members_attributes = list()
+
+ var/model = grid_models[model_key]
+ /////////////////////////////////////////////////////////
+ //Constructing members and corresponding variables lists
+ ////////////////////////////////////////////////////////
+
+ var/model_index = 1
+ while(model_path.Find(model, model_index))
+ var/variables_start = 0
+ var/member_string = model_path.group[1]
+ model_index = model_path.next
+ //findtext is a bit expensive, lets only do this if the last char of our string is a } (IE: we know we have vars)
+ //this saves about 25 miliseconds on my machine. Not a major optimization
+ if(member_string[length(member_string)] == "}")
+ variables_start = findtext(member_string, "{")
+
+ var/path_text = TRIM_TEXT(copytext(member_string, 1, variables_start))
var/atom_def = text2path(path_text) //path definition, e.g /obj/foo/bar
- if(dpos)
- old_position = dpos + length(model[dpos])
if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers!
if(bad_paths)
LAZYOR(bad_paths[path_text], model_key)
continue
- members.Add(atom_def)
+ members += atom_def
//transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7))
- var/list/fields = list()
-
+ // OF NOTE: this could be made faster by replacing readlist with a progressive regex
+ // I'm just too much of a bum to do it rn, especially since we mandate tgm format for any maps in repo
+ var/list/fields = default_list
if(variables_start)//if there's any variable
- full_def = copytext(full_def, variables_start + length(full_def[variables_start]), -length(copytext_char(full_def, -1))) //removing the last '}'
- fields = readlist(full_def, ";")
- if(fields.len)
- if(!trim(fields[fields.len]))
- --fields.len
- for(var/I in fields)
- var/value = fields[I]
- if(istext(value))
- fields[I] = apply_text_macros(value)
+ member_string = copytext(member_string, variables_start + length(member_string[variables_start]), -length(copytext_char(member_string, -1))) //removing the last '}'
+ fields = list(readlist(member_string, ";"))
+ for(var/I in fields)
+ var/value = fields[I]
+ if(istext(value))
+ fields[I] = apply_text_macros(value)
//then fill the members_attributes list with the corresponding variables
- members_attributes.len++
- members_attributes[index++] = fields
-
- CHECK_TICK
+ members_attributes += fields
+ MAPLOADING_CHECK_TICK
//check and see if we can just skip this turf
//So you don't have to understand this horrid statement, we can do this if
- // 1. no_changeturf is set
- // 2. the space_key isn't set yet
+ // 1. the space_key isn't set yet
+ // 2. no_changeturf is set
// 3. there are exactly 2 members
// 4. with no attributes
// 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]) \
+ if(!set_space \
+ && no_changeturf \
&& 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))
-
+ set_space = TRUE
.[SPACE_KEY] = model_key
continue
-
.[model_key] = list(members, members_attributes)
+ return .
-/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num, delete)
+/datum/parsed_map/proc/build_coordinate(list/model, turf/crds, no_changeturf as num, placeOnTop as num, new_z, delete)
+ // If we don't have a turf, nothing we will do next will actually acomplish anything, so just go back
+ // Note, this would actually drop area vvs in the tile, but like, why tho
+ if(!crds)
+ return
var/index
var/list/members = model[1]
var/list/members_attributes = model[2]
+ // We use static lists here because it's cheaper then passing them around
+ var/static/list/default_list = GLOB.map_model_default
////////////////
//Instanciation
////////////////
+ if(turf_blacklist?[crds])
+ return
+
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
//first instance the /area and remove it from the members list
index = members.len
+ var/area/old_area
if(members[index] != /area/template_noop)
- var/atype = members[index]
- GLOB._preloader.setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation
- var/atom/instance = areaCache[atype]
- if(!instance)
- instance = GLOB.areas_by_type[atype]
- if(!instance)
- instance = new atype(null)
- areaCache[atype] = instance
- if(crds)
- instance.contents.Add(crds)
-
- if(GLOB.use_preloader && instance)
- GLOB._preloader.load(instance)
-
- //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
-
- var/first_turf_index = 1
- while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members
- first_turf_index++
-
- //turn off base new Initialization until the whole thing is loaded
- SSatoms.map_loader_begin()
- //instanciate the first /turf
- var/turf/T
- if(members[first_turf_index] != /turf/template_noop)
- T = instance_atom(members[first_turf_index], members_attributes[first_turf_index], crds,no_changeturf, placeOnTop, delete)
-
- if(T)
- //if others /turf are presents, simulates the underlays piling effect
- index = first_turf_index + 1
- while(index <= members.len - 1) // Last item is an /area
- var/underlay = T.appearance
- T = instance_atom(members[index], members_attributes[index], crds,no_changeturf, placeOnTop, delete)//instance new turf
- T.underlays += underlay
- index++
+ if(members_attributes[index] != default_list)
+ world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation
+ var/area/area_instance = loaded_areas[members[index]]
+ if(!area_instance)
+ var/area_type = members[index]
+ // If this parsed map doesn't have that area already, we check the global cache
+ area_instance = GLOB.areas_by_type[area_type]
+ // If the global list DOESN'T have this area it's either not a unique area, or it just hasn't been created yet
+ if (!area_instance)
+ area_instance = new area_type(null)
+ if(!area_instance)
+ CRASH("[area_type] failed to be new'd, what'd you do?")
+ loaded_areas[area_type] = area_instance
+
+ if(!new_z)
+ old_area = crds.loc
+// old_area.turfs_to_uncontain += crds
+// area_instance.contained_turfs.Add(crds)
+ area_instance.contents.Add(crds)
+
+ if(GLOB.use_preloader)
+ world.preloader_load(area_instance)
+
+ // Index right before /area is /turf
+ index--
+ var/atom/instance
+ //then instance the /turf
+ //NOTE: this used to place any turfs before the last "underneath" it using .appearance and underlays
+ //We don't actually use this, and all it did was cost cpu, so we don't do this anymore
+ if(members[index] != /turf/template_noop)
+ if(members_attributes[index] != default_list)
+ world.preloader_setup(members_attributes[index], members[index])
+
+ // CM/TGMC addition: delete map contents before inserting if delete truthy
+ if(delete)
+ for(var/atom/turf_atom as anything in crds.GetAllTurfStrictContents())
+ if(isobserver(turf_atom))
+ continue
+ qdel(turf_atom, force = TRUE)
+
+ // Note: we make the assertion that the last path WILL be a turf. if it isn't, this will fail.
+ if(placeOnTop)
+ instance = crds.load_on_top(members[index], CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE))
+ else if(no_changeturf)
+ instance = create_atom(members[index], crds)//first preloader pass
+ else
+ instance = crds.ChangeTurf(members[index], null, CHANGETURF_DEFER_CHANGE)
+
+ if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
+ world.preloader_load(instance)
+ // If this isn't template work, we didn't change our turf and we changed area, then we've gotta handle area lighting transfer
+ else if(!no_changeturf && old_area)
+ // Don't do contain/uncontain stuff, this happens a few lines up when the area actally changes
+ crds.on_change_area(old_area, crds.loc)
+ MAPLOADING_CHECK_TICK
//finally instance all remainings objects/mobs
- for(index in 1 to first_turf_index-1)
- instance_atom(members[index], members_attributes[index], crds, no_changeturf, placeOnTop, delete)
- //Restore initialization to the previous value
- SSatoms.map_loader_stop()
+ for(var/atom_index in 1 to index-1)
+ if(members_attributes[atom_index] != default_list)
+ world.preloader_setup(members_attributes[atom_index], members[atom_index])
+
+ // We make the assertion that only /atom s will be in this portion of the code. if that isn't true, this will fail
+ instance = create_atom(members[atom_index], crds)//first preloader pass
+
+ if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
+ world.preloader_load(instance)
+ MAPLOADING_CHECK_TICK
////////////////
//Helpers procs
////////////////
-//Instance an atom at (x,y,z) and gives it the variables in attributes
-/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop, delete)
- GLOB._preloader.setup(attributes, path)
-
- if(crds)
- if(ispath(path, /turf))
- if(delete)
- for(var/atom/A as anything in crds.GetAllTurfStrictContents())
- if(isobserver(A))
- continue
- qdel(A, force=TRUE)
-
- if(placeOnTop)
- . = crds.PlaceOnTop(null, path, CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE))
- else if(!no_changeturf)
- . = crds.ChangeTurf(path, null, CHANGETURF_DEFER_CHANGE)
- else
- . = create_atom(path, crds)//first preloader pass
- else
- . = create_atom(path, crds)//first preloader pass
-
- if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New()
- GLOB._preloader.load(.)
-
- //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize
- if(TICK_CHECK)
- SSatoms.map_loader_stop()
- stoplag()
- SSatoms.map_loader_begin()
-
/datum/parsed_map/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)
-/datum/parsed_map/proc/trim_text(what as text,trim_quotes=0)
- if(trim_quotes)
- return trimQuotesRegex.Replace(what, "")
- else
- return trimRegex.Replace(what, "")
-
-
//find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape
//returns 0 if reached the last delimiter
/datum/parsed_map/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"")
@@ -410,7 +1007,6 @@
return next_delimiter
-
//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
/datum/parsed_map/proc/readlist(text as text, delimiter=",")
@@ -418,28 +1014,29 @@
if (!text)
return
+ // If we're using a semi colon, we can do this as splittext rather then constant calls to find_next_delimiter_position
+ // This does make the code a bit harder to read, but saves a good bit of time so suck it up
var/position
var/old_position = 1
-
while(position != 0)
// find next delimiter that is not within "..."
position = find_next_delimiter_position(text,old_position,delimiter)
// check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7))
var/equal_position = findtext(text,"=",old_position, position)
-
- var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position)))
- var/left_constant = delimiter == ";" ? trim_left : parse_constant(trim_left)
+ var/trim_left = TRIM_TEXT(copytext(text,old_position,(equal_position ? equal_position : position)))
+ var/left_constant = parse_constant(trim_left)
if(position)
old_position = position + length(text[position])
+ if(!left_constant) // damn newlines man. Exists to provide behavior consistency with the above loop. not a major cost becuase this path is cold
+ continue
if(equal_position && !isnum(left_constant))
// Associative var, so do the association.
// Note that numbers cannot be keys - the RHS is dropped if so.
- var/trim_right = trim_text(copytext(text, equal_position + length(text[equal_position]), position))
+ var/trim_right = TRIM_TEXT(copytext(text, equal_position + length(text[equal_position]), position))
var/right_constant = parse_constant(trim_right)
.[left_constant] = right_constant
-
else // simple var
. += list(left_constant)
@@ -451,7 +1048,10 @@
// string
if(text[1] == "\"")
- return copytext(text, length(text[1]) + 1, findtext(text, "\"", length(text[1]) + 1))
+ // insert implied locate \" and length("\"") here
+ // It's a minimal timesave but it is a timesave
+ // Safe becuase we're guarenteed trimmed constants
+ return copytext(text, 2, -1)
// list
if(copytext(text, 1, 6) == "list(")//6 == length("list(") + 1
@@ -479,4 +1079,17 @@
/datum/parsed_map/Destroy()
..()
+ SSatoms.map_loader_stop(REF(src)) // Just in case, I don't want to double up here
+ if(turf_blacklist)
+ turf_blacklist.Cut()
+ parsed_bounds.Cut()
+ bounds.Cut()
+ grid_models.Cut()
+ gridSets.Cut()
return QDEL_HINT_HARDDEL_NOW
+
+#undef MAP_DMM
+#undef MAP_TGM
+#undef MAP_UNKNOWN
+#undef TRIM_TEXT
+#undef MAPLOADING_CHECK_TICK
diff --git a/code/modules/mapping/space_management/space_level.dm b/code/modules/mapping/space_management/space_level.dm
index 861258aa20a2..48303b5e39ae 100644
--- a/code/modules/mapping/space_management/space_level.dm
+++ b/code/modules/mapping/space_management/space_level.dm
@@ -4,13 +4,20 @@
var/list/traits
var/z_value = 1 //actual z placement
var/linkage = SELFLOOPING
- var/x_bounds
- var/y_bounds
+ /// Bounds at time of loading the map
+ var/bounds
/datum/space_level/New(new_z, new_name, list/new_traits = list())
z_value = new_z
name = new_name
traits = new_traits
+
+ if (islist(new_traits))
+ for (var/trait in new_traits)
+ SSmapping.z_trait_levels[trait] += list(new_z)
+ else // in case a single trait is passed in
+ SSmapping.z_trait_levels[new_traits] += list(new_z)
//set_linkage(new_traits[ZTRAIT_LINKAGE])
- x_bounds = world.maxx
- y_bounds = world.maxy
+
+ //Lazy Init value, will be hopefully changed by SSmapping
+ bounds = list(1, 1, z_value, world.maxx, world.maxy, z_value)
diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm
index adaff04000ff..3c47ac3b5c2a 100644
--- a/code/modules/mapping/space_management/space_reservation.dm
+++ b/code/modules/mapping/space_management/space_reservation.dm
@@ -1,81 +1,240 @@
+/// Cordon area surrounding turf reservations
+/area/misc/cordon
+ name = "CORDON"
+ icon_state = "cordon"
+ static_lighting = FALSE
+ base_lighting_alpha = 255
+ requires_power = FALSE
+
+#define CORDON_TURF_TYPE /turf/closed/cordon
//Yes, they can only be rectangular.
//Yes, I'm sorry.
/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
- var/bottom_left_coords[3]
- var/top_right_coords[3]
- var/wipe_reservation_on_release = TRUE
+
+ /// The z stack size of the reservation. Note that reservations are ALWAYS reserved from the bottom up
+ var/z_size = 0
+
+ /// List of the bottom left turfs. Indexed by what their z index for this reservation is
+ var/list/bottom_left_turfs = list()
+
+ /// List of the top right turfs. Indexed by what their z index for this reservation is
+ var/list/top_right_turfs = list()
+
+ /// The turf type the reservation is initially made with
var/turf_type = /turf/open/space
+ ///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen
+ var/pre_cordon_distance = 0
+
/datum/turf_reservation/transit
turf_type = /turf/open/space/transit
+ pre_cordon_distance = 7
/datum/turf_reservation/interior
turf_type = /turf/open/void/vehicle
/datum/turf_reservation/proc/Release()
- var/v = reserved_turfs.Copy()
- for(var/i in reserved_turfs)
- var/turf/T = i
- T.flags_atom |= UNUSED_RESERVATION_TURF
- reserved_turfs -= i
- SSmapping.used_turfs -= i
- SSmapping.reserve_turfs(v)
+ bottom_left_turfs.Cut()
+ top_right_turfs.Cut()
+
+ 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()
-/datum/turf_reservation/proc/Reserve(width, height, zlevel)
+ var/release_turfs = reserved_copy + cordon_copy
+
+ for(var/turf/reserved_turf as anything in release_turfs)
+ SEND_SIGNAL(reserved_turf, COMSIG_TURF_RESERVATION_RELEASED, src)
+
+ // Makes the linter happy, even tho we don't await this
+ INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, reserve_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(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF))
+ return FALSE
+ cordon_turfs |= possible_turfs
+
+ if(pre_cordon_distance)
+ var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z)
+ var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon
+ for(var/turf/turf_being_added as anything in to_add)
+ pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates
+
+ 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/misc/cordon/cordon_area = GLOB.areas_by_type[/area/misc/cordon] || new
+ //var/area/old_area = cordon_turf.loc
+ //old_area.turfs_to_uncontain += cordon_turf
+ //cordon_area.contained_turfs += cordon_turf
+ cordon_area.contents += cordon_turf
+ // Its no longer unused, but its also not "used"
+ cordon_turf.turf_flags &= ~UNUSED_RESERVATION_TURF
+ cordon_turf.ChangeTurf(CORDON_TURF_TYPE, CORDON_TURF_TYPE)
+ SSmapping.unused_turfs["[cordon_turf.z]"] -= cordon_turf
+ // still gets linked to us though
+ SSmapping.used_turfs[cordon_turf] = src
+
+ //swap the area with the pre-cordoning area
+
+/// 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)
- log_debug("turf reservation had invalid dimensions")
return FALSE
var/list/avail = SSmapping.unused_turfs["[zlevel]"]
- var/turf/bottom_left
- var/turf/top_right
+ var/turf/BL
+ var/turf/TR
var/list/turf/final = list()
var/passing = FALSE
for(var/i in avail)
CHECK_TICK
- bottom_left = i
- if(!(bottom_left.flags_atom & UNUSED_RESERVATION_TURF))
+ BL = i
+ if(!(BL.turf_flags & UNUSED_RESERVATION_TURF))
continue
- if(bottom_left.x + width > world.maxx || bottom_left.y + height > world.maxy)
+ if(BL.x + width > world.maxx || BL.y + height > world.maxy)
continue
- top_right = locate(bottom_left.x + width - 1, bottom_left.y + height - 1, bottom_left.z)
- if(!(top_right.flags_atom & UNUSED_RESERVATION_TURF))
+ TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z)
+ if(!(TR.turf_flags & UNUSED_RESERVATION_TURF))
continue
- final = block(bottom_left, top_right)
+ final = block(BL, TR)
if(!final)
continue
passing = TRUE
- for(var/turf/checking as anything in final)
- if(!(checking.flags_atom & UNUSED_RESERVATION_TURF))
+ for(var/I in final)
+ 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(BL, TR)
if(!passing)
continue
break
- if(!passing || !istype(bottom_left) || !istype(top_right))
- log_debug("failed to pass reservation tests, [passing], [istype(bottom_left)], [istype(top_right)]")
+ if(!passing || !istype(BL) || !istype(TR))
return FALSE
- bottom_left_coords = list(bottom_left.x, bottom_left.y, bottom_left.z)
- top_right_coords = list(top_right.x, top_right.y, top_right.z)
- var/weakref = WEAKREF(src)
for(var/i in final)
var/turf/T = i
reserved_turfs |= T
SSmapping.unused_turfs["[T.z]"] -= T
- SSmapping.used_turfs[T] = weakref
- T = T.ChangeTurf(turf_type, turf_type)
- T.flags_atom &= ~UNUSED_RESERVATION_TURF
- src.width = width
- src.height = height
+ SSmapping.used_turfs[T] = src
+ T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF
+ T.ChangeTurf(turf_type, turf_type)
+
+ bottom_left_turfs += BL
+ top_right_turfs += TR
+ return TRUE
+
+/datum/turf_reservation/proc/reserve(width, height, z_size, z_reservation)
+ src.z_size = z_size
+ var/failed_reservation = FALSE
+ for(var/_ in 1 to z_size)
+ if(!_reserve_area(width, height, z_reservation))
+ failed_reservation = TRUE
+ break
+
+ if(failed_reservation)
+ Release()
+ return FALSE
+
+ generate_cordon()
return TRUE
+/// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable.
+/datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target)
+ for(var/z_idx in 1 to z_size)
+ var/turf/bottom_left = bottom_left_turfs[z_idx]
+ var/turf/top_right = top_right_turfs[z_idx]
+ var/bl_x = bottom_left.x
+ var/bl_y = bottom_left.y
+ var/tr_x = top_right.x
+ var/tr_y = top_right.y
+
+ if(target.x < bl_x)
+ continue
+
+ if(target.y < bl_y)
+ continue
+
+ if(target.x > tr_x)
+ continue
+
+ if(target.y > tr_y)
+ continue
+
+ var/list/return_information = list()
+ return_information["z_idx"] = z_idx
+ return_information["offset_x"] = target.x - bl_x
+ return_information["offset_y"] = target.y - bl_y
+ return return_information
+ return null
+
+/// Gets the turf below the given target. Returns null if there is no turf below the target
+/datum/turf_reservation/proc/get_turf_below(turf/target)
+ var/list/bounds_info = calculate_turf_bounds_information(target)
+ if(isnull(bounds_info))
+ return null
+
+ var/z_idx = bounds_info["z_idx"]
+ // check what z level, if its the max, then there is no turf below
+ if(z_idx == z_size)
+ return null
+
+ var/offset_x = bounds_info["offset_x"]
+ var/offset_y = bounds_info["offset_y"]
+ var/turf/bottom_left = bottom_left_turfs[z_idx + 1]
+ return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z)
+
+/// Gets the turf above the given target. Returns null if there is no turf above the target
+/datum/turf_reservation/proc/get_turf_above(turf/target)
+ var/list/bounds_info = calculate_turf_bounds_information(target)
+ if(isnull(bounds_info))
+ return null
+
+ var/z_idx = bounds_info["z_idx"]
+ // check what z level, if its the min, then there is no turf above
+ if(z_idx == 1)
+ return null
+
+ var/offset_x = bounds_info["offset_x"]
+ var/offset_y = bounds_info["offset_y"]
+ var/turf/bottom_left = bottom_left_turfs[z_idx - 1]
+ return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z)
+
/datum/turf_reservation/New()
LAZYADD(SSmapping.turf_reservations, src)
/datum/turf_reservation/Destroy()
- INVOKE_ASYNC(src, PROC_REF(Release))
+ Release()
LAZYREMOVE(SSmapping.turf_reservations, src)
return ..()
diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm
index 9311719ea7d8..2b96065929f8 100644
--- a/code/modules/mapping/space_management/zlevel_manager.dm
+++ b/code/modules/mapping/space_management/zlevel_manager.dm
@@ -14,16 +14,22 @@
for (var/I in 1 to default_map_traits.len)
var/list/features = default_map_traits[I]
var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS])
- z_list += S
+ manage_z_level(S, filled_with_space = FALSE)
+ //generate_z_level_linkages() // Default Zs don't use add_new_zlevel() so they don't automatically generate z-linkages.
-/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level)
+/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, contain_turfs = TRUE)
+ UNTIL(!adding_new_zlevel)
+ adding_new_zlevel = TRUE
var/new_z = z_list.len + 1
if (world.maxz < new_z)
world.incrementMaxZ()
CHECK_TICK
// TODO: sleep here if the Z level needs to be cleared
var/datum/space_level/S = new z_type(new_z, name, traits)
- z_list += S
+ manage_z_level(S, filled_with_space = TRUE, contain_turfs = contain_turfs)
+ //generate_linkages_for_z_level(new_z)
+ //calculate_z_level_gravity(new_z)
+ adding_new_zlevel = FALSE
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, S)
return S
diff --git a/code/modules/nightmare/nmtasks/mapload.dm b/code/modules/nightmare/nmtasks/mapload.dm
index 1ace7cd2fbec..d53b5663197d 100644
--- a/code/modules/nightmare/nmtasks/mapload.dm
+++ b/code/modules/nightmare/nmtasks/mapload.dm
@@ -53,7 +53,7 @@
if(!target_turf?.z)
log_debug("Nightmare Mapload: Could not find landmark: [landmark]")
return
- var/result = parsed.load(target_turf.x, target_turf.y, target_turf.z, cropMap = TRUE, no_changeturf = FALSE, placeOnTop = FALSE, delete = replace)
+ var/result = parsed.load(target_turf.x, target_turf.y, target_turf.z, crop_map = TRUE, no_changeturf = FALSE, place_on_top = FALSE, delete = replace)
if(!result || !parsed.bounds)
log_debug("Nightmare Mapload: Map insertion failed unexpectedly for file: [filepath]")
return
@@ -66,22 +66,23 @@
log_debug("Nightmare Mapload: Loaded map file but could not initialize: '[filepath]' at ([target_turf.x], [target_turf.y], [target_turf.z])")
return TRUE
-/// Initialize atoms/areas in bounds
+/// Initialize atoms/areas in bounds - basically a Nightmare version of [/datum/map_template/initTemplateBounds]
/datum/nmtask/mapload/proc/initialize_boundary_contents()
var/list/bounds = parsed.bounds
if(length(bounds) < 6)
return
- var/list/TT = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
+ var/list/turf_block = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
var/list/area/arealist = list()
var/list/atom/atomlist = list()
- for(var/turf/T as anything in TT)
- atomlist |= T
- if(T.loc) arealist |= T.loc
- for(var/A in T)
- atomlist |= A
- SSmapping.reg_in_areas_in_z(arealist)
- SSatoms.InitializeAtoms(atomlist)
- // We still defer lighting, area sorting, etc
+ for(var/turf/turf as anything in turf_block)
+ atomlist += turf
+ if(turf.loc)
+ arealist |= turf.loc
+ for(var/atom/movable/movable as anything in turf)
+ atomlist += movable // Much like initTemplateBounds() this only recurses content once. Never been an issue so far, but keep it in mind.
+ SSmapping.reg_in_areas_in_z(arealist) // Legacy. Not sure this is needed as it should already be carried out through area Initialize.
+ SSatoms.InitializeAtoms(atomlist + arealist)
+ // We still defer lighting, area sorting, etc, to be done all in one go!
SEND_SIGNAL(src, COMSIG_NIGHTMARE_TAINTED_BOUNDS, bounds)
return TRUE
diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm
index 79377d9c0849..b803a8f66b55 100644
--- a/code/modules/shuttle/computer.dm
+++ b/code/modules/shuttle/computer.dm
@@ -81,8 +81,8 @@
to_chat(usr, SPAN_NOTICE("Unable to comply."))
return TRUE
-/obj/structure/machinery/computer/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
- if(port && (shuttleId == initial(shuttleId) || override))
+/obj/structure/machinery/computer/shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ if(port && (shuttleId == initial(shuttleId)))
shuttleId = port.id
/obj/structure/machinery/computer/shuttle/ert
diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/docking.dm
index a21ec330d4c9..63e220deadc6 100644
--- a/code/modules/shuttle/docking.dm
+++ b/code/modules/shuttle/docking.dm
@@ -1,7 +1,5 @@
/// This is the main proc. It instantly moves our mobile port to stationary port `new_dock`.
/obj/docking_port/mobile/proc/initiate_docking(obj/docking_port/stationary/new_dock, movement_direction, force=FALSE)
- // Crashing this ship with NO SURVIVORS
-
if(new_dock.get_docked() == src)
remove_ripples()
return DOCKING_SUCCESS
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 6f02bf00e89d..d72dcdda5bfe 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -380,10 +380,82 @@
var/shuttle_flags = NONE
+#define WORLDMAXX_CUTOFF (world.maxx + 1)
+#define WORLDMAXY_CUTOFF (world.maxx + 1)
+/**
+ * Calculated and populates the information used for docking and some internal vars.
+ * This can also be used to calculate from shuttle_areas so that you can expand/shrink shuttles!
+ *
+ * Arguments:
+ * * loading_from - The template that the shuttle was loaded from, if not given we iterate shuttle_areas to calculate information instead
+ */
+/obj/docking_port/mobile/proc/calculate_docking_port_information(datum/map_template/shuttle/loading_from)
+ var/port_x_offset = loading_from?.port_x_offset
+ var/port_y_offset = loading_from?.port_y_offset
+ var/width = loading_from?.width
+ var/height = loading_from?.height
+ if(!loading_from)
+ if(!length(shuttle_areas))
+ CRASH("Attempted to calculate a docking port's information without a template before it was assigned any areas!")
+ // no template given, use shuttle_areas to calculate width and height
+ var/min_x = -1
+ var/min_y = -1
+ var/max_x = WORLDMAXX_CUTOFF
+ var/max_y = WORLDMAXY_CUTOFF
+ for(var/area/area as anything in shuttle_areas)
+ for(var/turf/turf in area)
+ min_x = max(turf.x, min_x)
+ max_x = min(turf.x, max_x)
+ min_y = max(turf.y, min_y)
+ max_y = min(turf.y, max_y)
+ CHECK_TICK
+
+ if(min_x == -1 || max_x == WORLDMAXX_CUTOFF)
+ CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.")
+ if(min_y == -1 || max_y == WORLDMAXY_CUTOFF)
+ CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.")
+
+ width = (max_x - min_x) + 1
+ height = (max_y - min_y) + 1
+ port_x_offset = min_x - x
+ port_y_offset = min_y - y
+
+ if(dir in list(EAST, WEST))
+ src.width = height
+ src.height = width
+ else
+ src.width = width
+ src.height = height
+
+ switch(dir)
+ if(NORTH)
+ dwidth = port_x_offset - 1
+ dheight = port_y_offset - 1
+ if(EAST)
+ dwidth = height - port_y_offset
+ dheight = port_x_offset - 1
+ if(SOUTH)
+ dwidth = width - port_x_offset
+ dheight = height - port_y_offset
+ if(WEST)
+ dwidth = port_y_offset - 1
+ dheight = width - port_x_offset
+#undef WORLDMAXX_CUTOFF
+#undef WORLDMAXY_CUTOFF
+
/obj/docking_port/mobile/register()
. = ..()
SSshuttle.mobile += src
+/**
+ * Actions to be taken after shuttle is loaded and has been moved to its final location
+ *
+ * Arguments:
+ * * replace - TRUE if this shuttle is replacing an existing one. FALSE by default.
+ */
+/obj/docking_port/mobile/proc/postregister(replace = FALSE)
+ return
+
/obj/docking_port/mobile/Destroy(force)
if(force)
QDEL_NULL(alarm_sound_loop)
@@ -442,6 +514,10 @@
// Called after the shuttle is loaded from template
/obj/docking_port/mobile/proc/linkup(datum/map_template/shuttle/template, obj/docking_port/stationary/dock)
+
+ // ================== CM Change ==================
+ // This is gone in /tg/ backend but kept for historical reasons
+ // Suspect this is supposed to be handled in register
var/list/static/shuttle_id = list()
var/idnum = ++shuttle_id[id]
if(idnum > 1)
@@ -449,12 +525,12 @@
id = "[id][idnum]"
if(name == initial(name))
name = "[name] [idnum]"
- for(var/place in shuttle_areas)
- var/area/area = place
- area.connect_to_shuttle(src, dock, idnum, FALSE)
- for(var/each in place)
- var/atom/atom = each
- atom.connect_to_shuttle(src, dock, idnum, FALSE)
+ // ================ END CM Change ================
+
+ for(var/area/place as anything in shuttle_areas)
+ place.connect_to_shuttle(TRUE, src, dock)
+ for(var/atom/individual_atoms in place)
+ individual_atoms.connect_to_shuttle(TRUE, src, dock)
//this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact
diff --git a/code/modules/shuttle/shuttles/trijent_elevator.dm b/code/modules/shuttle/shuttles/trijent_elevator.dm
index 457c150212c0..ff10485e8d5a 100644
--- a/code/modules/shuttle/shuttles/trijent_elevator.dm
+++ b/code/modules/shuttle/shuttles/trijent_elevator.dm
@@ -37,12 +37,6 @@
. = ..()
door_control.control_doors("force-lock-launch", "all", force=TRUE)
-/obj/docking_port/mobile/trijent_elevator/linkup(datum/map_template/shuttle/template, obj/docking_port/stationary/dock)
- ..()
- var/datum/map_template/shuttle/trijent_elevator/elev = template
- elevator_network = elev.elevator_network
- log_debug("Adding network [elev.elevator_network] to [id]")
-
/obj/docking_port/stationary/trijent_elevator
dir=NORTH
width=7
diff --git a/code/modules/vehicles/interior/interior.dm b/code/modules/vehicles/interior/interior.dm
index f2afcd5ae5f7..8fb65602c9b3 100644
--- a/code/modules/vehicles/interior/interior.dm
+++ b/code/modules/vehicles/interior/interior.dm
@@ -304,21 +304,13 @@
// Returns min and max turfs for the interior
/datum/interior/proc/get_bound_turfs()
- var/turf/min = TURF_FROM_COORDS_LIST(reservation.bottom_left_coords)
- if(!min)
- return null
-
- var/turf/max = TURF_FROM_COORDS_LIST(reservation.top_right_coords)
- if(!max)
- return null
-
- return list(min, max)
+ return list(reservation.bottom_left_turfs[1], reservation.top_right_turfs[1])
/datum/interior/proc/get_middle_coords()
- var/turf/min = reservation.bottom_left_coords
- var/turf/max = reservation.top_right_coords
+ var/turf/min = reservation.bottom_left_turfs[1]
+ var/turf/max = reservation.top_right_turfs[1]
+ return list(Floor(min.x + (max.x - min.x)/2), Floor(min.y + (max.y - min.y)/2), min.z)
- return list(Floor(min[1] + (max[1] - min[1])/2), Floor(min[2] + (max[2] - min[2])/2), min[3])
/datum/interior/proc/get_middle_turf()
var/list/turf/bounds = get_bound_turfs()
diff --git a/colonialmarines.dme b/colonialmarines.dme
index 71fe1545c2fe..1098868950db 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -112,6 +112,7 @@
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\traits.dm"
#include "code\__DEFINES\turf_flags.dm"
+#include "code\__DEFINES\turfs.dm"
#include "code\__DEFINES\tutorial.dm"
#include "code\__DEFINES\unit_tests.dm"
#include "code\__DEFINES\urls.dm"
@@ -184,6 +185,7 @@
#include "code\__HELPERS\sanitize_values.dm"
#include "code\__HELPERS\shell.dm"
#include "code\__HELPERS\status_effects.dm"
+#include "code\__HELPERS\string_lists.dm"
#include "code\__HELPERS\text.dm"
#include "code\__HELPERS\traits.dm"
#include "code\__HELPERS\type2type.dm"
diff --git a/maps/shuttles/dropship_alamo.dmm b/maps/shuttles/dropship_alamo.dmm
index d23036afdea3..7ea59dbb1185 100644
--- a/maps/shuttles/dropship_alamo.dmm
+++ b/maps/shuttles/dropship_alamo.dmm
@@ -101,7 +101,7 @@
/area/shuttle/drop1/sulaco)
"if" = (
/obj/structure/shuttle/part/dropship1/left_outer_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"ig" = (
/turf/closed/shuttle/dropship1{
@@ -218,7 +218,7 @@
/area/shuttle/drop1/sulaco)
"rl" = (
/obj/structure/shuttle/part/dropship1/lower_right_wall,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"rr" = (
/turf/closed/shuttle/dropship1/transparent{
@@ -239,7 +239,7 @@
/area/shuttle/drop1/sulaco)
"sA" = (
/obj/structure/shuttle/part/dropship1/lower_left_wall,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"tR" = (
/obj/item/device/radio/intercom/alamo{
@@ -279,7 +279,7 @@
/area/shuttle/drop1/sulaco)
"xM" = (
/obj/structure/shuttle/part/dropship1/bottom_right_wall,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"yQ" = (
/turf/closed/shuttle/dropship1/transparent{
@@ -288,7 +288,7 @@
/area/shuttle/drop1/sulaco)
"yU" = (
/obj/structure/shuttle/part/dropship1/bottom_left_wall,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"zw" = (
/obj/structure/shuttle/part/dropship1/transparent/upper_left_wing,
@@ -358,7 +358,7 @@
/area/shuttle/drop1/sulaco)
"EN" = (
/obj/structure/shuttle/part/dropship1/transparent/engine_left_exhaust,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"FA" = (
/obj/structure/shuttle/part/dropship1/transparent/engine_left_cap,
@@ -408,7 +408,7 @@
/area/shuttle/drop1/sulaco)
"Jm" = (
/obj/structure/shuttle/part/dropship1/transparent/engine_right_exhaust,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"JP" = (
/turf/closed/shuttle/dropship1{
@@ -442,7 +442,7 @@
/area/shuttle/drop1/sulaco)
"Me" = (
/obj/structure/shuttle/part/dropship1/left_inner_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"MP" = (
/turf/closed/shuttle/dropship1{
@@ -539,7 +539,7 @@
/area/shuttle/drop1/sulaco)
"Ta" = (
/obj/structure/shuttle/part/dropship1/right_inner_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"Te" = (
/obj/structure/shuttle/part/dropship1/transparent/right_inner_bottom_wing,
@@ -609,7 +609,7 @@
/area/shuttle/drop1/sulaco)
"Wg" = (
/obj/structure/shuttle/part/dropship1/right_outer_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop1/sulaco)
"WQ" = (
/turf/closed/shuttle/dropship1{
diff --git a/maps/shuttles/dropship_normandy.dmm b/maps/shuttles/dropship_normandy.dmm
index e64fbe62372d..0cf629e638e1 100644
--- a/maps/shuttles/dropship_normandy.dmm
+++ b/maps/shuttles/dropship_normandy.dmm
@@ -63,7 +63,7 @@
/area/shuttle/drop2/sulaco)
"fQ" = (
/obj/structure/shuttle/part/dropship2/left_inner_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop2/sulaco)
"gD" = (
/turf/closed/shuttle/dropship2/transparent{
@@ -118,7 +118,7 @@
/area/shuttle/drop2/sulaco)
"iI" = (
/obj/structure/shuttle/part/dropship2/right_inner_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop2/sulaco)
"jc" = (
/obj/item/device/radio/intercom/normandy{
@@ -216,7 +216,7 @@
/area/shuttle/drop2/sulaco)
"nS" = (
/obj/structure/shuttle/part/dropship2/lower_left_wall,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop2/sulaco)
"oc" = (
/obj/structure/shuttle/part/dropship2/transparent/nose_top_right,
@@ -514,7 +514,7 @@
/area/shuttle/drop2/sulaco)
"NM" = (
/obj/structure/shuttle/part/dropship2/left_outer_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop2/sulaco)
"Od" = (
/obj/effect/attach_point/fuel/dropship2{
@@ -587,7 +587,7 @@
/area/shuttle/drop2/sulaco)
"QK" = (
/obj/structure/shuttle/part/dropship2/lower_right_wall,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop2/sulaco)
"Rr" = (
/turf/template_noop,
@@ -607,7 +607,7 @@
/area/shuttle/drop2/sulaco)
"RJ" = (
/obj/structure/shuttle/part/dropship2/right_outer_wing_connector,
-/turf/open/space/basic,
+/turf/template_noop,
/area/shuttle/drop2/sulaco)
"SB" = (
/obj/structure/shuttle/part/dropship2/nose_front_left,