1 /*
2 Copyright (C) 2015-2021, by Laszlo Szeremi under the Boost license.
3 
4 PixelPerfectEditor
5 */
6 
7 
8 module app;
9 
10 import std.stdio;
11 import std.string;
12 import std.conv;
13 import std.format;
14 import std.random;
15 
16 import bindbc.sdl;
17 //import derelict.freeimage.freeimage;
18 
19 //import system.config;
20 
21 import pixelperfectengine.graphics.outputscreen;
22 import pixelperfectengine.graphics.raster;
23 import pixelperfectengine.graphics.layers;
24 
25 import pixelperfectengine.graphics.bitmap;
26 
27 import pixelperfectengine.collision.common;
28 import pixelperfectengine.collision.objectcollision;
29 
30 import pixelperfectengine.system.input;
31 import pixelperfectengine.system.file;
32 import pixelperfectengine.system.etc;
33 import pixelperfectengine.system.config;
34 //import pixelperfectengine.system.binarySearchTree;
35 import pixelperfectengine.system.common;
36 import core.memory;
37 
38 public import editor;
39 //import pixelperfectengine.extbmp.extbmp;
40 
41 public Editor prg;
42 
43 int main(string[] args){
44 	initialzeSDL();
45 	GC.disable();
46 	if (args.length > 1) {
47 		if (args[1] == "--test") {
48 			int mapWidth = 8, mapHeight = 8;
49 			if (args.length == 4) {
50 				mapWidth = to!int(args[2]);
51 				mapHeight = to!int(args[3]);
52 			}
53 			TileLayerTest lprg = new TileLayerTest(mapWidth, mapHeight);
54 			lprg.whereTheMagicHappens;
55 			return 0;
56 		}
57 	}
58 
59 	prg = new Editor(args);
60 	prg.whereTheMagicHappens;
61 	return 0;
62 }
63 /**
64  * Tests graphics output, input events, collision, etc.
65  */
66 class TileLayerTest : SystemEventListener, InputListener {
67 	bool isRunning, up, down, left, right, scrup, scrdown, scrleft, scrright;
68 	OutputScreen output;
69 	Raster r;
70 	TileLayer t;
71 	TileLayer textLayer;
72 	TransformableTileLayer!(Bitmap8Bit,16,16) tt;
73 	Bitmap8Bit[] tiles;
74 	Bitmap8Bit dlangMan;
75 	Bitmap1bit dlangManCS;
76 	SpriteLayer s;
77 	InputHandler ih;
78 	ObjectCollisionDetector ocd;
79 	float theta;
80 	int framecounter;
81 	this (int mapWidth, int mapHeight) {
82 		theta = 0;
83 		isRunning = true;
84 		Image tileSource = loadImage(File("../assets/sci-fi-tileset.png"));
85 		//Image tileSource = loadImage(File("../assets/_system/concreteGUIE0.tga"));
86 		Image spriteSource = loadImage(File("../assets/d-man.tga"));
87 		Image fontSource = loadImage(File("../system/codepage_8_8.png"));
88 		output = new OutputScreen("TileLayer test", 424 * 4, 240 * 4);
89 		r = new Raster(424,240,output,0);
90 		output.setMainRaster(r);
91 		t = new TileLayer(16,16, RenderingMode.Copy);
92 		textLayer = new TileLayer(8,8, RenderingMode.AlphaBlend);
93 		textLayer.paletteOffset = 512;
94 		textLayer.masterVal = 127;
95 		textLayer.loadMapping(53, 30, new MappingElement[](53 * 30));
96 		tt = new TransformableTileLayer!(Bitmap8Bit,16,16)(RenderingMode.AlphaBlend);
97 		s = new SpriteLayer(RenderingMode.AlphaBlend);
98 		r.addLayer(tt, 1);
99 		r.addLayer(t, 0);
100 		r.addLayer(s, 2);
101 		r.addLayer(textLayer, 65_536);
102 
103 		Color[] localPal = loadPaletteFromImage(tileSource);
104 		localPal.length = 256;
105 		r.addPaletteChunk(localPal);
106 		localPal = loadPaletteFromImage(spriteSource);
107 		localPal.length = 256;
108 		r.addPaletteChunk(localPal);
109 		r.addPaletteChunk([Color(0x00,0x00,0x00,0xFF),Color(0xff,0xff,0xff,0xFF),Color(0x00,0x00,0x00,0xFF),
110 				Color(0xff,0x00,0x00,0xFF),Color(0x00,0x00,0x00,0xFF),Color(0x00,0xff,0x00,0xFF),Color(0x00,0x00,0x00,0xFF),
111 				Color(0x00,0x00,0xff,0xFF)]);
112 
113 		//writeln(r.layerMap);
114 		//c = new CollisionDetector();
115 		dlangMan = loadBitmapFromImage!Bitmap8Bit(spriteSource);
116 		dlangManCS = dlangMan.generateStandardCollisionModel();
117 		ocd = new ObjectCollisionDetector(&onCollision, 0);
118 		s.addSprite(dlangMan, 65_536, 0, 0, 1);
119 		ocd.objects[65_536] = CollisionShape(Box(0, 0, 31, 31), dlangManCS);
120 		
121 		for(int i = 1 ; i < 10 ; i++){
122 			const int x = uniform(0,320), y = uniform(0,240);
123 			s.addSprite(dlangMan, i, x, y, 1);
124 			ocd.objects[i] = CollisionShape(Box(x, y, x + 31, y + 31), dlangManCS);
125 		}
126 		
127 		tiles = loadBitmapSheetFromImage!Bitmap8Bit(tileSource, 16, 16);//loadBitmapSheetFromFile!Bitmap8Bit("../assets/sci-fi-tileset.png",16,16);
128 		
129 		for (int i; i < tiles.length; i++) {
130 			tt.addTile(tiles[i], cast(wchar)i);
131 		}
132 		
133 		for (int i; i < tiles.length; i++) {
134 			t.addTile(tiles[i], cast(wchar)i);
135 		}
136 		
137 		{
138 			Bitmap8Bit[] fontSet = loadBitmapSheetFromImage!Bitmap8Bit(fontSource, 8, 8);
139 			for (ushort i; i < fontSet.length; i++) {
140 				textLayer.addTile(fontSet[i], i, 1);
141 			}
142 		}
143 		//wchar[] mapping;
144 		MappingElement[] mapping;
145 		mapping.length = mapWidth * mapHeight;//64*64;
146 		//attrMapping.length = 256*256;
147 		for(int i; i < mapping.length; i++){
148 			//mapping[i] = to!wchar(uniform(0x0000,0x00AA));
149 			const int rnd = uniform(0,1024);
150 			//attrMapping[i] = BitmapAttrib(rnd & 1 ? true : false, rnd & 2 ? true : false);
151 			mapping[i] = MappingElement(cast(wchar)(rnd & 63), BitmapAttrib(rnd & 1024 ? true : false, rnd & 512 ? true : false));
152 			//mapping[i] = MappingElement(0x0, BitmapAttrib(false,false));
153 		}
154 		ih = new InputHandler();
155 		ih.systemEventListener = this;
156 		ih.inputListener = this;
157 		
158 		{
159 			import pixelperfectengine.system.input.scancode;
160 			ih.addBinding(BindingCode(ScanCode.UP, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("up"));
161 			ih.addBinding(BindingCode(ScanCode.DOWN, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("down"));
162 			ih.addBinding(BindingCode(ScanCode.LEFT, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("left"));
163 			ih.addBinding(BindingCode(ScanCode.RIGHT, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("right"));
164 			ih.addBinding(BindingCode(ScanCode.np8, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("scrup"));
165 			ih.addBinding(BindingCode(ScanCode.np2, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("scrdown"));
166 			ih.addBinding(BindingCode(ScanCode.np4, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("scrleft"));
167 			ih.addBinding(BindingCode(ScanCode.np6, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("scrright"));
168 			ih.addBinding(BindingCode(ScanCode.Q, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("A+"));
169 			ih.addBinding(BindingCode(ScanCode.A, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("A-"));
170 			ih.addBinding(BindingCode(ScanCode.W, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("B+"));
171 			ih.addBinding(BindingCode(ScanCode.S, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("B-"));
172 			ih.addBinding(BindingCode(ScanCode.E, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("C+"));
173 			ih.addBinding(BindingCode(ScanCode.D, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("C-"));
174 			ih.addBinding(BindingCode(ScanCode.R, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("D+"));
175 			ih.addBinding(BindingCode(ScanCode.F, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("D-"));
176 			ih.addBinding(BindingCode(ScanCode.T, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("x0+"));
177 			ih.addBinding(BindingCode(ScanCode.G, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("x0-"));
178 			ih.addBinding(BindingCode(ScanCode.Y, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("y0+"));
179 			ih.addBinding(BindingCode(ScanCode.H, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("y0-"));
180 			ih.addBinding(BindingCode(ScanCode.PAGEUP, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("alpha+"));
181 			ih.addBinding(BindingCode(ScanCode.PAGEDOWN, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("alpha-"));
182 			ih.addBinding(BindingCode(ScanCode.HOME, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("hidettl"));
183 			ih.addBinding(BindingCode(ScanCode.END, 0, Devicetype.Keyboard, 0, KeyModifier.All), InputBinding("unhidettl"));
184 		}
185 		
186 		tt.loadMapping(mapWidth, mapHeight, mapping);
187 		tt.warpMode = WarpMode.Off;
188 		
189 		t.loadMapping(mapWidth, mapHeight, mapping);
190 		t.warpMode = WarpMode.TileRepeat;
191 		
192 		//t.setWrapMode(true);
193 		//tt.D = -256;
194 		//loadPaletteFromXMP(tileSource, "default", r);
195 
196 		/*for(int y ; y < 240 ; y++){
197 			for(int x ; x < 240 ; x++){
198 				writeln('[',x,',',y,"] : ", t.transformFunc([x,y]));
199 			}
200 		}*/
201 		
202 		//writeln(r.palette);
203 		//r.palette[0].alpha = 255;
204 		//r.palette[256].base = 0;
205 		//textLayer.writeTextToMap(2,2,0,"Hello world!",BitmapAttrib(true, false));
206 		textLayer.writeTextToMap(0, 0, 0, "Framerate:", BitmapAttrib(true, false));
207 		textLayer.writeTextToMap(0, 1, 0, "Collision:", BitmapAttrib(true, false));
208 		textLayer.writeTextToMap(0, 2, 0, "Col. type:", BitmapAttrib(true, false));
209 		//writeln(tt);
210 		//r.palette[0] = 255;
211 		//r.addRefreshListener(output, 0);
212 
213 	}
214 	private @nogc void ttlHBlankInterrupt(ref short[4] localABCD, ref short[2] localsXsY, ref short[2] localx0y0, short y){
215 		localABCD[0]++;
216 	}
217 	public void whereTheMagicHappens(){
218 		while(isRunning){
219 			r.refresh();
220 			ih.test();
221 			if(up) {
222 				s.relMoveSprite(65_536,0,-1);
223 			}
224 			if(down) {
225 				s.relMoveSprite(65_536,0,1);
226 			}
227 			if(left) {
228 				s.relMoveSprite(65_536,-1,0);
229 			}
230 			if(right) {
231 				s.relMoveSprite(65_536,1,0);
232 			}
233 			ocd.objects.ptrOf(65_536).position = s.getSpriteCoordinate(65_536);
234 			ocd.testSingle(65_536);
235 			if(scrup) {
236 				t.relScroll(0,-1);
237 				tt.relScroll(0,-1);
238 				s.relScroll(0,-1);
239 			}
240 			if(scrdown) {
241 				t.relScroll(0,1);
242 				tt.relScroll(0,1);
243 				s.relScroll(0,1);
244 			}
245 			if(scrleft) {
246 				t.relScroll(-1,0);
247 				tt.relScroll(-1,0);
248 				s.relScroll(-1,0);
249 			}
250 			if(scrright) {
251 				t.relScroll(1,0);
252 				tt.relScroll(1,0);
253 				s.relScroll(1,0);
254 			}
255 			
256 			framecounter++;
257 			if(framecounter == 10){
258 				float avgFPS = r.avgfps;
259 				wstring fpsCounter = format(" %3.3f"w, avgFPS);
260 				textLayer.writeTextToMap(10,0,0,fpsCounter,BitmapAttrib(true, false));
261 				framecounter = 0;
262 			}
263 			//t.relScroll(1,0);
264 		}
265 	}
266 	public void onCollision(ObjectCollisionEvent event) {
267 		textLayer.writeTextToMap(10,1,0,format("%8X"w,event.idB),BitmapAttrib(true, false));
268 		final switch (event.type) with (ObjectCollisionEvent.Type) {
269 			case None:
270 				textLayer.writeTextToMap(10,2,0,"        None",BitmapAttrib(true, false));
271 				break;
272 			case BoxEdge:
273 				textLayer.writeTextToMap(10,2,0,"     BoxEdge",BitmapAttrib(true, false));
274 				break;
275 			case BoxOverlap:
276 				textLayer.writeTextToMap(10,2,0,"  BoxOverlap",BitmapAttrib(true, false));
277 				break;
278 			case ShapeOverlap:
279 				textLayer.writeTextToMap(10,2,0,"ShapeOverlap",BitmapAttrib(true, false));
280 				break;
281 		}
282 	}
283 	override public void onQuit() {
284 		isRunning = false;
285 	}
286 	public void controllerAdded(uint id) {
287 
288 	}
289 	public void controllerRemoved(uint id) {
290 
291 	}
292 	/+override public void keyPressed(string ID,uint timestamp,uint devicenumber,uint devicetype) {
293 		//writeln(ID);
294 		import pixelperfectengine.graphics.transformFunctions;
295 		switch(ID){
296 			case "up": up = true; break;
297 			case "down": down = true; break;
298 			case "left": left = true; break;
299 			case "right": right = true; break;
300 			case "scrup": scrup = true; break;
301 			case "scrdown": scrdown = true; break;
302 			case "scrleft": scrleft = true; break;
303 			case "scrright": scrright = true; break;
304 			case "A+": tt.A = cast(short)(tt.A + 16); break;
305 			case "A-": tt.A = cast(short)(tt.A - 16); break;
306 			case "B+": tt.B = cast(short)(tt.B + 16); break;
307 			case "B-": tt.B = cast(short)(tt.B - 16); break;
308 			case "C+": tt.C = cast(short)(tt.C + 16); break;
309 			case "C-": tt.C = cast(short)(tt.C - 16); break;
310 			case "D+": tt.D = cast(short)(tt.D + 16); break;
311 			case "D-": tt.D = cast(short)(tt.D - 16); break;
312 			case "x0+": tt.x_0 = cast(short)(tt.x_0 + 1); break;
313 			case "x0-": tt.x_0 = cast(short)(tt.x_0 - 1); break;
314 			case "y0+": tt.y_0 = cast(short)(tt.y_0 + 1); break;
315 			case "y0-": tt.y_0 = cast(short)(tt.y_0 - 1); break;
316 			case "sH-":
317 				if(s.getScaleSpriteHoriz(0) == 16){
318 					s.scaleSpriteHoriz(0,-16);
319 					return;
320 				}
321 				s.scaleSpriteHoriz(0,s.getScaleSpriteHoriz(0) - 16);
322 				//writeln(s.getScaleSpriteHoriz(0));
323 				break;
324 			case "sH+":
325 				if(s.getScaleSpriteHoriz(0) == -16){
326 					s.scaleSpriteHoriz(0,16);
327 					return;
328 				}
329 				s.scaleSpriteHoriz(0,s.getScaleSpriteHoriz(0) + 16);
330 				//writeln(s.getScaleSpriteHoriz(0));
331 				break;
332 			case "sV-":
333 				if(s.getScaleSpriteVert(0) == 16){
334 					s.scaleSpriteVert(0,-16);
335 					return;
336 				}
337 				s.scaleSpriteVert(0,s.getScaleSpriteVert(0) - 16);
338 				break;
339 			case "sV+":
340 				if(s.getScaleSpriteVert(0) == -16){
341 					s.scaleSpriteVert(0,16);
342 					return;
343 				}
344 				s.scaleSpriteVert(0,s.getScaleSpriteVert(0) + 16);
345 				break;
346 			case "HM":
347 				s.scaleSpriteHoriz(0,s.getScaleSpriteHoriz(0) * -1);
348 				break;
349 			case "VM":
350 				s.scaleSpriteVert(0,s.getScaleSpriteVert(0) * -1);
351 				break;
352 			case "theta+":
353 				theta += 1;
354 				short[4] newTP = rotateFunction(theta);
355 				tt.A = newTP[0];
356 				tt.B = newTP[1];
357 				tt.C = newTP[2];
358 				tt.D = newTP[3];
359 				break;
360 			case "theta-":
361 				theta -= 1;
362 				short[4] newTP = rotateFunction(theta);
363 				tt.A = newTP[0];
364 				tt.B = newTP[1];
365 				tt.C = newTP[2];
366 				tt.D = newTP[3];
367 				break;
368 			default: break;
369 		}
370 	}
371 	override public void keyReleased(string ID,uint timestamp,uint devicenumber,uint devicetype) {
372 		switch(ID){
373 			case "up": up = false; break;
374 			case "down": down = false; break;
375 			case "left": left = false; break;
376 			case "right": right = false; break;
377 			case "scrup": scrup = false; break;
378 			case "scrdown": scrdown = false; break;
379 			case "scrleft": scrleft = false; break;
380 			case "scrright": scrright = false; break;
381 			default: break;
382 		}
383 	}+/
384 /*public void spriteCollision(CollisionEvent ce){
385 		writeln("COLLISION!!!!11!1111!!!ONEONEONE!!!");
386 	}
387 
388 	public void backgroundCollision(CollisionEvent ce){}*/
389 	/**
390 	 * Called when a keybinding event is generated.
391 	 * The `id` should be generated from a string, usually the name of the binding.
392 	 * `code` is a duplicate of the code used for fast lookup of the binding, which also contains other info (deviceID, etc).
393 	 * `timestamp` is the time lapsed since the start of the program, can be used to measure time between keypresses.
394 	 * NOTE: Hat events on joysticks don't generate keyReleased events, instead they generate keyPressed events on release.
395 	 */
396 	public void keyEvent(uint id, BindingCode code, uint timestamp, bool isPressed) {
397 		//writeln(id, ";", code, ";",timestamp, ";",isPressed, ";");
398 		switch (id) {
399 			case 1720810685:	//up
400 				up = isPressed;
401 				break;
402 			case 1672064345:	//down
403 				down = isPressed;
404 				break;
405 			case 2840212248:	//left
406 				left = isPressed;
407 				break;
408 			case 1786548735:	//right
409 				right = isPressed;
410 				break;
411 			case 3938104347:	//scrup
412 				scrup = isPressed;
413 				break;
414 			case 131561283:		//scrdown
415 				scrdown = isPressed;
416 				break;
417 			case 4011913815:	//scrleft
418 				scrleft = isPressed;
419 				break;
420 			case 2073272778:	//scrright
421 				scrright = isPressed;
422 				break;
423 			case 4284782897: 	//A+
424 				tt.A = cast(short)(tt.A + 16);
425 				break;
426 			case 142754382:		//A-
427 				tt.A = cast(short)(tt.A - 16);
428 				break;
429 			case 2060572171:	//B+
430 				tt.B = cast(short)(tt.B + 16);
431 				break;
432 			case 919786464:		//B-
433 				tt.B = cast(short)(tt.B - 16);
434 				break;
435 			case 2857229774:	//C+
436 				tt.C = cast(short)(tt.C + 16);
437 				break;
438 			case 1598464886:	//C-
439 				tt.C = cast(short)(tt.C - 16);
440 				break;
441 			case 2476135441:	//D+
442 				tt.D = cast(short)(tt.D + 16);
443 				break;
444 			case 3708187064:	//D-
445 				tt.D = cast(short)(tt.D - 16);
446 				break;
447 			case 3238134781:	//x0+
448 				tt.x_0 = cast(short)(tt.x_0 + 1);
449 				break;
450 			case 135027337:		//x0-
451 				tt.x_0 = cast(short)(tt.x_0 - 1);
452 				break;
453 			case 983492653:		//y0+
454 				tt.y_0 = cast(short)(tt.y_0 + 1);
455 				break;
456 			case 2733639921:	//y0-
457 				tt.y_0 = cast(short)(tt.y_0 - 1);
458 				break;
459 			case hashCalc("hidettl"):
460 				r.removeLayer(0);
461 				break;
462 			case hashCalc("unhidettl"):
463 				r.addLayer(tt, 0);
464 				break;
465 			default:
466 				break;
467 		}
468 	}
469 	/**
470 	 * Called when an axis is being operated.
471 	 * The `id` should be generated from a string, usually the name of the binding.
472 	 * `code` is a duplicate of the code used for fast lookup of the binding, which also contains other info (deviceID, etc).
473 	 * `timestamp` is the time lapsed since the start of the program, can be used to measure time between keypresses.
474 	 * `value` is the current position of the axis normalized between -1.0 and +1.0 for joysticks, and 0.0 and +1.0 for analog
475 	 * triggers.
476 	 */
477 	public void axisEvent(uint id, BindingCode code, uint timestamp, float value) {
478 
479 	}
480 }