scummvm/engines/grim/grim.cpp
Le Philousophe 1e4696f6d0 OPENGL: Rework renderer selection code
Add a class to group all renderer related (static) functions.
This allows to have getBestMatchingAvailableType inline in all engines.

The matching code is now shared between all engines but allows
customization for engines needing it (Grim, WME3D).

The new code takes runtime availability of features to select the
best renderer.
It avoid crashes when user choosed OpenGL but GLES2 is used.
2022-04-03 22:17:19 +02:00

1712 lines
48 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_fprintf
#define FORBIDDEN_SYMBOL_EXCEPTION_fgetc
#define FORBIDDEN_SYMBOL_EXCEPTION_stderr
#define FORBIDDEN_SYMBOL_EXCEPTION_stdin
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#include "common/archive.h"
#include "common/debug-channels.h"
#include "common/file.h"
#include "common/foreach.h"
#include "common/fs.h"
#include "common/config-manager.h"
#include "common/stuffit.h"
#include "common/translation.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/standard-actions.h"
#include "graphics/renderer.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/context.h"
#endif
#include "gui/error.h"
#include "gui/gui-manager.h"
#include "gui/message.h"
#include "image/png.h"
#include "engines/engine.h"
#include "engines/util.h"
#include "engines/grim/md5check.h"
#include "engines/grim/md5checkdialog.h"
#include "engines/grim/debug.h"
#include "engines/grim/grim.h"
#include "engines/grim/lua.h"
#include "engines/grim/lua_v1.h"
#include "engines/grim/emi/poolsound.h"
#include "engines/grim/emi/layer.h"
#include "engines/grim/actor.h"
#include "engines/grim/movie/movie.h"
#include "engines/grim/savegame.h"
#include "engines/grim/registry.h"
#include "engines/grim/resource.h"
#include "engines/grim/localize.h"
#include "engines/grim/gfx_base.h"
#include "engines/grim/bitmap.h"
#include "engines/grim/font.h"
#include "engines/grim/primitives.h"
#include "engines/grim/objectstate.h"
#include "engines/grim/set.h"
#include "engines/grim/sound.h"
#include "engines/grim/debugger.h"
#include "engines/grim/remastered/overlay.h"
#include "engines/grim/remastered/lua_remastered.h"
#include "engines/grim/remastered/commentary.h"
#include "engines/grim/imuse/imuse.h"
#include "engines/grim/emi/sound/emisound.h"
#include "engines/grim/lua/lua.h"
namespace Grim {
GrimEngine *g_grim = nullptr;
GfxBase *g_driver = nullptr;
int g_imuseState = -1;
GrimEngine::GrimEngine(OSystem *syst, uint32 gameFlags, GrimGameType gameType, Common::Platform platform, Common::Language language) :
Engine(syst), _currSet(nullptr), _selectedActor(nullptr), _pauseStartTime(0), _language(0) {
g_grim = this;
setDebugger(new Debugger());
_gameType = gameType;
_gameFlags = gameFlags;
_gamePlatform = platform;
_gameLanguage = language;
if (getGameType() == GType_GRIM)
g_registry = new Registry();
else
g_registry = nullptr;
g_resourceloader = nullptr;
g_localizer = nullptr;
g_movie = nullptr;
g_imuse = nullptr;
//Set default settings
ConfMan.registerDefault("use_arb_shaders", true);
_showFps = ConfMan.getBool("show_fps");
_softRenderer = true;
_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, 192);
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
_currSet = nullptr;
_selectedActor = nullptr;
_controlsEnabled = new bool[KEYCODE_EXTRA_LAST];
_controlsState = new bool[KEYCODE_EXTRA_LAST];
for (int i = 0; i < KEYCODE_EXTRA_LAST; i++) {
_controlsEnabled[i] = false;
_controlsState[i] = false;
}
_joyAxisPosition = new float[NUM_JOY_AXES];
for (int i = 0; i < NUM_JOY_AXES; i++) {
_joyAxisPosition[i] = 0;
}
_speechMode = TextAndVoice;
_textSpeed = 7;
_mode = _previousMode = NormalMode;
_flipEnable = true;
int speed = ConfMan.getInt("engine_speed");
if (speed == 0) {
_speedLimitMs = 0;
} else if (speed < 0 || speed > 100) {
_speedLimitMs = 1000 / 60;
ConfMan.setInt("engine_speed", 1000 / _speedLimitMs);
} else {
_speedLimitMs = 1000 / speed;
}
_listFilesIter = nullptr;
_savedState = nullptr;
_fps[0] = 0;
_iris = new Iris();
_buildActiveActorsList = false;
_justSaveLoaded = false;
Color c(0, 0, 0);
_printLineDefaults.setX(0);
_printLineDefaults.setY(100);
_printLineDefaults.setWidth(0);
_printLineDefaults.setHeight(0);
_printLineDefaults.setFGColor(c);
_printLineDefaults.setFont(nullptr);
_printLineDefaults.setJustify(TextObject::LJUSTIFY);
_sayLineDefaults.setX(0);
_sayLineDefaults.setY(100);
_sayLineDefaults.setWidth(0);
_sayLineDefaults.setHeight(0);
_sayLineDefaults.setFGColor(c);
_sayLineDefaults.setFont(nullptr);
_sayLineDefaults.setJustify(TextObject::CENTER);
_blastTextDefaults.setX(0);
_blastTextDefaults.setY(200);
_blastTextDefaults.setWidth(0);
_blastTextDefaults.setHeight(0);
_blastTextDefaults.setFGColor(c);
_blastTextDefaults.setFont(nullptr);
_blastTextDefaults.setJustify(TextObject::LJUSTIFY);
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "movies"); // Add 'movies' subdirectory for the demo
SearchMan.addSubDirectoryMatching(gameDataDir, "credits");
SearchMan.addSubDirectoryMatching(gameDataDir, "widescreen");
//Remastered:
if (isRemastered()) {
for (uint32 i = 0; i < kNumCutscenes; i++) {
_cutsceneEnabled[i] = false;
}
for (uint32 i = 0; i < kNumConcepts; i++) {
_conceptEnabled[i] = false;
}
_saveMeta1 = "";
_saveMeta2 = 0;
_saveMeta3 = "";
}
_commentary = nullptr;
}
GrimEngine::~GrimEngine() {
delete[] _controlsEnabled;
delete[] _controlsState;
delete[] _joyAxisPosition;
clearPools();
delete LuaBase::instance();
if (g_registry) {
g_registry->save();
delete g_registry;
g_registry = nullptr;
}
delete g_movie;
g_movie = nullptr;
delete g_imuse;
g_imuse = nullptr;
delete g_emiSound;
g_emiSound = nullptr;
delete g_sound;
g_sound = nullptr;
delete g_localizer;
g_localizer = nullptr;
delete g_resourceloader;
g_resourceloader = nullptr;
delete g_driver;
g_driver = nullptr;
delete _iris;
// Remastered:
delete _commentary;
ConfMan.flushToDisk();
g_grim = nullptr;
}
void GrimEngine::clearPools() {
Set::getPool().deleteObjects();
Actor::getPool().deleteObjects();
PrimitiveObject::getPool().deleteObjects();
TextObject::getPool().deleteObjects();
Bitmap::getPool().deleteObjects();
Font::getPool().deleteObjects();
ObjectState::getPool().deleteObjects();
_currSet = nullptr;
}
LuaBase *GrimEngine::createLua() {
if (isRemastered()) {
return new Lua_Remastered();
} else {
return new Lua_V1();
}
}
GfxBase *GrimEngine::createRenderer(int screenW, int screenH) {
Common::String rendererConfig = ConfMan.get("renderer");
Graphics::RendererType desiredRendererType = Graphics::Renderer::parseTypeCode(rendererConfig);
uint32 availableRendererTypes = Graphics::Renderer::getAvailableTypes();
availableRendererTypes &=
#if defined(USE_OPENGL_GAME)
Graphics::kRendererTypeOpenGL |
#endif
#if defined(USE_OPENGL_SHADERS)
Graphics::kRendererTypeOpenGLShaders |
#endif
#if defined(USE_TINYGL)
Graphics::kRendererTypeTinyGL |
#endif
0;
// For Grim Fandango, OpenGL renderer without shaders is preferred if available
if (desiredRendererType == Graphics::kRendererTypeDefault &&
(availableRendererTypes & Graphics::kRendererTypeOpenGL) &&
getGameType() == GType_GRIM) {
availableRendererTypes &= ~Graphics::kRendererTypeOpenGLShaders;
}
Graphics::RendererType matchingRendererType = Graphics::Renderer::getBestMatchingType(desiredRendererType, availableRendererTypes);
_softRenderer = matchingRendererType == Graphics::kRendererTypeTinyGL;
if (!_softRenderer) {
initGraphics3d(screenW, screenH);
} else {
initGraphics(screenW, screenH, nullptr);
}
GfxBase *renderer = nullptr;
#if defined(USE_OPENGL_SHADERS)
if (matchingRendererType == Graphics::kRendererTypeOpenGLShaders) {
renderer = CreateGfxOpenGLShader();
}
#endif
#if defined(USE_OPENGL_GAME)
if (matchingRendererType == Graphics::kRendererTypeOpenGL) {
renderer = CreateGfxOpenGL();
}
#endif
#if defined(USE_TINYGL)
if (matchingRendererType == Graphics::kRendererTypeTinyGL) {
renderer = CreateGfxTinyGL();
}
#endif
if (!renderer) {
/* We should never end up here, getBestMatchingRendererType would have failed before */
error("Unable to create a renderer");
}
renderer->setupScreen(screenW, screenH);
renderer->loadEmergFont();
return renderer;
}
const char *GrimEngine::getUpdateFilename() {
if (!(getGameFlags() & ADGF_DEMO))
return "gfupd101.exe";
else
return nullptr;
}
Common::Error GrimEngine::run() {
// Try to see if we have the EMI Mac installer present
// Currently, this requires the data fork to be standalone
if (getGameType() == GType_MONKEY4) {
if (SearchMan.hasFile("Monkey Island 4 Installer")) {
Common::Archive *archive = Common::createStuffItArchive("Monkey Island 4 Installer");
if (archive)
SearchMan.add("Monkey Island 4 Installer", archive, 0, true);
}
if (SearchMan.hasFile("EFMI Installer")) {
Common::Archive *archive = Common::createStuffItArchive("EFMI Installer");
if (archive)
SearchMan.add("EFMI Installer", archive, 0, true);
}
}
ConfMan.registerDefault("check_gamedata", true);
if (ConfMan.getBool("check_gamedata") && !isRemastered()) {
MD5CheckDialog d;
if (!d.runModal()) {
Common::U32String confirmString = Common::U32String::format(_(
"ScummVM found some problems with your game data files.\n"
"Running ScummVM nevertheless may cause game bugs or even crashes.\n"
"Do you still want to run %s?"),
GType_MONKEY4 == getGameType() ? "Escape From Monkey Island" : "Grim Fandango"
);
GUI::MessageDialog msg(confirmString, _("Yes"), _("No"));
if (msg.runModal() != GUI::kMessageOK) {
return Common::kUserCanceled;
}
}
ConfMan.setBool("check_gamedata", false);
ConfMan.flushToDisk();
}
g_resourceloader = new ResourceLoader();
bool demo = getGameFlags() & ADGF_DEMO;
if (getGameType() == GType_GRIM)
g_movie = CreateSmushPlayer(demo);
else if (getGameType() == GType_MONKEY4) {
if (_gamePlatform == Common::kPlatformPS2)
g_movie = CreateMpegPlayer();
else
g_movie = CreateBinkPlayer(demo);
}
if (getGameType() == GType_GRIM) {
g_imuse = new Imuse(20, demo);
g_emiSound = nullptr;
if (g_grim->isRemastered()) {
// This must happen here, since we need the resource loader set up.
_commentary = new Commentary();
}
} else if (getGameType() == GType_MONKEY4) {
g_emiSound = new EMISound(20);
g_imuse = nullptr;
}
g_sound = new SoundPlayer();
if (getGameType() == GType_GRIM && g_grim->isRemastered()) {
g_driver = createRenderer(1600, 900);
} else {
g_driver = createRenderer(640, 480);
}
if (getGameType() == GType_MONKEY4 && SearchMan.hasFile("AMWI.m4b")) {
// Play EMI Mac Aspyr logo
playAspyrLogo();
}
Bitmap *splash_bm = nullptr;
if (!(_gameFlags & ADGF_DEMO) && getGameType() == GType_GRIM)
splash_bm = Bitmap::create("splash.bm");
else if ((_gameFlags & ADGF_DEMO) && getGameType() == GType_MONKEY4)
splash_bm = Bitmap::create("splash.til");
else if (getGamePlatform() == Common::kPlatformPS2 && getGameType() == GType_MONKEY4)
splash_bm = Bitmap::create("load.tga");
g_driver->clearScreen();
if (splash_bm != nullptr)
splash_bm->draw();
// This flipBuffer() may make the OpenGL renderer show garbage instead of the splash,
// while the TinyGL renderer needs it.
if (_softRenderer)
g_driver->flipBuffer();
LuaBase *lua = createLua();
lua->registerOpcodes();
lua->registerLua();
// One version of the demo doesn't set the demo flag in scripts.
if (getGameType() == GType_GRIM && _gameFlags & ADGF_DEMO) {
lua->forceDemo();
}
//Initialize Localizer first. In system-script are already localizeable Strings
g_localizer = new Localizer();
lua->loadSystemScript();
lua->boot();
_savegameLoadRequest = false;
_savegameSaveRequest = false;
// Load game from specified slot, if any
if (ConfMan.hasKey("save_slot")) {
loadGameState(ConfMan.getInt("save_slot"));
}
g_grim->setMode(NormalMode);
delete splash_bm;
g_grim->mainLoop();
return Common::kNoError;
}
Common::KeymapArray GrimEngine::initKeymapsGrim(const char *target) {
using namespace Common;
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "grim", "Grim Fandango");
Action *act;
act = new Action(kStandardActionMoveUp, _("Up"));
act->setKeyEvent(KEYCODE_UP);
act->addDefaultInputMapping("JOY_UP");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMoveDown, _("Down"));
act->setKeyEvent(KEYCODE_DOWN);
act->addDefaultInputMapping("JOY_DOWN");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMoveLeft, _("Left"));
act->setKeyEvent(KEYCODE_LEFT);
act->addDefaultInputMapping("JOY_LEFT");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMoveRight, _("Right"));
act->setKeyEvent(KEYCODE_RIGHT);
act->addDefaultInputMapping("JOY_RIGHT");
engineKeyMap->addAction(act);
act = new Action("BRUN", _("Run"));
act->setKeyEvent(KeyState(KEYCODE_LSHIFT));
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
engineKeyMap->addAction(act);
act = new Action("EXAM", _("Examine"));
act->setKeyEvent(KeyState(KEYCODE_e, 'e'));
act->addDefaultInputMapping("JOY_X");
engineKeyMap->addAction(act);
act = new Action("BUSE", _("Use/Talk"));
act->setKeyEvent(KeyState(KEYCODE_u, 'u'));
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action("PICK", _("Pick up/Put away"));
act->setKeyEvent(KeyState(KEYCODE_p, 'p'));
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
act = new Action("INVT", _("Inventory"));
act->setKeyEvent(KeyState(KEYCODE_i, 'i'));
act->addDefaultInputMapping("JOY_Y");
engineKeyMap->addAction(act);
act = new Action("SKLI", _("Skip dialog lines"));
act->setKeyEvent(KeyState(KEYCODE_PERIOD, '.'));
act->addDefaultInputMapping("PERIOD");
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action(kStandardActionSkip, _("Skip"));
act->setKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE));
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
act = new Action("RETURN", _("Confirm"));
act->setKeyEvent(KeyState(KEYCODE_RETURN, ASCII_RETURN));
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action("GMNU", _("Menu"));
act->setKeyEvent(KeyState(KEYCODE_F1));
act->addDefaultInputMapping("JOY_GUIDE");
engineKeyMap->addAction(act);
act = new Action("PAUSE", _("Pause"));
act->setKeyEvent(KeyState(KEYCODE_PAUSE));
engineKeyMap->addAction(act);
return Keymap::arrayOf(engineKeyMap);
}
Common::KeymapArray GrimEngine::initKeymapsEMI(const char *target) {
using namespace Common;
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "monkey4", "Escape from the Monkey Island");
Action *act;
act = new Action(kStandardActionMoveUp, _("Up"));
act->setKeyEvent(KEYCODE_UP);
act->addDefaultInputMapping("JOY_UP");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMoveDown, _("Down"));
act->setKeyEvent(KEYCODE_DOWN);
act->addDefaultInputMapping("JOY_DOWN");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMoveLeft, _("Left"));
act->setKeyEvent(KEYCODE_LEFT);
act->addDefaultInputMapping("JOY_LEFT");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMoveRight, _("Right"));
act->setKeyEvent(KEYCODE_RIGHT);
act->addDefaultInputMapping("JOY_RIGHT");
engineKeyMap->addAction(act);
act = new Action("COUP", _("Cycle Objects Up"));
act->setKeyEvent(KeyState(KEYCODE_PAGEUP));
act->addDefaultInputMapping("JOY_LEFT_TRIGGER");
engineKeyMap->addAction(act);
act = new Action("CODW", _("Cycle Objects Down"));
act->setKeyEvent(KeyState(KEYCODE_PAGEDOWN));
act->addDefaultInputMapping("JOY_RIGHT_TRIGGER");
engineKeyMap->addAction(act);
act = new Action("BRUN", _("Run"));
act->setKeyEvent(KeyState(KEYCODE_LSHIFT));
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
engineKeyMap->addAction(act);
act = new Action("QEXT", _("Quick Room Exit"));
act->setKeyEvent(KeyState(KEYCODE_o, 'o'));
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
engineKeyMap->addAction(act);
act = new Action("EXAM", _("Examine/Look"));
act->setKeyEvent(KeyState(KEYCODE_e, 'e'));
act->addDefaultInputMapping("JOY_X");
engineKeyMap->addAction(act);
act = new Action("BUSE", _("Use/Talk"));
act->setKeyEvent(KeyState(KEYCODE_u, 'u'));
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action("PICK", _("Pick up/Put away"));
act->setKeyEvent(KeyState(KEYCODE_KP_PLUS, '+'));
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
act = new Action("INVT", _("Inventory"));
act->setKeyEvent(KeyState(KEYCODE_INSERT, 'i'));
act->addDefaultInputMapping("JOY_Y");
engineKeyMap->addAction(act);
act = new Action("SKLI", _("Skip dialog lines"));
act->setKeyEvent(KeyState(KEYCODE_PERIOD, '.'));
act->addDefaultInputMapping("PERIOD");
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action(kStandardActionSkip, _("Skip"));
act->setKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE));
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
act = new Action("RETURN", _("Confirm"));
act->setKeyEvent(KeyState(KEYCODE_RETURN, ASCII_RETURN));
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action("GMNU", _("Menu"));
act->setKeyEvent(KeyState(KEYCODE_F1, 0));
act->addDefaultInputMapping("JOY_GUIDE");
engineKeyMap->addAction(act);
return Keymap::arrayOf(engineKeyMap);
}
void GrimEngine::playAspyrLogo() {
// A trimmed down version of the code found in mainloop
// for the purpose of playing the Aspyr-logo.
// The reason for this, is that the logo needs a different
// codec than all the other videos (which are Bink).
// Code is provided to keep within the fps-limit, as well as to
// allow for pressing ESC to skip the movie.
MoviePlayer *defaultPlayer = g_movie;
g_movie = CreateQuickTimePlayer();
g_movie->play("AMWI.m4b", false, 0, 0);
setMode(SmushMode);
while (g_movie->isPlaying()) {
_doFlip = true;
uint32 startTime = g_system->getMillis();
updateDisplayScene();
if (_doFlip) {
doFlip();
}
// Process events to allow the user to skip the logo.
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
// Ignore everything but ESC when movies are playing
Common::EventType type = event.type;
if (type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) {
g_movie->stop();
break;
}
}
uint32 endTime = g_system->getMillis();
if (startTime > endTime)
continue;
uint32 diffTime = endTime - startTime;
if (_speedLimitMs == 0)
continue;
if (diffTime < _speedLimitMs) {
uint32 delayTime = _speedLimitMs - diffTime;
g_system->delayMillis(delayTime);
}
}
delete g_movie;
setMode(NormalMode);
g_movie = defaultPlayer;
}
Common::Error GrimEngine::loadGameState(int slot) {
assert(slot >= 0);
if (getGameType() == GType_MONKEY4) {
if (getGamePlatform() == Common::kPlatformPS2) {
_savegameFileName = Common::String::format("efmi%03d.ps2", slot);
} else {
_savegameFileName = Common::String::format("efmi%03d.gsv", slot);
}
} else {
_savegameFileName = Common::String::format("grim%02d.gsv", slot);
}
_savegameLoadRequest = true;
return Common::kNoError;
}
void GrimEngine::handlePause() {
if (!LuaBase::instance()->callback("pauseHandler")) {
error("handlePause: invalid handler");
}
}
void GrimEngine::handleExit() {
if (!LuaBase::instance()->callback("exitHandler")) {
error("handleExit: invalid handler");
}
}
void GrimEngine::handleUserPaint() {
if (!LuaBase::instance()->callback("userPaintHandler")) {
error("handleUserPaint: invalid handler");
}
}
void GrimEngine::cameraChangeHandle(int prev, int next) {
LuaObjects objects;
objects.add(prev);
objects.add(next);
LuaBase::instance()->callback("camChangeHandler", objects);
}
void GrimEngine::cameraPostChangeHandle(int num) {
LuaObjects objects;
objects.add(num);
LuaBase::instance()->callback("postCamChangeHandler", objects);
}
void GrimEngine::savegameCallback() {
if (!LuaBase::instance()->callback("saveGameCallback")) {
error("GrimEngine::savegameCallback: invalid handler");
}
}
void GrimEngine::handleDebugLoadResource() {
void *resource = nullptr;
int c, i = 0;
char buf[513];
// Tool for debugging the loading of a particular resource without
// having to actually make it all the way to it in the game
fprintf(stderr, "Enter resource to load (extension specifies type): ");
while (i < 512 && (c = fgetc(stdin)) != EOF && c != '\n')
buf[i++] = c;
buf[i] = '\0';
if (strstr(buf, ".key"))
resource = (void *)g_resourceloader->loadKeyframe(buf);
else if (strstr(buf, ".zbm") || strstr(buf, ".bm"))
resource = (void *)Bitmap::create(buf);
else if (strstr(buf, ".cmp"))
resource = (void *)g_resourceloader->loadColormap(buf);
else if (strstr(buf, ".cos"))
resource = (void *)g_resourceloader->loadCostume(buf, nullptr, nullptr);
else if (strstr(buf, ".lip"))
resource = (void *)g_resourceloader->loadLipSync(buf);
else if (strstr(buf, ".snm"))
resource = (void *)g_movie->play(buf, false, 0, 0);
else if (strstr(buf, ".wav") || strstr(buf, ".imu")) {
if (g_imuse)
g_imuse->startSfx(buf);
resource = (void *)1;
} else if (strstr(buf, ".mat")) {
CMap *cmap = g_resourceloader->loadColormap("item.cmp");
warning("Default colormap applied to resources loaded in this fashion");
// Default to repeating the texture as in GRIM
resource = (void *)g_resourceloader->loadMaterial(buf, cmap, false);
} else {
warning("Resource type not understood");
}
if (!resource)
warning("Requested resouce (%s) not found", buf);
}
void GrimEngine::drawTextObjects() {
foreach (TextObject *t, TextObject::getPool()) {
t->draw();
}
}
void GrimEngine::playIrisAnimation(Iris::Direction dir, int x, int y, int time) {
_iris->play(dir, x, y, time);
}
void GrimEngine::luaUpdate() {
if (_savegameLoadRequest || _savegameSaveRequest || _changeHardwareState)
return;
// Update timing information
unsigned newStart = g_system->getMillis();
if (newStart < _frameStart) {
_frameStart = newStart;
return;
}
_frameTime = newStart - _frameStart;
_frameStart = newStart;
if (_mode == PauseMode || _shortFrame) {
_frameTime = 0;
}
LuaBase::instance()->update(_frameTime, _movieTime);
if (_currSet && (_mode == NormalMode || _mode == SmushMode)) {
// call updateTalk() before calling update(), since it may modify costumes state, and
// the costumes are updated in update().
for (Common::List<Actor *>::iterator i = _talkingActors.begin(); i != _talkingActors.end(); ++i) {
Actor *a = *i;
if (!a->updateTalk(_frameTime)) {
i = _talkingActors.reverse_erase(i);
}
}
// Update the actors. Do it here so that we are sure to react asap to any change
// in the actors state caused by lua.
buildActiveActorsList();
foreach (Actor *a, _activeActors) {
// Note that the actor need not be visible to update chores, for example:
// when Manny has just brought Meche back he is offscreen several times
// when he needs to perform certain chores
a->update(_frameTime);
}
_iris->update(_frameTime);
foreach (TextObject *t, TextObject::getPool()) {
t->update();
}
}
}
void GrimEngine::updateDisplayScene() {
_doFlip = true;
if (_mode == SmushMode) {
if (g_movie->isPlaying()) {
_movieTime = g_movie->getMovieTime();
if (g_movie->isUpdateNeeded()) {
g_driver->prepareMovieFrame(g_movie->getDstSurface());
g_movie->clearUpdateNeeded();
}
int frame = g_movie->getFrame();
if (frame >= 0) {
if (frame != _prevSmushFrame) {
_prevSmushFrame = g_movie->getFrame();
g_driver->drawMovieFrame(g_movie->getX(), g_movie->getY());
if (_showFps)
g_driver->drawEmergString(550, 25, _fps, Color(255, 255, 255));
} else
_doFlip = false;
} else
g_driver->releaseMovieFrame();
}
// Draw Primitives
_iris->draw();
g_movie->drawMovieSubtitle();
} else if (_mode == NormalMode || _mode == OverworldMode) {
updateNormalMode();
} else if (_mode == DrawMode) {
updateDrawMode();
}
}
void GrimEngine::updateNormalMode() {
if (!_currSet || !_flipEnable)
return;
g_driver->clearScreen();
drawNormalMode();
_iris->draw();
drawTextObjects();
}
void GrimEngine::updateDrawMode() {
_doFlip = false;
_prevSmushFrame = 0;
_movieTime = 0;
}
void GrimEngine::drawNormalMode() {
_prevSmushFrame = 0;
_movieTime = 0;
_currSet->drawBackground();
// Draw underlying scene components
// Background objects are drawn underneath everything except the background
// There are a bunch of these, especially in the tube-switcher room
_currSet->drawBitmaps(ObjectState::OBJSTATE_BACKGROUND);
// State objects are drawn on top of other things, such as the flag
// on Manny's message tube
_currSet->drawBitmaps(ObjectState::OBJSTATE_STATE);
// Play SMUSH Animations
// This should occur on top of all underlying scene objects,
// a good example is the tube switcher room where some state objects
// need to render underneath the animation or you can't see what's going on
// This should not occur on top of everything though or Manny gets covered
// up when he's next to Glottis's service room
if (g_movie->isPlaying() && _movieSetup == _currSet->getCurrSetup()->_name) {
_movieTime = g_movie->getMovieTime();
if (g_movie->isUpdateNeeded()) {
g_driver->prepareMovieFrame(g_movie->getDstSurface());
g_movie->clearUpdateNeeded();
}
if (g_movie->getFrame() >= 0)
g_driver->drawMovieFrame(g_movie->getX(), g_movie->getY());
else
g_driver->releaseMovieFrame();
}
// Underlay objects must be drawn on top of movies
// Otherwise the lighthouse door will always be open as the underlay for
// the closed door will be overdrawn by a movie used as background image.
_currSet->drawBitmaps(ObjectState::OBJSTATE_UNDERLAY);
// Draw Primitives
foreach (PrimitiveObject *p, PrimitiveObject::getPool()) {
p->draw();
}
foreach (Overlay *p, Overlay::getPool()) {
p->draw();
}
_currSet->setupCamera();
g_driver->set3DMode();
if (_setupChanged) {
cameraPostChangeHandle(_currSet->getSetup());
_setupChanged = false;
}
// Draw actors
buildActiveActorsList();
foreach (Actor *a, _activeActors) {
if (a->isVisible())
a->draw();
}
flagRefreshShadowMask(false);
// Draw overlying scene components
// The overlay objects should be drawn on top of everything else,
// including 3D objects such as Manny and the message tube
_currSet->drawBitmaps(ObjectState::OBJSTATE_OVERLAY);
}
void GrimEngine::doFlip() {
_frameCounter++;
if (!_doFlip) {
return;
}
if (_showFps && _mode != DrawMode)
g_driver->drawEmergString(550, 25, _fps, Color(255, 255, 255));
if (_flipEnable)
g_driver->flipBuffer();
if (_showFps && _mode != DrawMode) {
unsigned int currentTime = g_system->getMillis();
unsigned int delta = currentTime - _lastFrameTime;
if (delta > 500) {
snprintf(_fps, sizeof(_fps), "%7.2f", (double)(_frameCounter * 1000) / (double)delta);
_frameCounter = 0;
_lastFrameTime = currentTime;
}
}
}
void GrimEngine::mainLoop() {
_movieTime = 0;
_frameTime = 0;
_frameStart = g_system->getMillis();
_frameCounter = 0;
_lastFrameTime = 0;
_prevSmushFrame = 0;
_refreshShadowMask = false;
_shortFrame = false;
bool resetShortFrame = false;
_changeHardwareState = false;
_setupChanged = true;
for (;;) {
uint32 startTime = g_system->getMillis();
if (_shortFrame) {
if (resetShortFrame) {
_shortFrame = false;
}
resetShortFrame = !resetShortFrame;
}
if (shouldQuit())
return;
if (_savegameLoadRequest) {
savegameRestore();
}
if (_savegameSaveRequest) {
savegameSave();
}
// If the backend destroys the OpenGL context or the user switched to a different
// renderer, the GFX driver needs to be recreated.
if (_changeHardwareState) {
_changeHardwareState = false;
uint screenWidth = g_driver->getScreenWidth();
uint screenHeight = g_driver->getScreenHeight();
EngineMode mode = getMode();
_savegameFileName = "";
savegameSave();
clearPools();
delete g_driver;
g_driver = createRenderer(screenWidth, screenHeight);
savegameRestore();
if (mode == DrawMode) {
setMode(GrimEngine::NormalMode);
updateDisplayScene();
g_driver->storeDisplay();
g_driver->dimScreen();
}
setMode(mode);
}
g_sound->flushTracks();
if (g_imuse) {
g_imuse->refreshScripts();
}
// Process events
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
// Handle any buttons, keys and joystick operations
Common::EventType type = event.type;
if (type == Common::EVENT_KEYDOWN || type == Common::EVENT_KEYUP) {
if (type == Common::EVENT_KEYDOWN) {
// Ignore everything but ESC when movies are playing
// This matches the retail and demo versions of EMI
// This also allows the PS2 version to skip movies
if (_mode == SmushMode && g_grim->getGameType() == GType_MONKEY4) {
if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
g_movie->stop();
break;
}
continue;
}
if (_mode != DrawMode && _mode != SmushMode && (event.kbd.ascii == 'q')) {
handleExit();
break;
} else if (_mode != DrawMode && (event.kbd.keycode == Common::KEYCODE_PAUSE)) {
handlePause();
break;
} else {
handleChars(type, event.kbd);
}
}
handleControls(type, event.kbd);
if (getGameType() != GType_MONKEY4) {
// Allow lua to react to the event.
// Without this lua_update switching the entries in the menu is slow because
// if the button is not kept pressed the KEYUP will arrive just after the KEYDOWN
// and it will break the lua scripts that checks for the state of the button
// with GetControlState()
//
// This call seems to be only necessary to handle Grim's menu correctly.
// In EMI it would have the side-effect that luaUpdate() is sometimes called
// in the same millisecond which causes getPerSecond() to return 0 for
// any given rate which is not compatible with e.g. actor walking.
// We do not want the scripts to update while a movie is playing in the PS2-version.
if (!(getGamePlatform() == Common::kPlatformPS2 && _mode == SmushMode)) {
luaUpdate();
}
}
}
if (type == Common::EVENT_JOYAXIS_MOTION)
handleJoyAxis(event.joystick.axis, event.joystick.position);
if (type == Common::EVENT_JOYBUTTON_DOWN || type == Common::EVENT_JOYBUTTON_UP)
handleJoyButton(type, event.joystick.button);
if (type == Common::EVENT_LBUTTONUP) {
_cursorX = event.mouse.x;
_cursorY = event.mouse.y;
Common::KeyState k;
k.keycode = (Common::KeyCode)KEYCODE_MOUSE_B1;
handleControls(Common::EVENT_KEYUP, k);
}
if (type == Common::EVENT_LBUTTONDOWN) {
_cursorX = event.mouse.x;
_cursorY = event.mouse.y;
Common::KeyState k;
k.keycode = (Common::KeyCode)KEYCODE_MOUSE_B1;
handleControls(Common::EVENT_KEYDOWN, k);
}
if (type == Common::EVENT_MOUSEMOVE) {
_cursorX = event.mouse.x;
_cursorY = event.mouse.y;
handleMouseAxis(0, _cursorX);
handleMouseAxis(1, _cursorY);
}
if (type == Common::EVENT_SCREEN_CHANGED) {
handleUserPaint();
}
}
if (_mode != PauseMode) {
// Draw the display scene before doing the luaUpdate.
// This give a large performance boost as OpenGL stores commands
// in a queue on the gpu to be rendered later. When doFlip is
// called the cpu must wait for the gpu to finish its queue.
// Now, it will queue all the OpenGL commands and draw them on the
// GPU while the CPU is busy updating the game world.
updateDisplayScene();
}
if (_mode != PauseMode) {
doFlip();
}
// We do not want the scripts to update while a movie is playing in the PS2-version.
if (!(getGamePlatform() == Common::kPlatformPS2 && _mode == SmushMode)) {
luaUpdate();
}
if (g_imuseState != -1) {
g_sound->setMusicState(g_imuseState);
g_imuseState = -1;
}
#if defined(__EMSCRIPTEN__)
// If SDL_HINT_EMSCRIPTEN_ASYNCIFY is enabled, SDL pauses the application and gives
// back control to the browser automatically by calling emscripten_sleep via SDL_Delay.
// Without this the page would completely lock up.
g_system->delayMillis(0);
#endif
uint32 endTime = g_system->getMillis();
if (startTime > endTime)
continue;
uint32 diffTime = endTime - startTime;
if (_speedLimitMs == 0)
continue;
if (diffTime < _speedLimitMs) {
uint32 delayTime = _speedLimitMs - diffTime;
g_system->delayMillis(delayTime);
}
}
}
void GrimEngine::changeHardwareState() {
_changeHardwareState = true;
}
void GrimEngine::saveGame(const Common::String &file) {
_savegameFileName = file;
_savegameSaveRequest = true;
}
void GrimEngine::loadGame(const Common::String &file) {
_savegameFileName = file;
_savegameLoadRequest = true;
}
void GrimEngine::savegameRestore() {
debug("GrimEngine::savegameRestore() started.");
_savegameLoadRequest = false;
Common::String filename;
if (_savegameFileName.size() == 0) {
filename = "grim.sav";
} else {
filename = _savegameFileName;
}
_savedState = SaveGame::openForLoading(filename);
if (!_savedState || !_savedState->isCompatible())
return;
if (g_imuse) {
g_imuse->stopAllSounds();
g_imuse->resetState();
}
g_movie->stop();
if (g_imuse)
g_imuse->pause(true);
g_movie->pause(true);
if (g_registry)
g_registry->save();
_selectedActor = nullptr;
delete _currSet;
_currSet = nullptr;
Bitmap::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "Bitmaps restored successfully.");
Font::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "Fonts restored successfully.");
ObjectState::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "ObjectStates restored successfully.");
Set::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "Sets restored successfully.");
TextObject::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "TextObjects restored successfully.");
PrimitiveObject::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "PrimitiveObjects restored successfully.");
Actor::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "Actors restored successfully.");
if (getGameType() == GType_MONKEY4) {
PoolSound::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "Pool sounds saved successfully.");
Layer::getPool().restoreObjects(_savedState);
Debug::debug(Debug::Engine, "Layers restored successfully.");
}
restoreGRIM();
Debug::debug(Debug::Engine, "Engine restored successfully.");
g_driver->restoreState(_savedState);
Debug::debug(Debug::Engine, "Renderer restored successfully.");
g_sound->restoreState(_savedState);
Debug::debug(Debug::Engine, "iMuse restored successfully.");
g_movie->restoreState(_savedState);
Debug::debug(Debug::Engine, "Movie restored successfully.");
_iris->restoreState(_savedState);
Debug::debug(Debug::Engine, "Iris restored successfully.");
lua_Restore(_savedState);
Debug::debug(Debug::Engine, "Lua restored successfully.");
delete _savedState;
_justSaveLoaded = true;
//Re-read the values, since we may have been in some state that changed them when loading the savegame,
//e.g. running a cutscene, which sets the sfx volume to 0.
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
LuaBase::instance()->postRestoreHandle();
if (g_imuse)
g_imuse->pause(false);
g_movie->pause(false);
debug("GrimEngine::savegameRestore() finished.");
_shortFrame = true;
clearEventQueue();
invalidateActiveActorsList();
buildActiveActorsList();
_currSet->setupCamera();
g_driver->set3DMode();
}
void GrimEngine::restoreGRIM() {
_savedState->beginSection('GRIM');
_mode = (EngineMode)_savedState->readLEUint32();
_previousMode = (EngineMode)_savedState->readLEUint32();
// Actor stuff
int32 id = _savedState->readLESint32();
if (id != 0) {
_selectedActor = Actor::getPool().getObject(id);
}
//TextObject stuff
_sayLineDefaults.setFGColor(_savedState->readColor());
_sayLineDefaults.setFont(Font::getPool().getObject(_savedState->readLESint32()));
_sayLineDefaults.setHeight(_savedState->readLESint32());
_sayLineDefaults.setJustify(_savedState->readLESint32());
_sayLineDefaults.setWidth(_savedState->readLESint32());
_sayLineDefaults.setX(_savedState->readLESint32());
_sayLineDefaults.setY(_savedState->readLESint32());
_sayLineDefaults.setDuration(_savedState->readLESint32());
if (_savedState->saveMinorVersion() > 5) {
_movieSubtitle = TextObject::getPool().getObject(_savedState->readLESint32());
}
// Set stuff
_currSet = Set::getPool().getObject(_savedState->readLESint32());
if (_savedState->saveMinorVersion() > 4) {
_movieSetup = _savedState->readString();
} else {
_movieSetup = _currSet->getCurrSetup()->_name;
}
_savedState->endSection();
}
void GrimEngine::storeSaveGameMetadata(SaveGame *state) {
if (!g_grim->isRemastered()) {
return;
}
state->beginSection('META');
state->writeString(_saveMeta1);
state->writeLEUint32(_saveMeta2);
state->writeString(_saveMeta3);
state->endSection();
}
void GrimEngine::storeSaveGameImage(SaveGame *state) {
const Graphics::PixelFormat image_format = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
int width = 250, height = 188;
Bitmap *screenshot;
debug("GrimEngine::StoreSaveGameImage() started.");
screenshot = g_driver->getScreenshot(width, height, true);
state->beginSection('SIMG');
if (screenshot) {
int size = screenshot->getWidth() * screenshot->getHeight();
screenshot->setActiveImage(0);
screenshot->getBitmapData()->convertToColorFormat(image_format);
const uint16 *data = (const uint16 *)screenshot->getData().getPixels();
for (int l = 0; l < size; l++) {
state->writeLEUint16(data[l]);
}
} else {
error("Unable to store screenshot");
}
state->endSection();
delete screenshot;
debug("GrimEngine::StoreSaveGameImage() finished.");
}
void GrimEngine::savegameSave() {
debug("GrimEngine::savegameSave() started.");
_savegameSaveRequest = false;
Common::String filename;
if (_savegameFileName.size() == 0) {
filename = "grim.sav";
} else {
filename = _savegameFileName;
}
if (getGameType() == GType_MONKEY4 && filename.contains('/')) {
filename = Common::lastPathComponent(filename, '/');
}
_savedState = SaveGame::openForSaving(filename);
if (!_savedState) {
//TODO: Translate this!
GUI::displayErrorDialog(_("Error: the game could not be saved."));
return;
}
storeSaveGameMetadata(_savedState);
storeSaveGameImage(_savedState);
if (g_imuse)
g_imuse->pause(true);
g_movie->pause(true);
savegameCallback();
Bitmap::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "Bitmaps saved successfully.");
Font::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "Fonts saved successfully.");
ObjectState::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "ObjectStates saved successfully.");
Set::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "Sets saved successfully.");
TextObject::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "TextObjects saved successfully.");
PrimitiveObject::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "PrimitiveObjects saved successfully.");
Actor::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "Actors saved successfully.");
if (getGameType() == GType_MONKEY4) {
PoolSound::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "Pool sounds saved successfully.");
Layer::getPool().saveObjects(_savedState);
Debug::debug(Debug::Engine, "Layers saved successfully.");
}
saveGRIM();
Debug::debug(Debug::Engine, "Engine saved successfully.");
g_driver->saveState(_savedState);
Debug::debug(Debug::Engine, "Renderer saved successfully.");
g_sound->saveState(_savedState);
Debug::debug(Debug::Engine, "iMuse saved successfully.");
g_movie->saveState(_savedState);
Debug::debug(Debug::Engine, "Movie saved successfully.");
_iris->saveState(_savedState);
Debug::debug(Debug::Engine, "Iris saved successfully.");
lua_Save(_savedState);
delete _savedState;
if (g_imuse)
g_imuse->pause(false);
g_movie->pause(false);
debug("GrimEngine::savegameSave() finished.");
_shortFrame = true;
clearEventQueue();
}
void GrimEngine::saveGRIM() {
_savedState->beginSection('GRIM');
_savedState->writeLEUint32((uint32)_mode);
_savedState->writeLEUint32((uint32)_previousMode);
//Actor stuff
if (_selectedActor) {
_savedState->writeLESint32(_selectedActor->getId());
} else {
_savedState->writeLESint32(0);
}
//TextObject stuff
_savedState->writeColor(_sayLineDefaults.getFGColor());
_savedState->writeLESint32(_sayLineDefaults.getFont()->getId());
_savedState->writeLESint32(_sayLineDefaults.getHeight());
_savedState->writeLESint32(_sayLineDefaults.getJustify());
_savedState->writeLESint32(_sayLineDefaults.getWidth());
_savedState->writeLESint32(_sayLineDefaults.getX());
_savedState->writeLESint32(_sayLineDefaults.getY());
_savedState->writeLESint32(_sayLineDefaults.getDuration());
_savedState->writeLESint32(_movieSubtitle ? _movieSubtitle->getId() : 0);
//Set stuff
_savedState->writeLESint32(_currSet->getId());
_savedState->writeString(_movieSetup);
_savedState->endSection();
}
Set *GrimEngine::findSet(const Common::String &name) {
// Find scene object
foreach (Set *s, Set::getPool()) {
if (s->getName() == name)
return s;
}
return nullptr;
}
void GrimEngine::setSetLock(const char *name, bool lockStatus) {
Set *scene = findSet(name);
if (!scene) {
Debug::warning(Debug::Engine, "Set object '%s' not found in list", name);
return;
}
// Change the locking status
scene->_locked = lockStatus;
}
Set *GrimEngine::loadSet(const Common::String &name) {
Set *s = findSet(name);
if (!s) {
Common::String filename(name);
// EMI-scripts refer to their .setb files as .set
if (g_grim->getGameType() == GType_MONKEY4) {
filename += "b";
}
Common::SeekableReadStream *stream;
stream = g_resourceloader->openNewStreamFile(filename.c_str());
if (!stream)
error("Could not find scene file %s", name.c_str());
s = new Set(name, stream);
delete stream;
}
return s;
}
void GrimEngine::setSet(const char *name) {
setSet(loadSet(name));
}
void GrimEngine::setSet(Set *scene) {
if (scene == _currSet)
return;
if (getGameType() == GType_MONKEY4) {
foreach (PoolSound *s, PoolSound::getPool()) {
s->stop();
}
}
// Stop the actors. This fixes bug #289 (https://github.com/residualvm/residualvm/issues/289)
// and it makes sense too, since when changing set the directions
// and coords change too.
foreach (Actor *a, Actor::getPool()) {
a->stopWalking();
}
Set *lastSet = _currSet;
_currSet = scene;
_currSet->setSoundParameters(20, 127);
// should delete the old scene after setting the new one
if (lastSet && !lastSet->_locked) {
delete lastSet;
}
_shortFrame = true;
_setupChanged = true;
invalidateActiveActorsList();
}
void GrimEngine::makeCurrentSetup(int num) {
int prevSetup = g_grim->getCurrSet()->getSetup();
if (prevSetup != num) {
getCurrSet()->setSetup(num);
getCurrSet()->setSoundParameters(20, 127);
cameraChangeHandle(prevSetup, num);
// here should be set sound position
_setupChanged = true;
}
}
void GrimEngine::setTextSpeed(int speed) {
if (speed < 1)
_textSpeed = 1;
if (speed > 10)
_textSpeed = 10;
_textSpeed = speed;
}
float GrimEngine::getControlAxis(int num) {
int idx = num - KEYCODE_AXIS_JOY1_X;
if (idx >= 0 && idx < NUM_JOY_AXES) {
return _joyAxisPosition[idx];
}
return 0;
}
bool GrimEngine::getControlState(int num) {
return _controlsState[num];
}
float GrimEngine::getPerSecond(float rate) const {
return rate * _frameTime / 1000;
}
void GrimEngine::invalidateActiveActorsList() {
_buildActiveActorsList = true;
}
void GrimEngine::immediatelyRemoveActor(Actor *actor) {
_activeActors.remove(actor);
_talkingActors.remove(actor);
}
void GrimEngine::buildActiveActorsList() {
if (!_buildActiveActorsList) {
return;
}
_activeActors.clear();
foreach (Actor *a, Actor::getPool()) {
if (((_mode == NormalMode || _mode == DrawMode) && a->isDrawableInSet(_currSet->getName())) ||
a->isInOverworld()) {
_activeActors.push_back(a);
}
}
_buildActiveActorsList = false;
}
void GrimEngine::addTalkingActor(Actor *a) {
_talkingActors.push_back(a);
}
bool GrimEngine::areActorsTalking() const {
//This takes into account that there may be actors which are still talking, but in the background.
bool talking = false;
foreach (Actor *a, _talkingActors) {
if (a->isTalkingForeground()) {
talking = true;
break;
}
}
return talking;
}
void GrimEngine::setMovieSubtitle(TextObject *to) {
if (_movieSubtitle != to) {
delete _movieSubtitle;
_movieSubtitle = to;
}
}
void GrimEngine::drawMovieSubtitle() {
if (_movieSubtitle)
_movieSubtitle->draw();
}
void GrimEngine::setMovieSetup() {
_movieSetup = _currSet->getCurrSetup()->_name;
}
void GrimEngine::setMode(EngineMode mode) {
_mode = mode;
invalidateActiveActorsList();
}
void GrimEngine::clearEventQueue() {
g_system->getEventManager()->purgeKeyboardEvents();
g_system->getEventManager()->purgeMouseEvents();
for (int i = 0; i < KEYCODE_EXTRA_LAST; ++i) {
_controlsState[i] = false;
}
}
bool GrimEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime);
}
void GrimEngine::pauseEngineIntern(bool pause) {
if (g_imuse)
g_imuse->pause(pause);
if (g_movie)
g_movie->pause(pause);
if (pause) {
_pauseStartTime = _system->getMillis();
} else {
_frameStart += _system->getMillis() - _pauseStartTime;
}
}
Graphics::Surface *loadPNG(const Common::String &filename) {
Image::PNGDecoder d;
Common::SeekableReadStream *s = SearchMan.createReadStreamForMember(filename);
if (!s)
return nullptr;
d.loadStream(*s);
delete s;
Graphics::Surface *srf = d.getSurface()->convertTo(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
return srf;
}
void GrimEngine::debugLua(const Common::String &str) {
lua_dostring(str.c_str());
}
Common::String GrimEngine::getLanguagePrefix() const {
switch (getLanguage()) {
case 0:
return Common::String("en");
case 1:
return Common::String("de");
case 2:
return Common::String("es");
case 3:
return Common::String("fr");
case 4:
return Common::String("it");
case 5:
return Common::String("pt");
default:
error("Unknown language id %d", getLanguage());
}
}
bool GrimEngine::isConceptEnabled(uint32 number) const {
assert (number < kNumConcepts);
return _conceptEnabled[number];
}
void GrimEngine::enableConcept(uint32 number) {
assert (number < kNumConcepts);
_conceptEnabled[number] = true;
}
bool GrimEngine::isCutsceneEnabled(uint32 number) const {
assert (number < kNumCutscenes);
return _cutsceneEnabled[number];
}
void GrimEngine::enableCutscene(uint32 number) {
assert (number < kNumCutscenes);
_cutsceneEnabled[number] = true;
}
void GrimEngine::setSaveMetaData(const char *meta1, int meta2, const char *meta3) {
_saveMeta1 = meta1;
_saveMeta2 = meta2;
_saveMeta3 = meta3;
}
} // end of namespace Grim