mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-16 13:06:46 +01:00
711 lines
24 KiB
Plaintext
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)
|