/* 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 3 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, see . * */ #include "engines/nancy/nancy.h" #include "engines/nancy/graphics.h" #include "engines/nancy/resource.h" #include "engines/nancy/sound.h" #include "engines/nancy/input.h" #include "engines/nancy/util.h" #include "engines/nancy/font.h" #include "engines/nancy/action/puzzle/telephone.h" #include "engines/nancy/state/scene.h" namespace Nancy { namespace Action { void Telephone::init() { Common::Rect screenBounds = NancySceneState.getViewport().getBounds(); _drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat()); _drawSurface.clear(g_nancy->_graphics->getTransColor()); setTransparent(true); setVisible(true); moveTo(screenBounds); g_nancy->_resource->loadImage(_imageName, _image); g_nancy->_resource->loadImage(_displayAnimName, _animImage); if (_isNewPhone) { _font = g_nancy->_graphics->getFont(_displayFont); } // Set the phone tutorial flag to false for Nancy9, so that // the actual phone interface is available after the tutorial. // TODO: Is this the right place to set this flag? if (g_nancy->getGameType() == kGameTypeNancy9) { NancySceneState.setEventFlag(592, g_nancy->_false); } } void Telephone::readData(Common::SeekableReadStream &stream) { readFilename(stream, _imageName); uint16 numButtons = 12; uint16 maxNumButtons = _isNewPhone ? 20 : 12; if (_isNewPhone) { _hasDisplay = stream.readByte(); _displayFont = stream.readUint16LE(); readFilename(stream, _displayAnimName); _displayAnimFrameTime = stream.readUint32LE(); uint16 numFrames = stream.readUint16LE(); readRectArray(stream, _displaySrcs, numFrames, 10); readRect(stream, _displayDest); _dialAutomatically = stream.readByte(); numButtons = stream.readUint16LE(); } readRectArray(stream, _srcRects, numButtons, maxNumButtons); readRectArray(stream, _destRects, numButtons, maxNumButtons); if (_isNewPhone) { readRect(stream, _dirHighlightSrc); readRect(stream, _dialHighlightSrc); _upDirButtonID = stream.readUint16LE(); _downDirButtonID = stream.readUint16LE(); _dialButtonID = stream.readUint16LE(); _dirButtonID = stream.readUint16LE(); readRect(stream, _displayDialingSrc); } if (!_isNewPhone) { _genericDialogueSound.readNormal(stream); _genericButtonSound.readNormal(stream); _ringSound.readNormal(stream); _dialToneSound.readNormal(stream); _dialAgainSound.readNormal(stream); _hangUpSound.readNormal(stream); } else { _ringSound.readNormal(stream); _dialToneSound.readNormal(stream); _preCallSound.readNormal(stream); _hangUpSound.readNormal(stream); _genericButtonSound.readNormal(stream); } readFilenameArray(stream, _buttonSoundNames, numButtons); stream.skip(33 * (maxNumButtons - numButtons)); char textBuf[200]; if (!_isNewPhone) { stream.read(textBuf, 200); textBuf[199] = '\0'; _addressBookString = textBuf; } else { _dialAgainSound.readNormal(stream); } stream.read(textBuf, 200); textBuf[199] = '\0'; _dialAgainString = textBuf; _reloadScene.readData(stream); stream.skip(1); _exitScene.readData(stream); stream.skip(1); readRect(stream, _exitHotspot); uint numCalls = stream.readUint16LE(); _calls.resize(numCalls); for (uint i = 0; i < numCalls; ++i) { PhoneCall &call = _calls[i]; if (_isNewPhone) { call.directoryDisplayCondition = stream.readSint16LE(); } call.phoneNumber.resize(11); for (uint j = 0; j < 11; ++j) { call.phoneNumber[j] = stream.readByte(); } if (!_isNewPhone) { readFilename(stream, call.soundName); stream.read(textBuf, 200); textBuf[199] = '\0'; call.text = textBuf; } else { readRect(stream, call.displaySrc); } call.sceneChange.readData(stream); stream.skip(1); } } void Telephone::execute() { switch (_state) { case kBegin: init(); registerGraphics(); g_nancy->_sound->loadSound(_dialToneSound); g_nancy->_sound->playSound(_dialToneSound); NancySceneState.getTextbox().clear(); NancySceneState.getTextbox().addTextLine(_addressBookString); _state = kRun; // fall through case kRun: switch (_callState) { case kWaiting: if (_isNewPhone && !_animIsStopped) { if (g_nancy->getTotalPlayTime() > _displayAnimEnd) { if (_displayAnimEnd == 0) { _displayAnimEnd = g_nancy->getTotalPlayTime() + _displayAnimFrameTime; } else { _displayAnimEnd += _displayAnimFrameTime; } _drawSurface.blitFrom(_animImage, _displaySrcs[_displayAnimFrame], _displayDest); _needsRedraw = true; ++_displayAnimFrame; if (_displayAnimFrame >= _displaySrcs.size()) { _displayAnimFrame = 0; } } } if (_checkNumbers) { // Pressed a new button, check all numbers for match // We do this before going to the ringing state to support nancy4's voice mail system, // where call numbers can be 1 digit long for (uint i = 0; i < _calls.size(); ++i) { auto &call = _calls[i]; bool invalid = false; for (uint j = 0; j < _calledNumber.size(); ++j) { if (_calledNumber[j] != call.phoneNumber[j]) { // Invalid number, move onto next invalid = true; break; } } // We do not want to check for a terminator if the dialed number is of // appropriate size (7 digits, or 11 when the number starts with '1') bool checkNextDigit = true; if (_calledNumber.size() >= 11 || (_calledNumber.size() >= 7 && (_calledNumber[0] != 1))) { checkNextDigit = false; } if (!invalid && checkNextDigit) { // Check if the next digit in the phone number is '10' (star). Presumably, that will never // be contained in a valid phone number if (_calls[i].phoneNumber[_calledNumber.size()] != 10) { invalid = true; } } if (!invalid) { _selected = i; break; } } bool shouldRing = false; if (_selected == -1) { // Did not find a suitable match, check if the dialed number is above allowed size if (_calledNumber.size() >= 11 || (_calledNumber.size() >= 7 && (_calledNumber[0] != 1))) { shouldRing = true; } } else { shouldRing = true; } if (shouldRing) { if (_ringSound.name != "NO SOUND") { if (_hasDisplay) { _drawSurface.blitFrom(_image, _displayDialingSrc, _displayDest); _needsRedraw = true; } else { NancySceneState.getTextbox().clear(); NancySceneState.getTextbox().addTextLine(g_nancy->getStaticData().ringingText); } g_nancy->_sound->loadSound(_ringSound); g_nancy->_sound->playSound(_ringSound); } _callState = kRinging; } _checkNumbers = false; } break; case kButtonPress: if (!g_nancy->_sound->isSoundPlaying(_genericButtonSound)) { g_nancy->_sound->stopSound(_genericButtonSound); _drawSurface.fillRect(_destRects[_buttonLastPushed], g_nancy->_graphics->getTransColor()); _needsRedraw = true; if (_isShowingDirectory) { _drawSurface.blitFrom(_image, _dirHighlightSrc, _destRects[_dirButtonID]); _drawSurface.blitFrom(_image, _calls[_displayedDirectory].displaySrc, _displayDest); } else if (_dirButtonID != -1) { _drawSurface.fillRect(_destRects[_dirButtonID], _drawSurface.getTransparentColor()); } _buttonLastPushed = -1; _callState = kWaiting; } break; case kRinging: if (!g_nancy->_sound->isSoundPlaying(_ringSound)) { g_nancy->_sound->stopSound(_ringSound); if (_selected != -1) { // Called a valid number if (_preCallSound.name == "NO SOUND") { // Old phone, go directly to call NancySceneState.getTextbox().clear(); NancySceneState.getTextbox().addTextLine(_calls[_selected].text); _genericDialogueSound.name = _calls[_selected].soundName; g_nancy->_sound->loadSound(_genericDialogueSound); g_nancy->_sound->playSound(_genericDialogueSound); _callState = kCall; } else { // New phone, play a short sound of phone being picked up g_nancy->_sound->loadSound(_preCallSound); g_nancy->_sound->playSound(_preCallSound); _callState = kPreCall; } } else { // Called an invalid number NancySceneState.getTextbox().clear(); NancySceneState.getTextbox().addTextLine(_dialAgainString); if (_hasDisplay) { _drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor()); _needsRedraw = true; } if (_dialButtonID != -1) { _drawSurface.fillRect(_destRects[_dialButtonID], _drawSurface.getTransparentColor()); _needsRedraw = true; } _calledNumber.clear(); g_nancy->_sound->loadSound(_dialAgainSound); g_nancy->_sound->playSound(_dialAgainSound); _callState = kBadNumber; } return; } break; case kPreCall: if (!g_nancy->_sound->isSoundPlaying(_preCallSound)) { g_nancy->_sound->stopSound(_preCallSound); if (!_calls[_selected].text.empty()) { NancySceneState.getTextbox().clear(); NancySceneState.getTextbox().addTextLine(_calls[_selected].text); } _genericDialogueSound.name = _calls[_selected].soundName; g_nancy->_sound->loadSound(_genericDialogueSound); g_nancy->_sound->playSound(_genericDialogueSound); _callState = kCall; } break; case kBadNumber: if (!g_nancy->_sound->isSoundPlaying(_dialAgainSound)) { _state = kActionTrigger; } break; case kCall: if (!g_nancy->_sound->isSoundPlaying(_genericDialogueSound)) { _state = kActionTrigger; } break; case kHangUp: if (!g_nancy->_sound->isSoundPlaying(_hangUpSound)) { _state = kActionTrigger; } break; } break; case kActionTrigger: switch (_callState) { case kBadNumber: _reloadScene.execute(); _calledNumber.clear(); _state = kRun; _callState = kWaiting; break; case kCall: { PhoneCall &call = _calls[_selected]; // Make sure we don't get stuck here. Happens in nancy3 when calling George's number // Check ignored in nancy1 since the HintSystem AR is in the same scene as the Telephone if (call.sceneChange._sceneChange.sceneID == kNoScene && g_nancy->getGameType() != kGameTypeNancy1) { call.sceneChange._sceneChange = NancySceneState.getSceneInfo(); } call.sceneChange.execute(); break; } case kHangUp: _exitScene.execute(); break; default: break; } g_nancy->_sound->stopSound(_hangUpSound); g_nancy->_sound->stopSound(_genericDialogueSound); g_nancy->_sound->stopSound(_genericButtonSound); g_nancy->_sound->stopSound(_dialAgainSound); g_nancy->_sound->stopSound(_ringSound); g_nancy->_sound->stopSound(_dialToneSound); finishExecution(); } } void Telephone::handleInput(NancyInput &input) { int buttonNr = -1; // Cursor gets changed regardless of state for (int i = 0; i < (int)_destRects.size(); ++i) { // Dial button is an exception if (i == _dialButtonID && !_calledNumber.size() && !_isShowingDirectory) { continue; } if (NancySceneState.getViewport().convertViewportToScreen(_destRects[i]).contains(input.mousePos)) { g_nancy->_cursor->setCursorType(CursorManager::kHotspot); buttonNr = i; break; } } if (_callState != kWaiting && _callState != kRinging) { return; } if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) { g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor); if (input.input & NancyInput::kLeftMouseButtonUp) { g_nancy->_sound->loadSound(_hangUpSound); g_nancy->_sound->playSound(_hangUpSound); _callState = kHangUp; } return; } if (_callState != kWaiting) { return; } if (buttonNr != -1) { if (input.input & NancyInput::kLeftMouseButtonUp) { if (g_nancy->_sound->isSoundPlaying(_dialToneSound)) { g_nancy->_sound->stopSound(_dialToneSound); } // Handle non-digit numbers bool directorySwitch = false; bool changeDirectoryEntry = false; int dirEntryDelta = 1; if (_dialButtonID != -1 && buttonNr == _dialButtonID) { if (_isShowingDirectory) { _calledNumber = _calls[_displayedDirectory].phoneNumber; while (_calledNumber.back() == 10) { _calledNumber.pop_back(); } } _checkNumbers = true; // Dial button doesn't make sound, and doesn't get pressed down _drawSurface.blitFrom(_image, _dialHighlightSrc, _destRects[_dialButtonID]); if (_dirButtonID != -1) { _drawSurface.fillRect(_destRects[_dirButtonID], _drawSurface.getTransparentColor()); } _animIsStopped = true; return; } else if (_upDirButtonID != -1 && buttonNr == _upDirButtonID) { if (!_isShowingDirectory) { directorySwitch = true; } else { ++_displayedDirectory; changeDirectoryEntry = true; } _animIsStopped = true; } else if (_downDirButtonID != -1 && buttonNr == _downDirButtonID) { if (!_isShowingDirectory) { directorySwitch = true; } else { --_displayedDirectory; dirEntryDelta = -1; changeDirectoryEntry = true; } _animIsStopped = true; } else if (_dirButtonID != -1 && buttonNr == _dirButtonID) { if (!_isShowingDirectory) { directorySwitch = true; } _animIsStopped = true; } else { if (_isShowingDirectory || !_calledNumber.size()) { _isShowingDirectory = false; _displayedDirectory = 0; _drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor()); } _calledNumber.push_back(buttonNr); _checkNumbers = _dialAutomatically; _animIsStopped = true; if (_calledNumber.size() > 11) { _calledNumber.clear(); if (_hasDisplay) { _drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor()); } else if (_isNewPhone) { NancySceneState.getTextbox().clear(); } _checkNumbers = false; } if (_isNewPhone && _calledNumber.size()) { Common::String numberString; for (uint j = 0; j < _calledNumber.size(); ++j) { numberString += '0' + _calledNumber[j]; } if (_hasDisplay) { _font->drawString(&_drawSurface, numberString, _displayDest.left + 19, _displayDest.top + 21 - _font->getFontHeight(), _displayDest.width() - 20, 0); } else { NancySceneState.getTextbox().clear(); NancySceneState.getTextbox().addTextLine(numberString); } } } if (directorySwitch) { // Handle switch to directory mode _isShowingDirectory = true; changeDirectoryEntry = true; _calledNumber.clear(); } if (changeDirectoryEntry) { int start = _displayedDirectory; do { if (_displayedDirectory >= (int)_calls.size()) { _displayedDirectory = 0; } else if (_displayedDirectory < 0) { _displayedDirectory = _calls.size() - 1; } if (_calls[_displayedDirectory].directoryDisplayCondition == kEvNoEvent) { break; } if (NancySceneState.getEventFlag(_calls[_displayedDirectory].directoryDisplayCondition, g_nancy->_true)) { break; } _displayedDirectory += dirEntryDelta; } while (_displayedDirectory != start); } _genericButtonSound.name = _buttonSoundNames[buttonNr]; g_nancy->_sound->loadSound(_genericButtonSound); g_nancy->_sound->playSound(_genericButtonSound); _drawSurface.blitFrom(_image, _srcRects[buttonNr], _destRects[buttonNr]); _needsRedraw = true; _displayAnimEnd = 0; _displayAnimFrame = 0; _buttonLastPushed = buttonNr; _callState = kButtonPress; } } } } // End of namespace Action } // End of namespace Nancy