mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
Maploader improvements: Now skips space tiles, properly parses lists, commented and cleaned up.
beach.dmm is now unescapable. Fixes #3114
This commit is contained in:
74
code/modules/maps/dmm_suite.dm
Normal file
74
code/modules/maps/dmm_suite.dm
Normal file
@@ -0,0 +1,74 @@
|
||||
var/global/dmm_suite/maploader = new
|
||||
|
||||
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, var/load_speed 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).
|
||||
// load_speed: How many tiles should be loaded per second, defaults to no pause (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).
|
||||
}
|
||||
}
|
||||
325
code/modules/maps/reader.dm
Normal file
325
code/modules/maps/reader.dm
Normal file
@@ -0,0 +1,325 @@
|
||||
dmm_suite
|
||||
|
||||
var/debug_file = file("maploader_debug.txt")
|
||||
|
||||
load_map(var/dmm_file as file, var/z_offset as num, var/y_offset as num, var/x_offset as num, var/load_speed = 0 as num)
|
||||
if(!z_offset)
|
||||
z_offset = world.maxz + 1
|
||||
|
||||
//Ensure values are sane.
|
||||
else if(z_offset < 0)
|
||||
z_offset = abs(z_offset)
|
||||
else if(!isnum(z_offset))
|
||||
z_offset = 0
|
||||
|
||||
if(x_offset < 0)
|
||||
x_offset = abs(x_offset)
|
||||
else if(!isnum(x_offset))
|
||||
x_offset = 0
|
||||
|
||||
if(y_offset < 0)
|
||||
y_offset = abs(y_offset)
|
||||
else if(!isnum(y_offset))
|
||||
y_offset = 0
|
||||
|
||||
debug_file << "Starting Map Load @ ([x_offset], [y_offset], [z_offset]), [load_speed] tiles per second."
|
||||
|
||||
//Handle slowed loading.
|
||||
var/delay_chance = 0
|
||||
if(load_speed > 0)
|
||||
//Chance out of 100 every tenth of a second.
|
||||
delay_chance = 1000 / load_speed
|
||||
|
||||
//String holding a quotation mark.
|
||||
var/quote = ascii2text(34)
|
||||
|
||||
var/input_file = file2text(dmm_file)
|
||||
var/input_file_len = length(input_file)
|
||||
|
||||
//Stores the contents of each tile model in the map
|
||||
var/list/grid_models = list()
|
||||
//Length of the tile model code. e.g. "aaa" is 3 long.
|
||||
var/key_len = length(copytext(input_file, 2 ,findtext(input_file, quote, 2)))
|
||||
//The key of the default tile model. (In SS13 this is: "/turf/space,/area")
|
||||
var/default_key
|
||||
|
||||
debug_file << " Building turf array."
|
||||
|
||||
//Iterates through the mapfile to build the model tiles for the map.
|
||||
for(var/line_position = 1; line_position < input_file_len; line_position = findtext(input_file,"\n", line_position) + 1)
|
||||
var/next_line = copytext(input_file, line_position, findtext(input_file,"\n", line_position) - 1)
|
||||
|
||||
//If the first character in the line is not a quote, the model tiles are all defined.
|
||||
if(copytext(next_line, 1, 2) != quote)
|
||||
break
|
||||
|
||||
//Copy contents of the model into the grid_models list.
|
||||
var/model_key = copytext(next_line, 2, findtext(input_file, quote, 2))
|
||||
var/model_contents = copytext(next_line, findtext(next_line, "=" ) + 3)
|
||||
if(!default_key && model_contents == "[world.turf],[world.area]")
|
||||
default_key = model_key
|
||||
grid_models[model_key] = model_contents
|
||||
if(prob(delay_chance))
|
||||
sleep(1)
|
||||
|
||||
//Co-ordinates of the tile being loaded.
|
||||
var/z_coordinate = -1
|
||||
var/y_coordinate = 0
|
||||
var/x_coordinate = 0
|
||||
|
||||
//Store the
|
||||
var/y_depth = 0
|
||||
|
||||
//Iterate through all z-levels to load the tiles.
|
||||
for(var/z_position = findtext(input_file, "\n(1,1,"); TRUE; z_position = findtext(input_file, "\n(1,1,", z_position + 1))
|
||||
//break when there are no more z-levels.
|
||||
if(z_position == 0)
|
||||
break
|
||||
|
||||
//Increment the z_coordinate and update the world's borders
|
||||
z_coordinate++
|
||||
world.maxz = max(world.maxz, z_coordinate + z_offset)
|
||||
|
||||
//Here we go!
|
||||
y_coordinate = 0
|
||||
y_depth = 0
|
||||
var/z_level = copytext(input_file, \
|
||||
findtext(input_file, quote + "\n", z_position) + 2,\
|
||||
findtext(input_file, "\n" + quote, z_position) + 1)
|
||||
|
||||
//Iterate through each line, increasing the y_coordinate.
|
||||
for(var/grid_position = 1; grid_position != 0; grid_position = findtext(z_level, "\n", grid_position) + 1)
|
||||
//Grab this line of data.
|
||||
var/grid_line = copytext(z_level, grid_position, findtext(z_level, "\n", grid_position))
|
||||
|
||||
//Compute the size of the z-levels y axis.
|
||||
if(!y_depth)
|
||||
y_depth = length(z_level) / (length(grid_line) + 1)
|
||||
y_depth += y_offset
|
||||
if(y_depth != round(y_depth, 1))
|
||||
debug_file << " Warning: y_depth is not a round number"
|
||||
|
||||
//And update the worlds variables.
|
||||
if(world.maxy < y_depth)
|
||||
world.maxy = y_depth
|
||||
//The top of the map is the highest "y" co-ordinate, so we start there and iterate downwards
|
||||
if(!y_coordinate)
|
||||
y_coordinate = y_depth + 1
|
||||
|
||||
//Decrement and load this line of the map.
|
||||
y_coordinate--
|
||||
x_coordinate = x_offset
|
||||
|
||||
//Iterate through the line loading the model tile data.
|
||||
for(var/model_position = 1; model_position <= length(grid_line); model_position += key_len)
|
||||
x_coordinate++
|
||||
|
||||
//Find the model key and load that model.
|
||||
var/model_key = copytext(grid_line, model_position, model_position + key_len)
|
||||
//If the key is the default one, skip it and save the computation time.
|
||||
if(model_key == default_key)
|
||||
continue
|
||||
|
||||
if(world.maxx < x_coordinate)
|
||||
world.maxx = x_coordinate
|
||||
parse_grid(grid_models[model_key], x_coordinate, y_coordinate, z_coordinate + z_offset)
|
||||
|
||||
if(prob(delay_chance))
|
||||
sleep(1)
|
||||
|
||||
//If we hit the last tile in this z-level, we should break out of the loop.
|
||||
if(grid_position + length(grid_line) + 1 > length(z_level))
|
||||
break
|
||||
|
||||
//Break out of the loop when we hit the end of the file.
|
||||
if(findtext(input_file, quote + "}", z_position) + 2 >= input_file_len)
|
||||
break
|
||||
|
||||
|
||||
proc/parse_grid(var/model as text, var/x_coordinate as num, var/y_coordinate as num, var/z_coordinate as num)
|
||||
//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/text_strings = list()
|
||||
for(var/index = 1; findtext(model, quote); index++)
|
||||
/*Loop: Stores quoted portions of text in text_strings, and replaces them with an
|
||||
index to that list.
|
||||
- Each iteration represents one quoted section of text.
|
||||
*/
|
||||
//Add the next section of quoted text to the list
|
||||
var/first_quote = findtext(model, quote)
|
||||
var/second_quote = findtext(model, quote, first_quote + 1)
|
||||
var/quoted_chunk = copytext(model, first_quote + 1, second_quote)
|
||||
text_strings += quoted_chunk
|
||||
//Then remove the quoted section.
|
||||
model = copytext(model, 1, first_quote) + "~[index]" + copytext(model, second_quote + 1)
|
||||
|
||||
var/debug_output = 0
|
||||
//if(x_coordinate == 86 && y_coordinate == 88 && z_coordinate == 7)
|
||||
// debug_output = 1
|
||||
|
||||
if(debug_output)
|
||||
debug_file << " Now debugging turf: [model] ([x_coordinate], [y_coordinate], [z_coordinate])"
|
||||
|
||||
var/next_position = 1
|
||||
for(var/data_position = 1, next_position || data_position != 1, data_position = next_position + 1)
|
||||
next_position = findtext(model, ",/", data_position)
|
||||
|
||||
var/full_def = copytext(model, data_position, next_position)
|
||||
|
||||
if(debug_output)
|
||||
debug_file << " Current Line: [full_def] -- ([data_position] - [next_position])"
|
||||
|
||||
/*Loop: Identifies each object's data, instantiates it, and reconstitues it's fields.
|
||||
- Each iteration represents one object's data, including type path and field values.
|
||||
*/
|
||||
|
||||
//Load the attribute data.
|
||||
var/attribute_position = findtext(full_def,"{")
|
||||
var/atom_def = text2path(copytext(full_def, 1, attribute_position))
|
||||
|
||||
var/list/attributes = list()
|
||||
if(attribute_position)
|
||||
full_def = copytext(full_def, attribute_position + 1)
|
||||
if(debug_output)
|
||||
debug_file << " Atom Def: [atom_def]"
|
||||
debug_file << " Parameters: [full_def]"
|
||||
|
||||
var/next_attribute = 1
|
||||
for(attribute_position = 1, next_attribute || attribute_position != 1, attribute_position = next_attribute + 1)
|
||||
next_attribute = findtext(full_def, ";", attribute_position)
|
||||
|
||||
//Loop: Identifies each attribute/value pair, and stores it in attributes[].
|
||||
attributes += copytext(full_def, attribute_position, next_attribute)
|
||||
|
||||
//Construct attributes associative list
|
||||
var/list/fields = list()
|
||||
for(var/attribute in attributes)
|
||||
var/trim_left = trim_text(copytext(attribute, 1, findtext(attribute, "=")))
|
||||
var/trim_right = trim_text(copytext(attribute, findtext(attribute, "=") + 1))
|
||||
|
||||
if(findtext(trim_right, "list("))
|
||||
trim_right = get_list(trim_right, text_strings)
|
||||
|
||||
else if(findtext(trim_right, "~"))//Check for strings
|
||||
while(findtext(trim_right,"~"))
|
||||
var/reference_index = copytext(trim_right, findtext(trim_right, "~") + 1)
|
||||
trim_right = text_strings[text2num(reference_index)]
|
||||
|
||||
//Check for numbers
|
||||
else if(isnum(text2num(trim_right)))
|
||||
trim_right = text2num(trim_right)
|
||||
|
||||
//Check for file
|
||||
else if(copytext(trim_right,1,2) == "'")
|
||||
trim_right = file(copytext(trim_right, 2, length(trim_right)))
|
||||
|
||||
fields[trim_left] = trim_right
|
||||
sleep(-1)
|
||||
|
||||
|
||||
if(debug_output)
|
||||
var/return_data = " Debug Fields:"
|
||||
for(var/item in fields)
|
||||
return_data += " [item] = [fields[item]];"
|
||||
debug_file << return_data
|
||||
|
||||
//Begin Instanciation
|
||||
var/atom/instance
|
||||
|
||||
if(ispath(atom_def,/area))
|
||||
instance = locate(atom_def)
|
||||
if(!istype(instance, atom_def))
|
||||
instance = new atom_def
|
||||
instance.contents.Add(locate(x_coordinate,y_coordinate,z_coordinate))
|
||||
|
||||
else
|
||||
instance = new atom_def(locate(x_coordinate,y_coordinate,z_coordinate))
|
||||
if(instance)
|
||||
for(var/item in fields)
|
||||
instance.vars[item] = fields[item]
|
||||
else if(!(atom_def in borked_paths))
|
||||
borked_paths += atom_def
|
||||
var/return_data = " Failure [atom_def] @ ([x_coordinate], [y_coordinate], [z_coordinate]) fields:"
|
||||
for(var/item in fields)
|
||||
return_data += " [item] = [fields[item]];"
|
||||
debug_file << return_data
|
||||
|
||||
sleep(-1)
|
||||
return 1
|
||||
|
||||
var/list/borked_paths = list()
|
||||
|
||||
proc/trim_text(var/what as text)
|
||||
while(length(what) && findtext(what, " ", 1, 2))
|
||||
what = copytext(what, 2)
|
||||
|
||||
while(length(what) && findtext(what, " ", length(what)))
|
||||
what = copytext(what, 1, length(what))
|
||||
|
||||
return what
|
||||
|
||||
proc/get_list(var/text, var/list/text_strings)
|
||||
//First, trim the data to just the list contents
|
||||
var/list_start = findtext(text, "(") + 1
|
||||
var/list_end = findtext(text, ")", list_start)
|
||||
var/list_contents = copytext(text, list_start, list_end)
|
||||
|
||||
//Then, we seperate it into the individual entries
|
||||
|
||||
var/list/entries = list()
|
||||
var/entry_end = 1
|
||||
|
||||
for(var/entry_start = 1, entry_end || entry_start != 1, entry_start = entry_end + 1)
|
||||
entry_end = findtext(list_contents, ",", entry_start)
|
||||
entries += copytext(list_contents, entry_start, entry_end)
|
||||
|
||||
//Finally, we assemble the completed list.
|
||||
var/list/final_list = list()
|
||||
for(var/entry in entries)
|
||||
var/equals_position = findtext(entry, "=")
|
||||
|
||||
if(equals_position)
|
||||
var/trim_left = trim_text(copytext(entry, 1, equals_position))
|
||||
var/trim_right = trim_text(copytext(entry, equals_position + 1))
|
||||
|
||||
if(findtext(trim_right, "list("))
|
||||
trim_right = get_list(trim_right, text_strings)
|
||||
|
||||
else if(findtext(trim_right, "~"))//Check for strings
|
||||
while(findtext(trim_right,"~"))
|
||||
var/reference_index = copytext(trim_right, findtext(trim_right, "~") + 1)
|
||||
trim_right = text_strings[text2num(reference_index)]
|
||||
|
||||
//Check for numbers
|
||||
else if(isnum(text2num(trim_right)))
|
||||
trim_right = text2num(trim_right)
|
||||
|
||||
//Check for file
|
||||
else if(copytext(trim_right,1,2) == "'")
|
||||
trim_right = file(copytext(trim_right, 2, length(trim_right)))
|
||||
|
||||
if(findtext(trim_left, "~"))//Check for strings
|
||||
while(findtext(trim_left,"~"))
|
||||
var/reference_index = copytext(trim_left, findtext(trim_left, "~") + 1)
|
||||
trim_left = text_strings[text2num(reference_index)]
|
||||
|
||||
final_list[trim_left] = trim_right
|
||||
|
||||
else
|
||||
if(findtext(entry, "~"))//Check for strings
|
||||
while(findtext(entry, "~"))
|
||||
var/reference_index = copytext(entry, findtext(entry, "~") + 1)
|
||||
entry = text_strings[text2num(reference_index)]
|
||||
|
||||
//Check for numbers
|
||||
else if(isnum(text2num(entry)))
|
||||
entry = text2num(entry)
|
||||
|
||||
//Check for file
|
||||
else if(copytext(entry, 1, 2) == "'")
|
||||
entry = file(copytext(entry, 2, length(entry)))
|
||||
|
||||
final_list += entry
|
||||
|
||||
return final_list
|
||||
678
code/modules/maps/swapmaps.dm
Normal file
678
code/modules/maps/swapmaps.dm
Normal file
@@ -0,0 +1,678 @@
|
||||
//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)
|
||||
del(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) del(O)
|
||||
for(var/mob/M in A)
|
||||
if(!M.key) del(M)
|
||||
else M.loc=null
|
||||
areas[A.loc]=null
|
||||
del(A)
|
||||
// delete areas that belong only to this map
|
||||
for(var/area/a in areas)
|
||||
if(a && !a.contents.len) del(a)
|
||||
if(x2>=world.maxx || y2>=world.maxy || z2>=world.maxz) CutXYZ()
|
||||
del(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
|
||||
del(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) del(O)
|
||||
for(var/mob/M in T)
|
||||
if(!M.key) del(M)
|
||||
else M.loc=null
|
||||
// finish the read
|
||||
T.Read(S)
|
||||
S.cd=".."
|
||||
S.cd=".."
|
||||
sleep()
|
||||
S.cd=oldcd
|
||||
locked=0
|
||||
del(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]
|
||||
del(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()
|
||||
del(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) del(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
|
||||
del(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
|
||||
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
|
||||
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)
|
||||
del(M)
|
||||
return 1
|
||||
|
||||
proc/SwapMaps_SaveChunk(chunk_id,turf/corner1,turf/corner2)
|
||||
if(!corner1 || !corner2)
|
||||
world.log << "SwapMaps error in SwapMaps_SaveChunk():"
|
||||
if(!corner1) world.log << " corner1 turf is null"
|
||||
if(!corner2) 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)
|
||||
del(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
|
||||
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)
|
||||
174
code/modules/maps/writer.dm
Normal file
174
code/modules/maps/writer.dm
Normal file
@@ -0,0 +1,174 @@
|
||||
#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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user