scummvm/engines/petka/big_dialogue.cpp

468 lines
12 KiB
C++
Raw Normal View History

2019-07-12 12:53:04 +03:00
/* 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 "common/debug.h"
2019-07-12 12:53:04 +03:00
#include "common/stream.h"
#include "petka/base.h"
2019-07-12 12:53:04 +03:00
#include "petka/big_dialogue.h"
2019-07-21 23:38:07 +03:00
#include "petka/interfaces/main.h"
#include "petka/interfaces/dialog_interface.h"
#include "petka/objects/heroes.h"
#include "petka/q_system.h"
2019-07-12 12:53:04 +03:00
#include "petka/petka.h"
namespace Petka {
BigDialogue::BigDialogue() {
2020-05-14 16:17:24 +03:00
_currOp = nullptr;
_startOpIndex = 0;
2019-07-12 12:53:04 +03:00
Common::ScopedPtr<Common::SeekableReadStream> file(g_vm->openFile("dialogue.fix", true));
if (!file)
return;
_objDialogs.resize(file->readUint32LE());
for (uint i = 0; i < _objDialogs.size(); ++i) {
_objDialogs[i].objId = file->readUint32LE();
2020-05-14 16:17:24 +03:00
_objDialogs[i].handlers.resize(file->readUint32LE());
2019-07-12 12:53:04 +03:00
file->skip(4); // pointer
}
for (uint i = 0; i < _objDialogs.size(); ++i) {
2020-05-14 16:17:24 +03:00
for (uint j = 0; j < _objDialogs[i].handlers.size(); ++j) {
_objDialogs[i].handlers[j].opcode = file->readUint16LE();
_objDialogs[i].handlers[j].objId = file->readUint16LE();
_objDialogs[i].handlers[j].dialogs.resize(file->readUint32LE());
_objDialogs[i].handlers[j].startDialogIndex = file->readUint32LE();
2019-07-12 12:53:04 +03:00
file->skip(4); // pointer
}
2020-05-14 16:17:24 +03:00
for (uint j = 0; j < _objDialogs[i].handlers.size(); ++j) {
for (uint z = 0; z < _objDialogs[i].handlers[j].dialogs.size(); ++z) {
_objDialogs[i].handlers[j].dialogs[z].startOpIndex = file->readUint32LE();
file->skip(4 + 4); // opsCount + pointer
2019-07-12 12:53:04 +03:00
}
}
}
2020-05-14 16:17:24 +03:00
load(file.get());
2019-07-12 12:53:04 +03:00
}
void BigDialogue::loadSpeechesInfo() {
if (!_speeches.empty())
return;
Common::ScopedPtr<Common::SeekableReadStream> file(g_vm->openFile("dialogue.lod", true));
if (!file)
return;
_speeches.resize(file->readUint32LE());
for (uint i = 0; i < _speeches.size(); ++i) {
_speeches[i].speakerId = file->readUint32LE();
file->read(_speeches[i].soundName, sizeof(_speeches[i].soundName));
file->skip(4);
2019-07-12 12:53:04 +03:00
}
char *str = new char[file->size() - file->pos()];
char *curr = str;
file->read(str, file->size() - file->pos());
for (uint i = 0; i < _speeches.size(); ++i) {
2019-08-25 21:58:00 +03:00
_speeches[i].text = Common::convertToU32String(curr, Common::kWindows1251);
2019-07-12 12:53:04 +03:00
curr += strlen(curr) + 1;
}
delete[] str;
}
2020-05-14 16:17:24 +03:00
const Common::U32String *BigDialogue::getSpeechInfo(int *talkerId, const char **soundName, int choice) {
if (!_currOp)
2019-07-12 12:53:04 +03:00
return nullptr;
2020-05-14 16:17:24 +03:00
switch (_currOp->type) {
case kOperationMenu: {
Operation *menuOp = _currOp;
2020-05-14 16:17:24 +03:00
uint bit = 1;
if (_currOp->menu.bits <= choice || choice < 0) {
break;
}
while (true) {
2020-05-14 16:17:24 +03:00
_currOp += 1;
if (choice == 0 && (bit & menuOp->menu.bitField))
break;
2020-05-14 16:17:24 +03:00
if (_currOp->type == kOperationBreak) {
if (bit & menuOp->menu.bitField)
2020-05-14 16:17:24 +03:00
choice--;
bit *= 2;
}
}
2020-05-14 16:17:24 +03:00
if (_currOp->type != kOperationPlay)
next();
if (_currOp->type != kOperationPlay) {
_currOp = menuOp;
break;
}
2020-05-14 16:17:24 +03:00
uint index = _currOp->play.messageIndex;
_currOp = menuOp;
*soundName = _speeches[index].soundName;
*talkerId = _speeches[index].speakerId;
return &_speeches[index].text;
}
2020-05-14 16:17:24 +03:00
case kOperationCircle:
_currOp += 1;
for (uint i = 0; i < _currOp->circle.count; ++i) {
while (_currOp->type != kOperationBreak)
_currOp += 1;
_currOp += 1;
2019-07-12 12:53:04 +03:00
}
2020-05-14 16:17:24 +03:00
assert(_currOp->type == kOperationPlay);
2019-07-12 12:53:04 +03:00
// fall through
2020-05-14 16:17:24 +03:00
case kOperationPlay:
*soundName = _speeches[_currOp->play.messageIndex].soundName;
*talkerId = _speeches[_currOp->play.messageIndex].speakerId;
return &_speeches[_currOp->play.messageIndex].text;
2019-07-12 12:53:04 +03:00
default:
break;
}
return nullptr;
}
2020-05-14 16:17:24 +03:00
const DialogHandler *BigDialogue::findHandler(uint objId, uint opcode, bool *res) const {
if (opcode == kEnd || opcode == kHalf) {
return nullptr;
}
if (res) {
2020-05-14 16:17:24 +03:00
*res = false;
}
for (uint i = 0; i < _objDialogs.size(); ++i) {
if (_objDialogs[i].objId == objId) {
2020-05-14 16:17:24 +03:00
for (uint j = 0; j < _objDialogs[i].handlers.size(); ++j) {
if (_objDialogs[i].handlers[j].opcode == opcode) {
return &_objDialogs[i].handlers[j];
}
}
if (opcode != kObjectUse) {
continue;
}
2020-05-14 16:17:24 +03:00
for (uint j = 0; j < _objDialogs[i].handlers.size(); ++j) {
if (_objDialogs[i].handlers[j].opcode == 0xFFFE) {
if (res)
2020-05-14 16:17:24 +03:00
*res = true;
return &_objDialogs[i].handlers[j];
}
}
}
}
for (uint i = 0; i < _objDialogs.size(); ++i) {
if (_objDialogs[i].objId != 0xFFFE)
continue;
2020-05-14 16:17:24 +03:00
for (uint j = 0; j < _objDialogs[i].handlers.size(); ++j) {
if (_objDialogs[i].handlers[j].opcode == opcode) {
if (res)
2020-05-14 16:17:24 +03:00
*res = true;
return &_objDialogs[i].handlers[j];
}
}
}
2019-07-12 12:53:04 +03:00
return nullptr;
}
2020-05-14 16:17:24 +03:00
void BigDialogue::setHandler(uint objId, uint opcode, int index) {
2019-07-12 12:53:04 +03:00
loadSpeechesInfo();
2020-05-14 16:17:24 +03:00
const DialogHandler *h = findHandler(objId, opcode, nullptr);
if (h) {
_startOpIndex = h->dialogs[h->startDialogIndex].startOpIndex;
_currOp = &_ops[_startOpIndex];
2019-07-12 12:53:04 +03:00
}
}
uint BigDialogue::opcode() {
2020-05-14 16:17:24 +03:00
while (_currOp) {
switch (_currOp->type) {
case kOperationMenu:
if (choicesCount() > 1)
return kOpcodeMenu;
next(0);
break;
case kOperationReturn:
return kOpcodeEnd;
case kOperationPlay:
case kOperationCircle:
return kOpcodePlay;
2020-05-17 13:46:44 +03:00
case kOperationUserMessage:
return kOpcodeUserMessage;
2020-05-14 16:17:24 +03:00
default:
next();
break;
}
2020-05-14 16:17:24 +03:00
}
return kOpcodeEnd;
}
void BigDialogue::load(Common::ReadStream *s) {
uint32 opsCount = s->readUint32LE();
_ops.resize(opsCount);
for (uint i = 0; i < opsCount; ++i) {
uint op = s->readUint32LE();
_ops[i].type = (byte)(op >> 24);
switch (_ops[i].type) {
case kOperationBreak:
break;
case kOperationMenu:
_ops[i].menu.bits = (byte)op;
_ops[i].menu.bitField = (uint16)(op >> 8);
break;
case kOperationGoTo:
_ops[i].goTo.opIndex = (uint16)(op);
break;
2020-05-17 13:46:44 +03:00
case kOperationDisableMenuItem:
_ops[i].disableMenuItem.opIndex = (uint16)op;
_ops[i].disableMenuItem.bit = (byte)(op >> 16);
2020-05-14 16:17:24 +03:00
break;
2020-05-17 13:46:44 +03:00
case kOperationEnableMenuItem:
_ops[i].enableMenuItem.opIndex = (uint16)op;
_ops[i].enableMenuItem.bit = (byte)(op >> 16);
2020-05-14 16:17:24 +03:00
break;
case kOperationReturn:
break;
case kOperationPlay:
_ops[i].play.messageIndex = (uint16)op;
break;
case kOperationCircle:
_ops[i].circle.count = (uint16)op;
_ops[i].circle.curr = (byte)(op >> 16);
break;
2020-05-17 13:46:44 +03:00
case kOperationUserMessage:
_ops[i].userMsg.arg = (uint16)op;
2020-05-14 16:17:24 +03:00
break;
default:
break;
}
}
}
2020-05-14 16:17:24 +03:00
void BigDialogue::save(Common::WriteStream *s) {
s->writeUint32LE(_ops.size());
for (uint i = 0; i < _ops.size(); ++i) {
switch (_ops[i].type) {
case kOperationBreak:
s->writeUint32LE(MKTAG(0, 0, 0, kOperationBreak));
break;
case kOperationMenu:
s->writeByte(_ops[i].menu.bits);
s->writeUint16LE(_ops[i].menu.bitField);
s->writeByte(kOperationMenu);
break;
case kOperationGoTo:
s->writeUint16LE(_ops[i].goTo.opIndex);
s->writeUint16LE(MKTAG16(0, kOperationGoTo));
break;
2020-05-17 13:46:44 +03:00
case kOperationDisableMenuItem:
s->writeUint16LE(_ops[i].disableMenuItem.opIndex);
s->writeUint16LE(MKTAG16(_ops[i].disableMenuItem.bit, kOperationDisableMenuItem));
2020-05-14 16:17:24 +03:00
break;
2020-05-17 13:46:44 +03:00
case kOperationEnableMenuItem:
s->writeUint16LE(_ops[i].enableMenuItem.opIndex);
s->writeUint16LE(MKTAG16(_ops[i].enableMenuItem.bit, kOperationEnableMenuItem));
2020-05-14 16:17:24 +03:00
break;
case kOperationReturn:
s->writeUint32LE(MKTAG(0, 0, 0, kOperationReturn));
break;
case kOperationPlay:
s->writeUint16LE(_ops[i].play.messageIndex);
s->writeUint16LE(MKTAG16(0, kOperationPlay));
break;
case kOperationCircle:
s->writeUint16LE(_ops[i].circle.count);
s->writeUint16LE(MKTAG16(_ops[i].circle.curr, kOperationPlay));
break;
2020-05-17 13:46:44 +03:00
case kOperationUserMessage:
s->writeUint16LE(_ops[i].userMsg.arg);
s->writeUint16LE(MKTAG16(0, kOperationUserMessage));
2020-05-14 16:17:24 +03:00
break;
default:
break;
}
}
}
void BigDialogue::next(int choice) {
bool processed = true;
if (!_currOp)
return;
2020-05-14 16:17:24 +03:00
if (choice != -1 && _currOp->type != kOperationMenu) {
choice = -1;
}
2020-05-14 16:17:24 +03:00
while (true) {
2020-05-14 16:17:24 +03:00
switch (_currOp->type) {
case kOperationBreak:
2020-05-16 21:30:03 +03:00
while (_currOp->type != kOperationMenu || _currOp->type != kOperationCircle) {
2020-05-14 16:17:24 +03:00
_currOp--;
2019-07-24 09:42:59 +03:00
}
2020-05-14 16:17:24 +03:00
next(choice);
return;
case kOperationMenu: {
if (!processed)
return;
if (choice == -1)
choice = 0;
if (_currOp->menu.bits <= choice)
choice = _currOp->menu.bits - 1;
uint bits = _currOp->menu.bits;
uint bit = 1;
uint i = 0;
while (bits) {
if (_currOp->type == kOperationBreak) {
bits--;
if (!(bit & _currOp->menu.bitField) && i <= (uint)choice)
choice++;
bit *= 2;
i++;
2019-07-24 09:42:59 +03:00
}
2020-05-14 16:17:24 +03:00
_currOp += 1;
2019-07-24 09:42:59 +03:00
}
2020-05-14 16:17:24 +03:00
_currOp += choice;
break;
2019-07-24 09:42:59 +03:00
}
2020-05-14 16:17:24 +03:00
case kOperationGoTo: {
_currOp = &_ops[_currOp->goTo.opIndex];
processed = false;
break;
2020-05-14 16:17:24 +03:00
}
2020-05-17 13:46:44 +03:00
case kOperationDisableMenuItem:
_ops[_currOp->disableMenuItem.opIndex].menu.bitField &= ~(1 << _currOp->disableMenuItem.bit); // disable menu item
checkMenu(_currOp->disableMenuItem.opIndex);
2020-05-14 16:17:24 +03:00
_currOp += 1;
processed = false;
break;
2020-05-17 13:46:44 +03:00
case kOperationEnableMenuItem:
_ops[_currOp->enableMenuItem.opIndex].menu.bitField |= (1 << _currOp->enableMenuItem.bit);
2020-05-14 16:17:24 +03:00
_currOp += 1;
processed = false;
break;
2020-05-14 16:17:24 +03:00
case kOperationReturn:
return;
2020-05-14 16:17:24 +03:00
case kOperationPlay:
if (!processed)
return;
2020-05-14 16:17:24 +03:00
_currOp += 1;
processed = false;
break;
2020-05-14 16:17:24 +03:00
case kOperationCircle:
if (!processed)
2019-07-24 07:43:27 +03:00
return;
2020-05-14 16:17:24 +03:00
_currOp->circle.curr = (byte)((_currOp->circle.curr + 1) % _currOp->circle.count);
for (uint i = 0; i < _currOp->circle.count; ++i) {
while (_currOp->type != kOperationBreak)
_currOp += 1;
_currOp += 1;
2019-07-24 07:43:27 +03:00
}
2020-05-14 16:17:24 +03:00
processed = false;
break;
2020-05-17 13:46:44 +03:00
case kOperationUserMessage:
2020-05-14 16:17:24 +03:00
if (processed)
_currOp += 1;
else {
2019-07-21 23:38:07 +03:00
g_vm->getQSystem()->_mainInterface->_dialog.sendMsg(kSaid);
2020-05-17 13:46:44 +03:00
g_vm->getQSystem()->_mainInterface->_dialog._isUserMsg = 1;
2019-07-21 23:38:07 +03:00
g_vm->getQSystem()->_mainInterface->_dialog.restoreCursorState();
2020-05-17 13:46:44 +03:00
g_vm->getQSystem()->addMessage(g_vm->getQSystem()->_chapayev->_id, kUserMsg, _currOp->userMsg.arg);
}
return;
default:
2020-05-14 16:17:24 +03:00
_currOp += 1;
processed = false;
break;
}
}
2020-05-14 16:17:24 +03:00
}
2020-05-14 16:17:24 +03:00
uint BigDialogue::choicesCount() {
if (!_currOp || _currOp->type != kOperationMenu)
return 0;
uint count = 0;
uint bit = 1;
for (uint i = 0; i < _currOp->menu.bits; ++i) {
if (_currOp->menu.bitField & bit) {
count++;
}
bit <<= 1;
}
return count;
}
2020-05-14 16:17:24 +03:00
bool BigDialogue::findOperation(uint index, uint opType, uint *resIndex) {
while (_ops[index].type != opType) {
switch(_ops[index].type) {
case kOperationGoTo:
if (index >= _ops[index].goTo.opIndex)
return false;
index = _ops[index].goTo.opIndex;
break;
case kOperationReturn:
return false;
default:
index++;
break;
}
}
*resIndex = index;
return true;
}
2020-05-14 16:17:24 +03:00
bool BigDialogue::checkMenu(uint menuIndex) {
if (_ops[menuIndex].type != kOperationMenu && !findOperation(menuIndex, kOperationMenu, &menuIndex)) {
return true;
}
uint count = 0;
uint bit = 1;
uint opIndex = menuIndex + 1;
for (uint i = 0; i < _ops[menuIndex].menu.bits; ++i) {
if (_ops[menuIndex].menu.bitField & bit) {
count++;
}
findOperation(opIndex, kOperationBreak, &opIndex);
opIndex++;
bit <<= 1;
}
if (!count)
return false;
bit = 1;
for (uint i = 0; i < _ops[menuIndex].menu.bits; ++i) {
uint subMenuIndex;
if ((_ops[menuIndex].menu.bitField & bit) && findOperation(_ops[opIndex + i].goTo.opIndex, kOperationMenu, &subMenuIndex) && !checkMenu(subMenuIndex)) {
_ops[menuIndex].menu.bitField &= ~bit;
count--;
if (count < 1)
return false;
}
bit <<= 1;
}
return true;
}
} // End of namespace Petka