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