From 17c5a68c3394f85cd6f71a9c3089e7d9ed3e6296 Mon Sep 17 00:00:00 2001 From: Ling Date: Sat, 31 Dec 2022 16:41:47 +0100 Subject: [PATCH] Backports several SSmapping improvements (#17208) * Optimizes SSmapping * Turfs inside area are stored https://github.com/tgstation/tgstation/pull/70966 * Add async reserving of turfs * Fix bug * Fix shuttle init --- code/__DEFINES/maths.dm | 2 + code/__DEFINES/turf_flags.dm | 6 - code/__DEFINES/turfs.dm | 10 + code/__HELPERS/_lists.dm | 35 + code/__HELPERS/areas.dm | 99 ++ code/__HELPERS/text.dm | 20 +- code/__HELPERS/unsorted.dm | 63 -- code/_globalvars/lists/mapping.dm | 8 +- code/controllers/subsystem/air.dm | 3 +- code/controllers/subsystem/area_contents.dm | 55 ++ code/controllers/subsystem/mapping.dm | 80 +- code/controllers/subsystem/shuttle.dm | 3 + code/datums/mapgen/CaveGenerator.dm | 186 ++-- code/datums/mapgen/Cavegens/IcemoonCaves.dm | 12 +- .../mapgen/Cavegens/LavalandGenerator.dm | 12 +- code/datums/mapgen/_MapGenerator.dm | 2 +- code/game/area/areas.dm | 74 +- code/game/atoms.dm | 2 +- code/game/gamemodes/objective.dm | 2 +- code/game/turfs/simulated/river.dm | 67 +- code/modules/admin/verbs/adminjump.dm | 33 +- code/modules/admin/verbs/debug.dm | 2 +- code/modules/antagonists/blob/overmind.dm | 2 +- code/modules/antagonists/cult/cult.dm | 2 +- code/modules/cargo/centcom_podlauncher.dm | 4 +- code/modules/cargo/expressconsole.dm | 4 +- .../clothing/glasses/engine_goggles.dm | 5 +- code/modules/events/anomaly.dm | 4 +- code/modules/events/aurora_caelus.dm | 15 +- code/modules/events/prison_break.dm | 2 +- code/modules/events/spacevine.dm | 4 +- code/modules/events/stray_cargo.dm | 2 +- code/modules/lighting/lighting_setup.dm | 5 +- code/modules/mapping/map_template.dm | 14 +- code/modules/mapping/preloader.dm | 15 +- code/modules/mapping/reader.dm | 919 +++++++++++++----- code/modules/mapping/ruins.dm | 49 +- .../space_management/space_reservation.dm | 10 +- .../space_management/space_transition.dm | 155 +-- .../space_management/zlevel_manager.dm | 5 +- code/modules/mob/dead/observer/observer.dm | 3 +- .../mapGenerators/repair.dm | 2 +- .../security_levels/keycard_authentication.dm | 20 +- code/modules/shuttle/arrivals.dm | 2 +- code/modules/shuttle/emergency.dm | 6 +- code/modules/shuttle/on_move.dm | 4 + code/modules/shuttle/shuttle.dm | 2 + yogstation.dme | 3 +- 48 files changed, 1390 insertions(+), 644 deletions(-) delete mode 100644 code/__DEFINES/turf_flags.dm create mode 100644 code/__DEFINES/turfs.dm create mode 100644 code/controllers/subsystem/area_contents.dm diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index ff280936a047..5f08898ada75 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -28,6 +28,8 @@ #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) +#define ROUND_UP(x) ( -round(-(x))) + // round() acts like floor(x, 1) by default but can't handle other values #define FLOOR(x, y) ( round((x) / (y)) * (y) ) diff --git a/code/__DEFINES/turf_flags.dm b/code/__DEFINES/turf_flags.dm deleted file mode 100644 index 881a535a40eb..000000000000 --- a/code/__DEFINES/turf_flags.dm +++ /dev/null @@ -1,6 +0,0 @@ -#define CHANGETURF_DEFER_CHANGE 1 -#define CHANGETURF_IGNORE_AIR 2 // This flag prevents changeturf from gathering air from nearby turfs to fill the new turf with an approximation of local air -#define CHANGETURF_FORCEOP 4 -#define CHANGETURF_SKIP 8 // A flag for PlaceOnTop to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE -#define CHANGETURF_INHERIT_AIR 16 // Inherit air from previous turf. Implies CHANGETURF_IGNORE_AIR -#define CHANGETURF_RECALC_ADJACENT 32 //Immediately recalc adjacent atmos turfs instead of queuing. diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm new file mode 100644 index 000000000000..25d4c40b9d38 --- /dev/null +++ b/code/__DEFINES/turfs.dm @@ -0,0 +1,10 @@ +#define CHANGETURF_DEFER_CHANGE (1<<0) +#define CHANGETURF_IGNORE_AIR (1<<1) // This flag prevents changeturf from gathering air from nearby turfs to fill the new turf with an approximation of local air +#define CHANGETURF_FORCEOP (1<<2) +#define CHANGETURF_SKIP (1<<3) // A flag for PlaceOnTop to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE +#define CHANGETURF_INHERIT_AIR (1<<4) // Inherit air from previous turf. Implies CHANGETURF_IGNORE_AIR +#define CHANGETURF_RECALC_ADJACENT (1<<5) //Immediately recalc adjacent atmos turfs instead of queuing. +#define CHANGETURF_TRAPDOOR_INDUCED (1<<6) // Caused by a trapdoor, for trapdoor to know that this changeturf was caused by itself + +///Returns all currently loaded turfs +#define ALL_TURFS(...) block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz)) diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 011f9bb23af1..d4ec01d8833f 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -346,6 +346,41 @@ return null +/// Takes a weighted list (see above) and expands it into raw entries +/// This eats more memory, but saves time when actually picking from it +/proc/expand_weights(list/list_to_pick) + var/list/values = list() + for(var/item in list_to_pick) + var/value = list_to_pick[item] + if(!value) + continue + values += value + + var/gcf = greatest_common_factor(values) + + var/list/output = list() + for(var/item in list_to_pick) + var/value = list_to_pick[item] + if(!value) + continue + for(var/i in 1 to value / gcf) + output += item + return output + +/// Takes a list of numbers as input, returns the highest value that is cleanly divides them all +/// Note: this implementation is expensive as heck for large numbers, I only use it because most of my usecase +/// Is < 10 ints +/proc/greatest_common_factor(list/values) + var/smallest = min(arglist(values)) + for(var/i in smallest to 1 step -1) + var/safe = TRUE + for(var/entry in values) + if(entry % i != 0) + safe = FALSE + break + if(safe) + return i + /// Pick a random element from the list and remove it from the list. /proc/pick_n_take(list/L) RETURN_TYPE(L[_].type) diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm index a0e7899de430..f49a40356873 100644 --- a/code/__HELPERS/areas.dm +++ b/code/__HELPERS/areas.dm @@ -92,7 +92,9 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(/area/engin for(var/i in 1 to turfs.len) var/turf/thing = turfs[i] var/area/old_area = thing.loc + old_area.turfs_to_uncontain += thing newA.contents += thing + newA.contained_turfs += thing thing.change_area(old_area, newA) newA.reg_in_areas_in_z() @@ -105,4 +107,101 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(/area/engin to_chat(creator, span_notice("You have created a new area, named [newA.name]. It is now weather proof, and constructing an APC will allow it to be powered.")) return TRUE +/proc/require_area_resort() + GLOB.sortedAreas = null + +/// Returns a sorted version of GLOB.areas, by name +/proc/get_sorted_areas() + if(!GLOB.sortedAreas) + GLOB.sortedAreas = sortTim(GLOB.areas.Copy(), /proc/cmp_name_asc) + return GLOB.sortedAreas + +//Takes: Area type as a text string from a variable. +//Returns: Instance for the area in the world. +/proc/get_area_instance_from_text(areatext) + if(istext(areatext)) + areatext = text2path(areatext) + return GLOB.areas_by_type[areatext] + +//Takes: Area type as text string or as typepath OR an instance of the area. +//Returns: A list of all areas of that type in the world. +/proc/get_areas(areatype, subtypes=TRUE) + if(istext(areatype)) + areatype = text2path(areatype) + else if(isarea(areatype)) + var/area/areatemp = areatype + areatype = areatemp.type + else if(!ispath(areatype)) + return null + + var/list/areas = list() + if(subtypes) + var/list/cache = typecacheof(areatype) + for(var/area/area_to_check as anything in GLOB.areas) + if(cache[area_to_check.type]) + areas += area_to_check + else + for(var/area/area_to_check as anything in GLOB.areas) + if(area_to_check.type == areatype) + areas += area_to_check + return areas + +/// Iterates over all turfs in the target area and returns the first non-dense one +/proc/get_first_open_turf_in_area(area/target) + if(!target) + return + for(var/turf/turf in target) + if(!turf.density) + return turf + +//Takes: Area type as text string or as typepath OR an instance of the area. +//Returns: A list of all turfs in areas of that type of that type in the world. +/proc/get_area_turfs(areatype, target_z = 0, subtypes=FALSE) + if(istext(areatype)) + areatype = text2path(areatype) + else if(isarea(areatype)) + var/area/areatemp = areatype + areatype = areatemp.type + else if(!ispath(areatype)) + return null + // Pull out the areas + var/list/areas_to_pull = list() + if(subtypes) + var/list/cache = typecacheof(areatype) + for(var/area/area_to_check as anything in GLOB.areas) + if(!cache[area_to_check.type]) + continue + areas_to_pull += area_to_check + else + for(var/area/area_to_check as anything in GLOB.areas) + if(area_to_check.type != areatype) + continue + areas_to_pull += area_to_check + + // Now their turfs + var/list/turfs = list() + for(var/area/pull_from as anything in areas_to_pull) + var/list/our_turfs = pull_from.get_contained_turfs() + if(target_z == 0) + turfs += our_turfs + else + for(var/turf/turf_in_area as anything in our_turfs) + if(target_z == turf_in_area.z) + turfs += turf_in_area + return turfs + + +///Takes: list of area types +///Returns: all mobs that are in an area type +/proc/mobs_in_area_type(list/area/checked_areas) + var/list/mobs_in_area = list() + for(var/mob/living/mob as anything in GLOB.mob_living_list) + if(QDELETED(mob)) + continue + for(var/area in checked_areas) + if(istype(get_area(mob), area)) + mobs_in_area += mob + break + return mobs_in_area + #undef BP_MAX_ROOM_SIZE diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 6214faeedaf9..5d939d6cf7ae 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -264,11 +264,29 @@ return "" +//Returns a string with reserved characters and spaces after the first and last letters removed +//Like trim(), but very slightly faster. worth it for niche usecases +/proc/trim_reduced(text) + var/starting_coord = 1 + var/text_len = length(text) + for (var/i in 1 to text_len) + if (text2ascii(text, i) > 32) + starting_coord = i + break + + for (var/i = text_len, i >= starting_coord, i--) + if (text2ascii(text, i) > 32) + return copytext(text, starting_coord, i + 1) + + if(starting_coord > 1) + return copytext(text, starting_coord) + return "" + //Returns a string with reserved characters and spaces before the first word and after the last word removed. /proc/trim(text, max_length) if(max_length) text = copytext_char(text, 1, max_length) - return trim_left(trim_right(text)) + return trim_reduced(text) //Returns a string with the first element of the string capitalized. /proc/capitalize(t as text) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 71e7e0ea6a86..fb2621081abf 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -583,69 +583,6 @@ Turf and target are separate in case you want to teleport some distance from a t GLOB.sortedAreas.Add(src) sortTim(GLOB.sortedAreas, /proc/cmp_name_asc) -//Takes: Area type as a text string from a variable. -//Returns: Instance for the area in the world. -/proc/get_area_instance_from_text(areatext) - if(istext(areatext)) - areatext = text2path(areatext) - return GLOB.areas_by_type[areatext] - -//Takes: Area type as text string or as typepath OR an instance of the area. -//Returns: A list of all areas of that type in the world. -/proc/get_areas(areatype, subtypes=TRUE) - if(istext(areatype)) - areatype = text2path(areatype) - else if(isarea(areatype)) - var/area/areatemp = areatype - areatype = areatemp.type - else if(!ispath(areatype)) - return null - - var/list/areas = list() - if(subtypes) - var/list/cache = typecacheof(areatype) - for(var/V in GLOB.sortedAreas) - var/area/A = V - if(cache[A.type]) - areas += V - else - for(var/V in GLOB.sortedAreas) - var/area/A = V - if(A.type == areatype) - areas += V - return areas - -//Takes: Area type as text string or as typepath OR an instance of the area. -//Returns: A list of all turfs in areas of that type of that type in the world. -/proc/get_area_turfs(areatype, target_z = 0, subtypes=FALSE) - if(istext(areatype)) - areatype = text2path(areatype) - else if(isarea(areatype)) - var/area/areatemp = areatype - areatype = areatemp.type - else if(!ispath(areatype)) - return null - - var/list/turfs = list() - if(subtypes) - var/list/cache = typecacheof(areatype) - for(var/V in GLOB.sortedAreas) - var/area/A = V - if(!cache[A.type]) - continue - for(var/turf/T in A) - if(target_z == 0 || target_z == T.z) - turfs += T - else - for(var/V in GLOB.sortedAreas) - var/area/A = V - if(A.type != areatype) - continue - for(var/turf/T in A) - if(target_z == 0 || target_z == T.z) - turfs += T - return turfs - /proc/get_cardinal_dir(atom/A, atom/B) var/dx = abs(B.x - A.x) var/dy = abs(B.y - A.y) diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm index c33839d57637..eceab9008add 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -45,11 +45,15 @@ GLOBAL_LIST_EMPTY(bar_areas) // IF YOU ARE MAKING A NEW BAR TEMPLATE AND WANT IT ROUNDSTART ADD IT TO THIS LIST! GLOBAL_LIST_INIT(potential_box_bars, list("Bar Trek", "Bar Spacious", "Bar Box", "Bar Casino", "Bar Citadel", "Bar Conveyor", "Bar Diner", "Bar Disco", "Bar Purple", "Bar Cheese", "Bar Grassy", "Bar Clock", "Bar Arcade")) - //away missions +/// Away missions GLOBAL_LIST_EMPTY(awaydestinations) //a list of landmarks that the warpgate can take you to GLOBAL_LIST_EMPTY(vr_spawnpoints) - //used by jump-to-area etc. Updated by area/updateName() +/// Just a list of all the area objects in the game +/// Note, areas can have duplicate types +GLOBAL_LIST_EMPTY(areas) +/// Used by jump-to-area etc. Updated by area/updateName() +/// If this is null, it needs to be recalculated. Use get_sorted_areas() as a getter please GLOBAL_LIST_EMPTY(sortedAreas) /// An association from typepath to area instance. Only includes areas with `unique` set. GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 2e1624d6d10a..9b380d76296c 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -351,7 +351,6 @@ SUBSYSTEM_DEF(air) queued_for_activation.Cut() /datum/controller/subsystem/air/proc/setup_allturfs() - var/list/turfs_to_init = block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz)) var/list/active_turfs = src.active_turfs var/times_fired = ++src.times_fired @@ -359,7 +358,7 @@ SUBSYSTEM_DEF(air) // one-by-one, and Initalize_Atmos only ever adds `src` back in. active_turfs.Cut() - for(var/thing in turfs_to_init) + for(var/thing in ALL_TURFS()) var/turf/T = thing if (T.blocks_air) continue diff --git a/code/controllers/subsystem/area_contents.dm b/code/controllers/subsystem/area_contents.dm new file mode 100644 index 000000000000..272c72eb7ea6 --- /dev/null +++ b/code/controllers/subsystem/area_contents.dm @@ -0,0 +1,55 @@ +#define ALLOWED_LOOSE_TURFS 500 +/** + * Responsible for managing the sizes of area.contained_turfs and area.turfs_to_uncontain + * These lists do not check for duplicates, which is fine, but it also means they can balloon in size over time + * as a consequence of repeated changes in area in a space + * They additionally may not always resolve often enough to avoid memory leaks + * This is annoying, so lets keep an eye on them and cut them down to size if needed + */ +SUBSYSTEM_DEF(area_contents) + name = "Area Contents" + flags = SS_NO_INIT + runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT + var/list/currentrun + var/list/area/marked_for_clearing = list() + +/datum/controller/subsystem/area_contents/stat_entry(msg) + var/total_clearing_from = 0 + var/total_to_clear = 0 + for(var/area/to_clear as anything in marked_for_clearing) + total_to_clear += length(to_clear.turfs_to_uncontain) + total_clearing_from += length(to_clear.contained_turfs) + msg = "A:[length(currentrun)] MR:[length(marked_for_clearing)] TC:[total_to_clear] CF:[total_clearing_from]" + return ..() + + +/datum/controller/subsystem/area_contents/fire(resumed) + if(!resumed) + currentrun = GLOB.areas.Copy() + + while(length(currentrun)) + var/area/test = currentrun[length(currentrun)] + if(length(test.turfs_to_uncontain) > ALLOWED_LOOSE_TURFS) + marked_for_clearing |= test + currentrun.len-- + if(MC_TICK_CHECK) + return + + // Alright, if we've done a scan on all our areas, it's time to knock the existing ones down to size + while(length(marked_for_clearing)) + var/area/clear = marked_for_clearing[length(marked_for_clearing)] + + // The operation of cutting large lists can be expensive + // It scales almost directly with the size of the list we're cutting with + // Because of this, we're gonna stick to cutting 1 entry at a time + // There's no reason to batch it I promise, this is faster. No overtime too + var/amount_cut = 0 + var/list/cut_from = clear.turfs_to_uncontain + for(amount_cut in 1 to length(cut_from)) + clear.contained_turfs -= cut_from[amount_cut] + if(MC_TICK_CHECK) + cut_from.Cut(1, amount_cut + 1) + return + + clear.turfs_to_uncontain = list() + marked_for_clearing.len-- diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 6f3349d978b2..cfa864d7ca7d 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(mapping) name = "Mapping" init_order = INIT_ORDER_MAPPING - flags = SS_NO_FIRE + runlevels = ALL loading_points = 11 SECONDS // Yogs -- loading times @@ -32,6 +32,8 @@ SUBSYSTEM_DEF(mapping) var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs) var/list/datum/turf_reservations //list of turf reservations var/list/used_turfs = list() //list of turf = datum/turf_reservation + /// List of lists of turfs to reserve + var/list/lists_to_reserve = list() var/clearing_reserved_turfs = FALSE @@ -63,7 +65,7 @@ SUBSYSTEM_DEF(mapping) to_chat(world, span_boldannounce("Unable to load next or default map config, defaulting to Box Station")) config = old_config loadWorld() - repopulate_sorted_areas() + require_area_resort() process_teleport_locs() //Sets up the wizard teleport locations preloadTemplates() run_map_generation() @@ -91,20 +93,20 @@ SUBSYSTEM_DEF(mapping) loading_ruins = TRUE 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), lava_ruins_templates) + seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), list(/area/lavaland/surface/outdoors/unexplored), lava_ruins_templates, clear_below = TRUE) for (var/lava_z in lava_ruins) spawn_rivers(lava_z) 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), ice_ruins_templates) + seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), ice_ruins_templates, clear_below = TRUE) for(var/ice_z in ice_ruins) spawn_rivers(ice_z, 4, /turf/open/openspace/icemoon, /area/icemoon/surface/outdoors/unexplored) 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), ice_ruins_underground_templates) + seedRuins(ice_ruins_underground, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/underground/unexplored), ice_ruins_underground_templates, clear_below = TRUE) for(var/ice_z in ice_ruins_underground) spawn_rivers(ice_z, 4, /turf/open/lava/plasma/ice_moon, /area/icemoon/underground/unexplored) @@ -134,7 +136,7 @@ SUBSYSTEM_DEF(mapping) PM.initTemplateBounds() // Add the transit level transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE)) - repopulate_sorted_areas() + require_area_resort() // Set up Z-level transitions. setup_map_transitions() generate_station_area_list() @@ -143,6 +145,35 @@ SUBSYSTEM_DEF(mapping) build_minimaps() 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) + 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.flags_1 |= UNUSED_RESERVATION_TURF_1 + world_contents += T + world_turf_contents += T + packet.len-- + packetlen = length(packet) + + index++ + lists_to_reserve.Cut(1, index) + /datum/controller/subsystem/mapping/proc/wipe_reservations(wipe_safety_delay = 100) if(clearing_reserved_turfs || !initialized) //in either case this is just not needed. return @@ -250,13 +281,13 @@ SUBSYSTEM_DEF(mapping) var/start_z = world.maxz + 1 var/i = 0 for (var/level in traits) - add_new_zlevel("[name][i ? " [i + 1]" : ""]", level) + add_new_zlevel("[name][i ? " [i + 1]" : ""]", level, contain_turfs = FALSE) ++i // load the maps for (var/P in parsed_maps) var/datum/parsed_map/pm = P - if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE)) + if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE, new_z = TRUE)) errorList |= pm.original_path if(!silent) INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") @@ -310,7 +341,7 @@ GLOBAL_LIST_EMPTY(the_station_areas) /datum/controller/subsystem/mapping/proc/generate_station_area_list() var/list/station_areas_blacklist = typecacheof(list(/area/space, /area/mine, /area/ruin, /area/asteroid/nearstation)) - for(var/area/A in world) + for(var/area/A in GLOB.areas) if (is_type_in_typecache(A, station_areas_blacklist)) continue if (!A.contents.len || !A.unique) @@ -323,7 +354,7 @@ GLOBAL_LIST_EMPTY(the_station_areas) log_world("ERROR: Station areas list failed to generate!") /datum/controller/subsystem/mapping/proc/run_map_generation() - for(var/area/A in world) + for(var/area/A as anything in GLOB.areas) A.RunGeneration() /datum/controller/subsystem/mapping/proc/maprotate() @@ -549,15 +580,12 @@ GLOBAL_LIST_EMPTY(the_station_areas) unused_turfs["[i]"] = block clearing_reserved_turfs = FALSE -/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs) - for(var/i in turfs) - var/turf/T = i - T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE) - LAZYINITLIST(unused_turfs["[T.z]"]) - unused_turfs["[T.z]"] |= T - T.flags_1 |= UNUSED_RESERVATION_TURF_1 - GLOB.areas_by_type[world.area].contents += T - CHECK_TICK +/// Schedules a group of turfs to be handed back to the reservation system's control +/// If await is true, will sleep until the turfs are finished work +/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs, await = FALSE) + lists_to_reserve += list(turfs) + if(await) + UNTIL(!length(turfs)) //DO NOT CALL THIS PROC DIRECTLY, CALL wipe_reservations(). /datum/controller/subsystem/mapping/proc/do_wipe_turf_reservations() @@ -575,7 +603,7 @@ GLOBAL_LIST_EMPTY(the_station_areas) clearing |= used_turfs //used turfs is an associative list, BUT, reserve_turfs() can still handle it. If the code above works properly, this won't even be needed as the turfs would be freed already. unused_turfs.Cut() used_turfs.Cut() - reserve_turfs(clearing) + reserve_turfs(clearing, await = TRUE) /datum/controller/subsystem/mapping/proc/build_minimaps() to_chat(world, span_boldannounce("Building minimaps...")) @@ -586,3 +614,15 @@ GLOBAL_LIST_EMPTY(the_station_areas) for(var/B in areas) var/area/A = B A.reg_in_areas_in_z() + +/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 diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index db8987a7aa3e..0f6d4beb9c00 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -516,9 +516,12 @@ SUBSYSTEM_DEF(shuttle) var/turf/midpoint = locate(transit_x, transit_y, bottomleft.z) if(!midpoint) return FALSE + var/area/old_area = midpoint.loc + old_area.turfs_to_uncontain += proposal.reserved_turfs var/area/shuttle/transit/A = new() A.parallax_movedir = travel_dir A.contents = proposal.reserved_turfs + A.contained_turfs = proposal.reserved_turfs var/obj/docking_port/stationary/transit/new_transit_dock = new(midpoint) new_transit_dock.reserved_area = proposal new_transit_dock.name = "Transit for [M.id]/[M.name]" diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm index 7f3a1d5600b1..5e6ace0f2038 100644 --- a/code/datums/mapgen/CaveGenerator.dm +++ b/code/datums/mapgen/CaveGenerator.dm @@ -1,19 +1,33 @@ /datum/map_generator/cave_generator var/name = "Cave Generator" ///Weighted list of the types that spawns if the turf is open - var/open_turf_types = list(/turf/open/floor/plating/asteroid/airless = 1) + var/weighted_open_turf_types = list(/turf/open/floor/plating/asteroid/airless = 1) + ///Expanded list of the types that spawns if the turf is open + var/open_turf_types ///Weighted list of the types that spawns if the turf is closed - var/closed_turf_types = list(/turf/closed/mineral/random = 1) + var/weighted_closed_turf_types = list(/turf/closed/mineral/random = 1) + ///Expanded list of the types that spawns if the turf is closed + var/closed_turf_types ///If this is set to TRUE then it will only change turfs that are /turf/open/genturf, for more flexability in design var/gen_gurf_only = TRUE ///Weighted list of mobs that can spawn in the area. + var/list/weighted_mob_spawn_list + ///Expanded list of mobs that can spawn in the area. Reads from the weighted list var/list/mob_spawn_list - // Weighted list of Megafauna that can spawn in the caves + ///The mob spawn list but with no megafauna markers. autogenerated + var/list/mob_spawn_no_mega_list + // Weighted list of Megafauna that can spawn in the area + var/list/weighted_megafauna_spawn_list + ///Expanded list of Megafauna that can spawn in the area. Reads from the weighted list var/list/megafauna_spawn_list ///Weighted list of flora that can spawn in the area. + var/list/weighted_flora_spawn_list + ///Expanded list of flora that can spawn in the area. Reads from the weighted list var/list/flora_spawn_list ///Weighted list of extra features that can spawn in the area, such as geysers. + var/list/weighted_feature_spawn_list + ///Expanded list of extra features that can spawn in the area. Reads from the weighted list var/list/feature_spawn_list @@ -37,113 +51,119 @@ /datum/map_generator/cave_generator/New() . = ..() - if(!mob_spawn_list) - mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goldgrub = 1, /mob/living/simple_animal/hostile/asteroid/goliath = 5, /mob/living/simple_animal/hostile/asteroid/basilisk = 4, /mob/living/simple_animal/hostile/asteroid/hivelord = 3) - if(!megafauna_spawn_list) - megafauna_spawn_list = GLOB.megafauna_spawn_list - if(!flora_spawn_list) - flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2) - if(!feature_spawn_list) - feature_spawn_list = list() //yogs no geysers in asteroid caves + if(!weighted_mob_spawn_list) + weighted_mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goldgrub = 1, /mob/living/simple_animal/hostile/asteroid/goliath = 5, /mob/living/simple_animal/hostile/asteroid/basilisk = 4, /mob/living/simple_animal/hostile/asteroid/hivelord = 3) + mob_spawn_list = expand_weights(weighted_mob_spawn_list) + mob_spawn_no_mega_list = expand_weights(weighted_mob_spawn_list - SPAWN_MEGAFAUNA) + if(!weighted_megafauna_spawn_list) + weighted_megafauna_spawn_list = GLOB.megafauna_spawn_list + megafauna_spawn_list = expand_weights(weighted_megafauna_spawn_list) + if(!weighted_flora_spawn_list) + weighted_flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2, /obj/structure/flora/ash/seraka = 2) + flora_spawn_list = expand_weights(weighted_flora_spawn_list) + if(!weighted_feature_spawn_list) + weighted_feature_spawn_list = list() // yogs - no geysers in asteroid caves + feature_spawn_list = expand_weights(weighted_feature_spawn_list) + open_turf_types = expand_weights(weighted_open_turf_types) + closed_turf_types = expand_weights(weighted_closed_turf_types) -/datum/map_generator/cave_generator/generate_terrain(list/turfs) +/datum/map_generator/cave_generator/generate_terrain(list/turfs, area/generate_in) . = ..() + if(!(generate_in.area_flags & CAVES_ALLOWED)) + return + var/start_time = REALTIMEOFDAY string_gen = rustg_cnoise_generate("[initial_closed_chance]", "[smoothing_iterations]", "[birth_limit]", "[death_limit]", "[world.maxx]", "[world.maxy]") //Generate the raw CA data + // Area var pullouts to make accessing in the loop faster + var/flora_allowed = (generate_in.area_flags & FLORA_ALLOWED) && length(flora_spawn_list) + var/feature_allowed = (generate_in.area_flags & FLORA_ALLOWED) && length(feature_spawn_list) + var/mobs_allowed = (generate_in.area_flags & MOB_SPAWN_ALLOWED) && length(mob_spawn_list) + var/megas_allowed = (generate_in.area_flags & MEGAFAUNA_SPAWN_ALLOWED) && length(megafauna_spawn_list) + for(var/i in turfs) //Go through all the turfs and generate them var/turf/gen_turf = i - if(gen_gurf_only && !istype(gen_turf,/turf/open/genturf)) + if(gen_gurf_only && !istype(gen_turf, /turf/open/genturf)) continue - if(istype(gen_turf,/turf/closed/mineral)) + if(istype(gen_turf, /turf/closed/mineral)) continue - var/area/A = gen_turf.loc - if(!(A.area_flags & CAVES_ALLOWED)) - continue + var/closed = string_gen[world.maxx * (gen_turf.y - 1) + gen_turf.x] != "0" + var/turf/new_turf = pick(closed ? closed_turf_types : open_turf_types) - var/closed = text2num(string_gen[world.maxx * (gen_turf.y - 1) + gen_turf.x]) + // The assumption is this will be faster then changeturf, and changeturf isn't required since by this point + // The old tile hasn't got the chance to init yet + new_turf = new new_turf(gen_turf) - var/stored_flags if(gen_turf.flags_1 & NO_RUINS_1) - stored_flags |= NO_RUINS_1 + new_turf.flags_1 |= NO_RUINS_1 - var/turf/new_turf = pickweight(closed ? closed_turf_types : open_turf_types) + if(closed) //Open turfs have some special behavior related to spawning flora and mobs. + CHECK_TICK + continue - new_turf = gen_turf.ChangeTurf(new_turf, initial(new_turf.baseturfs), CHANGETURF_DEFER_CHANGE) + // If we've spawned something yet + var/spawned_something = FALSE - new_turf.flags_1 |= stored_flags + ///Spawning isn't done in procs to save on overhead on the 60k turfs we're going through. + //FLORA SPAWNING HERE + if(flora_allowed && prob(flora_spawn_chance)) + var/flora_type = pick(flora_spawn_list) + new flora_type(new_turf) + spawned_something = TRUE - if(!closed)//Open turfs have some special behavior related to spawning flora and mobs. + //FEATURE SPAWNING HERE + if(feature_allowed && prob(feature_spawn_chance)) + var/can_spawn = TRUE - var/turf/open/new_open_turf = new_turf + var/atom/picked_feature = pick(feature_spawn_list) - ///Spawning isn't done in procs to save on overhead on the 60k turfs we're going through. - - //FLORA SPAWNING HERE - var/atom/spawned_flora - if(flora_spawn_list && !isemptylist(flora_spawn_list) && prob(flora_spawn_chance)) - var/can_spawn = TRUE - - if(!(A.area_flags & FLORA_ALLOWED)) + for(var/obj/structure/existing_feature in range(7, new_turf)) + if(istype(existing_feature, picked_feature)) can_spawn = FALSE - if(can_spawn) - spawned_flora = pickweight(flora_spawn_list) - spawned_flora = new spawned_flora(new_open_turf) + break + + if(can_spawn) + new picked_feature(new_turf) + spawned_something = TRUE - //FEATURE SPAWNING HERE - var/atom/spawned_feature - if(feature_spawn_list && !isemptylist(feature_spawn_list) && prob(feature_spawn_chance)) - var/can_spawn = TRUE + //MOB SPAWNING HERE + if(mobs_allowed && !spawned_something && prob(mob_spawn_chance)) + var/atom/picked_mob = pick(mob_spawn_list) - if(!(A.area_flags & FLORA_ALLOWED)) //checks the same flag because lol dunno + if(picked_mob == SPAWN_MEGAFAUNA) + if(megas_allowed) //this is danger. it's boss time. + picked_mob = pick(megafauna_spawn_list) + else //this is not danger, don't spawn a boss, spawn something else + picked_mob = pick(mob_spawn_no_mega_list) //What if we used 100% of the brain...and did something (slightly) less shit than a while loop? + + var/can_spawn = TRUE + + // prevents tendrils spawning in each other's collapse range + if(ispath(picked_mob, /obj/structure/spawner/lavaland)) + for(var/obj/structure/spawner/lavaland/spawn_blocker in range(2, new_turf)) can_spawn = FALSE - - var/atom/picked_feature = pickweight(feature_spawn_list) - - for(var/obj/structure/F in range(7, new_open_turf)) - if(istype(F, picked_feature)) - can_spawn = FALSE - - if(can_spawn) - spawned_feature = new picked_feature(new_open_turf) - - //MOB SPAWNING HERE - - if(mob_spawn_list && !isemptylist(mob_spawn_list) && !spawned_flora && !spawned_feature && prob(mob_spawn_chance)) - var/can_spawn = TRUE - - if(!(A.area_flags & MOB_SPAWN_ALLOWED)) + break + //if the random is a standard mob, avoid spawning if there's another one within 12 tiles + else if(ispath(picked_mob, /mob/living/simple_animal/hostile/asteroid)) + for(var/mob/living/simple_animal/hostile/asteroid/mob_blocker in range(12, new_turf)) can_spawn = FALSE + break + //if there's a megafauna within standard view don't spawn anything at all (This isn't really consistent, I don't know why we do this. you do you tho) + if(can_spawn) + for(var/mob/living/simple_animal/hostile/megafauna/found_fauna in range(7, new_turf)) + can_spawn = FALSE + break - var/atom/picked_mob = pickweight(mob_spawn_list) - - if(picked_mob == SPAWN_MEGAFAUNA) // - if((A.area_flags & MEGAFAUNA_SPAWN_ALLOWED) && megafauna_spawn_list?.len) //this is danger. it's boss time. - picked_mob = pickweight(megafauna_spawn_list) - else //this is not danger, don't spawn a boss, spawn something else - picked_mob = pickweight(mob_spawn_list - SPAWN_MEGAFAUNA) //What if we used 100% of the brain...and did something (slightly) less shit than a while loop? - - for(var/thing in urange(12, new_open_turf)) //prevents mob clumps - if(!ishostile(thing) && !istype(thing, /obj/structure/spawner)) - continue - if((ispath(picked_mob, /mob/living/simple_animal/hostile/megafauna) || ismegafauna(thing)) && get_dist(new_open_turf, thing) <= 7) - can_spawn = FALSE //if there's a megafauna within standard view don't spawn anything at all - break - if(ispath(picked_mob, /mob/living/simple_animal/hostile/asteroid) || istype(thing, /mob/living/simple_animal/hostile/asteroid)) - can_spawn = FALSE //if the random is a standard mob, avoid spawning if there's another one within 12 tiles - break - if((ispath(picked_mob, /obj/structure/spawner/lavaland) || istype(thing, /obj/structure/spawner/lavaland)) && get_dist(new_open_turf, thing) <= 2) - can_spawn = FALSE //prevents tendrils spawning in each other's collapse range - break - - if(can_spawn) - if(ispath(picked_mob, /mob/living/simple_animal/hostile/megafauna/bubblegum)) //there can be only one bubblegum, so don't waste spawns on it - megafauna_spawn_list.Remove(picked_mob) - - new picked_mob(new_open_turf) + if(can_spawn) + if(ispath(picked_mob, /mob/living/simple_animal/hostile/megafauna/bubblegum)) //there can be only one bubblegum, so don't waste spawns on it + weighted_megafauna_spawn_list.Remove(picked_mob) + megafauna_spawn_list = expand_weights(weighted_megafauna_spawn_list) + megas_allowed = megas_allowed && length(megafauna_spawn_list) + new picked_mob(new_turf) + spawned_something = TRUE CHECK_TICK var/message = "[name] finished in [(REALTIMEOFDAY - start_time)/10]s!" diff --git a/code/datums/mapgen/Cavegens/IcemoonCaves.dm b/code/datums/mapgen/Cavegens/IcemoonCaves.dm index 7963f1ac245b..ab6119cf7a6c 100644 --- a/code/datums/mapgen/Cavegens/IcemoonCaves.dm +++ b/code/datums/mapgen/Cavegens/IcemoonCaves.dm @@ -1,20 +1,20 @@ /datum/map_generator/cave_generator/icemoon - open_turf_types = list(/turf/open/floor/plating/asteroid/snow/icemoon = 19, /turf/open/floor/plating/ice/icemoon = 1) - closed_turf_types = list(/turf/closed/mineral/random/snow = 1) + weighted_open_turf_types = list(/turf/open/floor/plating/asteroid/snow/icemoon = 19, /turf/open/floor/plating/ice/icemoon = 1) + weighted_closed_turf_types = list(/turf/closed/mineral/random/snow = 1) - mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/wolf = 50, /obj/structure/spawner/ice_moon = 3, \ + weighted_mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/wolf = 50, /obj/structure/spawner/ice_moon = 3, \ /mob/living/simple_animal/hostile/asteroid/polarbear = 30, /obj/structure/spawner/ice_moon/polarbear = 3, \ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50, /mob/living/simple_animal/hostile/asteroid/marrowweaver/ice = 30, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10) - flora_spawn_list = list(/obj/structure/flora/tree/pine = 2, /obj/structure/flora/rock/icy = 2, /obj/structure/flora/rock/pile/icy = 2, /obj/structure/flora/grass/both = 6) + weighted_flora_spawn_list = list(/obj/structure/flora/tree/pine = 2, /obj/structure/flora/rock/icy = 2, /obj/structure/flora/rock/pile/icy = 2, /obj/structure/flora/grass/both = 6) ///Note that this spawn list is also in the lavaland generator - feature_spawn_list = list() + weighted_feature_spawn_list = null /datum/map_generator/cave_generator/icemoon/surface flora_spawn_chance = 4 - mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/wolf = 50, /obj/structure/spawner/ice_moon = 3, \ + weighted_mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/wolf = 50, /obj/structure/spawner/ice_moon = 3, \ /mob/living/simple_animal/hostile/asteroid/polarbear = 30, /obj/structure/spawner/ice_moon/polarbear = 3, \ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50, /mob/living/simple_animal/hostile/asteroid/marrowweaver/ice = 30, diff --git a/code/datums/mapgen/Cavegens/LavalandGenerator.dm b/code/datums/mapgen/Cavegens/LavalandGenerator.dm index 143bb21197aa..274803c5f06d 100644 --- a/code/datums/mapgen/Cavegens/LavalandGenerator.dm +++ b/code/datums/mapgen/Cavegens/LavalandGenerator.dm @@ -1,18 +1,18 @@ /datum/map_generator/cave_generator/lavaland name = "Lavaland Base" - open_turf_types = list(/turf/open/floor/plating/asteroid/basalt/lava_land_surface = 1) - closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1) + weighted_open_turf_types = list(/turf/open/floor/plating/asteroid/basalt/lava_land_surface = 1) + weighted_closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1) - mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, /obj/structure/spawner/lavaland/goliath = 3, \ + weighted_mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, /obj/structure/spawner/lavaland/goliath = 3, \ /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, \ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, /obj/structure/spawner/lavaland/legion = 3, \ /mob/living/simple_animal/hostile/asteroid/marrowweaver = 30, SPAWN_MEGAFAUNA = 4, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10 ) - flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2) + weighted_flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2) ///Note that this spawn list is also in the icemoon generator - feature_spawn_list = list(/obj/structure/geyser/ash = 10, /obj/structure/geyser/random = 2, /obj/structure/geyser/stable_plasma = 6, /obj/structure/geyser/oil = 8,/obj/structure/geyser/protozine = 10,/obj/structure/geyser/holywater = 2) //yogs, yes geysers + weighted_feature_spawn_list = list(/obj/structure/geyser/ash = 10, /obj/structure/geyser/random = 2, /obj/structure/geyser/stable_plasma = 6, /obj/structure/geyser/oil = 8,/obj/structure/geyser/protozine = 10,/obj/structure/geyser/holywater = 2) //yogs, yes geysers initial_closed_chance = 45 smoothing_iterations = 50 @@ -37,7 +37,7 @@ var/min_offset = 0 var/max_offset = 5 -/datum/map_generator/cave_generator/lavaland/generate_terrain(list/turfs) +/datum/map_generator/cave_generator/lavaland/generate_terrain(list/turfs, area/generate_in) . = ..() var/start_time = REALTIMEOFDAY var/node_amount = rand(6,10) diff --git a/code/datums/mapgen/_MapGenerator.dm b/code/datums/mapgen/_MapGenerator.dm index 794057186857..8f8a39fe6c14 100644 --- a/code/datums/mapgen/_MapGenerator.dm +++ b/code/datums/mapgen/_MapGenerator.dm @@ -2,7 +2,7 @@ /datum/map_generator ///This proc will be ran by areas on Initialize, and provides the areas turfs as argument to allow for generation. -/datum/map_generator/proc/generate_terrain(list/turfs) +/datum/map_generator/proc/generate_terrain(list/turfs, area/generate_in) return /turf/open/genturf diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index e7201d00519e..c2a3828e402b 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -14,6 +14,15 @@ mouse_opacity = MOUSE_OPACITY_TRANSPARENT invisibility = INVISIBILITY_LIGHTING + /// List of all turfs currently inside this area. Acts as a filtered bersion of area.contents + /// For faster lookup (area.contents is actually a filtered loop over world) + /// Semi fragile, but it prevents stupid so I think it's worth it + var/list/turf/contained_turfs = list() + /// Contained turfs is a MASSIVE list, so rather then adding/removing from it each time we have a problem turf + /// We should instead store a list of turfs to REMOVE from it, then hook into a getter for it + /// There is a risk of this and contained_turfs leaking, so a subsystem will run it down to 0 incrementally if it gets too large + var/list/turf/turfs_to_uncontain = list() + var/area_flags = 0 var/map_name // Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints. @@ -109,20 +118,16 @@ GLOBAL_LIST_EMPTY(teleportlocs) * The returned list of turfs is sorted by name */ /proc/process_teleport_locs() - for(var/V in GLOB.sortedAreas) - var/area/AR = V + for(var/area/AR as anything in get_sorted_areas()) if(istype(AR, /area/shuttle) || AR.noteleport) continue if(GLOB.teleportlocs[AR.name]) continue - if (!AR.contents.len) + if (!AR.has_contained_turfs()) continue - var/turf/picked = AR.contents[1] - if (picked && is_station_level(picked.z)) + if (is_station_level(AR.z)) GLOB.teleportlocs[AR.name] = AR - sortTim(GLOB.teleportlocs, /proc/cmp_text_dsc) - /** * Called when an area loads * @@ -142,6 +147,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) // rather than waiting for atoms to initialize. if (unique) GLOB.areas_by_type[type] = src + GLOB.areas += src return ..() /** @@ -197,14 +203,33 @@ GLOBAL_LIST_EMPTY(teleportlocs) var/list/turfs = list() for(var/turf/T in contents) turfs += T - map_generator.generate_terrain(turfs) + map_generator.generate_terrain(turfs, src) /area/proc/test_gen() if(map_generator) var/list/turfs = list() for(var/turf/T in contents) turfs += T - map_generator.generate_terrain(turfs) + map_generator.generate_terrain(turfs, src) + +/area/proc/get_contained_turfs() + if(length(turfs_to_uncontain)) + cannonize_contained_turfs() + return contained_turfs + +/// Ensures that the contained_turfs list properly represents the turfs actually inside us +/area/proc/cannonize_contained_turfs() + // This is massively suboptimal for LARGE removal lists + // Try and keep the mass removal as low as you can. We'll do this by ensuring + // We only actually add to contained turfs after large changes (Also the management subsystem) + // Do your damndest to keep turfs out of /area/space as a stepping stone + // That sucker gets HUGE and will make this take actual tens of seconds if you stuff turfs_to_uncontain + contained_turfs -= turfs_to_uncontain + turfs_to_uncontain = list() + +/// Returns TRUE if we have contained turfs, FALSE otherwise +/area/proc/has_contained_turfs() + return length(contained_turfs) - length(turfs_to_uncontain) > 0 /** * Register this area as belonging to a z level @@ -216,22 +241,16 @@ GLOBAL_LIST_EMPTY(teleportlocs) * areas don't have a valid z themself or something */ /area/proc/reg_in_areas_in_z() - if(contents.len) - var/list/areas_in_z = SSmapping.areas_in_z - var/z - update_areasize() - for(var/i in 1 to contents.len) - var/atom/thing = contents[i] - if(!thing) - continue - z = thing.z - break - if(!z) - WARNING("No z found for [src]") - return - if(!areas_in_z["[z]"]) - areas_in_z["[z]"] = list() - areas_in_z["[z]"] += src + if(!has_contained_turfs()) + return + var/list/areas_in_z = SSmapping.areas_in_z + update_areasize() + if(!z) + WARNING("No z found for [src]") + return + if(!areas_in_z["[z]"]) + areas_in_z["[z]"] = list() + areas_in_z["[z]"] += src /** * Destroy an area and clean it up @@ -244,6 +263,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) /area/Destroy() if(GLOB.areas_by_type[type] == src) GLOB.areas_by_type[type] = null + GLOB.areas -= src STOP_PROCESSING(SSobj, src) return ..() @@ -734,7 +754,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) always_unpowered = FALSE valid_territory = FALSE blob_allowed = FALSE - addSorted() + require_area_resort() /** * Set the area size of the area * @@ -745,7 +765,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) if(outdoors) return FALSE areasize = 0 - for(var/turf/open/T in contents) + for(var/turf/open/T in get_contained_turfs()) areasize++ /** diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 11e9c8fed0af..a43461b0ec25 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -94,7 +94,7 @@ */ /atom/New(loc, ...) //atom creation method that preloads variables at creation - if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() + if(GLOB.use_preloader && (src.type == GLOB._preloader_path))//in case the instanciated atom is creating other atoms in New() world.preloader_load(src) if(datum_flags & DF_USE_TAG) diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 38f4e12a0060..af80aa8dfa05 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -1480,7 +1480,7 @@ GLOBAL_LIST_EMPTY(possible_items_special) /datum/objective/contract/proc/generate_dropoff() var/found = FALSE while (!found) - var/area/dropoff_area = pick(GLOB.sortedAreas) + var/area/dropoff_area = pick(GLOB.areas) if(dropoff_area && is_station_level(dropoff_area.z) && !dropoff_area.outdoors) dropoff = dropoff_area found = TRUE diff --git a/code/game/turfs/simulated/river.dm b/code/game/turfs/simulated/river.dm index e451fe48925f..4b7930d3fb9c 100644 --- a/code/game/turfs/simulated/river.dm +++ b/code/game/turfs/simulated/river.dm @@ -22,22 +22,23 @@ var/obj/effect/landmark/river_waypoint/W = A if (W.z != target_z || W.connected) continue - W.connected = 1 + W.connected = TRUE + // Workaround around ChangeTurf that's safe because of when this proc is called var/turf/cur_turf = get_turf(W) - cur_turf.ChangeTurf(turf_type, null, CHANGETURF_IGNORE_AIR) + cur_turf = new turf_type(cur_turf) var/turf/target_turf = get_turf(pick(river_nodes - W)) if(!target_turf) break - var/detouring = 0 + var/detouring = FALSE var/cur_dir = get_dir(cur_turf, target_turf) while(cur_turf != target_turf) if(detouring) //randomly snake around a bit if(prob(20)) - detouring = 0 + detouring = FALSE cur_dir = get_dir(cur_turf, target_turf) else if(prob(20)) - detouring = 1 + detouring = TRUE if(prob(50)) cur_dir = turn(cur_dir, 45) else @@ -48,12 +49,13 @@ cur_turf = get_step(cur_turf, cur_dir) var/area/new_area = get_area(cur_turf) if(!istype(new_area, whitelist_area) || (cur_turf.flags_1 & NO_LAVA_GEN_1)) //Rivers will skip ruins - detouring = 0 + detouring = FALSE cur_dir = get_dir(cur_turf, target_turf) cur_turf = get_step(cur_turf, cur_dir) continue else - var/turf/river_turf = cur_turf.ChangeTurf(turf_type, null, CHANGETURF_IGNORE_AIR) + // Workaround around ChangeTurf that's safe because of when this proc is called + var/turf/river_turf = new turf_type(cur_turf) river_turf.Spread(25, 11, whitelist_area) for(var/WP in river_nodes) @@ -72,33 +74,42 @@ var/list/cardinal_turfs = list() var/list/diagonal_turfs = list() var/logged_turf_type - for(var/F in RANGE_TURFS(1, src) - src) - var/turf/T = F - var/area/new_area = get_area(T) - if(!T || (T.density && !ismineralturf(T)) || istype(T, /turf/open/indestructible) || (whitelisted_area && !istype(new_area, whitelisted_area)) || (T.flags_1 & NO_LAVA_GEN_1) ) + for(var/turf/candidate as anything in RANGE_TURFS(1, src) - src) + if(!candidate || (candidate.density && !ismineralturf(candidate)) || isindestructiblefloor(candidate)) continue - if(!logged_turf_type && ismineralturf(T)) - var/turf/closed/mineral/M = T - logged_turf_type = M.turf_type + var/area/new_area = get_area(candidate) + if((!istype(new_area, whitelisted_area) && whitelisted_area) || (candidate.flags_1 & NO_LAVA_GEN_1)) + continue - if(get_dir(src, F) in GLOB.cardinals) - cardinal_turfs += F + if(!logged_turf_type && ismineralturf(candidate)) + var/turf/closed/mineral/mineral_candidate = candidate + logged_turf_type = mineral_candidate.turf_type + + if(get_dir(src, candidate) in GLOB.cardinals) + cardinal_turfs += candidate else - diagonal_turfs += F + diagonal_turfs += candidate - for(var/F in cardinal_turfs) //cardinal turfs are always changed but don't always spread - var/turf/T = F - if(!istype(T, logged_turf_type) && T.ChangeTurf(type, null, CHANGETURF_IGNORE_AIR) && prob(probability)) - T.Spread(probability - prob_loss, prob_loss, whitelisted_area) + for(var/turf/cardinal_candidate as anything in cardinal_turfs) //cardinal turfs are always changed but don't always spread + // NOTE: WE ARE SKIPPING CHANGETURF HERE + // The calls in this proc only serve to provide a satisfactory (if it's not ALREADY this) check. They do not actually call changeturf + // This is safe because this proc can only be run during mapload, and nothing has initialized by now so there's nothing to inherit or delete + if(!istype(cardinal_candidate, logged_turf_type) && cardinal_candidate.ChangeTurf(type, baseturfs, CHANGETURF_SKIP) && prob(probability)) + if(baseturfs) + cardinal_candidate.baseturfs = baseturfs + cardinal_candidate.Spread(probability - prob_loss, prob_loss, whitelisted_area) - for(var/F in diagonal_turfs) //diagonal turfs only sometimes change, but will always spread if changed - var/turf/T = F - if(!istype(T, logged_turf_type) && prob(probability) && T.ChangeTurf(type, null, CHANGETURF_IGNORE_AIR)) - T.Spread(probability - prob_loss, prob_loss, whitelisted_area) - else if(ismineralturf(T)) - var/turf/closed/mineral/M = T - M.ChangeTurf(M.turf_type, null, CHANGETURF_IGNORE_AIR) + for(var/turf/diagonal_candidate as anything in diagonal_turfs) //diagonal turfs only sometimes change, but will always spread if changed + // Important NOTE: SEE ABOVE + if(!istype(diagonal_candidate, logged_turf_type) && prob(probability) && diagonal_candidate.ChangeTurf(type, baseturfs, CHANGETURF_SKIP)) + if(baseturfs) + diagonal_candidate.baseturfs = baseturfs + diagonal_candidate.Spread(probability - prob_loss, prob_loss, whitelisted_area) + else if(ismineralturf(diagonal_candidate)) + var/turf/closed/mineral/diagonal_mineral = diagonal_candidate + // SEE ABOVE, THIS IS ONLY VERY RARELY SAFE + new diagonal_mineral.turf_type(diagonal_mineral) #undef RANDOM_UPPER_X diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index 91b4c74e49ff..1dbe8edd71bf 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -1,4 +1,4 @@ -/client/proc/jumptoarea(area/A in GLOB.sortedAreas) +/client/proc/jumptoarea(area/A in get_sorted_areas()) set name = "Jump to Area" set desc = "Area to jump to" set category = "Admin" @@ -138,21 +138,28 @@ usr.forceMove(M.loc) SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/client/proc/sendmob(mob/M in sortmobs()) +/client/proc/sendmob(mob/jumper in sortmobs()) set category = "Admin.Player Interaction" set name = "Send Mob" if(!src.holder) to_chat(src, "Only administrators may use this command.", confidential=TRUE) return - var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null - if(A && istype(A)) - if(M.forceMove(safepick(get_area_turfs(A)))) + var/list/sorted_areas = get_sorted_areas() + if(!length(sorted_areas)) + to_chat(src, "No areas found.", confidential = TRUE) + return + var/area/target_area = input(src, "Pick an area", "Send Mob", sorted_areas) + if(isnull(target_area)) + return + if(!istype(target_area)) + return + var/list/turfs = get_area_turfs(target_area) + if(length(turfs) && jumper.forceMove(pick(turfs))) + message_admins("[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(jumper)] to [AREACOORD(jumper)]") - message_admins("[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]") - - var/log_msg = "[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]" - log_admin(log_msg) - admin_ticket_log(M, log_msg, TRUE) - else - to_chat(src, "Failed to move mob to a valid location.", confidential=TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/log_msg = "[key_name(usr)] teleported [key_name(jumper)] to [AREACOORD(jumper)]" + log_admin(log_msg) + admin_ticket_log(jumper, log_msg, TRUE) + else + to_chat(src, "Failed to move mob to a valid location.", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 014bd9421ab1..beab68253d29 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -582,7 +582,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) message_admins(span_adminnotice("[key_name_admin(usr)] used the Test Areas debug command checking [log_message].")) log_admin("[key_name(usr)] used the Test Areas debug command checking [log_message].") - for(var/area/A in world) + for(var/area/A as anything in GLOB.areas) if(on_station) var/turf/picked = safepick(get_area_turfs(A.type)) if(picked && is_station_level(picked.z)) diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm index 9cc0c8df26a9..0b2255a11b27 100644 --- a/code/modules/antagonists/blob/overmind.dm +++ b/code/modules/antagonists/blob/overmind.dm @@ -153,7 +153,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) else L.fully_heal() - for(var/area/A in GLOB.sortedAreas) + for(var/area/A in GLOB.areas) if(!(A.type in GLOB.the_station_areas)) continue if(!A.blob_allowed) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 99849d1c3070..d014109b7215 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -461,7 +461,7 @@ ..() var/sanity = 0 while(summon_spots.len < SUMMON_POSSIBILITIES && sanity < 100) - var/area/summon = pick(GLOB.sortedAreas - summon_spots) + var/area/summon = pick(GLOB.areas - summon_spots) if(summon && is_station_level(summon.z) && summon.valid_territory) summon_spots += summon sanity++ diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 0782d4885a99..42448d3924c4 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -71,9 +71,9 @@ else var/mob/user_mob = user holder = user_mob.client //if its a mob, assign the mob's client to holder - bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //Locate the default bay (one) from the centcom map + bay = locate(/area/centcom/supplypod/loading/one) in GLOB.areas //Locate the default bay (one) from the centcom map bayNumber = bay.loading_id //Used as quick reference to what bay we're taking items from - var/area/pod_storage_area = locate(/area/centcom/supplypod/pod_storage) in GLOB.sortedAreas + var/area/pod_storage_area = locate(/area/centcom/supplypod/pod_storage) in GLOB.areas temp_pod = new(pick(get_area_turfs(pod_storage_area))) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed) orderedArea = createOrderedArea(bay) //Order all the turfs in the selected bay (top left to bottom right) to a single list. Used for the "ordered" mode (launchChoice = 1) selector = new(null, holder.mob) diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 71e28ea159e7..785c55fb3f9e 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -179,7 +179,7 @@ if (!landingzone) WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") landingzone = get_area(src) - for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone + for(var/turf/open/floor/T in landingzone.get_contained_turfs())//uses default landing zone if(is_blocked_turf(T)) continue LAZYADD(empty_turfs, T) @@ -196,7 +196,7 @@ else if(SO.pack.get_cost() * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone - for(var/turf/open/floor/T in landingzone.contents) + for(var/turf/open/floor/T in landingzone.get_contained_turfs()) if(is_blocked_turf(T)) continue LAZYADD(empty_turfs, T) diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm index 4158a3cc6d65..1b478834051b 100644 --- a/code/modules/clothing/glasses/engine_goggles.dm +++ b/code/modules/clothing/glasses/engine_goggles.dm @@ -103,9 +103,8 @@ if(!port) return var/list/shuttle_areas = port.shuttle_areas - for(var/r in shuttle_areas) - var/area/region = r - for(var/turf/place in region.contents) + for(var/area/region as anything in shuttle_areas) + for(var/turf/place as anything in region.get_contained_turfs()) if(get_dist(user, place) > 7) continue var/image/pic diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index 5c261d52976d..b4a6244d3db5 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -30,7 +30,7 @@ allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - return safepick(typecache_filter_list(GLOB.sortedAreas,allowed_areas)) + return safepick(typecache_filter_list(GLOB.areas,allowed_areas)) /datum/round_event/anomaly/setup() impact_area = findEventArea() @@ -49,4 +49,4 @@ if(T) newAnomaly = new anomaly_path(T) if (newAnomaly) - announce_to_ghosts(newAnomaly) \ No newline at end of file + announce_to_ghosts(newAnomaly) diff --git a/code/modules/events/aurora_caelus.dm b/code/modules/events/aurora_caelus.dm index e47ada359ed9..0717d98b0f16 100644 --- a/code/modules/events/aurora_caelus.dm +++ b/code/modules/events/aurora_caelus.dm @@ -28,27 +28,24 @@ M.playsound_local(M, 'sound/ambience/aurora_caelus.ogg', 20, FALSE, pressure_affected = FALSE) /datum/round_event/aurora_caelus/start() - for(var/area in GLOB.sortedAreas) - var/area/A = area + for(var/area/A as anything in GLOB.areas) if(initial(A.dynamic_lighting) == DYNAMIC_LIGHTING_IFSTARLIGHT) - for(var/turf/open/space/S in A) + for(var/turf/open/space/S in A.get_contained_turfs()) S.set_light(S.light_range * 3, S.light_power * 2) /datum/round_event/aurora_caelus/tick() if(activeFor % 5 == 0) aurora_progress++ var/aurora_color = aurora_colors[aurora_progress] - for(var/area in GLOB.sortedAreas) - var/area/A = area + for(var/area/A as anything in GLOB.areas) if(initial(A.dynamic_lighting) == DYNAMIC_LIGHTING_IFSTARLIGHT) - for(var/turf/open/space/S in A) + for(var/turf/open/space/S in A.get_contained_turfs()) S.set_light(l_color = aurora_color) /datum/round_event/aurora_caelus/end() - for(var/area in GLOB.sortedAreas) - var/area/A = area + for(var/area/A as anything in GLOB.areas) if(initial(A.dynamic_lighting) == DYNAMIC_LIGHTING_IFSTARLIGHT) - for(var/turf/open/space/S in A) + for(var/turf/open/space/S in A.get_contained_turfs()) fade_to_black(S) priority_announce("The aurora caelus event is now ending. Starlight conditions will slowly return to normal. When this has concluded, please return to your workplace and continue work as normal. Have a pleasant shift, [station_name()], and thank you for watching with us.", sound = 'sound/misc/notice2.ogg', diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm index 8923ff7a05ce..a581d6c659ea 100644 --- a/code/modules/events/prison_break.dm +++ b/code/modules/events/prison_break.dm @@ -24,7 +24,7 @@ severity = rand(1,3) for(var/i in 1 to severity) var/picked_area = pick_n_take(potential_areas) - for(var/area/A in world) + for(var/area/A as anything in GLOB.areas) if(istype(A, picked_area)) areasToOpen += A diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index d11482e09675..f4772c7a76eb 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -13,8 +13,8 @@ var/obj/structure/spacevine/SV = new() - for(var/area/maintenance/A in world) - for(var/turf/F in A) + for(var/area/maintenance/A in GLOB.areas) + for(var/turf/F as anything in A.get_contained_turfs()) if(F.Enter(SV)) turfs += F diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index a6a3d2b00781..0666ca9bd206 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -76,6 +76,6 @@ ///Subtypes from the above that actually should explode. var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room)) allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) + var/list/possible_areas = typecache_filter_list(GLOB.areas, allowed_areas) if (length(possible_areas)) return pick(possible_areas) diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm index d320f0b28c92..4d365d6646d0 100644 --- a/code/modules/lighting/lighting_setup.dm +++ b/code/modules/lighting/lighting_setup.dm @@ -1,10 +1,9 @@ /proc/create_all_lighting_objects() - for(var/area/A in world) + for(var/area/A as anything in GLOB.areas) if(!IS_DYNAMIC_LIGHTING(A)) continue - for(var/turf/T in A) - + for(var/turf/T as anything in A.get_contained_turfs()) if(!IS_DYNAMIC_LIGHTING(T)) continue diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 3a6caa8a806d..257c597332e1 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -59,14 +59,13 @@ var/x = round((world.maxx - width)/2) var/y = round((world.maxy - height)/2) - var/datum/space_level/level = SSmapping.add_new_zlevel(name, secret ? ZTRAITS_AWAY_SECRET : ZTRAITS_AWAY) - var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + var/datum/space_level/level = SSmapping.add_new_zlevel(name, secret ? ZTRAITS_AWAY_SECRET : ZTRAITS_AWAY, contain_turfs = FALSE) + var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE, new_z = TRUE) var/list/bounds = parsed.bounds if(!bounds) return FALSE - repopulate_sorted_areas() - + require_area_resort() //initialize things that are normally initialized after map load parsed.initTemplateBounds() smooth_zlevel(world.maxz) @@ -79,9 +78,9 @@ T = locate(T.x - round(width/2) , T.y - round(height/2) , T.z) if(!T) return - if(T.x+width > world.maxx) + if((T.x+width) - 1 > world.maxx) return - if(T.y+height > world.maxy) + if((T.y+height) - 1 > world.maxy) return // Accept cached maps, but don't save them automatically - we don't want @@ -94,8 +93,7 @@ if(!bounds) return - if(!SSmapping.loading_ruins) //Will be done manually during mapping ss init - repopulate_sorted_areas() + require_area_resort() //initialize things that are normally initialized after map load parsed.initTemplateBounds() diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm index 4b61663f668e..242912edfd8d 100644 --- a/code/modules/mapping/preloader.dm +++ b/code/modules/mapping/preloader.dm @@ -1,25 +1,24 @@ // global datum that will preload variables on atoms instanciation GLOBAL_VAR_INIT(use_preloader, FALSE) -GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) +GLOBAL_LIST_INIT(_preloader_attributes, null) +GLOBAL_LIST_INIT(_preloader_path, null) /// Preloader datum /datum/map_preloader - parent_type = /datum var/list/attributes var/target_path /world/proc/preloader_setup(list/the_attributes, path) if(the_attributes.len) GLOB.use_preloader = TRUE - var/datum/map_preloader/preloader_local = GLOB._preloader - preloader_local.attributes = the_attributes - preloader_local.target_path = path + GLOB._preloader_attributes = the_attributes + GLOB._preloader_path = path /world/proc/preloader_load(atom/what) GLOB.use_preloader = FALSE - var/datum/map_preloader/preloader_local = GLOB._preloader - for(var/attribute in preloader_local.attributes) - var/value = preloader_local.attributes[attribute] + var/list/attributes = GLOB._preloader_attributes + for(var/attribute in attributes) + var/value = attributes[attribute] if(islist(value)) value = deepCopyList(value) #ifdef TESTING diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index f20c004b7ca8..36925007276d 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -2,6 +2,68 @@ //SS13 Optimized Map loader ////////////////////////////////////////////////////////////// #define SPACE_KEY "space" +// We support two different map formats +// It is kinda possible to process them together, but if we split them up +// I can make optimization decisions more easily +/** + * DMM SPEC: + * DMM is split into two parts. First we have strings of text linked to lists of paths and their modifications (I will call this the cache) + * We call these strings "keys" and the things they point to members. Keys have a static length + * + * The second part is a list of locations matched to a string of keys. (I'll be calling this the grid) + * These are used to lookup the cache we built earlier. + * We store location lists as grid_sets. the lines represent different things depending on the spec + * + * In standard DMM (which you can treat as the base case, since it also covers weird modifications) each line + * represents an x file, and there's typically only one grid set per z level. + * The meme is you can look at a DMM formatted map and literally see what it should roughly look like + * This differs in TGM, and we can pull some performance from this + * + * Any restrictions here also apply to TGM + * + * /tg/ Restrictions: + * Paths have a specified order. First atoms in the order in which they should be loaded, then a single turf, then the area of the cell + * DMM technically supports turf stacking, but this is deprecated for all formats + + */ +#define MAP_DMM "dmm" +/** + * TGM SPEC: + * TGM is a derevation of DMM, with restrictions placed on it + * to make it easier to parse and to reduce merge conflicts/ease their resolution + * + * Requirements: + * Each "statement" in a key's details ends with a new line, and wrapped in (...) + * All paths end with either a comma or occasionally a {, then a new line + * Excepting the area, who is listed last and ends with a ) to mark the end of the key + * + * {} denotes a list of variable edits applied to the path that came before the first { + * the final } is followed by a comma, and then a new line + * Variable edits have the form \tname = value;\n + * Except the last edit, which has no final ;, and just ends in a newline + * No extra padding is permitted + * Many values are supported. See parse_constant() + * Strings must be wrapped in "...", files in '...', and lists in list(...) + * Files are kinda susy, and may not actually work. buyer beware + * Lists support assoc values as expected + * These constants can be further embedded into lists + * One var edited list will be shared among all the things it is applied to + * + * There can be no padding in front of, or behind a path + * + * Therefore: + * "key" = ( + * /path, + * /other/path{ + * var = list("name" = 'filepath'); + * other_var = /path + * }, + * /turf, + * /area) + * + */ +#define MAP_TGM "tgm" +#define MAP_UNKNOWN "unknown" /datum/grid_set var/xcrd @@ -11,9 +73,20 @@ /datum/parsed_map var/original_path + var/map_format + /// The length of a key in this file. This is promised by the standard to be static var/key_len = 0 + /// The length of a line in this file. Not promised by dmm but standard dmm uses it, so we can trust it + var/line_len = 0 + /// If we've expanded world.maxx + var/expanded_y = FALSE + /// If we've expanded world.maxy + var/expanded_x = FALSE var/list/grid_models = list() var/list/gridSets = list() + /// List of area types we've loaded AS A PART OF THIS MAP + /// We do this to allow non unique areas, so we'll only load one per map + var/list/area/loaded_areas = list() var/list/modelCache @@ -22,33 +95,46 @@ /// Offset bounds. Same as parsed_bounds until load(). var/list/bounds + ///any turf in this list is skipped inside of build_coordinate. Lazy assoc list + var/list/turf_blacklist + // raw strings used to represent regexes more accurately // '' used to avoid confusing syntax highlighting - var/static/regex/dmmRegex = new(@'"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g") - var/static/regex/trimQuotesRegex = new(@'^[\s\n]+"?|"?[\s\n]+$|^"|"$', "g") - var/static/regex/trimRegex = new(@'^[\s\n]+|[\s\n]+$', "g") + var/static/regex/dmm_regex = new(@'"([a-zA-Z]+)" = (?:\(\n|\()((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g") + /// Matches key formats in TMG (IE: newline after the \() + var/static/regex/matches_tgm = new(@'^"[A-z]*"[\s]*=[\s]*\([\s]*\n', "m") + /// Pulls out key value pairs for TGM + var/static/regex/var_edits_tgm = new(@'^\t([A-z]*) = (.*?);?$') + /// Pulls out model paths for DMM + var/static/regex/model_path = new(@'(\/[^\{]*?(?:\{.*?\})?)(?:,|$)', "g") #ifdef TESTING var/turfsSkipped = 0 #endif +//text trimming (both directions) helper macro +#define TRIM_TEXT(text) (trim_reduced(text)) + /// Shortcut function to parse a map and apply it to the world. /// /// - `dmm_file`: A .dmm file to load (Required). /// - `x_offset`, `y_offset`, `z_offset`: Positions representign where to load the map (Optional). /// - `cropMap`: When true, the map will be cropped to fit the existing world dimensions (Optional). /// - `measureOnly`: When true, no changes will be made to the world (Optional). -/// - `no_changeturf`: When true, [turf/AfterChange] won't be called on loaded turfs +/// - `no_changeturf`: When true, [/turf/proc/AfterChange] won't be called on loaded turfs /// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional). -/// - `placeOnTop`: Whether to use [turf/PlaceOnTop] rather than [turf/ChangeTurf] (Optional). -/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num) +/// - `placeOnTop`: Whether to use [/turf/proc/PlaceOnTop] rather than [/turf/proc/ChangeTurf] (Optional). +/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num, new_z) var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, measureOnly) if(parsed.bounds && !measureOnly) - parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) + parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z = new_z) return parsed /// Parse a map, possibly cropping it. /datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE) + // This proc sleeps for like 6 seconds. why? + // Is it file accesses? if so, can those be done ahead of time, async to save on time here? I wonder. + // Love ya :) if(isfile(tfile)) original_path = "[tfile]" tfile = file2text(tfile) @@ -56,16 +142,30 @@ // create a new datum without loading a map return - bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) - var/stored_index = 1 + src.bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + if(findtext(tfile, matches_tgm)) + map_format = MAP_TGM + else + map_format = MAP_DMM // Fallback + + // lists are structs don't you know :) + var/list/bounds = src.bounds + var/list/grid_models = src.grid_models + var/key_len = src.key_len + var/line_len = src.line_len + + var/stored_index = 1 + var/list/regexOutput //multiz lool - while(dmmRegex.Find(tfile, stored_index)) - stored_index = dmmRegex.next + while(dmm_regex.Find(tfile, stored_index)) + stored_index = dmm_regex.next + // Datum var lookup is expensive, this isn't + regexOutput = dmm_regex.group // "aa" = (/type{vars=blah}) - if(dmmRegex.group[1]) // Model - var/key = dmmRegex.group[1] + if(regexOutput[1]) // Model + var/key = regexOutput[1] if(grid_models[key]) // Duplicate model keys are ignored in DMMs continue if(key_len != length(key)) @@ -74,14 +174,14 @@ else CRASH("Inconsistent key length in DMM") if(!measureOnly) - grid_models[key] = dmmRegex.group[2] + grid_models[key] = regexOutput[2] // (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} - else if(dmmRegex.group[3]) // Coords + else if(regexOutput[3]) // Coords if(!key_len) CRASH("Coords before model definition in DMM") - var/curr_x = text2num(dmmRegex.group[3]) + var/curr_x = text2num(regexOutput[3]) if(curr_x < x_lower || curr_x > x_upper) continue @@ -90,314 +190,668 @@ gridSet.xcrd = curr_x //position of the currently processed square - gridSet.ycrd = text2num(dmmRegex.group[4]) - gridSet.zcrd = text2num(dmmRegex.group[5]) + gridSet.ycrd = text2num(regexOutput[4]) + gridSet.zcrd = text2num(regexOutput[5]) - bounds[MAP_MINX] = min(bounds[MAP_MINX], clamp(gridSet.xcrd, x_lower, x_upper)) + bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x) bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd) bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd) - var/list/gridLines = splittext(dmmRegex.group[6], "\n") + var/list/gridLines = splittext(regexOutput[6], "\n") gridSet.gridLines = gridLines var/leadingBlanks = 0 - while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "") + while(leadingBlanks < length(gridLines) && gridLines[++leadingBlanks] == "") if(leadingBlanks > 1) gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines. - if(!gridLines.len) // Skip it if only blank lines exist. + if(!length(gridLines)) // Skip it if only blank lines exist. continue gridSets += gridSet - if(gridLines.len && gridLines[gridLines.len] == "") - gridLines.Cut(gridLines.len) // Remove only one blank line at the end. + if(gridLines[length(gridLines)] == "") + gridLines.Cut(length(gridLines)) // Remove only one blank line at the end. - bounds[MAP_MINY] = min(bounds[MAP_MINY], clamp(gridSet.ycrd, y_lower, y_upper)) - gridSet.ycrd += gridLines.len - 1 // Start at the top and work down - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], clamp(gridSet.ycrd, y_lower, y_upper)) + bounds[MAP_MINY] = min(bounds[MAP_MINY], gridSet.ycrd) + gridSet.ycrd += length(gridLines) - 1 // Start at the top and work down + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd) - var/maxx = gridSet.xcrd - if(gridLines.len) //Not an empty map - maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1) + if(!line_len) + line_len = length(gridLines[1]) - bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper) + var/maxx = curr_x + if(length(gridLines)) //Not an empty map + maxx = max(maxx, curr_x + line_len / key_len - 1) + + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], maxx) CHECK_TICK // Indicate failure to parse any coordinates by nulling bounds if(bounds[1] == 1.#INF) - bounds = null - parsed_bounds = bounds + src.bounds = null + else + // Clamp all our mins and maxes down to the proscribed limits + bounds[MAP_MINX] = clamp(bounds[MAP_MINX], x_lower, x_upper) + bounds[MAP_MAXX] = clamp(bounds[MAP_MAXX], x_lower, x_upper) + bounds[MAP_MINY] = clamp(bounds[MAP_MINY], y_lower, y_upper) + bounds[MAP_MAXY] = clamp(bounds[MAP_MAXY], y_lower, y_upper) + + parsed_bounds = src.bounds + src.key_len = key_len + src.line_len = line_len /// Load the parsed map into the world. See [/proc/load_map] for arguments. -/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) +/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, whitelist = FALSE, new_z) //How I wish for RAII Master.StartLoadingMap() - . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) + . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z) Master.StopLoadingMap() +#define MAPLOADING_CHECK_TICK \ + if(TICK_CHECK) { \ + SSatoms.map_loader_stop(); \ + stoplag(); \ + SSatoms.map_loader_begin(); \ + } + // Do not call except via load() above. -/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE) +/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, new_z = FALSE) PRIVATE_PROC(TRUE) - var/list/areaCache = list() - var/list/modelCache = build_cache(no_changeturf) - var/space_key = modelCache[SPACE_KEY] - var/list/bounds - var/did_expand = FALSE - src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + // Tell ss atoms that we're doing maploading + // We'll have to account for this in the following tick_checks so it doesn't overflow + SSatoms.map_loader_begin() - for(var/I in gridSets) - var/datum/grid_set/gset = I - var/ycrd = gset.ycrd + y_offset - 1 - var/zcrd = gset.zcrd + z_offset - 1 - if(!cropMap && ycrd > world.maxy) - world.maxy = ycrd // Expand Y here. X is expanded in the loop below - did_expand = TRUE - var/zexpansion = zcrd > world.maxz - if(zexpansion) - if(cropMap) - continue - else - while (zcrd > world.maxz) //create a new z_level if needed - world.incrementMaxZ() - did_expand = FALSE - if(!no_changeturf) - WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") + // Loading used to be done in this proc + // We make the assumption that if the inner procs runtime, we WANT to do cleanup on them, but we should stil tell our parents we failed + // Since well, we did + var/sucessful = FALSE + switch(map_format) + if(MAP_TGM) + sucessful = _tgm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z) + else + sucessful = _dmm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z) - for(var/line in gset.gridLines) - if((ycrd - y_offset + 1) < y_lower || (ycrd - y_offset + 1) > y_upper) //Reverse operation and check if it is out of bounds of cropping. - --ycrd - continue - if(ycrd <= world.maxy && ycrd >= 1) - var/xcrd = gset.xcrd + x_offset - 1 - for(var/tpos = 1 to length(line) - key_len + 1 step key_len) - if((xcrd - x_offset + 1) < x_lower || (xcrd - x_offset + 1) > x_upper) //Same as above. - ++xcrd - continue //X cropping. - if(xcrd > world.maxx) - if(cropMap) - break - else - world.maxx = xcrd - did_expand = TRUE + // And we are done lads, call it off + SSatoms.map_loader_stop() - if(xcrd >= 1) - var/model_key = copytext(line, tpos, tpos + key_len) - var/no_afterchange = no_changeturf || zexpansion - if(!no_afterchange || (model_key != space_key)) - var/list/cache = modelCache[model_key] - if(!cache) - CRASH("Undefined model key in DMM: [model_key]") - build_coordinate(areaCache, cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop) - - // only bother with bounds that actually exist - bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrd) - bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd) - bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) - bounds[MAP_MAXX] = max(bounds[MAP_MAXX], xcrd) - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd) - bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) - #ifdef TESTING - else - ++turfsSkipped - #endif - CHECK_TICK - ++xcrd - --ycrd - - CHECK_TICK + if(new_z) + for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ]) + SSmapping.build_area_turfs(z_index) if(!no_changeturf) - for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) - var/turf/T = t - //we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs + for(var/turf/T as anything in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) + //we do this after we load everything in. if we don't, we'll have weird atmos bugs regarding atmos adjacent turfs T.AfterChange(CHANGETURF_IGNORE_AIR) + //if(expanded_x || expanded_y) + // SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, expanded_x, expanded_y) + #ifdef TESTING if(turfsSkipped) testing("Skipped loading [turfsSkipped] default turfs") #endif - if(did_expand) - world.refresh_atmos_grid() + return sucessful + +// Wanna clear something up about maps, talking in 255x255 here +// In the tgm format, each gridset contains 255 lines, each line representing one tile, with 255 total gridsets +// In the dmm format, each gridset contains 255 lines, each line representing one row of tiles, containing 255 * line length characters, with one gridset per z +// You can think of dmm as storing maps in rows, whereas tgm stores them in columns +/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z) + // setup + var/list/modelCache = build_cache(no_changeturf) + var/space_key = modelCache[SPACE_KEY] + var/list/bounds + src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + + // Building y coordinate ranges + var/y_relative_to_absolute = y_offset - 1 + var/x_relative_to_absolute = x_offset - 1 + + // Ok so like. something important + // We talk in "relative" coords here, so the coordinate system of the map datum + // This is so we can do offsets, but it is NOT the same as positions in game + // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones + // TGM maps process in columns, so the starting y will always be the max size + // We know y starts at 1 + var/datum/grid_set/first_column = gridSets[1] + var/relative_y = first_column.ycrd + var/highest_y = relative_y + y_relative_to_absolute + + if(!cropMap && highest_y > world.maxy) + world.maxy = highest_y // Expand Y here. X is expanded later on + expanded_y = TRUE + + // Skip Y coords that are above the smallest of the three params + // So maxy and y_upper get to act as thresholds, and relative_y can play + var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y) + // How many lines to skip because they'd be above the y cuttoff line + var/y_starting_skip = relative_y - y_skip_above + highest_y += y_starting_skip + + + // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go + var/line_count = length(first_column.gridLines) + var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start + var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0) + + // X setup + var/x_delta_with = x_upper + if(cropMap) + // Take our smaller crop threshold yes? + x_delta_with = min(x_delta_with, world.maxx) + + // We're gonna skip all the entries above the upper x, or maxx if cropMap is set + // The last column is guarenteed to have the highest x value we;ll encounter + // Even if z scales, this still works + var/datum/grid_set/last_column = gridSets[length(gridSets)] + var/final_x = last_column.xcrd + x_relative_to_absolute + + if(final_x > x_delta_with) + // If our relative x is greater then X upper, well then we've gotta limit our expansion + var/delta = max(final_x - x_delta_with, 0) + final_x -= delta + if(final_x > world.maxx && !cropMap) + world.maxx = final_x + expanded_x = TRUE + + var/lowest_x = max(x_lower, 1 - x_relative_to_absolute) + + // We make the assumption that the last block of turfs will have the highest embedded z in it + var/highest_z = last_column.zcrd + z_offset - 1 // Lets not just make a new z level each time we increment maxz + var/z_threshold = world.maxz + if(highest_z > z_threshold && cropMap) + for(var/i in z_threshold + 1 to highest_z) //create a new z_level if needed + world.incrementMaxZ() + if(!no_changeturf) + WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") + + for(var/datum/grid_set/gset as anything in gridSets) + var/true_xcrd = gset.xcrd + x_relative_to_absolute + + // any cutoff of x means we just shouldn't iterate this gridset + if(final_x < true_xcrd || lowest_x > gset.xcrd) + continue + + var/zcrd = gset.zcrd + z_offset - 1 + // If we're using changeturf, we disable it if we load into a z level we JUST created + var/no_afterchange = no_changeturf || zcrd > z_threshold + + // We're gonna track the first and last pairs of coords we find + // Since x is always incremented in steps of 1, we only need to deal in y + // The first x is guarenteed to be the lowest, the first y the highest, and vis versa + // This is faster then doing mins and maxes inside the hot loop below + var/first_found = FALSE + var/first_y = 0 + var/last_y = 0 + + var/ycrd = highest_y + // Everything following this line is VERY hot. + for(var/i in 1 + y_starting_skip to line_count - y_ending_skip) + if(gset.gridLines[i] == space_key && no_afterchange) + #ifdef TESTING + ++turfsSkipped + #endif + ycrd-- + MAPLOADING_CHECK_TICK + continue + + var/list/cache = modelCache[gset.gridLines[i]] + if(!cache) + SSatoms.map_loader_stop() + CRASH("Undefined model key in DMM: [gset.gridLines[i]]") + build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z) + + // only bother with bounds that actually exist + if(!first_found) + first_found = TRUE + first_y = ycrd + last_y = ycrd + ycrd-- + MAPLOADING_CHECK_TICK + + // The x coord never changes, so not tracking first x is safe + // If no ycrd is found, we assume this row is totally empty and just continue on + if(first_found) + bounds[MAP_MINX] = min(bounds[MAP_MINX], true_xcrd) + bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], true_xcrd) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) + return TRUE + +/// Stanrdard loading, not used in production +/// Doesn't take advantage of any tgm optimizations, which makes it slower but also more general +/// Use this if for some reason your map format is messy +/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z) + // setup + var/list/modelCache = build_cache(no_changeturf) + var/space_key = modelCache[SPACE_KEY] + var/list/bounds + var/key_len = src.key_len + src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + + var/y_relative_to_absolute = y_offset - 1 + var/x_relative_to_absolute = x_offset - 1 + var/line_len = src.line_len + for(var/datum/grid_set/gset as anything in gridSets) + var/relative_x = gset.xcrd + var/relative_y = gset.ycrd + var/true_xcrd = relative_x + x_relative_to_absolute + var/ycrd = relative_y + y_relative_to_absolute + var/zcrd = gset.zcrd + z_offset - 1 + if(!cropMap && ycrd > world.maxy) + world.maxy = ycrd // Expand Y here. X is expanded in the loop below + expanded_y = TRUE + var/zexpansion = zcrd > world.maxz + var/no_afterchange = no_changeturf + if(zexpansion) + if(cropMap) + continue + else + while (zcrd > world.maxz) //create a new z_level if needed + world.incrementMaxZ() + if(!no_changeturf) + WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") + no_afterchange = TRUE + // Ok so like. something important + // We talk in "relative" coords here, so the coordinate system of the map datum + // This is so we can do offsets, but it is NOT the same as positions in game + // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones + + // Skip Y coords that are above the smallest of the three params + // So maxy and y_upper get to act as thresholds, and relative_y can play + var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y) + // How many lines to skip because they'd be above the y cuttoff line + var/y_starting_skip = relative_y - y_skip_above + ycrd += y_starting_skip + + // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go + var/line_count = length(gset.gridLines) + var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start + var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0) + + // Now we're gonna precompute the x thresholds + // We skip all the entries below the lower x, or 1 + var/starting_x_delta = max(max(x_lower, 1 - x_relative_to_absolute) - relative_x, 0) + // The x loop counts by key length, so we gotta multiply here + var/x_starting_skip = starting_x_delta * key_len + true_xcrd += starting_x_delta + + // We're gonna skip all the entries above the upper x, or maxx if cropMap is set + var/x_target = line_len - key_len + 1 + var/x_step_count = ROUND_UP(x_target / key_len) + var/final_x = relative_x + (x_step_count - 1) + var/x_delta_with = x_upper + if(cropMap) + // Take our smaller crop threshold yes? + x_delta_with = min(x_delta_with, world.maxx) + if(final_x > x_delta_with) + // If our relative x is greater then X upper, well then we've gotta limit our expansion + var/delta = max(final_x - x_delta_with, 0) + x_step_count -= delta + final_x -= delta + x_target = x_step_count * key_len + if(final_x > world.maxx && !cropMap) + world.maxx = final_x + expanded_x = TRUE + + // We're gonna track the first and last pairs of coords we find + // The first x is guarenteed to be the lowest, the first y the highest, and vis versa + // This is faster then doing mins and maxes inside the hot loop below + var/first_found = FALSE + var/first_x = 0 + var/first_y = 0 + var/last_x = 0 + var/last_y = 0 + + // Everything following this line is VERY hot. How hot depends on the map format + // (Yes this does mean dmm is technically faster to parse. shut up) + for(var/i in 1 + y_starting_skip to line_count - y_ending_skip) + var/line = gset.gridLines[i] + + var/xcrd = true_xcrd + for(var/tpos in 1 + x_starting_skip to x_target step key_len) + var/model_key = copytext(line, tpos, tpos + key_len) + if(model_key == space_key && no_afterchange) + #ifdef TESTING + ++turfsSkipped + #endif + MAPLOADING_CHECK_TICK + ++xcrd + continue + var/list/cache = modelCache[model_key] + if(!cache) + SSatoms.map_loader_stop() + CRASH("Undefined model key in DMM: [model_key]") + build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z) + + // only bother with bounds that actually exist + if(!first_found) + first_found = TRUE + first_x = xcrd + first_y = ycrd + last_x = xcrd + last_y = ycrd + MAPLOADING_CHECK_TICK + ++xcrd + ycrd-- + MAPLOADING_CHECK_TICK + bounds[MAP_MINX] = min(bounds[MAP_MINX], first_x) + bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], last_x) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) return TRUE -/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths=null) +GLOBAL_LIST_EMPTY(map_model_default) + +/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths) + if(map_format == MAP_TGM) + return tgm_build_cache(no_changeturf, bad_paths) + return dmm_build_cache(no_changeturf, bad_paths) + +/datum/parsed_map/proc/tgm_build_cache(no_changeturf, bad_paths=null) if(modelCache && !bad_paths) return modelCache . = modelCache = list() var/list/grid_models = src.grid_models + var/set_space = FALSE + // Use where a list is needed, but where it will not be modified + // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later + var/static/list/default_list = GLOB.map_model_default // It's stupid, but it saves += list(list) + var/static/list/wrapped_default_list = list(default_list) // It's stupid, but it saves += list(list) + var/static/regex/var_edits = var_edits_tgm + + var/path_to_init = "" + // Reference to the attributes list we're currently filling, if any + var/list/current_attributes + // If we are currently editing a path or not + var/editing = FALSE for(var/model_key in grid_models) - var/model = grid_models[model_key] - var/list/members = list() //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored) - var/list/members_attributes = list() //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + // We're going to split models by newline + // This guarentees that each entry will be of interest to us + // Then we'll process them step by step + // Hopefully this reduces the cost from read_list that we'd otherwise have + var/list/lines = splittext(grid_models[model_key], "\n") + // Builds list of path/edits for later + // Of note: we cannot preallocate them to save time in list expansion later + // But fortunately lists allocate at least 8 entries normally anyway, and + // We are unlikely to have more then that many members + //will contain all members (paths) in model (in our example : /turf/unsimulated/wall) + var/list/members = list() + //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + var/list/members_attributes = list() ///////////////////////////////////////////////////////// //Constructing members and corresponding variables lists //////////////////////////////////////////////////////// + // string representation of the path to init + for(var/line in lines) + // We do this here to avoid needing to check at each return statement + // No harm in it anyway + MAPLOADING_CHECK_TICK - var/index = 1 - var/old_position = 1 - var/dpos + switch(line[length(line)]) + if(";") // Var edit, we'll apply it + // Var edits look like \tname = value; + // I'm gonna try capturing them with regex, since it ought to be the fastest here + // Should hand back key = value + var_edits.Find(line) + var/value = parse_constant(var_edits.group[2]) + if(istext(value)) + value = apply_text_macros(value) + current_attributes[var_edits.group[1]] = value + continue // Keep on keeping on brother + if("{") // Start of an edit, and so also the start of a path + editing = TRUE + current_attributes = list() // Init the list we'll be filling + members_attributes += list(current_attributes) + path_to_init = copytext(line, 1, -1) + if(",") // Either the end of a path, or the end of an edit + if(editing) // it was the end of a path + editing = FALSE + continue + members_attributes += wrapped_default_list // We know this is a path, and we also know it has no vv's. so we'll just set this to the default list + // Drop the last char mind + path_to_init = copytext(line, 1, -1) + if("}") // Gotta be the end of an area edit, let's check to be sure + if(editing) // it was the end of an area edit (shouldn't do those anyhow) + editing = FALSE + continue + stack_trace("ended a line on JUST a }, with no ongoing edit. What? Area shit?") + else // If we're editing, this is a var edit entry. the last one in a stack, cause god hates me. Otherwise, it's an area + if(editing) // I want inline I want inline I want inline + // Var edits look like \tname = value; + // I'm gonna try capturing them with regex, since it ought to be the fastest here + // Should hand back key = value + var_edits.Find(line) + var/value = parse_constant(var_edits.group[2]) + if(istext(value)) + value = apply_text_macros(value) + current_attributes[var_edits.group[1]] = value + continue // Keep on keeping on brother - while(dpos != 0) - //finding next member (e.g /turf/unsimulated/wall{icon_state = "rock"} or /area/mine/explored) - dpos = find_next_delimiter_position(model, old_position, ",", "{", "}") //find next delimiter (comma here) that's not within {...} + members_attributes += wrapped_default_list // We know this is a path, and we also know it has no vv's. so we'll just set this to the default list + path_to_init = line - var/full_def = trim_text(copytext(model, old_position, dpos)) //full definition, e.g : /obj/foo/bar{variables=derp} - var/variables_start = findtext(full_def, "{") - var/path_text = trim_text(copytext(full_def, 1, variables_start)) - var/atom_def = text2path(path_text) //path definition, e.g /obj/foo/bar - if(dpos) - old_position = dpos + length(model[dpos]) + + // Alright, if we've gotten to this point, our string is a path + // Oh and we don't trim it, because we require no padding for these + // Saves like 1.5 deciseconds + var/atom_def = text2path(path_to_init) //path definition, e.g /obj/foo/bar if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers! if(bad_paths) - LAZYOR(bad_paths[path_text], model_key) + // Rare case, avoid the var to save time most of the time + LAZYOR(bad_paths[copytext(line, 1, -1)], model_key) continue - members.Add(atom_def) - - //transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) - var/list/fields = list() - - if(variables_start)//if there's any variable - full_def = copytext(full_def, variables_start + length(full_def[variables_start]), -length(copytext_char(full_def, -1))) //removing the last '}' - fields = readlist(full_def, ";") - if(fields.len) - if(!trim(fields[fields.len])) - --fields.len - for(var/I in fields) - var/value = fields[I] - if(istext(value)) - fields[I] = apply_text_macros(value) - - //then fill the members_attributes list with the corresponding variables - members_attributes.len++ - members_attributes[index++] = fields - - CHECK_TICK + // Index is already incremented either way, just gotta set the path and all + members += atom_def //check and see if we can just skip this turf //So you don't have to understand this horrid statement, we can do this if - // 1. no_changeturf is set - // 2. the space_key isn't set yet + // 1. the space_key isn't set yet + // 2. no_changeturf is set // 3. there are exactly 2 members // 4. with no attributes // 5. and the members are world.turf and world.area // Basically, if we find an entry like this: "XXX" = (/turf/default, /area/default) // We can skip calling this proc every time we see XXX - if(no_changeturf \ - && !(.[SPACE_KEY]) \ + if(!set_space \ + && no_changeturf \ + && members_attributes.len == 2 \ + && members.len == 2 \ + && members_attributes[1] == default_list \ + && members_attributes[2] == default_list \ + && members[2] == world.area \ + && members[1] == world.turf + ) + set_space = TRUE + .[SPACE_KEY] = model_key + continue + + .[model_key] = list(members, members_attributes) + return . + +/// Builds key caches for general formats +/// Slower then the proc above, tho it could still be optimized slightly. it's just not a priority +/// Since we don't run DMM maps, ever. +/datum/parsed_map/proc/dmm_build_cache(no_changeturf, bad_paths=null) + if(modelCache && !bad_paths) + return modelCache + . = modelCache = list() + var/list/grid_models = src.grid_models + var/set_space = FALSE + // Use where a list is needed, but where it will not be modified + // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later + var/static/list/default_list = list(GLOB.map_model_default) + for(var/model_key in grid_models) + //will contain all members (paths) in model (in our example : /turf/unsimulated/wall) + var/list/members = list() + //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + var/list/members_attributes = list() + + var/model = grid_models[model_key] + ///////////////////////////////////////////////////////// + //Constructing members and corresponding variables lists + //////////////////////////////////////////////////////// + + var/model_index = 1 + while(model_path.Find(model, model_index)) + var/variables_start = 0 + var/member_string = model_path.group[1] + model_index = model_path.next + //findtext is a bit expensive, lets only do this if the last char of our string is a } (IE: we know we have vars) + //this saves about 25 miliseconds on my machine. Not a major optimization + if(member_string[length(member_string)] == "}") + variables_start = findtext(member_string, "{") + + var/path_text = TRIM_TEXT(copytext(member_string, 1, variables_start)) + var/atom_def = text2path(path_text) //path definition, e.g /obj/foo/bar + + if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers! + if(bad_paths) + LAZYOR(bad_paths[path_text], model_key) + continue + members += atom_def + + //transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) + // OF NOTE: this could be made faster by replacing readlist with a progressive regex + // I'm just too much of a bum to do it rn, especially since we mandate tgm format for any maps in repo + var/list/fields = default_list + if(variables_start)//if there's any variable + member_string = copytext(member_string, variables_start + length(member_string[variables_start]), -length(copytext_char(member_string, -1))) //removing the last '}' + fields = list(readlist(member_string, ";")) + for(var/I in fields) + var/value = fields[I] + if(istext(value)) + fields[I] = apply_text_macros(value) + + //then fill the members_attributes list with the corresponding variables + members_attributes += fields + MAPLOADING_CHECK_TICK + + //check and see if we can just skip this turf + //So you don't have to understand this horrid statement, we can do this if + // 1. the space_key isn't set yet + // 2. no_changeturf is set + // 3. there are exactly 2 members + // 4. with no attributes + // 5. and the members are world.turf and world.area + // Basically, if we find an entry like this: "XXX" = (/turf/default, /area/default) + // We can skip calling this proc every time we see XXX + if(!set_space \ + && no_changeturf \ && members.len == 2 \ && members_attributes.len == 2 \ && length(members_attributes[1]) == 0 \ && length(members_attributes[2]) == 0 \ && (world.area in members) \ && (world.turf in members)) - + set_space = TRUE .[SPACE_KEY] = model_key continue - .[model_key] = list(members, members_attributes) + return . -/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num) +/datum/parsed_map/proc/build_coordinate(list/model, turf/crds, no_changeturf as num, placeOnTop as num, new_z) + // If we don't have a turf, nothing we will do next will actually acomplish anything, so just go back + // Note, this would actually drop area vvs in the tile, but like, why tho + if(!crds) + return var/index var/list/members = model[1] var/list/members_attributes = model[2] + // We use static lists here because it's cheaper then passing them around + var/static/list/default_list = GLOB.map_model_default //////////////// //Instanciation //////////////// + if(turf_blacklist?[crds]) + return + + SSatoms.map_loader_stop() // yogs - needed to properly initialize areas + //The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile //first instance the /area and remove it from the members list index = members.len if(members[index] != /area/template_noop) - var/atype = members[index] - world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation - var/atom/instance = areaCache[atype] - if (!instance) - instance = GLOB.areas_by_type[atype] - if (!instance) - instance = new atype(null) - areaCache[atype] = instance - if(crds) - instance.contents.Add(crds) + var/area/area_instance + if(members_attributes[index] != default_list) + world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation + area_instance = loaded_areas[members[index]] + if(!area_instance) + var/area_type = members[index] + // If this parsed map doesn't have that area already, we check the global cache + area_instance = GLOB.areas_by_type[area_type] + // If the global list DOESN'T have this area it's either not a unique area, or it just hasn't been created yet + if (!area_instance) + area_instance = new area_type(null) + if(!area_instance) + CRASH("[area_type] failed to be new'd, what'd you do?") + loaded_areas[area_type] = area_instance - if(GLOB.use_preloader && instance) + if(!new_z) + var/area/old_area = crds.loc + old_area.turfs_to_uncontain += crds + area_instance.contained_turfs.Add(crds) + area_instance.contents.Add(crds) + + if(GLOB.use_preloader) + world.preloader_load(area_instance) + + SSatoms.map_loader_begin() // yogs - needed to properly initialize areas + + // Index right before /area is /turf + index-- + var/atom/instance + //then instance the /turf + //NOTE: this used to place any turfs before the last "underneath" it using .appearance and underlays + //We don't actually use this, and all it did was cost cpu, so we don't do this anymore + if(members[index] != /turf/template_noop) + if(members_attributes[index] != default_list) + world.preloader_setup(members_attributes[index], members[index]) + + // Note: we make the assertion that the last path WILL be a turf. if it isn't, this will fail. + if(placeOnTop) + instance = crds.PlaceOnTop(null, members[index], CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE)) + else if(no_changeturf) + instance = create_atom(members[index], crds)//first preloader pass + else + instance = crds.ChangeTurf(members[index], null, CHANGETURF_DEFER_CHANGE) + + if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New() world.preloader_load(instance) - - //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect - - var/first_turf_index = 1 - while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members - first_turf_index++ - - //turn off base new Initialization until the whole thing is loaded - SSatoms.map_loader_begin() - //instanciate the first /turf - var/turf/T - if(members[first_turf_index] != /turf/template_noop) - T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,placeOnTop) - - if(T) - //if others /turf are presents, simulates the underlays piling effect - index = first_turf_index + 1 - while(index <= members.len - 1) // Last item is an /area - var/underlay = T.appearance - T = instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop)//instance new turf - T.underlays += underlay - index++ + MAPLOADING_CHECK_TICK //finally instance all remainings objects/mobs - for(index in 1 to first_turf_index-1) - instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop) - //Restore initialization to the previous value - SSatoms.map_loader_stop() + for(var/atom_index in 1 to index-1) + if(members_attributes[atom_index] != default_list) + world.preloader_setup(members_attributes[atom_index], members[atom_index]) + + // We make the assertion that only /atom s will be in this portion of the code. if that isn't true, this will fail + instance = create_atom(members[atom_index], crds)//first preloader pass + + if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New() + world.preloader_load(instance) + MAPLOADING_CHECK_TICK //////////////// //Helpers procs //////////////// -//Instance an atom at (x,y,z) and gives it the variables in attributes -/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop) - world.preloader_setup(attributes, path) - - if(crds) - if(ispath(path, /turf)) - if(placeOnTop) - . = crds.PlaceOnTop(null, path, CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE)) - else if(!no_changeturf) - . = crds.ChangeTurf(path, null, CHANGETURF_DEFER_CHANGE) - else - . = create_atom(path, crds)//first preloader pass - else - . = create_atom(path, crds)//first preloader pass - - if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New() - world.preloader_load(.) - - //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize - if(TICK_CHECK) - SSatoms.map_loader_stop() - stoplag() - SSatoms.map_loader_begin() - /datum/parsed_map/proc/create_atom(path, crds) set waitfor = FALSE . = new path (crds) -//text trimming (both directions) helper proc -//optionally removes quotes before and after the text (for variable name) -/datum/parsed_map/proc/trim_text(what as text,trim_quotes=0) - if(trim_quotes) - return trimQuotesRegex.Replace(what, "") - else - return trimRegex.Replace(what, "") - - //find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape //returns 0 if reached the last delimiter /datum/parsed_map/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"") @@ -412,7 +866,6 @@ return next_delimiter - //build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) //return the filled list /datum/parsed_map/proc/readlist(text as text, delimiter=",") @@ -420,28 +873,29 @@ if (!text) return + // If we're using a semi colon, we can do this as splittext rather then constant calls to find_next_delimiter_position + // This does make the code a bit harder to read, but saves a good bit of time so suck it up var/position var/old_position = 1 - while(position != 0) // find next delimiter that is not within "..." position = find_next_delimiter_position(text,old_position,delimiter) // check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7)) var/equal_position = findtext(text,"=",old_position, position) - - var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position))) - var/left_constant = delimiter == ";" ? trim_left : parse_constant(trim_left) + var/trim_left = TRIM_TEXT(copytext(text,old_position,(equal_position ? equal_position : position))) + var/left_constant = parse_constant(trim_left) if(position) old_position = position + length(text[position]) + if(!left_constant) // damn newlines man. Exists to provide behavior consistency with the above loop. not a major cost becuase this path is cold + continue if(equal_position && !isnum(left_constant)) // Associative var, so do the association. // Note that numbers cannot be keys - the RHS is dropped if so. - var/trim_right = trim_text(copytext(text, equal_position + length(text[equal_position]), position)) + var/trim_right = TRIM_TEXT(copytext(text, equal_position + length(text[equal_position]), position)) var/right_constant = parse_constant(trim_right) .[left_constant] = right_constant - else // simple var . += list(left_constant) @@ -453,7 +907,10 @@ // string if(text[1] == "\"") - return copytext(text, length(text[1]) + 1, findtext(text, "\"", length(text[1]) + 1)) + // insert implied locate \" and length("\"") here + // It's a minimal timesave but it is a timesave + // Safe becuase we're guarenteed trimmed constants + return copytext(text, 2, -1) // list if(copytext(text, 1, 6) == "list(")//6 == length("list(") + 1 @@ -481,4 +938,10 @@ /datum/parsed_map/Destroy() ..() + if(turf_blacklist) + turf_blacklist.Cut() + parsed_bounds.Cut() + bounds.Cut() + grid_models.Cut() + gridSets.Cut() return QDEL_HINT_HARDDEL_NOW diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm index ea769500b78b..6162364d0470 100644 --- a/code/modules/mapping/ruins.dm +++ b/code/modules/mapping/ruins.dm @@ -1,4 +1,4 @@ -/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf) +/datum/map_template/ruin/proc/try_to_place(z, list/allowed_areas_typecache, turf/forced_turf, clear_below) var/sanity = forced_turf ? 1 : PLACEMENT_TRIES while(sanity > 0) sanity-- @@ -6,18 +6,22 @@ var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2) var/turf/central_turf = locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z) var/valid = TRUE + var/list/affected_turfs = get_affected_turfs(central_turf,1) + var/list/affected_areas = list() - for(var/turf/check in get_affected_turfs(central_turf,1)) - var/area/new_area = get_area(check) + for(var/turf/check in affected_turfs) + // Use assoc lists to move this out, it's easier that way if(check.flags_1 & NO_RUINS_1) valid = FALSE - else - valid = FALSE // set to false before we check - for(var/type in allowed_areas) - if(istype(new_area, type)) // it's at least one of our types so it's whitelisted - valid = TRUE - break - if(!valid) + break + + var/area/new_area = get_area(check) + affected_areas[new_area] = TRUE + + // This is faster yes. Only BARELY but it is faster + for(var/area/affct_area as anything in affected_areas) + if(!allowed_areas_typecache[affct_area.type]) + valid = FALSE break if(!valid) @@ -25,29 +29,32 @@ testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])") - for(var/i in get_affected_turfs(central_turf, 1)) - var/turf/T = i - for(var/obj/structure/spawner/nest in T) - qdel(nest) - for(var/mob/living/simple_animal/monster in T) - qdel(monster) - for(var/obj/structure/flora/ash/plant in T) - qdel(plant) + if(clear_below) + var/list/static/clear_below_typecache = typecacheof(list( + /obj/structure/spawner, + /mob/living/simple_animal, + /obj/structure/flora + )) + for(var/turf/T as anything in affected_turfs) + for(var/atom/thing as anything in T) + if(clear_below_typecache[thing.type]) + qdel(thing) load(central_turf,centered = TRUE) loaded++ - for(var/turf/T in get_affected_turfs(central_turf, 1)) + for(var/turf/T in affected_turfs) T.flags_1 |= NO_RUINS_1 new /obj/effect/landmark/ruin(central_turf, src) return central_turf -/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins) +/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins, clear_below = FALSE) if(!z_levels || !z_levels.len) WARNING("No Z levels provided - Not generating ruins") return + var/list/whitelist_typecache = typecacheof(whitelist) for(var/zl in z_levels) var/turf/T = locate(1, 1, zl) @@ -111,7 +118,7 @@ else break outer - placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf) + placed_turf = current_pick.try_to_place(target_z,whitelist_typecache,forced_turf,clear_below) if(!placed_turf) continue else diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index 265d2c996d0f..6c291bf060c7 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -14,11 +14,11 @@ turf_type = /turf/open/space/transit /datum/turf_reservation/proc/Release() - var/v = reserved_turfs.Copy() - for(var/i in reserved_turfs) - reserved_turfs -= i - SSmapping.used_turfs -= i - SSmapping.reserve_turfs(v) + var/list/reserved_copy = reserved_turfs.Copy() + SSmapping.used_turfs -= reserved_turfs + reserved_turfs = list() + // Makes the linter happy, even tho we don't await this + INVOKE_ASYNC(SSmapping, /datum/controller/subsystem/mapping/proc/reserve_turfs, reserved_copy) /datum/turf_reservation/proc/Reserve(width, height, zlevel) if(width > world.maxx || height > world.maxy || width < 1 || height < 1) diff --git a/code/modules/mapping/space_management/space_transition.dm b/code/modules/mapping/space_management/space_transition.dm index 5a9d52033c1d..7311681fbfc4 100644 --- a/code/modules/mapping/space_management/space_transition.dm +++ b/code/modules/mapping/space_management/space_transition.dm @@ -22,122 +22,141 @@ neigbours[TEXT_WEST] = P.spl P.spl.neigbours[TEXT_EAST] = src +#define CHORDS_TO_1D(x, y, grid_diameter) ((x) + ((y) - 1) * (grid_diameter)) /datum/space_transition_point //this is explicitly utilitarian datum type made specially for the space map generation and are absolutely unusable for anything else var/list/neigbours = list() var/x var/y var/datum/space_level/spl -/datum/space_transition_point/New(nx, ny, list/point_grid) - if(!point_grid) +/datum/space_transition_point/New(nx, ny, list/grid) + if(!grid) qdel(src) return - var/list/L = point_grid[1] - if(nx > point_grid.len || ny > L.len) + var/grid_diameter = sqrt(length(grid)) + if(nx > grid_diameter || ny > grid_diameter) + stack_trace("Attempted to set a position outside the size of [grid_diameter]") qdel(src) return x = nx y = ny - if(point_grid[x][y]) + var/position = CHORDS_TO_1D(x, y, grid_diameter) + if(grid[position]) return - point_grid[x][y] = src + grid[position] = src -/datum/space_transition_point/proc/set_neigbours(list/grid) - var/max_X = grid.len - var/list/max_Y = grid[1] - max_Y = max_Y.len +/datum/space_transition_point/proc/set_neigbours(list/grid, size) neigbours.Cut() - if(x+1 <= max_X) - neigbours |= grid[x+1][y] + + if(x+1 <= size) + neigbours |= grid[CHORDS_TO_1D(x+1, y, size)] if(x-1 >= 1) - neigbours |= grid[x-1][y] - if(y+1 <= max_Y) - neigbours |= grid[x][y+1] + neigbours |= grid[CHORDS_TO_1D(x-1, y, size)] + if(y+1 <= size) + neigbours |= grid[CHORDS_TO_1D(x, y + 1, size)] if(y-1 >= 1) - neigbours |= grid[x][y-1] + neigbours |= grid[CHORDS_TO_1D(x, y - 1, size)] /datum/controller/subsystem/mapping/proc/setup_map_transitions() //listamania - var/list/SLS = list() + var/list/transition_levels = list() var/list/cached_z_list = z_list - var/conf_set_len = 0 - for(var/A in cached_z_list) - var/datum/space_level/D = A - if (D.linkage == CROSSLINKED) - SLS.Add(D) - conf_set_len++ - var/list/point_grid[conf_set_len*2+1][conf_set_len*2+1] - var/list/grid = list() - var/datum/space_transition_point/P - for(var/i = 1, i<=conf_set_len*2+1, i++) - for(var/j = 1, j<=conf_set_len*2+1, j++) - P = new/datum/space_transition_point(i,j, point_grid) - point_grid[i][j] = P - grid.Add(P) - for(var/datum/space_transition_point/pnt in grid) - pnt.set_neigbours(point_grid) - P = point_grid[conf_set_len+1][conf_set_len+1] + for(var/datum/space_level/level as anything in cached_z_list) + if (level.linkage == CROSSLINKED) + transition_levels.Add(level) + + var/grid_diameter = (length(transition_levels) * 2) + 1 + var/list/grid = new /list(grid_diameter ** 2) + + var/datum/space_transition_point/point + for(var/x in 1 to grid_diameter) + for(var/y in 1 to grid_diameter) + point = new/datum/space_transition_point(x, y, grid) + grid[CHORDS_TO_1D(x, y, grid_diameter)] = point + for(point as anything in grid) + point.set_neigbours(grid, grid_diameter) + + var/center = round(grid_diameter / 2) + point = grid[CHORDS_TO_1D(grid_diameter, center, center)] + grid.Cut() + + var/list/transition_pick = transition_levels.Copy() var/list/possible_points = list() var/list/used_points = list() - grid.Cut() - while(SLS.len) - var/datum/space_level/D = pick_n_take(SLS) - D.xi = P.x - D.yi = P.y - P.spl = D - possible_points |= P.neigbours - used_points |= P + while(transition_pick.len) + var/datum/space_level/level = pick_n_take(transition_pick) + level.xi = point.x + level.yi = point.y + point.spl = level + possible_points |= point.neigbours + used_points |= point possible_points.Remove(used_points) - D.set_neigbours(used_points) - P = pick(possible_points) + level.set_neigbours(used_points) + point = pick(possible_points) CHECK_TICK + + // Now that we've handed out neighbors, we're gonna handle an edge case + // Need to check if all our levels have neighbors in all directions + // If they don't, we'll make them wrap all the way around to the other side of the grid + for(var/direction in GLOB.cardinals) + var/dir = "[direction]" + var/inverse = "[turn(direction, 180)]" + for(var/datum/space_level/level as anything in transition_levels) + // If we have something in this dir that isn't just us, continue on + if(level.neigbours[dir] && level.neigbours[dir] != level) + continue + var/datum/space_level/head = level + while(head.neigbours[inverse] && head.neigbours[inverse] != head) + head = head.neigbours[inverse] + + // Alllright we've landed on someone who we can wrap around onto safely, let's make that connection yeah? + head.neigbours[inverse] = level + level.neigbours[dir] = head //Lists below are pre-calculated values arranged in the list in such a way to be easily accessable in the loop by the counter //Its either this or madness with lotsa math - var/list/x_pos_beginning = list(1, 1, world.maxx - TRANSITIONEDGE, 1) //x values of the lowest-leftest turfs of the respective 4 blocks on each side of zlevel - var/list/y_pos_beginning = list(world.maxy - TRANSITIONEDGE, 1, 1 + TRANSITIONEDGE, 1 + TRANSITIONEDGE) //y values respectively + var/inner_max_x = world.maxx - TRANSITIONEDGE + var/inner_max_y = world.maxy - TRANSITIONEDGE + var/list/x_pos_beginning = list(1, 1, inner_max_x, 1) //x values of the lowest-leftest turfs of the respective 4 blocks on each side of zlevel + var/list/y_pos_beginning = list(inner_max_y, 1, 1 + TRANSITIONEDGE, 1 + TRANSITIONEDGE) //y values respectively var/list/x_pos_ending = list(world.maxx, world.maxx, world.maxx, 1 + TRANSITIONEDGE) //x values of the highest-rightest turfs of the respective 4 blocks on each side of zlevel - var/list/y_pos_ending = list(world.maxy, 1 + TRANSITIONEDGE, world.maxy - TRANSITIONEDGE, world.maxy - TRANSITIONEDGE) //y values respectively - var/list/x_pos_transition = list(1, 1, TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 1) //values of x for the transition from respective blocks on the side of zlevel, 1 is being translated into turfs respective x value later in the code - var/list/y_pos_transition = list(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 1, 1, 1) //values of y for the transition from respective blocks on the side of zlevel, 1 is being translated into turfs respective y value later in the code + var/list/y_pos_ending = list(world.maxy, 1 + TRANSITIONEDGE, inner_max_y, inner_max_y) //y values respectively + var/list/x_pos_transition = list(1, 1, TRANSITIONEDGE + 2, inner_max_x - 1) //values of x for the transition from respective blocks on the side of zlevel, 1 is being translated into turfs respective x value later in the code + var/list/y_pos_transition = list(TRANSITIONEDGE + 2, inner_max_y - 1, 1, 1) //values of y for the transition from respective blocks on the side of zlevel, 1 is being translated into turfs respective y value later in the code - for(var/I in cached_z_list) - var/datum/space_level/D = I - if(!D.neigbours.len) + for(var/datum/space_level/level as anything in cached_z_list) + if(!level.neigbours.len) continue - var/zlevelnumber = D.z_value + var/zlevelnumber = level.z_value for(var/side in 1 to 4) var/turf/beginning = locate(x_pos_beginning[side], y_pos_beginning[side], zlevelnumber) var/turf/ending = locate(x_pos_ending[side], y_pos_ending[side], zlevelnumber) var/list/turfblock = block(beginning, ending) var/dirside = 2**(side-1) - var/zdestination = zlevelnumber - if(D.neigbours["[dirside]"] && D.neigbours["[dirside]"] != D) - D = D.neigbours["[dirside]"] - zdestination = D.z_value - else - dirside = turn(dirside, 180) - while(D.neigbours["[dirside]"] && D.neigbours["[dirside]"] != D) - D = D.neigbours["[dirside]"] - zdestination = D.z_value - D = I + var/x_target = x_pos_transition[side] == 1 ? 0 : x_pos_transition[side] + var/y_target = y_pos_transition[side] == 1 ? 0 : y_pos_transition[side] + var/datum/space_level/neighbor = level.neigbours["[dirside]"] + var/zdestination = neighbor.z_value + for(var/turf/open/space/S in turfblock) - S.destination_x = x_pos_transition[side] == 1 ? S.x : x_pos_transition[side] - S.destination_y = y_pos_transition[side] == 1 ? S.y : y_pos_transition[side] + S.destination_x = x_target || S.x + S.destination_y = y_target || S.y S.destination_z = zdestination // Mirage border code var/mirage_dir if(S.x == 1 + TRANSITIONEDGE) mirage_dir |= WEST - else if(S.x == world.maxx - TRANSITIONEDGE) + else if(S.x == inner_max_x) mirage_dir |= EAST if(S.y == 1 + TRANSITIONEDGE) mirage_dir |= SOUTH - else if(S.y == world.maxy - TRANSITIONEDGE) + else if(S.y == inner_max_y) mirage_dir |= NORTH if(!mirage_dir) continue - var/turf/place = locate(S.destination_x, S.destination_y, S.destination_z) + var/turf/place = locate(S.destination_x, S.destination_y, zdestination) S.AddComponent(/datum/component/mirage_border, place, mirage_dir) + +#undef CHORDS_TO_1D diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm index 6129c5fd2b6a..dbaad83f229b 100644 --- a/code/modules/mapping/space_management/zlevel_manager.dm +++ b/code/modules/mapping/space_management/zlevel_manager.dm @@ -14,9 +14,10 @@ for (var/I in 1 to default_map_traits.len) var/list/features = default_map_traits[I] var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS]) + build_area_turfs(I, FALSE) z_list += S -/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level) +/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, filled_with_space = TRUE, contain_turfs = TRUE) SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, args) var/new_z = z_list.len + 1 if (world.maxz < new_z) @@ -24,6 +25,8 @@ CHECK_TICK // TODO: sleep here if the Z level needs to be cleared var/datum/space_level/S = new z_type(new_z, name, traits) + if(contain_turfs) + build_area_turfs(new_z, filled_with_space) z_list += S return S diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 042df8c446a5..b5beb891ebe6 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -404,8 +404,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp to_chat(usr, "Not when you're not dead!") return var/list/filtered = list() - for(var/V in GLOB.sortedAreas) - var/area/A = V + for(var/area/A as anything in get_sorted_areas()) if(!A.hidden) filtered += A var/area/thearea = input("Area to jump to", "BOOYEA") as null|anything in filtered diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm index 20ba10b0e18a..200dfb1f072d 100644 --- a/code/modules/procedural_mapping/mapGenerators/repair.dm +++ b/code/modules/procedural_mapping/mapGenerators/repair.dm @@ -34,7 +34,7 @@ var/list/obj/structure/cable/cables = list() var/list/atom/atoms = list() - repopulate_sorted_areas() + require_area_resort() for(var/L in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], SSmapping.station_start), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], z_offset - 1))) diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 80737beb4101..abb96d99fb7a 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -133,19 +133,21 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) GLOBAL_VAR_INIT(emergency_access, FALSE) /proc/make_maint_all_access() - for(var/area/maintenance/A in world) - for(var/obj/machinery/door/airlock/D in A) - D.emergency = TRUE - D.update_icon(0) + for(var/area/maintenance/A in GLOB.areas) + for(var/turf/in_area as anything in A.get_contained_turfs()) + for(var/obj/machinery/door/airlock/D in in_area) + D.emergency = TRUE + D.update_icon(ALL, 0) minor_announce("Access restrictions on maintenance and external airlocks have been lifted.", "Attention! Station-wide emergency declared!",1) GLOB.emergency_access = TRUE SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "enabled")) /proc/revoke_maint_all_access() - for(var/area/maintenance/A in world) - for(var/obj/machinery/door/airlock/D in A) - D.emergency = FALSE - D.update_icon(0) + for(var/area/maintenance/A in GLOB.areas) + for(var/turf/in_area as anything in A.get_contained_turfs()) + for(var/obj/machinery/door/airlock/D in in_area) + D.emergency = FALSE + D.update_icon(ALL, 0) minor_announce("Access restrictions in maintenance areas have been restored.", "Attention! Station-wide emergency rescinded:") GLOB.emergency_access = FALSE SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "disabled")) @@ -157,4 +159,4 @@ GLOBAL_VAR_INIT(emergency_access, FALSE) #undef KEYCARD_RED_ALERT #undef KEYCARD_EMERGENCY_MAINTENANCE_ACCESS -#undef KEYCARD_BSA_UNLOCK \ No newline at end of file +#undef KEYCARD_BSA_UNLOCK diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index ab5fa6e699cd..44d7c3b0c22f 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -37,7 +37,7 @@ areas = list() var/list/new_latejoin = list() - for(var/area/shuttle/arrival/A in GLOB.sortedAreas) + for(var/area/shuttle/arrival/A in GLOB.areas) for(var/obj/structure/chair/C in A) new_latejoin += C if(!console) diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index f6f670d66f74..e8094fbc3f93 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -388,7 +388,7 @@ if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch sound_played = 1 //Only rev them up once. var/list/areas = list() - for(var/area/shuttle/escape/E in GLOB.sortedAreas) + for(var/area/shuttle/escape/E in GLOB.areas) areas += E hyperspace_sound(HYPERSPACE_WARMUP, areas) @@ -400,7 +400,7 @@ //now move the actual emergency shuttle to its transit dock var/list/areas = list() - for(var/area/shuttle/escape/E in GLOB.sortedAreas) + for(var/area/shuttle/escape/E in GLOB.areas) areas += E hyperspace_sound(HYPERSPACE_LAUNCH, areas) enterTransit() @@ -415,7 +415,7 @@ if(SHUTTLE_ESCAPE) if(sound_played && time_left <= HYPERSPACE_END_TIME) var/list/areas = list() - for(var/area/shuttle/escape/E in GLOB.sortedAreas) + for(var/area/shuttle/escape/E in GLOB.areas) areas += E hyperspace_sound(HYPERSPACE_END, areas) if(time_left <= PARALLAX_LOOP_TIME) diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm index 36f2c821c977..fcef0f3bc818 100644 --- a/code/modules/shuttle/on_move.dm +++ b/code/modules/shuttle/on_move.dm @@ -151,7 +151,9 @@ All ShuttleMove procs go here return TRUE contents -= oldT + turfs_to_uncontain += oldT underlying_old_area.contents += oldT + underlying_old_area.contained_turfs += oldT oldT.change_area(src, underlying_old_area) //The old turf has now been given back to the area that turf originaly belonged to @@ -159,7 +161,9 @@ All ShuttleMove procs go here parallax_movedir = old_dest_area.parallax_movedir old_dest_area.contents -= newT + old_dest_area.turfs_to_uncontain += newT contents += newT + contained_turfs += newT newT.change_area(old_dest_area, src) return TRUE diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 16b23fd70cc4..db6f40af8e93 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -482,7 +482,9 @@ if(!oldT || !istype(oldT.loc, area_type)) continue var/area/old_area = oldT.loc + old_area.turfs_to_uncontain += oldT underlying_area.contents += oldT + underlying_area.contained_turfs += oldT oldT.change_area(old_area, underlying_area) oldT.empty(FALSE) diff --git a/yogstation.dme b/yogstation.dme index 8f80014defd6..b20705b6b816 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -119,7 +119,7 @@ #include "code\__DEFINES\time.dm" #include "code\__DEFINES\tools.dm" #include "code\__DEFINES\traits.dm" -#include "code\__DEFINES\turf_flags.dm" +#include "code\__DEFINES\turfs.dm" #include "code\__DEFINES\typeids.dm" #include "code\__DEFINES\vehicles.dm" #include "code\__DEFINES\vv.dm" @@ -275,6 +275,7 @@ #include "code\controllers\subsystem\acid.dm" #include "code\controllers\subsystem\adjacent_air.dm" #include "code\controllers\subsystem\air.dm" +#include "code\controllers\subsystem\area_contents.dm" #include "code\controllers\subsystem\assets.dm" #include "code\controllers\subsystem\atoms.dm" #include "code\controllers\subsystem\augury.dm"