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 /*
24  * This code is based on Labyrinth of Time code with assistance of
25  *
26  * Copyright (c) 1993 Terra Nova Development
27  * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
28  *
29  */
30 
31 #include "common/config-manager.h"
32 #include "common/file.h"
33 
34 #include "gui/message.h"
35 
36 #include "lab/lab.h"
37 #include "lab/anim.h"
38 #include "lab/dispman.h"
39 #include "lab/eventman.h"
40 #include "lab/image.h"
41 #include "lab/interface.h"
42 #include "lab/intro.h"
43 #include "lab/labsets.h"
44 #include "lab/music.h"
45 #include "lab/processroom.h"
46 #include "lab/resource.h"
47 #include "lab/speciallocks.h"
48 #include "lab/utils.h"
49 
50 namespace Lab {
51 
52 enum SpecialLock {
53 	kLockCombination = 100,
54 	kLockTiles = 101,
55 	kLockTileSolution = 102
56 };
57 
58 enum Items {
59 	kItemHelmet = 1,
60 	kItemBelt = 3,
61 	kItemPithHelmet = 7,
62 	kItemJournal = 9,
63 	kItemNotes = 12,
64 	kItemWestPaper = 18,
65 	kItemWhiskey = 25,
66 	kItemLamp = 27,
67 	kItemMap = 28,
68 	kItemQuarter = 30
69 };
70 
71 enum Monitors {
72 	kMonitorMuseum = 71,
73 	kMonitorGramophone = 72,
74 	kMonitorUnicycle = 73,
75 	kMonitorStatue = 74,
76 	kMonitorTalisman = 75,
77 	kMonitorLute = 76,
78 	kMonitorClock = 77,
79 	kMonitorWindow = 78,
80 	//kMonitorBelt = 79,
81 	kMonitorLibrary = 80,
82 	kMonitorTerminal = 81
83 	//kMonitorLevers = 82
84 };
85 
86 enum AltButtons {
87 	kButtonMainDisplay,
88 	kButtonSaveLoad,
89 	kButtonUseItem,
90 	kButtonLookAtItem,
91 	kButtonPrevItem,
92 	kButtonNextItem,
93 	kButtonBreadCrumbs,
94 	kButtonFollowCrumbs
95 };
96 
97 static char initColors[] = { '\x00', '\x00', '\x00', '\x30',
98 							 '\x30', '\x30', '\x10', '\x10',
99 							 '\x10', '\x14', '\x14', '\x14',
100 							 '\x20', '\x20', '\x20', '\x24',
101 							 '\x24', '\x24', '\x2c', '\x2c',
102 							 '\x2c', '\x08', '\x08', '\x08' };
103 
handleTrialWarning()104 void LabEngine::handleTrialWarning() {
105 	// Check if this is the Wyrmkeep trial
106 	Common::File roomFile;
107 	bool knownVersion = true;
108 	bool roomFileOpened = roomFile.open("rooms/48");
109 
110 	if (!roomFileOpened)
111 		knownVersion = false;
112 	else if (roomFile.size() != 892)
113 		knownVersion = false;
114 	else {
115 		roomFile.seek(352);
116 		byte checkByte = roomFile.readByte();
117 		if (checkByte == 0x00) {
118 			// Full Windows version
119 		}
120 		else if (checkByte == 0x80) {
121 			// Wyrmkeep trial version
122 			_extraGameFeatures = GF_WINDOWS_TRIAL;
123 
124 			GUI::MessageDialog trialMessage("This is a trial Windows version of the game. To play the full version, you will need to use the original interpreter and purchase a key from Wyrmkeep");
125 			trialMessage.runModal();
126 		}
127 		else {
128 			knownVersion = false;
129 		}
130 
131 		roomFile.close();
132 	}
133 
134 	if (!knownVersion)
135 		error("Unknown Windows version found, please report this version to the ScummVM team");
136 }
137 
getQuarters()138 uint16 LabEngine::getQuarters() {
139 	return _inventory[kItemQuarter]._quantity;
140 }
141 
setQuarters(uint16 quarters)142 void LabEngine::setQuarters(uint16 quarters) {
143 	_inventory[kItemQuarter]._quantity = quarters;
144 }
145 
drawRoomMessage(uint16 curInv,const CloseData * closePtr)146 void LabEngine::drawRoomMessage(uint16 curInv, const CloseData *closePtr) {
147 	if (_lastTooLong) {
148 		_lastTooLong = false;
149 		return;
150 	}
151 
152 	if (_alternate) {
153 		if ((curInv <= _numInv) && _conditions->in(curInv) && !_inventory[curInv]._bitmapName.empty()) {
154 			if ((curInv == kItemLamp) && _conditions->in(kCondLampOn))
155 				// LAB: Labyrinth specific
156 				drawStaticMessage(kTextkLampOn);
157 			else if (_inventory[curInv]._quantity > 1) {
158 				Common::String roomMessage = _inventory[curInv]._name + "  (" + Common::String::format("%d", _inventory[curInv]._quantity) + ")";
159 				_graphics->drawMessage(roomMessage.c_str(), false);
160 			} else
161 				_graphics->drawMessage(_inventory[curInv]._name.c_str(), false);
162 		}
163 	} else
164 		drawDirection(closePtr);
165 
166 	_lastTooLong = _graphics->_lastMessageLong;
167 }
168 
freeScreens()169 void LabEngine::freeScreens() {
170 	for (int i = 0; i < 20; i++) {
171 		delete _moveImages[i];
172 		_moveImages[i] = nullptr;
173 	}
174 
175 	for (int imgIdx = 0; imgIdx < 10; imgIdx++) {
176 		delete _invImages[imgIdx];
177 		_invImages[imgIdx] = nullptr;
178 	}
179 
180 	// We can't use freeButtonList() here, because some buttons are shared
181 	// between the two lists.
182 	for (ButtonList::iterator buttonIter = _moveButtonList.begin(); buttonIter != _moveButtonList.end(); ++buttonIter) {
183 		delete *buttonIter;
184 	}
185 	_moveButtonList.clear();
186 
187 	for (ButtonList::iterator buttonIter = _invButtonList.begin(); buttonIter != _invButtonList.end(); ++buttonIter) {
188 		delete *buttonIter;
189 	}
190 	_invButtonList.clear();
191 }
192 
perFlipButton(uint16 buttonId)193 void LabEngine::perFlipButton(uint16 buttonId) {
194 	for (ButtonList::iterator button = _moveButtonList.begin(); button != _moveButtonList.end(); ++button) {
195 		Button *topButton = *button;
196 		if (topButton->_buttonId == buttonId) {
197 			SWAP<Image *>(topButton->_image, topButton->_altImage);
198 
199 			if (!_alternate)
200 				topButton->_image->drawImage(topButton->_x, topButton->_y);
201 
202 			break;
203 		}
204 	}
205 }
206 
eatMessages()207 void LabEngine::eatMessages() {
208 	IntuiMessage *msg;
209 
210 	do {
211 		msg = _event->getMsg();
212 	} while (msg && !shouldQuit());
213 }
214 
handleMonitorCloseup()215 void LabEngine::handleMonitorCloseup() {
216 	if (!_closeDataPtr)
217 		return;
218 
219 	Common::Rect textRect(2, 2, 317, 165);
220 	bool isInteractive = false;
221 
222 	switch (_closeDataPtr->_closeUpType) {
223 	case kMonitorMuseum:
224 	case kMonitorLibrary:
225 	case kMonitorWindow:
226 		break;
227 	case kMonitorGramophone:
228 		textRect.right = 171;
229 		break;
230 	case kMonitorUnicycle:
231 		textRect.left = 100;
232 		break;
233 	case kMonitorStatue:
234 		textRect.left = 117;
235 		break;
236 	case kMonitorTalisman:
237 		textRect.right = 184;
238 		break;
239 	case kMonitorLute:
240 		textRect.right = 128;
241 		break;
242 	case kMonitorClock:
243 		textRect.right = 206;
244 		break;
245 	case kMonitorTerminal:
246 		isInteractive = true;
247 		break;
248 	default:
249 		return;
250 	}
251 
252 	doMonitor(_closeDataPtr->_graphicName, _closeDataPtr->_message, isInteractive, textRect);
253 
254 	_curFileName = " ";
255 	_graphics->drawPanel();
256 
257 	_closeDataPtr = nullptr;
258 	_interface->mayShowCrumbIndicator();
259 	_graphics->screenUpdate();
260 }
261 
getInvName(uint16 curInv)262 Common::String LabEngine::getInvName(uint16 curInv) {
263 	if (_mainDisplay)
264 		return _inventory[curInv]._bitmapName;
265 
266 	if ((curInv == kItemLamp) && _conditions->in(kCondLampOn))
267 		return "P:Mines/120";
268 
269 	if ((curInv == kItemBelt) && _conditions->in(kCondBeltGlowing))
270 		return "P:Future/BeltGlow";
271 
272 	if (curInv == kItemWestPaper) {
273 		_curFileName = _inventory[curInv]._bitmapName;
274 		_anim->_noPalChange = true;
275 		_graphics->readPict(_curFileName, false);
276 		_anim->_noPalChange = false;
277 		doWestPaper();
278 	} else if (curInv == kItemNotes) {
279 		_curFileName = _inventory[curInv]._bitmapName;
280 		_anim->_noPalChange = true;
281 		_graphics->readPict(_curFileName, false);
282 		_anim->_noPalChange = false;
283 		doNotes();
284 	}
285 
286 	return _inventory[curInv]._bitmapName;
287 }
288 
interfaceOff()289 void LabEngine::interfaceOff() {
290 	_interface->attachButtonList(nullptr);
291 	_event->mouseHide();
292 }
293 
interfaceOn()294 void LabEngine::interfaceOn() {
295 	if (_graphics->_longWinInFront)
296 		_interface->attachButtonList(nullptr);
297 	else if (_alternate)
298 		_interface->attachButtonList(&_invButtonList);
299 	else
300 		_interface->attachButtonList(&_moveButtonList);
301 
302 	_event->mouseShow();
303 }
304 
doUse(uint16 curInv)305 bool LabEngine::doUse(uint16 curInv) {
306 	switch (curInv) {
307 	case kItemMap:
308 		drawStaticMessage(kTextUseMap);
309 		interfaceOff();
310 		_anim->stopDiff();
311 		_curFileName = " ";
312 		_closeDataPtr = nullptr;
313 		doMap();
314 		_graphics->setPalette(initColors, 8);
315 		_graphics->drawMessage("", false);
316 		_graphics->drawPanel();
317 		return true;
318 	case kItemJournal:
319 		drawStaticMessage(kTextUseJournal);
320 		interfaceOff();
321 		_anim->stopDiff();
322 		_curFileName = " ";
323 		_closeDataPtr = nullptr;
324 		doJournal();
325 		_graphics->drawPanel();
326 		_graphics->drawMessage("", false);
327 		return true;
328 	case kItemLamp:
329 		interfaceOff();
330 
331 		if (_conditions->in(kCondLampOn)) {
332 			drawStaticMessage(kTextTurnLampOff);
333 			_conditions->exclElement(kCondLampOn);
334 		} else {
335 			drawStaticMessage(kTextTurnkLampOn);
336 			_conditions->inclElement(kCondLampOn);
337 		}
338 
339 		_anim->_doBlack = false;
340 		_anim->_waitForEffect = true;
341 		_graphics->readPict("Music:Click");
342 		_anim->_waitForEffect = false;
343 
344 		_anim->_doBlack = false;
345 		_nextFileName = getInvName(curInv);
346 		return true;
347 	case kItemBelt:
348 		if (!_conditions->in(kCondBeltGlowing))
349 			_conditions->inclElement(kCondBeltGlowing);
350 
351 		_anim->_doBlack = false;
352 		_nextFileName = getInvName(curInv);
353 		return true;
354 	case kItemWhiskey:
355 		_conditions->inclElement(kCondUsedHelmet);
356 		drawStaticMessage(kTextUseWhiskey);
357 		return true;
358 	case kItemPithHelmet:
359 		_conditions->inclElement(kCondUsedHelmet);
360 		drawStaticMessage(kTextUsePith);
361 		return true;
362 	case kItemHelmet:
363 		_conditions->inclElement(kCondUsedHelmet);
364 		drawStaticMessage(kTextUseHelmet);
365 		return true;
366 	default:
367 		return false;
368 	}
369 }
370 
decIncInv(uint16 * curInv,bool decreaseFl)371 void LabEngine::decIncInv(uint16 *curInv, bool decreaseFl) {
372 	int8 step = (decreaseFl) ? -1 : 1;
373 	uint newInv = *curInv + step;
374 
375 	// Handle wrapping
376 	if (newInv < 1)
377 		newInv = _numInv;
378 	if (newInv > _numInv)
379 		newInv = 1;
380 
381 	interfaceOff();
382 
383 	while (newInv && (newInv <= _numInv)) {
384 		if (_conditions->in(newInv) && !_inventory[newInv]._bitmapName.empty()) {
385 			_nextFileName = getInvName(newInv);
386 			*curInv = newInv;
387 			break;
388 		}
389 
390 		newInv += step;
391 
392 		// Handle wrapping
393 		if (newInv < 1)
394 			newInv = _numInv;
395 		if (newInv > _numInv)
396 			newInv = 1;
397 	}
398 }
399 
mainGameLoop()400 void LabEngine::mainGameLoop() {
401 	_graphics->setPalette(initColors, 8);
402 
403 	_closeDataPtr = nullptr;
404 	_roomNum = 1;
405 	_direction = kDirectionNorth;
406 
407 	_resource->readRoomData("LAB:Doors");
408 	if (!(_inventory = _resource->readInventory("LAB:Inventor")))
409 		return;
410 
411 	if (!(_conditions = new LargeSet(_highestCondition + 1, this)))
412 		return;
413 
414 	if (!(_roomsFound = new LargeSet(_manyRooms + 1, this)))
415 		return;
416 
417 	_conditions->readInitialConditions("LAB:Conditio");
418 
419 	_graphics->_longWinInFront = false;
420 	_graphics->drawPanel();
421 
422 	uint16 actionMode = 4;
423 	perFlipButton(actionMode);
424 
425 	// Load saved slot from the launcher, if requested
426 	if (ConfMan.hasKey("save_slot")) {
427 		loadGame(ConfMan.getInt("save_slot"));
428 
429 		// Since the intro hasn't been shown, init the background music here
430 		_music->resetMusic(false);
431 	}
432 
433 	uint16 curInv = kItemMap;
434 	bool forceDraw = false;
435 	bool gotMessage = true;
436 	// Set up initial picture.
437 	while (1) {
438 		_event->processInput();
439 		_system->delayMillis(10);
440 
441 		if (gotMessage) {
442 			if (_quitLab || shouldQuit()) {
443 				_anim->stopDiff();
444 				break;
445 			}
446 
447 			handleMonitorCloseup();
448 
449 			// Sets the current picture properly on the screen
450 			if (_mainDisplay)
451 				_nextFileName = getPictName(true);
452 
453 			if (_noUpdateDiff) {
454 				// Potentially entered another room
455 				_roomsFound->inclElement(_roomNum);
456 				forceDraw |= (_nextFileName != _curFileName);
457 
458 				_noUpdateDiff = false;
459 				_curFileName = _nextFileName;
460 			} else if (_nextFileName != _curFileName) {
461 				interfaceOff();
462 				// Potentially entered another room
463 				_roomsFound->inclElement(_roomNum);
464 				_curFileName = _nextFileName;
465 
466 				if (_closeDataPtr && _mainDisplay) {
467 					switch (_closeDataPtr->_closeUpType) {
468 					case kLockCombination:
469 						_specialLocks->showCombinationLock(_curFileName);
470 						break;
471 					case kLockTiles:
472 					case kLockTileSolution:
473 						_specialLocks->showTileLock(_curFileName, (_closeDataPtr->_closeUpType == kLockTileSolution));
474 						break;
475 					default:
476 						_graphics->readPict(_curFileName, false);
477 						break;
478 					}
479 				} else
480 					_graphics->readPict(_curFileName, false);
481 
482 				drawRoomMessage(curInv, _closeDataPtr);
483 				forceDraw = false;
484 
485 				_interface->mayShowCrumbIndicator();
486 				_graphics->screenUpdate();
487 
488 				if (!_followingCrumbs)
489 					eatMessages();
490 			}
491 
492 			if (forceDraw) {
493 				drawRoomMessage(curInv, _closeDataPtr);
494 				forceDraw = false;
495 				_graphics->screenUpdate();
496 			}
497 		}
498 
499 		// Make sure we check the music at least after every message
500 		updateEvents();
501 		interfaceOn();
502 		IntuiMessage *curMsg = _event->getMsg();
503 		if (shouldQuit()) {
504 			_quitLab = true;
505 			return;
506 		}
507 
508 		if (!curMsg) {
509 			// Does music load and next animation frame when you've run out of messages
510 			gotMessage = false;
511 			updateEvents();
512 			_anim->diffNextFrame();
513 
514 			if (_followingCrumbs) {
515 				MainButton code = followCrumbs();
516 
517 				if (code == kButtonForward || code == kButtonLeft || code == kButtonRight) {
518 					gotMessage = true;
519 					_interface->mayShowCrumbIndicator();
520 					_graphics->screenUpdate();
521 					if (!processEvent(kMessageButtonUp, code, 0, _event->updateAndGetMousePos(), curInv, curMsg, forceDraw, code, actionMode))
522 						break;
523 				}
524 			}
525 
526 			_interface->mayShowCrumbIndicator();
527 			_graphics->screenUpdate();
528 		} else {
529 			gotMessage = true;
530 			_followingCrumbs = false;
531 			if (!processEvent(curMsg->_msgClass, curMsg->_code, curMsg->_qualifier, curMsg->_mouse, curInv, curMsg, forceDraw, curMsg->_code, actionMode))
532 				break;
533 		}
534 	}
535 }
536 
showLab2Teaser()537 void LabEngine::showLab2Teaser() {
538 	_graphics->blackAllScreen();
539 	_graphics->readPict("P:End/L2In.1");
540 
541 	for (int i = 0; i < 120; i++) {
542 		updateEvents();
543 		waitTOF();
544 	}
545 
546 	_graphics->readPict("P:End/L2In.9");
547 	_graphics->readPict("P:End/Lost");
548 
549 	while (!_event->getMsg() && !shouldQuit()) {
550 		updateEvents();
551 		_anim->diffNextFrame();
552 		waitTOF();
553 	}
554 }
555 
processEvent(MessageClass tmpClass,uint16 code,uint16 qualifier,Common::Point tmpPos,uint16 & curInv,IntuiMessage * curMsg,bool & forceDraw,uint16 buttonId,uint16 & actionMode)556 bool LabEngine::processEvent(MessageClass tmpClass, uint16 code, uint16 qualifier, Common::Point tmpPos,
557 			uint16 &curInv, IntuiMessage *curMsg, bool &forceDraw, uint16 buttonId, uint16 &actionMode) {
558 
559 	if (shouldQuit())
560 		return false;
561 
562 	MessageClass msgClass = tmpClass;
563 	Common::Point curPos = tmpPos;
564 	uint16 oldDirection = 0;
565 	uint16 lastInv = kItemMap;
566 
567 	if (code == Common::KEYCODE_RETURN)
568 		msgClass = kMessageLeftClick;
569 
570 	bool leftButtonClick = (msgClass == kMessageLeftClick);
571 	bool rightButtonClick = (msgClass == kMessageRightClick);
572 
573 	_anim->_doBlack = false;
574 
575 	if (_graphics->_longWinInFront) {
576 		if (msgClass == kMessageRawKey || leftButtonClick || rightButtonClick) {
577 			_graphics->_longWinInFront = false;
578 			_graphics->drawPanel();
579 			drawRoomMessage(curInv, _closeDataPtr);
580 			_graphics->screenUpdate();
581 		}
582 	} else if (msgClass == kMessageRawKey) {
583 		return processKey(curMsg, msgClass, qualifier, curPos, curInv, forceDraw, code);
584 	} else if (msgClass == kMessageButtonUp) {
585 		if (!_alternate)
586 			processMainButton(curInv, lastInv, oldDirection, forceDraw, buttonId, actionMode);
587 		else
588 			processAltButton(curInv, lastInv, buttonId, actionMode);
589 	} else if (leftButtonClick && _mainDisplay) {
590 		interfaceOff();
591 		_mainDisplay = true;
592 
593 		if (_closeDataPtr && _closeDataPtr->_closeUpType == kLockCombination)
594 			_specialLocks->combinationClick(curPos);
595 		else if (_closeDataPtr && _closeDataPtr->_closeUpType == kLockTiles)
596 			_specialLocks->tileClick(curPos);
597 		else
598 			performAction(actionMode, curPos, curInv);
599 
600 		_interface->mayShowCrumbIndicator();
601 		_graphics->screenUpdate();
602 	} else if (rightButtonClick) {
603 		eatMessages();
604 		_alternate = !_alternate;
605 		_anim->_doBlack = true;
606 		_mainDisplay = true;
607 		// Sets the correct button list
608 		interfaceOn();
609 
610 		if (_alternate) {
611 			if (lastInv && _conditions->in(lastInv))
612 				curInv = lastInv;
613 			else
614 				decIncInv(&curInv, false);
615 		}
616 
617 		_graphics->drawPanel();
618 		drawRoomMessage(curInv, _closeDataPtr);
619 
620 		_interface->mayShowCrumbIndicator();
621 		_graphics->screenUpdate();
622 	}
623 
624 	return true;
625 }
626 
processKey(IntuiMessage * curMsg,uint32 msgClass,uint16 & qualifier,Common::Point & curPos,uint16 & curInv,bool & forceDraw,uint16 code)627 bool LabEngine::processKey(IntuiMessage *curMsg, uint32 msgClass, uint16 &qualifier, Common::Point &curPos, uint16 &curInv, bool &forceDraw, uint16 code) {
628 	if ((getPlatform() == Common::kPlatformWindows) && (code == Common::KEYCODE_b)) {
629 		// Start bread crumbs
630 		_breadCrumbs[0]._crumbRoomNum = 0;
631 		_numCrumbs = 0;
632 		_droppingCrumbs = true;
633 		_interface->mayShowCrumbIndicator();
634 		_graphics->screenUpdate();
635 	} else if (getPlatform() == Common::kPlatformWindows && (code == Common::KEYCODE_f || code == Common::KEYCODE_r)) {
636 		// Follow bread crumbs
637 		if (_droppingCrumbs) {
638 			if (_numCrumbs > 0) {
639 				_followingCrumbs = true;
640 				_followCrumbsFast = (code == Common::KEYCODE_r);
641 				_isCrumbTurning = false;
642 				_isCrumbWaiting = false;
643 				_crumbTimestamp = _system->getMillis();
644 
645 				if (_alternate) {
646 					eatMessages();
647 					_alternate = false;
648 					_anim->_doBlack = true;
649 
650 					_mainDisplay = true;
651 					// Sets the correct button list
652 					interfaceOn();
653 					_graphics->drawPanel();
654 					drawRoomMessage(curInv, _closeDataPtr);
655 					_graphics->screenUpdate();
656 				}
657 			} else {
658 				_breadCrumbs[0]._crumbRoomNum = 0;
659 				_droppingCrumbs = false;
660 
661 				_interface->mayShowCrumbIndicatorOff();
662 				_graphics->screenUpdate();
663 			}
664 		}
665 	} else if ((code == Common::KEYCODE_x) || (code == Common::KEYCODE_q)) {
666 		// Quit?
667 		_graphics->drawMessage("Do you want to quit? (Y/N)", false);
668 		eatMessages();
669 		interfaceOff();
670 
671 		while (1) {
672 			// Make sure we check the music at least after every message
673 			updateEvents();
674 			curMsg = _event->getMsg();
675 
676 			if (shouldQuit())
677 				return false;
678 
679 			if (!curMsg) {
680 				// Does music load and next animation frame when you've run out of messages
681 				updateEvents();
682 				_anim->diffNextFrame();
683 			} else if (curMsg->_msgClass == kMessageRawKey) {
684 				if ((curMsg->_code == Common::KEYCODE_y) || (curMsg->_code == Common::KEYCODE_q)) {
685 					_anim->stopDiff();
686 					return false;
687 				} else if (curMsg->_code < 128)
688 					break;
689 			} else if ((curMsg->_msgClass == kMessageLeftClick) || (curMsg->_msgClass == kMessageRightClick))
690 				break;
691 		}
692 
693 		forceDraw = true;
694 		interfaceOn();
695 	} else if (code == Common::KEYCODE_ESCAPE) {
696 		_closeDataPtr = nullptr;
697 	} else if (code == Common::KEYCODE_TAB) {
698 		const CloseData *tmpClosePtr = _closeDataPtr;
699 
700 		// get next close-up in list after the one pointed to by curPos
701 		setCurrentClose(curPos, &tmpClosePtr, true, true);
702 
703 		if (tmpClosePtr != _closeDataPtr)
704 			_event->setMousePos(Common::Point(_utils->scaleX((tmpClosePtr->_x1 + tmpClosePtr->_x2) / 2), _utils->scaleY((tmpClosePtr->_y1 + tmpClosePtr->_y2) / 2)));
705 	}
706 
707 	eatMessages();
708 
709 	return true;
710 }
711 
processMainButton(uint16 & curInv,uint16 & lastInv,uint16 & oldDirection,bool & forceDraw,uint16 buttonId,uint16 & actionMode)712 void LabEngine::processMainButton(uint16 &curInv, uint16 &lastInv, uint16 &oldDirection, bool &forceDraw, uint16 buttonId, uint16 &actionMode) {
713 	switch (buttonId) {
714 	case kButtonPickup:
715 	case kButtonUse:
716 	case kButtonOpen:
717 	case kButtonClose:
718 	case kButtonLook:
719 		if ((actionMode == 4) && (buttonId == kButtonLook) && _closeDataPtr) {
720 			doMainView();
721 
722 			_anim->_doBlack = true;
723 			_closeDataPtr = nullptr;
724 			_interface->mayShowCrumbIndicator();
725 		} else {
726 			uint16 oldActionMode = actionMode;
727 			actionMode = buttonId;
728 
729 			if (oldActionMode < 5)
730 				perFlipButton(oldActionMode);
731 
732 			perFlipButton(actionMode);
733 			drawStaticMessage(kTextTakeWhat + buttonId);
734 		}
735 		break;
736 
737 	case kButtonInventory:
738 		eatMessages();
739 
740 		_alternate = true;
741 		_anim->_doBlack = true;
742 		// Sets the correct button list
743 		interfaceOn();
744 		_mainDisplay = false;
745 
746 		if (lastInv && _conditions->in(lastInv)) {
747 			curInv = lastInv;
748 			_nextFileName = getInvName(curInv);
749 		} else
750 			decIncInv(&curInv, false);
751 
752 		_graphics->drawPanel();
753 		drawRoomMessage(curInv, _closeDataPtr);
754 
755 		_interface->mayShowCrumbIndicator();
756 		break;
757 
758 	case kButtonLeft:
759 	case kButtonRight: {
760 		_closeDataPtr = nullptr;
761 		if (buttonId == kButtonLeft)
762 			drawStaticMessage(kTextTurnLeft);
763 		else
764 			drawStaticMessage(kTextTurnRight);
765 
766 		_curFileName = " ";
767 		oldDirection = _direction;
768 
769 		uint16 newDir = processArrow(_direction, buttonId - 6);
770 		doTurn(_direction, newDir);
771 		_anim->_doBlack = true;
772 		_direction = newDir;
773 		forceDraw = true;
774 		_interface->mayShowCrumbIndicator();
775 		}
776 		break;
777 
778 	case kButtonForward: {
779 		_closeDataPtr = nullptr;
780 		int oldRoomNum = _roomNum;
781 
782 		if (doGoForward()) {
783 			if (oldRoomNum == _roomNum)
784 				_anim->_doBlack = true;
785 		} else {
786 			_anim->_doBlack = true;
787 			_direction = processArrow(_direction, buttonId - 6);
788 
789 			if (oldRoomNum != _roomNum) {
790 				drawStaticMessage(kTextGoForward);
791 				// Potentially entered a new room
792 				_roomsFound->inclElement(_roomNum);
793 				_curFileName = " ";
794 				forceDraw = true;
795 			} else {
796 				_anim->_doBlack = true;
797 				drawStaticMessage(kTextNoPath);
798 			}
799 		}
800 
801 		if (_followingCrumbs) {
802 			if (_isCrumbTurning) {
803 				if (_direction == oldDirection)
804 					_followingCrumbs = false;
805 			} else if (_roomNum == oldRoomNum) { // didn't get there?
806 				_followingCrumbs = false;
807 			}
808 		} else if (_droppingCrumbs && (oldRoomNum != _roomNum)) {
809 			// If in surreal maze, turn off DroppingCrumbs.
810 			if ((_roomNum >= 245) && (_roomNum <= 280)) {
811 				_followingCrumbs = false;
812 				_droppingCrumbs = false;
813 				_numCrumbs = 0;
814 				_breadCrumbs[0]._crumbRoomNum = 0;
815 			} else {
816 				bool intersect = false;
817 				for (int idx = 0; idx < _numCrumbs; idx++) {
818 					if (_breadCrumbs[idx]._crumbRoomNum == _roomNum) {
819 						_numCrumbs = idx + 1;
820 						_breadCrumbs[_numCrumbs]._crumbRoomNum = 0;
821 						intersect = true;
822 					}
823 				}
824 
825 				if (!intersect) {
826 					if (_numCrumbs == MAX_CRUMBS) {
827 						_numCrumbs = MAX_CRUMBS - 1;
828 						memcpy(&_breadCrumbs[0], &_breadCrumbs[1], _numCrumbs * sizeof _breadCrumbs[0]);
829 					}
830 
831 					_breadCrumbs[_numCrumbs]._crumbRoomNum = _roomNum;
832 					_breadCrumbs[_numCrumbs++]._crumbDirection = _direction;
833 				}
834 			}
835 		}
836 
837 		_interface->mayShowCrumbIndicator();
838 		}
839 		break;
840 
841 	case kButtonMap:
842 		doUse(kItemMap);
843 
844 		_interface->mayShowCrumbIndicator();
845 		break;
846 	}
847 
848 	_graphics->screenUpdate();
849 }
850 
processAltButton(uint16 & curInv,uint16 & lastInv,uint16 buttonId,uint16 & actionMode)851 void LabEngine::processAltButton(uint16 &curInv, uint16 &lastInv, uint16 buttonId, uint16 &actionMode) {
852 	_anim->_doBlack = true;
853 
854 	switch (buttonId) {
855 	case kButtonMainDisplay:
856 		eatMessages();
857 		_alternate = false;
858 		_anim->_doBlack = true;
859 
860 		_mainDisplay = true;
861 		// Sets the correct button list
862 		interfaceOn();
863 		_graphics->drawPanel();
864 		drawRoomMessage(curInv, _closeDataPtr);
865 		break;
866 
867 	case kButtonSaveLoad: {
868 		interfaceOff();
869 		_anim->stopDiff();
870 		_curFileName = " ";
871 
872 		bool saveRestoreSuccessful = saveRestoreGame();
873 		_closeDataPtr = nullptr;
874 		_mainDisplay = true;
875 
876 		curInv = lastInv = kItemMap;
877 		_nextFileName = getInvName(curInv);
878 
879 		_graphics->drawPanel();
880 
881 		if (!saveRestoreSuccessful) {
882 			_graphics->drawMessage("Save/restore aborted", false);
883 			_graphics->setPalette(initColors, 8);
884 			_system->delayMillis(1000);
885 		}
886 		}
887 		break;
888 
889 	case kButtonUseItem:
890 		if (!doUse(curInv)) {
891 			uint16 oldActionMode = actionMode;
892 			// Use button
893 			actionMode = 5;
894 
895 			if (oldActionMode < 5)
896 				perFlipButton(oldActionMode);
897 
898 			drawStaticMessage(kTextUseOnWhat);
899 			_mainDisplay = true;
900 		}
901 		break;
902 
903 	case kButtonLookAtItem:
904 		_mainDisplay = !_mainDisplay;
905 
906 		if ((curInv == 0) || (curInv > _numInv)) {
907 			curInv = 1;
908 
909 			while ((curInv <= _numInv) && !_conditions->in(curInv))
910 				curInv++;
911 		}
912 
913 		if ((curInv <= _numInv) && _conditions->in(curInv) && !_inventory[curInv]._bitmapName.empty())
914 			_nextFileName = getInvName(curInv);
915 
916 		break;
917 
918 	case kButtonPrevItem:
919 		decIncInv(&curInv, true);
920 		lastInv = curInv;
921 		drawRoomMessage(curInv, _closeDataPtr);
922 		break;
923 
924 	case kButtonNextItem:
925 		decIncInv(&curInv, false);
926 		lastInv = curInv;
927 		drawRoomMessage(curInv, _closeDataPtr);
928 		break;
929 
930 	case kButtonBreadCrumbs:
931 		_breadCrumbs[0]._crumbRoomNum = 0;
932 		_numCrumbs = 0;
933 		_droppingCrumbs = true;
934 		_interface->mayShowCrumbIndicator();
935 		break;
936 
937 	case kButtonFollowCrumbs:
938 		if (_droppingCrumbs) {
939 			if (_numCrumbs > 0) {
940 				_followingCrumbs = true;
941 				_followCrumbsFast = false;
942 				_isCrumbTurning = false;
943 				_isCrumbWaiting = false;
944 				_crumbTimestamp = _system->getMillis();
945 
946 				eatMessages();
947 				_alternate = false;
948 				_anim->_doBlack = true;
949 
950 				_mainDisplay = true;
951 				// Sets the correct button list
952 				interfaceOn();
953 				_graphics->drawPanel();
954 				drawRoomMessage(curInv, _closeDataPtr);
955 			} else {
956 				_breadCrumbs[0]._crumbRoomNum = 0;
957 				_droppingCrumbs = false;
958 
959 				_interface->mayShowCrumbIndicatorOff();
960 			}
961 		}
962 		break;
963 	}
964 
965 	_graphics->screenUpdate();
966 }
967 
performAction(uint16 actionMode,Common::Point curPos,uint16 & curInv)968 void LabEngine::performAction(uint16 actionMode, Common::Point curPos, uint16 &curInv) {
969 	eatMessages();
970 
971 	switch (actionMode) {
972 	case 0:
973 		// Take something.
974 		if (doActionRule(curPos, actionMode, _roomNum))
975 			_curFileName = _newFileName;
976 		else if (takeItem(curPos))
977 			drawStaticMessage(kTextTakeItem);
978 		else if (doActionRule(curPos, kRuleActionTakeDef, _roomNum))
979 			_curFileName = _newFileName;
980 		else if (doActionRule(curPos, kRuleActionTake, 0))
981 			_curFileName = _newFileName;
982 		else if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
983 			drawStaticMessage(kTextNothing);
984 
985 		break;
986 
987 	case 1:
988 	case 2:
989 	case 3:
990 		// Manipulate an object, Open up a "door" or Close a "door"
991 		if (doActionRule(curPos, actionMode, _roomNum))
992 			_curFileName = _newFileName;
993 		else if (!doActionRule(curPos, actionMode, 0)) {
994 			if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
995 				drawStaticMessage(kTextNothing);
996 		}
997 		break;
998 
999 	case 4: {
1000 		// Look at closeups
1001 		const CloseData *tmpClosePtr = _closeDataPtr;
1002 		setCurrentClose(curPos, &tmpClosePtr, true);
1003 
1004 		if (_closeDataPtr == tmpClosePtr) {
1005 			if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
1006 				drawStaticMessage(kTextNothing);
1007 		} else if (!tmpClosePtr->_graphicName.empty()) {
1008 			_anim->_doBlack = true;
1009 			_closeDataPtr = tmpClosePtr;
1010 		} else if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
1011 			drawStaticMessage(kTextNothing);
1012 		}
1013 		break;
1014 
1015 	case 5:
1016 		if (_conditions->in(curInv)) {
1017 			// Use an item on something else
1018 			if (doOperateRule(curPos, curInv)) {
1019 				_curFileName = _newFileName;
1020 
1021 				if (!_conditions->in(curInv))
1022 					decIncInv(&curInv, false);
1023 			}
1024 			else if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
1025 				drawStaticMessage(kTextNothing);
1026 		}
1027 	}
1028 }
1029 
go()1030 void LabEngine::go() {
1031 	if (getPlatform() == Common::kPlatformWindows)
1032 		handleTrialWarning();
1033 
1034 	_isHiRes = ((getFeatures() & GF_LOWRES) == 0);
1035 	_graphics->setUpScreens();
1036 
1037 	_event->initMouse();
1038 	if (_msgFont)
1039 		_graphics->freeFont(&_msgFont);
1040 
1041 	if (getPlatform() != Common::kPlatformAmiga)
1042 		_msgFont = _resource->getFont("F:AvanteG.12");
1043 	else
1044 		_msgFont = _resource->getFont("F:Map.fon");
1045 
1046 	// If the user has requested to load a game from the launcher, skip the intro
1047 	if (!ConfMan.hasKey("save_slot")) {
1048 		_event->mouseHide();
1049 		_introPlaying = true;
1050 		Intro *intro = new Intro(this);
1051 		intro->play();
1052 		delete intro;
1053 		_introPlaying = false;
1054 		_event->mouseShow();
1055 	}
1056 
1057 	mainGameLoop();
1058 
1059 	_graphics->freeFont(&_msgFont);
1060 	_graphics->freePict();
1061 
1062 	freeScreens();
1063 
1064 	_music->freeMusic();
1065 }
1066 
followCrumbs()1067 MainButton LabEngine::followCrumbs() {
1068 	// kDirectionNorth, kDirectionSouth, kDirectionEast, kDirectionWest
1069 	MainButton movement[4][4] = {
1070 		{ kButtonForward, kButtonRight, kButtonRight, kButtonLeft },
1071 		{ kButtonRight, kButtonForward, kButtonLeft, kButtonRight },
1072 		{ kButtonLeft, kButtonRight, kButtonForward, kButtonRight },
1073 		{ kButtonRight, kButtonLeft, kButtonRight, kButtonForward }
1074 	};
1075 
1076 	if (_isCrumbWaiting) {
1077 		if (_system->getMillis() <= _crumbTimestamp)
1078 			return kButtonNone;
1079 
1080 		_isCrumbWaiting = false;
1081 	}
1082 
1083 	if (!_isCrumbTurning)
1084 		_breadCrumbs[_numCrumbs--]._crumbRoomNum = 0;
1085 
1086 	// Is the current crumb this room? If not, logic error.
1087 	if (_roomNum != _breadCrumbs[_numCrumbs]._crumbRoomNum) {
1088 		_numCrumbs = 0;
1089 		_breadCrumbs[0]._crumbRoomNum = 0;
1090 		_droppingCrumbs = false;
1091 		_followingCrumbs = false;
1092 		return kButtonNone;
1093 	}
1094 
1095 	Direction exitDir;
1096 	// which direction is last crumb
1097 	if (_breadCrumbs[_numCrumbs]._crumbDirection == kDirectionEast)
1098 		exitDir = kDirectionWest;
1099 	else if (_breadCrumbs[_numCrumbs]._crumbDirection == kDirectionWest)
1100 		exitDir = kDirectionEast;
1101 	else if (_breadCrumbs[_numCrumbs]._crumbDirection == kDirectionNorth)
1102 		exitDir = kDirectionSouth;
1103 	else
1104 		exitDir = kDirectionNorth;
1105 
1106 	MainButton moveDir = movement[_direction][exitDir];
1107 
1108 	if (_numCrumbs == 0) {
1109 		_isCrumbTurning = false;
1110 		_breadCrumbs[0]._crumbRoomNum = 0;
1111 		_droppingCrumbs = false;
1112 		_followingCrumbs = false;
1113 	} else {
1114 		_isCrumbTurning = (moveDir != kButtonForward);
1115 		_isCrumbWaiting = true;
1116 
1117 		int theDelay = (_followCrumbsFast ? 1000 / 4 : 1000);
1118 		_crumbTimestamp = theDelay + _system->getMillis();
1119 	}
1120 
1121 	return moveDir;
1122 }
1123 
1124 } // End of namespace Lab
1125