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