scummvm/engines/mads/conversations.cpp
2016-01-18 17:12:54 -05:00

1019 lines
29 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.
*
*/
#include "mads/conversations.h"
#include "mads/mads.h"
#include "mads/compression.h"
#include "common/file.h"
#include "common/util.h" // for Common::hexdump
namespace MADS {
GameConversations::GameConversations(MADSEngine *vm) : _vm(vm) {
_runningConv = nullptr;
_restoreRunning = 0;
_playerEnabled = false;
_inputMode = kInputBuildingSentences;
_startFrameNumber = 0;
_speakerVal = 0;
_currentMode = CONVMODE_NONE;
_priorMode = CONVMODE_NONE;
_popupVisible = false;
_verbId = 0;
_vars = _nextStartNode = nullptr;
_heroTrigger = 0;
_heroTriggerMode = SEQUENCE_TRIGGER_PARSER;
_interlocutorTrigger = 0;
_interlocutorTriggerMode = SEQUENCE_TRIGGER_PARSER;
_currentNode = 0;
_dialogNodeOffset = _dialogNodeSize = 0;
_dialog = nullptr;
_dialogAltFlag = false;
_personSpeaking = 0;
// Mark all conversation slots as empty
for (int idx = 0; idx < MAX_CONVERSATIONS; ++idx)
_conversations[idx]._convId = -1;
}
GameConversations::~GameConversations() {
}
void GameConversations::load(int id) {
// Scan through the conversation list for a free slot
int slotIndex = -1;
for (int idx = 0; idx < MAX_CONVERSATIONS && slotIndex == -1; ++idx) {
if (_conversations[idx]._convId == -1)
slotIndex = idx;
}
if (slotIndex == -1)
error("Too many conversations loaded");
// Set the conversation the slot will contain
_conversations[slotIndex]._convId = id;
// Load the conversation data
Common::String cnvFilename = Common::String::format("CONV%03d.CNV", id);
_conversations[slotIndex]._data.load(cnvFilename);
// Load the conversation's CND data
Common::String cndFilename = Common::String::format("CONV%03d.CND", id);
_conversations[slotIndex]._cnd.load(cndFilename);
}
ConversationEntry *GameConversations::getConv(int convId) {
for (uint idx = 0; idx < MAX_CONVERSATIONS; ++idx) {
if (_conversations[idx]._convId == convId)
return &_conversations[idx];
}
return nullptr;
}
void GameConversations::run(int id) {
// If another conversation is running, then stop it first
if (_runningConv)
stop();
// Get the next conversation to run
_runningConv = getConv(id);
if (!_runningConv)
error("Specified conversation %d not loaded", id);
// Initialize needed fields
_startFrameNumber = _vm->_events->getFrameCounter();
_playerEnabled = _vm->_game->_player._stepEnabled;
_inputMode = _vm->_game->_screenObjects._inputMode;
_heroTrigger = 0;
_interlocutorTrigger = 0;
_popupVisible = false;
_currentMode = CONVMODE_0;
_verbId = -1;
_speakerVal = 1;
_personSpeaking = 1;
// Initialize speaker arrays
Common::fill(&_speakerActive[0], &_speakerActive[MAX_SPEAKERS], false);
Common::fill(&_speakerSeries[0], &_speakerSeries[MAX_SPEAKERS], -1);
Common::fill(&_speakerFrame[0], &_speakerFrame[MAX_SPEAKERS], 1);
Common::fill(&_popupX[0], &_popupX[MAX_SPEAKERS], POPUP_CENTER);
Common::fill(&_popupY[0], &_popupY[MAX_SPEAKERS], POPUP_CENTER);
Common::fill(&_popupMaxLen[0], &_popupMaxLen[MAX_SPEAKERS], 30);
// Start the conversation
start();
// Setup variables to point to data in the speaker arrays
setVariable(2, &_speakerVal);
for (int idx = 0; idx < MAX_SPEAKERS; ++idx) {
setVariable(3 + idx, &_speakerFrame[idx]);
setVariable(8 + idx, &_popupX[idx]);
setVariable(13 + idx, &_popupY[idx]);
setVariable(18 + idx, &_popupMaxLen[idx]);
}
// Load sprite data for speaker portraits
for (uint idx = 0; idx < _runningConv->_data._speakerCount; ++idx) {
const Common::String &portraitName = _runningConv->_data._portraits[idx];
_speakerSeries[idx] = _vm->_game->_scene._sprites.addSprites(portraitName, PALFLAG_RESERVED);
if (_speakerSeries[idx] > 0) {
_speakerActive[idx] = true;
_speakerFrame[idx] = _runningConv->_data._speakerFrame[idx];
}
}
// Refresh colors if needed
if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE)
_vm->_palette->refreshSceneColors();
}
void GameConversations::start() {
assert(_runningConv->_cnd._vars.size() >= 2);
_vars = &_runningConv->_cnd._vars[0];
_nextStartNode = &_runningConv->_cnd._vars[1];
_runningConv->_cnd._currentNode = -1;
_runningConv->_cnd._numImports = 0;
_runningConv->_cnd._vars[0].setValue(_nextStartNode->_val);
// Store a reference to the variables list in the script handler for later reference
ScriptEntry::Conditional::_vars = &_runningConv->_cnd._vars;
}
void GameConversations::setVariable(uint idx, int val) {
if (active())
_runningConv->_cnd._vars[idx].setValue(val);
}
void GameConversations::setVariable(uint idx, int *val) {
if (active())
_runningConv->_cnd._vars[idx].setValue(val);
}
void GameConversations::setStartNode(uint nodeIndex) {
assert(_nextStartNode && _nextStartNode->_isPtr == false);
_nextStartNode->_val = nodeIndex;
}
void GameConversations::stop() {
// Only need to proceed if there is an active conversation
if (!active())
return;
// Reset player enabled state if needed
if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE)
_vm->_game->_player._stepEnabled = _playerEnabled;
// Remove any visible dialog window
removeActiveWindow();
// Release any sprites used for character portraits
for (int idx = 0; idx < _runningConv->_data._speakerCount; ++idx) {
if (_speakerActive[idx])
_vm->_game->_scene._sprites.remove(_speakerSeries[idx]);
}
// Flag conversation as no longer running
_runningConv = nullptr;
if (_inputMode == kInputConversation)
_vm->_game->_scene._userInterface.emptyConversationList();
_vm->_game->_scene._userInterface.setup(_inputMode);
}
void GameConversations::exportPointer(int *ptr) {
// Only need to proceed if there is an active conversation
if (!active())
return;
// Also don't proceed if the number of allowed imports has already been reached
if (_runningConv->_cnd._numImports >= _runningConv->_data._maxImports)
return;
// Get the variable to use for this next import and set it's value
int variableIndex = _runningConv->_cnd._importVariables[
_runningConv->_cnd._numImports++];
setVariable(variableIndex, ptr);
}
void GameConversations::exportValue(int val) {
// Only need to proceed if there is an active conversation
if (!active())
return;
// Also don't proceed if the number of allowed imports has already been reached
if (_runningConv->_cnd._numImports >= _runningConv->_data._maxImports)
return;
// Get the variable to use for this next import and set it's value
int variableIndex = _runningConv->_cnd._importVariables[
_runningConv->_cnd._numImports++];
setVariable(variableIndex, val);
}
void GameConversations::setHeroTrigger(int val) {
_heroTrigger = val;
_heroTriggerMode = _vm->_game->_triggerSetupMode;
}
void GameConversations::setInterlocutorTrigger(int val) {
_interlocutorTrigger = val;
_interlocutorTriggerMode = _vm->_game->_triggerSetupMode;
}
int *GameConversations::getVariable(int idx) {
assert(idx >= 0); // TODO: Some negative values are allowed? Investigate
return _vars[idx].getValue();
}
void GameConversations::hold() {
if (_currentMode != CONVMODE_NONE) {
_priorMode = _currentMode;
_currentMode = CONVMODE_NONE;
}
}
void GameConversations::release() {
if (_currentMode == CONVMODE_NONE) {
_currentMode = _priorMode;
if (_currentMode == 1 || _currentMode == 2)
update(true);
}
}
void GameConversations::flagEntry(DialogCommand mode, int entryIndex) {
assert(_runningConv);
uint &flags = _runningConv->_cnd._entryFlags[entryIndex];
switch (mode) {
case CMD_1:
flags |= ENTRYFLAG_4000;
flags &= ~ENTRYFLAG_8000;
break;
case CMD_HIDE:
flags &= ~ENTRYFLAG_8000;
break;
case CMD_UNHIDE:
if (!(flags & ENTRYFLAG_4000))
flags |= ENTRYFLAG_8000;
break;
default:
break;
}
}
void GameConversations::reset(int id) {
warning("TODO: GameConversations::reset");
}
void GameConversations::update(bool flag) {
// Only need to proceed if there is an active conversation
if (!active())
return;
ConversationVar &var0 = _runningConv->_cnd._vars[0];
switch (_currentMode) {
case CONVMODE_0:
assert(var0.isNumeric());
if (var0._val < 0) {
if (_vm->_game->_scene._frameStartTime >= _startFrameNumber) {
removeActiveWindow();
if (_heroTrigger) {
_vm->_game->_scene._action._activeAction._verbId = _verbId;
_vm->_game->_trigger = _heroTrigger;
_vm->_game->_triggerMode = _heroTriggerMode;
_heroTrigger = 0;
}
_currentMode = CONVMODE_STOP;
}
} else {
bool isActive = nextNode();
_currentNode = var0._val;
if (isActive) {
_verbId = _runningConv->_data._nodes[_currentNode]._index;
_vm->_game->_scene._action._activeAction._verbId = _verbId;
_vm->_game->_scene._action._inProgress = true;
_vm->_game->_scene._action._savedFields._commandError = false;
_currentMode = CONVMODE_1;
} else {
_currentMode = generateMenu();
}
}
break;
case CONVMODE_1:
if (flag)
_currentMode = CONVMODE_3;
break;
case CONVMODE_2:
if (flag) {
_vm->_game->_player._stepEnabled = false;
_verbId = _vm->_game->_scene._action._activeAction._verbId;
if (!(_runningConv->_cnd._entryFlags[_verbId] & ENTRYFLAG_2))
flagEntry(CMD_HIDE, _verbId);
removeActiveWindow();
_vm->_game->_scene._userInterface.emptyConversationList();
_vm->_game->_scene._userInterface.setup(kInputConversation);
_personSpeaking = 0;
executeEntry(_verbId);
ConvDialog &dialog = _runningConv->_data._dialogs[_verbId];
if (dialog._speechIndex) {
_runningConv->_cnd._messageList3.clear();
_runningConv->_cnd._messageList3.push_back(dialog._speechIndex);
}
generateText(dialog._textLineIndex, _runningConv->_cnd._messageList3);
_currentMode = CONVMODE_0;
if (_heroTrigger) {
_vm->_game->_scene._action._activeAction._verbId = _verbId;
_vm->_game->_trigger = _heroTrigger;
_vm->_game->_triggerMode = _heroTriggerMode;
_heroTrigger = 0;
}
}
break;
case CONVMODE_3:
if (_vm->_game->_scene._frameStartTime >= _startFrameNumber) {
removeActiveWindow();
_personSpeaking = 0;
executeEntry(_verbId);
generateMessage(_runningConv->_cnd._messageList1, _runningConv->_cnd._messageList3);
if (_heroTrigger && _popupVisible) {
_vm->_game->_scene._action._activeAction._verbId = _verbId;
_vm->_game->_trigger = _heroTrigger;
_vm->_game->_triggerMode = _heroTriggerMode;
_heroTrigger = 0;
}
_currentMode = CONVMODE_4;
}
break;
case CONVMODE_4:
if (_vm->_game->_scene._frameStartTime >= _startFrameNumber) {
removeActiveWindow();
_personSpeaking = _speakerVal;
generateMessage(_runningConv->_cnd._messageList2, _runningConv->_cnd._messageList4);
if (_interlocutorTrigger && _popupVisible) {
_vm->_game->_scene._action._activeAction._verbId = _verbId;
_vm->_game->_trigger = _interlocutorTrigger;
_vm->_game->_triggerMode = _interlocutorTriggerMode;
_interlocutorTrigger = 0;
}
}
break;
case CONVMODE_STOP:
stop();
break;
default:
break;
}
warning("TODO: GameConversations::update");
}
void GameConversations::removeActiveWindow() {
warning("TODO: GameConversations::removeActiveWindow");
}
ConversationMode GameConversations::generateMenu() {
error("TODO: GameConversations::generateMenu");
}
void GameConversations::generateText(int textLineIndex, Common::Array<int> &messages) {
_dialogAltFlag = true;
error("TODO: GameConversations::generateText");
}
void GameConversations::generateMessage(Common::Array<int> &messageList, Common::Array<int> &voiceList) {
_dialogAltFlag = false;
if (messageList.size() == 0)
return;
if (_dialog)
delete _dialog;
// Get the speaker portrait
SpriteAsset &sprites = *_vm->_game->_scene._sprites[_speakerSeries[_personSpeaking]];
MSprite *portrait = sprites.getFrame(_speakerFrame[_personSpeaking]);
// Create the new text dialog
_dialog = new TextDialog(_vm, FONT_INTERFACE,
Common::Point(_popupX[_personSpeaking], _popupY[_personSpeaking]),
portrait, _popupMaxLen[_personSpeaking]);
// Add in the lines
for (uint msgNum = 0; msgNum < messageList.size(); ++msgNum) {
ConvMessage &msg = _runningConv->_data._messages[messageList[msgNum]];
uint stringIndex = msg._stringIndex;
for (uint strNum = 0; strNum < msg._count; ++strNum, ++stringIndex) {
Common::String textLine = _runningConv->_data._textLines[stringIndex];
textLine.trim();
_dialog->addLine(textLine);
}
}
// Show the dialog
_popupVisible = true;
_dialog->show();
// Play the speech if one was provided
if (voiceList.size() > 0) {
_vm->_audio->setSoundGroup(_runningConv->_data._speechFile);
_vm->_audio->playSound(voiceList[0] - 1);
}
}
bool GameConversations::nextNode() {
ConversationVar &var0 = _runningConv->_cnd._vars[0];
_runningConv->_cnd._currentNode = var0._val;
return _runningConv->_data._nodes[var0._val]._active;
}
int GameConversations::executeEntry(int index) {
ConvDialog &dlg = _runningConv->_data._dialogs[index];
ConversationVar &var0 = _runningConv->_cnd._vars[0];
_runningConv->_cnd._messageList1.clear();
_runningConv->_cnd._messageList2.clear();
_runningConv->_cnd._messageList3.clear();
_runningConv->_cnd._messageList4.clear();
_nextStartNode->_val = var0._val;
bool flag = true;
for (uint scriptIdx = 0; scriptIdx < dlg._script.size() && flag; ) {
ScriptEntry &scrEntry = dlg._script[scriptIdx];
if (scrEntry._command == CMD_END)
break;
switch (scrEntry._command) {
case CMD_1:
case CMD_HIDE:
case CMD_UNHIDE:
for (uint idx = 0; scrEntry._entries.size(); ++idx)
flagEntry(scrEntry._command, scrEntry._entries[idx]);
break;
case CMD_MESSAGE1:
case CMD_MESSAGE2:
scriptMessage(scrEntry);
break;
case CMD_ERROR:
error("Conversation script generated error");
break;
case CMD_NODE:
flag = !scriptNode(scrEntry);
break;
case CMD_GOTO: {
bool gotoFlag = scrEntry._conditionals[0].evaluate();
if (gotoFlag) {
scriptIdx = scrEntry._index;
continue;
}
break;
}
case CMD_ASSIGN: {
bool setFlag = scrEntry._conditionals[0].evaluate();
if (setFlag) {
int *ptr = _runningConv->_cnd._vars[scrEntry._index].getValue();
*ptr = scrEntry._conditionals[1].evaluate();
}
break;
}
default:
error("Unknown script opcode");
}
++scriptIdx;
}
if (flag) {
var0._val = -1;
}
return var0._val;
}
void GameConversations::scriptMessage(ScriptEntry &scrEntry) {
// Check whether this operation should be done
bool doFlag = scrEntry._conditionals[0].evaluate();
if (!doFlag)
return;
// Figure out the entire range that messages can be selected from
int total = 0;
for (uint idx = 0; idx < scrEntry._entries2.size(); ++idx)
total += scrEntry._entries2[idx]._size;
// Choose a random entry from the list of possible values
int randomVal = _vm->getRandomNumber(1, total);
int randomIndex = -1;
while (randomVal > 0 && randomIndex < (int)scrEntry._entries2.size()) {
++randomIndex;
randomVal -= scrEntry._entries2[randomIndex]._size;
}
if (randomIndex == (int)scrEntry._entries2.size())
randomIndex = 0;
int entryVal = scrEntry._entries2[randomIndex]._v2;
if (scrEntry._command == CMD_MESSAGE1) {
_runningConv->_cnd._messageList2.push_back(entryVal);
if (scrEntry._entries2.size() <= 1) {
for (uint idx = 0; idx < scrEntry._entries.size(); ++idx)
_runningConv->_cnd._messageList4.push_back(scrEntry._entries[idx]);
}
else if (scrEntry._entries.size() > 0 && randomIndex < (int)scrEntry._entries.size()) {
_runningConv->_cnd._messageList4.push_back(entryVal);
}
} else {
_runningConv->_cnd._messageList1.push_back(entryVal);
if (scrEntry._entries2.size() <= 1) {
for (uint idx = 0; idx < scrEntry._entries.size(); ++idx)
_runningConv->_cnd._messageList3.push_back(scrEntry._entries[idx]);
} else if (scrEntry._entries.size() > 0 && randomIndex < (int)scrEntry._entries.size()) {
_runningConv->_cnd._messageList3.push_back(entryVal);
}
}
}
bool GameConversations::scriptNode(ScriptEntry &scrEntry) {
bool doFlag = scrEntry._conditionals[0].evaluate();
if (!doFlag)
return false;
ConversationVar &var0 = _runningConv->_cnd._vars[0];
int val1 = scrEntry._conditionals[1].evaluate();
int val2 = scrEntry._conditionals[2].evaluate();
var0._val = val1;
if (val1 >= 0)
_nextStartNode->_val = val1;
else if (val2 >= 0)
_nextStartNode->_val = val2;
return true;
}
/*------------------------------------------------------------------------*/
void ConversationData::load(const Common::String &filename) {
Common::File inFile;
char buffer[16];
inFile.open(filename);
MadsPack convFileUnpacked(&inFile);
// **** Section 0: Header *************************************************
Common::SeekableReadStream *convFile = convFileUnpacked.getItemStream(0);
_nodeCount = convFile->readUint16LE();
_dialogCount = convFile->readUint16LE();
_messageCount = convFile->readUint16LE();
_textLineCount = convFile->readUint16LE();
_unk2 = convFile->readUint16LE();
_maxImports = convFile->readUint16LE();
_speakerCount = convFile->readUint16LE();
for (uint idx = 0; idx < MAX_SPEAKERS; ++idx) {
convFile->read(buffer, 16);
_portraits[idx] = buffer;
}
for (uint idx = 0; idx < MAX_SPEAKERS; ++idx) {
_speakerFrame[idx] = convFile->readUint16LE();
}
convFile->read(buffer, 14);
_speechFile = Common::String(buffer);
// Total text length in section 5
_textSize = convFile->readUint32LE();
_commandsSize = convFile->readUint32LE();
// The rest of the section 0 is padding to allow room for a set of pointers
// to the contents of the remaining sections loaded into memory as a
// continuous data block containing both the header and the sections
delete convFile;
// **** Section 1: Nodes **************************************************
convFile = convFileUnpacked.getItemStream(1);
_nodes.clear();
for (uint i = 0; i < _nodeCount; i++) {
ConvNode node;
node._index = convFile->readUint16LE();
node._dialogCount = convFile->readUint16LE();
node._unk1 = convFile->readSint16LE(); // TODO
node._active = convFile->readSint16LE() != 0;
node._unk3 = convFile->readSint16LE(); // TODO
_nodes.push_back(node);
}
delete convFile;
// **** Section 2: Dialogs ************************************************
convFile = convFileUnpacked.getItemStream(2);
assert(convFile->size() == _dialogCount * 8);
_dialogs.resize(_dialogCount);
for (uint idx = 0; idx < _dialogCount; ++idx) {
_dialogs[idx]._textLineIndex = convFile->readSint16LE();
_dialogs[idx]._speechIndex = convFile->readSint16LE();
_dialogs[idx]._scriptOffset = convFile->readUint16LE();
_dialogs[idx]._scriptSize = convFile->readUint16LE();
}
delete convFile;
// **** Section 3: Messages ***********************************************
convFile = convFileUnpacked.getItemStream(3);
assert(convFile->size() == _messageCount * 4);
_messages.resize(_messageCount);
for (uint idx = 0; idx < _messageCount; ++idx) {
_messages[idx]._stringIndex = convFile->readUint16LE();
_messages[idx]._count = convFile->readUint16LE();
}
delete convFile;
// **** Section 4: Text line offsets **************************************
convFile = convFileUnpacked.getItemStream(4);
assert(convFile->size() == _textLineCount * 2);
uint16 *textLineOffsets = new uint16[_textLineCount]; // deleted below in section 5
for (uint16 i = 0; i < _textLineCount; i++)
textLineOffsets[i] = convFile->readUint16LE();
delete convFile;
// **** Section 5: Text lines *********************************************
convFile = convFileUnpacked.getItemStream(5);
assert(convFile->size() == _textSize);
Common::String textLine;
_textLines.resize(_textLineCount);
char textLineBuffer[256];
uint16 nextOffset;
for (uint16 i = 0; i < _textLineCount; i++) {
nextOffset = (i != _textLineCount - 1) ? textLineOffsets[i + 1] : convFile->size();
convFile->read(textLineBuffer, nextOffset - textLineOffsets[i]);
_textLines[i] = Common::String(textLineBuffer);
}
delete[] textLineOffsets;
delete convFile;
// **** Section 6: Scripts ************************************************
convFile = convFileUnpacked.getItemStream(6);
assert(convFile->size() == _commandsSize);
for (uint idx = 0; idx < _dialogs.size(); ++idx) {
// Move to the correct position for the dialog's script, and create
// a memory stream to represent the data for just that script
convFile->seek(_dialogs[idx]._scriptOffset);
Common::SeekableReadStream *scriptStream = convFile->readStream(_dialogs[idx]._scriptSize);
// Pass it to the dialog's script set class to parse into commands
_dialogs[idx]._script.load(*scriptStream, _dialogs[idx]._scriptOffset);
delete scriptStream;
}
delete convFile;
inFile.close();
}
/*------------------------------------------------------------------------*/
ConversationConditionals::ConversationConditionals() : _numImports(0) {
_currentNode = -1;
}
void ConversationConditionals::load(const Common::String &filename) {
Common::File inFile;
Common::SeekableReadStream *convFile;
// Open up the file for access
inFile.open(filename);
MadsPack convFileUnpacked(&inFile);
// **** Section 0: Header *************************************************
convFile = convFileUnpacked.getItemStream(0);
_currentNode = convFile->readUint16LE();
int entryFlagsCount = convFile->readUint16LE();
int varsCount = convFile->readUint16LE();
int importsCount = convFile->readUint16LE();
convFile->skip(4);
_messageList1.resize(convFile->readUint16LE());
_messageList2.resize(convFile->readUint16LE());
_messageList3.resize(convFile->readUint16LE());
_messageList4.resize(convFile->readUint16LE());
convFile->skip(20);
for (uint idx = 0; idx < 10; ++idx) {
int v = convFile->readUint16LE();
if (idx < _messageList1.size())
_messageList1[idx] = v;
}
for (uint idx = 0; idx < 10; ++idx) {
int v = convFile->readUint16LE();
if (idx < _messageList2.size())
_messageList2[idx] = v;
}
for (uint idx = 0; idx < 10; ++idx) {
int v = convFile->readUint16LE();
if (idx < _messageList3.size())
_messageList3[idx] = v;
}
for (uint idx = 0; idx < 10; ++idx) {
int v = convFile->readUint16LE();
if (idx < _messageList4.size())
_messageList4[idx] = v;
}
delete convFile;
// **** Section: Imports *************************************************
int streamNum = 1;
_importVariables.resize(importsCount);
if (importsCount > 0) {
convFile = convFileUnpacked.getItemStream(streamNum++);
// Read in the variable indexes that each import value will be stored in
for (int idx = 0; idx < importsCount; ++idx)
_importVariables[idx] = convFile->readUint16LE();
delete convFile;
}
// **** Section: Entry Flags *********************************************
convFile = convFileUnpacked.getItemStream(streamNum++);
assert(convFile->size() == (entryFlagsCount * 2));
_entryFlags.resize(entryFlagsCount);
for (int idx = 0; idx < entryFlagsCount; ++idx)
_entryFlags[idx] = convFile->readUint16LE();
delete convFile;
// **** Section: Variables ***********************************************
convFile = convFileUnpacked.getItemStream(streamNum);
assert(convFile->size() == (varsCount * 6));
_vars.resize(varsCount);
for (int idx = 0; idx < varsCount; ++idx) {
convFile->skip(2); // Loaded values are never pointers, so don't need this
_vars[idx]._isPtr = false;
_vars[idx]._val = convFile->readSint16LE();
convFile->skip(2); // Unused segment selector for pointer values
}
delete convFile;
}
/*------------------------------------------------------------------------*/
void ConversationVar::setValue(int val) {
_isPtr = false;
_valPtr = nullptr;
_val = val;
}
void ConversationVar::setValue(int *val) {
_isPtr = true;
_valPtr = val;
_val = 0;
}
/*------------------------------------------------------------------------*/
void DialogScript::load(Common::SeekableReadStream &s, uint startingOffset) {
clear();
Common::HashMap<uint, uint> instructionOffsets;
// Iterate getting each instruction in turn
while (s.pos() < s.size()) {
// Create a new entry for the next script command
instructionOffsets[startingOffset + s.pos()] = size();
push_back(ScriptEntry());
ScriptEntry &se = (*this)[size() - 1];
// Load the instruction
se.load(s);
}
// Do a final iteration over the loaded instructions to convert
// any GOTO instructions from original offsets to instruction indexes
for (uint idx = 0; idx < size(); ++idx) {
ScriptEntry &se = (*this)[idx];
if (se._command == CMD_GOTO)
se._index = instructionOffsets[se._index];
}
}
/*------------------------------------------------------------------------*/
void ScriptEntry::load(Common::SeekableReadStream &s) {
// Get the command byte
_command = (DialogCommand)s.readByte();
if (!(_command == CMD_DIALOG_END || (_command >= CMD_END && _command <= CMD_ASSIGN))) {
warning("unknown opcode - %d", _command);
s.seek(0, SEEK_END);
return;
}
// Get in the conditional values
int numConditionals = 1;
if (_command == CMD_NODE)
numConditionals = 3;
else if (_command == CMD_ASSIGN)
numConditionals = 2;
else if (_command == CMD_ERROR)
numConditionals = 0;
for (int idx = 0; idx < numConditionals; ++idx)
_conditionals[idx].load(s);
// Get further parameters
switch (_command) {
case CMD_1:
case CMD_HIDE:
case CMD_UNHIDE: {
// Read in the list of entries whose flags are to be updated
int count = s.readByte();
for (int idx = 0; idx < count; ++idx)
_entries.push_back(s.readSint16LE());
break;
}
case CMD_MESSAGE1:
case CMD_MESSAGE2: {
int count2 = s.readByte();
int count1 = s.readByte();
_entries2.resize(count2);
_entries.resize(count1);
for (uint idx = 0; idx < _entries2.size(); ++idx) {
int v = s.readByte();
if (idx < 10)
_entries2[idx]._size = v;
}
for (uint idx = 0; idx < _entries2.size(); ++idx) {
int v = s.readUint16LE();
if (idx < 10)
_entries2[idx]._v2 = v;
}
for (uint idx = 0; idx < _entries.size(); ++idx) {
int v = s.readUint16LE();
if (idx < 10)
_entries[idx] = v;
}
break;
}
case CMD_ERROR:
case CMD_NODE:
// These opcodes have no extra parameters
break;
case CMD_GOTO:
case CMD_ASSIGN:
// Goto has a single extra parameter for the destination
// Assign has a single extra parameter for the variable index
// that the value resulting from the condition will be set to
_index = s.readUint16LE();
break;
default:
break;
}
}
/*------------------------------------------------------------------------*/
Common::Array<ConversationVar> *ScriptEntry::Conditional::_vars = nullptr;
void ScriptEntry::Conditional::load(Common::SeekableReadStream &s) {
_operation = (ConditionalOperation)s.readUint16LE();
if (_operation == CONDOP_ABORT) {
_param1._isVariable = false;
_param1._val = 0;
} else {
_param1._isVariable = s.readByte() != 0;
_param1._val = s.readSint16LE();
}
if (_operation == CONDOP_ABORT || _operation == CONDOP_VALUE) {
_param2._isVariable = false;
_param2._val = 0;
} else {
_param2._isVariable = s.readByte() != 0;
_param2._val = s.readSint16LE();
}
}
int ScriptEntry::Conditional::evaluate() const {
if (_operation == CONDOP_NONE)
return -1;
int param1 = get(1);
if (_operation == CONDOP_VALUE)
return param1;
int param2 = get(2);
switch (_operation) {
case CONDOP_ADD:
return param1 + param2;
case CONDOP_SUBTRACT:
return param1 - param2;
case CONDOP_MULTIPLY:
return param1 * param2;
case CONDOP_DIVIDE:
return param1 / param2;
case CONDOP_MODULUS:
return param1 % param2;
case CONDOP_LTEQ:
return (param1 <= param2) ? 1 : 0;
case CONDOP_GTEQ:
return (param1 < param2) ? 1 : 0;
case CONDOP_LT:
return (param1 < param2) ? 1 : 0;
case CONDOP_GT:
return (param1 > param2) ? 1 : 0;
case CONDOP_NEQ:
return (param1 != param2) ? 1 : 0;
case CONDOP_EQ:
return (param1 == param2) ? 1 : 0;
case CONDOP_AND:
return (param1 || param2) ? 1 : 0;
case CONDOP_OR:
return (param1 && param2) ? 1 : 0;
default:
error("Unknown conditional operation");
}
}
int ScriptEntry::Conditional::get(int paramNum) const {
const CondtionalParamEntry &p = (paramNum == 1) ? _param1 : _param2;
return p._isVariable ? *(*_vars)[p._val].getValue() : p._val;
}
/*------------------------------------------------------------------------*/
} // End of namespace MADS