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