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
27 Contains (in this order):
\r
30 - "Star Wars" crawl text
\r
31 - level names & messages
\r
32 - Airlock opening & closing code
\r
33 - ScanInfoPlane() - for spawning the level objects and marking required sprites
\r
34 - game over animation
\r
35 - messages for breaking fuses
\r
57 WORLDKEEN_LUMP, // 16
\r
60 SHOCKSHUND_LUMP, // 19
\r
61 SPHEREFUL_LUMP, // 20
\r
64 SLICESTAR_LUMP, // 23
\r
69 SLOTPLAT_LUMP, // 28
\r
70 SPINDRED_LUMP, // 29
\r
72 PLATFORM_LUMP, // 31
\r
73 SMALLPLAT_LUMP, // 32
\r
77 STAREXPLODE_LUMP, // 36
\r
78 TELEPORT_LUMP, // 37
\r
80 NUMLUMPS=42 // Keen 5 has 4 unused lumps at the end
\r
83 Uint16 lumpstart[NUMLUMPS] = {
\r
85 CONTROLS_LUMP_START,
\r
86 KEENTALK_LUMP_START,
\r
100 WORLDKEEN_LUMP_START,
\r
102 SHIKADI_LUMP_START,
\r
103 SHOCKSHUND_LUMP_START,
\r
104 SPHEREFUL_LUMP_START,
\r
107 SLICESTAR_LUMP_START,
\r
108 ROBORED_LUMP_START,
\r
112 SLOTPLAT_LUMP_START,
\r
113 SPINDRED_LUMP_START,
\r
114 SHELLEY_LUMP_START,
\r
115 PLATFORM_LUMP_START,
\r
116 MINIPLAT_LUMP_START,
\r
117 KEYCARD_LUMP_START,
\r
118 SCOTTIE_LUMP_START,
\r
120 STAREXPLODE_LUMP_START,
\r
121 TELEPORT_LUMP_START
\r
124 Uint16 lumpend[NUMLUMPS] = {
\r
141 WORLDKEEN_LUMP_END,
\r
144 SHOCKSHUND_LUMP_END,
\r
145 SPHEREFUL_LUMP_END,
\r
148 SLICESTAR_LUMP_END,
\r
161 STAREXPLODE_LUMP_END,
\r
165 boolean lumpneeded[NUMLUMPS];
\r
167 #if GRMODE == EGAGR
\r
169 char far swtext[] =
\r
172 "The Armageddon Machine\n"
\r
174 "After learning the\n"
\r
175 "location of the secret\n"
\r
176 "Shikadi base, Keen\n"
\r
177 "jumped in the trusty\n"
\r
178 "Bean-with-Bacon\n"
\r
179 "Megarocket and blasted\n"
\r
180 "across interstellar\n"
\r
183 "Seventy-five furious\n"
\r
184 "games of Paddle War\n"
\r
185 "later, Keen dropped\n"
\r
186 "out of lightspeed near\n"
\r
187 " the Korath system.\n"
\r
189 "He flew toward the\n"
\r
190 "planet, keeping it\n"
\r
191 "between him and the\n"
\r
194 "Pulling up underside\n"
\r
195 "and docking at the\n"
\r
196 "Ion Ventilation System,\n"
\r
197 "Keen must destroy the\n"
\r
198 "Shikadi Armageddon\n"
\r
199 "Machine before it\n"
\r
200 "explodes and destroys\n"
\r
201 "the Milky Way! He\n"
\r
202 "steps into the dark\n"
\r
203 "ventilation duct and\n"
\r
204 "begins his most\n"
\r
205 "dangerous adventure\n"
\r
210 char far l0n[] = "Omegamatic";
\r
211 char far l1n[] = "Ion Ventilation System";
\r
212 char far l2n[] = "Security Center";
\r
213 char far l3n[] = "Defense Tunnel Vlook";
\r
214 char far l4n[] = "Energy Flow Systems";
\r
215 char far l5n[] = "Defense Tunnel Burrh";
\r
216 char far l6n[] = "Regulation\nControl Center";
\r
217 char far l7n[] = "Defense Tunnel Sorra";
\r
218 char far l8n[] = "Neutrino\nBurst Injector";
\r
219 char far l9n[] = "Defense Tunnel Teln";
\r
220 char far l10n[] = "Brownian\nMotion Inducer";
\r
221 char far l11n[] = "Gravitational\nDamping Hub";
\r
222 char far l12n[] = "Quantum\nExplosion Dynamo";
\r
223 char far l13n[] = "Korath III Base";
\r
224 char far l14n[] = "BWBMegarocket";
\r
225 char far l15n[] = "High Scores";
\r
227 char far l0e[] = "Keen purposefully\nwanders about the\nOmegamatic";
\r
228 char far l1e[] = "Keen investigates the\nIon Ventilation System";
\r
229 char far l2e[] = "Keen struts through\nthe Security Center";
\r
230 char far l3e[] = "Keen invades\nDefense Tunnel Vlook";
\r
231 char far l4e[] = "Keen engages\nEnergy Flow Systems";
\r
232 char far l5e[] = "Keen barrels into\nDefense Tunnel Burrh";
\r
233 char far l6e[] = "Keen goes nuts in\nthe Regulation\nControl Center";
\r
234 char far l7e[] = "Keen regrets entering\nDefense Tunnel Sorra";
\r
235 char far l8e[] = "Keen blows through\nthe Neutrino\nBurst Injector";
\r
236 char far l9e[] = "Keen trots through\nDefense Tunnel Teln";
\r
237 char far l10e[] = "Keen breaks into\nthe Brownian\nMotion Inducer";
\r
238 char far l11e[] = "Keen hurries through\nthe Gravitational\nDamping Hub";
\r
239 char far l12e[] = "Keen explodes into\nthe Quantum\nExplosion Dynamo";
\r
240 char far l13e[] = "Keen faces danger\nin the secret\nKorath III Base";
\r
241 char far l14e[] = "Keen will not be\nin the BWBMegarocket";
\r
242 char far l15e[] = "Keen unexplainedly\nfinds himself by\ntheHigh Scores"; // sic!
\r
244 char far *levelnames[GAMELEVELS] = {
\r
263 char far *levelenter[GAMELEVELS] = {
\r
282 Uint16 bonuslump[] = {
\r
283 KEYGEM_LUMP, KEYGEM_LUMP, KEYGEM_LUMP, KEYGEM_LUMP,
\r
284 SUGAR1_LUMP, SUGAR2_LUMP, SUGAR3_LUMP,
\r
285 SUGAR4_LUMP, SUGAR5_LUMP, SUGAR6_LUMP,
\r
286 ONEUP_LUMP, AMMO_LUMP, KEYCARD_LUMP, 0, 0
\r
289 //============================================================================
\r
292 ===========================
\r
296 ===========================
\r
299 void OpenMapDoor(Sint16 tileX, Sint16 tileY)
\r
302 Uint16 tiles[2][2];
\r
304 for (y=0; y<2; y++)
\r
306 for (x=0; x<2; x++)
\r
308 tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + x + 10);
\r
311 RF_MemToMap(&tiles[0][0], 1, tileX, tileY, 2, 2);
\r
315 ===========================
\r
319 ===========================
\r
322 void CloseMapDoor(Sint16 tileX, Sint16 tileY)
\r
325 Uint16 tiles[2][2];
\r
327 for (y=0; y<2; y++)
\r
329 for (x=0; x<2; x++)
\r
331 tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + x);
\r
334 RF_MemToMap(&tiles[0][0], 1, tileX, tileY, 2, 2);
\r
337 //============================================================================
\r
340 ==========================
\r
344 = Spawn all actors and mark down special places
\r
346 ==========================
\r
349 void ScanInfoPlane(void)
\r
351 Uint16 i, x, y, chunk;
\r
356 InitObjArray(); // start spawning things with a clean slate
\r
358 memset(lumpneeded, 0, sizeof(lumpneeded));
\r
359 gamestate.numfuses = 0;
\r
363 for (y=0; y<mapheight; y++)
\r
365 for (x=0; x<mapwidth; x++)
\r
375 SpawnKeen(x, y, 1);
\r
377 lumpneeded[KEEN_LUMP] = true;
\r
378 CA_MarkGrChunk(SCOREBOXSPR);
\r
382 SpawnKeen(x, y, -1);
\r
384 lumpneeded[KEEN_LUMP] = true;
\r
385 CA_MarkGrChunk(SCOREBOXSPR);
\r
390 lumpneeded[WORLDKEEN_LUMP] = true;
\r
391 CA_MarkGrChunk(SCOREBOXSPR);
\r
392 if (playstate == ex_portout)
\r
394 SpawnWorldKeen(x, y);
\r
398 if (playstate != ex_portout)
\r
400 SpawnWorldKeenPort(x, y);
\r
404 if (gamestate.difficulty < gd_Hard)
\r
407 if (gamestate.difficulty < gd_Normal)
\r
411 lumpneeded[SPARKY_LUMP] = true;
\r
415 if (gamestate.difficulty < gd_Hard)
\r
418 if (gamestate.difficulty < gd_Normal)
\r
422 lumpneeded[MINE_LUMP] = true;
\r
426 if (gamestate.difficulty < gd_Hard)
\r
429 if (gamestate.difficulty < gd_Normal)
\r
432 SpawnSlicestarSlide(x, y, 0);
\r
433 lumpneeded[SLICESTAR_LUMP] = true;
\r
437 if (gamestate.difficulty < gd_Hard)
\r
440 if (gamestate.difficulty < gd_Normal)
\r
443 SpawnRoboRed(x, y);
\r
444 lumpneeded[ROBORED_LUMP] = true;
\r
448 if (gamestate.difficulty < gd_Hard)
\r
451 if (gamestate.difficulty < gd_Normal)
\r
454 SpawnSpirogrip(x, y);
\r
455 lumpneeded[SPIRO_LUMP] = true;
\r
459 if (gamestate.difficulty < gd_Hard)
\r
462 if (gamestate.difficulty < gd_Normal)
\r
465 SpawnSlicestarBounce(x, y);
\r
466 lumpneeded[SLICESTAR_LUMP] = true;
\r
470 if (gamestate.difficulty < gd_Hard)
\r
473 if (gamestate.difficulty < gd_Normal)
\r
476 SpawnSlicestarSlide(x, y, 1);
\r
477 lumpneeded[SLICESTAR_LUMP] = true;
\r
481 RF_SetScrollBlock(x, y, true);
\r
484 // case 26 (teleported map keen) is handled above
\r
490 SpawnPlatform(x, y, info-27, 0);
\r
491 lumpneeded[PLATFORM_LUMP] = true;
\r
494 // case 31 is the block icon
\r
497 SpawnDropPlat(x, y);
\r
498 lumpneeded[PLATFORM_LUMP] = true;
\r
502 SpawnStaticPlat(x, y);
\r
503 lumpneeded[PLATFORM_LUMP] = true;
\r
506 if (gamestate.difficulty > gd_Normal)
\r
508 SpawnStaticPlat(x, y);
\r
509 lumpneeded[PLATFORM_LUMP] = true;
\r
512 if (gamestate.difficulty > gd_Easy)
\r
514 SpawnStaticPlat(x, y);
\r
515 lumpneeded[PLATFORM_LUMP] = true;
\r
522 SpawnGoPlat(x, y, info-36, 0);
\r
523 lumpneeded[PLATFORM_LUMP] = true;
\r
527 SpawnSneakPlat(x, y);
\r
528 lumpneeded[PLATFORM_LUMP] = true;
\r
532 if (gamestate.mapon == 12)
\r
534 gamestate.numfuses = 4;
\r
539 gamestate.numfuses++;
\r
541 lumpneeded[FUSE_LUMP] = true;
\r
545 if (gamestate.difficulty < gd_Hard)
\r
548 if (gamestate.difficulty < gd_Normal)
\r
552 lumpneeded[AMPTON_LUMP] = true;
\r
556 if (gamestate.difficulty < gd_Hard)
\r
559 if (gamestate.difficulty < gd_Normal)
\r
562 SpawnCannon(x, y, 0);
\r
563 lumpneeded[LASER_LUMP] = true;
\r
567 if (gamestate.difficulty < gd_Hard)
\r
570 if (gamestate.difficulty < gd_Normal)
\r
573 SpawnCannon(x, y, 1);
\r
574 lumpneeded[LASER_LUMP] = true;
\r
578 if (gamestate.difficulty < gd_Hard)
\r
581 if (gamestate.difficulty < gd_Normal)
\r
584 SpawnCannon(x, y, 2);
\r
585 lumpneeded[LASER_LUMP] = true;
\r
589 if (gamestate.difficulty < gd_Hard)
\r
592 if (gamestate.difficulty < gd_Normal)
\r
595 SpawnCannon(x, y, 3);
\r
596 lumpneeded[LASER_LUMP] = true;
\r
600 if (gamestate.ammo >= 5)
\r
602 info = 68; // spawn ammo
\r
616 SpawnBonus(x, y, info-57);
\r
617 lumpneeded[bonuslump[info-57]] = true;
\r
620 SpawnBonus(x, y, info-58);
\r
621 lumpneeded[bonuslump[info-58]] = true;
\r
625 if (gamestate.difficulty < gd_Hard)
\r
628 if (gamestate.difficulty < gd_Normal)
\r
632 lumpneeded[VOLTE_LUMP] = true;
\r
636 if (gamestate.difficulty < gd_Hard)
\r
639 if (gamestate.difficulty < gd_Normal)
\r
643 lumpneeded[SHELLEY_LUMP] = true;
\r
647 if (gamestate.difficulty < gd_Hard)
\r
650 if (gamestate.difficulty < gd_Normal)
\r
653 SpawnSpindread(x, y);
\r
654 lumpneeded[SPINDRED_LUMP] = true;
\r
661 SpawnGoPlat(x, y, info-80, 1);
\r
662 lumpneeded[SLOTPLAT_LUMP] = true;
\r
669 SpawnPlatform(x, y, info-84, 1);
\r
670 lumpneeded[SLOTPLAT_LUMP] = true;
\r
674 if (gamestate.difficulty < gd_Hard)
\r
677 if (gamestate.difficulty < gd_Normal)
\r
681 lumpneeded[MASTER_LUMP] = true;
\r
684 // cases 91 to 98 are direction arrows
\r
687 if (gamestate.difficulty < gd_Hard)
\r
690 if (gamestate.difficulty < gd_Normal)
\r
693 SpawnShikadi(x, y);
\r
694 lumpneeded[SHIKADI_LUMP] = true;
\r
698 if (gamestate.difficulty < gd_Hard)
\r
701 if (gamestate.difficulty < gd_Normal)
\r
705 lumpneeded[SHOCKSHUND_LUMP] = true;
\r
709 if (gamestate.difficulty < gd_Hard)
\r
712 if (gamestate.difficulty < gd_Normal)
\r
715 SpawnSphereful(x, y);
\r
716 lumpneeded[SPHEREFUL_LUMP] = true;
\r
719 // cases 108 to 123 are wall collision icons for the WallDebug cheat
\r
722 SpawnScottie(x, y);
\r
723 lumpneeded[SCOTTIE_LUMP] = true;
\r
727 lumpneeded[TELEPORT_LUMP] = true;
\r
733 for (ob = player; ob; ob = ob->next)
\r
735 if (ob->active != ac_allways)
\r
736 ob->active = ac_no;
\r
739 for (i = 0; i < NUMLUMPS; i++)
\r
743 for (chunk = lumpstart[i]; chunk <= lumpend[i]; chunk++)
\r
745 CA_MarkGrChunk(chunk);
\r
750 // Keen 5 addition to PatchWorldMap (PatchWorldMap is shared across Keen 4-6)
\r
751 if (gamestate.mapon == 0)
\r
753 info = CONVERT_GLOBAL_TO_TILE(player->y);
\r
754 if (info < 75 || info > 100)
\r
756 CloseMapDoor(24, 76);
\r
757 OpenMapDoor(22, 55);
\r
759 if (gamestate.leveldone[4] && gamestate.leveldone[6] && gamestate.leveldone[8] && gamestate.leveldone[10]
\r
760 && (info > 39 || info > 100) )
\r
762 OpenMapDoor(26, 55);
\r
764 if (info <= 39 || info > 100)
\r
766 OpenMapDoor(24, 30);
\r
772 =============================================================================
\r
776 =============================================================================
\r
780 ------------------------------------------------------------------------------
\r
781 The galaxy explosion chunk contains data in the following format:
\r
790 The values are stored as "fixed point" numbers (divide each number by 128 to
\r
791 get the amount in pixel units). The code in MoveStars basically does the
\r
794 1. set all pixels on the screen to black
\r
796 2. for each of the 4000 entries do:
\r
798 2.1 x := posx[i] + velx[i]
\r
800 2.2 if x (in pixels) is < 0 or > 320 then go to 2.8
\r
804 2.4 y := posy[i] + vely[i]
\r
806 2.5 if y (in pixels) is < 0 or > 200 then go to 2.8
\r
810 2.7 draw a white pixel at position (x, y) on the screen
\r
812 2.8 continue with the next entry
\r
814 ------------------------------------------------------------------------------
\r
817 #if GRMODE == CGAGR
\r
819 Uint8 plotpixels[8] = {0xC0, 0x30, 0x0C, 0x03};
\r
822 ===========================
\r
826 ===========================
\r
829 void MoveStars(void)
\r
834 mov ds, word ptr grsegs + 2*GALAXY;
\r
843 mov ax, [si]; // get posx
\r
844 add ax, [si+8000]; // add velx
\r
845 cmp ax, 40960; // check if new posx is on screen
\r
847 mov [si], ax; // set new posx
\r
848 mov bx, [si+16000]; // get posy
\r
849 add bx, [si+24000]; // add vely
\r
850 cmp bx, 25600; // check if new posy is on screen
\r
852 mov [si+16000], bx; // set new posy
\r
856 mov di, word ptr ss:ylookup[bx];
\r
864 mov al, BYTE PTR ss:plotpixels[bx];
\r
865 or es:[di], al; // draw the pixel
\r
877 ===========================
\r
881 ===========================
\r
884 void GameOver(void)
\r
889 CA_CacheGrChunk(MILKYWAYPIC);
\r
890 CA_CacheGrChunk(GALAXY);
\r
891 CA_CacheGrChunk(GAMEOVERPIC);
\r
893 VW_ClearVideo(BLACK);
\r
894 VWB_DrawPic(0, 0, MILKYWAYPIC);
\r
897 SD_WaitSoundDone();
\r
898 SD_PlaySound(SND_GAMEOVER2);
\r
899 SD_WaitSoundDone();
\r
900 SD_PlaySound(SND_GAMEOVER1);
\r
902 IN_ClearKeysDown();
\r
903 VW_SetLineWidth(80);
\r
905 for (i=0; i<60; i++)
\r
907 lasttimecount = TimeCount;
\r
911 do {} while (TimeCount-lasttimecount < 4);
\r
917 VW_SetLineWidth(SCREENWIDTH);
\r
918 VW_ClearVideo(BLACK);
\r
920 VWB_DrawPic(32, 80, GAMEOVERPIC);
\r
922 IN_UserInput(24*TickBase, false);
\r
926 //============================================================================
\r
930 Uint16 dim[18] = {8, 8, 7, 15, 7, 8, 0, 8, 7, 15, 7, 8, 0, 0, 0, 0, 0, 0};
\r
931 Uint16 bright[18] = {7, 7, 7, 7, 7, 15, 7, 8, 0, 7, 15, 7, 8, 0, 0, 0, 0, 0};
\r
932 Uint8 galaxycolors[17] = {0, 1, 2, 3, 4, 5, 6, 7, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 3};
\r
933 Uint8 plotpixels[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
\r
936 ===========================
\r
940 ===========================
\r
943 void MoveStars(Uint16 dest)
\r
947 mov al, GC_BITMASK;
\r
950 mov ds, word ptr grsegs + 2*GALAXY;
\r
961 mov ax, [si]; // get posx
\r
962 add ax, [si+8000]; // add velx
\r
963 cmp ax, 40960; // check if new posx is on screen
\r
965 mov [si], ax; // set new posx
\r
966 mov bx, [si+16000]; // get posy
\r
967 add bx, [si+24000]; // add vely
\r
968 cmp bx, 25600; // check if new posy is on screen
\r
970 mov [si+16000], bx; // set new posy
\r
974 mov di, word ptr ss:ylookup[bx];
\r
983 mov al, BYTE PTR ss:plotpixels[bx];
\r
986 xchg al, es:[di]; // draw the pixel
\r
1000 ===========================
\r
1004 ===========================
\r
1007 void SetCrtc(Uint16 addr)
\r
1012 mov dx, CRTC_INDEX;
\r
1013 mov al, CRTC_STARTHIGH;
\r
1019 mov al, CRTC_STARTLOW;
\r
1029 ===========================
\r
1033 ===========================
\r
1036 void GameOver(void)
\r
1042 CA_CacheGrChunk(MILKYWAYPIC);
\r
1043 CA_CacheGrChunk(GALAXY);
\r
1044 CA_CacheGrChunk(GAMEOVERPIC);
\r
1045 VW_SetLineWidth(40);
\r
1046 VW_SetScreen(0, 0);
\r
1049 VW_DrawPic(0, 0, MILKYWAYPIC);
\r
1050 VW_ScreenToScreen(0, 0x2000, 40, 200);
\r
1052 IN_ClearKeysDown();
\r
1053 SD_PlaySound(SND_GAMEOVER2);
\r
1055 for (i=0; i<18; i++)
\r
1057 galaxycolors[8] = dim[i];
\r
1058 galaxycolors[7] = bright[i];
\r
1060 SetPalette(galaxycolors);
\r
1069 SD_PlaySound(SND_GAMEOVER1);
\r
1071 for (i=0; i<30; i++)
\r
1073 lasttimecount = TimeCount;
\r
1074 MoveStars(0x2000);
\r
1076 do {} while (TimeCount-lasttimecount < 4);
\r
1078 lasttimecount = TimeCount;
\r
1081 do {} while (TimeCount-lasttimecount < 4);
\r
1089 VW_ClearVideo(BLACK);
\r
1090 VW_SetLineWidth(SCREENWIDTH);
\r
1091 VW_SetDefaultColors();
\r
1094 VWB_DrawPic(32, 80, GAMEOVERPIC);
\r
1095 VW_UpdateScreen();
\r
1096 IN_UserInput(24*TickBase, false);
\r
1102 //============================================================================
\r
1105 ===========================
\r
1109 ===========================
\r
1112 void FinishedFuse(void)
\r
1114 SD_WaitSoundDone();
\r
1118 CA_ClearMarks(); // don't cache more than we actually need here
\r
1120 CA_MarkGrChunk(KEENTALK1PIC);
\r
1121 CA_MarkGrChunk(KEENTALK2PIC);
\r
1122 CA_CacheMarks(NULL);
\r
1124 VW_FixRefreshBuffer();
\r
1125 US_CenterWindow(26, 8);
\r
1127 VWB_DrawPic(WindowX+WindowW, WindowY, KEENTALK1PIC);
\r
1129 if (gamestate.mapon == 13)
\r
1132 "I wonder what that\n"
\r
1133 "fuse was for....\n"
\r
1139 "One of the four\n"
\r
1140 "machines protecting the\n"
\r
1141 "main elevator shaft--\n"
\r
1145 VW_UpdateScreen();
\r
1147 IN_ClearKeysDown();
\r
1150 VWB_DrawPic(WindowX+WindowW, WindowY, KEENTALK2PIC);
\r
1151 VW_UpdateScreen();
\r
1153 IN_ClearKeysDown();
\r