/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "bbvs/bbvs.h" #include "bbvs/dialogs.h" #include "bbvs/gamemodule.h" #include "bbvs/graphics.h" #include "bbvs/sound.h" #include "bbvs/spritemodule.h" #include "bbvs/minigames/minigame.h" #include "bbvs/minigames/bbairguitar.h" #include "bbvs/minigames/bbant.h" #include "bbvs/minigames/bbloogie.h" #include "bbvs/minigames/bbtennis.h" #include "bbvs/minigames/minigame.h" #include "audio/audiostream.h" #include "common/config-manager.h" #include "common/debug-channels.h" #include "common/error.h" #include "common/fs.h" #include "common/timer.h" #include "engines/util.h" #include "graphics/cursorman.h" #include "graphics/font.h" #include "graphics/fontman.h" #include "graphics/palette.h" #include "graphics/surface.h" namespace Bbvs { static const BBPoint kInventorySlotPositions[] = { { 66, 191}, { 94, 217}, {192, 217}, {159, 213}, {228, 49}, {137, 49}, {168, 165}, {101, 55}, {177, 46}, {165, 165}, {202, 74}, {141, 53}, {164, 164}, {165, 78}, {167, 71}, {142, 188}, {171, 100}, {250, 216}, {200, 72}, {200, 72}, {101, 82}, { 67, 93}, {133, 87}, {123, 220}, {199, 129}, {188, 192}, {102, 82}, {188, 192}, { 99, 170}, { 68, 126}, {159, 130}, {102, 116}, {207, 157}, {130, 141}, {236, 100}, {102, 197}, {141, 186}, {200, 102}, {221, 220}, {222, 188}, {135, 93}, {134, 145}, { 96, 224}, {128, 224}, {160, 224}, {192, 224}, {224, 224}, {240, 224}, {256, 224}, { 0, 0} }; static const BBRect kVerbRects[6] = { {-32, -2, 19, 27}, {-33, -33, 19, 27}, { 12, -2, 19, 27}, { 13, -33, 19, 27}, {-10, 8, 19, 27}, {-11, -49, 19, 27} }; static const int8 kWalkTurnTbl[] = { 7, 9, 4, 8, 6, 10, 5, 11 }; static const int8 kWalkAnimTbl[32] = { 3, 0, 0, 0, 2, 1, 1, 1, 15, 12, 14, 13, 0, 0, 0, 0, 7, 9, 4, 8, 6, 10, 5, 11, 3, 0, 2, 1, 15, 12, 14, 13 }; static const int8 kTurnInfo[8][8] = { { 0, 1, 1, 1, 1, -1, -1, -1}, {-1, 0, 1, 1, 1, 1, -1, -1}, {-1, -1, 0, 1, 1, 1, 1, -1}, {-1, -1, -1, 0, 1, 1, 1, 1}, { 1, -1, -1, -1, 0, 1, 1, 1}, { 1, 1, -1, -1, -1, 0, 1, 1}, { 1, 1, 1, -1, -1, -1, 0, 1}, { 1, 1, 1, 1, -1, -1, -1, 0} }; static const byte kTurnTbl[] = { 2, 6, 4, 0, 2, 6, 4, 0, 3, 1, 5, 7, 0, 0, 0, 0 }; static const int kAfterVideoSceneNum[] = { // 0, 43, 23, 12, 4, 44, 2, // 16, 4, 4, 4, 44, 12, 44 0, 43, 23, 12, 4, 44, 2, 16, 4, 4, 4, 44, 12, 32 }; const int kMainMenu = 44; bool WalkArea::contains(const Common::Point &pt) const { return Common::Rect(x, y, x + width, y + height).contains(pt); } BbvsEngine::BbvsEngine(OSystem *syst, const ADGameDescription *gd) : Engine(syst), _gameDescription(gd) { _random = new Common::RandomSource("bbvs"); Engine::syncSoundSettings(); } BbvsEngine::~BbvsEngine() { delete _random; } void BbvsEngine::newGame() { _currInventoryItem = -1; _newSceneNum = 32; } void BbvsEngine::continueGameFromQuickSave() { _bootSaveSlot = 0; } void BbvsEngine::setNewSceneNum(int newSceneNum) { _newSceneNum = newSceneNum; } Common::Error BbvsEngine::run() { _isSaveAllowed = false; _hasSnapshot = false; initGraphics(320, 240, false); _screen = new Screen(_system); _gameModule = new GameModule(); _spriteModule = new SpriteModule(); _sound = new SoundMan(); allocSnapshot(); _gameTicks = 0; _playVideoNumber = 0; _bootSaveSlot = -1; memset(_inventoryItemStatus, 0, sizeof(_inventoryItemStatus)); memset(_gameVars, 0, sizeof(_gameVars)); memset(_sceneVisited, 0, sizeof(_sceneVisited)); _mouseX = 160; _mouseY = 120; _mouseButtons = 0; _currVerbNum = kVerbLook; _currInventoryItem = -1; _currTalkObjectIndex = -1; _currSceneNum = 0; //_newSceneNum = 31; //_newSceneNum = 23; // Class room _newSceneNum = kMainMenu; // Main menu (TODO Buttons etc.) //_newSceneNum = 25;// Tank and crash //_newSceneNum = 7; //_newSceneNum = 12; if (ConfMan.hasKey("save_slot")) _bootSaveSlot = ConfMan.getInt("save_slot"); while (!shouldQuit()) { updateEvents(); if (_currSceneNum < kMainMenu || _newSceneNum > 0 || _bootSaveSlot >= 0) updateGame(); else if (_currSceneNum == kMainMenu) runMainMenu(); if (_playVideoNumber > 0) { playVideo(_playVideoNumber); _playVideoNumber = 0; } } writeContinueSavegame(); freeSnapshot(); delete _sound; delete _spriteModule; delete _gameModule; delete _screen; debug(0, "run() done"); return Common::kNoError; } bool BbvsEngine::hasFeature(EngineFeature f) const { return (f == kSupportsRTL) || (f == kSupportsLoadingDuringRuntime) || (f == kSupportsSavingDuringRuntime); } void BbvsEngine::updateEvents() { Common::Event event; while (_eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_KEYDOWN: _keyCode = event.kbd.keycode; break; case Common::EVENT_KEYUP: _keyCode = Common::KEYCODE_INVALID; break; case Common::EVENT_MOUSEMOVE: _mouseX = event.mouse.x; _mouseY = event.mouse.y; break; case Common::EVENT_LBUTTONDOWN: _mouseButtons |= kLeftButtonClicked; _mouseButtons |= kLeftButtonDown; break; case Common::EVENT_LBUTTONUP: _mouseButtons &= ~kLeftButtonDown; break; case Common::EVENT_RBUTTONDOWN: _mouseButtons |= kRightButtonClicked; _mouseButtons |= kRightButtonDown; break; case Common::EVENT_RBUTTONUP: _mouseButtons &= ~kRightButtonDown; break; case Common::EVENT_QUIT: quitGame(); break; default: break; } } } int BbvsEngine::getRandom(int max) { return max == 0 ? 0 : _random->getRandomNumber(max - 1); } void BbvsEngine::drawDebugInfo() { #if 0 Graphics::Surface *s = _screen->_surface; const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont); for (int i = 0; i < _walkAreasCount; ++i) { WalkArea *walkArea = &_walkAreas[i]; Common::Rect r(walkArea->x, walkArea->y, walkArea->x + walkArea->width, walkArea->y + walkArea->height); s->frameRect(r, 255); Common::String text = Common::String::format("%d", i); font->drawString(s, text, r.left + 1, r.top + 1, 100, 11); } #endif } void BbvsEngine::drawScreen() { drawDebugInfo(); _screen->copyToScreen(); } void BbvsEngine::updateGame() { int currTicks, inputTicks; if (_gameTicks > 0) { currTicks = _system->getMillis(); inputTicks = (currTicks - _gameTicks) / 17; _gameTicks = currTicks - (currTicks - _gameTicks) % 17; } else { inputTicks = 1; _gameTicks = _system->getMillis(); } if (inputTicks > 20) { inputTicks = 20; _gameTicks = _system->getMillis(); } if (inputTicks == 0) return; if (_mouseX >= 320 || _mouseY >= 240) { _mouseY = -1; _mouseX = -1; } bool done; do { done = !update(_mouseX, _mouseY, _mouseButtons, _keyCode); _mouseButtons &= ~kLeftButtonClicked; _mouseButtons &= ~kRightButtonClicked; _keyCode = Common::KEYCODE_INVALID; } while (--inputTicks && _playVideoNumber == 0 && _gameTicks > 0 && !done); if (!done && _playVideoNumber == 0 && _gameTicks > 0) { DrawList drawList; buildDrawList(drawList); _screen->drawDrawList(drawList, _spriteModule); drawScreen(); } _system->delayMillis(10); } bool BbvsEngine::evalCondition(Conditions &conditions) { bool result = true; for (int i = 0; i < 8 && result; ++i) { const Condition &condition = conditions.conditions[i]; switch (condition.cond) { case kCondSceneObjectVerb: result = _activeItemType == KITSceneObject && condition.value1 == _currVerbNum && condition.value2 == _activeItemIndex; break; case kCondBgObjectVerb: result = _activeItemType == kITBgObject && condition.value1 == _currVerbNum && condition.value2 == _activeItemIndex; break; case kCondSceneObjectInventory: result = _activeItemType == KITSceneObject && _currVerbNum == kVerbInvItem && condition.value1 == _currInventoryItem && condition.value2 == _activeItemIndex; break; case kCondBgObjectInventory: result = _activeItemType == kITBgObject && _currVerbNum == kVerbInvItem && condition.value1 == _currInventoryItem && condition.value2 == _activeItemIndex; break; case kCondHasInventoryItem: result = _inventoryItemStatus[condition.value1] != 0; break; case kCondHasNotInventoryItem: result = _inventoryItemStatus[condition.value1] == 0; break; case kCondIsGameVar: result = _gameVars[condition.value2] != 0; break; case kCondIsNotGameVar: result = _gameVars[condition.value2] == 0; break; case kCondIsPrevSceneNum: result = condition.value2 == _prevSceneNum; break; case kCondIsCurrTalkObject: result = condition.value2 == _currTalkObjectIndex; break; case kCondIsDialogItem: result = _activeItemType == kITDialog && condition.value1 == _activeItemIndex; break; case kCondIsCameraNum: result = condition.value1 == _currCameraNum; break; case kCondIsNotPrevSceneNum: result = condition.value2 != _prevSceneNum; break; case kCondIsButtheadAtBgObject: result = _buttheadObject && _gameModule->getBgObject(condition.value2)->rect.contains(_buttheadObject->x >> 16, _buttheadObject->y >> 16); break; case kCondIsNotSceneVisited: result = _sceneVisited[_currSceneNum] == 0; break; case kCondIsSceneVisited: result = _sceneVisited[_currSceneNum] != 0; break; case kCondUnused: case kCondDialogItem0: case kCondIsCameraNumTransition: result = false; break; } } return result; } bool BbvsEngine::evalCameraCondition(Conditions &conditions, int value) { bool result = true; for (int i = 0; i < 8 && result; ++i) { const Condition &condition = conditions.conditions[i]; switch (condition.cond) { case kCondHasInventoryItem: result = _inventoryItemStatus[condition.value1] != 0; break; case kCondHasNotInventoryItem: result = _inventoryItemStatus[condition.value1] == 0; break; case kCondIsGameVar: result = _gameVars[condition.value2] != 0; break; case kCondIsNotGameVar: result = _gameVars[condition.value2] == 0; break; case kCondIsPrevSceneNum: result = condition.value2 == _prevSceneNum; break; case kCondIsNotPrevSceneNum: result = condition.value2 != _prevSceneNum; break; case kCondIsNotSceneVisited: result = _sceneVisited[_currSceneNum] == 0; break; case kCondIsSceneVisited: result = _sceneVisited[_currSceneNum] != 0; break; case kCondIsCameraNumTransition: result = condition.value1 == _currCameraNum && condition.value2 == value; break; case kCondUnused: case kCondSceneObjectVerb: case kCondBgObjectVerb: case kCondSceneObjectInventory: case kCondBgObjectInventory: case kCondIsCurrTalkObject: case kCondIsDialogItem: case kCondIsCameraNum: case kCondDialogItem0: case kCondIsButtheadAtBgObject: result = false; break; default: break; } } return result; } int BbvsEngine::evalDialogCondition(Conditions &conditions) { int result = -1; bool success = false; for (int i = 0; i < 8; ++i) { const Condition &condition = conditions.conditions[i]; switch (condition.cond) { case kCondSceneObjectVerb: success = _activeItemType == KITSceneObject && condition.value1 == _currVerbNum && condition.value2 == _activeItemIndex; break; case kCondBgObjectVerb: success = _activeItemType == kITBgObject && condition.value1 == _currVerbNum && condition.value2 == _activeItemIndex; break; case kCondSceneObjectInventory: success = _activeItemType == KITSceneObject && _currVerbNum == kVerbInvItem && condition.value1 == _currInventoryItem && condition.value2 == _activeItemIndex; break; case kCondBgObjectInventory: success = _activeItemType == kITBgObject && _currVerbNum == kVerbInvItem && condition.value1 == _currInventoryItem && condition.value2 == _activeItemIndex; break; case kCondHasInventoryItem: success = _inventoryItemStatus[condition.value1] != 0; break; case kCondHasNotInventoryItem: success = _inventoryItemStatus[condition.value1] == 0; break; case kCondIsGameVar: success = _gameVars[condition.value2] != 0; break; case kCondIsNotGameVar: success = _gameVars[condition.value2] == 0; break; case kCondIsPrevSceneNum: success = condition.value2 == _prevSceneNum; break; case kCondIsCurrTalkObject: success = condition.value2 == _currTalkObjectIndex; break; case kCondIsDialogItem: result = condition.value1; break; case kCondIsCameraNum: success = condition.value1 == _currCameraNum; break; case kCondIsNotPrevSceneNum: success = condition.value2 != _prevSceneNum; break; case kCondIsButtheadAtBgObject: success = _buttheadObject && _gameModule->getBgObject(condition.value2)->rect.contains(_buttheadObject->x >> 16, _buttheadObject->y >> 16); break; case kCondIsNotSceneVisited: success = _sceneVisited[_currSceneNum] == 0; break; case kCondIsSceneVisited: success = _sceneVisited[_currSceneNum] != 0; break; case kCondDialogItem0: return 0; case kCondUnused: case kCondIsCameraNumTransition: success = false; break; } if (!success) return -1; } return result; } void BbvsEngine::evalActionResults(ActionResults &results) { for (int i = 0; i < 8; ++i) { const ActionResult &result = results.actionResults[i]; switch (result.kind) { case kActResAddInventoryItem: _inventoryItemStatus[result.value1] = 1; _currVerbNum = kVerbInvItem; _currInventoryItem = result.value1; break; case kActResRemoveInventoryItem: _inventoryItemStatus[result.value1] = 0; if (result.value1 == _currInventoryItem) _currInventoryItem = -1; if (_currVerbNum == kVerbInvItem) _currVerbNum = kVerbLook; break; case kActResSetGameVar: _gameVars[result.value2] = 1; break; case kActResUnsetGameVar: _gameVars[result.value2] = 0; break; case kActResStartDialog: _gameState = kGSDialog; break; case kActResChangeScene: _newSceneNum = result.value2; break; } } } void BbvsEngine::updateBackgroundSounds() { for (int i = 0; i < _gameModule->getSceneSoundsCount(); ++i) { SceneSound *sceneSound = _gameModule->getSceneSound(i); bool isActive = evalCondition(sceneSound->conditions); debug(5, "bgSound(%d) isActive: %d; soundNum: %d", i, isActive, sceneSound->soundNum); if (isActive && !_backgroundSoundsActive[i]) { playSound(sceneSound->soundNum, true); _backgroundSoundsActive[i] = 1; } else if (!isActive && _backgroundSoundsActive[i]) { stopSound(sceneSound->soundNum); _backgroundSoundsActive[i] = 0; } } } void BbvsEngine::loadScene(int sceneNum) { debug(0, "BbvsEngine::loadScene() sceneNum: %d", sceneNum); Common::String sprFilename = Common::String::format("vnm/vspr%04d.vnm", sceneNum); Common::String gamFilename = Common::String::format("vnm/game%04d.vnm", sceneNum); _screen->clear(); _spriteModule->load(sprFilename.c_str()); _gameModule->load(gamFilename.c_str()); Palette palette = _spriteModule->getPalette(); _screen->setPalette(palette); // Preload sounds for (uint i = 0; i < _gameModule->getPreloadSoundsCount(); ++i) { Common::String filename = Common::String::format("snd/snd%05d.aif", _gameModule->getPreloadSound(i)); _sound->loadSound(filename); } if (sceneNum >= kMainMenu) { DrawList drawList; drawList.add(_gameModule->getBgSpriteIndex(0), 0, 0, 0); _screen->drawDrawList(drawList, _spriteModule); drawScreen(); } } void BbvsEngine::initScene(bool sounds) { stopSpeech(); stopSounds(); _sound->unloadSounds(); _gameState = kGSScene; _prevSceneNum = _currSceneNum; _sceneVisited[_currSceneNum] = 1; _mouseCursorSpriteIndex = 0; _verbPos.x = -1; _verbPos.y = -1; _activeItemType = kITEmpty; _activeItemIndex = 0; _cameraPos.x = 0; _cameraPos.y = 0; _newCameraPos.x = 0; _newCameraPos.y = 0; _inventoryButtonIndex = -1; _currTalkObjectIndex = -1; _currCameraNum = 0; _walkMousePos.x = -1; _walkMousePos.y = -1; _currAction = 0; _currActionCommandIndex = -1; _currActionCommandTimeStamp = 0; _dialogSlotCount = 0; _buttheadObject = 0; _beavisObject = 0; memset(_backgroundSoundsActive, 0, sizeof(_backgroundSoundsActive)); memset(_sceneObjects, 0, sizeof(_sceneObjects)); for (int i = 0; i < kSceneObjectsCount; ++i) { _sceneObjects[i].walkDestPt.x = -1; _sceneObjects[i].walkDestPt.y = -1; } memset(_dialogItemStatus, 0, sizeof(_dialogItemStatus)); _sceneObjectActions.clear(); loadScene(_newSceneNum); _currSceneNum = _newSceneNum; _newSceneNum = 0; for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) _sceneObjects[i].sceneObjectDef = _gameModule->getSceneObjectDef(i); for (int i = 0; i < _gameModule->getSceneObjectInitsCount(); ++i) { SceneObjectInit *soInit = _gameModule->getSceneObjectInit(i); if (evalCondition(soInit->conditions)) { SceneObject *sceneObject = &_sceneObjects[soInit->sceneObjectIndex]; sceneObject->anim = _gameModule->getAnimation(soInit->animIndex); sceneObject->animIndex = soInit->animIndex; sceneObject->frameIndex = sceneObject->anim->frameCount - 1; sceneObject->frameTicks = 1; sceneObject->x = soInit->x << 16; sceneObject->y = soInit->y << 16; } } if (_gameModule->getButtheadObjectIndex() >= 0) { _buttheadObject = &_sceneObjects[_gameModule->getButtheadObjectIndex()]; // Search for the Beavis object for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) if (!strcmp(_sceneObjects[i].sceneObjectDef->name, "Beavis")) { _beavisObject = &_sceneObjects[i]; break; } } updateSceneObjectsTurnValue(); updateWalkableRects(); _currCameraNum = 0; if (_buttheadObject) { int minDistance = 0xFFFFFF; for (int cameraNum = 0; cameraNum < 4; ++cameraNum) { CameraInit *cameraInit = _gameModule->getCameraInit(cameraNum); int curDistance = ABS(cameraInit->cameraPos.x - (int)(_buttheadObject->x >> 16) + 160); if (curDistance < minDistance) { minDistance = curDistance; _currCameraNum = cameraNum; } } } _cameraPos = _gameModule->getCameraInit(_currCameraNum)->cameraPos; _newCameraPos = _cameraPos; _walkAreaActions.clear(); for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); for (int j = 0; j < 8; ++j) if (action->conditions.conditions[j].cond == kCondIsButtheadAtBgObject) _walkAreaActions.push_back(action); } _mouseCursorSpriteIndex = 0; _activeItemIndex = 0; _activeItemType = kITEmpty; for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); if (evalCondition(action->conditions)) { _gameState = kGSWait; _currAction = action; for (uint j = 0; j < action->actionCommands.size(); ++j) { ActionCommand *actionCommand = &action->actionCommands[j]; if (actionCommand->cmd == kActionCmdSetCameraPos) { _currCameraNum = actionCommand->param; _cameraPos = _gameModule->getCameraInit(_currCameraNum)->cameraPos; _newCameraPos = _cameraPos; break; } } break; } } if (sounds) updateBackgroundSounds(); } bool BbvsEngine::changeScene() { writeContinueSavegame(); if (_newSceneNum >= 27 && _newSceneNum <= 30) { // Run minigames stopSpeech(); stopSounds(); _sceneVisited[_currSceneNum] = 1; if (runMinigame(_newSceneNum - 27)) { SWAP(_currSceneNum, _newSceneNum); } } else if (_newSceneNum >= 31 && _newSceneNum <= 43) { // Play video stopSpeech(); stopSounds(); _sceneVisited[_currSceneNum] = 1; _playVideoNumber = _newSceneNum - 30; _currSceneNum = _newSceneNum; _newSceneNum = kAfterVideoSceneNum[_playVideoNumber]; } else if (_newSceneNum >= 100 && _currSceneNum == 45) { // Play secret video stopSounds(); _playVideoNumber = _newSceneNum; _currSceneNum = 49; _newSceneNum = 45; } else { // Normal scene initScene(true); } return true; } bool BbvsEngine::update(int mouseX, int mouseY, uint mouseButtons, Common::KeyCode keyCode) { if (_bootSaveSlot >= 0) { loadGameState(_bootSaveSlot); _gameTicks = 0; _bootSaveSlot = -1; return false; } if (_newSceneNum != 0) { _gameTicks = 0; return changeScene(); } _mousePos.x = mouseX + _cameraPos.x; _mousePos.y = mouseY + _cameraPos.y; switch (_gameState) { case kGSScene: _isSaveAllowed = true; saveSnapshot(); if (mouseButtons & kRightButtonDown) { _verbPos = _mousePos; if (_mousePos.x - _cameraPos.x < 33) _verbPos.x = _cameraPos.x + 33; if (_verbPos.x - _cameraPos.x > 287) _verbPos.x = _cameraPos.x + 287; if (_verbPos.y - _cameraPos.y < 51) _verbPos.y = _cameraPos.y + 51; if (_verbPos.y - _cameraPos.y > 208) _verbPos.y = _cameraPos.y + 208; _gameState = kGSVerbs; } else { switch (keyCode) { case Common::KEYCODE_SPACE: case Common::KEYCODE_i: _inventoryButtonIndex = -1; _gameState = kGSInventory; return true; case Common::KEYCODE_l: _currVerbNum = kVerbLook; break; case Common::KEYCODE_t: _currVerbNum = kVerbTalk; break; case Common::KEYCODE_u: _currVerbNum = kVerbUse; break; case Common::KEYCODE_w: _currVerbNum = kVerbWalk; break; default: break; } updateScene(mouseButtons & kLeftButtonClicked); updateCommon(); } break; case kGSInventory: _isSaveAllowed = true; saveSnapshot(); if (mouseButtons & kRightButtonClicked) _currVerbNum = kVerbUse; switch (keyCode) { case Common::KEYCODE_SPACE: case Common::KEYCODE_i: _gameState = kGSScene; stopSpeech(); return true; case Common::KEYCODE_l: _currVerbNum = kVerbLook; break; case Common::KEYCODE_u: _currVerbNum = kVerbUse; break; default: break; } updateInventory(mouseButtons & kLeftButtonClicked); break; case kGSVerbs: _isSaveAllowed = false; updateVerbs(); if (!(mouseButtons & kRightButtonDown)) { if (_currVerbNum == kVerbShowInv) { _inventoryButtonIndex = -1; _gameState = kGSInventory; } else { _gameState = kGSScene; } } break; case kGSWait: case kGSWaitDialog: _isSaveAllowed = false; _activeItemType = kITEmpty; _activeItemIndex = 0; _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(9); if (keyCode == Common::KEYCODE_ESCAPE) skipCurrAction(); else updateCommon(); break; case kGSDialog: _isSaveAllowed = true; saveSnapshot(); updateDialog(mouseButtons & kLeftButtonClicked); updateCommon(); break; } return true; } void BbvsEngine::buildDrawList(DrawList &drawList) { if (_gameState == kGSInventory) { // Inventory background drawList.add(_gameModule->getGuiSpriteIndex(15), 0, 0, 0); // Inventory button if (_inventoryButtonIndex == 0) drawList.add(_gameModule->getGuiSpriteIndex(18 + 0), 97, 13, 1); else if (_inventoryButtonIndex == 1) drawList.add(_gameModule->getGuiSpriteIndex(18 + 1), 135, 15, 1); else if (_inventoryButtonIndex == 2) drawList.add(_gameModule->getGuiSpriteIndex(18 + 2), 202, 13, 1); // Inventory items int currItem = -1; if (_currVerbNum == kVerbInvItem) currItem = _currInventoryItem; for (int i = 0; i < 50; ++i) if (_inventoryItemStatus[i] && currItem != i) drawList.add(_gameModule->getInventoryItemSpriteIndex(i * 2), kInventorySlotPositions[i].x, kInventorySlotPositions[i].y, 1); } else { // Scene objects for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) { SceneObject *sceneObject = &_sceneObjects[i]; Animation *anim = sceneObject->anim; if (anim) { drawList.add(anim->frameSpriteIndices[sceneObject->frameIndex], (sceneObject->x >> 16) - _cameraPos.x, (sceneObject->y >> 16) - _cameraPos.y, sceneObject->y >> 16); } } // Background objects for (int i = 0; i < _gameModule->getBgSpritesCount(); ++i) drawList.add(_gameModule->getBgSpriteIndex(i), -_cameraPos.x, -_cameraPos.y, _gameModule->getBgSpritePriority(i)); if (_gameState == kGSVerbs) { // Verbs icon background for (int i = 0; i < 6; ++i) { if (i != 4) { int index = (i == _activeItemIndex) ? 17 : 16; drawList.add(_gameModule->getGuiSpriteIndex(index), _verbPos.x + kVerbRects[i].x - _cameraPos.x, _verbPos.y + kVerbRects[i].y - _cameraPos.y, 499); } } // Verbs background drawList.add(_gameModule->getGuiSpriteIndex(13), _verbPos.x - _cameraPos.x, _verbPos.y - _cameraPos.y, 500); // Selected inventory item if (_currInventoryItem >= 0) { drawList.add(_gameModule->getInventoryItemSpriteIndex(2 * _currInventoryItem), _verbPos.x - _cameraPos.x, _verbPos.y - _cameraPos.y + 27, 500); } } if (_gameState == kGSDialog) { // Dialog background drawList.add(_gameModule->getGuiSpriteIndex(14), 0, 0, 500); // Dialog icons int iconX = 16; for (int i = 0; i < 50; ++i) if (_dialogItemStatus[i]) { drawList.add(_gameModule->getDialogItemSpriteIndex(i), iconX, 36, 501); iconX += 32; } } } // Mouse cursor if (_mouseCursorSpriteIndex > 0 && _mousePos.x >= 0) drawList.add(_mouseCursorSpriteIndex, _mousePos.x - _cameraPos.x, _mousePos.y - _cameraPos.y, 1000); } void BbvsEngine::updateVerbs() { _activeItemIndex = 99; if (_mousePos.x < 0) { _mouseCursorSpriteIndex = 0; return; } for (int i = 0; i < 6; ++i) { const BBRect &verbRect = kVerbRects[i]; const int16 x = _verbPos.x + verbRect.x; const int16 y = _verbPos.y + verbRect.y; if (Common::Rect(x, y, x + verbRect.width, y + verbRect.height).contains(_mousePos)) { if (i != kVerbInvItem || _currInventoryItem >= 0) { _currVerbNum = i; _activeItemIndex = i; } break; } } switch (_currVerbNum) { case kVerbLook: case kVerbUse: case kVerbTalk: case kVerbWalk: _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(2 * _currVerbNum); break; case kVerbInvItem: _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(2 * _currInventoryItem); break; case kVerbShowInv: _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(8); break; } } void BbvsEngine::updateDialog(bool clicked) { if (_mousePos.x < 0) { _mouseCursorSpriteIndex = 0; _activeItemType = 0; return; } if (_mousePos.y > 32) { _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(10); _activeItemIndex = 0; _activeItemType = kITEmpty; if (clicked) _gameState = kGSScene; return; } int slotX = (_mousePos.x - _cameraPos.x) / 32; if (slotX >= _dialogSlotCount) { _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(4); _activeItemType = kITEmpty; _activeItemIndex = 0; return; } _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(5); _activeItemType = kITDialog; // Find the selected dialog item index for (int i = 0; i < 50 && slotX >= 0; ++i) { if (_dialogItemStatus[i]) { --slotX; _activeItemIndex = i; } } // Select the dialog item action if it was clicked if (clicked) { for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); if (evalCondition(action->conditions)) { _mouseCursorSpriteIndex = 0; _gameState = kGSWaitDialog; _currAction = action; break; } } } } void BbvsEngine::updateInventory(bool clicked) { Common::Rect kInvButtonRects[3] = { Common::Rect(97, 13, 97 + 20, 13 + 26), Common::Rect(135, 15, 135 + 46, 15 + 25), Common::Rect(202, 13, 202 + 20, 13 + 26)}; if (_mousePos.x < 0) { _mouseCursorSpriteIndex = 0; _activeItemType = 0; return; } if (_currVerbNum != kVerbLook && _currVerbNum != kVerbUse && _currVerbNum != kVerbInvItem) _currVerbNum = kVerbUse; const int16 mx = _mousePos.x - _cameraPos.x; const int16 my = _mousePos.y - _cameraPos.y; // Check inventory exit left/right edge of screen if (mx < 40 || mx > 280) { _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(10); _activeItemIndex = 0; _activeItemType = kITEmpty; if (clicked) { _gameState = kGSScene; stopSpeech(); } return; } // Check hovered/clicked inventory button _inventoryButtonIndex = -1; if (kInvButtonRects[0].contains(mx, my)) { _inventoryButtonIndex = 0; if (clicked) _currVerbNum = kVerbLook; } else if (kInvButtonRects[2].contains(mx, my)) { _inventoryButtonIndex = 2; if (clicked) _currVerbNum = kVerbUse; } else if (kInvButtonRects[1].contains(mx, my)) { _inventoryButtonIndex = 1; _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(10); _activeItemIndex = 0; _activeItemType = kITEmpty; if (clicked) { _gameState = kGSScene; stopSpeech(); } return; } // Find hovered/clicked inventory item int currItem = -1; if (_currVerbNum == kVerbInvItem) currItem = _currInventoryItem; _activeItemType = kITEmpty; for (int i = 0; i < 50; ++i) { if (_inventoryItemStatus[i] && i != currItem) { InventoryItemInfo *info = _gameModule->getInventoryItemInfo(i); const int16 sx = kInventorySlotPositions[i].x + info->xOffs; const int16 sy = kInventorySlotPositions[i].y + info->yOffs; if (Common::Rect(sx, sy, sx + info->width, sy + info->height).contains(mx, my)) { _activeItemType = kITInvItem; _activeItemIndex = i; break; } } } // Update mouse cursor and select inventory item if clicked if (_activeItemType == kITInvItem) { if (clicked) { if (_currVerbNum == kVerbLook) { stopSpeech(); playSpeech(_activeItemIndex + 10000); } else if (_currVerbNum == kVerbUse) { _currInventoryItem = _activeItemIndex; _currVerbNum = kVerbInvItem; _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(2 * _activeItemIndex); } else if (_currVerbNum == kVerbInvItem) { if ((_currInventoryItem == 22 && _activeItemIndex == 39) || (_currInventoryItem == 39 && _activeItemIndex == 22)) { _inventoryItemStatus[22] = 0; _inventoryItemStatus[39] = 0; _inventoryItemStatus[40] = 1; _currVerbNum = kVerbInvItem; _currInventoryItem = 40; _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(40); } if ((_currInventoryItem == 25 && _activeItemIndex == 26) || (_currInventoryItem == 26 && _activeItemIndex == 25)) { _inventoryItemStatus[26] = 0; _inventoryItemStatus[25] = 0; _inventoryItemStatus[27] = 1; _currVerbNum = kVerbInvItem; _currInventoryItem = 27; _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(27); } } } else { if (_currVerbNum == kVerbLook) _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(1); else if (_currVerbNum == kVerbUse) _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(3); else if (_currVerbNum == kVerbInvItem) _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(2 * _currInventoryItem + 1); } } else { if (_currVerbNum >= kVerbInvItem) _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(2 * _currInventoryItem); else _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(2 * _currVerbNum); } } void BbvsEngine::updateScene(bool clicked) { if (_mousePos.x < 0) { _mouseCursorSpriteIndex = 0; _activeItemType = kITNone; return; } int lastPriority = 0; _activeItemType = kITEmpty; for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) { SceneObject *sceneObject = &_sceneObjects[i]; if (sceneObject->anim) { Common::Rect frameRect = sceneObject->anim->frameRects1[sceneObject->frameIndex]; const int objY = sceneObject->y >> 16; frameRect.translate(sceneObject->x >> 16, objY); if (lastPriority <= objY && frameRect.width() > 0 && frameRect.contains(_mousePos)) { lastPriority = objY; _activeItemIndex = i; _activeItemType = KITSceneObject; } } } for (int i = 0; i < _gameModule->getBgObjectsCount(); ++i) { BgObject *bgObject = _gameModule->getBgObject(i); if (lastPriority <= bgObject->rect.bottom && bgObject->rect.contains(_mousePos)) { lastPriority = bgObject->rect.bottom; _activeItemIndex = i; _activeItemType = kITBgObject; } } if (_currVerbNum >= kVerbInvItem) _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(2 * _currInventoryItem); else _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(2 * _currVerbNum); bool checkMore = true; if (_activeItemType == KITSceneObject || _activeItemType == kITBgObject) { for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); if (evalCondition(action->conditions)) { checkMore = false; if (clicked) { _mouseCursorSpriteIndex = 0; _gameState = kGSWait; _currAction = action; if (_currVerbNum == kVerbTalk) _currTalkObjectIndex = _activeItemIndex; if (_buttheadObject) { _buttheadObject->walkDestPt.x = -1; _buttheadObject->walkCount = 0; } } else { if (_currVerbNum >= kVerbInvItem) _mouseCursorSpriteIndex = _gameModule->getInventoryItemSpriteIndex(2 * _currInventoryItem + 1); else _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(2 * _currVerbNum + 1); } break; } } } // Test scroll arrow left if (checkMore && _buttheadObject && _buttheadObject->anim && _mousePos.x - _cameraPos.x < 16 && _currCameraNum > 0) { --_currCameraNum; for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); if (evalCameraCondition(action->conditions, _currCameraNum + 1)) { checkMore = false; if (clicked) { _mouseCursorSpriteIndex = 0; _gameState = kGSWait; _currAction = action; _buttheadObject->walkDestPt.x = -1; _buttheadObject->walkCount = 0; } else { _activeItemType = kITScroll; _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(12); } break; } } ++_currCameraNum; } // Test scroll arrow right if (checkMore && _buttheadObject && _buttheadObject->anim && _mousePos.x - _cameraPos.x >= 304 && _currCameraNum < 4) { ++_currCameraNum; for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); if (evalCameraCondition(action->conditions, _currCameraNum - 1)) { checkMore = false; if (clicked) { _mouseCursorSpriteIndex = 0; _gameState = kGSWait; _currAction = action; _buttheadObject->walkDestPt.x = -1; _buttheadObject->walkCount = 0; } else { _activeItemType = kITScroll; _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(11); } break; } } --_currCameraNum; } if (checkMore && _buttheadObject && _buttheadObject->anim) { _walkMousePos = _mousePos; while (1) { int foundIndex = -1; for (int i = 0; i < _walkableRectsCount; ++i) if (_walkableRects[i].contains(_walkMousePos)) { foundIndex = i; break; } if (foundIndex >= 0) { if (_walkMousePos.y != _mousePos.y) _walkMousePos.y = _walkableRects[foundIndex].top; break; } else { _walkMousePos.y += 4; if (_walkMousePos.y >= 240) break; } } if (_beavisObject->anim) { Common::Rect frameRect = _beavisObject->anim->frameRects2[_beavisObject->frameIndex]; frameRect.translate(_beavisObject->x >> 16, (_beavisObject->y >> 16) + 1); if (!frameRect.isEmpty() && frameRect.contains(_walkMousePos)) _walkMousePos.y = frameRect.bottom; } if (_walkMousePos.y < 240 && canButtheadWalkToDest(_walkMousePos)) { if (clicked) { _buttheadObject->walkDestPt = _walkMousePos; _buttheadObject->walkCount = 0; } for (int i = 0; i < _gameModule->getSceneExitsCount(); ++i) { SceneExit *sceneExit = _gameModule->getSceneExit(i); if (sceneExit->rect.contains(_walkMousePos.x, _walkMousePos.y)) { _activeItemIndex = i; _activeItemType = kITSceneExit; _mouseCursorSpriteIndex = _gameModule->getGuiSpriteIndex(10); } } } else { _walkMousePos.x = -1; _walkMousePos.y = -1; } } } bool BbvsEngine::performActionCommand(ActionCommand *actionCommand) { debug(5, "BbvsEngine::performActionCommand() cmd: %d", actionCommand->cmd); switch (actionCommand->cmd) { case kActionCmdStop: stopSpeech(); return false; case kActionCmdWalkObject: { SceneObject *sceneObject = &_sceneObjects[actionCommand->sceneObjectIndex]; debug(5, "[%s] walks from (%d, %d) to (%d, %d)", sceneObject->sceneObjectDef->name, sceneObject->x >> 16, sceneObject->y >> 16, actionCommand->walkDest.x, actionCommand->walkDest.y); walkObject(sceneObject, actionCommand->walkDest, actionCommand->param); } return true; case kActionCmdMoveObject: { SceneObject *sceneObject = &_sceneObjects[actionCommand->sceneObjectIndex]; sceneObject->x = actionCommand->walkDest.x << 16; sceneObject->y = actionCommand->walkDest.y << 16; sceneObject->xIncr = 0; sceneObject->yIncr = 0; sceneObject->walkCount = 0; } return true; case kActionCmdAnimObject: { SceneObject *sceneObject = &_sceneObjects[actionCommand->sceneObjectIndex]; if (actionCommand->param == 0) { sceneObject->anim = 0; sceneObject->animIndex = 0; sceneObject->frameTicks = 0; sceneObject->frameIndex = 0; } else if (actionCommand->timeStamp != 0 || sceneObject->anim != _gameModule->getAnimation(actionCommand->param)) { sceneObject->animIndex = actionCommand->param; sceneObject->anim = _gameModule->getAnimation(actionCommand->param); sceneObject->frameIndex = sceneObject->anim->frameCount - 1; sceneObject->frameTicks = 1; } } return true; case kActionCmdSetCameraPos: _currCameraNum = actionCommand->param; _newCameraPos = _gameModule->getCameraInit(_currCameraNum)->cameraPos; updateBackgroundSounds(); return true; case kActionCmdPlaySpeech: playSpeech(actionCommand->param); return true; case kActionCmdPlaySound: playSound(actionCommand->param); return true; case kActionCmdStartBackgroundSound: { const uint soundIndex = _gameModule->getSceneSoundIndex(actionCommand->param); if (!_backgroundSoundsActive[soundIndex]) { _backgroundSoundsActive[soundIndex] = 1; playSound(actionCommand->param, true); } } return true; case kActionCmdStopBackgroundSound: { const uint soundIndex = _gameModule->getSceneSoundIndex(actionCommand->param); _backgroundSoundsActive[soundIndex] = 0; stopSound(actionCommand->param); } return true; default: return true; } } bool BbvsEngine::processCurrAction() { bool actionsFinished = false; if (_sceneObjectActions.size() == 0) { for (uint i = 0; i < _currAction->actionCommands.size(); ++i) { ActionCommand *actionCommand = &_currAction->actionCommands[i]; if (actionCommand->timeStamp != 0) break; if (actionCommand->cmd == kActionCmdMoveObject || actionCommand->cmd == kActionCmdAnimObject) { SceneObjectAction *sceneObjectAction = 0; // See if there's already an entry for the SceneObject for (uint j = 0; j < _sceneObjectActions.size(); ++j) if (_sceneObjectActions[j].sceneObjectIndex == actionCommand->sceneObjectIndex) { sceneObjectAction = &_sceneObjectActions[j]; break; } // If not, add one if (!sceneObjectAction) { SceneObjectAction newSceneObjectAction; newSceneObjectAction.sceneObjectIndex = actionCommand->sceneObjectIndex; _sceneObjectActions.push_back(newSceneObjectAction); sceneObjectAction = &_sceneObjectActions.back(); } if (actionCommand->cmd == kActionCmdMoveObject) { sceneObjectAction->walkDest = actionCommand->walkDest; } else { sceneObjectAction->animationIndex = actionCommand->param; } } if (actionCommand->cmd == kActionCmdSetCameraPos) { _currCameraNum = actionCommand->param; _newCameraPos = _gameModule->getCameraInit(actionCommand->param)->cameraPos; } } // Delete entries for SceneObjects without anim for (uint i = 0; i < _sceneObjectActions.size();) { if (!_sceneObjects[_sceneObjectActions[i].sceneObjectIndex].anim) _sceneObjectActions.remove_at(i); else ++i; } // Prepare affected scene objects for (uint i = 0; i < _sceneObjectActions.size(); ++i) { _sceneObjects[_sceneObjectActions[i].sceneObjectIndex].walkCount = 0; _sceneObjects[_sceneObjectActions[i].sceneObjectIndex].turnCount = 0; } } actionsFinished = true; // Update SceneObject actions (walk and turn) for (uint i = 0; i < _sceneObjectActions.size(); ++i) { SceneObjectAction *soAction = &_sceneObjectActions[i]; SceneObject *sceneObject = &_sceneObjects[soAction->sceneObjectIndex]; if (sceneObject->walkDestPt.x != -1) { debug(5, "waiting for walk to finish"); actionsFinished = false; } else if ((sceneObject->x >> 16) != soAction->walkDest.x || (sceneObject->y >> 16) != soAction->walkDest.y) { debug(5, "starting to walk"); sceneObject->walkDestPt = soAction->walkDest; actionsFinished = false; } else if (sceneObject->walkCount == 0 && sceneObject->turnCount == 0) { debug(5, "not walking"); for (int turnCount = 0; turnCount < 8; ++turnCount) if (sceneObject->sceneObjectDef->animIndices[kWalkTurnTbl[turnCount]] == soAction->animationIndex && sceneObject->turnValue != turnCount) { sceneObject->turnCount = turnCount | 0x80; break; } } if (sceneObject->turnCount) actionsFinished = false; } if (actionsFinished) _sceneObjectActions.clear(); return actionsFinished; } void BbvsEngine::skipCurrAction() { ActionCommands &actionCommands = _currAction->actionCommands; while (_currAction && _newSceneNum == 0) updateCommon(); for (uint i = 0; i < actionCommands.size(); ++i) if (actionCommands[i].cmd == kActionCmdPlaySound) stopSound(actionCommands[i].param); _system->delayMillis(250); _gameTicks = 0; } void BbvsEngine::updateCommon() { if (_currAction) { bool doActionCommands = true; if (_currActionCommandTimeStamp == 0) { doActionCommands = processCurrAction(); _currActionCommandIndex = 0; } if (doActionCommands) { ActionCommand *actionCommand = &_currAction->actionCommands[_currActionCommandIndex]; while (actionCommand->timeStamp == _currActionCommandTimeStamp && _currActionCommandIndex < (int)_currAction->actionCommands.size()) { if (!performActionCommand(actionCommand)) { _gameState = kGSScene; evalActionResults(_currAction->results); if (_gameState == kGSDialog) updateDialogConditions(); _currAction = 0; _currActionCommandTimeStamp = 0; _currActionCommandIndex = -1; updateSceneObjectsTurnValue(); updateWalkableRects(); break; } actionCommand = &_currAction->actionCommands[++_currActionCommandIndex]; } if (_currAction) { ++_currActionCommandTimeStamp; } else { _activeItemIndex = 0; _mouseCursorSpriteIndex = 0; _activeItemType = kITEmpty; for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); if (evalCondition(action->conditions)) { _gameState = kGSWait; _currAction = action; } } } } } for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) { SceneObject *sceneObject = &_sceneObjects[i]; if (sceneObject->walkDestPt.x != -1) { if (sceneObject->walkCount == 0) { debug(5, "[%s] needs to walk", sceneObject->sceneObjectDef->name); startWalkObject(sceneObject); if (sceneObject->walkCount == 0) { debug(5, "no walk possible"); sceneObject->walkDestPt.x = -1; sceneObject->walkDestPt.y = -1; sceneObject->xIncr = 0; sceneObject->yIncr = 0; } } updateWalkObject(sceneObject); } if (sceneObject->walkCount > 0 && sceneObject->turnCount == 0) { debug(5, "walk step, xIncr: %d, yIncr: %d", sceneObject->xIncr, sceneObject->yIncr); sceneObject->x += sceneObject->xIncr; sceneObject->y += sceneObject->yIncr; --sceneObject->walkCount; } else if (sceneObject->turnCount != 0) { debug(5, "need turn, turnCount: %d", sceneObject->turnCount); turnObject(sceneObject); } if (sceneObject == _buttheadObject && sceneObject->walkDestPt.x != -1) { for (uint j = 0; j < _walkAreaActions.size(); ++j) { if (_walkAreaActions[j] != _currAction && evalCondition(_walkAreaActions[j]->conditions)) { _sceneObjectActions.clear(); _gameState = kGSWait; _currAction = _walkAreaActions[j]; _currActionCommandTimeStamp = 0; _currActionCommandIndex = -1; for (int k = 0; k < _gameModule->getSceneObjectDefsCount(); ++k) { SceneObject *sceneObject2 = &_sceneObjects[k]; sceneObject2->walkDestPt.x = -1; sceneObject2->walkDestPt.y = -1; sceneObject2->walkCount = 0; } break; } } } if (sceneObject->anim && --sceneObject->frameTicks == 0) { if (++sceneObject->frameIndex >= sceneObject->anim->frameCount) sceneObject->frameIndex = 0; sceneObject->frameTicks = sceneObject->anim->frameTicks[sceneObject->frameIndex]; } } if (!_currAction && _buttheadObject) { int16 buttheadX = _buttheadObject->x >> 16; int16 buttheadY = _buttheadObject->y >> 16; CameraInit *cameraInit = _gameModule->getCameraInit(_currCameraNum); for (int i = 0; i < 8; ++i) { if (cameraInit->rects[i].contains(buttheadX, buttheadY)) { int newCameraNum = cameraInit->cameraLinks[i]; if (_currCameraNum != newCameraNum) { int prevCameraNum = _currCameraNum; _currCameraNum = newCameraNum; _newCameraPos = _gameModule->getCameraInit(newCameraNum)->cameraPos; for (int j = 0; j < _gameModule->getActionsCount(); ++j) { Action *action = _gameModule->getAction(j); if (evalCameraCondition(action->conditions, prevCameraNum)) { _gameState = kGSWait; _currAction = action; _mouseCursorSpriteIndex = 0; _buttheadObject->walkDestPt.x = -1; _buttheadObject->walkCount = 0; break; } } updateBackgroundSounds(); } } } } if (_cameraPos.x < _newCameraPos.x) ++_cameraPos.x; if (_cameraPos.x > _newCameraPos.x) --_cameraPos.x; if (_cameraPos.y < _newCameraPos.y) ++_cameraPos.y; if (_cameraPos.y > _newCameraPos.y) --_cameraPos.y; // Check if Butthead is inside a scene exit if (_newSceneNum == 0 && !_currAction && _buttheadObject) { int16 buttheadX = _buttheadObject->x >> 16; int16 buttheadY = _buttheadObject->y >> 16; for (int i = 0; i < _gameModule->getSceneExitsCount(); ++i) { SceneExit *sceneExit = _gameModule->getSceneExit(i); if (sceneExit->rect.contains(buttheadX, buttheadY)) { _newSceneNum = sceneExit->newModuleNum; break; } } } } void BbvsEngine::startWalkObject(SceneObject *sceneObject) { const int kMaxDistance = 0xFFFFFF; if (_buttheadObject != sceneObject && _beavisObject != sceneObject) return; initWalkAreas(sceneObject); _sourceWalkAreaPt.x = sceneObject->x >> 16; _sourceWalkAreaPt.y = sceneObject->y >> 16; _sourceWalkArea = getWalkAreaAtPos(_sourceWalkAreaPt); if (!_sourceWalkArea) return; _destWalkAreaPt = sceneObject->walkDestPt; _destWalkArea = getWalkAreaAtPos(_destWalkAreaPt); if (!_destWalkArea) return; if (_sourceWalkArea != _destWalkArea) { _currWalkDistance = kMaxDistance; walkFindPath(_sourceWalkArea, 0); _destWalkAreaPt = _currWalkDistance == kMaxDistance ? _sourceWalkAreaPt : _finalWalkPt; } walkObject(sceneObject, _destWalkAreaPt, sceneObject->sceneObjectDef->walkSpeed); } void BbvsEngine::updateWalkObject(SceneObject *sceneObject) { int animIndex; if (sceneObject->walkCount > 0 && (sceneObject->xIncr != 0 || sceneObject->yIncr != 0)) { if (ABS(sceneObject->xIncr) <= ABS(sceneObject->yIncr)) sceneObject->turnValue = sceneObject->yIncr >= 0 ? 0 : 4; else sceneObject->turnValue = sceneObject->xIncr >= 0 ? 6 : 2; animIndex = sceneObject->sceneObjectDef->animIndices[kWalkAnimTbl[sceneObject->turnValue]]; sceneObject->turnCount = 0; sceneObject->turnTicks = 0; } else { animIndex = sceneObject->sceneObjectDef->animIndices[kWalkTurnTbl[sceneObject->turnValue]]; } Animation *anim = 0; if (animIndex > 0) anim = _gameModule->getAnimation(animIndex); if (sceneObject->anim != anim) { if (anim) { sceneObject->anim = anim; sceneObject->animIndex = animIndex; sceneObject->frameTicks = 1; sceneObject->frameIndex = anim->frameCount - 1; } else { sceneObject->anim = 0; sceneObject->animIndex = 0; sceneObject->frameTicks = 0; sceneObject->frameIndex = 0; } } } void BbvsEngine::walkObject(SceneObject *sceneObject, const Common::Point &destPt, int walkSpeed) { int deltaX = destPt.x - (sceneObject->x >> 16); int deltaY = destPt.y - (sceneObject->y >> 16); float distance = sqrt(deltaX * deltaX + deltaY * deltaY); // NOTE The original doesn't have this check but without it the whole pathfinding breaks if (distance > 0.0) { sceneObject->walkCount = distance / ((((float)ABS(deltaX) / distance) + 1.0) * ((float)walkSpeed / 120)); sceneObject->xIncr = ((float)deltaX / sceneObject->walkCount) * 65536.0; sceneObject->yIncr = ((float)deltaY / sceneObject->walkCount) * 65536.0; sceneObject->x = (sceneObject->x & 0xFFFF0000) | 0x8000; sceneObject->y = (sceneObject->y & 0xFFFF0000) | 0x8000; } else sceneObject->walkCount = 0; } void BbvsEngine::turnObject(SceneObject *sceneObject) { if (sceneObject->turnTicks > 0) { --sceneObject->turnTicks; } else { int turnDir = kTurnInfo[sceneObject->turnValue][sceneObject->turnCount & 0x7F]; if (turnDir) { sceneObject->turnValue = (sceneObject->turnValue + turnDir) & 7; int turnAnimIndex = sceneObject->sceneObjectDef->animIndices[kWalkTurnTbl[sceneObject->turnValue]]; if (turnAnimIndex) { Animation *anim = _gameModule->getAnimation(turnAnimIndex); if (anim) { sceneObject->anim = anim; sceneObject->animIndex = turnAnimIndex; sceneObject->turnTicks = 4; sceneObject->frameTicks = 1; sceneObject->frameIndex = anim->frameCount - 1; } } } else { sceneObject->turnCount = 0; } } } bool BbvsEngine::rectIntersection(const Common::Rect &rect1, const Common::Rect &rect2, Common::Rect &outRect) { outRect.left = MAX(rect1.left, rect2.left); outRect.top = MAX(rect1.top, rect2.top); outRect.right = MIN(rect1.right, rect2.right); outRect.bottom = MIN(rect1.bottom, rect2.bottom); return !outRect.isEmpty(); } int BbvsEngine::rectSubtract(const Common::Rect &rect1, const Common::Rect &rect2, Common::Rect *outRects) { int count = 0; Common::Rect workRect; if (rectIntersection(rect1, rect2, workRect)) { count = 0; outRects[count] = Common::Rect(rect2.width(), workRect.top - rect2.top); if (!outRects[count].isEmpty()) { outRects[count].translate(rect2.left, rect2.top); ++count; } outRects[count] = Common::Rect(workRect.left - rect2.left, workRect.height()); if (!outRects[count].isEmpty()) { outRects[count].translate(rect2.left, workRect.top); ++count; } outRects[count] = Common::Rect(rect2.right - workRect.right, workRect.height()); if (!outRects[count].isEmpty()) { outRects[count].translate(workRect.right, workRect.top); ++count; } outRects[count] = Common::Rect(rect2.width(), rect2.bottom - workRect.bottom); if (!outRects[count].isEmpty()) { outRects[count].translate(rect2.left, workRect.bottom); ++count; } } else { outRects[0] = rect2; count = 1; } return count; } WalkInfo *BbvsEngine::addWalkInfo(int16 x, int16 y, int delta, int direction, int16 midPtX, int16 midPtY, int walkAreaIndex) { WalkInfo *walkInfo = &_walkInfos[_walkInfosCount++]; walkInfo->walkAreaIndex = walkAreaIndex; walkInfo->direction = direction; walkInfo->x = x; walkInfo->y = y; walkInfo->delta = delta; walkInfo->midPt.x = midPtX; walkInfo->midPt.y = midPtY; return walkInfo; } void BbvsEngine::initWalkAreas(SceneObject *sceneObject) { int16 objX = sceneObject->x >> 16; int16 objY = sceneObject->y >> 16; Common::Rect rect; bool doRect = false; Common::Rect *workWalkableRects; if (_buttheadObject == sceneObject && _beavisObject->anim) { rect = _beavisObject->anim->frameRects2[_beavisObject->frameIndex]; rect.translate(_beavisObject->x >> 16, 1 + (_beavisObject->y >> 16)); doRect = !rect.isEmpty(); } else if (_buttheadObject->anim) { rect = _buttheadObject->anim->frameRects2[_buttheadObject->frameIndex]; rect.translate(_buttheadObject->x >> 16, 1 + (_buttheadObject->y >> 16)); doRect = !rect.isEmpty(); } workWalkableRects = _walkableRects; _walkAreasCount = _walkableRectsCount; if (doRect && !rect.contains(objX, objY)) { _walkAreasCount = 0; for (int i = 0; i < _walkableRectsCount; ++i) _walkAreasCount += rectSubtract(rect, _walkableRects[i], &_tempWalkableRects1[_walkAreasCount]); workWalkableRects = _tempWalkableRects1; } for (int i = 0; i < _walkAreasCount; ++i) { _walkAreas[i].x = workWalkableRects[i].left; _walkAreas[i].y = workWalkableRects[i].top; _walkAreas[i].width = workWalkableRects[i].width(); _walkAreas[i].height = workWalkableRects[i].height(); _walkAreas[i].checked = false; _walkAreas[i].linksCount = 0; } _walkInfosCount = 0; // Find connections between the walkRects for (int i = 0; i < _walkAreasCount; ++i) { WalkArea *walkArea1 = &_walkAreas[i]; int xIter = walkArea1->x + walkArea1->width; int yIter = walkArea1->y + walkArea1->height; for (int j = 0; j < _walkAreasCount; ++j) { WalkArea *walkArea2 = &_walkAreas[j]; if (i == j) continue; if (walkArea2->y == yIter) { int wa1x = MAX(walkArea1->x, walkArea2->x); int wa2x = MIN(walkArea2->x + walkArea2->width, xIter); if (wa2x > wa1x) { debug(5, "WalkArea %d connected to %d by Y", i, j); WalkInfo *walkInfo1 = addWalkInfo(wa1x, yIter - 1, wa2x - wa1x, 0, wa1x + (wa2x - wa1x) / 2, yIter - 1, i); WalkInfo *walkInfo2 = addWalkInfo(wa1x, yIter, wa2x - wa1x, 0, wa1x + (wa2x - wa1x) / 2, yIter, j); walkArea1->linksD1[walkArea1->linksCount] = walkInfo1; walkArea1->linksD2[walkArea1->linksCount] = walkInfo2; walkArea1->links[walkArea1->linksCount++] = walkArea2; walkArea2->linksD1[walkArea2->linksCount] = walkInfo2; walkArea2->linksD2[walkArea2->linksCount] = walkInfo1; walkArea2->links[walkArea2->linksCount++] = walkArea1; } } if (walkArea2->x == xIter) { int wa1y = MAX(walkArea1->y, walkArea2->y); int wa2y = MIN(walkArea2->y + walkArea2->height, yIter); if (wa2y > wa1y) { debug(5, "WalkArea %d connected to %d by X", i, j); WalkInfo *walkInfo1 = addWalkInfo(xIter - 1, wa1y, wa2y - wa1y, 1, xIter - 1, wa1y + (wa2y - wa1y) / 2, i); WalkInfo *walkInfo2 = addWalkInfo(xIter, wa1y, wa2y - wa1y, 1, xIter, wa1y + (wa2y - wa1y) / 2, j); walkArea1->linksD1[walkArea1->linksCount] = walkInfo1; walkArea1->linksD2[walkArea1->linksCount] = walkInfo2; walkArea1->links[walkArea1->linksCount++] = walkArea2; walkArea2->linksD1[walkArea2->linksCount] = walkInfo2; walkArea2->linksD2[walkArea2->linksCount] = walkInfo1; walkArea2->links[walkArea2->linksCount++] = walkArea1; } } } } } WalkArea *BbvsEngine::getWalkAreaAtPos(const Common::Point &pt) { for (int i = 0; i < _walkAreasCount; ++i) { WalkArea *walkArea = &_walkAreas[i]; if (walkArea->contains(pt)) return walkArea; } return 0; } bool BbvsEngine::canButtheadWalkToDest(const Common::Point &destPt) { Common::Point srcPt; _walkReachedDestArea = false; initWalkAreas(_buttheadObject); srcPt.x = _buttheadObject->x >> 16; srcPt.y = _buttheadObject->y >> 16; _sourceWalkArea = getWalkAreaAtPos(srcPt); if (_sourceWalkArea) { _destWalkArea = getWalkAreaAtPos(destPt); if (_destWalkArea) canWalkToDest(_sourceWalkArea, 0); } return _walkReachedDestArea; } void BbvsEngine::canWalkToDest(WalkArea *walkArea, int infoCount) { if (_destWalkArea == walkArea) { _walkReachedDestArea = true; return; } if (_gameModule->getFieldC() <= 320 || infoCount <= 20) { walkArea->checked = true; for (int linkIndex = 0; linkIndex < walkArea->linksCount; ++linkIndex) { if (!walkArea->links[linkIndex]->checked) { canWalkToDest(walkArea->links[linkIndex], infoCount + 2); if (_walkReachedDestArea) break; } } walkArea->checked = false; } } bool BbvsEngine::walkTestLineWalkable(const Common::Point &sourcePt, const Common::Point &destPt, WalkInfo *walkInfo) { const float ptDeltaX = destPt.x - sourcePt.x; const float ptDeltaY = destPt.y - sourcePt.y; const float wDeltaX = walkInfo->x - sourcePt.x; const float wDeltaY = walkInfo->y - sourcePt.y; if (destPt.x == sourcePt.x) return true; if (walkInfo->direction) { const float nDeltaY = wDeltaX * ptDeltaY / ptDeltaX + (float)sourcePt.y - (float)walkInfo->y; return (nDeltaY >= 0.0) && (nDeltaY < (float)walkInfo->delta); } else { const float nDeltaX = wDeltaY / ptDeltaX * ptDeltaY + (float)sourcePt.x - (float)walkInfo->x; return (nDeltaX >= 0.0) && (nDeltaX < (float)walkInfo->delta); } return false; } void BbvsEngine::walkFindPath(WalkArea *sourceWalkArea, int infoCount) { if (_destWalkArea == sourceWalkArea) { walkFoundPath(infoCount); } else if (_gameModule->getFieldC() <= 320 || infoCount <= 20) { sourceWalkArea->checked = true; for (int linkIndex = 0; linkIndex < sourceWalkArea->linksCount; ++linkIndex) { if (!sourceWalkArea->links[linkIndex]->checked) { _walkInfoPtrs[infoCount + 0] = sourceWalkArea->linksD1[linkIndex]; _walkInfoPtrs[infoCount + 1] = sourceWalkArea->linksD2[linkIndex]; walkFindPath(sourceWalkArea->links[linkIndex], infoCount + 2); } } sourceWalkArea->checked = false; } } int BbvsEngine::calcDistance(const Common::Point &pt1, const Common::Point &pt2) { return sqrt((pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y)); } void BbvsEngine::walkFoundPath(int count) { debug(5, "BbvsEngine::walkFoundPath(%d)", count); Common::Point midPt = _sourceWalkAreaPt; int totalMidPtDistance = 0; if (count > 0) { Common::Point lastMidPt; int halfCount = (count + 1) >> 1; for (int i = 0; i < halfCount; ++i) { lastMidPt = midPt; midPt = _walkInfoPtrs[i * 2]->midPt; totalMidPtDistance += calcDistance(midPt, lastMidPt); } } int distance = calcDistance(midPt, _destWalkAreaPt) + totalMidPtDistance; debug(5, "BbvsEngine::walkFoundPath() distance: %d; _currWalkDistance: %d", distance, _currWalkDistance); if (distance >= _currWalkDistance) return; debug(5, "BbvsEngine::walkFoundPath() distance smaller"); _currWalkDistance = distance; Common::Point destPt = _destWalkAreaPt, newDestPt; // TODO This needs some cleanup but seems to work while (1) { int index = 0; if (count > 0) { do { if (!walkTestLineWalkable(_sourceWalkAreaPt, destPt, _walkInfoPtrs[index])) break; ++index; } while (index < count); } if (index == count) break; WalkInfo *walkInfo = _walkInfoPtrs[--count]; destPt.x = walkInfo->x; destPt.y = walkInfo->y; if (walkInfo->direction) { newDestPt.x = walkInfo->x; newDestPt.y = walkInfo->y + walkInfo->delta - 1; } else { newDestPt.x = walkInfo->x + walkInfo->delta - 1; newDestPt.y = walkInfo->y; } if ((newDestPt.x - _destWalkAreaPt.x) * (newDestPt.x - _destWalkAreaPt.x) + (newDestPt.y - _destWalkAreaPt.y) * (newDestPt.y - _destWalkAreaPt.y) < (destPt.x - _destWalkAreaPt.x) * (destPt.x - _destWalkAreaPt.x) + (destPt.y - _destWalkAreaPt.y) * (destPt.y - _destWalkAreaPt.y)) destPt = newDestPt; } debug(5, "BbvsEngine::walkFoundPath() destPt: (%d, %d)", destPt.x, destPt.y); _finalWalkPt = destPt; debug(5, "BbvsEngine::walkFoundPath() OK"); } void BbvsEngine::updateWalkableRects() { // Go through all walkable rects and subtract all scene object rects Common::Rect *rectsList1 = _tempWalkableRects1; Common::Rect *rectsList2 = _gameModule->getWalkRects(); _walkableRectsCount = _gameModule->getWalkRectsCount(); for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) { SceneObject *sceneObject = &_sceneObjects[i]; Animation *anim = sceneObject->anim; if (anim && _buttheadObject != sceneObject && _beavisObject != sceneObject) { Common::Rect rect = sceneObject->anim->frameRects2[sceneObject->frameIndex]; rect.translate(sceneObject->x >> 16, sceneObject->y >> 16); int count = _walkableRectsCount; _walkableRectsCount = 0; for (int j = 0; j < count; ++j) _walkableRectsCount += rectSubtract(rect, rectsList2[j], &rectsList1[_walkableRectsCount]); if (rectsList1 == _tempWalkableRects1) { rectsList1 = _tempWalkableRects2; rectsList2 = _tempWalkableRects1; } else { rectsList1 = _tempWalkableRects1; rectsList2 = _tempWalkableRects2; } } } for (int i = 0; i < _walkableRectsCount; ++i) _walkableRects[i] = rectsList2[i]; } void BbvsEngine::updateSceneObjectsTurnValue() { for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) { SceneObject *sceneObject = &_sceneObjects[i]; sceneObject->turnValue = 0; for (int j = 0; j < 12; ++j) { if (sceneObject->sceneObjectDef->animIndices[j] == sceneObject->animIndex) { sceneObject->turnValue = kTurnTbl[j]; break; } } } } void BbvsEngine::updateDialogConditions() { _dialogSlotCount = 0; memset(_dialogItemStatus, 0, sizeof(_dialogItemStatus)); for (int i = 0; i < _gameModule->getActionsCount(); ++i) { Action *action = _gameModule->getAction(i); int slotIndex = evalDialogCondition(action->conditions); if (slotIndex >= 0) { _dialogItemStatus[slotIndex] = 1; ++_dialogSlotCount; } } } void BbvsEngine::playSpeech(int soundNum) { debug(5, "playSpeech(%0d)", soundNum); Common::String sndFilename = Common::String::format("snd/snd%05d.aif", soundNum); Common::File *fd = new Common::File(); fd->open(sndFilename); Audio::AudioStream *audioStream = Audio::makeLoopingAudioStream(Audio::makeAIFFStream(fd, DisposeAfterUse::YES), 1); _mixer->playStream(Audio::Mixer::kSpeechSoundType, &_speechSoundHandle, audioStream); } void BbvsEngine::stopSpeech() { _mixer->stopHandle(_speechSoundHandle); } void BbvsEngine::playSound(uint soundNum, bool loop) { debug(5, "playSound(%0d)", soundNum); for (uint i = 0; i < _gameModule->getPreloadSoundsCount(); ++i) if (_gameModule->getPreloadSound(i) == soundNum) { _sound->playSound(i, loop); break; } } void BbvsEngine::stopSound(uint soundNum) { for (uint i = 0; i < _gameModule->getPreloadSoundsCount(); ++i) if (_gameModule->getPreloadSound(i) == soundNum) { _sound->stopSound(i); break; } } void BbvsEngine::stopSounds() { _sound->stopAllSounds(); } bool BbvsEngine::runMinigame(int minigameNum) { debug(0, "BbvsEngine::runMinigame() minigameNum: %d", minigameNum); bool fromMainGame = _currSceneNum != kMainMenu; _sound->unloadSounds(); Minigame *minigame = 0; switch (minigameNum) { case kMinigameBbLoogie: minigame = new MinigameBbLoogie(this); break; case kMinigameBbTennis: minigame = new MinigameBbTennis(this); break; case kMinigameBbAnt: minigame = new MinigameBbAnt(this); break; case kMinigameBbAirGuitar: minigame = new MinigameBbAirGuitar(this); break; default: error("Incorrect minigame number %d", minigameNum); break; } int minigameResult = minigame->run(fromMainGame); delete minigame; // Check if the prinicpal was hit with a megaloogie in the loogie minigame if (minigameNum == 0 && minigameResult == 1) _gameVars[42] = 1; #if 0 //DEBUG Fake it :) if (minigameNum == 0) _gameVars[42] = 1; #endif return true; } void BbvsEngine::runMainMenu() { MainMenu *mainMenu = new MainMenu(this); mainMenu->runModal(); delete mainMenu; } } // End of namespace Bbvs