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 "sherlock/tattoo/tattoo_user_interface.h"
24 #include "sherlock/tattoo/tattoo_fixed_text.h"
25 #include "sherlock/tattoo/tattoo_journal.h"
26 #include "sherlock/tattoo/tattoo_scene.h"
27 #include "sherlock/tattoo/tattoo.h"
28
29 namespace Sherlock {
30
31 namespace Tattoo {
32
contains(const WidgetBase * item) const33 bool WidgetList::contains(const WidgetBase *item) const {
34 for (const_iterator i = begin(); i != end(); ++i) {
35 if ((*i) == item)
36 return true;
37 }
38
39 return false;
40 }
41
42 /*-------------------------------------------------------------------------*/
43
TattooUserInterface(SherlockEngine * vm)44 TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm),
45 _inventoryWidget(vm), _messageWidget(vm), _textWidget(vm), _tooltipWidget(vm),
46 _verbsWidget(vm), _creditsWidget(vm), _optionsWidget(vm), _quitWidget(vm) {
47 Common::fill(&_lookupTable[0], &_lookupTable[PALETTE_COUNT], 0);
48 Common::fill(&_lookupTable1[0], &_lookupTable1[PALETTE_COUNT], 0);
49 _scrollSize = 0;
50 _scrollSpeed = 16;
51 _drawMenu = false;
52 _bgShape = nullptr;
53 _personFound = false;
54 _lockoutTimer = 0;
55 _exitZone = -1;
56 _scriptZone = -1;
57 _arrowZone = _oldArrowZone = -1;
58 _activeObj = -1;
59 _cAnimFramePause = 0;
60 _scrollHighlight = SH_NONE;
61 _mask = _mask1 = nullptr;
62 _maskCounter = 0;
63
64 _interfaceImages = new ImageFile("intrface.vgs");
65 }
66
~TattooUserInterface()67 TattooUserInterface::~TattooUserInterface() {
68 delete _interfaceImages;
69 delete _mask;
70 delete _mask1;
71 }
72
initScrollVars()73 void TattooUserInterface::initScrollVars() {
74 Screen &screen = *_vm->_screen;
75 _scrollSize = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
76 _targetScroll = Common::Point(0, 0);
77 screen._currentScroll = Common::Point(0, 0);
78 }
79
lookAtObject()80 void TattooUserInterface::lookAtObject() {
81 Events &events = *_vm->_events;
82 People &people = *_vm->_people;
83 Scene &scene = *_vm->_scene;
84 Sound &sound = *_vm->_sound;
85 Talk &talk = *_vm->_talk;
86 Common::Point mousePos = events.mousePos();
87 Common::String desc;
88
89 _lookPos = mousePos;
90 _menuMode = LOOK_MODE;
91
92 if (_personFound) {
93 desc = people[_bgFound - 1000]._examine;
94 } else {
95 // Check if there is a Look animation
96 if (_bgShape->_lookcAnim != 0) {
97 int cAnimSpeed = _bgShape->_lookcAnim & 0xe0;
98 cAnimSpeed >>= 5;
99 ++cAnimSpeed;
100
101 _cAnimFramePause = _bgShape->_lookFrames;
102 desc = _bgShape->_examine;
103
104 int cNum = (_bgShape->_lookcAnim & 0x1f) - 1;
105 scene.startCAnim(cNum);
106 } else if (_bgShape->_lookPosition.y != 0) {
107 // Need to walk to object before looking at it
108 people[HOLMES].walkToCoords(_bgShape->_lookPosition, _bgShape->_lookPosition._facing);
109 }
110
111 if (!talk._talkToAbort) {
112 desc = _bgShape->_examine;
113
114 if (_bgShape->_lookFlag)
115 _vm->setFlags(_bgShape->_lookFlag);
116
117 // Find the Sound File to Play if there is one
118 if (!desc.hasPrefix("_")) {
119 for (uint idx = 0; idx < scene._objSoundList.size(); ++idx) {
120 // Get the object name up to the equals
121 const char *p = strchr(scene._objSoundList[idx].c_str(), '=');
122
123 // Form the name and remove any trailing spaces
124 Common::String name(scene._objSoundList[idx].c_str(), p);
125 while (name.hasSuffix(" "))
126 name.deleteLastChar();
127
128 // See if this Object Sound List entry matches the object's name
129 if (!_bgShape->_name.compareToIgnoreCase(name)) {
130 // Move forward to get the sound filename
131 while ((*p == ' ') || (*p == '='))
132 ++p;
133
134 // If it's not "NONE", play the speech File
135 Common::String soundName(p);
136 if (soundName.compareToIgnoreCase("NONE")) {
137 soundName.toLowercase();
138 if (!soundName.contains('.'))
139 soundName += ".wav";
140
141 sound.playSound(soundName, WAIT_RETURN_IMMEDIATELY);
142 }
143
144 break;
145 }
146 }
147 }
148 }
149 }
150
151 // Only show the desciption if the object has one, and if no talk file interrupted while walking to it
152 if (!talk._talkToAbort && !desc.empty()) {
153 if (_cAnimFramePause == 0)
154 printObjectDesc(desc, true);
155 else
156 // The description was already printed by an animation
157 _cAnimFramePause = 0;
158 } else if (desc.empty()) {
159 // There was no description to display, so reset back to STD_MODE
160 _menuMode = STD_MODE;
161 }
162 }
163
printObjectDesc(const Common::String & str,bool firstTime)164 void TattooUserInterface::printObjectDesc(const Common::String &str, bool firstTime) {
165 Events &events = *_vm->_events;
166 TattooScene &scene = *(TattooScene *)_vm->_scene;
167 Talk &talk = *_vm->_talk;
168
169 if (str.hasPrefix("_")) {
170 // The passed string specifies a talk file
171 _lookScriptFlag = true;
172 events.setCursor(MAGNIFY);
173 int savedSelector = _selector;
174
175 if (!_invLookFlag)
176 _windowOpen = false;
177
178 talk.talkTo(str.c_str() + 1);
179 _lookScriptFlag = false;
180
181 if (talk._talkToAbort) {
182 events.setCursor(ARROW);
183 return;
184 }
185
186 // See if we're looking at an inventory item
187 if (_invLookFlag) {
188 _selector = _oldSelector = savedSelector;
189 doInventory(0);
190 _invLookFlag = false;
191
192 } else {
193 // Nope
194 events.setCursor(ARROW);
195 _key = -1;
196 _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
197 events._pressed = events._released = events._rightReleased = false;
198 events._oldButtons = 0;
199 }
200 } else {
201 events._pressed = events._released = events._rightReleased = false;
202
203 // Show text dialog
204 _textWidget.load(str);
205 _textWidget.summonWindow();
206
207 if (firstTime)
208 _selector = _oldSelector = -1;
209
210 _drawMenu = _windowOpen = true;
211 }
212 }
213
doJournal()214 void TattooUserInterface::doJournal() {
215 TattooJournal &journal = *(TattooJournal *)_vm->_journal;
216 TattooScene &scene = *(TattooScene *)_vm->_scene;
217 Screen &screen = *_vm->_screen;
218 byte lookupTable[PALETTE_COUNT], lookupTable1[PALETTE_COUNT];
219
220 Common::copy(&_lookupTable[0], &_lookupTable[PALETTE_COUNT], &lookupTable[0]);
221 Common::copy(&_lookupTable1[0], &_lookupTable1[PALETTE_COUNT], &lookupTable1[0]);
222 _menuMode = JOURNAL_MODE;
223 journal.show();
224
225 _menuMode = STD_MODE;
226 _windowOpen = false;
227 _key = -1;
228
229 // Restore the the old screen palette and greyscale lookup table
230 screen.clear();
231 screen.setPalette(screen._cMap);
232 Common::copy(&lookupTable[0], &lookupTable[PALETTE_COUNT], &_lookupTable[0]);
233 Common::copy(&lookupTable1[0], &lookupTable1[PALETTE_COUNT], &_lookupTable1[0]);
234
235 // Restore the scene
236 screen._backBuffer1.SHblitFrom(screen._backBuffer2);
237 scene.updateBackground();
238 screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
239 }
240
reset()241 void TattooUserInterface::reset() {
242 UserInterface::reset();
243 _lookPos = Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
244 _tooltipWidget.setText("");
245 _widgets.clear();
246 _fixedWidgets.clear();
247 }
248
handleInput()249 void TattooUserInterface::handleInput() {
250 TattooEngine &vm = *(TattooEngine *)_vm;
251 Events &events = *_vm->_events;
252 TattooScene &scene = *(TattooScene *)_vm->_scene;
253 Common::Point mousePos = events.mousePos();
254
255 _keyState.keycode = Common::KEYCODE_INVALID;
256
257 // Check for credits starting
258 if (_vm->readFlags(3000) && !_creditsWidget.active())
259 _creditsWidget.initCredits();
260
261 // Check the mouse positioning
262 if (events.isCursorVisible())
263 _bgFound = scene.findBgShape(mousePos);
264 _personFound = _bgFound >= 1000;
265 _bgShape = (_bgFound != -1 && _bgFound < 1000) ? &scene._bgShapes[_bgFound] : nullptr;
266
267 if (_lockoutTimer)
268 --_lockoutTimer;
269
270 // Key handling
271 if (events.kbHit()) {
272 _keyState = events.getKey();
273
274 if (_keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog && !_lockoutTimer) {
275 vm.setFlags(-76);
276 vm.setFlags(396);
277 scene._goToScene = STARTING_GAME_SCENE;
278 } else if (_menuMode == STD_MODE) {
279 if (_keyState.keycode == Common::KEYCODE_s && vm._allowFastMode) {
280 events.toggleSpeed();
281
282 } else if (_keyState.keycode == Common::KEYCODE_l && _bgFound != -1) {
283 // Beging used for testing that Look dialogs work
284 lookAtObject();
285 }
286 }
287 }
288
289 if (!events.isCursorVisible())
290 _keyState.keycode = Common::KEYCODE_INVALID;
291
292 // If there's any active widgets/windows, let the most recently open one do event processing
293 if (!_widgets.empty())
294 _widgets.back()->handleEvents();
295 else if (!_fixedWidgets.empty())
296 _fixedWidgets.back()->handleEvents();
297
298 // Handle input depending on what mode we're in
299 switch (_menuMode) {
300 case STD_MODE:
301 doStandardControl();
302 break;
303 case LOOK_MODE:
304 doLookControl();
305 break;
306 default:
307 break;
308 }
309 }
310
drawInterface(int bufferNum)311 void TattooUserInterface::drawInterface(int bufferNum) {
312 Screen &screen = *_vm->_screen;
313
314 // Draw any active on-screen widgets
315 for (Common::List<WidgetBase *>::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i)
316 (*i)->draw();
317 for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
318 (*i)->draw();
319
320 // Handle drawing credits
321 // TODO: See if credits are only shown on a single screen. If so, _fixedWidgets could be used
322 if (_creditsWidget.active())
323 _creditsWidget.drawCredits();
324
325 // Bring the widgets to the screen
326 if (_mask != nullptr)
327 screen._flushScreen = true;
328 }
329
doBgAnimRestoreUI()330 void TattooUserInterface::doBgAnimRestoreUI() {
331 TattooScene &scene = *((TattooScene *)_vm->_scene);
332 Screen &screen = *_vm->_screen;
333
334 // If there are any on-screen widgets, then erase them
335 for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
336 (*i)->erase();
337 for (Common::List<WidgetBase *>::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i)
338 (*i)->erase();
339
340 // If there is a Text Tag being display, restore the area underneath it
341 _tooltipWidget.erase();
342
343 // If a canimation is active, restore the graphics underneath it
344 if (scene._activeCAnim.active())
345 screen.restoreBackground(scene._activeCAnim._oldBounds);
346
347 // If a canimation just ended, remove its graphics from the backbuffer
348 if (scene._activeCAnim._removeBounds.width() > 0)
349 screen.restoreBackground(scene._activeCAnim._removeBounds);
350 }
351
doScroll()352 void TattooUserInterface::doScroll() {
353 Screen &screen = *_vm->_screen;
354
355 // If we're already at the target scroll position, nothing needs to be done
356 if (_targetScroll.x == screen._currentScroll.x)
357 return;
358
359 screen._flushScreen = true;
360 if (_targetScroll.x > screen._currentScroll.x) {
361 screen._currentScroll.x += _scrollSpeed;
362 if (screen._currentScroll.x > _targetScroll.x)
363 screen._currentScroll.x = _targetScroll.x;
364 } else if (_targetScroll.x < screen._currentScroll.x) {
365 screen._currentScroll.x -= _scrollSpeed;
366 if (screen._currentScroll.x < _targetScroll.x)
367 screen._currentScroll.x = _targetScroll.x;
368 }
369
370 // Reset the default look position to the center of the new screen area
371 _lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
372 }
373
doStandardControl()374 void TattooUserInterface::doStandardControl() {
375 TattooEngine &vm = *(TattooEngine *)_vm;
376 Events &events = *_vm->_events;
377 People &people = *_vm->_people;
378 SaveManager &saves = *_vm->_saves;
379 TattooScene &scene = *(TattooScene *)_vm->_scene;
380 Talk &talk = *_vm->_talk;
381 Common::Point mousePos = events.mousePos();
382
383 // Don't do any input processing whilst the prolog is running
384 if (vm._runningProlog)
385 return;
386
387 // When the end credits are active, any press will open the ScummVM global main menu
388 if (_creditsWidget.active()) {
389 if (_keyState.keycode || events._released || events._rightReleased) {
390 vm._canLoadSave = true;
391 vm.openMainMenuDialog();
392 vm._canLoadSave = false;
393 }
394
395 return;
396 }
397
398 // Display the names of any Objects the cursor is pointing at
399 displayObjectNames();
400
401 switch (_keyState.keycode) {
402 case Common::KEYCODE_F5:
403 // Save game
404 events.warpMouse();
405 saveGame();
406 return;
407
408 case Common::KEYCODE_F7:
409 // Load game
410 events.warpMouse();
411 loadGame();
412 return;
413
414 case Common::KEYCODE_F1:
415 // Display journal
416 if (vm.readFlags(FLAG_PLAYER_IS_HOLMES)) {
417 freeMenu();
418 doJournal();
419
420 // See if we're in a Lab Table Room
421 _menuMode = (scene._labTableScene) ? LAB_MODE : STD_MODE;
422 return;
423 }
424 break;
425
426 case Common::KEYCODE_TAB:
427 case Common::KEYCODE_F3:
428 // Display inventory
429 freeMenu();
430 doInventory(3);
431 return;
432
433 case Common::KEYCODE_F4:
434 // Display options
435 events.warpMouse();
436 _optionsWidget.load();
437 return;
438
439 case Common::KEYCODE_F10:
440 // Quit menu
441 freeMenu();
442 events.warpMouse();
443 doQuitMenu();
444 return;
445
446 default:
447 break;
448 }
449
450 // See if a mouse button was released
451 if (events._released || events._rightReleased) {
452 // See if the mouse was released in an exit (Arrow) zone. Unless it's also pointing at an object
453 // within the zone, in which case the object gets precedence
454 _exitZone = -1;
455 if (_arrowZone != -1 && events._released)
456 _exitZone = _arrowZone;
457
458 // Turn any Text display off
459 if (_arrowZone == -1 || events._rightReleased)
460 freeMenu();
461
462 bool noDesc = false;
463 if (_personFound) {
464 if (people[_bgFound - 1000]._description.empty() || people[_bgFound - 1000]._description.hasPrefix(" "))
465 noDesc = true;
466 } else if (_bgFound != -1) {
467 if (_bgShape->_description.empty() || _bgShape->_description.hasPrefix(" "))
468 noDesc = true;
469 } else {
470 noDesc = true;
471 }
472
473 if (events._rightReleased) {
474 // Show the verbs menu for the highlighted object
475 _tooltipWidget.banishWindow();
476 saves.createThumbnail();
477 _verbsWidget.load(!noDesc);
478 _verbsWidget.summonWindow();
479
480 _selector = _oldSelector = -1;
481 _activeObj = _bgFound;
482 _menuMode = VERB_MODE;
483 } else if (_personFound || (_bgFound != -1 && _bgFound < 1000 && _bgShape->_aType == PERSON)) {
484 // The object found is a person (the default for people is TALK)
485 talk.initTalk(_bgFound);
486 _activeObj = -1;
487 } else if (!noDesc) {
488 // Either call the code to Look at its Examine Field or call the Exit animation
489 // if the object is an exit, specified by the first four characters of the name being "EXIT"
490 Common::String name = _personFound ? people[_bgFound - 1000]._name : _bgShape->_name;
491 if (!name.hasPrefix("EXIT")) {
492 lookAtObject();
493 } else {
494 // Run the Exit animation and set which scene to go to next
495 for (int idx = 0; idx < 6; ++idx) {
496 if (!_bgShape->_use[idx]._verb.compareToIgnoreCase("Open")) {
497 checkAction(_bgShape->_use[idx], _bgFound);
498 _activeObj = -1;
499 }
500 }
501 }
502 } else {
503 // See if there are any Script Zones where they clicked
504 if (scene.checkForZones(mousePos, _scriptZone) != 0) {
505 // Mouse click in a script zone
506 events._pressed = events._released = false;
507 } else if (scene.checkForZones(mousePos, NOWALK_ZONE) != 0) {
508 events._pressed = events._released = false;
509 } else {
510 // Walk to where the mouse was clicked
511 people[HOLMES]._walkDest = mousePos;
512 people[HOLMES].goAllTheWay();
513 }
514 }
515 }
516 }
517
doLookControl()518 void TattooUserInterface::doLookControl() {
519 Events &events = *_vm->_events;
520 TattooScene &scene = *(TattooScene *)_vm->_scene;
521
522 // See if a mouse button was released or a key pressed to close the active look dialog
523 if (events._released || events._rightReleased || _keyState.keycode) {
524 // See if we were looking at an inventory object
525 if (!_invLookFlag) {
526 // See if there is any more text to display
527 if (!_textWidget._remainingText.empty()) {
528 printObjectDesc(_textWidget._remainingText, false);
529 } else {
530 // Otherwise restore the background and go back into STD_MODE
531 freeMenu();
532 _key = -1;
533 _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
534
535 events.setCursor(ARROW);
536 events._pressed = events._released = events._rightReleased = false;
537 events._oldButtons = 0;
538 }
539 } else {
540 // We were looking at a Inventory object
541 // Erase the text window, and then redraw the inventory window
542 _textWidget.banishWindow();
543 doInventory(0);
544
545 _invLookFlag = false;
546 _key = -1;
547
548 events.setCursor(ARROW);
549 events._pressed = events._released = events._rightReleased = false;
550 events._oldButtons = 0;
551 }
552 }
553 }
554
displayObjectNames()555 void TattooUserInterface::displayObjectNames() {
556 Events &events = *_vm->_events;
557 Scene &scene = *_vm->_scene;
558 Common::Point mousePos = events.mousePos();
559 _arrowZone = -1;
560
561 if (_bgFound == -1 || scene._currentScene == OVERHEAD_MAP2) {
562 for (uint idx = 0; idx < scene._exits.size() && _arrowZone == -1; ++idx) {
563 Exit &exit = scene._exits[idx];
564 if (exit.contains(mousePos))
565 _arrowZone = idx;
566 }
567 }
568
569 _tooltipWidget.handleEvents();
570 _oldArrowZone = _arrowZone;
571 }
572
doInventory(int mode)573 void TattooUserInterface::doInventory(int mode) {
574 People &people = *_vm->_people;
575 people[HOLMES].gotoStand();
576
577 _inventoryWidget.load(mode);
578 _inventoryWidget.summonWindow();
579
580 _menuMode = INV_MODE;
581 }
582
doControls()583 void TattooUserInterface::doControls() {
584 _optionsWidget.load();
585 }
586
pickUpObject(int objNum)587 void TattooUserInterface::pickUpObject(int objNum) {
588 Inventory &inv = *_vm->_inventory;
589 Scene &scene = *_vm->_scene;
590 Talk &talk = *_vm->_talk;
591 Object &obj = scene._bgShapes[objNum];
592 bool printed = false;
593 int verbField = -1;
594
595 // Find which Verb field to use for pick up data
596 for (int idx = 0; idx < 6; ++idx) {
597 if (!scumm_stricmp(obj._use[idx]._target.c_str(), "*PICKUP"))
598 verbField = idx;
599 }
600
601 if (verbField != -1) {
602 if (obj._use[verbField]._cAnimNum)
603 scene.startCAnim(obj._use[verbField]._cAnimNum - 1);
604 }
605
606 if (!talk._talkToAbort) {
607 if (obj._type == NO_SHAPE)
608 obj._type = INVALID;
609 else
610 // Erase shape
611 obj._type = REMOVE;
612 } else {
613 return;
614 }
615
616 if (verbField != -1) {
617 for (int idx = 0; idx < 4 && !talk._talkToAbort; ++idx) {
618 if (obj.checkNameForCodes(obj._use[verbField]._names[idx])) {
619 if (!talk._talkToAbort)
620 printed = true;
621 }
622 }
623 }
624
625 if (talk._talkToAbort)
626 return;
627
628 // Add the item to the player's inventory
629 inv.putItemInInventory(obj);
630
631 if (!printed) {
632 Common::String desc = obj._description;
633 desc.setChar(tolower(desc[0]), 0);
634
635 putMessage("%s %s", FIXED(PickedUp), desc.c_str());
636 }
637
638 if (_menuMode != TALK_MODE && _menuMode != MESSAGE_MODE) {
639 _menuMode = STD_MODE;
640 _keyState.keycode = Common::KEYCODE_INVALID;
641 }
642 }
643
doQuitMenu()644 void TattooUserInterface::doQuitMenu() {
645 _quitWidget.show();
646 }
647
putMessage(const char * formatStr,...)648 void TattooUserInterface::putMessage(const char *formatStr, ...) {
649 // Create the string to display
650 va_list args;
651 va_start(args, formatStr);
652 Common::String str = Common::String::vformat(formatStr, args);
653 va_end(args);
654
655 // Open the message widget
656 _menuMode = MESSAGE_MODE;
657 _messageWidget.load(str, 25);
658 _messageWidget.summonWindow();
659 }
660
setupBGArea(const byte cMap[PALETTE_SIZE])661 void TattooUserInterface::setupBGArea(const byte cMap[PALETTE_SIZE]) {
662 Scene &scene = *_vm->_scene;
663
664 // This requires that there is a 16 grayscale palette sequence in the palette that goes from lighter
665 // to darker as the palette numbers go up. The last palette entry in that run is specified by _bgColor
666 byte *p = &_lookupTable[0];
667 for (int idx = 0; idx < PALETTE_COUNT; ++idx)
668 *p++ = BG_GREYSCALE_RANGE_END - (cMap[idx * 3] * 30 + cMap[idx * 3 + 1] * 59 + cMap[idx * 3 + 2] * 11) / 480;
669
670 // If we're going to a scene with a haze special effect, initialize the translate table to lighten the colors
671 if (_mask != nullptr) {
672 p = &_lookupTable1[0];
673
674 for (int idx = 0; idx < PALETTE_COUNT; ++idx) {
675 int r, g, b;
676 switch (scene._currentScene) {
677 case 8:
678 r = cMap[idx * 3] * 4 / 5;
679 g = cMap[idx * 3 + 1] * 3 / 4;
680 b = cMap[idx * 3 + 2] * 3 / 4;
681 break;
682
683 case 18:
684 case 68:
685 r = cMap[idx * 3] * 4 / 3;
686 g = cMap[idx * 3 + 1] * 4 / 3;
687 b = cMap[idx * 3 + 2] * 4 / 3;
688 break;
689
690 case 7:
691 case 53:
692 r = cMap[idx * 3] * 4 / 3;
693 g = cMap[idx * 3 + 1] * 4 / 3;
694 b = cMap[idx * 3 + 2] * 4 / 3;
695 break;
696
697 default:
698 r = g = b = 0;
699 break;
700 }
701
702 byte c = 0xff;
703 int cd = 99999;
704
705 for (int pal = 0; pal < PALETTE_COUNT; ++pal) {
706 int d = (r - cMap[pal * 3]) * (r - cMap[pal * 3]) + (g - cMap[pal * 3 + 1]) * (g - cMap[pal * 3 + 1]) +
707 (b - cMap[pal * 3 + 2]) * (b - cMap[pal * 3 + 2]);
708
709 if (d < cd) {
710 c = pal;
711 cd = d;
712 if (!d)
713 break;
714 }
715 }
716 *p++ = c;
717 }
718 }
719 }
720
doBgAnimEraseBackground()721 void TattooUserInterface::doBgAnimEraseBackground() {
722 People &people = *_vm->_people;
723 Scene &scene = *_vm->_scene;
724 Screen &screen = *_vm->_screen;
725
726 static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 };
727
728 if (_mask != nullptr) {
729 // Since a mask is active, restore the screen from the secondary back buffer prior to applying the mask
730 screen._backBuffer1.SHblitFrom(screen._backBuffer2, screen._currentScroll, Common::Rect(screen._currentScroll.x, 0,
731 screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
732
733 switch (scene._currentScene) {
734 case 7:
735 if (++_maskCounter == 2) {
736 _maskCounter = 0;
737 if (--_maskOffset.x < 0)
738 _maskOffset.x = SHERLOCK_SCREEN_WIDTH - 1;
739 }
740 break;
741
742 case 8:
743 _maskOffset.x += 2;
744 if (_maskOffset.x >= SHERLOCK_SCREEN_WIDTH)
745 _maskOffset.x = 0;
746 break;
747
748 case 18:
749 case 68:
750 ++_maskCounter;
751 if (_maskCounter / 4 >= 16)
752 _maskCounter = 0;
753
754 _maskOffset.x = OFFSETS[_maskCounter / 4];
755 break;
756
757 case 53:
758 if (++_maskCounter == 2) {
759 _maskCounter = 0;
760 if (++_maskOffset.x == screen._backBuffer1.width())
761 _maskOffset.x = 0;
762 }
763 break;
764
765 default:
766 break;
767 }
768 } else {
769 // Standard scene without mask, so call user interface to erase any UI elements as necessary
770 doBgAnimRestoreUI();
771
772 // Restore background for any areas covered by characters and shapes
773 for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
774 screen.restoreBackground(Common::Rect(people[idx]._oldPosition.x, people[idx]._oldPosition.y,
775 people[idx]._oldPosition.x + people[idx]._oldSize.x, people[idx]._oldPosition.y + people[idx]._oldSize.y));
776
777 for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
778 Object &obj = scene._bgShapes[idx];
779
780 if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) ||
781 obj._type == HIDE_SHAPE || obj._type == REMOVE)
782 screen._backBuffer1.SHblitFrom(screen._backBuffer2, obj._oldPosition,
783 Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x,
784 obj._oldPosition.y + obj._oldSize.y));
785 }
786
787 // If credits are active, erase the area they cover
788 if (_creditsWidget.active())
789 _creditsWidget.eraseCredits();
790 }
791
792 for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
793 Object &obj = scene._bgShapes[idx];
794
795 if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
796 screen._backBuffer1.SHblitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds());
797
798 obj._oldPosition = obj._position;
799 obj._oldSize = obj._noShapeSize;
800 }
801 }
802
803 // Adjust the Target Scroll if needed
804 if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) <
805 (SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) {
806
807 _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
808 SHERLOCK_SCREEN_WIDTH / 8 - 250);
809 if (_targetScroll.x < 0)
810 _targetScroll.x = 0;
811 }
812
813 if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) >
814 (SHERLOCK_SCREEN_WIDTH / 4 * 3) && people[people._walkControl]._delta.x > 0)
815 _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
816 SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250);
817
818 if (_targetScroll.x > _scrollSize)
819 _targetScroll.x = _scrollSize;
820
821 doScroll();
822 }
823
drawMaskArea(bool mode)824 void TattooUserInterface::drawMaskArea(bool mode) {
825 Scene &scene = *_vm->_scene;
826 int xp = mode ? _maskOffset.x : 0;
827
828 if (_mask != nullptr) {
829 switch (scene._currentScene) {
830 case 7:
831 maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110));
832 maskArea(*_mask, Common::Point(_maskOffset.x, 110));
833 maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110));
834 break;
835
836 case 8:
837 maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180));
838 maskArea(*_mask, Common::Point(_maskOffset.x, 180));
839 maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180));
840 if (!_vm->readFlags(880))
841 maskArea(*_mask1, Common::Point(940, 300));
842 break;
843
844 case 18:
845 maskArea(*_mask, Common::Point(xp, 203));
846 if (!_vm->readFlags(189))
847 maskArea(*_mask1, Common::Point(124 + xp, 239));
848 break;
849
850 case 53:
851 maskArea(*_mask, Common::Point(_maskOffset.x, 110));
852 if (mode)
853 maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110));
854 break;
855
856 case 68:
857 maskArea(*_mask, Common::Point(xp, 203));
858 maskArea(*_mask1, Common::Point(124 + xp, 239));
859 break;
860 }
861 }
862 }
863
maskArea(Common::SeekableReadStream & mask,const Common::Point & pt)864 void TattooUserInterface::maskArea(Common::SeekableReadStream &mask, const Common::Point &pt) {
865 Screen &screen = *_vm->_screen;
866 Surface &bb1 = screen._backBuffer1;
867 mask.seek(0);
868 int xSize = mask.readUint16LE();
869 int ySize = mask.readUint16LE();
870 int pixel, len, xp, yp;
871
872 for (yp = 0; yp < ySize; ++yp) {
873 byte *ptr = (byte *)bb1.getBasePtr(pt.x, pt.y + yp);
874
875 for (xp = 0; xp < xSize;) {
876 // The mask data consists of pairs of pixel/lengths, where all non-zero pixels means that the
877 // given pixel on the back buffer is darkened (the mask pixel value isn't otherwise used)
878 pixel = mask.readByte();
879 len = mask.readByte();
880
881 for (; len > 0; --len, ++xp, ++ptr) {
882 if (pixel && (pt.x + xp) >= screen._currentScroll.x && (pt.x + xp) < (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) {
883 *ptr = _lookupTable1[*ptr];
884 }
885 }
886 }
887
888 assert(xp == xSize);
889 }
890 }
891
makeBGArea(const Common::Rect & r)892 void TattooUserInterface::makeBGArea(const Common::Rect &r) {
893 Screen &screen = *_vm->_screen;
894
895 for (int yp = r.top; yp < r.bottom; ++yp) {
896 byte *ptr = (byte *)screen._backBuffer1.getBasePtr(r.left, yp);
897
898 for (int xp = r.left; xp < r.right; ++xp, ++ptr)
899 *ptr = _lookupTable[*ptr];
900 }
901
902 screen.slamRect(r);
903 }
904
drawDialogRect(Surface & s,const Common::Rect & r,bool raised)905 void TattooUserInterface::drawDialogRect(Surface &s, const Common::Rect &r, bool raised) {
906 if (raised) {
907 // Draw Left
908 s.vLine(r.left, r.top, r.bottom - 1, INFO_TOP);
909 s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_TOP);
910 // Draw Top
911 s.hLine(r.left + 2, r.top, r.right - 1, INFO_TOP);
912 s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_TOP);
913 // Draw Right
914 s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_BOTTOM);
915 s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_BOTTOM);
916 // Draw Bottom
917 s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_BOTTOM);
918 s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_BOTTOM);
919
920 } else {
921 // Draw Left
922 s.vLine(r.left, r.top, r.bottom - 1, INFO_BOTTOM);
923 s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_BOTTOM);
924 // Draw Top
925 s.hLine(r.left + 2, r.top, r.right - 1, INFO_BOTTOM);
926 s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_BOTTOM);
927 // Draw Right
928 s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_TOP);
929 s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_TOP);
930 // Draw Bottom
931 s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_TOP);
932 s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_TOP);
933 }
934 }
935
banishWindow(bool slideUp)936 void TattooUserInterface::banishWindow(bool slideUp) {
937 if (!_widgets.empty())
938 _widgets.back()->banishWindow();
939 }
940
freeMenu()941 void TattooUserInterface::freeMenu() {
942 for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
943 (*i)->erase();
944 _widgets.clear();
945 }
946
clearWindow()947 void TattooUserInterface::clearWindow() {
948 banishWindow();
949 }
950
loadGame()951 void TattooUserInterface::loadGame() {
952 WidgetFiles &files = *(WidgetFiles *)_vm->_saves;
953 files.show(SAVEMODE_LOAD);
954 }
955
saveGame()956 void TattooUserInterface::saveGame() {
957 WidgetFiles &files = *(WidgetFiles *)_vm->_saves;
958 files.show(SAVEMODE_SAVE);
959 }
960
addFixedWidget(WidgetBase * widget)961 void TattooUserInterface::addFixedWidget(WidgetBase *widget) {
962 _fixedWidgets.push_back(widget);
963 widget->summonWindow();
964 }
965
966 } // End of namespace Tattoo
967
968 } // End of namespace Sherlock
969