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.cpp255
1 files changed, 224 insertions, 31 deletions
diff --git a/engines/mads/conversations.cpp b/engines/mads/conversations.cpp
index 10ce41d793..d45d49834a 100644
--- a/engines/mads/conversations.cpp
+++ b/engines/mads/conversations.cpp
@@ -24,27 +24,61 @@
#include "mads/mads.h"
#include "mads/compression.h"
#include "common/file.h"
+#include "common/util.h" // for Common::hexdump
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 nodes;
- uint16 unk1;
- uint16 messages;
+ 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 unk3;
- uint16 imports;
- uint16 speakers;
- Common::List<Common::String> portraits;
+ 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;
};
-#define MAX_SPEAKERS 5
-
GameConversation::GameConversation(MADSEngine *vm)
: _vm(vm) {
_running = _restoreRunning = 0;
- _nextStartNode = nullptr;
}
GameConversation::~GameConversation() {
@@ -53,6 +87,8 @@ 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
+
inFile.open(fileName);
MadsPack convFileUnpacked(&inFile);
Common::SeekableReadStream *convFile = convFileUnpacked.getItemStream(0);
@@ -61,48 +97,205 @@ void GameConversation::get(int id) {
ConvData conv;
- // Section 0: Header
- conv.nodes = convFile->readUint16LE();
- conv.unk1 = convFile->readUint16LE();
- conv.messages = convFile->readUint16LE();
+ // **** Section 0: Header *************************************************
+ conv.nodeCount = convFile->readUint16LE();
+ conv.dialogCount = convFile->readUint16LE();
+ conv.messageCount = convFile->readUint16LE();
+ conv.textLineCount = convFile->readUint16LE();
conv.unk2 = convFile->readUint16LE();
- conv.unk3 = convFile->readUint16LE();
- conv.imports = convFile->readUint16LE();
- conv.speakers = convFile->readUint16LE();
+ conv.importCount = convFile->readUint16LE();
+ conv.speakerCount = convFile->readUint16LE();
- debug("Conv %d has %d nodes, %d messages, %d imports and %d speakers", id, conv.nodes, conv.messages, conv.imports, conv.speakers);
+ //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++) {
convFile->read(buffer, 16);
- Common::String portrait = buffer;
- debug("Speaker %d, portrait %s", i, portrait.c_str());
- conv.portraits.push_back(portrait);
+ conv.portraits[i] = buffer;
+ //debug("Speaker %d, portrait %s", i, conv.portraits[i].c_str());
}
for (uint16 i = 0; i < MAX_SPEAKERS; i++) {
- convFile->skip(2);
+ conv.speakerExists[i] = convFile->readUint16LE();
+ //debug("Speaker %d exists: %d", i, conv.speakerExists[i]);
}
convFile->read(buffer, 14);
conv.speechFile = Common::String(buffer);
- debug("Speech file %s", conv.speechFile.c_str());
+ //debug("Speech file %s", conv.speechFile.c_str());
- convFile->skip(32); // unknown bytes
+ 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);
- // Section 1
+ /*debug("Section 0 unknown bytes");
+ byte *tmp0 = new byte[26];
+ convFile->read(tmp0, 26);
+ Common::hexdump(tmp0, 26);
+ delete[] tmp0;*/
+
+ // **** Section 1: Nodes **************************************************
convFile = convFileUnpacked.getItemStream(1);
- for (uint16 i = 0; i < conv.nodes; i++) {
- uint16 nodeIndex = convFile->readUint16LE();
- debug("Node %d, index %d", i, nodeIndex);
- convFile->skip(2); // 01 00
- convFile->skip(6);
+ for (uint16 i = 0; i < conv.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);
}
- // TODO: Read the rest of the sections
+ // **** 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);
+ }
+ }
+
+ // **** Section 3: ???? ***************************************************
+ /*debug("Section 3");
+ convFile = convFileUnpacked.getItemStream(3);
+ byte *tmp1 = new byte[convFile->size()];
+ convFile->read(tmp1, convFile->size());
+ Common::hexdump(tmp1, convFile->size());
+ delete[] tmp1;*/
+ // TODO
+
+ // **** Section 4: Text line offsets **************************************
+ convFile = convFileUnpacked.getItemStream(4);
+ assert(convFile->size() == conv.textLineCount * 2);
+
+ uint16 *textLineOffsets = new uint16[conv.textLineCount]; // deleted below in section 5
+ for (uint16 i = 0; i < conv.textLineCount; i++)
+ textLineOffsets[i] = convFile->readUint16LE();
+
+ // **** Section 5: Text lines *********************************************
+ convFile = convFileUnpacked.getItemStream(5);
+ assert(convFile->size() == textLength);
+
+ Common::String textLine;
+ conv.textLines.resize(conv.textLineCount);
+ char textLineBuffer[256];
+ uint16 nextOffset;
+ for (uint16 i = 0; i < conv.textLineCount; i++) {
+ nextOffset = (i != conv.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());
+ }
+
+ delete[] textLineOffsets;
+
+ // **** Section 6: Node entry commands ************************************
+ 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;
+ }
+
+ 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);
+ }
+ }
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;
+ }
+ }
+ }
+
+ _vm->_audio->playSound(firstSpeech - 1);
+
+ TextDialog *dialog = new TextDialog(_vm, FONT_INTERFACE, Common::Point(0, 100), 30);
+ dialog->addLine(conv.textLines[firstText]);
+ dialog->show();
+ delete dialog;
+ */
+
warning("TODO GameConversation::get");
}