NANCY: Implement even terser Conversation records

Implemented the ConversationSoundTerse and
ConversationCelTerse action records, which are even
shorter variants of the corresponding Conversation types.
Made changes to the base ConversationSound to reduce
code duplication.
This commit is contained in:
Kaloyan Chehlarski 2024-02-09 23:32:48 +01:00
parent a04bb51bf5
commit 5683e5f6e6
3 changed files with 167 additions and 85 deletions

View File

@ -174,7 +174,13 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new Autotext();
}
case 62:
return new MapCallHotMultiframe();
if (g_nancy->getGameType() <= kGameTypeNancy7) {
return new MapCallHotMultiframe(); // TVD/nancy1 only
} else {
return new ConversationCelTerse(); // nancy8 and up
}
case 63:
return new ConversationSoundTerse();
case 65:
return new TableIndexOverlay();
case 66:

View File

@ -39,8 +39,13 @@ namespace Nancy {
namespace Action {
ConversationSound::ConversationSound() :
RenderActionRecord(8),
_noResponse(g_nancy->getGameType() <= kGameTypeNancy2 ? 10 : 20) {}
RenderActionRecord(8),
_noResponse(g_nancy->getGameType() <= kGameTypeNancy2 ? 10 : 20),
_hasDrawnTextbox(false),
_pickedResponse(-1) {
_conditionalResponseCharacterID = _noResponse;
_goodbyeResponseCharacterID = _noResponse;
}
ConversationSound::~ConversationSound() {
if (NancySceneState.getActiveConversation() == this) {
@ -113,6 +118,83 @@ void ConversationSound::readData(Common::SeekableReadStream &stream) {
}
}
void ConversationSound::readTerseData(Common::SeekableReadStream &stream) {
readFilename(stream, _sound.name);
_sound.volume = stream.readUint16LE();
_sound.channelID = 12; // hardcoded
_sound.numLoops = 1;
_responseGenericSound.volume = _sound.volume;
_responseGenericSound.numLoops = 1;
_responseGenericSound.channelID = 13; // hardcoded
readTerseCaptionText(stream);
_conditionalResponseCharacterID = stream.readByte();
_goodbyeResponseCharacterID = stream.readByte();
_defaultNextScene = stream.readByte();
_sceneChange.sceneID = stream.readUint16LE();
_sceneChange.continueSceneSound = kContinueSceneSound;
uint16 numResponses = stream.readUint16LE();
_responses.resize(numResponses);
for (uint i = 0; i < numResponses; ++i) {
ResponseStruct &response = _responses[i];
response.conditionFlags.read(stream);
readTerseResponseText(stream, response);
readFilename(stream, response.soundName);
response.sceneChange.sceneID = stream.readUint16LE();
response.sceneChange.continueSceneSound = kContinueSceneSound;
}
// No scene branches
uint16 numFlagsStructs = stream.readUint16LE();
_flagsStructs.resize(numFlagsStructs);
for (uint i = 0; i < numFlagsStructs; ++i) {
FlagsStruct &flagsStruct = _flagsStructs[i];
flagsStruct.conditions.read(stream);
flagsStruct.flagToSet.type = stream.readByte();
flagsStruct.flagToSet.flag.label = stream.readSint16LE();
flagsStruct.flagToSet.flag.flag = stream.readByte();
}
}
void ConversationSound::readCaptionText(Common::SeekableReadStream &stream) {
char *rawText = new char[1500];
stream.read(rawText, 1500);
assembleTextLine(rawText, _text, 1500);
delete[] rawText;
}
void ConversationSound::readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
char *rawText = new char[400];
stream.read(rawText, 400);
assembleTextLine(rawText, response.text, 400);
delete[] rawText;
}
void ConversationSound::readTerseCaptionText(Common::SeekableReadStream &stream) {
Common::String key;
readFilename(stream, key);
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
_text = convo->texts[key];
}
void ConversationSound::readTerseResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
Common::String key;
readFilename(stream, key);
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
response.text = convo->texts[key];
}
void ConversationSound::execute() {
ConversationSound *activeConversation = NancySceneState.getActiveConversation();
if (activeConversation != this && activeConversation != nullptr) {
@ -289,20 +371,6 @@ void ConversationSound::execute() {
}
}
void ConversationSound::readCaptionText(Common::SeekableReadStream &stream) {
char *rawText = new char[1500];
stream.read(rawText, 1500);
assembleTextLine(rawText, _text, 1500);
delete[] rawText;
}
void ConversationSound::readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
char *rawText = new char[400];
stream.read(rawText, 400);
assembleTextLine(rawText, response.text, 400);
delete[] rawText;
}
void ConversationSound::addConditionalDialogue() {
for (const auto &res : g_nancy->getStaticData().conditionalDialogue[_conditionalResponseCharacterID]) {
bool isSatisfied = true;
@ -678,33 +746,7 @@ void ConversationCel::readData(Common::SeekableReadStream &stream) {
readFilename(stream, xsheetName);
readFilenameArray(stream, _treeNames, 4);
Common::SeekableReadStream *xsheet = SearchMan.createReadStreamForMember(Common::Path(xsheetName));
// Read the xsheet and load all images into the arrays
// Completely unoptimized, the original engine uses a buffer
xsheet->seek(0);
Common::String signature = xsheet->readString('\0', 18);
if (signature != "XSHEET WayneSikes") {
warning("XSHEET signature doesn't match!");
return;
}
xsheet->seek(0x22);
uint numFrames = xsheet->readUint16LE();
xsheet->skip(2);
_frameTime = xsheet->readUint16LE();
xsheet->skip(2);
_celNames.resize(4, Common::Array<Common::Path>(numFrames));
for (uint i = 0; i < numFrames; ++i) {
for (uint j = 0; j < _celNames.size(); ++j) {
readFilename(*xsheet, _celNames[j][i]);
}
// 4 unknown values
xsheet->skip(8);
}
readXSheet(stream, xsheetName);
// Continue reading the AR stream
@ -732,6 +774,35 @@ void ConversationCel::readData(Common::SeekableReadStream &stream) {
ConversationSound::readData(stream);
}
void ConversationCel::readXSheet(Common::SeekableReadStream &stream, const Common::String &xsheetName) {
Common::SeekableReadStream *xsheet = SearchMan.createReadStreamForMember(Common::Path(xsheetName));
// Read the xsheet and load all images into the arrays
// Completely unoptimized, the original engine uses a buffer
xsheet->seek(0);
Common::String signature = xsheet->readString('\0', 18);
if (signature != "XSHEET WayneSikes") {
warning("XSHEET signature doesn't match!");
return;
}
xsheet->seek(0x22);
uint numFrames = xsheet->readUint16LE();
xsheet->skip(2);
_frameTime = xsheet->readUint16LE();
xsheet->skip(2);
_celNames.resize(4, Common::Array<Common::Path>(numFrames));
for (uint i = 0; i < numFrames; ++i) {
for (uint j = 0; j < _celNames.size(); ++j) {
readFilename(*xsheet, _celNames[j][i]);
}
// 4 unknown values
xsheet->skip(8);
}
}
bool ConversationCel::isVideoDonePlaying() {
return _curFrame >= MIN<uint>(_lastFrame, _celNames[0].size()) && _nextFrameTime <= g_nancy->getTotalPlayTime();
}
@ -747,44 +818,22 @@ ConversationCel::Cel &ConversationCel::loadCel(const Common::Path &name, const C
return _celCache[name];
}
void ConversationSoundT::readCaptionText(Common::SeekableReadStream &stream) {
Common::String key;
readFilename(stream, key);
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
_text = convo->texts[key];
void ConversationSoundTerse::readData(Common::SeekableReadStream &stream) {
readTerseData(stream);
}
void ConversationSoundT::readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
Common::String key;
readFilename(stream, key);
void ConversationCelTerse::readData(Common::SeekableReadStream &stream) {
Common::String xsheetName;
readFilename(stream, xsheetName);
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
readFilenameArray(stream, _treeNames, 2); // Only 2
readXSheet(stream, xsheetName);
response.text = convo->texts[key];
}
_lastFrame = stream.readUint16LE();
_drawingOrder = { 1, 0, 2, 3 };
_overrideTreeRects.resize(4, kCelOverrideTreeRectsOff);
void ConversationCelT::readCaptionText(Common::SeekableReadStream &stream) {
Common::String key;
readFilename(stream, key);
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
_text = convo->texts[key];
}
void ConversationCelT::readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
Common::String key;
readFilename(stream, key);
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
response.text = convo->texts[key];
readTerseData(stream);
}
} // End of namespace Action

View File

@ -100,6 +100,11 @@ protected:
// Functions for reading captions are virtual to allow easier support for the terse Conversation variants
virtual void readCaptionText(Common::SeekableReadStream &stream);
virtual void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response);
// Used in subclasses
void readTerseData(Common::SeekableReadStream &stream);
void readTerseCaptionText(Common::SeekableReadStream &stream);
void readTerseResponseText(Common::SeekableReadStream &stream, ResponseStruct &response);
// Functions for handling the built-in dialogue responses found in the executable
void addConditionalDialogue();
@ -110,8 +115,8 @@ protected:
SoundDescription _sound;
SoundDescription _responseGenericSound;
byte _conditionalResponseCharacterID = 0;
byte _goodbyeResponseCharacterID = 0;
byte _conditionalResponseCharacterID;
byte _goodbyeResponseCharacterID;
byte _defaultNextScene = kDefaultNextSceneEnabled;
byte _popNextScene = kNoPopNextScene;
SceneChangeDescription _sceneChange;
@ -120,8 +125,8 @@ protected:
Common::Array<FlagsStruct> _flagsStructs;
Common::Array<SceneBranchStruct> _sceneBranchStructs;
bool _hasDrawnTextbox = false;
int16 _pickedResponse = -1;
bool _hasDrawnTextbox;
int16 _pickedResponse;
const byte _noResponse;
};
@ -186,6 +191,8 @@ protected:
bool isVideoDonePlaying() override;
Cel &loadCel(const Common::Path &name, const Common::String &treeName);
void readXSheet(Common::SeekableReadStream &stream, const Common::String &xsheetName);
Common::Array<Common::Array<Common::Path>> _celNames;
Common::Array<Common::String> _treeNames;
@ -209,20 +216,40 @@ protected:
Common::SharedPtr<ConversationCelLoader> _loaderPtr;
};
// A ConversationSound without embedded text; uses the CONVO chunk instead
class ConversationSoundT : public ConversationSound {
protected:
Common::String getRecordTypeName() const override { return "ConversationSoundT"; }
void readCaptionText(Common::SeekableReadStream &stream) override;
void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) override;
void readCaptionText(Common::SeekableReadStream &stream) override { readTerseCaptionText(stream); }
void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) override { readTerseResponseText(stream, response); }
};
// A ConversationCel without embedded text; uses the CONVO chunk instead
class ConversationCelT : public ConversationCel {
protected:
Common::String getRecordTypeName() const override { return "ConversationCelT"; }
void readCaptionText(Common::SeekableReadStream &stream) override;
void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) override;
void readCaptionText(Common::SeekableReadStream &stream) override { readTerseCaptionText(stream); }
void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) override { readTerseResponseText(stream, response); }
};
// A ConversationSound with a much smaller data footprint
class ConversationSoundTerse : public ConversationSound {
public:
void readData(Common::SeekableReadStream &stream) override;
protected:
Common::String getRecordTypeName() const override { return "ConversationSoundTerse"; }
};
// A ConversationCel with a much smaller data footprint
class ConversationCelTerse : public ConversationCel {
public:
void readData(Common::SeekableReadStream &stream) override;
protected:
Common::String getRecordTypeName() const override { return "ConversationCelTerse"; }
};
} // End of namespace Action