conveyor graphics fix
[jetset.git] / willy.d
blob64b37a2317a25eb3842aa2eadf9b606aad0057d3
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/>.
19 module willy;
21 import x11gfx;
22 import engine;
25 // ////////////////////////////////////////////////////////////////////////// //
26 enum GameState {
27 Title,
28 Playing,
29 Dead,
30 Rocket,
31 Teleport,
32 Boat,
36 // ////////////////////////////////////////////////////////////////////////// //
37 static struct WillySavePos {
38 int x, y, dir;
39 int roomnum = -1;
41 @property bool valid () const pure { return (roomnum >= 0); }
45 // ////////////////////////////////////////////////////////////////////////// //
46 static struct Willy {
47 @disable this (this); // no copies, please
49 GameState gstate = GameState.Title;
51 WillySavePos saveposPrevRoom;
52 WillySavePos savepos;
54 int x, y;
55 int dir; // -1: left; 1: right
56 int mRoomnum;
57 int y_correction;
59 bool close_to_lift;
60 bool dead;
61 int height_fallen;
62 bool jumping;
63 int x_delta;
64 int y_delta;
65 int onwhat;
66 int stairtype;
68 int exitDir = -1; // exit direction
70 int toleft;
71 int toright;
72 int tojump;
74 // keys
75 bool goright;
76 bool goleft;
77 bool dojump;
79 bool ropeDontCatch;
80 int ropeCatchCounter;
81 bool ropeCatched;
82 int ropeCatchedPos;
84 int invulnTimer; // >0: Willy is invulnerable (only for monsters)
86 int rocket_cnt;
87 int foot_cnt;
89 enum {
90 ON_WALL = (1U<<0),
91 ON_STAIR = (1U<<1),
92 ON_MOVELEFT = (1U<<2),
93 ON_MOVERIGHT = (1U<<3),
97 int nb_automoves;
98 int x_automove;
99 int y_automove;
100 int dir_automove;
101 int automove_offset = 0;
104 void newLife () {
105 x_delta = 0;
106 y_delta = 9;
107 y_correction = 0;
108 stairtype = 0;
109 jumping = 0;
110 height_fallen = 0;
111 onwhat = 0;
112 dead = false;
113 exitDir = -1;
114 ropeDontCatch = false;
115 ropeCatched = false;
116 ropeCatchCounter = 0;
117 room.loadRoomGfx();
118 room.specInit(this);
119 room.resetMonsters();
120 invulnTimer = 10; // 0.5 seconds
121 toleft = toright = tojump = 0;
122 goright = goleft = dojump = false;
125 // debug function
126 void setXY (int ax, int ay) {
127 x_delta = 0;
128 y_delta = 9;
129 y_correction = 0;
130 stairtype = 0;
131 jumping = 0;
132 height_fallen = 0;
133 onwhat = 0;
134 dead = false;
135 exitDir = -1;
136 ropeDontCatch = false;
137 ropeCatched = false;
138 ropeCatchCounter = 0;
139 x = (ax/2)&0x7f;
140 y = (ay/8*8)&0x7f;
141 toleft = toright = tojump = 0;
142 goright = goleft = dojump = false;
143 //rocket_cnt = 0;
144 //foot_cnt = 0;
147 bool restoreSave (const ref WillySavePos svp) {
148 if (!svp.valid) return false;
149 x = svp.x;
150 y = svp.y;
151 dir = svp.dir;
152 mRoomnum = svp.roomnum;
153 newLife();
154 return true;
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) {
169 exitDir = -1;
170 if (ridx < 0 || ridx >= gameRooms.length) return;
171 if (gameRooms[ridx] is null) return;
172 mRoomnum = ridx;
173 room.loadRoomGfx();
174 room.specInit(this);
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;
180 room.loadRoomGfx();
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);
195 // pre-check
197 room.rope.forEachPoint(delegate (int pxnum, int x, int y) {
198 if (pxnum >= Rope.NBP) return 0;
199 if (!ropeCatched) {
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);
203 if (col) {
204 collided = true;
205 return 1; // stop
208 return 0;
212 drawSprite(lmp, ofs, scrx, scry, (collided ? 5 : 3));
213 return 0;
217 bool checkMonsterCollision () {
218 return
219 forSprite(delegate (const(ubyte)[] lmp, uint ofs, int scrx, int scry) {
220 return (checkSpriteCollision(lmp, ofs, scrx, scry) ? 1 : 0);
221 }) != 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 () {
234 if (dead) return;
236 if (exitDir >= 0) {
237 if (room.exits[exitDir] >= 0) {
238 switch (exitDir) {
239 case JSWRoom.Exit.Up:
240 y = 104;
241 if (close_to_lift || jumping) x_delta = 0;
242 goto endupdown;
243 case JSWRoom.Exit.Down:
244 y = 0;
245 if ((height_fallen -= 8) < 0) height_fallen = 0;
246 endupdown:
247 x += x_delta;
248 x_delta = 0;
249 jumping = 0;
250 y_delta = 9;
251 break;
252 case JSWRoom.Exit.Left:
253 x = 120;
254 break;
255 case JSWRoom.Exit.Right:
256 x = 3;
257 break;
258 default:
260 gotoRoom(room.exits[exitDir]);
262 y_correction = 0;
263 exitDir = -1;
267 void move () {
268 int checkLifts () {
269 int y_on_lift;
271 this.close_to_lift = false;
273 if (y_delta >= 0) {
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;
278 int liy = mon.y;
279 int d = (mon.dy == 1 ? 1 : -1);
280 if (mon.counter == 1) d = -d;
281 liy += 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) {
292 this.dead = true;
293 return 0;
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;
301 this.y_delta = 9;
302 this.onwhat = ON_WALL;
303 this.close_to_lift = true;
304 return 2;
307 // returns `true` if Willy catched the rope
308 bool doRope () {
309 if (!room.hasRope) return false;
311 if (ropeCatchCounter > 0) --ropeCatchCounter;
312 if (ropeDontCatch || ropeCatchCounter /*|| !ropeCatched*/) return false;
314 // pre-check
315 if (!ropeCatched) {
316 room.rope.forEachPoint(delegate (int pxnum, int x, int y) {
317 if (pxnum >= Rope.NBP) return 0;
318 if (!ropeCatched) {
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);
322 if (col) {
323 ropeCatchedPos = pxnum;
324 ropeCatched = true;
325 return 1; // stop
328 return 0;
331 if (!ropeCatched) return false;
334 // post-check
335 room.rope.forEachPoint(delegate (int pxnum, int rx, int ry) {
336 if (pxnum == 0) return 0;
337 --pxnum;
338 if (ropeCatched && pxnum == ropeCatchedPos) {
339 if (mRoomnum == 0x1b || mRoomnum == 0x12 || mRoomnum == 0x6f) {
340 if (ropeCatchedPos <= 0x0e) ropeCatchedPos = 0x0e;
342 jumping = false;
343 height_fallen = 0;
344 y_delta = 9;
345 onwhat = ON_WALL;
346 y = ry-8;
347 if (y < 0) exitDir = JSWRoom.Exit.Up; /* XXX exit */
348 x = rx/2-2;
349 return 1; // stop
351 return 0;
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;
360 jumping = true;
361 y_delta = -8;
363 y &= ~7;
364 ropeDontCatch = true;
365 ropeCatchCounter = 7;
366 ropeCatched = false;
367 return false;
370 int old_x_pos;
371 int old_y_pos;
372 int nb;
373 bool shunt;
374 bool maybe_save_pos = false;
375 int maybe_save_x;
376 int maybe_save_y;
377 int maybe_save_lastdir;
378 int maybe_save_room;
380 JSWRoom.Tile c, c1, c2;
382 if (dead) {
383 invulnTimer = 0;
384 return;
386 if (--invulnTimer < 0) invulnTimer = 0;
388 toleft = goleft;
389 toright = goright;
390 tojump = dojump;
393 if (special_room) {
394 switch (do_special ()) {
395 case 0:
396 break;
397 case 3: /* teleport */
398 return;
399 default:
400 printf ("bug\n");
401 exit (2);
406 switch (checkLifts()) {
407 case 0: break;
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
416 shunt = doRope();
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) {
425 x_delta = 0;
426 if (toleft) --x_delta;
427 if (toright) ++x_delta;
429 aftertestjump:
430 y_delta = -8;
431 jumping = 1;
432 y_correction = 0;
433 stairtype = 0;
437 old_y_pos = y;
438 y += (y_delta >> 1);
440 if (y < 0) {
441 exitDir = JSWRoom.Exit.Up;
442 return;
445 if (y_delta >= 0 && (old_y_pos & 7) == 0) {
446 stairtype = 0;
447 y_correction = 0;
448 onwhat = 0;
449 for (int i = 0; i <= 4; i += 4) {
450 c = room.tileAt(x+i, y+16);
451 switch (c) {
452 case JSWRoom.Tile.Crumb:
453 case JSWRoom.Tile.Wall:
454 onwhat |= ON_WALL;
455 maybe_save_pos = true;
456 maybe_save_x = x;
457 maybe_save_y = y&~7;
458 maybe_save_lastdir = dir;
459 maybe_save_room = mRoomnum;
460 break;
461 case JSWRoom.Tile.Deadly:
462 dead = true;
463 return;
464 case JSWRoom.Tile.StairRight:
465 case JSWRoom.Tile.StairLeft:
466 onwhat |= ON_STAIR;
467 stairtype = c;
468 break;
469 case JSWRoom.Tile.ConvLeft:
470 onwhat |= ON_MOVELEFT;
471 break;
472 case JSWRoom.Tile.ConvRight:
473 onwhat |= ON_MOVERIGHT;
474 break;
475 case JSWRoom.Tile.Item:
476 break;
477 default:
481 if (onwhat&(ON_MOVELEFT|ON_MOVERIGHT)) {
482 int ndir = (onwhat&ON_MOVELEFT ? -1 : 1);
483 if (x_delta == 0) {
484 if (ndir == -1) toleft = 1; else toright = 1;
485 } else {
486 if (ndir == dir || ((toleft|toright) == 0) || (ndir == -1 ? toleft : toright)) {
487 if (ndir == -1) {
488 toleft = 1;
489 toright = 0;
490 } else {
491 toleft = 0;
492 toright = 1;
498 if (onwhat == 0) {
499 if (y_delta == 9) {
500 ropeDontCatch = false;
501 x_delta = 0;
502 height_fallen += 8;
503 if (height_fallen >= 0x96) height_fallen = 0x82; /* XXX ??? */
505 } else {
506 if (height_fallen+(jumping ? 0x14 : 0) >= 0x28) {
507 dead = true;
508 return;
510 ropeDontCatch = false;
511 y = old_y_pos;
512 jumping = 0;
513 height_fallen = 0;
514 y_delta = 9;
518 if (!jumping && onwhat) {
519 changedelta:
520 x_delta = 0;
521 if (toleft) --x_delta;
522 if (toright) ++x_delta;
525 old_x_pos = x;
526 x += x_delta;
528 if (!close_to_lift) {
529 if (!jumping) {
530 c = room.tileAt(x, y+8);
531 if (c == JSWRoom.Tile.StairLeft && (x&3) == 3 && x_delta == -1) {
532 y -= 8;
533 stairtype = c;
534 onwhat = ON_WALL;
536 c = room.tileAt(x+4, y+8);
537 if (c == JSWRoom.Tile.StairRight && (x&3) == 0 && x_delta == 1) {
538 y -= 8;
539 stairtype = c;
540 onwhat = ON_WALL;
542 c = room.tileAt(x-4, y+16);
543 if (c == JSWRoom.Tile.StairLeft && (x&3) == 0 && x_delta == 1) {
544 y += 8;
545 stairtype = c;
546 onwhat = ON_WALL;
548 c = room.tileAt(x+8, y+16);
549 if (c == JSWRoom.Tile.StairRight && (x&3) == 3 && x_delta == -1) {
550 y += 8;
551 stairtype = c;
552 onwhat = ON_WALL;
555 // test if at top of stairs
556 if (stairtype == JSWRoom.Tile.StairRight && room.tileAt(x+4, y+16) != JSWRoom.Tile.StairRight) {
557 stairtype = 0;
558 } else if (stairtype == JSWRoom.Tile.StairLeft && room.tileAt(x, y+16) != JSWRoom.Tile.StairLeft) {
559 stairtype = 0;
562 switch (stairtype) {
563 case JSWRoom.Tile.StairLeft:
564 y_correction = 2*(x&3);
565 break;
566 case JSWRoom.Tile.StairRight:
567 y_correction = 2*(3-(x&3));
568 break;
569 default:
570 y_correction = 0;
571 break;
575 aftershunt:
576 nb = (y&7 ? 3 : 2);
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) {
581 dead = true;
582 return;
584 if (c == JSWRoom.Tile.Item) {
585 takeItem(x+i, y+8*j);
586 } else if (c == JSWRoom.Tile.Wall) {
587 x = old_x_pos;
588 j = nb;
589 break;
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) {
598 dead = true;
599 return;
601 if (c == JSWRoom.Tile.Item) {
602 takeItem(x+i, y);
603 } else if (c == JSWRoom.Tile.Wall) {
604 y = old_y_pos;
605 y_delta = 7;
606 break;
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;
625 return;
628 if (y+y_correction >= 0x6d) {
629 exitDir = JSWRoom.Exit.Down;
630 return;
633 /* if (!onwhat || jumping) { dosound; } */
635 if (x_delta != 0) dir = x_delta;
637 if (x == 0) {
638 exitDir = JSWRoom.Exit.Left;
639 return;
642 if (x == 0x7b) {
643 exitDir = JSWRoom.Exit.Right;
644 return;
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);
659 willy.newLife();