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/>.
23 __gshared
bool debugColdet
= false;
24 __gshared
bool singleStep
= false;
25 __gshared
bool singleStepWait
= false;
28 // ////////////////////////////////////////////////////////////////////////// //
29 private __gshared VColor
[256] palette
;
30 __gshared
ubyte[][string
] gameData
;
31 __gshared Room
[] gameRooms
;
32 __gshared string gameLevelsName
;
35 loadfile (GFXDATA "air", GFXair, 2048);
36 loadfile (GFXDATA "fant", GFXfant, 8 * 96);
37 loadfile (GFXDATA "final", GFXfinal, 256 * 64);
38 loadfile (GFXDATA "fontb", fontb, 8 * 96);
39 loadfile (GFXDATA "fonts", fonts, 6 * 64);
40 loadfile (GFXDATA "house", GFXhouse, 1024);
41 loadfile (GFXDATA "load", GFXload, 32 * 8 * 2);
42 loadfile (GFXDATA "over", GFXover, 256 * 2);
43 loadfile (GFXDATA "piano", GFXpiano, 256 * 64);
44 loadfile (GFXDATA "pkeys", GFXpkeys, 128 * 8);
45 loadfile (GFXDATA "switch", GFXswitch, 2 * 64);
46 loadfile (GFXDATA "tplate", GFXtplate, 32 * 64);
47 loadfile (GFXDATA "win", GFXwin, 4864);
51 private void loadRooms (const(ubyte)[] data
) {
53 if (data
.length
< 8) throw new Exception("invalid levels");
54 if (data
[0..8] != "MANIC\x0d\x0a\x1a") throw new Exception("invalid levels");
56 if (data
.length
< 16) throw new Exception("invalid levels");
57 char[16] lname
= (cast(const(char)[])data
)[0..16];
60 foreach (immutable idx
, char ch
; lname
[]) {
64 while (nn
.length
&& nn
[$-1] <= ' ') nn
= nn
[0..$-1];
65 gameLevelsName
= nn
.idup
;
66 while (data
.length
> 0) {
67 gameRooms
.length
+= 1;
68 gameRooms
[$-1].roomIdx
= cast(int)gameRooms
.length
-1;
69 gameRooms
[$-1].load(data
);
74 public void loadGameData () {
76 auto pp
= wadLoadFile("palmain");
77 foreach (immutable cc
; 0..256) {
78 ubyte r
= cast(ubyte)(255*pp
[cc
*3+0]/63);
79 ubyte g
= cast(ubyte)(255*pp
[cc
*3+1]/63);
80 ubyte b
= cast(ubyte)(255*pp
[cc
*3+2]/63);
81 palette
[cc
] = rgbcol(r
, g
, b
);
85 forEachWadFile(delegate (string name
, uint size
) {
86 if (name
== "palmain") return;
87 auto buf
= wadLoadFile(name
);
88 if (name
== "levels") {
95 if (gameRooms
.length
== 0) throw new Exception("no levels");
99 // ////////////////////////////////////////////////////////////////////////// //
101 public VColor
palcol (ubyte c
) nothrow @trusted @nogc { pragma(inline
, true); return palette
[c
]; }
104 // ////////////////////////////////////////////////////////////////////////// //
105 public X11Image
buildImageMasked (const(ubyte)[] darr
, int w
, int h
) {
106 assert(w
> 0 && h
> 0);
108 auto img
= new X11Image(w
, h
);
109 foreach (immutable y
; 0..h
) {
110 foreach (immutable x
; 0..w
) {
111 ubyte c
= darr
[dpos
++];
113 img
.setPixel(x
, y
, palcol(c
));
115 img
.setPixel(x
, y
, Transparent
);
123 public X11Image
buildImageMaskedShiny (const(ubyte)[] darr
, int brightness
, int w
, int h
) {
124 assert(w
> 0 && h
> 0);
126 auto img
= new X11Image(w
, h
);
127 foreach (immutable y
; 0..h
) {
128 foreach (immutable x
; 0..w
) {
129 ubyte c
= darr
[dpos
++];
131 int b
= (c
&15)-brightness
;
132 if (b
< 0) b
= 0; else if (b
> 15) b
= 15;
133 img
.setPixel(x
, y
, palcol(cast(ubyte)((c
&240)|b
)));
135 img
.setPixel(x
, y
, Transparent
);
143 public X11Image
buildImageMaskedInk (const(ubyte)[] darr
, int ink
, int w
, int h
) {
144 assert(w
> 0 && h
> 0);
146 auto img
= new X11Image(w
, h
);
147 if (ink
< 0) ink
= 0;
149 foreach (immutable y
; 0..h
) {
150 foreach (immutable x
; 0..w
) {
151 ubyte c
= darr
[dpos
++];
153 img
.setPixel(x
, y
, palcol(cast(ubyte)(c
+ink
)));
155 img
.setPixel(x
, y
, Transparent
);
163 // ////////////////////////////////////////////////////////////////////////// //
164 public X11Image
buildImageBrick (const(ubyte)[] darr
, int ink
, int paper
, int skipy
=0) {
166 enum { w
= 8, h
= 8 }
168 auto img
= new X11Image(w
, h
);
170 foreach (immutable y
; 0..h
) {
171 foreach (immutable x
; 0..w
) {
172 ubyte c
= (y
>= skipy ? darr
[dpos
++] : 0);
173 img
.setPixel(x
, y
, palcol(cast(ubyte)(c ? ink
+c
: paper
)));
180 // ////////////////////////////////////////////////////////////////////////// //
181 // willy jump offsets
183 static immutable int[19] willyJ
= [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
185 struct SkyLabXY
{ int x
, y
; }
186 static immutable SkyLabXY
[4][3] skylabCoords
= [
187 [{x
: 8,y
:0}, {x
: 72,y
:0}, {x
:136,y
:0}, {x
:200,y
:0}],
188 [{x
:40,y
:0}, {x
:104,y
:0}, {x
:168,y
:0}, {x
:232,y
:0}],
189 [{x
:24,y
:0}, {x
: 88,y
:0}, {x
:152,y
:0}, {x
:216,y
:0}]
193 // ////////////////////////////////////////////////////////////////////////// //
194 // suitable to "unjson"
196 private enum SRZIgnore
;
197 enum { Width
= 32, Height
= 16 }
198 static struct WillyStart
{ int x
; int y
; int sd
; }
199 static struct ExitGfx
{ ubyte gfx
; int x
; int y
; }
200 static struct CommonGfx
{ ubyte gfx
; ubyte ink
; ubyte paper
; }
201 static struct Conveyor
{ int x
; int y
; int d
; int l
; ubyte gfx
; ubyte ink
; ubyte paper
; @SRZIgnore int frame
; }
202 static struct KeyPos
{ int x
; int y
; int s
; }
203 static struct Key
{ ubyte gfx
; KeyPos
[] info
; }
204 static struct SwitchPos
{ int x
; int y
; int s
; }
205 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
; }
206 static struct SkyLabEnemy
{ int p
, s
; ubyte ink
; int max
; int m
; int frame
; }
207 static struct SkyLabXY
{ int x
, y
, frame
; ubyte ink
; int m
; int s
; int max
; int p
; }
208 static struct SPGRay
{ int x
, y
; }
209 @SRZIgnore int roomIdx
;
214 ubyte[Height
*Width
] map
; // 16 rows, 32 cols
215 ubyte border
, ink
, paper
;
216 CommonGfx
[] platforms
;
219 CommonGfx
[] deadlies
;
222 SwitchPos
[] switches
;
225 @SRZIgnore int air8
= 8;
227 @SRZIgnore Enemy eugene
;
229 @SRZIgnore int holeLen
, holeY
;
230 @SRZIgnore Enemy kong
;
232 @SRZIgnore SkyLabEnemy
[] skylabEnemies
;
233 @SRZIgnore SkyLabXY
[] skylab
;
235 @SRZIgnore SPGRay
[] spg
; // solar power generator
238 int willyX
, willyY
, willyDir
, willyJump
, willyJumpDir
;
239 int willyLastMoveDir
, willyFall
, willyConv
, willyStall
;
240 public bool willyDead
;
244 bool kLeft
, kRight
, kJump
, kUp
, kDown
;
245 bool kLeftDown
, kRightDown
, kJumpDown
, kUpDown
, kDownDown
;
251 X11Image
[] brickCache
;
252 X11Image
[] convCache
;
253 X11Image
[] exitCache
;
255 X11Image
[][] monsterCache
;
256 int[][] monsterOfsCache
;
257 X11Image
[] eugeneSpr
;
259 X11Image
[] skylabSpr
;
260 X11Image
[] switchesSpr
;
265 ubyte saveKeyState () {
267 ((kLeftDown ?
1 : 0)<<0)|
268 ((kRightDown ?
1 : 0)<<1)|
269 ((kUpDown ?
1 : 0)<<2)|
270 ((kDownDown ?
1 : 0)<<3)|
271 ((kJumpDown ?
1 : 0)<<4)|
275 void restoreKeyState (ubyte b
) {
276 keyLeft
= ((b
&(1<<0)) != 0);
277 keyRight
= ((b
&(1<<1)) != 0);
278 keyUp
= ((b
&(1<<2)) != 0);
279 keyDown
= ((b
&(1<<3)) != 0);
280 keyJump
= ((b
&(1<<4)) != 0);
287 willyDir
= (willy
.sd
> 0 ?
-1 : 1);
288 willyJump
= willyFall
= willyConv
= willyStall
= 0;
290 willyLastMoveDir
= 0;
291 kLeft
= kRight
= kJump
= kUp
= kDown
= false;
292 kLeftDown
= kRightDown
= kJumpDown
= kUpDown
= kDownDown
= false;
300 buildMonsterImages();
306 finalSpr
= buildImageMasked(gameData
["final"], 256, 64);
307 sunSpr
= buildImageMasked(gameData
["sun"], 24, 16);
308 gfxAir
= buildImageMasked(gameData
["air"], 256, 8);
313 foreach (ref c
; map
) {
314 if (c
== 4) c
= 8; // 'crumb'
315 else if (c
< 1 || c
> 7) c
= 0; // emptyness
320 foreach (const ref ki
; keys
.info
) {
321 if (ki
.s
== 0) continue;
326 platforms
= platforms
.dup
;
327 deadlies
= deadlies
.dup
;
328 switches
= switches
.dup
;
329 enemies
= enemies
.dup
;
333 Enemy
[] a
= [{ x
:120, y
:1, d
/*ir*/:0, min
:1, max
:87, ink
:6 }];
337 if (roomIdx
== 7 || roomIdx
== 11) {
338 Enemy
[] a
= [{ x
:120, y
:0, max
:104, frame
:0, ink
:2, m
:0 }];
347 {p
:0, s
:4, ink
:6, max
:72, m
:0, frame
:0},
348 {p
:2, s
:3, ink
:5, max
:56, m
:0, frame
:0},
349 {p
:1, s
:1, ink
:4, max
:32, m
:0, frame
:0}
351 skylabEnemies
= a
.dup
;
352 foreach (immutable f
, const ref se
; skylabEnemies
) {
354 skylabCoords
[f
][se
.p
].x
, skylabCoords
[f
][se
.p
].y
,
355 se
.frame
, se
.ink
, se
.m
, se
.s
, se
.max
, se
.p
,
359 // Solar Power Generator?
360 if (roomIdx
== 18) buildSPG();
363 void keyLeft (bool pressed
) { if (pressed
) kLeft
= true; kLeftDown
= pressed
; }
364 void keyRight (bool pressed
) { if (pressed
) kRight
= true; kRightDown
= pressed
; }
365 void keyUp (bool pressed
) { if (pressed
) kUp
= true; kUpDown
= pressed
; }
366 void keyDown (bool pressed
) { if (pressed
) kDown
= true; kDownDown
= pressed
; }
367 void keyJump (bool pressed
) { if (pressed
) kJump
= true; kJumpDown
= pressed
; }
370 kLeft
= kLeft||kLeftDown
;
371 kRight
= kRight||kRightDown
;
373 kDown
= kDown||kDownDown
;
374 kJump
= kJump||kJumpDown
;
375 scope(exit
) kLeft
= kRight
= kJump
= kUp
= kDown
= false;
378 conveyor
.frame
+= (conveyor
.d ?
-1 : 1);
379 if (conveyor
.frame
< 0) conveyor
.frame
= 3; else if (conveyor
.frame
> 3) conveyor
.frame
= 0;
381 if (!willyJump
) stepCrumb();
397 void cheatRemoveKeys () { keys
.info
= null; }
399 // ////////////////////////////////////////////////////////////////////// //
403 ubyte getBlockAt (int x
, int y
, bool simplify
=false) const nothrow @nogc {
406 if (x
< 0 || y
< 0 || x
> 31 || y
> 15) return 0; // empty
407 ubyte b
= map
[y
*32+x
];
410 else if (b
> 7) b
= 4;
411 else if (b
== 6) b
= 5;
412 else if (b
== 2) b
= 1;
417 bool checkExit () const nothrow @nogc {
418 if (keys
.info
.length
!= 0) return false;
421 return (willyX
>= x
-2 && willyX
+10 <= x
+18 && willyY
>= y
-5 && willyY
+16 <= y
+22);
424 bool checkMonsters () const nothrow {
425 foreach (immutable f
, const ref m
; enemies
) {
426 auto cc
= monsterOfsCache
[f
];
427 if (m
.x
< 0 || m
.y
< 0) continue;
429 if (pixelCheckMonster(m
.x
, m
.y
, gameData
["vrobo"], cc
[m
.anim
])) return true;
431 if (pixelCheckMonster(m
.x
&248, m
.y
, gameData
["hrobo"], cc
[((m
.x
&m
.anim
)&0xfe)+m
.d
/*ir*/])) return true;
437 if (pixelCheckMonster(eugene
.x
, eugene
.y
, gameData
["eugene"], 256*curLSet
)) return true;
441 if (pixelCheckMonster(kong
.x
, kong
.y
, gameData
["kong"], 256*kong
.frame
)) return true;
445 foreach (const ref sk
; skylab
) {
446 if (pixelCheckMonster(sk
.x
, sk
.y
, gameData
["sky"], 256*sk
.frame
)) return true;
452 void checkSwitch () nothrow @nogc {
453 foreach (ref ss
; switches
) {
454 if (ss
.s
/*tate*/ != 1) continue;
457 if (x
+7 >= willyX
&& y
+7 >= willyY
&& x
< willyX
+8 && y
< willyY
+16) ss
.s
/*tate*/ = 1+1;
461 bool checkSPG () const nothrow @nogc {
462 foreach (const ref sp
; spg
) {
465 if (x
< 0 || y
< 0) break;
466 if (x
+7 >= willyX
&& x
< willyX
+8 && y
+7 >= willyY
&& y
< willyY
+16) return true;
471 X11Image
draw (X11Image img
=null) {
472 if (img
is null) img
= new X11Image(8*Room
.Width
, 8*Room
.Height
);
474 foreach (immutable y
; 0..img
.height
) {
475 foreach (immutable x
; 0..img
.width
) {
476 img
.setPixel(x
, y
, palcol(this.paper
));
479 foreach (immutable y
; 0..Room
.Height
) {
480 foreach (immutable x
; 0..Room
.Width
) {
481 ubyte blk
= this.map
[pos
++];
482 if (blk
< 1 || blk
== 7) continue;
483 if (blk
== 4) blk
= 8;
484 if (blk
< brickCache
.length
&& brickCache
[blk
] !is null) {
485 brickCache
[blk
].blitTo(img
, x
*8, y
*8);
489 if (this.roomIdx
== 19) {
490 finalSpr
.blitTo(img
, 0, 0);
491 sunSpr
.blitTo(img
, 60, 32);
494 void drawConveyor () {
495 auto conv
= &this.conveyor
;
496 if (conv
.l
< 1) return;
499 convCache
[conv
.frame
].blitTo(img
, conv
.x
, conv
.y
, conv
.l
);
504 if (this.keys
.info
.length
!= 0) {
507 br
= this.frameNo
&31;
508 if (br
> 15) br
= 31-br
;
511 exitCache
[br
].blitTo(img
, this.exit
.x
, this.exit
.y
);
515 auto ff
= this.frameNo
%16;
516 if (ff
>= 8) ff
= 15-ff
;
517 auto keyimg
= keyCache
[ff
];
518 foreach_reverse (const ref ki
; this.keys
.info
) {
519 if (ki
.x
< 0 || ki
.y
< 0) continue;
520 //eraseRect(ki.x, ki.y, 8, 8);
522 keyimg
.blitTo(img
, ki
.x
&248, ki
.y
);
526 void drawMonsters () {
527 foreach (immutable f
, const ref m
; this.enemies
) {
528 if (m
.x
< 0 || m
.y
< 0) continue;
529 auto slist
= monsterCache
[f
];
532 //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
533 slist
[m
.anim
].blitTo(img
, x
, m
.y
);
535 auto sidx
= ((x
&m
.anim
)&0xfe)+m
.d
/*ir*/;
536 if (sidx
< slist
.length
) {
537 slist
[sidx
].blitTo(img
, x
&248, m
.y
);
539 import core
.stdc
.stdio
: stderr
, fprintf
;
540 stderr
.fprintf("monster #%u is fucked: sidx=%u; max=%u", cast(uint)f
, cast(uint)sidx
, cast(uint)slist
.length
);
544 if (this.eugene
.x
>= 0) {
546 eugeneSpr
[curLSet
*8+this.eugene
.ink
].blitTo(img
, this.eugene
.x
, this.eugene
.y
);
548 if (this.kong
.x
>= 0) {
549 kongSpr
[this.kong
.frame
*8+this.kong
.ink
].blitTo(img
, this.kong
.x
, this.kong
.y
);
551 if (this.skylab
.length
) {
552 foreach (const ref sk
; this.skylab
) {
553 skylabSpr
[sk
.frame
*8+sk
.ink
].blitTo(img
, sk
.x
, sk
.y
);
559 foreach (const ref ss
; this.switches
) {
560 if (ss
.s
== 0) continue;
561 switchesSpr
[ss
.s
-1].blitTo(img
, ss
.x
, ss
.y
);
566 foreach (const ref sp
; this.spg
) {
569 if (x
< 0 || y
< 0) break;
570 //ctx.fillStyle = mainPal[noerase?6:this.paper];
571 //ctx.fillRect(x, y, 8, 8);
572 foreach (immutable dy
; 0..8) {
573 foreach (immutable dx
; 0..8) {
574 img
.setPixel(x
+dx
, y
+dy
, palcol(6));
581 auto willyPos
= (this.willyX
&15)>>1;
582 if (this.willyDir
< 0) willyPos
+= 8;
585 if (this.checkMonsters()) wy
= 16;
586 willySpr
.blitTo(willyPos
*16, wy
, 16, 16, img
, this.willyX
&248, this.willyY
);
588 willySpr
.blitTo(willyPos
*16, 0, 16, 16, img
, this.willyX
&248, this.willyY
);
593 gfxAir
.blitFast(0, 136);
594 printAt(1, 136, "AIR", 9);
595 //for (int x = 3; x >= 0; --x) PlotXY (30, 138 + x, GFXair + 512 + 256 * x + 30, 226, 1);
596 setPixel(32, 138, palcol(122));
597 setPixel(32, 139, palcol(116));
598 setPixel(32, 140, palcol(119));
599 setPixel(32, 141, palcol(123));
601 drawBar8(33, 138, air
-33, 1, 120);
602 drawBar8(33, 139, air
-33, 1, 114);
603 drawBar8(33, 140, air
-33, 1, 117);
604 drawBar8(33, 141, air
-33, 1, 121);
607 setPixel(air
-1, 138, palcol(122));
608 setPixel(air
-1, 139, palcol(116));
609 setPixel(air
-1, 140, palcol(119));
610 setPixel(air
-1, 141, palcol(123));
624 drawStr(128-cast(int)title
.length
*6/2, 128, title
, rgbcol(255, 255, 0));
630 // ////////////////////////////////////////////////////////////////////// //
631 // pixel-perfect collision detector
632 // fully stolen from Andy's sources %-)
633 bool pixelCheckMonster (int rx
, int ry
, const(ubyte)[] darr
, int dpos
=0) const nothrow {
634 static ubyte[256] mpcGrid
;
640 if (rx
< -15 || rx
> 15 || ry
< -15 || ry
> 15) return false;
643 if (rx
< 0) { x
= 0; rx
= -rx
; w
= 16-rx
; } else { x
= rx
; rx
= 0; w
= 16-x
; }
644 // partial plot monster
645 for (int y
= ry
+15; y
>= ry
; --y
) {
646 if (y
>= 0 && y
< 16) {
648 int rp
= dpos
+(y
-ry
)*16+rx
;
649 for (int dx
= 0; dx
< w
; ++dx
, ++gp
, ++rp
) mpcGrid
[gp
] = darr
[rp
];
652 auto warr
= gameData
["willy"];
653 int wptr
= ((willyX
&15)>>1)*256+(willyDir
< 0 ?
2048 : 0);
654 // check for collision
656 for (x
= 255; x
>= 0; --x
, ++wptr
, ++mp
) if (warr
[wptr
] && mpcGrid
[mp
]) return true;
660 // ////////////////////////////////////////////////////////////////////// //
661 bool isWillyFalling () const nothrow @nogc {
662 if (willyY
&7) return true;
663 for (int dx
= 0; dx
<= 8; dx
+= 8) {
664 ubyte b
= getBlockAt(willyX
+dx
, willyY
+16, true);
665 if (b
> 0 && b
!= 5) return false;
670 bool isWillyInDeadly () const nothrow @nogc {
671 for (int dx
= 0; dx
<= 8; dx
+= 8) {
672 for (int dy
= 0; dy
<= 16; dy
+= 8) {
673 if (getBlockAt(willyX
+dx
, willyY
+dy
, true) == 5) return true;
679 bool isWillyOnConv () const nothrow @nogc {
680 if (willyY
&7) return false;
681 ubyte b0
= getBlockAt(willyX
, willyY
+16);
682 ubyte b1
= getBlockAt(willyX
+8, willyY
+16);
683 return (b0
== 7 || b1
== 7);
686 // ////////////////////////////////////////////////////////////////////// //
687 // called on each frame
688 void buildSPG (bool forced
=false) {
689 if (!forced
&& roomIdx
!= 18) {
694 bool spgCheckMonster (int x
, int y
) {
697 foreach (const ref cm
; enemies
) {
700 if (x
+7 >= mx
&& x
< mx
+15 && y
+7 >= my
&& y
< my
+15) return true;
707 int dir
= 0, idx
= 0;
711 spg
.assumeSafeAppend
;
714 void addXY (int x
, int y
) {
716 while (spg
.length
< idx
) spg
~= SPGRay(-1, -1);
722 ubyte blockhit
= map
[y
*32+x
];
723 bool robohit
= spgCheckMonster(x
, y
);
725 if (blockhit
&& robohit
) {
728 } else if (!blockhit
&& robohit
) {
729 if (idx
&& spg
[idx
-1].x
== x
&& spg
[idx
-1].y
== y
) {
730 spg
[idx
-1].x
= spg
[idx
-1].y
= -1;
736 } else if (!blockhit
&& !robohit
) {
738 } else if (blockhit
&& !robohit
) {
739 if (idx
&& spg
[idx
-1].x
== x
&& spg
[idx
-1].y
== y
) {
740 spg
[idx
-1].x
= spg
[idx
-1].y
= -1;
749 blockhit
= map
[y
*32+x
];
750 if (y
== 15 || blockhit
) done
= true;
753 blockhit
= map
[y
*32+x
];
754 if (x
== 0 || blockhit
) done
= true;
757 if (!dir
) { --x
; if (!x
) done
= true; }
758 else { ++y
; if (++y
== 15) done
= true; }
764 void stepMonsters () {
765 foreach (ref m
; enemies
) {
766 if (m
.x
< 0 || m
.y
< 0) continue;
769 auto spd
= m
.s
/*peed*/;
770 if (m
.d
/*ir*/ != 0) {
773 if (y
< m
.min || y
< 0) { y
+= spd
; m
.d
/*ir*/ = 0; }
777 if (y
> m
.max
) { y
-= spd
; m
.d
/*ir*/ = 1; }
780 m
.anim
= (m
.anim
+1)&3;
783 auto spd
= (2>>m
.s
/*peed*/);
784 if (m
.d
/*ir*/ != 0) {
787 if (x
< m
.min
) { m
.d
/*ir*/ = 0; x
+= spd
; }
791 if (x
> m
.max
+6) { m
.d
/*ir*/ = 1; x
-= spd
; }
798 if (keys
.info
.length
== 0) {
799 // no keys, Eugene tries to block the exit
800 eugene
.ink
= (eugene
.ink
+1)&7;
801 eugene
.y
+= (eugene
.y
< eugene
.max ?
1 : 0);
803 if (eugene
.d
/*ir*/ != 0) {
806 if (eugene
.y
< eugene
.min
) { eugene
.d
/*ir*/ = 0; ++eugene
.y
; }
810 if (eugene
.y
> eugene
.max
) { eugene
.d
/*ir*/ = 1; --eugene
.y
; }
816 switch (kong
.falling
) {
817 case 1: // just started
820 //eraseRect(16, 120, 16, 8);
830 if (kong
.y
>= kong
.max
) { kong
.x
= -1; score
+= 100; }
831 if (!kong
.delay
) { kong
.delay
= 4; kong
.frame
= ((kong
.frame
-1)&1)+2; } else --kong
.delay
;
834 if (!kong
.delay
) { kong
.delay
= 8; kong
.frame
= (kong
.frame
+1)&1; } else --kong
.delay
;
839 foreach (immutable idx
, ref sk
; skylab
) {
851 if (sk
.frame
== 7) sk
.m
= 2;
855 sk
.x
= skylabCoords
[idx
][sk
.p
].x
;
856 sk
.y
= skylabCoords
[idx
][sk
.p
].y
;
865 if (willyY
&7) return;
866 for (int f
= 0; f
<= 8; f
+= 8) {
869 ubyte b
= getBlockAt(x
, y
);
876 //eraseRect(x*8, y*8, 8, 8);
881 while (keys
.info
.length
) {
883 foreach (immutable idx
; 0..keys
.info
.length
) {
884 auto ki
= &keys
.info
[idx
];
888 if (kx
+7 >= willyX
&& kx
< willyX
+10 && ky
+7 >= willyY
&& ky
< willyY
+16) {
890 foreach (immutable c
; idx
+1..keys
.info
.length
) keys
.info
[c
-1] = keys
.info
[c
];
891 keys
.info
.length
-= 1;
902 if (holeLen
> 0 && switches
.length
&& switches
[0].s
/*tate*/ > 1) {
905 enemies
[1].max
+= 24;
917 if (kong
.x
>= 0 && !kong
.falling
&& switches
.length
> 1 && switches
[1].s
/*tate*/ > 1) kong
.falling
= 1;
920 void stepWillyActions () {
921 bool doWillyLeft () {
922 if (willyDir
> 0) { willyDir
= -1; return true; }
924 auto b0
= getBlockAt(xx
, willyY
);
925 auto b1
= getBlockAt(xx
, willyY
+8);
926 auto b2
= (willyY
&7 ?
getBlockAt(xx
, willyY
+16) : b1
);
927 if (b0
== 3 || b1
== 3 || b2
== 3) return false;
929 if (willyX
< 0) willyX
+= 240;
930 willyLastMoveDir
= -1;
934 bool doWillyRight () {
935 if (willyDir
< 0) { willyDir
= 1; return true; }
936 if (willyX
> 245) return false;
938 auto b0
= getBlockAt(xx
, willyY
);
939 auto b1
= getBlockAt(xx
, willyY
+8);
940 auto b2
= (willyY
&7 ?
getBlockAt(xx
, willyY
+16) : b1
);
941 if (b0
== 3 || b1
== 3 || b2
== 3) return false;
943 if (willyX
> 240) willyX
-= 240;
944 willyLastMoveDir
= 1;
948 void doWillyJump () {
949 if (!willyJump
) return;
950 willyY
+= willyJ
[willyJump
];
953 if (willyJumpDir
< 0) mv
= doWillyLeft(); else if (willyJumpDir
> 0) mv
= doWillyRight();
957 auto b0
= getBlockAt(x
, willyY
);
958 auto b1
= getBlockAt(x
+8, willyY
);
959 if (b0
== 3 || b1
== 3) {
960 // headboom! (apstenu %-)
962 willyY
-= willyJ
[willyJump
];
963 willyJump
= 0; // enough flying
968 if (willyJump
> 12) willyFall
+= willyJ
[willyJump
];
969 if ((willyY
&7) == 0) {
970 auto b0
= getBlockAt(willyX
, willyY
+16);
971 auto b1
= getBlockAt(willyX
+8, willyY
+16);
973 if (b0
== 3 || b1
== 3) willyX
= x
;
974 willyFall
= 0; // can't fall too deep while jumping
975 willyJump
= 0; // enough flying
976 if (b0
== 7 || b1
== 7) willyStall
= 1; // conveyor?
982 if (willyJump
> 18) willyJump
= 0;
986 if (willyDead
) return;
988 if (isWillyInDeadly()) { willyDead
= true; return; }
990 if (checkMonsters()) { willyDead
= true; return; }
992 if (checkMonsters()) singleStep
= singleStepWait
= true;
995 auto wasJump
= false;
997 willyLastMoveDir
= 0;
999 if (willyJump
) return;
1003 auto falling
= isWillyFalling();
1004 if (!kDown
&& falling
) {
1005 willyConv
= willyStall
= willyLastMoveDir
= 0;
1008 if (willyY
> 112) willyY
-= 112;
1012 if (!falling
&& willyFall
> 34) willyDead
= true; // too high!
1013 auto lfall
= willyFall
;
1016 if (willyDead
) return;
1018 auto dx
= (kLeft ?
-1 : kRight ?
1 : 0);
1019 if (isWillyOnConv()) {
1020 auto cdir
= (conveyor
.d ?
1 : -1);
1021 //dx==cdir,!dx,lastmove==cdir
1022 if (willyLastMoveDir
== cdir || dx
== cdir ||
!dx
) { willyConv
= cdir
; willyStall
= 0; } // was moving in conv. dir or standing
1024 // Willy just steps on the conveyor, and Willy walking to the opposite side
1026 if (wasJump
&& willyLastMoveDir
== -cdir
) {
1027 willyConv
= dx
; // from jump, can do opposite
1030 if (lfall
> 0 ||
!willyLastMoveDir
) { dx
= 0; willyStall
= 1; } // lands on conveyor, not from jump
1033 // Willy was on conveyor
1034 dx
= (willyStall ?
0 : willyConv
);
1037 willyConv
= willyStall
= 0;
1040 //if (willyConv) dx = willyConv;
1041 if (kUp
&& !wasJump
) {
1042 willyConv
= willyStall
= willyLastMoveDir
= 0;
1048 if (kDown
) willyY
-= 8;
1049 willyLastMoveDir
= 0;
1050 if (dx
< 0) doWillyLeft(); else if (dx
> 0) doWillyRight();
1053 // ////////////////////////////////////////////////////////////////////// //
1054 void buildWilly () {
1055 auto img
= new X11Image(16*16, 16+16);
1056 auto ww
= gameData
["willy"];
1057 foreach (immutable f
; 0..16) {
1058 foreach (immutable y
; 0..16) {
1059 foreach (immutable x
; 0..16) {
1060 ubyte c
= ww
[f
*256+y
*16+x
];
1062 img
.setPixel(f
*16+x
, y
, Transparent
);
1064 img
.setPixel(f
*16+x
, y
, palcol(c
));
1066 img
.setPixel(f
*16+x
, y
+16, palcol(15));
1074 void buildBrickImages () {
1075 auto buildBrickImage (in ref Room
.CommonGfx brk
, int skipy
=0) {
1076 return buildImageBrick(gameData
["blocks"][brk
.gfx
*64..$], brk
.ink
, this.paper
, skipy
);
1079 foreach (immutable f
; 0..8) {
1082 //case 0: case 7: img = buildBrickImage(this.wall, 16); break;
1083 case 1: img
= buildBrickImage(this.platforms
[0]); break;
1084 case 2: img
= buildBrickImage(this.platforms
[1]); break;
1085 case 3: img
= buildBrickImage(this.wall
); break;
1086 //case 4: img = buildBrickImage(this.crumb); break;
1087 case 5: img
= buildBrickImage(this.deadlies
[0]); break;
1088 case 6: img
= buildBrickImage(this.deadlies
[1]); break;
1093 foreach (immutable f
; 0..9) brickCache
~= buildBrickImage(this.crumb
, f
);
1096 void buildConvImages () {
1098 auto conv
= &this.conveyor
;
1099 if (conv
.y
<= 0 || conv
.l
< 1) return;
1100 foreach (immutable f
; 0..4) {
1101 convCache
~= buildImageBrick(gameData
["conv"][conv
.gfx
*256+f
*64..$], conv
.ink
, this.paper
);
1105 void buildExitImages () {
1107 exitCache
~= buildImageMasked(gameData
["exits"][this.exit
.gfx
*256..$], 16, 16);
1108 foreach (immutable f
; 0..16) exitCache
~= buildImageMaskedShiny(gameData
["exits"][this.exit
.gfx
*256..$], f
, 16, 16);
1111 void buildKeysImages () {
1113 foreach (immutable f
; 0..16) keyCache
~= buildImageMaskedShiny(gameData
["keys"][this.keys
.gfx
*64..$], f
, 8, 8);
1116 void buildMonsterImages () {
1117 monsterCache
= null;
1118 monsterOfsCache
= null;
1119 foreach (immutable f
, const ref m
; this.enemies
) {
1120 if (m
.x
< 0 || m
.y
< 0) {
1121 monsterOfsCache
~= null;
1122 monsterCache
~= null;
1128 foreach (immutable c
; 0..4) {
1129 auto n
= (m
.gfx
+c
)*256;
1131 r
~= buildImageMaskedInk(gameData
["vrobo"][n
..$], m
.ink
-1, 16, 16);
1134 foreach (immutable c
; 0..(m
.anim
>>1)+1) {
1135 auto n
= (m
.gfx
+c
)*256;
1137 r
~= buildImageMaskedInk(gameData
["hrobo"][n
..$], m
.ink
-1, 16, 16);
1140 r
~= buildImageMaskedInk(gameData
["hrobo"][n
..$], m
.ink
-1, 16, 16);
1143 monsterOfsCache
~= cc
;
1148 void buildEugeneImages () {
1150 for (int f
= 0; f
<= 256; f
+= 256) {
1151 foreach (immutable c
; 0..8) {
1152 eugeneSpr
~= buildImageMaskedInk(gameData
["eugene"][f
..$], c
, 16, 16);
1157 void buildKongImages () {
1159 foreach (immutable f
; 0..4) {
1160 foreach (immutable c
; 0..8) {
1161 kongSpr
~= buildImageMaskedInk(gameData
["kong"][f
*256..$], c
, 16, 16);
1166 void buildSkyLabImages () {
1168 foreach (immutable f
; 0..8) {
1169 foreach (immutable c
; 0..8) {
1170 skylabSpr
~= buildImageMaskedInk(gameData
["sky"][f
*256..$], c
, 16, 16);
1175 void buildSwitchImages () {
1178 if (auto pp
= "switches" in gameData
) swg
= *pp
; else swg
= gameData
["switch"];
1179 foreach (immutable f
; 0..2) {
1180 switchesSpr
~= buildImageBrick(swg
[f
*64..$], this.platforms
[1].ink
, this.paper
);
1185 void load (ref const(ubyte)[] data
) {
1186 void readBuf (void[] buf
) {
1187 import core
.stdc
.string
: memcpy
;
1188 if (buf
.length
== 0) return;
1189 if (data
.length
< buf
.length
) throw new Exception("invalid level");
1190 memcpy(buf
.ptr
, data
.ptr
, buf
.length
);
1191 data
= data
[buf
.length
..$];
1195 if (data
.length
< 1) throw new Exception("invalid level");
1196 ubyte res
= data
[0];
1202 if (data
.length
< 2) throw new Exception("invalid level");
1203 short res
= cast(short)(data
[0]|
(data
[1]<<8));
1212 foreach (immutable idx
, char ch
; title
[]) {
1214 tit
= title
[0..idx
];
1216 while (tit
.length
&& tit
[0] <= ' ') tit
= tit
[1..$];
1217 while (tit
.length
&& tit
[$-1] <= ' ') tit
= tit
[0..$-1];
1218 this.title
= tit
.idup
;
1224 platforms
.length
= 2;
1225 foreach (immutable idx
; 0..2) {
1234 wall
.paper
= readU8
;
1238 crumb
.paper
= readU8
;
1241 deadlies
.length
= 2;
1242 foreach (immutable idx
; 0..2) {
1250 conveyor
.ink
= readU8
;
1251 conveyor
.paper
= readU8
;
1252 conveyor
.gfx
= readU8
;
1258 conveyor
.x
= readS16
;
1259 conveyor
.y
= readS16
;
1260 conveyor
.d
= readU8
;
1261 conveyor
.l
= readU8
;
1263 keys
.info
.length
= 5;
1264 foreach (immutable idx
; 0..5) keys
.info
[idx
].x
= readS16
;
1265 foreach (immutable idx
; 0..5) keys
.info
[idx
].y
= readS16
;
1267 foreach (immutable idx
; 0..5) keys
.info
[idx
].s
= readU8
;
1269 switches
.length
= 2;
1270 foreach (immutable idx
; 0..2) switches
[idx
].x
= readS16
;
1271 foreach (immutable idx
; 0..2) switches
[idx
].y
= readS16
;
1272 foreach (immutable idx
; 0..2) switches
[idx
].s
= readU8
;
1280 enemies
.length
= 4*2;
1282 foreach (immutable idx
; 0..4) enemies
[idx
].vert
= false;
1283 foreach (immutable idx
; 0..4) enemies
[idx
].ink
= readU8
;
1284 foreach (immutable idx
; 0..4) enemies
[idx
].paper
= readU8
;
1285 foreach (immutable idx
; 0..4) enemies
[idx
].x
= readS16
;
1286 foreach (immutable idx
; 0..4) enemies
[idx
].y
= readS16
;
1287 foreach (immutable idx
; 0..4) enemies
[idx
].min
= readS16
;
1288 foreach (immutable idx
; 0..4) enemies
[idx
].max
= readS16
;
1289 foreach (immutable idx
; 0..4) enemies
[idx
].d
= readU8
;
1290 foreach (immutable idx
; 0..4) enemies
[idx
].s
= readU8
;
1291 foreach (immutable idx
; 0..4) enemies
[idx
].gfx
= readS16
;
1292 foreach (immutable idx
; 0..4) enemies
[idx
].flip
= readU8
;
1293 foreach (immutable idx
; 0..4) enemies
[idx
].anim
= readU8
;
1295 foreach (immutable idx
; 4..8) enemies
[idx
].vert
= true;
1296 foreach (immutable idx
; 4..8) enemies
[idx
].ink
= readU8
;
1297 foreach (immutable idx
; 4..8) enemies
[idx
].paper
= readU8
;
1298 foreach (immutable idx
; 4..8) enemies
[idx
].x
= readS16
;
1299 foreach (immutable idx
; 4..8) enemies
[idx
].y
= readS16
;
1300 foreach (immutable idx
; 4..8) enemies
[idx
].min
= readS16
;
1301 foreach (immutable idx
; 4..8) enemies
[idx
].max
= readS16
;
1302 foreach (immutable idx
; 4..8) enemies
[idx
].d
= readU8
;
1303 foreach (immutable idx
; 4..8) enemies
[idx
].s
= readU8
;
1304 foreach (immutable idx
; 4..8) enemies
[idx
].gfx
= readS16
;
1305 foreach (immutable idx
; 4..8) enemies
[idx
].anim
= readU8
;
1310 // ////////////////////////////////////////////////////////////////////////// //
1311 public void blitTo (X11Image src
, X11Image dst
, int x
, int y
, int repx
=1) {
1312 foreach (immutable r
; 0..repx
) {
1313 foreach (immutable dy
; 0..src
.height
) {
1314 foreach (immutable dx
; 0..src
.width
) {
1317 if (xx
< 0 || yy
< 0 || xx
>= dst
.width || yy
>= dst
.height
) continue;
1318 auto c
= src
.getPixel(dx
, dy
);
1319 if (isTransparent(c
)) continue;
1320 dst
.setPixel(xx
, yy
, c
);
1328 public void blitTo (X11Image src
, int x0
, int y0
, int ww
, int hh
, X11Image dst
, int x
, int y
) {
1329 foreach (immutable dy
; 0..ww
) {
1330 foreach (immutable dx
; 0..hh
) {
1333 if (xx
< 0 || yy
< 0 || xx
>= dst
.width || yy
>= dst
.height
) continue;
1334 auto c
= src
.getPixel(x0
+dx
, y0
+dy
);
1335 if (isTransparent(c
)) continue;
1336 dst
.setPixel(xx
, yy
, c
);
1342 // ////////////////////////////////////////////////////////////////////////// //
1343 public void setPixel8 (int x
, int y
, ubyte col
) {
1344 setPixel(x
, y
, palcol(col
));
1348 public void drawBar8 (int x
, int y
, int w
, int h
, ubyte col
) {
1349 foreach (immutable dy
; 0..h
) {
1350 foreach (immutable dx
; 0..w
) {
1351 setPixel(x
+dx
, y
+dy
, palcol(col
));
1357 // ////////////////////////////////////////////////////////////////////////// //
1358 public void drawWilly4 (int ofs
) {
1359 auto gw
= gameData
["willy"][256*ofs
..$];
1360 enum x
= 232, y
= 72;
1361 foreach (immutable dy
; 0..16) {
1362 foreach (immutable dx
; 0..16) {
1365 setPixel8(x
+dx
, y
+dy
, (c ? c
+0*16 : 39));
1371 // ////////////////////////////////////////////////////////////////////////// //
1372 public void printAt (int x
, int y
, const(char)[] text
, ubyte col
) {
1373 if (text
.length
== 0) return;
1375 int count
= 0, count2
, count3
;
1376 int currentx
, currenty
;
1377 const(ubyte)* fonty
, fonty2
;
1380 currentx = ((x&127)*8)|((x&128) ? 4 : 0);
1381 currenty = ((y&127)*8)|((y&128) ? 4 : 0);
1382 { import std.stdio; writeln(currentx, "; ", currenty); }
1387 auto gfxFant
= gameData
["fant"];
1388 auto fontb
= gameData
["fontb"];
1390 while (text
.length
) {
1391 int alpha
= text
[0];
1394 if (text
.length
== 0) break;
1401 if (++count
== 33) return;
1403 fonty
= fontb
.ptr
+alpha
*8;
1404 fonty2
= gfxFant
.ptr
+alpha
*8;
1407 for (count2
= 0; count2
< 8; ++count2
) {
1409 for (count3
= 0; count3
< 8; ++count3
) {
1410 if (fonty
[0]&(1<<count3
)) {
1411 setPixel(x
, y
, palcol(col
));
1412 } else if (fonty2
[0]&(1<<count3
)) {
1414 BYTE data = mm_gfx_getpixel (x, y);
1415 BYTE data2 = (data & 15) + 3;
1416 setPixel(x, y, palcol((data&240)|(data2 > 15 ? 15 : data2)));
1418 putPixel(x
, y
, palcol(col
)|
(0x7f<<vlAShift
));
1420 //setPixel(x, y, palcol(data2 > 15 ? 15 : data2));
1422 //setPixel(x, y, rgbcol(255, 127, 0));