mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-23 19:16:21 +00:00
6fc4d50443
This allows the conversation in Phantom scene 101 to close, and enables player actions for the scene
349 lines
10 KiB
C++
349 lines
10 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 {
|
|
|
|
#define MAX_SPEAKERS 5
|
|
|
|
enum DialogCommands {
|
|
cmdNodeEnd = 0,
|
|
//
|
|
cmdHide = 2,
|
|
cmdUnhide = 3,
|
|
cmdMessage = 4,
|
|
//
|
|
//
|
|
cmdGoto = 7,
|
|
//
|
|
cmdAssign = 9,
|
|
cmdDialogEnd = 255
|
|
};
|
|
|
|
struct ConvDialog {
|
|
int16 textLineIndex; // 0-based
|
|
int16 speechIndex; // 1-based
|
|
uint16 nodeOffset; // offset in section 6
|
|
uint16 nodeSize; // size in section 6
|
|
};
|
|
|
|
struct ConvNode {
|
|
uint16 index;
|
|
uint16 dialogCount;
|
|
int16 unk1;
|
|
int16 unk2;
|
|
int16 unk3;
|
|
|
|
Common::Array<ConvDialog> dialogs;
|
|
};
|
|
|
|
struct ConvData {
|
|
uint16 nodeCount; // conversation nodes, each one containing several dialog options and messages
|
|
uint16 dialogCount; // messages (non-selectable) + texts (selectable)
|
|
uint16 messageCount; // messages (non-selectable)
|
|
uint16 textLineCount;
|
|
uint16 unk2;
|
|
uint16 importCount;
|
|
uint16 speakerCount;
|
|
Common::String portraits[MAX_SPEAKERS];
|
|
bool speakerExists[MAX_SPEAKERS];
|
|
Common::String speechFile;
|
|
Common::Array<Common::String> textLines;
|
|
Common::Array<ConvNode> convNodes;
|
|
};
|
|
|
|
GameConversation::GameConversation(MADSEngine *vm)
|
|
: _vm(vm) {
|
|
_running = _restoreRunning = 0;
|
|
}
|
|
|
|
GameConversation::~GameConversation() {
|
|
}
|
|
|
|
void GameConversation::get(int id) {
|
|
Common::File inFile;
|
|
Common::String fileName = Common::String::format("CONV%03d.CNV", id);
|
|
// TODO: Also handle the .CND file
|
|
|
|
inFile.open(fileName);
|
|
MadsPack convFileUnpacked(&inFile);
|
|
Common::SeekableReadStream *convFile = convFileUnpacked.getItemStream(0);
|
|
|
|
char buffer[16];
|
|
|
|
ConvData conv;
|
|
|
|
// **** Section 0: Header *************************************************
|
|
conv.nodeCount = convFile->readUint16LE();
|
|
conv.dialogCount = convFile->readUint16LE();
|
|
conv.messageCount = convFile->readUint16LE();
|
|
conv.textLineCount = convFile->readUint16LE();
|
|
conv.unk2 = convFile->readUint16LE();
|
|
conv.importCount = convFile->readUint16LE();
|
|
conv.speakerCount = convFile->readUint16LE();
|
|
|
|
//debug("Conv %d has %d nodes, %d dialogs, %d messages, %d text lines, %d unk2, %d imports and %d speakers",
|
|
// id, conv.nodeCount, conv.dialogCount, conv.messageCount, conv.textLineCount, conv.unk2, conv.importCount, conv.speakerCount);
|
|
|
|
for (uint16 i = 0; i < MAX_SPEAKERS; i++) {
|
|
convFile->read(buffer, 16);
|
|
conv.portraits[i] = buffer;
|
|
//debug("Speaker %d, portrait %s", i, conv.portraits[i].c_str());
|
|
}
|
|
|
|
for (uint16 i = 0; i < MAX_SPEAKERS; i++) {
|
|
conv.speakerExists[i] = convFile->readUint16LE();
|
|
//debug("Speaker %d exists: %d", i, conv.speakerExists[i]);
|
|
}
|
|
|
|
convFile->read(buffer, 14);
|
|
conv.speechFile = Common::String(buffer);
|
|
//debug("Speech file %s", conv.speechFile.c_str());
|
|
|
|
uint16 textLength = convFile->readUint16LE(); // Total text length in section 5
|
|
convFile->skip(2); // TODO: unknown
|
|
uint16 commandLength = convFile->readUint16LE(); // Total length of commands in section 6
|
|
//debug("Node entry commands length: %d", commandLength);
|
|
|
|
/*debug("Section 0 unknown bytes");
|
|
byte *tmp0 = new byte[26];
|
|
convFile->read(tmp0, 26);
|
|
Common::hexdump(tmp0, 26);
|
|
delete[] tmp0;*/
|
|
|
|
// **** Section 1: Nodes **************************************************
|
|
convFile = convFileUnpacked.getItemStream(1);
|
|
|
|
for (uint16 i = 0; i < conv.nodeCount; i++) {
|
|
ConvNode node;
|
|
node.index = convFile->readUint16LE();
|
|
node.dialogCount = convFile->readUint16LE();
|
|
node.unk1 = convFile->readSint16LE(); // TODO
|
|
node.unk2 = convFile->readSint16LE(); // TODO
|
|
node.unk3 = convFile->readSint16LE(); // TODO
|
|
conv.convNodes.push_back(node);
|
|
//debug("Node %d, index %d, entries %d - %d, %d, %d", i, node.index, node.dialogCount, node.unk1, node.unk2, node.unk3);
|
|
}
|
|
|
|
// **** Section 2: Dialogs ************************************************
|
|
convFile = convFileUnpacked.getItemStream(2);
|
|
assert(convFile->size() == conv.dialogCount * 8);
|
|
|
|
for (uint16 i = 0; i < conv.nodeCount; i++) {
|
|
uint16 dialogCount = conv.convNodes[i].dialogCount;
|
|
|
|
for (uint16 j = 0; j < dialogCount; j++) {
|
|
ConvDialog dialog;
|
|
dialog.textLineIndex = convFile->readSint16LE();
|
|
dialog.speechIndex = convFile->readSint16LE();
|
|
dialog.nodeOffset = convFile->readUint16LE();
|
|
dialog.nodeSize = convFile->readUint16LE();
|
|
conv.convNodes[i].dialogs.push_back(dialog);
|
|
//debug("Node %d, dialog %d: text line %d, speech index %d, node offset %d, node size %d", j, i, dialog.textLineIndex, dialog.speechIndex, dialog.nodeOffset, dialog.nodeSize);
|
|
}
|
|
}
|
|
|
|
// **** Section 3: ???? ***************************************************
|
|
/*debug("Section 3");
|
|
convFile = convFileUnpacked.getItemStream(3);
|
|
byte *tmp1 = new byte[convFile->size()];
|
|
convFile->read(tmp1, convFile->size());
|
|
Common::hexdump(tmp1, convFile->size());
|
|
delete[] tmp1;*/
|
|
// TODO
|
|
|
|
// **** Section 4: Text line offsets **************************************
|
|
convFile = convFileUnpacked.getItemStream(4);
|
|
assert(convFile->size() == conv.textLineCount * 2);
|
|
|
|
uint16 *textLineOffsets = new uint16[conv.textLineCount]; // deleted below in section 5
|
|
for (uint16 i = 0; i < conv.textLineCount; i++)
|
|
textLineOffsets[i] = convFile->readUint16LE();
|
|
|
|
// **** Section 5: Text lines *********************************************
|
|
convFile = convFileUnpacked.getItemStream(5);
|
|
assert(convFile->size() == textLength);
|
|
|
|
Common::String textLine;
|
|
conv.textLines.resize(conv.textLineCount);
|
|
char textLineBuffer[256];
|
|
uint16 nextOffset;
|
|
for (uint16 i = 0; i < conv.textLineCount; i++) {
|
|
nextOffset = (i != conv.textLineCount - 1) ? textLineOffsets[i + 1] : convFile->size();
|
|
convFile->read(textLineBuffer, nextOffset - textLineOffsets[i]);
|
|
conv.textLines[i] = Common::String(textLineBuffer);
|
|
//debug("Text line %d: %s", i, conv.textLines[i].c_str());
|
|
}
|
|
|
|
delete[] textLineOffsets;
|
|
|
|
// **** Section 6: Node entry commands ************************************
|
|
convFile = convFileUnpacked.getItemStream(6);
|
|
assert(convFile->size() == commandLength);
|
|
|
|
for (uint16 i = 0; i < conv.nodeCount; i++) {
|
|
uint16 dialogCount = conv.convNodes[i].dialogCount;
|
|
|
|
for (uint16 j = 0; j < dialogCount; j++) {
|
|
//ConvDialog dialog = conv.convNodes[i].dialogs[j];
|
|
byte command;
|
|
uint16 chk;
|
|
|
|
do {
|
|
command = convFile->readByte();
|
|
chk = convFile->readUint16BE();
|
|
if (chk != 0xFF00 && chk != 0x0000) {
|
|
warning("Error while reading conversation node entries - bailing out");
|
|
break;
|
|
}
|
|
|
|
switch (command) {
|
|
case cmdNodeEnd:
|
|
//debug("Node end");
|
|
break;
|
|
case cmdDialogEnd:
|
|
//debug("Dialog end");
|
|
break;
|
|
case cmdHide: {
|
|
byte count = convFile->readByte();
|
|
for (byte k = 0; k < count; k++) {
|
|
/*uint16 nodeRef = */convFile->readUint16LE();
|
|
//debug("Hide node %d", nodeRef);
|
|
}
|
|
|
|
}
|
|
break;
|
|
case cmdUnhide: {
|
|
byte count = convFile->readByte();
|
|
for (byte k = 0; k < count; k++) {
|
|
/*uint16 nodeRef = */convFile->readUint16LE();
|
|
//debug("Unhide node %d", nodeRef);
|
|
}
|
|
|
|
}
|
|
break;
|
|
case cmdMessage:
|
|
//debug("Message");
|
|
convFile->skip(7); // TODO
|
|
break;
|
|
case cmdGoto: {
|
|
convFile->skip(3); // unused?
|
|
/*byte nodeRef = */convFile->readByte();
|
|
//debug("Goto %d", nodeRef);
|
|
}
|
|
break;
|
|
case cmdAssign: {
|
|
convFile->skip(3); // unused?
|
|
/*uint16 value = */convFile->readUint16LE();
|
|
/*uint16 variable = */convFile->readUint16LE();
|
|
//debug("Variable %d = %d", variable, value);
|
|
}
|
|
break;
|
|
default:
|
|
error("Unknown conversation command %d", command);
|
|
break;
|
|
}
|
|
} while (command != cmdNodeEnd && command != cmdDialogEnd);
|
|
}
|
|
}
|
|
|
|
inFile.close();
|
|
|
|
/*
|
|
// DEBUG: Show the very first message, and play the very first speech
|
|
_vm->_audio->setSoundGroup(conv.speechFile);
|
|
uint16 firstText = 0, firstSpeech = 1;
|
|
|
|
for (uint16 i = 0; i < conv.convNodes.size(); i++) {
|
|
for (uint16 k = 0; k < conv.convNodes[i].dialogs.size(); k++) {
|
|
if (conv.convNodes[i].dialogs[k].textLineIndex >= 0) {
|
|
firstText = conv.convNodes[i].dialogs[k].textLineIndex;
|
|
firstSpeech = conv.convNodes[i].dialogs[k].speechIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_vm->_audio->playSound(firstSpeech - 1);
|
|
|
|
TextDialog *dialog = new TextDialog(_vm, FONT_INTERFACE, Common::Point(0, 100), 30);
|
|
dialog->addLine(conv.textLines[firstText]);
|
|
dialog->show();
|
|
delete dialog;
|
|
*/
|
|
|
|
warning("TODO GameConversation::get");
|
|
}
|
|
|
|
void GameConversation::run(int id) {
|
|
warning("TODO GameConversation::run");
|
|
}
|
|
|
|
void GameConversation::stop() {
|
|
warning("TODO GameConversation::stop");
|
|
}
|
|
|
|
void GameConversation::exportPointer(int *val) {
|
|
warning("TODO GameConversation::exportPointer");
|
|
}
|
|
|
|
void GameConversation::exportValue(int val) {
|
|
warning("TODO GameConversation::exportValue");
|
|
}
|
|
|
|
void GameConversation::setHeroTrigger(int val) {
|
|
_vm->_game->_trigger = val; // HACK
|
|
_running = -1; // HACK
|
|
warning("TODO: GameConversation::setHeroTrigger");
|
|
}
|
|
|
|
void GameConversation::setInterlocutorTrigger(int val) {
|
|
warning("TODO: GameConversation::setInterlocutorTrigger");
|
|
}
|
|
|
|
int* GameConversation::getVariable(int idx) {
|
|
warning("TODO: GameConversation::getVariable");
|
|
return nullptr;
|
|
}
|
|
|
|
void GameConversation::hold() {
|
|
warning("TODO: GameConversation::hold");
|
|
}
|
|
|
|
void GameConversation::release() {
|
|
warning("TODO: GameConversation::release");
|
|
}
|
|
|
|
void GameConversation::reset(int id) {
|
|
warning("TODO: GameConversation::reset");
|
|
}
|
|
|
|
void GameConversation::abortConv() {
|
|
warning("TODO: GameConversation::abort");
|
|
}
|
|
} // End of namespace MADS
|