aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/kyra.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/kyra/kyra.cpp')
-rw-r--r--engines/kyra/kyra.cpp1215
1 files changed, 1215 insertions, 0 deletions
diff --git a/engines/kyra/kyra.cpp b/engines/kyra/kyra.cpp
new file mode 100644
index 0000000000..c85ce9b323
--- /dev/null
+++ b/engines/kyra/kyra.cpp
@@ -0,0 +1,1215 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/stdafx.h"
+
+#include "backends/fs/fs.h"
+
+#include "base/gameDetector.h"
+#include "base/plugins.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "common/md5.h"
+#include "common/savefile.h"
+
+#include "sound/mixer.h"
+#include "sound/mididrv.h"
+
+#include "gui/message.h"
+
+#include "kyra/kyra.h"
+#include "kyra/resource.h"
+#include "kyra/screen.h"
+#include "kyra/script.h"
+#include "kyra/seqplayer.h"
+#include "kyra/sound.h"
+#include "kyra/sprites.h"
+#include "kyra/wsamovie.h"
+#include "kyra/animator.h"
+#include "kyra/text.h"
+#include "kyra/debugger.h"
+
+using namespace Kyra;
+
+enum {
+ // We only compute MD5 of the first megabyte of our data files.
+ kMD5FileSizeLimit = 1024 * 1024
+};
+
+// Kyra MD5 detection brutally ripped from the Gobliins engine.
+struct KyraGameSettings {
+ const char *gameid;
+ const char *description;
+ byte id;
+ uint32 features;
+ const char *md5sum;
+ const char *checkFile;
+ GameSettings toGameSettings() const {
+ GameSettings dummy = { gameid, description, features };
+ return dummy;
+ }
+};
+
+// We could get rid of md5 detection at least for kyra 1 since we can locate all
+// needed files for detecting the right language and version (Floppy, Talkie)
+static const KyraGameSettings kyra_games[] = {
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_ENGLISH | GF_FLOPPY, // english floppy 1.0 from Malice
+ "3c244298395520bb62b5edfe41688879", "GEMCUT.EMC" },
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_ENGLISH | GF_FLOPPY,
+ "796e44863dd22fa635b042df1bf16673", "GEMCUT.EMC" },
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_FRENCH | GF_FLOPPY,
+ "abf8eb360e79a6c2a837751fbd4d3d24", "GEMCUT.EMC" },
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_GERMAN | GF_FLOPPY,
+ "6018e1dfeaca7fe83f8d0b00eb0dd049", "GEMCUT.EMC"},
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_GERMAN | GF_FLOPPY, // from Arne.F
+ "f0b276781f47c130f423ec9679fe9ed9", "GEMCUT.EMC"},
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_SPANISH | GF_FLOPPY, // from VooD
+ "8909b41596913b3f5deaf3c9f1017b01", "GEMCUT.EMC"},
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_SPANISH | GF_FLOPPY, // floppy 1.8 from clemmy
+ "747861d2a9c643c59fdab570df5b9093", "GEMCUT.EMC"},
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_ENGLISH | GF_TALKIE,
+ "fac399fe62f98671e56a005c5e94e39f", "GEMCUT.PAK" },
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_GERMAN | GF_TALKIE,
+ "230f54e6afc007ab4117159181a1c722", "GEMCUT.PAK" },
+ { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_FRENCH | GF_TALKIE,
+ "b037c41768b652a040360ffa3556fd2a", "GEMCUT.PAK" },
+ { "kyra1", "The Legend of Kyrandia Demo", GI_KYRA1, GF_DEMO | GF_ENGLISH,
+ "fb722947d94897512b13b50cc84fd648", "DEMO1.WSA" },
+ { 0, 0, 0, 0, 0, 0 }
+};
+
+// Keep list of different supported games
+struct KyraGameList {
+ const char *gameid;
+ const char *description;
+ uint32 features;
+ GameSettings toGameSettings() const {
+ GameSettings dummy = { gameid, description, features };
+ return dummy;
+ }
+};
+
+static const KyraGameList kyra_list[] = {
+ { "kyra1", "The Legend of Kyrandia", 0 },
+ { 0, 0, 0 }
+};
+
+struct KyraLanguageTable {
+ const char *file;
+ uint32 language;
+ Common::Language detLanguage;
+};
+
+static const KyraLanguageTable kyra_languages[] = {
+ { "MAIN15.CPS", GF_ENGLISH, Common::EN_USA },
+ { "MAIN_ENG.CPS", GF_ENGLISH, Common::EN_USA },
+ { "MAIN_FRE.CPS", GF_FRENCH, Common::FR_FRA },
+ { "MAIN_GER.CPS", GF_GERMAN, Common::DE_DEU },
+ { "MAIN_SPA.CPS", GF_SPANISH, Common::ES_ESP },
+ { 0, 0, Common::UNK_LANG }
+};
+
+static Common::Language convertKyraLang(uint32 features) {
+ if (features & GF_ENGLISH) {
+ return Common::EN_USA;
+ } else if (features & GF_FRENCH) {
+ return Common::FR_FRA;
+ } else if (features & GF_GERMAN) {
+ return Common::DE_DEU;
+ } else if (features & GF_SPANISH) {
+ return Common::ES_ESP;
+ }
+ return Common::UNK_LANG;
+}
+
+GameList Engine_KYRA_gameList() {
+ GameList games;
+ const KyraGameList *g = kyra_list;
+
+ while (g->gameid) {
+ games.push_back(g->toGameSettings());
+ g++;
+ }
+ return games;
+}
+
+DetectedGameList Engine_KYRA_detectGames(const FSList &fslist) {
+ DetectedGameList detectedGames;
+ const KyraGameSettings *g;
+ FSList::const_iterator file;
+
+ // Iterate over all files in the given directory
+ bool isFound = false;
+ for (file = fslist.begin(); file != fslist.end(); file++) {
+ if (file->isDirectory())
+ continue;
+
+ for (g = kyra_games; g->gameid; g++) {
+ if (scumm_stricmp(file->displayName().c_str(), g->checkFile) == 0)
+ isFound = true;
+ }
+ if (isFound)
+ break;
+ }
+
+ if (file == fslist.end())
+ return detectedGames;
+
+ uint8 md5sum[16];
+ char md5str[32 + 1];
+
+ if (Common::md5_file(file->path().c_str(), md5sum, NULL, kMD5FileSizeLimit)) {
+ for (int i = 0; i < 16; i++) {
+ sprintf(md5str + i * 2, "%02x", (int)md5sum[i]);
+ }
+ for (g = kyra_games; g->gameid; g++) {
+ if (strcmp(g->md5sum, (char *)md5str) == 0) {
+ detectedGames.push_back(DetectedGame(g->toGameSettings(), convertKyraLang(g->features), Common::kPlatformUnknown));
+ }
+ }
+ if (detectedGames.isEmpty()) {
+ debug("Unknown MD5 (%s)! Please report the details (language, platform, etc.) of this game to the ScummVM team\n", md5str);
+
+ const KyraGameList *g1 = kyra_list;
+ while (g1->gameid) {
+ detectedGames.push_back(g1->toGameSettings());
+ g1++;
+ }
+ }
+ }
+ return detectedGames;
+}
+
+Engine *Engine_KYRA_create(GameDetector *detector, OSystem *system) {
+ return new KyraEngine(detector, system);
+}
+
+REGISTER_PLUGIN(KYRA, "Legend of Kyrandia Engine")
+
+namespace Kyra {
+
+KyraEngine::KyraEngine(GameDetector *detector, OSystem *system)
+ : Engine(system) {
+ _seq_Forest = _seq_KallakWriting = _seq_KyrandiaLogo = _seq_KallakMalcolm =
+ _seq_MalcolmTree = _seq_WestwoodLogo = _seq_Demo1 = _seq_Demo2 = _seq_Demo3 =
+ _seq_Demo4 = 0;
+
+ _seq_WSATable = _seq_CPSTable = _seq_COLTable = _seq_textsTable = 0;
+ _seq_WSATable_Size = _seq_CPSTable_Size = _seq_COLTable_Size = _seq_textsTable_Size = 0;
+
+ _roomFilenameTable = _characterImageTable = 0;
+ _roomFilenameTableSize = _characterImageTableSize = 0;
+ _itemList = _takenList = _placedList = _droppedList = _noDropList = 0;
+ _itemList_Size = _takenList_Size = _placedList_Size = _droppedList_Size = _noDropList_Size = 0;
+ _putDownFirst = _waitForAmulet = _blackJewel = _poisonGone = _healingTip = 0;
+ _putDownFirst_Size = _waitForAmulet_Size = _blackJewel_Size = _poisonGone_Size = _healingTip_Size = 0;
+ _thePoison = _fluteString = _wispJewelStrings = _magicJewelString = _flaskFull = _fullFlask = 0;
+ _thePoison_Size = _fluteString_Size = _wispJewelStrings_Size = 0;
+ _magicJewelString_Size = _flaskFull_Size = _fullFlask_Size = 0;
+
+ _defaultShapeTable = _healingShapeTable = _healingShape2Table = 0;
+ _defaultShapeTableSize = _healingShapeTableSize = _healingShape2TableSize = 0;
+ _posionDeathShapeTable = _fluteAnimShapeTable = 0;
+ _posionDeathShapeTableSize = _fluteAnimShapeTableSize = 0;
+ _winterScrollTable = _winterScroll1Table = _winterScroll2Table = 0;
+ _winterScrollTableSize = _winterScroll1TableSize = _winterScroll2TableSize = 0;
+ _drinkAnimationTable = _brandonToWispTable = _magicAnimationTable = _brandonStoneTable = 0;
+ _drinkAnimationTableSize = _brandonToWispTableSize = _magicAnimationTableSize = _brandonStoneTableSize = 0;
+
+ // Setup mixer
+ if (!_mixer->isReady()) {
+ warning("Sound initialization failed.");
+ }
+
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));
+
+ // Detect game features based on MD5. Again brutally ripped from Gobliins.
+ uint8 md5sum[16];
+ char md5str[32 + 1];
+
+ const KyraGameSettings *g;
+ bool found = false;
+
+ // TODO
+ // Fallback. Maybe we will be able to determine game type from game
+ // data contents
+ _features = 0;
+
+ for (g = kyra_games; g->gameid; g++) {
+ if (!Common::File::exists(g->checkFile))
+ continue;
+
+ if (Common::md5_file(g->checkFile, md5sum, ConfMan.get("path").c_str(), kMD5FileSizeLimit)) {
+ for (int j = 0; j < 16; j++) {
+ sprintf(md5str + j*2, "%02x", (int)md5sum[j]);
+ }
+ } else
+ continue;
+
+ if (strcmp(g->md5sum, (char *)md5str) == 0) {
+ _features = g->features;
+ _game = g->id;
+
+ if (g->description)
+ g_system->setWindowCaption(g->description);
+
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ debug("Unknown MD5 (%s)! Please report the details (language, platform, etc.) of this game to the ScummVM team", md5str);
+ _features = 0;
+ _game = GI_KYRA1;
+ Common::File test;
+ if (test.open("INTRO.VRM")) {
+ _features |= GF_TALKIE;
+ } else {
+ _features |= GF_FLOPPY;
+ }
+
+ // tries to detect the language
+ const KyraLanguageTable *lang = kyra_languages;
+ for (; lang->file; ++lang) {
+ if (test.open(lang->file)) {
+ _features |= lang->language;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ _features |= GF_LNGUNK;
+ }
+ }
+}
+
+int KyraEngine::init(GameDetector &detector) {
+ _system->beginGFXTransaction();
+ initCommonGFX(detector);
+ //for debug reasons (see Screen::updateScreen)
+ //_system->initSize(640, 200);
+ _system->initSize(320, 200);
+ _system->endGFXTransaction();
+
+ // for now we prefer MIDI-to-Adlib conversion over native midi
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB/* | MDT_PREFER_MIDI*/);
+ bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+
+ MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ if (midiDriver == MD_ADLIB) {
+ // In this case we should play the Adlib tracks, but for now
+ // the automagic MIDI-to-Adlib conversion will do.
+ } else if (native_mt32) {
+ driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+ }
+
+ _sound = new SoundPC(driver, _mixer, this);
+ assert(_sound);
+ static_cast<SoundPC*>(_sound)->hasNativeMT32(native_mt32);
+ _sound->setVolume(255);
+
+ _saveFileMan = _system->getSavefileManager();
+ assert(_saveFileMan);
+ _res = new Resource(this);
+ assert(_res);
+ _screen = new Screen(this, _system);
+ assert(_screen);
+ _sprites = new Sprites(this, _system);
+ assert(_sprites);
+ _seq = new SeqPlayer(this, _system);
+ assert(_seq);
+ _animator = new ScreenAnimator(this, _system);
+ assert(_animator);
+ _animator->init(5, 11, 12);
+ assert(*_animator);
+ _text = new TextDisplayer(_screen);
+ assert(_text);
+
+ _paletteChanged = 1;
+ _currentCharacter = 0;
+ _characterList = new Character[11];
+ assert(_characterList);
+ for (int i = 0; i < 11; ++i) {
+ memset(&_characterList[i], 0, sizeof(Character));
+ memset(_characterList[i].inventoryItems, 0xFF, sizeof(_characterList[i].inventoryItems));
+ }
+ _characterList[0].sceneId = 5;
+ _characterList[0].height = 48;
+ _characterList[0].facing = 3;
+ _characterList[0].currentAnimFrame = 7;
+
+ _scriptInterpreter = new ScriptHelper(this);
+ assert(_scriptInterpreter);
+
+ _npcScriptData = new ScriptData;
+ memset(_npcScriptData, 0, sizeof(ScriptData));
+ assert(_npcScriptData);
+ _npcScript = new ScriptState;
+ assert(_npcScript);
+ memset(_npcScript, 0, sizeof(ScriptState));
+
+ _scriptMain = new ScriptState;
+ assert(_scriptMain);
+ memset(_scriptMain, 0, sizeof(ScriptState));
+
+ _scriptClickData = new ScriptData;
+ assert(_scriptClickData);
+ memset(_scriptClickData, 0, sizeof(ScriptData));
+ _scriptClick = new ScriptState;
+ assert(_scriptClick);
+ memset(_scriptClick, 0, sizeof(ScriptState));
+
+ _debugger = new Debugger(this);
+ assert(_debugger);
+ memset(_shapes, 0, sizeof(_shapes));
+
+ for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) {
+ _movieObjects[i] = createWSAMovie();
+ }
+
+ memset(_flagsTable, 0, sizeof(_flagsTable));
+
+ _abortWalkFlag = false;
+ _abortWalkFlag2 = false;
+ _talkingCharNum = -1;
+ _charSayUnk3 = -1;
+ _mouseX = _mouseY = -1;
+ memset(_currSentenceColor, 0, 3);
+ _startSentencePalIndex = -1;
+ _fadeText = false;
+
+ _cauldronState = 0;
+ _crystalState[0] = _crystalState[1] = -1;
+
+ _brandonStatusBit = 0;
+ _brandonStatusBit0x02Flag = _brandonStatusBit0x20Flag = 10;
+ _brandonPosX = _brandonPosY = -1;
+ _deathHandler = 0xFF;
+ _poisonDeathCounter = 0;
+
+ memset(_itemTable, 0, sizeof(_itemTable));
+ memset(_exitList, 0xFFFF, sizeof(_exitList));
+ _exitListPtr = 0;
+ _pathfinderFlag = _pathfinderFlag2 = 0;
+ _lastFindWayRet = 0;
+ _sceneChangeState = _loopFlag2 = 0;
+ _timerNextRun = 0;
+
+ _movFacingTable = new int[150];
+ assert(_movFacingTable);
+ _movFacingTable[0] = 8;
+
+ _configTalkspeed = 1;
+
+ _marbleVaseItem = -1;
+ memset(_foyerItemTable, -1, sizeof(_foyerItemTable));
+ _mouseState = _itemInHand = -1;
+ _handleInput = false;
+
+ _currentRoom = 0xFFFF;
+ _scenePhasingFlag = 0;
+ _lastProcessedItem = 0;
+ _lastProcessedItemHeight = 16;
+
+ _unkScreenVar1 = 1;
+ _unkScreenVar2 = 0;
+ _unkScreenVar3 = 0;
+ _unkAmuletVar = 0;
+
+ _endSequenceNeedLoading = 1;
+ _malcolmFlag = 0;
+ _beadStateVar = 0;
+ _endSequenceSkipFlag = 0;
+ _unkEndSeqVar2 = 0;
+ _endSequenceBackUpRect = 0;
+ _unkEndSeqVar4 = 0;
+ _unkEndSeqVar5 = 0;
+ _lastDisplayedPanPage = 0;
+ memset(_panPagesTable, 0, sizeof(_panPagesTable));
+ _finalA = _finalB = _finalC = 0;
+ memset(&_kyragemFadingState, 0, sizeof(_kyragemFadingState));
+ _kyragemFadingState.gOffset = 0x13;
+ _kyragemFadingState.bOffset = 0x13;
+
+ memset(_specialPalettes, 0, sizeof(_specialPalettes));
+ _mousePressFlag = false;
+
+ _targetName = detector._targetName;
+ _menuDirectlyToLoad = false;
+
+ _lastMusicCommand = 0;
+
+ _gameSpeed = 60;
+ _tickLength = (uint8)(1000.0 / _gameSpeed);
+
+ return 0;
+}
+
+KyraEngine::~KyraEngine() {
+ closeFinalWsa();
+ _scriptInterpreter->unloadScript(_npcScriptData);
+ _scriptInterpreter->unloadScript(_scriptClickData);
+
+ delete _debugger;
+ delete _sprites;
+ delete _animator;
+ delete _screen;
+ delete _res;
+ delete _sound;
+ delete _saveFileMan;
+ delete _seq;
+ delete _scriptInterpreter;
+ delete _text;
+
+ delete _npcScriptData;
+ delete _scriptMain;
+
+ delete _scriptClickData;
+ delete _scriptClick;
+
+ delete [] _characterList;
+
+ delete [] _movFacingTable;
+
+ free(_scrollUpButton.process0PtrShape);
+ free(_scrollUpButton.process1PtrShape);
+ free(_scrollUpButton.process2PtrShape);
+ free(_scrollDownButton.process0PtrShape);
+ free(_scrollDownButton.process1PtrShape);
+ free(_scrollDownButton.process2PtrShape);
+
+ for (int i = 0; i < ARRAYSIZE(_shapes); ++i) {
+ if (_shapes[i] != 0) {
+ free(_shapes[i]);
+ _shapes[i] = 0;
+ for (int i2 = 0; i2 < ARRAYSIZE(_shapes); i2++) {
+ if (_shapes[i2] == _shapes[i] && i2 != i) {
+ _shapes[i2] = 0;
+ }
+ }
+ }
+ }
+ for (int i = 0; i < ARRAYSIZE(_sceneAnimTable); ++i) {
+ free(_sceneAnimTable[i]);
+ }
+}
+
+void KyraEngine::errorString(const char *buf1, char *buf2) {
+ strcpy(buf2, buf1);
+}
+
+int KyraEngine::go() {
+ _quitFlag = false;
+ uint32 sz;
+
+ res_loadResources();
+ if (_features & GF_FLOPPY) {
+ _screen->loadFont(Screen::FID_6_FNT, _res->fileData("6.FNT", &sz));
+ }
+ _screen->loadFont(Screen::FID_8_FNT, _res->fileData("8FAT.FNT", &sz));
+ _screen->setScreenDim(0);
+
+ _abortIntroFlag = false;
+
+ if (_features & GF_DEMO) {
+ seq_demo();
+ } else {
+ setGameFlag(0xF3);
+ setGameFlag(0xFD);
+ setGameFlag(0xEF);
+ seq_intro();
+ if (_skipIntroFlag &&_abortIntroFlag)
+ resetGameFlag(0xEF);
+ startup();
+ resetGameFlag(0xEF);
+ mainLoop();
+ }
+ quitGame();
+ return 0;
+}
+
+void KyraEngine::startup() {
+ debug(9, "KyraEngine::startup()");
+ static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 };
+ _screen->setTextColorMap(colorMap);
+// _screen->setFont(Screen::FID_6_FNT);
+ _screen->setAnimBlockPtr(3750);
+ memset(_sceneAnimTable, 0, sizeof(_sceneAnimTable));
+ loadMouseShapes();
+ _currentCharacter = &_characterList[0];
+ for (int i = 1; i < 5; ++i)
+ _animator->setCharacterDefaultFrame(i);
+ for (int i = 5; i <= 10; ++i)
+ setCharactersPositions(i);
+ _animator->setCharactersHeight();
+ resetBrandonPoisonFlags();
+ _maskBuffer = _screen->getPagePtr(5);
+ _screen->_curPage = 0;
+ // XXX
+ for (int i = 0; i < 0x0C; ++i) {
+ int size = _screen->getRectSize(3, 24);
+ _shapes[365+i] = (byte*)malloc(size);
+ }
+ _shapes[0] = (uint8*)malloc(_screen->getRectSize(3, 24));
+ memset(_shapes[0], 0, _screen->getRectSize(3, 24));
+ _shapes[1] = (uint8*)malloc(_screen->getRectSize(4, 32));
+ memset(_shapes[1], 0, _screen->getRectSize(4, 32));
+ _shapes[2] = (uint8*)malloc(_screen->getRectSize(8, 69));
+ memset(_shapes[2], 0, _screen->getRectSize(8, 69));
+ _shapes[3] = (uint8*)malloc(_screen->getRectSize(8, 69));
+ memset(_shapes[3], 0, _screen->getRectSize(8, 69));
+ for (int i = 0; i < _roomTableSize; ++i) {
+ for (int item = 0; item < 12; ++item) {
+ _roomTable[i].itemsTable[item] = 0xFF;
+ _roomTable[i].itemsXPos[item] = 0xFFFF;
+ _roomTable[i].itemsYPos[item] = 0xFF;
+ _roomTable[i].needInit[item] = 0;
+ }
+ }
+ loadCharacterShapes();
+ loadSpecialEffectShapes();
+ loadItems();
+ loadButtonShapes();
+ initMainButtonList();
+ loadMainScreen();
+ setupTimers();
+ loadPalette("PALETTE.COL", _screen->_currentPalette);
+
+ // XXX
+ _animator->initAnimStateList();
+ setCharactersInDefaultScene();
+
+ if (!_scriptInterpreter->loadScript("_STARTUP.EMC", _npcScriptData, _opcodeTable, _opcodeTableSize, 0)) {
+ error("Could not load \"_STARTUP.EMC\" script");
+ }
+ _scriptInterpreter->initScript(_scriptMain, _npcScriptData);
+ if (!_scriptInterpreter->startScript(_scriptMain, 0)) {
+ error("Could not start script function 0 of script \"_STARTUP.EMC\"");
+ }
+ while (_scriptInterpreter->validScript(_scriptMain)) {
+ _scriptInterpreter->runScript(_scriptMain);
+ }
+
+ _scriptInterpreter->unloadScript(_npcScriptData);
+ if (!_scriptInterpreter->loadScript("_NPC.EMC", _npcScriptData, _opcodeTable, _opcodeTableSize, 0)) {
+ error("Could not load \"_NPC.EMC\" script");
+ }
+
+ snd_playTheme(1);
+ snd_setSoundEffectFile(1);
+ enterNewScene(_currentCharacter->sceneId, _currentCharacter->facing, 0, 0, 1);
+
+ if (_abortIntroFlag && _skipFlag) {
+ _menuDirectlyToLoad = true;
+ _screen->setMouseCursor(1, 1, _shapes[4]);
+ buttonMenuCallback(0);
+ _menuDirectlyToLoad = false;
+ } else
+ saveGame(getSavegameFilename(0), "New game");
+}
+
+void KyraEngine::mainLoop() {
+ debug(9, "KyraEngine::mainLoop()");
+
+ while (!_quitFlag) {
+ int32 frameTime = (int32)_system->getMillis();
+ _skipFlag = false;
+
+ if (_currentCharacter->sceneId == 210) {
+ updateKyragemFading();
+ if (seq_playEnd()) {
+ if (_deathHandler != 8)
+ break;
+ }
+ }
+
+ if (_deathHandler != 0xFF) {
+ // this is only used until the original gui is implemented
+ GUI::MessageDialog dialog("Brandon is dead! Game over!", "Quit");
+ dialog.runModal();
+ break;
+ }
+
+ if (_brandonStatusBit & 2) {
+ if (_brandonStatusBit0x02Flag)
+ _animator->animRefreshNPC(0);
+ }
+ if (_brandonStatusBit & 0x20) {
+ if (_brandonStatusBit0x20Flag) {
+ _animator->animRefreshNPC(0);
+ _brandonStatusBit0x20Flag = 0;
+ }
+ }
+
+ _screen->showMouse();
+
+ processButtonList(_buttonList);
+ updateMousePointer();
+ updateGameTimers();
+ updateTextFade();
+
+ _handleInput = true;
+ delay((frameTime + _gameSpeed) - _system->getMillis(), true, true);
+ _handleInput = false;
+ }
+}
+
+void KyraEngine::quitGame() {
+ res_unloadResources(RES_ALL);
+
+ for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) {
+ _movieObjects[i]->close();
+ delete _movieObjects[i];
+ _movieObjects[i] = 0;
+ }
+
+ _system->quit();
+}
+
+void KyraEngine::delay(uint32 amount, bool update, bool isMainLoop) {
+ OSystem::Event event;
+ char saveLoadSlot[20];
+ char savegameName[14];
+
+ _mousePressFlag = false;
+ uint32 start = _system->getMillis();
+ do {
+ while (_system->pollEvent(event)) {
+ switch (event.type) {
+ case OSystem::EVENT_KEYDOWN:
+ if (event.kbd.keycode >= '1' && event.kbd.keycode <= '9' &&
+ (event.kbd.flags == OSystem::KBD_CTRL || event.kbd.flags == OSystem::KBD_ALT) && isMainLoop) {
+ sprintf(saveLoadSlot, "%s.00%d", _targetName.c_str(), event.kbd.keycode - '0');
+ if (event.kbd.flags == OSystem::KBD_CTRL)
+ loadGame(saveLoadSlot);
+ else {
+ sprintf(savegameName, "Quicksave %d", event.kbd.keycode - '0');
+ saveGame(saveLoadSlot, savegameName);
+ }
+ } else if (event.kbd.flags == OSystem::KBD_CTRL) {
+ if (event.kbd.keycode == 'd')
+ _debugger->attach();
+ else if (event.kbd.keycode == 'q')
+ _quitFlag = true;
+ } else if (event.kbd.keycode == '.')
+ _skipFlag = true;
+ else if (event.kbd.keycode == 13 || event.kbd.keycode == 32 || event.kbd.keycode == 27) {
+ _abortIntroFlag = true;
+ _skipFlag = true;
+ }
+
+ break;
+ case OSystem::EVENT_MOUSEMOVE:
+ _mouseX = event.mouse.x;
+ _mouseY = event.mouse.y;
+ _system->updateScreen();
+ break;
+ case OSystem::EVENT_QUIT:
+ quitGame();
+ break;
+ case OSystem::EVENT_LBUTTONDOWN:
+ _mousePressFlag = true;
+ if (_abortWalkFlag2) {
+ _abortWalkFlag = true;
+ _mouseX = event.mouse.x;
+ _mouseY = event.mouse.y;
+ }
+ if (_handleInput) {
+ _mouseX = event.mouse.x;
+ _mouseY = event.mouse.y;
+ _handleInput = false;
+ processInput(_mouseX, _mouseY);
+ _handleInput = true;
+ } else
+ _skipFlag = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (_debugger->isAttached())
+ _debugger->onFrame();
+
+ if (update)
+ _sprites->updateSceneAnims();
+ _animator->updateAllObjectShapes();
+
+ if (_currentCharacter && _currentCharacter->sceneId == 210) {
+ updateKyragemFading();
+ }
+
+ if (amount > 0 && !_skipFlag) {
+ _system->delayMillis((amount > 10) ? 10 : amount);
+ }
+ } while (!_skipFlag && _system->getMillis() < start + amount);
+
+}
+
+void KyraEngine::waitForEvent() {
+ bool finished = false;
+ OSystem::Event event;
+ while (!finished) {
+ while (_system->pollEvent(event)) {
+ switch (event.type) {
+ case OSystem::EVENT_KEYDOWN:
+ finished = true;
+ break;
+ case OSystem::EVENT_MOUSEMOVE:
+ _mouseX = event.mouse.x;
+ _mouseY = event.mouse.y;
+ break;
+ case OSystem::EVENT_QUIT:
+ quitGame();
+ break;
+ case OSystem::EVENT_LBUTTONDOWN:
+ finished = true;
+ _skipFlag = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (_debugger->isAttached())
+ _debugger->onFrame();
+
+ _system->delayMillis(10);
+ }
+}
+
+void KyraEngine::delayWithTicks(int ticks) {
+ uint32 nextTime = _system->getMillis() + ticks * _tickLength;
+ while (_system->getMillis() < nextTime && !_skipFlag) {
+ _sprites->updateSceneAnims();
+ _animator->updateAllObjectShapes();
+ if (_currentCharacter->sceneId == 210) {
+ updateKyragemFading();
+ seq_playEnd();
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark - Animation/shape specific code
+#pragma mark -
+
+void KyraEngine::setupShapes123(const Shape *shapeTable, int endShape, int flags) {
+ debug(9, "KyraEngine::setupShapes123(0x%X, startShape, flags)", shapeTable, endShape, flags);
+ for (int i = 123; i <= 172; ++i) {
+ _shapes[4+i] = NULL;
+ }
+ uint8 curImage = 0xFF;
+ int curPageBackUp = _screen->_curPage;
+ _screen->_curPage = 8; // we are using page 8 here in the original page 2 was backuped and then used for this stuff
+ int shapeFlags = 2;
+ if (flags)
+ shapeFlags = 3;
+ for (int i = 123; i < 123+endShape; ++i) {
+ uint8 newImage = shapeTable[i-123].imageIndex;
+ if (newImage != curImage && newImage != 0xFF) {
+ assert(_characterImageTable);
+ loadBitmap(_characterImageTable[newImage], 8, 8, 0);
+ curImage = newImage;
+ }
+ _shapes[4+i] = _screen->encodeShape(shapeTable[i-123].x<<3, shapeTable[i-123].y, shapeTable[i-123].w<<3, shapeTable[i-123].h, shapeFlags);
+ assert(i-7 < _defaultShapeTableSize);
+ _defaultShapeTable[i-7].xOffset = shapeTable[i-123].xOffset;
+ _defaultShapeTable[i-7].yOffset = shapeTable[i-123].yOffset;
+ _defaultShapeTable[i-7].w = shapeTable[i-123].w;
+ _defaultShapeTable[i-7].h = shapeTable[i-123].h;
+ }
+ _screen->_curPage = curPageBackUp;
+}
+
+void KyraEngine::freeShapes123() {
+ debug(9, "KyraEngine::freeShapes123()");
+ for (int i = 123; i <= 172; ++i) {
+ free(_shapes[4+i]);
+ _shapes[4+i] = NULL;
+ }
+}
+
+#pragma mark -
+#pragma mark - Misc stuff
+#pragma mark -
+
+Movie *KyraEngine::createWSAMovie() {
+ // for kyra2 here could be added then WSAMovieV2
+ return new WSAMovieV1(this);
+}
+
+int KyraEngine::setGameFlag(int flag) {
+ _flagsTable[flag >> 3] |= (1 << (flag & 7));
+ return 1;
+}
+
+int KyraEngine::queryGameFlag(int flag) {
+ return ((_flagsTable[flag >> 3] >> (flag & 7)) & 1);
+}
+
+int KyraEngine::resetGameFlag(int flag) {
+ _flagsTable[flag >> 3] &= ~(1 << (flag & 7));
+ return 0;
+}
+
+void KyraEngine::setBrandonPoisonFlags(int reset) {
+ debug(9, "KyraEngine::setBrandonPoisonFlags(%d)", reset);
+ _brandonStatusBit |= 1;
+ if (reset)
+ _poisonDeathCounter = 0;
+ for (int i = 0; i < 0x100; ++i) {
+ _brandonPoisonFlagsGFX[i] = i;
+ }
+ _brandonPoisonFlagsGFX[0x99] = 0x34;
+ _brandonPoisonFlagsGFX[0x9A] = 0x35;
+ _brandonPoisonFlagsGFX[0x9B] = 0x37;
+ _brandonPoisonFlagsGFX[0x9C] = 0x38;
+ _brandonPoisonFlagsGFX[0x9D] = 0x2B;
+}
+
+void KyraEngine::resetBrandonPoisonFlags() {
+ debug(9, "KyraEngine::resetBrandonPoisonFlags()");
+ _brandonStatusBit = 0;
+ for (int i = 0; i < 0x100; ++i) {
+ _brandonPoisonFlagsGFX[i] = i;
+ }
+}
+
+#pragma mark -
+#pragma mark - Input
+#pragma mark -
+
+void KyraEngine::processInput(int xpos, int ypos) {
+ debug(9, "KyraEngine::processInput(%d, %d)", xpos, ypos);
+ _abortWalkFlag2 = false;
+
+ if (processInputHelper(xpos, ypos)) {
+ return;
+ }
+ uint8 item = findItemAtPos(xpos, ypos);
+ if (item == 0xFF) {
+ _changedScene = false;
+ int handled = clickEventHandler(xpos, ypos);
+ if (_changedScene || handled)
+ return;
+ }
+
+ // XXX _deathHandler specific
+ if (ypos <= 158) {
+ uint16 exit = 0xFFFF;
+ if (xpos < 12) {
+ exit = _walkBlockWest;
+ } else if (xpos >= 308) {
+ exit = _walkBlockEast;
+ } else if (ypos >= 136) {
+ exit = _walkBlockSouth;
+ } else if (ypos < 12) {
+ exit = _walkBlockNorth;
+ }
+
+ if (exit != 0xFFFF) {
+ _abortWalkFlag2 = true;
+ handleSceneChange(xpos, ypos, 1, 1);
+ _abortWalkFlag2 = false;
+ return;
+ } else {
+ int script = checkForNPCScriptRun(xpos, ypos);
+ if (script >= 0) {
+ runNpcScript(script);
+ return;
+ }
+ if (_itemInHand != -1) {
+ if (ypos < 155) {
+ if (hasClickedOnExit(xpos, ypos)) {
+ _abortWalkFlag2 = true;
+ handleSceneChange(xpos, ypos, 1, 1);
+ _abortWalkFlag2 = false;
+ return;
+ }
+ dropItem(0, _itemInHand, xpos, ypos, 1);
+ }
+ } else {
+ if (ypos <= 155) {
+ _abortWalkFlag2 = true;
+ handleSceneChange(xpos, ypos, 1, 1);
+ _abortWalkFlag2 = false;
+ }
+ }
+ }
+ }
+}
+
+int KyraEngine::processInputHelper(int xpos, int ypos) {
+ debug(9, "KyraEngine::processInputHelper(%d, %d)", xpos, ypos);
+ uint8 item = findItemAtPos(xpos, ypos);
+ if (item != 0xFF) {
+ if (_itemInHand == -1) {
+ _screen->hideMouse();
+ _animator->animRemoveGameItem(item);
+ snd_playSoundEffect(53);
+ assert(_currentCharacter->sceneId < _roomTableSize);
+ Room *currentRoom = &_roomTable[_currentCharacter->sceneId];
+ int item2 = currentRoom->itemsTable[item];
+ currentRoom->itemsTable[item] = 0xFF;
+ setMouseItem(item2);
+ assert(_itemList && _takenList);
+ updateSentenceCommand(_itemList[item2], _takenList[0], 179);
+ _itemInHand = item2;
+ _screen->showMouse();
+ clickEventHandler2();
+ return 1;
+ } else {
+ exchangeItemWithMouseItem(_currentCharacter->sceneId, item);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int KyraEngine::clickEventHandler(int xpos, int ypos) {
+ debug(9, "KyraEngine::clickEventHandler(%d, %d)", xpos, ypos);
+ _scriptInterpreter->initScript(_scriptClick, _scriptClickData);
+ _scriptClick->variables[1] = xpos;
+ _scriptClick->variables[2] = ypos;
+ _scriptClick->variables[3] = 0;
+ _scriptClick->variables[4] = _itemInHand;
+ _scriptInterpreter->startScript(_scriptClick, 1);
+
+ while (_scriptInterpreter->validScript(_scriptClick)) {
+ _scriptInterpreter->runScript(_scriptClick);
+ }
+
+ return _scriptClick->variables[3];
+}
+
+void KyraEngine::updateMousePointer(bool forceUpdate) {
+ int shape = 0;
+
+ int newMouseState = 0;
+ int newX = 0;
+ int newY = 0;
+ if (_mouseY <= 158) {
+ if (_mouseX >= 12) {
+ if (_mouseX >= 308) {
+ if (_walkBlockEast == 0xFFFF) {
+ newMouseState = -2;
+ } else {
+ newMouseState = -5;
+ shape = 3;
+ newX = 7;
+ newY = 5;
+ }
+ } else if (_mouseY >= 136) {
+ if (_walkBlockSouth == 0xFFFF) {
+ newMouseState = -2;
+ } else {
+ newMouseState = -4;
+ shape = 4;
+ newX = 5;
+ newY = 7;
+ }
+ } else if (_mouseY < 12) {
+ if (_walkBlockNorth == 0xFFFF) {
+ newMouseState = -2;
+ } else {
+ newMouseState = -6;
+ shape = 2;
+ newX = 5;
+ newY = 1;
+ }
+ }
+ } else {
+ if (_walkBlockWest == 0xFFFF) {
+ newMouseState = -2;
+ } else {
+ newMouseState = -3;
+ newX = 1;
+ newY = shape = 5;
+ }
+ }
+ }
+
+ if (_mouseX >= _entranceMouseCursorTracks[0] && _mouseY >= _entranceMouseCursorTracks[1]
+ && _mouseX <= _entranceMouseCursorTracks[2] && _mouseY <= _entranceMouseCursorTracks[3]) {
+ switch (_entranceMouseCursorTracks[4]) {
+ case 0:
+ newMouseState = -6;
+ shape = 2;
+ newX = 5;
+ newY = 1;
+ break;
+
+ case 2:
+ newMouseState = -5;
+ shape = 3;
+ newX = 7;
+ newY = 5;
+ break;
+
+ case 4:
+ newMouseState = -4;
+ shape = 4;
+ newX = 5;
+ newY = 7;
+ break;
+
+ case 6:
+ newMouseState = -3;
+ shape = 5;
+ newX = 1;
+ newY = 5;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (newMouseState == -2) {
+ shape = 6;
+ newX = 4;
+ newY = 4;
+ }
+
+ if ((newMouseState && _mouseState != newMouseState) || (newMouseState && forceUpdate)) {
+ _mouseState = newMouseState;
+ _screen->hideMouse();
+ _screen->setMouseCursor(newX, newY, _shapes[4+shape]);
+ _screen->showMouse();
+ }
+
+ if (!newMouseState) {
+ if (_mouseState != _itemInHand || forceUpdate) {
+ if (_mouseY > 158 || (_mouseX >= 12 && _mouseX < 308 && _mouseY < 136 && _mouseY >= 12) || forceUpdate) {
+ _mouseState = _itemInHand;
+ _screen->hideMouse();
+ if (_itemInHand == -1) {
+ _screen->setMouseCursor(1, 1, _shapes[4]);
+ } else {
+ _screen->setMouseCursor(8, 15, _shapes[220+_itemInHand]);
+ }
+ _screen->showMouse();
+ }
+ }
+ }
+}
+
+bool KyraEngine::hasClickedOnExit(int xpos, int ypos) {
+ debug(9, "KyraEngine::hasClickedOnExit(%d, %d)", xpos, ypos);
+ if (xpos < 16 || xpos >= 304) {
+ return true;
+ }
+ if (ypos < 8)
+ return true;
+ if (ypos < 136 || ypos > 155) {
+ return false;
+ }
+ return true;
+}
+
+void KyraEngine::clickEventHandler2() {
+ debug(9, "KyraEngine::clickEventHandler2()");
+ _scriptInterpreter->initScript(_scriptClick, _scriptClickData);
+ _scriptClick->variables[0] = _currentCharacter->sceneId;
+ _scriptClick->variables[1] = _mouseX;
+ _scriptClick->variables[2] = _mouseY;
+ _scriptClick->variables[4] = _itemInHand;
+ _scriptInterpreter->startScript(_scriptClick, 6);
+
+ while (_scriptInterpreter->validScript(_scriptClick)) {
+ _scriptInterpreter->runScript(_scriptClick);
+ }
+}
+
+int KyraEngine::checkForNPCScriptRun(int xpos, int ypos) {
+ debug(9, "KyraEngine::checkForNPCScriptRun(%d, %d)", xpos, ypos);
+ int returnValue = -1;
+ const Character *currentChar = _currentCharacter;
+ int charLeft = 0, charRight = 0, charTop = 0, charBottom = 0;
+
+ int scaleFactor = _scaleTable[currentChar->y1];
+ int addX = (((scaleFactor*8)*3)>>8)>>1;
+ int addY = ((scaleFactor*3)<<4)>>8;
+
+ charLeft = currentChar->x1 - addX;
+ charRight = currentChar->x1 + addX;
+ charTop = currentChar->y1 - addY;
+ charBottom = currentChar->y1;
+
+ if (xpos >= charLeft && charRight >= xpos && charTop <= ypos && charBottom >= ypos) {
+ return 0;
+ }
+
+ if (xpos > 304 || xpos < 16) {
+ return -1;
+ }
+
+ for (int i = 1; i < 5; ++i) {
+ currentChar = &_characterList[i];
+
+ if (currentChar->sceneId != _currentCharacter->sceneId)
+ continue;
+
+ charLeft = currentChar->x1 - 12;
+ charRight = currentChar->x1 + 11;
+ charTop = currentChar->y1 - 48;
+ // if (!i) {
+ // charBottom = currentChar->y2 - 16;
+ // } else {
+ charBottom = currentChar->y1;
+ // }
+
+ if (xpos < charLeft || xpos > charRight || ypos < charTop || charBottom < ypos) {
+ continue;
+ }
+
+ if (returnValue != -1) {
+ if (currentChar->y1 >= _characterList[returnValue].y1) {
+ returnValue = i;
+ }
+ } else {
+ returnValue = i;
+ }
+ }
+
+ return returnValue;
+}
+
+void KyraEngine::runNpcScript(int func) {
+ debug(9, "KyraEngine::runNpcScript(%d)", func);
+ _scriptInterpreter->initScript(_npcScript, _npcScriptData);
+ _scriptInterpreter->startScript(_npcScript, func);
+ _npcScript->variables[0] = _currentCharacter->sceneId;
+ _npcScript->variables[4] = _itemInHand;
+ _npcScript->variables[5] = func;
+
+ while (_scriptInterpreter->validScript(_npcScript)) {
+ _scriptInterpreter->runScript(_npcScript);
+ }
+}
+} // End of namespace Kyra