//------------------------------------------------------------------------- /* Copyright (C) 2010-2019 EDuke32 developers and contributors Copyright (C) 2019 sirlemonhead, Nuke.YKT This file is part of PCExhumed. PCExhumed is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //------------------------------------------------------------------------- #include "ns.h" #include "compat.h" #include "build.h" #include "exhumed.h" #include "typedefs.h" #include "player.h" #include "sequence.h" #include "menu.h" #include "names.h" #include "engine.h" #include "keyboard.h" #include "status.h" #include "random.h" #include "sound.h" #include "names.h" #include "init.h" #include "ps_input.h" #include "gun.h" #include "view.h" #include "object.h" #include "light.h" #include "cd.h" #include "cdaudio.h" #include "input.h" #include #include #ifdef __WATCOMC__ #include #endif BEGIN_PS_NS #define kSaveFileName "savgamea.sav" #define kMaxSaveSlots 5 #define kMaxSaveSlotChars 25 GameStat GameStats; short nCinemaSeen[30]; // this might be static within the DoPlasma function? uint8_t * PlasmaBuffer; uint8_t energytile[66 * 66] = {0}; uint8_t cinemapal[768]; short nLeft[50] = {0}; int line; short SavePosition = -1; uint8_t *cur; uint8_t *dest; unsigned int nSmokeBottom; unsigned int nSmokeRight; unsigned int nSmokeTop; unsigned int nSmokeLeft; unsigned int nRandom = 0x41C6167E; int dword_9AB57 = 0x1F; short word_9AB5B = 0; int keytimer = 0; int plasma_A[5] = {0}; int plasma_B[5] = {0}; int plasma_C[5] = {0}; short nMenuKeys[] = { sc_N, sc_L, sc_M, sc_V, sc_Q, sc_None }; // select a menu item using the keys. 'N' for New Gane, 'V' for voume etc. 'M' picks Training for some reason... int zoomsize = 0; void menu_ResetKeyTimer(); enum { kMenuNewGame = 0, kMenuLoadGame, kMenuTraining, kMenuVolume, kMenuQuitGame, kMenuMaxItems }; void ClearCinemaSeen() { memset(nCinemaSeen, 0, sizeof(nCinemaSeen)); } unsigned int menu_RandomBit2() { unsigned int result = nRandom & 1; if ( --dword_9AB57 > 0 ) { nRandom = (result << 31) | (nRandom >> 1); } else { dword_9AB57 = 31; nRandom ^= nRandom >> 4; } return result; } int menu_RandomLong2() { int randLong = 0; for (int i = 0; i < 32; i++) { int val = menu_RandomBit2(); randLong *= 2; randLong |= val; } return randLong; } void InitEnergyTile() { memset(energytile, 96, sizeof(energytile)); } void DoEnergyTile() { nButtonColor += nButtonColor < 0 ? 8 : 0; auto energy1 = TileFiles.tileMakeWritable(kEnergy1); auto energy2 = TileFiles.tileMakeWritable(kEnergy2); uint8_t *ptr1 = energy1 + 1984; uint8_t *ptr2 = energy2 + 2048; short nColor = nButtonColor + 161; int i, j; for (i = 0; i < 32; i++) { memset(ptr1, nColor, 64); memset(ptr2, nColor, 64); ptr1 -= 64; ptr2 += 64; nColor++; if (nColor >= 168) { nColor = 160; } } tileInvalidate(kEnergy1, -1, -1); if (nSmokeSparks) { uint8_t *c = &energytile[67]; // skip a line uint8_t *ptrW = energy2; for (i = 0; i < 64; i++) { for (j = 0; j < 64; j++) { if (*c != 96) { if (*c <= 158) { *ptrW = 96; } else { *ptrW = (*c) - 1; } //continue; } else { if (menu_RandomBit2()) { *ptrW = *c; c++; ptrW++; continue; } char al = *(c + 1); char ah = *(c - 1); if (al <= ah) { al = ah; } char cl = al; al = *(c - 66); if (cl <= al) { cl = al; } al = *(c + 66); if (cl <= al) { cl = al; } al = *(c + 66); if (cl <= al) { cl = al; } al = *(c + 66); if (cl <= al) { cl = al; } al = *(c - 65); if (cl <= al) { cl = al; } al = *(c - 67); if (cl > al) { al = cl; } cl = al; if (al <= 159) { *ptrW = 96; //continue; } else { if (!menu_RandomBit2()) { cl--; } *ptrW = cl; } } c++; ptrW++; } c += 2; } c = &energytile[67]; ptrW = energy2; for (i = 0; i < 64; i++) { memcpy(c, ptrW, 64); c += 66; ptrW += 64; } ptrW = energy2; for (i = 0; i < 4096; i++) { if ((*ptrW) == 96) { *ptrW = 255; // -1? } ptrW++; } word_9AB5B--; if (word_9AB5B <= 0) { int randSize = (RandomSize(5) & 0x1F) + 16; int randSize2 = (RandomSize(5) & 0x1F) + 16; int val = randSize << 5; val += randSize; val *= 2; val += randSize2; energytile[val] = 175; word_9AB5B = 1; } tileInvalidate(kEnergy2, -1, -1); } } int nPlasmaTile = kTile4092; int nLogoTile; #define kPlasmaWidth 320 #define kPlasmaHeight 80 void menu_DoPlasma() { if (!nLogoTile) nLogoTile = EXHUMED ? kExhumedLogo : kPowerslaveLogo; if (!PlasmaBuffer) { auto pixels = TileFiles.tileCreate(kTile4092, kPlasmaWidth, kPlasmaHeight); memset(pixels, 96, kPlasmaWidth*kPlasmaHeight); PlasmaBuffer = TileFiles.tileCreate(kTile4093, kPlasmaWidth, kPlasmaHeight); memset(PlasmaBuffer, 96, kPlasmaWidth * kPlasmaHeight); nSmokeLeft = 160 - tilesiz[nLogoTile].x / 2; nSmokeRight = nSmokeLeft + tilesiz[nLogoTile].x; nSmokeTop = 40 - tilesiz[nLogoTile].y / 2; nSmokeBottom = nSmokeTop + tilesiz[nLogoTile].y - 1; //uint32_t t = time(0) << 16; //uint32_t t2 = time(0) | t; nRandom = timerGetTicksU64(); for (int i = 0; i < 5; i++) { int logoWidth = tilesiz[nLogoTile].x; plasma_C[i] = (nSmokeLeft + rand() % logoWidth) << 16; plasma_B[i] = (menu_RandomLong2() % 327680) + 0x10000; if (menu_RandomBit2()) { plasma_B[i] = -plasma_B[i]; } plasma_A[i] = menu_RandomBit2(); } } videoClearScreen(overscanindex); uint8_t* plasmapix = const_cast(tilePtr(nPlasmaTile)); uint8_t *r_ebx = plasmapix + 81; const uint8_t *r_edx = tilePtr(nPlasmaTile ^ 1) + 81; // flip between value of 4092 and 4093 with xor for (int x = 0; x < kPlasmaWidth - 2; x++) // for (int x = 1; x < 318; x++) { // for (int y = 1; y < 79; y++) for (int y = 0; y < kPlasmaHeight - 2; y++) { uint8_t al = *r_edx; if (al != 96) { if (al > 158) { *r_ebx = al - 1; } else { *r_ebx = 96; } } else { if (menu_RandomBit2()) { *r_ebx = *r_edx; } else { uint8_t al = *(r_edx + 1); uint8_t cl = *(r_edx - 1); if (al <= cl) { al = cl; } cl = al; al = *(r_edx - 80); if (cl <= al) { cl = al; } al = *(r_edx + 80); if (cl <= al) { cl = al; } al = *(r_edx + 80); if (cl <= al) { cl = al; } al = *(r_edx + 80); if (cl <= al) { cl = al; } al = *(r_edx - 79); if (cl > al) { al = cl; } cl = *(r_edx - 81); if (al <= cl) { al = cl; } cl = al; if (al <= 159) { *r_ebx = 96; } else { if (!menu_RandomBit2()) { cl--; } *r_ebx = cl; } } } // before restarting inner loop r_edx++; r_ebx++; } // before restarting outer loop r_edx += 2; r_ebx += 2; } auto logopix = tilePtr(nLogoTile); for (int j = 0; j < 5; j++) { int pB = plasma_B[j]; int pC = plasma_C[j]; int badOffset = (pC>>16) < nSmokeLeft || (pC>>16) >= nSmokeRight; const uint8_t *ptr3 = (logopix + ((pC >> 16) - nSmokeLeft) * tilesiz[nLogoTile].y); plasma_C[j] += plasma_B[j]; if ((pB > 0 && (plasma_C[j] >> 16) >= nSmokeRight) || (pB < 0 && (plasma_C[j] >> 16) <= nSmokeLeft)) { int esi = plasma_A[j]; plasma_B[j] = -plasma_B[j]; plasma_A[j] = esi == 0; } if (badOffset) continue; unsigned int nSmokeOffset = 0; if (plasma_A[j]) { nSmokeOffset = nSmokeTop; while (nSmokeOffset < nSmokeBottom) { uint8_t al = *ptr3; if (al != 255 && al != 96) { break; } nSmokeOffset++; ptr3++; } } else { nSmokeOffset = nSmokeBottom; ptr3 += tilesiz[nLogoTile].y - 1; while (nSmokeOffset > nSmokeTop) { uint8_t al = *ptr3; if (al != 255 && al != 96) { break; } nSmokeOffset--; ptr3--; } } uint8_t *v28 = plasmapix + (80 * (plasma_C[j] >> 16)); v28[nSmokeOffset] = 175; } tileInvalidate(nPlasmaTile,-1,-1); overwritesprite(0, 0, nPlasmaTile, 0, 2, kPalNormal); overwritesprite(160, 40, nLogoTile, 0, 3, kPalNormal); // flip between tile 4092 and 4093 if (nPlasmaTile == kTile4092) { nPlasmaTile = kTile4093; } else if (nPlasmaTile == kTile4093) { nPlasmaTile = kTile4092; } // draw the fire urn/lamp thingies int dword_9AB5F = ((int)totalclock/16) & 3; overwritesprite(50, 150, kTile3512 + dword_9AB5F, 0, 3, kPalNormal); overwritesprite(270, 150, kTile3512 + ((dword_9AB5F + 2) & 3), 0, 3, kPalNormal); // TEMP int time = (int)totalclock + 4; while ((int)totalclock < time) { HandleAsync(); } } int8_t MapLevelOffsets[] = { 0, 50, 10, 20, 0, 45, -20, 20, 5, 0, -10, 10, 30, -20, 0, 20, 0, 0, 0, 0 }; struct TILEFRAMEDEF { short nTile; short xOffs; short yOffs; }; // 22 bytes struct MapNamePlaque { short xPos; short yPos; TILEFRAMEDEF tiles[2]; TILEFRAMEDEF text; }; MapNamePlaque mapNamePlaques[] = { { 100, 170, kTile3376, 0, 0, kTile3377, 0, 0, kTile3411, 18, 6 }, { 230, 10, kTile3378, 0, 0, kTile3379, 0, 0, kTile3414, 18, 6 }, // DENDUR (level 2) { 180, 125, kTile3380, 0, 0, kTile3381, 0, 0, kTile3417, 18, 6 }, // Kalabash { 10, 95, kTile3382, 0, 0, kTile3383, 0, 0, kTile3420, 18, 6 }, { 210, 160, kTile3384, 0, 0, kTile3385, 0, 0, kTile3423, 18, 6 }, { 10, 110, kTile3371, 0, 0, kTile3386, 0, 0, kTile3426, 18, 6 }, { 10, 50, kTile3387, 0, 0, kTile3388, 0, 0, kTile3429, 18, 6 }, { 140, 0, kTile3389, 0, 0, kTile3390, 0, 0, kTile3432, 18, 6 }, { 30, 20, kTile3391, 0, 0, kTile3392, 0, 0, kTile3435, 18, 6 }, { 200, 150, kTile3409, 0, 0, kTile3410, 0, 0, kTile3418, 20, 4 }, { 145, 170, kTile3393, 0, 0, kTile3394, 0, 0, kTile3438, 18, 6 }, { 80, 80, kTile3395, 0, 0, kTile3396, 0, 0, kTile3441, 18, 6 }, { 15, 0, kTile3397, 0, 0, kTile3398, 0, 0, kTile3444, 18, 5 }, { 220, 35, kTile3399, 0, 0, kTile3400, 0, 0, kTile3447, 18, 6 }, { 190, 40, kTile3401, 0, 0, kTile3402, 0, 0, kTile3450, 18, 6 }, { 20, 130, kTile3403, 0, 0, kTile3404, 0, 0, kTile3453, 19, 6 }, { 220, 160, kTile3405, 0, 0, kTile3406, 0, 0, kTile3456, 18, 6 }, { 20, 10, kTile3407, 0, 0, kTile3408, 0, 0, kTile3459, 18, 6 }, { 200, 10, kTile3412, 0, 0, kTile3413, 0, 0, kTile3419, 18, 5 }, { 20, 10, kTile3415, 0, 0, kTile3416, 0, 0, kTile3421, 19, 4 } }; // 3 different types of fire, each with 4 frames TILEFRAMEDEF FireTiles[3][4] = { {{ kTile3484,0,3 },{ kTile3485,0,0 },{ kTile3486,0,3 },{ kTile3487,0,0 }}, {{ kTile3488,1,0 },{ kTile3489,1,0 },{ kTile3490,0,1 },{ kTile3491,1,1 }}, {{ kTile3492,1,2 },{ kTile3493,1,0 },{ kTile3494,1,2 },{ kTile3495,1,0 }} }; struct Fire { short nFireType; short xPos; short yPos; }; // 20 bytes struct MapFire { short nFires; Fire fires[3]; }; /* level 1 - 3 fires level 2 - 3 fires level 3 - 1 fire */ MapFire MapLevelFires[] = { 3, {{0, 107, 95}, {1, 58, 140}, {2, 28, 38}}, 3, {{2, 240, 0}, {0, 237, 32}, {1, 200, 30}}, 2, {{2, 250, 57}, {0, 250, 43}, {2, 200, 70}}, 2, {{1, 82, 59}, {2, 84, 16}, {0, 10, 95}}, 2, {{2, 237, 50}, {1, 215, 42}, {1, 210, 50}}, 3, {{0, 40, 7}, {1, 75, 6}, {2, 100, 10}}, 3, {{0, 58, 61}, {1, 85, 80}, {2, 111, 63}}, 3, {{0, 260, 65}, {1, 228, 0}, {2, 259, 15}}, 2, {{0, 81, 38}, {2, 58, 38}, {2, 30, 20}}, 3, {{0, 259, 49}, {1, 248, 76}, {2, 290, 65}}, 3, {{2, 227, 66}, {0, 224, 98}, {1, 277, 30}}, 2, {{0, 100, 10}, {2, 48, 76}, {2, 80, 80}}, 3, {{0, 17, 2}, {1, 29, 49}, {2, 53, 28}}, 3, {{0, 266, 42}, {1, 283, 99}, {2, 243, 108}}, 2, {{0, 238, 19}, {2, 240, 92}, {2, 190, 40}}, 2, {{0, 27, 0}, {1, 70, 40}, {0, 20, 130}}, 3, {{0, 275, 65}, {1, 235, 8}, {2, 274, 6}}, 3, {{0, 75, 45}, {1, 152, 105}, {2, 24, 68}}, 3, {{0, 290, 25}, {1, 225, 63}, {2, 260, 110}}, 0, {{1, 20, 10}, {1, 20, 10}, {1, 20, 10}} }; int menu_DrawTheMap(int nLevel, int nLevelNew, int nLevelBest) { int i; int x = 0; int var_2C = 0; int nIdleSeconds = 0; int bFadeDone = kFalse; int startTime = (int)totalclock; ClearAllKeys(); UnMaskStatus(); videoSetViewableArea(0, 0, xdim - 1, ydim - 1); // 0-offset the level numbers nLevel--; nLevelNew--; nLevelBest--; if (nLevel >= kMap20) { // max single player levels return -1; } if (nLevelNew >= kMap20) { return -1; } if (nLevel < 0) { nLevel = 0; } if (nLevelNew < 0) { nLevelNew = nLevel; } int curYPos = MapLevelOffsets[nLevel] + (200 * (nLevel / 2)); int destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2)); if (curYPos < destYPos) { var_2C = 2; } if (curYPos > destYPos) { var_2C = -2; } int runtimer = (int)totalclock; // User has 12 seconds to do something on the map screen before loading the current level while (nIdleSeconds < 12) { HandleAsync(); if (((int)totalclock - startTime) / kTimerTicks) { nIdleSeconds++; startTime = (int)totalclock; } int moveTimer = (int)totalclock; int tileY = curYPos; // Draw the background screens for (i = 0; i < 10; i++) { overwritesprite(x, tileY, kTile3353 + i, 0, 2, kPalNormal); tileY -= 200; } // for each level - drawing the 'level completed' on-fire smoke markers for (i = 0; i < kMap20; i++) { int screenY = (i >> 1) * -200; if (nLevelBest >= i) // check if the player has finished this level { for (int j = 0; j < MapLevelFires[i].nFires; j++) { int nFireFrame = (((int)totalclock >> 4) & 3); assert(nFireFrame >= 0 && nFireFrame < 4); int nFireType = MapLevelFires[i].fires[j].nFireType; assert(nFireType >= 0 && nFireType < 3); int nTile = FireTiles[nFireType][nFireFrame].nTile; int smokeX = MapLevelFires[i].fires[j].xPos + FireTiles[nFireType][nFireFrame].xOffs; int smokeY = MapLevelFires[i].fires[j].yPos + FireTiles[nFireType][nFireFrame].yOffs + curYPos + screenY; overwritesprite(smokeX, smokeY, nTile, 0, 2, kPalNormal); } } int t = ((((int)totalclock & 16) >> 4)); int nTile = mapNamePlaques[i].tiles[t].nTile; int nameX = mapNamePlaques[i].xPos + mapNamePlaques[i].tiles[t].xOffs; int nameY = mapNamePlaques[i].yPos + mapNamePlaques[i].tiles[t].yOffs + curYPos + screenY; // Draw level name plaque overwritesprite(nameX, nameY, nTile, 0, 2, kPalNormal); int8_t shade = 96; if (nLevelNew == i) { shade = (Sin(16 * (int)totalclock) + 31) >> 8; } else if (nLevelBest >= i) { shade = 31; } int textY = mapNamePlaques[i].yPos + mapNamePlaques[i].text.yOffs + curYPos + screenY; int textX = mapNamePlaques[i].xPos + mapNamePlaques[i].text.xOffs; nTile = mapNamePlaques[i].text.nTile; // draw the text, alternating between red and black overwritesprite(textX, textY, nTile, shade, 2, kPalNormal); } videoNextPage(); if (!bFadeDone) { bFadeDone = kTrue; FadeIn(); moveTimer = (int)totalclock; } if (curYPos == destYPos) { if (I_MenuUp()) { I_MenuUpClear(); if (nLevelNew <= nLevelBest) { nLevelNew++; assert(nLevelNew < 20); destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2)); if (curYPos <= destYPos) { var_2C = 2; } else { var_2C = -2; } nIdleSeconds = 0; } } if (I_MenuDown()) { I_MenuDownClear(); if (nLevelNew > 0) { nLevelNew--; assert(nLevelNew >= 0); destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2)); if (curYPos <= destYPos) { var_2C = 2; } else { var_2C = -2; } nIdleSeconds = 0; } } if (I_AdvanceTrigger()) { I_AdvanceTriggerClear(); return nLevelNew + 1; } } else { // scroll the map every couple of ms if (totalclock - runtimer >= (kTimerTicks / 32)) { curYPos += var_2C; runtimer = (int)totalclock; } //curYPos += var_2C * (((int)totalclock - moveTimer) / 2); if (I_AdvanceTrigger()) { I_AdvanceTriggerClear(); if (var_2C < 8) { var_2C *= 2; } } if (curYPos > destYPos&& var_2C > 0) { curYPos = destYPos; } if (curYPos < destYPos && var_2C < 0) { curYPos = destYPos; } nIdleSeconds = 0; } } MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); return nLevelNew + 1; } void menu_AdjustVolume() { int nOption = 1; int var_8 = 0; while (1) { HandleAsync(); menu_DoPlasma(); overwritesprite(80, 50, kMenuMusicTile, (Sin((int)totalclock << 4) >> 9) * (nOption == 0), 2, kPalNormal); overwritesprite(55, 75, kMenuBlankTitleTile, 0, 2, kPalNormal); seq_DrawGunSequence( SeqOffsets[kSeqSlider], // eax gMusicVolume % 3, // pick one of 3 frames? (gMusicVolume >> 1) - 93, // ebx. must be x??? -22, 0, 0); overwritesprite(80, 110, kMenuSoundFxTile, (Sin((int)totalclock << 4) >> 9) * (nOption == 1), 2, kPalNormal); overwritesprite(55, 135, kMenuBlankTitleTile, 0, 2, kPalNormal); seq_DrawGunSequence( SeqOffsets[kSeqSlider], gFXVolume % 3, (gFXVolume / 2) - 93, 38, 0, 0); int y = (60 * nOption) + 38; overwritesprite(60, y, kMenuCursorTile, 0, 2, kPalNormal); overwritesprite(206, y, kMenuCursorTile, 0, 10, kPalNormal); videoNextPage(); if (I_AdvanceTrigger()) { I_AdvanceTriggerClear(); PlayLocalSound(StaticSound[kSound33], 0); return; } if (I_MenuUp()) { I_MenuUpClear(); if (nOption > 0) { nOption--; PlayLocalSound(StaticSound[kSound35], 0); } } if (I_MenuDown()) { I_MenuDownClear(); if (nOption < 1) { nOption++; PlayLocalSound(StaticSound[kSound35], 0); } } if ((int)totalclock <= var_8) { continue; } var_8 = (int)totalclock + 5; if (I_MenuLeft()) { I_MenuLeftClear(); switch (nOption) { case 0: { if (gMusicVolume > 3) { gMusicVolume -= 4; } // TODO SetMusicVolume(); setCDaudiovolume(gMusicVolume); continue; } case 1: { if (gFXVolume > 3) { gFXVolume -= 4; } if (LocalSoundPlaying()) { UpdateLocalSound(); } else { PlayLocalSound(StaticSound[kSound23], 0); } continue; } } } if (I_MenuRight()) { I_MenuRightClear(); switch (nOption) { case 0: { if (gMusicVolume < 252) { gMusicVolume += 4; } // SetMusicVolume(); setCDaudiovolume(gMusicVolume); continue; } case 1: { if (gFXVolume < 252) { gFXVolume += 4; } if (LocalSoundPlaying()) { UpdateLocalSound(); } else { PlayLocalSound(StaticSound[kSound23], 0); } continue; } } } if (GetLocalSound() != 23) { continue; } else { StopLocalSound(); } } } int menu_NewGameMenu() { const char endMark = 0xF; char nameList[5][25]; int nNameLen = sizeof(nameList); int nNameOffset = 0; // char index into slot name string //int nPages = numpages; int arg_3E = tilesiz[kMenuBlankTitleTile].x - 10; int nSlot = 0; FILE *fp = fopen(kSaveFileName, "rb"); if (fp == NULL) { memset(nameList, 0, nNameLen); memset(&GameStats, 0, sizeof(GameStat)); fp = fopen(kSaveFileName, "wb+"); if (fp != NULL) { fwrite(nameList, nNameLen, 1, fp); fwrite(&GameStats, 75, 1, fp); //fwrite(&GameStats, 75, 5, fp); // CHECKME! the size fwrite(&endMark, sizeof(endMark), 1, fp); fclose(fp); } } else { int nRead = fread(nameList, 1, nNameLen, fp); if (nRead != nNameLen) { memset(nameList, 0, nNameLen); } fclose(fp); } // while (1) { ClearAllKeys(); while (1) { HandleAsync(); menu_DoPlasma(); int y = (tilesiz[kMenuBlankTitleTile].y - (tilesiz[kMenuBlankTitleTile].y / 2) / 2) + 65; rotatesprite(160 << 16, y << 16, 0x10000, 0, kMenuNewGameTile, 0, 0, 2, 0, 0, xdim, ydim); int edi = 0; int arg_4A = 90; int arg_4E = 98; // Loop #3 for (int i = 0; i < 5; i++) { // CHECKME int8_t shade = ((Sin((int)totalclock << 4) >> 9) * (i == nSlot)) + ((i != nSlot) * 31); overwritesprite(55, arg_4A, kMenuBlankTitleTile, shade, 2, kPalNormal); myprintext(63, arg_4E, nameList[i], 0); arg_4E += 22; arg_4A += 22; edi++; } edi = nSlot * 22; // draw selection markers overwritesprite(35, edi + 78, kMenuCursorTile, 0, 2, kPalNormal); overwritesprite(233, edi + 78, kMenuCursorTile, 0, 10, kPalNormal); videoNextPage(); //nPages--; //if (nPages > 0) { // continue; //} if (I_EscapeTrigger()) { PlayLocalSound(StaticSound[kSound33], 0); I_EscapeTriggerClear(); return -1; } if (I_MenuUp()) { I_MenuUpClear(); PlayLocalSound(StaticSound[kSound35], 0); if (nSlot <= 0) { nSlot = 4; } else { nSlot--; } ClearAllKeys(); continue; } if (I_MenuDown()) { I_MenuDownClear(); PlayLocalSound(StaticSound[kSound35], 0); if (nSlot >= 4) { nSlot = 0; } else { nSlot++; } inputState.ClearAllKeyStatus(); continue; } if (I_AdvanceTrigger() || inputState.keyBufferWaiting()) { break; } } } PlayLocalSound(StaticSound[kSound33], 0); if (!inputState.keyBufferWaiting()) { inputState.ClearAllKeyStatus(); } char *pName = nameList[nSlot]; int nNameLength = strlen(pName); memset(pName, 0, nNameLength); menu_DoPlasma(); overwritesprite(55, (nSlot * 22) + 90, kMenuBlankTitleTile, 0, 2, kPalNormal); int arg_5A = 90; int arg_52 = 98; for (int i = 0; i < 5; i++) { overwritesprite(55, arg_5A, kMenuBlankTitleTile, (i != nSlot) * 31, 2, kPalNormal); myprintext(63, arg_52, nameList[i], 0); arg_52 += 22; arg_5A += 22; } int x = 35; int y = (nSlot * 22) + 78; while (1) { HandleAsync(); overwritesprite(x, y, kMenuCursorTile, 0, 2, kPalNormal); overwritesprite(233, y, kMenuCursorTile, 0, 10, kPalNormal); videoNextPage(); char ch = 0; check_keys: if (inputState.keyBufferWaiting()) { HandleAsync(); ch = inputState.keyGetChar(); if (!ch) { inputState.keyGetChar(); //??? goto check_keys; } // handle key input if (ch == asc_Enter) { // loc_39ACA: nameList[nSlot][nNameOffset] = 0; PlayLocalSound(StaticSound[kSound33], 0); inputState.ClearKeyStatus(sc_Return); if (nameList[nSlot][0] == 0) { return -1; } if (nNameLength) // does the save slot already exist? { menu_DoPlasma(); if (Query(2, 4, "Overwrite existing game?", "Y/N", 'Y', 13, 'N', 27) >= 2) { return -1; } } FILE *fp = fopen(kSaveFileName, "rb+"); if (fp == NULL) { return -1; } memset(&GameStats, 0, sizeof(GameStat)); GameStats.nWeapons = 1; GameStats.nMap = 1; fwrite(nameList, sizeof(nameList), 1, fp); fseek(fp, sizeof(nameList), SEEK_SET); fseek(fp, nSlot * sizeof(GameStat), SEEK_CUR); fwrite(&GameStats, sizeof(GameStat), 1, fp); fclose(fp); return nSlot; } else { // Enter wasn't pressed PlayLocalSound(4, 0); // ?? if (ch == asc_BackSpace) { nameList[nSlot][nNameOffset] = 0; if (nNameOffset > 0) { nNameOffset--; } nameList[nSlot][nNameOffset] = 0; } else if (ch == asc_Escape) { PlayLocalSound(StaticSound[kSound33], 0); inputState.ClearAllKeyStatus(); inputState.keyFlushChars(); return -1; } else { // check if a slot name is being typed if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch == ' ')) { ch = toupper(ch); if (nNameOffset < 24) // n chars per slot name { nameList[nSlot][nNameOffset] = ch; nNameOffset++; nameList[nSlot][nNameOffset] = '\0'; // null terminate in the new offset int nLen = MyGetStringWidth(nameList[nSlot]); if (nLen > arg_3E) { nNameOffset--; nameList[nSlot][nNameOffset] = '\0'; } } } } } } // loc_399FD: menu_DoPlasma(); int arg_5E = ((int)totalclock / 30) & 1; int y = 90; int arg_42 = 98; for (int i = 0; i < 5; i++) { overwritesprite(55, y, kMenuBlankTitleTile, (i != nSlot) * 31, 2, kPalNormal); int nTextWidth = myprintext(63, arg_42, nameList[i], 0); // flash a full-stop to show the current typing position if (arg_5E != 0 && nSlot == i) { myprintext(nTextWidth, arg_42, ".", 0); } arg_42 += 22; y += 22; } } } int menu_LoadGameMenu() { char nameList[5][25]; int nSlot = 0; FILE *fp = fopen(kSaveFileName, "rb"); if (fp == NULL) { memset(nameList, 0, sizeof(nameList)); } else { fread(nameList, sizeof(nameList), 1, fp); fclose(fp); } while (1) { menu_DoPlasma(); HandleAsync(); overwritesprite(80, 65, kMenuLoadGameTile, 0, 2, kPalNormal); int spriteY = 90; int textY = 98; for (int i = 0; i < kMaxSaveSlots; i++) { // TODO - shade flashing overwritesprite(55, spriteY, kMenuBlankTitleTile, 0, 2, kPalNormal); myprintext(63, textY, nameList[i], 0); textY += 22; spriteY += 22; } int y = (nSlot * 22) + 78; overwritesprite(35, y, kMenuCursorTile, 0, 2, kPalNormal); overwritesprite(233, y, kMenuCursorTile, 0, 10, kPalNormal); videoNextPage(); if (I_EscapeTrigger()) { I_EscapeTriggerClear(); PlayLocalSound(StaticSound[kSound33], 0); return -1; } if (I_MenuUp()) { I_MenuUpClear(); PlayLocalSound(StaticSound[kSound35], 0); if (nSlot > 0) { nSlot--; } else { nSlot = kMaxSaveSlots - 1; } } if (I_MenuDown()) { I_MenuDownClear(); PlayLocalSound(StaticSound[kSound35], 0); if (nSlot < kMaxSaveSlots - 1) { nSlot++; } else { nSlot = 0; } } if (!I_AdvanceTrigger()) { continue; } PlayLocalSound(StaticSound[kSound33], 0); I_AdvanceTriggerClear(); inputState.ClearAllKeyStatus(); inputState.keyFlushChars(); if (nameList[nSlot][0] != '\0') { PlayLocalSound(StaticSound[33], 0); return nSlot; } PlayLocalSound(4, 0); } } void menu_ResetKeyTimer() { keytimer = (int)totalclock + 2400; } void menu_GameLoad2(FILE *fp) { fread(&GameStats, sizeof(GameStats), 1, fp); nPlayerWeapons[nLocalPlayer] = GameStats.nWeapons; PlayerList[nLocalPlayer].nCurrentWeapon = GameStats.nCurrentWeapon; nPlayerClip[nLocalPlayer] = GameStats.clip; int nPistolBullets = PlayerList[nLocalPlayer].nAmmo[kWeaponPistol]; if (nPistolBullets >= 6) { nPistolBullets = 6; } nPistolClip[nLocalPlayer] = nPistolBullets; memcpy(&PlayerList[nLocalPlayer], &GameStats.player, sizeof(Player)); nPlayerItem[nLocalPlayer] = GameStats.items; nPlayerLives[nLocalPlayer] = GameStats.nLives; SetPlayerItem(nLocalPlayer, nPlayerItem[nLocalPlayer]); CheckClip(nLocalPlayer); } short menu_GameLoad(int nSlot) { memset(&GameStats, 0, sizeof(GameStats)); FILE *fp = fopen(kSaveFileName, "rb"); if (fp == NULL) { return 0; } fseek(fp, 125, SEEK_SET); fseek(fp, nSlot * sizeof(GameStats), SEEK_CUR); menu_GameLoad2(fp); fclose(fp); return GameStats.nMap; } void menu_GameSave2(FILE *fp) { memset(&GameStats, 0, sizeof(GameStats)); GameStats.nMap = (uint8_t)levelnew; GameStats.nWeapons = nPlayerWeapons[nLocalPlayer]; GameStats.nCurrentWeapon = PlayerList[nLocalPlayer].nCurrentWeapon; GameStats.clip = nPlayerClip[nLocalPlayer]; GameStats.items = nPlayerItem[nLocalPlayer]; GameStats.nLives = nPlayerLives[nLocalPlayer]; memcpy(&GameStats.player, &PlayerList[nLocalPlayer], sizeof(GameStats.player)); fwrite(&GameStats, sizeof(GameStats), 1, fp); } void menu_GameSave(int nSaveSlot) { if (nSaveSlot < 0) { return; } FILE *fp = fopen(kSaveFileName, "rb+"); if (fp != NULL) { fseek(fp, 125, SEEK_SET); // skip save slot names fseek(fp, sizeof(GameStat) * nSaveSlot, SEEK_CUR); menu_GameSave2(fp); fclose(fp); } } void menu_ResetZoom() { zoomsize = 0; PlayLocalSound(StaticSound[kSound31], 0); } int menu_Menu(int nVal) { GrabPalette(); int var_1C = 0; videoSetViewableArea(0, 0, xdim - 1, ydim - 1); StopAllSounds(); StopLocalSound(); menu_ResetKeyTimer(); inputState.keyFlushChars(); inputState.ClearAllKeyStatus(); menu_ResetZoom(); short ptr[5]; memset(ptr, 1, sizeof(ptr)); // disable new game and load game if in multiplayer? if (nNetPlayerCount) { ptr[1] = 0; ptr[0] = 0; } // denote which menu item we've currently got selected int nMenu = 0; while (1) { HandleAsync(); // skip any disabled menu items so we're selecting the first active one while (!ptr[nMenu]) { nMenu++; if (nMenu == 5) { nMenu = 0; } } // handle the menu zoom-in if (zoomsize < 0x10000) { zoomsize += 4096; if (zoomsize >= 0x10000) { zoomsize = 0x10000; } } // TODO: Uncomment after fixing demo playback // menu idle timer // if (!nVal && (int)totalclock > keytimer) { // return 9; // } // loc_39F54: menu_DoPlasma(); int y = 65 - tilesiz[kMenuNewGameTile].y / 2; // YELLOW loop - Draw the 5 menu options (NEW GAME, TRAINING etc) for (int j = 0; j < 5; j++) { int8_t shade; if (nMenu == j) { // currently selected menu item shade = Sin((int)totalclock << 4) >> 9; } else if (ptr[j]) { shade = 0; } else { shade = 25; } picanm[j + kMenuNewGameTile].xofs = 0; picanm[j + kMenuNewGameTile].yofs = 0; rotatesprite(160 << 16, (y + tilesiz[j + kMenuNewGameTile].y) << 16, zoomsize, 0, kMenuNewGameTile + j, shade, 0, 2, 0, 0, xdim, ydim); y += 22; } // tilesizx is 51 // tilesizy is 33 int markerY = (22 * nMenu) + 53; overwritesprite(62, markerY, kMenuCursorTile, 0, 2, kPalNormal); overwritesprite(62 + 146, markerY, kMenuCursorTile, 0, 10, kPalNormal); videoNextPage(); int l = 0; // edi // ORANGE loop for (l = 0; ; l++) { int nKey = nMenuKeys[l]; if (!nKey) { break; } if (inputState.GetKeyStatus(nKey)) { goto LABEL_21; // TEMP } } // loc_3A0A7 while (I_EscapeTrigger()) { HandleAsync(); PlayLocalSound(StaticSound[kSound33], 0); I_EscapeTriggerClear(); if (nVal) { StopAllSounds(); PlayLocalSound(StaticSound[kSound33], 0); MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); return -1; } l = 4; LABEL_21: menu_ResetKeyTimer(); if (l != nMenu) { PlayLocalSound(StaticSound[kSound35], 0); inputState.ClearKeyStatus(nMenuKeys[l]); nMenu = l; } } if (I_AdvanceTrigger()) { I_AdvanceTriggerClear(); var_1C = 1; } else if (var_1C) { var_1C = 0; PlayLocalSound(StaticSound[kSound33], 0); switch (nMenu) // TODO - change var name? { case kMenuNewGame: { if (nTotalPlayers > 1) { menu_ResetZoom(); menu_ResetKeyTimer(); break; } SavePosition = menu_NewGameMenu(); if (SavePosition == -1) { menu_ResetZoom(); menu_ResetKeyTimer(); break; } FadeOut(1); StopAllSounds(); StopAllSounds(); PlayLocalSound(StaticSound[kSound33], 0); MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); return 1; } case kMenuLoadGame: { if (nTotalPlayers > 1) { menu_ResetZoom(); menu_ResetKeyTimer(); break; } SavePosition = menu_LoadGameMenu(); if (SavePosition == -1) { menu_ResetZoom(); menu_ResetKeyTimer(); break; } StopAllSounds(); StopAllSounds(); PlayLocalSound(StaticSound[kSound33], 0); MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); return 2; } case kMenuTraining: { if (nTotalPlayers > 1) { menu_ResetZoom(); menu_ResetKeyTimer(); break; } StopAllSounds(); PlayLocalSound(StaticSound[kSound33], 0); MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); return 3; } case kMenuVolume: { menu_AdjustVolume(); menu_ResetZoom(); menu_ResetKeyTimer(); break; } case kMenuQuitGame: { StopAllSounds(); StopAllSounds(); PlayLocalSound(StaticSound[kSound33], 0); MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); return 0; } default: menu_ResetZoom(); menu_ResetKeyTimer(); break; } } if (I_MenuUp()) { I_MenuUpClear(); PlayLocalSound(StaticSound[kSound35], 0); if (nMenu <= 0) { nMenu = 4; } else { nMenu--; } menu_ResetKeyTimer(); } if (I_MenuDown()) { I_MenuDownClear(); PlayLocalSound(StaticSound[kSound35], 0); if (nMenu >= 4) { nMenu = 0; } else { nMenu++; } menu_ResetKeyTimer(); } // TODO - change to #defines /* why are these cleares although they are never used anywhere? if (KB_KeyDown[0x5c]) { KB_KeyDown[0x5c] = 0; } if (KB_KeyDown[0x5d]) { KB_KeyDown[0x5d] = 0; } */ } return 0;// todo } #define kMaxCinemaPals 16 const char *cinpalfname[kMaxCinemaPals] = { "3454.pal", "3452.pal", "3449.pal", "3445.pal", "set.pal", "3448.pal", "3446.pal", "hsc1.pal", "2972.pal", "2973.pal", "2974.pal", "2975.pal", "2976.pal", "heli.pal", "2978.pal", "terror.pal" }; int linecount; int nextclock; short nHeight; short nCrawlY; short cinematile; // TODO - moveme int LoadCinemaPalette(int nPal) { nPal--; if (nPal < 0 || nPal >= kMaxCinemaPals) { return -2; } // original code strcpy'd into a buffer first... auto hFile = kopenFileReader(cinpalfname[nPal], 1); if (!hFile.isOpen()) { return -2; } hFile.Read(cinemapal, sizeof(cinemapal)); for (auto &c : cinemapal) c <<= 2; return nPal; } //int IncrementCinemaFadeIn() //{ // dest = cinemapal; // cur = curpal; // // int ebx = 0; // // for (int i = 0; i < 768; i++) // { // ebx++; // // if (*cur < *dest) // { // (*cur)++; // } // else if (*cur == *dest) // { // ebx--; // } // else // { // (*cur)--; // } // // cur++; // dest++; // } // // MySetPalette(curpal); // return ebx; //} void CinemaFadeIn() { BlackOut(); paletteSetColorTable(ANIMPAL, cinemapal); videoSetPalette(0, ANIMPAL, 2+8); #ifdef USE_OPENGL if (videoGetRenderMode() >= REND_POLYMOST) { videoNextPage(); return; } #endif int val; do { val = DoFadeIn(); WaitTicks(2); // need to page flip in each iteration of the loop for non DOS version videoNextPage(); } while (val > 0); } void ComputeCinemaText(int nLine) { linecount = 0; while (1) { if (!strcmp(gString[linecount + nLine], "END")) { break; } int nWidth = MyGetStringWidth(gString[linecount + nLine]); nLeft[linecount] = 160 - nWidth / 2; linecount++; } nCrawlY = 199; nHeight = linecount * 10; ClearAllKeys(); } void ReadyCinemaText(uint16_t nVal) { line = FindGString("CINEMAS"); if (line < 0) { return; } while (nVal) { while (strcmp(gString[line], "END")) { line++; } line++; nVal--; } ComputeCinemaText(line); } uint8_t AdvanceCinemaText() { int var_1C = nCDTrackLength; int tmp = nHeight + nCrawlY > 0; if (tmp || nCDTrackLength && nCDTrackLength > 0) { nextclock = (int)totalclock + 14; if (tmp > 0) { short y = nCrawlY; int edi = 0; while (edi < linecount && y <= 199) { if (y >= -10) { myprintext(nLeft[edi], y, gString[line + edi], 0); } edi++; y += 10; } nCrawlY--; } while (1) { HandleAsync(); if (I_AdvanceTrigger()) { I_AdvanceTriggerClear(); break; } if (var_1C || nCDTrackLength) { if (nextclock <= (int)totalclock) { return kTrue; } } else { return kTrue; } } } return kFalse; } void DoCinemaText(short nVal) { ReadyCinemaText(nVal); while (1) { overwritesprite(0, 0, cinematile, 0, 2, kPalNormal); uint8_t bContinue = AdvanceCinemaText(); WaitVBL(); videoNextPage(); // TEMP int time = (int)totalclock + 8; while ((int)totalclock < time) { HandleAsync(); } if (!bContinue) { return; } } } void GoToTheCinema(int nVal) { UnMaskStatus(); switch (nVal - 1) { default: return; case 0: { LoadCinemaPalette(1); cinematile = 3454; break; } case 1: { LoadCinemaPalette(2); cinematile = 3452; break; } case 2: { LoadCinemaPalette(3); cinematile = 3449; break; } case 3: { LoadCinemaPalette(4); cinematile = 3445; break; } case 4: { LoadCinemaPalette(5); cinematile = 3451; break; } case 5: { LoadCinemaPalette(6); cinematile = 3448; break; } case 6: { LoadCinemaPalette(7); cinematile = 3446; break; } } FadeOut(kFalse); StopAllSounds(); NoClip(); overwritesprite(0, 0, kMovieTile, 100, 2, kPalNormal); videoNextPage(); // int386(16, (const union REGS *)&val, (union REGS *)&val) overwritesprite(0, 0, cinematile, 0, 2, kPalNormal); videoNextPage(); CinemaFadeIn(); ClearAllKeys(); int ebx = -1; int edx = -1; switch (nVal - 1) { default: WaitAnyKey(10); break; case 0: ebx = 4; edx = ebx; break; case 1: ebx = 0; break; case 2: ebx = 2; edx = ebx; break; case 3: ebx = 7; break; case 4: ebx = 3; edx = ebx; break; case 5: ebx = 8; edx = ebx; break; case 6: ebx = 6; edx = ebx; break; } if (ebx != -1) { if (edx != -1) { if (CDplaying()) { fadecdaudio(); } playCDtrack(edx + 2); // , 1); } DoCinemaText(ebx); } FadeOut(kTrue); overwritesprite(0, 0, 764, 100, 2, kPalNormal); videoNextPage(); GrabPalette(); Clip(); } short nBeforeScene[] = { 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; void CheckBeforeScene(int nLevel) { if (nLevel == kMap20) { DoLastLevelCinema(); return; } short nScene = nBeforeScene[nLevel]; if (nScene) { if (!nCinemaSeen[nScene]) { GoToTheCinema(nScene); nCinemaSeen[nScene] = 1; } } } int showmap(short nLevel, short nLevelNew, short nLevelBest) { FadeOut(0); EraseScreen(overscanindex); GrabPalette(); BlackOut(); if (nLevelNew != 11) { CheckBeforeScene(nLevelNew); } int selectedLevel = menu_DrawTheMap(nLevel, nLevelNew, nLevelBest); if (selectedLevel == 11) { CheckBeforeScene(selectedLevel); } return selectedLevel; } void DoAfterCinemaScene(int nLevel) { short word_9ABD5[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 7, 0, 0, 0, 0, 6 }; if (word_9ABD5[nLevel]) { GoToTheCinema(word_9ABD5[nLevel]); } } void DoFailedFinalScene() { videoSetViewableArea(0, 0, xdim - 1, ydim - 1); if (CDplaying()) { fadecdaudio(); } playCDtrack(9); FadeToWhite(); // TODO GoToTheCinema(word_9ABFF); } int FindGString(const char *str) { int i = 0; while (1) { if (!strcmp(gString[i], str)) return i + 1; if (!strcmp(gString[i], "EOF")) break; i++; } return -1; } uint8_t CheckForEscape() { if (!inputState.keyBufferWaiting() || (inputState.keyGetChar() != 27)) { return kFalse; } return kTrue; } void DoStatic(int a, int b) { RandomLong(); // nothing done with the result of this? auto pixels = TileFiles.tileMakeWritable(kTileLoboLaptop); int v2 = 160 - a / 2; int v4 = 81 - b / 2; int var_18 = v2 + a; int v5 = v4 + b; auto pTile = (pixels + (200 * v2)) + v4; while (v2 < var_18) { uint8_t *pStart = pTile; pTile += 200; int v7 = v4; while (v7 < v5) { *pStart = RandomBit() * 16; v7++; pStart++; } v2++; } tileInvalidate(kTileLoboLaptop, 0, 0); overwritesprite(0, 0, kTileLoboLaptop, 0, 2, kPalNormal); videoNextPage(); } void DoLastLevelCinema() { FadeOut(0); UnMaskStatus(); videoSetViewableArea(0, 0, xdim - 1, ydim - 1); EraseScreen(-1); RestorePalette(); int nString = FindGString("LASTLEVEL"); PlayLocalSound(StaticSound[kSound75], 0); auto pixels = TileFiles.tileMakeWritable(kTileLoboLaptop); // uh, what? //memcpy((void*)waloff[kTileLoboLaptop], (void*)waloff[kTileLoboLaptop], tilesiz[kTileLoboLaptop].x * tilesiz[kTileLoboLaptop].y); int var_24 = 16; int var_28 = 12; int nEndTime = (int)totalclock + 240; while (inputState.keyBufferWaiting()) { inputState.keyGetChar(); } while (nEndTime > (int)totalclock) { HandleAsync(); if (var_24 >= 116) { if (var_28 < 192) var_28 += 20; } else { var_24 += 20; } DoStatic(var_28, var_24); } // loc_3AD75 do { HandleAsync(); LABEL_11: if (strlen(gString[nString]) == 0) break; int esi = nString; while (strlen(gString[esi]) != 0) esi++; int ebp = esi; ebp -= nString; ebp = 81 - (ebp <<= 2); int var_1C = esi - nString; // loc_3ADD7 while (1) { HandleAsync(); if (strlen(gString[nString]) == 0) break; int xPos = 70; const char *nChar = gString[nString]; nString++; TileFiles.tileMakeWritable(kTileLoboLaptop); while (*nChar) { HandleAsync(); if (*nChar != ' ') { PlayLocalSound(StaticSound[kSound71], 0); } xPos += CopyCharToBitmap(*nChar, kTileLoboLaptop, xPos, ebp); nChar++; overwritesprite(0, 0, kTileLoboLaptop, 0, 2, kPalNormal); videoNextPage(); WaitVBL(); if (CheckForEscape()) goto LABEL_28; } ebp += 8; } nString++; inputState.keyFlushChars(); inputState.ClearAllKeyStatus(); int v11 = kTimerTicks * (var_1C + 2) + (int)totalclock; do { HandleAsync(); if (v11 <= (int)totalclock) goto LABEL_11; } while (!inputState.keyBufferWaiting()); } while (inputState.keyGetChar() != 27); LABEL_28: PlayLocalSound(StaticSound[kSound75], 0); while (1) { HandleAsync(); DoStatic(var_28, var_24); if (var_28 > 20) { var_28 -= 20; continue; } if (var_24 > 20) { var_24 -= 20; continue; } break; } EraseScreen(-1); tileLoad(kTileLoboLaptop); FadeOut(0); MySetView(nViewLeft, nViewTop, nViewRight, nViewBottom); MaskStatus(); } END_PS_NS