Files
Bubberstation/code/controllers/subsystem/mapping.dm
SkyratBot 17a7b8f2fb [MIRROR] Starlight Control (Aurora works now, space gas doesn't touch starlight, narsie ending effects) [MDB IGNORE] (#24406)
* Starlight Control (Aurora works now, space gas doesn't touch starlight, narsie ending effects) (#78877)

## About The Pull Request

[Implements a setter for starlight
variables](af34f06b41)

I want to start to modify starlight more, and that means I need a way to
hook into everything that uses it and update it, so we can modify it on
the fly.

This does that, alongside removing space overlays from nearspace (too
many false positives) and making the aurora modify all turfs projecting
starlight, rather then all turfs in an area.

Do still need to figure out handling for the starlight color usage in
turf underlays tho (I gave up, we just keep it static. I'll fix it
someday but the render_relay strategy just doesn't work with its masking
setup)

[Reworks how starlight overlays
work](9da4bc38e2)

Instead of setting color on the overlays directly, we instead store an
object with our current settings in every mob's screen, and
render_target it down onto our overlays.

This lets us update overlay colors VERY trivially. Just need to set
color on the overlay var. Makes modifying starlight a lot cheaper.

It doesn't work on area overlays, because suffering, and it MIGHT induce
extra cost on clients. if it does we can do something about that, we'll
play it by ear

[Removes parallax starlight
coloring.](5f701a1b13)

I'm sorta iffy on the color, the effect can be real oppressive in some
cases, and I'd like to use starlight color for more events in world, and
having it vary can make that looking nice hard.

[Adds some visual effects to narsie being
summoned](a423cfcb2b)

As the rune drawing progresses space (starlight and parallax) go from
normal to greyscale. Then, right about when narsie shows up, starlight
becomes vibrant red.

It's a nice effect. I wanna do more shit like this, I think it'll
improve vibes significantly.
## Why It's Good For The Game

Can't embed it because of github's upload limit, can show a
[link](https://cdn.discordapp.com/attachments/458452245256601615/1160821856358645860/2023-10-08_22-31-22.mp4?ex=65360e99&is=65239999&hm=680e33e4e0026b89e132afc50c04a648a24f869eb662f274a381a5de5c5a36f2&)
for the narsie stuff

Here's
[one](https://cdn.discordapp.com/attachments/326831214667235328/1160813747196141568/2023-10-08_22-34-10.mp4?ex=6536070c&is=6523920c&hm=f8d571d1013da89887f49f3fec99f632251eeeac83085aa7dde97009aee3922f&)
for the aurora too.

This gives us more pretty starlight shit, and the ABILITY to do more
pretty starlight shit. I'm pretty jazzed, and I hope people use this
proc more (keeping in mind that it's pretty hard on the lighting system,
and needs significant delay between changes)
## Changelog

🆑
add: Narsie summoning has had some effects added to space and starlight
del: Removes the link between spacegas color and starlight. It was a
slight bit too vibrant and I think impacted the vibe too wildly to be
incidental.
fix: The aurora event actually... works now. Space lights up and all
that
/🆑

* Starlight Control (Aurora works now, space gas doesn't touch starlight, narsie ending effects)

* Update space.dm

* Update shuttles.dm

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
2023-10-18 01:23:18 -04:00

1037 lines
42 KiB
Plaintext

SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = INIT_ORDER_MAPPING
runlevels = ALL
var/list/nuke_tiles = list()
var/list/nuke_threats = list()
var/datum/map_config/config
var/datum/map_config/next_map_config
/// Has the map for the next round been voted for already?
var/map_voted = FALSE
/// Has the map for the next round been deliberately chosen by an admin?
var/map_force_chosen = FALSE
/// Has the map vote been rocked?
var/map_vote_rocked = FALSE
var/list/map_templates = list()
var/list/ruins_templates = list()
///List of ruins, separated by their theme
var/list/themed_ruins = list()
var/datum/space_level/isolated_ruins_z //Created on demand during ruin loading.
var/list/shuttle_templates = list()
var/list/shelter_templates = list()
var/list/holodeck_templates = list()
var/list/areas_in_z = list()
/// List of z level (as number) -> plane offset of that z level
/// Used to maintain the plane cube
var/list/z_level_to_plane_offset = list()
/// 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 z level (as number) -> The lowest plane offset in that z stack
var/list/z_level_to_lowest_plane_offset = list()
// This pair allows for easy conversion between an offset plane, and its true representation
// Both are in the form "input plane" -> output plane(s)
/// Assoc list of string plane values to their true, non offset representation
var/list/plane_offset_to_true
/// Assoc list of true string plane values to a list of all potential offset planess
var/list/true_to_offset_planes
/// Assoc list of string plane to the plane's offset value
var/list/plane_to_offset
/// List of planes that do not allow for offsetting
var/list/plane_offset_blacklist
/// List of render targets that do not allow for offsetting
var/list/render_offset_blacklist
/// List of plane masters that are of critical priority
var/list/critical_planes
/// The largest plane offset we've generated so far
var/max_plane_offset = 0
var/loading_ruins = FALSE
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
///All possible biomes in assoc list as type || instance
var/list/biomes = list()
// Z-manager stuff
var/station_start // should only be used for maploading-related tasks
var/space_levels_so_far = 0
///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 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()
var/datum/space_level/transit
var/datum/space_level/empty_space
var/num_of_res_levels = 1
/// True when in the process of adding a new Z-level, global locking
var/adding_new_zlevel = FALSE
///shows the default gravity value for each z level. recalculated when gravity generators change.
///List in the form: list(z level num = max generator gravity in that z level OR the gravity level trait)
var/list/gravity_by_z_level = list()
/// list of traits and their associated z leves
var/list/z_trait_levels = list()
/// list of lazy templates that have been loaded
var/list/loaded_lazy_templates
/datum/controller/subsystem/mapping/PreInit()
..()
#ifdef FORCE_MAP
config = load_map_config(FORCE_MAP, FORCE_MAP_DIRECTORY)
#else
config = load_map_config(error_if_missing = FALSE)
#endif
/datum/controller/subsystem/mapping/Initialize()
if(initialized)
return SS_INIT_SUCCESS
if(config.defaulted)
var/old_config = config
config = global.config.defaultmap
if(!config || config.defaulted)
to_chat(world, span_boldannounce("Unable to load next or default map config, defaulting to MetaStation."))
config = old_config
plane_offset_to_true = list()
true_to_offset_planes = list()
plane_to_offset = list()
// VERY special cases for FLOAT_PLANE, so it will be treated as expected by plane management logic
// Sorry :(
plane_offset_to_true["[FLOAT_PLANE]"] = FLOAT_PLANE
true_to_offset_planes["[FLOAT_PLANE]"] = list(FLOAT_PLANE)
plane_to_offset["[FLOAT_PLANE]"] = 0
plane_offset_blacklist = list()
// You aren't allowed to offset a floatplane that'll just fuck it all up
plane_offset_blacklist["[FLOAT_PLANE]"] = TRUE
render_offset_blacklist = list()
critical_planes = list()
create_plane_offsets(0, 0)
initialize_biomes()
loadWorld()
determine_fake_sale()
require_area_resort()
process_teleport_locs() //Sets up the wizard teleport locations
preloadTemplates()
#ifndef LOWMEMORYMODE
// Create space ruin levels
while (space_levels_so_far < config.space_ruin_levels)
add_new_zlevel("Ruin Area [space_levels_so_far+1]", ZTRAITS_SPACE)
++space_levels_so_far
// Create empty space levels
while (space_levels_so_far < config.space_empty_levels + config.space_ruin_levels)
empty_space = add_new_zlevel("Empty Area [space_levels_so_far+1]", list(ZTRAIT_LINKAGE = CROSSLINKED))
++space_levels_so_far
// Pick a random away mission.
if(CONFIG_GET(flag/roundstart_away))
createRandomZlevel(prob(CONFIG_GET(number/config_gateway_chance)))
else if (SSmapping.config.load_all_away_missions) // we're likely in a local testing environment, so punch it.
load_all_away_missions()
loading_ruins = TRUE
setup_ruins()
loading_ruins = FALSE
#endif
// Run map generation after ruin generation to prevent issues
run_map_terrain_generation()
// Generate our rivers, we do this here so the map doesn't load on top of them
setup_rivers()
// now that the terrain is generated, including rivers, we can safely populate it with objects and mobs
run_map_terrain_population()
// Add the first transit level
var/datum/space_level/base_transit = add_reservation_zlevel()
require_area_resort()
// Set up Z-level transitions.
setup_map_transitions()
generate_station_area_list()
initialize_reserved_level(base_transit.z_value)
calculate_default_z_level_gravities()
return SS_INIT_SUCCESS
/datum/controller/subsystem/mapping/fire(resumed)
// Cache for sonic speed
var/list/unused_turfs = src.unused_turfs
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/calculate_default_z_level_gravities()
for(var/z_level in 1 to length(z_list))
calculate_z_level_gravity(z_level)
/datum/controller/subsystem/mapping/proc/generate_z_level_linkages()
for(var/z_level in 1 to length(z_list))
generate_linkages_for_z_level(z_level)
/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
/datum/controller/subsystem/mapping/proc/calculate_z_level_gravity(z_level_number)
if(!isnum(z_level_number) || z_level_number < 1)
return FALSE
var/max_gravity = 0
for(var/obj/machinery/gravity_generator/main/grav_gen as anything in GLOB.gravity_generators["[z_level_number]"])
max_gravity = max(grav_gen.setting, max_gravity)
max_gravity = max_gravity || level_trait(z_level_number, ZTRAIT_GRAVITY) || 0//just to make sure no nulls
gravity_by_z_level[z_level_number] = max_gravity
return max_gravity
/**
* ##setup_ruins
*
* Sets up all of the ruins to be spawned
*/
/datum/controller/subsystem/mapping/proc/setup_ruins()
// Generate mining ruins
var/list/lava_ruins = levels_by_trait(ZTRAIT_LAVA_RUINS)
if (lava_ruins.len)
seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), list(/area/lavaland/surface/outdoors/unexplored), themed_ruins[ZTRAIT_LAVA_RUINS], clear_below = TRUE)
var/list/ice_ruins = levels_by_trait(ZTRAIT_ICE_RUINS)
if (ice_ruins.len)
// needs to be whitelisted for underground too so place_below ruins work
seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), themed_ruins[ZTRAIT_ICE_RUINS], clear_below = TRUE)
var/list/ice_ruins_underground = levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND)
if (ice_ruins_underground.len)
seedRuins(ice_ruins_underground, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/underground/unexplored), themed_ruins[ZTRAIT_ICE_RUINS_UNDERGROUND], clear_below = TRUE)
// Generate deep space ruins
var/list/space_ruins = levels_by_trait(ZTRAIT_SPACE_RUINS)
if (space_ruins.len)
// Create a proportional budget by multiplying the amount of space ruin levels in the current map over the default amount
var/proportional_budget = round(CONFIG_GET(number/space_budget) * (space_ruins.len / DEFAULT_SPACE_RUIN_LEVELS))
seedRuins(space_ruins, proportional_budget, list(/area/space), themed_ruins[ZTRAIT_SPACE_RUINS])
/// Sets up rivers, and things that behave like rivers. So lava/plasma rivers, and chasms
/// It is important that this happens AFTER generating mineral walls and such, since we rely on them for river logic
/datum/controller/subsystem/mapping/proc/setup_rivers()
// Generate mining ruins
var/list/lava_ruins = levels_by_trait(ZTRAIT_LAVA_RUINS)
for (var/lava_z in lava_ruins)
spawn_rivers(lava_z, 4, /turf/open/lava/smooth/lava_land_surface, /area/lavaland/surface/outdoors/unexplored)
var/list/ice_ruins = levels_by_trait(ZTRAIT_ICE_RUINS)
for (var/ice_z in ice_ruins)
var/river_type = HAS_TRAIT(SSstation, STATION_TRAIT_FORESTED) ? /turf/open/lava/plasma/ice_moon : /turf/open/openspace/icemoon
spawn_rivers(ice_z, 4, river_type, /area/icemoon/surface/outdoors/unexplored/rivers)
var/list/ice_ruins_underground = levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND)
for (var/ice_z in ice_ruins_underground)
spawn_rivers(ice_z, 4, level_trait(ice_z, ZTRAIT_BASETURF), /area/icemoon/underground/unexplored/rivers)
/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
SSshuttle.transit_requesters.Cut()
message_admins("Clearing dynamic reservation space.")
var/list/obj/docking_port/mobile/in_transit = list()
for(var/i in SSshuttle.transit_docking_ports)
var/obj/docking_port/stationary/transit/T = i
if(!istype(T))
continue
in_transit[T] = T.get_docked()
var/go_ahead = world.time + wipe_safety_delay
if(in_transit.len)
message_admins("Shuttles in transit detected. Attempting to fast travel. Timeout is [wipe_safety_delay/10] seconds.")
var/list/cleared = list()
for(var/i in in_transit)
INVOKE_ASYNC(src, PROC_REF(safety_clear_transit_dock), i, in_transit[i], cleared)
UNTIL((go_ahead < world.time) || (cleared.len == in_transit.len))
do_wipe_turf_reservations()
clearing_reserved_turfs = FALSE
/datum/controller/subsystem/mapping/proc/safety_clear_transit_dock(obj/docking_port/stationary/transit/T, obj/docking_port/mobile/M, list/returning)
M.setTimer(0)
var/error = M.initiate_docking(M.destination, M.preferred_direction)
if(!error)
returning += M
qdel(T, TRUE)
/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T)
RETURN_TYPE(/datum/turf_reservation)
return used_turfs[T]
/* Nuke threats, for making the blue tiles on the station go RED
Used by the AI doomsday and the self-destruct nuke.
*/
/datum/controller/subsystem/mapping/proc/add_nuke_threat(datum/nuke)
nuke_threats[nuke] = TRUE
check_nuke_threats()
/datum/controller/subsystem/mapping/proc/remove_nuke_threat(datum/nuke)
nuke_threats -= nuke
check_nuke_threats()
/datum/controller/subsystem/mapping/proc/check_nuke_threats()
for(var/datum/d in nuke_threats)
if(!istype(d) || QDELETED(d))
nuke_threats -= d
for(var/N in nuke_tiles)
var/turf/open/floor/circuit/C = N
C.update_appearance()
/datum/controller/subsystem/mapping/proc/determine_fake_sale()
if(length(SSmapping.levels_by_all_traits(list(ZTRAIT_STATION, ZTRAIT_NOPARALLAX))))
GLOB.arcade_prize_pool += /obj/item/stack/tile/fakeice/loaded
else
GLOB.arcade_prize_pool += /obj/item/stack/tile/fakespace/loaded
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT
initialized = SSmapping.initialized
map_templates = SSmapping.map_templates
ruins_templates = SSmapping.ruins_templates
for (var/theme in SSmapping.themed_ruins)
themed_ruins[theme] = SSmapping.themed_ruins[theme]
shuttle_templates = SSmapping.shuttle_templates
shelter_templates = SSmapping.shelter_templates
unused_turfs = SSmapping.unused_turfs
turf_reservations = SSmapping.turf_reservations
used_turfs = SSmapping.used_turfs
holodeck_templates = SSmapping.holodeck_templates
areas_in_z = SSmapping.areas_in_z
config = SSmapping.config
next_map_config = SSmapping.next_map_config
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
z_list = SSmapping.z_list
multiz_levels = SSmapping.multiz_levels
loaded_lazy_templates = SSmapping.loaded_lazy_templates
#define INIT_ANNOUNCE(X) to_chat(world, span_boldannounce("[X]")); log_world(X)
/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE)
. = list()
var/start_time = REALTIMEOFDAY
if (!islist(files)) // handle single-level maps
files = list(files)
// check that the total z count of all maps matches the list of traits
var/total_z = 0
var/list/parsed_maps = list()
for (var/file in files)
var/full_path = "_maps/[path]/[file]"
var/datum/parsed_map/pm = new(file(full_path))
var/bounds = pm?.bounds
if (!bounds)
errorList |= full_path
continue
parsed_maps[pm] = total_z // save the start Z of this file
total_z += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1
if (!length(traits)) // null or empty - default
for (var/i in 1 to total_z)
traits += list(default_traits)
else if (total_z != traits.len) // mismatch
INIT_ANNOUNCE("WARNING: [traits.len] trait sets specified for [total_z] z-levels in [path]!")
if (total_z < traits.len) // ignore extra traits
traits.Cut(total_z + 1)
while (total_z > traits.len) // fall back to defaults on extra levels
traits += list(default_traits)
// preload the relevant space_level datums
var/start_z = world.maxz + 1
var/i = 0
for (var/level in traits)
add_new_zlevel("[name][i ? " [i + 1]" : ""]", level, contain_turfs = FALSE)
++i
SSautomapper.preload_templates_from_toml(files) // SKYRAT EDIT ADDITION - We need to load our templates AFTER the Z level exists, otherwise, there is no z level to preload.
var/turf_blacklist = SSautomapper.get_turf_blacklists(files) // SKYRAT EDIT ADDITION - We use blacklisted turfs to carve out places for our templates.
// load the maps
for (var/P in parsed_maps)
var/datum/parsed_map/pm = P
pm.turf_blacklist = turf_blacklist // SKYRAT EDIT ADDITION - apply blacklist
var/bounds = pm.bounds
var/x_offset = bounds ? round(world.maxx / 2 - bounds[MAP_MAXX] / 2) + 1 : 1
var/y_offset = bounds ? round(world.maxy / 2 - bounds[MAP_MAXY] / 2) + 1 : 1
if (!pm.load(x_offset, y_offset, start_z + parsed_maps[P], no_changeturf = TRUE, new_z = TRUE))
errorList |= pm.original_path
// SKYRAT EDIT ADDITION BEGIN - We need to load our templates from cache after our space has been carved out.
if(!LAZYLEN(errorList))
SSautomapper.load_templates_from_cache(files)
// SKYRAT EDIT ADDITION END
if(!silent)
add_startup_message("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") //SKYRAT EDIT CHANGE
return parsed_maps
/datum/controller/subsystem/mapping/proc/loadWorld()
//if any of these fail, something has gone horribly, HORRIBLY, wrong
var/list/FailedZs = list()
// ensure we have space_level datums for compiled-in maps
InitializeDefaultZLevels()
// load the station
station_start = world.maxz + 1
add_startup_message("Loading [config.map_name]...") // SKYRAT EDIT CHANGE
LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION)
if(SSdbcore.Connect())
var/datum/db_query/query_round_map_name = SSdbcore.NewQuery({"
UPDATE [format_table_name("round")] SET map_name = :map_name WHERE id = :round_id
"}, list("map_name" = config.map_name, "round_id" = GLOB.round_id))
query_round_map_name.Execute()
qdel(query_round_map_name)
#ifndef LOWMEMORYMODE
if(config.minetype == "lavaland")
LoadGroup(FailedZs, "Lavaland", "map_files/Mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
else if (!isnull(config.minetype) && config.minetype != "none")
INIT_ANNOUNCE("WARNING: An unknown minetype '[config.minetype]' was set! This is being ignored! Update the maploader code!")
#endif
if(LAZYLEN(FailedZs)) //but seriously, unless the server's filesystem is messed up this will never happen
var/msg = "RED ALERT! The following map files failed to load: [FailedZs[1]]"
if(FailedZs.len > 1)
for(var/I in 2 to FailedZs.len)
msg += ", [FailedZs[I]]"
msg += ". Yell at your server host!"
INIT_ANNOUNCE(msg)
#undef INIT_ANNOUNCE
// Custom maps are removed after station loading so the map files does not persist for no reason.
if(config.map_path == CUSTOM_MAP_PATH)
fdel("_maps/custom/[config.map_file]")
// And as the file is now removed set the next map to default.
next_map_config = load_default_map_config()
/**
* Global list of AREA TYPES that are associated with the station.
*
* This tracks the types of all areas in existence that are a UNIQUE_AREA and are on the station Z.
*
* This does not track the area instances themselves - See [GLOB.areas] for that.
*/
GLOBAL_LIST_EMPTY(the_station_areas)
/// Generates the global station area list, filling it with typepaths of unique areas found on the station Z.
/datum/controller/subsystem/mapping/proc/generate_station_area_list()
for(var/area/station/station_area in GLOB.areas)
if (!(station_area.area_flags & UNIQUE_AREA))
continue
if (is_station_level(station_area.z))
GLOB.the_station_areas += station_area.type
if(!GLOB.the_station_areas.len)
log_world("ERROR: Station areas list failed to generate!")
/// Generate the turfs of the area
/datum/controller/subsystem/mapping/proc/run_map_terrain_generation()
for(var/area/A as anything in GLOB.areas)
A.RunTerrainGeneration()
/// Populate the turfs of the area
/datum/controller/subsystem/mapping/proc/run_map_terrain_population()
for(var/area/A as anything in GLOB.areas)
A.RunTerrainPopulation()
/datum/controller/subsystem/mapping/proc/maprotate()
if(map_voted || SSmapping.next_map_config) //If voted or set by other means.
return
var/players = GLOB.clients.len
var/list/mapvotes = list()
//count votes
var/pmv = CONFIG_GET(flag/preference_map_voting)
if(pmv)
for (var/client/c in GLOB.clients)
var/vote = c.prefs.read_preference(/datum/preference/choiced/preferred_map)
if (!vote)
if (global.config.defaultmap)
mapvotes[global.config.defaultmap.map_name] += 1
continue
mapvotes[vote] += 1
else
for(var/M in global.config.maplist)
mapvotes[M] = 1
//filter votes
for (var/map in mapvotes)
if (!map)
mapvotes.Remove(map)
continue
if (!(map in global.config.maplist))
mapvotes.Remove(map)
continue
if(map in SSpersistence.blocked_maps)
mapvotes.Remove(map)
continue
var/datum/map_config/VM = global.config.maplist[map]
if (!VM)
mapvotes.Remove(map)
continue
if (VM.voteweight <= 0)
mapvotes.Remove(map)
continue
if (VM.config_min_users > 0 && players < VM.config_min_users)
mapvotes.Remove(map)
continue
if (VM.config_max_users > 0 && players > VM.config_max_users)
mapvotes.Remove(map)
continue
if(pmv)
mapvotes[map] = mapvotes[map]*VM.voteweight
var/pickedmap = pick_weight(mapvotes)
if (!pickedmap)
return
var/datum/map_config/VM = global.config.maplist[pickedmap]
message_admins("Randomly rotating map to [VM.map_name]")
. = changemap(VM)
if (. && VM.map_name != config.map_name)
to_chat(world, span_boldannounce("Map rotation has chosen [VM.map_name] for next round!"))
/datum/controller/subsystem/mapping/proc/mapvote()
if(map_voted || SSmapping.next_map_config) //If voted or set by other means.
return
if(SSvote.current_vote) //Theres already a vote running, default to rotation.
maprotate()
return
SSvote.initiate_vote(/datum/vote/map_vote, "automatic map rotation", forced = TRUE)
/datum/controller/subsystem/mapping/proc/changemap(datum/map_config/change_to)
if(!change_to.MakeNextMap())
next_map_config = load_default_map_config()
message_admins("Failed to set new map with next_map.json for [change_to.map_name]! Using default as backup!")
return
var/filter_threshold = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE)
if (change_to.config_min_users > 0 && filter_threshold != 0 && filter_threshold < change_to.config_min_users)
message_admins("[change_to.map_name] was chosen for the next map, despite there being less current players than its set minimum population range!")
log_game("[change_to.map_name] was chosen for the next map, despite there being less current players than its set minimum population range!")
if (change_to.config_max_users > 0 && filter_threshold > change_to.config_max_users)
message_admins("[change_to.map_name] was chosen for the next map, despite there being more current players than its set maximum population range!")
log_game("[change_to.map_name] was chosen for the next map, despite there being more current players than its set maximum population range!")
next_map_config = change_to
return TRUE
/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.name] = T
preloadRuinTemplates()
preloadShuttleTemplates()
preloadShelterTemplates()
preloadHolodeckTemplates()
/datum/controller/subsystem/mapping/proc/preloadRuinTemplates()
// Still supporting bans by filename
var/list/banned = generateMapList("spaceruinblacklist.txt")
if(config.minetype == "lavaland")
banned += generateMapList("lavaruinblacklist.txt")
else if(config.blacklist_file)
banned += generateMapList(config.blacklist_file)
for(var/item in sort_list(subtypesof(/datum/map_template/ruin), GLOBAL_PROC_REF(cmp_ruincost_priority)))
var/datum/map_template/ruin/ruin_type = item
// screen out the abstract subtypes
if(!initial(ruin_type.id))
continue
var/datum/map_template/ruin/R = new ruin_type()
if(banned.Find(R.mappath))
continue
map_templates[R.name] = R
ruins_templates[R.name] = R
if (!(R.ruin_type in themed_ruins))
themed_ruins[R.ruin_type] = list()
themed_ruins[R.ruin_type][R.name] = R
/datum/controller/subsystem/mapping/proc/preloadShuttleTemplates()
var/list/unbuyable = generateMapList("unbuyableshuttles.txt")
for(var/item in subtypesof(/datum/map_template/shuttle))
var/datum/map_template/shuttle/shuttle_type = item
if(!(initial(shuttle_type.suffix)))
continue
var/datum/map_template/shuttle/S = new shuttle_type()
if(unbuyable.Find(S.mappath))
S.who_can_purchase = null
shuttle_templates[S.shuttle_id] = S
map_templates[S.shuttle_id] = S
/datum/controller/subsystem/mapping/proc/preloadShelterTemplates()
for(var/item in subtypesof(/datum/map_template/shelter))
var/datum/map_template/shelter/shelter_type = item
if(!(initial(shelter_type.mappath)))
continue
var/datum/map_template/shelter/S = new shelter_type()
shelter_templates[S.shelter_id] = S
map_templates[S.shelter_id] = S
/datum/controller/subsystem/mapping/proc/preloadHolodeckTemplates()
for(var/item in subtypesof(/datum/map_template/holodeck))
var/datum/map_template/holodeck/holodeck_type = item
if(!(initial(holodeck_type.mappath)))
continue
var/datum/map_template/holodeck/holo_template = new holodeck_type()
holodeck_templates[holo_template.template_id] = holo_template
//Manual loading of away missions.
/client/proc/admin_away()
set name = "Load Away Mission"
set category = "Admin.Events"
if(!holder || !check_rights(R_FUN))
return
if(!GLOB.the_gateway)
if(tgui_alert(usr, "There's no home gateway on the station. You sure you want to continue ?", "Uh oh", list("Yes", "No")) != "Yes")
return
var/list/possible_options = GLOB.potentialRandomZlevels + "Custom"
var/away_name
var/datum/space_level/away_level
var/secret = FALSE
if(tgui_alert(usr, "Do you want your mission secret? (This will prevent ghosts from looking at your map in any way other than through a living player's eyes.)", "Are you $$$ekret?", list("Yes", "No")) == "Yes")
secret = TRUE
var/answer = input("What kind?","Away") as null|anything in possible_options
switch(answer)
if("Custom")
var/mapfile = input("Pick file:", "File") as null|file
if(!mapfile)
return
away_name = "[mapfile] custom"
to_chat(usr,span_notice("Loading [away_name]..."))
var/datum/map_template/template = new(mapfile, "Away Mission")
away_level = template.load_new_z(secret)
else
if(answer in GLOB.potentialRandomZlevels)
away_name = answer
to_chat(usr,span_notice("Loading [away_name]..."))
var/datum/map_template/template = new(away_name, "Away Mission")
away_level = template.load_new_z(secret)
else
return
message_admins("Admin [key_name_admin(usr)] has loaded [away_name] away mission.")
log_admin("Admin [key_name(usr)] has loaded [away_name] away mission.")
if(!away_level)
message_admins("Loading [away_name] failed!")
return
/// 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_reservation)
for(var/i in levels_by_trait(ZTRAIT_RESERVED))
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
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
else
if(reserve.reserve(width, height, z_size, z_reservation))
return reserve
QDEL_NULL(reserve)
///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(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/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
/// 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
if(!QDELETED(TR))
qdel(TR, TRUE)
UNSETEMPTY(turf_reservations)
var/list/clearing = list()
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, await = TRUE)
///Initialize all biomes, assoc as type || instance
/datum/controller/subsystem/mapping/proc/initialize_biomes()
for(var/biome_path in subtypesof(/datum/biome))
var/datum/biome/biome_instance = new biome_path()
biomes[biome_path] += biome_instance
/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()
/datum/controller/subsystem/mapping/proc/get_isolated_ruin_z()
if(!isolated_ruins_z)
isolated_ruins_z = add_new_zlevel("Isolated Ruins/Reserved", list(ZTRAIT_RESERVED = TRUE, ZTRAIT_ISOLATED_RUINS = TRUE))
initialize_reserved_level(isolated_ruins_z.z_value)
return isolated_ruins_z.z_value
/// 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
// We are guarenteed that we'll always grow bottom up
// Suck it jannies
z_level_to_plane_offset.len += 1
z_level_to_lowest_plane_offset.len += 1
gravity_by_z_level.len += 1
z_level_to_stack.len += 1
// Bare minimum we have ourselves
z_level_to_stack[z_value] = list(z_value)
// 0's the default value, we'll update it later if required
z_level_to_plane_offset[z_value] = 0
z_level_to_lowest_plane_offset[z_value] = 0
// Now we check if this plane is offset or not
var/below_offset = new_z.traits[ZTRAIT_DOWN]
if(below_offset)
update_plane_tracking(new_z)
if(contain_turfs)
build_area_turfs(z_value, filled_with_space)
// And finally, misc global generation
// We'll have to update this if offsets change, because we load lowest z to highest z
generate_lighting_appearance_by_z(z_value)
/datum/controller/subsystem/mapping/proc/build_area_turfs(z_level, space_guaranteed)
// If we know this is filled with default tiles, we can use the default area
// Faster
if(space_guaranteed)
var/area/global_area = GLOB.areas_by_type[world.area]
global_area.contained_turfs += Z_TURFS(z_level)
return
for(var/turf/to_contain as anything in Z_TURFS(z_level))
var/area/our_area = to_contain.loc
our_area.contained_turfs += to_contain
/datum/controller/subsystem/mapping/proc/update_plane_tracking(datum/space_level/update_with)
// We're essentially going to walk down the stack of connected z levels, and set their plane offset as we go
var/plane_offset = 0
var/datum/space_level/current_z = update_with
var/list/datum/space_level/levels_checked = list()
var/list/z_stack = list()
while(TRUE)
var/z_level = current_z.z_value
z_stack += z_level
z_level_to_plane_offset[z_level] = plane_offset
levels_checked += current_z
if(!current_z.traits[ZTRAIT_DOWN]) // If there's nothing below, stop looking
break
// Otherwise, down down down we go
current_z = z_list[z_level - 1]
plane_offset += 1
/// Updates the lowest offset value
for(var/datum/space_level/level_to_update in levels_checked)
z_level_to_lowest_plane_offset[level_to_update.z_value] = plane_offset
z_level_to_stack[level_to_update.z_value] = z_stack
// This can be affected by offsets, so we need to update it
// PAIN
for(var/i in 1 to length(z_list))
generate_lighting_appearance_by_z(i)
var/old_max = max_plane_offset
max_plane_offset = max(max_plane_offset, plane_offset)
if(max_plane_offset == old_max)
return
generate_offset_lists(old_max + 1, max_plane_offset)
SEND_SIGNAL(src, COMSIG_PLANE_OFFSET_INCREASE, old_max, max_plane_offset)
// Sanity check
if(max_plane_offset > MAX_EXPECTED_Z_DEPTH)
stack_trace("We've loaded a map deeper then the max expected z depth. Preferences won't cover visually disabling all of it!")
/// Takes an offset to generate misc lists to, and a base to start from
/// Use this to react globally to maintain parity with plane offsets
/datum/controller/subsystem/mapping/proc/generate_offset_lists(gen_from, new_offset)
create_plane_offsets(gen_from, new_offset)
for(var/offset in gen_from to new_offset)
GLOB.starlight_objects += starlight_object(offset)
GLOB.starlight_overlays += starlight_overlay(offset)
for(var/datum/gas/gas_type as anything in GLOB.meta_gas_info)
var/list/gas_info = GLOB.meta_gas_info[gas_type]
if(initial(gas_type.moles_visible) != null)
gas_info[META_GAS_OVERLAY] += generate_gas_overlays(gen_from, new_offset, gas_type)
/datum/controller/subsystem/mapping/proc/create_plane_offsets(gen_from, new_offset)
for(var/plane_offset in gen_from to new_offset)
for(var/atom/movable/screen/plane_master/master_type as anything in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/rendering_plate)
var/plane_to_use = initial(master_type.plane)
var/string_real = "[plane_to_use]"
var/offset_plane = GET_NEW_PLANE(plane_to_use, plane_offset)
var/string_plane = "[offset_plane]"
if(!initial(master_type.allows_offsetting))
plane_offset_blacklist[string_plane] = TRUE
var/render_target = initial(master_type.render_target)
if(!render_target)
render_target = get_plane_master_render_base(initial(master_type.name))
render_offset_blacklist[render_target] = TRUE
if(plane_offset != 0)
continue
if(initial(master_type.critical) & PLANE_CRITICAL_DISPLAY)
critical_planes[string_plane] = TRUE
plane_offset_to_true[string_plane] = plane_to_use
plane_to_offset[string_plane] = plane_offset
if(!true_to_offset_planes[string_real])
true_to_offset_planes[string_real] = list()
true_to_offset_planes[string_real] |= offset_plane
/// Takes a turf or a z level, and returns a list of all the z levels that are connected to it
/datum/controller/subsystem/mapping/proc/get_connected_levels(turf/connected)
var/z_level = connected
if(isturf(z_level))
z_level = connected.z
return z_level_to_stack[z_level]
/datum/controller/subsystem/mapping/proc/lazy_load_template(template_key, force = FALSE)
RETURN_TYPE(/datum/turf_reservation)
var/static/lazy_loading = FALSE
UNTIL(!lazy_loading)
lazy_loading = TRUE
. = _lazy_load_template(template_key, force)
lazy_loading = FALSE
return .
/datum/controller/subsystem/mapping/proc/_lazy_load_template(template_key, force = FALSE)
PRIVATE_PROC(TRUE)
if(LAZYACCESS(loaded_lazy_templates, template_key) && !force)
var/datum/lazy_template/template = GLOB.lazy_templates[template_key]
return template.reservations[1]
LAZYSET(loaded_lazy_templates, template_key, TRUE)
var/datum/lazy_template/target = GLOB.lazy_templates[template_key]
if(!target)
CRASH("Attempted to lazy load a template key that does not exist: '[template_key]'")
return target.lazy_load()
/proc/generate_lighting_appearance_by_z(z_level)
if(length(GLOB.default_lighting_underlays_by_z) < z_level)
GLOB.default_lighting_underlays_by_z.len = z_level
GLOB.default_lighting_underlays_by_z[z_level] = mutable_appearance(LIGHTING_ICON, "transparent", z_level * 0.01, null, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM, offset_const = GET_Z_PLANE_OFFSET(z_level))
/// Returns true if the map we're playing on is on a planet
/datum/controller/subsystem/mapping/proc/is_planetary()
return config.planetary
/// For debug purposes, will add every single away mission present in a given directory.
/// You can optionally pass in a string directory to load from instead of the default.
/datum/controller/subsystem/mapping/proc/load_all_away_missions(map_directory)
if(!map_directory)
map_directory = "_maps/RandomZLevels/"
var/start_time = null // in case we're doing this at runtime, useful to know how much time we're spending loading all these away missions
var/confirmation_alert_result = null
var/new_wait = 0 // default to always zeroing out the wait time for away missions to be unlocked due to the unit-testery nature of this map
if(IsAdminAdvancedProcCall())
if(!check_rights(R_DEBUG))
return
var/confirmation_string = "This will load every single away mission in the [map_directory] directory. This might cause a bit of lag that can only be cleared on a world restart. Are you sure you want to do this?"
confirmation_alert_result = tgui_alert(usr, confirmation_string, "DEBUG ONLY!!!", list("Yes", "Cancel"))
if(confirmation_alert_result != "Yes")
return
var/current_wait_time = CONFIG_GET(number/gateway_delay)
switch(tgui_alert(usr, "Do you want to zero out the cooldown for access to these maps? Currently [DisplayTimeText(current_wait_time)]", "OH FUCK!!!", list("Yes", "No", "Cancel")))
if("No")
new_wait = current_wait_time
if("Cancel")
return
else
start_time = REALTIMEOFDAY
var/beginning_message = "Loading all away missions..."
to_chat(world, span_boldannounce(beginning_message))
log_world(beginning_message)
log_mapping(beginning_message)
var/list/all_away_missions = generate_map_list_from_directory(map_directory)
var/number_of_away_missions = length(all_away_missions)
for(var/entry in all_away_missions)
load_new_z_level(entry, entry, secret = FALSE) // entry in both fields so we know if something failed to load since it'll log the full file name of what was loaded.
for(var/datum/gateway_destination/away_datum in GLOB.gateway_destinations)
away_datum.wait = new_wait
log_mapping("Now loading [away_datum.name]...")
validate_z_level_loading(all_away_missions)
if(!isnull(start_time))
var/tracked_time = (REALTIMEOFDAY - start_time) / 10
var/finished_message = "Loaded [number_of_away_missions] away missions in [tracked_time] second[tracked_time == 1 ? "" : "s"]!"
to_chat(world, span_boldannounce(finished_message))
log_world(finished_message)
log_mapping(finished_message)
if(isnull(confirmation_alert_result))
log_mapping("All away missions have been loaded. List of away missions paired to corresponding Z-Levels are as follows:")
log_mapping(gather_z_level_information())
return
message_admins("[key_name_admin(usr)] has loaded every single away mission in the [map_directory] directory. [ADMIN_SEE_ZLEVEL_LAYOUT]")
log_game("[key_name(usr)] has loaded every single away mission in the [map_directory] directory.")
/// Lightweight proc that just checks to make sure that all of the expected z-levels were loaded. Split out for clarity from load_all_away_missions()
/// Argument "checkable_levels" is just a list of the names (typically the filepaths) of the z-levels we were expected to load, which should correspond to the name on the space level datum.
/datum/controller/subsystem/mapping/proc/validate_z_level_loading(list/checkable_levels)
for(var/z in 1 to max(world.maxz, length(z_list)))
var/datum/space_level/level = z_list[z]
if(isnull(level))
continue
var/level_name = level.name
if(level_name in checkable_levels)
checkable_levels -= level_name
continue
var/number_of_remaining_levels = length(checkable_levels)
if(number_of_remaining_levels > 0)
CRASH("The following [number_of_remaining_levels] away mission(s) were not loaded: [checkable_levels.Join("\n")]")