scummvm/engines/kyra/kyra_mr.cpp
Torbjörn Andersson 238aa2be2a KYRA: Let the VQA decoder draw directly to the backend
As an alternative to using the Screen class's functions, we can let
the VQA decoder draw directly to the backend. This won't work if the
game uses "hi-res mode", but I don't think that's ever the case for
Malcolm's Revenge. I believe the KyraEngine_MR::playVQA() function
ensures that the screen is properly updated after the movie has
finished.

This almost limits the VQA rewrite to vqa.cpp and vqa.h. Whether it's
better this way than changing the Screen functions to take a 'pitch'
parameter...? I don't know. But it's an alternative.
2014-01-18 03:18:40 +01:00

1427 lines
33 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 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 "kyra/kyra_mr.h"
#include "kyra/wsamovie.h"
#include "kyra/text_mr.h"
#include "kyra/vqa.h"
#include "kyra/timer.h"
#include "kyra/debugger.h"
#include "kyra/gui_mr.h"
#include "kyra/resource.h"
#include "kyra/sound_digital.h"
#include "common/system.h"
#include "common/config-manager.h"
namespace Kyra {
const KyraEngine_v2::EngineDesc KyraEngine_MR::_mrEngineDesc = {
// Generic shape related
248,
KyraEngine_MR::_characterFrameTable,
// Scene script
9,
// Animation script specific
9,
// Item specific
71
};
KyraEngine_MR::KyraEngine_MR(OSystem *system, const GameFlags &flags) : KyraEngine_v2(system, flags, _mrEngineDesc) {
_soundDigital = 0;
_musicSoundChannel = -1;
_menuAudioFile = "TITLE1";
_lastMusicCommand = -1;
_itemBuffer1 = _itemBuffer2 = 0;
_scoreFile = 0;
_cCodeFile = 0;
_scenesFile = 0;
_itemFile = 0;
_gamePlayBuffer = 0;
_interface = _interfaceCommandLine = 0;
_costPalBuffer = 0;
memset(_sceneShapes, 0, sizeof(_sceneShapes));
memset(_sceneAnimMovie, 0, sizeof(_sceneAnimMovie));
_gfxBackUpRect = 0;
_paletteOverlay = 0;
_sceneList = 0;
_mainCharacter.sceneId = 9;
_mainCharacter.height = 0x4C;
_mainCharacter.facing = 5;
_mainCharacter.animFrame = 0x57;
_mainCharacter.walkspeed = 5;
memset(_activeItemAnim, 0, sizeof(_activeItemAnim));
_nextAnimItem = 0;
_text = 0;
_commandLineY = 189;
_inventoryState = false;
memset(_characterAnimTable, 0, sizeof(_characterAnimTable));
_overwriteSceneFacing = false;
_maskPageMinY = _maskPageMaxY = 0;
_sceneStrings = 0;
_enterNewSceneLock = 0;
_mainCharX = _mainCharY = -1;
_animList = 0;
_drawNoShapeFlag = false;
_wasPlayingVQA = false;
_lastCharPalLayer = -1;
_charPalUpdate = false;
_runFlag = false;
_unk5 = 0;
_unkSceneScreenFlag1 = false;
_noScriptEnter = true;
_itemInHand = _mouseState = kItemNone;
_savedMouseState = -1;
_unk4 = 0;
_loadingState = false;
_noStartupChat = false;
_pathfinderFlag = 0;
_talkObjectList = 0;
memset(&_chatScriptState, 0, sizeof(_chatScriptState));
memset(&_chatScriptData, 0, sizeof(_chatScriptData));
_voiceSoundChannel = -1;
_charBackUpWidth2 = _charBackUpHeight2 = -1;
_charBackUpWidth = _charBackUpHeight = -1;
_useActorBuffer = false;
_curStudioSFX = 283;
_badConscienceShown = false;
_currentChapter = 1;
_unkHandleSceneChangeFlag = false;
memset(_sceneShapeDescs, 0, sizeof(_sceneShapeDescs));
_cnvFile = _dlgBuffer = 0;
_curDlgChapter = _curDlgIndex = _curDlgLang = -1;
_isStartupDialog = 0;
_stringBuffer = 0;
_menu = 0;
_menuAnim = 0;
_dialogSceneAnim = _dialogSceneScript = -1;
memset(&_dialogScriptData, 0, sizeof(_dialogScriptData));
memset(&_dialogScriptState, 0, sizeof(_dialogScriptState));
_dialogScriptFuncStart = _dialogScriptFuncProc = _dialogScriptFuncEnd = 0;
_malcolmsMood = 1;
_nextIdleAnim = 0;
_nextIdleType = false;
_inventoryScrollSpeed = -1;
_invWsa = 0;
_invWsaFrame = -1;
_score = 0;
memset(_scoreFlagTable, 0, sizeof(_scoreFlagTable));
_mainButtonData = 0;
_mainButtonList = 0;
_mainButtonListInitialized = false;
_enableInventory = true;
_goodConscienceShown = false;
_goodConscienceAnim = -1;
_goodConsciencePosition = false;
_menuDirectlyToLoad = false;
_optionsFile = 0;
_actorFile = 0;
_chatAltFlag = false;
_albumChatActive = false;
memset(&_album, 0, sizeof(_album));
_configHelium = false;
_fadeOutMusicChannel = -1;
memset(_scaleTable, 0, sizeof(_scaleTable));
}
KyraEngine_MR::~KyraEngine_MR() {
uninitMainMenu();
delete _screen;
delete _soundDigital;
delete[] _itemBuffer1;
delete[] _itemBuffer2;
delete[] _scoreFile;
delete[] _cCodeFile;
delete[] _scenesFile;
delete[] _itemFile;
delete[] _actorFile;
delete[] _gamePlayBuffer;
delete[] _interface;
delete[] _interfaceCommandLine;
delete[] _costPalBuffer;
for (uint i = 0; i < ARRAYSIZE(_sceneShapes); ++i)
delete[] _sceneShapes[i];
for (uint i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i)
delete _sceneAnimMovie[i];
delete[] _gfxBackUpRect;
delete[] _paletteOverlay;
for (ShapeMap::iterator i = _gameShapes.begin(); i != _gameShapes.end(); ++i) {
delete[] i->_value;
i->_value = 0;
}
_gameShapes.clear();
delete[] _sceneStrings;
delete[] _talkObjectList;
for (Common::Array<const Opcode *>::iterator i = _opcodesDialog.begin(); i != _opcodesDialog.end(); ++i)
delete *i;
_opcodesDialog.clear();
delete _cnvFile;
delete _dlgBuffer;
delete[] _stringBuffer;
delete _invWsa;
delete[] _mainButtonData;
delete _gui;
delete[] _optionsFile;
delete _album.wsa;
delete _album.leftPage.wsa;
delete _album.rightPage.wsa;
}
Common::Error KyraEngine_MR::init() {
_screen = new Screen_MR(this, _system);
assert(_screen);
_screen->setResolution();
_debugger = new Debugger_v2(this);
assert(_debugger);
KyraEngine_v1::init();
initStaticResource();
_soundDigital = new SoundDigital(this, _mixer);
assert(_soundDigital);
KyraEngine_v1::_text = _text = new TextDisplayer_MR(this, _screen);
assert(_text);
_gui = new GUI_MR(this);
assert(_gui);
_gui->initStaticData();
_screen->loadFont(Screen::FID_6_FNT, "6.FNT");
_screen->loadFont(Screen::FID_8_FNT, "8FAT.FNT");
_screen->loadFont(Screen::FID_BOOKFONT_FNT, "BOOKFONT.FNT");
_screen->setFont(Screen::FID_8_FNT);
_screen->setAnimBlockPtr(3500);
_screen->setScreenDim(0);
_screen->loadPalette("PALETTE.COL", _screen->getPalette(0));
_screen->setScreenPalette(_screen->getPalette(0));
return Common::kNoError;
}
Common::Error KyraEngine_MR::go() {
bool running = true;
preinit();
_screen->hideMouse();
initMainMenu();
_screen->clearPage(0);
_screen->clearPage(2);
const bool firstTimeGame = !saveFileLoadable(0);
if (firstTimeGame) {
playVQA("K3INTRO");
_wasPlayingVQA = false;
}
if (_gameToLoad != -1 || firstTimeGame) {
while (!_screen->isMouseVisible())
_screen->showMouse();
uninitMainMenu();
_musicSoundChannel = -1;
startup();
runLoop();
running = false;
}
while (running && !shouldQuit()) {
_screen->_curPage = 0;
_screen->clearPage(0);
_screen->setScreenPalette(_screen->getPalette(0));
playMenuAudioFile();
for (int i = 0; i < 64 && !shouldQuit(); ++i) {
uint32 nextRun = _system->getMillis() + 3 * _tickLength;
_menuAnim->displayFrame(i, 0, 0, 0, 0, 0, 0);
_screen->updateScreen();
delayUntil(nextRun);
}
for (int i = 64; i > 29 && !shouldQuit(); --i) {
uint32 nextRun = _system->getMillis() + 3 * _tickLength;
_menuAnim->displayFrame(i, 0, 0, 0, 0, 0, 0);
_screen->updateScreen();
delayUntil(nextRun);
}
_eventList.clear();
switch (_menu->handle(3)) {
case 2:
_menuDirectlyToLoad = true;
// fall through
case 0:
uninitMainMenu();
fadeOutMusic(60);
_screen->fadeToBlack(60);
_musicSoundChannel = -1;
startup();
runLoop();
running = false;
break;
case 1:
playVQA("K3INTRO");
_wasPlayingVQA = false;
_screen->hideMouse();
break;
case 3:
fadeOutMusic(60);
_screen->fadeToBlack(60);
uninitMainMenu();
quitGame();
running = false;
break;
default:
break;
}
}
if (_showOutro && !shouldQuit())
playVQA("CREDITS");
return Common::kNoError;
}
void KyraEngine_MR::initMainMenu() {
_menuAnim = new WSAMovie_v2(this);
_menuAnim->open("REVENGE.WSA", 1, &_screen->getPalette(0));
_screen->getPalette(0).fill(0, 1, 0);
_menu = new MainMenu(this);
MainMenu::StaticData data = {
{ _mainMenuStrings[_lang*4+0], _mainMenuStrings[_lang*4+1], _mainMenuStrings[_lang*4+2], _mainMenuStrings[_lang*4+3], 0 },
{ 0x01, 0x04, 0x0C, 0x04, 0x00, 0x80, 0xFF },
{ 0x16, 0x19, 0x1A, 0x16 },
Screen::FID_8_FNT, 240
};
if (_flags.lang == Common::ES_ESP) {
for (int i = 0; i < 4; ++i)
data.strings[i] = _mainMenuSpanishFan[i];
} else if (_flags.lang == Common::IT_ITA) {
for (int i = 0; i < 4; ++i)
data.strings[i] = _mainMenuItalianFan[i];
}
MainMenu::Animation anim;
anim.anim = _menuAnim;
anim.startFrame = 29;
anim.endFrame = 63;
anim.delay = 2;
_menu->init(data, anim);
}
void KyraEngine_MR::uninitMainMenu() {
delete _menuAnim;
_menuAnim = 0;
delete _menu;
_menu = 0;
}
void KyraEngine_MR::playVQA(const char *name) {
VQAMovie vqa(this, _system);
Common::String filename = Common::String::format("%s%d.VQA", name, _configVQAQuality);
if (vqa.open(filename.c_str())) {
for (int i = 0; i < 4; ++i) {
if (i != _musicSoundChannel)
_soundDigital->stopSound(i);
}
_screen->hideMouse();
_screen->copyPalette(1, 0);
fadeOutMusic(60);
_screen->fadeToBlack(60);
_screen->clearPage(0);
vqa.play();
vqa.close();
_soundDigital->stopAllSounds();
_screen->showMouse();
// Taken from original, it used '1' here too
_screen->getPalette(0).fill(0, 256, 1);
_screen->setScreenPalette(_screen->getPalette(0));
_screen->clearPage(0);
_screen->copyPalette(0, 1);
_wasPlayingVQA = true;
}
}
#pragma mark -
void KyraEngine_MR::playMenuAudioFile() {
if (_soundDigital->isPlaying(_musicSoundChannel))
return;
_musicSoundChannel = _soundDigital->playSound(_menuAudioFile, 0xFF, Audio::Mixer::kMusicSoundType, 255, true);
}
void KyraEngine_MR::snd_playWanderScoreViaMap(int track, int force) {
if (_musicSoundChannel != -1 && !_soundDigital->isPlaying(_musicSoundChannel))
force = 1;
else if (_musicSoundChannel == -1)
force = 1;
if (track == _lastMusicCommand && !force)
return;
stopMusicTrack();
if (_musicSoundChannel == -1) {
assert(track < _soundListSize && track >= 0);
_musicSoundChannel = _soundDigital->playSound(_soundList[track], 0xFF, Audio::Mixer::kMusicSoundType, 255, true);
}
_lastMusicCommand = track;
}
void KyraEngine_MR::stopMusicTrack() {
if (_musicSoundChannel != -1 && _soundDigital->isPlaying(_musicSoundChannel))
_soundDigital->stopSound(_musicSoundChannel);
_lastMusicCommand = -1;
_musicSoundChannel = -1;
}
void KyraEngine_MR::fadeOutMusic(int ticks) {
if (_musicSoundChannel >= 0) {
_fadeOutMusicChannel = _musicSoundChannel;
_soundDigital->beginFadeOut(_musicSoundChannel, ticks);
_lastMusicCommand = -1;
}
}
void KyraEngine_MR::snd_playSoundEffect(int item, int volume) {
if (_sfxFileMap[item*2+0] != 0xFF) {
assert(_sfxFileMap[item*2+0] < _sfxFileListSize);
Common::String filename = Common::String::format("%s", _sfxFileList[_sfxFileMap[item*2+0]]);
uint8 priority = _sfxFileMap[item*2+1];
_soundDigital->playSound(filename.c_str(), priority, Audio::Mixer::kSFXSoundType, volume);
}
}
void KyraEngine_MR::playVoice(int high, int low) {
snd_playVoiceFile(high * 1000 + low);
}
void KyraEngine_MR::snd_playVoiceFile(int file) {
Common::String filename = Common::String::format("%.08u", (uint)file);
if (speechEnabled())
_voiceSoundChannel = _soundDigital->playSound(filename.c_str(), 0xFE, Audio::Mixer::kSpeechSoundType, 255);
}
bool KyraEngine_MR::snd_voiceIsPlaying() {
return _soundDigital->isPlaying(_voiceSoundChannel);
}
void KyraEngine_MR::snd_stopVoice() {
if (_voiceSoundChannel != -1)
_soundDigital->stopSound(_voiceSoundChannel);
}
void KyraEngine_MR::playStudioSFX(const char *str) {
if (!_configStudio)
return;
if (_rnd.getRandomNumberRng(1, 2) != 2)
return;
const int strSize = strlen(str) - 1;
if (str[strSize] != '?' && str[strSize] != '!')
return;
snd_playSoundEffect(_curStudioSFX++, 128);
if (_curStudioSFX > 291)
_curStudioSFX = 283;
}
#pragma mark -
void KyraEngine_MR::preinit() {
_itemBuffer1 = new int8[72];
_itemBuffer2 = new int8[144];
initMouseShapes();
initItems();
_screen->setMouseCursor(0, 0, _gameShapes[0]);
}
void KyraEngine_MR::initMouseShapes() {
uint8 *data = _res->fileData("MOUSE.SHP", 0);
assert(data);
for (int i = 0; i <= 6; ++i)
_gameShapes[i] = _screen->makeShapeCopy(data, i);
delete[] data;
}
void KyraEngine_MR::startup() {
_album.wsa = new WSAMovie_v2(this);
assert(_album.wsa);
_album.leftPage.wsa = new WSAMovie_v2(this);
assert(_album.leftPage.wsa);
_album.rightPage.wsa = new WSAMovie_v2(this);
assert(_album.rightPage.wsa);
_gamePlayBuffer = new uint8[64000];
_interface = new uint8[17920];
_interfaceCommandLine = new uint8[3840];
_screen->setFont(Screen::FID_8_FNT);
_stringBuffer = new char[500];
allocAnimObjects(1, 16, 50);
memset(_sceneShapes, 0, sizeof(_sceneShapes));
_screenBuffer = new uint8[64000];
if (!loadLanguageFile("ITEMS.", _itemFile))
error("Couldn't load ITEMS");
if (!loadLanguageFile("SCORE.", _scoreFile))
error("Couldn't load SCORE");
if (!loadLanguageFile("C_CODE.", _cCodeFile))
error("Couldn't load C_CODE");
if (!loadLanguageFile("SCENES.", _scenesFile))
error("Couldn't load SCENES");
if (!loadLanguageFile("OPTIONS.", _optionsFile))
error("Couldn't load OPTIONS");
if (!loadLanguageFile("_ACTOR.", _actorFile))
error("couldn't load _ACTOR");
openTalkFile(0);
_currentTalkFile = 0;
openTalkFile(1);
loadCostPal();
for (int i = 0; i < 16; ++i) {
_sceneAnims[i].flags = 0;
_sceneAnimMovie[i] = new WSAMovie_v2(this);
assert(_sceneAnimMovie[i]);
}
_screen->_curPage = 0;
_talkObjectList = new TalkObject[88];
memset(_talkObjectList, 0, sizeof(TalkObject)*88);
for (int i = 0; i < 88; ++i)
_talkObjectList[i].sceneId = 0xFF;
_gfxBackUpRect = new uint8[_screen->getRectSize(32, 32)];
initItemList(50);
resetItemList();
loadShadowShape();
loadExtrasShapes();
_characterShapeFile = 0;
loadCharacterShapes(_characterShapeFile);
updateMalcolmShapes();
initMainButtonList(true);
loadButtonShapes();
loadInterfaceShapes();
_screen->loadPalette("PALETTE.COL", _screen->getPalette(0));
_paletteOverlay = new uint8[256];
_screen->generateOverlay(_screen->getPalette(0), _paletteOverlay, 0xF0, 0x19);
loadInterface();
clearAnimObjects();
_scoreMax = 0;
for (int i = 0; i < _scoreTableSize; ++i) {
if (_scoreTable[i] > 0)
_scoreMax += _scoreTable[i];
}
memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState));
memset(_conversationState, -1, sizeof(_conversationState));
_sceneList = new SceneDesc[98];
assert(_sceneList);
memset(_sceneList, 0, sizeof(SceneDesc)*98);
_sceneListSize = 98;
runStartupScript(1, 0);
_res->exists("MOODOMTR.WSA", true);
_invWsa = new WSAMovie_v2(this);
assert(_invWsa);
_invWsa->open("MOODOMTR.WSA", 1, 0);
_invWsaFrame = 6;
saveGameStateIntern(0, "New Game", 0);
if (_gameToLoad == -1)
enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1);
else
loadGameStateCheck(_gameToLoad);
if (_menuDirectlyToLoad)
(*_mainButtonData[0].buttonCallback)(&_mainButtonData[0]);
_screen->updateScreen();
_screen->showMouse();
setNextIdleAnimTimer();
setWalkspeed(_configWalkspeed);
}
void KyraEngine_MR::loadCostPal() {
_res->exists("_COSTPAL.DAT", true);
uint32 size = 0;
_costPalBuffer = _res->fileData("_COSTPAL.DAT", &size);
assert(_costPalBuffer);
assert(size == 864);
}
void KyraEngine_MR::loadShadowShape() {
_screen->loadBitmap("SHADOW.CSH", 3, 3, 0);
addShapeToPool(_screen->getCPagePtr(3), 421, 0);
}
void KyraEngine_MR::loadExtrasShapes() {
_screen->loadBitmap("EXTRAS.CSH", 3, 3, 0);
for (int i = 0; i < 20; ++i)
addShapeToPool(_screen->getCPagePtr(3), i+433, i);
addShapeToPool(_screen->getCPagePtr(3), 453, 20);
addShapeToPool(_screen->getCPagePtr(3), 454, 21);
}
void KyraEngine_MR::loadInterfaceShapes() {
_screen->loadBitmap("INTRFACE.CSH", 3, 3, 0);
for (int i = 422; i <= 432; ++i)
addShapeToPool(_screen->getCPagePtr(3), i, i-422);
}
void KyraEngine_MR::loadInterface() {
_screen->loadBitmap("INTRFACE.CPS", 3, 3, 0);
memcpy(_interface, _screen->getCPagePtr(3), 17920);
memcpy(_interfaceCommandLine, _screen->getCPagePtr(3), 3840);
}
void KyraEngine_MR::initItems() {
_screen->loadBitmap("ITEMS.CSH", 3, 3, 0);
for (int i = 248; i <= 319; ++i)
addShapeToPool(_screen->getCPagePtr(3), i, i-248);
_screen->loadBitmap("ITEMS2.CSH", 3, 3, 0);
for (int i = 320; i <= 397; ++i)
addShapeToPool(_screen->getCPagePtr(3), i, i-320);
uint32 size = 0;
uint8 *itemsDat = _res->fileData("_ITEMS.DAT", &size);
assert(size >= 72+144);
memcpy(_itemBuffer1, itemsDat , 72);
memcpy(_itemBuffer2, itemsDat+72, 144);
delete[] itemsDat;
_screen->_curPage = 0;
}
void KyraEngine_MR::runStartupScript(int script, int unk1) {
EMCState state;
EMCData data;
memset(&state, 0, sizeof(state));
memset(&data, 0, sizeof(data));
char filename[13];
strcpy(filename, "_START0X.EMC");
filename[7] = (script % 10) + '0';
_emc->load(filename, &data, &_opcodes);
_emc->init(&state, &data);
_emc->start(&state, 0);
state.regs[6] = unk1;
while (_emc->isValid(&state))
_emc->run(&state);
_emc->unload(&data);
}
void KyraEngine_MR::openTalkFile(int file) {
char talkFilename[16];
if (file == 0) {
strcpy(talkFilename, "ANYTALK.TLK");
} else {
if (_currentTalkFile > 0) {
sprintf(talkFilename, "CH%dTALK.TLK", _currentTalkFile);
_res->unloadPakFile(talkFilename);
}
sprintf(talkFilename, "CH%dTALK.TLK", file);
}
_currentTalkFile = file;
if (!_res->loadPakFile(talkFilename)) {
if (speechEnabled()) {
warning("Couldn't load voice file '%s', falling back to text only mode", talkFilename);
_configVoice = 0;
// Sync the config manager with the new settings
writeSettings();
}
}
}
#pragma mark -
void KyraEngine_MR::loadCharacterShapes(int newShapes) {
static const uint8 numberOffset[] = { 3, 3, 4, 4, 3, 3 };
static const uint8 startShape[] = { 0x32, 0x58, 0x78, 0x98, 0xB8, 0xD8 };
static const uint8 endShape[] = { 0x57, 0x77, 0x97, 0xB7, 0xD7, 0xF7 };
static const char *const filenames[] = {
"MSW##.SHP",
"MTA##.SHP",
"MTFL##.SHP",
"MTFR##.SHP",
"MTL##.SHP",
"MTR##.SHP"
};
for (int i = 50; i <= 247; ++i) {
if (i == 87)
continue;
ShapeMap::iterator iter = _gameShapes.find(i);
if (iter != _gameShapes.end()) {
delete[] iter->_value;
iter->_value = 0;
}
}
const char lowNum = (newShapes % 10) + '0';
const char highNum = (newShapes / 10) + '0';
for (int i = 0; i < 6; ++i) {
char filename[16];
strcpy(filename, filenames[i]);
filename[numberOffset[i]+0] = highNum;
filename[numberOffset[i]+1] = lowNum;
_res->exists(filename, true);
_res->loadFileToBuf(filename, _screenBuffer, 64000);
for (int j = startShape[i]; j <= endShape[i]; ++j) {
if (j == 87)
continue;
addShapeToPool(_screenBuffer, j, j-startShape[i]);
}
}
_characterShapeFile = newShapes;
updateMalcolmShapes();
}
void KyraEngine_MR::updateMalcolmShapes() {
assert(_characterShapeFile >= 0 && _characterShapeFile < _shapeDescsSize);
_malcolmShapeXOffset = _shapeDescs[_characterShapeFile].xOffset;
_malcolmShapeYOffset = _shapeDescs[_characterShapeFile].yOffset;
_animObjects[0].width = _shapeDescs[_characterShapeFile].width;
_animObjects[0].height = _shapeDescs[_characterShapeFile].height;
}
#pragma mark -
int KyraEngine_MR::getCharacterWalkspeed() const {
return _mainCharacter.walkspeed;
}
void KyraEngine_MR::updateCharAnimFrame(int *table) {
++_mainCharacter.animFrame;
int facing = _mainCharacter.facing;
if (table) {
if (table[0] != table[-1] && table[1] == table[-1]) {
facing = getOppositeFacingDirection(table[-1]);
table[0] = table[-1];
}
}
if (facing) {
if (facing == 7 || facing == 1) {
if (_characterAnimTable[0] > 2)
facing = 0;
memset(_characterAnimTable, 0, sizeof(_characterAnimTable));
} else if (facing == 4) {
++_characterAnimTable[1];
} else if (facing == 5 || facing == 3) {
if (_characterAnimTable[1] > 2)
facing = 4;
memset(_characterAnimTable, 0, sizeof(_characterAnimTable));
}
} else {
++_characterAnimTable[0];
}
switch (facing) {
case 0:
if (_mainCharacter.animFrame < 79 || _mainCharacter.animFrame > 86)
_mainCharacter.animFrame = 79;
break;
case 1: case 2: case 3:
if (_mainCharacter.animFrame < 71 || _mainCharacter.animFrame > 78)
_mainCharacter.animFrame = 71;
break;
case 4:
if (_mainCharacter.animFrame < 55 || _mainCharacter.animFrame > 62)
_mainCharacter.animFrame = 55;
break;
case 5: case 6: case 7:
if (_mainCharacter.animFrame < 63 || _mainCharacter.animFrame > 70)
_mainCharacter.animFrame = 63;
break;
default:
break;
}
updateCharacterAnim(0);
}
void KyraEngine_MR::updateCharPal(int unk1) {
int layer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1) - 1;
const uint8 *src = _costPalBuffer + _characterShapeFile * 72;
Palette &dst = _screen->getPalette(0);
const int8 *sceneDatPal = &_sceneDatPalette[layer * 3];
if (layer != _lastCharPalLayer && unk1) {
for (int i = 144; i < 168; ++i) {
for (int j = 0; j < 3; ++j) {
uint8 col = dst[i * 3 + j];
int subCol = src[(i - 144) * 3 + j] + sceneDatPal[j];
subCol = CLIP(subCol, 0, 63);
subCol = (col - subCol) / 2;
dst[i * 3 + j] -= subCol;
}
}
_charPalUpdate = true;
_screen->setScreenPalette(_screen->getPalette(0));
_lastCharPalLayer = layer;
} else if (_charPalUpdate || !unk1) {
dst.copy(_costPalBuffer, _characterShapeFile * 24, 24, 144);
for (int i = 144; i < 168; ++i) {
for (int j = 0; j < 3; ++j) {
int col = dst[i * 3 + j] + sceneDatPal[j];
dst[i * 3 + j] = CLIP(col, 0, 63);
}
}
_screen->setScreenPalette(_screen->getPalette(0));
_charPalUpdate = false;
}
}
bool KyraEngine_MR::checkCharCollision(int x, int y) {
int scale = getScale(_mainCharacter.x1, _mainCharacter.y1);
int width = (scale * 37) >> 8;
int height = (scale * 76) >> 8;
int x1 = _mainCharacter.x1 - width/2;
int x2 = _mainCharacter.x1 + width/2;
int y1 = _mainCharacter.y1 - height;
int y2 = _mainCharacter.y1;
if (x >= x1 && x <= x2 && y >= y1 && y <= y2)
return true;
return false;
}
#pragma mark -
void KyraEngine_MR::runLoop() {
// Initialize debugger since how it should be fully usable
_debugger->initialize();
_eventList.clear();
_runFlag = true;
while (_runFlag && !shouldQuit()) {
if (_deathHandler >= 0) {
removeHandItem();
delay(5);
_drawNoShapeFlag = 0;
_gui->optionsButton(0);
_deathHandler = -1;
if (!_runFlag || shouldQuit())
break;
}
if (_system->getMillis() >= _nextIdleAnim)
showIdleAnim();
int inputFlag = checkInput(_mainButtonList, true);
removeInputTop();
update();
_timer->update();
if (inputFlag == 198 || inputFlag == 199) {
_savedMouseState = _mouseState;
Common::Point mouse = getMousePos();
handleInput(mouse.x, mouse.y);
}
_system->delayMillis(10);
}
}
void KyraEngine_MR::handleInput(int x, int y) {
if (_inventoryState)
return;
setNextIdleAnimTimer();
if (_unk5) {
_unk5 = 0;
return;
}
if (!_screen->isMouseVisible())
return;
if (_savedMouseState == -3) {
snd_playSoundEffect(0x0D, 0x80);
return;
}
setNextIdleAnimTimer();
int skip = 0;
if (checkCharCollision(x, y) && _savedMouseState >= -1 && runSceneScript2()) {
return;
} else if (_itemInHand != 27 && pickUpItem(x, y, 1)) {
return;
} else if (checkItemCollision(x, y) == -1) {
resetGameFlag(1);
skip = runSceneScript1(x, y);
if (queryGameFlag(1)) {
resetGameFlag(1);
return;
} else if (_unk5) {
_unk5 = 0;
return;
}
}
if (_deathHandler >= 0)
skip = 1;
if (skip)
return;
if (checkCharCollision(x, y)) {
if (runSceneScript2())
return;
} else if (_itemInHand >= 0 && _savedMouseState >= 0) {
if (_itemInHand == 27) {
makeCharFacingMouse();
} else if (y <= 187) {
if (_itemInHand == 43)
removeHandItem();
else
dropItem(0, _itemInHand, x, y, 1);
}
return;
} else if (_savedMouseState == -3) {
return;
} else {
if (y > 187 && _savedMouseState > -4)
return;
if (_unk5) {
_unk5 = 0;
return;
}
}
inputSceneChange(x, y, 1, 1);
}
int KyraEngine_MR::inputSceneChange(int x, int y, int unk1, int unk2) {
uint16 curScene = _mainCharacter.sceneId;
_pathfinderFlag = 15;
if (!_unkHandleSceneChangeFlag) {
if (_savedMouseState == -4) {
if (_sceneList[curScene].exit4 != 0xFFFF) {
x = 4;
y = _sceneEnterY4;
_pathfinderFlag = 7;
}
} else if (_savedMouseState == -6) {
if (_sceneList[curScene].exit2 != 0xFFFF) {
x = 316;
y = _sceneEnterY2;
_pathfinderFlag = 7;
}
} else if (_savedMouseState == -7) {
if (_sceneList[curScene].exit1 != 0xFFFF) {
x = _sceneEnterX1;
y = _sceneEnterY1 - 2;
_pathfinderFlag = 14;
}
} else if (_savedMouseState == -5) {
if (_sceneList[curScene].exit3 != 0xFFFF) {
x = _sceneEnterX3;
y = 191;
_pathfinderFlag = 11;
}
}
}
if (ABS(_mainCharacter.x1 - x) < 4 && ABS(_mainCharacter.y1 - y) < 2) {
_pathfinderFlag = 0;
return 0;
}
int x1 = _mainCharacter.x1 & (~3);
int y1 = _mainCharacter.y1 & (~1);
x &= ~3;
y &= ~1;
int size = findWay(x1, y1, x, y, _movFacingTable, 600);
_pathfinderFlag = 0;
if (!size || size == 0x7D00)
return 0;
return trySceneChange(_movFacingTable, unk1, unk2);
}
void KyraEngine_MR::update() {
updateInput();
refreshAnimObjectsIfNeed();
updateMouse();
updateSpecialSceneScripts();
updateCommandLine();
updateItemAnimations();
_screen->updateScreen();
}
void KyraEngine_MR::updateWithText() {
updateInput();
updateMouse();
updateItemAnimations();
updateSpecialSceneScripts();
updateCommandLine();
restorePage3();
drawAnimObjects();
if (_chatTextEnabled && _chatText) {
int curPage = _screen->_curPage;
_screen->_curPage = 2;
objectChatPrintText(_chatText, _chatObject);
_screen->_curPage = curPage;
}
refreshAnimObjects(0);
_screen->updateScreen();
}
void KyraEngine_MR::updateMouse() {
int shape = 0, offsetX = 0, offsetY = 0;
Common::Point mouse = getMousePos();
bool hasItemCollision = checkItemCollision(mouse.x, mouse.y) != -1;
if (mouse.y > 187) {
bool setItemCursor = false;
if (_mouseState == -6) {
if (mouse.x < 311)
setItemCursor = true;
} else if (_mouseState == -5) {
if (mouse.x < _sceneMinX || mouse.x > _sceneMaxX)
setItemCursor = true;
} else if (_mouseState == -4) {
if (mouse.x > 8)
setItemCursor = true;
}
if (setItemCursor) {
setItemMouseCursor();
return;
}
}
if (_inventoryState) {
if (mouse.y >= 144)
return;
hideInventory();
}
if (hasItemCollision && _mouseState < -1 && _itemInHand < 0) {
_mouseState = kItemNone;
_itemInHand = kItemNone;
_screen->setMouseCursor(0, 0, _gameShapes[0]);
}
int type = 0;
if (mouse.y <= 199) {
if (mouse.x <= 8) {
if (_sceneExit4 != 0xFFFF) {
type = -4;
shape = 4;
offsetX = 0;
offsetY = 0;
}
} else if (mouse.x >= 311) {
if (_sceneExit2 != 0xFFFF) {
type = -6;
shape = 2;
offsetX = 13;
offsetY = 8;
}
} else if (mouse.y >= 171) {
if (_sceneExit3 != 0xFFFF) {
if (mouse.x >= _sceneMinX && mouse.x <= _sceneMaxX) {
type = -5;
shape = 3;
offsetX = 8;
offsetY = 13;
}
}
} else if (mouse.y <= 8) {
if (_sceneExit1 != 0xFFFF) {
type = -7;
shape = 1;
offsetX = 8;
offsetY = 0;
}
}
}
for (int i = 0; i < _specialExitCount; ++i) {
if (checkSpecialSceneExit(i, mouse.x, mouse.y)) {
switch (_specialExitTable[20+i]) {
case 0:
type = -7;
shape = 1;
offsetX = 8;
offsetY = 0;
break;
case 2:
type = -6;
shape = 2;
offsetX = 13;
offsetY = 8;
break;
case 4:
type = -5;
shape = 3;
offsetX = 8;
offsetY = 13;
break;
case 6:
type = -4;
shape = 4;
offsetX = 0;
offsetY = 8;
break;
default:
break;
}
}
}
if (type != 0 && type != _mouseState && !hasItemCollision) {
_mouseState = type;
_screen->setMouseCursor(offsetX, offsetY, _gameShapes[shape]);
} else if (type == 0 && _mouseState != _itemInHand && mouse.x > 8 && mouse.x < 311 && mouse.y < 171 && mouse.y > 8) {
setItemMouseCursor();
} else if (mouse.y > 187 && _mouseState > -4 && type == 0 && !_inventoryState) {
showInventory();
}
}
#pragma mark -
void KyraEngine_MR::makeCharFacingMouse() {
if (_mainCharacter.x1 > _mouseX)
_mainCharacter.facing = 5;
else
_mainCharacter.facing = 3;
_mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing];
updateCharacterAnim(0);
refreshAnimObjectsIfNeed();
}
#pragma mark -
int KyraEngine_MR::getDrawLayer(int x, int y) {
int layer = _screen->getLayer(x, y) - 1;
layer = _sceneDatLayerTable[layer];
return MAX(0, MIN(layer, 6));
}
int KyraEngine_MR::getScale(int x, int y) {
return _scaleTable[_screen->getLayer(x, y) - 1];
}
#pragma mark -
void KyraEngine_MR::backUpGfxRect32x32(int x, int y) {
_screen->copyRegionToBuffer(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect);
}
void KyraEngine_MR::restoreGfxRect32x32(int x, int y) {
_screen->copyBlockToPage(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect);
}
#pragma mark -
int KyraEngine_MR::loadLanguageFile(const char *file, uint8 *&buffer) {
delete[] buffer;
buffer = 0;
uint32 size = 0;
Common::String nBuf = file;
nBuf += _languageExtension[_lang];
buffer = _res->fileData(nBuf.c_str(), &size);
return buffer ? size : 0;
}
uint8 *KyraEngine_MR::getTableEntry(uint8 *buffer, int id) {
uint16 tableEntries = READ_LE_UINT16(buffer);
const uint16 *indexTable = (const uint16 *)(buffer + 2);
const uint16 *offsetTable = indexTable + tableEntries;
int num = 0;
while (id != READ_LE_UINT16(indexTable)) {
++indexTable;
++num;
}
return buffer + READ_LE_UINT16(offsetTable + num);
}
void KyraEngine_MR::getTableEntry(Common::SeekableReadStream *stream, int id, char *dst) {
stream->seek(0, SEEK_SET);
uint16 tableEntries = stream->readUint16LE();
int num = 0;
while (id != stream->readUint16LE())
++num;
stream->seek(2+tableEntries*2+num*2, SEEK_SET);
stream->seek(stream->readUint16LE(), SEEK_SET);
char c = 0;
while ((c = stream->readByte()) != 0)
*dst++ = c;
*dst = 0;
}
#pragma mark -
bool KyraEngine_MR::talkObjectsInCurScene() {
for (int i = 0; i < 88; ++i) {
if (_talkObjectList[i].sceneId == _mainCharacter.sceneId)
return true;
}
return false;
}
#pragma mark -
bool KyraEngine_MR::updateScore(int scoreId, int strId) {
int scoreIndex = (scoreId >> 3);
int scoreBit = scoreId & 7;
if ((_scoreFlagTable[scoreIndex] & (1 << scoreBit)) != 0)
return false;
setNextIdleAnimTimer();
_scoreFlagTable[scoreIndex] |= (1 << scoreBit);
strcpy(_stringBuffer, (const char *)getTableEntry(_scoreFile, strId));
strcat(_stringBuffer, ": ");
assert(scoreId < _scoreTableSize);
int count = _scoreTable[scoreId];
if (count > 0)
scoreIncrease(count, _stringBuffer);
setNextIdleAnimTimer();
return true;
}
void KyraEngine_MR::scoreIncrease(int count, const char *str) {
int drawOld = 1;
_screen->hideMouse();
showMessage(str, 0xFF, 0xF0);
const int x = getScoreX(str);
for (int i = 0; i < count; ++i) {
int oldScore = _score;
int newScore = ++_score;
if (newScore > _scoreMax) {
_score = _scoreMax;
break;
}
drawScoreCounting(oldScore, newScore, drawOld, x);
if (_inventoryState)
drawScore(0, 215, 191);
_screen->updateScreen();
delay(20, true);
snd_playSoundEffect(0x0E, 0xC8);
drawOld = 0;
}
_screen->showMouse();
}
#pragma mark -
void KyraEngine_MR::changeChapter(int newChapter, int sceneId, int malcolmShapes, int facing) {
resetItemList();
_currentChapter = newChapter;
runStartupScript(newChapter, 0);
_mainCharacter.dlgIndex = 0;
_malcolmsMood = 1;
memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState));
if (malcolmShapes >= 0)
loadCharacterShapes(malcolmShapes);
enterNewScene(sceneId, facing, 0, 0, 0);
}
#pragma mark -
bool KyraEngine_MR::skipFlag() const {
if (!_configSkip)
return false;
return KyraEngine_v2::skipFlag();
}
void KyraEngine_MR::resetSkipFlag(bool removeEvent) {
if (!_configSkip) {
if (removeEvent)
_eventList.clear();
return;
}
KyraEngine_v2::resetSkipFlag(removeEvent);
}
#pragma mark -
void KyraEngine_MR::registerDefaultSettings() {
KyraEngine_v1::registerDefaultSettings();
// Most settings already have sensible defaults. This one, however, is
// specific to the Kyra engine.
ConfMan.registerDefault("walkspeed", 5);
ConfMan.registerDefault("studio_audience", true);
ConfMan.registerDefault("skip_support", true);
ConfMan.registerDefault("helium_mode", false);
// 0 - best, 1 - mid, 2 - low
ConfMan.registerDefault("video_quality", 0);
}
void KyraEngine_MR::writeSettings() {
switch (_lang) {
case 1:
_flags.lang = Common::FR_FRA;
break;
case 2:
_flags.lang = Common::DE_DEU;
break;
case 0:
default:
_flags.lang = Common::EN_ANY;
}
if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG)
_flags.lang = _flags.fanLang;
ConfMan.set("language", Common::getLanguageCode(_flags.lang));
ConfMan.setBool("studio_audience", _configStudio);
ConfMan.setBool("skip_support", _configSkip);
ConfMan.setBool("helium_mode", _configHelium);
KyraEngine_v1::writeSettings();
}
void KyraEngine_MR::readSettings() {
KyraEngine_v2::readSettings();
_configStudio = ConfMan.getBool("studio_audience");
_configSkip = ConfMan.getBool("skip_support");
_configHelium = ConfMan.getBool("helium_mode");
_configVQAQuality = CLIP(ConfMan.getInt("video_quality"), 0, 2);
}
} // End of namespace Kyra