/* 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 3 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, see . * */ #include "common/file.h" #include "common/memstream.h" #include "common/config-manager.h" #include "common/random.h" #include "graphics/cursorman.h" #include "backends/keymapper/action.h" #include "backends/keymapper/keymap.h" #include "backends/keymapper/standard-actions.h" #include "common/translation.h" #include "freescape/freescape.h" #include "freescape/games/castle/castle.h" #include "freescape/language/8bitDetokeniser.h" namespace Freescape { CastleEngine::CastleEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEngine(syst, gd) { if (!Common::parseBool(ConfMan.get("rock_travel"), _useRockTravel)) error("Failed to parse bool from rock_travel option"); _soundIndexStart = 11; _soundIndexAreaChange = 24; _soundIndexCollide = 4; _soundIndexStepUp = 5; _soundIndexStepDown = 6; _soundIndexStartFalling = -1; k8bitVariableShield = 29; if (isDOS()) initDOS(); else if (isSpectrum()) initZX(); else if (isCPC()) initCPC(); _playerHeightNumber = 1; _playerHeightMaxNumber = 1; _lastTenSeconds = -1; _playerSteps.clear(); _playerSteps.push_back(15); _playerSteps.push_back(30); _playerSteps.push_back(120); _playerStepIndex = 2; _angleRotations.push_back(5); _playerWidth = 8; _playerDepth = 8; _stepUpDistance = 32; _maxShield = 24; _option = nullptr; _optionTexture = nullptr; _spiritsMeterIndicatorFrame = nullptr; _spiritsMeterIndicatorBackgroundFrame = nullptr; _spiritsMeterIndicatorSideFrame = nullptr; _strenghtBackgroundFrame = nullptr; _strenghtBarFrame = nullptr; _menu = nullptr; _menuButtons = nullptr; _riddleTopFrame = nullptr; _riddleBottomFrame = nullptr; _riddleBackgroundFrame = nullptr; _endGameThroneFrame = nullptr; _endGameBackgroundFrame = nullptr; _gameOverBackgroundFrame = nullptr; _menuCrawlIndicator = nullptr; _menuWalkIndicator = nullptr; _menuRunIndicator = nullptr; _menuFxOnIndicator = nullptr; _menuFxOffIndicator = nullptr; _spiritsMeter = 32; _spiritsToKill = 26; _spiritsMeterPosition = 0; _spiritsMeterMax = 64; _thunderTicks = 0; _thunderFrameDuration = 0; } CastleEngine::~CastleEngine() { if (_option) { _option->free(); delete _option; } for (int i = 0; i < int(_keysBorderFrames.size()); i++) { if (_keysBorderFrames[i]) { _keysBorderFrames[i]->free(); delete _keysBorderFrames[i]; } } for (int i = 0; i < int(_keysMenuFrames.size()); i++) { if (_keysMenuFrames[i]) { _keysMenuFrames[i]->free(); delete _keysMenuFrames[i]; } } if (_spiritsMeterIndicatorBackgroundFrame) { _spiritsMeterIndicatorBackgroundFrame->free(); delete _spiritsMeterIndicatorBackgroundFrame; } if (_spiritsMeterIndicatorFrame) { _spiritsMeterIndicatorFrame->free(); delete _spiritsMeterIndicatorFrame; } if (_spiritsMeterIndicatorSideFrame) { _spiritsMeterIndicatorSideFrame->free(); delete _spiritsMeterIndicatorSideFrame; } if (_strenghtBackgroundFrame) { _strenghtBackgroundFrame->free(); delete _strenghtBackgroundFrame; } if (_strenghtBarFrame) { _strenghtBarFrame->free(); delete _strenghtBarFrame; } for (int i = 0; i < int(_strenghtWeightsFrames.size()); i++) { if (_strenghtWeightsFrames[i]) { _strenghtWeightsFrames[i]->free(); delete _strenghtWeightsFrames[i]; } } for (int i = 0; i < int(_flagFrames.size()); i++) { if (_flagFrames[i]) { _flagFrames[i]->free(); delete _flagFrames[i]; } } for (int i = 0; i < int(_thunderFrames.size()); i++) { if (_thunderFrames[i]) { _thunderFrames[i]->free(); delete _thunderFrames[i]; } } for (int i = 0; i < int(_thunderTextures.size()); i++) { if (_thunderTextures[i]) { delete _thunderTextures[i]; } } if (_riddleTopFrame) { _riddleTopFrame->free(); delete _riddleTopFrame; } if (_riddleBackgroundFrame) { _riddleBackgroundFrame->free(); delete _riddleBackgroundFrame; } if (_riddleBottomFrame) { _riddleBottomFrame->free(); delete _riddleBottomFrame; } if (_endGameThroneFrame) { _endGameThroneFrame->free(); delete _endGameThroneFrame; } if (_endGameBackgroundFrame) { _endGameBackgroundFrame->free(); delete _endGameBackgroundFrame; } if (_gameOverBackgroundFrame) { _gameOverBackgroundFrame->free(); delete _gameOverBackgroundFrame; } if (_menu) { _menu->free(); delete _menu; } if (_menuButtons) { _menuButtons->free(); delete _menuButtons; } if (_menuCrawlIndicator) { _menuCrawlIndicator->free(); delete _menuCrawlIndicator; } if (_menuWalkIndicator) { _menuWalkIndicator->free(); delete _menuWalkIndicator; } if (_menuRunIndicator) { _menuRunIndicator->free(); delete _menuRunIndicator; } if (_menuFxOnIndicator) { _menuFxOnIndicator->free(); delete _menuFxOnIndicator; } if (_menuFxOffIndicator) { _menuFxOffIndicator->free(); delete _menuFxOffIndicator; } } void CastleEngine::initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *infoScreenKeyMap, const char *target) { FreescapeEngine::initKeymaps(engineKeyMap, infoScreenKeyMap, target); Common::Action *act; act = new Common::Action("SELECTPRINCE", _("Select Prince")); act->setCustomEngineActionEvent(kActionSelectPrince); act->addDefaultInputMapping("1"); infoScreenKeyMap->addAction(act); act = new Common::Action("SELECTPRINCESS", _("Select Princess")); act->setCustomEngineActionEvent(kActionSelectPrincess); act->addDefaultInputMapping("2"); infoScreenKeyMap->addAction(act); act = new Common::Action("SAVE", _("Save game")); act->setCustomEngineActionEvent(kActionSave); act->addDefaultInputMapping("s"); infoScreenKeyMap->addAction(act); act = new Common::Action("LOAD", _("Load game")); act->setCustomEngineActionEvent(kActionLoad); if (_language == Common::ES_ESP) act->addDefaultInputMapping("c"); else act->addDefaultInputMapping("l"); infoScreenKeyMap->addAction(act); act = new Common::Action("QUIT", _("Quit game")); act->setCustomEngineActionEvent(kActionQuit); act->addDefaultInputMapping("q"); infoScreenKeyMap->addAction(act); act = new Common::Action("TOGGLESOUND", _("Toggle Sound")); act->setCustomEngineActionEvent(kActionToggleSound); act->addDefaultInputMapping("t"); infoScreenKeyMap->addAction(act); act = new Common::Action("ROTL", _("Rotate Left")); act->setCustomEngineActionEvent(kActionRotateLeft); act->addDefaultInputMapping("z"); engineKeyMap->addAction(act); act = new Common::Action("ROTR", _("Rotate Right")); act->setCustomEngineActionEvent(kActionRotateRight); act->addDefaultInputMapping("x"); engineKeyMap->addAction(act); act = new Common::Action("RUNMODE", _("Run")); act->setCustomEngineActionEvent(kActionRunMode); act->addDefaultInputMapping("r"); engineKeyMap->addAction(act); act = new Common::Action("WALK", _("Walk")); act->setCustomEngineActionEvent(kActionWalkMode); act->addDefaultInputMapping("JOY_B"); act->addDefaultInputMapping("w"); engineKeyMap->addAction(act); act = new Common::Action("CRAWL", _("Crawl")); act->setCustomEngineActionEvent(kActionCrawlMode); act->addDefaultInputMapping("JOY_Y"); act->addDefaultInputMapping("c"); engineKeyMap->addAction(act); act = new Common::Action("FACEFRWARD", _("Face Forward")); act->setCustomEngineActionEvent(kActionFaceForward); act->addDefaultInputMapping("f"); engineKeyMap->addAction(act); act = new Common::Action("ACTIVATE", _("Activate")); act->setCustomEngineActionEvent(kActionActivate); act->addDefaultInputMapping("a"); engineKeyMap->addAction(act); } void CastleEngine::beforeStarting() { if (isDOS()) waitInLoop(250); else if (isSpectrum()) waitInLoop(100); } void CastleEngine::gotoArea(uint16 areaID, int entranceID) { debugC(1, kFreescapeDebugMove, "Jumping to area: %d, entrance: %d", areaID, entranceID); if (!_areaMap.contains(areaID) && isDemo()) return; // Abort area change if the destination does not exist (demo only) if (!_exploredAreas.contains(areaID)) { _gameStateVars[k8bitVariableScore] += 17500; _exploredAreas[areaID] = true; } assert(_areaMap.contains(areaID)); _currentArea = _areaMap[areaID]; _currentArea->show(); _maxFallingDistance = MAX(32, _currentArea->getScale() * 16 - 2); _nearClipPlane = _currentArea->isOutside() ? 2 : 0.5; if (entranceID > 0) traverseEntrance(entranceID); _position = _currentArea->separateFromWall(_position); _lastPosition = _position; if (_currentArea->_skyColor > 0 && _currentArea->_skyColor != 255) { _gfx->_keyColor = 0; } else _gfx->_keyColor = 255; _lastPosition = _position; if (areaID == _startArea && entranceID == _startEntrance) { if (getGameBit(31)) playSound(13, true, _soundFxHandle); else playSound(_soundIndexStart, false, _soundFxHandle); } else if (areaID == _endArea && entranceID == _endEntrance) { _pitch = -85; } else { // If escaped, play a different sound if (getGameBit(31)) playSound(13, true, _soundFxHandle); else playSound(_soundIndexAreaChange, true, _soundFxHandle); } debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z()); clearTemporalMessages(); // Ignore sky/ground fields _gfx->_keyColor = 0; _gfx->clearColorPairArray(); swapPalette(areaID); if (isDOS()) { _gfx->_colorPair[_currentArea->_underFireBackgroundColor] = _currentArea->_extraColor[1]; _gfx->_colorPair[_currentArea->_usualBackgroundColor] = _currentArea->_extraColor[0]; _gfx->_colorPair[_currentArea->_paperColor] = _currentArea->_extraColor[2]; _gfx->_colorPair[_currentArea->_inkColor] = _currentArea->_extraColor[3]; } else if (isAmiga()) { // Unclear why these colors are always overwritten byte (*palette)[16][3] = (byte (*)[16][3])_gfx->_palette; (*palette)[1][0] = 0x44; (*palette)[1][1] = 0x44; (*palette)[1][2] = 0x44; (*palette)[2][0] = 0x66; (*palette)[2][1] = 0x66; (*palette)[2][2] = 0x66; (*palette)[3][0] = 0x88; (*palette)[3][1] = 0x88; (*palette)[3][2] = 0x88; (*palette)[5][0] = 0xcc; (*palette)[5][1] = 0xcc; (*palette)[5][2] = 0xcc; } if (isSpectrum()) _gfx->_paperColor = 0; // Unclear why this is needed if (isCPC()) { for (int i = 0; i < 128; i++) { _gfx->_stipples[2][i] = _gfx->_stipples[11][i]; } ColorMap *cm = _gfx->_colorMap; (*cm)[2] = (*cm)[11]; } resetInput(); if (entranceID > 0) { executeMovementConditions(); } } void CastleEngine::initGameState() { FreescapeEngine::initGameState(); _playerHeightNumber = 1; _gameStateVars[k8bitVariableShield] = 16; _gameStateVars[k8bitVariableEnergy] = 1; _gameStateVars[8] = 128; // -1 _countdown = INT_MAX - 8; _keysCollected.clear(); _spiritsMeter = 32; _spiritsMeterPosition = _spiritsMeter * _spiritsToKill / _spiritsToKill; _exploredAreas[_startArea] = true; if (_useRockTravel) // Enable cheat setGameBit(k8bitGameBitTravelRock); _gfx->_shakeOffset = Common::Point(); int seconds, minutes, hours; getTimeFromCountdown(seconds, minutes, hours); _lastMinute = minutes; _lastTenSeconds = seconds / 10; _droppingGateStartTicks = 0; _thunderFrameDuration = 0; } bool CastleEngine::checkIfGameEnded() { if (_gameStateControl == kFreescapeGameStatePlaying) { if (_hasFallen && _avoidRenderingFrames == 0) { _hasFallen = false; playSound(_soundIndexFallen, false, _soundFxHandle); stopMovement(); // If shield is less than 11 after a fall, the game ends if (_gameStateVars[k8bitVariableShield] > 5) { _gameStateVars[k8bitVariableShield] -= 5; return false; // Game can continue } if (!_fallenMessage.empty()) insertTemporaryMessage(_fallenMessage, _countdown - 4); _gameStateControl = kFreescapeGameStateEnd; } if ((isSpectrum() && getGameBit(31)) || (isDOS() && _currentArea->getAreaID() == 74)) { // Escaped! _gameStateControl = kFreescapeGameStateEnd; return true; } } return FreescapeEngine::checkIfGameEnded(); } void CastleEngine::endGame() { _shootingFrames = 0; _delayedShootObject = nullptr; _endGamePlayerEndArea = true; if ((isSpectrum() && getGameBit(31)) || (isDOS() && _currentArea->getAreaID() == 74)) { // Escaped! insertTemporaryMessage(_messagesList[5], INT_MIN); if (isDOS()) { drawFullscreenEndGameAndWait(); } else drawFullscreenGameOverAndWait(); } else { drawFullscreenGameOverAndWait(); } _gameStateControl = kFreescapeGameStateRestart; _endGameKeyPressed = false; } void CastleEngine::pressedKey(const int keycode) { // This code is duplicated in the DrillerEngine::pressedKey (except for the J case) if (keycode == Common::KEYCODE_s) { // TODO: show score } else if (keycode == kActionRunMode) { if (_playerHeightNumber == 0) { if (_gameStateVars[k8bitVariableShield] <= 3) { insertTemporaryMessage(_messagesList[12], _countdown - 2); return; } if (!rise()) { _playerStepIndex = 0; insertTemporaryMessage(_messagesList[11], _countdown - 2); return; } _gameStateVars[k8bitVariableCrawling] = 0; } // TODO: raising can fail if there is no room, so the action should fail _playerStepIndex = 2; insertTemporaryMessage(_messagesList[15], _countdown - 2); } else if (keycode == kActionWalkMode) { if (_playerHeightNumber == 0) { if (_gameStateVars[k8bitVariableShield] <= 3) { insertTemporaryMessage(_messagesList[12], _countdown - 2); return; } if (!rise()) { _playerStepIndex = 0; insertTemporaryMessage(_messagesList[11], _countdown - 2); return; } _gameStateVars[k8bitVariableCrawling] = 0; } // TODO: raising can fail if there is no room, so the action should fail _playerStepIndex = 1; insertTemporaryMessage(_messagesList[14], _countdown - 2); } else if (keycode == kActionCrawlMode) { if (_playerHeightNumber == 1) { lower(); _gameStateVars[k8bitVariableCrawling] = 128; } _playerStepIndex = 0; insertTemporaryMessage(_messagesList[13], _countdown - 2); } else if (keycode == kActionFaceForward) { _pitch = 0; updateCamera(); } else if (keycode == kActionActivate) activate(); } void CastleEngine::drawInfoMenu() { PauseToken pauseToken = pauseEngine(); if (_savedScreen) { _savedScreen->free(); delete _savedScreen; } _savedScreen = _gfx->getScreenshot(); uint8 r, g, b; uint32 color = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00); Graphics::Surface *surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); surface->fillRect(_fullscreenViewArea, color); uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00); uint32 front = 0; surface->fillRect(_viewArea, black); int score = _gameStateVars[k8bitVariableScore]; int shield = _gameStateVars[k8bitVariableShield]; int spiritsDestroyed = _gameStateVars[k8bitVariableSpiritsDestroyed]; Common::Array keyRects; if (isDOS()) { CursorMan.setDefaultArrowCursor(); CursorMan.showMouse(true); surface->copyRectToSurface(*_menu, 47, 35, Common::Rect(0, 0, _menu->w, _menu->h)); _gfx->readFromPalette(10, r, g, b); front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); drawStringInSurface(Common::String::format("%07d", score), 166, 71, front, black, surface); drawStringInSurface(centerAndPadString(Common::String::format("%s", _messagesList[135 + shield / 6].c_str()), 10), 151, 102, front, black, surface); Common::String keysCollected = _messagesList[141]; Common::replace(keysCollected, "X", Common::String::format("%d", _keysCollected.size())); drawStringInSurface(keysCollected, 103, 41, front, black, surface); Common::String spiritsDestroyedString = _messagesList[133]; Common::replace(spiritsDestroyedString, "X", Common::String::format("%d", spiritsDestroyed)); drawStringInSurface(spiritsDestroyedString, 145 , 132, front, black, surface); for (int i = 0; i < int(_keysCollected.size()) ; i++) { int y = 58 + (i / 2) * 18; if (i % 2 == 0) { surface->copyRectToSurfaceWithKey(*_keysBorderFrames[i], 58, y, Common::Rect(0, 0, _keysBorderFrames[i]->w, _keysBorderFrames[i]->h), black); keyRects.push_back(Common::Rect(58, y, 58 + _keysBorderFrames[i]->w / 2, y + _keysBorderFrames[i]->h)); } else { surface->copyRectToSurfaceWithKey(*_keysBorderFrames[i], 80, y, Common::Rect(0, 0, _keysBorderFrames[i]->w, _keysBorderFrames[i]->h), black); keyRects.push_back(Common::Rect(80, y, 80 + _keysBorderFrames[i]->w / 2, y + _keysBorderFrames[i]->h)); } } } else if (isSpectrum()) { Common::Array lines; lines.push_back(centerAndPadString("********************", 21)); if (_language == Common::EN_ANY) { lines.push_back(centerAndPadString("s-save l-load q-quit", 21)); lines.push_back(""); lines.push_back(centerAndPadString(Common::String::format("keys %d collected", _keysCollected.size()), 21)); lines.push_back(centerAndPadString(Common::String::format("spirits %d destroyed", spiritsDestroyed), 21)); lines.push_back(centerAndPadString(Common::String::format("strength %s", _messagesList[62 + shield / 6].c_str()), 21)); lines.push_back(centerAndPadString(Common::String::format("score %07d", score), 21)); } else if (_language == Common::ES_ESP) { lines.push_back(centerAndPadString("s-salv c-carg q-quit", 21)); lines.push_back(""); lines.push_back(centerAndPadString(Common::String::format("llaves %d recogidas", _keysCollected.size()), 21)); lines.push_back(centerAndPadString(Common::String::format("espirit %d destruidos", spiritsDestroyed), 21)); lines.push_back(centerAndPadString(Common::String::format("fuerza %s", _messagesList[62 + shield / 6].c_str()), 21)); lines.push_back(centerAndPadString(Common::String::format("puntos %07d", score), 21)); } else { error("Language not supported"); } lines.push_back(""); lines.push_back(centerAndPadString("********************", 21)); surface = drawStringsInSurface(lines, surface); } Common::Event event; Common::Point mousePos; bool cont = true; Common::Rect loadGameRect(101, 67, 133, 79); Common::Rect saveGameRect(101, 82, 133, 95); Common::Rect toggleSoundRect(101, 101, 133, 114); Common::Rect cycleRect(101, 116, 133, 129); Common::Rect backRect(101, 131, 133, 144); Graphics::Surface *originalSurface = new Graphics::Surface(); originalSurface->copyFrom(*surface); Texture *menuTexture = _gfx->createTexture(surface); while (!shouldQuit() && cont) { while (_eventManager->pollEvent(event)) { // Events switch (event.type) { case Common::EVENT_CUSTOM_ENGINE_ACTION_START: if (event.customType == kActionLoad) { _gfx->setViewport(_fullscreenViewArea); _eventManager->purgeKeyboardEvents(); _eventManager->purgeMouseEvents(); loadGameDialog(); _eventManager->purgeMouseEvents(); if (isDOS() || isAmiga() || isAtariST()) { g_system->lockMouse(false); g_system->showMouse(true); } _gfx->setViewport(_viewArea); } else if (event.customType == kActionSave) { _gfx->setViewport(_fullscreenViewArea); _eventManager->purgeKeyboardEvents(); _eventManager->purgeMouseEvents(); saveGameDialog(); _eventManager->purgeMouseEvents(); if (isDOS() || isAmiga() || isAtariST()) { g_system->lockMouse(false); g_system->showMouse(true); } _gfx->setViewport(_viewArea); } else if (isDOS() && event.customType == kActionToggleSound) { // TODO } else if (event.customType == kActionQuit) { _forceEndGame = true; cont = false; } else cont = false; break; case Common::EVENT_KEYDOWN: cont = false; break; case Common::EVENT_SCREEN_CHANGED: _gfx->computeScreenViewport(); // TODO: properly refresh screen break; case Common::EVENT_RBUTTONDOWN: // fallthrough case Common::EVENT_LBUTTONDOWN: if (isSpectrum() || isCPC()) break; mousePos = getNormalizedPosition(event.mouse); for (int i = 0; i < int(keyRects.size()); i++) { if (keyRects[i].contains(mousePos)) { surface->copyFrom(*originalSurface); surface->frameRect(keyRects[i], front); drawStringInSurface(_messagesList[ 145 + _keysCollected[i] ], 103, 41, front, black, surface); menuTexture->update(surface); break; } } if (loadGameRect.contains(mousePos)) { _gfx->setViewport(_fullscreenViewArea); _eventManager->purgeKeyboardEvents(); loadGameDialog(); g_system->lockMouse(false); g_system->showMouse(true); _gfx->setViewport(_viewArea); } else if (saveGameRect.contains(mousePos)) { _gfx->setViewport(_fullscreenViewArea); _eventManager->purgeKeyboardEvents(); saveGameDialog(); g_system->lockMouse(false); g_system->showMouse(true); _gfx->setViewport(_viewArea); } else if (toggleSoundRect.contains(mousePos)) { // Toggle sounds } else if (cycleRect.contains(mousePos)) { // Cycle between crawl, walk or run // It can fail if there is no room } else if (backRect.contains(mousePos)) cont = false; // Back to game break; default: break; } } _gfx->clear(0, 0, 0, true); drawFrame(); if (surface) _gfx->drawTexturedRect2D(_fullscreenViewArea, _fullscreenViewArea, menuTexture); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(15); // try to target ~60 FPS } _savedScreen->free(); delete _savedScreen; _savedScreen = nullptr; originalSurface->free(); delete originalSurface; surface->free(); delete surface; delete menuTexture; pauseToken.clear(); CursorMan.showMouse(false); } void CastleEngine::drawFullscreenEndGameAndWait() { Graphics::Surface *surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); surface->fillRect(_fullscreenViewArea, _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00)); surface->fillRect(_viewArea, _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00)); surface->copyRectToSurface(*_endGameBackgroundFrame, 46, 38, Common::Rect(0, 0, _endGameBackgroundFrame->w, _endGameBackgroundFrame->h)); Common::Event event; bool cont = true; bool magisterAlive = true; while (!shouldQuit() && cont) { while (_eventManager->pollEvent(event)) { // Events switch (event.type) { case Common::EVENT_LBUTTONDOWN: if (magisterAlive) { surface->copyRectToSurface(*_endGameThroneFrame, 121, 52, Common::Rect(0, 0, _endGameThroneFrame->w - 1, _endGameThroneFrame->h)); magisterAlive = false; } else cont = false; break; case Common::EVENT_CUSTOM_ENGINE_ACTION_START: if (event.customType == kActionShoot) { if (magisterAlive) { surface->copyRectToSurface(*_endGameThroneFrame, 121, 52, Common::Rect(0, 0, _endGameThroneFrame->w - 1, _endGameThroneFrame->h)); magisterAlive = false; } else cont = false; } break; case Common::EVENT_SCREEN_CHANGED: _gfx->computeScreenViewport(); break; default: break; } } _gfx->clear(0, 0, 0, true); drawBorder(); if (_currentArea) drawUI(); drawFullscreenSurface(surface); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(15); // try to target ~60 FPS } surface->free(); delete surface; } void CastleEngine::drawFullscreenGameOverAndWait() { Common::Event event; bool cont = true; int score = _gameStateVars[k8bitVariableScore]; int spiritsDestroyed = _gameStateVars[k8bitVariableSpiritsDestroyed]; Common::String keysCollectedString; if (isDOS()) keysCollectedString = _messagesList[130]; else if (isSpectrum()) { if (_language == Common::EN_ANY) keysCollectedString = "X COLLECTED"; else if (_language == Common::ES_ESP) keysCollectedString = "X RECOGIDAS"; else error("Language not supported"); } if (isDOS() && _keysCollected.size() == 0) keysCollectedString = _messagesList[128]; else Common::replace(keysCollectedString, "X", Common::String::format("%d", _keysCollected.size())); keysCollectedString = centerAndPadString(keysCollectedString, 15); Common::String scoreString; if (isDOS()) scoreString = _messagesList[131]; else if (isSpectrum()) { if (_language == Common::EN_ANY) scoreString = "SCORE XXXXXXX"; else if (_language == Common::ES_ESP) scoreString = "PUNTOS XXXXXXX"; else error("Language not supported"); } Common::replace(scoreString, "XXXXXXX", Common::String::format("%07d", score)); scoreString = centerAndPadString(scoreString, 15); Common::String spiritsDestroyedString; if (isDOS()) spiritsDestroyedString = _messagesList[133]; else if (isSpectrum()) { if (_language == Common::EN_ANY) spiritsDestroyedString = "X DESTROYED"; else if (_language == Common::ES_ESP) spiritsDestroyedString = "X DESTRUIDOS"; else error("Language not supported"); } Common::replace(spiritsDestroyedString, "X", Common::String::format("%d", spiritsDestroyed)); spiritsDestroyedString = centerAndPadString(spiritsDestroyedString, 15); _droppingGateStartTicks = _ticks; if (isDOS()) { // TODO: playSound(X, false, _soundFxHandle); } else if (isSpectrum()) { playSound(9, false, _soundFxHandle); } if (isSpectrum() && getGameBit(31)) { insertTemporaryMessage(_messagesList[5], _countdown - 1); } while (!shouldQuit() && cont) { if (_temporaryMessageDeadlines.empty()) { insertTemporaryMessage(scoreString, _countdown - 2); insertTemporaryMessage(spiritsDestroyedString, _countdown - 4); insertTemporaryMessage(keysCollectedString, _countdown - 6); if (isSpectrum() && getGameBit(31)) { insertTemporaryMessage(_messagesList[5], _countdown - 8); } } while (_eventManager->pollEvent(event)) { // Events switch (event.type) { case Common::EVENT_LBUTTONDOWN: cont = false; break; case Common::EVENT_CUSTOM_ENGINE_ACTION_START: if (event.customType == kActionShoot || event.customType == kActionChangeMode || event.customType == kActionSkip) { cont = false; } break; case Common::EVENT_SCREEN_CHANGED: _gfx->computeScreenViewport(); break; default: break; } } _gfx->clear(0, 0, 0, true); drawFrame(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(15); // try to target ~60 FPS } } // Same as FreescapeEngine::executeExecute but updates the spirits destroyed counter void CastleEngine::executeDestroy(FCLInstruction &instruction) { uint16 objectID = 0; uint16 areaID = _currentArea->getAreaID(); if (instruction._destination > 0) { objectID = instruction._destination; areaID = instruction._source; } else { objectID = instruction._source; } debugC(1, kFreescapeDebugCode, "Destroying obj %d in area %d!", objectID, areaID); assert(_areaMap.contains(areaID)); Object *obj = _areaMap[areaID]->objectWithID(objectID); assert(obj); // We know that an object should be there if (!obj->isDestroyed() && obj->getType() == kSensorType && isCastle()) { _shootingFrames = 0; _gfx->_inkColor = _currentArea->_inkColor; _gfx->_shakeOffset = Common::Point(); } if (obj->isDestroyed()) debugC(1, kFreescapeDebugCode, "WARNING: Destroying obj %d in area %d already destroyed!", objectID, areaID); obj->makeInvisible(); obj->destroy(); } void CastleEngine::executePrint(FCLInstruction &instruction) { uint16 index = instruction._source; _currentAreaMessages.clear(); if (index == 128 && isSpectrum()) { drawFullscreenRiddleAndWait(8); return; } else if (index == 128 && isDemo()) { drawFullscreenRiddleAndWait(18); return; } else if (index >= 129) { index = index - 129; drawFullscreenRiddleAndWait(index); return; } debugC(1, kFreescapeDebugCode, "Printing message %d: \"%s\"", index, _messagesList[index].c_str()); insertTemporaryMessage(_messagesList[index], _countdown - 3); } void CastleEngine::executeRedraw(FCLInstruction &instruction) { FreescapeEngine::executeRedraw(instruction); tryToCollectKey(); } void CastleEngine::loadAssets() { FreescapeEngine::loadAssets(); addGhosts(); _endArea = 1; _endEntrance = 42; _timeoutMessage = _messagesList[1]; // Shield is unused in Castle Master _noEnergyMessage = _messagesList[2]; _crushedMessage = _messagesList[3]; _fallenMessage = _messagesList[4]; _outOfReachMessage = _messagesList[7]; _noEffectMessage = _messagesList[8]; if (!isAmiga() && !isCPC()) { Graphics::Surface *tmp; tmp = loadBundledImage("castle_gate", !isDOS()); _gameOverBackgroundFrame = new Graphics::ManagedSurface; _gameOverBackgroundFrame->copyFrom(*tmp); _gameOverBackgroundFrame->convertToInPlace(_gfx->_texturePixelFormat); tmp->free(); delete tmp; } Common::List globalIds = _areaMap[255]->getEntranceIds(); for (auto &it : _areaMap) { if (it._value->getAreaID() == 255) continue; it._value->addStructure(_areaMap[255]); if (isDOS() || isAmiga() || isAtariST()) { if (it._value->objectWithID(125)) { _areaMap[it._key]->addGroupFromArea(195, _areaMap[255]); //group = (Group *)_areaMap[it._key]->objectWithID(195); _areaMap[it._key]->addGroupFromArea(212, _areaMap[255]); //group = (Group *)_areaMap[it._key]->objectWithID(212); } if (it._value->objectWithID(126)) { _areaMap[it._key]->addGroupFromArea(191, _areaMap[255]); //group = (Group *)_areaMap[it._key]->objectWithID(191); } if (it._value->objectWithID(127)) { _areaMap[it._key]->addGroupFromArea(182, _areaMap[255]); //group = (Group *)_areaMap[it._key]->objectWithID(193); } } for (auto &id : globalIds) { if (it._value->entranceWithID(id)) continue; Object *obj = _areaMap[255]->entranceWithID(id); assert(obj); assert(obj->getType() == ObjectType::kEntranceType); // The entrance is not in the current area, so we need to add it it._value->addObjectFromArea(id, _areaMap[255]); } } _areaMap[1]->addFloor(); _areaMap[2]->addFloor(); } void CastleEngine::loadRiddles(Common::SeekableReadStream *file, int offset, int number) { file->seek(offset); Common::Array origins; for (int i = 0; i < number; i++) { Common::Point origin; origin.x = file->readByte(); origin.y = file->readByte(); debugC(1, kFreescapeDebugParser, "riddle %d origin: %d, %d", i, origin.x, origin.y); origins.push_back(origin); } debugC(1, kFreescapeDebugParser, "Riddle table:"); int maxLineSize = (isSpectrum()) ? 20 : 24; for (int i = 0; i < number; i++) { Riddle riddle; riddle._origin = origins[i]; int numberLines = file->readByte(); debugC(1, kFreescapeDebugParser, "riddle %d number of lines: %d", i, numberLines); int8 x, y; for (int j = 0; j < numberLines; j++) { x = file->readByte(); y = file->readByte(); int size = file->readByte(); debugC(1, kFreescapeDebugParser, "size: %d (max %d?)", size, maxLineSize); Common::String message = ""; if (size == 255) { size = 19; while (size-- > 0) message = message + "*"; debugC(1, kFreescapeDebugParser, "'%s' with offset: %d, %d", message.c_str(), x, y); riddle._lines.push_back(RiddleText(x, y, message)); continue; } else if (size > maxLineSize) { assert(0); } else if (size == 0) { assert(0); } debugC(1, kFreescapeDebugParser, "extra byte: %x", file->readByte()); if (i == 20 && j == 1 && _language == Common::ES_ESP) size = size + 3; while (size-- > 0) { byte c = file->readByte(); if (c > 0x7F) { file->seek(-1, SEEK_CUR); break; } else if (c != 0) message = message + c; } if (isAmiga() || isAtariST()) debug("extra byte: %x", file->readByte()); debugC(1, kFreescapeDebugParser, "'%s' with offset: %d, %d", message.c_str(), x, y); riddle._lines.push_back(RiddleText(x, y, message)); } _riddleList.push_back(riddle); } debugC(1, kFreescapeDebugParser, "End of riddles at %" PRIx64, file->pos()); } void CastleEngine::drawFullscreenRiddleAndWait(uint16 riddle) { debugC(1, kFreescapeDebugCode, "Printing fullscreen riddle %d", riddle); if (_savedScreen) { _savedScreen->free(); delete _savedScreen; } _savedScreen = _gfx->getScreenshot(); int frontColor = 6; switch (_renderMode) { case Common::kRenderZX: frontColor = 7; break; default: break; } uint8 r, g, b; _gfx->readFromPalette(frontColor, r, g, b); uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00); Graphics::Surface *surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); Common::Event event; bool cont = true; while (!shouldQuit() && cont) { while (_eventManager->pollEvent(event)) { // Events switch (event.type) { case Common::EVENT_CUSTOM_ENGINE_ACTION_START: if (event.customType == kActionSkip) { cont = false; } break; case Common::EVENT_SCREEN_CHANGED: _gfx->computeScreenViewport(); break; case Common::EVENT_RBUTTONDOWN: // fallthrough case Common::EVENT_LBUTTONDOWN: if (g_system->hasFeature(OSystem::kFeatureTouchscreen)) cont = false; break; default: break; } } _gfx->clear(0, 0, 0, true); drawBorder(); if (_currentArea) drawUI(); drawRiddle(riddle, front, transparent, surface); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(15); // try to target ~60 FPS } _savedScreen->free(); delete _savedScreen; _savedScreen = nullptr; surface->free(); delete surface; } void CastleEngine::drawRiddle(uint16 riddle, uint32 front, uint32 back, Graphics::Surface *surface) { int x = 0; int y = 0; int maxWidth = 136; if (isDOS()) { x = 40; y = 34; } else if (isSpectrum() || isCPC()) { x = 64; y = 37; } surface->copyRectToSurface((const Graphics::Surface)*_riddleTopFrame, x, y, Common::Rect(0, 0, _riddleTopFrame->w, _riddleTopFrame->h)); for (y += _riddleTopFrame->h; y < maxWidth;) { surface->copyRectToSurface((const Graphics::Surface)*_riddleBackgroundFrame, x, y, Common::Rect(0, 0, _riddleBackgroundFrame->w, _riddleBackgroundFrame->h)); y += _riddleBackgroundFrame->h; } surface->copyRectToSurface((const Graphics::Surface)*_riddleBottomFrame, x, maxWidth, Common::Rect(0, 0, _riddleBottomFrame->w, _riddleBottomFrame->h - 1)); Common::Array riddleMessages = _riddleList[riddle]._lines; x = _riddleList[riddle]._origin.x; y = _riddleList[riddle]._origin.y; if (isDOS()) { x = 38; y = 33; } else if (isSpectrum() || isCPC()) { x = 64; y = 36; } for (int i = 0; i < int(riddleMessages.size()); i++) { x = x + riddleMessages[i]._dx; y = y + riddleMessages[i]._dy; drawRiddleStringInSurface(riddleMessages[i]._text, x, y, front, back, surface); } drawFullscreenSurface(surface); } void CastleEngine::drawRiddleStringInSurface(const Common::String &str, int x, int y, uint32 fontColor, uint32 backColor, Graphics::Surface *surface) { Common::String ustr = str; ustr.toUppercase(); if (isDOS()) { _fontRiddle.setBackground(backColor); _fontRiddle.drawString(surface, ustr, x, y, _screenW, fontColor); } else { _font.setBackground(backColor); _font.drawString(surface, ustr, x, y, _screenW, fontColor); } } void CastleEngine::drawEnergyMeter(Graphics::Surface *surface, Common::Point origin) { if (!_strenghtBackgroundFrame) return; surface->copyRectToSurface((const Graphics::Surface)*_strenghtBackgroundFrame, origin.x, origin.y, Common::Rect(0, 0, _strenghtBackgroundFrame->w, _strenghtBackgroundFrame->h)); if (!_strenghtBarFrame) return; uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00); uint32 back = 0; if (isDOS()) back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00); Common::Point barFrameOrigin = origin; if (isDOS()) barFrameOrigin += Common::Point(5, 6); else if (isSpectrum()) barFrameOrigin += Common::Point(0, 6); surface->copyRectToSurfaceWithKey((const Graphics::Surface)*_strenghtBarFrame, barFrameOrigin.x, barFrameOrigin.y, Common::Rect(0, 0, _strenghtBarFrame->w, _strenghtBarFrame->h), black); Common::Point weightPoint; int frameIdx = -1; weightPoint = Common::Point(origin.x + 10, origin.y); frameIdx = _gameStateVars[k8bitVariableShield] % 4; if (_strenghtWeightsFrames.empty()) return; if (frameIdx != 0) { frameIdx = 4 - frameIdx; surface->copyRectToSurfaceWithKey((const Graphics::Surface)*_strenghtWeightsFrames[frameIdx], weightPoint.x, weightPoint.y, Common::Rect(0, 0, 3, _strenghtWeightsFrames[frameIdx]->h), back); weightPoint += Common::Point(3, 0); } for (int i = 0; i < _gameStateVars[k8bitVariableShield] / 4; i++) { surface->copyRectToSurfaceWithKey((const Graphics::Surface)*_strenghtWeightsFrames[0], weightPoint.x, weightPoint.y, Common::Rect(0, 0, 3, _strenghtWeightsFrames[0]->h), back); weightPoint += Common::Point(3, 0); } weightPoint = Common::Point(origin.x + 62, origin.y); frameIdx = _gameStateVars[k8bitVariableShield] % 4; if (frameIdx != 0) { frameIdx = 4 - frameIdx; surface->copyRectToSurfaceWithKey((const Graphics::Surface)*_strenghtWeightsFrames[frameIdx], weightPoint.x, weightPoint.y, Common::Rect(0, 0, 3, _strenghtWeightsFrames[frameIdx]->h), back); weightPoint += Common::Point(-3, 0); } for (int i = 0; i < _gameStateVars[k8bitVariableShield] / 4; i++) { surface->copyRectToSurfaceWithKey((const Graphics::Surface)*_strenghtWeightsFrames[0], weightPoint.x, weightPoint.y, Common::Rect(0, 0, 3, _strenghtWeightsFrames[0]->h), back); weightPoint += Common::Point(-3, 0); } } void CastleEngine::addGhosts() { } void CastleEngine::checkSensors() { if (_lastTick == _ticks) return; _lastTick = _ticks; if (_sensors.empty()) { _gfx->_shakeOffset = Common::Point(); return; } if (!ghostInArea()) { _mixer->stopHandle(_soundFxGhostHandle); _gfx->_shakeOffset = Common::Point(); return; } if (_disableSensors) return; /*if (!_mixer->isSoundHandleActive(_soundFxGhostHandle)) { _speaker->play(Audio::PCSpeaker::kWaveFormSquare, 25.0f, -1); _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxGhostHandle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); }*/ // This is the frequency to shake the screen if (_ticks % 5 == 0) { if (_underFireFrames <= 0) _underFireFrames = 1; } // This is the frequency to take damage if (_ticks % 100 == 0) { takeDamageFromSensor(); } } bool CastleEngine::ghostInArea() { for (auto &it : _sensors) { if (it->isDestroyed() || it->isInvisible()) continue; return true; break; } return false; } void CastleEngine::drawSensorShoot(Sensor *sensor) { if (isSpectrum()) { _gfx->_inkColor = 1 + (_gfx->_inkColor + 1) % 7; } else if (isDOS()) { float shakeIntensity = 10; Common::Point shakeOffset; shakeOffset.x = (_rnd->getRandomNumber(10) / 10.0 - 0.5f) * shakeIntensity; shakeOffset.y = (_rnd->getRandomNumber(10) / 10.0 - 0.5f) * shakeIntensity; _gfx->_shakeOffset = shakeOffset; } else { /* TODO */ } } void CastleEngine::tryToCollectKey() { if (_gameStateVars[32] > 0) { // Key collected! if (_keysCollected.size() < 10) { _gameStateVars[31]++; setGameBit(_gameStateVars[32]); _keysCollected.push_back(_gameStateVars[32]); } _gameStateVars[32] = 0; } } void CastleEngine::updateTimeVariables() { if (_gameStateControl != kFreescapeGameStatePlaying) return; // This function only executes "on collision" room/global conditions tryToCollectKey(); int seconds, minutes, hours; getTimeFromCountdown(seconds, minutes, hours); if (_lastMinute != minutes / 2) { int spiritsDestroyed = _gameStateVars[k8bitVariableSpiritsDestroyed]; _lastMinute = minutes / 2; _spiritsMeter++; _spiritsMeterPosition = _spiritsMeter * (_spiritsToKill - spiritsDestroyed) / _spiritsToKill; if (_spiritsMeterPosition >= _spiritsMeterMax) _countdown = -1; } if (_lastTenSeconds != seconds / 10) { _lastTenSeconds = seconds / 10; executeLocalGlobalConditions(false, false, true); } } void CastleEngine::borderScreen() { if (isAmiga() && isDemo()) return; // Skip character selection if (isSpectrum() || isCPC()) FreescapeEngine::borderScreen(); else { uint32 color = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00); Graphics::Surface *surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); surface->fillRect(_fullscreenViewArea, color); int x = 40; int y = 34; Common::Array selectMessage = _riddleList[19]._lines; for (int i = 0; i < int(selectMessage.size()); i++) { x = x + selectMessage[i]._dx; y = y + selectMessage[i]._dy; // Color is not important, as the font has already a palette drawStringInSurface(selectMessage[i]._text, x, y, 0, 0, surface); } drawFullscreenSurface(surface); drawBorderScreenAndWait(surface, 6 * 60); surface->free(); delete surface; } selectCharacterScreen(); } void CastleEngine::drawOption() { _gfx->setViewport(_fullscreenViewArea); if (_option) { if (!_optionTexture) { Graphics::Surface *title = _gfx->convertImageFormatIfNecessary(_option); _optionTexture = _gfx->createTexture(title); title->free(); delete title; } _gfx->drawTexturedRect2D(_fullscreenViewArea, _fullscreenViewArea, _optionTexture); } _gfx->setViewport(_viewArea); } void CastleEngine::selectCharacterScreen() { Common::Array lines; uint32 color = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00); Graphics::Surface *surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); surface->fillRect(_fullscreenViewArea, color); if (isSpectrum() || isCPC()) { if (_language == Common::ES_ESP) { // No accent in "príncipe" since it is not supported by the font lines.push_back(centerAndPadString("*******************", 21)); lines.push_back(centerAndPadString("Seleccion el ", 21)); lines.push_back(centerAndPadString("personaje que quiera", 21)); lines.push_back(centerAndPadString("ser y pulse enter", 21)); lines.push_back(""); lines.push_back(centerAndPadString("1. Principe", 21)); lines.push_back(centerAndPadString("2. Princesa", 21)); lines.push_back(""); lines.push_back(centerAndPadString("*******************", 21)); } else { lines.push_back(centerAndPadString("*******************", 21)); lines.push_back(centerAndPadString("Select the character", 21)); lines.push_back(centerAndPadString("you wish to play", 21)); lines.push_back(centerAndPadString("and press enter", 21)); lines.push_back(""); lines.push_back(centerAndPadString("1. Prince ", 21)); lines.push_back(centerAndPadString("2. Princess", 21)); lines.push_back(""); lines.push_back(centerAndPadString("*******************", 21)); } drawStringsInSurface(lines, surface); } else { int x = 0; int y = 0; Common::Array selectMessage = _riddleList[21]._lines; for (int i = 0; i < int(selectMessage.size()); i++) { x = x + selectMessage[i]._dx; y = y + selectMessage[i]._dy; drawStringInSurface(selectMessage[i]._text, x, y, color, color, surface); } drawFullscreenSurface(surface); } _system->lockMouse(false); _system->showMouse(true); Common::Rect princeSelector(82, 100, 163, 109); Common::Rect princessSelector(82, 110, 181, 120); bool selected = false; while (!selected) { Common::Event event; Common::Point mouse; while (_eventManager->pollEvent(event)) { switch (event.type) { case Common::EVENT_QUIT: case Common::EVENT_RETURN_TO_LAUNCHER: quitGame(); return; // Left mouse click case Common::EVENT_LBUTTONDOWN: // fallthrough case Common::EVENT_RBUTTONDOWN: mouse.x = _screenW * event.mouse.x / g_system->getWidth(); mouse.y = _screenH * event.mouse.y / g_system->getHeight(); if (princeSelector.contains(mouse)) { selected = true; // Nothing, since game bit should be already zero } else if (princessSelector.contains(mouse)) { selected = true; setGameBit(32); } break; case Common::EVENT_SCREEN_CHANGED: _gfx->computeScreenViewport(); _gfx->clear(0, 0, 0, true); break; default: break; } switch (event.customType) { case kActionSelectPrince: selected = true; // Nothing, since game bit should be already zero break; case kActionSelectPrincess: selected = true; setGameBit(32); break; default: break; } } _gfx->clear(0, 0, 0, true); if (_option) drawOption(); else drawBorder(); drawFullscreenSurface(surface); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(15); // try to target ~60 FPS } _system->lockMouse(true); _system->showMouse(false); _gfx->clear(0, 0, 0, true); } void CastleEngine::drawLiftingGate(Graphics::Surface *surface) { uint32 keyColor = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x24, 0xA5); int duration = 0; if (isDOS()) duration = 250; else if (isSpectrum()) duration = 100; if ((_gameStateControl == kFreescapeGameStateStart || _gameStateControl == kFreescapeGameStateRestart) && _ticks <= duration) { // Draw the _gameOverBackgroundFrame gate lifting up slowly int gate_w = _gameOverBackgroundFrame->w; int gate_h = _gameOverBackgroundFrame->h; // The gate should move up by the height of the view area to disappear. int y_offset = _ticks * _viewArea.height() / duration; // Initial position is with the gate bottom at the view area bottom. int dx = _viewArea.left + (_viewArea.width() - gate_w) / 2; int dy = (_viewArea.bottom - gate_h) - y_offset; // Define destination rect for the full gate Common::Rect destRect(dx, dy, dx + gate_w, dy + gate_h); // Find intersection with view area to clip Common::Rect clippedDest = destRect.findIntersectingRect(_viewArea); // If there is something to draw if (clippedDest.isValidRect() && clippedDest.width() > 0 && clippedDest.height() > 0) { // Adjust source rect based on clipping int src_x = clippedDest.left - destRect.left; int src_y = clippedDest.top - destRect.top; Common::Rect clippedSrc(src_x, src_y, src_x + clippedDest.width(), src_y + clippedDest.height()); // Draw the clipped part surface->copyRectToSurfaceWithKey(*_gameOverBackgroundFrame, clippedDest.left, clippedDest.top, clippedSrc, keyColor); } } } void CastleEngine::drawDroppingGate(Graphics::Surface *surface) { if (isSpectrum() && getGameBit(31)) return; // No gate dropping when the player escaped if (_droppingGateStartTicks <= 0) return; uint32 keyColor = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x24, 0xA5); int duration = 60; int ticks = _ticks - _droppingGateStartTicks; if (_gameStateControl == kFreescapeGameStateEnd && _ticks <= _droppingGateStartTicks + duration) { // Draw the _gameOverBackgroundFrame gate dropping down slowly int gate_w = _gameOverBackgroundFrame->w; int gate_h = _gameOverBackgroundFrame->h; // The gate should move down by the height of the view area to appear. int y_offset = (duration - ticks) * _viewArea.height() / duration; // Initial position is with the gate bottom at the view area bottom. int dx = _viewArea.left + (_viewArea.width() - gate_w) / 2; int dy = (_viewArea.bottom - gate_h) - y_offset; // Define destination rect for the full gate Common::Rect destRect(dx, dy, dx + gate_w, dy + gate_h); // Find intersection with view area to clip Common::Rect clippedDest = destRect.findIntersectingRect(_viewArea); // If there is something to draw if (clippedDest.isValidRect() && clippedDest.width() > 0 && clippedDest.height() > 0) { // Adjust source rect based on clipping int src_x = clippedDest.left - destRect.left; int src_y = clippedDest.top - destRect.top; Common::Rect clippedSrc(src_x, src_y, src_x + clippedDest.width(), src_y + clippedDest.height()); // Draw the clipped part surface->copyRectToSurfaceWithKey(*_gameOverBackgroundFrame, clippedDest.left, clippedDest.top, clippedSrc, keyColor); } } else { // Draw the gate fully down int gate_w = _gameOverBackgroundFrame->w; int gate_h = _gameOverBackgroundFrame->h; int dx = _viewArea.left + (_viewArea.width() - gate_w) / 2; int dy = (_viewArea.bottom - gate_h); surface->copyRectToSurfaceWithKey(*_gameOverBackgroundFrame, dx, dy, Common::Rect(0, 0, gate_w, gate_h), keyColor); } } Common::Error CastleEngine::saveGameStreamExtended(Common::WriteStream *stream, bool isAutosave) { stream->writeUint32LE(_keysCollected.size()); for (auto &it : _keysCollected) { stream->writeUint32LE(it); } stream->writeUint32LE(_spiritsMeter); for (auto &it : _areaMap) { stream->writeUint16LE(it._key); stream->writeUint32LE(_exploredAreas[it._key]); } return Common::kNoError; } Common::Error CastleEngine::loadGameStreamExtended(Common::SeekableReadStream *stream) { _keysCollected.clear(); int numberKeys = stream->readUint32LE(); for (int i = 0; i < numberKeys; i++) { _keysCollected.push_back(stream->readUint32LE()); } _spiritsMeter = stream->readUint32LE(); for (uint i = 0; i < _areaMap.size(); i++) { uint16 key = stream->readUint16LE(); _exploredAreas[key] = stream->readUint32LE(); } if (_useRockTravel) // Enable cheat setGameBit(k8bitGameBitTravelRock); for (auto &it : _areaMap) { it._value->resetAreaGroups(); } return Common::kNoError; } void CastleEngine::drawBackground() { clearBackground(); _gfx->drawBackground(_currentArea->_skyColor); if (_avoidRenderingFrames == 0 && _currentArea->isOutside()) { if (_background) { if (!_skyTexture) _skyTexture = _gfx->createTexture(_background->surfacePtr(), true); _gfx->drawSkybox(_skyTexture, _position); if (_thunderTextures.empty()) { for (auto &it : _thunderFrames ) { _thunderTextures.push_back(_gfx->createTexture(it->surfacePtr(), true)); } } updateThunder(); } } } void CastleEngine::updateThunder() { if (!_thunderFrames[0]) return; if (_thunderFrameDuration > 0) { //debug("Thunder frame duration: %d", _thunderFrameDuration); //debug("Size: %f", 2 * _thunderOffset.length()); //debug("Offset: %.1f, %.1f, %.1f", _thunderOffset.x(), _thunderOffset.y(), _thunderOffset.z()); _gfx->drawThunder(_thunderTextures[0], _position + _thunderOffset, 100); _thunderFrameDuration--; if (_thunderFrameDuration == 0) if (isSpectrum()) playSound(8, false, _soundFxHandle); return; } if (_thunderTicks > 0) { //debug("Thunder ticks: %d", _thunderTicks); _thunderTicks--; if (_thunderTicks <= 0) { _thunderFrameDuration = 10; } } else { // Schedule next thunder, between 10 and 10 + 10 seconds _thunderTicks = 50 * (10 + _rnd->getRandomNumber(10)); _thunderOffset = Math::Vector3d(); _thunderOffset.x() += (int(_rnd->getRandomNumber(100)) + 300); _thunderOffset.y() += int(_rnd->getRandomNumber(100)) + 50.0f; _thunderOffset.z() += (int(_rnd->getRandomNumber(100)) + 300); } } } // End of namespace Freescape