/* 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 "bladerunner/dialogue_menu.h" #include "bladerunner/bladerunner.h" #include "bladerunner/font.h" #include "bladerunner/game_constants.h" #include "bladerunner/mouse.h" #include "bladerunner/savefile.h" #include "bladerunner/settings.h" #include "bladerunner/shape.h" #include "bladerunner/text_resource.h" #include "common/debug.h" #include "common/rect.h" #include "common/util.h" namespace BladeRunner { DialogueMenu::DialogueMenu(BladeRunnerEngine *vm) { _vm = vm; reset(); _textResource = new TextResource(_vm); _shapes = new Shapes(_vm); _screenX = 0; _screenY = 0; _maxItemWidth = 0; _fadeInItemIndex = 0; } DialogueMenu::~DialogueMenu() { delete _textResource; delete _shapes; } bool DialogueMenu::loadResources() { bool r = _textResource->open("DLGMENU"); if (!r) { error("Failed to load dialogue menu text"); } r = _shapes->load("DIALOG.SHP"); if (!r) { error("Failed to load dialogue menu shapes"); } return r; } bool DialogueMenu::show() { int x, y; _vm->_mouse->getXY(&x, &y); return showAt(x, y); } bool DialogueMenu::showAt(int x, int y) { if (_isVisible) { return false; } _isVisible = true; _selectedItemIndex = 0; _centerX = x; _centerY = y; calculatePosition(x, y); return true; } bool DialogueMenu::hide() { _waitingForInput = false; if (!_isVisible) { return false; } _isVisible = false; return true; } bool DialogueMenu::clearList() { _selectedItemIndex = -1; _listSize = 0; return true; } bool DialogueMenu::addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly) { if (_listSize >= kMaxItems) { return false; } if (getAnswerIndex(answer) != -1) { return false; } #if BLADERUNNER_ORIGINAL_BUGS // Original uses incorrect spelling for entry id 1020: DRAGONFLY JEWERLY const Common::String &text = _textResource->getText(answer); #else // fix spelling or entry id 1020 to DRAGONFLY JEWELRY in English version const char *answerTextCP = _textResource->getText(answer); if (_vm->_language == Common::EN_ANY && answer == 1020 && strcmp(answerTextCP, "DRAGONFLY JEWERLY") == 0) { answerTextCP = "DRAGONFLY JEWELRY"; } const Common::String &text = answerTextCP; #endif // BLADERUNNER_ORIGINAL_BUGS if (text.empty() || text.size() >= 50) { return false; } int index = _listSize++; _items[index].text = text; _items[index].answerValue = answer; _items[index].colorIntensity = 0; _items[index].isDone = done; _items[index].priorityPolite = priorityPolite; _items[index].priorityNormal = priorityNormal; _items[index].prioritySurly = prioritySurly; // CHECK(madmoose): BLADE.EXE calls this needlessly // calculatePosition(); return true; } /** * Aux function - used in cut content mode to re-use some NeverRepeatOnceSelected dialogue options for different characters */ bool DialogueMenu::clearNeverRepeatWasSelectedFlag(int answer) { int foundIndex = -1; for (int i = 0; i != _neverRepeatListSize; ++i) { if (answer == _neverRepeatValues[i]) { foundIndex = i; break; } } if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) { _neverRepeatWasSelected[foundIndex] = false; return true; } return false; } bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly) { int foundIndex = -1; for (int i = 0; i != _neverRepeatListSize; ++i) { if (answer == _neverRepeatValues[i]) { foundIndex = i; break; } } if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) { return true; } if (foundIndex == -1) { _neverRepeatValues[_neverRepeatListSize] = answer; _neverRepeatWasSelected[_neverRepeatListSize] = false; ++_neverRepeatListSize; assert(_neverRepeatListSize <= 100); } return addToList(answer, false, priorityPolite, priorityNormal, prioritySurly); } bool DialogueMenu::removeFromList(int answer) { int index = getAnswerIndex(answer); if (index < 0) { return false; } if (index < _listSize - 1) { for (int i = index; i < _listSize; ++i) { _items[index] = _items[index + 1]; } } --_listSize; calculatePosition(); return true; } int DialogueMenu::queryInput() { if (!_isVisible || _listSize == 0) { return -1; } int answer = -1; if (_listSize == 1) { _selectedItemIndex = 0; answer = _items[_selectedItemIndex].answerValue; } else if (_listSize == 2) { #if BLADERUNNER_ORIGINAL_BUGS if (_items[0].isDone) { _selectedItemIndex = 1; answer = _items[_selectedItemIndex].answerValue; } else if (_items[1].isDone) { _selectedItemIndex = 0; answer = _items[_selectedItemIndex].answerValue; } #else // In User Choice mode, avoid auto-select of last option // In this mode, player should still have agency to skip the last (non- "DONE") // question instead of automatically asking it because the other remaining option is "DONE" if (_vm->_settings->getPlayerAgenda() != kPlayerAgendaUserChoice) { if (_items[0].isDone) { _selectedItemIndex = 1; answer = _items[_selectedItemIndex].answerValue; } else if (_items[1].isDone) { _selectedItemIndex = 0; answer = _items[_selectedItemIndex].answerValue; } } #endif // BLADERUNNER_ORIGINAL_BUGS } if (answer == -1) { int agenda = _vm->_settings->getPlayerAgenda(); if (agenda == kPlayerAgendaUserChoice) { _waitingForInput = true; do { while (!_vm->playerHasControl()) { _vm->playerGainsControl(); } while (_vm->_mouse->isDisabled()) { _vm->_mouse->enable(); } _vm->gameTick(); } while (_vm->_gameIsRunning && _waitingForInput); } else if (agenda == kPlayerAgendaErratic) { int tries = 0; bool searching = true; int i; do { i = _vm->_rnd.getRandomNumber(_listSize - 1); if (!_items[i].isDone) { searching = false; } else if (++tries > 1000) { searching = false; i = 0; } } while (searching); _selectedItemIndex = i; } else { int priority = -1; for (int i = 0; i < _listSize; ++i) { int priorityCompare = -1; if (agenda == kPlayerAgendaPolite) { priorityCompare = _items[i].priorityPolite; } else if (agenda == kPlayerAgendaNormal) { priorityCompare = _items[i].priorityNormal; } else if (agenda == kPlayerAgendaSurly) { priorityCompare = _items[i].prioritySurly; } if (priority < priorityCompare) { priority = priorityCompare; _selectedItemIndex = i; } } } } answer = _items[_selectedItemIndex].answerValue; for (int i = 0; i != _neverRepeatListSize; ++i) { if (answer == _neverRepeatValues[i]) { _neverRepeatWasSelected[i] = true; break; } } // debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text.c_str()); return answer; } int DialogueMenu::listSize() const { return _listSize; } bool DialogueMenu::isVisible() const { return _isVisible; } bool DialogueMenu::isOpen() const { return _isVisible || _waitingForInput; } void DialogueMenu::tick(int x, int y) { if (!_isVisible || _listSize == 0) { return; } int line = (y - (_screenY + kBorderSize)) / kLineHeight; line = CLIP(line, 0, _listSize - 1); _selectedItemIndex = line; } void DialogueMenu::draw(Graphics::Surface &s) { if (!_isVisible || _listSize == 0) { return; } int fadeInItemIndex = _fadeInItemIndex; if (fadeInItemIndex < listSize()) { ++_fadeInItemIndex; } for (int i = 0; i != _listSize; ++i) { int targetColorIntensity = 0; if (i == _selectedItemIndex) { targetColorIntensity = 31; } else { targetColorIntensity = 16; } if (i > fadeInItemIndex) { targetColorIntensity = 0; } if (_items[i].colorIntensity < targetColorIntensity) { _items[i].colorIntensity += 4; if (_items[i].colorIntensity > targetColorIntensity) { _items[i].colorIntensity = targetColorIntensity; } } else if (_items[i].colorIntensity > targetColorIntensity) { _items[i].colorIntensity -= 2; if (_items[i].colorIntensity < targetColorIntensity) { _items[i].colorIntensity = targetColorIntensity; } } } const int x1 = _screenX; const int y1 = _screenY; const int x2 = _screenX + kBorderSize + _maxItemWidth; const int y2 = _screenY + kBorderSize + _listSize * kLineHeight; darkenRect(s, x1 + 8, y1 + 8, x2 + 2, y2 + 2); int x = x1 + kBorderSize; int y = y1 + kBorderSize; Common::Point mouse = _vm->getMousePos(); if (mouse.x >= x && mouse.x < x2) { s.vLine(mouse.x, y1 + 8, y2 + 2, s.format.RGBToColor(64, 64, 64)); } if (mouse.y >= y && mouse.y < y2) { s.hLine(x1 + 8, mouse.y, x2 + 2, s.format.RGBToColor(64, 64, 64)); } _shapes->get(0)->draw(s, x1, y1); _shapes->get(3)->draw(s, x2, y1); _shapes->get(2)->draw(s, x1, y2); _shapes->get(5)->draw(s, x2, y2); for (int i = 0; i != _listSize; ++i) { _shapes->get(1)->draw(s, x1, y); _shapes->get(4)->draw(s, x2, y); uint32 color = s.format.RGBToColor((_items[i].colorIntensity / 2) * (256 / 32), (_items[i].colorIntensity / 2) * (256 / 32), _items[i].colorIntensity * (256 / 32)); _vm->_mainFont->drawString(&s, _items[i].text, x, y, s.w, color); y += kLineHeight; } for (; x != x2; ++x) { _shapes->get(6)->draw(s, x, y1); _shapes->get(7)->draw(s, x, y2); } } int DialogueMenu::getAnswerIndex(int answer) const { for (int i = 0; i != _listSize; ++i) { if (_items[i].answerValue == answer) { return i; } } return -1; } const char *DialogueMenu::getText(int id) const { return _textResource->getText((uint32)id); } void DialogueMenu::calculatePosition(int unusedX, int unusedY) { _maxItemWidth = 0; for (int i = 0; i != _listSize; ++i) { _maxItemWidth = MAX(_maxItemWidth, _vm->_mainFont->getStringWidth(_items[i].text)); } _maxItemWidth += 2; int w = kBorderSize + _shapes->get(4)->getWidth() + _maxItemWidth; int h = kBorderSize + _shapes->get(7)->getHeight() + kLineHeight * _listSize; _screenX = _centerX - w / 2; _screenY = _centerY - h / 2; _screenX = CLIP(_screenX, 0, 640 - w); _screenY = CLIP(_screenY, 0, 480 - h); _fadeInItemIndex = 0; } void DialogueMenu::mouseUp() { _waitingForInput = false; } bool DialogueMenu::waitingForInput() const { return _waitingForInput; } void DialogueMenu::save(SaveFileWriteStream &f) { f.writeBool(_isVisible); f.writeBool(_waitingForInput); f.writeInt(_selectedItemIndex); f.writeInt(_listSize); f.writeInt(_neverRepeatListSize); for (int i = 0; i < 100; ++i) { f.writeInt(_neverRepeatValues[i]); } for (int i = 0; i < 100; ++i) { f.writeBool(_neverRepeatWasSelected[i]); } for (int i = 0; i < 10; ++i) { f.writeStringSz(_items[i].text, 50); f.writeInt(_items[i].answerValue); f.writeInt(_items[i].colorIntensity); f.writeInt(_items[i].priorityPolite); f.writeInt(_items[i].priorityNormal); f.writeInt(_items[i].prioritySurly); f.writeInt(_items[i].isDone); } } void DialogueMenu::load(SaveFileReadStream &f) { _isVisible = f.readBool(); _waitingForInput = f.readBool(); _selectedItemIndex = f.readInt(); _listSize = f.readInt(); #if 0 /* fix for duplicated non-repeated entries in the save game */ f.readInt(); _neverRepeatListSize = 0; int answer[100]; bool selected[100]; for (int i = 0; i < 100; ++i) { _neverRepeatValues[i] = -1; answer[i] = f.readInt(); } for (int i = 0; i < 100; ++i) { _neverRepeatWasSelected[i] = false; selected[i] = f.readBool(); } for (int i = 0; i < 100; ++i) { int found = false; bool value = false; for (int j = 0; j < 100; ++j) { if (_neverRepeatValues[j] == answer[i]) { found = true; } if (answer[j] == answer[i]) { value |= selected[j]; } } if (!found) { _neverRepeatValues[_neverRepeatListSize] = answer[i]; _neverRepeatWasSelected[_neverRepeatListSize] = value; ++_neverRepeatListSize; } } #else _neverRepeatListSize = f.readInt(); for (int i = 0; i < 100; ++i) { _neverRepeatValues[i] = f.readInt(); } for (int i = 0; i < 100; ++i) { _neverRepeatWasSelected[i] = f.readBool(); } #endif for (int i = 0; i < 10; ++i) { _items[i].text = f.readStringSz(50); _items[i].answerValue = f.readInt(); _items[i].colorIntensity = f.readInt(); _items[i].priorityPolite = f.readInt(); _items[i].priorityNormal = f.readInt(); _items[i].prioritySurly = f.readInt(); _items[i].isDone = f.readInt(); } } void DialogueMenu::clear() { _isVisible = false; _waitingForInput = false; _selectedItemIndex = 0; _listSize = 0; for (int i = 0; i != kMaxItems; ++i) { _items[i].text.clear(); _items[i].answerValue = -1; _items[i].isDone = 0; _items[i].priorityPolite = -1; _items[i].priorityNormal = -1; _items[i].prioritySurly = -1; _items[i].colorIntensity = 0; } _neverRepeatListSize = 0; for (int i = 0; i != kMaxRepeatHistory; ++i) { _neverRepeatValues[i] = -1; _neverRepeatWasSelected[i] = false; } _centerX = 0; _centerY = 0; } void DialogueMenu::reset() { clear(); _textResource = nullptr; } void DialogueMenu::darkenRect(Graphics::Surface &s, int x1, int y1, int x2, int y2) { x1 = MAX(x1, 0); y1 = MAX(y1, 0); x2 = MIN(x2, 640); y2 = MIN(y2, 480); if (x1 < x2 && y1 < y2) { for (int y = y1; y != y2; ++y) { for (int x = x1; x != x2; ++x) { void *p = s.getBasePtr(CLIP(x, 0, s.w - 1), CLIP(y, 0, s.h - 1)); uint8 r, g, b; s.format.colorToRGB(READ_UINT32(p), r, g, b); r /= 4; g /= 4; b /= 4; drawPixel(s, p, s.format.RGBToColor(r, g, b)); } } } } } // End of namespace BladeRunner