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
33 ScanCode firescan = sc_Space;
\r
35 boolean singlestep, jumpcheat, godmode, keenkilled;
\r
40 objtype *new, *check, *player, *scoreobj;
\r
42 Uint16 originxtilemax;
\r
43 Uint16 originytilemax;
\r
46 boolean button2, button3; // never used
\r
52 boolean oldshooting, showscorebox, joypad;
\r
57 boolean jumpbutton, jumpheld, pogobutton, pogoheld, firebutton, fireheld, upheld;
\r
61 =============================================================================
\r
65 =============================================================================
\r
73 objtype objarray[MAXACTORS];
\r
75 objtype *objfreelist;
\r
77 Sint16 inactivateleft;
\r
78 Sint16 inactivateright;
\r
79 Sint16 inactivatetop;
\r
80 Sint16 inactivatebottom;
\r
83 Uint16 __dummy__; // never used, but must be present to recreate the original EXE
\r
92 Sint16 oldfirecount;
\r
94 //===========================================================================
\r
104 void CountObjects(void)
\r
106 Uint16 activeobjects, inactiveobjects;
\r
109 activeobjects = inactiveobjects = 0;
\r
110 for (ob=player; ob; ob=ob->next)
\r
121 VW_FixRefreshBuffer();
\r
122 US_CenterWindow(18, 4);
\r
124 US_Print("Active Objects :");
\r
125 US_PrintUnsigned(activeobjects);
\r
126 US_Print("\nInactive Objects:");
\r
127 US_PrintUnsigned(inactiveobjects);
\r
140 void DebugMemory(void)
\r
142 VW_FixRefreshBuffer();
\r
143 US_CenterWindow(16, 7);
\r
144 US_CPrint("Memory Usage");
\r
145 US_CPrint("------------");
\r
146 US_Print("Total :");
\r
147 US_PrintUnsigned((mminfo.mainmem+mminfo.EMSmem+mminfo.XMSmem)/1024);
\r
148 US_Print("k\nFree :");
\r
149 US_PrintUnsigned(MM_UnusedMemory()/1024);
\r
150 US_Print("k\nWith purge:");
\r
151 US_PrintUnsigned(MM_TotalFree()/1024);
\r
155 #if GRMODE != CGAGR
\r
161 ===================
\r
165 ===================
\r
168 void TestSprites(void)
\r
170 Uint16 infox, infoy;
\r
171 Sint16 chunk, oldchunk;
\r
173 Uint16 infobottom, drawx;
\r
174 spritetabletype far *info;
\r
180 VW_FixRefreshBuffer();
\r
181 US_CenterWindow(30, 17);
\r
183 US_CPrint("Sprite Test");
\r
184 US_CPrint("-----------");
\r
186 infox = (PrintX + 56) & ~7;
\r
187 drawx = infox + 40;
\r
188 US_Print("Chunk:\nWidth:\nHeight:\nOrgx:\nOrgy:\nXl:\nYl:\nXh:\nYh:\nShifts:\nMem:\n");
\r
189 infobottom = PrintY;
\r
190 chunk = STARTSPRITES;
\r
194 if (chunk >= STARTSPRITES+NUMSPRITES)
\r
196 chunk = STARTSPRITES+NUMSPRITES-1;
\r
198 else if (chunk < STARTSPRITES)
\r
200 chunk = STARTSPRITES;
\r
202 info = &spritetable[chunk-STARTSPRITES];
\r
203 block = grsegs[chunk];
\r
204 VWB_Bar(infox, infoy, 40, infobottom-infoy, WHITE);
\r
207 US_PrintUnsigned(chunk);
\r
210 US_PrintUnsigned(info->width);
\r
213 US_PrintUnsigned(info->height);
\r
216 US_PrintSigned(info->orgx);
\r
219 US_PrintSigned(info->orgy);
\r
222 US_PrintSigned(info->xl);
\r
225 US_PrintSigned(info->yl);
\r
228 US_PrintSigned(info->xh);
\r
231 US_PrintSigned(info->yh);
\r
234 US_PrintSigned(info->shifts);
\r
243 size = ((spritetype far *)block)->sourceoffset[3] + ((spritetype far *)block)->planesize[3]*5;
\r
244 size = (size + 15) & ~15; //round up to multiples of 16
\r
245 totalsize += size; //useless: the value stored in 'totalsize' is never used
\r
246 US_PrintUnsigned(size);
\r
252 VWB_Bar(drawx, infoy, 110, infobottom-infoy, WHITE);
\r
257 US_Print("Shift:");
\r
258 US_PrintUnsigned(shift);
\r
260 VWB_DrawSprite(drawx + 2*shift + 16, PrintY, chunk);
\r
263 scan = IN_WaitForKey();
\r
274 if (chunk >= STARTSPRITES+NUMSPRITES)
\r
276 chunk = STARTSPRITES+NUMSPRITES-1;
\r
281 if (chunk < STARTSPRITES)
\r
283 chunk = STARTSPRITES;
\r
292 case sc_RightArrow:
\r
302 } while (chunk == oldchunk);
\r
308 ===================
\r
312 ===================
\r
315 void PicturePause(void)
\r
321 // wait for a key press, abort if it's not Enter
\r
323 IN_ClearKeysDown();
\r
325 if (LastScan != sc_Enter)
\r
327 IN_ClearKeysDown();
\r
331 SD_PlaySound(SND_JUMP);
\r
332 SD_WaitSoundDone();
\r
335 // rearrange onscreen image into base EGA layout, so that it
\r
336 // can be grabbed correctly by an external screenshot tool
\r
338 source = displayofs + panadjust;
\r
340 VW_ColorBorder(15); // white (can't use WHITE as parameter, since that's defined as 3 for CGA and this must use 15)
\r
341 VW_SetLineWidth(40);
\r
342 VW_SetScreen(0, 0);
\r
344 if (source < 0x10000l-200*64)
\r
347 // copy top line first
\r
349 for (y=0; y<200; y++)
\r
351 VW_ScreenToScreen(source+y*64, y*40, 40, 1);
\r
357 // copy bottom line first
\r
359 for (y=199; y>=0; y--)
\r
361 VW_ScreenToScreen(source+y*64, y*40, 40, 1);
\r
366 // shut down input manager so that screenshot tool can see input again
\r
370 SD_PlaySound(SND_EXTRAKEEN);
\r
371 SD_WaitSoundDone();
\r
374 // shut down the remaining ID managers, except VW (stay in graphics mode!)
\r
384 // wait until user hits Escape
\r
386 while (((bioskey(0) >> 8) & 0xFF) != sc_Escape);
\r
389 // back to text mode and exit to DOS
\r
396 ===================
\r
400 ===================
\r
403 void MaskOnTile(Uint16 dest, Uint16 source)
\r
406 Uint16 _seg *sourceseg;
\r
407 Uint16 _seg *destseg;
\r
408 Uint16 sourceval, maskindex, sourcemask;
\r
410 sourceseg = (grsegs+STARTTILE16M)[source];
\r
411 destseg = (grsegs+STARTTILE16M)[dest];
\r
412 for (i=0; i<64; i++)
\r
414 maskindex = i & 15;
\r
416 sourceval = sourceseg[16+i];
\r
418 sourceval = (sourceseg+16)[i];
\r
420 sourcemask = sourceseg[maskindex];
\r
421 destseg[maskindex] &= sourcemask;
\r
422 destseg[16+i] &= sourcemask;
\r
423 destseg[16+i] |= sourceval;
\r
428 ===================
\r
432 ===================
\r
435 void WallDebug(void)
\r
439 VW_FixRefreshBuffer();
\r
440 US_CenterWindow(24, 3);
\r
441 US_PrintCentered("WORKING");
\r
443 for (i=STARTTILE16M+108; i<STARTTILE16M+124; i++)
\r
445 CA_CacheGrChunk(i);
\r
447 for (i=0; i<NUMTILE16M; i++)
\r
449 if (!grsegs[STARTTILE16M+i])
\r
453 val = tinf[i+NORTHWALL] & 7;
\r
456 MaskOnTile(i, val+107);
\r
458 val = tinf[i+SOUTHWALL] & 7;
\r
461 MaskOnTile(i, val+115);
\r
463 val = tinf[i+EASTWALL] & 7;
\r
466 strcpy(str, "WallDebug: East wall other than 1:");
\r
473 MaskOnTile(i, val+114); //Note: val is always 1 here, so you could use 115 as 2nd arg
\r
475 val = tinf[i+WESTWALL] & 7;
\r
478 strcpy(str, "WallDebug: West wall other than 1:");
\r
485 MaskOnTile(i, val+122); //Note: val is always 1 here, so you could use 123 as 2nd arg
\r
491 //===========================================================================
\r
501 boolean DebugKeys(void)
\r
503 Sint16 level, i, esc;
\r
505 if (Keyboard[sc_B] && ingame) // B = border color
\r
507 VW_FixRefreshBuffer();
\r
508 US_CenterWindow(24, 3);
\r
510 US_Print(" Border color (0-15):");
\r
512 esc = !US_LineInput(px, py, str, NULL, true, 2, 0);
\r
516 if (level >= 0 && level <= 15)
\r
518 VW_ColorBorder(level);
\r
524 if (Keyboard[sc_C] && ingame) // C = count objects
\r
530 if (Keyboard[sc_D] && ingame) // D = start / end demo record
\r
532 if (DemoMode == demo_Off)
\r
536 else if (DemoMode == demo_Record)
\r
539 playstate = ex_completed;
\r
544 if (Keyboard[sc_E] && ingame) // E = quit level
\r
550 playstate = ex_completed;
\r
551 //BUG? there is no return in this branch (should return false)
\r
554 if (Keyboard[sc_G] && ingame) // G = god mode
\r
556 VW_FixRefreshBuffer();
\r
557 US_CenterWindow(12, 2);
\r
560 US_PrintCentered("God mode OFF");
\r
564 US_PrintCentered("God mode ON");
\r
571 else if (Keyboard[sc_I]) // I = item cheat
\r
573 VW_FixRefreshBuffer();
\r
574 US_CenterWindow(12, 3);
\r
575 US_PrintCentered("Free items!");
\r
576 for (i=0; i<4; i++)
\r
578 gamestate.keys[i] = 99;
\r
580 gamestate.ammo = 99;
\r
582 gamestate.wetsuit = true;
\r
583 #elif defined KEEN5
\r
584 gamestate.keycard = true;
\r
585 #elif defined KEEN6
\r
586 gamestate.passcardstate=gamestate.hookstate=gamestate.sandwichstate = 1;
\r
593 else if (Keyboard[sc_J]) // J = jump cheat
\r
596 VW_FixRefreshBuffer();
\r
597 US_CenterWindow(18, 3);
\r
600 US_PrintCentered("Jump cheat ON");
\r
604 US_PrintCentered("Jump cheat OFF");
\r
610 else if (Keyboard[sc_M]) // M = memory info
\r
615 else if (Keyboard[sc_N]) // N = no clip
\r
617 VW_FixRefreshBuffer();
\r
618 US_CenterWindow(18, 3);
\r
619 if (player->needtoclip)
\r
621 US_PrintCentered("No clipping ON");
\r
622 player->needtoclip = cl_noclip;
\r
626 US_PrintCentered("No clipping OFF");
\r
627 player->needtoclip = cl_midclip;
\r
633 else if (Keyboard[sc_P]) // P = pause with no screen disruptioon
\r
635 IN_ClearKeysDown();
\r
639 else if (Keyboard[sc_S] && ingame) // S = slow motion
\r
641 singlestep ^= true;
\r
642 VW_FixRefreshBuffer();
\r
643 US_CenterWindow(18, 3);
\r
646 US_PrintCentered("Slow motion ON");
\r
650 US_PrintCentered("Slow motion OFF");
\r
656 else if (Keyboard[sc_T]) // T = sprite test
\r
661 else if (Keyboard[sc_V]) // V = extra VBLs
\r
663 VW_FixRefreshBuffer();
\r
664 US_CenterWindow(30, 3);
\r
666 US_Print(" Add how many extra VBLs(0-8):");
\r
668 esc = !US_LineInput(px, py, str, NULL, true, 2, 0);
\r
672 if (level >= 0 && level <= 8)
\r
679 else if (Keyboard[sc_W] && ingame) // W = warp to level
\r
681 VW_FixRefreshBuffer();
\r
682 US_CenterWindow(26, 3);
\r
684 US_Print(" Warp to which level(1-18):");
\r
686 esc = !US_LineInput(px, py, str, NULL, true, 2, 0);
\r
690 if (level > 0 && level <= 18)
\r
692 gamestate.mapon = level;
\r
693 playstate = ex_warped;
\r
698 else if (Keyboard[sc_Y]) // Y = wall debug
\r
703 else if (Keyboard[sc_Z]) // Z = game over
\r
705 gamestate.lives = 0;
\r
712 //===========================================================================
\r
722 void UserCheat(void)
\r
726 for (i=sc_A; i<=sc_Z; i++) //Note: this does NOT check the keys in alphabetical order!
\r
728 if (i != sc_B && i != sc_A && i != sc_T && Keyboard[i])
\r
733 US_CenterWindow(20, 7);
\r
738 "You just got all\n"
\r
739 "the keys, 99 shots,\n"
\r
740 "and an extra keen!");
\r
744 gamestate.ammo = 99;
\r
747 gamestate.keycard = true;
\r
749 gamestate.keys[0] = gamestate.keys[1] = gamestate.keys[2] = gamestate.keys[3] = 1;
\r
752 //===========================================================================
\r
755 =====================
\r
759 =====================
\r
762 void CheckKeys(void)
\r
764 if (screenfaded) // don't do anything with a faded screen
\r
770 // Enter for status screen
\r
772 if (Keyboard[sc_Enter] || (GravisGamepad && GravisAction[ga_Status]))
\r
775 IN_ClearKeysDown();
\r
777 lasttimecount = TimeCount; // BUG: should be the other way around
\r
781 // pause key wierdness can't be checked as a scan code
\r
786 VW_FixRefreshBuffer();
\r
787 US_CenterWindow(8, 3);
\r
788 US_PrintCentered("PAUSED");
\r
798 // F1 to enter help screens
\r
800 if (LastScan == sc_F1)
\r
804 StartMusic(gamestate.mapon);
\r
807 scoreobj->temp2 = -1;
\r
808 scoreobj->temp1 = -1;
\r
809 scoreobj->temp3 = -1;
\r
810 scoreobj->temp4 = -1;
\r
819 // F2-F7/ESC to enter control panel
\r
821 if (LastScan >= sc_F2 && LastScan <= sc_F7 || LastScan == sc_Escape)
\r
823 VW_FixRefreshBuffer();
\r
827 StartMusic(gamestate.mapon);
\r
828 if (!showscorebox && scoreobj->sprite)
\r
830 RF_RemoveSprite(&scoreobj->sprite);
\r
834 scoreobj->temp2 = -1;
\r
835 scoreobj->temp1 = -1;
\r
836 scoreobj->temp3 = -1;
\r
837 scoreobj->temp4 = -1;
\r
839 IN_ClearKeysDown();
\r
842 playstate = ex_resetgame;
\r
844 else if (!loadedgame)
\r
851 playstate = ex_abortgame;
\r
855 playstate = ex_loadedgame;
\r
857 lasttimecount = TimeCount; // BUG: should be the other way around
\r
863 if (LastScan == sc_F9)
\r
868 IN_ClearKeysDown();
\r
869 while (LastScan != sc_Escape);
\r
870 VW_SetScreenMode(GRMODE);
\r
871 VW_ColorBorder(bordercolor);
\r
873 IN_ClearKeysDown();
\r
874 lasttimecount = TimeCount; // BUG: should be the other way around
\r
880 // B-A-T cheat code
\r
882 if (Keyboard[sc_B] && Keyboard[sc_A] && Keyboard[sc_T])
\r
888 // F10-? debug keys
\r
890 if (debugok && Keyboard[sc_F10])
\r
895 lasttimecount = TimeCount; // BUG: should be the other way around
\r
900 // Ctrl-S toggles sound (only in storedemo mode)
\r
902 if (storedemo && Keyboard[sc_Control] && LastScan == sc_S)
\r
904 if (SoundMode != sdm_Off)
\r
906 SD_SetSoundMode(sdm_Off);
\r
907 SD_SetMusicMode(smm_Off);
\r
913 SD_SetSoundMode(sdm_AdLib);
\r
915 SD_SetMusicMode(smm_AdLib);
\r
919 SD_SetSoundMode(sdm_PC);
\r
920 SD_SetMusicMode(smm_Off);
\r
922 CA_LoadAllSounds();
\r
927 // Ctrl-Q quick quit
\r
929 if (Keyboard[sc_Control] && LastScan == sc_Q)
\r
931 IN_ClearKeysDown();
\r
936 //===========================================================================
\r
946 void PrintNumbers(Sint16 x, Sint16 y, Sint16 maxlen, Sint16 basetile, Sint32 number)
\r
952 ltoa(number, buffer, 10);
\r
953 len = strlen(buffer);
\r
957 VWB_DrawTile8(x, y, basetile);
\r
963 VWB_DrawTile8(x, y, basetile+buffer[len-i]+(1-'0'));
\r
977 #if GRMODE == CGAGR
\r
979 #define BACKCOLOR WHITE
\r
980 #define TEXTBACK BLACK
\r
981 #define NUMBERBACK BLACK
\r
985 #define BACKCOLOR LIGHTGRAY
\r
986 #define TEXTBACK WHITE
\r
987 #define NUMBERBACK BLACK
\r
991 void DrawStatusWindow(void)
\r
993 Sint16 off, x, y, w, h, i;
\r
994 Uint16 width, height;
\r
1000 VWB_DrawTile8(x, y, 54);
\r
1001 VWB_DrawTile8(x, y+h, 60);
\r
1002 for (i=x+8; i<=x+w-8; i+=8)
\r
1004 VWB_DrawTile8(i, y, 55);
\r
1005 VWB_DrawTile8(i, y+h, 61);
\r
1007 VWB_DrawTile8(i, y, 56);
\r
1008 VWB_DrawTile8(i, y+h, 62);
\r
1009 for (i=y+8; i<=y+h-8; i+=8)
\r
1011 VWB_DrawTile8(x, i, 57);
\r
1012 VWB_DrawTile8(x+w, i, 59);
\r
1014 VWB_Bar(72, 24, 176, 136, BACKCOLOR);
\r
1019 US_CPrint("LOCATION");
\r
1020 VWB_Bar(79, 38, 162, 20, TEXTBACK);
\r
1022 if (mapon == 0 && player->y > 100*TILEGLOBAL)
\r
1023 _fstrcpy(str, levelnames[13]);
\r
1025 _fstrcpy(str, levelnames[gamestate.mapon]);
\r
1027 _fstrcpy(str, levelnames[gamestate.mapon]);
\r
1029 SizeText(str, &width, &height);
\r
1030 PrintY = (20-height)/2+40-2;
\r
1036 US_CPrint("SCORE");
\r
1037 VWB_Bar(79, 71, 66, 10, NUMBERBACK);
\r
1038 PrintNumbers(80, 72, 8, 41, gamestate.score);
\r
1043 US_CPrint("EXTRA");
\r
1044 VWB_Bar(175, 71, 66, 10, NUMBERBACK);
\r
1045 PrintNumbers(176, 72, 8, 41, gamestate.nextextra);
\r
1051 US_CPrint("RESCUED");
\r
1052 VWB_Bar(79, 95, 66, 10, NUMBERBACK);
\r
1053 for (i = 0; i < gamestate.rescued; i++, off+=8)
\r
1055 VWB_DrawTile8(i*8 + 80, 96, 40);
\r
1057 #elif defined KEEN5
\r
1060 US_Print("KEYCARD");
\r
1061 VWB_Bar(135, 91, 10, 10, NUMBERBACK);
\r
1062 if (gamestate.keycard)
\r
1064 VWB_DrawTile8(136, 92, 40);
\r
1071 US_CPrint("LEVEL");
\r
1072 VWB_Bar(175, 95, 66, 10, TEXTBACK);
\r
1076 switch (gamestate.difficulty)
\r
1079 US_CPrint("Easy");
\r
1082 US_CPrint("Normal");
\r
1085 US_CPrint("Hard");
\r
1092 US_Print("ITEMS");
\r
1093 VWB_Bar(127, 95, 26, 10, NUMBERBACK);
\r
1094 if (gamestate.sandwichstate == 1)
\r
1096 VWB_DrawTile8(128, 96, 2);
\r
1100 VWB_DrawTile8(128, 96, 1);
\r
1102 if (gamestate.hookstate == 1)
\r
1104 VWB_DrawTile8(136, 96, 4);
\r
1108 VWB_DrawTile8(136, 96, 3);
\r
1110 if (gamestate.passcardstate == 1)
\r
1112 VWB_DrawTile8(144, 96, 6);
\r
1116 VWB_DrawTile8(144, 96, 5);
\r
1123 VWB_Bar(119, 111, 34, 10, NUMBERBACK);
\r
1124 for (i = 0; i < 4; i++)
\r
1126 if (gamestate.keys[i])
\r
1128 VWB_DrawTile8(i*8+120, 112, 36+i);
\r
1135 VWB_Bar(215, 111, 26, 10, NUMBERBACK);
\r
1136 PrintNumbers(216, 112, 3, 41, gamestate.ammo);
\r
1140 US_Print("KEENS");
\r
1141 VWB_Bar(127, 127, 18, 10, NUMBERBACK);
\r
1142 PrintNumbers(128, 128, 2, 41, gamestate.lives);
\r
1146 US_Print(DROPSNAME);
\r
1147 VWB_Bar(224, 127, 16, 10, NUMBERBACK);
\r
1148 PrintNumbers(224, 128, 2, 41, gamestate.drops);
\r
1151 VWB_Bar(79, 143, 66, 10, TEXTBACK);
\r
1155 if (gamestate.wetsuit)
\r
1157 US_CPrint("Wetsuit");
\r
1165 // draw the tiles for "PRESS A KEY":
\r
1166 for (i = 0; i < 10; i++)
\r
1168 VWB_DrawTile8(i*8+STATUS_PRESSKEY_X, 140, i+72);
\r
1169 VWB_DrawTile8(i*8+STATUS_PRESSKEY_X, 148, i+82);
\r
1174 ==================
\r
1176 = ScrollStatusWindow
\r
1178 ==================
\r
1181 void ScrollStatusWindow(void)
\r
1183 Uint16 source, dest;
\r
1186 if (vislines > 152)
\r
1188 height = vislines - 152;
\r
1189 source = windowofs + panadjust + 8;
\r
1190 dest = bufferofs + panadjust + 8;
\r
1191 VW_ScreenToScreen(source, dest, 192/BYTEPIXELS, height);
\r
1192 VW_ClipDrawMPic((pansx+136)/BYTEPIXELS, -(16-height)+pansy, METALPOLEPICM);
\r
1193 source = windowofs + panadjust + 16*SCREENWIDTH + 8*CHARWIDTH;
\r
1194 dest = bufferofs + panadjust + height*SCREENWIDTH + 8;
\r
1199 source = windowofs + panadjust + (152-vislines)*SCREENWIDTH + 16*SCREENWIDTH + 8*CHARWIDTH;
\r
1200 dest = bufferofs + panadjust + 8;
\r
1201 height = vislines;
\r
1205 VW_ScreenToScreen(source, dest, 192/BYTEPIXELS, height);
\r
1209 height = 168-vislines;
\r
1210 source = masterofs + panadjust + vislines*SCREENWIDTH + 8;
\r
1211 dest = bufferofs + panadjust + vislines*SCREENWIDTH + 8;
\r
1212 VW_ScreenToScreen(source, dest, 192/BYTEPIXELS, height);
\r
1213 height = vislines;
\r
1214 source = windowofs + panadjust + 8 - 24/BYTEPIXELS;
\r
1215 dest = bufferofs + panadjust + 8 - 24/BYTEPIXELS;
\r
1217 VW_ScreenToScreen(source, dest, 24/BYTEPIXELS, height);
\r
1221 height = vislines + -72;
\r
1224 source = windowofs + panadjust + 8 - 24/BYTEPIXELS;
\r
1225 dest = bufferofs + panadjust + 8 - 24/BYTEPIXELS;
\r
1227 VW_ScreenToScreen(source, dest, 24/BYTEPIXELS, height);
\r
1230 if (vislines >= 72)
\r
1232 VW_ClipDrawMPic((pansx+40)/BYTEPIXELS, vislines-168+pansy, CORDPICM);
\r
1234 VW_UpdateScreen();
\r
1238 ==================
\r
1242 ==================
\r
1245 void StatusWindow(void)
\r
1247 #if GRMODE == CGAGR
\r
1249 if (Keyboard[sc_A] && Keyboard[sc_2])
\r
1251 US_CenterWindow(20, 2);
\r
1253 US_Print("Debug keys active");
\r
1254 VW_UpdateScreen();
\r
1263 DrawStatusWindow();
\r
1264 VW_UpdateScreen();
\r
1265 IN_ClearKeysDown();
\r
1270 Uint16 oldbufferofs;
\r
1277 if (Keyboard[sc_A] && Keyboard[sc_2])
\r
1279 US_CenterWindow(20, 2);
\r
1281 US_Print("Debug keys active");
\r
1282 VW_UpdateScreen();
\r
1288 RFL_InitAnimList();
\r
1289 oldbufferofs = bufferofs;
\r
1290 bufferofs = windowofs = RF_FindFreeBuffer();
\r
1291 VW_ScreenToScreen(displayofs, displayofs, 44, 224); // useless (source and dest offsets are identical)
\r
1292 VW_ScreenToScreen(displayofs, masterofs, 44, 224);
\r
1293 VW_ScreenToScreen(displayofs, bufferofs, 44, 168);
\r
1294 DrawStatusWindow();
\r
1295 bufferofs = oldbufferofs;
\r
1298 SD_PlaySound(SND_SHOWSTATUS);
\r
1301 RF_SetRefreshHook(ScrollStatusWindow);
\r
1306 if (vislines == 168)
\r
1308 vislines = vislines + tics*8;
\r
1309 if (vislines > 168)
\r
1314 RF_SetRefreshHook(NULL);
\r
1315 IN_ClearKeysDown();
\r
1318 SD_PlaySound(SND_HIDESTATUS);
\r
1321 RF_SetRefreshHook(ScrollStatusWindow);
\r
1326 if (vislines == 0)
\r
1328 vislines = vislines - tics*8;
\r
1333 RF_SetRefreshHook(NULL);
\r
1335 scoreobj->x = 0; //force scorebox to redraw?
\r
1340 //===========================================================================
\r
1343 ==================
\r
1347 ==================
\r
1350 void CenterActor(objtype *ob)
\r
1352 Uint16 orgx, orgy;
\r
1354 centerlevel = 140;
\r
1355 if (ob->x < 152*PIXGLOBAL)
\r
1361 orgx = ob->x - 152*PIXGLOBAL;
\r
1365 if (ob->y < 80*PIXGLOBAL)
\r
1371 orgy = ob->y - 80*PIXGLOBAL;
\r
1376 if (ob->bottom < 140*PIXGLOBAL)
\r
1382 orgy = ob->bottom - 140*PIXGLOBAL;
\r
1385 if (!scorescreenkludge)
\r
1387 RF_NewPosition(orgx, orgy);
\r
1391 // update limits for onscreen and inactivate checks
\r
1393 originxtilemax = (originxtile + PORTTILESWIDE) - 1;
\r
1394 originytilemax = (originytile + PORTTILESHIGH) - 1;
\r
1395 inactivateleft = originxtile - INACTIVATEDIST;
\r
1396 if (inactivateleft < 0)
\r
1398 inactivateleft = 0;
\r
1400 inactivateright = originxtilemax + INACTIVATEDIST;
\r
1401 if (inactivateright < 0)
\r
1403 inactivateright = 0;
\r
1405 inactivatetop = originytile - INACTIVATEDIST;
\r
1406 if (inactivatetop < 0)
\r
1408 inactivatetop = 0;
\r
1410 inactivatebottom = originytilemax + INACTIVATEDIST;
\r
1411 if (inactivatebottom < 0)
\r
1413 inactivatebottom = 0;
\r
1417 //===========================================================================
\r
1420 ==================
\r
1422 = WorldScrollScreen
\r
1424 = Scroll if Keen is nearing an edge
\r
1426 ==================
\r
1429 void WorldScrollScreen(objtype *ob)
\r
1431 Sint16 xscroll, yscroll;
\r
1436 if (ob->left < originxglobal + 9*TILEGLOBAL)
\r
1438 xscroll = ob->left - (originxglobal + 9*TILEGLOBAL);
\r
1440 else if (ob->right > originxglobal + 12*TILEGLOBAL)
\r
1442 xscroll = ob->right + 16 - (originxglobal + 12*TILEGLOBAL);
\r
1449 if (ob->top < originyglobal + 5*TILEGLOBAL)
\r
1451 yscroll = ob->top - (originyglobal + 5*TILEGLOBAL);
\r
1453 else if (ob->bottom > originyglobal + 7*TILEGLOBAL)
\r
1455 yscroll = ob->bottom - (originyglobal + 7*TILEGLOBAL);
\r
1462 if (!xscroll && !yscroll)
\r
1466 // don't scroll more than one tile per frame
\r
1468 if (xscroll >= 0x100)
\r
1472 else if (xscroll <= -0x100)
\r
1476 if (yscroll >= 0x100)
\r
1480 else if (yscroll <= -0x100)
\r
1485 RF_Scroll(xscroll, yscroll);
\r
1488 // update limits for onscreen and inactivate checks
\r
1490 originxtilemax = (originxtile + PORTTILESWIDE) - 1;
\r
1491 originytilemax = (originytile + PORTTILESHIGH) - 1;
\r
1492 inactivateleft = originxtile - INACTIVATEDIST;
\r
1493 if (inactivateleft < 0)
\r
1495 inactivateleft = 0;
\r
1497 inactivateright = originxtilemax + INACTIVATEDIST;
\r
1498 if (inactivateright < 0)
\r
1500 inactivateright = 0;
\r
1502 inactivatetop = originytile - INACTIVATEDIST;
\r
1503 if (inactivatetop < 0)
\r
1505 inactivatetop = 0;
\r
1507 inactivatebottom = originytilemax + INACTIVATEDIST;
\r
1508 if (inactivatebottom < 0)
\r
1510 inactivatebottom = 0;
\r
1514 //===========================================================================
\r
1517 ==================
\r
1521 = Scroll if Keen is nearing an edge
\r
1522 = Set playstate to ex_completes
\r
1524 ==================
\r
1527 void ScrollScreen(objtype *ob)
\r
1529 Sint16 xscroll, yscroll, pix, speed;
\r
1536 // walked off edge of map?
\r
1538 if (ob->left < originxmin || ob->right > originxmax + 320*PIXGLOBAL)
\r
1540 playstate = ex_completed;
\r
1545 // fallen off bottom of world?
\r
1547 if (ob->bottom > originymax + 13*TILEGLOBAL)
\r
1549 ob->y -= ob->bottom - (originymax + 13*TILEGLOBAL);
\r
1550 SD_PlaySound(SND_PLUMMET);
\r
1556 xscroll=yscroll=0;
\r
1558 if (ob->x < originxglobal + 9*TILEGLOBAL)
\r
1560 xscroll = ob->x - (originxglobal + 9*TILEGLOBAL);
\r
1562 else if (ob->x > originxglobal + 12*TILEGLOBAL)
\r
1564 xscroll = ob->x - (originxglobal + 12*TILEGLOBAL);
\r
1567 if (ob->state == &s_keenlookup2)
\r
1569 if (centerlevel+tics > 167)
\r
1571 pix = 167-centerlevel;
\r
1577 centerlevel += pix;
\r
1578 yscroll = CONVERT_PIXEL_TO_GLOBAL(-pix);
\r
1580 else if (ob->state == &s_keenlookdown3)
\r
1582 if (centerlevel-tics < 33)
\r
1584 pix = centerlevel + -33;
\r
1590 centerlevel -= pix;
\r
1591 yscroll = CONVERT_PIXEL_TO_GLOBAL(pix);
\r
1597 static Sint16 shaketable[] = {0,
\r
1598 -64, -64, -64, 64, 64, 64,
\r
1599 -200, -200, -200, 200, 200, 200,
\r
1600 -250, -250, -250, 250, 250, 250,
\r
1601 -250, -250, -250, 250, 250, 250
\r
1603 yscroll = yscroll + (bottom - (ob->bottom + shaketable[groundslam])); // BUG: 'bottom' has not been initialized yet!
\r
1607 if ( (ob->hitnorth || !ob->needtoclip || ob->state == &s_keenholdon))
\r
1609 if ( ob->state != &s_keenclimbup
\r
1610 && ob->state != &s_keenclimbup2
\r
1611 && ob->state != &s_keenclimbup3
\r
1612 && ob->state != &s_keenclimbup4)
\r
1614 yscroll += ob->ymove;
\r
1615 bottom = originyglobal + yscroll + CONVERT_PIXEL_TO_GLOBAL(centerlevel);
\r
1616 if (ob->bottom == bottom)
\r
1618 // player didn't move, no additional scrolling
\r
1622 if (ob->bottom < bottom)
\r
1624 pix = bottom - ob->bottom;
\r
1628 pix = ob->bottom - bottom;
\r
1630 speed = CONVERT_PIXEL_TO_GLOBAL(pix) >> 7;
\r
1647 if (ob->bottom < bottom)
\r
1660 centerlevel = 140;
\r
1663 pix = (ob->bottom-32*PIXGLOBAL)-(originyglobal+yscroll);
\r
1668 pix = (ob->bottom+32*PIXGLOBAL)-(originyglobal+yscroll+200*PIXGLOBAL);
\r
1674 if (xscroll == 0 && yscroll == 0)
\r
1678 // don't scroll more than one tile per frame
\r
1680 if (xscroll >= 0x100)
\r
1684 else if (xscroll <= -0x100)
\r
1688 if (yscroll >= 0x100)
\r
1692 else if (yscroll <= -0x100)
\r
1696 RF_Scroll(xscroll, yscroll);
\r
1699 // update limits for onscreen and inactivate checks
\r
1701 originxtilemax = (originxtile + PORTTILESWIDE) - 1;
\r
1702 originytilemax = (originytile + PORTTILESHIGH) - 1;
\r
1703 inactivateleft = originxtile - INACTIVATEDIST;
\r
1704 if (inactivateleft < 0)
\r
1706 inactivateleft = 0;
\r
1708 inactivateright = originxtilemax + INACTIVATEDIST;
\r
1709 if (inactivateright < 0)
\r
1711 inactivateright = 0;
\r
1713 inactivatetop = originytile - INACTIVATEDIST;
\r
1714 if (inactivatetop < 0)
\r
1716 inactivatetop = 0;
\r
1718 inactivatebottom = originytilemax + INACTIVATEDIST;
\r
1719 if (inactivatebottom < 0)
\r
1721 inactivatebottom = 0;
\r
1725 //===========================================================================
\r
1729 #############################################################################
\r
1731 The objarray data structure
\r
1733 #############################################################################
\r
1735 Objarray contains structures for every actor currently playing. The structure
\r
1736 is accessed as a linked list starting at *player, ending when ob->next ==
\r
1737 NULL. GetNewObj inserts a new object at the end of the list, meaning that
\r
1738 if an actor spawns another actor, the new one WILL get to think and react the
\r
1739 same frame. RemoveObj unlinks the given object and returns it to the free
\r
1740 list, but does not damage the objects ->next pointer, so if the current object
\r
1741 removes itself, a linked list following loop can still safely get to the
\r
1744 <backwardly linked free list>
\r
1746 #############################################################################
\r
1751 =========================
\r
1755 = Call to clear out the entire object list, returning them all to the free
\r
1756 = list. Allocates a special spot for the player.
\r
1758 =========================
\r
1761 void InitObjArray(void)
\r
1765 for (i=0; i<MAXACTORS; i++)
\r
1767 objarray[i].prev = &objarray[i+1];
\r
1768 objarray[i].next = NULL;
\r
1771 objarray[MAXACTORS-1].prev = NULL;
\r
1773 objfreelist = &objarray[0];
\r
1779 // give the player and score the first free spots
\r
1787 //===========================================================================
\r
1790 =========================
\r
1794 = Sets the global variable new to point to a free spot in objarray.
\r
1795 = The free spot is inserted at the end of the liked list
\r
1797 = When the object list is full, the caller can either have it bomb out or
\r
1798 = return a dummy object pointer that will never get used
\r
1800 = Returns -1 when list was full, otherwise returns 0.
\r
1802 =========================
\r
1805 Sint16 GetNewObj(boolean usedummy)
\r
1814 Quit("GetNewObj: No free spots in objarray!");
\r
1816 new = objfreelist;
\r
1817 objfreelist = new->prev;
\r
1818 memset(new, 0, sizeof(*new));
\r
1821 lastobj->next = new;
\r
1823 new->prev = lastobj; // new->next is allready NULL from memset
\r
1825 new->active = ac_yes;
\r
1826 new->needtoclip = cl_midclip;
\r
1833 //===========================================================================
\r
1836 =========================
\r
1840 = Add the given object back into the free list, and unlink it from it's
\r
1843 =========================
\r
1846 void RemoveObj(objtype *ob)
\r
1849 Quit("RemoveObj: Tried to remove the player!");
\r
1852 // erase it from the refresh manager
\r
1854 RF_RemoveSprite(&ob->sprite);
\r
1855 if (ob->obclass == stunnedobj)
\r
1857 RF_RemoveSprite((void **)&ob->temp3);
\r
1861 // fix the next object's back link
\r
1863 if (ob == lastobj)
\r
1865 lastobj = ob->prev;
\r
1869 ob->next->prev = ob->prev;
\r
1873 // fix the previous object's forward link
\r
1875 ob->prev->next = ob->next;
\r
1878 // add it back in to the free list
\r
1880 ob->prev = objfreelist;
\r
1884 //==========================================================================
\r
1887 ====================
\r
1891 = Grants extra men at 20k,40k,80k,160k,320k
\r
1893 ====================
\r
1896 void GivePoints(Uint16 points)
\r
1898 gamestate.score += points;
\r
1899 if (!DemoMode && gamestate.score >= gamestate.nextextra)
\r
1901 SD_PlaySound(SND_EXTRAKEEN);
\r
1902 gamestate.lives++;
\r
1903 gamestate.nextextra *= 2;
\r
1907 //==========================================================================
\r
1910 ===================
\r
1914 ===================
\r
1917 void PollControls(void)
\r
1919 IN_ReadControl(0, &c);
\r
1920 if (c.yaxis != -1)
\r
1923 if (GravisGamepad && !DemoMode)
\r
1925 jumpbutton = GravisAction[ga_Jump];
\r
1926 pogobutton = GravisAction[ga_Pogo];
\r
1927 firebutton = GravisAction[ga_Fire];
\r
1935 else if (oldshooting || DemoMode)
\r
1937 if (c.button0 && c.button1)
\r
1939 firebutton = true;
\r
1940 jumpbutton = pogobutton = jumpheld = pogoheld = false;
\r
1944 firebutton = fireheld = false;
\r
1947 jumpbutton = true;
\r
1951 jumpbutton = jumpheld = false;
\r
1955 if (oldfirecount <= 8)
\r
1957 oldfirecount = oldfirecount + tics;
\r
1961 pogobutton = true;
\r
1966 if (oldfirecount != 0)
\r
1968 pogobutton = true;
\r
1972 pogobutton = pogoheld = false;
\r
1980 jumpbutton = c.button0;
\r
1981 pogobutton = c.button1;
\r
1982 firebutton = Keyboard[firescan];
\r
1992 //==========================================================================
\r
2003 void StopMusic(void)
\r
2008 for (i=0; i<LASTMUSIC; i++)
\r
2010 if (audiosegs[STARTMUSIC+i])
\r
2012 #ifdef FIX_MUSIC_MEMORY_ISSUES
\r
2013 //unlock any music blocks so that they can be purged
\r
2014 MM_SetLock(&(memptr)audiosegs[STARTMUSIC+i], false);
\r
2016 MM_SetPurge(&(memptr)audiosegs[STARTMUSIC+i], PURGE_FIRST);
\r
2021 //==========================================================================
\r
2031 void StartMusic(Uint16 num)
\r
2033 static Sint16 songs[] =
\r
2056 #elif defined KEEN5
\r
2077 #elif defined KEEN6
\r
2104 if (num >= ARRAYLENGTH(songs) && num != 0xFFFF)
\r
2106 Quit("StartMusic() - bad level number");
\r
2109 #ifdef FIX_MUSIC_MEMORY_ISSUES
\r
2116 if (num == 0xFFFF)
\r
2118 song = WONDER_MUS;
\r
2122 song = songs[num];
\r
2125 song = songs[num];
\r
2128 if (song == -1 || MusicMode != smm_AdLib)
\r
2133 MM_BombOnError(false);
\r
2134 CA_CacheAudioChunk(STARTMUSIC+song);
\r
2135 MM_BombOnError(true);
\r
2141 US_CenterWindow(20, 8);
\r
2143 US_CPrint("Insufficient memory\nfor background music!");
\r
2144 VW_UpdateScreen();
\r
2145 wasfaded = screenfaded;
\r
2148 VW_SetDefaultColors();
\r
2150 IN_ClearKeysDown();
\r
2151 IN_UserInput(3*TickBase, false);
\r
2160 #ifdef FIX_MUSIC_MEMORY_ISSUES
\r
2161 //The current music should be locked, so the memory manager will not
\r
2162 //mess with it when compressing memory blocks in MM_SortMem().
\r
2163 MM_SetLock(&(memptr)audiosegs[STARTMUSIC+song], true);
\r
2165 SD_StartMusic((MusicGroup far *)audiosegs[STARTMUSIC+song]);
\r
2169 //==========================================================================
\r
2173 ===================
\r
2177 ===================
\r
2180 void PlayLoop(void)
\r
2184 StartMusic(gamestate.mapon);
\r
2185 fireheld = pogoheld = upheld = jumpheld = false;
\r
2187 playstate = ex_stillplaying;
\r
2188 invincible = keenkilled = oldfirecount = 0;
\r
2190 CenterActor(player);
\r
2194 US_InitRndT(false);
\r
2198 US_InitRndT(true);
\r
2200 TimeCount = lasttimecount = tics = 3;
\r
2207 // go through state changes and propose movements
\r
2209 for (obj=player; obj; obj=obj->next)
\r
2211 if (!obj->active && obj->tileright >= originxtile-1
\r
2212 && obj->tileleft <= originxtilemax+1 && obj->tiletop <= originytilemax+1
\r
2213 && obj->tilebottom >= originytile-1)
\r
2215 obj->needtoreact = true;
\r
2216 obj->active = ac_yes;
\r
2220 if (obj->tileright < inactivateleft
\r
2221 || obj->tileleft > inactivateright
\r
2222 || obj->tiletop > inactivatebottom
\r
2223 || obj->tilebottom < inactivatetop)
\r
2225 if (obj->active == ac_removable)
\r
2230 else if (obj->active != ac_allways)
\r
2232 if (US_RndT() < tics*2 || screenfaded || loadedgame)
\r
2234 RF_RemoveSprite(&obj->sprite);
\r
2235 if (obj->obclass == stunnedobj)
\r
2236 RF_RemoveSprite((void **)&obj->temp3);
\r
2237 obj->active = ac_no;
\r
2242 StateMachine(obj);
\r
2246 if (gamestate.riding)
\r
2248 HandleRiding(player);
\r
2252 // check for and handle collisions between objects
\r
2254 for (obj=player; obj; obj=obj->next)
\r
2258 for (check=obj->next; check; check=check->next)
\r
2260 if (!check->active)
\r
2264 if (obj->right > check->left && obj->left < check->right
\r
2265 && obj->top < check->bottom && obj->bottom > check->top)
\r
2267 if (obj->state->contact)
\r
2269 obj->state->contact(obj, check);
\r
2271 if (check->state->contact)
\r
2273 check->state->contact(check, obj);
\r
2275 if (obj->obclass == nothing) //useless -- obclass is NOT set to nothing by RemoveObj
\r
2289 CheckInTiles(player);
\r
2293 CheckWorldInTiles(player);
\r
2297 // react to whatever happened, and post sprites to the refresh manager
\r
2299 for (obj=player; obj; obj=obj->next)
\r
2305 if (obj->tilebottom >= mapheight-1)
\r
2307 if (obj->obclass == keenobj)
\r
2309 playstate = ex_died;
\r
2317 if (obj->needtoreact && obj->state->react)
\r
2319 obj->needtoreact = false;
\r
2320 obj->state->react(obj);
\r
2325 // scroll the screen and update the score box
\r
2328 if (mapon != 0 && mapon != 17)
\r
2333 ScrollScreen(player);
\r
2337 WorldScrollScreen(player);
\r
2339 UpdateScore(scoreobj);
\r
2342 loadedgame = false;
\r
2346 // update the screen and calculate the number of tics it took to execute
\r
2347 // this cycle of events (for adaptive timing of next cycle)
\r
2353 if ((invincible = invincible - tics) < 0)
\r
2360 if ((groundslam = groundslam - tics) < 0)
\r
2365 // single step debug mode
\r
2369 VW_WaitVBL(14); //reduces framerate to 5 fps on VGA or 4.3 fps on EGA cards
\r
2370 lasttimecount = TimeCount;
\r
2373 // extra VBLs debug mode
\r
2377 VW_WaitVBL(extravbls);
\r
2381 // handle user inputs
\r
2383 if (DemoMode == demo_Playback)
\r
2385 if (!screenfaded && IN_IsUserInput())
\r
2387 playstate = ex_completed;
\r
2388 if (LastScan != sc_F1)
\r
2390 LastScan = sc_Space;
\r
2394 else if (DemoMode == demo_PlayDone)
\r
2396 playstate = ex_completed;
\r
2406 if (Keyboard[sc_E] && Keyboard[sc_N] && Keyboard[sc_D])
\r
2409 gamestate.rescued = 7;
\r
2410 playstate = ex_rescued;
\r
2411 #elif defined KEEN5
\r
2412 playstate = ex_qedbroke;
\r
2413 #elif defined KEEN6
\r
2414 playstate = ex_molly;
\r
2418 } while (playstate == ex_stillplaying);
\r