mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 23:58:07 +01:00
[MIRROR] Macro optimizes SSmapping saving 50% [MDB IGNORE] (#16375)
* Macro optimizes SSmapping saving 50% * fix merge issues, update to current turf_blacklist api Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: tastyfish <crazychris32@gmail.com>
This commit is contained in:
co-authored by
LemonInTheDark
tastyfish
parent
20fb529cf5
commit
c6de4c93b9
@@ -169,6 +169,7 @@
|
||||
var/list/turf_blacklist = list()
|
||||
update_blacklist(T, turf_blacklist)
|
||||
|
||||
UNSETEMPTY(turf_blacklist)
|
||||
parsed.turf_blacklist = turf_blacklist
|
||||
if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top))
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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
|
||||
@@ -10,15 +11,14 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
|
||||
/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 = deep_copy_list(value)
|
||||
#ifdef TESTING
|
||||
|
||||
+325
-193
@@ -11,6 +11,7 @@
|
||||
|
||||
/datum/parsed_map
|
||||
var/original_path
|
||||
/// The length of a key in this file. This is promised by the standard to be static
|
||||
var/key_len = 0
|
||||
var/list/grid_models = list()
|
||||
var/list/gridSets = list()
|
||||
@@ -22,19 +23,21 @@
|
||||
/// Offset bounds. Same as parsed_bounds until load().
|
||||
var/list/bounds
|
||||
|
||||
///any turf in this list is skipped inside of build_coordinate
|
||||
var/list/turf_blacklist = list()
|
||||
///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")
|
||||
|
||||
#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).
|
||||
@@ -52,6 +55,9 @@
|
||||
|
||||
/// 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)
|
||||
@@ -59,16 +65,23 @@
|
||||
// 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)
|
||||
// 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/stored_index = 1
|
||||
var/list/regexOutput
|
||||
//multiz lool
|
||||
while(dmmRegex.Find(tfile, stored_index))
|
||||
stored_index = dmmRegex.next
|
||||
// Datum var lookup is expensive, this isn't
|
||||
regexOutput = dmmRegex.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))
|
||||
@@ -77,14 +90,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
|
||||
@@ -93,73 +106,96 @@
|
||||
|
||||
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)
|
||||
var/maxx = curr_x
|
||||
if(length(gridLines)) //Not an empty map
|
||||
maxx = max(maxx, curr_x + length(gridLines[1]) / key_len - 1)
|
||||
|
||||
bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper)
|
||||
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
|
||||
|
||||
/// 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, list/blacklisted_turfs) // SKYRAT EDIT CHANGE - Added blacklisted_turfs
|
||||
turf_blacklist = blacklisted_turfs // SKYRAT EDIT ADDITION
|
||||
/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)
|
||||
//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)
|
||||
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)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/list/areaCache = list()
|
||||
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)
|
||||
|
||||
// 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()
|
||||
|
||||
//used for sending the maxx and maxy expanded global signals at the end of this proc
|
||||
var/has_expanded_world_maxx = FALSE
|
||||
var/has_expanded_world_maxy = FALSE
|
||||
|
||||
var/y_relative_to_absolute = y_offset - 1
|
||||
var/x_relative_to_absolute = x_offset - 1
|
||||
for(var/datum/grid_set/gset as anything in gridSets)
|
||||
var/ycrd = gset.ycrd + y_offset - 1
|
||||
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
|
||||
has_expanded_world_maxy = TRUE
|
||||
var/zexpansion = zcrd > world.maxz
|
||||
var/no_afterchange = no_changeturf
|
||||
if(zexpansion)
|
||||
if(cropMap)
|
||||
continue
|
||||
@@ -168,49 +204,141 @@
|
||||
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
|
||||
|
||||
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
|
||||
// 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
|
||||
|
||||
var/line_length = 0
|
||||
if(line_count)
|
||||
// This is promised as static, so we will treat it as such
|
||||
line_length = length(gset.gridLines[1])
|
||||
// We're gonna skip all the entries above the upper x, or maxx if cropMap is set
|
||||
var/x_target = line_length - 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
|
||||
has_expanded_world_maxx = 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)
|
||||
|
||||
// This is the "is this map tgm" check
|
||||
if(key_len == line_length)
|
||||
// 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
|
||||
// since this is the tgm branch any cutoff of x means we just shouldn't iterate this gridset
|
||||
if(!x_step_count || x_starting_skip)
|
||||
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
|
||||
has_expanded_world_maxx = TRUE
|
||||
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)
|
||||
for(var/i in 1 + y_starting_skip to line_count - y_ending_skip)
|
||||
var/line = gset.gridLines[i]
|
||||
if(line == space_key && no_afterchange)
|
||||
#ifdef TESTING
|
||||
++turfsSkipped
|
||||
#endif
|
||||
ycrd--
|
||||
MAPLOADING_CHECK_TICK
|
||||
continue
|
||||
|
||||
// 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)
|
||||
var/list/cache = modelCache[line]
|
||||
if(!cache)
|
||||
SSatoms.map_loader_stop()
|
||||
CRASH("Undefined model key in DMM: [line]")
|
||||
build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
|
||||
|
||||
// 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 this is safe
|
||||
if(first_found)
|
||||
first_x = true_xcrd
|
||||
last_x = true_xcrd
|
||||
else
|
||||
// This is the dmm parser, note the double loop
|
||||
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
|
||||
else
|
||||
++turfsSkipped
|
||||
++turfsSkipped
|
||||
#endif
|
||||
CHECK_TICK
|
||||
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)
|
||||
|
||||
// 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
|
||||
|
||||
CHECK_TICK
|
||||
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)
|
||||
|
||||
// And we are done lads, call it off
|
||||
SSatoms.map_loader_stop()
|
||||
if(!no_changeturf)
|
||||
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
|
||||
@@ -226,185 +354,167 @@
|
||||
|
||||
return TRUE
|
||||
|
||||
GLOBAL_LIST_EMPTY(map_model_default)
|
||||
|
||||
/datum/parsed_map/proc/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
|
||||
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())
|
||||
// This is safe because dmm strings will never actually newline
|
||||
// So we can parse things just fine
|
||||
var/list/entries = splittext(model, ",\n")
|
||||
//will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored)
|
||||
var/list/members = new /list(length(entries))
|
||||
//will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())
|
||||
//member attributes are rarish, so we could lazyinit this
|
||||
var/list/members_attributes = new /list(length(entries))
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
//Constructing members and corresponding variables lists
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
var/index = 1
|
||||
var/old_position = 1
|
||||
var/dpos
|
||||
for(var/member_string in entries)
|
||||
var/variables_start = 0
|
||||
//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, "{")
|
||||
|
||||
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 {...}
|
||||
|
||||
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/path_text = TRIM_TEXT(copytext(member_string, 1, variables_start))
|
||||
var/atom_def = text2path(path_text) //path definition, e.g /obj/foo/bar
|
||||
if(dpos)
|
||||
old_position = dpos + length(model[dpos])
|
||||
|
||||
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.Add(atom_def)
|
||||
members[index] = 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()
|
||||
|
||||
var/list/fields = default_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)
|
||||
member_string = copytext(member_string, variables_start + length(member_string[variables_start]), -length(copytext_char(member_string, -1))) //removing the last '}'
|
||||
fields = 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.len++
|
||||
members_attributes[index++] = fields
|
||||
|
||||
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. 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.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)
|
||||
|
||||
/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)
|
||||
// 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
|
||||
var/static/list/area_cache = GLOB.areas_by_type
|
||||
////////////////
|
||||
//Instanciation
|
||||
////////////////
|
||||
|
||||
for (var/turf_in_blacklist in turf_blacklist)
|
||||
if (crds == turf_in_blacklist) //if the given turf is blacklisted, dont do anything with it
|
||||
return
|
||||
if(turf_blacklist?[crds])
|
||||
return
|
||||
|
||||
//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
|
||||
var/atom/instance
|
||||
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(members_attributes[index] != default_list)
|
||||
world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation
|
||||
instance = area_cache[members[index]]
|
||||
if (!instance)
|
||||
instance = GLOB.areas_by_type[atype]
|
||||
if (!instance)
|
||||
instance = new atype(null)
|
||||
areaCache[atype] = instance
|
||||
if(crds)
|
||||
instance.contents.Add(crds)
|
||||
// Done here because it's cheaper then doing it in the outside check
|
||||
var/area_type = members[index]
|
||||
instance = new area_type(null)
|
||||
if(!instance)
|
||||
CRASH("[area_type] failed to be new'd, what'd you do?")
|
||||
area_cache[area_type] = instance
|
||||
|
||||
if(GLOB.use_preloader && instance)
|
||||
instance.contents.Add(crds)
|
||||
|
||||
if(GLOB.use_preloader)
|
||||
world.preloader_load(instance)
|
||||
|
||||
//then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
|
||||
// Index right before /area is /turf
|
||||
index--
|
||||
//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])
|
||||
|
||||
var/first_turf_index = 1
|
||||
while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members
|
||||
first_turf_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)
|
||||
|
||||
//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++
|
||||
if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
|
||||
world.preloader_load(instance)
|
||||
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="\"")
|
||||
@@ -419,7 +529,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=",")
|
||||
@@ -427,30 +536,49 @@
|
||||
if (!text)
|
||||
return
|
||||
|
||||
var/position
|
||||
var/old_position = 1
|
||||
// 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/using_semicolon = delimiter == ";"
|
||||
if(using_semicolon)
|
||||
var/list/line_entries = splittext(text, ";\n")
|
||||
for(var/entry in line_entries)
|
||||
// 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(entry,"=")
|
||||
// This could in theory happen if someone inserts an improper newline
|
||||
// Let's be nice and kill it here rather then later, it'll save like 0.02 seconds if we don't need to run trims in build_cache
|
||||
if(!equal_position)
|
||||
continue
|
||||
var/trim_left = TRIM_TEXT(copytext(entry,1,equal_position))
|
||||
|
||||
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)
|
||||
if(position)
|
||||
old_position = position + length(text[position])
|
||||
|
||||
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(entry, equal_position + length(entry[equal_position])))
|
||||
var/right_constant = parse_constant(trim_right)
|
||||
.[left_constant] = right_constant
|
||||
.[trim_left] = right_constant
|
||||
else
|
||||
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)
|
||||
|
||||
else // simple var
|
||||
. += list(left_constant)
|
||||
// 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 = 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/right_constant = parse_constant(trim_right)
|
||||
.[left_constant] = right_constant
|
||||
else // simple var
|
||||
. += list(left_constant)
|
||||
|
||||
/datum/parsed_map/proc/parse_constant(text)
|
||||
// number
|
||||
@@ -460,7 +588,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
|
||||
@@ -488,7 +619,8 @@
|
||||
|
||||
/datum/parsed_map/Destroy()
|
||||
..()
|
||||
turf_blacklist.Cut()
|
||||
if(turf_blacklist)
|
||||
turf_blacklist.Cut()
|
||||
parsed_bounds.Cut()
|
||||
bounds.Cut()
|
||||
grid_models.Cut()
|
||||
|
||||
@@ -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
|
||||
if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS))
|
||||
return place_on_isolated_level(z)
|
||||
@@ -8,17 +8,21 @@
|
||||
var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2)
|
||||
var/turf/central_turf = forced_turf ? forced_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)
|
||||
valid = FALSE // set to false before we check
|
||||
for(var/turf/check in affected_turfs)
|
||||
// Use assoc lists to move this out, it's easier that way
|
||||
if(check.turf_flags & NO_RUINS)
|
||||
valid = FALSE // set to false before we check
|
||||
break
|
||||
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)
|
||||
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)
|
||||
@@ -26,19 +30,21 @@
|
||||
|
||||
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/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.turf_flags |= NO_RUINS
|
||||
|
||||
new /obj/effect/landmark/ruin(central_turf, src)
|
||||
@@ -58,10 +64,11 @@
|
||||
return center
|
||||
|
||||
|
||||
/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)
|
||||
@@ -123,7 +130,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
|
||||
|
||||
@@ -22,122 +22,140 @@
|
||||
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/x in 1 to conf_set_len*2+1)
|
||||
for(var/y in 1 to conf_set_len*2+1)
|
||||
P = new/datum/space_transition_point(x, y, point_grid)
|
||||
point_grid[x][y] = 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
|
||||
|
||||
Reference in New Issue
Block a user