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  * This code is based on the original source code of Lord Avalot d'Argent version 1.3.
25  * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman.
26  */
27 
28  /* SCROLLS		The scroll driver. */
29 
30 #include "avalanche/avalanche.h"
31 #include "avalanche/dialogs.h"
32 
33 #include "common/system.h"
34 #include "common/random.h"
35 
36 namespace Avalanche {
37 
38 const Dialogs::TuneType Dialogs::kTune = {
39 	kPitchHigher, kPitchHigher, kPitchLower, kPitchSame, kPitchHigher, kPitchHigher, kPitchLower, kPitchHigher, kPitchHigher, kPitchHigher,
40 	kPitchLower, kPitchHigher, kPitchHigher, kPitchSame, kPitchHigher, kPitchLower, kPitchLower, kPitchLower, kPitchLower, kPitchHigher,
41 	kPitchHigher, kPitchLower, kPitchLower, kPitchLower, kPitchLower, kPitchSame, kPitchLower, kPitchHigher, kPitchSame, kPitchLower, kPitchHigher
42 };
43 
44 // A quasiped defines how people who aren't sprites talk. For example, quasiped
45 // "A" is Dogfood. The rooms aren't stored because I'm leaving that to context.
46 const QuasipedType Dialogs::kQuasipeds[16] = {
47 	//_whichPed, _foregroundColor,   _room,      _backgroundColor,     _who
48 	{1, kColorLightgray,    kRoomArgentPub,    kColorBrown,    kPeopleDogfood},   // A: Dogfood (screen 19).
49 	{2, kColorGreen,        kRoomArgentPub,    kColorWhite,    kPeopleIbythneth}, // B: Ibythneth (screen 19).
50 	{2, kColorWhite,        kRoomYours,        kColorMagenta,  kPeopleArkata},    // C: Arkata (screen 1).
51 	{2, kColorBlack,        kRoomLustiesRoom,  kColorRed,      kPeopleInvisible}, // D: Hawk (screen 23).
52 	{2, kColorLightgreen,   kRoomOutsideDucks, kColorBrown,    kPeopleTrader},    // E: Trader (screen 50).
53 	{5, kColorYellow,       kRoomRobins,       kColorRed,      kPeopleAvalot},    // F: Avvy, tied up (scr.42)
54 	{1, kColorBlue,         kRoomAylesOffice,  kColorWhite,    kPeopleAyles},     // G: Ayles (screen 16).
55 	{1, kColorBrown,        kRoomMusicRoom,    kColorWhite,    kPeopleJacques},   // H: Jacques (screen 7).
56 	{1, kColorLightgreen,   kRoomNottsPub,     kColorGreen,    kPeopleSpurge},    // I: Spurge (screen 47).
57 	{2, kColorYellow,       kRoomNottsPub,     kColorRed,      kPeopleAvalot},    // J: Avalot (screen 47).
58 	{1, kColorLightgray,    kRoomLustiesRoom,  kColorBlack,    kPeopleDuLustie},  // K: du Lustie (screen 23).
59 	{1, kColorYellow,       kRoomOubliette,    kColorRed,      kPeopleAvalot},    // L: Avalot (screen 27).
60 	{2, kColorWhite,        kRoomOubliette,    kColorRed,      kPeopleInvisible}, // M: Avaroid (screen 27).
61 	{3, kColorLightgray,    kRoomArgentPub,    kColorDarkgray, kPeopleMalagauche},// N: Malagauche (screen 19).
62 	{4, kColorLightmagenta, kRoomNottsPub,     kColorRed,      kPeoplePort},      // O: Port (screen 47).
63 	{1, kColorLightgreen,   kRoomDucks,        kColorDarkgray, kPeopleDrDuck}     // P: Duck (screen 51).
64 };
65 
Dialogs(AvalancheEngine * vm)66 Dialogs::Dialogs(AvalancheEngine *vm) {
67 	_vm = vm;
68 	_noError = true;
69 
70 	_aboutBox = false;
71 	_talkX = 0;
72 	_talkY = 0;
73 	_maxLineNum = 0;
74 	_scReturn = false;
75 	_currentFont = kFontStyleRoman;
76 	_param = 0;
77 	_useIcon = 0;
78 	_scrollBells = 0;
79 	_underScroll = 0;
80 	_shadowBoxX = 0;
81 	_shadowBoxY = 0;
82 }
83 
init()84 void Dialogs::init() {
85 	loadFont();
86 	resetScrollDriver();
87 }
88 
89 /**
90  * Determine the color of the ready light and draw it
91  * @remarks	Originally called 'state'
92  */
setReadyLight(byte state)93 void Dialogs::setReadyLight(byte state) {
94 	if (_vm->_ledStatus == state)
95 		return; // Already like that!
96 
97 	// TODO: Implement different patterns for green color.
98 	Color color = kColorBlack;
99 	switch (state) {
100 	default:
101 	case 0:
102 		color = kColorBlack;
103 		break; // Off
104 	case 1:
105 	case 2:
106 	case 3:
107 		color = kColorGreen;
108 		break; // Hit a key
109 	}
110 
111 	_vm->_graphics->drawReadyLight(color);
112 	CursorMan.showMouse(true);
113 	_vm->_ledStatus = state;
114 }
115 
easterEgg()116 void Dialogs::easterEgg() {
117 	warning("STUB: Scrolls::easterEgg()");
118 }
119 
say(int16 x,int16 y,Common::String z)120 void Dialogs::say(int16 x, int16 y, Common::String z) {
121 	FontType itw;
122 	byte lz = z.size();
123 
124 	bool offset = x % 8 == 4;
125 	x /= 8;
126 	y++;
127 	int16 i = 0;
128 	for (int xx = 0; xx < lz; xx++) {
129 		switch (z[xx]) {
130 		case kControlRoman:
131 			_currentFont = kFontStyleRoman;
132 			break;
133 		case kControlItalic:
134 			_currentFont = kFontStyleItalic;
135 			break;
136 		default: {
137 			for (int yy = 0; yy < 12; yy++)
138 				itw[(byte)z[xx]][yy] = _fonts[_currentFont][(byte)z[xx]][yy + 2];
139 
140 			// We have to draw the characters one-by-one because of the accidental font changes.
141 			i++;
142 			Common::String chr(z[xx]);
143 			_vm->_graphics->drawScrollText(chr, itw, 12, (x - 1) * 8 + offset * 4 + i * 8, y, kColorBlack);
144 			}
145 		}
146 	}
147 }
148 
149 /**
150  * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters.
151  * @remarks	Originally called 'normscroll'
152  */
scrollModeNormal()153 void Dialogs::scrollModeNormal() {
154 	// Original code is:
155 	// egg : array[1..8] of char = ^P^L^U^G^H+'***';
156 	// this is not using kControl characters: it's the secret code to be entered to trigger the easter egg
157 	// TODO: To be fixed when the Easter egg code is implemented
158 	Common::String egg = Common::String::format("%c%c%c%c%c***", kControlParagraph, kControlLeftJustified, kControlNegative, kControlBell, kControlBackspace);
159 	Common::String e = "(c) 1994";
160 
161 	setReadyLight(3);
162 	_vm->_animationsEnabled = false;
163 	_vm->_graphics->loadMouse(kCurFletch);
164 
165 	_vm->_graphics->saveScreen();
166 	_vm->_graphics->showScroll();
167 
168 	Common::Event event;
169 	bool escape = false;
170 	while (!_vm->shouldQuit() && !escape) {
171 		_vm->_graphics->refreshScreen();
172 		while (_vm->getEvent(event)) {
173 			if ((event.type == Common::EVENT_LBUTTONUP) ||
174 				((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_ESCAPE) ||
175 				(event.kbd.keycode == Common::KEYCODE_RETURN) || (event.kbd.keycode == Common::KEYCODE_HASH) ||
176 				(event.kbd.keycode == Common::KEYCODE_PLUS)))) {
177 				escape = true;
178 				break;
179 			} else if (event.type == Common::EVENT_KEYDOWN)
180 				_vm->errorLed();
181 		}
182 	}
183 
184 	_vm->_graphics->restoreScreen();
185 	_vm->_graphics->removeBackup();
186 
187 	warning("STUB: scrollModeNormal() - Check Easter Egg trigger");
188 #if 0
189 	char r;
190 	bool oktoexit;
191 	do {
192 		do {
193 			_vm->check(); // was "checkclick;"
194 
195 //#ifdef RECORD slowdown(); basher::count++; #endif
196 
197 			if (_vm->_enhanced->keypressede())
198 				break;
199 		} while (!((mrelease > 0) || (buttona1()) || (buttonb1())));
200 
201 		if (mrelease == 0) {
202 			inkey();
203 			if (aboutscroll) {
204 				move(e[2 - 1], e[1 - 1], 7);
205 				e[8 - 1] = inchar;
206 				if (egg == e)
207 					easteregg();
208 			}
209 			oktoexit = set::of('\15', '\33', '+', '#', eos).has(inchar);
210 			if (!oktoexit)  errorled();
211 		}
212 
213 	} while (!((oktoexit) || (mrelease > 0)));
214 
215 //#ifdef RECORD record_one(); #endif
216 
217 	_vm->screturn = r == '#'; // "back door"
218 #endif
219 
220 	setReadyLight(0);
221 	_vm->_animationsEnabled = true;
222 	_vm->_holdLeftMouse = false; // Used in Lucerna::checkclick().
223 }
224 
225 /**
226  * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters.
227  * The "asking" scroll. Used indirectly in diplayQuestion().
228  * @remarks	Originally called 'dialogue'
229  */
scrollModeDialogue()230 void Dialogs::scrollModeDialogue() {
231 	_vm->_graphics->loadMouse(kCurHand);
232 
233 	_vm->_graphics->saveScreen();
234 	_vm->_graphics->showScroll();
235 
236 	Common::Event event;
237 	while (!_vm->shouldQuit()) {
238 		_vm->_graphics->refreshScreen();
239 
240 		_vm->getEvent(event);
241 
242 		Common::Point cursorPos = _vm->getMousePos();
243 		cursorPos.y /= 2;
244 
245 		char inChar = 0;
246 		if ((event.type == Common::EVENT_KEYDOWN) && (event.kbd.ascii <= 122) && (event.kbd.ascii >= 97)) {
247 			inChar = (char)event.kbd.ascii;
248 			Common::String temp(inChar);
249 			temp.toUppercase();
250 			inChar = temp[0];
251 		}
252 
253 		if (_vm->shouldQuit() || (event.type == Common::EVENT_LBUTTONUP) || (event.type == Common::EVENT_KEYDOWN)) {
254 			if (((cursorPos.x >= _shadowBoxX - 65) && (cursorPos.y >= _shadowBoxY - 24) && (cursorPos.x <= _shadowBoxX - 5) && (cursorPos.y <= _shadowBoxY - 10))
255 				|| (inChar == 'Y') || (inChar == 'J') || (inChar == 'O')) { // Yes, Ja, Oui
256 				_scReturn = true;
257 				break;
258 			} else if (((cursorPos.x >= _shadowBoxX + 5) && (cursorPos.y >= _shadowBoxY - 24) && (cursorPos.x <= _shadowBoxX + 65) && (cursorPos.y <= _shadowBoxY - 10))
259 						|| (inChar == 'N')){ // No, Non, Nein
260 				_scReturn = false;
261 				break;
262 			}
263 		}
264 	}
265 
266 	_vm->_graphics->restoreScreen();
267 	_vm->_graphics->removeBackup();
268 }
269 
store(byte what,TuneType & played)270 void Dialogs::store(byte what, TuneType &played) {
271 	memmove(played, played + 1, sizeof(played) - 1);
272 	played[30] = what;
273 }
274 
theyMatch(TuneType & played)275 bool Dialogs::theyMatch(TuneType &played) {
276 	byte mistakes = 0;
277 
278 	for (unsigned int i = 0; i < sizeof(played); i++) {
279 		if (played[i] != kTune[i])
280 			mistakes++;
281 	}
282 
283 	return mistakes < 5;
284 }
285 
286 /**
287  * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters.
288  * Part of the harp mini-game.
289  * @remarks	Originally called 'music_Scroll'
290  */
scrollModeMusic()291 void Dialogs::scrollModeMusic() {
292 	setReadyLight(3);
293 	_vm->_animationsEnabled = false;
294 	CursorMan.showMouse(false);
295 	_vm->_graphics->loadMouse(kCurFletch);
296 
297 	TuneType played;
298 	for (unsigned int i = 0; i < sizeof(played); i++)
299 		played[i] = kPitchInvalid;
300 	int8 lastOne = -1, thisOne = -1; // Invalid values.
301 
302 	_vm->_animationsEnabled = false;
303 
304 	_vm->_graphics->saveScreen();
305 	_vm->_graphics->showScroll();
306 
307 	Common::Event event;
308 	while (!_vm->shouldQuit()) {
309 		_vm->_graphics->refreshScreen();
310 
311 		_vm->getEvent(event);
312 
313 		// When we stop playing?
314 		if ((event.type == Common::EVENT_LBUTTONDOWN) ||
315 			((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_RETURN) || (event.kbd.keycode == Common::KEYCODE_ESCAPE)))) {
316 				break;
317 		}
318 
319 		// When we DO play:
320 		if ((event.type == Common::EVENT_KEYDOWN)
321 			&& ((event.kbd.keycode == Common::KEYCODE_q) || (event.kbd.keycode == Common::KEYCODE_w)
322 			|| (event.kbd.keycode == Common::KEYCODE_e) || (event.kbd.keycode == Common::KEYCODE_r)
323 			|| (event.kbd.keycode == Common::KEYCODE_t) || (event.kbd.keycode == Common::KEYCODE_y)
324 			|| (event.kbd.keycode == Common::KEYCODE_u) || (event.kbd.keycode == Common::KEYCODE_i)
325 			|| (event.kbd.keycode == Common::KEYCODE_o) || (event.kbd.keycode == Common::KEYCODE_p)
326 			|| (event.kbd.keycode == Common::KEYCODE_LEFTBRACKET) || (event.kbd.keycode == Common::KEYCODE_RIGHTBRACKET))) {
327 				byte value = 0;
328 				switch (event.kbd.keycode) {
329 				case Common::KEYCODE_q:
330 					value = 0;
331 					break;
332 				case Common::KEYCODE_w:
333 					value = 1;
334 					break;
335 				case Common::KEYCODE_e:
336 					value = 2;
337 					break;
338 				case Common::KEYCODE_r:
339 					value = 3;
340 					break;
341 				case Common::KEYCODE_t:
342 					value = 4;
343 					break;
344 				case Common::KEYCODE_y:
345 					value = 5;
346 					break;
347 				case Common::KEYCODE_u:
348 					value = 6;
349 					break;
350 				case Common::KEYCODE_i:
351 					value = 7;
352 					break;
353 				case Common::KEYCODE_o:
354 					value = 8;
355 					break;
356 				case Common::KEYCODE_p:
357 					value = 9;
358 					break;
359 				case Common::KEYCODE_LEFTBRACKET:
360 					value = 10;
361 					break;
362 				case Common::KEYCODE_RIGHTBRACKET:
363 					value = 11;
364 					break;
365 				default:
366 					error("cannot happen");
367 					break;
368 				}
369 
370 				lastOne = thisOne;
371 				thisOne = value;
372 
373 				_vm->_sound->playNote(_vm->kNotes[thisOne], 100);
374 				_vm->_system->delayMillis(200);
375 
376 				if (!_vm->_bellsAreRinging) { // These handle playing the right tune.
377 					if (thisOne < lastOne)
378 						store(kPitchLower, played);
379 					else if (thisOne == lastOne)
380 						store(kPitchSame, played);
381 					else
382 						store(kPitchHigher, played);
383 				}
384 
385 				if (theyMatch(played)) {
386 					setReadyLight(0);
387 					_vm->_timer->addTimer(8, Timer::kProcJacquesWakesUp, Timer::kReasonJacquesWakingUp);
388 					break;
389 				}
390 		}
391 	}
392 
393 	_vm->_graphics->restoreScreen();
394 	_vm->_graphics->removeBackup();
395 
396 	_vm->_animationsEnabled = true;
397 	CursorMan.showMouse(true);
398 }
399 
resetScrollDriver()400 void Dialogs::resetScrollDriver() {
401 	_scrollBells = 0;
402 	_currentFont = kFontStyleRoman;
403 	_useIcon = 0;
404 	_vm->_interrogation = 0; // Always reset after a scroll comes up.
405 }
406 
407 /**
408  * Rings the bell x times
409  * @remarks	Originally called 'dingdongbell'
410  */
ringBell()411 void Dialogs::ringBell() {
412 	for (int i = 0; i < _scrollBells; i++)
413 		_vm->errorLed(); // Ring the bell "_scrollBells" times.
414 }
415 
416 /**
417  * This moves the mouse pointer off the scroll so that you can read it.
418  * @remarks	Originally called 'dodgem'
419  */
dodgem()420 void Dialogs::dodgem() {
421 	_dodgeCoord = _vm->getMousePos();
422 	g_system->warpMouse(_dodgeCoord.x, _underScroll); // Move the pointer off the scroll.
423 }
424 
425 /**
426  * This is the opposite of Dodgem.
427  * It moves the mouse pointer back, IF you haven't moved it in the meantime.
428  * @remarks	Originally called 'undodgem'
429  */
unDodgem()430 void Dialogs::unDodgem() {
431 	Common::Point actCoord = _vm->getMousePos();
432 	if ((actCoord.x == _dodgeCoord.x) && (actCoord.y == _underScroll))
433 		g_system->warpMouse(_dodgeCoord.x, _dodgeCoord.y); // No change, so restore the pointer's original position.
434 }
435 
drawScroll(DialogFunctionType modeFunc)436 void Dialogs::drawScroll(DialogFunctionType modeFunc) {
437 	int16 lx = 0;
438 	int16 ly = (_maxLineNum + 1) * 6;
439 	int16 ex;
440 	for (int i = 0; i <= _maxLineNum; i++) {
441 		ex = _scroll[i].size() * 8;
442 		if (lx < ex)
443 			lx = ex;
444 	}
445 	int16 mx = 320;
446 	int16 my = 100; // Getmaxx & getmaxy div 2, both.
447 	lx /= 2;
448 	ly -= 2;
449 
450 	if ((1 <= _useIcon) && (_useIcon <= 34))
451 		lx += kHalfIconWidth;
452 
453 	CursorMan.showMouse(false);
454 	_vm->_graphics->drawScroll(mx, lx, my, ly);
455 
456 	mx -= lx;
457 	my -= ly + 2;
458 
459 	bool center = false;
460 
461 	byte iconIndent = 0;
462 	switch (_useIcon) {
463 	default:
464 	case 0:
465 		iconIndent = 0;
466 		break; // No icon.
467 	case 34:
468 		_vm->_graphics->drawSign("about", 28, 76, 15);
469 		iconIndent = 0;
470 		break;
471 	case 35:
472 		_vm->_graphics->drawSign("gameover", 52, 59, 71);
473 		iconIndent = 0;
474 		break;
475 	}
476 
477 	if ((1 <= _useIcon) && (_useIcon <= 33)) { // Standard icon.
478 		_vm->_graphics->drawIcon(mx, my + ly / 2, _useIcon);
479 		iconIndent = 53;
480 	}
481 
482 	for (int i = 0; i <= _maxLineNum; i++) {
483 		if (!_scroll[i].empty())
484 			switch (_scroll[i][_scroll[i].size() - 1]) {
485 			case kControlCenter:
486 				center = true;
487 				_scroll[i].deleteLastChar();
488 				break;
489 			case kControlLeftJustified:
490 				center = false;
491 				_scroll[i].deleteLastChar();
492 				break;
493 			case kControlQuestion:
494 				_shadowBoxX = mx + lx;
495 				_shadowBoxY = my + ly;
496 				_scroll[i].setChar(' ', 0);
497 				_vm->_graphics->drawShadowBox(_shadowBoxX - 65, _shadowBoxY - 24, _shadowBoxX - 5, _shadowBoxY - 10, "Yes.");
498 				_vm->_graphics->drawShadowBox(_shadowBoxX + 5, _shadowBoxY - 24, _shadowBoxX + 65, _shadowBoxY - 10, "No.");
499 				break;
500 			default:
501 				break;
502 			}
503 
504 		if (center)
505 			say(320 - _scroll[i].size() * 4 + iconIndent, my, _scroll[i]);
506 		else
507 			say(mx + iconIndent, my, _scroll[i]);
508 
509 		my += 12;
510 	}
511 
512 	_underScroll = (my + 3) * 2; // Multiplying because of the doubled screen height.
513 	ringBell();
514 
515 	_vm->_dropsOk = false;
516 	dodgem();
517 
518 	(this->*modeFunc)();
519 
520 	unDodgem();
521 	_vm->_dropsOk = true;
522 
523 	resetScrollDriver();
524 }
525 
drawBubble(DialogFunctionType modeFunc)526 void Dialogs::drawBubble(DialogFunctionType modeFunc) {
527 	Common::Point points[3];
528 
529 	CursorMan.showMouse(false);
530 	int16 xl = 0;
531 	int16 yl = (_maxLineNum + 1) * 5;
532 	for (int i = 0; i <= _maxLineNum; i++) {
533 		uint16 textWidth = _scroll[i].size() * 8;
534 		if (textWidth > xl)
535 			xl = textWidth;
536 	}
537 	xl /= 2;
538 
539 	int16 xw = xl + 18;
540 	int16 yw = yl + 7;
541 	int16 my = yw * 2 - 2;
542 	int16 xc = 0;
543 
544 	if (_talkX - xw < 0)
545 		xc = -(_talkX - xw);
546 	if (_talkX + xw > 639)
547 		xc = 639 - (_talkX + xw);
548 
549 	// Compute triangle coords for the tail of the bubble
550 	points[0].x = _talkX - 10;
551 	points[0].y = yw;
552 	points[1].x = _talkX + 10;
553 	points[1].y = yw;
554 	points[2].x = _talkX;
555 	points[2].y = _talkY;
556 
557 	_vm->_graphics->prepareBubble(xc, xw, my, points);
558 
559 	// Draw the text of the bubble. The centering of the text was improved here compared to Pascal's settextjustify().
560 	// The font is not the same that outtextxy() uses in Pascal. I don't have that, so I used characters instead.
561 	// It's almost the same, only notable differences are '?', '!', etc.
562 	for (int i = 0; i <= _maxLineNum; i++) {
563 		int16 x = xc + _talkX - _scroll[i].size() / 2 * 8;
564 		bool offset = _scroll[i].size() % 2;
565 		_vm->_graphics->drawScrollText(_scroll[i], _vm->_font, 8, x - offset * 4, (i * 10) + 12, _vm->_graphics->_talkFontColor);
566 	}
567 
568 	ringBell();
569 	CursorMan.showMouse(false);
570 	_vm->_dropsOk = false;
571 
572 	// This does the actual drawing to the screen.
573 	(this->*modeFunc)();
574 
575 	_vm->_dropsOk = true;
576 	CursorMan.showMouse(true); // sink;
577 	resetScrollDriver();
578 }
579 
reset()580 void Dialogs::reset() {
581 	_maxLineNum = 0;
582 	for (int i = 0; i < 15; i++) {
583 		if (!_scroll[i].empty())
584 			_scroll[i].clear();
585 	}
586 }
587 
588 /**
589  * Natural state of bubbles
590  * @remarks	Originally called 'natural'
591  */
setBubbleStateNatural()592 void Dialogs::setBubbleStateNatural() {
593 	_talkX = 320;
594 	_talkY = 200;
595 	_vm->_graphics->setDialogColor(kColorDarkgray, kColorWhite);
596 }
597 
displayMoney()598 Common::String Dialogs::displayMoney() {
599 	Common::String result;
600 
601 	if (_vm->_money < 12) { // just pence
602 		result = Common::String::format("%dd", _vm->_money);
603 	} else if (_vm->_money < 240) { // shillings & pence
604 		if ((_vm->_money % 12) == 0)
605 			result = Common::String::format("%d/-", _vm->_money / 12);
606 		else
607 			result = Common::String::format("%d/%d", _vm->_money / 12, _vm->_money % 12);
608 	} else { // L, s & d
609 		result = Common::String::format("\x9C%d.%d.%d", _vm->_money / 240, (_vm->_money / 12) % 20,
610 		                _vm->_money % 12);
611 	}
612 	if (_vm->_money > 12) {
613 		Common::String extraStr = Common::String::format(" (that's %dd)", _vm->_money);
614 		result += extraStr;
615 	}
616 
617 	return result;
618 }
619 
620 /**
621  * Strip trailing character in a string
622  * @remarks	Originally called 'strip'
623  */
stripTrailingSpaces(Common::String & str)624 void Dialogs::stripTrailingSpaces(Common::String &str) {
625 	while (str.lastChar() == ' ')
626 		str.deleteLastChar();
627 	// We don't use String::trim() here because we need the leading whitespaces.
628 }
629 
630 /**
631  * Does the word wrapping.
632  */
solidify(byte n)633 void Dialogs::solidify(byte n) {
634 	if (!_scroll[n].contains(' '))
635 		return; // No spaces.
636 
637 	// So there MUST be a space there, somewhere...
638 	do {
639 		_scroll[n + 1] = _scroll[n][_scroll[n].size() - 1] + _scroll[n + 1];
640 		_scroll[n].deleteLastChar();
641 	} while (_scroll[n][_scroll[n].size() - 1] != ' ');
642 
643 	stripTrailingSpaces(_scroll[n]);
644 }
645 
646 /**
647  * @remarks	Originally called 'calldriver'
648  * Display text by calling the dialog driver. It unifies the function of the original
649  * 'calldriver' and 'display' by using Common::String instead of a private buffer.
650  */
displayText(Common::String text)651 void Dialogs::displayText(Common::String text) {
652 	_vm->_sound->stopSound();
653 
654 	setReadyLight(0);
655 	_scReturn = false;
656 	bool mouthnext = false;
657 	bool callSpriteRun = true; // Only call sprite_run the FIRST time.
658 
659 	switch (text.lastChar()) {
660 	case kControlToBuffer:
661 		text.deleteLastChar();
662 		break; // ^D = (D)on't include pagebreak
663 	case kControlSpeechBubble:
664 	case kControlQuestion:
665 		break; // ^B = speech (B)ubble, ^Q = (Q)uestion in dialogue box
666 	default:
667 		text.insertChar(kControlParagraph, text.size());
668 	}
669 
670 	for (uint16 i = 0; i < text.size(); i++) {
671 		if (mouthnext) {
672 			if (text[i] == kControlRegister)
673 				_param = 0;
674 			else if (('0' <= text[i]) && (text[i] <= '9'))
675 				_param = text[i] - 48;
676 			else if (('A' <= text[i]) && (text[i] <= 'Z'))
677 				_param = text[i] - 55;
678 
679 			mouthnext = false;
680 		} else {
681 			switch (text[i]) {
682 			case kControlParagraph:
683 				if ((_maxLineNum == 0) && (_scroll[0].empty()))
684 					break;
685 
686 				if (callSpriteRun)
687 					_vm->spriteRun();
688 				callSpriteRun = false;
689 
690 				drawScroll(&Avalanche::Dialogs::scrollModeNormal);
691 
692 				reset();
693 
694 				if (_scReturn)
695 					return;
696 				break;
697 			case kControlBell:
698 				_scrollBells++;
699 				break;
700 			case kControlSpeechBubble:
701 				if ((_maxLineNum == 0) && (_scroll[0].empty()))
702 					break;
703 
704 				if (callSpriteRun)
705 					_vm->spriteRun();
706 				callSpriteRun = false;
707 
708 				if (_param == 0)
709 					setBubbleStateNatural();
710 				else if ((1 <= _param) && (_param <= 9)) {
711 					assert(_param - 1 < _vm->_animation->kSpriteNumbMax);
712 					AnimationType *spr = _vm->_animation->_sprites[_param - 1];
713 					if ((_param > _vm->_animation->kSpriteNumbMax) || (!spr->_quick)) { // Not valid.
714 						_vm->errorLed();
715 						setBubbleStateNatural();
716 					} else
717 						spr->chatter(); // Normal sprite talking routine.
718 				} else if ((10 <= _param) && (_param <= 36)) {
719 					// Quasi-peds. (This routine performs the same
720 					// thing with QPs as triptype.chatter does with the
721 					// sprites.)
722 					assert(_param - 10 < 16);
723 					PedType *quasiPed = &_vm->_peds[kQuasipeds[_param - 10]._whichPed];
724 					_talkX = quasiPed->_x;
725 					_talkY = quasiPed->_y; // Position.
726 
727 					_vm->_graphics->setDialogColor(kQuasipeds[_param - 10]._backgroundColor, kQuasipeds[_param - 10]._textColor);
728 				} else {
729 					_vm->errorLed(); // Not valid.
730 					setBubbleStateNatural();
731 				}
732 
733 				drawBubble(&Avalanche::Dialogs::scrollModeNormal);
734 
735 				reset();
736 
737 				if (_scReturn)
738 					return;
739 				break;
740 
741 			// CHECKME: The whole kControlNegative block seems completely unused, as the only use (the easter egg check) is a false positive
742 			case kControlNegative:
743 				switch (_param) {
744 				case 1:
745 					displayText(displayMoney() + kControlToBuffer); // Insert cash balance. (Recursion)
746 					break;
747 				case 2: {
748 					int pwdId = _vm->_parser->kFirstPassword + _vm->_passwordNum;
749 					displayText(_vm->_parser->_vocabulary[pwdId]._word + kControlToBuffer);
750 					}
751 					break;
752 				case 3:
753 					displayText(_vm->_favoriteDrink + kControlToBuffer);
754 					break;
755 				case 4:
756 					displayText(_vm->_favoriteSong + kControlToBuffer);
757 					break;
758 				case 5:
759 					displayText(_vm->_worstPlaceOnEarth + kControlToBuffer);
760 					break;
761 				case 6:
762 					displayText(_vm->_spareEvening + kControlToBuffer);
763 					break;
764 				case 9: {
765 					Common::String tmpStr = Common::String::format("%d,%d%c",_vm->_catacombX, _vm->_catacombY, kControlToBuffer);
766 					displayText(tmpStr);
767 					}
768 					break;
769 				case 10:
770 					switch (_vm->_boxContent) {
771 					case 0: // Sixpence.
772 						displayScrollChain('Q', 37); // You find the sixpence.
773 						_vm->_money += 6;
774 						_vm->_boxContent = _vm->_parser->kNothing;
775 						_vm->incScore(2);
776 						return;
777 					case Parser::kNothing:
778 						displayText("nothing at all. It's completely empty.");
779 						break;
780 					default:
781 						displayText(_vm->getItem(_vm->_boxContent) + '.');
782 					}
783 					break;
784 				case 11:
785 					for (int j = 0; j < kObjectNum; j++) {
786 						if (_vm->_objects[j])
787 							displayText(_vm->getItem(j) + ", " + kControlToBuffer);
788 					}
789 					break;
790 				default:
791 					break;
792 				}
793 				break;
794 			case kControlIcon:
795 				_useIcon = _param;
796 				break;
797 			case kControlNewLine:
798 				_maxLineNum++;
799 				break;
800 			case kControlQuestion:
801 				if (callSpriteRun)
802 					_vm->spriteRun();
803 				callSpriteRun = false;
804 
805 				_maxLineNum++;
806 				_scroll[_maxLineNum] = kControlQuestion;
807 
808 				drawScroll(&Avalanche::Dialogs::scrollModeDialogue);
809 				reset();
810 				break;
811 			case kControlRegister:
812 				mouthnext = true;
813 				break;
814 			case kControlInsertSpaces:
815 				for (int j = 0; j < 9; j++)
816 					_scroll[_maxLineNum] += ' ';
817 				break;
818 			default: // Add new char.
819 				if (_scroll[_maxLineNum].size() == 50) {
820 					solidify(_maxLineNum);
821 					_maxLineNum++;
822 				}
823 				_scroll[_maxLineNum] += text[i];
824 				break;
825 			}
826 		}
827 	}
828 
829 	setReadyLight(2);
830 }
831 
setTalkPos(int16 x,int16 y)832 void Dialogs::setTalkPos(int16 x, int16 y) {
833 	_talkX = x;
834 	_talkY = y;
835 }
836 
getTalkPosX()837 int16 Dialogs::getTalkPosX() {
838 	return _talkX;
839 }
840 
displayQuestion(Common::String question)841 bool Dialogs::displayQuestion(Common::String question) {
842 	displayText(question + kControlNewLine + kControlQuestion);
843 
844 	if (_scReturn && (_vm->_rnd->getRandomNumber(1) == 0)) { // Half-and-half chance.
845 		Common::String tmpStr = Common::String::format("...Positive about that?%cI%c%c%c", kControlRegister, kControlIcon, kControlNewLine, kControlQuestion);
846 		displayText(tmpStr); // Be annoying!
847 		if (_scReturn && (_vm->_rnd->getRandomNumber(3) == 3)) { // Another 25% chance
848 			// \? are used to avoid that ??! is parsed as a trigraph
849 			tmpStr = Common::String::format("%c100%% certain\?\?!%c%c%c%c", kControlInsertSpaces, kControlInsertSpaces, kControlIcon, kControlNewLine, kControlQuestion);
850 			displayText(tmpStr); // Be very annoying!
851 		}
852 	}
853 
854 	return _scReturn;
855 }
856 
loadFont()857 void Dialogs::loadFont() {
858 	Common::File file;
859 
860 	if (!file.open("avalot.fnt"))
861 		error("AVALANCHE: Scrolls: File not found: avalot.fnt");
862 
863 	for (int16 i = 0; i < 256; i++)
864 		file.read(_fonts[0][i], 16);
865 	file.close();
866 
867 	if (!file.open("avitalic.fnt"))
868 		error("AVALANCHE: Scrolls: File not found: avitalic.fnt");
869 
870 	for (int16 i = 0; i < 256; i++)
871 		file.read(_fonts[1][i], 16);
872 	file.close();
873 
874 	if (!file.open("ttsmall.fnt"))
875 		error("AVALANCHE: Scrolls: File not found: ttsmall.fnt");
876 
877 	for (int16 i = 0; i < 256; i++)
878 		file.read(_vm->_font[i],16);
879 	file.close();
880 }
881 
882 /**
883  * Practically this one is a mini-game which called when you play the harp in the monastery.
884  * @remarks	Originally called 'musical_scroll'
885  */
displayMusicalScroll()886 void Dialogs::displayMusicalScroll() {
887 	Common::String tmpStr = Common::String::format("To play the harp...%c%cUse these keys:%c%cQ W E R T Y U I O P [ ]%c%cOr press Enter to stop playing.%c",
888 		        kControlNewLine, kControlNewLine, kControlNewLine, kControlInsertSpaces, kControlNewLine, kControlNewLine, kControlToBuffer);
889 	displayText(tmpStr);
890 
891 	_vm->spriteRun();
892 	CursorMan.showMouse(false);
893 	drawScroll(&Avalanche::Dialogs::scrollModeMusic);
894 	CursorMan.showMouse(true);
895 	reset();
896 }
897 
unSkrimble(Common::String & text)898 void Dialogs::unSkrimble(Common::String &text) {
899 	for (uint16  i = 0; i < text.size(); i++)
900 		text.setChar((~(text[i] - (i + 1))) % 256, i);
901 }
902 
doTheBubble(Common::String & text)903 void Dialogs::doTheBubble(Common::String &text) {
904 	text.insertChar(kControlSpeechBubble, text.size());
905 	assert(text.size() < 2000);
906 }
907 
908 /**
909  * Display a string in a scroll
910  * @remarks	Originally called 'dixi'
911  */
displayScrollChain(char block,byte point,bool report,bool bubbling)912 void Dialogs::displayScrollChain(char block, byte point, bool report, bool bubbling) {
913 	Common::File indexfile;
914 	if (!indexfile.open("avalot.idx"))
915 		error("AVALANCHE: Visa: File not found: avalot.idx");
916 
917 	bool error = false;
918 
919 	indexfile.seek((toupper(block) - 'A') * 2);
920 	uint16 idx_offset = indexfile.readUint16LE();
921 	if (idx_offset == 0)
922 		error = true;
923 
924 	indexfile.seek(idx_offset + point * 2);
925 	uint16 sez_offset = indexfile.readUint16LE();
926 	if (sez_offset == 0)
927 		error = true;
928 
929 	indexfile.close();
930 
931 	_noError = !error;
932 
933 	if (error) {
934 		if (report) {
935 			Common::String todisplay = Common::String::format("%cError accessing scroll %c%d", kControlBell, block, point);
936 			displayText(todisplay);
937 		}
938 		return;
939 	}
940 
941 	Common::File sezfile;
942 	if (!sezfile.open("avalot.sez"))
943 		::error("AVALANCHE: Visa: File not found: avalot.sez");
944 
945 	sezfile.seek(sez_offset);
946 	uint16 _bufSize = sezfile.readUint16LE();
947 	assert(_bufSize < 2000);
948 	char *_buffer = new char[_bufSize];
949 	sezfile.read(_buffer, _bufSize);
950 	sezfile.close();
951 	Common::String text(_buffer, _bufSize);
952 	delete[] _buffer;
953 
954 	unSkrimble(text);
955 	if (bubbling)
956 		doTheBubble(text);
957 	displayText(text);
958 }
959 
960 /**
961  * Start speech
962  * @remarks	Originally called 'speech'
963  */
speak(byte who,byte subject)964 void Dialogs::speak(byte who, byte subject) {
965 	if (subject == 0) { // No subject.
966 		displayScrollChain('S', who, false, true);
967 		return;
968 	}
969 
970 	// Subject given.
971 	_noError = false; // Assume that until we know otherwise.
972 
973 	Common::File indexfile;
974 	if (!indexfile.open("converse.avd"))
975 		error("AVALANCHE: Visa: File not found: converse.avd");
976 
977 	indexfile.seek(who * 2 - 2);
978 	uint16 idx_offset = indexfile.readUint16LE();
979 	uint16 next_idx_offset = indexfile.readUint16LE();
980 
981 	if ((idx_offset == 0) || ((((next_idx_offset - idx_offset) / 2) - 1) < subject))
982 		return;
983 
984 	indexfile.seek(idx_offset + subject * 2);
985 	uint16 sezOffset = indexfile.readUint16LE();
986 	if ((sezOffset == 0) || (indexfile.err()))
987 		return;
988 	indexfile.close();
989 
990 	Common::File sezfile;
991 	if (!sezfile.open("avalot.sez"))
992 		error("AVALANCHE: Visa: File not found: avalot.sez");
993 
994 	sezfile.seek(sezOffset);
995 	uint16 _bufSize = sezfile.readUint16LE();
996 	assert(_bufSize < 2000);
997 	char *_buffer = new char[_bufSize];
998 	sezfile.read(_buffer, _bufSize);
999 	sezfile.close();
1000 	Common::String text(_buffer, _bufSize);
1001 	delete[] _buffer;
1002 
1003 	unSkrimble(text);
1004 	doTheBubble(text);
1005 	displayText(text);
1006 
1007 	_noError = true;
1008 }
1009 
talkTo(byte whom)1010 void Dialogs::talkTo(byte whom) {
1011 	if (_vm->_parser->_person == kPeoplePardon) {
1012 		_vm->_parser->_person = (People)_vm->_subjectNum;
1013 		_vm->_subjectNum = 0;
1014 	}
1015 
1016 	if (_vm->_subjectNum == 0) {
1017 		switch (whom) {
1018 		case kPeopleSpludwick:
1019 			if ((_vm->_lustieIsAsleep) & (!_vm->_objects[kObjectPotion - 1])) {
1020 				displayScrollChain('Q', 68);
1021 				_vm->_objects[kObjectPotion - 1] = true;
1022 				_vm->refreshObjectList();
1023 				_vm->incScore(3);
1024 				return;
1025 			} else if (_vm->_talkedToCrapulus) {
1026 				// Spludwick - what does he need?
1027 				// 0 - let it through to use normal routine.
1028 				switch (_vm->_givenToSpludwick) {
1029 				case 1: // Fallthrough is intended.
1030 				case 2: {
1031 					Common::String objStr = _vm->getItem(AvalancheEngine::kSpludwicksOrder[_vm->_givenToSpludwick]);
1032 					Common::String tmpStr = Common::String::format("Can you get me %s, please?%c2%c",
1033 						objStr.c_str(), kControlRegister, kControlSpeechBubble);
1034 					displayText(tmpStr);
1035 					}
1036 					return;
1037 				case 3:
1038 					displayScrollChain('Q', 30); // Need any help with the game?
1039 					return;
1040 				default:
1041 					break;
1042 				}
1043 			} else {
1044 				displayScrollChain('Q', 42); // Haven't talked to Crapulus. Go and talk to him.
1045 				return;
1046 			}
1047 			break;
1048 		case kPeopleIbythneth:
1049 			if (_vm->_givenBadgeToIby) {
1050 				displayScrollChain('Q', 33); // Thanks a lot!
1051 				return; // And leave the proc.
1052 			}
1053 			break; // Or... just continue, 'cos he hasn't got it.
1054 		case kPeopleDogfood:
1055 			if (_vm->_wonNim) { // We've won the game.
1056 				displayScrollChain('Q', 6); // "I'm Not Playing!"
1057 				return; // Zap back.
1058 			} else
1059 				_vm->_askedDogfoodAboutNim = true;
1060 			break;
1061 		case kPeopleAyles:
1062 			if (!_vm->_aylesIsAwake) {
1063 				displayScrollChain('Q', 43); // He's fast asleep!
1064 				return;
1065 			} else if (!_vm->_givenPenToAyles) {
1066 				displayScrollChain('Q', 44); // Can you get me a pen, Avvy?
1067 				return;
1068 			}
1069 			break;
1070 
1071 		case kPeopleJacques:
1072 			displayScrollChain('Q', 43);
1073 			return;
1074 
1075 		case kPeopleGeida:
1076 			if (_vm->_givenPotionToGeida)
1077 				_vm->_geidaFollows = true;
1078 			else {
1079 				displayScrollChain('U', 17);
1080 				return;
1081 			}
1082 			break;
1083 		case kPeopleSpurge:
1084 			if (!_vm->_sittingInPub) {
1085 				displayScrollChain('Q', 71); // Try going over and sitting down.
1086 				return;
1087 			} else {
1088 				if (_vm->_spurgeTalkCount < 5)
1089 					_vm->_spurgeTalkCount++;
1090 				if (_vm->_spurgeTalkCount > 1) { // no. 1 falls through
1091 					displayScrollChain('Q', 70 + _vm->_spurgeTalkCount);
1092 					return;
1093 				}
1094 			}
1095 			break;
1096 
1097 		default:
1098 			break;
1099 		}
1100 	// On a subject. Is there any reason to block it?
1101 	} else if ((whom == kPeopleAyles) && (!_vm->_aylesIsAwake)) {
1102 		displayScrollChain('Q', 43); // He's fast asleep!
1103 		return;
1104 	}
1105 
1106 	if (whom > 149)
1107 		whom -= 149;
1108 
1109 	bool noMatches = true;
1110 	for (int i = 0; i < _vm->_animation->kSpriteNumbMax; i++) {
1111 		if (_vm->_animation->_sprites[i]->_characterId == whom) {
1112 			Common::String tmpStr = Common::String::format("%c%c%c", kControlRegister, i + 49, kControlToBuffer);
1113 			displayText(tmpStr);
1114 			noMatches = false;
1115 			break;
1116 		}
1117 	}
1118 
1119 	if (noMatches) {
1120 		Common::String tmpStr = Common::String::format("%c%c%c", kControlRegister, kControlRegister, kControlToBuffer);
1121 		displayText(tmpStr);
1122 	}
1123 
1124 	speak(whom, _vm->_subjectNum);
1125 
1126 	if (!_noError)
1127 		displayScrollChain('N', whom); // File not found!
1128 
1129 	if ((_vm->_subjectNum == 0) && ((whom + 149) == kPeopleCrapulus)) { // Crapulus: get the badge - first time only
1130 		_vm->_objects[kObjectBadge - 1] = true;
1131 		_vm->refreshObjectList();
1132 		displayScrollChain('Q', 1); // Circular from Cardiff.
1133 		_vm->_talkedToCrapulus = true;
1134 		_vm->setRoom(kPeopleCrapulus, kRoomDummy); // Crapulus walks off.
1135 
1136 		AnimationType *spr = _vm->_animation->_sprites[1];
1137 		spr->_vanishIfStill = true;
1138 		spr->walkTo(2); // Walks away.
1139 
1140 		_vm->incScore(2);
1141 	}
1142 }
1143 
1144 /**
1145  * This makes Avalot say the response.
1146  * @remarks	Originally called 'sayit'
1147  */
sayIt(Common::String str)1148 void Dialogs::sayIt(Common::String str) {
1149 	Common::String x = str;
1150 	x.setChar(toupper(x[0]), 0);
1151 	Common::String tmpStr = Common::String::format("%c1%s.%c%c2", kControlRegister, x.c_str(), kControlSpeechBubble, kControlRegister);
1152 	displayText(tmpStr);
1153 }
1154 
personSpeaks()1155 Common::String Dialogs::personSpeaks() {
1156 	if ((_vm->_parser->_person == kPeoplePardon) || (_vm->_parser->_person == kPeopleNone)) {
1157 		if ((_vm->_him == kPeoplePardon) || (_vm->getRoom(_vm->_him) != _vm->_room))
1158 			_vm->_parser->_person = _vm->_her;
1159 		else
1160 			_vm->_parser->_person = _vm->_him;
1161 	}
1162 
1163 	if (_vm->getRoom(_vm->_parser->_person) != _vm->_room) {
1164 		return Common::String::format("%c1", kControlRegister); // Avvy himself!
1165 	}
1166 
1167 	bool found = false; // The _person we're looking for's code is in _person.
1168 	Common::String tmpStr;
1169 
1170 	for (int i = 0; i < _vm->_animation->kSpriteNumbMax; i++) {
1171 		AnimationType *curSpr = _vm->_animation->_sprites[i];
1172 		if (curSpr->_quick && (curSpr->_characterId + 149 == _vm->_parser->_person)) {
1173 			tmpStr += Common::String::format("%c%c", kControlRegister, '1' + i);
1174 			found = true;
1175 		}
1176 	}
1177 
1178 	if (found)
1179 		return tmpStr;
1180 
1181 	for (int i = 0; i < 16; i++) {
1182 		if ((kQuasipeds[i]._who == _vm->_parser->_person) && (kQuasipeds[i]._room == _vm->_room))
1183 			tmpStr += Common::String::format("%c%c", kControlRegister, 'A' + i);
1184 	}
1185 
1186 	return tmpStr;
1187 }
1188 
1189 /**
1190  * Display a message when (uselessly) giving an object away
1191  * @remarks	Originally called 'heythanks'
1192  */
sayThanks(byte thing)1193 void Dialogs::sayThanks(byte thing) {
1194 	Common::String tmpStr = personSpeaks();
1195 	tmpStr += Common::String::format("Hey, thanks!%c(But now, you've lost it!)", kControlSpeechBubble);
1196 	displayText(tmpStr);
1197 
1198 	if (thing < kObjectNum)
1199 		_vm->_objects[thing] = false;
1200 }
1201 
1202 /**
1203  * Display a 'Hello' message
1204  */
sayHello()1205 void Dialogs::sayHello() {
1206 	Common::String tmpStr = personSpeaks();
1207 	tmpStr += Common::String::format("Hello.%c", kControlSpeechBubble);
1208 	displayText(tmpStr);
1209 }
1210 
1211 /**
1212  * Display a 'OK' message
1213  */
sayOK()1214 void Dialogs::sayOK() {
1215 	Common::String tmpStr = personSpeaks();
1216 	tmpStr += Common::String::format("That's OK.%c", kControlSpeechBubble);
1217 	displayText(tmpStr);
1218 }
1219 
1220 /**
1221  * Display a 'Silly' message
1222  * @remarks	Originally called 'silly'
1223  */
saySilly()1224 void Dialogs::saySilly() {
1225 	displayText("Don't be silly!");
1226 }
1227 
1228 } // End of namespace Avalanche
1229