1 /* Reconstructed Commander Keen 4-6 Source Code
\r
2 * Copyright (C) 2021 K1n9_Duk3
\r
4 * This file is loosely based on:
\r
5 * Keen Dreams Source Code
\r
6 * Copyright (C) 2014 Javier M. Chavez
\r
8 * This program is free software; you can redistribute it and/or modify
\r
9 * it under the terms of the GNU General Public License as published by
\r
10 * the Free Software Foundation; either version 2 of the License, or
\r
11 * (at your option) any later version.
\r
13 * This program is distributed in the hope that it will be useful,
\r
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
16 * GNU General Public License for more details.
\r
18 * You should have received a copy of the GNU General Public License along
\r
19 * with this program; if not, write to the Free Software Foundation, Inc.,
\r
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
26 =============================================================================
\r
30 =============================================================================
\r
34 Sint16 levelcompleted;
\r
35 Sint32 chunkcount, chunkmax, handpic;
\r
38 =============================================================================
\r
42 =============================================================================
\r
45 void FadeAndUnhook(void);
\r
47 //===========================================================================
\r
50 ============================
\r
54 ============================
\r
57 void FreeGraphics(void)
\r
60 for (i=STARTSPRITES; i<STARTSPRITES+NUMSPRITES; i++)
\r
64 MM_SetPurge(&grsegs[i], PURGE_LAST);
\r
67 for (i=STARTTILE16; i<STARTEXTERNS; i++)
\r
71 MM_SetPurge(&grsegs[i], PURGE_LAST);
\r
76 //===========================================================================
\r
79 =====================
\r
83 = Set up new game to start from the beginning
\r
85 =====================
\r
90 memset(&gamestate, 0, sizeof(gamestate));
\r
91 gamestate.nextextra = 20000;
\r
92 gamestate.lives = 3;
\r
96 //===========================================================================
\r
100 ============================
\r
104 ============================
\r
107 void GameOver(void)
\r
109 VW_FixRefreshBuffer();
\r
110 US_CenterWindow(16, 3);
\r
111 US_PrintCentered("Game Over!");
\r
113 IN_ClearKeysDown();
\r
114 IN_UserInput(4*TickBase, false);
\r
118 //===========================================================================
\r
121 ============================
\r
125 ============================
\r
128 #define RLETAG 0xABCD
\r
130 boolean SaveTheGame(Sint16 handle)
\r
132 Uint16 i,compressed,expanded;
\r
136 gamestate.riding = NULL;
\r
138 if (!CA_FarWrite(handle, (byte far *)&gamestate, sizeof(gamestate)))
\r
141 expanded = mapwidth * mapheight * 2;
\r
142 MM_GetPtr(&bigbuffer, expanded);
\r
144 for (i = 0; i < 3; i++)
\r
146 compressed = CA_RLEWCompress(mapsegs[i], expanded, (Uint16 huge *)bigbuffer+1, RLETAG);
\r
147 *(Uint16 huge *)bigbuffer = compressed;
\r
148 if (!CA_FarWrite(handle, bigbuffer, compressed+2))
\r
150 MM_FreePtr(&bigbuffer);
\r
154 for (ob = player; ob; ob=ob->next)
\r
156 if (!CA_FarWrite(handle, (byte far *)ob, sizeof(objtype)))
\r
158 MM_FreePtr(&bigbuffer);
\r
162 MM_FreePtr(&bigbuffer);
\r
166 //===========================================================================
\r
169 ============================
\r
173 ============================
\r
176 boolean LoadTheGame(Sint16 handle)
\r
179 objtype *prev,*next,*followed;
\r
180 Uint16 compressed,expanded;
\r
186 if (!CA_FarRead(handle, (byte far *)&gamestate, sizeof(gamestate)))
\r
191 // remember the fuses value for later - SetupGameLevel calls
\r
192 // ScanInfoPlane, which resets this part of the gamestate
\r
194 numfuses = gamestate.numfuses;
\r
199 SetupGameLevel(false);
\r
203 US_CenterWindow(20, 8);
\r
205 US_CPrint("Not enough memory\nto load game!");
\r
213 expanded = mapwidth * mapheight * 2;
\r
214 MM_BombOnError(true); //BUG: this should use false to avoid an instant crash
\r
215 MM_GetPtr(&bigbuffer, expanded);
\r
216 MM_BombOnError(false); //BUG: this should use true to force an instant crash
\r
220 US_CenterWindow(20, 8);
\r
222 US_CPrint("Not enough memory\nto load game!");
\r
227 for (i = 0; i < 3; i++)
\r
229 if (!CA_FarRead(handle, (byte far *)&compressed, sizeof(compressed)))
\r
231 MM_FreePtr(&bigbuffer);
\r
234 if (!CA_FarRead(handle, (byte far *)bigbuffer, compressed))
\r
236 MM_FreePtr(&bigbuffer);
\r
239 CA_RLEWexpand(bigbuffer, mapsegs[i], expanded, RLETAG);
\r
241 MM_FreePtr(&bigbuffer);
\r
247 if (!CA_FarRead(handle, (byte far *)new, sizeof(objtype)))
\r
253 new->needtoreact = true;
\r
254 new->sprite = NULL;
\r
260 if (!CA_FarRead(handle, (byte far *)new, sizeof(objtype)))
\r
264 followed = new->next;
\r
267 new->needtoreact = true;
\r
268 new->sprite = NULL;
\r
269 if (new->obclass == stunnedobj)
\r
271 new->temp3 = 0; //clear sprite ptr for the stars
\r
274 else if (new->obclass == platformobj)
\r
276 new->temp2 = new->temp3 = 0; //clear sprite ptrs
\r
278 #elif defined KEEN5
\r
279 else if (new->obclass == mineobj)
\r
281 new->temp4 = 0; //clear sprite ptr
\r
283 else if (new->obclass == spherefulobj)
\r
285 new->temp1 = new->temp2 = new->temp3 = new->temp4 = 0; //clear sprite ptrs
\r
287 #elif defined KEEN6
\r
288 else if (new->obclass == platformobj)
\r
290 new->temp3 = 0; //clear sprite ptr
\r
302 scoreobj->temp2 = -1;
\r
303 scoreobj->temp1 = -1;
\r
304 scoreobj->temp3 = -1;
\r
305 scoreobj->temp4 = -1;
\r
307 gamestate.numfuses = numfuses; // put value from saved game back in place
\r
312 //===========================================================================
\r
315 ============================
\r
319 ============================
\r
322 void ResetGame(void)
\r
332 //===========================================================================
\r
336 ==========================
\r
340 = Takes out blocking squares and spawns flags
\r
342 ==========================
\r
345 void PatchWorldMap(void)
\r
347 Uint16 x, y, planeoff, info, level, tag;
\r
348 Uint16 far *infoptr;
\r
351 infoptr = mapsegs[2];
\r
352 for (y = 0; y < mapheight; y++)
\r
354 for (x = 0; x < mapwidth; x++, infoptr++, planeoff++)
\r
357 level = info & 0xFF;
\r
358 if (level >= MINDONELEVEL && level <= MAXDONELEVEL && gamestate.leveldone[level])
\r
361 *infoptr = 0; // BUG: infoplane value should only be set to 0 if tag == 0xC0
\r
364 mapsegs[1][planeoff] = 0;
\r
366 else if (tag == 0xF0)
\r
371 if (levelcompleted == level)
\r
373 SpawnThrowFlag(x, y);
\r
386 //===========================================================================
\r
389 ==========================
\r
393 = Fades out and latches FadeAndUnhook onto the refresh
\r
395 ==========================
\r
398 void DelayedFade(void)
\r
402 RF_SetRefreshHook(&FadeAndUnhook);
\r
406 ==========================
\r
410 = Latch this onto the refresh so the screen only gets faded in after two
\r
411 = refreshes. This lets all actors draw themselves to both pages before
\r
412 = fading the screen in.
\r
414 ==========================
\r
417 void FadeAndUnhook(void)
\r
419 if (++fadecount == 2)
\r
422 RF_SetRefreshHook(NULL);
\r
423 TimeCount = lasttimecount; // don't adaptively time the fade
\r
427 //===========================================================================
\r
431 ==========================
\r
435 = Load in map mapon and cache everything needed for it
\r
437 ==========================
\r
440 void SetupGameLevel(boolean loadnow)
\r
443 // randomize if not a demo
\r
447 US_InitRndT(false);
\r
448 gamestate.difficulty = gd_Normal;
\r
456 // load the level header and three map planes
\r
458 CA_CacheMap(gamestate.mapon);
\r
461 // let the refresh manager set up some variables
\r
466 // decide which graphics are needed and spawn actors
\r
474 RF_MarkTileGraphics();
\r
477 // have the caching manager load and purge stuff to make sure all marks
\r
480 MM_BombOnError(false);
\r
481 CA_LoadAllSounds();
\r
484 if (scorescreenkludge)
\r
486 CA_CacheMarks(NULL);
\r
490 CA_CacheMarks("DEMO");
\r
493 else if (mapon == 0 && player->tiletop > 100)
\r
495 CA_CacheMarks("Keen steps out\nonto Korath III");
\r
500 _fstrcpy(str, levelenter[mapon]);
\r
501 CA_CacheMarks(str);
\r
504 MM_BombOnError(true);
\r
506 if (!mmerror && loadnow)
\r
512 //===========================================================================
\r
516 ==========================
\r
520 ==========================
\r
523 void DialogDraw(char *title, Uint16 numcache)
\r
526 Uint16 width, height;
\r
529 totalfree = MM_TotalFree();
\r
530 if (totalfree < 2048)
\r
537 for (i = 0; i < 6; i++)
\r
539 CA_CacheGrChunk(i+KEENCOUNT1PIC);
\r
540 CA_UnmarkGrChunk(i+KEENCOUNT1PIC);
\r
541 if (grsegs[i+KEENCOUNT1PIC])
\r
543 MM_SetPurge(&grsegs[i+KEENCOUNT1PIC], PURGE_FIRST);
\r
553 US_CenterWindow(26, 8);
\r
554 if (grsegs[KEENCOUNT1PIC])
\r
556 VWB_DrawPic(WindowX, WindowY, KEENCOUNT1PIC);
\r
562 CA_UnmarkGrChunk(KEENCOUNT1PIC); //redundant
\r
565 SizeText(title, &width, &height);
\r
566 PrintY += (WindowH-height)/2 - 4;
\r
569 chunkmax = chunkcount = numcache / 6;
\r
570 if (!chunkmax && !handpic)
\r
573 if (grsegs[KEENCOUNT6PIC])
\r
574 VWB_DrawPic(WindowX-24, WindowY+40, KEENCOUNT6PIC);
\r
580 ==========================
\r
584 ==========================
\r
587 void DialogUpdate(void)
\r
589 if (--chunkcount || handpic > 4)
\r
592 chunkcount = chunkmax;
\r
593 if (grsegs[handpic+KEENCOUNT2PIC])
\r
595 VWB_DrawPic(WindowX-24, WindowY+40, handpic+KEENCOUNT2PIC);
\r
602 ==========================
\r
606 ==========================
\r
609 void DialogFinish(void)
\r
614 //==========================================================================
\r
624 void StartDemoRecord(void)
\r
629 VW_FixRefreshBuffer();
\r
630 US_CenterWindow(30, 3);
\r
632 US_Print(" Record a demo from level(0-21):");
\r
634 esc = !US_LineInput(px, py, str, NULL, true, 2, 0);
\r
638 if (level >= 0 && level <= 21)
\r
640 gamestate.mapon = level;
\r
641 playstate = ex_warped;
\r
642 IN_StartDemoRecord(0x1000);
\r
655 void EndDemoRecord(void)
\r
659 char filename[] = "DEMO?."EXTENSION;
\r
662 VW_FixRefreshBuffer();
\r
663 US_CenterWindow(22, 3);
\r
665 US_Print(" Save as demo #(0-9):");
\r
667 esc = !US_LineInput(px, py, str, NULL, true, 2, 0);
\r
668 if (!esc && str[0] >= '0' && str[0] <= '9')
\r
670 filename[4] = str[0];
\r
671 handle = open(filename, O_BINARY|O_WRONLY|O_CREAT, S_IFREG|S_IREAD|S_IWRITE);
\r
674 Quit("EndDemoRecord: Cannot write demo file!");
\r
676 write(handle, &mapon, sizeof(mapon));
\r
677 write(handle, &DemoOffset, sizeof(DemoOffset));
\r
678 CA_FarWrite(handle, DemoBuffer, DemoOffset);
\r
681 IN_FreeDemoBuffer();
\r
684 //==========================================================================
\r
687 ==========================
\r
691 ==========================
\r
694 void HandleDeath(void)
\r
696 Uint16 y, color, top, bottom, selection, w, h;
\r
698 _fstrcpy(str, levelnames[mapon]);
\r
699 SizeText(str, &w, &h);
\r
701 memset(gamestate.keys, 0, sizeof(gamestate.keys));
\r
703 if (gamestate.lives >= 0)
\r
705 VW_FixRefreshBuffer();
\r
706 US_CenterWindow(20, 8);
\r
708 US_CPrint("You didn't make it past");
\r
714 US_CPrint("Try Again");
\r
717 US_CPrint("Exit to "WORLDMAPNAME);
\r
719 IN_ClearKeysDown();
\r
733 if ((TimeCount / 16) & 1)
\r
735 color = SECONDCOLOR;
\r
739 color = FIRSTCOLOR;
\r
741 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y, color);
\r
742 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y+1, color);
\r
743 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y+12, color);
\r
744 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y+13, color);
\r
745 VWB_Vlin(y+1, y+11, WindowX+4, color);
\r
746 VWB_Vlin(y+1, y+11, WindowX+5, color);
\r
747 VWB_Vlin(y+1, y+11, WindowX+WindowW-4, color);
\r
748 VWB_Vlin(y+1, y+11, WindowX+WindowW-5, color);
\r
752 // erase select bar
\r
753 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y, WHITE);
\r
754 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y+1, WHITE);
\r
755 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y+12, WHITE);
\r
756 VWB_Hlin(WindowX+4, WindowX+WindowW-4, y+13, WHITE);
\r
757 VWB_Vlin(y+1, y+11, WindowX+4, WHITE);
\r
758 VWB_Vlin(y+1, y+11, WindowX+5, WHITE);
\r
759 VWB_Vlin(y+1, y+11, WindowX+WindowW-4, WHITE);
\r
760 VWB_Vlin(y+1, y+11, WindowX+WindowW-5, WHITE);
\r
762 if (LastScan == sc_Escape)
\r
764 gamestate.mapon = 0; // exit to world map
\r
765 IN_ClearKeysDown();
\r
769 IN_ReadControl(0, &c);
\r
770 if (c.button0 || c.button1 || LastScan == sc_Return || LastScan == sc_Space)
\r
773 gamestate.mapon = 0; // exit to world map
\r
776 if (c.yaxis == -1 || LastScan == sc_UpArrow)
\r
780 else if (c.yaxis == 1 || LastScan == sc_DownArrow)
\r
788 //==========================================================================
\r
791 ============================
\r
795 = A game has just started (after the cinematic or load game)
\r
797 ============================
\r
800 void GameLoop(void)
\r
810 if (!US_ManualCheck())
\r
812 loadedgame = false;
\r
813 restartgame = gd_Continue;
\r
819 if (playstate == ex_loadedgame)
\r
824 gamestate.difficulty = restartgame;
\r
825 restartgame = gd_Continue;
\r
829 SetupGameLevel(true);
\r
832 if (gamestate.mapon != 0)
\r
835 US_CenterWindow(20, 8);
\r
837 US_CPrint("Insufficient memory\nto load level!");
\r
840 gamestate.mapon = 0; // exit to world map
\r
841 SetupGameLevel(true);
\r
845 Quit("GameLoop: Insufficient memory to load world map!");
\r
849 keenkilled = false;
\r
850 SD_WaitSoundDone();
\r
854 if (playstate != ex_loadedgame)
\r
856 memset(gamestate.keys, 0, sizeof(gamestate.keys));
\r
858 gamestate.keycard = false;
\r
861 VW_FixRefreshBuffer();
\r
865 if (playstate == ex_loadedgame)
\r
869 else if (playstate == ex_died)
\r
879 levelcompleted = -1;
\r
885 case ex_loadedgame:
\r
896 SD_PlaySound(SND_LEVELDONE);
\r
898 levelcompleted = mapon;
\r
899 gamestate.leveldone[mapon] = true;
\r
901 if (gamestate.rescued != 8)
\r
903 gamestate.mapon = 0;
\r
909 VW_FixRefreshBuffer();
\r
911 CheckHighScore(gamestate.score, gamestate.rescued);
\r
916 #elif defined KEEN5
\r
918 SD_PlaySound(SND_LEVELDONE);
\r
919 levelcompleted = mapon;
\r
920 gamestate.leveldone[mapon] = ex_fusebroke;
\r
922 gamestate.mapon = 0;
\r
928 VW_FixRefreshBuffer();
\r
930 CheckHighScore(gamestate.score, 0);
\r
933 #elif defined KEEN6
\r
949 VW_FixRefreshBuffer();
\r
960 SD_PlaySound(SND_LEVELDONE);
\r
961 gamestate.mapon = 0;
\r
962 levelcompleted = mapon;
\r
963 gamestate.leveldone[mapon] = true;
\r
964 if (storedemo && mapon == 2)
\r
966 IN_ClearKeysDown();
\r
972 #if GRMODE != CGAGR
\r
974 bufferofs = displayofs;
\r
976 US_CenterWindow(26, 8);
\r
978 US_CPrint("One moment");
\r
979 #if GRMODE == CGAGR
\r
988 IN_ClearKeysDown();
\r
991 } while (gamestate.lives >= 0);
\r
997 CheckHighScore(gamestate.score, gamestate.rescued);
\r
1001 for (i = 0; i < GAMELEVELS; i++)
\r
1003 if (gamestate.leveldone[i])
\r
1007 CheckHighScore(gamestate.score, temp);
\r