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 * Copyright 2020 Google
22 *
23 */
24
25 #include "common/debug-channels.h"
26 #include "common/error.h"
27 #include "common/events.h"
28 #include "common/ini-file.h"
29 #include "common/stream.h"
30 #include "common/system.h"
31 #include "common/file.h"
32 #include "common/keyboard.h"
33 #include "common/macresman.h"
34 #include "common/util.h"
35 #include "common/zlib.h"
36 #include "common/config-manager.h"
37
38 #include "engines/advancedDetector.h"
39 #include "engines/util.h"
40
41 #include "graphics/surface.h"
42
43 #include "video/smk_decoder.h"
44
45 #include "hadesch/pod_file.h"
46 #include "hadesch/hadesch.h"
47 #include "hadesch/video.h"
48 #include "hadesch/pod_image.h"
49
50 #include "graphics/palette.h"
51 #include "common/memstream.h"
52 #include "common/winexe_pe.h"
53 #include "common/substream.h"
54 #include "common/md5.h"
55 #include "graphics/wincursor.h"
56 #include "graphics/cursorman.h"
57 #include "graphics/maccursor.h"
58
59 namespace Hadesch {
60
61 HadeschEngine *g_vm;
62
63 static const uint32 cursorids[] = {
64 127, 128, 129, // normal and active
65 125, 134, 135, 136, 137, 138, 139, 140, 141, 142, // waiting
66 130, // up arrow
67 131, // left arrow
68 132, // down arrow
69 133, // right arrow
70 // 143:cross, 146:cross
71 146
72 };
73
HadeschEngine(OSystem * system,const ADGameDescription * desc)74 HadeschEngine::HadeschEngine(OSystem *system, const ADGameDescription *desc)
75 : Engine(system), _desc(desc), _rnd("hadesch"), _cheatsEnabled(false) {
76
77 g_vm = this;
78 _sceneStartTime = _system->getMillis();
79 _currentTime = 0;
80 _isQuitting = false;
81 _isRestoring = false;
82
83 _subtitleID = 0;
84
85 debug("HadeschEngine::ctor");
86 }
87
~HadeschEngine()88 HadeschEngine::~HadeschEngine() {
89 debug("HadeschEngine::dtor");
90 for (unsigned i = 0; i < _winCursors.size(); i++) {
91 delete _winCursors[i];
92 _winCursors[i] = nullptr;
93 }
94 for (unsigned i = 0; i < _macCursors.size(); i++) {
95 delete _macCursors[i];
96 _macCursors[i] = nullptr;
97 }
98
99 #ifdef USE_TRANSLATION
100 delete _transMan;
101 #endif
102 }
103
setVideoRoom(Common::SharedPtr<VideoRoom> room,Common::SharedPtr<Handler> handler,RoomId roomId)104 void HadeschEngine::setVideoRoom(Common::SharedPtr<VideoRoom> room,
105 Common::SharedPtr<Handler> handler,
106 RoomId roomId) {
107 assert(!_isInOptions || _isRestoring);
108 _sceneVideoRoom = room;
109 _sceneHandler = handler;
110 _currentTime = 0;
111 _sceneStartTime = _system->getMillis();
112 if (!_isRestoring) {
113 _persistent._previousRoomId = _persistent._currentRoomId;
114 _persistent._currentRoomId = roomId;
115 }
116 _sceneTimers.clear();
117 }
118
getMousePos()119 Common::Point HadeschEngine::getMousePos() {
120 return _mousePos;
121 }
122
handleEvent(EventHandlerWrapper event)123 void HadeschEngine::handleEvent(EventHandlerWrapper event) {
124 event();
125 }
126
newGame()127 void HadeschEngine::newGame() {
128 _persistent = Persistent();
129 _persistent._quest = kCreteQuest;
130 moveToRoom(kWallOfFameRoom);
131 }
132
133 #if defined(USE_ZLIB)
134
135 struct WiseFile {
136 uint start;
137 uint end;
138 uint uncompressedLength;
139 };
140
141 // Some variants use wise installer. Wise installer use raw zlib compressed files
142 // Rather than parsing out the wise structures, we just store offsets for
143 // the files we care about
144 static const struct {
145 const char *md5;
146 uint setupLength;
147 WiseFile _wdPod;
148 WiseFile _hadeschExe;
149 } setups[] = {
150 {
151 // Russian, Windows
152 "9ddf1b0b271426b9d023dbf3edbb1caa",
153 7491209,
154 {0xB8DA2, 0x7246CB, 8691909},
155 {0x4109C, 0xB4628, 1007616}
156 }
157 };
158
readWiseFile(Common::File & setupFile,const struct WiseFile & wiseFile)159 Common::MemoryReadStream *readWiseFile(Common::File &setupFile, const struct WiseFile &wiseFile) {
160 // -4 to skip CRC
161 byte *compressedBuffer = new byte[wiseFile.end - wiseFile.start - 4];
162 byte *uncompressedBuffer = new byte[wiseFile.uncompressedLength];
163 setupFile.seek(wiseFile.start);
164 setupFile.read(compressedBuffer, wiseFile.end - wiseFile.start - 4);
165 if (!Common::inflateZlibHeaderless(uncompressedBuffer, wiseFile.uncompressedLength,
166 compressedBuffer, wiseFile.end - wiseFile.start - 4)) {
167 debug("wise inflate failed");
168 delete[] compressedBuffer;
169 delete[] uncompressedBuffer;
170 return nullptr;
171 }
172
173 delete[] compressedBuffer;
174 return new Common::MemoryReadStream(uncompressedBuffer, wiseFile.uncompressedLength);
175 }
176 #endif
177
loadWindowsCursors(Common::PEResources & exe)178 Common::ErrorCode HadeschEngine::loadWindowsCursors(Common::PEResources &exe) {
179 for (unsigned i = 0; i < ARRAYSIZE(cursorids); i++) {
180 Graphics::WinCursorGroup *group = Graphics::WinCursorGroup::createCursorGroup(&exe, cursorids[i]);
181
182 if (!group) {
183 debug("Cannot find cursor group %d", cursorids[i]);
184 return Common::kUnsupportedGameidError;
185 }
186
187 _cursors.push_back(group->cursors[0].cursor);
188 _winCursors.push_back(group);
189 }
190
191 return Common::kNoError;
192 }
193
loadCursors()194 Common::ErrorCode HadeschEngine::loadCursors() {
195 debug("HadeschEngine: loading cursors");
196
197 {
198 Common::PEResources exe = Common::PEResources();
199 if (exe.loadFromEXE("HADESCH.EXE")) {
200 return loadWindowsCursors(exe);
201 }
202 }
203
204 const char *const macPaths[] = {
205 "Hades_-_Copy_To_Hard_Drive/Hades_Challenge/Hades_Challenge_PPC",
206 "Hades - Copy To Hard Drive/Hades Challenge/Hades Challenge PPC"
207 };
208
209 for (uint j = 0; j < ARRAYSIZE(macPaths); ++j) {
210 Common::MacResManager resMan = Common::MacResManager();
211 if (!resMan.open(macPaths[j])) {
212 continue;
213 }
214
215 for (unsigned i = 0; i < ARRAYSIZE(cursorids); i++) {
216 Common::SeekableReadStream *stream = resMan.getResource(MKTAG('c','r','s','r'), cursorids[i]);
217 if (!stream) {
218 debug("Couldn't load cursor %d", cursorids[i]);
219 return Common::kUnsupportedGameidError;
220 }
221 Graphics::MacCursor *macCursor = new Graphics::MacCursor();
222 macCursor->readFromStream(*stream);
223 _cursors.push_back(macCursor);
224 delete stream;
225 _macCursors.push_back(macCursor);
226 }
227 return Common::kNoError;
228 }
229
230 #if defined(USE_ZLIB)
231 Common::File setupFile;
232 if (setupFile.open("Setup.exe")) {
233 uint len = setupFile.size();
234 Common::String md5 = Common::computeStreamMD5AsString(setupFile, len);
235 for (uint chosenSetup = 0; chosenSetup < ARRAYSIZE(setups); chosenSetup++) {
236 if (setups[chosenSetup].setupLength == len && setups[chosenSetup].md5 == md5) {
237 Common::MemoryReadStream *wdPod = readWiseFile(setupFile, setups[0]._wdPod);
238 if (!wdPod) {
239 debug("wd.pod inflate failed");
240 return Common::kUnsupportedGameidError;
241 }
242 _wdPodFile = Common::SharedPtr<PodFile>(new PodFile("WD.POD"));
243 _wdPodFile->openStore(Common::SharedPtr<Common::SeekableReadStream>(wdPod));
244
245 Common::MemoryReadStream *hadeschExe = readWiseFile(setupFile, setups[0]._hadeschExe);
246 if (!hadeschExe) {
247 debug("hadesch.exe inflate failed");
248 return Common::kUnsupportedGameidError;
249 }
250
251 Common::PEResources exe = Common::PEResources();
252 if (exe.loadFromEXE(hadeschExe)) {
253 return loadWindowsCursors(exe);
254 }
255 }
256 }
257 }
258 #endif
259
260 debug("Cannot open hadesch.exe");
261 return Common::kUnsupportedGameidError;
262 }
263
264 static const char *roomCheats[] = {
265 "",
266 "in",
267 "mo",
268 "ht",
269 "se",
270 "at",
271 "mi",
272 "me",
273 "ar",
274 "tr",
275 "ca",
276 "pr",
277 "th",
278 "cp",
279 "mp",
280 "dw",
281 "mn",
282 "vt",
283 "nr",
284 "htr",
285 "ff",
286 "mm",
287 "hc",
288 "cr",
289 "op"
290 };
291
292 static const char *itemCheats[] = {
293 "",
294 "",
295 "straw",
296 "stone",
297 "bricks",
298 "message",
299 "key",
300 "decree",
301 "wood",
302 "statue1",
303 "statue2",
304 "statue3",
305 "statue4",
306 "statue",
307 "coin",
308 "potion",
309 "shield",
310 "sword",
311 "bag",
312 "helmet",
313 "sandals",
314 "torch"
315 };
316
handleGenericCheat(const Common::String & cheat)317 bool HadeschEngine::handleGenericCheat(const Common::String &cheat) {
318 if (cheat == "cheatsoff") {
319 _cheatsEnabled = false;
320 return true;
321 }
322
323 for (int i = kIntroRoom; i < kOptionsRoom; i++)
324 if (cheat == roomCheats[i]) {
325 moveToRoom((RoomId) i);
326 getVideoRoom()->disableMouse();
327 return true;
328 }
329
330 if (cheat == "commandson") {
331 getVideoRoom()->enableMouse();
332 return true;
333 }
334
335 if (cheat == "commandsoff") {
336 getVideoRoom()->disableMouse();
337 return true;
338 }
339
340 if (cheat == "cretequest") {
341 _persistent._quest = kCreteQuest;
342 _persistent._roomVisited[kWallOfFameRoom] = true;
343 return true;
344 }
345
346 if (cheat == "troyquest") {
347 _persistent._quest = kTroyQuest;
348 _persistent._roomVisited[kWallOfFameRoom] = true;
349 _persistent._roomVisited[kMinotaurPuzzle] = true;
350
351 _persistent._powerLevel[0] = MAX(_persistent._powerLevel[0], 1);
352 return true;
353 }
354
355 if (cheat == "medusaquest") {
356 _persistent._quest = kMedusaQuest;
357 _persistent._roomVisited[kWallOfFameRoom] = true;
358 _persistent._roomVisited[kMinotaurPuzzle] = true;
359 _persistent._roomVisited[kTrojanHorsePuzzle] = true;
360
361 for (int i = 0; i < 2; i++)
362 _persistent._powerLevel[i] = MAX(_persistent._powerLevel[i], 1);
363 return true;
364 }
365
366 if (cheat == "rescuephilquest") {
367 _persistent._quest = kRescuePhilQuest;
368 _persistent._roomVisited[kWallOfFameRoom] = true;
369 _persistent._roomVisited[kMedusaPuzzle] = true;
370 _persistent._roomVisited[kTrojanHorsePuzzle] = true;
371 _persistent._roomVisited[kMinotaurPuzzle] = true;
372
373 for (int i = 0; i < 3; i++)
374 _persistent._powerLevel[i] = MAX(_persistent._powerLevel[i], 1);
375
376 return true;
377 }
378
379 // TODO: "noquest", "op", "click*", "memory", "showhotspots", "hidehotspots", "showcursor",
380 // giveall, takeall, givekeyanddecree, takekeyanddecree
381
382 for (int i = kStraw; i <= kTorch; i++)
383 if (cheat == Common::String("give") + itemCheats[i]) {
384 _heroBelt->placeToInventory((InventoryItem)i);
385 return true;
386 }
387
388 for (int i = kStraw; i <= kTorch; i++)
389 if (cheat == Common::String("take") + itemCheats[i]) {
390 _heroBelt->removeFromInventory((InventoryItem)i);
391 return true;
392 }
393
394 if (cheat == "hero") {
395 _persistent._gender = kMale;
396 return true;
397 }
398
399 if (cheat == "heroine") {
400 _persistent._gender = kFemale;
401 return true;
402 }
403
404 if (cheat.matchString("powerstrength#")) {
405 _persistent._powerLevel[kPowerStrength] = cheat.substr(13).asUint64();
406 return true;
407 }
408
409 if (cheat.matchString("powerstealth#")) {
410 _persistent._powerLevel[kPowerStealth] = cheat.substr(12).asUint64();
411 return true;
412 }
413
414 if (cheat.matchString("powerwisdom#")) {
415 _persistent._powerLevel[kPowerWisdom] = cheat.substr(11).asUint64();
416 return true;
417 }
418
419 if (cheat.matchString("powerall#")) {
420 int val = cheat.substr(8).asUint64();
421 _persistent._powerLevel[kPowerWisdom] = val;
422 _persistent._powerLevel[kPowerStealth] = val;
423 _persistent._powerLevel[kPowerStrength] = val;
424 return true;
425 }
426
427 return false;
428 }
429
resetOptionsRoom()430 void HadeschEngine::resetOptionsRoom() {
431 _optionsRoom = Common::SharedPtr<VideoRoom>(new VideoRoom("", "", "OPAssets.txt"));
432 }
433
enterOptions()434 void HadeschEngine::enterOptions() {
435 _isInOptions = true;
436 _optionsEnterTime = _system->getMillis();
437 _sceneVideoRoom->pause();
438 resetOptionsRoom();
439 _optionsHandler = makeOptionsHandler();
440 _optionsHandler->prepareRoom();
441 }
442
enterOptionsCredits()443 void HadeschEngine::enterOptionsCredits() {
444 if (_isInOptions) {
445 _sceneStartTime += _system->getMillis() - _optionsEnterTime;
446 }
447 _isInOptions = true;
448 _optionsEnterTime = _system->getMillis();
449 _optionsRoom = Common::SharedPtr<VideoRoom>(new VideoRoom("CREDITS", "CR", ""));
450 _optionsHandler = makeCreditsHandler(true);
451 _optionsHandler->prepareRoom();
452 }
453
exitOptions()454 void HadeschEngine::exitOptions() {
455 _isInOptions = false;
456 _sceneStartTime += _system->getMillis() - _optionsEnterTime;
457 _optionsHandler.reset();
458 _optionsRoom.reset();
459 _sceneVideoRoom->unpause();
460 }
461
translate(const Common::String & str)462 Common::U32String HadeschEngine::translate(const Common::String &str) {
463 #ifdef USE_TRANSLATION
464 return _transMan->getTranslation(str);
465 #else
466 return str.decode();
467 #endif
468 }
469
run()470 Common::Error HadeschEngine::run() {
471 debug("HadeschEngine::run");
472
473 #ifdef USE_TRANSLATION
474 _transMan = new Common::TranslationManager("hadesch_translations.dat");
475 _transMan->setLanguage(TransMan.getCurrentLanguage());
476 #endif
477
478 const Common::FSNode gameDataDir(ConfMan.get("path"));
479 SearchMan.addSubDirectoryMatching(gameDataDir, "WIN9x");
480
481 Common::ErrorCode err = loadCursors();
482 if (err != Common::kNoError)
483 return err;
484
485 if (!_wdPodFile) {
486 const char *const wdpodpaths[] = {
487 "WIN9x/WORLD/WD.POD", "WD.POD",
488 "Hades_-_Copy_To_Hard_Drive/Hades_Challenge/World/wd.pod",
489 "Hades - Copy To Hard Drive/Hades Challenge/World/wd.pod"};
490 debug("HadeschEngine: loading wd.pod");
491 for (uint i = 0; i < ARRAYSIZE(wdpodpaths); ++i) {
492 Common::SharedPtr<Common::File> file(new Common::File());
493 if (file->open(wdpodpaths[i])) {
494 _wdPodFile = Common::SharedPtr<PodFile>(new PodFile("WD.POD"));
495 _wdPodFile->openStore(file);
496 break;
497 }
498 }
499 }
500
501 if (!_wdPodFile) {
502 debug("Cannot find WD.POD");
503 return Common::kUnsupportedGameidError;
504 }
505
506 _cdScenesPath = "";
507
508 // It's tempting to use SearchMan for this but it
509 // doesn't work because we need to access subdirs based
510 // on cdScenePath
511 const char *const scenepaths[] = {"CDAssets/", "Scenes/"};
512 for (uint i = 0; i < ARRAYSIZE(scenepaths); ++i) {
513 Common::ScopedPtr<Common::File> file(new Common::File());
514 if (file->open(Common::String(scenepaths[i]) + "OLYMPUS/OL.POD")) {
515 _cdScenesPath = scenepaths[i];
516 break;
517 }
518 }
519
520 if (_cdScenesPath == "") {
521 debug("Cannot find OL.POD");
522 return Common::kUnsupportedGameidError;
523 }
524
525 debug("HadeschEngine: intro");
526 initGraphics(kVideoWidth, kVideoHeight);
527
528 _heroBelt = Common::SharedPtr<HeroBelt>(new HeroBelt());
529 _gfxContext = Common::SharedPtr<GfxContext8Bit>(new GfxContext8Bit(2 * kVideoWidth + 10, kVideoHeight + 50));
530 _isInOptions = false;
531
532 ConfMan.registerDefault("subtitles", "false");
533 ConfMan.registerDefault("sfx_volume", 192);
534 ConfMan.registerDefault("music_volume", 192);
535 ConfMan.registerDefault("speech_volume", 192);
536 ConfMan.registerDefault("mute", "false");
537 ConfMan.registerDefault("speech_mute", "false");
538 ConfMan.registerDefault("talkspeed", 60);
539 _mixer->setVolumeForSoundType(_mixer->kMusicSoundType, ConfMan.getInt("music_volume"));
540 _mixer->setVolumeForSoundType(_mixer->kSFXSoundType, ConfMan.getInt("sfx_volume"));
541 _mixer->setVolumeForSoundType(_mixer->kSpeechSoundType, ConfMan.getInt("speech_volume"));
542
543 if (!ConfMan.getBool("subtitles"))
544 _subtitleDelayPerChar = -1;
545 else
546 _subtitleDelayPerChar = 4500 / ConfMan.getInt("talkspeed");
547
548 debug("HadeschEngine: moving to main loop");
549 _nextRoom.clear();
550 int loadSlot = ConfMan.getInt("save_slot");
551 if (loadSlot >= 0) {
552 loadGameState(loadSlot);
553 } else {
554 _nextRoom.push_back(kIntroRoom);
555 }
556
557 while (true) {
558 if (_isRestoring) {
559 moveToRoomReal(_persistent._currentRoomId);
560 _isRestoring = false;
561 CursorMan.showMouse(true);
562 CursorMan.replaceCursor(_cursors[3]);
563 } else if (!_nextRoom.empty()) {
564 moveToRoomReal(_nextRoom.remove_at(0));
565 CursorMan.showMouse(true);
566 CursorMan.replaceCursor(_cursors[3]);
567 }
568
569 if (_isQuitting) {
570 return Common::kNoError;
571 }
572 Common::Event event;
573 bool stopVideo = false;
574 while (_eventMan->pollEvent(event)) {
575 switch (event.type) {
576 case Common::EVENT_QUIT:
577 case Common::EVENT_RETURN_TO_LAUNCHER:
578 return Common::kNoError;
579 case Common::EVENT_LBUTTONDOWN: {
580 if(getVideoRoom()->isMouseEnabled() && getVideoRoom()->isHeroBeltEnabled()
581 && _heroBelt->isPositionOverHeroBelt(event.mouse)) {
582 debug("handling belt click");
583 _heroBelt->handleClick(event.mouse);
584 break;
585 }
586 const Common::String &q = getVideoRoom()->mapClick(event.mouse);
587 debug("handling click on <%s>", q.c_str());
588 if (getVideoRoom()->isHeroBeltEnabled() && _heroBelt->isHoldingItem())
589 getCurrentHandler()->handleClickWithItem(q, _heroBelt->getHoldingItem());
590 else {
591 getCurrentHandler()->handleAbsoluteClick(event.mouse);
592 getCurrentHandler()->handleClick(q);
593 }
594 }
595 break;
596 case Common::EVENT_LBUTTONUP:
597 getCurrentHandler()->handleUnclick(getVideoRoom()->mapClick(event.mouse), event.mouse);
598 break;
599 case Common::EVENT_KEYDOWN:
600 // TODO: make equivalents for mobile devices. Keyboard is
601 // used for 4 things:
602 //
603 // * Skipping cutscenes (press space)
604 // * Entering name.
605 // Original requires a non-empty name. We allow an
606 // empty name.
607 // * Optional save name
608 // * Cheats
609 if (event.kbd.keycode == Common::KEYCODE_SPACE)
610 stopVideo = true;
611 if ((event.kbd.ascii >= 'a' && event.kbd.ascii <= 'z')
612 || (event.kbd.ascii >= '0' && event.kbd.ascii <= '9')) {
613 _cheat += event.kbd.ascii;
614 }
615 if (event.kbd.keycode == Common::KEYCODE_RETURN) {
616 Common::String cheat = _cheat;
617 _cheat = "";
618 if (cheat == "qazxcdewsrfvbnhytg") {
619 debug("Cheats enabled");
620 _cheatsEnabled = true;
621 break;
622 }
623 if (_cheatsEnabled) {
624 if (handleGenericCheat(cheat))
625 break;
626 if (getCurrentHandler()->handleCheat(cheat))
627 break;
628 }
629 }
630 getCurrentHandler()->handleKeypress(event.kbd.ascii);
631 break;
632 default:
633 break;
634 }
635 }
636
637 if (_isInOptions) {
638 _currentTime = _system->getMillis() - _optionsEnterTime;
639 } else {
640 _currentTime = _system->getMillis() - _sceneStartTime;
641 for (Common::List<Timer>::iterator it = _sceneTimers.begin();
642 it != _sceneTimers.end();) {
643 if ((it->period_count != 0 && it->next_time < _currentTime)
644 || (it->skippable && stopVideo)) {
645 it->next_time = _currentTime + it->period;
646 if (it->period_count != -1) {
647 it->period_count--;
648 }
649 handleEvent(it->event);
650 }
651
652 if (it->period_count == 0) {
653 it = _sceneTimers.erase(it);
654 } else
655 it++;
656 }
657 }
658
659 Common::String oldhotzone = getVideoRoom()->getHotZone();
660 _mousePos = _eventMan->getMousePos();
661 getVideoRoom()->computeHotZone(_currentTime, _mousePos);
662
663 Common::String newhotzone = getVideoRoom()->getHotZone();
664
665 if (oldhotzone != newhotzone) {
666 getCurrentHandler()->handleMouseOut(oldhotzone);
667 getCurrentHandler()->handleMouseOver(newhotzone);
668 }
669
670 getCurrentHandler()->frameCallback();
671
672 getVideoRoom()->nextFrame(_gfxContext, _currentTime, stopVideo);
673
674 if (getVideoRoom()->getDragged()) {
675 CursorMan.showMouse(true);
676 CursorMan.replaceCursor(getVideoRoom()->getDragged());
677 } else if (getVideoRoom()->isHeroBeltEnabled()
678 && _heroBelt->isHoldingItem()) {
679 const Graphics::Cursor *cursor = _heroBelt->getHoldingItemCursor(
680 getVideoRoom()->getCursorAnimationFrame(_currentTime));
681 CursorMan.showMouse(true);
682 CursorMan.replaceCursor(cursor);
683 } else {
684 int cursor = getVideoRoom()->getCursor();
685 if (cursor >= 0) {
686 CursorMan.showMouse(true);
687 CursorMan.replaceCursor(_cursors[cursor]);
688 } else {
689 CursorMan.showMouse(true);
690 CursorMan.replaceCursor(_cursors[3]);
691 }
692 }
693
694 _system->updateScreen();
695 _system->delayMillis(15);
696 }
697
698 return Common::kNoError;
699 }
700
getRnd()701 Common::RandomSource &HadeschEngine::getRnd() {
702 return _rnd;
703 }
704
hasFeature(EngineFeature f) const705 bool HadeschEngine::hasFeature(EngineFeature f) const {
706 return
707 f == kSupportsReturnToLauncher ||
708 f == kSupportsLoadingDuringRuntime ||
709 f == kSupportsSavingDuringRuntime ||
710 f == kSupportsChangingOptionsDuringRuntime;
711 }
712
loadGameStream(Common::SeekableReadStream * stream)713 Common::Error HadeschEngine::loadGameStream(Common::SeekableReadStream *stream) {
714 Common::Serializer s(stream, nullptr);
715 if (!_persistent.syncGameStream(s))
716 return Common::kUnknownError;
717
718 _isRestoring = true;
719
720 return Common::kNoError;
721 }
722
saveGameStream(Common::WriteStream * stream,bool isAutosave)723 Common::Error HadeschEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
724 Common::Serializer s(nullptr, stream);
725 if (isAutosave)
726 _persistent._slotDescription = "Autosave";
727 if(_persistent._currentRoomId == 0)
728 return Common::kUnknownError;
729 bool res = _persistent.syncGameStream(s);
730 _persistent._slotDescription = "";
731 return res ? Common::kNoError
732 : Common::kUnknownError;
733 }
734
getCDScenesPath() const735 const Common::String &HadeschEngine::getCDScenesPath() const {
736 return _cdScenesPath;
737 }
738
addTimer(EventHandlerWrapper eventId,int32 start_time,int period,int repeat,bool skippable)739 void HadeschEngine::addTimer(EventHandlerWrapper eventId, int32 start_time, int period, int repeat, bool skippable) {
740 struct Timer timer;
741 assert(!_isInOptions);
742 timer.next_time = start_time + period;
743 timer.period_count = repeat;
744 timer.period = period;
745 timer.event = eventId;
746 timer.skippable = skippable;
747 _sceneTimers.push_back(timer);
748 }
749
addTimer(EventHandlerWrapper eventId,int period,int repeat)750 void HadeschEngine::addTimer(EventHandlerWrapper eventId, int period, int repeat) {
751 addTimer(eventId, _currentTime, period, repeat, false);
752 }
753
addSkippableTimer(EventHandlerWrapper eventId,int period,int repeat)754 void HadeschEngine::addSkippableTimer(EventHandlerWrapper eventId, int period, int repeat) {
755 addTimer(eventId, _currentTime, period, repeat, true);
756 }
757
cancelTimer(int eventId)758 void HadeschEngine::cancelTimer(int eventId) {
759 assert(!_isInOptions);
760 for (Common::List<Timer>::iterator it = _sceneTimers.begin();
761 it != _sceneTimers.end();) {
762 if (it->event == eventId) {
763 it = _sceneTimers.erase(it);
764 } else
765 it++;
766 }
767 }
768
getCurrentHandler()769 Common::SharedPtr<Handler> HadeschEngine::getCurrentHandler() {
770 return _isInOptions ? _optionsHandler : _sceneHandler;
771 }
772
getVideoRoom()773 Common::SharedPtr<VideoRoom> HadeschEngine::getVideoRoom() {
774 return _isInOptions ? _optionsRoom : _sceneVideoRoom;
775 }
776
moveToRoomReal(RoomId id)777 void HadeschEngine::moveToRoomReal(RoomId id) {
778 if (_sceneVideoRoom)
779 _sceneVideoRoom->finish();
780
781 _heroBelt->reset();
782
783 switch (id) {
784 case kWallOfFameRoom:
785 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("WALLFAME", "WF", "HTAssets.txt")),
786 makeWallOfFameHandler(), id);
787 break;
788 case kArgoRoom:
789 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("ARGO", "AR", "ARAssets.txt")),
790 makeArgoHandler(), id);
791 break;
792 case kCreteRoom:
793 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CRETE", "CR", "")),
794 makeCreteHandler(), id);
795 break;
796 case kOlympusRoom:
797 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("OLYMPUS", "OL", "MOAssets.txt")),
798 makeOlympusHandler(), id);
799 break;
800 case kMinosPalaceRoom:
801 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MINOS", "MI", "MIAssets.txt")),
802 makeMinosHandler(), id);
803 break;
804 case kDaedalusRoom:
805 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("DAEDALUS", "DA", "DWAssets.txt")),
806 makeDaedalusHandler(), id);
807 break;
808 case kSeriphosRoom:
809 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("SERIPHOS", "SE", "")),
810 makeSeriphosHandler(), id);
811 break;
812 case kMedIsleRoom:
813 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MEDISLE", "MI", "")),
814 makeMedIsleHandler(), id);
815 break;
816 case kMedusaPuzzle:
817 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MEDUSA", "ME", "")),
818 makeMedusaHandler(), id);
819 break;
820 case kTroyRoom:
821 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("TROY", "TR", "")),
822 makeTroyHandler(), id);
823 break;
824 case kTrojanHorsePuzzle:
825 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("TROJAN", "TH", "")),
826 makeTrojanHandler(), id);
827 break;
828 case kMinotaurPuzzle:
829 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MINOTAUR", "MM", "")),
830 makeMinotaurHandler(), id);
831 break;
832 case kQuiz:
833 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("HADESCH", "HC", "HcAssets.txt")),
834 makeQuizHandler(), id);
835 break;
836 case kCatacombsRoom:
837 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CATACOMB", "CA", "CaAssets.txt")),
838 makeCatacombsHandler(), id);
839 break;
840 case kPriamRoom:
841 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("PRIAM", "PR", "PrAssets.txt")),
842 makePriamHandler(), id);
843 break;
844 case kAthenaRoom:
845 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("ATHENA", "AT", "")),
846 makeAthenaHandler(), id);
847 break;
848 case kVolcanoRoom:
849 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("VOLCANO", "VO", "VTAssets.txt")),
850 makeVolcanoHandler(), id);
851 break;
852 case kRiverStyxRoom:
853 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("NEARR", "NR", "NRAssets.txt")),
854 makeRiverStyxHandler(), id);
855 break;
856 case kHadesThroneRoom:
857 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("THRONE", "TH", "HTRAsset.txt")),
858 makeHadesThroneHandler(), id);
859 break;
860 case kCreditsRoom:
861 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CREDITS", "CR", "")),
862 makeCreditsHandler(false), id);
863 break;
864 case kIntroRoom:
865 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("INTRO", "IN", "")),
866 makeIntroHandler(), id);
867 break;
868 case kFerrymanPuzzle:
869 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("FERRY", "FF", "")),
870 makeFerryHandler(), id);
871 break;
872 case kMonsterPuzzle:
873 setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MONSTER", "MM", "")),
874 makeMonsterHandler(), id);
875 break;
876 default:
877 debug("unknown room %d", id);
878 assert(0);
879 return;
880 }
881
882 _sceneHandler->prepareRoom();
883
884 _persistent._roomVisited[id] = true;
885 }
886
genSubtitleID()887 int HadeschEngine::genSubtitleID() {
888 return _subtitleID++;
889 }
890
firstAvailableSlot()891 int HadeschEngine::firstAvailableSlot() {
892 for (unsigned slot = 3; ; slot++) {
893 SaveStateDescriptor desc = getMetaEngine()->querySaveMetaInfos(_targetName.c_str(), slot);
894 if (desc.getSaveSlot() == -1 && !desc.getWriteProtectedFlag())
895 return slot;
896 }
897 }
898
quit()899 void HadeschEngine::quit() {
900 _isQuitting = true;
901 }
902
hasAnySaves()903 bool HadeschEngine::hasAnySaves() {
904 Common::SaveFileManager *saveFileMan = getSaveFileManager();
905 Common::StringArray filenames;
906 Common::String pattern(getMetaEngine()->getSavegameFilePattern(_targetName.c_str()));
907
908 filenames = saveFileMan->listSavefiles(pattern);
909
910 return !filenames.empty();
911 }
912
getHadeschSavesList()913 Common::Array<HadeschSaveDescriptor> HadeschEngine::getHadeschSavesList() {
914 Common::SaveFileManager *saveFileMan = getSaveFileManager();
915 Common::StringArray filenames;
916 Common::String pattern(getMetaEngine()->getSavegameFilePattern(_targetName.c_str()));
917
918 filenames = saveFileMan->listSavefiles(pattern);
919
920 Common::Array<HadeschSaveDescriptor> saveList;
921 for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
922 // Obtain the last 2 digits of the filename, since they correspond to the save slot
923 int slotNum = atoi(file->c_str() + file->size() - 3);
924
925 if (slotNum >= 0) {
926 Common::ScopedPtr<Common::InSaveFile> in(saveFileMan->openForLoading(*file));
927 if (!in) {
928 continue;
929 }
930
931 Common::Serializer s(in.get(), nullptr);
932
933 saveList.push_back(HadeschSaveDescriptor(s, slotNum));
934 }
935 }
936
937 // Sort saves based on slot number.
938 // TODO: change it to chronological save id
939 Common::sort(saveList.begin(), saveList.end(), HadeschSaveDescriptorSlotComparator());
940 return saveList;
941 }
942
deleteSave(int slot)943 void HadeschEngine::deleteSave(int slot) {
944 getMetaEngine()->removeSaveState(_targetName.c_str(), slot);
945 }
946
operator ()() const947 void EventHandlerWrapper::operator()() const {
948 if (_handler && _eventId == -1)
949 debug("handling anon event");
950 else if (_eventId != 20001 && _eventId != 14006 && _eventId != 15266)
951 debug("handling event %d", _eventId);
952 if (_handler)
953 _handler->operator()();
954 if (_eventId > 0)
955 g_vm->getCurrentHandler()->handleEvent(_eventId);
956 }
957
operator ==(int b) const958 bool EventHandlerWrapper::operator==(int b) const {
959 return _eventId == b;
960 }
961
getSubtitleDelayPerChar() const962 uint32 HadeschEngine::getSubtitleDelayPerChar() const {
963 return _subtitleDelayPerChar;
964 }
965
966 } // End of namespace Hadesch
967