/* 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. * */ #include "titanic/pet_control/pet_conversations.h" #include "titanic/pet_control/pet_control.h" #include "titanic/debugger.h" #include "titanic/game_manager.h" #include "titanic/titanic.h" namespace Titanic { CPetConversations::CPetConversations() : CPetSection(), _logChanged(false), _field418(0), _npcNum(-1), _rect1(22, 352, 598, 478) { Rect logRect(85, 18, 513, 87); logRect.translate(20, 350); _log.setBounds(logRect); _log.resize(50); _log.setHasBorder(false); _log.setColor(getColor(2)); _log.setup(); _log.addLine("Welcome to your PET v1.0a"); Rect inputRect(85, 95, 513, 135); inputRect.translate(20, 350); _textInput.setBounds(inputRect); _textInput.setHasBorder(false); _textInput.resize(2); _textInput.setMaxCharsPerLine(74); _textInput.setColor(getColor(0)); _textInput.setup(); _npcLevels[0] = _npcLevels[1] = _npcLevels[2] = 0; } bool CPetConversations::setup(CPetControl *petControl) { if (petControl && setupControl(petControl)) return reset(); return false; } bool CPetConversations::reset() { _dials[0].setup(MODE_UNSELECTED, "3PetDial1", _petControl); _dials[1].setup(MODE_UNSELECTED, "3PetDial2", _petControl); _dials[2].setup(MODE_UNSELECTED, "3PetDial3", _petControl); _dialBackground.reset("PetDialBack", _petControl); _scrollUp.reset("PetScrollUp", _petControl); _scrollDown.reset("PetScrollDown", _petControl); _doorBot.reset("PetCallDoorOut", _petControl, MODE_UNSELECTED); _doorBot.reset("PetCallDoorIn", _petControl, MODE_SELECTED); _bellBot.reset("PetCallBellOut", _petControl, MODE_UNSELECTED); _bellBot.reset("PetCallBellIn", _petControl, MODE_SELECTED); _indent.reset("PetSmallCharacterIndent", _petControl); _splitter.reset("PetSplitter", _petControl); _npcIcons[0].setup(MODE_UNSELECTED, "3PetSmlDoorbot", _petControl); _npcIcons[1].setup(MODE_UNSELECTED, "3PetSmlDeskbot", _petControl); _npcIcons[2].setup(MODE_UNSELECTED, "3PetSmlLiftbot", _petControl); _npcIcons[3].setup(MODE_UNSELECTED, "3PetSmlParrot", _petControl); _npcIcons[4].setup(MODE_UNSELECTED, "3PetSmlBarbot", _petControl); _npcIcons[5].setup(MODE_UNSELECTED, "3PetSmlChatterbot", _petControl); _npcIcons[6].setup(MODE_UNSELECTED, "3PetSmlBellbot", _petControl); _npcIcons[7].setup(MODE_UNSELECTED, "3PetSmlMaitreD", _petControl); _npcIcons[8].setup(MODE_UNSELECTED, "3PetSmlSuccubus", _petControl); if (_petControl->getPassengerClass() == 1) { uint col = getColor(0); _textInput.setColor(col); _textInput.setLineColor(0, col); // Replace the log colors with new 1st class ones uint colors1[5], colors2[5]; copyColors(2, colors1); copyColors(1, colors2); _log.remapColors(5, colors1, colors2); _log.setColor(getColor(2)); } // WORKAROUND: After loading, mark log as changed so the // current NPC portrait to display gets recalculated _logChanged = true; return true; } void CPetConversations::draw(CScreenManager *screenManager) { _dialBackground.draw(screenManager); _splitter.draw(screenManager); _dials[0].draw(screenManager); _dials[1].draw(screenManager); _dials[2].draw(screenManager); _indent.draw(screenManager); _doorBot.draw(screenManager); _bellBot.draw(screenManager); _scrollUp.draw(screenManager); _scrollDown.draw(screenManager); _log.draw(screenManager); _textInput.draw(screenManager); if (_logChanged) { int endIndex = _log.displayEndIndex(); if (endIndex >= 0) { int npcNum = _log.getNPCNum(1, endIndex); if (npcNum > 0 && npcNum < 10) _npcNum = npcNum - 1; } _logChanged = false; } if (_npcNum >= 0) _npcIcons[_npcNum].draw(screenManager); } Rect CPetConversations::getBounds() const { Rect rect = _dials[0].getBounds(); rect.combine(_dials[1].getBounds()); rect.combine(_dials[2].getBounds()); return rect; } bool CPetConversations::isValid(CPetControl *petControl) { return setupControl(petControl); } bool CPetConversations::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { if (_scrollDown.MouseButtonDownMsg(msg->_mousePos)) { scrollDown(); return true; } else if (_scrollUp.MouseButtonDownMsg(msg->_mousePos)) { scrollUp(); return true; } return _doorBot.MouseButtonDownMsg(msg->_mousePos) || _bellBot.MouseButtonDownMsg(msg->_mousePos); } bool CPetConversations::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { if (_scrollUp.MouseButtonUpMsg(msg->_mousePos) || _scrollDown.MouseButtonUpMsg(msg->_mousePos)) return true; if (_doorBot.MouseButtonUpMsg(msg->_mousePos)) { switch (canSummonBot("DoorBot")) { case SUMMON_CANT: _log.addLine(g_vm->_strings[CANT_SUMMON_DOORBOT], getColor(1)); break; case SUMMON_CAN: summonBot("DoorBot"); return true; default: break; } // Scroll to the bottom of the log scrollToBottom(); return true; } if (_bellBot.MouseButtonUpMsg(msg->_mousePos)) { switch (canSummonBot("BellBot")) { case SUMMON_CANT: _log.addLine(g_vm->_strings[CANT_SUMMON_BELLBOT], getColor(1)); break; case SUMMON_CAN: summonBot("BellBot"); return true; default: break; } // Scroll to the bottom of the log scrollToBottom(); return true; } return false; } bool CPetConversations::MouseDoubleClickMsg(CMouseDoubleClickMsg *msg) { return _scrollDown.MouseDoubleClickMsg(msg->_mousePos) || _scrollUp.MouseDoubleClickMsg(msg->_mousePos); } bool CPetConversations::MouseWheelMsg(CMouseWheelMsg *msg) { if (msg->_wheelUp) scrollUp(); else scrollDown(); return true; } bool CPetConversations::KeyCharMsg(CKeyCharMsg *msg) { Common::KeyState keyState; keyState.ascii = msg->_key; return handleKey(keyState); } bool CPetConversations::VirtualKeyCharMsg(CVirtualKeyCharMsg *msg) { return handleKey(msg->_keyState); } void CPetConversations::displayMessage(const CString &msg) { _log.addLine(msg, getColor(1)); scrollToBottom(); } void CPetConversations::load(SimpleFile *file, int param) { _textInput.load(file, param); _log.load(file, param); for (int idx = 0; idx < TOTAL_DIALS; ++idx) _npcLevels[idx] = file->readNumber(); } void CPetConversations::postLoad() { reset(); } void CPetConversations::save(SimpleFile *file, int indent) { _textInput.save(file, indent); _log.save(file, indent); for (int idx = 0; idx < TOTAL_DIALS; ++idx) file->writeNumberLine(_npcLevels[idx], indent); } void CPetConversations::enter(PetArea oldArea) { resetDials(); if (_petControl && _petControl->_activeNPC) // Start a timer for the NPC startNPCTimer(); // Show the text cursor _textInput.showCursor(-2); } void CPetConversations::leave() { _textInput.hideCursor(); stopNPCTimer(); } void CPetConversations::timerExpired(int val) { if (val != 1) { CPetSection::timerExpired(val); } else { CString name = _field418 ? _npcName : getActiveNPCName(); for (int idx = 0; idx < TOTAL_DIALS; ++idx) { if (!_dials[idx].hasActiveMovie()) updateDial(idx, name); } } } void CPetConversations::displayNPCName(CGameObject *npc) { const Strings &strings = g_vm->_strings; if (npc) { displayMessage(CString()); CString msg = strings[TALKING_TO]; CString name = npc->getName(); int id = 1; if (name.containsIgnoreCase("Doorbot")) { msg += strings[DOORBOT_NAME]; } else if (name.containsIgnoreCase("Deskbot")) { id = 2; msg += strings[DESKBOT_NAME]; } else if (name.containsIgnoreCase("LiftBot")) { id = 3; msg += strings[LIFTBOT_NAME]; } else if (name.containsIgnoreCase("Parrot")) { id = 4; msg += strings[PARROT_NAME]; } else if (name.containsIgnoreCase("BarBot")) { id = 5; msg += strings[BARBOT_NAME]; } else if (name.containsIgnoreCase("ChatterBot")) { id = 6; msg += strings[CHATTERBOT_NAME]; } else if (name.containsIgnoreCase("BellBot")) { id = 7; msg += strings[BELLBOT_NAME]; } else if (name.containsIgnoreCase("Maitre")) { id = 8; msg += strings[MAITRED_NAME]; } else if (name.containsIgnoreCase("Succubus") || name.containsIgnoreCase("Sub")) { id = 9; msg += strings[SUCCUBUS_NAME]; } else { msg += strings[UNKNOWN_NAME]; } _log.setNPC(1, id); displayMessage(msg); } } void CPetConversations::setNPC(const CString &name) { _field418 = 0; resetDials(name); startNPCTimer(); } void CPetConversations::resetNPC() { stopNPCTimer(); resetDials("0"); } void CPetConversations::showCursor() { _textInput.showCursor(-2); } void CPetConversations::hideCursor() { _textInput.hideCursor(); } bool CPetConversations::setupControl(CPetControl *petControl) { if (petControl) { _petControl = petControl; _dialBackground.setBounds(Rect(0, 0, 21, 130)); _dialBackground.translate(20, 350); const Rect rect1(0, 0, 22, 36); _dials[0].setBounds(rect1); _dials[0].translate(20, 359); _dials[1].setBounds(rect1); _dials[1].translate(20, 397); _dials[2].setBounds(rect1); _dials[2].translate(20, 434); const Rect rect2(0, 0, 11, 24); _scrollUp.setBounds(rect2); _scrollUp.translate(87, 374); _scrollDown.setBounds(rect2); _scrollDown.translate(87, 421); const Rect rect3(0, 0, 39, 39); _doorBot.setBounds(rect3); _doorBot.translate(546, 372); _bellBot.setBounds(rect3); _bellBot.translate(546, 418); _indent.setBounds(Rect(0, 0, 37, 70)); _indent.translate(46, 374); _splitter.setBounds(Rect(0, 0, 435, 3)); _splitter.translate(102, 441); const Rect rect4(0, 0, 33, 66); for (int idx = 0; idx < 9; ++idx) { _npcIcons[idx].setBounds(rect4); _npcIcons[idx].translate(48, 376); } } return true; } void CPetConversations::scrollUp() { _log.scrollUp(CScreenManager::_screenManagerPtr); if (_petControl) _petControl->makeDirty(); _logChanged = true; } void CPetConversations::scrollDown() { _log.scrollDown(CScreenManager::_screenManagerPtr); if (_petControl) _petControl->makeDirty(); _logChanged = true; } void CPetConversations::scrollUpPage() { _log.scrollUpPage(CScreenManager::_screenManagerPtr); if (_petControl) _petControl->makeDirty(); _logChanged = true; } void CPetConversations::scrollDownPage() { _log.scrollDownPage(CScreenManager::_screenManagerPtr); if (_petControl) _petControl->makeDirty(); _logChanged = true; } void CPetConversations::scrollToTop() { _log.scrollToTop(CScreenManager::_screenManagerPtr); if (_petControl) _petControl->makeDirty(); _logChanged = true; } void CPetConversations::scrollToBottom() { _log.scrollToBottom(CScreenManager::_screenManagerPtr); if (_petControl) _petControl->makeDirty(); _logChanged = true; } int CPetConversations::canSummonBot(const CString &name) { return _petControl ? _petControl->canSummonBot(name) : SUMMON_CANT; } void CPetConversations::summonBot(const CString &name) { if (_petControl) { if (_petControl->getPassengerClass() >= UNCHECKED) { _petControl->displayMessage(AT_LEAST_3RD_CLASS_FOR_HELP); } else { _petControl->summonBot(name, 0); } } } void CPetConversations::startNPCTimer() { _petControl->startPetTimer(1, 1000, 1000, this); } void CPetConversations::stopNPCTimer() { _petControl->stopPetTimer(1); } TTnpcScript *CPetConversations::getNPCScript(const CString &name) const { if (name.empty() || !_petControl) return nullptr; CGameManager *gameManager = _petControl->getGameManager(); if (!gameManager) return nullptr; CTrueTalkManager *trueTalk = gameManager->getTalkManager(); if (!trueTalk) return nullptr; return trueTalk->getTalker(name); } bool CPetConversations::handleKey(const Common::KeyState &keyState) { switch (keyState.keycode) { case Common::KEYCODE_PAGEUP: case Common::KEYCODE_KP9: scrollUpPage(); return true; case Common::KEYCODE_PAGEDOWN: case Common::KEYCODE_KP3: scrollDownPage(); return true; case Common::KEYCODE_HOME: case Common::KEYCODE_KP7: scrollToTop(); return true; case Common::KEYCODE_END: case Common::KEYCODE_KP1: scrollToBottom(); return true; default: if (keyState.ascii > 0 && keyState.ascii <= 127 && keyState.ascii != Common::KEYCODE_TAB) { if (_textInput.handleKey(keyState.ascii)) // Text line finished, so process line textLineEntered(_textInput.getText()); return true; } break; } return false; } void CPetConversations::textLineEntered(const CString &textLine) { if (textLine.empty() || !_petControl) return; if (_petControl->_activeNPC) { _log.addLine("- " + textLine, getColor(0)); CTextInputMsg inputMsg(textLine, ""); inputMsg.execute(_petControl->_activeNPC); if (!inputMsg._response.empty()) _log.addLine(inputMsg._response); } else { _log.addLine(g_vm->_strings[NO_ONE_TO_TALK_TO], getColor(1)); } // Clear input line and scroll log down to end to show response _textInput.setup(); scrollToBottom(); } void CPetConversations::setActiveNPC(const CString &name) { _npcName = name; _field418 = 1; resetDials(); startNPCTimer(); } void CPetConversations::updateDial(uint dialNum, const CString &npcName) { TTnpcScript *script = getNPCScript(npcName); uint newLevel = getDialLevel(dialNum, script); npcDialChange(dialNum, _npcLevels[dialNum], newLevel); _npcLevels[dialNum] = newLevel; } uint CPetConversations::getDialLevel(uint dialNum, TTnpcScript *script, bool flag) { if (!script) return 0; else return MAX(script->getDialLevel(dialNum, flag), 15); } void CPetConversations::npcDialChange(uint dialNum, uint oldLevel, uint newLevel) { const uint ascending[2] = { 0, 21 }; const uint descending[2] = { 43, 22 }; assert(oldLevel <= 100 && newLevel <= 100); if (newLevel != oldLevel) { debugC(DEBUG_DETAILED, kDebugScripts, "Dial %d change from %d to %d", dialNum, oldLevel, newLevel); uint src = ascending[0], dest = ascending[1]; if (newLevel < oldLevel) { src = descending[0]; dest = descending[1]; } uint val1 = (oldLevel * dest) + (100 - oldLevel) * src; uint startFrame = val1 / 100; uint val2 = (newLevel * dest) + (100 - newLevel) * src; uint endFrame = val2 / 100; if (startFrame != endFrame) _dials[dialNum].playMovie(startFrame, endFrame); } } void CPetConversations::resetDials() { resetDials(getActiveNPCName()); } void CPetConversations::resetDials(const CString &name) { TTnpcScript *script = getNPCScript(name); for (int idx = 0; idx < TOTAL_DIALS; ++idx) { uint oldLevel = _npcLevels[idx]; uint newLevel = getDialLevel(idx, script); npcDialChange(idx, oldLevel, newLevel); _npcLevels[idx] = newLevel; } } void CPetConversations::resetDials0() { stopNPCTimer(); resetDials("0"); } void CPetConversations::addLine(const CString &line) { _log.addLine(line); scrollToBottom(); } } // End of namespace Titanic