1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
3 * Based on Jet-Set Willy, v1.0.1 by <Florent.Guillaume@ens.fr>
4 * Linux port and preliminary sound by jmd@dcs.ed.ac.uk
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 // ////////////////////////////////////////////////////////////////////////// //
36 // ////////////////////////////////////////////////////////////////////////// //
37 static struct WillySavePos
{
41 @property bool valid () const pure { return (roomnum
>= 0); }
45 // ////////////////////////////////////////////////////////////////////////// //
47 @disable this (this); // no copies, please
49 GameState gstate
= GameState
.Title
;
51 WillySavePos saveposPrevRoom
;
55 int dir
; // -1: left; 1: right
68 int exitDir
= -1; // exit direction
84 int invulnTimer
; // >0: Willy is invulnerable (only for monsters)
92 ON_MOVELEFT
= (1U<<2),
93 ON_MOVERIGHT
= (1U<<3),
101 int automove_offset = 0;
114 ropeDontCatch
= false;
116 ropeCatchCounter
= 0;
119 room
.resetMonsters();
120 invulnTimer
= 10; // 0.5 seconds
121 toleft
= toright
= tojump
= 0;
122 goright
= goleft
= dojump
= false;
126 void setXY (int ax
, int ay
) {
136 ropeDontCatch
= false;
138 ropeCatchCounter
= 0;
141 toleft
= toright
= tojump
= 0;
142 goright
= goleft
= dojump
= false;
147 bool restoreSave (const ref WillySavePos svp
) {
148 if (!svp
.valid
) return false;
152 mRoomnum
= svp
.roomnum
;
157 bool restoreSavePos () { return restoreSave(savepos
); }
158 bool restoreSavePosPrev () { return restoreSave(saveposPrevRoom
); }
160 WillySavePos
saveSave () {
161 if (savepos
.valid
) return savepos
;
162 return WillySavePos();
165 @property int roomnum () { return mRoomnum
; }
166 @property JSWRoom
room () { return gameRooms
[mRoomnum
]; }
168 void gotoRoom (int ridx
) {
170 if (ridx
< 0 || ridx
>= gameRooms
.length
) return;
171 if (gameRooms
[ridx
] is null) return;
175 room
.resetMonsters();
178 int forSprite (scope int delegate (const(ubyte)[] lmp
, uint ofs
, int scrx
, int scry
) dg
) {
179 if (dg
is null) return 0;
181 auto lmp
= room
.willyLmp
;
182 auto sprbase
= room
.willySprIdx
;
183 // nightmare room: backwards
184 //spridx = sprbase+(x&3)+4*(dir == (roomnum == 0x1c ? 1 : -1) ? 1 : 0);
185 auto spridx
= sprbase
+(x
&3)+4*(dir
== (room
.willyLump
== "sprmons" && sprbase
== 0x62 ?
1 : -1) ?
1 : 0);
186 auto scrx
= ((x
&~3)<<1);
187 auto scry
= y
+y_correction
;
188 return dg(lmp
, spridx
, scrx
, scry
);
191 void draw (bool debugShowCollision
=false) {
192 forSprite(delegate (const(ubyte)[] lmp
, uint ofs
, int scrx
, int scry
) {
193 bool collided
= (debugShowCollision ?
checkSpriteCollision(lmp
, ofs
, scrx
, scry
) : false);
197 room.rope.forEachPoint(delegate (int pxnum, int x, int y) {
198 if (pxnum >= Rope.NBP) return 0;
200 auto col = forSprite(delegate (const(ubyte)[] lmp, uint ofs, int scrx, int scry) {
201 return (checkSpritePixelCollision(lmp, ofs, scrx, scry, x, y) ? 1 : 0);
212 drawSprite(lmp
, ofs
, scrx
, scry
, (collided ?
5 : 3));
217 bool checkMonsterCollision () {
219 forSprite(delegate (const(ubyte)[] lmp
, uint ofs
, int scrx
, int scry
) {
220 return (checkSpriteCollision(lmp
, ofs
, scrx
, scry
) ?
1 : 0);
224 void delegate () onTakeItem
;
226 void takeItem (int x
, int y
) {
227 if (room
.takeAt(x
, y
)) {
228 if (onTakeItem
!is null) onTakeItem();
232 // ////////////////////////////////////////////////////////////////////// //
233 void processExit () {
237 if (room
.exits
[exitDir
] >= 0) {
239 case JSWRoom
.Exit
.Up
:
241 if (close_to_lift || jumping
) x_delta
= 0;
243 case JSWRoom
.Exit
.Down
:
245 if ((height_fallen
-= 8) < 0) height_fallen
= 0;
252 case JSWRoom
.Exit
.Left
:
255 case JSWRoom
.Exit
.Right
:
260 gotoRoom(room
.exits
[exitDir
]);
271 this.close_to_lift
= false;
274 foreach (ref mon
; this.room
.monsters
) {
275 if (!mon
.lift
) continue;
276 int dx
= this.x
-mon
.x
+4;
277 if (dx
< 0 || dx
>= 12) continue;
279 int d
= (mon
.dy
== 1 ?
1 : -1);
280 if (mon
.counter
== 1) d
= -d
;
282 int dy
= this.y
+this.y_correction
-liy
+21;
283 if (dy
< 0 || dy
>= 7) continue;
284 this.close_to_lift
= true;
285 y_on_lift
= liy
-0x10;
289 if (!this.close_to_lift
) return 0;
291 if (this.height_fallen
>= 48) {
295 this.close_to_lift
= false;
296 this.height_fallen
= 0;
297 if (this.tojump
) return 1;
298 this.y
= y_on_lift
&~7;
299 this.y_correction
= y_on_lift
&7;
300 this.jumping
= false;
302 this.onwhat
= ON_WALL
;
303 this.close_to_lift
= true;
307 // returns `true` if Willy catched the rope
309 if (!room
.hasRope
) return false;
311 if (ropeCatchCounter
> 0) --ropeCatchCounter
;
312 if (ropeDontCatch || ropeCatchCounter
/*|| !ropeCatched*/) return false;
316 room
.rope
.forEachPoint(delegate (int pxnum
, int x
, int y
) {
317 if (pxnum
>= Rope
.NBP
) return 0;
319 auto col
= forSprite(delegate (const(ubyte)[] lmp
, uint ofs
, int scrx
, int scry
) {
320 return (checkSpritePixelCollision(lmp
, ofs
, scrx
, scry
, x
, y
) ?
1 : 0);
323 ropeCatchedPos
= pxnum
;
331 if (!ropeCatched
) return false;
335 room
.rope
.forEachPoint(delegate (int pxnum
, int rx
, int ry
) {
336 if (pxnum
== 0) return 0;
338 if (ropeCatched
&& pxnum
== ropeCatchedPos
) {
339 if (mRoomnum
== 0x1b || mRoomnum
== 0x12 || mRoomnum
== 0x6f) {
340 if (ropeCatchedPos
<= 0x0e) ropeCatchedPos
= 0x0e;
347 if (y
< 0) exitDir
= JSWRoom
.Exit
.Up
; /* XXX exit */
354 int dpos
= (toright
!= 0 ?
1 : 0)-(toleft
!= 0 ?
1 : 0);
355 if (dpos
!= 0) x_delta
= dpos
;
356 if (room
.rope
.ropeDir
== room
.rope
.ropeSpeed
) dpos
= -dpos
;
357 ropeCatchedPos
-= dpos
;
358 if (ropeCatchedPos
!= Rope
.NBP
) {
359 if (!tojump
) return true;
364 ropeDontCatch
= true;
365 ropeCatchCounter
= 7;
374 bool maybe_save_pos
= false;
377 int maybe_save_lastdir
;
380 JSWRoom
.Tile c
, c1
, c2
;
386 if (--invulnTimer
< 0) invulnTimer
= 0;
394 switch (do_special ()) {
397 case 3: /* teleport */
406 switch (checkLifts()) {
408 case 1: goto aftertestjump
;
409 case 2: goto changedelta
;
410 default: assert(0, "wtf?!");
413 close_to_lift
= false;
415 // inside "move_player" because has to override some movements
417 if (exitDir
>= 0) return;
418 if (shunt
) goto aftershunt
;
420 if (tojump
&& !jumping
&& onwhat
) {
421 c1
= room
.tileAt(x
, y
+16);
422 c2
= room
.tileAt(x
+4, y
+16);
423 if (c1
!= 0 || c2
!= 0) {
424 if (c1
!= JSWRoom
.Tile
.ConvLeft
&& c1
!= JSWRoom
.Tile
.ConvRight
&& c2
!= JSWRoom
.Tile
.ConvLeft
&& c2
!= JSWRoom
.Tile
.ConvRight
) {
426 if (toleft
) --x_delta
;
427 if (toright
) ++x_delta
;
441 exitDir
= JSWRoom
.Exit
.Up
;
445 if (y_delta
>= 0 && (old_y_pos
& 7) == 0) {
449 for (int i
= 0; i
<= 4; i
+= 4) {
450 c
= room
.tileAt(x
+i
, y
+16);
452 case JSWRoom
.Tile
.Crumb
:
453 case JSWRoom
.Tile
.Wall
:
455 maybe_save_pos
= true;
458 maybe_save_lastdir
= dir
;
459 maybe_save_room
= mRoomnum
;
461 case JSWRoom
.Tile
.Deadly
:
464 case JSWRoom
.Tile
.StairRight
:
465 case JSWRoom
.Tile
.StairLeft
:
469 case JSWRoom
.Tile
.ConvLeft
:
470 onwhat |
= ON_MOVELEFT
;
472 case JSWRoom
.Tile
.ConvRight
:
473 onwhat |
= ON_MOVERIGHT
;
475 case JSWRoom
.Tile
.Item
:
481 if (onwhat
&(ON_MOVELEFT|ON_MOVERIGHT
)) {
482 int ndir
= (onwhat
&ON_MOVELEFT ?
-1 : 1);
484 if (ndir
== -1) toleft
= 1; else toright
= 1;
486 if (ndir
== dir ||
((toleft|toright
) == 0) ||
(ndir
== -1 ? toleft
: toright
)) {
500 ropeDontCatch
= false;
503 if (height_fallen
>= 0x96) height_fallen
= 0x82; /* XXX ??? */
506 if (height_fallen
+(jumping ?
0x14 : 0) >= 0x28) {
510 ropeDontCatch
= false;
518 if (!jumping
&& onwhat
) {
521 if (toleft
) --x_delta
;
522 if (toright
) ++x_delta
;
528 if (!close_to_lift
) {
530 c
= room
.tileAt(x
, y
+8);
531 if (c
== JSWRoom
.Tile
.StairLeft
&& (x
&3) == 3 && x_delta
== -1) {
536 c
= room
.tileAt(x
+4, y
+8);
537 if (c
== JSWRoom
.Tile
.StairRight
&& (x
&3) == 0 && x_delta
== 1) {
542 c
= room
.tileAt(x
-4, y
+16);
543 if (c
== JSWRoom
.Tile
.StairLeft
&& (x
&3) == 0 && x_delta
== 1) {
548 c
= room
.tileAt(x
+8, y
+16);
549 if (c
== JSWRoom
.Tile
.StairRight
&& (x
&3) == 3 && x_delta
== -1) {
555 // test if at top of stairs
556 if (stairtype
== JSWRoom
.Tile
.StairRight
&& room
.tileAt(x
+4, y
+16) != JSWRoom
.Tile
.StairRight
) {
558 } else if (stairtype
== JSWRoom
.Tile
.StairLeft
&& room
.tileAt(x
, y
+16) != JSWRoom
.Tile
.StairLeft
) {
563 case JSWRoom
.Tile
.StairLeft
:
564 y_correction
= 2*(x
&3);
566 case JSWRoom
.Tile
.StairRight
:
567 y_correction
= 2*(3-(x
&3));
577 for (int j
= 0; j
< nb
; ++j
) {
578 for (int i
= 0; i
<= 4; i
+= 4) {
579 c
= room
.tileAt(x
+i
, y
+8*j
);
580 if (c
== JSWRoom
.Tile
.Deadly
) {
584 if (c
== JSWRoom
.Tile
.Item
) {
585 takeItem(x
+i
, y
+8*j
);
586 } else if (c
== JSWRoom
.Tile
.Wall
) {
594 if (jumping
&& y_delta
< 0 && (old_y_pos
&7) == 0) {
595 for (int i
= 0; i
<= 4; i
+= 4) {
596 c
= room
.tileAt(x
+i
, y
);
597 if (c
== JSWRoom
.Tile
.Deadly
) {
601 if (c
== JSWRoom
.Tile
.Item
) {
603 } else if (c
== JSWRoom
.Tile
.Wall
) {
611 if (maybe_save_pos
) {
612 if (savepos
.roomnum
>= 0 && savepos
.roomnum
!= maybe_save_room
) {
613 saveposPrevRoom
= savepos
;
615 savepos
.x
= maybe_save_x
;
616 savepos
.y
= maybe_save_y
;
617 savepos
.dir
= maybe_save_lastdir
;
618 savepos
.roomnum
= maybe_save_room
;
621 if (++y_delta
== 10) y_delta
= 9;
623 if (y
+y_correction
< 0) {
624 exitDir
= JSWRoom
.Exit
.Up
;
628 if (y
+y_correction
>= 0x6d) {
629 exitDir
= JSWRoom
.Exit
.Down
;
633 /* if (!onwhat || jumping) { dosound; } */
635 if (x_delta
!= 0) dir
= x_delta
;
638 exitDir
= JSWRoom
.Exit
.Left
;
643 exitDir
= JSWRoom
.Exit
.Right
;
650 // ////////////////////////////////////////////////////////////////////////// //
651 void restartGame (ref Willy willy
) {
652 foreach (JSWRoom room
; gameRooms
) room
.resetRoom();
653 willy
.savepos
.roomnum
= -1;
654 willy
.saveposPrevRoom
.roomnum
= -1;
655 willy
.x
= willyStart
.x
/2;
656 willy
.y
= willyStart
.y
;
657 willy
.dir
= willyStart
.dir
;
658 willy
.gotoRoom(willyStart
.room
);