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