1 module PixelPerfectEngine.map.mapformat;
2 /*
3  * Copyright (C) 2015-2019, by Laszlo Szeremi under the Boost license.
4  *
5  * Pixel Perfect Engine, map.mapformat module
6  */
7 import sdlang;
8 
9 import PixelPerfectEngine.graphics.layers;
10 import PixelPerfectEngine.graphics.raster : PaletteContainer;
11 import std.stdio;
12 public import PixelPerfectEngine.map.mapdata;
13 
14 /**
15  * Serializes/deserializes XMF map data in SDLang format.
16  * Each layer can contain objects (eg. for marking events, clipping, or sprites if applicable), tilemapping (not for SpriteLayers), embedded
17  * data such as tilemapping or scripts, and so on.
18  * <br/>
19  * Note on layer tags:
20  * As of this version, additional tags within layers must have individual names. Subtags within a parent also need to have individual names.
21  * Namespaces are reserved for internal use (eg. file sources, objects).
22  */
23 public class MapFormat {
24 	public Tag[int] 	layerData;	///Layerdata stored as SDLang tags.
25 	public Layer[int]	layeroutput;	///Used to fast map and object data pullback in editors
26 	protected Tag 		metadata;	///Stores metadata.
27 	protected Tag		root;		///Root tag for common information.
28 	public TileInfo[][int]	tileDataFromExt;///Stores basic TileData that are loaded through extensions
29 	/**
30 	 * Associative array used for rendering mode lookups in one way.
31 	 */
32 	public static immutable RenderingMode[string] renderingModeLookup;
33 	shared static this() {
34 		renderingModeLookup["Copy"] = RenderingMode.Copy;
35 		renderingModeLookup["Blitter"] = RenderingMode.Blitter;
36 		renderingModeLookup["AlphaBlend"] = RenderingMode.AlphaBlend;
37 		renderingModeLookup["Add"] = RenderingMode.Add;
38 		renderingModeLookup["AddBl"] = RenderingMode.AddBl;
39 		renderingModeLookup["Subtract"] = RenderingMode.Subtract;
40 		renderingModeLookup["SubtractBl"] = RenderingMode.SubtractBl;
41 		renderingModeLookup["Diff"] = RenderingMode.Diff;
42 		renderingModeLookup["DiffBl"] = RenderingMode.DiffBl;
43 		renderingModeLookup["Multiply"] = RenderingMode.Multiply;
44 		renderingModeLookup["MultiplyBl"] = RenderingMode.MultiplyBl;
45 		renderingModeLookup["Screen"] = RenderingMode.Screen;
46 		renderingModeLookup["ScreenBl"] = RenderingMode.ScreenBl;
47 		renderingModeLookup["AND"] = RenderingMode.AND;
48 		renderingModeLookup["OR"] = RenderingMode.OR;
49 		renderingModeLookup["XOR"] = RenderingMode.XOR;
50 	}
51 	/**
52 	 * Creates new instance from scratch.
53 	 */
54 	public this(string name, int resX, int resY) @trusted {
55 		root = new Tag();
56 		metadata = new Tag(root, null, "Metadata");
57 		new Tag(metadata, null, "Version", [Value(1), Value(0)]);
58 		new Tag(metadata, null, "Name", [Value(name)]);
59 		new Tag(metadata, null, "Resolution", [Value(resX), Value(resY)]);
60 	}
61 	/**
62 	 * Serializes itself from file.
63 	 */
64 	public this(F)(F file) @trusted {
65 		//File f = File(path, "rb");
66 		char[] source;
67 		source.length = cast(size_t)file.size;
68 		source = file.rawRead(source);
69 		root = parseSource(cast(string)source);
70 		//Just quickly go through the tags and sort them out
71 		foreach (Tag t0 ; root.all.tags) {
72 			switch (t0.namespace) {
73 				case "Layer":
74 					const int priority = t0.values[1].get!int;
75 					layerData[priority] = t0;
76 					RenderingMode lrd = renderingModeLookup.get(t0.getTagValue!string("RenderingMode"), RenderingMode.Copy);
77 					/+switch (t0.getTagValue!string("RenderingMode")) {
78 						case "AlphaBlending":
79 							lrd = RenderingMode.AlphaBlend;
80 							break;
81 						case "Blitter":
82 							lrd = LayerRenderingMode.BLITTER;
83 							break;
84 						case "Copy":
85 							lrd = LayerRenderingMode.COPY;
86 							break;
87 						default:
88 							break;
89 					}+/
90 					switch (t0.name) {
91 						case "Tile":
92 							layeroutput[priority] = new TileLayer(t0.values[2].get!int, t0.values[3].get!int, lrd);
93 							break;
94 						default:
95 							throw new Exception("Unsupported layer format");
96 					}
97 					break;
98 				/*case "Metadata":
99 					metadata = t0;
100 					break;*/
101 				default:
102 					if(t0.name == "Metadata"){
103 						metadata = t0;
104 					}
105 					break;
106 			}
107 		}
108 		//assert(layerData.length == layeroutput.length);
109 	}
110 	/**
111 	 * Loads tiles from disk to all layers. Also loads the palette.
112 	 * TODO: Add dpk support
113 	 */
114 	public void loadTiles (PaletteContainer paletteTarget) @trusted {
115 		import PixelPerfectEngine.system.file;
116 		foreach (key, value ; layerData) {
117 			if (value.name != "Tile") continue;
118 			Tag[] tileSource = getAllTileSources(key);
119 			foreach (t0; tileSource) {
120 				string path = t0.getValue!string();
121 				Image i = loadImage(File(path, "rb"));
122 				void helperFunc(T)(T[] bitmaps, Tag source) {
123 					TileLayer tl = cast(TileLayer)layeroutput[key];
124 					Tag tileInfo = source.getTag("Embed:TileInfo", null);
125 					if(tileInfo !is null)
126 						foreach (t1 ; tileInfo.tags) {
127 							tl.addTile(bitmaps[t1.values[0].get!int()], cast(wchar)t1.values[1].get!int());
128 						}
129 				}
130 				switch(i.getBitdepth){
131 					case 4:
132 						Bitmap4Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap4Bit)(i, value.values[2].get!int(), 
133 								value.values[3].get!int());
134 						helperFunc(bitmaps, t0);
135 						break;
136 					case 8:
137 						Bitmap8Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap8Bit)(i, value.values[2].get!int(), 
138 								value.values[3].get!int());
139 						helperFunc(bitmaps, t0);
140 						break;
141 					case 16:
142 						Bitmap16Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap16Bit)(i, value.values[2].get!int(), 
143 								value.values[3].get!int());
144 						helperFunc(bitmaps, t0);
145 						break;
146 					case 32:
147 						Bitmap32Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap32Bit)(i, value.values[2].get!int(), 
148 								value.values[3].get!int());
149 						helperFunc(bitmaps, t0);
150 						break;
151 					default:
152 						throw new Exception("Unsupported image bitdepth");
153 						
154 				}
155 				if (paletteTarget !is null && isPaletteFileExists(path)) {
156 					paletteTarget.loadPaletteChunk(loadPaletteFromImage(i), cast(ushort)t0.getAttribute!int("offset", 0));
157 				}
158 				//debug writeln(paletteTarget.palette);
159 			}
160 		}
161 	}
162 	/**
163 	 * Loads mapping data from disk to all layers.
164 	 */
165 	public void loadMappingData () @trusted {
166 		import PixelPerfectEngine.system.etc : reinterpretCast;
167 		foreach (key, value ; layerData) {
168 			Tag t0 = value.getTag("Embed:MapData");
169 			if (t0 !is null) {
170 				TileLayer tl = cast(TileLayer)layeroutput[key];
171 				//writeln(t0.getValue!(ubyte[])());
172 				tl.loadMapping(value.values[4].get!int(), value.values[5].get!int(), 
173 						reinterpretCast!MappingElement(t0.expectValue!(ubyte[])()));
174 				
175 				continue;
176 			}
177 			t0 = value.getTag("File:MapData");
178 			if (t0 !is null) {
179 				TileLayer tl = cast(TileLayer)layeroutput[key];
180 				MapDataHeader mdf;
181 				File mapfile = File(t0.expectValue!string());
182 				tl.loadMapping(value.values[4].get!int(), value.values[5].get!int(), loadMapFile(mapfile, mdf));
183 			}
184 		}
185 	}
186 	/**
187 	 * Saves the document to disc.
188 	 */
189 	public void save (string path) @trusted {
190 		debug writeln(root.tags);
191 		foreach(i; layerData.byKey){
192 			if(layerData[i].name == "Tile")
193 				pullMapDataFromLayer (i);
194 		}
195 		string output = root.toSDLDocument();
196 		File f = File(path, "wb+");
197 		f.write(output);
198 	}
199 	/**
200 	 * Returns given metadata.
201 	 */
202 	public T getMetadata(T)(string name)
203 			if (T.stringof == int.stringof || T.stringof == string.stringof) {
204 		return metadata.getTagValue!T(name);
205 	}
206 	/**
207 	 * Returns the requested layer
208 	 */
209 	public Layer opIndex(int index) @safe pure {
210 		return layeroutput.get(index, null);
211 	}
212 	/**
213 	 * Returns all layer's basic information.
214 	 */
215 	public LayerInfo[] getLayerInfo() @trusted {
216 		import std.algorithm.sorting : sort;
217 		LayerInfo[] result;
218 		foreach (Tag t ; layerData) {
219 			result ~= LayerInfo(LayerInfo.parseLayerTypeString(t.name), t.values[1].get!int(), t.values[0].get!string());
220 		}
221 		result.sort;
222 		return result;
223 	}
224 	/**
225 	 * Alters a tile layer data.
226 	 */
227 	public void alterTileLayerInfo(T)(int layerNum, int dataNum, T value) @trusted {
228 		layerData[layerNum].values[dataNum] = Value(value);
229 	}
230 	/**
231 	 * Returns a selected tile layer's all tile's basic information.
232 	 * Mainly used to display information in editors.
233 	 */
234 	public TileInfo[] getTileInfo(int pri) @trusted {
235 		import std.algorithm.sorting : sort;
236 		TileInfo[] result;
237 		try {
238 			foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
239 				//writeln(t0.toSDLString);
240 				if (t0.name == "TileSource") {
241 					Tag t1 = t0.getTag("Embed:TileInfo");
242 					ushort palShift = cast(ushort)t0.getAttribute!int("palShift", 0);
243 					if (t1 !is null) {
244 						foreach (Tag t2 ; t1.tags) {
245 							result ~= TileInfo(cast(wchar)t2.values[0].get!int(), palShift, t2.values[1].get!int(), t2.values[2].get!string());
246 						}
247 					}
248 				}
249 			}
250 		} catch (DOMRangeException e) {	///Just means there's no File namespace within the tag. Should be OK.
251 			debug writeln(e);
252 		} catch (Exception e) {
253 			debug writeln(e);
254 		}
255 		//writeln(result.length);
256 		result ~= tileDataFromExt.get(pri, []);
257 		result.sort;
258 		return result;
259 	}
260 	/**
261 	 * Adds TileInfo to a TileLayer.
262 	 * Joins together multiple chunks with the same source identifier. (should be a path)
263 	 */
264 	public void addTileInfo(int pri, TileInfo[] list, string source, string dpkSource = null) @trusted {
265 		if(list.length == 0) throw new Exception("Empty list!");
266 		Tag t;
267 		try{
268 			foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
269 				if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
270 					t = t0.getTag("Embed:TileInfo", null);
271 					if (t is null) { 
272 						t = new Tag(t0, "Embed", "TileInfo");
273 					}
274 					break;
275 				}
276 			}
277 			foreach (item ; list) {
278 				new Tag(t, null, null, [Value(cast(int)item.id), Value(item.num), Value(item.name)]);
279 			}
280 		} catch (Exception e) {
281 			debug writeln (e);
282 		}
283 		//writeln(t.tags.length);
284 		assert(t.tags.length == list.length);
285 	}
286 	///Ditto, but from preexisting Tag.
287 	public void addTileInfo(int pri, Tag t, string source, string dpkSource = null) @trusted {
288 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
289 			if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
290 				t0.add(t);
291 				return;
292 			}
293 		}
294 
295 	}
296 	/**
297 	 * Adds a single TileInfo to a preexisting chunk on the layer.
298 	 */
299 	public void addSingleTileInfo(int pri, TileInfo item, string source, string dpkSource = null) {
300 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
301 			if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
302 				Tag t1 = t0.getTag("Embed:TileInfo");
303 				if (t1 !is null) {
304 					new Tag (t1, null, null, [Value(cast(int)item.id), Value(item.num), Value(item.name)]);
305 				}
306 			}
307 		}
308 	}
309 	///Ditto, but from preexiting Tag.
310 	public void addSingleTileInfo(int pri, Tag t, string source) @trusted {
311 		foreach (Tag t0 ; layerData[pri].namespaces["Embed"].tags) {
312 			if (t0.name == "TileInfo" && t0.values.length >= 1 && t0.values[0].get!string() == source) {
313 				t0.add(t);
314 				return;
315 			}
316 		}
317 	}
318 	/**
319 	 * Removes a single tile from a TileInfo chunk.
320 	 * Returns a tag as a backup.
321 	 * Returns null if source is not found.
322 	 */
323 	public Tag removeTileInfo(int pri, string source) @trusted {
324 		foreach (Tag t0 ; layerData[pri].namespaces["Embed"].tags) {
325 			if (t0.name == "TileInfo" && t0.values.length >= 1 && t0.values[0].get!string() == source) {
326 				return t0.remove;
327 			}
328 		}
329 		return null;
330 	}
331 	/**
332 	 * Removes a given layer of any kind.
333 	 * Returns the Tag of the layer as a backup.
334 	 */
335 	public Tag removeLayer(int pri) @trusted {
336 		Tag backup = layerData[pri];
337 		layeroutput.remove(pri);
338 		layerData.remove(pri);
339 		return backup.remove;
340 	}
341 	/**
342 	 * Adds a layer from external tag.
343 	 */
344 	public void addNewLayer(int pri, Tag t, Layer l) @trusted {
345 		layeroutput[pri] = l;
346 		layerData[pri] = t;
347 		root.add(t);
348 	}
349 	/**
350 	 * Adds a new TileLayer to the document.
351 	 */
352 	public void addNewTileLayer(int pri, int tX, int tY, int mX, int mY, string name, TileLayer l) @trusted {
353 		layeroutput[pri] = l;
354 		l.setRasterizer(getHorizontalResolution, getVerticalResolution);
355 		layerData[pri] = new Tag(root, "Layer", "Tile", [Value(name), Value(pri), Value(tX), Value(tY), Value(mX), Value(mY)]);
356 		new Tag(layerData[pri], null, "RenderingMode", [Value("Copy")]);
357 	}
358 	/**
359 	 * Adds a new tag to a layer.
360 	 */
361 	public void addTagToLayer(T...)(int pri, string name, T args) @trusted {
362 		Value[] vals;
363 		foreach (arg; args) {
364 			vals ~= Value(arg);
365 		}
366 		new Tag(layerData[pri], null, name, vals);
367 	}
368 	/**
369 	 * Adds a new subtag to a layer's property tag.
370 	 */
371 	public void addSubTagToLayersProperty(T...)(int pri, string name, string parent, T args) @trusted {
372 		Value[] vals;
373 		foreach (arg; args) {
374 			vals ~= Value(arg);
375 		}
376 		new Tag(layerData[pri].expectTag(parent), null, name, vals);
377 	}
378 	/**
379 	 * Gets the values of a layer's root tag.
380 	 */
381 	public Value[] getLayerRootTagValues(int pri) @trusted {
382 		return layerData[pri].values;
383 	}
384 	/**
385 	 * Gets the values of a layer's tag.
386 	 */
387 	public Value[] getLayerTagValues(int pri, string name) @trusted {
388 		return layerData[pri].expectTag(name).values;
389 	}
390 	/**
391 	 * Gets the values of a layer's tag.
392 	 */
393 	public Value[] getLayerPropertyTagValues(int pri, string name, string parent) @trusted {
394 		return layerData[pri].expectTag(parent).expectTag(name).values;
395 	}
396 	/**
397 	 * Edits the values of a layer's tag. Returns the original values in an array.
398 	 */
399 	public Value[] editLayerTagValues(T...)(int pri, string name, T args) @trusted {
400 		Value[] backup = layerData[pri].expectTag(name).values;
401 		Value[] vals;
402 		foreach (arg; args) {
403 			vals ~= Value(arg);
404 		}
405 		//new Tag(layerData[pri], null, name, vals);
406 		layerData[pri].expectTag(name).values = vals;
407 		return backup;
408 	}
409 	/**
410 	 * Edits the values of a layer's subtag. Returns the original values in an array.
411 	 */
412 	public Value[] editLayerSubtagValues(T...)(int pri, string name, string parent, T args) @trusted {
413 		Value[] backup = layerData[pri].expectTag(parent).expectTag(name).values;
414 		Value[] vals;
415 		foreach (arg; args) {
416 			vals ~= Value(arg);
417 		}
418 		layerData[pri].expectTag(parent).expectTag(name).values = vals;
419 		return backup;
420 	}
421 	/**
422 	 * Removes a layer's tag.
423 	 * Returns a backup for undoing.
424 	 */
425 	public Tag removeLayerTagValues(int pri, string name) @trusted {
426 		return layerData[pri].expectTag(name).remove;
427 	}
428 	/**
429 	 * Adds an embedded MapData to a TileLayer.
430 	 */
431 	public void addEmbeddedMapData(int pri, ubyte[] base64Code) @trusted {
432 		layerData[pri].add(new Tag("Embed", "MapData", [Value(base64Code)]));
433 	}
434 	///Ditto
435 	public void addEmbeddedMapData(int pri, MappingElement[] me) @safe {
436 		import PixelPerfectEngine.system.etc : reinterpretCast;
437 		addEmbeddedMapData(pri, reinterpretCast!ubyte(me));
438 	}
439 	/**
440 	 * Adds a TileData file to a TileLayer.
441 	 * Filename must contain relative path.
442 	 */
443 	public void addMapDataFile(int pri, string filename, string dataPakSrc = null) @trusted {
444 		Attribute[] a;
445 		if (dataPakSrc !is null) a ~= new Attribute("dataPakSrc", Value(dataPakSrc));
446 		layerData[pri].add(new Tag("File", "MapData", [Value(filename)], a));
447 	}
448 	/+///Ditto, but for files found in DataPak archives
449 	public void addMapDataFile(int pri, string dataPakPath, string filename) @trusted {
450 		new Tag(layerData[pri], "File", "MapData", [Value(dataPakPath), Value(filename)]);
451 	}+/
452 	/**
453 	 * Removes embedded TileData from a TileLayer.
454 	 * Returns a backup for undoing.
455 	 */
456 	public Tag removeEmbeddedMapData(int pri) @trusted {
457 		return layerData[pri].expectTag("Embed:MapData").remove;
458 	}
459 	/**
460 	 * Removes a TileData file from a TileLayer.
461 	 * Returns a backup for undoing.
462 	 */
463 	public Tag removeMapDataFile(int pri) @trusted {
464 		return layerData[pri].expectTag("File:MapData").remove;
465 	}
466 	/**
467 	 * Pulls TileLayer data from the layer, and stores it in the preconfigured location.
468 	 * Only works with uncompressed data due to the need of recompression.
469 	 */
470 	public void pullMapDataFromLayer(int pri) @trusted {
471 		import PixelPerfectEngine.system.etc : reinterpretCast;
472 		ITileLayer t = cast(ITileLayer)layeroutput[pri];
473 		MappingElement[] mapping = t.getMapping;
474 		if (layerData[pri].getTag("Embed:MapData") !is null) {
475 			layerData[pri].getTag("Embed:MapData").values[0] = Value(reinterpretCast!ubyte(mapping));
476 		} else if (layerData[pri].getTag("File:MapData") !is null) {
477 			string filename = layerData[pri].getTag("File:MapData").getValue!string();
478 			MapDataHeader mdh = MapDataHeader(layerData[pri].values[4].get!int, layerData[pri].values[5].get!int);
479 			saveMapFile(mdh, mapping, File(filename, "wb"));
480 		}
481 
482 	}
483 	/**
484 	 * Adds a tile source file to a TileLayer.
485 	 */
486 	public void addTileSourceFile(int pri, string filename, string dataPakSrc = null, int palShift = 0) @trusted {
487 		Attribute[] a;
488 		if (dataPakSrc !is null) a ~= new Attribute("dataPakSrc", Value(dataPakSrc));
489 		if (palShift) a ~= new Attribute("palShift", Value(palShift));
490 		new Tag(layerData[pri],"File", "TileSource", [Value(filename)], a);
491 	}
492 	/**
493 	 * Removes a tile source.
494 	 * Returns a backup copy.
495 	 */
496 	public Tag removeTileSourceFile(int pri, string filename, string dataPakSrc = null) @trusted {
497 		try {
498 			auto namespace = layerData[pri].namespaces["File"];
499 			foreach (t ; namespace.tags) {
500 				if (t.name == "TileSource" && t.values[0] == filename && t.getAttribute!string("dataPakSrc", null) == dataPakSrc) {
501 					return t.remove;
502 				}
503 			}
504 		} catch (DOMRangeException e) {
505 			debug writeln(e);
506 		} catch (Exception e) {
507 			debug writeln(e);
508 		}
509 		return null;
510 	}
511 	/**
512 	 * Accesses tile source tags in documents for adding extra data (eg. tile names).
513 	 */
514 	public Tag getTileSourceTag(int pri, string filename, string dataPakSrc = null) @trusted {
515 		try {
516 			auto namespace = layerData[pri].namespaces["File"];
517 			foreach (t ; namespace.tags) {
518 				if (t.name == "TileSource" && t.values[0] == filename && t.getAttribute!string("dataPakSrc", null) == dataPakSrc) {
519 					return t;
520 				}
521 			}
522 		} catch (DOMRangeException e) {
523 			debug writeln(e);
524 		} catch (Exception e) {
525 			debug writeln(e);
526 		}
527 		return null;
528 	}
529 	/**
530 	 * Returns all tile sources for a given layer.
531 	 * Intended to use with a loader.
532 	 */
533 	public Tag[] getAllTileSources (int pri) @trusted {
534 		Tag[] result;
535 		try {
536 			auto namespace = layerData[pri].namespaces["File"];
537 			foreach (t ; namespace.tags) {
538 				if (t.name == "TileSource") {
539 					result ~= t;
540 				}
541 			}
542 		} catch (DOMRangeException e) {
543 			debug writeln(e);
544 		} catch (Exception e) {
545 			debug writeln(e);
546 		}
547 		return result;
548 	}
549 	/**
550 	 * Adds a palette file source to the document.
551 	 */
552 	public Tag addPaletteFile (string filename, string dataPakSrc, int offset, int palShift) @trusted {
553 		Attribute[] a;
554 		if (offset) a ~= new Attribute("offset", Value(offset));
555 		if (palShift) a ~= new Attribute("palShift", Value(palShift));
556 		if (dataPakSrc.length) a ~= new Attribute("dataPakSrc", Value(dataPakSrc));
557 		return new Tag(root,"File", "Palette", [Value(filename)], a);
558 	}
559 	/**
560 	 * Adds an embedded palette to the document.
561 	 */
562 	public Tag addEmbeddedPalette (Color[] c, string name, int offset) @trusted {
563 		import PixelPerfectEngine.system.etc : reinterpretCast;
564 		Attribute[] a;
565 		if (offset) a ~= new Attribute("offset", Value(offset));
566 		return new Tag(root, "Embed", "Palette", [Value(name), Value(reinterpretCast!ubyte(c))], a);
567 	}
568 	/**
569 	 * Returns whether the given palette file source exists.
570 	 */
571 	public bool isPaletteFileExists (string filename, string dataPakSrc = null) @trusted {
572 		foreach (t0 ; root.all.tags) {
573 			if (t0.getFullName.toString == "File:Palette") {
574 				if (t0.getValue!string() == filename && t0.getAttribute!string("dataPakSrc", null) == dataPakSrc) 
575 					return true;
576 			}
577 		}
578 		return false;
579 	}
580 	/**
581 	 * Returns the name of the map from metadata.
582 	 */
583 	public string getName () @trusted {
584 		return metadata.getTagValue!string("Name");
585 	}
586 	/**
587 	 * Returns the horizontal resolution.
588 	 */
589 	public int getHorizontalResolution () @trusted {
590 		return metadata.getTag("Resolution").values[0].get!int();
591 	}
592 	/**
593 	 * Returns the vertical resolution.
594 	 */
595 	public int getVerticalResolution () @trusted {
596 		return metadata.getTag("Resolution").values[1].get!int();
597 	}
598 }
599 /**
600  * Represents a single object within a layer, that can represent many different things.
601  * All objects have a priority identifier (int), a group identifier (int), and a name.
602  */
603 abstract class MapObject {
604 	/**
605 	 * Enumerator used for differentiating between multiple kinds of objects.
606 	 * The value serialized as a string as the name of a tag.
607 	 */
608 	public enum MapObjectType {
609 		box,			///Can be used for collision detection, event marking for scripts, and masking. Has two coordinates.
610 		/**
611 		 * Only applicable for SpriteLayer.
612 		 * Has one coordinate, a horizontal and vertical scaling indicator (int), and a source indicator (int).
613 		 */
614 		sprite,
615 	}
616 	public int 			pID;		///priority identifier
617 	public int			gID;		///group identifier (equals with layer number)
618 	public string		name;		///name of object
619 	protected MapObjectType	_type;	///type of the object
620 	public Tag[]		ancillaryTags;	///Tags that hold extra information
621 	///Returns the type of this object
622 	public @property MapObjectType type () const @nogc nothrow @safe pure {
623 		return type;
624 	}
625 	///Serializes the object into an SDL tag
626 	public abstract Tag serialize () @trusted;
627 	/**
628 	 * Checks if two objects have the same identifier.
629 	 */
630 	public bool opEquals (MapObject rhs) @nogc @safe nothrow pure const {
631 		return pID == rhs.pID && gID == rhs.gID;
632 	}
633 }
634 /**
635  * Implements a Box object. Adds a single Coordinate property to the default MapObject
636  */
637 public class BoxObject : MapObject {
638 	public Box			position;	///position of object on the layer
639 	public Color		color;		///identifying color
640 	/**
641 	 * Creates a new instance from scratch.
642 	 */
643 	public this (int pID, int gID, string name, Box position) @nogc nothrow @safe pure {
644 		this.pID = pID;
645 		this.gID = gID;
646 		this.name = name;
647 		this.position = position;
648 		_type = MapObjectType.box;
649 	}
650 	/**
651 	 * Deserializes itself from a Tag.
652 	 */
653 	public this (Tag t, int gID) @trusted {
654 		name = t.values[0].get!string();
655 		pID = t.values[1].get!int();
656 		position = getCoordinate(t);
657 		this.gID = gID;
658 		_type = MapObjectType.box;
659 		//ancillaryTags = t.tags;
660 		foreach (tag ; t.tags)
661 			ancillaryTags ~= tag;
662 	}
663 	/**
664 	 * Serializes the object into an SDL tag
665 	 */
666 	public override Tag serialize () @trusted {
667 		return new Tag("Object", "Box", [Value(name), Value(pID)], [new Attribute(null,"left",Value(position.left)),
668 				new Attribute(null,"top",Value(position.top)), new Attribute(null,"right",Value(position.right)),
669 				new Attribute(null,"bottom",Value(position.bottom))], ancillaryTags);
670 	}
671 }
672 /**
673  * Implements a sprite object. Adds a sprite source identifier, X and Y coordinates, and two 1024 based scaling indicator.
674  */
675 public class SpriteObject : MapObject {
676 	protected int 		_ssID;	///Sprite source identifier
677 	public int			x;		///X position
678 	public int			y;		///Y position
679 	public int			scaleHoriz;	///Horizontal scaling value
680 	public int			scaleVert;	///Vertical scaling value
681 	/**
682 	 * Creates a new instance from scratch.
683 	 */
684 	public this (int pID, int gID, string name, int ssID, int x, int y, int scaleHoriz, int scaleVert) {
685 		this.pID = pID;
686 		this.gID = gID;
687 		this.name = name;
688 		this._ssID = ssID;
689 		this.x = x;
690 		this.y = y;
691 		this.scaleHoriz = scaleHoriz;
692 		this.scaleVert = scaleVert;
693 		_type = MapObjectType.sprite;
694 	}
695 	/**
696 	 * Deserializes itself from a Tag.
697 	 */
698 	public this (Tag t, int gID) @trusted {
699 		name = t.values[0].get!string();
700 		pID = t.values[1].get!int();
701 		_ssID = t.values[2].get!int();
702 		x = t.expectAttribute!int("x");
703 		y = t.expectAttribute!int("y");
704 		scaleHoriz = t.expectAttribute!int("scaleHoriz");
705 		scaleVert = t.expectAttribute!int("scaleVert");
706 		foreach (tag ; t.tags)
707 			ancillaryTags ~= tag;
708 	}
709 	/**
710 	 * Serializes the object into an SDL tag
711 	 */
712 	public override Tag serialize () @trusted {
713 		return new Tag("Object", "Sprite", [Value(name), Value(pID), Value(_ssID)], [new Attribute("x",Value(x)),
714 				new Attribute("y",Value(y)), new Attribute("scaleHoriz",Value(scaleHoriz)),
715 				new Attribute("scaleVert",Value(scaleVert))], ancillaryTags);
716 	}
717 	
718 }
719 /**
720  * Gets a coordinate out from a Tag's Attributes with standard attribute namings.
721  */
722 public Coordinate getCoordinate(Tag t) @trusted {
723 	return Coordinate(t.expectAttribute!int("position:left"), t.expectAttribute!int("position:top"),
724 			t.expectAttribute!int("position:right"), t.expectAttribute!int("position:bottom"));
725 }
726 /**
727  * Simple LayerInfo struct, mostly for internal communications.
728  */
729 public struct LayerInfo {
730 	LayerType	type;	///Type of layer
731 	int			pri;	///Priority of layer
732 	string		name;	///Name of layer
733 	int opCmp (LayerInfo rhs) const pure @safe @nogc {
734 		if (pri > rhs.pri)
735 			return 1;
736 		else if (pri < rhs.pri)
737 			return -1;
738 		else
739 			return 0;
740 	}
741 	/**
742 	 * Parses a string as a layer type
743 	 */
744 	static LayerType parseLayerTypeString (string s) pure @safe {
745 		import std.uni : toLower;
746 		s = toLower(s);
747 		switch (s) {
748 			case "tile":
749 				return LayerType.Tile;
750 			case "sprite":
751 				return LayerType.Sprite;
752 			case "transformabletile":
753 				return LayerType.TransformableTile;
754 			default:
755 				return LayerType.init;
756 		}
757 	}
758 }
759 /**
760  * Simple TileInfo struct, mostly for internal communication and loading.
761  */
762 public struct TileInfo {
763 	wchar		id;		///ID of the tile in wchar format
764 	ushort		palShift;	///palShift offset of the tile
765 	int			num;	///Number of tile in the file
766 	string		name;	///Name of the tile
767 	int opCmp (TileInfo rhs) const pure @safe @nogc {
768 		if (id > rhs.id)
769 			return 1;
770 		else if (id < rhs.id)
771 			return -1;
772 		else
773 			return 0;
774 	}
775 	public string toString() const pure {
776 		import std.conv : to;
777 		return to!string(id) ~ ";" ~ to!string(num) ~ ";" ~ name;
778 	}
779 }