mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 01:39:57 +00:00
2180b2d6b5
svn-id: r54385
1240 lines
42 KiB
C++
1240 lines
42 KiB
C++
/* 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 "common/substream.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(MadsM4Engine *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->current()->setColours(2, 1, 3);
|
|
|
|
_currentNodeIndex = nodeIndex;
|
|
|
|
_activeItems.clear();
|
|
|
|
if (nodeIndex != -1) {
|
|
ConvEntry *node = _m4Vm->_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) {
|
|
//warning(kDebugConversations, "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->current()->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) {
|
|
//warning(kDebugConversations, "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 = _m4Vm->_converse->getEntryInfo(node->fallthroughOffset);
|
|
//warning(kDebugConversations, "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;
|
|
clear();
|
|
|
|
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->current()->setColour((_highlightedIndex == i) ? CONVERSATION_ENTRY_HIGHLIGHTED :
|
|
CONVERSATION_ENTRY_NORMAL);
|
|
|
|
_vm->_font->current()->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, int32 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)) {
|
|
//debugCN(kDebugConversations, "Hiding selected entry\n");
|
|
_m4Vm->_converse->getNode(_currentNodeIndex)->entries[entryIndex]->visible = false;
|
|
} else {
|
|
//debugCN(kDebugConversations, "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 (!_m4Vm->_converse->evaluateCondition(
|
|
_m4Vm->_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;
|
|
//debugCN(kDebugConversations, "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
|
|
|
|
//debugCN(kDebugConversations, "Current selection does %i actions\n", _activeItems[entryIndex]->actions.size());
|
|
for (uint32 i = 0; i < _activeItems[_highlightedIndex]->actions.size(); i++) {
|
|
if (!_m4Vm->_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 = _m4Vm->scene()->getInterface()->isVisible();
|
|
_vm->_player->setCommandsAllowed(false);
|
|
_style = style;
|
|
|
|
if (showConverseBox) {
|
|
_vm->_conversationView->show();
|
|
_vm->_mouse->lockCursor(CURSOR_ARROW);
|
|
|
|
if (_interfaceWasVisible)
|
|
_m4Vm->scene()->getInterface()->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)
|
|
_m4Vm->scene()->getInterface()->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 = 0;
|
|
uint32 i = 0;
|
|
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) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "Conversation name: %s\n", buffer);
|
|
|
|
while (true) {
|
|
chunkPos = convS->pos();
|
|
chunk = convS->readUint32LE(); // read chunk
|
|
if (convS->eos()) break;
|
|
|
|
if (debugFlag) debugC(kDebugConversations, "***** Pos: %i -> ", chunkPos);
|
|
switch (chunk) {
|
|
case CHUNK_DECL: // Declare
|
|
if (debugFlag) debugCN(kDebugConversations, "DECL chunk\n");
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Tag: %i\n", data);
|
|
if (data > 0)
|
|
warning("Tag > 0 in DECL chunk, value is: %i", data); // TODO
|
|
val = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Value: %i\n", val);
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "NODE chunk\n");
|
|
} else {
|
|
if (debugFlag) debugCN(kDebugConversations, "LNOD chunk\n");
|
|
}
|
|
curNode = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Node number: %i\n", curNode);
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Tag: %i\n", data);
|
|
if (chunk == CHUNK_LNOD) {
|
|
autoSelectIndex = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Autoselect entry number: %i\n", autoSelectIndex);
|
|
}
|
|
size = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Number of entries: %i\n", size);
|
|
for (i = 0; i < size; i++) {
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "ETRY chunk\n");
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Unknown (attributes perhaps?): %i\n", data);
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Entry flags: ");
|
|
if (debugFlag) if (data & kEntryInitial) debugCN(kDebugConversations, "Initial ");
|
|
if (debugFlag) if (data & kEntryPersists) debugCN(kDebugConversations, "Persists ");
|
|
if (debugFlag) debugCN(kDebugConversations, "\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) debugCN(kDebugConversations, "WRPL chunk\n");
|
|
size = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "- Weight: %i ", data);
|
|
weightedEntry->weight = data;
|
|
replyEntry->totalWeight += weightedEntry->weight;
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "offset: %i\n", data);
|
|
replyEntry->entries.push_back(weightedEntry);
|
|
}
|
|
currentWeightedEntry = 0;
|
|
} else {
|
|
replyEntry->entryType = kReply;
|
|
if (debugFlag) debugCN(kDebugConversations, "RPLY chunk\n");
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Reply data offset: %i\n", data);
|
|
}
|
|
|
|
if (!curEntry)
|
|
error("Converse::loadConversation(): curEntry is NULL while adding a reply");
|
|
|
|
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((int)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 && (int)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) debugCN(kDebugConversations, "TEXT chunk\n");
|
|
} else {
|
|
if (debugFlag) debugCN(kDebugConversations, "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++;
|
|
} else {
|
|
error("Converse::loadConversation(): Unexpected reply entry while processing TEXT/MESG chunk");
|
|
}
|
|
|
|
size = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "Voice file: %s\n", parentEntry->voiceFile);
|
|
|
|
if (chunk == CHUNK_TEXT) {
|
|
convS->read(buffer, size);
|
|
if (debugFlag) debugCN(kDebugConversations, "Text: %s\n", buffer);
|
|
sprintf(parentEntry->text, "%s", buffer);
|
|
} else {
|
|
while (size > 0) {
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "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) debugCN(kDebugConversations, "GOTO chunk\n");
|
|
} else if (chunk == CHUNK_HIDE || chunk == CHUNK_CHDE) {
|
|
curAction->actionType = kHideEntry;
|
|
if (debugFlag) debugCN(kDebugConversations, "HIDE chunk\n");
|
|
} else if (chunk == CHUNK_UHID || chunk == CHUNK_CUHD) {
|
|
curAction->actionType = kUnhideEntry;
|
|
if (debugFlag) debugCN(kDebugConversations, "UHID chunk\n");
|
|
} else if (chunk == CHUNK_DSTR || chunk == CHUNK_CDST) {
|
|
curAction->actionType = kDestroyEntry;
|
|
if (debugFlag) debugCN(kDebugConversations, "DSTR chunk\n");
|
|
} else if (chunk == CHUNK_EXIT || chunk == CHUNK_CEGO) {
|
|
curAction->actionType = kExitConv;
|
|
if (debugFlag) debugCN(kDebugConversations, "EXIT chunk\n");
|
|
}
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Offset: %i\n", data);
|
|
curAction->targetOffset = data;
|
|
curEntry->actions.push_back(curAction);
|
|
break;
|
|
case CHUNK_FALL: // Fallthrough
|
|
if (debugFlag) debugCN(kDebugConversations, "FALL chunk\n");
|
|
size = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "Minimum nodes: %i\n", size);
|
|
_convNodes[curNode]->fallthroughMinEntries = size;
|
|
data = convS->readUint32LE();
|
|
if (debugFlag) debugCN(kDebugConversations, "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 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);
|
|
debugCN(kDebugConversations, "Chunk 0\n");
|
|
debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
|
|
while (!convS->eos()) { // FIXME (eos changed)
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
}
|
|
debugCN(kDebugConversations, "\n");
|
|
|
|
// ------------------------------------------------------------
|
|
// Chunk 1
|
|
convS = convDataD.getItemStream(1);
|
|
debugCN(kDebugConversations, "Chunk 1\n");
|
|
debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
|
|
while (!convS->eos()) { // FIXME (eos changed)
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
}
|
|
debugCN(kDebugConversations, "\n");
|
|
|
|
// ------------------------------------------------------------
|
|
// Chunk 2
|
|
convS = convDataD.getItemStream(2);
|
|
debugCN(kDebugConversations, "Chunk 2\n");
|
|
debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
|
|
while (!convS->eos()) { // FIXME (eos changed)
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
}
|
|
debugCN(kDebugConversations, "\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);
|
|
debugCN(kDebugConversations, "Chunk 0\n");
|
|
debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
debugCN(kDebugConversations, "\n");
|
|
count = convS->readUint16LE(); // conversation face count (usually 2)
|
|
debugCN(kDebugConversations, "Conversation faces: %i\n", count);
|
|
for (i = 0; i < 5; i++) {
|
|
convS->read(buffer, 16);
|
|
debugCN(kDebugConversations, "Face %i: %s ", i + 1, buffer);
|
|
}
|
|
debugCN(kDebugConversations, "\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++) {
|
|
debugCN(kDebugConversations, "%i ", convS->readUint16LE());
|
|
}
|
|
debugCN(kDebugConversations, "\n");
|
|
|
|
convS->read(buffer, 14); // speech file
|
|
debugCN(kDebugConversations, "Speech file: %s\n", buffer);
|
|
|
|
while (!convS->eos()) { // FIXME: eos changed
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
}
|
|
debugCN(kDebugConversations, "\n");
|
|
|
|
delete convS;
|
|
|
|
// ------------------------------------------------------------
|
|
// Chunk 1: Conversation nodes
|
|
convS = convData.getItemStream(1);
|
|
debugCN(kDebugConversations, "Chunk 1: conversation nodes\n");
|
|
debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
|
|
while (true) {
|
|
uint16 id = convS->readUint16LE();
|
|
if (convS->eos()) break;
|
|
|
|
curEntry = new ConvEntry();
|
|
curEntry->id = id;
|
|
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);
|
|
debugCN(kDebugConversations, "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
|
|
}
|
|
debugCN(kDebugConversations, "Conversation has %i nodes\n", _convNodes.size());
|
|
debugCN(kDebugConversations, "\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);
|
|
//debugCN(kDebugConversations, "Chunk 5: conversation strings\n");
|
|
//debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
|
|
*buffer = 0;
|
|
|
|
while (true) {
|
|
//if (curPos == 0)
|
|
// debugCN(kDebugConversations, "%i: Offset %i: ", _convStrings.size(), convS->pos());
|
|
uint8 b = convS->readByte();
|
|
if (convS->eos()) break;
|
|
|
|
buffer[curPos++] = b;
|
|
if (buffer[curPos - 1] == '~') { // filter out special characters
|
|
curPos--;
|
|
continue;
|
|
}
|
|
if (buffer[curPos - 1] == '\0') {
|
|
// end of string
|
|
//debugCN(kDebugConversations, "%s\n", buffer);
|
|
buf = new char[strlen(buffer) + 1];
|
|
strcpy(buf, buffer);
|
|
_convStrings.push_back(buf);
|
|
curPos = 0;
|
|
*buffer = 0;
|
|
}
|
|
}
|
|
|
|
delete convS;
|
|
|
|
// ------------------------------------------------------------
|
|
// Chunk 2: entry data
|
|
convS = convData.getItemStream(2);
|
|
//debugCN(kDebugConversations, "Chunk 2 - entry data\n");
|
|
//debugCN(kDebugConversations, "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);
|
|
//debugCN(kDebugConversations, "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);
|
|
//debugCN(kDebugConversations, "Chunk 3 - MESG chunk data\n");
|
|
//debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
|
|
while (true) {
|
|
uint16 index = convS->readUint16LE();
|
|
if (convS->eos()) break;
|
|
|
|
curMessage = new MessageEntry();
|
|
stringIndex = index;
|
|
stringCount = convS->readUint16LE();
|
|
*buffer = 0;
|
|
//debugCN(kDebugConversations, "Message: %i\n", _madsMessageList.size());
|
|
for (i = stringIndex; i < stringIndex + stringCount; i++) {
|
|
//debugCN(kDebugConversations, "%i: %s\n", i, _convStrings[i]);
|
|
curMessage->messageStrings.push_back(_convStrings[i]);
|
|
}
|
|
_madsMessageList.push_back(curMessage);
|
|
//debugCN(kDebugConversations, "----------\n");
|
|
}
|
|
//debugCN(kDebugConversations, "\n");
|
|
|
|
delete convS;
|
|
|
|
// ------------------------------------------------------------
|
|
// TODO: finish this
|
|
// Chunk 6: conversation script
|
|
convS = convData.getItemStream(6);
|
|
debugCN(kDebugConversations, "Chunk 6\n");
|
|
debugCN(kDebugConversations, "Conv stream size: %i\n", convS->size());
|
|
/*while (!convS->eos()) { // FIXME (eos changed)
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "%i ", convS->readByte());
|
|
debugCN(kDebugConversations, "\n");
|
|
}
|
|
return;*/
|
|
|
|
for (i = 0; i < _convNodes.size(); i++) {
|
|
for (j = 0; j < _convNodes[i]->entryCount; j++) {
|
|
debugCN(kDebugConversations, "*** Node %i entry %i data size %i\n", i, j, _convNodes[i]->entries[j]->size);
|
|
debugCN(kDebugConversations, "Entry ID %i, text %s\n", _convNodes[i]->entries[j]->id, _convNodes[i]->entries[j]->text);
|
|
Common::ReadStream *entryStream = new Common::SubReadStream(convS, _convNodes[i]->entries[j]->size);
|
|
readConvEntryActions(entryStream, _convNodes[i]->entries[j]);
|
|
delete entryStream;
|
|
debugCN(kDebugConversations, "--------------------\n");
|
|
}
|
|
}
|
|
|
|
delete convS;
|
|
}
|
|
|
|
void Converse::readConvEntryActions(Common::ReadStream *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 (true) {
|
|
chunk = convS->readByte();
|
|
if (convS->eos()) break;
|
|
|
|
type = convS->readByte();
|
|
|
|
switch (chunk) {
|
|
case 1:
|
|
debugCN(kDebugConversations, "TODO: chunk type %i\n", chunk);
|
|
break;
|
|
case 2:
|
|
debugCN(kDebugConversations, "HIDE\n");
|
|
convS->readByte();
|
|
count = convS->readByte();
|
|
debugCN(kDebugConversations, "%i entries: ", count);
|
|
for (int i = 0; i < count; i++)
|
|
debugCN(kDebugConversations, "%i %d", i, convS->readUint16LE());
|
|
debugCN(kDebugConversations, "\n");
|
|
break;
|
|
case 3:
|
|
debugCN(kDebugConversations, "UNHIDE\n");
|
|
convS->readByte();
|
|
count = convS->readByte();
|
|
debugCN(kDebugConversations, "%i entries: ", count);
|
|
for (int i = 0; i < count; i++)
|
|
debugCN(kDebugConversations, "%i %d", i, convS->readUint16LE());
|
|
debugCN(kDebugConversations, "\n");
|
|
break;
|
|
case 4: // MESSAGE
|
|
debugCN(kDebugConversations, "MESSAGE\n");
|
|
|
|
if (type == 255) {
|
|
//debugCN(kDebugConversations, "unconditional\n");
|
|
} else if (type == 11) {
|
|
//debugCN(kDebugConversations, "conditional\n");
|
|
} else {
|
|
debugCN(kDebugConversations, "unknown type: %i\n", type);
|
|
}
|
|
|
|
// Conditional part
|
|
if (type == 11) {
|
|
unk = convS->readUint16LE(); // 1
|
|
if (unk != 1)
|
|
debugCN(kDebugConversations, "Message: unk != 1 (it's %i)\n", unk);
|
|
|
|
var = convS->readUint16LE();
|
|
val = convS->readUint16LE();
|
|
debugCN(kDebugConversations, "Var %i == %i\n", var, val);
|
|
}
|
|
unk = convS->readUint16LE(); // 256
|
|
if (unk != 256)
|
|
debugCN(kDebugConversations, "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();
|
|
debugCN(kDebugConversations, "Message 1 index: %i, text:\n", messageIndex);
|
|
for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
|
|
debugCN(kDebugConversations, "%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
|
|
}
|
|
}
|
|
|
|
if (hasText2 == 1) {
|
|
messageIndex = convS->readUint16LE();
|
|
if (hasText1 == 0) {
|
|
debugCN(kDebugConversations, "Message 2 index: %i, text:\n", messageIndex);
|
|
for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
|
|
debugCN(kDebugConversations, "%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 5: // AUTO
|
|
debugCN(kDebugConversations, "AUTO\n");
|
|
for (int k = 0; k < 4; k++)
|
|
convS->readByte();
|
|
messageIndex = convS->readUint16LE();
|
|
debugCN(kDebugConversations, "Message index: %i, text:\n", messageIndex);
|
|
for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
|
|
debugCN(kDebugConversations, "%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
|
|
}
|
|
|
|
convS->readUint16LE();
|
|
break;
|
|
case 6:
|
|
debugCN(kDebugConversations, "TODO: chunk type %i\n", chunk);
|
|
break;
|
|
case 7: // GOTO
|
|
unk = convS->readUint32LE(); // 0
|
|
if (unk != 0 && unk != 1)
|
|
debugCN(kDebugConversations, "Goto: unk != 0 or 1 (it's %i)\n", unk);
|
|
|
|
target = convS->readUint16LE();
|
|
convS->readUint16LE(); // 255
|
|
|
|
if (unk != 0)
|
|
debugCN(kDebugConversations, "Goto: unk != 0 (it's %i)\n", unk);
|
|
|
|
if (target != 65535)
|
|
debugCN(kDebugConversations, "GOTO node %i\n", target);
|
|
else
|
|
debugCN(kDebugConversations, "GOTO exit\n");
|
|
break;
|
|
case 8:
|
|
debugCN(kDebugConversations, "TODO: chunk type %i\n", chunk);
|
|
break;
|
|
case 9: // ASSIGN
|
|
//debugCN(kDebugConversations, "ASSIGN\n");
|
|
unk = convS->readUint32LE(); // 0
|
|
|
|
if (unk != 0)
|
|
debugCN(kDebugConversations, "Assign: unk != 0 (it's %i)\n", unk);
|
|
|
|
val = convS->readUint16LE();
|
|
var = convS->readUint16LE();
|
|
//debugCN(kDebugConversations, "Var %i = %i\n", var, val);
|
|
break;
|
|
default:
|
|
debugCN(kDebugConversations, "Unknown chunk type! (%i)\n", chunk);
|
|
break;
|
|
}
|
|
}
|
|
debugCN(kDebugConversations, "\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;
|
|
//debugCN(kDebugConversations, "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;
|
|
}
|
|
|
|
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) {
|
|
//debugCN(kDebugConversations, "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:
|
|
//debugCN(kDebugConversations, "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:
|
|
//debugCN(kDebugConversations, "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:
|
|
//debugCN(kDebugConversations, "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:
|
|
//debugCN(kDebugConversations, "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:
|
|
//debugCN(kDebugConversations, "Exit conversation\n");
|
|
endConversation();
|
|
return false;
|
|
default:
|
|
warning("Unknown entry action");
|
|
return false;
|
|
} // end switch
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
MadsConversation::MadsConversation() {
|
|
for (int i = 0; i < MADS_TALK_SIZE; ++i) {
|
|
_talkList[i].desc = NULL;
|
|
_talkList[i].id = 0;
|
|
}
|
|
}
|
|
|
|
|
|
} // End of namespace M4
|