scummvm/engines/kyra/text_v2.cpp

687 lines
18 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "kyra/text_v2.h"
#include "kyra/kyra_v2.h"
#include "kyra/resource.h"
#include "common/endian.h"
namespace Kyra {
TextDisplayer_v2::TextDisplayer_v2(KyraEngine_v2 *vm, Screen_v2 *screen)
: TextDisplayer(vm, screen), _vm(vm) {
}
void TextDisplayer_v2::backupTalkTextMessageBkgd(int srcPage, int dstPage) {
_screen->copyRegion(_talkCoords.x, _talkMessageY, 0, 144, _talkCoords.w, _talkMessageH, srcPage, dstPage);
}
void TextDisplayer_v2::restoreScreen() {
_vm->restorePage3();
_vm->drawAnimObjects();
_screen->hideMouse();
_screen->copyRegion(_talkCoords.x, _talkMessageY, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, 2, 0);
_screen->showMouse();
_vm->flagAnimObjsForRefresh();
_vm->refreshAnimObjects(0);
}
char *TextDisplayer_v2::preprocessString(const char *str) {
debugC(9, kDebugLevelMain, "TextDisplayer_v2::preprocessString('%s')", str);
if (str != _talkBuffer) {
assert(strlen(str) < sizeof(_talkBuffer) - 1);
strcpy(_talkBuffer, str);
}
char *p = _talkBuffer;
while (*p) {
if (*p == '\r')
return _talkBuffer;
++p;
}
p = _talkBuffer;
Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT);
_screen->_charWidth = -2;
int textWidth = _screen->getTextWidth(p);
_screen->_charWidth = 0;
// longer text strings for German versions
int maxTextWidth = (_vm->language() == 2 ? 240 : 176);
if (textWidth > maxTextWidth) {
if (textWidth > (maxTextWidth*2)) {
int count = getCharLength(p, textWidth / 3);
int offs = dropCRIntoString(p, count);
p += count + offs;
_screen->_charWidth = -2;
textWidth = _screen->getTextWidth(p);
_screen->_charWidth = 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_v2::calcWidestLineBounds(int &x1, int &x2, int w, int x) {
debugC(9, kDebugLevelMain, "TextDisplayer_v2::calcWidestLineBounds(%d, %d)", w, 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_v2::chatGetType(const char *str) {
str += strlen(str);
--str;
switch (*str) {
case '!':
return 2;
case ')':
return -1;
case '?':
return 1;
default:
return 0;
}
}
int KyraEngine_v2::chatCalcDuration(const char *str) {
static const uint8 durationMultiplicator[] = { 16, 14, 12, 10, 8, 8, 7, 6, 5, 4 };
// TODO / HACK: imlement this correctly
const int _configTextspeed = 50;
int duration = strlen(str);
duration *= _flags.isTalkie ? 8 : durationMultiplicator[(_configTextspeed / 10)];
return MAX<int>(duration, 120);
}
void KyraEngine_v2::objectChat(const char *str, int object, int vocHigh, int vocLow) {
setNextIdleAnimTimer();
_chatVocHigh = _chatVocLow = -1;
objectChatInit(str, object, vocHigh, vocLow);
_chatText = str;
_chatObject = object;
_chatIsNote = (chatGetType(str) == -1);
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 + object < ARRAYSIZE(talkScriptTable));
int script = talkScriptTable[_mainCharacter.facing * 3 + object];
static const char *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 = 0;
_chatObject = -1;
setNextIdleAnimTimer();
}
void KyraEngine_v2::objectChatInit(const char *str, int object, int vocHigh, int vocLow) {
str = _text->preprocessString(str);
int lineNum = _text->buildMessageSubstrings(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 * 10;
yPos = MAX(yPos, 0);
_text->_talkMessageY = yPos;
_text->_talkMessageH = lineNum*10;
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);
_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_v2::objectChatPrintText(const char *str, int object) {
int c1 = _talkObjectList[object].color;
str = _text->preprocessString(str);
int lineNum = _text->buildMessageSubstrings(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 = &_text->_talkSubstrings[i*_text->maxSubstringLen()];
int y = _text->_talkMessageY + i * 10;
x = _text->getCenterStringX(str, cX1, cX2);
_text->printText(str, x, y, c1, 0xCF, 0);
}
}
void KyraEngine_v2::objectChatProcess(const char *script) {
memset(&_chatScriptData, 0, sizeof(_chatScriptData));
memset(&_chatScriptState, 0, sizeof(_chatScriptState));
_scriptInterpreter->loadScript(script, &_chatScriptData, &_opcodesTemporary);
_scriptInterpreter->initScript(&_chatScriptState, &_chatScriptData);
_scriptInterpreter->startScript(&_chatScriptState, 0);
while (_scriptInterpreter->validScript(&_chatScriptState))
_scriptInterpreter->runScript(&_chatScriptState);
_newShapeFilename[2] = _loadedZTable + '0';
uint8 *shapeBuffer = _res->fileData(_newShapeFilename, 0);
if (shapeBuffer) {
int shapeCount = initNewShapes(shapeBuffer);
if (_chatVocHigh >= 0) {
playVoice(_chatVocHigh, _chatVocLow);
_chatVocHigh = _chatVocLow = -1;
}
objectChatWaitToFinish();
resetNewShapes(shapeCount, shapeBuffer);
} else {
warning("couldn't load file '%s'", _newShapeFilename);
}
_scriptInterpreter->unloadScript(&_chatScriptData);
}
void KyraEngine_v2::objectChatWaitToFinish() {
int charAnimFrame = _mainCharacter.animFrame;
setCharacterAnimDim(_newShapeWidth, _newShapeHeight);
_scriptInterpreter->initScript(&_chatScriptState, &_chatScriptData);
_scriptInterpreter->startScript(&_chatScriptState, 1);
bool running = true;
const uint32 endTime = _chatEndTime;
while (running && !_quitFlag) {
if (!_scriptInterpreter->validScript(&_chatScriptState))
_scriptInterpreter->startScript(&_chatScriptState, 1);
_temporaryScriptExecBit = false;
while (!_temporaryScriptExecBit && _scriptInterpreter->validScript(&_chatScriptState))
_scriptInterpreter->runScript(&_chatScriptState);
int curFrame = _newShapeAnimFrame;
uint32 delayTime = _newShapeDelay;
if (!_chatIsNote)
_mainCharacter.animFrame = 33 + curFrame;
updateCharacterAnim(0);
uint32 nextFrame = _system->getMillis() + delayTime * _tickLength;
while (_system->getMillis() < nextFrame && !_quitFlag) {
updateWithText();
int inputFlag = checkInput(0);
removeInputTop();
if (inputFlag == 198 || inputFlag == 199) {
//XXX
_skipFlag = true;
snd_stopVoice();
}
const uint32 curTime = _system->getMillis();
if ((textEnabled() && curTime > endTime) || (speechEnabled() && !textEnabled() && !snd_voiceIsPlaying()) || _skipFlag) {
_skipFlag = false;
nextFrame = curTime;
running = false;
}
delay(10);
}
}
_mainCharacter.animFrame = charAnimFrame;
updateCharacterAnim(0);
resetCharacterAnimDim();
}
void KyraEngine_v2::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_v2::zanthSceneStartupChat() {
int tableIndex = _mainCharacter.sceneId - READ_LE_UINT16(&_ingameTalkObjIndex[5 + _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_v2::zanthRandomChat() {
int tableIndex = _mainCharacter.sceneId - READ_LE_UINT16(&_ingameTalkObjIndex[5 + _newChapterFile]);
if (queryGameFlag(0x164))
return;
int csEntry, vocH, scIndex1, unused;
updateDlgBuffer();
loadDlgHeader(csEntry, vocH, scIndex1, unused);
if (_unkFlag1) {
_unkFlag1 = 0;
tableIndex += 2;
} else {
_unkFlag1 = 1;
}
uint8 bufferIndex = 8 + scIndex1 * 6 + tableIndex * 2;
int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
processDialogue(offs, vocH, csEntry);
}
void KyraEngine_v2::updateDlgBuffer() {
static const char DlgFileTemplate[] = "CH**-S**.DLG";
char filename[13];
filename[12] = 0;
memcpy(filename, DlgFileTemplate, 12);
static const char suffixTalkie[] = "EFG";
static const char suffixTowns[] = "G J";
const char * suffix = _flags.isTalkie ? suffixTalkie : suffixTowns;
filename[2] = (char)((_currentChapter / 10 + 48) & 0xFF);
filename[3] = (char)((_currentChapter % 10 + 48) & 0xFF);
if (!(_flags.platform == Common::kPlatformPC && !_flags.isTalkie))
filename[11] = suffix[_lang];
if (_currentChapter == _npcTalkChpIndex && _mainCharacter.dlgIndex == _npcTalkDlgIndex)
return;
_npcTalkChpIndex = _currentChapter;
_npcTalkDlgIndex = _mainCharacter.dlgIndex;
filename[6] = (char)((_npcTalkDlgIndex / 10 + 48) & 0xFF);
filename[7] = (char)((_npcTalkDlgIndex % 10 + 48) & 0xFF);
if (_dlgBuffer)
delete [] _dlgBuffer;
_dlgBuffer = _res->fileData(filename, 0);
}
void KyraEngine_v2::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_v2::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);
setNewDlgIndex(csEntry);
offs += 2;
} else {
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;
}
memcpy(_unkBuf500Bytes, _dlgBuffer + offs, len);
_unkBuf500Bytes[len] = 0;
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;
memcpy(_unkBuf500Bytes, _dlgBuffer + offs, len);
_unkBuf500Bytes[len] = 0;
offs += len;
}
if (_unkBuf500Bytes[0]) {
if ((!_flags.isTalkie && cmd == 11) || (_flags.isTalkie && cmd == 12)) {
if (activeTimSequence > -1) {
deinitTalkObject(activeTimSequence);
activeTimSequence = -1;
}
objectChat((const char*) _unkBuf500Bytes, 0, vocHi, vocLo);
} else {
if (activeTimSequence != nextTimSequence ) {
if (activeTimSequence > -1) {
deinitTalkObject(activeTimSequence);
activeTimSequence = -1;
}
initTalkObject(nextTimSequence);
activeTimSequence = nextTimSequence;
}
npcChatSequence((const char *)_unkBuf500Bytes, nextTimSequence, vocHi, vocLo);
}
}
}
}
if (activeTimSequence > -1)
deinitTalkObject(activeTimSequence);
_screen->showMouse();
}
void KyraEngine_v2::initTalkObject(int index) {
TalkObject &object = _talkObjectList[index];
char STAFilename[13];
char ENDFilename[13];
strcpy(STAFilename, object.filename);
strcpy(_TLKFilename, object.filename);
strcpy(ENDFilename, object.filename);
strcpy(STAFilename + 4, "_STA.TIM");
strcpy(_TLKFilename + 4, "_TLK.TIM");
strcpy(ENDFilename + 4, "_END.TIM");
_currentTalkSections.STATim = tim_loadFile(STAFilename, NULL, 0);
_currentTalkSections.TLKTim = tim_loadFile(_TLKFilename, NULL, 0);
_currentTalkSections.ENDTim = tim_loadFile(ENDFilename, NULL, 0);
if (object.scriptId != -1) {
_specialSceneScriptStateBackup[object.scriptId] = _specialSceneScriptState[object.scriptId];
_specialSceneScriptState[object.scriptId] = 1;
}
if (_currentTalkSections.STATim) {
_objectChatFinished = false;
while (!_objectChatFinished) {
tim_processSequence(_currentTalkSections.STATim, 0);
if (_chatText)
updateWithText();
else
update();
}
}
}
void KyraEngine_v2::deinitTalkObject(int index) {
TalkObject &object = _talkObjectList[index];
if (_currentTalkSections.ENDTim) {
_objectChatFinished = false;
while (!_objectChatFinished) {
tim_processSequence(_currentTalkSections.ENDTim, 0);
if (_chatText)
updateWithText();
else
update();
}
}
if (object.scriptId != -1) {
_specialSceneScriptState[object.scriptId] = _specialSceneScriptStateBackup[object.scriptId];
}
if (_currentTalkSections.STATim != NULL) {
tim_releaseBuffer(_currentTalkSections.STATim);
_currentTalkSections.STATim = NULL;
}
if (_currentTalkSections.TLKTim != NULL) {
tim_releaseBuffer(_currentTalkSections.TLKTim);
_currentTalkSections.TLKTim = NULL;
}
if (_currentTalkSections.ENDTim != NULL) {
tim_releaseBuffer(_currentTalkSections.ENDTim);
_currentTalkSections.ENDTim = NULL;
}
}
void KyraEngine_v2::npcChatSequence(const char *str, int objectId, int vocHigh, int vocLow) {
_chatText = str;
_chatObject = objectId;
objectChatInit(str, objectId, vocHigh, vocLow);
if (!_currentTalkSections.TLKTim)
_currentTalkSections.TLKTim = tim_loadFile(_TLKFilename, 0, 0);
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())) && !(_quitFlag || _skipFlag)) {
if (!speechEnabled() && chatAnimEndTime > _system->getMillis() || speechEnabled() && snd_voiceIsPlaying()) {
_objectChatFinished = false;
while (!_objectChatFinished && !_skipFlag) {
if (_currentTalkSections.TLKTim)
tim_processSequence(_currentTalkSections.TLKTim, 0);
else
_objectChatFinished = false;
updateWithText();
int inputFlag = checkInput(0);
removeInputTop();
if (inputFlag == 198 || inputFlag == 199) {
//XXX
_skipFlag = true;
snd_stopVoice();
}
delay(10);
}
if (_currentTalkSections.TLKTim)
tim_o_abort(0);
}
updateWithText();
}
_skipFlag = false;
if (_currentTalkSections.TLKTim) {
tim_releaseBuffer(_currentTalkSections.TLKTim);
_currentTalkSections.TLKTim = 0;
}
_text->restoreScreen();
_chatText = 0;
_chatObject = -1;
setNextIdleAnimTimer();
}
void KyraEngine_v2::setNewDlgIndex(int dlgIndex) {
if (dlgIndex == _mainCharacter.dlgIndex)
return;
memset(_newSceneDlgState, 0, 32);
for (int i = 0; i < 19; i++)
memset(_conversationState[i], -1, 14);
_unkFlag1 = false;
_mainCharacter.dlgIndex = dlgIndex;
}
} // end of namespace Kyra