mirror of
https://github.com/Yawn-Wider/YWPolarisVore.git
synced 2026-07-13 09:05:39 +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:
@@ -0,0 +1,36 @@
|
||||
//THIS FILE IS AUTOMATICALLY GENERATED BY BAPI_DMM_READER, PLEASE DO NOT TOUCH IT
|
||||
//PROC DEFINITIONS MAY MOVE AROUND, THIS IS NORMAL
|
||||
|
||||
/* This comment bypasses grep checks */ /var/__bapi_dmm_reader
|
||||
|
||||
/proc/__detect_bapi_dmm_reader()
|
||||
if (world.system_type == UNIX)
|
||||
if(fexists("./libbapi_dmm_reader.so"))
|
||||
// No need for LD_LIBRARY_PATH badness.
|
||||
return __bapi_dmm_reader = "./libbapi_dmm_reader.so"
|
||||
// It's not in the current directory, so try others
|
||||
return __bapi_dmm_reader = "libbapi_dmm_reader"
|
||||
else
|
||||
return __bapi_dmm_reader = "bapi_dmm_reader"
|
||||
|
||||
#define BAPI_DMM_READER (__bapi_dmm_reader || __detect_bapi_dmm_reader())
|
||||
|
||||
/proc/_bapidmm_test_connection()
|
||||
return call_ext(BAPI_DMM_READER, "byond:_bapidmm_test_connection_ffi")()
|
||||
|
||||
/proc/_bapidmm_parse_map_blocking(dmm_file, map_datum)
|
||||
return call_ext(BAPI_DMM_READER, "byond:_bapidmm_parse_map_blocking_ffi")(dmm_file, map_datum)
|
||||
|
||||
/proc/_bapidmm_load_map_buffered(parsed_map, x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower,
|
||||
x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
|
||||
return call_ext(BAPI_DMM_READER, "byond:_bapidmm_load_map_buffered_ffi")(parsed_map, x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower,
|
||||
x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
|
||||
|
||||
/proc/_bapidmm_work_commandbuffer(parsed_map, resume_key)
|
||||
return call_ext(BAPI_DMM_READER, "byond:_bapidmm_work_commandbuffer_ffi")(parsed_map, resume_key)
|
||||
|
||||
/proc/_bapidmm_clear_map_data()
|
||||
return call_ext(BAPI_DMM_READER, "byond:_bapidmm_clear_map_data_ffi")()
|
||||
|
||||
/proc/bapidmm_generate_automata(limit_x, limit_y, iterations, initial_wall_cell)
|
||||
return call_ext(BAPI_DMM_READER, "byond:bapidmm_generate_automata_ffi")(limit_x, limit_y, iterations, initial_wall_cell)
|
||||
@@ -0,0 +1,274 @@
|
||||
// This file provides manually written utility types and such for the BAPI DMM Reader
|
||||
|
||||
GLOBAL_LIST_EMPTY(cached_maps)
|
||||
|
||||
GLOBAL_VAR_INIT(use_preloader, FALSE)
|
||||
GLOBAL_VAR_INIT(_preloader_path, null)
|
||||
GLOBAL_LIST_EMPTY(_preloader_attributes)
|
||||
|
||||
/world/proc/preloader_setup(list/the_attributes, path)
|
||||
if(LAZYLEN(the_attributes))
|
||||
GLOB.use_preloader = TRUE
|
||||
GLOB._preloader_attributes = the_attributes
|
||||
GLOB._preloader_path = path
|
||||
|
||||
/world/proc/preloader_load(atom/what)
|
||||
GLOB.use_preloader = FALSE
|
||||
var/list/attributes = GLOB._preloader_attributes
|
||||
for(var/attribute in attributes)
|
||||
var/value = attributes[attribute]
|
||||
if(islist(value))
|
||||
value = deepCopyList(value)
|
||||
#ifdef TESTING
|
||||
if(what.vars[attribute] == value)
|
||||
var/message = "<font color=green>[what.type]</font> at [AREACOORD(what)] - <b>VAR:</b> <font color=red>[attribute] = [isnull(value) ? "null" : (isnum(value) ? value : "\"[value]\"")]</font>"
|
||||
world.log << "DIRTY VAR: [message]"
|
||||
dirty_vars += message
|
||||
#endif
|
||||
what.vars[attribute] = value
|
||||
|
||||
#define MAP_DMM "dmm"
|
||||
/**
|
||||
* TGM SPEC:
|
||||
* TGM is a derevation of DMM, with restrictions placed on it
|
||||
* to make it easier to parse and to reduce merge conflicts/ease their resolution
|
||||
*
|
||||
* Requirements:
|
||||
* Each "statement" in a key's details ends with a new line, and wrapped in (...)
|
||||
* All paths end with either a comma or occasionally a {, then a new line
|
||||
* Excepting the area, who is listed last and ends with a ) to mark the end of the key
|
||||
*
|
||||
* {} denotes a list of variable edits applied to the path that came before the first {
|
||||
* the final } is followed by a comma, and then a new line
|
||||
* Variable edits have the form \tname = value;\n
|
||||
* Except the last edit, which has no final ;, and just ends in a newline
|
||||
* No extra padding is permitted
|
||||
* Many values are supported. See parse_constant()
|
||||
* Strings must be wrapped in "...", files in '...', and lists in list(...)
|
||||
* Files are kinda susy, and may not actually work. buyer beware
|
||||
* Lists support assoc values as expected
|
||||
* These constants can be further embedded into lists
|
||||
*
|
||||
* There can be no padding in front of, or behind a path
|
||||
*
|
||||
* Therefore:
|
||||
* "key" = (
|
||||
* /path,
|
||||
* /other/path{
|
||||
* var = list("name" = 'filepath');
|
||||
* other_var = /path
|
||||
* },
|
||||
* /turf,
|
||||
* /area)
|
||||
*
|
||||
*/
|
||||
#define MAP_TGM "tgm"
|
||||
#define MAP_UNKNOWN "unknown"
|
||||
|
||||
/// Returned from parse_map to give some metadata about the map
|
||||
/datum/bapi_parsed_map
|
||||
var/_internal_index = -1
|
||||
|
||||
var/original_path = ""
|
||||
var/map_format = MAP_UNKNOWN
|
||||
var/key_len = 0
|
||||
var/line_len = 0
|
||||
var/expanded_y = FALSE
|
||||
var/expanded_x = FALSE
|
||||
|
||||
/// Unoffset bounds. Null on parse failure.
|
||||
var/list/bounds = list()
|
||||
/// Offset bounds. Same as parsed_bounds until load().
|
||||
var/list/parsed_bounds = list()
|
||||
|
||||
///any turf in this list is skipped inside of build_coordinate. Lazy assoc list
|
||||
var/list/turf_blacklist
|
||||
|
||||
var/loading = FALSE
|
||||
var/loaded_warnings = list()
|
||||
|
||||
/**
|
||||
* Helper and recommened way to load a map file
|
||||
* - dmm_file: The path to the map file
|
||||
* - x_offset: The x offset to load the map at
|
||||
* - y_offset: The y offset to load the map at
|
||||
* - z_offset: The z offset to load the map at
|
||||
* - crop_map: If true, the map will be cropped to the world bounds
|
||||
* - measure_only: If true, the map will not be loaded, but the bounds will be calculated
|
||||
* - no_changeturf: If true, the map will not call /turf/AfterChange
|
||||
* - x_lower: The minimum x coordinate to load
|
||||
* - x_upper: The maximum x coordinate to load
|
||||
* - y_lower: The minimum y coordinate to load
|
||||
* - y_upper: The maximum y coordinate to load
|
||||
* - z_lower: The minimum z coordinate to load
|
||||
* - z_upper: The maximum z coordinate to load
|
||||
* - place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf
|
||||
* - new_z: If true, a new z level will be created for the map
|
||||
*/
|
||||
/proc/load_map_bapi(
|
||||
dmm_file,
|
||||
x_offset = 1,
|
||||
y_offset = 1,
|
||||
z_offset,
|
||||
crop_map = FALSE,
|
||||
measure_only = FALSE,
|
||||
no_changeturf = FALSE,
|
||||
x_lower = -INFINITY,
|
||||
x_upper = INFINITY,
|
||||
y_lower = -INFINITY,
|
||||
y_upper = INFINITY,
|
||||
z_lower = -INFINITY,
|
||||
z_upper = INFINITY,
|
||||
place_on_top = FALSE,
|
||||
new_z = FALSE,
|
||||
)
|
||||
if(isfile(dmm_file))
|
||||
log_debug("bapi-dmm was passed a file instead of a path string: [dmm_file]")
|
||||
var/name = sanitize_filename("[dmm_file]")
|
||||
var/path = "data/baked_dmm_files/[name]"
|
||||
if(!fexists(path))
|
||||
var/text = file2text(dmm_file)
|
||||
rustg_file_write(text, path)
|
||||
dmm_file = path
|
||||
|
||||
if(!fexists(dmm_file))
|
||||
stack_trace("Invalid map path: [dmm_file]")
|
||||
return
|
||||
|
||||
if(!(dmm_file in GLOB.cached_maps))
|
||||
GLOB.cached_maps[dmm_file] = new /datum/bapi_parsed_map(dmm_file)
|
||||
|
||||
// Bay assumption
|
||||
if(!z_offset)
|
||||
z_offset = world.maxz + 1
|
||||
|
||||
var/datum/bapi_parsed_map/parsed_map = GLOB.cached_maps[dmm_file]
|
||||
parsed_map = parsed_map.copy()
|
||||
if(!measure_only && !isnull(parsed_map.bounds))
|
||||
parsed_map.load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
|
||||
return parsed_map
|
||||
|
||||
/datum/bapi_parsed_map/New(tfile)
|
||||
if(isnull(tfile))
|
||||
return // create a new datum without loading a map
|
||||
var/ret = _bapidmm_parse_map_blocking(tfile, src)
|
||||
if(!ret)
|
||||
CRASH("Failed to load map [tfile], check rust_log.txt")
|
||||
|
||||
/datum/bapi_parsed_map/Destroy()
|
||||
..()
|
||||
SSatoms.map_loader_stop(REF(src)) // Just in case, I don't want to double up here
|
||||
if(turf_blacklist)
|
||||
turf_blacklist.Cut()
|
||||
parsed_bounds.Cut()
|
||||
bounds.Cut()
|
||||
return QDEL_HINT_HARDDEL_NOW
|
||||
|
||||
/datum/bapi_parsed_map/proc/copy()
|
||||
// Avoids duped work just in case
|
||||
var/datum/bapi_parsed_map/newfriend = new()
|
||||
// use the same under-the-hood data
|
||||
newfriend._internal_index = _internal_index
|
||||
newfriend.original_path = original_path
|
||||
newfriend.map_format = map_format
|
||||
newfriend.key_len = key_len
|
||||
newfriend.line_len = line_len
|
||||
newfriend.parsed_bounds = parsed_bounds.Copy()
|
||||
// Copy parsed bounds to reset to initial values
|
||||
newfriend.bounds = parsed_bounds.Copy()
|
||||
newfriend.turf_blacklist = turf_blacklist?.Copy()
|
||||
// Explicitly do NOT copy `loaded` and `loaded_warnings`
|
||||
return newfriend
|
||||
|
||||
/datum/bapi_parsed_map/proc/load(
|
||||
x_offset = 1,
|
||||
y_offset = 1,
|
||||
z_offset = 1,
|
||||
crop_map = FALSE,
|
||||
no_changeturf = FALSE,
|
||||
x_lower = -INFINITY,
|
||||
x_upper = INFINITY,
|
||||
y_lower = -INFINITY,
|
||||
y_upper = INFINITY,
|
||||
z_lower = -INFINITY,
|
||||
z_upper = INFINITY,
|
||||
place_on_top = FALSE,
|
||||
new_z = FALSE,
|
||||
)
|
||||
Master.StartLoadingMap()
|
||||
. = _load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
|
||||
Master.StopLoadingMap()
|
||||
|
||||
/datum/bapi_parsed_map/proc/_load_impl(
|
||||
x_offset = 1,
|
||||
y_offset = 1,
|
||||
z_offset = 1,
|
||||
crop_map = FALSE,
|
||||
no_changeturf = FALSE,
|
||||
x_lower = -INFINITY,
|
||||
x_upper = INFINITY,
|
||||
y_lower = -INFINITY,
|
||||
y_upper = INFINITY,
|
||||
z_lower = -INFINITY,
|
||||
z_upper = INFINITY,
|
||||
place_on_top = FALSE,
|
||||
new_z = FALSE,
|
||||
)
|
||||
PRIVATE_PROC(TRUE)
|
||||
SSatoms.map_loader_begin(REF(src))
|
||||
|
||||
// `loading` var handled by bapidmm
|
||||
var/resume_key = _bapidmm_load_map_buffered(
|
||||
src,
|
||||
x_offset,
|
||||
y_offset,
|
||||
z_offset,
|
||||
crop_map,
|
||||
no_changeturf,
|
||||
x_lower,
|
||||
x_upper,
|
||||
y_lower,
|
||||
y_upper,
|
||||
z_lower,
|
||||
z_upper,
|
||||
place_on_top,
|
||||
new_z
|
||||
)
|
||||
|
||||
if(!resume_key)
|
||||
SSatoms.map_loader_stop(REF(src))
|
||||
CRASH("Failed to generate command buffer, check rust_log.txt and other runtimes")
|
||||
|
||||
var/work_remaining = FALSE
|
||||
do
|
||||
work_remaining = _bapidmm_work_commandbuffer(src, resume_key)
|
||||
stoplag()
|
||||
while(work_remaining)
|
||||
|
||||
SSatoms.map_loader_stop(REF(src))
|
||||
|
||||
// if(new_z)
|
||||
// for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ])
|
||||
// SSmapping.build_area_turfs(z_index)
|
||||
|
||||
// if(!no_changeturf)
|
||||
// var/list/turfs = block(
|
||||
// locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
|
||||
// locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
|
||||
// for(var/turf/T as anything in turfs)
|
||||
// //we do this after we load everything in. if we don't, we'll have weird atmos bugs regarding atmos adjacent turfs
|
||||
// T.AfterChange(CHANGETURF_IGNORE_AIR)
|
||||
|
||||
// if(expanded_x || expanded_y)
|
||||
// SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, expanded_x, expanded_y)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/bapi_parsed_map/proc/has_warnings()
|
||||
if(length(loaded_warnings))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
// #undef MAP_DMM
|
||||
// #undef MAP_TGM
|
||||
// #undef MAP_UNKNOWN
|
||||
@@ -0,0 +1,110 @@
|
||||
// Internal bapi-dmm helpers
|
||||
/datum/bapi_parsed_map/proc/_bapi_add_warning(warning)
|
||||
loaded_warnings += list(warning)
|
||||
|
||||
/datum/bapi_parsed_map/proc/_bapi_expand_map(x, y, z, new_z, z_offset)
|
||||
if(x > world.maxx)
|
||||
expanded_x = TRUE
|
||||
world.maxx = x
|
||||
// if(new_z)
|
||||
// world.increase_max_x(x, map_load_z_cutoff = z_offset - 1)
|
||||
// else
|
||||
// world.increase_max_x(x)
|
||||
if(y > world.maxy)
|
||||
expanded_y = TRUE
|
||||
world.maxy = y
|
||||
// if(new_z)
|
||||
// world.increase_max_y(y, map_load_z_cutoff = z_offset - 1)
|
||||
// else
|
||||
// world.increase_max_y(y)
|
||||
if(z > world.maxz)
|
||||
while(world.maxz < z)
|
||||
world.increment_max_z()
|
||||
|
||||
/proc/_bapi_helper_get_world_bounds()
|
||||
. = list(world.maxx, world.maxy, world.maxz)
|
||||
|
||||
/proc/_bapi_helper_text2path(text)
|
||||
. = text2path(text)
|
||||
|
||||
/proc/_bapi_helper_text2file(text)
|
||||
. = file(text)
|
||||
|
||||
/proc/_bapi_create_atom(path, crds)
|
||||
set waitfor = FALSE
|
||||
. = new path (crds)
|
||||
|
||||
/proc/_bapi_setup_preloader(list/attributes, path)
|
||||
world.preloader_setup(attributes, path)
|
||||
|
||||
/proc/_bapi_apply_preloader(atom/A)
|
||||
if(GLOB.use_preloader)
|
||||
world.preloader_load(A)
|
||||
|
||||
/proc/_bapi_new_atom(text_path, turf/crds, list/attributes)
|
||||
var/path = text2path(text_path)
|
||||
if(!path)
|
||||
CRASH("Bad atom path [text_path]")
|
||||
|
||||
if(attributes != null)
|
||||
world.preloader_setup(attributes, path)
|
||||
|
||||
var/atom/instance = _bapi_create_atom(path, crds) // first preloader pass
|
||||
|
||||
if(GLOB.use_preloader && instance) // second preloader pass for atoms that don't ..() in New()
|
||||
world.preloader_load(instance)
|
||||
|
||||
/proc/_bapi_create_or_get_area(text_path)
|
||||
var/path = text2path(text_path)
|
||||
if(!path)
|
||||
CRASH("Bad area path [text_path]")
|
||||
|
||||
var/area/area_instance = GLOB.areas_by_type[path]
|
||||
if(!area_instance)
|
||||
area_instance = new path(null)
|
||||
if(!area_instance)
|
||||
CRASH("[path] failed to be new'd, what'd you do?")
|
||||
|
||||
return area_instance
|
||||
|
||||
/proc/_bapi_handle_area_contain(turf/T, area/A)
|
||||
// var/area/old_area = T.loc
|
||||
// if(old_area == A)
|
||||
// return // no changing areas that already contain this turf, it'll confuse them
|
||||
// LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, T.z, list())
|
||||
// LISTASSERTLEN(A.turfs_by_zlevel, T.z, list())
|
||||
// old_area.turfs_to_uncontain_by_zlevel[T.z] += T
|
||||
// A.turfs_by_zlevel[T.z] += T
|
||||
|
||||
/proc/_bapi_create_turf(turf/crds, text_path, list/attributes, place_on_top, no_changeturf)
|
||||
var/path = text2path(text_path)
|
||||
if(!path)
|
||||
CRASH("Bad turf path [text_path]")
|
||||
|
||||
if(attributes != null)
|
||||
world.preloader_setup(attributes, path)
|
||||
|
||||
var/atom/instance
|
||||
if(no_changeturf)
|
||||
instance = _bapi_create_atom(path, crds)
|
||||
else
|
||||
instance = crds.ChangeTurf(path, FALSE, TRUE)
|
||||
|
||||
if(GLOB.use_preloader && instance) // second preloader pass for atoms that don't ..() in New()
|
||||
world.preloader_load(instance)
|
||||
|
||||
/proc/_bapi_add_turf_to_area(area/A, turf/T)
|
||||
if(!A || !T)
|
||||
return
|
||||
A.contents.Add(T)
|
||||
|
||||
/proc/_bapi_helper_get_world_type_turf()
|
||||
return "[world.turf]"
|
||||
|
||||
/proc/_bapi_helper_get_world_type_area()
|
||||
return "[world.area]"
|
||||
|
||||
/// Implement this to have bapi-dmm sleep occasionally
|
||||
/// by returning true
|
||||
/proc/_bapi_helper_tick_check()
|
||||
return TICK_CHECK
|
||||
@@ -1,73 +0,0 @@
|
||||
var/global/dmm_suite/maploader = null
|
||||
|
||||
dmm_suite{
|
||||
/*
|
||||
|
||||
dmm_suite version 1.0
|
||||
Released January 30th, 2011.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
verb/load_map(var/dmm_file as file, var/z_offset 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).
|
||||
|
||||
verb/write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){
|
||||
// t1: A turf representing one corner of a three dimensional grid (Required).
|
||||
// t2: Another turf representing the other corner of the same grid (Required).
|
||||
// flags: Any, or a combination, of several bit flags (Optional, see documentation).
|
||||
}
|
||||
|
||||
// save_map is included as a legacy proc. Use write_map instead.
|
||||
verb/save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){
|
||||
// t1: A turf representing one corner of a three dimensional grid (Required).
|
||||
// t2: Another turf representing the other corner of the same grid (Required).
|
||||
// map_name: A valid name for the map to be saved, such as "castle" (Required).
|
||||
// flags: Any, or a combination, of several bit flags (Optional, see documentation).
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
DMP to swapmap converter
|
||||
version 1.0
|
||||
|
||||
by Lummox JR
|
||||
*/
|
||||
|
||||
/mob/verb/Convert(filename as file)
|
||||
dmp2swapmap(filename)
|
||||
|
||||
/proc/d2sm_prepmap(filename)
|
||||
var/txt = file2text(filename)
|
||||
if(!txt) return
|
||||
var/i,j
|
||||
i=findText(txt,ascii2text(13)) // eliminate carriage returns
|
||||
while(i)
|
||||
txt=copytext(txt,1,i)+copytext(txt,i+1)
|
||||
i=findText(txt,ascii2text(13),i)
|
||||
i=findText(txt,"\\\n")
|
||||
while(i)
|
||||
for(j=i+2,j<=length(txt),++j) if(text2ascii(txt,j)>32) break
|
||||
txt=copytext(txt,1,i)+copytext(txt,j)
|
||||
i=findText(txt,"\\\n",i)
|
||||
return txt
|
||||
|
||||
/proc/dmp2swapmap(filename)
|
||||
//var/txt = file2text(filename)
|
||||
//if(!txt) return
|
||||
var/txt = d2sm_prepmap(filename)
|
||||
var/mapname="[filename]"
|
||||
var/i,j,k
|
||||
i=findtext(mapname,".dmp")
|
||||
while(i && i+4<length(mapname)) i=findtext(mapname,".dmp",i+1)
|
||||
mapname=copytext(mapname,1,i)
|
||||
/* i=findText(txt,ascii2text(13))
|
||||
while(i)
|
||||
txt=copytext(txt,1,i)+copytext(txt,i+1)
|
||||
i=findText(txt,ascii2text(13),i)
|
||||
i=findText(txt,"\\\n")
|
||||
while(i)
|
||||
for(j=i+2,j<=length(txt),++j) if(text2ascii(txt,j)>32) break
|
||||
txt=copytext(txt,1,i)+copytext(txt,j)
|
||||
i=findText(txt,"\\\n",i) */
|
||||
var/list/codes=new
|
||||
var/codelen=1
|
||||
var/list/areas
|
||||
var/mode=34
|
||||
var/z=0
|
||||
var/X=0,Y=0,Z=0
|
||||
while(txt)
|
||||
if(text2ascii(txt)==34)
|
||||
if(mode!=34)
|
||||
to_world("Corrupt map file [filename]: Unexpected code found after z-level [z]")
|
||||
return
|
||||
// standard line:
|
||||
// "a" = (/obj, /obj, /turf, /area)
|
||||
i=findtext(txt,"\"",2)
|
||||
var/code=copytext(txt,2,i)
|
||||
codelen=length(code)
|
||||
i=findtext(txt,"(",i)
|
||||
if(!i)
|
||||
to_world("Corrupt map file [filename]: No type list follows \"[code]\"")
|
||||
return
|
||||
k=findtext(txt,"\n",++i)
|
||||
j=(k || length(txt+1))
|
||||
while(--j>=i && text2ascii(txt,j)!=41)
|
||||
if(j<i)
|
||||
to_world("Corrupt map file [filename]: Type list following \"[code]\" is incomplete")
|
||||
return
|
||||
var/list/L = d2sm_ParseCommaList(copytext(txt,i,j))
|
||||
if(istext(L))
|
||||
to_world("Corrupt map file [filename]: [L]")
|
||||
return
|
||||
if(L.len<2)
|
||||
to_world("Corrupt map file [filename]: Type list following \"[code]\" has only 1 item")
|
||||
return
|
||||
txt=k?copytext(txt,k+1):null
|
||||
if(L[L.len] == "[world.area]") L[L.len]=0
|
||||
else
|
||||
if(!areas) areas=list()
|
||||
i=areas.Find(L[L.len])
|
||||
if(i) L[L.len]=i
|
||||
else
|
||||
areas+=L[L.len]
|
||||
L[L.len]=areas.len
|
||||
var/codetrans=d2sm_ConvertType(L[L.len-1],"\t\t\t\t")
|
||||
if(L[L.len]) codetrans+="\t\t\t\tAREA = [L[L.len]]\n"
|
||||
if(L.len>2) codetrans+=d2sm_Contents(L,L.len-2,"\t\t\t\t")
|
||||
codes[code]=copytext(codetrans,1,length(codetrans))
|
||||
else if(text2ascii(txt)==40)
|
||||
mode=40
|
||||
// standard line (top-down, left-right symbol order):
|
||||
// (1,1,1) = {"
|
||||
// abcde
|
||||
// bcdef
|
||||
// "}
|
||||
i=d2sm_MatchBrace(txt,1,40)
|
||||
if(!i)
|
||||
to_world("Corrupt map file [filename]: No matching ) for coordinates: [copytext(txt,1,findtext(txt,"\n"))]")
|
||||
return
|
||||
var/list/coords=d2sm_ParseCommaList(copytext(txt,2,i))
|
||||
if(istext(coords) || coords.len!=3)
|
||||
to_world("Corrupt map file [filename]: [istext(coords)?(coords):"[copytext(txt,1,i+1)] is not a valid (x,y,z) coordinate"]")
|
||||
return
|
||||
j=findtext(txt,"{",i+1)
|
||||
if(!j)
|
||||
to_world("Corrupt map file [filename]: No braces {} following [copytext(txt,1,i+1)]")
|
||||
return
|
||||
k=d2sm_MatchBrace(txt,j,123)
|
||||
if(!k)
|
||||
to_world("Corrupt map file [filename]: No closing brace } following [copytext(txt,1,i+1)]")
|
||||
return
|
||||
var/mtxt=copytext(txt,j+1,k)
|
||||
if(findText(mtxt,"\"\n")!=1 || !findText(mtxt,"\n\"",length(mtxt)-1))
|
||||
to_world(findText(mtxt,"\"\n"))
|
||||
to_world(findText(mtxt,"\n\"",length(mtxt)-1))
|
||||
to_world("Corrupt map file [filename]: No quotes in braces following [copytext(txt,1,i+1)]")
|
||||
return
|
||||
mtxt=copytext(mtxt,2,length(mtxt))
|
||||
var/_x=0,_y=0
|
||||
for(i=1,,++_y)
|
||||
j=findText(mtxt,"\n",i+1)
|
||||
if(!j) break
|
||||
_x=max(_x,(j-i-1)/codelen)
|
||||
i=j
|
||||
X=max(X,_x)
|
||||
Y=max(Y,_y)
|
||||
z=text2num(coords[3])
|
||||
Z=max(Z,z)
|
||||
txt=copytext(txt,k+1)
|
||||
else
|
||||
i=findtext(txt,"\n")
|
||||
txt=i?copytext(txt,i+1):null
|
||||
to_world("Map size: [X],[Y],[Z]")
|
||||
//for(var/code in codes)
|
||||
// to_world("Code \"[code]\":\n[codes[code]]")
|
||||
fdel("map_[mapname].txt")
|
||||
var/F = file("map_[mapname].txt")
|
||||
to_chat(F, ". = object(\".0\")\n.0\n\ttype = /swapmap\n\tid = \"[mapname]\"\n\tz = [Z]\n\ty = [Y]\n\tx = [X]")
|
||||
if(areas)
|
||||
txt=""
|
||||
for(i=0,i<areas.len,++i)
|
||||
txt+="[i?", ":""]object(\".[i]\")"
|
||||
to_chat(F, "\tareas = list([txt])")
|
||||
for(i=0,i<areas.len,++i)
|
||||
to_chat(F, "\t\t.[i]")
|
||||
txt=d2sm_ConvertType(areas[i+1],"\t\t\t")
|
||||
to_chat(F,copytext(txt,1,length(txt)))
|
||||
|
||||
// 2nd pass
|
||||
txt=d2sm_prepmap(filename)
|
||||
while(txt)
|
||||
// skip all non-data sections
|
||||
if(text2ascii(txt)!=40)
|
||||
i=findText(txt,"\n")
|
||||
if(i) txt=copytext(txt,i+1)
|
||||
else txt=null
|
||||
continue
|
||||
i=d2sm_MatchBrace(txt,1,40)
|
||||
var/list/coords=d2sm_ParseCommaList(copytext(txt,2,i))
|
||||
j=findtext(txt,"{",i+1)
|
||||
k=d2sm_MatchBrace(txt,j,123)
|
||||
var/mtxt=copytext(txt,j+2,k-1)
|
||||
var/_x=0,_y=0
|
||||
for(i=1,,++_y)
|
||||
j=findText(mtxt,"\n",i+1)
|
||||
if(!j) break
|
||||
_x=max(_x,(j-i-1)/codelen)
|
||||
i=j
|
||||
// print out this z-level now
|
||||
to_chat(F, "\t[coords[3]]")
|
||||
i=1
|
||||
for(var/y=_y,y>0,--y) // map is top-down
|
||||
++i
|
||||
to_chat(F, "\t\t[y]")
|
||||
for(var/x in 1 to _x)
|
||||
to_chat(F, "\t\t\t[x]")
|
||||
j=i+codelen
|
||||
to_chat(F, codes[copytext(mtxt,i,j)])
|
||||
i=j
|
||||
txt=copytext(txt,k+1)
|
||||
/* for(z in 1 to Z)
|
||||
to_chat(F, "\t[z]")
|
||||
for(var/y in 1 to Y)
|
||||
to_chat(F, "\t\t[y]")
|
||||
for(var/x in 1 to X)
|
||||
to_chat(F, "\t\t\t[x]")
|
||||
to_chat(F, codes[pick(codes)]) */
|
||||
|
||||
/proc/d2sm_ParseCommaList(txt)
|
||||
var/list/L=new
|
||||
var/i,ch
|
||||
for(i=1,i<=length(txt),++i)
|
||||
if(text2ascii(txt,i)>32) break
|
||||
for(,i<=length(txt),++i)
|
||||
ch=text2ascii(txt,i)
|
||||
if(ch==44)
|
||||
L+=copytext(txt,1,i)
|
||||
for(++i,i<=length(txt),++i) if(text2ascii(txt,i)>32) break
|
||||
txt=copytext(txt,i)
|
||||
i=0;continue
|
||||
if(ch==40 || ch==91 || ch==123)
|
||||
i=d2sm_MatchBrace(txt,i,ch)
|
||||
if(!i) return "No matching brace found for [ascii2text(ch)]"
|
||||
if(i>1) L+=copytext(txt,1,i)
|
||||
return L
|
||||
|
||||
/proc/d2sm_MatchBrace(txt, i, which)
|
||||
if(which==40) ++which
|
||||
else which+=2
|
||||
var/j,ch
|
||||
for(j=i+1,j<=length(txt),++j)
|
||||
ch=text2ascii(txt,j)
|
||||
if(ch==which) return j
|
||||
if(ch==40 || ch==91 || ch==123)
|
||||
j=d2sm_MatchBrace(txt,j,ch)
|
||||
if(!j) return 0
|
||||
|
||||
/proc/d2sm_ConvertType(tt,tabs="")
|
||||
var/i=findText(tt,"{")
|
||||
if(!i) return "[tabs]type = [tt]\n"
|
||||
.="[tabs]type = [copytext(tt,1,i)]\n"
|
||||
var/list/L=d2sm_ParseCommaList(copytext(tt,i+1,d2sm_MatchBrace(tt,i,123)))
|
||||
if(istext(L)) return
|
||||
for(var/pair in L)
|
||||
.="[.][tabs][pair]\n"
|
||||
|
||||
/proc/d2sm_Contents(list/conts,n,tabs="")
|
||||
.="[tabs]contents = list("
|
||||
var/i
|
||||
for(i=0,i<n,++i)
|
||||
.+="[i?", ":""]object(\".[i]\")"
|
||||
.+=")\n"
|
||||
tabs+="\t"
|
||||
for(i=0,i<n,++i)
|
||||
.+="[tabs].[i]\n"
|
||||
.+=d2sm_ConvertType(conts[i+1],tabs+"\t")
|
||||
@@ -1,332 +0,0 @@
|
||||
///////////////////////////////////////////////////////////////
|
||||
//SS13 Optimized Map loader
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
//global datum that will preload variables on atoms instanciation
|
||||
var/global/dmm_suite/preloader/_preloader = null
|
||||
|
||||
|
||||
/**
|
||||
* 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(var/dmm_file as file, var/z_offset as num)
|
||||
if(!z_offset)//what z_level we are creating the map on
|
||||
z_offset = world.maxz+1
|
||||
|
||||
var/quote = ascii2text(34)
|
||||
var/tfile = file2text(dmm_file)//the map file we're creating
|
||||
var/tfile_len = length(tfile)
|
||||
var/lpos = 1 // the models definition index
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
//first let's map model keys (e.g "aa") to their contents (e.g /turf/space{variables})
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
var/list/grid_models = list()
|
||||
var/key_len = length(copytext(tfile,2,findtext(tfile,quote,2,0)))//the length of the model key (e.g "aa" or "aba")
|
||||
|
||||
//proceed line by line
|
||||
for(lpos=1; lpos<tfile_len; lpos=findtext(tfile,"\n",lpos,0)+1)
|
||||
var/tline = copytext(tfile,lpos,findtext(tfile,"\n",lpos,0))
|
||||
if(copytext(tline,1,2) != quote)//we reached the map "layout"
|
||||
break
|
||||
var/model_key = copytext(tline,2,2+key_len)
|
||||
var/model_contents = copytext(tline,findtext(tfile,"=")+3,length(tline))
|
||||
grid_models[model_key] = model_contents
|
||||
sleep(-1)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
//now let's fill the map with turf and objects using the constructed model map
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//position of the currently processed square
|
||||
var/zcrd=-1
|
||||
var/ycrd=0
|
||||
var/xcrd=0
|
||||
|
||||
for(var/zpos=findtext(tfile,"\n(1,1,",lpos,0);zpos!=0;zpos=findtext(tfile,"\n(1,1,",zpos+1,0)) //in case there's several maps to load
|
||||
|
||||
zcrd++
|
||||
world.maxz = max(world.maxz, zcrd+z_offset)//create a new z_level if needed
|
||||
|
||||
var/zgrid = copytext(tfile,findtext(tfile,quote+"\n",zpos,0)+2,findtext(tfile,"\n"+quote,zpos,0)+1) //copy the whole map grid
|
||||
var/z_depth = length(zgrid)
|
||||
|
||||
//if exceeding the world max x or y, increase it
|
||||
var/x_depth = length(copytext(zgrid,1,findtext(zgrid,"\n",2,0)))
|
||||
if(world.maxx<x_depth)
|
||||
world.maxx=x_depth
|
||||
|
||||
var/y_depth = z_depth / (x_depth+1)//x_depth + 1 because we're counting the '\n' characters in z_depth
|
||||
if(world.maxy<y_depth)
|
||||
world.maxy=y_depth
|
||||
|
||||
//then proceed it line by line, starting from top
|
||||
ycrd = y_depth
|
||||
|
||||
for(var/gpos=1;gpos!=0;gpos=findtext(zgrid,"\n",gpos,0)+1)
|
||||
var/grid_line = copytext(zgrid,gpos,findtext(zgrid,"\n",gpos,0))
|
||||
|
||||
//fill the current square using the model map
|
||||
xcrd=0
|
||||
for(var/mpos=1;mpos<=x_depth;mpos+=key_len)
|
||||
xcrd++
|
||||
var/model_key = copytext(grid_line,mpos,mpos+key_len)
|
||||
parse_grid(grid_models[model_key],xcrd,ycrd,zcrd+z_offset)
|
||||
|
||||
//reached end of current map
|
||||
if(gpos+x_depth+1>z_depth)
|
||||
break
|
||||
|
||||
ycrd--
|
||||
|
||||
sleep(-1)
|
||||
|
||||
//reached End Of File
|
||||
if(findtext(tfile,quote+"}",zpos,0)+2==tfile_len)
|
||||
break
|
||||
sleep(-1)
|
||||
|
||||
/**
|
||||
* 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(var/model as text,var/xcrd as num,var/ycrd as num,var/zcrd 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 = 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
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
var/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 = copytext(model,old_position,dpos)//full definition, e.g : /obj/foo/bar{variables=derp}
|
||||
var/atom_def = text2path(copytext(full_def,1,findtext(full_def,"{")))//path definition, e.g /obj/foo/bar
|
||||
members.Add(atom_def)
|
||||
old_position = dpos + 1
|
||||
|
||||
//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/variables_start = findtext(full_def,"{")
|
||||
if(variables_start)//if there's any variable
|
||||
full_def = copytext(full_def,variables_start+1,length(full_def))//removing the last '}'
|
||||
fields = text2list(full_def,";")
|
||||
|
||||
//then fill the members_attributes list with the corresponding variables
|
||||
members_attributes.len++
|
||||
members_attributes[index++] = fields
|
||||
|
||||
sleep(-1)
|
||||
while(dpos != 0)
|
||||
|
||||
|
||||
////////////////
|
||||
//Instanciation
|
||||
////////////////
|
||||
|
||||
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
|
||||
|
||||
//in case of multiples turfs on one tile,
|
||||
//will contains the images of all underlying turfs, to simulate the DMM multiple tiles piling
|
||||
var/list/turfs_underlays = list()
|
||||
|
||||
//first instance the /area and remove it from the members list
|
||||
index = members.len
|
||||
var/atom/instance
|
||||
_preloader = new(members_attributes[index])//preloader for assigning set variables on atom creation
|
||||
|
||||
instance = locate(members[index])
|
||||
instance.contents.Add(locate(xcrd,ycrd,zcrd))
|
||||
|
||||
if(_preloader && instance)
|
||||
_preloader.load(instance)
|
||||
|
||||
members.Remove(members[index])
|
||||
|
||||
//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++
|
||||
|
||||
//instanciate the first /turf
|
||||
var/turf/T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],xcrd,ycrd,zcrd)
|
||||
|
||||
//if others /turf are presents, simulates the underlays piling effect
|
||||
index = first_turf_index + 1
|
||||
while(index <= members.len)
|
||||
turfs_underlays.Insert(1,image(T.icon,null,T.icon_state,T.layer,T.dir))//add the current turf image to the underlays list
|
||||
var/turf/UT = instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd)//instance new turf
|
||||
add_underlying_turf(UT,T,turfs_underlays)//simulates the DMM piling effect
|
||||
T = UT
|
||||
index++
|
||||
|
||||
//finally instance all remainings objects/mobs
|
||||
for(index=1,index < first_turf_index,index++)
|
||||
instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd)
|
||||
|
||||
////////////////
|
||||
//Helpers procs
|
||||
////////////////
|
||||
|
||||
//Instance an atom at (x,y,z) and gives it the variables in attributes
|
||||
/dmm_suite/proc/instance_atom(var/path,var/list/attributes, var/x, var/y, var/z)
|
||||
var/atom/instance
|
||||
_preloader = new(attributes, path)
|
||||
|
||||
instance = new path (locate(x,y,z))//first preloader pass
|
||||
|
||||
if(_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
|
||||
_preloader.load(instance)
|
||||
|
||||
return instance
|
||||
|
||||
//text trimming (both directions) helper proc
|
||||
//optionally removes quotes before and after the text (for variable name)
|
||||
/dmm_suite/proc/trim_text(var/what as text,var/trim_quotes=0)
|
||||
while(length(what) && (findtext(what," ",1,2)))
|
||||
what=copytext(what,2,0)
|
||||
while(length(what) && (findtext(what," ",length(what),0)))
|
||||
what=copytext(what,1,length(what))
|
||||
if(trim_quotes)
|
||||
while(length(what) && (findtext(what,quote,1,2)))
|
||||
what=copytext(what,2,0)
|
||||
while(length(what) && (findtext(what,quote,length(what),0)))
|
||||
what=copytext(what,1,length(what))
|
||||
return 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(var/text as text,var/initial_position as num, var/delimiter=",",var/opening_escape=quote,var/closing_escape=quote)
|
||||
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))
|
||||
//return the filled list
|
||||
/dmm_suite/proc/text2list(var/text as text,var/delimiter=",")
|
||||
|
||||
var/list/to_return = list()
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
//Check for string
|
||||
if(findtext(trim_right,quote,1,2))
|
||||
trim_right = copytext(trim_right,2,findtext(trim_right,quote,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 = text2list(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)))
|
||||
|
||||
to_return[trim_left] = trim_right
|
||||
|
||||
else//simple var
|
||||
to_return[trim_left] = null
|
||||
|
||||
while(position != 0)
|
||||
|
||||
return to_return
|
||||
|
||||
//simulates the DM multiple turfs on one tile underlaying
|
||||
/dmm_suite/proc/add_underlying_turf(var/turf/placed,var/turf/underturf, var/list/turfs_underlays)
|
||||
if(underturf.density)
|
||||
placed.density = TRUE
|
||||
if(underturf.opacity)
|
||||
placed.opacity = 1
|
||||
placed.underlays += turfs_underlays
|
||||
|
||||
//atom creation method that preloads variables at creation
|
||||
/atom/New()
|
||||
if(_preloader && (src.type == _preloader.target_path))//in case the instanciated atom is creating other atoms in New()
|
||||
_preloader.load(src)
|
||||
|
||||
. = ..()
|
||||
|
||||
//////////////////
|
||||
//Preloader datum
|
||||
//////////////////
|
||||
|
||||
/dmm_suite/preloader
|
||||
parent_type = /datum
|
||||
var/list/attributes
|
||||
var/target_path
|
||||
|
||||
/dmm_suite/preloader/New(var/list/the_attributes, var/path)
|
||||
.=..()
|
||||
if(!the_attributes.len)
|
||||
Del()
|
||||
return
|
||||
attributes = the_attributes
|
||||
target_path = path
|
||||
|
||||
/dmm_suite/preloader/proc/load(atom/what)
|
||||
for(var/attribute in attributes)
|
||||
what.vars[attribute] = attributes[attribute]
|
||||
Del()
|
||||
@@ -1,678 +0,0 @@
|
||||
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32
|
||||
|
||||
/*
|
||||
SwapMaps library by Lummox JR
|
||||
developed for digitalBYOND
|
||||
http://www.digitalbyond.org
|
||||
|
||||
Version 2.1
|
||||
|
||||
The purpose of this library is to make it easy for authors to swap maps
|
||||
in and out of their game using savefiles. Swapped-out maps can be
|
||||
transferred between worlds for an MMORPG, sent to the client, etc.
|
||||
This is facilitated by the use of a special datum and a global list.
|
||||
|
||||
Uses of swapmaps:
|
||||
|
||||
- Temporary battle arenas
|
||||
- House interiors
|
||||
- Individual custom player houses
|
||||
- Virtually unlimited terrain
|
||||
- Sharing maps between servers running different instances of the same
|
||||
game
|
||||
- Loading and saving pieces of maps for reusable room templates
|
||||
*/
|
||||
|
||||
/*
|
||||
User Interface:
|
||||
|
||||
VARS:
|
||||
|
||||
swapmaps_iconcache
|
||||
An associative list of icon files with names, like
|
||||
'player.dmi' = "player"
|
||||
swapmaps_mode
|
||||
This must be set at runtime, like in world/New().
|
||||
|
||||
SWAPMAPS_SAV 0 (default)
|
||||
Uses .sav files for raw /savefile output.
|
||||
SWAPMAPS_TEXT 1
|
||||
Uses .txt files via ExportText() and ImportText(). These maps
|
||||
are easily editable and appear to take up less space in the
|
||||
current version of BYOND.
|
||||
|
||||
PROCS:
|
||||
|
||||
SwapMaps_Find(id)
|
||||
Find a map by its id
|
||||
SwapMaps_Load(id)
|
||||
Load a map by its id
|
||||
SwapMaps_Save(id)
|
||||
Save a map by its id (calls swapmap.Save())
|
||||
SwapMaps_Unload(id)
|
||||
Save and unload a map by its id (calls swapmap.Unload())
|
||||
SwapMaps_Save_All()
|
||||
Save all maps
|
||||
SwapMaps_DeleteFile(id)
|
||||
Delete a map file
|
||||
SwapMaps_CreateFromTemplate(id)
|
||||
Create a new map by loading another map to use as a template.
|
||||
This map has id==src and will not be saved. To make it savable,
|
||||
change id with swapmap.SetID(newid).
|
||||
SwapMaps_LoadChunk(id,turf/locorner)
|
||||
Load a swapmap as a "chunk", at a specific place. A new datum is
|
||||
created but it's not added to the list of maps to save or unload.
|
||||
The new datum can be safely deleted without affecting the turfs
|
||||
it loaded. The purpose of this is to load a map file onto part of
|
||||
another swapmap or an existing part of the world.
|
||||
locorner is the corner turf with the lowest x,y,z values.
|
||||
SwapMaps_SaveChunk(id,turf/corner1,turf/corner2)
|
||||
Save a piece of the world as a "chunk". A new datum is created
|
||||
for the chunk, but it can be deleted without destroying any turfs.
|
||||
The chunk file can be reloaded as a swapmap all its own, or loaded
|
||||
via SwapMaps_LoadChunk() to become part of another map.
|
||||
SwapMaps_GetSize(id)
|
||||
Return a list corresponding to the x,y,z sizes of a map file,
|
||||
without loading the map.
|
||||
Returns null if the map is not found.
|
||||
SwapMaps_AddIconToCache(name,icon)
|
||||
Cache an icon file by name for space-saving storage
|
||||
|
||||
swapmap.New(id,x,y,z)
|
||||
Create a new map; specify id, width (x), height (y), and
|
||||
depth (z)
|
||||
Default size is world.maxx,world.maxy,1
|
||||
swapmap.New(id,turf1,turf2)
|
||||
Create a new map; specify id and 2 corners
|
||||
This becomes a /swapmap for one of the compiled-in maps, for
|
||||
easy saving.
|
||||
swapmap.New()
|
||||
Create a new map datum, but does not allocate space or assign an
|
||||
ID (used for loading).
|
||||
swapmap.Del()
|
||||
Deletes a map but does not save
|
||||
swapmap.Save()
|
||||
Saves to map_[id].sav
|
||||
Maps with id==src are not saved.
|
||||
swapmap.Unload()
|
||||
Saves the map and then deletes it
|
||||
Maps with id==src are not saved.
|
||||
swapmap.SetID(id)
|
||||
Change the map's id and make changes to the lookup list
|
||||
swapmap.AllTurfs(z)
|
||||
Returns a block of turfs encompassing the entire map, or on just
|
||||
one z-level
|
||||
z is in world coordinates; it is optional
|
||||
swapmap.Contains(turf/T)
|
||||
Returns nonzero if T is inside the map's boundaries.
|
||||
Also works for objs and mobs, but the proc is not area-safe.
|
||||
swapmap.InUse()
|
||||
Returns nonzero if a mob with a key is within the map's
|
||||
boundaries.
|
||||
swapmap.LoCorner(z=z1)
|
||||
Returns locate(x1,y1,z), where z=z1 if none is specified.
|
||||
swapmap.HiCorner(z=z2)
|
||||
Returns locate(x2,y2,z), where z=z2 if none is specified.
|
||||
swapmap.BuildFilledRectangle(turf/corner1,turf/corner2,item)
|
||||
Builds a filled rectangle of item from one corner turf to the
|
||||
other, on multiple z-levels if necessary. The corners may be
|
||||
specified in any order.
|
||||
item is a type path like /turf/wall or /obj/barrel{full=1}.
|
||||
swapmap.BuildRectangle(turf/corner1,turf/corner2,item)
|
||||
Builds an unfilled rectangle of item from one corner turf to
|
||||
the other, on multiple z-levels if necessary.
|
||||
swapmap.BuildInTurfs(list/turfs,item)
|
||||
Builds item on all of the turfs listed. The list need not
|
||||
contain only turfs, or even only atoms.
|
||||
*/
|
||||
|
||||
swapmap
|
||||
var/id // a string identifying this map uniquely
|
||||
var/x1 // minimum x,y,z coords
|
||||
var/y1
|
||||
var/z1
|
||||
var/x2 // maximum x,y,z coords (also used as width,height,depth until positioned)
|
||||
var/y2
|
||||
var/z2
|
||||
var/tmp/locked // don't move anyone to this map; it's saving or loading
|
||||
var/tmp/mode // save as text-mode
|
||||
var/ischunk // tells the load routine to load to the specified location
|
||||
|
||||
New(_id,x,y,z)
|
||||
if(isnull(_id)) return
|
||||
id=_id
|
||||
mode=swapmaps_mode
|
||||
if(isturf(x) && isturf(y))
|
||||
/*
|
||||
Special format: Defines a map as an existing set of turfs;
|
||||
this is useful for saving a compiled map in swapmap format.
|
||||
Because this is a compiled-in map, its turfs are not deleted
|
||||
when the datum is deleted.
|
||||
*/
|
||||
x1=min(x:x,y:x);x2=max(x:x,y:x)
|
||||
y1=min(x:y,y:y);y2=max(x:y,y:y)
|
||||
z1=min(x:z,y:z);z2=max(x:z,y:z)
|
||||
InitializeSwapMaps()
|
||||
if(z2>swapmaps_compiled_maxz ||\
|
||||
y2>swapmaps_compiled_maxy ||\
|
||||
x2>swapmaps_compiled_maxx)
|
||||
qdel(src)
|
||||
return
|
||||
x2=x?(x):world.maxx
|
||||
y2=y?(y):world.maxy
|
||||
z2=z?(z):1
|
||||
AllocateSwapMap()
|
||||
|
||||
Del()
|
||||
// a temporary datum for a chunk can be deleted outright
|
||||
// for others, some cleanup is necessary
|
||||
if(!ischunk)
|
||||
swapmaps_loaded-=src
|
||||
swapmaps_byname-=id
|
||||
if(z2>swapmaps_compiled_maxz ||\
|
||||
y2>swapmaps_compiled_maxy ||\
|
||||
x2>swapmaps_compiled_maxx)
|
||||
var/list/areas=new
|
||||
for(var/atom/A in block(locate(x1,y1,z1),locate(x2,y2,z2)))
|
||||
for(var/obj/O in A) qdel(O)
|
||||
for(var/mob/M in A)
|
||||
if(!M.key) qdel(M)
|
||||
else M.loc=null
|
||||
areas[A.loc]=null
|
||||
qdel(A)
|
||||
// delete areas that belong only to this map
|
||||
for(var/area/a in areas)
|
||||
if(a && !a.contents.len) qdel(a)
|
||||
if(x2>=world.maxx || y2>=world.maxy || z2>=world.maxz) CutXYZ()
|
||||
qdel(areas)
|
||||
..()
|
||||
|
||||
/*
|
||||
Savefile format:
|
||||
map
|
||||
id
|
||||
x // size, not coords
|
||||
y
|
||||
z
|
||||
areas // list of areas, not including default
|
||||
[each z; 1 to depth]
|
||||
[each y; 1 to height]
|
||||
[each x; 1 to width]
|
||||
type // of turf
|
||||
AREA // if non-default; saved as a number (index into areas list)
|
||||
vars // all other changed vars
|
||||
*/
|
||||
Write(savefile/S)
|
||||
var/x
|
||||
var/y
|
||||
var/z
|
||||
var/n
|
||||
var/list/areas
|
||||
var/area/defarea=locate(world.area)
|
||||
if(!defarea) defarea=new world.area
|
||||
areas=list()
|
||||
for(var/turf/T in block(locate(x1,y1,z1),locate(x2,y2,z2)))
|
||||
areas[T.loc]=null
|
||||
for(n in areas) // quickly eliminate associations for smaller storage
|
||||
areas-=n
|
||||
areas+=n
|
||||
areas-=defarea
|
||||
InitializeSwapMaps()
|
||||
locked=1
|
||||
S["id"] << id
|
||||
S["z"] << z2-z1+1
|
||||
S["y"] << y2-y1+1
|
||||
S["x"] << x2-x1+1
|
||||
S["areas"] << areas
|
||||
for(n in 1 to areas.len) areas[areas[n]]=n
|
||||
var/oldcd=S.cd
|
||||
for(z=z1,z<=z2,++z)
|
||||
S.cd="[z-z1+1]"
|
||||
for(y=y1,y<=y2,++y)
|
||||
S.cd="[y-y1+1]"
|
||||
for(x=x1,x<=x2,++x)
|
||||
S.cd="[x-x1+1]"
|
||||
var/turf/T=locate(x,y,z)
|
||||
S["type"] << T.type
|
||||
if(T.loc!=defarea) S["AREA"] << areas[T.loc]
|
||||
T.Write(S)
|
||||
S.cd=".."
|
||||
S.cd=".."
|
||||
sleep()
|
||||
S.cd=oldcd
|
||||
locked=0
|
||||
qdel(areas)
|
||||
|
||||
Read(savefile/S,_id,turf/locorner)
|
||||
var/x
|
||||
var/y
|
||||
var/z
|
||||
var/n
|
||||
var/list/areas
|
||||
var/area/defarea=locate(world.area)
|
||||
id=_id
|
||||
if(locorner)
|
||||
ischunk=1
|
||||
x1=locorner.x
|
||||
y1=locorner.y
|
||||
z1=locorner.z
|
||||
if(!defarea) defarea=new world.area
|
||||
if(!_id)
|
||||
S["id"] >> id
|
||||
else
|
||||
var/dummy
|
||||
S["id"] >> dummy
|
||||
S["z"] >> z2 // these are depth,
|
||||
S["y"] >> y2 // height,
|
||||
S["x"] >> x2 // width
|
||||
S["areas"] >> areas
|
||||
locked=1
|
||||
AllocateSwapMap() // adjust x1,y1,z1 - x2,y2,z2 coords
|
||||
var/oldcd=S.cd
|
||||
for(z=z1,z<=z2,++z)
|
||||
S.cd="[z-z1+1]"
|
||||
for(y=y1,y<=y2,++y)
|
||||
S.cd="[y-y1+1]"
|
||||
for(x=x1,x<=x2,++x)
|
||||
S.cd="[x-x1+1]"
|
||||
var/tp
|
||||
S["type"]>>tp
|
||||
var/turf/T=locate(x,y,z)
|
||||
T.loc.contents-=T
|
||||
T=new tp(locate(x,y,z))
|
||||
if("AREA" in S.dir)
|
||||
S["AREA"]>>n
|
||||
var/area/A=areas[n]
|
||||
A.contents+=T
|
||||
else defarea.contents+=T
|
||||
// clear the turf
|
||||
for(var/obj/O in T) qdel(O)
|
||||
for(var/mob/M in T)
|
||||
if(!M.key) qdel(M)
|
||||
else M.loc=null
|
||||
// finish the read
|
||||
T.Read(S)
|
||||
S.cd=".."
|
||||
S.cd=".."
|
||||
sleep()
|
||||
S.cd=oldcd
|
||||
locked=0
|
||||
qdel(areas)
|
||||
|
||||
/*
|
||||
Find an empty block on the world map in which to load this map.
|
||||
If no space is found, increase world.maxz as necessary. (If the
|
||||
map is greater in x,y size than the current world, expand
|
||||
world.maxx and world.maxy too.)
|
||||
|
||||
Ignore certain operations if loading a map as a chunk. Use the
|
||||
x1,y1,z1 position for it, and *don't* count it as a loaded map.
|
||||
*/
|
||||
proc/AllocateSwapMap()
|
||||
InitializeSwapMaps()
|
||||
world.maxx=max(x2,world.maxx) // stretch x/y if necessary
|
||||
world.maxy=max(y2,world.maxy)
|
||||
if(!ischunk)
|
||||
if(world.maxz<=swapmaps_compiled_maxz)
|
||||
z1=swapmaps_compiled_maxz+1
|
||||
x1=1;y1=1
|
||||
else
|
||||
var/list/l=ConsiderRegion(1,1,world.maxx,world.maxy,swapmaps_compiled_maxz+1)
|
||||
x1=l[1]
|
||||
y1=l[2]
|
||||
z1=l[3]
|
||||
qdel(l)
|
||||
x2+=x1-1
|
||||
y2+=y1-1
|
||||
z2+=z1-1
|
||||
world.maxz=max(z2,world.maxz) // stretch z if necessary
|
||||
if(!ischunk)
|
||||
swapmaps_loaded[src]=null
|
||||
swapmaps_byname[id]=src
|
||||
|
||||
proc/ConsiderRegion(X1,Y1,X2,Y2,Z1,Z2)
|
||||
while(1)
|
||||
var/nextz=0
|
||||
var/swapmap/M
|
||||
for(M in swapmaps_loaded)
|
||||
if(M.z2<Z1 || (Z2 && M.z1>Z2) || M.z1>=Z1+z2 ||\
|
||||
M.x1>X2 || M.x2<X1 || M.x1>=X1+x2 ||\
|
||||
M.y1>Y2 || M.y2<Y1 || M.y1>=Y1+y2) continue
|
||||
// look for sub-regions with a defined ceiling
|
||||
var/nz2=Z2?(Z2):Z1+z2-1+M.z2-M.z1
|
||||
if(M.x1>=X1+x2)
|
||||
.=ConsiderRegion(X1,Y1,M.x1-1,Y2,Z1,nz2)
|
||||
if(.) return
|
||||
else if(M.x2<=X2-x2)
|
||||
.=ConsiderRegion(M.x2+1,Y1,X2,Y2,Z1,nz2)
|
||||
if(.) return
|
||||
if(M.y1>=Y1+y2)
|
||||
.=ConsiderRegion(X1,Y1,X2,M.y1-1,Z1,nz2)
|
||||
if(.) return
|
||||
else if(M.y2<=Y2-y2)
|
||||
.=ConsiderRegion(X1,M.y2+1,X2,Y2,Z1,nz2)
|
||||
if(.) return
|
||||
nextz=nextz?min(nextz,M.z2+1):(M.z2+1)
|
||||
if(!M)
|
||||
/* If nextz is not 0, then at some point there was an overlap that
|
||||
could not be resolved by using an area to the side */
|
||||
if(nextz) Z1=nextz
|
||||
if(!nextz || (Z2 && Z2-Z1+1<z2))
|
||||
return (!Z2 || Z2-Z1+1>=z2)?list(X1,Y1,Z1):null
|
||||
X1=1;X2=world.maxx
|
||||
Y1=1;Y2=world.maxy
|
||||
|
||||
proc/CutXYZ()
|
||||
var/mx=swapmaps_compiled_maxx
|
||||
var/my=swapmaps_compiled_maxy
|
||||
var/mz=swapmaps_compiled_maxz
|
||||
for(var/swapmap/M in swapmaps_loaded) // may not include src
|
||||
mx=max(mx,M.x2)
|
||||
my=max(my,M.y2)
|
||||
mz=max(mz,M.z2)
|
||||
world.maxx=mx
|
||||
world.maxy=my
|
||||
world.maxz=mz
|
||||
|
||||
// save and delete
|
||||
proc/Unload()
|
||||
Save()
|
||||
qdel(src)
|
||||
|
||||
proc/Save()
|
||||
if(id==src) return 0
|
||||
var/savefile/S=mode?(new):new("map_[id].sav")
|
||||
S << src
|
||||
while(locked) sleep(1)
|
||||
if(mode)
|
||||
fdel("map_[id].txt")
|
||||
S.ExportText("/","map_[id].txt")
|
||||
return 1
|
||||
|
||||
// this will not delete existing savefiles for this map
|
||||
proc/SetID(newid)
|
||||
swapmaps_byname-=id
|
||||
id=newid
|
||||
swapmaps_byname[id]=src
|
||||
|
||||
proc/AllTurfs(z)
|
||||
if(isnum(z) && (z<z1 || z>z2)) return null
|
||||
return block(LoCorner(z),HiCorner(z))
|
||||
|
||||
// this could be safely called for an obj or mob as well, but
|
||||
// probably not an area
|
||||
proc/Contains(turf/T)
|
||||
return (T && T.x>=x1 && T.x<=x2\
|
||||
&& T.y>=y1 && T.y<=y2\
|
||||
&& T.z>=z1 && T.z<=z2)
|
||||
|
||||
proc/InUse()
|
||||
for(var/turf/T in AllTurfs())
|
||||
for(var/mob/M in T) if(M.key) return 1
|
||||
|
||||
proc/LoCorner(z=z1)
|
||||
return locate(x1,y1,z)
|
||||
proc/HiCorner(z=z2)
|
||||
return locate(x2,y2,z)
|
||||
|
||||
/*
|
||||
Build procs: Take 2 turfs as corners, plus an item type.
|
||||
An item may be like:
|
||||
|
||||
turf/wall
|
||||
obj/fence{icon_state="iron"}
|
||||
*/
|
||||
proc/BuildFilledRectangle(turf/T1,turf/T2,item)
|
||||
if(!Contains(T1) || !Contains(T2)) return
|
||||
var/turf/T=T1
|
||||
// pick new corners in a block()-friendly form
|
||||
T1=locate(min(T1.x,T2.x),min(T1.y,T2.y),min(T1.z,T2.z))
|
||||
T2=locate(max(T.x,T2.x),max(T.y,T2.y),max(T.z,T2.z))
|
||||
for(T in block(T1,T2)) new item(T)
|
||||
|
||||
proc/BuildRectangle(turf/T1,turf/T2,item)
|
||||
if(!Contains(T1) || !Contains(T2)) return
|
||||
var/turf/T=T1
|
||||
// pick new corners in a block()-friendly form
|
||||
T1=locate(min(T1.x,T2.x),min(T1.y,T2.y),min(T1.z,T2.z))
|
||||
T2=locate(max(T.x,T2.x),max(T.y,T2.y),max(T.z,T2.z))
|
||||
if(T2.x-T1.x<2 || T2.y-T1.y<2) BuildFilledRectangle(T1,T2,item)
|
||||
else
|
||||
//for(T in block(T1,T2)-block(locate(T1.x+1,T1.y+1,T1.z),locate(T2.x-1,T2.y-1,T2.z)))
|
||||
for(T in block(T1,locate(T2.x,T1.y,T2.z))) new item(T)
|
||||
for(T in block(locate(T1.x,T2.y,T1.z),T2)) new item(T)
|
||||
for(T in block(locate(T1.x,T1.y+1,T1.z),locate(T1.x,T2.y-1,T2.z))) new item(T)
|
||||
for(T in block(locate(T2.x,T1.y+1,T1.z),locate(T2.x,T2.y-1,T2.z))) new item(T)
|
||||
|
||||
/*
|
||||
Supplementary build proc: Takes a list of turfs, plus an item
|
||||
type. Actually the list doesn't have to be just turfs.
|
||||
*/
|
||||
proc/BuildInTurfs(list/turfs,item)
|
||||
for(var/T in turfs) new item(T)
|
||||
|
||||
atom
|
||||
Write(savefile/S)
|
||||
for(var/V in vars-"x"-"y"-"z"-"contents"-"icon"-"overlays"-"underlays")
|
||||
if(issaved(vars[V]))
|
||||
if(vars[V]!=initial(vars[V])) S[V]<<vars[V]
|
||||
else S.dir.Remove(V)
|
||||
if(icon!=initial(icon))
|
||||
if(swapmaps_iconcache && swapmaps_iconcache[icon])
|
||||
S["icon"]<<swapmaps_iconcache[icon]
|
||||
else S["icon"]<<icon
|
||||
// do not save mobs with keys; do save other mobs
|
||||
var/mob/M
|
||||
for(M in src) if(M.key) break
|
||||
if(overlays.len) S["overlays"]<<overlays
|
||||
if(underlays.len) S["underlays"]<<underlays
|
||||
if(contents.len && !isarea(src))
|
||||
var/list/l=contents
|
||||
if(M)
|
||||
l=l.Copy()
|
||||
for(M in src) if(M.key) l-=M
|
||||
if(l.len) S["contents"]<<l
|
||||
if(l!=contents) qdel(l)
|
||||
Read(savefile/S)
|
||||
var/list/l
|
||||
if(contents.len) l=contents
|
||||
..()
|
||||
// if the icon was a text string, it would not have loaded properly
|
||||
// replace it from the cache list
|
||||
if(!icon && ("icon" in S.dir))
|
||||
var/ic
|
||||
S["icon"]>>ic
|
||||
if(istext(ic)) icon=swapmaps_iconcache[ic]
|
||||
if(l && contents!=l)
|
||||
contents+=l
|
||||
qdel(l)
|
||||
|
||||
|
||||
// set this up (at runtime) as follows:
|
||||
// list(\
|
||||
// 'player.dmi'="player",\
|
||||
// 'monster.dmi'="monster",\
|
||||
// ...
|
||||
// 'item.dmi'="item")
|
||||
var/list/swapmaps_iconcache
|
||||
|
||||
// preferred mode; sav or text
|
||||
var/const/SWAPMAPS_SAV=0
|
||||
var/const/SWAPMAPS_TEXT=1
|
||||
var/swapmaps_mode=SWAPMAPS_SAV
|
||||
|
||||
var/swapmaps_compiled_maxx
|
||||
var/swapmaps_compiled_maxy
|
||||
var/swapmaps_compiled_maxz
|
||||
var/swapmaps_initialized
|
||||
var/swapmaps_loaded
|
||||
var/swapmaps_byname
|
||||
|
||||
/proc/InitializeSwapMaps()
|
||||
if(swapmaps_initialized) return
|
||||
swapmaps_initialized=1
|
||||
swapmaps_compiled_maxx=world.maxx
|
||||
swapmaps_compiled_maxy=world.maxy
|
||||
swapmaps_compiled_maxz=world.maxz
|
||||
swapmaps_loaded=list()
|
||||
swapmaps_byname=list()
|
||||
if(swapmaps_iconcache)
|
||||
for(var/V in swapmaps_iconcache)
|
||||
// reverse-associate everything
|
||||
// so you can look up an icon file by name or vice-versa
|
||||
swapmaps_iconcache[swapmaps_iconcache[V]]=V
|
||||
|
||||
/proc/SwapMaps_AddIconToCache(name,icon)
|
||||
if(!swapmaps_iconcache) swapmaps_iconcache=list()
|
||||
swapmaps_iconcache[name]=icon
|
||||
swapmaps_iconcache[icon]=name
|
||||
|
||||
/proc/SwapMaps_Find(id)
|
||||
InitializeSwapMaps()
|
||||
return swapmaps_byname[id]
|
||||
|
||||
/proc/SwapMaps_Load(id)
|
||||
InitializeSwapMaps()
|
||||
var/swapmap/M=swapmaps_byname[id]
|
||||
if(!M)
|
||||
var/savefile/S
|
||||
var/text=0
|
||||
if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[id].txt"))
|
||||
text=1
|
||||
else if(fexists("map_[id].sav"))
|
||||
S=new("map_[id].sav")
|
||||
else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[id].txt"))
|
||||
text=1
|
||||
else return // no file found
|
||||
if(text)
|
||||
S=new
|
||||
S.ImportText("/",file("map_[id].txt"))
|
||||
S >> M
|
||||
while(M.locked) sleep(1)
|
||||
M.mode=text
|
||||
return M
|
||||
|
||||
/proc/SwapMaps_Save(id)
|
||||
InitializeSwapMaps()
|
||||
var/swapmap/M=swapmaps_byname[id]
|
||||
if(M) M.Save()
|
||||
return M
|
||||
|
||||
/proc/SwapMaps_Save_All()
|
||||
InitializeSwapMaps()
|
||||
for(var/swapmap/M in swapmaps_loaded)
|
||||
if(M) M.Save()
|
||||
|
||||
/proc/SwapMaps_Unload(id)
|
||||
InitializeSwapMaps()
|
||||
var/swapmap/M=swapmaps_byname[id]
|
||||
if(!M) return // return silently from an error
|
||||
M.Unload()
|
||||
return 1
|
||||
|
||||
/proc/SwapMaps_DeleteFile(id)
|
||||
fdel("map_[id].sav")
|
||||
fdel("map_[id].txt")
|
||||
|
||||
/proc/SwapMaps_CreateFromTemplate(template_id)
|
||||
var/swapmap/M=new
|
||||
var/savefile/S
|
||||
var/text=0
|
||||
if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[template_id].txt"))
|
||||
text=1
|
||||
else if(fexists("map_[template_id].sav"))
|
||||
S=new("map_[template_id].sav")
|
||||
else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[template_id].txt"))
|
||||
text=1
|
||||
else
|
||||
to_world_log("SwapMaps error in SwapMaps_CreateFromTemplate(): map_[template_id] file not found.")
|
||||
return
|
||||
if(text)
|
||||
S=new
|
||||
S.ImportText("/",file("map_[template_id].txt"))
|
||||
/*
|
||||
This hacky workaround is needed because S >> M will create a brand new
|
||||
M to fill with data. There's no way to control the Read() process
|
||||
properly otherwise. The //.0 path should always match the map, however.
|
||||
*/
|
||||
S.cd="//.0"
|
||||
M.Read(S,M)
|
||||
M.mode=text
|
||||
while(M.locked) sleep(1)
|
||||
return M
|
||||
|
||||
/proc/SwapMaps_LoadChunk(chunk_id,turf/locorner)
|
||||
var/swapmap/M=new
|
||||
var/savefile/S
|
||||
var/text=0
|
||||
if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[chunk_id].txt"))
|
||||
text=1
|
||||
else if(fexists("map_[chunk_id].sav"))
|
||||
S=new("map_[chunk_id].sav")
|
||||
else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[chunk_id].txt"))
|
||||
text=1
|
||||
else
|
||||
to_world_log("SwapMaps error in SwapMaps_LoadChunk(): map_[chunk_id] file not found.")
|
||||
return
|
||||
if(text)
|
||||
S=new
|
||||
S.ImportText("/",file("map_[chunk_id].txt"))
|
||||
/*
|
||||
This hacky workaround is needed because S >> M will create a brand new
|
||||
M to fill with data. There's no way to control the Read() process
|
||||
properly otherwise. The //.0 path should always match the map, however.
|
||||
*/
|
||||
S.cd="//.0"
|
||||
M.Read(S,M,locorner)
|
||||
while(M.locked) sleep(1)
|
||||
qdel(M)
|
||||
return 1
|
||||
|
||||
/proc/SwapMaps_SaveChunk(chunk_id,turf/corner1,turf/corner2)
|
||||
if(!corner1 || !corner2)
|
||||
to_world_log("SwapMaps error in SwapMaps_SaveChunk():")
|
||||
if(!corner1) to_world_log(" corner1 turf is null")
|
||||
if(!corner2) to_world_log(" corner2 turf is null")
|
||||
return
|
||||
var/swapmap/M=new
|
||||
M.id=chunk_id
|
||||
M.ischunk=1 // this is a chunk
|
||||
M.x1=min(corner1.x,corner2.x)
|
||||
M.y1=min(corner1.y,corner2.y)
|
||||
M.z1=min(corner1.z,corner2.z)
|
||||
M.x2=max(corner1.x,corner2.x)
|
||||
M.y2=max(corner1.y,corner2.y)
|
||||
M.z2=max(corner1.z,corner2.z)
|
||||
M.mode=swapmaps_mode
|
||||
M.Save()
|
||||
while(M.locked) sleep(1)
|
||||
qdel(M)
|
||||
return 1
|
||||
|
||||
/proc/SwapMaps_GetSize(id)
|
||||
var/savefile/S
|
||||
var/text=0
|
||||
if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[id].txt"))
|
||||
text=1
|
||||
else if(fexists("map_[id].sav"))
|
||||
S=new("map_[id].sav")
|
||||
else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[id].txt"))
|
||||
text=1
|
||||
else
|
||||
to_world_log("SwapMaps error in SwapMaps_GetSize(): map_[id] file not found.")
|
||||
return
|
||||
if(text)
|
||||
S=new
|
||||
S.ImportText("/",file("map_[id].txt"))
|
||||
/*
|
||||
The //.0 path should always be the map. There's no other way to
|
||||
read this data.
|
||||
*/
|
||||
S.cd="//.0"
|
||||
var/x
|
||||
var/y
|
||||
var/z
|
||||
S["x"] >> x
|
||||
S["y"] >> y
|
||||
S["z"] >> z
|
||||
return list(x,y,z)
|
||||
@@ -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"
|
||||
@@ -1,181 +0,0 @@
|
||||
#define DMM_IGNORE_AREAS 1
|
||||
#define DMM_IGNORE_TURFS 2
|
||||
#define DMM_IGNORE_OBJS 4
|
||||
#define DMM_IGNORE_NPCS 8
|
||||
#define DMM_IGNORE_PLAYERS 16
|
||||
#define DMM_IGNORE_MOBS 24
|
||||
dmm_suite{
|
||||
var{
|
||||
quote = "\""
|
||||
list/letter_digits = list(
|
||||
"a","b","c","d","e",
|
||||
"f","g","h","i","j",
|
||||
"k","l","m","n","o",
|
||||
"p","q","r","s","t",
|
||||
"u","v","w","x","y",
|
||||
"z",
|
||||
"A","B","C","D","E",
|
||||
"F","G","H","I","J",
|
||||
"K","L","M","N","O",
|
||||
"P","Q","R","S","T",
|
||||
"U","V","W","X","Y",
|
||||
"Z"
|
||||
)
|
||||
}
|
||||
save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){
|
||||
//Check for illegal characters in file name... in a cheap way.
|
||||
if(!((ckeyEx(map_name)==map_name) && ckeyEx(map_name))){
|
||||
CRASH("Invalid text supplied to proc save_map, invalid characters or empty string.")
|
||||
}
|
||||
//Check for valid turfs.
|
||||
if(!isturf(t1) || !isturf(t2)){
|
||||
CRASH("Invalid arguments supplied to proc save_map, arguments were not turfs.")
|
||||
}
|
||||
var/file_text = write_map(t1,t2,flags)
|
||||
if(fexists("[map_name].dmm")){
|
||||
fdel("[map_name].dmm")
|
||||
}
|
||||
var/saved_map = file("[map_name].dmm")
|
||||
saved_map << file_text
|
||||
return saved_map
|
||||
}
|
||||
write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){
|
||||
//Check for valid turfs.
|
||||
if(!isturf(t1) || !isturf(t2)){
|
||||
CRASH("Invalid arguments supplied to proc write_map, arguments were not turfs.")
|
||||
}
|
||||
var/turf/nw = locate(min(t1.x,t2.x),max(t1.y,t2.y),min(t1.z,t2.z))
|
||||
var/turf/se = locate(max(t1.x,t2.x),min(t1.y,t2.y),max(t1.z,t2.z))
|
||||
var/list/templates[0]
|
||||
var/template_buffer = {""}
|
||||
var/dmm_text = {""}
|
||||
for(var/pos_z=nw.z;pos_z<=se.z;pos_z++){
|
||||
for(var/pos_y=nw.y;pos_y>=se.y;pos_y--){
|
||||
for(var/pos_x=nw.x;pos_x<=se.x;pos_x++){
|
||||
var/turf/test_turf = locate(pos_x,pos_y,pos_z)
|
||||
var/test_template = make_template(test_turf, flags)
|
||||
var/template_number = templates.Find(test_template)
|
||||
if(!template_number){
|
||||
templates.Add(test_template)
|
||||
template_number = templates.len
|
||||
}
|
||||
template_buffer += "[template_number],"
|
||||
}
|
||||
template_buffer += ";"
|
||||
}
|
||||
template_buffer += "."
|
||||
}
|
||||
var/key_length = round/*floor*/(log(letter_digits.len,templates.len-1)+1)
|
||||
var/list/keys[templates.len]
|
||||
for(var/key_pos=1;key_pos<=templates.len;key_pos++){
|
||||
keys[key_pos] = get_model_key(key_pos,key_length)
|
||||
dmm_text += {""[keys[key_pos]]" = ([templates[key_pos]])\n"}
|
||||
}
|
||||
var/z_level = 0
|
||||
for(var/z_pos=1;TRUE;z_pos=findtext(template_buffer,".",z_pos)+1){
|
||||
if(z_pos>=length(template_buffer)){break}
|
||||
if(z_level){dmm_text+={"\n"}}
|
||||
dmm_text += {"\n(1,1,[++z_level]) = {"\n"}
|
||||
var/z_block = copytext(template_buffer,z_pos,findtext(template_buffer,".",z_pos))
|
||||
for(var/y_pos=1;TRUE;y_pos=findtext(z_block,";",y_pos)+1){
|
||||
if(y_pos>=length(z_block)){break}
|
||||
var/y_block = copytext(z_block,y_pos,findtext(z_block,";",y_pos))
|
||||
for(var/x_pos=1;TRUE;x_pos=findtext(y_block,",",x_pos)+1){
|
||||
if(x_pos>=length(y_block)){break}
|
||||
var/x_block = copytext(y_block,x_pos,findtext(y_block,",",x_pos))
|
||||
var/key_number = text2num(x_block)
|
||||
var/temp_key = keys[key_number]
|
||||
dmm_text += temp_key
|
||||
sleep(-1)
|
||||
}
|
||||
dmm_text += {"\n"}
|
||||
sleep(-1)
|
||||
}
|
||||
dmm_text += {"\"}"}
|
||||
sleep(-1)
|
||||
}
|
||||
return dmm_text
|
||||
}
|
||||
proc{
|
||||
make_template(var/turf/model as turf, var/flags as num){
|
||||
var/template = ""
|
||||
var/obj_template = ""
|
||||
var/mob_template = ""
|
||||
var/turf_template = ""
|
||||
if(!(flags & DMM_IGNORE_TURFS)){
|
||||
turf_template = "[model.type][check_attributes(model)],"
|
||||
} else{ turf_template = "[world.turf],"}
|
||||
var/area_template = ""
|
||||
if(!(flags & DMM_IGNORE_OBJS)){
|
||||
for(var/obj/O in model.contents){
|
||||
obj_template += "[O.type][check_attributes(O)],"
|
||||
}
|
||||
}
|
||||
for(var/mob/M in model.contents){
|
||||
if(M.client){
|
||||
if(!(flags & DMM_IGNORE_PLAYERS)){
|
||||
mob_template += "[M.type][check_attributes(M)],"
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(!(flags & DMM_IGNORE_NPCS)){
|
||||
mob_template += "[M.type][check_attributes(M)],"
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!(flags & DMM_IGNORE_AREAS)){
|
||||
var/area/m_area = model.loc
|
||||
area_template = "[m_area.type][check_attributes(m_area)]"
|
||||
} else{ area_template = "[world.area]"}
|
||||
template = "[obj_template][mob_template][turf_template][area_template]"
|
||||
return template
|
||||
}
|
||||
check_attributes(var/atom/A){
|
||||
var/attributes_text = {"{"}
|
||||
for(var/V in A.vars){
|
||||
sleep(-1)
|
||||
if((!issaved(A.vars[V])) || (A.vars[V]==initial(A.vars[V]))){continue}
|
||||
if(istext(A.vars[V])){
|
||||
attributes_text += {"[V] = "[A.vars[V]]""}
|
||||
}
|
||||
else if(isnum(A.vars[V])||ispath(A.vars[V])){
|
||||
attributes_text += {"[V] = [A.vars[V]]"}
|
||||
}
|
||||
else if(isicon(A.vars[V])||isfile(A.vars[V])){
|
||||
attributes_text += {"[V] = '[A.vars[V]]'"}
|
||||
}
|
||||
else{
|
||||
continue
|
||||
}
|
||||
if(attributes_text != {"{"}){
|
||||
attributes_text+={"; "}
|
||||
}
|
||||
}
|
||||
if(attributes_text=={"{"}){
|
||||
return
|
||||
}
|
||||
if(copytext(attributes_text, length(attributes_text)-1, 0) == {"; "}){
|
||||
attributes_text = copytext(attributes_text, 1, length(attributes_text)-1)
|
||||
}
|
||||
attributes_text += {"}"}
|
||||
return attributes_text
|
||||
}
|
||||
get_model_key(var/which as num, var/key_length as num){
|
||||
var/key = ""
|
||||
var/working_digit = which-1
|
||||
for(var/digit_pos=key_length;digit_pos>=1;digit_pos--){
|
||||
var/place_value = round/*floor*/(working_digit/(letter_digits.len**(digit_pos-1)))
|
||||
working_digit-=place_value*(letter_digits.len**(digit_pos-1))
|
||||
key = "[key][letter_digits[place_value+1]]"
|
||||
}
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef DMM_IGNORE_AREAS
|
||||
#undef DMM_IGNORE_TURFS
|
||||
#undef DMM_IGNORE_OBJS
|
||||
#undef DMM_IGNORE_NPCS
|
||||
#undef DMM_IGNORE_PLAYERS
|
||||
#undef DMM_IGNORE_MOBS
|
||||
Reference in New Issue
Block a user