scummvm/engines/bladerunner/dialogue_menu.cpp
Thanasis Antoniou cd076b26ae BLADERUNNER: Using preincrement and pre-decrease where possible
In case the compiler won't optimize such cases
2020-02-24 21:24:06 +02:00

567 lines
14 KiB
C++

/* 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