diff options
Diffstat (limited to 'engines/avalanche/dialogs.cpp')
-rw-r--r-- | engines/avalanche/dialogs.cpp | 1196 |
1 files changed, 1196 insertions, 0 deletions
diff --git a/engines/avalanche/dialogs.cpp b/engines/avalanche/dialogs.cpp new file mode 100644 index 0000000000..e5acd9cae2 --- /dev/null +++ b/engines/avalanche/dialogs.cpp @@ -0,0 +1,1196 @@ +/* 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/random.h" + +namespace Avalanche { + +// 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; +} + +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! + + Color color = kColorBlack; + switch (state) { + case 0: + color = kColorBlack; + break; // Off + case 1: + case 2: + case 3: + color = kColorGreen; + break; // Hit a key + } + warning("STUB: Dialogs::setReadyLight()"); + + CursorMan.showMouse(false); + _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->_seeScroll = true; + _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; + } + } + } + + _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->_seeScroll = false; + _vm->_holdLeftMouse = false; // Used in Lucerna::checkclick(). + + warning("STUB: Scrolls::scrollModeNormal()"); +} + +/** + * 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] != _vm->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->_seeScroll = true; + 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->_seeScroll = true; + + _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; + 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: + 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->_seeScroll = false; + 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 centre = false; + + byte iconIndent = 0; + switch (_useIcon) { + 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: + centre = true; + _scroll[i].deleteLastChar(); + break; + case kControlLeftJustified: + centre = 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; + } + + if (centre) + 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) { +// bool was_virtual; // Was the mouse cursor virtual on entry to this proc? + warning("STUB: Scrolls::calldrivers()"); + + _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)) { + 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.) + 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; + + // CHECME: 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->_favouriteDrink + kControlToBuffer); + break; + case 4: + displayText(_vm->_favouriteSong + 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; + } + 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; + } + } + } +} + +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) - 65) * 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; + } + } 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; + } + // 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); + _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 |