/* 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