From 8c906fbb3ee56cf3869a66ad44277a7969e3964c Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Mon, 11 Jan 2016 06:20:46 +1100 Subject: MADS: Implement conversation dialog scripts loading --- engines/mads/conversations.cpp | 288 ++++++++++++++++++++++++++++++----------- engines/mads/conversations.h | 98 ++++++++++---- 2 files changed, 288 insertions(+), 98 deletions(-) diff --git a/engines/mads/conversations.cpp b/engines/mads/conversations.cpp index 61391c1b12..85093d1885 100644 --- a/engines/mads/conversations.cpp +++ b/engines/mads/conversations.cpp @@ -45,6 +45,7 @@ GameConversations::GameConversations(MADSEngine *vm) : _vm(vm) { _interlocutorTrigger = 0; _interlocutorTriggerMode = SEQUENCE_TRIGGER_PARSER; _currentNode = 0; + _dialogNodeOffset = _dialogNodeSize = 0; // Mark all conversation slots as empty for (int idx = 0; idx < MAX_CONVERSATIONS; ++idx) @@ -147,7 +148,9 @@ void GameConversations::start() { _vars = &_runningConv->_cnd._vars[0]; _nextStartNode = &_runningConv->_cnd._vars[1]; - warning("TODO: GameConversations::start"); + _runningConv->_cnd._currentNode = -1; + _runningConv->_cnd._numImports = 0; + _runningConv->_cnd._vars[0].setValue(_nextStartNode->_val); } void GameConversations::setVariable(uint idx, int val) { @@ -233,6 +236,7 @@ void GameConversations::setInterlocutorTrigger(int val) { } int *GameConversations::getVariable(int idx) { + assert(idx >= 0); // TODO: Some negative values are allowed? Investigate return _vars[idx].getValue(); } @@ -251,21 +255,21 @@ void GameConversations::release() { } } -void GameConversations::flagEntry(ConvFlagMode mode, int entryIndex) { +void GameConversations::flagEntry(DialogCommand mode, int entryIndex) { assert(_runningConv); uint &flags = _runningConv->_cnd._entryFlags[entryIndex]; switch (mode) { - case FLAGMODE_1: + case CMD_1: flags |= ENTRYFLAG_4000; flags &= ~ENTRYFLAG_8000; break; - case FLAGMODE_2: + case CMD_HIDE: flags &= ~ENTRYFLAG_8000; break; - case FLAGMODE_3: + case CMD_UNHIDE: if (!(flags & ENTRYFLAG_4000)) flags |= ENTRYFLAG_8000; break; @@ -328,7 +332,7 @@ void GameConversations::update(bool flag) { _verbId = _vm->_game->_scene._action._activeAction._verbId; if (!(_runningConv->_cnd._entryFlags[_verbId] & ENTRYFLAG_2)) - flagEntry(FLAGMODE_2, _verbId); + flagEntry(CMD_HIDE, _verbId); removeActiveWindow(); _vm->_game->_scene._userInterface.emptyConversationList(); @@ -424,8 +428,27 @@ bool GameConversations::nextNode() { return _runningConv->_data._nodes[var0._val]._active; } -void GameConversations::executeEntry(int index) { +int GameConversations::executeEntry(int index) { + ConvDialog &dlg = _runningConv->_data._dialogs[index]; + ConversationVar &var0 = _runningConv->_cnd._vars[0]; + + _runningConv->_cnd._fieldC = 0; + _runningConv->_cnd._fieldE = 0; + _runningConv->_cnd._field10 = 0; + _runningConv->_cnd._field12 = 0; + _nextStartNode->_val = var0._val; + bool flag = true; + for (uint scriptIdx = 0; scriptIdx < dlg._script.size(); ++scriptIdx) { + DialogCommand cmd = dlg._script[scriptIdx]._command; + // TODO + } + + if (flag) { + var0._val = -1; + } + + return var0._val; } /*------------------------------------------------------------------------*/ @@ -493,8 +516,8 @@ void ConversationData::load(const Common::String &filename) { for (uint idx = 0; idx < _dialogCount; ++idx) { _dialogs[idx]._textLineIndex = convFile->readSint16LE(); _dialogs[idx]._speechIndex = convFile->readSint16LE(); - _dialogs[idx]._nodeOffset = convFile->readUint16LE(); - _dialogs[idx]._nodeSize = convFile->readUint16LE(); + _dialogs[idx]._scriptOffset = convFile->readUint16LE(); + _dialogs[idx]._scriptSize = convFile->readUint16LE(); } delete convFile; @@ -536,81 +559,23 @@ void ConversationData::load(const Common::String &filename) { delete[] textLineOffsets; delete convFile; - // **** Section 6: Node entry commands ************************************ + // **** Section 6: Scripts ************************************************ convFile = convFileUnpacked.getItemStream(6); assert(convFile->size() == _commandsSize); - for (uint16 i = 0; i < _nodeCount; i++) { - uint16 dialogCount = _nodes[i]._dialogCount; - - for (uint16 j = 0; j < dialogCount; j++) { - //ConvDialog dialog = _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; - } + 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); - 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); - } + // 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(); - - // TODO: Still stuff to do - warning("TODO GameConversations::get"); } /*------------------------------------------------------------------------*/ @@ -700,4 +665,175 @@ void ConversationVar::setValue(int *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()] = s.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._params[0] = instructionOffsets[se._params[0]]; + } +} + +/*------------------------------------------------------------------------*/ + +void ScriptEntry::load(Common::SeekableReadStream &s) { + // Get the command byte + _command = (DialogCommand)s.readByte(); + + if (!(_command == CMD_DIALOG_END || (_command >= CMD_NODE_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_7) + numConditionals = 3; + 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) + _params.push_back(s.readSint16LE()); + break; + } + + case CMD_MESSAGE: + case CMD_5: { + int count1 = s.readByte(); + int count2 = s.readByte(); + _params.push_back(count1); + _params.push_back(count2); + + for (int idx = 0; idx < count1; ++idx) + _params.push_back(s.readByte()); + for (int idx = 0; idx < count1; ++idx) + _params.push_back(s.readUint16LE()); + for (int idx = 0; idx < count2; ++idx) + _params.push_back(s.readUint16LE()); + break; + } + + case CMD_ERROR: + case CMD_7: + // 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 + _params.push_back(s.readUint16LE()); + break; + + + + default: + break; + } +} + +void ScriptEntry::Conditional::load(Common::SeekableReadStream &s) { + _paramsFlag = s.readUint16LE(); + + if (_paramsFlag == 0xff) { + _param1._isVariable = false; + _param1._val = 0; + _param2._isVariable = false; + _param2._val = 0; + } else { + _param1._isVariable = s.readByte() != 0; + _param1._val = 0; + _param2._isVariable = s.readByte() != 0; + _param2._val = 0; + } +} + +/* +do { +command = convFile->readByte(); +chk = convFile->readUint16BE(); +if (chk != 0xFF00 && chk != 0x0000) { +warning("Error while reading conversation node entries - bailing out"); +break; +} + +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); +*/ + } // End of namespace MADS diff --git a/engines/mads/conversations.h b/engines/mads/conversations.h index dbf000f5a5..812896566c 100644 --- a/engines/mads/conversations.h +++ b/engines/mads/conversations.h @@ -48,24 +48,18 @@ enum ConversationMode { CONVMODE_STOP = 10 }; -enum DialogCommands { - cmdNodeEnd = 0, - // - cmdHide = 2, - cmdUnhide = 3, - cmdMessage = 4, - // - // - cmdGoto = 7, - // - cmdAssign = 9, - cmdDialogEnd = 255 -}; - -enum ConvFlagMode { - FLAGMODE_1 = 1, - FLAGMODE_2 = 2, - FLAGMODE_3 = 3 +enum DialogCommand { + CMD_NODE_END = 0, + CMD_1 = 1, + CMD_HIDE = 2, + CMD_UNHIDE = 3, + CMD_MESSAGE = 4, + CMD_5 = 5, + CMD_ERROR = 6, + CMD_7 = 7, + CMD_GOTO = 8, + CMD_ASSIGN = 9, + CMD_DIALOG_END = 255 }; enum ConvEntryFlag { @@ -74,14 +68,73 @@ enum ConvEntryFlag { ENTRYFLAG_8000 = 0x8000 }; +struct ScriptEntry { + struct Conditional { + struct CondtionalParamEntry { + bool _isVariable; + int _val; + + /** + * Constructor + */ + CondtionalParamEntry() : _isVariable(false), _val(0) {} + }; + + uint _paramsFlag; + CondtionalParamEntry _param1; + CondtionalParamEntry _param2; + + /** + * Constructor + */ + Conditional() : _paramsFlag(false) {} + + /** + * Loads data from a passed stream into the parameters structure + */ + void load(Common::SeekableReadStream &s); + }; + + DialogCommand _command; + Conditional _conditionals[3]; + Common::Array _params; + + /** + * Constructor + */ + ScriptEntry() : _command(CMD_NODE_END) {} + + /** + * Loads data from a passed stream into the parameters structure + */ + void load(Common::SeekableReadStream &s); +}; + +/** + * Representation of scripts associated with a dialog + */ +class DialogScript : public Common::Array { +public: + /** + * Loads a script from the passed stream + */ + void load(Common::SeekableReadStream &s, uint startingOffset); +}; + /** * Reperesents the data for a dialog to be displayed in a conversation */ struct ConvDialog { + struct ScriptEntry { + DialogCommand _command; + }; + int16 _textLineIndex; // 0-based int16 _speechIndex; // 1-based - uint16 _nodeOffset; // offset in section 6 - uint16 _nodeSize; // size in section 6 + uint16 _scriptOffset; // offset of script entry + uint16 _scriptSize; // size of script entry + + DialogScript _script; }; /** @@ -232,6 +285,7 @@ private: ConversationVar *_vars; ConversationVar *_nextStartNode; int _currentNode; + int _dialogNodeOffset, _dialogNodeSize; /** * Returns the record for the specified conversation, if it's loaded @@ -251,7 +305,7 @@ private: /** * Flags a conversation option/entry */ - void flagEntry(ConvFlagMode mode, int entryIndex); + void flagEntry(DialogCommand mode, int entryIndex); /** * Generate a menu @@ -276,7 +330,7 @@ private: /** * Executes a conversation entry */ - void executeEntry(int index); + int executeEntry(int index); public: /** * Constructor -- cgit v1.2.3