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 #include "kyra/text/text_hof.h"
24 #include "kyra/resource/resource.h"
25 
26 #include "common/system.h"
27 
28 namespace Kyra {
29 
TextDisplayer_HoF(KyraEngine_HoF * vm,Screen_v2 * screen)30 TextDisplayer_HoF::TextDisplayer_HoF(KyraEngine_HoF *vm, Screen_v2 *screen)
31 	: TextDisplayer(vm, screen), _vm(vm) {
32 }
33 
backupTalkTextMessageBkgd(int srcPage,int dstPage)34 void TextDisplayer_HoF::backupTalkTextMessageBkgd(int srcPage, int dstPage) {
35 	_screen->copyRegion(_talkCoords.x, _talkMessageY, 0, 144, _talkCoords.w, _talkMessageH, srcPage, dstPage);
36 }
37 
restoreTalkTextMessageBkgd(int srcPage,int dstPage)38 void TextDisplayer_HoF::restoreTalkTextMessageBkgd(int srcPage, int dstPage) {
39 	_screen->copyRegion(0, 144, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, srcPage, dstPage);
40 }
41 
restoreScreen()42 void TextDisplayer_HoF::restoreScreen() {
43 	_vm->restorePage3();
44 	_vm->drawAnimObjects();
45 	_screen->copyRegion(_talkCoords.x, _talkMessageY, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, 2, 0, Screen::CR_NO_P_CHECK);
46 	_vm->flagAnimObjsForRefresh();
47 	_vm->refreshAnimObjects(0);
48 }
49 
printCustomCharacterText(const char * text,int x,int y,uint8 c1,int srcPage,int dstPage)50 void TextDisplayer_HoF::printCustomCharacterText(const char *text, int x, int y, uint8 c1, int srcPage, int dstPage) {
51 	text = preprocessString(text);
52 	int lineCount = buildMessageSubstrings(text);
53 	int w = getWidestLineWidth(lineCount);
54 	int h = lineCount * 10;
55 	y = MAX(0, y - (lineCount * 10));
56 	int x1 = 0, x2 = 0;
57 	calcWidestLineBounds(x1, x2, w, x);
58 
59 	_talkCoords.x = x1;
60 	_talkCoords.w = w+2;
61 	_talkCoords.y = y;
62 	_talkMessageY = y;
63 	_talkMessageH = h;
64 
65 	backupTalkTextMessageBkgd(srcPage, dstPage);
66 	int curPageBackUp = _screen->_curPage;
67 	_screen->_curPage = srcPage;
68 
69 	if (_vm->textEnabled()) {
70 		for (int i = 0; i < lineCount; ++i) {
71 			const char *msg = &_talkSubstrings[i * TALK_SUBSTRING_LEN];
72 			printText(msg, getCenterStringX(msg, x1, x2), i * 10 + _talkMessageY, c1, 0xCF, 0);
73 		}
74 	}
75 
76 	_screen->_curPage = curPageBackUp;
77 }
78 
preprocessString(const char * str)79 char *TextDisplayer_HoF::preprocessString(const char *str) {
80 	if (str != _talkBuffer) {
81 		assert(strlen(str) < sizeof(_talkBuffer) - 1);
82 		strcpy(_talkBuffer, str);
83 	}
84 
85 	char *p = _talkBuffer;
86 	while (*p) {
87 		if (*p == '\r')
88 			return _talkBuffer;
89 		++p;
90 	}
91 
92 	p = _talkBuffer;
93 	Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT);
94 	_screen->_charWidth = -2;
95 	int textWidth = _screen->getTextWidth(p);
96 	_screen->_charWidth = 0;
97 
98 	int maxTextWidth = (_vm->language() == 0) ? 176 : 240;
99 
100 	if (textWidth > maxTextWidth) {
101 		if (textWidth > (maxTextWidth*2)) {
102 			int count = getCharLength(p, textWidth / 3);
103 			int offs = dropCRIntoString(p, count);
104 			p += count + offs;
105 			_screen->_charWidth = -2;
106 			textWidth = _screen->getTextWidth(p);
107 			_screen->_charWidth = 0;
108 			count = getCharLength(p, textWidth / 2);
109 			dropCRIntoString(p, count);
110 		} else {
111 			int count = getCharLength(p, textWidth / 2);
112 			dropCRIntoString(p, count);
113 		}
114 	}
115 	_screen->setFont(curFont);
116 	return _talkBuffer;
117 }
118 
calcWidestLineBounds(int & x1,int & x2,int w,int x)119 void TextDisplayer_HoF::calcWidestLineBounds(int &x1, int &x2, int w, int x) {
120 	x1 = x;
121 	x1 -= (w >> 1);
122 	x2 = x1 + w + 1;
123 
124 	if (x1 + w >= 311)
125 		x1 = 311 - w - 1;
126 
127 	if (x1 < 8)
128 		x1 = 8;
129 
130 	x2 = x1 + w + 1;
131 }
132 
133 #pragma mark -
134 
chatGetType(const char * str)135 int KyraEngine_HoF::chatGetType(const char *str) {
136 	str += strlen(str);
137 	--str;
138 	switch (*str) {
139 	case '!':
140 		return 2;
141 
142 	case ')':
143 		return -1;
144 
145 	case '?':
146 		return 1;
147 
148 	default:
149 		return 0;
150 	}
151 }
152 
chatCalcDuration(const char * str)153 int KyraEngine_HoF::chatCalcDuration(const char *str) {
154 	static const uint8 durationMultiplicator[] = { 16, 14, 12, 10, 8, 8, 7, 6, 5, 4 };
155 
156 	int duration = strlen(str);
157 	duration *= _flags.isTalkie ? 8 : durationMultiplicator[(_configTextspeed / 10)];
158 	return MAX<int>(duration, 120);
159 }
160 
objectChat(const char * str,int object,int vocHigh,int vocLow)161 void KyraEngine_HoF::objectChat(const char *str, int object, int vocHigh, int vocLow) {
162 	setNextIdleAnimTimer();
163 
164 	_chatVocHigh = _chatVocLow = -1;
165 
166 	objectChatInit(str, object, vocHigh, vocLow);
167 	_chatText = str;
168 	_chatObject = object;
169 	int chatType = chatGetType(str);
170 	if (chatType == -1) {
171 		_chatIsNote = true;
172 		chatType = 0;
173 	}
174 
175 	if (_mainCharacter.facing > 7)
176 		_mainCharacter.facing = 5;
177 
178 	static const uint8 talkScriptTable[] = {
179 		6, 7, 8,
180 		3, 4, 5,
181 		3, 4, 5,
182 		0, 1, 2,
183 		0, 1, 2,
184 		0, 1, 2,
185 		3, 4, 5,
186 		3, 4, 5
187 	};
188 
189 	assert(_mainCharacter.facing * 3 + chatType < ARRAYSIZE(talkScriptTable));
190 	int script = talkScriptTable[_mainCharacter.facing * 3 + chatType];
191 
192 	static const char *const chatScriptFilenames[] = {
193 		"_Z1FSTMT.EMC",
194 		"_Z1FQUES.EMC",
195 		"_Z1FEXCL.EMC",
196 		"_Z1SSTMT.EMC",
197 		"_Z1SQUES.EMC",
198 		"_Z1SEXCL.EMC",
199 		"_Z1BSTMT.EMC",
200 		"_Z1BQUES.EMC",
201 		"_Z1BEXCL.EMC"
202 	};
203 
204 	objectChatProcess(chatScriptFilenames[script]);
205 	_chatIsNote = false;
206 
207 	_text->restoreScreen();
208 
209 	_mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing];
210 	updateCharacterAnim(0);
211 
212 	_chatText = 0;
213 	_chatObject = -1;
214 
215 	setNextIdleAnimTimer();
216 }
217 
objectChatInit(const char * str,int object,int vocHigh,int vocLow)218 void KyraEngine_HoF::objectChatInit(const char *str, int object, int vocHigh, int vocLow) {
219 	str = _text->preprocessString(str);
220 	int lineNum = _text->buildMessageSubstrings(str);
221 
222 	int yPos = 0, xPos = 0;
223 
224 	if (!object) {
225 		int scale = getScale(_mainCharacter.x1, _mainCharacter.y1);
226 		yPos = _mainCharacter.y1 - ((_mainCharacter.height * scale) >> 8) - 8;
227 		xPos = _mainCharacter.x1;
228 	} else {
229 		yPos = _talkObjectList[object].y;
230 		xPos = _talkObjectList[object].x;
231 	}
232 
233 	yPos -= lineNum * 10;
234 	yPos = MAX(yPos, 0);
235 	_text->_talkMessageY = yPos;
236 	_text->_talkMessageH = lineNum*10;
237 
238 	int width = _text->getWidestLineWidth(lineNum);
239 	_text->calcWidestLineBounds(xPos, yPos, width, xPos);
240 	_text->_talkCoords.x = xPos;
241 	_text->_talkCoords.w = width + 2;
242 
243 	restorePage3();
244 	_text->backupTalkTextMessageBkgd(2, 2);
245 
246 	_chatTextEnabled = textEnabled();
247 	if (_chatTextEnabled) {
248 		objectChatPrintText(str, object);
249 		_chatEndTime = _system->getMillis() + chatCalcDuration(str) * _tickLength;
250 	} else {
251 		_chatEndTime = _system->getMillis();
252 	}
253 
254 	if (speechEnabled()) {
255 		_chatVocHigh = vocHigh;
256 		_chatVocLow = vocLow;
257 	} else {
258 		_chatVocHigh = _chatVocLow = -1;
259 	}
260 }
261 
objectChatPrintText(const char * str,int object)262 void KyraEngine_HoF::objectChatPrintText(const char *str, int object) {
263 	int c1 = _talkObjectList[object].color;
264 	str = _text->preprocessString(str);
265 	int lineNum = _text->buildMessageSubstrings(str);
266 	int maxWidth = _text->getWidestLineWidth(lineNum);
267 	int x = (object == 0) ? _mainCharacter.x1 : _talkObjectList[object].x;
268 	int cX1 = 0, cX2 = 0;
269 	_text->calcWidestLineBounds(cX1, cX2, maxWidth, x);
270 
271 	for (int i = 0; i < lineNum; ++i) {
272 		str = &_text->_talkSubstrings[i*_text->maxSubstringLen()];
273 
274 		int y = _text->_talkMessageY + i * 10;
275 		x = _text->getCenterStringX(str, cX1, cX2);
276 
277 		_text->printText(str, x, y, c1, 0xCF, 0);
278 	}
279 }
280 
objectChatProcess(const char * script)281 void KyraEngine_HoF::objectChatProcess(const char *script) {
282 	memset(&_chatScriptData, 0, sizeof(_chatScriptData));
283 	memset(&_chatScriptState, 0, sizeof(_chatScriptState));
284 
285 	_emc->load(script, &_chatScriptData, &_opcodesAnimation);
286 	_emc->init(&_chatScriptState, &_chatScriptData);
287 	_emc->start(&_chatScriptState, 0);
288 	while (_emc->isValid(&_chatScriptState))
289 		_emc->run(&_chatScriptState);
290 
291 	_animShapeFilename[2] = _characterShapeFile + '0';
292 	uint8 *shapeBuffer = _res->fileData(_animShapeFilename, 0);
293 	if (shapeBuffer) {
294 		int shapeCount = initAnimationShapes(shapeBuffer);
295 
296 		if (_chatVocHigh >= 0) {
297 			playVoice(_chatVocHigh, _chatVocLow);
298 			_chatVocHigh = _chatVocLow = -1;
299 		}
300 
301 		objectChatWaitToFinish();
302 
303 		uninitAnimationShapes(shapeCount, shapeBuffer);
304 	} else {
305 		warning("couldn't load file '%s'", _animShapeFilename);
306 	}
307 
308 	_emc->unload(&_chatScriptData);
309 }
310 
objectChatWaitToFinish()311 void KyraEngine_HoF::objectChatWaitToFinish() {
312 	int charAnimFrame = _mainCharacter.animFrame;
313 	setCharacterAnimDim(_animShapeWidth, _animShapeHeight);
314 
315 	_emc->init(&_chatScriptState, &_chatScriptData);
316 	_emc->start(&_chatScriptState, 1);
317 
318 	bool running = true;
319 	const uint32 endTime = _chatEndTime;
320 	resetSkipFlag();
321 
322 	while (running && !shouldQuit()) {
323 		if (!_emc->isValid(&_chatScriptState))
324 			_emc->start(&_chatScriptState, 1);
325 
326 		_animNeedUpdate = false;
327 		while (!_animNeedUpdate && _emc->isValid(&_chatScriptState))
328 			_emc->run(&_chatScriptState);
329 
330 		int curFrame = _animNewFrame;
331 		uint32 delayTime = _animDelayTime;
332 
333 		if (!_chatIsNote)
334 			_mainCharacter.animFrame = 33 + curFrame;
335 
336 		updateCharacterAnim(0);
337 
338 		uint32 nextFrame = _system->getMillis() + delayTime * _tickLength;
339 
340 		while (_system->getMillis() < nextFrame && !shouldQuit()) {
341 			updateWithText();
342 
343 			const uint32 curTime = _system->getMillis();
344 			if ((textEnabled() && curTime > endTime) || (speechEnabled() && !textEnabled() && !snd_voiceIsPlaying()) || skipFlag()) {
345 				resetSkipFlag();
346 				nextFrame = curTime;
347 				running = false;
348 			}
349 
350 			delay(10);
351 		}
352 	}
353 
354 	_mainCharacter.animFrame = charAnimFrame;
355 	updateCharacterAnim(0);
356 	resetCharacterAnimDim();
357 }
358 
startDialogue(int dlgIndex)359 void KyraEngine_HoF::startDialogue(int dlgIndex) {
360 	updateDlgBuffer();
361 	int csEntry, vocH, unused1, unused2;
362 	loadDlgHeader(csEntry, vocH, unused1, unused2);
363 	int s = _conversationState[dlgIndex][csEntry];
364 	uint8 bufferIndex = 8;
365 
366 	if (s == -1) {
367 		bufferIndex += (dlgIndex * 6);
368 		_conversationState[dlgIndex][csEntry] = 0;
369 	} else if (!s || s == 2) {
370 		bufferIndex += (dlgIndex * 6 + 2);
371 		_conversationState[dlgIndex][csEntry] = 1;
372 	} else {
373 		bufferIndex += (dlgIndex * 6 + 4);
374 		_conversationState[dlgIndex][csEntry] = 2;
375 	}
376 
377 	int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
378 	processDialogue(offs, vocH, csEntry);
379 }
380 
zanthSceneStartupChat()381 void KyraEngine_HoF::zanthSceneStartupChat() {
382 	int lowest = _flags.isTalkie ? 6 : 5;
383 	int tableIndex = _mainCharacter.sceneId - READ_LE_UINT16(&_ingameTalkObjIndex[lowest + _newChapterFile]);
384 	if (queryGameFlag(0x159) || _newSceneDlgState[tableIndex])
385 		return;
386 
387 	int csEntry, vocH, scIndex1, scIndex2;
388 	updateDlgBuffer();
389 	loadDlgHeader(csEntry, vocH, scIndex1, scIndex2);
390 
391 	uint8 bufferIndex = 8 + scIndex1 * 6 + scIndex2 * 4 + tableIndex * 2;
392 	int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
393 	processDialogue(offs, vocH, csEntry);
394 
395 	_newSceneDlgState[tableIndex] = 1;
396 }
397 
randomSceneChat()398 void KyraEngine_HoF::randomSceneChat() {
399 	int lowest = _flags.isTalkie ? 6 : 5;
400 	int tableIndex = (_mainCharacter.sceneId - READ_LE_UINT16(&_ingameTalkObjIndex[lowest + _newChapterFile])) << 2;
401 	if (queryGameFlag(0x164))
402 		return;
403 
404 	int csEntry, vocH, scIndex1, unused;
405 	updateDlgBuffer();
406 	loadDlgHeader(csEntry, vocH, scIndex1, unused);
407 
408 	if (_chatAltFlag) {
409 		_chatAltFlag = 0;
410 		tableIndex += 2;
411 	} else {
412 		_chatAltFlag = 1;
413 	}
414 
415 	uint8 bufferIndex = 8 + scIndex1 * 6 + tableIndex;
416 	int offs = READ_LE_UINT16(_dlgBuffer + bufferIndex);
417 	processDialogue(offs, vocH, csEntry);
418 }
419 
updateDlgBuffer()420 void KyraEngine_HoF::updateDlgBuffer() {
421 	static const char suffixTalkie[] = "EFG";
422 	static const char suffixTowns[] = "G  J";
423 
424 	if (_currentChapter == _npcTalkChpIndex && _mainCharacter.dlgIndex == _npcTalkDlgIndex)
425 		return;
426 
427 	_npcTalkChpIndex = _currentChapter;
428 	_npcTalkDlgIndex = _mainCharacter.dlgIndex;
429 
430 	Common::String filename = Common::String::format("CH%.02d-S%.02d.DL", _currentChapter, _npcTalkDlgIndex);
431 
432 	const char *suffix = _flags.isTalkie ? suffixTalkie : suffixTowns;
433 	if (_flags.platform != Common::kPlatformDOS || _flags.isTalkie)
434 		filename += suffix[_lang];
435 	else
436 		filename += 'G';
437 
438 	delete[] _dlgBuffer;
439 	_dlgBuffer = _res->fileData(filename.c_str(), 0);
440 }
441 
loadDlgHeader(int & csEntry,int & vocH,int & scIndex1,int & scIndex2)442 void KyraEngine_HoF::loadDlgHeader(int &csEntry, int &vocH, int &scIndex1, int &scIndex2) {
443 	csEntry = READ_LE_UINT16(_dlgBuffer);
444 	vocH = READ_LE_UINT16(_dlgBuffer + 2);
445 	scIndex1 = READ_LE_UINT16(_dlgBuffer + 4);
446 	scIndex2 = READ_LE_UINT16(_dlgBuffer + 6);
447 }
448 
processDialogue(int dlgOffset,int vocH,int csEntry)449 void KyraEngine_HoF::processDialogue(int dlgOffset, int vocH, int csEntry) {
450 	int activeTimSequence = -1;
451 	int nextTimSequence = -1;
452 	int cmd = 0;
453 	int vocHi = -1;
454 	int vocLo = -1;
455 	bool loop = true;
456 	int offs = dlgOffset;
457 
458 	_screen->hideMouse();
459 
460 	while (loop) {
461 		cmd = READ_LE_UINT16(_dlgBuffer + offs);
462 		offs += 2;
463 
464 		nextTimSequence = READ_LE_UINT16(&_ingameTalkObjIndex[cmd]);
465 
466 		if (nextTimSequence == 10) {
467 			if (queryGameFlag(0x3E))
468 				nextTimSequence = 14;
469 			if (queryGameFlag(0x3F))
470 				nextTimSequence = 15;
471 			if (queryGameFlag(0x40))
472 				nextTimSequence = 16;
473 		}
474 
475 		if (nextTimSequence == 27 && _mainCharacter.sceneId == 34)
476 			nextTimSequence = 41;
477 
478 		if (queryGameFlag(0x72)) {
479 			if (nextTimSequence == 18)
480 				nextTimSequence = 43;
481 			else if (nextTimSequence == 19)
482 				nextTimSequence = 44;
483 		}
484 
485 		if (_mainCharacter.x1 > 160) {
486 			if (nextTimSequence == 4)
487 				nextTimSequence = 46;
488 			else if (nextTimSequence == 5)
489 				nextTimSequence = 47;
490 		}
491 
492 		if (cmd == 10) {
493 			loop = false;
494 
495 		} else if (cmd == 4) {
496 			csEntry = READ_LE_UINT16(_dlgBuffer + offs);
497 			setDlgIndex(csEntry);
498 			offs += 2;
499 
500 		} else {
501 			if (!_flags.isTalkie || cmd == 11) {
502 				int len = READ_LE_UINT16(_dlgBuffer + offs);
503 				offs += 2;
504 				if (_flags.isTalkie) {
505 					vocLo = READ_LE_UINT16(_dlgBuffer + offs);
506 					offs += 2;
507 				}
508 				memcpy(_unkBuf500Bytes, _dlgBuffer + offs, len);
509 				_unkBuf500Bytes[len] = 0;
510 				offs += len;
511 				if (_flags.isTalkie)
512 					continue;
513 
514 			} else if (_flags.isTalkie) {
515 				int len = READ_LE_UINT16(_dlgBuffer + offs);
516 				offs += 2;
517 				static const int irnv[] = { 91, 105, 110, 114, 118 };
518 				vocHi = irnv[vocH - 1] + csEntry;
519 				vocLo = READ_LE_UINT16(_dlgBuffer + offs);
520 				offs += 2;
521 				memcpy(_unkBuf500Bytes, _dlgBuffer + offs, len);
522 				_unkBuf500Bytes[len] = 0;
523 				offs += len;
524 			}
525 
526 			if (_unkBuf500Bytes[0]) {
527 				if ((!_flags.isTalkie && cmd == 11) || (_flags.isTalkie && cmd == 12)) {
528 					if (activeTimSequence > -1) {
529 						deinitTalkObject(activeTimSequence);
530 						activeTimSequence = -1;
531 					}
532 					objectChat((const char *)_unkBuf500Bytes, 0, vocHi, vocLo);
533 				} else {
534 					if (activeTimSequence != nextTimSequence) {
535 						if (activeTimSequence > -1) {
536 							deinitTalkObject(activeTimSequence);
537 							activeTimSequence = -1;
538 						}
539 						initTalkObject(nextTimSequence);
540 						activeTimSequence = nextTimSequence;
541 					}
542 					npcChatSequence((const char *)_unkBuf500Bytes, nextTimSequence, vocHi, vocLo);
543 				}
544 			}
545 		}
546 	}
547 
548 	if (activeTimSequence > -1)
549 		deinitTalkObject(activeTimSequence);
550 
551 	_screen->showMouse();
552 }
553 
initTalkObject(int index)554 void KyraEngine_HoF::initTalkObject(int index) {
555 	TalkObject &object = _talkObjectList[index];
556 
557 	char STAFilename[13];
558 	char ENDFilename[13];
559 
560 	strcpy(STAFilename, object.filename);
561 	strcpy(_TLKFilename, object.filename);
562 	strcpy(ENDFilename, object.filename);
563 
564 	strcat(STAFilename + 4, "_STA.TIM");
565 	strcat(_TLKFilename + 4, "_TLK.TIM");
566 	strcat(ENDFilename + 4, "_END.TIM");
567 
568 	_currentTalkSections.STATim = _tim->load(STAFilename, &_timOpcodes);
569 	_currentTalkSections.TLKTim = _tim->load(_TLKFilename, &_timOpcodes);
570 	_currentTalkSections.ENDTim = _tim->load(ENDFilename, &_timOpcodes);
571 
572 	if (object.scriptId != -1) {
573 		_specialSceneScriptStateBackup[object.scriptId] = _specialSceneScriptState[object.scriptId];
574 		_specialSceneScriptState[object.scriptId] = 1;
575 	}
576 
577 	if (_currentTalkSections.STATim) {
578 		_tim->resetFinishedFlag();
579 		while (!shouldQuit() && !_tim->finished()) {
580 			_tim->exec(_currentTalkSections.STATim, false);
581 			if (_chatText)
582 				updateWithText();
583 			else
584 				update();
585 			delay(10);
586 		}
587 	}
588 }
589 
deinitTalkObject(int index)590 void KyraEngine_HoF::deinitTalkObject(int index) {
591 	TalkObject &object = _talkObjectList[index];
592 
593 	if (_currentTalkSections.ENDTim) {
594 		_tim->resetFinishedFlag();
595 		while (!shouldQuit() && !_tim->finished()) {
596 			_tim->exec(_currentTalkSections.ENDTim, false);
597 			if (_chatText)
598 				updateWithText();
599 			else
600 				update();
601 			delay(10);
602 		}
603 	}
604 
605 	if (object.scriptId != -1)
606 		_specialSceneScriptState[object.scriptId] = _specialSceneScriptStateBackup[object.scriptId];
607 
608 	_tim->unload(_currentTalkSections.STATim);
609 	_tim->unload(_currentTalkSections.TLKTim);
610 	_tim->unload(_currentTalkSections.ENDTim);
611 }
612 
npcChatSequence(const char * str,int objectId,int vocHigh,int vocLow)613 void KyraEngine_HoF::npcChatSequence(const char *str, int objectId, int vocHigh, int vocLow) {
614 	_chatText = str;
615 	_chatObject = objectId;
616 	objectChatInit(str, objectId, vocHigh, vocLow);
617 
618 	if (!_currentTalkSections.TLKTim)
619 		_currentTalkSections.TLKTim = _tim->load(_TLKFilename, &_timOpcodes);
620 
621 	setNextIdleAnimTimer();
622 
623 	uint32 ct = chatCalcDuration(str);
624 	uint32 time = _system->getMillis();
625 	_chatEndTime =  time + (3 + ct) * _tickLength;
626 	uint32 chatAnimEndTime = time + (3 + (ct >> 1)) * _tickLength;
627 
628 	if (_chatVocHigh >= 0) {
629 		playVoice(_chatVocHigh, _chatVocLow);
630 		_chatVocHigh = _chatVocLow = -1;
631 	}
632 
633 	while (((textEnabled() && _chatEndTime > _system->getMillis()) || (speechEnabled() && snd_voiceIsPlaying())) && !(shouldQuit() || skipFlag())) {
634 		if ((!speechEnabled() && chatAnimEndTime > _system->getMillis()) || (speechEnabled() && snd_voiceIsPlaying())) {
635 			_tim->resetFinishedFlag();
636 			while (!_tim->finished() && !skipFlag() && !shouldQuit()) {
637 				if (_currentTalkSections.TLKTim)
638 					_tim->exec(_currentTalkSections.TLKTim, false);
639 				else
640 					_tim->resetFinishedFlag();
641 
642 				updateWithText();
643 				delay(10);
644 			}
645 
646 			if (_currentTalkSections.TLKTim)
647 				_tim->stopCurFunc();
648 		}
649 		updateWithText();
650 	}
651 
652 	resetSkipFlag();
653 
654 	_tim->unload(_currentTalkSections.TLKTim);
655 
656 	_text->restoreScreen();
657 	_chatText = 0;
658 	_chatObject = -1;
659 	setNextIdleAnimTimer();
660 }
661 
setDlgIndex(int dlgIndex)662 void KyraEngine_HoF::setDlgIndex(int dlgIndex) {
663 	if (dlgIndex == _mainCharacter.dlgIndex)
664 		return;
665 	memset(_newSceneDlgState, 0, 32);
666 	for (int i = 0; i < 19; i++)
667 		memset(_conversationState[i], -1, 14);
668 	_chatAltFlag = false;
669 	_mainCharacter.dlgIndex = dlgIndex;
670 }
671 
672 } // End of namespace Kyra
673