]> 4ch.mooo.com Git - 16.git/blobdiff - src/lib/hb/kd_play.c
[16_ca needs huge amounts of work and I should remember what needs to be done soon...
[16.git] / src / lib / hb / kd_play.c
diff --git a/src/lib/hb/kd_play.c b/src/lib/hb/kd_play.c
new file mode 100755 (executable)
index 0000000..02bdccd
--- /dev/null
@@ -0,0 +1,1928 @@
+/* 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
+// KD_PLAY.C\r
+\r
+#include "KD_DEF.H"\r
+#pragma        hdrstop\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                LOCAL CONSTANTS\r
+\r
+=============================================================================\r
+*/\r
+\r
+#define        INACTIVATEDIST  10\r
+\r
+#define MAXMOVE        (TILEGLOBAL-17)\r
+\r
+#define NUMLUMPS       22\r
+\r
+\r
+#define CONTROLSLUMP   0\r
+#define KEENLUMP               1\r
+#define WORLDKEENLUMP  2\r
+#define BROCCOLUMP             3\r
+#define        TOMATLUMP       4\r
+#define        CARROTLUMP              5\r
+#define        ASPARLUMP               6\r
+#define        GRAPELUMP               7\r
+#define        TATERLUMP               8\r
+#define        CARTLUMP                9\r
+#define        FRENCHYLUMP             10\r
+#define        MELONLUMP               11\r
+#define        SQUASHLUMP              12\r
+#define        APELLUMP                13\r
+#define        PEALUMP                 14\r
+#define        BOOBUSLUMP              15\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                GLOBAL VARIABLES\r
+\r
+=============================================================================\r
+*/\r
+\r
+exittype       playstate;\r
+gametype       gamestate;\r
+\r
+boolean                button0held,button1held;\r
+objtype                *new,*check,*player,*scoreobj;\r
+\r
+unsigned       originxtilemax,originytilemax;\r
+\r
+ControlInfo    c;\r
+\r
+objtype dummyobj;\r
+\r
+char           *levelnames[21] =\r
+{\r
+"The Land of Tuberia",\r
+"Horseradish Hill",\r
+"The Melon Mines",\r
+"Bridge Bottoms",\r
+"Rhubarb Rapids",\r
+"Parsnip Pass",\r
+"Level 6",\r
+"Spud City",\r
+"Level 8",\r
+"Apple Acres",\r
+"Grape Grove",\r
+"Level 11",\r
+"Brussels Sprout Bay",\r
+"Level 13",\r
+"Squash Swamp",\r
+"Boobus' Chamber",\r
+"Castle Tuberia",\r
+"",\r
+"Title Page"\r
+};\r
+\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                LOCAL VARIABLES\r
+\r
+=============================================================================\r
+*/\r
+\r
+// for asm scaning of map planes\r
+unsigned       mapx,mapy,mapxcount,mapycount,maptile,mapspot;\r
+\r
+int                    plummet;\r
+\r
+int                    objectcount;\r
+\r
+objtype                objarray[MAXACTORS],*lastobj,*objfreelist;\r
+\r
+int                    oldtileleft,oldtiletop,oldtileright,oldtilebottom,oldtilemidx;\r
+int                    oldleft,oldtop,oldright,oldbottom,oldmidx;\r
+int                    leftmoved,topmoved,rightmoved,bottommoved,midxmoved;\r
+\r
+int                    topmove,bottommove,midxmove;\r
+\r
+int                    inactivateleft,inactivateright,inactivatetop,inactivatebottom;\r
+\r
+int                    fadecount;\r
+\r
+boolean                bombspresent;\r
+\r
+boolean                lumpneeded[NUMLUMPS];\r
+int                    lumpstart[NUMLUMPS] =\r
+{\r
+CONTROLS_LUMP_START,\r
+KEEN_LUMP_START,\r
+WORLDKEEN_LUMP_START,\r
+BROCCOLASH_LUMP_START,\r
+TOMATO_LUMP_START,\r
+CARROT_LUMP_START,\r
+ASPAR_LUMP_START,\r
+GRAPE_LUMP_START,\r
+TATER_LUMP_START,\r
+CANTA_LUMP_START,\r
+FRENCHY_LUMP_START,\r
+MELONLIPS_LUMP_START,\r
+SQUASHER_LUMP_START,\r
+APEL_LUMP_START,\r
+PEAS_LUMP_START,\r
+BOOBUS_LUMP_START,\r
+};\r
+\r
+int                    lumpend[NUMLUMPS] =\r
+{\r
+CONTROLS_LUMP_END,\r
+KEEN_LUMP_END,\r
+WORLDKEEN_LUMP_END,\r
+BROCCOLASH_LUMP_END,\r
+TOMATO_LUMP_END,\r
+CARROT_LUMP_END,\r
+ASPAR_LUMP_END,\r
+GRAPE_LUMP_END,\r
+TATER_LUMP_END,\r
+CANTA_LUMP_END,\r
+FRENCHY_LUMP_END,\r
+MELONLIPS_LUMP_END,\r
+SQUASHER_LUMP_END,\r
+APEL_LUMP_END,\r
+PEAS_LUMP_END,\r
+BOOBUS_LUMP_END,\r
+};\r
+\r
+\r
+void   CheckKeys (void);\r
+void   CalcInactivate (void);\r
+void   InitObjArray (void);\r
+void   GetNewObj (boolean usedummy);\r
+void   RemoveObj (objtype *gone);\r
+void   ScanInfoPlane (void);\r
+void   PatchWorldMap (void);\r
+void   MarkTileGraphics (void);\r
+void   FadeAndUnhook (void);\r
+void   SetupGameLevel (boolean loadnow);\r
+void   ScrollScreen (void);\r
+void   MoveObjVert (objtype *ob, int ymove);\r
+void   MoveObjHoriz (objtype *ob, int xmove);\r
+void   GivePoints (unsigned points);\r
+void   ClipToEnds (objtype *ob);\r
+void   ClipToEastWalls (objtype *ob);\r
+void   ClipToWestWalls (objtype *ob);\r
+void   ClipToWalls (objtype *ob);\r
+void   ClipToSpriteSide (objtype *push, objtype *solid);\r
+void   ClipToSprite (objtype *push, objtype *solid, boolean squish);\r
+int    DoActor (objtype *ob,int tics);\r
+void   StateMachine (objtype *ob);\r
+void   NewState (objtype *ob,statetype *state);\r
+void   PlayLoop (void);\r
+void   GameLoop (void);\r
+\r
+//===========================================================================\r
+\r
+/*\r
+=====================\r
+=\r
+= CheckKeys\r
+=\r
+=====================\r
+*/\r
+\r
+void CheckKeys (void)\r
+{\r
+       if (screenfaded)                        // don't do anything with a faded screen\r
+               return;\r
+\r
+//\r
+// space for status screen\r
+//\r
+       if (Keyboard[sc_Space])\r
+       {\r
+               StatusWindow ();\r
+               IN_ClearKeysDown();\r
+               RF_ForceRefresh();\r
+               lasttimecount = TimeCount;\r
+       }\r
+\r
+//\r
+// pause key wierdness can't be checked as a scan code\r
+//\r
+       if (Paused)\r
+       {\r
+               VW_FixRefreshBuffer ();\r
+               US_CenterWindow (8,3);\r
+               US_PrintCentered ("PAUSED");\r
+               VW_UpdateScreen ();\r
+               IN_Ack();\r
+               RF_ForceRefresh ();\r
+               Paused = false;\r
+       }\r
+\r
+//\r
+// F1-F7/ESC to enter control panel\r
+//\r
+       if ( (LastScan >= sc_F1 && LastScan <= sc_F7) || LastScan == sc_Escape)\r
+       {\r
+               VW_FixRefreshBuffer ();\r
+               US_CenterWindow (20,8);\r
+               US_CPrint ("Loading");\r
+               VW_UpdateScreen ();\r
+               US_ControlPanel();\r
+               IN_ClearKeysDown();\r
+               if (restartgame)\r
+                       playstate = resetgame;\r
+               else if (!loadedgame)\r
+                       RF_ForceRefresh();              // don't refresh if loading a new game\r
+\r
+               lasttimecount = TimeCount;\r
+       }\r
+\r
+//\r
+// F10-? debug keys\r
+//\r
+       if (Keyboard[sc_F10] && DebugKeys() )\r
+       {\r
+               RF_ForceRefresh();\r
+               lasttimecount = TimeCount;\r
+       }\r
+\r
+}\r
+\r
+//===========================================================================\r
+\r
+\r
+/*\r
+=======================\r
+=\r
+= CalcInactivate\r
+=\r
+=======================\r
+*/\r
+\r
+void CalcInactivate (void)\r
+{\r
+       originxtilemax = originxtile+PORTTILESWIDE-1;\r
+       originytilemax = originytile+PORTTILESHIGH-1;\r
+\r
+       inactivateleft = originxtile-INACTIVATEDIST;\r
+       if (inactivateleft < 0)\r
+               inactivateleft = 0;\r
+       inactivateright = originxtilemax+INACTIVATEDIST;\r
+       inactivatetop = originytile-INACTIVATEDIST;\r
+       if (inactivatetop < 0)\r
+               inactivatetop = 0;\r
+       inactivatebottom = originytilemax+INACTIVATEDIST;\r
+}\r
+\r
+\r
+//===========================================================================\r
+\r
+\r
+/*\r
+#############################################################################\r
+\r
+                                 The objarray data structure\r
+\r
+#############################################################################\r
+\r
+Objarray containt structures for every actor currently playing.  The structure\r
+is accessed as a linked list starting at *player, ending when ob->next ==\r
+NULL.  GetNewObj inserts a new object at the end of the list, meaning that\r
+if an actor spawn another actor, the new one WILL get to think and react the\r
+same frame.  RemoveObj unlinks the given object and returns it to the free\r
+list, but does not damage the objects ->next pointer, so if the current object\r
+removes itself, a linked list following loop can still safely get to the\r
+next element.\r
+\r
+<backwardly linked free list>\r
+\r
+#############################################################################\r
+*/\r
+\r
+\r
+/*\r
+=========================\r
+=\r
+= InitObjArray\r
+=\r
+= Call to clear out the entire object list, returning them all to the free\r
+= list.  Allocates a special spot for the player.\r
+=\r
+=========================\r
+*/\r
+\r
+void InitObjArray (void)\r
+{\r
+       int     i;\r
+\r
+       for (i=0;i<MAXACTORS;i++)\r
+       {\r
+               objarray[i].prev = &objarray[i+1];\r
+               objarray[i].next = NULL;\r
+       }\r
+\r
+       objarray[MAXACTORS-1].prev = NULL;\r
+\r
+       objfreelist = &objarray[0];\r
+       lastobj = NULL;\r
+\r
+       objectcount = 0;\r
+\r
+//\r
+// give the player and score the first free spots\r
+//\r
+       GetNewObj (false);\r
+       player = new;\r
+       GetNewObj (false);\r
+       scoreobj = new;\r
+}\r
+\r
+//===========================================================================\r
+\r
+/*\r
+=========================\r
+=\r
+= GetNewObj\r
+=\r
+= Sets the global variable new to point to a free spot in objarray.\r
+= The free spot is inserted at the end of the liked list\r
+=\r
+= When the object list is full, the caller can either have it bomb out ot\r
+= return a dummy object pointer that will never get used\r
+=\r
+=========================\r
+*/\r
+\r
+void GetNewObj (boolean usedummy)\r
+{\r
+       if (!objfreelist)\r
+       {\r
+               if (usedummy)\r
+               {\r
+                       new = &dummyobj;\r
+                       return;\r
+               }\r
+               Quit ("GetNewObj: No free spots in objarray!");\r
+       }\r
+\r
+       new = objfreelist;\r
+       objfreelist = new->prev;\r
+       memset (new,0,sizeof(*new));\r
+\r
+       if (lastobj)\r
+               lastobj->next = new;\r
+       new->prev = lastobj;    // new->next is allready NULL from memset\r
+\r
+       new->active = yes;\r
+       new->needtoclip = true;\r
+       lastobj = new;\r
+\r
+       objectcount++;\r
+}\r
+\r
+//===========================================================================\r
+\r
+/*\r
+=========================\r
+=\r
+= RemoveObj\r
+=\r
+= Add the given object back into the free list, and unlink it from it's\r
+= neighbors\r
+=\r
+=========================\r
+*/\r
+\r
+void RemoveObj (objtype *gone)\r
+{\r
+       if (gone == player)\r
+               Quit ("RemoveObj: Tried to remove the player!");\r
+\r
+//\r
+// erase it from the refresh manager\r
+//\r
+       RF_RemoveSprite (&gone->sprite);\r
+\r
+//\r
+// fix the next object's back link\r
+//\r
+       if (gone == lastobj)\r
+               lastobj = (objtype *)gone->prev;\r
+       else\r
+               gone->next->prev = gone->prev;\r
+\r
+//\r
+// fix the previous object's forward link\r
+//\r
+       gone->prev->next = gone->next;\r
+\r
+//\r
+// add it back in to the free list\r
+//\r
+       gone->prev = objfreelist;\r
+       objfreelist = gone;\r
+}\r
+\r
+//===========================================================================\r
+\r
+\r
+void near HandleInfo (void)\r
+{\r
+       switch (maptile)\r
+       {\r
+       case 1:\r
+               SpawnKeen(mapx,mapy,1);\r
+               break;\r
+       case 2:\r
+               SpawnKeen(mapx,mapy,-1);\r
+               break;\r
+       case 19:\r
+               SpawnWorldKeen(mapx,mapy);\r
+               lumpneeded[WORLDKEENLUMP] = true;\r
+               break;\r
+\r
+       case 31:\r
+               bombspresent = true;\r
+       case 21:\r
+       case 22:\r
+       case 23:\r
+       case 24:\r
+       case 25:\r
+       case 26:\r
+       case 27:\r
+       case 28:\r
+       case 29:\r
+       case 30:\r
+       case 32:\r
+               SpawnBonus(mapx,mapy,maptile-21);\r
+               new->active = false;\r
+               break;\r
+       case 33:\r
+               SpawnDoor(mapx,mapy);\r
+               new->active = false;\r
+               break;\r
+       case 41:\r
+               SpawnBrocco(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[BROCCOLUMP] = true;\r
+               break;\r
+       case 42:\r
+               SpawnTomat(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[TOMATLUMP] = true;\r
+               break;\r
+       case 43:\r
+               SpawnCarrot(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[CARROTLUMP] = true;\r
+               break;\r
+       case 45:\r
+               SpawnAspar(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[ASPARLUMP] = true;\r
+               break;\r
+       case 46:\r
+               SpawnGrape(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[GRAPELUMP] = true;\r
+               break;\r
+       case 47:\r
+               SpawnTater(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[TATERLUMP] = true;\r
+               break;\r
+       case 48:\r
+               SpawnCart(mapx,mapy);\r
+               lumpneeded[CARTLUMP] = true;\r
+               break;\r
+       case 49:\r
+               SpawnFrenchy(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[FRENCHYLUMP] = true;\r
+               break;\r
+       case 50:\r
+       case 51:\r
+       case 52:\r
+               SpawnMelon(mapx,mapy,maptile-50);\r
+               new->active = false;\r
+               lumpneeded[MELONLUMP] = true;\r
+               break;\r
+       case 57:\r
+               SpawnSquasher(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[SQUASHLUMP] = true;\r
+               break;\r
+       case 58:\r
+               SpawnApel(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[APELLUMP] = true;\r
+               break;\r
+       case 59:\r
+               SpawnPeaPod(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[PEALUMP] = true;\r
+               break;\r
+       case 60:\r
+               SpawnPeaBrain(mapx,mapy);\r
+               new->active = false;\r
+               lumpneeded[PEALUMP] = true;\r
+               break;\r
+       case 61:\r
+               SpawnBoobus(mapx,mapy);\r
+               lumpneeded[BOOBUSLUMP] = true;\r
+               break;\r
+       }\r
+\r
+       if (new->active != allways)\r
+               new->active = false;\r
+}\r
+\r
+/*\r
+==========================\r
+=\r
+= ScanInfoPlane\r
+=\r
+= Spawn all actors and mark down special places\r
+=\r
+==========================\r
+*/\r
+\r
+void ScanInfoPlane (void)\r
+{\r
+       unsigned        x,y,i,j;\r
+       int                     tile;\r
+       unsigned        far     *start;\r
+\r
+       InitObjArray();                 // start spawning things with a clean slate\r
+\r
+       memset (lumpneeded,0,sizeof(lumpneeded));\r
+\r
+#if 0\r
+       start = mapsegs[2];\r
+       for (y=0;y<mapheight;y++)\r
+               for (x=0;x<mapwidth;x++)\r
+               {\r
+                       tile = *start++;\r
+                       if (!tile)\r
+                               continue;\r
+               }\r
+#endif\r
+\r
+//\r
+// This doesn't really need to be in asm.  I thought it was a bottleneck,\r
+// but I was wrong...\r
+//\r
+\r
+       asm     mov     es,[WORD PTR mapsegs+4]\r
+       asm     xor     si,si\r
+       asm     mov     [mapy],0\r
+       asm     mov     ax,[mapheight]\r
+       asm     mov     [mapycount],ax\r
+yloop:\r
+       asm     mov     [mapx],0\r
+       asm     mov     ax,[mapwidth]\r
+       asm     mov     [mapxcount],ax\r
+xloop:\r
+       asm     mov     ax,[es:si]\r
+       asm     or      ax,ax\r
+       asm     jz      nothing\r
+       asm     mov     [maptile],ax\r
+       HandleInfo ();                                          // si is saved\r
+       asm     mov     es,[WORD PTR mapsegs+4]\r
+nothing:\r
+       asm     inc     [mapx]\r
+       asm     add     si,2\r
+       asm     dec     [mapxcount]\r
+       asm     jnz     xloop\r
+       asm     inc     [mapy]\r
+       asm     dec     [mapycount]\r
+       asm     jnz     yloop\r
+\r
+       for (i=0;i<NUMLUMPS;i++)\r
+               if (lumpneeded[i])\r
+                       for (j=lumpstart[i];j<=lumpend[i];j++)\r
+                               CA_MarkGrChunk(j);\r
+}\r
+\r
+//===========================================================================\r
+\r
+\r
+/*\r
+==========================\r
+=\r
+= PatchWorldMap\r
+=\r
+= Takes out blocking squares and puts in dones\r
+=\r
+==========================\r
+*/\r
+\r
+void PatchWorldMap (void)\r
+{\r
+       unsigned        size,spot,info,foreground;\r
+\r
+       size = mapwidth*mapheight;\r
+       spot = 0;\r
+       do\r
+       {\r
+               info = *(mapsegs[2] + spot);\r
+               // finished a city here?\r
+               if (info>=3 && info<=18 && gamestate.leveldone[info-2])\r
+               {\r
+                       *(mapsegs[2] + spot) = 0;\r
+                       foreground = *(mapsegs[1] + spot);\r
+                       if (foreground == 130)\r
+                               *(mapsegs[1]+spot) = 0; // not blocking now\r
+                       else if (foreground == 90)\r
+                       {\r
+                       // plant done flag\r
+                               *(mapsegs[1]+spot) = 133;\r
+                               *(mapsegs[1]+(spot-mapwidth-1)) = 131;\r
+                               *(mapsegs[1]+(spot-mapwidth)) = 132;\r
+                       }\r
+               }\r
+               spot++;\r
+       } while (spot<size);\r
+}\r
+\r
+//===========================================================================\r
+\r
+/*\r
+==========================\r
+=\r
+= FadeAndUnhook\r
+=\r
+= Latch this onto the refresh so the screen only gets faded in after two\r
+= refreshes.  This lets all actors draw themselves to both pages before\r
+= fading the screen in.\r
+=\r
+==========================\r
+*/\r
+\r
+void FadeAndUnhook (void)\r
+{\r
+       if (++fadecount==2)\r
+       {\r
+               RF_ForceRefresh();\r
+               VW_FadeIn ();\r
+               RF_SetRefreshHook (NULL);\r
+               lasttimecount = TimeCount;      // don't adaptively time the fade\r
+       }\r
+}\r
+\r
+//===========================================================================\r
+\r
+\r
+/*\r
+==========================\r
+=\r
+= SetupGameLevel\r
+=\r
+= Load in map mapon and cache everything needed for it\r
+=\r
+==========================\r
+*/\r
+\r
+void   SetupGameLevel (boolean loadnow)\r
+{\r
+       long    orgx,orgy;\r
+\r
+       bombspresent = false;\r
+//\r
+// load the level header and three map planes\r
+//\r
+       CA_CacheMap (gamestate.mapon);\r
+\r
+//\r
+// let the refresh manager set up some variables\r
+//\r
+       RF_NewMap ();\r
+\r
+//\r
+// decide which graphics are needed and spawn actors\r
+//\r
+       CA_ClearMarks ();\r
+\r
+       if (!mapon)\r
+               PatchWorldMap ();\r
+\r
+       if (mapon!=20)                  // map 20 is the title screen\r
+               ScanInfoPlane ();\r
+       RF_MarkTileGraphics ();\r
+\r
+//\r
+// have the caching manager load and purge stuff to make sure all marks\r
+// are in memory\r
+//\r
+       if (loadnow)\r
+       {\r
+               if (bombspresent)\r
+               {\r
+                       VW_FixRefreshBuffer ();\r
+                       US_DrawWindow (10,1,20,2);\r
+                       US_PrintCentered ("Boobus Bombs Near!");\r
+                       VW_UpdateScreen ();\r
+               }\r
+               CA_CacheMarks (levelnames[mapon], 0);\r
+       }\r
+\r
+#if 0\r
+       VW_FixRefreshBuffer ();\r
+       US_CenterWindow (20,8);\r
+       US_Print ("\n\n\nObject count:");\r
+       itoa (objectcount,str,10);\r
+       US_Print (str);\r
+       VW_UpdateScreen ();\r
+       IN_Ack ();\r
+#endif\r
+\r
+       if (mapon!=20 && loadnow)                       // map 20 is the title screen\r
+       {\r
+               VW_FadeOut ();\r
+               fadecount = 0;\r
+               RF_SetRefreshHook (&FadeAndUnhook);\r
+               SpawnScore ();\r
+\r
+//\r
+// start the initial view position to center the player\r
+//\r
+               orgx = (long)player->x - (150<<G_P_SHIFT);\r
+               orgy = (long)player->y-(84<<G_P_SHIFT);\r
+               if (orgx<0)\r
+                       orgx=0;\r
+               if (orgy<0)\r
+                       orgy=0;\r
+\r
+               RF_NewPosition (orgx,orgy);\r
+               CalcInactivate ();\r
+       }\r
+\r
+\r
+}\r
+\r
+//==========================================================================\r
+\r
+/*\r
+===============\r
+=\r
+= ScrollScreen\r
+=\r
+= Scroll if Keen is nearing an edge\r
+= Set playstate to levelcomplete\r
+=\r
+===============\r
+*/\r
+\r
+void ScrollScreen (void)\r
+{\r
+       int     xscroll,yscroll;\r
+\r
+//\r
+// walked off edge of map?\r
+//\r
+       if (player->left < originxmin\r
+       || player->right > originxmax+20*TILEGLOBAL)\r
+       {\r
+               playstate = levelcomplete;\r
+               return;\r
+       }\r
+\r
+//\r
+// fallen off bottom of world?\r
+//\r
+       if (!plummet && player->bottom > originymax+13*TILEGLOBAL)\r
+       {\r
+               godmode = 0;\r
+               plummet = 1;\r
+               KillKeen ();\r
+               return;\r
+       }\r
+\r
+       if (player->x < originxglobal+SCROLLWEST)\r
+               xscroll = player->x - (originxglobal+SCROLLWEST);\r
+       else if (player->x > originxglobal+SCROLLEAST)\r
+               xscroll = player->x - (originxglobal+SCROLLEAST);\r
+       else\r
+               xscroll = 0;\r
+\r
+       if (player->y < originyglobal+SCROLLNORTH)\r
+               yscroll = player->y - (originyglobal+SCROLLNORTH);\r
+       else if (player->y > originyglobal+SCROLLSOUTH)\r
+               yscroll = player->y - (originyglobal+SCROLLSOUTH);\r
+       else yscroll = 0;\r
+\r
+       if (xscroll || yscroll)\r
+       {\r
+               RF_Scroll (xscroll,yscroll);\r
+               CalcInactivate ();\r
+               scoreobj->needtoreact = true;\r
+       }\r
+}\r
+\r
+//==========================================================================\r
+\r
+/*\r
+====================\r
+=\r
+= GivePoints\r
+=\r
+= Grants extra men at 20k,40k,80k,160k,320k\r
+=\r
+====================\r
+*/\r
+\r
+void GivePoints (unsigned points)\r
+{\r
+       gamestate.score += points;\r
+       if (gamestate.score >= gamestate.nextextra)\r
+       {\r
+               SD_PlaySound (EXTRAKEENSND);\r
+               gamestate.lives++;\r
+               gamestate.nextextra*=2;\r
+       }\r
+}\r
+\r
+\r
+//==========================================================================\r
+\r
+/*\r
+====================\r
+=\r
+= MoveObjVert\r
+=\r
+====================\r
+*/\r
+\r
+void MoveObjVert (objtype *ob, int ymove)\r
+{\r
+       ob->y += ymove;\r
+       ob->top += ymove;\r
+       ob->bottom += ymove;\r
+       ob->tiletop = ob->top >> G_T_SHIFT;\r
+       ob->tilebottom = ob->bottom >> G_T_SHIFT;\r
+}\r
+\r
+\r
+/*\r
+====================\r
+=\r
+= MoveObjHoriz\r
+=\r
+====================\r
+*/\r
+\r
+void MoveObjHoriz (objtype *ob, int xmove)\r
+{\r
+       ob->x += xmove;\r
+       ob->left += xmove;\r
+       ob->right += xmove;\r
+       ob->tileleft = ob->left >> G_T_SHIFT;\r
+       ob->tileright = ob->right >> G_T_SHIFT;\r
+}\r
+\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                       Actor to tile clipping rouitnes\r
+\r
+=============================================================================\r
+*/\r
+\r
+// walltype / x coordinate (0-15)\r
+\r
+int    wallclip[8][16] = {                     // the height of a given point in a tile\r
+{ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256},\r
+{   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0},\r
+{   0,0x08,0x10,0x18,0x20,0x28,0x30,0x38,0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78},\r
+{0x80,0x88,0x90,0x98,0xa0,0xa8,0xb0,0xb8,0xc0,0xc8,0xd0,0xd8,0xe0,0xe8,0xf0,0xf8},\r
+{   0,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0},\r
+{0x78,0x70,0x68,0x60,0x58,0x50,0x48,0x40,0x38,0x30,0x28,0x20,0x18,0x10,0x08,   0},\r
+{0xf8,0xf0,0xe8,0xe0,0xd8,0xd0,0xc8,0xc0,0xb8,0xb0,0xa8,0xa0,0x98,0x90,0x88,0x80},\r
+{0xf0,0xe0,0xd0,0xc0,0xb0,0xa0,0x90,0x80,0x70,0x60,0x50,0x40,0x30,0x20,0x10,   0}\r
+};\r
+\r
+// assignment within ifs are used heavily here, so turn off the warning\r
+#pragma warn -pia\r
+\r
+/*\r
+===========================\r
+=\r
+= ClipToEnds\r
+=\r
+===========================\r
+*/\r
+\r
+void ClipToEnds (objtype *ob)\r
+{\r
+       unsigned        far *map,tile,facetile,info,wall;\r
+       int     leftpix,rightpix,midtiles,toppix,bottompix;\r
+       int     x,y,clip,move,totalmove,maxmove,midxpix;\r
+\r
+       midxpix = (ob->midx&0xf0) >> 4;\r
+\r
+       maxmove = -abs(midxmoved) - bottommoved - 16;\r
+       map = (unsigned far *)mapsegs[1] +\r
+               mapbwidthtable[oldtilebottom-1]/2 + ob->tilemidx;\r
+       for (y=oldtilebottom-1 ; y<=ob->tilebottom ; y++,map+=mapwidth)\r
+       {\r
+               if (wall = tinf[NORTHWALL+*map])\r
+               {\r
+                       clip = wallclip[wall&7][midxpix];\r
+                       move = ( (y<<G_T_SHIFT)+clip - 1) - ob->bottom;\r
+                       if (move<0 && move>=maxmove)\r
+                       {\r
+                               ob->hitnorth = wall;\r
+                               MoveObjVert (ob,move);\r
+                               return;\r
+                       }\r
+               }\r
+       }\r
+\r
+       maxmove = abs(midxmoved) - topmoved + 16;\r
+       map = (unsigned far *)mapsegs[1] +\r
+               mapbwidthtable[oldtiletop+1]/2 + ob->tilemidx;\r
+       for (y=oldtiletop+1 ; y>=ob->tiletop ; y--,map-=mapwidth)\r
+       {\r
+               if (wall = tinf[SOUTHWALL+*map])\r
+               {\r
+                       clip = wallclip[wall&7][midxpix];\r
+                       move = ( ((y+1)<<G_T_SHIFT)-clip ) - ob->top;\r
+                       if (move > 0 && move<=maxmove)\r
+                       {\r
+                               totalmove = ob->ymove + move;\r
+                               if (totalmove < TILEGLOBAL && totalmove > -TILEGLOBAL)\r
+                               {\r
+                                       ob->hitsouth = wall;\r
+                                       MoveObjVert (ob,move);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+\r
+/*\r
+===========================\r
+=\r
+= ClipToEastWalls / ClipToWestWalls\r
+=\r
+===========================\r
+*/\r
+\r
+void ClipToEastWalls (objtype *ob)\r
+{\r
+       int                     y,move,top,bottom;\r
+       unsigned        far *map,tile,info,wall;\r
+\r
+       // clip to east walls if moving west\r
+\r
+       top = ob->tiletop;\r
+       if (ob->hitsouth>1)\r
+               top++;                  // on a slope inside a tile\r
+       bottom = ob->tilebottom;\r
+       if (ob->hitnorth>1)\r
+               bottom--;                       // on a slope inside a tile\r
+\r
+       for (y=top;y<=bottom;y++)\r
+       {\r
+               map = (unsigned far *)mapsegs[1] +\r
+                       mapbwidthtable[y]/2 + ob->tileleft;\r
+\r
+               if (ob->hiteast = tinf[EASTWALL+*map])\r
+               {\r
+                       move = ( (ob->tileleft+1)<<G_T_SHIFT ) - ob->left;\r
+                       MoveObjHoriz (ob,move);\r
+                       return;\r
+               }\r
+       }\r
+}\r
+\r
+\r
+void ClipToWestWalls (objtype *ob)\r
+{\r
+       int                     y,move,top,bottom;\r
+       unsigned        far *map,tile,info,wall;\r
+\r
+       // check west walls if moving east\r
+\r
+       top = ob->tiletop;\r
+       if (ob->hitsouth>1)\r
+               top++;                  // on a slope inside a tile\r
+       bottom = ob->tilebottom;\r
+       if (ob->hitnorth>1)\r
+               bottom--;                       // on a slope inside a tile\r
+\r
+       for (y=top;y<=bottom;y++)\r
+       {\r
+               map = (unsigned far *)mapsegs[1] +\r
+                       mapbwidthtable[y]/2 + ob->tileright;\r
+\r
+               if (ob->hitwest = tinf[WESTWALL+*map])\r
+               {\r
+                       move = ( (ob->tileright<<G_T_SHIFT ) -1) - ob->right;\r
+                       MoveObjHoriz (ob,move);\r
+                       return;\r
+               }\r
+       }\r
+}\r
+\r
+// turn 'possibly incorrect assignment' warnings back on\r
+#pragma warn +pia\r
+\r
+\r
+//==========================================================================\r
+\r
+/*\r
+================\r
+=\r
+= ClipToWalls\r
+=\r
+= Moves the current object xmove/ymove units, clipping to walls\r
+=\r
+================\r
+*/\r
+\r
+void ClipToWalls (objtype *ob)\r
+{\r
+       unsigned        x,y,tile;\r
+       spritetabletype far *shape;\r
+       boolean endfirst;\r
+\r
+//\r
+// make sure it stays in contact with a 45 degree slope\r
+//\r
+       if (ob->state->pushtofloor)\r
+       {\r
+               if (ob->xmove > 0)\r
+                       ob->ymove = ob->xmove + 16;\r
+               else\r
+                       ob->ymove = -ob->xmove + 16;\r
+       }\r
+\r
+//\r
+// move the shape\r
+//\r
+       if (ob->xmove > MAXMOVE)\r
+               ob->xmove = MAXMOVE;\r
+       else if (ob->xmove < -MAXMOVE)\r
+               ob->xmove = -MAXMOVE;\r
+\r
+       if (ob->ymove > MAXMOVE+16)                     // +16 for push to floor\r
+               ob->ymove = MAXMOVE+16;\r
+       else if (ob->ymove < -MAXMOVE)\r
+               ob->ymove = -MAXMOVE;\r
+\r
+       ob->x += ob->xmove;\r
+       ob->y += ob->ymove;\r
+\r
+       ob->needtoreact = true;\r
+\r
+       if (!ob->shapenum)                              // can't get a hit rect with no shape!\r
+               return;\r
+\r
+       shape = &spritetable[ob->shapenum-STARTSPRITES];\r
+\r
+       oldtileright = ob->tileright;\r
+       oldtiletop = ob->tiletop;\r
+       oldtileleft = ob->tileleft;\r
+       oldtilebottom = ob->tilebottom;\r
+       oldtilemidx = ob->tilemidx;\r
+\r
+       oldright = ob->right;\r
+       oldtop = ob->top;\r
+       oldleft = ob->left;\r
+       oldbottom = ob->bottom;\r
+       oldmidx = ob->midx;\r
+\r
+       ob->left = ob->x + shape->xl;\r
+       ob->right = ob->x + shape->xh;\r
+       ob->top = ob->y + shape->yl;\r
+       ob->bottom = ob->y + shape->yh;\r
+       ob->midx = ob->left + (ob->right - ob->left)/2;\r
+\r
+       ob->tileleft = ob->left >> G_T_SHIFT;\r
+       ob->tileright = ob->right >> G_T_SHIFT;\r
+       ob->tiletop = ob->top >> G_T_SHIFT;\r
+       ob->tilebottom = ob->bottom >> G_T_SHIFT;\r
+       ob->tilemidx = ob->midx >> G_T_SHIFT;\r
+\r
+       ob->hitnorth = ob->hiteast = ob->hitsouth = ob->hitwest = 0;\r
+\r
+       if (!ob->needtoclip)\r
+               return;\r
+\r
+       leftmoved = ob->left - oldleft;\r
+       rightmoved = ob->right - oldright;\r
+       topmoved = ob->top - oldtop;\r
+       bottommoved = ob->bottom - oldbottom;\r
+       midxmoved = ob->midx - oldmidx;\r
+\r
+//\r
+// clip it\r
+//\r
+\r
+       ClipToEnds(ob);\r
+\r
+       if (leftmoved < 0 || ob == player)      // make sure player gets cliped\r
+               ClipToEastWalls (ob);\r
+       if (rightmoved > 0 || ob == player)\r
+               ClipToWestWalls (ob);\r
+}\r
+\r
+//==========================================================================\r
+\r
+\r
+/*\r
+==================\r
+=\r
+= ClipToSpriteSide\r
+=\r
+= Clips push to solid\r
+=\r
+==================\r
+*/\r
+\r
+void ClipToSpriteSide (objtype *push, objtype *solid)\r
+{\r
+       int xmove,leftinto,rightinto;\r
+\r
+       //\r
+       // amount the push shape can be pushed\r
+       //\r
+       xmove = solid->xmove - push->xmove;\r
+\r
+       //\r
+       // amount it is inside\r
+       //\r
+       leftinto = solid->right - push->left;\r
+       rightinto = push->right - solid->left;\r
+\r
+       if (leftinto>0 && leftinto<= xmove)\r
+       {\r
+               push->xmove = leftinto;\r
+               if (push->state->pushtofloor)\r
+                       push->ymove = leftinto+16;\r
+               ClipToWalls (push);\r
+               push->hiteast = 1;\r
+               return;\r
+       }\r
+\r
+       if (rightinto>0 && rightinto<= -xmove)\r
+       {\r
+               push->xmove = -rightinto;\r
+               if (push->state->pushtofloor)\r
+                       push->ymove = rightinto+16;\r
+               ClipToWalls (push);\r
+               push->hitwest = 1;\r
+               return;\r
+       }\r
+\r
+}\r
+\r
+//==========================================================================\r
+\r
+\r
+/*\r
+==================\r
+=\r
+= ClipToSprite\r
+=\r
+= Clips push to solid\r
+=\r
+==================\r
+*/\r
+\r
+void ClipToSprite (objtype *push, objtype *solid, boolean squish)\r
+{\r
+       boolean temp;\r
+       int walltemp,xmove,leftinto,rightinto,topinto,bottominto;\r
+\r
+       xmove = solid->xmove - push->xmove;\r
+\r
+       push->xmove = push->ymove = 0;\r
+\r
+       //\r
+       // left / right\r
+       //\r
+       leftinto = solid->right - push->left;\r
+       rightinto = push->right - solid->left;\r
+\r
+       if (leftinto>0 && leftinto<=xmove)\r
+       {\r
+               push->xmove = leftinto;\r
+               walltemp = push->hitnorth;\r
+               ClipToWalls (push);\r
+               if (!push->hitnorth)\r
+                       push->hitnorth = walltemp;\r
+               if (squish && push->hitwest)\r
+                       KillKeen ();\r
+               push->hiteast = 1;\r
+               return;\r
+       }\r
+       else if (rightinto>0 && rightinto<=-xmove)\r
+       {\r
+               push->xmove = -rightinto;\r
+               walltemp = push->hitnorth;\r
+               ClipToWalls (push);\r
+               if (!push->hitnorth)\r
+                       push->hitnorth = walltemp;\r
+               if (squish && push->hiteast)\r
+                       KillKeen ();\r
+               push->hitwest = 1;\r
+               return;\r
+       }\r
+\r
+       //\r
+       // top / bottom\r
+       //\r
+       topinto = solid->bottom - push->top;\r
+       bottominto = push->bottom - solid->top;\r
+\r
+       if (bottominto>0)\r
+       {\r
+               push->ymove = -bottominto+16;\r
+               push->xmove = solid->xmove;\r
+               temp = push->state->pushtofloor;\r
+               push->state->pushtofloor = false;\r
+               walltemp = push->hitnorth;\r
+               ClipToWalls (push);\r
+               if (!push->hitnorth)\r
+                       push->hitnorth = walltemp;\r
+               push->state->pushtofloor = temp;\r
+               push->hitnorth = 25;\r
+       }\r
+       else if (topinto>0)\r
+       {\r
+               push->ymove = topinto;\r
+               ClipToWalls (push);\r
+               push->hitsouth = 25;\r
+       }\r
+}\r
+\r
+//==========================================================================\r
+\r
+\r
+/*\r
+==================\r
+=\r
+= DoActor\r
+=\r
+= Moves an actor in its current state by a given number of tics.\r
+= If that time takes it into the next state, it changes the state\r
+= and returns the number of excess tics after the state change\r
+=\r
+==================\r
+*/\r
+\r
+int DoActor (objtype *ob,int tics)\r
+{\r
+       int     newtics,movetics,excesstics;\r
+       statetype *state;\r
+\r
+       state = ob->state;\r
+\r
+       if (state->progress == think)\r
+       {\r
+               if (state->think)\r
+               {\r
+                       if (ob->nothink)\r
+                               ob->nothink--;\r
+                       else\r
+#pragma warn -pro\r
+                               state->think(ob);\r
+#pragma warn +pro\r
+               }\r
+               return 0;\r
+       }\r
+\r
+       newtics = ob->ticcount+tics;\r
+\r
+       if (newtics < state->tictime || state->tictime == 0)\r
+       {\r
+               ob->ticcount = newtics;\r
+               if (state->progress == slide || state->progress == slidethink)\r
+               {\r
+                       if (ob->xdir)\r
+                               ob->xmove += ob->xdir == 1 ? tics*state->xmove\r
+                               : -tics*state->xmove;\r
+                       if (ob->ydir)\r
+                               ob->ymove += ob->ydir == 1 ? tics*state->ymove\r
+                               : -tics*state->ymove;\r
+               }\r
+               if (state->progress == slidethink || state->progress == stepthink)\r
+               {\r
+                       if (state->think)\r
+                       {\r
+                               if (ob->nothink)\r
+                                       ob->nothink--;\r
+                               else\r
+#pragma warn -pro\r
+                                       state->think(ob);\r
+#pragma warn +pro\r
+                       }\r
+               }\r
+               return 0;\r
+       }\r
+       else\r
+       {\r
+               movetics = state->tictime - ob->ticcount;\r
+               excesstics = newtics - state->tictime;\r
+               ob->ticcount = 0;\r
+               if (state->progress == slide || state->progress == slidethink)\r
+               {\r
+                       if (ob->xdir)\r
+                               ob->xmove += ob->xdir == 1 ? movetics*state->xmove\r
+                               : -movetics*state->xmove;\r
+                       if (ob->ydir)\r
+                               ob->ymove += ob->ydir == 1 ? movetics*state->ymove\r
+                               : -movetics*state->ymove;\r
+               }\r
+               else\r
+               {\r
+                       if (ob->xdir)\r
+                               ob->xmove += ob->xdir == 1 ? state->xmove : -state->xmove;\r
+                       if (ob->ydir)\r
+                               ob->ymove += ob->ydir == 1 ? state->ymove : -state->ymove;\r
+               }\r
+\r
+               if (state->think)\r
+               {\r
+                       if (ob->nothink)\r
+                               ob->nothink--;\r
+                       else\r
+#pragma warn -pro\r
+                               state->think(ob);\r
+#pragma warn +pro\r
+               }\r
+\r
+               if (ob->state == state)\r
+                       ob->state = state->nextstate;   // go to next state\r
+               else if (!ob->state)\r
+                       return 0;                       // object removed itself\r
+               return excesstics;\r
+       }\r
+}\r
+\r
+//==========================================================================\r
+\r
+\r
+/*\r
+====================\r
+=\r
+= StateMachine\r
+=\r
+= Change state and give directions\r
+=\r
+====================\r
+*/\r
+\r
+void StateMachine (objtype *ob)\r
+{\r
+       int excesstics,oldshapenum;\r
+       statetype *state;\r
+\r
+       ob->xmove = ob->ymove = 0;\r
+       oldshapenum = ob->shapenum;\r
+\r
+       state = ob->state;\r
+\r
+       excesstics = DoActor(ob,tics);\r
+       if (ob->state != state)\r
+       {\r
+               ob->ticcount = 0;               // start the new state at 0, then use excess\r
+               state = ob->state;\r
+       }\r
+\r
+       while (excesstics)\r
+       {\r
+       //\r
+       // passed through to next state\r
+       //\r
+               if (!state->skippable && excesstics >= state->tictime)\r
+                       excesstics = DoActor(ob,state->tictime-1);\r
+               else\r
+                       excesstics = DoActor(ob,excesstics);\r
+               if (ob->state != state)\r
+               {\r
+                       ob->ticcount = 0;               // start the new state at 0, then use excess\r
+                       state = ob->state;\r
+               }\r
+       }\r
+\r
+       if (!state)                     // object removed itself\r
+       {\r
+               RemoveObj (ob);\r
+               return;\r
+       }\r
+\r
+\r
+       //\r
+       // if state->rightshapenum == NULL, the state does not have a standard\r
+       // shape (the think routine should have set it)\r
+       //\r
+       if (state->rightshapenum)\r
+       {\r
+               if (ob->xdir>0)\r
+                       ob->shapenum = state->rightshapenum;\r
+               else\r
+                       ob->shapenum = state->leftshapenum;\r
+       }\r
+       if (ob->shapenum == (unsigned)-1)\r
+               ob->shapenum = 0;               // make it invisable this time\r
+\r
+       if (ob->xmove || ob->ymove || ob->shapenum != oldshapenum)\r
+       {\r
+       //\r
+       // actor moved or changed shape\r
+       // make sure the movement is within limits (one tile)\r
+       //\r
+               ClipToWalls (ob);\r
+       }\r
+}\r
+\r
+//==========================================================================\r
+\r
+\r
+/*\r
+====================\r
+=\r
+= NewState\r
+=\r
+====================\r
+*/\r
+\r
+void NewState (objtype *ob,statetype *state)\r
+{\r
+       boolean temp;\r
+\r
+       ob->state = state;\r
+\r
+       if (state->rightshapenum)\r
+       {\r
+               if (ob->xdir>0)\r
+                       ob->shapenum = state->rightshapenum;\r
+               else\r
+                       ob->shapenum = state->leftshapenum;\r
+       }\r
+\r
+       temp = ob->needtoclip;\r
+\r
+       ob->needtoclip = false;\r
+\r
+       ClipToWalls (ob);                                       // just calculate values\r
+\r
+       ob->needtoclip = temp;\r
+\r
+       if (ob->needtoclip)\r
+               ClipToWalls (ob);\r
+\r
+}\r
+\r
+//==========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= PlayLoop\r
+=\r
+============================\r
+*/\r
+\r
+void PlayLoop (void)\r
+{\r
+       objtype *obj, *check;\r
+       long    newtime;\r
+\r
+       button0held = button1held = false;\r
+\r
+       ingame = true;\r
+       playstate = 0;\r
+       plummet = 0;\r
+\r
+       FixScoreBox ();                                 // draw bomb/flower\r
+\r
+       do\r
+       {\r
+               CalcSingleGravity ();\r
+               IN_ReadControl(0,&c);           // get player input\r
+               if (!c.button0)\r
+                       button0held = 0;\r
+               if (!c.button1)\r
+                       button1held = 0;\r
+\r
+//\r
+// go through state changes and propose movements\r
+//\r
+               obj = player;\r
+               do\r
+               {\r
+                       if (!obj->active\r
+                       && obj->tileright >= originxtile\r
+                       && obj->tileleft <= originxtilemax\r
+                       && obj->tiletop <= originytilemax\r
+                       && obj->tilebottom >= originytile)\r
+                       {\r
+                               obj->needtoreact = true;\r
+                               obj->active = yes;\r
+                       }\r
+\r
+                       if (obj->active)\r
+                               StateMachine(obj);\r
+\r
+                       if ( (obj->active == true || obj->active == removable) &&\r
+                       (  obj->tileright < inactivateleft\r
+                       || obj->tileleft > inactivateright\r
+                       || obj->tiletop > inactivatebottom\r
+                       || obj->tilebottom < inactivatetop) )\r
+                       {\r
+                               if (obj->active == removable)\r
+                                       RemoveObj (obj);                                // temp thing (shots, etc)\r
+                               else\r
+                               {\r
+                                       if (US_RndT()<tics)                             // let them get a random dist\r
+                                       {\r
+                                               RF_RemoveSprite (&obj->sprite);\r
+                                               obj->active = no;\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       obj = (objtype *)obj->next;\r
+               } while (obj);\r
+\r
+//\r
+// check for and handle collisions between objects\r
+//\r
+               obj = player;\r
+               do\r
+               {\r
+                       if (obj->active)\r
+                       {\r
+                               check = (objtype *)obj->next;\r
+                               while (check)\r
+                               {\r
+                                       if ( check->active\r
+                                       && obj->right > check->left\r
+                                       && obj->left < check->right\r
+                                       && obj->top < check->bottom\r
+                                       && obj->bottom > check->top)\r
+                                       {\r
+#pragma warn -pro\r
+                                               if (obj->state->contact)\r
+                                                       obj->state->contact(obj,check);\r
+                                               if (check->state->contact)\r
+                                                       check->state->contact(check,obj);\r
+#pragma warn +pro\r
+                                               if (!obj->obclass)\r
+                                                       break;                          // contact removed object\r
+                                       }\r
+                                       check = (objtype *)check->next;\r
+                               }\r
+                       }\r
+                       obj = (objtype *)obj->next;\r
+               } while (obj);\r
+\r
+\r
+               ScrollScreen();\r
+\r
+//\r
+// react to whatever happened, and post sprites to the refresh manager\r
+//\r
+               obj = player;\r
+               do\r
+               {\r
+                       if (obj->needtoreact && obj->state->react)\r
+                       {\r
+                               obj->needtoreact = false;\r
+#pragma warn -pro\r
+                               obj->state->react(obj);\r
+#pragma warn +pro\r
+                       }\r
+                       obj = (objtype *)obj->next;\r
+               } while (obj);\r
+\r
+\r
+//\r
+// update the screen and calculate the number of tics it took to execute\r
+// this cycle of events (for adaptive timing of next cycle)\r
+//\r
+               RF_Refresh();\r
+\r
+//\r
+// single step debug mode\r
+//\r
+               if (singlestep)\r
+               {\r
+                       VW_WaitVBL(14);\r
+                       lasttimecount = TimeCount;\r
+               }\r
+\r
+               CheckKeys();\r
+       } while (!loadedgame && !playstate);\r
+\r
+       ingame = false;\r
+}\r
+\r
+\r
+//==========================================================================\r
+\r
+/*\r
+==========================\r
+=\r
+= GameFinale\r
+=\r
+==========================\r
+*/\r
+\r
+void GameFinale (void)\r
+{\r
+struct date d;\r
+\r
+       VW_FixRefreshBuffer ();\r
+\r
+/* screen 1 of finale text (16 lines) */\r
+       US_CenterWindow (30,21);\r
+       PrintY += 4;\r
+       US_CPrint (\r
+"Yes!  Boobus Tuber's hash-brown-\n"\r
+"like remains rained down from\n"\r
+"the skies as Commander Keen\n"\r
+"walked up to the Dream Machine.\n"\r
+"He analyzed all the complex\n"\r
+"controls and readouts on it, then\n"\r
+"pulled down a huge red lever\n"\r
+"marked \"On/Off Switch.\"  The\n"\r
+"machine clanked and rattled,\n"\r
+"then went silent. He had freed\n"\r
+"all the children from their\n"\r
+"vegetable-enforced slavery!\n"\r
+"Everything around Keen wobbled\n"\r
+"in a disconcerting manner, his\n"\r
+"eyelids grew heavy, and he\n"\r
+"fell asleep....\n"\r
+       );\r
+       VW_UpdateScreen();\r
+       VW_WaitVBL(60);\r
+       SD_WaitSoundDone ();\r
+       IN_ClearKeysDown ();\r
+       IN_Ack();\r
+\r
+/* screen 2 of finale (15 lines) */\r
+       US_CenterWindow (30,21);\r
+       PrintY += 9;\r
+       US_CPrint (\r
+"Billy woke up, looking around the\n"\r
+"room, the early morning sun\n"\r
+"shining in his face.  Nothing.\n"\r
+"No vegetables to be seen.  Was it\n"\r
+"all just a dream?\n\n"\r
+"Billy's mom entered the room.\n\n"\r
+"\"Good morning, dear. I heard some\n"\r
+"news on TV that you'd be\n"\r
+"interested in,\" she said, sitting\n"\r
+"by him on the bed.\n\n"\r
+"\"What news?\" Billy asked,\n"\r
+"still groggy.\n\n"\r
+       );\r
+       VW_UpdateScreen();\r
+       VW_WaitVBL(60);\r
+       IN_ClearKeysDown ();\r
+       IN_Ack();\r
+\r
+/* screen 3 of finale (12 lines)*/\r
+       US_CenterWindow (30,21);\r
+       PrintY += 23;\r
+       US_CPrint (\r
+"\"The President declared today\n"\r
+"National 'I Hate Broccoli' Day.\n"\r
+"He said kids are allowed to pick\n"\r
+"one vegetable today, and they\n"\r
+"don't have to eat it.\"\n\n"\r
+"\"Aw, mom, I'm not afraid of any\n"\r
+"stupid vegetables,\" Billy said.\n"\r
+"\"But if it's okay with you, I'd\n"\r
+"rather not have any french fries\n"\r
+"for awhile.\"\n\n"\r
+"THE END"\r
+       );\r
+       VW_UpdateScreen();\r
+       VW_WaitVBL(60);\r
+       IN_ClearKeysDown ();\r
+       IN_Ack();\r
+\r
+}\r
+\r
+//==========================================================================\r
+\r
+/*\r
+==========================\r
+=\r
+= HandleDeath\r
+=\r
+==========================\r
+*/\r
+\r
+void HandleDeath (void)\r
+{\r
+       unsigned        top,bottom,selection,y,color;\r
+\r
+       gamestate.keys = 0;\r
+       gamestate.boobusbombs -= gamestate.bombsthislevel;\r
+       gamestate.lives--;\r
+       if (gamestate.lives < 0)\r
+               return;\r
+\r
+       VW_FixRefreshBuffer ();\r
+       US_CenterWindow (20,8);\r
+       PrintY += 4;\r
+       US_CPrint ("You didn't make it past");\r
+       US_CPrint (levelnames[mapon]);\r
+       PrintY += 8;\r
+       top = PrintY-2;\r
+       US_CPrint ("Try Again");\r
+       PrintY += 4;\r
+       bottom = PrintY-2;\r
+       US_CPrint ("Exit to Tuberia");\r
+\r
+       selection = 0;\r
+       do\r
+       {\r
+               if (selection)\r
+                       y = bottom;\r
+               else\r
+                       y = top;\r
+\r
+// draw select bar\r
+               if ( (TimeCount / 16)&1 )\r
+                       color = SECONDCOLOR;\r
+               else\r
+                       color = FIRSTCOLOR;\r
+\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y,color);\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+1,color);\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+12,color);\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+13,color);\r
+               VWB_Vlin (y+1,y+11, WindowX+4,color);\r
+               VWB_Vlin (y+1,y+11, WindowX+5,color);\r
+               VWB_Vlin (y+1,y+11, WindowX+WindowW-4,color);\r
+               VWB_Vlin (y+1,y+11, WindowX+WindowW-5,color);\r
+\r
+               VW_UpdateScreen ();\r
+\r
+// erase select bar\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y,WHITE);\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+1,WHITE);\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+12,WHITE);\r
+               VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+13,WHITE);\r
+               VWB_Vlin (y+1,y+11, WindowX+4,WHITE);\r
+               VWB_Vlin (y+1,y+11, WindowX+5,WHITE);\r
+               VWB_Vlin (y+1,y+11, WindowX+WindowW-4,WHITE);\r
+               VWB_Vlin (y+1,y+11, WindowX+WindowW-5,WHITE);\r
+\r
+               if (LastScan == sc_Escape)\r
+               {\r
+                       gamestate.mapon = 0;            // exit to tuberia\r
+                       IN_ClearKeysDown ();\r
+                       return;\r
+               }\r
+\r
+               IN_ReadControl(0,&c);           // get player input\r
+               if (c.button0 || c.button1 || LastScan == sc_Return\r
+               || LastScan == sc_Space)\r
+               {\r
+                       if (selection)\r
+                               gamestate.mapon = 0;            // exit to tuberia\r
+                       return;\r
+               }\r
+               if (c.yaxis == -1 || LastScan == sc_UpArrow)\r
+                       selection = 0;\r
+               else if (c.yaxis == 1 || LastScan == sc_DownArrow)\r
+                       selection = 1;\r
+       } while (1);\r
+\r
+}\r
+\r
+//==========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= GameLoop\r
+=\r
+= A game has just started (after the cinematic or load game)\r
+=\r
+============================\r
+*/\r
+\r
+void GameLoop (void)\r
+{\r
+       unsigned        cities,i;\r
+       long    orgx,orgy;\r
+\r
+       gamestate.difficulty = restartgame;\r
+       restartgame = gd_Continue;\r
+\r
+       do\r
+       {\r
+startlevel:\r
+               if (loadedgame)\r
+               {\r
+                       loadedgame = false;\r
+                       //\r
+                       // start the initial view position to center the player\r
+                       //\r
+                       orgx = (long)player->x - (150<<G_P_SHIFT);\r
+                       orgy = (long)player->y-(84<<G_P_SHIFT);\r
+                       if (orgx<0)\r
+                               orgx=0;\r
+                       if (orgy<0)\r
+                               orgy=0;\r
+\r
+                       VW_FadeOut ();\r
+                       fadecount = 0;\r
+                       RF_SetRefreshHook (&FadeAndUnhook);\r
+                       RF_NewPosition (orgx,orgy);\r
+                       CalcInactivate ();\r
+               }\r
+               else\r
+               {\r
+                       VW_FixRefreshBuffer ();\r
+                       US_CenterWindow (20,8);\r
+                       US_CPrint ("Loading");\r
+                       VW_UpdateScreen ();\r
+                       gamestate.bombsthislevel = 0;\r
+                       SetupGameLevel (true);\r
+               }\r
+\r
+\r
+               PlayLoop ();\r
+\r
+#if FRILLS\r
+               if (tedlevel)\r
+               {\r
+                       if (playstate == died)\r
+                               goto startlevel;\r
+                       else\r
+                               TEDDeath ();\r
+               }\r
+#endif\r
+\r
+               if (loadedgame)\r
+                       goto startlevel;\r
+\r
+               switch (playstate)\r
+               {\r
+               case warptolevel:\r
+                       goto startlevel;\r
+\r
+               case died:\r
+                       HandleDeath ();\r
+                       break;\r
+\r
+               case levelcomplete:\r
+                       if (mapon)\r
+                               SD_PlaySound (LEVELDONESND);\r
+                       gamestate.leveldone[mapon] = true;      // finished the level\r
+                       if (mapon != 0)\r
+                               gamestate.mapon = 0;\r
+                       break;\r
+\r
+               case resetgame:\r
+                       return;\r
+\r
+               case victorious:\r
+                       GameFinale ();\r
+                       goto done;\r
+               }\r
+\r
+\r
+       } while (gamestate.lives>-1 && playstate!=victorious);\r
+\r
+       GameOver ();\r
+\r
+done:\r
+       cities = 0;\r
+       for (i= 1; i<=16; i++)\r
+               if (gamestate.leveldone[i])\r
+                       cities++;\r
+       US_CheckHighScore (gamestate.score,cities);\r
+       VW_ClearVideo (FIRSTCOLOR);\r
+}\r
+\r