use wad files instead of json data
[mmd.git] / engine.d
blob41a43145bb3b6b99b63de2d1946a464c61c407af
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;
16 __gshared string gameLevelsName;
19 public void loadGameData () {
20 //gameData.txtunser(VFile("data/data.js"));
22 foreach (const ref de; vfsAllFiles()) {
23 auto fi = VFile(de.name);
24 if (fi.size == 0 || fi.size > 1024*1024*8) continue;
25 auto data = new ubyte[](cast(uint)fi.size);
26 fi.rawReadExact(data[]);
27 gameData[de.name.idup] = data;
30 //gameRooms.txtunser(VFile("data/levelset0.js"));
31 //foreach (immutable idx, ref room; gameRooms) room.roomIdx = cast(int)idx;
32 gameRooms.length = 20;
34 char[8] sign;
35 auto fi = VFile("levels");
36 fi.rawReadExact(sign[]);
37 if (sign != "MANIC\x0d\x0a\x1a") throw new Exception("invalid levels");
38 char[16] lname;
39 fi.rawReadExact(lname[]);
40 const(char)[] nn;
41 foreach (immutable idx, char ch; lname[]) {
42 if (ch == 0) break;
43 nn = lname[0..idx];
45 while (nn.length && nn[$-1] <= ' ') nn = nn[0..$-1];
46 gameLevelsName = nn.idup;
47 foreach (immutable ridx; 0..20) {
48 gameRooms[ridx].roomIdx = cast(int)ridx;
49 gameRooms[ridx].load(fi);
55 // ////////////////////////////////////////////////////////////////////////// //
56 // image builders
57 VColor palcol (ubyte c) {
58 static VColor[256] palette;
59 static bool palset = false;
60 if (!palset) {
61 auto pp = gameData["palmain"];
62 foreach (immutable cc; 0..256) {
63 ubyte r = cast(ubyte)(255*pp[cc*3+0]/63);
64 ubyte g = cast(ubyte)(255*pp[cc*3+1]/63);
65 ubyte b = cast(ubyte)(255*pp[cc*3+2]/63);
66 palette[cc] = rgbcol(r, g, b);
68 palset = true;
70 return palette[c];
74 // ////////////////////////////////////////////////////////////////////////// //
75 private X11Image buildImageMasked (const(ubyte)[] darr, int w, int h) {
76 assert(w > 0 && h > 0);
77 int dpos = 0;
78 auto img = new X11Image(w, h);
79 foreach (immutable y; 0..h) {
80 foreach (immutable x; 0..w) {
81 ubyte c = darr[dpos++];
82 if (c) {
83 img.setPixel(x, y, palcol(c));
84 } else {
85 img.setPixel(x, y, Transparent);
89 return img;
93 private X11Image buildImageMaskedShiny (const(ubyte)[] darr, int brightness, int w, int h) {
94 assert(w > 0 && h > 0);
95 int dpos = 0;
96 auto img = new X11Image(w, h);
97 foreach (immutable y; 0..h) {
98 foreach (immutable x; 0..w) {
99 ubyte c = darr[dpos++];
100 if (c) {
101 int b = (c&15)-brightness;
102 if (b < 0) b = 0; else if (b > 15) b = 15;
103 img.setPixel(x, y, palcol(cast(ubyte)((c&240)|b)));
104 } else {
105 img.setPixel(x, y, Transparent);
109 return img;
113 private X11Image buildImageMaskedInk (const(ubyte)[] darr, int ink, int w, int h) {
114 assert(w > 0 && h > 0);
115 int dpos = 0;
116 auto img = new X11Image(w, h);
117 if (ink < 0) ink = 0;
118 ink *= 16;
119 foreach (immutable y; 0..h) {
120 foreach (immutable x; 0..w) {
121 ubyte c = darr[dpos++];
122 if (c) {
123 img.setPixel(x, y, palcol(cast(ubyte)(c+ink)));
124 } else {
125 img.setPixel(x, y, Transparent);
129 return img;
133 // ////////////////////////////////////////////////////////////////////////// //
134 private X11Image buildImageBrick (const(ubyte)[] darr, int ink, int paper, int skipy=0) {
135 assert(skipy >= 0);
136 enum { w = 8, h = 8 }
137 int dpos = 0;
138 auto img = new X11Image(w, h);
139 ink *= 16;
140 foreach (immutable y; 0..h) {
141 foreach (immutable x; 0..w) {
142 ubyte c = (y >= skipy ? darr[dpos++] : 0);
143 img.setPixel(x, y, palcol(cast(ubyte)(c ? ink+c : paper)));
146 return img;
150 // ////////////////////////////////////////////////////////////////////////// //
151 // willy jump offsets
152 private:
153 static immutable int[19] willyJ = [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
155 struct SkyLabXY { int x, y; }
156 static immutable SkyLabXY[4][3] skylabCoords = [
157 [{x: 8,y:0}, {x: 72,y:0}, {x:136,y:0}, {x:200,y:0}],
158 [{x:40,y:0}, {x:104,y:0}, {x:168,y:0}, {x:232,y:0}],
159 [{x:24,y:0}, {x: 88,y:0}, {x:152,y:0}, {x:216,y:0}]
163 // ////////////////////////////////////////////////////////////////////////// //
164 // suitable to "unjson"
165 public struct Room {
166 enum { Width = 32, Height = 16 }
167 static struct WillyStart { int x; int y; int sd; }
168 static struct ExitGfx { ubyte gfx; int x; int y; }
169 static struct CommonGfx { ubyte gfx; ubyte ink; ubyte paper; }
170 static struct Conveyor { int x; int y; int d; int l; ubyte gfx; ubyte ink; ubyte paper; @SRZIgnore int frame; }
171 static struct KeyPos { int x; int y; int s; }
172 static struct Key { ubyte gfx; KeyPos[] info; }
173 static struct SwitchPos { int x; int y; int s; }
174 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; }
175 static struct SkyLabEnemy { int p, s; ubyte ink; int max; int m; int frame; }
176 static struct SkyLabXY { int x, y, frame; ubyte ink; int m; int s; int max; int p; }
177 static struct SPGRay { int x, y; }
178 @SRZIgnore int roomIdx;
179 string title;
180 WillyStart willy;
181 ExitGfx exit;
182 int air;
183 ubyte[Height*Width] map; // 16 rows, 32 cols
184 ubyte border, ink, paper;
185 CommonGfx[] platforms;
186 CommonGfx wall;
187 CommonGfx crumb;
188 CommonGfx[] deadlies;
189 Conveyor conveyor;
190 Key keys;
191 SwitchPos[] switches;
192 Enemy[] enemies;
194 @SRZIgnore Enemy eugene;
196 @SRZIgnore int holeLen, holeY;
197 @SRZIgnore Enemy kong;
199 @SRZIgnore SkyLabEnemy[] skylabEnemies;
200 @SRZIgnore SkyLabXY[] skylab;
202 @SRZIgnore SPGRay[] spg; // solar power generator
204 @SRZIgnore private {
205 int willyX, willyY, willyDir, willyJump, willyJumpDir;
206 int willyLastMoveDir, willyFall, willyConv, willyStall;
207 public bool willyDead;
208 int frameNo;
209 public int score;
210 // keys
211 bool kLeft, kRight, kJump, kUp, kDown;
212 bool kLeftDown, kRightDown, kJumpDown, kUpDown, kDownDown;
215 @SRZIgnore private {
216 X11Image finalSpr;
217 X11Image sunSpr;
218 X11Image[] brickCache;
219 X11Image[] convCache;
220 X11Image[] exitCache;
221 X11Image[] keyCache;
222 X11Image[][] monsterCache;
223 int[][] monsterOfsCache;
224 X11Image[] eugeneSpr;
225 X11Image[] kongSpr;
226 X11Image[] skylabSpr;
227 X11Image[] switchesSpr;
228 X11Image willySpr;
231 ubyte saveKeyState () {
232 return
233 ((kLeftDown ? 1 : 0)<<0)|
234 ((kRightDown ? 1 : 0)<<1)|
235 ((kUpDown ? 1 : 0)<<2)|
236 ((kDownDown ? 1 : 0)<<3)|
237 ((kJumpDown ? 1 : 0)<<4)|
241 void restoreKeyState (ubyte b) {
242 keyLeft = ((b&(1<<0)) != 0);
243 keyRight = ((b&(1<<1)) != 0);
244 keyUp = ((b&(1<<2)) != 0);
245 keyDown = ((b&(1<<3)) != 0);
246 keyJump = ((b&(1<<4)) != 0);
249 void initRoom () {
250 void initWilly () {
251 willyX = willy.x;
252 willyY = willy.y;
253 willyDir = (willy.sd > 0 ? -1 : 1);
254 willyJump = willyFall = willyConv = willyStall = 0;
255 willyDead = false;
256 willyLastMoveDir = 0;
257 kLeft = kRight = kJump = kUp = kDown = false;
258 kLeftDown = kRightDown = kJumpDown = kUpDown = kDownDown = false;
261 buildWilly();
262 buildBrickImages();
263 buildConvImages();
264 buildExitImages();
265 buildKeysImages();
266 buildMonsterImages();
267 buildEugeneImages();
268 buildKongImages();
269 buildSkyLabImages();
270 buildSwitchImages();
271 finalSpr = buildImageMasked(gameData["final"], 256, 64);
272 sunSpr = buildImageMasked(gameData["sun"], 24, 16);
274 initWilly();
276 frameNo = 0;
277 foreach (ref c; map) {
278 if (c == 4) c = 8; // 'crumb'
279 else if (c < 1 || c > 7) c = 0; // emptyness
282 // rebuild keys
283 KeyPos[] kinfo;
284 foreach (const ref ki; keys.info) {
285 if (ki.s == 0) continue;
286 kinfo ~= ki;
288 keys.info = kinfo;
290 platforms = platforms.dup;
291 deadlies = deadlies.dup;
292 switches = switches.dup;
293 enemies = enemies.dup;
295 // Eugene?
296 if (roomIdx == 4) {
297 Enemy[] a = [{ x:120, y:1, d/*ir*/:0, min:1, max:87, ink:6 }];
298 eugene = a[0];
300 // Kong?
301 if (roomIdx == 7 || roomIdx == 11) {
302 Enemy[] a = [{ x:120, y:0, max:104, frame:0, ink:2, m:0 }];
303 kong = a[0];
304 holeLen = 2*8;
305 holeY = 0;
307 // SkyLab?
308 if (roomIdx == 13) {
309 enemies = null;
310 SkyLabEnemy[] a = [
311 {p:0, s:4, ink:6, max:72, m:0, frame:0},
312 {p:2, s:3, ink:5, max:56, m:0, frame:0},
313 {p:1, s:1, ink:4, max:32, m:0, frame:0}
315 skylabEnemies = a.dup;
316 foreach (immutable f, const ref se; skylabEnemies) {
317 skylab ~= SkyLabXY(
318 skylabCoords[f][se.p].x, skylabCoords[f][se.p].y,
319 se.frame, se.ink, se.m, se.s, se.max, se.p,
323 // Solar Power Generator?
324 if (roomIdx == 18) buildSPG();
327 void keyLeft (bool pressed) { if (pressed) kLeft = true; kLeftDown = pressed; }
328 void keyRight (bool pressed) { if (pressed) kRight = true; kRightDown = pressed; }
329 void keyUp (bool pressed) { if (pressed) kUp = true; kUpDown = pressed; }
330 void keyDown (bool pressed) { if (pressed) kDown = true; kDownDown = pressed; }
331 void keyJump (bool pressed) { if (pressed) kJump = true; kJumpDown = pressed; }
333 void stepGame () {
334 kLeft = kLeft||kLeftDown;
335 kRight = kRight||kRightDown;
336 kUp = kUp||kUpDown;
337 kDown = kDown||kDownDown;
338 kJump = kJump||kJumpDown;
339 scope(exit) kLeft = kRight = kJump = kUp = kDown = false;
340 ++frameNo;
341 conveyor.frame += (conveyor.d ? -1 : 1);
342 if (conveyor.frame < 0) conveyor.frame = 3; else if (conveyor.frame > 3) conveyor.frame = 0;
344 if (!willyJump) stepCrumb();
345 stepMonsters();
346 stepWillyActions();
347 stepKeys();
348 stepSwitch();
349 buildSPG();
352 void cheatRemoveKeys () { keys.info = null; }
354 // ////////////////////////////////////////////////////////////////////// //
355 // checkers
357 // x & y: in pixels
358 ubyte getBlockAt (int x, int y, bool simplify=false) const nothrow @nogc {
359 x = x>>3;
360 y = y>>3;
361 if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
362 ubyte b = map[y*32+x];
363 if (simplify) {
364 if (b > 15) b = 0;
365 else if (b > 7) b = 4;
366 else if (b == 6) b = 5;
367 else if (b == 2) b = 1;
369 return b;
372 bool checkExit () const nothrow @nogc {
373 if (keys.info.length != 0) return false;
374 auto x = exit.x;
375 auto y = exit.y;
376 return (willyX >= x-2 && willyX+10 <= x+18 && willyY >= y-5 && willyY+16 <= y+22);
379 bool checkMonsters () const nothrow {
380 foreach (immutable f, const ref m; enemies) {
381 auto cc = monsterOfsCache[f];
382 if (m.x < 0 || m.y < 0) continue;
383 if (m.vert) {
384 if (pixelCheckMonster(m.x, m.y, gameData["vrobo"], cc[m.anim])) return true;
385 } else {
386 if (pixelCheckMonster(m.x&248, m.y, gameData["hrobo"], cc[((m.x&m.anim)&0xfe)+m.d/*ir*/])) return true;
389 // Eugene?
390 if (eugene.x >= 0) {
391 enum curLSet = 0;
392 if (pixelCheckMonster(eugene.x, eugene.y, gameData["eugene"], 256*curLSet)) return true;
394 // Kong?
395 if (kong.x >= 0) {
396 if (pixelCheckMonster(kong.x, kong.y, gameData["kong"], 256*kong.frame)) return true;
398 // SkyLab?
399 if (skylab.length) {
400 foreach (const ref sk; skylab) {
401 if (pixelCheckMonster(sk.x, sk.y, gameData["sky"], 256*sk.frame)) return true;
404 return false;
407 void checkSwitch () nothrow @nogc {
408 foreach (ref ss; switches) {
409 if (ss.s/*tate*/ != 1) continue;
410 auto x = ss.x;
411 auto y = ss.y;
412 if (x+7 >= willyX && y+7 >= willyY && x < willyX+8 && y < willyY+16) ss.s/*tate*/ = 1+1;
416 bool checkSPG () const nothrow @nogc {
417 foreach (const ref sp; spg) {
418 auto x = sp.x*8;
419 auto y = sp.y*8;
420 if (x < 0 || y < 0) break;
421 if (x+7 >= willyX && x < willyX+8 && y+7 >= willyY && y < willyY+16) return true;
423 return false;
426 X11Image draw (X11Image img=null) {
427 if (img is null) img = new X11Image(8*Room.Width, 8*Room.Height);
428 int pos = 0;
429 foreach (immutable y; 0..img.height) {
430 foreach (immutable x; 0..img.width) {
431 img.setPixel(x, y, palcol(this.paper));
434 foreach (immutable y; 0..Room.Height) {
435 foreach (immutable x; 0..Room.Width) {
436 ubyte blk = this.map[pos++];
437 if (blk < 1 || blk == 7) continue;
438 if (blk == 4) blk = 8;
439 if (blk < brickCache.length && brickCache[blk] !is null) {
440 brickCache[blk].blitTo(img, x*8, y*8);
444 if (this.roomIdx == 19) {
445 finalSpr.blitTo(img, 0, 0);
446 sunSpr.blitTo(img, 60, 32);
449 void drawConveyor () {
450 auto conv = &this.conveyor;
451 if (conv.l < 1) return;
452 auto y = conv.y;
453 if (y <= 0) return;
454 convCache[conv.frame].blitTo(img, conv.x, conv.y, conv.l);
457 void drawExit () {
458 int br;
459 if (this.keys.info.length != 0) {
460 br = 0;
461 } else {
462 br = this.frameNo&31;
463 if (br > 15) br = 31-br;
464 ++br;
466 exitCache[br].blitTo(img, this.exit.x, this.exit.y);
469 void drawKeys () {
470 auto ff = this.frameNo%16;
471 if (ff >= 8) ff = 15-ff;
472 auto keyimg = keyCache[ff];
473 foreach_reverse (const ref ki; this.keys.info) {
474 if (ki.x < 0 || ki.y < 0) continue;
475 //eraseRect(ki.x, ki.y, 8, 8);
476 if (!ki.s) continue;
477 keyimg.blitTo(img, ki.x&248, ki.y);
481 void drawMonsters () {
482 foreach (immutable f, const ref m; this.enemies) {
483 if (m.x < 0 || m.y < 0) continue;
484 auto slist = monsterCache[f];
485 auto x = m.x;
486 if (m.vert) {
487 //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
488 slist[m.anim].blitTo(img, x, m.y);
489 } else {
490 auto sidx = ((x&m.anim)&0xfe)+m.d/*ir*/;
491 if (sidx < slist.length) {
492 slist[sidx].blitTo(img, x&248, m.y);
493 } else {
494 import core.stdc.stdio : stderr, fprintf;
495 stderr.fprintf("monster #%u is fucked: sidx=%u; max=%u", cast(uint)f, cast(uint)sidx, cast(uint)slist.length);
499 if (this.eugene.x >= 0) {
500 enum curLSet = 0;
501 eugeneSpr[curLSet*8+this.eugene.ink].blitTo(img, this.eugene.x, this.eugene.y);
503 if (this.kong.x >= 0) {
504 kongSpr[this.kong.frame*8+this.kong.ink].blitTo(img, this.kong.x, this.kong.y);
506 if (this.skylab.length) {
507 foreach (const ref sk; this.skylab) {
508 skylabSpr[sk.frame*8+sk.ink].blitTo(img, sk.x, sk.y);
513 void drawSwitch () {
514 foreach (const ref ss; this.switches) {
515 if (ss.s == 0) continue;
516 switchesSpr[ss.s-1].blitTo(img, ss.x, ss.y);
520 void drawSPG () {
521 foreach (const ref sp; this.spg) {
522 auto x = sp.x*8;
523 auto y = sp.y*8;
524 if (x < 0 || y < 0) break;
525 //ctx.fillStyle = mainPal[noerase?6:this.paper];
526 //ctx.fillRect(x, y, 8, 8);
527 foreach (immutable dy; 0..8) {
528 foreach (immutable dx; 0..8) {
529 img.setPixel(x+dx, y+dy, palcol(6));
535 void drawWilly () {
536 auto willyPos = (this.willyX&15)>>1;
537 if (this.willyDir < 0) willyPos += 8;
538 if (debugColdet) {
539 auto wy = 0;
540 if (this.checkMonsters()) wy = 16;
541 willySpr.blitTo(willyPos*16, wy, 16, 16, img, this.willyX&248, this.willyY);
542 } else {
543 willySpr.blitTo(willyPos*16, 0, 16, 16, img, this.willyX&248, this.willyY);
547 drawConveyor();
548 drawKeys();
549 drawMonsters();
550 drawSwitch();
551 drawWilly();
552 drawExit();
553 drawSPG();
555 return img;
558 private:
559 // ////////////////////////////////////////////////////////////////////// //
560 // pixel-perfect collision detector
561 // fully stolen from Andy's sources %-)
562 bool pixelCheckMonster (int rx, int ry, const(ubyte)[] darr, int dpos=0) const nothrow {
563 static ubyte[256] mpcGrid;
564 assert(dpos >= 0);
565 //int x, y, w;
566 int x, w;
567 rx -= willyX&248;
568 ry -= willyY;
569 if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
570 // clear grid
571 mpcGrid[] = 0;
572 if (rx < 0) { x = 0; rx = -rx; w = 16-rx; } else { x = rx; rx = 0; w = 16-x; }
573 // partial plot monster
574 for (int y = ry+15; y >= ry; --y) {
575 if (y >= 0 && y < 16) {
576 int gp = y*16+x;
577 int rp = dpos+(y-ry)*16+rx;
578 for (int dx = 0; dx < w; ++dx, ++gp, ++rp) mpcGrid[gp] = darr[rp];
581 auto warr = gameData["willy"];
582 int wptr = ((willyX&15)>>1)*256+(willyDir < 0 ? 2048 : 0);
583 // check for collision
584 int mp = 0;
585 for (x = 255; x >= 0; --x, ++wptr, ++mp) if (warr[wptr] && mpcGrid[mp]) return true;
586 return false;
589 // ////////////////////////////////////////////////////////////////////// //
590 bool isWillyFalling () const nothrow @nogc {
591 if (willyY&7) return true;
592 for (int dx = 0; dx <= 8; dx += 8) {
593 ubyte b = getBlockAt(willyX+dx, willyY+16, true);
594 if (b > 0 && b != 5) return false;
596 return true;
599 bool isWillyInDeadly () const nothrow @nogc {
600 for (int dx = 0; dx <= 8; dx += 8) {
601 for (int dy = 0; dy <= 16; dy += 8) {
602 if (getBlockAt(willyX+dx, willyY+dy, true) == 5) return true;
605 return false;
608 bool isWillyOnConv () const nothrow @nogc {
609 if (willyY&7) return false;
610 ubyte b0 = getBlockAt(willyX, willyY+16);
611 ubyte b1 = getBlockAt(willyX+8, willyY+16);
612 return (b0 == 7 || b1 == 7);
615 // ////////////////////////////////////////////////////////////////////// //
616 // called on each frame
617 void buildSPG (bool forced=false) {
618 if (!forced && roomIdx != 18) {
619 spg = null;
620 return;
623 bool spgCheckMonster (int x, int y) {
624 x *= 8;
625 y *= 8;
626 foreach (const ref cm; enemies) {
627 auto mx = cm.x;
628 auto my = cm.y;
629 if (x+7 >= mx && x < mx+15 && y+7 >= my && y < my+15) return true;
631 return false;
634 int x = 23, y = 0;
635 bool done = false;
636 int dir = 0, idx = 0;
638 if (spg.length) {
639 spg.length = 0;
640 spg.assumeSafeAppend;
643 void addXY (int x, int y) {
644 ++idx;
645 while (spg.length < idx) spg ~= SPGRay(-1, -1);
646 spg[idx-1].x = x;
647 spg[idx-1].y = y;
650 do {
651 ubyte blockhit = map[y*32+x];
652 bool robohit = spgCheckMonster(x, y);
654 if (blockhit && robohit) {
655 addXY(-1, -1);
656 done = true;
657 } else if (!blockhit && robohit) {
658 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
659 spg[idx-1].x = spg[idx-1].y = -1;
660 done = true;
661 } else {
662 addXY(x, y);
664 dir ^= 1;
665 } else if (!blockhit && !robohit) {
666 addXY(x, y);
667 } else if (blockhit && !robohit) {
668 if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
669 spg[idx-1].x = spg[idx-1].y = -1;
670 done = true;
672 dir ^= 1;
675 if (!blockhit) {
676 if (!dir) {
677 ++y;
678 blockhit = map[y*32+x];
679 if (y == 15 || blockhit) done = true;
680 } else {
681 --x;
682 blockhit = map[y*32+x];
683 if (x == 0 || blockhit) done = true;
685 } else {
686 if (!dir) { --x; if (!x) done = true; }
687 else { ++y; if (++y == 15) done = true; }
689 } while (!done);
690 addXY(-1, -1);
693 void stepMonsters () {
694 foreach (ref m; enemies) {
695 if (m.x < 0 || m.y < 0) continue;
696 if (m.vert) {
697 auto y = m.y;
698 auto spd = m.s/*peed*/;
699 if (m.d/*ir*/ != 0) {
700 // up
701 y -= spd;
702 if (y < m.min || y < 0) { y += spd; m.d/*ir*/ = 0; }
703 } else {
704 // down
705 y += spd;
706 if (y > m.max) { y -= spd; m.d/*ir*/ = 1; }
708 m.y = y;
709 m.anim = (m.anim+1)&3;
710 } else {
711 auto x = m.x;
712 auto spd = (2>>m.s/*peed*/);
713 if (m.d/*ir*/ != 0) {
714 // left
715 x -= spd;
716 if (x < m.min) { m.d/*ir*/ = 0; x += spd; }
717 } else {
718 // right
719 x += spd;
720 if (x > m.max+6) { m.d/*ir*/ = 1; x -= spd; }
722 m.x = x;
725 // Eugene
726 if (eugene.x >= 0) {
727 if (keys.info.length == 0) {
728 // no keys, Eugene tries to block the exit
729 eugene.ink = (eugene.ink+1)&7;
730 eugene.y += (eugene.y < eugene.max ? 1 : 0);
731 } else {
732 if (eugene.d/*ir*/ != 0) {
733 // up
734 --eugene.y;
735 if (eugene.y < eugene.min) { eugene.d/*ir*/ = 0; ++eugene.y; }
736 } else {
737 // down
738 ++eugene.y;
739 if (eugene.y > eugene.max) { eugene.d/*ir*/ = 1; --eugene.y; }
743 // Kong
744 if (kong.x >= 0) {
745 switch (kong.falling) {
746 case 1: // just started
747 map[2*32+15] = 0;
748 map[2*32+16] = 0;
749 //eraseRect(16, 120, 16, 8);
750 kong.falling = 2;
751 break;
752 case 2:
753 kong.ink = 4;
754 kong.frame += 2;
755 kong.falling = 3;
756 break;
757 case 3:
758 kong.y += 4;
759 if (kong.y >= kong.max) { kong.x = -1; score += 100; }
760 if (!kong.delay) { kong.delay = 4; kong.frame = ((kong.frame-1)&1)+2; } else --kong.delay;
761 break;
762 default:
763 if (!kong.delay) { kong.delay = 8; kong.frame = (kong.frame+1)&1; } else --kong.delay;
764 break;
767 // SkyLab
768 foreach (immutable idx, ref sk; skylab) {
769 switch (sk.m) {
770 case 0:
771 sk.y += sk.s;
772 if (sk.y > sk.max) {
773 sk.y = sk.max;
774 sk.m = 1;
775 ++sk.frame;
777 break;
778 case 1:
779 ++sk.frame;
780 if (sk.frame == 7) sk.m = 2;
781 break;
782 case 2:
783 sk.p = (sk.p+1)&3;
784 sk.x = skylabCoords[idx][sk.p].x;
785 sk.y = skylabCoords[idx][sk.p].y;
786 sk.frame = sk.m = 0;
787 break;
788 default:
793 void stepCrumb () {
794 if (willyY&7) return;
795 for (int f = 0; f <= 8; f += 8) {
796 int x = willyX+f;
797 int y = willyY+16;
798 ubyte b = getBlockAt(x, y);
799 if (b == 4) b = 8;
800 if (b < 8) continue;
801 x >>= 3;
802 y >>= 3;
803 if (++b > 15) b = 0;
804 map[y*32+x] = b;
805 //eraseRect(x*8, y*8, 8, 8);
809 void stepKeys () {
810 while (keys.info.length) {
811 bool again = false;
812 foreach (immutable idx; 0..keys.info.length) {
813 auto ki = &keys.info[idx];
814 assert(ki.s);
815 int kx = ki.x;
816 int ky = ki.y;
817 if (kx+7 >= willyX && kx < willyX+10 && ky+7 >= willyY && ky < willyY+16) {
818 score += 100;
819 foreach (immutable c; idx+1..keys.info.length) keys.info[c-1] = keys.info[c];
820 keys.info.length -= 1;
821 again = true;
822 break;
825 if (!again) break;
829 void stepSwitch () {
830 // hole?
831 if (holeLen > 0 && switches.length && switches[0].s/*tate*/ > 1) {
832 if (holeLen < 0) {
833 //FIXME
834 enemies[1].max += 24;
835 holeLen = 0;
836 } else {
837 ++holeY;
838 if (holeY == 16) {
839 map[11*32+17] = 0;
840 map[12*32+17] = 0;
841 holeLen = -1;
845 // Kong?
846 if (kong.x >= 0 && !kong.falling && switches.length > 1 && switches[1].s/*tate*/ > 1) kong.falling = 1;
849 void stepWillyActions () {
850 bool doWillyLeft () {
851 if (willyDir > 0) { willyDir = -1; return true; }
852 auto xx = willyX-2;
853 auto b0 = getBlockAt(xx, willyY);
854 auto b1 = getBlockAt(xx, willyY+8);
855 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
856 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
857 willyX -= 2;
858 if (willyX < 0) willyX += 240;
859 willyLastMoveDir = -1;
860 return true;
863 bool doWillyRight () {
864 if (willyDir < 0) { willyDir = 1; return true; }
865 if (willyX > 245) return false;
866 auto xx = willyX+10;
867 auto b0 = getBlockAt(xx, willyY);
868 auto b1 = getBlockAt(xx, willyY+8);
869 auto b2 = (willyY&7 ? getBlockAt(xx, willyY+16) : b1);
870 if (b0 == 3 || b1 == 3 || b2 == 3) return false;
871 willyX += 2;
872 if (willyX > 240) willyX -= 240;
873 willyLastMoveDir = 1;
874 return true;
877 void doWillyJump () {
878 if (!willyJump) return;
879 willyY += willyJ[willyJump];
880 auto x = willyX;
881 auto mv = false;
882 if (willyJumpDir < 0) mv = doWillyLeft(); else if (willyJumpDir > 0) mv = doWillyRight();
883 if (willyJump < 9) {
884 willyFall = 0;
885 // up
886 auto b0 = getBlockAt(x, willyY);
887 auto b1 = getBlockAt(x+8, willyY);
888 if (b0 == 3 || b1 == 3) {
889 // headboom! (apstenu %-)
890 willyX = x;
891 willyY -= willyJ[willyJump];
892 willyJump = 0; // enough flying
893 return;
895 } else {
896 // down
897 if (willyJump > 12) willyFall += willyJ[willyJump];
898 if ((willyY&7) == 0) {
899 auto b0 = getBlockAt(willyX, willyY+16);
900 auto b1 = getBlockAt(willyX+8, willyY+16);
901 if (b0 || b1) {
902 if (b0 == 3 || b1 == 3) willyX = x;
903 willyFall = 0; // can't fall too deep while jumping
904 willyJump = 0; // enough flying
905 if (b0 == 7 || b1 == 7) willyStall = 1; // conveyor?
906 return;
910 ++willyJump;
911 if (willyJump > 18) willyJump = 0;
915 if (willyDead) return;
916 checkSwitch();
917 if (isWillyInDeadly()) { willyDead = true; return; }
918 if (!debugColdet) {
919 if (checkMonsters()) { willyDead = true; return; }
920 } else {
921 if (checkMonsters()) singleStep = singleStepWait = true;
924 auto wasJump = false;
925 if (willyJump) {
926 willyLastMoveDir = 0;
927 doWillyJump();
928 if (willyJump) return;
929 wasJump = true;
932 auto falling = isWillyFalling();
933 if (!kDown && falling) {
934 willyConv = willyStall = willyLastMoveDir = 0;
935 willyFall += 4;
936 willyY += 4;
937 if (willyY > 112) willyY -= 112;
938 return;
941 if (!falling && willyFall > 34) willyDead = true; // too high!
942 auto lfall = willyFall;
943 willyFall = 0;
945 if (willyDead) return;
947 auto dx = (kLeft ? -1 : kRight ? 1 : 0);
948 if (isWillyOnConv()) {
949 auto cdir = (conveyor.d ? 1 : -1);
950 //dx==cdir,!dx,lastmove==cdir
951 if (willyLastMoveDir == cdir || dx == cdir || !dx) { willyConv = cdir; willyStall = 0; } // was moving in conv. dir or standing
952 if (!willyConv) {
953 // Willy just steps on the conveyor, and Willy walking to the opposite side
954 willyStall = 0;
955 if (wasJump && willyLastMoveDir == -cdir) {
956 willyConv = dx; // from jump, can do opposite
957 } else {
958 willyConv = dx;
959 if (lfall > 0 || !willyLastMoveDir) { dx = 0; willyStall = 1; } // lands on conveyor, not from jump
961 } else {
962 // Willy was on conveyor
963 dx = (willyStall ? 0 : willyConv);
965 } else {
966 willyConv = willyStall = 0;
969 //if (willyConv) dx = willyConv;
970 if (kUp && !wasJump) {
971 willyConv = willyStall = willyLastMoveDir = 0;
972 willyJumpDir = dx;
973 willyJump = 1;
974 doWillyJump();
975 return;
977 if (kDown) willyY -= 8;
978 willyLastMoveDir = 0;
979 if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
982 // ////////////////////////////////////////////////////////////////////// //
983 void buildWilly () {
984 auto img = new X11Image(16*16, 16+16);
985 auto ww = gameData["willy"];
986 foreach (immutable f; 0..16) {
987 foreach (immutable y; 0..16) {
988 foreach (immutable x; 0..16) {
989 ubyte c = ww[f*256+y*16+x];
990 if (!c) {
991 img.setPixel(f*16+x, y, Transparent);
992 } else {
993 img.setPixel(f*16+x, y, palcol(c));
994 // white
995 img.setPixel(f*16+x, y+16, palcol(15));
1000 willySpr = img;
1003 void buildBrickImages () {
1004 auto buildBrickImage (in ref Room.CommonGfx brk, int skipy=0) {
1005 return buildImageBrick(gameData["blocks"][brk.gfx*64..$], brk.ink, this.paper, skipy);
1007 brickCache = null;
1008 foreach (immutable f; 0..8) {
1009 X11Image img;
1010 switch (f) {
1011 //case 0: case 7: img = buildBrickImage(this.wall, 16); break;
1012 case 1: img = buildBrickImage(this.platforms[0]); break;
1013 case 2: img = buildBrickImage(this.platforms[1]); break;
1014 case 3: img = buildBrickImage(this.wall); break;
1015 //case 4: img = buildBrickImage(this.crumb); break;
1016 case 5: img = buildBrickImage(this.deadlies[0]); break;
1017 case 6: img = buildBrickImage(this.deadlies[1]); break;
1018 default:
1020 brickCache ~= img;
1022 foreach (immutable f; 0..9) brickCache ~= buildBrickImage(this.crumb, f);
1025 void buildConvImages () {
1026 convCache = null;
1027 auto conv = &this.conveyor;
1028 if (conv.y <= 0 || conv.l < 1) return;
1029 foreach (immutable f; 0..4) {
1030 convCache ~= buildImageBrick(gameData["conv"][conv.gfx*256+f*64..$], conv.ink, this.paper);
1034 void buildExitImages () {
1035 exitCache = null;
1036 exitCache ~= buildImageMasked(gameData["exits"][this.exit.gfx*256..$], 16, 16);
1037 foreach (immutable f; 0..16) exitCache ~= buildImageMaskedShiny(gameData["exits"][this.exit.gfx*256..$], f, 16, 16);
1040 void buildKeysImages () {
1041 keyCache = null;
1042 foreach (immutable f; 0..16) keyCache ~= buildImageMaskedShiny(gameData["keys"][this.keys.gfx*64..$], f, 8, 8);
1045 void buildMonsterImages () {
1046 monsterCache = null;
1047 monsterOfsCache = null;
1048 foreach (immutable f, const ref m; this.enemies) {
1049 if (m.x < 0 || m.y < 0) {
1050 monsterOfsCache ~= null;
1051 monsterCache ~= null;
1052 continue;
1054 X11Image[] r;
1055 int[] cc;
1056 if (m.vert) {
1057 foreach (immutable c; 0..4) {
1058 auto n = (m.gfx+c)*256;
1059 cc ~= n;
1060 r ~= buildImageMaskedInk(gameData["vrobo"][n..$], m.ink-1, 16, 16);
1062 } else {
1063 foreach (immutable c; 0..(m.anim>>1)+1) {
1064 auto n = (m.gfx+c)*256;
1065 cc ~= n;
1066 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1067 n += m.flip*256;
1068 cc ~= n;
1069 r ~= buildImageMaskedInk(gameData["hrobo"][n..$], m.ink-1, 16, 16);
1072 monsterOfsCache ~= cc;
1073 monsterCache ~= r;
1077 void buildEugeneImages () {
1078 eugeneSpr = null;
1079 for (int f = 0; f <= 256; f += 256) {
1080 foreach (immutable c; 0..8) {
1081 eugeneSpr ~= buildImageMaskedInk(gameData["eugene"][f..$], c, 16, 16);
1086 void buildKongImages () {
1087 kongSpr = null;
1088 foreach (immutable f; 0..4) {
1089 foreach (immutable c; 0..8) {
1090 kongSpr ~= buildImageMaskedInk(gameData["kong"][f*256..$], c, 16, 16);
1095 void buildSkyLabImages () {
1096 skylabSpr = null;
1097 foreach (immutable f; 0..8) {
1098 foreach (immutable c; 0..8) {
1099 skylabSpr ~= buildImageMaskedInk(gameData["sky"][f*256..$], c, 16, 16);
1104 void buildSwitchImages () {
1105 switchesSpr = null;
1106 ubyte[] swg;
1107 if (auto pp = "switches" in gameData) swg = *pp; else swg = gameData["switch"];
1108 foreach (immutable f; 0..2) {
1109 switchesSpr ~= buildImageBrick(swg[f*64..$], this.platforms[1].ink, this.paper);
1113 private:
1114 void load (VFile fl) {
1115 char[33] title;
1116 fl.rawReadExact(map[]);
1117 fl.rawReadExact(title[]);
1118 const(char)[] tit;
1119 foreach (immutable idx, char ch; title[]) {
1120 if (ch == 0) break;
1121 tit = title[0..idx];
1123 while (tit.length && tit[0] <= ' ') tit = tit[1..$];
1124 while (tit.length && tit[$-1] <= ' ') tit = tit[0..$-1];
1125 this.title = tit.idup;
1127 border = fl.readNum!ubyte;
1128 ink = fl.readNum!ubyte;
1129 paper = fl.readNum!ubyte;
1131 platforms.length = 2;
1132 foreach (immutable idx; 0..2) {
1133 CommonGfx g;
1134 g.ink = fl.readNum!ubyte;
1135 g.paper = fl.readNum!ubyte;
1136 g.gfx = fl.readNum!ubyte;
1137 platforms[idx] = g;
1140 wall.ink = fl.readNum!ubyte;
1141 wall.paper = fl.readNum!ubyte;
1142 wall.gfx = fl.readNum!ubyte;
1144 crumb.ink = fl.readNum!ubyte;
1145 crumb.paper = fl.readNum!ubyte;
1146 crumb.gfx = fl.readNum!ubyte;
1148 deadlies.length = 2;
1149 foreach (immutable idx; 0..2) {
1150 CommonGfx g;
1151 g.ink = fl.readNum!ubyte;
1152 g.paper = fl.readNum!ubyte;
1153 g.gfx = fl.readNum!ubyte;
1154 deadlies[idx] = g;
1157 conveyor.ink = fl.readNum!ubyte;
1158 conveyor.paper = fl.readNum!ubyte;
1159 conveyor.gfx = fl.readNum!ubyte;
1161 willy.x = fl.readNum!short;
1162 willy.y = fl.readNum!short;
1163 willy.sd = fl.readNum!ubyte;
1165 conveyor.x = fl.readNum!short;
1166 conveyor.y = fl.readNum!short;
1167 conveyor.d = fl.readNum!ubyte;
1168 conveyor.l = fl.readNum!ubyte;
1170 keys.info.length = 5;
1171 foreach (immutable idx; 0..5) keys.info[idx].x = fl.readNum!short;
1172 foreach (immutable idx; 0..5) keys.info[idx].y = fl.readNum!short;
1173 keys.gfx = fl.readNum!ubyte;
1174 foreach (immutable idx; 0..5) keys.info[idx].s = fl.readNum!ubyte;
1176 switches.length = 2;
1177 foreach (immutable idx; 0..2) switches[idx].x = fl.readNum!short;
1178 foreach (immutable idx; 0..2) switches[idx].y = fl.readNum!short;
1179 foreach (immutable idx; 0..2) switches[idx].s = fl.readNum!ubyte;
1181 exit.x = fl.readNum!short;
1182 exit.y = fl.readNum!short;
1183 exit.gfx = fl.readNum!ubyte;
1185 air = fl.readNum!ubyte;
1187 enemies.length = 4*2;
1189 foreach (immutable idx; 0..4) enemies[idx].vert = false;
1190 foreach (immutable idx; 0..4) enemies[idx].ink = fl.readNum!ubyte;
1191 foreach (immutable idx; 0..4) enemies[idx].paper = fl.readNum!ubyte;
1192 foreach (immutable idx; 0..4) enemies[idx].x = fl.readNum!short;
1193 foreach (immutable idx; 0..4) enemies[idx].y = fl.readNum!short;
1194 foreach (immutable idx; 0..4) enemies[idx].min = fl.readNum!short;
1195 foreach (immutable idx; 0..4) enemies[idx].max = fl.readNum!short;
1196 foreach (immutable idx; 0..4) enemies[idx].d = fl.readNum!ubyte;
1197 foreach (immutable idx; 0..4) enemies[idx].s = fl.readNum!ubyte;
1198 foreach (immutable idx; 0..4) enemies[idx].gfx = fl.readNum!short;
1199 foreach (immutable idx; 0..4) enemies[idx].flip = fl.readNum!ubyte;
1200 foreach (immutable idx; 0..4) enemies[idx].anim = fl.readNum!ubyte;
1202 foreach (immutable idx; 4..8) enemies[idx].vert = true;
1203 foreach (immutable idx; 4..8) enemies[idx].ink = fl.readNum!ubyte;
1204 foreach (immutable idx; 4..8) enemies[idx].paper = fl.readNum!ubyte;
1205 foreach (immutable idx; 4..8) enemies[idx].x = fl.readNum!short;
1206 foreach (immutable idx; 4..8) enemies[idx].y = fl.readNum!short;
1207 foreach (immutable idx; 4..8) enemies[idx].min = fl.readNum!short;
1208 foreach (immutable idx; 4..8) enemies[idx].max = fl.readNum!short;
1209 foreach (immutable idx; 4..8) enemies[idx].d = fl.readNum!ubyte;
1210 foreach (immutable idx; 4..8) enemies[idx].s = fl.readNum!ubyte;
1211 foreach (immutable idx; 4..8) enemies[idx].gfx = fl.readNum!short;
1212 foreach (immutable idx; 4..8) enemies[idx].anim = fl.readNum!ubyte;
1217 // ////////////////////////////////////////////////////////////////////////// //
1218 public void blitTo (X11Image src, X11Image dst, int x, int y, int repx=1) {
1219 foreach (immutable r; 0..repx) {
1220 foreach (immutable dy; 0..src.height) {
1221 foreach (immutable dx; 0..src.width) {
1222 int xx = x+dx;
1223 int yy = y+dy;
1224 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1225 auto c = src.getPixel(dx, dy);
1226 if (isTransparent(c)) continue;
1227 dst.setPixel(xx, yy, c);
1230 x += src.width;
1235 public void blitTo (X11Image src, int x0, int y0, int ww, int hh, X11Image dst, int x, int y) {
1236 foreach (immutable dy; 0..ww) {
1237 foreach (immutable dx; 0..hh) {
1238 int xx = x+dx;
1239 int yy = y+dy;
1240 if (xx < 0 || yy < 0 || xx >= dst.width || yy >= dst.height) continue;
1241 auto c = src.getPixel(x0+dx, y0+dy);
1242 if (isTransparent(c)) continue;
1243 dst.setPixel(xx, yy, c);