1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11 
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "backends/keymapper/keymapper.h"
24 
25 #include "common/achievements.h"
26 #include "common/debug-channels.h"
27 #include "common/rect.h"
28 
29 #include "engines/util.h"
30 
31 #include "asylum/asylum.h"
32 
33 #include "asylum/resources/actor.h"
34 #include "asylum/resources/encounters.h"
35 #include "asylum/resources/script.h"
36 #include "asylum/resources/special.h"
37 #include "asylum/resources/worldstats.h"
38 
39 #include "asylum/puzzles/puzzles.h"
40 
41 #include "asylum/system/cursor.h"
42 #include "asylum/system/savegame.h"
43 #include "asylum/system/screen.h"
44 #include "asylum/system/speech.h"
45 #include "asylum/system/text.h"
46 
47 #include "asylum/views/scene.h"
48 #include "asylum/views/menu.h"
49 #include "asylum/views/video.h"
50 
51 #include "asylum/respack.h"
52 
53 namespace Asylum {
54 
AsylumEngine(OSystem * system,const ADGameDescription * gd)55 AsylumEngine::AsylumEngine(OSystem *system, const ADGameDescription *gd) : Engine(system), _gameDescription(gd),
56 	_console(NULL), _cursor(NULL), _encounter(NULL), _menu(NULL), _resource(NULL), _savegame(NULL),
57 	_scene(NULL), _screen(NULL), _script(NULL), _special(NULL), _speech(NULL), _sound(NULL), _text(NULL),
58 	_video(NULL), _handler(NULL), _puzzles(NULL) {
59 
60 	// Init data
61 	memset(&_gameFlags, 0, sizeof(_gameFlags));
62 	_introPlayed = false;
63 	_tickOffset = 0;
64 
65 	screenUpdateCount = 0;
66 	lastScreenUpdate  = 0;
67 
68 	// Debug
69 	_delayedSceneIndex = kResourcePackInvalid;
70 	_delayedVideoIndex = -1;
71 	_previousScene = NULL;
72 
73 	// Add default search directories
74 	const Common::FSNode gameDataDir(ConfMan.get("path"));
75 	SearchMan.addSubDirectoryMatching(gameDataDir, "data");
76 	SearchMan.addSubDirectoryMatching(gameDataDir, "data/vids");
77 	SearchMan.addSubDirectoryMatching(gameDataDir, "data/music");
78 
79 	// Initialize random number source
80 	_rnd = new Common::RandomSource("asylum");
81 }
82 
~AsylumEngine()83 AsylumEngine::~AsylumEngine() {
84 	_handler = NULL;
85 
86 	delete _cursor;
87 	delete _scene;
88 	delete _encounter;
89 	delete _puzzles;
90 	delete _savegame;
91 	delete _screen;
92 	delete _script;
93 	delete _special;
94 	delete _speech;
95 	delete _sound;
96 	delete _text;
97 	delete _video;
98 	delete _menu;
99 	delete _resource;
100 
101 	_previousScene = NULL;
102 
103 	delete _rnd;
104 
105 	// Zero passed pointers
106 	_gameDescription = NULL;
107 }
108 
hasFeature(EngineFeature f) const109 bool AsylumEngine::hasFeature(EngineFeature f) const {
110 	return (f == kSupportsReturnToLauncher);
111 }
112 
run()113 Common::Error AsylumEngine::run() {
114 	// Initialize the graphics
115 	initGraphics(640, 480);
116 
117 	// Create debugger. It requires GFX to be initialized
118 	_console   = new Console(this);
119 	setDebugger(_console);
120 
121 	// Create resource manager
122 	_resource  = new ResourceManager();
123 	_resource->setCdNumber(1);
124 
125 	// Create all game classes
126 	_encounter = new Encounter(this);
127 	_cursor    = new Cursor(this);
128 	_puzzles   = new Puzzles(this);
129 	_savegame  = new Savegame(this);
130 	_screen    = new Screen(this);
131 	_script    = new ScriptManager(this);
132 	_sound     = new Sound(this, _mixer);
133 	_special   = new Special(this);
134 	_speech    = new Speech(this);
135 	_text      = new Text(this);
136 	_video     = new VideoPlayer(this, _mixer);
137 
138 	// Create main menu
139 	_menu  = new Menu(this);
140 	_handler = _menu;
141 
142 	// Load config
143 	Config.read();
144 
145 	// Setup mixer
146 	syncSoundSettings();
147 
148 	// Set up achievements system
149 	Common::String gameTarget = ConfMan.getActiveDomainName();
150 	AchMan.setActiveDomain(getMetaEngine()->getAchievementsInfo(gameTarget));
151 
152 	// Send init event to our default event handler
153 	AsylumEvent initEvt(EVENT_ASYLUM_INIT);
154 	if (_handler)
155 		_handler->handleEvent(initEvt);
156 
157 	// Start running event loop
158 	while (!shouldQuit()) {
159 		handleEvents();
160 
161 		_system->delayMillis(10);
162 
163 		_system->updateScreen();
164 
165 		if (_scene)
166 			checkAchievements();
167 	}
168 
169 	return Common::kNoError;
170 }
171 
startGame(ResourcePackId sceneId,StartGameType type)172 void AsylumEngine::startGame(ResourcePackId sceneId, StartGameType type) {
173 	if (!_cursor || !_screen || !_savegame)
174 		error("[AsylumEngine::startGame] Subsystems not initialized properly!");
175 
176 	// Load the default mouse cursor
177 	_cursor->set(MAKE_RESOURCE(kResourcePackSound, 14), 0, kCursorAnimationNone);
178 	_cursor->hide();
179 
180 	// Clear the graphic list
181 	_screen->clearGraphicsInQueue();
182 
183 	// Reset scene (this ensures the current resource pack is closed as in the original)
184 	delete _scene;
185 	_scene = new Scene(this);
186 	_handler = _scene;
187 
188 	// Set the current cd number (necessary for proper SharedSound resource pack initialization)
189 	int32 cdNumber = _resource->getCdNumber();
190 
191 	switch (sceneId) {
192 	default:
193 		_resource->setCdNumber(-1);
194 		break;
195 
196 	case kResourcePackTowerCells:
197 	case kResourcePackInnocentAbandoned:
198 	case kResourcePackCourtyardAndChapel:
199 		_resource->setCdNumber(1);
200 		break;
201 
202 	case kResourcePackCircusOfFools:
203 	case kResourcePackCave:
204 	case kResourcePackMansion:
205 	case kResourcePackLaboratory:
206 	case kResourcePackHive:
207 		_resource->setCdNumber(2);
208 		break;
209 
210 	case kResourcePackMorgueAndCemetery:
211 	case kResourcePackLostVillage:
212 	case kResourcePackMaze:
213 	case kResourcePackGauntlet:
214 	case kResourcePackMorgansLastGame:
215 		_resource->setCdNumber(3);
216 		break;
217 	}
218 
219 	if (_resource->getCdNumber() != cdNumber)
220 		_resource->clearSharedSoundCache();
221 
222 	_resource->clearMusicCache();
223 
224 	switch (type) {
225 	default:
226 		error("[AsylumEngine::startGame] Invalid start game type!");
227 
228 	case kStartGamePlayIntro:
229 		_scene->enter(sceneId);
230 		playIntro();
231 		break;
232 
233 	case kStartGameLoad:
234 		if (_savegame->load()) {
235 			_scene->enterLoad();
236 			updateReverseStereo();
237 			switchEventHandler(_scene);
238 		}
239 		break;
240 
241 	case kStartGameScene:
242 		_scene->enter(sceneId);
243 		break;
244 	}
245 
246 	_cursor->show();
247 }
248 
restart()249 void AsylumEngine::restart() {
250 	if (!_cursor || !_script)
251 		error("[AsylumEngine::restart] Subsystems not initialized properly!");
252 
253 	_cursor->hide();
254 
255 	// Cleanup
256 	memset(&_gameFlags, 0, sizeof(_gameFlags));
257 	delete _scene;
258 	_scene = NULL;
259 	delete _encounter;
260 	_encounter = new Encounter(this);
261 	_script->resetQueue();
262 
263 	_data.setGlobalPoint(Common::Point(-1, -1));
264 
265 	reset();
266 
267 	_introPlayed = false;
268 
269 	_screen->clear();
270 	_sound->playMusic(kResourceNone, 0);
271 
272 	startGame(kResourcePackTowerCells, kStartGamePlayIntro);
273 }
274 
reset()275 void AsylumEngine::reset() {
276 	if (!_menu || !_special || !_puzzles)
277 		error("[AsylumEngine::reset] Subsystems not initialized properly!");
278 
279 	// Set game as started
280 	_menu->setGameStarted();
281 
282 	// Reset puzzles
283 	_puzzles->reset();
284 
285 	// Reset shared data
286 	_data.reset();
287 
288 	// Reset special palette info
289 	_special->reset(true);
290 }
291 
playIntro()292 void AsylumEngine::playIntro() {
293 	if (!_video || !_screen)
294 		error("[AsylumEngine::playIntro] Subsystems not initialized properly!");
295 
296 	updateReverseStereo();
297 
298 	if (!_introPlayed) {
299 		_cursor->hide();
300 		_cursor->setForceHide(true);
301 		if (!Config.showIntro) {
302 			if (_scene->worldstats()->chapter == kChapter1)
303 				_sound->playMusic(MAKE_RESOURCE(kResourcePackMusic, _scene->worldstats()->musicCurrentResourceIndex));
304 		} else {
305 			_sound->playMusic(kResourceNone, 0);
306 
307 			_video->play(1, _menu);
308 
309 			if (_scene->worldstats()->musicCurrentResourceIndex != kMusicStopped)
310 				_sound->playMusic(MAKE_RESOURCE(kResourcePackMusic, _scene->worldstats()->musicCurrentResourceIndex));
311 
312 			_screen->clear();
313 
314 			setGameFlag(kGameFlag4);
315 			setGameFlag(kGameFlag12);
316 
317 			// Play the intro speech: it is played after the intro video over a black background,
318 			// and the game is "locked" until the speech is completed.
319 			ResourceId introSpeech = MAKE_RESOURCE(kResourcePackSound, 7);
320 			_sound->playSound(introSpeech);
321 
322 			int8 skip = 0;
323 			do {
324 				// Poll events (this ensures we don't freeze the screen)
325 				Common::Event ev;
326 				while (_eventMan->pollEvent(ev)) {
327 					switch (ev.type) {
328 					case Common::EVENT_LBUTTONDOWN:
329 					case Common::EVENT_KEYDOWN:
330 						skip = true;
331 						break;
332 					default:
333 						break;
334 					}
335 				}
336 
337 				_system->updateScreen();
338 				_system->delayMillis(100);
339 
340 			} while (_sound->isPlaying(introSpeech) && !skip);
341 
342 			if (_sound->isPlaying(introSpeech)) {
343 				_sound->stop(introSpeech);
344 			}
345 		}
346 		_cursor->setForceHide(false);
347 		_introPlayed = true;
348 	}
349 
350 	_cursor->show();
351 
352 	_savegame->loadMoviesViewed();
353 
354 	// Switch to scene event handling
355 	switchEventHandler(_scene);
356 }
357 
handleEvents()358 void AsylumEngine::handleEvents() {
359 	if (!_console || !_video || !_screen || !_sound || !_menu || !_cursor)
360 		error("[AsylumEngine::handleEvents] Subsystems not initialized properly!");
361 
362 	// Show the debugger if required
363 	_console->onFrame();
364 
365 	AsylumEvent ev;
366 	Common::Keymapper *const keymapper = _eventMan->getKeymapper();
367 
368 	while (_eventMan->pollEvent(ev)) {
369 		keymapper->setEnabled(_handler == _scene || (_handler == _menu && !_menu->isEditingSavegameName()));
370 		switch (ev.type) {
371 		default:
372 			break;
373 
374 		case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
375 			// Handle custom actions
376 			if (_handler)
377 				_handler->handleEvent(ev);
378 			break;
379 
380 		case Common::EVENT_KEYDOWN:
381 			if ((ev.kbd.flags & Common::KBD_CTRL) && ev.kbd.keycode == Common::KEYCODE_d) {
382 				_console->attach();
383 				break;
384 			}
385 
386 			// Handle key events
387 			if (_handler)
388 				_handler->handleEvent(ev);
389 			break;
390 
391 		case Common::EVENT_KEYUP:
392 			// Handle key events
393 			if (_handler)
394 				_handler->handleEvent(ev);
395 			break;
396 
397 		case Common::EVENT_MOUSEMOVE:
398 		case Common::EVENT_LBUTTONDOWN:
399 		case Common::EVENT_LBUTTONUP:
400 		case Common::EVENT_RBUTTONDOWN:
401 		case Common::EVENT_RBUTTONUP:
402 		case Common::EVENT_MBUTTONUP:
403 		case Common::EVENT_MBUTTONDOWN:
404 			// Handle mouse events
405 			_cursor->setState(ev);
406 
407 			if (_handler)
408 				_handler->handleEvent(ev);
409 			break;
410 
411 		case Common::EVENT_QUIT:
412 			quitGame();
413 			break;
414 
415 		// TODO handle cases where we receive a midi or music event
416 		}
417 	}
418 
419 	// Animate cursor
420 	_cursor->animate();
421 
422 	// Send update event to our event handler
423 	AsylumEvent updateEvt = AsylumEvent(EVENT_ASYLUM_UPDATE);
424 	if (_handler)
425 		_handler->handleEvent(updateEvt);
426 
427 	// Handle debug events
428 	processDelayedEvents();
429 }
430 
processDelayedEvents()431 void AsylumEngine::processDelayedEvents() {
432 	if (!_video || !_sound || !_menu || !_script || !_screen)
433 		error("[AsylumEngine::processDelayedEvents] Subsystems not initialized properly!");
434 
435 	// check for a delayed scene change
436 	if (_delayedSceneIndex != kResourcePackInvalid && isGameFlagNotSet(kGameFlagScriptProcessing)) {
437 		ResourcePackId sceneIndex = _delayedSceneIndex;
438 		_delayedSceneIndex = kResourcePackInvalid;
439 
440 		// Reset actor and script queue
441 		_script->resetQueue();
442 		_script->reset();
443 		if (_scene)
444 			_scene->getActor(0)->changeStatus(kActorStatusDisabled);
445 
446 		// Fade screen to black
447 		_screen->paletteFade(0, 75, 8);
448 		_screen->clear();
449 
450 		// Stop all sounds & music
451 		_sound->stopMusic();
452 		_sound->stopAll();
453 
454 		// Switch the scene
455 		switchScene(sceneIndex);
456 	}
457 
458 	// Check for delayed video
459 	if (_delayedVideoIndex != -1 && isGameFlagNotSet(kGameFlagScriptProcessing)) {
460 		uint32 index = (uint32)_delayedVideoIndex;
461 		_delayedVideoIndex = -1;
462 
463 		_video->play(index, _handler);
464 	}
465 }
466 
467 //////////////////////////////////////////////////////////////////////////
468 // Message handlers
469 //////////////////////////////////////////////////////////////////////////
switchEventHandler(EventHandler * handler)470 void AsylumEngine::switchEventHandler(EventHandler *handler) {
471 	if (handler == NULL)
472 		warning("[AsylumEngine::switchMessageHandler] NULL handler parameter (shouldn't happen outside of debug commands)!");
473 
474 	// De-init previous handler
475 	if (_handler != NULL) {
476 		AsylumEvent deinit(EVENT_ASYLUM_DEINIT);
477 		_handler->handleEvent(deinit);
478 	}
479 
480 	//////////////////////////////////////////////////////////////////////////
481 	// DEBUG - If a previous scene is found, replace the current scene by this one
482 	if (handler == _scene) {
483 		if (_previousScene) {
484 			delete _scene;
485 			_scene = _previousScene;
486 			handler = _scene;
487 			_previousScene = NULL;
488 		}
489 	}
490 	//////////////////////////////////////////////////////////////////////////
491 
492 	// replace message handler
493 	_handler = handler;
494 
495 	// Init new handler
496 	AsylumEvent init(EVENT_ASYLUM_INIT);
497 	if (_handler)
498 		_handler->handleEvent(init);
499 }
500 
notify(AsylumEventType type,int32 param1,int32 param2)501 void AsylumEngine::notify(AsylumEventType type, int32 param1, int32 param2) {
502 	if (_handler == NULL)
503 		error("[AsylumEngine::notify] Invalid handler parameter (cannot be NULL)!");
504 
505 	AsylumEvent evt(type, param1, param2);
506 	_handler->handleEvent(evt);
507 }
508 
updateReverseStereo()509 void AsylumEngine::updateReverseStereo() {
510 	if (_scene && _scene->worldstats())
511 		_scene->worldstats()->reverseStereo = Config.reverseStereo;
512 }
513 
saveLoadWithSerializer(Common::Serializer & s)514 void AsylumEngine::saveLoadWithSerializer(Common::Serializer &s) {
515 	if (!_script)
516 		error("[AsylumEngine::saveLoadWithSerializer] Subsystems not initialized properly!");
517 
518 	// Game flags
519 	for (uint32 i = 0; i < ARRAYSIZE(_gameFlags); i++)
520 		s.syncAsUint32LE(_gameFlags[i]);
521 
522 	// The original has the script data in the middle of other shared data,
523 	// so to be compatible with original savegames, we want to save it in
524 	// the proper order
525 	_data.saveLoadAmbientSoundData(s);
526 
527 	// Original skips two elements
528 	// (original has one unused, one used for debugging screen update counts)
529 	s.skip(8);
530 
531 	// Script queue
532 	_script->saveQueue(s);
533 
534 	// Shared data (the rest of it)
535 	_data.saveLoadWithSerializer(s);
536 }
537 
538 //////////////////////////////////////////////////////////////////////////
539 // Game flags
540 //////////////////////////////////////////////////////////////////////////
setGameFlag(GameFlag flag)541 void AsylumEngine::setGameFlag(GameFlag flag) {
542 	_gameFlags[flag / 32] |= 1 << (flag % 32);
543 }
544 
clearGameFlag(GameFlag flag)545 void AsylumEngine::clearGameFlag(GameFlag flag) {
546 	_gameFlags[flag / 32] &= ~(1 << (flag % 32));
547 }
548 
toggleGameFlag(GameFlag flag)549 void AsylumEngine::toggleGameFlag(GameFlag flag) {
550 	_gameFlags[flag / 32] ^= 1 << (flag % 32);
551 }
552 
isGameFlagSet(GameFlag flag) const553 bool AsylumEngine::isGameFlagSet(GameFlag flag) const {
554 	return ((1 << (flag % 32)) & _gameFlags[flag / 32]) >> (flag % 32) != 0;
555 }
556 
isGameFlagNotSet(GameFlag flag) const557 bool AsylumEngine::isGameFlagNotSet(GameFlag flag) const {
558 	return ((1 << (flag % 32)) & _gameFlags[flag / 32]) >> (flag % 32) == 0;
559 }
560 
areGameFlagsSet(uint from,uint to) const561 bool AsylumEngine::areGameFlagsSet(uint from, uint to) const {
562 	while (from <= to)
563 		if (isGameFlagNotSet((GameFlag)from++))
564 			return false;
565 
566 	return true;
567 }
568 
569 //////////////////////////////////////////////////////////////////////////
570 // Steam achievements
571 //////////////////////////////////////////////////////////////////////////
unlockAchievement(const Common::String & id)572 void AsylumEngine::unlockAchievement(const Common::String &id) {
573 	AchMan.setAchievement(id);
574 }
575 
checkAchievements()576 void AsylumEngine::checkAchievements() {
577 	switch (_scene->worldstats()->chapter) {
578 	default:
579 		return;
580 
581 	case kChapter2:
582 		if (isGameFlagSet(kGameFlag128) && !isGameFlagSet(kGameFlag3189)) {
583 			unlockAchievement("ASYLUM_HIDE_AND_SEEK");
584 			setGameFlag(kGameFlag3189);
585 		}
586 		break;
587 
588 	case kChapter3:
589 		if (isGameFlagSet(kGameFlag86) && !isGameFlagSet(kGameFlag3386))
590 			setGameFlag(kGameFlag3386);
591 		if (isGameFlagSet(kGameFlag87) && !isGameFlagSet(kGameFlag3387))
592 			setGameFlag(kGameFlag3387);
593 		if (isGameFlagSet(kGameFlag88) && !isGameFlagSet(kGameFlag3388))
594 			setGameFlag(kGameFlag3388);
595 
596 		if (areGameFlagsSet(kGameFlag3386, kGameFlag3388) && !isGameFlagSet(kGameFlag3389)) {
597 			unlockAchievement("ASYLUM_DANCE");
598 			setGameFlag(kGameFlag3389);
599 		}
600 		break;
601 
602 	case kChapter5:
603 		if (!isGameFlagSet(kGameFlag3351) && areGameFlagsSet(kGameFlag284, kGameFlag289)) {
604 			unlockAchievement("ASYLUM_PASSWORD");
605 			setGameFlag(kGameFlag3351);
606 		}
607 		break;
608 
609 	case kChapter6:
610 		if (!isGameFlagSet(kGameFlag3754) && isGameFlagSet(kGameFlagSolveHiveMachine) && !isGameFlagSet(kGameFlag3755)) {
611 			unlockAchievement("ASYLUM_MELODY");
612 			setGameFlag(kGameFlag3755);
613 		}
614 		break;
615 
616 	case kChapter8:
617 		if (!isGameFlagSet(kGameFlag3842) && areGameFlagsSet(kGameFlag3810, kGameFlag3823)) {
618 			unlockAchievement("ASYLUM_SOCIAL");
619 			setGameFlag(kGameFlag3842);
620 		}
621 
622 		if (!isGameFlagSet(kGameFlag3843) && isGameFlagSet(kGameFlag899)) {
623 			unlockAchievement("ASYLUM_SORT");
624 			setGameFlag(kGameFlag3843);
625 		}
626 		break;
627 	}
628 }
629 
630 //////////////////////////////////////////////////////////////////////////
631 // Misc
632 //////////////////////////////////////////////////////////////////////////
rectContains(const int16 (* rectPtr)[4],const Common::Point & p) const633 bool AsylumEngine::rectContains(const int16 (*rectPtr)[4], const Common::Point &p) const {
634 	return ((*rectPtr)[0] <= p.x) && (p.x < (*rectPtr)[2]) && ((*rectPtr)[1] <= p.y) && (p.y < (*rectPtr)[3]);
635 }
636 
637 } // namespace Asylum
638