1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23
24 #include "common/debug.h"
25 #include "common/endian.h"
26 #include "common/textconsole.h"
27
28 #include "sky/disk.h"
29 #include "sky/logic.h"
30 #include "sky/text.h"
31 #include "sky/sky.h"
32 #include "sky/skydefs.h"
33 #include "sky/struc.h"
34 #include "sky/compact.h"
35
36 namespace Sky {
37
38 #define FIRST_TEXT_SEC 77
39 #define FIRST_TEXT_BUFFER 274
40 #define LAST_TEXT_BUFFER 284
41 #define NO_OF_TEXT_SECTIONS 8 // 8 sections per language
42 #define CHAR_SET_FILE 60150
43 #define MAX_SPEECH_SECTION 7
44 #define CHAR_SET_HEADER 128
45 #define MAX_NO_LINES 10
46
Text(Disk * skyDisk,SkyCompact * skyCompact)47 Text::Text(Disk *skyDisk, SkyCompact *skyCompact) {
48 _skyDisk = skyDisk;
49 _skyCompact = skyCompact;
50
51 initHuffTree();
52
53 _mainCharacterSet.addr = _skyDisk->loadFile(CHAR_SET_FILE);
54 _mainCharacterSet.charHeight = MAIN_CHAR_HEIGHT;
55 _mainCharacterSet.charSpacing = 0;
56
57 fnSetFont(0);
58
59 if (!SkyEngine::isDemo()) {
60 _controlCharacterSet.addr = _skyDisk->loadFile(60520);
61 _controlCharacterSet.charHeight = 12;
62 _controlCharacterSet.charSpacing = 0;
63
64 _linkCharacterSet.addr = _skyDisk->loadFile(60521);
65 _linkCharacterSet.charHeight = 12;
66 _linkCharacterSet.charSpacing = 1;
67 } else {
68 _controlCharacterSet.addr = NULL;
69 _linkCharacterSet.addr = NULL;
70 }
71 }
72
~Text()73 Text::~Text() {
74 for (int i = FIRST_TEXT_BUFFER; i <= LAST_TEXT_BUFFER; i++)
75 if (SkyEngine::_itemList[i]) {
76 free(SkyEngine::_itemList[i]);
77 SkyEngine::_itemList[i] = NULL;
78 }
79
80 free(_mainCharacterSet.addr);
81 free(_controlCharacterSet.addr);
82 free(_linkCharacterSet.addr);
83 }
84
fnSetFont(uint32 fontNr)85 void Text::fnSetFont(uint32 fontNr) {
86 charSet *newCharSet;
87
88 switch (fontNr) {
89 case 0:
90 newCharSet = &_mainCharacterSet;
91 break;
92 case 1:
93 newCharSet = &_linkCharacterSet;
94 break;
95 case 2:
96 newCharSet = &_controlCharacterSet;
97 break;
98 default:
99 error("Tried to set invalid font (%d)", fontNr);
100 }
101
102 _curCharSet = fontNr;
103 _characterSet = newCharSet->addr;
104 _charHeight = (byte)newCharSet->charHeight;
105 _dtCharSpacing = newCharSet->charSpacing;
106 }
107
fnTextModule(uint32 textInfoId,uint32 textNo)108 void Text::fnTextModule(uint32 textInfoId, uint32 textNo) {
109 fnSetFont(1);
110 uint16* msgData = (uint16 *)_skyCompact->fetchCpt(textInfoId);
111 DisplayedText textId = lowTextManager(textNo, msgData[1], msgData[2], 209, false);
112 Logic::_scriptVariables[RESULT] = textId.compactNum;
113 Compact *textCompact = _skyCompact->fetchCpt(textId.compactNum);
114 textCompact->xcood = msgData[3];
115 textCompact->ycood = msgData[4];
116 fnSetFont(0);
117 }
118
getText(uint32 textNr)119 void Text::getText(uint32 textNr) { //load text #"textNr" into textBuffer
120 if (patchMessage(textNr))
121 return;
122
123 uint32 sectionNo = (textNr & 0x0F000) >> 12;
124
125 if (SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] == NULL) { //check if already loaded
126 debug(5, "Loading Text item(s) for Section %d", (sectionNo >> 2));
127
128 uint32 fileNo = sectionNo + ((SkyEngine::_systemVars.language * NO_OF_TEXT_SECTIONS) + 60600);
129 SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] = (void **)_skyDisk->loadFile((uint16)fileNo);
130 }
131 uint8 *textDataPtr = (uint8 *)SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo];
132
133 uint32 offset = 0;
134
135 uint32 blockNr = textNr & 0xFE0;
136 textNr &= 0x1F;
137
138 if (blockNr) {
139 uint16 *blockPtr = (uint16 *)(textDataPtr + 4);
140 uint32 nr32MsgBlocks = blockNr >> 5;
141
142 do {
143 offset += READ_LE_UINT16(blockPtr);
144 blockPtr++;
145 } while (--nr32MsgBlocks);
146 }
147
148 if (textNr) {
149 uint8 *blockPtr = textDataPtr + blockNr + READ_LE_UINT16(textDataPtr);
150 do {
151 uint16 skipBytes = *blockPtr++;
152 if (skipBytes & 0x80) {
153 skipBytes &= 0x7F;
154 skipBytes <<= 3;
155 }
156 offset += skipBytes;
157 } while (--textNr);
158 }
159
160 uint32 bitPos = offset & 3;
161 offset >>= 2;
162 offset += READ_LE_UINT16(textDataPtr + 2);
163
164 textDataPtr += offset;
165
166 //bit pointer: 0->8, 1->6, 2->4 ...
167 bitPos ^= 3;
168 bitPos++;
169 bitPos <<= 1;
170
171 char *dest = (char *)_textBuffer;
172 char textChar;
173
174 do {
175 textChar = getTextChar(&textDataPtr, &bitPos);
176 *dest++ = textChar;
177 } while (textChar);
178 }
179
fnPointerText(uint32 pointedId,uint16 mouseX,uint16 mouseY)180 void Text::fnPointerText(uint32 pointedId, uint16 mouseX, uint16 mouseY) {
181 Compact *ptrComp = _skyCompact->fetchCpt(pointedId);
182 DisplayedText text = lowTextManager(ptrComp->cursorText, TEXT_MOUSE_WIDTH, L_CURSOR, 242, false);
183 Logic::_scriptVariables[CURSOR_ID] = text.compactNum;
184 if (Logic::_scriptVariables[MENU]) {
185 _mouseOfsY = TOP_LEFT_Y - 2;
186 if (mouseX < 150)
187 _mouseOfsX = TOP_LEFT_X + 24;
188 else
189 _mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
190 } else {
191 _mouseOfsY = TOP_LEFT_Y - 10;
192 if (mouseX < 150)
193 _mouseOfsX = TOP_LEFT_X + 13;
194 else
195 _mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
196 }
197 Compact *textCompact = _skyCompact->fetchCpt(text.compactNum);
198 logicCursor(textCompact, mouseX, mouseY);
199 }
200
logicCursor(Compact * textCompact,uint16 mouseX,uint16 mouseY)201 void Text::logicCursor(Compact *textCompact, uint16 mouseX, uint16 mouseY) {
202 textCompact->xcood = (uint16)(mouseX + _mouseOfsX);
203 textCompact->ycood = (uint16)(mouseY + _mouseOfsY);
204 if (textCompact->ycood < TOP_LEFT_Y)
205 textCompact->ycood = TOP_LEFT_Y;
206 }
207
getTextBit(uint8 ** data,uint32 * bitPos)208 bool Text::getTextBit(uint8 **data, uint32 *bitPos) {
209 if (*bitPos) {
210 (*bitPos)--;
211 } else {
212 (*data)++;
213 *bitPos = 7;
214 }
215
216 return (bool)(((**data) >> (*bitPos)) & 1);
217 }
218
getTextChar(uint8 ** data,uint32 * bitPos)219 char Text::getTextChar(uint8 **data, uint32 *bitPos) {
220 int pos = 0;
221 while (1) {
222 if (getTextBit(data, bitPos))
223 pos = _huffTree[pos].rChild;
224 else
225 pos = _huffTree[pos].lChild;
226
227 if (_huffTree[pos].lChild == 0 && _huffTree[pos].rChild == 0) {
228 return _huffTree[pos].value;
229 }
230 }
231 }
232
displayText(uint32 textNum,uint8 * dest,bool center,uint16 pixelWidth,uint8 color)233 DisplayedText Text::displayText(uint32 textNum, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
234 //Render text into buffer *dest
235 getText(textNum);
236 return displayText(_textBuffer, dest, center, pixelWidth, color);
237 }
238
displayText(char * textPtr,uint8 * dest,bool center,uint16 pixelWidth,uint8 color)239 DisplayedText Text::displayText(char *textPtr, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
240 //Render text pointed to by *textPtr in buffer *dest
241 uint32 centerTable[10];
242 uint16 lineWidth = 0;
243
244 uint32 numLines = 0;
245 _numLetters = 2;
246
247 // work around bug #778105 (line width exceeded)
248 char *tmpPtr = strstr(textPtr, "MUND-BEATMUNG!");
249 if (tmpPtr)
250 strcpy(tmpPtr, "MUND BEATMUNG!");
251
252 // work around bug #1151924 (line width exceeded when talking to gardener using spanish text)
253 // This text apparently only is broken in the floppy versions, the CD versions contain
254 // the correct string "MANIFESTACION - ARTISTICA.", which doesn't break the algorithm/game.
255 tmpPtr = strstr(textPtr, "MANIFESTACION-ARTISTICA.");
256 if (tmpPtr)
257 strcpy(tmpPtr, "MANIFESTACION ARTISTICA.");
258
259 char *curPos = textPtr;
260 char *lastSpace = textPtr;
261 uint8 textChar = (uint8)*curPos++;
262
263 while (textChar >= 0x20) {
264 if ((_curCharSet == 1) && (textChar >= 0x80))
265 textChar = 0x20;
266
267 textChar -= 0x20;
268 if (textChar == 0) {
269 lastSpace = curPos; //keep track of last space
270 centerTable[numLines] = lineWidth;
271 }
272
273 lineWidth += _characterSet[textChar]; //add character width
274 lineWidth += (uint16)_dtCharSpacing; //include character spacing
275
276 if (pixelWidth <= lineWidth) {
277 if (*(lastSpace-1) == 10)
278 error("line width exceeded");
279
280 *(lastSpace-1) = 10;
281 lineWidth = 0;
282 numLines++;
283 curPos = lastSpace; //go back for new count
284 }
285
286 textChar = (uint8)*curPos++;
287 _numLetters++;
288 }
289
290 uint32 dtLastWidth = lineWidth; //save width of last line
291 centerTable[numLines] = lineWidth; //and update centering table
292 numLines++;
293
294 if (numLines > MAX_NO_LINES)
295 error("Maximum no. of lines exceeded");
296
297 uint32 dtLineSize = pixelWidth * _charHeight;
298 uint32 numBytes = (dtLineSize * numLines) + sizeof(DataFileHeader) + 4;
299
300 if (!dest)
301 dest = (uint8 *)malloc(numBytes);
302
303 // clear text sprite buffer
304 memset(dest + sizeof(DataFileHeader), 0, numBytes - sizeof(DataFileHeader));
305
306 //make the header
307 ((DataFileHeader *)dest)->s_width = pixelWidth;
308 ((DataFileHeader *)dest)->s_height = (uint16)(_charHeight * numLines);
309 ((DataFileHeader *)dest)->s_sp_size = (uint16)(pixelWidth * _charHeight * numLines);
310 ((DataFileHeader *)dest)->s_offset_x = 0;
311 ((DataFileHeader *)dest)->s_offset_y = 0;
312
313 //reset position
314 curPos = textPtr;
315
316 uint8 *curDest = dest + sizeof(DataFileHeader); //point to where pixels start
317 byte *prevDest = curDest;
318 uint32 *centerTblPtr = centerTable;
319
320 do {
321 if (center) {
322 uint32 width = (pixelWidth - *centerTblPtr) >> 1;
323 centerTblPtr++;
324 curDest += width;
325 }
326
327 textChar = (uint8)*curPos++;
328 while (textChar >= 0x20) {
329 makeGameCharacter(textChar - 0x20, _characterSet, curDest, color, pixelWidth);
330 textChar = *curPos++;
331 }
332
333 prevDest = curDest = prevDest + dtLineSize; //start of last line + start of next
334
335 } while (textChar >= 10);
336
337 DisplayedText ret;
338 memset(&ret, 0, sizeof(ret));
339 ret.textData = dest;
340 ret.textWidth = dtLastWidth;
341 return ret;
342 }
343
makeGameCharacter(uint8 textChar,uint8 * charSetPtr,uint8 * & dest,uint8 color,uint16 bufPitch)344 void Text::makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
345 bool maskBit, dataBit;
346 uint8 charWidth = (uint8)((*(charSetPtr + textChar)) + 1 - _dtCharSpacing);
347 uint16 data, mask;
348 byte *charSpritePtr = charSetPtr + (CHAR_SET_HEADER + ((_charHeight << 2) * textChar));
349 byte *startPos = dest;
350 byte *curPos = startPos;
351
352 for (int i = 0; i < _charHeight; i++) {
353 byte *prevPos = curPos;
354
355 data = READ_BE_UINT16(charSpritePtr);
356 mask = READ_BE_UINT16(charSpritePtr + 2);
357 charSpritePtr += 4;
358
359 for (int j = 0; j < charWidth; j++) {
360
361 maskBit = (mask & 0x8000) != 0; //check mask
362 mask <<= 1;
363 dataBit = (data & 0x8000) != 0; //check data
364 data <<= 1;
365
366 if (maskBit) {
367 if (dataBit)
368 *curPos = color;
369 else
370 *curPos = 240; //black edge
371 }
372 curPos++;
373 }
374 //advance a line
375 curPos = prevPos + bufPitch;
376 }
377 //update position
378 dest = startPos + charWidth + _dtCharSpacing * 2 - 1;
379 }
380
lowTextManager(uint32 textNum,uint16 width,uint16 logicNum,uint8 color,bool center)381 DisplayedText Text::lowTextManager(uint32 textNum, uint16 width, uint16 logicNum, uint8 color, bool center) {
382 getText(textNum);
383 DisplayedText textInfo = displayText(_textBuffer, NULL, center, width, color);
384
385 uint32 compactNum = FIRST_TEXT_COMPACT;
386 Compact *cpt = _skyCompact->fetchCpt(compactNum);
387 while (cpt->status != 0) {
388 compactNum++;
389 cpt = _skyCompact->fetchCpt(compactNum);
390 }
391
392 cpt->flag = (uint16)(compactNum - FIRST_TEXT_COMPACT) + FIRST_TEXT_BUFFER;
393
394 if (SkyEngine::_itemList[cpt->flag])
395 free(SkyEngine::_itemList[cpt->flag]);
396
397 SkyEngine::_itemList[cpt->flag] = textInfo.textData;
398
399 cpt->logic = logicNum;
400 cpt->status = ST_LOGIC | ST_FOREGROUND | ST_RECREATE;
401 cpt->screen = (uint16) Logic::_scriptVariables[SCREEN];
402
403 textInfo.compactNum = (uint16)compactNum;
404 return textInfo;
405 }
406
changeTextSpriteColor(uint8 * sprData,uint8 newCol)407 void Text::changeTextSpriteColor(uint8 *sprData, uint8 newCol) {
408 DataFileHeader *header = (DataFileHeader *)sprData;
409 sprData += sizeof(DataFileHeader);
410 for (uint16 cnt = 0; cnt < header->s_sp_size; cnt++)
411 if (sprData[cnt] >= 241)
412 sprData[cnt] = newCol;
413 }
414
giveCurrentCharSet()415 uint32 Text::giveCurrentCharSet() {
416 return _curCharSet;
417 }
418
initHuffTree()419 void Text::initHuffTree() {
420 switch (SkyEngine::_systemVars.gameVersion) {
421 case 109:
422 _huffTree = _huffTree_00109;
423 break;
424 case 272: // FIXME: Extract data
425 case 267:
426 _huffTree = _huffTree_00267;
427 break;
428 case 288:
429 _huffTree = _huffTree_00288;
430 break;
431 case 303:
432 _huffTree = _huffTree_00303;
433 break;
434 case 331:
435 _huffTree = _huffTree_00331;
436 break;
437 case 348:
438 _huffTree = _huffTree_00348;
439 break;
440 case 365:
441 _huffTree = _huffTree_00365;
442 break;
443 case 368:
444 _huffTree = _huffTree_00368;
445 break;
446 case 372:
447 _huffTree = _huffTree_00372;
448 break;
449 default:
450 error("Unknown game version %d", SkyEngine::_systemVars.gameVersion);
451 }
452 }
453
patchMessage(uint32 textNum)454 bool Text::patchMessage(uint32 textNum) {
455 uint16 patchIdx = _patchLangIdx[SkyEngine::_systemVars.language];
456 uint16 patchNum = _patchLangNum[SkyEngine::_systemVars.language];
457 for (uint16 cnt = 0; cnt < patchNum; cnt++) {
458 if (_patchedMessages[cnt + patchIdx].textNr == textNum) {
459 strcpy(_textBuffer, _patchedMessages[cnt + patchIdx].text);
460 return true;
461 }
462 }
463 return false;
464 }
465
466 const PatchMessage Text::_patchedMessages[NUM_PATCH_MSG] = {
467 { 28724, "Testo e Parlato" }, // - italian
468 { 28707, "Solo Testo" },
469 { 28693, "Solo Parlato" },
470 { 28724, "Text och tal" }, // - swedish
471 { 28707, "Endast text" },
472 { 28693, "Endast tal" },
473 { 28686, "Musikvolym" },
474 { 4336, "Wir befinden uns EINHUNDERTZWANZIG METER #ber dem ERBODEN!" }, // - german
475 { 28686, "Volume de musique" }, // - french
476 };
477
478 const uint16 Text::_patchLangIdx[8] = {
479 0xFFFF, // SKY_ENGLISH
480 7, // SKY_GERMAN
481 8, // SKY_FRENCH
482 0xFFFF, // SKY_USA
483 3, // SKY_SWEDISH
484 0, // SKY_ITALIAN
485 0xFFFF, // SKY_PORTUGUESE
486 0xFFFF // SKY_SPANISH
487 };
488
489 const uint16 Text::_patchLangNum[8] = {
490 0, // SKY_ENGLISH
491 1, // SKY_GERMAN
492 1, // SKY_FRENCH
493 0, // SKY_USA
494 4, // SKY_SWEDISH
495 3, // SKY_ITALIAN
496 0, // SKY_PORTUGUESE
497 0 // SKY_SPANISH
498 };
499
500 } // End of namespace Sky
501