scummvm/engines/sky/text.cpp
2014-02-18 02:39:38 +01:00

501 lines
13 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.
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "sky/disk.h"
#include "sky/logic.h"
#include "sky/text.h"
#include "sky/sky.h"
#include "sky/skydefs.h"
#include "sky/struc.h"
#include "sky/compact.h"
namespace Sky {
#define FIRST_TEXT_SEC 77
#define FIRST_TEXT_BUFFER 274
#define LAST_TEXT_BUFFER 284
#define NO_OF_TEXT_SECTIONS 8 // 8 sections per language
#define CHAR_SET_FILE 60150
#define MAX_SPEECH_SECTION 7
#define CHAR_SET_HEADER 128
#define MAX_NO_LINES 10
Text::Text(Disk *skyDisk, SkyCompact *skyCompact) {
_skyDisk = skyDisk;
_skyCompact = skyCompact;
initHuffTree();
_mainCharacterSet.addr = _skyDisk->loadFile(CHAR_SET_FILE);
_mainCharacterSet.charHeight = MAIN_CHAR_HEIGHT;
_mainCharacterSet.charSpacing = 0;
fnSetFont(0);
if (!SkyEngine::isDemo()) {
_controlCharacterSet.addr = _skyDisk->loadFile(60520);
_controlCharacterSet.charHeight = 12;
_controlCharacterSet.charSpacing = 0;
_linkCharacterSet.addr = _skyDisk->loadFile(60521);
_linkCharacterSet.charHeight = 12;
_linkCharacterSet.charSpacing = 1;
} else {
_controlCharacterSet.addr = NULL;
_linkCharacterSet.addr = NULL;
}
}
Text::~Text() {
for (int i = FIRST_TEXT_BUFFER; i <= LAST_TEXT_BUFFER; i++)
if (SkyEngine::_itemList[i]) {
free(SkyEngine::_itemList[i]);
SkyEngine::_itemList[i] = NULL;
}
free(_mainCharacterSet.addr);
free(_controlCharacterSet.addr);
free(_linkCharacterSet.addr);
}
void Text::fnSetFont(uint32 fontNr) {
charSet *newCharSet;
switch (fontNr) {
case 0:
newCharSet = &_mainCharacterSet;
break;
case 1:
newCharSet = &_linkCharacterSet;
break;
case 2:
newCharSet = &_controlCharacterSet;
break;
default:
error("Tried to set invalid font (%d)", fontNr);
}
_curCharSet = fontNr;
_characterSet = newCharSet->addr;
_charHeight = (byte)newCharSet->charHeight;
_dtCharSpacing = newCharSet->charSpacing;
}
void Text::fnTextModule(uint32 textInfoId, uint32 textNo) {
fnSetFont(1);
uint16* msgData = (uint16 *)_skyCompact->fetchCpt(textInfoId);
DisplayedText textId = lowTextManager(textNo, msgData[1], msgData[2], 209, false);
Logic::_scriptVariables[RESULT] = textId.compactNum;
Compact *textCompact = _skyCompact->fetchCpt(textId.compactNum);
textCompact->xcood = msgData[3];
textCompact->ycood = msgData[4];
fnSetFont(0);
}
void Text::getText(uint32 textNr) { //load text #"textNr" into textBuffer
if (patchMessage(textNr))
return;
uint32 sectionNo = (textNr & 0x0F000) >> 12;
if (SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] == NULL) { //check if already loaded
debug(5, "Loading Text item(s) for Section %d", (sectionNo >> 2));
uint32 fileNo = sectionNo + ((SkyEngine::_systemVars.language * NO_OF_TEXT_SECTIONS) + 60600);
SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] = (void **)_skyDisk->loadFile((uint16)fileNo);
}
uint8 *textDataPtr = (uint8 *)SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo];
uint32 offset = 0;
uint32 blockNr = textNr & 0xFE0;
textNr &= 0x1F;
if (blockNr) {
uint16 *blockPtr = (uint16 *)(textDataPtr + 4);
uint32 nr32MsgBlocks = blockNr >> 5;
do {
offset += READ_LE_UINT16(blockPtr);
blockPtr++;
} while (--nr32MsgBlocks);
}
if (textNr) {
uint8 *blockPtr = textDataPtr + blockNr + READ_LE_UINT16(textDataPtr);
do {
uint16 skipBytes = *blockPtr++;
if (skipBytes & 0x80) {
skipBytes &= 0x7F;
skipBytes <<= 3;
}
offset += skipBytes;
} while (--textNr);
}
uint32 bitPos = offset & 3;
offset >>= 2;
offset += READ_LE_UINT16(textDataPtr + 2);
textDataPtr += offset;
//bit pointer: 0->8, 1->6, 2->4 ...
bitPos ^= 3;
bitPos++;
bitPos <<= 1;
char *dest = (char *)_textBuffer;
char textChar;
do {
textChar = getTextChar(&textDataPtr, &bitPos);
*dest++ = textChar;
} while (textChar);
}
void Text::fnPointerText(uint32 pointedId, uint16 mouseX, uint16 mouseY) {
Compact *ptrComp = _skyCompact->fetchCpt(pointedId);
DisplayedText text = lowTextManager(ptrComp->cursorText, TEXT_MOUSE_WIDTH, L_CURSOR, 242, false);
Logic::_scriptVariables[CURSOR_ID] = text.compactNum;
if (Logic::_scriptVariables[MENU]) {
_mouseOfsY = TOP_LEFT_Y - 2;
if (mouseX < 150)
_mouseOfsX = TOP_LEFT_X + 24;
else
_mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
} else {
_mouseOfsY = TOP_LEFT_Y - 10;
if (mouseX < 150)
_mouseOfsX = TOP_LEFT_X + 13;
else
_mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
}
Compact *textCompact = _skyCompact->fetchCpt(text.compactNum);
logicCursor(textCompact, mouseX, mouseY);
}
void Text::logicCursor(Compact *textCompact, uint16 mouseX, uint16 mouseY) {
textCompact->xcood = (uint16)(mouseX + _mouseOfsX);
textCompact->ycood = (uint16)(mouseY + _mouseOfsY);
if (textCompact->ycood < TOP_LEFT_Y)
textCompact->ycood = TOP_LEFT_Y;
}
bool Text::getTextBit(uint8 **data, uint32 *bitPos) {
if (*bitPos) {
(*bitPos)--;
} else {
(*data)++;
*bitPos = 7;
}
return (bool)(((**data) >> (*bitPos)) & 1);
}
char Text::getTextChar(uint8 **data, uint32 *bitPos) {
int pos = 0;
while (1) {
if (getTextBit(data, bitPos))
pos = _huffTree[pos].rChild;
else
pos = _huffTree[pos].lChild;
if (_huffTree[pos].lChild == 0 && _huffTree[pos].rChild == 0) {
return _huffTree[pos].value;
}
}
}
DisplayedText Text::displayText(uint32 textNum, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
//Render text into buffer *dest
getText(textNum);
return displayText(_textBuffer, dest, center, pixelWidth, color);
}
DisplayedText Text::displayText(char *textPtr, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
//Render text pointed to by *textPtr in buffer *dest
uint32 centerTable[10];
uint16 lineWidth = 0;
uint32 numLines = 0;
_numLetters = 2;
// work around bug #778105 (line width exceeded)
char *tmpPtr = strstr(textPtr, "MUND-BEATMUNG!");
if (tmpPtr)
strcpy(tmpPtr, "MUND BEATMUNG!");
// work around bug #1151924 (line width exceeded when talking to gardener using spanish text)
// This text apparently only is broken in the floppy versions, the CD versions contain
// the correct string "MANIFESTACION - ARTISTICA.", which doesn't break the algorithm/game.
tmpPtr = strstr(textPtr, "MANIFESTACION-ARTISTICA.");
if (tmpPtr)
strcpy(tmpPtr, "MANIFESTACION ARTISTICA.");
char *curPos = textPtr;
char *lastSpace = textPtr;
uint8 textChar = (uint8)*curPos++;
while (textChar >= 0x20) {
if ((_curCharSet == 1) && (textChar >= 0x80))
textChar = 0x20;
textChar -= 0x20;
if (textChar == 0) {
lastSpace = curPos; //keep track of last space
centerTable[numLines] = lineWidth;
}
lineWidth += _characterSet[textChar]; //add character width
lineWidth += (uint16)_dtCharSpacing; //include character spacing
if (pixelWidth <= lineWidth) {
if (*(lastSpace-1) == 10)
error("line width exceeded");
*(lastSpace-1) = 10;
lineWidth = 0;
numLines++;
curPos = lastSpace; //go back for new count
}
textChar = (uint8)*curPos++;
_numLetters++;
}
uint32 dtLastWidth = lineWidth; //save width of last line
centerTable[numLines] = lineWidth; //and update centering table
numLines++;
if (numLines > MAX_NO_LINES)
error("Maximum no. of lines exceeded");
uint32 dtLineSize = pixelWidth * _charHeight;
uint32 numBytes = (dtLineSize * numLines) + sizeof(DataFileHeader) + 4;
if (!dest)
dest = (uint8 *)malloc(numBytes);
// clear text sprite buffer
memset(dest + sizeof(DataFileHeader), 0, numBytes - sizeof(DataFileHeader));
//make the header
((DataFileHeader *)dest)->s_width = pixelWidth;
((DataFileHeader *)dest)->s_height = (uint16)(_charHeight * numLines);
((DataFileHeader *)dest)->s_sp_size = (uint16)(pixelWidth * _charHeight * numLines);
((DataFileHeader *)dest)->s_offset_x = 0;
((DataFileHeader *)dest)->s_offset_y = 0;
//reset position
curPos = textPtr;
uint8 *curDest = dest + sizeof(DataFileHeader); //point to where pixels start
byte *prevDest = curDest;
uint32 *centerTblPtr = centerTable;
do {
if (center) {
uint32 width = (pixelWidth - *centerTblPtr) >> 1;
centerTblPtr++;
curDest += width;
}
textChar = (uint8)*curPos++;
while (textChar >= 0x20) {
makeGameCharacter(textChar - 0x20, _characterSet, curDest, color, pixelWidth);
textChar = *curPos++;
}
prevDest = curDest = prevDest + dtLineSize; //start of last line + start of next
} while (textChar >= 10);
DisplayedText ret;
memset(&ret, 0, sizeof(ret));
ret.textData = dest;
ret.textWidth = dtLastWidth;
return ret;
}
void Text::makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
bool maskBit, dataBit;
uint8 charWidth = (uint8)((*(charSetPtr + textChar)) + 1 - _dtCharSpacing);
uint16 data, mask;
byte *charSpritePtr = charSetPtr + (CHAR_SET_HEADER + ((_charHeight << 2) * textChar));
byte *startPos = dest;
byte *curPos = startPos;
for (int i = 0; i < _charHeight; i++) {
byte *prevPos = curPos;
data = READ_BE_UINT16(charSpritePtr);
mask = READ_BE_UINT16(charSpritePtr + 2);
charSpritePtr += 4;
for (int j = 0; j < charWidth; j++) {
maskBit = (mask & 0x8000) != 0; //check mask
mask <<= 1;
dataBit = (data & 0x8000) != 0; //check data
data <<= 1;
if (maskBit) {
if (dataBit)
*curPos = color;
else
*curPos = 240; //black edge
}
curPos++;
}
//advance a line
curPos = prevPos + bufPitch;
}
//update position
dest = startPos + charWidth + _dtCharSpacing * 2 - 1;
}
DisplayedText Text::lowTextManager(uint32 textNum, uint16 width, uint16 logicNum, uint8 color, bool center) {
getText(textNum);
DisplayedText textInfo = displayText(_textBuffer, NULL, center, width, color);
uint32 compactNum = FIRST_TEXT_COMPACT;
Compact *cpt = _skyCompact->fetchCpt(compactNum);
while (cpt->status != 0) {
compactNum++;
cpt = _skyCompact->fetchCpt(compactNum);
}
cpt->flag = (uint16)(compactNum - FIRST_TEXT_COMPACT) + FIRST_TEXT_BUFFER;
if (SkyEngine::_itemList[cpt->flag])
free(SkyEngine::_itemList[cpt->flag]);
SkyEngine::_itemList[cpt->flag] = textInfo.textData;
cpt->logic = logicNum;
cpt->status = ST_LOGIC | ST_FOREGROUND | ST_RECREATE;
cpt->screen = (uint16) Logic::_scriptVariables[SCREEN];
textInfo.compactNum = (uint16)compactNum;
return textInfo;
}
void Text::changeTextSpriteColor(uint8 *sprData, uint8 newCol) {
DataFileHeader *header = (DataFileHeader *)sprData;
sprData += sizeof(DataFileHeader);
for (uint16 cnt = 0; cnt < header->s_sp_size; cnt++)
if (sprData[cnt] >= 241)
sprData[cnt] = newCol;
}
uint32 Text::giveCurrentCharSet() {
return _curCharSet;
}
void Text::initHuffTree() {
switch (SkyEngine::_systemVars.gameVersion) {
case 109:
_huffTree = _huffTree_00109;
break;
case 272: // FIXME: Extract data
case 267:
_huffTree = _huffTree_00267;
break;
case 288:
_huffTree = _huffTree_00288;
break;
case 303:
_huffTree = _huffTree_00303;
break;
case 331:
_huffTree = _huffTree_00331;
break;
case 348:
_huffTree = _huffTree_00348;
break;
case 365:
_huffTree = _huffTree_00365;
break;
case 368:
_huffTree = _huffTree_00368;
break;
case 372:
_huffTree = _huffTree_00372;
break;
default:
error("Unknown game version %d", SkyEngine::_systemVars.gameVersion);
}
}
bool Text::patchMessage(uint32 textNum) {
uint16 patchIdx = _patchLangIdx[SkyEngine::_systemVars.language];
uint16 patchNum = _patchLangNum[SkyEngine::_systemVars.language];
for (uint16 cnt = 0; cnt < patchNum; cnt++) {
if (_patchedMessages[cnt + patchIdx].textNr == textNum) {
strcpy(_textBuffer, _patchedMessages[cnt + patchIdx].text);
return true;
}
}
return false;
}
const PatchMessage Text::_patchedMessages[NUM_PATCH_MSG] = {
{ 28724, "Testo e Parlato" }, // - italian
{ 28707, "Solo Testo" },
{ 28693, "Solo Parlato" },
{ 28724, "Text och tal" }, // - swedish
{ 28707, "Endast text" },
{ 28693, "Endast tal" },
{ 28686, "Musikvolym" },
{ 4336, "Wir befinden uns EINHUNDERTZWANZIG METER #ber dem ERBODEN!" }, // - german
{ 28686, "Volume de musique" }, // - french
};
const uint16 Text::_patchLangIdx[8] = {
0xFFFF, // SKY_ENGLISH
7, // SKY_GERMAN
8, // SKY_FRENCH
0xFFFF, // SKY_USA
3, // SKY_SWEDISH
0, // SKY_ITALIAN
0xFFFF, // SKY_PORTUGUESE
0xFFFF // SKY_SPANISH
};
const uint16 Text::_patchLangNum[8] = {
0, // SKY_ENGLISH
1, // SKY_GERMAN
1, // SKY_FRENCH
0, // SKY_USA
4, // SKY_SWEDISH
3, // SKY_ITALIAN
0, // SKY_PORTUGUESE
0 // SKY_SPANISH
};
} // End of namespace Sky