From a4c799238841e4e9ae63985b503a669449521cd6 Mon Sep 17 00:00:00 2001 From: Thomas Fach-Pedersen Date: Tue, 22 Aug 2017 18:57:50 +0200 Subject: [PATCH] BLADERUNNER: Add Dialogue Menu --- engines/bladerunner/bladerunner.cpp | 29 ++- engines/bladerunner/bladerunner.h | 2 + engines/bladerunner/dialogue_menu.cpp | 359 ++++++++++++++++++++++++++ engines/bladerunner/dialogue_menu.h | 103 ++++++++ engines/bladerunner/font.cpp | 2 +- engines/bladerunner/module.mk | 1 + engines/bladerunner/mouse.cpp | 5 + engines/bladerunner/mouse.h | 2 + engines/bladerunner/script/script.cpp | 39 ++- engines/bladerunner/script/script.h | 2 +- engines/bladerunner/shape.h | 3 + 11 files changed, 518 insertions(+), 29 deletions(-) create mode 100644 engines/bladerunner/dialogue_menu.cpp create mode 100644 engines/bladerunner/dialogue_menu.h diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp index 76c0c3f7ea5..3622130b45d 100644 --- a/engines/bladerunner/bladerunner.cpp +++ b/engines/bladerunner/bladerunner.cpp @@ -30,6 +30,7 @@ #include "bladerunner/chapters.h" #include "bladerunner/combat.h" #include "bladerunner/crimes_database.h" +#include "bladerunner/dialogue_menu.h" #include "bladerunner/font.h" #include "bladerunner/gameflags.h" #include "bladerunner/gameinfo.h" @@ -280,7 +281,9 @@ bool BladeRunnerEngine::startup(bool hasSavegames) { if (!_textOptions->open("OPTIONS")) return false; - // TODO: Dialogue Menu (DLGMENU.TRE) + _dialogueMenu = new DialogueMenu(this); + if (!_dialogueMenu->loadText("DLGMENU")) + return false; _suspectsDatabase = new SuspectsDatabase(this, _gameInfo->getSuspectsDatabaseSize()); @@ -602,8 +605,12 @@ void BladeRunnerEngine::gameTick() { if (_scene->didPlayerWalkIn()) { _sceneScript->PlayerWalkedIn(); } - // TODO: Gun range announcements + bool inDialogueMenu = _dialogueMenu->isVisible(); + if (!inDialogueMenu) { + // TODO: actors combat-tick + } + // TODO: Gun range announcements _zbuffer->clean(); _ambientSounds->tick(); @@ -621,8 +628,9 @@ void BladeRunnerEngine::gameTick() { // TODO: Render overlays - //if (!dialogueMenu) + if (!inDialogueMenu) { actorsUpdate(); + } if (_settings->getNewScene() == -1 || _sceneScript->IsInsideScript() || _aiScripts->IsInsideScript()) { _sliceRenderer->setView(*_view); @@ -645,9 +653,13 @@ void BladeRunnerEngine::gameTick() { _itemPickup->tick(); _itemPickup->draw(); - // TODO: Draw dialogue menu - Common::Point p = getMousePos(); + + if (_dialogueMenu->isVisible()) { + _dialogueMenu->tick(p.x, p.y); + _dialogueMenu->draw(); + } + _mouse->tick(p.x, p.y); _mouse->draw(_surfaceGame, p.x, p.y); @@ -833,6 +845,13 @@ void BladeRunnerEngine::handleMouseAction(int x, int y, bool buttonLeft, bool bu return; } + if (_dialogueMenu->waitingForInput()) { + if (buttonLeft && !buttonDown) { + _dialogueMenu->mouseUp(); + } + return; + } + Vector3 mousePosition = _mouse->getXYZ(x, y); int isClickable; diff --git a/engines/bladerunner/bladerunner.h b/engines/bladerunner/bladerunner.h index fd654ac5fb1..405952595a4 100644 --- a/engines/bladerunner/bladerunner.h +++ b/engines/bladerunner/bladerunner.h @@ -54,6 +54,7 @@ class AudioSpeech; class Chapters; class CrimesDatabase; class Combat; +class DialogueMenu; class Font; class GameFlags; class GameInfo; @@ -92,6 +93,7 @@ public: Chapters *_chapters; CrimesDatabase *_crimesDatabase; Combat *_combat; + DialogueMenu *_dialogueMenu; GameFlags *_gameFlags; GameInfo *_gameInfo; ItemPickup *_itemPickup; diff --git a/engines/bladerunner/dialogue_menu.cpp b/engines/bladerunner/dialogue_menu.cpp new file mode 100644 index 00000000000..2ef137f9d4e --- /dev/null +++ b/engines/bladerunner/dialogue_menu.cpp @@ -0,0 +1,359 @@ +/* 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/mouse.h" +#include "bladerunner/shape.h" +#include "bladerunner/text_resource.h" + +#include "common/debug.h" +#include "common/util.h" + +#define LINE_HEIGHT 9 +#define BORDER_SIZE 10 + +namespace BladeRunner { + +DialogueMenu::DialogueMenu(BladeRunnerEngine *vm) + : _vm(vm) +{ + reset(); + _textResource = new TextResource(_vm); + _shapes.reserve(8); + for (int i = 0; i != 8; ++i) { + _shapes.push_back(Shape(_vm)); + bool r = _shapes[i].readFromContainer("DIALOG.SHP", i); + assert(r); + (void)r; + } +} + +DialogueMenu::~DialogueMenu() { + delete _textResource; +} + +bool DialogueMenu::loadText(const char *name) { + bool r = _textResource->open(name); + if (!r) { + error("Failed to load dialogue menu text"); + } + return r; +} + +bool DialogueMenu::show() { + int x, y; + + _vm->_mouse->getXY(&x, &y); + + return showAt(x, y); +} + +bool DialogueMenu::showAt(int x, int y) { + debug("DialogueMenu::showAt %d %d %d", _isVisible, x, y); + if (_isVisible) { + return false; + } + + _isVisible = true; + _selectedItemIndex = 0; + _centerX = x; + _centerY = y; + calculatePosition(x, y); + + return true; +} + +bool DialogueMenu::hide() { + _waitingForInput = 0; + if (!_isVisible) { + return false; + } + + _isVisible = false; + return true; +} + +bool DialogueMenu::clearList() { + _selectedItemIndex = -1; + _listSize = 0; + return true; +} + +bool DialogueMenu::addToList(int answer, int a3, int a4, int a5, int a6) { + if (_listSize >= 10) + return false; + if (getAnswerIndex(answer) != -1) + return false; + + const char *text = _textResource->getText(answer); + if (!text || strlen(text) >= 50) + return false; + + int index = _listSize++; + strcpy(_items[index].text, text); + _items[index].answerValue = answer; + _items[index].field_36 = 0; + _items[index].field_46 = a3; + _items[index].field_3A = a4; + _items[index].field_3E = a5; + _items[index].field_42 = a6; + + // CHECK(madmoose): BLADE.EXE calls this needlessly + // calculatePosition(); + + return true; +} + +bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int a3, int a4, int a5) { + for (int i = 0; i != _neverRepeatListSize; ++i) { + if (answer == _neverRepeatValues[i] && _neverRepeatWasSelected[i]) { + return true; + } + } + + _neverRepeatValues[_neverRepeatListSize] = answer; + _neverRepeatWasSelected[_neverRepeatListSize] = false; + ++_neverRepeatListSize; + return addToList(answer, 0, a3, a4, a5); +} + +int DialogueMenu::queryInput() { + if (!_isVisible || _listSize == 0) + return -1; + + int answer = -1; + if (_listSize == 1) { + _selectedItemIndex = 0; + answer = _items[0].answerValue; + } else if (_listSize == 2) { + if (_items[0].field_46) { + _selectedItemIndex = 1; + answer = _items[0].answerValue; + } else if (_items[0].field_46) { + _selectedItemIndex = 0; + answer = _items[1].answerValue; + } + } + + if (answer == -1) { + int agenda = 4; //_vm->_settings.getPlayerAgenda(); + if (agenda == 4) { + _waitingForInput = true; + do { + // if (!_vm->_gameRunning) + // break; + + while (!_vm->playerHasControl()) { + _vm->playerGainsControl(); + } + + while (_vm->_mouse->isDisabled()) { + _vm->_mouse->enable(); + } + + _vm->gameTick(); + } while (_waitingForInput); + + } else if (agenda == 3) { + int tries = 0; + bool searching = true; + int i; + do { + i = _vm->_rnd.getRandomNumber(_listSize - 1); + if (!_items[i].field_46) { + searching = false; + } else if (++tries > 1000) { + searching = false; + i = 0; + } + } while (searching); + _selectedItemIndex = i; + } else { + error("unimplemented..."); + } + } + + answer = _items[_selectedItemIndex].answerValue; + for (int i = 0; i != _neverRepeatListSize; ++i) { + if (answer == _neverRepeatValues[i]) { + _neverRepeatWasSelected[i] = true; + break; + } + } + + if (_selectedItemIndex >= 0) { + debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text); + } + + return answer; +} + +int DialogueMenu::listSize() { + return _listSize; +} + +bool DialogueMenu::isVisible() { + return _isVisible; +} + +bool DialogueMenu::isOpen() { + return _isVisible || _waitingForInput; +} + +void DialogueMenu::tick(int x, int y) { + if (!_isVisible || _listSize == 0) { + return; + } + + int line = (y - (_screenY + BORDER_SIZE)) / LINE_HEIGHT; + line = CLIP(line, 0, _listSize - 1); + + _selectedItemIndex = line; +} + +void DialogueMenu::draw() { + if (!_isVisible || _listSize == 0) + return; + + for (int i = 0; i != _listSize; ++i) { + if (i == _selectedItemIndex) { + _items[i].field_36 = 31; + } else { + _items[i].field_36 = 16; + } + + // TODO(madmoose): There's more logic here + } + + const int x1 = _screenX; + const int y1 = _screenY; + const int x2 = _screenX + BORDER_SIZE + _maxItemWidth; + const int y2 = _screenY + BORDER_SIZE + _listSize * LINE_HEIGHT; + + Graphics::Surface &s = _vm->_surfaceGame; + + darkenRect(s, x1 + 8, y1 + 8, x2 + 2, y2 + 2); + + _shapes[0].draw(s, x1, y1); + _shapes[3].draw(s, x2, y1); + _shapes[2].draw(s, x1, y2); + _shapes[5].draw(s, x2, y2); + + int x = x1 + BORDER_SIZE; + int y = y1 + BORDER_SIZE; + for (int i = 0; i != _listSize; ++i) { + _shapes[1].draw(s, x1, y); + _shapes[4].draw(s, x2, y); + uint16 color = ((_items[i].field_36 >> 1) << 10) | ((_items[i].field_36 >> 1) << 6) | _items[i].field_36; + _vm->_mainFont->drawColor(_items[i].text, s, x, y, color); + y += LINE_HEIGHT; + } + for (; x != x2; ++x) { + _shapes[6].draw(s, x, y1); + _shapes[7].draw(s, x, y2); + } +} + +int DialogueMenu::getAnswerIndex(int answer) { + for (int i = 0; i != _listSize; ++i) { + if (_items[i].answerValue == answer) { + return i; + } + } + + return -1; +} + +const char *DialogueMenu::getText(int id) { + 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->getTextWidth(_items[i].text)); + } + _maxItemWidth += 2; + + int w = BORDER_SIZE + _shapes[4].getWidth() + _maxItemWidth; + int h = BORDER_SIZE + _shapes[7].getHeight() + LINE_HEIGHT * _listSize; + + _screenX = _centerX - w / 2; + _screenY = _centerY - h / 2; + + _screenX = CLIP(_screenX, 0, 640 - w); + _screenY = CLIP(_screenY, 0, 480 - h); + + debug("calculatePosition: %d %d %d %d %d", _screenX, _screenY, _centerX, _centerY, _maxItemWidth); +} + +void DialogueMenu::mouseUp() { + _waitingForInput = false; +} + +bool DialogueMenu::waitingForInput() { + return _waitingForInput; +} + +void DialogueMenu::clear() { + _isVisible = false; + _waitingForInput = false; + _selectedItemIndex = 0; + _listSize = 0; + for (int i = 0; i != 10; ++i) { + _items[0].text[0] = '\0'; + _items[0].answerValue = -1; + _items[0].field_36 = 0; + _items[0].field_42 = -1; + _items[0].field_3A = -1; + _items[0].field_3E = -1; + _items[0].field_46 = 0; + } + _neverRepeatListSize = 0; + for (int i = 0; i != 100; ++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) { + for (int y = y1; y != y2; ++y) { + for (int x = x1; x != x2; ++x) { + // TODO(madmoose) + uint16 *p = (uint16*)s.getBasePtr(x, y); + *p = 0; + } + } +} + +} // End of namespace BladeRunner diff --git a/engines/bladerunner/dialogue_menu.h b/engines/bladerunner/dialogue_menu.h new file mode 100644 index 00000000000..7a6b99e967a --- /dev/null +++ b/engines/bladerunner/dialogue_menu.h @@ -0,0 +1,103 @@ +/* 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. + * + */ + +#ifndef BLADERUNNER_DIALOGUE_MENU_H +#define BLADERUNNER_DIALOGUE_MENU_H + +#include "bladerunner/shape.h" + +#include "common/array.h" + +#include "graphics/surface.h" + +namespace BladeRunner { + +class BladeRunnerEngine; +class TextResource; + +struct DialogueItem { + char text[50]; + int answerValue; + int field_36; + int field_3A; + int field_3E; + int field_42; + int field_46; +}; + +class DialogueMenu { + BladeRunnerEngine *_vm; + + TextResource *_textResource; + Common::Array _shapes; + bool _isVisible; + bool _waitingForInput; + int _selectedItemIndex; + int _listSize; + + // These track whether a dialogue option + // has previously been selected + int _neverRepeatListSize; + int _neverRepeatValues[100]; + bool _neverRepeatWasSelected[100]; + + int _centerX; + int _centerY; + int _screenX; + int _screenY; + int _maxItemWidth; + DialogueItem _items[10]; + +public: + DialogueMenu(BladeRunnerEngine *vm); + ~DialogueMenu(); + + bool loadText(const char *name); + + bool show(); + bool showAt(int x, int y); + bool hide(); + bool clearList(); + bool addToList(int answer, int a3, int a4, int a5, int a6); + bool addToListNeverRepeatOnceSelected(int answer, int a3, int a4, int a5); + int queryInput(); + int listSize(); + bool isVisible(); + bool isOpen(); + void tick(int x, int y); + void draw(); + int getAnswerIndex(int answer); + const char *getText(int id); + void calculatePosition(int unusedX = 0, int unusedY = 0); + + void mouseUp(); + bool waitingForInput(); + + void clear(); + void reset(); + + static void darkenRect(Graphics::Surface &s, int x1, int y1, int x2, int y2); +}; + +} // End of namespace BladeRunner + +#endif diff --git a/engines/bladerunner/font.cpp b/engines/bladerunner/font.cpp index fd8b9f204d2..8ab5cfab134 100644 --- a/engines/bladerunner/font.cpp +++ b/engines/bladerunner/font.cpp @@ -130,7 +130,7 @@ int Font::getTextWidth(const Common::String &text) { return 0; } while (*character != 0) { - totalWidth = _spacing1 + _characters[*character + 1]._width; + totalWidth += _spacing1 + _characters[*character + 1]._width; character++; } return totalWidth - _spacing1; diff --git a/engines/bladerunner/module.mk b/engines/bladerunner/module.mk index 9a43ef4457f..3e496ff3b56 100644 --- a/engines/bladerunner/module.mk +++ b/engines/bladerunner/module.mk @@ -20,6 +20,7 @@ MODULE_OBJS = \ decompress_lcw.o \ decompress_lzo.o \ detection.o \ + dialogue_menu.o \ fog.o \ font.o \ gameflags.o \ diff --git a/engines/bladerunner/mouse.cpp b/engines/bladerunner/mouse.cpp index be114dc1bbc..4c5a9b91a08 100644 --- a/engines/bladerunner/mouse.cpp +++ b/engines/bladerunner/mouse.cpp @@ -146,6 +146,11 @@ void Mouse::setCursor(int cursor) { } } +void Mouse::getXY(int *x, int *y) { + *x = _x; + *y = _y; +} + void Mouse::disable() { ++_disabledCounter; } diff --git a/engines/bladerunner/mouse.h b/engines/bladerunner/mouse.h index 8fb5f324a20..214089fed6a 100644 --- a/engines/bladerunner/mouse.h +++ b/engines/bladerunner/mouse.h @@ -52,6 +52,8 @@ public: void setCursor(int cursor); + void getXY(int *x, int *y); + void disable(); void enable(); bool isDisabled(); diff --git a/engines/bladerunner/script/script.cpp b/engines/bladerunner/script/script.cpp index ba78a42b4cd..32528c9728a 100644 --- a/engines/bladerunner/script/script.cpp +++ b/engines/bladerunner/script/script.cpp @@ -32,6 +32,7 @@ #include "bladerunner/audio_speech.h" #include "bladerunner/crimes_database.h" #include "bladerunner/combat.h" +#include "bladerunner/dialogue_menu.h" #include "bladerunner/gameflags.h" #include "bladerunner/gameinfo.h" #include "bladerunner/items.h" @@ -896,47 +897,44 @@ void ScriptBase::Setup_Scene_Information(float actorX, float actorY, float actor } bool ScriptBase::Dialogue_Menu_Appear(int x, int y) { - //TODO - warning("Dialogue_Menu_Appear(%d, %d)", x, y); + if (!_vm->_dialogueMenu->isVisible()) { + return _vm->_dialogueMenu->show(); + } return false; } bool ScriptBase::Dialogue_Menu_Disappear() { - //TODO - warning("Dialogue_Menu_Disappear()"); + if (_vm->_dialogueMenu->isVisible()) { + return _vm->_dialogueMenu->hide(); + } return false; } bool ScriptBase::Dialogue_Menu_Clear_List() { - //TODO - warning("Dialogue_Menu_Clear_List()"); + _vm->_dialogueMenu->clearList(); return false; } bool ScriptBase::Dialogue_Menu_Add_To_List(int answer) { - //TODO - warning("Dialogue_Menu_Add_To_List(%d)", answer); + _vm->_dialogueMenu->addToList(answer, 0, 5, 5, 5); return false; } bool ScriptBase::Dialogue_Menu_Add_DONE_To_List(int answerValue) { - //TODO - warning("Dialogue_Menu_Add_DONE_To_List(%d)", answerValue); + _vm->_dialogueMenu->addToList(answerValue, 1, 0, 0, 0); return false; } -// Dialogue_Menu_Add_To_List_Never_Repeat_Once_Selected +bool ScriptBase::Dialogue_Menu_Add_To_List_Never_Repeat_Once_Selected(int answer) { + return _vm->_dialogueMenu->addToListNeverRepeatOnceSelected(answer, 5, 5, 5); +} bool ScriptBase::DM_Add_To_List(int answer, int a2, int a3, int a4) { - //TODO - warning("DM_Add_To_List(%d, %d, %d, %d)", answer, a2, a3, a4); - return false; + return _vm->_dialogueMenu->addToList(answer, 0, a2, a3, a4); } bool ScriptBase::DM_Add_To_List_Never_Repeat_Once_Selected(int answer, int a2, int a3, int a4) { - //TODO - warning("DM_Add_To_List_Never_Repeat_Once_Selected(%d, %d, %d, %d)", answer, a2, a3, a4); - return false; + return _vm->_dialogueMenu->addToListNeverRepeatOnceSelected(answer, a2, a3, a4); } void ScriptBase::Dialogue_Menu_Remove_From_List(int answer) { @@ -946,14 +944,11 @@ void ScriptBase::Dialogue_Menu_Remove_From_List(int answer) { int ScriptBase::Dialogue_Menu_Query_Input() { //TODO - warning("Dialogue_Menu_Query_Input()"); - return 0; + return _vm->_dialogueMenu->queryInput(); } int ScriptBase::Dialogue_Menu_Query_List_Size() { - //TODO - warning("Dialogue_Menu_Query_List_Size()"); - return 0; + return _vm->_dialogueMenu->listSize(); } void ScriptBase::Scene_Exit_Add_2D_Exit(int index, int left, int top, int right, int down, int type) { diff --git a/engines/bladerunner/script/script.h b/engines/bladerunner/script/script.h index 94112a9e42b..93c955dedd5 100644 --- a/engines/bladerunner/script/script.h +++ b/engines/bladerunner/script/script.h @@ -587,7 +587,7 @@ protected: bool Dialogue_Menu_Clear_List(); bool Dialogue_Menu_Add_To_List(int answer); bool Dialogue_Menu_Add_DONE_To_List(int answer); - // Dialogue_Menu_Add_To_List_Never_Repeat_Once_Selected + bool Dialogue_Menu_Add_To_List_Never_Repeat_Once_Selected(int answer); bool DM_Add_To_List(int answer, int a2, int a3, int a4); bool DM_Add_To_List_Never_Repeat_Once_Selected(int answer, int a2, int a3, int a4); void Dialogue_Menu_Remove_From_List(int answer); diff --git a/engines/bladerunner/shape.h b/engines/bladerunner/shape.h index e2e77b1a760..cd06e79a249 100644 --- a/engines/bladerunner/shape.h +++ b/engines/bladerunner/shape.h @@ -47,6 +47,9 @@ public: bool readFromContainer(const Common::String &container, int index); void draw(Graphics::Surface &surface, int x, int y); + + int getWidth() const { return _width; } + int getHeight() const { return _height; } }; } // End of namespace BladeRunner