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