/** @file menu.cpp @brief This file contains main menu create and processing routines. TwinEngine: a Little Big Adventure engine Copyright (C) 2013 The TwinEngine team Copyright (C) 2008-2013 Prequengine team Copyright (C) 2002-2007 The TwinEngine team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sys.h" #include "main.h" #include "menu.h" #include "menuoptions.h" #include "resources.h" #include "music.h" #include "sound.h" #include "screens.h" #include "sdlengine.h" #include "hqrdepack.h" #include "lbaengine.h" #include "text.h" #include "interface.h" #include "redraw.h" #include "keyboard.h" #include "scene.h" #include "animations.h" #include "actor.h" #include "movements.h" #include "gamestate.h" #include "renderer.h" #include "grid.h" #include "gamestate.h" /** Main menu background image number Used when returning from credit sequence to redraw the main menu background image */ uint32 kPlasmaEffectFilesize = 262176; /** Menu buttons width */ uint16 kMainMenuButtonWidth = 320; /** Used to calculate the spanning between button and screen */ uint16 kMainMenuButtonSpan = 550; /** Main menu types */ enum MainMenuType { kNewGame = 20, kContinueGame = 21, kOptions = 23, kQuit = 22, kBackground = 9999 }; /** Give up menu types */ enum GiveUpMenuType { kContinue = 28, kGiveUp = 27 // quit }; /** Options menu types */ enum OptionsMenuType { kReturnGame = 15, kReturnMenu = 26, kVolume = 30, kSaveManage = 46, kAdvanced = 47 }; /** Volume menu types */ enum VolumeMenuType { kMusicVolume = 1, kSoundVolume = 2, kCDVolume = 3, kLineVolume = 4, kMasterVolume = 5 }; /** Main Menu Settings Used to create the game main menu. */ int16 MainMenuSettings[] = { 0, // Current loaded button (button number) 4, // Num of buttons 200, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 20, // new game 0, 21, // continue game 0, 23, // options 0, 22, // quit }; /** Give Up Menu Settings Used to create the in-game menu. */ int16 GiveUpMenuSettings[] = { 0, // Current loaded button (button number) 2, // Num of buttons 240, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 28, // continue game 0, 27, // quit game }; /** Give Up Menu Settings Used to create the in-game menu. This menu have one extra item to save the game */ int16 GiveUpMenuSettingsWithSave[] = { 0, // Current loaded button (button number) 3, // Num of buttons 240, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 28, // continue game 0, 16, // save game 0, 27, // quit game }; /** Options Menu Settings Used to create the options menu. */ int16 OptionsMenuSettings[] = { 0, // Current loaded button (button number) 4, // Num of buttons 0, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 24, // return to previous menu 0, 30, // volume settings 0, 46, // save game management 0, 47, // advanced options }; /** Advanced Options Menu Settings Used to create the advanced options menu. */ int16 AdvOptionsMenuSettings[] = { 0, // Current loaded button (button number) 5, // Num of buttons 0, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 26, // return to main menu 0, 4, // aggressive mode (manual|auto) 6, 31, // Polygon detail (full|medium|low) 7, 32, // Shadows (all|character|no) 8, 33, // scenary zoon (on|off) }; /** Save Game Management Menu Settings Used to create the save game management menu. */ int16 SaveManageMenuSettings[] = { 0, // Current loaded button (button number) 3, // Num of buttons 0, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 26, // return to main menu 0, 41, // copy saved game 0, 45, // delete saved game }; /** Volume Menu Settings Used to create the volume menu. */ int16 VolumeMenuSettings[] = { 0, // Current loaded button (button number) 7, // Num of buttons 0, // Buttons box height ( is used to calc the height where the first button will appear ) 0, // unused 0, 26, // return to main menu 1, 10, // music volume 2, 11, // sfx volume 3, 12, // cd volume 4, 13, // line-in volume 5, 14, // master volume 0, 16, // save parameters }; /** Plasma Effect pointer to file content: RESS.HQR:51 */ uint8 *plasmaEffectPtr; /** Hero behaviour menu entity */ uint8 *behaviourEntity; /** Behaviour menu anim state */ int16 behaviourAnimState[4]; // winTab /** Behaviour menu anim data pointer */ AnimTimerDataStruct behaviourAnimData[4]; int32 inventorySelectedColor; int32 inventorySelectedItem; // currentSelectedObjectInInventory #define PLASMA_WIDTH 320 #define PLASMA_HEIGHT 50 #define SCREEN_W 640 void plasmaEffectRenderFrame() { int16 c; int32 i, j; uint8 *dest; uint8 *src; for (j = 1; j < PLASMA_HEIGHT - 1; j++) { for (i = 1; i < PLASMA_WIDTH - 1; i++) { /*Here we calculate the average of all 8 neighbour pixel values*/ c = plasmaEffectPtr[(i-1) + (j-1) * PLASMA_WIDTH]; //top-left c += plasmaEffectPtr[(i+0) + (j-1) * PLASMA_WIDTH]; //top c += plasmaEffectPtr[(i+1) + (j-1) * PLASMA_WIDTH]; //top-right c += plasmaEffectPtr[(i-1) + (j+0) * PLASMA_WIDTH]; //left c += plasmaEffectPtr[(i+1) + (j+0) * PLASMA_WIDTH]; //right c += plasmaEffectPtr[(i-1) + (j+1) * PLASMA_WIDTH]; // bottom-left c += plasmaEffectPtr[(i+0) + (j+1) * PLASMA_WIDTH]; // bottom c += plasmaEffectPtr[(i+1) + (j+1) * PLASMA_WIDTH]; // bottom-right c = (c >> 3) | ((c & 0x0003) << 13); /* And the 2 least significant bits are used as a randomizing parameter for statistically fading the flames */ if (!(c & 0x6500) && (j >= (PLASMA_HEIGHT-4) || c > 0)){ c--; /*fade this pixel*/ } /* plot the pixel using the calculated color */ plasmaEffectPtr[i + (PLASMA_HEIGHT+j)*PLASMA_WIDTH] = (uint8) c; } } // flip the double-buffer while scrolling the effect vertically: dest = plasmaEffectPtr; src = plasmaEffectPtr + (PLASMA_HEIGHT+1) * PLASMA_WIDTH; for (i = 0; i < PLASMA_HEIGHT * PLASMA_WIDTH; i++) *(dest++) = *(src++); } /** Process the plasma effect @param top top height where the effect will be draw in the front buffer @param color plasma effect start color */ void processPlasmaEffect(int32 top, int32 color) { uint8 *in; uint8 *out; int32 i, j, target; uint8 c; uint8 max_value = color + 15; plasmaEffectRenderFrame(); in = plasmaEffectPtr + 5 * PLASMA_WIDTH; out = frontVideoBuffer + screenLookupTable[top]; for (i = 0; i < 25; i++) { for (j = 0; j < kMainMenuButtonWidth; j++) { c = in[i*kMainMenuButtonWidth + j] / 2 + color; if (c > max_value) c = max_value; /* 2x2 squares sharing the same pixel color: */ target = 2*(i*SCREEN_W + j); out[target] = c; out[target + 1] = c; out[target + SCREEN_W] = c; out[target + SCREEN_W + 1] = c; } } } /** Draw the entire button box @param left start width to draw the button @param top start height to draw the button @param right end width to draw the button @param bottom end height to draw the button */ void drawBox(int32 left, int32 top, int32 right, int32 bottom) { drawLine(left, top, right, top, 79); // top line drawLine(left, top, left, bottom, 79); // left line drawLine(right, ++top, right, bottom, 73); // right line drawLine(++left, bottom, right, bottom, 73); // bottom line } /** Draws main menu button @param width menu button width @param topheight is the height between the top of the screen and the first button @param id current button identification from menu settings @param value current button key pressed value @param mode flag to know if should draw as a hover button or not */ void drawButtonGfx(int32 width, int32 topheight, int32 id, int32 value, int32 mode) { int32 right; int32 top; int32 left; int32 bottom2; int32 bottom; int32 textSize; int8 dialText[256]; /* * int CDvolumeRemaped; int musicVolumeRemaped; int masterVolumeRemaped; int lineVolumeRemaped; * int waveVolumeRemaped; */ memset(dialText, 0, sizeof(dialText)); left = width - kMainMenuButtonSpan / 2; right = width + kMainMenuButtonSpan / 2; // topheigh is the center Y pos of the button top = topheight - 25; // this makes the button be 50 height bottom = bottom2 = topheight + 25; // || if (mode != 0) { if (id <= 5 && id >= 1) { int32 newWidth = 0; switch (id) { case 1: { if (cfgfile.MusicVolume > 255) cfgfile.MusicVolume = 255; if (cfgfile.MusicVolume < 0) cfgfile.MusicVolume = 0; newWidth = crossDot(left, right, 255, cfgfile.MusicVolume); break; } case 2: { if (cfgfile.WaveVolume > 255) cfgfile.WaveVolume = 255; if (cfgfile.WaveVolume < 0) cfgfile.WaveVolume = 0; newWidth = crossDot(left, right, 255, cfgfile.WaveVolume); break; } case 3: { if (cfgfile.CDVolume > 255) cfgfile.CDVolume = 255; if (cfgfile.CDVolume < 0) cfgfile.CDVolume = 0; newWidth = crossDot(left, right, 255, cfgfile.CDVolume); break; } case 4: { if (cfgfile.LineVolume > 255) cfgfile.LineVolume = 255; if (cfgfile.LineVolume < 0) cfgfile.LineVolume = 0; newWidth = crossDot(left, right, 255, cfgfile.LineVolume); break; } case 5: { if (cfgfile.MasterVolume > 255) cfgfile.MasterVolume = 255; if (cfgfile.MasterVolume < 0) cfgfile.MasterVolume = 0; newWidth = crossDot(left, right, 255, cfgfile.MasterVolume); break; } }; processPlasmaEffect(top, 80); if (!(rand() % 5)) { plasmaEffectPtr[rand() % 140 * 10 + 1900] = 255; } drawSplittedBox(newWidth, top, right, bottom, 68); } else { processPlasmaEffect(top, 64); if (!(rand() % 5)) { plasmaEffectPtr[rand() % 320 * 10 + 6400] = 255; } } if (id <= 5 && id >= 1) { // implement this } } else { blitBox(left, top, right, bottom, (int8 *) workVideoBuffer, left, top, (int8 *) frontVideoBuffer); drawTransparentBox(left, top, right, bottom2, 4); } drawBox(left, top, right, bottom); setFontColor(15); setFontParameters(2, 8); getMenuText(value, dialText); textSize = getTextSize(dialText); drawText(width - (textSize / 2), topheight - 18, dialText); // TODO: make volume buttons copyBlockPhys(left, top, right, bottom); } /** Process the menu button draw @param data menu settings array @param mode flag to know if should draw as a hover button or not */ void drawButton(int16 *menuSettings, int32 mode) { int32 buttonNumber; int32 maxButton; int16 *localData = menuSettings; int32 topHeight; uint8 menuItemId; uint16 menuItemValue; // applicable for sound menus, to save the volume/sound bar int8 currentButton; buttonNumber = *localData; localData += 1; maxButton = *localData; localData += 1; topHeight = *localData; localData += 2; if (topHeight == 0) { topHeight = 35; } else { topHeight = topHeight - (((maxButton - 1) * 6) + ((maxButton) * 50)) / 2; } if (maxButton <= 0) { return; } currentButton = 0; do { // get menu item settings menuItemId = (uint8) * localData; localData += 1; menuItemValue = *localData; localData += 1; if (mode != 0) { if (currentButton == buttonNumber) { drawButtonGfx(kMainMenuButtonWidth, topHeight, menuItemId, menuItemValue, 1); } } else { if (currentButton == buttonNumber) { drawButtonGfx(kMainMenuButtonWidth, topHeight, menuItemId, menuItemValue, 1); } else { drawButtonGfx(kMainMenuButtonWidth, topHeight, menuItemId, menuItemValue, 0); } } currentButton++; topHeight += 56; // increase button top height // slow down the CPU sdldelay(1); } while (currentButton < maxButton); } /** Where the main menu options are processed @param menuSettings menu settings array with the information to build the menu options @return pressed menu button identification */ int32 processMenu(int16 * menuSettings) { int32 localTime; int32 numEntry; int32 buttonNeedRedraw; int32 maxButton; int16 *localData = menuSettings; int16 currentButton; int16 id; int32 musicChanged; int32 buttonReleased = 1; musicChanged = 0; buttonNeedRedraw = 1; numEntry = localData[1]; currentButton = 0; // localData[0]; localTime = lbaTime; maxButton = numEntry - 1; readKeys(); do { // if its on main menu if (localData == MainMenuSettings) { if (lbaTime - localTime <= 11650) { if (skipIntro == 46) if (skippedKey != 32) return kBackground; } else { return kBackground; } } if (pressedKey == 0) { buttonReleased = 1; } if (buttonReleased) { key = pressedKey; if (((uint8) key & 2)) { // on arrow key down currentButton++; if (currentButton == numEntry) { // if current button is the last, than next button is the first currentButton = 0; } buttonNeedRedraw = 1; buttonReleased = 0; } if (((uint8) key & 1)) { // on arrow key up currentButton--; if (currentButton < 0) { // if current button is the first, than previous button is the last currentButton = maxButton; } buttonNeedRedraw = 1; buttonReleased = 0; } if (*(localData + 8) <= 5) { // if its a volume button id = *(localData + currentButton * 2 + 4); // get button parameters from settings array switch (id) { case kMusicVolume: { if (((uint8) key & 4)) { // on arrow key left cfgfile.MusicVolume -= 4; } if (((uint8) key & 8)) { // on arrow key right cfgfile.MusicVolume += 4; } musicVolume(cfgfile.MusicVolume); break; } case kSoundVolume: { if (((uint8) key & 4)) { // on arrow key left cfgfile.WaveVolume -= 4; } if (((uint8) key & 8)) { // on arrow key right cfgfile.WaveVolume += 4; } sampleVolume(-1, cfgfile.WaveVolume); break; } case kCDVolume: { if (((uint8) key & 4)) { // on arrow key left cfgfile.CDVolume -= 4; } if (((uint8) key & 8)) { // on arrow key right cfgfile.CDVolume += 4; } break; } case kLineVolume: { if (((uint8) key & 4)) { // on arrow key left cfgfile.LineVolume -= 4; } if (((uint8) key & 8)) { // on arrow key right cfgfile.LineVolume += 4; } break; } case kMasterVolume: { if (((uint8) key & 4)) { // on arrow key left cfgfile.MasterVolume -= 4; } if (((uint8) key & 8)) { // on arrow key right cfgfile.MasterVolume += 4; } musicVolume(cfgfile.MusicVolume); sampleVolume(-1, cfgfile.WaveVolume); break; } default: break; } } } if (buttonNeedRedraw == 1) { *localData = currentButton; drawButton(localData, 0); // current button do { readKeys(); drawButton(localData, 1); } while (pressedKey == 0 && skippedKey == 0 && skipIntro == 0); buttonNeedRedraw = 0; } else { if (musicChanged) { // TODO: update volume settings } buttonNeedRedraw = 0; drawButton(localData, 1); readKeys(); // WARNING: this is here to prevent a fade bug while quit the menu copyScreen(workVideoBuffer, frontVideoBuffer); } } while (!(skippedKey & 2) && !(skippedKey & 1)); currentButton = *(localData + 5 + currentButton * 2); // get current browsed button readKeys(); return currentButton; } /** Used to run the advanced options menu */ int32 advoptionsMenu() { int32 ret = 0; copyScreen(workVideoBuffer, frontVideoBuffer); do { switch (processMenu(AdvOptionsMenuSettings)) { case kReturnMenu: { ret = 1; // quit option menu break; } //TODO: add other options default: break; } } while (ret != 1); copyScreen(workVideoBuffer, frontVideoBuffer); flip(); return 0; } /** Used to run the save game management menu */ int32 savemanageMenu() { int32 ret = 0; copyScreen(workVideoBuffer, frontVideoBuffer); do { switch (processMenu(SaveManageMenuSettings)) { case kReturnMenu: { ret = 1; // quit option menu break; } //TODO: add other options default: break; } } while (ret != 1); copyScreen(workVideoBuffer, frontVideoBuffer); flip(); return 0; } /** Used to run the volume menu */ int32 volumeMenu() { int32 ret = 0; copyScreen(workVideoBuffer, frontVideoBuffer); do { switch (processMenu(VolumeMenuSettings)) { case kReturnMenu: { ret = 1; // quit option menu break; } //TODO: add other options default: break; } } while (ret != 1); copyScreen(workVideoBuffer, frontVideoBuffer); flip(); return 0; } /** Used to run the options menu */ int32 optionsMenu() { int32 ret = 0; copyScreen(workVideoBuffer, frontVideoBuffer); stopSamples(); //playCDtrack(9); do { switch (processMenu(OptionsMenuSettings)) { case kReturnGame: case kReturnMenu: { ret = 1; // quit option menu break; } case kVolume: { copyScreen(workVideoBuffer, frontVideoBuffer); flip(); volumeMenu(); break; } case kSaveManage: { copyScreen(workVideoBuffer, frontVideoBuffer); flip(); savemanageMenu(); break; } case kAdvanced: { copyScreen(workVideoBuffer, frontVideoBuffer); flip(); advoptionsMenu(); break; } default: break; } } while (ret != 1); copyScreen(workVideoBuffer, frontVideoBuffer); flip(); return 0; } /** Used to run the main menu */ void mainMenu() { stopSamples(); copyScreen(frontVideoBuffer, workVideoBuffer); // load menu effect file only once plasmaEffectPtr = (uint8 *)malloc(kPlasmaEffectFilesize); memset(plasmaEffectPtr, 0, kPlasmaEffectFilesize); hqrGetEntry(plasmaEffectPtr, HQR_RESS_FILE, RESSHQR_PLASMAEFFECT); while (!cfgfile.Quit) { initTextBank(0); playTrackMusic(9); // LBA's Theme stopSamples(); switch (processMenu(MainMenuSettings)) { case kNewGame: { newGameMenu(); break; } case kContinueGame: { continueGameMenu(); break; } case kOptions: { copyScreen(workVideoBuffer, frontVideoBuffer); flip(); OptionsMenuSettings[5] = kReturnMenu; optionsMenu(); break; } case kQuit: { cfgfile.Quit = 1; break; } case kBackground: { loadMenuImage(1); } } fpsCycles(cfgfile.Fps); } } /** Used to process give up menu while playing game */ int32 giveupMenu() { //int32 saveLangue=0; int32 menuId; int16 * localMenu; copyScreen(frontVideoBuffer, workVideoBuffer); pauseSamples(); if (cfgfile.UseAutoSaving == 1) localMenu = GiveUpMenuSettings; else localMenu = GiveUpMenuSettingsWithSave; do { //saveLangue = languageCD1; //languageCD1 = 0; initTextBank(0); menuId = processMenu(localMenu); //languageCD1 = saveLangue; initTextBank(currentTextBank + 3); fpsCycles(cfgfile.Fps); } while (menuId != kGiveUp && menuId != kContinue); if (menuId == kGiveUp) { stopSamples(); return 1; } resumeSamples(); return 0; } void drawInfoMenu(int16 left, int16 top) { int32 boxLeft, boxTop, boxRight, boxBottom; int32 newBoxLeft, newBoxLeft2, i; resetClip(); drawBox(left, top, left + 450, top + 80); drawSplittedBox(left + 1, top + 1, left + 449, top + 79, 0); newBoxLeft2 = left + 9; drawSprite(0, newBoxLeft2, top + 13, spriteTable[SPRITEHQR_LIFEPOINTS]); boxRight = left + 325; newBoxLeft = left + 25; boxLeft = crossDot(newBoxLeft, boxRight, 50, sceneHero->life); boxTop = top + 10; boxBottom = top + 25; drawSplittedBox(newBoxLeft, boxTop, boxLeft, boxBottom, 91); drawBox(left + 25, top + 10, left + 324, top + 10 + 14); if (!gameFlags[GAMEFLAG_INVENTORY_DISABLED] && gameFlags[GAMEFLAG_TUNIC]) { drawSprite(0, newBoxLeft2, top + 36, spriteTable[SPRITEHQR_MAGICPOINTS]); if(magicLevelIdx > 0) { drawSplittedBox(newBoxLeft, top + 35, crossDot(newBoxLeft, boxRight, 80, inventoryMagicPoints),top + 50, 75); } drawBox(left + 25, top + 35, left + magicLevelIdx * 80 + 20, top + 35 + 15); } boxLeft = left + 340; /** draw coin sprite */ drawSprite(0, boxLeft, top + 15, spriteTable[SPRITEHQR_KASHES]); setFontColor(155); drawText(left + 370, top + 5, ITOA(inventoryNumKashes)); /** draw key sprite */ drawSprite(0, boxLeft, top + 55, spriteTable[SPRITEHQR_KEY]); setFontColor(155); drawText(left + 370, top + 40, ITOA(inventoryNumKeys)); // prevent if (inventoryNumLeafs > inventoryNumLeafsBox) { inventoryNumLeafs = inventoryNumLeafsBox; } // Clover leaf boxes for (i = 0; i < inventoryNumLeafsBox; i++) { drawSprite(0, crossDot(left + 25, left + 325, 10, i), top + 58, spriteTable[SPRITEHQR_CLOVERLEAFBOX]); } // Clover leafs for (i = 0; i < inventoryNumLeafs; i++) { drawSprite(0, crossDot(left + 25, left + 325, 10, i) + 2, top + 60, spriteTable[SPRITEHQR_CLOVERLEAF]); } copyBlockPhys(left, top, left + 450, top + 135); } void drawBehaviour(int16 behaviour, int32 angle, int16 cantDrawBox) { uint8 *currentAnim; int32 boxLeft, boxTop, boxRight, boxBottom, currentAnimState; int8 dialText[256]; boxLeft = behaviour * 110 + 110; boxRight = boxLeft + 99; boxTop = 110; boxBottom = 229; currentAnim = animTable[heroAnimIdx[behaviour]]; currentAnimState = behaviourAnimState[behaviour]; if (setModelAnimation(currentAnimState, currentAnim, behaviourEntity, &behaviourAnimData[behaviour])) { currentAnimState++; // keyframe if (currentAnimState >= getNumKeyframes(currentAnim)) { currentAnimState = getStartKeyframe(currentAnim); } behaviourAnimState[behaviour] = currentAnimState; } if (cantDrawBox == 0) { drawBox(boxLeft - 1, boxTop - 1, boxRight + 1, boxBottom + 1); } saveClip(); resetClip(); if (behaviour != heroBehaviour) { // unselected drawSplittedBox(boxLeft, boxTop, boxRight, boxBottom, 0); } else { // selected drawSplittedBox(boxLeft, boxTop, boxRight, boxBottom, 69); // behaviour menu title drawSplittedBox(110, 239, 540, 279, 0); drawBox(110, 239, 540, 279); setFontColor(15); if (heroBehaviour == 2 && autoAgressive == 1) { getMenuText(4, dialText); } else { getMenuText(heroBehaviour, dialText); } drawText((650 - getTextSize(dialText)) / 2, 240, dialText); } renderBehaviourModel(boxLeft, boxTop, boxRight, boxBottom, -600, angle, behaviourEntity); copyBlockPhys(boxLeft, boxTop, boxRight, boxBottom); copyBlockPhys(110, 239, 540, 279); loadClip(); } void drawBehaviourMenu(int32 angle) { drawBox(100, 100, 550, 290); drawTransparentBox(101, 101, 549, 289, 2); setAnimAtKeyframe(behaviourAnimState[kNormal], animTable[heroAnimIdx[kNormal]], behaviourEntity, &behaviourAnimData[kNormal]); drawBehaviour(kNormal, angle, 0); setAnimAtKeyframe(behaviourAnimState[kAthletic], animTable[heroAnimIdx[kAthletic]], behaviourEntity, &behaviourAnimData[kAthletic]); drawBehaviour(kAthletic, angle, 0); setAnimAtKeyframe(behaviourAnimState[kAggressive], animTable[heroAnimIdx[kAggressive]], behaviourEntity, &behaviourAnimData[kAggressive]); drawBehaviour(kAggressive, angle, 0); setAnimAtKeyframe(behaviourAnimState[kDiscrete], animTable[heroAnimIdx[kDiscrete]], behaviourEntity, &behaviourAnimData[kDiscrete]); drawBehaviour(kDiscrete, angle, 0); drawInfoMenu(100, 300); copyBlockPhys(100, 100, 550, 290); } /** Process hero behaviour menu */ void processBehaviourMenu() { int32 tmpLanguageCD; int32 tmpTextBank; int32 tmpHeroBehaviour; int32 tmpTime; if (heroBehaviour == kProtoPack) { stopSamples(); setBehaviour(kNormal); } behaviourEntity = bodyTable[sceneHero->entity]; heroAnimIdx[kNormal] = heroAnimIdxNORMAL; heroAnimIdx[kAthletic] = heroAnimIdxATHLETIC; heroAnimIdx[kAggressive] = heroAnimIdxAGGRESSIVE; heroAnimIdx[kDiscrete] = heroAnimIdxDISCRETE; setActorAngleSafe(sceneHero->angle, sceneHero->angle - 256, 50, &moveMenu); copyScreen(frontVideoBuffer, workVideoBuffer); tmpLanguageCD = cfgfile.LanguageCDId; cfgfile.LanguageCDId = 0; tmpTextBank = currentTextBank; currentTextBank = -1; initTextBank(0); drawBehaviourMenu(sceneHero->angle); tmpHeroBehaviour = heroBehaviour; setAnimAtKeyframe(behaviourAnimState[heroBehaviour], animTable[heroAnimIdx[heroBehaviour]], behaviourEntity, &behaviourAnimData[heroBehaviour]); readKeys(); tmpTime = lbaTime; while (skippedKey & 4 || (skipIntro >= 59 && skipIntro <= 62)) { readKeys(); key = pressedKey; if (key & 8) { heroBehaviour++; } if (key & 4) { heroBehaviour--; } if (heroBehaviour < 0) { heroBehaviour = 3; } if (heroBehaviour >= 4) { heroBehaviour = 0; } if (tmpHeroBehaviour != heroBehaviour) { drawBehaviour(tmpHeroBehaviour, sceneHero->angle, 1); tmpHeroBehaviour = heroBehaviour; setActorAngleSafe(sceneHero->angle, sceneHero->angle - 256, 50, &moveMenu); setAnimAtKeyframe(behaviourAnimState[heroBehaviour], animTable[heroAnimIdx[heroBehaviour]], behaviourEntity, &behaviourAnimData[heroBehaviour]); while (pressedKey) { readKeys(); drawBehaviour(heroBehaviour, -1, 1); } } drawBehaviour(heroBehaviour, -1, 1); fpsCycles(50); lbaTime++; } lbaTime = tmpTime; setBehaviour(heroBehaviour); initEngineProjections(); currentTextBank = tmpTextBank; initTextBank(currentTextBank + 3); cfgfile.LanguageCDId = tmpLanguageCD; } /** Draw the entire button box @param left start width to draw the button @param top start height to draw the button @param right end width to draw the button @param bottom end height to draw the button */ void drawMagicItemsBox(int32 left, int32 top, int32 right, int32 bottom, int32 color) { // Rect drawLine(left, top, right, top, color); // top line drawLine(left, top, left, bottom, color); // left line drawLine(right, ++top, right, bottom, color); // right line drawLine(++left, bottom, right, bottom, color); // bottom line } void drawItem(int32 item) { int32 itemX = (item / 4) * 85 + 64; int32 itemY = (item & 3) * 75 + 52; int32 left = itemX - 37; int32 right = itemX + 37; int32 top = itemY - 32; int32 bottom = itemY + 32; drawSplittedBox(left, top, right, bottom, inventorySelectedItem == item ? inventorySelectedColor : 0); if (gameFlags[item] && !gameFlags[GAMEFLAG_INVENTORY_DISABLED] && item < NUM_INVENTORY_ITEMS) { prepareIsoModel(inventoryTable[item]); itemAngle[item] += 8; renderInventoryItem(itemX, itemY, inventoryTable[item], itemAngle[item], 15000); if (item == 15) { // has GAS setFontColor(15); drawText(left + 3, top + 32, ITOA(inventoryNumGas)); } } drawBox(left, top, right, bottom); copyBlockPhys(left, top, right, bottom); } void drawInventoryItems() { int32 item; drawTransparentBox(17, 10, 622, 320, 4); drawBox(17, 10, 622, 320); drawMagicItemsBox(110, 18, 188, 311, 75); copyBlockPhys(17, 10, 622, 320); for (item = 0; item < NUM_INVENTORY_ITEMS; item++) { drawItem(item); } } /** Process in-game inventory menu */ void processInventoryMenu() { int32 di = 1; int32 prevSelectedItem, tmpLanguageCD, bx, tmpAlphaLight, tmpBetaLight; tmpAlphaLight = alphaLight; tmpBetaLight = betaLight; copyScreen(frontVideoBuffer, workVideoBuffer); setLightVector(896, 950, 0); inventorySelectedColor = 68; if (inventoryNumLeafs > 0) { gameFlags[GAMEFLAG_HAS_CLOVER_LEAF] = 1; } drawInventoryItems(); tmpLanguageCD = cfgfile.LanguageCDId; cfgfile.LanguageCDId = 0; initTextBank(2); bx = 3; setFontCrossColor(4); initDialogueBox(); while (skipIntro != 1) { readKeys(); prevSelectedItem = inventorySelectedItem; if (!di) { key = pressedKey; loopPressedKey = skippedKey; loopCurrentKey = skipIntro; if (key != 0 || skippedKey != 0) { di = 1; } } else { loopCurrentKey = 0; key = 0; loopPressedKey = 0; if (!pressedKey && !skippedKey) { di = 0; } } if (loopCurrentKey == 1 || loopPressedKey & 0x20) break; if (key & 2) { // down inventorySelectedItem++; if (inventorySelectedItem >= NUM_INVENTORY_ITEMS) { inventorySelectedItem = 0; } drawItem(prevSelectedItem); bx = 3; } if (key & 1) { // up inventorySelectedItem--; if (inventorySelectedItem < 0) { inventorySelectedItem = NUM_INVENTORY_ITEMS - 1; } drawItem(prevSelectedItem); bx = 3; } if (key & 4) { // left inventorySelectedItem -= 4; if (inventorySelectedItem < 0) { inventorySelectedItem += NUM_INVENTORY_ITEMS; } drawItem(prevSelectedItem); bx = 3; } if (key & 8) { // right inventorySelectedItem += 4; if (inventorySelectedItem >= NUM_INVENTORY_ITEMS) { inventorySelectedItem -= NUM_INVENTORY_ITEMS; } drawItem(prevSelectedItem); bx = 3; } if (bx == 3) { initInventoryDialogueBox(); if (gameFlags[inventorySelectedItem] == 1 && !gameFlags[GAMEFLAG_INVENTORY_DISABLED] && inventorySelectedItem < NUM_INVENTORY_ITEMS) { initText(inventorySelectedItem + 100); } else { initText(128); } bx = 0; } if (bx != 2) { bx = printText10(); } // TRICKY: 3D model rotation delay - only apply when no text is drawing if (bx == 0 || bx == 2) { sdldelay(15); } if (loopPressedKey & 1) { if (bx == 2) { initInventoryDialogueBox(); bx = 0; } else { if (gameFlags[inventorySelectedItem] == 1 && !gameFlags[GAMEFLAG_INVENTORY_DISABLED] && inventorySelectedItem < NUM_INVENTORY_ITEMS) { initInventoryDialogueBox(); initText(inventorySelectedItem + 100); } } } drawItem(inventorySelectedItem); if ((loopPressedKey & 2) && gameFlags[inventorySelectedItem] == 1 && !gameFlags[GAMEFLAG_INVENTORY_DISABLED] && inventorySelectedItem < NUM_INVENTORY_ITEMS) { loopInventoryItem = inventorySelectedItem; inventorySelectedColor = 91; drawItem(inventorySelectedItem); break; } } printTextVar13 = 0; alphaLight = tmpAlphaLight; betaLight = tmpBetaLight; initEngineProjections(); cfgfile.LanguageCDId = tmpLanguageCD; initTextBank(currentTextBank + 3); while (skipIntro != 0 && skippedKey != 0) { readKeys(); sdldelay(1); } }