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