aboutsummaryrefslogtreecommitdiff
path: root/engines/m4/converse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/m4/converse.cpp')
-rw-r--r--engines/m4/converse.cpp1211
1 files changed, 1211 insertions, 0 deletions
diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp
new file mode 100644
index 0000000000..68daa08942
--- /dev/null
+++ b/engines/m4/converse.cpp
@@ -0,0 +1,1211 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/array.h"
+#include "common/hashmap.h"
+
+#include "m4/converse.h"
+#include "m4/resource.h"
+#include "m4/globals.h"
+#include "m4/m4_views.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+#define CONV_ENTRIES_X_OFFSET 20
+#define CONV_ENTRIES_Y_OFFSET 4
+#define CONV_ENTRIES_HEIGHT 20
+#define CONV_MAX_SHOWN_ENTRIES 5
+
+#define CONVERSATION_ENTRY_HIGHLIGHTED 22
+#define CONVERSATION_ENTRY_NORMAL 3
+
+// Conversation chunks
+// Header
+#define HEAD_CONV MKID_BE('CONV') // conversation
+#define CHUNK_DECL MKID_BE('DECL') // declaration
+#define CHUNK_NODE MKID_BE('NODE') // node
+#define CHUNK_LNOD MKID_BE('LNOD') // linear node
+#define CHUNK_ETRY MKID_BE('ETRY') // entry
+#define CHUNK_TEXT MKID_BE('TEXT') // text
+#define CHUNK_MESG MKID_BE('MESG') // message
+// Conversation chunks - entry related (unconditional)
+#define CHUNK_RPLY MKID_BE('RPLY') // reply
+#define CHUNK_HIDE MKID_BE('HIDE') // hide entry
+#define CHUNK_UHID MKID_BE('UHID') // unhide entry
+#define CHUNK_DSTR MKID_BE('DSTR') // destroy entry
+// Conversation chunks - entry related (conditional)
+#define CHUNK_CRPL MKID_BE('CRPL') // reply
+#define CHUNK_CHDE MKID_BE('CHDE') // hide entry
+#define CHUNK_CUHD MKID_BE('CUHD') // unhide entry
+#define CHUNK_CDST MKID_BE('DDTS') // destroy entry
+// Conversation chunks - branching and logic (unconditional)
+#define CHUNK_ASGN MKID_BE('ASGN') // assign
+#define CHUNK_GOTO MKID_BE('GOTO') // goto chunk
+#define CHUNK_EXIT MKID_BE('EXIT') // exit/return from goto
+// Conversation chunks - branching and logic (conditional)
+#define CHUNK_CASN MKID_BE('CASN') // assign
+#define CHUNK_CCGO MKID_BE('CCGO') // goto chunk
+#define CHUNK_CEGO MKID_BE('CEGO') // exit/return from goto
+// Others
+#define CHUNK_FALL MKID_BE('FALL') // fallthrough
+#define CHUNK_WRPL MKID_BE('WRPL') // weighted reply chunk
+#define CHUNK_WPRL MKID_BE('WPRL') // weighted preply chunk
+
+
+ConversationView::ConversationView(M4Engine *vm): View(vm, Common::Rect(0,
+ vm->_screen->height() - INTERFACE_HEIGHT, vm->_screen->width(), vm->_screen->height())) {
+
+ _screenType = VIEWID_CONVERSATION;
+ _screenFlags.layer = LAYER_INTERFACE;
+ _screenFlags.visible = false;
+ _screenFlags.get = SCREVENT_MOUSE;
+ _conversationState = kNoConversation;
+ _currentHandle = NULL;
+}
+
+ConversationView::~ConversationView() {
+ _activeItems.clear();
+}
+
+void ConversationView::setNode(int32 nodeIndex) {
+ _highlightedIndex = -1;
+ _xEnd = CONV_ENTRIES_X_OFFSET;
+ _vm->_font->setFont(FONT_CONVERSATION);
+
+ // TODO: Conversation styles and colors
+ _vm->_font->setColors(2, 1, 3);
+
+ _currentNodeIndex = nodeIndex;
+
+ _activeItems.clear();
+
+ if (nodeIndex != -1) {
+ ConvEntry *node = _vm->_converse->getNode(nodeIndex);
+
+ for (uint i = 0; i < node->entries.size(); ++i) {
+ if (!node->entries[i]->visible)
+ continue;
+
+ if ((int)_activeItems.size() > CONV_MAX_SHOWN_ENTRIES) {
+ warning("TODO: scrolling. Max shown entries are %i, skipping entry %i",
+ CONV_MAX_SHOWN_ENTRIES, i);
+ }
+
+ // Add node to active items list
+ _activeItems.push_back(node->entries[i]);
+
+ if (node->entries[i]->autoSelect || strlen(node->entries[i]->text) == 0) {
+ //printf("Auto selecting entry %i of node %i\n", i, nodeIndex);
+ selectEntry(i);
+ return;
+ }
+
+ // Figure out the longest string to determine where option highlighting ends
+ int tempX = _vm->_font->getWidth(node->entries[i]->text, 0) +
+ CONV_ENTRIES_X_OFFSET + 10;
+ _xEnd = MAX(_xEnd, tempX);
+ }
+
+ // Make sure that there aren't too many entries
+ //assert((int)_activeItems.size() < (height() - CONV_ENTRIES_Y_OFFSET) / CONV_ENTRIES_HEIGHT);
+
+ // Fallthrough
+ if (node->fallthroughMinEntries >= 0 && node->fallthroughOffset >= 0) {
+ //printf("Current node falls through node at offset %i when entries are less or equal than %i\n",
+ // node->fallthroughOffset, node->fallthroughMinEntries);
+ if (_activeItems.size() <= (uint32)node->fallthroughMinEntries) {
+ const EntryInfo *entryInfo = _vm->_converse->getEntryInfo(node->fallthroughOffset);
+ //printf("Entries are less than or equal to %i, falling through to node at offset %i, index %i\n",
+ // node->fallthroughMinEntries, node->fallthroughOffset, entryInfo->nodeIndex);
+ setNode(entryInfo->nodeIndex);
+ return;
+ }
+ }
+
+ _entriesShown = true;
+ _conversationState = kConversationOptionsShown;
+ }
+}
+
+void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) {
+ //if (!this->isVisible())
+ // return;
+ empty();
+
+ if (_entriesShown) {
+ // Write out the conversation options
+ _vm->_font->setFont(FONT_CONVERSATION);
+ for (int i = 0; i < (int)_activeItems.size(); ++i) {
+ // TODO: scrolling
+ if (i > CONV_MAX_SHOWN_ENTRIES - 1)
+ break;
+
+ _vm->_font->setColor((_highlightedIndex == i) ? CONVERSATION_ENTRY_HIGHLIGHTED :
+ CONVERSATION_ENTRY_NORMAL);
+
+ _vm->_font->writeString(this, _activeItems[i]->text, CONV_ENTRIES_X_OFFSET,
+ CONV_ENTRIES_Y_OFFSET + CONV_ENTRIES_HEIGHT * i, 0, 0);
+ }
+ }
+ View::onRefresh(rects, destSurface);
+}
+
+bool ConversationView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ //if (!this->isVisible())
+ // return false;
+ if (!_entriesShown)
+ return false;
+ if (eventType == KEVENT_KEY)
+ return false;
+
+ int localY = y - _coords.top;
+ int selectedIndex = _highlightedIndex;
+
+ switch (eventType) {
+ case MEVENT_MOVE:
+ if ((x < CONV_ENTRIES_X_OFFSET) || (x >= _xEnd) || (localY < CONV_ENTRIES_Y_OFFSET))
+ _highlightedIndex = -1;
+ else {
+ int index = (localY - CONV_ENTRIES_Y_OFFSET) / CONV_ENTRIES_HEIGHT;
+ _highlightedIndex = (index >= (int)_activeItems.size()) ? -1 : index;
+ }
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ if (_highlightedIndex != -1) {
+ selectEntry(selectedIndex);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void ConversationView::selectEntry(int entryIndex) {
+ char buffer[20];
+ sprintf(buffer, "%s.raw", _activeItems[entryIndex]->voiceFile);
+
+ _entriesShown = false;
+ _conversationState = kEntryIsActive;
+ _vm->_player->setCommandsAllowed(false);
+ // Necessary, as entries can be selected programmatically
+ _highlightedIndex = entryIndex;
+
+ // Play the selected entry's voice
+ if (strlen(_activeItems[entryIndex]->voiceFile) > 0) {
+ _currentHandle = _vm->_sound->getHandle();
+ _vm->_sound->playVoice(buffer, 255);
+ } else {
+ _currentHandle = NULL;
+ }
+
+ // Hide selected entry, unless it has a persistent flag set
+ if (!(_activeItems[entryIndex]->flags & kEntryPersists)) {
+ //printf("Hiding selected entry\n");
+ _vm->_converse->getNode(_currentNodeIndex)->entries[entryIndex]->visible = false;
+ } else {
+ //printf("Selected entry is persistent, not hiding it\n");
+ }
+}
+
+void ConversationView::updateState() {
+ switch(_conversationState) {
+ case kConversationOptionsShown:
+ return;
+ case kEntryIsActive:
+ case kReplyIsActive:
+ // FIXME: for now, we determine whether a conversation entry is
+ // finished by waiting for its associated speech file to finish playing
+ if (_currentHandle != NULL && _vm->_sound->isHandleActive(_currentHandle)) {
+ return;
+ } else {
+ playNextReply();
+ } // end else
+ break;
+ case kNoConversation:
+ return;
+ default:
+ error("Unknown converstation state");
+ break;
+ }
+}
+
+void ConversationView::playNextReply() {
+ char buffer[20];
+
+ assert(_highlightedIndex >= 0);
+
+ // Start playing the first reply
+ for (uint32 i = 0; i < _activeItems[_highlightedIndex]->entries.size(); i++) {
+ ConvEntry *currentEntry = _activeItems[_highlightedIndex]->entries[i];
+
+ if (currentEntry->isConditional) {
+ if (!_vm->_converse->evaluateCondition(
+ _vm->_converse->getValue(currentEntry->condition.offset),
+ currentEntry->condition.op, currentEntry->condition.val))
+ continue; // don't play this reply
+ }
+
+ if (currentEntry->entryType != kWeightedReply) {
+ sprintf(buffer, "%s.raw", currentEntry->voiceFile);
+ if (strlen(currentEntry->voiceFile) > 0) {
+ _currentHandle = _vm->_sound->getHandle();
+ _vm->_sound->playVoice(buffer, 255);
+ // Remove reply from the list of replies
+ _activeItems[_highlightedIndex]->entries.remove_at(i);
+ _conversationState = kReplyIsActive;
+ return;
+ } else {
+ _currentHandle = NULL;
+ }
+ } else {
+ int selectedWeight = _vm->_random->getRandomNumber(currentEntry->totalWeight - 1) + 1;
+ //printf("Selected weight: %i\n", selectedWeight);
+ int previousWeight = 1;
+ int currentWeight = 0;
+
+ for(uint32 j = 0; j < currentEntry->entries.size(); j++) {
+ currentWeight += currentEntry->entries[j]->weight;
+ if (selectedWeight >= previousWeight && selectedWeight <= currentWeight) {
+ sprintf(buffer, "%s.raw", currentEntry->entries[j]->voiceFile);
+ if (strlen(currentEntry->entries[j]->voiceFile) > 0) {
+ _currentHandle = _vm->_sound->getHandle();
+ _vm->_sound->playVoice(buffer, 255);
+ // Remove reply from the list of replies
+ _activeItems[_highlightedIndex]->entries.remove_at(i);
+ _conversationState = kReplyIsActive;
+ return;
+ } else {
+ _currentHandle = NULL;
+ }
+ break;
+ }
+ previousWeight += currentWeight;
+ } // end for j
+ } // end if
+ } // end for i
+
+ // If we reached here, there are no more replies, so perform the active entry's actions
+
+ //printf("Current selection does %i actions\n", _activeItems[entryIndex]->actions.size());
+ for (uint32 i = 0; i < _activeItems[_highlightedIndex]->actions.size(); i++) {
+ if (!_vm->_converse->performAction(_activeItems[_highlightedIndex]->actions[i]))
+ break;
+ } // end for
+
+ // Refresh the conversation node, in case it hasn't changed
+ setNode(_currentNodeIndex);
+
+ _entriesShown = true;
+ _vm->_player->setCommandsAllowed(true);
+
+ // Check if the conversation has been ended
+ if (_currentNodeIndex == -1) {
+ _conversationState = kNoConversation;
+ }
+}
+
+//--------------------------------------------------------------------------
+
+void Converse::startConversation(const char *convName, bool showConverseBox, ConverseStyle style) {
+ if (_vm->isM4())
+ loadConversation(convName);
+ else
+ loadConversationMads(convName);
+
+ if (!_vm->isM4()) showConverseBox = false; // TODO: remove
+
+ _playerCommandsAllowed = _vm->_player->commandsAllowed;
+ if (_vm->isM4()) // TODO: remove (interface not implemented yet in MADS games)
+ _interfaceWasVisible = _vm->_interfaceView->isVisible();
+ _vm->_player->setCommandsAllowed(false);
+ _style = style;
+
+ if (showConverseBox) {
+ _vm->_conversationView->show();
+ _vm->_mouse->lockCursor(CURSOR_ARROW);
+
+ if (_interfaceWasVisible)
+ _vm->_interfaceView->hide();
+
+ _vm->_conversationView->setNode(0);
+ _vm->_conversationView->show();
+ }
+}
+
+void Converse::endConversation() {
+ _vm->_conversationView->setNode(-1);
+ _vm->_conversationView->hide();
+ // TODO: do a more proper cleanup here
+ _convNodes.clear();
+ _variables.clear();
+ _offsetMap.clear();
+ _vm->_player->setCommandsAllowed(_playerCommandsAllowed);
+ if (_interfaceWasVisible)
+ _vm->_interfaceView->show();
+}
+
+void Converse::loadConversation(const char *convName) {
+ char name[40];
+ char buffer[256];
+ sprintf(name, "%s.chk", convName);
+ Common::SeekableReadStream *convS = _vm->res()->get(name);
+ uint32 header = convS->readUint32LE();
+ uint32 size;
+ uint32 chunk;
+ uint32 data;
+ uint32 i;
+ ConvEntry* curEntry = NULL;
+ ConvEntry* replyEntry = NULL;
+ int32 currentWeightedEntry = -1;
+ EntryAction* curAction = NULL;
+ uint32 curNode = 0;
+ uint32 chunkPos = 0;
+ uint32 val;
+ int32 autoSelectIndex = -1;
+ int returnAddress = -1;
+
+ bool debugFlag = false; // set to true for debug messages
+
+ // Conversation *.chk files contain a 'CONV' header in LE format
+ if (header != HEAD_CONV) {
+ warning("Unexpected conversation file external header");
+ return;
+ }
+ size = convS->readUint32LE(); // is this used at all?
+ if (debugFlag) printf("Conv chunk size (external header): %i\n", size);
+
+ // Conversation name, which is the conversation file's name
+ // without the extension
+ convS->read(buffer, 8);
+ if (debugFlag) printf("Conversation name: %s\n", buffer);
+
+ while(!convS->eos()) {
+ chunkPos = convS->pos();
+ if (debugFlag) printf("***** Pos: %i -> ", chunkPos);
+ chunk = convS->readUint32LE(); // read chunk
+ switch(chunk) {
+ case CHUNK_DECL: // Declare
+ if (debugFlag) printf("DECL chunk\n");
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Tag: %i\n", data);
+ if (data > 0)
+ warning("Tag > 0 in DECL chunk, value is: %i", data); // TODO
+ val = convS->readUint32LE();
+ if (debugFlag) printf("Value: %i\n", val);
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Flags: %i\n", data);
+ if (data > 0)
+ warning("Flags != 0 in DECL chunk, value is %i", data); // TODO
+ setValue(chunkPos, val);
+ break;
+ // ----------------------------------------------------------------------------
+ case CHUNK_NODE: // Node
+ case CHUNK_LNOD: // Linear node
+ // Create new node
+ curEntry = new ConvEntry();
+ curEntry->offset = chunkPos;
+ curEntry->entryType = (chunk == CHUNK_NODE) ? kNode : kLinearNode;
+ curEntry->fallthroughMinEntries = -1;
+ curEntry->fallthroughOffset = -1;
+ if (chunk == CHUNK_NODE) {
+ if (debugFlag) printf("NODE chunk\n");
+ } else {
+ if (debugFlag) printf("LNOD chunk\n");
+ }
+ curNode = convS->readUint32LE();
+ if (debugFlag) printf("Node number: %i\n", curNode);
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Tag: %i\n", data);
+ if (chunk == CHUNK_LNOD) {
+ autoSelectIndex = convS->readUint32LE();
+ if (debugFlag) printf("Autoselect entry number: %i\n", autoSelectIndex);
+ }
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Number of entries: %i\n", size);
+ for (i = 0; i < size; i++) {
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Entry %i: %i\n", i + 1, data);
+ }
+ _convNodes.push_back(curEntry);
+ setEntryInfo(curEntry->offset, curEntry->entryType, curNode, -1);
+ break;
+ case CHUNK_ETRY: // Entry
+ // Create new entry
+ curEntry = new ConvEntry();
+ curEntry->offset = chunkPos;
+ curEntry->entryType = kEntry;
+ strcpy(curEntry->voiceFile, "");
+ strcpy(curEntry->text, "");
+ if (debugFlag) printf("ETRY chunk\n");
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Unknown (attributes perhaps?): %i\n", data);
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Entry flags: ");
+ if (debugFlag) if (data & kEntryInitial) printf ("Initial ");
+ if (debugFlag) if (data & kEntryPersists) printf ("Persists ");
+ if (debugFlag) printf("\n");
+ curEntry->flags = data;
+ curEntry->visible = (curEntry->flags & kEntryInitial) ? true : false;
+ if (autoSelectIndex >= 0) {
+ if (_convNodes[curNode]->entries.size() == (uint32)autoSelectIndex) {
+ curEntry->autoSelect = true;
+ autoSelectIndex = -1;
+ } else {
+ curEntry->autoSelect = false;
+ }
+ } else {
+ curEntry->autoSelect = false;
+ }
+ _convNodes[curNode]->entries.push_back(curEntry);
+ setEntryInfo(curEntry->offset, curEntry->entryType,
+ curNode, _convNodes[curNode]->entries.size() - 1);
+ replyEntry = NULL;
+ break;
+ case CHUNK_WPRL: // Weighted preply
+ // WPRL chunks are random entries that the character would say, not an NPC
+ // They don't seem to be used in Orion Burger
+ warning("WPRL chunk - treating as WRPL chunk");
+ case CHUNK_WRPL: // Weighted reply
+ case CHUNK_CRPL: // Conditional reply
+ case CHUNK_RPLY: // Reply
+ {
+ ConvEntry* weightedEntry = NULL;
+ // Create new reply
+ replyEntry = new ConvEntry();
+ replyEntry->offset = chunkPos;
+ strcpy(replyEntry->voiceFile, "");
+
+ // Conditional part
+ if (chunk == CHUNK_CRPL) {
+ replyEntry->isConditional = true;
+ replyEntry->condition.offset = convS->readUint32LE();
+ replyEntry->condition.op = convS->readUint32LE();
+ replyEntry->condition.val = convS->readUint32LE();
+ } else {
+ replyEntry->isConditional = false;
+ }
+
+ if (chunk == CHUNK_WPRL || chunk == CHUNK_WRPL) {
+ replyEntry->entryType = kWeightedReply;
+ replyEntry->totalWeight = 0;
+ if (debugFlag) printf("WRPL chunk\n");
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Weighted reply %i - %i entries:\n", i, size);
+ for (i = 0; i < size; i++) {
+ weightedEntry = new ConvEntry();
+ weightedEntry->offset = chunkPos;
+ strcpy(weightedEntry->voiceFile, "");
+ weightedEntry->entryType = kWeightedReply;
+ data = convS->readUint32LE();
+ if (debugFlag) printf("- Weight: %i ", data);
+ weightedEntry->weight = data;
+ replyEntry->totalWeight += weightedEntry->weight;
+ data = convS->readUint32LE();
+ if (debugFlag) printf("offset: %i\n", data);
+ replyEntry->entries.push_back(weightedEntry);
+ }
+ currentWeightedEntry = 0;
+ } else {
+ replyEntry->entryType = kReply;
+ if (debugFlag) printf("RPLY chunk\n");
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Reply data offset: %i\n", data);
+ }
+
+ curEntry->entries.push_back(replyEntry);
+ setEntryInfo(replyEntry->offset, replyEntry->entryType,
+ curNode, _convNodes[curNode]->entries.size() - 1);
+ // Seek to chunk data (i.e. TEXT/MESG tag, which is usually right
+ // after this chunk but it can be further on in conditional reply chunks
+ assert(data >= convS->pos());
+ // If the entry's data is not right after the entry, remember the position
+ // to return to after the data is read
+ if (chunk == CHUNK_CRPL && data != convS->pos())
+ returnAddress = convS->pos();
+ convS->seek(data, SEEK_SET);
+ }
+ break;
+ // ----------------------------------------------------------------------------
+ case CHUNK_TEXT: // Text (contains text and voice)
+ case CHUNK_MESG: // Message (contains voice only)
+ {
+ ConvEntry* parentEntry = NULL;
+ if (chunk == CHUNK_TEXT) {
+ if (debugFlag) printf("TEXT chunk\n");
+ } else {
+ if (debugFlag) printf("MESG chunk\n");
+ }
+
+ if (replyEntry == NULL) {
+ parentEntry = curEntry;
+ } else if (replyEntry != NULL && replyEntry->entryType != kWeightedReply) {
+ parentEntry = replyEntry;
+ } else if (replyEntry != NULL && replyEntry->entryType == kWeightedReply) {
+ parentEntry = replyEntry->entries[currentWeightedEntry];
+ currentWeightedEntry++;
+ }
+
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Entry data size: %i\n", size);
+ convS->read(buffer, 8);
+ size -= 8; // subtract maximum length of voice file name
+ // If the file name is 8 characters, it will not be 0-terminated, so use strncpy
+ strncpy(parentEntry->voiceFile, buffer, 8);
+ parentEntry->voiceFile[8] = '\0';
+ if (debugFlag) printf("Voice file: %s\n", parentEntry->voiceFile);
+
+ if (chunk == CHUNK_TEXT) {
+ convS->read(buffer, size);
+ if (debugFlag) printf("Text: %s\n", buffer);
+ sprintf(parentEntry->text, "%s", buffer);
+ } else {
+ while (size > 0) {
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Unknown: %i\n", data); // TODO
+ size -= 4;
+ }
+ }
+ // Now that the data chunk has been read, if we skipped a reply node,
+ // jump back to it
+ if (returnAddress != -1) {
+ convS->seek(returnAddress, SEEK_SET);
+ returnAddress = -1;
+ }
+ }
+ break;
+ // ----------------------------------------------------------------------------
+ // Entry action chunks
+ case CHUNK_CASN: // Conditional assign
+ case CHUNK_ASGN: // Assign
+ curAction = new EntryAction();
+ if (debugFlag) printf("ASGN chunk\n");
+ curAction->actionType = kAssignValue;
+
+ // Conditional part
+ if (chunk == CHUNK_CASN) {
+ curAction->isConditional = true;
+ curAction->condition.offset = convS->readUint32LE();
+ curAction->condition.op = convS->readUint32LE();
+ curAction->condition.val = convS->readUint32LE();
+ } else {
+ curAction->isConditional = false;
+ }
+
+ curAction->targetOffset = convS->readUint32LE();
+ assert(convS->readUint32LE() == kOpAssign);
+ curAction->value = convS->readUint32LE();
+ break;
+ case CHUNK_CCGO: // Conditional go to entry
+ case CHUNK_CHDE: // Conditional hide entry
+ case CHUNK_CUHD: // Conditional unhide entry
+ case CHUNK_CDST: // Conditional destroy entry
+ case CHUNK_CEGO: // Conditional exit conversation / go to
+
+ case CHUNK_GOTO: // Go to entry
+ case CHUNK_HIDE: // Hide entry
+ case CHUNK_UHID: // Unhide entry
+ case CHUNK_DSTR: // Destroy entry
+ case CHUNK_EXIT: // Exit conversation
+ curAction = new EntryAction();
+
+ // Conditional part
+ if (chunk == CHUNK_CCGO || chunk == CHUNK_CHDE || chunk == CHUNK_CUHD ||
+ chunk == CHUNK_CDST || chunk == CHUNK_CEGO) {
+ curAction->isConditional = true;
+ curAction->condition.offset = convS->readUint32LE();
+ curAction->condition.op = convS->readUint32LE();
+ curAction->condition.val = convS->readUint32LE();
+ } else {
+ curAction->isConditional = false;
+ }
+
+ if (chunk == CHUNK_GOTO || chunk == CHUNK_CCGO) {
+ curAction->actionType = kGotoEntry;
+ if (debugFlag) printf("GOTO chunk\n");
+ } else if (chunk == CHUNK_HIDE || chunk == CHUNK_CHDE) {
+ curAction->actionType = kHideEntry;
+ if (debugFlag) printf("HIDE chunk\n");
+ } else if (chunk == CHUNK_UHID || chunk == CHUNK_CUHD) {
+ curAction->actionType = kUnhideEntry;
+ if (debugFlag) printf("UHID chunk\n");
+ } else if (chunk == CHUNK_DSTR || chunk == CHUNK_CDST) {
+ curAction->actionType = kDestroyEntry;
+ if (debugFlag) printf("DSTR chunk\n");
+ } else if (chunk == CHUNK_EXIT || chunk == CHUNK_CEGO) {
+ curAction->actionType = kExitConv;
+ if (debugFlag) printf("EXIT chunk\n");
+ }
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Offset: %i\n", data);
+ curAction->targetOffset = data;
+ curEntry->actions.push_back(curAction);
+ break;
+ case CHUNK_FALL: // Fallthrough
+ if (debugFlag) printf("FALL chunk\n");
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Minimum nodes: %i\n", size);
+ _convNodes[curNode]->fallthroughMinEntries = size;
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Offset: %i\n", data);
+ _convNodes[curNode]->fallthroughOffset = data;
+ break;
+ // ----------------------------------------------------------------------------
+ default:
+ // Should never happen
+ error("Unknown conversation chunk");
+ break;
+ }
+ }
+
+ _vm->res()->toss(name);
+}
+
+void Converse::loadConversationMads(const char *convName) {
+ char name[40];
+ char buffer[256];
+ char *buf;
+ Common::SeekableReadStream *convS;
+ int curPos = 0;
+ int unk = 0;
+ uint32 stringIndex = 0;
+ uint32 stringCount = 0;
+ int offset = 0;
+ int flags = 0;
+ int count = 0;
+ uint32 i, j;
+ ConvEntry* curEntry = NULL;
+ MessageEntry *curMessage;
+ Common::Array<char *> messageList;
+ // TODO
+
+ // CND file
+ sprintf(name, "%s.cnd", convName);
+ MadsPack convDataD(name, _vm);
+
+ // ------------------------------------------------------------
+ // Chunk 0
+ convS = convDataD.getItemStream(0);
+ printf("Chunk 0\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ // ------------------------------------------------------------
+ // Chunk 1
+ convS = convDataD.getItemStream(1);
+ printf("Chunk 1\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ // ------------------------------------------------------------
+ // Chunk 2
+ convS = convDataD.getItemStream(2);
+ printf("Chunk 2\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ // ------------------------------------------------------------
+ // CNV file
+ sprintf(name, "%s.cnv", convName);
+ MadsPack convData(name, _vm);
+ // *.cnv files have 7 chunks
+ // Here is the chunk output of conv001.cnv (from the dump_file command)
+ /*
+ Dumping conv001.cnv, size: 3431
+ Dumping compressed chunk 1 of 7, size is 150
+ Dumping compressed chunk 2 of 7, size is 130
+ Dumping compressed chunk 3 of 7, size is 224
+ Dumping compressed chunk 4 of 7, size is 92
+ Dumping compressed chunk 5 of 7, size is 168
+ Dumping compressed chunk 6 of 7, size is 4064
+ Dumping compressed chunk 7 of 7, size is 2334
+ */
+
+ // ------------------------------------------------------------
+ // TODO: finish this
+ // Chunk 0
+ convS = convData.getItemStream(0);
+ printf("Chunk 0\n");
+ printf("Conv stream size: %i\n", convS->size());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("\n");
+ count = convS->readUint16LE(); // conversation face count (usually 2)
+ printf("Conversation faces: %i\n", count);
+ for (i = 0; i < 5; i++) {
+ convS->read(buffer, 16);
+ printf("Face %i: %s ", i + 1, buffer);
+ }
+ printf("\n");
+
+ // 5 face slots
+ // 1 = face slot has a face (with the filename specified above)
+ // 0 = face slot doesn't contain face data
+ for (i = 0; i < 5; i++) {
+ printf("%i ", convS->readUint16LE());
+ }
+ printf("\n");
+
+ convS->read(buffer, 14); // speech file
+ printf("Speech file: %s\n", buffer);
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 1: Conversation nodes
+ convS = convData.getItemStream(1);
+ printf("Chunk 1: conversation nodes\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ curEntry = new ConvEntry();
+ curEntry->id = convS->readUint16LE();
+ curEntry->entryCount = convS->readUint16LE();
+ curEntry->flags = convS->readUint16LE();
+ if (curEntry->entryCount == 1 && curEntry->flags != 65535) {
+ warning("Entry count is 1 and flags is not 65535 (it's %i)", flags);
+ } else if (curEntry->entryCount != 1 && flags != 0) {
+ warning("Entry count > 1 and flags is not 0 (it's %i)", flags);
+ }
+ unk = convS->readUint16LE();
+ assert (unk == 65535);
+ unk = convS->readUint16LE();
+ assert (unk == 65535);
+ _convNodes.push_back(curEntry);
+ printf("Node %i, ID %i, entries %i\n", _convNodes.size(), curEntry->id, curEntry->entryCount);
+ // flags = 0: node has more than 1 entry
+ // flags = 65535: node has 1 entry
+ }
+ printf("Conversation has %i nodes\n", _convNodes.size());
+ printf("\n");
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 4 contains the conversation string offsets of chunk 5
+ // (unused, as it's unneeded - we find the offsets ourselves)
+
+ // ------------------------------------------------------------
+ // Chunk 5 contains the conversation strings
+ convS = convData.getItemStream(5);
+ //printf("Chunk 5: conversation strings\n");
+ //printf("Conv stream size: %i\n", convS->size());
+
+ *buffer = 0;
+
+ while(!convS->eos()) {
+ //if (curPos == 0)
+ // printf("%i: Offset %i: ", _convStrings.size(), convS->pos());
+ buffer[curPos++] = convS->readByte();
+ if (buffer[curPos - 1] == '~') { // filter out special characters
+ curPos--;
+ continue;
+ }
+ if (buffer[curPos - 1] == '\0') {
+ // end of string
+ //printf("%s\n", buffer);
+ buf = new char[strlen(buffer)];
+ sprintf(buf, "%s", buffer);
+ _convStrings.push_back(buf);
+ curPos = 0;
+ *buffer = 0;
+ }
+ }
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 2: entry data
+ convS = convData.getItemStream(2);
+ //printf("Chunk 2 - entry data\n");
+ //printf("Conv stream size: %i\n", convS->size());
+
+ for (i = 0; i < _convNodes.size(); i++) {
+ for (j = 0; j < _convNodes[i]->entryCount; j++) {
+ curEntry = new ConvEntry();
+ stringIndex = convS->readUint16LE();
+ if (stringIndex != 65535)
+ sprintf(curEntry->text, "%s", _convStrings[stringIndex]);
+ else
+ *curEntry->text = 0;
+ curEntry->id = convS->readUint16LE();
+ curEntry->offset = convS->readUint16LE();
+ curEntry->size = convS->readUint16LE();
+
+ _convNodes[i]->entries.push_back(curEntry);
+ //printf("Node %i, entry %i, id %i, offset %i, size %i, text: %s\n",
+ // i, j, curEntry->id, curEntry->offset, curEntry->size, curEntry->text);
+ }
+ }
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 3: message (MESG) chunks, created from the strings of chunk 5
+ convS = convData.getItemStream(3);
+ //printf("Chunk 3 - MESG chunk data\n");
+ //printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ curMessage = new MessageEntry();
+ stringIndex = convS->readUint16LE();
+ stringCount = convS->readUint16LE();
+ *buffer = 0;
+ //printf("Message: %i\n", _madsMessageList.size());
+ for (i = stringIndex; i < stringIndex + stringCount; i++) {
+ //printf("%i: %s\n", i, _convStrings[i]);
+ curMessage->messageStrings.push_back(_convStrings[i]);
+ }
+ _madsMessageList.push_back(curMessage);
+ //printf("----------\n");
+ }
+ //printf("\n");
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // TODO: finish this
+ // Chunk 6: conversation script
+ convS = convData.getItemStream(6);
+ printf("Chunk 6\n");
+ printf("Conv stream size: %i\n", convS->size());
+ /*while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("\n");
+ }
+ return;*/
+
+ for (i = 0; i < _convNodes.size(); i++) {
+ for (j = 0; j < _convNodes[i]->entryCount; j++) {
+ printf("*** Node %i entry %i data size %i\n", i, j, _convNodes[i]->entries[j]->size);
+ printf("Entry ID %i, text %s\n", _convNodes[i]->entries[j]->id, _convNodes[i]->entries[j]->text);
+ Common::SubReadStream *entryStream = new Common::SubReadStream(convS, _convNodes[i]->entries[j]->size);
+ readConvEntryActions(entryStream, _convNodes[i]->entries[j]);
+ delete entryStream;
+ printf("--------------------\n");
+ }
+ }
+
+ delete convS;
+}
+
+void Converse::readConvEntryActions(Common::SubReadStream *convS, ConvEntry *curEntry) {
+ uint8 chunk;
+ uint8 type; // 255: normal, 11: conditional
+ uint8 hasText1, hasText2;
+ int target;
+ int count = 0;
+ int var, val;
+ int messageIndex = 0;
+ int unk = 0;
+
+ while(!convS->eos()) {
+ chunk = convS->readByte();
+ type = convS->readByte();
+
+ switch (chunk) {
+ case 1:
+ printf("TODO: chunk type %i\n", chunk);
+ break;
+ case 2:
+ printf("HIDE\n");
+ convS->readByte();
+ count = convS->readByte();
+ printf("%i entries: ", count);
+ for (int i = 0; i < count; i++)
+ printf("%i %d", i, convS->readUint16LE());
+ printf("\n");
+ break;
+ case 3:
+ printf("UNHIDE\n");
+ convS->readByte();
+ count = convS->readByte();
+ printf("%i entries: ", count);
+ for (int i = 0; i < count; i++)
+ printf("%i %d", i, convS->readUint16LE());
+ printf("\n");
+ break;
+ case 4: // MESSAGE
+ printf("MESSAGE\n");
+
+ if (type == 255) {
+ //printf("unconditional\n");
+ } else if (type == 11) {
+ //printf("conditional\n");
+ } else {
+ printf("unknown type: %i\n", type);
+ }
+
+ // Conditional part
+ if (type == 11) {
+ unk = convS->readUint16LE(); // 1
+ if (unk != 1)
+ printf("Message: unk != 1 (it's %i)\n", unk);
+
+ var = convS->readUint16LE();
+ val = convS->readUint16LE();
+ printf("Var %i == %i\n", var, val);
+ }
+ unk = convS->readUint16LE(); // 256
+ if (unk != 256)
+ printf("Message: unk != 256 (it's %i)\n", unk);
+
+ // it seems that the first text entry is set when the message
+ // chunk is supposed to be shown unconditionally, whereas the second text
+ // entry is set when the message is supposed to be shown conditionally
+ hasText1 = convS->readByte();
+ hasText2 = convS->readByte();
+
+ if (hasText1 == 1) {
+ messageIndex = convS->readUint16LE();
+ printf("Message 1 index: %i, text:\n", messageIndex);
+ for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
+ printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
+ }
+ }
+
+ if (hasText2 == 1) {
+ messageIndex = convS->readUint16LE();
+ if (hasText1 == 0) {
+ printf("Message 2 index: %i, text:\n", messageIndex);
+ for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
+ printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
+ }
+ }
+ }
+
+ break;
+ case 5: // AUTO
+ printf("AUTO\n");
+ for (int k = 0; k < 4; k++)
+ convS->readByte();
+ messageIndex = convS->readUint16LE();
+ printf("Message index: %i, text:\n", messageIndex);
+ for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
+ printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
+ }
+
+ convS->readUint16LE();
+ break;
+ case 6:
+ printf("TODO: chunk type %i\n", chunk);
+ break;
+ case 7: // GOTO
+ unk = convS->readUint32LE(); // 0
+ if (unk != 0 && unk != 1)
+ printf("Goto: unk != 0 or 1 (it's %i)\n", unk);
+
+ target = convS->readUint16LE();
+ convS->readUint16LE(); // 255
+
+ if (unk != 0)
+ printf("Goto: unk != 0 (it's %i)\n", unk);
+
+ if (target != 65535)
+ printf("GOTO node %i\n", target);
+ else
+ printf("GOTO exit\n");
+ break;
+ case 8:
+ printf("TODO: chunk type %i\n", chunk);
+ break;
+ case 9: // ASSIGN
+ //printf("ASSIGN\n");
+ unk = convS->readUint32LE(); // 0
+
+ if (unk != 0)
+ printf("Assign: unk != 0 (it's %i)\n", unk);
+
+ val = convS->readUint16LE();
+ var = convS->readUint16LE();
+ //printf("Var %i = %i\n", var, val);
+ break;
+ default:
+ printf ("Unknown chunk type! (%i)\n", chunk);
+ break;
+ }
+ }
+ printf("\n");
+}
+
+void Converse::setEntryInfo(int32 offset, EntryType type, int32 nodeIndex, int32 entryIndex) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ EntryInfo info;
+ info.targetType = type;
+ info.nodeIndex = nodeIndex;
+ info.entryIndex = entryIndex;
+ _offsetMap[hashOffset] = info;
+ //printf("Set entry info: offset %i, type %i, node %i, entry %i\n", offset, type, nodeIndex, entryIndex);
+}
+
+const EntryInfo* Converse::getEntryInfo(int32 offset) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ OffsetHashMap::const_iterator entry = _offsetMap.find(hashOffset);
+ if (entry != _offsetMap.end())
+ return &(entry->_value);
+ else
+ error("Undeclared entry offset: %i", offset);
+}
+
+void Converse::setValue(int32 offset, int32 value) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ _variables[hashOffset] = value;
+}
+
+const int32 Converse::getValue(int32 offset) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ ConvVarHashMap::const_iterator entry = _variables.find(hashOffset);
+ if (entry != _variables.end())
+ return entry->_value;
+ else
+ error("Undeclared variable offset: %i", offset);
+}
+
+bool Converse::evaluateCondition(int32 leftVal, int32 op, int32 rightVal) {
+ switch (op) {
+ case kOpPercent:
+ return (leftVal % rightVal == 0);
+ case kOpGreaterOrEqual:
+ return leftVal >= rightVal;
+ case kOpLessOrEqual:
+ return leftVal <= rightVal;
+ case kOpGreaterThan:
+ return leftVal > rightVal;
+ case kOpLessThan:
+ return leftVal < rightVal;
+ case kOpNotEqual:
+ case kOpCondNotEqual:
+ return leftVal != rightVal;
+ case kOpAssign:
+ return leftVal == rightVal;
+ case kOpAnd:
+ return leftVal && rightVal;
+ case kOpOr:
+ return leftVal || rightVal;
+ default:
+ error("Unknown conditional operator: %i", op);
+ }
+}
+
+bool Converse::performAction(EntryAction *action) {
+ if (action->isConditional) {
+ if (!evaluateCondition(getValue(action->condition.offset),
+ action->condition.op, action->condition.val))
+ return true; // don't perform this action
+ }
+
+ if (action->actionType == kAssignValue) {
+ //printf("Assigning variable at offset %i to value %i\n",
+ // action->targetOffset, action->value);
+ setValue(action->targetOffset, action->value);
+ return true; // nothing else to do in an assignment action
+ }
+
+ const EntryInfo *entryInfo = getEntryInfo(action->targetOffset);
+ ConvEntry *targetEntry;
+
+ if (entryInfo->nodeIndex >= 0 && entryInfo->entryIndex >= 0)
+ targetEntry = getNode(entryInfo->nodeIndex)->entries[entryInfo->entryIndex];
+ else if (entryInfo->nodeIndex >= 0)
+ targetEntry = getNode(entryInfo->nodeIndex);
+ else
+ error("Target node id is negative");
+
+ switch (action->actionType) {
+ case kGotoEntry:
+ //printf("Goto entry at offset %i. Associated node is %i, entry %i\n",
+ // action->targetOffset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ _vm->_conversationView->setNode(entryInfo->nodeIndex);
+ if (entryInfo->entryIndex >= 0)
+ _vm->_conversationView->selectEntry(entryInfo->entryIndex);
+ return false;
+ case kHideEntry:
+ //printf("Hide entry at offset %i. Associated node is %i, entry %i\n",
+ // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ targetEntry->visible = false;
+ return true;
+ case kUnhideEntry:
+ //printf("Show entry at offset %i. Associated node is %i, entry %i\n",
+ // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ targetEntry->visible = true;
+ return true;
+ case kDestroyEntry:
+ //printf("Destroy entry at offset %i. Associated node is %i, entry %i\n",
+ // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ if (entryInfo->entryIndex >= 0)
+ getNode(entryInfo->nodeIndex)->entries.remove_at(entryInfo->entryIndex);
+ else
+ warning("Target entry is a node, not destroying it");
+ targetEntry->visible = true;
+ return true;
+ case kExitConv:
+ //printf("Exit conversation\n");
+ endConversation();
+ return false;
+ default:
+ warning("Unknown entry action");
+ return false;
+ } // end switch
+}
+
+} // End of namespace M4