module things, jfc

This commit is contained in:
Poojawa
2018-09-11 07:51:01 -05:00
parent 8b9ef1e400
commit 284e9d0325
695 changed files with 11343 additions and 5661 deletions
+52
View File
@@ -0,0 +1,52 @@
The code in this module originally evolved from dmm_suite and has since been
specialized for SS13 and otherwise tweaked to fit /tg/station's needs.
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.
+21 -10
View File
@@ -4,24 +4,28 @@
var/height = 0
var/mappath = null
var/loaded = 0 // Times loaded this round
var/static/dmm_suite/maploader = new
var/datum/parsed_map/cached_map
var/keep_cached_map = FALSE
/datum/map_template/New(path = null, rename = null)
/datum/map_template/New(path = null, rename = null, cache = FALSE)
if(path)
mappath = path
if(mappath)
preload_size(mappath)
preload_size(mappath, cache)
if(rename)
name = rename
/datum/map_template/proc/preload_size(path)
var/bounds = maploader.load_map(file(path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE)
/datum/map_template/proc/preload_size(path, cache = FALSE)
var/datum/parsed_map/parsed = new(file(path))
var/bounds = parsed?.bounds
if(bounds)
width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1
height = bounds[MAP_MAXY]
if(cache)
cached_map = parsed
return bounds
/datum/map_template/proc/initTemplateBounds(var/list/bounds)
/datum/parsed_map/proc/initTemplateBounds()
var/list/obj/machinery/atmospherics/atmos_machines = list()
var/list/obj/structure/cable/cables = list()
var/list/atom/atoms = list()
@@ -53,14 +57,15 @@
var/y = round((world.maxy - height)/2)
var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(ZTRAIT_AWAY = TRUE))
var/list/bounds = maploader.load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)
var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)
var/list/bounds = parsed.bounds
if(!bounds)
return FALSE
repopulate_sorted_areas()
//initialize things that are normally initialized after map load
initTemplateBounds(bounds)
parsed.initTemplateBounds()
smooth_zlevel(world.maxz)
log_game("Z-level [name] loaded at at [x],[y],[world.maxz]")
@@ -76,7 +81,13 @@
if(T.y+height > world.maxy)
return
var/list/bounds = maploader.load_map(file(mappath), T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)
// Accept cached maps, but don't save them automatically - we don't want
// ruins clogging up memory for the whole round.
var/datum/parsed_map/parsed = cached_map || new(file(mappath))
cached_map = keep_cached_map ? parsed : null
if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE))
return
var/list/bounds = parsed.bounds
if(!bounds)
return
@@ -84,7 +95,7 @@
repopulate_sorted_areas()
//initialize things that are normally initialized after map load
initTemplateBounds(bounds)
parsed.initTemplateBounds()
log_game("[name] loaded at at [T.x],[T.y],[T.z]")
return bounds
+19 -6
View File
@@ -133,6 +133,21 @@
else
log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]")
/obj/effect/mapping_helpers/airlock/unres
name = "airlock unresctricted side helper"
icon_state = "airlock_unres_helper"
/obj/effect/mapping_helpers/airlock/unres/Initialize(mapload)
. = ..()
if(!mapload)
log_world("### MAP WARNING, [src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
airlock.unres_sides ^= dir
else
log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]")
//needs to do its thing before spawn_rivers() is called
INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
@@ -145,17 +160,15 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
var/turf/T = get_turf(src)
T.flags_1 |= NO_LAVA_GEN_1
//Contains the list of planetary z-levels defined by the planet_z helper.
GLOBAL_LIST_EMPTY(z_is_planet)
/obj/effect/mapping_helpers/planet_z //adds the map it is on to the z_is_planet list
/// Adds the map it is on to the z_is_planet list
/obj/effect/mapping_helpers/planet_z
name = "planet z helper"
layer = POINT_LAYER
/obj/effect/mapping_helpers/planet_z/Initialize()
. = ..()
var/turf/T = get_turf(src)
GLOB.z_is_planet["[T.z]"] = TRUE
var/datum/space_level/S = SSmapping.get_level(z)
S.traits[ZTRAIT_PLANET] = TRUE
//This helper applies components to things on the map directly.
+31
View File
@@ -0,0 +1,31 @@
// global datum that will preload variables on atoms instanciation
GLOBAL_VAR_INIT(use_preloader, FALSE)
GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
/// Preloader datum
/datum/map_preloader
parent_type = /datum
var/list/attributes
var/target_path
/datum/map_preloader/proc/setup(list/the_attributes, path)
if(the_attributes.len)
GLOB.use_preloader = TRUE
attributes = the_attributes
target_path = path
/datum/map_preloader/proc/load(atom/what)
GLOB.use_preloader = FALSE
for(var/attribute in attributes)
var/value = attributes[attribute]
if(islist(value))
value = deepCopyList(value)
what.vars[attribute] = value
/area/template_noop
name = "Area Passthrough"
/turf/template_noop
name = "Turf Passthrough"
icon_state = "noop"
bullet_bounce_sound = null
+243 -246
View File
@@ -1,66 +1,65 @@
///////////////////////////////////////////////////////////////
//SS13 Optimized Map loader
//////////////////////////////////////////////////////////////
#define SPACE_KEY "space"
//global datum that will preload variables on atoms instanciation
GLOBAL_VAR_INIT(use_preloader, FALSE)
GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
/datum/grid_set
var/xcrd
var/ycrd
var/zcrd
var/gridLines
/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, lower_crop_x as num, lower_crop_y as num, upper_crop_x as num, upper_crop_y as num, placeOnTop as num)
//How I wish for RAII
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, lower_crop_x, upper_crop_x, lower_crop_y, upper_crop_y, placeOnTop)
#ifdef TESTING
if(turfsSkipped)
testing("Skipped loading [turfsSkipped] default turfs")
#endif
Master.StopLoadingMap()
/dmm_suite/proc/load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE)
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
var/list/bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
var/list/grid_models = list()
/datum/parsed_map
var/original_path
var/key_len = 0
var/list/grid_models = list()
var/list/gridSets = list()
var/list/modelCache
/// Unoffset bounds. Null on parse failure.
var/list/parsed_bounds
/// Offset bounds. Same as parsed_bounds until load().
var/list/bounds
// 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
/// 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
/// - `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)
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)
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)
if(isfile(tfile))
original_path = "[tfile]"
tfile = file2text(tfile)
else if(isnull(tfile))
// 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
//multiz lool
while(dmmRegex.Find(tfile, stored_index))
stored_index = dmmRegex.next
@@ -73,41 +72,33 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
if(!key_len)
key_len = length(key)
else
throw EXCEPTION("Inconsistent key length in DMM")
CRASH("Inconsistent 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")
CRASH("Coords before model definition in DMM")
var/curr_x = text2num(dmmRegex.group[3])
if(curr_x < x_lower || curr_x > x_upper)
continue
var/xcrdStart = curr_x + x_offset - 1
var/datum/grid_set/gridSet = new
gridSet.xcrd = curr_x
//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
gridSet.ycrd = text2num(dmmRegex.group[4])
gridSet.zcrd = text2num(dmmRegex.group[5])
var/zexpansion = zcrd > world.maxz
if(zexpansion && !measureOnly)
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")
bounds[MAP_MINX] = min(bounds[MAP_MINX], CLAMP(xcrdStart, x_lower, x_upper))
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
bounds[MAP_MINX] = min(bounds[MAP_MINX], CLAMP(gridSet.xcrd, x_lower, x_upper))
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")
gridSet.gridLines = gridLines
var/leadingBlanks = 0
while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "")
@@ -117,125 +108,144 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
if(!gridLines.len) // 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.
bounds[MAP_MINY] = min(bounds[MAP_MINY], CLAMP(ycrd, y_lower, y_upper))
ycrd += gridLines.len - 1 // Start at the top and work down
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))
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], CLAMP(ycrd, y_lower, y_upper))
var/maxx = gridSet.xcrd
if(gridLines.len) //Not an empty map
maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1)
bounds[MAP_MAXX] = CLAMP(max(bounds[MAP_MAXX], maxx), x_lower, x_upper)
CHECK_TICK
// Indicate failure to parse any coordinates by nulling bounds
if(bounds[1] == 1.#INF)
bounds = null
parsed_bounds = bounds
/// 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)
//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()
// 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)
var/list/areaCache = list()
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)
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
var/zexpansion = zcrd > world.maxz
if(zexpansion)
if(cropMap)
continue
else
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], CLAMP(min(ycrd, world.maxy), y_lower, y_upper))
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")
var/maxx = xcrdStart
if(measureOnly)
for(var/line in gridLines)
maxx = max(maxx, xcrdStart + length(line) / key_len - 1)
else
for(var/line in 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)
xcrd = xcrdStart
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
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
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))
if(!grid_models[model_key])
throw EXCEPTION("Undefined model key in DMM.")
parse_grid(grid_models[model_key], model_key, xcrd, ycrd, zcrd, no_changeturf || zexpansion, placeOnTop)
#ifdef TESTING
else
++turfsSkipped
#endif
CHECK_TICK
maxx = max(maxx, xcrd)
++xcrd
--ycrd
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)
bounds[MAP_MAXX] = CLAMP(max(bounds[MAP_MAXX], cropMap ? min(maxx, world.maxx) : maxx), x_lower, x_upper)
// 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(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.AfterChange(CHANGETURF_IGNORE_AIR)
return bounds
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.AfterChange(CHANGETURF_IGNORE_AIR)
/**
* 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, placeOnTop 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.
*/
#ifdef TESTING
if(turfsSkipped)
testing("Skipped loading [turfsSkipped] default turfs")
#endif
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
return TRUE
if(cached)
members = cached[1]
members_attributes = cached[2]
else
/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
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())
/////////////////////////////////////////////////////////
//Constructing members and corresponding variables lists
////////////////////////////////////////////////////////
members = list()
members_attributes = list()
index = 1
var/index = 1
var/old_position = 1
var/dpos
do
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/atom_def = text2path(trim_text(copytext(full_def, 1, variables_start))) //path definition, e.g /obj/foo/bar
var/path_text = trim_text(copytext(full_def, 1, variables_start))
var/atom_def = text2path(path_text) //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!
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)
@@ -258,7 +268,6 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
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
@@ -269,33 +278,42 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
// 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
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
continue
modelCache[model] = list(members, members_attributes)
.[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)
var/index
var/list/members = model[1]
var/list/members_attributes = model[2]
////////////////
//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)
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)
@@ -335,7 +353,7 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
////////////////
//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, placeOnTop)
/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop)
GLOB._preloader.setup(attributes, path)
if(crds)
@@ -358,13 +376,13 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
stoplag()
SSatoms.map_loader_begin()
/dmm_suite/proc/create_atom(path, crds)
/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)
/dmm_suite/proc/trim_text(what as text,trim_quotes=0)
/datum/parsed_map/proc/trim_text(what as text,trim_quotes=0)
if(trim_quotes)
return trimQuotesRegex.Replace(what, "")
else
@@ -373,7 +391,7 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
//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="\"")
/datum/parsed_map/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)
@@ -388,90 +406,69 @@ GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new)
//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
/dmm_suite/proc/readlist(text as text, delimiter=",")
var/list/to_return = list()
/datum/parsed_map/proc/readlist(text as text, delimiter=",")
. = list()
if (!text)
return
var/position
var/old_position = 1
do
//find next delimiter that is not within "..."
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))
// 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)),1)//the name of the variable, must trim quotes to build a BYOND compliant associatives list
var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position)))
var/left_constant = delimiter == ";" ? trim_left : parse_constant(trim_left)
old_position = position + 1
if(equal_position)//associative var, so do the association
var/trim_right = trim_text(copytext(text,equal_position+1,position))//the content of the variable
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+1,position))
var/right_constant = parse_constant(trim_right)
.[left_constant] = right_constant
//Check for string
if(findtext(trim_right,"\"",1,2))
trim_right = copytext(trim_right,2,findtext(trim_right,"\"",3,0))
else // simple var
. += list(left_constant)
//Check for number
else if(isnum(text2num(trim_right)))
trim_right = text2num(trim_right)
/datum/parsed_map/proc/parse_constant(text)
// number
var/num = text2num(text)
if(isnum(num))
return num
//Check for null
else if(trim_right == "null")
trim_right = null
// string
if(findtext(text,"\"",1,2))
return copytext(text,2,findtext(text,"\"",3,0))
//Check for list
else if(copytext(trim_right,1,5) == "list")
trim_right = readlist(copytext(trim_right,6,length(trim_right)))
// list
if(copytext(text,1,6) == "list(")
return readlist(copytext(text,6,length(text)))
//Check for file
else if(copytext(trim_right,1,2) == "'")
trim_right = file(copytext(trim_right,2,length(trim_right)))
// typepath
var/path = text2path(text)
if(ispath(path))
return path
//Check for path
else if(ispath(text2path(trim_right)))
trim_right = text2path(trim_right)
// file
if(copytext(text,1,2) == "'")
return file(copytext(text,2,length(text)))
to_return[trim_left] = trim_right
// null
if(text == "null")
return null
else//simple var
to_return[trim_left] = null
// not parsed:
// - pops: /obj{name="foo"}
// - new(), newlist(), icon(), matrix(), sound()
while(position != 0)
// fallback: string
return text
return to_return
/dmm_suite/Destroy()
/datum/parsed_map/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 = "noop"
bullet_bounce_sound = null
+98
View File
@@ -0,0 +1,98 @@
/// An error report generated by [parsed_map/check_for_errors].
/datum/map_report
var/original_path
var/list/bad_paths = list()
var/list/bad_keys = list()
/// Whether this map can be loaded safely despite the errors.
var/loadable = TRUE
var/crashed = TRUE
var/static/tag_number = 0
/datum/map_report/New(datum/parsed_map/map)
original_path = map.original_path || "Untitled"
/// Show a rendered version of this report to a client.
/datum/map_report/proc/show_to(client/C)
var/list/html = list()
html += "<p>Report for map file <tt>[original_path]</tt></p>"
if(crashed)
html += "<p><b>Validation crashed</b>: check the runtime logs.</p>"
if(!loadable)
html += "<p><b>Not loadable</b>: some tiles are missing their turfs or areas.</p>"
if(bad_paths.len)
html += "<p>Bad paths: <ol>"
for(var/path in bad_paths)
var/list/keys = bad_paths[path]
html += "<li><tt>[path]</tt>: used in ([keys.len]): <tt>[keys.Join("</tt>, <tt>")]</tt>"
html += "</ol></p>"
if(bad_keys.len)
html += "<p>Bad keys: <ul>"
for(var/key in bad_keys)
var/list/messages = bad_keys[key]
html += "<li><tt>[key]</tt>"
if(messages.len == 1)
html += ": [bad_keys[key][1]]"
else
html += "<ul><li>[messages.Join("</li><li>")]</li></ul>"
html += "</li>"
html += "</ul></p>"
C << browse(html.Join(), "window=[tag];size=600x400")
/datum/map_report/Topic(href, href_list)
. = ..()
if(. || !check_rights(R_ADMIN, FALSE) || !usr.client.holder.CheckAdminHref(href, href_list))
return
if (href_list["show"])
show_to(usr)
/// Check a parsed but not yet loaded map for errors.
///
/// Returns a [/datum/map_report] if there are errors or `FALSE` otherwise.
/datum/parsed_map/proc/check_for_errors()
var/datum/map_report/report = new(src)
. = report
// build_cache will check bad paths for us
var/list/modelCache = build_cache(TRUE, report.bad_paths)
for(var/path in report.bad_paths)
if(copytext(path, 1, 7) == "/turf/" || copytext(path, 1, 7) == "/area/")
report.loadable = FALSE
// check for tiles with the wrong number of turfs or areas
for(var/key in modelCache)
if(key == SPACE_KEY)
continue
var/model = modelCache[key]
var/list/members = model[1]
var/turfs = 0
var/areas = 0
for(var/i in 1 to members.len)
var/atom/path = members[i]
turfs += ispath(path, /turf)
areas += ispath(path, /area)
if(turfs == 0)
report.loadable = FALSE
LAZYADD(report.bad_keys[key], "no turf")
else if(turfs > 1)
LAZYADD(report.bad_keys[key], "[turfs] stacked turfs")
if(areas != 1)
report.loadable = FALSE
LAZYADD(report.bad_keys[key], "[areas] areas instead of 1")
// return the report
if(report.bad_paths.len || report.bad_keys.len || !report.loadable)
// keep the report around so it can be referenced later
report.tag = "mapreport_[++report.tag_number]"
report.crashed = FALSE
else
return FALSE