--- /dev/null
+/* 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