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