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.cpp1166
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