scummvm/engines/myst3/myst3.cpp

1679 lines
43 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the AUTHORS
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/error.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/util.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "gui/debugger.h"
#include "gui/error.h"
#include "engines/engine.h"
#include "engines/myst3/console.h"
#include "engines/myst3/database.h"
#include "engines/myst3/effects.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/nodecube.h"
#include "engines/myst3/nodeframe.h"
#include "engines/myst3/state.h"
#include "engines/myst3/cursor.h"
#include "engines/myst3/inventory.h"
#include "engines/myst3/script.h"
#include "engines/myst3/menu.h"
#include "engines/myst3/sound.h"
#include "engines/myst3/ambient.h"
#include "engines/myst3/transition.h"
#include "image/jpeg.h"
#include "graphics/conversion.h"
#include "graphics/yuv_to_rgb.h"
#include "math/vector2d.h"
namespace Myst3 {
enum MystLanguage {
kEnglish,
kDutch,
kFrench,
kGerman,
kItalian,
kSpanish
};
Myst3Engine::Myst3Engine(OSystem *syst, const Myst3GameDescription *version) :
Engine(syst), _system(syst), _gameDescription(version),
_db(0), _console(0), _scriptEngine(0),
_state(0), _node(0), _scene(0), _archiveNode(0),
_cursor(0), _inventory(0), _gfx(0), _menu(0),
_rnd(0), _sound(0), _ambient(0),
_inputSpacePressed(false), _inputEnterPressed(false),
_inputEscapePressed(false), _inputTildePressed(false),
_menuAction(0), _projectorBackground(0),
_shakeEffect(0), _rotationEffect(0), _backgroundSoundScriptLastRoomId(0),
_transition(0) {
DebugMan.addDebugChannel(kDebugVariable, "Variable", "Track Variable Accesses");
DebugMan.addDebugChannel(kDebugSaveLoad, "SaveLoad", "Track Save/Load Function");
DebugMan.addDebugChannel(kDebugScript, "Script", "Track Script Execution");
DebugMan.addDebugChannel(kDebugNode, "Node", "Track Node Changes");
// Add subdirectories to the search path to allow running from a full HDD install
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "bin");
SearchMan.addSubDirectoryMatching(gameDataDir, "M3Data");
SearchMan.addSubDirectoryMatching(gameDataDir, "M3Data/TEXT");
SearchMan.addSubDirectoriesMatching(gameDataDir, "EXILE Disc ?/Data", true);
// Win DVD version directories
SearchMan.addSubDirectoryMatching(gameDataDir, "English");
SearchMan.addSubDirectoryMatching(gameDataDir, "Data");
// Mac DVD version directories
SearchMan.addSubDirectoryMatching(gameDataDir, "Exile DVD");
SearchMan.addSubDirectoryMatching(gameDataDir, "Exile DVD/data");
// PS2 version directories
SearchMan.addSubDirectoryMatching(gameDataDir, "GAMEDATA");
SearchMan.addSubDirectoryMatching(gameDataDir, "GAMEDATA/WORLD");
SearchMan.addSubDirectoryMatching(gameDataDir, "GAMEDATA/WORLD/SOUND");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/DISCS");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/DISCS/DATA");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA/TEXT");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA/TEXT/NTSC");
SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA/TEXT/PAL");
settingsInitDefaults();
syncSoundSettings();
}
Myst3Engine::~Myst3Engine() {
DebugMan.clearAllDebugChannels();
closeArchives();
delete _menu;
delete _inventory;
delete _cursor;
delete _scene;
delete _archiveNode;
delete _db;
delete _scriptEngine;
delete _console;
delete _state;
delete _rnd;
delete _sound;
delete _ambient;
delete _gfx;
}
bool Myst3Engine::hasFeature(EngineFeature f) const {
// The TinyGL renderer does not support arbitrary resolutions for now
bool softRenderer = ConfMan.getBool("soft_renderer");
return
(f == kSupportsRTL) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsArbitraryResolutions && !softRenderer);
}
Common::Error Myst3Engine::run() {
if (!checkDatafiles()) {
// An error message has already been displayed
return Common::kUserCanceled;
}
bool softRenderer = ConfMan.getBool("soft_renderer");
if (softRenderer) {
_gfx = CreateGfxTinyGL(_system);
} else {
#if defined(USE_GLES2) || defined(USE_OPENGL_SHADERS)
_gfx = CreateGfxOpenGLShader(_system);
#elif defined(USE_OPENGL)
_gfx = CreateGfxOpenGL(_system);
#else
_gfx = CreateGfxTinyGL(_system);
#endif
}
_sound = new Sound(this);
_ambient = new Ambient(this);
_rnd = new Common::RandomSource("sprint");
_console = new Console(this);
_scriptEngine = new Script(this);
_db = new Database(this);
_state = new GameState(this);
_scene = new Scene(this);
_menu = new Menu(this);
_archiveNode = new Archive();
_system->showMouse(false);
openArchives();
_gfx->init();
_cursor = new Cursor(this);
_inventory = new Inventory(this);
// Init the font
Graphics::Surface *font = loadTexture(1206);
_gfx->initFont(font);
font->free();
delete font;
if (ConfMan.hasKey("save_slot")) {
// Load game from specified slot, if any
loadGameState(ConfMan.getInt("save_slot"));
} else {
// Game init script, loads the menu
loadNode(1, 101, 1);
}
while (!shouldQuit()) {
runNodeBackgroundScripts();
processInput(false);
updateCursor();
if (_menuAction) {
_menu->updateMainMenu(_menuAction);
_menuAction = 0;
}
drawFrame();
}
unloadNode();
_archiveNode->close();
_gfx->freeFont();
// Make sure the mouse is unlocked
_system->lockMouse(false);
return Common::kNoError;
}
bool Myst3Engine::addArchive(const Common::String &file, bool mandatory) {
Archive *archive = new Archive();
bool opened = archive->open(file.c_str(), 0);
if (opened) {
_archivesCommon.push_back(archive);
} else {
delete archive;
if (mandatory)
error("Unable to open archive %s", file.c_str());
}
return opened;
}
void Myst3Engine::openArchives() {
// The language of the menus is always the same as the executable
// The English CD version can only display English text
// The non English CD versions can display their localized language and English
// The DVD version can display 6 different languages
Common::String menuLanguage;
Common::String textLanguage;
switch (getDefaultLanguage()) {
case Common::NL_NLD:
menuLanguage = "DUTCH";
break;
case Common::FR_FRA:
menuLanguage = "FRENCH";
break;
case Common::DE_DEU:
menuLanguage = "GERMAN";
break;
case Common::IT_ITA:
menuLanguage = "ITALIAN";
break;
case Common::ES_ESP:
menuLanguage = "SPANISH";
break;
case Common::JA_JPN:
menuLanguage = "JAPANESE";
break;
case Common::PL_POL:
menuLanguage = "POLISH";
break;
case Common::EN_ANY:
case Common::RU_RUS:
default:
menuLanguage = "ENGLISH";
break;
}
if (getExecutableVersion()->flags & kFlagDVD) {
switch (ConfMan.getInt("text_language")) {
case kDutch:
textLanguage = "DUTCH";
break;
case kFrench:
textLanguage = "FRENCH";
break;
case kGerman:
textLanguage = "GERMAN";
break;
case kItalian:
textLanguage = "ITALIAN";
break;
case kSpanish:
textLanguage = "SPANISH";
break;
case kEnglish:
default:
textLanguage = "ENGLISH";
break;
}
} else {
if (isMonolingual() || ConfMan.getInt("text_language")) {
textLanguage = menuLanguage;
} else {
textLanguage = "ENGLISHjp";
}
}
if (getPlatform() == Common::kPlatformXbox) {
menuLanguage += "X";
textLanguage += "X";
}
// Load all the override files in the search path
Common::ArchiveMemberList overrides;
SearchMan.listMatchingMembers(overrides, "*.m3o");
for (Common::ArchiveMemberList::const_iterator it = overrides.begin(); it != overrides.end(); it++) {
addArchive(it->get()->getName(), false);
}
addArchive(textLanguage + ".m3t", true);
if ((getExecutableVersion()->flags & kFlagDVD) || !isMonolingual())
if (!addArchive("language.m3u", false))
addArchive(menuLanguage + ".m3u", true);
addArchive("RSRC.m3r", true);
}
void Myst3Engine::closeArchives() {
for (uint i = 0; i < _archivesCommon.size(); i++)
delete _archivesCommon[i];
_archivesCommon.clear();
}
bool Myst3Engine::checkDatafiles() {
#ifndef USE_SAFEDISC
if (getExecutableVersion()->safeDiskKey) {
static const char *safediscMessage =
_("This version of Myst III is encrypted with a copy-protection\n"
"preventing ResidualVM from reading required data.\n"
"Please replace your 'M3.exe' file with the one from the official update\n"
"corresponding to your game's language and redetect the game.\n"
"These updates don't contain the copy-protection and can be downloaded from\n"
"http://www.residualvm.org/downloads/");
warning("%s", safediscMessage);
GUI::displayErrorDialog(safediscMessage);
return false;
}
#endif // USE_SAFEDISC
return true;
}
bool Myst3Engine::isMonolingual() const {
return getDefaultLanguage() == Common::EN_ANY
|| getDefaultLanguage() == Common::RU_RUS;
}
HotSpot *Myst3Engine::getHoveredHotspot(NodePtr nodeData, uint16 var) {
_state->setHotspotHovered(false);
_state->setHotspotActiveRect(0);
if (_state->getViewType() == kCube) {
float pitch, heading;
_cursor->getDirection(pitch, heading);
for (uint j = 0; j < nodeData->hotspots.size(); j++) {
int32 hitRect = nodeData->hotspots[j].isPointInRectsCube(pitch, heading);
if (hitRect >= 0 && nodeData->hotspots[j].isEnabled(_state, var)) {
if (nodeData->hotspots[j].rects.size() > 1) {
_state->setHotspotHovered(true);
_state->setHotspotActiveRect(hitRect);
}
return &nodeData->hotspots[j];
}
}
} else {
Common::Point mouse = _cursor->getPosition();
if (_state->getViewType() == kFrame) {
mouse.y -= Renderer::kTopBorderHeight;
mouse.y = CLIP<uint>(mouse.y, 0, Renderer::kFrameHeight);
}
for (uint j = 0; j < nodeData->hotspots.size(); j++) {
int32 hitRect = nodeData->hotspots[j].isPointInRectsFrame(_state, mouse);
if (hitRect >= 0 && nodeData->hotspots[j].isEnabled(_state, var)) {
if (nodeData->hotspots[j].rects.size() > 1) {
_state->setHotspotHovered(true);
_state->setHotspotActiveRect(hitRect);
}
return &nodeData->hotspots[j];
}
}
}
return 0;
}
void Myst3Engine::updateCursor() {
NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom());
_state->setHotspotIgnoreClick(true);
HotSpot *hovered = getHoveredHotspot(nodeData);
_state->setHotspotIgnoreClick(false);
uint16 hoveredInventory = _inventory->hoveredItem();
if (hovered) {
_cursor->changeCursor(hovered->cursor);
} else if (isInventoryVisible() && hoveredInventory > 0) {
_cursor->changeCursor(1);
} else {
_cursor->changeCursor(8);
}
}
void Myst3Engine::processInput(bool lookOnly) {
// Process events
Common::Event event;
if (getPlatform() == Common::kPlatformXbox) {
// Reset the gamepad directions once they had a chance to be read by the scripts
// This combined with keyboard repeat ensures the menu does not scroll too fast
_state->setGamePadUpPressed(false);
_state->setGamePadDownPressed(false);
_state->setGamePadLeftPressed(false);
_state->setGamePadRightPressed(false);
}
while (getEventManager()->pollEvent(event)) {
if (getPlatform() == Common::kPlatformXbox) {
processEventForGamepad(event);
}
if (event.type == Common::EVENT_MOUSEMOVE) {
if (_state->getViewType() == kCube
&& _cursor->isPositionLocked()) {
_scene->updateCamera(event.relMouse);
}
_cursor->updatePosition(event.mouse);
} else if (event.type == Common::EVENT_LBUTTONDOWN) {
interactWithHoveredElement(lookOnly);
} else if (event.type == Common::EVENT_RBUTTONDOWN) {
// Skip the event when in look only mode
if (lookOnly)
continue;
// Nothing to do if not in cube view
if (_state->getViewType() != kCube)
continue;
// Don't unlock if the cursor is transparent
if (!_state->getCursorTransparency())
continue;
bool cursorLocked = _cursor->isPositionLocked();
_cursor->lockPosition(!cursorLocked);
} else if (event.type == Common::EVENT_KEYDOWN) {
// Save file name input
_menu->handleInput(event.kbd);
switch (event.kbd.keycode) {
case Common::KEYCODE_ESCAPE:
_inputEscapePressed = true;
// Open main menu
if (_cursor->isVisible()) {
if (_state->getLocationRoom() != 901)
_menu->goToNode(100);
else
_state->setMenuEscapePressed(1);
}
break;
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
_inputEnterPressed = true;
interactWithHoveredElement(lookOnly);
break;
case Common::KEYCODE_SPACE:
_inputSpacePressed = true;
break;
case Common::KEYCODE_TILDE:
_inputTildePressed = true;
break;
case Common::KEYCODE_d:
if (event.kbd.flags & Common::KBD_CTRL) {
_system->lockMouse(false);
_console->attach();
_console->onFrame();
_system->lockMouse(_cursor->isPositionLocked());
}
break;
default:
break;
}
} else if (event.type == Common::EVENT_KEYUP) {
switch (event.kbd.keycode) {
case Common::KEYCODE_ESCAPE:
_inputEscapePressed = false;
break;
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
_inputEnterPressed = false;
break;
case Common::KEYCODE_SPACE:
_inputSpacePressed = false;
break;
case Common::KEYCODE_TILDE:
_inputTildePressed = false;
break;
default:
break;
}
}
}
}
void Myst3Engine::processEventForGamepad(const Common::Event &event) {
if (event.type == Common::EVENT_LBUTTONDOWN) {
_state->setGamePadActionPressed(true);
} else if (event.type == Common::EVENT_LBUTTONUP) {
_state->setGamePadActionPressed(false);
} else if (event.type == Common::EVENT_KEYDOWN) {
switch (event.kbd.keycode) {
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
_state->setGamePadActionPressed(true);
break;
case Common::KEYCODE_UP:
_state->setGamePadUpPressed(true);
break;
case Common::KEYCODE_DOWN:
_state->setGamePadDownPressed(true);
break;
case Common::KEYCODE_LEFT:
_state->setGamePadLeftPressed(true);
break;
case Common::KEYCODE_RIGHT:
_state->setGamePadRightPressed(true);
break;
case Common::KEYCODE_ESCAPE:
_state->setGamePadCancelPressed(true);
break;
default:
break;
}
} else if (event.type == Common::EVENT_KEYUP) {
switch (event.kbd.keycode) {
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
_state->setGamePadActionPressed(false);
break;
case Common::KEYCODE_ESCAPE:
_state->setGamePadCancelPressed(false);
break;
default:
break;
}
}
}
void Myst3Engine::interactWithHoveredElement(bool lookOnly) {
// Skip the event when in look only mode
if (lookOnly)
return;
uint16 hoveredInventory = _inventory->hoveredItem();
if (isInventoryVisible() && hoveredInventory > 0) {
_inventory->useItem(hoveredInventory);
return;
}
NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom());
HotSpot *hovered = getHoveredHotspot(nodeData);
if (hovered) {
_scriptEngine->run(&hovered->script);
return;
}
// Bad click
_sound->playEffect(697, 5);
}
void Myst3Engine::drawFrame(bool noSwap) {
_sound->update();
_gfx->clear();
if (_state->getViewType() == kCube) {
float pitch = _state->getLookAtPitch();
float heading = _state->getLookAtHeading();
float fov = _state->getLookAtFOV();
// Apply the rotation effect
if (_rotationEffect) {
_rotationEffect->update();
heading += _rotationEffect->getHeadingOffset();
_state->lookAt(pitch, heading);
}
// Apply the shake effect
if (_shakeEffect) {
_shakeEffect->update();
pitch += _shakeEffect->getPitchOffset();
heading += _shakeEffect->getHeadingOffset();
}
_gfx->setupCameraPerspective(pitch, heading, fov);
} else {
_gfx->setupCameraOrtho2D(false);
}
if (_node) {
_node->update();
_node->draw();
}
for (int i = _movies.size() - 1; i >= 0 ; i--) {
_movies[i]->update();
_movies[i]->draw();
}
if (_state->getViewType() == kMenu) {
_menu->draw();
}
for (uint i = 0; i < _drawables.size(); i++) {
_drawables[i]->draw();
}
if (_state->getViewType() == kCube) {
_gfx->setupCameraOrtho2D(false);
}
if (_state->getViewType() != kMenu) {
float pitch = _state->getLookAtPitch();
float heading = _state->getLookAtHeading();
SunSpot flare = computeSunspotsIntensity(pitch, heading);
if (flare.intensity >= 0)
_scene->drawSunspotFlare(flare);
_scene->drawBlackBorders();
}
if (isInventoryVisible())
_inventory->draw();
// Draw overlay 2D movies
for (int i = _movies.size() - 1; i >= 0 ; i--) {
_movies[i]->drawOverlay();
}
for (uint i = 0; i < _drawables.size(); i++) {
_drawables[i]->drawOverlay();
}
// Draw spot subtitles
if (_node) {
_node->drawOverlay();
}
// The cursor is drawn unscaled
_gfx->setupCameraOrtho2D(true);
if (_cursor->isVisible())
_cursor->draw();
_gfx->flipBuffer();
if (!noSwap) {
_system->updateScreen();
_system->delayMillis(10);
_state->updateFrameCounters();
}
}
bool Myst3Engine::isInventoryVisible() {
if (_state->getViewType() == kMenu)
return false;
if (_node && _node->hasSubtitlesToDraw())
return false;
return true;
}
void Myst3Engine::setupTransition() {
delete _transition;
_transition = new Transition(this);
}
void Myst3Engine::drawTransition(TransitionType transitionType) {
if (_transition) {
_transition->draw(transitionType);
delete _transition;
_transition = 0;
}
}
void Myst3Engine::goToNode(uint16 nodeID, TransitionType transitionType) {
uint16 node = _state->getLocationNextNode();
if (node == 0)
node = nodeID;
uint16 room = _state->getLocationNextRoom();
uint16 age = _state->getLocationNextAge();
setupTransition();
ViewType sourceViewType = _state->getViewType();
if (sourceViewType == kCube) {
// The lookat direction in the next node should be
// the direction of the mouse cursor
float pitch, heading;
_cursor->getDirection(pitch, heading);
_state->lookAt(pitch, heading);
}
loadNode(node, room, age);
_state->setLocationNextNode(0);
_state->setLocationNextRoom(0);
_state->setLocationNextAge(0);
if (_state->getAmbiantPreviousFadeOutDelay() > 0) {
_ambient->playCurrentNode(100, _state->getAmbiantPreviousFadeOutDelay());
}
drawTransition(transitionType);
}
void Myst3Engine::loadNode(uint16 nodeID, uint32 roomID, uint32 ageID) {
unloadNode();
_scriptEngine->run(&_db->getNodeInitScript());
if (nodeID)
_state->setLocationNode(_state->valueOrVarValue(nodeID));
if (roomID)
_state->setLocationRoom(_state->valueOrVarValue(roomID));
else
roomID = _state->getLocationRoom();
if (ageID)
_state->setLocationAge(_state->valueOrVarValue(ageID));
char oldRoomName[8];
char newRoomName[8];
_db->getRoomName(oldRoomName);
_db->getRoomName(newRoomName, roomID);
if (strcmp(newRoomName, "JRNL") && strcmp(newRoomName, "XXXX")
&& strcmp(newRoomName, "MENU") && strcmp(newRoomName, oldRoomName)) {
_db->setCurrentRoom(roomID);
Common::String nodeFile = Common::String::format("%snodes.m3a", newRoomName);
_archiveNode->close();
if (!_archiveNode->open(nodeFile.c_str(), newRoomName)) {
error("Unable to open archive %s", nodeFile.c_str());
}
}
runNodeInitScripts();
// These effects can only be created after running the node init scripts
_shakeEffect = ShakeEffect::create(this);
_rotationEffect = RotationEffect::create(this);
// WORKAROUND: In Narayan, the scripts in node NACH 9 test on var 39
// without first reinitializing it leading to Saavedro not always giving
// Releeshan to the player when he is trapped between both shields.
if (nodeID == 9 && roomID == 801)
_state->setVar(39, 0);
}
void Myst3Engine::unloadNode() {
if (!_node)
return;
// Delete all movies
removeMovie(0);
// Remove all sunspots
for (uint i = 0; i < _sunspots.size(); i++)
delete _sunspots[i];
_sunspots.clear();
// Clean up the effects
delete _shakeEffect;
_shakeEffect = nullptr;
_state->setShakeEffectAmpl(0);
delete _rotationEffect;
_rotationEffect = nullptr;
delete _node;
_node = nullptr;
}
void Myst3Engine::runNodeInitScripts() {
NodePtr nodeData = _db->getNodeData(
_state->getLocationNode(),
_state->getLocationRoom(),
_state->getLocationAge());
NodePtr nodeDataInit = _db->getNodeData(
32765,
_state->getLocationRoom(),
_state->getLocationAge());
if (nodeDataInit)
runScriptsFromNode(32765);
if (!nodeData)
error("Node %d unknown in the database", _state->getLocationNode());
for (uint j = 0; j < nodeData->scripts.size(); j++) {
if (_state->evaluate(nodeData->scripts[j].condition)) {
_scriptEngine->run(&nodeData->scripts[j].script);
}
}
// Mark the node as a reachable zip destination
_state->markNodeAsVisited(
_state->getLocationNode(),
_state->getLocationRoom());
}
void Myst3Engine::runNodeBackgroundScripts() {
NodePtr nodeDataRoom = _db->getNodeData(32765, _state->getLocationRoom());
if (nodeDataRoom) {
for (uint j = 0; j < nodeDataRoom->hotspots.size(); j++) {
if (nodeDataRoom->hotspots[j].condition == -1) {
if (!_scriptEngine->run(&nodeDataRoom->hotspots[j].script))
break;
}
}
}
NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom());
for (uint j = 0; j < nodeData->hotspots.size(); j++) {
if (nodeData->hotspots[j].condition == -1) {
if (!_scriptEngine->run(&nodeData->hotspots[j].script))
break;
}
}
}
void Myst3Engine::loadNodeCubeFaces(uint16 nodeID) {
_state->setViewType(kCube);
_cursor->lockPosition(true);
updateCursor();
_node = new NodeCube(this, nodeID);
}
void Myst3Engine::loadNodeFrame(uint16 nodeID) {
_state->setViewType(kFrame);
_cursor->lockPosition(false);
updateCursor();
_node = new NodeFrame(this, nodeID);
}
void Myst3Engine::loadNodeMenu(uint16 nodeID) {
_state->setViewType(kMenu);
_cursor->lockPosition(false);
updateCursor();
_node = new NodeFrame(this, nodeID);
}
void Myst3Engine::runScriptsFromNode(uint16 nodeID, uint32 roomID, uint32 ageID) {
if (roomID == 0)
roomID = _state->getLocationRoom();
if (ageID == 0)
ageID = _state->getLocationAge();
NodePtr nodeData = _db->getNodeData(nodeID, roomID, ageID);
for (uint j = 0; j < nodeData->scripts.size(); j++) {
if (_state->evaluate(nodeData->scripts[j].condition)) {
if (!_scriptEngine->run(&nodeData->scripts[j].script))
break;
}
}
}
void Myst3Engine::runBackgroundSoundScriptsFromNode(uint16 nodeID, uint32 roomID, uint32 ageID) {
if (_state->getSoundScriptsSuspended())
return;
if (roomID == 0)
roomID = _state->getLocationRoom();
if (ageID == 0)
ageID = _state->getLocationAge();
NodePtr nodeData = _db->getNodeData(nodeID, roomID, ageID);
if (!nodeData) return;
// Stop previous music when changing room
if (_backgroundSoundScriptLastRoomId != roomID) {
if (_backgroundSoundScriptLastRoomId != 0
&& _backgroundSoundScriptLastRoomId != 901
&& _backgroundSoundScriptLastRoomId != 902
&& roomID != 0 && roomID != 901 && roomID != 902) {
_sound->stopMusic(_state->getSoundScriptFadeOutDelay());
if (nodeData->backgroundSoundScripts.size() != 0) {
_state->setSoundScriptsPaused(1);
_state->setSoundScriptsTimer(0);
}
}
_backgroundSoundScriptLastRoomId = roomID;
}
for (uint j = 0; j < nodeData->backgroundSoundScripts.size(); j++) {
if (_state->evaluate(nodeData->backgroundSoundScripts[j].condition)) {
if (!_scriptEngine->run(&nodeData->backgroundSoundScripts[j].script))
break;
}
}
}
void Myst3Engine::runAmbientScripts(uint32 node) {
uint32 room = _ambient->_scriptRoom;
uint32 age = _ambient->_scriptAge;
if (room == 0)
room = _state->getLocationRoom();
if (age == 0)
age = _state->getLocationAge();
NodePtr nodeData = _db->getNodeData(node, room, age);
if (!nodeData) return;
for (uint j = 0; j < nodeData->soundScripts.size(); j++)
if (_state->evaluate(nodeData->soundScripts[j].condition))
_scriptEngine->run(&nodeData->soundScripts[j].script);
}
void Myst3Engine::loadMovie(uint16 id, uint16 condition, bool resetCond, bool loop) {
ScriptedMovie *movie;
if (!_state->getMovieUseBackground()) {
movie = new ScriptedMovie(this, id);
} else {
movie = new ProjectorMovie(this, id, _projectorBackground);
_projectorBackground = 0;
_state->setMovieUseBackground(0);
}
movie->setCondition(condition);
movie->setDisableWhenComplete(resetCond);
movie->setLoop(loop);
if (_state->getMovieScriptDriven()) {
movie->setScriptDriven(_state->getMovieScriptDriven());
_state->setMovieScriptDriven(0);
}
if (_state->getMovieStartFrameVar()) {
movie->setStartFrameVar(_state->getMovieStartFrameVar());
_state->setMovieStartFrameVar(0);
}
if (_state->getMovieEndFrameVar()) {
movie->setEndFrameVar(_state->getMovieEndFrameVar());
_state->setMovieEndFrameVar(0);
}
if (_state->getMovieStartFrame()) {
movie->setStartFrame(_state->getMovieStartFrame());
_state->setMovieStartFrame(0);
}
if (_state->getMovieEndFrame()) {
movie->setEndFrame(_state->getMovieEndFrame());
_state->setMovieEndFrame(0);
}
if (_state->getMovieNextFrameGetVar()) {
movie->setNextFrameReadVar(_state->getMovieNextFrameGetVar());
_state->setMovieNextFrameGetVar(0);
}
if (_state->getMovieNextFrameSetVar()) {
movie->setNextFrameWriteVar(_state->getMovieNextFrameSetVar());
_state->setMovieNextFrameSetVar(0);
}
if (_state->getMoviePlayingVar()) {
movie->setPlayingVar(_state->getMoviePlayingVar());
_state->setMoviePlayingVar(0);
}
if (_state->getMovieOverridePosition()) {
movie->setPosU(_state->getMovieOverridePosU());
movie->setPosV(_state->getMovieOverridePosV());
_state->setMovieOverridePosition(0);
}
if (_state->getMovieUVar()) {
movie->setPosUVar(_state->getMovieUVar());
_state->setMovieUVar(0);
}
if (_state->getMovieVVar()) {
movie->setPosVVar(_state->getMovieVVar());
_state->setMovieVVar(0);
}
if (_state->getMovieOverrideCondition()) {
movie->setCondition(_state->getMovieOverrideCondition());
_state->setMovieOverrideCondition(0);
}
if (_state->getMovieConditionBit()) {
movie->setConditionBit(_state->getMovieConditionBit());
_state->setMovieConditionBit(0);
}
if (_state->getMovieForce2d()) {
movie->setForce2d(_state->getMovieForce2d());
_state->setMovieForce2d(0);
}
if (_state->getMovieVolume1()) {
movie->setVolume(_state->getMovieVolume1());
_state->setMovieVolume1(0);
} else {
movie->setVolume(_state->getMovieVolume2());
}
if (_state->getMovieVolumeVar()) {
movie->setVolumeVar(_state->getMovieVolumeVar());
_state->setMovieVolumeVar(0);
}
if (_state->getMovieSoundHeading()) {
movie->setSoundHeading(_state->getMovieSoundHeading());
_state->setMovieSoundHeading(0);
}
if (_state->getMoviePanningStrenght()) {
movie->setSoundAttenuation(_state->getMoviePanningStrenght());
_state->setMoviePanningStrenght(0);
}
if (_state->getMovieAdditiveBlending()) {
movie->setAdditiveBlending(true);
_state->setMovieAdditiveBlending(0);
}
if (_state->getMovieTransparency()) {
movie->setTransparency(_state->getMovieTransparency());
_state->setMovieTransparency(0);
} else {
movie->setTransparency(100);
}
if (_state->getMovieTransparencyVar()) {
movie->setTransparencyVar(_state->getMovieTransparencyVar());
_state->setMovieTransparencyVar(0);
}
_movies.push_back(movie);
}
void Myst3Engine::playSimpleMovie(uint16 id, bool fullframe) {
SimpleMovie movie = SimpleMovie(this, id);
if (_state->getMovieSynchronized()) {
movie.setSynchronized(_state->getMovieSynchronized());
_state->setMovieSynchronized(0);
}
if (_state->getMovieStartFrame()) {
movie.setStartFrame(_state->getMovieStartFrame());
_state->setMovieStartFrame(0);
}
if (_state->getMovieEndFrame()) {
movie.setEndFrame(_state->getMovieEndFrame());
_state->setMovieEndFrame(0);
}
if (_state->getMovieVolume1()) {
movie.setVolume(_state->getMovieVolume1());
_state->setMovieVolume1(0);
} else {
movie.setVolume(_state->getMovieVolume2());
}
if (fullframe) {
movie.setForce2d(_state->getViewType() == kCube);
movie.setForceOpaque(true);
movie.setPosU(0);
movie.setPosV(0);
}
movie.playStartupSound();
_drawables.push_back(&movie);
bool skip = false;
while (!skip && !shouldQuit() && movie.update()) {
// Process events
Common::Event event;
while (getEventManager()->pollEvent(event))
if (event.type == Common::EVENT_MOUSEMOVE) {
if (_state->getViewType() == kCube)
_scene->updateCamera(event.relMouse);
_cursor->updatePosition(event.mouse);
} else if (event.type == Common::EVENT_KEYDOWN) {
if (event.kbd.keycode == Common::KEYCODE_SPACE
|| event.kbd.keycode == Common::KEYCODE_ESCAPE)
skip = true;
}
drawFrame();
}
_drawables.pop_back();
// Reset the movie script so that the next movie will not try to run them
// when the user has skipped this one before the script is triggered.
_state->setMovieScriptStartFrame(0);
_state->setMovieScript(0);
_state->setMovieAmbiantScriptStartFrame(0);
_state->setMovieAmbiantScript(0);
}
void Myst3Engine::removeMovie(uint16 id) {
if (id == 0) {
for (uint i = 0; i < _movies.size(); i++)
delete _movies[i];
_movies.clear();
return;
} else {
for (uint i = 0; i < _movies.size(); i++)
if (_movies[i]->getId() == id) {
delete _movies[i];
_movies.remove_at(i);
break;
}
}
}
void Myst3Engine::setMovieLooping(uint16 id, bool loop) {
for (uint i = 0; i < _movies.size(); i++) {
if (_movies[i]->getId() == id) {
// Enable or disable looping
_movies[i]->setLoop(loop);
_movies[i]->setDisableWhenComplete(!loop);
break;
}
}
}
void Myst3Engine::addSpotItem(uint16 id, uint16 condition, bool fade) {
assert(_node);
_node->loadSpotItem(id, condition, fade);
}
SpotItemFace *Myst3Engine::addMenuSpotItem(uint16 id, uint16 condition, const Common::Rect &rect) {
assert(_node);
SpotItemFace *face = _node->loadMenuSpotItem(condition, rect);
if (id == 1)
_menu->setSaveLoadSpotItem(face);
return face;
}
void Myst3Engine::loadNodeSubtitles(uint32 id) {
assert(_node);
_node->loadSubtitles(id);
}
const DirectorySubEntry *Myst3Engine::getFileDescription(const char* room, uint32 index, uint16 face, DirectorySubEntry::ResourceType type) {
char currentRoom[8];
if (!room) {
_db->getRoomName(currentRoom, _state->getLocationRoom());
room = currentRoom;
}
const DirectorySubEntry *desc = 0;
// Search common archives
uint i = 0;
while (!desc && i < _archivesCommon.size()) {
desc = _archivesCommon[i]->getDescription(room, index, face, type);
i++;
}
// Search currently loaded node archive
if (!desc && _archiveNode)
desc = _archiveNode->getDescription(room, index, face, type);
return desc;
}
Graphics::Surface *Myst3Engine::loadTexture(uint16 id) {
const DirectorySubEntry *desc = getFileDescription("GLOB", id, 0, DirectorySubEntry::kRawData);
if (!desc)
error("Texture %d does not exist", id);
Common::MemoryReadStream *data = desc->getData();
uint32 magic = data->readUint32LE();
if (magic != MKTAG('.', 'T', 'E', 'X'))
error("Wrong texture format %d", id);
data->readUint32LE(); // unk 1
uint32 width = data->readUint32LE();
uint32 height = data->readUint32LE();
data->readUint32LE(); // unk 2
data->readUint32LE(); // unk 3
Graphics::Surface *s = new Graphics::Surface();
s->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0));
data->read(s->getPixels(), height * s->pitch);
delete data;
// ARGB => RGBA
s->convertToInPlace(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
return s;
}
Graphics::Surface *Myst3Engine::decodeJpeg(const DirectorySubEntry *jpegDesc) {
Common::MemoryReadStream *jpegStream = jpegDesc->getData();
Image::JPEGDecoder jpeg;
if (!jpeg.loadStream(*jpegStream))
error("Could not decode Myst III JPEG");
delete jpegStream;
const Graphics::Surface *bitmap = jpeg.getSurface();
return bitmap->convertTo(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
}
int16 Myst3Engine::openDialog(uint16 id) {
ButtonsDialog dialog(this, id);
_drawables.push_back(&dialog);
int16 result = -1;
while (result == -1 && !shouldQuit()) {
result = dialog.update();
drawFrame();
}
_drawables.pop_back();
return result;
}
void Myst3Engine::dragSymbol(uint16 var, uint16 id) {
DragItem drag(this, id);
_drawables.push_back(&drag);
_cursor->changeCursor(2);
_state->setVar(var, -1);
NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom());
while (inputValidatePressed() && !shouldQuit()) {
processInput(true);
HotSpot *hovered = getHoveredHotspot(nodeData, var);
drag.setFrame(hovered ? 2 : 1);
drawFrame();
}
_state->setVar(var, 1);
_drawables.pop_back();
HotSpot *hovered = getHoveredHotspot(nodeData, var);
if (hovered) {
_cursor->setVisible(false);
_scriptEngine->run(&hovered->script);
_cursor->setVisible(true);
}
}
void Myst3Engine::dragItem(uint16 statusVar, uint16 movie, uint16 frame, uint16 hoverFrame, uint16 itemVar) {
DragItem drag(this, movie);
_drawables.push_back(&drag);
_cursor->changeCursor(2);
_state->setVar(statusVar, 0);
_state->setVar(itemVar, 1);
NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom());
while (inputValidatePressed() && !shouldQuit()) {
processInput(true);
HotSpot *hovered = getHoveredHotspot(nodeData, itemVar);
drag.setFrame(hovered ? hoverFrame : frame);
drawFrame();
}
_drawables.pop_back();
HotSpot *hovered = getHoveredHotspot(nodeData, itemVar);
if (hovered) {
_cursor->setVisible(false);
_scriptEngine->run(&hovered->script);
_cursor->setVisible(true);
} else {
_state->setVar(statusVar, 1);
_state->setVar(itemVar, 0);
}
}
bool Myst3Engine::canLoadGameStateCurrently() {
return true;
}
Common::Error Myst3Engine::loadGameState(int slot) {
if (_state->load(_saveFileMan->listSavefiles("*.M3S")[slot])) {
_inventory->loadFromState();
_state->setLocationNextAge(_state->getMenuSavedAge());
_state->setLocationNextRoom(_state->getMenuSavedRoom());
_state->setLocationNextNode(_state->getMenuSavedNode());
_state->setMenuSavedAge(0);
_state->setMenuSavedRoom(0);
_state->setMenuSavedNode(0);
_sound->stopMusic(15);
_state->setSoundScriptsSuspended(0);
_sound->playEffect(696, 60);
goToNode(0, kTransitionNone);
return Common::kNoError;
}
return Common::kUnknownError;
}
void Myst3Engine::animateDirectionChange(float targetPitch, float targetHeading, uint16 scriptFrames) {
float startPitch = _state->getLookAtPitch();
float startHeading = _state->getLookAtHeading();
if (startPitch == targetPitch && startHeading == targetHeading)
return; // Fast path
float pitchDistance = targetPitch - startPitch;
float headingDistance = targetHeading - startHeading;
// Make sure to use the shortest direction
while (ABS(headingDistance) > 180) {
if (headingDistance > 0) {
headingDistance -= 360;
} else {
headingDistance += 360;
}
}
// Compute animation duration in frames
float numFrames;
if (scriptFrames) {
numFrames = scriptFrames;
} else {
numFrames = sqrt(pitchDistance * pitchDistance + headingDistance * headingDistance)
* 30.0f / _state->getCameraMoveSpeed();
if (numFrames > 0.0f)
numFrames += 10.0f;
}
uint startFrame = _state->getFrameCount();
// Draw animation
if (numFrames != 0.0f) {
while (1) {
uint elapsedFrames = _state->getFrameCount() - startFrame;
if (elapsedFrames >= numFrames)
break;
float step;
if (numFrames >= 15) {
// Fast then slow movement
if (elapsedFrames > numFrames / 2.0f)
step = 1.0f - (numFrames - elapsedFrames) * (numFrames - elapsedFrames)
/ (numFrames / 2.0f * numFrames / 2.0f) / 2.0f;
else
step = elapsedFrames * elapsedFrames / (numFrames / 2.0f * numFrames / 2.0f) / 2.0f;
} else {
// Constant speed movement
step = elapsedFrames / numFrames;
}
float nextPitch = startPitch + pitchDistance * step;
float nextHeading = startHeading + headingDistance * step;
_state->lookAt(nextPitch, nextHeading);
drawFrame();
}
}
_state->lookAt(targetPitch, targetHeading);
drawFrame();
}
void Myst3Engine::getMovieLookAt(uint16 id, bool start, float &pitch, float &heading) {
const DirectorySubEntry *desc = getFileDescription(0, id, 0, DirectorySubEntry::kMovie);
if (!desc)
desc = getFileDescription(0, id, 0, DirectorySubEntry::kMultitrackMovie);
if (!desc)
error("Movie %d does not exist", id);
Math::Vector3d v;
if (start)
v = desc->getVideoData().v1;
else
v = desc->getVideoData().v2;
Math::Vector2d horizontalProjection = Math::Vector2d(v.x(), v.z());
horizontalProjection.normalize();
pitch = 90 - Math::Angle::arcCosine(v.y()).getDegrees();
heading = Math::Angle::arcCosine(horizontalProjection.getY()).getDegrees();
if (horizontalProjection.getX() < 0.0) {
heading = 360 - heading;
}
}
void Myst3Engine::playMovieGoToNode(uint16 movie, uint16 node) {
uint16 room = _state->getLocationNextRoom();
uint16 age = _state->getLocationNextAge();
if (_state->getLocationNextNode() != 0) {
node = _state->getLocationNextNode();
}
if (_state->getViewType() == kCube && !_state->getCameraSkipAnimation()) {
float startPitch, startHeading;
getMovieLookAt(movie, true, startPitch, startHeading);
animateDirectionChange(startPitch, startHeading, 0);
}
_state->setCameraSkipAnimation(0);
loadNode(node, room, age);
playSimpleMovie(movie, true);
_state->setLocationNextNode(0);
_state->setLocationNextRoom(0);
_state->setLocationNextAge(0);
if (_state->getViewType() == kCube) {
float endPitch, endHeading;
getMovieLookAt(movie, false, endPitch, endHeading);
_state->lookAt(endPitch, endHeading);
}
}
void Myst3Engine::playMovieFullFrame(uint16 movie) {
if (_state->getViewType() == kCube && !_state->getCameraSkipAnimation()) {
float startPitch, startHeading;
getMovieLookAt(movie, true, startPitch, startHeading);
animateDirectionChange(startPitch, startHeading, 0);
}
_state->setCameraSkipAnimation(0);
playSimpleMovie(movie, true);
if (_state->getViewType() == kCube) {
float endPitch, endHeading;
getMovieLookAt(movie, false, endPitch, endHeading);
_state->lookAt(endPitch, endHeading);
}
setupTransition();
}
bool Myst3Engine::inputValidatePressed() {
return _inputEnterPressed ||
_inputSpacePressed ||
getEventManager()->getButtonState() & Common::EventManager::LBUTTON;
}
bool Myst3Engine::inputEscapePressed() {
return _inputEscapePressed;
}
bool Myst3Engine::inputSpacePressed() {
return _inputSpacePressed;
}
bool Myst3Engine::inputTilePressed() {
return _inputTildePressed;
}
void Myst3Engine::addSunSpot(uint16 pitch, uint16 heading, uint16 intensity,
uint16 color, uint16 var, bool varControlledIntensity, uint16 radius) {
SunSpot *s = new SunSpot();
s->pitch = pitch;
s->heading = heading;
s->intensity = intensity * 2.55;
s->color = (color & 0xF) | 16
* ((color & 0xF) | 16
* (((color >> 4) & 0xF) | 16
* (((color >> 4) & 0xF) | 16
* (((color >> 8) & 0xF) | 16
* (((color >> 8) & 0xF))))));
s->var = var;
s->variableIntensity = varControlledIntensity;
s->radius = radius;
_sunspots.push_back(s);
}
SunSpot Myst3Engine::computeSunspotsIntensity(float pitch, float heading) {
SunSpot result;
result.intensity = -1;
result.color = 0;
result.radius = 0;
for (uint i = 0; i < _sunspots.size(); i++) {
SunSpot *s = _sunspots[i];
uint32 value = _state->getVar(s->var);
// Skip disabled items
if (value == 0) continue;
float distance = _scene->distanceToZone(s->heading, s->pitch, s->radius, heading, pitch);
if (distance > result.radius) {
result.radius = distance;
result.color = s->color;
result.intensity = s->intensity;
result.variableIntensity = s->variableIntensity;
if (result.variableIntensity) {
result.radius = value * distance / 100;
}
}
}
return result;
}
void Myst3Engine::settingsInitDefaults() {
Common::Language executableLanguage = getDefaultLanguage();
int defaultLanguage;
switch (executableLanguage) {
case Common::NL_NLD:
defaultLanguage = kDutch;
break;
case Common::FR_FRA:
defaultLanguage = kFrench;
break;
case Common::DE_DEU:
defaultLanguage = kGerman;
break;
case Common::IT_ITA:
defaultLanguage = kItalian;
break;
case Common::ES_ESP:
defaultLanguage = kSpanish;
break;
case Common::EN_ANY:
default:
defaultLanguage = kEnglish;
break;
}
int defaultTextLanguage;
if (getExecutableVersion()->flags & kFlagDVD)
defaultTextLanguage = defaultLanguage;
else
defaultTextLanguage = executableLanguage != Common::EN_ANY;
ConfMan.registerDefault("overall_volume", Audio::Mixer::kMaxMixerVolume);
ConfMan.registerDefault("music_volume", Audio::Mixer::kMaxMixerVolume / 2);
ConfMan.registerDefault("music_frequency", 75);
ConfMan.registerDefault("audio_language", defaultLanguage);
ConfMan.registerDefault("text_language", defaultTextLanguage);
ConfMan.registerDefault("water_effects", true);
ConfMan.registerDefault("transition_speed", 50);
ConfMan.registerDefault("mouse_speed", 50);
ConfMan.registerDefault("zip_mode", false);
ConfMan.registerDefault("subtitles", false);
}
void Myst3Engine::settingsLoadToVars() {
_state->setOverallVolume(CLIP<uint>(ConfMan.getInt("overall_volume") * 100 / 256, 0, 100));
_state->setMusicVolume(CLIP<uint>(ConfMan.getInt("music_volume") * 100 / 256, 0, 100));
_state->setMusicFrequency(ConfMan.getInt("music_frequency"));
_state->setLanguageAudio(ConfMan.getInt("audio_language"));
_state->setLanguageText(ConfMan.getInt("text_language"));
_state->setWaterEffects(ConfMan.getBool("water_effects"));
_state->setTransitionSpeed(ConfMan.getInt("transition_speed"));
_state->setMouseSpeed(ConfMan.getInt("mouse_speed"));
_state->setZipModeEnabled(ConfMan.getBool("zip_mode"));
_state->setSubtitlesEnabled(ConfMan.getBool("subtitles"));
}
void Myst3Engine::settingsApplyFromVars() {
int32 oldTextLanguage = ConfMan.getInt("text_language");
ConfMan.setInt("overall_volume", _state->getOverallVolume() * 256 / 100);
ConfMan.setInt("music_volume", _state->getMusicVolume() * 256 / 100);
ConfMan.setInt("music_frequency", _state->getMusicFrequency());
ConfMan.setInt("audio_language", _state->getLanguageAudio());
ConfMan.setInt("text_language", _state->getLanguageText());
ConfMan.setBool("water_effects", _state->getWaterEffects());
ConfMan.setInt("transition_speed", _state->getTransitionSpeed());
ConfMan.setInt("mouse_speed", _state->getMouseSpeed());
ConfMan.setBool("zip_mode", _state->getZipModeEnabled());
ConfMan.setBool("subtitles", _state->getSubtitlesEnabled());
// Mouse speed may have changed, refresh it
_scene->updateMouseSpeed();
// The language changed, reload the correct archives
if (_state->getLanguageText() != oldTextLanguage) {
closeArchives();
openArchives();
}
syncSoundSettings();
}
void Myst3Engine::syncSoundSettings() {
Engine::syncSoundSettings();
uint soundOverall = ConfMan.getInt("overall_volume");
uint soundVolumeMusic = ConfMan.getInt("music_volume");
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundOverall);
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic * soundOverall / 256);
}
} // end of namespace Myst3