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 }