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