Files

711 lines
24 KiB
Plaintext

//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station Developers *//
/**
* a struct for holding information about a zlevel
*
* ## Loading
*
* * See `map.dm` for load call order.
*/
/datum/map_level
//* Core *//
/// id - must be globally unique across all possible maps
/// if we are New()'d with a map, this is namespaced with that map automatically! you do not need
/// to namespace map levels yourself.
/// please use 'dashes-as-spacing' to format ids.
///
/// * if this is null, one will be assigned.
/// * this is used to generate mangling ID.
var/id
/// friendly debug / code name of level
var/name
/// explicit mangling id
var/mangling_id
//* Attributes *//
/// traits
var/list/traits
/// attributes associated key-values
var/list/attributes
//* File *//
/// Absolute path to the map .dmm file.
/// * This can be a literal file.
/// * This is determined with regards to the context of the load.
/// * Hardcoded shuttle templates will be the path from the server's working directory.
var/path
//* Fluff *//
/// player visible id for technical displays - randomized if unset
var/display_id
/// player visible name for non-technical displays - randomized if unset
var/display_name
//* Instance / Orchestration *//
/// host /datum/map, if any
var/datum/map/parent_map
/// are we modified from our prototype/definition?
var/tmp/modified = FALSE
//* Linkage *//
/// linkage enum
var/linkage = Z_LINKAGE_NORMAL
/// transition enum
var/transition = Z_TRANSITION_DEFAULT
/// set to FALSE if transition borders are defined via /turf/level_border, to disable trampling the turf into /turf/level_border
var/transition_trampling = TRUE
/// id of north zlevel
var/link_north_id
/// id of south zlevel
var/link_south_id
/// id of west zlevel
var/link_west_id
/// id of east zlevel
var/link_east_id
/// id of below zlevel
var/link_below_id
/// id of above zlevel
var/link_above_id
//* Turf Properties *//
/// base turf typepath for this level
/// * if unset, will not overwrite the level's turfs
var/base_turf
/// base area typepath for this level
/// * if unset, will not overwrite the level's area
var/base_area
/// gas string / atmosphere path / atmosphere id for indoors air
/// if atmosphere path, it'll be automatically packed to ID on serialize, as we don't want to serialize paths to disk.
var/air_indoors = GAS_STRING_STP
/// gas string / atmosphere path / atmosphere id for outdoors air
/// if atmosphere path, it'll be automatically packed to ID on serialize, as we don't want to serialize paths to disk.
var/air_outdoors = GAS_STRING_VACUUM
//* World State *//
/// Are we loaded in?
var/tmp/loaded = FALSE
/// our zlevel once loaded
var/tmp/z_index
//* Load Options *//
/// load orientation - overridden if loaded as part of a /datum/map
var/load_orientation = SOUTH
/// load should crop - overridden if loaded as part of a /datum/map
var/load_crop = FALSE
/// load should center us on the world level - overridden if loaded as part of a /datum/map
var/load_center = TRUE
//* Rebuilds *//
/// a vertical rebuild is blocking; don't bother queuing another
var/tmp/multiz_vertical_rebuild_queued = FALSE
/// multiz vertical rebuild mutex
var/tmp/multiz_vertical_rebuild_mutex = FALSE
/// tracking for perf
var/tmp/multiz_vertical_rebuild_count = 0
/// a horizontal rebuild is blocking; don't bother queuing another
var/tmp/multiz_horizontal_rebuild_queued = FALSE
/// multiz horizontal rebuild mutex
var/tmp/multiz_horizontal_rebuild_mutex = FALSE
/// tracking for perf
var/tmp/multiz_horizontal_rebuild_count = 0
//* Simulation *//
/// canonical height of level in meters
///
/// * '100m' means that the level above is 100m above us, and we're 100m below the above level
/// * basically, this is the height from ground before the next level, not the height below ground
/// * if null, will inherit from the map we're in (or default to a sane value)
/// * if non-null, the map will force that z-plane to be that height
/// * if non-null and two levels in the same plane have different values, the parent map will runtime.
/// * if one level on a plane is non-null height, all of them must be, and they must match.
var/ceiling_height
//* Structs / Stitching / Virtual Coordinates *//
/// Are we in a struct? Do not set directly, this is set on load
/// if struct x/y/z are set.
var/tmp/struct_active = FALSE
/// our virtual x on the struct; this is not tile coordinates, this is struct coordinates
/// * set as needed so the map knows where to put us in the virtual struct.
var/struct_x
/// our virtual y on the struct; this is not tile coordinates, this is struct coordinates
/// * set as needed so the map knows where to put us in the virtual struct.
var/struct_y
/// the coordinate we are actually on, zlevel wise
///
/// * basically for fluff and simulation, not byond engine
/// * this is used for virtual coordinates as the 'z'
/// * set as needed so the map knows where to put us in the virtual struct.
var/struct_z
//* Virtual Coordinates *//
/// the coordinate of the lower-left / southwest corner border of the map
///
/// * this is not 1,1 on the map, this is the offset to reach 0,0 on the map from 1,1.
/// * this is the turf right **outside** the on the lower left corner.
/// * basically for fluff and simulation, not byond engine
/// * 0-indexed for ease of use by implementations of get virtual coord.
/// * this is used for virtual coordinates as the 'x + [tile.x]'
var/tmp/virtual_alignment_x = 0
/// the coordinate of the lower-left / southwest corner border of the map
///
/// * this is not 1,1 on the map, this is the offset to reach 0,0 on the map from 1,1.
/// * this is the turf right **outside** the on the lower left corner.
/// * basically for fluff and simulation, not byond engine
/// * 0-indexed for ease of use by implementations of get virtual coord.
/// * this is used for virtual coordinates as the 'y + [tile.y]'
var/tmp/virtual_alignment_y = 0
/// the canonical height from ground floor that we are
///
/// * virtual_z = 0 is always 0
/// * virtual_z = 1 is the ceiling_height of virtual_z = 0
/// * virtual_z = -1 is the subtracted ceiling height of virtual_z = -1
var/tmp/virtual_elevation = 0
//* Persistence *//
/// loaded persistence metadata, if any
var/tmp/datum/map_level_persistence/persistence
/// allow persistence?
var/persistence_allowed = FALSE
/// override level id for persistence so two levels are considered the same
/// two levels should **never** be loaded at the same time with the same persistence ID!
var/persistence_id
//* Persistence - Debris *//
/// drop n largest zones
///
/// 0 to disable
var/persistent_debris_drop_n_largest = OBJ_PERSIST_DEFAULT_TUNING_DEBRIS_DROP_N_LARGEST
/// drop n smallest non-single zones
///
/// 0 to disable
var/persistent_debris_drop_n_smallest = OBJ_PERSIST_DEFAULT_TUNING_DEBRIS_DROP_N_SMALLEST
/// drop n single object zones
///
/// 0 to disable
var/persistent_debris_drop_n_single = OBJ_PERSIST_DEFAULT_TUNING_DEBRIS_DROP_N_SINGLE
/// % chance per round to drop 'important' persistent debris like graffiti
/// we drop them as a zone when we drop.
///
/// 0 to disable
var/persistent_debris_important_drop_chance = OBJ_PERSIST_DEFAULT_TUNING_DEBRIS_IMPORTANT_DROP_CHANCE
/// critical mass of important debris count needed in a zone before they're treated
/// as regular debris: mostly for grief prevention
///
/// 0 to disable; will override defaults
var/persistent_debris_important_demotion_zone_threshold = OBJ_PERSIST_DEFAULT_TUNING_DEBRIS_IMPORTANT_DEMOTION_ZONE_THRESHOLD
/// critical mass of important debris total needed before they're treated
/// as regular debris; mostly for grief prevention
///
/// 0 to disable; will override defaults
var/persistent_debris_important_demotion_level_threshold = OBJ_PERSIST_DEFAULT_TUNING_DEBRIS_IMPORTANT_DEMOTION_LEVEL_THRESHOLD
//* Persistence - Trash *//
/// drop n largest meshes of trash
var/persistent_trash_drop_n_largest = OBJ_PERSIST_DEFAULT_TUNING_TRASH_DROP_N_LARGEST
/// drop n smallest meshes of trash
var/persistent_trash_drop_n_smallest = OBJ_PERSIST_DEFAULT_TUNING_TRASH_DROP_N_SMALLEST
/// drop n least dense single items of trash
var/persistent_trash_drop_n_most_isolated = OBJ_PERSIST_DEFAULT_TUNING_TRASH_DROP_N_MOST_ISOLATED
/// drop n most dense single items of trash
var/persistent_trash_drop_n_least_isolated = OBJ_PERSIST_DEFAULT_TUNING_TRASH_DROP_N_LEAST_ISOLATED
/// mesh heuristic in tiles
var/persistent_trash_mesh_heuristic = OBJ_PERSIST_DEFAULT_TUNING_TRASH_MESH_HEURISTIC
/// additional mesh heuristic per 1000 objects
var/persistent_trash_mesh_heuristic_escalate_per_thousand = OBJ_PERSIST_DEFAULT_TUNING_TRASH_MESH_HEURISTIC_ESCALATE_PER_THOUSAND
/datum/map_level/New(datum/map/parent_map)
src.parent_map = parent_map
/datum/map_level/Destroy()
if(loaded)
. = QDEL_HINT_LETMELIVE
CRASH("UH OH, SOMETHING TRIED TO DELETE AN INSTANTIATED LEVEL.")
parent_map = null
return ..()
/datum/map_level/serialize()
. = ..()
.["id"] = id
.["name"] = name
.["display_id"] = display_id
.["display_name"] = display_name
.["traits"] = traits
.["attributes"] = attributes
// not sure why we're even serializing paths but here we go lol
.["path"] = path
// end
.["linkage"] = linkage
.["transition"] = transition
.["base_turf"] = "[base_turf]"
.["base_area"] = "[base_area]"
.["link_north_id"] = link_north_id
.["link_south_id"] = link_south_id
.["link_west_id"] = link_west_id
.["link_east_id"] = link_east_id
.["link_above_id"] = link_above_id
.["link_below_id"] = link_below_id
var/unpacked_air_indoors
if(ispath(air_indoors, /datum/atmosphere))
var/datum/atmosphere/cast_air_indoors = air_indoors
// cast to id if possible, otherwise keep as type
unpacked_air_indoors = initial(cast_air_indoors.id) || air_indoors
var/unpacked_air_outdoors
if(ispath(air_outdoors, /datum/atmosphere))
var/datum/atmosphere/cast_air_outdoors = air_outdoors
// cast to id if possible, otherwise keep as type
unpacked_air_outdoors = initial(cast_air_outdoors.id) || air_outdoors
.["air_indoors"] = unpacked_air_indoors
.["air_outdoors"] = unpacked_air_outdoors
.["load_orientation"] = load_orientation
.["load_center"] = load_center
.["load_crop"] = load_crop
/datum/map_level/deserialize(list/data)
if(loaded)
CRASH("attempted deserialize while loaded")
. = ..()
if(!isnull(data["id"]))
id = data["id"]
if(!isnull(data["name"]))
name = data["name"]
if(!isnull(data["display_id"]))
display_id = data["display_id"]
if(!isnull(data["display_name"]))
display_name = data["display_name"]
if(!isnull(data["traits"]))
traits = data["traits"]
if(!isnull(data["attributes"]))
attributes = data["attributes"]
// not sure why we're even serializing paths but here we go lol
if(!isnull(data["path"]))
path = data["path"]
// end
if(!isnull(data["linkage"]))
linkage = data["linkage"]
if(!isnull(data["transition"]))
transition = data["transition"]
if(!isnull(data["base_turf"]))
base_turf = text2path(data["base_turf"])
if(!isnull(data["base_area"]))
base_area = text2path(data["base_area"])
link_north_id = data["link_north_id"]
link_south_id = data["link_south_id"]
link_east_id = data["link_east_id"]
link_west_id = data["link_west_id"]
link_above_id = data["link_above_id"]
link_below_id = data["link_below_id"]
if(!isnull(data["air_indoors"]))
air_indoors = data["air_indoors"]
if(!isnull(data["air_outdoors"]))
air_outdoors = data["air_outdoors"]
if(!isnull(data["load_orientation"]))
load_orientation = data["load_orientation"]
if(!isnull(data["load_center"]))
load_orientation = data["load_center"]
if(!isnull(data["load_crop"]))
load_orientation = data["load_crop"]
/**
* Checks if we have a .dmm. If this is FALSE,
* we are a naked map_level that should only generate a blank level.
*/
/datum/map_level/proc/has_map_path()
return !!path
/**
* get .dmm path or file
* * null is an acceptable return, if no file is attached to us.
*/
/datum/map_level/proc/resolve_map_path()
return path
/**
* get /datum/dmm_parsed from us.
* * null is an acceptable return, if no file is attached to us.
*/
/datum/map_level/proc/parse_map_path()
var/map_path = resolve_map_path()
if(!map_path)
return null
// this is a real map path
if(isfile(map_path))
else if(!fexists(map_path))
return null
else
map_path = file(map_path)
return parse_map(map_path)
/**
* allow deallocation/unload
*/
/datum/map_level/proc/allow_deallocate()
return TRUE
/**
* should we be part of a struct?
*/
/datum/map_level/proc/is_in_struct()
return !isnull(struct_x) && !isnull(struct_y) && !isnull(struct_z)
//* Directions *//
/**
* Set level in dir, breaking the link whatever level was there previously had with us,
* and adding a link for the new level to us.
*
* TODO: what happens if this is targeting an ID that is currently unloaded?
*/
/datum/map_level/proc/connect_level_in_dir(dir, datum/map_level/level_or_id, skip_loaded_rebuild)
var/datum/map_level/existing_level = get_level_in_dir(dir)
var/datum/map_level/new_level = istext(level_or_id) ? SSmapping.keyed_levels[level_or_id] : level_or_id
if(existing_level == new_level)
return
if(existing_level.get_level_in_dir(turn(dir, 180)) == src)
existing_level.set_level_in_dir(turn(dir, 180), null, skip_loaded_rebuild)
set_level_in_dir(dir, level_or_id, skip_loaded_rebuild)
new_level?.set_level_in_dir(turn(dir, 180), src, skip_loaded_rebuild)
/**
* Set level in dir
* * This will not break a link with the previous level from them to us; use [connect_level_in_dir] for that.
*
* TODO: what happens if this is targeting an ID that is currently unloaded?
*/
/datum/map_level/proc/set_level_in_dir(dir, datum/map_level/level_or_id, skip_loaded_rebuild)
var/datum/map_level/new_level = istext(level_or_id) ? SSmapping.keyed_levels[level_or_id] : level_or_id
switch(dir)
if(NORTH)
if(link_north_id == new_level?.id)
return
link_north_id = new_level?.id
if(SOUTH)
if(link_south_id == new_level?.id)
return
link_south_id = new_level?.id
if(EAST)
if(link_east_id == new_level?.id)
return
link_east_id = new_level?.id
if(WEST)
if(link_west_id == new_level?.id)
return
link_west_id = new_level?.id
if(UP)
if(link_above_id == new_level?.id)
return
link_above_id = new_level?.id
if(DOWN)
if(link_below_id == new_level?.id)
return
link_below_id = new_level?.id
else
CRASH("invalid dir passed in")
if(loaded && !skip_loaded_rebuild)
if(dir & (NORTH|SOUTH|EAST|WEST))
rebuild_multiz_horizontal()
else if(dir & (UP|DOWN))
rebuild_multiz_vertical()
/**
* get level datum in dir
*
* * This is authoritative, and is used to rebuild SSmapping's caches.
* * If diagonal, only returns a level if both steps are consistent with each other.
* * This will only return a level if it's currently loaded.
*/
/datum/map_level/proc/get_level_in_dir(dir) as /datum/map_level
if(dir & (dir - 1))
if(dir & (UP|DOWN))
CRASH("unsupported operation of attempting to grab a vertical + diagonal direction.")
switch(dir)
if(NORTHWEST)
if(NORTHEAST)
if(SOUTHWEST)
if(SOUTHEAST)
else
CRASH("invalid dir: [dir]")
var/d1 = NSCOMPONENT(dir)
var/d2 = EWCOMPONENT(dir)
var/datum/map_level/l1 = get_level_in_dir(d1)
l1 = l1?.get_level_in_dir(d2)
var/datum/map_level/l2 = get_level_in_dir(d2)
l2 = l2?.get_level_in_dir(d1)
// if one side is null, we listen to the other
if(isnull(l1))
return l2
if(isnull(l2))
return l1
// if both sides are not null, we require agreement between the two
return (l1 == l2)? l1 : null
switch(dir)
if(NORTH)
return SSmapping.keyed_levels[link_north_id]
if(SOUTH)
return SSmapping.keyed_levels[link_south_id]
if(EAST)
return SSmapping.keyed_levels[link_east_id]
if(WEST)
return SSmapping.keyed_levels[link_west_id]
if(UP)
return SSmapping.keyed_levels[link_above_id]
if(DOWN)
return SSmapping.keyed_levels[link_below_id]
//* Traits *//
/datum/map_level/proc/has_trait(trait)
return trait in traits
/datum/map_level/proc/add_trait(trait)
if(has_trait(trait))
return
LAZYDISTINCTADD(traits, trait)
if(loaded)
SSmapping.on_level_trait_add(src, trait)
/datum/map_level/proc/remove_trait(trait)
if(!has_trait(trait))
return
LAZYREMOVE(traits, trait)
if(loaded)
SSmapping.on_level_trait_del(src, trait)
//* Attributes *//
/datum/map_level/proc/get_attribute(attribute)
return attributes?[attribute]
/datum/map_level/proc/set_attribute(attribute, value)
var/old = get_attribute(attribute)
LAZYSET(attributes, attribute, value)
if(loaded)
SSmapping.on_level_attribute_set(src, attribute, old, value)
/datum/map_level/proc/unset_attribute(attribute)
var/old = get_attribute(attribute)
LAZYREMOVE(attributes, attribute)
if(loaded)
SSmapping.on_level_attribute_set(src, attribute, old, null)
//* Rebuilds / Transitions *//
/**
* expensive as hell, rebuild all dirs
* * use after loading into an existing map that didn't have us prior
* * will block / sleep!
*
* @params
* * reciprocal_if_linked - order adjacent levels to rebuild towards us if they are linked to us
*/
/datum/map_level/proc/rebuild_multiz(reciprocal_if_linked)
rebuild_multiz_horizontal()
rebuild_multiz_vertical()
for(var/dir in list(NORTH, SOUTH, EAST, WEST, UP, DOWN))
var/datum/map_level/partner = get_level_in_dir(dir)
if(partner?.get_level_in_dir(turn(dir, 180)) == src)
if(dir & (NORTH|SOUTH|EAST|WEST))
partner.rebuild_multiz_horizontal()
else
partner.rebuild_multiz_vertical()
/**
* causes an immediate rebuild in given dir (or all if none specified)
* * multiple dir bits is allowed
* * dir must be vertical if specified
* * will block / sleep!
*
* TODO: this proc is a lie, it doesn't actually touch zmimic; that's fine for now, though
*/
/datum/map_level/proc/rebuild_multiz_vertical()
// internally rebuilds are coalesced if two happen at the same time,
// but we still want to track it to see if we're calling this unnecessarily.
multiz_vertical_rebuild_count++
if(multiz_vertical_rebuild_queued)
return
multiz_vertical_rebuild_queued = TRUE
UNTIL(!multiz_vertical_rebuild_mutex)
multiz_vertical_rebuild_queued = FALSE
multiz_vertical_rebuild_mutex = TRUE
rebuild_multiz_vertical_impl()
multiz_vertical_rebuild_mutex = FALSE
/datum/map_level/proc/rebuild_multiz_vertical_impl()
PROTECTED_PROC(TRUE)
for(var/turf/T as anything in level_turfs())
T.update_multiz()
CHECK_TICK
/**
* causes an immediate rebuild in given dir (or all if none specified)
* * multiple dir bits is allowed
* * dir must be horizontal if specified
* * will block / sleep!
*/
/datum/map_level/proc/rebuild_multiz_horizontal()
// internally rebuilds are coalesced if two happen at the same time,
// but we still want to track it to see if we're calling this unnecessarily.
multiz_horizontal_rebuild_count++
if(multiz_horizontal_rebuild_queued)
return
multiz_horizontal_rebuild_queued = TRUE
UNTIL(!multiz_horizontal_rebuild_mutex)
multiz_horizontal_rebuild_queued = FALSE
multiz_horizontal_rebuild_mutex = TRUE
rebuild_multiz_horizontal_impl()
multiz_horizontal_rebuild_mutex = FALSE
/datum/map_level/proc/rebuild_multiz_horizontal_impl()
PROTECTED_PROC(TRUE)
switch(transition)
// do nothing
if(Z_TRANSITION_DISABLED)
for(var/turf/T as anything in transition_turfs())
T._dispose_transition_border()
CHECK_TICK
// just obey the link ids
if(Z_TRANSITION_FORCED, Z_TRANSITION_DEFAULT, Z_TRANSITION_INVISIBLE)
var/visible = transition != Z_TRANSITION_INVISIBLE
// cardinals
if(!isnull(link_south_id))
for(var/turf/T as anything in transition_turfs(SOUTH))
T._make_transition_border(SOUTH, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(SOUTH))
T._dispose_transition_border()
CHECK_TICK
if(!isnull(link_north_id))
for(var/turf/T as anything in transition_turfs(NORTH))
T._make_transition_border(NORTH, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(NORTH))
T._dispose_transition_border()
CHECK_TICK
if(!isnull(link_east_id))
for(var/turf/T as anything in transition_turfs(EAST))
T._make_transition_border(EAST, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(EAST))
T._dispose_transition_border()
CHECK_TICK
if(!isnull(link_west_id))
for(var/turf/T as anything in transition_turfs(WEST))
T._make_transition_border(WEST, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(WEST))
T._dispose_transition_border()
CHECK_TICK
// diagonals
var/datum/map_level/resolved
resolved = get_level_in_dir(NORTHWEST)
if(!isnull(resolved))
for(var/turf/T as anything in transition_turfs(NORTHWEST))
T._make_transition_border(NORTHWEST, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(NORTHWEST))
T._dispose_transition_border()
CHECK_TICK
resolved = get_level_in_dir(NORTHEAST)
if(!isnull(resolved))
for(var/turf/T as anything in transition_turfs(NORTHEAST))
T._make_transition_border(NORTHEAST, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(NORTHEAST))
T._dispose_transition_border()
CHECK_TICK
resolved = get_level_in_dir(SOUTHWEST)
if(!isnull(resolved))
for(var/turf/T as anything in transition_turfs(SOUTHWEST))
T._make_transition_border(SOUTHWEST, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(SOUTHWEST))
T._dispose_transition_border()
CHECK_TICK
resolved = get_level_in_dir(SOUTHEAST)
if(!isnull(resolved))
for(var/turf/T as anything in transition_turfs(SOUTHEAST))
T._make_transition_border(SOUTHEAST, visible)
CHECK_TICK
else
for(var/turf/T as anything in transition_turfs(SOUTHEAST))
T._dispose_transition_border()
CHECK_TICK
//* Turf Fetch *//
/**
* get transition turfs
*
* * there is no overlap between any of this proc's outputs' for a given unique input.
* what this means is NORTH will not overlap with NORTHEAST. the only exception is when
* no dir is provided.
*
* @params
* * dir - direction; if null, we grab all, including diagonals
*/
/datum/map_level/proc/transition_turfs(dir)
switch(dir)
if(null)
. = (
block(
locate(LEVEL_BORDER_WIDTH, LEVEL_BORDER_WIDTH, z_index),
locate(world.maxx - (LEVEL_BORDER_WIDTH - 1), LEVEL_BORDER_WIDTH, z_index),
) + \
block(
locate(LEVEL_BORDER_WIDTH, world.maxy - (LEVEL_BORDER_WIDTH - 1), z_index),
locate(world.maxx - (LEVEL_BORDER_WIDTH - 1), world.maxy - (LEVEL_BORDER_WIDTH - 1), z_index),
) + \
block(
locate(LEVEL_BORDER_WIDTH, LEVEL_BORDER_WIDTH + 1, z_index),
locate(LEVEL_BORDER_WIDTH, world.maxy - (LEVEL_BORDER_WIDTH + 1), z_index),
) + \
block(
locate(world.maxx - (LEVEL_BORDER_WIDTH - 1), LEVEL_BORDER_WIDTH + 1, z_index),
locate(world.maxx - (LEVEL_BORDER_WIDTH - 1), world.maxy - (LEVEL_BORDER_WIDTH + 1), z_index),
)
)
if(NORTH)
. = block(locate(LEVEL_BORDER_WIDTH + 1, world.maxy, z_index), locate(world.maxx - LEVEL_BORDER_WIDTH, world.maxy, z_index))
if(SOUTH)
. = block(locate(LEVEL_BORDER_WIDTH + 1, LEVEL_BORDER_WIDTH, z_index), locate(world.maxx - LEVEL_BORDER_WIDTH, LEVEL_BORDER_WIDTH, z_index))
if(EAST)
. = block(locate(world.maxx, LEVEL_BORDER_WIDTH + 1, z_index), locate(world.maxx, world.maxy - LEVEL_BORDER_WIDTH, z_index))
if(WEST)
. = block(locate(LEVEL_BORDER_WIDTH, LEVEL_BORDER_WIDTH + 1, z_index), locate(LEVEL_BORDER_WIDTH, world.maxy - LEVEL_BORDER_WIDTH, z_index))
if(NORTHEAST)
. = list(locate(world.maxx, world.maxy, z_index))
if(NORTHWEST)
. = list(locate(LEVEL_BORDER_WIDTH, world.maxy, z_index))
if(SOUTHEAST)
. = list(locate(world.maxx, LEVEL_BORDER_WIDTH, z_index))
if(SOUTHWEST)
. = list(locate(LEVEL_BORDER_WIDTH, LEVEL_BORDER_WIDTH, z_index))
else
CRASH("what?")
if(transition_trampling)
return
var/list/transformed = list()
for(var/turf/level_border/border in .)
transformed += border
return transformed
/**
* get all turfs
*/
/datum/map_level/proc/level_turfs()
return Z_TURFS(z_index)