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