1 /* 2 * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, map module 5 */ 6 module PixelPerfectEngine.map.mapload; 7 8 import std.xml; 9 import std.stdio; 10 import std.file; 11 import std.algorithm.mutation; 12 //import std.array; 13 import std.conv; 14 import PixelPerfectEngine.extbmp.extbmp; 15 16 public import PixelPerfectEngine.map.mapdata; 17 import PixelPerfectEngine.graphics.layers; 18 import PixelPerfectEngine.graphics.bitmap; 19 import PixelPerfectEngine.system.file; 20 import PixelPerfectEngine.system.exc; 21 import PixelPerfectEngine.system.etc; 22 23 /** 24 * Stores, loads, and saves a level data from an XML and multiple MAP files. 25 */ 26 27 public class ExtendibleMap{ 28 //private void[] rawData; ///DEPRECATED. Binary data field buffer, no longer used. 29 private Element[int] tileSource, objectSource; ///Stores XMP sources for the Layers 30 public TileLayerData[int] tld; ///Stores the data regarding the tile layers. 31 public SpriteLayerData[int] sld; ///Stores the data regarding the sprite layers. 32 private string[int] mapDataFileSource; 33 public string[string] metaData; ///Stores metadata. Serialized as: [index] = value < = > < index > value < / index > 34 public string filename; ///Name of the file alongside with the path. 35 /// Load from datastream 36 37 /// Load from file 38 this(string filename){ 39 this.filename = filename; 40 loadFile(); 41 } 42 ///Create new from scratch 43 this(){ 44 45 } 46 47 /+public T[wchar] loadTileSet(T)(int num){ 48 T[wchar] result; 49 50 foreach(Element e1; tileSource[num].elements){ 51 if(e1.tag.name == "File"){ 52 ExtendibleBitmap xmp = new ExtendibleBitmap(e1.tag.attr["source"]); 53 foreach(Element e2; e1.elements){ 54 result[to!wchar(parseHex(e2.tag.attr["wcharID"]))] = loadBitmapFromXMP(T)(xmp, e2.tag.attr["source"]); 55 56 } 57 } 58 } 59 return result; 60 }+/ 61 /// Loads the bitmaps for the Tilelayer from the XMP files 62 public ABitmap[wchar] loadTileSet(int num){ 63 ABitmap[wchar] result; 64 foreach(Element e1; tileSource[num].elements){ 65 if(e1.tag.name == "File"){ 66 ExtendibleBitmap xmp = new ExtendibleBitmap(e1.tag.attr["source"]); 67 foreach(Element e2; e1.elements){ 68 result[to!wchar(parseHex(e2.tag.attr["wcharID"]))] = loadBitmapFromXMP!ABitmap(xmp, e2.tag.attr["source"]); 69 70 } 71 } 72 } 73 return result; 74 } 75 /// Loads the bitmaps for the Tilelayer from the XMP files 76 /+deprecated Bitmap16Bit[wchar] loadTileSet(int num){ 77 Bitmap16Bit[wchar] result; 78 79 foreach(Element e1; tileSource[num].elements){ 80 if(e1.tag.name == "File"){ 81 ExtendibleBitmap xmp = new ExtendibleBitmap(e1.tag.attr["source"]); 82 foreach(Element e2; e1.elements){ 83 result[to!wchar(parseHex(e2.tag.attr["wcharID"]))] = loadBitmapFromXMP(xmp, e2.tag.attr["source"]); 84 85 } 86 } 87 } 88 return result; 89 } 90 /// Loads the 32bit bitmaps for the Tilelayer from the XMP files 91 deprecated Bitmap32Bit[wchar] load32BitTileSet(int num){ 92 Bitmap32Bit[wchar] result; 93 foreach(Element e1; tileSource[num].elements){ 94 if(e1.tag.name == "File"){ 95 ExtendibleBitmap xmp = new ExtendibleBitmap(e1.tag.attr["source"]); 96 foreach(Element e2; e1.elements){ 97 result[to!wchar(parseHex(e2.tag.attr["wcharID"]))] = load32BitBitmapFromXMP(xmp, e2.tag.attr["source"]); 98 } 99 } 100 } 101 return result; 102 }+/ 103 /// Adds a new file for the tilesource. 104 void addFileToTileSource(int num, string file){ 105 Element e = new Element(new Tag("File")); 106 e.tag.attr["source"] = file; 107 tileSource[num] ~= e; 108 } 109 /// Adds a new tile for the tilesource. If file source doesn't exist, it adds to the filelist. Source: the ID in the file. 110 void addTileToTileSource(int num, wchar ID, string name, string source, string file){ 111 foreach(Element e; tileSource[num].elements){ 112 if(e.tag.attr["source"] == file){ 113 Element e0 = new Element("TileSource",name); 114 e0.tag.attr["wcharID"] = intToHex(ID, 4); 115 e0.tag.attr["source"] = source; 116 e ~= e0; 117 return; 118 } 119 } 120 Element e = new Element(new Tag("File")); 121 e.tag.attr["source"] = file; 122 tileSource[num] ~= e; 123 Element e0 = new Element("TileSource",name); 124 e0.tag.attr["wcharID"] = intToHex(ID, 4); 125 e0.tag.attr["source"] = source; 126 e ~= e0; 127 } 128 /// Adds a new TileLayer to the file. 129 void addTileLayer(TileLayerData t){ 130 tld[t.priority] = t; 131 //create placeholder element 132 Element e = new Element("tileSource"); 133 tileSource[t.priority] = e; 134 } 135 /// Gets the TileLayer from the file. 136 TileLayerData getTileLayer(int num){ 137 return tld[num]; 138 } 139 /// Gets the number of layers. 140 int getNumOfLayers(){ 141 return tld.length + sld.length; 142 } 143 /// Removes a tilelayer. 144 void removeTileLayer(int num){ 145 tld.remove(num); 146 tileSource.remove(num); 147 } 148 /// Loads a file 149 void loadFile(){ 150 string header = cast(string)std.file.read(filename); 151 Document d = new Document(header); 152 foreach(Element e1; d.elements){ 153 switch(e1.tag.name){ 154 case "MetaData": 155 //writeln("MetaData found"); 156 foreach(Element e2; e1.elements){ 157 metaData[e2.tag.name] = e2.text; 158 } 159 break; 160 case "TileLayer": 161 //tileSource ~= e1; 162 int priority = to!int(e1.tag.attr["priority"]); 163 MappingElement[] md; 164 foreach(Element e2; e1.elements){ 165 switch(e2.tag.name){ 166 case "file": 167 MapDataHeader mheader; 168 md = loadMapFile(&mheader, e2.tag.text); 169 mapDataFileSource[priority] = e2.tag.text; 170 break; 171 case "base64": 172 //md = new MapData(to!int(e1.tag.attr["mX"]), to!int(e1.tag.attr["mY"]), e2.tag.text); 173 md = loadMapFromBase64(e2.tag.text, to!int(e1.tag.attr["mX"]) * to!int(e1.tag.attr["mY"])); 174 break; 175 case "tileSource": 176 tileSource[priority] = e2; 177 break; 178 default: 179 break; 180 } 181 } 182 tld[priority] = new TileLayerData(to!int(e1.tag.attr["tX"]), to!int(e1.tag.attr["tY"]), 183 to!int(e1.tag.attr["mX"]), to!int(e1.tag.attr["mY"]), to!double(e1.tag.attr["sX"]), to!double(e1.tag.attr["sY"]), 184 to!int(e1.tag.attr["priority"]), md, e1.tag.attr["name"], e1.tag.attr.get("subType","")); 185 186 break; 187 case "SpriteLayer": 188 auto ea = new Element("SpriteLayer"); 189 SpriteLayerData s = new SpriteLayerData(e1.tag.attr["name"], to!double(e1.tag.attr["sX"]), to!double(e1.tag.attr["sY"]), 190 to!int(e1.tag.attr["priority"]), e1.tag.attr.get("subType","")); 191 foreach(Element e2; e1.elements){ 192 if(e2.tag.name == "Object"){ 193 ObjectPlacement o = new ObjectPlacement(to!int(e2.tag.attr["x"]), to!int(e2.tag.attr["y"]), to!int(e2.tag.attr["num"]),e2.tag.attr["ID"]); 194 o.addAuxData(e2.elements); 195 196 }else{ 197 ea ~= e2; 198 } 199 } 200 objectSource[to!int(e1.tag.attr["priority"])] = ea; 201 sld[to!int(e1.tag.attr["priority"])] = s; 202 break; 203 default: break; 204 } 205 } 206 } 207 /// Saves the file to the given location 208 void saveFile(string filename){ 209 this.filename = filename; 210 saveFile(); 211 } 212 /// Saves the file to the last location 213 void saveFile(){ 214 auto doc = new Document(new Tag("HEADER")); 215 auto e0 = new Element("MetaData"); 216 foreach(string s; metaData.byKey()){ 217 e0 ~= new Element(s, metaData[s]); 218 } 219 doc ~= e0; 220 221 foreach(int i; tld.byKey){ 222 Element e1 = new Element("TileLayer"); 223 e1.tag.attr["name"] = tld[i].name; 224 e1.tag.attr["tX"] = to!string(tld[i].tX); 225 e1.tag.attr["tY"] = to!string(tld[i].tY); 226 e1.tag.attr["mX"] = to!string(tld[i].mX); 227 e1.tag.attr["mY"] = to!string(tld[i].mY); 228 e1.tag.attr["sX"] = to!string(tld[i].sX); 229 e1.tag.attr["sY"] = to!string(tld[i].sY); 230 e1 ~= tileSource[i]; 231 if(tld[i].subtype) 232 e1.tag.attr["subtype"] = tld[i].subtype; 233 e1.tag.attr["priority"] = to!string(tld[i].priority); 234 if(tld[i].isEmbedded){ 235 Element e2 = new Element("base64", to!string(saveMapToBase64(tld[i].mapping))); 236 }else{ 237 Element e2 = new Element("file",mapDataFileSource[i]); 238 //tld[i].mapping.save(mapDataFileSource[i]); 239 MapDataHeader header = MapDataHeader(tld[i].mX, tld[i].mY); 240 saveMapFile(&header, tld[i].mapping, mapDataFileSource[i]); 241 } 242 doc ~= e1; 243 //e1.items ~= new ProcessingInstruction("map " ~ tld[i].getMapdataForSaving()); 244 245 } 246 247 for(int i; i < objectSource.length; i++){ 248 Element e1 = objectSource[i]; 249 e1.tag.attr["name"] = sld[i].name; 250 e1.tag.attr["sX"] = to!string(sld[i].sX); 251 e1.tag.attr["sY"] = to!string(sld[i].sY); 252 e1.tag.attr["subtype"] = sld[i].subtype; 253 e1.tag.attr["priority"] = to!string(sld[i].priority); 254 /*foreach(ObjectPlacement o;sld[i].placement){ 255 auto e2 = o.getAuxData(); 256 e2.tag.attr["x"] = to!string(o.x); 257 e2.tag.attr["y"] = to!string(o.y); 258 e2.tag.attr["num"] = to!string(o.num); 259 e2.tag.attr["ID"] = o.ID; 260 e1 ~= e2; 261 }*/ 262 doc ~= e1; 263 } 264 string header = stringArrayJoin(doc.pretty()); 265 //rawData.length = 8; 266 //*cast(uint*)rawData.ptr = flags; 267 //*cast(int*)(rawData.ptr+4) = header.length; 268 //rawData ~= cast(void[])header; 269 //rawData ~= rawData0; 270 std.file.write(filename, header); 271 //rawData0.length = 0; 272 } 273 } 274 275 276 /** 277 * Stores Data regarding to the TileLayer. 278 */ 279 public class TileLayerData{ 280 public MappingElement[] mapping; ///Mapping data. 281 public bool isEmbedded; ///Whether the data is embedded with base64 coding or not. 282 public bool warp; ///Toggle warp. 283 public string name; ///Name of the layer, primarily used by the editors. 284 public string subtype; ///Subtype of the layer, eg. 32bit, transformable. 285 public int tX, tY; ///Sizes of the tile for the layer. 286 public int mX, mY; ///Sizes of the mapping. 287 public int priority; ///Layerpriority. 288 public double sX, sY, sXOffset, sYOffset; ///Used by the autoscroll for paralax scrolling. 289 290 /// Constructor for TileLayers with preexisting mapping. 291 public this(int tX, int tY, int mX, int mY, double sX, double sY, int priority, MappingElement[] mapping, string name, string subtype = ""){ 292 this.tX = tX; 293 this.tY = tY; 294 this.mX = mX; 295 this.mY = mY; 296 this.sX = sX; 297 this.sY = sY; 298 this.priority = priority; 299 this.mapping = mapping; 300 this.name = name; 301 this.subtype = subtype; 302 } 303 /// Constructor for TileLayers without preexisting mapping. 304 public this(int tX, int tY, int mX, int mY, double sX, double sY, int priority, string name, string subtype = ""){ 305 this.tX = tX; 306 this.tY = tY; 307 this.mX = mX; 308 this.mY = mY; 309 this.sX = sX; 310 this.sY = sY; 311 this.priority = priority; 312 //this.mapping = mapping; 313 this.name = name; 314 this.subtype = subtype; 315 isEmbedded = true; 316 /*wchar[] initMapping; 317 initMapping.length = mX*mY; 318 this.mapping = initMapping;*/ 319 } 320 321 322 } 323 /** 324 * Stores data regarding to the SpriteLayer. 325 */ 326 public class SpriteLayerData{ 327 public string name; ///Name of the layer, primarily used by the editors. 328 public string subtype; ///Subtype of the layer, eg. 32bit, transformable. 329 public double sX, sY; ///Used by the autoscroll for paralax scrolling. 330 public int priority; ///Layerpriority. 331 public ObjectPlacement[] placement; ///Objectdata 332 /// Creates a new spritelayer in the file 333 this(string name, double sX, double sY, int priority, string subtype = ""){ 334 this.name = name; 335 this.subtype = subtype; 336 this.sX = sX; 337 this.sY = sY; 338 this.priority = priority; 339 } 340 } 341 /** 342 * Stores object placement data 343 */ 344 public class ObjectPlacement{ 345 protected Element[] auxObjectData; ///XML data regarding of this object. 346 public int x, y; ///Position of the object. 347 public int num; ///Identification number and rendering priority. 348 public string ID; ///Type of the object 349 /// Creates a new data. 350 this(int x, int y, int num, string ID){ 351 this.x = x; 352 this.y = y; 353 this.num = num; 354 this.ID = ID; 355 } 356 /// Sets XML data. 357 public void setAuxData(Element[] auxData){ 358 auxObjectData = auxData; 359 } 360 /// Gets the XML data. 361 /// It's recommended to inherit from the class instead and write custom functions instead. 362 public Element[] getAuxData(){ 363 return auxObjectData; 364 } 365 public void addAuxData(Element[] e){ 366 auxObjectData ~= e; 367 } 368 }