mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
ae3ebc5b02
This is a regression from the scene text refactoring done in 8eadac5eed
376 lines
9.0 KiB
C++
376 lines
9.0 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h"
|
|
#include "chewy/resource.h"
|
|
#include "chewy/text.h"
|
|
|
|
#include "types.h"
|
|
#include "chewy/atds.h"
|
|
#include "chewy/defines.h"
|
|
|
|
namespace Chewy {
|
|
|
|
Text::Text() : Resource("atds.tap") {
|
|
memset(_hotspotStrings, 0, sizeof(_hotspotStrings));
|
|
|
|
Common::File f;
|
|
|
|
if (!f.open(ROOM_ATS_STEUER))
|
|
error("Error reading file: %s", ROOM_ATS_STEUER);
|
|
for (int16 i = 0; i < ROOM_ATS_MAX; i++)
|
|
_hotspotStrings[i * MAX_ATS_STATUS] = f.readByte();
|
|
|
|
f.close();
|
|
|
|
if (!f.open(INV_ATS_STEUER))
|
|
error("Error reading file: %s", INV_ATS_STEUER);
|
|
for (int16 i = 0; i < MAX_MOV_OBJ; i++)
|
|
_inventoryStrings[i * MAX_ATS_STATUS] = f.readByte();
|
|
|
|
f.close();
|
|
|
|
// WORKAROUND: For English version, taxi hotspot in
|
|
// room 45 (Big City) isn't turned on by default
|
|
_hotspotStrings[295] = ATS_ACTION_BIT;
|
|
}
|
|
|
|
Text::~Text() {
|
|
}
|
|
|
|
TextEntryList *Text::getDialog(uint chunk, uint entry) {
|
|
if (chunk >= kADSTextMax)
|
|
error("getDialog(): Invalid entry number requested, %d (max %d)", chunk, kADSTextMax - 1);
|
|
|
|
TextEntryList *l = new TextEntryList();
|
|
|
|
byte *data = getChunkData(chunk);
|
|
byte *ptr = data;
|
|
|
|
ptr += 2; // entry number
|
|
ptr += 2; // number of persons
|
|
ptr += 2; // automove count
|
|
ptr += 2; // cursor number
|
|
ptr += 13; // misc data
|
|
|
|
for (uint i = 0; i <= entry; i++) {
|
|
do {
|
|
TextEntry curDialog;
|
|
ptr++; // current entry
|
|
ptr += 2;
|
|
curDialog._speechId = READ_LE_UINT16(ptr) - VOICE_OFFSET;
|
|
ptr += 2;
|
|
|
|
do {
|
|
curDialog._text += *ptr++;
|
|
|
|
if (*ptr == 0 && *(ptr + 1) != kEndText) {
|
|
// TODO: Split lines
|
|
*ptr = ' ';
|
|
}
|
|
} while (*ptr != kEndText);
|
|
|
|
if (i == entry)
|
|
l->push_back(curDialog);
|
|
|
|
} while (*(ptr + 1) != kEndEntry);
|
|
|
|
ptr += 2; // kEndText, kEndEntry
|
|
|
|
if (*ptr == kEndBlock) // not found
|
|
break;
|
|
}
|
|
|
|
delete[] data;
|
|
|
|
return l;
|
|
}
|
|
|
|
TextEntry *Text::getText(uint chunk, uint entry, int type, int subEntry) {
|
|
bool isText = false;
|
|
bool isAutoDialog = false;
|
|
bool isInvDesc = false;
|
|
uint origChunk = chunk;
|
|
|
|
//debug("getText %d, %d, %d, %d", chunk, entry, type, subEntry);
|
|
|
|
switch (type) {
|
|
case AAD_DATA:
|
|
chunk += kADSTextMax + kATSTextMax;
|
|
isAutoDialog = true;
|
|
break;
|
|
case ATS_DATA:
|
|
chunk += kADSTextMax;
|
|
isText = true;
|
|
break;
|
|
case DIALOG_CLOSEUP_DATA:
|
|
// No change - chunk starts from 0
|
|
break;
|
|
case INV_USE_DATA:
|
|
case INV_USE_DEF:
|
|
chunk += kADSTextMax + kATSTextMax + kAADTextMax + kINVTextMax;
|
|
isInvDesc = true;
|
|
isText = true;
|
|
break;
|
|
case INV_ATS_DATA:
|
|
chunk += kADSTextMax + kATSTextMax + kAADTextMax;
|
|
isInvDesc = true;
|
|
break;
|
|
}
|
|
|
|
if (chunk < kADSTextMax)
|
|
error("getText(): Invalid chunk number requested, %d (min %d)", chunk, kADSTextMax);
|
|
|
|
TextEntry *d = new TextEntry();
|
|
const uint8 altSubString = !isInvDesc ?
|
|
getTextId(entry, subEntry, type) :
|
|
getTextId(origChunk, 0, type);
|
|
|
|
byte *data = getChunkData(chunk);
|
|
byte *ptr = data;
|
|
uint entryId = 0;
|
|
uint16 headerBytes, txtNum;
|
|
int curSubEntry = -1;
|
|
|
|
//Common::hexdump(data, _chunkList[chunk].size);
|
|
|
|
if (isAutoDialog)
|
|
ptr += 3;
|
|
|
|
while (true) {
|
|
ptr += 3;
|
|
headerBytes = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
|
|
if (headerBytes == 0xFEF2) {
|
|
// Start of subchunk
|
|
curSubEntry = *ptr;
|
|
ptr++;
|
|
headerBytes = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
}
|
|
|
|
if (headerBytes != 0xFEF0)
|
|
break;
|
|
|
|
txtNum = !isInvDesc ? READ_LE_UINT16(ptr) : entryId++;
|
|
ptr += 2;
|
|
ptr += 6;
|
|
d->_speechId = READ_LE_UINT16(ptr) - VOICE_OFFSET;
|
|
ptr += 2;
|
|
|
|
do {
|
|
if (txtNum == entry && curSubEntry == subEntry)
|
|
d->_text += *ptr++;
|
|
else
|
|
ptr++;
|
|
|
|
if (*ptr == 0 && *(ptr + 1) != kEndText)
|
|
*ptr = '|';
|
|
} while (*ptr);
|
|
|
|
// Pick the appropriate substring
|
|
// Each substring starts with 0xf1 0xfe and ends with 0x0 0x0d
|
|
if (*(ptr + 1) == kEndText && *(ptr + 2) == 0xf1 && *(ptr + 3) == 0xfe) {
|
|
ptr += 6;
|
|
uint curSubString = 0;
|
|
bool endOfSubString = (*ptr == 0 && *(ptr + 1) == kEndText);
|
|
bool endOfChunk = (endOfSubString && *(ptr + 2) == kEndChunk);
|
|
|
|
if (txtNum == entry && curSubEntry == subEntry && curSubString < altSubString) {
|
|
d->_text = "";
|
|
d->_speechId++;
|
|
curSubString++;
|
|
|
|
while (!endOfChunk && curSubString <= altSubString) {
|
|
d->_text += *ptr++;
|
|
|
|
endOfSubString = (*ptr == 0 && *(ptr + 1) == kEndText);
|
|
endOfChunk = (endOfSubString && *(ptr + 2) == kEndChunk);
|
|
|
|
if (*ptr == 0 && *(ptr + 1) != kEndText)
|
|
*ptr = '|';
|
|
|
|
if (endOfSubString) {
|
|
if (curSubString < altSubString) {
|
|
d->_text = "";
|
|
d->_speechId++;
|
|
curSubString++;
|
|
ptr += 6;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
endOfSubString = (*ptr == 0 && *(ptr + 1) == kEndText);
|
|
endOfChunk = (endOfSubString && *(ptr + 2) == kEndChunk);
|
|
|
|
// Keep going until the chunk ends
|
|
while (!endOfChunk) {
|
|
ptr++;
|
|
endOfSubString = (*ptr == 0 && *(ptr + 1) == kEndText);
|
|
endOfChunk = (endOfSubString && *(ptr + 2) == kEndChunk);
|
|
}
|
|
}
|
|
|
|
if (*(ptr + 1) != kEndText || *(ptr + 2) != kEndChunk) {
|
|
warning("Invalid text resource - %d, %d", chunk, entry);
|
|
|
|
delete[] data;
|
|
delete d;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
if (!isText)
|
|
ptr += 3; // 0, kEndText, kEndChunk
|
|
if (isAutoDialog)
|
|
ptr += 3;
|
|
|
|
d->_text.trim();
|
|
|
|
if (txtNum == entry && curSubEntry == subEntry && d->_text.size() > 1) {
|
|
// Found
|
|
delete[] data;
|
|
return d;
|
|
}
|
|
}
|
|
|
|
// Not found
|
|
delete[] data;
|
|
delete d;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Common::StringArray Text::getTextArray(uint chunk, uint entry, int type, int subEntry) {
|
|
TextEntry *textData = getText(chunk, entry, type, subEntry);
|
|
Common::StringArray res;
|
|
Common::String txt = textData ? textData->_text : "";
|
|
char *text = new char[txt.size() + 1];
|
|
Common::strlcpy(text, txt.c_str(), txt.size() + 1);
|
|
char *line = strtok(text, "|");
|
|
|
|
while (line) {
|
|
res.push_back(line);
|
|
line = strtok(nullptr, "|");
|
|
}
|
|
|
|
_lastSpeechId = textData ? textData->_speechId : -1;
|
|
|
|
delete[] text;
|
|
delete textData;
|
|
|
|
return res;
|
|
}
|
|
|
|
Common::String Text::getTextEntry(uint chunk, uint entry, int type, int subEntry) {
|
|
Common::StringArray res = getTextArray(chunk, entry, type, subEntry);
|
|
return res.size() > 0 ? res[0] : "";
|
|
}
|
|
|
|
const char *Text::strPos(const char *txtAdr, int16 pos) {
|
|
const char *ptr = txtAdr;
|
|
for (int16 i = 0; i < pos;) {
|
|
if (*ptr == 0 || *ptr == '|')
|
|
++i;
|
|
++ptr;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
void Text::syncHotspotStrings(Common::Serializer &s) {
|
|
for (size_t i = 0; i < sizeof(_hotspotStrings); ++i)
|
|
s.syncAsByte(_hotspotStrings[i]);
|
|
}
|
|
|
|
void Text::syncInventoryStrings(Common::Serializer &s) {
|
|
for (size_t i = 0; i < sizeof(_inventoryStrings); ++i)
|
|
s.syncAsByte(_inventoryStrings[i]);
|
|
}
|
|
|
|
void Text::syncInventoryUseStrings(Common::Serializer &s) {
|
|
for (size_t i = 0; i < sizeof(_inventoryUseStrings); ++i)
|
|
s.syncAsByte(_inventoryUseStrings[i]);
|
|
}
|
|
|
|
bool Text::getControlBit(int16 txtNr, int16 bitIdx) {
|
|
return (_hotspotStrings[txtNr * MAX_ATS_STATUS] & bitIdx) != 0;
|
|
}
|
|
|
|
void Text::setControlBit(int16 txtNr, int16 bitIdx) {
|
|
_hotspotStrings[txtNr * MAX_ATS_STATUS] |= bitIdx;
|
|
}
|
|
|
|
void Text::delControlBit(int16 txtNr, int16 bitIdx) {
|
|
_hotspotStrings[txtNr * MAX_ATS_STATUS] &= ~bitIdx;
|
|
}
|
|
|
|
uint8 *Text::getBuffer(uint8 type) {
|
|
switch (type) {
|
|
case ATS_DATA:
|
|
return _hotspotStrings;
|
|
case INV_USE_DATA:
|
|
return _inventoryUseStrings;
|
|
case INV_ATS_DATA:
|
|
return _inventoryStrings;
|
|
default:
|
|
error("getBuffer called for type %d", type);
|
|
}
|
|
}
|
|
|
|
uint8 getNibble(uint8 value, uint8 subEntry) {
|
|
if ((subEntry + 1) % 2 == 0)
|
|
return value & 0x0F;
|
|
else
|
|
return (value >> 4) & 0x0F;
|
|
}
|
|
|
|
uint8 setNibble(uint8 value, uint8 subEntry, uint8 nibble) {
|
|
if ((subEntry + 1) % 2 == 0)
|
|
return (nibble & 0x0F) | (value & 0xF0);
|
|
else
|
|
return (nibble << 4) | (value & 0x0F);
|
|
}
|
|
|
|
uint8 Text::getTextId(uint16 entry, uint8 subEntry, uint8 type) {
|
|
if (type != ATS_DATA && type != INV_USE_DATA && type != INV_ATS_DATA)
|
|
return 0;
|
|
|
|
const uint8 *buffer = getBuffer(type);
|
|
const uint8 value = buffer[(entry * MAX_ATS_STATUS) + (subEntry + 1) / 2];
|
|
return getNibble(value, subEntry);
|
|
}
|
|
|
|
void Text::setTextId(uint16 entry, uint8 subEntry, uint8 strNr, uint8 type) {
|
|
if (type != ATS_DATA && type != INV_USE_DATA && type != INV_ATS_DATA)
|
|
return;
|
|
|
|
uint8 *buffer = getBuffer(type);
|
|
const uint8 value = buffer[(entry * MAX_ATS_STATUS) + (subEntry + 1) / 2];
|
|
buffer[(entry * MAX_ATS_STATUS) + (subEntry + 1) / 2] = setNibble(value, subEntry, (uint8)strNr);
|
|
}
|
|
|
|
} // namespace Chewy
|