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