]> 4ch.mooo.com Git - 16.git/blobdiff - 16/keen456/KEEN4-6/CK_KEEN2.C
extrcted keen code remake
[16.git] / 16 / keen456 / KEEN4-6 / CK_KEEN2.C
diff --git a/16/keen456/KEEN4-6/CK_KEEN2.C b/16/keen456/KEEN4-6/CK_KEEN2.C
new file mode 100755 (executable)
index 0000000..e30e5d7
--- /dev/null
@@ -0,0 +1,1606 @@
+/* Reconstructed Commander Keen 4-6 Source Code\r
+ * Copyright (C) 2021 K1n9_Duk3\r
+ *\r
+ * This file is loosely based on:\r
+ * Keen Dreams Source Code\r
+ * Copyright (C) 2014 Javier M. Chavez\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License along\r
+ * with this program; if not, write to the Free Software Foundation, Inc.,\r
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+ */\r
+\r
+/*\r
+CK_KEEN2.C\r
+==========\r
+\r
+Contains the following actor types (in this order):\r
+\r
+- Score Box & Demo sprites\r
+- Keen (world map)\r
+- Flags (world map)\r
+- Neural Stunner Shots\r
+- Gem Door Opener\r
+- Card Door Opener (Keen 5 only)\r
+\r
+*/\r
+\r
+#include "CK_DEF.H"\r
+\r
+Direction opposite[8] = {dir_South, dir_SouthWest, dir_West, dir_NorthWest, dir_North, dir_NorthEast, dir_East, dir_SouthEast};\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                SCORE BOX ROUTINES\r
+\r
+=============================================================================\r
+*/\r
+\r
+statetype s_score         = {  0,   0, think, false, false, 0, 0, 0, NULL, NULL, NULL, NULL};\r
+statetype s_demo          = {DEMOPLAQUESPR, DEMOPLAQUESPR, think, false, false, 0, 0, 0, NULL, NULL, NULL, NULL};\r
+\r
+/*\r
+======================\r
+=\r
+= SpawnScore\r
+=\r
+======================\r
+*/\r
+\r
+void SpawnScore(void)\r
+{\r
+       scoreobj->obclass = inertobj;\r
+       scoreobj->priority = 3;\r
+       scoreobj->active = ac_allways;\r
+       scoreobj->needtoclip = cl_noclip;\r
+       scoreobj->temp2 = -1;\r
+       scoreobj->temp1 = -1;\r
+       scoreobj->temp3 = -1;\r
+       scoreobj->temp4 = -1;\r
+       if (scorescreenkludge)\r
+       {\r
+               scoreobj->state = &sc_deadstate;\r
+       }\r
+       else if (!DemoMode)\r
+       {\r
+               NewState(scoreobj, &s_score);\r
+       }\r
+       else\r
+       {\r
+               NewState(scoreobj, &s_demo);\r
+               CA_MarkGrChunk(DEMOPLAQUESPR);\r
+       }\r
+}\r
+\r
+\r
+// Taken from Keen Dreams: MemDrawChar and ShiftScore\r
+\r
+/*\r
+======================\r
+=\r
+= MemDrawChar\r
+=\r
+======================\r
+*/\r
+\r
+#if GRMODE == EGAGR\r
+\r
+void MemDrawChar(Sint16 char8, Uint8 far *dest, Uint16 width, Uint16 planesize)\r
+{\r
+       Uint16 source = (Uint16)grsegs[STARTTILE8];     // Note: this differs from Keen Dreams source\r
+\r
+asm    mov     si,[char8]\r
+asm    shl     si,1\r
+asm    shl     si,1\r
+asm    shl     si,1\r
+asm    shl     si,1\r
+asm    shl     si,1            // index into char 8 segment\r
+\r
+asm    mov     ds,[WORD PTR source]    // Note: this differs from Keen Dreams source\r
+asm    mov     es,[WORD PTR dest+2]\r
+\r
+asm    mov     cx,4            // draw four planes\r
+asm    mov     bx,[width]\r
+asm    dec     bx\r
+\r
+planeloop:\r
+\r
+asm    mov     di,[WORD PTR dest]\r
+\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+asm    add     di,bx\r
+asm    movsb\r
+\r
+asm    mov     ax,[planesize]\r
+asm    add     [WORD PTR dest],ax\r
+\r
+asm    loop    planeloop\r
+\r
+asm    mov     ax,ss\r
+asm    mov     ds,ax\r
+}\r
+\r
+#elif GRMODE == CGAGR\r
+\r
+void MemDrawChar (int char8,byte far *dest,unsigned width,unsigned planesize)\r
+{\r
+asm    mov     si,[char8]\r
+asm    shl     si,1\r
+asm    shl     si,1\r
+asm    shl     si,1\r
+asm    shl     si,1            // index into char 8 segment\r
+\r
+asm    mov     ds,[WORD PTR grsegs+STARTTILE8*2]\r
+asm    mov     es,[WORD PTR dest+2]\r
+\r
+asm    mov     bx,[width]\r
+asm    sub     bx,2\r
+\r
+asm    mov     di,[WORD PTR dest]\r
+\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+asm    add     di,bx\r
+asm    movsw\r
+\r
+asm    mov     ax,ss\r
+asm    mov     ds,ax\r
+\r
+       planesize++;            // shut the compiler up\r
+}\r
+#endif\r
+\r
+/*\r
+====================\r
+=\r
+= ShiftScore\r
+=\r
+====================\r
+*/\r
+#if GRMODE == EGAGR\r
+void ShiftScore (void)\r
+{\r
+       spritetabletype far *spr;\r
+       spritetype _seg *dest;\r
+\r
+       spr = &spritetable[SCOREBOXSPR-STARTSPRITES];\r
+       dest = (spritetype _seg *)grsegs[SCOREBOXSPR];\r
+\r
+       CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0],\r
+               dest->sourceoffset[1],spr->width,spr->height,2);\r
+\r
+       CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0],\r
+               dest->sourceoffset[2],spr->width,spr->height,4);\r
+\r
+       CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0],\r
+               dest->sourceoffset[3],spr->width,spr->height,6);\r
+}\r
+#endif\r
+\r
+/*\r
+===============\r
+=\r
+= UpdateScore\r
+=\r
+===============\r
+*/\r
+\r
+void UpdateScore(objtype *ob)\r
+{\r
+       char            str[10],*ch;\r
+       spritetype      _seg    *block;\r
+       Uint8           far *dest;\r
+       Uint16  i, length, width, planesize, number;\r
+       boolean changed;\r
+\r
+       if (scorescreenkludge)\r
+               return;\r
+\r
+       if (DemoMode)\r
+       {\r
+               DrawDemoPlaque(ob);\r
+               return;\r
+       }\r
+\r
+       if (!showscorebox)\r
+               return;\r
+\r
+       changed = false;\r
+\r
+//code below is a combination of ScoreThink and ScoreReact from Keen Dreams with minor changes\r
+\r
+//\r
+// score changed\r
+//\r
+       if ((gamestate.score>>16) != ob->temp1\r
+               || (Uint16)gamestate.score != ob->temp2 )\r
+       {\r
+               block = (spritetype _seg *)grsegs[SCOREBOXSPR];\r
+               width = block->width[0];\r
+               planesize = block->planesize[0];\r
+               dest = (Uint8 far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]\r
+                       + planesize + width*4;\r
+\r
+               ltoa (gamestate.score,str,10);\r
+\r
+               // erase leading spaces\r
+               length = strlen(str);\r
+               for (i=9;i>length;i--)\r
+                       MemDrawChar (41,dest+=CHARWIDTH,width,planesize);\r
+\r
+               // draw digits\r
+               ch = str;\r
+               while (*ch)\r
+                       MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);\r
+\r
+#if GRMODE == EGAGR\r
+               ShiftScore ();\r
+#endif\r
+               ob->needtoreact = true;\r
+               ob->temp1 = gamestate.score>>16;\r
+               ob->temp2 = gamestate.score;\r
+\r
+               changed = true;\r
+       }\r
+\r
+//\r
+// ammo changed\r
+//\r
+       number = gamestate.ammo;\r
+       if (number != ob->temp3)\r
+       {\r
+               block = (spritetype _seg *)grsegs[SCOREBOXSPR];\r
+               width = block->width[0];\r
+               planesize = block->planesize[0];\r
+               dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]\r
+                       + planesize + width*20 + 7*CHARWIDTH;\r
+\r
+               if (number > 99)\r
+                       strcpy (str,"99");\r
+               else\r
+                       ltoa (number,str,10);\r
+\r
+               // erase leading spaces\r
+               length = strlen(str);\r
+               for (i=2;i>length;i--)\r
+                       MemDrawChar (41,dest+=CHARWIDTH,width,planesize);\r
+\r
+               // draw digits\r
+               ch = str;\r
+               while (*ch)\r
+                       MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);\r
+\r
+#if GRMODE == EGAGR\r
+               ShiftScore ();\r
+#endif\r
+               ob->needtoreact = true;\r
+               ob->temp3 = number;\r
+\r
+               changed = true;\r
+       }\r
+\r
+//\r
+// lives changed\r
+//\r
+       if (gamestate.lives != ob->temp4)\r
+       {\r
+               block = (spritetype _seg *)grsegs[SCOREBOXSPR];\r
+               width = block->width[0];\r
+               planesize = block->planesize[0];\r
+               dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]\r
+                       + planesize + width*20 + 2*CHARWIDTH;\r
+\r
+               if (gamestate.lives > 99)\r
+                       strcpy (str,"99");\r
+               else\r
+                       ltoa (gamestate.lives,str,10);\r
+\r
+               // erase leading spaces\r
+               length = strlen(str);\r
+               for (i=2;i>length;i--)\r
+                       MemDrawChar (41,dest+=CHARWIDTH,width,planesize);\r
+\r
+               // draw digits\r
+               ch = str;\r
+               while (*ch)\r
+                       MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);\r
+\r
+#if GRMODE == EGAGR\r
+               ShiftScore ();\r
+#endif\r
+               ob->needtoreact = true;\r
+               ob->temp4 = gamestate.lives;\r
+\r
+               changed = true;\r
+       }\r
+\r
+/*\r
+Note:\r
+-----\r
+\r
+It would be more efficient to use\r
+\r
+       if (changed)\r
+               ShiftScore();\r
+\r
+here instead of the individual ShiftScore() calls above. Because if the player\r
+gains a life by collecting points items, both the score and lives numbers need\r
+to be updated, which means the sprite would be shifted twice. And if the player\r
+fires a shot during the same frame, the ammo number also needs to be updated,\r
+leading to up to three shifts in one frame.\r
+*/\r
+\r
+       if (ob->x != originxglobal || ob->y != originyglobal)\r
+       {\r
+               ob->x = originxglobal;\r
+               ob->y = originyglobal;\r
+               changed = true;\r
+       }\r
+\r
+       if (changed)\r
+#if GRMODE == EGAGR\r
+               RF_PlaceSprite(&ob->sprite, ob->x+4*PIXGLOBAL, ob->y+4*PIXGLOBAL, SCOREBOXSPR, spritedraw, 3);\r
+#elif GRMODE == CGAGR\r
+               RF_PlaceSprite(&ob->sprite, ob->x+8*PIXGLOBAL, ob->y+8*PIXGLOBAL, SCOREBOXSPR, spritedraw, 3);\r
+#endif\r
+}\r
+\r
+/*\r
+===============\r
+=\r
+= DrawDemoPlaque\r
+=\r
+===============\r
+*/\r
+\r
+void DrawDemoPlaque(objtype *ob)\r
+{\r
+       if (ob->x != originxglobal || ob->y != originyglobal)\r
+       {\r
+               ob->x = originxglobal;\r
+               ob->y = originyglobal;\r
+               RF_PlaceSprite(&ob->sprite, ob->x + 160*PIXGLOBAL - 32*PIXGLOBAL, ob->y + 8*PIXGLOBAL, DEMOPLAQUESPR, spritedraw, 3);\r
+       }\r
+}\r
+\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                          MINI KEEN\r
+\r
+player->temp1 = dir\r
+player->temp2 = animation stage\r
+\r
+=============================================================================\r
+*/\r
+\r
+#ifdef KEEN4\r
+statetype s_keenonfoot1 = {WOLRDKEENRIDE1SPR, WOLRDKEENRIDE1SPR, stepthink, false, false, 30, 0, 0, T_FootFly, NULL, R_Draw, &s_keenonfoot2};\r
+statetype s_keenonfoot2 = {WOLRDKEENRIDE2SPR, WOLRDKEENRIDE2SPR, stepthink, false, false, 30, 0, 0, T_FootFly, NULL, R_Draw, &s_keenonfoot1};\r
+statetype s_worldswim = {0, 0, slide, true, false, 6, 16, 16, T_KeenWorldSwim, NULL, R_Draw, &s_worldswim};\r
+#endif\r
+\r
+#ifdef KEEN5\r
+statetype s_worldelevate = {-1, -1, think, true, false, 6, 16, 16, T_Elevate, NULL, R_Draw, NULL};\r
+#endif\r
+\r
+statetype s_worldkeen     = {0, 0, stepthink, false, false, 360, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave1};\r
+\r
+statetype s_worldkeenwave1 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave2};\r
+statetype s_worldkeenwave2 = {WORLDKEENWAVE2SPR, WORLDKEENWAVE2SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave3};\r
+statetype s_worldkeenwave3 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave4};\r
+statetype s_worldkeenwave4 = {WORLDKEENWAVE2SPR, WORLDKEENWAVE2SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave5};\r
+statetype s_worldkeenwave5 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorldWalk, NULL, R_Draw, &s_worldkeen};\r
+\r
+statetype s_worldkeenwalk = {0, 0, slide, true, false, 4, 24, 24, T_KeenWorldWalk, NULL, R_Draw, &s_worldkeenwalk};\r
+\r
+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
+Sint16 worldanims[4] = {2, 3, 1, 3};\r
+#ifdef KEEN4\r
+Sint16 swimshapes[8] = {WORLDKEENSWIMU1SPR, WORLDKEENSWIMUR1SPR, WORLDKEENSWIMR1SPR, WORLDKEENSWIMDR1SPR, WORLDKEENSWIMD1SPR, WORLDKEENSWIMDL1SPR, WORLDKEENSWIML1SPR, WORLDKEENSWIMUL1SPR};\r
+#endif\r
+#ifndef KEEN6\r
+Sint16 tiledir[4] = {dir_South, dir_West, dir_North, dir_East};\r
+#endif\r
+\r
+/*\r
+======================\r
+=\r
+= SpawnWorldKeen\r
+=\r
+======================\r
+*/\r
+\r
+void SpawnWorldKeen(Sint16 x, Sint16 y)\r
+{\r
+#ifdef KEEN4\r
+       if (playstate == ex_foot)\r
+       {\r
+               player->needtoclip = cl_noclip;\r
+               player->obclass = keenobj;\r
+               player->x = gamestate.worldx;\r
+               player->y = gamestate.worldy;\r
+               player->active = ac_allways;\r
+               player->priority = 3;\r
+               player->xdir = 0;\r
+               player->ydir = 0;\r
+               if (gamestate.worldx < 20*TILEGLOBAL)\r
+               {\r
+                       player->temp1 = 280;\r
+                       player->xspeed = (30*TILEGLOBAL - player->x)/280 + 1;\r
+                       player->yspeed = (55*TILEGLOBAL - player->y)/280 + 1;\r
+               }\r
+               else\r
+               {\r
+                       player->temp1 = 140;\r
+                       player->xspeed = (Sint16)(16*TILEGLOBAL - player->x)/140 + 1;\r
+                       player->yspeed = (Sint16)(47*TILEGLOBAL - player->y)/140 + 1;\r
+               }\r
+               NewState(player, &s_keenonfoot1);\r
+               return;\r
+       }\r
+#endif\r
+\r
+       player->obclass = keenobj;\r
+       if (gamestate.worldx == 0)\r
+       {\r
+               player->x = CONVERT_TILE_TO_GLOBAL(x);\r
+               player->y = CONVERT_TILE_TO_GLOBAL(y);\r
+       }\r
+       else\r
+       {\r
+               player->x = gamestate.worldx;\r
+               player->y = gamestate.worldy;\r
+       }\r
+       player->active = ac_allways;\r
+       player->priority = 1;\r
+       player->xdir = 0;\r
+       player->ydir = 0;\r
+       player->temp1 = dir_West;\r
+       player->temp2 = 3;\r
+       player->temp3 = 0;\r
+       player->shapenum = WORLDKEENL3SPR;\r
+       NewState(player, &s_worldkeen);\r
+}\r
+\r
+#ifdef KEEN5\r
+/*\r
+======================\r
+=\r
+= SpawnWorldKeenPort\r
+=\r
+======================\r
+*/\r
+\r
+void SpawnWorldKeenPort(Uint16 tileX, Uint16 tileY)\r
+{\r
+       player->obclass = keenobj;\r
+       player->x = CONVERT_TILE_TO_GLOBAL(tileX);\r
+       player->y = CONVERT_TILE_TO_GLOBAL(tileY);\r
+       player->active = ac_allways;\r
+       player->priority = 1;\r
+       player->xdir = 0;\r
+       player->ydir = 0;\r
+       player->temp1 = dir_West;\r
+       player->temp2 = 3;\r
+       player->temp3 = 0;\r
+       player->shapenum = WORLDKEENL3SPR;\r
+       NewState(player, &s_worldkeen);\r
+}\r
+#endif\r
+\r
+\r
+/*\r
+======================\r
+=\r
+= CheckEnterLevel\r
+=\r
+======================\r
+*/\r
+\r
+void CheckEnterLevel(objtype *ob)\r
+{\r
+       Uint16 x, y, info;\r
+\r
+       for (y = ob->tiletop; y <= ob->tilebottom; y++)\r
+       {\r
+               for (x = ob->tileleft; x <= ob->tileright; x++)\r
+               {\r
+                       info = *(mapsegs[2]+mapbwidthtable[y]/2 + x);\r
+                       if (info > 0xC000 && info <= (0xC000 + 18))\r
+                       {\r
+                               gamestate.worldx = ob->x;\r
+                               gamestate.worldy = ob->y;\r
+                               gamestate.mapon = info - 0xC000;\r
+                               playstate = ex_completed;\r
+                               SD_PlaySound(SND_ENTERLEVEL);\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= T_KeenWorld\r
+=\r
+======================\r
+*/\r
+\r
+void T_KeenWorld(objtype *ob)\r
+{\r
+       if (c.dir != dir_None)\r
+       {\r
+               ob->state = &s_worldkeenwalk;\r
+               ob->temp2 = 0;\r
+               T_KeenWorldWalk(ob);\r
+       }\r
+       if (jumpbutton || pogobutton || firebutton)\r
+       {\r
+               CheckEnterLevel(ob);\r
+       }\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= T_KeenWorldWalk\r
+=\r
+======================\r
+*/\r
+\r
+void T_KeenWorldWalk(objtype *ob)\r
+{\r
+       if (ob->temp3)\r
+       {\r
+               ob->temp3 -= 4;\r
+               if (ob->temp3 < 0)\r
+                       ob->temp3 = 0;\r
+       }\r
+       else\r
+       {\r
+               ob->xdir = c.xaxis;\r
+               ob->ydir = c.yaxis;\r
+               if (pogobutton || firebutton || jumpbutton)\r
+               {\r
+                       CheckEnterLevel(ob);\r
+               }\r
+               if (c.dir == dir_None)\r
+               {\r
+                       ob->state = &s_worldkeen;\r
+                       ob->shapenum = worldshapes[ob->temp1] + 3;\r
+                       return;\r
+               }\r
+               ob->temp1 = c.dir;\r
+       }\r
+       if (++ob->temp2 == 4)\r
+               ob->temp2 = 0;\r
+       ob->shapenum = worldshapes[ob->temp1] + worldanims[ob->temp2];\r
+\r
+       if (ob->temp2 == 1)\r
+       {\r
+               SD_PlaySound(SND_WORLDWALK1);\r
+       }\r
+       else if (ob->temp2 == 3)\r
+       {\r
+               SD_PlaySound(SND_WORLDWALK2);\r
+       }\r
+}\r
+\r
+#ifdef KEEN4\r
+/*\r
+======================\r
+=\r
+= T_FootFly\r
+=\r
+======================\r
+*/\r
+\r
+void T_FootFly(objtype *ob)\r
+{\r
+       ob->temp1 = ob->temp1 - tics;\r
+       xtry = ob->xspeed * tics;\r
+       ytry = ob->yspeed * tics;\r
+       if (ob->temp1 <= 0)\r
+       {\r
+               xtry -= ob->xspeed * -ob->temp1;\r
+               ytry -= ob->yspeed * -ob->temp1;\r
+               ob->priority = 1;\r
+               ob->temp1 = dir_West;\r
+               ob->temp2 = 3;\r
+               ob->temp3 = 0;\r
+               player->xdir = 0;\r
+               player->ydir = 0;\r
+               ob->state = &s_worldkeen;\r
+               ob->shapenum = WORLDKEENL3SPR;\r
+               ob->needtoclip = cl_midclip;\r
+       }\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= T_KeenWorldSwim\r
+=\r
+======================\r
+*/\r
+\r
+void T_KeenWorldSwim(objtype *ob)\r
+{\r
+       if (ob->temp3)\r
+       {\r
+               ob->temp3 -= 6;\r
+               if (ob->temp3 < 0)\r
+                       ob->temp3 = 0;\r
+       }\r
+       else\r
+       {\r
+               ob->xdir = c.xaxis;\r
+               ob->ydir = c.yaxis;\r
+               if (c.xaxis || c.yaxis)\r
+                       ob->temp1 = c.dir;\r
+       }\r
+       ob->shapenum = swimshapes[ob->temp1] + ob->temp2;\r
+       if (++ob->temp2 == 2)\r
+               ob->temp2 = 0;\r
+\r
+       if (ob->temp2 == 0)\r
+       {\r
+               SD_PlaySound(SND_SWIM1);\r
+       }\r
+       else\r
+       {\r
+               SD_PlaySound(SND_SWIM2);\r
+       }\r
+}\r
+\r
+#else  // NOT Keen 4 (i.e. Keen 5 & 6):\r
+\r
+/*\r
+======================\r
+=\r
+= Teleport\r
+=\r
+======================\r
+*/\r
+\r
+void Teleport(Uint16 tileX, Uint16 tileY)\r
+{\r
+       Uint16 tile, globalx, globaly, duration, move;\r
+       objtype *o;\r
+       objtype *ob = player;\r
+\r
+       //\r
+       // enter the teleporter\r
+       //\r
+       SD_PlaySound(SND_TELEPORT);\r
+       globalx = CONVERT_TILE_TO_GLOBAL(tileX);\r
+       globaly = CONVERT_TILE_TO_GLOBAL(tileY);\r
+\r
+#ifdef KEEN6Ev15\r
+       // We need to make the compiler "forget" that duration starts at 0\r
+       // to make sure the while-loop check is performed when entering the\r
+       // loop. Can't change compiler settings since we do need that loop\r
+       // optimization for the for-loop at the end of this routine.\r
+       if (true)\r
+               duration = 0;\r
+#else\r
+       duration = 0;\r
+#endif\r
+\r
+       while (duration < 130)\r
+       {\r
+               RF_Refresh();\r
+               move = tics*2;\r
+               duration += tics;\r
+\r
+               if (ob->x == globalx && ob->y == globaly)\r
+                       break;\r
+\r
+               if (ob->y < globaly)\r
+               {\r
+                       ob->y += move;\r
+                       if (ob->y > globaly)\r
+                               ob->y = globaly;\r
+               }\r
+               else if (ob->y > globaly)\r
+               {\r
+                       ob->y -= move;\r
+                       if (ob->y < globaly)\r
+                               ob->y = globaly;\r
+               }\r
+\r
+               if (ob->x < globalx)\r
+               {\r
+                       ob->x += move;\r
+                       if (ob->x > globalx)\r
+                               ob->x = globalx;\r
+               }\r
+               else if (ob->x > globalx)\r
+               {\r
+                       ob->x -= move;\r
+                       if (ob->x < globalx)\r
+                               ob->x = globalx;\r
+               }\r
+\r
+               ob->shapenum = ((TimeCount >> 3) % 3) + WORLDKEENU1SPR;\r
+               RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);\r
+\r
+               tile = ((TimeCount >> 2) & TELEPORERTILEMASK) + TELEPORTERTILE1;\r
+               RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);\r
+       }\r
+\r
+       tile = TELEPORTERTILE2;\r
+       RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);\r
+\r
+       //\r
+       // teleport to new location\r
+       //\r
+       tile = *(mapsegs[2]+mapbwidthtable[tileY]/2 + tileX);\r
+       tileX = tile >> 8;\r
+       tileY = tile & 0x7F;    // BUG? y coordinate is limited to 1..127\r
+       ob->x = CONVERT_TILE_TO_GLOBAL(tileX);\r
+       ob->y = CONVERT_TILE_TO_GLOBAL(tileY);\r
+       ob->xdir = 0;\r
+       ob->ydir = 1;\r
+       ob->temp1 = dir_South;\r
+       NewState(ob, ob->state);\r
+       CenterActor(ob);\r
+\r
+       //\r
+       // draw flags/signs for new location\r
+       //\r
+       for (o=player->next; o; o=o->next)\r
+       {\r
+               if (!o->active && o->obclass == flagobj\r
+                       && o->tileright >= originxtile-1 && o->tileleft <= originxtilemax+1\r
+                       && o->tiletop <= originytilemax+1 && o->tilebottom >= originytile-1)\r
+               {\r
+                       o->needtoreact = true;\r
+                       o->active = ac_yes;\r
+                       RF_PlaceSprite(&o->sprite, o->x, o->y, o->shapenum, spritedraw, o->priority);\r
+               }\r
+       }\r
+       UpdateScore(scoreobj);\r
+       RF_Refresh();\r
+       RF_Refresh();\r
+\r
+       //\r
+       // leave teleporter\r
+       //\r
+       SD_PlaySound(SND_TELEPORT);\r
+\r
+       for (duration = 0; duration < 90; )\r
+       {\r
+               RF_Refresh();\r
+               duration += tics;\r
+               ob->y += tics*2 + tics;\r
+\r
+               ob->shapenum = ((TimeCount >> 3) % 3) + WORLDKEEND1SPR;\r
+               RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);\r
+\r
+               tile = ((TimeCount >> 2) & TELEPORERTILEMASK) + TELEPORTERTILE3;\r
+               RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);\r
+       }\r
+\r
+       tile = TELEPORTERTILE4;\r
+       RF_MemToMap(&tile, 1, tileX, tileY, 1, 1);\r
+       xtry = ytry = 0;\r
+       ClipToWalls(ob);\r
+}\r
+\r
+#ifdef KEEN5\r
+\r
+/*\r
+======================\r
+=\r
+= T_Elevate\r
+=\r
+======================\r
+*/\r
+\r
+void T_Elevate(objtype *ob)\r
+{\r
+       Sint16 i, x, y, tx, ty;\r
+       Uint16 tiles[2][2];\r
+\r
+       ytry = ob->ydir * 64 * tics;\r
+       if (ob->x != ob->temp2)\r
+       {\r
+               xtry = ob->xdir * 12 * tics;\r
+               if ( (ob->xdir == 1 && ob->x + xtry > ob->temp2)\r
+                       || (ob->xdir == -1 && ob->x + xtry < ob->temp2) )\r
+               {\r
+                       xtry = ob->temp2 - ob->x;\r
+               }\r
+       }\r
+\r
+       //\r
+       // Keen has no sprite in this state, so we need to update the hitbox manually\r
+       // to avoid issues (the screen scrolling routines use left/right/top/bottom)\r
+       //\r
+       ob->left = ob->x + xtry;\r
+       ob->right = ob->left + (TILEGLOBAL-1);\r
+       ob->top = ob->y + ytry;\r
+       ob->bottom = ob->top + (TILEGLOBAL-1);\r
+\r
+       if (ob->ydir == 1)\r
+       {\r
+               if (ob->y + ytry < ob->temp1)\r
+                       return;\r
+       }\r
+       else\r
+       {\r
+               if (ob->y + ytry > ob->temp1)\r
+                       return;\r
+       }\r
+\r
+       //\r
+       // the invisible Keen has arrived at its destination\r
+       //\r
+       ytry = 0;\r
+       xtry = 0;\r
+       ob->x = ob->temp2;\r
+       ob->y = ob->temp1;\r
+       ob->priority = 1;\r
+       ob->temp1 = 4;\r
+       ob->temp2 = 3;\r
+       ob->temp3 = 0;\r
+       player->xdir = 0;\r
+       player->ydir = 0;\r
+       ob->state = &s_worldkeen;\r
+       ob->shapenum = WORLDKEEND3SPR;\r
+       ob->needtoclip = cl_midclip;\r
+       tx = CONVERT_GLOBAL_TO_TILE(ob->x);\r
+       ty = CONVERT_GLOBAL_TO_TILE(ob->y);\r
+       WorldScrollScreen(ob);\r
+       UpdateScore(scoreobj);\r
+       RF_Refresh();\r
+       RF_Refresh();\r
+\r
+       ob->y -= TILEGLOBAL;\r
+       RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);\r
+\r
+       //\r
+       // open the elevator door\r
+       //\r
+       SD_PlaySound(SND_ELEVATORDOOR);\r
+       for (i=0; i<=5; i++)\r
+       {\r
+               for (y=0; y<2; y++)\r
+               {\r
+                       for (x=0; x<2; x++)\r
+                       {\r
+                               tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + i*2 + x);\r
+                       }\r
+               }\r
+               RF_MemToMap(&tiles[0][0], 1, tx, ty-2, 2, 2);\r
+               RF_Refresh();\r
+               VW_WaitVBL(8);\r
+       }\r
+\r
+       //\r
+       // make Keen walk out of the elevator\r
+       //\r
+       for (y=0; y<32; y++)\r
+       {\r
+               ob->y += 8;     // move half a pixel every frame for 32 frames -> move down 16 pixels total\r
+               ob->shapenum = (y / 4) % 3 + WORLDKEEND1SPR;\r
+               RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);\r
+               RF_Refresh();\r
+       }\r
+       ob->needtoclip = cl_midclip;    // redundant, but doesn't do any harm\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= Elevator\r
+=\r
+======================\r
+*/\r
+\r
+void Elevator(Uint16 tileX, Uint16 tileY, Sint16 dir)\r
+{\r
+       Uint16 info, globalx, globaly, duration, move;\r
+       Sint16 x, y, i;\r
+       Uint16 tiles[2][2];\r
+       objtype *ob = player;\r
+\r
+       globalx = CONVERT_TILE_TO_GLOBAL(tileX);\r
+       globaly = CONVERT_TILE_TO_GLOBAL(tileY);\r
+\r
+       //\r
+       // make Keen walk into the elevator\r
+       //\r
+       for (duration = 0; duration < 130; )\r
+       {\r
+               CalcBounds(ob);\r
+               WorldScrollScreen(ob);\r
+               UpdateScore(scoreobj);\r
+               RF_Refresh();\r
+\r
+               move = tics * 2;\r
+               duration += tics;\r
+\r
+               if (ob->x == globalx && ob->y == globaly)\r
+                       break;\r
+\r
+               if (ob->y < globaly)\r
+               {\r
+                       ob->y += move;\r
+                       if (ob->y > globaly)\r
+                               ob->y = globaly;\r
+               }\r
+               else if (ob->y > globaly)\r
+               {\r
+                       ob->y -= move;\r
+                       if (ob->y < globaly)\r
+                               ob->y = globaly;\r
+               }\r
+\r
+               if (ob->x < globalx)\r
+               {\r
+                       ob->x += move;\r
+                       if (ob->x > globalx)\r
+                               ob->x = globalx;\r
+               }\r
+               else if (ob->x > globalx)\r
+               {\r
+                       ob->x -= move;\r
+                       if (ob->x < globalx)\r
+                               ob->x = globalx;\r
+               }\r
+\r
+               ob->shapenum = ((duration / 8) % 3) + WORLDKEENU1SPR;\r
+               RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);\r
+       }\r
+\r
+       //\r
+       // close the elevator door\r
+       //\r
+       SD_PlaySound(SND_ELEVATORDOOR);\r
+       for (i=5; i >= 0; i--)\r
+       {\r
+               for (y=0; y<2; y++)\r
+               {\r
+                       for (x=0; x<2; x++)\r
+                       {\r
+                               tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + i*2 + x);\r
+                       }\r
+               }\r
+               RF_MemToMap(&tiles[0][0], 1, tileX+dir, tileY-1, 2, 2);\r
+               RF_Refresh();\r
+               VW_WaitVBL(8);\r
+       }\r
+\r
+       //\r
+       // make Keen invisible (and not clipping) and send him to the destination\r
+       //\r
+       RF_RemoveSprite(&ob->sprite);\r
+       info = *(mapsegs[2] + mapbwidthtable[tileY]/2 + tileX);\r
+       ob->temp2 = CONVERT_TILE_TO_GLOBAL(info >> 8);\r
+       ob->temp1 = CONVERT_TILE_TO_GLOBAL((info & 0x7F) + 1);  // BUG? y coordinate is limited to 1..127\r
+       if (ob->temp1 < ob->y)\r
+       {\r
+               ob->ydir = -1;\r
+       }\r
+       else\r
+       {\r
+               ob->ydir = 1;\r
+       }\r
+       if (ob->temp2 < ob->x)\r
+       {\r
+               ob->xdir = -1;\r
+       }\r
+       else\r
+       {\r
+               ob->xdir = 1;\r
+       }\r
+       ob->needtoclip = cl_noclip;\r
+       ob->state = &s_worldelevate;\r
+}\r
+\r
+#endif //ifdef KEEN5\r
+\r
+#endif //ifdef KEEN4 ... else ...\r
+\r
+/*\r
+======================\r
+=\r
+= CheckWorldInTiles\r
+=\r
+======================\r
+*/\r
+\r
+void CheckWorldInTiles(objtype *ob)\r
+{\r
+       Uint16 tx, ty, intile;\r
+\r
+       if (ob->temp3)\r
+               return;\r
+\r
+       tx = ob->tilemidx;\r
+       ty = CONVERT_GLOBAL_TO_TILE(ob->top + (ob->bottom-ob->top)/2);\r
+       intile = tinf[INTILE + *(mapsegs[1]+mapbwidthtable[ty]/2+tx)];\r
+#if defined KEEN4\r
+       if (intile == INTILE_SHORESOUTH || intile == INTILE_SHORENORTH\r
+               || intile == INTILE_SHOREEAST || intile == INTILE_SHOREWEST)\r
+       {\r
+               if (!gamestate.wetsuit)\r
+               {\r
+                       SD_PlaySound(SND_NOWAY);\r
+                       CantSwim();\r
+                       RF_ForceRefresh();\r
+                       xtry = -ob->xmove;\r
+                       ytry = -ob->ymove;\r
+                       ob->xdir = ob->ydir = 0;\r
+                       ClipToWalls(ob);\r
+               }\r
+               else\r
+               {\r
+                       ob->temp1 = tiledir[intile-INTILE_SHORESOUTH];\r
+                       if (ob->state == &s_worldswim)\r
+                       {\r
+                               ob->temp1 = opposite[ob->temp1];\r
+                       }\r
+                       switch (ob->temp1)\r
+                       {\r
+                       case dir_North:\r
+                               ob->xdir = 0;\r
+                               ob->ydir = -1;\r
+                               break;\r
+                       case dir_East:\r
+                               ob->xdir = 1;\r
+                               ob->ydir = 0;\r
+                               break;\r
+                       case dir_South:\r
+                               ob->xdir = 0;\r
+                               ob->ydir = 1;\r
+                               break;\r
+                       case dir_West:\r
+                               ob->xdir = -1;\r
+                               ob->ydir = 0;\r
+                               break;\r
+                       }\r
+                       ob->temp2 = 0;\r
+                       ob->temp3 = 18;\r
+                       if (ob->state == &s_worldswim)\r
+                       {\r
+                               ChangeState(ob, &s_worldkeenwalk);\r
+                       }\r
+                       else\r
+                       {\r
+                               ChangeState(ob, &s_worldswim);\r
+                       }\r
+               }\r
+       }\r
+#elif defined KEEN5\r
+       switch (intile)\r
+       {\r
+       case INTILE_TELEPORT:\r
+               Teleport(tx, ty);\r
+               break;\r
+       case INTILE_ELEVATORLEFT:\r
+               Elevator(tx, ty, 0);\r
+               break;\r
+       case INTILE_ELEVATORRIGHT:\r
+               Elevator(tx, ty, -1);\r
+               break;\r
+       }\r
+#elif defined KEEN6\r
+       switch (intile)\r
+       {\r
+       case INTILE_TELEPORT:\r
+               Teleport(tx, ty);\r
+               break;\r
+       }\r
+#endif\r
+}\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                          FLAGS\r
+\r
+temp1 = x destination for the thrown flag\r
+temp2 = y destination for the thrown flag\r
+temp3 = amount of time passed since flag was thrown (in tics)\r
+\r
+=============================================================================\r
+*/\r
+\r
+statetype s_flagwave1     = {FLAGFLAP1SPR, FLAGFLAP1SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave2};\r
+statetype s_flagwave2     = {FLAGFLAP2SPR, FLAGFLAP2SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave3};\r
+statetype s_flagwave3     = {FLAGFLAP3SPR, FLAGFLAP3SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave4};\r
+statetype s_flagwave4     = {FLAGFLAP4SPR, FLAGFLAP4SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave1};\r
+\r
+#ifndef KEEN5\r
+statetype s_throwflag0    = {FLAGFLIP1SPR, FLAGFLIP1SPR, think, false, false, 6, 0, 0, TossThink, NULL, R_Draw, &s_throwflag1};\r
+statetype s_throwflag1    = {FLAGFLIP1SPR, FLAGFLIP1SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag2};\r
+statetype s_throwflag2    = {FLAGFLIP2SPR, FLAGFLIP2SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag3};\r
+statetype s_throwflag3    = {FLAGFLIP3SPR, FLAGFLIP3SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag4};\r
+statetype s_throwflag4    = {FLAGFLIP4SPR, FLAGFLIP4SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag5};\r
+statetype s_throwflag5    = {FLAGFALL1SPR, FLAGFALL1SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag6};\r
+statetype s_throwflag6    = {FLAGFALL1SPR, FLAGFALL1SPR, stepthink, true, false, 1, 0, 0, FlagAlign, NULL, R_Draw, &s_flagwave1};\r
+#endif\r
+\r
+Sint16 flagx, flagy;\r
+Point flagpath[30];\r
+\r
+/*\r
+======================\r
+=\r
+= SpawnFlag\r
+=\r
+======================\r
+*/\r
+\r
+void SpawnFlag(Sint16 x, Sint16 y)\r
+{\r
+       GetNewObj(false);\r
+       new->needtoclip = cl_noclip;\r
+       new->priority = 3;\r
+       new->obclass = flagobj;\r
+       new->active = ac_yes;\r
+#if defined KEEN4\r
+       new->x = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL;\r
+       new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL;\r
+#elif defined KEEN5\r
+       new->x = CONVERT_TILE_TO_GLOBAL(x) + -5*PIXGLOBAL;\r
+       new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL;\r
+#elif defined KEEN6\r
+       new->x = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL;               // useless!\r
+       new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL;     // useless!\r
+#if GRMODE == CGAGR\r
+       new->x = CONVERT_TILE_TO_GLOBAL(x) + 10*PIXGLOBAL;\r
+#else\r
+       new->x = CONVERT_TILE_TO_GLOBAL(x) + 14*PIXGLOBAL;\r
+#endif\r
+       new->y = CONVERT_TILE_TO_GLOBAL(y) + -26*PIXGLOBAL;\r
+       {\r
+               Uint16 tile = *(mapsegs[1]+mapbwidthtable[y]/2 + x) + 1;\r
+               RF_MemToMap(&tile, 1, x, y, 1, 1);\r
+       }\r
+#endif\r
+       new->ticcount = US_RndT() / 16;\r
+       NewState(new, &s_flagwave1);\r
+}\r
+\r
+#ifndef KEEN5\r
+/*\r
+======================\r
+=\r
+= SpawnThrowFlag\r
+=\r
+======================\r
+*/\r
+\r
+void SpawnThrowFlag(Sint16 x, Sint16 y)\r
+{\r
+       Sint16 i;\r
+       Sint32 xdist, ydist;\r
+\r
+       GetNewObj(false);\r
+       new->needtoclip = cl_noclip;\r
+       new->priority = 3;\r
+       new->obclass = flagobj;\r
+       new->active = ac_allways;\r
+       new->x = gamestate.worldx - 16*PIXGLOBAL;\r
+       new->y = gamestate.worldy - 16*PIXGLOBAL;\r
+#if defined KEEN4\r
+       new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL;\r
+       new->temp2 = CONVERT_TILE_TO_GLOBAL(y) + -38*PIXGLOBAL;\r
+#elif defined KEEN6\r
+       flagx = x;\r
+       flagy = y;\r
+#if GRMODE == CGAGR\r
+       new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 10*PIXGLOBAL;\r
+#else\r
+       new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 14*PIXGLOBAL;\r
+#endif\r
+       new->temp2 = CONVERT_TILE_TO_GLOBAL(y) + -34*PIXGLOBAL;\r
+#endif\r
+       xdist = (Sint32)new->temp1 - (Sint32)new->x;\r
+       ydist = (Sint32)new->temp2 - (Sint32)new->y;\r
+       for (i = 0; i < 30; i++)\r
+       {\r
+               flagpath[i].x = new->x + (xdist * min(i, 24))/24;\r
+               flagpath[i].y = new->y + (ydist * i)/30;\r
+               if (i < 10)\r
+               {\r
+                       flagpath[i].y -= i*3*PIXGLOBAL;\r
+               }\r
+               else if (i < 15)\r
+               {\r
+                       flagpath[i].y -= i*PIXGLOBAL + 20*PIXGLOBAL;\r
+               }\r
+               else if (i < 20)\r
+               {\r
+                       flagpath[i].y -= (20-i)*PIXGLOBAL + 30*PIXGLOBAL;\r
+               }\r
+               else\r
+               {\r
+                       flagpath[i].y -= (29-i)*3*PIXGLOBAL;\r
+               }\r
+       }\r
+       NewState(new, &s_throwflag0);\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= TossThink\r
+=\r
+======================\r
+*/\r
+\r
+void TossThink(objtype *ob)\r
+{\r
+       if (screenfaded)\r
+               return;\r
+\r
+       SD_StopSound();\r
+       SD_PlaySound(SND_FLAGSPIN);\r
+       ob->state = ob->state->nextstate;\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= PathThink\r
+=\r
+======================\r
+*/\r
+\r
+void PathThink(objtype *ob)\r
+{\r
+       ob->temp3 = ob->temp3 + tics;\r
+       if (ob->temp3 > 58)\r
+               ob->temp3 = 58;\r
+\r
+       ob->x = flagpath[ob->temp3/2].x;\r
+       ob->y = flagpath[ob->temp3/2].y;\r
+       ob->needtoreact = true;\r
+       if (ob->temp1 == 0)\r
+       {\r
+               SD_PlaySound(SND_FLAGSPIN);\r
+       }\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= FlagAlign\r
+=\r
+======================\r
+*/\r
+\r
+void FlagAlign(objtype *ob)\r
+{\r
+       ob->x = ob->temp1;\r
+       ob->y = ob->temp2 + 8*PIXGLOBAL;\r
+       SD_PlaySound(SND_FLAGLAND);\r
+#ifdef KEEN6\r
+       {\r
+               Uint16 tile = *(mapsegs[1]+mapbwidthtable[flagy]/2 + flagx) + 1;\r
+               RF_MemToMap(&tile, 1, flagx, flagy, 1, 1);\r
+       }\r
+#endif\r
+}\r
+#endif\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                NEURAL STUNNER\r
+\r
+=============================================================================\r
+*/\r
+statetype s_stunray1 = {STUN1SPR, STUN1SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray2};\r
+statetype s_stunray2 = {STUN2SPR, STUN2SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray3};\r
+statetype s_stunray3 = {STUN3SPR, STUN3SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray4};\r
+statetype s_stunray4 = {STUN4SPR, STUN4SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray1};\r
+\r
+statetype s_stunhit  = {STUNHIT1SPR, STUNHIT1SPR, step, false, false, 12, 0, 0, NULL, NULL, R_Draw, &s_stunhit2};\r
+statetype s_stunhit2 = {STUNHIT2SPR, STUNHIT2SPR, step, false, false, 12, 0, 0, NULL, NULL, R_Draw, NULL};\r
+\r
+/*\r
+======================\r
+=\r
+= SpawnShot\r
+=\r
+======================\r
+*/\r
+\r
+void SpawnShot(Uint16 x, Uint16 y, Direction dir)\r
+{\r
+       if (!gamestate.ammo)\r
+       {\r
+               SD_PlaySound(SND_USESWITCH);\r
+               return;\r
+       }\r
+\r
+       gamestate.ammo--;\r
+       GetNewObj(true);\r
+       new->x = x;\r
+       new->y = y;\r
+       new->priority = 0;\r
+       new->obclass = stunshotobj;\r
+       new->active = ac_allways;\r
+       SD_PlaySound(SND_KEENFIRE);\r
+       switch (dir)\r
+       {\r
+       case dir_North:\r
+               new->xdir = 0;\r
+               new->ydir = -1;\r
+               break;\r
+       case dir_East:\r
+               new->xdir = 1;\r
+               new->ydir = 0;\r
+               break;\r
+       case dir_South:\r
+               new->xdir = 0;\r
+               new->ydir = 1;\r
+               break;\r
+       case dir_West:\r
+               new->xdir = -1;\r
+               new->ydir = 0;\r
+               break;\r
+       default:\r
+               Quit("SpawnShot: Bad dir!");\r
+               break;\r
+       }\r
+       NewState(new, &s_stunray1);\r
+\r
+#ifdef KEEN6\r
+       {\r
+               objtype *ob;\r
+\r
+               for (ob=player->next; ob; ob=ob->next)\r
+               {\r
+                       if (ob->active\r
+                               && new->right > ob->left && new->left < ob->right\r
+                               && new->top < ob->bottom && new->bottom > ob->top\r
+                               && ob->state->contact)\r
+                       {\r
+                               ob->state->contact(ob, new);\r
+                               return;\r
+                       }\r
+               }\r
+       }\r
+#endif\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= ExplodeShot\r
+=\r
+======================\r
+*/\r
+\r
+void ExplodeShot(objtype *ob)\r
+{\r
+       ob->obclass = inertobj;\r
+       ChangeState(ob, &s_stunhit);\r
+       SD_PlaySound(SND_SHOTEXPLODE);\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= T_Shot\r
+=\r
+======================\r
+*/\r
+\r
+void T_Shot(objtype *ob)\r
+{\r
+       objtype *ob2;\r
+\r
+       if (ob->tileright >= originxtile && ob->tilebottom >= originytile\r
+               && ob->tileleft <= originxtilemax && ob->tiletop <= originytilemax)\r
+       {\r
+               //object is visible, so do nothing\r
+               return;\r
+       }\r
+\r
+       if (ob->tileright+10 < originxtile\r
+               || ob->tileleft-10 > originxtilemax\r
+               || ob->tilebottom+6 < originytile\r
+               || ob->tiletop-6 > originytilemax)\r
+       {\r
+               //shot is off-screen by more than half a screen, so remove it\r
+               RemoveObj(ob);\r
+               return;\r
+       }\r
+\r
+       //check for collisions with INACTIVE objects\r
+       for (ob2 = player->next; ob2; ob2 = ob2->next)\r
+       {\r
+               if (!ob2->active && ob->right > ob2->left && ob->left < ob2->right\r
+                       && ob->top < ob2->bottom && ob->bottom > ob2->top)\r
+               {\r
+                       if (ob2->state->contact)\r
+                       {\r
+                               ob2->state->contact(ob2, ob);\r
+                               ob2->needtoreact = true;\r
+                               ob2->active = ac_yes;\r
+                       }\r
+\r
+                       if (ob->obclass == nothing)     //BUG: obclass is 'inertobj' for the exploded shot\r
+                               break;\r
+               }\r
+       }\r
+}\r
+\r
+/*\r
+======================\r
+=\r
+= R_Shot\r
+=\r
+======================\r
+*/\r
+\r
+void R_Shot(objtype *ob)\r
+{\r
+       Uint16 tile;\r
+\r
+       if (ob->hitnorth == 1 && ob->tileleft != ob->tileright)\r
+       {\r
+               tile = *(mapsegs[1]+mapbwidthtable[ob->tiletop-1]/2+ob->tileright);\r
+               if (tinf[NORTHWALL+tile] == 17)\r
+               {\r
+                       ob->hitnorth = 17;\r
+                       ob->x += 0x100 - (ob->x & 0xFF);\r
+               }\r
+       }\r
+       else if (ob->hitnorth == 17 && ob->tileleft != ob->tileright)\r
+       {\r
+               ob->x &= 0xFF00;\r
+       }\r
+       if (ob->hitsouth == 1 && ob->tileleft != ob->tileright)\r
+       {\r
+               tile = *(mapsegs[1]+mapbwidthtable[ob->tilebottom+1]/2+ob->tileright);\r
+               if (tinf[SOUTHWALL+tile] == 17)\r
+               {\r
+                       ob->hitsouth = 17;\r
+                       ob->x += 0x100 - (ob->x & 0xFF);\r
+               }\r
+       }\r
+       else if (ob->hitsouth == 17 && ob->tileleft != ob->tileright)\r
+       {\r
+               ob->x &= 0xFF00;\r
+       }\r
+       if (ob->hitnorth == 17 || ob->hitsouth == 17)\r
+       {\r
+               ytry = ob->state->ymove * tics * ob->ydir;\r
+               ob->y += ytry;\r
+               ob->top += ytry;\r
+               ob->bottom += ytry;\r
+               ob->tiletop = CONVERT_GLOBAL_TO_TILE(ob->top);\r
+               ob->tilebottom = CONVERT_GLOBAL_TO_TILE(ob->bottom);\r
+       }\r
+       else if (ob->hitnorth || ob->hitsouth || ob->hiteast || ob->hitwest)\r
+       {\r
+               ExplodeShot(ob);\r
+       }\r
+       RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority);\r
+}\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                DOOR\r
+\r
+temp1 = height of the door's main section (identical tiles!), in tiles\r
+        DoorOpen changes two more tiles at the bottom end of the door\r
+                 (total door height in tiles is temp1 + 2)\r
+\r
+=============================================================================\r
+*/\r
+\r
+statetype s_door1    = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, &s_door2};\r
+statetype s_door2    = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, &s_door3};\r
+statetype s_door3    = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, NULL};\r
+\r
+/*\r
+======================\r
+=\r
+= DoorOpen\r
+=\r
+======================\r
+*/\r
+\r
+void DoorOpen(objtype *ob)\r
+{\r
+       Sint16 i;\r
+       Uint16 far *map;\r
+       Uint16 tiles[50];\r
+\r
+       map = mapsegs[1] + mapbwidthtable[ob->y]/2 + ob->x;\r
+       for (i=0; i < ob->temp1+2; i++, map+=mapwidth)\r
+       {\r
+               tiles[i] = *map + 1;\r
+       }\r
+       RF_MemToMap(tiles, 1, ob->x, ob->y, 1, ob->temp1+2);\r
+}\r
+\r
+#ifdef KEEN5\r
+/*\r
+=============================================================================\r
+\r
+                                                CARD DOOR\r
+\r
+temp1 = frame counter\r
+\r
+=============================================================================\r
+*/\r
+statetype s_carddoor    = {0, 0, step, false, false, 15, 0, 0, CardDoorOpen, NULL, NULL, &s_carddoor};\r
+\r
+/*\r
+======================\r
+=\r
+= CardDoorOpen\r
+=\r
+======================\r
+*/\r
+\r
+void CardDoorOpen(objtype *ob)\r
+{\r
+       Sint16 x, y;\r
+       Uint16 far *map;\r
+       Uint16 tiles[16], *tileptr;\r
+\r
+       tileptr = tiles;\r
+       map = mapsegs[1] + mapbwidthtable[ob->y]/2 + ob->x;\r
+       for (y=0; y<4; y++, map+=mapwidth)\r
+       {\r
+               for (x=0; x<4; x++)\r
+               {\r
+                       *tileptr++ = map[x]-4;\r
+               }\r
+       }\r
+       RF_MemToMap(tiles, 1, ob->x, ob->y, 4, 4);\r
+\r
+       if (++ob->temp1 == 3)\r
+               ob->state = NULL;\r
+}\r
+\r
+#endif
\ No newline at end of file