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