NANCY: Render text in The Vampire Diaries

Text in TVD is now rendered in the textbox, and should be
pixel-accurate to the original engine.
This commit is contained in:
Kaloyan Chehlarski 2021-04-29 15:11:27 +03:00
parent 6efcbebef5
commit 6abc61edf7
5 changed files with 62 additions and 16 deletions

View File

@ -75,6 +75,11 @@ void Font::read(Common::SeekableReadStream &stream) {
Common::Rect &cur = _symbolRects[i];
readRect(stream, cur);
if (g_nancy->getGameType() == kGameTypeVampire) {
++cur.bottom;
++cur.right;
}
_maxCharWidth = MAX<int>(cur.width(), _maxCharWidth);
_fontHeight = MAX<int>(cur.height(), _maxCharWidth);
}
@ -90,16 +95,29 @@ void Font::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 col
srcRect.translate(_colorCoordsOffset.x, _colorCoordsOffset.y);
}
uint width = srcRect.width();
uint vampireAdjust = g_nancy->getGameType() == kGameTypeVampire ? 1 : 0;
uint width = MAX<int>(srcRect.width() - vampireAdjust, 0);
uint height = srcRect.height();
uint yOffset = getFontHeight() - height;
height = MAX<int>(height - vampireAdjust, 0);
for (uint curY = 0; curY < height; ++curY) {
for (uint curX = 0; curX < width; ++curX) {
switch (g_nancy->_graphicsManager->getInputPixelFormat().bytesPerPixel) {
case 1:
// TODO
case 1: {
byte colorID = *(const byte *)_image.getBasePtr(srcRect.left + curX, srcRect.top + curY);
if (colorID != _transColor) {
uint8 r, g, b;
uint curColor = _image.getPalette()[colorID];
r = curColor & 0xFF;
g = (curColor & 0xFF00) >> 8;
b = (curColor & 0xFF0000) >> 16;
*(uint16 *)dst->getBasePtr(x + curX, y + yOffset + curY) = dst->format.RGBToColor(r, g, b);
}
break;
}
case 2: {
uint16 curColor = *(const uint16 *)_image.getBasePtr(srcRect.left + curX, srcRect.top + curY);
@ -121,6 +139,12 @@ void Font::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 col
void Font::wordWrap(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines, int initWidth) const {
Common::String temp;
for (const char *c = str.begin(); c != str.end(); ++c) {
if (*c == '\n') {
lines.push_back(temp);
temp.clear();
continue;
}
temp += *c;
int size = getStringWidth(temp) + (lines.size() == 0 ? initWidth : 0);
if (size >= maxWidth) {

View File

@ -25,7 +25,7 @@
#include "common/array.h"
#include "graphics/font.h"
#include "graphics/surface.h"
#include "graphics/managed_surface.h"
namespace Common {
class SeekableReadStream;
@ -82,7 +82,7 @@ private:
Common::Array<Common::Rect> _symbolRects; // 0x62
Graphics::Surface _image;
Graphics::ManagedSurface _image;
int _fontHeight;
int _maxCharWidth;

View File

@ -37,6 +37,7 @@
#include "engines/nancy/dialogs.h"
#include "engines/nancy/console.h"
#include "engines/nancy/constants.h"
#include "engines/nancy/util.h"
#include "engines/nancy/action/primaryvideo.h"
@ -467,8 +468,12 @@ void NancyEngine::readBootSummary(const IFF &boot) {
readChunkList(boot, ser, "OB");
}
ser.skip(0x96, kGameTypeVampire, kGameTypeVampire);
ser.skip(0x79, kGameTypeNancy1, kGameTypeNancy1);
ser.skip(0x28, kGameTypeVampire, kGameTypeVampire);
ser.skip(0x10, kGameTypeNancy1, kGameTypeNancy1);
readRect(*bsum, _textboxScreenPosition);
ser.skip(0x5E, kGameTypeVampire, kGameTypeVampire);
ser.skip(0x59, kGameTypeNancy1, kGameTypeNancy1);
ser.syncAsUint16LE(_horizontalEdgesSize, kGameTypeVampire, kGameTypeNancy1);
ser.syncAsUint16LE(_verticalEdgesSize, kGameTypeVampire, kGameTypeNancy1);
ser.skip(0x1C, kGameTypeVampire, kGameTypeNancy1);

View File

@ -122,6 +122,7 @@ public:
Common::RandomSource *_randomSource;
// BSUM data
uint16 _firstSceneID;
uint16 _startTimeHours;
@ -133,6 +134,8 @@ public:
uint _horizontalEdgesSize;
uint _verticalEdgesSize;
Common::Rect _textboxScreenPosition;
private:
struct GameFlow {
NancyState::NancyState curState = NancyState::kNone;

View File

@ -81,9 +81,7 @@ void Textbox::init() {
chunk->seek(0x1FE, SEEK_SET);
_fontID = chunk->readUint16LE();
chunk = g_nancy->getBootChunkStream("BSUM");
chunk->seek(0x164);
readRect(*chunk, _screenPosition);
_screenPosition = g_nancy->_textboxScreenPosition;
Common::Rect outerBoundingBox = _screenPosition;
outerBoundingBox.moveTo(0, 0);
@ -142,11 +140,10 @@ void Textbox::drawTextbox() {
const Font *font = g_nancy->_graphicsManager->getFont(_fontID);
uint maxWidth = _fullSurface.w - _maxWidthDifference - _borderWidth - 2;
uint lineDist = _lineHeight + _lineHeight / 4;
uint lineDist = _lineHeight + _lineHeight / 4 + (g_nancy->getGameType() == kGameTypeVampire ? 1 : 0);
for (uint lineID = 0; lineID < _textLines.size(); ++lineID) {
Common::String currentLine = _textLines[lineID];
currentLine.trim();
uint horizontalOffset = 0;
bool hasHotspot = false;
@ -168,12 +165,21 @@ void Textbox::drawTextbox() {
currentLine = currentLine.substr(0, currentLine.size() - ARRAYSIZE(_telephoneEndToken) + 1);
}
// Remove hotspot token and mark that we need to calculate the bounds
// Assumes a single text line has a single hotspot
uint32 hotspotPos = currentLine.find(_hotspotToken);
if (hotspotPos != String::npos) {
// Remove hotspot tokens and mark that we need to calculate the bounds
// A single text line should only have one hotspot, but there's at least
// one malformed line in TVD that breaks this
uint32 hotspotPos, lastHotspotPos;
while (hotspotPos = currentLine.find(_hotspotToken), hotspotPos != String::npos) {
currentLine.erase(hotspotPos, ARRAYSIZE(_hotspotToken) - 1);
if (hasHotspot) {
// Replace the second hotspot token with a newline to copy the original behavior
// Maybe consider fixing the glitch instead of replicating it??
currentLine.insertChar('\n', lastHotspotPos);
}
hasHotspot = true;
lastHotspotPos = hotspotPos;
}
// Subdivide current line into sublines for proper handling of the tab and color tokens
@ -280,6 +286,14 @@ void Textbox::assembleTextLine(char *rawCaption, Common::String &output, uint si
i += newBit.size();
}
}
// Fix spaces at the end of the string in nancy1
output.trim();
// Fix at least one broken string in TVD
if (output.hasSuffix(">>")) {
output.deleteLastChar();
}
}
void Textbox::onScrollbarMove() {