[Scummvm-git-logs] scummvm master -> 1f4f556e194351202487c48b5d396d62ba32c4b5
sev-
noreply at scummvm.org
Tue Feb 21 16:17:16 UTC 2023
This automated email contains information about 30 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
c6a6a0d6e7 VCRUISE: Initial commit, boilerplate
9d179f101f VCRUISE: Add script compiler and initial loading things
e3360047a1 VCRUISE: Add map loader
974fe14b2c VIDEO: Tag V-Cruise as using AVIDecoder
e58c6ba1b2 VCRUISE: Add enough stuff to render opening cinematic
8f8836617b VIDEO: Increase stream name capacity to deal with long (67 character) stream names in Reah
5216131a5d VCRUISE: Add more things to get Reah to the first screen
db5c7050f2 VCRUISE: Rename detection entry from GOG to DVD
9df627d497 VCRUISE: Add game ID initializer for end marker.
3cbb072dd1 VCRUISE: Remove unused variable
10273c1ad0 VCRUISE: Quiet some warnings
4807ac156a VCRUISE: Remove unused variable
8dccd9d2b0 VCRUISE: Quiet more unused variable warnings
0d77df259c VCRUISE: Only allow real 32-bit formats, permit 555 RGB.
c5956be096 VCRUISE: Add Schizm and Reah CD
0ea1beacc8 VCRUISE: Fix video frame overrun
83dee81242 VCRUISE: Fix formatting
bfa24934b7 VCRUISE: Fix script miscompilation
2f69cda8f0 VCRUISE: Implement debug mode and some idle state functionality
0b7c19a070 VCRUISE: Combine env vars, make some tweaks for panorama input detection
02df16b6af VCRUISE: Clean up some code quality issues
bdcf695c13 VCRUISE: Clean up file access
42188df4cc VCRUISE: Stub unsupported opcodes
f64666d782 VCRUISE: Clean up hash map types
31a1fcdfdd VCRUISE: Remove input buffering from TextParser
94de968496 VCRUISE: Use INI loader for index
90354659ab VCRUISE: Use sscanf for number parsing
c7f6aa409a VCRUISE: Cleanup
cbe78b083f VCRUISE: Use sscanf for TextParser number parsing
1f4f556e19 VCRUISE: Initial panorama controls
Commit: c6a6a0d6e70862609caab1ff3420ba0df56d230a
https://github.com/scummvm/scummvm/commit/c6a6a0d6e70862609caab1ff3420ba0df56d230a
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Initial commit, boilerplate
Changed paths:
A engines/vcruise/configure.engine
A engines/vcruise/credits.pl
A engines/vcruise/detection.cpp
A engines/vcruise/detection.h
A engines/vcruise/detection_tables.h
A engines/vcruise/metaengine.cpp
A engines/vcruise/module.mk
A engines/vcruise/runtime.cpp
A engines/vcruise/runtime.h
A engines/vcruise/vcruise.cpp
A engines/vcruise/vcruise.h
diff --git a/engines/vcruise/configure.engine b/engines/vcruise/configure.engine
new file mode 100644
index 00000000000..1f953f440f0
--- /dev/null
+++ b/engines/vcruise/configure.engine
@@ -0,0 +1,4 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine vcruise "V-Cruise" no "schizm" "" "16bit highres"
+add_engine schizm "Schizm" no "" "" "16bit highres jpeg"
diff --git a/engines/vcruise/credits.pl b/engines/vcruise/credits.pl
new file mode 100644
index 00000000000..00258c50b8d
--- /dev/null
+++ b/engines/vcruise/credits.pl
@@ -0,0 +1,3 @@
+begin_section("V-Cruise");
+ add_person("Eric Lasota", "OneEightHundred", "");
+end_section();
diff --git a/engines/vcruise/detection.cpp b/engines/vcruise/detection.cpp
new file mode 100644
index 00000000000..4780a534c39
--- /dev/null
+++ b/engines/vcruise/detection.cpp
@@ -0,0 +1,105 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "common/config-manager.h"
+#include "common/language.h"
+
+#include "engines/advancedDetector.h"
+
+#include "vcruise/detection.h"
+
+static const PlainGameDescriptor vCruiseGames[] = {
+ {"reah", "Reah: Face the Unknown"},
+ {"schizm", "Schizm: Mysterious Journey"},
+ {nullptr, nullptr}
+};
+
+#include "vcruise/detection_tables.h"
+
+static const char *directoryGlobs[] = {
+ nullptr
+};
+
+class VCruiseMetaEngineDetection : public AdvancedMetaEngineDetection {
+public:
+ VCruiseMetaEngineDetection() : AdvancedMetaEngineDetection(VCruise::gameDescriptions, sizeof(VCruise::VCruiseGameDescription), vCruiseGames) {
+ _guiOptions = GUIO1(GAMEOPTION_LAUNCH_DEBUG);
+ _maxScanDepth = 1;
+ _directoryGlobs = directoryGlobs;
+ _flags = kADFlagCanPlayUnknownVariants;
+ }
+
+ const char *getName() const override {
+ return "vcruise";
+ }
+
+ const char *getEngineName() const override {
+ return "V-Cruise";
+ }
+
+ const char *getOriginalCopyright() const override {
+ return "V-Cruise (C) LK Avalon";
+ }
+
+ DetectedGame toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const override {
+ DetectedGame game = AdvancedMetaEngineDetection::toDetectedGame(adGame, extraInfo);
+
+ VCruise::VCruiseGameID gameID = reinterpret_cast<const VCruise::VCruiseGameDescription *>(adGame.desc)->gameID;
+
+ if (gameID == VCruise::GID_REAH) {
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::NL_NLD));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::FR_FRA));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::IT_ITA));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::DE_DEU));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::PL_POL));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ES_ESP));
+ } else if (gameID == VCruise::GID_SCHIZM) {
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::NL_NLD));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::FR_FRA));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::IT_ITA));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::DE_DEU));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::PL_POL));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ES_ESP));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::EL_GRC));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::RU_RUS));
+ }
+
+ return game;
+ }
+
+ /*
+ Common::String parseAndCustomizeGuiOptions(const Common::String &optionsString, const Common::String &domain) const {
+ Common::String guiOptions = AdvancedMetaEngineDetection::parseAndCustomizeGuiOptions(optionsString, domain);
+
+ if (domain.hasPrefix("reah")) {
+ guiOptions += " lang_Dutch lang_French lang_Italian lang_German lang_Polish lang_Spanish";
+ } else if (domain.hasPrefix("schizm")) {
+ guiOptions += " lang_Dutch lang_French lang_Italian lang_German lang_Greek lang_Polish lang_Russian lang_Spanish";
+ }
+
+ return guiOptions;
+ }
+ */
+};
+
+REGISTER_PLUGIN_STATIC(VCRUISE_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, VCruiseMetaEngineDetection);
diff --git a/engines/vcruise/detection.h b/engines/vcruise/detection.h
new file mode 100644
index 00000000000..8e7c8c64efc
--- /dev/null
+++ b/engines/vcruise/detection.h
@@ -0,0 +1,46 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_DETECTION_H
+#define VCRUISE_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace VCruise {
+
+enum VCruiseGameID {
+ GID_REAH = 0,
+ GID_SCHIZM = 1,
+};
+
+struct VCruiseGameDescription {
+ ADGameDescription desc;
+
+ VCruiseGameID gameID;
+};
+
+
+#define GAMEOPTION_LAUNCH_DEBUG GUIO_GAMEOPTIONS1
+
+
+} // End of namespace VCruise
+
+#endif // VCRUISE_DETECTION_H
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
new file mode 100644
index 00000000000..44b555f485f
--- /dev/null
+++ b/engines/vcruise/detection_tables.h
@@ -0,0 +1,53 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_DETECTION_TABLES_H
+#define VCRUISE_DETECTION_TABLES_H
+
+#include "engines/advancedDetector.h"
+
+#include "vcruise/detection.h"
+
+namespace VCruise {
+
+static const VCruiseGameDescription gameDescriptions[] = {
+
+ { // Reah: Face the Unknown, GOG downloadable version
+ {
+ "reah",
+ "GOG",
+ {
+ {"Reah.exe", 0, "60ec19c53f1323cc7f0314f98d396283", 304128},
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_REAH,
+ },
+ { AD_TABLE_END_MARKER }
+};
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/vcruise/metaengine.cpp b/engines/vcruise/metaengine.cpp
new file mode 100644
index 00000000000..0994980ed8b
--- /dev/null
+++ b/engines/vcruise/metaengine.cpp
@@ -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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/translation.h"
+
+#include "engines/advancedDetector.h"
+#include "vcruise/vcruise.h"
+
+namespace Graphics {
+
+struct Surface;
+
+} // End of namespace Graphics
+
+namespace VCruise {
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ {GAMEOPTION_LAUNCH_DEBUG,
+ {_s("Start with debugger"),
+ _s("Starts with the debugger dashboard active."),
+ "vcruise_debug",
+ false,
+ 0,
+ 0}},
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR};
+
+} // End of namespace VCruise
+
+class VCruiseMetaEngine : public AdvancedMetaEngine {
+public:
+ const char *getName() const override {
+ return "vcruise";
+ }
+
+ const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
+ return VCruise::optionsList;
+ }
+
+ bool hasFeature(MetaEngineFeature f) const override;
+ Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
+};
+
+bool VCruiseMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return checkExtendedSaves(f);
+}
+
+Common::Error VCruiseMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ *engine = new VCruise::VCruiseEngine(syst, reinterpret_cast<const VCruise::VCruiseGameDescription *>(desc));
+ return Common::kNoError;
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(VCRUISE)
+REGISTER_PLUGIN_DYNAMIC(VCRUISE, PLUGIN_TYPE_ENGINE, VCruiseMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(VCRUISE, PLUGIN_TYPE_ENGINE, VCruiseMetaEngine);
+#endif
diff --git a/engines/vcruise/module.mk b/engines/vcruise/module.mk
new file mode 100644
index 00000000000..63eeca9f809
--- /dev/null
+++ b/engines/vcruise/module.mk
@@ -0,0 +1,18 @@
+MODULE := engines/vcruise
+
+MODULE_OBJS = \
+ metaengine.o \
+ runtime.o \
+ vcruise.o
+
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_VCRUISE), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
new file mode 100644
index 00000000000..d77b0771c5f
--- /dev/null
+++ b/engines/vcruise/runtime.cpp
@@ -0,0 +1,106 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/formats/winexe.h"
+#include "common/ptr.h"
+#include "common/system.h"
+
+#include "graphics/cursorman.h"
+#include "graphics/wincursor.h"
+#include "graphics/managed_surface.h"
+
+#include "vcruise/runtime.h"
+
+
+
+namespace VCruise {
+
+Runtime::Runtime(OSystem *system) : _system(system), _roomNumber(1), _gameState(kGameStateBoot) {
+}
+
+Runtime::~Runtime() {
+}
+
+void Runtime::loadCursors(const char *exeName) {
+ Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(exeName));
+ if (!winRes)
+ error("Couldn't open executable file %s", exeName);
+
+ Common::Array<Common::WinResourceID> cursorGroupIDs = winRes->getIDList(Common::kWinGroupCursor);
+ for (Common::Array<Common::WinResourceID>::const_iterator it = cursorGroupIDs.begin(), itEnd = cursorGroupIDs.end(); it != itEnd; ++it) {
+ const Common::WinResourceID &id = *it;
+
+ Common::SharedPtr<Graphics::WinCursorGroup> cursorGroup(Graphics::WinCursorGroup::createCursorGroup(winRes.get(), *it));
+ if (!winRes) {
+ warning("Couldn't load cursor group");
+ continue;
+ }
+
+ Common::String nameStr = id.getString();
+ if (nameStr.size() == 8 && nameStr.substr(0, 7) == "CURSOR_") {
+ char c = nameStr[7];
+ if (c >= '0' && c <= '9') {
+ uint shortID = c - '0';
+ if (shortID >= _cursorsShort.size())
+ _cursorsShort.resize(shortID + 1);
+ _cursorsShort[shortID] = cursorGroup;
+ }
+ } else if (nameStr.size() == 13 && nameStr.substr(0, 11) == "CURSOR_CUR_") {
+ char c1 = nameStr[11];
+ char c2 = nameStr[12];
+ if (c1 >= '0' && c1 <= '9' && c2 >= '0' && c2 <= '9') {
+ uint longID = (c1 - '0') * 10 + (c2 - '0');
+ if (longID >= _cursors.size())
+ _cursors.resize(longID + 1);
+ _cursors[longID] = cursorGroup;
+ }
+ }
+ }
+}
+
+bool Runtime::runFrame() {
+ bool moreActions = true;
+ while (moreActions) {
+ moreActions = false;
+ switch (_gameState) {
+ case kGameStateBoot:
+ bootGame();
+ break;
+ case kGameStateQuit:
+ return false;
+ default:
+ error("Unknown game state");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void Runtime::bootGame() {
+}
+
+void Runtime::drawFrame() {
+
+ _system->updateScreen();
+}
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
new file mode 100644
index 00000000000..b6c653cb068
--- /dev/null
+++ b/engines/vcruise/runtime.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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_RUNTIME_H
+#define VCRUISE_RUNTIME_H
+
+class OSystem;
+
+namespace Graphics {
+
+struct WinCursorGroup;
+
+} // End of namespace Graphics
+
+namespace VCruise {
+
+enum GameState {
+ kGameStateBoot,
+ kGameStateCinematic,
+ kGameStateQuit,
+};
+
+class Runtime {
+public:
+ explicit Runtime(OSystem *system);
+ virtual ~Runtime();
+
+ void loadCursors(const char *exeName);
+
+ bool runFrame();
+ void drawFrame();
+
+private:
+ void bootGame();
+
+ Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors; // Cursors indexed as CURSOR_CUR_##
+ Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort; // Cursors indexed as CURSOR_#
+
+ OSystem *_system;
+ uint _roomNumber;
+ GameState _gameState;
+};
+
+} // End of namespace VCruise
+
+#endif
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
new file mode 100644
index 00000000000..c084988b570
--- /dev/null
+++ b/engines/vcruise/vcruise.cpp
@@ -0,0 +1,157 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/util.h"
+
+#include "common/config-manager.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "common/algorithm.h"
+
+#include "vcruise/runtime.h"
+#include "vcruise/vcruise.h"
+
+namespace VCruise {
+
+VCruiseEngine::VCruiseEngine(OSystem *syst, const VCruiseGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+}
+
+VCruiseEngine::~VCruiseEngine() {
+}
+
+void VCruiseEngine::handleEvents() {
+ Common::Event evt;
+ Common::EventManager *eventMan = _system->getEventManager();
+
+ while (eventMan->pollEvent(evt)) {
+ switch (evt.type) {
+ default:
+ break;
+ }
+ }
+}
+
+Common::Error VCruiseEngine::run() {
+ Common::List<Graphics::PixelFormat> pixelFormats = _system->getSupportedFormats();
+
+ const Graphics::PixelFormat *fmt16 = nullptr;
+ const Graphics::PixelFormat *fmt32 = nullptr;
+
+ for (const Graphics::PixelFormat &fmt : pixelFormats) {
+ if (fmt.rBits() == 8 && fmt.gBits() == 8 && fmt.bBits() == 8)
+ fmt32 = &fmt;
+ if ((fmt.rBits() + fmt.gBits() + fmt.bBits()) == 16)
+ fmt16 = &fmt;
+ }
+
+ // Figure out screen layout
+ Common::Point size;
+
+ Common::Point videoSize;
+ Common::Point traySize;
+ Common::Point menuBarSize;
+ const char *exeName = nullptr;
+
+ if (_gameDescription->gameID == GID_REAH) {
+ videoSize = Common::Point(608, 348);
+ menuBarSize = Common::Point(640, 44);
+ traySize = Common::Point(640, 88);
+ exeName = "Reah.exe";
+ } else if (_gameDescription->gameID == GID_SCHIZM) {
+ videoSize = Common::Point(640, 360);
+ menuBarSize = Common::Point(640, 32);
+ traySize = Common::Point(640, 88);
+ exeName = "Schizm.exe";
+ } else {
+ error("Unknown game");
+ }
+
+ size.x = videoSize.x;
+ if (menuBarSize.x > size.x)
+ size.x = menuBarSize.x;
+ if (traySize.x > size.x)
+ size.x = traySize.x;
+
+ size.y = videoSize.y + menuBarSize.y + traySize.y;
+
+ Common::Point menuTL = Common::Point((size.x - menuBarSize.x) / 2, 0);
+ Common::Point videoTL = Common::Point((size.x - videoSize.x) / 2, menuTL.y);
+ Common::Point trayTL = Common::Point((size.x - traySize.x) / 2, videoTL.y);
+
+ _menuBarRect = Common::Rect(menuTL.x, menuTL.y, menuTL.x + menuBarSize.x, menuTL.y + menuBarSize.y);
+ _videoRect = Common::Rect(videoTL.x, videoTL.y, videoTL.x + videoSize.x, videoTL.y + videoSize.y);
+ _trayRect = Common::Rect(trayTL.x, trayTL.y, trayTL.x + traySize.x, trayTL.y + traySize.y);
+
+ if (fmt32)
+ initGraphics(size.x, size.y, fmt32);
+ else if (fmt16)
+ initGraphics(size.x, size.y, fmt16);
+ else
+ error("Unable to find a suitable graphics format");
+
+ _runtime.reset(new Runtime(_system));
+
+ _runtime->loadCursors(exeName);
+
+ // Run the game
+ while (!shouldQuit()) {
+ handleEvents();
+
+ if (!_runtime->runFrame())
+ break;
+
+ _runtime->drawFrame();
+ _system->delayMillis(10);
+ }
+
+ _runtime.reset();
+
+ return Common::kNoError;
+}
+
+void VCruiseEngine::pauseEngineIntern(bool pause) {
+ Engine::pauseEngineIntern(pause);
+}
+
+bool VCruiseEngine::hasFeature(EngineFeature f) const {
+ switch (f) {
+ case kSupportsReturnToLauncher:
+ case kSupportsSavingDuringRuntime:
+ return true;
+ default:
+ return false;
+ };
+}
+
+Common::Error VCruiseEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
+ return Common::Error(Common::kUnknownError);
+}
+
+bool VCruiseEngine::canSaveAutosaveCurrently() {
+ return false;
+}
+
+bool VCruiseEngine::canSaveGameStateCurrently() {
+ return false;
+}
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/vcruise.h b/engines/vcruise/vcruise.h
new file mode 100644
index 00000000000..0aa35004c8a
--- /dev/null
+++ b/engines/vcruise/vcruise.h
@@ -0,0 +1,75 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_VCRUISE_H
+#define VCRUISE_VCRUISE_H
+
+#include "engines/engine.h"
+#include "common/rect.h"
+
+#include "vcruise/runtime.h"
+#include "vcruise/detection.h"
+
+/**
+ * This is the namespace of the V-Cruise engine.
+ *
+ * Status of this engine: ???
+ *
+ * Games using this engine:
+ * - Reah: Face the Unknown
+ * - Schizm: Mysterious Journey
+ */
+namespace VCruise {
+
+class VCruiseEngine : public ::Engine {
+protected:
+ // Engine APIs
+ Common::Error run() override;
+
+public:
+ VCruiseEngine(OSystem *syst, const VCruiseGameDescription *gameDesc);
+ ~VCruiseEngine() override;
+
+ bool hasFeature(EngineFeature f) const override;
+ //void syncSoundSettings() override;
+
+ const VCruiseGameDescription *_gameDescription;
+
+ Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave) override;
+ bool canSaveAutosaveCurrently() override;
+ bool canSaveGameStateCurrently() override;
+
+protected:
+ void pauseEngineIntern(bool pause) override;
+
+private:
+ void handleEvents();
+
+ Common::Rect _videoRect;
+ Common::Rect _menuBarRect;
+ Common::Rect _trayRect;
+
+ Common::SharedPtr<Runtime> _runtime;
+};
+
+} // End of namespace VCruise
+
+#endif /* VCRUISE_VCRUISE_H */
Commit: 9d179f101f32b771ed2680bf4cdaf14b4bb04603
https://github.com/scummvm/scummvm/commit/9d179f101f32b771ed2680bf4cdaf14b4bb04603
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Add script compiler and initial loading things
Changed paths:
A engines/vcruise/script.cpp
A engines/vcruise/script.h
A engines/vcruise/textparser.cpp
A engines/vcruise/textparser.h
engines/vcruise/module.mk
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/vcruise.cpp
engines/vcruise/vcruise.h
diff --git a/engines/vcruise/module.mk b/engines/vcruise/module.mk
index 63eeca9f809..026a3a98277 100644
--- a/engines/vcruise/module.mk
+++ b/engines/vcruise/module.mk
@@ -3,6 +3,8 @@ MODULE := engines/vcruise
MODULE_OBJS = \
metaengine.o \
runtime.o \
+ script.o \
+ textparser.o \
vcruise.o
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index d77b0771c5f..df09638a907 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -22,18 +22,29 @@
#include "common/formats/winexe.h"
#include "common/ptr.h"
#include "common/system.h"
+#include "common/stream.h"
#include "graphics/cursorman.h"
#include "graphics/wincursor.h"
#include "graphics/managed_surface.h"
#include "vcruise/runtime.h"
-
+#include "vcruise/script.h"
+#include "vcruise/textparser.h"
namespace VCruise {
-Runtime::Runtime(OSystem *system) : _system(system), _roomNumber(1), _gameState(kGameStateBoot) {
+AnimationDef::AnimationDef() : animNum(0), firstFrame(0), lastFrame(0) {
+}
+
+
+Runtime::Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
+ : _system(system), _roomNumber(1), _screenNumber(0), _loadedRoomNumber(0), _activeScreenNumber(0), _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false) {
+
+ _logDir = _rootFSNode.getChild("Log");
+ if (!_logDir.exists() || !_logDir.isDirectory())
+ error("Couldn't resolve Log directory");
}
Runtime::~Runtime() {
@@ -82,10 +93,13 @@ bool Runtime::runFrame() {
moreActions = false;
switch (_gameState) {
case kGameStateBoot:
- bootGame();
+ moreActions = bootGame();
break;
case kGameStateQuit:
return false;
+ case kGameStateRunning:
+ moreActions = runGame();
+ break;
default:
error("Unknown game state");
return false;
@@ -95,7 +109,244 @@ bool Runtime::runFrame() {
return true;
}
-void Runtime::bootGame() {
+bool Runtime::bootGame() {
+ debug(1, "Booting V-Cruise game...");
+ loadIndex();
+ debug(1, "Index loaded OK");
+
+ _gameState = kGameStateRunning;
+
+ if (_gameID == GID_REAH) {
+ // TODO: Change to the logo instead (0xb1) instead when menus are implemented
+ _roomNumber = 1;
+ _screenNumber = 0xb0;
+ _havePendingScreenChange = true;
+ } else
+ error("Couldn't figure out what screen to start on");
+
+ return true;
+}
+
+bool Runtime::runGame() {
+
+ if (_havePendingScreenChange) {
+ _havePendingScreenChange = false;
+
+ changeToScreen(_roomNumber, _screenNumber);
+ return true;
+ }
+
+ return false;
+}
+
+void Runtime::loadIndex() {
+ Common::FSNode indexFSNode = _logDir.getChild("Index.txt");
+
+ Common::ReadStream *stream = indexFSNode.createReadStream();
+ if (!stream)
+ error("Failed to open main index");
+
+ Common::String blamePath = indexFSNode.getPath();
+
+ TextParser parser(stream);
+
+ Common::String token;
+ TextParserState state;
+
+ static const IndexPrefixTypePair parsePrefixes[] = {
+ {"Room", kIndexParseTypeRoom},
+ {"RRoom", kIndexParseTypeRRoom},
+ {"YRoom", kIndexParseTypeYRoom},
+ {"VRoom", kIndexParseTypeVRoom},
+ {"TRoom", kIndexParseTypeTRoom},
+ {"CRoom", kIndexParseTypeCRoom},
+ {"SRoom", kIndexParseTypeSRoom},
+ };
+
+ IndexParseType indexParseType = kIndexParseTypeNone;
+ uint currentRoomNumber = 0;
+
+ for (;;) {
+ char firstCh = 0;
+ if (!parser.skipWhitespaceAndComments(firstCh, state))
+ break;
+
+ if (firstCh == '[') {
+ if (!parser.parseToken(token, state))
+ error("Index open bracket wasn't terminated");
+
+ if (token == "NameRoom") {
+ indexParseType = kIndexParseTypeNameRoom;
+ } else {
+ bool foundType = false;
+ uint prefixLen = 0;
+ for (const IndexPrefixTypePair &prefixTypePair : parsePrefixes) {
+ uint len = strlen(prefixTypePair.prefix);
+ if (token.size() > len && !memcmp(token.c_str(), prefixTypePair.prefix, len)) {
+ indexParseType = prefixTypePair.parseType;
+ foundType = true;
+ prefixLen = len;
+ break;
+ }
+ }
+
+ if (!foundType)
+ error("Unknown index heading type %s", token.c_str());
+
+ currentRoomNumber = 0;
+ for (uint i = prefixLen; i < token.size(); i++) {
+ char digit = token[i];
+ if (digit < '0' || digit > '9')
+ error("Malformed room def");
+ currentRoomNumber = currentRoomNumber * 10 + (token[i] - '0');
+ }
+ }
+
+ parser.expect("]", blamePath);
+
+ allocateRoomsUpTo(currentRoomNumber);
+ } else {
+ parser.requeue(&firstCh, 1, state);
+
+ if (!parseIndexDef(parser, indexParseType, currentRoomNumber, blamePath))
+ break;
+ }
+ }
+}
+
+void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
+ bool changedRoom = (roomNumber != _loadedRoomNumber);
+ bool changedScreen = (screenNumber != _activeScreenNumber) || changedRoom;
+
+ if (changedRoom) {
+ Common::String logFileName = Common::String::format("Room%02i.log", static_cast<int>(roomNumber));
+
+ Common::FSNode logFileNode = _logDir.getChild(logFileName);
+ if (Common::SeekableReadStream *logicFile = logFileNode.createReadStream())
+ _scriptSet = compileLogicFile(*logicFile, static_cast<uint>(logicFile->size()), logFileNode.getPath());
+ else
+ _scriptSet.reset();
+ }
+}
+
+bool Runtime::parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath) {
+ Common::String lineText;
+ parser.expectLine(lineText, blamePath, true);
+
+ Common::MemoryReadStream lineStream(reinterpret_cast<const byte *>(lineText.c_str()), lineText.size(), DisposeAfterUse::NO);
+ TextParser strParser(&lineStream);
+
+ switch (parseType) {
+ case kIndexParseTypeNameRoom: {
+ uint nameRoomNumber = 0;
+ Common::String name;
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectUInt(nameRoomNumber, blamePath);
+
+ allocateRoomsUpTo(nameRoomNumber);
+ _roomDefs[nameRoomNumber]->name = name;
+ } break;
+ case kIndexParseTypeRoom: {
+ Common::String name;
+
+ AnimationDef animDef;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectInt(animDef.animNum, blamePath);
+ strParser.expect(",", blamePath);
+ strParser.expectUInt(animDef.firstFrame, blamePath);
+ strParser.expect(",", blamePath);
+ strParser.expectUInt(animDef.lastFrame, blamePath);
+ _roomDefs[roomNumber]->animations[name] = animDef;
+ } break;
+ case kIndexParseTypeRRoom: {
+ Common::String name;
+
+ Common::Rect rect;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectShort(rect.left, blamePath);
+ strParser.expect(",", blamePath);
+ strParser.expectShort(rect.top, blamePath);
+ strParser.expect(",", blamePath);
+ strParser.expectShort(rect.right, blamePath);
+
+ // Line 4210 in Reah contains an animation def instead of a rect def, detect this and discard
+ if (!strParser.checkEOL()) {
+ strParser.expect(",", blamePath);
+ strParser.expectShort(rect.bottom, blamePath);
+
+ _roomDefs[roomNumber]->rects[name] = rect;
+ }
+
+ } break;
+ case kIndexParseTypeYRoom: {
+ Common::String name;
+
+ uint varSlot = 0;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectUInt(varSlot, blamePath);
+
+ _roomDefs[roomNumber]->vars[name] = varSlot;
+ } break;
+ case kIndexParseTypeVRoom: {
+ Common::String name;
+
+ int value = 0;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectInt(value, blamePath);
+
+ _roomDefs[roomNumber]->values[name] = value;
+ } break;
+ case kIndexParseTypeTRoom: {
+ Common::String name;
+ Common::String value;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectLine(value, blamePath, false);
+
+ _roomDefs[roomNumber]->texts[name] = value;
+ } break;
+ case kIndexParseTypeCRoom: {
+ Common::String name;
+ int value;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectInt(value, blamePath);
+
+ _roomDefs[roomNumber]->consts[name] = value;
+ } break;
+ case kIndexParseTypeSRoom: {
+ Common::String name;
+ int value;
+
+ strParser.expectToken(name, blamePath);
+ strParser.expect("=", blamePath);
+ strParser.expectInt(value, blamePath);
+
+ _roomDefs[roomNumber]->sounds[name] = value;
+ } break;
+ default:
+ assert(false);
+ return false;
+ }
+
+ return true;
+}
+
+void Runtime::allocateRoomsUpTo(uint roomNumber) {
+ while (_roomDefs.size() <= roomNumber) {
+ _roomDefs.push_back(Common::SharedPtr<RoomDef>(new RoomDef()));
+ }
}
void Runtime::drawFrame() {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index b6c653cb068..7928dfcaa6a 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -22,6 +22,10 @@
#ifndef VCRUISE_RUNTIME_H
#define VCRUISE_RUNTIME_H
+#include "common/hashmap.h"
+
+#include "vcruise/detection.h"
+
class OSystem;
namespace Graphics {
@@ -32,15 +36,39 @@ struct WinCursorGroup;
namespace VCruise {
+class TextParser;
+struct ScriptSet;
+
enum GameState {
kGameStateBoot,
kGameStateCinematic,
kGameStateQuit,
+ kGameStateRunning,
+};
+
+struct AnimationDef {
+ AnimationDef();
+
+ int animNum; // May be negative if reversed
+ uint firstFrame;
+ uint lastFrame; // Inclusive
+};
+
+struct RoomDef {
+ Common::HashMap<Common::String, AnimationDef> animations;
+ Common::HashMap<Common::String, Common::Rect> rects;
+ Common::HashMap<Common::String, uint> vars;
+ Common::HashMap<Common::String, int> values;
+ Common::HashMap<Common::String, Common::String> texts;
+ Common::HashMap<Common::String, int> consts;
+ Common::HashMap<Common::String, int> sounds;
+
+ Common::String name;
};
class Runtime {
public:
- explicit Runtime(OSystem *system);
+ Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
virtual ~Runtime();
void loadCursors(const char *exeName);
@@ -49,14 +77,50 @@ public:
void drawFrame();
private:
- void bootGame();
+ bool bootGame();
+ bool runGame();
+
+ void loadIndex();
+ void changeToScreen(uint roomNumber, uint screenNumber);
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort; // Cursors indexed as CURSOR_#
OSystem *_system;
- uint _roomNumber;
+ uint _roomNumber; // Room number can be changed independently of the loaded room, the screen doesn't change until a command changes it
+ uint _screenNumber;
+
+ uint _loadedRoomNumber;
+ uint _activeScreenNumber;
+ bool _havePendingScreenChange;
GameState _gameState;
+ VCruiseGameID _gameID;
+
+ Common::FSNode _rootFSNode;
+ Common::FSNode _logDir;
+
+ Common::Array<Common::SharedPtr<RoomDef> > _roomDefs;
+ Common::SharedPtr<ScriptSet> _scriptSet;
+
+ enum IndexParseType {
+ kIndexParseTypeNone,
+ kIndexParseTypeRoom,
+ kIndexParseTypeRRoom, // Rectangle room (constrains animation areas)
+ kIndexParseTypeYRoom, // Yes room (variable/ID mappings)
+ kIndexParseTypeVRoom, // Value room (value/ID mappings?)
+ kIndexParseTypeTRoom, // Text
+ kIndexParseTypeCRoom, // Const
+ kIndexParseTypeSRoom, // Sound
+ kIndexParseTypeNameRoom,
+ };
+
+ struct IndexPrefixTypePair {
+ const char *prefix;
+ IndexParseType parseType;
+ };
+
+ bool parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath);
+ void allocateRoomsUpTo(uint roomNumber);
};
} // End of namespace VCruise
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
new file mode 100644
index 00000000000..569e3c79f56
--- /dev/null
+++ b/engines/vcruise/script.cpp
@@ -0,0 +1,770 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/hash-str.h"
+
+#include "engines/vcruise/script.h"
+#include "engines/vcruise/textparser.h"
+
+
+namespace VCruise {
+
+class LogicUnscrambleStream : public Common::ReadStream {
+public:
+ LogicUnscrambleStream(Common::ReadStream *stream, uint streamSize);
+
+ bool eos() const override;
+ uint32 read(void *dataPtr, uint32 dataSize) override;
+
+private:
+ byte _cipher[255];
+ uint _cipherOffset;
+ Common::ReadStream *_stream;
+};
+
+LogicUnscrambleStream::LogicUnscrambleStream(Common::ReadStream *stream, uint streamSize) : _stream(stream) {
+ int key = 255;
+ for (int i = 0; i < 255; i++) {
+ int parityBit = ((key ^ (key >> 1) ^ (key >> 6) ^ (key >> 7))) & 1;
+ key = (key >> 1) | (parityBit << 7);
+ _cipher[254 - i] = key;
+ }
+
+ _cipherOffset = 255u - (streamSize % 255u);
+}
+
+bool LogicUnscrambleStream::eos() const {
+ return _stream->eos();
+}
+
+uint32 LogicUnscrambleStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 numRead = _stream->read(dataPtr, dataSize);
+
+ byte *decipher = static_cast<byte *>(dataPtr);
+
+ uint cipherOffset = _cipherOffset;
+
+ uint32 remaining = numRead;
+ while (remaining) {
+ if (cipherOffset == 255)
+ cipherOffset = 0;
+
+ (*decipher++) ^= _cipher[cipherOffset++];
+ remaining--;
+ }
+
+ _cipherOffset = cipherOffset;
+ return numRead;
+}
+
+Instruction::Instruction() : op(ScriptOps::kInvalid), arg(0) {
+}
+
+Instruction::Instruction(ScriptOps::ScriptOp paramOp) : op(paramOp), arg(0) {
+}
+
+Instruction::Instruction(ScriptOps::ScriptOp paramOp, int32 paramArg) : op(paramOp), arg(paramArg) {
+}
+
+enum ProtoOp {
+ kProtoOpScript, // Use script opcode
+
+ kProtoOpJumpToLabel,
+ kProtoOpLabel,
+
+ kProtoOpIf,
+ kProtoOpElse,
+ kProtoOpEndIf,
+ kProtoOpSwitch,
+ kProtoOpCase,
+ kProtoOpEndSwitch,
+ kProtoOpDefault,
+ kProtoOpBreak,
+};
+
+struct ProtoInstruction {
+ ProtoInstruction();
+ explicit ProtoInstruction(ScriptOps::ScriptOp op);
+ ProtoInstruction(ScriptOps::ScriptOp paramOp, int32 paramArg);
+ ProtoInstruction(ProtoOp paramProtoOp, ScriptOps::ScriptOp paramOp, int32 paramArg);
+
+ ProtoOp protoOp;
+ ScriptOps::ScriptOp op;
+ int32 arg;
+};
+
+ProtoInstruction::ProtoInstruction() : protoOp(kProtoOpScript), op(ScriptOps::kInvalid), arg(0) {
+}
+
+ProtoInstruction::ProtoInstruction(ScriptOps::ScriptOp paramOp) : protoOp(kProtoOpScript), op(paramOp), arg(0) {
+}
+
+ProtoInstruction::ProtoInstruction(ScriptOps::ScriptOp paramOp, int32 paramArg) : protoOp(kProtoOpScript), op(paramOp), arg(paramArg) {
+}
+
+ProtoInstruction::ProtoInstruction(ProtoOp paramProtoOp, ScriptOps::ScriptOp paramOp, int32 paramArg) : protoOp(paramProtoOp), op(paramOp), arg(paramArg) {
+}
+
+struct ProtoScript {
+ Common::Array<ProtoInstruction> instrs;
+
+ void reset();
+};
+
+void ProtoScript::reset() {
+ instrs.clear();
+}
+
+struct ScriptNamedInstruction {
+ const char *str;
+ ProtoOp protoOp;
+ ScriptOps::ScriptOp op;
+};
+
+class ScriptCompiler {
+public:
+ ScriptCompiler(TextParser &parser, const Common::String &blamePath);
+
+ void compileRoomScriptSet(ScriptSet *ss);
+
+private:
+ bool parseNumber(const Common::String &token, uint32 &outNumber) const;
+ static bool parseDecNumber(const Common::String &token, uint start, uint32 &outNumber);
+ static bool parseHexNumber(const Common::String &token, uint start, uint32 &outNumber);
+ void expectNumber(uint32 &outNumber);
+
+ void compileRoomScriptSet(RoomScriptSet *rss);
+ void compileScreenScriptSet(ScreenScriptSet *sss);
+ bool compileInstructionToken(ProtoScript &script, const Common::String &token);
+
+ void codeGenScript(ProtoScript &protoScript, Script &script);
+
+ uint indexString(const Common::String &str);
+
+ enum NumberParsingMode {
+ kNumberParsingDec,
+ kNumberParsingHex,
+ };
+
+ TextParser &_parser;
+ NumberParsingMode _numberParsingMode;
+ const Common::String _blamePath;
+
+ Common::HashMap<Common::String, uint> _stringToIndex;
+ Common::Array<Common::String> _strings;
+};
+
+ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath) : _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath) {
+}
+
+bool ScriptCompiler::parseNumber(const Common::String &token, uint32 &outNumber) const {
+ if (token.size() == 0)
+ return false;
+
+ if (token[0] == 'd')
+ return parseDecNumber(token, 1, outNumber);
+
+ if (token[0] == '0') {
+ switch (_numberParsingMode) {
+ case kNumberParsingDec:
+ return parseDecNumber(token, 0, outNumber);
+ case kNumberParsingHex:
+ return parseHexNumber(token, 0, outNumber);
+ default:
+ error("Unknown number parsing mode");
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool ScriptCompiler::parseDecNumber(const Common::String &token, uint start, uint32 &outNumber) {
+ if (start == token.size())
+ return false;
+
+ uint32 num = 0;
+ for (uint i = start; i < token.size(); i++) {
+ char c = token[i];
+ uint32 digit = 0;
+ if (c >= '0' && c <= '9')
+ digit = c - '0';
+ else
+ return false;
+
+ num = num * 10u + digit;
+ }
+
+ outNumber = num;
+ return true;
+}
+
+bool ScriptCompiler::parseHexNumber(const Common::String &token, uint start, uint32 &outNumber) {
+ if (start == token.size())
+ return false;
+
+ uint32 num = 0;
+ for (uint i = start; i < token.size(); i++) {
+ char c = token[i];
+ uint32 digit = 0;
+ if (c >= '0' && c <= '9')
+ digit = c - '0';
+ else if (c >= 'a' && c <= 'f')
+ digit = (c - 'a') + 0xa;
+ else
+ return false;
+
+ num = num * 16u + digit;
+ }
+
+ outNumber = num;
+ return true;
+}
+
+void ScriptCompiler::expectNumber(uint32 &outNumber) {
+ TextParserState state;
+ Common::String token;
+ if (_parser.parseToken(token, state)) {
+ if (!parseNumber(token, outNumber))
+ error("Error compiling script at line %i col %i: Expected number but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ } else {
+ error("Error compiling script at line %i col %i: Expected number", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+ }
+}
+
+void ScriptCompiler::compileRoomScriptSet(ScriptSet *ss) {
+ Common::SharedPtr<RoomScriptSet> roomScript;
+ uint roomID = 0;
+
+ TextParserState state;
+ Common::String token;
+ while (_parser.parseToken(token, state)) {
+ if (token == "~ROOM") {
+ if (roomScript)
+ error("Error compiling script at line %i col %i: Encountered ~ROOM without ~EROOM", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+ roomScript.reset(new RoomScriptSet());
+
+ uint32 roomNumber = 0;
+ expectNumber(roomNumber);
+
+ ss->roomScripts[roomNumber] = roomScript;
+
+ compileRoomScriptSet(roomScript.get());
+ } else {
+ error("Error compiling script at line %i col %i: Expected ~ROOM and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ }
+ }
+
+ ss->strings = Common::move(_strings);
+}
+
+void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
+ TextParserState state;
+ Common::String token;
+ while (_parser.parseToken(token, state)) {
+ if (token == "~EROOM") {
+ return;
+ } else if (token == "~SCR") {
+ uint32 screenNumber = 0;
+ expectNumber(screenNumber);
+
+ Common::SharedPtr<ScreenScriptSet> sss(new ScreenScriptSet());
+ compileScreenScriptSet(sss.get());
+
+ rss->screenScripts[screenNumber] = sss;
+ } else {
+ error("Error compiling script at line %i col %i: Expected ~EROOM or ~SCR and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ }
+ }
+
+ error("Error compiling script: Room wasn't terminated");
+}
+
+void ScriptCompiler::compileScreenScriptSet(ScreenScriptSet *sss) {
+ TextParserState state;
+ Common::String token;
+
+ ProtoScript protoScript;
+ Common::SharedPtr<Script> currentScript(new Script());
+
+ sss->entryScript.reset(currentScript);
+
+ while (_parser.parseToken(token, state)) {
+ if (token == "~EROOM" || token == "~SCR") {
+ _parser.requeue(token, state);
+
+ codeGenScript(protoScript, *currentScript);
+ return;
+ } else if (token == "~*") {
+ uint32 interactionNumber = 0;
+ expectNumber(interactionNumber);
+
+ codeGenScript(protoScript, *currentScript);
+
+ currentScript.reset(new Script());
+
+ sss->interactionScripts[interactionNumber] = currentScript;
+ } else if (compileInstructionToken(protoScript, token)) {
+ // Nothing
+ } else {
+ error("Error compiling script at line %i col %i: Expected ~EROOM or ~SCR or ~* or instruction but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ }
+ }
+}
+
+static ScriptNamedInstruction g_namedInstructions[] = {
+ {"rotate", ProtoOp::kProtoOpScript, ScriptOps::kRotate},
+ {"angle", ProtoOp::kProtoOpScript, ScriptOps::kAngle},
+ {"angleG@", ProtoOp::kProtoOpScript, ScriptOps::kAngleGGet},
+ {"speed", ProtoOp::kProtoOpScript, ScriptOps::kSpeed},
+ {"sanimL", ProtoOp::kProtoOpScript, ScriptOps::kSAnimL},
+ {"changeL", ProtoOp::kProtoOpScript, ScriptOps::kChangeL},
+ {"animR", ProtoOp::kProtoOpScript, ScriptOps::kAnimR},
+ {"animF", ProtoOp::kProtoOpScript, ScriptOps::kAnimF},
+ {"animN", ProtoOp::kProtoOpScript, ScriptOps::kAnimN},
+ {"animG", ProtoOp::kProtoOpScript, ScriptOps::kAnimG},
+ {"animS", ProtoOp::kProtoOpScript, ScriptOps::kAnimS},
+ {"anim", ProtoOp::kProtoOpScript, ScriptOps::kAnim},
+ {"static", ProtoOp::kProtoOpScript, ScriptOps::kStatic},
+ {"yes@", ProtoOp::kProtoOpScript, ScriptOps::kVarLoad},
+ {"yes!", ProtoOp::kProtoOpScript, ScriptOps::kVarStore},
+ {"cursor!", ProtoOp::kProtoOpScript, ScriptOps::kSetCursor},
+ {"room!", ProtoOp::kProtoOpScript, ScriptOps::kSetRoom},
+ {"lmb", ProtoOp::kProtoOpScript, ScriptOps::kLMB},
+ {"lmb1", ProtoOp::kProtoOpScript, ScriptOps::kLMB1},
+ {"volumeDn4", ProtoOp::kProtoOpScript, ScriptOps::kVolumeDn4},
+ {"volumeUp3", ProtoOp::kProtoOpScript, ScriptOps::kVolumeUp3},
+ {"rnd", ProtoOp::kProtoOpScript, ScriptOps::kRandom},
+ {"drop", ProtoOp::kProtoOpScript, ScriptOps::kDrop},
+ {"dup", ProtoOp::kProtoOpScript, ScriptOps::kDup},
+ {"say3", ProtoOp::kProtoOpScript, ScriptOps::kSay3},
+ {"setTimer", ProtoOp::kProtoOpScript, ScriptOps::kSetTimer},
+ {"lo!", ProtoOp::kProtoOpScript, ScriptOps::kLoSet},
+ {"lo@", ProtoOp::kProtoOpScript, ScriptOps::kLoGet},
+ {"hi!", ProtoOp::kProtoOpScript, ScriptOps::kHiSet},
+ {"hi@", ProtoOp::kProtoOpScript, ScriptOps::kHiGet},
+
+ {"and", ProtoOp::kProtoOpScript, ScriptOps::kAnd},
+ {"or", ProtoOp::kProtoOpScript, ScriptOps::kOr},
+ {"not", ProtoOp::kProtoOpScript, ScriptOps::kNot},
+ {"=", ProtoOp::kProtoOpScript, ScriptOps::kCmpEq},
+
+ {"bit@", ProtoOp::kProtoOpScript, ScriptOps::kBitLoad},
+ {"bit0!", ProtoOp::kProtoOpScript, ScriptOps::kBitSet0},
+ {"bit1!", ProtoOp::kProtoOpScript, ScriptOps::kBitSet1},
+
+ {"soundS1", ProtoOp::kProtoOpScript, ScriptOps::kSoundS1},
+ {"soundL2", ProtoOp::kProtoOpScript, ScriptOps::kSoundL2},
+ {"music", ProtoOp::kProtoOpScript, ScriptOps::kMusic},
+ {"musicUp", ProtoOp::kProtoOpScript, ScriptOps::kMusicUp},
+
+ {"parm1", ProtoOp::kProtoOpScript, ScriptOps::kParm1},
+ {"parm2", ProtoOp::kProtoOpScript, ScriptOps::kParm2},
+ {"parm3", ProtoOp::kProtoOpScript, ScriptOps::kParm3},
+ {"parmG", ProtoOp::kProtoOpScript, ScriptOps::kParmG},
+
+ {"disc1", ProtoOp::kProtoOpScript, ScriptOps::kDisc1},
+ {"disc2", ProtoOp::kProtoOpScript, ScriptOps::kDisc2},
+ {"disc3", ProtoOp::kProtoOpScript, ScriptOps::kDisc3},
+
+ {"#if", ProtoOp::kProtoOpIf, ScriptOps::kInvalid},
+ {"#eif", ProtoOp::kProtoOpEndIf, ScriptOps::kInvalid},
+ {"#else", ProtoOp::kProtoOpElse, ScriptOps::kInvalid},
+
+ {"#switch:", ProtoOp::kProtoOpSwitch, ScriptOps::kInvalid},
+ {"#eswitch", ProtoOp::kProtoOpEndSwitch, ScriptOps::kInvalid},
+ {"break", ProtoOp::kProtoOpBreak, ScriptOps::kInvalid},
+ {"#default", ProtoOp::kProtoOpDefault, ScriptOps::kInvalid},
+
+ {"esc_on", ProtoOp::kProtoOpScript, ScriptOps::kEscOn},
+ {"esc_off", ProtoOp::kProtoOpScript, ScriptOps::kEscOff},
+ {"esc_get@", ProtoOp::kProtoOpScript, ScriptOps::kEscGet},
+ {"backStart", ProtoOp::kProtoOpScript, ScriptOps::kBackStart},
+};
+
+bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::String &token) {
+ uint32 number = 0;
+ if (parseNumber(token, number)) {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, number));
+ return true;
+ }
+
+ if (token.size() >= 1 && token[0] == ':') {
+ if (token.size() >= 3 && token[2] == ':') {
+ if (token[1] == 'Y') {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kVarName, indexString(token.substr(3))));
+ return true;
+ } else if (token[1] == 'V') {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kValueName, indexString(token.substr(3))));
+ return true;
+ } else
+ return false;
+ }
+
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kAnimName, indexString(token.substr(1))));
+ return true;
+ }
+
+ if (token.size() >= 2 && token[0] == '_') {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kSoundName, indexString(token.substr(1))));
+ return true;
+ }
+
+ if (token.size() >= 5 && token[0] == 'C' && token[1] == 'U' && token[2] == 'R' && token[3] == '_') {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
+ return true;
+ }
+
+ if (token == "#switch") {
+ _parser.expect(":", _blamePath);
+ script.instrs.push_back(ProtoInstruction(kProtoOpSwitch, ScriptOps::kInvalid, 0));
+ return true;
+ }
+
+ if (token == "#case") {
+ uint32 caseNumber = 0;
+ _parser.expect(":", _blamePath);
+ expectNumber(caseNumber);
+
+ script.instrs.push_back(ProtoInstruction(kProtoOpCase, ScriptOps::kInvalid, caseNumber));
+ return true;
+ }
+
+ if (token == "#case:") {
+ uint32 caseNumber = 0;
+ expectNumber(caseNumber);
+
+ script.instrs.push_back(ProtoInstruction(kProtoOpCase, ScriptOps::kInvalid, caseNumber));
+ return true;
+ }
+
+ for (const ScriptNamedInstruction &namedInstr : g_namedInstructions) {
+ if (token == namedInstr.str) {
+ script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, 0));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+enum CodeGenFlowControlBlockType {
+ kFlowControlInvalid,
+
+ kFlowControlIf,
+ kFlowControlSwitch,
+};
+
+struct CodeGenControlFlowBlock {
+ CodeGenControlFlowBlock();
+
+ CodeGenFlowControlBlockType type;
+ uint index;
+};
+
+CodeGenControlFlowBlock::CodeGenControlFlowBlock() : type(kFlowControlInvalid), index(0) {
+}
+
+struct CodeGenSwitchCase {
+ CodeGenSwitchCase();
+
+ int32 value;
+ uint label;
+};
+
+CodeGenSwitchCase::CodeGenSwitchCase() : value(0), label(0) {
+}
+
+struct CodeGenSwitch {
+ CodeGenSwitch();
+
+ Common::Array<CodeGenSwitchCase> cases;
+
+ uint endLabel;
+
+ uint defaultLabel;
+ bool hasDefault;
+};
+
+CodeGenSwitch::CodeGenSwitch() : defaultLabel(0), endLabel(0), hasDefault(false) {
+}
+
+struct CodeGenIf {
+ CodeGenIf();
+
+ uint endLabel;
+
+ uint elseLabel;
+ bool hasElse;
+};
+
+CodeGenIf::CodeGenIf() : endLabel(0), elseLabel(0), hasElse(false) {
+}
+
+
+void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
+ Common::Array<ProtoInstruction> instrs;
+ Common::Array<CodeGenSwitch> switches;
+ Common::Array<CodeGenIf> ifs;
+ Common::Array<CodeGenControlFlowBlock> controlFlowStack;
+
+ int32 nextLabel = 0;
+
+ // Pass 1: Convert all flow control constructs into references to the flow control construct.
+ // This changes Switch, If, and Break proto-ops to point to the corresponding if or switch index
+ for (const ProtoInstruction &instr : protoScript.instrs) {
+ switch (instr.protoOp) {
+ case kProtoOpScript:
+ instrs.push_back(instr);
+ break;
+ case kProtoOpBreak: {
+ bool found = false;
+ for (uint ri = 0; ri < controlFlowStack.size(); ri++) {
+ const CodeGenControlFlowBlock &cf = controlFlowStack[controlFlowStack.size() - 1 - ri];
+ if (cf.type == kFlowControlSwitch) {
+ found = true;
+ instrs.push_back(ProtoInstruction(kProtoOpBreak, ScriptOps::kInvalid, cf.index));
+ break;
+ }
+ }
+
+ if (!found)
+ error("Error in codegen: break statement outside of a switch case");
+ } break;
+ case kProtoOpIf: {
+ CodeGenControlFlowBlock cf;
+ cf.type = kFlowControlIf;
+ cf.index = ifs.size();
+ controlFlowStack.push_back(cf);
+
+ CodeGenIf ifBlock;
+ ifBlock.endLabel = nextLabel++;
+ ifs.push_back(ifBlock);
+
+ instrs.push_back(ProtoInstruction(kProtoOpIf, ScriptOps::kInvalid, cf.index));
+ } break;
+ case kProtoOpElse: {
+ if (controlFlowStack.size() == 0)
+ error("Error in codegen: #else outside of control flow");
+
+ CodeGenControlFlowBlock &cf = controlFlowStack[controlFlowStack.size() - 1];
+
+ if (cf.type != kFlowControlIf)
+ error("Error in codegen: #else inside wrong control block type");
+
+ CodeGenIf &ifBlock = ifs[cf.index];
+
+ if (ifBlock.hasElse)
+ error("Error in codegen: #else already set for #if block");
+
+ ifBlock.hasElse = true;
+ ifBlock.elseLabel = nextLabel++;
+
+ instrs.push_back(ProtoInstruction(kProtoOpJumpToLabel, ScriptOps::kInvalid, ifBlock.endLabel));
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, ifBlock.elseLabel));
+ } break;
+ case kProtoOpEndIf: {
+ if (controlFlowStack.size() == 0)
+ error("Error in codegen: #eif outside of control flow");
+
+ CodeGenControlFlowBlock &cf = controlFlowStack[controlFlowStack.size() - 1];
+
+ if (cf.type != kFlowControlIf)
+ error("Error in codegen: #eif inside wrong control block type");
+
+ const CodeGenIf &ifBlock = ifs[cf.index];
+
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, ifBlock.endLabel));
+
+ controlFlowStack.pop_back();
+ } break;
+ case kProtoOpSwitch: {
+ CodeGenControlFlowBlock cf;
+ cf.type = kFlowControlSwitch;
+ cf.index = switches.size();
+ controlFlowStack.push_back(cf);
+
+ CodeGenSwitch switchBlock;
+ switchBlock.endLabel = nextLabel++;
+ switches.push_back(switchBlock);
+
+ instrs.push_back(ProtoInstruction(kProtoOpSwitch, ScriptOps::kInvalid, cf.index));
+ } break;
+ case kProtoOpCase: {
+ if (controlFlowStack.size() == 0)
+ error("Error in codegen: #case outside of control flow");
+
+ CodeGenControlFlowBlock &cf = controlFlowStack[controlFlowStack.size() - 1];
+
+ if (cf.type != kFlowControlSwitch)
+ error("Error in codegen: #case inside wrong control block type");
+
+ CodeGenSwitch &switchBlock = switches[cf.index];
+
+ CodeGenSwitchCase caseDef;
+ caseDef.label = nextLabel++;
+ caseDef.value = instr.arg;
+
+ switchBlock.cases.push_back(caseDef);
+
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, caseDef.label));
+ } break;
+ case kProtoOpDefault: {
+ if (controlFlowStack.size() == 0)
+ error("Error in codegen: #case outside of control flow");
+
+ CodeGenControlFlowBlock &cf = controlFlowStack[controlFlowStack.size() - 1];
+
+ if (cf.type != kFlowControlSwitch)
+ error("Error in codegen: #case inside wrong control block type");
+
+ CodeGenSwitch &switchBlock = switches[cf.index];
+
+ if (switchBlock.hasDefault)
+ error("Error in codegen: #switch already has a default");
+
+ switchBlock.hasDefault = true;
+ switchBlock.defaultLabel = nextLabel++;
+
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, switchBlock.defaultLabel));
+ } break;
+ case kProtoOpEndSwitch: {
+ if (controlFlowStack.size() == 0)
+ error("Error in codegen: #eswitch outside of control flow");
+
+ CodeGenControlFlowBlock &cf = controlFlowStack[controlFlowStack.size() - 1];
+
+ if (cf.type != kFlowControlSwitch)
+ error("Error in codegen: #eswitch inside wrong control block type");
+
+ const CodeGenSwitch &switchBlock = switches[cf.index];
+
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, switchBlock.endLabel));
+
+ controlFlowStack.pop_back();
+ } break;
+ default:
+ error("Internal error: Unhandled proto-op");
+ break;
+ }
+ }
+
+ if (controlFlowStack.size() > 0)
+ error("Error in codegen: Unterminated flow control construct");
+
+ Common::Array<ProtoInstruction> instrs2;
+
+ Common::HashMap<uint, uint> labelToInstr;
+
+ // Pass 2: Convert CF block references into label-targeting ops
+ // This eliminates If, Switch, and Break ops
+ for (const ProtoInstruction &instr : instrs) {
+ switch (instr.protoOp) {
+ case kProtoOpScript:
+ case kProtoOpJumpToLabel:
+ instrs2.push_back(instr);
+ break;
+ case kProtoOpIf: {
+ const CodeGenIf &ifBlock = ifs[instr.arg];
+
+ instrs2.push_back(ProtoInstruction(ScriptOps::kCheckValue, 0));
+ instrs2.push_back(ProtoInstruction(kProtoOpJumpToLabel, ScriptOps::kInvalid, ifBlock.hasElse ? ifBlock.elseLabel : ifBlock.endLabel));
+ instrs2.push_back(ProtoInstruction(ScriptOps::kDrop));
+ } break;
+ case kProtoOpSwitch: {
+ const CodeGenSwitch &switchBlock = switches[instr.arg];
+
+ for (const CodeGenSwitchCase &caseDef : switchBlock.cases) {
+ instrs2.push_back(ProtoInstruction(ScriptOps::kCheckValue, caseDef.value));
+ instrs2.push_back(ProtoInstruction(kProtoOpJumpToLabel, ScriptOps::kInvalid, caseDef.value));
+ }
+
+ instrs2.push_back(ProtoInstruction(ScriptOps::kDrop));
+ instrs2.push_back(ProtoInstruction(kProtoOpJumpToLabel, ScriptOps::kInvalid, switchBlock.hasDefault ? switchBlock.defaultLabel : switchBlock.endLabel));
+ } break;
+ case kProtoOpLabel:
+ labelToInstr[static_cast<uint>(instr.arg)] = instrs2.size();
+ break;
+ case kProtoOpBreak: {
+ const CodeGenSwitch &switchBlock = switches[instr.arg];
+
+ instrs2.push_back(ProtoInstruction(kProtoOpJumpToLabel, ScriptOps::kInvalid, switchBlock.endLabel));
+ } break;
+ default:
+ error("Internal error: Unhandled proto-op");
+ break;
+ }
+ }
+
+ instrs.clear();
+
+ // Pass 3: Resolve labels and write out finished instructions
+ script.instrs.reserve(instrs2.size());
+
+ for (const ProtoInstruction &instr : instrs2) {
+ switch (instr.protoOp) {
+ case kProtoOpScript:
+ script.instrs.push_back(Instruction(instr.op, instr.arg));
+ break;
+ case kProtoOpJumpToLabel: {
+ Common::HashMap<uint, uint>::const_iterator it = labelToInstr.find(static_cast<uint>(instr.arg));
+ if (it == labelToInstr.end())
+ error("Internal error: Unmatched label");
+
+ script.instrs.push_back(Instruction(ScriptOps::kJump, it->_value));
+ } break;
+ default:
+ error("Internal error: Unhandled proto-op");
+ break;
+ }
+ }
+}
+
+uint ScriptCompiler::indexString(const Common::String &str) {
+ Common::HashMap<Common::String, uint>::const_iterator it = _stringToIndex.find(str);
+ if (it == _stringToIndex.end()) {
+ uint index = _strings.size();
+ _stringToIndex[str] = index;
+ _strings.push_back(str);
+ return index;
+ }
+
+ return it->_value;
+}
+
+Common::SharedPtr<ScriptSet> compileLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath) {
+
+ uint cipherOffset = 255u - (streamSize % 255u);
+
+ LogicUnscrambleStream unscrambleStream(&stream, streamSize);
+ TextParser parser(&unscrambleStream);
+
+ Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
+
+ ScriptCompiler compiler(parser, blamePath);
+
+ compiler.compileRoomScriptSet(scriptSet.get());
+
+ return scriptSet;
+}
+
+} // namespace VCruise
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
new file mode 100644
index 00000000000..afec75d6c55
--- /dev/null
+++ b/engines/vcruise/script.h
@@ -0,0 +1,145 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_SCRIPT_H
+#define VCRUISE_SCRIPT_H
+
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/ptr.h"
+#include "common/types.h"
+
+namespace Common {
+
+class ReadStream;
+
+} // End of namespace Common
+
+namespace VCruise {
+
+namespace ScriptOps {
+
+enum ScriptOp {
+ kInvalid,
+
+ kNumber,
+
+ kRotate,
+ kAngle,
+ kAngleGGet,
+ kSpeed,
+ kSAnimL,
+ kChangeL,
+ kAnimR,
+ kAnimF,
+ kAnimN,
+ kAnimG,
+ kAnimS,
+ kAnim,
+ kStatic,
+ kVarLoad,
+ kVarStore,
+ kSetCursor,
+ kSetRoom,
+ kLMB,
+ kLMB1,
+ kSoundS1,
+ kSoundL2,
+ kMusic,
+ kMusicUp,
+ kParm1,
+ kParm2,
+ kParm3,
+ kParmG,
+ kVolumeDn4,
+ kVolumeUp3,
+ kRandom,
+ kDrop,
+ kDup,
+ kSay3,
+ kSetTimer,
+ kLoSet,
+ kLoGet,
+ kHiSet,
+ kHiGet,
+
+ kNot,
+ kAnd,
+ kOr,
+ kCmpEq,
+
+ kBitLoad,
+ kBitSet0,
+ kBitSet1,
+
+ kDisc1,
+ kDisc2,
+ kDisc3,
+
+ kEscOn,
+ kEscOff,
+ kEscGet,
+ kBackStart,
+
+ kAnimName,
+ kValueName,
+ kVarName,
+ kSoundName,
+ kCursorName,
+
+ kCheckValue, // Check if stack top is equal to arg. If it is, pop the argument, otherwise leave it on the stack and skip the next instruction.
+ kJump, // Offset instruction index by arg.
+};
+
+} // End of namespace ScriptOps
+
+struct Instruction {
+ Instruction();
+ explicit Instruction(ScriptOps::ScriptOp paramOp);
+ Instruction(ScriptOps::ScriptOp paramOp, int32 paramArg);
+
+ ScriptOps::ScriptOp op;
+ int32 arg;
+};
+
+struct Script {
+ Common::Array<Instruction> instrs;
+};
+
+struct ScreenScriptSet {
+ Common::SharedPtr<Script> entryScript;
+ Common::HashMap<uint, Common::SharedPtr<Script> > interactionScripts;
+};
+
+struct RoomScriptSet {
+ Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> > screenScripts;
+};
+
+struct ScriptSet {
+ Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> > roomScripts;
+ Common::Array<Common::String> strings;
+};
+
+Common::SharedPtr<ScriptSet> compileLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath);
+
+}
+
+#endif
diff --git a/engines/vcruise/textparser.cpp b/engines/vcruise/textparser.cpp
new file mode 100644
index 00000000000..116b06dbee2
--- /dev/null
+++ b/engines/vcruise/textparser.cpp
@@ -0,0 +1,322 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "vcruise/textparser.h"
+
+namespace VCruise {
+
+TextParserState::TextParserState() : _lineNum(1), _col(1), _prevWasCR(false), _isParsingComment(false) {
+}
+
+TextParser::TextParser(Common::ReadStream *stream) : _stream(stream), _readBufferPos(0), _readBufferEnd(0), _returnBufferPos(kReturnBufferSize) {
+ memset(_readBuffer, 0, kReadBufferSize);
+ memset(_returnedBuffer, 0, kReturnBufferSize);
+}
+
+bool TextParser::readOneChar(char &outC, TextParserState &outState) {
+ if (_returnBufferPos == kReturnBufferSize) {
+ if (_readBufferPos == _readBufferEnd) {
+ if (_stream->eos())
+ return false;
+
+ _readBufferPos = 0;
+ _readBufferEnd = _stream->read(_readBuffer, kReadBufferSize);
+ if (_readBufferEnd == 0)
+ return false;
+ }
+ }
+
+ char c = 0;
+
+ if (_returnBufferPos != kReturnBufferSize) {
+ c = _returnedBuffer[_returnBufferPos++];
+ } else {
+ c = _readBuffer[_readBufferPos++];
+ }
+
+ TextParserState prevState = _state;
+
+ if (c == '\r') {
+ _state._lineNum++;
+ _state._col = 1;
+ _state._isParsingComment = false;
+ _state._prevWasCR = true;
+ } else if (c == '\n') {
+ if (!_state._prevWasCR) {
+ _state._lineNum++;
+ _state._col = 1;
+ }
+ _state._prevWasCR = false;
+ } else {
+ _state._col++;
+ _state._prevWasCR = false;
+
+ if (c == ';')
+ _state._isParsingComment = true;
+ }
+
+
+ outC = c;
+ outState = prevState;
+
+ return true;
+}
+
+bool TextParser::skipWhitespaceAndComments(char &outC, TextParserState &outState) {
+ char c = 0;
+ TextParserState firstCharState;
+
+ while (readOneChar(c, firstCharState)) {
+ if (isWhitespace(c))
+ continue;
+
+ if (_state._isParsingComment)
+ continue;
+
+ outC = c;
+ outState = firstCharState;
+ return true;
+ }
+
+ return false;
+}
+
+bool TextParser::isDelimiter(char c) {
+ if (c == ',' || c == '=' || c == '[' || c == ']')
+ return true;
+
+ return false;
+}
+
+bool TextParser::isWhitespace(char c) {
+ return (c == ' ') || ((c & 0xe0) == 0);
+}
+
+void TextParser::requeue(const char *chars, uint numChars, const TextParserState &state) {
+ _state = state;
+ assert(_returnBufferPos >= numChars);
+ _returnBufferPos -= numChars;
+ memcpy(_returnedBuffer + _returnBufferPos, chars, numChars);
+}
+
+void TextParser::requeue(const Common::String &str, const TextParserState &state) {
+ requeue(str.c_str(), str.size(), state);
+}
+
+void TextParser::expectToken(Common::String &outToken, const Common::String &blamePath) {
+ TextParserState state;
+ expectTokenInternal(outToken, blamePath, state);
+}
+
+void TextParser::expectShort(int16 &outInt, const Common::String &blamePath) {
+ int i;
+ expectInt(i, blamePath);
+ outInt = static_cast<int16>(i);
+}
+
+void TextParser::expectInt(int &outInt, const Common::String &blamePath) {
+ Common::String token;
+ TextParserState state;
+ expectTokenInternal(token, blamePath, state);
+
+ int result = 0;
+ bool isNegative = false;
+ uint startIndex = 0;
+ if (token[0] == '-') {
+ if (token.size() == 1)
+ error("Parsing error in '%s' at line %i col %i: Signed integer was malformed", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ isNegative = true;
+ startIndex = 1;
+ }
+
+ int base = 10;
+ bool isHex = (token.size() >= (startIndex) + 3 && token[startIndex] == '0' && token[startIndex + 1] == 'x');
+ if (isHex) {
+ startIndex += 2;
+ base = 16;
+ }
+
+ for (uint i = startIndex; i < token.size(); i++) {
+ char c = token[i];
+ int digit = 0;
+ if (c >= '0' && c <= '9')
+ digit = (c - '0');
+ else if (isHex && (c >= 'a' && c <= 'f'))
+ digit = (c - 'a') + 0xa;
+ else if (isHex && (c >= 'A' && c <= 'F'))
+ digit = (c - 'A') + 0xa;
+ else
+ error("Parsing error in '%s' at line %i col %i: Integer contained non-digits", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ if (isNegative)
+ digit = -digit;
+
+ result = result * base + digit;
+ }
+
+ outInt = result;
+}
+
+void TextParser::expectUInt(uint &outUInt, const Common::String &blamePath) {
+ Common::String token;
+ TextParserState state;
+ expectTokenInternal(token, blamePath, state);
+
+ uint result = 0;
+
+ for (uint i = 0; i < token.size(); i++) {
+ char c = token[i];
+ if (c < '0' || c > '9')
+ error("Parsing error in '%s' at line %i col %i: Integer contained non-digits", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ uint additional = c - '0';
+
+ result = result * 10 + additional;
+ }
+
+ outUInt = result;
+}
+
+void TextParser::expectLine(Common::String &outToken, const Common::String &blamePath, bool continueToNextLine) {
+ outToken.clear();
+
+ char c = 0;
+ TextParserState state;
+
+ bool isSkippingWhitespace = true;
+ uint nonWhitespaceLength = 0;
+
+ while (readOneChar(c, state)) {
+ if (c == '\r' || c == '\n' || _state._isParsingComment) {
+ requeue(&c, 1, state);
+ if (continueToNextLine)
+ skipToEOL();
+ break;
+ }
+
+ bool cIsWhitespace = isWhitespace(c);
+ if (isSkippingWhitespace) {
+ if (cIsWhitespace)
+ continue;
+ isSkippingWhitespace = false;
+ }
+
+ outToken += c;
+ if (!cIsWhitespace)
+ nonWhitespaceLength = outToken.size();
+ }
+
+ if (nonWhitespaceLength != outToken.size())
+ outToken = outToken.substr(0, nonWhitespaceLength);
+}
+
+void TextParser::expect(const char *str, const Common::String &blamePath) {
+ Common::String token;
+ TextParserState state;
+ expectTokenInternal(token, blamePath, state);
+
+ if (token != str)
+ error("Parsing error in '%s' at line %i col %i: Expected token '%s' but found '%s'", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col), str, token.c_str());
+}
+
+void TextParser::skipToEOL() {
+ char c = 0;
+ TextParserState state;
+
+ while (readOneChar(c, state)) {
+ if (c == '\n')
+ return;
+
+ if (c == '\r') {
+ if (readOneChar(c, state)) {
+ if (c != '\n')
+ requeue(&c, 1, state);
+ return;
+ }
+ }
+ }
+}
+
+bool TextParser::checkEOL() {
+ char c = 0;
+ TextParserState state;
+
+ for (;;) {
+ if (!readOneChar(c, state))
+ return true;
+
+ if (_state._isParsingComment || c == '\n' || c == '\r') {
+ // EOL or comment
+ requeue(&c, 1, state);
+ return true;
+ }
+
+ if (!isWhitespace(c)) {
+ // Non-whitespace
+ requeue(&c, 1, state);
+ return false;
+ }
+ }
+}
+
+void TextParser::expectTokenInternal(Common::String &outToken, const Common::String &blamePath, TextParserState &outState) {
+ if (!parseToken(outToken, outState))
+ error("Parsing error in '%s' unexpected end of file", blamePath.c_str());
+}
+
+bool TextParser::parseToken(Common::String &outString, TextParserState &outState) {
+ outString.clear();
+
+ char c = 0;
+ TextParserState state;
+
+ if (!skipWhitespaceAndComments(c, state))
+ return false;
+
+ outState = state;
+
+ outString += c;
+
+ if (isDelimiter(c))
+ return true;
+
+ while (readOneChar(c, state)) {
+ if (isWhitespace(c) || _state._isParsingComment) {
+ requeue(&c, 1, state);
+ return true;
+ }
+
+ if (isDelimiter(c)) {
+ requeue(&c, 1, state);
+ return true;
+ }
+
+ outString += c;
+ }
+
+ return true;
+}
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/textparser.h b/engines/vcruise/textparser.h
new file mode 100644
index 00000000000..0c3d92f6269
--- /dev/null
+++ b/engines/vcruise/textparser.h
@@ -0,0 +1,91 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_TEXTPARSER_H
+#define VCRUISE_TEXTPARSER_H
+
+#include "common/str.h"
+#include "common/memstream.h"
+
+namespace Common {
+
+class ReadStream;
+
+} // End of namespace Common
+
+namespace VCruise {
+
+struct TextParserState {
+ TextParserState();
+
+ uint _lineNum;
+ uint _col;
+ bool _prevWasCR;
+ bool _isParsingComment;
+};
+
+class TextParser {
+public:
+ explicit TextParser(Common::ReadStream *stream);
+
+ bool parseToken(Common::String &outString, TextParserState &outState);
+
+ bool readOneChar(char &outC, TextParserState &outState);
+ bool skipWhitespaceAndComments(char &outC, TextParserState &outState);
+
+ void requeue(const char *chars, uint numChars, const TextParserState &state);
+ void requeue(const Common::String &str, const TextParserState &state);
+
+ void expectToken(Common::String &outToken, const Common::String &blamePath);
+ void expectShort(int16 &outInt, const Common::String &blamePath);
+ void expectInt(int &outInt, const Common::String &blamePath);
+ void expectUInt(uint &outUInt, const Common::String &blamePath);
+ void expectLine(Common::String &outToken, const Common::String &blamePath, bool continueToNextLine);
+
+ void expect(const char *str, const Common::String &blamePath);
+
+ void skipToEOL();
+ bool checkEOL();
+
+private:
+ void expectTokenInternal(Common::String &outToken, const Common::String &blamePath, TextParserState &outState);
+
+ static bool isDelimiter(char c);
+ static bool isWhitespace(char c);
+
+ TextParserState _state;
+
+ Common::ReadStream *_stream;
+
+ static const uint kReadBufferSize = 4096;
+ static const uint kReturnBufferSize = 8;
+
+ char _returnedBuffer[kReturnBufferSize];
+ uint _returnBufferPos;
+
+ char _readBuffer[kReadBufferSize];
+ uint _readBufferPos;
+ uint _readBufferEnd;
+};
+
+} // End of namespace VCruise
+
+#endif
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index c084988b570..a3193de72d0 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -108,7 +108,7 @@ Common::Error VCruiseEngine::run() {
else
error("Unable to find a suitable graphics format");
- _runtime.reset(new Runtime(_system));
+ _runtime.reset(new Runtime(_system, _rootFSNode, _gameDescription->gameID));
_runtime->loadCursors(exeName);
@@ -154,4 +154,10 @@ bool VCruiseEngine::canSaveGameStateCurrently() {
return false;
}
+void VCruiseEngine::initializePath(const Common::FSNode &gamePath) {
+ Engine::initializePath(gamePath);
+
+ _rootFSNode = gamePath;
+}
+
} // End of namespace VCruise
diff --git a/engines/vcruise/vcruise.h b/engines/vcruise/vcruise.h
index 0aa35004c8a..b5551c5e169 100644
--- a/engines/vcruise/vcruise.h
+++ b/engines/vcruise/vcruise.h
@@ -57,6 +57,8 @@ public:
bool canSaveAutosaveCurrently() override;
bool canSaveGameStateCurrently() override;
+ void initializePath(const Common::FSNode &gamePath) override;
+
protected:
void pauseEngineIntern(bool pause) override;
@@ -67,6 +69,8 @@ private:
Common::Rect _menuBarRect;
Common::Rect _trayRect;
+ Common::FSNode _rootFSNode;
+
Common::SharedPtr<Runtime> _runtime;
};
Commit: e3360047a133bc4946d2ad52851915d3cac42616
https://github.com/scummvm/scummvm/commit/e3360047a133bc4946d2ad52851915d3cac42616
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Add map loader
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index df09638a907..26540f8271e 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -38,6 +38,26 @@ namespace VCruise {
AnimationDef::AnimationDef() : animNum(0), firstFrame(0), lastFrame(0) {
}
+InteractionDef::InteractionDef() : objectType(0), interactionID(0) {
+}
+
+void MapDef::clear() {
+ for (uint screen = 0; screen < kNumScreens; screen++)
+ for (uint direction = 0; direction < kNumDirections; direction++)
+ screenDirections[screen][direction].reset();
+}
+
+const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direction) {
+ if (screen < kFirstScreen)
+ return nullptr;
+
+ screen -= kFirstScreen;
+
+ if (screen >= kNumScreens)
+ return nullptr;
+
+ return screenDirections[screen][direction].get();
+}
Runtime::Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _roomNumber(1), _screenNumber(0), _loadedRoomNumber(0), _activeScreenNumber(0), _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false) {
@@ -45,6 +65,10 @@ Runtime::Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameI
_logDir = _rootFSNode.getChild("Log");
if (!_logDir.exists() || !_logDir.isDirectory())
error("Couldn't resolve Log directory");
+
+ _mapDir = _rootFSNode.getChild("Map");
+ if (!_mapDir.exists() || !_mapDir.isDirectory())
+ error("Couldn't resolve Map directory");
}
Runtime::~Runtime() {
@@ -219,13 +243,73 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
bool changedScreen = (screenNumber != _activeScreenNumber) || changedRoom;
if (changedRoom) {
- Common::String logFileName = Common::String::format("Room%02i.log", static_cast<int>(roomNumber));
+ _scriptSet.reset();
+ Common::String logFileName = Common::String::format("Room%02i.log", static_cast<int>(roomNumber));
Common::FSNode logFileNode = _logDir.getChild(logFileName);
- if (Common::SeekableReadStream *logicFile = logFileNode.createReadStream())
- _scriptSet = compileLogicFile(*logicFile, static_cast<uint>(logicFile->size()), logFileNode.getPath());
- else
- _scriptSet.reset();
+ if (logFileNode.exists()) {
+ if (Common::SeekableReadStream *logicFile = logFileNode.createReadStream()) {
+ _scriptSet = compileLogicFile(*logicFile, static_cast<uint>(logicFile->size()), logFileNode.getPath());
+ delete logicFile;
+ }
+ }
+
+ _map.clear();
+
+ Common::String mapFileName = Common::String::format("Room%02i.map", static_cast<int>(roomNumber));
+ Common::FSNode mapFileNode = _mapDir.getChild(mapFileName);
+ if (mapFileNode.exists()) {
+ if (Common::SeekableReadStream *mapFile = mapFileNode.createReadStream()) {
+ loadMap(mapFile);
+ delete mapFile;
+ }
+ }
+ }
+}
+
+void Runtime::loadMap(Common::SeekableReadStream *stream) {
+ byte screenDefOffsets[MapDef::kNumScreens * MapDef::kNumDirections * 4];
+
+ if (!stream->seek(16))
+ error("Error skipping map file header");
+
+ if (stream->read(screenDefOffsets, sizeof(screenDefOffsets)) != sizeof(screenDefOffsets))
+ error("Error reading map offset table");
+
+ for (uint screen = 0; screen < MapDef::kNumScreens; screen++) {
+ for (uint direction = 0; direction < MapDef::kNumDirections; direction++) {
+ uint32 offset = READ_LE_UINT32(screenDefOffsets + (MapDef::kNumDirections * screen + direction) * 4);
+ if (!offset)
+ continue;
+
+ if (!stream->seek(offset))
+ error("Error seeking to screen data");
+
+ byte screenDefHeader[16];
+ if (stream->read(screenDefHeader, 16) != 16)
+ error("Error reading screen def header");
+
+ uint16 numInteractions = READ_LE_UINT16(screenDefHeader + 0);
+
+ if (numInteractions > 0) {
+ Common::SharedPtr<MapScreenDirectionDef> screenDirectionDef(new MapScreenDirectionDef());
+ screenDirectionDef->interactions.resize(numInteractions);
+
+ for (uint i = 0; i < numInteractions; i++) {
+ InteractionDef &idef = screenDirectionDef->interactions[i];
+
+ byte interactionData[12];
+ if (stream->read(interactionData, 12) != 12)
+ error("Error reading interaction data");
+
+ idef.rect = Common::Rect(READ_LE_INT16(interactionData + 0), READ_LE_INT16(interactionData + 2), READ_LE_INT16(interactionData + 4), READ_LE_INT16(interactionData + 6));
+ idef.interactionID = READ_LE_UINT16(interactionData + 8);
+ idef.objectType = READ_LE_UINT16(interactionData + 10);
+ }
+
+ _map.screenDirections[screen][direction] = screenDirectionDef;
+ }
+ }
}
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 7928dfcaa6a..8cbc66eb25c 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -66,6 +66,29 @@ struct RoomDef {
Common::String name;
};
+struct InteractionDef {
+ InteractionDef();
+
+ Common::Rect rect;
+ uint16 interactionID;
+ uint16 objectType;
+};
+
+struct MapScreenDirectionDef {
+ Common::Array<InteractionDef> interactions;
+};
+
+struct MapDef {
+ static const uint kNumScreens = 96;
+ static const uint kNumDirections = 8;
+ static const uint kFirstScreen = 0xa0;
+
+ Common::SharedPtr<MapScreenDirectionDef> screenDirections[kNumScreens][kNumDirections];
+
+ void clear();
+ const MapScreenDirectionDef *getScreenDirection(uint screen, uint direction);
+};
+
class Runtime {
public:
Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
@@ -82,6 +105,7 @@ private:
void loadIndex();
void changeToScreen(uint roomNumber, uint screenNumber);
+ void loadMap(Common::SeekableReadStream *stream);
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort; // Cursors indexed as CURSOR_#
@@ -98,10 +122,13 @@ private:
Common::FSNode _rootFSNode;
Common::FSNode _logDir;
+ Common::FSNode _mapDir;
Common::Array<Common::SharedPtr<RoomDef> > _roomDefs;
Common::SharedPtr<ScriptSet> _scriptSet;
+ MapDef _map;
+
enum IndexParseType {
kIndexParseTypeNone,
kIndexParseTypeRoom,
Commit: 974fe14b2c0996f6837c4e39a4c1aa265a7cfe2f
https://github.com/scummvm/scummvm/commit/974fe14b2c0996f6837c4e39a4c1aa265a7cfe2f
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VIDEO: Tag V-Cruise as using AVIDecoder
Changed paths:
video/avi_decoder.h
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index a52cf42b044..0b6bdda7652 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -58,6 +58,7 @@ namespace Video {
* - sword1
* - sword2
* - titanic
+ * - vcruise
* - zvision
*/
class AVIDecoder : public VideoDecoder {
Commit: e58c6ba1b2ff62144e65256056f4da43b3bf0e28
https://github.com/scummvm/scummvm/commit/e58c6ba1b2ff62144e65256056f4da43b3bf0e28
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Add enough stuff to render opening cinematic
Changed paths:
A engines/vcruise/audio_player.cpp
A engines/vcruise/audio_player.h
engines/vcruise/configure.engine
engines/vcruise/module.mk
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/audio_player.cpp b/engines/vcruise/audio_player.cpp
new file mode 100644
index 00000000000..7b648902a3f
--- /dev/null
+++ b/engines/vcruise/audio_player.cpp
@@ -0,0 +1,75 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "vcruise/audio_player.h"
+
+namespace VCruise {
+
+AudioPlayer::AudioPlayer(Audio::Mixer *mixer, const Common::SharedPtr<Audio::AudioStream> &baseStream, byte volume, int8 balance)
+ : _exhausted(false), _mixer(nullptr), _baseStream(baseStream) {
+ _mixer = mixer;
+ mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, volume, balance, DisposeAfterUse::NO);
+}
+
+AudioPlayer::~AudioPlayer() {
+ stop();
+}
+
+int AudioPlayer::readBuffer(int16 *buffer, const int numSamplesTimesChannelCount) {
+ Common::StackLock lock(_mutex);
+
+ int samplesRead = 0;
+ if (_exhausted)
+ return 0;
+
+ samplesRead = _baseStream->readBuffer(buffer, numSamplesTimesChannelCount);
+
+ if (samplesRead != numSamplesTimesChannelCount)
+ _exhausted = true;
+
+ return samplesRead;
+}
+
+bool AudioPlayer::isStereo() const {
+ return _baseStream->isStereo();
+}
+
+int AudioPlayer::getRate() const {
+ return _baseStream->getRate();
+}
+
+bool AudioPlayer::endOfData() const {
+ return _exhausted;
+}
+
+void AudioPlayer::sendToMixer(Audio::Mixer *mixer, byte volume, int8 balance) {
+ mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, volume, balance, DisposeAfterUse::NO);
+}
+
+void AudioPlayer::stop() {
+ if (_mixer)
+ _mixer->stopHandle(_handle);
+
+ _exhausted = true;
+ _mixer = nullptr;
+}
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/audio_player.h b/engines/vcruise/audio_player.h
new file mode 100644
index 00000000000..f6597e2d79c
--- /dev/null
+++ b/engines/vcruise/audio_player.h
@@ -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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_AUDIO_PLAYER_H
+#define VCRUISE_AUDIO_PLAYER_H
+
+#include "common/mutex.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace VCruise {
+
+struct AudioMetadata;
+class CachedAudio;
+
+class AudioPlayer : public Audio::AudioStream {
+public:
+ AudioPlayer(Audio::Mixer *mixer, const Common::SharedPtr<Audio::AudioStream> &baseStream, byte volume, int8 balance);
+ ~AudioPlayer();
+
+ int readBuffer(int16 *buffer, const int numSamples) override;
+ bool isStereo() const override;
+ int getRate() const override;
+ bool endOfData() const override;
+
+ void sendToMixer(Audio::Mixer *mixer, byte volume, int8 balance);
+ void stop();
+
+private:
+ Common::Mutex _mutex;
+
+ Audio::SoundHandle _handle;
+ bool _isLooping;
+ bool _exhausted;
+ Audio::Mixer *_mixer;
+ Common::SharedPtr<Audio::AudioStream> _baseStream;
+};
+
+} // End of namespace VCruise
+
+#endif
diff --git a/engines/vcruise/configure.engine b/engines/vcruise/configure.engine
index 1f953f440f0..2a9c2384f8e 100644
--- a/engines/vcruise/configure.engine
+++ b/engines/vcruise/configure.engine
@@ -1,4 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine vcruise "V-Cruise" no "schizm" "" "16bit highres"
-add_engine schizm "Schizm" no "" "" "16bit highres jpeg"
+add_engine vcruise "V-Cruise" no "schizm" "" "16bit highres mad"
+add_engine schizm "Schizm" no "" "" "16bit highres mad jpeg"
diff --git a/engines/vcruise/module.mk b/engines/vcruise/module.mk
index 026a3a98277..5360a78e6d6 100644
--- a/engines/vcruise/module.mk
+++ b/engines/vcruise/module.mk
@@ -1,6 +1,7 @@
MODULE := engines/vcruise
MODULE_OBJS = \
+ audio_player.o \
metaengine.o \
runtime.o \
script.o \
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 26540f8271e..b23d081b117 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -28,6 +28,12 @@
#include "graphics/wincursor.h"
#include "graphics/managed_surface.h"
+#include "audio/decoders/wave.h"
+#include "audio/audiostream.h"
+
+#include "video/avi_decoder.h"
+
+#include "vcruise/audio_player.h"
#include "vcruise/runtime.h"
#include "vcruise/script.h"
#include "vcruise/textparser.h"
@@ -59,8 +65,16 @@ const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direct
return screenDirections[screen][direction].get();
}
-Runtime::Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
- : _system(system), _roomNumber(1), _screenNumber(0), _loadedRoomNumber(0), _activeScreenNumber(0), _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false) {
+void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt) {
+ rect = paramRect;
+ surf.reset(new Graphics::ManagedSurface(paramRect.width(), paramRect.height(), fmt));
+ surf->fillRect(Common::Rect(0, 0, surf->w, surf->h), 0xffffffff);
+}
+
+Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
+ : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _loadedRoomNumber(0), _activeScreenNumber(0),
+ _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false), _scriptNextInstruction(0),
+ _escOn(false), _loadedAnimation(0), _animFrameNumber(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped) {
_logDir = _rootFSNode.getChild("Log");
if (!_logDir.exists() || !_logDir.isDirectory())
@@ -69,11 +83,25 @@ Runtime::Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameI
_mapDir = _rootFSNode.getChild("Map");
if (!_mapDir.exists() || !_mapDir.isDirectory())
error("Couldn't resolve Map directory");
+
+ _sfxDir = _rootFSNode.getChild("Sfx");
+ if (!_sfxDir.exists() || !_sfxDir.isDirectory())
+ error("Couldn't resolve Sfx directory");
+
+ _animsDir = _rootFSNode.getChild("Anims");
+ if (!_animsDir.exists() || !_animsDir.isDirectory())
+ error("Couldn't resolve Anims directory");
}
Runtime::~Runtime() {
}
+void Runtime::initSections(Common::Rect gameRect, Common::Rect menuRect, Common::Rect trayRect, const Graphics::PixelFormat &pixFmt) {
+ _gameSection.init(gameRect, pixFmt);
+ _menuSection.init(menuRect, pixFmt);
+ _traySection.init(trayRect, pixFmt);
+}
+
void Runtime::loadCursors(const char *exeName) {
Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(exeName));
if (!winRes)
@@ -121,8 +149,14 @@ bool Runtime::runFrame() {
break;
case kGameStateQuit:
return false;
- case kGameStateRunning:
- moreActions = runGame();
+ case kGameStateIdle:
+ moreActions = runIdle();
+ break;
+ case kGameStateScript:
+ moreActions = runScript();
+ break;
+ case kGameStateWaitingForAnimation:
+ moreActions = runWaitForAnimation();
break;
default:
error("Unknown game state");
@@ -138,20 +172,18 @@ bool Runtime::bootGame() {
loadIndex();
debug(1, "Index loaded OK");
- _gameState = kGameStateRunning;
+ _gameState = kGameStateIdle;
if (_gameID == GID_REAH) {
// TODO: Change to the logo instead (0xb1) instead when menus are implemented
- _roomNumber = 1;
- _screenNumber = 0xb0;
- _havePendingScreenChange = true;
+ changeToScreen(1, 0xb0);
} else
error("Couldn't figure out what screen to start on");
return true;
}
-bool Runtime::runGame() {
+bool Runtime::runIdle() {
if (_havePendingScreenChange) {
_havePendingScreenChange = false;
@@ -163,6 +195,175 @@ bool Runtime::runGame() {
return false;
}
+bool Runtime::runWaitForAnimation() {
+ if (!_animDecoder) {
+ _gameState = kGameStateScript;
+ return true;
+ }
+
+ bool needsFirstFrame = false;
+ if (_animDecoderState == kAnimDecoderStatePaused) {
+ _animDecoder->pauseVideo(false);
+ _animDecoderState = kAnimDecoderStatePlaying;
+ needsFirstFrame = true;
+ } else if (_animDecoderState == kAnimDecoderStateStopped) {
+ _animDecoder->start();
+ _animDecoderState = kAnimDecoderStatePlaying;
+ needsFirstFrame = true;
+ }
+
+ for (;;) {
+ bool needNewFrame = needsFirstFrame || (_animDecoder->getTimeToNextFrame() == 0);
+ needsFirstFrame = false;
+
+ if (!needNewFrame)
+ break;
+
+ const Graphics::Surface *surface = _animDecoder->decodeNextFrame();
+ if (!surface) {
+ _gameState = kGameStateScript;
+ return true;
+ }
+
+ Common::Rect copyRect = Common::Rect(0, 0, surface->w, surface->h);
+
+ Common::Rect constraintRect = Common::Rect(0, 0, _gameSection.rect.width(), _gameSection.rect.height());
+
+ copyRect = copyRect.findIntersectingRect(constraintRect);
+
+ if (copyRect.isValidRect() || !copyRect.isEmpty()) {
+ _gameSection.surf->blitFrom(*surface, copyRect, copyRect);
+ _system->copyRectToScreen(_gameSection.surf->getBasePtr(copyRect.left, copyRect.top), _gameSection.surf->pitch, copyRect.left + _gameSection.rect.left, copyRect.top + _gameSection.rect.top, copyRect.width(), copyRect.height());
+ }
+
+ // The current frame will be the frame to be decoded on the next decode call,
+ // which means if it exceeds the last frame then it's time to stop
+ if (_animDecoder->getCurFrame() > static_cast<int>(_animLastFrame)) {
+ _animDecoder->pauseVideo(true);
+ _animDecoderState = kAnimDecoderStatePaused;
+
+ _gameState = kGameStateScript;
+ return true;
+ }
+ }
+
+ // Pump and handle events
+ return false;
+}
+
+#ifdef DISPATCH_OP
+#error "DISPATCH_OP already defined"
+#endif
+
+#define DISPATCH_OP(op) \
+ case ScriptOps::k##op: this->scriptOp##op(arg); break
+
+bool Runtime::runScript() {
+ while (_gameState == kGameStateScript) {
+ uint instrNum = _scriptNextInstruction;
+ if (!_activeScript || instrNum >= _activeScript->instrs.size()) {
+ terminateScript();
+ return true;
+ }
+
+ _scriptNextInstruction++;
+
+ const Instruction &instr = _activeScript->instrs[instrNum];
+ int32 arg = instr.arg;
+
+ switch (instr.op) {
+ DISPATCH_OP(Number);
+ DISPATCH_OP(Rotate);
+ DISPATCH_OP(Angle);
+ DISPATCH_OP(AngleGGet);
+ DISPATCH_OP(Speed);
+ DISPATCH_OP(SAnimL);
+ DISPATCH_OP(ChangeL);
+
+ DISPATCH_OP(AnimR);
+ DISPATCH_OP(AnimF);
+ DISPATCH_OP(AnimN);
+ DISPATCH_OP(AnimG);
+ DISPATCH_OP(AnimS);
+ DISPATCH_OP(Anim);
+
+ DISPATCH_OP(Static);
+ DISPATCH_OP(VarLoad);
+ DISPATCH_OP(VarStore);
+ DISPATCH_OP(SetCursor);
+ DISPATCH_OP(SetRoom);
+ DISPATCH_OP(LMB);
+ DISPATCH_OP(LMB1);
+ DISPATCH_OP(SoundS1);
+ DISPATCH_OP(SoundL2);
+
+ DISPATCH_OP(Music);
+ DISPATCH_OP(MusicUp);
+ DISPATCH_OP(Parm1);
+ DISPATCH_OP(Parm2);
+ DISPATCH_OP(Parm3);
+ DISPATCH_OP(ParmG);
+
+ DISPATCH_OP(VolumeDn4);
+ DISPATCH_OP(VolumeUp3);
+ DISPATCH_OP(Random);
+ DISPATCH_OP(Drop);
+ DISPATCH_OP(Dup);
+ DISPATCH_OP(Say3);
+ DISPATCH_OP(SetTimer);
+ DISPATCH_OP(LoSet);
+ DISPATCH_OP(LoGet);
+ DISPATCH_OP(HiSet);
+ DISPATCH_OP(HiGet);
+
+ DISPATCH_OP(Not);
+ DISPATCH_OP(And);
+ DISPATCH_OP(Or);
+ DISPATCH_OP(CmpEq);
+
+ DISPATCH_OP(BitLoad);
+ DISPATCH_OP(BitSet0);
+ DISPATCH_OP(BitSet1);
+
+ DISPATCH_OP(Disc1);
+ DISPATCH_OP(Disc2);
+ DISPATCH_OP(Disc3);
+
+ DISPATCH_OP(EscOn);
+ DISPATCH_OP(EscOff);
+ DISPATCH_OP(EscGet);
+ DISPATCH_OP(BackStart);
+
+ DISPATCH_OP(AnimName);
+ DISPATCH_OP(ValueName);
+ DISPATCH_OP(VarName);
+ DISPATCH_OP(SoundName);
+ DISPATCH_OP(CursorName);
+
+ DISPATCH_OP(CheckValue);
+ DISPATCH_OP(Jump);
+
+ default:
+ error("Unimplemented opcode %i", static_cast<int>(instr.op));
+ }
+ }
+
+ return true;
+}
+
+#undef DISPATCH_OP
+
+void Runtime::terminateScript() {
+ _activeScript.reset();
+ _scriptNextInstruction = 0;
+
+ if (_gameState == kGameStateScript)
+ _gameState = kGameStateIdle;
+
+ if (_havePendingScreenChange)
+ changeToScreen(_roomNumber, _screenNumber);
+}
+
void Runtime::loadIndex() {
Common::FSNode indexFSNode = _logDir.getChild("Index.txt");
@@ -242,7 +443,16 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
bool changedRoom = (roomNumber != _loadedRoomNumber);
bool changedScreen = (screenNumber != _activeScreenNumber) || changedRoom;
+ _roomNumber = roomNumber;
+ _screenNumber = screenNumber;
+
+ _loadedRoomNumber = roomNumber;
+ _activeScreenNumber = screenNumber;
+
if (changedRoom) {
+ // Scripts are not allowed
+ assert(!_activeScript);
+
_scriptSet.reset();
Common::String logFileName = Common::String::format("Room%02i.log", static_cast<int>(roomNumber));
@@ -265,6 +475,21 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
}
}
}
+
+ if (changedScreen) {
+ if (_scriptSet) {
+ Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> >::const_iterator roomScriptIt = _scriptSet->roomScripts.find(_roomNumber);
+ if (roomScriptIt != _scriptSet->roomScripts.end()) {
+ Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> > &screenScriptsMap = roomScriptIt->_value->screenScripts;
+ Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> >::const_iterator screenScriptIt = screenScriptsMap.find(_screenNumber);
+ if (screenScriptIt != screenScriptsMap.end()) {
+ const Common::SharedPtr<Script> &script = screenScriptIt->_value->entryScript;
+ if (script)
+ activateScript(script);
+ }
+ }
+ }
+ }
}
void Runtime::loadMap(Common::SeekableReadStream *stream) {
@@ -313,6 +538,79 @@ void Runtime::loadMap(Common::SeekableReadStream *stream) {
}
}
+void Runtime::changeMusicTrack(int track) {
+ _musicPlayer.reset();
+
+ Common::String wavFileName = Common::String::format("Music-%02i.wav", static_cast<int>(track));
+ Common::FSNode wavFileNode = _sfxDir.getChild(wavFileName);
+ if (wavFileNode.exists()) {
+ if (Common::SeekableReadStream *wavFile = wavFileNode.createReadStream()) {
+ if (Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(wavFile, DisposeAfterUse::YES)) {
+ Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
+
+ _musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, 255, 0));
+ }
+ }
+ }
+}
+
+void Runtime::changeAnimation(const AnimationDef &animDef) {
+ int animFile = animDef.animNum;
+ if (animFile < 0)
+ animFile = -animFile;
+
+ if (_loadedAnimation != static_cast<uint>(animFile)) {
+ _loadedAnimation = animFile;
+ _animDecoder.reset();
+ _animDecoderState = kAnimDecoderStateStopped;
+
+ Common::String aviFileName = Common::String::format("Anim%04i.avi", animFile);
+ Common::FSNode aviFileNode = _animsDir.getChild(aviFileName);
+ if (!aviFileNode.exists()) {
+ warning("Animation file %i is missing", animFile);
+ return;
+ }
+
+ if (Common::SeekableReadStream *aviFile = aviFileNode.createReadStream()) {
+ _animDecoder.reset(new Video::AVIDecoder());
+ if (!_animDecoder->loadStream(aviFile)) {
+ warning("Animation file %i could not be loaded", animFile);
+ return;
+ }
+ } else {
+ warning("Animation file %i is missing", animFile);
+ return;
+ }
+ }
+
+ if (_animDecoderState == kAnimDecoderStatePlaying) {
+ _animDecoder->pauseVideo(true);
+ _animDecoderState = kAnimDecoderStatePaused;
+ }
+
+ _animDecoder->seekToFrame(animDef.firstFrame);
+ _animFrameNumber = animDef.firstFrame;
+ _animLastFrame = animDef.lastFrame;
+}
+
+AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
+ AnimationDef def;
+ def.animNum = args[0];
+ def.firstFrame = args[1];
+ def.lastFrame = args[2];
+
+ return def;
+}
+
+void Runtime::activateScript(const Common::SharedPtr<Script> &script) {
+ if (script->instrs.size() == 0)
+ return;
+
+ _activeScript = script;
+ _scriptNextInstruction = 0;
+ _gameState = kGameStateScript;
+}
+
bool Runtime::parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath) {
Common::String lineText;
parser.expectLine(lineText, blamePath, true);
@@ -433,8 +731,183 @@ void Runtime::allocateRoomsUpTo(uint roomNumber) {
}
}
-void Runtime::drawFrame() {
+#ifdef CHECK_STACK
+#error "CHECK_STACK is already defined"
+#endif
+
+#define PEEK_STACK(n) \
+ if (this->_scriptStack.size() < (n)) { \
+ error("Script stack underflow"); \
+ return; \
+ } \
+ const ScriptArg_t *stackArgs = &this->_scriptStack[this->_scriptStack.size() - (n)]
+
+
+#define TAKE_STACK(n) \
+ StackValue_t stackArgs[n]; \
+ do { \
+ const uint stackSize = _scriptStack.size(); \
+ if (stackSize < (n)) { \
+ error("Script stack underflow"); \
+ return; \
+ } \
+ const StackValue_t *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
+ for (uint i = 0; i < (n); i++) \
+ stackArgs[i] = stackArgsPtr[i]; \
+ this->_scriptStack.resize(stackSize - (n)); \
+ } while (false)
+
+void Runtime::scriptOpNumber(ScriptArg_t arg) {
+ _scriptStack.push_back(arg);
+}
+
+void Runtime::scriptOpRotate(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAngle(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAngleGGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSpeed(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSAnimL(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpChangeL(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpAnimR(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAnimF(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAnimN(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAnimG(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAnimS(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpAnim(ScriptArg_t arg) {
+ TAKE_STACK(kAnimDefStackArgs + 2);
+
+ AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
+ changeAnimation(animDef);
+
+ _gameState = kGameStateWaitingForAnimation;
+ _screenNumber = stackArgs[kAnimDefStackArgs + 0];
+ _direction = stackArgs[kAnimDefStackArgs + 1];
+}
+
+void Runtime::scriptOpStatic(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpVarLoad(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpVarStore(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSetCursor(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSetRoom(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpLMB(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpLMB1(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSoundS1(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSoundL2(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpMusic(ScriptArg_t arg) {
+ TAKE_STACK(1);
+
+ changeMusicTrack(stackArgs[0]);
+}
+
+void Runtime::scriptOpMusicUp(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpParm1(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpParm2(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpParm3(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpParmG(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpVolumeDn4(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpVolumeUp3(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpRandom(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpDrop(ScriptArg_t arg) {
+ TAKE_STACK(1);
+}
+
+void Runtime::scriptOpDup(ScriptArg_t arg) {
+ TAKE_STACK(1);
+
+ _scriptStack.push_back(stackArgs[0]);
+ _scriptStack.push_back(stackArgs[0]);
+}
+
+void Runtime::scriptOpSay3(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSetTimer(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpLoSet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpLoGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpHiSet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpHiGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpNot(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpAnd(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpOr(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpCmpEq(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpBitLoad(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpBitSet0(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpBitSet1(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpDisc1(ScriptArg_t arg) {
+ // Always pass disc check
+ TAKE_STACK(1);
+ _scriptStack.push_back(1);
+}
+
+void Runtime::scriptOpDisc2(ScriptArg_t arg) {
+ // Always pass disc check
+ TAKE_STACK(2);
+ _scriptStack.push_back(1);
+}
+
+void Runtime::scriptOpDisc3(ScriptArg_t arg) {
+ // Always pass disc check
+ TAKE_STACK(3);
+ _scriptStack.push_back(1);
+}
+
+void Runtime::scriptOpEscOn(ScriptArg_t arg) {
+ TAKE_STACK(1);
+
+ _escOn = (stackArgs[0] != 0);
+}
+
+void Runtime::scriptOpEscOff(ScriptArg_t arg) {
+ _escOn = false;
+}
+
+void Runtime::scriptOpEscGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpBackStart(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpAnimName(ScriptArg_t arg) {
+ // I doubt this is actually how it works internally but whatever
+ if (_roomNumber >= _roomDefs.size())
+ error("Can't resolve animation for room, room number was invalid");
+
+ Common::SharedPtr<RoomDef> roomDef = _roomDefs[_roomNumber];
+ if (!roomDef)
+ error("Can't resolve animation for room, room number was invalid");
+
+
+ Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_scriptSet->strings[arg]);
+ if (it == roomDef->animations.end())
+ error("Can't resolve animation for room, couldn't find animation '%s'", _scriptSet->strings[arg].c_str());
+
+ _scriptStack.push_back(it->_value.animNum);
+ _scriptStack.push_back(it->_value.firstFrame);
+ _scriptStack.push_back(it->_value.lastFrame);
+}
+
+
+void Runtime::scriptOpValueName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpVarName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSoundName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpCursorName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpCheckValue(ScriptArg_t arg) {
+ PEEK_STACK(1);
+
+ if (arg == stackArgs[0])
+ _scriptStack.pop_back();
+ else
+ _scriptNextInstruction++;
+}
+
+void Runtime::scriptOpJump(ScriptArg_t arg) {
+ _scriptNextInstruction = arg;
+}
+
+void Runtime::drawFrame() {
_system->updateScreen();
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 8cbc66eb25c..77dd957813c 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -30,20 +30,32 @@ class OSystem;
namespace Graphics {
+struct PixelFormat;
struct WinCursorGroup;
+class ManagedSurface;
} // End of namespace Graphics
+namespace Video {
+
+class AVIDecoder;
+
+} // End of namespace Video
+
namespace VCruise {
+class AudioPlayer;
class TextParser;
struct ScriptSet;
+struct Script;
+struct Instruction;
enum GameState {
- kGameStateBoot,
- kGameStateCinematic,
- kGameStateQuit,
- kGameStateRunning,
+ kGameStateBoot, // Booting the game
+ kGameStateWaitingForAnimation, // Waiting for a blocking animation to complete, then resuming script
+ kGameStateQuit, // Quitting
+ kGameStateIdle, // Waiting for input events
+ kGameStateScript, // Running a script
};
struct AnimationDef {
@@ -91,63 +103,188 @@ struct MapDef {
class Runtime {
public:
- Runtime(OSystem *system, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
+ Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
virtual ~Runtime();
+ void initSections(Common::Rect gameRect, Common::Rect menuRect, Common::Rect trayRect, const Graphics::PixelFormat &pixFmt);
+
void loadCursors(const char *exeName);
bool runFrame();
void drawFrame();
private:
+ enum IndexParseType {
+ kIndexParseTypeNone,
+ kIndexParseTypeRoom,
+ kIndexParseTypeRRoom, // Rectangle room (constrains animation areas)
+ kIndexParseTypeYRoom, // Yes room (variable/ID mappings)
+ kIndexParseTypeVRoom, // Value room (value/ID mappings?)
+ kIndexParseTypeTRoom, // Text
+ kIndexParseTypeCRoom, // Const
+ kIndexParseTypeSRoom, // Sound
+ kIndexParseTypeNameRoom,
+ };
+
+ enum AnimDecoderState {
+ kAnimDecoderStateStopped,
+ kAnimDecoderStatePlaying,
+ kAnimDecoderStatePaused,
+ };
+
+ struct IndexPrefixTypePair {
+ const char *prefix;
+ IndexParseType parseType;
+ };
+
+ struct RenderSection {
+ Common::SharedPtr<Graphics::ManagedSurface> surf;
+ Common::Rect rect;
+
+ void init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt);
+ };
+
+ typedef int32 ScriptArg_t;
+ typedef int32 StackValue_t;
+
bool bootGame();
- bool runGame();
+ bool runIdle();
+ bool runScript();
+ bool runWaitForAnimation();
+ void terminateScript();
void loadIndex();
void changeToScreen(uint roomNumber, uint screenNumber);
void loadMap(Common::SeekableReadStream *stream);
+ void changeMusicTrack(int musicID);
+ void changeAnimation(const AnimationDef &animDef);
+
+ AnimationDef stackArgsToAnimDef(const StackValue_t *args) const;
+
+ void activateScript(const Common::SharedPtr<Script> &script);
+
+ bool parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath);
+ void allocateRoomsUpTo(uint roomNumber);
+
+ void scriptOpNumber(ScriptArg_t arg);
+ void scriptOpRotate(ScriptArg_t arg);
+ void scriptOpAngle(ScriptArg_t arg);
+ void scriptOpAngleGGet(ScriptArg_t arg);
+ void scriptOpSpeed(ScriptArg_t arg);
+ void scriptOpSAnimL(ScriptArg_t arg);
+ void scriptOpChangeL(ScriptArg_t arg);
+
+ void scriptOpAnimR(ScriptArg_t arg);
+ void scriptOpAnimF(ScriptArg_t arg);
+ void scriptOpAnimN(ScriptArg_t arg);
+ void scriptOpAnimG(ScriptArg_t arg);
+ void scriptOpAnimS(ScriptArg_t arg);
+ void scriptOpAnim(ScriptArg_t arg);
+
+ void scriptOpStatic(ScriptArg_t arg);
+ void scriptOpVarLoad(ScriptArg_t arg);
+ void scriptOpVarStore(ScriptArg_t arg);
+ void scriptOpSetCursor(ScriptArg_t arg);
+ void scriptOpSetRoom(ScriptArg_t arg);
+ void scriptOpLMB(ScriptArg_t arg);
+ void scriptOpLMB1(ScriptArg_t arg);
+ void scriptOpSoundS1(ScriptArg_t arg);
+ void scriptOpSoundL2(ScriptArg_t arg);
+
+ void scriptOpMusic(ScriptArg_t arg);
+ void scriptOpMusicUp(ScriptArg_t arg);
+ void scriptOpParm1(ScriptArg_t arg);
+ void scriptOpParm2(ScriptArg_t arg);
+ void scriptOpParm3(ScriptArg_t arg);
+ void scriptOpParmG(ScriptArg_t arg);
+
+ void scriptOpVolumeDn4(ScriptArg_t arg);
+ void scriptOpVolumeUp3(ScriptArg_t arg);
+ void scriptOpRandom(ScriptArg_t arg);
+ void scriptOpDrop(ScriptArg_t arg);
+ void scriptOpDup(ScriptArg_t arg);
+ void scriptOpSay3(ScriptArg_t arg);
+ void scriptOpSetTimer(ScriptArg_t arg);
+ void scriptOpLoSet(ScriptArg_t arg);
+ void scriptOpLoGet(ScriptArg_t arg);
+ void scriptOpHiSet(ScriptArg_t arg);
+ void scriptOpHiGet(ScriptArg_t arg);
+
+ void scriptOpNot(ScriptArg_t arg);
+ void scriptOpAnd(ScriptArg_t arg);
+ void scriptOpOr(ScriptArg_t arg);
+ void scriptOpCmpEq(ScriptArg_t arg);
+
+ void scriptOpBitLoad(ScriptArg_t arg);
+ void scriptOpBitSet0(ScriptArg_t arg);
+ void scriptOpBitSet1(ScriptArg_t arg);
+
+ void scriptOpDisc1(ScriptArg_t arg);
+ void scriptOpDisc2(ScriptArg_t arg);
+ void scriptOpDisc3(ScriptArg_t arg);
+
+ void scriptOpEscOn(ScriptArg_t arg);
+ void scriptOpEscOff(ScriptArg_t arg);
+ void scriptOpEscGet(ScriptArg_t arg);
+ void scriptOpBackStart(ScriptArg_t arg);
+
+ void scriptOpAnimName(ScriptArg_t arg);
+ void scriptOpValueName(ScriptArg_t arg);
+ void scriptOpVarName(ScriptArg_t arg);
+ void scriptOpSoundName(ScriptArg_t arg);
+ void scriptOpCursorName(ScriptArg_t arg);
+
+ void scriptOpCheckValue(ScriptArg_t arg);
+ void scriptOpJump(ScriptArg_t arg);
+
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort; // Cursors indexed as CURSOR_#
OSystem *_system;
uint _roomNumber; // Room number can be changed independently of the loaded room, the screen doesn't change until a command changes it
uint _screenNumber;
+ uint _direction;
uint _loadedRoomNumber;
uint _activeScreenNumber;
bool _havePendingScreenChange;
GameState _gameState;
+
+ bool _escOn;
+
VCruiseGameID _gameID;
Common::FSNode _rootFSNode;
Common::FSNode _logDir;
Common::FSNode _mapDir;
+ Common::FSNode _sfxDir;
+ Common::FSNode _animsDir;
Common::Array<Common::SharedPtr<RoomDef> > _roomDefs;
Common::SharedPtr<ScriptSet> _scriptSet;
- MapDef _map;
+ Common::SharedPtr<Script> _activeScript;
+ uint _scriptNextInstruction;
+ Common::Array<StackValue_t> _scriptStack;
- enum IndexParseType {
- kIndexParseTypeNone,
- kIndexParseTypeRoom,
- kIndexParseTypeRRoom, // Rectangle room (constrains animation areas)
- kIndexParseTypeYRoom, // Yes room (variable/ID mappings)
- kIndexParseTypeVRoom, // Value room (value/ID mappings?)
- kIndexParseTypeTRoom, // Text
- kIndexParseTypeCRoom, // Const
- kIndexParseTypeSRoom, // Sound
- kIndexParseTypeNameRoom,
- };
+ Common::SharedPtr<AudioPlayer> _musicPlayer;
- struct IndexPrefixTypePair {
- const char *prefix;
- IndexParseType parseType;
- };
+ Common::SharedPtr<Video::AVIDecoder> _animDecoder;
+ AnimDecoderState _animDecoderState;
+ uint _animFrameNumber;
+ uint _animLastFrame;
+ uint _loadedAnimation;
- bool parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath);
- void allocateRoomsUpTo(uint roomNumber);
+ Audio::Mixer *_mixer;
+
+ MapDef _map;
+
+ RenderSection _gameSection;
+ RenderSection _menuSection;
+ RenderSection _traySection;
+
+ static const uint kAnimDefStackArgs = 3;
};
} // End of namespace VCruise
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 569e3c79f56..99573faabff 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -22,8 +22,8 @@
#include "common/stream.h"
#include "common/hash-str.h"
-#include "engines/vcruise/script.h"
-#include "engines/vcruise/textparser.h"
+#include "vcruise/script.h"
+#include "vcruise/textparser.h"
namespace VCruise {
@@ -529,8 +529,8 @@ void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
int32 nextLabel = 0;
- // Pass 1: Convert all flow control constructs into references to the flow control construct.
- // This changes Switch, If, and Break proto-ops to point to the corresponding if or switch index
+ // Pass 1: Collect flow control constructs, make all flow control constructs point to the index of the construct,
+ // replace Else, Case, EndIf, EndSwitch, and Default instructions with Label and JumpToLabel.
for (const ProtoInstruction &instr : protoScript.instrs) {
switch (instr.protoOp) {
case kProtoOpScript:
@@ -675,8 +675,7 @@ void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
Common::HashMap<uint, uint> labelToInstr;
- // Pass 2: Convert CF block references into label-targeting ops
- // This eliminates If, Switch, and Break ops
+ // Pass 2: Unroll If and Switch instructions into CheckValue and JumpToLabel ops, resolve label locations
for (const ProtoInstruction &instr : instrs) {
switch (instr.protoOp) {
case kProtoOpScript:
@@ -717,7 +716,7 @@ void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
instrs.clear();
- // Pass 3: Resolve labels and write out finished instructions
+ // Pass 3: Change all JumpToLabel ops to Jump ops and write out final instructions
script.instrs.reserve(instrs2.size());
for (const ProtoInstruction &instr : instrs2) {
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index afec75d6c55..8d2e7402eab 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -107,6 +107,8 @@ enum ScriptOp {
kCheckValue, // Check if stack top is equal to arg. If it is, pop the argument, otherwise leave it on the stack and skip the next instruction.
kJump, // Offset instruction index by arg.
+
+ kNumOps,
};
} // End of namespace ScriptOps
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index a3193de72d0..39d59ac4ac1 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -94,8 +94,8 @@ Common::Error VCruiseEngine::run() {
size.y = videoSize.y + menuBarSize.y + traySize.y;
Common::Point menuTL = Common::Point((size.x - menuBarSize.x) / 2, 0);
- Common::Point videoTL = Common::Point((size.x - videoSize.x) / 2, menuTL.y);
- Common::Point trayTL = Common::Point((size.x - traySize.x) / 2, videoTL.y);
+ Common::Point videoTL = Common::Point((size.x - videoSize.x) / 2, menuTL.y + menuBarSize.y);
+ Common::Point trayTL = Common::Point((size.x - traySize.x) / 2, videoTL.y + videoSize.y);
_menuBarRect = Common::Rect(menuTL.x, menuTL.y, menuTL.x + menuBarSize.x, menuTL.y + menuBarSize.y);
_videoRect = Common::Rect(videoTL.x, videoTL.y, videoTL.x + videoSize.x, videoTL.y + videoSize.y);
@@ -108,7 +108,10 @@ Common::Error VCruiseEngine::run() {
else
error("Unable to find a suitable graphics format");
- _runtime.reset(new Runtime(_system, _rootFSNode, _gameDescription->gameID));
+ _system->fillScreen(0);
+
+ _runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID));
+ _runtime->initSections(_videoRect, _menuBarRect, _trayRect, _system->getScreenFormat());
_runtime->loadCursors(exeName);
Commit: 8f8836617bf4b8f487f95bd62fd2957251051c36
https://github.com/scummvm/scummvm/commit/8f8836617bf4b8f487f95bd62fd2957251051c36
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VIDEO: Increase stream name capacity to deal with long (67 character) stream names in Reah
Changed paths:
video/avi_decoder.cpp
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp
index e868d80f31a..3a589c5fd55 100644
--- a/video/avi_decoder.cpp
+++ b/video/avi_decoder.cpp
@@ -383,8 +383,8 @@ void AVIDecoder::readStreamName(uint32 size) {
skipChunk(size);
} else {
// Get in the name
- assert(size > 0 && size < 64);
- char buffer[64];
+ assert(size > 0 && size < 128);
+ char buffer[128];
_fileStream->read(buffer, size);
if (size & 1)
_fileStream->skip(1);
Commit: 5216131a5d3f6a7fb9dc5963476b46f864fc0eca
https://github.com/scummvm/scummvm/commit/5216131a5d3f6a7fb9dc5963476b46f864fc0eca
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Add more things to get Reah to the first screen
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index b23d081b117..32ea00f9a8e 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -72,10 +72,13 @@ void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics:
}
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
- : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _loadedRoomNumber(0), _activeScreenNumber(0),
+ : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _havePanAnimations(0), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false), _scriptNextInstruction(0),
_escOn(false), _loadedAnimation(0), _animFrameNumber(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped) {
+ for (uint i = 0; i < kNumDirections; i++)
+ _haveIdleAnimations[i] = false;
+
_logDir = _rootFSNode.getChild("Log");
if (!_logDir.exists() || !_logDir.isDirectory())
error("Couldn't resolve Log directory");
@@ -299,6 +302,7 @@ bool Runtime::runScript() {
DISPATCH_OP(Music);
DISPATCH_OP(MusicUp);
+ DISPATCH_OP(MusicDn);
DISPATCH_OP(Parm1);
DISPATCH_OP(Parm2);
DISPATCH_OP(Parm3);
@@ -489,11 +493,16 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
}
}
}
+
+ _havePanAnimations = false;
+
+ for (uint i = 0; i < kNumDirections; i++)
+ _haveIdleAnimations[i] = false;
}
}
void Runtime::loadMap(Common::SeekableReadStream *stream) {
- byte screenDefOffsets[MapDef::kNumScreens * MapDef::kNumDirections * 4];
+ byte screenDefOffsets[MapDef::kNumScreens * kNumDirections * 4];
if (!stream->seek(16))
error("Error skipping map file header");
@@ -502,8 +511,8 @@ void Runtime::loadMap(Common::SeekableReadStream *stream) {
error("Error reading map offset table");
for (uint screen = 0; screen < MapDef::kNumScreens; screen++) {
- for (uint direction = 0; direction < MapDef::kNumDirections; direction++) {
- uint32 offset = READ_LE_UINT32(screenDefOffsets + (MapDef::kNumDirections * screen + direction) * 4);
+ for (uint direction = 0; direction < kNumDirections; direction++) {
+ uint32 offset = READ_LE_UINT32(screenDefOffsets + (kNumDirections * screen + direction) * 4);
if (!offset)
continue;
@@ -731,6 +740,31 @@ void Runtime::allocateRoomsUpTo(uint roomNumber) {
}
}
+void Runtime::onLButtonDown(int16 x, int16 y) {
+}
+
+void Runtime::onLButtonUp(int16 x, int16 y) {
+}
+
+void Runtime::onMouseMove(int16 x, int16 y) {
+}
+
+void Runtime::onKeyDown(Common::KeyCode keyCode) {
+ if (keyCode == Common::KEYCODE_ESCAPE) {
+ if (_gameState == kGameStateWaitingForAnimation) {
+ if (_escOn) {
+ // Terminate the animation
+ if (_animDecoderState == kAnimDecoderStatePlaying) {
+ _animDecoder->pauseVideo(true);
+ _animDecoderState = kAnimDecoderStatePaused;
+ }
+ _gameState = kGameStateScript;
+ return;
+ }
+ }
+ }
+}
+
#ifdef CHECK_STACK
#error "CHECK_STACK is already defined"
#endif
@@ -761,18 +795,54 @@ void Runtime::scriptOpNumber(ScriptArg_t arg) {
_scriptStack.push_back(arg);
}
-void Runtime::scriptOpRotate(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpRotate(ScriptArg_t arg) {
+ TAKE_STACK(kAnimDefStackArgs + kAnimDefStackArgs);
+
+ _panLeftAnimationDef = stackArgsToAnimDef(stackArgs + 0);
+ _panRightAnimationDef = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
+ _havePanAnimations = true;
+}
+
void Runtime::scriptOpAngle(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpAngleGGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpSpeed(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpSAnimL(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
+ TAKE_STACK(kAnimDefStackArgs + 2);
+
+ if (stackArgs[kAnimDefStackArgs] != 0)
+ warning("sanimL second operand wasn't zero (what does that do??)");
+
+ AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
+ uint direction = stackArgs[kAnimDefStackArgs + 1];
+
+ if (direction >= kNumDirections)
+ error("sanimL invalid direction");
+
+ _haveIdleAnimations[direction] = true;
+ _idleAnimations[direction] = animDef;
+}
+
void Runtime::scriptOpChangeL(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpAnimR(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpAnimF(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpAnimN(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpAnimG(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpAnimS(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpAnimS(ScriptArg_t arg) {
+ TAKE_STACK(kAnimDefStackArgs + 2);
+
+ AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
+ animDef.lastFrame = animDef.firstFrame; // Static animation
+
+ changeAnimation(animDef);
+
+ _gameState = kGameStateWaitingForAnimation; // FIXME
+ _screenNumber = stackArgs[kAnimDefStackArgs + 0];
+ _direction = stackArgs[kAnimDefStackArgs + 1];
+ _havePendingScreenChange = true;
+}
void Runtime::scriptOpAnim(ScriptArg_t arg) {
TAKE_STACK(kAnimDefStackArgs + 2);
@@ -783,17 +853,42 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
_gameState = kGameStateWaitingForAnimation;
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
_direction = stackArgs[kAnimDefStackArgs + 1];
+ _havePendingScreenChange = true;
}
void Runtime::scriptOpStatic(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpVarLoad(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpVarStore(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpVarLoad(ScriptArg_t arg) {
+ TAKE_STACK(1);
+
+ uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[0]);
+
+ Common::HashMap<uint32, int32>::const_iterator it = _variables.find(varID);
+ if (it == _variables.end())
+ _scriptStack.push_back(0);
+ else
+ _scriptStack.push_back(it->_value);
+}
+
+void Runtime::scriptOpVarStore(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[1]);
+
+ _variables[varID] = stackArgs[0];
+}
+
void Runtime::scriptOpSetCursor(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpSetRoom(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpLMB(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpLMB1(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpSoundS1(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpSoundL2(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ warning("Sound loop not implemented yet");
+}
void Runtime::scriptOpMusic(ScriptArg_t arg) {
TAKE_STACK(1);
@@ -801,7 +896,18 @@ void Runtime::scriptOpMusic(ScriptArg_t arg) {
changeMusicTrack(stackArgs[0]);
}
-void Runtime::scriptOpMusicUp(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpMusicUp(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ warning("Music volume changes are not implemented");
+}
+
+void Runtime::scriptOpMusicDn(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ warning("Music volume changes are not implemented");
+}
+
void Runtime::scriptOpParm1(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpParm2(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpParm3(ScriptArg_t arg) { error("Unimplemented opcode"); }
@@ -829,29 +935,48 @@ void Runtime::scriptOpLoGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpHiSet(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpHiGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpNot(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpAnd(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpOr(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpCmpEq(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpNot(ScriptArg_t arg) {
+ TAKE_STACK(1);
+
+ _scriptStack.push_back((stackArgs[0] == 0) ? 1 : 0);
+}
+
+void Runtime::scriptOpAnd(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ _scriptStack.push_back((stackArgs[0] != 0 && stackArgs[1] != 0) ? 1 : 0);
+}
+
+void Runtime::scriptOpOr(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ _scriptStack.push_back((stackArgs[0] != 0 || stackArgs[1] != 0) ? 1 : 0);
+}
+
+void Runtime::scriptOpCmpEq(ScriptArg_t arg) {
+ TAKE_STACK(2);
+
+ _scriptStack.push_back((stackArgs[0] == stackArgs[1]) ? 1 : 0);
+}
void Runtime::scriptOpBitLoad(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpBitSet0(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpBitSet1(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpDisc1(ScriptArg_t arg) {
- // Always pass disc check
+ // Disc check, always pass
TAKE_STACK(1);
_scriptStack.push_back(1);
}
void Runtime::scriptOpDisc2(ScriptArg_t arg) {
- // Always pass disc check
+ // Disc check, always pass
TAKE_STACK(2);
_scriptStack.push_back(1);
}
void Runtime::scriptOpDisc3(ScriptArg_t arg) {
- // Always pass disc check
+ // Disc check, always pass
TAKE_STACK(3);
_scriptStack.push_back(1);
}
@@ -890,8 +1015,36 @@ void Runtime::scriptOpAnimName(ScriptArg_t arg) {
void Runtime::scriptOpValueName(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpVarName(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpSoundName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpVarName(ScriptArg_t arg) {
+ if (_roomNumber >= _roomDefs.size())
+ error("Invalid room number for var name op");
+
+ const RoomDef *roomDef = _roomDefs[_roomNumber].get();
+ if (!roomDef)
+ error("Room def doesn't exist");
+
+ const Common::String &varName = _scriptSet->strings[arg];
+
+ Common::HashMap<Common::String, uint>::const_iterator it = roomDef->vars.find(varName);
+ if (it == roomDef->vars.end())
+ error("Var '%s' doesn't exist in room %i", varName.c_str(), static_cast<int>(_roomNumber));
+
+ _scriptStack.push_back(it->_value);
+}
+
+void Runtime::scriptOpSoundName(ScriptArg_t arg) {
+ const Common::String sndName = _scriptSet->strings[arg];
+
+ warning("Sound IDs are not implemented yet");
+
+ int32 soundID = 0;
+ for (uint i = 0; i < 4; i++)
+ soundID = soundID * 10 + (sndName[i] - '0');
+
+ _scriptStack.push_back(soundID);
+}
+
void Runtime::scriptOpCursorName(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpCheckValue(ScriptArg_t arg) {
@@ -911,4 +1064,5 @@ void Runtime::drawFrame() {
_system->updateScreen();
}
+
} // End of namespace VCruise
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 77dd957813c..ab514e0c05a 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -23,6 +23,7 @@
#define VCRUISE_RUNTIME_H
#include "common/hashmap.h"
+#include "common/keyboard.h"
#include "vcruise/detection.h"
@@ -44,6 +45,8 @@ class AVIDecoder;
namespace VCruise {
+static const uint kNumDirections = 8;
+
class AudioPlayer;
class TextParser;
struct ScriptSet;
@@ -92,7 +95,6 @@ struct MapScreenDirectionDef {
struct MapDef {
static const uint kNumScreens = 96;
- static const uint kNumDirections = 8;
static const uint kFirstScreen = 0xa0;
Common::SharedPtr<MapScreenDirectionDef> screenDirections[kNumScreens][kNumDirections];
@@ -113,6 +115,11 @@ public:
bool runFrame();
void drawFrame();
+ void onLButtonDown(int16 x, int16 y);
+ void onLButtonUp(int16 x, int16 y);
+ void onMouseMove(int16 x, int16 y);
+ void onKeyDown(Common::KeyCode keyCode);
+
private:
enum IndexParseType {
kIndexParseTypeNone,
@@ -194,6 +201,7 @@ private:
void scriptOpMusic(ScriptArg_t arg);
void scriptOpMusicUp(ScriptArg_t arg);
+ void scriptOpMusicDn(ScriptArg_t arg);
void scriptOpParm1(ScriptArg_t arg);
void scriptOpParm2(ScriptArg_t arg);
void scriptOpParm3(ScriptArg_t arg);
@@ -246,6 +254,18 @@ private:
uint _screenNumber;
uint _direction;
+ AnimationDef _panLeftAnimationDef;
+ AnimationDef _panRightAnimationDef;
+ bool _havePanAnimations;
+
+ AnimationDef _idleAnimations[kNumDirections];
+ bool _haveIdleAnimations[kNumDirections];
+
+ Common::HashMap<uint32, int32> _variables;
+
+ static const uint kPanLeftInteraction = 1;
+ static const uint kPanRightInteraction = 3;
+
uint _loadedRoomNumber;
uint _activeScreenNumber;
bool _havePendingScreenChange;
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 99573faabff..8701a433064 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -376,6 +376,7 @@ static ScriptNamedInstruction g_namedInstructions[] = {
{"soundL2", ProtoOp::kProtoOpScript, ScriptOps::kSoundL2},
{"music", ProtoOp::kProtoOpScript, ScriptOps::kMusic},
{"musicUp", ProtoOp::kProtoOpScript, ScriptOps::kMusicUp},
+ {"musicDn", ProtoOp::kProtoOpScript, ScriptOps::kMusicDn},
{"parm1", ProtoOp::kProtoOpScript, ScriptOps::kParm1},
{"parm2", ProtoOp::kProtoOpScript, ScriptOps::kParm2},
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 8d2e7402eab..b8e86f780f8 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -65,6 +65,7 @@ enum ScriptOp {
kSoundL2,
kMusic,
kMusicUp,
+ kMusicDn,
kParm1,
kParm2,
kParm3,
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 39d59ac4ac1..b87d2c6d18a 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -44,6 +44,18 @@ void VCruiseEngine::handleEvents() {
while (eventMan->pollEvent(evt)) {
switch (evt.type) {
+ case Common::EVENT_LBUTTONDOWN:
+ _runtime->onLButtonDown(evt.mouse.x, evt.mouse.y);
+ break;
+ case Common::EVENT_LBUTTONUP:
+ _runtime->onLButtonUp(evt.mouse.x, evt.mouse.y);
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ _runtime->onMouseMove(evt.mouse.x, evt.mouse.y);
+ break;
+ case Common::EVENT_KEYDOWN:
+ _runtime->onKeyDown(evt.kbd.keycode);
+ break;
default:
break;
}
Commit: db5c7050f291464d676febbed9eed88ceed6c534
https://github.com/scummvm/scummvm/commit/db5c7050f291464d676febbed9eed88ceed6c534
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Rename detection entry from GOG to DVD
Changed paths:
engines/vcruise/detection_tables.h
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index 44b555f485f..c6ac0508c0c 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -30,10 +30,10 @@ namespace VCruise {
static const VCruiseGameDescription gameDescriptions[] = {
- { // Reah: Face the Unknown, GOG downloadable version
+ { // Reah: Face the Unknown, DVD/digital version
{
"reah",
- "GOG",
+ "DVD",
{
{"Reah.exe", 0, "60ec19c53f1323cc7f0314f98d396283", 304128},
AD_LISTEND
Commit: 9df627d4972abac3fbeff269d589f72560459aaf
https://github.com/scummvm/scummvm/commit/9df627d4972abac3fbeff269d589f72560459aaf
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Add game ID initializer for end marker.
Changed paths:
engines/vcruise/detection.h
engines/vcruise/detection_tables.h
diff --git a/engines/vcruise/detection.h b/engines/vcruise/detection.h
index 8e7c8c64efc..373ba7a482b 100644
--- a/engines/vcruise/detection.h
+++ b/engines/vcruise/detection.h
@@ -27,8 +27,10 @@
namespace VCruise {
enum VCruiseGameID {
- GID_REAH = 0,
- GID_SCHIZM = 1,
+ GID_UNKNOWN = 0,
+
+ GID_REAH = 1,
+ GID_SCHIZM = 2,
};
struct VCruiseGameDescription {
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index c6ac0508c0c..15883611af0 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -45,7 +45,7 @@ static const VCruiseGameDescription gameDescriptions[] = {
},
GID_REAH,
},
- { AD_TABLE_END_MARKER }
+ { AD_TABLE_END_MARKER, GID_UNKNOWN }
};
} // End of namespace MTropolis
Commit: 3cbb072dd1d353f28dfc5f7606027a308d4f3758
https://github.com/scummvm/scummvm/commit/3cbb072dd1d353f28dfc5f7606027a308d4f3758
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Remove unused variable
Changed paths:
engines/vcruise/script.cpp
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 8701a433064..42b8bb7a549 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -752,9 +752,6 @@ uint ScriptCompiler::indexString(const Common::String &str) {
}
Common::SharedPtr<ScriptSet> compileLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath) {
-
- uint cipherOffset = 255u - (streamSize % 255u);
-
LogicUnscrambleStream unscrambleStream(&stream, streamSize);
TextParser parser(&unscrambleStream);
Commit: 10273c1ad03d1574488e901aed912145a1187773
https://github.com/scummvm/scummvm/commit/10273c1ad03d1574488e901aed912145a1187773
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Quiet some warnings
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 32ea00f9a8e..d734454ef18 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -811,7 +811,7 @@ void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
TAKE_STACK(kAnimDefStackArgs + 2);
if (stackArgs[kAnimDefStackArgs] != 0)
- warning("sanimL second operand wasn't zero (what does that do??)");
+ warning("sanimL second operand wasn't zero (what does that do?)");
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
uint direction = stackArgs[kAnimDefStackArgs + 1];
@@ -888,6 +888,7 @@ void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
TAKE_STACK(2);
warning("Sound loop not implemented yet");
+ (void)stackArgs;
}
void Runtime::scriptOpMusic(ScriptArg_t arg) {
@@ -900,12 +901,14 @@ void Runtime::scriptOpMusicUp(ScriptArg_t arg) {
TAKE_STACK(2);
warning("Music volume changes are not implemented");
+ (void)stackArgs;
}
void Runtime::scriptOpMusicDn(ScriptArg_t arg) {
TAKE_STACK(2);
warning("Music volume changes are not implemented");
+ (void)stackArgs;
}
void Runtime::scriptOpParm1(ScriptArg_t arg) { error("Unimplemented opcode"); }
@@ -919,6 +922,7 @@ void Runtime::scriptOpRandom(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpDrop(ScriptArg_t arg) {
TAKE_STACK(1);
+ (void)stackArgs;
}
void Runtime::scriptOpDup(ScriptArg_t arg) {
Commit: 4807ac156a4248b701c426f24e7ea0dbb14026c0
https://github.com/scummvm/scummvm/commit/4807ac156a4248b701c426f24e7ea0dbb14026c0
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Remove unused variable
Changed paths:
engines/vcruise/script.cpp
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 42b8bb7a549..96a56c14103 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -253,7 +253,6 @@ void ScriptCompiler::expectNumber(uint32 &outNumber) {
void ScriptCompiler::compileRoomScriptSet(ScriptSet *ss) {
Common::SharedPtr<RoomScriptSet> roomScript;
- uint roomID = 0;
TextParserState state;
Common::String token;
Commit: 8dccd9d2b0945c841ac8b3534a772ab63bbcc27b
https://github.com/scummvm/scummvm/commit/8dccd9d2b0945c841ac8b3534a772ab63bbcc27b
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Quiet more unused variable warnings
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index d734454ef18..658f54c9a38 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -970,18 +970,21 @@ void Runtime::scriptOpBitSet1(ScriptArg_t arg) { error("Unimplemented opcode");
void Runtime::scriptOpDisc1(ScriptArg_t arg) {
// Disc check, always pass
TAKE_STACK(1);
+ (void)stackArgs;
_scriptStack.push_back(1);
}
void Runtime::scriptOpDisc2(ScriptArg_t arg) {
// Disc check, always pass
TAKE_STACK(2);
+ (void)stackArgs;
_scriptStack.push_back(1);
}
void Runtime::scriptOpDisc3(ScriptArg_t arg) {
// Disc check, always pass
TAKE_STACK(3);
+ (void)stackArgs;
_scriptStack.push_back(1);
}
Commit: 0d77df259c4f528d0fabf1cb67d71dd7d21eeaa1
https://github.com/scummvm/scummvm/commit/0d77df259c4f528d0fabf1cb67d71dd7d21eeaa1
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Only allow real 32-bit formats, permit 555 RGB.
Changed paths:
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index b87d2c6d18a..dda9e4c0ef5 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -65,14 +65,17 @@ void VCruiseEngine::handleEvents() {
Common::Error VCruiseEngine::run() {
Common::List<Graphics::PixelFormat> pixelFormats = _system->getSupportedFormats();
- const Graphics::PixelFormat *fmt16 = nullptr;
+ const Graphics::PixelFormat *fmt16_565 = nullptr;
+ const Graphics::PixelFormat *fmt16_555 = nullptr;
const Graphics::PixelFormat *fmt32 = nullptr;
for (const Graphics::PixelFormat &fmt : pixelFormats) {
- if (fmt.rBits() == 8 && fmt.gBits() == 8 && fmt.bBits() == 8)
+ if (fmt32 == nullptr && fmt.bytesPerPixel == 4 && fmt.rBits() == 8 && fmt.gBits() == 8 && fmt.bBits() == 8)
fmt32 = &fmt;
- if ((fmt.rBits() + fmt.gBits() + fmt.bBits()) == 16)
- fmt16 = &fmt;
+ if (fmt16_555 == nullptr && fmt.rBits() == 5 && fmt.gBits() == 5 && fmt.bBits() == 5)
+ fmt16_555 = &fmt;
+ if (fmt16_565 == nullptr && fmt.rBits() == 5 && fmt.gBits() == 6 && fmt.bBits() == 5)
+ fmt16_565 = &fmt;
}
// Figure out screen layout
@@ -115,8 +118,10 @@ Common::Error VCruiseEngine::run() {
if (fmt32)
initGraphics(size.x, size.y, fmt32);
- else if (fmt16)
- initGraphics(size.x, size.y, fmt16);
+ else if (fmt16_565)
+ initGraphics(size.x, size.y, fmt16_565);
+ else if (fmt16_555)
+ initGraphics(size.x, size.y, fmt16_555);
else
error("Unable to find a suitable graphics format");
Commit: c5956be096f4ddfef996713ebfe222a4253c68aa
https://github.com/scummvm/scummvm/commit/c5956be096f4ddfef996713ebfe222a4253c68aa
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Add Schizm and Reah CD
Changed paths:
engines/vcruise/detection_tables.h
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index 15883611af0..cdcea576603 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -45,6 +45,36 @@ static const VCruiseGameDescription gameDescriptions[] = {
},
GID_REAH,
},
+ { // Reah: Face the Unknown, 6 CD Version
+ {
+ "reah",
+ "CD",
+ {
+ {"Reah.exe", 0, "77bc7f7819cdd443f52b193529138c87", 305664},
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_REAH,
+ },
+ { // Schizm, 5 CD Version
+ {
+ "schizm",
+ "CD",
+ {
+ {"Schizm.exe", 0, "296edd26d951c3bdc4d303c4c88b27cd", 364544},
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_SCHIZM,
+ },
{ AD_TABLE_END_MARKER, GID_UNKNOWN }
};
Commit: 0ea1beacc8cb38889304ff5f9f1618873f1cf752
https://github.com/scummvm/scummvm/commit/0ea1beacc8cb38889304ff5f9f1618873f1cf752
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Fix video frame overrun
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 658f54c9a38..872afb6a581 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -239,9 +239,7 @@ bool Runtime::runWaitForAnimation() {
_system->copyRectToScreen(_gameSection.surf->getBasePtr(copyRect.left, copyRect.top), _gameSection.surf->pitch, copyRect.left + _gameSection.rect.left, copyRect.top + _gameSection.rect.top, copyRect.width(), copyRect.height());
}
- // The current frame will be the frame to be decoded on the next decode call,
- // which means if it exceeds the last frame then it's time to stop
- if (_animDecoder->getCurFrame() > static_cast<int>(_animLastFrame)) {
+ if (_animDecoder->getCurFrame() >= static_cast<int>(_animLastFrame)) {
_animDecoder->pauseVideo(true);
_animDecoderState = kAnimDecoderStatePaused;
Commit: 83dee812429b682a744a198de1c69df4845eabe1
https://github.com/scummvm/scummvm/commit/83dee812429b682a744a198de1c69df4845eabe1
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Fix formatting
Changed paths:
engines/vcruise/metaengine.cpp
diff --git a/engines/vcruise/metaengine.cpp b/engines/vcruise/metaengine.cpp
index 0994980ed8b..56c5535e8fb 100644
--- a/engines/vcruise/metaengine.cpp
+++ b/engines/vcruise/metaengine.cpp
@@ -33,14 +33,19 @@ struct Surface;
namespace VCruise {
static const ADExtraGuiOptionsMap optionsList[] = {
- {GAMEOPTION_LAUNCH_DEBUG,
- {_s("Start with debugger"),
- _s("Starts with the debugger dashboard active."),
- "vcruise_debug",
- false,
- 0,
- 0}},
- AD_EXTRA_GUI_OPTIONS_TERMINATOR};
+ {
+ GAMEOPTION_LAUNCH_DEBUG,
+ {
+ _s("Start with debugger"),
+ _s("Starts with the debugger dashboard active."),
+ "vcruise_debug",
+ false,
+ 0,
+ 0
+ }
+ },
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
} // End of namespace VCruise
Commit: bfa24934b7b765dc8a7cdd7d8dfedbe58621dd15
https://github.com/scummvm/scummvm/commit/bfa24934b7b765dc8a7cdd7d8dfedbe58621dd15
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Fix script miscompilation
Changed paths:
engines/vcruise/script.cpp
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 96a56c14103..6d104aead69 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -320,6 +320,7 @@ void ScriptCompiler::compileScreenScriptSet(ScreenScriptSet *sss) {
codeGenScript(protoScript, *currentScript);
currentScript.reset(new Script());
+ protoScript.reset();
sss->interactionScripts[interactionNumber] = currentScript;
} else if (compileInstructionToken(protoScript, token)) {
Commit: 2f69cda8f0ed7cbc184924cbf27ed0645393dfd7
https://github.com/scummvm/scummvm/commit/2f69cda8f0ed7cbc184924cbf27ed0645393dfd7
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Implement debug mode and some idle state functionality
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 872afb6a581..580ba39a54a 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -25,6 +25,8 @@
#include "common/stream.h"
#include "graphics/cursorman.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
#include "graphics/wincursor.h"
#include "graphics/managed_surface.h"
@@ -71,10 +73,16 @@ void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics:
surf->fillRect(Common::Rect(0, 0, surf->w, surf->h), 0xffffffff);
}
+Runtime::OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)) {
+}
+
+
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _havePanAnimations(0), _loadedRoomNumber(0), _activeScreenNumber(0),
- _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false), _scriptNextInstruction(0),
- _escOn(false), _loadedAnimation(0), _animFrameNumber(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped) {
+ _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false), _havePendingReturnToIdleState(false), _scriptNextInstruction(0),
+ _escOn(false), _lmbInteractionState(false), _debugMode(false),
+ _loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped),
+ _animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleInteractionID(0) {
for (uint i = 0; i < kNumDirections; i++)
_haveIdleAnimations[i] = false;
@@ -140,6 +148,24 @@ void Runtime::loadCursors(const char *exeName) {
}
}
}
+
+ if (_gameID == GID_REAH) {
+ _namedCursors["CUR_TYL"] = 13; // Tyl = back
+ //_namedCursors["CUR_NIC"] = ? // Nic = nothing
+ //_namedCursors["CUR_WEZ"] = 50 // Wez = call? FIXME
+ _namedCursors["CUR_LUPA"] = 21; // Lupa = magnifier, could be 36 too?
+ _namedCursors["CUR_NAC"] = 35; // Nac = top? Not sure. But this is the finger pointer.
+ _namedCursors["CUR_PRZOD"] = 1; // Przod = forward
+
+ // CUR_ZOSTAW is in the executable memory but appears to be unused
+ }
+}
+
+void Runtime::setDebugMode(bool debugMode) {
+ if (debugMode) {
+ _debugMode = true;
+ _gameDebugBackBuffer.init(_gameSection.rect, _gameSection.surf->format);
+ }
}
bool Runtime::runFrame() {
@@ -167,6 +193,9 @@ bool Runtime::runFrame() {
}
}
+ // Discard any unconsumed OS events
+ _pendingEvents.clear();
+
return true;
}
@@ -191,17 +220,71 @@ bool Runtime::runIdle() {
if (_havePendingScreenChange) {
_havePendingScreenChange = false;
+ _havePendingReturnToIdleState = true;
+
changeToScreen(_roomNumber, _screenNumber);
return true;
}
+ if (_havePendingReturnToIdleState) {
+ _havePendingReturnToIdleState = false;
+
+ returnToIdleState();
+ return true;
+ }
+
+ if (_animPlayWhileIdle) {
+ bool animEnded = false;
+ continuePlayingAnimation(true, animEnded);
+ }
+
+ if (_debugMode)
+ drawDebugOverlay();
+
+ OSEvent osEvent;
+ while (popOSEvent(osEvent)) {
+ if (osEvent.type == kOSEventTypeMouseMove)
+ dischargeIdleMouseMove();
+ }
+
return false;
}
bool Runtime::runWaitForAnimation() {
- if (!_animDecoder) {
+ bool animEnded = false;
+ continuePlayingAnimation(false, animEnded);
+
+ if (animEnded) {
_gameState = kGameStateScript;
return true;
+ } else {
+ // Still waiting, check events
+ OSEvent evt;
+ while (popOSEvent(evt)) {
+ if (evt.type == kOSEventTypeKeyDown && evt.keyCode == Common::KEYCODE_ESCAPE) {
+ if (_escOn) {
+ // Terminate the animation
+ if (_animDecoderState == kAnimDecoderStatePlaying) {
+ _animDecoder->pauseVideo(true);
+ _animDecoderState = kAnimDecoderStatePaused;
+ }
+ _gameState = kGameStateScript;
+ return true;
+ }
+ }
+ }
+
+ // Yield
+ return false;
+ }
+}
+
+void Runtime::continuePlayingAnimation(bool loop, bool &outAnimationEnded) {
+ outAnimationEnded = false;
+
+ if (!_animDecoder) {
+ outAnimationEnded = true;
+ return;
}
bool needsFirstFrame = false;
@@ -222,12 +305,27 @@ bool Runtime::runWaitForAnimation() {
if (!needNewFrame)
break;
+ // We check this here for timing reasons: The no-loop case after the draw terminates the animation as soon as the last frame
+ // starts delaying without waiting for the time until the next frame to expire.
+ // The loop check here DOES wait for the time until next frame to expire.
+ if (loop && _animPendingDecodeFrame > _animLastFrame) {
+ if (!_animDecoder->seekToFrame(_animFirstFrame)) {
+ outAnimationEnded = true;
+ return;
+ }
+
+ _animPendingDecodeFrame = _animFirstFrame;
+ }
+
const Graphics::Surface *surface = _animDecoder->decodeNextFrame();
if (!surface) {
- _gameState = kGameStateScript;
- return true;
+ outAnimationEnded = true;
+ return;
}
+ _animDisplayingFrame = _animPendingDecodeFrame;
+ _animPendingDecodeFrame++;
+
Common::Rect copyRect = Common::Rect(0, 0, surface->w, surface->h);
Common::Rect constraintRect = Common::Rect(0, 0, _gameSection.rect.width(), _gameSection.rect.height());
@@ -236,20 +334,31 @@ bool Runtime::runWaitForAnimation() {
if (copyRect.isValidRect() || !copyRect.isEmpty()) {
_gameSection.surf->blitFrom(*surface, copyRect, copyRect);
- _system->copyRectToScreen(_gameSection.surf->getBasePtr(copyRect.left, copyRect.top), _gameSection.surf->pitch, copyRect.left + _gameSection.rect.left, copyRect.top + _gameSection.rect.top, copyRect.width(), copyRect.height());
+ drawSectionToScreen(_gameSection, copyRect);
}
- if (_animDecoder->getCurFrame() >= static_cast<int>(_animLastFrame)) {
- _animDecoder->pauseVideo(true);
- _animDecoderState = kAnimDecoderStatePaused;
+ if (!loop) {
+ if (_animDisplayingFrame >= static_cast<int>(_animLastFrame)) {
+ _animDecoder->pauseVideo(true);
+ _animDecoderState = kAnimDecoderStatePaused;
- _gameState = kGameStateScript;
- return true;
+ outAnimationEnded = true;
+ return;
+ }
}
}
+}
- // Pump and handle events
- return false;
+void Runtime::drawSectionToScreen(const RenderSection §ion, const Common::Rect &rect) {
+ if (_debugMode && (&_gameSection == §ion)) {
+ _gameDebugBackBuffer.surf->blitFrom(*section.surf, rect, rect);
+ commitSectionToScreen(_gameDebugBackBuffer, rect);
+ } else
+ commitSectionToScreen(section, rect);
+}
+
+void Runtime::commitSectionToScreen(const RenderSection §ion, const Common::Rect &rect) {
+ _system->copyRectToScreen(section.surf->getBasePtr(rect.left, rect.top), section.surf->pitch, rect.left + section.rect.left, rect.top + section.rect.top, rect.width(), rect.height());
}
#ifdef DISPATCH_OP
@@ -366,6 +475,30 @@ void Runtime::terminateScript() {
changeToScreen(_roomNumber, _screenNumber);
}
+bool Runtime::popOSEvent(OSEvent &evt) {
+ OSEvent tempEvent;
+
+ while (_pendingEvents.size() > 0) {
+ tempEvent = _pendingEvents[0];
+ _pendingEvents.remove_at(0);
+
+ // Button events automatically inject a mouse move position
+ if (tempEvent.type == kOSEventTypeMouseMove) {
+ // If this didn't actually change the mouse position, which is common with synthetic mouse events,
+ // discard the event.
+ if (_mousePos == tempEvent.pos)
+ continue;
+
+ _mousePos = tempEvent.pos;
+ }
+
+ evt = tempEvent;
+ return true;
+ }
+
+ return false;
+}
+
void Runtime::loadIndex() {
Common::FSNode indexFSNode = _logDir.getChild("Index.txt");
@@ -487,7 +620,7 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
if (screenScriptIt != screenScriptsMap.end()) {
const Common::SharedPtr<Script> &script = screenScriptIt->_value->entryScript;
if (script)
- activateScript(script);
+ activateScript(script, false);
}
}
}
@@ -496,6 +629,63 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
for (uint i = 0; i < kNumDirections; i++)
_haveIdleAnimations[i] = false;
+
+ _havePendingReturnToIdleState = true;
+ }
+}
+
+void Runtime::returnToIdleState() {
+ _animPlayWhileIdle = false;
+
+ if (_haveIdleAnimations[_direction]) {
+ changeAnimation(_idleAnimations[_direction]);
+ _animPlayWhileIdle = true;
+ }
+
+ _idleIsOnInteraction = false;
+
+ changeToCursor(_cursors[kCursorArrow]);
+ dischargeIdleMouseMove();
+}
+
+void Runtime::changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor) {
+ if (!cursor)
+ CursorMan.showMouse(false);
+ else {
+ CursorMan.replaceCursor(cursor->cursors[0].cursor);
+ CursorMan.showMouse(true);
+ }
+}
+
+void Runtime::dischargeIdleMouseMove() {
+ const MapScreenDirectionDef *sdDef = _map.getScreenDirection(_screenNumber, _direction);
+
+ Common::Point relMouse(_mousePos.x - _gameSection.rect.left, _mousePos.y - _gameSection.rect.top);
+
+ bool isOnInteraction = false;
+ uint interactionID = 0;
+ if (sdDef) {
+ for (const InteractionDef &idef : sdDef->interactions) {
+ if (idef.rect.contains(relMouse)) {
+ isOnInteraction = true;
+ interactionID = idef.interactionID;
+ break;
+ }
+ }
+ }
+
+ if (isOnInteraction && (_idleIsOnInteraction == false || interactionID != _idleInteractionID)) {
+ _idleIsOnInteraction = true;
+ _idleInteractionID = interactionID;
+
+ // New interaction, is there a script?
+ Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
+
+ if (script)
+ activateScript(script, false);
+ } else if (!isOnInteraction && _idleIsOnInteraction) {
+ _idleIsOnInteraction = false;
+ changeToCursor(_cursors[kCursorArrow]);
}
}
@@ -596,7 +786,8 @@ void Runtime::changeAnimation(const AnimationDef &animDef) {
}
_animDecoder->seekToFrame(animDef.firstFrame);
- _animFrameNumber = animDef.firstFrame;
+ _animPendingDecodeFrame = animDef.firstFrame;
+ _animFirstFrame = animDef.firstFrame;
_animLastFrame = animDef.lastFrame;
}
@@ -609,10 +800,11 @@ AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
return def;
}
-void Runtime::activateScript(const Common::SharedPtr<Script> &script) {
+void Runtime::activateScript(const Common::SharedPtr<Script> &script, bool lmbInteractionState) {
if (script->instrs.size() == 0)
return;
+ _lmbInteractionState = false;
_activeScript = script;
_scriptNextInstruction = 0;
_gameState = kGameStateScript;
@@ -738,29 +930,92 @@ void Runtime::allocateRoomsUpTo(uint roomNumber) {
}
}
+void Runtime::drawDebugOverlay() {
+ if (!_debugMode)
+ return;
+
+ const Graphics::PixelFormat pixFmt = _gameDebugBackBuffer.surf->format;
+
+ const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
+
+ uint32 whiteColor = pixFmt.ARGBToColor(255, 255, 255, 255);
+ uint32 blackColor = pixFmt.ARGBToColor(255, 0, 0, 0);
+
+ const MapScreenDirectionDef *sdDef = _map.getScreenDirection(_screenNumber, _direction);
+ if (sdDef) {
+ for (const InteractionDef &idef : sdDef->interactions) {
+ Common::Rect rect = idef.rect;
+
+ Common::String label = Common::String::format("0%x", static_cast<int>(idef.interactionID));
+
+ Graphics::ManagedSurface *surf = _gameDebugBackBuffer.surf.get();
+
+ if (font) {
+ Common::Point pt = Common::Point(rect.left + 2, rect.top + 2);
+
+ font->drawString(surf, label, pt.x + 1, pt.y + 1, rect.width(), blackColor);
+ font->drawString(surf, label, pt.x, pt.y, rect.width(), whiteColor);
+ }
+
+ surf->frameRect(Common::Rect(rect.left + 1, rect.top + 1, rect.right + 1, rect.bottom + 1), blackColor);
+ surf->frameRect(rect, whiteColor);
+ }
+ }
+
+ commitSectionToScreen(_gameDebugBackBuffer, Common::Rect(0, 0, _gameDebugBackBuffer.rect.width(), _gameDebugBackBuffer.rect.height()));
+}
+
+Common::SharedPtr<Script> Runtime::findScriptForInteraction(uint interactionID) const {
+ if (_scriptSet) {
+ Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> >::const_iterator roomScriptIt = _scriptSet->roomScripts.find(_roomNumber);
+
+ if (roomScriptIt != _scriptSet->roomScripts.end()) {
+ const RoomScriptSet &roomScriptSet = *roomScriptIt->_value;
+
+ Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> >::const_iterator screenScriptIt = roomScriptSet.screenScripts.find(_screenNumber);
+ if (screenScriptIt != roomScriptSet.screenScripts.end()) {
+ const ScreenScriptSet &screenScriptSet = *screenScriptIt->_value;
+
+ Common::HashMap<uint, Common::SharedPtr<Script> >::const_iterator interactionScriptIt = screenScriptSet.interactionScripts.find(interactionID);
+ if (interactionScriptIt != screenScriptSet.interactionScripts.end())
+ return interactionScriptIt->_value;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
void Runtime::onLButtonDown(int16 x, int16 y) {
+ onMouseMove(x, y);
+
+ OSEvent evt;
+ evt.type = kOSEventTypeLButtonDown;
+ evt.pos = Common::Point(x, y);
+ _pendingEvents.push_back(evt);
}
void Runtime::onLButtonUp(int16 x, int16 y) {
+ onMouseMove(x, y);
+
+ OSEvent evt;
+ evt.type = kOSEventTypeLButtonUp;
+ evt.pos = Common::Point(x, y);
+ _pendingEvents.push_back(evt);
}
void Runtime::onMouseMove(int16 x, int16 y) {
+ OSEvent evt;
+ evt.type = kOSEventTypeMouseMove;
+ evt.pos = Common::Point(x, y);
+ _pendingEvents.push_back(evt);
}
void Runtime::onKeyDown(Common::KeyCode keyCode) {
- if (keyCode == Common::KEYCODE_ESCAPE) {
- if (_gameState == kGameStateWaitingForAnimation) {
- if (_escOn) {
- // Terminate the animation
- if (_animDecoderState == kAnimDecoderStatePlaying) {
- _animDecoder->pauseVideo(true);
- _animDecoderState = kAnimDecoderStatePaused;
- }
- _gameState = kGameStateScript;
- return;
- }
- }
- }
+ OSEvent evt;
+ evt.type = kOSEventTypeKeyDown;
+ evt.keyCode = keyCode;
+ _pendingEvents.push_back(evt);
}
#ifdef CHECK_STACK
@@ -836,7 +1091,7 @@ void Runtime::scriptOpAnimS(ScriptArg_t arg) {
changeAnimation(animDef);
- _gameState = kGameStateWaitingForAnimation; // FIXME
+ _gameState = kGameStateWaitingForAnimation;
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
_direction = stackArgs[kAnimDefStackArgs + 1];
_havePendingScreenChange = true;
@@ -876,9 +1131,22 @@ void Runtime::scriptOpVarStore(ScriptArg_t arg) {
_variables[varID] = stackArgs[0];
}
-void Runtime::scriptOpSetCursor(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpSetCursor(ScriptArg_t arg) {
+ TAKE_STACK(1);
+
+ if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= _cursors.size())
+ error("Invalid cursor ID");
+
+ changeToCursor(_cursors[stackArgs[0]]);
+}
+
void Runtime::scriptOpSetRoom(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpLMB(ScriptArg_t arg) { error("Unimplemented opcode"); }
+
+void Runtime::scriptOpLMB(ScriptArg_t arg) {
+ if (!_lmbInteractionState)
+ terminateScript();
+}
+
void Runtime::scriptOpLMB1(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpSoundS1(ScriptArg_t arg) { error("Unimplemented opcode"); }
@@ -1050,7 +1318,17 @@ void Runtime::scriptOpSoundName(ScriptArg_t arg) {
_scriptStack.push_back(soundID);
}
-void Runtime::scriptOpCursorName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+void Runtime::scriptOpCursorName(ScriptArg_t arg) {
+ const Common::String &cursorName = _scriptSet->strings[arg];
+
+ Common::HashMap<Common::String, StackValue_t>::const_iterator namedCursorIt = _namedCursors.find(cursorName);
+ if (namedCursorIt == _namedCursors.end()) {
+ error("Unimplemented cursor name '%s'", cursorName.c_str());
+ return;
+ }
+
+ _scriptStack.push_back(namedCursorIt->_value);
+}
void Runtime::scriptOpCheckValue(ScriptArg_t arg) {
PEEK_STACK(1);
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index ab514e0c05a..042e7cc9fb5 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -111,6 +111,7 @@ public:
void initSections(Common::Rect gameRect, Common::Rect menuRect, Common::Rect trayRect, const Graphics::PixelFormat &pixFmt);
void loadCursors(const char *exeName);
+ void setDebugMode(bool debugMode);
bool runFrame();
void drawFrame();
@@ -151,6 +152,24 @@ private:
void init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt);
};
+ enum OSEventType {
+ kOSEventTypeInvalid,
+
+ kOSEventTypeMouseMove,
+ kOSEventTypeLButtonDown,
+ kOSEventTypeLButtonUp,
+
+ kOSEventTypeKeyDown,
+ };
+
+ struct OSEvent {
+ OSEvent();
+
+ OSEventType type;
+ Common::Point pos;
+ Common::KeyCode keyCode;
+ };
+
typedef int32 ScriptArg_t;
typedef int32 StackValue_t;
@@ -158,10 +177,18 @@ private:
bool runIdle();
bool runScript();
bool runWaitForAnimation();
+ void continuePlayingAnimation(bool loop, bool &outEndedAnimation);
+ void drawSectionToScreen(const RenderSection §ion, const Common::Rect &rect);
+ void commitSectionToScreen(const RenderSection §ion, const Common::Rect &rect);
void terminateScript();
+ bool popOSEvent(OSEvent &evt);
+
void loadIndex();
void changeToScreen(uint roomNumber, uint screenNumber);
+ void returnToIdleState();
+ void changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor);
+ void dischargeIdleMouseMove();
void loadMap(Common::SeekableReadStream *stream);
void changeMusicTrack(int musicID);
@@ -169,11 +196,16 @@ private:
AnimationDef stackArgsToAnimDef(const StackValue_t *args) const;
- void activateScript(const Common::SharedPtr<Script> &script);
+ void activateScript(const Common::SharedPtr<Script> &script, bool lmbInteractionState);
bool parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath);
void allocateRoomsUpTo(uint roomNumber);
+ void drawDebugOverlay();
+
+ Common::SharedPtr<Script> findScriptForInteraction(uint interactionID) const;
+
+ // Script things
void scriptOpNumber(ScriptArg_t arg);
void scriptOpRotate(ScriptArg_t arg);
void scriptOpAngle(ScriptArg_t arg);
@@ -249,6 +281,8 @@ private:
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort; // Cursors indexed as CURSOR_#
+ Common::HashMap<Common::String, StackValue_t> _namedCursors;
+
OSystem *_system;
uint _roomNumber; // Room number can be changed independently of the loaded room, the screen doesn't change until a command changes it
uint _screenNumber;
@@ -269,9 +303,12 @@ private:
uint _loadedRoomNumber;
uint _activeScreenNumber;
bool _havePendingScreenChange;
+ bool _havePendingReturnToIdleState;
GameState _gameState;
bool _escOn;
+ bool _lmbInteractionState;
+ bool _debugMode;
VCruiseGameID _gameID;
@@ -292,19 +329,31 @@ private:
Common::SharedPtr<Video::AVIDecoder> _animDecoder;
AnimDecoderState _animDecoderState;
- uint _animFrameNumber;
+ uint _animPendingDecodeFrame;
+ uint _animDisplayingFrame;
+ uint _animFirstFrame;
uint _animLastFrame;
uint _loadedAnimation;
+ bool _animPlayWhileIdle;
+
+ bool _idleIsOnInteraction;
+ uint _idleInteractionID;
Audio::Mixer *_mixer;
MapDef _map;
RenderSection _gameSection;
+ RenderSection _gameDebugBackBuffer;
RenderSection _menuSection;
RenderSection _traySection;
+ Common::Point _mousePos;
+ Common::Array<OSEvent> _pendingEvents;
+
static const uint kAnimDefStackArgs = 3;
+
+ static const uint kCursorArrow = 0;
};
} // End of namespace VCruise
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index dda9e4c0ef5..d6857288fe3 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -132,6 +132,10 @@ Common::Error VCruiseEngine::run() {
_runtime->loadCursors(exeName);
+ if (ConfMan.getBool("vcruise_debug")) {
+ _runtime->setDebugMode(true);
+ }
+
// Run the game
while (!shouldQuit()) {
handleEvents();
Commit: 0b7c19a0702d1c6ff873552b12600640d64c4e83
https://github.com/scummvm/scummvm/commit/0b7c19a0702d1c6ff873552b12600640d64c4e83
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Combine env vars, make some tweaks for panorama input detection
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 580ba39a54a..54c2caf3abc 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -67,6 +67,9 @@ const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direct
return screenDirections[screen][direction].get();
}
+ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false) {
+}
+
void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt) {
rect = paramRect;
surf.reset(new Graphics::ManagedSurface(paramRect.width(), paramRect.height(), fmt));
@@ -76,13 +79,13 @@ void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics:
Runtime::OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)) {
}
-
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _havePanAnimations(0), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false), _havePendingReturnToIdleState(false), _scriptNextInstruction(0),
- _escOn(false), _lmbInteractionState(false), _debugMode(false),
+ _escOn(false), _debugMode(false), _panoramaDirectionFlags(0),
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped),
- _animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleInteractionID(0) {
+ _animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleInteractionID(0),
+ _lmbDown(false), _lmbDragging(false), _lmbDownTime(0) {
for (uint i = 0; i < kNumDirections; i++)
_haveIdleAnimations[i] = false;
@@ -194,7 +197,10 @@ bool Runtime::runFrame() {
}
// Discard any unconsumed OS events
- _pendingEvents.clear();
+ OSEvent evt;
+ while (popOSEvent(evt)) {
+ // Do nothing
+ }
return true;
}
@@ -216,7 +222,6 @@ bool Runtime::bootGame() {
}
bool Runtime::runIdle() {
-
if (_havePendingScreenChange) {
_havePendingScreenChange = false;
@@ -489,7 +494,27 @@ bool Runtime::popOSEvent(OSEvent &evt) {
if (_mousePos == tempEvent.pos)
continue;
+ if (_lmbDown && tempEvent.pos != _lmbDownPos)
+ _lmbDragging = true;
+
_mousePos = tempEvent.pos;
+ } else if (tempEvent.type == kOSEventTypeLButtonDown) {
+ // Discard LButtonDown events missing a matching release (can happen if e.g. user holds button down
+ // and then alt-tabs out of the process on Windows)
+ if (_lmbDown)
+ continue;
+
+ _lmbDown = true;
+ _lmbDragging = false;
+ _lmbDownTime = tempEvent.timestamp;
+ _lmbDownPos = tempEvent.pos;
+ } else if (tempEvent.type == kOSEventTypeLButtonUp) {
+ // Discard LButtonUp events missing a matching down
+ if (!_lmbDown)
+ continue;
+
+ _lmbDown = false;
+ // Don't reset _lmbDragging - Want that available to determine if this was a drag release or click
}
evt = tempEvent;
@@ -499,6 +524,13 @@ bool Runtime::popOSEvent(OSEvent &evt) {
return false;
}
+void Runtime::queueOSEvent(const OSEvent &evt) {
+ OSEvent timedEvt = evt;
+ timedEvt.timestamp = g_system->getMillis();
+
+ _pendingEvents.push_back(timedEvt);
+}
+
void Runtime::loadIndex() {
Common::FSNode indexFSNode = _logDir.getChild("Index.txt");
@@ -620,7 +652,7 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
if (screenScriptIt != screenScriptsMap.end()) {
const Common::SharedPtr<Script> &script = screenScriptIt->_value->entryScript;
if (script)
- activateScript(script, false);
+ activateScript(script, ScriptEnvironmentVars());
}
}
}
@@ -644,6 +676,8 @@ void Runtime::returnToIdleState() {
_idleIsOnInteraction = false;
+ detectPanoramaDirections();
+
changeToCursor(_cursors[kCursorArrow]);
dischargeIdleMouseMove();
}
@@ -682,7 +716,7 @@ void Runtime::dischargeIdleMouseMove() {
Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
if (script)
- activateScript(script, false);
+ activateScript(script, ScriptEnvironmentVars());
} else if (!isOnInteraction && _idleIsOnInteraction) {
_idleIsOnInteraction = false;
changeToCursor(_cursors[kCursorArrow]);
@@ -800,11 +834,11 @@ AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
return def;
}
-void Runtime::activateScript(const Common::SharedPtr<Script> &script, bool lmbInteractionState) {
+void Runtime::activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars) {
if (script->instrs.size() == 0)
return;
- _lmbInteractionState = false;
+ _scriptEnv = envVars;
_activeScript = script;
_scriptNextInstruction = 0;
_gameState = kGameStateScript;
@@ -986,13 +1020,21 @@ Common::SharedPtr<Script> Runtime::findScriptForInteraction(uint interactionID)
return nullptr;
}
+void Runtime::detectPanoramaDirections() {
+ _panoramaDirectionFlags = 0;
+
+ if (_havePanAnimations)
+ _panoramaDirectionFlags |= kPanoramaHorizFlags;
+}
+
void Runtime::onLButtonDown(int16 x, int16 y) {
onMouseMove(x, y);
OSEvent evt;
evt.type = kOSEventTypeLButtonDown;
evt.pos = Common::Point(x, y);
- _pendingEvents.push_back(evt);
+
+ queueOSEvent(evt);
}
void Runtime::onLButtonUp(int16 x, int16 y) {
@@ -1001,21 +1043,24 @@ void Runtime::onLButtonUp(int16 x, int16 y) {
OSEvent evt;
evt.type = kOSEventTypeLButtonUp;
evt.pos = Common::Point(x, y);
- _pendingEvents.push_back(evt);
+
+ queueOSEvent(evt);
}
void Runtime::onMouseMove(int16 x, int16 y) {
OSEvent evt;
evt.type = kOSEventTypeMouseMove;
evt.pos = Common::Point(x, y);
- _pendingEvents.push_back(evt);
+
+ queueOSEvent(evt);
}
void Runtime::onKeyDown(Common::KeyCode keyCode) {
OSEvent evt;
evt.type = kOSEventTypeKeyDown;
evt.keyCode = keyCode;
- _pendingEvents.push_back(evt);
+
+ queueOSEvent(evt);
}
#ifdef CHECK_STACK
@@ -1143,7 +1188,7 @@ void Runtime::scriptOpSetCursor(ScriptArg_t arg) {
void Runtime::scriptOpSetRoom(ScriptArg_t arg) { error("Unimplemented opcode"); }
void Runtime::scriptOpLMB(ScriptArg_t arg) {
- if (!_lmbInteractionState)
+ if (!_scriptEnv.lmb)
terminateScript();
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 042e7cc9fb5..cd0d0dc1cb6 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -103,6 +103,12 @@ struct MapDef {
const MapScreenDirectionDef *getScreenDirection(uint screen, uint direction);
};
+struct ScriptEnvironmentVars {
+ ScriptEnvironmentVars();
+
+ bool lmb;
+};
+
class Runtime {
public:
Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
@@ -162,12 +168,21 @@ private:
kOSEventTypeKeyDown,
};
+ enum PanoramaState {
+ kPanoramaStateInactive,
+ kPanoramaStatePanningLeft,
+ kPanoramaStatePanningRight,
+ kPanoramaStatePanningUp,
+ kPanoramaStatePanningDown,
+ };
+
struct OSEvent {
OSEvent();
OSEventType type;
Common::Point pos;
Common::KeyCode keyCode;
+ uint32 timestamp;
};
typedef int32 ScriptArg_t;
@@ -183,6 +198,7 @@ private:
void terminateScript();
bool popOSEvent(OSEvent &evt);
+ void queueOSEvent(const OSEvent &evt);
void loadIndex();
void changeToScreen(uint roomNumber, uint screenNumber);
@@ -196,7 +212,7 @@ private:
AnimationDef stackArgsToAnimDef(const StackValue_t *args) const;
- void activateScript(const Common::SharedPtr<Script> &script, bool lmbInteractionState);
+ void activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars);
bool parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath);
void allocateRoomsUpTo(uint roomNumber);
@@ -204,6 +220,7 @@ private:
void drawDebugOverlay();
Common::SharedPtr<Script> findScriptForInteraction(uint interactionID) const;
+ void detectPanoramaDirections();
// Script things
void scriptOpNumber(ScriptArg_t arg);
@@ -298,7 +315,17 @@ private:
Common::HashMap<uint32, int32> _variables;
static const uint kPanLeftInteraction = 1;
+ static const uint kPanDownInteraction = 2;
static const uint kPanRightInteraction = 3;
+ static const uint kPanUpInteraction = 4;
+
+ static const uint kPanoramaLeftFlag = 1;
+ static const uint kPanoramaRightFlag = 2;
+ static const uint kPanoramaUpFlag = 4;
+ static const uint kPanoramaDownFlag = 8;
+ static const uint kPanoramaHorizFlags = (kPanoramaLeftFlag | kPanoramaRightFlag);
+
+ uint _panoramaDirectionFlags;
uint _loadedRoomNumber;
uint _activeScreenNumber;
@@ -307,7 +334,6 @@ private:
GameState _gameState;
bool _escOn;
- bool _lmbInteractionState;
bool _debugMode;
VCruiseGameID _gameID;
@@ -324,6 +350,7 @@ private:
Common::SharedPtr<Script> _activeScript;
uint _scriptNextInstruction;
Common::Array<StackValue_t> _scriptStack;
+ ScriptEnvironmentVars _scriptEnv;
Common::SharedPtr<AudioPlayer> _musicPlayer;
@@ -349,6 +376,11 @@ private:
RenderSection _traySection;
Common::Point _mousePos;
+ Common::Point _lmbDownPos;
+ uint32 _lmbDownTime;
+ bool _lmbDown;
+ bool _lmbDragging;
+
Common::Array<OSEvent> _pendingEvents;
static const uint kAnimDefStackArgs = 3;
Commit: 02df16b6afd9ccd77353f69021c22efdc925a18f
https://github.com/scummvm/scummvm/commit/02df16b6afd9ccd77353f69021c22efdc925a18f
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Clean up some code quality issues
Changed paths:
A engines/vcruise/POTFILES
engines/vcruise/detection.cpp
engines/vcruise/detection_tables.h
engines/vcruise/runtime.cpp
engines/vcruise/vcruise.cpp
engines/vcruise/vcruise.h
diff --git a/engines/vcruise/POTFILES b/engines/vcruise/POTFILES
new file mode 100644
index 00000000000..bb49b390af7
--- /dev/null
+++ b/engines/vcruise/POTFILES
@@ -0,0 +1 @@
+engines/vcruise/metaengine.cpp
diff --git a/engines/vcruise/detection.cpp b/engines/vcruise/detection.cpp
index 4780a534c39..0c0a7fae4ba 100644
--- a/engines/vcruise/detection.cpp
+++ b/engines/vcruise/detection.cpp
@@ -36,16 +36,12 @@ static const PlainGameDescriptor vCruiseGames[] = {
#include "vcruise/detection_tables.h"
-static const char *directoryGlobs[] = {
- nullptr
-};
-
class VCruiseMetaEngineDetection : public AdvancedMetaEngineDetection {
public:
VCruiseMetaEngineDetection() : AdvancedMetaEngineDetection(VCruise::gameDescriptions, sizeof(VCruise::VCruiseGameDescription), vCruiseGames) {
_guiOptions = GUIO1(GAMEOPTION_LAUNCH_DEBUG);
_maxScanDepth = 1;
- _directoryGlobs = directoryGlobs;
+ _directoryGlobs = nullptr;
_flags = kADFlagCanPlayUnknownVariants;
}
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index cdcea576603..2a3929c6ba8 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -34,10 +34,7 @@ static const VCruiseGameDescription gameDescriptions[] = {
{
"reah",
"DVD",
- {
- {"Reah.exe", 0, "60ec19c53f1323cc7f0314f98d396283", 304128},
- AD_LISTEND
- },
+ AD_ENTRY1s("Reah.exe", "60ec19c53f1323cc7f0314f98d396283", 304128),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_UNSTABLE,
@@ -49,10 +46,7 @@ static const VCruiseGameDescription gameDescriptions[] = {
{
"reah",
"CD",
- {
- {"Reah.exe", 0, "77bc7f7819cdd443f52b193529138c87", 305664},
- AD_LISTEND
- },
+ AD_ENTRY1s("Reah.exe", "77bc7f7819cdd443f52b193529138c87", 305664),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_UNSTABLE,
@@ -64,10 +58,7 @@ static const VCruiseGameDescription gameDescriptions[] = {
{
"schizm",
"CD",
- {
- {"Schizm.exe", 0, "296edd26d951c3bdc4d303c4c88b27cd", 364544},
- AD_LISTEND
- },
+ AD_ENTRY1s("Schizm.exe", "296edd26d951c3bdc4d303c4c88b27cd", 364544),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_UNSTABLE,
@@ -78,6 +69,6 @@ static const VCruiseGameDescription gameDescriptions[] = {
{ AD_TABLE_END_MARKER, GID_UNKNOWN }
};
-} // End of namespace MTropolis
+} // End of namespace VCruise
#endif
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 54c2caf3abc..759fcffa9df 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -262,26 +262,26 @@ bool Runtime::runWaitForAnimation() {
if (animEnded) {
_gameState = kGameStateScript;
return true;
- } else {
- // Still waiting, check events
- OSEvent evt;
- while (popOSEvent(evt)) {
- if (evt.type == kOSEventTypeKeyDown && evt.keyCode == Common::KEYCODE_ESCAPE) {
- if (_escOn) {
- // Terminate the animation
- if (_animDecoderState == kAnimDecoderStatePlaying) {
- _animDecoder->pauseVideo(true);
- _animDecoderState = kAnimDecoderStatePaused;
- }
- _gameState = kGameStateScript;
- return true;
+ }
+
+ // Still waiting, check events
+ OSEvent evt;
+ while (popOSEvent(evt)) {
+ if (evt.type == kOSEventTypeKeyDown && evt.keyCode == Common::KEYCODE_ESCAPE) {
+ if (_escOn) {
+ // Terminate the animation
+ if (_animDecoderState == kAnimDecoderStatePlaying) {
+ _animDecoder->pauseVideo(true);
+ _animDecoderState = kAnimDecoderStatePaused;
}
+ _gameState = kGameStateScript;
+ return true;
}
}
-
- // Yield
- return false;
}
+
+ // Yield
+ return false;
}
void Runtime::continuePlayingAnimation(bool loop, bool &outAnimationEnded) {
@@ -343,7 +343,7 @@ void Runtime::continuePlayingAnimation(bool loop, bool &outAnimationEnded) {
}
if (!loop) {
- if (_animDisplayingFrame >= static_cast<int>(_animLastFrame)) {
+ if (_animDisplayingFrame >= _animLastFrame) {
_animDecoder->pauseVideo(true);
_animDecoderState = kAnimDecoderStatePaused;
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index d6857288fe3..9f6cc537485 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -33,6 +33,8 @@ namespace VCruise {
VCruiseEngine::VCruiseEngine(OSystem *syst, const VCruiseGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
const Common::FSNode gameDataDir(ConfMan.get("path"));
+
+ SearchMan.addDirectory(gameDataDir.getPath(), gameDataDir);
}
VCruiseEngine::~VCruiseEngine() {
@@ -84,18 +86,15 @@ Common::Error VCruiseEngine::run() {
Common::Point videoSize;
Common::Point traySize;
Common::Point menuBarSize;
- const char *exeName = nullptr;
if (_gameDescription->gameID == GID_REAH) {
videoSize = Common::Point(608, 348);
menuBarSize = Common::Point(640, 44);
traySize = Common::Point(640, 88);
- exeName = "Reah.exe";
} else if (_gameDescription->gameID == GID_SCHIZM) {
videoSize = Common::Point(640, 360);
menuBarSize = Common::Point(640, 32);
traySize = Common::Point(640, 88);
- exeName = "Schizm.exe";
} else {
error("Unknown game");
}
@@ -130,6 +129,8 @@ Common::Error VCruiseEngine::run() {
_runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID));
_runtime->initSections(_videoRect, _menuBarRect, _trayRect, _system->getScreenFormat());
+ const char *exeName = _gameDescription->desc.filesDescriptions[0].fileName;
+
_runtime->loadCursors(exeName);
if (ConfMan.getBool("vcruise_debug")) {
@@ -159,7 +160,7 @@ void VCruiseEngine::pauseEngineIntern(bool pause) {
bool VCruiseEngine::hasFeature(EngineFeature f) const {
switch (f) {
case kSupportsReturnToLauncher:
- case kSupportsSavingDuringRuntime:
+ //case kSupportsSavingDuringRuntime:
return true;
default:
return false;
diff --git a/engines/vcruise/vcruise.h b/engines/vcruise/vcruise.h
index b5551c5e169..eb16bc7adcf 100644
--- a/engines/vcruise/vcruise.h
+++ b/engines/vcruise/vcruise.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef VCRUISE_VCRUISE_H
-#define VCRUISE_VCRUISE_H
+#ifndef VCRUISE_H
+#define VCRUISE_H
#include "engines/engine.h"
#include "common/rect.h"
@@ -76,4 +76,4 @@ private:
} // End of namespace VCruise
-#endif /* VCRUISE_VCRUISE_H */
+#endif /* VCRUISE_H */
Commit: bdcf695c131302afb8727ce78925fc832569ac94
https://github.com/scummvm/scummvm/commit/bdcf695c131302afb8727ce78925fc832569ac94
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Clean up file access
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 759fcffa9df..507e8e6f520 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -23,6 +23,7 @@
#include "common/ptr.h"
#include "common/system.h"
#include "common/stream.h"
+#include "common/file.h"
#include "graphics/cursorman.h"
#include "graphics/font.h"
@@ -81,7 +82,7 @@ Runtime::OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Com
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _havePanAnimations(0), _loadedRoomNumber(0), _activeScreenNumber(0),
- _gameState(kGameStateBoot), _gameID(gameID), _rootFSNode(rootFSNode), _havePendingScreenChange(false), _havePendingReturnToIdleState(false), _scriptNextInstruction(0),
+ _gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _havePendingReturnToIdleState(false), _scriptNextInstruction(0),
_escOn(false), _debugMode(false), _panoramaDirectionFlags(0),
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleInteractionID(0),
@@ -89,22 +90,6 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
for (uint i = 0; i < kNumDirections; i++)
_haveIdleAnimations[i] = false;
-
- _logDir = _rootFSNode.getChild("Log");
- if (!_logDir.exists() || !_logDir.isDirectory())
- error("Couldn't resolve Log directory");
-
- _mapDir = _rootFSNode.getChild("Map");
- if (!_mapDir.exists() || !_mapDir.isDirectory())
- error("Couldn't resolve Map directory");
-
- _sfxDir = _rootFSNode.getChild("Sfx");
- if (!_sfxDir.exists() || !_sfxDir.isDirectory())
- error("Couldn't resolve Sfx directory");
-
- _animsDir = _rootFSNode.getChild("Anims");
- if (!_animsDir.exists() || !_animsDir.isDirectory())
- error("Couldn't resolve Anims directory");
}
Runtime::~Runtime() {
@@ -122,33 +107,29 @@ void Runtime::loadCursors(const char *exeName) {
error("Couldn't open executable file %s", exeName);
Common::Array<Common::WinResourceID> cursorGroupIDs = winRes->getIDList(Common::kWinGroupCursor);
- for (Common::Array<Common::WinResourceID>::const_iterator it = cursorGroupIDs.begin(), itEnd = cursorGroupIDs.end(); it != itEnd; ++it) {
- const Common::WinResourceID &id = *it;
-
- Common::SharedPtr<Graphics::WinCursorGroup> cursorGroup(Graphics::WinCursorGroup::createCursorGroup(winRes.get(), *it));
+ for (const Common::WinResourceID &id : cursorGroupIDs) {
+ Common::SharedPtr<Graphics::WinCursorGroup> cursorGroup(Graphics::WinCursorGroup::createCursorGroup(winRes.get(), id));
if (!winRes) {
warning("Couldn't load cursor group");
continue;
}
Common::String nameStr = id.getString();
- if (nameStr.size() == 8 && nameStr.substr(0, 7) == "CURSOR_") {
+ if (nameStr.matchString("CURSOR_#")) {
char c = nameStr[7];
- if (c >= '0' && c <= '9') {
- uint shortID = c - '0';
- if (shortID >= _cursorsShort.size())
- _cursorsShort.resize(shortID + 1);
- _cursorsShort[shortID] = cursorGroup;
- }
- } else if (nameStr.size() == 13 && nameStr.substr(0, 11) == "CURSOR_CUR_") {
+
+ uint shortID = c - '0';
+ if (shortID >= _cursorsShort.size())
+ _cursorsShort.resize(shortID + 1);
+ _cursorsShort[shortID] = cursorGroup;
+ } else if (nameStr.matchString("CURSOR_CUR_##")) {
char c1 = nameStr[11];
char c2 = nameStr[12];
- if (c1 >= '0' && c1 <= '9' && c2 >= '0' && c2 <= '9') {
- uint longID = (c1 - '0') * 10 + (c2 - '0');
- if (longID >= _cursors.size())
- _cursors.resize(longID + 1);
- _cursors[longID] = cursorGroup;
- }
+
+ uint longID = (c1 - '0') * 10 + (c2 - '0');
+ if (longID >= _cursors.size())
+ _cursors.resize(longID + 1);
+ _cursors[longID] = cursorGroup;
}
}
@@ -532,15 +513,15 @@ void Runtime::queueOSEvent(const OSEvent &evt) {
}
void Runtime::loadIndex() {
- Common::FSNode indexFSNode = _logDir.getChild("Index.txt");
+ const char *indexPath = "Log/Index.txt";
- Common::ReadStream *stream = indexFSNode.createReadStream();
- if (!stream)
+ Common::File stream;
+ if (!stream.open(indexPath))
error("Failed to open main index");
- Common::String blamePath = indexFSNode.getPath();
+ Common::String blamePath = indexPath;
- TextParser parser(stream);
+ TextParser parser(&stream);
Common::String token;
TextParserState state;
@@ -622,24 +603,21 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
_scriptSet.reset();
- Common::String logFileName = Common::String::format("Room%02i.log", static_cast<int>(roomNumber));
- Common::FSNode logFileNode = _logDir.getChild(logFileName);
- if (logFileNode.exists()) {
- if (Common::SeekableReadStream *logicFile = logFileNode.createReadStream()) {
- _scriptSet = compileLogicFile(*logicFile, static_cast<uint>(logicFile->size()), logFileNode.getPath());
- delete logicFile;
- }
+ Common::String logicFileName = Common::String::format("Log/Room%02i.log", static_cast<int>(roomNumber));
+ Common::File logicFile;
+ if (logicFile.open(logicFileName)) {
+ _scriptSet = compileLogicFile(logicFile, static_cast<uint>(logicFile.size()), logicFileName);
+ logicFile.close();
}
_map.clear();
- Common::String mapFileName = Common::String::format("Room%02i.map", static_cast<int>(roomNumber));
- Common::FSNode mapFileNode = _mapDir.getChild(mapFileName);
- if (mapFileNode.exists()) {
- if (Common::SeekableReadStream *mapFile = mapFileNode.createReadStream()) {
- loadMap(mapFile);
- delete mapFile;
- }
+ Common::String mapFileName = Common::String::format("Map/Room%02i.map", static_cast<int>(roomNumber));
+ Common::File mapFile;
+
+ if (mapFile.open(mapFileName)) {
+ loadMap(&mapFile);
+ mapFile.close();
}
}
@@ -772,16 +750,17 @@ void Runtime::loadMap(Common::SeekableReadStream *stream) {
void Runtime::changeMusicTrack(int track) {
_musicPlayer.reset();
- Common::String wavFileName = Common::String::format("Music-%02i.wav", static_cast<int>(track));
- Common::FSNode wavFileNode = _sfxDir.getChild(wavFileName);
- if (wavFileNode.exists()) {
- if (Common::SeekableReadStream *wavFile = wavFileNode.createReadStream()) {
- if (Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(wavFile, DisposeAfterUse::YES)) {
- Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
+ Common::String wavFileName = Common::String::format("Sfx/Music-%02i.wav", static_cast<int>(track));
+ Common::File *wavFile = new Common::File();
+ if (wavFile->open(wavFileName)) {
+ if (Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(wavFile, DisposeAfterUse::YES)) {
+ Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
- _musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, 255, 0));
- }
+ _musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, 255, 0));
}
+ } else {
+ warning("Music file '%s' is missing", wavFileName.c_str());
+ delete wavFile;
}
}
@@ -795,14 +774,10 @@ void Runtime::changeAnimation(const AnimationDef &animDef) {
_animDecoder.reset();
_animDecoderState = kAnimDecoderStateStopped;
- Common::String aviFileName = Common::String::format("Anim%04i.avi", animFile);
- Common::FSNode aviFileNode = _animsDir.getChild(aviFileName);
- if (!aviFileNode.exists()) {
- warning("Animation file %i is missing", animFile);
- return;
- }
+ Common::String aviFileName = Common::String::format("Anims/Anim%04i.avi", animFile);
+ Common::File *aviFile = new Common::File();
- if (Common::SeekableReadStream *aviFile = aviFileNode.createReadStream()) {
+ if (aviFile->open(aviFileName)) {
_animDecoder.reset(new Video::AVIDecoder());
if (!_animDecoder->loadStream(aviFile)) {
warning("Animation file %i could not be loaded", animFile);
@@ -810,7 +785,7 @@ void Runtime::changeAnimation(const AnimationDef &animDef) {
}
} else {
warning("Animation file %i is missing", animFile);
- return;
+ delete aviFile;
}
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index cd0d0dc1cb6..70673868f3b 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -338,12 +338,6 @@ private:
VCruiseGameID _gameID;
- Common::FSNode _rootFSNode;
- Common::FSNode _logDir;
- Common::FSNode _mapDir;
- Common::FSNode _sfxDir;
- Common::FSNode _animsDir;
-
Common::Array<Common::SharedPtr<RoomDef> > _roomDefs;
Common::SharedPtr<ScriptSet> _scriptSet;
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 9f6cc537485..117885e9d88 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -34,7 +34,7 @@ namespace VCruise {
VCruiseEngine::VCruiseEngine(OSystem *syst, const VCruiseGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
const Common::FSNode gameDataDir(ConfMan.get("path"));
- SearchMan.addDirectory(gameDataDir.getPath(), gameDataDir);
+ SearchMan.addDirectory(gameDataDir.getPath(), gameDataDir, 0, 2);
}
VCruiseEngine::~VCruiseEngine() {
Commit: 42188df4cc1e6782b9852a1c0a5fbc9ce6972f37
https://github.com/scummvm/scummvm/commit/42188df4cc1e6782b9852a1c0a5fbc9ce6972f37
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Stub unsupported opcodes
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 507e8e6f520..0674c9230bd 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1038,8 +1038,16 @@ void Runtime::onKeyDown(Common::KeyCode keyCode) {
queueOSEvent(evt);
}
-#ifdef CHECK_STACK
-#error "CHECK_STACK is already defined"
+#ifdef PEEK_STACK
+#error "PEEK_STACK is already defined"
+#endif
+
+#ifdef TAKE_STACK
+#error "TAKE_STACK is already defined"
+#endif
+
+#ifdef OPCODE_STUB
+#error "OPCODE_STUB is already defined"
#endif
#define PEEK_STACK(n) \
@@ -1064,6 +1072,11 @@ void Runtime::onKeyDown(Common::KeyCode keyCode) {
this->_scriptStack.resize(stackSize - (n)); \
} while (false)
+#define OPCODE_STUB(op) \
+ void Runtime::scriptOp##op(ScriptArg_t arg) { \
+ error("Unimplemented opcode '" #op "'"); \
+ }
+
void Runtime::scriptOpNumber(ScriptArg_t arg) {
_scriptStack.push_back(arg);
}
@@ -1076,9 +1089,9 @@ void Runtime::scriptOpRotate(ScriptArg_t arg) {
_havePanAnimations = true;
}
-void Runtime::scriptOpAngle(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpAngleGGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpSpeed(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(Angle)
+OPCODE_STUB(AngleGGet)
+OPCODE_STUB(Speed)
void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
TAKE_STACK(kAnimDefStackArgs + 2);
@@ -1096,12 +1109,12 @@ void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
_idleAnimations[direction] = animDef;
}
-void Runtime::scriptOpChangeL(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(ChangeL)
-void Runtime::scriptOpAnimR(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpAnimF(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpAnimN(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpAnimG(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(AnimR)
+OPCODE_STUB(AnimF)
+OPCODE_STUB(AnimN)
+OPCODE_STUB(AnimG)
void Runtime::scriptOpAnimS(ScriptArg_t arg) {
TAKE_STACK(kAnimDefStackArgs + 2);
@@ -1129,7 +1142,7 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
_havePendingScreenChange = true;
}
-void Runtime::scriptOpStatic(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(Static)
void Runtime::scriptOpVarLoad(ScriptArg_t arg) {
TAKE_STACK(1);
@@ -1160,15 +1173,15 @@ void Runtime::scriptOpSetCursor(ScriptArg_t arg) {
changeToCursor(_cursors[stackArgs[0]]);
}
-void Runtime::scriptOpSetRoom(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(SetRoom)
void Runtime::scriptOpLMB(ScriptArg_t arg) {
if (!_scriptEnv.lmb)
terminateScript();
}
-void Runtime::scriptOpLMB1(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpSoundS1(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(LMB1)
+OPCODE_STUB(SoundS1)
void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
TAKE_STACK(2);
@@ -1186,25 +1199,26 @@ void Runtime::scriptOpMusic(ScriptArg_t arg) {
void Runtime::scriptOpMusicUp(ScriptArg_t arg) {
TAKE_STACK(2);
- warning("Music volume changes are not implemented");
+ warning("Music volume ramp up is not implemented");
(void)stackArgs;
}
void Runtime::scriptOpMusicDn(ScriptArg_t arg) {
TAKE_STACK(2);
- warning("Music volume changes are not implemented");
+ warning("Music volume ramp down is not implemented");
(void)stackArgs;
}
-void Runtime::scriptOpParm1(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpParm2(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpParm3(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpParmG(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpVolumeDn4(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpVolumeUp3(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpRandom(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(Parm1)
+OPCODE_STUB(Parm2)
+OPCODE_STUB(Parm3)
+OPCODE_STUB(ParmG)
+
+OPCODE_STUB(VolumeDn4)
+OPCODE_STUB(VolumeUp3)
+OPCODE_STUB(Random)
void Runtime::scriptOpDrop(ScriptArg_t arg) {
TAKE_STACK(1);
@@ -1218,12 +1232,12 @@ void Runtime::scriptOpDup(ScriptArg_t arg) {
_scriptStack.push_back(stackArgs[0]);
}
-void Runtime::scriptOpSay3(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpSetTimer(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpLoSet(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpLoGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpHiSet(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpHiGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(Say3)
+OPCODE_STUB(SetTimer)
+OPCODE_STUB(LoSet)
+OPCODE_STUB(LoGet)
+OPCODE_STUB(HiSet)
+OPCODE_STUB(HiGet)
void Runtime::scriptOpNot(ScriptArg_t arg) {
TAKE_STACK(1);
@@ -1249,9 +1263,9 @@ void Runtime::scriptOpCmpEq(ScriptArg_t arg) {
_scriptStack.push_back((stackArgs[0] == stackArgs[1]) ? 1 : 0);
}
-void Runtime::scriptOpBitLoad(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpBitSet0(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpBitSet1(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(BitLoad)
+OPCODE_STUB(BitSet0)
+OPCODE_STUB(BitSet1)
void Runtime::scriptOpDisc1(ScriptArg_t arg) {
// Disc check, always pass
@@ -1284,8 +1298,8 @@ void Runtime::scriptOpEscOff(ScriptArg_t arg) {
_escOn = false;
}
-void Runtime::scriptOpEscGet(ScriptArg_t arg) { error("Unimplemented opcode"); }
-void Runtime::scriptOpBackStart(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(EscGet)
+OPCODE_STUB(BackStart)
void Runtime::scriptOpAnimName(ScriptArg_t arg) {
// I doubt this is actually how it works internally but whatever
@@ -1307,7 +1321,7 @@ void Runtime::scriptOpAnimName(ScriptArg_t arg) {
}
-void Runtime::scriptOpValueName(ScriptArg_t arg) { error("Unimplemented opcode"); }
+OPCODE_STUB(ValueName)
void Runtime::scriptOpVarName(ScriptArg_t arg) {
if (_roomNumber >= _roomDefs.size())
@@ -1363,6 +1377,11 @@ void Runtime::scriptOpJump(ScriptArg_t arg) {
_scriptNextInstruction = arg;
}
+#undef TAKE_STACK
+#undef PEEK_STACK
+#undef OPCODE_STUB
+
+
void Runtime::drawFrame() {
_system->updateScreen();
}
Commit: f64666d7827448b43ecdd79d7134a0479c465c1e
https://github.com/scummvm/scummvm/commit/f64666d7827448b43ecdd79d7134a0479c465c1e
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Clean up hash map types
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 0674c9230bd..8b9ac6fbf2b 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -623,10 +623,10 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
if (changedScreen) {
if (_scriptSet) {
- Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> >::const_iterator roomScriptIt = _scriptSet->roomScripts.find(_roomNumber);
+ RoomScriptSetMap_t::const_iterator roomScriptIt = _scriptSet->roomScripts.find(_roomNumber);
if (roomScriptIt != _scriptSet->roomScripts.end()) {
- Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> > &screenScriptsMap = roomScriptIt->_value->screenScripts;
- Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> >::const_iterator screenScriptIt = screenScriptsMap.find(_screenNumber);
+ const ScreenScriptSetMap_t &screenScriptsMap = roomScriptIt->_value->screenScripts;
+ ScreenScriptSetMap_t::const_iterator screenScriptIt = screenScriptsMap.find(_screenNumber);
if (screenScriptIt != screenScriptsMap.end()) {
const Common::SharedPtr<Script> &script = screenScriptIt->_value->entryScript;
if (script)
@@ -976,16 +976,16 @@ void Runtime::drawDebugOverlay() {
Common::SharedPtr<Script> Runtime::findScriptForInteraction(uint interactionID) const {
if (_scriptSet) {
- Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> >::const_iterator roomScriptIt = _scriptSet->roomScripts.find(_roomNumber);
+ RoomScriptSetMap_t::const_iterator roomScriptIt = _scriptSet->roomScripts.find(_roomNumber);
if (roomScriptIt != _scriptSet->roomScripts.end()) {
const RoomScriptSet &roomScriptSet = *roomScriptIt->_value;
- Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> >::const_iterator screenScriptIt = roomScriptSet.screenScripts.find(_screenNumber);
+ ScreenScriptSetMap_t::const_iterator screenScriptIt = roomScriptSet.screenScripts.find(_screenNumber);
if (screenScriptIt != roomScriptSet.screenScripts.end()) {
const ScreenScriptSet &screenScriptSet = *screenScriptIt->_value;
- Common::HashMap<uint, Common::SharedPtr<Script> >::const_iterator interactionScriptIt = screenScriptSet.interactionScripts.find(interactionID);
+ ScriptMap_t::const_iterator interactionScriptIt = screenScriptSet.interactionScripts.find(interactionID);
if (interactionScriptIt != screenScriptSet.interactionScripts.end())
return interactionScriptIt->_value;
}
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index b8e86f780f8..ddf6cc84f46 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -35,6 +35,10 @@ class ReadStream;
namespace VCruise {
+struct ScreenScriptSet;
+struct RoomScriptSet;
+struct ScriptSet;
+
namespace ScriptOps {
enum ScriptOp {
@@ -127,17 +131,21 @@ struct Script {
Common::Array<Instruction> instrs;
};
+typedef Common::HashMap<uint, Common::SharedPtr<Script> > ScriptMap_t;
+typedef Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> > ScreenScriptSetMap_t;
+typedef Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> > RoomScriptSetMap_t;
+
struct ScreenScriptSet {
Common::SharedPtr<Script> entryScript;
- Common::HashMap<uint, Common::SharedPtr<Script> > interactionScripts;
+ ScriptMap_t interactionScripts;
};
struct RoomScriptSet {
- Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> > screenScripts;
+ ScreenScriptSetMap_t screenScripts;
};
struct ScriptSet {
- Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> > roomScripts;
+ RoomScriptSetMap_t roomScripts;
Common::Array<Common::String> strings;
};
Commit: 31a1fcdfddbba6c9601bde2a8909a06111201922
https://github.com/scummvm/scummvm/commit/31a1fcdfddbba6c9601bde2a8909a06111201922
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Remove input buffering from TextParser
Changed paths:
engines/vcruise/textparser.cpp
engines/vcruise/textparser.h
diff --git a/engines/vcruise/textparser.cpp b/engines/vcruise/textparser.cpp
index 116b06dbee2..9cbdec32a1f 100644
--- a/engines/vcruise/textparser.cpp
+++ b/engines/vcruise/textparser.cpp
@@ -29,30 +29,23 @@ namespace VCruise {
TextParserState::TextParserState() : _lineNum(1), _col(1), _prevWasCR(false), _isParsingComment(false) {
}
-TextParser::TextParser(Common::ReadStream *stream) : _stream(stream), _readBufferPos(0), _readBufferEnd(0), _returnBufferPos(kReturnBufferSize) {
- memset(_readBuffer, 0, kReadBufferSize);
- memset(_returnedBuffer, 0, kReturnBufferSize);
+TextParser::TextParser(Common::ReadStream *stream) : _stream(stream), _returnedBufferPos(kReturnedBufferSize) {
+ memset(_returnedBuffer, 0, kReturnedBufferSize);
}
bool TextParser::readOneChar(char &outC, TextParserState &outState) {
- if (_returnBufferPos == kReturnBufferSize) {
- if (_readBufferPos == _readBufferEnd) {
- if (_stream->eos())
- return false;
-
- _readBufferPos = 0;
- _readBufferEnd = _stream->read(_readBuffer, kReadBufferSize);
- if (_readBufferEnd == 0)
- return false;
- }
+ if (_returnedBufferPos == kReturnedBufferSize) {
+ if (_stream->eos())
+ return false;
}
char c = 0;
- if (_returnBufferPos != kReturnBufferSize) {
- c = _returnedBuffer[_returnBufferPos++];
+ if (_returnedBufferPos != kReturnedBufferSize) {
+ c = _returnedBuffer[_returnedBufferPos++];
} else {
- c = _readBuffer[_readBufferPos++];
+ if (!_stream->read(&c, 1))
+ return false;
}
TextParserState prevState = _state;
@@ -115,9 +108,9 @@ bool TextParser::isWhitespace(char c) {
void TextParser::requeue(const char *chars, uint numChars, const TextParserState &state) {
_state = state;
- assert(_returnBufferPos >= numChars);
- _returnBufferPos -= numChars;
- memcpy(_returnedBuffer + _returnBufferPos, chars, numChars);
+ assert(_returnedBufferPos >= numChars);
+ _returnedBufferPos -= numChars;
+ memcpy(_returnedBuffer + _returnedBufferPos, chars, numChars);
}
void TextParser::requeue(const Common::String &str, const TextParserState &state) {
diff --git a/engines/vcruise/textparser.h b/engines/vcruise/textparser.h
index 0c3d92f6269..7bbe73eb115 100644
--- a/engines/vcruise/textparser.h
+++ b/engines/vcruise/textparser.h
@@ -75,15 +75,10 @@ private:
Common::ReadStream *_stream;
- static const uint kReadBufferSize = 4096;
- static const uint kReturnBufferSize = 8;
+ static const uint kReturnedBufferSize = 8;
- char _returnedBuffer[kReturnBufferSize];
- uint _returnBufferPos;
-
- char _readBuffer[kReadBufferSize];
- uint _readBufferPos;
- uint _readBufferEnd;
+ char _returnedBuffer[kReturnedBufferSize];
+ uint _returnedBufferPos;
};
} // End of namespace VCruise
Commit: 94de968496ed5ebcf7032d6461f1d963f4d4e93a
https://github.com/scummvm/scummvm/commit/94de968496ed5ebcf7032d6461f1d963f4d4e93a
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Use INI loader for index
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 8b9ac6fbf2b..e64e8e3bdbe 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -515,16 +515,12 @@ void Runtime::queueOSEvent(const OSEvent &evt) {
void Runtime::loadIndex() {
const char *indexPath = "Log/Index.txt";
- Common::File stream;
- if (!stream.open(indexPath))
- error("Failed to open main index");
+ Common::INIFile iniFile;
+ iniFile.allowNonEnglishCharacters();
+ if (!iniFile.loadFromFile(indexPath))
+ error("Failed to load main logic index");
- Common::String blamePath = indexPath;
-
- TextParser parser(&stream);
-
- Common::String token;
- TextParserState state;
+ IndexParseType indexParseType = kIndexParseTypeNone;
static const IndexPrefixTypePair parsePrefixes[] = {
{"Room", kIndexParseTypeRoom},
@@ -536,52 +532,35 @@ void Runtime::loadIndex() {
{"SRoom", kIndexParseTypeSRoom},
};
- IndexParseType indexParseType = kIndexParseTypeNone;
- uint currentRoomNumber = 0;
-
- for (;;) {
- char firstCh = 0;
- if (!parser.skipWhitespaceAndComments(firstCh, state))
- break;
+ for (const Common::INIFile::Section §ion : iniFile.getSections()) {
+ uint roomNumber = 0;
- if (firstCh == '[') {
- if (!parser.parseToken(token, state))
- error("Index open bracket wasn't terminated");
-
- if (token == "NameRoom") {
- indexParseType = kIndexParseTypeNameRoom;
- } else {
- bool foundType = false;
- uint prefixLen = 0;
- for (const IndexPrefixTypePair &prefixTypePair : parsePrefixes) {
- uint len = strlen(prefixTypePair.prefix);
- if (token.size() > len && !memcmp(token.c_str(), prefixTypePair.prefix, len)) {
- indexParseType = prefixTypePair.parseType;
- foundType = true;
- prefixLen = len;
- break;
- }
+ if (section.name == "NameRoom") {
+ indexParseType = kIndexParseTypeNameRoom;
+ } else {
+ bool foundType = false;
+ uint prefixLen = 0;
+ for (const IndexPrefixTypePair &prefixTypePair : parsePrefixes) {
+ if (section.name.hasPrefix(prefixTypePair.prefix)) {
+ indexParseType = prefixTypePair.parseType;
+ foundType = true;
+ prefixLen = strlen(prefixTypePair.prefix);
+ break;
}
+ }
- if (!foundType)
- error("Unknown index heading type %s", token.c_str());
+ if (!foundType)
+ error("Unknown index heading type %s", section.name.c_str());
- currentRoomNumber = 0;
- for (uint i = prefixLen; i < token.size(); i++) {
- char digit = token[i];
- if (digit < '0' || digit > '9')
- error("Malformed room def");
- currentRoomNumber = currentRoomNumber * 10 + (token[i] - '0');
- }
- }
- parser.expect("]", blamePath);
+ if (!sscanf(section.name.c_str() + prefixLen, "%u", &roomNumber))
+ error("Malformed room def '%s'", section.name.c_str());
- allocateRoomsUpTo(currentRoomNumber);
- } else {
- parser.requeue(&firstCh, 1, state);
+ allocateRoomsUpTo(roomNumber);
+ }
- if (!parseIndexDef(parser, indexParseType, currentRoomNumber, blamePath))
+ for (const Common::INIFile::KeyValue &keyValue : section.getKeys()) {
+ if (!parseIndexDef(indexParseType, roomNumber, keyValue.key, keyValue.value))
break;
}
}
@@ -819,112 +798,85 @@ void Runtime::activateScript(const Common::SharedPtr<Script> &script, const Scri
_gameState = kGameStateScript;
}
-bool Runtime::parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath) {
- Common::String lineText;
- parser.expectLine(lineText, blamePath, true);
-
- Common::MemoryReadStream lineStream(reinterpret_cast<const byte *>(lineText.c_str()), lineText.size(), DisposeAfterUse::NO);
- TextParser strParser(&lineStream);
-
+bool Runtime::parseIndexDef(IndexParseType parseType, uint roomNumber, const Common::String &key, const Common::String &value) {
switch (parseType) {
case kIndexParseTypeNameRoom: {
- uint nameRoomNumber = 0;
- Common::String name;
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectUInt(nameRoomNumber, blamePath);
-
- allocateRoomsUpTo(nameRoomNumber);
- _roomDefs[nameRoomNumber]->name = name;
- } break;
+ uint nameRoomNumber = 0;
+
+ if (!sscanf(value.c_str(), "%u", &nameRoomNumber))
+ error("Malformed NameRoom def '%s'", value.c_str());
+
+ allocateRoomsUpTo(nameRoomNumber);
+ _roomDefs[nameRoomNumber]->name = key;
+ } break;
case kIndexParseTypeRoom: {
- Common::String name;
-
- AnimationDef animDef;
-
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectInt(animDef.animNum, blamePath);
- strParser.expect(",", blamePath);
- strParser.expectUInt(animDef.firstFrame, blamePath);
- strParser.expect(",", blamePath);
- strParser.expectUInt(animDef.lastFrame, blamePath);
- _roomDefs[roomNumber]->animations[name] = animDef;
- } break;
+ int animNum = 0;
+ uint firstFrame = 0;
+ uint lastFrame = 0;
+ if (sscanf(value.c_str(), "%i, %u, %u", &animNum, &firstFrame, &lastFrame) != 3)
+ error("Malformed room animation def '%s'", value.c_str());
+
+
+ AnimationDef animDef;
+ animDef.animNum = animNum;
+ animDef.firstFrame = firstFrame;
+ animDef.lastFrame = lastFrame;
+
+ _roomDefs[roomNumber]->animations[key] = animDef;
+ } break;
case kIndexParseTypeRRoom: {
- Common::String name;
+ Common::String name;
- Common::Rect rect;
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectShort(rect.left, blamePath);
- strParser.expect(",", blamePath);
- strParser.expectShort(rect.top, blamePath);
- strParser.expect(",", blamePath);
- strParser.expectShort(rect.right, blamePath);
+ int left = 0;
+ int top = 0;
+ int width = 0;
+ int height = 0;
- // Line 4210 in Reah contains an animation def instead of a rect def, detect this and discard
- if (!strParser.checkEOL()) {
- strParser.expect(",", blamePath);
- strParser.expectShort(rect.bottom, blamePath);
+ int numValuesRead = sscanf(value.c_str(), "%i, %i, %i, %i", &left, &top, &width, &height);
- _roomDefs[roomNumber]->rects[name] = rect;
- }
-
- } break;
+ if (numValuesRead == 4) {
+ _roomDefs[roomNumber]->rects[key] = Common::Rect(left, top, left + width, top + height);
+ } else {
+ // Line 4210 in Reah contains an animation def instead of a rect def, so we need to tolerate invalid values here
+ warning("Invalid rect def in logic index '%s'", value.c_str());
+ }
+ } break;
case kIndexParseTypeYRoom: {
- Common::String name;
+ uint varSlot = 0;
- uint varSlot = 0;
-
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectUInt(varSlot, blamePath);
-
- _roomDefs[roomNumber]->vars[name] = varSlot;
- } break;
+ if (!sscanf(value.c_str(), "%u", &varSlot))
+ error("Malformed var def '%s'", value.c_str());
+
+ _roomDefs[roomNumber]->vars[key] = varSlot;
+ } break;
case kIndexParseTypeVRoom: {
- Common::String name;
+ Common::String name;
- int value = 0;
+ int val = 0;
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectInt(value, blamePath);
+ if (!sscanf(value.c_str(), "%i", &val))
+ error("Malformed value def '%s'", value.c_str());
- _roomDefs[roomNumber]->values[name] = value;
- } break;
+ _roomDefs[roomNumber]->values[key] = val;
+ } break;
case kIndexParseTypeTRoom: {
- Common::String name;
- Common::String value;
-
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectLine(value, blamePath, false);
-
- _roomDefs[roomNumber]->texts[name] = value;
- } break;
+ _roomDefs[roomNumber]->texts[key] = value;
+ } break;
case kIndexParseTypeCRoom: {
- Common::String name;
- int value;
-
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectInt(value, blamePath);
-
- _roomDefs[roomNumber]->consts[name] = value;
- } break;
+ // This is only used for one entry ("PrzedDrzwiamiDoZsypu" = "In front of the door to the chute") in Reah
+ // and doesn't seem to be referenced in any scripts or anything else. Discard it.
+ } break;
case kIndexParseTypeSRoom: {
- Common::String name;
- int value;
+ Common::String name;
+
+ int soundID = 0;
- strParser.expectToken(name, blamePath);
- strParser.expect("=", blamePath);
- strParser.expectInt(value, blamePath);
+ if (!sscanf(value.c_str(), "%i", &soundID))
+ error("Malformed sound def '%s'", value.c_str());
- _roomDefs[roomNumber]->sounds[name] = value;
- } break;
+ _roomDefs[roomNumber]->values[key] = soundID;
+ } break;
default:
assert(false);
return false;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 70673868f3b..fb48aea1cc8 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -75,7 +75,6 @@ struct RoomDef {
Common::HashMap<Common::String, uint> vars;
Common::HashMap<Common::String, int> values;
Common::HashMap<Common::String, Common::String> texts;
- Common::HashMap<Common::String, int> consts;
Common::HashMap<Common::String, int> sounds;
Common::String name;
@@ -214,7 +213,7 @@ private:
void activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars);
- bool parseIndexDef(TextParser &parser, IndexParseType parseType, uint roomNumber, const Common::String &blamePath);
+ bool parseIndexDef(IndexParseType parseType, uint roomNumber, const Common::String &key, const Common::String &value);
void allocateRoomsUpTo(uint roomNumber);
void drawDebugOverlay();
Commit: 90354659ab39923279b311dc3cbe87897d018669
https://github.com/scummvm/scummvm/commit/90354659ab39923279b311dc3cbe87897d018669
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Use sscanf for number parsing
Changed paths:
engines/vcruise/script.cpp
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 6d104aead69..d31eab21756 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -202,17 +202,9 @@ bool ScriptCompiler::parseDecNumber(const Common::String &token, uint start, uin
if (start == token.size())
return false;
- uint32 num = 0;
- for (uint i = start; i < token.size(); i++) {
- char c = token[i];
- uint32 digit = 0;
- if (c >= '0' && c <= '9')
- digit = c - '0';
- else
- return false;
-
- num = num * 10u + digit;
- }
+ uint num = 0;
+ if (!sscanf(token.c_str() + start, "%u", &num))
+ return false;
outNumber = num;
return true;
@@ -222,19 +214,9 @@ bool ScriptCompiler::parseHexNumber(const Common::String &token, uint start, uin
if (start == token.size())
return false;
- uint32 num = 0;
- for (uint i = start; i < token.size(); i++) {
- char c = token[i];
- uint32 digit = 0;
- if (c >= '0' && c <= '9')
- digit = c - '0';
- else if (c >= 'a' && c <= 'f')
- digit = (c - 'a') + 0xa;
- else
- return false;
-
- num = num * 16u + digit;
- }
+ uint num = 0;
+ if (!sscanf(token.c_str() + start, "%x", &num))
+ return false;
outNumber = num;
return true;
Commit: c7f6aa409a515b8ee86aac5bd45f3448b78daab0
https://github.com/scummvm/scummvm/commit/c7f6aa409a515b8ee86aac5bd45f3448b78daab0
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Cleanup
Changed paths:
engines/vcruise/script.cpp
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index d31eab21756..5f8474f7527 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -412,7 +412,7 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
return true;
}
- if (token.size() >= 5 && token[0] == 'C' && token[1] == 'U' && token[2] == 'R' && token[3] == '_') {
+ if (token.hasPrefix("CUR_")) {
script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
return true;
}
Commit: cbe78b083f04e53ccc23b0f4ea436b799882a49d
https://github.com/scummvm/scummvm/commit/cbe78b083f04e53ccc23b0f4ea436b799882a49d
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Use sscanf for TextParser number parsing
Changed paths:
engines/vcruise/textparser.cpp
diff --git a/engines/vcruise/textparser.cpp b/engines/vcruise/textparser.cpp
index 9cbdec32a1f..b8ecf16189e 100644
--- a/engines/vcruise/textparser.cpp
+++ b/engines/vcruise/textparser.cpp
@@ -133,43 +133,8 @@ void TextParser::expectInt(int &outInt, const Common::String &blamePath) {
TextParserState state;
expectTokenInternal(token, blamePath, state);
- int result = 0;
- bool isNegative = false;
- uint startIndex = 0;
- if (token[0] == '-') {
- if (token.size() == 1)
- error("Parsing error in '%s' at line %i col %i: Signed integer was malformed", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
-
- isNegative = true;
- startIndex = 1;
- }
-
- int base = 10;
- bool isHex = (token.size() >= (startIndex) + 3 && token[startIndex] == '0' && token[startIndex + 1] == 'x');
- if (isHex) {
- startIndex += 2;
- base = 16;
- }
-
- for (uint i = startIndex; i < token.size(); i++) {
- char c = token[i];
- int digit = 0;
- if (c >= '0' && c <= '9')
- digit = (c - '0');
- else if (isHex && (c >= 'a' && c <= 'f'))
- digit = (c - 'a') + 0xa;
- else if (isHex && (c >= 'A' && c <= 'F'))
- digit = (c - 'A') + 0xa;
- else
- error("Parsing error in '%s' at line %i col %i: Integer contained non-digits", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
-
- if (isNegative)
- digit = -digit;
-
- result = result * base + digit;
- }
-
- outInt = result;
+ if (!sscanf(token.c_str(), "%i", &outInt))
+ error("Parsing error in '%s' at line %i col %i: Integer was malformed", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
}
void TextParser::expectUInt(uint &outUInt, const Common::String &blamePath) {
@@ -177,19 +142,8 @@ void TextParser::expectUInt(uint &outUInt, const Common::String &blamePath) {
TextParserState state;
expectTokenInternal(token, blamePath, state);
- uint result = 0;
-
- for (uint i = 0; i < token.size(); i++) {
- char c = token[i];
- if (c < '0' || c > '9')
- error("Parsing error in '%s' at line %i col %i: Integer contained non-digits", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
-
- uint additional = c - '0';
-
- result = result * 10 + additional;
- }
-
- outUInt = result;
+ if (!sscanf(token.c_str(), "%u", &outUInt))
+ error("Parsing error in '%s' at line %i col %i: Unsigned integer was malformed", blamePath.c_str(), static_cast<int>(state._lineNum), static_cast<int>(state._col));
}
void TextParser::expectLine(Common::String &outToken, const Common::String &blamePath, bool continueToNextLine) {
Commit: 1f4f556e194351202487c48b5d396d62ba32c4b5
https://github.com/scummvm/scummvm/commit/1f4f556e194351202487c48b5d396d62ba32c4b5
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-21T17:16:53+01:00
Commit Message:
VCRUISE: Initial panorama controls
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index e64e8e3bdbe..fb5a02b1918 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -68,7 +68,7 @@ const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direct
return screenDirections[screen][direction].get();
}
-ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false) {
+ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), panInteractionID(0) {
}
void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt) {
@@ -86,7 +86,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_escOn(false), _debugMode(false), _panoramaDirectionFlags(0),
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleInteractionID(0),
- _lmbDown(false), _lmbDragging(false), _lmbDownTime(0) {
+ _lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0),
+ _panoramaState(kPanoramaStateInactive) {
for (uint i = 0; i < kNumDirections; i++)
_haveIdleAnimations[i] = false;
@@ -165,6 +166,12 @@ bool Runtime::runFrame() {
case kGameStateIdle:
moreActions = runIdle();
break;
+ case kGameStatePanLeft:
+ moreActions = runHorizontalPan(false);
+ break;
+ case kGameStatePanRight:
+ moreActions = runHorizontalPan(true);
+ break;
case kGameStateScript:
moreActions = runScript();
break;
@@ -227,12 +234,65 @@ bool Runtime::runIdle() {
if (_debugMode)
drawDebugOverlay();
+ detectPanoramaMouseMovement();
+
OSEvent osEvent;
while (popOSEvent(osEvent)) {
- if (osEvent.type == kOSEventTypeMouseMove)
- dischargeIdleMouseMove();
+ if (osEvent.type == kOSEventTypeMouseMove) {
+ bool changedState = dischargeIdleMouseMove();
+ if (changedState)
+ return true;
+ }
}
+ // Yield
+ return false;
+}
+
+bool Runtime::runHorizontalPan(bool isRight) {
+ bool animEnded = false;
+ continuePlayingAnimation(true, animEnded);
+
+ Common::Point panRelMouse = _mousePos - _panoramaAnchor;
+
+ if (!_lmbDown) {
+ debug(1, "Terminating pan: LMB is not down");
+ startTerminatingHorizontalPan(isRight);
+ return true;
+ }
+
+ if (!isRight && panRelMouse.x > -kPanoramaPanningMarginX) {
+ debug(1, "Terminating pan: Over threshold for left movement");
+ startTerminatingHorizontalPan(isRight);
+ return true;
+ } else if (isRight && panRelMouse.x < kPanoramaPanningMarginX) {
+ debug(1, "Terminating pan: Over threshold for right movement");
+ startTerminatingHorizontalPan(isRight);
+ return true;
+ }
+
+ OSEvent osEvent;
+ while (popOSEvent(osEvent)) {
+ if (osEvent.type == kOSEventTypeLButtonUp) {
+ debug(1, "Terminating pan: LMB up");
+ startTerminatingHorizontalPan(isRight);
+ return true;
+ } else if (osEvent.type == kOSEventTypeMouseMove) {
+ panRelMouse = osEvent.pos - _panoramaAnchor;
+
+ if (!isRight && panRelMouse.x > -kPanoramaPanningMarginX) {
+ debug(1, "Terminating pan: Over threshold for left movement (from queue)");
+ startTerminatingHorizontalPan(isRight);
+ return true;
+ } else if (isRight && panRelMouse.x < kPanoramaPanningMarginX) {
+ debug(1, "Terminating pan: Over threshold for right movement (from queue)");
+ startTerminatingHorizontalPan(isRight);
+ return true;
+ }
+ }
+ }
+
+ // Didn't terminate, yield
return false;
}
@@ -295,6 +355,7 @@ void Runtime::continuePlayingAnimation(bool loop, bool &outAnimationEnded) {
// starts delaying without waiting for the time until the next frame to expire.
// The loop check here DOES wait for the time until next frame to expire.
if (loop && _animPendingDecodeFrame > _animLastFrame) {
+ debug(4, "Looped animation at frame %u", _animLastFrame);
if (!_animDecoder->seekToFrame(_animFirstFrame)) {
outAnimationEnded = true;
return;
@@ -303,6 +364,8 @@ void Runtime::continuePlayingAnimation(bool loop, bool &outAnimationEnded) {
_animPendingDecodeFrame = _animFirstFrame;
}
+ debug(4, "Decoding animation frame %u", _animPendingDecodeFrame);
+
const Graphics::Surface *surface = _animDecoder->decodeNextFrame();
if (!surface) {
outAnimationEnded = true;
@@ -461,6 +524,28 @@ void Runtime::terminateScript() {
changeToScreen(_roomNumber, _screenNumber);
}
+void Runtime::startTerminatingHorizontalPan(bool isRight) {
+
+ // Figure out what slice this is. The last frame is 1 less than usual.
+ uint slice = (_animDisplayingFrame - _animFirstFrame) * kNumDirections / (_animLastFrame - _animFirstFrame + 1);
+
+ // Compute an end frame at the end of the slice.
+ _animLastFrame = (slice + 1) * (_animLastFrame - _animFirstFrame + 1) / kNumDirections + _animFirstFrame;
+
+ debug(1, "Terminating pan at frame slice %u -> frame %u", slice, _animLastFrame);
+
+ if (isRight)
+ _direction = (slice + 1) % kNumDirections;
+ else
+ _direction = kNumDirections - 1 - slice;
+
+ _gameState = kGameStateWaitingForAnimation;
+ _panoramaState = kPanoramaStateInactive;
+
+ // Need to return to idle after direction change
+ _havePendingReturnToIdleState = true;
+}
+
bool Runtime::popOSEvent(OSEvent &evt) {
OSEvent tempEvent;
@@ -494,8 +579,9 @@ bool Runtime::popOSEvent(OSEvent &evt) {
if (!_lmbDown)
continue;
+ _lmbReleaseWasClick = !_lmbDragging;
_lmbDown = false;
- // Don't reset _lmbDragging - Want that available to determine if this was a drag release or click
+ _lmbDragging = false;
}
evt = tempEvent;
@@ -635,8 +721,11 @@ void Runtime::returnToIdleState() {
detectPanoramaDirections();
+ _panoramaState = kPanoramaStateInactive;
+ detectPanoramaMouseMovement();
+
changeToCursor(_cursors[kCursorArrow]);
- dischargeIdleMouseMove();
+ (void) dischargeIdleMouseMove();
}
void Runtime::changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor) {
@@ -648,36 +737,65 @@ void Runtime::changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &
}
}
-void Runtime::dischargeIdleMouseMove() {
+bool Runtime::dischargeIdleMouseMove() {
const MapScreenDirectionDef *sdDef = _map.getScreenDirection(_screenNumber, _direction);
- Common::Point relMouse(_mousePos.x - _gameSection.rect.left, _mousePos.y - _gameSection.rect.top);
+ if (_panoramaState == kPanoramaStateInactive) {
+ Common::Point relMouse(_mousePos.x - _gameSection.rect.left, _mousePos.y - _gameSection.rect.top);
- bool isOnInteraction = false;
- uint interactionID = 0;
- if (sdDef) {
- for (const InteractionDef &idef : sdDef->interactions) {
- if (idef.rect.contains(relMouse)) {
- isOnInteraction = true;
- interactionID = idef.interactionID;
- break;
+ bool isOnInteraction = false;
+ uint interactionID = 0;
+ if (sdDef) {
+ for (const InteractionDef &idef : sdDef->interactions) {
+ if (idef.rect.contains(relMouse)) {
+ isOnInteraction = true;
+ interactionID = idef.interactionID;
+ break;
+ }
}
}
- }
- if (isOnInteraction && (_idleIsOnInteraction == false || interactionID != _idleInteractionID)) {
- _idleIsOnInteraction = true;
- _idleInteractionID = interactionID;
+ if (isOnInteraction && (_idleIsOnInteraction == false || interactionID != _idleInteractionID)) {
+ _idleIsOnInteraction = true;
+ _idleInteractionID = interactionID;
- // New interaction, is there a script?
- Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
+ // New interaction, is there a script?
+ Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
- if (script)
- activateScript(script, ScriptEnvironmentVars());
- } else if (!isOnInteraction && _idleIsOnInteraction) {
- _idleIsOnInteraction = false;
- changeToCursor(_cursors[kCursorArrow]);
+ if (script) {
+ activateScript(script, ScriptEnvironmentVars());
+ return true;
+ }
+ } else if (!isOnInteraction && _idleIsOnInteraction) {
+ _idleIsOnInteraction = false;
+ changeToCursor(_cursors[kCursorArrow]);
+ }
+ } else {
+ uint interactionID = 0;
+
+ Common::Point panRelMouse = _mousePos - _panoramaAnchor;
+ if (_havePanAnimations) {
+ if (panRelMouse.x <= -kPanoramaPanningMarginX)
+ interactionID = kPanLeftInteraction;
+ else if (panRelMouse.x >= kPanoramaPanningMarginX)
+ interactionID = kPanRightInteraction;
+
+ if (interactionID) {
+ // If there's an interaction script for this direction, execute it
+ Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
+
+ if (script) {
+ ScriptEnvironmentVars vars;
+ vars.panInteractionID = interactionID;
+ activateScript(script, vars);
+ return true;
+ }
+ }
+ }
}
+
+ // Didn't do anything
+ return false;
}
void Runtime::loadMap(Common::SeekableReadStream *stream) {
@@ -744,6 +862,10 @@ void Runtime::changeMusicTrack(int track) {
}
void Runtime::changeAnimation(const AnimationDef &animDef) {
+ changeAnimation(animDef, animDef.firstFrame);
+}
+
+void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame) {
int animFile = animDef.animNum;
if (animFile < 0)
animFile = -animFile;
@@ -773,10 +895,14 @@ void Runtime::changeAnimation(const AnimationDef &animDef) {
_animDecoderState = kAnimDecoderStatePaused;
}
- _animDecoder->seekToFrame(animDef.firstFrame);
- _animPendingDecodeFrame = animDef.firstFrame;
+ assert(initialFrame >= animDef.firstFrame && initialFrame <= animDef.lastFrame);
+
+ _animDecoder->seekToFrame(initialFrame);
+ _animPendingDecodeFrame = initialFrame;
_animFirstFrame = animDef.firstFrame;
_animLastFrame = animDef.lastFrame;
+
+ debug(1, "Animation last frame set to %u", animDef.lastFrame);
}
AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
@@ -954,6 +1080,19 @@ void Runtime::detectPanoramaDirections() {
_panoramaDirectionFlags |= kPanoramaHorizFlags;
}
+void Runtime::detectPanoramaMouseMovement() {
+ if (_panoramaState == kPanoramaStateInactive && (_lmbDragging || (_lmbDown && (g_system->getMillis() - _lmbDownTime) >= 500)))
+ panoramaActivate();
+}
+
+void Runtime::panoramaActivate() {
+ assert(_panoramaState == kPanoramaStateInactive);
+ _panoramaState = kPanoramaStatePanningUncertainDirection;
+ _panoramaAnchor = _mousePos;
+
+ // TODO: Change mouse cursor
+}
+
void Runtime::onLButtonDown(int16 x, int16 y) {
onMouseMove(x, y);
@@ -1063,7 +1202,38 @@ void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
OPCODE_STUB(ChangeL)
-OPCODE_STUB(AnimR)
+void Runtime::scriptOpAnimR(ScriptArg_t arg) {
+ if (_scriptEnv.panInteractionID == kPanLeftInteraction) {
+ debug(1, "Pan-left interaction from direction %u", _direction);
+
+ uint reverseDirectionSlice = (kNumDirections - _direction);
+ if (reverseDirectionSlice == kNumDirections)
+ reverseDirectionSlice = 0;
+
+ uint initialFrame = reverseDirectionSlice * (_panLeftAnimationDef.lastFrame - _panLeftAnimationDef.firstFrame) / kNumDirections + _panLeftAnimationDef.firstFrame;
+
+ AnimationDef trimmedAnimation = _panLeftAnimationDef;
+ trimmedAnimation.lastFrame--;
+
+ debug(1, "Running frame loop of %u - %u from frame %u", trimmedAnimation.firstFrame, trimmedAnimation.lastFrame, initialFrame);
+
+ changeAnimation(trimmedAnimation, initialFrame);
+ _gameState = kGameStatePanLeft;
+ } else if (_scriptEnv.panInteractionID == kPanRightInteraction) {
+ debug(1, "Pan-right interaction from direction %u", _direction);
+
+ uint initialFrame = _direction * (_panRightAnimationDef.lastFrame - _panRightAnimationDef.firstFrame) / kNumDirections + _panRightAnimationDef.firstFrame;
+
+ AnimationDef trimmedAnimation = _panRightAnimationDef;
+ trimmedAnimation.lastFrame--;
+
+ debug(1, "Running frame loop of %u - %u from frame %u", trimmedAnimation.firstFrame, trimmedAnimation.lastFrame, initialFrame);
+
+ changeAnimation(_panRightAnimationDef, initialFrame);
+ _gameState = kGameStatePanRight;
+ }
+}
+
OPCODE_STUB(AnimF)
OPCODE_STUB(AnimN)
OPCODE_STUB(AnimG)
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index fb48aea1cc8..1fc3e09a692 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -59,6 +59,9 @@ enum GameState {
kGameStateQuit, // Quitting
kGameStateIdle, // Waiting for input events
kGameStateScript, // Running a script
+
+ kGameStatePanLeft,
+ kGameStatePanRight,
};
struct AnimationDef {
@@ -106,6 +109,7 @@ struct ScriptEnvironmentVars {
ScriptEnvironmentVars();
bool lmb;
+ uint panInteractionID;
};
class Runtime {
@@ -169,6 +173,9 @@ private:
enum PanoramaState {
kPanoramaStateInactive,
+
+ kPanoramaStatePanningUncertainDirection,
+
kPanoramaStatePanningLeft,
kPanoramaStatePanningRight,
kPanoramaStatePanningUp,
@@ -189,6 +196,7 @@ private:
bool bootGame();
bool runIdle();
+ bool runHorizontalPan(bool isRight);
bool runScript();
bool runWaitForAnimation();
void continuePlayingAnimation(bool loop, bool &outEndedAnimation);
@@ -196,6 +204,8 @@ private:
void commitSectionToScreen(const RenderSection §ion, const Common::Rect &rect);
void terminateScript();
+ void startTerminatingHorizontalPan(bool isRight);
+
bool popOSEvent(OSEvent &evt);
void queueOSEvent(const OSEvent &evt);
@@ -203,11 +213,12 @@ private:
void changeToScreen(uint roomNumber, uint screenNumber);
void returnToIdleState();
void changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor);
- void dischargeIdleMouseMove();
+ bool dischargeIdleMouseMove();
void loadMap(Common::SeekableReadStream *stream);
void changeMusicTrack(int musicID);
void changeAnimation(const AnimationDef &animDef);
+ void changeAnimation(const AnimationDef &animDef, uint initialFrame);
AnimationDef stackArgsToAnimDef(const StackValue_t *args) const;
@@ -219,7 +230,10 @@ private:
void drawDebugOverlay();
Common::SharedPtr<Script> findScriptForInteraction(uint interactionID) const;
+
void detectPanoramaDirections();
+ void detectPanoramaMouseMovement();
+ void panoramaActivate();
// Script things
void scriptOpNumber(ScriptArg_t arg);
@@ -373,12 +387,23 @@ private:
uint32 _lmbDownTime;
bool _lmbDown;
bool _lmbDragging;
+ bool _lmbReleaseWasClick; // If true, then the mouse didn't move at all since the LMB down
+
+ PanoramaState _panoramaState;
+ // The anchor point behavior is kind of weird. The way it works in Reah is that if the camera is panning left or right and the mouse is moved
+ // back across the anchor threshold at all, then the anchor point is reset to the mouse position. This means it can drift, because the center
+ // is moved by roughly the margin amount.
+ // Vertical panning on the other hand resets the anchor vertical coordinate every time.
+ Common::Point _panoramaAnchor;
Common::Array<OSEvent> _pendingEvents;
static const uint kAnimDefStackArgs = 3;
static const uint kCursorArrow = 0;
+
+ static const int kPanoramaPanningMarginX = 10;
+ static const int kPanoramaPanningMarginY = 10;
};
} // End of namespace VCruise
More information about the Scummvm-git-logs
mailing list