scummvm/engines/twine/twine.cpp

1194 lines
36 KiB
C++
Raw Normal View History

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "twine/twine.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/savefile.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "engines/metaengine.h"
#include "engines/util.h"
#include "graphics/colormasks.h"
#include "graphics/cursorman.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
2020-10-21 14:47:25 +02:00
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "graphics/pixelformat.h"
#include "graphics/surface.h"
#include "gui/debugger.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/debugger/console.h"
#include "twine/debugger/debug.h"
#include "twine/debugger/debug_grid.h"
#include "twine/debugger/debug_scene.h"
#include "twine/detection.h"
#include "twine/flamovies.h"
#include "twine/holomap.h"
2020-10-23 13:44:38 +02:00
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/menu/menuoptions.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
2020-12-24 15:04:57 +01:00
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
2020-12-24 15:04:57 +01:00
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/script/script_life_v1.h"
#include "twine/script/script_move_v1.h"
#include "twine/text.h"
namespace TwinE {
ScopedEngineFreeze::ScopedEngineFreeze(TwinEEngine *engine) : _engine(engine) {
2020-11-02 23:09:27 +01:00
_engine->freezeTime();
}
ScopedEngineFreeze::~ScopedEngineFreeze() {
_engine->unfreezeTime();
}
ScopedCursor::ScopedCursor(TwinEEngine *engine) : _engine(engine) {
_engine->pushMouseCursorVisible();
}
ScopedCursor::~ScopedCursor() {
_engine->popMouseCursorVisible();
}
2020-12-15 20:52:20 +01:00
ScopedFPS::ScopedFPS(uint32 fps) : _fps(fps) {
_start = g_system->getMillis();
}
ScopedFPS::~ScopedFPS() {
const uint32 end = g_system->getMillis();
const uint32 frameTime = end - _start;
const uint32 maxDelay = 1000 / _fps;
if (frameTime > maxDelay) {
return;
}
const uint32 waitMillis = maxDelay - frameTime;
g_system->delayMillis(waitMillis);
}
2021-01-09 18:42:25 +01:00
FrameMarker::~FrameMarker() {
g_system->updateScreen();
}
TwinEEngine::TwinEEngine(OSystem *system, Common::Language language, uint32 flags, TwineGameType gameType)
: Engine(system), _gameType(gameType), _gameLang(language), _gameFlags(flags), _rnd("twine") {
// Add default file directories
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "fla");
SearchMan.addSubDirectoryMatching(gameDataDir, "vox");
if (flags & TF_DOTEMU_ENHANCED) {
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/hqr");
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/fla");
if (_gameLang == Common::Language::DE_DEU) {
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/vox/de_voice");
}
if (_gameLang == Common::Language::EN_ANY || _gameLang == Common::Language::EN_GRB || _gameLang == Common::Language::EN_USA) {
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/vox/en_voice");
}
if (_gameLang == Common::Language::FR_FRA) {
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/vox/fr_voice");
}
#ifdef USE_MAD
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/music");
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/midi_mp3");
#endif
#ifdef USE_VORBIS
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/music/ogg");
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/midi_mp3/ogg");
#endif
}
setDebugger(new TwinEConsole(this));
_actor = new Actor(this);
_animations = new Animations(this);
_collision = new Collision(this);
_extra = new Extra(this);
_gameState = new GameState(this);
_grid = new Grid(this);
_movements = new Movements(this);
_interface = new Interface(this);
_menu = new Menu(this);
_flaMovies = new FlaMovies(this);
_menuOptions = new MenuOptions(this);
_music = new Music(this);
_redraw = new Redraw(this);
_renderer = new Renderer(this);
_resources = new Resources(this);
_scene = new Scene(this);
_screens = new Screens(this);
_scriptLife = new ScriptLife(this);
_scriptMove = new ScriptMove(this);
_holomap = new Holomap(this);
_sound = new Sound(this);
_text = new Text(this);
_debugGrid = new DebugGrid(this);
2020-10-23 13:44:38 +02:00
_input = new Input(this);
_debug = new Debug(this);
_debugScene = new DebugScene(this);
}
TwinEEngine::~TwinEEngine() {
2021-02-11 17:04:48 +01:00
ConfMan.flushToDisk();
delete _actor;
delete _animations;
delete _collision;
delete _extra;
delete _gameState;
delete _grid;
delete _movements;
delete _interface;
delete _menu;
delete _flaMovies;
2020-11-17 17:37:53 +01:00
delete _menuOptions;
delete _music;
delete _redraw;
delete _renderer;
delete _resources;
delete _scene;
delete _screens;
delete _scriptLife;
delete _scriptMove;
delete _holomap;
delete _sound;
delete _text;
delete _debugGrid;
2020-10-23 13:44:38 +02:00
delete _input;
delete _debug;
delete _debugScene;
}
void TwinEEngine::pushMouseCursorVisible() {
++_mouseCursorState;
if (!cfgfile.Mouse) {
return;
}
if (_mouseCursorState == 1) {
CursorMan.showMouse(cfgfile.Mouse);
}
}
void TwinEEngine::popMouseCursorVisible() {
--_mouseCursorState;
if (_mouseCursorState == 0) {
CursorMan.showMouse(false);
}
}
Common::Error TwinEEngine::run() {
2020-10-30 15:13:53 +01:00
debug("Based on TwinEngine v0.2.2");
2021-01-01 00:00:18 +01:00
debug("(c) 2002 The TwinEngine team.");
debug("(c) 2020, 2021 The ScummVM team.");
debug("Refer to the credits for further details.");
debug("The original Little Big Adventure game is:");
2021-01-01 00:00:18 +01:00
debug("(c) 1994 by Adeline Software International, All Rights Reserved.");
syncSoundSettings();
2021-01-07 22:54:49 +01:00
int32 w = 640;
int32 h = 480;
if (ConfMan.hasKey("usehighres")) {
const bool highRes = ConfMan.getBool("usehighres");
if (highRes) {
w = 1024;
h = 768;
}
}
initGraphics(w, h);
allocVideoMemory(w, h);
initAll();
initEngine();
_sound->stopSamples();
_screens->copyScreen(frontVideoBuffer, workVideoBuffer);
_menu->init();
_holomap->loadLocations();
if (ConfMan.hasKey("save_slot")) {
const int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot >= 0 && saveSlot <= 999) {
Common::Error state = loadGameState(saveSlot);
if (state.getCode() != Common::kNoError) {
return state;
}
}
}
while (!shouldQuit()) {
2020-11-01 19:22:34 +01:00
readKeys();
switch (_state) {
case EngineState::QuitGame: {
Common::Event event;
event.type = Common::EVENT_QUIT;
_system->getEventManager()->pushEvent(event);
break;
}
case EngineState::LoadedGame:
debug("Loaded game");
if (_scene->newHeroX == -1) {
_scene->heroPositionType = ScenePositionType::kNoPosition;
}
_text->renderTextTriangle = false;
2020-11-01 19:22:34 +01:00
_text->textClipSmall();
_text->drawTextBoxBackground = true;
2020-11-01 19:22:34 +01:00
_state = EngineState::GameLoop;
break;
case EngineState::GameLoop:
if (gameEngineLoop()) {
_menuOptions->showCredits();
_menuOptions->showEndSequence();
2020-11-01 19:22:34 +01:00
}
_state = EngineState::Menu;
break;
case EngineState::Menu:
#if 0
// this will enter the game and execute the commands in the file "debug"
_gameState->initEngineVars();
_text->textClipSmall();
_text->drawTextBoxBackground = true;
_text->renderTextTriangle = false;
if (!((TwinEConsole*)getDebugger())->exec("debug")) {
debug("Failed to execute debug file before entering the scene");
}
gameEngineLoop();
#else
2020-11-01 19:22:34 +01:00
_state = _menu->run();
#endif
2020-11-01 19:22:34 +01:00
break;
}
}
2021-02-11 17:04:48 +01:00
ConfMan.setBool("combatauto", _actor->autoAggressive);
2021-01-03 12:58:21 +01:00
ConfMan.setInt("shadow", cfgfile.ShadowMode);
2021-02-11 17:04:48 +01:00
ConfMan.setBool("scezoom", cfgfile.SceZoom);
2021-01-03 12:58:21 +01:00
ConfMan.setInt("polygondetails", cfgfile.PolygonDetails);
2020-10-26 23:16:45 +01:00
_sound->stopSamples();
_music->stopTrackMusic();
_music->stopMidiMusic();
return Common::kNoError;
}
bool TwinEEngine::hasFeature(EngineFeature f) const {
switch (f) {
case EngineFeature::kSupportsReturnToLauncher:
case EngineFeature::kSupportsLoadingDuringRuntime:
case EngineFeature::kSupportsSavingDuringRuntime:
case EngineFeature::kSupportsChangingOptionsDuringRuntime:
return true;
default:
break;
}
return false;
}
SaveStateList TwinEEngine::getSaveSlots() const {
return getMetaEngine().listSaves(_targetName.c_str());
}
void TwinEEngine::wipeSaveSlot(int slot) {
Common::SaveFileManager *saveFileMan = getSaveFileManager();
2020-11-01 19:22:34 +01:00
const Common::String &saveFile = getSaveStateName(slot);
saveFileMan->removeSavefile(saveFile);
}
2020-11-01 19:22:34 +01:00
bool TwinEEngine::canSaveGameStateCurrently() { return _scene->currentScene != nullptr; }
Common::Error TwinEEngine::loadGameStream(Common::SeekableReadStream *stream) {
debug("load game stream");
if (!_gameState->loadGame(stream)) {
return Common::Error(Common::kReadingFailed);
}
_state = EngineState::LoadedGame;
return Common::Error(Common::kNoError);
}
2020-11-01 19:22:34 +01:00
Common::Error TwinEEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
if (!_gameState->saveGame(stream)) {
return Common::Error(Common::kWritingFailed);
}
return Common::Error(Common::kNoError);
}
void TwinEEngine::autoSave() {
2020-11-01 19:22:34 +01:00
// TODO: scene title, not player name
saveGameState(getAutosaveSlot(), _gameState->playerName, true);
}
void TwinEEngine::allocVideoMemory(int32 w, int32 h) {
const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
imageBuffer.create(640, 480, format); // original lba1 resolution for a lot of images.
workVideoBuffer.create(w, h, format);
frontVideoBuffer.create(w, h, format);
_renderer->init(w, h);
_grid->init(w, h);
}
static int getLanguageTypeIndex(const char *languageName) {
char buffer[256];
Common::strlcpy(buffer, languageName, sizeof(buffer));
char *ptr = strchr(buffer, ' ');
if (ptr != nullptr) {
*ptr = '\0';
}
const int32 length = ARRAYSIZE(LanguageTypes);
for (int32 i = 0; i < length; i++) {
if (!strcmp(LanguageTypes[i].name, buffer)) {
return i;
}
}
debug("Failed to detect language %s - falling back to english", languageName);
// select english for the fan translations
return 0; // English
}
#define ConfGetOrDefault(key, defaultVal) (ConfMan.hasKey(key) ? ConfMan.get(key) : Common::String(defaultVal))
#define ConfGetIntOrDefault(key, defaultVal) (ConfMan.hasKey(key) ? atoi(ConfMan.get(key).c_str()) : (defaultVal))
#define ConfGetBoolOrDefault(key, defaultVal) (ConfMan.hasKey(key) ? ConfMan.get(key) == "true" || atoi(ConfMan.get(key).c_str()) == 1 : (defaultVal))
void TwinEEngine::initConfigurations() {
// TODO: use existing entries for some of the settings - like volume and so on.
const char *lng = Common::getLanguageDescription(_gameLang);
cfgfile.LanguageId = getLanguageTypeIndex(lng);
2020-11-12 21:30:02 +01:00
cfgfile.Voice = ConfGetIntOrDefault("voice", 1) == 1;
cfgfile.FlagDisplayText = ConfGetIntOrDefault("displaytext", 1) == 1;
const Common::String midiType = ConfGetOrDefault("miditype", "auto");
if (midiType == "None") {
cfgfile.MidiType = MIDIFILE_NONE;
} else {
Common::File midiHqr;
if (midiHqr.exists(Resources::HQR_MIDI_MI_WIN_FILE)) {
cfgfile.MidiType = MIDIFILE_WIN;
debug("Use %s for midi", Resources::HQR_MIDI_MI_WIN_FILE);
} else if (midiHqr.exists(Resources::HQR_MIDI_MI_DOS_FILE)) {
cfgfile.MidiType = MIDIFILE_DOS;
debug("Use %s for midi", Resources::HQR_MIDI_MI_DOS_FILE);
} else {
cfgfile.MidiType = MIDIFILE_NONE;
debug("Could not find midi hqr file");
}
}
if (_gameFlags & TwineFeatureFlags::TF_VERSION_EUROPE) {
cfgfile.Version = EUROPE_VERSION;
} else if (_gameFlags & TwineFeatureFlags::TF_VERSION_USA) {
cfgfile.Version = USA_VERSION;
} else if (_gameFlags & TwineFeatureFlags::TF_VERSION_CUSTOM) {
cfgfile.Version = MODIFICATION_VERSION;
}
if (_gameFlags & TwineFeatureFlags::TF_USE_GIF) {
cfgfile.Movie = CONF_MOVIE_FLAGIF;
}
cfgfile.UseCD = ConfGetBoolOrDefault("usecd", false);
cfgfile.Sound = ConfGetBoolOrDefault("sound", true);
2020-11-12 21:30:02 +01:00
cfgfile.Fps = ConfGetIntOrDefault("fps", DEFAULT_FRAMES_PER_SECOND);
cfgfile.Debug = ConfGetBoolOrDefault("debug", false);
cfgfile.Mouse = ConfGetIntOrDefault("mouse", true);
2020-11-12 21:30:02 +01:00
cfgfile.UseAutoSaving = ConfGetBoolOrDefault("useautosaving", false);
cfgfile.CrossFade = ConfGetBoolOrDefault("crossfade", false);
cfgfile.WallCollision = ConfGetBoolOrDefault("wallcollision", false);
2020-11-12 21:30:02 +01:00
2021-01-06 07:51:57 +01:00
_actor->autoAggressive = ConfGetBoolOrDefault("combatauto", true);
2020-11-12 21:30:02 +01:00
cfgfile.ShadowMode = ConfGetIntOrDefault("shadow", 2);
cfgfile.SceZoom = ConfGetBoolOrDefault("scezoom", false);
2020-11-12 21:30:02 +01:00
cfgfile.PolygonDetails = ConfGetIntOrDefault("polygondetails", 2);
2021-01-06 07:53:33 +01:00
debug(1, "UseCD: %s", (cfgfile.UseCD ? "true" : "false"));
debug(1, "Sound: %s", (cfgfile.Sound ? "true" : "false"));
debug(1, "Movie: %i", cfgfile.Movie);
debug(1, "Fps: %i", cfgfile.Fps);
debug(1, "Debug: %s", (cfgfile.Debug ? "true" : "false"));
debug(1, "UseAutoSaving: %s", (cfgfile.UseAutoSaving ? "true" : "false"));
debug(1, "CrossFade: %s", (cfgfile.CrossFade ? "true" : "false"));
debug(1, "WallCollision: %s", (cfgfile.WallCollision ? "true" : "false"));
debug(1, "AutoAggressive: %s", (_actor->autoAggressive ? "true" : "false"));
debug(1, "ShadowMode: %i", cfgfile.ShadowMode);
debug(1, "PolygonDetails: %i", cfgfile.PolygonDetails);
debug(1, "SceZoom: %s", (cfgfile.SceZoom ? "true" : "false"));
}
void TwinEEngine::queueMovie(const char *filename) {
_queuedFlaMovie = filename;
}
void TwinEEngine::initEngine() {
// getting configuration file
initConfigurations();
_screens->clearScreen();
// Check if LBA CD-Rom is on drive
_music->initCdrom();
2020-10-25 21:55:14 +01:00
_input->enableKeyMap(cutsceneKeyMapId);
// Display company logo
bool abort = false;
abort |= _screens->adelineLogo();
// verify game version screens
if (!abort && cfgfile.Version == EUROPE_VERSION) {
// Little Big Adventure screen
abort |= _screens->loadImageDelay(RESSHQR_LBAIMG, RESSHQR_LBAPAL, 3);
if (!abort) {
// Electronic Arts Logo
abort |= _screens->loadImageDelay(RESSHQR_EAIMG, RESSHQR_EAPAL, 2);
}
} else if (!abort && cfgfile.Version == USA_VERSION) {
// Relentless screen
abort |= _screens->loadImageDelay(RESSHQR_RELLENTIMG, RESSHQR_RELLENTPAL, 3);
if (!abort) {
// Electronic Arts Logo
abort |= _screens->loadImageDelay(RESSHQR_EAIMG, RESSHQR_EAPAL, 2);
}
} else if (!abort && cfgfile.Version == MODIFICATION_VERSION) {
// Modification screen
abort |= _screens->loadImageDelay(RESSHQR_RELLENTIMG, RESSHQR_RELLENTPAL, 2);
}
if (!abort) {
_flaMovies->playFlaMovie(FLA_DRAGON3);
}
_input->enableKeyMap(uiKeyMapId);
_screens->loadMenuImage();
}
2021-02-10 18:37:55 +01:00
void TwinEEngine::initSceneryView() {
_redraw->inSceneryView = true;
}
2021-02-10 18:37:55 +01:00
void TwinEEngine::exitSceneryView() {
_redraw->inSceneryView = false;
}
void TwinEEngine::initAll() {
2021-02-25 12:46:28 +01:00
Common::fill(&_menu->itemAngle[0], &_menu->itemAngle[NUM_INVENTORY_ITEMS], 0);
_redraw->bubbleSpriteIndex = SPRITEHQR_DIAG_BUBBLE_LEFT;
_scene->sceneHero = _scene->getActor(OWN_ACTOR_SCENE_INDEX);
2021-01-07 21:37:27 +01:00
_redraw->renderRect = rect();
// Set clip to fullscreen by default, allows main menu to render properly after load
_interface->resetClip();
_resources->initResources();
2021-02-10 18:37:55 +01:00
exitSceneryView();
}
int TwinEEngine::getRandomNumber(uint max) {
if (max == 0) {
return 0;
}
return _rnd.getRandomNumber(max - 1);
}
void TwinEEngine::freezeTime() {
if (!isTimeFreezed) {
saveFreezedTime = lbaTime;
_pauseToken = pauseEngine();
}
isTimeFreezed++;
}
void TwinEEngine::unfreezeTime() {
--isTimeFreezed;
if (isTimeFreezed == 0) {
lbaTime = saveFreezedTime;
_pauseToken.clear();
}
}
void TwinEEngine::processActorSamplePosition(int32 actorIdx) {
const ActorStruct *actor = _scene->getActor(actorIdx);
const int32 channelIdx = _sound->getActorChannel(actorIdx);
_sound->setSamplePosition(channelIdx, actor->x, actor->y, actor->z);
}
2020-12-28 22:55:02 +01:00
void TwinEEngine::processBookOfBu() {
_screens->fadeToBlack(_screens->paletteRGBA);
_screens->loadImage(RESSHQR_INTROSCREEN1IMG, RESSHQR_INTROSCREEN1PAL);
_text->initTextBank(TextBankId::Inventory_Intro_and_Holomap);
_text->drawTextBoxBackground = false;
_text->textClipFull();
_text->setFontCrossColor(COLOR_WHITE);
2020-12-28 22:55:02 +01:00
const bool tmpFlagDisplayText = cfgfile.FlagDisplayText;
cfgfile.FlagDisplayText = true;
2021-01-30 14:16:23 +01:00
_text->drawTextProgressive(TextId::kBookOfBu);
2020-12-28 22:55:02 +01:00
cfgfile.FlagDisplayText = tmpFlagDisplayText;
_text->textClipSmall();
_text->drawTextBoxBackground = true;
_text->initSceneTextBank();
_screens->fadeToBlack(_screens->paletteRGBACustom);
_screens->clearScreen();
flip();
setPalette(_screens->paletteRGBA);
_screens->lockPalette = true;
}
void TwinEEngine::processBonusList() {
_text->initTextBank(TextBankId::Inventory_Intro_and_Holomap);
_text->textClipFull();
_text->setFontCrossColor(COLOR_WHITE);
const bool tmpFlagDisplayText = cfgfile.FlagDisplayText;
cfgfile.FlagDisplayText = true;
2021-01-30 14:16:23 +01:00
_text->drawTextProgressive(TextId::kBonusList);
cfgfile.FlagDisplayText = tmpFlagDisplayText;
2020-12-28 22:55:02 +01:00
_text->textClipSmall();
_text->initSceneTextBank();
}
2020-11-22 11:44:03 +01:00
void TwinEEngine::processInventoryAction() {
ScopedEngineFreeze scoped(this);
exitSceneryView();
2020-11-22 11:44:03 +01:00
_menu->processInventoryMenu();
switch (loopInventoryItem) {
case kiHolomap:
_holomap->processHolomap();
_screens->lockPalette = true;
break;
case kiMagicBall:
if (_gameState->usingSabre) {
2021-01-26 20:14:46 +01:00
_actor->initModelActor(BodyType::btNormal, OWN_ACTOR_SCENE_INDEX);
2020-11-22 11:44:03 +01:00
}
_gameState->usingSabre = false;
break;
case kiUseSabre:
2021-01-26 20:14:46 +01:00
if (_scene->sceneHero->body != BodyType::btSabre) {
2020-11-22 11:44:03 +01:00
if (_actor->heroBehaviour == HeroBehaviourType::kProtoPack) {
_actor->setBehaviour(HeroBehaviourType::kNormal);
}
2021-01-26 20:14:46 +01:00
_actor->initModelActor(BodyType::btSabre, OWN_ACTOR_SCENE_INDEX);
2021-01-26 19:58:29 +01:00
_animations->initAnim(AnimationTypes::kSabreUnknown, kAnimationType_1, AnimationTypes::kStanding, OWN_ACTOR_SCENE_INDEX);
2020-11-22 11:44:03 +01:00
_gameState->usingSabre = true;
}
break;
case kiBookOfBu: {
2020-12-28 22:55:02 +01:00
processBookOfBu();
2020-11-22 11:44:03 +01:00
break;
}
case kiProtoPack:
2020-11-22 12:07:28 +01:00
if (_gameState->hasItem(InventoryItems::kiBookOfBu)) {
2021-01-26 20:14:46 +01:00
_scene->sceneHero->body = BodyType::btNormal;
2020-11-22 11:44:03 +01:00
} else {
2021-01-26 20:14:46 +01:00
_scene->sceneHero->body = BodyType::btTunic;
2020-11-22 11:44:03 +01:00
}
if (_actor->heroBehaviour == HeroBehaviourType::kProtoPack) {
_actor->setBehaviour(HeroBehaviourType::kNormal);
} else {
_actor->setBehaviour(HeroBehaviourType::kProtoPack);
}
break;
case kiPinguin: {
ActorStruct *pinguin = _scene->getActor(_scene->mecaPinguinIdx);
pinguin->x = _renderer->destX + _scene->sceneHero->x;
pinguin->y = _scene->sceneHero->y;
pinguin->z = _renderer->destZ + _scene->sceneHero->z;
pinguin->angle = _scene->sceneHero->angle;
_movements->rotateActor(0, 800, pinguin->angle);
if (!_collision->checkCollisionWithActors(_scene->mecaPinguinIdx)) {
pinguin->life = 50;
2021-01-26 20:14:46 +01:00
pinguin->body = BodyType::btNone;
_actor->initModelActor(BodyType::btNormal, _scene->mecaPinguinIdx);
2020-11-22 11:44:03 +01:00
pinguin->dynamicFlags.bIsDead = 0; // &= 0xDF
pinguin->setBrickShape(ShapeType::kNone);
_movements->moveActor(pinguin->angle, pinguin->angle, pinguin->speed, &pinguin->move);
2020-11-22 12:07:28 +01:00
_gameState->removeItem(InventoryItems::kiPinguin); // byte_50D89 = 0;
2020-11-22 11:44:03 +01:00
pinguin->delayInMillis = lbaTime + 1500;
}
break;
}
case kiBonusList: {
unfreezeTime();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
2020-11-22 11:44:03 +01:00
freezeTime();
2020-12-28 22:55:02 +01:00
processBonusList();
2020-11-22 11:44:03 +01:00
break;
}
case kiCloverLeaf:
if (_scene->sceneHero->life < 50) {
if (_gameState->inventoryNumLeafs > 0) {
_scene->sceneHero->life = 50;
_gameState->inventoryMagicPoints = _gameState->magicLevelIdx * 20;
_gameState->inventoryNumLeafs--;
2020-12-07 17:00:17 +01:00
_redraw->addOverlay(OverlayType::koInventoryItem, InventoryItems::kiCloverLeaf, 0, 0, 0, OverlayPosType::koNormal, 3);
2020-11-22 11:44:03 +01:00
}
}
break;
}
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
2020-11-22 11:44:03 +01:00
}
void TwinEEngine::processOptionsMenu() {
ScopedEngineFreeze scoped(this);
exitSceneryView();
2020-11-22 11:44:03 +01:00
_sound->pauseSamples();
_menu->inGameOptionsMenu();
_scene->playSceneMusic();
2020-11-22 11:44:03 +01:00
_sound->resumeSamples();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
2020-11-22 11:44:03 +01:00
}
void TwinEEngine::centerScreenOnActor() {
if (disableScreenRecenter) {
return;
}
if (_debugGrid->useFreeCamera) {
return;
}
2020-11-22 11:44:03 +01:00
ActorStruct *actor = _scene->getActor(_scene->currentlyFollowedActor);
2021-02-25 15:22:19 +01:00
_renderer->projectPositionOnScreen(actor->x - (_grid->newCamera.x * BRICK_SIZE),
actor->y - (_grid->newCamera.y * BRICK_HEIGHT),
actor->z - (_grid->newCamera.z * BRICK_SIZE));
if (_renderer->projPosX < 80 || _renderer->projPosX >= width() - 60 || _renderer->projPosY < 80 || _renderer->projPosY >= height() - 50) {
2021-02-25 15:22:19 +01:00
_grid->newCamera.x = ((actor->x + BRICK_HEIGHT) / BRICK_SIZE) + (((actor->x + BRICK_HEIGHT) / BRICK_SIZE) - _grid->newCamera.x) / 2;
_grid->newCamera.y = actor->y / BRICK_HEIGHT;
_grid->newCamera.z = ((actor->z + BRICK_HEIGHT) / BRICK_SIZE) + (((actor->z + BRICK_HEIGHT) / BRICK_SIZE) - _grid->newCamera.z) / 2;
2021-02-25 15:22:19 +01:00
if (_grid->newCamera.x >= GRID_SIZE_X) {
_grid->newCamera.x = GRID_SIZE_X - 1;
}
2020-11-22 11:44:03 +01:00
2021-02-25 15:22:19 +01:00
if (_grid->newCamera.z >= GRID_SIZE_Z) {
_grid->newCamera.z = GRID_SIZE_Z - 1;
2020-11-22 11:44:03 +01:00
}
_redraw->reqBgRedraw = true;
2020-11-22 11:44:03 +01:00
}
}
int32 TwinEEngine::runGameEngine() { // mainLoopInteration
2021-01-09 18:42:25 +01:00
FrameMarker frame;
2020-10-25 21:55:14 +01:00
_input->enableKeyMap(mainKeyMapId);
readKeys();
if (!_queuedFlaMovie.empty()) {
_flaMovies->playFlaMovie(_queuedFlaMovie.c_str());
_queuedFlaMovie.clear();
}
if (_scene->needChangeScene > -1) {
_scene->changeScene();
}
_movements->update();
2020-10-24 13:46:18 +02:00
_debug->processDebug();
if (_menuOptions->canShowCredits) {
// TODO: if current music playing != 8, than play_track(8);
2020-10-24 13:46:18 +02:00
if (_input->toggleAbortAction()) {
return 1;
}
} else {
// Process give up menu - Press ESC
2020-10-24 13:46:18 +02:00
if (_input->toggleAbortAction() && _scene->sceneHero->life > 0 && _scene->sceneHero->entity != -1 && !_scene->sceneHero->staticFlags.bIsHidden) {
freezeTime();
exitSceneryView();
const int giveUp = _menu->giveupMenu();
if (giveUp == kQuitEngine) {
return 0;
}
if (giveUp == 1) {
unfreezeTime();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
quitGame = 0;
return 0;
}
unfreezeTime();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
}
if (_input->toggleActionIfActive(TwinEActionType::OptionsMenu)) {
2020-11-22 11:44:03 +01:00
processOptionsMenu();
}
// inventory menu
loopInventoryItem = -1;
if (_input->isActionActive(TwinEActionType::InventoryMenu) && _scene->sceneHero->entity != -1 && _scene->sceneHero->controlMode == ControlMode::kManual) {
2020-11-22 11:44:03 +01:00
processInventoryAction();
}
if (_input->toggleActionIfActive(TwinEActionType::ChangeBehaviourNormal)) {
_actor->setBehaviour(HeroBehaviourType::kNormal);
} else if (_input->toggleActionIfActive(TwinEActionType::ChangeBehaviourAthletic)) {
_actor->setBehaviour(HeroBehaviourType::kAthletic);
} else if (_input->toggleActionIfActive(TwinEActionType::ChangeBehaviourAggressive)) {
_actor->setBehaviour(HeroBehaviourType::kAggressive);
} else if (_input->toggleActionIfActive(TwinEActionType::ChangeBehaviourDiscreet)) {
_actor->setBehaviour(HeroBehaviourType::kDiscrete);
}
// Process behaviour menu
if ((_input->isActionActive(TwinEActionType::BehaviourMenu, false) ||
_input->isActionActive(TwinEActionType::QuickBehaviourNormal, false) ||
_input->isActionActive(TwinEActionType::QuickBehaviourAthletic, false) ||
_input->isActionActive(TwinEActionType::QuickBehaviourAggressive, false) ||
_input->isActionActive(TwinEActionType::QuickBehaviourDiscreet, false)) &&
_scene->sceneHero->entity != -1 && _scene->sceneHero->controlMode == ControlMode::kManual) {
if (_input->isActionActive(TwinEActionType::QuickBehaviourNormal, false)) {
_actor->heroBehaviour = HeroBehaviourType::kNormal;
} else if (_input->isActionActive(TwinEActionType::QuickBehaviourAthletic, false)) {
_actor->heroBehaviour = HeroBehaviourType::kAthletic;
} else if (_input->isActionActive(TwinEActionType::QuickBehaviourAggressive, false)) {
_actor->heroBehaviour = HeroBehaviourType::kAggressive;
} else if (_input->isActionActive(TwinEActionType::QuickBehaviourDiscreet, false)) {
_actor->heroBehaviour = HeroBehaviourType::kDiscrete;
}
freezeTime();
_menu->processBehaviourMenu();
unfreezeTime();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
}
// use Proto-Pack
2020-11-22 12:07:28 +01:00
if (_input->toggleActionIfActive(TwinEActionType::UseProtoPack) && _gameState->hasItem(InventoryItems::kiProtoPack)) {
if (_gameState->hasItem(InventoryItems::kiBookOfBu)) {
2021-01-26 20:14:46 +01:00
_scene->sceneHero->body = BodyType::btNormal;
} else {
2021-01-26 20:14:46 +01:00
_scene->sceneHero->body = BodyType::btTunic;
}
if (_actor->heroBehaviour == HeroBehaviourType::kProtoPack) {
_actor->setBehaviour(HeroBehaviourType::kNormal);
} else {
_actor->setBehaviour(HeroBehaviourType::kProtoPack);
}
}
// Recenter Screen
if (_input->toggleActionIfActive(TwinEActionType::RecenterScreenOnTwinsen) && !disableScreenRecenter) {
const ActorStruct *currentlyFollowedActor = _scene->getActor(_scene->currentlyFollowedActor);
2021-02-25 14:37:28 +01:00
_grid->centerOnActor(currentlyFollowedActor);
}
// Draw holomap
if (_input->toggleActionIfActive(TwinEActionType::OpenHolomap) && _gameState->hasItem(InventoryItems::kiHolomap) && !_gameState->inventoryDisabled()) {
freezeTime();
_holomap->processHolomap();
2020-10-22 12:42:57 +02:00
_screens->lockPalette = true;
unfreezeTime();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
}
// Process Pause
if (_input->toggleActionIfActive(TwinEActionType::Pause)) {
freezeTime();
const char *PauseString = "Pause";
2021-01-08 19:44:19 +01:00
_text->setFontColor(COLOR_WHITE);
if (_redraw->inSceneryView) {
_text->drawText(_redraw->_sceneryViewX + 5, _redraw->_sceneryViewY, PauseString);
_redraw->zoomScreenScale();
} else {
const int width = _text->getTextSize(PauseString);
const int bottom = height() - _text->lineHeight;
_text->drawText(5, bottom, PauseString);
copyBlockPhys(5, bottom, 5 + width, bottom + _text->lineHeight);
}
do {
2021-01-09 18:42:25 +01:00
FrameMarker frameWait;
ScopedFPS scopedFps;
readKeys();
if (shouldQuit()) {
break;
}
} while (!_input->toggleActionIfActive(TwinEActionType::Pause));
unfreezeTime();
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
}
}
loopActorStep = loopMovePtr.getRealValue(lbaTime);
if (!loopActorStep) {
loopActorStep = 1;
}
2020-12-31 00:26:09 +01:00
_movements->setActorAngle(ANGLE_0, -ANGLE_90, ANGLE_1, &loopMovePtr);
2020-10-27 18:29:32 +01:00
disableScreenRecenter = false;
_scene->processEnvironmentSound();
// Reset HitBy state
for (int32 a = 0; a < _scene->sceneNumActors; a++) {
_scene->getActor(a)->hitBy = -1;
}
_extra->processExtras();
for (int32 a = 0; a < _scene->sceneNumActors; a++) {
ActorStruct *actor = _scene->getActor(a);
2020-11-22 11:44:03 +01:00
if (actor->dynamicFlags.bIsDead) {
continue;
}
2020-11-22 11:44:03 +01:00
if (actor->life == 0) {
if (IS_HERO(a)) {
2021-01-26 19:58:29 +01:00
_animations->initAnim(AnimationTypes::kLandDeath, kAnimationType_4, AnimationTypes::kStanding, 0);
2020-11-22 11:44:03 +01:00
actor->controlMode = ControlMode::kNoMove;
} else {
_sound->playSample(Samples::Explode, 1, actor->x, actor->y, actor->z, a);
2020-11-22 11:44:03 +01:00
if (a == _scene->mecaPinguinIdx) {
_extra->addExtraExplode(actor->x, actor->y, actor->z);
}
}
2020-11-22 11:44:03 +01:00
if (!actor->bonusParameter.unk1 && (actor->bonusParameter.cloverleaf || actor->bonusParameter.kashes || actor->bonusParameter.key || actor->bonusParameter.lifepoints || actor->bonusParameter.magicpoints)) {
_actor->processActorExtraBonus(a);
}
}
2020-11-22 11:44:03 +01:00
_movements->processActorMovements(a);
2020-11-22 11:44:03 +01:00
actor->collisionX = actor->x;
actor->collisionY = actor->y;
actor->collisionZ = actor->z;
if (actor->positionInMoveScript != -1) {
_scriptMove->processMoveScript(a);
}
2020-11-22 11:44:03 +01:00
_animations->processActorAnimations(a);
2020-11-22 11:44:03 +01:00
if (actor->staticFlags.bIsZonable) {
_scene->processActorZones(a);
}
2020-11-22 11:44:03 +01:00
if (actor->positionInLifeScript != -1) {
_scriptLife->processLifeScript(a);
}
2020-11-22 11:44:03 +01:00
processActorSamplePosition(a);
2020-11-22 11:44:03 +01:00
if (quitGame != -1) {
return quitGame;
}
if (actor->staticFlags.bCanDrown) {
int32 brickSound = _grid->getBrickSoundType(actor->x, actor->y - 1, actor->z);
actor->brickSound = brickSound;
2020-11-22 11:44:03 +01:00
if ((brickSound & 0xF0) == 0xF0) {
if ((brickSound & 0x0F) == 1) {
if (IS_HERO(a)) {
if (_actor->heroBehaviour != HeroBehaviourType::kProtoPack || actor->anim != AnimationTypes::kForward) {
if (!_actor->cropBottomScreen) {
2021-01-26 19:58:29 +01:00
_animations->initAnim(AnimationTypes::kDrawn, kAnimationType_4, AnimationTypes::kStanding, 0);
2021-02-25 15:22:19 +01:00
_renderer->projectPositionOnScreen(actor->x - _grid->camera.x, actor->y - _grid->camera.y, actor->z - _grid->camera.z);
_actor->cropBottomScreen = _renderer->projPosY;
}
2021-02-25 15:22:19 +01:00
_renderer->projectPositionOnScreen(actor->x - _grid->camera.x, actor->y - _grid->camera.y, actor->z - _grid->camera.z);
2020-11-22 11:44:03 +01:00
actor->controlMode = ControlMode::kNoMove;
actor->life = -1;
_actor->cropBottomScreen = _renderer->projPosY;
actor->staticFlags.bCanDrown |= 0x10; // TODO: doesn't make sense
}
} else {
_sound->playSample(Samples::Explode, 1, actor->x, actor->y, actor->z, a);
2020-11-22 11:44:03 +01:00
if (actor->bonusParameter.cloverleaf || actor->bonusParameter.kashes || actor->bonusParameter.key || actor->bonusParameter.lifepoints || actor->bonusParameter.magicpoints) {
if (!actor->bonusParameter.unk1) {
_actor->processActorExtraBonus(a);
}
2020-11-22 11:44:03 +01:00
actor->life = 0;
}
}
}
}
2020-11-22 11:44:03 +01:00
}
2020-11-22 11:44:03 +01:00
if (actor->life <= 0) {
if (IS_HERO(a)) {
if (actor->dynamicFlags.bAnimEnded) {
if (_gameState->inventoryNumLeafs > 0) { // use clover leaf automaticaly
_scene->sceneHero->x = _scene->newHeroX;
_scene->sceneHero->y = _scene->newHeroY;
_scene->sceneHero->z = _scene->newHeroZ;
2020-11-22 11:44:03 +01:00
_scene->needChangeScene = _scene->currentSceneIdx;
_gameState->inventoryMagicPoints = _gameState->magicLevelIdx * 20;
2021-02-25 14:37:28 +01:00
_grid->centerOnActor(_scene->sceneHero);
2020-11-22 11:44:03 +01:00
_scene->heroPositionType = ScenePositionType::kReborn;
2020-11-22 11:44:03 +01:00
_scene->sceneHero->life = 50;
_redraw->reqBgRedraw = true;
_screens->lockPalette = true;
_gameState->inventoryNumLeafs--;
_actor->cropBottomScreen = 0;
} else { // game over
_gameState->inventoryNumLeafsBox = 2;
_gameState->inventoryNumLeafs = 1;
_gameState->inventoryMagicPoints = _gameState->magicLevelIdx * 20;
_actor->heroBehaviour = _actor->previousHeroBehaviour;
actor->angle = _actor->previousHeroAngle;
actor->life = 50;
if (_scene->previousSceneIdx != _scene->currentSceneIdx) {
_scene->newHeroX = -1;
_scene->newHeroY = -1;
_scene->newHeroZ = -1;
_scene->currentSceneIdx = _scene->previousSceneIdx;
_scene->stopRunningGame();
}
2020-11-22 11:44:03 +01:00
autoSave();
_gameState->processGameoverAnimation();
quitGame = 0;
return 0;
}
}
} else {
_actor->processActorCarrier(a);
actor->dynamicFlags.bIsDead = 1;
actor->entity = -1;
actor->zone = -1;
}
2020-11-22 11:44:03 +01:00
}
2020-11-22 11:44:03 +01:00
if (_scene->needChangeScene != -1) {
return 0;
}
}
2020-11-22 11:44:03 +01:00
centerScreenOnActor();
_redraw->redrawEngineActions(_redraw->reqBgRedraw);
// workaround to fix hero redraw after drowning
if (_actor->cropBottomScreen && _redraw->reqBgRedraw) {
_scene->sceneHero->staticFlags.bIsHidden = 1;
2020-11-26 22:23:30 +01:00
_redraw->redrawEngineActions(true);
_scene->sceneHero->staticFlags.bIsHidden = 0;
}
_scene->needChangeScene = -1;
_redraw->reqBgRedraw = false;
return 0;
}
2020-10-27 18:29:32 +01:00
bool TwinEEngine::gameEngineLoop() {
2020-10-22 12:42:57 +02:00
_redraw->reqBgRedraw = true;
_screens->lockPalette = true;
2020-12-31 00:26:09 +01:00
_movements->setActorAngle(ANGLE_0, -ANGLE_90, ANGLE_1, &loopMovePtr);
while (quitGame == -1) {
2020-10-24 13:46:18 +02:00
uint32 start = g_system->getMillis();
while (g_system->getMillis() < start + cfgfile.Fps) {
if (runGameEngine()) {
return true;
}
g_system->delayMillis(1);
}
lbaTime++;
if (shouldQuit()) {
break;
}
}
return false;
}
bool TwinEEngine::delaySkip(uint32 time) {
uint32 startTicks = _system->getMillis();
uint32 stopTicks = 0;
do {
2021-01-09 18:42:25 +01:00
FrameMarker frame;
ScopedFPS scopedFps;
readKeys();
2020-10-24 12:32:00 +02:00
if (_input->toggleAbortAction()) {
return true;
}
if (shouldQuit()) {
return true;
}
stopTicks = _system->getMillis() - startTicks;
//lbaTime++;
} while (stopTicks <= time);
return false;
}
void TwinEEngine::setPalette(const uint32 *palette) {
uint8 pal[NUMOFCOLORS * 3];
uint8 *out = pal;
const uint8 *in = (const uint8 *)palette;
for (int i = 0; i < NUMOFCOLORS; i++) {
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
out += 3;
in += 4;
}
2020-12-25 12:22:34 +01:00
g_system->getPaletteManager()->setPalette(pal, 0, NUMOFCOLORS);
flip();
}
void TwinEEngine::setPalette(uint8 startColor, uint8 numColors, const byte *palette) {
g_system->getPaletteManager()->setPalette(palette, startColor, numColors);
flip();
}
void TwinEEngine::flip() {
g_system->copyRectToScreen(frontVideoBuffer.getPixels(), frontVideoBuffer.pitch, 0, 0, frontVideoBuffer.w, frontVideoBuffer.h);
g_system->updateScreen();
}
2021-01-09 17:35:39 +01:00
void TwinEEngine::copyBlockPhys(const Common::Rect &rect, bool updateScreen) {
copyBlockPhys(rect.left, rect.top, rect.right, rect.bottom, updateScreen);
}
2021-01-09 17:35:39 +01:00
void TwinEEngine::copyBlockPhys(int32 left, int32 top, int32 right, int32 bottom, bool updateScreen) {
assert(left <= right);
assert(top <= bottom);
2020-10-27 18:08:06 +01:00
int32 width = right - left + 1;
int32 height = bottom - top + 1;
if (left + width > this->width()) {
width = this->width() - left;
2020-10-26 00:18:17 +01:00
}
if (top + height > this->height()) {
height = this->height() - top;
2020-10-27 18:08:06 +01:00
}
if (width <= 0 || height <= 0) {
return;
2020-10-27 18:08:06 +01:00
}
2020-10-25 23:35:17 +01:00
g_system->copyRectToScreen(frontVideoBuffer.getBasePtr(left, top), frontVideoBuffer.pitch, left, top, width, height);
2021-01-09 17:35:39 +01:00
if (updateScreen) {
g_system->updateScreen();
}
}
void TwinEEngine::crossFade(const Graphics::ManagedSurface &buffer, const uint32 *palette) {
Graphics::ManagedSurface backupSurface;
Graphics::ManagedSurface newSurface;
Graphics::ManagedSurface tempSurface;
Graphics::ManagedSurface surfaceTable;
Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 24, 16, 8, 0);
backupSurface.create(frontVideoBuffer.w, frontVideoBuffer.h, fmt);
newSurface.create(frontVideoBuffer.w, frontVideoBuffer.h, fmt);
tempSurface.create(frontVideoBuffer.w, frontVideoBuffer.h, Graphics::PixelFormat::createFormatCLUT8());
tempSurface.setPalette(palette, 0, NUMOFCOLORS);
surfaceTable.create(frontVideoBuffer.w, frontVideoBuffer.h, fmt);
backupSurface.transBlitFrom(frontVideoBuffer);
newSurface.transBlitFrom(tempSurface);
for (int32 i = 0; i < 8; i++) {
surfaceTable.blitFrom(backupSurface);
surfaceTable.transBlitFrom(newSurface, 0, false, 0, i * NUMOFCOLORS / 8);
frontVideoBuffer.blitFrom(surfaceTable);
flip();
delaySkip(50);
}
frontVideoBuffer.blitFrom(newSurface);
flip();
backupSurface.free();
newSurface.free();
tempSurface.free();
surfaceTable.free();
}
void TwinEEngine::readKeys() {
2020-10-23 13:44:38 +02:00
_input->readKeys();
}
void TwinEEngine::drawText(int32 x, int32 y, const char *string, int32 center) {
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
if (!font) {
return;
}
int width = 100;
const Common::String text(string);
font->drawString(&frontVideoBuffer, text,
x, y, width,
frontVideoBuffer.format.RGBToColor(255, 255, 255),
center ? Graphics::kTextAlignCenter : Graphics::kTextAlignLeft);
}
const char *TwinEEngine::getGameId() const {
if (isLBA1()) {
return "lba";
}
assert(isLBA2());
return "lba2";
}
bool TwinEEngine::unlockAchievement(const Common::String &id) {
const MetaEngine &meta = getMetaEngine();
const Common::AchievementsInfo &achievementsInfo = meta.getAchievementsInfo(getGameId());
Common::String msg = id;
for (uint32 i = 0; i < achievementsInfo.descriptions.size(); i++) {
if (id == achievementsInfo.descriptions[i].id) {
msg = achievementsInfo.descriptions[i].title;
break;
}
}
return AchMan.setAchievement(id, msg);
}
} // namespace TwinE