diff options
Diffstat (limited to 'engines/mads/conversations.cpp')
-rw-r--r-- | engines/mads/conversations.cpp | 1166 |
1 files changed, 911 insertions, 255 deletions
diff --git a/engines/mads/conversations.cpp b/engines/mads/conversations.cpp index 53b8e7900d..469aaedb81 100644 --- a/engines/mads/conversations.cpp +++ b/engines/mads/conversations.cpp @@ -28,335 +28,991 @@ namespace MADS { -#define MAX_SPEAKERS 5 - -enum DialogCommands { - cmdNodeEnd = 0, - // - cmdHide = 2, - cmdUnhide = 3, - cmdMessage = 4, - // - // - cmdGoto = 7, - // - cmdAssign = 9, - cmdDialogEnd = 255 -}; - -struct ConvDialog { - int16 textLineIndex; // 0-based - int16 speechIndex; // 1-based - uint16 nodeOffset; // offset in section 6 - uint16 nodeSize; // size in section 6 -}; - -struct ConvNode { - uint16 index; - uint16 dialogCount; - int16 unk1; - int16 unk2; - int16 unk3; - - Common::Array<ConvDialog> dialogs; -}; - -struct ConvData { - uint16 nodeCount; // conversation nodes, each one containing several dialog options and messages - uint16 dialogCount; // messages (non-selectable) + texts (selectable) - uint16 messageCount; // messages (non-selectable) - uint16 textLineCount; - uint16 unk2; - uint16 importCount; - uint16 speakerCount; - Common::String portraits[MAX_SPEAKERS]; - bool speakerExists[MAX_SPEAKERS]; - Common::String speechFile; - Common::Array<Common::String> textLines; - Common::Array<ConvNode> convNodes; -}; - -GameConversation::GameConversation(MADSEngine *vm) - : _vm(vm) { - _running = _restoreRunning = 0; - _nextStartNode = nullptr; -} - -GameConversation::~GameConversation() { -} - -void GameConversation::get(int id) { - Common::File inFile; - Common::String fileName = Common::String::format("CONV%03d.CNV", id); - // TODO: Also handle the .CND file +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; +} - inFile.open(fileName); - MadsPack convFileUnpacked(&inFile); - Common::SeekableReadStream *convFile = convFileUnpacked.getItemStream(0); +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]; - ConvData conv; + inFile.open(filename); + MadsPack convFileUnpacked(&inFile); // **** Section 0: Header ************************************************* - conv.nodeCount = convFile->readUint16LE(); - conv.dialogCount = convFile->readUint16LE(); - conv.messageCount = convFile->readUint16LE(); - conv.textLineCount = convFile->readUint16LE(); - conv.unk2 = convFile->readUint16LE(); - conv.importCount = convFile->readUint16LE(); - conv.speakerCount = convFile->readUint16LE(); - - //debug("Conv %d has %d nodes, %d dialogs, %d messages, %d text lines, %d unk2, %d imports and %d speakers", - // id, conv.nodeCount, conv.dialogCount, conv.messageCount, conv.textLineCount, conv.unk2, conv.importCount, conv.speakerCount); - - for (uint16 i = 0; i < MAX_SPEAKERS; i++) { + 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); - conv.portraits[i] = buffer; - //debug("Speaker %d, portrait %s", i, conv.portraits[i].c_str()); + _portraits[idx] = buffer; } - for (uint16 i = 0; i < MAX_SPEAKERS; i++) { - conv.speakerExists[i] = convFile->readUint16LE(); - //debug("Speaker %d exists: %d", i, conv.speakerExists[i]); + for (uint idx = 0; idx < MAX_SPEAKERS; ++idx) { + _speakerFrame[idx] = convFile->readUint16LE(); } convFile->read(buffer, 14); - conv.speechFile = Common::String(buffer); - //debug("Speech file %s", conv.speechFile.c_str()); - - uint16 textLength = convFile->readUint16LE(); // Total text length in section 5 - convFile->skip(2); // TODO: unknown - uint16 commandLength = convFile->readUint16LE(); // Total length of commands in section 6 - //debug("Node entry commands length: %d", commandLength); - -#if 0 - debug("Section 0 unknown bytes"); - byte *tmp0 = new byte[26]; - convFile->read(tmp0, 26); - Common::hexdump(tmp0, 26); - delete[] tmp0; + _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; -#else - warning("Section 0 unknown bytes"); -#endif + // **** Section 1: Nodes ************************************************** convFile = convFileUnpacked.getItemStream(1); - for (uint16 i = 0; i < conv.nodeCount; i++) { + _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.unk2 = convFile->readSint16LE(); // TODO - node.unk3 = convFile->readSint16LE(); // TODO - conv.convNodes.push_back(node); - //debug("Node %d, index %d, entries %d - %d, %d, %d", i, node.index, node.dialogCount, node.unk1, node.unk2, node.unk3); + 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() == conv.dialogCount * 8); - - for (uint16 i = 0; i < conv.nodeCount; i++) { - uint16 dialogCount = conv.convNodes[i].dialogCount; - - for (uint16 j = 0; j < dialogCount; j++) { - ConvDialog dialog; - dialog.textLineIndex = convFile->readSint16LE(); - dialog.speechIndex = convFile->readSint16LE(); - dialog.nodeOffset = convFile->readUint16LE(); - dialog.nodeSize = convFile->readUint16LE(); - conv.convNodes[i].dialogs.push_back(dialog); - //debug("Node %d, dialog %d: text line %d, speech index %d, node offset %d, node size %d", j, i, dialog.textLineIndex, dialog.speechIndex, dialog.nodeOffset, dialog.nodeSize); - } + 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: ???? *************************************************** -#if 0 - debug("Section 3"); + // **** Section 3: Messages *********************************************** convFile = convFileUnpacked.getItemStream(3); - byte *tmp1 = new byte[convFile->size()]; - convFile->read(tmp1, convFile->size()); - Common::hexdump(tmp1, convFile->size()); - delete[] tmp1; + 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; -#else - warning("Section 3 - TODO"); -#endif + // **** Section 4: Text line offsets ************************************** convFile = convFileUnpacked.getItemStream(4); - assert(convFile->size() == conv.textLineCount * 2); + assert(convFile->size() == _textLineCount * 2); - uint16 *textLineOffsets = new uint16[conv.textLineCount]; // deleted below in section 5 - for (uint16 i = 0; i < conv.textLineCount; i++) + 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() == textLength); + assert(convFile->size() == _textSize); Common::String textLine; - conv.textLines.resize(conv.textLineCount); + _textLines.resize(_textLineCount); char textLineBuffer[256]; uint16 nextOffset; - for (uint16 i = 0; i < conv.textLineCount; i++) { - nextOffset = (i != conv.textLineCount - 1) ? textLineOffsets[i + 1] : convFile->size(); + for (uint16 i = 0; i < _textLineCount; i++) { + nextOffset = (i != _textLineCount - 1) ? textLineOffsets[i + 1] : convFile->size(); convFile->read(textLineBuffer, nextOffset - textLineOffsets[i]); - conv.textLines[i] = Common::String(textLineBuffer); - //debug("Text line %d: %s", i, conv.textLines[i].c_str()); + _textLines[i] = Common::String(textLineBuffer); } delete[] textLineOffsets; delete convFile; - // **** Section 6: Node entry commands ************************************ + // **** Section 6: Scripts ************************************************ convFile = convFileUnpacked.getItemStream(6); - assert(convFile->size() == commandLength); - - for (uint16 i = 0; i < conv.nodeCount; i++) { - uint16 dialogCount = conv.convNodes[i].dialogCount; - - for (uint16 j = 0; j < dialogCount; j++) { - //ConvDialog dialog = conv.convNodes[i].dialogs[j]; - byte command; - uint16 chk; - - do { - command = convFile->readByte(); - chk = convFile->readUint16BE(); - if (chk != 0xFF00 && chk != 0x0000) { - warning("Error while reading conversation node entries - bailing out"); - break; - } + assert(convFile->size() == _commandsSize); - switch (command) { - case cmdNodeEnd: - //debug("Node end"); - break; - case cmdDialogEnd: - //debug("Dialog end"); - break; - case cmdHide: { - byte count = convFile->readByte(); - for (byte k = 0; k < count; k++) { - /*uint16 nodeRef = */convFile->readUint16LE(); - //debug("Hide node %d", nodeRef); - } - - } - break; - case cmdUnhide: { - byte count = convFile->readByte(); - for (byte k = 0; k < count; k++) { - /*uint16 nodeRef = */convFile->readUint16LE(); - //debug("Unhide node %d", nodeRef); - } - - } - break; - case cmdMessage: - //debug("Message"); - convFile->skip(7); // TODO - break; - case cmdGoto: { - convFile->skip(3); // unused? - /*byte nodeRef = */convFile->readByte(); - //debug("Goto %d", nodeRef); - } - break; - case cmdAssign: { - convFile->skip(3); // unused? - /*uint16 value = */convFile->readUint16LE(); - /*uint16 variable = */convFile->readUint16LE(); - //debug("Variable %d = %d", variable, value); - } - break; - default: - error("Unknown conversation command %d", command); - break; - } - } while (command != cmdNodeEnd && command != cmdDialogEnd); - } + 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(); +} - /* - // DEBUG: Show the very first message, and play the very first speech - _vm->_audio->setSoundGroup(conv.speechFile); - uint16 firstText = 0, firstSpeech = 1; - - for (uint16 i = 0; i < conv.convNodes.size(); i++) { - for (uint16 k = 0; k < conv.convNodes[i].dialogs.size(); k++) { - if (conv.convNodes[i].dialogs[k].textLineIndex >= 0) { - firstText = conv.convNodes[i].dialogs[k].textLineIndex; - firstSpeech = conv.convNodes[i].dialogs[k].speechIndex; - break; - } - } +/*------------------------------------------------------------------------*/ + +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; } - _vm->_audio->playSound(firstSpeech - 1); + delete convFile; - TextDialog *dialog = new TextDialog(_vm, FONT_INTERFACE, Common::Point(0, 100), 30); - dialog->addLine(conv.textLines[firstText]); - dialog->show(); - delete dialog; - */ + // **** Section: Imports ************************************************* + int streamNum = 1; + + _importVariables.resize(importsCount); + if (importsCount > 0) { + convFile = convFileUnpacked.getItemStream(streamNum++); - warning("TODO GameConversation::get"); -} + // Read in the variable indexes that each import value will be stored in + for (int idx = 0; idx < importsCount; ++idx) + _importVariables[idx] = convFile->readUint16LE(); -void GameConversation::run(int id) { - warning("TODO GameConversation::run"); -} + delete convFile; + } -void GameConversation::stop() { - warning("TODO GameConversation::stop"); -} + // **** Section: Entry Flags ********************************************* + convFile = convFileUnpacked.getItemStream(streamNum++); + assert(convFile->size() == (entryFlagsCount * 2)); -void GameConversation::exportPointer(int *val) { - warning("TODO GameConversation::exportPointer"); -} + _entryFlags.resize(entryFlagsCount); + for (int idx = 0; idx < entryFlagsCount; ++idx) + _entryFlags[idx] = convFile->readUint16LE(); -void GameConversation::exportValue(int val) { - warning("TODO GameConversation::exportValue"); + 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 GameConversation::setHeroTrigger(int val) { - _vm->_game->_trigger = val; // HACK - _running = -1; // HACK - warning("TODO: GameConversation::setHeroTrigger"); +/*------------------------------------------------------------------------*/ + +void ConversationVar::setValue(int val) { + _isPtr = false; + _valPtr = nullptr; + _val = val; } -void GameConversation::setInterlocutorTrigger(int val) { - warning("TODO: GameConversation::setInterlocutorTrigger"); +void ConversationVar::setValue(int *val) { + _isPtr = true; + _valPtr = val; + _val = 0; } -int* GameConversation::getVariable(int idx) { - warning("TODO: GameConversation::getVariable"); - return nullptr; +/*------------------------------------------------------------------------*/ + +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 GameConversation::hold() { - warning("TODO: GameConversation::hold"); +/*------------------------------------------------------------------------*/ + +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; + } } -void GameConversation::release() { - warning("TODO: GameConversation::release"); +/*------------------------------------------------------------------------*/ + +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(); + } } -void GameConversation::reset(int id) { - warning("TODO: GameConversation::reset"); +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"); + } } -void GameConversation::abortConv() { - warning("TODO: GameConversation::abort"); +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 |