1 /* Keen Dreams Source Code
\r
2 * Copyright (C) 2014 Javier M. Chavez
\r
4 * This program is free software; you can redistribute it and/or modify
\r
5 * it under the terms of the GNU General Public License as published by
\r
6 * the Free Software Foundation; either version 2 of the License, or
\r
7 * (at your option) any later version.
\r
9 * This program is distributed in the hope that it will be useful,
\r
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
12 * GNU General Public License for more details.
\r
14 * You should have received a copy of the GNU General Public License along
\r
15 * with this program; if not, write to the Free Software Foundation, Inc.,
\r
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
25 =============================================================================
\r
29 =============================================================================
\r
32 #define INACTIVATEDIST 10
\r
34 #define MAXMOVE (TILEGLOBAL-17)
\r
39 #define CONTROLSLUMP 0
\r
41 #define WORLDKEENLUMP 2
\r
42 #define BROCCOLUMP 3
\r
44 #define CARROTLUMP 5
\r
49 #define FRENCHYLUMP 10
\r
50 #define MELONLUMP 11
\r
51 #define SQUASHLUMP 12
\r
54 #define BOOBUSLUMP 15
\r
57 =============================================================================
\r
61 =============================================================================
\r
67 boolean button0held,button1held;
\r
68 objtype *new,*check,*player,*scoreobj;
\r
70 unsigned originxtilemax,originytilemax;
\r
76 char *levelnames[21] =
\r
78 "The Land of Tuberia",
\r
90 "Brussels Sprout Bay",
\r
101 =============================================================================
\r
105 =============================================================================
\r
108 // for asm scaning of map planes
\r
109 unsigned mapx,mapy,mapxcount,mapycount,maptile,mapspot;
\r
115 objtype objarray[MAXACTORS],*lastobj,*objfreelist;
\r
117 int oldtileleft,oldtiletop,oldtileright,oldtilebottom,oldtilemidx;
\r
118 int oldleft,oldtop,oldright,oldbottom,oldmidx;
\r
119 int leftmoved,topmoved,rightmoved,bottommoved,midxmoved;
\r
121 int topmove,bottommove,midxmove;
\r
123 int inactivateleft,inactivateright,inactivatetop,inactivatebottom;
\r
127 boolean bombspresent;
\r
129 boolean lumpneeded[NUMLUMPS];
\r
130 int lumpstart[NUMLUMPS] =
\r
132 CONTROLS_LUMP_START,
\r
134 WORLDKEEN_LUMP_START,
\r
135 BROCCOLASH_LUMP_START,
\r
142 FRENCHY_LUMP_START,
\r
143 MELONLIPS_LUMP_START,
\r
144 SQUASHER_LUMP_START,
\r
150 int lumpend[NUMLUMPS] =
\r
154 WORLDKEEN_LUMP_END,
\r
155 BROCCOLASH_LUMP_END,
\r
163 MELONLIPS_LUMP_END,
\r
171 void CheckKeys (void);
\r
172 void CalcInactivate (void);
\r
173 void InitObjArray (void);
\r
174 void GetNewObj (boolean usedummy);
\r
175 void RemoveObj (objtype *gone);
\r
176 void ScanInfoPlane (void);
\r
177 void PatchWorldMap (void);
\r
178 void MarkTileGraphics (void);
\r
179 void FadeAndUnhook (void);
\r
180 void SetupGameLevel (boolean loadnow);
\r
181 void ScrollScreen (void);
\r
182 void MoveObjVert (objtype *ob, int ymove);
\r
183 void MoveObjHoriz (objtype *ob, int xmove);
\r
184 void GivePoints (unsigned points);
\r
185 void ClipToEnds (objtype *ob);
\r
186 void ClipToEastWalls (objtype *ob);
\r
187 void ClipToWestWalls (objtype *ob);
\r
188 void ClipToWalls (objtype *ob);
\r
189 void ClipToSpriteSide (objtype *push, objtype *solid);
\r
190 void ClipToSprite (objtype *push, objtype *solid, boolean squish);
\r
191 int DoActor (objtype *ob,int tics);
\r
192 void StateMachine (objtype *ob);
\r
193 void NewState (objtype *ob,statetype *state);
\r
194 void PlayLoop (void);
\r
195 void GameLoop (void);
\r
197 //===========================================================================
\r
200 =====================
\r
204 =====================
\r
207 void CheckKeys (void)
\r
209 if (screenfaded) // don't do anything with a faded screen
\r
213 // space for status screen
\r
215 if (Keyboard[sc_Space])
\r
218 IN_ClearKeysDown();
\r
220 lasttimecount = TimeCount;
\r
224 // pause key wierdness can't be checked as a scan code
\r
228 VW_FixRefreshBuffer ();
\r
229 US_CenterWindow (8,3);
\r
230 US_PrintCentered ("PAUSED");
\r
231 VW_UpdateScreen ();
\r
233 RF_ForceRefresh ();
\r
238 // F1-F7/ESC to enter control panel
\r
240 if ( (LastScan >= sc_F1 && LastScan <= sc_F7) || LastScan == sc_Escape)
\r
242 VW_FixRefreshBuffer ();
\r
243 US_CenterWindow (20,8);
\r
244 US_CPrint ("Loading");
\r
245 VW_UpdateScreen ();
\r
247 IN_ClearKeysDown();
\r
249 playstate = resetgame;
\r
250 else if (!loadedgame)
\r
251 RF_ForceRefresh(); // don't refresh if loading a new game
\r
253 lasttimecount = TimeCount;
\r
257 // F10-? debug keys
\r
259 if (Keyboard[sc_F10] && DebugKeys() )
\r
262 lasttimecount = TimeCount;
\r
267 //===========================================================================
\r
271 =======================
\r
275 =======================
\r
278 void CalcInactivate (void)
\r
280 originxtilemax = originxtile+PORTTILESWIDE-1;
\r
281 originytilemax = originytile+PORTTILESHIGH-1;
\r
283 inactivateleft = originxtile-INACTIVATEDIST;
\r
284 if (inactivateleft < 0)
\r
285 inactivateleft = 0;
\r
286 inactivateright = originxtilemax+INACTIVATEDIST;
\r
287 inactivatetop = originytile-INACTIVATEDIST;
\r
288 if (inactivatetop < 0)
\r
290 inactivatebottom = originytilemax+INACTIVATEDIST;
\r
294 //===========================================================================
\r
298 #############################################################################
\r
300 The objarray data structure
\r
302 #############################################################################
\r
304 Objarray containt structures for every actor currently playing. The structure
\r
305 is accessed as a linked list starting at *player, ending when ob->next ==
\r
306 NULL. GetNewObj inserts a new object at the end of the list, meaning that
\r
307 if an actor spawn another actor, the new one WILL get to think and react the
\r
308 same frame. RemoveObj unlinks the given object and returns it to the free
\r
309 list, but does not damage the objects ->next pointer, so if the current object
\r
310 removes itself, a linked list following loop can still safely get to the
\r
313 <backwardly linked free list>
\r
315 #############################################################################
\r
320 =========================
\r
324 = Call to clear out the entire object list, returning them all to the free
\r
325 = list. Allocates a special spot for the player.
\r
327 =========================
\r
330 void InitObjArray (void)
\r
334 for (i=0;i<MAXACTORS;i++)
\r
336 objarray[i].prev = &objarray[i+1];
\r
337 objarray[i].next = NULL;
\r
340 objarray[MAXACTORS-1].prev = NULL;
\r
342 objfreelist = &objarray[0];
\r
348 // give the player and score the first free spots
\r
356 //===========================================================================
\r
359 =========================
\r
363 = Sets the global variable new to point to a free spot in objarray.
\r
364 = The free spot is inserted at the end of the liked list
\r
366 = When the object list is full, the caller can either have it bomb out ot
\r
367 = return a dummy object pointer that will never get used
\r
369 =========================
\r
372 void GetNewObj (boolean usedummy)
\r
381 Quit ("GetNewObj: No free spots in objarray!");
\r
385 objfreelist = new->prev;
\r
386 memset (new,0,sizeof(*new));
\r
389 lastobj->next = new;
\r
390 new->prev = lastobj; // new->next is allready NULL from memset
\r
393 new->needtoclip = true;
\r
399 //===========================================================================
\r
402 =========================
\r
406 = Add the given object back into the free list, and unlink it from it's
\r
409 =========================
\r
412 void RemoveObj (objtype *gone)
\r
414 if (gone == player)
\r
415 Quit ("RemoveObj: Tried to remove the player!");
\r
418 // erase it from the refresh manager
\r
420 RF_RemoveSprite (&gone->sprite);
\r
423 // fix the next object's back link
\r
425 if (gone == lastobj)
\r
426 lastobj = (objtype *)gone->prev;
\r
428 gone->next->prev = gone->prev;
\r
431 // fix the previous object's forward link
\r
433 gone->prev->next = gone->next;
\r
436 // add it back in to the free list
\r
438 gone->prev = objfreelist;
\r
439 objfreelist = gone;
\r
442 //===========================================================================
\r
445 void near HandleInfo (void)
\r
450 SpawnKeen(mapx,mapy,1);
\r
453 SpawnKeen(mapx,mapy,-1);
\r
456 SpawnWorldKeen(mapx,mapy);
\r
457 lumpneeded[WORLDKEENLUMP] = true;
\r
461 bombspresent = true;
\r
473 SpawnBonus(mapx,mapy,maptile-21);
\r
474 new->active = false;
\r
477 SpawnDoor(mapx,mapy);
\r
478 new->active = false;
\r
481 SpawnBrocco(mapx,mapy);
\r
482 new->active = false;
\r
483 lumpneeded[BROCCOLUMP] = true;
\r
486 SpawnTomat(mapx,mapy);
\r
487 new->active = false;
\r
488 lumpneeded[TOMATLUMP] = true;
\r
491 SpawnCarrot(mapx,mapy);
\r
492 new->active = false;
\r
493 lumpneeded[CARROTLUMP] = true;
\r
496 SpawnAspar(mapx,mapy);
\r
497 new->active = false;
\r
498 lumpneeded[ASPARLUMP] = true;
\r
501 SpawnGrape(mapx,mapy);
\r
502 new->active = false;
\r
503 lumpneeded[GRAPELUMP] = true;
\r
506 SpawnTater(mapx,mapy);
\r
507 new->active = false;
\r
508 lumpneeded[TATERLUMP] = true;
\r
511 SpawnCart(mapx,mapy);
\r
512 lumpneeded[CARTLUMP] = true;
\r
515 SpawnFrenchy(mapx,mapy);
\r
516 new->active = false;
\r
517 lumpneeded[FRENCHYLUMP] = true;
\r
522 SpawnMelon(mapx,mapy,maptile-50);
\r
523 new->active = false;
\r
524 lumpneeded[MELONLUMP] = true;
\r
527 SpawnSquasher(mapx,mapy);
\r
528 new->active = false;
\r
529 lumpneeded[SQUASHLUMP] = true;
\r
532 SpawnApel(mapx,mapy);
\r
533 new->active = false;
\r
534 lumpneeded[APELLUMP] = true;
\r
537 SpawnPeaPod(mapx,mapy);
\r
538 new->active = false;
\r
539 lumpneeded[PEALUMP] = true;
\r
542 SpawnPeaBrain(mapx,mapy);
\r
543 new->active = false;
\r
544 lumpneeded[PEALUMP] = true;
\r
547 SpawnBoobus(mapx,mapy);
\r
548 lumpneeded[BOOBUSLUMP] = true;
\r
552 if (new->active != allways)
\r
553 new->active = false;
\r
557 ==========================
\r
561 = Spawn all actors and mark down special places
\r
563 ==========================
\r
566 void ScanInfoPlane (void)
\r
570 unsigned far *start;
\r
572 InitObjArray(); // start spawning things with a clean slate
\r
574 memset (lumpneeded,0,sizeof(lumpneeded));
\r
577 start = mapsegs[2];
\r
578 for (y=0;y<mapheight;y++)
\r
579 for (x=0;x<mapwidth;x++)
\r
588 // This doesn't really need to be in asm. I thought it was a bottleneck,
\r
589 // but I was wrong...
\r
592 asm mov es,[WORD PTR mapsegs+4]
\r
595 asm mov ax,[mapheight]
\r
596 asm mov [mapycount],ax
\r
599 asm mov ax,[mapwidth]
\r
600 asm mov [mapxcount],ax
\r
605 asm mov [maptile],ax
\r
606 HandleInfo (); // si is saved
\r
607 asm mov es,[WORD PTR mapsegs+4]
\r
611 asm dec [mapxcount]
\r
614 asm dec [mapycount]
\r
617 for (i=0;i<NUMLUMPS;i++)
\r
619 for (j=lumpstart[i];j<=lumpend[i];j++)
\r
623 //===========================================================================
\r
627 ==========================
\r
631 = Takes out blocking squares and puts in dones
\r
633 ==========================
\r
636 void PatchWorldMap (void)
\r
638 unsigned size,spot,info,foreground;
\r
640 size = mapwidth*mapheight;
\r
644 info = *(mapsegs[2] + spot);
\r
645 // finished a city here?
\r
646 if (info>=3 && info<=18 && gamestate.leveldone[info-2])
\r
648 *(mapsegs[2] + spot) = 0;
\r
649 foreground = *(mapsegs[1] + spot);
\r
650 if (foreground == 130)
\r
651 *(mapsegs[1]+spot) = 0; // not blocking now
\r
652 else if (foreground == 90)
\r
655 *(mapsegs[1]+spot) = 133;
\r
656 *(mapsegs[1]+(spot-mapwidth-1)) = 131;
\r
657 *(mapsegs[1]+(spot-mapwidth)) = 132;
\r
661 } while (spot<size);
\r
664 //===========================================================================
\r
667 ==========================
\r
671 = Latch this onto the refresh so the screen only gets faded in after two
\r
672 = refreshes. This lets all actors draw themselves to both pages before
\r
673 = fading the screen in.
\r
675 ==========================
\r
678 void FadeAndUnhook (void)
\r
680 if (++fadecount==2)
\r
684 RF_SetRefreshHook (NULL);
\r
685 lasttimecount = TimeCount; // don't adaptively time the fade
\r
689 //===========================================================================
\r
693 ==========================
\r
697 = Load in map mapon and cache everything needed for it
\r
699 ==========================
\r
702 void SetupGameLevel (boolean loadnow)
\r
706 bombspresent = false;
\r
708 // load the level header and three map planes
\r
710 CA_CacheMap (gamestate.mapon);
\r
713 // let the refresh manager set up some variables
\r
718 // decide which graphics are needed and spawn actors
\r
725 if (mapon!=20) // map 20 is the title screen
\r
727 RF_MarkTileGraphics ();
\r
730 // have the caching manager load and purge stuff to make sure all marks
\r
737 VW_FixRefreshBuffer ();
\r
738 US_DrawWindow (10,1,20,2);
\r
739 US_PrintCentered ("Boobus Bombs Near!");
\r
740 VW_UpdateScreen ();
\r
742 CA_CacheMarks (levelnames[mapon], 0);
\r
746 VW_FixRefreshBuffer ();
\r
747 US_CenterWindow (20,8);
\r
748 US_Print ("\n\n\nObject count:");
\r
749 itoa (objectcount,str,10);
\r
751 VW_UpdateScreen ();
\r
755 if (mapon!=20 && loadnow) // map 20 is the title screen
\r
759 RF_SetRefreshHook (&FadeAndUnhook);
\r
763 // start the initial view position to center the player
\r
765 orgx = (long)player->x - (150<<G_P_SHIFT);
\r
766 orgy = (long)player->y-(84<<G_P_SHIFT);
\r
772 RF_NewPosition (orgx,orgy);
\r
779 //==========================================================================
\r
786 = Scroll if Keen is nearing an edge
\r
787 = Set playstate to levelcomplete
\r
792 void ScrollScreen (void)
\r
794 int xscroll,yscroll;
\r
797 // walked off edge of map?
\r
799 if (player->left < originxmin
\r
800 || player->right > originxmax+20*TILEGLOBAL)
\r
802 playstate = levelcomplete;
\r
807 // fallen off bottom of world?
\r
809 if (!plummet && player->bottom > originymax+13*TILEGLOBAL)
\r
817 if (player->x < originxglobal+SCROLLWEST)
\r
818 xscroll = player->x - (originxglobal+SCROLLWEST);
\r
819 else if (player->x > originxglobal+SCROLLEAST)
\r
820 xscroll = player->x - (originxglobal+SCROLLEAST);
\r
824 if (player->y < originyglobal+SCROLLNORTH)
\r
825 yscroll = player->y - (originyglobal+SCROLLNORTH);
\r
826 else if (player->y > originyglobal+SCROLLSOUTH)
\r
827 yscroll = player->y - (originyglobal+SCROLLSOUTH);
\r
830 if (xscroll || yscroll)
\r
832 RF_Scroll (xscroll,yscroll);
\r
834 scoreobj->needtoreact = true;
\r
838 //==========================================================================
\r
841 ====================
\r
845 = Grants extra men at 20k,40k,80k,160k,320k
\r
847 ====================
\r
850 void GivePoints (unsigned points)
\r
852 gamestate.score += points;
\r
853 if (gamestate.score >= gamestate.nextextra)
\r
855 SD_PlaySound (EXTRAKEENSND);
\r
857 gamestate.nextextra*=2;
\r
862 //==========================================================================
\r
865 ====================
\r
869 ====================
\r
872 void MoveObjVert (objtype *ob, int ymove)
\r
876 ob->bottom += ymove;
\r
877 ob->tiletop = ob->top >> G_T_SHIFT;
\r
878 ob->tilebottom = ob->bottom >> G_T_SHIFT;
\r
883 ====================
\r
887 ====================
\r
890 void MoveObjHoriz (objtype *ob, int xmove)
\r
894 ob->right += xmove;
\r
895 ob->tileleft = ob->left >> G_T_SHIFT;
\r
896 ob->tileright = ob->right >> G_T_SHIFT;
\r
901 =============================================================================
\r
903 Actor to tile clipping rouitnes
\r
905 =============================================================================
\r
908 // walltype / x coordinate (0-15)
\r
910 int wallclip[8][16] = { // the height of a given point in a tile
\r
911 { 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256},
\r
912 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
\r
913 { 0,0x08,0x10,0x18,0x20,0x28,0x30,0x38,0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78},
\r
914 {0x80,0x88,0x90,0x98,0xa0,0xa8,0xb0,0xb8,0xc0,0xc8,0xd0,0xd8,0xe0,0xe8,0xf0,0xf8},
\r
915 { 0,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0},
\r
916 {0x78,0x70,0x68,0x60,0x58,0x50,0x48,0x40,0x38,0x30,0x28,0x20,0x18,0x10,0x08, 0},
\r
917 {0xf8,0xf0,0xe8,0xe0,0xd8,0xd0,0xc8,0xc0,0xb8,0xb0,0xa8,0xa0,0x98,0x90,0x88,0x80},
\r
918 {0xf0,0xe0,0xd0,0xc0,0xb0,0xa0,0x90,0x80,0x70,0x60,0x50,0x40,0x30,0x20,0x10, 0}
\r
921 // assignment within ifs are used heavily here, so turn off the warning
\r
925 ===========================
\r
929 ===========================
\r
932 void ClipToEnds (objtype *ob)
\r
934 unsigned far *map,tile,facetile,info,wall;
\r
935 int leftpix,rightpix,midtiles,toppix,bottompix;
\r
936 int x,y,clip,move,totalmove,maxmove,midxpix;
\r
938 midxpix = (ob->midx&0xf0) >> 4;
\r
940 maxmove = -abs(midxmoved) - bottommoved - 16;
\r
941 map = (unsigned far *)mapsegs[1] +
\r
942 mapbwidthtable[oldtilebottom-1]/2 + ob->tilemidx;
\r
943 for (y=oldtilebottom-1 ; y<=ob->tilebottom ; y++,map+=mapwidth)
\r
945 if (wall = tinf[NORTHWALL+*map])
\r
947 clip = wallclip[wall&7][midxpix];
\r
948 move = ( (y<<G_T_SHIFT)+clip - 1) - ob->bottom;
\r
949 if (move<0 && move>=maxmove)
\r
951 ob->hitnorth = wall;
\r
952 MoveObjVert (ob,move);
\r
958 maxmove = abs(midxmoved) - topmoved + 16;
\r
959 map = (unsigned far *)mapsegs[1] +
\r
960 mapbwidthtable[oldtiletop+1]/2 + ob->tilemidx;
\r
961 for (y=oldtiletop+1 ; y>=ob->tiletop ; y--,map-=mapwidth)
\r
963 if (wall = tinf[SOUTHWALL+*map])
\r
965 clip = wallclip[wall&7][midxpix];
\r
966 move = ( ((y+1)<<G_T_SHIFT)-clip ) - ob->top;
\r
967 if (move > 0 && move<=maxmove)
\r
969 totalmove = ob->ymove + move;
\r
970 if (totalmove < TILEGLOBAL && totalmove > -TILEGLOBAL)
\r
972 ob->hitsouth = wall;
\r
973 MoveObjVert (ob,move);
\r
982 ===========================
\r
984 = ClipToEastWalls / ClipToWestWalls
\r
986 ===========================
\r
989 void ClipToEastWalls (objtype *ob)
\r
991 int y,move,top,bottom;
\r
992 unsigned far *map,tile,info,wall;
\r
994 // clip to east walls if moving west
\r
997 if (ob->hitsouth>1)
\r
998 top++; // on a slope inside a tile
\r
999 bottom = ob->tilebottom;
\r
1000 if (ob->hitnorth>1)
\r
1001 bottom--; // on a slope inside a tile
\r
1003 for (y=top;y<=bottom;y++)
\r
1005 map = (unsigned far *)mapsegs[1] +
\r
1006 mapbwidthtable[y]/2 + ob->tileleft;
\r
1008 if (ob->hiteast = tinf[EASTWALL+*map])
\r
1010 move = ( (ob->tileleft+1)<<G_T_SHIFT ) - ob->left;
\r
1011 MoveObjHoriz (ob,move);
\r
1018 void ClipToWestWalls (objtype *ob)
\r
1020 int y,move,top,bottom;
\r
1021 unsigned far *map,tile,info,wall;
\r
1023 // check west walls if moving east
\r
1025 top = ob->tiletop;
\r
1026 if (ob->hitsouth>1)
\r
1027 top++; // on a slope inside a tile
\r
1028 bottom = ob->tilebottom;
\r
1029 if (ob->hitnorth>1)
\r
1030 bottom--; // on a slope inside a tile
\r
1032 for (y=top;y<=bottom;y++)
\r
1034 map = (unsigned far *)mapsegs[1] +
\r
1035 mapbwidthtable[y]/2 + ob->tileright;
\r
1037 if (ob->hitwest = tinf[WESTWALL+*map])
\r
1039 move = ( (ob->tileright<<G_T_SHIFT ) -1) - ob->right;
\r
1040 MoveObjHoriz (ob,move);
\r
1046 // turn 'possibly incorrect assignment' warnings back on
\r
1050 //==========================================================================
\r
1057 = Moves the current object xmove/ymove units, clipping to walls
\r
1062 void ClipToWalls (objtype *ob)
\r
1064 unsigned x,y,tile;
\r
1065 spritetabletype far *shape;
\r
1069 // make sure it stays in contact with a 45 degree slope
\r
1071 if (ob->state->pushtofloor)
\r
1073 if (ob->xmove > 0)
\r
1074 ob->ymove = ob->xmove + 16;
\r
1076 ob->ymove = -ob->xmove + 16;
\r
1082 if (ob->xmove > MAXMOVE)
\r
1083 ob->xmove = MAXMOVE;
\r
1084 else if (ob->xmove < -MAXMOVE)
\r
1085 ob->xmove = -MAXMOVE;
\r
1087 if (ob->ymove > MAXMOVE+16) // +16 for push to floor
\r
1088 ob->ymove = MAXMOVE+16;
\r
1089 else if (ob->ymove < -MAXMOVE)
\r
1090 ob->ymove = -MAXMOVE;
\r
1092 ob->x += ob->xmove;
\r
1093 ob->y += ob->ymove;
\r
1095 ob->needtoreact = true;
\r
1097 if (!ob->shapenum) // can't get a hit rect with no shape!
\r
1100 shape = &spritetable[ob->shapenum-STARTSPRITES];
\r
1102 oldtileright = ob->tileright;
\r
1103 oldtiletop = ob->tiletop;
\r
1104 oldtileleft = ob->tileleft;
\r
1105 oldtilebottom = ob->tilebottom;
\r
1106 oldtilemidx = ob->tilemidx;
\r
1108 oldright = ob->right;
\r
1110 oldleft = ob->left;
\r
1111 oldbottom = ob->bottom;
\r
1112 oldmidx = ob->midx;
\r
1114 ob->left = ob->x + shape->xl;
\r
1115 ob->right = ob->x + shape->xh;
\r
1116 ob->top = ob->y + shape->yl;
\r
1117 ob->bottom = ob->y + shape->yh;
\r
1118 ob->midx = ob->left + (ob->right - ob->left)/2;
\r
1120 ob->tileleft = ob->left >> G_T_SHIFT;
\r
1121 ob->tileright = ob->right >> G_T_SHIFT;
\r
1122 ob->tiletop = ob->top >> G_T_SHIFT;
\r
1123 ob->tilebottom = ob->bottom >> G_T_SHIFT;
\r
1124 ob->tilemidx = ob->midx >> G_T_SHIFT;
\r
1126 ob->hitnorth = ob->hiteast = ob->hitsouth = ob->hitwest = 0;
\r
1128 if (!ob->needtoclip)
\r
1131 leftmoved = ob->left - oldleft;
\r
1132 rightmoved = ob->right - oldright;
\r
1133 topmoved = ob->top - oldtop;
\r
1134 bottommoved = ob->bottom - oldbottom;
\r
1135 midxmoved = ob->midx - oldmidx;
\r
1143 if (leftmoved < 0 || ob == player) // make sure player gets cliped
\r
1144 ClipToEastWalls (ob);
\r
1145 if (rightmoved > 0 || ob == player)
\r
1146 ClipToWestWalls (ob);
\r
1149 //==========================================================================
\r
1153 ==================
\r
1155 = ClipToSpriteSide
\r
1157 = Clips push to solid
\r
1159 ==================
\r
1162 void ClipToSpriteSide (objtype *push, objtype *solid)
\r
1164 int xmove,leftinto,rightinto;
\r
1167 // amount the push shape can be pushed
\r
1169 xmove = solid->xmove - push->xmove;
\r
1172 // amount it is inside
\r
1174 leftinto = solid->right - push->left;
\r
1175 rightinto = push->right - solid->left;
\r
1177 if (leftinto>0 && leftinto<= xmove)
\r
1179 push->xmove = leftinto;
\r
1180 if (push->state->pushtofloor)
\r
1181 push->ymove = leftinto+16;
\r
1182 ClipToWalls (push);
\r
1183 push->hiteast = 1;
\r
1187 if (rightinto>0 && rightinto<= -xmove)
\r
1189 push->xmove = -rightinto;
\r
1190 if (push->state->pushtofloor)
\r
1191 push->ymove = rightinto+16;
\r
1192 ClipToWalls (push);
\r
1193 push->hitwest = 1;
\r
1199 //==========================================================================
\r
1203 ==================
\r
1207 = Clips push to solid
\r
1209 ==================
\r
1212 void ClipToSprite (objtype *push, objtype *solid, boolean squish)
\r
1215 int walltemp,xmove,leftinto,rightinto,topinto,bottominto;
\r
1217 xmove = solid->xmove - push->xmove;
\r
1219 push->xmove = push->ymove = 0;
\r
1224 leftinto = solid->right - push->left;
\r
1225 rightinto = push->right - solid->left;
\r
1227 if (leftinto>0 && leftinto<=xmove)
\r
1229 push->xmove = leftinto;
\r
1230 walltemp = push->hitnorth;
\r
1231 ClipToWalls (push);
\r
1232 if (!push->hitnorth)
\r
1233 push->hitnorth = walltemp;
\r
1234 if (squish && push->hitwest)
\r
1236 push->hiteast = 1;
\r
1239 else if (rightinto>0 && rightinto<=-xmove)
\r
1241 push->xmove = -rightinto;
\r
1242 walltemp = push->hitnorth;
\r
1243 ClipToWalls (push);
\r
1244 if (!push->hitnorth)
\r
1245 push->hitnorth = walltemp;
\r
1246 if (squish && push->hiteast)
\r
1248 push->hitwest = 1;
\r
1255 topinto = solid->bottom - push->top;
\r
1256 bottominto = push->bottom - solid->top;
\r
1260 push->ymove = -bottominto+16;
\r
1261 push->xmove = solid->xmove;
\r
1262 temp = push->state->pushtofloor;
\r
1263 push->state->pushtofloor = false;
\r
1264 walltemp = push->hitnorth;
\r
1265 ClipToWalls (push);
\r
1266 if (!push->hitnorth)
\r
1267 push->hitnorth = walltemp;
\r
1268 push->state->pushtofloor = temp;
\r
1269 push->hitnorth = 25;
\r
1271 else if (topinto>0)
\r
1273 push->ymove = topinto;
\r
1274 ClipToWalls (push);
\r
1275 push->hitsouth = 25;
\r
1279 //==========================================================================
\r
1283 ==================
\r
1287 = Moves an actor in its current state by a given number of tics.
\r
1288 = If that time takes it into the next state, it changes the state
\r
1289 = and returns the number of excess tics after the state change
\r
1291 ==================
\r
1294 int DoActor (objtype *ob,int tics)
\r
1296 int newtics,movetics,excesstics;
\r
1299 state = ob->state;
\r
1301 if (state->progress == think)
\r
1315 newtics = ob->ticcount+tics;
\r
1317 if (newtics < state->tictime || state->tictime == 0)
\r
1319 ob->ticcount = newtics;
\r
1320 if (state->progress == slide || state->progress == slidethink)
\r
1323 ob->xmove += ob->xdir == 1 ? tics*state->xmove
\r
1324 : -tics*state->xmove;
\r
1326 ob->ymove += ob->ydir == 1 ? tics*state->ymove
\r
1327 : -tics*state->ymove;
\r
1329 if (state->progress == slidethink || state->progress == stepthink)
\r
1345 movetics = state->tictime - ob->ticcount;
\r
1346 excesstics = newtics - state->tictime;
\r
1348 if (state->progress == slide || state->progress == slidethink)
\r
1351 ob->xmove += ob->xdir == 1 ? movetics*state->xmove
\r
1352 : -movetics*state->xmove;
\r
1354 ob->ymove += ob->ydir == 1 ? movetics*state->ymove
\r
1355 : -movetics*state->ymove;
\r
1360 ob->xmove += ob->xdir == 1 ? state->xmove : -state->xmove;
\r
1362 ob->ymove += ob->ydir == 1 ? state->ymove : -state->ymove;
\r
1375 if (ob->state == state)
\r
1376 ob->state = state->nextstate; // go to next state
\r
1377 else if (!ob->state)
\r
1378 return 0; // object removed itself
\r
1379 return excesstics;
\r
1383 //==========================================================================
\r
1387 ====================
\r
1391 = Change state and give directions
\r
1393 ====================
\r
1396 void StateMachine (objtype *ob)
\r
1398 int excesstics,oldshapenum;
\r
1401 ob->xmove = ob->ymove = 0;
\r
1402 oldshapenum = ob->shapenum;
\r
1404 state = ob->state;
\r
1406 excesstics = DoActor(ob,tics);
\r
1407 if (ob->state != state)
\r
1409 ob->ticcount = 0; // start the new state at 0, then use excess
\r
1410 state = ob->state;
\r
1413 while (excesstics)
\r
1416 // passed through to next state
\r
1418 if (!state->skippable && excesstics >= state->tictime)
\r
1419 excesstics = DoActor(ob,state->tictime-1);
\r
1421 excesstics = DoActor(ob,excesstics);
\r
1422 if (ob->state != state)
\r
1424 ob->ticcount = 0; // start the new state at 0, then use excess
\r
1425 state = ob->state;
\r
1429 if (!state) // object removed itself
\r
1437 // if state->rightshapenum == NULL, the state does not have a standard
\r
1438 // shape (the think routine should have set it)
\r
1440 if (state->rightshapenum)
\r
1443 ob->shapenum = state->rightshapenum;
\r
1445 ob->shapenum = state->leftshapenum;
\r
1447 if (ob->shapenum == (unsigned)-1)
\r
1448 ob->shapenum = 0; // make it invisable this time
\r
1450 if (ob->xmove || ob->ymove || ob->shapenum != oldshapenum)
\r
1453 // actor moved or changed shape
\r
1454 // make sure the movement is within limits (one tile)
\r
1460 //==========================================================================
\r
1464 ====================
\r
1468 ====================
\r
1471 void NewState (objtype *ob,statetype *state)
\r
1475 ob->state = state;
\r
1477 if (state->rightshapenum)
\r
1480 ob->shapenum = state->rightshapenum;
\r
1482 ob->shapenum = state->leftshapenum;
\r
1485 temp = ob->needtoclip;
\r
1487 ob->needtoclip = false;
\r
1489 ClipToWalls (ob); // just calculate values
\r
1491 ob->needtoclip = temp;
\r
1493 if (ob->needtoclip)
\r
1498 //==========================================================================
\r
1501 ============================
\r
1505 ============================
\r
1508 void PlayLoop (void)
\r
1510 objtype *obj, *check;
\r
1513 button0held = button1held = false;
\r
1519 FixScoreBox (); // draw bomb/flower
\r
1523 CalcSingleGravity ();
\r
1524 IN_ReadControl(0,&c); // get player input
\r
1531 // go through state changes and propose movements
\r
1537 && obj->tileright >= originxtile
\r
1538 && obj->tileleft <= originxtilemax
\r
1539 && obj->tiletop <= originytilemax
\r
1540 && obj->tilebottom >= originytile)
\r
1542 obj->needtoreact = true;
\r
1543 obj->active = yes;
\r
1547 StateMachine(obj);
\r
1549 if ( (obj->active == true || obj->active == removable) &&
\r
1550 ( obj->tileright < inactivateleft
\r
1551 || obj->tileleft > inactivateright
\r
1552 || obj->tiletop > inactivatebottom
\r
1553 || obj->tilebottom < inactivatetop) )
\r
1555 if (obj->active == removable)
\r
1556 RemoveObj (obj); // temp thing (shots, etc)
\r
1559 if (US_RndT()<tics) // let them get a random dist
\r
1561 RF_RemoveSprite (&obj->sprite);
\r
1567 obj = (objtype *)obj->next;
\r
1571 // check for and handle collisions between objects
\r
1578 check = (objtype *)obj->next;
\r
1581 if ( check->active
\r
1582 && obj->right > check->left
\r
1583 && obj->left < check->right
\r
1584 && obj->top < check->bottom
\r
1585 && obj->bottom > check->top)
\r
1588 if (obj->state->contact)
\r
1589 obj->state->contact(obj,check);
\r
1590 if (check->state->contact)
\r
1591 check->state->contact(check,obj);
\r
1593 if (!obj->obclass)
\r
1594 break; // contact removed object
\r
1596 check = (objtype *)check->next;
\r
1599 obj = (objtype *)obj->next;
\r
1606 // react to whatever happened, and post sprites to the refresh manager
\r
1611 if (obj->needtoreact && obj->state->react)
\r
1613 obj->needtoreact = false;
\r
1615 obj->state->react(obj);
\r
1618 obj = (objtype *)obj->next;
\r
1623 // update the screen and calculate the number of tics it took to execute
\r
1624 // this cycle of events (for adaptive timing of next cycle)
\r
1629 // single step debug mode
\r
1634 lasttimecount = TimeCount;
\r
1638 } while (!loadedgame && !playstate);
\r
1644 //==========================================================================
\r
1647 ==========================
\r
1651 ==========================
\r
1654 void GameFinale (void)
\r
1658 VW_FixRefreshBuffer ();
\r
1660 /* screen 1 of finale text (16 lines) */
\r
1661 US_CenterWindow (30,21);
\r
1664 "Yes! Boobus Tuber's hash-brown-\n"
\r
1665 "like remains rained down from\n"
\r
1666 "the skies as Commander Keen\n"
\r
1667 "walked up to the Dream Machine.\n"
\r
1668 "He analyzed all the complex\n"
\r
1669 "controls and readouts on it, then\n"
\r
1670 "pulled down a huge red lever\n"
\r
1671 "marked \"On/Off Switch.\" The\n"
\r
1672 "machine clanked and rattled,\n"
\r
1673 "then went silent. He had freed\n"
\r
1674 "all the children from their\n"
\r
1675 "vegetable-enforced slavery!\n"
\r
1676 "Everything around Keen wobbled\n"
\r
1677 "in a disconcerting manner, his\n"
\r
1678 "eyelids grew heavy, and he\n"
\r
1679 "fell asleep....\n"
\r
1681 VW_UpdateScreen();
\r
1683 SD_WaitSoundDone ();
\r
1684 IN_ClearKeysDown ();
\r
1687 /* screen 2 of finale (15 lines) */
\r
1688 US_CenterWindow (30,21);
\r
1691 "Billy woke up, looking around the\n"
\r
1692 "room, the early morning sun\n"
\r
1693 "shining in his face. Nothing.\n"
\r
1694 "No vegetables to be seen. Was it\n"
\r
1695 "all just a dream?\n\n"
\r
1696 "Billy's mom entered the room.\n\n"
\r
1697 "\"Good morning, dear. I heard some\n"
\r
1698 "news on TV that you'd be\n"
\r
1699 "interested in,\" she said, sitting\n"
\r
1700 "by him on the bed.\n\n"
\r
1701 "\"What news?\" Billy asked,\n"
\r
1702 "still groggy.\n\n"
\r
1704 VW_UpdateScreen();
\r
1706 IN_ClearKeysDown ();
\r
1709 /* screen 3 of finale (12 lines)*/
\r
1710 US_CenterWindow (30,21);
\r
1713 "\"The President declared today\n"
\r
1714 "National 'I Hate Broccoli' Day.\n"
\r
1715 "He said kids are allowed to pick\n"
\r
1716 "one vegetable today, and they\n"
\r
1717 "don't have to eat it.\"\n\n"
\r
1718 "\"Aw, mom, I'm not afraid of any\n"
\r
1719 "stupid vegetables,\" Billy said.\n"
\r
1720 "\"But if it's okay with you, I'd\n"
\r
1721 "rather not have any french fries\n"
\r
1722 "for awhile.\"\n\n"
\r
1725 VW_UpdateScreen();
\r
1727 IN_ClearKeysDown ();
\r
1732 //==========================================================================
\r
1735 ==========================
\r
1739 ==========================
\r
1742 void HandleDeath (void)
\r
1744 unsigned top,bottom,selection,y,color;
\r
1746 gamestate.keys = 0;
\r
1747 gamestate.boobusbombs -= gamestate.bombsthislevel;
\r
1748 gamestate.lives--;
\r
1749 if (gamestate.lives < 0)
\r
1752 VW_FixRefreshBuffer ();
\r
1753 US_CenterWindow (20,8);
\r
1755 US_CPrint ("You didn't make it past");
\r
1756 US_CPrint (levelnames[mapon]);
\r
1759 US_CPrint ("Try Again");
\r
1761 bottom = PrintY-2;
\r
1762 US_CPrint ("Exit to Tuberia");
\r
1772 // draw select bar
\r
1773 if ( (TimeCount / 16)&1 )
\r
1774 color = SECONDCOLOR;
\r
1776 color = FIRSTCOLOR;
\r
1778 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y,color);
\r
1779 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+1,color);
\r
1780 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+12,color);
\r
1781 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+13,color);
\r
1782 VWB_Vlin (y+1,y+11, WindowX+4,color);
\r
1783 VWB_Vlin (y+1,y+11, WindowX+5,color);
\r
1784 VWB_Vlin (y+1,y+11, WindowX+WindowW-4,color);
\r
1785 VWB_Vlin (y+1,y+11, WindowX+WindowW-5,color);
\r
1787 VW_UpdateScreen ();
\r
1789 // erase select bar
\r
1790 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y,WHITE);
\r
1791 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+1,WHITE);
\r
1792 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+12,WHITE);
\r
1793 VWB_Hlin (WindowX+4,WindowX+WindowW-4,y+13,WHITE);
\r
1794 VWB_Vlin (y+1,y+11, WindowX+4,WHITE);
\r
1795 VWB_Vlin (y+1,y+11, WindowX+5,WHITE);
\r
1796 VWB_Vlin (y+1,y+11, WindowX+WindowW-4,WHITE);
\r
1797 VWB_Vlin (y+1,y+11, WindowX+WindowW-5,WHITE);
\r
1799 if (LastScan == sc_Escape)
\r
1801 gamestate.mapon = 0; // exit to tuberia
\r
1802 IN_ClearKeysDown ();
\r
1806 IN_ReadControl(0,&c); // get player input
\r
1807 if (c.button0 || c.button1 || LastScan == sc_Return
\r
1808 || LastScan == sc_Space)
\r
1811 gamestate.mapon = 0; // exit to tuberia
\r
1814 if (c.yaxis == -1 || LastScan == sc_UpArrow)
\r
1816 else if (c.yaxis == 1 || LastScan == sc_DownArrow)
\r
1822 //==========================================================================
\r
1825 ============================
\r
1829 = A game has just started (after the cinematic or load game)
\r
1831 ============================
\r
1834 void GameLoop (void)
\r
1836 unsigned cities,i;
\r
1839 gamestate.difficulty = restartgame;
\r
1840 restartgame = gd_Continue;
\r
1847 loadedgame = false;
\r
1849 // start the initial view position to center the player
\r
1851 orgx = (long)player->x - (150<<G_P_SHIFT);
\r
1852 orgy = (long)player->y-(84<<G_P_SHIFT);
\r
1860 RF_SetRefreshHook (&FadeAndUnhook);
\r
1861 RF_NewPosition (orgx,orgy);
\r
1862 CalcInactivate ();
\r
1866 VW_FixRefreshBuffer ();
\r
1867 US_CenterWindow (20,8);
\r
1868 US_CPrint ("Loading");
\r
1869 VW_UpdateScreen ();
\r
1870 gamestate.bombsthislevel = 0;
\r
1871 SetupGameLevel (true);
\r
1880 if (playstate == died)
\r
1890 switch (playstate)
\r
1899 case levelcomplete:
\r
1901 SD_PlaySound (LEVELDONESND);
\r
1902 gamestate.leveldone[mapon] = true; // finished the level
\r
1904 gamestate.mapon = 0;
\r
1916 } while (gamestate.lives>-1 && playstate!=victorious);
\r
1922 for (i= 1; i<=16; i++)
\r
1923 if (gamestate.leveldone[i])
\r
1925 US_CheckHighScore (gamestate.score,cities);
\r
1926 VW_ClearVideo (FIRSTCOLOR);
\r