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 std.exception : enforce;
13 import std.typecons : BitFlags;
14 import pixelperfectengine.system.etc : parseHex;
15 import std.format : format;
16 import std.conv : to;
17 import collections.treemap;
18 public import pixelperfectengine.map.mapdata;
19 import pixelperfectengine.system.file;
20 import pixelperfectengine.collision.objectcollision;
21 import pixelperfectengine.system.exc : PPEException;
22 
23 /**
24  * Serializes/deserializes XMF map data in SDLang format.
25  * Each layer can contain objects (eg. for marking events, clipping, or sprites if applicable), tilemapping (not for SpriteLayers), embedded
26  * data such as tilemapping or scripts, and so on.
27  *
28  * Also does some basic resource managing.
29  *
30  * Note on layer tags:
31  * As of this version, additional tags within layers must have individual names. Subtags within a parent also need to have individual names.
32  * Namespaces are reserved for internal use (eg. file sources, objects).
33  */
34 public class MapFormat {
35 	public TreeMap!(int,Tag) 	layerData;	///Layerdata stored as SDLang tags.
36 	public TreeMap!(int,Layer)	layeroutput;///Used to fast map and object data pullback in editors
37 	protected Tag 				metadata;	///Stores metadata.
38 	protected Tag				root;		///Root tag for common information.
39 	public TileInfo[][int]		tileDataFromExt;///Stores basic TileData that are loaded through extensions
40 	/**
41 	 * Associative array used for rendering mode lookups in one way.
42 	 */
43 	public static immutable RenderingMode[string] renderingModeLookup;
44 	shared static this() {
45 		renderingModeLookup["null"] = RenderingMode.init;
46 		renderingModeLookup["Copy"] = RenderingMode.Copy;
47 		renderingModeLookup["Blitter"] = RenderingMode.Blitter;
48 		renderingModeLookup["AlphaBlend"] = RenderingMode.AlphaBlend;
49 		renderingModeLookup["Add"] = RenderingMode.Add;
50 		renderingModeLookup["AddBl"] = RenderingMode.AddBl;
51 		renderingModeLookup["Subtract"] = RenderingMode.Subtract;
52 		renderingModeLookup["SubtractBl"] = RenderingMode.SubtractBl;
53 		renderingModeLookup["Diff"] = RenderingMode.Diff;
54 		renderingModeLookup["DiffBl"] = RenderingMode.DiffBl;
55 		renderingModeLookup["Multiply"] = RenderingMode.Multiply;
56 		renderingModeLookup["MultiplyBl"] = RenderingMode.MultiplyBl;
57 		renderingModeLookup["Screen"] = RenderingMode.Screen;
58 		renderingModeLookup["ScreenBl"] = RenderingMode.ScreenBl;
59 		renderingModeLookup["AND"] = RenderingMode.AND;
60 		renderingModeLookup["OR"] = RenderingMode.OR;
61 		renderingModeLookup["XOR"] = RenderingMode.XOR;
62 	}
63 	/**
64 	 * Creates new instance from scratch.
65 	 */
66 	public this(string name, int resX, int resY) @trusted {
67 		root = new Tag();
68 		metadata = new Tag(root, null, "Metadata");
69 		new Tag(metadata, null, "Version", [Value(1), Value(0)]);
70 		new Tag(metadata, null, "Name", [Value(name)]);
71 		new Tag(metadata, null, "Resolution", [Value(resX), Value(resY)]);
72 	}
73 	/**
74 	 * Serializes itself from file.
75 	 */
76 	public this(F)(F file) @trusted {
77 		//File f = File(path, "rb");
78 		char[] source;
79 		source.length = cast(size_t)file.size;
80 		source = file.rawRead(source);
81 		root = parseSource(cast(string)source);
82 		//Just quickly go through the tags and sort them out
83 		foreach (Tag t0 ; root.all.tags) {
84 			switch (t0.namespace) {
85 				case "Layer":
86 					const int priority = t0.values[1].get!int;
87 					layerData[priority] = t0;
88 					RenderingMode lrd = renderingModeLookup.get(t0.getTagValue!string("RenderingMode"), RenderingMode.Copy);
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 						case "Sprite":
95 							layeroutput[priority] = new SpriteLayer(lrd);
96 							break;
97 						default:
98 							throw new Exception("Unsupported layer format");
99 					}
100 					break;
101 				
102 				default:
103 					if(t0.name == "Metadata"){
104 						metadata = t0;
105 					}
106 					break;
107 			}
108 		}
109 		//assert(layerData.length == layeroutput.length);
110 	}
111 	/**
112 	 * Loads tiles from disk to all layers. Also loads the palette.
113 	 * TODO: Add dpk support
114 	 * Params:
115 	 *   paletteTarget: The destination, where the palettes should be loaded into.
116 	 */
117 	public void loadTiles(PaletteContainer paletteTarget) @trusted {
118 		import pixelperfectengine.system.file;
119 		foreach (key, value ; layerData) {
120 			if (value.name != "Tile") continue;
121 			Tag[] tileSource = getAllTileSources(key);
122 			foreach (t0; tileSource) {
123 				string path = t0.getValue!string();
124 				Image i = loadImage(File(path, "rb"));
125 				void helperFunc(T)(T[] bitmaps, Tag source) {
126 					TileLayer tl = cast(TileLayer)layeroutput[key];
127 					Tag tileInfo = source.getTag("Embed:TileInfo", null);
128 					if(tileInfo !is null)
129 						foreach (t1 ; tileInfo.tags) {
130 							tl.addTile(bitmaps[t1.values[0].get!int()], cast(wchar)t1.values[1].get!int());
131 						}
132 				}
133 				switch(i.getBitdepth){
134 					case 2:
135 						Bitmap2Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap2Bit)(i, value.values[2].get!int(), 
136 								value.values[3].get!int());
137 						helperFunc(bitmaps, t0);
138 						break;
139 					case 4:
140 						Bitmap4Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap4Bit)(i, value.values[2].get!int(), 
141 								value.values[3].get!int());
142 						helperFunc(bitmaps, t0);
143 						break;
144 					case 8:
145 						Bitmap8Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap8Bit)(i, value.values[2].get!int(), 
146 								value.values[3].get!int());
147 						helperFunc(bitmaps, t0);
148 						break;
149 					case 16:
150 						Bitmap16Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap16Bit)(i, value.values[2].get!int(), 
151 								value.values[3].get!int());
152 						helperFunc(bitmaps, t0);
153 						break;
154 					case 32:
155 						Bitmap32Bit[] bitmaps = loadBitmapSheetFromImage!(Bitmap32Bit)(i, value.values[2].get!int(), 
156 								value.values[3].get!int());
157 						helperFunc(bitmaps, t0);
158 						break;
159 					default:
160 						throw new Exception("Unsupported image bitdepth");
161 						
162 				}
163 				if (paletteTarget !is null && isPaletteFileExists(path)) {
164 					paletteTarget.loadPaletteChunk(loadPaletteFromImage(i), cast(ushort)t0.getAttribute!int("offset", 0));
165 				}
166 				//debug writeln(paletteTarget.palette);
167 			}
168 		}
169 	}
170 	/** 
171 	 * Loads the sprites associated with the layer ID.
172 	 * Params:
173 	 *   layerID = the ID of the layer.
174 	 *   paletteTarget = target for any loaded palettes, ideally the raster.
175 	 * Returns: An associative array with sprite identifiers as the keys, and the sprite bitmaps as its elements.
176 	 * Note: It's mainly intended for editor placeholders, but also could work with production-made games as long
177 	 * as the limitations don't intercept anything major.
178 	 */
179 	public ABitmap[int] loadSprites(int layerID, PaletteContainer paletteTarget) @trusted {
180 		import pixelperfectengine.system.file;
181 		ABitmap[int] result;
182 		Image[string] imageBuffer;	//Resource manager to minimize reloading image files
183 		Tag tBase = layerData[layerID];
184 		if (tBase.name != "Sprite") return result;
185 		foreach (Tag t0; tBase.all.tags) {
186 			switch (t0.getFullName.toString) {
187 				case "File:SpriteSource":
188 					string filename = t0.expectValue!string();
189 					if (imageBuffer.get(filename, null) is null) {
190 						imageBuffer[filename] = loadImage(File(filename));
191 					}
192 					const int id = t0.expectValue!int();
193 					/+if ("horizOffset" in t0.attributes && "vertOffset" in t0.attributes && "width" in t0.attributes && 
194 							"height" in t0.attributes) {+/
195 					if (t0.getAttribute!int("horizOffset", -1) != -1 && t0.getAttribute!int("vertOffset") != -1 && 
196 							t0.getAttribute!int("width") && t0.getAttribute!int("height")) {
197 						const int hOffset = t0.getAttribute!int("horizOffset"), vOffset = t0.getAttribute!int("vertOffset"),
198 							w = t0.getAttribute!int("width"), h = t0.getAttribute!int("height");
199 						switch (imageBuffer[filename].getBitdepth) {
200 							case 2:
201 								result[id] = loadBitmapSliceFromImage!Bitmap2Bit(imageBuffer[filename], hOffset, vOffset, w, h);
202 								break;
203 							case 4:
204 								result[id] = loadBitmapSliceFromImage!Bitmap4Bit(imageBuffer[filename], hOffset, vOffset, w, h);
205 								break;
206 							case 8:
207 								result[id] = loadBitmapSliceFromImage!Bitmap8Bit(imageBuffer[filename], hOffset, vOffset, w, h);
208 								break;
209 							case 16:
210 								result[id] = loadBitmapSliceFromImage!Bitmap16Bit(imageBuffer[filename], hOffset, vOffset, w, h);
211 								break;
212 							default:
213 								result[id] = loadBitmapSliceFromImage!Bitmap32Bit(imageBuffer[filename], hOffset, vOffset, w, h);
214 								break;
215 						}
216 					} else {
217 						switch (imageBuffer[filename].getBitdepth) {
218 							case 2:
219 								result[id] = loadBitmapFromImage!Bitmap2Bit(imageBuffer[filename]);
220 								break;
221 							case 4:
222 								result[id] = loadBitmapFromImage!Bitmap4Bit(imageBuffer[filename]);
223 								break;
224 							case 8:
225 								result[id] = loadBitmapFromImage!Bitmap8Bit(imageBuffer[filename]);
226 								break;
227 							case 16:
228 								result[id] = loadBitmapFromImage!Bitmap16Bit(imageBuffer[filename]);
229 								break;
230 							default:
231 								result[id] = loadBitmapFromImage!Bitmap32Bit(imageBuffer[filename]);
232 								break;
233 						}
234 					}
235 					break;
236 				case "File:SpriteSheet":
237 					string filename = t0.expectValue!string();
238 					if (imageBuffer.get(filename, null) is null) {
239 						imageBuffer[filename] = loadImage(File(filename));
240 					}
241 					foreach (Tag t1 ; t0.tags) {
242 						if (t1.name == "SheetData") {
243 							foreach (Tag t2 ; t1.tags) {
244 								const int id = t2.values[0].get!int(), hOffset = t2.values[1].get!int(), vOffset = t2.values[2].get!int(),
245 										w = t2.values[3].get!int(), h = t2.values[4].get!int();
246 								switch (imageBuffer[filename].getBitdepth) {
247 									case 2:
248 										result[id] = loadBitmapSliceFromImage!Bitmap2Bit(imageBuffer[filename], hOffset, vOffset, w, h);
249 										break;
250 									case 4:
251 										result[id] = loadBitmapSliceFromImage!Bitmap4Bit(imageBuffer[filename], hOffset, vOffset, w, h);
252 										break;
253 									case 8:
254 										result[id] = loadBitmapSliceFromImage!Bitmap8Bit(imageBuffer[filename], hOffset, vOffset, w, h);
255 										break;
256 									case 16:
257 										result[id] = loadBitmapSliceFromImage!Bitmap16Bit(imageBuffer[filename], hOffset, vOffset, w, h);
258 										break;
259 									default:
260 										result[id] = loadBitmapSliceFromImage!Bitmap32Bit(imageBuffer[filename], hOffset, vOffset, w, h);
261 										break;
262 								}
263 							}
264 						}
265 					}
266 					break;
267 				case "File:Palette":
268 					string filename = t0.expectValue!string();
269 					if (imageBuffer.get(filename, null) is null) {
270 						imageBuffer[filename] = loadImage(File(filename));
271 					}
272 					Color[] pal = loadPaletteFromImage(imageBuffer[filename]);
273 					const size_t palLength = t0.getAttribute!int("palShift") ? 1<<(t0.getAttribute!int("palShift")) : pal.length;
274 					const int palOffset = t0.getAttribute!int("offset");
275 					paletteTarget.loadPaletteChunk(pal[0..palLength], cast(ushort)palOffset);
276 					break;
277 				default:
278 					break;
279 			}
280 		}
281 		
282 		return result;
283 	}
284 	/**
285 	 * Returns all objects belonging to a `layerID` in an array.
286 	 */
287 	public MapObject[] getLayerObjects(int layerID) @trusted {
288 		Tag t0 = layerData[layerID];
289 		if (t0 is null) return null;
290 		MapObject[] result;
291 		try {
292 			foreach (Tag t1; t0.namespaces["Object"].tags) {
293 				MapObject obj = parseObject(t1, layerID);
294 				if (obj !is null)
295 					result ~= obj;
296 			}
297 		} catch (Exception e) {
298 			debug writeln(e);
299 			return null;
300 		}
301 		return result;
302 	}
303 	/**
304 	 * Loads all sprites and objects to thir respective layers and the supplied ObjectCollisionDetector.
305 	 * Params:
306 	 *   paletteTarget: A raster to load the palettes into. Must be not null.
307 	 *   ocd: The supplied ObjectCollisionDetector. Can be null.
308 	 * Note: This is a default parser and loader, one might want to write a more complicated one for their application.
309 	 */
310 	public void loadAllSpritesAndObjects(PaletteContainer paletteTarget, ObjectCollisionDetector ocd) @trusted {
311 		import pixelperfectengine.collision.common;
312 		foreach (key, value; layeroutput) {
313 			ABitmap[int] spr = loadSprites(key, paletteTarget);
314 			MapObject[] objList = getLayerObjects(key);
315 			if (spr.length) {
316 				SpriteLayer sl = cast(SpriteLayer)value;
317 				foreach (MapObject key0; objList) {
318 					if (key0.type == MapObject.MapObjectType.sprite) {
319 						SpriteObject so = cast(SpriteObject)key0;
320 						sl.addSprite(spr[so.ssID], so.pID, so.x, so.y, so.palSel, so.palShift, so.masterAlpha, so.scaleHoriz, 
321 								so.scaleVert, so.rendMode);
322 						if (ocd !is null && so.flags.toCollision) {
323 							ocd.objects[so.pID] = CollisionShape(sl.getSpriteCoordinate(so.pID), null);
324 						}
325 					} else if (ocd !is null && key0.type == MapObject.MapObjectType.box && key0.flags.toCollision) {
326 						BoxObject bo = cast(BoxObject)key0;
327 						ocd.objects[bo.pID] = CollisionShape(bo.position, null);
328 					}
329 				}
330 			} else if (ocd !is null) {
331 				foreach (MapObject key0; objList) {
332 					if (ocd !is null && key0.type == MapObject.MapObjectType.box && key0.flags.toCollision) {
333 						BoxObject bo = cast(BoxObject)key0;
334 						ocd.objects[bo.pID] = CollisionShape(bo.position, null);
335 					}
336 				}
337 			}
338 		}
339 	}
340 	/**
341 	 * Loads mapping data from disk to all layers.
342 	 */
343 	public void loadMappingData () @trusted {
344 		import pixelperfectengine.system.etc : reinterpretCast;
345 		foreach (key, value ; layerData) {
346 			Tag t0 = value.getTag("Embed:MapData");
347 			if (t0 !is null) {
348 				TileLayer tl = cast(TileLayer)layeroutput[key];
349 				//writeln(t0.getValue!(ubyte[])());
350 				tl.loadMapping(value.values[4].get!int(), value.values[5].get!int(), 
351 						reinterpretCast!MappingElement(t0.expectValue!(ubyte[])()));
352 				
353 				continue;
354 			}
355 			t0 = value.getTag("File:MapData");
356 			if (t0 !is null) {
357 				TileLayer tl = cast(TileLayer)layeroutput[key];
358 				MapDataHeader mdf;
359 				File mapfile = File(t0.expectValue!string());
360 				tl.loadMapping(value.values[4].get!int(), value.values[5].get!int(), loadMapFile(mapfile, mdf));
361 			}
362 		}
363 	}
364 	/**
365 	 * Saves the document to disc.
366 	 * Params:
367 	 *   path = the path where the document is should be saved to.
368 	 */
369 	public void save(string path) @trusted {
370 		debug writeln(root.tags);
371 		foreach(int i, Tag t; layerData){
372 			if(t.name == "Tile")
373 				pullMapDataFromLayer (i);
374 		}
375 		string output = root.toSDLDocument();
376 		File f = File(path, "wb+");
377 		f.write(output);
378 	}
379 	/**
380 	 * Returns the given metadata.
381 	 * Params:
382 	 *   name = the name of the parameter.
383 	 * Template params:
384 	 *   T = The type of the parameter.
385 	 */
386 	public T getMetadata(T)(string name)
387 			if (T.stringof == int.stringof || T.stringof == string.stringof) {
388 		return metadata.getTagValue!T(name);
389 	}
390 	/**
391 	 * Returns the requested layer.
392 	 */
393 	public Layer opIndex(int index) @safe pure {
394 		return layeroutput[index];
395 	}
396 	/**
397 	 * Returns all layer's basic information.
398 	 */
399 	public LayerInfo[] getLayerInfo() @trusted {
400 		import std.algorithm.sorting : sort;
401 		LayerInfo[] result;
402 		foreach (Tag t ; layerData) {
403 			result ~= LayerInfo(LayerInfo.parseLayerTypeString(t.name), t.values[1].get!int(), t.values[0].get!string());
404 		}
405 		result.sort;
406 		return result;
407 	}
408 	/**
409 	 * Returns a specified layer's basic information.
410 	 */
411 	public LayerInfo getLayerInfo(int pri) @trusted {
412 		Tag t = layerData[pri];
413 		if (t !is null) return LayerInfo(LayerInfo.parseLayerTypeString(t.name), t.values[1].get!int(), 
414 				t.values[0].get!string());
415 		else return LayerInfo.init;
416 	}
417 	/**
418 	 * Alters a tile layer data.
419 	 * Params:
420 	 *   layerNum = The numer of the layer
421 	 *   dataNum = The index of the data
422 	 *   value = The new value.
423 	 * Template params:
424 	 *   T = The type of the parameter.
425 	 */
426 	public void alterTileLayerInfo(T)(int layerNum, int dataNum, T value) @trusted {
427 		layerData[layerNum].values[dataNum] = Value(value);
428 	}
429 	/**
430 	 * Returns a selected tile layer's all tile's basic information.
431 	 * Mainly used to display information in editors.
432 	 * Params:
433 	 *   pri = Layer priority ID
434 	 * Returns: an array with the tile information.
435 	 */
436 	public TileInfo[] getTileInfo(int pri) @trusted {
437 		import std.algorithm.sorting : sort;
438 		TileInfo[] result;
439 		try {
440 			foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
441 				//writeln(t0.toSDLString);
442 				if (t0.name == "TileSource") {
443 					Tag t1 = t0.getTag("Embed:TileInfo");
444 					ushort palShift = cast(ushort)t0.getAttribute!int("palShift", 0);
445 					if (t1 !is null) {
446 						foreach (Tag t2 ; t1.tags) {
447 							result ~= TileInfo(cast(wchar)t2.values[0].get!int(), palShift, t2.values[1].get!int(), t2.values[2].get!string());
448 						}
449 					}
450 				}
451 			}
452 		} catch (DOMRangeException e) {	///Just means there's no File namespace within the tag. Should be OK.
453 			debug writeln(e);
454 		} catch (Exception e) {
455 			debug writeln(e);
456 		}
457 		//writeln(result.length);
458 		result ~= tileDataFromExt.get(pri, []);
459 		result.sort;
460 		return result;
461 	}
462 	/**
463 	 * Adds TileInfo to a TileLayer from an array.
464 	 * Joins together multiple chunks with the same source identifier. (should be a path)
465 	 * Params:
466 	 *   pri = Layer priority ID.
467 	 *   list = An array of TileInfo, which need to be added to the document.
468 	 *   source = The file origin of the tiles (file or DataPak path).
469 	 *   dpkSource = Path to the DataPak file if it's used, null otherwise.
470 	 */
471 	public void addTileInfo(int pri, TileInfo[] list, string source, string dpkSource = null) @trusted {
472 		if(list.length == 0) throw new Exception("Empty list!");
473 		Tag t;
474 		try{
475 			foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
476 				if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
477 					t = t0.getTag("Embed:TileInfo", null);
478 					if (t is null) { 
479 						t = new Tag(t0, "Embed", "TileInfo");
480 					}
481 					break;
482 				}
483 			}
484 			foreach (item ; list) {
485 				new Tag(t, null, null, [Value(cast(int)item.id), Value(item.num), Value(item.name)]);
486 			}
487 		} catch (Exception e) {
488 			debug writeln (e);
489 		}
490 		//writeln(t.tags.length);
491 		assert(t.tags.length == list.length);
492 	}
493 	/**
494 	 * Adds TileInfo to a TileLayer from a preexiting tag.
495 	 * Joins together multiple chunks with the same source identifier. (should be a path)
496 	 * Params:
497 	 *   pri = Layer priority ID.
498 	 *   t = The SDL tag to be added to the Layer.
499 	 *   source = The file origin of the tiles (file or DataPak path).
500 	 *   dpkSource = Path to the DataPak file if it's used, null otherwise.
501 	 */
502 	public void addTileInfo(int pri, Tag t, string source, string dpkSource = null) @trusted {
503 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
504 			if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
505 				t0.add(t);
506 				return;
507 			}
508 		}
509 
510 	}
511 	/**
512 	 * Adds a single TileInfo to a preexisting chunk on the layer.
513 	 */
514 	public void addTile(int pri, TileInfo item, string source, string dpkSource = null) {
515 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
516 			if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
517 				Tag t1 = t0.getTag("Embed:TileInfo");
518 				if (t1 !is null) {
519 					new Tag (t1, null, null, [Value(cast(int)item.id), Value(item.num), Value(item.name)]);
520 				}
521 			}
522 		}
523 	}
524 	///Ditto, but from preexiting Tag.
525 	public void addTile(int pri, Tag t, string source, string dpkSource = null) @trusted {
526 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
527 			if (t0.name == "TileSource" && t0.values[0] == source && t0.getAttribute!string("dataPakSrc", null) == dpkSource) {
528 				Tag t1 = t0.getTag("Embed:TileInfo");
529 				t1.add(t);
530 			}
531 		}
532 	}
533 	/**
534 	 * Renames a single tile.
535 	 * Params:
536 	 *   pri = Layer priority ID.
537 	 *   id = Tile character ID.
538 	 *   newName = The new name of the tile.
539 	 * Returns: the previous name if the action was successful, or null if there was some issue.
540 	 */
541 	public string renameTile(int pri, int id, string newName) {
542 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
543 			if (t0.name == "TileSource") {
544 				Tag t1 = t0.getTag("Embed:TileInfo");
545 				if (t1 !is null) {
546 					foreach (Tag t2; t1.tags) {
547 						if (t2.values[0].get!int() == id) {
548 							string oldName = t2.values[2].get!string();
549 							t2.values[2] = Value(newName);
550 							return oldName;
551 						}
552 					}
553 				}
554 				
555 			}
556 		}
557 		return null;
558 	}
559 	/**
560 	 * Removes a single tile from a TileInfo chunk.
561 	 * Params:
562 	 *   pri = Layer priority ID.
563 	 *   id = Tile character ID.
564 	 *   source = The file origin of the tiles (file or DataPak path).
565 	 *   dpkSource = Path to the DataPak file if it's used, null otherwise.
566 	 * Returns: a tag as a backup if tile is found and removed, or null if it's not found.
567 	 */
568 	public Tag removeTile(int pri, int id, string source, string dpkSource = null) @trusted {
569 		foreach (Tag t0 ; layerData[pri].namespaces["File"].tags) {
570 			if (t0.name == "TileSource") {
571 				Tag t1 = t0.getTag("Embed:TileInfo");
572 				if (t1 !is null) {
573 					source = t0.values[0].get!string();
574 					dpkSource = t0.getAttribute!string("dpkSource", null);
575 					foreach (Tag t2; t1.tags) {
576 						if (t2.values[0].get!int() == id) {
577 							return t2.remove();
578 						}
579 					}
580 				}
581 				
582 			}
583 		}
584 		return null;
585 	}
586 	/**
587 	 * Removes a given layer of any kind.
588 	 * Params:
589 	 *   pri = Layer priority ID.
590 	 * Returns: the Tag of the layer as a backup.
591 	 */
592 	public Tag removeLayer(int pri) @trusted {
593 		Tag backup = layerData[pri];
594 		layeroutput.remove(pri);
595 		layerData.remove(pri);
596 		return backup.remove;
597 	}
598 	/**
599 	 * Adds a layer from preexsting tag.
600 	 * Params:
601 	 *   pri = Layer priority ID.
602 	 *   t = The tag containing layer information.
603 	 *   l = The layer.
604 	 */
605 	public void addNewLayer(int pri, Tag t, Layer l) @trusted {
606 		layeroutput[pri] = l;
607 		layerData[pri] = t;
608 		root.add(t);
609 	}
610 	/**
611 	 * Adds a newly created TileLayer to the document.
612 	 * Params:
613 	 *   pri = Layer priority ID.
614 	 *   tX = Tile width.
615 	 *   tY = Tile height.
616 	 *   mX = Map width.
617 	 *   mY = Map height.
618 	 *   name = Name of the layer.
619 	 *   l = The layer itself.
620 	 */
621 	public void addNewTileLayer(int pri, int tX, int tY, int mX, int mY, string name, TileLayer l) @trusted {
622 		layeroutput[pri] = l;
623 		l.setRasterizer(getHorizontalResolution, getVerticalResolution);
624 		layerData[pri] = new Tag(root, "Layer", "Tile", [Value(name), Value(pri), Value(tX), Value(tY), Value(mX), Value(mY)]);
625 		new Tag(layerData[pri], null, "RenderingMode", [Value("Copy")]);
626 	}
627 	/**
628 	 * Adds a new tag to a layer.
629 	 * Params:
630 	 *   pri = Layer priority ID.
631 	 *   name = Name of the tag.
632 	 *   args = The values of the tag.
633 	 */
634 	public void addTagToLayer(T...)(int pri, string name, T args) @trusted {
635 		Value[] vals;
636 		foreach (arg; args) {
637 			vals ~= Value(arg);
638 		}
639 		new Tag(layerData[pri], null, name, vals);
640 	}
641 	/**
642 	 * Adds a new property tag to a layer's tag.
643 	 * Note: Property tag must be first created by `addTagToLayer`.
644 	 * Params:
645 	 *   pri = Layer priority ID.
646 	 *   name = Name of the tag.
647 	 *   parent = Name of the tag this one will be contained by.
648 	 *   args = The values to be added to the tag.
649 	 */
650 	public void addPropertyTagToLayer(T...)(int pri, string name, string parent, T args) @trusted {
651 		Value[] vals;
652 		foreach (arg; args) {
653 			vals ~= Value(arg);
654 		}
655 		new Tag(layerData[pri].expectTag(parent), null, name, vals);
656 	}
657 	/**
658 	 * Gets the values of a layer's root tag.
659 	 * Params:
660 	 *   pri = Layer priority ID.
661 	 * Returns: an array containing all the values belonging to the root tag.
662 	 */
663 	public Value[] getLayerRootTagValues(int pri) @trusted {
664 		return layerData[pri].values;
665 	}
666 	/**
667 	 * Gets the values of a layer's tag.
668 	 * Params:
669 	 *   pri = Layer priority ID.
670 	 *   name = The name of the tag.
671 	 * Returns: an array containing all the values belonging to the given tag.
672 	 */
673 	public Value[] getLayerTagValues(int pri, string name) @trusted {
674 		return layerData[pri].expectTag(name).values;
675 	}
676 	/**
677 	 * Gets the values of a layer's property tag.
678 	 * Params:
679 	 *   pri = Layer priority ID.
680 	 *   name = The name of the property tag.
681 	 *   parent = The name of the tag the property tag is contained within.
682 	 * Returns: an array containing all the values belonging to the given property tag.
683 	 */
684 	public Value[] getLayerPropertyTagValues(int pri, string name, string parent) @trusted {
685 		return layerData[pri].expectTag(parent).expectTag(name).values;
686 	}
687 	/**
688 	 * Edits the values of a layer's tag. 
689 	 * Params:
690 	 *   pri = Layer priority ID.
691 	 *   name = Name of the tag.
692 	 *   args = The values to be added to the tag.
693 	 * Returns: the original values in an array.
694 	 */
695 	public Value[] editLayerTagValues(T...)(int pri, string name, T args) @trusted {
696 		Value[] backup = layerData[pri].expectTag(name).values;
697 		Value[] vals;
698 		foreach (arg; args) {
699 			vals ~= Value(arg);
700 		}
701 		//new Tag(layerData[pri], null, name, vals);
702 		layerData[pri].expectTag(name).values = vals;
703 		return backup;
704 	}
705 	/**
706 	 * Edits the values of a layer's property tag.
707 	 * Params:
708 	 *   pri = Layer priority ID.
709 	 *   name = Name of the tag.
710 	 *   parent = Name of the tag this one will be contained by.
711 	 *   args = The values to be added to the tag.
712 	 * Returns: the original values in an array.
713 	 */
714 	public Value[] editLayerPropertyTagValues(T...)(int pri, string name, string parent, T args) @trusted {
715 		Value[] backup = layerData[pri].expectTag(parent).expectTag(name).values;
716 		Value[] vals;
717 		foreach (arg; args) {
718 			vals ~= Value(arg);
719 		}
720 		layerData[pri].expectTag(parent).expectTag(name).values = vals;
721 		return backup;
722 	}
723 	/**
724 	 * Removes a layer's tag.
725 	 * Params:
726 	 *   pri = Layer priority ID.
727 	 *   name = Name of the tag.
728 	 * Returns: a backup for undoing.
729 	 */
730 	public Tag removeLayerTagValues(int pri, string name) @trusted {
731 		return layerData[pri].expectTag(name).remove;
732 	}
733 	/**
734 	 * Adds an embedded MapData to a TileLayer.
735 	 * Params:
736 	 *   pri = Layer priority ID.
737 	 *   base64Code = The data to be embedded,
738 	 */
739 	public void addEmbeddedMapData(int pri, ubyte[] base64Code) @trusted {
740 		layerData[pri].add(new Tag("Embed", "MapData", [Value(base64Code)]));
741 	}
742 	/**
743 	 * Adds an embedded MapData to a TileLayer.
744 	 * Params:
745 	 *   pri = Layer priority ID.
746 	 *   me = The data to be embedded,
747 	 */
748 	public void addEmbeddedMapData(int pri, MappingElement[] me) @safe {
749 		import pixelperfectengine.system.etc : reinterpretCast;
750 		addEmbeddedMapData(pri, reinterpretCast!ubyte(me));
751 	}
752 	/**
753 	 * Adds a TileData file to a TileLayer.
754 	 * Filename should contain relative path.
755 	 * Params:
756 	 *   pri = Layer priority ID.
757 	 *   filename = Path to the map data file. (Either on the disk or within the DataPak file)
758 	 *   dataPakSrc = Path to the DataPak source file if used, null otherwise.
759 	 */
760 	public void addMapDataFile(int pri, string filename, string dataPakSrc = null) @trusted {
761 		Attribute[] a;
762 		if (dataPakSrc !is null) a ~= new Attribute("dataPakSrc", Value(dataPakSrc));
763 		layerData[pri].add(new Tag("File", "MapData", [Value(filename)], a));
764 	}
765 	/**
766 	 * Removes embedded TileData from a TileLayer.
767 	 * Params:
768 	 *   pri = Layer priority ID.
769 	 * Returns: a backup for undoing.
770 	 */
771 	public Tag removeEmbeddedMapData(int pri) @trusted {
772 		return layerData[pri].expectTag("Embed:MapData").remove;
773 	}
774 	/**
775 	 * Removes a TileData file from a TileLayer.
776 	 * Params:
777 	 *   pri = Layer priority ID.
778 	 * Returns: a backup for undoing.
779 	 */
780 	public Tag removeMapDataFile(int pri) @trusted {
781 		return layerData[pri].expectTag("File:MapData").remove;
782 	}
783 	/**
784 	 * Pulls TileLayer data from the layer, and stores it in the preconfigured location.
785 	 * Params:
786 	 *   pri = Layer priority ID.
787 	 * Only works with uncompressed data due to the need of recompression.
788 	 */
789 	public void pullMapDataFromLayer(int pri) @trusted {
790 		import pixelperfectengine.system.etc : reinterpretCast;
791 		ITileLayer t = cast(ITileLayer)layeroutput[pri];
792 		MappingElement[] mapping = t.getMapping;
793 		if (layerData[pri].getTag("Embed:MapData") !is null) {
794 			layerData[pri].getTag("Embed:MapData").values[0] = Value(reinterpretCast!ubyte(mapping));
795 		} else if (layerData[pri].getTag("File:MapData") !is null) {
796 			string filename = layerData[pri].getTag("File:MapData").getValue!string();
797 			MapDataHeader mdh = MapDataHeader(layerData[pri].values[4].get!int, layerData[pri].values[5].get!int);
798 			saveMapFile(mdh, mapping, File(filename, "wb"));
799 		}
800 	}
801 	/**
802 	 * Adds a tile source file (file that contains the tiles) to a TileLayer.
803 	 * Params:
804 	 *   pri = Layer priority ID.
805 	 *   filename = Path to the file.
806 	 *   dataPakSrc = Path to the DataPak file if used, null otherwise.
807 	 *   palShift = Amount of palette shiting, 0 for default.
808 	 */
809 	public void addTileSourceFile(int pri, string filename, string dataPakSrc = null, int palShift = 0) @trusted {
810 		Attribute[] a;
811 		if (dataPakSrc !is null) a ~= new Attribute("dataPakSrc", Value(dataPakSrc));
812 		if (palShift) a ~= new Attribute("palShift", Value(palShift));
813 		new Tag(layerData[pri],"File", "TileSource", [Value(filename)], a);
814 	}
815 	/**
816 	 * Removes a tile source.
817 	 * Params:
818 	 *   pri = Layer priority ID.
819 	 *   filename = Path to the file.
820 	 *   dataPakSrc = Path to the DataPak file if used, null otherwise.
821 	 * Returns: a backup copy of the tag.
822 	 */
823 	public Tag removeTileSourceFile(int pri, string filename, string dataPakSrc = null) @trusted {
824 		try {
825 			auto namespace = layerData[pri].namespaces["File"];
826 			foreach (t ; namespace.tags) {
827 				if (t.name == "TileSource" && t.values[0] == filename && t.getAttribute!string("dataPakSrc", null) == dataPakSrc) {
828 					return t.remove;
829 				}
830 			}
831 		} catch (DOMRangeException e) {
832 			debug writeln(e);
833 		} catch (Exception e) {
834 			debug writeln(e);
835 		}
836 		return null;
837 	}
838 	/**
839 	 * Accesses tile source tags in documents for adding extra data (eg. tile names).
840 	 * Params:
841 	 *   pri = Layer priority ID.
842 	 *   filename = Path to the file.
843 	 *   dataPakSrc = Path to the DataPak file if used, null otherwise.
844 	 */
845 	public Tag getTileSourceTag(int pri, string filename, string dataPakSrc = null) @trusted {
846 		try {
847 			auto namespace = layerData[pri].namespaces["File"];
848 			foreach (t ; namespace.tags) {
849 				if (t.name == "TileSource" && t.values[0] == filename && t.getAttribute!string("dataPakSrc", null) == dataPakSrc) {
850 					return t;
851 				}
852 			}
853 		} catch (DOMRangeException e) {
854 			debug writeln(e);
855 		} catch (Exception e) {
856 			debug writeln(e);
857 		}
858 		return null;
859 	}
860 	/**
861 	 * Returns all tile sources for a given layer.
862 	 * Intended to use with a loader.
863 	 * Params:
864 	 *   pri = Layer priority ID.
865 	 */
866 	public Tag[] getAllTileSources (int pri) @trusted {
867 		Tag[] result;
868 		try {
869 			void loadFromLayer(int _pri) {
870 				//auto namespace = layerData[pri].namespaces["File"];
871 				foreach (Tag t ; layerData[_pri].namespaces["File"].tags) {
872 					if (t.name == "TileSource") {
873 						result ~= t;
874 					}
875 				}
876 			}
877 			loadFromLayer(pri);
878 			foreach (Tag t ; layerData[pri].namespaces["Shared"].tags) {
879 				if (t.name == "TileData") {
880 					loadFromLayer(t.expectValue!int());
881 				}
882 			}
883 
884 		} catch (DOMRangeException e) {
885 			debug writeln(e);
886 		} catch (Exception e) {
887 			debug writeln(e);
888 		}
889 		return result;
890 	}
891 	/**
892 	 * Adds a palette file source to the document.
893 	 * Params:
894 	 *   filename = Path to the file containing the palette.
895 	 *   dataPakSrc = Path to the DataPak file if used, null otherwise.
896 	 *   offset = Palette offset, or where the palette should be loaded.
897 	 *   palShift = Palette shifting, or how many bits the target bitmap will use.
898 	 */
899 	public Tag addPaletteFile (string filename, string dataPakSrc, int offset, int palShift) @trusted {
900 		Attribute[] a;
901 		if (offset) a ~= new Attribute("offset", Value(offset));
902 		if (palShift) a ~= new Attribute("palShift", Value(palShift));
903 		if (dataPakSrc.length) a ~= new Attribute("dataPakSrc", Value(dataPakSrc));
904 		return new Tag(root,"File", "Palette", [Value(filename)], a);
905 	}
906 	/**
907 	 * Adds an embedded palette to the document.
908 	 * Params:
909 	 *   c = The palette to be embedded.
910 	 *   name = Name of the palette.
911 	 *   offset = Palette offset, or where the palette should be loaded.
912 	 */
913 	public Tag addEmbeddedPalette (Color[] c, string name, int offset) @trusted {
914 		import pixelperfectengine.system.etc : reinterpretCast;
915 		Attribute[] a;
916 		if (offset) a ~= new Attribute("offset", Value(offset));
917 		return new Tag(root, "Embed", "Palette", [Value(name), Value(reinterpretCast!ubyte(c))], a);
918 	}
919 	/**
920 	 * Returns whether the given palette file source exists.
921 	 * Params:
922 	 *   filename = Path to the file containing the palette.
923 	 *   dataPakSrc = Path to the DataPak file if used, null otherwise.
924 	 * Returns: True if the palette file source exists.
925 	 */
926 	public bool isPaletteFileExists (string filename, string dataPakSrc = null) @trusted {
927 		foreach (t0 ; root.all.tags) {
928 			if (t0.getFullName.toString == "File:Palette") {
929 				if (t0.getValue!string() == filename && t0.getAttribute!string("dataPakSrc", null) == dataPakSrc) 
930 					return true;
931 			}
932 		}
933 		return false;
934 	}
935 	/**
936 	 * Returns the name of the map from metadata.
937 	 */
938 	public string getName () @trusted {
939 		return metadata.getTagValue!string("Name");
940 	}
941 	/**
942 	 * Adds an object to a layer.
943 	 * Intended for editor use.
944 	 * Params:
945 	 *   layer = The ID of the layer.
946 	 *   t = The serialized tag of the object.
947 	 * Returns: The backup of the previous object's copy, or null if no object have existed with the same ID.
948 	 */
949 	public Tag addObjectToLayer(int layer, Tag t) @trusted {
950 		Tag result;
951 		try {
952 			foreach (Tag t0; layerData[layer].namespaces["Object"].tags) {
953 				if (t0.values[1].get!int == t.values[1].get!int) {
954 					layerData[layer].add(t);
955 					result = t0.remove();
956 					break;
957 				}
958 			}
959 		} catch (Exception e) {
960 			debug writeln(e);
961 		}
962 		layerData[layer].add(t);
963 		return result;
964 	}
965 	/**
966 	 * Removes an object from a layer.
967 	 * Intended for editor use.
968 	 * Params:
969 	 *   layer = ID of the layer from which we want to remove the object from.
970 	 *   objID = ID of the object we want to remove.
971 	 * Returns: the tag of the object that has been removed if the operation is successful.
972 	 */
973 	public Tag removeObjectFromLayer(int layer, int objID) @trusted {
974 		try {
975 			foreach (Tag t0; layerData[layer].namespaces["Object"].tags) {
976 				if (t0.values[1].get!int == objID) {
977 					return t0.remove();
978 				}
979 			}
980 		} catch (Exception e) {
981 			debug writeln(e);
982 		}
983 		return null;
984 	}
985 	/**
986 	 * Returns the horizontal resolution.
987 	 */
988 	public int getHorizontalResolution () @trusted {
989 		return metadata.getTag("Resolution").values[0].get!int();
990 	}
991 	/**
992 	 * Returns the vertical resolution.
993 	 */
994 	public int getVerticalResolution () @trusted {
995 		return metadata.getTag("Resolution").values[1].get!int();
996 	}
997 }
998 /**
999  * Represents a single object within a layer, that can represent many different things.
1000  * All objects have a priority identifier (int), a group identifier (int), and a name.
1001  */
1002 abstract class MapObject {
1003 	/**
1004 	 * Enumerator used for differentiating between multiple kinds of objects.
1005 	 * The value serialized as a string as the name of a tag.
1006 	 */
1007 	public enum MapObjectType : ubyte {
1008 		box,			///Can be used for collision detection, event marking for scripts, and masking. Has two coordinates.
1009 		/**
1010 		 * Only applicable for SpriteLayer.
1011 		 * Has one coordinate, a horizontal and vertical scaling indicator (int), and a source indicator (int).
1012 		 */
1013 		sprite,
1014 		polyline,
1015 	}
1016 	///Defines various flags for objects
1017 	public enum MapObjectFlags : ushort {
1018 		toCollision		=	1<<0,	///Marks the object to be included to collision detection.
1019 
1020 	}
1021 	public int 			pID;		///priority identifier
1022 	public int			gID;		///group identifier (equals with layer number)
1023 	public string		name;		///name of object
1024 	protected MapObjectType	_type;	///type of the object
1025 	public BitFlags!MapObjectFlags	flags;///Contains property flags
1026 	public Tag			mainTag;	///Tag that holds the data related to this mapobject + ancillary tags
1027 	///Returns the type of this object
1028 	public @property MapObjectType type () const @nogc nothrow @safe pure {
1029 		return _type;
1030 	}
1031 	///Serializes the object into an SDL tag
1032 	public abstract Tag serialize () @trusted;
1033 	/**
1034 	 * Checks if two objects have the same identifier.
1035 	 */
1036 	public bool opEquals (MapObject rhs) @nogc @safe nothrow pure const {
1037 		return pID == rhs.pID && gID == rhs.gID;
1038 	}
1039 }
1040 /**
1041  * Implements a Box object. Adds a single Coordinate property to the default MapObject
1042  */
1043 public class BoxObject : MapObject {
1044 	public Box			position;	///position of object on the layer
1045 	
1046 	/**
1047 	 * Creates a new instance from scratch.
1048 	 */
1049 	public this (int pID, int gID, string name, Box position)  {
1050 		this.pID = pID;
1051 		this.gID = gID;
1052 		this.name = name;
1053 		this.position = position;
1054 		_type = MapObjectType.box;
1055 		mainTag = new Tag("Object", "Box", [Value(name), Value(pID), Value(position.left), Value(position.top), 
1056 				Value(position.right), Value(position.bottom)]);
1057 	}
1058 	/**
1059 	 * Deserializes itself from a Tag.
1060 	 */
1061 	public this (Tag t, int gID) @trusted {
1062 		name = t.values[0].get!string();
1063 		pID = t.values[1].get!int();
1064 		position = Box(t.values[2].get!int(), t.values[3].get!int(), t.values[4].get!int(), t.values[5].get!int());
1065 		this.gID = gID;
1066 		_type = MapObjectType.box;
1067 		//ancillaryTags = t.tags;
1068 		mainTag = t;
1069 		if (t.getTag("ToCollision"))
1070 			flags.toCollision = true;
1071 	}
1072 	/**
1073 	 * Serializes the object into an SDL tag
1074 	 */
1075 	public override Tag serialize () @trusted {
1076 		return mainTag;
1077 	}
1078 	///Gets the identifying color of this object.
1079 	public Color color() @trusted {
1080 		Tag t0 = mainTag.getTag("Color");
1081 		if (t0) {
1082 			return parseColor(t0);
1083 		} else {
1084 			return Color.init;
1085 		}
1086 	}
1087 	///Sets the identifying color of this object.
1088 	public Color color(Color c) @trusted {
1089 		Tag t0 = mainTag.getTag("Color");
1090 		if (t0) {
1091 			t0.remove;
1092 		} 
1093 		mainTag.add(storeColor(c));
1094 		return c;
1095 	}
1096 }
1097 /**
1098  * Implements a sprite object. Adds a sprite source identifier, X and Y coordinates, and two 1024 based scaling indicator.
1099  */
1100 public class SpriteObject : MapObject {
1101 	public int 			ssID;	///Sprite source identifier
1102 	public int			x;		///X position
1103 	public int			y;		///Y position
1104 	public int			scaleHoriz;	///Horizontal scaling value
1105 	public int			scaleVert;	///Vertical scaling value
1106 	public RenderingMode	rendMode;///The rendering mode of the sprite
1107 	public ushort		palSel;	///Palette selector. Selects the given palette for the object.
1108 	public ubyte		palShift;	///Palette shift value. Determines palette length (2^x).
1109 	public ubyte		masterAlpha;///The main alpha channel of the sprite.
1110 	/**
1111 	 * Creates a new instance from scratch.
1112 	 */
1113 	public this (int pID, int gID, string name, int ssID, int x, int y, int scaleHoriz = 1024, int scaleVert = 1024, 
1114 			RenderingMode rendMode = RenderingMode.init, ushort palSel = 0, ubyte palShift = 0, ubyte masterAlpha = 0xFF) {
1115 		this.pID = pID;
1116 		this.gID = gID;
1117 		this.name = name;
1118 		this.ssID = ssID;
1119 		this.x = x;
1120 		this.y = y;
1121 		this.scaleHoriz = scaleHoriz;
1122 		this.scaleVert = scaleVert;
1123 		_type = MapObjectType.sprite;
1124 		Attribute[] attr;
1125 		if (scaleHoriz != 1024)
1126 			attr ~= new Attribute("scaleHoriz", Value(scaleHoriz));
1127 		if (scaleVert != 1024)
1128 			attr ~= new Attribute("scaleVert", Value(scaleVert));
1129 		if (palSel)
1130 			attr ~= new Attribute("palSel", Value(cast(int)palSel));
1131 		if (palShift)
1132 			attr ~= new Attribute("palShift", Value(cast(int)palShift));
1133 		if (masterAlpha)
1134 			attr ~= new Attribute("masterAlpha", Value(cast(int)masterAlpha));
1135 		mainTag = new Tag("Object", "Sprite", [Value(name), Value(pID), Value(ssID), Value(x), Value(y)]);
1136 		if (rendMode != RenderingMode.init)
1137 			new Tag(mainTag, null,"RenderingMode", [Value(to!string(rendMode))]);
1138 	}
1139 	/**
1140 	 * Deserializes itself from a Tag.
1141 	 */
1142 	public this (Tag t, int gID) @trusted {
1143 		this.gID = gID;
1144 		name = t.values[0].get!string();
1145 		pID = t.values[1].get!int();
1146 		ssID = t.values[2].get!int();
1147 		x = t.values[3].get!int();
1148 		y = t.values[4].get!int();
1149 		scaleHoriz = t.getAttribute!int("scaleHoriz", 1024);
1150 		scaleVert = t.getAttribute!int("scaleVert", 1024);
1151 		rendMode = MapFormat.renderingModeLookup[t.getTagValue!string("RenderingMode", "null")];
1152 		palSel = cast(ushort)t.getAttribute!int("palSel", 0);
1153 		palShift = cast(ubyte)t.getAttribute!int("palShift", 0);
1154 		masterAlpha = cast(ubyte)t.getAttribute!int("masterAlpha", 255);
1155 		mainTag = t;
1156 		_type = MapObjectType.sprite;
1157 		if (t.getTag("ToCollision"))
1158 			flags.toCollision = true;
1159 	}
1160 	/**
1161 	 * Serializes the object into an SDL tag
1162 	 */
1163 	public override Tag serialize () @trusted {
1164 		return mainTag;
1165 	}
1166 	
1167 }
1168 /**
1169  * Describes a polyline object for things like Vectoral Tile Layer.
1170  */
1171 public class PolylineObject : MapObject {
1172 	///The points of the object's path.
1173 	public Point[]		path;
1174 	public this (int pID, int gID, string name, Point[] path) {
1175 		this.gID = gID;
1176 		this.pID = pID;
1177 		this.name = name;
1178 		this.path = path;
1179 		mainTag = new Tag(null, "Object", "Polyline", [Value(name), Value(pID)]);
1180 		new Tag(mainTag, null, "Begin", [Value(path[0].x), Value(path[0].y)]);
1181 		foreach (Point key; path[1..$-1]) {
1182 			new Tag(mainTag, null, "Segment", [Value(key.x), Value(key.y)]);
1183 		}
1184 		if (path[0] == path[$-1]) {
1185 			new Tag(mainTag, null, "Close");
1186 		} else {
1187 			new Tag(mainTag, null, "Segment", [Value(path[$-1].x), Value(path[$-1].y)]);
1188 		}
1189 	}
1190 	public this (Tag t, int gID) @trusted {
1191 		this.gID = gID;
1192 		name = t.values[0].get!string();
1193 		pID = t.values[1].get!int();
1194 		foreach (Tag t0 ; t.tags) {
1195 			switch (t0.name) {
1196 				case "Begin":
1197 					enforce!MapFormatException(path.length == 0, "'Begin' node found in the middle of the path.");
1198 					goto case "Segment";
1199 				case "Segment":
1200 					enforce!MapFormatException(path.length != 0, "No 'Begin' node found");
1201 					path ~= Point(t0.values[0].get!int, t0.values[1].get!int);
1202 					break;
1203 				case "Close":
1204 					path ~= path[0];
1205 					break;
1206 				default:
1207 					break;
1208 			}
1209 		}
1210 		mainTag = t;
1211 		_type = MapObjectType.polyline;
1212 	}
1213 	/**
1214 	 * Sets the color to 'c' for the polyline object's given segment indicated by 'num'.
1215 	 */
1216 	public Color color(Color c, int num) @trusted {
1217 		int i;
1218 		foreach (Tag t0 ; mainTag.tags) {
1219 			switch (t0.name) {
1220 				case "Begin", "Segment", "Close":
1221 					if (num == i) {
1222 						t0.add(storeColor(c));
1223 						return c;
1224 					}
1225 					i++;
1226 					break;
1227 				default:
1228 					break;
1229 			}
1230 		}
1231 		throw new PPEException("Out of index error!");
1232 	}
1233 	/**
1234 	 * Returns the color for the polyline object's given segment indicated by 'num'.
1235 	 */
1236 	public Color color(int num) @trusted {
1237 		int i;
1238 		foreach (Tag t0 ; mainTag.tags) {
1239 			switch (t0.name) {
1240 				case "Begin", "Segment", "Close":
1241 					if (num == i) {
1242 						Tag t1 = t0.getTag("Color");
1243 						if (t1) {
1244 							return parseColor(t1);
1245 						} else {
1246 							return Color.init;
1247 						}
1248 					}
1249 					i++;
1250 					break;
1251 				default:
1252 					break;
1253 			}
1254 		}
1255 		throw new PPEException("Out of index error!");
1256 	}
1257 	override public Tag serialize() @trusted {
1258 		return Tag.init; // TODO: implement
1259 	}
1260 }
1261 ///Parses a color from SDLang Tag 't', then returns it as the engine's default format.
1262 public Color parseColor(Tag t) @trusted {
1263 	Color c;
1264 	switch (t.values.length) {
1265 		case 1:
1266 			if (t.values[0].peek!long)
1267 				c.base = cast(uint)t.getValue!long();
1268 			else
1269 				c.base = parseHex!uint(t.getValue!string);
1270 			break;
1271 		case 4:
1272 			c.a = cast(ubyte)t.values[0].get!int();
1273 			c.r = cast(ubyte)t.values[1].get!int();
1274 			c.g = cast(ubyte)t.values[2].get!int();
1275 			c.b = cast(ubyte)t.values[3].get!int();
1276 			break;
1277 		default:
1278 			throw new MapFormatException("Unrecognized color format tag!");
1279 	}
1280 	return c;
1281 }
1282 ///Serializes the engine's color format into an SDLang Tag.
1283 public Tag storeColor(Color c) @trusted {
1284 	return new Tag(null, "Color", [Value(format("%08x", c.base))]);
1285 }
1286 /**
1287  * Parses an ofject from an SDLang tag.
1288  * Params:
1289  *   t = The source tag.
1290  *   gID = Group (layer) ID.
1291  * Returns: The parsed object.
1292  */
1293 public MapObject parseObject(Tag t, int gID) @trusted {
1294 	if (t.namespace != "Object") return null;
1295 	switch (t.name) {
1296 		case "Box":
1297 			return new BoxObject(t, gID);
1298 		case "Sprite":
1299 			return new SpriteObject(t, gID);
1300 		case "Polyline":
1301 			return new PolylineObject(t, gID);
1302 		default:
1303 			return null;
1304 	}
1305 }
1306 /**
1307  * Simple LayerInfo struct, mostly for internal communications.
1308  */
1309 public struct LayerInfo {
1310 	LayerType	type;	///Type of layer
1311 	int			pri;	///Priority of layer
1312 	string		name;	///Name of layer
1313 	int opCmp (LayerInfo rhs) const pure @safe @nogc {
1314 		if (pri > rhs.pri)
1315 			return 1;
1316 		else if (pri < rhs.pri)
1317 			return -1;
1318 		else
1319 			return 0;
1320 	}
1321 	/**
1322 	 * Parses a string as a layer type
1323 	 */
1324 	static LayerType parseLayerTypeString (string s) pure @safe {
1325 		import std.uni : toLower;
1326 		s = toLower(s);
1327 		switch (s) {
1328 			case "tile":
1329 				return LayerType.Tile;
1330 			case "sprite":
1331 				return LayerType.Sprite;
1332 			case "transformabletile":
1333 				return LayerType.TransformableTile;
1334 			default:
1335 				return LayerType.init;
1336 		}
1337 	}
1338 }
1339 /**
1340  * Simple TileInfo struct, mostly for internal communication and loading.
1341  */
1342 public struct TileInfo {
1343 	wchar		id;		///ID of the tile in wchar format
1344 	ushort		palShift;	///palShift offset of the tile
1345 	int			num;	///Number of tile in the file
1346 	string		name;	///Name of the tile
1347 	int opCmp (TileInfo rhs) const pure @safe @nogc {
1348 		if (id > rhs.id)
1349 			return 1;
1350 		else if (id < rhs.id)
1351 			return -1;
1352 		else
1353 			return 0;
1354 	}
1355 	public string toString() const pure {
1356 		import std.conv : to;
1357 		return to!string(id) ~ ";" ~ to!string(num) ~ ";" ~ name;
1358 	}
1359 }
1360 public class MapFormatException : PPEException {
1361 	///
1362 	@nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
1363     {
1364         super(msg, file, line, nextInChain);
1365     }
1366 	///
1367     @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
1368     {
1369         super(msg, file, line, nextInChain);
1370     }
1371 }