Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

394
engines/kyra/text/text.cpp Normal file
View File

@@ -0,0 +1,394 @@
/* 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 "kyra/text/text.h"
#include "kyra/kyra_v1.h"
namespace Kyra {
TextDisplayer::TextDisplayer(KyraEngine_v1 *vm, Screen *screen) {
_screen = screen;
_vm = vm;
_talkCoords.y = 0x88;
_talkCoords.x = 0;
_talkCoords.w = 0;
_talkMessageY = 0xC;
_talkMessageH = 0;
_talkMessagePrinted = false;
_langExtraSpacing = (vm->gameFlags().lang == Common::KO_KOR) ? 2 : 0;
_lineBreakChar = (_vm->gameFlags().platform == Common::kPlatformMacintosh) ? '\n' : '\r';
memset(_talkSubstrings, 0, sizeof(_talkSubstrings));
memset(_talkBuffer, 0, sizeof(_talkBuffer));
}
void TextDisplayer::setTalkCoords(uint16 y) {
_talkCoords.y = y;
}
int TextDisplayer::getCenterStringX(const Common::String &str, int x1, int x2) {
_screen->_charSpacing = -2;
int strWidth = _screen->getTextWidth(str.c_str());
_screen->_charSpacing = 0;
int w = x2 - x1 + 1;
return x1 + (w - strWidth) / 2;
}
int TextDisplayer::getCharLength(const char *str, int len) {
int charsCount = 0;
if (*str) {
_screen->_charSpacing = -2;
int i = 0;
while (i <= len && *str) {
uint c = *str++;
c &= 0xFF;
if (c > 0x7F && (_vm->gameFlags().lang == Common::JA_JPN || _vm->gameFlags().lang == Common::KO_KOR)) {
c = READ_LE_UINT16(str - 1);
++charsCount;
++str;
}
i += _screen->getCharWidth(c);
++charsCount;
}
_screen->_charSpacing = 0;
}
return charsCount;
}
int TextDisplayer::dropCRIntoString(char *str, int offs) {
int pos = 0;
str += offs;
while (*str) {
if (*str == ' ') {
*str = _lineBreakChar;
return pos;
}
++str;
++pos;
}
return 0;
}
char *TextDisplayer::preprocessString(const char *str) {
if (str != _talkBuffer) {
assert(strlen(str) < sizeof(_talkBuffer) - 1);
Common::strlcpy(_talkBuffer, str, sizeof(_talkBuffer));
}
if (_vm->gameFlags().lang == Common::ZH_TWN)
return _talkBuffer;
char *p = _talkBuffer;
while (*p) {
if (*p == _lineBreakChar) {
return _talkBuffer;
}
++p;
}
p = _talkBuffer;
static const uint16 limDef[2] = { 176, 352 };
static const uint16 limKor[2] = { 240, 480 };
const uint16 *lim = (_vm->gameFlags().lang == Common::KO_KOR) ? limKor : limDef;
Screen::FontId curFont = _screen->setFont(_vm->gameFlags().lang == Common::Language::ZH_TWN && _vm->gameFlags().gameID == GI_LOL ? Screen::FID_CHINESE_FNT :
_vm->gameFlags().lang == Common::KO_KOR ? Screen::FID_KOREAN_FNT : Screen::FID_8_FNT);
_screen->_charSpacing = -2;
int textWidth = _screen->getTextWidth(p);
_screen->_charSpacing = 0;
if (textWidth > lim[0]) {
if (textWidth > lim[1]) {
int count = getCharLength(p, textWidth / 3);
int offs = dropCRIntoString(p, count);
p += count + offs;
_screen->_charSpacing = -2;
textWidth = _screen->getTextWidth(p);
_screen->_charSpacing = 0;
count = getCharLength(p, textWidth / 2);
dropCRIntoString(p, count);
} else {
int count = getCharLength(p, textWidth / 2);
dropCRIntoString(p, count);
}
}
_screen->setFont(curFont);
return _talkBuffer;
}
int TextDisplayer::buildMessageSubstrings(const char *str) {
int currentLine = 0;
int pos = 0;
while (*str) {
if (*str == _lineBreakChar) {
assert(currentLine < TALK_SUBSTRING_NUM);
_talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = '\0';
++currentLine;
pos = 0;
} else {
_talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = *str;
++pos;
if (_vm->game() == GI_KYRA2 && _vm->gameFlags().lang == Common::ZH_TWN && pos == 32) {
_talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = '\0';
++currentLine;
pos = 0;
} else if (pos >= TALK_SUBSTRING_LEN - 2) {
pos = TALK_SUBSTRING_LEN - 2;
}
}
++str;
}
_talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = '\0';
return currentLine + 1;
}
int TextDisplayer::getWidestLineWidth(int linesCount) {
int maxWidth = 0;
_screen->_charSpacing = -2;
for (int l = 0; l < linesCount; ++l) {
int w = _screen->getTextWidth(&_talkSubstrings[l * TALK_SUBSTRING_LEN]);
if (maxWidth < w) {
maxWidth = w;
}
}
_screen->_charSpacing = 0;
return maxWidth;
}
void TextDisplayer::calcWidestLineBounds(int &x1, int &x2, int w, int cx) {
int margin = (_vm->game() == GI_KYRA2 && _vm->gameFlags().lang == Common::ZH_TWN) ? 8 : 12;
x1 = cx - w / 2;
if (x1 + w >= Screen::SCREEN_W - margin) {
x1 = Screen::SCREEN_W - margin - w - 1;
} else if (x1 < margin) {
x1 = margin;
}
x2 = x1 + w + 1;
}
void TextDisplayer::restoreTalkTextMessageBkgd(int srcPage, int dstPage) {
if (_talkMessagePrinted) {
_talkMessagePrinted = false;
_screen->copyRegion(_talkCoords.x, _talkCoords.y, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, srcPage, dstPage, Screen::CR_NO_P_CHECK);
}
}
void TextDisplayer::printTalkTextMessage(const char *text, int x, int y, uint8 color, int srcPage, int dstPage) {
char *str = preprocessString(text);
int lineCount = buildMessageSubstrings(str);
// For Chinese we call this before recalculating the line count
int w = getWidestLineWidth(lineCount);
int marginTop = 0;
if (_vm->gameFlags().lang == Common::ZH_TWN) {
lineCount = (strlen(str) + 31) >> 5;
marginTop = 10;
w = MIN<int>(w, 302);
}
int top = y - (lineCount * _screen->getFontHeight() + (lineCount - 1) * _screen->_lineSpacing) - _langExtraSpacing;
if (top < marginTop)
top = marginTop;
_talkMessageY = top;
_talkMessageH = (lineCount * _screen->getFontHeight() + (lineCount - 1) * _screen->_lineSpacing) + _langExtraSpacing;
int x1 = 12;
int x2 = Screen::SCREEN_W - 12;
if (_vm->gameFlags().lang != Common::ZH_TWN || lineCount == 1)
calcWidestLineBounds(x1, x2, w, x);
_talkCoords.x = x1;
_talkCoords.w = w + 2;
_screen->copyRegion(_talkCoords.x, _talkMessageY, _talkCoords.x, _talkCoords.y, _talkCoords.w, _talkMessageH, srcPage, dstPage, Screen::CR_NO_P_CHECK);
int curPage = _screen->_curPage;
_screen->_curPage = srcPage;
if (_vm->gameFlags().platform == Common::kPlatformAmiga)
setTextColor(color);
if (_vm->gameFlags().lang == Common::ZH_TWN && lineCount > 1) {
// The Chinese version leaves the wrapping to the default font handling
printText(_talkSubstrings, 12, top, color, 0xC, 0xC);
} else {
for (int i = 0; i < lineCount; ++i) {
top = i * (_screen->getFontHeight() + _screen->_lineSpacing) + _talkMessageY;
char *msg = &_talkSubstrings[i * TALK_SUBSTRING_LEN];
int left = getCenterStringX(msg, x1, x2);
printText(msg, left, top, color, 0xC, _vm->gameFlags().lang == Common::ZH_TWN ? 0xC : 0);
}
}
_screen->_curPage = curPage;
_talkMessagePrinted = true;
}
void TextDisplayer::printText(const Common::String &str, int x, int y, uint8 c0, uint8 c1, uint8 c2) {
Common::String revBuffer;
const char *tmp = str.c_str();
if (_vm->gameFlags().lang == Common::HE_ISR) {
for (int i = str.size() - 1; i >= 0; --i)
revBuffer += str[i];
tmp = revBuffer.c_str();
}
uint8 colorMap[] = { 0, 15, 12, 12 };
colorMap[3] = c1;
_screen->setTextColor(colorMap, 0, 3);
_screen->_charSpacing = -2;
int ls = _screen->_lineSpacing;
_screen->_lineSpacing = _langExtraSpacing >> 1;
_screen->printText(tmp, x, y, c0, c2);
_screen->_charSpacing = 0;
_screen->_lineSpacing = ls;
}
void TextDisplayer::printCharacterText(const char *text, int8 charNum, int charX) {
int top, left, w, x;
char *msg;
text = preprocessString(text);
int lineCount = buildMessageSubstrings(text);
// For Chinese we call this before recalculating the line count
w = getWidestLineWidth(lineCount);
if (_vm->gameFlags().lang == Common::ZH_TWN) {
lineCount = (strlen(text) + 31) >> 5;
w = MIN<int>(w, 302);
}
int x1 = 12;
int x2 = Screen::SCREEN_W - 12;
if (_vm->gameFlags().lang != Common::ZH_TWN || lineCount == 1) {
x = charX;
calcWidestLineBounds(x1, x2, w, x);
}
uint8 color = 0;
if (_vm->gameFlags().platform == Common::kPlatformAmiga) {
const uint8 colorTable[] = { 0x1F, 0x1B, 0xC9, 0x80, 0x1E, 0x81, 0x11, 0xD8, 0x55, 0x3A, 0x3A };
color = colorTable[charNum];
setTextColor(color);
} else {
const uint8 colorTable[] = { 0x0F, 0x09, 0xC9, 0x80, 0x05, 0x81, 0x0E, 0xD8, 0x55, 0x3A, 0x3A };
color = colorTable[charNum];
}
if (_vm->gameFlags().lang == Common::ZH_TWN && lineCount > 1) {
// The Chinese version leaves the wrapping to the default font handling
printText(_talkSubstrings, 12, _talkMessageY, color, 0xC, 0xC);
return;
}
for (int i = 0; i < lineCount; ++i) {
top = i * (_screen->getFontHeight() + _screen->_lineSpacing) + _talkMessageY;
msg = &_talkSubstrings[i * TALK_SUBSTRING_LEN];
left = getCenterStringX(msg, x1, x2);
printText(msg, left, top, color, 0xC, _vm->gameFlags().lang == Common::ZH_TWN ? 0xC : 0);
}
}
void TextDisplayer::setTextColor(uint8 color) {
byte r, g, b;
switch (color) {
case 4:
// 0x09E
r = 0;
g = 37;
b = 58;
break;
case 5:
// 0xFF5
r = 63;
g = 63;
b = 21;
break;
case 27:
// 0x5FF
r = 21;
g = 63;
b = 63;
break;
case 34:
// 0x8E5
r = 33;
g = 58;
b = 21;
break;
case 58:
// 0x9FB
r = 37;
g = 63;
b = 46;
break;
case 85:
// 0x7CF
r = 29;
g = 50;
b = 63;
break;
case 114:
case 117:
// 0xFAF
r = 63;
g = 42;
b = 63;
break;
case 128:
case 129:
// 0xFCC
r = 63;
g = 50;
b = 50;
break;
case 201:
// 0xFD8
r = 63;
g = 54;
b = 33;
break;
case 216:
// 0xFC6
r = 63;
g = 50;
b = 25;
break;
default:
// 0xEEE
r = 58;
g = 58;
b = 58;
}
_screen->setPaletteIndex(0x10, r, g, b);
}
} // End of namespace Kyra

83
engines/kyra/text/text.h Normal file
View File

@@ -0,0 +1,83 @@
/* 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/>.
*
*/
#ifndef KYRA_TEXT_H
#define KYRA_TEXT_H
#include "common/scummsys.h"
#include "kyra/graphics/screen.h"
namespace Kyra {
class KyraEngine_v1;
class TextDisplayer {
public:
TextDisplayer(KyraEngine_v1 *vm, Screen *screen);
virtual ~TextDisplayer() {}
int maxSubstringLen() const { return TALK_SUBSTRING_LEN; }
void setTalkCoords(uint16 y);
int getCenterStringX(const Common::String &str, int x1, int x2);
int getCharLength(const char *str, int len);
int dropCRIntoString(char *str, int offs);
virtual char *preprocessString(const char *str);
int buildMessageSubstrings(const char *str);
int getWidestLineWidth(int linesCount);
virtual void calcWidestLineBounds(int &x1, int &x2, int w, int cx);
virtual void restoreTalkTextMessageBkgd(int srcPage, int dstPage);
void printTalkTextMessage(const char *text, int x, int y, uint8 color, int srcPage, int dstPage);
virtual void printText(const Common::String &str, int x, int y, uint8 c0, uint8 c1, uint8 c2);
void printCharacterText(const char *text, int8 charNum, int charX);
uint16 _talkMessageY;
uint16 _talkMessageH;
int _langExtraSpacing;
bool printed() const { return _talkMessagePrinted; }
protected:
Screen *_screen;
KyraEngine_v1 *_vm;
struct TalkCoords {
uint16 y, x, w;
};
// TODO: AMIGA and LoK specific, move to a better location
void setTextColor(uint8 color);
enum {
TALK_SUBSTRING_LEN = 80,
TALK_SUBSTRING_NUM = 6
};
char _talkBuffer[1040];
char _talkSubstrings[TALK_SUBSTRING_LEN * TALK_SUBSTRING_NUM];
TalkCoords _talkCoords;
bool _talkMessagePrinted;
char _lineBreakChar;
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,251 @@
/* 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/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/engine/eob.h"
#include "kyra/graphics/screen_eob.h"
#include "kyra/graphics/screen_eob_segacd.h"
#include "kyra/text/text_eob_segacd.h"
namespace Kyra {
TextDisplayer_SegaCD::TextDisplayer_SegaCD(EoBEngine *engine, Screen_EoB *scr) : TextDisplayer_rpg(engine, scr), _engine(engine), _screen(scr), _renderer(scr->sega_getRenderer()),
_curDim(0), _textColor(0xFF), _curPosY(0), _curPosX(0) {
assert(_renderer);
_msgRenderBufferSize = 320 * 48;
_msgRenderBuffer = new uint8[_msgRenderBufferSize]();
}
TextDisplayer_SegaCD::~TextDisplayer_SegaCD() {
delete[] _msgRenderBuffer;
}
void TextDisplayer_SegaCD::printDialogueText(int id, const char *string1, const char *string2) {
if (string1 && string2) {
_engine->runDialogue(id, 2, 2, string1, string2);
} else {
_screen->hideMouse();
_engine->seq_segaPlaySequence(id);
_screen->showMouse();
}
}
void TextDisplayer_SegaCD::printDialogueText(const char *str, bool wait) {
int cs = _screen->setFontStyles(Screen::FID_8_FNT, _vm->gameFlags().lang == Common::JA_JPN ? Font::kStyleNone : Font::kStyleFullWidth);
clearDim(_curDim);
if (wait) {
printShadedText(str, 32, 12);
_engine->resetSkipFlag();
_renderer->render(0);
_screen->updateScreen();
_engine->delay(500);
} else {
printShadedText(str, 0, 0);
_renderer->render(0);
_screen->updateScreen();
}
_screen->setFontStyles(Screen::FID_8_FNT, cs);
}
void TextDisplayer_SegaCD::printShadedText(const char *str, int x, int y, int textColor, int shadowColor, int pitchW, int pitchH, int marginRight, bool screenUpdate) {
const ScreenDim *s = &_dimTable[_curDim];
if (x == -1)
x = s->sx;
if (y == -1)
y = s->sy;
if (textColor == -1)
textColor = s->col1;
if (shadowColor == -1)
shadowColor = 0;
if (pitchW == -1)
pitchW = s->w;
if (pitchH == -1)
pitchH = s->h;
_screen->setTextMarginRight(pitchW - marginRight);
_screen->printShadedText(str, x, y, textColor, 0, shadowColor, pitchW >> 3);
if (!screenUpdate)
return;
if (s->column) {
for (int i = 0; i < (pitchH >> 3); ++i)
_screen->sega_loadTextBufferToVRAM(i * (pitchW << 2), ((s->line & 0x7FF) + i * s->column) << 5, pitchW << 2);
} else {
_screen->sega_loadTextBufferToVRAM(0, (s->line & 0x7FF) << 5, (pitchW * pitchH) >> 1);
}
}
int TextDisplayer_SegaCD::clearDim(int dim) {
int res = _curDim;
_curDim = dim;
_curPosY = _curPosX = 0;
const ScreenDim *s = &_dimTable[dim];
_renderer->memsetVRAM((s->line & 0x7FF) << 5, s->col2, (s->w * s->h) >> 1);
_screen->sega_clearTextBuffer(s->col2);
memset(_msgRenderBuffer, s->col2, _msgRenderBufferSize);
return res;
}
void TextDisplayer_SegaCD::displayText(char *str, ...) {
_screen->sega_setTextBuffer(_msgRenderBuffer, _msgRenderBufferSize);
int cs = _screen->setFontStyles(Screen::FID_8_FNT, Font::kStyleFullWidth);
_screen->setFontStyles(Screen::FID_8_FNT, cs | Font::kStyleFullWidth);
char tmp[3] = " ";
int posX = _curPosX;
bool updated = false;
va_list args;
va_start(args, str);
int tc = va_arg(args, int);
va_end(args);
if (tc != -1)
SWAP(_textColor, tc);
for (const char *pos = str; *pos; ) {
uint8 cmd = fetchCharacter(tmp, pos);
updated = false;
if (_dimTable[_curDim].h < _curPosY + _screen->getFontHeight()) {
_curPosY -= _screen->getFontHeight();
linefeed();
}
if (cmd == 6) {
_textColor = (uint8)*pos++;
} else if (cmd == 2) {
pos++;
} else if (cmd == 13) {
_curPosX = 0;
_curPosY += _screen->getFontHeight();
} else if (cmd == 9) {
_curPosX = posX;
_curPosY += _screen->getFontHeight();
} else {
if (((tmp[0] == ' ' || (tmp[0] == '\x81' && tmp[1] == '\x40')) && (_curPosX + _screen->getTextWidth(tmp) + _screen->getTextWidth((const char*)(pos), true) >= _dimTable[_curDim].w)) || (_curPosX + _screen->getTextWidth(tmp) >= _dimTable[_curDim].w)) {
// Skip space at the beginning of the new line
if (tmp[0] == ' ' || (tmp[0] == '\x81' && tmp[1] == '\x40'))
fetchCharacter(tmp, pos);
_curPosX = 0;
_curPosY += _screen->getFontHeight();
if (_dimTable[_curDim].h < _curPosY + _screen->getFontHeight()) {
_curPosY -= _screen->getFontHeight();
linefeed();
}
}
printShadedText(tmp, _curPosX, _curPosY, _textColor);
_curPosX += _screen->getTextWidth(tmp);
updated = true;
}
}
if (!updated)
printShadedText("", _curPosX, _curPosY, _textColor);
if (tc != -1)
SWAP(_textColor, tc);
_renderer->render(Screen_EoB::kSegaRenderPage);
_screen->setFontStyles(Screen::FID_8_FNT, cs);
_screen->copyRegion(8, 176, 8, 176, 280, 24, Screen_EoB::kSegaRenderPage, 0, Screen::CR_NO_P_CHECK);
_screen->sega_setTextBuffer(0, 0);
}
uint8 TextDisplayer_SegaCD::fetchCharacter(char *dest, const char *&src) {
uint8 c = (uint8)*src++;
if (c <= (uint8)'\r') {
dest[0] = '\0';
return c;
}
dest[0] = (char)c;
dest[1] = (c <= 0x7F || (c >= 0xA1 && c <= 0xDF)) ? '\0' : *src++;
return 0;
}
void TextDisplayer_SegaCD::linefeed() {
copyTextBufferLine(_screen->getFontHeight(), 0, (_dimTable[_curDim].h & ~7) - _screen->getFontHeight(), _dimTable[_curDim].w >> 3);
clearTextBufferLine(_screen->getFontHeight(), _screen->getFontHeight(), _dimTable[_curDim].w >> 3, _dimTable[_curDim].col2);
}
void TextDisplayer_SegaCD::clearTextBufferLine(uint16 y, uint16 lineHeight, uint16 pitch, uint8 col) {
uint32 *dst = (uint32*)(_msgRenderBuffer + (((y >> 3) * pitch) << 5) + ((y & 7) << 2));
int ln = y;
uint32 c = col | (col << 8) | (col << 16) | (col << 24);
while (lineHeight--) {
uint32 *dst2 = dst;
for (uint16 w = pitch; w; --w) {
*dst = c;
dst += 8;
}
dst = dst2 + 1;
if (((++ln) & 7) == 0)
dst += ((pitch - 1) << 3);
}
}
void TextDisplayer_SegaCD::copyTextBufferLine(uint16 srcY, uint16 dstY, uint16 lineHeight, uint16 pitch) {
uint32 *src = (uint32*)(_msgRenderBuffer + (((srcY >> 3) * pitch) << 5) + ((srcY & 7) << 2));
uint32 *dst = (uint32*)(_msgRenderBuffer + (((dstY >> 3) * pitch) << 5) + ((dstY & 7) << 2));
int src_ln = srcY;
int dst_ln = dstY;
while (lineHeight--) {
uint32 *src2 = src;
uint32 *dst2 = dst;
for (uint16 w = pitch; w; --w) {
*dst = *src;
src += 8;
dst += 8;
}
src = src2 + 1;
dst = dst2 + 1;
if (((++dst_ln) & 7) == 0)
dst += ((pitch - 1) << 3);
if (((++src_ln) & 7) == 0)
src += ((pitch - 1) << 3);
}
}
const ScreenDim TextDisplayer_SegaCD::_dimTable[6] = {
{ 0x0001, 0x0017, 0x0118, 0x0018, 0xff, 0x44, 0x2597, 0x0000 },
{ 0x0012, 0x0009, 0x00a0, 0x0080, 0xff, 0x99, 0x0153, 0x0028 },
{ 0x0001, 0x0014, 0x0130, 0x0030, 0xff, 0xee, 0xe51c, 0x0000 },
{ 0x0001, 0x0017, 0x00D0, 0x0030, 0xff, 0x00, 0x0461, 0x0000 },
{ 0x0000, 0x0000, 0x00F0, 0x0100, 0xff, 0x00, 0x600A, 0x0000 },
{ 0x0000, 0x0000, 0x0140, 0x00B0, 0xff, 0x11, 0x4001, 0x0000 }
};
} // End of namespace Kyra
#endif // ENABLE_EOB

View File

@@ -0,0 +1,70 @@
/* 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/>.
*
*/
#ifdef ENABLE_EOB
#ifndef KYRA_TEXT_EOB_SEGACD_H
#define KYRA_TEXT_EOB_SEGACD_H
#include "kyra/text/text_rpg.h"
namespace Kyra {
class EoBEngine;
class Screen_EoB;
class TextDisplayer_SegaCD : public TextDisplayer_rpg {
public:
TextDisplayer_SegaCD(EoBEngine *engine, Screen_EoB *scr);
virtual ~TextDisplayer_SegaCD();
void printDialogueText(int id, const char *string1, const char *string2) override;
void printDialogueText(const char *str, bool wait = false) override;
void printShadedText(const char *str, int x = -1, int y = -1, int textColor = -1, int shadowColor = -1, int pitchW = -1, int pitchH = -1, int marginRight = 0, bool screenUpdate = true) override;
int clearDim(int dim) override;
private:
void displayText(char *str, ...) override;
uint8 fetchCharacter(char *dest, const char *&src);
void linefeed();
void clearTextBufferLine(uint16 y, uint16 lineHeight, uint16 pitch, uint8 col);
void copyTextBufferLine(uint16 srcY, uint16 dstY, uint16 lineHeight, uint16 pitch);
Screen_EoB *_screen;
SegaRenderer *_renderer;
EoBEngine *_engine;
uint8 *_msgRenderBuffer;
uint32 _msgRenderBufferSize;
int _curDim;
int _curPosY;
int _curPosX;
int _textColor;
static const ScreenDim _dimTable[6];
};
} // End of namespace Kyra
#endif
#endif // ENABLE_EOB

View File

@@ -0,0 +1,667 @@
/* 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 "kyra/text/text_hof.h"
#include "kyra/resource/resource.h"
#include "common/system.h"
namespace Kyra {
TextDisplayer_HoF::TextDisplayer_HoF(KyraEngine_HoF *vm, Screen_v2 *screen)
: TextDisplayer(vm, screen), _vm(vm) {
}
void TextDisplayer_HoF::backupTalkTextMessageBkgd(int srcPage, int dstPage) {
_screen->copyRegion(_talkCoords.x, _talkMessageY, 0, 144, _talkCoords.w, _talkMessageH, srcPage, dstPage);
}
void TextDisplayer_HoF::restoreTalkTextMessageBkgd(int srcPage, int dstPage) {
_screen->copyRegion(0, 144, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, srcPage, dstPage);
}
void TextDisplayer_HoF::restoreScreen() {
_vm->restorePage3();
_vm->drawAnimObjects();
_screen->copyRegion(_talkCoords.x, _talkMessageY, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, 2, 0, Screen::CR_NO_P_CHECK);
_vm->flagAnimObjsForRefresh();
_vm->refreshAnimObjects(0);
}
void TextDisplayer_HoF::printCustomCharacterText(const char *text, int x, int y, uint8 c1, int srcPage, int dstPage) {
text = preprocessString(text);
int lineCount = buildMessageSubstrings(text);
int w = getWidestLineWidth(lineCount);
int h = lineCount * _vm->_lineHeight;
y = MAX(0, y - (lineCount * _vm->_lineHeight));
int x1 = 0, x2 = 0;
calcWidestLineBounds(x1, x2, w, x);
_talkCoords.x = x1;
_talkCoords.w = w+2;
_talkCoords.y = y;
_talkMessageY = y;
_talkMessageH = h;
backupTalkTextMessageBkgd(srcPage, dstPage);
int curPageBackUp = _screen->_curPage;
_screen->_curPage = srcPage;
if (_vm->textEnabled()) {
for (int i = 0; i < lineCount; ++i) {
const char *msg = &_talkSubstrings[i * TALK_SUBSTRING_LEN];
if (i == 0 || _vm->gameFlags().lang != Common::ZH_TWN)
x = getCenterStringX(msg, x1, x2);
printText(msg, x, i * _vm->_lineHeight + _talkMessageY, c1, 0xCF, 0);
}
}
_screen->_curPage = curPageBackUp;
}
char *TextDisplayer_HoF::preprocessString(const char *str) {
if (str != _talkBuffer) {
assert(strlen(str) < sizeof(_talkBuffer) - 1);
Common::strlcpy(_talkBuffer, str, sizeof(_talkBuffer));
}
if (_vm->gameFlags().lang == Common::ZH_TWN)
return _talkBuffer;
char *p = _talkBuffer;
while (*p) {
if (*p == '\r')
return _talkBuffer;
++p;
}
p = _talkBuffer;
Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT);
_screen->_charSpacing = -2;
int textWidth = _screen->getTextWidth(p);
_screen->_charSpacing = 0;
int maxTextWidth = (_vm->language() == 0) ? 176 : 240;
if (textWidth > maxTextWidth) {
if (textWidth > (maxTextWidth*2)) {
int count = getCharLength(p, textWidth / 3);
int offs = dropCRIntoString(p, count);
p += count + offs;
_screen->_charSpacing = -2;
textWidth = _screen->getTextWidth(p);
_screen->_charSpacing = 0;
count = getCharLength(p, textWidth / 2);
dropCRIntoString(p, count);
} else {
int count = getCharLength(p, textWidth / 2);
dropCRIntoString(p, count);
}
}
_screen->setFont(curFont);
return _talkBuffer;
}
void TextDisplayer_HoF::calcWidestLineBounds(int &x1, int &x2, int w, int x) {
x1 = x;
x1 -= (w >> 1);
x2 = x1 + w + 1;
if (x1 + w >= 311)
x1 = 311 - w - 1;
if (x1 < 8)
x1 = 8;
x2 = x1 + w + 1;
}
#pragma mark -
int KyraEngine_HoF::chatGetType(const char *str) {
str += strlen(str);
--str;
switch (*str) {
case '!':
return 2;
case ')':
return -1;
case '?':
return 1;
default:
return 0;
}
}
int KyraEngine_HoF::chatCalcDuration(const Common::String &str) {
static const uint8 durationMultiplicator[] = { 16, 14, 12, 10, 8, 8, 7, 6, 5, 4 };
int duration = str.size();
duration *= _flags.isTalkie ? 8 : durationMultiplicator[(_configTextspeed / 10)];
return MAX<int>(duration, 120);
}
void KyraEngine_HoF::objectChat(const Common::String &str, int object, int vocHigh, int vocLow) {
setNextIdleAnimTimer();
_chatVocHigh = _chatVocLow = -1;
objectChatInit(str, object, vocHigh, vocLow);
_chatText = str;
_chatObject = object;
int chatType = chatGetType(str.c_str());
if (chatType == -1) {
_chatIsNote = true;
chatType = 0;
}
if (_mainCharacter.facing > 7)
_mainCharacter.facing = 5;
static const uint8 talkScriptTable[] = {
6, 7, 8,
3, 4, 5,
3, 4, 5,
0, 1, 2,
0, 1, 2,
0, 1, 2,
3, 4, 5,
3, 4, 5
};
assert(_mainCharacter.facing * 3 + chatType < ARRAYSIZE(talkScriptTable));
int script = talkScriptTable[_mainCharacter.facing * 3 + chatType];
static const char *const chatScriptFilenames[] = {
"_Z1FSTMT.EMC",
"_Z1FQUES.EMC",
"_Z1FEXCL.EMC",
"_Z1SSTMT.EMC",
"_Z1SQUES.EMC",
"_Z1SEXCL.EMC",
"_Z1BSTMT.EMC",
"_Z1BQUES.EMC",
"_Z1BEXCL.EMC"
};
objectChatProcess(chatScriptFilenames[script]);
_chatIsNote = false;
_text->restoreScreen();
_mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing];
updateCharacterAnim(0);
_chatText = "";
_chatObject = -1;
setNextIdleAnimTimer();
}
void KyraEngine_HoF::objectChatInit(const Common::String &str0, int object, int vocHigh, int vocLow) {
Common::String str = _text->preprocessString(str0.c_str());
int lineNum = _text->buildMessageSubstrings(str.c_str());
int yPos = 0, xPos = 0;
if (!object) {
int scale = getScale(_mainCharacter.x1, _mainCharacter.y1);
yPos = _mainCharacter.y1 - ((_mainCharacter.height * scale) >> 8) - 8;
xPos = _mainCharacter.x1;
} else {
yPos = _talkObjectList[object].y;
xPos = _talkObjectList[object].x;
}
yPos -= lineNum * _lineHeight;
yPos = MAX(yPos, 0);
_text->_talkMessageY = yPos;
_text->_talkMessageH = lineNum * _lineHeight;
int width = _text->getWidestLineWidth(lineNum);
_text->calcWidestLineBounds(xPos, yPos, width, xPos);
_text->_talkCoords.x = xPos;
_text->_talkCoords.w = width + 2;
restorePage3();
_text->backupTalkTextMessageBkgd(2, 2);
_chatTextEnabled = textEnabled();
if (_chatTextEnabled) {
objectChatPrintText(str, object);
_chatEndTime = _system->getMillis() + chatCalcDuration(str) * _tickLength;
} else {
_chatEndTime = _system->getMillis();
}
if (speechEnabled()) {
_chatVocHigh = vocHigh;
_chatVocLow = vocLow;
} else {
_chatVocHigh = _chatVocLow = -1;
}
}
void KyraEngine_HoF::objectChatPrintText(const Common::String &str0, int object) {
int c1 = _talkObjectList[object].color;
Common::String str = _text->preprocessString(str0.c_str());
int lineNum = _text->buildMessageSubstrings(str.c_str());
int maxWidth = _text->getWidestLineWidth(lineNum);
int x = (object == 0) ? _mainCharacter.x1 : _talkObjectList[object].x;
int cX1 = 0, cX2 = 0;
_text->calcWidestLineBounds(cX1, cX2, maxWidth, x);
for (int i = 0; i < lineNum; ++i) {
str = Common::String(&_text->_talkSubstrings[i*_text->maxSubstringLen()]);
int y = _text->_talkMessageY + i * _lineHeight;
if (i == 0 || _flags.lang != Common::ZH_TWN)
x = _text->getCenterStringX(str, cX1, cX2);
_text->printText(str, x, y, c1, 0xCF, 0);
}
}
void KyraEngine_HoF::objectChatProcess(const char *script) {
memset(&_chatScriptData, 0, sizeof(_chatScriptData));
memset(&_chatScriptState, 0, sizeof(_chatScriptState));
_emc->load(script, &_chatScriptData, &_opcodesAnimation);
_emc->init(&_chatScriptState, &_chatScriptData);
_emc->start(&_chatScriptState, 0);
while (_emc->isValid(&_chatScriptState))
_emc->run(&_chatScriptState);
_animShapeFilename[2] = _characterShapeFile + '0';
uint8 *shapeBuffer = _res->fileData(_animShapeFilename, nullptr);
if (shapeBuffer) {
int shapeCount = initAnimationShapes(shapeBuffer);
if (_chatVocHigh >= 0) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
objectChatWaitToFinish();
uninitAnimationShapes(shapeCount, shapeBuffer);
} else {
warning("couldn't load file '%s'", _animShapeFilename);
}
_emc->unload(&_chatScriptData);
}
void KyraEngine_HoF::objectChatWaitToFinish() {
int charAnimFrame = _mainCharacter.animFrame;
setCharacterAnimDim(_animShapeWidth, _animShapeHeight);
_emc->init(&_chatScriptState, &_chatScriptData);
_emc->start(&_chatScriptState, 1);
bool running = true;
const uint32 endTime = _chatEndTime;
resetSkipFlag();
while (running && !shouldQuit()) {
if (!_emc->isValid(&_chatScriptState))
_emc->start(&_chatScriptState, 1);
_animNeedUpdate = false;
while (!_animNeedUpdate && _emc->isValid(&_chatScriptState))
_emc->run(&_chatScriptState);
int curFrame = _animNewFrame;
uint32 delayTime = _animDelayTime;
if (!_chatIsNote)
_mainCharacter.animFrame = 33 + curFrame;
updateCharacterAnim(0);
uint32 nextFrame = _system->getMillis() + delayTime * _tickLength;
while (_system->getMillis() < nextFrame && !shouldQuit()) {
updateWithText();
const uint32 curTime = _system->getMillis();
if ((textEnabled() && curTime > endTime) || (speechEnabled() && !textEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
resetSkipFlag();
nextFrame = curTime;
running = false;
}
delay(10);
}
}
_mainCharacter.animFrame = charAnimFrame;
updateCharacterAnim(0);
resetCharacterAnimDim();
}
void KyraEngine_HoF::startDialogue(int dlgIndex) {
updateDlgBuffer();
int csEntry, vocH, unused1, unused2;
loadDlgHeader(csEntry, vocH, unused1, unused2);
int s = _conversationState[dlgIndex][csEntry];
uint8 bufferIndex = 8;
if (s == -1) {
bufferIndex += (dlgIndex * 6);
_conversationState[dlgIndex][csEntry] = 0;
} else if (!s || s == 2) {
bufferIndex += (dlgIndex * 6 + 2);
_conversationState[dlgIndex][csEntry] = 1;
} else {
bufferIndex += (dlgIndex * 6 + 4);
_conversationState[dlgIndex][csEntry] = 2;
}
int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
processDialogue(offs, vocH, csEntry);
}
void KyraEngine_HoF::zanthSceneStartupChat() {
int lowest = _flags.isTalkie ? 6 : 5;
int tableIndex = _mainCharacter.sceneId - READ_LE_UINT16(&_ingameTalkObjIndex[lowest + _newChapterFile]);
if (queryGameFlag(0x159) || _newSceneDlgState[tableIndex])
return;
int csEntry, vocH, scIndex1, scIndex2;
updateDlgBuffer();
loadDlgHeader(csEntry, vocH, scIndex1, scIndex2);
uint8 bufferIndex = 8 + scIndex1 * 6 + scIndex2 * 4 + tableIndex * 2;
int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
processDialogue(offs, vocH, csEntry);
_newSceneDlgState[tableIndex] = 1;
}
void KyraEngine_HoF::randomSceneChat() {
int lowest = _flags.isTalkie ? 6 : 5;
int tableIndex = (_mainCharacter.sceneId - READ_LE_UINT16(&_ingameTalkObjIndex[lowest + _newChapterFile])) << 2;
if (queryGameFlag(0x164))
return;
int csEntry, vocH, scIndex1, unused;
updateDlgBuffer();
loadDlgHeader(csEntry, vocH, scIndex1, unused);
if (_chatAltFlag) {
_chatAltFlag = 0;
tableIndex += 2;
} else {
_chatAltFlag = 1;
}
uint8 bufferIndex = 8 + scIndex1 * 6 + tableIndex;
int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
processDialogue(offs, vocH, csEntry);
}
void KyraEngine_HoF::updateDlgBuffer() {
static const char suffixTalkie[] = "EFG";
static const char suffixTowns[] = "G J";
if (_currentChapter == _npcTalkChpIndex && _mainCharacter.dlgIndex == _npcTalkDlgIndex)
return;
_npcTalkChpIndex = _currentChapter;
_npcTalkDlgIndex = _mainCharacter.dlgIndex;
Common::String filename = Common::String::format("CH%.02d-S%.02d.DL", _currentChapter, _npcTalkDlgIndex);
const char *suffix = _flags.isTalkie ? suffixTalkie : suffixTowns;
if (_flags.platform != Common::kPlatformDOS || _flags.isTalkie)
filename += suffix[_lang];
else
filename += 'G';
delete[] _dlgBuffer;
_dlgBuffer = _res->fileData(filename.c_str(), nullptr);
}
void KyraEngine_HoF::loadDlgHeader(int &csEntry, int &vocH, int &scIndex1, int &scIndex2) {
csEntry = READ_LE_UINT16(_dlgBuffer);
vocH = READ_LE_UINT16(_dlgBuffer + 2);
scIndex1 = READ_LE_UINT16(_dlgBuffer + 4);
scIndex2 = READ_LE_UINT16(_dlgBuffer + 6);
}
void KyraEngine_HoF::processDialogue(int dlgOffset, int vocH, int csEntry) {
int activeTimSequence = -1;
int nextTimSequence = -1;
int cmd = 0;
int vocHi = -1;
int vocLo = -1;
bool loop = true;
int offs = dlgOffset;
_screen->hideMouse();
while (loop) {
cmd = READ_LE_UINT16(_dlgBuffer + offs);
offs += 2;
nextTimSequence = READ_LE_UINT16(&_ingameTalkObjIndex[cmd]);
if (nextTimSequence == 10) {
if (queryGameFlag(0x3E))
nextTimSequence = 14;
if (queryGameFlag(0x3F))
nextTimSequence = 15;
if (queryGameFlag(0x40))
nextTimSequence = 16;
}
if (nextTimSequence == 27 && _mainCharacter.sceneId == 34)
nextTimSequence = 41;
if (queryGameFlag(0x72)) {
if (nextTimSequence == 18)
nextTimSequence = 43;
else if (nextTimSequence == 19)
nextTimSequence = 44;
}
if (_mainCharacter.x1 > 160) {
if (nextTimSequence == 4)
nextTimSequence = 46;
else if (nextTimSequence == 5)
nextTimSequence = 47;
}
if (cmd == 10) {
loop = false;
} else if (cmd == 4) {
csEntry = READ_LE_UINT16(_dlgBuffer + offs);
setDlgIndex(csEntry);
offs += 2;
} else {
Common::String str;
if (!_flags.isTalkie || cmd == 11) {
int len = READ_LE_UINT16(_dlgBuffer + offs);
offs += 2;
if (_flags.isTalkie) {
vocLo = READ_LE_UINT16(_dlgBuffer + offs);
offs += 2;
}
str = Common::String((const char *) _dlgBuffer + offs, len);
offs += len;
if (_flags.isTalkie)
continue;
} else if (_flags.isTalkie) {
int len = READ_LE_UINT16(_dlgBuffer + offs);
offs += 2;
static const int irnv[] = { 91, 105, 110, 114, 118 };
vocHi = irnv[vocH - 1] + csEntry;
vocLo = READ_LE_UINT16(_dlgBuffer + offs);
offs += 2;
str = Common::String((const char *) _dlgBuffer + offs, len);
offs += len;
}
if (!str.empty()) {
if ((!_flags.isTalkie && cmd == 11) || (_flags.isTalkie && cmd == 12)) {
if (activeTimSequence > -1) {
deinitTalkObject(activeTimSequence);
activeTimSequence = -1;
}
objectChat(str, 0, vocHi, vocLo);
} else {
if (activeTimSequence != nextTimSequence) {
if (activeTimSequence > -1)
deinitTalkObject(activeTimSequence);
initTalkObject(nextTimSequence);
activeTimSequence = nextTimSequence;
}
npcChatSequence(str, nextTimSequence, vocHi, vocLo);
}
}
}
}
if (activeTimSequence > -1)
deinitTalkObject(activeTimSequence);
_screen->showMouse();
}
void KyraEngine_HoF::initTalkObject(int index) {
TalkObject &object = _talkObjectList[index];
Common::String STAFilename = Common::String(object.filename) + "_STA.TIM";
_TLKFilename = Common::String(object.filename) + "_TLK.TIM";
Common::String ENDFilename = Common::String(object.filename) + "_END.TIM";
_currentTalkSections.STATim = _tim->load(STAFilename.c_str(), &_timOpcodes);
_currentTalkSections.TLKTim = _tim->load(_TLKFilename.c_str(), &_timOpcodes);
_currentTalkSections.ENDTim = _tim->load(ENDFilename.c_str(), &_timOpcodes);
if (object.scriptId != -1) {
_specialSceneScriptStateBackup[object.scriptId] = _specialSceneScriptState[object.scriptId];
_specialSceneScriptState[object.scriptId] = 1;
}
if (_currentTalkSections.STATim) {
_tim->resetFinishedFlag();
while (!shouldQuit() && !_tim->finished()) {
_tim->exec(_currentTalkSections.STATim, false);
if (!_chatText.empty())
updateWithText();
else
update();
delay(10);
}
}
}
void KyraEngine_HoF::deinitTalkObject(int index) {
TalkObject &object = _talkObjectList[index];
if (_currentTalkSections.ENDTim) {
_tim->resetFinishedFlag();
while (!shouldQuit() && !_tim->finished()) {
_tim->exec(_currentTalkSections.ENDTim, false);
if (!_chatText.empty())
updateWithText();
else
update();
delay(10);
}
}
if (object.scriptId != -1)
_specialSceneScriptState[object.scriptId] = _specialSceneScriptStateBackup[object.scriptId];
_tim->unload(_currentTalkSections.STATim);
_tim->unload(_currentTalkSections.TLKTim);
_tim->unload(_currentTalkSections.ENDTim);
}
void KyraEngine_HoF::npcChatSequence(const Common::String &str, int objectId, int vocHigh, int vocLow) {
_chatText = str;
_chatObject = objectId;
objectChatInit(str, objectId, vocHigh, vocLow);
if (!_currentTalkSections.TLKTim)
_currentTalkSections.TLKTim = _tim->load(_TLKFilename.c_str(), &_timOpcodes);
setNextIdleAnimTimer();
uint32 ct = chatCalcDuration(str);
uint32 time = _system->getMillis();
_chatEndTime = time + (3 + ct) * _tickLength;
uint32 chatAnimEndTime = time + (3 + (ct >> 1)) * _tickLength;
if (_chatVocHigh >= 0) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
while (((textEnabled() && _chatEndTime > _system->getMillis()) || (speechEnabled() && snd_voiceIsPlaying())) && !(shouldQuit() || skipFlag())) {
if ((!speechEnabled() && chatAnimEndTime > _system->getMillis()) || (speechEnabled() && snd_voiceIsPlaying())) {
_tim->resetFinishedFlag();
while (!_tim->finished() && !skipFlag() && !shouldQuit()) {
if (_currentTalkSections.TLKTim)
_tim->exec(_currentTalkSections.TLKTim, false);
else
_tim->resetFinishedFlag();
updateWithText();
delay(10);
}
if (_currentTalkSections.TLKTim)
_tim->stopCurFunc();
}
updateWithText();
}
resetSkipFlag();
_tim->unload(_currentTalkSections.TLKTim);
_text->restoreScreen();
_chatText = "";
_chatObject = -1;
setNextIdleAnimTimer();
}
void KyraEngine_HoF::setDlgIndex(int dlgIndex) {
if (dlgIndex == _mainCharacter.dlgIndex)
return;
memset(_newSceneDlgState, 0, 32);
for (int i = 0; i < 19; i++)
memset(_conversationState[i], -1, 14);
_chatAltFlag = false;
_mainCharacter.dlgIndex = dlgIndex;
}
} // End of namespace Kyra

View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
#ifndef KYRA_TEXT_HOF_H
#define KYRA_TEXT_HOF_H
#include "kyra/text/text.h"
namespace Kyra {
class Screen_v2;
class KyraEngine_HoF;
class TextDisplayer_HoF : public TextDisplayer {
friend class KyraEngine_HoF;
public:
TextDisplayer_HoF(KyraEngine_HoF *vm, Screen_v2 *screen);
void backupTalkTextMessageBkgd(int srcPage, int dstPage);
void restoreTalkTextMessageBkgd(int srcPage, int dstPage) override;
void restoreScreen();
void printCustomCharacterText(const char *src, int x, int y, uint8 c1, int srcPage, int dstPage);
char *preprocessString(const char *str) override;
void calcWidestLineBounds(int &x1, int &x2, int w, int x) override;
private:
KyraEngine_HoF *_vm;
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,428 @@
/* 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 "kyra/text/text.h"
#include "kyra/engine/kyra_lok.h"
#include "kyra/graphics/animator_lok.h"
#include "kyra/engine/sprites.h"
#include "kyra/engine/timer.h"
#include "common/system.h"
namespace Kyra {
void KyraEngine_LoK::waitForChatToFinish(int vocFile, int chatDuration, const char *chatStr, uint8 charNum, const bool printText) {
bool hasUpdatedNPCs = false;
bool runLoop = true;
uint8 currPage;
uint32 timeToEnd = strlen(chatStr) * 8 * _tickLength + _system->getMillis();
bool textOnly = textEnabled() && vocFile == - 1;
if (textOnly && chatDuration != -1) {
switch (_configTextspeed) {
case 0:
chatDuration *= 2;
break;
case 2:
chatDuration /= 4;
break;
case 3:
chatDuration = -1;
break;
default:
break;
}
}
if (chatDuration != -1)
chatDuration *= _tickLength;
if (vocFile != -1)
snd_playVoiceFile(vocFile);
_timer->disable(14);
_timer->disable(18);
_timer->disable(19);
uint32 timeAtStart = _system->getMillis();
uint32 loopStart;
while (runLoop) {
loopStart = _system->getMillis();
if (_currentCharacter->sceneId == 210)
if (seq_playEnd())
break;
if (_system->getMillis() > timeToEnd && !hasUpdatedNPCs) {
hasUpdatedNPCs = true;
_timer->disable(15);
_currHeadShape = 4;
_animator->animRefreshNPC(0);
_animator->animRefreshNPC(_talkingCharNum);
if (_charSayUnk2 != -1) {
_animator->sprites()[_charSayUnk2].active = 0;
_sprites->_anims[_charSayUnk2].play = false;
_charSayUnk2 = -1;
}
}
_timer->update();
_sprites->updateSceneAnims();
_animator->restoreAllObjectBackgrounds();
_animator->preserveAnyChangedBackgrounds();
_animator->prepDrawAllObjects();
if (printText) {
currPage = _screen->_curPage;
_screen->_curPage = 2;
_text->printCharacterText(chatStr, charNum, _characterList[charNum].x1);
_screen->_curPage = currPage;
}
_animator->copyChangedObjectsForward(0);
updateTextFade();
if (((chatDuration < (int)(_system->getMillis() - timeAtStart)) && chatDuration != -1 && printText && textOnly) || (!textOnly && !snd_voiceIsPlaying()))
break;
uint32 nextTime = loopStart + _tickLength;
while (_system->getMillis() < nextTime) {
updateInput();
if (skipFlag()) {
runLoop = false;
break;
}
if (nextTime - _system->getMillis() >= 10) {
_system->delayMillis(10);
_screen->updateBackendScreen(true);
}
}
}
if (skipFlag()) {
resetSkipFlag();
snd_stopVoice();
}
_timer->enable(14);
_timer->enable(15);
_timer->enable(18);
_timer->enable(19);
}
void KyraEngine_LoK::endCharacterChat(int8 charNum, int16 convoInitialized) {
_talkHeadAnimCharNum = -1;
if (charNum > 4 && charNum < 11) {
_animator->sprites()[_disabledTalkAnimObject].active = 1;
_sprites->_anims[_disabledTalkAnimObject].play = true;
_animator->sprites()[_enabledTalkAnimObject].active = 0;
_sprites->_anims[_enabledTalkAnimObject].play = false;
}
if (convoInitialized != 0) {
_talkingCharNum = -1;
if (_currentCharacter->currentAnimFrame != 88)
_currentCharacter->currentAnimFrame = 7;
_animator->animRefreshNPC(0);
_animator->updateAllObjectShapes();
}
}
void KyraEngine_LoK::restoreChatPartnerAnimFrame(int8 charNum) {
_talkingCharNum = -1;
if (charNum > 0 && charNum < 5) {
_characterList[charNum].currentAnimFrame = _currentChatPartnerBackupFrame;
_animator->animRefreshNPC(charNum);
}
if (_currentCharacter->currentAnimFrame != 88)
_currentCharacter->currentAnimFrame = 7;
_animator->animRefreshNPC(0);
_animator->updateAllObjectShapes();
}
void KyraEngine_LoK::backupChatPartnerAnimFrame(int8 charNum) {
_talkingCharNum = 0;
if (charNum < 5 && charNum > 0)
_currentChatPartnerBackupFrame = _characterList[charNum].currentAnimFrame;
if (_currentCharacter->currentAnimFrame != 88) {
_currentCharacter->currentAnimFrame = 16;
if (_scaleMode != 0)
_currentCharacter->currentAnimFrame = 7;
}
_animator->animRefreshNPC(0);
_animator->updateAllObjectShapes();
}
int8 KyraEngine_LoK::getChatPartnerNum() {
uint8 sceneTable[] = {0x2, 0x5, 0x2D, 0x7, 0x1B, 0x8, 0x22, 0x9, 0x30, 0x0A};
int pos = 0;
int partner = -1;
for (int i = 1; i < 6; i++) {
if (_currentCharacter->sceneId == sceneTable[pos]) {
partner = sceneTable[pos + 1];
break;
}
pos += 2;
}
for (int i = 1; i < 5; i++) {
if (_characterList[i].sceneId == _currentCharacter->sceneId) {
partner = i;
break;
}
}
return partner;
}
int KyraEngine_LoK::initCharacterChat(int8 charNum) {
int returnValue = 0;
if (_talkingCharNum == -1) {
returnValue = 1;
_talkingCharNum = 0;
if (_currentCharacter->currentAnimFrame != 88) {
_currentCharacter->currentAnimFrame = 16;
if (_scaleMode != 0)
_currentCharacter->currentAnimFrame = 7;
}
_animator->animRefreshNPC(0);
_animator->updateAllObjectShapes();
}
_charSayUnk2 = -1;
_animator->flagAllObjectsForBkgdChange();
_animator->restoreAllObjectBackgrounds();
if (charNum > 4 && charNum < 11) {
const uint8 animDisableTable[] = { 3, 1, 1, 5, 0, 6 };
const uint8 animEnableTable[] = { 4, 2, 5, 6, 1, 7 };
_disabledTalkAnimObject = animDisableTable[charNum - 5];
_enabledTalkAnimObject = animEnableTable[charNum - 5];
_animator->sprites()[_disabledTalkAnimObject].active = 0;
_sprites->_anims[_disabledTalkAnimObject].play = false;
_animator->sprites()[_enabledTalkAnimObject].active = 1;
_sprites->_anims[_enabledTalkAnimObject].play = true;
_charSayUnk2 = _enabledTalkAnimObject;
}
_animator->flagAllObjectsForRefresh();
_animator->flagAllObjectsForBkgdChange();
_animator->preserveAnyChangedBackgrounds();
_talkHeadAnimCharNum = charNum;
return returnValue;
}
void KyraEngine_LoK::characterSays(int vocFile, const char *chatStr, int16 charNum, int16 chatDuration) {
uint8 startAnimFrames[] = { 0x10, 0x32, 0x56, 0x0, 0x0, 0x0 };
uint16 chatTicks;
int16 convoInitialized;
int8 chatPartnerNum;
if (_currentCharacter->sceneId == 210)
return;
snd_voiceWaitForFinish(true);
convoInitialized = initCharacterChat(charNum);
chatPartnerNum = getChatPartnerNum();
if (chatPartnerNum >= 0 && chatPartnerNum < 5)
backupChatPartnerAnimFrame(chatPartnerNum);
if (charNum < 5) {
if (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98 || _animator->_brandonScaleX == 0x100 || !_scaleMode) {
_characterList[charNum].currentAnimFrame = startAnimFrames[charNum];
_talkHeadAnimCharNum = charNum;
_talkingCharNum = charNum;
}
_animator->animRefreshNPC(charNum);
}
char *processedString = _text->preprocessString(chatStr);
int lineNum = _text->buildMessageSubstrings(processedString);
if (_flags.lang == Common::ZH_TWN)
lineNum = (strlen(chatStr) + 31) >> 5;
int16 yPos = _characterList[charNum].y1;
yPos -= ((_scaleTable[yPos] * _characterList[charNum].height) >> 8);
yPos -= 8;
yPos -= (lineNum * _screen->getFontHeight() + (lineNum - 1) * _screen->_lineSpacing);
_text->_talkMessageY = (_flags.lang == Common::ZH_TWN) ? CLIP<int>(yPos, 10, 80) : CLIP<int>(yPos, 11, 100);
_text->_talkMessageH = lineNum * _screen->getFontHeight() + (lineNum - 1) * _screen->_lineSpacing + _text->_langExtraSpacing;
const bool printText = textEnabled();
if (printText) {
_animator->restoreAllObjectBackgrounds();
_screen->copyRegion(8, _text->_talkMessageY, 8, 136, 304, _text->_talkMessageH, 2, 2);
_text->printCharacterText(processedString, charNum, _characterList[charNum].x1);
}
// This happens right at the beginning, when talking to the tree and can be seen in DOSBox, too.
// It will make the sentence stay basically forever. We just set it to
// the value from the other versions (probably some typo from the translators).
if (_flags.lang == Common::KO_KOR && chatDuration == -20)
chatDuration = -2;
if (chatDuration == -2)
chatTicks = strlen(processedString) * 9;
else
chatTicks = (uint16)chatDuration;
if (!speechEnabled())
vocFile = -1;
waitForChatToFinish(vocFile, (chatDuration == -1) ? -1 : chatTicks, chatStr, charNum, printText);
if (printText) {
_animator->restoreAllObjectBackgrounds();
_screen->copyRegion(8, 136, 8, _text->_talkMessageY, 304, _text->_talkMessageH, 2, 2);
_animator->preserveAllBackgrounds();
_animator->prepDrawAllObjects();
_screen->copyRegion(8, _text->_talkMessageY, 8, _text->_talkMessageY, 304, _text->_talkMessageH, 2, 0);
_animator->flagAllObjectsForRefresh();
_animator->copyChangedObjectsForward(0);
}
if (chatPartnerNum != -1 && chatPartnerNum < 5)
restoreChatPartnerAnimFrame(chatPartnerNum);
endCharacterChat(charNum, convoInitialized);
}
void KyraEngine_LoK::drawSentenceCommand(const char *sentence, int color) {
int boxY1 = 143;
int boxY2 = 152;
int col2 = _flags.platform == Common::kPlatformAmiga ? 19 : 12;
if (_flags.lang == Common::ZH_TWN || _flags.lang == Common::KO_KOR) {
boxY1 = 140;
boxY2 = _flags.lang == Common::KO_KOR ? 155 : 153;
col2 = 0;
}
_screen->fillRect(8, boxY1, 311, boxY2, _flags.platform == Common::kPlatformAmiga ? 19 : 12);
if (_flags.platform == Common::kPlatformAmiga) {
if (color != 19) {
_currSentenceColor[0] = 0x3F;
_currSentenceColor[1] = 0x3F;
_currSentenceColor[2] = 0x3F;
_screen->setInterfacePalette(_screen->getPalette(1),
_currSentenceColor[0], _currSentenceColor[1], _currSentenceColor[2]);
}
} else if (_startSentencePalIndex != color || _fadeText != false) {
_currSentenceColor[0] = _screen->getPalette(0)[765] = _screen->getPalette(0)[color * 3 + 0];
_currSentenceColor[1] = _screen->getPalette(0)[766] = _screen->getPalette(0)[color * 3 + 1];
_currSentenceColor[2] = _screen->getPalette(0)[767] = _screen->getPalette(0)[color * 3 + 2];
_screen->setScreenPalette(_screen->getPalette(0));
_startSentencePalIndex = color;
}
if (_flags.lang != Common::HE_ISR) {
_text->printText(sentence, 8, boxY1, 0xFF, col2, 0);
} else {
_screen->_charSpacing = -2;
_text->printText(sentence, 311 - _screen->getTextWidth(sentence), boxY1, 0xFF, col2, 0);
_screen->_charSpacing = 0;
}
setTextFadeTimerCountdown(15);
_fadeText = false;
}
void KyraEngine_LoK::updateSentenceCommand(const char *str1, const char *str2, int color) {
char sentenceCommand[500];
if (_flags.lang == Common::ZH_TWN) {
Common::strlcpy(sentenceCommand, str2 ? str2 : str1, sizeof(sentenceCommand));
if (str2)
Common::strlcat(sentenceCommand, str1, sizeof(sentenceCommand));
} else if (_flags.lang == Common::HE_ISR) {
if (str2)
Common::strlcpy(sentenceCommand, str2, sizeof(sentenceCommand));
Common::strlcat(sentenceCommand, str1, sizeof(sentenceCommand));
} else {
Common::strlcpy(sentenceCommand, str1, sizeof(sentenceCommand));
if (str2)
Common::strlcat(sentenceCommand, str2, sizeof(sentenceCommand));
}
drawSentenceCommand(sentenceCommand, color);
_screen->updateScreen();
}
void KyraEngine_LoK::updateTextFade() {
if (!_fadeText)
return;
bool finished = false;
for (int i = 0; i < 3; i++) {
if (_currSentenceColor[i] > 4)
_currSentenceColor[i] -= 4;
else if (_currSentenceColor[i]) {
_currSentenceColor[i] = 0;
finished = true;
}
}
if (_flags.platform == Common::kPlatformAmiga) {
_screen->setInterfacePalette(_screen->getPalette(1),
_currSentenceColor[0], _currSentenceColor[1], _currSentenceColor[2]);
} else {
_screen->getPalette(0)[765] = _currSentenceColor[0];
_screen->getPalette(0)[766] = _currSentenceColor[1];
_screen->getPalette(0)[767] = _currSentenceColor[2];
_screen->setScreenPalette(_screen->getPalette(0));
}
if (finished) {
_fadeText = false;
_startSentencePalIndex = -1;
}
}
} // End of namespace Kyra

View File

@@ -0,0 +1,363 @@
/* 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/>.
*
*/
#ifdef ENABLE_LOL
#include "kyra/text/text_lol.h"
#include "kyra/engine/lol.h"
#include "kyra/graphics/screen_lol.h"
#include "kyra/engine/timer.h"
#include "kyra/sound/sound.h"
#include "common/system.h"
namespace Kyra {
TextDisplayer_LoL::TextDisplayer_LoL(LoLEngine *engine, Screen_LoL *screenLoL) : TextDisplayer_rpg(engine, screenLoL),
_vm(engine), _screen(screenLoL), _scriptTextParameter(0) {
memset(_stringParameters, 0, 15 * sizeof(char *));
_buffer = new char[600]();
_waitButtonSpace = 0;
}
TextDisplayer_LoL::~TextDisplayer_LoL() {
delete[] _buffer;
}
void TextDisplayer_LoL::setupField(bool mode) {
if (_vm->textEnabled()) {
int y = 142;
int h = 37;
int stepY = 3;
int stepH = 1;
if (_vm->gameFlags().use16ColorMode) {
y = 140;
h = 39;
stepY = 4;
stepH = 2;
}
if (mode) {
_screen->copyRegionToBuffer(3, 0, 0, 320, 40, _vm->_pageBuffer1);
_screen->copyRegion(80, y, 0, 0, 240, h, 0, 3, Screen::CR_NO_P_CHECK);
_screen->copyRegionToBuffer(3, 0, 0, 320, 40, _vm->_pageBuffer2);
_screen->copyBlockToPage(3, 0, 0, 320, 40, _vm->_pageBuffer1);
} else {
_screen->setScreenDim(clearDim(4));
int cp = _screen->setCurPage(2);
_screen->copyRegionToBuffer(3, 0, 0, 320, 40, _vm->_pageBuffer1);
_screen->copyBlockToPage(3, 0, 0, 320, 40, _vm->_pageBuffer2);
_screen->copyRegion(0, 0, 80, y, 240, h, 3, _screen->_curPage, Screen::CR_NO_P_CHECK);
uint32 endTime = _vm->_system->getMillis();
for (int i = 177; i > 141; i--) {
endTime += _vm->_tickLength;
_screen->copyRegion(83, i - stepH + 1, 83, i - stepH, 235, stepY, 0, 0, Screen::CR_NO_P_CHECK);
_screen->copyRegion(83, i + 1, 83, i + 1, 235, 1, 2, 0, Screen::CR_NO_P_CHECK);
_vm->updateInput();
_screen->updateScreen();
_vm->delayUntil(endTime);
}
_screen->copyBlockToPage(3, 0, 0, 320, 200, _vm->_pageBuffer1);
_screen->setCurPage(cp);
_vm->_updateFlags &= 0xFFFD;
}
} else {
if (!mode)
_screen->setScreenDim(clearDim(4));
_vm->toggleSelectedCharacterFrame(1);
}
}
void TextDisplayer_LoL::expandField() {
uint8 *tmp = _vm->_pageBuffer1 + 13000;
if (_vm->textEnabled()) {
_vm->_fadeText = false;
_vm->_textColorFlag = 0;
_vm->_timer->disable(11);
_screen->setScreenDim(clearDim(3));
_screen->copyRegionToBuffer(3, 0, 0, 320, 10, tmp);
int y = 140;
int h = 3;
int stepH = 0;
if (_vm->gameFlags().use16ColorMode) {
y = 139;
h = 4;
stepH = 1;
}
_screen->copyRegion(83, y, 0, 0, 235, h, 0, 2, Screen::CR_NO_P_CHECK);
uint32 endTime = _vm->_system->getMillis();
for (int i = 140; i < 177; i++) {
endTime += _vm->_tickLength;
_screen->copyRegion(0, 0, 83, i - stepH, 235, h, 2, 0, Screen::CR_NO_P_CHECK);
_vm->updateInput();
_screen->updateScreen();
_vm->delayUntil(endTime);
}
_screen->copyBlockToPage(3, 0, 0, 320, 10, tmp);
_vm->_updateFlags |= 2;
} else {
clearDim(3);
_vm->toggleSelectedCharacterFrame(0);
}
}
void TextDisplayer_LoL::printDialogueText2(int dim, const char *str, EMCState *script, const uint16 *paramList, int16 paramIndex) {
int oldDim = 0;
if (dim == 3) {
if (_vm->_updateFlags & 2) {
oldDim = clearDim(4);
_textDimData[4].color1= _vm->gameFlags().use16ColorMode ? 0x33 : 254;
_textDimData[4].color2 = _screen->_curDim->col2;
} else {
oldDim = clearDim(3);
_textDimData[3].color1= _vm->gameFlags().use16ColorMode ? 0x33 : 192;
_textDimData[3].color2 = _screen->_curDim->col2;
if (!_vm->gameFlags().use16ColorMode)
_screen->copyColor(192, 254);
_vm->enableTimer(11);
_vm->_textColorFlag = 0;
_vm->_fadeText = false;
}
} else {
oldDim = _screen->curDimIndex();
_screen->setScreenDim(dim);
_lineCount = 0;
_textDimData[dim].color1= _vm->gameFlags().use16ColorMode ? 0x33 : 254;
_textDimData[dim].color2 = _screen->_curDim->col2;
}
int cp = _screen->setCurPage(0);
Screen::FontId of = _screen->setFont(_vm->gameFlags().lang == Common::Language::ZH_TWN ? Screen::FID_CHINESE_FNT : _pc98TextMode ? Screen::FID_SJIS_TEXTMODE_FNT : Screen::FID_9_FNT);
preprocessString(str, script, paramList, paramIndex);
_numCharsTotal = Common::strnlen(_dialogueBuffer, 2559);
displayText(_dialogueBuffer);
_screen->setScreenDim(oldDim);
_screen->setCurPage(cp);
_screen->setFont(of);
_lineCount = 0;
_vm->_fadeText = false;
}
void TextDisplayer_LoL::printMessage(uint16 type, const char *str, ...) {
static const uint8 textColors256[] = { 0xFE, 0xA2, 0x84, 0x97, 0x9F };
static const uint8 textColors16[] = { 0x33, 0xAA, 0x88, 0x55, 0x99 };
static const uint8 soundEffect[] = { 0x0B, 0x00, 0x2B, 0x1B, 0x00 };
const uint8 *textColors = _vm->gameFlags().use16ColorMode ? textColors16 : textColors256;
if (type & 4)
type &= ~4;
else
_vm->stopPortraitSpeechAnim();
int index = type & 0x7FFF;
assert(index < 5);
uint16 col = textColors[index];
int od = _screen->curDimIndex();
if (_vm->_updateFlags & 2) {
clearDim(4);
_textDimData[4].color1= col;
} else {
clearDim(3);
if (_vm->gameFlags().use16ColorMode) {
_textDimData[3].color1= col;
} else {
_screen->copyColor(192, col);
_textDimData[3].color1= 192;
}
_vm->enableTimer(11);
}
va_list args;
va_start(args, str);
vsnprintf((char *)_buffer, 240, str, args);
va_end(args);
displayText(_buffer);
_screen->setScreenDim(od);
_lineCount = 0;
if (!(type & 0x8000)) {
if (soundEffect[index])
_vm->sound()->playSoundEffect(soundEffect[index]);
}
_vm->_textColorFlag = index;
_vm->_fadeText = false;
}
void TextDisplayer_LoL::preprocessString(const char *str, EMCState *script, const uint16 *paramList, int16 paramIndex) {
char *dst = _dialogueBuffer;
for (const char *s = str; *s;) {
if (_vm->gameFlags().lang == Common::JA_JPN) {
uint8 c = *s;
if (c >= 0xE0 || (c > 0x80 && c < 0xA0)) {
*dst++ = *s++;
*dst++ = *s++;
continue;
}
}
if (*s != '%') {
*dst++ = *s++;
continue;
}
char para = *++s;
bool eos = false;
switch (para) {
case '\0':
eos = true;
break;
case '#':
para = *++s;
switch (para) {
case 'E':
case 'G':
case 'X':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 's':
case 'u':
case 'x':
break;
default:
eos = true;
}
break;
case ' ':
case '+':
case '-':
++s;
default:
break;
}
if (eos)
continue;
para = *s;
switch (para) {
case '\0':
eos = true;
break;
case '0':
++s;
break;
default:
while (para && para > 47 && para < 58)
para = *++s;
break;
}
if (eos)
continue;
para = *s++;
switch (para) {
case 'a':
Common::strlcpy(dst, Common::String::format("%d", _scriptTextParameter).c_str(), 2560 - (dst - _dialogueBuffer));
dst += Common::strnlen(dst, 2559 - (dst - _dialogueBuffer));
break;
case 'n':
if (script || paramList) {
Common::strlcpy(dst, _vm->_characters[script ? script->stack[script->sp + paramIndex] : paramList[paramIndex]].name, 2560 - (dst - _dialogueBuffer));
dst += Common::strnlen(dst, 2559 - (dst - _dialogueBuffer));
} else {
warning("TextDisplayer_LoL::preprocessString(): Missing replacement data for placeholder '%%%c'", para);
}
break;
case 's':
if (script || paramList) {
Common::strlcpy(dst, _vm->getLangString(script ? script->stack[script->sp + paramIndex] : paramList[paramIndex]), 2560 - (dst - _dialogueBuffer));
dst += Common::strnlen(dst, 2559 - (dst - _dialogueBuffer));
} else {
warning("TextDisplayer_LoL::preprocessString(): Missing replacement data for placeholder '%%%c'", para);
}
break;
case 'X':
case 'd':
case 'u':
case 'x':
if (script || paramList) {
Common::strlcpy(dst, Common::String::format("%d", script ? script->stack[script->sp + paramIndex] : paramList[paramIndex]).c_str(), 2560 - (dst - _dialogueBuffer));
dst += Common::strnlen(dst, 2559 - (dst - _dialogueBuffer));
} else {
warning("TextDisplayer_LoL::preprocessString(): Missing replacement data for placeholder '%%%c'", para);
}
break;
case '\0':
default:
continue;
}
}
*dst = 0;
}
KyraRpgEngine *TextDisplayer_LoL::vm() {
return _vm;
}
Screen *TextDisplayer_LoL::screen() {
return _screen;
}
void TextDisplayer_LoL::textPageBreak() {
_pageBreakString = _vm->getLangString(0x4073);
TextDisplayer_rpg::textPageBreak();
}
} // End of namespace Kyra
#endif // ENABLE_LOL

View File

@@ -0,0 +1,69 @@
/* 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/>.
*
*/
#ifndef KYRA_TEXT_LOL_H
#define KYRA_TEXT_LOL_H
#if defined(ENABLE_EOB) || defined(ENABLE_LOL)
#include "kyra/text/text_rpg.h"
#endif
#include "common/scummsys.h"
#ifdef ENABLE_LOL
namespace Kyra {
class Screen_LoL;
class LoLEngine;
struct EMCState;
class TextDisplayer_LoL : public TextDisplayer_rpg {
public:
TextDisplayer_LoL(LoLEngine *engine, Screen_LoL *screenLoL);
~TextDisplayer_LoL() override;
void setupField(bool mode);
void expandField();
void printDialogueText2(int dim, const char *str, EMCState *script, const uint16 *paramList, int16 paramIndex);
void printMessage(uint16 type, MSVC_PRINTF const char *str, ...) GCC_PRINTF(3, 4);
int16 _scriptTextParameter;
private:
KyraRpgEngine *vm() override;
Screen *screen() override;
void preprocessString(const char *str, EMCState *script, const uint16 *paramList, int16 paramIndex);
void textPageBreak() override;
char *_stringParameters[15];
char *_buffer;
LoLEngine *_vm;
Screen_LoL *_screen;
};
} // End of namespace Kyra
#endif // ENABLE_LOL
#endif

View File

@@ -0,0 +1,925 @@
/* 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 "kyra/text/text_mr.h"
#include "kyra/resource/resource.h"
#include "common/system.h"
namespace Kyra {
TextDisplayer_MR::TextDisplayer_MR(KyraEngine_MR *vm, Screen_MR *screen)
: TextDisplayer(vm, screen), _vm(vm), _screen(screen) {
}
char *TextDisplayer_MR::preprocessString(const char *str) {
if (_talkBuffer != str) {
assert(strlen(str) < sizeof(_talkBuffer) - 1);
Common::strlcpy(_talkBuffer, str, sizeof(_talkBuffer));
}
char *p = _talkBuffer;
while (*p) {
if (*p++ == '\r')
return _talkBuffer;
}
p = _talkBuffer;
if (_vm->_lang == 3) {
Screen::FontId curFont = _screen->setFont(Screen::FID_CHINESE_FNT);
int textLen = Common::strnlen(p, sizeof(_talkBuffer));
if (textLen > 68) {
int maxTextWidth = ((textLen + 3) / 3) & ~1;
for (int i = textLen + 1; i >= maxTextWidth; --i)
p[i + 1] = p[i];
p[maxTextWidth] = '\r';
p += (maxTextWidth + 1);
textLen -= maxTextWidth;
}
if (textLen > 34) {
int maxTextWidth = ((textLen + 2) / 2) & ~1;
for (int i = textLen + 1; i >= maxTextWidth; --i)
p[i + 1] = p[i];
p[maxTextWidth] = '\r';
}
_screen->setFont(curFont);
} else {
Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT);
_screen->_charSpacing = -2;
const int maxTextWidth = (_vm->language() == 0) ? 176 : 240;
int textWidth = _screen->getTextWidth(p);
if (textWidth > maxTextWidth) {
int count = 0, offs = 0;
if (textWidth > (3*maxTextWidth)) {
count = getCharLength(p, textWidth/4);
offs = dropCRIntoString(p, count, getCharLength(p, maxTextWidth));
p += count + offs;
// No update of textWidth here
}
if (textWidth > (2*maxTextWidth)) {
count = getCharLength(p, textWidth/3);
offs = dropCRIntoString(p, count, getCharLength(p, maxTextWidth));
p += count + offs;
textWidth = _screen->getTextWidth(p);
}
count = getCharLength(p, textWidth/2);
offs = dropCRIntoString(p, count, getCharLength(p, maxTextWidth));
p += count + offs;
textWidth = _screen->getTextWidth(p);
if (textWidth > maxTextWidth) {
count = getCharLength(p, textWidth/2);
offs = dropCRIntoString(p, count, getCharLength(p, maxTextWidth));
}
}
_screen->setFont(curFont);
}
return _talkBuffer;
}
int TextDisplayer_MR::dropCRIntoString(char *str, int minOffs, int maxOffs) {
int offset = 0;
char *proc = str + minOffs;
for (int i = minOffs; i < maxOffs; ++i) {
if (*proc == ' ') {
*proc = '\r';
return offset;
} else if (*proc == '-') {
memmove(proc+1, proc, strlen(proc)+1);
*(++proc) = '\r';
++offset;
return offset;
}
++offset;
++proc;
if (!*proc)
return 0;
}
offset = 0;
proc = str + minOffs;
for (int i = minOffs; i >= 0; --i) {
if (*proc == ' ') {
*proc = '\r';
return offset;
} else if (*proc == '-') {
memmove(proc+1, proc, strlen(proc)+1);
*(++proc) = '\r';
++offset;
return offset;
}
--offset;
--proc;
if (!*proc)
return 0;
}
*(str + minOffs) = '\r';
return 0;
}
void TextDisplayer_MR::printText(const Common::String &str, int x, int y, uint8 c0, uint8 c1, uint8 c2) {
if (_vm->_albumChatActive) {
c0 = 0xEE;
c1 = 0xE3;
c2 = 0x00;
}
uint8 colorMap[] = { 0, 255, 240, 240 };
colorMap[3] = c1;
_screen->setTextColor(colorMap, 0, 3);
_screen->_charSpacing = -2;
Common::String revBuffer;
const char *cstr = str.c_str();
if (_vm->gameFlags().lang == Common::HE_ISR) {
for (int i = str.size() - 1; i >= 0; --i)
revBuffer += str[i];
cstr = revBuffer.c_str();
}
_screen->printText(cstr, x, y, c0, c2);
_screen->_charSpacing = 0;
}
void TextDisplayer_MR::restoreScreen() {
_vm->restorePage3();
_vm->drawAnimObjects();
_screen->copyRegion(_talkCoords.x, _talkMessageY, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, 2, 0, Screen::CR_NO_P_CHECK);
_vm->flagAnimObjsForRefresh();
_vm->refreshAnimObjects(0);
}
void TextDisplayer_MR::calcWidestLineBounds(int &x1, int &x2, int w, int x) {
x1 = x;
x1 -= (w >> 1);
x2 = x1 + w + 1;
if (x1 + w >= 311)
x1 = 311 - w - 1;
if (x1 < 8)
x1 = 8;
x2 = x1 + w + 1;
}
#pragma mark -
int KyraEngine_MR::chatGetType(const char *str) {
while (*str)
++str;
--str;
switch (*str) {
case '!':
return 2;
case ')':
return 3;
case '?':
return 1;
case '.':
default:
return 0;
}
}
int KyraEngine_MR::chatCalcDuration(const char *str) {
return MAX<int>(120, strlen(str)*6);
}
void KyraEngine_MR::objectChat(const char *str, int object, int vocHigh, int vocLow) {
if (_mainCharacter.animFrame == 87 || _mainCharacter.animFrame == 0xFFFF || _mainCharacter.x1 <= 0 || _mainCharacter.y1 <= 0)
return;
_chatVocLow = _chatVocHigh = -1;
objectChatInit(str, object, vocHigh, vocLow);
_chatText = str;
_chatObject = object;
int chatType = chatGetType(str);
if (_mainCharacter.facing > 7)
_mainCharacter.facing = 5;
static const uint8 talkScriptTable[] = {
0x10, 0x11, 0x12, 0x13,
0x0C, 0x0D, 0x0E, 0x0F,
0x0C, 0x0D, 0x0E, 0x0F,
0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03,
0x00, 0x01, 0x02, 0x03,
0x08, 0x09, 0x0A, 0x0B,
0x08, 0x09, 0x0A, 0x0B
};
static const char *const talkFilenameTable[] = {
"MTFL00S.EMC", "MTFL00Q.EMC", "MTFL00E.EMC", "MTFL00T.EMC",
"MTFR00S.EMC", "MTFR00Q.EMC", "MTFR00E.EMC", "MTFR00T.EMC",
"MTL00S.EMC", "MTL00Q.EMC", "MTL00E.EMC", "MTL00T.EMC",
"MTR00S.EMC", "MTR00Q.EMC", "MTR00E.EMC", "MTR00T.EMC",
"MTA00S.EMC", "MTA00Q.EMC", "MTA00E.EMC", "MTA00T.EMC"
};
int chat = talkScriptTable[chatType + _mainCharacter.facing * 4];
objectChatProcess(talkFilenameTable[chat]);
_text->restoreScreen();
// _mainCharacter.facing can not be 0xFF here, so this is safe.
_mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing];
updateCharacterAnim(0);
_chatText = "";
_chatObject = -1;
setNextIdleAnimTimer();
}
void KyraEngine_MR::objectChatInit(const char *str, int object, int vocHigh, int vocLow) {
str = _text->preprocessString(str);
int lineNum = _text->buildMessageSubstrings(str);
int xPos = 0, yPos = 0;
if (!object) {
int scale = getScale(_mainCharacter.x1, _mainCharacter.y1);
yPos = _mainCharacter.y1 - ((_mainCharacter.height * scale) >> 8) - 8;
xPos = _mainCharacter.x1;
} else {
yPos = _talkObjectList[object].y;
xPos = _talkObjectList[object].x;
}
_text->_talkMessageH = lineNum * _screen->getFontHeight() + (lineNum - 1) * _screen->_lineSpacing;
yPos -= _text->_talkMessageH;
yPos = MAX(yPos, 0);
_text->_talkMessageY = yPos;
int width = _text->getWidestLineWidth(lineNum);
_text->calcWidestLineBounds(xPos, yPos, width, xPos);
_text->_talkCoords.x = xPos;
_text->_talkCoords.w = width + 2;
restorePage3();
_chatTextEnabled = textEnabled();
if (_chatTextEnabled) {
objectChatPrintText(str, object);
_chatEndTime = _system->getMillis() + chatCalcDuration(str) * _tickLength;
} else {
_chatEndTime = _system->getMillis();
}
if (speechEnabled()) {
_chatVocHigh = vocHigh;
_chatVocLow = vocLow;
} else {
_chatVocHigh = _chatVocLow = -1;
}
}
void KyraEngine_MR::objectChatPrintText(const Common::String &str0, int object) {
int c1 = (_lang == 3 && _albumChatActive) ? 0xEE : _talkObjectList[object].color;
int c2 = (_lang == 3) ? 0 : 0xF0;
Common::String str = _text->preprocessString(str0.c_str());
int lineNum = _text->buildMessageSubstrings(str.c_str());
int maxWidth = _text->getWidestLineWidth(lineNum);
int x = (object == 0) ? _mainCharacter.x1 : _talkObjectList[object].x;
int cX1 = 0, cX2 = 0;
_text->calcWidestLineBounds(cX1, cX2, maxWidth, x);
for (int i = 0; i < lineNum; ++i) {
str = Common::String(&_text->_talkSubstrings[i * _text->maxSubstringLen()]);
int y = _text->_talkMessageY + i * (_screen->getFontHeight() + _screen->_lineSpacing);
x = _text->getCenterStringX(str, cX1, cX2);
_text->printText(str, x, y, c1, c2, 0);
}
}
void KyraEngine_MR::objectChatProcess(const char *script) {
memset(&_chatScriptData, 0, sizeof(_chatScriptData));
memset(&_chatScriptState, 0, sizeof(_chatScriptState));
_emc->load(script, &_chatScriptData, &_opcodesAnimation);
_emc->init(&_chatScriptState, &_chatScriptData);
_emc->start(&_chatScriptState, 0);
while (_emc->isValid(&_chatScriptState))
_emc->run(&_chatScriptState);
if (_chatVocHigh >= 0) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
_useFrameTable = true;
objectChatWaitToFinish();
_useFrameTable = false;
_emc->unload(&_chatScriptData);
}
void KyraEngine_MR::objectChatWaitToFinish() {
int charAnimFrame = _mainCharacter.animFrame;
setCharacterAnimDim(_animShapeWidth, _animShapeHeight);
_emc->init(&_chatScriptState, &_chatScriptData);
_emc->start(&_chatScriptState, 1);
bool running = true;
const uint32 endTime = _chatEndTime;
resetSkipFlag();
while (running && !shouldQuit()) {
if (!_emc->isValid(&_chatScriptState))
_emc->start(&_chatScriptState, 1);
_animNeedUpdate = false;
while (!_animNeedUpdate && _emc->isValid(&_chatScriptState) && !shouldQuit())
_emc->run(&_chatScriptState);
int curFrame = _animNewFrame;
uint32 delayTime = _animDelayTime;
_mainCharacter.animFrame = curFrame;
updateCharacterAnim(0);
uint32 nextFrame = _system->getMillis() + delayTime * _tickLength;
while (_system->getMillis() < nextFrame && !shouldQuit()) {
updateWithText();
const uint32 curTime = _system->getMillis();
if ((textEnabled() && !speechEnabled() && curTime > endTime) || (speechEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
snd_stopVoice();
resetSkipFlag();
nextFrame = curTime;
running = false;
}
delay(10);
}
}
_mainCharacter.animFrame = charAnimFrame;
updateCharacterAnim(0);
resetCharacterAnimDim();
}
void KyraEngine_MR::badConscienceChat(const char *str, int vocHigh, int vocLow) {
if (!_badConscienceShown)
return;
setNextIdleAnimTimer();
_chatVocHigh = _chatVocLow = -1;
objectChatInit(str, 1, vocHigh, vocLow);
_chatText = str;
_chatObject = 1;
badConscienceChatWaitToFinish();
updateSceneAnim(0x0E, _badConscienceFrameTable[_badConscienceAnim+16]);
_text->restoreScreen();
update();
_chatText = "";
_chatObject = -1;
}
void KyraEngine_MR::badConscienceChatWaitToFinish() {
if (_chatVocHigh) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
bool running = true;
const uint32 endTime = _chatEndTime;
resetSkipFlag();
uint32 nextFrame = _system->getMillis() + _rnd.getRandomNumberRng(4, 8) * _tickLength;
int frame = _badConscienceFrameTable[_badConscienceAnim+24];
while (running && !shouldQuit()) {
if (nextFrame < _system->getMillis()) {
++frame;
if (_badConscienceFrameTable[_badConscienceAnim+32] < frame)
frame = _badConscienceFrameTable[_badConscienceAnim+24];
updateSceneAnim(0x0E, frame);
updateWithText();
nextFrame = _system->getMillis() + _rnd.getRandomNumberRng(4, 8) * _tickLength;
}
updateWithText();
const uint32 curTime = _system->getMillis();
if ((textEnabled() && !speechEnabled() && curTime > endTime) || (speechEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
snd_stopVoice();
resetSkipFlag();
nextFrame = curTime;
running = false;
}
delay(10);
}
}
void KyraEngine_MR::goodConscienceChat(const char *str, int vocHigh, int vocLow) {
if (!_goodConscienceShown)
return;
setNextIdleAnimTimer();
_chatVocHigh = _chatVocLow = -1;
objectChatInit(str, 87, vocHigh, vocLow);
_chatText = str;
_chatObject = 87;
goodConscienceChatWaitToFinish();
updateSceneAnim(0x0F, _goodConscienceFrameTable[_goodConscienceAnim+10]);
_text->restoreScreen();
update();
_chatText = "";
_chatObject = -1;
}
void KyraEngine_MR::goodConscienceChatWaitToFinish() {
if (_chatVocHigh) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
bool running = true;
const uint32 endTime = _chatEndTime;
resetSkipFlag();
uint32 nextFrame = _system->getMillis() + _rnd.getRandomNumberRng(3, 6) * _tickLength;
int frame = _goodConscienceFrameTable[_goodConscienceAnim+15];
while (running && !shouldQuit()) {
if (nextFrame < _system->getMillis()) {
++frame;
if (_goodConscienceFrameTable[_goodConscienceAnim+20] < frame)
frame = _goodConscienceFrameTable[_goodConscienceAnim+15];
updateSceneAnim(0x0F, frame);
updateWithText();
nextFrame = _system->getMillis() + _rnd.getRandomNumberRng(3, 6) * _tickLength;
}
updateWithText();
const uint32 curTime = _system->getMillis();
if ((textEnabled() && !speechEnabled() && curTime > endTime) || (speechEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
snd_stopVoice();
resetSkipFlag();
nextFrame = curTime;
running = false;
}
delay(10);
}
}
void KyraEngine_MR::albumChat(const char *str, int vocHigh, int vocLow) {
_talkObjectList[1].x = 190;
_talkObjectList[1].y = _interfaceCommandLineY1;
_chatVocHigh = _chatVocLow = -1;
_albumChatActive = true;
albumChatInit(str, 1, vocHigh, vocLow);
_albumChatActive = false;
_chatText = str;
_chatObject = 1;
_screen->hideMouse();
albumChatWaitToFinish();
_screen->showMouse();
_chatText = "";
_chatObject = -1;
}
void KyraEngine_MR::albumChatInit(const char *str, int object, int vocHigh, int vocLow) {
Common::String realString;
while (*str) {
if (str[0] == '\\' && str[1] == 'r') {
realString += '\r';
++str;
} else {
realString += *str;
}
++str;
}
str = realString.c_str();
str = _text->preprocessString(str);
int lineNum = _text->buildMessageSubstrings(str);
int xPos = 0, yPos = 0;
if (!object) {
int scale = getScale(_mainCharacter.x1, _mainCharacter.y1);
yPos = _mainCharacter.y1 - ((_mainCharacter.height * scale) >> 8) - 8;
xPos = _mainCharacter.x1;
} else {
yPos = _talkObjectList[object].y;
xPos = _talkObjectList[object].x;
}
_text->_talkMessageH = lineNum * _screen->getFontHeight() + (lineNum - 1) * _screen->_lineSpacing;
yPos -= _text->_talkMessageH;
yPos = MAX(yPos, 0);
_text->_talkMessageY = yPos;
int width = _text->getWidestLineWidth(lineNum);
_text->calcWidestLineBounds(xPos, yPos, width, xPos);
_text->_talkCoords.x = xPos;
_text->_talkCoords.w = width + 2;
_screen->hideMouse();
if (textEnabled()) {
objectChatPrintText(str, object);
_chatEndTime = _system->getMillis() + chatCalcDuration(str) * _tickLength;
} else {
_chatEndTime = _system->getMillis();
}
if (speechEnabled()) {
_chatVocHigh = vocHigh;
_chatVocLow = vocLow;
} else {
_chatVocHigh = _chatVocLow = -1;
}
_screen->showMouse();
}
void KyraEngine_MR::albumChatWaitToFinish() {
if (_chatVocHigh) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
bool running = true;
const uint32 endTime = _chatEndTime;
resetSkipFlag();
uint32 nextFrame = 0;
int frame = 12;
while (running && !shouldQuit()) {
if (nextFrame < _system->getMillis()) {
++frame;
if (frame > 22)
frame = 13;
albumRestoreRect();
_album.wsa->displayFrame(frame, 2, -100, 90, 0x4000, nullptr, nullptr);
albumUpdateRect();
nextFrame = _system->getMillis() + _rnd.getRandomNumberRng(4, 8) * _tickLength;
}
if (_album.nextPage != 14)
albumUpdateAnims();
else
_screen->updateScreen();
const uint32 curTime = _system->getMillis();
if ((textEnabled() && !speechEnabled() && curTime > endTime) || (speechEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
snd_stopVoice();
resetSkipFlag();
nextFrame = curTime;
running = false;
}
delay(10);
}
}
void KyraEngine_MR::malcolmSceneStartupChat() {
if (_noStartupChat)
return;
int index = _mainCharacter.sceneId - _chapterLowestScene[_currentChapter];
if (_newSceneDlgState[index])
return;
updateDlgBuffer();
int vocHighBase = 0, vocHighIndex = 0, index1 = 0, index2 = 0;
loadDlgHeader(vocHighBase, vocHighIndex, index1, index2);
_cnvFile->seek(index1*6, SEEK_CUR);
_cnvFile->seek(index2*4, SEEK_CUR);
_cnvFile->seek(index*2, SEEK_CUR);
_cnvFile->seek(_cnvFile->readUint16LE(), SEEK_SET);
_isStartupDialog = true;
processDialog(vocHighIndex, vocHighBase, 0);
_isStartupDialog = false;
_newSceneDlgState[index] = true;
}
void KyraEngine_MR::updateDlgBuffer() {
if (_cnvFile)
_cnvFile->seek(0, SEEK_SET);
if (_curDlgIndex == _mainCharacter.dlgIndex && _curDlgChapter == _currentChapter && _curDlgLang == _lang)
return;
Common::Path dlgFile(Common::String::format("CH%.02d-S%.02d.%s", _currentChapter, _mainCharacter.dlgIndex, _languageExtension[_lang]));
Common::Path cnvFile(Common::String::format("CH%.02d-S%.02d.CNV", _currentChapter, _mainCharacter.dlgIndex));
delete _cnvFile;
delete _dlgBuffer;
_res->exists(cnvFile, true);
_res->exists(dlgFile, true);
_cnvFile = _res->createReadStream(cnvFile);
_dlgBuffer = _res->createReadStream(dlgFile);
assert(_cnvFile);
assert(_dlgBuffer);
}
void KyraEngine_MR::loadDlgHeader(int &vocHighBase, int &vocHighIndex, int &index1, int &index2) {
assert(_cnvFile);
vocHighIndex = _cnvFile->readSint16LE();
vocHighBase = _cnvFile->readSint16LE();
index1 = _cnvFile->readSint16LE();
index2 = _cnvFile->readSint16LE();
}
void KyraEngine_MR::setDlgIndex(int index) {
if (_mainCharacter.dlgIndex != index) {
memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState));
memset(_conversationState, -1, sizeof(_conversationState));
_chatAltFlag = false;
_mainCharacter.dlgIndex = index;
}
}
void KyraEngine_MR::updateDlgIndex() {
uint16 dlgIndex = _mainCharacter.dlgIndex;
if (_currentChapter == 1) {
static const uint8 dlgIndexMoodNice[] = { 0x0C, 0x0E, 0x10, 0x0F, 0x11 };
static const uint8 dlgIndexMoodNormal[] = { 0x00, 0x02, 0x04, 0x03, 0x05 };
static const uint8 dlgIndexMoodEvil[] = { 0x06, 0x08, 0x0A, 0x09, 0x0B };
if (_malcolmsMood == 0)
dlgIndex = dlgIndexMoodNice[_characterShapeFile];
else if (_malcolmsMood == 1)
dlgIndex = dlgIndexMoodNormal[_characterShapeFile];
else if (_malcolmsMood == 2)
dlgIndex = dlgIndexMoodEvil[_characterShapeFile];
} else if (_currentChapter == 2) {
if (dlgIndex >= 8)
dlgIndex -= 4;
if (dlgIndex >= 4)
dlgIndex -= 4;
if (_malcolmsMood == 0)
dlgIndex += 8;
else if (_malcolmsMood == 2)
dlgIndex += 4;
} else if (_currentChapter == 4) {
if (dlgIndex >= 10)
dlgIndex -= 5;
if (dlgIndex >= 5)
dlgIndex -= 5;
if (_malcolmsMood == 0)
dlgIndex += 10;
else if (_malcolmsMood == 2)
dlgIndex += 5;
}
_mainCharacter.dlgIndex = dlgIndex;
}
void KyraEngine_MR::processDialog(int vocHighIndex, int vocHighBase, int funcNum) {
bool running = true;
int script = -1;
int vocHigh = -1, vocLow = -1;
while (running) {
uint16 cmd = _cnvFile->readUint16LE();
int object = cmd - 12;
if (cmd == 10) {
break;
} else if (cmd == 4) {
vocHighBase = _cnvFile->readUint16LE();
setDlgIndex(vocHighBase);
} else if (cmd == 11) {
int strSize = _cnvFile->readUint16LE();
_cnvFile->readUint16LE();
_cnvFile->read(_stringBuffer, strSize);
_stringBuffer[strSize] = 0;
} else {
vocHigh = _vocHighTable[vocHighIndex-1] + vocHighBase;
vocLow = _cnvFile->readUint16LE();
getTableEntry(_dlgBuffer, vocLow, _stringBuffer);
if (_isStartupDialog) {
delay(60*_tickLength, true);
_isStartupDialog = false;
}
if (*_stringBuffer == 0)
continue;
if (cmd != 12) {
if (object != script) {
if (script >= 0)
dialogEndScript(script);
dialogStartScript(object, funcNum);
script = object;
}
npcChatSequence(_stringBuffer, object, vocHigh, vocLow);
} else {
if (script >= 0) {
dialogEndScript(script);
script = -1;
}
objectChat(_stringBuffer, 0, vocHigh, vocLow);
playStudioSFX(_stringBuffer);
}
}
}
if (script != -1)
dialogEndScript(script);
}
void KyraEngine_MR::dialogStartScript(int object, int funcNum) {
_dialogSceneAnim = _talkObjectList[object].sceneAnim;
_dialogSceneScript = _talkObjectList[object].sceneScript;
if (_dialogSceneAnim >= 0 && _dialogSceneScript >= 0) {
_specialSceneScriptStateBackup[_dialogSceneScript] = _specialSceneScriptState[_dialogSceneScript];
_specialSceneScriptState[_dialogSceneScript] = true;
}
_emc->init(&_dialogScriptState, &_dialogScriptData);
_emc->load(_talkObjectList[object].filename, &_dialogScriptData, &_opcodesDialog);
_dialogScriptFuncStart = funcNum * 3 + 0;
_dialogScriptFuncProc = funcNum * 3 + 1;
_dialogScriptFuncEnd = funcNum * 3 + 2;
_emc->start(&_dialogScriptState, _dialogScriptFuncStart);
while (_emc->isValid(&_dialogScriptState))
_emc->run(&_dialogScriptState);
}
void KyraEngine_MR::dialogEndScript(int object) {
_emc->init(&_dialogScriptState, &_dialogScriptData);
_emc->start(&_dialogScriptState, _dialogScriptFuncEnd);
while (_emc->isValid(&_dialogScriptState))
_emc->run(&_dialogScriptState);
if (_dialogSceneAnim >= 0 && _dialogSceneScript >= 0) {
_specialSceneScriptState[_dialogSceneScript] = _specialSceneScriptStateBackup[_dialogSceneScript];
_dialogSceneScript = _dialogSceneAnim = -1;
}
_emc->unload(&_dialogScriptData);
}
void KyraEngine_MR::npcChatSequence(const char *str, int object, int vocHigh, int vocLow) {
_chatText = str;
_chatObject = object;
_chatVocHigh = _chatVocLow = -1;
objectChatInit(str, object, vocHigh, vocLow);
if (_chatVocHigh >= 0 && _chatVocLow >= 0) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
_emc->init(&_dialogScriptState, &_dialogScriptData);
_emc->start(&_dialogScriptState, _dialogScriptFuncProc);
resetSkipFlag();
uint32 endTime = _chatEndTime;
bool running = true;
while (running && !shouldQuit()) {
if (!_emc->run(&_dialogScriptState)) {
_emc->init(&_dialogScriptState, &_dialogScriptData);
_emc->start(&_dialogScriptState, _dialogScriptFuncProc);
}
const uint32 curTime = _system->getMillis();
if ((textEnabled() && !speechEnabled() && curTime > endTime) || (speechEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
snd_stopVoice();
resetSkipFlag();
running = false;
}
}
_text->restoreScreen();
_chatText = "";
_chatObject= - 1;
}
void KyraEngine_MR::randomSceneChat() {
updateDlgBuffer();
int index = (_mainCharacter.sceneId - _chapterLowestScene[_currentChapter]) * 2;
int vocHighBase = 0, vocHighIndex = 0, index1 = 0, index2 = 0;
loadDlgHeader(vocHighBase, vocHighIndex, index1, index2);
if (_chatAltFlag)
index++;
_chatAltFlag = !_chatAltFlag;
_cnvFile->seek(index1*6, SEEK_CUR);
_cnvFile->seek(index*2, SEEK_CUR);
_cnvFile->seek(_cnvFile->readUint16LE(), SEEK_SET);
processDialog(vocHighIndex, vocHighBase, 0);
}
void KyraEngine_MR::doDialog(int dlgIndex, int funcNum) {
switch (_currentChapter-2) {
case 0:
dlgIndex -= 34;
break;
case 1:
dlgIndex -= 54;
break;
case 2:
dlgIndex -= 55;
break;
case 3:
dlgIndex -= 70;
break;
default:
break;
}
updateDlgBuffer();
int vocHighBase = 0, vocHighIndex = 0, index1 = 0, index2 = 0;
loadDlgHeader(vocHighBase, vocHighIndex, index1, index2);
int convState = _conversationState[dlgIndex][vocHighBase];
uint32 offset = ((vocHighIndex == 1) ? dlgIndex - 1 : dlgIndex) * 6;
if (convState == -1) {
_cnvFile->seek(offset, SEEK_CUR);
_conversationState[dlgIndex][vocHighBase] = 0;
} else if (convState == 0 || convState == 2) {
_cnvFile->seek(offset+2, SEEK_CUR);
_conversationState[dlgIndex][vocHighBase] = 1;
} else {
_cnvFile->seek(offset+4, SEEK_CUR);
_conversationState[dlgIndex][vocHighBase] = 2;
}
_cnvFile->seek(_cnvFile->readUint16LE(), SEEK_SET);
processDialog(vocHighIndex, vocHighBase, funcNum);
}
} // End of namespace Kyra

View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
#ifndef KYRA_TEXT_MR_H
#define KYRA_TEXT_MR_H
#include "kyra/text/text.h"
#include "kyra/engine/kyra_mr.h"
namespace Kyra {
class TextDisplayer_MR : public TextDisplayer {
friend class KyraEngine_MR;
public:
TextDisplayer_MR(KyraEngine_MR *vm, Screen_MR *screen);
char *preprocessString(const char *str) override;
int dropCRIntoString(char *str, int minOffs, int maxOffs);
void printText(const Common::String &str, int x, int y, uint8 c0, uint8 c1, uint8 c2) override;
void restoreScreen();
void calcWidestLineBounds(int &x1, int &x2, int w, int x) override;
protected:
KyraEngine_MR *_vm;
Screen_MR *_screen;
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,877 @@
/* 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/>.
*
*/
#if defined(ENABLE_EOB) || defined(ENABLE_LOL)
#include "kyra/engine/kyra_rpg.h"
#include "kyra/engine/timer.h"
#include "common/system.h"
namespace Kyra {
enum {
kEoBTextBufferSize = 2560
};
TextDisplayer_rpg::TextDisplayer_rpg(KyraRpgEngine *engine, Screen *scr) : _vm(engine), _screen(scr),
_lineCount(0), _printFlag(false), _lineWidth(0), _numCharsTotal(0), _allowPageBreak(true), _dimCount(scr ? scr->screenDimTableCount() : 0),
_numCharsLeft(0), _numCharsPrinted(0), _twoByteLineBreakFlag(false), _waitButtonMode(1),
_pc98TextMode(engine->gameFlags().use16ColorMode && engine->game() == GI_LOL),
_waitButtonFont(Screen::FID_6_FNT), _isChinese(_vm->gameFlags().lang == Common::Language::ZH_TWN || _vm->gameFlags().lang == Common::Language::ZH_CHN) {
_dialogueBuffer = new char[kEoBTextBufferSize]();
_currentLine = new char[85]();
if (_pc98TextMode)
_waitButtonFont = Screen::FID_SJIS_TEXTMODE_FNT;
else if ((_vm->game() == GI_EOB2 && _vm->gameFlags().platform == Common::kPlatformFMTowns))
_waitButtonFont = Screen::FID_8_FNT;
else if (_vm->gameFlags().platform == Common::kPlatformPC98)
_waitButtonFont = Screen::FID_SJIS_FNT;
else if ((_vm->game() == GI_LOL && _vm->gameFlags().lang == Common::Language::ZH_TWN))
_waitButtonFont = Screen::FID_CHINESE_FNT;
_textDimData = new TextDimData[_dimCount];
memset(_textDimData, 0, sizeof(TextDimData) * _dimCount);
applySetting(-1, kNoHalfWidthLineEnd, ((_vm->gameFlags().lang == Common::JA_JPN && _vm->game() == GI_EOB1) || (_vm->gameFlags().lang == Common::Language::ZH_TWN && _vm->game() == GI_EOB2)) ? 1 : 0);
for (int i = 0; i < 256; ++i)
_colorMap[i] = i;
for (int i = 0; i < _dimCount; ++i) {
const ScreenDim *d = _screen->getScreenDim(i);
_textDimData[i].color1= d->col1;
_textDimData[i].color2 = d->col2;
_textDimData[i].line = d->line;
_textDimData[i].column = d->column;
}
_table1 = new char[128]();
_table2 = new char[16]();
_tempString1 = _tempString2 = 0;
_ctrl[0] = _ctrl[1] = _ctrl[2] = '\0';
_waitButtonSpace = 0;
}
TextDisplayer_rpg::~TextDisplayer_rpg() {
setColorMapping(-1, 0 ,0);
delete[] _dialogueBuffer;
delete[] _currentLine;
delete[] _textDimData;
delete[] _table1;
delete[] _table2;
}
void TextDisplayer_rpg::setupField(int dim, bool mode) {
setPageBreakFlag();
_textDimData[dim].color2 = _vm->guiSettings()->colors.fill;
_screen->setScreenDim(dim);
if (mode) {
_screen->set16bitShadingLevel(4);
clearCurDim();
_screen->set16bitShadingLevel(0);
} else {
resetDimTextPositions(dim);
}
_vm->_dialogueFieldAmiga = false;
}
void TextDisplayer_rpg::resetDimTextPositions(int dim) {
_textDimData[dim].column = 0;
_textDimData[dim].line = 0;
}
void TextDisplayer_rpg::resetPageBreakString() {
if (_vm->_moreStrings)
_pageBreakString = _vm->_moreStrings[0];
}
void TextDisplayer_rpg::setPageBreakFlag() {
_allowPageBreak = true;
_lineCount = 0;
}
void TextDisplayer_rpg::removePageBreakFlag() {
_allowPageBreak = false;
}
void TextDisplayer_rpg::setColorMapping(int sd, uint8 from, uint8 to) {
if (sd < -1 || sd >= _dimCount)
error("TextDisplayer_rpg::mapColor(): arg out of range");
if (sd == -1) {
for (int i = 0; i < _dimCount; ++i) {
delete[] _textDimData[i].colorMap;
_textDimData[i].colorMap = nullptr;
}
_colorMap[from] = to;
} else {
if (_textDimData[sd].colorMap == nullptr) {
_textDimData[sd].colorMap = new uint8[256];
for (int i = 0; i < 256; ++i)
_textDimData[sd].colorMap[i] = i;
}
_textDimData[sd].colorMap[from] = to;
}
}
void TextDisplayer_rpg::displayText(char *str, ...) {
convertString(str);
_printFlag = false;
_lineWidth = 0;
_numCharsLeft = 0;
_numCharsPrinted = 0;
_tempString1 = str;
_tempString2 = 0;
_currentLine[0] = 0;
memset(_ctrl, 0, 3);
char c = parseCommand();
va_list args;
va_start(args, str);
const ScreenDim *sd = _screen->_curDim;
int sdx = _screen->curDimIndex();
bool sjisTextMode = (_pc98TextMode && (sdx == 3 || sdx == 4 || sdx == 5 || sdx == 15)) ? true : false;
Screen::FontId of = (_vm->game() == GI_EOB2 && _vm->gameFlags().platform == Common::kPlatformFMTowns) ? _screen->setFont(Screen::FID_8_FNT) : _screen->_currentFont;
uint16 charsPerLine = (sd->w << 3) / (_screen->getFontWidth() + _screen->_charSpacing);
while (c) {
char a = tolower((unsigned char)_ctrl[1]);
if (!_tempString2 && c == '%') {
if (a == 'd') {
_scriptParaString = Common::String::format("%d", va_arg(args, int));
_tempString2 = _scriptParaString.c_str();
} else if (a == 's') {
_tempString2 = va_arg(args, const char*);
} else {
break;
}
_ctrl[0] = _ctrl[2];
_ctrl[2] = _ctrl[1] = 0;
c = parseCommand();
}
if (isTwoByteChar(c)) {
char next = parseCommand();
int cw = _screen->getCharWidth((uint8)c | (uint8)next << 8) + _textDimData[sdx].charSpacing;
if (_textDimData[sdx].column + _lineWidth + cw > (sd->w << 3))
printLine(_currentLine);
_currentLine[_numCharsLeft++] = c;
_currentLine[_numCharsLeft++] = next;
_currentLine[_numCharsLeft] = '\0';
_lineWidth += cw;
if (_textDimData[sdx].noHalfWidthLineEnd && (_textDimData[sdx].column + _lineWidth + cw >= (sd->w << 3)))
printLine(_currentLine);
c = parseCommand();
continue;
}
uint16 dv = _textDimData[sdx].column / (_screen->getFontWidth() + _screen->_charSpacing);
switch (c - 1) {
case 0:
printLine(_currentLine);
_screen->updateScreen();
textPageBreak();
_numCharsPrinted = 0;
break;
case 1:
printLine(_currentLine);
_textDimData[sdx].color2 = parseCommand();
break;
case 5:
printLine(_currentLine);
_textDimData[sdx].color1= parseCommand();
break;
case 8:
printLine(_currentLine);
dv = _textDimData[sdx].column / (_screen->getFontWidth() + _screen->_charSpacing);
dv = ((dv + 8) & 0xFFF8) - 1;
if (dv >= charsPerLine)
dv = 0;
_textDimData[sdx].column = (_screen->getFontWidth() + _screen->_charSpacing) * dv;
break;
case 12:
if (sjisTextMode)
_twoByteLineBreakFlag = true;
printLine(_currentLine);
_twoByteLineBreakFlag = false;
//_lineWidth = 0;
_lineCount++;
_textDimData[sdx].column = 0;
_textDimData[sdx].line++;
break;
case 11: case 18: case 23:
case 24: case 26: case 28:
// These are at the time of writing this comment not known to be
// used. In case there is some use of them in some odd version
// we display this warning here.
warning("TextDisplayer_rpg::displayText: Triggered stub function %d", c - 1);
break;
default:
if (_vm->game() == GI_EOB1 || _vm->game() == GI_LOL || (unsigned char)c > 30) {
int cw = _screen->getCharWidth((uint8)c) + _textDimData[sdx].charSpacing;
_lineWidth += cw;
_currentLine[_numCharsLeft++] = c;
_currentLine[_numCharsLeft] = 0;
if ((_textDimData[sdx].column + _lineWidth) > (sd->w << 3))
printLine(_currentLine);
}
}
c = parseCommand();
}
va_end(args);
if (_numCharsLeft)
printLine(_currentLine);
_screen->setFont(of);
_screen->updateScreen();
}
char TextDisplayer_rpg::parseCommand() {
if (!_ctrl[1])
readNextPara();
char res = _ctrl[1];
_ctrl[1] = _ctrl[2];
_ctrl[2] = 0;
if (!_ctrl[1])
readNextPara();
return res;
}
void TextDisplayer_rpg::readNextPara() {
char c = 0;
char d = 0;
if (_tempString2) {
if (*_tempString2) {
d = *_tempString2++;
} else {
_tempString2 = 0;
d = _ctrl[0];
}
}
if (!d && _tempString1) {
if (*_tempString1)
d = *_tempString1++;
else
_tempString1 = 0;
}
// This seems to be some sort of character conversion mechanism. The original doesn't make any use of it, however.
// All necessary conversions take place somewhere else. This code actually causes issues if the character conversions
// don't take place before calling displayText(). So we disable it for now. If some (not yet supported) localized
// versions depend on this code we'll have to look at this again.
#if 0
if ((_vm->game() != GI_LOL) && (d & 0x80)) {
d &= 0x7F;
c = d & 7;
d = (d & 0x78) >> 3;
uint8 l = d;
c = _table1[(l << 3) + c];
d = _table2[l];
}
#endif
_ctrl[1] = d;
_ctrl[2] = c;
}
void TextDisplayer_rpg::printLine(char *str) {
const ScreenDim *sd = _screen->_curDim;
int sdx = _screen->curDimIndex();
bool sjisTextMode = _pc98TextMode && (sdx == 3 || sdx == 4 || sdx == 5 || sdx == 15) ? true : false;
int fh = _screen->getFontHeight() + _screen->_lineSpacing + _textDimData[sdx].lineSpacing;
int lines = (sd->h - _screen->_lineSpacing) / fh;
// Another hack for Chinese EOB II...The original prints text at the very bottom of the text field,
// even if there is a good risk of printing text over the dialogue buttons.
if (_isChinese && _allowPageBreak)
++lines;
while (_textDimData[sdx].line >= lines) {
if ((lines - _waitButtonSpace) <= _lineCount && _allowPageBreak) {
_lineCount = 0;
_screen->updateScreen();
textPageBreak();
_numCharsPrinted = 0;
}
int h1 = ((sd->h / fh) - 1) * fh;
int h2 = sd->h - fh;
int wOffs = (_textDimData[sdx].shadowColor && sd->sx > 0) ? 1 : 0;
if (h2)
_screen->copyRegion((sd->sx << 3) - wOffs, sd->sy + fh, (sd->sx << 3) - wOffs, sd->sy, (sd->w << 3) + wOffs, h2, _screen->_curPage, _screen->_curPage, Screen::CR_NO_P_CHECK);
// HACK: In Chinese EOBII some characters overdraw the valid boundaries by one pixel
// (at least the ',' does). So, the original redraws the border here. We do the same
// since for now I don't have any good idea how to do this less ugly...
if (_isChinese && _vm->_flags.gameID == GI_EOB2 && sdx == 7)
_screen->drawBox(3, 170, 290, 199, _vm->guiSettings()->colors.fill);
_screen->set16bitShadingLevel(4);
_screen->fillRect((sd->sx << 3) - wOffs, sd->sy + h1, ((sd->sx + sd->w) << 3) - 1, sd->sy + sd->h - 1, remapColor(sdx, _textDimData[sdx].color2));
_screen->set16bitShadingLevel(0);
if (_textDimData[sdx].line)
_textDimData[sdx].line--;
}
int x1 = (sd->sx << 3) + _textDimData[sdx].column;
int y = sd->sy + (fh + _textDimData[sdx].visualLineSpacingAdjust) * _textDimData[sdx].line;
int w = sd->w << 3;
int lw = _lineWidth;
int s = _numCharsLeft;
uint8 twoByteCharOffs = 0;
if (sjisTextMode) {
bool ct = true;
if ((lw + _textDimData[sdx].column) >= w) {
if ((lines - 1 - (_waitButtonSpace << 1)) <= _lineCount)
// cut off line to leave space for "MORE" button
w -= _vm->guiSettings()->buttons.waitReserve;
} else {
if (!_twoByteLineBreakFlag || (_lineCount + 1 < lines - 1))
ct = false;
else
// cut off line to leave space for "MORE" button
w -= _vm->guiSettings()->buttons.waitReserve;
}
if (ct) {
w -= _textDimData[sdx].column;
int n2 = 0;
int n1 = (w / 4) - 1;
while (n2 < n1 && n2 < s) {
if (isTwoByteChar(str[n2]))
n2++;
n2++;
}
s = n2;
}
} else {
for (int i = 0; i < s; ++i) {
if (isTwoByteChar(str[i]))
twoByteCharOffs = (_vm->game() == GI_EOB1 || _isChinese) ? 16 : 8;
}
if (_isChinese) {
s = strlen(str);
if ((lw + _textDimData[sdx].column) >= w) {
s -= ((lw + _textDimData[sdx].column - w) >> 3);
w -= _textDimData[sdx].column;
}
} else if ((lw + _textDimData[sdx].column) >= w) {
if ((lines - 1) <= _lineCount && _allowPageBreak)
// cut off line to leave space for "MORE" button
w -= _vm->guiSettings()->buttons.waitReserve;
w -= _textDimData[sdx].column;
int lineLastCharPos = 0;
int strPos = s - 1;
if (twoByteCharOffs) {
lw = 0;
int prevStrPos = 0;
for (strPos = 0; strPos < s; ++strPos) {
uint8 c = str[strPos];
if (isTwoByteChar(c))
lw += (_screen->getCharWidth(c | (uint8)str[++strPos] << 8) + _textDimData[sdx].charSpacing);
else
lw += _screen->getCharWidth(c);
if (!lineLastCharPos && w < lw + twoByteCharOffs)
lineLastCharPos = prevStrPos;
if (lineLastCharPos && c == ' ') {
s = strPos;
_printFlag = false;
break;
}
prevStrPos = strPos;
}
if (!lineLastCharPos) {
lineLastCharPos = s - 1;
if (lineLastCharPos && str[lineLastCharPos] == ' ') {
s = strPos;
_printFlag = false;
}
}
lw = _lineWidth;
} else {
while (strPos > 0) {
//cut off line after last space
uint8 c = str[strPos];
lw -= _screen->getCharWidth(c);
if (!lineLastCharPos && lw <= w)
lineLastCharPos = strPos;
if (lineLastCharPos && c == ' ') {
s = strPos;
_printFlag = false;
break;
}
strPos--;
}
}
if (!strPos) {
if (_textDimData[sdx].column && !_printFlag) {
s = lw = 0;
_printFlag = true;
} else {
s = lineLastCharPos;
}
}
}
}
char lastChr = str[s];
str[s] = 0;
uint8 col1 = remapColor(sdx, _textDimData[sdx].color1);
uint8 col2 = remapColor(sdx, _textDimData[sdx].color2);
if (sjisTextMode && (sdx == 2 || sdx == 3 || sdx == 4 || sdx == 5 || sdx == 15)) {
x1 &= ~3;
y = (y + 8) & ~7;
col2 = 0;
}
if (_textDimData[sdx].shadowColor) {
_screen->printText(str, x1 - 1, y, _textDimData[sdx].shadowColor, col2);
_screen->printText(str, x1, y + 1, _textDimData[sdx].shadowColor, 0);
_screen->printText(str, x1 - 1, y + 1, _textDimData[sdx].shadowColor, 0);
// Another hack for Chinese EOBII. Due to the reduced line spacing - while still drawing a shadow for the font - the
// lines will overdraw by one pixel if we don't clear the bottom line. This will otherwise cause glitches when doing line feeds.
for (int i = 0; i < -_textDimData[sdx].lineSpacing && y + fh + i < sd->sy + sd->h; ++i)
_screen->drawClippedLine(x1 - 1, y + fh + i, x1 + lw, y + fh + i, col2);
col2 = 0;
}
_screen->printText(str, x1, y, col1, col2);
_textDimData[sdx].column += lw;
_numCharsPrinted += strlen(str);
str[s] = lastChr;
if (lastChr == ' ')
s++;
if (str[s] == ' ')
s++;
uint32 len = strlen(&str[s]);
for (uint32 i = 0; i < len; i++)
str[i] = str[s + i];
str[len] = 0;
_numCharsLeft = strlen(str);
_lineWidth = _screen->getTextWidth(str) + _textDimData[sdx].charSpacing * _numCharsLeft;
if (!_numCharsLeft && (_textDimData[sdx].column + twoByteCharOffs) <= (sd->w << 3))
return;
_textDimData[sdx].column = 0;
_textDimData[sdx].line++;
_lineCount++;
if (_numCharsLeft || !_isChinese)
printLine(str);
}
void TextDisplayer_rpg::printDialogueText(int stringId, const char *pageBreakString, const char*) {
const char *str = (const char *)(_screen->getCPagePtr(5) + READ_LE_UINT16(&_screen->getCPagePtr(5)[(stringId - 1) << 1]));
assert(strlen(str) < kEoBTextBufferSize);
Common::strlcpy(_dialogueBuffer, str, kEoBTextBufferSize);
_screen->set16bitShadingLevel(4);
int cs = (_vm->gameFlags().platform == Common::kPlatformPC98 && !_vm->gameFlags().use16ColorMode) ? _screen->setFontStyles(_screen->_currentFont, Font::kStyleFat) : -1;
displayText(_dialogueBuffer);
if (cs != -1)
_screen->setFontStyles(_screen->_currentFont, cs);
_screen->set16bitShadingLevel(0);
if (pageBreakString) {
if (pageBreakString[0]) {
_pageBreakString = pageBreakString;
displayWaitButton();
resetPageBreakString();
}
}
_vm->_dialogueFieldAmiga = true;
}
void TextDisplayer_rpg::printDialogueText(const char *str, bool wait) {
assert(Common::strnlen(str, kEoBTextBufferSize) < kEoBTextBufferSize);
Common::strlcpy(_dialogueBuffer, str, kEoBTextBufferSize);
int cs = (_vm->gameFlags().platform == Common::kPlatformPC98 && !_vm->gameFlags().use16ColorMode) ? _screen->setFontStyles(_screen->_currentFont, Font::kStyleFat) : -1;
displayText(_dialogueBuffer);
if (cs != -1)
_screen->setFontStyles(_screen->_currentFont, cs);
if (wait)
displayWaitButton();
}
void TextDisplayer_rpg::printMessage(const char *str, int textColor, ...) {
int tc = _textDimData[_screen->curDimIndex()].color1;
if (textColor != -1)
_textDimData[_screen->curDimIndex()].color1= textColor;
va_list args;
va_start(args, textColor);
vsnprintf(_dialogueBuffer, kEoBTextBufferSize - 1, str, args);
va_end(args);
displayText(_dialogueBuffer, textColor);
if (_vm->game() != GI_EOB1)
_textDimData[_screen->curDimIndex()].color1= tc;
if (!_screen->_curPage)
_screen->updateScreen();
}
int TextDisplayer_rpg::clearDim(int dim) {
int res = _screen->curDimIndex();
_screen->setScreenDim(dim);
_textDimData[dim].color1 = _screen->_curDim->col1;
_textDimData[dim].color2 = (_vm->game() == GI_LOL || _vm->gameFlags().platform == Common::kPlatformAmiga) ? _screen->_curDim->col2 : _vm->guiSettings()->colors.fill;
clearCurDim();
return res;
}
void TextDisplayer_rpg::clearCurDim() {
int d = _screen->curDimIndex();
const ScreenDim *tmp = _screen->getScreenDim(d);
int xOffs = 0;
int wOffs = 0;
int hOffs = 0;
if (_textDimData[d].shadowColor) {
if (tmp->sx > 0)
xOffs = wOffs = 1;
}
if (_pc98TextMode) {
--wOffs;
--hOffs;
}
_screen->fillRect((tmp->sx << 3) - xOffs, tmp->sy, ((tmp->sx + tmp->w) << 3) - 1 + wOffs, (tmp->sy + tmp->h) - 1 + hOffs, _textDimData[d].color2);
_lineCount = 0;
_textDimData[d].column = _textDimData[d].line = 0;
}
void TextDisplayer_rpg::textPageBreak() {
if (_vm->game() != GI_LOL)
SWAP(_vm->_dialogueButtonLabelColor1, _vm->_dialogueButtonLabelColor2);
int cp = _screen->setCurPage(0);
Screen::FontId cf = _screen->setFont(_waitButtonFont);
int cs = (_vm->gameFlags().platform == Common::kPlatformPC98 && !_vm->gameFlags().use16ColorMode) ? _screen->setFontStyles(_waitButtonFont, Font::kStyleFat) : -1;
if (_vm->game() == GI_LOL)
_vm->_timer->pauseSingleTimer(11, true);
_vm->_fadeText = false;
int resetPortraitAfterSpeechAnim = 0;
int updatePortraitSpeechAnimDuration = 0;
if (_vm->_updateCharNum != -1) {
resetPortraitAfterSpeechAnim = _vm->_resetPortraitAfterSpeechAnim;
_vm->_resetPortraitAfterSpeechAnim = 0;
updatePortraitSpeechAnimDuration = _vm->_updatePortraitSpeechAnimDuration;
if (_vm->_updatePortraitSpeechAnimDuration > 36)
_vm->_updatePortraitSpeechAnimDuration = 36;
}
uint32 speechPartTime = 0;
if (_vm->speechEnabled() && _vm->_activeVoiceFileTotalTime && _numCharsTotal)
speechPartTime = _vm->_system->getMillis() + ((_numCharsPrinted * _vm->_activeVoiceFileTotalTime) / _numCharsTotal);
int sdx = _screen->curDimIndex();
const ScreenDim *dim = _screen->getScreenDim(sdx);
int x = ((dim->sx + dim->w) << 3) - (_vm->_dialogueButtonWidth + 3);
int y = 0;
int w = _vm->_dialogueButtonWidth;
if (_vm->game() == GI_LOL) {
if (_isChinese) {
y = dim->sy + dim->h - 15;
} else if (_vm->_needSceneRestore && (_vm->_updateFlags & 2)) {
if (_vm->_currentControlMode || !(_vm->_updateFlags & 2)) {
y = dim->sy + dim->h - 5;
} else {
x += 6;
y = dim->sy + dim->h - 2;
}
} else {
y = dim->sy + dim->h - 10;
}
} else {
y = _vm->guiSettings()->buttons.waitY[_waitButtonMode];
x = _vm->guiSettings()->buttons.waitX[_waitButtonMode];
w = _vm->guiSettings()->buttons.waitWidth[_waitButtonMode];
}
if (_vm->game() == GI_LOL && _vm->gameFlags().use16ColorMode) {
_vm->gui_drawBox(x + 8, (y & ~7) - 1, 66, 10, 0xEE, 0xCC, -1);
_screen->printText(_pageBreakString.c_str(), (x + 37 - (_pageBreakString.size() << 1) + 4) & ~3, (y + 2) & ~7, 0xC1, 0);
} else {
_screen->set16bitShadingLevel(4);
_vm->gui_drawBox(x, y, w, _vm->guiSettings()->buttons.height, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill);
_screen->set16bitShadingLevel(0);
#if defined(ENABLE_EOB)
if (_vm->guiSettings()->buttons.labelShadow && _vm->game() != GI_LOL)
((Screen_EoB*)screen())->printShadedText(_pageBreakString.c_str(), x + (w >> 1) - (_vm->screen()->getTextWidth(_pageBreakString.c_str()) >> 1), y + _vm->guiSettings()->buttons.txtOffsY, _vm->_dialogueButtonLabelColor1, 0, _vm->guiSettings()->colors.guiColorBlack);
else
#endif
_screen->printText(_pageBreakString.c_str(), x + (w >> 1) - (_vm->screen()->getTextWidth(_pageBreakString.c_str()) >> 1), y + _vm->guiSettings()->buttons.txtOffsY, _vm->_dialogueButtonLabelColor1, 0);
}
_vm->removeInputTop();
bool loop = true;
bool target = false;
do {
int inputFlag = _vm->checkInput(0, false) & 0xFF;
_vm->removeInputTop();
while (!inputFlag && !_vm->shouldQuit()) {
_vm->update();
if (_vm->speechEnabled()) {
if (((_vm->_system->getMillis() > speechPartTime) || (_vm->snd_updateCharacterSpeech() != 2)) && speechPartTime) {
loop = false;
inputFlag = _vm->_keyMap[Common::KEYCODE_RETURN];
break;
}
}
inputFlag = _vm->checkInput(0, false) & 0xFF;
_vm->removeInputTop();
}
_vm->gui_notifyButtonListChanged();
if (inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN]) {
loop = false;
} else if (inputFlag == 199 || inputFlag == 201) {
if (_vm->posWithinRect(_vm->_mouseX, _vm->_mouseY, x, y, x + w, y + _vm->guiSettings()->buttons.height)) {
if (_vm->game() == GI_LOL)
target = true;
else
loop = false;
}
} else if (inputFlag == 200 || inputFlag == 202) {
if (target)
loop = false;
}
} while (loop && !_vm->shouldQuit());
_screen->set16bitShadingLevel(4);
if (_vm->game() == GI_LOL && _vm->gameFlags().use16ColorMode)
_screen->fillRect(x + 8, y, x + 57, y + _vm->guiSettings()->buttons.height, _textDimData[sdx].color2);
else
_screen->fillRect(x, y, x + w - 1, y + _vm->guiSettings()->buttons.height - 1, _textDimData[sdx].color2);
// Fix border overdraw glitch
if (_vm->game() == GI_EOB2 && _isChinese && y + _vm->guiSettings()->buttons.height == 200)
_screen->drawClippedLine(x, 199, x + w - 1, 199, _vm->guiSettings()->colors.frame1);
clearCurDim();
_screen->set16bitShadingLevel(0);
_screen->updateScreen();
if (_vm->game() == GI_LOL)
_vm->_timer->pauseSingleTimer(11, false);
if (_vm->_updateCharNum != -1) {
_vm->_resetPortraitAfterSpeechAnim = resetPortraitAfterSpeechAnim;
if (updatePortraitSpeechAnimDuration > 36)
updatePortraitSpeechAnimDuration -= 36;
else
updatePortraitSpeechAnimDuration >>= 1;
_vm->_updatePortraitSpeechAnimDuration = updatePortraitSpeechAnimDuration;
}
if (cs != -1)
_screen->setFontStyles(_waitButtonFont, cs);
_screen->setFont(cf);
_screen->setCurPage(cp);
if (_vm->game() != GI_LOL)
SWAP(_vm->_dialogueButtonLabelColor1, _vm->_dialogueButtonLabelColor2);
_vm->removeInputTop();
}
void TextDisplayer_rpg::displayWaitButton() {
_vm->_dialogueNumButtons = 1;
_vm->_dialogueButtonString[0] = _pageBreakString.c_str();
_vm->_dialogueButtonString[1] = 0;
_vm->_dialogueButtonString[2] = 0;
_vm->_dialogueHighlightedButton = 0;
_vm->_dialogueButtonPosX = &_vm->guiSettings()->buttons.waitX[_waitButtonMode];
_vm->_dialogueButtonPosY = &_vm->guiSettings()->buttons.waitY[_waitButtonMode];
_vm->_dialogueButtonWidth = _vm->guiSettings()->buttons.waitWidth[_waitButtonMode];
_vm->_dialogueButtonYoffs = 0;
SWAP(_vm->_dialogueButtonLabelColor1, _vm->_dialogueButtonLabelColor2);
_vm->drawDialogueButtons();
if (!_vm->shouldQuit())
_vm->removeInputTop();
while (!_vm->processDialogue() && !_vm->shouldQuit()) {}
_screen->set16bitShadingLevel(4);
_screen->fillRect(_vm->_dialogueButtonPosX[0], _vm->_dialogueButtonPosY[0], _vm->_dialogueButtonPosX[0] + _vm->_dialogueButtonWidth - 1, _vm->_dialogueButtonPosY[0] + _vm->guiSettings()->buttons.height - 1, _vm->guiSettings()->colors.fill);
clearCurDim();
_screen->set16bitShadingLevel(0);
_screen->updateScreen();
_vm->_dialogueButtonWidth = _vm->guiSettings()->buttons.width;
SWAP(_vm->_dialogueButtonLabelColor1, _vm->_dialogueButtonLabelColor2);
}
void TextDisplayer_rpg::convertString(char *str) {
static const char convertTable_EOB2_Amiga_DE[] = {
'\x84', '\x7F', '\x8E', '\x7F', '\x81', '\x7D', '\x9A', '\x7D', '\x94', '\x7E', '\x99', '\x7E', '\0', '\0'
};
const char *table = 0;
if (_vm->game() == GI_EOB2 && _vm->gameFlags().platform == Common::kPlatformAmiga && _vm->gameFlags().lang == Common::DE_DEU)
table = convertTable_EOB2_Amiga_DE;
if (!table)
return;
for (; *str; ++str) {
for (const char *c = table; *c; c += 2) {
if ((*str) == c[0])
*str = c[1];
}
}
}
bool TextDisplayer_rpg::isTwoByteChar(uint8 c) const {
if (_vm->gameFlags().lang == Common::JA_JPN)
return (c >= 0xE0 || (c > 0x80 && c < 0xA0));
else if (_isChinese)
return (c & 0x80);
return false;
}
void TextDisplayer_rpg::applySetting(int sd, int ix, int val) {
if (sd < -1 || sd >= _dimCount || ix >= kOutOfRange)
error("TextDisplayer_rpg::applySetting(): arg out of range");
const int *memberAddr[] = {
&_textDimData[0].lineSpacing,
&_textDimData[0].visualLineSpacingAdjust,
&_textDimData[0].charSpacing,
&_textDimData[0].shadowColor,
&_textDimData[0].noHalfWidthLineEnd
};
int offset = (const byte*)memberAddr[ix] - (const byte*)&_textDimData[0];
if (sd == -1) {
for (int i = 0; i < _dimCount; ++i)
*(int*)((byte*)&_textDimData[i] + offset) = val;
} else {
*(int*)((byte*)&_textDimData[sd] + offset) = val;
}
}
uint8 TextDisplayer_rpg::remapColor(int sd, uint8 color) const {
if (sd < -1 || sd >= _dimCount)
error("TextDisplayer_rpg::applySetting(): arg out of range");
// HACK: Apparently, this needs a better implementation (allowing to set
// mappings for col1 and col2 independently). But this will do for now...
if (_vm->gameFlags().platform == Common::kPlatformAmiga && sd != 7 && color == _textDimData[sd].color2)
return color;
if (sd != -1 && _textDimData[sd].colorMap != nullptr)
return _textDimData[sd].colorMap[color];
return _colorMap[color];
}
} // End of namespace Kyra
#endif // (ENABLE_EOB || ENABLE_LOL)

View File

@@ -0,0 +1,156 @@
/* 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/>.
*
*/
#if defined(ENABLE_EOB) || defined(ENABLE_LOL)
#ifndef KYRA_TEXT_EOB_H
#define KYRA_TEXT_EOB_H
#include "common/scummsys.h"
#include "kyra/graphics/screen.h"
namespace Kyra {
class KyraRpgEngine;
class TextDisplayer_rpg {
public:
TextDisplayer_rpg(KyraRpgEngine *engine, Screen *scr);
virtual ~TextDisplayer_rpg();
void setupField(int dim, bool mode);
virtual void printDialogueText(int stringId, const char *pageBreakString, const char *pageBreakString2 = 0);
virtual void printDialogueText(const char *str, bool wait = false);
void printMessage(const char *str, int textColor = -1, ...);
virtual void printShadedText(const char *str, int x = -1, int y = -1, int textColor = -1, int shadowColor = -1, int pitchW = -1, int pitchH = -1, int marginRight = 0, bool screenUpdate = true) {}
virtual int clearDim(int dim);
void clearCurDim();
void resetDimTextPositions(int dim);
void resetPageBreakString();
void setPageBreakFlag();
void removePageBreakFlag();
void allowPageBreak(bool mode) { _allowPageBreak = mode; }
void setWaitButtonMode(int mode) { _waitButtonMode = mode; }
int lineCount() const { return _lineCount; }
//const uint8 *colorMap() const { return _colorMap; }
// These methods are ScummVM specific. They are supposed to make necessary modifications
// to the text displayer for the various Japanese and Chinese versions without too much
// hackery...
void setColorMapping(int sd, uint8 from, uint8 to);
void setShadowColor(int sd, int col) { applySetting(sd, kShadowColor, col); }
void setLineSpacing(int sd, int spacing) { applySetting(sd, kLineSpacing, spacing); }
void setVisualLineSpacingAdjust(int sd, int adj) { applySetting(sd, kVisualLineSpacingAdjust, adj); }
void setCharSpacing(int sd, int spacing) { applySetting(sd, kCharSpacing, spacing); }
protected:
virtual KyraRpgEngine *vm() { return _vm; }
virtual Screen *screen() { return _screen; }
virtual void displayText(char *str, ...);
char parseCommand();
void readNextPara();
void printLine(char *str);
virtual void textPageBreak();
void displayWaitButton();
void convertString(char *str);
char *_dialogueBuffer;
const char *_tempString1;
const char *_tempString2;
char *_currentLine;
char _ctrl[3];
uint16 _lineWidth;
uint32 _numCharsTotal;
uint32 _numCharsLeft;
uint32 _numCharsPrinted;
bool _printFlag;
bool _twoByteLineBreakFlag;
const bool _pc98TextMode;
Common::String _pageBreakString;
Common::String _scriptParaString;
int _lineCount;
bool _allowPageBreak;
int _waitButtonSpace;
int _waitButtonMode;
static const char _pageBreakDefault[3][5];
struct TextDimData {
uint8 color1;
uint8 color2;
uint16 column;
uint8 line;
// These properties don't appear in the original code. The various Japanese and Chinese versions
// just had their modifications hacked in in whatever way the devs felt like. These properties
// help making the necessary adjustments without too much hackery...
int lineSpacing;
int visualLineSpacingAdjust; // LOL PC-98 has the worst hack here. The visual line spacing is different than the one that is used to measure the text field space.
int charSpacing;
int shadowColor;
int noHalfWidthLineEnd;
uint8 *colorMap;
};
TextDimData *_textDimData;
const int _dimCount;
KyraRpgEngine *_vm;
private:
bool isTwoByteChar(uint8 c) const;
void applySetting(int sd, int ix, int val);
uint8 remapColor(int sd, uint8 color) const;
enum TextFieldVar {
kLineSpacing = 0,
kVisualLineSpacingAdjust,
kCharSpacing,
kShadowColor,
kNoHalfWidthLineEnd,
kOutOfRange
};
Screen *_screen;
char *_table1;
char *_table2;
Screen::FontId _waitButtonFont;
uint8 _colorMap[256];
bool _isChinese;
};
} // End of namespace Kyra
#endif
#endif // ENABLE_EOB || ENABLE_LOL