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 __gshared
ubyte[][string
] gameData
;
30 __gshared Room
[] gameRooms
;
31 __gshared string gameLevelsName
;
34 private void loadRooms (const(ubyte)[] data
) {
36 if (data
.length
< 8) throw new Exception("invalid levels");
37 if (data
[0..8] != "MANIC\x0d\x0a\x1a") throw new Exception("invalid levels");
39 if (data
.length
< 16) throw new Exception("invalid levels");
40 char[16] lname
= (cast(const(char)[])data
)[0..16];
43 foreach (immutable idx
, char ch
; lname
[]) {
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") {
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 // ////////////////////////////////////////////////////////////////////////// //
76 VColor
palcol (ubyte c
) {
77 static VColor
[256] palette
;
78 static bool palset
= false;
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
);
93 // ////////////////////////////////////////////////////////////////////////// //
94 private X11Image
buildImageMasked (const(ubyte)[] darr
, int w
, int h
) {
95 assert(w
> 0 && h
> 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
++];
102 img
.setPixel(x
, y
, palcol(c
));
104 img
.setPixel(x
, y
, Transparent
);
112 private X11Image
buildImageMaskedShiny (const(ubyte)[] darr
, int brightness
, int w
, int h
) {
113 assert(w
> 0 && h
> 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
++];
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
)));
124 img
.setPixel(x
, y
, Transparent
);
132 private X11Image
buildImageMaskedInk (const(ubyte)[] darr
, int ink
, int w
, int h
) {
133 assert(w
> 0 && h
> 0);
135 auto img
= new X11Image(w
, h
);
136 if (ink
< 0) ink
= 0;
138 foreach (immutable y
; 0..h
) {
139 foreach (immutable x
; 0..w
) {
140 ubyte c
= darr
[dpos
++];
142 img
.setPixel(x
, y
, palcol(cast(ubyte)(c
+ink
)));
144 img
.setPixel(x
, y
, Transparent
);
152 // ////////////////////////////////////////////////////////////////////////// //
153 private X11Image
buildImageBrick (const(ubyte)[] darr
, int ink
, int paper
, int skipy
=0) {
155 enum { w
= 8, h
= 8 }
157 auto img
= new X11Image(w
, h
);
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
)));
169 // ////////////////////////////////////////////////////////////////////////// //
170 // willy jump offsets
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"
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
;
203 ubyte[Height
*Width
] map
; // 16 rows, 32 cols
204 ubyte border
, ink
, paper
;
205 CommonGfx
[] platforms
;
208 CommonGfx
[] deadlies
;
211 SwitchPos
[] switches
;
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
225 int willyX
, willyY
, willyDir
, willyJump
, willyJumpDir
;
226 int willyLastMoveDir
, willyFall
, willyConv
, willyStall
;
227 public bool willyDead
;
231 bool kLeft
, kRight
, kJump
, kUp
, kDown
;
232 bool kLeftDown
, kRightDown
, kJumpDown
, kUpDown
, kDownDown
;
238 X11Image
[] brickCache
;
239 X11Image
[] convCache
;
240 X11Image
[] exitCache
;
242 X11Image
[][] monsterCache
;
243 int[][] monsterOfsCache
;
244 X11Image
[] eugeneSpr
;
246 X11Image
[] skylabSpr
;
247 X11Image
[] switchesSpr
;
251 ubyte saveKeyState () {
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);
273 willyDir
= (willy
.sd
> 0 ?
-1 : 1);
274 willyJump
= willyFall
= willyConv
= willyStall
= 0;
276 willyLastMoveDir
= 0;
277 kLeft
= kRight
= kJump
= kUp
= kDown
= false;
278 kLeftDown
= kRightDown
= kJumpDown
= kUpDown
= kDownDown
= false;
286 buildMonsterImages();
291 finalSpr
= buildImageMasked(gameData
["final"], 256, 64);
292 sunSpr
= buildImageMasked(gameData
["sun"], 24, 16);
297 foreach (ref c
; map
) {
298 if (c
== 4) c
= 8; // 'crumb'
299 else if (c
< 1 || c
> 7) c
= 0; // emptyness
304 foreach (const ref ki
; keys
.info
) {
305 if (ki
.s
== 0) continue;
310 platforms
= platforms
.dup
;
311 deadlies
= deadlies
.dup
;
312 switches
= switches
.dup
;
313 enemies
= enemies
.dup
;
317 Enemy
[] a
= [{ x
:120, y
:1, d
/*ir*/:0, min
:1, max
:87, ink
:6 }];
321 if (roomIdx
== 7 || roomIdx
== 11) {
322 Enemy
[] a
= [{ x
:120, y
:0, max
:104, frame
:0, ink
:2, m
:0 }];
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
) {
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
; }
354 kLeft
= kLeft||kLeftDown
;
355 kRight
= kRight||kRightDown
;
357 kDown
= kDown||kDownDown
;
358 kJump
= kJump||kJumpDown
;
359 scope(exit
) kLeft
= kRight
= kJump
= kUp
= kDown
= false;
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();
372 void cheatRemoveKeys () { keys
.info
= null; }
374 // ////////////////////////////////////////////////////////////////////// //
378 ubyte getBlockAt (int x
, int y
, bool simplify
=false) const nothrow @nogc {
381 if (x
< 0 || y
< 0 || x
> 31 || y
> 15) return 0; // empty
382 ubyte b
= map
[y
*32+x
];
385 else if (b
> 7) b
= 4;
386 else if (b
== 6) b
= 5;
387 else if (b
== 2) b
= 1;
392 bool checkExit () const nothrow @nogc {
393 if (keys
.info
.length
!= 0) return false;
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;
404 if (pixelCheckMonster(m
.x
, m
.y
, gameData
["vrobo"], cc
[m
.anim
])) return true;
406 if (pixelCheckMonster(m
.x
&248, m
.y
, gameData
["hrobo"], cc
[((m
.x
&m
.anim
)&0xfe)+m
.d
/*ir*/])) return true;
412 if (pixelCheckMonster(eugene
.x
, eugene
.y
, gameData
["eugene"], 256*curLSet
)) return true;
416 if (pixelCheckMonster(kong
.x
, kong
.y
, gameData
["kong"], 256*kong
.frame
)) return true;
420 foreach (const ref sk
; skylab
) {
421 if (pixelCheckMonster(sk
.x
, sk
.y
, gameData
["sky"], 256*sk
.frame
)) return true;
427 void checkSwitch () nothrow @nogc {
428 foreach (ref ss
; switches
) {
429 if (ss
.s
/*tate*/ != 1) continue;
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
) {
440 if (x
< 0 || y
< 0) break;
441 if (x
+7 >= willyX
&& x
< willyX
+8 && y
+7 >= willyY
&& y
< willyY
+16) return true;
446 X11Image
draw (X11Image img
=null) {
447 if (img
is null) img
= new X11Image(8*Room
.Width
, 8*Room
.Height
);
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;
474 convCache
[conv
.frame
].blitTo(img
, conv
.x
, conv
.y
, conv
.l
);
479 if (this.keys
.info
.length
!= 0) {
482 br
= this.frameNo
&31;
483 if (br
> 15) br
= 31-br
;
486 exitCache
[br
].blitTo(img
, this.exit
.x
, this.exit
.y
);
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);
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
];
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
);
510 auto sidx
= ((x
&m
.anim
)&0xfe)+m
.d
/*ir*/;
511 if (sidx
< slist
.length
) {
512 slist
[sidx
].blitTo(img
, x
&248, m
.y
);
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) {
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
);
534 foreach (const ref ss
; this.switches
) {
535 if (ss
.s
== 0) continue;
536 switchesSpr
[ss
.s
-1].blitTo(img
, ss
.x
, ss
.y
);
541 foreach (const ref sp
; this.spg
) {
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));
556 auto willyPos
= (this.willyX
&15)>>1;
557 if (this.willyDir
< 0) willyPos
+= 8;
560 if (this.checkMonsters()) wy
= 16;
561 willySpr
.blitTo(willyPos
*16, wy
, 16, 16, img
, this.willyX
&248, this.willyY
);
563 willySpr
.blitTo(willyPos
*16, 0, 16, 16, img
, this.willyX
&248, this.willyY
);
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
;
589 if (rx
< -15 || rx
> 15 || ry
< -15 || ry
> 15) return false;
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) {
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
605 for (x
= 255; x
>= 0; --x
, ++wptr
, ++mp
) if (warr
[wptr
] && mpcGrid
[mp
]) return true;
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;
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;
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) {
643 bool spgCheckMonster (int x
, int y
) {
646 foreach (const ref cm
; enemies
) {
649 if (x
+7 >= mx
&& x
< mx
+15 && y
+7 >= my
&& y
< my
+15) return true;
656 int dir
= 0, idx
= 0;
660 spg
.assumeSafeAppend
;
663 void addXY (int x
, int y
) {
665 while (spg
.length
< idx
) spg
~= SPGRay(-1, -1);
671 ubyte blockhit
= map
[y
*32+x
];
672 bool robohit
= spgCheckMonster(x
, y
);
674 if (blockhit
&& robohit
) {
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;
685 } else if (!blockhit
&& !robohit
) {
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;
698 blockhit
= map
[y
*32+x
];
699 if (y
== 15 || blockhit
) done
= true;
702 blockhit
= map
[y
*32+x
];
703 if (x
== 0 || blockhit
) done
= true;
706 if (!dir
) { --x
; if (!x
) done
= true; }
707 else { ++y
; if (++y
== 15) done
= true; }
713 void stepMonsters () {
714 foreach (ref m
; enemies
) {
715 if (m
.x
< 0 || m
.y
< 0) continue;
718 auto spd
= m
.s
/*peed*/;
719 if (m
.d
/*ir*/ != 0) {
722 if (y
< m
.min || y
< 0) { y
+= spd
; m
.d
/*ir*/ = 0; }
726 if (y
> m
.max
) { y
-= spd
; m
.d
/*ir*/ = 1; }
729 m
.anim
= (m
.anim
+1)&3;
732 auto spd
= (2>>m
.s
/*peed*/);
733 if (m
.d
/*ir*/ != 0) {
736 if (x
< m
.min
) { m
.d
/*ir*/ = 0; x
+= spd
; }
740 if (x
> m
.max
+6) { m
.d
/*ir*/ = 1; x
-= spd
; }
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);
752 if (eugene
.d
/*ir*/ != 0) {
755 if (eugene
.y
< eugene
.min
) { eugene
.d
/*ir*/ = 0; ++eugene
.y
; }
759 if (eugene
.y
> eugene
.max
) { eugene
.d
/*ir*/ = 1; --eugene
.y
; }
765 switch (kong
.falling
) {
766 case 1: // just started
769 //eraseRect(16, 120, 16, 8);
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
;
783 if (!kong
.delay
) { kong
.delay
= 8; kong
.frame
= (kong
.frame
+1)&1; } else --kong
.delay
;
788 foreach (immutable idx
, ref sk
; skylab
) {
800 if (sk
.frame
== 7) sk
.m
= 2;
804 sk
.x
= skylabCoords
[idx
][sk
.p
].x
;
805 sk
.y
= skylabCoords
[idx
][sk
.p
].y
;
814 if (willyY
&7) return;
815 for (int f
= 0; f
<= 8; f
+= 8) {
818 ubyte b
= getBlockAt(x
, y
);
825 //eraseRect(x*8, y*8, 8, 8);
830 while (keys
.info
.length
) {
832 foreach (immutable idx
; 0..keys
.info
.length
) {
833 auto ki
= &keys
.info
[idx
];
837 if (kx
+7 >= willyX
&& kx
< willyX
+10 && ky
+7 >= willyY
&& ky
< willyY
+16) {
839 foreach (immutable c
; idx
+1..keys
.info
.length
) keys
.info
[c
-1] = keys
.info
[c
];
840 keys
.info
.length
-= 1;
851 if (holeLen
> 0 && switches
.length
&& switches
[0].s
/*tate*/ > 1) {
854 enemies
[1].max
+= 24;
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; }
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;
878 if (willyX
< 0) willyX
+= 240;
879 willyLastMoveDir
= -1;
883 bool doWillyRight () {
884 if (willyDir
< 0) { willyDir
= 1; return true; }
885 if (willyX
> 245) return false;
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;
892 if (willyX
> 240) willyX
-= 240;
893 willyLastMoveDir
= 1;
897 void doWillyJump () {
898 if (!willyJump
) return;
899 willyY
+= willyJ
[willyJump
];
902 if (willyJumpDir
< 0) mv
= doWillyLeft(); else if (willyJumpDir
> 0) mv
= doWillyRight();
906 auto b0
= getBlockAt(x
, willyY
);
907 auto b1
= getBlockAt(x
+8, willyY
);
908 if (b0
== 3 || b1
== 3) {
909 // headboom! (apstenu %-)
911 willyY
-= willyJ
[willyJump
];
912 willyJump
= 0; // enough flying
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);
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?
931 if (willyJump
> 18) willyJump
= 0;
935 if (willyDead
) return;
937 if (isWillyInDeadly()) { willyDead
= true; return; }
939 if (checkMonsters()) { willyDead
= true; return; }
941 if (checkMonsters()) singleStep
= singleStepWait
= true;
944 auto wasJump
= false;
946 willyLastMoveDir
= 0;
948 if (willyJump
) return;
952 auto falling
= isWillyFalling();
953 if (!kDown
&& falling
) {
954 willyConv
= willyStall
= willyLastMoveDir
= 0;
957 if (willyY
> 112) willyY
-= 112;
961 if (!falling
&& willyFall
> 34) willyDead
= true; // too high!
962 auto lfall
= willyFall
;
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
973 // Willy just steps on the conveyor, and Willy walking to the opposite side
975 if (wasJump
&& willyLastMoveDir
== -cdir
) {
976 willyConv
= dx
; // from jump, can do opposite
979 if (lfall
> 0 ||
!willyLastMoveDir
) { dx
= 0; willyStall
= 1; } // lands on conveyor, not from jump
982 // Willy was on conveyor
983 dx
= (willyStall ?
0 : willyConv
);
986 willyConv
= willyStall
= 0;
989 //if (willyConv) dx = willyConv;
990 if (kUp
&& !wasJump
) {
991 willyConv
= willyStall
= willyLastMoveDir
= 0;
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
];
1011 img
.setPixel(f
*16+x
, y
, Transparent
);
1013 img
.setPixel(f
*16+x
, y
, palcol(c
));
1015 img
.setPixel(f
*16+x
, y
+16, palcol(15));
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
);
1028 foreach (immutable f
; 0..8) {
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;
1042 foreach (immutable f
; 0..9) brickCache
~= buildBrickImage(this.crumb
, f
);
1045 void buildConvImages () {
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 () {
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 () {
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;
1077 foreach (immutable c
; 0..4) {
1078 auto n
= (m
.gfx
+c
)*256;
1080 r
~= buildImageMaskedInk(gameData
["vrobo"][n
..$], m
.ink
-1, 16, 16);
1083 foreach (immutable c
; 0..(m
.anim
>>1)+1) {
1084 auto n
= (m
.gfx
+c
)*256;
1086 r
~= buildImageMaskedInk(gameData
["hrobo"][n
..$], m
.ink
-1, 16, 16);
1089 r
~= buildImageMaskedInk(gameData
["hrobo"][n
..$], m
.ink
-1, 16, 16);
1092 monsterOfsCache
~= cc
;
1097 void buildEugeneImages () {
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 () {
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 () {
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 () {
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
);
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
..$];
1144 if (data
.length
< 1) throw new Exception("invalid level");
1145 ubyte res
= data
[0];
1151 if (data
.length
< 2) throw new Exception("invalid level");
1152 short res
= cast(short)(data
[0]|
(data
[1]<<8));
1161 foreach (immutable idx
, char ch
; title
[]) {
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
;
1173 platforms
.length
= 2;
1174 foreach (immutable idx
; 0..2) {
1183 wall
.paper
= readU8
;
1187 crumb
.paper
= readU8
;
1190 deadlies
.length
= 2;
1191 foreach (immutable idx
; 0..2) {
1199 conveyor
.ink
= readU8
;
1200 conveyor
.paper
= readU8
;
1201 conveyor
.gfx
= 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
;
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
;
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
) {
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
);
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
) {
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
);