1 /* Reconstructed Commander Keen 4-6 Source Code
\r
2 * Copyright (C) 2021 K1n9_Duk3
\r
4 * This file is loosely based on:
\r
5 * Keen Dreams Source Code
\r
6 * Copyright (C) 2014 Javier M. Chavez
\r
8 * This program is free software; you can redistribute it and/or modify
\r
9 * it under the terms of the GNU General Public License as published by
\r
10 * the Free Software Foundation; either version 2 of the License, or
\r
11 * (at your option) any later version.
\r
13 * This program is distributed in the hope that it will be useful,
\r
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
16 * GNU General Public License for more details.
\r
18 * You should have received a copy of the GNU General Public License along
\r
19 * with this program; if not, write to the Free Software Foundation, Inc.,
\r
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
27 Contains the following actor types (in this order):
\r
29 - Score Box & Demo sprites
\r
32 - Neural Stunner Shots
\r
34 - Card Door Opener (Keen 5 only)
\r
40 Direction opposite[8] = {dir_South, dir_SouthWest, dir_West, dir_NorthWest, dir_North, dir_NorthEast, dir_East, dir_SouthEast};
\r
43 =============================================================================
\r
47 =============================================================================
\r
50 statetype s_score = { 0, 0, think, false, false, 0, 0, 0, NULL, NULL, NULL, NULL};
\r
51 statetype s_demo = {DEMOPLAQUESPR, DEMOPLAQUESPR, think, false, false, 0, 0, 0, NULL, NULL, NULL, NULL};
\r
54 ======================
\r
58 ======================
\r
61 void SpawnScore(void)
\r
63 scoreobj->obclass = inertobj;
\r
64 scoreobj->priority = 3;
\r
65 scoreobj->active = ac_allways;
\r
66 scoreobj->needtoclip = cl_noclip;
\r
67 scoreobj->temp2 = -1;
\r
68 scoreobj->temp1 = -1;
\r
69 scoreobj->temp3 = -1;
\r
70 scoreobj->temp4 = -1;
\r
71 if (scorescreenkludge)
\r
73 scoreobj->state = &sc_deadstate;
\r
77 NewState(scoreobj, &s_score);
\r
81 NewState(scoreobj, &s_demo);
\r
82 CA_MarkGrChunk(DEMOPLAQUESPR);
\r
87 // Taken from Keen Dreams: MemDrawChar and ShiftScore
\r
90 ======================
\r
94 ======================
\r
99 void MemDrawChar(Sint16 char8, Uint8 far *dest, Uint16 width, Uint16 planesize)
\r
101 Uint16 source = (Uint16)grsegs[STARTTILE8]; // Note: this differs from Keen Dreams source
\r
108 asm shl si,1 // index into char 8 segment
\r
110 asm mov ds,[WORD PTR source] // Note: this differs from Keen Dreams source
\r
111 asm mov es,[WORD PTR dest+2]
\r
113 asm mov cx,4 // draw four planes
\r
119 asm mov di,[WORD PTR dest]
\r
137 asm mov ax,[planesize]
\r
138 asm add [WORD PTR dest],ax
\r
146 #elif GRMODE == CGAGR
\r
148 void MemDrawChar (int char8,byte far *dest,unsigned width,unsigned planesize)
\r
154 asm shl si,1 // index into char 8 segment
\r
156 asm mov ds,[WORD PTR grsegs+STARTTILE8*2]
\r
157 asm mov es,[WORD PTR dest+2]
\r
162 asm mov di,[WORD PTR dest]
\r
183 planesize++; // shut the compiler up
\r
188 ====================
\r
192 ====================
\r
194 #if GRMODE == EGAGR
\r
195 void ShiftScore (void)
\r
197 spritetabletype far *spr;
\r
198 spritetype _seg *dest;
\r
200 spr = &spritetable[SCOREBOXSPR-STARTSPRITES];
\r
201 dest = (spritetype _seg *)grsegs[SCOREBOXSPR];
\r
203 CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0],
\r
204 dest->sourceoffset[1],spr->width,spr->height,2);
\r
206 CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0],
\r
207 dest->sourceoffset[2],spr->width,spr->height,4);
\r
209 CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0],
\r
210 dest->sourceoffset[3],spr->width,spr->height,6);
\r
222 void UpdateScore(objtype *ob)
\r
225 spritetype _seg *block;
\r
227 Uint16 i, length, width, planesize, number;
\r
230 if (scorescreenkludge)
\r
235 DrawDemoPlaque(ob);
\r
244 //code below is a combination of ScoreThink and ScoreReact from Keen Dreams with minor changes
\r
249 if ((gamestate.score>>16) != ob->temp1
\r
250 || (Uint16)gamestate.score != ob->temp2 )
\r
252 block = (spritetype _seg *)grsegs[SCOREBOXSPR];
\r
253 width = block->width[0];
\r
254 planesize = block->planesize[0];
\r
255 dest = (Uint8 far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]
\r
256 + planesize + width*4;
\r
258 ltoa (gamestate.score,str,10);
\r
260 // erase leading spaces
\r
261 length = strlen(str);
\r
262 for (i=9;i>length;i--)
\r
263 MemDrawChar (41,dest+=CHARWIDTH,width,planesize);
\r
268 MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);
\r
270 #if GRMODE == EGAGR
\r
273 ob->needtoreact = true;
\r
274 ob->temp1 = gamestate.score>>16;
\r
275 ob->temp2 = gamestate.score;
\r
283 number = gamestate.ammo;
\r
284 if (number != ob->temp3)
\r
286 block = (spritetype _seg *)grsegs[SCOREBOXSPR];
\r
287 width = block->width[0];
\r
288 planesize = block->planesize[0];
\r
289 dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]
\r
290 + planesize + width*20 + 7*CHARWIDTH;
\r
295 ltoa (number,str,10);
\r
297 // erase leading spaces
\r
298 length = strlen(str);
\r
299 for (i=2;i>length;i--)
\r
300 MemDrawChar (41,dest+=CHARWIDTH,width,planesize);
\r
305 MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);
\r
307 #if GRMODE == EGAGR
\r
310 ob->needtoreact = true;
\r
311 ob->temp3 = number;
\r
319 if (gamestate.lives != ob->temp4)
\r
321 block = (spritetype _seg *)grsegs[SCOREBOXSPR];
\r
322 width = block->width[0];
\r
323 planesize = block->planesize[0];
\r
324 dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]
\r
325 + planesize + width*20 + 2*CHARWIDTH;
\r
327 if (gamestate.lives > 99)
\r
330 ltoa (gamestate.lives,str,10);
\r
332 // erase leading spaces
\r
333 length = strlen(str);
\r
334 for (i=2;i>length;i--)
\r
335 MemDrawChar (41,dest+=CHARWIDTH,width,planesize);
\r
340 MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);
\r
342 #if GRMODE == EGAGR
\r
345 ob->needtoreact = true;
\r
346 ob->temp4 = gamestate.lives;
\r
355 It would be more efficient to use
\r
360 here instead of the individual ShiftScore() calls above. Because if the player
\r
361 gains a life by collecting points items, both the score and lives numbers need
\r
362 to be updated, which means the sprite would be shifted twice. And if the player
\r
363 fires a shot during the same frame, the ammo number also needs to be updated,
\r
364 leading to up to three shifts in one frame.
\r
367 if (ob->x != originxglobal || ob->y != originyglobal)
\r
369 ob->x = originxglobal;
\r
370 ob->y = originyglobal;
\r
375 #if GRMODE == EGAGR
\r
376 RF_PlaceSprite(&ob->sprite, ob->x+4*PIXGLOBAL, ob->y+4*PIXGLOBAL, SCOREBOXSPR, spritedraw, 3);
\r
377 #elif GRMODE == CGAGR
\r
378 RF_PlaceSprite(&ob->sprite, ob->x+8*PIXGLOBAL, ob->y+8*PIXGLOBAL, SCOREBOXSPR, spritedraw, 3);
\r
390 void DrawDemoPlaque(objtype *ob)
\r
392 if (ob->x != originxglobal || ob->y != originyglobal)
\r
394 ob->x = originxglobal;
\r
395 ob->y = originyglobal;
\r
396 RF_PlaceSprite(&ob->sprite, ob->x + 160*PIXGLOBAL - 32*PIXGLOBAL, ob->y + 8*PIXGLOBAL, DEMOPLAQUESPR, spritedraw, 3);
\r
402 =============================================================================
\r
406 player->temp1 = dir
\r
407 player->temp2 = animation stage
\r
409 =============================================================================
\r
413 statetype s_keenonfoot1 = {WOLRDKEENRIDE1SPR, WOLRDKEENRIDE1SPR, stepthink, false, false, 30, 0, 0, T_FootFly, NULL, R_Draw, &s_keenonfoot2};
\r
414 statetype s_keenonfoot2 = {WOLRDKEENRIDE2SPR, WOLRDKEENRIDE2SPR, stepthink, false, false, 30, 0, 0, T_FootFly, NULL, R_Draw, &s_keenonfoot1};
\r
415 statetype s_worldswim = {0, 0, slide, true, false, 6, 16, 16, T_KeenWorldSwim, NULL, R_Draw, &s_worldswim};
\r
419 statetype s_worldelevate = {-1, -1, think, true, false, 6, 16, 16, T_Elevate, NULL, R_Draw, NULL};
\r
422 statetype s_worldkeen = {0, 0, stepthink, false, false, 360, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave1};
\r
424 statetype s_worldkeenwave1 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave2};
\r
425 statetype s_worldkeenwave2 = {WORLDKEENWAVE2SPR, WORLDKEENWAVE2SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave3};
\r
426 statetype s_worldkeenwave3 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave4};
\r
427 statetype s_worldkeenwave4 = {WORLDKEENWAVE2SPR, WORLDKEENWAVE2SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave5};
\r
428 statetype s_worldkeenwave5 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorldWalk, NULL, R_Draw, &s_worldkeen};
\r
430 statetype s_worldkeenwalk = {0, 0, slide, true, false, 4, 24, 24, T_KeenWorldWalk, NULL, R_Draw, &s_worldkeenwalk};
\r
432 Sint16 worldshapes[8] = {WORLDKEENU1SPR-1, WORLDKEENUR1SPR-1, WORLDKEENR1SPR-1, WORLDKEENDR1SPR-1, WORLDKEEND1SPR-1, WORLDKEENDL1SPR-1, WORLDKEENL1SPR-1, WORLDKEENUL1SPR-1}; //-1 to everything because worldanims values are 1-based
\r
433 Sint16 worldanims[4] = {2, 3, 1, 3};
\r
435 Sint16 swimshapes[8] = {WORLDKEENSWIMU1SPR, WORLDKEENSWIMUR1SPR, WORLDKEENSWIMR1SPR, WORLDKEENSWIMDR1SPR, WORLDKEENSWIMD1SPR, WORLDKEENSWIMDL1SPR, WORLDKEENSWIML1SPR, WORLDKEENSWIMUL1SPR};
\r
438 Sint16 tiledir[4] = {dir_South, dir_West, dir_North, dir_East};
\r
442 ======================
\r
446 ======================
\r
449 void SpawnWorldKeen(Sint16 x, Sint16 y)
\r
452 if (playstate == ex_foot)
\r
454 player->needtoclip = cl_noclip;
\r
455 player->obclass = keenobj;
\r
456 player->x = gamestate.worldx;
\r
457 player->y = gamestate.worldy;
\r
458 player->active = ac_allways;
\r
459 player->priority = 3;
\r
462 if (gamestate.worldx < 20*TILEGLOBAL)
\r
464 player->temp1 = 280;
\r
465 player->xspeed = (30*TILEGLOBAL - player->x)/280 + 1;
\r
466 player->yspeed = (55*TILEGLOBAL - player->y)/280 + 1;
\r
470 player->temp1 = 140;
\r
471 player->xspeed = (Sint16)(16*TILEGLOBAL - player->x)/140 + 1;
\r
472 player->yspeed = (Sint16)(47*TILEGLOBAL - player->y)/140 + 1;
\r
474 NewState(player, &s_keenonfoot1);
\r
479 player->obclass = keenobj;
\r
480 if (gamestate.worldx == 0)
\r
482 player->x = CONVERT_TILE_TO_GLOBAL(x);
\r
483 player->y = CONVERT_TILE_TO_GLOBAL(y);
\r
487 player->x = gamestate.worldx;
\r
488 player->y = gamestate.worldy;
\r
490 player->active = ac_allways;
\r
491 player->priority = 1;
\r
494 player->temp1 = dir_West;
\r
497 player->shapenum = WORLDKEENL3SPR;
\r
498 NewState(player, &s_worldkeen);
\r
503 ======================
\r
505 = SpawnWorldKeenPort
\r
507 ======================
\r
510 void SpawnWorldKeenPort(Uint16 tileX, Uint16 tileY)
\r
512 player->obclass = keenobj;
\r
513 player->x = CONVERT_TILE_TO_GLOBAL(tileX);
\r
514 player->y = CONVERT_TILE_TO_GLOBAL(tileY);
\r
515 player->active = ac_allways;
\r
516 player->priority = 1;
\r
519 player->temp1 = dir_West;
\r
522 player->shapenum = WORLDKEENL3SPR;
\r
523 NewState(player, &s_worldkeen);
\r
529 ======================
\r
533 ======================
\r
536 void CheckEnterLevel(objtype *ob)
\r
540 for (y = ob->tiletop; y <= ob->tilebottom; y++)
\r
542 for (x = ob->tileleft; x <= ob->tileright; x++)
\r
544 info = *(mapsegs[2]+mapbwidthtable[y]/2 + x);
\r
545 if (info > 0xC000 && info <= (0xC000 + 18))
\r
547 gamestate.worldx = ob->x;
\r
548 gamestate.worldy = ob->y;
\r
549 gamestate.mapon = info - 0xC000;
\r
550 playstate = ex_completed;
\r
551 SD_PlaySound(SND_ENTERLEVEL);
\r
558 ======================
\r
562 ======================
\r
565 void T_KeenWorld(objtype *ob)
\r
567 if (c.dir != dir_None)
\r
569 ob->state = &s_worldkeenwalk;
\r
571 T_KeenWorldWalk(ob);
\r
573 if (jumpbutton || pogobutton || firebutton)
\r
575 CheckEnterLevel(ob);
\r
580 ======================
\r
584 ======================
\r
587 void T_KeenWorldWalk(objtype *ob)
\r
597 ob->xdir = c.xaxis;
\r
598 ob->ydir = c.yaxis;
\r
599 if (pogobutton || firebutton || jumpbutton)
\r
601 CheckEnterLevel(ob);
\r
603 if (c.dir == dir_None)
\r
605 ob->state = &s_worldkeen;
\r
606 ob->shapenum = worldshapes[ob->temp1] + 3;
\r
611 if (++ob->temp2 == 4)
\r
613 ob->shapenum = worldshapes[ob->temp1] + worldanims[ob->temp2];
\r
615 if (ob->temp2 == 1)
\r
617 SD_PlaySound(SND_WORLDWALK1);
\r
619 else if (ob->temp2 == 3)
\r
621 SD_PlaySound(SND_WORLDWALK2);
\r
627 ======================
\r
631 ======================
\r
634 void T_FootFly(objtype *ob)
\r
636 ob->temp1 = ob->temp1 - tics;
\r
637 xtry = ob->xspeed * tics;
\r
638 ytry = ob->yspeed * tics;
\r
639 if (ob->temp1 <= 0)
\r
641 xtry -= ob->xspeed * -ob->temp1;
\r
642 ytry -= ob->yspeed * -ob->temp1;
\r
644 ob->temp1 = dir_West;
\r
649 ob->state = &s_worldkeen;
\r
650 ob->shapenum = WORLDKEENL3SPR;
\r
651 ob->needtoclip = cl_midclip;
\r
656 ======================
\r
660 ======================
\r
663 void T_KeenWorldSwim(objtype *ob)
\r
673 ob->xdir = c.xaxis;
\r
674 ob->ydir = c.yaxis;
\r
675 if (c.xaxis || c.yaxis)
\r
678 ob->shapenum = swimshapes[ob->temp1] + ob->temp2;
\r
679 if (++ob->temp2 == 2)
\r
682 if (ob->temp2 == 0)
\r
684 SD_PlaySound(SND_SWIM1);
\r
688 SD_PlaySound(SND_SWIM2);
\r
692 #else // NOT Keen 4 (i.e. Keen 5 & 6):
\r
695 ======================
\r
699 ======================
\r
702 void Teleport(Uint16 tileX, Uint16 tileY)
\r
704 Uint16 tile, globalx, globaly, duration, move;
\r
706 objtype *ob = player;
\r
709 // enter the teleporter
\r
711 SD_PlaySound(SND_TELEPORT);
\r
712 globalx = CONVERT_TILE_TO_GLOBAL(tileX);
\r
713 globaly = CONVERT_TILE_TO_GLOBAL(tileY);
\r
716 // We need to make the compiler "forget" that duration starts at 0
\r
717 // to make sure the while-loop check is performed when entering the
\r
718 // loop. Can't change compiler settings since we do need that loop
\r
719 // optimization for the for-loop at the end of this routine.
\r
726 while (duration < 130)
\r
732 if (ob->x == globalx && ob->y == globaly)
\r
735 if (ob->y < globaly)
\r
738 if (ob->y > globaly)
\r
741 else if (ob->y > globaly)
\r
744 if (ob->y < globaly)
\r
748 if (ob->x < globalx)
\r
751 if (ob->x > globalx)
\r
754 else if (ob->x > globalx)
\r
757 if (ob->x < globalx)
\r
761 ob->shapenum = ((TimeCount >> 3) % 3) + WORLDKEENU1SPR;
\r
762 RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);
\r
764 tile = ((TimeCount >> 2) & TELEPORERTILEMASK) + TELEPORTERTILE1;
\r
765 RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);
\r
768 tile = TELEPORTERTILE2;
\r
769 RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);
\r
772 // teleport to new location
\r
774 tile = *(mapsegs[2]+mapbwidthtable[tileY]/2 + tileX);
\r
776 tileY = tile & 0x7F; // BUG? y coordinate is limited to 1..127
\r
777 ob->x = CONVERT_TILE_TO_GLOBAL(tileX);
\r
778 ob->y = CONVERT_TILE_TO_GLOBAL(tileY);
\r
781 ob->temp1 = dir_South;
\r
782 NewState(ob, ob->state);
\r
786 // draw flags/signs for new location
\r
788 for (o=player->next; o; o=o->next)
\r
790 if (!o->active && o->obclass == flagobj
\r
791 && o->tileright >= originxtile-1 && o->tileleft <= originxtilemax+1
\r
792 && o->tiletop <= originytilemax+1 && o->tilebottom >= originytile-1)
\r
794 o->needtoreact = true;
\r
795 o->active = ac_yes;
\r
796 RF_PlaceSprite(&o->sprite, o->x, o->y, o->shapenum, spritedraw, o->priority);
\r
799 UpdateScore(scoreobj);
\r
804 // leave teleporter
\r
806 SD_PlaySound(SND_TELEPORT);
\r
808 for (duration = 0; duration < 90; )
\r
812 ob->y += tics*2 + tics;
\r
814 ob->shapenum = ((TimeCount >> 3) % 3) + WORLDKEEND1SPR;
\r
815 RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);
\r
817 tile = ((TimeCount >> 2) & TELEPORERTILEMASK) + TELEPORTERTILE3;
\r
818 RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);
\r
821 tile = TELEPORTERTILE4;
\r
822 RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);
\r
830 ======================
\r
834 ======================
\r
837 void T_Elevate(objtype *ob)
\r
839 Sint16 i, x, y, tx, ty;
\r
840 Uint16 tiles[2][2];
\r
842 ytry = ob->ydir * 64 * tics;
\r
843 if (ob->x != ob->temp2)
\r
845 xtry = ob->xdir * 12 * tics;
\r
846 if ( (ob->xdir == 1 && ob->x + xtry > ob->temp2)
\r
847 || (ob->xdir == -1 && ob->x + xtry < ob->temp2) )
\r
849 xtry = ob->temp2 - ob->x;
\r
854 // Keen has no sprite in this state, so we need to update the hitbox manually
\r
855 // to avoid issues (the screen scrolling routines use left/right/top/bottom)
\r
857 ob->left = ob->x + xtry;
\r
858 ob->right = ob->left + (TILEGLOBAL-1);
\r
859 ob->top = ob->y + ytry;
\r
860 ob->bottom = ob->top + (TILEGLOBAL-1);
\r
864 if (ob->y + ytry < ob->temp1)
\r
869 if (ob->y + ytry > ob->temp1)
\r
874 // the invisible Keen has arrived at its destination
\r
886 ob->state = &s_worldkeen;
\r
887 ob->shapenum = WORLDKEEND3SPR;
\r
888 ob->needtoclip = cl_midclip;
\r
889 tx = CONVERT_GLOBAL_TO_TILE(ob->x);
\r
890 ty = CONVERT_GLOBAL_TO_TILE(ob->y);
\r
891 WorldScrollScreen(ob);
\r
892 UpdateScore(scoreobj);
\r
896 ob->y -= TILEGLOBAL;
\r
897 RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);
\r
900 // open the elevator door
\r
902 SD_PlaySound(SND_ELEVATORDOOR);
\r
903 for (i=0; i<=5; i++)
\r
905 for (y=0; y<2; y++)
\r
907 for (x=0; x<2; x++)
\r
909 tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + i*2 + x);
\r
912 RF_MemToMap(&tiles[0][0], 1, tx, ty-2, 2, 2);
\r
918 // make Keen walk out of the elevator
\r
920 for (y=0; y<32; y++)
\r
922 ob->y += 8; // move half a pixel every frame for 32 frames -> move down 16 pixels total
\r
923 ob->shapenum = (y / 4) % 3 + WORLDKEEND1SPR;
\r
924 RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);
\r
927 ob->needtoclip = cl_midclip; // redundant, but doesn't do any harm
\r
931 ======================
\r
935 ======================
\r
938 void Elevator(Uint16 tileX, Uint16 tileY, Sint16 dir)
\r
940 Uint16 info, globalx, globaly, duration, move;
\r
942 Uint16 tiles[2][2];
\r
943 objtype *ob = player;
\r
945 globalx = CONVERT_TILE_TO_GLOBAL(tileX);
\r
946 globaly = CONVERT_TILE_TO_GLOBAL(tileY);
\r
949 // make Keen walk into the elevator
\r
951 for (duration = 0; duration < 130; )
\r
954 WorldScrollScreen(ob);
\r
955 UpdateScore(scoreobj);
\r
961 if (ob->x == globalx && ob->y == globaly)
\r
964 if (ob->y < globaly)
\r
967 if (ob->y > globaly)
\r
970 else if (ob->y > globaly)
\r
973 if (ob->y < globaly)
\r
977 if (ob->x < globalx)
\r
980 if (ob->x > globalx)
\r
983 else if (ob->x > globalx)
\r
986 if (ob->x < globalx)
\r
990 ob->shapenum = ((duration / 8) % 3) + WORLDKEENU1SPR;
\r
991 RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);
\r
995 // close the elevator door
\r
997 SD_PlaySound(SND_ELEVATORDOOR);
\r
998 for (i=5; i >= 0; i--)
\r
1000 for (y=0; y<2; y++)
\r
1002 for (x=0; x<2; x++)
\r
1004 tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + i*2 + x);
\r
1007 RF_MemToMap(&tiles[0][0], 1, tileX+dir, tileY-1, 2, 2);
\r
1013 // make Keen invisible (and not clipping) and send him to the destination
\r
1015 RF_RemoveSprite(&ob->sprite);
\r
1016 info = *(mapsegs[2] + mapbwidthtable[tileY]/2 + tileX);
\r
1017 ob->temp2 = CONVERT_TILE_TO_GLOBAL(info >> 8);
\r
1018 ob->temp1 = CONVERT_TILE_TO_GLOBAL((info & 0x7F) + 1); // BUG? y coordinate is limited to 1..127
\r
1019 if (ob->temp1 < ob->y)
\r
1027 if (ob->temp2 < ob->x)
\r
1035 ob->needtoclip = cl_noclip;
\r
1036 ob->state = &s_worldelevate;
\r
1039 #endif //ifdef KEEN5
\r
1041 #endif //ifdef KEEN4 ... else ...
\r
1044 ======================
\r
1046 = CheckWorldInTiles
\r
1048 ======================
\r
1051 void CheckWorldInTiles(objtype *ob)
\r
1053 Uint16 tx, ty, intile;
\r
1058 tx = ob->tilemidx;
\r
1059 ty = CONVERT_GLOBAL_TO_TILE(ob->top + (ob->bottom-ob->top)/2);
\r
1060 intile = tinf[INTILE + *(mapsegs[1]+mapbwidthtable[ty]/2+tx)];
\r
1062 if (intile == INTILE_SHORESOUTH || intile == INTILE_SHORENORTH
\r
1063 || intile == INTILE_SHOREEAST || intile == INTILE_SHOREWEST)
\r
1065 if (!gamestate.wetsuit)
\r
1067 SD_PlaySound(SND_NOWAY);
\r
1069 RF_ForceRefresh();
\r
1070 xtry = -ob->xmove;
\r
1071 ytry = -ob->ymove;
\r
1072 ob->xdir = ob->ydir = 0;
\r
1077 ob->temp1 = tiledir[intile-INTILE_SHORESOUTH];
\r
1078 if (ob->state == &s_worldswim)
\r
1080 ob->temp1 = opposite[ob->temp1];
\r
1082 switch (ob->temp1)
\r
1103 if (ob->state == &s_worldswim)
\r
1105 ChangeState(ob, &s_worldkeenwalk);
\r
1109 ChangeState(ob, &s_worldswim);
\r
1113 #elif defined KEEN5
\r
1116 case INTILE_TELEPORT:
\r
1119 case INTILE_ELEVATORLEFT:
\r
1120 Elevator(tx, ty, 0);
\r
1122 case INTILE_ELEVATORRIGHT:
\r
1123 Elevator(tx, ty, -1);
\r
1126 #elif defined KEEN6
\r
1129 case INTILE_TELEPORT:
\r
1137 =============================================================================
\r
1141 temp1 = x destination for the thrown flag
\r
1142 temp2 = y destination for the thrown flag
\r
1143 temp3 = amount of time passed since flag was thrown (in tics)
\r
1145 =============================================================================
\r
1148 statetype s_flagwave1 = {FLAGFLAP1SPR, FLAGFLAP1SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave2};
\r
1149 statetype s_flagwave2 = {FLAGFLAP2SPR, FLAGFLAP2SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave3};
\r
1150 statetype s_flagwave3 = {FLAGFLAP3SPR, FLAGFLAP3SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave4};
\r
1151 statetype s_flagwave4 = {FLAGFLAP4SPR, FLAGFLAP4SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave1};
\r
1154 statetype s_throwflag0 = {FLAGFLIP1SPR, FLAGFLIP1SPR, think, false, false, 6, 0, 0, TossThink, NULL, R_Draw, &s_throwflag1};
\r
1155 statetype s_throwflag1 = {FLAGFLIP1SPR, FLAGFLIP1SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag2};
\r
1156 statetype s_throwflag2 = {FLAGFLIP2SPR, FLAGFLIP2SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag3};
\r
1157 statetype s_throwflag3 = {FLAGFLIP3SPR, FLAGFLIP3SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag4};
\r
1158 statetype s_throwflag4 = {FLAGFLIP4SPR, FLAGFLIP4SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag5};
\r
1159 statetype s_throwflag5 = {FLAGFALL1SPR, FLAGFALL1SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag6};
\r
1160 statetype s_throwflag6 = {FLAGFALL1SPR, FLAGFALL1SPR, stepthink, true, false, 1, 0, 0, FlagAlign, NULL, R_Draw, &s_flagwave1};
\r
1163 Sint16 flagx, flagy;
\r
1164 Point flagpath[30];
\r
1167 ======================
\r
1171 ======================
\r
1174 void SpawnFlag(Sint16 x, Sint16 y)
\r
1177 new->needtoclip = cl_noclip;
\r
1178 new->priority = 3;
\r
1179 new->obclass = flagobj;
\r
1180 new->active = ac_yes;
\r
1182 new->x = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL;
\r
1183 new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL;
\r
1184 #elif defined KEEN5
\r
1185 new->x = CONVERT_TILE_TO_GLOBAL(x) + -5*PIXGLOBAL;
\r
1186 new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL;
\r
1187 #elif defined KEEN6
\r
1188 new->x = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL; // useless!
\r
1189 new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL; // useless!
\r
1190 #if GRMODE == CGAGR
\r
1191 new->x = CONVERT_TILE_TO_GLOBAL(x) + 10*PIXGLOBAL;
\r
1193 new->x = CONVERT_TILE_TO_GLOBAL(x) + 14*PIXGLOBAL;
\r
1195 new->y = CONVERT_TILE_TO_GLOBAL(y) + -26*PIXGLOBAL;
\r
1197 Uint16 tile = *(mapsegs[1]+mapbwidthtable[y]/2 + x) + 1;
\r
1198 RF_MemToMap(&tile, 1, x, y, 1, 1);
\r
1201 new->ticcount = US_RndT() / 16;
\r
1202 NewState(new, &s_flagwave1);
\r
1207 ======================
\r
1211 ======================
\r
1214 void SpawnThrowFlag(Sint16 x, Sint16 y)
\r
1217 Sint32 xdist, ydist;
\r
1220 new->needtoclip = cl_noclip;
\r
1221 new->priority = 3;
\r
1222 new->obclass = flagobj;
\r
1223 new->active = ac_allways;
\r
1224 new->x = gamestate.worldx - 16*PIXGLOBAL;
\r
1225 new->y = gamestate.worldy - 16*PIXGLOBAL;
\r
1227 new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL;
\r
1228 new->temp2 = CONVERT_TILE_TO_GLOBAL(y) + -38*PIXGLOBAL;
\r
1229 #elif defined KEEN6
\r
1232 #if GRMODE == CGAGR
\r
1233 new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 10*PIXGLOBAL;
\r
1235 new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 14*PIXGLOBAL;
\r
1237 new->temp2 = CONVERT_TILE_TO_GLOBAL(y) + -34*PIXGLOBAL;
\r
1239 xdist = (Sint32)new->temp1 - (Sint32)new->x;
\r
1240 ydist = (Sint32)new->temp2 - (Sint32)new->y;
\r
1241 for (i = 0; i < 30; i++)
\r
1243 flagpath[i].x = new->x + (xdist * min(i, 24))/24;
\r
1244 flagpath[i].y = new->y + (ydist * i)/30;
\r
1247 flagpath[i].y -= i*3*PIXGLOBAL;
\r
1251 flagpath[i].y -= i*PIXGLOBAL + 20*PIXGLOBAL;
\r
1255 flagpath[i].y -= (20-i)*PIXGLOBAL + 30*PIXGLOBAL;
\r
1259 flagpath[i].y -= (29-i)*3*PIXGLOBAL;
\r
1262 NewState(new, &s_throwflag0);
\r
1266 ======================
\r
1270 ======================
\r
1273 void TossThink(objtype *ob)
\r
1279 SD_PlaySound(SND_FLAGSPIN);
\r
1280 ob->state = ob->state->nextstate;
\r
1284 ======================
\r
1288 ======================
\r
1291 void PathThink(objtype *ob)
\r
1293 ob->temp3 = ob->temp3 + tics;
\r
1294 if (ob->temp3 > 58)
\r
1297 ob->x = flagpath[ob->temp3/2].x;
\r
1298 ob->y = flagpath[ob->temp3/2].y;
\r
1299 ob->needtoreact = true;
\r
1300 if (ob->temp1 == 0)
\r
1302 SD_PlaySound(SND_FLAGSPIN);
\r
1307 ======================
\r
1311 ======================
\r
1314 void FlagAlign(objtype *ob)
\r
1316 ob->x = ob->temp1;
\r
1317 ob->y = ob->temp2 + 8*PIXGLOBAL;
\r
1318 SD_PlaySound(SND_FLAGLAND);
\r
1321 Uint16 tile = *(mapsegs[1]+mapbwidthtable[flagy]/2 + flagx) + 1;
\r
1322 RF_MemToMap(&tile, 1, flagx, flagy, 1, 1);
\r
1329 =============================================================================
\r
1333 =============================================================================
\r
1335 statetype s_stunray1 = {STUN1SPR, STUN1SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray2};
\r
1336 statetype s_stunray2 = {STUN2SPR, STUN2SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray3};
\r
1337 statetype s_stunray3 = {STUN3SPR, STUN3SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray4};
\r
1338 statetype s_stunray4 = {STUN4SPR, STUN4SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray1};
\r
1340 statetype s_stunhit = {STUNHIT1SPR, STUNHIT1SPR, step, false, false, 12, 0, 0, NULL, NULL, R_Draw, &s_stunhit2};
\r
1341 statetype s_stunhit2 = {STUNHIT2SPR, STUNHIT2SPR, step, false, false, 12, 0, 0, NULL, NULL, R_Draw, NULL};
\r
1344 ======================
\r
1348 ======================
\r
1351 void SpawnShot(Uint16 x, Uint16 y, Direction dir)
\r
1353 if (!gamestate.ammo)
\r
1355 SD_PlaySound(SND_USESWITCH);
\r
1363 new->priority = 0;
\r
1364 new->obclass = stunshotobj;
\r
1365 new->active = ac_allways;
\r
1366 SD_PlaySound(SND_KEENFIRE);
\r
1386 Quit("SpawnShot: Bad dir!");
\r
1389 NewState(new, &s_stunray1);
\r
1395 for (ob=player->next; ob; ob=ob->next)
\r
1398 && new->right > ob->left && new->left < ob->right
\r
1399 && new->top < ob->bottom && new->bottom > ob->top
\r
1400 && ob->state->contact)
\r
1402 ob->state->contact(ob, new);
\r
1411 ======================
\r
1415 ======================
\r
1418 void ExplodeShot(objtype *ob)
\r
1420 ob->obclass = inertobj;
\r
1421 ChangeState(ob, &s_stunhit);
\r
1422 SD_PlaySound(SND_SHOTEXPLODE);
\r
1426 ======================
\r
1430 ======================
\r
1433 void T_Shot(objtype *ob)
\r
1437 if (ob->tileright >= originxtile && ob->tilebottom >= originytile
\r
1438 && ob->tileleft <= originxtilemax && ob->tiletop <= originytilemax)
\r
1440 //object is visible, so do nothing
\r
1444 if (ob->tileright+10 < originxtile
\r
1445 || ob->tileleft-10 > originxtilemax
\r
1446 || ob->tilebottom+6 < originytile
\r
1447 || ob->tiletop-6 > originytilemax)
\r
1449 //shot is off-screen by more than half a screen, so remove it
\r
1454 //check for collisions with INACTIVE objects
\r
1455 for (ob2 = player->next; ob2; ob2 = ob2->next)
\r
1457 if (!ob2->active && ob->right > ob2->left && ob->left < ob2->right
\r
1458 && ob->top < ob2->bottom && ob->bottom > ob2->top)
\r
1460 if (ob2->state->contact)
\r
1462 ob2->state->contact(ob2, ob);
\r
1463 ob2->needtoreact = true;
\r
1464 ob2->active = ac_yes;
\r
1467 if (ob->obclass == nothing) //BUG: obclass is 'inertobj' for the exploded shot
\r
1474 ======================
\r
1478 ======================
\r
1481 void R_Shot(objtype *ob)
\r
1485 if (ob->hitnorth == 1 && ob->tileleft != ob->tileright)
\r
1487 tile = *(mapsegs[1]+mapbwidthtable[ob->tiletop-1]/2+ob->tileright);
\r
1488 if (tinf[NORTHWALL+tile] == 17)
\r
1490 ob->hitnorth = 17;
\r
1491 ob->x += 0x100 - (ob->x & 0xFF);
\r
1494 else if (ob->hitnorth == 17 && ob->tileleft != ob->tileright)
\r
1498 if (ob->hitsouth == 1 && ob->tileleft != ob->tileright)
\r
1500 tile = *(mapsegs[1]+mapbwidthtable[ob->tilebottom+1]/2+ob->tileright);
\r
1501 if (tinf[SOUTHWALL+tile] == 17)
\r
1503 ob->hitsouth = 17;
\r
1504 ob->x += 0x100 - (ob->x & 0xFF);
\r
1507 else if (ob->hitsouth == 17 && ob->tileleft != ob->tileright)
\r
1511 if (ob->hitnorth == 17 || ob->hitsouth == 17)
\r
1513 ytry = ob->state->ymove * tics * ob->ydir;
\r
1516 ob->bottom += ytry;
\r
1517 ob->tiletop = CONVERT_GLOBAL_TO_TILE(ob->top);
\r
1518 ob->tilebottom = CONVERT_GLOBAL_TO_TILE(ob->bottom);
\r
1520 else if (ob->hitnorth || ob->hitsouth || ob->hiteast || ob->hitwest)
\r
1524 RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);
\r
1528 =============================================================================
\r
1532 temp1 = height of the door's main section (identical tiles!), in tiles
\r
1533 DoorOpen changes two more tiles at the bottom end of the door
\r
1534 (total door height in tiles is temp1 + 2)
\r
1536 =============================================================================
\r
1539 statetype s_door1 = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, &s_door2};
\r
1540 statetype s_door2 = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, &s_door3};
\r
1541 statetype s_door3 = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, NULL};
\r
1544 ======================
\r
1548 ======================
\r
1551 void DoorOpen(objtype *ob)
\r
1557 map = mapsegs[1] + mapbwidthtable[ob->y]/2 + ob->x;
\r
1558 for (i=0; i < ob->temp1+2; i++, map+=mapwidth)
\r
1560 tiles[i] = *map + 1;
\r
1562 RF_MemToMap(tiles, 1, ob->x, ob->y, 1, ob->temp1+2);
\r
1567 =============================================================================
\r
1571 temp1 = frame counter
\r
1573 =============================================================================
\r
1575 statetype s_carddoor = {0, 0, step, false, false, 15, 0, 0, CardDoorOpen, NULL, NULL, &s_carddoor};
\r
1578 ======================
\r
1582 ======================
\r
1585 void CardDoorOpen(objtype *ob)
\r
1589 Uint16 tiles[16], *tileptr;
\r
1592 map = mapsegs[1] + mapbwidthtable[ob->y]/2 + ob->x;
\r
1593 for (y=0; y<4; y++, map+=mapwidth)
\r
1595 for (x=0; x<4; x++)
\r
1597 *tileptr++ = map[x]-4;
\r
1600 RF_MemToMap(tiles, 1, ob->x, ob->y, 4, 4);
\r
1602 if (++ob->temp1 == 3)
\r