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 &lt = &gt &lt index &gt value &lt / index &gt
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 }