added "Blood!" level set
[mmd.git] / mminer.d
blob97d29d50a1d1db1cc050f648ca71706786b6b992
1 module mminer is aliced;
3 import arsd.simpledisplay;
4 //import arsd.color;
6 import iv.txtser;
7 import iv.vfs.io;
9 import x11gfx;
12 // ////////////////////////////////////////////////////////////////////////// //
13 // willy jump offsets
14 static immutable int[19] willyJ = [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
16 struct SkyLabXY { int x, y; }
17 static immutable SkyLabXY[4][3] skylabCoords = [
18 [{x: 8,y:0}, {x: 72,y:0}, {x:136,y:0}, {x:200,y:0}],
19 [{x:40,y:0}, {x:104,y:0}, {x:168,y:0}, {x:232,y:0}],
20 [{x:24,y:0}, {x: 88,y:0}, {x:152,y:0}, {x:216,y:0}]
24 // ////////////////////////////////////////////////////////////////////////// //
25 // suitable to "unjson"
26 struct Room {
27 enum { Width = 32, Height = 16 }
28 static struct WillyStart { int x; int y; int sd; }
29 static struct ExitGfx { ubyte gfx; int x; int y; }
30 static struct CommonGfx { ubyte gfx; ubyte ink; ubyte paper; }
31 static struct Conveyor { int x; int y; int d; int l; ubyte gfx; ubyte ink; ubyte paper; @SRZIgnore int frame; }
32 static struct KeyPos { int x; int y; int s; }
33 static struct Key { ubyte gfx; KeyPos[] info; }
34 static struct SwitchPos { int x; int y; int s; }
35 static struct Enemy { bool vert; ubyte gfx; ubyte ink; ubyte paper; int x=-1; int y; int min; int max; int d; int s; int flip; int anim; @SRZIgnore int frame; @SRZIgnore int m; @SRZIgnore int falling; @SRZIgnore int delay; }
36 static struct SkyLabEnemy { int p, s; ubyte ink; int max; int m; int frame; }
37 static struct SkyLabXY { int x, y, frame; ubyte ink; int m; int s; int max; int p; }
38 static struct SPGRay { int x, y; }
39 @SRZIgnore int roomIdx;
40 string title;
41 WillyStart willy;
42 ExitGfx exit;
43 int air;
44 ubyte[Height*Width] map; // 16 rows, 32 cols
45 ubyte border, ink, paper;
46 CommonGfx[] platforms;
47 CommonGfx wall;
48 CommonGfx crumb;
49 CommonGfx[] deadlies;
50 Conveyor conveyor;
51 Key keys;
52 SwitchPos[] switches;
53 Enemy[] enemies;
55 @SRZIgnore Enemy eugene;
57 @SRZIgnore int holeLen, holeY;
58 @SRZIgnore Enemy kong;
60 @SRZIgnore SkyLabEnemy[] skylabEnemies;
61 @SRZIgnore SkyLabXY[] skylab;
63 @SRZIgnore SPGRay[] spg; // solar power generator
65 @SRZIgnore {
66 int willyX, willyY, willyDir, willyJump, willyJumpDir;
67 int willyLastMoveDir, willyFall, willyConv, willyStall;
68 bool willyDead;
69 int frameNo;
70 int score;
71 // keys
72 bool kLeft, kRight, kJump, kUp, kDown;
75 void initWilly () {
76 // init willy
77 willyX = willy.x;
78 willyY = willy.y;
79 willyDir = (willy.sd > 0 ? -1 : 1);
80 willyJump = willyFall = willyConv = willyStall = 0;
81 willyDead = false;
82 willyLastMoveDir = 0;
83 // reset keys
84 kLeft = kRight = kJump = kUp = kDown = false;
87 void initRoom () {
88 initWilly();
90 frameNo = 0;
91 foreach (ref c; map) {
92 if (c == 4) c = 8; // 'crumb'
93 else if (c < 1 || c > 7) c = 0; // emptyness
96 // rebuild keys
97 KeyPos[] kinfo;
98 foreach (const ref ki; keys.info) {
99 if (ki.s == 0) continue;
100 kinfo ~= ki;
102 keys.info = kinfo;
104 platforms = platforms.dup;
105 deadlies = deadlies.dup;
106 switches = switches.dup;
107 enemies = enemies.dup;
109 // Eugene?
110 if (roomIdx == 4) {
111 Enemy[] a = [{ x:120, y:1, d/*ir*/:0, min:1, max:87, ink:6 }];
112 eugene = a[0];
114 // Kong?
115 if (roomIdx == 7 || roomIdx == 11) {
116 Enemy[] a = [{ x:120, y:0, max:104, frame:0, ink:2, m:0 }];
117 kong = a[0];
118 holeLen = 2*8;
119 holeY = 0;
121 // SkyLab?
122 if (roomIdx == 13) {
123 enemies = null;
124 SkyLabEnemy[] a = [
125 {p:0, s:4, ink:6, max:72, m:0, frame:0},
126 {p:2, s:3, ink:5, max:56, m:0, frame:0},
127 {p:1, s:1, ink:4, max:32, m:0, frame:0}
129 skylabEnemies = a.dup;
130 foreach (immutable f, const ref se; skylabEnemies) {
131 skylab ~= SkyLabXY(
132 skylabCoords[f][se.p].x, skylabCoords[f][se.p].y,
133 se.frame, se.ink, se.m, se.s, se.max, se.p,
137 // Solar Power Generator?
138 if (roomIdx == 18) buildSPG();
141 // called on each frame
142 void buildSPG (bool forced=false) {
143 if (!forced && roomIdx != 18) {
144 spg = null;
145 return;
148 bool spgCheckMonster (int x, int y) {
149 x *= 8;
150 y *= 8;
151 foreach (const ref cm; enemies) {
152 auto mx = cm.x;
153 auto my = cm.y;
154 if (x+7 >= mx && x < mx+15 && y+7 >= my && y < my+15) return true;
156 return false;
159 int x = 23, y = 0;
160 bool done = false;
161 int dir = 0, idx = 0;
163 if (spg.length) {
164 spg.length = 0;
165 spg.assumeSafeAppend;
168 void addXY (int x, int y) {
169 ++idx;
170 while (spg.length < idx) spg ~= SPGRay(-1, -1);
171 spg[idx-1].x = x;
172 spg[idx-1].y = y;
175 do {
176 ubyte blockhit = map[y*32+x];
177 bool robohit = spgCheckMonster(x, y);
179 if (blockhit && robohit) {
180 addXY(-1, -1);
181 done = true;
182 } else if (!blockhit && robohit) {
183 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
184 spg[idx-1].x = spg[idx-1].y = -1;
185 done = true;
186 } else {
187 addXY(x, y);
189 dir ^= 1;
190 } else if (!blockhit && !robohit) {
191 addXY(x, y);
192 } else if (blockhit && !robohit) {
193 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
194 spg[idx-1].x = spg[idx-1].y = -1;
195 done = true;
197 dir ^= 1;
200 if (!blockhit) {
201 if (!dir) {
202 ++y;
203 blockhit = map[y*32+x];
204 if (y == 15 || blockhit) done = true;
205 } else {
206 --x;
207 blockhit = map[y*32+x];
208 if (x == 0 || blockhit) done = true;
210 } else {
211 if (!dir) { --x; if (!x) done = true; }
212 else { ++y; if (++y == 15) done = true; }
214 } while (!done);
215 addXY(-1, -1);
218 void stepMonsters () {
219 foreach (ref m; enemies) {
220 if (m.x < 0 || m.y < 0) continue;
221 if (m.vert) {
222 auto y = m.y;
223 auto spd = m.s/*peed*/;
224 if (m.d/*ir*/ != 0) {
225 // up
226 y -= spd;
227 if (y < m.min || y < 0) { y += spd; m.d/*ir*/ = 0; }
228 } else {
229 // down
230 y += spd;
231 if (y > m.max) { y -= spd; m.d/*ir*/ = 1; }
233 m.y = y;
234 m.anim = (m.anim+1)&3;
235 } else {
236 auto x = m.x;
237 auto spd = (2>>m.s/*peed*/);
238 if (m.d/*ir*/ != 0) {
239 // left
240 x -= spd;
241 if (x < m.min) { m.d/*ir*/ = 0; x += spd; }
242 } else {
243 // right
244 x += spd;
245 if (x > m.max+6) { m.d/*ir*/ = 1; x -= spd; }
247 m.x = x;
250 // Eugene
251 if (eugene.x >= 0) {
252 if (keys.info.length == 0) {
253 // no keys, Eugene tries to block the exit
254 eugene.ink = (eugene.ink+1)&7;
255 eugene.y += (eugene.y < eugene.max ? 1 : 0);
256 } else {
257 if (eugene.d/*ir*/ != 0) {
258 // up
259 --eugene.y;
260 if (eugene.y < eugene.min) { eugene.d/*ir*/ = 0; ++eugene.y; }
261 } else {
262 // down
263 ++eugene.y;
264 if (eugene.y > eugene.max) { eugene.d/*ir*/ = 1; --eugene.y; }
268 // Kong
269 if (kong.x >= 0) {
270 switch (kong.falling) {
271 case 1: // just started
272 map[2*32+15] = 0;
273 map[2*32+16] = 0;
274 //eraseRect(16, 120, 16, 8);
275 kong.falling = 2;
276 break;
277 case 2:
278 kong.ink = 4;
279 kong.frame += 2;
280 kong.falling = 3;
281 break;
282 case 3:
283 kong.y += 4;
284 if (kong.y >= kong.max) { kong.x = -1; score += 100; }
285 if (!kong.delay) { kong.delay = 4; kong.frame = ((kong.frame-1)&1)+2; } else --kong.delay;
286 break;
287 default:
288 if (!kong.delay) { kong.delay = 8; kong.frame = (kong.frame+1)&1; } else --kong.delay;
289 break;
292 // SkyLab
293 foreach (immutable idx, ref sk; skylab) {
294 switch (sk.m) {
295 case 0:
296 sk.y += sk.s;
297 if (sk.y > sk.max) {
298 sk.y = sk.max;
299 sk.m = 1;
300 ++sk.frame;
302 break;
303 case 1:
304 ++sk.frame;
305 if (sk.frame == 7) sk.m = 2;
306 break;
307 case 2:
308 sk.p = (sk.p+1)&3;
309 sk.x = skylabCoords[idx][sk.p].x;
310 sk.y = skylabCoords[idx][sk.p].y;
311 sk.frame = sk.m = 0;
312 break;
313 default:
318 void stepCrumb () {
319 if (willyY&7) return;
320 for (int f = 0; f <= 8; f += 8) {
321 int x = willyX+f;
322 int y = willyY+16;
323 ubyte b = getBlockAt(x, y);
324 if (b == 4) b = 8;
325 if (b < 8) continue;
326 x >>= 3;
327 y >>= 3;
328 if (++b > 15) b = 0;
329 map[y*32+x] = b;
330 //eraseRect(x*8, y*8, 8, 8);
334 void stepKeys () {
335 while (keys.info.length) {
336 bool again = false;
337 foreach (immutable idx; 0..keys.info.length) {
338 auto ki = &keys.info[idx];
339 assert(ki.s);
340 int kx = ki.x;
341 int ky = ki.y;
342 if (kx+7 >= willyX && kx < willyX+10 && ky+7 >= willyY && ky < willyY+16) {
343 score += 100;
344 foreach (immutable c; idx+1..keys.info.length) keys.info[c-1] = keys.info[c];
345 keys.info.length -= 1;
346 again = true;
347 break;
350 if (!again) break;
354 void stepSwitch () {
355 // hole?
356 if (holeLen > 0 && switches.length && switches[0].s/*tate*/ > 1) {
357 if (holeLen < 0) {
358 //FIXME
359 enemies[1].max += 24;
360 holeLen = 0;
361 } else {
362 ++holeY;
363 //eraseRect(136, 88, 8, 16);
364 //ctx.fillStyle = mainPal[curRoom.paper];
365 //ctx.fillRect(136, 88+16-holeY, 8, holeY);
366 if (holeY == 16) {
367 map[11*32+17] = 0;
368 map[12*32+17] = 0;
369 holeLen = -1;
373 // Kong?
374 if (kong.x >= 0 && !kong.falling && switches.length > 1 && switches[1].s/*tate*/ > 1) kong.falling = 1;
377 void stepWillyActions () {
378 bool doWillyLeft () {
379 if (willyDir > 0) { willyDir = -1; return true; }
380 auto xx = willyX-2;
381 auto b0 = getBlockAt(xx, willyY);
382 auto b1 = getBlockAt(xx, willyY+8);
383 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
384 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
385 willyX -= 2;
386 if (willyX < 0) willyX += 240;
387 willyLastMoveDir = -1;
388 return true;
391 bool doWillyRight () {
392 if (willyDir < 0) { willyDir = 1; return true; }
393 if (willyX > 245) return false;
394 auto xx = willyX+10;
395 auto b0 = getBlockAt(xx, willyY);
396 auto b1 = getBlockAt(xx, willyY+8);
397 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
398 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
399 willyX += 2;
400 if (willyX > 240) willyX -= 240;
401 willyLastMoveDir = 1;
402 return true;
405 void doWillyJump () {
406 if (!willyJump) return;
407 willyY += willyJ[willyJump];
408 auto x = willyX;
409 auto mv = false;
410 if (willyJumpDir < 0) mv = doWillyLeft(); else if (willyJumpDir > 0) mv = doWillyRight();
411 if (willyJump < 9) {
412 willyFall = 0;
413 // up
414 auto b0 = getBlockAt(x, willyY);
415 auto b1 = getBlockAt(x+8, willyY);
416 if (b0 == 3 || b1 == 3) {
417 // headboom! (apstenu %-)
418 willyX = x;
419 willyY -= willyJ[willyJump];
420 willyJump = 0; // enough flying
421 return;
423 } else {
424 // down
425 if (willyJump > 12) willyFall += willyJ[willyJump];
426 if ((willyY&7) == 0) {
427 auto b0 = getBlockAt(willyX, willyY+16);
428 auto b1 = getBlockAt(willyX+8, willyY+16);
429 if (b0 || b1) {
430 if (b0 == 3 || b1 == 3) willyX = x;
431 willyFall = 0; // can't fall too deep while jumping
432 willyJump = 0; // enough flying
433 if (b0 == 7 || b1 == 7) willyStall = 1; // conveyor?
434 return;
438 ++willyJump;
439 if (willyJump > 18) willyJump = 0;
443 if (willyDead) return;
444 checkSwitch();
445 if (isWillyInDeadly()) { willyDead = true; return; }
446 /*if (!DEBUG_COLDET)*/ {
447 if (checkMonsters()) { willyDead = true; return; }
450 auto wasJump = false;
451 if (willyJump) {
452 willyLastMoveDir = 0;
453 doWillyJump();
454 if (willyJump) return;
455 wasJump = true;
458 auto falling = isWillyFalling();
459 if (!kDown && falling) {
460 willyConv = willyStall = willyLastMoveDir = 0;
461 willyFall += 4;
462 willyY += 4;
463 if (willyY > 112) willyY -= 112;
464 return;
467 if (!falling && willyFall > 34) willyDead = true; // too high!
468 auto lfall = willyFall;
469 willyFall = 0;
471 if (willyDead) return;
473 auto dx = (kLeft ? -1 : kRight ? 1 : 0);
474 if (isWillyOnConv()) {
475 auto cdir = (conveyor.d ? 1 : -1);
476 //dx==cdir,!dx,lastmove==cdir
477 if (willyLastMoveDir == cdir || dx == cdir || !dx) { willyConv = cdir; willyStall = 0; } // was moving in conv. dir or standing
478 if (!willyConv) {
479 // Willy just steps on the conveyor, and Willy walking to the opposite side
480 willyStall = 0;
481 if (wasJump && willyLastMoveDir == -cdir) {
482 willyConv = dx; // from jump, can do opposite
483 } else {
484 willyConv = dx;
485 if (lfall > 0 || !willyLastMoveDir) { dx = 0; willyStall = 1; } // lands on conveyor, not from jump
487 } else {
488 // Willy was on conveyor
489 dx = (willyStall ? 0 : willyConv);
491 } else {
492 willyConv = willyStall = 0;
495 //if (willyConv) dx = willyConv;
496 if (kUp && !wasJump) {
497 willyConv = willyStall = willyLastMoveDir = 0;
498 willyJumpDir = dx;
499 willyJump = 1;
500 doWillyJump();
501 return;
503 if (kDown) willyY -= 8;
504 willyLastMoveDir = 0;
505 if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
508 void stepGame () {
509 ++frameNo;
510 conveyor.frame += (conveyor.d ? -1 : 1);
511 if (conveyor.frame < 0) conveyor.frame = 3; else if (conveyor.frame > 3) conveyor.frame = 0;
513 // must erase SPG, Willy and monsters here -- before coords change
515 drawSPG(false);
516 eraseMonsters();
517 eraseRect(willyX&248, willyY, 16, 16);
519 if (!willyJump) stepCrumb();
520 stepMonsters();
521 stepWillyActions();
523 if (DEBUG_COLDET) {
524 if (checkMonsters()) {
525 dbgSingleStep = true; dbgNF = false;
526 dbgColDetected = true;
527 } else dbgColDetected = false;
531 stepKeys();
532 stepSwitch();
533 buildSPG();
536 if (willyDead) {
537 // dead %-(
538 gameRunning = false;
539 if (gameTID) clearInterval(gameTID), gameTID = null;
540 blockPage();
541 message("you are dead!");
542 setTimeout(startRoom, 1000);
543 return;
546 if (checkExit()) {
547 // room complete!
548 gameRunning = false;
549 if (gameTID) clearInterval(gameTID), gameTID = null;
550 blockPage();
551 message(curRoom.title+" complete!");
552 curRoomNo++;
553 if (curRoomNo >= me.lsets[curLSet].rooms.length) {
554 curRoomNo = 0;
555 curLSet++;
556 if (curLSet >= me.lsets.length) curLSet = 0;
558 setTimeout(startRoom, 2000);
559 return;
564 // ////////////////////////////////////////////////////////////////////// //
565 // checkers
567 // x & y: in pixels
568 ubyte getBlockAt (int x, int y, bool simplify=false) const nothrow @nogc {
569 x = x>>3;
570 y = y>>3;
571 if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
572 ubyte b = map[y*32+x];
573 if (simplify) {
574 if (b > 15) b = 0;
575 else if (b > 7) b = 4;
576 else if (b == 6) b = 5;
577 else if (b == 2) b = 1;
579 return b;
582 bool isWillyFalling () const nothrow @nogc {
583 if (willyY&7) return true;
584 for (int dx = 0; dx <= 8; dx += 8) {
585 ubyte b = getBlockAt(willyX+dx, willyY+16, true);
586 if (b > 0 && b != 5) return false;
588 return true;
591 bool isWillyInDeadly () const nothrow @nogc {
592 for (int dx = 0; dx <= 8; dx += 8) {
593 for (int dy = 0; dy <= 16; dy += 8) {
594 if (getBlockAt(willyX+dx, willyY+dy, true) == 5) return true;
597 return false;
600 bool isWillyOnConv () const nothrow @nogc {
601 if (willyY&7) return false;
602 ubyte b0 = getBlockAt(willyX, willyY+16);
603 ubyte b1 = getBlockAt(willyX+8, willyY+16);
604 return (b0 == 7 || b1 == 7);
607 bool checkExit () const nothrow @nogc {
608 if (keys.info.length != 0) return false;
609 auto x = exit.x;
610 auto y = exit.y;
611 return (willyX >= x-2 && willyX+10 <= x+18 && willyY >= y-5 && willyY+16 <= y+22);
615 // pixel-perfect collision detector
616 // fully stolen from Andy's sources %-)
618 bool pixelCheckMonster (int rx, int ry, const(ubyte)[] darr, int dpos=0) const nothrow {
619 static ubyte[256] mpcGrid;
620 assert(dpos >= 0);
621 //int x, y, w;
622 int x, w;
623 rx -= willyX&248;
624 ry -= willyY;
625 if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
626 // clear grid
627 mpcGrid[] = 0;
628 if (rx < 0) { x = 0; rx = -rx; w = 16-rx; } else { x = rx; rx = 0; w = 16-x; }
629 // partial plot monster
630 for (int y = ry+15; y >= ry; --y) {
631 if (y >= 0 && y < 16) {
632 int gp = y*16+x;
633 int rp = dpos+(y-ry)*16+rx;
634 for (int dx = 0; dx < w; ++dx, ++gp, ++rp) mpcGrid[gp] = darr[rp];
637 auto warr = gameData["willy"];
638 int wptr = ((willyX&15)>>1)*256+(willyDir < 0 ? 2048 : 0);
639 // check for collision
640 int mp = 0;
641 for (x = 255; x >= 0; --x, ++wptr, ++mp) if (warr[wptr] && mpcGrid[mp]) return true;
642 return false;
645 bool checkMonsters () const nothrow {
646 foreach (immutable f, const ref m; enemies) {
647 auto cc = monsterOfsCache[f];
648 if (m.x < 0 || m.y < 0) continue;
649 if (m.vert) {
650 if (pixelCheckMonster(m.x, m.y, gameData["vrobo"], cc[m.anim])) return true;
651 } else {
652 if (pixelCheckMonster(m.x&248, m.y, gameData["hrobo"], cc[((m.x&m.anim)&0xfe)+m.d/*ir*/])) return true;
655 // Eugene?
656 if (eugene.x >= 0) {
657 enum curLSet = 0;
658 if (pixelCheckMonster(eugene.x, eugene.y, gameData["eugene"], 256*curLSet)) return true;
660 // Kong?
661 if (kong.x >= 0) {
662 if (pixelCheckMonster(kong.x, kong.y, gameData["kong"], 256*kong.frame)) return true;
664 // SkyLab?
665 if (skylab.length) {
666 foreach (const ref sk; skylab) {
667 if (pixelCheckMonster(sk.x, sk.y, gameData["sky"], 256*sk.frame)) return true;
670 return false;
673 void checkSwitch () nothrow @nogc {
674 foreach (ref ss; switches) {
675 if (ss.s/*tate*/ != 1) continue;
676 auto x = ss.x;
677 auto y = ss.y;
678 if (x+7 >= willyX && y+7 >= willyY && x < willyX+8 && y < willyY+16) ss.s/*tate*/ = 1+1;
682 bool checkSPG () const nothrow @nogc {
683 foreach (const ref sp; spg) {
684 auto x = sp.x*8;
685 auto y = sp.y*8;
686 if (x < 0 || y < 0) break;
687 if (x+7 >= willyX && x < willyX+8 && y+7 >= willyY && y < willyY+16) return true;
689 return false;
693 __gshared Room[] gameRooms;
696 // ////////////////////////////////////////////////////////////////////////// //
697 __gshared ubyte[][string] gameData;
700 // ////////////////////////////////////////////////////////////////////////// //
701 // image builders
702 VColor palcol (ubyte c) {
703 static VColor[256] palette;
704 static bool palset = false;
705 if (!palset) {
706 //palette = gameData["palmain"][];
707 auto pp = gameData["palmain"];
708 foreach (immutable cc; 0..256) {
709 ubyte r = cast(ubyte)(255*pp[cc*3+0]/63);
710 ubyte g = cast(ubyte)(255*pp[cc*3+1]/63);
711 ubyte b = cast(ubyte)(255*pp[cc*3+2]/63);
712 palette[cc] = rgbcol(r, g, b);
714 palset = true;
716 return palette[c];
720 // ////////////////////////////////////////////////////////////////////////// //
721 X11Image buildImageMasked (const(ubyte)[] darr, int w, int h) {
722 assert(w > 0 && h > 0);
723 int dpos = 0;
724 auto img = new X11Image(w, h);
725 foreach (immutable y; 0..h) {
726 foreach (immutable x; 0..w) {
727 ubyte c = darr[dpos++];
728 if (c) {
729 img.setPixel(x, y, palcol(c));
730 } else {
731 img.setPixel(x, y, Transparent);
735 return img;
739 X11Image buildImageMaskedShiny (const(ubyte)[] darr, int brightness, int w, int h) {
740 assert(w > 0 && h > 0);
741 int dpos = 0;
742 auto img = new X11Image(w, h);
743 foreach (immutable y; 0..h) {
744 foreach (immutable x; 0..w) {
745 ubyte c = darr[dpos++];
746 if (c) {
747 int b = (c&15)-brightness;
748 if (b < 0) b = 0; else if (b > 15) b = 15;
749 img.setPixel(x, y, palcol(cast(ubyte)((c&240)|b)));
750 } else {
751 img.setPixel(x, y, Transparent);
755 return img;
759 X11Image buildImageMaskedInk (const(ubyte)[] darr, int ink, int w, int h) {
760 assert(w > 0 && h > 0);
761 int dpos = 0;
762 auto img = new X11Image(w, h);
763 if (ink < 0) ink = 0;
764 ink *= 16;
765 foreach (immutable y; 0..h) {
766 foreach (immutable x; 0..w) {
767 ubyte c = darr[dpos++];
768 if (c) {
769 img.setPixel(x, y, palcol(cast(ubyte)(c+ink)));
770 } else {
771 img.setPixel(x, y, Transparent);
775 return img;
779 // ////////////////////////////////////////////////////////////////////////// //
780 X11Image buildImageBrick (const(ubyte)[] darr, int ink, int paper, int skipy=0) {
781 assert(skipy >= 0);
782 enum { w = 8, h = 8 }
783 int dpos = 0;
784 auto img = new X11Image(w, h);
785 ink *= 16;
786 foreach (immutable y; 0..h) {
787 foreach (immutable x; 0..w) {
788 ubyte c = (y >= skipy ? darr[dpos++] : 0);
789 img.setPixel(x, y, palcol(cast(ubyte)(c ? ink+c : paper)));
792 return img;
796 // ////////////////////////////////////////////////////////////////////////// //
797 X11Image finalSpr;
798 X11Image sunSpr;
799 X11Image[] brickCache;
800 X11Image[] convCache;
801 X11Image[] exitCache;
802 X11Image[] keyCache;
803 X11Image[][] monsterCache;
804 int[][] monsterOfsCache;
805 X11Image[] eugeneSpr;
806 X11Image[] kongSpr;
807 X11Image[] skylabSpr;
808 X11Image[] switchesSpr;
809 X11Image willySpr;
812 // ////////////////////////////////////////////////////////////////////////// //
813 void buildWilly (in ref Room curRoom) {
814 auto img = new X11Image(16*16, 16+16);
815 auto ww = gameData["willy"];
816 foreach (immutable f; 0..16) {
817 foreach (immutable y; 0..16) {
818 foreach (immutable x; 0..16) {
819 ubyte c = ww[f*256+y*16+x];
820 if (!c) {
821 img.setPixel(f*16+x, y, Transparent);
822 } else {
823 img.setPixel(f*16+x, y, palcol(c));
824 // white
825 img.setPixel(f*16+x, y+16, palcol(15));
830 willySpr = img;
834 void buildBrickImages (in ref Room curRoom) {
835 auto buildBrickImage (in ref Room.CommonGfx brk, int skipy=0) {
836 return buildImageBrick(gameData["blocks"][brk.gfx*64..$], brk.ink, curRoom.paper, skipy);
838 brickCache = null;
839 foreach (immutable f; 0..8) {
840 X11Image img;
841 switch (f) {
842 //case 0: case 7: img = buildBrickImage(curRoom.wall, 16); break;
843 case 1: img = buildBrickImage(curRoom.platforms[0]); break;
844 case 2: img = buildBrickImage(curRoom.platforms[1]); break;
845 case 3: img = buildBrickImage(curRoom.wall); break;
846 //case 4: img = buildBrickImage(curRoom.crumb); break;
847 case 5: img = buildBrickImage(curRoom.deadlies[0]); break;
848 case 6: img = buildBrickImage(curRoom.deadlies[1]); break;
849 default:
851 brickCache ~= img;
853 foreach (immutable f; 0..9) brickCache ~= buildBrickImage(curRoom.crumb, f);
857 void buildConvImages (in ref Room curRoom) {
858 convCache = null;
859 auto conv = &curRoom.conveyor;
860 if (conv.y <= 0 || conv.l < 1) return;
861 foreach (immutable f; 0..4) {
862 convCache ~= buildImageBrick(gameData["conv"][conv.gfx*256+f*64..$], conv.ink, curRoom.paper);
867 void buildExitImages (in ref Room curRoom) {
868 exitCache = null;
869 exitCache ~= buildImageMasked(gameData["exits"][curRoom.exit.gfx*256..$], 16, 16);
870 foreach (immutable f; 0..16) exitCache ~= buildImageMaskedShiny(gameData["exits"][curRoom.exit.gfx*256..$], f, 16, 16);
874 void buildKeysImages (in ref Room curRoom) {
875 keyCache = null;
876 foreach (immutable f; 0..16) keyCache ~= buildImageMaskedShiny(gameData["keys"][curRoom.keys.gfx*64..$], f, 8, 8);
880 void buildMonsterImages (in ref Room curRoom) {
881 monsterCache = null;
882 monsterOfsCache = null;
883 //auto l = curRoom.enemies.length;
884 foreach (immutable f, const ref m; curRoom.enemies) {
885 if (m.x < 0 || m.y < 0) {
886 monsterOfsCache ~= null;
887 monsterCache ~= null;
888 continue;
890 X11Image[] r;
891 int[] cc;
892 if (m.vert) {
893 foreach (immutable c; 0..4) {
894 auto n = (m.gfx+c)*256;
895 cc ~= n;
896 r ~= buildImageMaskedInk(gameData["vrobo"][n..$], m.ink-1, 16, 16);
898 } else {
899 foreach (immutable c; 0..(m.anim>>1)+1) {
900 auto n = (m.gfx+c)*256;
901 cc ~= n;
902 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
903 n += m.flip*256;
904 cc ~= n;
905 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
908 monsterOfsCache ~= cc;
909 monsterCache ~= r;
914 void buildEugeneImages (in ref Room curRoom) {
915 eugeneSpr = null;
916 for (int f = 0; f <= 256; f += 256) {
917 foreach (immutable c; 0..8) {
918 eugeneSpr ~= buildImageMaskedInk(gameData["eugene"][f..$], c, 16, 16);
924 void buildKongImages (in ref Room curRoom) {
925 kongSpr = null;
926 foreach (immutable f; 0..4) {
927 foreach (immutable c; 0..8) {
928 kongSpr ~= buildImageMaskedInk(gameData["kong"][f*256..$], c, 16, 16);
934 void buildSkyLabImages (in ref Room curRoom) {
935 skylabSpr = null;
936 foreach (immutable f; 0..8) {
937 foreach (immutable c; 0..8) {
938 skylabSpr ~= buildImageMaskedInk(gameData["sky"][f*256..$], c, 16, 16);
944 void buildSwitchImages (in ref Room curRoom) {
945 switchesSpr = null;
946 foreach (immutable f; 0..2) {
947 switchesSpr ~= buildImageBrick(gameData["switches"][f*64..$], curRoom.platforms[1].ink, curRoom.paper);
952 // ////////////////////////////////////////////////////////////////////////// //
953 void blitTo (X11Image src, X11Image dst, int x, int y, int repx=1) {
954 foreach (immutable r; 0..repx) {
955 foreach (immutable dy; 0..src.height) {
956 foreach (immutable dx; 0..src.width) {
957 int xx = x+dx;
958 int yy = y+dy;
959 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
960 auto c = src.getPixel(dx, dy);
961 if (isTransparent(c)) continue;
962 dst.setPixel(xx, yy, c);
965 x += src.width;
970 void blitTo (X11Image src, int x0, int y0, int ww, int hh, X11Image dst, int x, int y) {
971 foreach (immutable dy; 0..ww) {
972 foreach (immutable dx; 0..hh) {
973 int xx = x+dx;
974 int yy = y+dy;
975 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
976 auto c = src.getPixel(x0+dx, y0+dy);
977 if (isTransparent(c)) continue;
978 dst.setPixel(xx, yy, c);
984 X11Image drawRoom (in ref Room curRoom, X11Image img=null) {
985 if (img is null) img = new X11Image(8*Room.Width, 8*Room.Height);
986 int pos = 0;
987 foreach (immutable y; 0..img.height) {
988 foreach (immutable x; 0..img.width) {
989 img.setPixel(x, y, palcol(curRoom.paper));
992 foreach (immutable y; 0..Room.Height) {
993 foreach (immutable x; 0..Room.Width) {
994 ubyte blk = curRoom.map[pos++];
995 if (blk < 1 || blk == 7) continue;
996 if (blk == 4) blk = 8;
997 brickCache[blk].blitTo(img, x*8, y*8);
1000 if (curRoom.roomIdx == 19) {
1001 finalSpr.blitTo(img, 0, 0);
1002 sunSpr.blitTo(img, 60, 32);
1005 void drawConveyor () {
1006 auto conv = &curRoom.conveyor;
1007 if (conv.l < 1) return;
1008 auto y = conv.y;
1009 if (y <= 0) return;
1010 convCache[conv.frame].blitTo(img, conv.x, conv.y, conv.l);
1013 void drawExit () {
1014 int br;
1015 if (curRoom.keys.info.length != 0) {
1016 br = 0;
1017 } else {
1018 br = curRoom.frameNo&31;
1019 if (br > 15) br = 31-br;
1020 ++br;
1022 exitCache[br].blitTo(img, curRoom.exit.x, curRoom.exit.y);
1025 void drawKeys () {
1026 auto ff = curRoom.frameNo%16;
1027 if (ff >= 8) ff = 15-ff;
1028 auto keyimg = keyCache[ff];
1029 foreach_reverse (const ref ki; curRoom.keys.info) {
1030 if (ki.x < 0 || ki.y < 0) continue;
1031 //eraseRect(ki.x, ki.y, 8, 8);
1032 if (!ki.s) continue;
1033 keyimg.blitTo(img, ki.x&248, ki.y);
1037 void drawMonsters () {
1038 foreach (immutable f, const ref m; curRoom.enemies) {
1039 if (m.x < 0 || m.y < 0) continue;
1040 auto slist = monsterCache[f];
1041 auto x = m.x;
1042 if (m.vert) {
1043 //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
1044 slist[m.anim].blitTo(img, x, m.y);
1045 } else {
1046 auto sidx = ((x&m.anim)&0xfe)+m.d/*ir*/;
1047 if (sidx < slist.length) {
1048 slist[sidx].blitTo(img, x&248, m.y);
1049 } else {
1050 writeln("monster #", f, " is fucked: sidx=", sidx, "; max=", slist.length);
1054 if (curRoom.eugene.x >= 0) {
1055 enum curLSet = 0;
1056 eugeneSpr[curLSet*8+curRoom.eugene.ink].blitTo(img, curRoom.eugene.x, curRoom.eugene.y);
1058 if (curRoom.kong.x >= 0) {
1059 kongSpr[curRoom.kong.frame*8+curRoom.kong.ink].blitTo(img, curRoom.kong.x, curRoom.kong.y);
1061 if (curRoom.skylab.length) {
1062 foreach (const ref sk; curRoom.skylab) {
1063 skylabSpr[sk.frame*8+sk.ink].blitTo(img, sk.x, sk.y);
1068 void drawSwitch () {
1069 foreach (const ref ss; curRoom.switches) {
1070 if (ss.s == 0) continue;
1071 switchesSpr[ss.s-1].blitTo(img, ss.x, ss.y);
1075 void drawSPG () {
1076 foreach (const ref sp; curRoom.spg) {
1077 auto x = sp.x*8;
1078 auto y = sp.y*8;
1079 if (x < 0 || y < 0) break;
1080 //ctx.fillStyle = mainPal[noerase?6:curRoom.paper];
1081 //ctx.fillRect(x, y, 8, 8);
1082 foreach (immutable dy; 0..8) {
1083 foreach (immutable dx; 0..8) {
1084 img.setPixel(x+dx, y+dy, palcol(6));
1090 void drawWilly () {
1091 auto willyPos = (curRoom.willyX&15)>>1;
1092 if (curRoom.willyDir < 0) willyPos += 8;
1093 auto wy = 0;
1094 //if (DEBUG_COLDET && dbgColDetected) wy = 16;
1095 willySpr.blitTo(willyPos*16, wy, 16, 16, img, curRoom.willyX&248, curRoom.willyY);
1098 drawConveyor();
1099 drawKeys();
1100 drawMonsters();
1101 drawSwitch();
1102 if (curRoom.keys.info.length) drawExit();
1103 drawWilly();
1104 if (!curRoom.keys.info.length) drawExit();
1105 drawSPG();
1107 return img;
1111 // ////////////////////////////////////////////////////////////////////////// //
1112 void main (string[] args) {
1113 gameRooms.txtunser(VFile("levelset0.js"));
1114 foreach (immutable idx, ref room; gameRooms) room.roomIdx = cast(int)idx;
1115 gameData.txtunser(VFile("data.js"));
1117 finalSpr = buildImageMasked(gameData["final"], 256, 64);
1118 sunSpr = buildImageMasked(gameData["sun"], 24, 16);
1121 Room curRoom;
1123 void loadRoom (int idx) {
1124 curRoom = gameRooms[idx];
1125 curRoom.initRoom();
1126 buildWilly(curRoom);
1127 buildBrickImages(curRoom);
1128 buildConvImages(curRoom);
1129 buildExitImages(curRoom);
1130 buildKeysImages(curRoom);
1131 buildMonsterImages(curRoom);
1132 buildEugeneImages(curRoom);
1133 buildKongImages(curRoom);
1134 buildSkyLabImages(curRoom);
1135 buildSwitchImages(curRoom);
1137 loadRoom(0);
1140 initVBuf();
1141 auto sdwin = new SimpleWindow(vbufW, vbufH, "Manic Miner", OpenGlOptions.no, Resizablity.fixedSize);
1143 bool doQuit;
1144 Image vbimg = new Image(vbufW, vbufH);
1146 X11Image roomImg;
1148 sdwin.eventLoop(1000/20,
1149 // timer
1150 delegate () {
1151 if (sdwin.closed) return;
1153 if (!curRoom.willyDead) {
1154 curRoom.stepGame();
1155 if (!curRoom.willyDead && curRoom.checkExit()) {
1156 if (gameRooms.length-curRoom.roomIdx > 1) {
1157 loadRoom(curRoom.roomIdx+1);
1158 curRoom.willyDead = true;
1162 //clear(rgbcol(255, 127, 0));
1163 clear(0);
1165 roomImg = drawRoom(curRoom, roomImg);
1166 roomImg.blit2xTV(0, 0);
1168 //map.drawMap();
1169 drawStr(10, vbufH-10, curRoom.title, rgbcol(255, 255, 0));
1171 realizeVBuf(vbimg);
1173 auto painter = sdwin.draw();
1174 painter.drawImage(Point(0, 0), vbimg);
1175 //map.drawMap(painter, player);
1178 // keyboard
1179 delegate (KeyEvent evt) {
1180 switch (evt.key) {
1181 case Key.Left: curRoom.kLeft = evt.pressed; break;
1182 case Key.Right: curRoom.kRight = evt.pressed; break;
1183 case Key.Up: curRoom.kUp = evt.pressed; break;
1184 case Key.Down: curRoom.kDown = evt.pressed; break;
1185 case Key.Ctrl: curRoom.kJump = evt.pressed; break;
1186 case Key.R:
1187 if (evt.pressed && curRoom.willyDead) {
1188 loadRoom(curRoom.roomIdx);
1190 break;
1191 case Key.Q: case Key.Escape: doQuit = true; break;
1192 case Key.Minus:
1193 if (!evt.pressed) break;
1194 if (curRoom.roomIdx > 0) {
1195 loadRoom(curRoom.roomIdx-1);
1197 break;
1198 case Key.Plus:
1199 if (!evt.pressed) break;
1200 if (gameRooms.length-curRoom.roomIdx > 1) {
1201 loadRoom(curRoom.roomIdx+1);
1203 break;
1204 default: break;
1206 if (doQuit) { sdwin.close(); return; }
1208 // mouse
1209 delegate (MouseEvent evt) {
1211 // char
1212 delegate (dchar ch) {
1215 flushGui();
1216 delete vbimg;
1217 flushGui();