engine moved to separate module
[mmd.git] / engine.d
blobc8c0075151e2b007268f55cd51443080014257da
1 module engine is aliced;
3 import iv.txtser;
4 import iv.vfs.io;
6 import x11gfx;
8 __gshared bool debugColdet = false;
9 __gshared bool singleStep = false;
10 __gshared bool singleStepWait = false;
13 // ////////////////////////////////////////////////////////////////////////// //
14 __gshared ubyte[][string] gameData;
15 __gshared Room[] gameRooms;
18 public void loadGameData () {
19 gameRooms.txtunser(VFile("levelset0.js"));
20 foreach (immutable idx, ref room; gameRooms) room.roomIdx = cast(int)idx;
21 gameData.txtunser(VFile("data.js"));
25 // ////////////////////////////////////////////////////////////////////////// //
26 // image builders
27 VColor palcol (ubyte c) {
28 static VColor[256] palette;
29 static bool palset = false;
30 if (!palset) {
31 //palette = gameData["palmain"][];
32 auto pp = gameData["palmain"];
33 foreach (immutable cc; 0..256) {
34 ubyte r = cast(ubyte)(255*pp[cc*3+0]/63);
35 ubyte g = cast(ubyte)(255*pp[cc*3+1]/63);
36 ubyte b = cast(ubyte)(255*pp[cc*3+2]/63);
37 palette[cc] = rgbcol(r, g, b);
39 palset = true;
41 return palette[c];
45 // ////////////////////////////////////////////////////////////////////////// //
46 private X11Image buildImageMasked (const(ubyte)[] darr, int w, int h) {
47 assert(w > 0 && h > 0);
48 int dpos = 0;
49 auto img = new X11Image(w, h);
50 foreach (immutable y; 0..h) {
51 foreach (immutable x; 0..w) {
52 ubyte c = darr[dpos++];
53 if (c) {
54 img.setPixel(x, y, palcol(c));
55 } else {
56 img.setPixel(x, y, Transparent);
60 return img;
64 private X11Image buildImageMaskedShiny (const(ubyte)[] darr, int brightness, int w, int h) {
65 assert(w > 0 && h > 0);
66 int dpos = 0;
67 auto img = new X11Image(w, h);
68 foreach (immutable y; 0..h) {
69 foreach (immutable x; 0..w) {
70 ubyte c = darr[dpos++];
71 if (c) {
72 int b = (c&15)-brightness;
73 if (b < 0) b = 0; else if (b > 15) b = 15;
74 img.setPixel(x, y, palcol(cast(ubyte)((c&240)|b)));
75 } else {
76 img.setPixel(x, y, Transparent);
80 return img;
84 private X11Image buildImageMaskedInk (const(ubyte)[] darr, int ink, int w, int h) {
85 assert(w > 0 && h > 0);
86 int dpos = 0;
87 auto img = new X11Image(w, h);
88 if (ink < 0) ink = 0;
89 ink *= 16;
90 foreach (immutable y; 0..h) {
91 foreach (immutable x; 0..w) {
92 ubyte c = darr[dpos++];
93 if (c) {
94 img.setPixel(x, y, palcol(cast(ubyte)(c+ink)));
95 } else {
96 img.setPixel(x, y, Transparent);
100 return img;
104 // ////////////////////////////////////////////////////////////////////////// //
105 private X11Image buildImageBrick (const(ubyte)[] darr, int ink, int paper, int skipy=0) {
106 assert(skipy >= 0);
107 enum { w = 8, h = 8 }
108 int dpos = 0;
109 auto img = new X11Image(w, h);
110 ink *= 16;
111 foreach (immutable y; 0..h) {
112 foreach (immutable x; 0..w) {
113 ubyte c = (y >= skipy ? darr[dpos++] : 0);
114 img.setPixel(x, y, palcol(cast(ubyte)(c ? ink+c : paper)));
117 return img;
121 // ////////////////////////////////////////////////////////////////////////// //
122 // willy jump offsets
123 private:
124 static immutable int[19] willyJ = [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
126 struct SkyLabXY { int x, y; }
127 static immutable SkyLabXY[4][3] skylabCoords = [
128 [{x: 8,y:0}, {x: 72,y:0}, {x:136,y:0}, {x:200,y:0}],
129 [{x:40,y:0}, {x:104,y:0}, {x:168,y:0}, {x:232,y:0}],
130 [{x:24,y:0}, {x: 88,y:0}, {x:152,y:0}, {x:216,y:0}]
134 // ////////////////////////////////////////////////////////////////////////// //
135 // suitable to "unjson"
136 public struct Room {
137 enum { Width = 32, Height = 16 }
138 static struct WillyStart { int x; int y; int sd; }
139 static struct ExitGfx { ubyte gfx; int x; int y; }
140 static struct CommonGfx { ubyte gfx; ubyte ink; ubyte paper; }
141 static struct Conveyor { int x; int y; int d; int l; ubyte gfx; ubyte ink; ubyte paper; @SRZIgnore int frame; }
142 static struct KeyPos { int x; int y; int s; }
143 static struct Key { ubyte gfx; KeyPos[] info; }
144 static struct SwitchPos { int x; int y; int s; }
145 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; }
146 static struct SkyLabEnemy { int p, s; ubyte ink; int max; int m; int frame; }
147 static struct SkyLabXY { int x, y, frame; ubyte ink; int m; int s; int max; int p; }
148 static struct SPGRay { int x, y; }
149 @SRZIgnore int roomIdx;
150 string title;
151 WillyStart willy;
152 ExitGfx exit;
153 int air;
154 ubyte[Height*Width] map; // 16 rows, 32 cols
155 ubyte border, ink, paper;
156 CommonGfx[] platforms;
157 CommonGfx wall;
158 CommonGfx crumb;
159 CommonGfx[] deadlies;
160 Conveyor conveyor;
161 Key keys;
162 SwitchPos[] switches;
163 Enemy[] enemies;
165 @SRZIgnore Enemy eugene;
167 @SRZIgnore int holeLen, holeY;
168 @SRZIgnore Enemy kong;
170 @SRZIgnore SkyLabEnemy[] skylabEnemies;
171 @SRZIgnore SkyLabXY[] skylab;
173 @SRZIgnore SPGRay[] spg; // solar power generator
175 @SRZIgnore private {
176 int willyX, willyY, willyDir, willyJump, willyJumpDir;
177 int willyLastMoveDir, willyFall, willyConv, willyStall;
178 public bool willyDead;
179 int frameNo;
180 public int score;
181 // keys
182 bool kLeft, kRight, kJump, kUp, kDown;
183 bool kLeftDown, kRightDown, kJumpDown, kUpDown, kDownDown;
186 @SRZIgnore private {
187 X11Image finalSpr;
188 X11Image sunSpr;
189 X11Image[] brickCache;
190 X11Image[] convCache;
191 X11Image[] exitCache;
192 X11Image[] keyCache;
193 X11Image[][] monsterCache;
194 int[][] monsterOfsCache;
195 X11Image[] eugeneSpr;
196 X11Image[] kongSpr;
197 X11Image[] skylabSpr;
198 X11Image[] switchesSpr;
199 X11Image willySpr;
203 void initRoom () {
204 void initWilly () {
205 willyX = willy.x;
206 willyY = willy.y;
207 willyDir = (willy.sd > 0 ? -1 : 1);
208 willyJump = willyFall = willyConv = willyStall = 0;
209 willyDead = false;
210 willyLastMoveDir = 0;
211 kLeft = kRight = kJump = kUp = kDown = false;
212 kLeftDown = kRightDown = kJumpDown = kUpDown = kDownDown = false;
215 buildWilly();
216 buildBrickImages();
217 buildConvImages();
218 buildExitImages();
219 buildKeysImages();
220 buildMonsterImages();
221 buildEugeneImages();
222 buildKongImages();
223 buildSkyLabImages();
224 buildSwitchImages();
225 finalSpr = buildImageMasked(gameData["final"], 256, 64);
226 sunSpr = buildImageMasked(gameData["sun"], 24, 16);
228 initWilly();
230 frameNo = 0;
231 foreach (ref c; map) {
232 if (c == 4) c = 8; // 'crumb'
233 else if (c < 1 || c > 7) c = 0; // emptyness
236 // rebuild keys
237 KeyPos[] kinfo;
238 foreach (const ref ki; keys.info) {
239 if (ki.s == 0) continue;
240 kinfo ~= ki;
242 keys.info = kinfo;
244 platforms = platforms.dup;
245 deadlies = deadlies.dup;
246 switches = switches.dup;
247 enemies = enemies.dup;
249 // Eugene?
250 if (roomIdx == 4) {
251 Enemy[] a = [{ x:120, y:1, d/*ir*/:0, min:1, max:87, ink:6 }];
252 eugene = a[0];
254 // Kong?
255 if (roomIdx == 7 || roomIdx == 11) {
256 Enemy[] a = [{ x:120, y:0, max:104, frame:0, ink:2, m:0 }];
257 kong = a[0];
258 holeLen = 2*8;
259 holeY = 0;
261 // SkyLab?
262 if (roomIdx == 13) {
263 enemies = null;
264 SkyLabEnemy[] a = [
265 {p:0, s:4, ink:6, max:72, m:0, frame:0},
266 {p:2, s:3, ink:5, max:56, m:0, frame:0},
267 {p:1, s:1, ink:4, max:32, m:0, frame:0}
269 skylabEnemies = a.dup;
270 foreach (immutable f, const ref se; skylabEnemies) {
271 skylab ~= SkyLabXY(
272 skylabCoords[f][se.p].x, skylabCoords[f][se.p].y,
273 se.frame, se.ink, se.m, se.s, se.max, se.p,
277 // Solar Power Generator?
278 if (roomIdx == 18) buildSPG();
281 void keyLeft (bool pressed) { if (pressed) kLeft = true; kLeftDown = pressed; }
282 void keyRight (bool pressed) { if (pressed) kRight = true; kRightDown = pressed; }
283 void keyUp (bool pressed) { if (pressed) kUp = true; kUpDown = pressed; }
284 void keyDown (bool pressed) { if (pressed) kDown = true; kDownDown = pressed; }
285 void keyJump (bool pressed) { if (pressed) kJump = true; kJumpDown = pressed; }
287 void stepGame () {
288 kLeft = kLeft||kLeftDown;
289 kRight = kRight||kRightDown;
290 kUp = kUp||kUpDown;
291 kDown = kDown||kDownDown;
292 kJump = kJump||kJumpDown;
293 scope(exit) kLeft = kRight = kJump = kUp = kDown = false;
294 ++frameNo;
295 conveyor.frame += (conveyor.d ? -1 : 1);
296 if (conveyor.frame < 0) conveyor.frame = 3; else if (conveyor.frame > 3) conveyor.frame = 0;
298 if (!willyJump) stepCrumb();
299 stepMonsters();
300 stepWillyActions();
301 stepKeys();
302 stepSwitch();
303 buildSPG();
306 void cheatRemoveKeys () { keys.info = null; }
308 // ////////////////////////////////////////////////////////////////////// //
309 // checkers
311 // x & y: in pixels
312 ubyte getBlockAt (int x, int y, bool simplify=false) const nothrow @nogc {
313 x = x>>3;
314 y = y>>3;
315 if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
316 ubyte b = map[y*32+x];
317 if (simplify) {
318 if (b > 15) b = 0;
319 else if (b > 7) b = 4;
320 else if (b == 6) b = 5;
321 else if (b == 2) b = 1;
323 return b;
326 bool checkExit () const nothrow @nogc {
327 if (keys.info.length != 0) return false;
328 auto x = exit.x;
329 auto y = exit.y;
330 return (willyX >= x-2 && willyX+10 <= x+18 && willyY >= y-5 && willyY+16 <= y+22);
333 bool checkMonsters () const nothrow {
334 foreach (immutable f, const ref m; enemies) {
335 auto cc = monsterOfsCache[f];
336 if (m.x < 0 || m.y < 0) continue;
337 if (m.vert) {
338 if (pixelCheckMonster(m.x, m.y, gameData["vrobo"], cc[m.anim])) return true;
339 } else {
340 if (pixelCheckMonster(m.x&248, m.y, gameData["hrobo"], cc[((m.x&m.anim)&0xfe)+m.d/*ir*/])) return true;
343 // Eugene?
344 if (eugene.x >= 0) {
345 enum curLSet = 0;
346 if (pixelCheckMonster(eugene.x, eugene.y, gameData["eugene"], 256*curLSet)) return true;
348 // Kong?
349 if (kong.x >= 0) {
350 if (pixelCheckMonster(kong.x, kong.y, gameData["kong"], 256*kong.frame)) return true;
352 // SkyLab?
353 if (skylab.length) {
354 foreach (const ref sk; skylab) {
355 if (pixelCheckMonster(sk.x, sk.y, gameData["sky"], 256*sk.frame)) return true;
358 return false;
361 void checkSwitch () nothrow @nogc {
362 foreach (ref ss; switches) {
363 if (ss.s/*tate*/ != 1) continue;
364 auto x = ss.x;
365 auto y = ss.y;
366 if (x+7 >= willyX && y+7 >= willyY && x < willyX+8 && y < willyY+16) ss.s/*tate*/ = 1+1;
370 bool checkSPG () const nothrow @nogc {
371 foreach (const ref sp; spg) {
372 auto x = sp.x*8;
373 auto y = sp.y*8;
374 if (x < 0 || y < 0) break;
375 if (x+7 >= willyX && x < willyX+8 && y+7 >= willyY && y < willyY+16) return true;
377 return false;
380 X11Image draw (X11Image img=null) {
381 if (img is null) img = new X11Image(8*Room.Width, 8*Room.Height);
382 int pos = 0;
383 foreach (immutable y; 0..img.height) {
384 foreach (immutable x; 0..img.width) {
385 img.setPixel(x, y, palcol(this.paper));
388 foreach (immutable y; 0..Room.Height) {
389 foreach (immutable x; 0..Room.Width) {
390 ubyte blk = this.map[pos++];
391 if (blk < 1 || blk == 7) continue;
392 if (blk == 4) blk = 8;
393 if (blk < brickCache.length && brickCache[blk] !is null) {
394 brickCache[blk].blitTo(img, x*8, y*8);
398 if (this.roomIdx == 19) {
399 finalSpr.blitTo(img, 0, 0);
400 sunSpr.blitTo(img, 60, 32);
403 void drawConveyor () {
404 auto conv = &this.conveyor;
405 if (conv.l < 1) return;
406 auto y = conv.y;
407 if (y <= 0) return;
408 convCache[conv.frame].blitTo(img, conv.x, conv.y, conv.l);
411 void drawExit () {
412 int br;
413 if (this.keys.info.length != 0) {
414 br = 0;
415 } else {
416 br = this.frameNo&31;
417 if (br > 15) br = 31-br;
418 ++br;
420 exitCache[br].blitTo(img, this.exit.x, this.exit.y);
423 void drawKeys () {
424 auto ff = this.frameNo%16;
425 if (ff >= 8) ff = 15-ff;
426 auto keyimg = keyCache[ff];
427 foreach_reverse (const ref ki; this.keys.info) {
428 if (ki.x < 0 || ki.y < 0) continue;
429 //eraseRect(ki.x, ki.y, 8, 8);
430 if (!ki.s) continue;
431 keyimg.blitTo(img, ki.x&248, ki.y);
435 void drawMonsters () {
436 foreach (immutable f, const ref m; this.enemies) {
437 if (m.x < 0 || m.y < 0) continue;
438 auto slist = monsterCache[f];
439 auto x = m.x;
440 if (m.vert) {
441 //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
442 slist[m.anim].blitTo(img, x, m.y);
443 } else {
444 auto sidx = ((x&m.anim)&0xfe)+m.d/*ir*/;
445 if (sidx < slist.length) {
446 slist[sidx].blitTo(img, x&248, m.y);
447 } else {
448 writeln("monster #", f, " is fucked: sidx=", sidx, "; max=", slist.length);
452 if (this.eugene.x >= 0) {
453 enum curLSet = 0;
454 eugeneSpr[curLSet*8+this.eugene.ink].blitTo(img, this.eugene.x, this.eugene.y);
456 if (this.kong.x >= 0) {
457 kongSpr[this.kong.frame*8+this.kong.ink].blitTo(img, this.kong.x, this.kong.y);
459 if (this.skylab.length) {
460 foreach (const ref sk; this.skylab) {
461 skylabSpr[sk.frame*8+sk.ink].blitTo(img, sk.x, sk.y);
466 void drawSwitch () {
467 foreach (const ref ss; this.switches) {
468 if (ss.s == 0) continue;
469 switchesSpr[ss.s-1].blitTo(img, ss.x, ss.y);
473 void drawSPG () {
474 foreach (const ref sp; this.spg) {
475 auto x = sp.x*8;
476 auto y = sp.y*8;
477 if (x < 0 || y < 0) break;
478 //ctx.fillStyle = mainPal[noerase?6:this.paper];
479 //ctx.fillRect(x, y, 8, 8);
480 foreach (immutable dy; 0..8) {
481 foreach (immutable dx; 0..8) {
482 img.setPixel(x+dx, y+dy, palcol(6));
488 void drawWilly () {
489 auto willyPos = (this.willyX&15)>>1;
490 if (this.willyDir < 0) willyPos += 8;
491 if (debugColdet) {
492 auto wy = 0;
493 if (this.checkMonsters()) wy = 16;
494 willySpr.blitTo(willyPos*16, wy, 16, 16, img, this.willyX&248, this.willyY);
495 } else {
496 willySpr.blitTo(willyPos*16, 0, 16, 16, img, this.willyX&248, this.willyY);
500 drawConveyor();
501 drawKeys();
502 drawMonsters();
503 drawSwitch();
504 drawWilly();
505 drawExit();
506 drawSPG();
508 return img;
511 private:
512 // ////////////////////////////////////////////////////////////////////// //
513 // pixel-perfect collision detector
514 // fully stolen from Andy's sources %-)
515 bool pixelCheckMonster (int rx, int ry, const(ubyte)[] darr, int dpos=0) const nothrow {
516 static ubyte[256] mpcGrid;
517 assert(dpos >= 0);
518 //int x, y, w;
519 int x, w;
520 rx -= willyX&248;
521 ry -= willyY;
522 if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
523 // clear grid
524 mpcGrid[] = 0;
525 if (rx < 0) { x = 0; rx = -rx; w = 16-rx; } else { x = rx; rx = 0; w = 16-x; }
526 // partial plot monster
527 for (int y = ry+15; y >= ry; --y) {
528 if (y >= 0 && y < 16) {
529 int gp = y*16+x;
530 int rp = dpos+(y-ry)*16+rx;
531 for (int dx = 0; dx < w; ++dx, ++gp, ++rp) mpcGrid[gp] = darr[rp];
534 auto warr = gameData["willy"];
535 int wptr = ((willyX&15)>>1)*256+(willyDir < 0 ? 2048 : 0);
536 // check for collision
537 int mp = 0;
538 for (x = 255; x >= 0; --x, ++wptr, ++mp) if (warr[wptr] && mpcGrid[mp]) return true;
539 return false;
542 // ////////////////////////////////////////////////////////////////////// //
543 bool isWillyFalling () const nothrow @nogc {
544 if (willyY&7) return true;
545 for (int dx = 0; dx <= 8; dx += 8) {
546 ubyte b = getBlockAt(willyX+dx, willyY+16, true);
547 if (b > 0 && b != 5) return false;
549 return true;
552 bool isWillyInDeadly () const nothrow @nogc {
553 for (int dx = 0; dx <= 8; dx += 8) {
554 for (int dy = 0; dy <= 16; dy += 8) {
555 if (getBlockAt(willyX+dx, willyY+dy, true) == 5) return true;
558 return false;
561 bool isWillyOnConv () const nothrow @nogc {
562 if (willyY&7) return false;
563 ubyte b0 = getBlockAt(willyX, willyY+16);
564 ubyte b1 = getBlockAt(willyX+8, willyY+16);
565 return (b0 == 7 || b1 == 7);
568 // ////////////////////////////////////////////////////////////////////// //
569 // called on each frame
570 void buildSPG (bool forced=false) {
571 if (!forced && roomIdx != 18) {
572 spg = null;
573 return;
576 bool spgCheckMonster (int x, int y) {
577 x *= 8;
578 y *= 8;
579 foreach (const ref cm; enemies) {
580 auto mx = cm.x;
581 auto my = cm.y;
582 if (x+7 >= mx && x < mx+15 && y+7 >= my && y < my+15) return true;
584 return false;
587 int x = 23, y = 0;
588 bool done = false;
589 int dir = 0, idx = 0;
591 if (spg.length) {
592 spg.length = 0;
593 spg.assumeSafeAppend;
596 void addXY (int x, int y) {
597 ++idx;
598 while (spg.length < idx) spg ~= SPGRay(-1, -1);
599 spg[idx-1].x = x;
600 spg[idx-1].y = y;
603 do {
604 ubyte blockhit = map[y*32+x];
605 bool robohit = spgCheckMonster(x, y);
607 if (blockhit && robohit) {
608 addXY(-1, -1);
609 done = true;
610 } else if (!blockhit && robohit) {
611 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
612 spg[idx-1].x = spg[idx-1].y = -1;
613 done = true;
614 } else {
615 addXY(x, y);
617 dir ^= 1;
618 } else if (!blockhit && !robohit) {
619 addXY(x, y);
620 } else if (blockhit && !robohit) {
621 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
622 spg[idx-1].x = spg[idx-1].y = -1;
623 done = true;
625 dir ^= 1;
628 if (!blockhit) {
629 if (!dir) {
630 ++y;
631 blockhit = map[y*32+x];
632 if (y == 15 || blockhit) done = true;
633 } else {
634 --x;
635 blockhit = map[y*32+x];
636 if (x == 0 || blockhit) done = true;
638 } else {
639 if (!dir) { --x; if (!x) done = true; }
640 else { ++y; if (++y == 15) done = true; }
642 } while (!done);
643 addXY(-1, -1);
646 void stepMonsters () {
647 foreach (ref m; enemies) {
648 if (m.x < 0 || m.y < 0) continue;
649 if (m.vert) {
650 auto y = m.y;
651 auto spd = m.s/*peed*/;
652 if (m.d/*ir*/ != 0) {
653 // up
654 y -= spd;
655 if (y < m.min || y < 0) { y += spd; m.d/*ir*/ = 0; }
656 } else {
657 // down
658 y += spd;
659 if (y > m.max) { y -= spd; m.d/*ir*/ = 1; }
661 m.y = y;
662 m.anim = (m.anim+1)&3;
663 } else {
664 auto x = m.x;
665 auto spd = (2>>m.s/*peed*/);
666 if (m.d/*ir*/ != 0) {
667 // left
668 x -= spd;
669 if (x < m.min) { m.d/*ir*/ = 0; x += spd; }
670 } else {
671 // right
672 x += spd;
673 if (x > m.max+6) { m.d/*ir*/ = 1; x -= spd; }
675 m.x = x;
678 // Eugene
679 if (eugene.x >= 0) {
680 if (keys.info.length == 0) {
681 // no keys, Eugene tries to block the exit
682 eugene.ink = (eugene.ink+1)&7;
683 eugene.y += (eugene.y < eugene.max ? 1 : 0);
684 } else {
685 if (eugene.d/*ir*/ != 0) {
686 // up
687 --eugene.y;
688 if (eugene.y < eugene.min) { eugene.d/*ir*/ = 0; ++eugene.y; }
689 } else {
690 // down
691 ++eugene.y;
692 if (eugene.y > eugene.max) { eugene.d/*ir*/ = 1; --eugene.y; }
696 // Kong
697 if (kong.x >= 0) {
698 switch (kong.falling) {
699 case 1: // just started
700 map[2*32+15] = 0;
701 map[2*32+16] = 0;
702 //eraseRect(16, 120, 16, 8);
703 kong.falling = 2;
704 break;
705 case 2:
706 kong.ink = 4;
707 kong.frame += 2;
708 kong.falling = 3;
709 break;
710 case 3:
711 kong.y += 4;
712 if (kong.y >= kong.max) { kong.x = -1; score += 100; }
713 if (!kong.delay) { kong.delay = 4; kong.frame = ((kong.frame-1)&1)+2; } else --kong.delay;
714 break;
715 default:
716 if (!kong.delay) { kong.delay = 8; kong.frame = (kong.frame+1)&1; } else --kong.delay;
717 break;
720 // SkyLab
721 foreach (immutable idx, ref sk; skylab) {
722 switch (sk.m) {
723 case 0:
724 sk.y += sk.s;
725 if (sk.y > sk.max) {
726 sk.y = sk.max;
727 sk.m = 1;
728 ++sk.frame;
730 break;
731 case 1:
732 ++sk.frame;
733 if (sk.frame == 7) sk.m = 2;
734 break;
735 case 2:
736 sk.p = (sk.p+1)&3;
737 sk.x = skylabCoords[idx][sk.p].x;
738 sk.y = skylabCoords[idx][sk.p].y;
739 sk.frame = sk.m = 0;
740 break;
741 default:
746 void stepCrumb () {
747 if (willyY&7) return;
748 for (int f = 0; f <= 8; f += 8) {
749 int x = willyX+f;
750 int y = willyY+16;
751 ubyte b = getBlockAt(x, y);
752 if (b == 4) b = 8;
753 if (b < 8) continue;
754 x >>= 3;
755 y >>= 3;
756 if (++b > 15) b = 0;
757 map[y*32+x] = b;
758 //eraseRect(x*8, y*8, 8, 8);
762 void stepKeys () {
763 while (keys.info.length) {
764 bool again = false;
765 foreach (immutable idx; 0..keys.info.length) {
766 auto ki = &keys.info[idx];
767 assert(ki.s);
768 int kx = ki.x;
769 int ky = ki.y;
770 if (kx+7 >= willyX && kx < willyX+10 && ky+7 >= willyY && ky < willyY+16) {
771 score += 100;
772 foreach (immutable c; idx+1..keys.info.length) keys.info[c-1] = keys.info[c];
773 keys.info.length -= 1;
774 again = true;
775 break;
778 if (!again) break;
782 void stepSwitch () {
783 // hole?
784 if (holeLen > 0 && switches.length && switches[0].s/*tate*/ > 1) {
785 if (holeLen < 0) {
786 //FIXME
787 enemies[1].max += 24;
788 holeLen = 0;
789 } else {
790 ++holeY;
791 if (holeY == 16) {
792 map[11*32+17] = 0;
793 map[12*32+17] = 0;
794 holeLen = -1;
798 // Kong?
799 if (kong.x >= 0 && !kong.falling && switches.length > 1 && switches[1].s/*tate*/ > 1) kong.falling = 1;
802 void stepWillyActions () {
803 bool doWillyLeft () {
804 if (willyDir > 0) { willyDir = -1; return true; }
805 auto xx = willyX-2;
806 auto b0 = getBlockAt(xx, willyY);
807 auto b1 = getBlockAt(xx, willyY+8);
808 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
809 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
810 willyX -= 2;
811 if (willyX < 0) willyX += 240;
812 willyLastMoveDir = -1;
813 return true;
816 bool doWillyRight () {
817 if (willyDir < 0) { willyDir = 1; return true; }
818 if (willyX > 245) return false;
819 auto xx = willyX+10;
820 auto b0 = getBlockAt(xx, willyY);
821 auto b1 = getBlockAt(xx, willyY+8);
822 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
823 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
824 willyX += 2;
825 if (willyX > 240) willyX -= 240;
826 willyLastMoveDir = 1;
827 return true;
830 void doWillyJump () {
831 if (!willyJump) return;
832 willyY += willyJ[willyJump];
833 auto x = willyX;
834 auto mv = false;
835 if (willyJumpDir < 0) mv = doWillyLeft(); else if (willyJumpDir > 0) mv = doWillyRight();
836 if (willyJump < 9) {
837 willyFall = 0;
838 // up
839 auto b0 = getBlockAt(x, willyY);
840 auto b1 = getBlockAt(x+8, willyY);
841 if (b0 == 3 || b1 == 3) {
842 // headboom! (apstenu %-)
843 willyX = x;
844 willyY -= willyJ[willyJump];
845 willyJump = 0; // enough flying
846 return;
848 } else {
849 // down
850 if (willyJump > 12) willyFall += willyJ[willyJump];
851 if ((willyY&7) == 0) {
852 auto b0 = getBlockAt(willyX, willyY+16);
853 auto b1 = getBlockAt(willyX+8, willyY+16);
854 if (b0 || b1) {
855 if (b0 == 3 || b1 == 3) willyX = x;
856 willyFall = 0; // can't fall too deep while jumping
857 willyJump = 0; // enough flying
858 if (b0 == 7 || b1 == 7) willyStall = 1; // conveyor?
859 return;
863 ++willyJump;
864 if (willyJump > 18) willyJump = 0;
868 if (willyDead) return;
869 checkSwitch();
870 if (isWillyInDeadly()) { willyDead = true; return; }
871 if (!debugColdet) {
872 if (checkMonsters()) { willyDead = true; return; }
873 } else {
874 if (checkMonsters()) singleStep = singleStepWait = true;
877 auto wasJump = false;
878 if (willyJump) {
879 willyLastMoveDir = 0;
880 doWillyJump();
881 if (willyJump) return;
882 wasJump = true;
885 auto falling = isWillyFalling();
886 if (!kDown && falling) {
887 willyConv = willyStall = willyLastMoveDir = 0;
888 willyFall += 4;
889 willyY += 4;
890 if (willyY > 112) willyY -= 112;
891 return;
894 if (!falling && willyFall > 34) willyDead = true; // too high!
895 auto lfall = willyFall;
896 willyFall = 0;
898 if (willyDead) return;
900 auto dx = (kLeft ? -1 : kRight ? 1 : 0);
901 if (isWillyOnConv()) {
902 auto cdir = (conveyor.d ? 1 : -1);
903 //dx==cdir,!dx,lastmove==cdir
904 if (willyLastMoveDir == cdir || dx == cdir || !dx) { willyConv = cdir; willyStall = 0; } // was moving in conv. dir or standing
905 if (!willyConv) {
906 // Willy just steps on the conveyor, and Willy walking to the opposite side
907 willyStall = 0;
908 if (wasJump && willyLastMoveDir == -cdir) {
909 willyConv = dx; // from jump, can do opposite
910 } else {
911 willyConv = dx;
912 if (lfall > 0 || !willyLastMoveDir) { dx = 0; willyStall = 1; } // lands on conveyor, not from jump
914 } else {
915 // Willy was on conveyor
916 dx = (willyStall ? 0 : willyConv);
918 } else {
919 willyConv = willyStall = 0;
922 //if (willyConv) dx = willyConv;
923 if (kUp && !wasJump) {
924 willyConv = willyStall = willyLastMoveDir = 0;
925 willyJumpDir = dx;
926 willyJump = 1;
927 doWillyJump();
928 return;
930 if (kDown) willyY -= 8;
931 willyLastMoveDir = 0;
932 if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
935 // ////////////////////////////////////////////////////////////////////// //
936 void buildWilly () {
937 auto img = new X11Image(16*16, 16+16);
938 auto ww = gameData["willy"];
939 foreach (immutable f; 0..16) {
940 foreach (immutable y; 0..16) {
941 foreach (immutable x; 0..16) {
942 ubyte c = ww[f*256+y*16+x];
943 if (!c) {
944 img.setPixel(f*16+x, y, Transparent);
945 } else {
946 img.setPixel(f*16+x, y, palcol(c));
947 // white
948 img.setPixel(f*16+x, y+16, palcol(15));
953 willySpr = img;
956 void buildBrickImages () {
957 auto buildBrickImage (in ref Room.CommonGfx brk, int skipy=0) {
958 return buildImageBrick(gameData["blocks"][brk.gfx*64..$], brk.ink, this.paper, skipy);
960 brickCache = null;
961 foreach (immutable f; 0..8) {
962 X11Image img;
963 switch (f) {
964 //case 0: case 7: img = buildBrickImage(this.wall, 16); break;
965 case 1: img = buildBrickImage(this.platforms[0]); break;
966 case 2: img = buildBrickImage(this.platforms[1]); break;
967 case 3: img = buildBrickImage(this.wall); break;
968 //case 4: img = buildBrickImage(this.crumb); break;
969 case 5: img = buildBrickImage(this.deadlies[0]); break;
970 case 6: img = buildBrickImage(this.deadlies[1]); break;
971 default:
973 brickCache ~= img;
975 foreach (immutable f; 0..9) brickCache ~= buildBrickImage(this.crumb, f);
978 void buildConvImages () {
979 convCache = null;
980 auto conv = &this.conveyor;
981 if (conv.y <= 0 || conv.l < 1) return;
982 foreach (immutable f; 0..4) {
983 convCache ~= buildImageBrick(gameData["conv"][conv.gfx*256+f*64..$], conv.ink, this.paper);
987 void buildExitImages () {
988 exitCache = null;
989 exitCache ~= buildImageMasked(gameData["exits"][this.exit.gfx*256..$], 16, 16);
990 foreach (immutable f; 0..16) exitCache ~= buildImageMaskedShiny(gameData["exits"][this.exit.gfx*256..$], f, 16, 16);
993 void buildKeysImages () {
994 keyCache = null;
995 foreach (immutable f; 0..16) keyCache ~= buildImageMaskedShiny(gameData["keys"][this.keys.gfx*64..$], f, 8, 8);
998 void buildMonsterImages () {
999 monsterCache = null;
1000 monsterOfsCache = null;
1001 foreach (immutable f, const ref m; this.enemies) {
1002 if (m.x < 0 || m.y < 0) {
1003 monsterOfsCache ~= null;
1004 monsterCache ~= null;
1005 continue;
1007 X11Image[] r;
1008 int[] cc;
1009 if (m.vert) {
1010 foreach (immutable c; 0..4) {
1011 auto n = (m.gfx+c)*256;
1012 cc ~= n;
1013 r ~= buildImageMaskedInk(gameData["vrobo"][n..$], m.ink-1, 16, 16);
1015 } else {
1016 foreach (immutable c; 0..(m.anim>>1)+1) {
1017 auto n = (m.gfx+c)*256;
1018 cc ~= n;
1019 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1020 n += m.flip*256;
1021 cc ~= n;
1022 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1025 monsterOfsCache ~= cc;
1026 monsterCache ~= r;
1030 void buildEugeneImages () {
1031 eugeneSpr = null;
1032 for (int f = 0; f <= 256; f += 256) {
1033 foreach (immutable c; 0..8) {
1034 eugeneSpr ~= buildImageMaskedInk(gameData["eugene"][f..$], c, 16, 16);
1039 void buildKongImages () {
1040 kongSpr = null;
1041 foreach (immutable f; 0..4) {
1042 foreach (immutable c; 0..8) {
1043 kongSpr ~= buildImageMaskedInk(gameData["kong"][f*256..$], c, 16, 16);
1048 void buildSkyLabImages () {
1049 skylabSpr = null;
1050 foreach (immutable f; 0..8) {
1051 foreach (immutable c; 0..8) {
1052 skylabSpr ~= buildImageMaskedInk(gameData["sky"][f*256..$], c, 16, 16);
1057 void buildSwitchImages () {
1058 switchesSpr = null;
1059 foreach (immutable f; 0..2) {
1060 switchesSpr ~= buildImageBrick(gameData["switches"][f*64..$], this.platforms[1].ink, this.paper);
1066 // ////////////////////////////////////////////////////////////////////////// //
1067 public void blitTo (X11Image src, X11Image dst, int x, int y, int repx=1) {
1068 foreach (immutable r; 0..repx) {
1069 foreach (immutable dy; 0..src.height) {
1070 foreach (immutable dx; 0..src.width) {
1071 int xx = x+dx;
1072 int yy = y+dy;
1073 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1074 auto c = src.getPixel(dx, dy);
1075 if (isTransparent(c)) continue;
1076 dst.setPixel(xx, yy, c);
1079 x += src.width;
1084 public void blitTo (X11Image src, int x0, int y0, int ww, int hh, X11Image dst, int x, int y) {
1085 foreach (immutable dy; 0..ww) {
1086 foreach (immutable dx; 0..hh) {
1087 int xx = x+dx;
1088 int yy = y+dy;
1089 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1090 auto c = src.getPixel(x0+dx, y0+dy);
1091 if (isTransparent(c)) continue;
1092 dst.setPixel(xx, yy, c);