cosmetix
[mmd.git] / engine.d
blobc65de1d52e298b8bc961328ec05eed37ae55f8e3
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
3 * Partially based on Andy Noble's Manic Miner PC
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module engine;
20 import x11gfx;
21 import wadarc;
23 __gshared bool debugColdet = false;
24 __gshared bool singleStep = false;
25 __gshared bool singleStepWait = false;
28 // ////////////////////////////////////////////////////////////////////////// //
29 __gshared ubyte[][string] gameData;
30 __gshared Room[] gameRooms;
31 __gshared string gameLevelsName;
34 private void loadRooms (const(ubyte)[] data) {
35 gameRooms.length = 0;
36 if (data.length < 8) throw new Exception("invalid levels");
37 if (data[0..8] != "MANIC\x0d\x0a\x1a") throw new Exception("invalid levels");
38 data = data[8..$];
39 if (data.length < 16) throw new Exception("invalid levels");
40 char[16] lname = (cast(const(char)[])data)[0..16];
41 data = data[16..$];
42 const(char)[] nn;
43 foreach (immutable idx, char ch; lname[]) {
44 if (ch == 0) break;
45 nn = lname[0..idx];
47 while (nn.length && nn[$-1] <= ' ') nn = nn[0..$-1];
48 gameLevelsName = nn.idup;
49 while (data.length > 0) {
50 gameRooms.length += 1;
51 gameRooms[$-1].roomIdx = cast(int)gameRooms.length-1;
52 gameRooms[$-1].load(data);
57 public void loadGameData () {
58 //gameData.txtunser(VFile("data/data.js"));
59 forEachWadFile(delegate (string name, uint size) {
60 auto buf = wadLoadFile(name);
61 //{ import std.stdio; writeln(name); }
62 if (name == "levels") {
63 loadRooms(buf);
64 } else {
65 gameData[name] = buf;
67 });
68 //gameRooms.txtunser(VFile("data/levelset0.js"));
69 //foreach (immutable idx, ref room; gameRooms) room.roomIdx = cast(int)idx;
70 if (gameRooms.length == 0) throw new Exception("no levels");
74 // ////////////////////////////////////////////////////////////////////////// //
75 // image builders
76 VColor palcol (ubyte c) {
77 static VColor[256] palette;
78 static bool palset = false;
79 if (!palset) {
80 auto pp = gameData["palmain"];
81 foreach (immutable cc; 0..256) {
82 ubyte r = cast(ubyte)(255*pp[cc*3+0]/63);
83 ubyte g = cast(ubyte)(255*pp[cc*3+1]/63);
84 ubyte b = cast(ubyte)(255*pp[cc*3+2]/63);
85 palette[cc] = rgbcol(r, g, b);
87 palset = true;
89 return palette[c];
93 // ////////////////////////////////////////////////////////////////////////// //
94 private X11Image buildImageMasked (const(ubyte)[] darr, int w, int h) {
95 assert(w > 0 && h > 0);
96 int dpos = 0;
97 auto img = new X11Image(w, h);
98 foreach (immutable y; 0..h) {
99 foreach (immutable x; 0..w) {
100 ubyte c = darr[dpos++];
101 if (c) {
102 img.setPixel(x, y, palcol(c));
103 } else {
104 img.setPixel(x, y, Transparent);
108 return img;
112 private X11Image buildImageMaskedShiny (const(ubyte)[] darr, int brightness, int w, int h) {
113 assert(w > 0 && h > 0);
114 int dpos = 0;
115 auto img = new X11Image(w, h);
116 foreach (immutable y; 0..h) {
117 foreach (immutable x; 0..w) {
118 ubyte c = darr[dpos++];
119 if (c) {
120 int b = (c&15)-brightness;
121 if (b < 0) b = 0; else if (b > 15) b = 15;
122 img.setPixel(x, y, palcol(cast(ubyte)((c&240)|b)));
123 } else {
124 img.setPixel(x, y, Transparent);
128 return img;
132 private X11Image buildImageMaskedInk (const(ubyte)[] darr, int ink, int w, int h) {
133 assert(w > 0 && h > 0);
134 int dpos = 0;
135 auto img = new X11Image(w, h);
136 if (ink < 0) ink = 0;
137 ink *= 16;
138 foreach (immutable y; 0..h) {
139 foreach (immutable x; 0..w) {
140 ubyte c = darr[dpos++];
141 if (c) {
142 img.setPixel(x, y, palcol(cast(ubyte)(c+ink)));
143 } else {
144 img.setPixel(x, y, Transparent);
148 return img;
152 // ////////////////////////////////////////////////////////////////////////// //
153 private X11Image buildImageBrick (const(ubyte)[] darr, int ink, int paper, int skipy=0) {
154 assert(skipy >= 0);
155 enum { w = 8, h = 8 }
156 int dpos = 0;
157 auto img = new X11Image(w, h);
158 ink *= 16;
159 foreach (immutable y; 0..h) {
160 foreach (immutable x; 0..w) {
161 ubyte c = (y >= skipy ? darr[dpos++] : 0);
162 img.setPixel(x, y, palcol(cast(ubyte)(c ? ink+c : paper)));
165 return img;
169 // ////////////////////////////////////////////////////////////////////////// //
170 // willy jump offsets
171 private:
172 static immutable int[19] willyJ = [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
174 struct SkyLabXY { int x, y; }
175 static immutable SkyLabXY[4][3] skylabCoords = [
176 [{x: 8,y:0}, {x: 72,y:0}, {x:136,y:0}, {x:200,y:0}],
177 [{x:40,y:0}, {x:104,y:0}, {x:168,y:0}, {x:232,y:0}],
178 [{x:24,y:0}, {x: 88,y:0}, {x:152,y:0}, {x:216,y:0}]
182 // ////////////////////////////////////////////////////////////////////////// //
183 // suitable to "unjson"
184 public struct Room {
185 private enum SRZIgnore;
186 enum { Width = 32, Height = 16 }
187 static struct WillyStart { int x; int y; int sd; }
188 static struct ExitGfx { ubyte gfx; int x; int y; }
189 static struct CommonGfx { ubyte gfx; ubyte ink; ubyte paper; }
190 static struct Conveyor { int x; int y; int d; int l; ubyte gfx; ubyte ink; ubyte paper; @SRZIgnore int frame; }
191 static struct KeyPos { int x; int y; int s; }
192 static struct Key { ubyte gfx; KeyPos[] info; }
193 static struct SwitchPos { int x; int y; int s; }
194 static struct Enemy { bool vert; ushort 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; }
195 static struct SkyLabEnemy { int p, s; ubyte ink; int max; int m; int frame; }
196 static struct SkyLabXY { int x, y, frame; ubyte ink; int m; int s; int max; int p; }
197 static struct SPGRay { int x, y; }
198 @SRZIgnore int roomIdx;
199 string title;
200 WillyStart willy;
201 ExitGfx exit;
202 int air;
203 ubyte[Height*Width] map; // 16 rows, 32 cols
204 ubyte border, ink, paper;
205 CommonGfx[] platforms;
206 CommonGfx wall;
207 CommonGfx crumb;
208 CommonGfx[] deadlies;
209 Conveyor conveyor;
210 Key keys;
211 SwitchPos[] switches;
212 Enemy[] enemies;
214 @SRZIgnore Enemy eugene;
216 @SRZIgnore int holeLen, holeY;
217 @SRZIgnore Enemy kong;
219 @SRZIgnore SkyLabEnemy[] skylabEnemies;
220 @SRZIgnore SkyLabXY[] skylab;
222 @SRZIgnore SPGRay[] spg; // solar power generator
224 @SRZIgnore private {
225 int willyX, willyY, willyDir, willyJump, willyJumpDir;
226 int willyLastMoveDir, willyFall, willyConv, willyStall;
227 public bool willyDead;
228 int frameNo;
229 public int score;
230 // keys
231 bool kLeft, kRight, kJump, kUp, kDown;
232 bool kLeftDown, kRightDown, kJumpDown, kUpDown, kDownDown;
235 @SRZIgnore private {
236 X11Image finalSpr;
237 X11Image sunSpr;
238 X11Image[] brickCache;
239 X11Image[] convCache;
240 X11Image[] exitCache;
241 X11Image[] keyCache;
242 X11Image[][] monsterCache;
243 int[][] monsterOfsCache;
244 X11Image[] eugeneSpr;
245 X11Image[] kongSpr;
246 X11Image[] skylabSpr;
247 X11Image[] switchesSpr;
248 X11Image willySpr;
251 ubyte saveKeyState () {
252 return
253 ((kLeftDown ? 1 : 0)<<0)|
254 ((kRightDown ? 1 : 0)<<1)|
255 ((kUpDown ? 1 : 0)<<2)|
256 ((kDownDown ? 1 : 0)<<3)|
257 ((kJumpDown ? 1 : 0)<<4)|
261 void restoreKeyState (ubyte b) {
262 keyLeft = ((b&(1<<0)) != 0);
263 keyRight = ((b&(1<<1)) != 0);
264 keyUp = ((b&(1<<2)) != 0);
265 keyDown = ((b&(1<<3)) != 0);
266 keyJump = ((b&(1<<4)) != 0);
269 void initRoom () {
270 void initWilly () {
271 willyX = willy.x;
272 willyY = willy.y;
273 willyDir = (willy.sd > 0 ? -1 : 1);
274 willyJump = willyFall = willyConv = willyStall = 0;
275 willyDead = false;
276 willyLastMoveDir = 0;
277 kLeft = kRight = kJump = kUp = kDown = false;
278 kLeftDown = kRightDown = kJumpDown = kUpDown = kDownDown = false;
281 buildWilly();
282 buildBrickImages();
283 buildConvImages();
284 buildExitImages();
285 buildKeysImages();
286 buildMonsterImages();
287 buildEugeneImages();
288 buildKongImages();
289 buildSkyLabImages();
290 buildSwitchImages();
291 finalSpr = buildImageMasked(gameData["final"], 256, 64);
292 sunSpr = buildImageMasked(gameData["sun"], 24, 16);
294 initWilly();
296 frameNo = 0;
297 foreach (ref c; map) {
298 if (c == 4) c = 8; // 'crumb'
299 else if (c < 1 || c > 7) c = 0; // emptyness
302 // rebuild keys
303 KeyPos[] kinfo;
304 foreach (const ref ki; keys.info) {
305 if (ki.s == 0) continue;
306 kinfo ~= ki;
308 keys.info = kinfo;
310 platforms = platforms.dup;
311 deadlies = deadlies.dup;
312 switches = switches.dup;
313 enemies = enemies.dup;
315 // Eugene?
316 if (roomIdx == 4) {
317 Enemy[] a = [{ x:120, y:1, d/*ir*/:0, min:1, max:87, ink:6 }];
318 eugene = a[0];
320 // Kong?
321 if (roomIdx == 7 || roomIdx == 11) {
322 Enemy[] a = [{ x:120, y:0, max:104, frame:0, ink:2, m:0 }];
323 kong = a[0];
324 holeLen = 2*8;
325 holeY = 0;
327 // SkyLab?
328 if (roomIdx == 13) {
329 enemies = null;
330 SkyLabEnemy[] a = [
331 {p:0, s:4, ink:6, max:72, m:0, frame:0},
332 {p:2, s:3, ink:5, max:56, m:0, frame:0},
333 {p:1, s:1, ink:4, max:32, m:0, frame:0}
335 skylabEnemies = a.dup;
336 foreach (immutable f, const ref se; skylabEnemies) {
337 skylab ~= SkyLabXY(
338 skylabCoords[f][se.p].x, skylabCoords[f][se.p].y,
339 se.frame, se.ink, se.m, se.s, se.max, se.p,
343 // Solar Power Generator?
344 if (roomIdx == 18) buildSPG();
347 void keyLeft (bool pressed) { if (pressed) kLeft = true; kLeftDown = pressed; }
348 void keyRight (bool pressed) { if (pressed) kRight = true; kRightDown = pressed; }
349 void keyUp (bool pressed) { if (pressed) kUp = true; kUpDown = pressed; }
350 void keyDown (bool pressed) { if (pressed) kDown = true; kDownDown = pressed; }
351 void keyJump (bool pressed) { if (pressed) kJump = true; kJumpDown = pressed; }
353 void stepGame () {
354 kLeft = kLeft||kLeftDown;
355 kRight = kRight||kRightDown;
356 kUp = kUp||kUpDown;
357 kDown = kDown||kDownDown;
358 kJump = kJump||kJumpDown;
359 scope(exit) kLeft = kRight = kJump = kUp = kDown = false;
360 ++frameNo;
361 conveyor.frame += (conveyor.d ? -1 : 1);
362 if (conveyor.frame < 0) conveyor.frame = 3; else if (conveyor.frame > 3) conveyor.frame = 0;
364 if (!willyJump) stepCrumb();
365 stepMonsters();
366 stepWillyActions();
367 stepKeys();
368 stepSwitch();
369 buildSPG();
372 void cheatRemoveKeys () { keys.info = null; }
374 // ////////////////////////////////////////////////////////////////////// //
375 // checkers
377 // x & y: in pixels
378 ubyte getBlockAt (int x, int y, bool simplify=false) const nothrow @nogc {
379 x = x>>3;
380 y = y>>3;
381 if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
382 ubyte b = map[y*32+x];
383 if (simplify) {
384 if (b > 15) b = 0;
385 else if (b > 7) b = 4;
386 else if (b == 6) b = 5;
387 else if (b == 2) b = 1;
389 return b;
392 bool checkExit () const nothrow @nogc {
393 if (keys.info.length != 0) return false;
394 auto x = exit.x;
395 auto y = exit.y;
396 return (willyX >= x-2 && willyX+10 <= x+18 && willyY >= y-5 && willyY+16 <= y+22);
399 bool checkMonsters () const nothrow {
400 foreach (immutable f, const ref m; enemies) {
401 auto cc = monsterOfsCache[f];
402 if (m.x < 0 || m.y < 0) continue;
403 if (m.vert) {
404 if (pixelCheckMonster(m.x, m.y, gameData["vrobo"], cc[m.anim])) return true;
405 } else {
406 if (pixelCheckMonster(m.x&248, m.y, gameData["hrobo"], cc[((m.x&m.anim)&0xfe)+m.d/*ir*/])) return true;
409 // Eugene?
410 if (eugene.x >= 0) {
411 enum curLSet = 0;
412 if (pixelCheckMonster(eugene.x, eugene.y, gameData["eugene"], 256*curLSet)) return true;
414 // Kong?
415 if (kong.x >= 0) {
416 if (pixelCheckMonster(kong.x, kong.y, gameData["kong"], 256*kong.frame)) return true;
418 // SkyLab?
419 if (skylab.length) {
420 foreach (const ref sk; skylab) {
421 if (pixelCheckMonster(sk.x, sk.y, gameData["sky"], 256*sk.frame)) return true;
424 return false;
427 void checkSwitch () nothrow @nogc {
428 foreach (ref ss; switches) {
429 if (ss.s/*tate*/ != 1) continue;
430 auto x = ss.x;
431 auto y = ss.y;
432 if (x+7 >= willyX && y+7 >= willyY && x < willyX+8 && y < willyY+16) ss.s/*tate*/ = 1+1;
436 bool checkSPG () const nothrow @nogc {
437 foreach (const ref sp; spg) {
438 auto x = sp.x*8;
439 auto y = sp.y*8;
440 if (x < 0 || y < 0) break;
441 if (x+7 >= willyX && x < willyX+8 && y+7 >= willyY && y < willyY+16) return true;
443 return false;
446 X11Image draw (X11Image img=null) {
447 if (img is null) img = new X11Image(8*Room.Width, 8*Room.Height);
448 int pos = 0;
449 foreach (immutable y; 0..img.height) {
450 foreach (immutable x; 0..img.width) {
451 img.setPixel(x, y, palcol(this.paper));
454 foreach (immutable y; 0..Room.Height) {
455 foreach (immutable x; 0..Room.Width) {
456 ubyte blk = this.map[pos++];
457 if (blk < 1 || blk == 7) continue;
458 if (blk == 4) blk = 8;
459 if (blk < brickCache.length && brickCache[blk] !is null) {
460 brickCache[blk].blitTo(img, x*8, y*8);
464 if (this.roomIdx == 19) {
465 finalSpr.blitTo(img, 0, 0);
466 sunSpr.blitTo(img, 60, 32);
469 void drawConveyor () {
470 auto conv = &this.conveyor;
471 if (conv.l < 1) return;
472 auto y = conv.y;
473 if (y <= 0) return;
474 convCache[conv.frame].blitTo(img, conv.x, conv.y, conv.l);
477 void drawExit () {
478 int br;
479 if (this.keys.info.length != 0) {
480 br = 0;
481 } else {
482 br = this.frameNo&31;
483 if (br > 15) br = 31-br;
484 ++br;
486 exitCache[br].blitTo(img, this.exit.x, this.exit.y);
489 void drawKeys () {
490 auto ff = this.frameNo%16;
491 if (ff >= 8) ff = 15-ff;
492 auto keyimg = keyCache[ff];
493 foreach_reverse (const ref ki; this.keys.info) {
494 if (ki.x < 0 || ki.y < 0) continue;
495 //eraseRect(ki.x, ki.y, 8, 8);
496 if (!ki.s) continue;
497 keyimg.blitTo(img, ki.x&248, ki.y);
501 void drawMonsters () {
502 foreach (immutable f, const ref m; this.enemies) {
503 if (m.x < 0 || m.y < 0) continue;
504 auto slist = monsterCache[f];
505 auto x = m.x;
506 if (m.vert) {
507 //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
508 slist[m.anim].blitTo(img, x, m.y);
509 } else {
510 auto sidx = ((x&m.anim)&0xfe)+m.d/*ir*/;
511 if (sidx < slist.length) {
512 slist[sidx].blitTo(img, x&248, m.y);
513 } else {
514 import core.stdc.stdio : stderr, fprintf;
515 stderr.fprintf("monster #%u is fucked: sidx=%u; max=%u", cast(uint)f, cast(uint)sidx, cast(uint)slist.length);
519 if (this.eugene.x >= 0) {
520 enum curLSet = 0;
521 eugeneSpr[curLSet*8+this.eugene.ink].blitTo(img, this.eugene.x, this.eugene.y);
523 if (this.kong.x >= 0) {
524 kongSpr[this.kong.frame*8+this.kong.ink].blitTo(img, this.kong.x, this.kong.y);
526 if (this.skylab.length) {
527 foreach (const ref sk; this.skylab) {
528 skylabSpr[sk.frame*8+sk.ink].blitTo(img, sk.x, sk.y);
533 void drawSwitch () {
534 foreach (const ref ss; this.switches) {
535 if (ss.s == 0) continue;
536 switchesSpr[ss.s-1].blitTo(img, ss.x, ss.y);
540 void drawSPG () {
541 foreach (const ref sp; this.spg) {
542 auto x = sp.x*8;
543 auto y = sp.y*8;
544 if (x < 0 || y < 0) break;
545 //ctx.fillStyle = mainPal[noerase?6:this.paper];
546 //ctx.fillRect(x, y, 8, 8);
547 foreach (immutable dy; 0..8) {
548 foreach (immutable dx; 0..8) {
549 img.setPixel(x+dx, y+dy, palcol(6));
555 void drawWilly () {
556 auto willyPos = (this.willyX&15)>>1;
557 if (this.willyDir < 0) willyPos += 8;
558 if (debugColdet) {
559 auto wy = 0;
560 if (this.checkMonsters()) wy = 16;
561 willySpr.blitTo(willyPos*16, wy, 16, 16, img, this.willyX&248, this.willyY);
562 } else {
563 willySpr.blitTo(willyPos*16, 0, 16, 16, img, this.willyX&248, this.willyY);
567 drawConveyor();
568 drawKeys();
569 drawMonsters();
570 drawSwitch();
571 drawWilly();
572 drawExit();
573 drawSPG();
575 return img;
578 private:
579 // ////////////////////////////////////////////////////////////////////// //
580 // pixel-perfect collision detector
581 // fully stolen from Andy's sources %-)
582 bool pixelCheckMonster (int rx, int ry, const(ubyte)[] darr, int dpos=0) const nothrow {
583 static ubyte[256] mpcGrid;
584 assert(dpos >= 0);
585 //int x, y, w;
586 int x, w;
587 rx -= willyX&248;
588 ry -= willyY;
589 if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
590 // clear grid
591 mpcGrid[] = 0;
592 if (rx < 0) { x = 0; rx = -rx; w = 16-rx; } else { x = rx; rx = 0; w = 16-x; }
593 // partial plot monster
594 for (int y = ry+15; y >= ry; --y) {
595 if (y >= 0 && y < 16) {
596 int gp = y*16+x;
597 int rp = dpos+(y-ry)*16+rx;
598 for (int dx = 0; dx < w; ++dx, ++gp, ++rp) mpcGrid[gp] = darr[rp];
601 auto warr = gameData["willy"];
602 int wptr = ((willyX&15)>>1)*256+(willyDir < 0 ? 2048 : 0);
603 // check for collision
604 int mp = 0;
605 for (x = 255; x >= 0; --x, ++wptr, ++mp) if (warr[wptr] && mpcGrid[mp]) return true;
606 return false;
609 // ////////////////////////////////////////////////////////////////////// //
610 bool isWillyFalling () const nothrow @nogc {
611 if (willyY&7) return true;
612 for (int dx = 0; dx <= 8; dx += 8) {
613 ubyte b = getBlockAt(willyX+dx, willyY+16, true);
614 if (b > 0 && b != 5) return false;
616 return true;
619 bool isWillyInDeadly () const nothrow @nogc {
620 for (int dx = 0; dx <= 8; dx += 8) {
621 for (int dy = 0; dy <= 16; dy += 8) {
622 if (getBlockAt(willyX+dx, willyY+dy, true) == 5) return true;
625 return false;
628 bool isWillyOnConv () const nothrow @nogc {
629 if (willyY&7) return false;
630 ubyte b0 = getBlockAt(willyX, willyY+16);
631 ubyte b1 = getBlockAt(willyX+8, willyY+16);
632 return (b0 == 7 || b1 == 7);
635 // ////////////////////////////////////////////////////////////////////// //
636 // called on each frame
637 void buildSPG (bool forced=false) {
638 if (!forced && roomIdx != 18) {
639 spg = null;
640 return;
643 bool spgCheckMonster (int x, int y) {
644 x *= 8;
645 y *= 8;
646 foreach (const ref cm; enemies) {
647 auto mx = cm.x;
648 auto my = cm.y;
649 if (x+7 >= mx && x < mx+15 && y+7 >= my && y < my+15) return true;
651 return false;
654 int x = 23, y = 0;
655 bool done = false;
656 int dir = 0, idx = 0;
658 if (spg.length) {
659 spg.length = 0;
660 spg.assumeSafeAppend;
663 void addXY (int x, int y) {
664 ++idx;
665 while (spg.length < idx) spg ~= SPGRay(-1, -1);
666 spg[idx-1].x = x;
667 spg[idx-1].y = y;
670 do {
671 ubyte blockhit = map[y*32+x];
672 bool robohit = spgCheckMonster(x, y);
674 if (blockhit && robohit) {
675 addXY(-1, -1);
676 done = true;
677 } else if (!blockhit && robohit) {
678 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
679 spg[idx-1].x = spg[idx-1].y = -1;
680 done = true;
681 } else {
682 addXY(x, y);
684 dir ^= 1;
685 } else if (!blockhit && !robohit) {
686 addXY(x, y);
687 } else if (blockhit && !robohit) {
688 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
689 spg[idx-1].x = spg[idx-1].y = -1;
690 done = true;
692 dir ^= 1;
695 if (!blockhit) {
696 if (!dir) {
697 ++y;
698 blockhit = map[y*32+x];
699 if (y == 15 || blockhit) done = true;
700 } else {
701 --x;
702 blockhit = map[y*32+x];
703 if (x == 0 || blockhit) done = true;
705 } else {
706 if (!dir) { --x; if (!x) done = true; }
707 else { ++y; if (++y == 15) done = true; }
709 } while (!done);
710 addXY(-1, -1);
713 void stepMonsters () {
714 foreach (ref m; enemies) {
715 if (m.x < 0 || m.y < 0) continue;
716 if (m.vert) {
717 auto y = m.y;
718 auto spd = m.s/*peed*/;
719 if (m.d/*ir*/ != 0) {
720 // up
721 y -= spd;
722 if (y < m.min || y < 0) { y += spd; m.d/*ir*/ = 0; }
723 } else {
724 // down
725 y += spd;
726 if (y > m.max) { y -= spd; m.d/*ir*/ = 1; }
728 m.y = y;
729 m.anim = (m.anim+1)&3;
730 } else {
731 auto x = m.x;
732 auto spd = (2>>m.s/*peed*/);
733 if (m.d/*ir*/ != 0) {
734 // left
735 x -= spd;
736 if (x < m.min) { m.d/*ir*/ = 0; x += spd; }
737 } else {
738 // right
739 x += spd;
740 if (x > m.max+6) { m.d/*ir*/ = 1; x -= spd; }
742 m.x = x;
745 // Eugene
746 if (eugene.x >= 0) {
747 if (keys.info.length == 0) {
748 // no keys, Eugene tries to block the exit
749 eugene.ink = (eugene.ink+1)&7;
750 eugene.y += (eugene.y < eugene.max ? 1 : 0);
751 } else {
752 if (eugene.d/*ir*/ != 0) {
753 // up
754 --eugene.y;
755 if (eugene.y < eugene.min) { eugene.d/*ir*/ = 0; ++eugene.y; }
756 } else {
757 // down
758 ++eugene.y;
759 if (eugene.y > eugene.max) { eugene.d/*ir*/ = 1; --eugene.y; }
763 // Kong
764 if (kong.x >= 0) {
765 switch (kong.falling) {
766 case 1: // just started
767 map[2*32+15] = 0;
768 map[2*32+16] = 0;
769 //eraseRect(16, 120, 16, 8);
770 kong.falling = 2;
771 break;
772 case 2:
773 kong.ink = 4;
774 kong.frame += 2;
775 kong.falling = 3;
776 break;
777 case 3:
778 kong.y += 4;
779 if (kong.y >= kong.max) { kong.x = -1; score += 100; }
780 if (!kong.delay) { kong.delay = 4; kong.frame = ((kong.frame-1)&1)+2; } else --kong.delay;
781 break;
782 default:
783 if (!kong.delay) { kong.delay = 8; kong.frame = (kong.frame+1)&1; } else --kong.delay;
784 break;
787 // SkyLab
788 foreach (immutable idx, ref sk; skylab) {
789 switch (sk.m) {
790 case 0:
791 sk.y += sk.s;
792 if (sk.y > sk.max) {
793 sk.y = sk.max;
794 sk.m = 1;
795 ++sk.frame;
797 break;
798 case 1:
799 ++sk.frame;
800 if (sk.frame == 7) sk.m = 2;
801 break;
802 case 2:
803 sk.p = (sk.p+1)&3;
804 sk.x = skylabCoords[idx][sk.p].x;
805 sk.y = skylabCoords[idx][sk.p].y;
806 sk.frame = sk.m = 0;
807 break;
808 default:
813 void stepCrumb () {
814 if (willyY&7) return;
815 for (int f = 0; f <= 8; f += 8) {
816 int x = willyX+f;
817 int y = willyY+16;
818 ubyte b = getBlockAt(x, y);
819 if (b == 4) b = 8;
820 if (b < 8) continue;
821 x >>= 3;
822 y >>= 3;
823 if (++b > 15) b = 0;
824 map[y*32+x] = b;
825 //eraseRect(x*8, y*8, 8, 8);
829 void stepKeys () {
830 while (keys.info.length) {
831 bool again = false;
832 foreach (immutable idx; 0..keys.info.length) {
833 auto ki = &keys.info[idx];
834 assert(ki.s);
835 int kx = ki.x;
836 int ky = ki.y;
837 if (kx+7 >= willyX && kx < willyX+10 && ky+7 >= willyY && ky < willyY+16) {
838 score += 100;
839 foreach (immutable c; idx+1..keys.info.length) keys.info[c-1] = keys.info[c];
840 keys.info.length -= 1;
841 again = true;
842 break;
845 if (!again) break;
849 void stepSwitch () {
850 // hole?
851 if (holeLen > 0 && switches.length && switches[0].s/*tate*/ > 1) {
852 if (holeLen < 0) {
853 //FIXME
854 enemies[1].max += 24;
855 holeLen = 0;
856 } else {
857 ++holeY;
858 if (holeY == 16) {
859 map[11*32+17] = 0;
860 map[12*32+17] = 0;
861 holeLen = -1;
865 // Kong?
866 if (kong.x >= 0 && !kong.falling && switches.length > 1 && switches[1].s/*tate*/ > 1) kong.falling = 1;
869 void stepWillyActions () {
870 bool doWillyLeft () {
871 if (willyDir > 0) { willyDir = -1; return true; }
872 auto xx = willyX-2;
873 auto b0 = getBlockAt(xx, willyY);
874 auto b1 = getBlockAt(xx, willyY+8);
875 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
876 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
877 willyX -= 2;
878 if (willyX < 0) willyX += 240;
879 willyLastMoveDir = -1;
880 return true;
883 bool doWillyRight () {
884 if (willyDir < 0) { willyDir = 1; return true; }
885 if (willyX > 245) return false;
886 auto xx = willyX+10;
887 auto b0 = getBlockAt(xx, willyY);
888 auto b1 = getBlockAt(xx, willyY+8);
889 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
890 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
891 willyX += 2;
892 if (willyX > 240) willyX -= 240;
893 willyLastMoveDir = 1;
894 return true;
897 void doWillyJump () {
898 if (!willyJump) return;
899 willyY += willyJ[willyJump];
900 auto x = willyX;
901 auto mv = false;
902 if (willyJumpDir < 0) mv = doWillyLeft(); else if (willyJumpDir > 0) mv = doWillyRight();
903 if (willyJump < 9) {
904 willyFall = 0;
905 // up
906 auto b0 = getBlockAt(x, willyY);
907 auto b1 = getBlockAt(x+8, willyY);
908 if (b0 == 3 || b1 == 3) {
909 // headboom! (apstenu %-)
910 willyX = x;
911 willyY -= willyJ[willyJump];
912 willyJump = 0; // enough flying
913 return;
915 } else {
916 // down
917 if (willyJump > 12) willyFall += willyJ[willyJump];
918 if ((willyY&7) == 0) {
919 auto b0 = getBlockAt(willyX, willyY+16);
920 auto b1 = getBlockAt(willyX+8, willyY+16);
921 if (b0 || b1) {
922 if (b0 == 3 || b1 == 3) willyX = x;
923 willyFall = 0; // can't fall too deep while jumping
924 willyJump = 0; // enough flying
925 if (b0 == 7 || b1 == 7) willyStall = 1; // conveyor?
926 return;
930 ++willyJump;
931 if (willyJump > 18) willyJump = 0;
935 if (willyDead) return;
936 checkSwitch();
937 if (isWillyInDeadly()) { willyDead = true; return; }
938 if (!debugColdet) {
939 if (checkMonsters()) { willyDead = true; return; }
940 } else {
941 if (checkMonsters()) singleStep = singleStepWait = true;
944 auto wasJump = false;
945 if (willyJump) {
946 willyLastMoveDir = 0;
947 doWillyJump();
948 if (willyJump) return;
949 wasJump = true;
952 auto falling = isWillyFalling();
953 if (!kDown && falling) {
954 willyConv = willyStall = willyLastMoveDir = 0;
955 willyFall += 4;
956 willyY += 4;
957 if (willyY > 112) willyY -= 112;
958 return;
961 if (!falling && willyFall > 34) willyDead = true; // too high!
962 auto lfall = willyFall;
963 willyFall = 0;
965 if (willyDead) return;
967 auto dx = (kLeft ? -1 : kRight ? 1 : 0);
968 if (isWillyOnConv()) {
969 auto cdir = (conveyor.d ? 1 : -1);
970 //dx==cdir,!dx,lastmove==cdir
971 if (willyLastMoveDir == cdir || dx == cdir || !dx) { willyConv = cdir; willyStall = 0; } // was moving in conv. dir or standing
972 if (!willyConv) {
973 // Willy just steps on the conveyor, and Willy walking to the opposite side
974 willyStall = 0;
975 if (wasJump && willyLastMoveDir == -cdir) {
976 willyConv = dx; // from jump, can do opposite
977 } else {
978 willyConv = dx;
979 if (lfall > 0 || !willyLastMoveDir) { dx = 0; willyStall = 1; } // lands on conveyor, not from jump
981 } else {
982 // Willy was on conveyor
983 dx = (willyStall ? 0 : willyConv);
985 } else {
986 willyConv = willyStall = 0;
989 //if (willyConv) dx = willyConv;
990 if (kUp && !wasJump) {
991 willyConv = willyStall = willyLastMoveDir = 0;
992 willyJumpDir = dx;
993 willyJump = 1;
994 doWillyJump();
995 return;
997 if (kDown) willyY -= 8;
998 willyLastMoveDir = 0;
999 if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
1002 // ////////////////////////////////////////////////////////////////////// //
1003 void buildWilly () {
1004 auto img = new X11Image(16*16, 16+16);
1005 auto ww = gameData["willy"];
1006 foreach (immutable f; 0..16) {
1007 foreach (immutable y; 0..16) {
1008 foreach (immutable x; 0..16) {
1009 ubyte c = ww[f*256+y*16+x];
1010 if (!c) {
1011 img.setPixel(f*16+x, y, Transparent);
1012 } else {
1013 img.setPixel(f*16+x, y, palcol(c));
1014 // white
1015 img.setPixel(f*16+x, y+16, palcol(15));
1020 willySpr = img;
1023 void buildBrickImages () {
1024 auto buildBrickImage (in ref Room.CommonGfx brk, int skipy=0) {
1025 return buildImageBrick(gameData["blocks"][brk.gfx*64..$], brk.ink, this.paper, skipy);
1027 brickCache = null;
1028 foreach (immutable f; 0..8) {
1029 X11Image img;
1030 switch (f) {
1031 //case 0: case 7: img = buildBrickImage(this.wall, 16); break;
1032 case 1: img = buildBrickImage(this.platforms[0]); break;
1033 case 2: img = buildBrickImage(this.platforms[1]); break;
1034 case 3: img = buildBrickImage(this.wall); break;
1035 //case 4: img = buildBrickImage(this.crumb); break;
1036 case 5: img = buildBrickImage(this.deadlies[0]); break;
1037 case 6: img = buildBrickImage(this.deadlies[1]); break;
1038 default:
1040 brickCache ~= img;
1042 foreach (immutable f; 0..9) brickCache ~= buildBrickImage(this.crumb, f);
1045 void buildConvImages () {
1046 convCache = null;
1047 auto conv = &this.conveyor;
1048 if (conv.y <= 0 || conv.l < 1) return;
1049 foreach (immutable f; 0..4) {
1050 convCache ~= buildImageBrick(gameData["conv"][conv.gfx*256+f*64..$], conv.ink, this.paper);
1054 void buildExitImages () {
1055 exitCache = null;
1056 exitCache ~= buildImageMasked(gameData["exits"][this.exit.gfx*256..$], 16, 16);
1057 foreach (immutable f; 0..16) exitCache ~= buildImageMaskedShiny(gameData["exits"][this.exit.gfx*256..$], f, 16, 16);
1060 void buildKeysImages () {
1061 keyCache = null;
1062 foreach (immutable f; 0..16) keyCache ~= buildImageMaskedShiny(gameData["keys"][this.keys.gfx*64..$], f, 8, 8);
1065 void buildMonsterImages () {
1066 monsterCache = null;
1067 monsterOfsCache = null;
1068 foreach (immutable f, const ref m; this.enemies) {
1069 if (m.x < 0 || m.y < 0) {
1070 monsterOfsCache ~= null;
1071 monsterCache ~= null;
1072 continue;
1074 X11Image[] r;
1075 int[] cc;
1076 if (m.vert) {
1077 foreach (immutable c; 0..4) {
1078 auto n = (m.gfx+c)*256;
1079 cc ~= n;
1080 r ~= buildImageMaskedInk(gameData["vrobo"][n..$], m.ink-1, 16, 16);
1082 } else {
1083 foreach (immutable c; 0..(m.anim>>1)+1) {
1084 auto n = (m.gfx+c)*256;
1085 cc ~= n;
1086 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1087 n += m.flip*256;
1088 cc ~= n;
1089 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1092 monsterOfsCache ~= cc;
1093 monsterCache ~= r;
1097 void buildEugeneImages () {
1098 eugeneSpr = null;
1099 for (int f = 0; f <= 256; f += 256) {
1100 foreach (immutable c; 0..8) {
1101 eugeneSpr ~= buildImageMaskedInk(gameData["eugene"][f..$], c, 16, 16);
1106 void buildKongImages () {
1107 kongSpr = null;
1108 foreach (immutable f; 0..4) {
1109 foreach (immutable c; 0..8) {
1110 kongSpr ~= buildImageMaskedInk(gameData["kong"][f*256..$], c, 16, 16);
1115 void buildSkyLabImages () {
1116 skylabSpr = null;
1117 foreach (immutable f; 0..8) {
1118 foreach (immutable c; 0..8) {
1119 skylabSpr ~= buildImageMaskedInk(gameData["sky"][f*256..$], c, 16, 16);
1124 void buildSwitchImages () {
1125 switchesSpr = null;
1126 ubyte[] swg;
1127 if (auto pp = "switches" in gameData) swg = *pp; else swg = gameData["switch"];
1128 foreach (immutable f; 0..2) {
1129 switchesSpr ~= buildImageBrick(swg[f*64..$], this.platforms[1].ink, this.paper);
1133 private:
1134 void load (ref const(ubyte)[] data) {
1135 void readBuf (void[] buf) {
1136 import core.stdc.string : memcpy;
1137 if (buf.length == 0) return;
1138 if (data.length < buf.length) throw new Exception("invalid level");
1139 memcpy(buf.ptr, data.ptr, buf.length);
1140 data = data[buf.length..$];
1143 ubyte readU8 () {
1144 if (data.length < 1) throw new Exception("invalid level");
1145 ubyte res = data[0];
1146 data = data[1..$];
1147 return res;
1150 short readS16 () {
1151 if (data.length < 2) throw new Exception("invalid level");
1152 short res = cast(short)(data[0]|(data[1]<<8));
1153 data = data[2..$];
1154 return res;
1157 char[33] title;
1158 readBuf(map[]);
1159 readBuf(title[]);
1160 const(char)[] tit;
1161 foreach (immutable idx, char ch; title[]) {
1162 if (ch == 0) break;
1163 tit = title[0..idx];
1165 while (tit.length && tit[0] <= ' ') tit = tit[1..$];
1166 while (tit.length && tit[$-1] <= ' ') tit = tit[0..$-1];
1167 this.title = tit.idup;
1169 border = readU8;
1170 ink = readU8;
1171 paper = readU8;
1173 platforms.length = 2;
1174 foreach (immutable idx; 0..2) {
1175 CommonGfx g;
1176 g.ink = readU8;
1177 g.paper = readU8;
1178 g.gfx = readU8;
1179 platforms[idx] = g;
1182 wall.ink = readU8;
1183 wall.paper = readU8;
1184 wall.gfx = readU8;
1186 crumb.ink = readU8;
1187 crumb.paper = readU8;
1188 crumb.gfx = readU8;
1190 deadlies.length = 2;
1191 foreach (immutable idx; 0..2) {
1192 CommonGfx g;
1193 g.ink = readU8;
1194 g.paper = readU8;
1195 g.gfx = readU8;
1196 deadlies[idx] = g;
1199 conveyor.ink = readU8;
1200 conveyor.paper = readU8;
1201 conveyor.gfx = readU8;
1203 willy.x = readS16;
1204 willy.y = readS16;
1205 willy.sd = readU8;
1207 conveyor.x = readS16;
1208 conveyor.y = readS16;
1209 conveyor.d = readU8;
1210 conveyor.l = readU8;
1212 keys.info.length = 5;
1213 foreach (immutable idx; 0..5) keys.info[idx].x = readS16;
1214 foreach (immutable idx; 0..5) keys.info[idx].y = readS16;
1215 keys.gfx = readU8;
1216 foreach (immutable idx; 0..5) keys.info[idx].s = readU8;
1218 switches.length = 2;
1219 foreach (immutable idx; 0..2) switches[idx].x = readS16;
1220 foreach (immutable idx; 0..2) switches[idx].y = readS16;
1221 foreach (immutable idx; 0..2) switches[idx].s = readU8;
1223 exit.x = readS16;
1224 exit.y = readS16;
1225 exit.gfx = readU8;
1227 air = readU8;
1229 enemies.length = 4*2;
1231 foreach (immutable idx; 0..4) enemies[idx].vert = false;
1232 foreach (immutable idx; 0..4) enemies[idx].ink = readU8;
1233 foreach (immutable idx; 0..4) enemies[idx].paper = readU8;
1234 foreach (immutable idx; 0..4) enemies[idx].x = readS16;
1235 foreach (immutable idx; 0..4) enemies[idx].y = readS16;
1236 foreach (immutable idx; 0..4) enemies[idx].min = readS16;
1237 foreach (immutable idx; 0..4) enemies[idx].max = readS16;
1238 foreach (immutable idx; 0..4) enemies[idx].d = readU8;
1239 foreach (immutable idx; 0..4) enemies[idx].s = readU8;
1240 foreach (immutable idx; 0..4) enemies[idx].gfx = readS16;
1241 foreach (immutable idx; 0..4) enemies[idx].flip = readU8;
1242 foreach (immutable idx; 0..4) enemies[idx].anim = readU8;
1244 foreach (immutable idx; 4..8) enemies[idx].vert = true;
1245 foreach (immutable idx; 4..8) enemies[idx].ink = readU8;
1246 foreach (immutable idx; 4..8) enemies[idx].paper = readU8;
1247 foreach (immutable idx; 4..8) enemies[idx].x = readS16;
1248 foreach (immutable idx; 4..8) enemies[idx].y = readS16;
1249 foreach (immutable idx; 4..8) enemies[idx].min = readS16;
1250 foreach (immutable idx; 4..8) enemies[idx].max = readS16;
1251 foreach (immutable idx; 4..8) enemies[idx].d = readU8;
1252 foreach (immutable idx; 4..8) enemies[idx].s = readU8;
1253 foreach (immutable idx; 4..8) enemies[idx].gfx = readS16;
1254 foreach (immutable idx; 4..8) enemies[idx].anim = readU8;
1259 // ////////////////////////////////////////////////////////////////////////// //
1260 public void blitTo (X11Image src, X11Image dst, int x, int y, int repx=1) {
1261 foreach (immutable r; 0..repx) {
1262 foreach (immutable dy; 0..src.height) {
1263 foreach (immutable dx; 0..src.width) {
1264 int xx = x+dx;
1265 int yy = y+dy;
1266 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1267 auto c = src.getPixel(dx, dy);
1268 if (isTransparent(c)) continue;
1269 dst.setPixel(xx, yy, c);
1272 x += src.width;
1277 public void blitTo (X11Image src, int x0, int y0, int ww, int hh, X11Image dst, int x, int y) {
1278 foreach (immutable dy; 0..ww) {
1279 foreach (immutable dx; 0..hh) {
1280 int xx = x+dx;
1281 int yy = y+dy;
1282 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1283 auto c = src.getPixel(x0+dx, y0+dy);
1284 if (isTransparent(c)) continue;
1285 dst.setPixel(xx, yy, c);