cosmetix
[mmd.git] / engine.d
blobe61a5f7e135998f9252abe6ab20bdc0854201ea8
1 module engine is aliced;
3 import iv.txtser;
4 import iv.vfs;
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("data/levelset0.js"));
20 foreach (immutable idx, ref room; gameRooms) room.roomIdx = cast(int)idx;
21 gameData.txtunser(VFile("data/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 auto pp = gameData["palmain"];
32 foreach (immutable cc; 0..256) {
33 ubyte r = cast(ubyte)(255*pp[cc*3+0]/63);
34 ubyte g = cast(ubyte)(255*pp[cc*3+1]/63);
35 ubyte b = cast(ubyte)(255*pp[cc*3+2]/63);
36 palette[cc] = rgbcol(r, g, b);
38 palset = true;
40 return palette[c];
44 // ////////////////////////////////////////////////////////////////////////// //
45 private X11Image buildImageMasked (const(ubyte)[] darr, int w, int h) {
46 assert(w > 0 && h > 0);
47 int dpos = 0;
48 auto img = new X11Image(w, h);
49 foreach (immutable y; 0..h) {
50 foreach (immutable x; 0..w) {
51 ubyte c = darr[dpos++];
52 if (c) {
53 img.setPixel(x, y, palcol(c));
54 } else {
55 img.setPixel(x, y, Transparent);
59 return img;
63 private X11Image buildImageMaskedShiny (const(ubyte)[] darr, int brightness, int w, int h) {
64 assert(w > 0 && h > 0);
65 int dpos = 0;
66 auto img = new X11Image(w, h);
67 foreach (immutable y; 0..h) {
68 foreach (immutable x; 0..w) {
69 ubyte c = darr[dpos++];
70 if (c) {
71 int b = (c&15)-brightness;
72 if (b < 0) b = 0; else if (b > 15) b = 15;
73 img.setPixel(x, y, palcol(cast(ubyte)((c&240)|b)));
74 } else {
75 img.setPixel(x, y, Transparent);
79 return img;
83 private X11Image buildImageMaskedInk (const(ubyte)[] darr, int ink, int w, int h) {
84 assert(w > 0 && h > 0);
85 int dpos = 0;
86 auto img = new X11Image(w, h);
87 if (ink < 0) ink = 0;
88 ink *= 16;
89 foreach (immutable y; 0..h) {
90 foreach (immutable x; 0..w) {
91 ubyte c = darr[dpos++];
92 if (c) {
93 img.setPixel(x, y, palcol(cast(ubyte)(c+ink)));
94 } else {
95 img.setPixel(x, y, Transparent);
99 return img;
103 // ////////////////////////////////////////////////////////////////////////// //
104 private X11Image buildImageBrick (const(ubyte)[] darr, int ink, int paper, int skipy=0) {
105 assert(skipy >= 0);
106 enum { w = 8, h = 8 }
107 int dpos = 0;
108 auto img = new X11Image(w, h);
109 ink *= 16;
110 foreach (immutable y; 0..h) {
111 foreach (immutable x; 0..w) {
112 ubyte c = (y >= skipy ? darr[dpos++] : 0);
113 img.setPixel(x, y, palcol(cast(ubyte)(c ? ink+c : paper)));
116 return img;
120 // ////////////////////////////////////////////////////////////////////////// //
121 // willy jump offsets
122 private:
123 static immutable int[19] willyJ = [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
125 struct SkyLabXY { int x, y; }
126 static immutable SkyLabXY[4][3] skylabCoords = [
127 [{x: 8,y:0}, {x: 72,y:0}, {x:136,y:0}, {x:200,y:0}],
128 [{x:40,y:0}, {x:104,y:0}, {x:168,y:0}, {x:232,y:0}],
129 [{x:24,y:0}, {x: 88,y:0}, {x:152,y:0}, {x:216,y:0}]
133 // ////////////////////////////////////////////////////////////////////////// //
134 // suitable to "unjson"
135 public struct Room {
136 enum { Width = 32, Height = 16 }
137 static struct WillyStart { int x; int y; int sd; }
138 static struct ExitGfx { ubyte gfx; int x; int y; }
139 static struct CommonGfx { ubyte gfx; ubyte ink; ubyte paper; }
140 static struct Conveyor { int x; int y; int d; int l; ubyte gfx; ubyte ink; ubyte paper; @SRZIgnore int frame; }
141 static struct KeyPos { int x; int y; int s; }
142 static struct Key { ubyte gfx; KeyPos[] info; }
143 static struct SwitchPos { int x; int y; int s; }
144 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; }
145 static struct SkyLabEnemy { int p, s; ubyte ink; int max; int m; int frame; }
146 static struct SkyLabXY { int x, y, frame; ubyte ink; int m; int s; int max; int p; }
147 static struct SPGRay { int x, y; }
148 @SRZIgnore int roomIdx;
149 string title;
150 WillyStart willy;
151 ExitGfx exit;
152 int air;
153 ubyte[Height*Width] map; // 16 rows, 32 cols
154 ubyte border, ink, paper;
155 CommonGfx[] platforms;
156 CommonGfx wall;
157 CommonGfx crumb;
158 CommonGfx[] deadlies;
159 Conveyor conveyor;
160 Key keys;
161 SwitchPos[] switches;
162 Enemy[] enemies;
164 @SRZIgnore Enemy eugene;
166 @SRZIgnore int holeLen, holeY;
167 @SRZIgnore Enemy kong;
169 @SRZIgnore SkyLabEnemy[] skylabEnemies;
170 @SRZIgnore SkyLabXY[] skylab;
172 @SRZIgnore SPGRay[] spg; // solar power generator
174 @SRZIgnore private {
175 int willyX, willyY, willyDir, willyJump, willyJumpDir;
176 int willyLastMoveDir, willyFall, willyConv, willyStall;
177 public bool willyDead;
178 int frameNo;
179 public int score;
180 // keys
181 bool kLeft, kRight, kJump, kUp, kDown;
182 bool kLeftDown, kRightDown, kJumpDown, kUpDown, kDownDown;
185 @SRZIgnore private {
186 X11Image finalSpr;
187 X11Image sunSpr;
188 X11Image[] brickCache;
189 X11Image[] convCache;
190 X11Image[] exitCache;
191 X11Image[] keyCache;
192 X11Image[][] monsterCache;
193 int[][] monsterOfsCache;
194 X11Image[] eugeneSpr;
195 X11Image[] kongSpr;
196 X11Image[] skylabSpr;
197 X11Image[] switchesSpr;
198 X11Image willySpr;
201 ubyte saveKeyState () {
202 return
203 ((kLeftDown ? 1 : 0)<<0)|
204 ((kRightDown ? 1 : 0)<<1)|
205 ((kUpDown ? 1 : 0)<<2)|
206 ((kDownDown ? 1 : 0)<<3)|
207 ((kJumpDown ? 1 : 0)<<4)|
211 void restoreKeyState (ubyte b) {
212 keyLeft = ((b&(1<<0)) != 0);
213 keyRight = ((b&(1<<1)) != 0);
214 keyUp = ((b&(1<<2)) != 0);
215 keyDown = ((b&(1<<3)) != 0);
216 keyJump = ((b&(1<<4)) != 0);
219 void initRoom () {
220 void initWilly () {
221 willyX = willy.x;
222 willyY = willy.y;
223 willyDir = (willy.sd > 0 ? -1 : 1);
224 willyJump = willyFall = willyConv = willyStall = 0;
225 willyDead = false;
226 willyLastMoveDir = 0;
227 kLeft = kRight = kJump = kUp = kDown = false;
228 kLeftDown = kRightDown = kJumpDown = kUpDown = kDownDown = false;
231 buildWilly();
232 buildBrickImages();
233 buildConvImages();
234 buildExitImages();
235 buildKeysImages();
236 buildMonsterImages();
237 buildEugeneImages();
238 buildKongImages();
239 buildSkyLabImages();
240 buildSwitchImages();
241 finalSpr = buildImageMasked(gameData["final"], 256, 64);
242 sunSpr = buildImageMasked(gameData["sun"], 24, 16);
244 initWilly();
246 frameNo = 0;
247 foreach (ref c; map) {
248 if (c == 4) c = 8; // 'crumb'
249 else if (c < 1 || c > 7) c = 0; // emptyness
252 // rebuild keys
253 KeyPos[] kinfo;
254 foreach (const ref ki; keys.info) {
255 if (ki.s == 0) continue;
256 kinfo ~= ki;
258 keys.info = kinfo;
260 platforms = platforms.dup;
261 deadlies = deadlies.dup;
262 switches = switches.dup;
263 enemies = enemies.dup;
265 // Eugene?
266 if (roomIdx == 4) {
267 Enemy[] a = [{ x:120, y:1, d/*ir*/:0, min:1, max:87, ink:6 }];
268 eugene = a[0];
270 // Kong?
271 if (roomIdx == 7 || roomIdx == 11) {
272 Enemy[] a = [{ x:120, y:0, max:104, frame:0, ink:2, m:0 }];
273 kong = a[0];
274 holeLen = 2*8;
275 holeY = 0;
277 // SkyLab?
278 if (roomIdx == 13) {
279 enemies = null;
280 SkyLabEnemy[] a = [
281 {p:0, s:4, ink:6, max:72, m:0, frame:0},
282 {p:2, s:3, ink:5, max:56, m:0, frame:0},
283 {p:1, s:1, ink:4, max:32, m:0, frame:0}
285 skylabEnemies = a.dup;
286 foreach (immutable f, const ref se; skylabEnemies) {
287 skylab ~= SkyLabXY(
288 skylabCoords[f][se.p].x, skylabCoords[f][se.p].y,
289 se.frame, se.ink, se.m, se.s, se.max, se.p,
293 // Solar Power Generator?
294 if (roomIdx == 18) buildSPG();
297 void keyLeft (bool pressed) { if (pressed) kLeft = true; kLeftDown = pressed; }
298 void keyRight (bool pressed) { if (pressed) kRight = true; kRightDown = pressed; }
299 void keyUp (bool pressed) { if (pressed) kUp = true; kUpDown = pressed; }
300 void keyDown (bool pressed) { if (pressed) kDown = true; kDownDown = pressed; }
301 void keyJump (bool pressed) { if (pressed) kJump = true; kJumpDown = pressed; }
303 void stepGame () {
304 kLeft = kLeft||kLeftDown;
305 kRight = kRight||kRightDown;
306 kUp = kUp||kUpDown;
307 kDown = kDown||kDownDown;
308 kJump = kJump||kJumpDown;
309 scope(exit) kLeft = kRight = kJump = kUp = kDown = false;
310 ++frameNo;
311 conveyor.frame += (conveyor.d ? -1 : 1);
312 if (conveyor.frame < 0) conveyor.frame = 3; else if (conveyor.frame > 3) conveyor.frame = 0;
314 if (!willyJump) stepCrumb();
315 stepMonsters();
316 stepWillyActions();
317 stepKeys();
318 stepSwitch();
319 buildSPG();
322 void cheatRemoveKeys () { keys.info = null; }
324 // ////////////////////////////////////////////////////////////////////// //
325 // checkers
327 // x & y: in pixels
328 ubyte getBlockAt (int x, int y, bool simplify=false) const nothrow @nogc {
329 x = x>>3;
330 y = y>>3;
331 if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
332 ubyte b = map[y*32+x];
333 if (simplify) {
334 if (b > 15) b = 0;
335 else if (b > 7) b = 4;
336 else if (b == 6) b = 5;
337 else if (b == 2) b = 1;
339 return b;
342 bool checkExit () const nothrow @nogc {
343 if (keys.info.length != 0) return false;
344 auto x = exit.x;
345 auto y = exit.y;
346 return (willyX >= x-2 && willyX+10 <= x+18 && willyY >= y-5 && willyY+16 <= y+22);
349 bool checkMonsters () const nothrow {
350 foreach (immutable f, const ref m; enemies) {
351 auto cc = monsterOfsCache[f];
352 if (m.x < 0 || m.y < 0) continue;
353 if (m.vert) {
354 if (pixelCheckMonster(m.x, m.y, gameData["vrobo"], cc[m.anim])) return true;
355 } else {
356 if (pixelCheckMonster(m.x&248, m.y, gameData["hrobo"], cc[((m.x&m.anim)&0xfe)+m.d/*ir*/])) return true;
359 // Eugene?
360 if (eugene.x >= 0) {
361 enum curLSet = 0;
362 if (pixelCheckMonster(eugene.x, eugene.y, gameData["eugene"], 256*curLSet)) return true;
364 // Kong?
365 if (kong.x >= 0) {
366 if (pixelCheckMonster(kong.x, kong.y, gameData["kong"], 256*kong.frame)) return true;
368 // SkyLab?
369 if (skylab.length) {
370 foreach (const ref sk; skylab) {
371 if (pixelCheckMonster(sk.x, sk.y, gameData["sky"], 256*sk.frame)) return true;
374 return false;
377 void checkSwitch () nothrow @nogc {
378 foreach (ref ss; switches) {
379 if (ss.s/*tate*/ != 1) continue;
380 auto x = ss.x;
381 auto y = ss.y;
382 if (x+7 >= willyX && y+7 >= willyY && x < willyX+8 && y < willyY+16) ss.s/*tate*/ = 1+1;
386 bool checkSPG () const nothrow @nogc {
387 foreach (const ref sp; spg) {
388 auto x = sp.x*8;
389 auto y = sp.y*8;
390 if (x < 0 || y < 0) break;
391 if (x+7 >= willyX && x < willyX+8 && y+7 >= willyY && y < willyY+16) return true;
393 return false;
396 X11Image draw (X11Image img=null) {
397 if (img is null) img = new X11Image(8*Room.Width, 8*Room.Height);
398 int pos = 0;
399 foreach (immutable y; 0..img.height) {
400 foreach (immutable x; 0..img.width) {
401 img.setPixel(x, y, palcol(this.paper));
404 foreach (immutable y; 0..Room.Height) {
405 foreach (immutable x; 0..Room.Width) {
406 ubyte blk = this.map[pos++];
407 if (blk < 1 || blk == 7) continue;
408 if (blk == 4) blk = 8;
409 if (blk < brickCache.length && brickCache[blk] !is null) {
410 brickCache[blk].blitTo(img, x*8, y*8);
414 if (this.roomIdx == 19) {
415 finalSpr.blitTo(img, 0, 0);
416 sunSpr.blitTo(img, 60, 32);
419 void drawConveyor () {
420 auto conv = &this.conveyor;
421 if (conv.l < 1) return;
422 auto y = conv.y;
423 if (y <= 0) return;
424 convCache[conv.frame].blitTo(img, conv.x, conv.y, conv.l);
427 void drawExit () {
428 int br;
429 if (this.keys.info.length != 0) {
430 br = 0;
431 } else {
432 br = this.frameNo&31;
433 if (br > 15) br = 31-br;
434 ++br;
436 exitCache[br].blitTo(img, this.exit.x, this.exit.y);
439 void drawKeys () {
440 auto ff = this.frameNo%16;
441 if (ff >= 8) ff = 15-ff;
442 auto keyimg = keyCache[ff];
443 foreach_reverse (const ref ki; this.keys.info) {
444 if (ki.x < 0 || ki.y < 0) continue;
445 //eraseRect(ki.x, ki.y, 8, 8);
446 if (!ki.s) continue;
447 keyimg.blitTo(img, ki.x&248, ki.y);
451 void drawMonsters () {
452 foreach (immutable f, const ref m; this.enemies) {
453 if (m.x < 0 || m.y < 0) continue;
454 auto slist = monsterCache[f];
455 auto x = m.x;
456 if (m.vert) {
457 //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
458 slist[m.anim].blitTo(img, x, m.y);
459 } else {
460 auto sidx = ((x&m.anim)&0xfe)+m.d/*ir*/;
461 if (sidx < slist.length) {
462 slist[sidx].blitTo(img, x&248, m.y);
463 } else {
464 import core.stdc.stdio : stderr, fprintf;
465 stderr.fprintf("monster #%u is fucked: sidx=%u; max=%u", cast(uint)f, cast(uint)sidx, cast(uint)slist.length);
469 if (this.eugene.x >= 0) {
470 enum curLSet = 0;
471 eugeneSpr[curLSet*8+this.eugene.ink].blitTo(img, this.eugene.x, this.eugene.y);
473 if (this.kong.x >= 0) {
474 kongSpr[this.kong.frame*8+this.kong.ink].blitTo(img, this.kong.x, this.kong.y);
476 if (this.skylab.length) {
477 foreach (const ref sk; this.skylab) {
478 skylabSpr[sk.frame*8+sk.ink].blitTo(img, sk.x, sk.y);
483 void drawSwitch () {
484 foreach (const ref ss; this.switches) {
485 if (ss.s == 0) continue;
486 switchesSpr[ss.s-1].blitTo(img, ss.x, ss.y);
490 void drawSPG () {
491 foreach (const ref sp; this.spg) {
492 auto x = sp.x*8;
493 auto y = sp.y*8;
494 if (x < 0 || y < 0) break;
495 //ctx.fillStyle = mainPal[noerase?6:this.paper];
496 //ctx.fillRect(x, y, 8, 8);
497 foreach (immutable dy; 0..8) {
498 foreach (immutable dx; 0..8) {
499 img.setPixel(x+dx, y+dy, palcol(6));
505 void drawWilly () {
506 auto willyPos = (this.willyX&15)>>1;
507 if (this.willyDir < 0) willyPos += 8;
508 if (debugColdet) {
509 auto wy = 0;
510 if (this.checkMonsters()) wy = 16;
511 willySpr.blitTo(willyPos*16, wy, 16, 16, img, this.willyX&248, this.willyY);
512 } else {
513 willySpr.blitTo(willyPos*16, 0, 16, 16, img, this.willyX&248, this.willyY);
517 drawConveyor();
518 drawKeys();
519 drawMonsters();
520 drawSwitch();
521 drawWilly();
522 drawExit();
523 drawSPG();
525 return img;
528 private:
529 // ////////////////////////////////////////////////////////////////////// //
530 // pixel-perfect collision detector
531 // fully stolen from Andy's sources %-)
532 bool pixelCheckMonster (int rx, int ry, const(ubyte)[] darr, int dpos=0) const nothrow {
533 static ubyte[256] mpcGrid;
534 assert(dpos >= 0);
535 //int x, y, w;
536 int x, w;
537 rx -= willyX&248;
538 ry -= willyY;
539 if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
540 // clear grid
541 mpcGrid[] = 0;
542 if (rx < 0) { x = 0; rx = -rx; w = 16-rx; } else { x = rx; rx = 0; w = 16-x; }
543 // partial plot monster
544 for (int y = ry+15; y >= ry; --y) {
545 if (y >= 0 && y < 16) {
546 int gp = y*16+x;
547 int rp = dpos+(y-ry)*16+rx;
548 for (int dx = 0; dx < w; ++dx, ++gp, ++rp) mpcGrid[gp] = darr[rp];
551 auto warr = gameData["willy"];
552 int wptr = ((willyX&15)>>1)*256+(willyDir < 0 ? 2048 : 0);
553 // check for collision
554 int mp = 0;
555 for (x = 255; x >= 0; --x, ++wptr, ++mp) if (warr[wptr] && mpcGrid[mp]) return true;
556 return false;
559 // ////////////////////////////////////////////////////////////////////// //
560 bool isWillyFalling () const nothrow @nogc {
561 if (willyY&7) return true;
562 for (int dx = 0; dx <= 8; dx += 8) {
563 ubyte b = getBlockAt(willyX+dx, willyY+16, true);
564 if (b > 0 && b != 5) return false;
566 return true;
569 bool isWillyInDeadly () const nothrow @nogc {
570 for (int dx = 0; dx <= 8; dx += 8) {
571 for (int dy = 0; dy <= 16; dy += 8) {
572 if (getBlockAt(willyX+dx, willyY+dy, true) == 5) return true;
575 return false;
578 bool isWillyOnConv () const nothrow @nogc {
579 if (willyY&7) return false;
580 ubyte b0 = getBlockAt(willyX, willyY+16);
581 ubyte b1 = getBlockAt(willyX+8, willyY+16);
582 return (b0 == 7 || b1 == 7);
585 // ////////////////////////////////////////////////////////////////////// //
586 // called on each frame
587 void buildSPG (bool forced=false) {
588 if (!forced && roomIdx != 18) {
589 spg = null;
590 return;
593 bool spgCheckMonster (int x, int y) {
594 x *= 8;
595 y *= 8;
596 foreach (const ref cm; enemies) {
597 auto mx = cm.x;
598 auto my = cm.y;
599 if (x+7 >= mx && x < mx+15 && y+7 >= my && y < my+15) return true;
601 return false;
604 int x = 23, y = 0;
605 bool done = false;
606 int dir = 0, idx = 0;
608 if (spg.length) {
609 spg.length = 0;
610 spg.assumeSafeAppend;
613 void addXY (int x, int y) {
614 ++idx;
615 while (spg.length < idx) spg ~= SPGRay(-1, -1);
616 spg[idx-1].x = x;
617 spg[idx-1].y = y;
620 do {
621 ubyte blockhit = map[y*32+x];
622 bool robohit = spgCheckMonster(x, y);
624 if (blockhit && robohit) {
625 addXY(-1, -1);
626 done = true;
627 } else if (!blockhit && robohit) {
628 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
629 spg[idx-1].x = spg[idx-1].y = -1;
630 done = true;
631 } else {
632 addXY(x, y);
634 dir ^= 1;
635 } else if (!blockhit && !robohit) {
636 addXY(x, y);
637 } else if (blockhit && !robohit) {
638 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
639 spg[idx-1].x = spg[idx-1].y = -1;
640 done = true;
642 dir ^= 1;
645 if (!blockhit) {
646 if (!dir) {
647 ++y;
648 blockhit = map[y*32+x];
649 if (y == 15 || blockhit) done = true;
650 } else {
651 --x;
652 blockhit = map[y*32+x];
653 if (x == 0 || blockhit) done = true;
655 } else {
656 if (!dir) { --x; if (!x) done = true; }
657 else { ++y; if (++y == 15) done = true; }
659 } while (!done);
660 addXY(-1, -1);
663 void stepMonsters () {
664 foreach (ref m; enemies) {
665 if (m.x < 0 || m.y < 0) continue;
666 if (m.vert) {
667 auto y = m.y;
668 auto spd = m.s/*peed*/;
669 if (m.d/*ir*/ != 0) {
670 // up
671 y -= spd;
672 if (y < m.min || y < 0) { y += spd; m.d/*ir*/ = 0; }
673 } else {
674 // down
675 y += spd;
676 if (y > m.max) { y -= spd; m.d/*ir*/ = 1; }
678 m.y = y;
679 m.anim = (m.anim+1)&3;
680 } else {
681 auto x = m.x;
682 auto spd = (2>>m.s/*peed*/);
683 if (m.d/*ir*/ != 0) {
684 // left
685 x -= spd;
686 if (x < m.min) { m.d/*ir*/ = 0; x += spd; }
687 } else {
688 // right
689 x += spd;
690 if (x > m.max+6) { m.d/*ir*/ = 1; x -= spd; }
692 m.x = x;
695 // Eugene
696 if (eugene.x >= 0) {
697 if (keys.info.length == 0) {
698 // no keys, Eugene tries to block the exit
699 eugene.ink = (eugene.ink+1)&7;
700 eugene.y += (eugene.y < eugene.max ? 1 : 0);
701 } else {
702 if (eugene.d/*ir*/ != 0) {
703 // up
704 --eugene.y;
705 if (eugene.y < eugene.min) { eugene.d/*ir*/ = 0; ++eugene.y; }
706 } else {
707 // down
708 ++eugene.y;
709 if (eugene.y > eugene.max) { eugene.d/*ir*/ = 1; --eugene.y; }
713 // Kong
714 if (kong.x >= 0) {
715 switch (kong.falling) {
716 case 1: // just started
717 map[2*32+15] = 0;
718 map[2*32+16] = 0;
719 //eraseRect(16, 120, 16, 8);
720 kong.falling = 2;
721 break;
722 case 2:
723 kong.ink = 4;
724 kong.frame += 2;
725 kong.falling = 3;
726 break;
727 case 3:
728 kong.y += 4;
729 if (kong.y >= kong.max) { kong.x = -1; score += 100; }
730 if (!kong.delay) { kong.delay = 4; kong.frame = ((kong.frame-1)&1)+2; } else --kong.delay;
731 break;
732 default:
733 if (!kong.delay) { kong.delay = 8; kong.frame = (kong.frame+1)&1; } else --kong.delay;
734 break;
737 // SkyLab
738 foreach (immutable idx, ref sk; skylab) {
739 switch (sk.m) {
740 case 0:
741 sk.y += sk.s;
742 if (sk.y > sk.max) {
743 sk.y = sk.max;
744 sk.m = 1;
745 ++sk.frame;
747 break;
748 case 1:
749 ++sk.frame;
750 if (sk.frame == 7) sk.m = 2;
751 break;
752 case 2:
753 sk.p = (sk.p+1)&3;
754 sk.x = skylabCoords[idx][sk.p].x;
755 sk.y = skylabCoords[idx][sk.p].y;
756 sk.frame = sk.m = 0;
757 break;
758 default:
763 void stepCrumb () {
764 if (willyY&7) return;
765 for (int f = 0; f <= 8; f += 8) {
766 int x = willyX+f;
767 int y = willyY+16;
768 ubyte b = getBlockAt(x, y);
769 if (b == 4) b = 8;
770 if (b < 8) continue;
771 x >>= 3;
772 y >>= 3;
773 if (++b > 15) b = 0;
774 map[y*32+x] = b;
775 //eraseRect(x*8, y*8, 8, 8);
779 void stepKeys () {
780 while (keys.info.length) {
781 bool again = false;
782 foreach (immutable idx; 0..keys.info.length) {
783 auto ki = &keys.info[idx];
784 assert(ki.s);
785 int kx = ki.x;
786 int ky = ki.y;
787 if (kx+7 >= willyX && kx < willyX+10 && ky+7 >= willyY && ky < willyY+16) {
788 score += 100;
789 foreach (immutable c; idx+1..keys.info.length) keys.info[c-1] = keys.info[c];
790 keys.info.length -= 1;
791 again = true;
792 break;
795 if (!again) break;
799 void stepSwitch () {
800 // hole?
801 if (holeLen > 0 && switches.length && switches[0].s/*tate*/ > 1) {
802 if (holeLen < 0) {
803 //FIXME
804 enemies[1].max += 24;
805 holeLen = 0;
806 } else {
807 ++holeY;
808 if (holeY == 16) {
809 map[11*32+17] = 0;
810 map[12*32+17] = 0;
811 holeLen = -1;
815 // Kong?
816 if (kong.x >= 0 && !kong.falling && switches.length > 1 && switches[1].s/*tate*/ > 1) kong.falling = 1;
819 void stepWillyActions () {
820 bool doWillyLeft () {
821 if (willyDir > 0) { willyDir = -1; return true; }
822 auto xx = willyX-2;
823 auto b0 = getBlockAt(xx, willyY);
824 auto b1 = getBlockAt(xx, willyY+8);
825 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
826 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
827 willyX -= 2;
828 if (willyX < 0) willyX += 240;
829 willyLastMoveDir = -1;
830 return true;
833 bool doWillyRight () {
834 if (willyDir < 0) { willyDir = 1; return true; }
835 if (willyX > 245) return false;
836 auto xx = willyX+10;
837 auto b0 = getBlockAt(xx, willyY);
838 auto b1 = getBlockAt(xx, willyY+8);
839 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
840 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
841 willyX += 2;
842 if (willyX > 240) willyX -= 240;
843 willyLastMoveDir = 1;
844 return true;
847 void doWillyJump () {
848 if (!willyJump) return;
849 willyY += willyJ[willyJump];
850 auto x = willyX;
851 auto mv = false;
852 if (willyJumpDir < 0) mv = doWillyLeft(); else if (willyJumpDir > 0) mv = doWillyRight();
853 if (willyJump < 9) {
854 willyFall = 0;
855 // up
856 auto b0 = getBlockAt(x, willyY);
857 auto b1 = getBlockAt(x+8, willyY);
858 if (b0 == 3 || b1 == 3) {
859 // headboom! (apstenu %-)
860 willyX = x;
861 willyY -= willyJ[willyJump];
862 willyJump = 0; // enough flying
863 return;
865 } else {
866 // down
867 if (willyJump > 12) willyFall += willyJ[willyJump];
868 if ((willyY&7) == 0) {
869 auto b0 = getBlockAt(willyX, willyY+16);
870 auto b1 = getBlockAt(willyX+8, willyY+16);
871 if (b0 || b1) {
872 if (b0 == 3 || b1 == 3) willyX = x;
873 willyFall = 0; // can't fall too deep while jumping
874 willyJump = 0; // enough flying
875 if (b0 == 7 || b1 == 7) willyStall = 1; // conveyor?
876 return;
880 ++willyJump;
881 if (willyJump > 18) willyJump = 0;
885 if (willyDead) return;
886 checkSwitch();
887 if (isWillyInDeadly()) { willyDead = true; return; }
888 if (!debugColdet) {
889 if (checkMonsters()) { willyDead = true; return; }
890 } else {
891 if (checkMonsters()) singleStep = singleStepWait = true;
894 auto wasJump = false;
895 if (willyJump) {
896 willyLastMoveDir = 0;
897 doWillyJump();
898 if (willyJump) return;
899 wasJump = true;
902 auto falling = isWillyFalling();
903 if (!kDown && falling) {
904 willyConv = willyStall = willyLastMoveDir = 0;
905 willyFall += 4;
906 willyY += 4;
907 if (willyY > 112) willyY -= 112;
908 return;
911 if (!falling && willyFall > 34) willyDead = true; // too high!
912 auto lfall = willyFall;
913 willyFall = 0;
915 if (willyDead) return;
917 auto dx = (kLeft ? -1 : kRight ? 1 : 0);
918 if (isWillyOnConv()) {
919 auto cdir = (conveyor.d ? 1 : -1);
920 //dx==cdir,!dx,lastmove==cdir
921 if (willyLastMoveDir == cdir || dx == cdir || !dx) { willyConv = cdir; willyStall = 0; } // was moving in conv. dir or standing
922 if (!willyConv) {
923 // Willy just steps on the conveyor, and Willy walking to the opposite side
924 willyStall = 0;
925 if (wasJump && willyLastMoveDir == -cdir) {
926 willyConv = dx; // from jump, can do opposite
927 } else {
928 willyConv = dx;
929 if (lfall > 0 || !willyLastMoveDir) { dx = 0; willyStall = 1; } // lands on conveyor, not from jump
931 } else {
932 // Willy was on conveyor
933 dx = (willyStall ? 0 : willyConv);
935 } else {
936 willyConv = willyStall = 0;
939 //if (willyConv) dx = willyConv;
940 if (kUp && !wasJump) {
941 willyConv = willyStall = willyLastMoveDir = 0;
942 willyJumpDir = dx;
943 willyJump = 1;
944 doWillyJump();
945 return;
947 if (kDown) willyY -= 8;
948 willyLastMoveDir = 0;
949 if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
952 // ////////////////////////////////////////////////////////////////////// //
953 void buildWilly () {
954 auto img = new X11Image(16*16, 16+16);
955 auto ww = gameData["willy"];
956 foreach (immutable f; 0..16) {
957 foreach (immutable y; 0..16) {
958 foreach (immutable x; 0..16) {
959 ubyte c = ww[f*256+y*16+x];
960 if (!c) {
961 img.setPixel(f*16+x, y, Transparent);
962 } else {
963 img.setPixel(f*16+x, y, palcol(c));
964 // white
965 img.setPixel(f*16+x, y+16, palcol(15));
970 willySpr = img;
973 void buildBrickImages () {
974 auto buildBrickImage (in ref Room.CommonGfx brk, int skipy=0) {
975 return buildImageBrick(gameData["blocks"][brk.gfx*64..$], brk.ink, this.paper, skipy);
977 brickCache = null;
978 foreach (immutable f; 0..8) {
979 X11Image img;
980 switch (f) {
981 //case 0: case 7: img = buildBrickImage(this.wall, 16); break;
982 case 1: img = buildBrickImage(this.platforms[0]); break;
983 case 2: img = buildBrickImage(this.platforms[1]); break;
984 case 3: img = buildBrickImage(this.wall); break;
985 //case 4: img = buildBrickImage(this.crumb); break;
986 case 5: img = buildBrickImage(this.deadlies[0]); break;
987 case 6: img = buildBrickImage(this.deadlies[1]); break;
988 default:
990 brickCache ~= img;
992 foreach (immutable f; 0..9) brickCache ~= buildBrickImage(this.crumb, f);
995 void buildConvImages () {
996 convCache = null;
997 auto conv = &this.conveyor;
998 if (conv.y <= 0 || conv.l < 1) return;
999 foreach (immutable f; 0..4) {
1000 convCache ~= buildImageBrick(gameData["conv"][conv.gfx*256+f*64..$], conv.ink, this.paper);
1004 void buildExitImages () {
1005 exitCache = null;
1006 exitCache ~= buildImageMasked(gameData["exits"][this.exit.gfx*256..$], 16, 16);
1007 foreach (immutable f; 0..16) exitCache ~= buildImageMaskedShiny(gameData["exits"][this.exit.gfx*256..$], f, 16, 16);
1010 void buildKeysImages () {
1011 keyCache = null;
1012 foreach (immutable f; 0..16) keyCache ~= buildImageMaskedShiny(gameData["keys"][this.keys.gfx*64..$], f, 8, 8);
1015 void buildMonsterImages () {
1016 monsterCache = null;
1017 monsterOfsCache = null;
1018 foreach (immutable f, const ref m; this.enemies) {
1019 if (m.x < 0 || m.y < 0) {
1020 monsterOfsCache ~= null;
1021 monsterCache ~= null;
1022 continue;
1024 X11Image[] r;
1025 int[] cc;
1026 if (m.vert) {
1027 foreach (immutable c; 0..4) {
1028 auto n = (m.gfx+c)*256;
1029 cc ~= n;
1030 r ~= buildImageMaskedInk(gameData["vrobo"][n..$], m.ink-1, 16, 16);
1032 } else {
1033 foreach (immutable c; 0..(m.anim>>1)+1) {
1034 auto n = (m.gfx+c)*256;
1035 cc ~= n;
1036 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1037 n += m.flip*256;
1038 cc ~= n;
1039 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1042 monsterOfsCache ~= cc;
1043 monsterCache ~= r;
1047 void buildEugeneImages () {
1048 eugeneSpr = null;
1049 for (int f = 0; f <= 256; f += 256) {
1050 foreach (immutable c; 0..8) {
1051 eugeneSpr ~= buildImageMaskedInk(gameData["eugene"][f..$], c, 16, 16);
1056 void buildKongImages () {
1057 kongSpr = null;
1058 foreach (immutable f; 0..4) {
1059 foreach (immutable c; 0..8) {
1060 kongSpr ~= buildImageMaskedInk(gameData["kong"][f*256..$], c, 16, 16);
1065 void buildSkyLabImages () {
1066 skylabSpr = null;
1067 foreach (immutable f; 0..8) {
1068 foreach (immutable c; 0..8) {
1069 skylabSpr ~= buildImageMaskedInk(gameData["sky"][f*256..$], c, 16, 16);
1074 void buildSwitchImages () {
1075 switchesSpr = null;
1076 foreach (immutable f; 0..2) {
1077 switchesSpr ~= buildImageBrick(gameData["switches"][f*64..$], this.platforms[1].ink, this.paper);
1083 // ////////////////////////////////////////////////////////////////////////// //
1084 public void blitTo (X11Image src, X11Image dst, int x, int y, int repx=1) {
1085 foreach (immutable r; 0..repx) {
1086 foreach (immutable dy; 0..src.height) {
1087 foreach (immutable dx; 0..src.width) {
1088 int xx = x+dx;
1089 int yy = y+dy;
1090 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1091 auto c = src.getPixel(dx, dy);
1092 if (isTransparent(c)) continue;
1093 dst.setPixel(xx, yy, c);
1096 x += src.width;
1101 public void blitTo (X11Image src, int x0, int y0, int ww, int hh, X11Image dst, int x, int y) {
1102 foreach (immutable dy; 0..ww) {
1103 foreach (immutable dx; 0..hh) {
1104 int xx = x+dx;
1105 int yy = y+dy;
1106 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1107 auto c = src.getPixel(x0+dx, y0+dy);
1108 if (isTransparent(c)) continue;
1109 dst.setPixel(xx, yy, c);