/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * This code is based on the original source code of Lord Avalot d'Argent version 1.3. * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. */ /* SCROLLS The scroll driver. */ #include "avalanche/avalanche.h" #include "avalanche/dialogs.h" #include "common/system.h" #include "common/random.h" namespace Avalanche { const Dialogs::TuneType Dialogs::kTune = { kPitchHigher, kPitchHigher, kPitchLower, kPitchSame, kPitchHigher, kPitchHigher, kPitchLower, kPitchHigher, kPitchHigher, kPitchHigher, kPitchLower, kPitchHigher, kPitchHigher, kPitchSame, kPitchHigher, kPitchLower, kPitchLower, kPitchLower, kPitchLower, kPitchHigher, kPitchHigher, kPitchLower, kPitchLower, kPitchLower, kPitchLower, kPitchSame, kPitchLower, kPitchHigher, kPitchSame, kPitchLower, kPitchHigher }; // A quasiped defines how people who aren't sprites talk. For example, quasiped // "A" is Dogfood. The rooms aren't stored because I'm leaving that to context. const QuasipedType Dialogs::kQuasipeds[16] = { //_whichPed, _foregroundColor, _room, _backgroundColor, _who {1, kColorLightgray, kRoomArgentPub, kColorBrown, kPeopleDogfood}, // A: Dogfood (screen 19). {2, kColorGreen, kRoomArgentPub, kColorWhite, kPeopleIbythneth}, // B: Ibythneth (screen 19). {2, kColorWhite, kRoomYours, kColorMagenta, kPeopleArkata}, // C: Arkata (screen 1). {2, kColorBlack, kRoomLustiesRoom, kColorRed, kPeopleInvisible}, // D: Hawk (screen 23). {2, kColorLightgreen, kRoomOutsideDucks, kColorBrown, kPeopleTrader}, // E: Trader (screen 50). {5, kColorYellow, kRoomRobins, kColorRed, kPeopleAvalot}, // F: Avvy, tied up (scr.42) {1, kColorBlue, kRoomAylesOffice, kColorWhite, kPeopleAyles}, // G: Ayles (screen 16). {1, kColorBrown, kRoomMusicRoom, kColorWhite, kPeopleJacques}, // H: Jacques (screen 7). {1, kColorLightgreen, kRoomNottsPub, kColorGreen, kPeopleSpurge}, // I: Spurge (screen 47). {2, kColorYellow, kRoomNottsPub, kColorRed, kPeopleAvalot}, // J: Avalot (screen 47). {1, kColorLightgray, kRoomLustiesRoom, kColorBlack, kPeopleDuLustie}, // K: du Lustie (screen 23). {1, kColorYellow, kRoomOubliette, kColorRed, kPeopleAvalot}, // L: Avalot (screen 27). {2, kColorWhite, kRoomOubliette, kColorRed, kPeopleInvisible}, // M: Avaroid (screen 27). {3, kColorLightgray, kRoomArgentPub, kColorDarkgray, kPeopleMalagauche},// N: Malagauche (screen 19). {4, kColorLightmagenta, kRoomNottsPub, kColorRed, kPeoplePort}, // O: Port (screen 47). {1, kColorLightgreen, kRoomDucks, kColorDarkgray, kPeopleDrDuck} // P: Duck (screen 51). }; Dialogs::Dialogs(AvalancheEngine *vm) { _vm = vm; _noError = true; _aboutBox = false; _talkX = 0; _talkY = 0; _maxLineNum = 0; _scReturn = false; _currentFont = kFontStyleRoman; _param = 0; _useIcon = 0; _scrollBells = 0; _underScroll = 0; _shadowBoxX = 0; _shadowBoxY = 0; } void Dialogs::init() { loadFont(); resetScrollDriver(); } /** * Determine the color of the ready light and draw it * @remarks Originally called 'state' */ void Dialogs::setReadyLight(byte state) { if (_vm->_ledStatus == state) return; // Already like that! // TODO: Implement different patterns for green color. Color color = kColorBlack; switch (state) { default: case 0: color = kColorBlack; break; // Off case 1: case 2: case 3: color = kColorGreen; break; // Hit a key } _vm->_graphics->drawReadyLight(color); CursorMan.showMouse(true); _vm->_ledStatus = state; } void Dialogs::easterEgg() { warning("STUB: Scrolls::easterEgg()"); } void Dialogs::say(int16 x, int16 y, Common::String z) { FontType itw; byte lz = z.size(); bool offset = x % 8 == 4; x /= 8; y++; int16 i = 0; for (int xx = 0; xx < lz; xx++) { switch (z[xx]) { case kControlRoman: _currentFont = kFontStyleRoman; break; case kControlItalic: _currentFont = kFontStyleItalic; break; default: { for (int yy = 0; yy < 12; yy++) itw[(byte)z[xx]][yy] = _fonts[_currentFont][(byte)z[xx]][yy + 2]; // We have to draw the characters one-by-one because of the accidental font changes. i++; Common::String chr(z[xx]); _vm->_graphics->drawScrollText(chr, itw, 12, (x - 1) * 8 + offset * 4 + i * 8, y, kColorBlack); } } } } /** * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters. * @remarks Originally called 'normscroll' */ void Dialogs::scrollModeNormal() { // Original code is: // egg : array[1..8] of char = ^P^L^U^G^H+'***'; // this is not using kControl characters: it's the secret code to be entered to trigger the easter egg // TODO: To be fixed when the Easter egg code is implemented Common::String egg = Common::String::format("%c%c%c%c%c***", kControlParagraph, kControlLeftJustified, kControlNegative, kControlBell, kControlBackspace); Common::String e = "(c) 1994"; setReadyLight(3); _vm->_animationsEnabled = false; _vm->_graphics->loadMouse(kCurFletch); _vm->_graphics->saveScreen(); _vm->_graphics->showScroll(); Common::Event event; bool escape = false; while (!_vm->shouldQuit() && !escape) { _vm->_graphics->refreshScreen(); while (_vm->getEvent(event)) { if ((event.type == Common::EVENT_LBUTTONUP) || ((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_ESCAPE) || (event.kbd.keycode == Common::KEYCODE_RETURN) || (event.kbd.keycode == Common::KEYCODE_HASH) || (event.kbd.keycode == Common::KEYCODE_PLUS)))) { escape = true; break; } else if (event.type == Common::EVENT_KEYDOWN) _vm->errorLed(); } } _vm->_graphics->restoreScreen(); _vm->_graphics->removeBackup(); warning("STUB: scrollModeNormal() - Check Easter Egg trigger"); #if 0 char r; bool oktoexit; do { do { _vm->check(); // was "checkclick;" //#ifdef RECORD slowdown(); basher::count++; #endif if (_vm->_enhanced->keypressede()) break; } while (!((mrelease > 0) || (buttona1()) || (buttonb1()))); if (mrelease == 0) { inkey(); if (aboutscroll) { move(e[2 - 1], e[1 - 1], 7); e[8 - 1] = inchar; if (egg == e) easteregg(); } oktoexit = set::of('\15', '\33', '+', '#', eos).has(inchar); if (!oktoexit) errorled(); } } while (!((oktoexit) || (mrelease > 0))); //#ifdef RECORD record_one(); #endif _vm->screturn = r == '#'; // "back door" #endif setReadyLight(0); _vm->_animationsEnabled = true; _vm->_holdLeftMouse = false; // Used in Lucerna::checkclick(). } /** * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters. * The "asking" scroll. Used indirectly in diplayQuestion(). * @remarks Originally called 'dialogue' */ void Dialogs::scrollModeDialogue() { _vm->_graphics->loadMouse(kCurHand); _vm->_graphics->saveScreen(); _vm->_graphics->showScroll(); Common::Event event; while (!_vm->shouldQuit()) { _vm->_graphics->refreshScreen(); _vm->getEvent(event); Common::Point cursorPos = _vm->getMousePos(); cursorPos.y /= 2; char inChar = 0; if ((event.type == Common::EVENT_KEYDOWN) && (event.kbd.ascii <= 122) && (event.kbd.ascii >= 97)) { inChar = (char)event.kbd.ascii; Common::String temp(inChar); temp.toUppercase(); inChar = temp[0]; } if (_vm->shouldQuit() || (event.type == Common::EVENT_LBUTTONUP) || (event.type == Common::EVENT_KEYDOWN)) { if (((cursorPos.x >= _shadowBoxX - 65) && (cursorPos.y >= _shadowBoxY - 24) && (cursorPos.x <= _shadowBoxX - 5) && (cursorPos.y <= _shadowBoxY - 10)) || (inChar == 'Y') || (inChar == 'J') || (inChar == 'O')) { // Yes, Ja, Oui _scReturn = true; break; } else if (((cursorPos.x >= _shadowBoxX + 5) && (cursorPos.y >= _shadowBoxY - 24) && (cursorPos.x <= _shadowBoxX + 65) && (cursorPos.y <= _shadowBoxY - 10)) || (inChar == 'N')){ // No, Non, Nein _scReturn = false; break; } } } _vm->_graphics->restoreScreen(); _vm->_graphics->removeBackup(); } void Dialogs::store(byte what, TuneType &played) { memcpy(played, played + 1, sizeof(played) - 1); played[30] = what; } bool Dialogs::theyMatch(TuneType &played) { byte mistakes = 0; for (unsigned int i = 0; i < sizeof(played); i++) { if (played[i] != kTune[i]) mistakes++; } return mistakes < 5; } /** * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters. * Part of the harp mini-game. * @remarks Originally called 'music_Scroll' */ void Dialogs::scrollModeMusic() { setReadyLight(3); _vm->_animationsEnabled = false; CursorMan.showMouse(false); _vm->_graphics->loadMouse(kCurFletch); TuneType played; for (unsigned int i = 0; i < sizeof(played); i++) played[i] = kPitchInvalid; int8 lastOne = -1, thisOne = -1; // Invalid values. _vm->_animationsEnabled = false; _vm->_graphics->saveScreen(); _vm->_graphics->showScroll(); Common::Event event; while (!_vm->shouldQuit()) { _vm->_graphics->refreshScreen(); _vm->getEvent(event); // When we stop playing? if ((event.type == Common::EVENT_LBUTTONDOWN) || ((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_RETURN) || (event.kbd.keycode == Common::KEYCODE_ESCAPE)))) { break; } // When we DO play: if ((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_q) || (event.kbd.keycode == Common::KEYCODE_w) || (event.kbd.keycode == Common::KEYCODE_e) || (event.kbd.keycode == Common::KEYCODE_r) || (event.kbd.keycode == Common::KEYCODE_t) || (event.kbd.keycode == Common::KEYCODE_y) || (event.kbd.keycode == Common::KEYCODE_u) || (event.kbd.keycode == Common::KEYCODE_i) || (event.kbd.keycode == Common::KEYCODE_o) || (event.kbd.keycode == Common::KEYCODE_p) || (event.kbd.keycode == Common::KEYCODE_LEFTBRACKET) || (event.kbd.keycode == Common::KEYCODE_RIGHTBRACKET))) { byte value = 0; switch (event.kbd.keycode) { case Common::KEYCODE_q: value = 0; break; case Common::KEYCODE_w: value = 1; break; case Common::KEYCODE_e: value = 2; break; case Common::KEYCODE_r: value = 3; break; case Common::KEYCODE_t: value = 4; break; case Common::KEYCODE_y: value = 5; break; case Common::KEYCODE_u: value = 6; break; case Common::KEYCODE_i: value = 7; break; case Common::KEYCODE_o: value = 8; break; case Common::KEYCODE_p: value = 9; break; case Common::KEYCODE_LEFTBRACKET: value = 10; break; case Common::KEYCODE_RIGHTBRACKET: value = 11; break; default: error("cannot happen"); break; } lastOne = thisOne; thisOne = value; _vm->_sound->playNote(_vm->kNotes[thisOne], 100); _vm->_system->delayMillis(200); if (!_vm->_bellsAreRinging) { // These handle playing the right tune. if (thisOne < lastOne) store(kPitchLower, played); else if (thisOne == lastOne) store(kPitchSame, played); else store(kPitchHigher, played); } if (theyMatch(played)) { setReadyLight(0); _vm->_timer->addTimer(8, Timer::kProcJacquesWakesUp, Timer::kReasonJacquesWakingUp); break; } } } _vm->_graphics->restoreScreen(); _vm->_graphics->removeBackup(); _vm->_animationsEnabled = true; CursorMan.showMouse(true); } void Dialogs::resetScrollDriver() { _scrollBells = 0; _currentFont = kFontStyleRoman; _useIcon = 0; _vm->_interrogation = 0; // Always reset after a scroll comes up. } /** * Rings the bell x times * @remarks Originally called 'dingdongbell' */ void Dialogs::ringBell() { for (int i = 0; i < _scrollBells; i++) _vm->errorLed(); // Ring the bell "_scrollBells" times. } /** * This moves the mouse pointer off the scroll so that you can read it. * @remarks Originally called 'dodgem' */ void Dialogs::dodgem() { _dodgeCoord = _vm->getMousePos(); g_system->warpMouse(_dodgeCoord.x, _underScroll); // Move the pointer off the scroll. } /** * This is the opposite of Dodgem. * It moves the mouse pointer back, IF you haven't moved it in the meantime. * @remarks Originally called 'undodgem' */ void Dialogs::unDodgem() { Common::Point actCoord = _vm->getMousePos(); if ((actCoord.x == _dodgeCoord.x) && (actCoord.y == _underScroll)) g_system->warpMouse(_dodgeCoord.x, _dodgeCoord.y); // No change, so restore the pointer's original position. } void Dialogs::drawScroll(DialogFunctionType modeFunc) { int16 lx = 0; int16 ly = (_maxLineNum + 1) * 6; int16 ex; for (int i = 0; i <= _maxLineNum; i++) { ex = _scroll[i].size() * 8; if (lx < ex) lx = ex; } int16 mx = 320; int16 my = 100; // Getmaxx & getmaxy div 2, both. lx /= 2; ly -= 2; if ((1 <= _useIcon) && (_useIcon <= 34)) lx += kHalfIconWidth; CursorMan.showMouse(false); _vm->_graphics->drawScroll(mx, lx, my, ly); mx -= lx; my -= ly + 2; bool center = false; byte iconIndent = 0; switch (_useIcon) { default: case 0: iconIndent = 0; break; // No icon. case 34: _vm->_graphics->drawSign("about", 28, 76, 15); iconIndent = 0; break; case 35: _vm->_graphics->drawSign("gameover", 52, 59, 71); iconIndent = 0; break; } if ((1 <= _useIcon) && (_useIcon <= 33)) { // Standard icon. _vm->_graphics->drawIcon(mx, my + ly / 2, _useIcon); iconIndent = 53; } for (int i = 0; i <= _maxLineNum; i++) { if (!_scroll[i].empty()) switch (_scroll[i][_scroll[i].size() - 1]) { case kControlCenter: center = true; _scroll[i].deleteLastChar(); break; case kControlLeftJustified: center = false; _scroll[i].deleteLastChar(); break; case kControlQuestion: _shadowBoxX = mx + lx; _shadowBoxY = my + ly; _scroll[i].setChar(' ', 0); _vm->_graphics->drawShadowBox(_shadowBoxX - 65, _shadowBoxY - 24, _shadowBoxX - 5, _shadowBoxY - 10, "Yes."); _vm->_graphics->drawShadowBox(_shadowBoxX + 5, _shadowBoxY - 24, _shadowBoxX + 65, _shadowBoxY - 10, "No."); break; default: break; } if (center) say(320 - _scroll[i].size() * 4 + iconIndent, my, _scroll[i]); else say(mx + iconIndent, my, _scroll[i]); my += 12; } _underScroll = (my + 3) * 2; // Multiplying because of the doubled screen height. ringBell(); _vm->_dropsOk = false; dodgem(); (this->*modeFunc)(); unDodgem(); _vm->_dropsOk = true; resetScrollDriver(); } void Dialogs::drawBubble(DialogFunctionType modeFunc) { Common::Point points[3]; CursorMan.showMouse(false); int16 xl = 0; int16 yl = (_maxLineNum + 1) * 5; for (int i = 0; i <= _maxLineNum; i++) { uint16 textWidth = _scroll[i].size() * 8; if (textWidth > xl) xl = textWidth; } xl /= 2; int16 xw = xl + 18; int16 yw = yl + 7; int16 my = yw * 2 - 2; int16 xc = 0; if (_talkX - xw < 0) xc = -(_talkX - xw); if (_talkX + xw > 639) xc = 639 - (_talkX + xw); // Compute triangle coords for the tail of the bubble points[0].x = _talkX - 10; points[0].y = yw; points[1].x = _talkX + 10; points[1].y = yw; points[2].x = _talkX; points[2].y = _talkY; _vm->_graphics->prepareBubble(xc, xw, my, points); // Draw the text of the bubble. The centering of the text was improved here compared to Pascal's settextjustify(). // The font is not the same that outtextxy() uses in Pascal. I don't have that, so I used characters instead. // It's almost the same, only notable differences are '?', '!', etc. for (int i = 0; i <= _maxLineNum; i++) { int16 x = xc + _talkX - _scroll[i].size() / 2 * 8; bool offset = _scroll[i].size() % 2; _vm->_graphics->drawScrollText(_scroll[i], _vm->_font, 8, x - offset * 4, (i * 10) + 12, _vm->_graphics->_talkFontColor); } ringBell(); CursorMan.showMouse(false); _vm->_dropsOk = false; // This does the actual drawing to the screen. (this->*modeFunc)(); _vm->_dropsOk = true; CursorMan.showMouse(true); // sink; resetScrollDriver(); } void Dialogs::reset() { _maxLineNum = 0; for (int i = 0; i < 15; i++) { if (!_scroll[i].empty()) _scroll[i].clear(); } } /** * Natural state of bubbles * @remarks Originally called 'natural' */ void Dialogs::setBubbleStateNatural() { _talkX = 320; _talkY = 200; _vm->_graphics->setDialogColor(kColorDarkgray, kColorWhite); } Common::String Dialogs::displayMoney() { Common::String result; if (_vm->_money < 12) { // just pence result = Common::String::format("%dd", _vm->_money); } else if (_vm->_money < 240) { // shillings & pence if ((_vm->_money % 12) == 0) result = Common::String::format("%d/-", _vm->_money / 12); else result = Common::String::format("%d/%d", _vm->_money / 12, _vm->_money % 12); } else { // L, s & d result = Common::String::format("\x9C%d.%d.%d", _vm->_money / 240, (_vm->_money / 12) % 20, _vm->_money % 12); } if (_vm->_money > 12) { Common::String extraStr = Common::String::format(" (that's %dd)", _vm->_money); result += extraStr; } return result; } /** * Strip trailing character in a string * @remarks Originally called 'strip' */ void Dialogs::stripTrailingSpaces(Common::String &str) { while (str.lastChar() == ' ') str.deleteLastChar(); // We don't use String::trim() here because we need the leading whitespaces. } /** * Does the word wrapping. */ void Dialogs::solidify(byte n) { if (!_scroll[n].contains(' ')) return; // No spaces. // So there MUST be a space there, somewhere... do { _scroll[n + 1] = _scroll[n][_scroll[n].size() - 1] + _scroll[n + 1]; _scroll[n].deleteLastChar(); } while (_scroll[n][_scroll[n].size() - 1] != ' '); stripTrailingSpaces(_scroll[n]); } /** * @remarks Originally called 'calldriver' * Display text by calling the dialog driver. It unifies the function of the original * 'calldriver' and 'display' by using Common::String instead of a private buffer. */ void Dialogs::displayText(Common::String text) { _vm->_sound->stopSound(); setReadyLight(0); _scReturn = false; bool mouthnext = false; bool callSpriteRun = true; // Only call sprite_run the FIRST time. switch (text.lastChar()) { case kControlToBuffer: text.deleteLastChar(); break; // ^D = (D)on't include pagebreak case kControlSpeechBubble: case kControlQuestion: break; // ^B = speech (B)ubble, ^Q = (Q)uestion in dialogue box default: text.insertChar(kControlParagraph, text.size()); } for (uint16 i = 0; i < text.size(); i++) { if (mouthnext) { if (text[i] == kControlRegister) _param = 0; else if (('0' <= text[i]) && (text[i] <= '9')) _param = text[i] - 48; else if (('A' <= text[i]) && (text[i] <= 'Z')) _param = text[i] - 55; mouthnext = false; } else { switch (text[i]) { case kControlParagraph: if ((_maxLineNum == 0) && (_scroll[0].empty())) break; if (callSpriteRun) _vm->spriteRun(); callSpriteRun = false; drawScroll(&Avalanche::Dialogs::scrollModeNormal); reset(); if (_scReturn) return; break; case kControlBell: _scrollBells++; break; case kControlSpeechBubble: if ((_maxLineNum == 0) && (_scroll[0].empty())) break; if (callSpriteRun) _vm->spriteRun(); callSpriteRun = false; if (_param == 0) setBubbleStateNatural(); else if ((1 <= _param) && (_param <= 9)) { assert(_param - 1 < _vm->_animation->kSpriteNumbMax); AnimationType *spr = _vm->_animation->_sprites[_param - 1]; if ((_param > _vm->_animation->kSpriteNumbMax) || (!spr->_quick)) { // Not valid. _vm->errorLed(); setBubbleStateNatural(); } else spr->chatter(); // Normal sprite talking routine. } else if ((10 <= _param) && (_param <= 36)) { // Quasi-peds. (This routine performs the same // thing with QPs as triptype.chatter does with the // sprites.) assert(_param - 10 < 16); PedType *quasiPed = &_vm->_peds[kQuasipeds[_param - 10]._whichPed]; _talkX = quasiPed->_x; _talkY = quasiPed->_y; // Position. _vm->_graphics->setDialogColor(kQuasipeds[_param - 10]._backgroundColor, kQuasipeds[_param - 10]._textColor); } else { _vm->errorLed(); // Not valid. setBubbleStateNatural(); } drawBubble(&Avalanche::Dialogs::scrollModeNormal); reset(); if (_scReturn) return; break; // CHECKME: The whole kControlNegative block seems completely unused, as the only use (the easter egg check) is a false positive case kControlNegative: switch (_param) { case 1: displayText(displayMoney() + kControlToBuffer); // Insert cash balance. (Recursion) break; case 2: { int pwdId = _vm->_parser->kFirstPassword + _vm->_passwordNum; displayText(_vm->_parser->_vocabulary[pwdId]._word + kControlToBuffer); } break; case 3: displayText(_vm->_favoriteDrink + kControlToBuffer); break; case 4: displayText(_vm->_favoriteSong + kControlToBuffer); break; case 5: displayText(_vm->_worstPlaceOnEarth + kControlToBuffer); break; case 6: displayText(_vm->_spareEvening + kControlToBuffer); break; case 9: { Common::String tmpStr = Common::String::format("%d,%d%c",_vm->_catacombX, _vm->_catacombY, kControlToBuffer); displayText(tmpStr); } break; case 10: switch (_vm->_boxContent) { case 0: // Sixpence. displayScrollChain('Q', 37); // You find the sixpence. _vm->_money += 6; _vm->_boxContent = _vm->_parser->kNothing; _vm->incScore(2); return; case Parser::kNothing: displayText("nothing at all. It's completely empty."); break; default: displayText(_vm->getItem(_vm->_boxContent) + '.'); } break; case 11: for (int j = 0; j < kObjectNum; j++) { if (_vm->_objects[j]) displayText(_vm->getItem(j) + ", " + kControlToBuffer); } break; default: break; } break; case kControlIcon: _useIcon = _param; break; case kControlNewLine: _maxLineNum++; break; case kControlQuestion: if (callSpriteRun) _vm->spriteRun(); callSpriteRun = false; _maxLineNum++; _scroll[_maxLineNum] = kControlQuestion; drawScroll(&Avalanche::Dialogs::scrollModeDialogue); reset(); break; case kControlRegister: mouthnext = true; break; case kControlInsertSpaces: for (int j = 0; j < 9; j++) _scroll[_maxLineNum] += ' '; break; default: // Add new char. if (_scroll[_maxLineNum].size() == 50) { solidify(_maxLineNum); _maxLineNum++; } _scroll[_maxLineNum] += text[i]; break; } } } setReadyLight(2); } void Dialogs::setTalkPos(int16 x, int16 y) { _talkX = x; _talkY = y; } int16 Dialogs::getTalkPosX() { return _talkX; } bool Dialogs::displayQuestion(Common::String question) { displayText(question + kControlNewLine + kControlQuestion); if (_scReturn && (_vm->_rnd->getRandomNumber(1) == 0)) { // Half-and-half chance. Common::String tmpStr = Common::String::format("...Positive about that?%cI%c%c%c", kControlRegister, kControlIcon, kControlNewLine, kControlQuestion); displayText(tmpStr); // Be annoying! if (_scReturn && (_vm->_rnd->getRandomNumber(3) == 3)) { // Another 25% chance // \? are used to avoid that ??! is parsed as a trigraph tmpStr = Common::String::format("%c100%% certain\?\?!%c%c%c%c", kControlInsertSpaces, kControlInsertSpaces, kControlIcon, kControlNewLine, kControlQuestion); displayText(tmpStr); // Be very annoying! } } return _scReturn; } void Dialogs::loadFont() { Common::File file; if (!file.open("avalot.fnt")) error("AVALANCHE: Scrolls: File not found: avalot.fnt"); for (int16 i = 0; i < 256; i++) file.read(_fonts[0][i], 16); file.close(); if (!file.open("avitalic.fnt")) error("AVALANCHE: Scrolls: File not found: avitalic.fnt"); for (int16 i = 0; i < 256; i++) file.read(_fonts[1][i], 16); file.close(); if (!file.open("ttsmall.fnt")) error("AVALANCHE: Scrolls: File not found: ttsmall.fnt"); for (int16 i = 0; i < 256; i++) file.read(_vm->_font[i],16); file.close(); } /** * Practically this one is a mini-game which called when you play the harp in the monastery. * @remarks Originally called 'musical_scroll' */ void Dialogs::displayMusicalScroll() { 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", kControlNewLine, kControlNewLine, kControlNewLine, kControlInsertSpaces, kControlNewLine, kControlNewLine, kControlToBuffer); displayText(tmpStr); _vm->spriteRun(); CursorMan.showMouse(false); drawScroll(&Avalanche::Dialogs::scrollModeMusic); CursorMan.showMouse(true); reset(); } void Dialogs::unSkrimble(Common::String &text) { for (uint16 i = 0; i < text.size(); i++) text.setChar((~(text[i] - (i + 1))) % 256, i); } void Dialogs::doTheBubble(Common::String &text) { text.insertChar(kControlSpeechBubble, text.size()); assert(text.size() < 2000); } /** * Display a string in a scroll * @remarks Originally called 'dixi' */ void Dialogs::displayScrollChain(char block, byte point, bool report, bool bubbling) { Common::File indexfile; if (!indexfile.open("avalot.idx")) error("AVALANCHE: Visa: File not found: avalot.idx"); bool error = false; indexfile.seek((toupper(block) - 'A') * 2); uint16 idx_offset = indexfile.readUint16LE(); if (idx_offset == 0) error = true; indexfile.seek(idx_offset + point * 2); uint16 sez_offset = indexfile.readUint16LE(); if (sez_offset == 0) error = true; indexfile.close(); _noError = !error; if (error) { if (report) { Common::String todisplay = Common::String::format("%cError accessing scroll %c%d", kControlBell, block, point); displayText(todisplay); } return; } Common::File sezfile; if (!sezfile.open("avalot.sez")) ::error("AVALANCHE: Visa: File not found: avalot.sez"); sezfile.seek(sez_offset); uint16 _bufSize = sezfile.readUint16LE(); assert(_bufSize < 2000); char *_buffer = new char[_bufSize]; sezfile.read(_buffer, _bufSize); sezfile.close(); Common::String text(_buffer, _bufSize); delete[] _buffer; unSkrimble(text); if (bubbling) doTheBubble(text); displayText(text); } /** * Start speech * @remarks Originally called 'speech' */ void Dialogs::speak(byte who, byte subject) { if (subject == 0) { // No subject. displayScrollChain('S', who, false, true); return; } // Subject given. _noError = false; // Assume that until we know otherwise. Common::File indexfile; if (!indexfile.open("converse.avd")) error("AVALANCHE: Visa: File not found: converse.avd"); indexfile.seek(who * 2 - 2); uint16 idx_offset = indexfile.readUint16LE(); uint16 next_idx_offset = indexfile.readUint16LE(); if ((idx_offset == 0) || ((((next_idx_offset - idx_offset) / 2) - 1) < subject)) return; indexfile.seek(idx_offset + subject * 2); uint16 sezOffset = indexfile.readUint16LE(); if ((sezOffset == 0) || (indexfile.err())) return; indexfile.close(); Common::File sezfile; if (!sezfile.open("avalot.sez")) error("AVALANCHE: Visa: File not found: avalot.sez"); sezfile.seek(sezOffset); uint16 _bufSize = sezfile.readUint16LE(); assert(_bufSize < 2000); char *_buffer = new char[_bufSize]; sezfile.read(_buffer, _bufSize); sezfile.close(); Common::String text(_buffer, _bufSize); delete[] _buffer; unSkrimble(text); doTheBubble(text); displayText(text); _noError = true; } void Dialogs::talkTo(byte whom) { if (_vm->_parser->_person == kPeoplePardon) { _vm->_parser->_person = (People)_vm->_subjectNum; _vm->_subjectNum = 0; } if (_vm->_subjectNum == 0) { switch (whom) { case kPeopleSpludwick: if ((_vm->_lustieIsAsleep) & (!_vm->_objects[kObjectPotion - 1])) { displayScrollChain('Q', 68); _vm->_objects[kObjectPotion - 1] = true; _vm->refreshObjectList(); _vm->incScore(3); return; } else if (_vm->_talkedToCrapulus) { // Spludwick - what does he need? // 0 - let it through to use normal routine. switch (_vm->_givenToSpludwick) { case 1: // Fallthrough is intended. case 2: { Common::String objStr = _vm->getItem(AvalancheEngine::kSpludwicksOrder[_vm->_givenToSpludwick]); Common::String tmpStr = Common::String::format("Can you get me %s, please?%c2%c", objStr.c_str(), kControlRegister, kControlSpeechBubble); displayText(tmpStr); } return; case 3: displayScrollChain('Q', 30); // Need any help with the game? return; default: break; } } else { displayScrollChain('Q', 42); // Haven't talked to Crapulus. Go and talk to him. return; } break; case kPeopleIbythneth: if (_vm->_givenBadgeToIby) { displayScrollChain('Q', 33); // Thanks a lot! return; // And leave the proc. } break; // Or... just continue, 'cos he hasn't got it. case kPeopleDogfood: if (_vm->_wonNim) { // We've won the game. displayScrollChain('Q', 6); // "I'm Not Playing!" return; // Zap back. } else _vm->_askedDogfoodAboutNim = true; break; case kPeopleAyles: if (!_vm->_aylesIsAwake) { displayScrollChain('Q', 43); // He's fast asleep! return; } else if (!_vm->_givenPenToAyles) { displayScrollChain('Q', 44); // Can you get me a pen, Avvy? return; } break; case kPeopleJacques: displayScrollChain('Q', 43); return; case kPeopleGeida: if (_vm->_givenPotionToGeida) _vm->_geidaFollows = true; else { displayScrollChain('U', 17); return; } break; case kPeopleSpurge: if (!_vm->_sittingInPub) { displayScrollChain('Q', 71); // Try going over and sitting down. return; } else { if (_vm->_spurgeTalkCount < 5) _vm->_spurgeTalkCount++; if (_vm->_spurgeTalkCount > 1) { // no. 1 falls through displayScrollChain('Q', 70 + _vm->_spurgeTalkCount); return; } } break; default: break; } // On a subject. Is there any reason to block it? } else if ((whom == kPeopleAyles) && (!_vm->_aylesIsAwake)) { displayScrollChain('Q', 43); // He's fast asleep! return; } if (whom > 149) whom -= 149; bool noMatches = true; for (int i = 0; i < _vm->_animation->kSpriteNumbMax; i++) { if (_vm->_animation->_sprites[i]->_characterId == whom) { Common::String tmpStr = Common::String::format("%c%c%c", kControlRegister, i + 49, kControlToBuffer); displayText(tmpStr); noMatches = false; break; } } if (noMatches) { Common::String tmpStr = Common::String::format("%c%c%c", kControlRegister, kControlRegister, kControlToBuffer); displayText(tmpStr); } speak(whom, _vm->_subjectNum); if (!_noError) displayScrollChain('N', whom); // File not found! if ((_vm->_subjectNum == 0) && ((whom + 149) == kPeopleCrapulus)) { // Crapulus: get the badge - first time only _vm->_objects[kObjectBadge - 1] = true; _vm->refreshObjectList(); displayScrollChain('Q', 1); // Circular from Cardiff. _vm->_talkedToCrapulus = true; _vm->setRoom(kPeopleCrapulus, kRoomDummy); // Crapulus walks off. AnimationType *spr = _vm->_animation->_sprites[1]; spr->_vanishIfStill = true; spr->walkTo(2); // Walks away. _vm->incScore(2); } } /** * This makes Avalot say the response. * @remarks Originally called 'sayit' */ void Dialogs::sayIt(Common::String str) { Common::String x = str; x.setChar(toupper(x[0]), 0); Common::String tmpStr = Common::String::format("%c1%s.%c%c2", kControlRegister, x.c_str(), kControlSpeechBubble, kControlRegister); displayText(tmpStr); } Common::String Dialogs::personSpeaks() { if ((_vm->_parser->_person == kPeoplePardon) || (_vm->_parser->_person == kPeopleNone)) { if ((_vm->_him == kPeoplePardon) || (_vm->getRoom(_vm->_him) != _vm->_room)) _vm->_parser->_person = _vm->_her; else _vm->_parser->_person = _vm->_him; } if (_vm->getRoom(_vm->_parser->_person) != _vm->_room) { return Common::String::format("%c1", kControlRegister); // Avvy himself! } bool found = false; // The _person we're looking for's code is in _person. Common::String tmpStr; for (int i = 0; i < _vm->_animation->kSpriteNumbMax; i++) { AnimationType *curSpr = _vm->_animation->_sprites[i]; if (curSpr->_quick && (curSpr->_characterId + 149 == _vm->_parser->_person)) { tmpStr += Common::String::format("%c%c", kControlRegister, '1' + i); found = true; } } if (found) return tmpStr; for (int i = 0; i < 16; i++) { if ((kQuasipeds[i]._who == _vm->_parser->_person) && (kQuasipeds[i]._room == _vm->_room)) tmpStr += Common::String::format("%c%c", kControlRegister, 'A' + i); } return tmpStr; } /** * Display a message when (uselessly) giving an object away * @remarks Originally called 'heythanks' */ void Dialogs::sayThanks(byte thing) { Common::String tmpStr = personSpeaks(); tmpStr += Common::String::format("Hey, thanks!%c(But now, you've lost it!)", kControlSpeechBubble); displayText(tmpStr); if (thing < kObjectNum) _vm->_objects[thing] = false; } /** * Display a 'Hello' message */ void Dialogs::sayHello() { Common::String tmpStr = personSpeaks(); tmpStr += Common::String::format("Hello.%c", kControlSpeechBubble); displayText(tmpStr); } /** * Display a 'OK' message */ void Dialogs::sayOK() { Common::String tmpStr = personSpeaks(); tmpStr += Common::String::format("That's OK.%c", kControlSpeechBubble); displayText(tmpStr); } /** * Display a 'Silly' message * @remarks Originally called 'silly' */ void Dialogs::saySilly() { displayText("Don't be silly!"); } } // End of namespace Avalanche