/* 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 &messages) { _dialogAltFlag = true; error("TODO: GameConversations::generateText"); } void GameConversations::generateMessage(Common::Array &messageList, Common::Array &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 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 *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