Files
2026-07-12 20:45:26 +00:00

278 lines
11 KiB
Plaintext

SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = INIT_ORDER_AWAY_MAPS
flags = SS_NO_FIRE
var/list/map_templates = list()
var/list/space_ruins_templates = list()
var/list/exoplanet_ruins_templates = list()
var/list/away_sites_templates = list()
var/list/submaps = list()
/// Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
var/list/unused_turfs = list()
/// List of actual datum/turf_reservations - reservation-centric
var/list/turf_reservations = list()
/// List of turfs that are currently reserved and in use - turf-centric, used to get the reservations from turfs
var/list/used_turfs = list()
var/list/reservation_ready = list()
var/clearing_reserved_turfs = FALSE
var/num_of_res_levels = 0
/// List of z level (as number) -> list of all z levels vertically connected to ours
/// Useful for fast grouping lookups and such
var/list/z_level_to_stack = 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 = list()
///list of all z level indices that form multiz connections and whether theyre linked up or down.
///list of lists, inner lists are of the form: list("up or down link direction" = TRUE)
var/list/multiz_levels = list()
/// list of traits and their associated z leves
var/list/z_trait_levels = list()
/// True when in the process of adding a new Z-level, global locking
var/adding_new_zlevel = FALSE
/datum/controller/subsystem/mapping/Initialize(timeofday)
//If we're in fastboot and not spawning exoplanets or awaysites
//this is different from TG and Bay, which always preload, but it saves a lot of time for us
//so we'll do it this way and hope for the best
if(!GLOB.config.fastboot || GLOB.config.exoplanets["enable_loading"] || GLOB.config.awaysites["enable_loading"])
// Load templates and build away sites.
preloadTemplates()
SSatlas.current_map.build_away_sites()
SSatlas.current_map.build_exoplanets()
return SS_INIT_SUCCESS
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT
map_templates = SSmapping.map_templates
space_ruins_templates = SSmapping.space_ruins_templates
exoplanet_ruins_templates = SSmapping.exoplanet_ruins_templates
away_sites_templates = SSmapping.away_sites_templates
unused_turfs = SSmapping.unused_turfs
turf_reservations = SSmapping.turf_reservations
used_turfs = SSmapping.used_turfs
reservation_ready = SSmapping.reservation_ready
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
num_of_res_levels = SSmapping.num_of_res_levels
/datum/controller/subsystem/mapping/proc/preloadTemplates(path = "maps/templates/") //see master controller setup
var/list/filelist = flist(path)
for(var/map in filelist)
var/datum/map_template/T = new(path = "[path][map]", rename = "[map]")
map_templates[T.id] = T
preloadBlacklistableTemplates()
/datum/controller/subsystem/mapping/proc/preloadBlacklistableTemplates()
// Still supporting bans by filename
var/list/banned_exoplanet_dmms = generateMapList("config/exoplanet_ruin_blacklist.txt")
var/list/banned_space_dmms = generateMapList("config/space_ruin_blacklist.txt")
var/list/banned_away_site_dmms = generateMapList("config/away_site_blacklist.txt")
if (!banned_exoplanet_dmms || !banned_space_dmms || !banned_away_site_dmms)
log_admin("One or more map blacklist files are not present in the config directory!")
var/list/banned_maps = list() + banned_exoplanet_dmms + banned_space_dmms + banned_away_site_dmms
for(var/item in sortList(subtypesof(/datum/map_template), GLOBAL_PROC_REF(cmp_ruincost_priority)))
var/datum/map_template/map_template_type = item
// screen out the abstract subtypes
if(!initial(map_template_type.id))
continue
var/datum/map_template/MT = new map_template_type()
if (banned_maps)
if(banned_maps.Find(MT.mappath))
continue
map_templates[MT.id] = MT
// This is nasty..
if(istype(MT, /datum/map_template/ruin/exoplanet))
exoplanet_ruins_templates[MT.id] = MT
else if(istype(MT, /datum/map_template/ruin/space))
space_ruins_templates[MT.id] = MT
else if(istype(MT, /datum/map_template/ruin/away_site))
away_sites_templates[MT.id] = MT
/datum/controller/subsystem/mapping/proc/generate_linkages_for_z_level(z_level)
if(!isnum(z_level) || z_level <= 0)
return FALSE
if(multiz_levels.len < z_level)
multiz_levels.len = z_level
var/z_above = level_trait(z_level, ZTRAIT_UP)
var/z_below = level_trait(z_level, ZTRAIT_DOWN)
if(!(z_above == TRUE || z_above == FALSE || z_above == null) || !(z_below == TRUE || z_below == FALSE || z_below == null))
stack_trace("Warning, numeric mapping offsets are deprecated. Instead, mark z level connections by setting UP/DOWN to true if the connection is allowed")
multiz_levels[z_level] = new /list(LARGEST_Z_LEVEL_INDEX)
multiz_levels[z_level][Z_LEVEL_UP] = !!z_above
multiz_levels[z_level][Z_LEVEL_DOWN] = !!z_below
/// 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
z_level_to_stack.len += 1
// Bare minimum we have ourselves
z_level_to_stack[z_value] = list(z_value)
/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T)
RETURN_TYPE(/datum/turf_reservation)
return used_turfs[T]
/// 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), contain_turfs = FALSE)
/// 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/min_x = SHUTTLE_TRANSIT_BORDER
var/min_y = SHUTTLE_TRANSIT_BORDER
var/max_x = world.maxx - SHUTTLE_TRANSIT_BORDER
var/max_y = world.maxy - SHUTTLE_TRANSIT_BORDER
if(min_x > max_x || min_y > max_y)
clearing_reserved_turfs = FALSE
CRASH("World bounds are too small for dynamic reservations with SHUTTLE_TRANSIT_BORDER [SHUTTLE_TRANSIT_BORDER].")
var/list/reserved_block = block(locate(min_x, min_y, z), locate(max_x, max_y, z))
var/list/prepared_block = list()
var/area/space/space_area = locate(/area/space)
for(var/turf/T as anything in reserved_block)
var/turf/reservation_turf = reset_turf_for_reservation(T, space_area)
if(!reservation_turf)
continue
reservation_turf.turf_flags = (reservation_turf.turf_flags | UNUSED_RESERVATION_TURF) & ~RESERVATION_TURF
prepared_block += 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]"] = prepared_block
reservation_ready["[z]"] = TRUE
clearing_reserved_turfs = FALSE
/// 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(!clearing_reserved_turfs)
var/datum/turf_reservation/reserve = new reservation_type
if(!isnull(turf_type_override))
reserve.turf_type = turf_type_override
if(!z_reservation)
for(var/reserved_z in levels_by_trait(ZTRAIT_RESERVED))
if(!reservation_ready["[reserved_z]"])
continue
if(reserve.reserve(width, height, z_size, reserved_z))
return reserve
// If we didn't return at this point, theres a good chance we ran out of room on the existing reserved z levels, so lets try a new one
var/datum/space_level/newReserved = add_reservation_zlevel()
initialize_reserved_level(newReserved.z_value)
if(reserve.reserve(width, height, z_size, newReserved.z_value))
return reserve
else
if(!level_trait(z_reservation, ZTRAIT_RESERVED))
qdel(reserve)
return
if(!reservation_ready["[z_reservation]"])
initialize_reserved_level(z_reservation)
if(reserve.reserve(width, height, z_size, z_reservation))
return reserve
QDEL_NULL(reserve)
/// 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)
if(!islist(turfs) || !length(turfs))
return
if(await)
return reset_turf_reservation_list(turfs)
INVOKE_ASYNC(src, PROC_REF(reset_turf_reservation_list), turfs)
/datum/controller/subsystem/mapping/proc/reset_turf_reservation_list(list/turfs)
var/area/space/space_area = locate(/area/space)
while(length(turfs))
var/turf/T = turfs[length(turfs)]
turfs.len--
if(!istype(T))
continue
var/turf/reservation_turf = reset_turf_for_reservation(T, space_area)
if(!reservation_turf)
continue
reservation_turf.turf_flags = (reservation_turf.turf_flags | UNUSED_RESERVATION_TURF) & ~RESERVATION_TURF
LAZYINITLIST(unused_turfs["[reservation_turf.z]"])
unused_turfs["[reservation_turf.z]"] |= reservation_turf
CHECK_TICK
/datum/controller/subsystem/mapping/proc/reset_turf_for_reservation(turf/T, area/target_area)
RETURN_TYPE(/turf)
if(!istype(T))
return
if(T.type != RESERVED_TURF_TYPE)
T = T.ChangeTurf(RESERVED_TURF_TYPE, TRUE, FALSE, TRUE)
if(target_area && T.loc != target_area)
T.change_area(T.loc, target_area)
T.baseturf = RESERVED_TURF_TYPE
T.blocks_air = TRUE
return T
/proc/generateMapList(filename)
var/list/potentialMaps = list()
var/list/Lines = world.file2list(filename)
if(!Lines.len)
return
for (var/t in Lines)
if (!t)
continue
t = trim(t)
if (length(t) == 0)
continue
else if (copytext(t, 1, 2) == "#")
continue
var/pos = findtext(t, " ")
var/name = null
if (pos)
name = lowertext(copytext(t, 1, pos))
else
name = lowertext(t)
if (!name)
continue
potentialMaps.Add(t)
return potentialMaps