[Scummvm-git-logs] scummvm master -> 058592794f9c199316f26529558a530c957907d6
sev-
sev at scummvm.org
Sat Oct 24 17:51:34 UTC 2020
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
058592794f HADESCH: Add new engine for Hades' Challenge
Commit: 058592794f9c199316f26529558a530c957907d6
https://github.com/scummvm/scummvm/commit/058592794f9c199316f26529558a530c957907d6
Author: Vladimir Serbinenko (phcoder at google.com)
Date: 2020-10-24T19:51:30+02:00
Commit Message:
HADESCH: Add new engine for Hades' Challenge
The game is completable but 3 arcade sequences at the end of Minotaur, Medusa and Troy quests.
Probably full of bugs but I already publish it for consideration
Changed paths:
A engines/hadesch/ambient.cpp
A engines/hadesch/ambient.h
A engines/hadesch/baptr.cpp
A engines/hadesch/baptr.h
A engines/hadesch/configure.engine
A engines/hadesch/detection.cpp
A engines/hadesch/detection_tables.h
A engines/hadesch/enums.h
A engines/hadesch/event.h
A engines/hadesch/gfx_context.cpp
A engines/hadesch/gfx_context.h
A engines/hadesch/hadesch.cpp
A engines/hadesch/hadesch.h
A engines/hadesch/herobelt.cpp
A engines/hadesch/herobelt.h
A engines/hadesch/hotzone.cpp
A engines/hadesch/hotzone.h
A engines/hadesch/metaengine.cpp
A engines/hadesch/module.mk
A engines/hadesch/persistent.cpp
A engines/hadesch/persistent.h
A engines/hadesch/pod_file.cpp
A engines/hadesch/pod_file.h
A engines/hadesch/pod_image.cpp
A engines/hadesch/pod_image.h
A engines/hadesch/rooms/argo.cpp
A engines/hadesch/rooms/athena.cpp
A engines/hadesch/rooms/catacombs.cpp
A engines/hadesch/rooms/credits.cpp
A engines/hadesch/rooms/crete.cpp
A engines/hadesch/rooms/daedalus.cpp
A engines/hadesch/rooms/ferry.cpp
A engines/hadesch/rooms/hadesthrone.cpp
A engines/hadesch/rooms/intro.cpp
A engines/hadesch/rooms/medisle.cpp
A engines/hadesch/rooms/medusa.cpp
A engines/hadesch/rooms/minos.cpp
A engines/hadesch/rooms/minotaur.cpp
A engines/hadesch/rooms/monster.cpp
A engines/hadesch/rooms/monster.h
A engines/hadesch/rooms/monster/cyclops.cpp
A engines/hadesch/rooms/monster/illusion.cpp
A engines/hadesch/rooms/monster/projectile.cpp
A engines/hadesch/rooms/monster/typhoon.cpp
A engines/hadesch/rooms/olympus.cpp
A engines/hadesch/rooms/options.cpp
A engines/hadesch/rooms/priam.cpp
A engines/hadesch/rooms/quiz.cpp
A engines/hadesch/rooms/riverstyx.cpp
A engines/hadesch/rooms/seriphos.cpp
A engines/hadesch/rooms/trojan.cpp
A engines/hadesch/rooms/troy.cpp
A engines/hadesch/rooms/volcano.cpp
A engines/hadesch/rooms/walloffame.cpp
A engines/hadesch/table.cpp
A engines/hadesch/table.h
A engines/hadesch/tag_file.cpp
A engines/hadesch/tag_file.h
A engines/hadesch/video.cpp
A engines/hadesch/video.h
AUTHORS
NEWS.md
devtools/credits.pl
gui/credits.h
diff --git a/AUTHORS b/AUTHORS
index d37ba83668..8af2ee51fc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -182,6 +182,9 @@ ScummVM Team
Scott Thomas
Jordi Vilalta Prat
+ Hades' Challenge:
+ Vladimir Serbinenko/Google
+
HDB:
Eugene Sandulenko
Nipun Garg - GSoC student
diff --git a/NEWS.md b/NEWS.md
index 69b2fd0e7e..6fce3f671d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -8,6 +8,7 @@ For a more comprehensive changelog of the latest experimental code, see:
- Added support for Escape from Monkey Island.
- Added support for The Longest Journey.
- Added support for Myst 3: Exile.
+ - Added support for Hades' Challenge.
General:
- Switched ScummVM GUI output to UTF-32.
diff --git a/devtools/credits.pl b/devtools/credits.pl
index 33eea17332..01b084dc26 100755
--- a/devtools/credits.pl
+++ b/devtools/credits.pl
@@ -1540,8 +1540,15 @@ begin_credits("Credits");
add_person("", "Faalagorn", "Few code improvements");
add_person("", "orangeforest11", "Few engine improvements");
end_persons();
+ end_section();
+
+ begin_section("Hades' Challenge", "hadesch_contrib");
+ begin_persons();
+ add_person("Vladimir Serbinenko/Google", "phcoder", "Engine implementation");
+ end_persons();
end_section();
+
add_paragraph("And to all the contributors, users, and beta testers we've missed. Thanks!");
end_section();
diff --git a/engines/hadesch/ambient.cpp b/engines/hadesch/ambient.cpp
new file mode 100644
index 0000000000..74be4c7d78
--- /dev/null
+++ b/engines/hadesch/ambient.cpp
@@ -0,0 +1,357 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/ambient.h"
+
+namespace Hadesch {
+class AmbientAnimStarter : public EventHandler {
+public:
+ void operator()() override {
+ _anim.play(true);
+ }
+
+ AmbientAnimStarter(AmbientAnim anim) {
+ _anim = anim;
+ }
+private:
+ AmbientAnim _anim;
+};
+
+class AmbientAnimPlayEnded : public EventHandler {
+public:
+ void operator()() override {
+ _anim.playFinished(_reschedule);
+ }
+
+ AmbientAnimPlayEnded(AmbientAnim anim, bool reschedule) {
+ _anim = anim;
+ _reschedule = reschedule;
+ }
+private:
+ AmbientAnim _anim;
+ bool _reschedule;
+};
+
+AmbientAnim::AmbientAnim(const Common::String &animName,
+ const Common::String &sound, int zValue,
+ int minint, int maxint, AnimType loop,
+ Common::Point offset, PanType pan) {
+ _internal = Common::SharedPtr<AmbiantAnimInternal>(
+ new AmbiantAnimInternal());
+ _internal->_descs.push_back(AmbientDesc(animName, sound));
+ _internal->_minInterval = minint;
+ _internal->_maxInterval = maxint;
+ _internal->_offset = offset;
+ _internal->_loopType = loop;
+ _internal->_zValue = zValue;
+ _internal->_paused = false;
+ _internal->_playing = false;
+ _internal->_pan = pan;
+ _internal->_isFwd = true;
+}
+
+AmbientAnim::AmbientAnim(const Common::Array<AmbientDesc> &descs, int zValue,
+ int minint, int maxint, AnimType loop,
+ Common::Point offset, PanType pan) {
+ _internal = Common::SharedPtr<AmbiantAnimInternal>(
+ new AmbiantAnimInternal());
+ _internal->_descs = descs;
+ _internal->_minInterval = minint;
+ _internal->_maxInterval = maxint;
+ _internal->_offset = offset;
+ _internal->_loopType = loop;
+ _internal->_zValue = zValue;
+ _internal->_paused = false;
+ _internal->_playing = false;
+ _internal->_pan = pan;
+ _internal->_isFwd = true;
+}
+
+AmbientAnim::AmbientAnim() {
+ _internal = Common::SharedPtr<AmbiantAnimInternal>(
+ new AmbiantAnimInternal());
+ _internal->_minInterval = 0;
+ _internal->_maxInterval = 0;
+ _internal->_loopType = KEEP_LOOP;
+ _internal->_zValue = 0;
+ _internal->_paused = false;
+ _internal->_playing = false;
+ _internal->_isFwd = true;
+}
+
+void AmbientAnim::pause() {
+ _internal->_paused = true;
+}
+
+void AmbientAnim::unpause() {
+ _internal->_paused = false;
+}
+
+void AmbientAnim::hide() {
+ pause();
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->stopAnim(_internal->_descs[0]._animName);
+ _internal->_playing = false;
+ _internal->_paused = true;
+}
+
+void AmbientAnim::selectFirstFrame() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->selectFrame(_internal->_descs[0]._animName, _internal->_zValue,
+ 0, _internal->_offset);
+}
+
+void AmbientAnim::unpauseAndFirstFrame() {
+ unpause();
+ selectFirstFrame();
+}
+
+bool AmbientAnim::isPanOK() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ if (_internal->_pan == PAN_ANY)
+ return true;
+
+ if (_internal->_pan == PAN_LEFT && room->isPanLeft())
+ return true;
+
+ if (_internal->_pan == PAN_RIGHT && room->isPanRight())
+ return true;
+
+ return false;
+}
+
+bool AmbientAnim::isReady() {
+ return !_internal->_paused && !_internal->_playing && isPanOK();
+}
+
+void AmbientAnim::play(bool reschedule) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ if (_internal->_paused || _internal->_playing || !isPanOK()) {
+ if (reschedule)
+ schedule();
+ return;
+ }
+ _internal->_playing = true;
+ unsigned variant = 0;
+ if (_internal->_descs.size() > 1) {
+ variant = g_vm->getRnd().getRandomNumberRng(0, _internal->_descs.size() - 1);
+ for (unsigned i = 0; i < _internal->_descs.size(); i++) {
+ if (i != variant)
+ room->stopAnim(_internal->_descs[i]._animName);
+ }
+ }
+
+ PlayAnimParams params = PlayAnimParams::disappear();
+
+ switch (_internal->_loopType) {
+ case DISAPPEAR:
+ params = PlayAnimParams::disappear();
+ break;
+ case KEEP_LOOP:
+ params = PlayAnimParams::keepLastFrame();
+ break;
+ case BACK_AND_FORTH:
+ if (_internal->_isFwd) {
+ params = PlayAnimParams::keepLastFrame();
+ } else {
+ params = PlayAnimParams::disappear().backwards();
+ }
+ _internal->_isFwd = !_internal->_isFwd;
+ break;
+ }
+
+ room->playAnim(_internal->_descs[variant]._animName, _internal->_zValue,
+ params,
+ Common::SharedPtr<EventHandler>(new AmbientAnimPlayEnded(*this, reschedule)),
+ _internal->_offset);
+
+ if (_internal->_descs[variant]._soundName != "")
+ room->playSound(_internal->_descs[variant]._soundName, -1);
+}
+
+void AmbientAnim::schedule() {
+ if (_internal->_minInterval >= 0 && _internal->_maxInterval >= 0)
+ g_vm->addTimer(
+ Common::SharedPtr<EventHandler>(new AmbientAnimStarter(*this)),
+ g_vm->getRnd().getRandomNumberRng(_internal->_minInterval,
+ _internal->_maxInterval));
+}
+
+void AmbientAnim::playFinished(bool reschedule) {
+ _internal->_playing = false;
+ if (reschedule)
+ schedule();
+}
+
+void AmbientAnim::start() {
+ if (_internal->_loopType == KEEP_LOOP) {
+ selectFirstFrame();
+ }
+ schedule();
+}
+
+void AmbientAnimWeightedSet::readTableFilePriam(const TextTable &table) {
+ for (int row = 0; row < table.size(); row++) {
+ AmbientAnimWeightedSetElement el;
+ el.name = table.get(row, "name");
+ el.weight = table.get(row, "weight").asUint64();
+ el.valid = table.get(row, "anim") != "";
+ if (el.valid)
+ el.anim = AmbientAnim(table.get(row, "anim"),
+ table.get(row, "sound"),
+ table.get(row, "depth").asUint64(),
+ -1, -1, AmbientAnim::KEEP_LOOP, Common::Point(0, 0),
+ AmbientAnim::PAN_ANY);
+ // TODO: volume
+ _elements.push_back(el);
+ }
+}
+
+void AmbientAnimWeightedSet::readTableFile(const TextTable &table, AmbientAnim::PanType pan) {
+ for (int row = 0; row < table.size(); row++) {
+ AmbientAnimWeightedSetElement el;
+ el.name = table.get(row, "anim");
+ el.weight = 1;
+ el.valid = table.get(row, "anim") != "";
+ if (el.valid)
+ el.anim = AmbientAnim(table.get(row, "anim"),
+ table.get(row, "sound"),
+ table.get(row, "Z").asUint64(),
+ -1, -1, AmbientAnim::KEEP_LOOP, Common::Point(
+ table.get(row, "X").asUint64(),
+ table.get(row, "Y").asUint64()),
+ pan);
+ // TODO: volume
+ _elements.push_back(el);
+ }
+}
+
+void AmbientAnimWeightedSet::firstFrame() {
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (_elements[i].valid)
+ _elements[i].anim.selectFirstFrame();
+ }
+}
+
+void AmbientAnimWeightedSet::tick() {
+ int chosen = -1, chosenWeight = -1;
+ // This is not how weighted random should be
+ // but this is howoriginal game generates it and
+ // it's probably slightly wrong from mathematical
+ // point of view
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (!_elements[i].anim.isReady())
+ continue;
+ int curWeight = _elements[i].weight * g_vm->getRnd().getRandomNumberRng(
+ 0, 100);
+ if (curWeight > chosenWeight) {
+ chosen = i;
+ chosenWeight = curWeight;
+ }
+ }
+ if (chosen < 0 || !_elements[chosen].valid)
+ return;
+ _elements[chosen].anim.play(false);
+}
+
+void AmbientAnimWeightedSet::pause(const Common::String &name) {
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (_elements[i].name == name && _elements[i].valid) {
+ _elements[i].anim.pause();
+ }
+ }
+}
+
+void AmbientAnimWeightedSet::unpause(const Common::String &name) {
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (_elements[i].name == name && _elements[i].valid) {
+ _elements[i].anim.unpause();
+ }
+ }
+}
+
+void AmbientAnimWeightedSet::play(const Common::String &name, bool reschedule) {
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (_elements[i].name == name && _elements[i].valid) {
+ _elements[i].anim.play(reschedule);
+ }
+ }
+}
+
+void AmbientAnimWeightedSet::unpauseAndFirstFrame(const Common::String &name) {
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (_elements[i].name == name && _elements[i].valid) {
+ _elements[i].anim.unpauseAndFirstFrame();
+ }
+ }
+}
+
+void AmbientAnimWeightedSet::hide(const Common::String &name) {
+ for (unsigned i = 0; i < _elements.size(); i++) {
+ if (_elements[i].name == name && _elements[i].valid) {
+ _elements[i].anim.hide();
+ }
+ }
+}
+
+void AnimClickables::playChosen(const Common::String &name, int counter, const EventHandlerWrapper &event) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ int rk = _table.rowCount(name);
+ if (rk == 0) {
+ event();
+ return;
+ }
+ counter %= rk;
+ Common::String smacker = _table.get(name, "smacker", counter);
+ Common::String anim = _table.get(name, "anim", counter);
+ Common::String sound = _table.get(name, "sound", counter);
+ int zValue = _table.get(name, "Z", counter).asUint64();
+ if (smacker != "")
+ room->playVideo(smacker.substr(1), zValue, event,
+ Common::Point(_table.get(name, "smackerX", counter).asUint64(),
+ _table.get(name, "smackerY", counter).asUint64()));
+ else if (anim != "")
+ room->playAnimWithSound(
+ anim, sound, zValue, PlayAnimParams::disappear(), event,
+ Common::Point(_table.get(name, "X", counter).asUint64(),
+ _table.get(name, "Y", counter).asUint64()));
+ else if (sound != "")
+ room->playSound(sound, event);
+ else
+ event();
+}
+
+// TODO: should counters be persistent?
+void AnimClickables::playNext(const Common::String &name, const EventHandlerWrapper &event) {
+ playChosen(name, _counters[name], event);
+ _counters[name]++;
+}
+
+void AnimClickables::readTable(Common::SharedPtr<Hadesch::VideoRoom> room, const Common::String &name) {
+ _table = TextTable(Common::SharedPtr<Common::SeekableReadStream>(room->openFile(name)), 14);
+}
+
+}
diff --git a/engines/hadesch/ambient.h b/engines/hadesch/ambient.h
new file mode 100644
index 0000000000..0c71f28ffe
--- /dev/null
+++ b/engines/hadesch/ambient.h
@@ -0,0 +1,131 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/str.h"
+#include "common/rect.h"
+#include "common/noncopyable.h"
+
+#ifndef HADESCH_AMBIENT_H
+#define HADESCH_AMBIENT_H
+
+namespace Hadesch {
+class AmbientAnim {
+public:
+ struct AmbientDesc {
+ Common::String _animName;
+ Common::String _soundName;
+ AmbientDesc(Common::String animName, Common::String soundName) {
+ _animName = animName;
+ _soundName = soundName;
+ }
+ };
+
+ enum PanType {
+ PAN_ANY,
+ PAN_LEFT,
+ PAN_RIGHT
+ };
+
+ enum AnimType {
+ DISAPPEAR,
+ KEEP_LOOP,
+ BACK_AND_FORTH
+ };
+
+ AmbientAnim(const Common::String &animName,
+ const Common::String &sound, int zValue,
+ int minint, int maxint, AnimType loop,
+ Common::Point offset, PanType pan);
+ AmbientAnim(const Common::Array<AmbientDesc> &descs, int zValue,
+ int minint, int maxint, AnimType loop,
+ Common::Point offset, PanType pan);
+ AmbientAnim();
+ void play(bool reschedule);
+ void schedule();
+ void start();
+ void pause();
+ void unpause();
+ void hide();
+ void unpauseAndFirstFrame();
+ void selectFirstFrame();
+ void playFinished(bool reschedule);
+ bool isReady();
+private:
+ class AmbiantAnimInternal : Common::NonCopyable {
+ public:
+ Common::Array<AmbientDesc> _descs;
+ int _minInterval, _maxInterval;
+ int _zValue;
+ AnimType _loopType;
+ bool _isFwd;
+ Common::Point _offset;
+ bool _playing;
+ bool _paused;
+ PanType _pan;
+ };
+
+ bool isPanOK();
+
+ Common::SharedPtr<AmbiantAnimInternal> _internal;
+};
+
+
+class AmbientAnimWeightedSet {
+public:
+ void readTableFilePriam(const TextTable &table);
+ void readTableFile(const TextTable &table, AmbientAnim::PanType pan);
+ void tick();
+ void firstFrame();
+ void pause(const Common::String &name);
+ void unpause(const Common::String &name);
+ void unpauseAndFirstFrame(const Common::String &name);
+ void hide(const Common::String &name);
+ void play(const Common::String &name, bool reschedule);
+private:
+ struct AmbientAnimWeightedSetElement {
+ AmbientAnim anim;
+ int weight;
+ bool valid;
+ Common::String name;
+ };
+ Common::Array<AmbientAnimWeightedSetElement> _elements;
+};
+
+class AnimClickables {
+public:
+ void playNext(const Common::String &name, const EventHandlerWrapper &event);
+ void playChosen(const Common::String &name, int counter, const EventHandlerWrapper &event);
+ void setTable(const TextTable table) {
+ _table = table;
+ }
+ void readTable(Common::SharedPtr<Hadesch::VideoRoom> room,
+ const Common::String &name);
+
+private:
+ TextTable _table;
+ Common::HashMap<Common::String, int> _counters;
+};
+
+
+}
+#endif
diff --git a/engines/hadesch/baptr.cpp b/engines/hadesch/baptr.cpp
new file mode 100644
index 0000000000..5bc1e37f7b
--- /dev/null
+++ b/engines/hadesch/baptr.cpp
@@ -0,0 +1,35 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/baptr.h"
+
+namespace Hadesch {
+class ByteArrayDeleter {
+public:
+ void operator()(byte *ptr) { delete [] ptr; }
+};
+
+Common::SharedPtr<byte> sharedPtrByteAlloc(size_t sz) {
+ return Common::SharedPtr<byte>(new (std::nothrow) byte[sz], ByteArrayDeleter());
+}
+}
diff --git a/engines/hadesch/baptr.h b/engines/hadesch/baptr.h
new file mode 100644
index 0000000000..9d752c8fea
--- /dev/null
+++ b/engines/hadesch/baptr.h
@@ -0,0 +1,32 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_BAPTR_H
+#define HADESCH_BAPTR_H
+
+#include "common/ptr.h"
+
+namespace Hadesch {
+Common::SharedPtr<byte> sharedPtrByteAlloc(size_t sz);
+}
+#endif
diff --git a/engines/hadesch/configure.engine b/engines/hadesch/configure.engine
new file mode 100644
index 0000000000..dad132c7e4
--- /dev/null
+++ b/engines/hadesch/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine hadesch "Hades Challenge" no "" "" "highres"
diff --git a/engines/hadesch/detection.cpp b/engines/hadesch/detection.cpp
new file mode 100644
index 0000000000..9cd399393b
--- /dev/null
+++ b/engines/hadesch/detection.cpp
@@ -0,0 +1,83 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/system.h"
+#include "common/savefile.h"
+
+#include "engines/advancedDetector.h"
+
+#include "hadesch/hadesch.h"
+
+#include "detection_tables.h"
+
+namespace Hadesch {
+static const PlainGameDescriptor hadeschGames[] = {
+ {"hadesch", "Hades Challenge"},
+ {0, 0}
+};
+
+// The list is pretty long but it's because we need just a few files but
+// in pretty deep paths:
+// * Setup.exe [Russian-Windows]
+// * WIN9x/WORLD/wd.pod [English-Windows]
+// * CDAssets/OLYMPUS/ol.pod [English-Windows]
+// * Scenes/OLYMPUS/ol.pod [English-Mac and Russian-Windows]
+// * Hades - Copy To Hard Drive/Hades Challenge/World/wd.pod [English-Mac]
+// * Hades - Copy To Hard Drive/Hades Challenge/World/wd.pod [English-Mac]
+// The difference between 2 last one is how the files were copied
+static const char *const directoryGlobs[] = {
+ "WIN9x",
+ "WORLD",
+ "CDAssets",
+ "OLYMPUS",
+ "Scenes",
+ "Hades_-_Copy_To_Hard_Drive",
+ "Hades - Copy To Hard Drive",
+ "Hades Challenge",
+ "Hades_Challenge",
+ 0
+};
+}
+
+class HadeschMetaEngineDetection : public AdvancedMetaEngineDetection {
+public:
+ HadeschMetaEngineDetection() : AdvancedMetaEngineDetection(Hadesch::gameDescriptions, sizeof(ADGameDescription), Hadesch::hadeschGames) {
+ // mac puts wd.pod in Hades - Copy To Hard Drive/Hades Challenge/World. So we need 4 levels
+ _maxScanDepth = 4;
+ _directoryGlobs = Hadesch::directoryGlobs;
+ }
+
+ const char *getEngineId() const override {
+ return "hadesch";
+ }
+
+ const char *getName() const override {
+ return "Hades Challenge";
+ }
+
+ const char *getOriginalCopyright() const override {
+ return "Hades Challenge (C) Disney's Interactive";
+ }
+};
+
+REGISTER_PLUGIN_STATIC(HADESCH_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, HadeschMetaEngineDetection);
diff --git a/engines/hadesch/detection_tables.h b/engines/hadesch/detection_tables.h
new file mode 100644
index 0000000000..200497f62b
--- /dev/null
+++ b/engines/hadesch/detection_tables.h
@@ -0,0 +1,98 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+
+#ifndef HADESCH_DETECTION_TABLES_H
+#define HADESCH_DETECTION_TABLES_H
+
+namespace Hadesch {
+
+static const ADGameDescription gameDescriptions[] = {
+
+ // Hades Challenge
+ {
+ "hadesch",
+ 0,
+ {
+ {"hadesch.exe", 0, "178b3a69171cb5a4eeeddd0d5993b8c5", 1134592},
+ {"WD.POD", 0, "be7030fc4229e69e719ee2c756eb6ba1", 7479768},
+ {"ol.pod", 0, "7cabba8d1d4f1239e312e045ef4e9735", 5621074},
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+ GUIO1(GUIO_NOMIDI)
+
+ },
+ {
+ "hadesch",
+ 0,
+ {
+ {"Hades Challenge PPC", 0, "c7213a365a3cab7e9f2b423fa4a204f5", 1724646},
+ {"WD.POD", 0, "be7030fc4229e69e719ee2c756eb6ba1", 7479768},
+ {"ol.pod", 0, "7cabba8d1d4f1239e312e045ef4e9735", 5621074},
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_UNSTABLE | ADGF_DROPPLATFORM | ADGF_MACRESFORK,
+ GUIO1(GUIO_NOMIDI)
+
+ },
+ {
+ "hadesch",
+ 0,
+ {
+ {"hadesch.exe", 0, "660735787346ab1bfe0d219bea441486", 1007616},
+ {"WD.POD", 0, "5098edae755135814bb86f2676c41cc2", 8691909},
+ {"ol.pod", 0, "c82e105d9013edc2cc20f0a630e304d5", 5684953},
+ AD_LISTEND
+ },
+ Common::RU_RUS,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+ GUIO1(GUIO_NOMIDI)
+
+ },
+ {
+ "hadesch",
+ 0,
+ {
+ {"setup.exe", 0, "853c199f1ef35d576213f71092bcd0c3", 7491209},
+ {"ol.pod", 0, "c82e105d9013edc2cc20f0a630e304d5", 5684953},
+ AD_LISTEND
+ },
+ Common::RU_RUS,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+ GUIO1(GUIO_NOMIDI)
+
+ },
+
+ AD_TABLE_END_MARKER
+};
+
+} // End of namespace Hadesch
+
+#endif
diff --git a/engines/hadesch/enums.h b/engines/hadesch/enums.h
new file mode 100644
index 0000000000..c6c47b306e
--- /dev/null
+++ b/engines/hadesch/enums.h
@@ -0,0 +1,177 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_ENUMS_H
+#define HADESCH_ENUMS_H
+
+namespace Hadesch {
+enum {
+ kHadeschDebugGeneral = 1 << 0,
+ kHadeschDebugResources = 1 << 1,
+ kHadeschDebugMessagingSystem = 1 << 2,
+ kHadeschDebugDialogs = 1 << 3
+};
+
+enum Gender {
+ kFemale = 0,
+ kMale = 1,
+ // Make it 2, rather than -1, so that we can serialize it in one 1
+ // byte.
+ kUnknown = 2
+};
+
+enum Quest {
+ kNoQuest,
+ kCreteQuest,
+ kTroyQuest,
+ kMedusaQuest,
+ kRescuePhilQuest,
+ kEndGame,
+ kNumQuests
+};
+
+enum RoomId {
+ kInvalidRoom = 0,
+ kIntroRoom = 1,
+ kOlympusRoom = 2,
+ kWallOfFameRoom = 3,
+ kSeriphosRoom = 4,
+ kAthenaRoom = 5,
+ kMedIsleRoom = 6,
+ kMedusaPuzzle = 7,
+ kArgoRoom = 8,
+ kTroyRoom = 9,
+ kCatacombsRoom = 10,
+ kPriamRoom = 11,
+ kTrojanHorsePuzzle = 12,
+ kCreteRoom = 13,
+ kMinosPalaceRoom = 14,
+ kDaedalusRoom = 15,
+ kMinotaurPuzzle = 16,
+ kVolcanoRoom = 17,
+ kRiverStyxRoom = 18,
+ kHadesThroneRoom = 19,
+ kFerrymanPuzzle = 20,
+ kMonsterPuzzle = 21,
+ kQuiz = 22,
+ kCreditsRoom = 23,
+ kOptionsRoom = 24,
+ kNumRooms
+};
+
+enum StatueId {
+ kBacchusStatue = 0,
+ kHermesStatue = 1,
+ kZeusStatue = 2,
+ kPoseidonStatue = 3,
+ kAresStatue = 4,
+ kAphroditeStatue = 5,
+ kApolloStatue = 6,
+ kArtemisStatue = 7,
+ kDemeterStatue = 8,
+ kAthenaStatue = 9,
+ kHeraStatue = 10,
+ kHephaestusStatue = 11,
+ kNumStatues
+};
+
+enum InventoryItem {
+ kNone = 0,
+ kStraw = 2,
+ kStone = 3,
+ kBricks = 4,
+ kMessage = 5,
+ kKey = 6,
+ kDecree = 7,
+ kWood = 8,
+ kHornlessStatue1 = 9,
+ kHornlessStatue2 = 10,
+ kHornlessStatue3 = 11,
+ kHornlessStatue4 = 12,
+ kHornedStatue = 13,
+ kCoin = 14,
+ kPotion = 15,
+ kShield = 16,
+ kSword = 17,
+ kBag = 18,
+ kHelmet = 19,
+ kSandals = 20,
+ kTorch = 21
+};
+
+// Also includes InventoryItem - 1
+enum HeroBeltFrame {
+ kLightning1 = 21,
+ kLightning2 = 22,
+ kLightning3 = 23,
+ kNumberI = 24,
+ kNumberII = 25,
+ kNumberIII = 26,
+ kQuestScroll = 27,
+ kQuestScrollHighlighted = 28,
+ kHadesScroll = 29,
+ kHadesScrollHighlighted = 30,
+ kOptionsButton = 31,
+ kInactiveHints = 32,
+ kActiveHints = 33,
+ kBranchOfLife = 34,
+ kReturnToWall = 35,
+ kPowerOfWisdom = 38,
+ kPowerOfStrength = 39,
+ kPowerOfStealth = 40
+};
+
+enum FateId {
+ kLachesis,
+ kAtropos,
+ kClotho,
+ kNumFates
+};
+
+enum CatacombsPosition {
+ kCatacombsLeft = 0,
+ kCatacombsCenter = 1,
+ kCatacombsRight = 2
+};
+
+enum CatacombsPath {
+ kCatacombsHelen = 0,
+ kCatacombsGuards = 1,
+ kCatacombsPainAndPanic = 2
+};
+
+enum CatacombsLevel {
+ kCatacombLevelSign,
+ kCatacombLevelTorch,
+ kCatacombLevelMusic
+};
+
+enum HeroPower {
+ kPowerNone = -1,
+ kPowerStrength = 0,
+ kPowerStealth = 1,
+ kPowerWisdom = 2
+};
+}
+
+#endif
diff --git a/engines/hadesch/event.h b/engines/hadesch/event.h
new file mode 100644
index 0000000000..11faadbb21
--- /dev/null
+++ b/engines/hadesch/event.h
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_EVENT_H
+#define HADESCH_EVENT_H
+
+namespace Hadesch {
+
+class EventHandler {
+public:
+ virtual void operator()() = 0;
+ virtual ~EventHandler() {}
+};
+
+class EventHandlerWrapper {
+public:
+ void operator()() const;
+ bool operator==(int eventId) const;
+
+ EventHandlerWrapper(int eventId) {
+ _eventId = eventId;
+ }
+
+ EventHandlerWrapper() {
+ _eventId = -1;
+ }
+
+ EventHandlerWrapper(Common::SharedPtr<EventHandler> handler) {
+ _handler = handler;
+ _eventId = -1;
+ }
+
+ Common::String getDebugString() const {
+ return Common::String::format("eventid=%d, handler is %s", _eventId,
+ !!_handler ? "valid" : "null");
+ }
+
+private:
+ Common::SharedPtr<EventHandler> _handler;
+ int _eventId;
+};
+}
+
+#endif
diff --git a/engines/hadesch/gfx_context.cpp b/engines/hadesch/gfx_context.cpp
new file mode 100644
index 0000000000..c4c467e390
--- /dev/null
+++ b/engines/hadesch/gfx_context.cpp
@@ -0,0 +1,107 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/gfx_context.h"
+#include "hadesch/video.h"
+#include "hadesch/baptr.h"
+#include "common/system.h"
+#include "graphics/palette.h"
+
+namespace Hadesch {
+
+void blendVideo8To8(byte *targetPixels, int targetPitch, int targetW, int targetH,
+ byte *sourcePixels, int sourceW, int sourceH, Common::Point offset) {
+ int ymin = MAX(0, -offset.y);
+ int ymax = MIN(sourceH, targetH - offset.y);
+ for (int y = ymin; y < ymax; y++) {
+ int xmin = MAX(0, -offset.x);
+ int xmax = MIN(sourceW, targetW - offset.x);
+ const byte *inptr = sourcePixels + sourceW * y + xmin;
+ byte *outptr = targetPixels + targetPitch * (y + offset.y) + offset.x + xmin;
+ for (int x = xmin; x < xmax; x++, inptr++, outptr++) {
+ if (*inptr)
+ *outptr = *inptr;
+ }
+ }
+}
+
+void GfxContext8Bit::blitPodImage(byte *sourcePixels, int sourcePitch, int sourceW, int sourceH,
+ byte *sourcePalette, size_t ncolours, Common::Point offset) {
+
+ blendVideo8To8(_pixels.get(), _pitch,
+ _w, _h, sourcePixels, sourceW, sourceH,
+ offset);
+ for (unsigned i = 0; i < ncolours; i++) {
+ int col = sourcePalette[4 * i] & 0xff;
+
+ _palette[3 * col ] = sourcePalette[4 * i + 1];
+ _palette[3 * col + 1] = sourcePalette[4 * i + 2];
+ _palette[3 * col + 2] = sourcePalette[4 * i + 3];
+ _paletteUsed[col] = true;
+ }
+}
+
+void GfxContext8Bit::blitVideo(byte *sourcePixels, int sourcePitch, int sourceW, int sourceH,
+ byte *sourcePalette, Common::Point offset) {
+ blendVideo8To8(_pixels.get(), _pitch, _w, _h, sourcePixels, sourceW, sourceH, offset);
+ for (int i = 0; i < 256; i++)
+ if (!_paletteUsed[i]) {
+ _palette[3 * i] = sourcePalette[3 * i];
+ _palette[3 * i + 1] = sourcePalette[3 * i + 1];
+ _palette[3 * i + 2] = sourcePalette[3 * i + 2];
+ }
+}
+
+void GfxContext8Bit::fade(int val) {
+ if (val == 0x100)
+ return;
+ for (int i = 0; i < 256 * 3; i++) {
+ _palette[i] = ((_palette[i] & 0xff) * val) >> 8;
+ }
+}
+
+void GfxContext8Bit::clear() {
+ memset(_pixels.get(), 0, _w * _h);
+ memset(_palette, 0, sizeof(_palette));
+ memset(_paletteUsed, 0, sizeof(_paletteUsed));
+}
+
+GfxContext8Bit::GfxContext8Bit(int canvasW, int canvasH) {
+ _w = canvasW;
+ _h = canvasH;
+ _pitch = _w;
+ _pixels = sharedPtrByteAlloc(_w * _h);
+ memset(_palette, 0, sizeof(_palette));
+ memset(_paletteUsed, 0, sizeof(_paletteUsed));
+}
+
+void GfxContext8Bit::renderToScreen(Common::Point viewPoint) {
+ if (_palette) {
+ g_system->getPaletteManager()->setPalette(_palette, 0, 256);
+ }
+
+ g_system->copyRectToScreen(_pixels.get() + viewPoint.x + _pitch * viewPoint.y, _w, 0, 0,
+ kVideoWidth, kVideoHeight);
+}
+
+}
diff --git a/engines/hadesch/gfx_context.h b/engines/hadesch/gfx_context.h
new file mode 100644
index 0000000000..94e4780c78
--- /dev/null
+++ b/engines/hadesch/gfx_context.h
@@ -0,0 +1,71 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_GFX_CONTEXT_H
+#define HADESCH_GFX_CONTEXT_H
+
+#include "common/ptr.h"
+#include "common/rect.h"
+
+namespace Hadesch {
+class GfxContext {
+public:
+ virtual void blitVideo(byte *sourcePixels, int sourcePitch, int sourceW, int sourceH,
+ byte *palette, Common::Point offset) = 0;
+ // Use only in pod_image.cpp because of palette format
+ virtual void blitPodImage(byte *sourcePixels, int sourcePitch, int sourceW, int sourceH,
+ byte *palette, size_t ncolours, Common::Point offset) = 0;
+ virtual void clear() = 0;
+ virtual void fade(int val) = 0;
+ virtual void renderToScreen(Common::Point viewPoint) = 0;
+ virtual ~GfxContext() {}
+};
+
+class GfxContext8Bit : public GfxContext {
+public:
+ void blitVideo(byte *sourcePixels, int sourcePitch, int sourceW, int sourceH,
+ byte *palette, Common::Point offset) override;
+ void blitPodImage(byte *sourcePixels, int sourcePitch, int sourceW, int sourceH,
+ byte *palette, size_t ncolours, Common::Point offset) override;
+ void clear() override;
+ void fade(int val) override;
+ void renderToScreen(Common::Point viewPoint) override;
+
+ GfxContext8Bit(int canvasW, int canvasH);
+ ~GfxContext8Bit() {}
+
+private:
+ Common::SharedPtr<byte> _pixels;
+ byte _palette[256 * 4];
+ bool _paletteUsed[256];
+ int _pitch;
+ int _w;
+ int _h;
+};
+
+void blendVideo8To8(byte *targetPixels, int targetPitch, int targetW, int targetH,
+ byte *sourcePixels, int sourceW, int sourceH, Common::Point offset);
+
+}
+
+#endif
diff --git a/engines/hadesch/hadesch.cpp b/engines/hadesch/hadesch.cpp
new file mode 100644
index 0000000000..55bde4f072
--- /dev/null
+++ b/engines/hadesch/hadesch.cpp
@@ -0,0 +1,922 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+
+#include "common/debug-channels.h"
+#include "common/error.h"
+#include "common/events.h"
+#include "common/ini-file.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/file.h"
+#include "common/keyboard.h"
+#include "common/macresman.h"
+#include "common/util.h"
+#include "common/zlib.h"
+#include "common/config-manager.h"
+
+#include "engines/advancedDetector.h"
+#include "engines/util.h"
+
+#include "graphics/surface.h"
+
+#include "video/smk_decoder.h"
+
+#include "hadesch/pod_file.h"
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/pod_image.h"
+
+#include "graphics/palette.h"
+#include "common/memstream.h"
+#include "common/winexe_pe.h"
+#include "common/substream.h"
+#include "common/md5.h"
+#include "graphics/wincursor.h"
+#include "graphics/cursorman.h"
+#include "graphics/maccursor.h"
+
+namespace Hadesch {
+
+HadeschEngine *g_vm;
+
+static const uint32 cursorids[] = {
+ 127, 128, 129, // normal and active
+ 125, 134, 135, 136, 137, 138, 139, 140, 141, 142, // waiting
+ 130, // up arrow
+ 131, // left arrow
+ 132, // down arrow
+ 133, // right arrow
+ // 143:cross, 146:cross
+ 146
+};
+
+HadeschEngine::HadeschEngine(OSystem *system, const ADGameDescription *desc)
+ : Engine(system), _desc(desc), _rnd("hadesch") {
+
+ DebugMan.addDebugChannel(kHadeschDebugGeneral, "general", "General issues");
+ DebugMan.addDebugChannel(kHadeschDebugMessagingSystem, "resources", "Resources");
+ DebugMan.addDebugChannel(kHadeschDebugMessagingSystem, "message_system", "Engine message system");
+ DebugMan.addDebugChannel(kHadeschDebugDialogs, "dialogs", "Dialogs");
+
+ g_vm = this;
+ _sceneStartTime = _system->getMillis();
+ _currentTime = 0;
+ _isQuitting = false;
+ _isRestoring = false;
+
+ debug("HadeschEngine::ctor");
+}
+
+HadeschEngine::~HadeschEngine() {
+ debug("HadeschEngine::dtor");
+ DebugMan.clearAllDebugChannels();
+ for (unsigned i = 0; i < _winCursors.size(); i++) {
+ delete _winCursors[i];
+ _winCursors[i] = nullptr;
+ }
+ for (unsigned i = 0; i < _macCursors.size(); i++) {
+ delete _macCursors[i];
+ _macCursors[i] = nullptr;
+ }
+}
+
+void HadeschEngine::setVideoRoom(Common::SharedPtr<VideoRoom> room,
+ Common::SharedPtr<Handler> handler,
+ RoomId roomId) {
+ assert(!_isInOptions || _isRestoring);
+ _sceneVideoRoom = room;
+ _sceneHandler = handler;
+ _currentTime = 0;
+ _sceneStartTime = _system->getMillis();
+ if (!_isRestoring) {
+ _persistent._previousRoomId = _persistent._currentRoomId;
+ _persistent._currentRoomId = roomId;
+ }
+ _sceneTimers.clear();
+}
+
+Common::Point HadeschEngine::getMousePos() {
+ return _mousePos;
+}
+
+void HadeschEngine::handleEvent(EventHandlerWrapper event) {
+ event();
+}
+
+void HadeschEngine::newGame() {
+ _persistent = Persistent();
+ _persistent._quest = kCreteQuest;
+ moveToRoom(kWallOfFameRoom);
+}
+
+#if defined(USE_ZLIB)
+
+struct WiseFile {
+ uint start;
+ uint end;
+ uint uncompressedLength;
+};
+
+// Some variants use wise installer. Wise installer use raw zlib compressed files
+// Rather than parsing out the wise structures, we just store offsets for
+// the files we care about
+static const struct {
+ const char *md5;
+ uint setupLength;
+ WiseFile _wdPod;
+ WiseFile _hadeschExe;
+} setups[] = {
+ {
+ // Russian, Windows
+ "9ddf1b0b271426b9d023dbf3edbb1caa",
+ 7491209,
+ {0xB8DA2, 0x7246CB, 8691909},
+ {0x4109C, 0xB4628, 1007616}
+ }
+};
+
+Common::MemoryReadStream *readWiseFile(Common::File &setupFile, const struct WiseFile &wiseFile) {
+ // -4 to skip CRC
+ byte *compressedBuffer = new byte[wiseFile.end - wiseFile.start - 4];
+ byte *uncompressedBuffer = new byte[wiseFile.uncompressedLength];
+ setupFile.seek(wiseFile.start);
+ setupFile.read(compressedBuffer, wiseFile.end - wiseFile.start - 4);
+ if (!Common::inflateZlibHeaderless(uncompressedBuffer, wiseFile.uncompressedLength,
+ compressedBuffer, wiseFile.end - wiseFile.start - 4)) {
+ debug("wise inflate failed");
+ delete[] compressedBuffer;
+ delete[] uncompressedBuffer;
+ return nullptr;
+ }
+
+ delete[] compressedBuffer;
+ return new Common::MemoryReadStream(uncompressedBuffer, wiseFile.uncompressedLength);
+}
+#endif
+
+Common::ErrorCode HadeschEngine::loadWindowsCursors(Common::PEResources &exe) {
+ for (unsigned i = 0; i < ARRAYSIZE(cursorids); i++) {
+ Graphics::WinCursorGroup *group = Graphics::WinCursorGroup::createCursorGroup(&exe, cursorids[i]);
+
+ if (!group) {
+ debug("Cannot find cursor group %d", cursorids[i]);
+ return Common::kUnsupportedGameidError;
+ }
+
+ _cursors.push_back(group->cursors[0].cursor);
+ _winCursors.push_back(group);
+ }
+
+ return Common::kNoError;
+}
+
+Common::ErrorCode HadeschEngine::loadCursors() {
+ debug("HadeschEngine: loading cursors");
+
+ {
+ Common::PEResources exe = Common::PEResources();
+ if (exe.loadFromEXE("HADESCH.EXE")) {
+ return loadWindowsCursors(exe);
+ }
+ }
+
+ const char *const macPaths[] = {
+ "Hades_-_Copy_To_Hard_Drive/Hades_Challenge/Hades_Challenge_PPC",
+ "Hades - Copy To Hard Drive/Hades Challenge/Hades Challenge PPC"
+ };
+
+ for (uint j = 0; j < ARRAYSIZE(macPaths); ++j) {
+ Common::MacResManager resMan = Common::MacResManager();
+ if (!resMan.open(macPaths[j])) {
+ continue;
+ }
+
+ for (unsigned i = 0; i < ARRAYSIZE(cursorids); i++) {
+ Common::SeekableReadStream *stream = resMan.getResource(MKTAG('c','r','s','r'), cursorids[i]);
+ if (!stream) {
+ debug("Couldn't load cursor %d", cursorids[i]);
+ return Common::kUnsupportedGameidError;
+ }
+ Graphics::MacCursor *macCursor = new Graphics::MacCursor();
+ macCursor->readFromStream(*stream);
+ _cursors.push_back(macCursor);
+ delete stream;
+ _macCursors.push_back(macCursor);
+ }
+ return Common::kNoError;
+ }
+
+#if defined(USE_ZLIB)
+ Common::File setupFile;
+ if (setupFile.open("Setup.exe")) {
+ uint len = setupFile.size();
+ Common::String md5 = Common::computeStreamMD5AsString(setupFile, len);
+ for (uint chosenSetup = 0; chosenSetup < ARRAYSIZE(setups); chosenSetup++) {
+ if (setups[chosenSetup].setupLength == len && setups[chosenSetup].md5 == md5) {
+ Common::MemoryReadStream *wdPod = readWiseFile(setupFile, setups[0]._wdPod);
+ if (!wdPod) {
+ debug("wd.pod inflate failed");
+ return Common::kUnsupportedGameidError;
+ }
+ _wdPodFile = Common::SharedPtr<PodFile>(new PodFile("WD.POD"));
+ _wdPodFile->openStore(Common::SharedPtr<Common::SeekableReadStream>(wdPod));
+
+ Common::MemoryReadStream *hadeschExe = readWiseFile(setupFile, setups[0]._hadeschExe);
+ if (!hadeschExe) {
+ debug("hadesch.exe inflate failed");
+ return Common::kUnsupportedGameidError;
+ }
+
+ Common::PEResources exe = Common::PEResources();
+ if (exe.loadFromEXE(hadeschExe)) {
+ return loadWindowsCursors(exe);
+ }
+ }
+ }
+ }
+#endif
+
+ debug("Cannot open hadesch.exe");
+ return Common::kUnsupportedGameidError;
+}
+
+static const char *roomCheats[] = {
+ "",
+ "in",
+ "mo",
+ "ht",
+ "se",
+ "at",
+ "mi",
+ "me",
+ "ar",
+ "tr",
+ "ca",
+ "pr",
+ "th",
+ "cp",
+ "mp",
+ "dw",
+ "mn",
+ "vt",
+ "nr",
+ "htr",
+ "ff",
+ "mm",
+ "hc",
+ "cr",
+ "op"
+};
+
+static const char *itemCheats[] = {
+ "",
+ "",
+ "straw",
+ "stone",
+ "bricks",
+ "message",
+ "key",
+ "decree",
+ "wood",
+ "statue1",
+ "statue2",
+ "statue3",
+ "statue4",
+ "statue",
+ "coin",
+ "potion",
+ "shield",
+ "sword",
+ "bag",
+ "helmet",
+ "sandals",
+ "torch"
+};
+
+bool HadeschEngine::handleGenericCheat(const Common::String &cheat) {
+ if (cheat == "cheatsoff") {
+ _cheatsEnabled = false;
+ return true;
+ }
+
+ for (int i = kIntroRoom; i < kOptionsRoom; i++)
+ if (cheat == roomCheats[i]) {
+ moveToRoom((RoomId) i);
+ getVideoRoom()->disableMouse();
+ return true;
+ }
+
+ if (cheat == "commandson") {
+ getVideoRoom()->enableMouse();
+ return true;
+ }
+
+ if (cheat == "commandsoff") {
+ getVideoRoom()->disableMouse();
+ return true;
+ }
+
+ if (cheat == "cretequest") {
+ _persistent._quest = kCreteQuest;
+ _persistent._roomVisited[kWallOfFameRoom] = true;
+ return true;
+ }
+
+ if (cheat == "troyquest") {
+ _persistent._quest = kTroyQuest;
+ _persistent._roomVisited[kWallOfFameRoom] = true;
+ _persistent._roomVisited[kMinotaurPuzzle] = true;
+
+ _persistent._powerLevel[0] = MAX(_persistent._powerLevel[0], 1);
+ return true;
+ }
+
+ if (cheat == "medusaquest") {
+ _persistent._quest = kMedusaQuest;
+ _persistent._roomVisited[kWallOfFameRoom] = true;
+ _persistent._roomVisited[kMinotaurPuzzle] = true;
+ _persistent._roomVisited[kTrojanHorsePuzzle] = true;
+
+ for (int i = 0; i < 2; i++)
+ _persistent._powerLevel[i] = MAX(_persistent._powerLevel[i], 1);
+ return true;
+ }
+
+ if (cheat == "rescuephilquest") {
+ _persistent._quest = kRescuePhilQuest;
+ _persistent._roomVisited[kWallOfFameRoom] = true;
+ _persistent._roomVisited[kMedusaPuzzle] = true;
+ _persistent._roomVisited[kTrojanHorsePuzzle] = true;
+ _persistent._roomVisited[kMinotaurPuzzle] = true;
+
+ for (int i = 0; i < 3; i++)
+ _persistent._powerLevel[i] = MAX(_persistent._powerLevel[i], 1);
+
+ return true;
+ }
+
+ // TODO: "noquest", "op", "click*", "memory", "showhotspots", "hidehotspots", "showcursor",
+ // giveall, takeall, givekeyanddecree, takekeyanddecree
+
+ for (int i = kStraw; i <= kTorch; i++)
+ if (cheat == Common::String("give") + itemCheats[i]) {
+ _heroBelt->placeToInventory((InventoryItem)i);
+ return true;
+ }
+
+ for (int i = kStraw; i <= kTorch; i++)
+ if (cheat == Common::String("take") + itemCheats[i]) {
+ _heroBelt->removeFromInventory((InventoryItem)i);
+ return true;
+ }
+
+ if (cheat == "hero") {
+ _persistent._gender = kMale;
+ return true;
+ }
+
+ if (cheat == "heroine") {
+ _persistent._gender = kFemale;
+ return true;
+ }
+
+ if (cheat.matchString("powerstrength#")) {
+ _persistent._powerLevel[kPowerStrength] = cheat.substr(13).asUint64();
+ return true;
+ }
+
+ if (cheat.matchString("powerstealth#")) {
+ _persistent._powerLevel[kPowerStealth] = cheat.substr(12).asUint64();
+ return true;
+ }
+
+ if (cheat.matchString("powerwisdom#")) {
+ _persistent._powerLevel[kPowerWisdom] = cheat.substr(11).asUint64();
+ return true;
+ }
+
+ if (cheat.matchString("powerall#")) {
+ int val = cheat.substr(8).asUint64();
+ _persistent._powerLevel[kPowerWisdom] = val;
+ _persistent._powerLevel[kPowerStealth] = val;
+ _persistent._powerLevel[kPowerStrength] = val;
+ return true;
+ }
+
+ return false;
+}
+
+void HadeschEngine::resetOptionsRoom() {
+ _optionsRoom = Common::SharedPtr<VideoRoom>(new VideoRoom("", "", "OPAssets.txt"));
+}
+
+void HadeschEngine::enterOptions() {
+ _isInOptions = true;
+ _optionsEnterTime = _system->getMillis();
+ _sceneVideoRoom->pause();
+ resetOptionsRoom();
+ _optionsHandler = makeOptionsHandler();
+ _optionsHandler->prepareRoom();
+}
+
+void HadeschEngine::enterOptionsCredits() {
+ if (_isInOptions) {
+ _sceneStartTime += _system->getMillis() - _optionsEnterTime;
+ }
+ _isInOptions = true;
+ _optionsEnterTime = _system->getMillis();
+ _optionsRoom = Common::SharedPtr<VideoRoom>(new VideoRoom("CREDITS", "CR", ""));
+ _optionsHandler = makeCreditsHandler(true);
+ _optionsHandler->prepareRoom();
+}
+
+void HadeschEngine::exitOptions() {
+ _isInOptions = false;
+ _sceneStartTime += _system->getMillis() - _optionsEnterTime;
+ _optionsHandler.reset();
+ _optionsRoom.reset();
+ _sceneVideoRoom->unpause();
+}
+
+Common::Error HadeschEngine::run() {
+ debug("HadeschEngine::run");
+
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+ SearchMan.addSubDirectoryMatching(gameDataDir, "WIN9x");
+
+ Common::ErrorCode err = loadCursors();
+ if (err != Common::kNoError)
+ return err;
+
+ if (!_wdPodFile) {
+ const char *const wdpodpaths[] = {
+ "WIN9x/WORLD/WD.POD", "WD.POD",
+ "Hades_-_Copy_To_Hard_Drive/Hades_Challenge/World/wd.pod",
+ "Hades - Copy To Hard Drive/Hades Challenge/World/wd.pod"};
+ debug("HadeschEngine: loading wd.pod");
+ for (uint i = 0; i < ARRAYSIZE(wdpodpaths); ++i) {
+ Common::SharedPtr<Common::File> file(new Common::File());
+ if (file->open(wdpodpaths[i])) {
+ _wdPodFile = Common::SharedPtr<PodFile>(new PodFile("WD.POD"));
+ _wdPodFile->openStore(file);
+ break;
+ }
+ }
+ }
+
+ if (!_wdPodFile) {
+ debug("Cannot find WD.POD");
+ return Common::kUnsupportedGameidError;
+ }
+
+ _cdScenesPath = "";
+
+ // It's tempting to use SearchMan for this but it
+ // doesn't work because we need to access subdirs based
+ // on cdScenePath
+ const char *const scenepaths[] = {"CDAssets/", "Scenes/"};
+ for (uint i = 0; i < ARRAYSIZE(scenepaths); ++i) {
+ Common::ScopedPtr<Common::File> file(new Common::File());
+ if (file->open(Common::String(scenepaths[i]) + "OLYMPUS/OL.POD")) {
+ _cdScenesPath = scenepaths[i];
+ break;
+ }
+ }
+
+ if (_cdScenesPath == "") {
+ debug("Cannot find OL.POD");
+ return Common::kUnsupportedGameidError;
+ }
+
+ debug("HadeschEngine: intro");
+ initGraphics(kVideoWidth, kVideoHeight);
+
+ _heroBelt = Common::SharedPtr<HeroBelt>(new HeroBelt());
+ _gfxContext = Common::SharedPtr<GfxContext8Bit>(new GfxContext8Bit(2 * kVideoWidth + 10, kVideoHeight + 50));
+ _isInOptions = false;
+
+ debug("HadeschEngine: moving to main loop");
+ _nextRoom = kIntroRoom;
+
+ while (true) {
+ if (_isRestoring) {
+ moveToRoomReal(_persistent._currentRoomId);
+ _isRestoring = false;
+ CursorMan.showMouse(true);
+ CursorMan.replaceCursor(_cursors[3]);
+ } else if (_nextRoom != kInvalidRoom) {
+ moveToRoomReal(_nextRoom);
+ _nextRoom = kInvalidRoom;
+ CursorMan.showMouse(true);
+ CursorMan.replaceCursor(_cursors[3]);
+ }
+
+ if (_isQuitting) {
+ return Common::kNoError;
+ }
+ Common::Event event;
+ bool stopVideo = false;
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RETURN_TO_LAUNCHER:
+ return Common::kNoError;
+ case Common::EVENT_LBUTTONDOWN: {
+ if(getVideoRoom()->isMouseEnabled() && getVideoRoom()->isHeroBeltEnabled()
+ && _heroBelt->isPositionOverHeroBelt(event.mouse)) {
+ debug("handling belt click");
+ _heroBelt->handleClick(event.mouse);
+ break;
+ }
+ const Common::String &q = getVideoRoom()->mapClick(event.mouse);
+ debug("handling click on <%s>", q.c_str());
+ if (getVideoRoom()->isHeroBeltEnabled() && _heroBelt->isHoldingItem())
+ getCurrentHandler()->handleClickWithItem(q, _heroBelt->getHoldingItem());
+ else {
+ getCurrentHandler()->handleAbsoluteClick(event.mouse);
+ getCurrentHandler()->handleClick(q);
+ }
+ }
+ break;
+ case Common::EVENT_LBUTTONUP:
+ getCurrentHandler()->handleUnclick(getVideoRoom()->mapClick(event.mouse), event.mouse);
+ break;
+ case Common::EVENT_KEYDOWN:
+ // TODO: make equivalents for mobile devices. Keyboard is
+ // used for 4 things:
+ //
+ // * Skipping cutscenes (press space)
+ // * Entering name.
+ // Original requires a non-empty name. We allow an
+ // empty name.
+ // * Optional save name
+ // * Cheats
+ if (event.kbd.keycode == Common::KEYCODE_SPACE)
+ stopVideo = true;
+ if ((event.kbd.ascii >= 'a' && event.kbd.ascii <= 'z')
+ || (event.kbd.ascii >= '0' && event.kbd.ascii <= '9')) {
+ _cheat += event.kbd.ascii;
+ }
+ if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+ Common::String cheat = _cheat;
+ _cheat = "";
+ if (cheat == "qazxcdewsrfvbnhytg") {
+ debug("Cheats enabled");
+ _cheatsEnabled = true;
+ break;
+ }
+ if (_cheatsEnabled) {
+ if (handleGenericCheat(cheat))
+ break;
+ if (getCurrentHandler()->handleCheat(cheat))
+ break;
+ }
+ }
+ getCurrentHandler()->handleKeypress(event.kbd.ascii);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (_isInOptions) {
+ _currentTime = _system->getMillis() - _optionsEnterTime;
+ } else {
+ _currentTime = _system->getMillis() - _sceneStartTime;
+ for (Common::List<Timer>::iterator it = _sceneTimers.begin();
+ it != _sceneTimers.end();) {
+ if ((it->period_count != 0 && it->next_time < _currentTime)
+ || (it->skippable && stopVideo)) {
+ it->next_time = _currentTime + it->period;
+ if (it->period_count != -1) {
+ it->period_count--;
+ }
+ handleEvent(it->event);
+ }
+
+ if (it->period_count == 0) {
+ it = _sceneTimers.erase(it);
+ } else
+ it++;
+ }
+ }
+
+ Common::String oldhotzone = getVideoRoom()->getHotZone();
+ _mousePos = _eventMan->getMousePos();
+ getVideoRoom()->computeHotZone(_currentTime, _mousePos);
+
+ Common::String newhotzone = getVideoRoom()->getHotZone();
+
+ if (oldhotzone != newhotzone) {
+ getCurrentHandler()->handleMouseOut(oldhotzone);
+ getCurrentHandler()->handleMouseOver(newhotzone);
+ }
+
+ getCurrentHandler()->frameCallback();
+
+ getVideoRoom()->nextFrame(_gfxContext, _currentTime, stopVideo);
+
+ if (getVideoRoom()->getDragged()) {
+ CursorMan.showMouse(true);
+ CursorMan.replaceCursor(getVideoRoom()->getDragged());
+ } else if (getVideoRoom()->isHeroBeltEnabled()
+ && _heroBelt->isHoldingItem()) {
+ const Graphics::Cursor *cursor = _heroBelt->getHoldingItemCursor(
+ getVideoRoom()->getCursorAnimationFrame(_currentTime));
+ CursorMan.showMouse(true);
+ CursorMan.replaceCursor(cursor);
+ } else {
+ int cursor = getVideoRoom()->getCursor();
+ if (cursor >= 0) {
+ CursorMan.showMouse(true);
+ CursorMan.replaceCursor(_cursors[cursor]);
+ } else {
+ CursorMan.showMouse(true);
+ CursorMan.replaceCursor(_cursors[3]);
+ }
+ }
+
+ _system->updateScreen();
+ _system->delayMillis(15);
+ }
+
+ return Common::kNoError;
+}
+
+Common::RandomSource &HadeschEngine::getRnd() {
+ return _rnd;
+}
+
+bool HadeschEngine::hasFeature(EngineFeature f) const {
+ return
+ f == kSupportsReturnToLauncher ||
+ f == kSupportsLoadingDuringRuntime ||
+ f == kSupportsSavingDuringRuntime ||
+ f == kSupportsChangingOptionsDuringRuntime;
+}
+
+Common::Error HadeschEngine::loadGameStream(Common::SeekableReadStream *stream) {
+ Common::Serializer s(stream, nullptr);
+ if (!_persistent.syncGameStream(s))
+ return Common::kUnknownError;
+
+ _isRestoring = true;
+
+ return Common::kNoError;
+}
+
+Common::Error HadeschEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
+ Common::Serializer s(nullptr, stream);
+ if (isAutosave)
+ _persistent._slotDescription = "Autosave";
+ bool res = _persistent.syncGameStream(s);
+ _persistent._slotDescription = "";
+ return res ? Common::kNoError
+ : Common::kUnknownError;
+}
+
+const Common::String &HadeschEngine::getCDScenesPath() const {
+ return _cdScenesPath;
+}
+
+void HadeschEngine::addTimer(EventHandlerWrapper eventId, int32 start_time, int period, int repeat, bool skippable) {
+ struct Timer timer;
+ assert(!_isInOptions);
+ timer.next_time = start_time + period;
+ timer.period_count = repeat;
+ timer.period = period;
+ timer.event = eventId;
+ timer.skippable = skippable;
+ _sceneTimers.push_back(timer);
+}
+
+void HadeschEngine::addTimer(EventHandlerWrapper eventId, int period, int repeat) {
+ addTimer(eventId, _currentTime, period, repeat, false);
+}
+
+void HadeschEngine::addSkippableTimer(EventHandlerWrapper eventId, int period, int repeat) {
+ addTimer(eventId, _currentTime, period, repeat, true);
+}
+
+void HadeschEngine::cancelTimer(int eventId) {
+ assert(!_isInOptions);
+ for (Common::List<Timer>::iterator it = _sceneTimers.begin();
+ it != _sceneTimers.end();) {
+ if (it->event == eventId) {
+ it = _sceneTimers.erase(it);
+ } else
+ it++;
+ }
+}
+
+Common::SharedPtr<Handler> HadeschEngine::getCurrentHandler() {
+ return _isInOptions ? _optionsHandler : _sceneHandler;
+}
+
+Common::SharedPtr<VideoRoom> HadeschEngine::getVideoRoom() {
+ return _isInOptions ? _optionsRoom : _sceneVideoRoom;
+}
+
+void HadeschEngine::moveToRoomReal(RoomId id) {
+ if (_sceneVideoRoom)
+ _sceneVideoRoom->finish();
+
+ _heroBelt->reset();
+
+ switch (id) {
+ case kWallOfFameRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("WALLFAME", "WF", "HTAssets.txt")),
+ makeWallOfFameHandler(), id);
+ break;
+ case kArgoRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("ARGO", "AR", "ARAssets.txt")),
+ makeArgoHandler(), id);
+ break;
+ case kCreteRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CRETE", "CR", "")),
+ makeCreteHandler(), id);
+ break;
+ case kOlympusRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("OLYMPUS", "OL", "MOAssets.txt")),
+ makeOlympusHandler(), id);
+ break;
+ case kMinosPalaceRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MINOS", "MI", "MIAssets.txt")),
+ makeMinosHandler(), id);
+ break;
+ case kDaedalusRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("DAEDALUS", "DA", "DWAssets.txt")),
+ makeDaedalusHandler(), id);
+ break;
+ case kSeriphosRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("SERIPHOS", "SE", "")),
+ makeSeriphosHandler(), id);
+ break;
+ case kMedIsleRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MEDISLE", "MI", "")),
+ makeMedIsleHandler(), id);
+ break;
+ case kMedusaPuzzle:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MEDUSA", "ME", "")),
+ makeMedusaHandler(), id);
+ break;
+ case kTroyRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("TROY", "TR", "")),
+ makeTroyHandler(), id);
+ break;
+ case kTrojanHorsePuzzle:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("TROJAN", "TH", "")),
+ makeTrojanHandler(), id);
+ break;
+ case kMinotaurPuzzle:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MINOTAUR", "MM", "")),
+ makeMinotaurHandler(), id);
+ break;
+ case kQuiz:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("HADESCH", "HC", "HcAssets.txt")),
+ makeQuizHandler(), id);
+ break;
+ case kCatacombsRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CATACOMB", "CA", "CaAssets.txt")),
+ makeCatacombsHandler(), id);
+ break;
+ case kPriamRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("PRIAM", "PR", "PrAssets.txt")),
+ makePriamHandler(), id);
+ break;
+ case kAthenaRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("ATHENA", "AT", "")),
+ makeAthenaHandler(), id);
+ break;
+ case kVolcanoRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("VOLCANO", "VO", "VTAssets.txt")),
+ makeVolcanoHandler(), id);
+ break;
+ case kRiverStyxRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("NEARR", "NR", "NRAssets.txt")),
+ makeRiverStyxHandler(), id);
+ break;
+ case kHadesThroneRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("THRONE", "TH", "HTRAsset.txt")),
+ makeHadesThroneHandler(), id);
+ break;
+ case kCreditsRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CREDITS", "CR", "")),
+ makeCreditsHandler(false), id);
+ break;
+ case kIntroRoom:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("INTRO", "IN", "")),
+ makeIntroHandler(), id);
+ break;
+ case kFerrymanPuzzle:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("FERRY", "FF", "")),
+ makeFerryHandler(), id);
+ break;
+ case kMonsterPuzzle:
+ setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MONSTER", "MM", "")),
+ makeMonsterHandler(), id);
+ break;
+ default:
+ debug("unknown room %d", id);
+ assert(0);
+ return;
+ }
+
+ _sceneHandler->prepareRoom();
+
+ _persistent._roomVisited[id] = true;
+}
+
+int HadeschEngine::firstAvailableSlot() {
+ for (unsigned slot = 3; ; slot++) {
+ SaveStateDescriptor desc = getMetaEngine().querySaveMetaInfos(_targetName.c_str(), slot);
+ if (desc.getSaveSlot() == -1 && !desc.getWriteProtectedFlag())
+ return slot;
+ }
+}
+
+void HadeschEngine::quit() {
+ _isQuitting = true;
+}
+
+bool HadeschEngine::hasAnySaves() {
+ Common::SaveFileManager *saveFileMan = getSaveFileManager();
+ Common::StringArray filenames;
+ Common::String pattern(getMetaEngine().getSavegameFilePattern(_targetName.c_str()));
+
+ filenames = saveFileMan->listSavefiles(pattern);
+
+ return !filenames.empty();
+}
+
+Common::Array<HadeschSaveDescriptor> HadeschEngine::getHadeschSavesList() {
+ Common::SaveFileManager *saveFileMan = getSaveFileManager();
+ Common::StringArray filenames;
+ Common::String pattern(getMetaEngine().getSavegameFilePattern(_targetName.c_str()));
+
+ filenames = saveFileMan->listSavefiles(pattern);
+
+ Common::Array<HadeschSaveDescriptor> saveList;
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the last 2 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 3);
+
+ if (slotNum >= 0) {
+ Common::ScopedPtr<Common::InSaveFile> in(saveFileMan->openForLoading(*file));
+ if (!in) {
+ continue;
+ }
+
+ Common::Serializer s(in.get(), nullptr);
+
+ saveList.push_back(HadeschSaveDescriptor(s, slotNum));
+ }
+ }
+
+ // Sort saves based on slot number.
+ // TODO: change it to chronological save id
+ Common::sort(saveList.begin(), saveList.end(), HadeschSaveDescriptorSlotComparator());
+ return saveList;
+}
+
+void HadeschEngine::deleteSave(int slot) {
+ getMetaEngine().removeSaveState(_targetName.c_str(), slot);
+}
+
+void EventHandlerWrapper::operator()() const {
+ if (_handler && _eventId == -1)
+ debug("handling anon event");
+ else if (_eventId != 20001 && _eventId != 14006 && _eventId != 15266)
+ debug("handling event %d", _eventId);
+ if (_handler)
+ _handler->operator()();
+ if (_eventId > 0)
+ g_vm->getCurrentHandler()->handleEvent(_eventId);
+}
+
+bool EventHandlerWrapper::operator==(int b) const {
+ return _eventId == b;
+}
+
+} // End of namespace Hadesch
diff --git a/engines/hadesch/hadesch.h b/engines/hadesch/hadesch.h
new file mode 100644
index 0000000000..182e7f0e58
--- /dev/null
+++ b/engines/hadesch/hadesch.h
@@ -0,0 +1,232 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_HADESCH_H
+#define HADESCH_HADESCH_H
+
+#include "common/random.h"
+#include "common/stream.h"
+#include "common/savefile.h"
+#include "common/list.h"
+
+#include "engines/engine.h"
+#include "engines/savestate.h"
+
+#include "gui/debugger.h"
+#include "graphics/cursor.h"
+#include "hadesch/pod_file.h"
+#include "hadesch/video.h"
+#include "hadesch/enums.h"
+#include "hadesch/event.h"
+#include "hadesch/herobelt.h"
+#include "hadesch/persistent.h"
+
+struct ADGameDescription;
+
+namespace Common {
+class SeekableReadStream;
+class PEResources;
+}
+
+namespace Graphics {
+struct WinCursorGroup;
+struct MacCursor;
+}
+
+namespace Hadesch {
+
+class VideoRoom;
+
+class Handler {
+public:
+ virtual void handleClick(const Common::String &name) = 0;
+ virtual void handleAbsoluteClick(Common::Point pnt) {}
+ virtual bool handleClickWithItem(const Common::String &name, InventoryItem item) {
+ return false;
+ }
+ virtual void handleEvent(int eventId) = 0;
+ virtual void handleMouseOver(const Common::String &name) {}
+ virtual void handleMouseOut(const Common::String &name) {}
+ virtual void frameCallback() {}
+ virtual void handleKeypress(uint16 ucode) {}
+ virtual void prepareRoom() = 0;
+ virtual bool handleCheat(const Common::String &cheat) {
+ return false;
+ }
+ virtual void handleUnclick(const Common::String &name, Common::Point pnt) {}
+ virtual ~Handler() {}
+};
+
+Common::SharedPtr<Hadesch::Handler> makeOlympusHandler();
+Common::SharedPtr<Hadesch::Handler> makeWallOfFameHandler();
+Common::SharedPtr<Hadesch::Handler> makeArgoHandler();
+Common::SharedPtr<Hadesch::Handler> makeCreteHandler();
+Common::SharedPtr<Hadesch::Handler> makeMinosHandler();
+Common::SharedPtr<Hadesch::Handler> makeDaedalusHandler();
+Common::SharedPtr<Hadesch::Handler> makeSeriphosHandler();
+Common::SharedPtr<Hadesch::Handler> makeMedIsleHandler();
+Common::SharedPtr<Hadesch::Handler> makeTroyHandler();
+Common::SharedPtr<Hadesch::Handler> makeMinotaurHandler();
+Common::SharedPtr<Hadesch::Handler> makeQuizHandler();
+Common::SharedPtr<Hadesch::Handler> makeCatacombsHandler();
+Common::SharedPtr<Hadesch::Handler> makePriamHandler();
+Common::SharedPtr<Hadesch::Handler> makeAthenaHandler();
+Common::SharedPtr<Hadesch::Handler> makeVolcanoHandler();
+Common::SharedPtr<Hadesch::Handler> makeRiverStyxHandler();
+Common::SharedPtr<Hadesch::Handler> makeHadesThroneHandler();
+Common::SharedPtr<Hadesch::Handler> makeCreditsHandler(bool inOptions);
+Common::SharedPtr<Hadesch::Handler> makeIntroHandler();
+Common::SharedPtr<Hadesch::Handler> makeFerryHandler();
+Common::SharedPtr<Hadesch::Handler> makeOptionsHandler();
+Common::SharedPtr<Hadesch::Handler> makeMonsterHandler();
+Common::SharedPtr<Hadesch::Handler> makeMedusaHandler();
+Common::SharedPtr<Hadesch::Handler> makeTrojanHandler();
+
+class HadeschEngine : public Engine, Common::NonCopyable {
+public:
+ HadeschEngine(OSystem *syst, const ADGameDescription *desc);
+ ~HadeschEngine() override;
+
+ Common::Error run() override;
+
+ bool hasFeature(EngineFeature f) const override;
+
+ bool canLoadGameStateCurrently() override { return true; }
+ bool canSaveGameStateCurrently() override { return true; }
+ Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
+ Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
+
+ Common::SeekableReadStream *openFile(const Common::String &name, bool addCurrentPath);
+
+ Common::RandomSource &getRnd();
+
+ const Common::String &getCDScenesPath() const;
+
+ Common::SharedPtr<VideoRoom> getVideoRoom();
+
+ void moveToRoom(RoomId id) {
+ _nextRoom = id;
+ _heroBelt->clearHold();
+ }
+
+ void handleEvent(EventHandlerWrapper event);
+ int32 getCurrentTime() {
+ return _currentTime;
+ }
+ Common::Point getMousePos();
+
+ void addTimer(EventHandlerWrapper event, int period, int repeat = 1);
+ void addSkippableTimer(EventHandlerWrapper event, int period, int repeat = 1);
+ void cancelTimer(int eventId);
+
+ Common::SharedPtr<PodFile> getWdPodFile() {
+ return _wdPodFile;
+ }
+
+ RoomId getPreviousRoomId() const {
+ return _persistent._previousRoomId;
+ }
+
+ bool isRoomVisited(RoomId id) const {
+ return _persistent._roomVisited[id];
+ }
+
+ Persistent *getPersistent() {
+ return &_persistent;
+ }
+
+ Common::SharedPtr<Handler> getCurrentHandler();
+
+ Common::SharedPtr<HeroBelt> getHeroBelt() {
+ return _heroBelt;
+ }
+
+ int firstAvailableSlot();
+
+ void newGame();
+ void enterOptions();
+ void resetOptionsRoom();
+ void exitOptions();
+ void enterOptionsCredits();
+ void quit();
+ bool hasAnySaves();
+
+ Common::Array<HadeschSaveDescriptor> getHadeschSavesList();
+ void deleteSave(int slot);
+
+private:
+ void addTimer(EventHandlerWrapper event, int32 start_time, int period,
+ int repeat, bool skippable);
+ void moveToRoomReal(RoomId id);
+ void setVideoRoom(Common::SharedPtr<VideoRoom> scene,
+ Common::SharedPtr<Handler> handler,
+ RoomId roomId);
+ Common::ErrorCode loadCursors();
+ bool handleGenericCheat(const Common::String &cheat);
+ Common::ErrorCode loadWindowsCursors(Common::PEResources &exe);
+
+ struct Timer {
+ int32 next_time;
+ int32 period;
+ int32 period_count;
+ EventHandlerWrapper event;
+ bool skippable;
+ };
+ const ADGameDescription *_desc;
+
+ Common::RandomSource _rnd;
+
+ Common::String _cdScenesPath;
+
+ Common::SharedPtr<VideoRoom> _sceneVideoRoom;
+ Common::SharedPtr<Handler> _sceneHandler;
+ Common::SharedPtr<VideoRoom> _optionsRoom;
+ Common::SharedPtr<Handler> _optionsHandler;
+ bool _isInOptions;
+ uint32 _optionsEnterTime;
+ uint32 _sceneStartTime;
+ int32 _currentTime;
+ Common::Array<Graphics::Cursor *> _cursors;
+ Common::List<Timer> _sceneTimers;
+ Common::SharedPtr<PodFile> _wdPodFile;
+ Common::SharedPtr<HeroBelt> _heroBelt;
+ Common::String _cheat;
+ Common::SharedPtr<GfxContext8Bit> _gfxContext;
+ bool _cheatsEnabled;
+ Common::Point _mousePos;
+
+ Persistent _persistent;
+ RoomId _nextRoom;
+ bool _isRestoring;
+ bool _isQuitting;
+
+ // For freeing purposes
+ Common::Array <Graphics::MacCursor *> _macCursors;
+ Common::Array <Graphics::WinCursorGroup *> _winCursors;
+};
+
+extern HadeschEngine *g_vm;
+
+} // End of namespace Hadesch
+
+#endif
diff --git a/engines/hadesch/herobelt.cpp b/engines/hadesch/herobelt.cpp
new file mode 100644
index 0000000000..00abab15af
--- /dev/null
+++ b/engines/hadesch/herobelt.cpp
@@ -0,0 +1,574 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/file.h"
+#include "hadesch/video.h"
+#include "hadesch/pod_image.h"
+#include "hadesch/tag_file.h"
+#include "hadesch/hadesch.h"
+#include "common/system.h"
+#include "video/smk_decoder.h"
+#include "audio/decoders/aiff.h"
+#include "hadesch/pod_file.h"
+#include "hadesch/baptr.h"
+
+namespace Hadesch {
+
+static const int kHeroBeltMinY = 378;
+static const int kHeroBeltMaxY = 471;
+
+static const char *powerSounds[3][2] = {
+ {"g0280nc0", "g0280ng0"},
+ {"g0280nb0", "g0280nf0"},
+ {"g0280ne0", "g0280nh0"},
+};
+
+static Common::Array <PodImage> loadImageArray(const Common::String &name) {
+ Common::SharedPtr<Common::SeekableReadStream> rs(g_vm->getWdPodFile()->getFileStream(name + ".pod"));
+ PodFile pf2(name);
+ pf2.openStore(rs);
+ return pf2.loadImageArray();
+}
+
+static PodImage loadImage(const Common::String &name) {
+ return loadImageArray(name)[0];
+}
+
+static const struct {
+ const char *background;
+ const char *iconNames;
+ const char *icons;
+
+ // TODO: figure out how the original handles
+ // cursors.
+ const char *iconCursors;
+ const char *iconCursorsBright;
+
+ const char *scrollBg;
+ const char *scrollBgHades;
+ const char *scrollTextCrete;
+ const char *scrollTextTroyFemale;
+ const char *scrollTextTroyMale;
+ const char *scrollTextMedusa;
+ const char *scrollTextHades;
+ const char *powerImageWisdom;
+ const char *powerImageStrength;
+ const char *powerImageStealth;
+} assetTable[3] = {
+ // Warm
+ {
+ "g0010oa0",
+ "g0091ba0",
+ "g0091bb0",
+ "g0091bc0",
+ "g0093bc0",
+ "g0015oy0",
+ "g0015oy3",
+ "g0015td0",
+ "g0015te0",
+ "g0015te3",
+ "g0015tf0",
+ "g0015tg0",
+ "g0290oa0",
+ "g0290ob0",
+ "g0290oc0"
+ },
+
+ // Cold
+ {
+ "g0010ob0",
+ "g0092ba0",
+ "g0092bb0",
+ "g0091bc0",
+ "g0093bc0",
+ "g0015oy2",
+ "g0015oy5",
+ "g0015td2",
+ "g0015te2",
+ "g0015te5",
+ "g0015tf2",
+ "g0015tg2",
+ "g0092oa0",
+ "g0092ob0",
+ "g0092oc0"
+ },
+
+ // Cool
+ {
+ "g0010oc0",
+ "g0093ba0",
+ "g0093bb0",
+ "g0091bc0",
+ "g0093bc0",
+ "g0015oy1",
+ "g0015oy4",
+ "g0015td1",
+ "g0015te1",
+ "g0015te4",
+ "g0015tf1",
+ "g0015tg1",
+ "g0093oa0",
+ "g0093ob0",
+ "g0093oc0"
+ }
+};
+
+HeroBelt::HeroBelt() {
+ for (int i = 0; i < 3; i++) {
+ _background[i] = loadImage(assetTable[i].background);
+ _iconNames[i] = loadImageArray(assetTable[i].iconNames);
+ _icons[i] = loadImageArray(assetTable[i].icons);
+ _iconCursors[i] = loadImageArray(assetTable[i].iconCursors);
+ _iconCursorsBright[i] = loadImageArray(assetTable[i].iconCursorsBright);
+ _powerImages[0][i] = loadImageArray(assetTable[i].powerImageStrength);
+ _powerImages[1][i] = loadImageArray(assetTable[i].powerImageStealth);
+ _powerImages[2][i] = loadImageArray(assetTable[i].powerImageWisdom);
+
+ _scrollBg[i] = loadImage(assetTable[i].scrollBg);
+ _scrollBgHades[i] = loadImage(assetTable[i].scrollBgHades);
+ _scrollTextCrete[i] = loadImage(assetTable[i].scrollTextCrete);
+ _scrollTextTroyFemale[i] = loadImage(assetTable[i].scrollTextTroyFemale);
+ _scrollTextTroyMale[i] = loadImage(assetTable[i].scrollTextTroyMale);
+ _scrollTextMedusa[i] = loadImage(assetTable[i].scrollTextMedusa);
+ _scrollTextHades[i] = loadImage(assetTable[i].scrollTextHades);
+ }
+
+ _branchOfLife = loadImageArray("v7150ba0");
+ _overHeroBelt = false;
+ _heroBeltY = kHeroBeltMaxY;
+ _bottomEdge = false;
+ _heroBeltSpeed = 0;
+ _edgeStartTime = 0;
+ _animateItem = kNone;
+ _animateItemTargetSlot = -1;
+ _hotZone = -1;
+ _holdingItem = kNone;
+ _holdingSlot = -1;
+ _highlightTextIdx = -1;
+ _showScroll = false;
+ _hotZones.readHotzones(
+ Common::SharedPtr<Common::SeekableReadStream>(g_vm->getWdPodFile()->getFileStream("HeroBelt.HOT")),
+ true);
+}
+
+void HeroBelt::computeHotZone(int time, Common::Point mousePos, bool mouseEnabled) {
+ bool wasBottomEdge = _bottomEdge;
+
+ _overHeroBelt = false;
+ _bottomEdge = false;
+
+ _mousePos = mousePos;
+
+ if (!mouseEnabled) {
+ return;
+ }
+
+ _bottomEdge = mousePos.y > 460;
+ _overHeroBelt = (_bottomEdge && _heroBeltSpeed < 0) || mousePos.y > _heroBeltY;
+
+ if (!wasBottomEdge && _bottomEdge)
+ _edgeStartTime = time;
+
+ _currentTime = time;
+
+ int wasHotZone = _hotZone;
+ _hotZone = _hotZones.pointToIndex(mousePos);
+ if (_hotZone >= 0 && wasHotZone < 0) {
+ _startHotTime = time;
+ }
+
+ computeHighlight();
+}
+
+static bool isInFrieze() {
+ Persistent *persistent = g_vm->getPersistent();
+ return (persistent->_currentRoomId == kMinotaurPuzzle && persistent->_quest != kCreteQuest)
+ || (persistent->_currentRoomId == kTrojanHorsePuzzle && persistent->_quest != kTroyQuest)
+ || (persistent->_currentRoomId == kMedusaPuzzle && persistent->_quest != kMedusaQuest)
+ || (persistent->_currentRoomId == kFerrymanPuzzle && persistent->_quest != kRescuePhilQuest)
+ || (persistent->_currentRoomId == kMonsterPuzzle && persistent->_quest != kRescuePhilQuest);
+}
+
+void HeroBelt::render(Common::SharedPtr<GfxContext> context, int time, Common::Point viewPoint) {
+ Persistent *persistent = g_vm->getPersistent();
+ Common::Point beltPoint;
+
+ // TODO: use beltopen hotzone instead?
+ if (persistent->_currentRoomId == kMonsterPuzzle) {
+ _heroBeltY = kHeroBeltMinY;
+ } else {
+ if (_bottomEdge && _heroBeltY == kHeroBeltMaxY
+ && (time > _edgeStartTime + 500)) {
+ _heroBeltSpeed = -10;
+ }
+
+ if (!_overHeroBelt && _heroBeltY != kHeroBeltMaxY
+ && _animateItemTargetSlot == -1) {
+ _showScroll = false;
+ _heroBeltSpeed = +10;
+ }
+
+ if (_heroBeltSpeed != 0) {
+ _heroBeltY += _heroBeltSpeed;
+ if (_heroBeltY <= kHeroBeltMinY) {
+ _heroBeltY = kHeroBeltMinY;
+ _heroBeltSpeed = 0;
+ }
+ if (_heroBeltY >= kHeroBeltMaxY) {
+ _heroBeltY = kHeroBeltMaxY;
+ _heroBeltSpeed = 0;
+ }
+ }
+ }
+
+ _currentTime = time;
+
+ beltPoint = Common::Point(0, _heroBeltY) + viewPoint;
+
+ _background[_colour].render(context, beltPoint);
+
+ if (_animateItem != kNone) {
+ if (_currentTime > _animateItemStartTime + _animItemTime) {
+ _animateItem = kNone;
+ _animateItemTargetSlot = -1;
+ _animItemCallbackEvent();
+ }
+ }
+
+ if (_animateItem != kNone) {
+ Common::Point target = computeSlotPoint(_animateItemTargetSlot, true);
+ Common::Point delta1 = target - _animateItemStartPoint;
+ int elapsed = _currentTime - _animateItemStartTime;
+ double fraction = ((elapsed + 0.0) / _animItemTime);
+ Common::Point deltaPartial = fraction * delta1;
+ Common::Point cur = _animateItemStartPoint + deltaPartial;
+ _iconCursors[_colour][_animateItem - 1].render(
+ context, cur + viewPoint);
+ }
+
+ if (persistent->_currentRoomId == kMonsterPuzzle) {
+ _icons[_colour][kNumberI].render(
+ context,
+ computeSlotPoint(0, false) + viewPoint
+ + Common::Point(0, 4));
+ _icons[_colour][kNumberII].render(
+ context,
+ computeSlotPoint(1, false) + viewPoint);
+ _icons[_colour][kNumberIII].render(
+ context,
+ computeSlotPoint(2, false) + viewPoint
+ + Common::Point(0, 4));
+ for (unsigned i = 0; i < 3; i++) {
+ _icons[_colour][_thunderboltFrame].render(
+ context,
+ computeSlotPoint(3 + i, false) + viewPoint);
+ }
+
+ _branchOfLife[_branchOfLifeFrame].render(
+ context, beltPoint);
+
+ } else {
+ for (int i = 0; i < inventorySize; i++) {
+ if (i == _animateItemTargetSlot || i == _holdingSlot
+ || persistent->_inventory[i] == kNone) {
+ continue;
+ }
+ _icons[_colour][persistent->_inventory[i] - 1].render(
+ context,
+ computeSlotPoint(i, false) + viewPoint);
+ }
+ }
+
+ _icons[_colour][persistent->_hintsAreEnabled ? kActiveHints : kInactiveHints].render(
+ context,
+ computeSlotPoint(6, false) + viewPoint);
+
+ if (isInFrieze())
+ _icons[_colour][kReturnToWall].render(
+ context,
+ computeSlotPoint(7, false) + viewPoint);
+ else
+ _icons[_colour][kQuestScroll].render(
+ context,
+ computeSlotPoint(7, false) + viewPoint);
+
+ _icons[_colour][kOptionsButton].render(
+ context,
+ computeSlotPoint(8, false) + viewPoint);
+
+ if (_highlightTextIdx >= 0) {
+ _iconNames[_colour][_highlightTextIdx].render(
+ context, beltPoint);
+ }
+
+ for (unsigned i = 0; i < ARRAYSIZE(persistent->_powerLevel); i++)
+ if (persistent->_powerLevel[i] > 0) {
+ _powerImages[i][_colour][(int)i == _selectedPower].render(
+ context, beltPoint);
+ }
+
+ if (_showScroll) {
+ PodImage *bg = _scrollBg, *text = nullptr;
+ switch (persistent->_quest) {
+ case kCreteQuest:
+ text = _scrollTextCrete;
+ break;
+ case kTroyQuest:
+ if (persistent->_gender == kMale)
+ text = _scrollTextTroyMale;
+ else
+ text = _scrollTextTroyFemale;
+ break;
+ case kMedusaQuest:
+ text = _scrollTextCrete;
+ break;
+ case kRescuePhilQuest:
+ text = _scrollTextHades;
+ bg = _scrollBgHades;
+ break;
+ case kEndGame:
+ case kNoQuest:
+ case kNumQuests:
+ break;
+ }
+ bg[_colour].render(context, viewPoint);
+ if (text != nullptr)
+ text[_colour].render(
+ context, viewPoint);
+ }
+}
+
+bool HeroBelt::isPositionOverHeroBelt(Common::Point mousePos) const {
+ return mousePos.y > _heroBeltY;
+}
+
+void HeroBelt::handleClick(Common::Point mousePos) {
+ Persistent *persistent = g_vm->getPersistent();
+ Common::String q = _hotZones.pointToName(mousePos);
+ debug("handling belt click on <%s>", q.c_str());
+ for (int i = 0; i < inventorySize; i++) {
+ if (q == inventoryName(i)) {
+ if (_holdingItem != kNone) {
+ if (persistent->_inventory[i] != kNone &&
+ _holdingSlot != i)
+ return;
+ persistent->_inventory[_holdingSlot] = kNone;
+ persistent->_inventory[i] = _holdingItem;
+ _holdingItem = kNone;
+ _holdingSlot = -1;
+ return;
+ }
+ if (i == _animateItemTargetSlot
+ || persistent->_inventory[i] == kNone)
+ return;
+ _holdingItem = persistent->_inventory[i];
+ _holdingSlot = i;
+ return;
+ }
+ }
+
+ if (q == "quest scroll") {
+ if (isInFrieze())
+ g_vm->moveToRoom(kWallOfFameRoom);
+ else
+ _showScroll = true;
+ }
+
+ if (q == "hints") {
+ persistent->_hintsAreEnabled = !persistent->_hintsAreEnabled;
+ }
+
+ if (q == "options") {
+ g_vm->enterOptions();
+ }
+
+ if (q == "strength")
+ clickPower(kPowerStrength);
+ if (q == "stealth")
+ clickPower(kPowerStealth);
+ if (q == "wisdom")
+ clickPower(kPowerWisdom);
+}
+
+void HeroBelt::clickPower(HeroPower pwr) {
+ Persistent *persistent = g_vm->getPersistent();
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ if (persistent->_currentRoomId == kMonsterPuzzle) {
+ _selectedPower = pwr;
+ return;
+ }
+
+ if (persistent->_quest == kRescuePhilQuest)
+ return;
+
+ room->playSound(powerSounds[pwr][!!persistent->_powerLevel[pwr]]);
+}
+
+void HeroBelt::computeHighlight() {
+ Common::String hotZone = _hotZones.indexToName(_hotZone);
+ Persistent *persistent = g_vm->getPersistent();
+ for (int i = 0; i < inventorySize; i++) {
+ if (hotZone == inventoryName(i)) {
+ if (persistent->_currentRoomId == kMonsterPuzzle) {
+ _highlightTextIdx = i < 3 ? kNumberI + i : kLightning1 + i - 3;
+ return;
+ } else {
+ if (persistent->_inventory[i] != kNone &&
+ _holdingSlot != i) {
+ _highlightTextIdx = persistent->_inventory[i] - 1;
+ return;
+ }
+ }
+ }
+ }
+
+ if (hotZone == "quest scroll") {
+ // TODO: what's with 30 and 28?
+ if (isInFrieze())
+ _highlightTextIdx = kReturnToWall;
+ else if (persistent->_quest == kRescuePhilQuest)
+ _highlightTextIdx = kHadesScroll;
+ else
+ _highlightTextIdx = kQuestScroll;
+ return;
+ }
+
+ if (hotZone == "stealth") {
+ _highlightTextIdx = kPowerOfStealth;
+ return;
+ }
+
+ if (hotZone == "strength") {
+ _highlightTextIdx = kPowerOfStrength;
+ return;
+ }
+
+ if (hotZone == "wisdom") {
+ _highlightTextIdx = kPowerOfWisdom;
+ return;
+ }
+
+ if (hotZone == "hints") {
+ _highlightTextIdx = persistent->_hintsAreEnabled ? kActiveHints : kInactiveHints;
+ return;
+ }
+
+ if (hotZone == "options") {
+ _highlightTextIdx = kOptionsButton;
+ return;
+ }
+
+ _highlightTextIdx = -1;
+}
+
+void HeroBelt::placeToInventory(InventoryItem item, EventHandlerWrapper callbackEvent) {
+ Persistent *persistent = g_vm->getPersistent();
+ unsigned i;
+ for (i = 0; i < inventorySize; i++) {
+ if (persistent->_inventory[i] == kNone)
+ break;
+ }
+
+ if (i == inventorySize) {
+ debug("Out of inventory space");
+ return;
+ }
+
+ persistent->_inventory[i] = item;
+ _animateItem = item;
+ _animItemCallbackEvent = callbackEvent;
+ _animateItemStartPoint = _mousePos;
+ _animateItemTargetSlot = i;
+ _animateItemStartTime = _currentTime;
+ _animItemTime = 2000;
+ _heroBeltSpeed = -10;
+}
+
+void HeroBelt::removeFromInventory(InventoryItem item) {
+ Persistent *persistent = g_vm->getPersistent();
+ for (unsigned i = 0; i < inventorySize; i++) {
+ if (persistent->_inventory[i] == item) {
+ persistent->_inventory[i] = kNone;
+ }
+ }
+
+ if (_holdingItem == item) {
+ _holdingItem = kNone;
+ _holdingSlot = -1;
+ }
+
+ if (_animateItem == item) {
+ _animateItem = kNone;
+ _animateItemTargetSlot = -1;
+ }
+}
+
+void HeroBelt::clearHold() {
+ _holdingItem = kNone;
+ _holdingSlot = -1;
+}
+
+Common::Point HeroBelt::computeSlotPoint(int slot, bool fullyExtended) {
+ Common::Point ret = Common::Point(19, 35);
+ ret += Common::Point(0, (fullyExtended ? kHeroBeltMinY : _heroBeltY));
+ ret += Common::Point(39 * slot, slot % 2 ? 4 : 0);
+ if (slot >= 6) {
+ ret += Common::Point(253, 0);
+ }
+ return ret;
+}
+
+int HeroBelt::getCursor(int time) {
+ Common::String q = _hotZones.indexToName(_hotZone);
+ Persistent *persistent = g_vm->getPersistent();
+ if (q == "")
+ return 0;
+ for (unsigned i = 0; i < inventorySize; i++) {
+ if (q == inventoryName(i)) {
+ if ((int) i != _animateItemTargetSlot
+ && persistent->_inventory[i] != kNone)
+ return (time - _startHotTime) / kDefaultSpeed % 3;
+ return 0;
+ }
+ }
+
+ return (time - _startHotTime) / kDefaultSpeed % 3;
+}
+
+Common::String HeroBelt::inventoryName(int slot) {
+ return Common::String::format("inventory%d", slot);
+}
+
+bool HeroBelt::isHoldingItem() const {
+ return _holdingItem != kNone;
+}
+
+const Graphics::Cursor *HeroBelt::getHoldingItemCursor(int cursorAnimationFrame) const {
+ if ((cursorAnimationFrame / 2) % 2 == 1)
+ return &_iconCursorsBright[_colour][_holdingItem - 1];
+ else
+ return &_iconCursors[_colour][_holdingItem - 1];
+}
+
+}
diff --git a/engines/hadesch/herobelt.h b/engines/hadesch/herobelt.h
new file mode 100644
index 0000000000..fd559efcb8
--- /dev/null
+++ b/engines/hadesch/herobelt.h
@@ -0,0 +1,134 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_HEROBELT_H
+#define HADESCH_HEROBELT_H
+
+#include "common/array.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/rect.h"
+#include "common/ptr.h"
+#include "hadesch/pod_file.h"
+#include "hadesch/pod_image.h"
+#include "common/hashmap.h"
+#include "common/str.h"
+#include "hadesch/enums.h"
+#include "hadesch/event.h"
+
+namespace Hadesch {
+class HeroBelt {
+public:
+ HeroBelt();
+
+ enum HeroBeltColour {
+ kWarm,
+ kCold,
+ kCool,
+ kNumColours
+ };
+
+ void render(Common::SharedPtr<GfxContext> context, int time, Common::Point viewPoint);
+ void computeHotZone(int time, Common::Point mousePos, bool mouseEnabled);
+ void placeToInventory(InventoryItem item, EventHandlerWrapper callbackEvent = EventHandlerWrapper());
+ void removeFromInventory(InventoryItem item);
+ bool isOverHeroBelt() const {
+ return _overHeroBelt;
+ }
+ bool isPositionOverHeroBelt(Common::Point mousePos) const;
+ void handleClick(Common::Point mousePos);
+ int getCursor(int time);
+ bool isHoldingItem() const;
+ InventoryItem getHoldingItem() const {
+ return _holdingItem;
+ }
+ const Graphics::Cursor *getHoldingItemCursor(int cursorAnimationFrame) const;
+ void clearHold();
+ void setColour(HeroBeltColour colour) {
+ _colour = colour;
+ }
+ void reset() {
+ _colour = HeroBelt::kWarm;
+ _selectedPower = kPowerNone;
+ _branchOfLifeFrame = 0;
+ _thunderboltFrame = kLightning1;
+ }
+ void setBranchOfLifeFrame(int frame) {
+ _branchOfLifeFrame = frame;
+ }
+ void setThunderboltFrame(HeroBeltFrame frame) {
+ _thunderboltFrame = frame;
+ }
+ HeroPower getSelectedStrength() {
+ return _selectedPower;
+ }
+
+private:
+ Common::Point computeSlotPoint(int slot, bool fullyExtended);
+ Common::String inventoryName(int slot);
+ void computeHighlight();
+ void clickPower(HeroPower pwr);
+
+ PodImage _background[kNumColours];
+ Common::Array<PodImage> _iconNames[kNumColours];
+ Common::Array<PodImage> _icons[kNumColours];
+ Common::Array<PodImage> _iconCursors[kNumColours];
+ Common::Array<PodImage> _iconCursorsBright[kNumColours];
+ PodImage _scrollBg[kNumColours];
+ PodImage _scrollBgHades[kNumColours];
+ PodImage _scrollTextCrete[kNumColours];
+ PodImage _scrollTextTroyMale[kNumColours];
+ PodImage _scrollTextTroyFemale[kNumColours];
+ PodImage _scrollTextMedusa[kNumColours];
+ PodImage _scrollTextHades[kNumColours];
+ Common::Array<PodImage> _powerImages[3][kNumColours];
+ Common::Array<PodImage> _branchOfLife;
+
+ Common::Point _mousePos;
+ EventHandlerWrapper _animItemCallbackEvent;
+ HotZoneArray _hotZones;
+ HeroBeltColour _colour;
+ int _heroBeltY;
+ int _heroBeltSpeed;
+ bool _overHeroBelt;
+ bool _bottomEdge;
+ int _edgeStartTime;
+ InventoryItem _animateItem;
+ Common::Point _animateItemStartPoint;
+ int _animateItemTargetSlot;
+ int _animateItemStartTime;
+ int _currentTime;
+ int _animItemTime;
+ int _cursor;
+ int _hotZone;
+ int _startHotTime;
+ int _branchOfLifeFrame;
+ InventoryItem _holdingItem;
+ int _holdingSlot;
+ int _highlightTextIdx;
+ bool _showScroll;
+ HeroPower _selectedPower;
+ HeroBeltFrame _thunderboltFrame;
+};
+}
+#endif
diff --git a/engines/hadesch/hotzone.cpp b/engines/hadesch/hotzone.cpp
new file mode 100644
index 0000000000..c5b3202207
--- /dev/null
+++ b/engines/hadesch/hotzone.cpp
@@ -0,0 +1,201 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/file.h"
+
+#include "hadesch/hotzone.h"
+
+namespace Hadesch {
+bool HotZone::isEnabled() const {
+ return _enabled;
+}
+
+void HotZone::setEnabled(bool val) {
+ _enabled = val;
+}
+
+
+void HotZone::setOffset(Common::Point offset) {
+ _offset = offset;
+}
+
+// Following function copied from sword 2.5 and simplified
+bool HotZone::isInside(const Common::Point &point_in) const {
+ Common::Point point = point_in - _offset;
+ int rcross = 0; // Number of right-side overlaps
+
+ // Each edge is checked whether it cuts the outgoing stream from the point
+ for (unsigned i = 0; i < _polygon.size(); i++) {
+ const Common::Point &edgeStart = _polygon[i];
+ const Common::Point &edgeEnd = _polygon[(i + 1) % _polygon.size()];
+
+ // A vertex is a point? Then it lies on one edge of the polygon
+ if (point == edgeStart)
+ return true;
+
+ if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) {
+ int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
+ int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
+ if ((term1 > 0) == (term2 >= 0))
+ rcross++;
+ }
+ }
+
+ // The point is strictly inside the polygon if and only if the number of overlaps is odd
+ return ((rcross % 2) == 1);
+}
+
+const Common::String &HotZone::getID() const {
+ return _hotid;
+}
+
+int HotZone::getICSH() const {
+ return _icsh;
+}
+
+HotZone::HotZone(const Common::Array<Common::Point> &polygon,
+ const Common::String &hotid, bool enabled,
+ int icsh) : _polygon(polygon), _hotid(hotid),
+ _enabled(enabled), _icsh(icsh) {
+}
+
+void HotZoneArray::readHotzones(Common::SharedPtr<Common::SeekableReadStream> hzFile, bool enable, Common::Point offset) {
+ if (!hzFile) {
+ debug("Invalid hzFile");
+ return;
+ }
+ TagFile tf;
+ tf.openStoreHot(hzFile);
+ Common::ScopedPtr<Common::SeekableReadStream> tcshStream(tf.getFileStream(MKTAG('T', 'C', 'S', 'H')));
+ int hzCnt = tcshStream->readUint32LE();
+
+ for (int idx = 0; idx < hzCnt; idx++) {
+ Common::SharedPtr<Common::SeekableReadStream> tdshFile(tf.getFileStream(MKTAG('T', 'D', 'S', 'H'), idx));
+ TagFile tdsh;
+ tdsh.openStoreHotSub(tdshFile);
+
+ Common::SharedPtr<Common::SeekableReadStream> tvshFile(tdsh.getFileStream(MKTAG('T', 'V', 'S', 'H')));
+
+ Common::Array<Common::Point> polygon;
+
+ for (int j = 0; j < tvshFile->size() / 8; j++) {
+ uint32 x = tvshFile->readUint32LE();
+ uint32 y = tvshFile->readUint32LE();
+ polygon.push_back(Common::Point(x, y) + offset);
+ }
+
+ Common::SharedPtr<Common::SeekableReadStream> mnshFile(tdsh.getFileStream(MKTAG('M', 'N', 'S', 'H')));
+
+ int mnshSize = mnshFile->size();
+ char *name = new (std::nothrow) char[mnshSize + 1];
+ mnshFile->read(name, mnshSize);
+ name[mnshSize] = 0;
+
+ Common::SharedPtr<Common::SeekableReadStream> icshFile(tdsh.getFileStream(MKTAG('I', 'C', 'S', 'H')));
+
+ int icsh = icshFile->readUint32LE();
+
+ _hotZones.push_back(HotZone(polygon, name, enable, icsh));
+
+ delete[] name;
+ }
+}
+
+HotZoneArray::HotZoneArray() {
+}
+
+HotZoneArray::HotZoneArray(Common::SharedPtr<Common::SeekableReadStream> hzFile, bool enable) {
+ readHotzones(hzFile, enable);
+}
+
+void HotZoneArray::setHotzoneEnabled(Common::String const &name, bool val) {
+ for (unsigned i = 0; i < _hotZones.size(); i++) {
+ if (_hotZones[i].getID() == name)
+ _hotZones[i].setEnabled(val);
+ }
+}
+
+int HotZoneArray::pointToIndex(Common::Point point) {
+ for (unsigned i = 0; i < _hotZones.size(); i++) {
+ if (_hotZones[i].isEnabled() && _hotZones[i].isInside(point)) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+void HotZoneArray::setHotZoneOffset(const Common::String &name, Common::Point offset) {
+ for (unsigned i = 0; i < _hotZones.size(); i++) {
+ if (_hotZones[i].getID() == name) {
+ _hotZones[i].setOffset(offset);
+ }
+ }
+}
+
+Common::String HotZoneArray::pointToName(Common::Point point) {
+ for (unsigned i = 0; i < _hotZones.size(); i++) {
+ if (_hotZones[i].isEnabled() && _hotZones[i].isInside(point)) {
+ return _hotZones[i].getID();
+ }
+ }
+ return "";
+}
+
+Common::String HotZoneArray::indexToName(int idx) {
+ if (idx >= 0 && idx < (int)_hotZones.size()) {
+ return _hotZones[idx].getID();
+ } else
+ return "";
+}
+
+int HotZoneArray::indexToICSH(int idx) {
+ if (idx < 0 || idx >= (int)_hotZones.size()) {
+ return -1;
+ }
+
+ return _hotZones[idx].getICSH();
+}
+
+int HotZoneArray::indexToCursor(int idx, int frame) {
+ if (idx < 0 || idx >= (int)_hotZones.size()) {
+ return 0;
+ }
+
+ switch (_hotZones[idx].getICSH()) {
+ default:
+ return frame % 3;
+ case 1:
+ return 0;
+ case 2:
+ return 14; // left arrow
+ case 3:
+ return 16; // right arrow
+ case 4:
+ return 13; // up arrow
+ case 5:
+ return 15; // down arrow, never used, just a guess
+ }
+}
+
+}
diff --git a/engines/hadesch/hotzone.h b/engines/hadesch/hotzone.h
new file mode 100644
index 0000000000..f0867cc48b
--- /dev/null
+++ b/engines/hadesch/hotzone.h
@@ -0,0 +1,74 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_HOTZONE_H
+#define HADESCH_HOTZONE_H
+
+#include "common/ptr.h"
+#include "common/str.h"
+#include "common/rect.h"
+
+#include "hadesch/tag_file.h"
+
+namespace Hadesch {
+
+class HotZone {
+public:
+ HotZone(const Common::Array<Common::Point> &polygon,
+ const Common::String &hotid, bool enabled,
+ int icsh);
+ bool isInside(const Common::Point &pnt) const;
+ const Common::String &getID() const;
+ void load(const TagFile &file, int idx);
+ void setEnabled(bool enabled);
+ bool isEnabled() const;
+ void setOffset(Common::Point offset);
+ int getICSH() const;
+private:
+ Common::String _hotid;
+ Common::Array<Common::Point> _polygon;
+ Common::Point _offset;
+ bool _enabled;
+ int _icsh;
+};
+
+class HotZoneArray {
+public:
+ HotZoneArray();
+ HotZoneArray(Common::SharedPtr<Common::SeekableReadStream> hzFile, bool enable);
+
+ void setHotzoneEnabled(const Common::String &name, bool enabled);
+ void readHotzones(Common::SharedPtr<Common::SeekableReadStream> hzFile,
+ bool enable, Common::Point offset = Common::Point(0, 0));
+ int pointToIndex(Common::Point point);
+ Common::String pointToName(Common::Point point);
+ void setHotZoneOffset(const Common::String &name, Common::Point offset);
+ Common::String indexToName(int idx);
+ int indexToCursor(int idx, int frame);
+ int indexToICSH(int idx);
+private:
+ Common::Array<HotZone> _hotZones;
+};
+
+}
+#endif
diff --git a/engines/hadesch/metaengine.cpp b/engines/hadesch/metaengine.cpp
new file mode 100644
index 0000000000..41a68317b6
--- /dev/null
+++ b/engines/hadesch/metaengine.cpp
@@ -0,0 +1,59 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/system.h"
+#include "common/savefile.h"
+
+#include "hadesch/hadesch.h"
+#include "engines/advancedDetector.h"
+
+class HadeschMetaEngine : public AdvancedMetaEngine {
+public:
+ bool hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail) ||
+ (f == kSavesSupportCreationDate) ||
+ (f == kSavesSupportPlayTime) ||
+ (f == kSavesUseExtendedFormat);
+ }
+
+ bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ if (desc)
+ *engine = new Hadesch::HadeschEngine(syst, desc);
+
+ return desc != 0;
+ }
+
+ const char *getName() const override {
+ return "hadesch";
+ }
+};
+
+#if PLUGIN_ENABLED_DYNAMIC(HADESCH)
+ REGISTER_PLUGIN_DYNAMIC(HADESCH, PLUGIN_TYPE_ENGINE, HadeschMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(HADESCH, PLUGIN_TYPE_ENGINE, HadeschMetaEngine);
+#endif
diff --git a/engines/hadesch/module.mk b/engines/hadesch/module.mk
new file mode 100644
index 0000000000..da71321b11
--- /dev/null
+++ b/engines/hadesch/module.mk
@@ -0,0 +1,54 @@
+MODULE := engines/hadesch
+
+MODULE_OBJS = \
+ metaengine.o \
+ pod_file.o \
+ tag_file.o \
+ pod_image.o \
+ video.o \
+ hadesch.o \
+ baptr.o \
+ rooms/olympus.o \
+ rooms/walloffame.o \
+ rooms/argo.o \
+ rooms/crete.o \
+ rooms/minos.o \
+ rooms/daedalus.o \
+ rooms/seriphos.o \
+ rooms/medisle.o \
+ rooms/troy.o \
+ rooms/quiz.o \
+ rooms/minotaur.o \
+ rooms/catacombs.o \
+ rooms/priam.o \
+ rooms/athena.o \
+ rooms/volcano.o \
+ rooms/riverstyx.o \
+ rooms/hadesthrone.o \
+ rooms/credits.o \
+ rooms/intro.o \
+ rooms/ferry.o \
+ rooms/options.o \
+ rooms/monster.o \
+ rooms/monster/projectile.o \
+ rooms/monster/typhoon.o \
+ rooms/monster/cyclops.o \
+ rooms/monster/illusion.o \
+ rooms/medusa.o \
+ rooms/trojan.o \
+ gfx_context.o \
+ ambient.o \
+ herobelt.o \
+ hotzone.o \
+ table.o \
+ persistent.o
+
+DETECT_OBJS += $(MODULE)/detection.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_HADESCH), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/hadesch/persistent.cpp b/engines/hadesch/persistent.cpp
new file mode 100644
index 0000000000..2c36db31fe
--- /dev/null
+++ b/engines/hadesch/persistent.cpp
@@ -0,0 +1,293 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/debug-channels.h"
+#include "common/error.h"
+#include "common/events.h"
+#include "common/ini-file.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/file.h"
+#include "common/keyboard.h"
+#include "common/macresman.h"
+#include "common/util.h"
+
+#include "hadesch/persistent.h"
+
+namespace Hadesch {
+
+Persistent::Persistent() {
+ _currentRoomId = kInvalidRoom;
+ _previousRoomId = kInvalidRoom;
+ _quest = kNoQuest;
+ for (unsigned i = 0; i < ARRAYSIZE(_powerLevel); i++)
+ _powerLevel[i] = 0;
+ _hintsAreEnabled = true;
+
+ for (unsigned i = 0; i < ARRAYSIZE(_roomVisited); i++)
+ _roomVisited[i] = false;
+ for (unsigned i = 0; i < ARRAYSIZE(_argoSailedInQuest); i++)
+ for (unsigned j = 0; j < ARRAYSIZE(_argoSailedInQuest[0]); j++)
+ _argoSailedInQuest[i][j] = false;
+ for (unsigned i = 0; i < ARRAYSIZE(_statuesTouched); i++)
+ _statuesTouched[i] = false;
+ for (unsigned i = 0; i < ARRAYSIZE(_statuePhase); i++)
+ _statuePhase[i] = 0;
+
+ _argoSaidTroyFinally = false;
+ _argoSaidCretePort = false;
+
+ _creteShowMerchant = false;
+ _creteShowAtlantisBoat = false;
+ _creteShowHorned = false;
+ _creteShowHornless1 = false;
+ _creteShowHornless2 = false;
+ _creteShowHornless3 = false;
+ _creteShowHornless4 = false;
+ _creteDaedalusRoomAvailable = false;
+ _creteMinosInstructed = false;
+ _creteIntroMerchant = false;
+ _cretePlayedEyeGhostTown = false;
+ _creteIntroAtlantisBoat = false;
+ _creteIntroAtlantisWood = false;
+ _creteSandalsState = SANDALS_NOT_SOLVED;
+ _creteStrongBoxState = BOX_CLOSED;
+ _creteAlchemistExploded = false;
+ _cretePlayedPhilAlchemist = false;
+ _cretePlayedZeusCheckOutThatBox = false;
+ _creteHadesPusnishesPainAndPanic = false;
+ _creteVisitedAfterAlchemistIntro = false;
+ _creteSaidHelenPermanentResident = false;
+
+ _daedalusShowedNote = false;
+
+ _seriphosStrawCartTaken = false;
+ _seriphosPlayedMedusa = false;
+ _seriphosPhilWarnedAthena = false;
+ _seriphosPhilCurtainsItems = false;
+
+ _athenaPuzzleSolved = false;
+ _athenaSwordTaken = false;
+ _athenaShieldTaken = false;
+ _athenaPlayedPainAndPanic = false;
+ _athenaIntroPlayed = false;
+
+ _medisleStoneTaken = false;
+ _medislePlayedPerseusIntro = false;
+ _medisleShowFates = false;
+ _medisleShowFatesIntro = false;
+ for (unsigned i = 0; i < ARRAYSIZE(_statuePhase); i++)
+ _medislePlacedItems[i] = false;
+ _medisleEyeballIsActive = false;
+ _medisleEyePosition = kLachesis;
+ _medisleBagPuzzleState = BAG_NOT_STARTED;
+ _medislePlayedPhilFatesDesc = false;
+
+ _troyPlayAttack = false;
+ _troyWallDamaged = false;
+ _troyShowBricks = false;
+ _troyShowBricks = false;
+ _troyIsDefeated = false;
+ _troyPlayOdysseus = false;
+ _troyKeyAndDecreeState = KEY_AND_DECREE_NOT_GIVEN;
+ _troyMessageIsDelivered = false;
+ _troyCatacombCounter = 0;
+ _troyCatacombsUnlocked = false;
+ _troyPlayedOdysseusCongrats = false;
+ _troyPlayFinish = false;
+
+ for (unsigned i = 0; i < ARRAYSIZE(_catacombVariants); i++)
+ for (unsigned j = 0; j < ARRAYSIZE(_catacombVariants[0]); j++)
+ _catacombVariants[i][j] = 0;
+ for (unsigned i = 0; i < ARRAYSIZE(_catacombPaths); i++)
+ for (unsigned j = 0; j < ARRAYSIZE(_catacombPaths[0]); j++)
+ _catacombPaths[i][j] = kCatacombsHelen;
+ _catacombLevel = kCatacombLevelSign;
+ _catacombLastLevel = kCatacombLevelSign;
+ _catacombDecoderSkullPosition = kCatacombsLeft;
+ _catacombPainAndPanic = false;
+
+ for (unsigned i = 0; i < ARRAYSIZE(_creteTriedHornless); i++)
+ _creteTriedHornless[i] = false;
+ for (unsigned i = 0; i < ARRAYSIZE(_daedalusLabItem); i++)
+ _daedalusLabItem[i] = false;
+
+ _volcanoPainAndPanicIntroDone = false;
+ _volcanoPuzzleState = Persistent::VOLCANO_NO_BOULDERS_THROWN;
+ _volcanoHeyKid = false;
+ _volcanoToStyxCounter = 0;
+
+ _styxCharonUsedPotion = false;
+ _styxCharonUsedCoin = false;
+ _styxAlchemistSaidIntro = false;
+
+ for (unsigned i = 0; i < ARRAYSIZE(_inventory); i++)
+ _inventory[i] = kNone;
+}
+
+void Persistent::clearInventory() {
+ memset(_inventory, 0, sizeof (_inventory));
+}
+
+bool Persistent::isInInventory(InventoryItem item) {
+ for (unsigned i = 0; i < inventorySize; i++) {
+ if (_inventory[i] == item) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+HadeschSaveDescriptor::HadeschSaveDescriptor(Common::Serializer &s, int slot) {
+ s.matchBytes("hadesch", 7);
+ s.syncVersion(0);
+ s.syncString(_heroName);
+ s.syncString(_slotName);
+ s.syncAsByte(_room);
+ _slot = slot;
+}
+
+bool Persistent::syncGameStream(Common::Serializer &s) {
+ if(!s.matchBytes("hadesch", 7))
+ return false;
+ if (!s.syncVersion(0))
+ return false;
+
+ s.syncString(_heroName);
+ s.syncString(_slotDescription);
+
+ s.syncAsByte(_currentRoomId);
+ s.syncAsByte(_previousRoomId);
+
+ s.syncAsByte(_quest);
+ for (unsigned i = 0; i < ARRAYSIZE(_powerLevel); i++)
+ s.syncAsByte(_powerLevel[i]);
+ s.syncAsByte(_hintsAreEnabled);
+
+ for (unsigned i = 0; i < ARRAYSIZE(_roomVisited); i++)
+ s.syncAsByte(_roomVisited[i]);
+ for (unsigned i = 0; i < ARRAYSIZE(_argoSailedInQuest); i++)
+ for (unsigned j = 0; j < ARRAYSIZE(_argoSailedInQuest[0]); j++)
+ s.syncAsByte(_argoSailedInQuest[i][j]);
+ for (unsigned i = 0; i < ARRAYSIZE(_statuesTouched); i++)
+ s.syncAsByte(_statuesTouched[i]);
+ for (unsigned i = 0; i < ARRAYSIZE(_statuePhase); i++)
+ s.syncAsByte(_statuePhase[i]);
+
+ s.syncAsByte(_argoSaidTroyFinally);
+ s.syncAsByte(_argoSaidCretePort);
+
+ s.syncAsByte(_creteShowMerchant);
+ s.syncAsByte(_creteShowAtlantisBoat);
+ s.syncAsByte(_creteShowHorned);
+ s.syncAsByte(_creteShowHornless1);
+ s.syncAsByte(_creteShowHornless2);
+ s.syncAsByte(_creteShowHornless3);
+ s.syncAsByte(_creteShowHornless4);
+ s.syncAsByte(_creteDaedalusRoomAvailable);
+ s.syncAsByte(_creteMinosInstructed);
+ s.syncAsByte(_creteIntroMerchant);
+ s.syncAsByte(_cretePlayedEyeGhostTown);
+ s.syncAsByte(_creteIntroAtlantisBoat);
+ s.syncAsByte(_creteIntroAtlantisWood);
+ s.syncAsByte(_creteSandalsState);
+ s.syncAsByte(_creteStrongBoxState);
+ s.syncAsByte(_creteAlchemistExploded);
+ s.syncAsByte(_cretePlayedPhilAlchemist);
+ s.syncAsByte(_cretePlayedZeusCheckOutThatBox);
+ s.syncAsByte(_creteHadesPusnishesPainAndPanic);
+ s.syncAsByte(_creteVisitedAfterAlchemistIntro);
+ s.syncAsByte(_creteSaidHelenPermanentResident);
+
+ s.syncAsByte(_daedalusShowedNote);
+
+ s.syncAsByte(_seriphosStrawCartTaken);
+ s.syncAsByte(_seriphosPlayedMedusa);
+ s.syncAsByte(_seriphosPhilWarnedAthena);
+ s.syncAsByte(_seriphosPhilCurtainsItems);
+
+ s.syncAsByte(_athenaPuzzleSolved);
+ s.syncAsByte(_athenaSwordTaken);
+ s.syncAsByte(_athenaShieldTaken);
+ s.syncAsByte(_athenaPlayedPainAndPanic);
+ s.syncAsByte(_athenaIntroPlayed);
+
+ s.syncAsByte(_medisleStoneTaken);
+ s.syncAsByte(_medislePlayedPerseusIntro);
+ s.syncAsByte(_medisleShowFates);
+ s.syncAsByte(_medisleShowFatesIntro);
+ for (unsigned i = 0; i < ARRAYSIZE(_statuePhase); i++)
+ s.syncAsByte(_medislePlacedItems[i]);
+ s.syncAsByte(_medisleEyeballIsActive);
+ s.syncAsByte(_medisleEyePosition);
+ s.syncAsByte(_medisleBagPuzzleState);
+ s.syncAsByte(_medislePlayedPhilFatesDesc);
+
+ s.syncAsByte(_troyPlayAttack);
+ s.syncAsByte(_troyWallDamaged);
+ s.syncAsByte(_troyShowBricks);
+ s.syncAsByte(_troyShowBricks);
+ s.syncAsByte(_troyIsDefeated);
+ s.syncAsByte(_troyPlayOdysseus);
+ s.syncAsByte(_troyKeyAndDecreeState);
+ s.syncAsByte(_troyMessageIsDelivered);
+ s.syncAsByte(_troyCatacombCounter);
+ s.syncAsByte(_troyCatacombsUnlocked);
+ s.syncAsByte(_troyPlayedOdysseusCongrats);
+ s.syncAsByte(_troyPlayFinish);
+
+ for (unsigned i = 0; i < ARRAYSIZE(_catacombVariants); i++)
+ for (unsigned j = 0; j < ARRAYSIZE(_catacombVariants[0]); j++)
+ s.syncAsByte(_catacombVariants[i][j]);
+ for (unsigned i = 0; i < ARRAYSIZE(_catacombPaths); i++)
+ for (unsigned j = 0; j < ARRAYSIZE(_catacombPaths[0]); j++)
+ s.syncAsByte(_catacombPaths[i][j]);
+ s.syncAsByte(_catacombLevel);
+ s.syncAsByte(_catacombLastLevel);
+ s.syncAsByte(_catacombDecoderSkullPosition);
+ s.syncAsByte(_catacombPainAndPanic);
+
+ for (unsigned i = 0; i < ARRAYSIZE(_creteTriedHornless); i++)
+ s.syncAsByte(_creteTriedHornless[i]);
+ for (unsigned i = 0; i < ARRAYSIZE(_daedalusLabItem); i++)
+ s.syncAsByte(_daedalusLabItem[i]);
+
+ s.syncAsByte(_volcanoPainAndPanicIntroDone);
+ s.syncAsByte(_volcanoPuzzleState);
+ s.syncAsByte(_volcanoHeyKid);
+ s.syncAsByte(_volcanoToStyxCounter);
+
+ s.syncAsByte(_styxCharonUsedPotion);
+ s.syncAsByte(_styxCharonUsedCoin);
+ s.syncAsByte(_styxAlchemistSaidIntro);
+
+ for (unsigned i = 0; i < ARRAYSIZE(_inventory); i++)
+ s.syncAsByte(_inventory[i]);
+
+ debug("serialized");
+
+ return true;
+}
+
+}
diff --git a/engines/hadesch/persistent.h b/engines/hadesch/persistent.h
new file mode 100644
index 0000000000..c5b5c04660
--- /dev/null
+++ b/engines/hadesch/persistent.h
@@ -0,0 +1,188 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+
+#include "common/serializer.h"
+
+#include "hadesch/enums.h"
+
+#ifndef HADESCH_PERSISTENT_H
+#define HADESCH_PERSISTENT_H
+
+namespace Hadesch {
+
+struct HadeschSaveDescriptor {
+ HadeschSaveDescriptor(Common::Serializer &s, int slot);
+
+ int _slot;
+ Common::String _heroName;
+ Common::String _slotName;
+ RoomId _room;
+};
+
+struct HadeschSaveDescriptorSlotComparator {
+ bool operator()(const HadeschSaveDescriptor &x, const HadeschSaveDescriptor &y) const {
+ return x._slot < y._slot;
+ }
+};
+
+static const int inventorySize = 6;
+
+struct Persistent {
+ // Generic
+ Gender _gender;
+ Common::String _heroName;
+ Common::String _slotDescription; // valid only in saves
+ Quest _quest;
+ int _powerLevel[3];
+ RoomId _currentRoomId;
+ RoomId _previousRoomId;
+ bool _roomVisited[kNumRooms];
+ bool _statuesTouched[kNumStatues];
+ int _statuePhase[kNumStatues];
+ bool _doQuestIntro;
+ InventoryItem _inventory[inventorySize];
+ bool _hintsAreEnabled;
+
+ // Argo
+ bool _argoSailedInQuest[kNumRooms][kNumQuests];
+ bool _argoSaidTroyFinally;
+ bool _argoSaidCretePort;
+
+ // Crete and Minos
+ bool _creteShowMerchant;
+ bool _creteShowAtlantisBoat;
+ bool _creteShowHorned;
+ bool _creteShowHornless1;
+ bool _creteShowHornless2;
+ bool _creteShowHornless3;
+ bool _creteShowHornless4;
+ bool _creteDaedalusRoomAvailable;
+ bool _creteMinosInstructed;
+ bool _creteIntroMerchant;
+ bool _cretePlayedEyeGhostTown;
+ bool _creteTriedHornless[4];
+ bool _creteIntroAtlantisBoat;
+ bool _creteIntroAtlantisWood;
+ bool _creteAlchemistExploded;
+ enum {
+ SANDALS_NOT_SOLVED,
+ SANDALS_SOLVED,
+ SANDALS_TAKEN
+ } _creteSandalsState;
+ enum { BOX_CLOSED, BOX_OPEN,
+ BOX_OPEN_POTION, BOX_OPEN_NO_POTION } _creteStrongBoxState;
+ bool _cretePlayedPhilAlchemist;
+ bool _cretePlayedZeusCheckOutThatBox;
+ bool _creteHadesPusnishesPainAndPanic;
+ bool _creteVisitedAfterAlchemistIntro;
+ bool _creteSaidHelenPermanentResident;
+
+ // Daedalus
+ bool _daedalusShowedNote;
+ bool _daedalusLabItem[4];
+
+ // Seriphos
+ bool _seriphosStrawCartTaken;
+ bool _seriphosPlayedMedusa;
+ bool _seriphosPhilWarnedAthena;
+ bool _seriphosPhilCurtainsItems;
+
+ // Athena
+ bool _athenaPuzzleSolved;
+ bool _athenaSwordTaken;
+ bool _athenaShieldTaken;
+ bool _athenaPlayedPainAndPanic;
+ bool _athenaIntroPlayed;
+
+ // Medusa Island
+ bool _medisleStoneTaken;
+ bool _medislePlacedItems[5];
+ bool _medislePlayedPerseusIntro;
+ bool _medisleShowFates;
+ bool _medisleShowFatesIntro;
+ bool _medisleEyeballIsActive;
+ FateId _medisleEyePosition;
+ enum {
+ BAG_NOT_STARTED,
+ BAG_STARTED,
+ BAG_SOLVED,
+ BAG_TAKEN
+ } _medisleBagPuzzleState;
+ bool _medislePlayedPhilFatesDesc;
+
+ // Troy
+ bool _troyPlayAttack;
+ bool _troyWallDamaged;
+ bool _troyShowBricks;
+ bool _troyIsDefeated;
+ bool _troyPlayOdysseus;
+ bool _troyMessageIsDelivered;
+ enum {
+ KEY_AND_DECREE_NOT_GIVEN,
+ KEY_AND_DECREE_THROWN,
+ KEY_AND_DECREE_TAKEN
+ } _troyKeyAndDecreeState;
+ int _troyCatacombCounter;
+ bool _troyCatacombsUnlocked;
+ bool _troyPlayedOdysseusCongrats;
+ bool _troyPlayFinish;
+
+ // Catacombs
+ int _catacombVariants[3][3];
+ CatacombsPath _catacombPaths[3][3];
+ CatacombsLevel _catacombLevel;
+ CatacombsPosition _catacombDecoderSkullPosition;
+ CatacombsLevel _catacombLastLevel;
+ bool _catacombPainAndPanic;
+
+ // Volcano
+ bool _volcanoPainAndPanicIntroDone;
+ bool _volcanoHeyKid;
+ enum {
+ VOLCANO_NO_BOULDERS_THROWN,
+ VOLCANO_SQUASHED_PANIC,
+ VOLCANO_BOULDER_ON_VOLCANO,
+ VOLCANO_HELMET_SHOWN
+ } _volcanoPuzzleState;
+ int _volcanoToStyxCounter;
+
+ // River Styx
+ bool _styxCharonUsedPotion;
+ bool _styxCharonUsedCoin;
+ bool _styxAlchemistSaidIntro;
+
+ Persistent();
+
+ bool isInInventory(InventoryItem item);
+
+ bool isRoomVisited(RoomId id) const {
+ return _roomVisited[id];
+ }
+
+ void clearInventory();
+
+ bool syncGameStream(Common::Serializer &s);
+};
+}
+#endif
diff --git a/engines/hadesch/pod_file.cpp b/engines/hadesch/pod_file.cpp
new file mode 100644
index 0000000000..7d6fe84aa6
--- /dev/null
+++ b/engines/hadesch/pod_file.cpp
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/memstream.h"
+
+#include "hadesch/hadesch.h"
+#include "hadesch/pod_file.h"
+
+namespace Hadesch {
+
+PodFile::PodFile(const Common::String &debugName) {
+ _debugName = debugName;
+}
+
+Common::String PodFile::getDebugName() const {
+ return _debugName;
+}
+
+bool PodFile::openStore(const Common::SharedPtr<Common::SeekableReadStream> &parentStream) {
+ byte buf[16];
+ if (!parentStream) {
+ return false;
+ }
+
+ if (parentStream->read(buf, 12) != 12) {
+ return false;
+ }
+
+ if (memcmp(buf, "Pod File\0\0\0\0", 12) != 0 &&
+ memcmp(buf, "Pod\0file\0\0\0\0", 12) != 0 &&
+ memcmp(buf, "Pod\0\0\0\0\0\0\0\0\0", 12) != 0) {
+ return false;
+ }
+
+ const uint32 numFiles = parentStream->readUint32LE();
+ uint32 offset = 16 + 16 * numFiles;
+
+ _descriptions.resize(numFiles);
+
+ for (uint i = 0; i < _descriptions.size(); ++i) {
+ parentStream->read(buf, 12);
+ buf[12] = '\0';
+ const uint32 size = parentStream->readUint32LE();
+
+ _descriptions[i].name = (const char *) buf;
+ _descriptions[i].offset = offset;
+ _descriptions[i].size = size;
+ offset += size;
+ }
+
+ _file = parentStream;
+
+ return true;
+}
+
+bool PodFile::openStore(const Common::String &name) {
+ Common::SharedPtr<Common::File> file(new Common::File());
+ if (name.empty() || !file->open(name)) {
+ return false;
+ }
+
+ return openStore(file);
+}
+
+// It's tempting to use substream but substream is not thread safe
+Common::SeekableReadStream *memSubstream(Common::SharedPtr<Common::SeekableReadStream> file,
+ uint32 offset, uint32 size) {
+ byte *contents = (byte *) malloc(size);
+ if (!contents)
+ return nullptr;
+ file->seek(offset);
+ file->read(contents, size);
+ return new Common::MemoryReadStream(contents, size, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *PodFile::getFileStream(const Common::String &name) const {
+ for (uint j = 0; j < _descriptions.size(); ++j) {
+ const Description &desc = _descriptions[j];
+ if (desc.name.compareToIgnoreCase(name) == 0) {
+ return memSubstream(
+ _file, desc.offset, desc.size);
+ }
+ }
+ debugC(kHadeschDebugResources, "PodFile: %s not found", name.c_str());
+ return nullptr;
+}
+
+Common::Array <PodImage> PodFile::loadImageArray() const {
+ Common::Array <PodImage> pis;
+
+ for (int idx = 1; ; idx++) {
+ PodImage pi;
+ if (!pi.loadImage(*this, idx)) {
+ break;
+ }
+ pis.push_back(pi);
+ }
+
+ return pis;
+}
+
+} // End of namespace Hadesch
diff --git a/engines/hadesch/pod_file.h b/engines/hadesch/pod_file.h
new file mode 100644
index 0000000000..1b12cf53b1
--- /dev/null
+++ b/engines/hadesch/pod_file.h
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_FILE_MGR_H
+#define HADESCH_FILE_MGR_H
+
+#include "common/array.h"
+#include "common/ptr.h"
+
+namespace Common {
+ class File;
+ class SeekableReadStream;
+}
+
+namespace Hadesch {
+
+class PodImage;
+
+Common::SeekableReadStream *memSubstream(Common::SharedPtr<Common::SeekableReadStream> file,
+ uint32 offset, uint32 size);
+class PodFile {
+public:
+ PodFile(const Common::String &debugName);
+ bool openStore(const Common::String &name);
+ bool openStore(const Common::SharedPtr<Common::SeekableReadStream> &parentstream);
+
+ Common::SeekableReadStream *getFileStream(const Common::String &name) const;
+ Common::String getDebugName() const;
+ Common::Array <PodImage> loadImageArray() const;
+
+private:
+ struct Description {
+ Common::String name;
+ uint32 offset;
+ uint32 size;
+ };
+ Common::SharedPtr<Common::SeekableReadStream> _file;
+ Common::Array<Description> _descriptions;
+ Common::String _debugName;
+};
+
+} // End of namespace Hadesch
+
+#endif
diff --git a/engines/hadesch/pod_image.cpp b/engines/hadesch/pod_image.cpp
new file mode 100644
index 0000000000..5b8c0eea02
--- /dev/null
+++ b/engines/hadesch/pod_image.cpp
@@ -0,0 +1,271 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "common/debug.h"
+#include "common/stream.h"
+
+#include "hadesch/pod_image.h"
+#include "hadesch/tag_file.h"
+#include "hadesch/baptr.h"
+#include "hadesch/gfx_context.h"
+
+namespace Hadesch {
+PodImage::PodImage() {
+ _w = 0;
+ _h = 0;
+ _pos = Common::Point(0, 0);
+ _ncolors = 0;
+}
+
+PodImage::~PodImage() {
+}
+
+bool PodImage::loadImage(const PodFile &col, int index) {
+ char bufname[256];
+ snprintf (bufname, sizeof(bufname) - 1, "%d", index);
+ Common::SharedPtr<Common::SeekableReadStream> dataStream(col.getFileStream(bufname));
+ if (!dataStream) {
+ return false;
+ }
+
+ Common::SharedPtr<Common::SeekableReadStream> palStream(col.getFileStream("0"));
+ TagFile palTags;
+ if (!palStream || !palTags.openStoreCel(palStream)) {
+ debug("Couldn't open palette");
+ return false;
+ }
+
+ Common::SharedPtr<Common::SeekableReadStream> palTagStream(palTags.getFileStream(MKTAG('P', 'A', 'L', ' ')));
+
+ if (!palTagStream) {
+ debug("Couldn't open PAL palette in image %s", col.getDebugName().c_str());
+ return false;
+ }
+
+ uint palSize = palTagStream->size();
+ if (palSize > 256 * 4) {
+ debug("Palette unexpectedly large");
+ palSize = 256 * 4;
+ }
+
+ _palette = sharedPtrByteAlloc(256 * 4);
+ memset(_palette.get(), 0, 256 * 4);
+ _paletteCursor = sharedPtrByteAlloc(256 * 3);
+ memset(_paletteCursor.get(), 0, 256 * 3);
+
+ palTagStream->read(_palette.get(), palSize);
+ _ncolors = palSize / 4;
+
+ for (int i = 0; i < _ncolors; i++) {
+ int color = _palette.get()[4 * i] & 0xff;
+
+ _paletteCursor.get()[3 * color ] = _palette.get()[4 * i + 1];
+ _paletteCursor.get()[3 * color + 1] = _palette.get()[4 * i + 2];
+ _paletteCursor.get()[3 * color + 2] = _palette.get()[4 * i + 3];
+ }
+
+ TagFile dataTags;
+ if (!dataTags.openStoreCel(dataStream)) {
+ debug("Couldn't open data for image %d", index);
+ return false;
+ }
+
+ Common::ScopedPtr<Common::SeekableReadStream> infoTagStream(dataTags.getFileStream(MKTAG('I', 'N', 'F', 'O')));
+
+ if (!infoTagStream) {
+ debug("Couldn't open INFO");
+ return false;
+ }
+
+ if (infoTagStream->size() < 0x1c) {
+ debug("INFO section too small");
+ return false;
+ }
+
+ infoTagStream->skip(0xc);
+ int x = -infoTagStream->readUint32BE();
+ int y = -infoTagStream->readUint32BE();
+ _pos = Common::Point(x,y);
+ _w = infoTagStream->readUint32BE();
+ _h = infoTagStream->readUint32BE();
+
+ // Empty image
+ if (_w < 0 || _h < 0) {
+ _w = 0;
+ _h = 0;
+ _pixels = sharedPtrByteAlloc(1);
+ memset(_pixels.get(), 0, 1);
+ return true;
+ }
+
+ _pixels = sharedPtrByteAlloc(_w * _h);
+ memset(_pixels.get(), 0, _w * _h);
+ // TODO: check this
+ _hotspot = Common::Point(_w / 2, _h / 2);
+
+ Common::ScopedPtr<Common::SeekableReadStream> dataTagStream(dataTags.getFileStream(MKTAG('D', 'A', 'T', 'A')));
+
+ if (!dataTagStream) {
+ debug("Couldn't open DATA in image %s, index %d", col.getDebugName().c_str(), index);
+ return false;
+ }
+
+ int linerem = _w;
+ int line = 0;
+
+ for (int pos = 0; pos < _w * _h && !dataTagStream->eos(); ) {
+ byte rlelen = dataTagStream->readByte();
+ byte rleval = dataTagStream->readByte();
+ if (dataTagStream->eos()) {
+ break;
+ }
+ if (rlelen != 0) {
+ int len = rlelen;
+ if (len > linerem) {
+ len = linerem;
+ }
+ memset(_pixels.get() + pos, rleval, len);
+ linerem -= len;
+ pos += len;
+ continue;
+ }
+
+ if (rleval != 0) {
+ int len = rleval;
+ if (len > linerem) {
+ len = linerem;
+ }
+ dataTagStream->read(_pixels.get() + pos, len);
+ linerem -= len;
+ pos += len;
+ continue;
+ }
+
+ // End of line
+ line++;
+ linerem = _w;
+ pos = line * _w;
+ }
+
+ return true;
+}
+
+// Naive implementation as it's used very rarely
+// Nearest neighbour, unoptimized
+void PodImage::makeScale(int scale) const {
+ struct ScaledVersion sv;
+ sv._w = _w * scale / 100;
+ sv._h = _h * scale / 100;
+ sv._pixels = sharedPtrByteAlloc(sv._h * sv._w);
+ for (int x = 0; x < sv._w; x++) {
+ int ox = x * _w / sv._w;
+ if (ox >= _w)
+ ox = _w - 1;
+ if (ox < 0)
+ ox = 0;
+ for (int y = 0; y < sv._h; y++) {
+ int oy = y * _h / sv._h;
+ if (oy >= _h)
+ oy = _h - 1;
+ if (oy < 0)
+ oy = 0;
+ sv._pixels.get()[x + y * sv._w] = _pixels.get()[ox + oy * _w];
+ }
+ }
+ _scales[scale] = sv;
+}
+
+void PodImage::render(Common::SharedPtr<GfxContext> context,
+ Common::Point offset,
+ int colourScale,
+ int scale) const {
+ byte *originalPalette = _palette.get();
+ byte *scaledPalette = nullptr;
+ if (colourScale != 0x100) {
+ scaledPalette = new byte[_ncolors * 4];
+ for (unsigned i = 0; (int)i < _ncolors; i++) {
+ scaledPalette[4 * i] = originalPalette[4 * i];
+ scaledPalette[4 * i + 1] = (originalPalette[4 * i + 1] * colourScale) >> 8;
+ scaledPalette[4 * i + 2] = (originalPalette[4 * i + 2] * colourScale) >> 8;
+ scaledPalette[4 * i + 3] = (originalPalette[4 * i + 3] * colourScale) >> 8;
+ }
+ }
+
+ if (scale == 100)
+ context->blitPodImage(_pixels.get(), _w, _w, _h,
+ scaledPalette ? scaledPalette : originalPalette, _ncolors, _pos + offset);
+ else {
+ if (!_scales.contains(scale))
+ makeScale(scale);
+ context->blitPodImage(_scales[scale]._pixels.get(), _scales[scale]._w, _scales[scale]._w, _scales[scale]._h,
+ scaledPalette ? scaledPalette : originalPalette, _ncolors, _pos * (scale / 100.0) + offset);
+ }
+ if (scaledPalette)
+ delete [] scaledPalette;
+}
+
+Common::Point PodImage::getOffset() const {
+ return _pos;
+}
+
+uint16 PodImage::getWidth() const {
+ return _w;
+}
+
+uint16 PodImage::getHeight() const {
+ return _h;
+}
+
+uint16 PodImage::getHotspotX() const {
+ return _hotspot.x;
+}
+
+uint16 PodImage::getHotspotY() const {
+ return _hotspot.y;
+}
+
+byte PodImage::getKeyColor() const {
+ return 0;
+}
+
+const byte *PodImage::getSurface() const {
+ return _pixels.get();
+}
+
+const byte *PodImage::getPalette() const {
+ return _paletteCursor.get();
+}
+
+byte PodImage::getPaletteStartIndex() const {
+ return 0;
+}
+
+uint16 PodImage::getPaletteCount() const {
+ return 256;
+}
+
+void PodImage::setHotspot(Common::Point hotspot) {
+ _hotspot = hotspot;
+}
+
+}
diff --git a/engines/hadesch/pod_image.h b/engines/hadesch/pod_image.h
new file mode 100644
index 0000000000..c90bfcaa1b
--- /dev/null
+++ b/engines/hadesch/pod_image.h
@@ -0,0 +1,74 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#ifndef HADESCH_POD_IMAGE_H
+#define HADESCH_POD_IMAGE_H
+
+#include "hadesch/pod_file.h"
+#include "common/ptr.h"
+#include "common/rect.h"
+#include "common/hashmap.h"
+#include "hadesch/gfx_context.h"
+#include "graphics/cursor.h"
+
+namespace Hadesch {
+
+class PodImage : public Graphics::Cursor {
+public:
+ PodImage();
+ bool loadImage(const PodFile &col, int index);
+ void render(Common::SharedPtr<GfxContext>, Common::Point offset,
+ int colourScale = 0x100, int scale = 100) const;
+ bool isValid() const;
+ void setHotspot(Common::Point pnt);
+ Common::Point getOffset() const;
+
+ uint16 getWidth() const override;
+ uint16 getHeight() const override;
+ uint16 getHotspotX() const override;
+ uint16 getHotspotY() const override;
+ byte getKeyColor() const override;
+ const byte *getSurface() const override;
+ const byte *getPalette() const override;
+ byte getPaletteStartIndex() const override;
+ uint16 getPaletteCount() const override;
+
+ ~PodImage();
+private:
+ struct ScaledVersion {
+ Common::SharedPtr<byte> _pixels;
+ int _w, _h;
+ };
+ void makeScale(int scale) const;
+
+ mutable Common::HashMap<int, ScaledVersion> _scales;
+ int _w, _h;
+ Common::Point _pos, _hotspot;
+ int _ncolors;
+ Common::SharedPtr<byte> _pixels;
+ Common::SharedPtr<byte> _palette;
+ Common::SharedPtr<byte> _paletteCursor;
+};
+}
+
+#endif
diff --git a/engines/hadesch/rooms/argo.cpp b/engines/hadesch/rooms/argo.cpp
new file mode 100644
index 0000000000..4b40116df1
--- /dev/null
+++ b/engines/hadesch/rooms/argo.cpp
@@ -0,0 +1,346 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+
+namespace Hadesch {
+
+static const char *kIslandNames = "islandnames";
+static const char *kMastHeadAnim = "mastheadanim";
+
+enum {
+ kSkyZ = 10200,
+ kCloudsZ = 10100,
+ kWavesRightZ = 10050,
+ kWavesLeftZ = 10050,
+ kBackgroundZ = 10000,
+ kFlagsZ = 9000,
+ kMastHeadZ = 8000,
+ kChessPieceZ = 701,
+ kIslandNamesZ = 601
+};
+
+static const struct island {
+ const char *hotname;
+ const char *mouseoverAnim; // not mapped
+ const char *nameSound; // not mapped
+ const char *sfxSound; // not mapped
+ RoomId roomId;
+ int zValue;
+} islands[] = {
+ {"Phils", "a1030bh0", "a1030nf0", "a1030ef0", kWallOfFameRoom, 901},
+ {"Medusa", "a1030bf0", "a1030nc0", "a1030ed0", kMedIsleRoom, 901},
+ {"Troy", "a1030bd0", "a1030na0", "a1030eb0", kTroyRoom, 901},
+ {"Seriphos", "a1030be0", "a1030nd0", "a1030ec0", kSeriphosRoom, 801},
+ {"Crete", "a1030bc0", "a1030nb0", "a1030ea0", kCreteRoom, 801},
+ {"Volcano", "a1030bg0", "a1030ne0", "a1030ee0", kVolcanoRoom, 801},
+};
+
+static const int nislands = sizeof(islands) / sizeof(islands[0]);
+
+static const char *intros[] = {
+ "a1150na0",
+ "a1150nb0",
+ "a1150nc0",
+ "a1150nd0",
+ "a1150ne0",
+ "a1150nf0"
+};
+
+static const char *defaultOutros[] = {
+ "a1170na0",
+ "a1170nb0",
+ "a1170nc0",
+ "a1170nd0",
+ "a1170ne0"
+};
+
+enum {
+ kPlayIntro2 = 27001,
+ kPlayIntro3 = 27002,
+ kReturnToIdleEvent = 27003,
+ kIdleEvent = 27008,
+ kOutroFinished = 27009,
+ kOutroFinishedCounter = 1027001
+};
+
+static Common::String
+getOutroName(RoomId dest) {
+ Persistent *persistent = g_vm->getPersistent();
+ Quest quest = persistent->_quest;
+
+ switch (dest) {
+ case kWallOfFameRoom:
+ if (!persistent->_argoSailedInQuest[dest][quest])
+ return "philsfirst";
+ break;
+ case kSeriphosRoom:
+ if (quest == kCreteQuest && !persistent->_argoSailedInQuest[dest][quest])
+ return "seriphoscretetroy";
+ if (quest == kTroyQuest && !persistent->_argoSailedInQuest[dest][quest])
+ return "seriphoscretetroy";
+ if (quest == kMedusaQuest && !persistent->_argoSailedInQuest[dest][quest])
+ return "seriphosperseus";
+ break;
+ case kMedIsleRoom:
+ if (quest == kMedusaQuest && !persistent->_argoSailedInQuest[dest][quest])
+ return "medusabeware";
+ break;
+ case kTroyRoom:
+ if (!persistent->isRoomVisited(kTroyRoom))
+ return "troytenyears";
+ if (quest == kTroyQuest && !persistent->_argoSailedInQuest[dest][quest])
+ return "troyregards";
+ if (quest > kTroyQuest && !persistent->_argoSaidTroyFinally) {
+ persistent->_argoSaidTroyFinally = true;
+ return "troyfinally";
+ }
+ break;
+ case kCreteRoom:
+ if (!persistent->isRoomVisited(kCreteRoom))
+ return "cretedaedalus";
+
+ if (quest != kCreteQuest && !persistent->_argoSaidCretePort)
+ return "creteport";
+ break;
+ case kVolcanoRoom:
+ if (!persistent->isRoomVisited(kVolcanoRoom))
+ return "volcanotopfirst";
+
+ if (quest == kRescuePhilQuest && !!persistent->_argoSailedInQuest[dest][quest])
+ return "volcanotopyoufirst";
+ break;
+
+ default:
+ assert(0);
+ }
+ int rnd = g_vm->getRnd().getRandomNumberRng(0, sizeof(defaultOutros) / sizeof(defaultOutros[0]) - 1);
+ debug("rnd = %d", rnd);
+ return defaultOutros[rnd];
+}
+
+class ArgoHandler : public Handler {
+public:
+ ArgoHandler() {
+ _prevId = kInvalidRoom;
+ _destination = kInvalidRoom;
+ }
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ _destination = kInvalidRoom;
+ for (unsigned i = 0; i < nislands; i++) {
+ if (name == islands[i].hotname) {
+ _destination = islands[i].roomId;
+ break;
+ }
+ }
+ if (_destination != kInvalidRoom) {
+ Persistent *persistent = g_vm->getPersistent();
+ room->disableMouse();
+ room->stopAnim("idlesound");
+ if (_destination == _prevId) {
+ playMastSound("currentlocation", kOutroFinished);
+ return;
+ }
+
+ _outroCounter = 4;
+ _cloudsMoving = true;
+ _cloudsMoveStart = g_vm->getCurrentTime();
+ playMastSound(getOutroName(_destination), kOutroFinishedCounter);
+ room->playAnimWithSound("wavesleft", "wavesleftSFX", kWavesLeftZ,
+ PlayAnimParams::disappear(),
+ kOutroFinishedCounter);
+ room->playAnimWithSound("wavesright", "wavesrightSFX", kWavesRightZ,
+ PlayAnimParams::disappear(),
+ kOutroFinishedCounter);
+ room->playSound("A1030eG0", kOutroFinishedCounter);
+ persistent->_argoSailedInQuest[_destination][persistent->_quest] = true;
+ }
+ }
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ switch (eventId) {
+ case kPlayIntro2:
+ playMastSound("intro2", kPlayIntro3);
+ break;
+ case kPlayIntro3:
+ playMastSound("intro3", kReturnToIdleEvent);
+ break;
+ case kReturnToIdleEvent:
+ room->enableMouse();
+ break;
+ case kOutroFinishedCounter:
+ if (--_outroCounter > 0)
+ break;
+ // Fallthrough
+ case kOutroFinished:
+ room->selectFrame(kMastHeadAnim, kMastHeadZ, 0);
+ g_vm->moveToRoom(_destination);
+ break;
+ case kIdleEvent:
+ playMastSound("idlesound");
+ room->selectFrame(kMastHeadAnim, kMastHeadZ, 1);
+ g_vm->addTimer(kIdleEvent, 30000);
+ break;
+ case 27301:
+ room->playAnimWithSound(kMastHeadAnim, _mastSoundName, kMastHeadZ,
+ PlayAnimParams::keepLastFrame().partial(8, 21), 27303);
+ break;
+ // 27302 was for event chaining and frame keeping
+ case 27303:
+ room->playAnim(kMastHeadAnim, kMastHeadZ,
+ PlayAnimParams::keepLastFrame().partial(8, 0), _mastHeadEndEvent);
+ break;
+ }
+ }
+ void handleMouseOver(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ for (unsigned i = 0; i < nislands; i++) {
+ if (name == islands[i].hotname) {
+ room->selectFrame(kIslandNames, kIslandNamesZ, i);
+ room->playAnimKeepLastFrame(islands[i].mouseoverAnim, islands[i].zValue);
+ playMastSound(islands[i].nameSound);
+ room->playSoundLoop(islands[i].sfxSound);
+ return;
+ }
+ }
+ }
+
+ void handleMouseOut(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ for (unsigned i = 0; i < nislands; i++)
+ if (name == islands[i].hotname) {
+ if (_destination != islands[i].roomId) {
+ room->stopAnim(kIslandNames);
+ room->stopAnim(islands[i].mouseoverAnim);
+ }
+ room->stopAnim(islands[i].nameSound);
+ room->stopAnim(islands[i].sfxSound);
+ return;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ _prevId = g_vm->getPreviousRoomId();
+ room->loadHotZones("argo.HOT");
+ room->addStaticLayer("background", kBackgroundZ);
+ Common::String sky;
+ int chesspiece;
+ Common::String bgsound;
+
+ switch (_prevId) {
+ default:
+ sky = "bluesky";
+ chesspiece = 0;
+ bgsound = "a1180ea0";
+ break;
+ case kSeriphosRoom:
+ sky = "pinksky";
+ chesspiece = 3;
+ bgsound = "A1070eA0";
+ break;
+ case kMedIsleRoom:
+ sky = "mauvesky";
+ chesspiece = 1;
+ bgsound = "a1210ea0";
+ break;
+ case kTroyRoom:
+ sky = "goldsky";
+ chesspiece = 2;
+ bgsound = "a1190eb0";
+ break;
+ case kCreteRoom:
+ sky = "bluesky";
+ chesspiece = 4;
+ bgsound = "a1180ea0";
+ break;
+ case kVolcanoRoom:
+ sky = "pinksky";
+ chesspiece = 5;
+ bgsound = "a1210ea0";
+ break;
+ }
+ room->addStaticLayer(sky, kSkyZ);
+ room->playSoundLoop(bgsound);
+
+ room->selectFrame("chesspiece", kChessPieceZ, chesspiece);
+
+ room->disableMouse();
+ // Originally event 4015
+ if (!persistent->isRoomVisited(kArgoRoom))
+ playMastSound("intro1", kPlayIntro2);
+ else {
+ int rnd = g_vm->getRnd().getRandomNumberRng(0, sizeof(intros) / sizeof(intros[0]) - 1);
+ debug("rnd = %d", rnd);
+ if (rnd == 1 || rnd == 2)
+ rnd = persistent->_gender == kFemale ? 2 : 1;
+ playMastSound(intros[rnd], kReturnToIdleEvent);
+ }
+
+ room->playAnimWithSound("flags", "flagsSFX", kFlagsZ, PlayAnimParams::loop());
+ g_vm->addTimer(kIdleEvent, 30000);
+ g_vm->getHeroBelt()->setColour(HeroBelt::kCool);
+ room->playSound("intromusic");
+ _cloudsMoving = false;
+ cloudMove(0);
+ }
+
+ void frameCallback() override {
+ if (_cloudsMoving) {
+ cloudMove(g_vm->getCurrentTime() - _cloudsMoveStart);
+ }
+ }
+
+ void cloudMove(int cloudMoveTime) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ double div = cloudMoveTime / 15000.0;
+ room->selectFrame("cloudright", kCloudsZ, 0, Common::Point(450, 0) + Common::Point(650, -50) * div);
+ room->selectFrame("cloudmiddle", kCloudsZ, 1, Common::Point(220, 0) + Common::Point(220, -50) * div);
+ room->selectFrame("cloudleft", kCloudsZ, 2, Common::Point(0, 0) + Common::Point(-200, -50) * div);
+ }
+
+private:
+ void playMastSound(const Common::String &name, int event = -1) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ _mastSoundName = name;
+ _mastHeadEndEvent = event;
+ room->playAnim(kMastHeadAnim, kMastHeadZ, PlayAnimParams::keepLastFrame().partial(1, 8), 27301);
+ }
+
+ RoomId _prevId;
+ RoomId _destination;
+ int _outroCounter;
+ int _cloudsMoveStart;
+ bool _cloudsMoving;
+ int _mastHeadEndEvent;
+ Common::String _mastSoundName;
+};
+
+Common::SharedPtr<Hadesch::Handler> makeArgoHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new ArgoHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/athena.cpp b/engines/hadesch/rooms/athena.cpp
new file mode 100644
index 0000000000..07a2c89947
--- /dev/null
+++ b/engines/hadesch/rooms/athena.cpp
@@ -0,0 +1,417 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/ambient.h"
+
+namespace Hadesch {
+static const char *kAthenaAnim = "c8060ba0";
+static const char *kLights = "c8110bb0";
+
+// Keep in order
+enum {
+ kWonPuzzle = -2,
+ kNowhere = -1,
+ kBubo = 0,
+ kArtist = 1,
+ kOrator = 2,
+ kScholar = 3,
+ kWarrior = 4
+};
+static const int kNumPuzzleElements = 5;
+static const struct {
+ const char *hotname;
+ int lights[2];
+ int rays[2];
+} puzzleElements[kNumPuzzleElements] = {
+ {"Bubo", { kArtist, kOrator}, {2, 1}},
+ {"Artist", { kNowhere, kOrator}, {6, 5}},
+ {"Orator", { kArtist, kScholar}, {3, 4}},
+ {"Scholar", { kNowhere, kWarrior}, {8, 9}},
+ {"Warrior", { kScholar, kWonPuzzle}, {7, 10}}
+};
+
+enum {
+ kBackgroundZ = 10000,
+ kLightsZ = 201
+};
+
+enum {
+ kPhilForgettingEnd = 23009,
+ kPhilHardwareFinished = 23012,
+ kIntroFinished = 1023001
+};
+
+class AthenaHandler : public Handler {
+public:
+ AthenaHandler() {
+ _playAreYouForgetting = true;
+ _playAthenaTempleHardware = true;
+ _isPuzzleWon = false;
+ _hintTimerLength = 20000;
+ memset(_puzzleState, 0, sizeof (_puzzleState));
+ }
+
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ Quest quest = persistent->_quest;
+ if (name == "Seriphos") {
+ if (quest == kMedusaQuest) {
+ if (persistent->_athenaPuzzleSolved &&
+ (!persistent->_athenaSwordTaken
+ || !persistent->_athenaShieldTaken)
+ && persistent->_hintsAreEnabled
+ && _playAreYouForgetting) {
+ _playAreYouForgetting = false;
+ room->disableMouse();
+ room->playVideo("c8020ba0", 0,
+ kPhilForgettingEnd,
+ Common::Point(0, 216));
+ return;
+ } else if (persistent->_athenaPuzzleSolved &&
+ persistent->_athenaSwordTaken &&
+ persistent->_athenaShieldTaken &&
+ !persistent->_athenaPlayedPainAndPanic
+ ) {
+ persistent->_athenaPlayedPainAndPanic = true;
+ room->disableMouse();
+ room->fadeOut(1000, 23019);
+ return;
+ } else if (persistent->_athenaPuzzleSolved &&
+ !_playAthenaTempleHardware
+ && persistent->_hintsAreEnabled) {
+ _playAthenaTempleHardware = false;
+ room->disableMouse();
+ room->playVideo("c8160ba0", 0,
+ kPhilHardwareFinished,
+ Common::Point(0, 216));
+ return;
+ }
+ }
+ g_vm->moveToRoom(kSeriphosRoom);
+ return;
+ }
+
+ if (name == "Athena") {
+ Common::Array<Common::String> videos;
+ if (quest == kMedusaQuest && !persistent->_athenaPuzzleSolved) {
+ videos.push_back(persistent->_gender == kMale ? "c8060wa0" : "c8060wb0");
+ } else {
+ videos.push_back("c8060wc0");
+ videos.push_back("c8060wd0");
+ }
+
+ room->playStatueSMK(kAthenaStatue,
+ kAthenaAnim,
+ 1101,
+ videos, 26, 42);
+ return;
+ }
+
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ if (name == puzzleElements[i].hotname) {
+ handlePuzzleClick(i);
+ return;
+ }
+
+ if (name == "Sword") {
+ persistent->_athenaSwordTaken = true;
+ g_vm->getHeroBelt()->placeToInventory(kSword);
+ room->stopAnim("c8130bf0");
+ room->disableHotzone("Sword");
+ room->disableMouse();
+ room->playSound("c8140wa0", 23026);
+ return;
+ }
+
+ if (name == "Shield") {
+ persistent->_athenaShieldTaken = true;
+ g_vm->getHeroBelt()->placeToInventory(kShield);
+ room->stopAnim("c8130be0");
+ room->disableHotzone("Shield");
+ room->disableMouse();
+ room->playSound("c8150wa0", 23027);
+ return;
+ }
+
+ if (name == "Athena's Sword") {
+ room->disableMouse();
+ room->playAnimLoop("c8010oc0", 2101);
+ room->playVideo("c8080wa0", 0, 23043);
+ room->playSound("C8080eA1");
+ return;
+ }
+
+ if (name == "Athena's Shield") {
+ room->disableMouse();
+ room->playAnimLoop("c8010ob0", 2101);
+ room->playVideo("c8070wa0", 0, 23044);
+ room->playSound("C8080eA1");
+ return;
+ }
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ switch(eventId) {
+ case kIntroFinished:
+ room->stopAnim(kAthenaAnim);
+ room->playAnim("c8110ba0", 0, PlayAnimParams::disappear(), 23035);
+ room->enableMouse();
+ _hintTimerLength = 20000;
+ rescheduleHintTimer();
+ break;
+ case kPhilForgettingEnd:
+ case kPhilHardwareFinished:
+ case 23026:
+ case 23027:
+ room->enableMouse();
+ break;
+ case 23007:
+ handleEvent(23010);
+ _hintTimerLength = 40000;
+ rescheduleHintTimer();
+ break;
+ case 23008:
+ room->playAnim("c8140ba0", 1101, PlayAnimParams::disappear(), 23015);
+ room->playAnim("c8150ba0", 1101, PlayAnimParams::disappear(), 23016);
+ room->playSound("c8130ma0", 23020);
+ room->playSound("c8130eb0");
+ room->playSound("c8130ec0");
+ break;
+ case 23010:
+ if (persistent->_hintsAreEnabled && room->isMouseEnabled()) {
+ room->disableMouse();
+ room->playVideo("c8170ba0", 0, 23011, Common::Point(0, 216));
+ }
+ break;
+ case 23011:
+ room->enableMouse();
+ break;
+ case 23015:
+ room->playAnimKeepLastFrame("c8130bf0", 1101, 23017);
+ room->playSound("c8130ef0");
+ break;
+ case 23016:
+ room->playAnimKeepLastFrame("c8130be0", 1101, 23018);
+ room->playSound("c8130ee0");
+ break;
+ case 23017:
+ room->enableHotzone("Sword");
+ room->enableMouse();
+ break;
+ case 23018:
+ room->enableHotzone("Shield");
+ room->enableMouse();
+ break;
+ case 23019:
+ room->resetFade();
+ room->disableHeroBelt();
+ room->resetLayers();
+ room->addStaticLayer("c8180pa0", 9000);
+ room->playSound("g0261ma0", 23031);
+ room->playSound(persistent->_gender == kMale ? "c8180wa0" : "c8180wb0", 23029);
+ break;
+ case 23029:
+ room->playSound("c8180wc0", 23030);
+ break;
+ case 23030:
+ room->playVideo("c8180ba0", 0, 23032);
+ break;
+ case 23031:
+ break;
+ case 23032:
+ room->selectFrame("c8180bb0", 101, 0);
+ g_vm->moveToRoom(kSeriphosRoom);
+ break;
+ // TODO: lighting up of the beam: 23035/23036 are for lighting up
+ case 23035:
+ case 23036:
+ room->selectFrame(LayerId(kLights, 0, "source"), kLightsZ, 0);
+ /* Fallthrough */
+ case 23037:
+ room->playAnimLoop("c8110bc0", 211);
+ room->enableHotzone("Athena's Sword");
+ room->enableHotzone("Athena's Shield");
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ room->enableHotzone(puzzleElements[i].hotname);
+ break;
+ case 23020:
+ room->playAnim("c8130bd0", 0, PlayAnimParams::disappear(), 23041);
+ /*Fallthrough */
+ case 23038:
+ g_vm->addTimer(23040, 640);
+ g_vm->addTimer(23039, 40, 15);
+ break;
+ case 23039:
+ //TODO progressive beams
+ break;
+ case 23040:
+ for (unsigned i = 0; i < 12; i++)
+ room->stopAnim(LayerId(kLights, i, "internal"));
+ room->stopAnim(LayerId(kLights, 0, "source"));
+ room->stopAnim("c8110bc0");
+ break;
+ case 23043:
+ room->stopAnim("c8010oc0");
+ room->enableMouse();
+ break;
+ case 23044:
+ room->stopAnim("c8010ob0");
+ room->enableMouse();
+ break;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ Quest quest = persistent->_quest;
+ room->loadHotZones("Athena.HOT", false);
+ room->addStaticLayer("c8010pa0", kBackgroundZ);
+ room->addStaticLayer("c8010ta0", 601);
+ room->enableHotzone("Athena");
+ room->enableHotzone("Seriphos");
+
+ if (quest == kMedusaQuest && !persistent->_athenaPuzzleSolved) {
+ persistent->_athenaIntroPlayed = true;
+ room->disableMouse();
+ room->playVideo(persistent->_gender == kMale ? "c8040wa0" : "c8040wb0",
+ 1101, kIntroFinished);
+ room->playAnim(kAthenaAnim, 1101, PlayAnimParams::loop());
+ room->playSound("c8040ma0", 23013);
+ }
+
+ if (!persistent->_athenaShieldTaken) {
+ if (persistent->_athenaPuzzleSolved) {
+ room->selectFrame("c8130be0", 1101, 4);
+ room->enableHotzone("Shield");
+ } else {
+ room->selectFrame("c8150ba0", 1101, 0);
+ }
+ }
+
+ if (!persistent->_athenaSwordTaken) {
+ if (persistent->_athenaPuzzleSolved) {
+ room->selectFrame("c8130bf0", 1101, 7);
+ room->enableHotzone("Sword");
+ } else {
+ room->selectFrame("c8140ba0", 1101, 0);
+ }
+ }
+
+ room->playAnimLoop("c8030ba0", 201);
+ g_vm->getHeroBelt()->setColour(HeroBelt::kCool);
+ }
+private:
+ void rescheduleHintTimer() {
+ Persistent *persistent = g_vm->getPersistent();
+
+ g_vm->cancelTimer(23007);
+ if (!persistent->_athenaPuzzleSolved)
+ g_vm->addTimer(23007, _hintTimerLength);
+ }
+
+ void handlePuzzleClick(int num) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+
+ rescheduleHintTimer();
+
+ _puzzleState[num] = (_puzzleState[num] + 1) % 3;
+ memset(_isPuzzleLit, 0, sizeof (_isPuzzleLit));
+ _isPuzzleLit[0] = true;
+ bool done = false;
+ while (!done) {
+ done = true;
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ if (_puzzleState[i] != 0 && _isPuzzleLit[i]
+ && puzzleElements[i].lights[_puzzleState[i] - 1] >= 0
+ && !_isPuzzleLit[puzzleElements[i].lights[_puzzleState[i] - 1]]) {
+ _isPuzzleLit[puzzleElements[i].lights[_puzzleState[i] - 1]] = true;
+ done = false;
+ }
+ }
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ if (_puzzleState[i] != 0 && _isPuzzleLit[i]
+ && puzzleElements[i].lights[_puzzleState[i] - 1] == kWonPuzzle) {
+ _isPuzzleWon = true;
+ break;
+ }
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ if (!_isPuzzleLit[i])
+ _puzzleState[i] = 0;
+ for (unsigned i = 0; i < 11; i++)
+ room->stopAnim(LayerId(kLights, i, "internal"));
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ if (_puzzleState[i] != 0 && _isPuzzleLit[i]) {
+ int ray = puzzleElements[i].rays[_puzzleState[i] - 1];
+ if (ray <= 0)
+ continue;
+ room->selectFrame(LayerId(kLights, ray, "internal"), kLightsZ, ray);
+ }
+
+ if (_isPuzzleLit[kArtist] && _puzzleState[kArtist] == 1)
+ room->playAnimLoop("c8120bg0", 601);
+ else
+ room->stopAnim("c8120bg0");
+ if (_isPuzzleLit[kScholar] && _puzzleState[kScholar] == 1)
+ room->playAnimLoop("c8120bg1", 601);
+ else
+ room->stopAnim("c8120bg1");
+
+ if (_isPuzzleLit[num] && _puzzleState[num])
+ room->playSound("c8120ea0");
+
+ if (_isPuzzleWon) {
+ room->selectFrame(LayerId(kLights, 11, "internal"), kLightsZ, 11);
+ room->playSound("C8130eA0");
+ g_vm->addTimer(23008, 1000);
+ room->disableHotzone("Athena's Sword");
+ room->disableHotzone("Athena's Shield");
+ for (unsigned i = 0; i < kNumPuzzleElements; i++)
+ room->disableHotzone(puzzleElements[i].hotname);
+ room->disableMouse();
+ persistent->_athenaPuzzleSolved = true;
+ }
+
+ room->playSoundLoop(
+ persistent->_quest != kMedusaQuest || persistent->_athenaPuzzleSolved
+ ? "c8010ea0" : "c8110ea0");
+ }
+ bool _playAreYouForgetting;
+ bool _playAthenaTempleHardware;
+ bool _isPuzzleLit[kNumPuzzleElements];
+ int _puzzleState[kNumPuzzleElements];
+ bool _isPuzzleWon;
+ int _hintTimerLength;
+};
+
+Common::SharedPtr<Hadesch::Handler> makeAthenaHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new AthenaHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/catacombs.cpp b/engines/hadesch/rooms/catacombs.cpp
new file mode 100644
index 0000000000..84fab752a5
--- /dev/null
+++ b/engines/hadesch/rooms/catacombs.cpp
@@ -0,0 +1,477 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/ambient.h"
+
+namespace Hadesch {
+
+static const char *caTxtNames[] = {
+ "CaLeft.txt",
+ "CaCenter.txt",
+ "CaRight.txt"
+};
+
+static const char *skullHotzones[] = {
+ "LSkull",
+ "CSkull",
+ "RSkull"
+};
+
+static const char *torchHotzones[] = {
+ "LTorch",
+ "CTorch",
+ "RTorch"
+};
+
+static const char *signNames[] = {
+ "SignToHelen",
+ "SignToGuards",
+ "SignToPainPanic"
+};
+
+static const char *musicNames[] = {
+ "MusicHelen",
+ "MusicGuard",
+ "MusicPainPanic"
+};
+
+static const char *painSounds[] = {
+ "SndPainBedtime",
+ "SndPanicBoneHead",
+ "SndPainRecognize"
+};
+
+static const char *painSounds2[] = {
+ "SndPanicLightsOut",
+ "SndPainByeBye",
+ "SndPanicMaybeHit"
+};
+
+enum {
+ kBackgroundCenterZ = 10001,
+ kBackgroundZ = 10000
+};
+
+enum {
+ // Originally 22014 for all 3 but I'd rather avoid passing extra args around
+ kL1TrochLitLeft = 1022001,
+ kL1TrochLitCenter = 1022002,
+ kL1TrochLitRight = 1022003,
+ kBonkVideoFinished = 1022004
+};
+
+class CatacombsHandler : public Handler {
+public:
+ CatacombsHandler() {
+ _philWarnedTorch = false;
+ _philBangPlayed = false;
+ }
+
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+
+ if (name == "LExit") {
+ handleExit(kCatacombsLeft);
+ return;
+ }
+ if (name == "CExit") {
+ handleExit(kCatacombsCenter);
+ return;
+ }
+ if (name == "RExit") {
+ handleExit(kCatacombsRight);
+ return;
+ }
+ if (level == 0 && (name == "LTorch" || name == "CTorch" || name == "RTorch")) {
+ g_vm->getHeroBelt()->placeToInventory(kTorch);
+ room->stopAnim(caVariantGet(_torchPosition, "TorchNormal"));
+ return;
+ }
+
+ if (level == 1 && name == "LTorch") {
+ lightTorchL1(kCatacombsLeft);
+ return;
+ }
+
+ if (level == 1 && name == "CTorch") {
+ lightTorchL1(kCatacombsCenter);
+ return;
+ }
+
+ if (level == 1 && name == "RTorch") {
+ lightTorchL1(kCatacombsRight);
+ return;
+ }
+
+ if (name == "LSkull" || name == "CSkull" || name == "RSkull") {
+ _decoderPosition = 0;
+ renderDecoder();
+ if (!_philBangPlayed) {
+ _philBangPlayed = false;
+ room->playSound("SndBigBang", 22012);
+ }
+ return;
+ }
+
+ if (name == "DecoderDown" && _decoderPosition < 6) {
+ _decoderPosition++;
+ renderDecoder();
+ room->playAnim("AnimDecoderArrows", 149, PlayAnimParams::disappear().partial(0, 0));
+ return;
+ }
+
+ if (name == "DecoderUp" && _decoderPosition > 0) {
+ _decoderPosition--;
+ renderDecoder();
+ room->playAnim("AnimDecoderArrows", 149, PlayAnimParams::disappear().partial(1, 1));
+ return;
+ }
+ }
+
+ bool handleClickWithItem(const Common::String &name, InventoryItem item) override {
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+ if (item == kTorch && level == 1) {
+ if (name == "LTorch") {
+ lightTorchL1(kCatacombsLeft);
+ return true;
+ }
+
+ if (name == "CTorch") {
+ lightTorchL1(kCatacombsCenter);
+ return true;
+ }
+
+ if (name == "RTorch") {
+ lightTorchL1(kCatacombsRight);
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+ switch(eventId) {
+ case kL1TrochLitLeft:
+ case kL1TrochLitCenter:
+ case kL1TrochLitRight: {
+ CatacombsPosition side = (CatacombsPosition) (eventId - kL1TrochLitLeft + kCatacombsLeft);
+ bool isHelen = persistent->_catacombPaths[1][side] == kCatacombsHelen;
+ room->playAnimLoop(
+ caVariantGet(side, isHelen ? "TorchLong" : "TorchNormal"),
+ caVariantGet(side, "TorchZ").asUint64());
+ break;
+ }
+ case 22009:
+ room->playVideo("PhilQuickNameThatTune", 0);
+ break;
+ case 22012:
+ room->playVideo("PhilWowLowOnTroops", 0);
+ break;
+ case 22016:
+ room->playSound("SndGuardTrapDoorOpen", 22017);
+ break;
+ case 22017:
+ room->playSound("SndGuardLaugh", 22018);
+ break;
+ case 22018:
+ room->playSound(
+ Common::String::format("T3220w%c0",
+ g_vm->getRnd().getRandomNumberRng('A', 'C')),
+ 22019);
+ break;
+ case 22019:
+ room->playSound("SndGuardTrapDoorClose", 22020);
+ break;
+ case 22020:
+ persistent->_catacombLevel = kCatacombLevelSign;
+ g_vm->moveToRoom(kTroyRoom);
+ break;
+ case 22022:
+ room->playSound(painSounds2[level], 22023);
+ persistent->_catacombLevel = kCatacombLevelSign;
+ break;
+ case 22023:
+ room->playVideo("MovPainPanicBonk", 103, kBonkVideoFinished);
+ break;
+ case kBonkVideoFinished:
+ g_vm->moveToRoom(kTroyRoom);
+ break;
+ }
+ }
+
+ void handleMouseOver(const Common::String &name) override {
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+
+ if (level == 2) {
+ if (name == "LExit") {
+ playTune(kCatacombsLeft);
+ return;
+ }
+ if (name == "CExit") {
+ playTune(kCatacombsCenter);
+ return;
+ }
+ if (name == "RExit") {
+ playTune(kCatacombsRight);
+ return;
+ }
+ }
+ }
+
+ void handleMouseOut(const Common::String &name) override {
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+
+ if (level == 2 && (name == "LExit" || name == "CExit" || name == "RExit"))
+ stopTune();
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ CatacombsLevel level = persistent->_catacombLevel;
+
+ persistent->_catacombLastLevel = level;
+
+ room->playSoundLoop("T3010eA0");
+
+ if (persistent->_catacombPainAndPanic) {
+ persistent->_catacombPainAndPanic = false;
+ room->addStaticLayer("DeadEndBackground", 10001);
+ room->playSound("SndPainPanicStinger", 22022);
+ room->playSound(painSounds[persistent->_catacombLevel]);
+ return;
+ }
+
+ if (level == 0)
+ room->loadHotZones("CaDecode.HOT", false);
+ room->playSoundLoop("T3010eA0");
+ // TODO: tremmors
+ // TODO: handle timer
+ g_vm->addTimer(22007, level == 2 ? 30000 : 40000, -1);
+
+ for (int i = 0; i < 3; i++)
+ _caMapTxt[i] = TextTable(
+ Common::SharedPtr<Common::SeekableReadStream>(room->openFile(caTxtNames[i])), 13);
+
+ if (persistent->_catacombPaths[0][0] == kCatacombsHelen
+ && persistent->_catacombPaths[1][0] == kCatacombsHelen) {
+ for (int i = 0; i < 3; i++) {
+ Common::Array<int> p3 = permute3();
+ persistent->_catacombVariants[0][i] = p3[0];
+ persistent->_catacombVariants[1][i] = p3[1];
+ persistent->_catacombVariants[2][i] = p3[2];
+ }
+ for (int i = 0; i < 3; i++) {
+ Common::Array<int> p3 = permute3();
+ persistent->_catacombPaths[i][0] = (CatacombsPath) p3[0];
+ persistent->_catacombPaths[i][1] = (CatacombsPath) p3[1];
+ persistent->_catacombPaths[i][2] = (CatacombsPath) p3[2];
+ }
+ persistent->_catacombDecoderSkullPosition = (CatacombsPosition) g_vm->getRnd().getRandomNumberRng(0, 2);
+ }
+
+ for (CatacombsPosition i = kCatacombsLeft; i <= kCatacombsRight; i = (CatacombsPosition) (i + 1)) {
+ room->loadHotZones(
+ caVariantGet(i, "Hotspots"), false);
+ room->addStaticLayer(
+ caVariantGet(i, "Background"),
+ i == kCatacombsCenter ? kBackgroundCenterZ : kBackgroundZ);
+ }
+
+ if (persistent->_catacombVariants[level][0] == 2) {
+ room->playAnimLoop("GlowingEyes", 900);
+ }
+
+ room->enableHotzone("LExit");
+ room->enableHotzone("CExit");
+ room->enableHotzone("RExit");
+
+ switch (level) {
+ case 0:
+ room->playSound("IntroMusic");
+ room->enableHotzone(skullHotzones[persistent->_catacombDecoderSkullPosition]);
+ room->selectFrame(
+ caVariantGet(persistent->_catacombDecoderSkullPosition, "SkullDecoder"), 450, 1);
+ for (CatacombsPosition i = kCatacombsLeft; i <= kCatacombsRight; i = (CatacombsPosition) (i + 1)) {
+ room->selectFrame(
+ caVariantGet(i, "SignBoard"), 501, 0);
+ room->selectFrame(
+ caVariantGet(i, signNames[persistent->_catacombPaths[level][i]]), 500, 0);
+ }
+ if (!persistent->isInInventory(kTorch)) {
+ _torchPosition = (CatacombsPosition) g_vm->getRnd().getRandomNumberRng(0, 2);
+ room->enableHotzone(torchHotzones[_torchPosition]);
+ room->playAnimLoop(
+ caVariantGet(_torchPosition, "TorchNormal"),
+ caVariantGet(_torchPosition, "TorchZ").asUint64());
+ }
+ break;
+ case 1:
+ room->enableHotzone("LTorch");
+ room->enableHotzone("CTorch");
+ room->enableHotzone("RTorch");
+ for (CatacombsPosition side = kCatacombsLeft; side <= kCatacombsRight; side = (CatacombsPosition) (side + 1)) {
+ room->selectFrame(
+ caVariantGet(side, "TorchNormalBurst"),
+ caVariantGet(side, "TorchZ").asUint64(), 0);
+ }
+ break;
+ case 2:
+ room->playSound("CollapseSnd", 22009);
+ for (CatacombsPosition side = kCatacombsLeft; side <= kCatacombsRight; side = (CatacombsPosition) (side + 1)) {
+ room->playAnimLoop(
+ caVariantGet(side, "TorchNormal"),
+ caVariantGet(side, "TorchZ").asUint64());
+ }
+ break;
+ }
+
+ g_vm->getHeroBelt()->setColour(HeroBelt::kCool);
+ }
+
+private:
+ void stopTune() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ for (int i = 0; i < 3; i++)
+ room->stopAnim(musicNames[i]);
+ }
+
+ void playTune(CatacombsPosition side) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ stopTune();
+ room->playSoundLoop(musicNames[persistent->_catacombPaths[2][side]]);
+ }
+
+ void renderDecoder() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ room->selectFrame("AnimDecoderScroll", 151, 0);
+ room->selectFrame("AnimDecoderSymbols", 150, _decoderPosition);
+ room->selectFrame(
+ caVariantGet(persistent->_catacombDecoderSkullPosition, "SkullDecoder"), 450, 0);
+ room->enableHotzone("DecoderDone");
+ room->enableHotzone("DecoderDown");
+ room->enableHotzone("DecoderUp");
+ }
+
+ void handleExit(CatacombsPosition side) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+
+ if (level == 0 && !_philWarnedTorch && !persistent->isInInventory(kTorch) && persistent->_hintsAreEnabled) {
+ _philWarnedTorch = true;
+ room->playVideo("PhilGrabTheTorch", 0, 22003);
+ return;
+ }
+
+ switch (persistent->_catacombPaths[level][side]) {
+ case kCatacombsHelen:
+ room->disableMouse();
+ if (persistent->_catacombLevel == kCatacombLevelMusic) {
+ persistent->_catacombLevel = kCatacombLevelSign;
+ g_vm->moveToRoom(kPriamRoom);
+ } else {
+ persistent->_catacombLevel = (CatacombsLevel) (persistent->_catacombLevel + 1);
+ g_vm->moveToRoom(kCatacombsRoom);
+ }
+ break;
+ case kCatacombsGuards:
+ room->disableMouse();
+ g_vm->cancelTimer(22007);
+ room->fadeOut(1000, 22016);
+ break;
+ case kCatacombsPainAndPanic:
+ room->disableMouse();
+ g_vm->cancelTimer(22007);
+ persistent->_catacombPainAndPanic = true;
+ g_vm->moveToRoom(kCatacombsRoom);
+ break;
+ }
+ }
+
+ void lightTorchL1(CatacombsPosition side) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ bool isHelen = persistent->_catacombPaths[1][side] == kCatacombsHelen;
+ room->playAnim(
+ caVariantGet(side, isHelen ? "TorchLongBurst" : "TorchNormalBurst"),
+ caVariantGet(side, "TorchZ").asUint64(),
+ PlayAnimParams::disappear(), kL1TrochLitLeft + side - kCatacombsLeft);
+ room->playSound("SndTorchBurst");
+ room->disableHotzone(torchHotzones[side]);
+ }
+
+ Common::String caVariantGet(CatacombsPosition side, const Common::String &property) {
+ Persistent *persistent = g_vm->getPersistent();
+ int level = persistent->_catacombLevel;
+ int variant = persistent->_catacombVariants[level][side];
+ Common::String ret = _caMapTxt[side].get(variant, property);
+ if (ret == "") {
+ debug("No attrinute for %d/%s", side, property.c_str());
+ }
+ return ret;
+ }
+
+ Common::Array<int> permute3() {
+ Common::Array <int> ret;
+ int x = g_vm->getRnd().getRandomNumberRng(0, 5);
+ int a = x / 2;
+ ret.push_back(a);
+ int cand1 = a == 0 ? 1 : 0;
+ int cand2 = 0;
+ for (cand2 = 0; cand2 == a || cand2 == cand1; cand2++);
+ if (x % 2) {
+ ret.push_back(cand2);
+ ret.push_back(cand1);
+ } else {
+ ret.push_back(cand1);
+ ret.push_back(cand2);
+ }
+ return ret;
+ }
+
+ CatacombsPosition _torchPosition;
+ TextTable _caMapTxt[3];
+ bool _philWarnedTorch;
+ bool _philBangPlayed;
+ int _decoderPosition;
+};
+
+Common::SharedPtr<Hadesch::Handler> makeCatacombsHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new CatacombsHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/credits.cpp b/engines/hadesch/rooms/credits.cpp
new file mode 100644
index 0000000000..8f56d148cf
--- /dev/null
+++ b/engines/hadesch/rooms/credits.cpp
@@ -0,0 +1,84 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+
+namespace Hadesch {
+
+enum {
+ kBackgroundZ = 10000,
+ kCreditsZ = 1000
+};
+
+class CreditsHandler : public Handler {
+public:
+ CreditsHandler(bool inOptions) {
+ _inOptions = inOptions;
+ }
+
+ void handleClick(const Common::String &name) override {
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ switch(eventId) {
+ case 31001:
+ if (_inOptions)
+ g_vm->enterOptions();
+ else
+ g_vm->moveToRoom(g_vm->getPreviousRoomId());
+ break;
+ }
+ }
+
+ void frameCallback() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ int timeElapsed = (g_vm->getCurrentTime() - startTime);
+ room->selectFrame("h2030ba0", kCreditsZ, 0,
+ Common::Point(0, 481
+ - timeElapsed * 6151 / 136000
+ ));
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->disableHeroBelt();
+ room->disableMouse();
+ room->addStaticLayer("h2030pa0", kBackgroundZ);
+ room->playVideo("c2590ma0", 0, 31001);
+ room->selectFrame("h2030ba0", kCreditsZ, 0,
+ Common::Point(0, 481));
+ startTime = g_vm->getCurrentTime();
+ }
+private:
+ int startTime;
+ bool _inOptions;
+};
+
+Common::SharedPtr<Hadesch::Handler> makeCreditsHandler(bool inOptions) {
+ return Common::SharedPtr<Hadesch::Handler>(new CreditsHandler(inOptions));
+}
+
+}
diff --git a/engines/hadesch/rooms/crete.cpp b/engines/hadesch/rooms/crete.cpp
new file mode 100644
index 0000000000..c9ad845da8
--- /dev/null
+++ b/engines/hadesch/rooms/crete.cpp
@@ -0,0 +1,1608 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/ambient.h"
+
+namespace Hadesch {
+
+static const char *kTalusImage = "r1100bb0";
+static const char *kTalusImageWithShip = "r1100bc0";
+static const char *kTalusMovie = "r1100ba0";
+static const char *kTalusHotzone = "Talus";
+static const char *kTavernImage = "r2190ba0";
+static const char *kTavernHotzone = "Tavern";
+static const char *kPoseidonHighlight = "r1230ba0";
+static const char *kZeusHighlight = "r1240ba0";
+static const char *kHermesHighlight = "r2320ba0";
+static const char *kHornless1 = "g0170ob0";
+static const char *kHornless2 = "g0170oe0";
+static const char *kHornless3 = "g0170oh0";
+static const char *kHornless4 = "g0170ok0";
+static const char *kHorned = "g1800ob0";
+static const char *kHornedHotzone = "HornedStatue";
+static const char *kHornless1Hotzone = "HornlessStatue1";
+static const char *kHornless2Hotzone = "HornlessStatue2";
+static const char *kHornless3Hotzone = "HornlessStatue3";
+static const char *kHornless4Hotzone = "HornlessStatue4";
+static const char *kMerchantAnim = "r2130ba0";
+static const char *kOneManBandAnim = "r2040ba1";
+static const char *kOneManBandHotZone = "OneManBand";
+static const char *kAtlantisOpening = "r1210bb0";
+static const char *kAtlantisLargeDisk = "r1210be0";
+static const char *kAtlantisMediumDisk = "r1210bf0";
+static const char *kAtlantisSmallDisk = "r1210bg0";
+static const char *kAtlantisDiskBackground = "r1210os0";
+
+static const int vaseSol[] = {2, 3, 2, 3};
+static const char *vaseSound[] = {
+ "r1220ec0",
+ "r1220ed0",
+ "r1220ee0",
+ "r1220ef0"
+};
+
+static const char *vaseSegment[] = {
+ "r1220bc0",
+ "r1220bd0",
+ "r1220be0",
+ "r1220bf0"
+};
+
+enum {
+ kBackgroundZ = 10000,
+ kTalusZ = 6000,
+ kTavernImageZ = 5000,
+ kOneManBandZ = 1600,
+ kMerchantStandZ = 1205,
+ kMerchantZ = 1200,
+ kAtlantisDiskBackgroundZ = 1100,
+ kAtlantisLargeDiskZ = 1000,
+ kAtlantisMediumDiskZ = 900,
+ kAtlantisSmallDiskZ = 800,
+ kAtlantisOpeningZ = 700
+};
+
+enum {
+ kIntroMerchantVideoFinished = 12107,
+ kTakenHorned = 12115,
+ kTakenHornless = 12118,
+ kMerchantIdleTimer = 12121,
+ kMerchantIdleAnimCleanup = 12122,
+ kTalueMovieCompleted = 12129,
+ // 12137 is the end of statue animation that we handle as functor instead
+ kAtlantisDoorOpens = 12307,
+ kTavernMovieCompleted = 1012001,
+ kIntroMerchantPanFinished = 1012002,
+ kAtlantisBoatIntro2Finished = 1012003,
+ kTakenWood = 1012005,
+ kSoundVaseSegment1Finished = 1012006,
+ kSoundVaseSegment2Finished = 1012007,
+ kSoundVaseSegment3Finished = 1012008,
+ kSoundVaseSegment4Finished = 1012009,
+ kCoinGiven = 1012010,
+ kSandalsPlaced = 1012011
+};
+
+static const Common::Point strongBoxSideDotOffsets[] = {
+ Common::Point(1060, 278),
+ Common::Point(1090, 310),
+ Common::Point(1060, 339),
+ Common::Point(1032, 305)
+};
+
+static const Common::Point strongBoxTileOffsets[] = {
+ Common::Point(1005, 274),
+ Common::Point(1035, 276),
+ Common::Point(1064, 278),
+ Common::Point(1093, 280),
+ Common::Point(1005, 304),
+ Common::Point(1035, 306),
+ Common::Point(1064, 308),
+ Common::Point(1093, 310),
+ Common::Point(1035, 246),
+ Common::Point(1064, 248),
+ Common::Point(1035, 337),
+ Common::Point(1064, 339)
+};
+
+static const int baseFrame[4][8] = {
+ { 1, 3, 4, 6, 1, 3, 4, 6 },
+ { 7, 9, 10, 12, 13, 15, 16, 18 },
+ { 19, 21, 22, 24, 25, 27, 28, 30 },
+ { 31, 33, 34, 36, 31, 33, 34, 36 }
+};
+
+static const int kTileMovementTime = 500;
+
+class StrongBoxTile {
+public:
+ enum Letter {
+ kLetterZ = 1,
+ kLetterE,
+ kLetterU,
+ kLetterS
+ };
+ enum Orientation {
+ kOrientation0 = 0,
+ kOrientation90 = 90,
+ kOrientation180 = 180,
+ kOrientation270 = 270
+ };
+
+ void rotate() {
+ _orientation = (Orientation) ((_orientation + 90) % 360);
+ }
+
+ void show() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ int orientation = _orientation / 45;
+ if (_nextPosition != -1 && g_vm->getCurrentTime() > kTileMovementTime + _movementStartTime) {
+ _position = _nextPosition;
+ _nextPosition = -1;
+ }
+ Common::Point pos = strongBoxTileOffsets[_position];
+ if (_nextPosition != -1) {
+ double frac = (g_vm->getCurrentTime() - _movementStartTime + 0.0) / kTileMovementTime;
+ pos = strongBoxTileOffsets[_position] * (1-frac) + strongBoxTileOffsets[_nextPosition] * frac;
+ }
+ int zVal = 500;
+ switch (_rotationPhase) {
+ case 0:
+ case 2:
+ pos += Common::Point(-5, 4);
+ zVal = 300;
+ break;
+ case 1:
+ orientation++;
+ switch (_position) {
+ case 1:
+ pos = Common::Point(1046, 277);
+ break;
+ case 2:
+ pos = Common::Point(1065, 298);
+ break;
+ case 5:
+ pos = Common::Point(1025, 297);
+ break;
+ case 6:
+ pos = Common::Point(1045, 318);
+ break;
+ }
+ zVal = 300;
+ break;
+ }
+ int frame = baseFrame[_letter - kLetterZ][orientation] - 1;
+ if (_position < 4 && _nextPosition == -1 && _rotationPhase < 0)
+ frame++;
+ room->selectFrame(LayerId("r2010om0", _position, "pos"),
+ zVal, frame, pos);
+ }
+
+ void setRotationPhase(int phase) {
+ _rotationPhase = phase;
+ }
+
+ bool isMoving() {
+ return _nextPosition != -1 || _rotationPhase != -1;
+ }
+
+ int getPosition() {
+ return _position;
+ }
+
+ Letter getLetter() {
+ return _letter;
+ }
+
+ Orientation getOrientation() {
+ return _orientation;
+ }
+
+ void setPosition(int position) {
+ _position = position;
+ }
+
+ void moveTo(int pos) {
+ _nextPosition = pos;
+ _movementStartTime = g_vm->getCurrentTime();
+ }
+
+ StrongBoxTile() {
+ _letter = kLetterZ;
+ _position = 0;
+ _orientation = kOrientation0;
+ _nextPosition = -1;
+ _rotationPhase = -1;
+ }
+
+ StrongBoxTile(Letter letter, Orientation orientation, int position) {
+ _letter = letter;
+ _position = position;
+ _orientation = orientation;
+ _nextPosition = -1;
+ _rotationPhase = -1;
+ }
+private:
+ Letter _letter;
+ int _position;
+ int _nextPosition;
+ int _movementStartTime;
+ int _rotationPhase;
+ Orientation _orientation;
+};
+
+static const struct {
+ StrongBoxTile::Letter letter;
+ StrongBoxTile::Orientation orientation;
+} initialLetters[12] = {
+ {StrongBoxTile::kLetterZ, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterE, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterU, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterS, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterE, StrongBoxTile::kOrientation0},
+ {StrongBoxTile::kLetterZ, StrongBoxTile::kOrientation0},
+ {StrongBoxTile::kLetterU, StrongBoxTile::kOrientation270},
+ {StrongBoxTile::kLetterS, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterU, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterE, StrongBoxTile::kOrientation90},
+ {StrongBoxTile::kLetterZ, StrongBoxTile::kOrientation0},
+ {StrongBoxTile::kLetterS, StrongBoxTile::kOrientation0}
+};
+
+class CreteHandler : public Handler {
+public:
+ CreteHandler() {
+ _tavernCounter = 0;
+ _oneManBandCounter = 0;
+ _merchantIsBusy = false;
+ memset(_vaseBusy, 0, sizeof(_vaseBusy));
+ for (int i =0; i < 12; i++)
+ _strongBoxTiles[i] = StrongBoxTile(initialLetters[i].letter, initialLetters[i].orientation, i);
+ _strongBoxPopup = false;
+ _fadingHades = false;
+ _fadingHadesStartTime = 0;
+ }
+
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ if (name == kTalusHotzone) {
+ room->playVideo(kTalusMovie, kTalusZ, kTalueMovieCompleted,
+ Common::Point(54, 29));
+ room->setLayerEnabled(kTalusImageWithShip, false);
+ return;
+ }
+
+ if (name == "MinosPalace") {
+ room->disableMouse();
+ g_vm->moveToRoom(kMinosPalaceRoom);
+ return;
+ }
+
+ if (name == "Poseidon") {
+ Common::Array<Common::String> videos;
+ videos.push_back("r1230na0");
+ videos.push_back("r1230nb0");
+ videos.push_back("r1230nc0");
+
+ room->playStatueSMK(kPoseidonStatue,
+ kPoseidonHighlight,
+ 5000,
+ videos, 16, 39);
+ return;
+ }
+
+ if (name == "Zeus") {
+ Common::Array<Common::String> videos;
+ videos.push_back("r1240wa0");
+ videos.push_back("r1240wb0");
+
+ room->playStatueSMK(kZeusStatue, kZeusHighlight, 5000,
+ videos, 23, 35);
+ return;
+ }
+
+ if (name == "Hermes") {
+ Common::Array<Common::String> videos;
+ videos.push_back("r2320na0");
+ videos.push_back("r2320nb0");
+
+ room->playStatueSMK(kHermesStatue,
+ kHermesHighlight,
+ 4000,
+ videos, 22, 39, kOffsetRightRoom);
+ return;
+ }
+
+ if (name == kTavernHotzone) {
+ room->playAnimLoop(kTavernImage, 5000, kOffsetRightRoom);
+ room->playVideo(_tavernTalks[_tavernCounter], 5000,
+ kTavernMovieCompleted,
+ kOffsetRightRoom);
+ _tavernCounter = (_tavernCounter + 1) % _tavernTalks.size();
+ return;
+ }
+
+ if (name == "Argo") {
+ g_vm->moveToRoom(kArgoRoom);
+ return;
+ }
+
+ if (name == kHornedHotzone) {
+ g_vm->getHeroBelt()->placeToInventory(kHornedStatue, kTakenHornless);
+ room->setLayerEnabled(kHorned, false);
+ persistent->_creteShowHorned = false;
+ _merchantIsBusy = true;
+ room->disableHotzone(kHornedHotzone);
+ room->disableMouse();
+ return;
+ }
+
+ if (name == kHornless1Hotzone) {
+ g_vm->getHeroBelt()->placeToInventory(kHornlessStatue1, kTakenHorned);
+ room->setLayerEnabled(kHornless1, false);
+ persistent->_creteShowHornless1 = false;
+ room->disableHotzone(kHornless1Hotzone);
+ _merchantIsBusy = true;
+ room->disableMouse();
+ return;
+ }
+
+ if (name == kHornless2Hotzone) {
+ g_vm->getHeroBelt()->placeToInventory(kHornlessStatue2, kTakenHorned);
+ room->setLayerEnabled(kHornless2, false);
+ persistent->_creteShowHornless2 = false;
+ room->disableHotzone(kHornless2Hotzone);
+ _merchantIsBusy = true;
+ room->disableMouse();
+ return;
+ }
+
+ if (name == kHornless3Hotzone) {
+ g_vm->getHeroBelt()->placeToInventory(kHornlessStatue3, kTakenHorned);
+ room->setLayerEnabled(kHornless3, false);
+ persistent->_creteShowHornless3 = false;
+ room->disableHotzone(kHornless3Hotzone);
+ _merchantIsBusy = true;
+ room->disableMouse();
+ return;
+ }
+
+ if (name == kHornless4Hotzone) {
+ g_vm->getHeroBelt()->placeToInventory(kHornlessStatue4, kTakenHorned);
+ room->setLayerEnabled(kHornless4, false);
+ persistent->_creteShowHornless4 = false;
+ room->disableHotzone(kHornless4Hotzone);
+ _merchantIsBusy = true;
+ room->disableMouse();
+ return;
+ }
+
+ if (name == kOneManBandHotZone) {
+ room->playVideo(
+ Common::String::format(
+ "r2040b%c0", 'a' + (_oneManBandCounter % 3)),
+ 1600,
+ 12135, Common::Point(730, 183));
+ room->stopAnim(kOneManBandAnim);
+ _oneManBandCounter++;
+ return;
+ }
+
+ if (name == "AtlantisBoat") {
+ room->pushHotZones("Door.HOT");
+ room->disableHotzone("wood");
+ room->selectFrame("r1010ob0", 1200, 0);
+ room->selectFrame(kAtlantisDiskBackground, kAtlantisDiskBackgroundZ, 0);
+ for (unsigned i = 0; i < 3; i++)
+ _atlantisBoatPosition[i] = g_vm->getRnd().getRandomNumberRng(1, 7);
+ renderAtlantisDisks();
+ if (persistent->_creteIntroAtlantisWood) {
+ room->disableMouse();
+ room->playVideo("R1210BA0", 600, kAtlantisBoatIntro2Finished);
+ persistent->_creteIntroAtlantisWood = false;
+ }
+ return;
+
+ }
+
+ if (name == "SmallDisk") {
+ advanceAtlantisDisk(2);
+ return;
+ }
+
+ if (name == "MediumDisk") {
+ advanceAtlantisDisk(1);
+ return;
+ }
+
+ if (name == "LargeDisk") {
+ advanceAtlantisDisk(0);
+ return;
+ }
+
+ if (name == "wood") {
+ room->selectFrame(kAtlantisOpening, kAtlantisOpeningZ, 9);
+ g_vm->getHeroBelt()->placeToInventory(kWood, kTakenWood);
+ room->popHotZones();
+ room->disableHotzone("AtlantisBoat");
+ persistent->_creteShowAtlantisBoat = false;
+ return;
+ }
+
+ if (name == "Background") {
+ room->popHotZones();
+ room->stopAnim("r1010ob0");
+ hideStrongBox();
+ hideAtlantisPopupOverlays();
+ showMiniStrongBox();
+ return;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (name == Common::String::format("VaseSegment%d", i + 1) && !_vaseBusy[i]) {
+ _vasePos[i]++;
+ if (i == 1 || i == 3)
+ _vasePos[i] %= 6;
+ else
+ _vasePos[i] %= 4;
+ _vaseBusy[i] = true;
+ renderVase();
+ room->playSound(vaseSound[i], kSoundVaseSegment1Finished + i);
+ return;
+ }
+ }
+
+ if (name == "sandals") {
+ g_vm->getHeroBelt()->placeToInventory(kSandals, kSandalsPlaced);
+ room->selectFrame("r1220ba0", 500, 0);
+ room->disableMouse();
+ persistent->_creteSandalsState = Persistent::SANDALS_TAKEN;
+ return;
+ }
+
+ if (name == "AlchemistStand") {
+ _alchemistAmbient.play(false);
+ return;
+ }
+
+ if (name == "StrongBoxClosed"
+ || name == "StrongBoxOpen"
+ || name == "StrongBoxOpenPotion"
+ || name == "StrongBoxOpenNoPotion") {
+ showStrongBox();
+ return;
+ }
+
+ if (name == "Latch") {
+ room->disableMouse();
+ persistent->_creteStrongBoxState = Persistent::BOX_OPEN;
+ room->playAnimWithSound("r2230bb0", "g0082ea0", 1000,
+ PlayAnimParams::keepLastFrame().partial(1, 3),
+ 12402, kOffsetRightRoom);
+ return;
+ }
+
+ if (name == "ButtonN" && !strongBoxIsBusy()) {
+ strongBoxMoveTiles(1, 8, 9, 2);
+ return;
+ }
+
+ if (name == "ButtonE" && !strongBoxIsBusy()) {
+ strongBoxMoveTiles(2, 3, 7, 6);
+ return;
+ }
+
+ if (name == "ButtonW" && !strongBoxIsBusy()) {
+ strongBoxMoveTiles(1, 5, 4, 0);
+ return;
+ }
+
+ if (name == "ButtonS" && !strongBoxIsBusy()) {
+ strongBoxMoveTiles(5, 6, 11, 10);
+ return;
+ }
+
+ if (name == "ButtonC" && !strongBoxIsBusy()) {
+ g_vm->addTimer(12409, 250);
+ room->playSound("r2230ed0", 12412);
+ for (int i = 0; i < 12; i++) {
+ int pos = _strongBoxTiles[i].getPosition();
+ if (pos == 1 || pos == 2 || pos == 5 || pos == 6) {
+ _strongBoxTiles[i].setRotationPhase(0);
+ }
+ }
+ return;
+ }
+
+ if (name == "Potion") {
+ room->disableHotzone("Potion");
+ g_vm->getHeroBelt()->placeToInventory(kCoin, kCoinGiven);
+ persistent->_creteStrongBoxState = Persistent::BOX_OPEN_NO_POTION;
+ room->selectFrame("r2230bf0", 300, -1, kOffsetRightRoom);
+ return;
+ }
+ /*
+TODO:
+ MNSH: Merchant
+*/
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ switch (eventId) {
+ case kTakenWood:
+ room->stopAnim(kAtlantisOpening);
+ room->stopAnim("r1010ob0");
+ room->stopAnim(kAtlantisDiskBackground);
+ room->enableMouse();
+ break;
+ case kTalueMovieCompleted:
+ room->setLayerEnabled(kTalusImageWithShip, true);
+ break;
+ case kTavernMovieCompleted:
+ room->selectFrame(kTavernImage, kTavernImageZ, 0, kOffsetRightRoom);
+ break;
+ case kMerchantIdleTimer:
+ if (!persistent->_creteShowMerchant
+ || !persistent->_creteShowHornless4 || _merchantIsBusy)
+ break;
+ room->setLayerEnabled(kHornless4, false);
+ room->disableHotzone(kHornless4Hotzone);
+ room->playAnim(kMerchantAnim, kMerchantZ,
+ PlayAnimParams::disappear().partial(1, -2),
+ kMerchantIdleAnimCleanup);
+ break;
+ case kMerchantIdleAnimCleanup:
+ room->selectFrame(kMerchantAnim, kMerchantZ, 0);
+ room->setLayerEnabled(kHornless4, persistent->_creteShowHornless4);
+ room->setHotzoneEnabled(kHornless4Hotzone, persistent->_creteShowHornless4 && persistent->_creteTriedHornless[2]);
+ break;
+ case kIntroMerchantPanFinished:
+ room->setLayerEnabled(kMerchantAnim, false);
+ room->playVideo("R2200BA0", kMerchantZ,
+ kIntroMerchantVideoFinished,
+ Common::Point(308, 99));
+ break;
+ // TODO: replay of R2200BB0 and R2200BC0
+ case kIntroMerchantVideoFinished:
+ room->disableMouse();
+ room->playVideo("R2200BB0", kMerchantZ,
+ 12108,
+ Common::Point(304, 113));
+ _merchantIsBusy = true;
+ break;
+ case 12108:
+ room->disableMouse();
+ room->playVideo("R2200BC0", kMerchantZ,
+ 12109,
+ Common::Point(304, 110));
+ _merchantIsBusy = true;
+ break;
+ case 12109:
+ room->selectFrame(kMerchantAnim, kMerchantZ, 0);
+ room->enableMouse();
+ _merchantIsBusy = false;
+ break;
+ case kTakenHorned:
+ room->setLayerEnabled(kMerchantAnim, false);
+ room->playVideo("r2210ba0", kMerchantZ, 12117,
+ Common::Point(344, 111));
+ room->disableMouse();
+ break;
+ case 12117:
+ room->playVideo("r2210bb0", kMerchantZ, 12119,
+ Common::Point(308, 112));
+ break;
+ case kTakenHornless:
+ room->setLayerEnabled(kMerchantAnim, false);
+ room->playVideo("r2240ba0", kMerchantZ, 12119,
+ Common::Point(314, 91));
+ _merchantIsBusy = true;
+ break;
+ case 12119:
+ room->selectFrame(kMerchantAnim, kMerchantZ, 0);
+ if (!persistent->_cretePlayedEyeGhostTown) {
+ room->playVideo("r2210bc0", 1000, 12120,
+ Common::Point(0, 216));
+ persistent->_cretePlayedEyeGhostTown = true;
+ } else {
+ room->enableMouse();
+ _merchantIsBusy = false;
+ }
+ break;
+ case 12120:
+ room->selectFrame(kMerchantAnim, kMerchantZ, 0);
+ room->enableMouse();
+ _merchantIsBusy = false;
+ break;
+ case 12128:
+ case 12134:
+ case 12143:
+ case 12146:
+ room->enableMouse();
+ break;
+ case 12135:
+ room->playAnimLoop(kOneManBandAnim, kOneManBandZ, kOffsetRightRoom);
+ break;
+ case 12142:
+ if (persistent->_quest == kMedusaQuest && !persistent->_cretePlayedPhilAlchemist) {
+ persistent->_cretePlayedPhilAlchemist = true;
+ room->playVideo("r2220bc0", 1000, 12143, Common::Point(640, 216));
+ room->disableMouse();
+ }
+ if (persistent->_quest == kRescuePhilQuest && !persistent->_cretePlayedZeusCheckOutThatBox && persistent->_hintsAreEnabled) {
+ g_vm->addTimer(12144, 5000, -1);
+ }
+ break;
+ case 12144:
+ if (!room->isMouseEnabled() || persistent->_cretePlayedZeusCheckOutThatBox)
+ break;
+ persistent->_cretePlayedZeusCheckOutThatBox = true;
+ room->disableMouse();
+ room->playAnimWithSound("r2230ba0", "r2230wa0", 4000, PlayAnimParams::keepLastFrame(), 12145,
+ kOffsetRightRoom);
+ break;
+ case kAtlantisBoatIntro2Finished:
+ room->enableMouse();
+ break;
+ case kAtlantisDoorOpens:
+ room->disableHotzone("LargeDisk");
+ room->disableHotzone("MediumDisk");
+ room->disableHotzone("SmallDisk");
+ room->disableHotzone("Background");
+ room->enableHotzone("wood");
+ room->playVideo("r1210bd0", 600, 12308);
+ break;
+ case 12308:
+ room->enableMouse();
+ break;
+ case kSoundVaseSegment1Finished:
+ case kSoundVaseSegment2Finished:
+ case kSoundVaseSegment3Finished:
+ case kSoundVaseSegment4Finished: {
+ int vase = eventId - kSoundVaseSegment1Finished;
+ bool won = true;
+ _vaseBusy[vase] = false;
+ for (int i = 0; i < 4; i++) {
+ if (_vaseBusy[i])
+ won = false;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (_vasePos[i] != vaseSol[i])
+ won = false;
+ }
+
+ if (!won)
+ break;
+
+ for (int i = 0; i < 4; i++)
+ room->stopAnim(vaseSegment[i]);
+
+ for (int i = 0; i < 4; i++)
+ room->disableHotzone(Common::String::format("VaseSegment%d", i + 1));
+
+ room->playAnim("r1220ba0", 500,
+ PlayAnimParams::disappear().partial(0, 17), 12206);
+ room->playVideo("r1220mb0", 0);
+ room->playSound("r1220ea0");
+ room->disableMouse();
+ break;
+ }
+ case 12206:
+ showSandals();
+ persistent->_creteSandalsState = Persistent::SANDALS_SOLVED;
+ room->enableMouse();
+ break;
+ case 12303:
+ case 12304:
+ case 12305:
+ if (_atlantisBoatPosition[0] == 0
+ && _atlantisBoatPosition[1] == 0
+ && _atlantisBoatPosition[2] == 0) {
+ handleEvent(12306);
+ }
+ break;
+ case 12306:
+ room->disableMouse();
+ hideAtlantisPopupOverlays();
+ room->playAnimWithSound(kAtlantisOpening, "r1210eb0",kAtlantisOpeningZ,
+ PlayAnimParams::keepLastFrame().partial(0, 8),
+ kAtlantisDoorOpens);
+ break;
+ case 12402:
+ room->enableMouse();
+ redrawStrongBox();
+ // Fallthrough
+ case 12403:
+ room->disableMouse();
+ room->playAnimWithSound("r2230ba0", "r2230wb0", 4000,
+ PlayAnimParams::keepLastFrame(), 12404,
+ kOffsetRightRoom);
+ break;
+ case 12404:
+ case 12405:
+ if (persistent->_hintsAreEnabled)
+ g_vm->addTimer(12406, 5000);
+ // Fallthrough
+ case 12407:
+ case 12408:
+ case 12145:
+ room->playAnim("r2230ba0", 4000, PlayAnimParams::disappear().backwards(), -1,
+ kOffsetRightRoom);
+ room->enableMouse();
+ break;
+ case 12406:
+ if (!room->isMouseEnabled())
+ break;
+ room->disableMouse();
+ room->playAnimWithSound("r2230ba0", "r2230wc0", 4000,
+ PlayAnimParams::keepLastFrame(), 12407,
+ kOffsetRightRoom);
+ break;
+ case 12409:
+ for (int i = 0; i < 12; i++) {
+ int pos = _strongBoxTiles[i].getPosition();
+ if (pos == 1 || pos == 2 || pos == 5 || pos == 6) {
+ _strongBoxTiles[i].setRotationPhase(1);
+ }
+ }
+ g_vm->addTimer(12410, 250);
+ break;
+ case 12410:
+ for (int i = 0; i < 12; i++) {
+ int pos = _strongBoxTiles[i].getPosition();
+ if (pos == 1 || pos == 2 || pos == 5 || pos == 6) {
+ _strongBoxTiles[i].setRotationPhase(2);
+ _strongBoxTiles[i].rotate();
+ switch (pos) {
+ case 1:
+ _strongBoxTiles[i].setPosition(2);
+ break;
+ case 2:
+ _strongBoxTiles[i].setPosition(6);
+ break;
+ case 5:
+ _strongBoxTiles[i].setPosition(1);
+ break;
+ case 6:
+ _strongBoxTiles[i].setPosition(5);
+ break;
+ }
+ }
+ }
+ g_vm->addTimer(12411, 250);
+ break;
+ case 12411:
+ for (int i = 0; i < 12; i++) {
+ _strongBoxTiles[i].setRotationPhase(-1);
+ }
+ redrawStrongBox();
+ break;
+ case 12414:
+ room->playVideo("v4190ma0", 0, 12415);
+ break;
+ case 12204:
+ case 12415:
+ room->enableMouse();
+ break;
+ case kCoinGiven:
+ g_vm->getHeroBelt()->placeToInventory(kPotion);
+ break;
+ case 12124:
+ _fadingHades = true;
+ _fadingHadesStartTime = g_vm->getCurrentTime();
+ break;
+ case 12125:
+ room->stopAnim("r2035ba0");
+ room->playVideo("R2035BE0", 1200, 12127);
+ break;
+ case 12127:
+ g_vm->moveToRoom(kDaedalusRoom);
+ break;
+ case kSandalsPlaced:
+ room->playVideo("r1220bb0", 0, 12204);
+ break;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ Quest quest = persistent->_quest;
+
+ if (persistent->_creteHadesPusnishesPainAndPanic) {
+ room->disableHeroBelt();
+ room->disableMouse();
+ room->addStaticLayer("r2035pa0", kBackgroundZ); // background
+ room->selectFrame("r2035ba0", 1200, 0);
+
+ // Originally event 12123
+ room->playSound("r2035wa0", 12124);
+ g_vm->moveToRoom(kDaedalusRoom);
+ return;
+ }
+
+ room->loadHotZones("Crete.HOT", false);
+ room->addStaticLayer("r1010pa0", kBackgroundZ); // background
+ g_vm->getHeroBelt()->setColour(HeroBelt::kWarm);
+
+ if (quest != kMedusaQuest && quest != kRescuePhilQuest) {
+ room->playAnimLoop("r2010oe0", 7000, kOffsetRightRoom);
+ room->playAnimLoop("r2010tb0", 6500, kOffsetRightRoom);
+ }
+ room->playAnimLoop("r2010ta0", 7600, kOffsetRightRoom);
+ room->playAnimLoop("r1120ba0", 5500);
+ room->playAnimLoop("r1160ba0", 5000);
+ room->playAnimLoop("r1170ba0", 5000);
+
+ room->selectFrame(kTavernImage, kTavernImageZ, 0, kOffsetRightRoom);
+ room->enableMouse();
+ room->setPannable(true);
+ room->enableHotzone(kTavernHotzone);
+ room->enableHotzone("Argo");
+ room->enableHotzone("Hermes");
+ room->enableHotzone("Zeus");
+ room->enableHotzone("Poseidon");
+ room->enableHotzone("MinosPalace");
+ if (quest != kMedusaQuest && quest != kRescuePhilQuest) {
+ room->enableHotzone("AlchemistStand");
+ }
+
+ room->setUserPanCallback(-1, -1, 12140, 12142);
+
+ if (quest == kMedusaQuest && !persistent->_creteAlchemistExploded) {
+ persistent->_creteAlchemistExploded = true;
+ room->disableMouse();
+ room->playAnimWithSound("r1190ba0", "r1190ea0", 1005, PlayAnimParams::disappear(), 12128);
+ }
+
+ if (quest == kRescuePhilQuest || quest == kMedusaQuest) {
+ room->selectFrame("r2010op0", 7500, 0, kOffsetRightRoom);
+ }
+
+ if (quest == kMedusaQuest) {
+ room->playAnimWithSound("r2220bb0", "r2220eb0", 4500, PlayAnimParams::loop(), -1, kOffsetRightRoom);
+ }
+
+ showMiniStrongBox();
+
+ switch (quest) {
+ case kCreteQuest:
+ _tavernTalks.push_back("r2250xa0");
+ _tavernTalks.push_back("r2250xb0");
+ _tavernTalks.push_back("r2250xc0");
+ _tavernTalks.push_back("r2250xd0");
+ _tavernTalks.push_back("r2250xe0");
+ _tavernTalks.push_back("r2250xf0");
+ _tavernTalks.push_back("r2220xa0");
+ _tavernTalks.push_back("r2220xb0");
+ _tavernTalks.push_back("r2220xc0");
+ _tavernTalks.push_back("r2220xd0");
+ _tavernTalks.push_back("r2220xe0");
+ _tavernTalks.push_back("r2220xf0");
+ break;
+ case kTroyQuest:
+ _tavernTalks.push_back("r2260xa0");
+ _tavernTalks.push_back("r2260xb0");
+ _tavernTalks.push_back("r2195xa0");
+ _tavernTalks.push_back("r2195xb0");
+ _tavernTalks.push_back("r2290xf0");
+ _tavernTalks.push_back("r2220xa0");
+ _tavernTalks.push_back("r2220xb0");
+ _tavernTalks.push_back("r2220xc0");
+ _tavernTalks.push_back("r2220xd0");
+ _tavernTalks.push_back("r2220xe0");
+ _tavernTalks.push_back("r2220xf0");
+ break;
+ case kMedusaQuest:
+ _tavernTalks.push_back("r2270xa0");
+ _tavernTalks.push_back("r2270xb0");
+ _tavernTalks.push_back("r2290xg0");
+ _tavernTalks.push_back("r2290xh0");
+ _tavernTalks.push_back("r2290xi0");
+ _tavernTalks.push_back("r2290xj0");
+ _tavernTalks.push_back("r2290xk0");
+ _tavernTalks.push_back("r2290xl0");
+ break;
+ case kRescuePhilQuest:
+ _tavernTalks.push_back("r2280xa0");
+ _tavernTalks.push_back("r2280xc0");
+ _tavernTalks.push_back("r2280xd0");
+ _tavernTalks.push_back("r2290xg0");
+ _tavernTalks.push_back("r2290xh0");
+ _tavernTalks.push_back("r2290xi0");
+ _tavernTalks.push_back("r2290xj0");
+ _tavernTalks.push_back("r2290xk0");
+ _tavernTalks.push_back("r2290xl0");
+ break;
+
+ // To silence warning
+ case kNoQuest:
+ case kEndGame:
+ case kNumQuests:
+ break;
+ }
+ _tavernTalks.push_back("r2290xa0");
+ _tavernTalks.push_back("r2290xb0");
+ _tavernTalks.push_back("r2290xc0");
+ _tavernTalks.push_back("r2290xd0");
+ _tavernTalks.push_back("r2290xe0");
+
+ int bg1_variant = -1;
+ int bg2_variant = -1;
+
+ bool showOiBoat = false;
+
+ if (!persistent->_creteShowMerchant && (quest != kMedusaQuest || !persistent->_medisleShowFates)) {
+ bg1_variant = g_vm->getRnd().getRandomNumberRng(0, 2);
+ bg2_variant = g_vm->getRnd().getRandomNumberRng(0, 2);
+ debug("BG variants %d and %d", bg1_variant, bg2_variant);
+ }
+
+ if (randomBool()) {
+ showOiBoat = true;
+ room->addStaticLayer("r1010oi0", 4000);
+ }
+ if (randomBool())
+ room->addStaticLayer("r1010oj0", 4000);
+ if (randomBool())
+ room->addStaticLayer("r1010ok0", 4000);
+ if (randomBool())
+ room->addStaticLayer("r1010ol0", 4000);
+ if (randomBool())
+ room->addStaticLayer("r1010on0", 4001);
+ if (randomBool())
+ room->addStaticLayer("r1010oo0", 4000);
+ if (randomBool())
+ room->addStaticLayer("r1010op0", 4100);
+ if (randomBool())
+ room->addStaticLayer("r1010or0", 6500);
+
+ if (quest != kCreteQuest)
+ persistent->_creteShowAtlantisBoat = false;
+
+ if (persistent->_creteShowAtlantisBoat) {
+ room->addStaticLayer("r1010od0", 3900);
+ room->addStaticLayer("r1010ta0", 3800);
+ room->enableHotzone("AtlantisBoat");
+ }
+
+ room->playAnimLoop("r1010om0", 3000);
+
+ int minotaurPosition = -1;
+ int toughGuyPosition = -1;
+ int womanSmellPosition = -1;
+ if (quest == kCreteQuest && !persistent->_creteShowMerchant && !persistent->_creteShowAtlantisBoat)
+ minotaurPosition = g_vm->getRnd().getRandomNumberRng(0, 3);
+
+ bool showCat = false;
+ bool showStatueMan = false;
+ bool showOldMan = false;
+ bool showBlondBoy = false;
+ bool showMotherAndKidSmall = false;
+ bool showMotherAndKid = false;
+ bool showManSelling = false;
+ bool showDrawinWithoutWoman = false;
+ bool showWomanDrawing = false;
+ bool showWomanGraffiti = false;
+ bool showGirlAndNut = false;
+ bool showOneManBand = false;
+ int ambientComposite = g_vm->getRnd().getRandomNumberRng(1, 8);
+ debug("Ambients from Composite %d selected. \n", ambientComposite);
+ switch (ambientComposite) {
+ case 1:
+ showGirlAndNut = true;
+ if (quest != kCreteQuest && quest != kTroyQuest) {
+ toughGuyPosition = 1;
+ break;
+ }
+ if (minotaurPosition == 1) {
+ if (randomBool())
+ toughGuyPosition = 0;
+ break;
+
+ }
+ if (persistent->_creteShowMerchant) {
+ showOneManBand = true;
+ showWomanGraffiti = true;
+ break;
+ }
+ if (randomBool()) {
+ toughGuyPosition = 0;
+ showOneManBand = true;
+ showWomanGraffiti = true;
+ }
+ else
+ toughGuyPosition = 1;
+ break;
+ case 2:
+ womanSmellPosition = g_vm->getRnd().getRandomNumberRng(0, 2);
+ showWomanDrawing = true;
+ showManSelling = true;
+ if (quest != kMedusaQuest && quest != kRescuePhilQuest)
+ showMotherAndKid = true;
+ showBlondBoy = true;
+ break;
+ case 3:
+ if (quest != kRescuePhilQuest && bg1_variant != 0 && bg1_variant != 1)
+ showCat = true;
+ showOldMan = true;
+ if (!persistent->_creteShowMerchant) {
+ showManSelling = true;
+ toughGuyPosition = 2;
+ }
+ showWomanGraffiti = true;
+ showWomanDrawing = true;
+ break;
+ case 4:
+ if (quest != kRescuePhilQuest && bg1_variant != 0 && bg1_variant != 1)
+ showCat = true;
+ showWomanDrawing = true;
+ toughGuyPosition = 2;
+ showOneManBand = true;
+ break;
+ case 5:
+ if (quest != kCreteQuest && quest != kTroyQuest)
+ toughGuyPosition = 1;
+ else
+ toughGuyPosition = g_vm->getRnd().getRandomNumberRng(0, 1);
+ womanSmellPosition = g_vm->getRnd().getRandomNumberRng(0, 1);
+ break;
+ case 6:
+ showWomanGraffiti = true;
+ showWomanDrawing = true;
+ if (quest != kCreteQuest && quest != kTroyQuest)
+ toughGuyPosition = 1;
+ else
+ toughGuyPosition = g_vm->getRnd().getRandomNumberRng(0, 1);
+ break;
+ case 7:
+ womanSmellPosition = g_vm->getRnd().getRandomNumberRng(0, 2);
+ if (womanSmellPosition == 2 && (quest != kCreteQuest && quest != kTroyQuest))
+ womanSmellPosition = -1;
+ if (quest == kMedusaQuest)
+ showStatueMan = true;
+ showMotherAndKidSmall = true;
+ showWomanGraffiti = true;
+ break;
+ case 8:
+ if (quest != kCreteQuest && quest != kTroyQuest) {
+ toughGuyPosition = 1;
+ showDrawinWithoutWoman = true;
+ break;
+ }
+
+ if (!persistent->_creteShowMerchant && randomBool()) {
+ toughGuyPosition = 2;
+ showBlondBoy = true;
+ }
+ else
+ toughGuyPosition = 1;
+ showOldMan = true;
+ showDrawinWithoutWoman = true;
+ break;
+ }
+
+ int birdsState = g_vm->getRnd().getRandomNumberRng(0, 2);
+ int dolphinPosition = -1;
+ if (!persistent->_creteShowAtlantisBoat)
+ dolphinPosition = g_vm->getRnd().getRandomNumberRng(0, showOiBoat ? 1 : 2);
+ if (quest != kMedusaQuest && quest != kRescuePhilQuest)
+ _alchemistAmbient = ambient("r2220ba0", "r2220ec0", 5500, 15, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom, true);
+
+ // Disable conflicting images
+ if (minotaurPosition == 1
+ && (toughGuyPosition == 1 || toughGuyPosition == 2))
+ toughGuyPosition = -1;
+ if (minotaurPosition == 1) {
+ showWomanGraffiti = false;
+ showManSelling = false;
+ showOneManBand = false;
+ }
+ if (persistent->_creteShowMerchant &&
+ (toughGuyPosition == 0 || toughGuyPosition == 1))
+ toughGuyPosition = -1;
+ if (womanSmellPosition == 2)
+ showMotherAndKid = false;
+ if (persistent->_creteShowMerchant)
+ showCat = false;
+ if (quest != kCreteQuest && quest != kTroyQuest)
+ showOldMan = false;
+
+ debug("toughGuyPosition = %d, minotaurPosition = %d, dolphinPosition = %d",
+ toughGuyPosition, minotaurPosition, dolphinPosition);
+ if (showWomanGraffiti)
+ ambient("r2370ba0", "r2370ea0", 1700, 15, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ switch (toughGuyPosition) {
+ case 0:
+ ambient("r2340ba0", "r2340ea0", 1400, 15, 25, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ break;
+ case 1:
+ ambient("r2340bb0", "r2340eb0", 1400, 15, 25, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ break;
+ case 2:
+ ambient("r2340be0", "r2340ee0", 1600, 15, 25, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ break;
+ }
+ if (showOneManBand) {
+ room->playAnimLoop(kOneManBandAnim, kOneManBandZ, kOffsetRightRoom);
+ room->enableHotzone(kOneManBandHotZone);
+ }
+ if (showGirlAndNut)
+ ambient("r2140ba0", "r2140ea0", 1500, 15, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ if (showWomanDrawing)
+ ambient("r2110ba0", "r2110ea0", 1600, 10, 30, AmbientAnim::PAN_LEFT);
+ if (showDrawinWithoutWoman)
+ room->addStaticLayer("r2110bb0", 1600);
+ if (showManSelling)
+ ambient("r2150ba0", "r2150ea0", 1600, 10, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ if (showMotherAndKid)
+ ambient("r2160ba0", "r2160ea0", 1500, 15, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ if (showMotherAndKidSmall)
+ ambient("r2160bb0", "", 1500, 15, 30, AmbientAnim::PAN_LEFT);
+ if (showBlondBoy)
+ ambient("r2350ba0", "r2350ea0", 1500, 15, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ if (showOldMan)
+ ambient("r2100bb0", "r2100eb0", 1500, 10, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ if (showStatueMan)
+ ambient("r2050ba0", "r2050ea0", 1500, 10, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ if (showCat) {
+ room->addStaticLayer("r2010on0", kMerchantStandZ);
+ ambient("r2080bb0", "r2080eb0", kMerchantZ, 10, 30, AmbientAnim::PAN_LEFT);
+ }
+
+ if (persistent->_creteShowMerchant) {
+ room->addStaticLayer("r2010on0", kMerchantStandZ);
+ room->selectFrame(kMerchantAnim, kMerchantZ, 0);
+ room->playSound("G0261mA0");
+ if (persistent->_creteShowHorned) {
+ room->addStaticLayer(kHorned, 1220);
+ room->enableHotzone(kHornedHotzone);
+ }
+ if (persistent->_creteShowHornless2) {
+ room->addStaticLayer(kHornless2, 1190);
+ room->setHotzoneEnabled(kHornless2Hotzone,
+ persistent->_creteTriedHornless[0]);
+ }
+ if (persistent->_creteShowHornless1) {
+ room->addStaticLayer(kHornless1, 1180);
+ room->enableHotzone(kHornless1Hotzone);
+ }
+ if (persistent->_creteShowHornless3) {
+ room->addStaticLayer(kHornless3, 1180);
+ room->setHotzoneEnabled(kHornless3Hotzone,
+ persistent->_creteTriedHornless[1]);
+ }
+ if (persistent->_creteShowHornless4) {
+ room->addStaticLayer(kHornless4, 1180);
+ room->setHotzoneEnabled(kHornless4Hotzone,
+ persistent->_creteTriedHornless[2]);
+ g_vm->addTimer(kMerchantIdleTimer, 10000, -1);
+ }
+ }
+
+ if (birdsState == 0) {
+ room->addStaticLayer(kTalusImageWithShip, kTalusZ);
+ room->enableHotzone(kTalusHotzone);
+ } else
+ room->addStaticLayer(kTalusImage, kTalusZ);
+
+ // TODO: fix this
+ switch (minotaurPosition) {
+ case 0:
+ ambient("r2060bf0", "r2060ee0", 1800, 3, 4, AmbientAnim::PAN_RIGHT, kOffsetRightRoom); // shouldn't be an ambient
+ break;
+ case 1:
+ ambient("r2060bt0", "r2060ei0", 1650, 3, 4, AmbientAnim::PAN_RIGHT, kOffsetRightRoom); // shouldn't be an ambient
+ break;
+ case 2:
+// ambient("r1110ea0", "", 15, 30); // wrong
+ break;
+ case 3:
+ ambient("r1110ba0", "r1110eb0", 850, 20, 40, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ break;
+ }
+
+ switch (womanSmellPosition) {
+ case 0:
+ ambient("r2380ba0", "r2380ea0", 1550, 15, 30, AmbientAnim::PAN_ANY);
+ break;
+ case 1:
+ ambient("r2380bc0", "r2380ec0", 1550, 15, 30, AmbientAnim::PAN_ANY);
+ break;
+ case 2:
+ ambient("r2380bd0", "r2380ed0", 1550, 15, 30, AmbientAnim::PAN_RIGHT, kOffsetRightRoom);
+ break;
+ }
+
+ switch (birdsState) {
+ case 0:
+ ambient("r1060ba0", "r1060ea0", 4500, 10, 40, AmbientAnim::PAN_ANY);
+ break;
+ case 1:
+ ambient("r1060bb0","r1060eb0", 4500, 5, 40, AmbientAnim::PAN_ANY);
+ break;
+ case 2:
+ ambient("r1060bc0", "r1060ec0", 4500, 5, 40, AmbientAnim::PAN_ANY);
+ break;
+ }
+
+ if (dolphinPosition >= 0) {
+ Common::String dolphinAnim = Common::String::format(
+ "r1150b%c0", 'a' + dolphinPosition);
+ Common::String dolphinSound = Common::String::format(
+ "r1150e%c0", 'a' + dolphinPosition);
+
+ ambient(dolphinAnim, dolphinSound, 5500, 5, 20, AmbientAnim::PAN_LEFT);
+ }
+
+ if (bg1_variant >= 0 && bg2_variant >= 0) {
+ Common::String bg2 = Common::String::format(
+ "r1010o%c1", 'e' + bg2_variant);
+ room->addStaticLayer(bg2, 3500);
+
+ Common::String bg1 = Common::String::format(
+ "r1010o%c0", 'e' + bg1_variant);
+ room->addStaticLayer(bg1, 1000);
+ }
+
+ room->playSound("R1010eA0", true);
+
+ if (g_vm->getPreviousRoomId() == kMinosPalaceRoom) {
+ room->panRightInstant();
+ if (persistent->_creteIntroMerchant) {
+ room->disableMouse();
+ room->panLeftAnim(kIntroMerchantPanFinished);
+ _merchantIsBusy = true;
+ persistent->_creteIntroMerchant = false;
+ }
+
+ if (persistent->_creteIntroAtlantisBoat) {
+ persistent->_creteIntroAtlantisBoat = false;
+ room->disableMouse();
+ room->playVideo("r1180ba0", 1000, 12134, Common::Point(640, 216));
+ }
+
+ }
+
+ if (quest == kMedusaQuest && persistent->_medisleShowFates) {
+ switch (persistent->_creteSandalsState) {
+ case Persistent::SANDALS_NOT_SOLVED:
+ for (int i = 0; i < 4; i++)
+ room->enableHotzone(Common::String::format("VaseSegment%d", i + 1));
+ _vasePos[0] = (g_vm->getRnd().getRandomNumberRng(1, 3) + 2) % 4;
+ _vasePos[1] = (g_vm->getRnd().getRandomNumberRng(1, 5) + 3) % 6;
+ _vasePos[2] = (g_vm->getRnd().getRandomNumberRng(1, 5) + 2) % 6;
+ _vasePos[3] = (g_vm->getRnd().getRandomNumberRng(1, 3) + 3) % 4;
+ renderVase();
+ break;
+ case Persistent::SANDALS_SOLVED:
+ showSandals();
+ break;
+ case Persistent::SANDALS_TAKEN:
+ room->selectFrame("r1220ba0", 500, 0);
+ break;
+ }
+ }
+
+ switch (persistent->_quest) {
+ case kCreteQuest:
+ if (!persistent->_roomVisited[kMinosPalaceRoom]
+ || persistent->_creteShowAtlantisBoat) {
+ room->playVideo("r1260ma0", 0);
+ break;
+ }
+
+ if (persistent->_creteShowMerchant) {
+ room->playVideo("g0261ma0", 0);
+ break;
+ }
+ break;
+ case kMedusaQuest:
+ if (persistent->_medisleShowFates && persistent->_creteSandalsState == Persistent::SANDALS_NOT_SOLVED)
+ room->playVideo("r1220ma0", 0);
+ break;
+ case kRescuePhilQuest:
+ if (persistent->_creteStrongBoxState == Persistent::BOX_CLOSED || persistent->_creteStrongBoxState == Persistent::BOX_OPEN)
+ room->playVideo("r2230ma0", 0);
+ break;
+ // To silence warning
+ case kTroyQuest:
+ case kNoQuest:
+ case kEndGame:
+ case kNumQuests:
+ break;
+ }
+
+ if (!persistent->_creteSaidHelenPermanentResident && persistent->_quest == kTroyQuest) {
+ persistent->_creteSaidHelenPermanentResident = true;
+ room->disableMouse();
+ room->playVideo("r1250ba0", 0, 12134, Common::Point(0, 216));
+ }
+ }
+
+ void frameCallback() override {
+ if (_strongBoxPopup && strongBoxIsBusy())
+ redrawStrongBox();
+ if (_fadingHades) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ int val = (256 * (g_vm->getCurrentTime() - _fadingHadesStartTime)) / 2000;
+ if (val >= 256) {
+ _fadingHades = false;
+ val = 256;
+ handleEvent(12125);
+ }
+ room->setColorScale("r2035pa0", 256 - val);
+ }
+ }
+
+private:
+ void showMiniStrongBox() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ if (persistent->_quest != kRescuePhilQuest)
+ return;
+
+ switch(persistent->_creteStrongBoxState) {
+ case Persistent::BOX_CLOSED:
+ room->enableHotzone("StrongBoxClosed");
+ room->selectFrame("r2010ba0", 5000, 0, kOffsetRightRoom);
+ break;
+ case Persistent::BOX_OPEN:
+ room->enableHotzone("StrongBoxOpen");
+ room->selectFrame("r2010ba0", 5000, 1, kOffsetRightRoom);
+ break;
+ case Persistent::BOX_OPEN_POTION:
+ room->enableHotzone("StrongBoxOpenPotion");
+ room->selectFrame("r2010ba0", 5000, 2, kOffsetRightRoom);
+ break;
+ case Persistent::BOX_OPEN_NO_POTION:
+ room->enableHotzone("StrongBoxOpenNoPotion");
+ room->selectFrame("r2010ba0", 5000, 3, kOffsetRightRoom);
+ break;
+ }
+ }
+
+ AmbientAnim ambient(const Common::String &anim, const Common::String &sound,
+ int zValue, int minint, int maxint,
+ AmbientAnim::PanType pan,
+ Common::Point offset = Common::Point(0,0),
+ bool loop = true) {
+ AmbientAnim ret = AmbientAnim(anim, sound, zValue, minint * 1000, maxint * 1000,
+ loop ? AmbientAnim::KEEP_LOOP : AmbientAnim::DISAPPEAR, offset, pan);
+ ret.start();
+ return ret;
+ }
+
+ bool randomBool() const {
+ return g_vm->getRnd().getRandomNumberRng(0, 1);
+ }
+
+ void renderAtlantisDisks() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->selectFrame(kAtlantisLargeDisk, kAtlantisLargeDiskZ, _atlantisBoatPosition[0]);
+ room->selectFrame(kAtlantisMediumDisk, kAtlantisMediumDiskZ, _atlantisBoatPosition[1]);
+ room->selectFrame(kAtlantisSmallDisk, kAtlantisSmallDiskZ, _atlantisBoatPosition[2]);
+ }
+
+ void advanceAtlantisDisk(int diskNum) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ _atlantisBoatPosition[diskNum] = (_atlantisBoatPosition[diskNum] + 1) % 8;
+ renderAtlantisDisks();
+ room->playSound(Common::String::format("r1210e%c0", 'e' + diskNum), 12303 + diskNum);
+ }
+
+ void hideAtlantisPopupOverlays() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->stopAnim(kAtlantisLargeDisk);
+ room->stopAnim(kAtlantisMediumDisk);
+ room->stopAnim(kAtlantisSmallDisk);
+ room->stopAnim(kAtlantisDiskBackground);
+ }
+
+ void renderVase() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ for(int i = 0; i < 4; i++)
+ room->selectFrame(vaseSegment[i], 1000, _vasePos[i]);
+
+ }
+
+ void showSandals() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->playAnim("r1220ba0", 500, PlayAnimParams::loop().partial(9, 17));
+ room->enableHotzone("sandals");
+ }
+
+ void showStrongBox() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ // User already clicked, no need to play that anim
+ persistent->_cretePlayedZeusCheckOutThatBox = true;
+ room->pushHotZones("Box.Hot");
+ room->playSound("g0082ea0");
+ redrawStrongBox();
+ _strongBoxPopup = true;
+ switch(persistent->_creteStrongBoxState) {
+ case Persistent::BOX_CLOSED:
+ room->selectFrame("r2230bb0", 1000, 1, kOffsetRightRoom);
+ break;
+ case Persistent::BOX_OPEN:
+ room->selectFrame("r2230bb0", 1000, 3, kOffsetRightRoom);
+ break;
+ case Persistent::BOX_OPEN_POTION:
+ room->selectFrame("r2230bb0", 1000, 3, kOffsetRightRoom);
+ room->selectFrame("r2230bf0", 300, -2, kOffsetRightRoom);
+ break;
+ case Persistent::BOX_OPEN_NO_POTION:
+ room->selectFrame("r2230bb0", 1000, 3, kOffsetRightRoom);
+ room->selectFrame("r2230bf0", 300, -1, kOffsetRightRoom);
+ break;
+ }
+ }
+
+ void hideStrongBox() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->stopAnim("r2230bb0");
+ room->stopAnim("r2230bf0");
+ for (int i = 0; i < 12; i++)
+ room->stopAnim(LayerId("r2010om0", i, "pos"));
+ room->stopAnim(LayerId("r2010om1", 0, "center"));
+ for (int i = 0; i < 4; i++) {
+ room->stopAnim(LayerId("r2010om1", i, "side"));
+ }
+ _strongBoxPopup = false;
+ }
+
+ void redrawStrongBox() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+
+ room->setHotzoneEnabled("Potion", persistent->_creteStrongBoxState == Persistent::BOX_OPEN_POTION);
+ room->setHotzoneEnabled("Latch", persistent->_creteStrongBoxState == Persistent::BOX_CLOSED);
+ room->setHotzoneEnabled("ButtonN", persistent->_creteStrongBoxState == Persistent::BOX_OPEN);
+ room->setHotzoneEnabled("ButtonS", persistent->_creteStrongBoxState == Persistent::BOX_OPEN);
+ room->setHotzoneEnabled("ButtonE", persistent->_creteStrongBoxState == Persistent::BOX_OPEN);
+ room->setHotzoneEnabled("ButtonW", persistent->_creteStrongBoxState == Persistent::BOX_OPEN);
+ room->setHotzoneEnabled("ButtonC", persistent->_creteStrongBoxState == Persistent::BOX_OPEN);
+
+ switch(persistent->_creteStrongBoxState) {
+ case Persistent::BOX_CLOSED:
+ break;
+ case Persistent::BOX_OPEN:
+ room->selectFrame(LayerId("r2010om1", 0, "center"), 400, 0, Common::Point(1060, 308));
+ for (int i = 0; i < 4; i++) {
+ room->selectFrame(LayerId("r2010om1", i, "side"), 400, 1,
+ strongBoxSideDotOffsets[i]);
+ }
+ {
+ bool wasMoving = strongBoxIsBusy();
+ for (int i = 0; i < 12; i++) {
+ _strongBoxTiles[i].show();
+ }
+ if (wasMoving && !strongBoxIsBusy()) {
+ strongBoxCheckSolution();
+ }
+ }
+ break;
+ case Persistent::BOX_OPEN_POTION:
+ case Persistent::BOX_OPEN_NO_POTION:
+ room->selectFrame(LayerId("r2010om1", 0, "center"), 400, 0, Common::Point(1060, 308));
+ for (int i = 0; i < 4; i++) {
+ room->selectFrame(LayerId("r2010om1", i, "side"), 400, 1,
+ strongBoxSideDotOffsets[i]);
+ }
+ for (int i = 0; i < 12; i++) {
+ _strongBoxTiles[i].show();
+ }
+ break;
+ }
+ }
+
+ bool strongBoxIsBusy() {
+ for (int i = 0; i < 12; i++) {
+ if (_strongBoxTiles[i].isMoving()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void strongBoxCheckSolution() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ bool zOk = false, eOk = false, uOk = false, sOk = false;
+ for (int i = 0; i < 12; i++) {
+ if (_strongBoxTiles[i].getPosition() == 0
+ && _strongBoxTiles[i].getLetter() == StrongBoxTile::kLetterZ
+ && (_strongBoxTiles[i].getOrientation() == 0
+ || _strongBoxTiles[i].getOrientation() == 180))
+ zOk = true;
+ if (_strongBoxTiles[i].getPosition() == 1
+ && _strongBoxTiles[i].getLetter() == StrongBoxTile::kLetterE
+ && _strongBoxTiles[i].getOrientation() == 0)
+ eOk = true;
+ if (_strongBoxTiles[i].getPosition() == 2
+ && _strongBoxTiles[i].getLetter() == StrongBoxTile::kLetterU
+ && _strongBoxTiles[i].getOrientation() == 0)
+ uOk = true;
+ if (_strongBoxTiles[i].getPosition() == 3
+ && _strongBoxTiles[i].getLetter() == StrongBoxTile::kLetterS
+ && (_strongBoxTiles[i].getOrientation() == 0
+ || _strongBoxTiles[i].getOrientation() == 180))
+ sOk = true;
+ }
+
+ if (zOk && eOk && uOk && sOk) {
+ persistent->_creteStrongBoxState = Persistent::BOX_OPEN_POTION;
+ room->disableMouse();
+ room->enableHotzone("Potion");
+ room->disableHotzone("ButtonS");
+ room->disableHotzone("ButtonN");
+ room->disableHotzone("ButtonE");
+ room->disableHotzone("ButtonW");
+ room->disableHotzone("ButtonC");
+ room->playAnimWithSound("r2230bf0", "r2230ea0", 300, PlayAnimParams::keepLastFrame().partial(0, -2),
+ 12414, kOffsetRightRoom);
+ }
+ }
+
+ void strongBoxMoveTiles(int p1, int p2, int p3, int p4) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->playSound("r2230ee0");
+ for (int i = 0; i < 12; i++) {
+ int pos = _strongBoxTiles[i].getPosition();
+ if (pos == p1)
+ _strongBoxTiles[i].moveTo(p2);
+ if (pos == p2)
+ _strongBoxTiles[i].moveTo(p3);
+ if (pos == p3)
+ _strongBoxTiles[i].moveTo(p4);
+ if (pos == p4)
+ _strongBoxTiles[i].moveTo(p1);
+ }
+ }
+
+ bool _fadingHades;
+ int _fadingHadesStartTime;
+ int _tavernCounter;
+ int _oneManBandCounter;
+ int _atlantisBoatPosition[3];
+ bool _merchantIsBusy;
+ int _vasePos[4];
+ bool _vaseBusy[4];
+ bool _strongBoxPopup;
+ StrongBoxTile _strongBoxTiles[12];
+ Common::Array<Common::String> _tavernTalks;
+ AmbientAnim _alchemistAmbient;
+};
+
+Common::SharedPtr<Hadesch::Handler> makeCreteHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new CreteHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/daedalus.cpp b/engines/hadesch/rooms/daedalus.cpp
new file mode 100644
index 0000000000..9f29a8660c
--- /dev/null
+++ b/engines/hadesch/rooms/daedalus.cpp
@@ -0,0 +1,361 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/ambient.h"
+
+namespace Hadesch {
+
+static const char *kDaedalusStillFrame = "daedalus still frame";
+static const char *kDaedalusAmbient = "daedalus ambient";
+static const char *kModelPiece = "model piece";
+static const char *kLabyrinthWorkers = "labyrinth workers";
+
+enum {
+ kDaedalusTick = 13901,
+
+ // TODO: Remove this once we have a possibility
+ // to pass an argument to event handler.
+ // Originally: "daedalus intro 1" -> 13002[1] -> "phil intro 1"
+ // -> 13003[1] -> "daedalus intro 2" -> 13002[2]
+ // -> "phil intro 2" -> 13003[2] -> "daedalus intro 3"
+ kIntroStep1 = 1013001,
+ kIntroStep2 = 1013002,
+ kIntroStep3 = 1013003,
+ kIntroStep4 = 1013004,
+ kIntroStep5 = 1013005
+};
+
+enum {
+ kBackgroundZ = 10000,
+ kLabyrinthWorkersZ = 900,
+ kDaedalusZ = 500,
+ kModelPieceZ = 500,
+ kPhilZ = 0
+};
+
+class DaedalusHandler : public Handler {
+public:
+ DaedalusHandler() {
+ _daedalusIsBusy = false;
+ }
+
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+
+ if (name == "minos palace") {
+ g_vm->moveToRoom(kMinosPalaceRoom);
+ return;
+ }
+
+ if (name == "daedalus") {
+ playDaedalusVideo("daedalus no materials", 13005, Common::Point(76, 0));
+ return;
+ }
+
+ if (name == "wings") {
+ playDaedalusVideo("daedalus wings", 4009, Common::Point(10, 56));
+ return;
+ }
+
+ if (name == "labyrinth" && persistent->_quest != kCreteQuest) {
+ room->disableMouse();
+ room->playVideo("phil navigation help", 0, 13007, Common::Point(0, 216));
+ return;
+ }
+
+ if (name == "brick wall") {
+ daedalusWallMotion();
+ return;
+ }
+ }
+
+ bool handleClickWithItem(const Common::String &name, InventoryItem item) override {
+ Persistent *persistent = g_vm->getPersistent();
+
+ int labItem = -1;
+ debug("Item is %d", item);
+ switch (item) {
+ case kStone:
+ labItem = 0;
+ break;
+ case kBricks:
+ labItem = 1;
+ break;
+ case kWood:
+ labItem = 2;
+ break;
+ case kStraw:
+ labItem = 3;
+ break;
+ default:
+ labItem = -1;
+ break;
+ }
+
+ if ((name == "daedalus" || name == "chute") && labItem < 0) {
+ playDaedalusVideo("daedalus what to do with that", 13005, Common::Point(10, 40));
+ return true;
+ }
+
+ if (name == "daedalus" && labItem >= 0) {
+ playDaedalusVideo("daedalus put that in the chute", 4009, Common::Point(64, 48));
+ return true;
+ }
+
+ if (name == "chute" && labItem >= 0) {
+ bool hasAll = true;
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ g_vm->getHeroBelt()->removeFromInventory(item);
+ persistent->_daedalusLabItem[labItem] = true;
+ for (int i = 0; i < 4; i++) {
+ if (!persistent->_daedalusLabItem[i]) {
+ hasAll = false;
+ break;
+ }
+ }
+
+ renderCheckMarks();
+
+ room->playAnimWithSound("dust cloud", "dust cloud sound", 850, PlayAnimParams::disappear());
+
+ if (hasAll)
+ playDaedalusVideo("daedalus exclaims", 13008, Common::Point(0, 2));
+ else {
+ // Original goes to event 4009
+ if (g_vm->getRnd().getRandomNumberRng(0, 1))
+ playDaedalusVideo("daedalus congrats 1", 4009, Common::Point(70, 30));
+ else
+ playDaedalusVideo("daedalus congrats 2", 4009, Common::Point(68, 32));
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ switch(eventId) {
+ case kIntroStep1:
+ room->playVideo("phil intro 1", kPhilZ, kIntroStep2,
+ Common::Point(0, 216));
+ room->selectFrame(kDaedalusAmbient, kDaedalusZ, 0);
+ break;
+ case kIntroStep2:
+ playDaedalusVideo("daedalus intro 2", kIntroStep3,
+ Common::Point(76, 55));
+ break;
+ case kIntroStep3:
+ room->playVideo("phil intro 2", kPhilZ, kIntroStep4,
+ Common::Point(0, 216));
+ room->selectFrame(kDaedalusStillFrame, kDaedalusZ, 0);
+ break;
+
+ case kIntroStep4:
+ playDaedalusVideo("daedalus intro 3", kIntroStep5,
+ Common::Point(76, 60));
+ break;
+ case 13004:
+ room->stopAnim("daedalus note");
+ room->stopAnim("daedalus note text male");
+ room->stopAnim("daedalus note text female");
+ break;
+ case 13005:
+ g_vm->addTimer(13006, 5000, 1);
+ // Fallthrough
+ case kIntroStep5:
+ case 4009:
+ daedalusBecomesIdle();
+ room->enableMouse();
+ break;
+ case 13006:
+ if (!room->isMouseEnabled()) {
+ g_vm->addTimer(13006, 5000, 1);
+ break;
+ }
+ room->disableMouse();
+ room->playVideo("phil coerces", 0, 13007, Common::Point(0, 216));
+ break;
+ case 13007:
+ room->enableMouse();
+ break;
+ case 13008:
+ room->enableMouse();
+ room->selectFrame("daedalus exclaims still", kDaedalusZ,0);
+ // TODO: for now we skip arcade sequence until it's implemented
+ // g_vm->moveToRoom(kMinotaurPuzzle);
+ g_vm->moveToRoom(kQuiz);
+ break;
+ case 13011: {
+ // TODO: use right algorithm
+ int roarNum = g_vm->getRnd().getRandomNumberRng(1, 5);
+ room->playSound(Common::String::format("ambient minotaur roar %d", roarNum), 13012);
+ break;
+ }
+ case 13012:
+ g_vm->addTimer(13011, g_vm->getRnd().getRandomNumberRng(5000, 10000));
+ break;
+ case kDaedalusTick:
+ if (_daedalusIsBusy)
+ break;
+ _daedalusIsBusy = true;
+ switch (g_vm->getRnd().getRandomNumberRng(1, 6)) {
+ case 1:
+ daedalusWallMotion();
+ break;
+ case 2:
+ case 3:
+ case 4:
+ room->playAnim(kDaedalusAmbient, kDaedalusZ, PlayAnimParams::keepLastFrame().partial(0, 21), 13904);
+ break;
+ case 5:
+ case 6:
+ room->playAnim(kDaedalusAmbient, kDaedalusZ, PlayAnimParams::keepLastFrame().partial(23, 30), 13903);
+ break;
+ }
+ break;
+ case 13902:
+ room->playAnim(kDaedalusAmbient, kDaedalusZ, PlayAnimParams::keepLastFrame().partial(35, -1), 13904);
+ room->playAnimWithSound(kLabyrinthWorkers, "labyrinth workers sound", kLabyrinthWorkersZ,
+ PlayAnimParams::keepLastFrame());
+ break;
+ case 13903:
+ room->playAnim(kDaedalusAmbient, kDaedalusZ, PlayAnimParams::keepLastFrame().partial(57, -1), 13904);
+ break;
+ case 13904:
+ _daedalusIsBusy = false;
+ break;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ Quest quest = persistent->_quest;
+ room->loadHotZones("Daedalus.HOT", false);
+ room->addStaticLayer("background", kBackgroundZ);
+ room->addStaticLayer("chute label", 850);
+ g_vm->getHeroBelt()->setColour(HeroBelt::kWarm);
+
+ if (quest == kCreteQuest) {
+ room->addStaticLayer("wings", 900);
+ room->addStaticLayer("check list", 800);
+ room->addStaticLayer("check list text", 799);
+ room->enableHotzone("brick wall");
+ room->enableHotzone("chute");
+ room->enableHotzone("wings");
+ room->enableHotzone("daedalus");
+ room->selectFrame(kLabyrinthWorkers, kLabyrinthWorkersZ, 0);
+ room->selectFrame(kDaedalusAmbient, kDaedalusZ, 0);
+ g_vm->addTimer(13011, g_vm->getRnd().getRandomNumberRng(5000, 10000));
+ g_vm->addTimer(kDaedalusTick, g_vm->getRnd().getRandomNumberRng(5000, 10000), -1);
+ } else {
+ room->enableHotzone("labyrinth");
+ if (!persistent->_daedalusShowedNote) {
+ persistent->_daedalusShowedNote = true;
+ room->selectFrame("daedalus note", 800, 0);
+ room->selectFrame(persistent->_gender == kMale ? "daedalus note text male"
+ : "daedalus note text female", 799, 0);
+ room->playSound(persistent->_gender == kMale ? "daedalus note vo male"
+ : "daedalus note vo female", 13004);
+ }
+ }
+
+ renderCheckMarks();
+
+ room->enableHotzone("minos palace");
+
+ if (quest == kCreteQuest
+ && !persistent->isRoomVisited(kDaedalusRoom)) {
+ persistent->_creteIntroAtlantisBoat = true;
+ persistent->_creteShowAtlantisBoat = true;
+ persistent->_creteIntroAtlantisWood = true;
+ persistent->_troyPlayAttack = true;
+ playDaedalusVideo("daedalus intro 1", kIntroStep1,
+ Common::Point(50, 35));
+ room->playSoundLoop("theme music 1");
+ } else if (quest == kCreteQuest &&
+ (persistent->_daedalusLabItem[0] || persistent->isInInventory(kStone)) &&
+ (persistent->_daedalusLabItem[1] || persistent->isInInventory(kBricks)) &&
+ (persistent->_daedalusLabItem[2] || persistent->isInInventory(kWood)) &&
+ (persistent->_daedalusLabItem[3] || persistent->isInInventory(kStraw))) {
+ room->playSoundLoop("theme music 2");
+ } else {
+ room->playSoundLoop("R4010eA0");
+ }
+ AmbientAnim("mouse", "mouse sound", 900, 5000, 10000, AmbientAnim::KEEP_LOOP, Common::Point(0, 0),
+ AmbientAnim::PAN_ANY).start();
+ }
+private:
+ void playDaedalusVideo(const Common::String &name, int callback, const Common::Point &offset) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ _daedalusIsBusy = true;
+
+ room->stopAnim(kDaedalusStillFrame);
+ room->stopAnim(kDaedalusAmbient);
+ room->selectFrame(kModelPiece, kModelPieceZ, 0);
+ room->disableMouse();
+ room->playVideo(name, kDaedalusZ, callback, offset);
+ }
+
+ void daedalusBecomesIdle() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ room->selectFrame(kDaedalusAmbient, kDaedalusZ, 0);
+ room->stopAnim(kModelPiece);
+ _daedalusIsBusy = false;
+ }
+
+ void renderCheckMarks() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ for (int i = 0; i < 4; i++) {
+ Common::String layer = Common::String::format("check mark %d", i + 1);
+ if (persistent->_daedalusLabItem[i]) {
+ room->selectFrame(layer, 798, 0);
+ } else {
+ room->stopAnim(layer);
+ }
+ }
+ }
+
+ void daedalusWallMotion() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->playAnim(kDaedalusAmbient, kDaedalusZ, PlayAnimParams::keepLastFrame().partial(0, 34), 13902);
+ room->playSound("daedalus ambient sound");
+ _daedalusIsBusy = true;
+ }
+
+ bool _daedalusIsBusy;
+};
+
+Common::SharedPtr<Hadesch::Handler> makeDaedalusHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new DaedalusHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/ferry.cpp b/engines/hadesch/rooms/ferry.cpp
new file mode 100644
index 0000000000..fcc42f5f1e
--- /dev/null
+++ b/engines/hadesch/rooms/ferry.cpp
@@ -0,0 +1,1074 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+
+namespace Hadesch {
+
+enum {
+ kBackgroundZ = 10000,
+ kCharonZ = 701
+};
+
+static const int kNumSeats = 10;
+
+enum {
+ kHuman = 1 << 0,
+ kMonster = 1 << 1,
+ kAnimal = 1 << 2,
+ kSmoking = 1 << 6,
+ kFlat = 1 << 7,
+ kFrozen = 1 << 8,
+ kPierced = 1 << 9,
+ kWet = 1 << 10,
+ kDismembered = 1 << 11,
+ kWeaponInjury = 1 << 12,
+ kTridentInjured = 1 << 13,
+ kHeadInjury = 1 << 14,
+ kDrownedGuy = 1 << 15,
+ kHorned = 1 << 16,
+ kCrushed = 1 << 17,
+ kSnakeKilled = 1 << 18,
+ kCold = 1 << 19,
+ kTwoHeaded = 1 << 20,
+ kHot = 1 << 21,
+ kHeadless = 1 << 22,
+ kChokedDog = 1 << 23,
+ kWithHoles = 1 << 24,
+ kCat = 1 << 25,
+ kDog = 1 << 26
+};
+
+static const struct {
+ const char *image;
+ const char *nameImage;
+ const char *name;
+ int waityoffset;
+ int priorityleft;
+ int priorityright;
+ int attributes;
+ Common::Point waitBubble;
+ Common::Point starboardBubble;
+ Common::Point portBubble;
+ int portFrame;
+ int starboardFrame;
+ const char *animSound;
+ int animPlayCount;
+} shadows[] = {
+ {
+ "xxxxxxxx", "" , "Chariot Wheel Guy" , 0, 0, 0 , kHuman | kFlat | kCrushed,
+ Common::Point(0, 0), Common::Point(0, 0), Common::Point(0, 0), 2, 3,
+ nullptr, 0
+ },
+ {"V9070bD0", "V9520tD0", "Chariot Wheel Dog" , 40, 20, 5 , kAnimal | kFlat | kCrushed | kDog,
+ Common::Point(113, 177), Common::Point(66, 134), Common::Point(106, 176), 16, 32,
+ nullptr, 0
+ },
+ {"V9070bE0", "V9520tE0", "Drowned Guy" , 0, 10, 15 , kHuman | kWet | kDrownedGuy,
+ Common::Point(124, 84), Common::Point(78, 67), Common::Point(104, 80), 19, 35,
+ "V9070eE0", 1
+ },
+ {"V9070bI0", "V9520tI0", "Holey Guy" , 0, 15, 20 , kHuman | kPierced | kWeaponInjury | kWithHoles,
+ Common::Point(84, 107), Common::Point(68, 66), Common::Point(92, 100), 6, 13,
+ nullptr, 0
+ },
+ {"V9070bP0", "V9520tO0", "Cyclops" , 14, 50, 50 , kMonster | kPierced | kWeaponInjury | kHeadInjury | kCrushed | kTwoHeaded,
+ Common::Point(77, 64), Common::Point(73, 72), Common::Point(63, 68), 14, 33,
+ nullptr, 0
+ },
+ {"V9070bM0", "V9520tM0", "Toasted Guy" , -13, 40, 50 , kHuman | kSmoking | kHot,
+ Common::Point(115, 89), Common::Point(78, 71), Common::Point(104, 73), 14, 28,
+ nullptr, 0
+ },
+ {"V9070bJ0", "V9520tJ0", "Minotaur" , 0, 25, 20 , kMonster | kHorned,
+ Common::Point(111, 100), Common::Point(63, 79), Common::Point(106, 111), 12, 45,
+ nullptr, 0
+ },
+ {"V9070bA0", "V9520tA0", "Ball Chain Monster", 0, 20, 20 , kMonster | kWeaponInjury | kHeadInjury | kHeadless,
+ Common::Point(79, 92), Common::Point(85, 91), Common::Point(83, 102), 13, 26,
+ nullptr, 0
+ },
+ {"V9070bL0", "V9520tL0", "Snake Man" , 0, 25, 40 , kHuman | kAnimal | kCrushed | kSnakeKilled,
+ Common::Point(82, 65), Common::Point(93, 62), Common::Point(74, 45), 4, 8,
+ "V9070eL0", 3
+ },
+ {"xxxxxxxx", "" , "Arrow Guy" , 0, 0, 0 , kHuman | kPierced | kWeaponInjury,
+ Common::Point(0, 0), Common::Point(0, 0), Common::Point(0, 0), 0, 0,
+ nullptr, 0 },
+ // 10
+ {"V9070bC0", "V9520tC0", "Choked Dog" , 46, 30, 5 , kAnimal | kDog | kChokedDog,
+ Common::Point(97, 91), Common::Point(83, 85), Common::Point(81, 85), 11, 22,
+ "V9070eC0", 2
+ },
+ {"V9070bK0", "V9520tK0", "Mounted Aries" , 23, 35, 40 , kAnimal | kHeadInjury | kHorned,
+ Common::Point(121, 62), Common::Point(75, 54), Common::Point(113, 52), 6, 12,
+ "V9070eK0", 1
+ },
+ {"V9070bF0", "V9520tF0", "Flat Cat" , 43, 40, 5 , kAnimal | kFlat | kCrushed | kCat,
+ Common::Point(75, 142), Common::Point(72, 142), Common::Point(72, 142), 5, 19,
+ nullptr, 0
+ },
+ {"V9070bH0", "V9520tH0", "Headless Guy" , -14, 5, 15 , kHuman | kDismembered | kWeaponInjury | kHeadInjury | kHeadless,
+ Common::Point(110, 119), Common::Point(50, 98), Common::Point(115, 117), 26, 52,
+ nullptr, 0
+ },
+ {"V9070bG0", "V9520tG0", "Frozen Guy" , 0, 15, 40 , kHuman | kCold | kWet | kFrozen,
+ Common::Point(71, 67), Common::Point(91, 80), Common::Point(74, 58), 9, 18,
+ "V9070eG0", 2
+ },
+ // 15
+ {"xxxxxxxx", "" , "Caesar" , 0, 0, 0 , kHuman | kPierced | kWeaponInjury,
+ Common::Point(0, 0), Common::Point(0, 0), Common::Point(0, 0), 0, 0,
+ nullptr, 0 },
+ {"V9070bN0", "V9520tN0", "Trident Guy" , 9, 5, 10 , kHuman | kPierced | kWeaponInjury | kHeadInjury | kTridentInjured,
+ Common::Point(89, 91), Common::Point(81, 88), Common::Point(94, 88), 5, 10,
+ nullptr, 0
+ },
+ {"xxxxxxxx", "" , "BeeSting Guy" , 0, 0, 0 , kHuman | kPierced,
+ Common::Point(0, 0), Common::Point(0, 0), Common::Point(0, 0), 0, 0,
+ nullptr, 0 },
+ {"xxxxxxxx", "" , "Half Man" , 0, 0, 0 , kHuman | kDismembered,
+ Common::Point(0, 0), Common::Point(0, 0), Common::Point(0, 0), 0, 0,
+ nullptr, 0 },
+ {"V9070bQ0", "V9520tP0", "Toasted Cat" , 41, 45, 5 , kAnimal | kSmoking | kHot | kCat,
+ Common::Point(111, 158), Common::Point(59, 135), Common::Point(105, 161), 6, 12,
+ "V9070eQ0", 3
+ },
+ // 20
+ {"V9070bR0", "V9520tQ0", "Pillar Guy" , 0, 15, 10 , kHuman | kFlat | kHeadInjury | kCrushed,
+ Common::Point(86, 188), Common::Point(78, 186), Common::Point(92, 186), 15, 30,
+ "V9070eR0", 1
+ },
+ {"xxxxxxxx", "" , "Ax-Head Guy" , 0, 0, 0 , kHuman | kWeaponInjury | kHeadInjury,
+ Common::Point(0, 0), Common::Point(0, 0), Common::Point(0, 0), 0, 0,
+ nullptr, 0 },
+ {"V9070bB0", "V9520tB0", "Boiled Guy" , 45, 5, 10 , kHuman | kHot | kWet,
+ Common::Point(80, 62), Common::Point(81, 68), Common::Point(79, 60), 13, 42,
+ nullptr, 0
+ }
+};
+
+static const struct {
+ const char *image;
+ const char *sound;
+} thoughts[] = {
+ { "V9140tA0", "V9140nA0" }, // 0
+ { "V9140tB0", "V9140nB0" },
+ { "V9140tC0", "V9140nC0" },
+ { "V9140tD0", "V9140nD0" },
+ { "V9140tE0", "V9140nE0" },
+ { "V9140tF0", "V9140nF0" }, // 5
+ { "V9140tG0", "V9140nG0" },
+ { "V9140tH0", "V9140nH0" },
+ { "V9140tI0", "V9140nI0" },
+ { "V9140tJ0", "V9140nJ0" },
+ { "V9140tK0", "V9140nK0" }, // 10
+ { "V9140tL0", "V9140nL0" },
+ { "V9140tM0", "V9140nM0" },
+ { "V9140tN0", "V9140nN0" },
+ { "V9140tO0", "V9140nO0" },
+ { "V9140tP0", "V9140nP0" }, // 15
+ { "V9140tQ0", "V9140nQ0" },
+ { "V9140tR0", "V9140nR0" },
+ { "V9140tS0", "V9140nS0" },
+ { "V9140tT0", "V9140nT0" },
+ { "V9140tU0", "V9140nU0" }, // 20
+ { "V9140tV0", "V9140nV0" },
+ { "V9140tW0", "V9140nW0" },
+ { "V9160tA0", "V9160nA0" },
+ { "V9160tB0", "V9160nB0" },
+ { "V9160tC0", "V9160nC0" }, // 25
+ { "V9160tD0", "V9160nD0" },
+ { "V9160tE0", "V9160nE0" },
+ { "V9160tF0", "V9160nF0" },
+ { "V9160tG0", "V9160nG0" },
+ { "V9160tH0", "V9160nH0" }, // 30
+ { "V9160tI0", "V9160nI0" },
+ { "V9160tJ0", "V9160nJ0" },
+ { "V9160tK0", "V9160nK0" },
+ { "V9160tL0", "V9160nL0" },
+ { "V9170tA0", "V9170nA0" }, // 35
+ { "V9170tB0", "V9170nB0" },
+ { "V9170tC0", "V9170nC0" },
+ { "V9170tD0", "V9170nD0" },
+ { "V9170tE0", "V9170nE0" },
+ { "V9170tF0", "V9170nF0" }, // 40
+ { "V9170tG0", "V9170nG0" },
+ { "V9170tH0", "V9170nH0" },
+ { "V9170tI0", "V9170nI0" },
+ { "V9170tJ0", "V9170nJ0" },
+ { "V9170tK0", "V9170nK0" }, // 45
+ { "V9170tL0", "V9170nL0" },
+ { "V9170tM0", "V9170nM0" },
+ { "V9170tN0", "V9170nN0" },
+ { "V9170tO0", "V9170nO0" },
+ { "V9170tP0", "V9170nP0" }, // 50
+ { "V9170tQ0", "V9170nQ0" },
+ { "V9170tR0", "V9170nR0" },
+ { "V9170tS0", "V9170nS0" },
+ { "V9170tT0", "V9170nT0" },
+ { "V9170tU0", "V9170nU0" }, // 55
+ { "V9170tV0", "V9170nV0" },
+ { "V9170tY0", "V9170nY0" },
+ { "V9200tA0", "V9200nA0" },
+ { "V9200tB0", "V9200nB0" },
+ { "V9200tC0", "V9200nC0" }, // 60
+ { "V9200tD0", "V9200nD0" },
+ { "V9200tE0", "V9200nE0" },
+ { "V9200tF0", "V9200nF0" },
+ { "V9200tG0", "V9200nG0" },
+ { "V9200tH0", "V9200nH0" }, // 65
+ { "V9200tI0", "V9200nI0" },
+ { "V9200tJ0", "V9200nJ0" },
+ { "V9200tK0", "V9200nK0" },
+ { "V9200tL0", "V9200nL0" },
+ { "V9200tM0", "V9200nM0" }, // 70
+ { "V9200tN0", "V9200nN0" },
+ { "V9200tO0", "V9200nO0" },
+ { "V9240tA0", "V9240nA0" },
+ { "V9210tA0", "V9210nA0" },
+ { "V9210tB0", "V9210nB0" }, // 75
+ { "V9210tC0", "V9210nC0" },
+ { "V9210tD0", "V9210nD0" },
+ { "V9220tA0", "V9220nA0" },
+ { "V9220tB0", "V9220nB0" },
+ { "V9220tC0", "V9220nC0" }, // 80
+ { "V9220tD0", "V9220nD0" },
+ { "V9245tA0", "V9300nE0" }
+};
+
+static const struct {
+ const char *image;
+ const char *sound;
+} charonRules[] = {
+ { "V9250tA0", "V9250aA0" }, // 0
+ { "V9250tB0", "V9250aB0" },
+ { "V9250tC0", "V9250aC0" },
+ { "V9250tD0", "V9250aD0" },
+ { "V9260tA0", "V9260nA0" },
+ { "V9260tB0", "V9260nB0" }, // 5
+ { "V9260tC0", "V9260nC0" },
+ { "V9260tD0", "V9260nD0" },
+ { "V9260tE0", "V9260nE0" },
+ { "V9260tF0", "V9260nF0" },
+ { "V9260tG0", "V9260nG0" }, // 10
+ { "V9260tJ0", "V9260nJ0" },
+ { "V9270tA0", "XXXXXXXX" }
+};
+
+static const char *charonFinishSounds[] = {
+ "V9300nA0",
+ "V9300nH0",
+ "V9300nI0"
+};
+
+static const char *charonAnims[] = {
+ "V9140BA0",
+ "V9140BB0",
+ "V9140BC0"
+};
+
+static struct {
+ const char *name;
+ Common::Point offset;
+} charonIdleVideos[] = {
+ {"V9140BD0", Common::Point(418, 40)},
+ {"V9140BE0", Common::Point(370, 64)}
+};
+
+struct Shade {
+ int shadowId;
+ int thoughtId;
+ int currentPos;
+ int waitingPos;
+ bool positionIsFixed;
+ int rowidx;
+ int tabNA;
+ int tabN;
+ int tabO;
+ int tabA;
+ int tabNN;
+};
+
+struct ImagePos {
+ int x, y, z;
+
+ Common::Point getPoint() const {
+ return Common::Point(x, y);
+ }
+};
+
+static const ImagePos boatPosition[] = {
+ {150, 380, 555},
+ {210, 352, 556},
+ {266, 328, 557},
+ {321, 307, 558},
+ {381, 293, 559},
+ {240, 452, 550},
+ {310, 440, 551},
+ {373, 425, 552},
+ {434, 405, 553},
+ {491, 383, 554}
+};
+
+static const ImagePos waitPosition[] = {
+ {41, 119, 1058},
+ {114, 94, 1059},
+ {187, 69, 1060},
+ {41, 226, 1051},
+ {114, 201, 1052},
+ {187, 176, 1053},
+ {260, 151, 1054},
+ {333, 126, 1055},
+ {406, 101, 1056},
+ {479, 76, 1057}
+};
+
+enum {
+ kCharonEndTalk = 24812,
+ // 24802 is split into 2 to avoid having an argument
+ k24802_arg0 = 1024801,
+ k24802_arg1 = 1024802,
+ k24017_arg0 = 1024001,
+ k24017_arg9 = 1024010,
+ k24018_arg0 = 1024011,
+ k24018_arg9 = 1024020,
+ k24801_arg1 = 1024021,
+ k24801_arg2 = 1024022,
+ k24801_arg3 = 1024023,
+ k24801_arg4 = 1024024,
+ k24801_arg5 = 1024025,
+ k24801_arg6 = 1024026
+};
+
+class FerryHandler : public Handler {
+public:
+ FerryHandler() {
+ _dragged = -1;
+ _clickTimer = -1;
+ _isPlayingYuck = false;
+ _lastCharonAnim = -1;
+ _charonIsBusy = false;
+ memset(_isInAnim, 0, sizeof(_isInAnim));
+ }
+
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ if (name.matchString("s##")) {
+ g_vm->addTimer(24012, 350);
+ _clickTimer = typeToIdx(name.substr(1).asUint64());
+ return;
+ }
+
+ if (name.matchString("f##")) {
+ int fPos = (name[1] - '0') * 5 + (name[2] - '0');
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ if (_shades[i].currentPos == fPos) {
+ if (_shades[i].positionIsFixed)
+ showThoughtByShadowId(i);
+ else {
+ _clickTimer = i;
+ g_vm->addTimer(24012, 350);
+ }
+ break;
+ }
+ }
+ return;
+ }
+
+ if (name == "Sign" && _charonTID != 12) {
+ playCharonSound(charonRules[_charonTID].sound);
+ return;
+ }
+
+ /*
+ TODO:
+ MNSH: Charon
+ MNSH: sp##
+ MNSH: ss##
+*/
+ }
+
+ void handleUnclick(const Common::String &name, const Common::Point pnt) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ if (_clickTimer >= 0) {
+ g_vm->cancelTimer(24012);
+ showThoughtByShadowId(_clickTimer);
+ _clickTimer = -1;
+ } else if (_dragged >= 0) {
+ if (name.matchString("f##")) {
+ moveToFerry(_dragged, (name[1] - '0') * 5
+ + (name[2] - '0'));
+ } else {
+ backToWaiting(_dragged);
+ }
+ room->stopAnim("v9010bc0");
+ _dragged = -1;
+ hideThought();
+ levelRender();
+ }
+ }
+
+ void handleMouseOver(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ if (name.matchString("f##") && _dragged != -1) {
+ room->selectFrame("v9010bc0", 800, name[1] == '1' ? (9 - (name[2] - '0')) : (name[2] - '0'));
+ }
+ }
+
+ void handleMouseOut(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ if (name.matchString("f##") && _dragged != -1) {
+ room->stopAnim("v9010bc0");
+ }
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+
+ switch (eventId) {
+ case 24006:
+ if (_count24006++ >= 3) {
+ hideThought();
+ break;
+ }
+ g_vm->addTimer(24006, 1200);
+ // TODO: don't issue duplicates
+ showThoughtByShadowId(g_vm->getRnd().getRandomNumberRng(0, 9), true);
+ break;
+ case 24010:
+ _isPlayingYuck = false;
+ break;
+ case 24012:
+ _dragged = _clickTimer;
+ _clickTimer = -1;
+ levelRender();
+ break;
+ case k24017_arg0:
+ case k24017_arg0 + 1:
+ case k24017_arg0 + 2:
+ case k24017_arg0 + 3:
+ case k24017_arg0 + 4:
+ case k24017_arg0 + 5:
+ case k24017_arg0 + 6:
+ case k24017_arg0 + 7:
+ case k24017_arg0 + 8:
+ case k24017_arg9:
+ {
+ int shade = eventId - k24017_arg0;
+ g_vm->addTimer(k24017_arg0 + shade, g_vm->getRnd().getRandomNumberRng(10200, 21800));
+ if (shade == _dragged)
+ return;
+ if (_shades[shade].currentPos >= 5) {
+ idleAnimShade(shade, boatPosition[_shades[shade].currentPos].z,
+ shadows[_shades[shade].shadowId].portFrame,
+ shadows[_shades[shade].shadowId].starboardFrame - 1);
+ return;
+ }
+
+ if (_shades[shade].currentPos >= 0) {
+ idleAnimShade(shade, boatPosition[_shades[shade].currentPos].z,
+ shadows[_shades[shade].shadowId].starboardFrame, -1);
+ return;
+ }
+
+ if (_shades[shade].waitingPos >= 0) {
+ idleAnimShade(shade, waitPosition[_shades[shade].waitingPos].z,
+ 0, shadows[_shades[shade].shadowId].portFrame - 1);
+ return;
+ }
+
+ break;
+ }
+ case k24018_arg0:
+ case k24018_arg0 + 1:
+ case k24018_arg0 + 2:
+ case k24018_arg0 + 3:
+ case k24018_arg0 + 4:
+ case k24018_arg0 + 5:
+ case k24018_arg0 + 6:
+ case k24018_arg0 + 7:
+ case k24018_arg0 + 8:
+ case k24018_arg9: {
+ uint shade = eventId - k24018_arg0;
+ _isInAnim[shade] = false;
+ levelRender();
+ break;
+ }
+ case 24019:
+ if (persistent->_quest == kRescuePhilQuest) {
+ g_vm->moveToRoom(kMonsterPuzzle);
+ } else {
+ _levelS++;
+ if (_levelS < 15) {
+ levelClear();
+ loadLevel();
+ levelRender();
+ showCharon();
+ break;
+ }
+
+ playCharonSound(Common::String::format("V9280w%c0", 'A' + _levelL), 24020);
+ _levelL++;
+ _levelS = 0;
+ }
+ break;
+ case 24020:
+ if (_levelL == 4)
+ g_vm->moveToRoom(kWallOfFameRoom);
+ break;
+ case k24802_arg0:
+ playCharonSound(charonFinishSounds[g_vm->getRnd().getRandomNumberRng(0, 2)], 24807);
+ break;
+ case k24802_arg1:
+ hideCharon();
+ room->playVideo("V9300bA0", kCharonZ, 24019,
+ Common::Point(406, 68));
+ break;
+ case 24807:
+ g_vm->addTimer(k24802_arg1, 500);
+ break;
+ case 24811:
+ showCharon();
+ break;
+ case kCharonEndTalk:
+ _charonIsBusy = false;
+ break;
+ case 24813: {
+ g_vm->addTimer(24813, g_vm->getRnd().getRandomNumberRng(12000, 18000));
+ if (_charonIsBusy)
+ break;
+ hideCharon();
+ int vid = g_vm->getRnd().getRandomNumberRng(0, ARRAYSIZE(charonIdleVideos) - 1);
+ room->playVideo(charonIdleVideos[vid].name,
+ kCharonZ, 24811, charonIdleVideos[vid].offset);
+ break;
+ }
+ case k24801_arg1:
+ playCharonSoundSMK("V9090NH0", k24801_arg2);
+ _count24006 = 0;
+ g_vm->addTimer(24006, 1200);
+ break;
+ case k24801_arg2:
+ playCharonSoundSMK("V9090ND0", k24801_arg3);
+ break;
+ case k24801_arg3:
+ playCharonSoundSMK("V9090NE0", k24801_arg4);
+ break;
+ case k24801_arg4:
+ playCharonSoundSMK("V9090NI0", k24801_arg5);
+ break;
+ case k24801_arg5:
+ playCharonSoundSMK("V9130NA0", k24801_arg6);
+ break;
+ case k24801_arg6:
+ room->enableMouse();
+ showCharon();
+ break;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ room->addStaticLayer("V9010pA0", kBackgroundZ);
+ room->selectFrame("V9010oA0", 540, 0);
+ room->selectFrame("V9010oB0", 700, 0);
+ // TODO: semi-transparency
+ room->playAnimLoop("V9060bA0", 540);
+ room->playAnimLoop("V9060bB0", 540);
+ room->playAnimLoop("V9060bC0", 540);
+ room->playVideo("V9010xA0", 0, 24002);
+ room->loadHotZones("ff.hot", false);
+
+ room->playSoundLoop("V9010eA0");
+ room->playSoundLoop("V9300eA0");
+
+ _levelL = 1;
+ if (persistent->_quest == kRescuePhilQuest) {
+ _levelS = 0;
+ } else {
+ _levelS = 1;
+ }
+
+ levelClear();
+ loadLevel();
+ levelRender();
+
+ showCharon();
+ g_vm->addTimer(24813, g_vm->getRnd().getRandomNumberRng(12000, 18000));
+ for (uint i = 0; i < kNumSeats; i++) {
+ g_vm->addTimer(k24017_arg0 + i, g_vm->getRnd().getRandomNumberRng(10200, 21800));
+ }
+
+ g_vm->getHeroBelt()->setColour(HeroBelt::kCold);
+
+ if (persistent->_quest == kRescuePhilQuest) {
+ room->disableMouse();
+ // originally 24800
+ playCharonSoundSMK("V9090NA0", k24801_arg1);
+ }
+ }
+
+ void frameCallback() override {
+ if (_dragged != -1) {
+ levelRender();
+ }
+ }
+
+ bool handleCheat(const Common::String &cheat) override {
+ if (cheat == "done") {
+ win();
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ void idleAnimShade(int shade, int z, int startFrame, int endFrame) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ LayerId sl(shadows[_shades[shade].shadowId].image, shade, "shadow");
+ _isInAnim[shade] = true;
+ // TODO: play sound multiple times if needed
+ PlayAnimParams params(PlayAnimParams::keepLastFrame().partial(startFrame, endFrame));
+ const char *snd = shadows[_shades[shade].shadowId].animSound;
+ if (snd && snd[0] != 0)
+ room->playAnimWithSound(sl, snd, z, params, EventHandlerWrapper(), getShadowPos(shade));
+ else
+ room->playAnim(sl, z, params, EventHandlerWrapper(), getShadowPos(shade));
+ }
+
+ void hideCharon() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ for (uint i = 0; i < ARRAYSIZE(charonAnims); i++)
+ room->stopAnim(charonAnims[i]);
+ for (uint i = 0; i < ARRAYSIZE(charonIdleVideos); i++)
+ room->stopAnim(charonIdleVideos[i].name);
+ _charonIsBusy = true;
+ }
+
+ void showCharon() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ hideCharon();
+ room->selectFrame(charonAnims[0], kCharonZ, 0);
+ _charonIsBusy = false;
+ }
+
+ void playCharonSound(const Common::String &sound,
+ EventHandlerWrapper ev = kCharonEndTalk, bool isSMK = false) {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ int selected = -2;
+ hideCharon();
+ do {
+ selected = g_vm->getRnd().getRandomNumberRng(0, ARRAYSIZE(charonAnims) - 1);
+ } while (selected == _lastCharonAnim);
+ _lastCharonAnim = selected;
+ room->playAnim(charonAnims[selected], kCharonZ, PlayAnimParams::loop());
+ if (isSMK)
+ room->playVideo(sound, 0, ev);
+ else
+ room->playSound(sound, ev);
+ }
+
+ void playCharonSoundSMK(const Common::String &sound,
+ EventHandlerWrapper ev = kCharonEndTalk) {
+ playCharonSound(sound, ev, true);
+ }
+
+ void hideThought() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->stopAnim("V9090oA0");
+ for (unsigned i = 0; i < ARRAYSIZE(shadows); i++)
+ room->stopAnim(shadows[i].nameImage);
+ for (unsigned i = 0; i < ARRAYSIZE(thoughts); i++)
+ room->stopAnim(thoughts[i].image);
+ room->stopAnim("V9150tA0");
+ }
+
+ void showThoughtByShadowId(int id, bool silent = false) {
+ Common::Point bubblePos;
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ int thoughtId = _shades[id].thoughtId;
+ if (_shades[id].positionIsFixed && thoughtId == 82)
+ thoughtId = -1;
+ hideThought();
+ bubblePos = getShadowPos(id) - Common::Point(71, 71);
+ if (_shades[id].currentPos >= 0 && _shades[id].currentPos <= 4) {
+ bubblePos += shadows[_shades[id].shadowId].starboardBubble;
+ } else if (_shades[id].currentPos >= 5) {
+ bubblePos += shadows[_shades[id].shadowId].portBubble;
+ } else
+ bubblePos += shadows[_shades[id].shadowId].waitBubble;
+ bubblePos.x = MAX<int>(bubblePos.x, -10);
+ bubblePos.y = MAX<int>(bubblePos.y, 0);
+ if (!silent) {
+ if (thoughtId >= 0)
+ playCharonSound(thoughts[thoughtId].sound);
+ else if (thoughtId == -1)
+ playCharonSound("V9150nA0");
+ }
+ room->selectFrame("V9090oA0", 112, 0, bubblePos);
+ room->selectFrame(shadows[_shades[id].shadowId].nameImage, 111, 0, bubblePos);
+ if (thoughtId >= 0)
+ room->selectFrame(thoughts[thoughtId].image, 111, 0, bubblePos);
+ else if (thoughtId == -1)
+ room->selectFrame("V9150tA0", 111, 0, bubblePos);
+ g_vm->addTimer(24014, 3000);
+ }
+
+ void moveToFerry(int shadeId, int ferryPos) {
+ _shades[shadeId].currentPos = ferryPos;
+
+ // First all other who are disgusted move out
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ if (_shades[i].positionIsFixed || _shades[i].currentPos < 0
+ || (int) i == shadeId)
+ continue;
+ if (!checkCombinationIsAllowed(i))
+ backToWaiting(i);
+ }
+
+ // Then the new shade
+ if (!checkCombinationIsAllowed(shadeId))
+ backToWaiting(shadeId);
+
+ // Then everybody who is fixed can kick out new shade
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ if (!_shades[i].positionIsFixed)
+ continue;
+ if (!checkCombinationIsAllowed(i))
+ backToWaiting(shadeId);
+ }
+
+ bool isWon = true;
+ for (unsigned i = 0; i < _shades.size(); i++)
+ if (_shades[i].currentPos < 0)
+ isWon = false;
+
+ if (isWon) {
+ win();
+ }
+ }
+
+ void win() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ for (unsigned i = 0; i < ARRAYSIZE(shadows); i++)
+ room->disableHotzone(Common::String::format("s%02d", i));
+ for (unsigned i = 0; i < 2; i++)
+ for (unsigned j = 0; j < 5; j++)
+ room->disableHotzone(Common::String::format("f%01d%01d", i, j));
+ g_vm->addTimer(k24802_arg0, 500);
+ }
+
+ void backToWaiting(int shadeId) {
+ // TODO: anim
+ _shades[shadeId].currentPos = -1;
+ }
+
+ bool isAttribMatch(Shade *shade, int attr) {
+ if (attr == 0)
+ return true;
+ if (!shade)
+ return false;
+ return (shadows[shade->shadowId].attributes & attr) == attr;
+ }
+
+ void yuck() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ if (_isPlayingYuck)
+ return;
+ _isPlayingYuck = true;
+ room->playSound(Common::String::format("V9290n%c0", g_vm->getRnd().getRandomNumberRng('A', 'E')),
+ 24010);
+ }
+
+ bool checkCombinationIsAllowed(int shadeId) {
+ Shade *cur = &_shades[shadeId];
+ int curPos = cur->currentPos;
+ int side = curPos / 5;
+ int pos = curPos % 5;
+ Shade *pNextTo1 = pos == 0 ? nullptr : posToShade(curPos-1);
+ Shade *pNextTo2 = pos == 4 ? nullptr : posToShade(curPos+1);
+ Shade *pAfrontOf = posToShade(pos + (side ? 0 : 5));
+ bool pNextTo1Potential = pos != 0 && pNextTo1 == nullptr;
+ bool pNextTo2Potential = pos != 4 && pNextTo2 == nullptr;
+
+ // "Must sit next to"
+ if (!isAttribMatch(pNextTo1, cur->tabN)
+ && !isAttribMatch(pNextTo2, cur->tabN)
+ && !pNextTo1Potential && !pNextTo2Potential) {
+ return false;
+ }
+
+ // "Must not sit next to"
+ if (cur->tabNN && (isAttribMatch(pNextTo1, cur->tabNN)
+ || isAttribMatch(pNextTo2, cur->tabNN))) {
+ yuck();
+ return false;
+ }
+
+ // "Must sit in front of"
+ if (!isAttribMatch(pAfrontOf, cur->tabA)
+ && pAfrontOf != nullptr) {
+ return false;
+ }
+
+ // "Must not sit in front of"
+ if (cur->tabNA && isAttribMatch(pAfrontOf, cur->tabNA)) {
+ yuck();
+ return false;
+ }
+
+ // "Must sit in front or back"
+ if ((cur->tabO & 1) && pos != 0 && pos != 4) {
+ return false;
+ }
+
+ // Charon: only X in front
+ if (pos == 4 && !isAttribMatch(cur, _charonN))
+ return false;
+
+ // Charon: no X in front
+ if (pos == 4 && _charonNN && isAttribMatch(cur, _charonNN)) {
+ yuck();
+ return false;
+ }
+
+ // TODO: sound on "must" path
+
+ return true;
+ }
+
+ int typeToIdx(int type) {
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ if (_shades[i].shadowId == type)
+ return i;
+ }
+
+ return -1;
+ }
+
+ Shade *posToShade(int pos) {
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ if (_shades[i].currentPos == pos)
+ return &_shades[i];
+ }
+
+ return nullptr;
+ }
+
+
+ void levelClear() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ for (unsigned i = 0; i < ARRAYSIZE(charonRules); i++) {
+ room->stopAnim(charonRules[i].image);
+ }
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ room->stopAnim(LayerId(shadows[_shades[i].shadowId].image, i, "shadow"));
+ room->stopAnim(shadows[_shades[i].shadowId].nameImage);
+ }
+ room->stopAnim("V9090oA0");
+ for (unsigned i = 0; i < ARRAYSIZE(thoughts); i++) {
+ room->stopAnim(thoughts[i].image);
+ }
+ }
+
+ void levelRender() {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->selectFrame(charonRules[_charonTID].image, 699, 0);
+ room->setHotzoneEnabled("Sign", _charonTID != 12);
+ room->enableHotzone("Charon");
+
+ for (unsigned i = 0; i < ARRAYSIZE(shadows); i++)
+ room->disableHotzone(Common::String::format("s%02d", i));
+
+ bool ffUsed[10];
+
+ memset(ffUsed, 0, sizeof(ffUsed));
+
+ for (uint i = 0; i < _shades.size(); i++) {
+ if ((int) i == _dragged) {
+ Common::Point pos = g_vm->getMousePos();
+ room->selectFrame(LayerId(shadows[_shades[i].shadowId].image,
+ i, "shadow"), 0, 0, pos - Common::Point(88, 160));
+ _isInAnim[i] = false;
+ continue;
+ }
+
+ if (_isInAnim[i])
+ continue;
+
+ if (_shades[i].currentPos >= 0) {
+ LayerId sl(shadows[_shades[i].shadowId].image,
+ i, "shadow");
+ int frame = _shades[i].currentPos / 5 ? shadows[_shades[i].shadowId].portFrame
+ : shadows[_shades[i].shadowId].starboardFrame;
+ room->selectFrame(sl, boatPosition[_shades[i].currentPos].z,
+ frame, getShadowPos(i));
+ ffUsed[_shades[i].currentPos] = true;
+ continue;
+ }
+
+ if (_shades[i].waitingPos >= 0) {
+ Common::Point pos = getShadowPos(i);
+ Common::String hz = Common::String::format("s%02d", _shades[i].shadowId);
+ room->enableHotzone(hz);
+ room->setHotZoneOffset(hz, waitPosition[_shades[i].waitingPos].getPoint()
+ - Common::Point(88, 160) + Common::Point(0, shadows[_shades[i].shadowId].waityoffset));
+ room->selectFrame(LayerId(shadows[_shades[i].shadowId].image,
+ i, "shadow"),
+ waitPosition[_shades[i].waitingPos].z, 0, pos);
+ continue;
+ }
+ }
+
+ for (unsigned i = 0; i < 2; i++)
+ for (unsigned j = 0; j < 5; j++)
+ room->setHotzoneEnabled(Common::String::format("f%01d%01d", i, j),
+ _dragged != -1 ? !ffUsed[i*5 + j] : ffUsed[i*5 + j]);
+ }
+
+ Common::Point getShadowPos(unsigned idx) {
+ if (_shades[idx].currentPos >= 0) {
+ Common::Point delta;
+ if (_shades[idx].shadowId == 14 || _shades[idx].shadowId == 20)
+ delta = Common::Point(0, -35);
+ return boatPosition[_shades[idx].currentPos].getPoint()
+ - Common::Point(88, 160) + delta;
+ }
+
+ if (_shades[idx].waitingPos >= 0) {
+ return waitPosition[_shades[idx].waitingPos].getPoint()
+ - Common::Point(88, 160) + Common::Point(0, shadows[_shades[idx].shadowId].waityoffset);
+ }
+
+ return Common::Point(0, 0);
+ }
+
+ void loadLevel() {
+ TextTable _levelTable;
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ _levelTable = TextTable(
+ Common::SharedPtr<Common::SeekableReadStream>(
+ room->openFile(
+ Common::String::format("l%ds%02d.ff", _levelL, _levelS))),
+ 14);
+ _shades.clear();
+ for (int i = 0; i < _levelTable.size(); i++) {
+ if (_levelTable.get(i, "Num") == "") {
+ _charonTID = _levelTable.get(i, "TID").asUint64Ext();
+ _charonN = _levelTable.get(i, "N").asUint64Ext();
+ _charonNN = _levelTable.get(i, "NN").asUint64Ext();
+ continue;
+ }
+
+ Shade shade;
+ shade.shadowId = _levelTable.get(i, "Num").asUint64Ext();
+ shade.thoughtId = _levelTable.get(i, "TID").asUint64Ext();
+ if (_levelTable.get(i, "BoatPos") == "" || _levelTable.get(i, "BoatPos") == "0") {
+ shade.currentPos = -1;
+ shade.positionIsFixed = false;
+ } else {
+ shade.currentPos = _levelTable.get(i, "BoatPos").asUint64Ext() - 1;
+ shade.positionIsFixed = true;
+ }
+ shade.tabNA = _levelTable.get(i, "NA").asUint64Ext();
+ shade.tabNN = _levelTable.get(i, "NN").asUint64Ext();
+ shade.tabN = _levelTable.get(i, "N").asUint64Ext();
+ shade.tabO = _levelTable.get(i, "O").asUint64Ext();
+ shade.tabA = _levelTable.get(i, "A").asUint64Ext();
+ shade.waitingPos = -1;
+ shade.rowidx = _shades.size();
+ _shades.push_back(shade);
+ }
+
+ for (int j = 0; j < 2; j++) {
+ int m = 100000, mpos = -1;
+ for (unsigned i = j; i < _shades.size(); i++) {
+ if (shadows[_shades[i].shadowId].priorityleft <= m && !_shades[i].positionIsFixed) {
+ m = shadows[_shades[i].shadowId].priorityleft;
+ mpos = i;
+ }
+ }
+ if (shadows[_shades[j].shadowId].priorityleft <= m && !_shades[j].positionIsFixed)
+ continue;
+ Shade t = _shades[mpos];
+ _shades[mpos] = _shades[j];
+ _shades[j] = t;
+ }
+
+ for (int j = 0; j < 2; j++) {
+ int m = 100000, mpos = -1;
+ for (unsigned i = 2; i < _shades.size() - j; i++) {
+ if (shadows[_shades[i].shadowId].priorityright <= m && !_shades[i].positionIsFixed) {
+ m = shadows[_shades[i].shadowId].priorityright;
+ mpos = i;
+ }
+ }
+ if (shadows[_shades[_shades.size() - j - 1].shadowId].priorityright <= m
+ && !_shades[_shades.size() - j - 1].positionIsFixed)
+ continue;
+ Shade t = _shades[mpos];
+ _shades[mpos] = _shades[_shades.size() - j - 1];
+ _shades[_shades.size() - j - 1] = t;
+ }
+
+ int waitidx = 0;
+
+ for (unsigned i = 0; i < _shades.size(); i++) {
+ if (!_shades[i].positionIsFixed)
+ _shades[i].waitingPos = waitidx;
+ debug("%s, leftpriority %d, rightpriority %d, idx %d, res %d",
+ shadows[_shades[i].shadowId].name,
+ shadows[_shades[i].shadowId].priorityleft,
+ shadows[_shades[i].shadowId].priorityright,
+ _shades[i].rowidx,
+ _shades[i].waitingPos);
+ waitidx++;
+ }
+ }
+ int _levelL;
+ int _levelS;
+ int _charonTID;
+ int _charonN;
+ int _charonNN;
+ int _dragged;
+ int _clickTimer;
+ int _lastCharonAnim;
+ int _count24006;
+ bool _isPlayingYuck;
+ bool _charonIsBusy;
+ Common::Array<Shade> _shades;
+ bool _isInAnim[kNumSeats];
+};
+
+Common::SharedPtr<Hadesch::Handler> makeFerryHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new FerryHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/hadesthrone.cpp b/engines/hadesch/rooms/hadesthrone.cpp
new file mode 100644
index 0000000000..731678f1ad
--- /dev/null
+++ b/engines/hadesch/rooms/hadesthrone.cpp
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+
+namespace Hadesch {
+
+class HadesThroneHandler : public Handler {
+public:
+ HadesThroneHandler() {
+ }
+
+ void handleClick(const Common::String &name) override {
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+
+ switch(eventId) {
+ case 29001:
+ persistent->_quest = kEndGame;
+ persistent->clearInventory();
+ persistent->_doQuestIntro = true;
+ g_vm->moveToRoom(kWallOfFameRoom);
+ break;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->playVideo("movie", 500, 29001);
+ room->disableHeroBelt();
+ room->playSoundLoop("V6010eA0");
+ room->disableMouse();
+ }
+};
+
+Common::SharedPtr<Hadesch::Handler> makeHadesThroneHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new HadesThroneHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/intro.cpp b/engines/hadesch/rooms/intro.cpp
new file mode 100644
index 0000000000..9e6eb44e61
--- /dev/null
+++ b/engines/hadesch/rooms/intro.cpp
@@ -0,0 +1,60 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+
+namespace Hadesch {
+
+class IntroHandler : public Handler {
+public:
+ IntroHandler() {
+ }
+
+ void handleClick(const Common::String &name) override {
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ switch(eventId) {
+ // 32002 handles performance testing
+ case 32003:
+ g_vm->moveToRoom(kOlympusRoom);
+ break;
+ }
+ }
+
+ void prepareRoom() override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ room->playVideo("o0010ba0", 101, 32003);
+ room->disableHeroBelt();
+ room->disableMouse();
+ }
+};
+
+Common::SharedPtr<Hadesch::Handler> makeIntroHandler() {
+ return Common::SharedPtr<Hadesch::Handler>(new IntroHandler());
+}
+
+}
diff --git a/engines/hadesch/rooms/medisle.cpp b/engines/hadesch/rooms/medisle.cpp
new file mode 100644
index 0000000000..50e6edf7d3
--- /dev/null
+++ b/engines/hadesch/rooms/medisle.cpp
@@ -0,0 +1,1284 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020 Google
+ *
+ */
+#include "hadesch/hadesch.h"
+#include "hadesch/video.h"
+#include "hadesch/ambient.h"
+
+namespace Hadesch {
+
+static const char *kStoneAnim = "g0110ob0";
+static const char *kStoneHotzone = "stone";
+static const char *snakes[] = {
+ "m1220bb0",
+ "m1210bb0",
+ "m1190bb0",
+ "m1180bb0"
+};
+
+static const char *greenSnakes[] = {
+ "m1160bd0",
+ "m1160bc0",
+ "m1160bb0",
+ "m1160ba0"
+};
+
+static const char *itemImages[] = {
+ "m1010bb0",
+ "m1010bc0",
+ "m1010ba0",
+ "m1010bd0",
+ "m1010be0"
+};
+
+static const char *itemImagesGlow[] = {
+ "m1010bb1",
+ "m1010bc1",
+ "m1010ba1",
+ "m1010bd1",
+ "m1010be1"
+};
+
+static const char *itemSounds[] = {
+ "m1190ea0",
+ "m1180ec0",
+ "m1220ea0",
+ "m1210ea0",
+ "m1230ea0"
+};
+
+static const char *itemClickSounds[] = {
+ "m1150ne0",
+ "m1150nd0",
+ "m1150na0",
+ "m1150nb0",
+ "m1150nc0"
+};
+
+static const char *perseusItemAnims[] = {
+ "m1190ba0",
+ "m1180ba0",
+ "m1220ba0",
+ "m1210ba0",
+ "m1230ba0"
+};
+
+static const char *perseusItemSounds[] = {
+ "m1190na0",
+ "m1180na0",
+ "m1220na0",
+ "m1210na0",
+ "m1230na0"
+};
+
+static const int kBagPuzzleNumElements = 10;
+
+static const char *statueFullElements[kBagPuzzleNumElements] = {
+ "m1010or0",
+ "m1010or1",
+ "m1010ov0",
+ "m1010ow0",
+ "m1010os0",
+ "m1010ot0",
+ "m1010ou0",
+ "m1010op0",
+ "m1010oq0",
+ "m1010oo0"
+};
+
+static const char *statueEmptyElements[kBagPuzzleNumElements] = {
+ "m1010or2",
+ "m1010or3",
+ "m1010ov1",
+ "m1010ow1",
+ "m1010os1",
+ "m1010ot1",
+ "m1010ou1",
+ "m1010op1",
+ "m1010oq1",
+ "m1010oo1"
+};
+
+static const int statuesZvals1[kBagPuzzleNumElements] = {
+ 2009,
+ 2008,
+ 2010,
+ 2006,
+ 2007,
+ 2005,
+ 2004,
+ 2002,
+ 2001,
+ 2003
+};
+
+static Common::Point statuesOffsets1[kBagPuzzleNumElements] = {
+ Common::Point(519, 282),
+ Common::Point(571, 276),
+ Common::Point(528, 225),
+ Common::Point(547, 225),
+ Common::Point(518, 159),
+ Common::Point(500, 161),
+ Common::Point(563, 173),
+ Common::Point(471, 132),
+ Common::Point(565, 193),
+ Common::Point(539, 107)
+};
+
+static Common::Point statueBrokenPiecesOffsets[kBagPuzzleNumElements] = {
+ Common::Point(461, 395),
+ Common::Point(339, 358),
+ Common::Point(590, 356),
+ Common::Point(298, 415),
+ Common::Point(517, 407),
+ Common::Point(582, 453),
+ Common::Point(381, 362),
+ Common::Point(483, 357),
+ Common::Point(426, 360),
+ Common::Point(406, 307)
+};
+
+static Common::Point statuePieceHotspot[kBagPuzzleNumElements] = {
+ Common::Point(26, 28),
+ Common::Point(15, 30),
+ Common::Point(11, 24),
+ Common::Point(15, 25),
+ Common::Point(23, 26),
+ Common::Point(13, 10),
+ Common::Point(10, 13),
+ Common::Point(20, 24),
+ Common::Point(25, 35),
+ Common::Point(24, 32)
+};
+
+static const char *statuePieceNames[kBagPuzzleNumElements] = {
+ "LowerLeg1",
+ "LowerLeg2",
+ "UpperLeg1",
+ "UpperLeg2",
+ "Torso",
+ "UpperArm1",
+ "UpperArm2",
+ "LowerArm1",
+ "LowerArm2",
+ "Head"
+};
+
+static const char *fatesHotzoneNames[kNumFates] = {
+ "Lachesis",
+ "Atropos",
+ "Clotho"
+};
+
+static const char *itemNames[] = {
+ "shield",
+ "sword",
+ "bag",
+ "helmet",
+ "sandals"
+};
+
+static const int statuePiecesDepMap[kBagPuzzleNumElements][2] = {
+ { -1, -1 },
+ { -1, -1 },
+ { 0, -1 },
+ { 1, -1 },
+ { 2, 3 },
+ { 4, -1 },
+ { 4, -1 },
+ { 5, -1 },
+ { 6, -1 },
+ { 4, -1 }
+};
+
+static const struct {
+ const char *image;
+ int minint, maxint;
+ int zVal;
+} ambientsLeft[] = {
+ { "m1030ba0", 10000, 40000, 4000 },
+ { "m1320ba0", 15000, 30000, 4000 },
+ { "m1320bb0", 10000, 50000, 4000 },
+ { "m1320bc0", 5000, 20000, 4000 },
+ { "m1330ba0", 10000, 40000, 4000 },
+ { "m1330bb0", 5000, 20000, 4000 },
+ { "m1330bc0", 5000, 30000, 4000 },
+ { "m1340ba0", 5000, 20000, 4000 },
+ { "m1340bb0", 15000, 30000, 4000 },
+ { "m1340bc0", 5000, 20000, 4000 }
+};
+
+static const struct {
+ const char *image;
+ const char *sound;
+ int minint, maxint;
+ int zVal;
+ int parallax;
+ bool keep;
+} ambientsLeftSnakesAndRats[] = {
+ { "m1090ba0", "m1090ea0", 5000, 45000, 300, -200, true },
+ { "m1310ba0", "m1310ea0", 5000, 45000, 300, -200, true },
+ { "m1100ba0", "m1100ea0", 5000, 45000, 4100, 0, true },
+ { "m1300ba0", "m1300ea0", 5000, 45000, 4100, 0, true },
+ { "m1060ba0", "m1060ea0", 5000, 50000, 4200, 0, false },
+ { "m1280ba0", "m1280ea0", 5000, 50000, 1500, -200, false }
+};
+
+static const struct {
+ const char *image;
+ int minint, maxint;
+ int zVal;
+} ambientsRight[] = {
+ {"m2060ba0", 5000, 40000, 4000},
+ {"m2060bb0", 10000, 30000, 4000},
+ {"m2280ba0", 5000, 20000, 2000},
+ {"m2270ba0", 3000, 20000, 4000}
+};
+
+enum {
+ kStoneTakenCleanup = 11050,
+ kStoneTaken = 1011050
+};
+
+enum {
+ kBackgroundZ = 10000,
+ kStatuesZVal2 = 3000,
+ kFatesZ = 1500
+};
+
+enum {
+ kLoopFatesShadow = 1011001
+};
+
+class MedIsleHandler : public Handler {
+public:
+ MedIsleHandler() {
+ _eyeInsistCounter = 0;
+ _eyeIsGivenBack = false;
+ _eyeIsPickedUp = false;
+ _fatesShadowIsActive = false;
+ _isFirstFates = false;
+ _statueDrag = -1;
+ _depProblemState = 0;
+ _fatesAreBusy = false;
+ _lastClickedItem = -1;
+ _hintsCounter = 0;
+ }
+
+ void handleClick(const Common::String &name) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ Quest quest = persistent->_quest;
+
+ for (int i = 0; i < kBagPuzzleNumElements; i++) {
+ if (name == Common::String("D") + statuePieceNames[i]) {
+ statueDClick(i);
+ return;
+ }
+ if (name == Common::String("S") + statuePieceNames[i]) {
+ statueSClick(i);
+ return;
+ }
+ }
+
+ if (_statueDrag >= 0) {
+ return;
+ }
+ if (name == kStoneHotzone) {
+ room->stopAnim(kStoneAnim);
+ g_vm->getHeroBelt()->placeToInventory(kStone, kStoneTaken);
+ room->disableHotzone(kStoneHotzone);
+ room->playSound("m1360ma0");
+ persistent->_medisleStoneTaken = true;
+ room->disableMouse();
+ return;
+ }
+
+ if (name == "Argo") {
+ g_vm->moveToRoom(kArgoRoom);
+ return;
+ }
+
+ if (name == "Eyeball") {
+ room->drag("m2010oa0", 0, Common::Point(21, 20));
+ room->stopAnim("m2010oa0");
+ room->disableMouse();
+ room->playSound("m2130ea0", 11027);
+ _eyeIsPickedUp = true;
+ return;
+ }
+
+ if (_eyeIsGivenBack) {
+ static const int nextEvent[] = {
+ 11621, 11623, 11626
+ };
+ for (FateId i = kLachesis; i < kNumFates; i = (FateId) (i + 1)) {
+ if (name == fatesHotzoneNames[i]) {
+ moveEye(i, nextEvent[i]);
+ return;
+ }
+ }
+ }
+
+ if (_eyeIsPickedUp && !_eyeIsGivenBack) {
+ for (FateId i = kLachesis; i < kNumFates; i = (FateId) (i + 1))
+ if (name == fatesHotzoneNames[i]) {
+ persistent->_medisleEyePosition = i;
+ room->disableMouse();
+ room->playSound("m2130ee0", 11029);
+ room->disableHotzone("Eyeball");
+ room->clearDrag();
+ _eyeIsGivenBack = true;
+ _isFirstFates = true;
+ return;
+ }
+ }
+
+ if (name == "MagicBag") {
+ hideMagicBag();
+ persistent->_medisleBagPuzzleState = Persistent::BAG_TAKEN;
+ g_vm->getHeroBelt()->placeToInventory(kBag);
+ renderFatesAll();
+ return;
+ }
+
+ for (int i = 0; i < 5; i++) {
+ if (name == itemNames[i]) {
+ itemGlow(i);
+ return;
+ }
+ }
+
+ if (name == "FatesLair") {
+ if (showAllFates())
+ return;
+ room->disableMouse();
+ if (showNoFates()) {
+ _hintsCounter++;
+ if (_hintsCounter == 1 && persistent->_medislePlayedPhilFatesDesc)
+ _hintsCounter = 2;
+ if (_hintsCounter == 1) {
+ if (quest > kMedusaQuest || (quest == kMedusaQuest && persistent->_medisleShowFates)) {
+ playFatesLairBackupSound();
+ return;
+ }
+ persistent->_medislePlayedPhilFatesDesc = true;
+ room->playVideo("m2210ba0", 0, 11049, Common::Point(640, 216));
+ return;
+ }
+
+ Common::Array <Common::String> hints;
+
+ switch (quest) {
+ case kCreteQuest:
+ hints.push_back("m2220wa0");
+ hints.push_back("m2220wb0");
+ hints.push_back("m2220wc0");
+ break;
+ case kTroyQuest:
+ hints.push_back("m2230wa0");
+ hints.push_back("m2230wb0");
+ hints.push_back("m2230wc0");
+ break;
+ case kMedusaQuest:
+ if (persistent->_medisleShowFates) {
+ hints.push_back("m2210wa0");
+ hints.push_back("m2250wa0");
+ hints.push_back("m2250wb0");
+ }
+ break;
+ case kRescuePhilQuest:
+ hints.push_back("m2240wa0");
+ hints.push_back("m2240wb0");
+ break;
+ // To silence warning
+ case kNoQuest:
+ case kEndGame:
+ case kNumQuests:
+ break;
+ }
+
+ if (hints.empty()) {
+ playFatesLairBackupSound();
+ } else {
+ fatesShadowSound(hints[(_hintsCounter - 2) % hints.size()], 11632);
+ }
+ return;
+ }
+
+ if (showFate(kLachesis) && showFate(kAtropos) && !showFate(kClotho)) {
+ fatesShadowSound("m2210wd0", 11632);
+ }
+
+ if (showFate(kLachesis) && !showFate(kAtropos) && showFate(kClotho)) {
+ fatesShadowSound("m2210wa0", 11632);
+ }
+
+ if (showFate(kLachesis) && !showFate(kAtropos) && !showFate(kClotho)) {
+ fatesShadowSound("m2190wd0", 11632);
+ }
+
+ if (!showFate(kLachesis) && showFate(kAtropos) && showFate(kClotho)) {
+ fatesShadowSound("m2190wa0", 11632);
+ }
+
+ if (!showFate(kLachesis) && showFate(kAtropos) && !showFate(kClotho)) {
+ fatesShadowSound(_hintsCounter & 1 ? "m2190wa0" : "m2190wd0", 11632);
+ }
+
+ if (!showFate(kLachesis) && !showFate(kAtropos) && showFate(kClotho)) {
+ switch (_hintsCounter % 3) {
+ case 0:
+ fatesShadowSound("m2190wb0", 11629);
+ return;
+ case 1:
+ fatesShadowSound("m2190wa0", 11632);
+ return;
+ case 2:
+ fatesShadowSound("m2210wa0", 11632);
+ return;
+ }
+ }
+
+ return;
+ }
+
+ /*
+TODO (medusa quest):
+ MNSH: Perseus
+ MNSH: MedusasLair
+*/
+ }
+
+ bool handleClickWithItem(const Common::String &name, InventoryItem item) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+
+ for (int i = 0; i < 5; i++) {
+ if (name == itemNames[i] && item == kShield + i) {
+ itemPlaced(item);
+ return true;
+ }
+ }
+
+ if (name == "Perseus" && (
+ item >= kShield && item <= kSandals)) {
+ room->disableMouse();
+ playPerseusAnim("m1240ba0", "m1240na0", 11053);
+ }
+
+ return false;
+ }
+
+ void handleEvent(int eventId) override {
+ Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
+ Persistent *persistent = g_vm->getPersistent();
+ switch (eventId) {
+ case 11002:
+ if (!room->isMouseEnabled() || !room->isPanRight())
+ break;
+ room->disableMouse();
+ room->playVideo("m2100ba0", 0, 11003,
+ Common::Point(640, 216));
+ break;
+ case 11003:
+ room->playSound("m2100wa0", 11004);
+ room->enableMouse();
+ break;
+ case kStoneTaken:
+ room->playVideo("m1360ba0", 200, kStoneTakenCleanup, Common::Point(0, 216));
+ break;
+ case 11064: // Right pan
+ if (persistent->_medisleShowFatesIntro) {
+ persistent->_medisleShowFatesIntro = false;
+ persistent->_medisleShowFates = true;
+ room->setPannable(false);
+ room->disableHotzone("Argo");
+ room->playSound("m2120ma0", 11642);
+ g_vm->addTimer(11601, 6500);
+ room->disableMouse();
+ persistent->_medisleEyeballIsActive = true;
+ }
+ break;
+ case 11601:
+ fatesShadowSound("m2120wa0", 11602);
+ break;
+ case 11602:
+ fatesShadowSound("m2120wb0", 11603);
+ break;
+ case 11603:
+ fatesShadowSound("m2120wc0", 11604);
+ break;
+ case 11604:
+ fatesShadowSoundEnd();
+ room->playAnimWithSound("m2120ba0", "m2120ea0", 280, PlayAnimParams::disappear(), 11605, kOffsetRightRoom);
+ break;
+ case 11605:
+ room->enableMouse();
+ room->enableHotzone("Eyeball");
+ room->selectFrame("m2010oa0", 280, 0, Common::Point(949, 409));
+ room->setLayerParallax("m2010oa0", -200);
+ g_vm->addTimer(11606, g_vm->getRnd().getRandomNumberRng(10000, 20000), -1);
+ break;
+ case 11606:
+ if (_eyeIsGivenBack || _eyeIsPickedUp)
+ break;
+ fatesShadowSound(Common::String::format("m2120w%c0", 'd' + (_eyeInsistCounter % 3)), 11608);
+ _eyeInsistCounter++;
+ break;
+ case 11608:
+ fatesShadowSoundEnd();
+ break;
+ case 11065: // Left pan
+ if (persistent->_seriphosPlayedMedusa && !persistent->_medislePlayedPerseusIntro) {
+ room->disableMouse();
+ room->playVideo("m1140ba0", 0, 11005, Common::Point(0, 216));
+ }
+ break;
+ case 11005:
+ playPerseusAnim("m1140bb0", "m1140nb0", 11007);
+ persistent->_medislePlayedPerseusIntro = true;
+ break;
+ case 11006:
+ finishPerseusAnim();
+ room->enableMouse();
+ break;
+ case 11007:
+ playPerseusAnim("m1140bc0", "m1140nc0", 11008);
+ break;
+ case 11008:
+ playPerseusAnim("m1140bd0", "m1140nd0", 11006);
+ break;
+ case 11009:
+ room->playSound(itemClickSounds[_lastClickedItem], 11010);
+ break;
+ case 11010:
+ for (int i = 0; i < 5; i++)
+ room->stopAnim(itemImagesGlow[i]);
+ break;
+ case 11012:
+ room->playSound("m1210ma0", 11013);
+ break;
+ case 11013:
+ playPerseusAnim(perseusItemAnims[_lastPlacedItem - kShield],
+ perseusItemSounds[_lastPlacedItem - kShield], 11014);
+ break;
+ case 11014: {
+ finishPerseusAnim();
+ int event = -1;
+ switch (getNumberOfBroughtItems()) {
+ case 1:
+ event = 11015;
+ break;
+ case 2:
+ persistent->_medisleShowFatesIntro = true;
+ event = 11017;
+ break;
+ case 3:
+ event = 11020;
+ break;
+ case 4:
+ event = 11024;
+ break;
+ case 5:
+ room->playAnimWithSound("m1170ba0",
+ "m1190ec1",
+ 806,
+ PlayAnimParams::disappear(),
+ 11021);
+ return;
+ }
+ int snakeIdx = 4 - getNumberOfBroughtItems();
+ room->playAnimWithSound(snakes[snakeIdx],
+ "m1190ec1", 807 + snakeIdx,
+ PlayAnimParams::disappear(),
+ event);
+ }
+ break;
+ case 11015:
+ renderPerseus();
+ if (_lastPlacedItem == kSword)
+ playPerseusAnim("m1180bc0", "m1180nb0", 11016);
+ else
+ playPerseusAnim("m1190bc0", "m1190nb0", 11016);
+ break;
+ case 11016:
+ case 11025:
+ case 11053:
+ finishPerseusAnim();
+ room->enableMouse();
+ break;
+ case 11017:
+ renderPerseus();
+ playPerseusAnim("m1200ba0", "m1200na0", 11018);
+ break;
+ case 11018:
+ finishPerseusAnim();
+ room->playVideo("m1200ma0", 0, 11019);
+ break;
+ case 11019:
+ case 11037:
+ case 11042:
+ case 11049:
+ case kStoneTakenCleanup:
+ room->enableMouse();
+ break;
+ case 11021:
+ for (int i = 0; i < 5; i++)
+ room->stopAnim(itemImages[i]);
+ for (int i = 0; i < 4; i++)
+ room->stopAnim(snakes[i]);
+ for (int i = 0; i < 4; i++)
+ room->stopAnim(greenSnakes[i]);
+ _perseusAnim.hide();
+ room->stopAnim("m1010oi0");
+ room->playVideo("m1260bh0", 900, 11023, Common::Point(2, 60));
+ break;
+ case 11023:
+ room->selectFrame("m1260bh1", 900, 0);
+ // TODO: arcade sequence
+ if (0) {
+ g_vm->moveToRoom(kMedusaPuzzle);
+ } else
+ g_vm->moveToRoom(kQuiz);
+ break;
+ case 11024:
+ renderPerseus();
+ playPerseusAnim("m1250bb0", "m1250nb0", 11025);
+ break;
+ case 11020:
+ renderPerseus();
+ playPerseusAnim("m1250ba0", "m1250na0", 11025);
+ break;
+ case 11027:
+ room->playVideo("m2130ba0", kFatesZ, 11609, Common::Point(756, 0));
+ break;
+ case 11029:
+ moveEye(kLachesis, 11621);
+ break;
+ case 11621:
+ renderFatesExcept(kLachesis);
+ room->playVideo("m2160ba0", kFatesZ, _isFirstFates ? 11622 : 11627, Common::Point(854, 0));
+ break;
+ case 11622:
+ moveEye(kAtropos, 11623);
+ break;
+ case 11623:
+ renderFatesExcept(kAtropos);
+ room->playVideo("m2170ba0", kFatesZ, _isFirstFates ? 11624 : 11627, Common::Point(1002, 96));
+ break;
+ case 11624:
+ if (persistent->_medisleBagPuzzleState < Persistent::BAG_STARTED)
+ persistent->_medisleBagPuzzleState = Persistent::BAG_STARTED;
+ room->playSound("m2200ea0");
+ startBagPuzzle();
+ // Fallthrough
+ case 11625:
+ moveEye(kClotho, 11626);
+ break;
+ case 11626:
+ renderFatesExcept(kClotho);
+ room->playVideo("m2180ba0", kFatesZ, 11627, Common::Point(1090, 68));
+ break;
+ case 11627:
+ renderFatesAll();
+ room->enableMouse();
+ _isFirstFates = false;
+ room->setPannable(true);
+ room->enableHotzone("Argo");
+ break;
+ case 11044:
+ if (room->isMouseEnabled() && !_eyeIsGivenBack) {
+ room->playVideo("m2130bd0", 0, 11045, Common::Point(640, 216));
+ }
+ break;
+ case 11609:
+ renderFatesExcept(kAtropos, kClotho);
+ room->playVideo("m2130bb0", kFatesZ, 11610, Common::Point(922, 0));
+ break;
+ case 11610:
+ renderFatesExcept(kClotho);
+ room->playVideo("m2130bc0", kFatesZ, 11611, Common::Point(1024, 0));
+ break;
+ case 11611:
+ renderFatesAll();
+ room->enableMouse();
+ g_vm->addTimer(11044, 5000);
+ g_vm->addTimer(11615, g_vm->getRnd().getRandomNumberRng(5000, 10000), -1);
+ break;
+ case 11615: {
+ if (showNoFates() || _fatesAreBusy || !room->isPanRight())
+ break;
+ _fatesAreBusy = true;
+
+ FateId fate = (FateId) g_vm->getRnd().getRandomNumberRng(0, 2);
+
+ if (!showFate(fate) && fate == kLachesis) {
+ room->playSound(
+ g_vm->getRnd().getRandomNumberRng(0, 1) ? "m2160wb0" : "m2160wa0", 11616);
+ break;
+ }
+
+ if (!showFate(fate)) {
+ _fatesAreBusy = false;
+ break;
+ }
+
+ // Move eye to another fate
+ if (persistent->_medisleEyePosition == fate) {
+ int variants = 0;
+ for (FateId i = kLachesis; i < kNumFates; i = (FateId) (i + 1))
+ if (showFate(i) && i != fate)
+ variants++;
+ if (variants <= 0) {
+ _fatesAreBusy = false;
+ break;
+ }
+
+ int off = variants == 1 ? 0 : g_vm->getRnd().getRandomNumberRng(0, 1);
+
+ FateId moveTo = kLachesis;
+ int j = 0;
+
+ for (FateId i = kLachesis; i < kNumFates; i = (FateId) (i + 1))
+ if (showFate(i) && i != fate) {
+ if (j == off) {
+ moveTo = i;
+ break;
+ }
+ j++;
+ }
+
+ moveEye(moveTo, 11617);
+ break;
+ }
+
+ switch (fate) {
+ case kLachesis:
+ room->stopAnim("m2140od0");
+ if (g_vm->getRnd().getRandomNumberRng(0, 1)) {
+ room->playAnimWithSound("m2140ba0", "m2140ea0", 1500,
+ PlayAnimParams::disappear(), 11617, kOffsetRightRoom);
+ } else {
+ room->playAnimWithSound("m2140be0", "m2140ee0", 1500,
+ PlayAnimParams::disappear(), 11617, kOffsetRightRoom);
+ }
+ break;
+ case kAtropos:
+ room->stopAnim("m2140oe0");
+ room->playAnimWithSound("m2140bg0", "m2140eg0", 1500,
+ PlayAnimParams::disappear(), 11617, kOffsetRightRoom);
+ break;
+ case kClotho:
+ room->stopAnim("m2140of0");
+ room->playAnimWithSound("m2140bh0", "m2140eh0", 1500,
+ PlayAnimParams::disappear(), 11617, kOffsetRightRoom);
+ break;
+ // To silence warning
+ case kNumFates:
+ break;
+ }
+ break;
+ }
+ case 11616:
+ case 11617:
+ renderFatesAll();
+ break;
+ case 11629:
+ room->playSound("m2190wc0", 11630);
+ break;
+ case 11630:
+ case 11632:
+ room->enableMouse();
+ fatesShadowSoundEnd();
+ break;
+ case kLoopFatesShadow:
+ room->playAnim("m2280bc0", 4000,
+ PlayAnimParams::loop().partial(10, 49), -1, kOffsetRightRoom);
+ break;
+ case 11035:
+ room->playSound("m1270ea0", 11203);
+ _statueDrag = -1;
+ renderStatue();
+ break;
+ case 11203:
+ if (isAllPlaced()) {
+ room->playAnimWithSound("m1270bc0", "m1270eb0", 500, PlayAnimParams::disappear(), 11038);
+ room->disableMouse();
+ }
+ break;
+ case 11038:
+ persistent->_medisleBagPuzzleState = Persistent::BAG_SOLVED;
+ showMagicBag();
More information about the Scummvm-git-logs
mailing list