aboutsummaryrefslogtreecommitdiff
path: root/engines/mads/conversations.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/mads/conversations.cpp')
-rw-r--r--engines/mads/conversations.cpp1018
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