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/translation.h"
32 #include "gui/message.h"
33
34 #include "lab/lab.h"
35
36 #include "lab/anim.h"
37 #include "lab/dispman.h"
38 #include "lab/labsets.h"
39 #include "lab/music.h"
40 #include "lab/processroom.h"
41 #include "lab/resource.h"
42 #include "lab/utils.h"
43
44 namespace Lab {
45
46 #define NOFILE "no file"
47
checkConditions(const Common::Array<int16> & condition)48 bool LabEngine::checkConditions(const Common::Array<int16> &condition) {
49 for (unsigned int i = 0; i < condition.size(); ++i)
50 if (!_conditions->in(condition[i]))
51 return false;
52
53 return true;
54 }
55
getViewData(uint16 roomNum,uint16 direction)56 ViewData *LabEngine::getViewData(uint16 roomNum, uint16 direction) {
57 if (_rooms[roomNum]._roomMsg.empty())
58 _resource->readViews(roomNum);
59
60 ViewDataList &views = _rooms[roomNum]._view[direction];
61 ViewDataList::iterator view;
62
63 for (view = views.begin(); view != views.end(); ++view) {
64 if (checkConditions(view->_condition))
65 return &(*view);
66 }
67
68 error("No view with matching condition found");
69 }
70
getObject(Common::Point pos,const CloseData * closePtr)71 const CloseData *LabEngine::getObject(Common::Point pos, const CloseData *closePtr) {
72 const CloseDataList *list;
73 if (!closePtr)
74 list = &(getViewData(_roomNum, _direction)->_closeUps);
75 else
76 list = &(closePtr->_subCloseUps);
77
78 CloseDataList::const_iterator wrkClosePtr;
79
80 for (wrkClosePtr = list->begin(); wrkClosePtr != list->end(); ++wrkClosePtr) {
81 Common::Rect objRect;
82 objRect = _utils->rectScale(wrkClosePtr->_x1, wrkClosePtr->_y1, wrkClosePtr->_x2, wrkClosePtr->_y2);
83 if (objRect.contains(pos))
84 return &(*wrkClosePtr);
85 }
86
87 return nullptr;
88 }
89
findClosePtrMatch(const CloseData * closePtr,const CloseDataList & list)90 const CloseData *LabEngine::findClosePtrMatch(const CloseData *closePtr, const CloseDataList &list) {
91 CloseDataList::const_iterator i;
92
93 for (i = list.begin(); i != list.end(); ++i) {
94 if ((closePtr->_x1 == i->_x1) && (closePtr->_x2 == i->_x2) &&
95 (closePtr->_y1 == i->_y1) && (closePtr->_y2 == i->_y2) &&
96 (closePtr->_depth == i->_depth))
97 return &(*i);
98
99 const CloseData *resClosePtr = findClosePtrMatch(closePtr, i->_subCloseUps);
100
101 if (resClosePtr)
102 return resClosePtr;
103 }
104
105 return nullptr;
106 }
107
getPictName(bool useClose)108 Common::String LabEngine::getPictName(bool useClose) {
109 ViewData *viewPtr = getViewData(_roomNum, _direction);
110
111 if (useClose && _closeDataPtr) {
112 _closeDataPtr = findClosePtrMatch(_closeDataPtr, viewPtr->_closeUps);
113
114 if (_closeDataPtr)
115 return _closeDataPtr->_graphicName;
116 }
117
118 return viewPtr->_graphicName;
119 }
120
drawDirection(const CloseData * closePtr)121 void LabEngine::drawDirection(const CloseData *closePtr) {
122 if (closePtr && !closePtr->_message.empty()) {
123 _graphics->drawMessage(closePtr->_message, false);
124 return;
125 }
126
127 Common::String message;
128
129 if (!_rooms[_roomNum]._roomMsg.empty())
130 message = _rooms[_roomNum]._roomMsg + ", ";
131
132 if (_direction == kDirectionNorth)
133 message += _resource->getStaticText(kTextFacingNorth);
134 else if (_direction == kDirectionEast)
135 message += _resource->getStaticText(kTextFacingEast);
136 else if (_direction == kDirectionSouth)
137 message += _resource->getStaticText(kTextFacingSouth);
138 else if (_direction == kDirectionWest)
139 message += _resource->getStaticText(kTextFacingWest);
140
141 _graphics->drawMessage(message, false);
142 }
143
processArrow(uint16 curDirection,uint16 arrow)144 uint16 LabEngine::processArrow(uint16 curDirection, uint16 arrow) {
145 if (arrow == 1) { // Forward
146 uint16 room = _rooms[_roomNum]._doors[curDirection];
147 if (room != 0) {
148 _music->checkRoomMusic(_roomNum, room);
149 _roomNum = room;
150 }
151
152 return curDirection;
153 } else if (arrow == 0) { // Left
154 if (curDirection == kDirectionNorth)
155 return kDirectionWest;
156 else if (curDirection == kDirectionWest)
157 return kDirectionSouth;
158 else if (curDirection == kDirectionSouth)
159 return kDirectionEast;
160 else
161 return kDirectionNorth;
162 } else if (arrow == 2) { // Right
163 if (curDirection == kDirectionNorth)
164 return kDirectionEast;
165 else if (curDirection == kDirectionEast)
166 return kDirectionSouth;
167 else if (curDirection == kDirectionSouth)
168 return kDirectionWest;
169 else
170 return kDirectionNorth;
171 }
172
173 // Should never reach here!
174 return curDirection;
175 }
176
setCurrentClose(Common::Point pos,const CloseData ** closePtrList,bool useAbsoluteCoords,bool next)177 void LabEngine::setCurrentClose(Common::Point pos, const CloseData **closePtrList, bool useAbsoluteCoords, bool next) {
178 const CloseDataList *list;
179
180 if (!*closePtrList)
181 list = &(getViewData(_roomNum, _direction)->_closeUps);
182 else
183 list = &((*closePtrList)->_subCloseUps);
184
185 CloseDataList::const_iterator closePtr;
186 for (closePtr = list->begin(); closePtr != list->end(); ++closePtr) {
187 Common::Rect target;
188 if (!useAbsoluteCoords)
189 target = Common::Rect(closePtr->_x1, closePtr->_y1, closePtr->_x2, closePtr->_y2);
190 else
191 target = _utils->rectScale(closePtr->_x1, closePtr->_y1, closePtr->_x2, closePtr->_y2);
192
193 if (target.contains(pos) && (next || !closePtr->_graphicName.empty())) {
194
195 if (next) {
196 // cycle to the next one
197 ++closePtr;
198 if (closePtr == list->end())
199 closePtr = list->begin();
200 }
201 *closePtrList = &(*closePtr);
202
203 return;
204 }
205 }
206
207 // If we got here, no match was found. If we want the "next" close-up,
208 // return the first one in the list, if any.
209 if (next) {
210 if (!list->empty())
211 *closePtrList = &(*list->begin());
212 }
213 }
214
takeItem(Common::Point pos)215 bool LabEngine::takeItem(Common::Point pos) {
216 const CloseDataList *list;
217 if (!_closeDataPtr) {
218 list = &(getViewData(_roomNum, _direction)->_closeUps);
219 } else if (_closeDataPtr->_closeUpType < 0) {
220 _conditions->inclElement(abs(_closeDataPtr->_closeUpType));
221 return true;
222 } else
223 list = &(_closeDataPtr->_subCloseUps);
224
225 CloseDataList::const_iterator closePtr;
226 for (closePtr = list->begin(); closePtr != list->end(); ++closePtr) {
227 Common::Rect objRect;
228 objRect = _utils->rectScale(closePtr->_x1, closePtr->_y1, closePtr->_x2, closePtr->_y2);
229 if (objRect.contains(pos) && (closePtr->_closeUpType < 0)) {
230 _conditions->inclElement(abs(closePtr->_closeUpType));
231 return true;
232 }
233 }
234
235 return false;
236 }
237
doActions(const ActionList & actionList)238 void LabEngine::doActions(const ActionList &actionList) {
239 ActionList::const_iterator action;
240 for (action = actionList.begin(); action != actionList.end(); ++action) {
241 updateEvents();
242 if (_quitLab || shouldQuit())
243 return;
244
245 switch (action->_actionType) {
246 case kActionPlaySound:
247 _music->loadSoundEffect(action->_messages[0], false, true);
248 break;
249
250 case kActionPlaySoundNoWait: // only used in scene 7 (street, when teleporting to the surreal maze)
251 _music->loadSoundEffect(action->_messages[0], false, false);
252 break;
253
254 case kActionPlaySoundLooping:
255 _music->loadSoundEffect(action->_messages[0], true, false);
256 break;
257
258 case kActionShowDiff:
259 _graphics->readPict(action->_messages[0], true);
260 break;
261
262 case kActionShowDiffLooping: // used in scene 44 (heart of the labyrinth, minotaur)
263 _graphics->readPict(action->_messages[0], false);
264 break;
265
266 case kActionLoadDiff:
267 if (!action->_messages[0].empty())
268 // Puts a file into memory
269 _graphics->loadPict(action->_messages[0]);
270 break;
271
272 case kActionLoadBitmap:
273 error("Unused opcode kActionLoadBitmap has been called");
274
275 case kActionShowBitmap:
276 error("Unused opcode kActionShowBitmap has been called");
277
278 case kActionTransition:
279 _graphics->doTransition((TransitionType)action->_param1, action->_messages[0].c_str());
280 break;
281
282 case kActionNoUpdate:
283 _noUpdateDiff = true;
284 _anim->_doBlack = false;
285 break;
286
287 case kActionForceUpdate:
288 _curFileName = " ";
289 break;
290
291 case kActionShowCurPict: {
292 Common::String test = getPictName(true);
293
294 if (test != _curFileName) {
295 _curFileName = test;
296 _graphics->readPict(_curFileName);
297 }
298 }
299 break;
300
301 case kActionSetElement:
302 _conditions->inclElement(action->_param1);
303 break;
304
305 case kActionUnsetElement:
306 _conditions->exclElement(action->_param1);
307 break;
308
309 case kActionShowMessage:
310 if (_graphics->_longWinInFront)
311 _graphics->longDrawMessage(action->_messages[0], true);
312 else
313 _graphics->drawMessage(action->_messages[0], true);
314 break;
315
316 case kActionCShowMessage:
317 if (!_closeDataPtr)
318 _graphics->drawMessage(action->_messages[0], true);
319 break;
320
321 case kActionShowMessages:
322 _graphics->drawMessage(action->_messages[_utils->getRandom(action->_param1)], true);
323 break;
324
325 case kActionChangeRoom:
326 if (action->_param1 & 0x8000) {
327 // This is a Wyrmkeep Windows trial version, thus stop at this
328 // point, since we can't check for game payment status
329 _graphics->readPict(getPictName(true));
330 GUI::MessageDialog trialMessage(_("This is the end of the trial version. You can play the full game using the original interpreter from Wyrmkeep"));
331 trialMessage.runModal();
332 break;
333 }
334
335 _music->checkRoomMusic(_roomNum, action->_param1);
336 _roomNum = action->_param1;
337 _direction = action->_param2 - 1;
338 _closeDataPtr = nullptr;
339 _anim->_doBlack = true;
340 break;
341
342 case kActionSetCloseup: {
343 Common::Point curPos = Common::Point(_utils->scaleX(action->_param1), _utils->scaleY(action->_param2));
344 const CloseData *tmpClosePtr = getObject(curPos, _closeDataPtr);
345
346 if (tmpClosePtr)
347 _closeDataPtr = tmpClosePtr;
348 }
349 break;
350
351 case kActionMainView:
352 _closeDataPtr = nullptr;
353 break;
354
355 case kActionSubInv:
356 if (_inventory[action->_param1]._quantity)
357 (_inventory[action->_param1]._quantity)--;
358
359 if (_inventory[action->_param1]._quantity == 0)
360 _conditions->exclElement(action->_param1);
361
362 break;
363
364 case kActionAddInv:
365 (_inventory[action->_param1]._quantity) += action->_param2;
366 _conditions->inclElement(action->_param1);
367 break;
368
369 case kActionShowDir:
370 _graphics->setActionMessage(false);
371 break;
372
373 case kActionWaitSecs: {
374 uint32 targetMillis = _system->getMillis() + action->_param1 * 1000;
375
376 _graphics->screenUpdate();
377
378 while (_system->getMillis() < targetMillis) {
379 updateEvents();
380 if (_quitLab || shouldQuit())
381 return;
382 _anim->diffNextFrame();
383 }
384 }
385 break;
386
387 case kActionStopMusic: // used in scene 44 (heart of the labyrinth, minotaur)
388 _music->freeMusic();
389 break;
390
391 case kActionStartMusic: // unused
392 error("Unused opcode kActionStartMusic has been called");
393 break;
394
395 case kActionChangeMusic: // used in scene 46 (museum exhibit, for the alarm)
396 _music->changeMusic(action->_messages[0], true, false);
397 break;
398
399 case kActionResetMusic: // used in scene 45 (sheriff's office, after museum)
400 _music->resetMusic(true);
401 break;
402
403 case kActionFillMusic:
404 error("Unused opcode kActionFillMusic has been called");
405 break;
406
407 case kActionWaitSound: // used in scene 44 (heart of the labyrinth / ending)
408 while (_music->isSoundEffectActive()) {
409 updateEvents();
410 if (_quitLab || shouldQuit())
411 return;
412 _anim->diffNextFrame();
413 waitTOF();
414 }
415 break;
416
417 case kActionClearSound:
418 _music->stopSoundEffect();
419 break;
420
421 case kActionWinMusic: // used in scene 44 (heart of the labyrinth / ending)
422 _music->freeMusic();
423 _music->changeMusic("Music:WinGame", false, false);
424 break;
425
426 case kActionWinGame: // used in scene 44 (heart of the labyrinth / ending)
427 _quitLab = true;
428 showLab2Teaser();
429 break;
430
431 case kActionLostGame:
432 error("Unused opcode kActionLostGame has been called");
433
434 case kActionResetBuffer:
435 _graphics->freePict();
436 break;
437
438 case kActionSpecialCmd:
439 if (action->_param1 == 0)
440 _anim->_doBlack = true;
441 else if (action->_param1 == 1)
442 _anim->_doBlack = (_closeDataPtr == nullptr);
443 else if (action->_param1 == 2)
444 _anim->_doBlack = (_closeDataPtr != nullptr);
445 else if (action->_param1 == 5) {
446 // inverse the palette
447 for (int idx = (8 * 3); idx < (255 * 3); idx++)
448 _anim->_diffPalette[idx] = 255 - _anim->_diffPalette[idx];
449
450 waitTOF();
451 _graphics->setPalette(_anim->_diffPalette, 256);
452 waitTOF();
453 waitTOF();
454 } else if (action->_param1 == 4) {
455 // white the palette
456 _graphics->whiteScreen();
457 waitTOF();
458 waitTOF();
459 } else if (action->_param1 == 6) {
460 // Restore the palette
461 waitTOF();
462 _graphics->setPalette(_anim->_diffPalette, 256);
463 waitTOF();
464 waitTOF();
465 } else if (action->_param1 == 7) {
466 // Quick pause
467 waitTOF();
468 waitTOF();
469 waitTOF();
470 }
471
472 break;
473
474 default:
475 break;
476 }
477 }
478
479 _music->stopSoundEffect();
480 }
481
doActionRuleSub(int16 action,int16 roomNum,const CloseData * closePtr,bool allowDefaults)482 bool LabEngine::doActionRuleSub(int16 action, int16 roomNum, const CloseData *closePtr, bool allowDefaults) {
483 action++;
484
485 if (closePtr) {
486 RuleList *rules = &(_rooms[_roomNum]._rules);
487
488 if (rules->empty() && (roomNum == 0)) {
489 _resource->readViews(roomNum);
490 rules = &(_rooms[roomNum]._rules);
491 }
492
493 for (RuleList::iterator rule = rules->begin(); rule != rules->end(); ++rule) {
494 if ((rule->_ruleType == kRuleTypeAction) &&
495 ((rule->_param1 == action) || ((rule->_param1 == 0) && allowDefaults))) {
496 if (((rule->_param2 == closePtr->_closeUpType) ||
497 ((rule->_param2 == 0) && allowDefaults)) ||
498 ((action == 1) && (rule->_param2 == -closePtr->_closeUpType))) {
499 if (checkConditions(rule->_condition)) {
500 doActions(rule->_actionList);
501 return true;
502 }
503 }
504 }
505 }
506 }
507
508 return false;
509 }
510
doActionRule(Common::Point pos,int16 action,int16 roomNum)511 bool LabEngine::doActionRule(Common::Point pos, int16 action, int16 roomNum) {
512 if (roomNum)
513 _newFileName = NOFILE;
514 else
515 _newFileName = _curFileName;
516
517 const CloseData *curClosePtr = getObject(pos, _closeDataPtr);
518
519 if (doActionRuleSub(action, roomNum, curClosePtr, false))
520 return true;
521 else if (doActionRuleSub(action, roomNum, _closeDataPtr, false))
522 return true;
523 else if (doActionRuleSub(action, roomNum, curClosePtr, true))
524 return true;
525 else if (doActionRuleSub(action, roomNum, _closeDataPtr, true))
526 return true;
527
528 return false;
529 }
530
doOperateRuleSub(int16 itemNum,int16 roomNum,const CloseData * closePtr,bool allowDefaults)531 bool LabEngine::doOperateRuleSub(int16 itemNum, int16 roomNum, const CloseData *closePtr, bool allowDefaults) {
532 if (closePtr)
533 if (closePtr->_closeUpType > 0) {
534 RuleList *rules = &(_rooms[roomNum]._rules);
535
536 if (rules->empty() && (roomNum == 0)) {
537 _resource->readViews(roomNum);
538 rules = &(_rooms[roomNum]._rules);
539 }
540
541 for (RuleList::iterator rule = rules->begin(); rule != rules->end(); ++rule) {
542 if ((rule->_ruleType == kRuleTypeOperate) &&
543 ((rule->_param1 == itemNum) || ((rule->_param1 == 0) && allowDefaults)) &&
544 ((rule->_param2 == closePtr->_closeUpType) || ((rule->_param2 == 0) && allowDefaults))) {
545 if (checkConditions(rule->_condition)) {
546 doActions(rule->_actionList);
547 return true;
548 }
549 }
550 }
551 }
552
553 return false;
554 }
555
doOperateRule(Common::Point pos,int16 ItemNum)556 bool LabEngine::doOperateRule(Common::Point pos, int16 ItemNum) {
557 _newFileName = NOFILE;
558 const CloseData *closePtr = getObject(pos, _closeDataPtr);
559
560 if (doOperateRuleSub(ItemNum, _roomNum, closePtr, false))
561 return true;
562 else if (doOperateRuleSub(ItemNum, _roomNum, _closeDataPtr, false))
563 return true;
564 else if (doOperateRuleSub(ItemNum, _roomNum, closePtr, true))
565 return true;
566 else if (doOperateRuleSub(ItemNum, _roomNum, _closeDataPtr, true))
567 return true;
568 else {
569 _newFileName = _curFileName;
570
571 if (doOperateRuleSub(ItemNum, 0, closePtr, false))
572 return true;
573 else if (doOperateRuleSub(ItemNum, 0, _closeDataPtr, false))
574 return true;
575 else if (doOperateRuleSub(ItemNum, 0, closePtr, true))
576 return true;
577 else if (doOperateRuleSub(ItemNum, 0, _closeDataPtr, true))
578 return true;
579 }
580
581 return false;
582 }
583
doGoForward()584 bool LabEngine::doGoForward() {
585 RuleList &rules = _rooms[_roomNum]._rules;
586
587 for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
588 if ((rule->_ruleType == kRuleTypeGoForward) && (rule->_param1 == (_direction + 1))) {
589 if (checkConditions(rule->_condition)) {
590 doActions(rule->_actionList);
591 return true;
592 }
593 }
594 }
595
596 return false;
597 }
598
doTurn(uint16 from,uint16 to)599 bool LabEngine::doTurn(uint16 from, uint16 to) {
600 from++;
601 to++;
602
603 RuleList &rules = _rooms[_roomNum]._rules;
604
605 for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
606 if ((rule->_ruleType == kRuleTypeTurn) ||
607 ((rule->_ruleType == kRuleTypeTurnFromTo) &&
608 (rule->_param1 == from) && (rule->_param2 == to))) {
609 if (checkConditions(rule->_condition)) {
610 doActions(rule->_actionList);
611 return true;
612 }
613 }
614 }
615
616 return false;
617 }
618
doMainView()619 bool LabEngine::doMainView() {
620 RuleList &rules = _rooms[_roomNum]._rules;
621 for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
622 if (rule->_ruleType == kRuleTypeGoMainView) {
623 if (checkConditions(rule->_condition)) {
624 doActions(rule->_actionList);
625 return true;
626 }
627 }
628 }
629
630 return false;
631 }
632
633 } // End of namespace Lab
634