mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-22 04:28:33 +01:00
Add bapi-dmm to reduce startup time by... a lot (#16148)
* Add bapi-dmm, a rust maploader that goes BRRRRRRR * A few more maps switched away from file paths * Update to include 48f4879d8704cb057a (better path error handling)
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
/dmm_suite{
|
||||
/*
|
||||
|
||||
dmm_suite version 1.0
|
||||
Released January 30th, 2011.
|
||||
|
||||
NOTE: Map saving functionality removed
|
||||
|
||||
defines the object /dmm_suite
|
||||
- Provides the proc load_map()
|
||||
- Loads the specified map file onto the specified z-level.
|
||||
- provides the proc write_map()
|
||||
- Returns a text string of the map in dmm format
|
||||
ready for output to a file.
|
||||
- provides the proc save_map()
|
||||
- Returns a .dmm file if map is saved
|
||||
- Returns FALSE if map fails to save
|
||||
|
||||
The dmm_suite provides saving and loading of map files in BYOND's native DMM map
|
||||
format. It approximates the map saving and loading processes of the Dream Maker
|
||||
and Dream Seeker programs so as to allow editing, saving, and loading of maps at
|
||||
runtime.
|
||||
|
||||
------------------------
|
||||
|
||||
To save a map at runtime, create an instance of /dmm_suite, and then call
|
||||
write_map(), which accepts three arguments:
|
||||
- A turf representing one corner of a three dimensional grid (Required).
|
||||
- Another turf representing the other corner of the same grid (Required).
|
||||
- Any, or a combination, of several bit flags (Optional, see documentation).
|
||||
|
||||
The order in which the turfs are supplied does not matter, the /dmm_writer will
|
||||
determine the grid containing both, in much the same way as DM's block() function.
|
||||
write_map() will then return a string representing the saved map in dmm format;
|
||||
this string can then be saved to a file, or used for any other purose.
|
||||
|
||||
------------------------
|
||||
|
||||
To load a map at runtime, create an instance of /dmm_suite, and then call load_map(),
|
||||
which accepts two arguments:
|
||||
- A .dmm file to load (Required).
|
||||
- A number representing the z-level on which to start loading the map (Optional).
|
||||
|
||||
The /dmm_suite will load the map file starting on the specified z-level. If no
|
||||
z-level was specified, world.maxz will be increased so as to fit the map. Note
|
||||
that if you wish to load a map onto a z-level that already has objects on it,
|
||||
you will have to handle the removal of those objects. Otherwise the new map will
|
||||
simply load the new objects on top of the old ones.
|
||||
|
||||
Also note that all type paths specified in the .dmm file must exist in the world's
|
||||
code, and that the /dmm_reader trusts that files to be loaded are in fact valid
|
||||
.dmm files. Errors in the .dmm format will cause runtime errors.
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/dmm_suite/verb/load_map(var/dmm_file as file, var/x_offset as num, var/y_offset as num, var/z_offset as num, var/cropMap as num, var/measureOnly as num, no_changeturf as num){
|
||||
// dmm_file: A .dmm file to load (Required).
|
||||
// z_offset: A number representing the z-level on which to start loading 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
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
var/mappath = null
|
||||
var/loaded = 0 // Times loaded this round
|
||||
var/annihilate = FALSE // If true, all (movable) atoms at the location where the map is loaded will be deleted before the map is loaded in.
|
||||
var/fixed_orientation = FALSE // If true, the submap will not be rotated randomly when loaded.
|
||||
|
||||
var/cost = null /* The map generator has a set 'budget' it spends to place down different submaps. It will pick available submaps randomly until
|
||||
it runs out. The cost of a submap should roughly corrispond with several factors such as size, loot, difficulty, desired scarcity, etc.
|
||||
@@ -24,15 +23,12 @@
|
||||
if(rename)
|
||||
name = rename
|
||||
|
||||
/datum/map_template/proc/preload_size(path, orientation = 0)
|
||||
var/bounds = SSmapping.maploader.load_map(file(path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE, orientation=orientation)
|
||||
/datum/map_template/proc/preload_size(path)
|
||||
var/datum/bapi_parsed_map/map = load_map_bapi(path, 1, 1, 1, crop_map=FALSE, measure_only=TRUE)
|
||||
var/list/bounds = map.parsed_bounds
|
||||
if(bounds)
|
||||
if(orientation & (90 | 270))
|
||||
width = bounds[MAP_MAXY]
|
||||
height = bounds[MAP_MAXX]
|
||||
else
|
||||
width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1
|
||||
height = bounds[MAP_MAXY]
|
||||
width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1
|
||||
height = bounds[MAP_MAXY]
|
||||
return bounds
|
||||
|
||||
/datum/map_template/proc/initTemplateBounds(var/list/bounds)
|
||||
@@ -80,7 +76,7 @@
|
||||
|
||||
admin_notice(span_danger("Submap initializations finished."), R_DEBUG)
|
||||
|
||||
/datum/map_template/proc/load_new_z(var/centered = FALSE, var/orientation = 0)
|
||||
/datum/map_template/proc/load_new_z(var/centered = FALSE)
|
||||
var/x = 1
|
||||
var/y = 1
|
||||
|
||||
@@ -88,7 +84,8 @@
|
||||
x = round((world.maxx - width)/2)
|
||||
y = round((world.maxy - height)/2)
|
||||
|
||||
var/list/bounds = SSmapping.maploader.load_map(file(mappath), x, y, no_changeturf = TRUE, orientation=orientation)
|
||||
var/datum/bapi_parsed_map/map = load_map_bapi(mappath, x, y, no_changeturf = TRUE)
|
||||
var/list/bounds = map.bounds
|
||||
if(!bounds)
|
||||
return FALSE
|
||||
|
||||
@@ -100,10 +97,10 @@
|
||||
on_map_loaded(world.maxz) //VOREStation Edit
|
||||
return TRUE
|
||||
|
||||
/datum/map_template/proc/load(turf/T, centered = FALSE, orientation = 0)
|
||||
/datum/map_template/proc/load(turf/T, centered = FALSE)
|
||||
var/old_T = T
|
||||
if(centered)
|
||||
T = locate(T.x - round(((orientation%180) ? height : width)/2) , T.y - round(((orientation%180) ? width : height)/2) , T.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false
|
||||
T = locate(T.x - round((width)/2) , T.y - round((height)/2) , T.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false
|
||||
if(!T)
|
||||
return
|
||||
if(T.x+width > world.maxx)
|
||||
@@ -112,9 +109,10 @@
|
||||
return
|
||||
|
||||
if(annihilate)
|
||||
annihilate_bounds(old_T, centered, orientation)
|
||||
annihilate_bounds(old_T, centered)
|
||||
|
||||
var/list/bounds = SSmapping.maploader.load_map(file(mappath), T.x, T.y, T.z, cropMap=TRUE, orientation = orientation)
|
||||
var/datum/bapi_parsed_map/map = load_map_bapi(mappath, T.x, T.y, T.z, crop_map = TRUE)
|
||||
var/list/bounds = map.bounds
|
||||
if(!bounds)
|
||||
return
|
||||
|
||||
@@ -128,18 +126,18 @@
|
||||
loaded++
|
||||
return TRUE
|
||||
|
||||
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE, orientation = 0)
|
||||
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE)
|
||||
var/turf/placement = T
|
||||
if(centered)
|
||||
var/turf/corner = locate(placement.x - round(((orientation%180) ? height : width)/2), placement.y - round(((orientation%180) ? width : height)/2), placement.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false
|
||||
var/turf/corner = locate(placement.x - round((width)/2), placement.y - round((height)/2), placement.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false
|
||||
if(corner)
|
||||
placement = corner
|
||||
return block(placement, locate(placement.x+((orientation%180) ? height : width)-1, placement.y+((orientation%180) ? width : height)-1, placement.z))
|
||||
return block(placement, locate(placement.x+(width)-1, placement.y+(height)-1, placement.z))
|
||||
|
||||
/datum/map_template/proc/annihilate_bounds(turf/origin, centered = FALSE, orientation = 0)
|
||||
/datum/map_template/proc/annihilate_bounds(turf/origin, centered = FALSE)
|
||||
var/deleted_atoms = 0
|
||||
admin_notice(span_danger("Annihilating objects in submap loading locatation."), R_DEBUG)
|
||||
var/list/turfs_to_clean = get_affected_turfs(origin, centered, orientation)
|
||||
var/list/turfs_to_clean = get_affected_turfs(origin, centered)
|
||||
if(turfs_to_clean.len)
|
||||
for(var/turf/T in turfs_to_clean)
|
||||
for(var/atom/movable/AM in T)
|
||||
@@ -150,9 +148,9 @@
|
||||
|
||||
//for your ever biggening badminnery kevinz000
|
||||
//❤ - Cyberboss
|
||||
/proc/load_new_z_level(var/file, var/name, var/orientation = 0)
|
||||
/proc/load_new_z_level(var/file, var/name)
|
||||
var/datum/map_template/template = new(file, name)
|
||||
template.load_new_z(orientation)
|
||||
template.load_new_z()
|
||||
|
||||
// Very similar to the /tg/ version.
|
||||
/proc/seed_submaps(var/list/z_levels, var/budget = 0, var/whitelist = /area/space, var/desired_map_template_type = null)
|
||||
@@ -231,20 +229,14 @@
|
||||
while(specific_sanity > 0)
|
||||
specific_sanity--
|
||||
|
||||
var/orientation
|
||||
if(chosen_template.fixed_orientation || !CONFIG_GET(flag/random_submap_orientation))
|
||||
orientation = 0
|
||||
else
|
||||
orientation = pick(list(0, 90, 180, 270))
|
||||
|
||||
chosen_template.preload_size(chosen_template.mappath, orientation)
|
||||
var/width_border = SUBMAP_MAP_EDGE_PAD + round(((orientation%180) ? chosen_template.height : chosen_template.width) / 2) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false //VOREStation Edit
|
||||
var/height_border = SUBMAP_MAP_EDGE_PAD + round(((orientation%180) ? chosen_template.width : chosen_template.height) / 2) //VOREStation Edit
|
||||
chosen_template.preload_size(chosen_template.mappath)
|
||||
var/width_border = SUBMAP_MAP_EDGE_PAD + round((chosen_template.width) / 2)
|
||||
var/height_border = SUBMAP_MAP_EDGE_PAD + round((chosen_template.height) / 2) //VOREStation Edit
|
||||
var/z_level = pick(z_levels)
|
||||
var/turf/T = locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z_level)
|
||||
var/valid = TRUE
|
||||
|
||||
for(var/turf/check in chosen_template.get_affected_turfs(T,TRUE,orientation))
|
||||
for(var/turf/check in chosen_template.get_affected_turfs(T,TRUE))
|
||||
var/area/new_area = get_area(check)
|
||||
if(!(istype(new_area, whitelist)))
|
||||
valid = FALSE // Probably overlapping something important.
|
||||
@@ -267,7 +259,7 @@
|
||||
specific_sanity = -1 // force end the placement loop
|
||||
|
||||
// Do loading here.
|
||||
chosen_template.load(T, centered = TRUE, orientation=orientation) // This is run before the main map's initialization routine, so that can initilize our submaps for us instead.
|
||||
chosen_template.load(T, centered = TRUE) // This is run before the main map's initialization routine, so that can initilize our submaps for us instead.
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
@@ -306,3 +298,10 @@
|
||||
else
|
||||
admin_notice("Submaps loaded.", R_DEBUG)
|
||||
admin_notice("Loaded: [english_list(pretty_submap_list)]", R_DEBUG)
|
||||
|
||||
/area/template_noop
|
||||
name = "Area Passthrough"
|
||||
|
||||
/turf/template_noop
|
||||
name = "Turf Passthrough"
|
||||
icon_state = "template_void"
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
///////////////////////////////////////////////////////////////
|
||||
//SS13 Optimized Map loader
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
//global datum that will preload variables on atoms instanciation
|
||||
GLOBAL_VAR_INIT(use_preloader, FALSE)
|
||||
GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
|
||||
|
||||
/dmm_suite
|
||||
// /"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}/g
|
||||
var/static/regex/dmmRegex = new/regex({""(\[a-zA-Z]+)" = \\(((?:.|\n)*?)\\)\n(?!\t)|\\((\\d+),(\\d+),(\\d+)\\) = \\{"(\[a-zA-Z\n]*)"\\}"}, "g")
|
||||
// /^[\s\n]+"?|"?[\s\n]+$|^"|"$/g
|
||||
var/static/regex/trimQuotesRegex = new/regex({"^\[\\s\n]+"?|"?\[\\s\n]+$|^"|"$"}, "g")
|
||||
// /^[\s\n]+|[\s\n]+$/
|
||||
var/static/regex/trimRegex = new/regex("^\[\\s\n]+|\[\\s\n]+$", "g")
|
||||
var/static/list/modelCache = list()
|
||||
var/static/space_key
|
||||
#ifdef TESTING
|
||||
var/static/turfsSkipped
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Construct the model map and control the loading process
|
||||
*
|
||||
* WORKING :
|
||||
*
|
||||
* 1) Makes an associative mapping of model_keys with model
|
||||
* e.g aa = /turf/unsimulated/wall{icon_state = "rock"}
|
||||
* 2) Read the map line by line, parsing the result (using parse_grid)
|
||||
*
|
||||
*/
|
||||
/dmm_suite/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, orientation as num)
|
||||
|
||||
dmmRegex = new/regex({""(\[a-zA-Z]+)" = \\(((?:.|\n)*?)\\)\n(?!\t)|\\((\\d+),(\\d+),(\\d+)\\) = \\{"(\[a-zA-Z\n]*)"\\}"}, "g")
|
||||
trimQuotesRegex = new/regex({"^\[\\s\n]+"?|"?\[\\s\n]+$|^"|"$"}, "g")
|
||||
trimRegex = new/regex("^\[\\s\n]+|\[\\s\n]+$", "g")
|
||||
modelCache = list()
|
||||
|
||||
//How I wish for RAII
|
||||
if(!measureOnly)
|
||||
Master.StartLoadingMap()
|
||||
space_key = null
|
||||
#ifdef TESTING
|
||||
turfsSkipped = 0
|
||||
#endif
|
||||
. = load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf, orientation)
|
||||
#ifdef TESTING
|
||||
if(turfsSkipped)
|
||||
testing("Skipped loading [turfsSkipped] default turfs")
|
||||
#endif
|
||||
if(!measureOnly)
|
||||
Master.StopLoadingMap()
|
||||
|
||||
/dmm_suite/proc/load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf, orientation)
|
||||
var/tfile = dmm_file//the map file we're creating
|
||||
if(isfile(tfile))
|
||||
tfile = file2text(tfile)
|
||||
|
||||
if(!x_offset)
|
||||
x_offset = 1
|
||||
if(!y_offset)
|
||||
y_offset = 1
|
||||
if(!z_offset)
|
||||
z_offset = world.maxz + 1
|
||||
|
||||
// If it's not a single dir, default to 0 degrees rotation (Default orientation)
|
||||
if(!(orientation in list(0, 90, 180, 270)))
|
||||
orientation = 0
|
||||
|
||||
var/list/bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
|
||||
var/list/grid_models = list()
|
||||
var/key_len = 0
|
||||
|
||||
var/stored_index = 1
|
||||
while(dmmRegex.Find(tfile, stored_index))
|
||||
stored_index = dmmRegex.next
|
||||
|
||||
// "aa" = (/type{vars=blah})
|
||||
if(dmmRegex.group[1]) // Model
|
||||
var/key = dmmRegex.group[1]
|
||||
if(grid_models[key]) // Duplicate model keys are ignored in DMMs
|
||||
continue
|
||||
if(key_len != length(key))
|
||||
if(!key_len)
|
||||
key_len = length(key)
|
||||
else
|
||||
throw EXCEPTION("Inconsistant key length in DMM")
|
||||
if(!measureOnly)
|
||||
grid_models[key] = dmmRegex.group[2]
|
||||
|
||||
// (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
|
||||
else if(dmmRegex.group[3]) // Coords
|
||||
if(!key_len)
|
||||
throw EXCEPTION("Coords before model definition in DMM")
|
||||
|
||||
var/xcrdStart = text2num(dmmRegex.group[3]) + x_offset - 1
|
||||
//position of the currently processed square
|
||||
var/xcrd
|
||||
var/ycrd = text2num(dmmRegex.group[4]) + y_offset - 1
|
||||
var/zcrd = text2num(dmmRegex.group[5]) + z_offset - 1
|
||||
|
||||
if(orientation & (EAST | WEST)) //VOREStation edit we just have to pray the upstream spacebrains take into consideration before their refator is done.
|
||||
xcrd = ycrd // temp variable
|
||||
ycrd = xcrdStart
|
||||
xcrdStart = xcrd
|
||||
|
||||
var/zexpansion = zcrd > world.maxz
|
||||
if(zexpansion && !measureOnly)
|
||||
if(cropMap)
|
||||
continue
|
||||
else
|
||||
while(world.maxz < zcrd)
|
||||
world.increment_max_z() // create a new z_level if needed.
|
||||
if(!no_changeturf)
|
||||
WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems")
|
||||
|
||||
bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrdStart)
|
||||
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
|
||||
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
|
||||
|
||||
var/list/gridLines = splittext(dmmRegex.group[6], "\n")
|
||||
|
||||
var/leadingBlanks = 0
|
||||
while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "")
|
||||
if(leadingBlanks > 1)
|
||||
gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines.
|
||||
|
||||
if(!gridLines.len) // Skip it if only blank lines exist.
|
||||
continue
|
||||
|
||||
if(gridLines.len && gridLines[gridLines.len] == "")
|
||||
gridLines.Cut(gridLines.len) // Remove only one blank line at the end.
|
||||
|
||||
bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd)
|
||||
ycrd += gridLines.len - 1 // Start at the top and work down
|
||||
|
||||
if(!cropMap && ycrd > world.maxy)
|
||||
if(!measureOnly)
|
||||
world.maxy = ycrd // Expand Y here. X is expanded in the loop below
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd)
|
||||
else
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], min(ycrd, world.maxy))
|
||||
|
||||
var/maxx = xcrdStart
|
||||
|
||||
// Assemble the grid of keys
|
||||
var/list/list/key_list = list()
|
||||
for(var/line in gridLines)
|
||||
var/list/line_keys = list()
|
||||
xcrd = 1
|
||||
for(var/tpos = 1 to length(line) - key_len + 1 step key_len)
|
||||
if(xcrd > world.maxx)
|
||||
if(cropMap)
|
||||
break
|
||||
else if(!measureOnly)
|
||||
world.maxx = xcrd
|
||||
|
||||
if(xcrd >= 1)
|
||||
var/model_key = copytext(line, tpos, tpos + key_len)
|
||||
line_keys[++line_keys.len] = model_key
|
||||
#ifdef TESTING
|
||||
++turfsSkipped
|
||||
#endif
|
||||
CHECK_TICK
|
||||
maxx = max(maxx, xcrd++)
|
||||
key_list[++key_list.len] = line_keys
|
||||
|
||||
// Rotate the list according to orientation
|
||||
if(orientation != 0)
|
||||
var/num_cols = key_list[1].len
|
||||
var/num_rows = key_list.len
|
||||
var/list/list/new_key_list = list()
|
||||
// If it's rotated 180 degrees, the dimensions are the same
|
||||
if(orientation == 180)
|
||||
new_key_list.len = num_rows
|
||||
for(var/i = 1 to new_key_list.len)
|
||||
new_key_list[i] = list()
|
||||
new_key_list[i].len = num_cols
|
||||
// Else, the dimensions are swapped
|
||||
else
|
||||
new_key_list.len = num_cols
|
||||
for(var/i = 1 to new_key_list.len)
|
||||
new_key_list[i] = list()
|
||||
new_key_list[i].len = num_rows
|
||||
|
||||
num_rows++ // Buffering against the base index of 1
|
||||
num_cols++
|
||||
// Populate the new list
|
||||
for(var/i = 1 to new_key_list.len)
|
||||
for(var/j = 1 to new_key_list[i].len)
|
||||
switch(orientation)
|
||||
if(180)
|
||||
new_key_list[i][j] = key_list[num_rows - i][num_cols - j]
|
||||
if(270)
|
||||
new_key_list[i][j] = key_list[num_rows - j][i]
|
||||
if(90)
|
||||
new_key_list[i][j] = key_list[j][num_cols - i]
|
||||
|
||||
key_list = new_key_list
|
||||
|
||||
if(measureOnly)
|
||||
for(var/list/line in key_list)
|
||||
maxx = max(maxx, line.len)
|
||||
else
|
||||
for(var/i = 1 to key_list.len)
|
||||
if(ycrd <= world.maxy && ycrd >= 1)
|
||||
xcrd = xcrdStart
|
||||
for(var/j = 1 to key_list[1].len)
|
||||
if(xcrd > world.maxx)
|
||||
if(cropMap)
|
||||
break
|
||||
else
|
||||
world.maxx = xcrd
|
||||
|
||||
if(xcrd >= 1)
|
||||
var/no_afterchange = no_changeturf || zexpansion
|
||||
if(!no_afterchange || (key_list[i][j] != space_key))
|
||||
if(!grid_models[key_list[i][j]])
|
||||
throw EXCEPTION("Undefined model key in DMM: [dmm_file], [key_list[i][j]]")
|
||||
parse_grid(grid_models[key_list[i][j]], key_list[i][j], xcrd, ycrd, zcrd, no_afterchange, orientation)
|
||||
#ifdef TESTING
|
||||
else
|
||||
++turfsSkipped
|
||||
#endif
|
||||
CHECK_TICK
|
||||
maxx = max(maxx, xcrd)
|
||||
++xcrd
|
||||
--ycrd
|
||||
|
||||
bounds[MAP_MAXX] = max(bounds[MAP_MAXX], cropMap ? min(maxx, world.maxx) : maxx)
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
if(bounds[1] == 1.#INF) // Shouldn't need to check every item
|
||||
return null
|
||||
else
|
||||
// if(!measureOnly)
|
||||
// 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
|
||||
// T.post_change()
|
||||
return bounds
|
||||
|
||||
/**
|
||||
* Fill a given tile with its area/turf/objects/mobs
|
||||
* Variable model is one full map line (e.g /turf/unsimulated/wall{icon_state = "rock"}, /area/mine/explored)
|
||||
*
|
||||
* WORKING :
|
||||
*
|
||||
* 1) Read the model string, member by member (delimiter is ',')
|
||||
*
|
||||
* 2) Get the path of the atom and store it into a list
|
||||
*
|
||||
* 3) a) Check if the member has variables (text within '{' and '}')
|
||||
*
|
||||
* 3) b) Construct an associative list with found variables, if any (the atom index in members is the same as its variables in members_attributes)
|
||||
*
|
||||
* 4) Instanciates the atom with its variables
|
||||
*
|
||||
*/
|
||||
/dmm_suite/proc/parse_grid(model as text, model_key as text, xcrd as num,ycrd as num,zcrd as num, no_changeturf as num, orientation as num)
|
||||
/*Method parse_grid()
|
||||
- Accepts a text string containing a comma separated list of type paths of the
|
||||
same construction as those contained in a .dmm file, and instantiates them.
|
||||
*/
|
||||
|
||||
var/list/members //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored)
|
||||
var/list/members_attributes //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())
|
||||
var/list/cached = modelCache[model]
|
||||
var/index
|
||||
|
||||
if(cached)
|
||||
members = cached[1]
|
||||
members_attributes = cached[2]
|
||||
else
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
//Constructing members and corresponding variables lists
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
members = list()
|
||||
members_attributes = list()
|
||||
index = 1
|
||||
|
||||
var/old_position = 1
|
||||
var/dpos
|
||||
|
||||
do
|
||||
//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/atom_def = text2path(trim_text(copytext(full_def, 1, variables_start))) //path definition, e.g /obj/foo/bar
|
||||
old_position = dpos + 1
|
||||
|
||||
if(!atom_def) // Skip the item if the path does not exist. Fix your crap, mappers!
|
||||
error("Maploader skipping undefined type: '[trim_text(copytext(full_def, 1, variables_start))]' (key=[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+1,length(full_def))//removing the last '}'
|
||||
fields = readlist(full_def, ";", TRUE)
|
||||
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
|
||||
while(dpos != 0)
|
||||
|
||||
//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
|
||||
// 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 && 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))
|
||||
space_key = model_key
|
||||
return
|
||||
|
||||
|
||||
modelCache[model] = list(members, members_attributes)
|
||||
|
||||
|
||||
////////////////
|
||||
//Instanciation
|
||||
////////////////
|
||||
|
||||
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
|
||||
var/turf/crds = locate(xcrd,ycrd,zcrd)
|
||||
|
||||
//first instance the /area and remove it from the members list
|
||||
index = members.len
|
||||
if(members[index] != /area/template_noop)
|
||||
var/atom/instance
|
||||
GLOB._preloader.setup(members_attributes[index])//preloader for assigning set variables on atom creation
|
||||
var/atype = members[index]
|
||||
for(var/area/A in world)
|
||||
if(A.type == atype)
|
||||
instance = A
|
||||
break
|
||||
if(!instance)
|
||||
instance = new atype(null)
|
||||
if(crds)
|
||||
instance.contents.Add(crds)
|
||||
|
||||
if(GLOB.use_preloader && instance)
|
||||
GLOB._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,orientation)
|
||||
|
||||
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,orientation)//instance new turf
|
||||
T.underlays += underlay
|
||||
index++
|
||||
|
||||
//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,orientation)
|
||||
//Restore initialization to the previous value
|
||||
SSatoms.map_loader_stop()
|
||||
|
||||
////////////////
|
||||
//Helpers procs
|
||||
////////////////
|
||||
|
||||
//Instance an atom at (x,y,z) and gives it the variables in attributes
|
||||
/dmm_suite/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, orientation=0)
|
||||
GLOB._preloader.setup(attributes, path)
|
||||
|
||||
if(crds)
|
||||
if(!no_changeturf && ispath(path, /turf))
|
||||
. = crds.ChangeTurf(path, FALSE, TRUE)
|
||||
else
|
||||
. = create_atom(path, crds)//first preloader pass
|
||||
|
||||
if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New()
|
||||
GLOB._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()
|
||||
|
||||
// Rotate the atom now that it exists, rather than changing its orientation beforehand through the fields["dir"]
|
||||
if(orientation != 0) // 0 means no rotation
|
||||
var/atom/A = .
|
||||
A.set_dir(turn(A.dir, orientation))
|
||||
|
||||
/dmm_suite/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)
|
||||
/dmm_suite/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
|
||||
/dmm_suite/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"")
|
||||
var/position = initial_position
|
||||
var/next_delimiter = findtext(text,delimiter,position,0)
|
||||
var/next_opening = findtext(text,opening_escape,position,0)
|
||||
|
||||
while((next_opening != 0) && (next_opening < next_delimiter))
|
||||
position = findtext(text,closing_escape,next_opening + 1,0)+1
|
||||
next_delimiter = findtext(text,delimiter,position,0)
|
||||
next_opening = findtext(text,opening_escape,position,0)
|
||||
|
||||
return next_delimiter
|
||||
|
||||
|
||||
//build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7))
|
||||
// text - variables in text form. Not including surrounding {} or list()
|
||||
// delimiter - Delimiter between list entries
|
||||
// keys_only_string - If true, text that looks like an associative list has its keys treated as var names,
|
||||
// otherwise they are parsed as valid associative list keys.
|
||||
//return the filled list
|
||||
/dmm_suite/proc/readlist(text as text, delimiter=",", keys_only_string = FALSE)
|
||||
|
||||
var/list/to_return = list()
|
||||
if(text == "")
|
||||
return to_return // Fast bail-out
|
||||
|
||||
var/position
|
||||
var/old_position = 1
|
||||
|
||||
do
|
||||
//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)
|
||||
|
||||
// part to the left of = (the key/var name), or the entire value. If treating it as a var name, strip quotes at the same time.
|
||||
var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position)), keys_only_string)
|
||||
old_position = position + 1
|
||||
|
||||
var/trim_right = trim_left
|
||||
if(equal_position)//associative var, so do the association
|
||||
trim_right = trim_text(copytext(text,equal_position+1,position))//the content of the variable
|
||||
if(!keys_only_string) // We also need to evaluate the key for the types it is permitted to be
|
||||
if(findtext(trim_left,"\"",1,2)) //Check for string
|
||||
trim_left = copytext(trim_left,2,findtext(trim_left,"\"",3,0))
|
||||
else if(isnum(text2num(trim_left))) //Check for number
|
||||
trim_left = text2num(trim_left)
|
||||
else if(ispath(text2path(trim_left))) //Check for path
|
||||
trim_left = text2path(trim_left)
|
||||
|
||||
// Parse the value in trim_right
|
||||
//Check for string
|
||||
if(findtext(trim_right,"\"",1,2))
|
||||
trim_right = copytext(trim_right,2,findtext(trim_right,"\"",3,0))
|
||||
//Check for number
|
||||
else if(isnum(text2num(trim_right)))
|
||||
trim_right = text2num(trim_right)
|
||||
//Check for null
|
||||
else if(trim_right == "null")
|
||||
trim_right = null
|
||||
//Check for list
|
||||
else if(copytext(trim_right,1,5) == "list")
|
||||
trim_right = readlist(copytext(trim_right,6,length(trim_right)))
|
||||
//Check for file
|
||||
else if(copytext(trim_right,1,2) == "'")
|
||||
trim_right = file(copytext(trim_right,2,length(trim_right)))
|
||||
//Check for path
|
||||
else if(ispath(text2path(trim_right)))
|
||||
trim_right = text2path(trim_right)
|
||||
|
||||
// Now put the trim_right into the result. Method by which we do so varies on if its assoc or not
|
||||
if(equal_position)
|
||||
to_return[trim_left] = trim_right
|
||||
else
|
||||
to_return += trim_right
|
||||
|
||||
while(position != 0)
|
||||
|
||||
return to_return
|
||||
|
||||
/dmm_suite/Destroy()
|
||||
..()
|
||||
return QDEL_HINT_HARDDEL_NOW
|
||||
|
||||
//////////////////
|
||||
//Preloader datum
|
||||
//////////////////
|
||||
|
||||
/dmm_suite/preloader
|
||||
parent_type = /datum
|
||||
var/list/attributes
|
||||
var/target_path
|
||||
|
||||
/dmm_suite/preloader/proc/setup(list/the_attributes, path)
|
||||
if(the_attributes.len)
|
||||
GLOB.use_preloader = TRUE
|
||||
attributes = the_attributes
|
||||
target_path = path
|
||||
|
||||
/dmm_suite/preloader/proc/load(atom/what)
|
||||
for(var/attribute in attributes)
|
||||
var/value = attributes[attribute]
|
||||
if(islist(value))
|
||||
value = deepCopyList(value)
|
||||
what.vars[attribute] = value
|
||||
GLOB.use_preloader = FALSE
|
||||
|
||||
/area/template_noop
|
||||
name = "Area Passthrough"
|
||||
|
||||
/turf/template_noop
|
||||
name = "Turf Passthrough"
|
||||
icon_state = "template_void"
|
||||
Reference in New Issue
Block a user