diff options
Diffstat (limited to 'engines/mads/conversations.cpp')
-rw-r--r-- | engines/mads/conversations.cpp | 1018 |
1 files changed, 1018 insertions, 0 deletions
diff --git a/engines/mads/conversations.cpp b/engines/mads/conversations.cpp new file mode 100644 index 0000000000..469aaedb81 --- /dev/null +++ b/engines/mads/conversations.cpp @@ -0,0 +1,1018 @@ +/* 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 "mads/conversations.h" +#include "mads/mads.h" +#include "mads/compression.h" +#include "common/file.h" +#include "common/util.h" // for Common::hexdump + +namespace MADS { + +GameConversations::GameConversations(MADSEngine *vm) : _vm(vm) { + _runningConv = nullptr; + _restoreRunning = 0; + _playerEnabled = false; + _inputMode = kInputBuildingSentences; + _startFrameNumber = 0; + _speakerVal = 0; + _currentMode = CONVMODE_NONE; + _priorMode = CONVMODE_NONE; + _popupVisible = false; + _verbId = 0; + _vars = _nextStartNode = nullptr; + _heroTrigger = 0; + _heroTriggerMode = SEQUENCE_TRIGGER_PARSER; + _interlocutorTrigger = 0; + _interlocutorTriggerMode = SEQUENCE_TRIGGER_PARSER; + _currentNode = 0; + _dialogNodeOffset = _dialogNodeSize = 0; + _dialog = nullptr; + _dialogAltFlag = false; + _personSpeaking = 0; + + // Mark all conversation slots as empty + for (int idx = 0; idx < MAX_CONVERSATIONS; ++idx) + _conversations[idx]._convId = -1; +} + +GameConversations::~GameConversations() { +} + +void GameConversations::load(int id) { + // Scan through the conversation list for a free slot + int slotIndex = -1; + for (int idx = 0; idx < MAX_CONVERSATIONS && slotIndex == -1; ++idx) { + if (_conversations[idx]._convId == -1) + slotIndex = idx; + } + if (slotIndex == -1) + error("Too many conversations loaded"); + + // Set the conversation the slot will contain + _conversations[slotIndex]._convId = id; + + // Load the conversation data + Common::String cnvFilename = Common::String::format("CONV%03d.CNV", id); + _conversations[slotIndex]._data.load(cnvFilename); + + // Load the conversation's CND data + Common::String cndFilename = Common::String::format("CONV%03d.CND", id); + _conversations[slotIndex]._cnd.load(cndFilename); +} + +ConversationEntry *GameConversations::getConv(int convId) { + for (uint idx = 0; idx < MAX_CONVERSATIONS; ++idx) { + if (_conversations[idx]._convId == convId) + return &_conversations[idx]; + } + + return nullptr; +} + +void GameConversations::run(int id) { + // If another conversation is running, then stop it first + if (_runningConv) + stop(); + + // Get the next conversation to run + _runningConv = getConv(id); + if (!_runningConv) + error("Specified conversation %d not loaded", id); + + // Initialize needed fields + _startFrameNumber = _vm->_events->getFrameCounter(); + _playerEnabled = _vm->_game->_player._stepEnabled; + _inputMode = _vm->_game->_screenObjects._inputMode; + _heroTrigger = 0; + _interlocutorTrigger = 0; + _popupVisible = false; + _currentMode = CONVMODE_0; + _verbId = -1; + _speakerVal = 1; + _personSpeaking = 1; + + // Initialize speaker arrays + Common::fill(&_speakerActive[0], &_speakerActive[MAX_SPEAKERS], false); + Common::fill(&_speakerSeries[0], &_speakerSeries[MAX_SPEAKERS], -1); + Common::fill(&_speakerFrame[0], &_speakerFrame[MAX_SPEAKERS], 1); + Common::fill(&_popupX[0], &_popupX[MAX_SPEAKERS], POPUP_CENTER); + Common::fill(&_popupY[0], &_popupY[MAX_SPEAKERS], POPUP_CENTER); + Common::fill(&_popupMaxLen[0], &_popupMaxLen[MAX_SPEAKERS], 30); + + // Start the conversation + start(); + + // Setup variables to point to data in the speaker arrays + setVariable(2, &_speakerVal); + for (int idx = 0; idx < MAX_SPEAKERS; ++idx) { + setVariable(3 + idx, &_speakerFrame[idx]); + setVariable(8 + idx, &_popupX[idx]); + setVariable(13 + idx, &_popupY[idx]); + setVariable(18 + idx, &_popupMaxLen[idx]); + } + + // Load sprite data for speaker portraits + for (uint idx = 0; idx < _runningConv->_data._speakerCount; ++idx) { + const Common::String &portraitName = _runningConv->_data._portraits[idx]; + _speakerSeries[idx] = _vm->_game->_scene._sprites.addSprites(portraitName, PALFLAG_RESERVED); + + if (_speakerSeries[idx] > 0) { + _speakerActive[idx] = true; + _speakerFrame[idx] = _runningConv->_data._speakerFrame[idx]; + } + } + + // Refresh colors if needed + if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE) + _vm->_palette->refreshSceneColors(); +} + +void GameConversations::start() { + assert(_runningConv->_cnd._vars.size() >= 2); + _vars = &_runningConv->_cnd._vars[0]; + _nextStartNode = &_runningConv->_cnd._vars[1]; + + _runningConv->_cnd._currentNode = -1; + _runningConv->_cnd._numImports = 0; + _runningConv->_cnd._vars[0].setValue(_nextStartNode->_val); + + // Store a reference to the variables list in the script handler for later reference + ScriptEntry::Conditional::_vars = &_runningConv->_cnd._vars; +} + +void GameConversations::setVariable(uint idx, int val) { + if (active()) + _runningConv->_cnd._vars[idx].setValue(val); +} + +void GameConversations::setVariable(uint idx, int *val) { + if (active()) + _runningConv->_cnd._vars[idx].setValue(val); +} + +void GameConversations::setStartNode(uint nodeIndex) { + assert(_nextStartNode && _nextStartNode->_isPtr == false); + _nextStartNode->_val = nodeIndex; +} + +void GameConversations::stop() { + // Only need to proceed if there is an active conversation + if (!active()) + return; + + // Reset player enabled state if needed + if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE) + _vm->_game->_player._stepEnabled = _playerEnabled; + + // Remove any visible dialog window + removeActiveWindow(); + + // Release any sprites used for character portraits + for (int idx = 0; idx < _runningConv->_data._speakerCount; ++idx) { + if (_speakerActive[idx]) + _vm->_game->_scene._sprites.remove(_speakerSeries[idx]); + } + + // Flag conversation as no longer running + _runningConv = nullptr; + + if (_inputMode == kInputConversation) + _vm->_game->_scene._userInterface.emptyConversationList(); + + _vm->_game->_scene._userInterface.setup(_inputMode); +} + +void GameConversations::exportPointer(int *ptr) { + // Only need to proceed if there is an active conversation + if (!active()) + return; + + // Also don't proceed if the number of allowed imports has already been reached + if (_runningConv->_cnd._numImports >= _runningConv->_data._maxImports) + return; + + // Get the variable to use for this next import and set it's value + int variableIndex = _runningConv->_cnd._importVariables[ + _runningConv->_cnd._numImports++]; + setVariable(variableIndex, ptr); +} + +void GameConversations::exportValue(int val) { + // Only need to proceed if there is an active conversation + if (!active()) + return; + + // Also don't proceed if the number of allowed imports has already been reached + if (_runningConv->_cnd._numImports >= _runningConv->_data._maxImports) + return; + + // Get the variable to use for this next import and set it's value + int variableIndex = _runningConv->_cnd._importVariables[ + _runningConv->_cnd._numImports++]; + setVariable(variableIndex, val); +} + +void GameConversations::setHeroTrigger(int val) { + _heroTrigger = val; + _heroTriggerMode = _vm->_game->_triggerSetupMode; +} + +void GameConversations::setInterlocutorTrigger(int val) { + _interlocutorTrigger = val; + _interlocutorTriggerMode = _vm->_game->_triggerSetupMode; +} + +int *GameConversations::getVariable(int idx) { + assert(idx >= 0); // TODO: Some negative values are allowed? Investigate + return _vars[idx].getValue(); +} + +void GameConversations::hold() { + if (_currentMode != CONVMODE_NONE) { + _priorMode = _currentMode; + _currentMode = CONVMODE_NONE; + } +} + +void GameConversations::release() { + if (_currentMode == CONVMODE_NONE) { + _currentMode = _priorMode; + if (_currentMode == 1 || _currentMode == 2) + update(true); + } +} + +void GameConversations::flagEntry(DialogCommand mode, int entryIndex) { + assert(_runningConv); + uint &flags = _runningConv->_cnd._entryFlags[entryIndex]; + + switch (mode) { + case CMD_1: + flags |= ENTRYFLAG_4000; + flags &= ~ENTRYFLAG_8000; + break; + + case CMD_HIDE: + flags &= ~ENTRYFLAG_8000; + break; + + case CMD_UNHIDE: + if (!(flags & ENTRYFLAG_4000)) + flags |= ENTRYFLAG_8000; + break; + + default: + break; + } +} + +void GameConversations::reset(int id) { + warning("TODO: GameConversations::reset"); +} + +void GameConversations::update(bool flag) { + // Only need to proceed if there is an active conversation + if (!active()) + return; + + ConversationVar &var0 = _runningConv->_cnd._vars[0]; + + switch (_currentMode) { + case CONVMODE_0: + assert(var0.isNumeric()); + if (var0._val < 0) { + if (_vm->_game->_scene._frameStartTime >= _startFrameNumber) { + removeActiveWindow(); + if (_heroTrigger) { + _vm->_game->_scene._action._activeAction._verbId = _verbId; + _vm->_game->_trigger = _heroTrigger; + _vm->_game->_triggerMode = _heroTriggerMode; + _heroTrigger = 0; + } + + _currentMode = CONVMODE_STOP; + } + } else { + bool isActive = nextNode(); + _currentNode = var0._val; + + if (isActive) { + _verbId = _runningConv->_data._nodes[_currentNode]._index; + _vm->_game->_scene._action._activeAction._verbId = _verbId; + _vm->_game->_scene._action._inProgress = true; + _vm->_game->_scene._action._savedFields._commandError = false; + _currentMode = CONVMODE_1; + } else { + _currentMode = generateMenu(); + } + } + break; + + case CONVMODE_1: + if (flag) + _currentMode = CONVMODE_3; + break; + + case CONVMODE_2: + if (flag) { + _vm->_game->_player._stepEnabled = false; + _verbId = _vm->_game->_scene._action._activeAction._verbId; + + if (!(_runningConv->_cnd._entryFlags[_verbId] & ENTRYFLAG_2)) + flagEntry(CMD_HIDE, _verbId); + + removeActiveWindow(); + _vm->_game->_scene._userInterface.emptyConversationList(); + _vm->_game->_scene._userInterface.setup(kInputConversation); + _personSpeaking = 0; + executeEntry(_verbId); + + ConvDialog &dialog = _runningConv->_data._dialogs[_verbId]; + if (dialog._speechIndex) { + _runningConv->_cnd._messageList3.clear(); + _runningConv->_cnd._messageList3.push_back(dialog._speechIndex); + } + + generateText(dialog._textLineIndex, _runningConv->_cnd._messageList3); + _currentMode = CONVMODE_0; + + if (_heroTrigger) { + _vm->_game->_scene._action._activeAction._verbId = _verbId; + _vm->_game->_trigger = _heroTrigger; + _vm->_game->_triggerMode = _heroTriggerMode; + _heroTrigger = 0; + } + } + break; + + case CONVMODE_3: + if (_vm->_game->_scene._frameStartTime >= _startFrameNumber) { + removeActiveWindow(); + _personSpeaking = 0; + executeEntry(_verbId); + generateMessage(_runningConv->_cnd._messageList1, _runningConv->_cnd._messageList3); + + if (_heroTrigger && _popupVisible) { + _vm->_game->_scene._action._activeAction._verbId = _verbId; + _vm->_game->_trigger = _heroTrigger; + _vm->_game->_triggerMode = _heroTriggerMode; + _heroTrigger = 0; + } + + _currentMode = CONVMODE_4; + } + break; + + case CONVMODE_4: + if (_vm->_game->_scene._frameStartTime >= _startFrameNumber) { + removeActiveWindow(); + _personSpeaking = _speakerVal; + + generateMessage(_runningConv->_cnd._messageList2, _runningConv->_cnd._messageList4); + + if (_interlocutorTrigger && _popupVisible) { + _vm->_game->_scene._action._activeAction._verbId = _verbId; + _vm->_game->_trigger = _interlocutorTrigger; + _vm->_game->_triggerMode = _interlocutorTriggerMode; + _interlocutorTrigger = 0; + } + } + break; + + case CONVMODE_STOP: + stop(); + break; + + default: + break; + } + + warning("TODO: GameConversations::update"); +} + +void GameConversations::removeActiveWindow() { + warning("TODO: GameConversations::removeActiveWindow"); +} + +ConversationMode GameConversations::generateMenu() { + error("TODO: GameConversations::generateMenu"); +} + +void GameConversations::generateText(int textLineIndex, Common::Array<int> &messages) { + _dialogAltFlag = true; + + error("TODO: GameConversations::generateText"); +} + +void GameConversations::generateMessage(Common::Array<int> &messageList, Common::Array<int> &voiceList) { + _dialogAltFlag = false; + if (messageList.size() == 0) + return; + + if (_dialog) + delete _dialog; + + // Get the speaker portrait + SpriteAsset &sprites = *_vm->_game->_scene._sprites[_speakerSeries[_personSpeaking]]; + MSprite *portrait = sprites.getFrame(_speakerFrame[_personSpeaking]); + + // Create the new text dialog + _dialog = new TextDialog(_vm, FONT_INTERFACE, + Common::Point(_popupX[_personSpeaking], _popupY[_personSpeaking]), + portrait, _popupMaxLen[_personSpeaking]); + + // Add in the lines + for (uint msgNum = 0; msgNum < messageList.size(); ++msgNum) { + ConvMessage &msg = _runningConv->_data._messages[messageList[msgNum]]; + uint stringIndex = msg._stringIndex; + + for (uint strNum = 0; strNum < msg._count; ++strNum, ++stringIndex) { + Common::String textLine = _runningConv->_data._textLines[stringIndex]; + textLine.trim(); + _dialog->addLine(textLine); + } + } + + // Show the dialog + _popupVisible = true; + _dialog->show(); + + // Play the speech if one was provided + if (voiceList.size() > 0) { + _vm->_audio->setSoundGroup(_runningConv->_data._speechFile); + _vm->_audio->playSound(voiceList[0] - 1); + } +} + +bool GameConversations::nextNode() { + ConversationVar &var0 = _runningConv->_cnd._vars[0]; + _runningConv->_cnd._currentNode = var0._val; + return _runningConv->_data._nodes[var0._val]._active; +} + +int GameConversations::executeEntry(int index) { + ConvDialog &dlg = _runningConv->_data._dialogs[index]; + ConversationVar &var0 = _runningConv->_cnd._vars[0]; + + _runningConv->_cnd._messageList1.clear(); + _runningConv->_cnd._messageList2.clear(); + _runningConv->_cnd._messageList3.clear(); + _runningConv->_cnd._messageList4.clear(); + _nextStartNode->_val = var0._val; + + bool flag = true; + for (uint scriptIdx = 0; scriptIdx < dlg._script.size() && flag; ) { + ScriptEntry &scrEntry = dlg._script[scriptIdx]; + if (scrEntry._command == CMD_END) + break; + + switch (scrEntry._command) { + case CMD_1: + case CMD_HIDE: + case CMD_UNHIDE: + for (uint idx = 0; scrEntry._entries.size(); ++idx) + flagEntry(scrEntry._command, scrEntry._entries[idx]); + break; + + case CMD_MESSAGE1: + case CMD_MESSAGE2: + scriptMessage(scrEntry); + break; + + case CMD_ERROR: + error("Conversation script generated error"); + break; + + case CMD_NODE: + flag = !scriptNode(scrEntry); + break; + + case CMD_GOTO: { + bool gotoFlag = scrEntry._conditionals[0].evaluate(); + if (gotoFlag) { + scriptIdx = scrEntry._index; + continue; + } + break; + } + + case CMD_ASSIGN: { + bool setFlag = scrEntry._conditionals[0].evaluate(); + if (setFlag) { + int *ptr = _runningConv->_cnd._vars[scrEntry._index].getValue(); + *ptr = scrEntry._conditionals[1].evaluate(); + } + break; + } + + default: + error("Unknown script opcode"); + } + + ++scriptIdx; + } + + if (flag) { + var0._val = -1; + } + + return var0._val; +} + +void GameConversations::scriptMessage(ScriptEntry &scrEntry) { + // Check whether this operation should be done + bool doFlag = scrEntry._conditionals[0].evaluate(); + if (!doFlag) + return; + + // Figure out the entire range that messages can be selected from + int total = 0; + for (uint idx = 0; idx < scrEntry._entries2.size(); ++idx) + total += scrEntry._entries2[idx]._size; + + // Choose a random entry from the list of possible values + int randomVal = _vm->getRandomNumber(1, total); + int randomIndex = -1; + while (randomVal > 0 && randomIndex < (int)scrEntry._entries2.size()) { + ++randomIndex; + randomVal -= scrEntry._entries2[randomIndex]._size; + } + if (randomIndex == (int)scrEntry._entries2.size()) + randomIndex = 0; + int entryVal = scrEntry._entries2[randomIndex]._v2; + + if (scrEntry._command == CMD_MESSAGE1) { + _runningConv->_cnd._messageList2.push_back(entryVal); + + if (scrEntry._entries2.size() <= 1) { + for (uint idx = 0; idx < scrEntry._entries.size(); ++idx) + _runningConv->_cnd._messageList4.push_back(scrEntry._entries[idx]); + } + else if (scrEntry._entries.size() > 0 && randomIndex < (int)scrEntry._entries.size()) { + _runningConv->_cnd._messageList4.push_back(entryVal); + } + } else { + _runningConv->_cnd._messageList1.push_back(entryVal); + + if (scrEntry._entries2.size() <= 1) { + for (uint idx = 0; idx < scrEntry._entries.size(); ++idx) + _runningConv->_cnd._messageList3.push_back(scrEntry._entries[idx]); + } else if (scrEntry._entries.size() > 0 && randomIndex < (int)scrEntry._entries.size()) { + _runningConv->_cnd._messageList3.push_back(entryVal); + } + } +} + +bool GameConversations::scriptNode(ScriptEntry &scrEntry) { + bool doFlag = scrEntry._conditionals[0].evaluate(); + if (!doFlag) + return false; + + ConversationVar &var0 = _runningConv->_cnd._vars[0]; + int val1 = scrEntry._conditionals[1].evaluate(); + int val2 = scrEntry._conditionals[2].evaluate(); + + var0._val = val1; + if (val1 >= 0) + _nextStartNode->_val = val1; + else if (val2 >= 0) + _nextStartNode->_val = val2; + + return true; +} + +/*------------------------------------------------------------------------*/ + +void ConversationData::load(const Common::String &filename) { + Common::File inFile; + char buffer[16]; + + inFile.open(filename); + MadsPack convFileUnpacked(&inFile); + + // **** Section 0: Header ************************************************* + Common::SeekableReadStream *convFile = convFileUnpacked.getItemStream(0); + + _nodeCount = convFile->readUint16LE(); + _dialogCount = convFile->readUint16LE(); + _messageCount = convFile->readUint16LE(); + _textLineCount = convFile->readUint16LE(); + _unk2 = convFile->readUint16LE(); + _maxImports = convFile->readUint16LE(); + _speakerCount = convFile->readUint16LE(); + + for (uint idx = 0; idx < MAX_SPEAKERS; ++idx) { + convFile->read(buffer, 16); + _portraits[idx] = buffer; + } + + for (uint idx = 0; idx < MAX_SPEAKERS; ++idx) { + _speakerFrame[idx] = convFile->readUint16LE(); + } + + convFile->read(buffer, 14); + _speechFile = Common::String(buffer); + + // Total text length in section 5 + _textSize = convFile->readUint32LE(); + _commandsSize = convFile->readUint32LE(); + + // The rest of the section 0 is padding to allow room for a set of pointers + // to the contents of the remaining sections loaded into memory as a + // continuous data block containing both the header and the sections + delete convFile; + + // **** Section 1: Nodes ************************************************** + convFile = convFileUnpacked.getItemStream(1); + + _nodes.clear(); + for (uint i = 0; i < _nodeCount; i++) { + ConvNode node; + node._index = convFile->readUint16LE(); + node._dialogCount = convFile->readUint16LE(); + node._unk1 = convFile->readSint16LE(); // TODO + node._active = convFile->readSint16LE() != 0; + node._unk3 = convFile->readSint16LE(); // TODO + _nodes.push_back(node); + } + + delete convFile; + + // **** Section 2: Dialogs ************************************************ + convFile = convFileUnpacked.getItemStream(2); + assert(convFile->size() == _dialogCount * 8); + + _dialogs.resize(_dialogCount); + for (uint idx = 0; idx < _dialogCount; ++idx) { + _dialogs[idx]._textLineIndex = convFile->readSint16LE(); + _dialogs[idx]._speechIndex = convFile->readSint16LE(); + _dialogs[idx]._scriptOffset = convFile->readUint16LE(); + _dialogs[idx]._scriptSize = convFile->readUint16LE(); + } + + delete convFile; + + // **** Section 3: Messages *********************************************** + convFile = convFileUnpacked.getItemStream(3); + assert(convFile->size() == _messageCount * 4); + + _messages.resize(_messageCount); + for (uint idx = 0; idx < _messageCount; ++idx) { + _messages[idx]._stringIndex = convFile->readUint16LE(); + _messages[idx]._count = convFile->readUint16LE(); + } + + delete convFile; + + // **** Section 4: Text line offsets ************************************** + convFile = convFileUnpacked.getItemStream(4); + assert(convFile->size() == _textLineCount * 2); + + uint16 *textLineOffsets = new uint16[_textLineCount]; // deleted below in section 5 + for (uint16 i = 0; i < _textLineCount; i++) + textLineOffsets[i] = convFile->readUint16LE(); + + delete convFile; + + // **** Section 5: Text lines ********************************************* + convFile = convFileUnpacked.getItemStream(5); + assert(convFile->size() == _textSize); + + Common::String textLine; + _textLines.resize(_textLineCount); + char textLineBuffer[256]; + uint16 nextOffset; + for (uint16 i = 0; i < _textLineCount; i++) { + nextOffset = (i != _textLineCount - 1) ? textLineOffsets[i + 1] : convFile->size(); + convFile->read(textLineBuffer, nextOffset - textLineOffsets[i]); + _textLines[i] = Common::String(textLineBuffer); + } + + delete[] textLineOffsets; + delete convFile; + + // **** Section 6: Scripts ************************************************ + convFile = convFileUnpacked.getItemStream(6); + assert(convFile->size() == _commandsSize); + + for (uint idx = 0; idx < _dialogs.size(); ++idx) { + // Move to the correct position for the dialog's script, and create + // a memory stream to represent the data for just that script + convFile->seek(_dialogs[idx]._scriptOffset); + Common::SeekableReadStream *scriptStream = convFile->readStream(_dialogs[idx]._scriptSize); + + // Pass it to the dialog's script set class to parse into commands + _dialogs[idx]._script.load(*scriptStream, _dialogs[idx]._scriptOffset); + delete scriptStream; + } + + delete convFile; + inFile.close(); +} + +/*------------------------------------------------------------------------*/ + +ConversationConditionals::ConversationConditionals() : _numImports(0) { + _currentNode = -1; +} + +void ConversationConditionals::load(const Common::String &filename) { + Common::File inFile; + Common::SeekableReadStream *convFile; + + // Open up the file for access + inFile.open(filename); + MadsPack convFileUnpacked(&inFile); + + // **** Section 0: Header ************************************************* + convFile = convFileUnpacked.getItemStream(0); + + _currentNode = convFile->readUint16LE(); + int entryFlagsCount = convFile->readUint16LE(); + int varsCount = convFile->readUint16LE(); + int importsCount = convFile->readUint16LE(); + + convFile->skip(4); + + _messageList1.resize(convFile->readUint16LE()); + _messageList2.resize(convFile->readUint16LE()); + _messageList3.resize(convFile->readUint16LE()); + _messageList4.resize(convFile->readUint16LE()); + convFile->skip(20); + + for (uint idx = 0; idx < 10; ++idx) { + int v = convFile->readUint16LE(); + if (idx < _messageList1.size()) + _messageList1[idx] = v; + } + for (uint idx = 0; idx < 10; ++idx) { + int v = convFile->readUint16LE(); + if (idx < _messageList2.size()) + _messageList2[idx] = v; + } + for (uint idx = 0; idx < 10; ++idx) { + int v = convFile->readUint16LE(); + if (idx < _messageList3.size()) + _messageList3[idx] = v; + } + for (uint idx = 0; idx < 10; ++idx) { + int v = convFile->readUint16LE(); + if (idx < _messageList4.size()) + _messageList4[idx] = v; + } + + delete convFile; + + // **** Section: Imports ************************************************* + int streamNum = 1; + + _importVariables.resize(importsCount); + if (importsCount > 0) { + convFile = convFileUnpacked.getItemStream(streamNum++); + + // Read in the variable indexes that each import value will be stored in + for (int idx = 0; idx < importsCount; ++idx) + _importVariables[idx] = convFile->readUint16LE(); + + delete convFile; + } + + // **** Section: Entry Flags ********************************************* + convFile = convFileUnpacked.getItemStream(streamNum++); + assert(convFile->size() == (entryFlagsCount * 2)); + + _entryFlags.resize(entryFlagsCount); + for (int idx = 0; idx < entryFlagsCount; ++idx) + _entryFlags[idx] = convFile->readUint16LE(); + + delete convFile; + + // **** Section: Variables *********************************************** + convFile = convFileUnpacked.getItemStream(streamNum); + assert(convFile->size() == (varsCount * 6)); + + _vars.resize(varsCount); + for (int idx = 0; idx < varsCount; ++idx) { + convFile->skip(2); // Loaded values are never pointers, so don't need this + _vars[idx]._isPtr = false; + _vars[idx]._val = convFile->readSint16LE(); + convFile->skip(2); // Unused segment selector for pointer values + } + + delete convFile; +} + +/*------------------------------------------------------------------------*/ + +void ConversationVar::setValue(int val) { + _isPtr = false; + _valPtr = nullptr; + _val = val; +} + +void ConversationVar::setValue(int *val) { + _isPtr = true; + _valPtr = val; + _val = 0; +} + +/*------------------------------------------------------------------------*/ + +void DialogScript::load(Common::SeekableReadStream &s, uint startingOffset) { + clear(); + Common::HashMap<uint, uint> instructionOffsets; + + // Iterate getting each instruction in turn + while (s.pos() < s.size()) { + // Create a new entry for the next script command + instructionOffsets[startingOffset + s.pos()] = size(); + push_back(ScriptEntry()); + ScriptEntry &se = (*this)[size() - 1]; + + // Load the instruction + se.load(s); + } + + // Do a final iteration over the loaded instructions to convert + // any GOTO instructions from original offsets to instruction indexes + for (uint idx = 0; idx < size(); ++idx) { + ScriptEntry &se = (*this)[idx]; + + if (se._command == CMD_GOTO) + se._index = instructionOffsets[se._index]; + } +} + +/*------------------------------------------------------------------------*/ + +void ScriptEntry::load(Common::SeekableReadStream &s) { + // Get the command byte + _command = (DialogCommand)s.readByte(); + + if (!(_command == CMD_DIALOG_END || (_command >= CMD_END && _command <= CMD_ASSIGN))) { + warning("unknown opcode - %d", _command); + s.seek(0, SEEK_END); + return; + } + + // Get in the conditional values + int numConditionals = 1; + if (_command == CMD_NODE) + numConditionals = 3; + else if (_command == CMD_ASSIGN) + numConditionals = 2; + else if (_command == CMD_ERROR) + numConditionals = 0; + + for (int idx = 0; idx < numConditionals; ++idx) + _conditionals[idx].load(s); + + // Get further parameters + switch (_command) { + case CMD_1: + case CMD_HIDE: + case CMD_UNHIDE: { + // Read in the list of entries whose flags are to be updated + int count = s.readByte(); + for (int idx = 0; idx < count; ++idx) + _entries.push_back(s.readSint16LE()); + break; + } + + case CMD_MESSAGE1: + case CMD_MESSAGE2: { + int count2 = s.readByte(); + int count1 = s.readByte(); + _entries2.resize(count2); + _entries.resize(count1); + + for (uint idx = 0; idx < _entries2.size(); ++idx) { + int v = s.readByte(); + if (idx < 10) + _entries2[idx]._size = v; + } + for (uint idx = 0; idx < _entries2.size(); ++idx) { + int v = s.readUint16LE(); + if (idx < 10) + _entries2[idx]._v2 = v; + } + for (uint idx = 0; idx < _entries.size(); ++idx) { + int v = s.readUint16LE(); + if (idx < 10) + _entries[idx] = v; + } + break; + } + + case CMD_ERROR: + case CMD_NODE: + // These opcodes have no extra parameters + break; + + case CMD_GOTO: + case CMD_ASSIGN: + // Goto has a single extra parameter for the destination + // Assign has a single extra parameter for the variable index + // that the value resulting from the condition will be set to + _index = s.readUint16LE(); + break; + + default: + break; + } +} + +/*------------------------------------------------------------------------*/ + +Common::Array<ConversationVar> *ScriptEntry::Conditional::_vars = nullptr; + +void ScriptEntry::Conditional::load(Common::SeekableReadStream &s) { + _operation = (ConditionalOperation)s.readUint16LE(); + + if (_operation == CONDOP_ABORT) { + _param1._isVariable = false; + _param1._val = 0; + } else { + _param1._isVariable = s.readByte() != 0; + _param1._val = s.readSint16LE(); + } + + if (_operation == CONDOP_ABORT || _operation == CONDOP_VALUE) { + _param2._isVariable = false; + _param2._val = 0; + } else { + _param2._isVariable = s.readByte() != 0; + _param2._val = s.readSint16LE(); + } +} + +int ScriptEntry::Conditional::evaluate() const { + if (_operation == CONDOP_NONE) + return -1; + + int param1 = get(1); + if (_operation == CONDOP_VALUE) + return param1; + int param2 = get(2); + + switch (_operation) { + case CONDOP_ADD: + return param1 + param2; + case CONDOP_SUBTRACT: + return param1 - param2; + case CONDOP_MULTIPLY: + return param1 * param2; + case CONDOP_DIVIDE: + return param1 / param2; + case CONDOP_MODULUS: + return param1 % param2; + case CONDOP_LTEQ: + return (param1 <= param2) ? 1 : 0; + case CONDOP_GTEQ: + return (param1 < param2) ? 1 : 0; + case CONDOP_LT: + return (param1 < param2) ? 1 : 0; + case CONDOP_GT: + return (param1 > param2) ? 1 : 0; + case CONDOP_NEQ: + return (param1 != param2) ? 1 : 0; + case CONDOP_EQ: + return (param1 == param2) ? 1 : 0; + case CONDOP_AND: + return (param1 || param2) ? 1 : 0; + case CONDOP_OR: + return (param1 && param2) ? 1 : 0; + default: + error("Unknown conditional operation"); + } +} + +int ScriptEntry::Conditional::get(int paramNum) const { + const CondtionalParamEntry &p = (paramNum == 1) ? _param1 : _param2; + return p._isVariable ? *(*_vars)[p._val].getValue() : p._val; +} + +/*------------------------------------------------------------------------*/ + + +} // End of namespace MADS |