1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "bladerunner/dialogue_menu.h"
24
25 #include "bladerunner/bladerunner.h"
26 #include "bladerunner/font.h"
27 #include "bladerunner/game_constants.h"
28 #include "bladerunner/mouse.h"
29 #include "bladerunner/savefile.h"
30 #include "bladerunner/settings.h"
31 #include "bladerunner/shape.h"
32 #include "bladerunner/text_resource.h"
33
34 #include "common/debug.h"
35 #include "common/rect.h"
36 #include "common/util.h"
37
38 namespace BladeRunner {
39
DialogueMenu(BladeRunnerEngine * vm)40 DialogueMenu::DialogueMenu(BladeRunnerEngine *vm) {
41 _vm = vm;
42 reset();
43 _textResource = new TextResource(_vm);
44 _shapes = new Shapes(_vm);
45
46 _screenX = 0;
47 _screenY = 0;
48 _maxItemWidth = 0;
49 _fadeInItemIndex = 0;
50 }
51
~DialogueMenu()52 DialogueMenu::~DialogueMenu() {
53 delete _textResource;
54 delete _shapes;
55 }
56
loadResources()57 bool DialogueMenu::loadResources() {
58 bool r = _textResource->open("DLGMENU");
59 if (!r) {
60 error("Failed to load dialogue menu text");
61 }
62 r = _shapes->load("DIALOG.SHP");
63 if (!r) {
64 error("Failed to load dialogue menu shapes");
65 }
66 return r;
67 }
68
show()69 bool DialogueMenu::show() {
70 int x, y;
71
72 _vm->_mouse->getXY(&x, &y);
73
74 return showAt(x, y);
75 }
76
showAt(int x,int y)77 bool DialogueMenu::showAt(int x, int y) {
78 if (_isVisible) {
79 return false;
80 }
81
82 _isVisible = true;
83 _selectedItemIndex = 0;
84 _centerX = x;
85 _centerY = y;
86 calculatePosition(x, y);
87
88 return true;
89 }
90
hide()91 bool DialogueMenu::hide() {
92 _waitingForInput = false;
93 if (!_isVisible) {
94 return false;
95 }
96
97 _isVisible = false;
98 return true;
99 }
100
clearList()101 bool DialogueMenu::clearList() {
102 _selectedItemIndex = -1;
103 _listSize = 0;
104 return true;
105 }
106
addToList(int answer,bool done,int priorityPolite,int priorityNormal,int prioritySurly)107 bool DialogueMenu::addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly) {
108 if (_listSize >= kMaxItems) {
109 return false;
110 }
111 if (getAnswerIndex(answer) != -1) {
112 return false;
113 }
114
115 #if BLADERUNNER_ORIGINAL_BUGS
116 // Original uses incorrect spelling for entry id 1020: DRAGONFLY JEWERLY
117 const Common::String &text = _textResource->getText(answer);
118 #else
119 // fix spelling or entry id 1020 to DRAGONFLY JEWELRY in English version
120 const char *answerTextCP = _textResource->getText(answer);
121 if (_vm->_language == Common::EN_ANY && answer == 1020 && strcmp(answerTextCP, "DRAGONFLY JEWERLY") == 0) {
122 answerTextCP = "DRAGONFLY JEWELRY";
123 }
124 const Common::String &text = answerTextCP;
125 #endif // BLADERUNNER_ORIGINAL_BUGS
126 if (text.empty() || text.size() >= 50) {
127 return false;
128 }
129
130 int index = _listSize++;
131 _items[index].text = text;
132 _items[index].answerValue = answer;
133 _items[index].colorIntensity = 0;
134 _items[index].isDone = done;
135 _items[index].priorityPolite = priorityPolite;
136 _items[index].priorityNormal = priorityNormal;
137 _items[index].prioritySurly = prioritySurly;
138
139 // CHECK(madmoose): BLADE.EXE calls this needlessly
140 // calculatePosition();
141
142 return true;
143 }
144
145 /**
146 * Aux function - used in cut content mode to re-use some NeverRepeatOnceSelected dialogue options for different characters
147 */
clearNeverRepeatWasSelectedFlag(int answer)148 bool DialogueMenu::clearNeverRepeatWasSelectedFlag(int answer) {
149 int foundIndex = -1;
150 for (int i = 0; i != _neverRepeatListSize; ++i) {
151 if (answer == _neverRepeatValues[i]) {
152 foundIndex = i;
153 break;
154 }
155 }
156
157 if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) {
158 _neverRepeatWasSelected[foundIndex] = false;
159 return true;
160 }
161 return false;
162 }
163
addToListNeverRepeatOnceSelected(int answer,int priorityPolite,int priorityNormal,int prioritySurly)164 bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly) {
165 int foundIndex = -1;
166 for (int i = 0; i != _neverRepeatListSize; ++i) {
167 if (answer == _neverRepeatValues[i]) {
168 foundIndex = i;
169 break;
170 }
171 }
172
173 if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) {
174 return true;
175 }
176
177 if (foundIndex == -1) {
178 _neverRepeatValues[_neverRepeatListSize] = answer;
179 _neverRepeatWasSelected[_neverRepeatListSize] = false;
180 ++_neverRepeatListSize;
181
182 assert(_neverRepeatListSize <= 100);
183 }
184
185 return addToList(answer, false, priorityPolite, priorityNormal, prioritySurly);
186 }
187
removeFromList(int answer)188 bool DialogueMenu::removeFromList(int answer) {
189 int index = getAnswerIndex(answer);
190 if (index < 0) {
191 return false;
192 }
193 if (index < _listSize - 1) {
194 for (int i = index; i < _listSize; ++i) {
195 _items[index] = _items[index + 1];
196 }
197 }
198 --_listSize;
199
200 calculatePosition();
201 return true;
202 }
203
queryInput()204 int DialogueMenu::queryInput() {
205 if (!_isVisible || _listSize == 0) {
206 return -1;
207 }
208
209 int answer = -1;
210 if (_listSize == 1) {
211 _selectedItemIndex = 0;
212 answer = _items[_selectedItemIndex].answerValue;
213 } else if (_listSize == 2) {
214 #if BLADERUNNER_ORIGINAL_BUGS
215 if (_items[0].isDone) {
216 _selectedItemIndex = 1;
217 answer = _items[_selectedItemIndex].answerValue;
218 } else if (_items[1].isDone) {
219 _selectedItemIndex = 0;
220 answer = _items[_selectedItemIndex].answerValue;
221 }
222 #else
223 // In User Choice mode, avoid auto-select of last option
224 // In this mode, player should still have agency to skip the last (non- "DONE")
225 // question instead of automatically asking it because the other remaining option is "DONE"
226 if (_vm->_settings->getPlayerAgenda() != kPlayerAgendaUserChoice) {
227 if (_items[0].isDone) {
228 _selectedItemIndex = 1;
229 answer = _items[_selectedItemIndex].answerValue;
230 } else if (_items[1].isDone) {
231 _selectedItemIndex = 0;
232 answer = _items[_selectedItemIndex].answerValue;
233 }
234 }
235 #endif // BLADERUNNER_ORIGINAL_BUGS
236 }
237
238 if (answer == -1) {
239 int agenda = _vm->_settings->getPlayerAgenda();
240 if (agenda == kPlayerAgendaUserChoice) {
241 _waitingForInput = true;
242 do {
243 while (!_vm->playerHasControl()) {
244 _vm->playerGainsControl();
245 }
246
247 while (_vm->_mouse->isDisabled()) {
248 _vm->_mouse->enable();
249 }
250
251 _vm->gameTick();
252 } while (_vm->_gameIsRunning && _waitingForInput);
253 } else if (agenda == kPlayerAgendaErratic) {
254 int tries = 0;
255 bool searching = true;
256 int i;
257 do {
258 i = _vm->_rnd.getRandomNumber(_listSize - 1);
259 if (!_items[i].isDone) {
260 searching = false;
261 } else if (++tries > 1000) {
262 searching = false;
263 i = 0;
264 }
265 } while (searching);
266 _selectedItemIndex = i;
267 } else {
268 int priority = -1;
269 for (int i = 0; i < _listSize; ++i) {
270 int priorityCompare = -1;
271 if (agenda == kPlayerAgendaPolite) {
272 priorityCompare = _items[i].priorityPolite;
273 } else if (agenda == kPlayerAgendaNormal) {
274 priorityCompare = _items[i].priorityNormal;
275 } else if (agenda == kPlayerAgendaSurly) {
276 priorityCompare = _items[i].prioritySurly;
277 }
278 if (priority < priorityCompare) {
279 priority = priorityCompare;
280 _selectedItemIndex = i;
281 }
282 }
283 }
284 }
285
286 answer = _items[_selectedItemIndex].answerValue;
287 for (int i = 0; i != _neverRepeatListSize; ++i) {
288 if (answer == _neverRepeatValues[i]) {
289 _neverRepeatWasSelected[i] = true;
290 break;
291 }
292 }
293
294 // debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text.c_str());
295
296 return answer;
297 }
298
listSize() const299 int DialogueMenu::listSize() const {
300 return _listSize;
301 }
302
isVisible() const303 bool DialogueMenu::isVisible() const {
304 return _isVisible;
305 }
306
isOpen() const307 bool DialogueMenu::isOpen() const {
308 return _isVisible || _waitingForInput;
309 }
310
tick(int x,int y)311 void DialogueMenu::tick(int x, int y) {
312 if (!_isVisible || _listSize == 0) {
313 return;
314 }
315
316 int line = (y - (_screenY + kBorderSize)) / kLineHeight;
317 line = CLIP(line, 0, _listSize - 1);
318
319 _selectedItemIndex = line;
320 }
321
draw(Graphics::Surface & s)322 void DialogueMenu::draw(Graphics::Surface &s) {
323 if (!_isVisible || _listSize == 0) {
324 return;
325 }
326
327 int fadeInItemIndex = _fadeInItemIndex;
328 if (fadeInItemIndex < listSize()) {
329 ++_fadeInItemIndex;
330 }
331
332 for (int i = 0; i != _listSize; ++i) {
333 int targetColorIntensity = 0;
334 if (i == _selectedItemIndex) {
335 targetColorIntensity = 31;
336 } else {
337 targetColorIntensity = 16;
338 }
339 if (i > fadeInItemIndex) {
340 targetColorIntensity = 0;
341 }
342
343 if (_items[i].colorIntensity < targetColorIntensity) {
344 _items[i].colorIntensity += 4;
345 if (_items[i].colorIntensity > targetColorIntensity) {
346 _items[i].colorIntensity = targetColorIntensity;
347 }
348 } else if (_items[i].colorIntensity > targetColorIntensity) {
349 _items[i].colorIntensity -= 2;
350 if (_items[i].colorIntensity < targetColorIntensity) {
351 _items[i].colorIntensity = targetColorIntensity;
352 }
353 }
354 }
355
356 const int x1 = _screenX;
357 const int y1 = _screenY;
358 const int x2 = _screenX + kBorderSize + _maxItemWidth;
359 const int y2 = _screenY + kBorderSize + _listSize * kLineHeight;
360
361 darkenRect(s, x1 + 8, y1 + 8, x2 + 2, y2 + 2);
362
363 int x = x1 + kBorderSize;
364 int y = y1 + kBorderSize;
365
366 Common::Point mouse = _vm->getMousePos();
367 if (mouse.x >= x && mouse.x < x2) {
368 s.vLine(mouse.x, y1 + 8, y2 + 2, s.format.RGBToColor(64, 64, 64));
369 }
370 if (mouse.y >= y && mouse.y < y2) {
371 s.hLine(x1 + 8, mouse.y, x2 + 2, s.format.RGBToColor(64, 64, 64));
372 }
373
374 _shapes->get(0)->draw(s, x1, y1);
375 _shapes->get(3)->draw(s, x2, y1);
376 _shapes->get(2)->draw(s, x1, y2);
377 _shapes->get(5)->draw(s, x2, y2);
378
379 for (int i = 0; i != _listSize; ++i) {
380 _shapes->get(1)->draw(s, x1, y);
381 _shapes->get(4)->draw(s, x2, y);
382 uint32 color = s.format.RGBToColor((_items[i].colorIntensity / 2) * (256 / 32), (_items[i].colorIntensity / 2) * (256 / 32), _items[i].colorIntensity * (256 / 32));
383 _vm->_mainFont->drawString(&s, _items[i].text, x, y, s.w, color);
384 y += kLineHeight;
385 }
386 for (; x != x2; ++x) {
387 _shapes->get(6)->draw(s, x, y1);
388 _shapes->get(7)->draw(s, x, y2);
389 }
390 }
391
getAnswerIndex(int answer) const392 int DialogueMenu::getAnswerIndex(int answer) const {
393 for (int i = 0; i != _listSize; ++i) {
394 if (_items[i].answerValue == answer) {
395 return i;
396 }
397 }
398
399 return -1;
400 }
401
getText(int id) const402 const char *DialogueMenu::getText(int id) const {
403 return _textResource->getText((uint32)id);
404 }
405
calculatePosition(int unusedX,int unusedY)406 void DialogueMenu::calculatePosition(int unusedX, int unusedY) {
407 _maxItemWidth = 0;
408 for (int i = 0; i != _listSize; ++i) {
409 _maxItemWidth = MAX(_maxItemWidth, _vm->_mainFont->getStringWidth(_items[i].text));
410 }
411 _maxItemWidth += 2;
412
413 int w = kBorderSize + _shapes->get(4)->getWidth() + _maxItemWidth;
414 int h = kBorderSize + _shapes->get(7)->getHeight() + kLineHeight * _listSize;
415
416 _screenX = _centerX - w / 2;
417 _screenY = _centerY - h / 2;
418
419 _screenX = CLIP(_screenX, 0, 640 - w);
420 _screenY = CLIP(_screenY, 0, 480 - h);
421
422 _fadeInItemIndex = 0;
423 }
424
mouseUp()425 void DialogueMenu::mouseUp() {
426 _waitingForInput = false;
427 }
428
waitingForInput() const429 bool DialogueMenu::waitingForInput() const {
430 return _waitingForInput;
431 }
432
save(SaveFileWriteStream & f)433 void DialogueMenu::save(SaveFileWriteStream &f) {
434 f.writeBool(_isVisible);
435 f.writeBool(_waitingForInput);
436 f.writeInt(_selectedItemIndex);
437 f.writeInt(_listSize);
438
439 f.writeInt(_neverRepeatListSize);
440 for (int i = 0; i < 100; ++i) {
441 f.writeInt(_neverRepeatValues[i]);
442 }
443 for (int i = 0; i < 100; ++i) {
444 f.writeBool(_neverRepeatWasSelected[i]);
445 }
446 for (int i = 0; i < 10; ++i) {
447 f.writeStringSz(_items[i].text, 50);
448 f.writeInt(_items[i].answerValue);
449 f.writeInt(_items[i].colorIntensity);
450 f.writeInt(_items[i].priorityPolite);
451 f.writeInt(_items[i].priorityNormal);
452 f.writeInt(_items[i].prioritySurly);
453 f.writeInt(_items[i].isDone);
454 }
455 }
456
load(SaveFileReadStream & f)457 void DialogueMenu::load(SaveFileReadStream &f) {
458 _isVisible = f.readBool();
459 _waitingForInput = f.readBool();
460 _selectedItemIndex = f.readInt();
461 _listSize = f.readInt();
462
463 #if 0
464 /* fix for duplicated non-repeated entries in the save game */
465 f.readInt();
466 _neverRepeatListSize = 0;
467 int answer[100];
468 bool selected[100];
469 for (int i = 0; i < 100; ++i) {
470 _neverRepeatValues[i] = -1;
471 answer[i] = f.readInt();
472 }
473 for (int i = 0; i < 100; ++i) {
474 _neverRepeatWasSelected[i] = false;
475 selected[i] = f.readBool();
476 }
477 for (int i = 0; i < 100; ++i) {
478 int found = false;
479 bool value = false;
480
481 for (int j = 0; j < 100; ++j) {
482 if (_neverRepeatValues[j] == answer[i]) {
483 found = true;
484 }
485 if (answer[j] == answer[i]) {
486 value |= selected[j];
487 }
488 }
489
490 if (!found) {
491 _neverRepeatValues[_neverRepeatListSize] = answer[i];
492 _neverRepeatWasSelected[_neverRepeatListSize] = value;
493 ++_neverRepeatListSize;
494 }
495 }
496 #else
497 _neverRepeatListSize = f.readInt();
498 for (int i = 0; i < 100; ++i) {
499 _neverRepeatValues[i] = f.readInt();
500 }
501 for (int i = 0; i < 100; ++i) {
502 _neverRepeatWasSelected[i] = f.readBool();
503 }
504 #endif
505
506 for (int i = 0; i < 10; ++i) {
507 _items[i].text = f.readStringSz(50);
508 _items[i].answerValue = f.readInt();
509 _items[i].colorIntensity = f.readInt();
510 _items[i].priorityPolite = f.readInt();
511 _items[i].priorityNormal = f.readInt();
512 _items[i].prioritySurly = f.readInt();
513 _items[i].isDone = f.readInt();
514 }
515 }
516
clear()517 void DialogueMenu::clear() {
518 _isVisible = false;
519 _waitingForInput = false;
520 _selectedItemIndex = 0;
521 _listSize = 0;
522 for (int i = 0; i != kMaxItems; ++i) {
523 _items[i].text.clear();
524 _items[i].answerValue = -1;
525 _items[i].isDone = 0;
526 _items[i].priorityPolite = -1;
527 _items[i].priorityNormal = -1;
528 _items[i].prioritySurly = -1;
529 _items[i].colorIntensity = 0;
530 }
531 _neverRepeatListSize = 0;
532 for (int i = 0; i != kMaxRepeatHistory; ++i) {
533 _neverRepeatValues[i] = -1;
534 _neverRepeatWasSelected[i] = false;
535 }
536 _centerX = 0;
537 _centerY = 0;
538 }
539
reset()540 void DialogueMenu::reset() {
541 clear();
542 _textResource = nullptr;
543 }
544
darkenRect(Graphics::Surface & s,int x1,int y1,int x2,int y2)545 void DialogueMenu::darkenRect(Graphics::Surface &s, int x1, int y1, int x2, int y2) {
546 x1 = MAX(x1, 0);
547 y1 = MAX(y1, 0);
548 x2 = MIN(x2, 640);
549 y2 = MIN(y2, 480);
550
551 if (x1 < x2 && y1 < y2) {
552 for (int y = y1; y != y2; ++y) {
553 for (int x = x1; x != x2; ++x) {
554 void *p = s.getBasePtr(CLIP(x, 0, s.w - 1), CLIP(y, 0, s.h - 1));
555 uint8 r, g, b;
556 s.format.colorToRGB(READ_UINT32(p), r, g, b);
557 r /= 4;
558 g /= 4;
559 b /= 4;
560 drawPixel(s, p, s.format.RGBToColor(r, g, b));
561 }
562 }
563 }
564 }
565
566 } // End of namespace BladeRunner
567