[Scummvm-git-logs] scummvm master -> 05da459107a838cec49e4b2d6e446b2f11eed309
elasota
noreply at scummvm.org
Sun Mar 31 19:40:07 UTC 2024
This automated email contains information about 31 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
26c054e0c5 IMAGE: Add support for loading CUR and ANI files
bdd801ce00 VCRUISE: Detect and boot AD2044
9db748a3f9 VCRUISE: More AD2044 loading stuff
b7f0a753ca VCRUISE: Add #M and #EM opcodes
e26ec842f9 VCRUISE: Add CUR_LUPA binding for AD2044
cd2e9aa37f VCRUISE: Load AD2044 font. Fix cursor animations.
3ccaa683cd VCRUISE: Fix AD2044 fullscreen UI
09a7b1fb5f VCRUISE: Add AD2044 subtitle rendering
3729728a83 VCRUISE: Add missing opcode dispatches
073a3d35d1 VCRUISE: Fix wrong forward cursor ID
e907bb1153 VCRUISE: Add say1rnd opcode
e33a407f56 VCRUISE: Stub sound ops
e7c47fb500 VCRUISE: Fix sound IDs, open cursor, and some screen overrides
b313b4068f VCRUISE: AD2044 inventory support
63bcca8cc9 VCRUISE: Add some boilerplate for loading graphics and strings
712481918e VCRUISE: Fix a bunch of things to get bathroom working
3b1bebf0a2 VCRUISE: Fix up bathroom mirror behavior
a90317c90d VCRUISE: Stub out inventory interactions
6892545213 VCRUISE: Add inventory pickup/stash
00c7323818 VCRUISE: Stub Say3K op
965e7afab6 VCRUISE: Fix static looping animations not persisting through reload
d1b08c9960 VCRUISE: Add MIDI playback
1d7c33e893 VCRUISE: Fix some missing mutex locks
c8d2d34644 VCRUISE: Add item examination
ffa36e1898 VCRUISE: Fix MIDI crash when restarting the game
774a7dfac3 VCRUISE: Avoid restarting music if the track didn't change
e9000d2a8e VCRUISE: Add return from item examination
0867fd9126 VCRUISE: Add say cycle ops and some item infos
fa2dfbcd7b VCRUISE: Disallow examining while already examining
b66044f2e7 VCRUISE: Add some more handling of unusual animations
05da459107 VCRUISE: Fix C++11 narrowing conversion warning
Commit: 26c054e0c50cdd8da87d98ec7bbe9e54a2809d13
https://github.com/scummvm/scummvm/commit/26c054e0c50cdd8da87d98ec7bbe9e54a2809d13
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
IMAGE: Add support for loading CUR and ANI files
Changed paths:
A image/ani.cpp
A image/ani.h
A image/icocur.cpp
A image/icocur.h
graphics/wincursor.cpp
graphics/wincursor.h
image/module.mk
diff --git a/graphics/wincursor.cpp b/graphics/wincursor.cpp
index e5634a0fdca..15e7aba7718 100644
--- a/graphics/wincursor.cpp
+++ b/graphics/wincursor.cpp
@@ -31,7 +31,7 @@ namespace Graphics {
/** A Windows cursor. */
class WinCursor : public Cursor {
public:
- WinCursor();
+ WinCursor(uint16 hotspotX, uint16 hotspotY);
~WinCursor();
/** Return the cursor's width. */
@@ -56,6 +56,8 @@ public:
bool readFromStream(Common::SeekableReadStream &stream);
private:
+ WinCursor() = delete;
+
byte *_surface;
byte *_mask;
byte _palette[256 * 3];
@@ -70,11 +72,11 @@ private:
void clear();
};
-WinCursor::WinCursor() {
+WinCursor::WinCursor(uint16 hotspotX, uint16 hotspotY) {
_width = 0;
_height = 0;
- _hotspotX = 0;
- _hotspotY = 0;
+ _hotspotX = hotspotX;
+ _hotspotY = hotspotY;
_surface = nullptr;
_mask = nullptr;
_keyColor = 0;
@@ -111,9 +113,6 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
const bool supportOpacity = g_system->hasFeature(OSystem::kFeatureCursorMask);
const bool supportInvert = g_system->hasFeature(OSystem::kFeatureCursorMaskInvert);
- _hotspotX = stream.readUint16LE();
- _hotspotY = stream.readUint16LE();
-
// Check header size
if (stream.readUint32LE() != 40)
return false;
@@ -151,8 +150,10 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
if (numColors == 0)
numColors = 1 << bitsPerPixel;
+ // Skip number of important colors
+ stream.skip(4);
+
// Reading the palette
- stream.seek(40 + 4);
for (uint32 i = 0 ; i < numColors; i++) {
_palette[i * 3 + 2] = stream.readByte();
_palette[i * 3 + 1] = stream.readByte();
@@ -311,11 +312,14 @@ WinCursorGroup *WinCursorGroup::createCursorGroup(Common::WinResources *exe, con
return 0;
}
- WinCursor *cursor = new WinCursor();
- if (!cursor->readFromStream(*cursorStream)) {
- delete cursor;
+ uint16 hotspotX = cursorStream->readUint16LE();
+ uint16 hotspotY = cursorStream->readUint16LE();
+
+ Cursor *cursor = loadWindowsCursorFromDIB(*cursorStream, hotspotX, hotspotY);
+
+ if (!cursor) {
delete group;
- return 0;
+ return nullptr;
}
CursorItem item;
@@ -448,4 +452,14 @@ Cursor *makeBusyWinCursor() {
return new BusyWinCursor();
}
+Cursor *loadWindowsCursorFromDIB(Common::SeekableReadStream &stream, uint16 hotspotX, uint16 hotspotY) {
+ WinCursor *cursor = new WinCursor(hotspotX, hotspotY);
+ if (!cursor->readFromStream(stream)) {
+ delete cursor;
+ return nullptr;
+ }
+
+ return cursor;
+}
+
} // End of namespace Graphics
diff --git a/graphics/wincursor.h b/graphics/wincursor.h
index 92e6479575e..210128fdd8f 100644
--- a/graphics/wincursor.h
+++ b/graphics/wincursor.h
@@ -80,6 +80,13 @@ Cursor *makeDefaultWinCursor();
*/
Cursor *makeBusyWinCursor();
+/**
+ * Create a Cursor from DIB-format data, i.e. starting with a BITMAPINFOHEADER
+ *
+ * @note The calling code is responsible for deleting the returned pointer.
+ */
+Cursor *loadWindowsCursorFromDIB(Common::SeekableReadStream &stream, uint16 hotspotX, uint16 hotspotY);
+
/** @} */
} // End of namespace Graphics
diff --git a/image/ani.cpp b/image/ani.cpp
new file mode 100644
index 00000000000..c556e32815f
--- /dev/null
+++ b/image/ani.cpp
@@ -0,0 +1,304 @@
+/* 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/memstream.h"
+#include "common/stream.h"
+#include "common/substream.h"
+
+#include "image/ani.h"
+
+namespace Image {
+
+AniDecoder::Metadata::Metadata()
+ : numFrames(0), numSteps(0), width(0), height(0), bitCount(0),
+ numPlanes(0), perFrameDelay(0), haveSeqData(false), isCURFormat(false) {
+}
+
+AniDecoder::FrameDef::FrameDef() : delay(0), imageIndex(0) {
+}
+
+AniDecoder::AniDecoder() : _stream(nullptr), _disposeAfterUse(DisposeAfterUse::NO) {
+}
+
+AniDecoder::~AniDecoder() {
+ close();
+}
+
+void AniDecoder::close() {
+ if (_disposeAfterUse == DisposeAfterUse::YES && _stream != nullptr)
+ delete _stream;
+
+ _stream = nullptr;
+}
+
+const AniDecoder::Metadata &AniDecoder::getMetadata() const {
+ return _metadata;
+}
+
+AniDecoder::FrameDef AniDecoder::getSequenceFrame(uint sequenceIndex) const {
+ FrameDef frameDef;
+
+ if (sequenceIndex >= _rateData.size())
+ frameDef.delay = _metadata.perFrameDelay;
+ else
+ frameDef.delay = _rateData[sequenceIndex];
+
+ if (sequenceIndex >= _seqData.size())
+ frameDef.imageIndex = sequenceIndex;
+ else
+ frameDef.imageIndex = _seqData[sequenceIndex];
+
+ return frameDef;
+}
+
+Common::SeekableReadStream *AniDecoder::openImageStream(uint imageIndex) const {
+ if (imageIndex >= _frameDataLocations.size())
+ error("Invalid ANI image index");
+
+ const FrameDataRange &frameDataRange = _frameDataLocations[imageIndex];
+
+ return new Common::SafeSeekableSubReadStream(_stream, frameDataRange.pos, frameDataRange.pos + frameDataRange.size);
+}
+
+bool AniDecoder::open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag disposeAfterUse) {
+ close();
+
+ _stream = &stream;
+ _disposeAfterUse = disposeAfterUse;
+
+ bool loadedOK = load();
+ if (!loadedOK)
+ close();
+
+ return loadedOK;
+}
+
+bool AniDecoder::load() {
+ if (!parseRIFFChunks(*_stream, Common::Functor2Mem<const RIFFChunkDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseTopLevelChunk))) {
+ warning("AniDecoder::load: Failed to load ANI container");
+ return false;
+ }
+
+ return true;
+}
+
+bool AniDecoder::parseRIFFChunks(Common::SeekableReadStream &stream, const RIFFChunkParseFunc_t &callback) {
+ int64 nextChunkStartPos = 0;
+ int64 endPos = stream.size();
+
+ while (nextChunkStartPos < endPos) {
+ if (!stream.seek(nextChunkStartPos)) {
+ warning("AniDecoder::parseRIFFChunks: Failed to reset to start of RIFF chunk");
+ return false;
+ }
+
+ byte riffChunkHeader[8];
+
+ if (stream.read(riffChunkHeader, 8) != 8) {
+ warning("AniDecoder::parseRIFFChunks: Failed to read RIFF chunk header");
+ return false;
+ }
+
+ uint32 chunkSize = READ_LE_UINT32(riffChunkHeader + 4);
+
+ int64 actualChunkSize = chunkSize;
+ if (chunkSize & 1)
+ actualChunkSize++;
+
+ int64 chunkAvailable = stream.size() - stream.pos();
+ if (chunkAvailable < actualChunkSize) {
+ warning("AniDecoder::parseRIFFChunk: RIFF chunk is too large");
+ return false;
+ }
+
+ RIFFChunkDef chunkDef;
+ chunkDef.id = READ_BE_UINT32(riffChunkHeader);
+ chunkDef.size = chunkSize;
+
+ Common::SeekableSubReadStream substream(&stream, static_cast<uint32>(stream.pos()), static_cast<uint32>(stream.pos()) + chunkSize);
+ if (!callback(chunkDef, substream))
+ return false;
+
+ nextChunkStartPos += actualChunkSize + 8;
+ }
+
+ return true;
+}
+
+bool AniDecoder::parseRIFFContainer(Common::SeekableReadStream &chunkStream, const RIFFChunkDef &chunkDef, const RIFFContainerParseFunc_t &callback) {
+ if (chunkDef.size < 4) {
+ warning("AniDecoder::parseRIFFContainer: RIFF container is too small");
+ return false;
+ }
+
+ byte containerTypeID[4];
+ if (chunkStream.read(containerTypeID, 4) != 4) {
+ warning("AniDecoder::parseRIFFContainer: Failed to read RIFF container type");
+ return false;
+ }
+
+ RIFFContainerDef containerDef;
+ containerDef.id = READ_BE_UINT32(containerTypeID);
+ containerDef.size = chunkDef.size - 4;
+
+ Common::SeekableSubReadStream substream(&chunkStream, 4, chunkDef.size);
+ return callback(containerDef, substream);
+}
+
+bool AniDecoder::parseTopLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
+ if (chunk.id != MKTAG('R', 'I', 'F', 'F')) {
+ warning("AniDecoder::parseTopLevelChunk: Top-level chunk isn't RIFF");
+ return false;
+ }
+
+ return parseRIFFContainer(stream, chunk, Common::Functor2Mem<const RIFFContainerDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseTopLevelContainer));
+}
+
+bool AniDecoder::parseTopLevelContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream) {
+ if (container.id == MKTAG('A', 'C', 'O', 'N'))
+ return parseRIFFChunks(stream, Common::Functor2Mem<const RIFFChunkDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseSecondLevelChunk));
+
+ warning("AniDecoder::parseTopLevelContainer: Top-level container isn't ACON");
+ return false;
+}
+
+bool AniDecoder::parseSecondLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
+ if (chunk.id == MKTAG('L', 'I', 'S', 'T'))
+ return parseRIFFContainer(stream, chunk, Common::Functor2Mem<const RIFFContainerDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseListContainer));
+
+ if (chunk.id == MKTAG('a', 'n', 'i', 'h'))
+ return parseAnimHeaderChunk(chunk, stream);
+
+ if (chunk.id == MKTAG('s', 'e', 'q', ' '))
+ return parseSeqChunk(chunk, stream);
+
+ if (chunk.id == MKTAG('r', 'a', 't', 'e'))
+ return parseRateChunk(chunk, stream);
+
+ return true;
+}
+
+bool AniDecoder::parseListContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream) {
+ if (container.id == MKTAG('f', 'r', 'a', 'm'))
+ return parseRIFFChunks(stream, Common::Functor2Mem<const RIFFChunkDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseIconChunk));
+
+ return true;
+}
+
+bool AniDecoder::parseAnimHeaderChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
+ byte animHeader[36];
+ for (byte &b : animHeader)
+ b = 0;
+
+ uint32 amountToRead = 36;
+ if (chunk.size < amountToRead)
+ amountToRead = chunk.size;
+
+ if (amountToRead > 0 && stream.read(animHeader, amountToRead) != amountToRead) {
+ warning("AniDecoder::parseAnimHeaderChunk: Read failed");
+ return false;
+ }
+
+ uint32 structSize = READ_LE_UINT32(animHeader);
+ if (structSize < 36) {
+ for (uint i = structSize; i < 36; i++)
+ animHeader[i] = 0;
+ }
+
+ _metadata.numFrames = READ_LE_UINT32(animHeader + 4);
+ _metadata.numSteps = READ_LE_UINT32(animHeader + 8);
+ _metadata.width = READ_LE_UINT32(animHeader + 12);
+ _metadata.height = READ_LE_UINT32(animHeader + 16);
+ _metadata.bitCount = READ_LE_UINT32(animHeader + 20);
+ _metadata.numPlanes = READ_LE_UINT32(animHeader + 24);
+ _metadata.perFrameDelay = READ_LE_UINT32(animHeader + 28);
+
+ uint32 flags = READ_LE_UINT32(animHeader + 32);
+ _metadata.isCURFormat = ((flags & 1) != 0);
+ _metadata.haveSeqData = ((flags & 2) != 0);
+
+ return true;
+}
+
+bool AniDecoder::parseSeqChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
+ uint32 numFrames = chunk.size / 4u;
+
+ if (numFrames > 1000u) {
+ warning("AniDecoder::parseRateChunk: Too many frames");
+ return false;
+ }
+
+ if (numFrames > _seqData.size())
+ _seqData.resize(numFrames);
+
+ for (uint i = 0; i < numFrames; i++) {
+ byte seqData[4];
+
+ if (stream.read(seqData, 4) != 4) {
+ warning("AniDecoder::parseRateChunk: Failed to read sequence information");
+ return false;
+ }
+
+ _seqData[i] = READ_LE_UINT32(seqData);
+ }
+
+ return true;
+}
+
+bool AniDecoder::parseRateChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
+ uint32 numFrames = chunk.size / 4u;
+
+ if (numFrames > 1000u) {
+ warning("AniDecoder::parseRateChunk: Too many frames");
+ return false;
+ }
+
+ if (numFrames > _rateData.size())
+ _rateData.resize(numFrames);
+
+ for (uint i = 0; i < numFrames; i++) {
+ byte rateData[4];
+
+ if (stream.read(rateData, 4) != 4) {
+ warning("AniDecoder::parseRateChunk: Failed to read rate information");
+ return false;
+ }
+
+ _rateData[i] = READ_LE_UINT32(rateData);
+ }
+
+ return true;
+}
+
+bool AniDecoder::parseIconChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
+ FrameDataRange frameDataRange;
+
+ // Get the global stream position
+ frameDataRange.pos = static_cast<uint32>(_stream->pos());
+ frameDataRange.size = chunk.size;
+
+ _frameDataLocations.push_back(frameDataRange);
+
+ return true;
+}
+
+
+} // End of namespace Image
diff --git a/image/ani.h b/image/ani.h
new file mode 100644
index 00000000000..dc47b62bb08
--- /dev/null
+++ b/image/ani.h
@@ -0,0 +1,136 @@
+/* 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 GFX_ANI_H
+#define GFX_ANI_H
+
+#include "common/array.h"
+#include "common/types.h"
+#include "common/func.h"
+
+namespace Common {
+
+class SeekableReadStream;
+struct IFFChunk;
+
+} // End of namespace Common
+
+namespace Graphics {
+
+class Cursor;
+struct Surface;
+
+} // End of namespace Graphics
+
+namespace Image {
+
+class AniDecoder {
+public:
+ struct Metadata {
+ Metadata();
+
+ uint32 numFrames; // Number of images
+ uint32 numSteps; // Number of frames (use the FrameDef to determine which frame)
+ uint32 width;
+ uint32 height;
+ uint32 bitCount;
+ uint32 numPlanes;
+ uint32 perFrameDelay;
+ bool haveSeqData;
+ bool isCURFormat;
+ };
+
+ struct FrameDef {
+ FrameDef();
+
+ uint32 imageIndex;
+ uint32 delay; // In 1/60 sec
+ };
+
+ AniDecoder();
+ ~AniDecoder();
+
+ bool open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag = DisposeAfterUse::NO);
+ void close();
+
+ const Metadata &getMetadata() const;
+ FrameDef getSequenceFrame(uint sequenceIndex) const;
+
+ /**
+ * Opens a substream for an image. If the metadata field
+ * "isCURFormat" is set, you can pass the stream to IcoCurDecoder to
+ * read it. Otherwise, you must determine the format. The stream
+ * is valid for as long as the stream used to construct the AniDecoder
+ * is valid.
+ *
+ * @param imageIndex The index of the image in the ANI file.
+ * @return A substream for the image.
+ */
+ Common::SeekableReadStream *openImageStream(uint imageIndex) const;
+
+private:
+ struct RIFFContainerDef {
+ uint32 id;
+ uint32 size;
+ };
+
+ struct RIFFChunkDef {
+ uint32 id;
+ uint32 size;
+ };
+
+ struct FrameDataRange {
+ uint32 pos;
+ uint32 size;
+ };
+
+ typedef Common::Functor2<const RIFFContainerDef &, Common::SeekableReadStream &, bool> RIFFContainerParseFunc_t;
+ typedef Common::Functor2<const RIFFChunkDef &, Common::SeekableReadStream &, bool> RIFFChunkParseFunc_t;
+
+ bool load();
+
+ static bool parseRIFFChunks(Common::SeekableReadStream &stream, const RIFFChunkParseFunc_t &callback);
+ static bool parseRIFFContainer(Common::SeekableReadStream &stream, const RIFFChunkDef &chunkDef, const RIFFContainerParseFunc_t &callback);
+
+ bool parseTopLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
+ bool parseTopLevelContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream);
+
+ bool parseSecondLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
+
+ bool parseListContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream);
+
+ bool parseAnimHeaderChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
+ bool parseSeqChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
+ bool parseRateChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
+ bool parseIconChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
+
+ Metadata _metadata;
+ Common::Array<uint32> _rateData;
+ Common::Array<uint32> _seqData;
+ Common::Array<FrameDataRange> _frameDataLocations;
+
+ Common::SeekableReadStream *_stream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/icocur.cpp b/image/icocur.cpp
new file mode 100644
index 00000000000..60876facf3a
--- /dev/null
+++ b/image/icocur.cpp
@@ -0,0 +1,142 @@
+/* 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/substream.h"
+#include "common/memstream.h"
+
+#include "image/icocur.h"
+
+#include "graphics/wincursor.h"
+
+namespace Image {
+
+IcoCurDecoder::IcoCurDecoder() : _type(kTypeInvalid), _stream(nullptr), _disposeAfterUse(DisposeAfterUse::NO) {
+}
+
+IcoCurDecoder::~IcoCurDecoder() {
+ close();
+}
+
+void IcoCurDecoder::close() {
+ if (_disposeAfterUse == DisposeAfterUse::YES && _stream != nullptr)
+ delete _stream;
+
+ _stream = nullptr;
+ _type = kTypeInvalid;
+ _items.clear();
+}
+
+bool IcoCurDecoder::open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag disposeAfterUse) {
+ close();
+
+ _stream = &stream;
+ _disposeAfterUse = disposeAfterUse;
+
+ bool loadedOK = load();
+ if (!loadedOK)
+ close();
+
+ return loadedOK;
+}
+
+bool IcoCurDecoder::load() {
+ uint8 iconDirData[6];
+
+ if (_stream->read(iconDirData, 6) != 6)
+ return false;
+
+ if (iconDirData[0] != 0 || iconDirData[1] != 0 || (iconDirData[2] != 1 && iconDirData[2] != 2) || iconDirData[3] != 0) {
+ warning("Malformed ICO/CUR header");
+ return false;
+ }
+
+ uint16 numImages = READ_LE_UINT16(iconDirData + 4);
+ _type = static_cast<Type>(iconDirData[2]);
+
+ if (numImages == 0)
+ return true;
+
+ uint32 dirSize = static_cast<uint32>(numImages) * 16;
+
+ Common::Array<uint8> iconDir;
+ iconDir.resize(dirSize);
+
+ if (_stream->read(&iconDir[0], dirSize) != dirSize)
+ return false;
+
+ _items.resize(numImages);
+ for (uint i = 0; i < numImages; i++) {
+ const uint8 *entryData = &iconDir[i * 16u];
+ Item &item = _items[i];
+
+ item.width = entryData[0];
+ if (item.width == 0)
+ item.width = 256;
+
+ item.height = entryData[1];
+ if (item.height == 0)
+ item.height = 256;
+
+ item.numColors = entryData[2];
+
+ item.data.ico.numPlanes = READ_LE_UINT16(entryData + 4);
+ item.data.ico.bitsPerPixel = READ_LE_UINT16(entryData + 6);
+ item.dataSize = READ_LE_UINT32(entryData + 8);
+ item.dataOffset = READ_LE_UINT32(entryData + 12);
+ }
+
+ return true;
+}
+
+IcoCurDecoder::Type IcoCurDecoder::getType() const {
+ return _type;
+}
+
+uint IcoCurDecoder::numItems() const {
+ return _items.size();
+}
+
+const IcoCurDecoder::Item &IcoCurDecoder::getItem(uint itemIndex) const {
+ return _items[itemIndex];
+}
+
+Graphics::Cursor *IcoCurDecoder::loadItemAsCursor(uint itemIndex) const {
+ const IcoCurDecoder::Item &dirItem = _items[itemIndex];
+
+ if (_type != kTypeCUR)
+ warning("ICO/CUR file type wasn't a cursor, but is being requested as a cursor anyway");
+
+ if (static_cast<int64>(dirItem.dataOffset) > _stream->size()) {
+ warning("ICO/CUR data offset was outside of the file");
+ return nullptr;
+ }
+
+ if (_stream->size() - static_cast<int64>(dirItem.dataOffset) < static_cast<int64>(dirItem.dataSize)) {
+ warning("ICO/CUR data bounds were outside of the file");
+ return nullptr;
+ }
+
+ Common::SeekableSubReadStream substream(_stream, dirItem.dataOffset, dirItem.dataOffset + dirItem.dataSize);
+ return Graphics::loadWindowsCursorFromDIB(substream, dirItem.data.cur.hotspotX, dirItem.data.cur.hotspotY);
+}
+
+} // End of namespace Image
diff --git a/image/icocur.h b/image/icocur.h
new file mode 100644
index 00000000000..b0858a98aa3
--- /dev/null
+++ b/image/icocur.h
@@ -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/>.
+ *
+ */
+
+#ifndef GFX_ICOCUR_H
+#define GFX_ICOCUR_H
+
+#include "common/array.h"
+#include "common/types.h"
+
+namespace Common {
+
+class SeekableReadStream;
+
+} // End of namespace Common
+
+namespace Graphics {
+
+class Cursor;
+struct Surface;
+
+} // End of namespace Graphics
+
+namespace Image {
+
+class IcoCurDecoder {
+public:
+ enum Type {
+ kTypeInvalid,
+
+ kTypeICO,
+ kTypeCUR,
+ };
+
+ struct Item {
+ struct IconData {
+ uint16 numPlanes;
+ uint16 bitsPerPixel;
+ };
+
+ struct CursorData {
+ uint16 hotspotX;
+ uint16 hotspotY;
+ };
+
+ union DataUnion {
+ IconData ico;
+ CursorData cur;
+ };
+
+ uint16 width;
+ uint16 height;
+ uint8 numColors; // May be 0
+ DataUnion data;
+ uint32 dataSize;
+ uint32 dataOffset;
+ };
+
+ IcoCurDecoder();
+ ~IcoCurDecoder();
+
+ bool open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag = DisposeAfterUse::NO);
+ void close();
+
+ Type getType() const;
+ uint numItems() const;
+ const Item &getItem(uint itemIndex) const;
+
+ /**
+ * Loads an item from the directory as a cursor.
+ *
+ * @param itemIndex The index of the item in the directory.
+ * @return Loaded cursor.
+ */
+ Graphics::Cursor *loadItemAsCursor(uint itemIndex) const;
+
+private:
+ bool load();
+
+ Type _type;
+ Common::Array<Item> _items;
+
+ Common::SeekableReadStream *_stream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/module.mk b/image/module.mk
index df24ea3f61f..5bcc928f43a 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -1,9 +1,11 @@
MODULE := image
MODULE_OBJS := \
+ ani.o \
bmp.o \
cel_3do.o \
gif.o \
+ icocur.o \
iff.o \
jpeg.o \
neo.o \
Commit: bdd801ce0069a88e14dc01cf1c5c9a10ce6b6fd5
https://github.com/scummvm/scummvm/commit/bdd801ce0069a88e14dc01cf1c5c9a10ce6b6fd5
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Detect and boot AD2044
Changed paths:
engines/vcruise/detection.cpp
engines/vcruise/detection.h
engines/vcruise/detection_tables.h
engines/vcruise/metaengine.cpp
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
engines/vcruise/script.cpp
engines/vcruise/script.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/detection.cpp b/engines/vcruise/detection.cpp
index afb968ec6fc..db1eee153b4 100644
--- a/engines/vcruise/detection.cpp
+++ b/engines/vcruise/detection.cpp
@@ -29,6 +29,7 @@
#include "vcruise/detection.h"
static const PlainGameDescriptor g_vcruiseGames[] = {
+ {"ad2044", "A.D. 2044"},
{"reah", "Reah: Face the Unknown"},
{"schizm", "Schizm: Mysterious Journey"},
{nullptr, nullptr}
@@ -39,6 +40,7 @@ static const char *const g_vcruiseDirectoryGlobs[] = {
"Log",
"Waves-12",
"Waves-22",
+ "WAVE-01",
nullptr
};
@@ -71,7 +73,10 @@ public:
VCruise::VCruiseGameID gameID = reinterpret_cast<const VCruise::VCruiseGameDescription *>(adGame.desc)->gameID;
if ((adGame.desc->flags & VCruise::VCRUISE_GF_FORCE_LANGUAGE) == 0) {
- if (gameID == VCruise::GID_REAH) {
+ if (gameID == VCruise::GID_AD2044) {
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
+ game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::PL_POL));
+ } else if (gameID == VCruise::GID_REAH) {
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::NL_NLD));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::FR_FRA));
diff --git a/engines/vcruise/detection.h b/engines/vcruise/detection.h
index 00df2271d39..cdb143a05e6 100644
--- a/engines/vcruise/detection.h
+++ b/engines/vcruise/detection.h
@@ -31,6 +31,7 @@ enum VCruiseGameID {
GID_REAH = 1,
GID_SCHIZM = 2,
+ GID_AD2044 = 3,
};
enum VCruiseGameFlag {
@@ -41,6 +42,8 @@ enum VCruiseGameFlag {
VCRUISE_GF_STEAM_LANGUAGES = (1 << 4),
VCRUISE_GF_FORCE_LANGUAGE = (1 << 5),
+
+ VCRUISE_GF_WANT_MIDI = (1 << 6),
};
struct VCruiseGameDescription {
@@ -58,6 +61,7 @@ struct VCruiseGameDescription {
#define GAMEOPTION_FAST_ANIMATIONS GUIO_GAMEOPTIONS2
#define GAMEOPTION_SKIP_MENU GUIO_GAMEOPTIONS3
#define GAMEOPTION_INCREASE_DRAG_DISTANCE GUIO_GAMEOPTIONS4
+#define GAMEOPTION_USE_4BIT_GRAPHICS GUIO_GAMEOPTIONS5
} // End of namespace VCruise
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index db1f9b77108..43f398a1f32 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -29,6 +29,20 @@
namespace VCruise {
static const VCruiseGameDescription gameDescriptions[] = {
+ { // A.D. 2044, GOG English digital version
+ {
+ "ad2044",
+ "English Digital",
+ AD_ENTRY2s("ad2044.exe", "0ab1e3f8b3a17a5b18bb5ee356face25", 327168,
+ "00010001.wav", "d385bb2f1b10ea8c13bbb2948794c9f6", 74950),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ VCRUISE_GF_WANT_MP3 | ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_AD2044,
+ Common::EN_ANY,
+ },
{ // Reah: Face the Unknown, English DVD version
{
"reah",
diff --git a/engines/vcruise/metaengine.cpp b/engines/vcruise/metaengine.cpp
index 49d7e3938d3..73a4e1a794d 100644
--- a/engines/vcruise/metaengine.cpp
+++ b/engines/vcruise/metaengine.cpp
@@ -80,6 +80,17 @@ static const ADExtraGuiOptionsMap optionsList[] = {
0
}
},
+ {
+ GAMEOPTION_USE_4BIT_GRAPHICS,
+ {
+ _s("Use 16-color graphics"),
+ _s("Uses 16-color graphics."),
+ "vcruise_use_4bit",
+ false,
+ 0,
+ 0
+ }
+ },
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 03d5c8fdbb4..a31a331d2c8 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -39,7 +39,9 @@
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
+#include "image/ani.h"
#include "image/bmp.h"
+#include "image/icocur.h"
#include "audio/decoders/wave.h"
#include "audio/decoders/vorbis.h"
@@ -198,13 +200,70 @@ 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();
+MapLoader::~MapLoader() {
+}
+
+Common::SharedPtr<MapScreenDirectionDef> MapLoader::loadScreenDirectionDef(Common::ReadStream &stream) {
+ 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);
+ }
+
+ return screenDirectionDef;
+ }
+
+ return nullptr;
}
-const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direction) {
+class ReahSchizmMapLoader : public MapLoader {
+public:
+ ReahSchizmMapLoader();
+
+ void setRoomNumber(uint roomNumber) override;
+ const MapScreenDirectionDef *getScreenDirection(uint screen, uint direction) override;
+ void unload() override;
+
+private:
+ void load();
+
+ static const uint kNumScreens = 96;
+ static const uint kFirstScreen = 0xa0;
+
+ uint _roomNumber;
+ bool _isLoaded;
+
+ Common::SharedPtr<MapScreenDirectionDef> _screenDirections[kNumScreens][kNumDirections];
+};
+
+ReahSchizmMapLoader::ReahSchizmMapLoader() : _roomNumber(0), _isLoaded(false) {
+}
+
+void ReahSchizmMapLoader::setRoomNumber(uint roomNumber) {
+ if (_roomNumber != roomNumber)
+ unload();
+
+ _roomNumber = roomNumber;
+}
+
+const MapScreenDirectionDef *ReahSchizmMapLoader::getScreenDirection(uint screen, uint direction) {
if (screen < kFirstScreen)
return nullptr;
@@ -213,9 +272,115 @@ const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direct
if (screen >= kNumScreens)
return nullptr;
- return screenDirections[screen][direction].get();
+ if (!_isLoaded)
+ load();
+
+ return _screenDirections[screen][direction].get();
+}
+
+void ReahSchizmMapLoader::load() {
+ // This is loaded even if the open fails
+ _isLoaded = true;
+
+ Common::Path mapFileName(Common::String::format("Map/Room%02i.map", static_cast<int>(_roomNumber)));
+ Common::File mapFile;
+
+ if (!mapFile.open(mapFileName))
+ return;
+
+ byte screenDefOffsets[kNumScreens * kNumDirections * 4];
+
+ if (!mapFile.seek(16))
+ error("Error skipping map file header");
+
+ if (mapFile.read(screenDefOffsets, sizeof(screenDefOffsets)) != sizeof(screenDefOffsets))
+ error("Error reading map offset table");
+
+ for (uint screen = 0; screen < kNumScreens; screen++) {
+ for (uint direction = 0; direction < kNumDirections; direction++) {
+ uint32 offset = READ_LE_UINT32(screenDefOffsets + (kNumDirections * screen + direction) * 4);
+ if (!offset)
+ continue;
+
+ // QUIRK: The stone game in the tower in Reah (Room 06) has two 0cb screens and the second one is damaged,
+ // so it must be ignored.
+ if (!_screenDirections[screen][direction]) {
+ if (!mapFile.seek(offset))
+ error("Error seeking to screen data");
+
+ _screenDirections[screen][direction] = loadScreenDirectionDef(mapFile);
+ }
+ }
+ }
+}
+
+void ReahSchizmMapLoader::unload() {
+ for (uint screen = 0; screen < kNumScreens; screen++)
+ for (uint direction = 0; direction < kNumDirections; direction++)
+ _screenDirections[screen][direction].reset();
}
+class AD2044MapLoader : public MapLoader {
+public:
+ AD2044MapLoader();
+
+ void setRoomNumber(uint roomNumber) override;
+ const MapScreenDirectionDef *getScreenDirection(uint screen, uint direction) override;
+ void unload() override;
+
+private:
+ void load();
+
+ static const uint kNumScreens = 96;
+ static const uint kFirstScreen = 0xa0;
+
+ uint _roomNumber;
+ uint _screenNumber;
+ bool _isLoaded;
+
+ Common::SharedPtr<MapScreenDirectionDef> _currentMap;
+};
+
+AD2044MapLoader::AD2044MapLoader() : _roomNumber(0), _screenNumber(0), _isLoaded(false) {
+}
+
+void AD2044MapLoader::setRoomNumber(uint roomNumber) {
+ if (_roomNumber != roomNumber)
+ unload();
+
+ _roomNumber = roomNumber;
+}
+
+const MapScreenDirectionDef *AD2044MapLoader::getScreenDirection(uint screen, uint direction) {
+ if (screen != _screenNumber)
+ unload();
+
+ _screenNumber = screen;
+
+ if (!_isLoaded)
+ load();
+
+ return _currentMap.get();
+}
+
+void AD2044MapLoader::load() {
+ // This is loaded even if the open fails
+ _isLoaded = true;
+
+ Common::Path mapFileName(Common::String::format("map/SCR%i.MAP", static_cast<int>(_roomNumber * 100u + _screenNumber)));
+ Common::File mapFile;
+
+ if (!mapFile.open(mapFileName))
+ return;
+
+ _currentMap = loadScreenDirectionDef(mapFile);
+}
+
+void AD2044MapLoader::unload() {
+ _currentMap.reset();
+}
+
+
ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), lmbDrag(false), esc(false), exitToMenu(false), animChangeSet(false), isEntryScript(false), puzzleWasSet(false),
panInteractionID(0), fpsOverride(0), lastHighlightedItem(0), animChangeFrameOffset(0), animChangeNumFrames(0) {
}
@@ -1043,7 +1208,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _swapOutRoom(0), _swapOutScreen(0), _swapOutDirection(0),
_haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingPreIdleActions(false), _havePendingReturnToIdleState(false), _havePendingPostSwapScreenReset(false),
- _havePendingCompletionCheck(false), _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
+ _havePendingCompletionCheck(false), _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _lowQualityGraphicsMode(false),
_musicTrack(0), _musicActive(true), _musicMute(false), _musicMuteDisabled(false),
_scoreSectionEndTime(0), _musicVolume(getDefaultSoundVolume()), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
_panoramaDirectionFlags(0),
@@ -1061,7 +1226,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_isInGame(false),
_subtitleFont(nullptr), _isDisplayingSubtitles(false), _isSubtitleSourceAnimation(false),
_languageIndex(0), _defaultLanguageIndex(0), _defaultLanguage(defaultLanguage), _charSet(kCharSetLatin),
- _isCDVariant(false) {
+ _isCDVariant(false), _currentAnimatedCursor(nullptr), _currentCursor(nullptr), _cursorTimeBase(0), _cursorCycleLength(0) {
for (uint i = 0; i < kNumDirections; i++) {
_haveIdleAnimations[i] = false;
@@ -1102,34 +1267,72 @@ void Runtime::initSections(const Common::Rect &gameRect, const Common::Rect &men
}
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 (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;
+ if (_gameID == GID_AD2044) {
+ const int staticCursorIDs[] = {0, 29, 30, 31, 32, 33, 34, 35, 36, 39, 40, 41, 50, 96, 97, 99};
+ const int animatedCursorIDs[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+ _cursors.resize(100);
+
+ for (int cid : staticCursorIDs) {
+ Common::String cursorPath = Common::String::format("gfx/CURSOR%02i.CUR", static_cast<int>(cid));
+
+ Common::File f;
+ Image::IcoCurDecoder decoder;
+ if (!f.open(Common::Path(cursorPath)) || !decoder.open(f))
+ error("Couldn't load cursor %s", cursorPath.c_str());
+
+ uint numItems = decoder.numItems();
+
+ if (numItems < 1)
+ error("Cursor %s had no items", cursorPath.c_str());
+
+ Graphics::Cursor *cursor = decoder.loadItemAsCursor(0);
+ if (!cursor)
+ error("Couldn't load cursor %s", cursorPath.c_str());
+
+ _cursors[cid] = staticCursorToAnimatedCursor(Common::SharedPtr<Graphics::Cursor>(cursor));
}
- Common::String nameStr = id.getString();
- if (nameStr.matchString("CURSOR_#")) {
- char c = nameStr[7];
-
- 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];
-
- uint longID = (c1 - '0') * 10 + (c2 - '0');
- if (longID >= _cursors.size())
- _cursors.resize(longID + 1);
- _cursors[longID] = cursorGroup;
+ for (int cid : animatedCursorIDs) {
+ Common::String cursorPath = Common::String::format("gfx/CURSOR%i.ani", static_cast<int>(cid));
+
+ Common::File f;
+ Image::AniDecoder decoder;
+ if (!f.open(Common::Path(cursorPath)) || !decoder.open(f))
+ error("Couldn't load cursor %s", cursorPath.c_str());
+
+ _cursors[cid] = aniFileToAnimatedCursor(decoder);
+ }
+ } else {
+ 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 (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.matchString("CURSOR_#")) {
+ char c = nameStr[7];
+
+ uint shortID = c - '0';
+ if (shortID >= _cursorsShort.size())
+ _cursorsShort.resize(shortID + 1);
+ _cursorsShort[shortID] = winCursorGroupToAnimatedCursor(cursorGroup);
+ } else if (nameStr.matchString("CURSOR_CUR_##")) {
+ char c1 = nameStr[11];
+ char c2 = nameStr[12];
+
+ uint longID = (c1 - '0') * 10 + (c2 - '0');
+ if (longID >= _cursors.size())
+ _cursors.resize(longID + 1);
+ _cursors[longID] = winCursorGroupToAnimatedCursor(cursorGroup);
+ }
}
}
@@ -1182,6 +1385,10 @@ void Runtime::setFastAnimationMode(bool fastAnimationMode) {
_fastAnimationMode = fastAnimationMode;
}
+void Runtime::setLowQualityGraphicsMode(bool lowQualityGraphicsMode) {
+ _lowQualityGraphicsMode = lowQualityGraphicsMode;
+}
+
bool Runtime::runFrame() {
bool moreActions = true;
while (moreActions) {
@@ -1253,6 +1460,8 @@ bool Runtime::runFrame() {
updateSounds(timestamp);
updateSubtitles();
+ refreshCursor(timestamp);
+
return true;
}
@@ -1291,18 +1500,27 @@ bool Runtime::bootGame(bool newGame) {
loadAllSchizmScreenNames();
debug(1, "Screen names resolved OK");
- } else {
+ } else if (_gameID == GID_REAH) {
StartConfigDef &startConfig = _startConfigs[kStartConfigInitial];
startConfig.disc = 1;
startConfig.room = 1;
startConfig.screen = 0xb0;
startConfig.direction = 0;
- }
+ } else if (_gameID == GID_AD2044) {
+ StartConfigDef &startConfig = _startConfigs[kStartConfigInitial];
+ startConfig.disc = 1;
+ startConfig.room = 1;
+ startConfig.screen = 5;
+ startConfig.direction = 0;
+ } else
+ error("Don't have a start config for this game");
- _trayBackgroundGraphic = loadGraphic("Pocket", true);
- _trayHighlightGraphic = loadGraphic("Select", true);
- _trayCompassGraphic = loadGraphic("Select_1", true);
- _trayCornerGraphic = loadGraphic("Select_2", true);
+ if (_gameID != GID_AD2044) {
+ _trayBackgroundGraphic = loadGraphic("Pocket", true);
+ _trayHighlightGraphic = loadGraphic("Select", true);
+ _trayCompassGraphic = loadGraphic("Select_1", true);
+ _trayCornerGraphic = loadGraphic("Select_2", true);
+ }
Common::Language lang = _defaultLanguage;
@@ -1394,26 +1612,33 @@ bool Runtime::bootGame(bool newGame) {
Common::CodePage codePage = Common::CodePage::kASCII;
resolveCodePageForLanguage(lang, codePage, _charSet);
- bool subtitlesLoadedOK = loadSubtitles(codePage, false);
+ bool subtitlesLoadedOK = false;
- if (!subtitlesLoadedOK) {
- lang = _defaultLanguage;
- _languageIndex = _defaultLanguageIndex;
-
- warning("Localization data failed to load, retrying with default language");
-
- resolveCodePageForLanguage(lang, codePage, _charSet);
+ if (_gameID == GID_AD2044) {
+ subtitlesLoadedOK = true;
+ } else {
subtitlesLoadedOK = loadSubtitles(codePage, false);
if (!subtitlesLoadedOK) {
- if (_languageIndex != 0) {
- codePage = Common::CodePage::kWindows1250;
- _languageIndex = 0;
- _defaultLanguageIndex = 0;
+ lang = _defaultLanguage;
+ _languageIndex = _defaultLanguageIndex;
+
+ warning("Localization data failed to load, retrying with default language");
- warning("Localization data failed to load again, trying one more time and guessing the encoding");
+ resolveCodePageForLanguage(lang, codePage, _charSet);
- subtitlesLoadedOK = loadSubtitles(codePage, true);
+ subtitlesLoadedOK = loadSubtitles(codePage, false);
+
+ if (!subtitlesLoadedOK) {
+ if (_languageIndex != 0) {
+ codePage = Common::CodePage::kWindows1250;
+ _languageIndex = 0;
+ _defaultLanguageIndex = 0;
+
+ warning("Localization data failed to load again, trying one more time and guessing the encoding");
+
+ subtitlesLoadedOK = loadSubtitles(codePage, true);
+ }
}
}
}
@@ -1425,17 +1650,24 @@ bool Runtime::bootGame(bool newGame) {
else
warning("Localization data failed to load! Text and subtitles will be disabled.");
- _uiGraphics.resize(24);
- for (uint i = 0; i < _uiGraphics.size(); i++) {
- if (_gameID == GID_REAH) {
- _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(_languageIndex * 100u + i)), false);
- if (_languageIndex != 0 && !_uiGraphics[i])
- _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(i)), false);
- } else if (_gameID == GID_SCHIZM) {
- _uiGraphics[i] = loadGraphic(Common::String::format("Data%03u", i), false);
+ if (_gameID != GID_AD2044) {
+ _uiGraphics.resize(24);
+ for (uint i = 0; i < _uiGraphics.size(); i++) {
+ if (_gameID == GID_REAH) {
+ _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(_languageIndex * 100u + i)), false);
+ if (_languageIndex != 0 && !_uiGraphics[i])
+ _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(i)), false);
+ } else if (_gameID == GID_SCHIZM) {
+ _uiGraphics[i] = loadGraphic(Common::String::format("Data%03u", i), false);
+ }
}
}
+ if (_gameID == GID_AD2044)
+ _mapLoader.reset(new AD2044MapLoader());
+ else
+ _mapLoader.reset(new ReahSchizmMapLoader());
+
_gameState = kGameStateIdle;
if (newGame) {
@@ -1443,7 +1675,9 @@ bool Runtime::bootGame(bool newGame) {
_mostRecentValidSaveState = generateNewGameSnapshot();
restoreSaveGameSnapshot();
} else {
- changeToScreen(1, 0xb1);
+ uint initialScreen = (_gameID == GID_AD2044) ? 5 : 0xb1;
+
+ changeToScreen(1, initialScreen);
}
}
@@ -2489,6 +2723,11 @@ void Runtime::queueOSEvent(const OSEvent &evt) {
}
void Runtime::loadIndex() {
+ if (_gameID == GID_AD2044) {
+ // No index
+ return;
+ }
+
const char *indexPath = "Log/Index.txt";
Common::INIFile iniFile;
@@ -2543,19 +2782,47 @@ void Runtime::loadIndex() {
}
void Runtime::findWaves() {
- Common::ArchiveMemberList waves;
- SearchMan.listMatchingMembers(waves, "Sfx/Waves-##/####*.wav", true);
+ if (_gameID == GID_AD2044) {
+ for (int disc = 0; disc < 2; disc++) {
+ for (int cat = 0; cat < 3; cat++) {
+ if (disc == 1 && cat == 0)
+ continue;
+
+ char subdir[3] = {static_cast<char>('0' + disc), static_cast<char>('0' + cat), 0};
+
+ Common::String searchPattern = Common::String("sfx/WAVE-") + subdir + "/########.WAV";
+
+ Common::ArchiveMemberList waves;
+ SearchMan.listMatchingMembers(waves, Common::Path(searchPattern, '/'), true);
+
+ for (const Common::ArchiveMemberPtr &wave : waves) {
+ Common::String name = wave->getFileName();
+
+ // Strip .wav extension
+ name = name.substr(0, name.size() - 4);
+
+ // Make case-insensitive
+ name.toLowercase();
- for (const Common::ArchiveMemberPtr &wave : waves) {
- Common::String name = wave->getName();
+ _waves[Common::String(subdir) + "-" + name] = wave;
+ }
+ }
+ }
+ } else {
+ Common::ArchiveMemberList waves;
+ SearchMan.listMatchingMembers(waves, "Sfx/Waves-##/####*.wav", true);
- // Strip .wav extension
- name = name.substr(0, name.size() - 4);
+ for (const Common::ArchiveMemberPtr &wave : waves) {
+ Common::String name = wave->getFileName();
- // Make case-insensitive
- name.toLowercase();
+ // Strip .wav extension
+ name = name.substr(0, name.size() - 4);
- _waves[name] = wave;
+ // Make case-insensitive
+ name.toLowercase();
+
+ _waves[name] = wave;
+ }
}
}
@@ -2882,20 +3149,24 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
if (logicFile.open(logicFileName)) {
_scriptSet = compileReahLogicFile(logicFile, static_cast<uint>(logicFile.size()), logicFileName);
+ logicFile.close();
+ }
+ } else if (_gameID == GID_AD2044) {
+ _scriptSet.reset();
+
+ Common::Path logicFileName(Common::String::format("log/kom%i.log", static_cast<int>(roomNumber)));
+ Common::File logicFile;
+ if (logicFile.open(logicFileName)) {
+ _scriptSet = compileAD2044LogicFile(logicFile, static_cast<uint>(logicFile.size()), logicFileName);
+
logicFile.close();
}
} else
error("Don't know how to compile scripts for this game");
- _map.clear();
-
- Common::Path mapFileName(Common::String::format("Map/Room%02i.map", static_cast<int>(roomNumber)));
- Common::File mapFile;
+ _mapLoader->unload();
- if (mapFile.open(mapFileName)) {
- loadMap(&mapFile);
- mapFile.close();
- }
+ _mapLoader->setRoomNumber(roomNumber);
}
if (changedScreen) {
@@ -3050,17 +3321,65 @@ void Runtime::returnToIdleState() {
(void) dischargeIdleMouseMove();
}
-void Runtime::changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor) {
+void Runtime::changeToCursor(const Common::SharedPtr<AnimatedCursor> &cursor) {
if (!cursor)
CursorMan.showMouse(false);
else {
- CursorMan.replaceCursor(cursor->cursors[0].cursor);
+ _currentAnimatedCursor = cursor.get();
+
+ _cursorCycleLength = 0;
+ for (const AnimatedCursor::FrameDef &frame : cursor->frames)
+ _cursorCycleLength += frame.delay;
+
+ _cursorTimeBase = g_system->getMillis(true);
+
+ refreshCursor(_cursorTimeBase);
CursorMan.showMouse(true);
}
}
+void Runtime::refreshCursor(uint32 currentTime) {
+ if (!_currentAnimatedCursor)
+ return;
+
+ uint32 timeSinceTimeBase = currentTime - _cursorTimeBase;
+
+ uint stepTime = 0;
+
+ if (_cursorCycleLength > 0) {
+ // 3 ticks at 60Hz is 50ms, so this will reduce the precision of the math that we have to do
+ timeSinceTimeBase %= _cursorCycleLength * 50u;
+ _cursorTimeBase = currentTime - timeSinceTimeBase;
+
+ stepTime = timeSinceTimeBase * 60u / 1000u;
+ }
+
+ uint imageIndex = 0;
+
+ if (_currentAnimatedCursor) {
+ uint frameStartTime = 0;
+ for (const AnimatedCursor::FrameDef &frame : _currentAnimatedCursor->frames) {
+ if (frameStartTime > stepTime)
+ break;
+
+ imageIndex = frame.imageIndex;
+ frameStartTime += frame.delay;
+ }
+ }
+
+ if (imageIndex >= _currentAnimatedCursor->images.size())
+ error("Out-of-bounds animated cursor image index");
+
+ Graphics::Cursor *cursor = _currentAnimatedCursor->images[imageIndex];
+
+ if (!cursor)
+ error("Missing cursor");
+
+ CursorMan.replaceCursor(cursor);
+}
+
bool Runtime::dischargeIdleMouseMove() {
- const MapScreenDirectionDef *sdDef = _map.getScreenDirection(_screenNumber, _direction);
+ const MapScreenDirectionDef *sdDef = _mapLoader->getScreenDirection(_screenNumber, _direction);
if (_inGameMenuState != kInGameMenuStateInvisible) {
checkInGameMenuHover();
@@ -3254,55 +3573,6 @@ bool Runtime::dischargeIdleClick() {
return false;
}
-void Runtime::loadMap(Common::SeekableReadStream *stream) {
- byte screenDefOffsets[MapDef::kNumScreens * 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 < kNumDirections; direction++) {
- uint32 offset = READ_LE_UINT32(screenDefOffsets + (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);
- }
-
- // QUIRK: The stone game in the tower in Reah (Room 06) has two 0cb screens and the second one is damaged,
- // so it must be ignored.
- if (!_map.screenDirections[screen][direction])
- _map.screenDirections[screen][direction] = screenDirectionDef;
- }
- }
- }
-}
-
void Runtime::loadFrameData(Common::SeekableReadStream *stream) {
int64 size = stream->size();
if (size < 2048 || size > 0xffffffu)
@@ -4373,7 +4643,7 @@ void Runtime::drawDebugOverlay() {
uint32 whiteColor = pixFmt.ARGBToColor(255, 255, 255, 255);
uint32 blackColor = pixFmt.ARGBToColor(255, 0, 0, 0);
- const MapScreenDirectionDef *sdDef = _map.getScreenDirection(_screenNumber, _direction);
+ const MapScreenDirectionDef *sdDef = _mapLoader->getScreenDirection(_screenNumber, _direction);
if (sdDef) {
for (const InteractionDef &idef : sdDef->interactions) {
Common::Rect rect = idef.rect;
@@ -5305,6 +5575,75 @@ Common::Rect Runtime::padCircuitInteractionRect(const Common::Rect &rect) {
return result;
}
+Common::SharedPtr<AnimatedCursor> Runtime::winCursorGroupToAnimatedCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursorGroup) {
+ Common::SharedPtr<AnimatedCursor> result(new AnimatedCursor());
+
+ result->cursorGroupKeepAlive = cursorGroup;
+ result->images.push_back(cursorGroup->cursors[0].cursor);
+
+ AnimatedCursor::FrameDef frameDef;
+ frameDef.delay = 1;
+ frameDef.imageIndex = 0;
+
+ result->frames.push_back(frameDef);
+
+ return result;
+}
+
+Common::SharedPtr<AnimatedCursor> Runtime::aniFileToAnimatedCursor(Image::AniDecoder &aniDecoder) {
+ Common::SharedPtr<AnimatedCursor> result(new AnimatedCursor());
+
+ const Image::AniDecoder::Metadata &metadata = aniDecoder.getMetadata();
+
+ if (!metadata.isCURFormat)
+ error("ANI file isn't CUR format");
+
+ for (uint step = 0; step < metadata.numSteps; step++) {
+ const Image::AniDecoder::FrameDef frame = aniDecoder.getSequenceFrame(step);
+
+ AnimatedCursor::FrameDef outFrameDef;
+ outFrameDef.delay = frame.delay;
+ outFrameDef.imageIndex = frame.imageIndex;
+
+ result->frames.push_back(outFrameDef);
+ }
+
+ for (uint frame = 0; frame < metadata.numFrames; frame++) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(aniDecoder.openImageStream(frame));
+
+ if (!stream)
+ error("Couldn't open animated cursor frame");
+
+ Image::IcoCurDecoder icoCurDecoder;
+ icoCurDecoder.open(*stream);
+
+ Graphics::Cursor *cursor = icoCurDecoder.loadItemAsCursor(0);
+
+ if (!cursor)
+ error("Couldn't load cursor frame");
+
+ result->cursorKeepAlive.push_back(Common::SharedPtr<Graphics::Cursor>(cursor));
+ result->images.push_back(cursor);
+ }
+
+ return result;
+}
+
+Common::SharedPtr<AnimatedCursor> Runtime::staticCursorToAnimatedCursor(const Common::SharedPtr<Graphics::Cursor> &cursor) {
+ Common::SharedPtr<AnimatedCursor> result(new AnimatedCursor());
+
+ result->cursorKeepAlive.push_back(cursor);
+ result->images.push_back(cursor.get());
+
+ AnimatedCursor::FrameDef frameDef;
+ frameDef.delay = 1;
+ frameDef.imageIndex = 0;
+
+ result->frames.push_back(frameDef);
+
+ return result;
+}
+
void Runtime::onLButtonDown(int16 x, int16 y) {
onMouseMove(x, y);
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index b94b4538fa0..d554363efae 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -53,6 +53,7 @@ struct PixelFormat;
struct WinCursorGroup;
class ManagedSurface;
class Font;
+class Cursor;
} // End of namespace Graphics
@@ -62,6 +63,12 @@ class AVIDecoder;
} // End of namespace Video
+namespace Image {
+
+class AniDecoder;
+
+} // End of namespace Image
+
namespace VCruise {
static const uint kNumDirections = 8;
@@ -143,14 +150,16 @@ struct MapScreenDirectionDef {
Common::Array<InteractionDef> interactions;
};
-struct MapDef {
- static const uint kNumScreens = 96;
- static const uint kFirstScreen = 0xa0;
+class MapLoader {
+public:
+ virtual ~MapLoader();
- Common::SharedPtr<MapScreenDirectionDef> screenDirections[kNumScreens][kNumDirections];
+ virtual void setRoomNumber(uint roomNumber) = 0;
+ virtual const MapScreenDirectionDef *getScreenDirection(uint screen, uint direction) = 0;
+ virtual void unload() = 0;
- void clear();
- const MapScreenDirectionDef *getScreenDirection(uint screen, uint direction);
+protected:
+ static Common::SharedPtr<MapScreenDirectionDef> loadScreenDirectionDef(Common::ReadStream &stream);
};
struct ScriptEnvironmentVars {
@@ -560,6 +569,19 @@ struct FontCacheItem {
typedef Common::HashMap<Common::String, uint> ScreenNameToRoomMap_t;
typedef Common::HashMap<uint, ScreenNameToRoomMap_t> RoomToScreenNameToRoomMap_t;
+struct AnimatedCursor {
+ struct FrameDef {
+ uint imageIndex;
+ uint delay;
+ };
+
+ Common::Array<FrameDef> frames;
+ Common::Array<Graphics::Cursor *> images;
+
+ Common::Array<Common::SharedPtr<Graphics::Cursor> > cursorKeepAlive;
+ Common::SharedPtr<Graphics::WinCursorGroup> cursorGroupKeepAlive;
+};
+
class Runtime {
public:
friend class RuntimeMenuInterface;
@@ -581,6 +603,7 @@ public:
void loadCursors(const char *exeName);
void setDebugMode(bool debugMode);
void setFastAnimationMode(bool fastAnimationMode);
+ void setLowQualityGraphicsMode(bool lowQualityGraphicsMode);
bool runFrame();
void drawFrame();
@@ -839,11 +862,11 @@ private:
void changeHero();
bool triggerPreIdleActions();
void returnToIdleState();
- void changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor);
+ void changeToCursor(const Common::SharedPtr<AnimatedCursor> &cursor);
+ void refreshCursor(uint32 currentTime);
bool dischargeIdleMouseMove();
bool dischargeIdleMouseDown();
bool dischargeIdleClick();
- void loadMap(Common::SeekableReadStream *stream);
void loadFrameData(Common::SeekableReadStream *stream);
void loadFrameData2(Common::SeekableReadStream *stream);
@@ -925,6 +948,10 @@ private:
void drawCircuitHighlightRect(const Common::Rect &rect);
static Common::Rect padCircuitInteractionRect(const Common::Rect &rect);
+ static Common::SharedPtr<AnimatedCursor> winCursorGroupToAnimatedCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursorGroup);
+ static Common::SharedPtr<AnimatedCursor> aniFileToAnimatedCursor(Image::AniDecoder &aniDecoder);
+ static Common::SharedPtr<AnimatedCursor> staticCursorToAnimatedCursor(const Common::SharedPtr<Graphics::Cursor> &cursor);
+
// Script things
void scriptOpNumber(ScriptArg_t arg);
void scriptOpRotate(ScriptArg_t arg);
@@ -1109,8 +1136,31 @@ private:
void scriptOpFn(ScriptArg_t arg);
void scriptOpItemHighlightSetTrue(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_#
+ // AD2044 ops
+ void scriptOpAnimT(ScriptArg_t arg);
+ void scriptOpAnimForward(ScriptArg_t arg);
+ void scriptOpAnimReverse(ScriptArg_t arg);
+ void scriptOpAnimKForward(ScriptArg_t arg);
+ void scriptOpNoUpdate(ScriptArg_t arg);
+ void scriptOpNoClear(ScriptArg_t arg);
+ void scriptOpSay1_AD2044(ScriptArg_t arg);
+ void scriptOpSay2_AD2044(ScriptArg_t arg);
+ void scriptOpSay1Rnd(ScriptArg_t arg);
+ void scriptOpM(ScriptArg_t arg);
+ void scriptOpEM(ScriptArg_t arg);
+ void scriptOpSE(ScriptArg_t arg);
+ void scriptOpSDot(ScriptArg_t arg);
+ void scriptOpE(ScriptArg_t arg);
+ void scriptOpDot(ScriptArg_t arg);
+ void scriptOpSound(ScriptArg_t arg);
+ void scriptOpISound(ScriptArg_t arg);
+ void scriptOpUSound(ScriptArg_t arg);
+ void scriptOpSay2K(ScriptArg_t arg);
+ void scriptOpSay3K(ScriptArg_t arg);
+ void scriptOpRGet(ScriptArg_t arg);
+
+ Common::Array<Common::SharedPtr<AnimatedCursor> > _cursors; // Cursors indexed as CURSOR_CUR_##
+ Common::Array<Common::SharedPtr<AnimatedCursor> > _cursorsShort; // Cursors indexed as CURSOR_#
InventoryItem _inventory[kNumInventorySlots];
@@ -1192,6 +1242,7 @@ private:
bool _escOn;
bool _debugMode;
bool _fastAnimationMode;
+ bool _lowQualityGraphicsMode;
VCruiseGameID _gameID;
@@ -1271,7 +1322,7 @@ private:
Audio::Mixer *_mixer;
- MapDef _map;
+ Common::SharedPtr<MapLoader> _mapLoader;
RenderSection _gameSection;
RenderSection _gameDebugBackBuffer;
@@ -1357,6 +1408,11 @@ private:
Common::Array<Common::SharedPtr<FontCacheItem> > _fontCache;
+ AnimatedCursor *_currentAnimatedCursor;
+ Graphics::Cursor *_currentCursor;
+ uint32 _cursorTimeBase;
+ uint32 _cursorCycleLength;
+
int32 _dbToVolume[49];
};
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index a3d2ff062aa..69dbca7462f 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2041,6 +2041,30 @@ void Runtime::scriptOpPuzzleDone(ScriptArg_t arg) {
_circuitPuzzle.reset();
}
+// AD2044 ops
+OPCODE_STUB(AnimT)
+OPCODE_STUB(AnimForward)
+OPCODE_STUB(AnimReverse)
+OPCODE_STUB(AnimKForward)
+OPCODE_STUB(Say2K)
+OPCODE_STUB(Say3K)
+OPCODE_STUB(NoUpdate)
+OPCODE_STUB(NoClear)
+
+OPCODE_STUB(Say1_AD2044)
+OPCODE_STUB(Say2_AD2044)
+OPCODE_STUB(Say1Rnd)
+OPCODE_STUB(M)
+OPCODE_STUB(EM)
+OPCODE_STUB(SE)
+OPCODE_STUB(SDot)
+OPCODE_STUB(E)
+OPCODE_STUB(Dot)
+OPCODE_STUB(Sound)
+OPCODE_STUB(ISound)
+OPCODE_STUB(USound)
+OPCODE_STUB(RGet)
+
// Only used in fnRandomBirds and fnRandomMachines in Room 60, both of which are unused
OPCODE_STUB(SndAddRandom)
OPCODE_STUB(SndClearRandom)
@@ -2109,6 +2133,7 @@ bool Runtime::runScript() {
DISPATCH_OP(AnimN);
DISPATCH_OP(AnimG);
DISPATCH_OP(AnimS);
+ DISPATCH_OP(AnimT);
DISPATCH_OP(Anim);
DISPATCH_OP(Static);
@@ -2274,6 +2299,12 @@ bool Runtime::runScript() {
DISPATCH_OP(Fn);
DISPATCH_OP(ItemHighlightSetTrue);
+ DISPATCH_OP(AnimForward);
+ DISPATCH_OP(AnimReverse);
+ DISPATCH_OP(AnimKForward);
+ DISPATCH_OP(NoUpdate);
+ DISPATCH_OP(NoClear);
+
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
}
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index f86fe71f0b9..92d6abdbd71 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -32,6 +32,7 @@ namespace VCruise {
enum ScriptDialect {
kScriptDialectReah,
kScriptDialectSchizm,
+ kScriptDialectAD2044,
};
class LogicUnscrambleStream : public Common::ReadStream {
@@ -162,7 +163,7 @@ private:
void expectNumber(uint32 &outNumber);
void compileRoomScriptSet(RoomScriptSet *rss);
- void compileReahScreenScriptSet(ScreenScriptSet *sss);
+ void compileReahOrAD2044ScreenScriptSet(ScreenScriptSet *sss);
void compileSchizmScreenScriptSet(ScreenScriptSet *sss);
void compileFunction(Script *script);
bool compileInstructionToken(ProtoScript &script, const Common::String &token);
@@ -235,10 +236,10 @@ bool ScriptCompiler::parseNumber(const Common::String &token, uint32 &outNumber)
if (token.size() == 0)
return false;
- if (_dialect == kScriptDialectReah) {
- if (token[0] == 'd')
- return parseDecNumber(token, 1, outNumber);
+ if (_dialect == kScriptDialectReah && token[0] == 'd')
+ return parseDecNumber(token, 1, outNumber);
+ if (_dialect == kScriptDialectReah || _dialect == kScriptDialectAD2044) {
if (token[0] == '0') {
switch (_numberParsingMode) {
case kNumberParsingDec:
@@ -349,7 +350,7 @@ void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
const char *roomToken = nullptr;
- if (_dialect == kScriptDialectReah) {
+ if (_dialect == kScriptDialectReah || _dialect == kScriptDialectAD2044) {
roomToken = "~ROOM";
_eroomToken = "~EROOM";
_scrToken = "~SCR";
@@ -408,10 +409,9 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
expectNumber(screenNumber);
Common::SharedPtr<ScreenScriptSet> sss(new ScreenScriptSet());
- if (_dialect == kScriptDialectReah)
- compileReahScreenScriptSet(sss.get());
+ if (_dialect == kScriptDialectReah || _dialect == kScriptDialectAD2044)
+ compileReahOrAD2044ScreenScriptSet(sss.get());
else if (_dialect == kScriptDialectSchizm) {
-
if (!_parser.parseToken(token, state))
error("Error compiling script at line %i col %i: Expected screen name", static_cast<int>(state._lineNum), static_cast<int>(state._col));
@@ -475,7 +475,7 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
error("Error compiling script: Room wasn't terminated");
}
-void ScriptCompiler::compileReahScreenScriptSet(ScreenScriptSet *sss) {
+void ScriptCompiler::compileReahOrAD2044ScreenScriptSet(ScreenScriptSet *sss) {
TextParserState state;
Common::String token;
@@ -506,7 +506,7 @@ void ScriptCompiler::compileReahScreenScriptSet(ScreenScriptSet *sss) {
_numberParsingMode = kNumberParsingHex;
} else if (token == "BIN") {
_numberParsingMode = kNumberParsingBin;
- } else if (token == "dubbing") {
+ } else if (_dialect == kScriptDialectReah && token == "dubbing") {
Common::String dubbingName;
_parser.expectToken(dubbingName, _blamePath);
protoScript.instrs.push_back(ProtoInstruction(ScriptOps::kDubbing, indexString(dubbingName)));
@@ -571,6 +571,145 @@ void ScriptCompiler::compileFunction(Script *script) {
}
}
+static ScriptNamedInstruction g_ad2044NamedInstructions[] = {
+ //{"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},
+ //{"changeL1", ProtoOp::kProtoOpScript, ScriptOps::kChangeL}, // This seems wrong, but not sure what changeL1 does differently from changeL yet
+ //{"animF", ProtoOp::kProtoOpScript, ScriptOps::kAnimF},
+ //{"animG", ProtoOp::kProtoOpScript, ScriptOps::kAnimG},
+ //{"animN", ProtoOp::kProtoOpScript, ScriptOps::kAnimN},
+ //{"animR", ProtoOp::kProtoOpScript, ScriptOps::kAnimR},
+ //{"animS", ProtoOp::kProtoOpScript, ScriptOps::kAnimS},
+ //{"anim", ProtoOp::kProtoOpScript, ScriptOps::kAnim},
+ {"animT", ProtoOp::kProtoOpScript, ScriptOps::kAnimT},
+ {"ani+", ProtoOp::kProtoOpScript, ScriptOps::kAnimForward},
+ {"ani-", ProtoOp::kProtoOpScript, ScriptOps::kAnimReverse},
+ {"kani+", ProtoOp::kProtoOpScript, ScriptOps::kAnimForward},
+ //{"static", ProtoOp::kProtoOpScript, ScriptOps::kStatic},
+ {"yes@", ProtoOp::kProtoOpScript, ScriptOps::kVarLoad},
+ {"yes!", ProtoOp::kProtoOpScript, ScriptOps::kVarStore},
+ {"yesg@", ProtoOp::kProtoOpScript, ScriptOps::kVarGlobalLoad},
+ {"yesg!", ProtoOp::kProtoOpScript, ScriptOps::kVarGlobalStore},
+ //{"setaX+!", ProtoOp::kProtoOpScript, ScriptOps::kVarAddAndStore},
+ //{"cr?", ProtoOp::kProtoOpScript, ScriptOps::kItemCheck},
+ //{"cr!", ProtoOp::kProtoOpScript, ScriptOps::kItemRemove},
+ //{"sr!", ProtoOp::kProtoOpScript, ScriptOps::kItemHighlightSet},
+ //{"r?", ProtoOp::kProtoOpScript, ScriptOps::kItemHaveSpace},
+ //{"r!", ProtoOp::kProtoOpScript, ScriptOps::kItemAdd},
+ {"r@", ProtoOp::kProtoOpScript, ScriptOps::kRGet},
+ //{"clearPocket", ProtoOp::kProtoOpScript, ScriptOps::kItemClear},
+ {"cursor!", ProtoOp::kProtoOpScript, ScriptOps::kSetCursor},
+ {"room!", ProtoOp::kProtoOpScript, ScriptOps::kSetRoom},
+ {"lmb", ProtoOp::kProtoOpScript, ScriptOps::kLMB},
+ //{"lmb1", ProtoOp::kProtoOpScript, ScriptOps::kLMB1},
+ //{"volumeDn2", ProtoOp::kProtoOpScript, ScriptOps::kVolumeDn2},
+ //{"volumeDn3", ProtoOp::kProtoOpScript, ScriptOps::kVolumeDn3},
+ //{"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},
+ //{"swap", ProtoOp::kProtoOpScript, ScriptOps::kSwap},
+ //{"say1", ProtoOp::kProtoOpScript, ScriptOps::kSay1},
+ //{"say2", ProtoOp::kProtoOpScript, ScriptOps::kSay2},
+ //{"say3", ProtoOp::kProtoOpScript, ScriptOps::kSay3},
+ //{"say3@", ProtoOp::kProtoOpScript, ScriptOps::kSay3Get},
+ {"say2k", ProtoOp::kProtoOpScript, ScriptOps::kSay2K},
+ {"say3k", ProtoOp::kProtoOpScript, ScriptOps::kSay3K},
+ //{"setTimer", ProtoOp::kProtoOpScript, ScriptOps::kSetTimer},
+ //{"getTimer", ProtoOp::kProtoOpScript, ScriptOps::kGetTimer},
+ //{"delay", ProtoOp::kProtoOpScript, ScriptOps::kDelay},
+ //{"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},
+ {"+", ProtoOp::kProtoOpScript, ScriptOps::kAdd},
+ {"-", ProtoOp::kProtoOpScript, ScriptOps::kSub},
+ {"not", ProtoOp::kProtoOpScript, ScriptOps::kNot},
+ {"=", ProtoOp::kProtoOpScript, ScriptOps::kCmpEq},
+ {">", ProtoOp::kProtoOpScript, ScriptOps::kCmpGt},
+ {"<", ProtoOp::kProtoOpScript, ScriptOps::kCmpLt},
+
+ //{"bit@", ProtoOp::kProtoOpScript, ScriptOps::kBitLoad},
+ //{"bit0!", ProtoOp::kProtoOpScript, ScriptOps::kBitSet0},
+ //{"bit1!", ProtoOp::kProtoOpScript, ScriptOps::kBitSet1},
+
+ //{"soundS1", ProtoOp::kProtoOpScript, ScriptOps::kSoundS1},
+ //{"soundS2", ProtoOp::kProtoOpScript, ScriptOps::kSoundS2},
+ //{"soundS3", ProtoOp::kProtoOpScript, ScriptOps::kSoundS3},
+ //{"soundL1", ProtoOp::kProtoOpScript, ScriptOps::kSoundL1},
+ //{"soundL2", ProtoOp::kProtoOpScript, ScriptOps::kSoundL2},
+ //{"soundL3", ProtoOp::kProtoOpScript, ScriptOps::kSoundL3},
+ //{"3DsoundS2", ProtoOp::kProtoOpScript, ScriptOps::k3DSoundS2},
+ //{"3DsoundL2", ProtoOp::kProtoOpScript, ScriptOps::k3DSoundL2},
+ //{"3DsoundL3", ProtoOp::kProtoOpScript, ScriptOps::k3DSoundL3},
+ //{"stopaL", ProtoOp::kProtoOpScript, ScriptOps::kStopAL},
+ //{"range", ProtoOp::kProtoOpScript, ScriptOps::kRange},
+ //{"addXsound", ProtoOp::kProtoOpScript, ScriptOps::kAddXSound},
+ //{"clrXsound", ProtoOp::kProtoOpScript, ScriptOps::kClrXSound},
+ //{"stopSndLA", ProtoOp::kProtoOpScript, ScriptOps::kStopSndLA},
+ //{"stopSndLO", ProtoOp::kProtoOpScript, ScriptOps::kStopSndLO},
+
+ //{"music", ProtoOp::kProtoOpScript, ScriptOps::kMusic},
+ //{"musicUp", ProtoOp::kProtoOpScript, ScriptOps::kMusicVolRamp},
+ //{"musicDn", ProtoOp::kProtoOpScript, ScriptOps::kMusicVolRamp},
+
+ //{"parm0", ProtoOp::kProtoOpScript, ScriptOps::kParm0},
+ //{"parm1", ProtoOp::kProtoOpScript, ScriptOps::kParm1},
+ //{"parm2", ProtoOp::kProtoOpScript, ScriptOps::kParm2},
+ //{"parm3", ProtoOp::kProtoOpScript, ScriptOps::kParm3},
+ //{"parmG", ProtoOp::kProtoOpScript, ScriptOps::kParmG},
+ //{"sparmX", ProtoOp::kProtoOpScript, ScriptOps::kSParmX},
+ //{"sanimX", ProtoOp::kProtoOpScript, ScriptOps::kSAnimX},
+
+ //{"disc1", ProtoOp::kProtoOpScript, ScriptOps::kDisc1},
+ //{"disc2", ProtoOp::kProtoOpScript, ScriptOps::kDisc2},
+ //{"disc3", ProtoOp::kProtoOpScript, ScriptOps::kDisc3},
+
+ //{"goto", ProtoOp::kProtoOpScript, ScriptOps::kGoto},
+
+ {"#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},
+ //{"saveAs", ProtoOp::kProtoOpScript, ScriptOps::kSaveAs},
+ //{"save0", ProtoOp::kProtoOpNoop, ScriptOps::kSave0},
+ //{"exit", ProtoOp::kProtoOpScript, ScriptOps::kExit},
+ //{"allowedSave", ProtoOp::kProtoOpScript, ScriptOps::kAllowSaves},
+
+ {"NO_UPDATE", ProtoOp::kProtoOpScript, ScriptOps::kNoUpdate},
+ {"no_clear", ProtoOp::kProtoOpScript, ScriptOps::kNoClear},
+ {"#M\"", ProtoOp::kProtoOpScript, ScriptOps::kM},
+ {"#EM", ProtoOp::kProtoOpScript, ScriptOps::kEM},
+ {"e\"", ProtoOp::kProtoOpScript, ScriptOps::kE},
+ {"se\"", ProtoOp::kProtoOpScript, ScriptOps::kSE},
+ {".\"", ProtoOp::kProtoOpScript, ScriptOps::kDot},
+ {"s.\"", ProtoOp::kProtoOpScript, ScriptOps::kSDot},
+ {"say1", ProtoOp::kProtoOpScript, ScriptOps::kSay1_AD2044},
+ {"say2", ProtoOp::kProtoOpScript, ScriptOps::kSay2_AD2044},
+ {"say1rnd", ProtoOp::kProtoOpScript, ScriptOps::kSay1Rnd},
+ {"sound", ProtoOp::kProtoOpScript, ScriptOps::kSound},
+ {"isound", ProtoOp::kProtoOpScript, ScriptOps::kISound},
+ {"usound", ProtoOp::kProtoOpScript, ScriptOps::kUSound},
+ {"r@", ProtoOp::kProtoOpScript, ScriptOps::kRGet},
+};
+
static ScriptNamedInstruction g_reahNamedInstructions[] = {
{"rotate", ProtoOp::kProtoOpScript, ScriptOps::kRotate},
{"angle", ProtoOp::kProtoOpScript, ScriptOps::kAngle},
@@ -688,8 +827,6 @@ static ScriptNamedInstruction g_reahNamedInstructions[] = {
{"allowedSave", ProtoOp::kProtoOpScript, ScriptOps::kAllowSaves},
};
-
-
static ScriptNamedInstruction g_schizmNamedInstructions[] = {
{"StopScore", ProtoOp::kProtoOpScript, ScriptOps::kMusicStop},
{"PlayScore", ProtoOp::kProtoOpScript, ScriptOps::kMusicPlayScore},
@@ -863,7 +1000,7 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
return true;
}
- if (_dialect == kScriptDialectReah) {
+ if (_dialect == kScriptDialectReah || _dialect == kScriptDialectAD2044) {
if (token.hasPrefix("CUR_")) {
script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
return true;
@@ -893,7 +1030,38 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
return true;
}
- if (_dialect == kScriptDialectReah) {
+ if (_dialect == kScriptDialectAD2044) {
+ for (const ScriptNamedInstruction &namedInstr : g_ad2044NamedInstructions) {
+ if (token == namedInstr.str) {
+ int32 paramArg = 0;
+
+ if (token.hasSuffix("\"")) {
+ TextParserState state;
+
+ char c = 0;
+ if (!_parser.skipWhitespaceAndComments(c, state))
+ error("Error compiling script at line %i col %i: Unterminated string", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ Common::String str;
+ for (;;) {
+ if (c == '\"')
+ break;
+
+ str += c;
+
+ if (!_parser.readOneChar(c, state)) {
+ error("Error compiling script at line %i col %i: Unterminated string", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+ }
+ }
+
+ paramArg = indexString(str);
+ }
+
+ script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, paramArg));
+ return true;
+ }
+ }
+ } else if (_dialect == kScriptDialectReah) {
for (const ScriptNamedInstruction &namedInstr : g_reahNamedInstructions) {
if (token == namedInstr.str) {
script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, 0));
@@ -1375,6 +1543,13 @@ Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, ui
return scriptSet;
}
+Common::SharedPtr<ScriptSet> compileAD2044LogicFile(Common::ReadStream &stream, uint streamSize, const Common::Path &blamePath) {
+ Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
+
+ compileLogicFile(*scriptSet, stream, streamSize, blamePath, kScriptDialectAD2044, 0, 0, nullptr);
+ return scriptSet;
+}
+
void compileSchizmLogicFile(ScriptSet &scriptSet, uint loadAsRoom, uint fileRoom, Common::ReadStream &stream, uint streamSize, const Common::Path &blamePath, IScriptCompilerGlobalState *gs) {
compileLogicFile(scriptSet, stream, streamSize, blamePath, kScriptDialectSchizm, loadAsRoom, fileRoom, gs);
}
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 39ff3dadcf1..a758183d59c 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -219,6 +219,29 @@ enum ScriptOp {
kFn,
kItemHighlightSetTrue,
+ // AD2044 ops
+ kAnimT,
+ kAnimForward,
+ kAnimReverse,
+ kAnimKForward,
+ kNoUpdate,
+ kNoClear,
+ kSay1_AD2044,
+ kSay2_AD2044,
+ kSay1Rnd,
+ kM,
+ kEM,
+ kSE,
+ kSDot,
+ kE,
+ kDot,
+ kSound,
+ kISound,
+ kUSound,
+ kSay2K,
+ kSay3K,
+ kRGet,
+
kNumOps,
};
@@ -285,6 +308,8 @@ struct IScriptCompilerGlobalState {
Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState();
Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::Path &blamePath);
+Common::SharedPtr<ScriptSet> compileAD2044LogicFile(Common::ReadStream &stream, uint streamSize, const Common::Path &blamePath);
+
void compileSchizmLogicFile(ScriptSet &scriptSet, uint loadAsRoom, uint fileRoom, Common::ReadStream &stream, uint streamSize, const Common::Path &blamePath, IScriptCompilerGlobalState *gs);
bool checkSchizmLogicForDuplicatedRoom(Common::ReadStream &stream, uint streamSize);
void optimizeScriptSet(ScriptSet &scriptSet);
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index e9546bd73f5..809eceeb546 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -135,37 +135,51 @@ Common::Error VCruiseEngine::run() {
// Figure out screen layout
Common::Point size;
- Common::Point videoSize;
- Common::Point traySize;
- Common::Point menuBarSize;
-
- if (_gameDescription->gameID == GID_REAH) {
- videoSize = Common::Point(608, 348);
- menuBarSize = Common::Point(640, 44);
- traySize = Common::Point(640, 88);
- } else if (_gameDescription->gameID == GID_SCHIZM) {
- videoSize = Common::Point(640, 360);
- menuBarSize = Common::Point(640, 32);
- traySize = Common::Point(640, 88);
+ if (_gameDescription->gameID == GID_AD2044) {
+ size = Common::Point(640, 480);
+
+ Common::Point traySize = Common::Point(640, 97);
+ Common::Point menuBarSize = Common::Point(188, 102);
+ Common::Point videoTL = Common::Point(20, 21);
+
+ Common::Point videoSize = Common::Point(432, 307);
+
+ _menuBarRect = Common::Rect(size.x - menuBarSize.x, 0, size.x, menuBarSize.y);
+ _videoRect = Common::Rect(videoTL, videoTL + videoSize);
+ _trayRect = Common::Rect(0, size.y - traySize.y, size.x, size.y);
} else {
- error("Unknown game");
- }
+ Common::Point videoSize;
+ Common::Point traySize;
+ Common::Point menuBarSize;
+
+ if (_gameDescription->gameID == GID_REAH) {
+ videoSize = Common::Point(608, 348);
+ menuBarSize = Common::Point(640, 44);
+ traySize = Common::Point(640, 88);
+ } else if (_gameDescription->gameID == GID_SCHIZM) {
+ videoSize = Common::Point(640, 360);
+ menuBarSize = Common::Point(640, 32);
+ traySize = Common::Point(640, 88);
+ } 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.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;
+ 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 + menuBarSize.y);
- Common::Point trayTL = Common::Point((size.x - traySize.x) / 2, videoTL.y + videoSize.y);
+ Common::Point menuTL = Common::Point((size.x - menuBarSize.x) / 2, 0);
+ 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);
- _trayRect = Common::Rect(trayTL.x, trayTL.y, trayTL.x + traySize.x, trayTL.y + traySize.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);
@@ -196,6 +210,10 @@ Common::Error VCruiseEngine::run() {
_runtime->setFastAnimationMode(true);
}
+ if (ConfMan.getBool("vcruise_use_4bit")) {
+ _runtime->setLowQualityGraphicsMode(true);
+ }
+
if (ConfMan.hasKey("save_slot")) {
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot >= 0) {
@@ -215,7 +233,7 @@ Common::Error VCruiseEngine::run() {
break;
_runtime->drawFrame();
- _system->delayMillis(10);
+ _system->delayMillis(5);
}
_runtime.reset();
Commit: 9db748a3f9459a841df08e2d17301d25f45e1b3c
https://github.com/scummvm/scummvm/commit/9db748a3f9459a841df08e2d17301d25f45e1b3c
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: More AD2044 loading stuff
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index a31a331d2c8..82881769f07 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -331,7 +331,6 @@ public:
private:
void load();
- static const uint kNumScreens = 96;
static const uint kFirstScreen = 0xa0;
uint _roomNumber;
@@ -367,7 +366,15 @@ void AD2044MapLoader::load() {
// This is loaded even if the open fails
_isLoaded = true;
- Common::Path mapFileName(Common::String::format("map/SCR%i.MAP", static_cast<int>(_roomNumber * 100u + _screenNumber)));
+ if (_screenNumber < kFirstScreen)
+ return;
+
+ uint adjustedScreenNumber = _screenNumber - kFirstScreen;
+
+ if (adjustedScreenNumber > 99)
+ return;
+
+ Common::Path mapFileName(Common::String::format("map/SCR%i.MAP", static_cast<int>(_roomNumber * 100u + adjustedScreenNumber)));
Common::File mapFile;
if (!mapFile.open(mapFileName))
@@ -840,6 +847,9 @@ FrameData::FrameData() : areaID{0, 0, 0, 0}, areaFrameIndex(0), frameIndex(0), f
FrameData2::FrameData2() : x(0), y(0), angle(0), frameNumberInArea(0), unknown(0) {
}
+AnimFrameRange::AnimFrameRange() : animationNum(0), firstFrame(0), lastFrame(0) {
+}
+
SoundParams3D::SoundParams3D() : minRange(0), maxRange(0), unknownRange(0) {
}
@@ -1361,6 +1371,11 @@ void Runtime::loadCursors(const char *exeName) {
_namedCursors["curDrop"] = 91;
}
+ if (_gameID == GID_AD2044) {
+ _namedCursors["CUR_PRZOD"] = 2; // Przod = forward
+ _namedCursors["CUR_PRAWO"] = 3; // Prawo = right
+ }
+
_panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
_panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableDown] = 3;
_panCursors[kPanCursorDraggableHoriz] = 4;
@@ -1477,7 +1492,12 @@ bool Runtime::bootGame(bool newGame) {
_musicMute = false;
debug(1, "Booting V-Cruise game...");
- loadIndex();
+
+ if (_gameID == GID_AD2044)
+ loadAD2044Index();
+ else
+ loadReahSchizmIndex();
+
debug(1, "Index loaded OK");
findWaves();
debug(1, "Waves indexed OK");
@@ -1510,7 +1530,7 @@ bool Runtime::bootGame(bool newGame) {
StartConfigDef &startConfig = _startConfigs[kStartConfigInitial];
startConfig.disc = 1;
startConfig.room = 1;
- startConfig.screen = 5;
+ startConfig.screen = 0xa5;
startConfig.direction = 0;
} else
error("Don't have a start config for this game");
@@ -1671,13 +1691,11 @@ bool Runtime::bootGame(bool newGame) {
_gameState = kGameStateIdle;
if (newGame) {
- if (ConfMan.hasKey("vcruise_skip_menu") && ConfMan.getBool("vcruise_skip_menu")) {
+ if (_gameID == GID_AD2044 || (ConfMan.hasKey("vcruise_skip_menu") && ConfMan.getBool("vcruise_skip_menu"))) {
_mostRecentValidSaveState = generateNewGameSnapshot();
restoreSaveGameSnapshot();
} else {
- uint initialScreen = (_gameID == GID_AD2044) ? 5 : 0xb1;
-
- changeToScreen(1, initialScreen);
+ changeToScreen(1, 0xb1);
}
}
@@ -2722,12 +2740,7 @@ void Runtime::queueOSEvent(const OSEvent &evt) {
_pendingEvents.push_back(timedEvt);
}
-void Runtime::loadIndex() {
- if (_gameID == GID_AD2044) {
- // No index
- return;
- }
-
+void Runtime::loadReahSchizmIndex() {
const char *indexPath = "Log/Index.txt";
Common::INIFile iniFile;
@@ -2781,6 +2794,65 @@ void Runtime::loadIndex() {
}
}
+void Runtime::loadAD2044Index() {
+ const byte searchPattern[] = {0x01, 0x01, 0xa1, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x00, 0xc7, 0x00, 0x00, 0x00};
+
+ Common::File f;
+ if (!f.open("ad2044.exe") || f.size() > 2u * 1024u * 1024u)
+ error("Couldn't open ad2044.exe to read animation index");
+
+ uint32 exeSize = static_cast<uint32>(f.size());
+
+ Common::Array<byte> exeContents;
+ exeContents.resize(exeSize);
+
+ if (exeSize < sizeof(searchPattern) || f.read(&exeContents[0], exeSize) != exeSize)
+ error("Couldn't load executable to scan for animation table");
+
+ uint endPoint = exeSize - sizeof(searchPattern);
+
+ bool foundAnimTable = false;
+ uint32 animTablePos = 0;
+
+ for (uint i = 0; i <= endPoint; i++) {
+ bool match = true;
+ for (uint j = 0; j < sizeof(searchPattern); j++) {
+ if (exeContents[i + j] != searchPattern[j]) {
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ if (foundAnimTable)
+ error("Found multiple byte patterns matching animation table in ad2044.exe");
+
+ foundAnimTable = true;
+ animTablePos = i;
+ }
+ }
+
+ if (!foundAnimTable)
+ error("Couldn't find animation table in ad2044.exe");
+
+ // Parse the animation table. The unparsed values are the same for all entries.
+ for (uint entryPos = animTablePos; entryPos < exeSize - 15u; entryPos += 16) {
+ const byte *entry = &exeContents[entryPos];
+
+ AD2044AnimationDef animDef;
+ animDef.roomID = entry[1];
+ animDef.lookupID = entry[2];
+
+ animDef.fwdAnimationID = READ_LE_INT16(entry + 10);
+ animDef.revAnimationID = READ_LE_INT16(entry + 12);
+
+ if (animDef.lookupID == 0) // Terminator
+ break;
+
+ _ad2044AnimationDefs.push_back(animDef);
+ }
+}
+
void Runtime::findWaves() {
if (_gameID == GID_AD2044) {
for (int disc = 0; disc < 2; disc++) {
@@ -3600,13 +3672,28 @@ void Runtime::loadFrameData(Common::SeekableReadStream *stream) {
char decAreaFrameIndex[4];
memcpy(decAreaFrameIndex, frameData + 12, 4);
+ bool isPadFrame = false;
+
+ if (_gameID == GID_AD2044) {
+ isPadFrame = true;
+
+ for (uint j = 0; j < 8; j++) {
+ if (frameData[j + 8] != 0) {
+ isPadFrame = false;
+ break;
+ }
+ }
+ }
+
uint areaFrameIndex = 0;
- for (int digit = 0; digit < 4; digit++) {
- char c = decAreaFrameIndex[digit];
- if (c < '0' || c > '9')
- error("Invalid area frame index in DTA data");
+ if (!isPadFrame) {
+ for (int digit = 0; digit < 4; digit++) {
+ char c = decAreaFrameIndex[digit];
+ if (c < '0' || c > '9')
+ error("Invalid area frame index in DTA data");
- areaFrameIndex = areaFrameIndex * 10u + static_cast<uint>(c - '0');
+ areaFrameIndex = areaFrameIndex * 10u + static_cast<uint>(c - '0');
+ }
}
fd.areaFrameIndex = areaFrameIndex;
@@ -3642,6 +3729,99 @@ void Runtime::loadFrameData2(Common::SeekableReadStream *stream) {
}
}
+void Runtime::loadTabData(uint animNumber, Common::SeekableReadStream *stream) {
+ int64 size64 = stream->size() - stream->pos();
+
+ if (size64 > UINT_MAX || size64 < 0)
+ error("Internal error: Oversized TAB file");
+
+ uint32 fileSize = static_cast<uint32>(size64);
+
+ Common::Array<Common::String> lines;
+
+ Common::Array<char> chars;
+ chars.resize(fileSize);
+
+ if (fileSize > 0 && stream->read(&chars[0], fileSize) != fileSize)
+ error("Failed to read TAB file data");
+
+ uint searchStart = 0;
+ while (searchStart < chars.size()) {
+ uint endPos = searchStart;
+ while (endPos != chars.size()) {
+ if (chars[endPos] == '\n' || chars[endPos] == '\r')
+ break;
+
+ endPos++;
+ }
+
+ if (endPos == searchStart)
+ lines.push_back(Common::String(""));
+ else
+ lines.push_back(Common::String(&chars[searchStart], endPos - searchStart));
+
+
+ if (endPos != chars.size() && chars[endPos] == '\r')
+ endPos++;
+ if (endPos != chars.size() && chars[endPos] == '\n')
+ endPos++;
+
+ searchStart = endPos;
+ }
+
+ for (const Common::String &line : lines) {
+ if (line.hasPrefix("//"))
+ continue;
+
+ uint32 openBracePos = line.find('{');
+ if (openBracePos == Common::String::npos)
+ continue;
+
+ uint32 closeBracePos = line.find("},", openBracePos + 1);
+ if (closeBracePos == Common::String::npos)
+ error("Strangely-formatted animation table line: %s", line.c_str());
+
+ Common::String enclosedContents = line.substr(1, closeBracePos - 1);
+
+ uint32 firstCommaPos = enclosedContents.find(',');
+ if (firstCommaPos == Common::String::npos)
+ error("Strangely-formatted animation table line: %s", line.c_str());
+
+ uint32 secondCommaPos = enclosedContents.find(',', firstCommaPos + 1);
+ if (secondCommaPos == Common::String::npos)
+ error("Strangely-formatted animation table line: %s", line.c_str());
+
+ Common::String numbers[3] = {
+ enclosedContents.substr(0, firstCommaPos),
+ enclosedContents.substr(firstCommaPos + 1, secondCommaPos - (firstCommaPos + 1)),
+ enclosedContents.substr(secondCommaPos + 1)
+ };
+
+ int parsedNumbers[3] = {0, 0, 0};
+
+ uint i = 0;
+ for (Common::String &str : numbers) {
+ str.trim();
+
+ if (sscanf(str.c_str(), "%i", &parsedNumbers[i]) != 1)
+ error("Strangely-formatted animation table line: %s", line.c_str());
+
+ i++;
+ }
+
+ AnimFrameRange frameRange;
+ frameRange.animationNum = animNumber;
+ frameRange.firstFrame = static_cast<uint>(parsedNumbers[1]);
+ frameRange.lastFrame = static_cast<uint>(parsedNumbers[2]);
+
+ // Animation ID 9099 is duplicated but it doesn't really matter since the duplicate is identical
+ if (_animIDToFrameRange.find(parsedNumbers[0]) == _animIDToFrameRange.end())
+ _animIDToFrameRange[parsedNumbers[0]] = frameRange;
+ else
+ warning("Animation ID %i was duplicated", parsedNumbers[0]);
+ }
+}
+
void Runtime::changeMusicTrack(int track) {
if (track == _musicTrack && _musicPlayer.get() != nullptr)
return;
@@ -3757,6 +3937,8 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
if (animFile < 0)
animFile = -animFile;
+ bool isAD2044 = (_gameID == GID_AD2044);
+
if (_loadedAnimation != static_cast<uint>(animFile)) {
_loadedAnimation = animFile;
_frameData.clear();
@@ -3764,7 +3946,7 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
_animDecoder.reset();
_animDecoderState = kAnimDecoderStateStopped;
- Common::Path aviFileName(Common::String::format("Anims/Anim%04i.avi", animFile));
+ Common::Path aviFileName(Common::String::format(isAD2044 ? "anims/ANIM%04i.AVI" : "Anims/Anim%04i.avi", animFile));
Common::File *aviFile = new Common::File();
if (aviFile->open(aviFileName)) {
@@ -3780,28 +3962,43 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
applyAnimationVolume();
- Common::Path sfxFileName(Common::String::format("Sfx/Anim%04i.sfx", animFile));
- Common::File sfxFile;
-
_sfxData.reset();
- if (sfxFile.open(sfxFileName))
- _sfxData.load(sfxFile, _mixer);
- sfxFile.close();
+ if (!isAD2044) {
+ Common::Path sfxFileName(Common::String::format("Sfx/Anim%04i.sfx", animFile));
+ Common::File sfxFile;
+
+ if (sfxFile.open(sfxFileName))
+ _sfxData.load(sfxFile, _mixer);
+ sfxFile.close();
+ }
- Common::Path dtaFileName(Common::String::format("Anims/Anim%04i.dta", animFile));
+ Common::Path dtaFileName(Common::String::format(isAD2044 ? "anims/ANIM0001.DTA" : "Anims/Anim%04i.dta", animFile));
Common::File dtaFile;
if (dtaFile.open(dtaFileName))
loadFrameData(&dtaFile);
dtaFile.close();
- Common::Path twoDtFileName(Common::String::format("Dta/Anim%04i.2dt", animFile));
- Common::File twoDtFile;
+ if (!isAD2044) {
+ Common::Path twoDtFileName(Common::String::format("Dta/Anim%04i.2dt", animFile));
+ Common::File twoDtFile;
- if (twoDtFile.open(twoDtFileName))
- loadFrameData2(&twoDtFile);
- twoDtFile.close();
+ if (twoDtFile.open(twoDtFileName))
+ loadFrameData2(&twoDtFile);
+ twoDtFile.close();
+ }
+
+ if (isAD2044) {
+ _animIDToFrameRange.clear();
+
+ Common::Path tabFileName(Common::String::format("anims/ANIM%04i.TAB", animFile));
+ Common::File tabFile;
+
+ if (tabFile.open(tabFileName))
+ loadTabData(animFile, &tabFile);
+ tabFile.close();
+ }
_loadedAnimationHasSound = (_animDecoder->getAudioTrackCount() > 0);
@@ -4862,6 +5059,9 @@ void Runtime::drawCompass() {
if (!isTrayVisible())
return;
+ if (_gameID == GID_AD2044)
+ return;
+
bool haveHorizontalRotate = false;
bool haveUp = false;
bool haveDown = false;
@@ -5224,6 +5424,10 @@ void Runtime::changeToMenuPage(MenuPage *menuPage) {
}
void Runtime::checkInGameMenuHover() {
+ // TODO
+ if (_gameID == GID_AD2044)
+ return;
+
if (_inGameMenuState == kInGameMenuStateInvisible) {
if (_menuSection.rect.contains(_mousePos) && _isInGame) {
// Figure out what elements should be visible
@@ -5981,6 +6185,12 @@ Common::SharedPtr<SaveGameSnapshot> Runtime::generateNewGameSnapshot() const {
} else
mainState->loadedAnimation = 1;
+ // AD2044 new game normally loads a pre-packaged save. Unlike Reah and Schizm,
+ // it doesn't appear to have a startup script, so we need to set up everything
+ // that it needs here.
+ if (_gameID == GID_AD2044)
+ mainState->animDisplayingFrame = 345;
+
return snapshot;
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index d554363efae..2909ee5063b 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -116,6 +116,13 @@ enum GameState {
kGameStateMenu,
};
+struct AD2044AnimationDef {
+ byte roomID;
+ byte lookupID;
+ short fwdAnimationID;
+ short revAnimationID;
+};
+
struct AnimationDef {
AnimationDef();
@@ -370,6 +377,14 @@ struct FrameData2 {
uint16 unknown; // Subarea or something?
};
+struct AnimFrameRange {
+ AnimFrameRange();
+
+ uint animationNum;
+ uint firstFrame;
+ uint lastFrame; // Inclusive
+};
+
struct InventoryItem {
InventoryItem();
@@ -845,7 +860,8 @@ private:
void processUniversalKeymappedEvents(KeymappedEvent evt);
- void loadIndex();
+ void loadReahSchizmIndex();
+ void loadAD2044Index();
void findWaves();
void loadConfig(const char *cfgPath);
void loadScore();
@@ -869,6 +885,7 @@ private:
bool dischargeIdleClick();
void loadFrameData(Common::SeekableReadStream *stream);
void loadFrameData2(Common::SeekableReadStream *stream);
+ void loadTabData(uint animNumber, Common::SeekableReadStream *stream);
void changeMusicTrack(int musicID);
void startScoreSection();
@@ -1250,6 +1267,7 @@ private:
Common::Array<uint> _roomDuplicationOffsets;
RoomToScreenNameToRoomMap_t _globalRoomScreenNameToScreenIDs;
Common::SharedPtr<ScriptSet> _scriptSet;
+ Common::Array<AD2044AnimationDef> _ad2044AnimationDefs;
Common::Array<CallStackFrame> _scriptCallStack;
@@ -1299,9 +1317,13 @@ private:
Common::Array<FrameData2> _frameData2;
//uint32 _loadedArea;
+ // Reah/Schizm animation map
Common::Array<Common::String> _animDefNames;
Common::HashMap<Common::String, uint> _animDefNameToIndex;
+ // AD2044 animation map
+ Common::HashMap<int, AnimFrameRange> _animIDToFrameRange;
+
bool _idleLockInteractions;
bool _idleIsOnInteraction;
bool _idleHaveClickInteraction;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 69dbca7462f..0e094b9554a 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2043,7 +2043,13 @@ void Runtime::scriptOpPuzzleDone(ScriptArg_t arg) {
// AD2044 ops
OPCODE_STUB(AnimT)
-OPCODE_STUB(AnimForward)
+
+void Runtime::scriptOpAnimForward(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ error("AnimForward NYI");
+}
+
OPCODE_STUB(AnimReverse)
OPCODE_STUB(AnimKForward)
OPCODE_STUB(Say2K)
@@ -2305,6 +2311,12 @@ bool Runtime::runScript() {
DISPATCH_OP(NoUpdate);
DISPATCH_OP(NoClear);
+ DISPATCH_OP(M);
+ DISPATCH_OP(EM);
+ DISPATCH_OP(SE);
+ DISPATCH_OP(SDot);
+ DISPATCH_OP(E);
+
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
}
Commit: b7f0a753ca3aa9b1cf5025c24ad62f77f7b48866
https://github.com/scummvm/scummvm/commit/b7f0a753ca3aa9b1cf5025c24ad62f77f7b48866
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Add #M and #EM opcodes
Changed paths:
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 0e094b9554a..2a1957a49cb 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2060,8 +2060,17 @@ OPCODE_STUB(NoClear)
OPCODE_STUB(Say1_AD2044)
OPCODE_STUB(Say2_AD2044)
OPCODE_STUB(Say1Rnd)
-OPCODE_STUB(M)
-OPCODE_STUB(EM)
+
+void Runtime::scriptOpM(ScriptArg_t arg) {
+ // Looks like this is possibly support to present a mouse click prompt and end
+ // with the #EM instruction, but so far as best I can tell, it just stops
+ // execution.
+ scriptOpLMB(arg);
+}
+
+void Runtime::scriptOpEM(ScriptArg_t arg) {
+}
+
OPCODE_STUB(SE)
OPCODE_STUB(SDot)
OPCODE_STUB(E)
@@ -2071,6 +2080,8 @@ OPCODE_STUB(ISound)
OPCODE_STUB(USound)
OPCODE_STUB(RGet)
+
+// Unused Schizm ops
// Only used in fnRandomBirds and fnRandomMachines in Room 60, both of which are unused
OPCODE_STUB(SndAddRandom)
OPCODE_STUB(SndClearRandom)
Commit: e26ec842f931b5f413628409dae525fd9b1e1126
https://github.com/scummvm/scummvm/commit/e26ec842f931b5f413628409dae525fd9b1e1126
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Add CUR_LUPA binding for AD2044
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 82881769f07..c152f38138b 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1374,6 +1374,7 @@ void Runtime::loadCursors(const char *exeName) {
if (_gameID == GID_AD2044) {
_namedCursors["CUR_PRZOD"] = 2; // Przod = forward
_namedCursors["CUR_PRAWO"] = 3; // Prawo = right
+ _namedCursors["CUR_LUPA"] = 6; // Lupa = magnifier
}
_panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
Commit: cd2e9aa37f5f1c8a70ac6ec01d29e62a2428a4e6
https://github.com/scummvm/scummvm/commit/cd2e9aa37f5f1c8a70ac6ec01d29e62a2428a4e6
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Load AD2044 font. Fix cursor animations.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index c152f38138b..d432da4b081 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -318,6 +318,8 @@ void ReahSchizmMapLoader::unload() {
for (uint screen = 0; screen < kNumScreens; screen++)
for (uint direction = 0; direction < kNumDirections; direction++)
_screenDirections[screen][direction].reset();
+
+ _isLoaded = false;
}
class AD2044MapLoader : public MapLoader {
@@ -385,6 +387,7 @@ void AD2044MapLoader::load() {
void AD2044MapLoader::unload() {
_currentMap.reset();
+ _isLoaded = false;
}
@@ -1250,7 +1253,13 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_rng.reset(new Common::RandomSource("vcruise"));
#ifdef USE_FREETYPE2
- _subtitleFontKeepalive.reset(Graphics::loadTTFFontFromArchive("NotoSans-Regular.ttf", 16, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeLight));
+ if (_gameID == GID_AD2044) {
+ Common::File f;
+ if (f.open("gfx/AD2044.TTF"))
+ _subtitleFontKeepalive.reset(Graphics::loadTTFFont(f, 16, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeLight));
+ } else
+ _subtitleFontKeepalive.reset(Graphics::loadTTFFontFromArchive("NotoSans-Regular.ttf", 16, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeLight));
+
_subtitleFont = _subtitleFontKeepalive.get();
#endif
@@ -1374,6 +1383,7 @@ void Runtime::loadCursors(const char *exeName) {
if (_gameID == GID_AD2044) {
_namedCursors["CUR_PRZOD"] = 2; // Przod = forward
_namedCursors["CUR_PRAWO"] = 3; // Prawo = right
+ _namedCursors["CUR_LEWO"] = 1; // Lewo = left
_namedCursors["CUR_LUPA"] = 6; // Lupa = magnifier
}
@@ -3424,7 +3434,7 @@ void Runtime::refreshCursor(uint32 currentTime) {
timeSinceTimeBase %= _cursorCycleLength * 50u;
_cursorTimeBase = currentTime - timeSinceTimeBase;
- stepTime = timeSinceTimeBase * 60u / 1000u;
+ stepTime = (timeSinceTimeBase * 60u / 1000u) % _cursorCycleLength;
}
uint imageIndex = 0;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 2909ee5063b..89d72a1ccad 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -1154,6 +1154,7 @@ private:
void scriptOpItemHighlightSetTrue(ScriptArg_t arg);
// AD2044 ops
+ void scriptOpAnimAD2044(bool isForward);
void scriptOpAnimT(ScriptArg_t arg);
void scriptOpAnimForward(ScriptArg_t arg);
void scriptOpAnimReverse(ScriptArg_t arg);
@@ -1388,6 +1389,7 @@ private:
static const uint kAnimDefStackArgs = 8;
static const uint kCursorArrow = 0;
+ static const uint kCursorWait = 29;
static const int kPanoramaPanningMarginX = 11;
static const int kPanoramaPanningMarginY = 11;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 2a1957a49cb..3cbd969b53e 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2044,17 +2044,63 @@ void Runtime::scriptOpPuzzleDone(ScriptArg_t arg) {
// AD2044 ops
OPCODE_STUB(AnimT)
-void Runtime::scriptOpAnimForward(ScriptArg_t arg) {
+void Runtime::scriptOpAnimAD2044(bool isForward) {
TAKE_STACK_INT(2);
- error("AnimForward NYI");
+ int16 animationID = 0;
+
+ bool found = false;
+
+ for (const AD2044AnimationDef &def : _ad2044AnimationDefs) {
+ if (static_cast<StackInt_t>(def.lookupID) == stackArgs[0]) {
+ animationID = isForward ? def.fwdAnimationID : def.revAnimationID;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ error("Couldn't resolve animation lookup ID %i", static_cast<int>(stackArgs[0]));
+
+ Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _animIDToFrameRange.find(animationID);
+ if (animRangeIt == _animIDToFrameRange.end())
+ error("Couldn't resolve animation ID %i", static_cast<int>(animationID));
+
+ AnimationDef animDef;
+ animDef.animNum = animRangeIt->_value.animationNum;
+ animDef.firstFrame = animRangeIt->_value.firstFrame;
+ animDef.lastFrame = animRangeIt->_value.lastFrame;
+
+ changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
+
+ _gameState = kGameStateWaitingForAnimation;
+ _screenNumber = stackArgs[1];
+ _havePendingScreenChange = true;
+
+ clearIdleAnimations();
+
+ if (_loadedAnimationHasSound)
+ changeToCursor(nullptr);
+ else {
+ changeToCursor(_cursors[kCursorWait]);
+ }
+}
+
+void Runtime::scriptOpAnimForward(ScriptArg_t arg) {
+ scriptOpAnimAD2044(true);
+}
+
+void Runtime::scriptOpAnimReverse(ScriptArg_t arg) {
+ scriptOpAnimAD2044(false);
}
-OPCODE_STUB(AnimReverse)
OPCODE_STUB(AnimKForward)
OPCODE_STUB(Say2K)
OPCODE_STUB(Say3K)
-OPCODE_STUB(NoUpdate)
+
+void Runtime::scriptOpNoUpdate(ScriptArg_t arg) {
+}
+
OPCODE_STUB(NoClear)
OPCODE_STUB(Say1_AD2044)
Commit: 3ccaa683cdaae11db02e22087fdb7390281378d3
https://github.com/scummvm/scummvm/commit/3ccaa683cdaae11db02e22087fdb7390281378d3
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Fix AD2044 fullscreen UI
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 d432da4b081..17f584d6471 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1553,6 +1553,9 @@ bool Runtime::bootGame(bool newGame) {
_trayCornerGraphic = loadGraphic("Select_2", true);
}
+ if (_gameID == GID_AD2044)
+ _backgroundGraphic = loadGraphicFromPath("SCR0.BMP", true);
+
Common::Language lang = _defaultLanguage;
if (ConfMan.hasKey("language")) {
@@ -5002,7 +5005,11 @@ void Runtime::inventoryRemoveItem(uint itemID) {
}
void Runtime::clearScreen() {
- _system->fillScreen(_system->getScreenFormat().RGBToColor(0, 0, 0));
+ if (_gameID == GID_AD2044) {
+ _fullscreenMenuSection.surf->blitFrom(*_backgroundGraphic);
+ commitSectionToScreen(_fullscreenMenuSection, _fullscreenMenuSection.rect);
+ } else
+ _system->fillScreen(_system->getScreenFormat().RGBToColor(0, 0, 0));
}
void Runtime::redrawTray() {
@@ -5018,10 +5025,17 @@ void Runtime::redrawTray() {
}
void Runtime::clearTray() {
- uint32 blackColor = _traySection.surf->format.RGBToColor(0, 0, 0);
- Common::Rect trayRect(0, 0, _traySection.surf->w, _traySection.surf->h);
+ Common::Rect trayRect;
+ if (_gameID == GID_AD2044) {
+ trayRect = _traySection.rect;
+ trayRect.translate(-trayRect.left, -trayRect.top);
+ _traySection.surf->blitFrom(*_backgroundGraphic, _traySection.rect, trayRect);
+ } else {
+ uint32 blackColor = _traySection.surf->format.RGBToColor(0, 0, 0);
+ trayRect = Common::Rect(0, 0, _traySection.surf->w, _traySection.surf->h);
- _traySection.surf->fillRect(trayRect, blackColor);
+ _traySection.surf->fillRect(trayRect, blackColor);
+ }
this->commitSectionToScreen(_traySection, trayRect);
}
@@ -5030,6 +5044,9 @@ void Runtime::drawInventory(uint slot) {
if (!isTrayVisible())
return;
+ if (_gameID == GID_AD2044)
+ return;
+
Common::Rect trayRect = _traySection.rect;
trayRect.translate(-trayRect.left, -trayRect.top);
@@ -5196,6 +5213,10 @@ Common::SharedPtr<Graphics::Surface> Runtime::loadGraphic(const Common::String &
filePath.appendInPlace(graphicName);
filePath.appendInPlace(".bmp");
+ return loadGraphicFromPath(filePath, required);
+}
+
+Common::SharedPtr<Graphics::Surface> Runtime::loadGraphicFromPath(const Common::Path &filePath, bool required) {
Common::File f;
if (!f.open(filePath)) {
warning("Couldn't open BMP file '%s'", filePath.toString(Common::Path::kNativeSeparator).c_str());
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 89d72a1ccad..a1d53e9899a 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -947,6 +947,7 @@ private:
Common::String getFileNameForItemGraphic(uint itemID) const;
Common::SharedPtr<Graphics::Surface> loadGraphic(const Common::String &graphicName, bool required);
+ Common::SharedPtr<Graphics::Surface> loadGraphicFromPath(const Common::Path &path, bool required);
bool loadSubtitles(Common::CodePage codePage, bool guessCodePage);
@@ -1186,6 +1187,7 @@ private:
Common::SharedPtr<Graphics::Surface> _trayBackgroundGraphic;
Common::SharedPtr<Graphics::Surface> _trayHighlightGraphic;
Common::SharedPtr<Graphics::Surface> _trayCornerGraphic;
+ Common::SharedPtr<Graphics::Surface> _backgroundGraphic;
Common::Array<Common::SharedPtr<Graphics::Surface> > _uiGraphics;
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 809eceeb546..3967182491e 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -80,6 +80,7 @@ Common::Error VCruiseEngine::run() {
#if !defined(USE_JPEG)
if (_gameDescription->desc.flags & VCRUISE_GF_NEED_JPEG) {
+ .
return Common::Error(Common::kUnknownError, _s("This game requires JPEG support, which was not compiled in."));
}
#endif
Commit: 09a7b1fb5f23c0f8373b0cfadb31b2ff8df00215
https://github.com/scummvm/scummvm/commit/09a7b1fb5f23c0f8373b0cfadb31b2ff8df00215
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Add AD2044 subtitle rendering
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
engines/vcruise/vcruise.cpp
engines/vcruise/vcruise.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 17f584d6471..ba545b739db 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1256,7 +1256,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
if (_gameID == GID_AD2044) {
Common::File f;
if (f.open("gfx/AD2044.TTF"))
- _subtitleFontKeepalive.reset(Graphics::loadTTFFont(f, 16, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeLight));
+ _subtitleFontKeepalive.reset(Graphics::loadTTFFont(f, 16, Graphics::kTTFSizeModeCharacter, 108, 72, Graphics::kTTFRenderModeLight));
} else
_subtitleFontKeepalive.reset(Graphics::loadTTFFontFromArchive("NotoSans-Regular.ttf", 16, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeLight));
@@ -1278,11 +1278,14 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
Runtime::~Runtime() {
}
-void Runtime::initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt) {
+void Runtime::initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &subtitleRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt) {
_gameSection.init(gameRect, pixFmt);
_menuSection.init(menuRect, pixFmt);
_traySection.init(trayRect, pixFmt);
_fullscreenMenuSection.init(fullscreenMenuRect, pixFmt);
+
+ if (!subtitleRect.isEmpty())
+ _subtitleSection.init(subtitleRect, pixFmt);
}
void Runtime::loadCursors(const char *exeName) {
@@ -3537,6 +3540,11 @@ bool Runtime::dischargeIdleMouseMove() {
_idleHaveDragInteraction = false;
changeToCursor(_cursors[kCursorArrow]);
resetInventoryHighlights();
+
+ if (_gameID == GID_AD2044 && _tooltipText.size() > 0) {
+ _tooltipText.clear();
+ redrawSubtitleSection();
+ }
}
bool changedCircuitState = false;
@@ -4234,7 +4242,11 @@ void Runtime::stopSubtitles() {
_subtitleQueue.clear();
_isDisplayingSubtitles = false;
_isSubtitleSourceAnimation = false;
- redrawTray();
+
+ if (_gameID == GID_AD2044)
+ redrawSubtitleSection();
+ else
+ redrawTray();
}
void Runtime::stopSound(SoundInstance &sound) {
@@ -4397,17 +4409,24 @@ void Runtime::updateSubtitles() {
_isDisplayingSubtitles = false;
if (_subtitleQueue.size() == 0) {
+ // Queue was exhausted
+
// Is this really what we want to be doing?
- if (_escOn)
- clearTray();
- else
- redrawTray();
+ if (_escOn) {
+ if (_gameID == GID_AD2044)
+ clearSubtitleSection();
+ else
+ clearTray();
+ } else {
+ if (_gameID == GID_AD2044)
+ redrawSubtitleSection();
+ else
+ redrawTray();
+ }
}
} else
break;
} else {
- Graphics::ManagedSurface *surf = _traySection.surf.get();
-
Common::Array<Common::U32String> lines;
uint lineStart = 0;
@@ -4422,23 +4441,12 @@ void Runtime::updateSubtitles() {
lineStart = lineEnd + 1;
}
- clearTray();
-
- if (_subtitleFont) {
- int lineHeight = _subtitleFont->getFontHeight();
-
- int topY = (surf->h - lineHeight * static_cast<int>(lines.size())) / 2;
-
- uint32 textColor = surf->format.RGBToColor(queueItem.color[0], queueItem.color[1], queueItem.color[2]);
-
- for (uint lineIndex = 0; lineIndex < lines.size(); lineIndex++) {
- const Common::U32String &line = lines[lineIndex];
- int lineWidth = _subtitleFont->getStringWidth(line);
- _subtitleFont->drawString(surf, line, (surf->w - lineWidth) / 2, topY + static_cast<int>(lineIndex) * lineHeight, lineWidth, textColor);
- }
- }
+ if (_gameID == GID_AD2044)
+ clearSubtitleSection();
+ else
+ clearTray();
- commitSectionToScreen(_traySection, Common::Rect(0, 0, _traySection.rect.width(), _traySection.rect.height()));
+ drawSubtitleText(lines, queueItem.color);
_isDisplayingSubtitles = true;
}
@@ -5040,6 +5048,79 @@ void Runtime::clearTray() {
this->commitSectionToScreen(_traySection, trayRect);
}
+void Runtime::redrawSubtitleSection() {
+ if (_subtitleQueue.size() != 0)
+ return;
+
+ clearSubtitleSection();
+
+ if (!_tooltipText.empty()) {
+ Common::CodePage codePage = Common::kWindows1250;
+
+ Common::Array<Common::U32String> lines;
+
+ uint32 lastStringStart = 0;
+ for (;;) {
+ uint32 backslashPos = _tooltipText.find('\\', lastStringStart);
+ if (backslashPos == Common::String::npos)
+ break;
+
+ Common::String slice = _tooltipText.substr(lastStringStart, backslashPos - lastStringStart);
+ lines.push_back(slice.decode(codePage));
+ lastStringStart = backslashPos + 1;
+ }
+
+ Common::String lastSlice = _tooltipText.substr(lastStringStart, _tooltipText.size() - lastStringStart);
+ lines.push_back(lastSlice.decode(codePage));
+
+ uint8 color[3] = {255, 255, 0};
+ drawSubtitleText(lines, color);
+ }
+}
+
+void Runtime::clearSubtitleSection() {
+ Common::Rect stRect;
+ if (_gameID == GID_AD2044) {
+ stRect = _subtitleSection.rect;
+ stRect.translate(-stRect.left, -stRect.top);
+ _subtitleSection.surf->blitFrom(*_backgroundGraphic, _subtitleSection.rect, stRect);
+ }
+
+ this->commitSectionToScreen(_subtitleSection, stRect);
+}
+
+void Runtime::drawSubtitleText(const Common::Array<Common::U32String> &lines, const uint8 (&color)[3]) {
+ RenderSection &stSection = (_gameID == GID_AD2044) ? _subtitleSection : _traySection;
+ Graphics::ManagedSurface *surf = stSection.surf.get();
+
+ if (_subtitleFont) {
+ int lineHeight = _subtitleFont->getFontHeight();
+
+ int xOffset = 0;
+ int topY = 0;
+ if (_gameID == GID_AD2044) {
+ topY = 13;
+ xOffset = 5;
+ } else
+ topY = (surf->h - lineHeight * static_cast<int>(lines.size())) / 2;
+
+ uint32 textColor = surf->format.RGBToColor(color[0], color[1], color[2]);
+
+ for (uint lineIndex = 0; lineIndex < lines.size(); lineIndex++) {
+ const Common::U32String &line = lines[lineIndex];
+ int lineWidth = _subtitleFont->getStringWidth(line);
+
+ int xPos = (surf->w - lineWidth) / 2 + xOffset;
+ int yPos = topY + static_cast<int>(lineIndex) * lineHeight;
+
+ _subtitleFont->drawString(surf, line, xPos + 2, yPos + 2, lineWidth, 0);
+ _subtitleFont->drawString(surf, line, xPos, yPos, lineWidth, textColor);
+ }
+ }
+
+ commitSectionToScreen(stSection, Common::Rect(0, 0, stSection.rect.width(), stSection.rect.height()));
+}
+
void Runtime::drawInventory(uint slot) {
if (!isTrayVisible())
return;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index a1d53e9899a..916be46cea1 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -613,7 +613,7 @@ public:
Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, Common::Language defaultLanguage);
virtual ~Runtime();
- void initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt);
+ void initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &subtitleRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt);
void loadCursors(const char *exeName);
void setDebugMode(bool debugMode);
@@ -940,6 +940,9 @@ private:
void clearScreen();
void redrawTray();
void clearTray();
+ void redrawSubtitleSection();
+ void clearSubtitleSection();
+ void drawSubtitleText(const Common::Array<Common::U32String> &lines, const uint8 (&color)[3]);
void drawInventory(uint slot);
void drawCompass();
bool isTrayVisible() const;
@@ -1354,6 +1357,7 @@ private:
RenderSection _menuSection;
RenderSection _traySection;
RenderSection _fullscreenMenuSection;
+ RenderSection _subtitleSection;
Common::Point _mousePos;
Common::Point _lmbDownPos;
@@ -1440,6 +1444,9 @@ private:
uint32 _cursorCycleLength;
int32 _dbToVolume[49];
+
+ // AD2044 tooltips
+ Common::String _tooltipText;
};
} // End of namespace VCruise
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 3cbd969b53e..3b44b69db8f 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2119,7 +2119,12 @@ void Runtime::scriptOpEM(ScriptArg_t arg) {
OPCODE_STUB(SE)
OPCODE_STUB(SDot)
-OPCODE_STUB(E)
+
+void Runtime::scriptOpE(ScriptArg_t arg) {
+ _tooltipText = _scriptSet->strings[arg];
+ redrawSubtitleSection();
+}
+
OPCODE_STUB(Dot)
OPCODE_STUB(Sound)
OPCODE_STUB(ISound)
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 3967182491e..7565d3219f5 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -148,6 +148,7 @@ Common::Error VCruiseEngine::run() {
_menuBarRect = Common::Rect(size.x - menuBarSize.x, 0, size.x, menuBarSize.y);
_videoRect = Common::Rect(videoTL, videoTL + videoSize);
_trayRect = Common::Rect(0, size.y - traySize.y, size.x, size.y);
+ _subtitleRect = Common::Rect(_videoRect.left, _videoRect.bottom, _videoRect.right, _trayRect.top);
} else {
Common::Point videoSize;
Common::Point traySize;
@@ -194,7 +195,7 @@ Common::Error VCruiseEngine::run() {
_system->fillScreen(0);
_runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID, _gameDescription->defaultLanguage));
- _runtime->initSections(_videoRect, _menuBarRect, _trayRect, Common::Rect(640, 480), _system->getScreenFormat());
+ _runtime->initSections(_videoRect, _menuBarRect, _trayRect, _subtitleRect, Common::Rect(640, 480), _system->getScreenFormat());
const char *exeName = _gameDescription->desc.filesDescriptions[0].fileName;
diff --git a/engines/vcruise/vcruise.h b/engines/vcruise/vcruise.h
index 920037171e2..4dee4b73aba 100644
--- a/engines/vcruise/vcruise.h
+++ b/engines/vcruise/vcruise.h
@@ -76,6 +76,7 @@ private:
Common::Rect _videoRect;
Common::Rect _menuBarRect;
Common::Rect _trayRect;
+ Common::Rect _subtitleRect;
Common::FSNode _rootFSNode;
Commit: 3729728a83ac5d83bf68251642f4f0b49ffead80
https://github.com/scummvm/scummvm/commit/3729728a83ac5d83bf68251642f4f0b49ffead80
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Add missing opcode dispatches
Changed paths:
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 3b44b69db8f..34a6f81cb63 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2373,6 +2373,10 @@ bool Runtime::runScript() {
DISPATCH_OP(NoUpdate);
DISPATCH_OP(NoClear);
+ DISPATCH_OP(Say1_AD2044);
+ DISPATCH_OP(Say2_AD2044);
+ DISPATCH_OP(Say1Rnd);
+
DISPATCH_OP(M);
DISPATCH_OP(EM);
DISPATCH_OP(SE);
Commit: 073a3d35d1c068b8476197ba916e77259df0aae3
https://github.com/scummvm/scummvm/commit/073a3d35d1c068b8476197ba916e77259df0aae3
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Fix wrong forward cursor ID
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index ba545b739db..7b7aaf9b860 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1384,7 +1384,7 @@ void Runtime::loadCursors(const char *exeName) {
}
if (_gameID == GID_AD2044) {
- _namedCursors["CUR_PRZOD"] = 2; // Przod = forward
+ _namedCursors["CUR_PRZOD"] = 4; // Przod = forward
_namedCursors["CUR_PRAWO"] = 3; // Prawo = right
_namedCursors["CUR_LEWO"] = 1; // Lewo = left
_namedCursors["CUR_LUPA"] = 6; // Lupa = magnifier
Commit: e907bb11536f2925d00a00a3f534c97490ff1696
https://github.com/scummvm/scummvm/commit/e907bb11536f2925d00a00a3f534c97490ff1696
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Add say1rnd opcode
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 7b7aaf9b860..2d6bd5fe7ed 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -940,7 +940,7 @@ void SaveGameSwappableState::Sound::read(Common::ReadStream *stream, uint saveGa
params3D.read(stream);
}
-SaveGameSwappableState::SaveGameSwappableState() : roomNumber(0), screenNumber(0), direction(0), havePendingPostSwapScreenReset(false),
+SaveGameSwappableState::SaveGameSwappableState() : roomNumber(0), screenNumber(0), direction(0), disc(0), havePendingPostSwapScreenReset(false),
musicTrack(0), musicVolume(100), musicActive(true), musicMuteDisabled(false), animVolume(100),
loadedAnimation(0), animDisplayingFrame(0) {
}
@@ -959,6 +959,7 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeUint32BE(states[sti]->roomNumber);
stream->writeUint32BE(states[sti]->screenNumber);
stream->writeUint32BE(states[sti]->direction);
+ stream->writeUint32BE(states[sti]->disc);
stream->writeByte(states[sti]->havePendingPostSwapScreenReset ? 1 : 0);
}
@@ -1067,6 +1068,9 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
states[sti]->screenNumber = stream->readUint32BE();
states[sti]->direction = stream->readUint32BE();
+ if (saveVersion >= 10)
+ states[sti]->disc = stream->readUint32BE();
+
if (saveVersion >= 7)
states[sti]->havePendingPostSwapScreenReset = (stream->readByte() != 0);
}
@@ -1218,7 +1222,7 @@ FontCacheItem::FontCacheItem() : font(nullptr), size(0) {
}
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, Common::Language defaultLanguage)
- : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _swapOutRoom(0), _swapOutScreen(0), _swapOutDirection(0),
+ : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _disc(0), _swapOutRoom(0), _swapOutScreen(0), _swapOutDirection(0),
_haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingPreIdleActions(false), _havePendingReturnToIdleState(false), _havePendingPostSwapScreenReset(false),
_havePendingCompletionCheck(false), _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _lowQualityGraphicsMode(false),
@@ -1388,6 +1392,8 @@ void Runtime::loadCursors(const char *exeName) {
_namedCursors["CUR_PRAWO"] = 3; // Prawo = right
_namedCursors["CUR_LEWO"] = 1; // Lewo = left
_namedCursors["CUR_LUPA"] = 6; // Lupa = magnifier
+ _namedCursors["CUR_NAC"] = 5; // Nac = top? Not sure. But this is the finger pointer.
+ _namedCursors["CUR_TYL"] = 2; // Tyl = back
}
_panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
@@ -6155,6 +6161,7 @@ void Runtime::restoreSaveGameSnapshot() {
_roomNumber = mainState->roomNumber;
_screenNumber = mainState->screenNumber;
_direction = mainState->direction;
+ _disc = mainState->disc;
_havePendingPostSwapScreenReset = mainState->havePendingPostSwapScreenReset;
_hero = snapshot->hero;
_swapOutRoom = snapshot->swapOutRoom;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 916be46cea1..60a4ff2cc23 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -451,6 +451,7 @@ struct SaveGameSwappableState {
uint roomNumber;
uint screenNumber;
uint direction;
+ uint disc;
bool havePendingPostSwapScreenReset;
uint loadedAnimation;
@@ -478,7 +479,7 @@ struct SaveGameSnapshot {
LoadGameOutcome read(Common::ReadStream *stream);
static const uint kSaveGameIdentifier = 0x53566372;
- static const uint kSaveGameCurrentVersion = 9;
+ static const uint kSaveGameCurrentVersion = 10;
static const uint kSaveGameEarliestSupportedVersion = 2;
static const uint kMaxStates = 2;
@@ -1204,6 +1205,7 @@ private:
uint _screenNumber;
uint _direction;
uint _hero;
+ uint _disc;
uint _swapOutRoom;
uint _swapOutScreen;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 34a6f81cb63..1a9a01551f6 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2103,9 +2103,32 @@ void Runtime::scriptOpNoUpdate(ScriptArg_t arg) {
OPCODE_STUB(NoClear)
-OPCODE_STUB(Say1_AD2044)
+void Runtime::scriptOpSay1_AD2044(ScriptArg_t arg) {
+ TAKE_STACK_INT(1);
+
+ Common::String soundName = Common::String::format("%02i-%08i", static_cast<int>(_disc * 10u + 1u), static_cast<int>(stackArgs[0]));
+
+ StackInt_t soundID = 0;
+ SoundInstance *cachedSound = nullptr;
+ resolveSoundByName(soundName, true, soundID, cachedSound);
+
+ if (cachedSound) {
+ TriggeredOneShot oneShot;
+ oneShot.soundID = soundID;
+ oneShot.uniqueSlot = _disc;
+
+ if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
+ triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
+ _triggeredOneShots.push_back(oneShot);
+ }
+ }
+}
+
+void Runtime::scriptOpSay1Rnd(ScriptArg_t arg) {
+ scriptOpSay1_AD2044(arg);
+}
+
OPCODE_STUB(Say2_AD2044)
-OPCODE_STUB(Say1Rnd)
void Runtime::scriptOpM(ScriptArg_t arg) {
// Looks like this is possibly support to present a mouse click prompt and end
@@ -2383,6 +2406,9 @@ bool Runtime::runScript() {
DISPATCH_OP(SDot);
DISPATCH_OP(E);
+ DISPATCH_OP(Sound);
+ DISPATCH_OP(ISound);
+
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
}
Commit: e33a407f56843e883ec6a0fbd8eef9a570f56438
https://github.com/scummvm/scummvm/commit/e33a407f56843e883ec6a0fbd8eef9a570f56438
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:28-04:00
Commit Message:
VCRUISE: Stub sound ops
Changed paths:
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 1a9a01551f6..937d0be55a8 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2128,7 +2128,9 @@ void Runtime::scriptOpSay1Rnd(ScriptArg_t arg) {
scriptOpSay1_AD2044(arg);
}
-OPCODE_STUB(Say2_AD2044)
+void Runtime::scriptOpSay2_AD2044(ScriptArg_t arg) {
+ scriptOpSay1_AD2044(arg);
+}
void Runtime::scriptOpM(ScriptArg_t arg) {
// Looks like this is possibly support to present a mouse click prompt and end
@@ -2149,8 +2151,15 @@ void Runtime::scriptOpE(ScriptArg_t arg) {
}
OPCODE_STUB(Dot)
-OPCODE_STUB(Sound)
-OPCODE_STUB(ISound)
+
+void Runtime::scriptOpSound(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+}
+
+void Runtime::scriptOpISound(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+}
+
OPCODE_STUB(USound)
OPCODE_STUB(RGet)
Commit: e7c47fb50082523b402b914b36645a4c8c490c99
https://github.com/scummvm/scummvm/commit/e7c47fb50082523b402b914b36645a4c8c490c99
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Fix sound IDs, open cursor, and some screen overrides
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 2d6bd5fe7ed..721beaf3728 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -331,6 +331,12 @@ public:
void unload() override;
private:
+ struct ScreenOverride {
+ uint roomNumber;
+ uint screenNumber;
+ int actualMapFileID;
+ };
+
void load();
static const uint kFirstScreen = 0xa0;
@@ -340,6 +346,15 @@ private:
bool _isLoaded;
Common::SharedPtr<MapScreenDirectionDef> _currentMap;
+
+ static const ScreenOverride sk_screenOverrides[];
+};
+
+const AD2044MapLoader::ScreenOverride AD2044MapLoader::sk_screenOverrides[] = {
+ // Room 1
+ {1, 0xb6, 145}, // After pushing the button to open the capsule
+ {1, 0x6a, 142}, // Opening an apple on the table
+ {1, 0x6b, 143}, // Clicking the tablet in the apple
};
AD2044MapLoader::AD2044MapLoader() : _roomNumber(0), _screenNumber(0), _isLoaded(false) {
@@ -368,19 +383,35 @@ void AD2044MapLoader::load() {
// This is loaded even if the open fails
_isLoaded = true;
- if (_screenNumber < kFirstScreen)
- return;
+ int scrFileID = -1;
- uint adjustedScreenNumber = _screenNumber - kFirstScreen;
+ for (const ScreenOverride &screenOverride : sk_screenOverrides) {
+ if (screenOverride.roomNumber == _roomNumber && screenOverride.screenNumber == _screenNumber) {
+ scrFileID = screenOverride.actualMapFileID;
+ break;
+ }
+ }
- if (adjustedScreenNumber > 99)
- return;
+ if (scrFileID < 0) {
+ if (_screenNumber < kFirstScreen)
+ return;
+
+ uint adjustedScreenNumber = _screenNumber - kFirstScreen;
- Common::Path mapFileName(Common::String::format("map/SCR%i.MAP", static_cast<int>(_roomNumber * 100u + adjustedScreenNumber)));
+ if (adjustedScreenNumber > 99)
+ return;
+
+ scrFileID = static_cast<int>(_roomNumber * 100u + adjustedScreenNumber);
+ }
+
+ Common::Path mapFileName(Common::String::format("map/SCR%i.MAP", scrFileID));
Common::File mapFile;
- if (!mapFile.open(mapFileName))
- return;
+ debug(1, "Loading screen map %s", mapFileName.toString(Common::Path::kNativeSeparator).c_str());
+
+ if (!mapFile.open(mapFileName)) {
+ error("Couldn't resolve map file for room %u screen %u", _roomNumber, _screenNumber);
+ }
_currentMap = loadScreenDirectionDef(mapFile);
}
@@ -1394,6 +1425,7 @@ void Runtime::loadCursors(const char *exeName) {
_namedCursors["CUR_LUPA"] = 6; // Lupa = magnifier
_namedCursors["CUR_NAC"] = 5; // Nac = top? Not sure. But this is the finger pointer.
_namedCursors["CUR_TYL"] = 2; // Tyl = back
+ _namedCursors["CUR_OTWORZ"] = 11; // Otworz = open
}
_panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
@@ -3157,8 +3189,16 @@ void Runtime::resolveSoundByName(const Common::String &soundName, bool load, Sta
Common::String sndName = soundName;
uint soundID = 0;
- for (uint i = 0; i < 4; i++)
- soundID = soundID * 10u + (sndName[i] - '0');
+
+ if (_gameID == GID_AD2044) {
+ for (uint i = 0; i < 2; i++)
+ soundID = soundID * 10u + (sndName[i] - '0');
+ for (uint i = 0; i < 5; i++)
+ soundID = soundID * 10u + (sndName[6 + i] - '0');
+ } else {
+ for (uint i = 0; i < 4; i++)
+ soundID = soundID * 10u + (sndName[i] - '0');
+ }
sndName.toLowercase();
Commit: b313b4068f9c369e04c55632b92e31bf9e0a15d5
https://github.com/scummvm/scummvm/commit/b313b4068f9c369e04c55632b92e31bf9e0a15d5
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: AD2044 inventory support
Changed paths:
engines/vcruise/metaengine.cpp
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/metaengine.cpp b/engines/vcruise/metaengine.cpp
index 73a4e1a794d..575f0f1b405 100644
--- a/engines/vcruise/metaengine.cpp
+++ b/engines/vcruise/metaengine.cpp
@@ -197,6 +197,10 @@ Common::Array<Common::Keymap *> VCruiseMetaEngine::initKeymaps(const char *targe
act->setCustomEngineActionEvent(VCruise::kKeymappedEventSkipAnimation);
keymap->addAction(act);
+ act = new Common::Action("VCRUISE_PUT_ITEM", _("Cycle item in scene (debug cheat)"));
+ act->setCustomEngineActionEvent(VCruise::kKeymappedEventPutItem);
+ keymap->addAction(act);
+
return Common::Keymap::arrayOf(keymap);
}
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 721beaf3728..829095fdd05 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -64,6 +64,17 @@
namespace VCruise {
+struct InitialItemPlacement {
+ uint roomNumber;
+ uint screenNumber;
+ uint itemID;
+};
+
+const InitialItemPlacement g_ad2044InitialItemPlacements[] = {
+ {1, 0xb8, 24}, // Cigarette pack
+ {1, 0xac, 27}, // Matches
+};
+
struct CodePageGuess {
Common::CodePage codePage;
Runtime::CharSet charSet;
@@ -431,8 +442,13 @@ OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyC
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);
+ pixFmt = fmt;
+ if (paramRect.isEmpty())
+ surf.reset();
+ else {
+ surf.reset(new Graphics::ManagedSurface(paramRect.width(), paramRect.height(), fmt));
+ surf->fillRect(Common::Rect(0, 0, surf->w, surf->h), 0xffffffff);
+ }
}
Runtime::StackValue::ValueUnion::ValueUnion() {
@@ -976,8 +992,36 @@ SaveGameSwappableState::SaveGameSwappableState() : roomNumber(0), screenNumber(0
loadedAnimation(0), animDisplayingFrame(0) {
}
+SaveGameSnapshot::PagedInventoryItem::PagedInventoryItem() : page(0), slot(0), itemID(0) {
+}
+
+void SaveGameSnapshot::PagedInventoryItem::write(Common::WriteStream *stream) const {
+ stream->writeByte(page);
+ stream->writeByte(slot);
+ stream->writeByte(itemID);
+}
+
+void SaveGameSnapshot::PagedInventoryItem::read(Common::ReadStream *stream, uint saveGameVersion) {
+ page = stream->readByte();
+ slot = stream->readByte();
+ itemID = stream->readByte();
+}
+
+SaveGameSnapshot::PlacedInventoryItem::PlacedInventoryItem() : locationID(0), itemID(0) {
+}
+
+void SaveGameSnapshot::PlacedInventoryItem::write(Common::WriteStream *stream) const {
+ stream->writeUint32BE(locationID);
+ stream->writeByte(itemID);
+}
+
+void SaveGameSnapshot::PlacedInventoryItem::read(Common::ReadStream *stream, uint saveGameVersion) {
+ locationID = stream->readUint32BE();
+ itemID = stream->readByte();
+}
+
SaveGameSnapshot::SaveGameSnapshot() : hero(0), swapOutRoom(0), swapOutScreen(0), swapOutDirection(0),
- escOn(false), numStates(1), listenerX(0), listenerY(0), listenerAngle(0) {
+ escOn(false), numStates(1), listenerX(0), listenerY(0), listenerAngle(0), inventoryPage(0), inventoryActiveItem(0) {
}
void SaveGameSnapshot::write(Common::WriteStream *stream) const {
@@ -1035,6 +1079,10 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeUint32BE(variables.size());
stream->writeUint32BE(timers.size());
+ stream->writeUint32BE(placedItems.size());
+ stream->writeUint32BE(pagedItems.size());
+ stream->writeByte(inventoryPage);
+ stream->writeByte(inventoryActiveItem);
for (uint sti = 0; sti < numStates; sti++) {
for (const SaveGameSwappableState::InventoryItem &invItem : states[sti]->inventory)
@@ -1066,6 +1114,12 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeUint32BE(timer._key);
stream->writeUint32BE(timer._value);
}
+
+ for (const PlacedInventoryItem &item : placedItems)
+ item.write(stream);
+
+ for (const PagedInventoryItem &item : pagedItems)
+ item.write(stream);
}
LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
@@ -1180,6 +1234,19 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
uint numVars = stream->readUint32BE();
uint numTimers = stream->readUint32BE();
+ uint numPlacedItems = 0;
+ uint numPagedItems = 0;
+
+ if (saveVersion >= 10) {
+ numPlacedItems = stream->readUint32BE();
+ numPagedItems = stream->readUint32BE();
+ this->inventoryPage = stream->readByte();
+ this->inventoryActiveItem = stream->readByte();
+ } else {
+ this->inventoryPage = 0;
+ this->inventoryActiveItem = 0;
+ }
+
if (stream->eos() || stream->err())
return kLoadGameOutcomeSaveDataCorrupted;
@@ -1191,7 +1258,6 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
triggeredOneShots.resize(numOneShots);
-
for (uint sti = 0; sti < numStates; sti++) {
for (uint i = 0; i < numInventory[sti]; i++)
states[sti]->inventory[i].read(stream);
@@ -1229,6 +1295,18 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
timers[key] = value;
}
+ for (uint i = 0; i < numPlacedItems; i++) {
+ PlacedInventoryItem item;
+ item.read(stream, saveVersion);
+ placedItems.push_back(item);
+ }
+
+ for (uint i = 0; i < numPagedItems; i++) {
+ PagedInventoryItem item;
+ item.read(stream, saveVersion);
+ pagedItems.push_back(item);
+ }
+
if (stream->eos() || stream->err())
return kLoadGameOutcomeSaveDataCorrupted;
@@ -1274,7 +1352,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_isInGame(false),
_subtitleFont(nullptr), _isDisplayingSubtitles(false), _isSubtitleSourceAnimation(false),
_languageIndex(0), _defaultLanguageIndex(0), _defaultLanguage(defaultLanguage), _charSet(kCharSetLatin),
- _isCDVariant(false), _currentAnimatedCursor(nullptr), _currentCursor(nullptr), _cursorTimeBase(0), _cursorCycleLength(0) {
+ _isCDVariant(false), _currentAnimatedCursor(nullptr), _currentCursor(nullptr), _cursorTimeBase(0), _cursorCycleLength(0),
+ _inventoryActivePage(0) {
for (uint i = 0; i < kNumDirections; i++) {
_haveIdleAnimations[i] = false;
@@ -1321,6 +1400,8 @@ void Runtime::initSections(const Common::Rect &gameRect, const Common::Rect &men
if (!subtitleRect.isEmpty())
_subtitleSection.init(subtitleRect, pixFmt);
+
+ _placedItemBackBufferSection.init(Common::Rect(), pixFmt);
}
void Runtime::loadCursors(const char *exeName) {
@@ -1588,10 +1669,10 @@ bool Runtime::bootGame(bool newGame) {
error("Don't have a start config for this game");
if (_gameID != GID_AD2044) {
- _trayBackgroundGraphic = loadGraphic("Pocket", true);
- _trayHighlightGraphic = loadGraphic("Select", true);
- _trayCompassGraphic = loadGraphic("Select_1", true);
- _trayCornerGraphic = loadGraphic("Select_2", true);
+ _trayBackgroundGraphic = loadGraphic("Pocket", "", true);
+ _trayHighlightGraphic = loadGraphic("Select", "", true);
+ _trayCompassGraphic = loadGraphic("Select_1", "", true);
+ _trayCornerGraphic = loadGraphic("Select_2", "", true);
}
if (_gameID == GID_AD2044)
@@ -1729,11 +1810,11 @@ bool Runtime::bootGame(bool newGame) {
_uiGraphics.resize(24);
for (uint i = 0; i < _uiGraphics.size(); i++) {
if (_gameID == GID_REAH) {
- _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(_languageIndex * 100u + i)), false);
+ _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(_languageIndex * 100u + i)), "", false);
if (_languageIndex != 0 && !_uiGraphics[i])
- _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(i)), false);
+ _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(i)), "", false);
} else if (_gameID == GID_SCHIZM) {
- _uiGraphics[i] = loadGraphic(Common::String::format("Data%03u", i), false);
+ _uiGraphics[i] = loadGraphic(Common::String::format("Data%03u", i), "", false);
}
}
}
@@ -2003,6 +2084,9 @@ bool Runtime::runIdle() {
case kKeymappedEventQuit:
changeToMenuPage(createMenuQuit(_gameID == GID_SCHIZM));
return true;
+ case kKeymappedEventPutItem:
+ cheatPutItem();
+ return true;
default:
break;
}
@@ -3343,6 +3427,33 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
_forceAllowSaves = false;
recordSaveGameSnapshot();
+
+ _placedItemRect = Common::Rect();
+ if (_gameID == GID_AD2044) {
+ const MapScreenDirectionDef *screenDef = _mapLoader->getScreenDirection(_screenNumber, _direction);
+ if (screenDef) {
+ for (const InteractionDef &interaction : screenDef->interactions) {
+ if (interaction.objectType == 2) {
+ _placedItemRect = interaction.rect;
+ break;
+ }
+ }
+ }
+
+ _placedItemRect = _placedItemRect.findIntersectingRect(Common::Rect(640, 480));
+
+ Common::Rect renderRect = _placedItemRect;
+ renderRect.translate(_gameSection.rect.left, _gameSection.rect.top);
+
+ _placedItemBackBufferSection.init(renderRect, _placedItemBackBufferSection.pixFmt);
+
+ if (!_placedItemRect.isEmpty())
+ _placedItemBackBufferSection.surf->blitFrom(*_gameSection.surf, _placedItemRect, Common::Point(0, 0));
+
+ updatePlacedItemCache();
+
+ drawPlacedItemGraphic();
+ }
}
}
@@ -5036,10 +5147,12 @@ void Runtime::inventoryAddItem(uint item) {
if (firstOpenSlot == kNumInventorySlots)
error("Tried to add an inventory item but ran out of slots");
- Common::String itemFileName = getFileNameForItemGraphic(item);
+ Common::String itemFileName;
+ Common::String alphaFileName;
+ getFileNamesForItemGraphic(item, itemFileName, alphaFileName);
_inventory[firstOpenSlot].itemID = item;
- _inventory[firstOpenSlot].graphic = loadGraphic(itemFileName, false);
+ _inventory[firstOpenSlot].graphic = loadGraphic(itemFileName, alphaFileName, false);
drawInventory(firstOpenSlot);
}
@@ -5324,23 +5437,120 @@ void Runtime::resetInventoryHighlights() {
}
}
-Common::String Runtime::getFileNameForItemGraphic(uint itemID) const {
+void Runtime::loadInventoryFromPage() {
+ for (uint slot = 0; slot < kNumInventorySlots; slot++)
+ _inventory[slot] = _inventoryPages[_inventoryActivePage][slot];
+}
+
+void Runtime::copyInventoryToPage() {
+ for (uint slot = 0; slot < kNumInventorySlots; slot++)
+ _inventoryPages[_inventoryActivePage][slot] = _inventory[slot];
+}
+
+void Runtime::cheatPutItem() {
+ uint32 location = getLocationForScreen(_roomNumber, _screenNumber);
+
+ uint8 &pid = _placedItems[location];
+ pid++;
+
+ if (pid == 30 || pid == 45 || pid == 49 || pid == 59)
+ pid++;
+ else if (pid == 62)
+ pid += 2;
+ else if (pid == 74)
+ pid = 1;
+
+ updatePlacedItemCache();
+
+ clearPlacedItemGraphic();
+ drawPlacedItemGraphic();
+}
+
+uint32 Runtime::getLocationForScreen(uint roomNumber, uint screenNumber) {
+ return roomNumber * 10000u + screenNumber;
+}
+
+void Runtime::updatePlacedItemCache() {
+ uint32 placedItemLocationID = getLocationForScreen(_roomNumber, _screenNumber);
+ Common::HashMap<uint32, uint8>::const_iterator placedItemIt = _placedItems.find(placedItemLocationID);
+ if (placedItemIt != _placedItems.end()) {
+ uint8 itemID = placedItemIt->_value;
+
+ if (_inventoryPlacedItemCache.itemID != itemID) {
+ Common::String itemFileName;
+ Common::String alphaFileName;
+
+ _inventoryPlacedItemCache.itemID = itemID;
+ getFileNamesForItemGraphic(itemID, itemFileName, alphaFileName);
+ _inventoryPlacedItemCache.graphic = loadGraphic(itemFileName, alphaFileName, false);
+ }
+ } else {
+ _inventoryPlacedItemCache = InventoryItem();
+ }
+}
+
+void Runtime::drawPlacedItemGraphic() {
+ const Graphics::Surface *surf = _inventoryPlacedItemCache.graphic.get();
+ if (surf) {
+ Common::Point drawPos((_placedItemRect.left + _placedItemRect.right - surf->w) / 2, (_placedItemRect.top + _placedItemRect.bottom - surf->h) / 2);
+
+ _gameSection.surf->blitFrom(*surf, drawPos);
+ drawSectionToScreen(_gameSection, _placedItemRect);
+ }
+}
+
+void Runtime::clearPlacedItemGraphic() {
+ if (!_placedItemRect.isEmpty()) {
+ _gameSection.surf->blitFrom(*_placedItemBackBufferSection.surf, Common::Point(_placedItemRect.left, _placedItemRect.top));
+ drawSectionToScreen(_gameSection, _placedItemRect);
+ }
+}
+
+void Runtime::getFileNamesForItemGraphic(uint itemID, Common::String &outFileName, Common::String &outAlphaFileName) const {
if (_gameID == GID_REAH)
- return Common::String::format("Thing%u", itemID);
+ outFileName = Common::String::format("Thing%u", itemID);
else if (_gameID == GID_SCHIZM)
- return Common::String::format("Item%u", itemID);
- else {
+ outFileName = Common::String::format("Item%u", itemID);
+ else if (_gameID == GID_AD2044) {
+ outFileName = Common::String::format(_lowQualityGraphicsMode ? "RZB%u" : "RZE%u", itemID);
+ outAlphaFileName = Common::String::format("MAS%u", itemID);
+ } else
error("Unknown game, can't format inventory item");
- return "";
- }
}
-Common::SharedPtr<Graphics::Surface> Runtime::loadGraphic(const Common::String &graphicName, bool required) {
- Common::Path filePath("Gfx/");
+Common::SharedPtr<Graphics::Surface> Runtime::loadGraphic(const Common::String &graphicName, const Common::String &alphaName, bool required) {
+ Common::Path filePath((_gameID == GID_AD2044) ? "rze/" : "Gfx/");
+
filePath.appendInPlace(graphicName);
- filePath.appendInPlace(".bmp");
+ filePath.appendInPlace((_gameID == GID_AD2044) ? ".BMP" : ".bmp");
+
+ Common::SharedPtr<Graphics::Surface> surf = loadGraphicFromPath(filePath, required);
+
+ if (surf && !alphaName.empty()) {
+ Common::SharedPtr<Graphics::Surface> alphaSurf = loadGraphic(alphaName, "", required);
+ if (alphaSurf) {
+ if (surf->w != alphaSurf->w || surf->h != alphaSurf->h)
+ error("Mismatched graphic sizes");
+
+ int h = surf->h;
+ int w = surf->w;
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ uint32 alphaSurfPixel = alphaSurf->getPixel(x, y);
+
+ uint8 r = 0;
+ uint8 g = 0;
+ uint8 b = 0;
+ uint8 a = 0;
+ alphaSurf->format.colorToARGB(alphaSurfPixel, a, r, g, b);
+ if (r < 128)
+ surf->setPixel(x, y, 0);
+ }
+ }
+ }
+ }
- return loadGraphicFromPath(filePath, required);
+ return surf;
}
Common::SharedPtr<Graphics::Surface> Runtime::loadGraphicFromPath(const Common::Path &filePath, bool required) {
@@ -5362,7 +5572,8 @@ Common::SharedPtr<Graphics::Surface> Runtime::loadGraphicFromPath(const Common::
Common::SharedPtr<Graphics::Surface> surf(new Graphics::Surface(), Graphics::SurfaceDeleter());
surf->copyFrom(*bmpDecoder.getSurface());
- surf = Common::SharedPtr<Graphics::Surface>(surf->convertTo(Graphics::createPixelFormat<8888>()), Graphics::SurfaceDeleter());
+ surf = Common::SharedPtr<Graphics::Surface>(surf->convertTo(Graphics::createPixelFormat<8888>(), bmpDecoder.getPalette(), bmpDecoder.getPaletteColorCount()), Graphics::SurfaceDeleter());
+
return surf;
}
@@ -6078,19 +6289,23 @@ void Runtime::recordSaveGameSnapshot() {
snapshot->states[0].reset(new SaveGameSwappableState());
if (_gameID == GID_REAH)
snapshot->numStates = 1;
- else if (_gameID == GID_SCHIZM) {
+ else if (_gameID == GID_SCHIZM || _gameID == GID_AD2044) {
snapshot->numStates = 2;
snapshot->states[1] = _altState;
}
SaveGameSwappableState *mainState = snapshot->states[0].get();
- for (const InventoryItem &inventoryItem : _inventory) {
- SaveGameSwappableState::InventoryItem saveItem;
- saveItem.itemID = inventoryItem.itemID;
- saveItem.highlighted = inventoryItem.highlighted;
+ if (_gameID == GID_AD2044) {
+ copyInventoryToPage();
+ } else {
+ for (const InventoryItem &inventoryItem : _inventory) {
+ SaveGameSwappableState::InventoryItem saveItem;
+ saveItem.itemID = inventoryItem.itemID;
+ saveItem.highlighted = inventoryItem.highlighted;
- mainState->inventory.push_back(saveItem);
+ mainState->inventory.push_back(saveItem);
+ }
}
mainState->roomNumber = _roomNumber;
@@ -6098,6 +6313,8 @@ void Runtime::recordSaveGameSnapshot() {
mainState->direction = _direction;
mainState->havePendingPostSwapScreenReset = false;
snapshot->hero = _hero;
+ snapshot->inventoryPage = _inventoryActivePage;
+ snapshot->inventoryActiveItem = _inventoryActiveItem.itemID;
snapshot->pendingStaticAnimParams = _pendingStaticAnimParams;
@@ -6134,6 +6351,28 @@ void Runtime::recordSaveGameSnapshot() {
snapshot->listenerX = _listenerX;
snapshot->listenerY = _listenerY;
snapshot->listenerAngle = _listenerAngle;
+
+ for (const Common::HashMap<uint32, uint8>::Node &placedItem : _placedItems) {
+ SaveGameSnapshot::PlacedInventoryItem saveItem;
+ saveItem.locationID = placedItem._key;
+ saveItem.itemID = placedItem._value;
+
+ snapshot->placedItems.push_back(saveItem);
+ }
+
+ for (uint page = 0; page < kNumInventoryPages; page++) {
+ for (uint slot = 0; slot < kNumInventorySlots; slot++) {
+ uint itemID = _inventoryPages[page][slot].itemID;
+ if (itemID != 0) {
+ SaveGameSnapshot::PagedInventoryItem pagedItem;
+ pagedItem.page = page;
+ pagedItem.slot = slot;
+ pagedItem.itemID = itemID;
+
+ snapshot->pagedItems.push_back(pagedItem);
+ }
+ }
+ }
}
void Runtime::recordSounds(SaveGameSwappableState &state) {
@@ -6184,17 +6423,72 @@ void Runtime::restoreSaveGameSnapshot() {
SaveGameSwappableState *mainState = snapshot->states[0].get();
- for (uint i = 0; i < kNumInventorySlots && i < mainState->inventory.size(); i++) {
- const SaveGameSwappableState::InventoryItem &saveItem = mainState->inventory[i];
+ if (_gameID == GID_AD2044) {
+ for (uint page = 0; page < kNumInventoryPages; page++)
+ for (uint slot = 0; slot < kNumInventorySlots; slot++)
+ _inventoryPages[page][slot].itemID = 0;
+
+ for (const SaveGameSnapshot::PagedInventoryItem &pagedItem : snapshot->pagedItems) {
+ if (pagedItem.page >= kNumInventoryPages || pagedItem.slot >= kNumInventorySlots)
+ error("Invalid item slot in save game snapshot");
+
+ InventoryItem &invItem = _inventoryPages[pagedItem.page][pagedItem.slot];
+ invItem.itemID = pagedItem.itemID;
+
+ if (invItem.itemID) {
+ Common::String itemFileName;
+ Common::String alphaFileName;
+ getFileNamesForItemGraphic(invItem.itemID, itemFileName, alphaFileName);
+ invItem.graphic = loadGraphic(itemFileName, alphaFileName, false);
+ }
+ }
- _inventory[i].itemID = saveItem.itemID;
- _inventory[i].highlighted = saveItem.highlighted;
+ for (uint page = 0; page < kNumInventoryPages; page++) {
+ for (uint slot = 0; slot < kNumInventorySlots; slot++) {
+ InventoryItem &invItem = _inventoryPages[page][slot];
+ if (!invItem.itemID)
+ invItem.graphic.reset();
+ }
+ }
- if (saveItem.itemID) {
- Common::String itemFileName = getFileNameForItemGraphic(saveItem.itemID);
- _inventory[i].graphic = loadGraphic(itemFileName, false);
+ _inventoryActiveItem.itemID = snapshot->inventoryActiveItem;
+ if (_inventoryActiveItem.itemID) {
+ Common::String itemFileName;
+ Common::String alphaFileName;
+ getFileNamesForItemGraphic(_inventoryActiveItem.itemID, itemFileName, alphaFileName);
+ _inventoryActiveItem.graphic = loadGraphic(itemFileName, alphaFileName, false);
} else {
- _inventory[i].graphic.reset();
+ _inventoryActiveItem.graphic.reset();
+ }
+
+ _inventoryPlacedItemCache = InventoryItem();
+
+ if (snapshot->inventoryPage >= kNumInventoryPages)
+ error("Invalid inventory page");
+
+ _inventoryActivePage = snapshot->inventoryPage;
+
+ loadInventoryFromPage();
+
+ _placedItems.clear();
+ for (const SaveGameSnapshot::PlacedInventoryItem &placedItem : snapshot->placedItems) {
+ _placedItems[placedItem.locationID] = placedItem.itemID;
+ }
+ } else {
+ for (uint i = 0; i < kNumInventorySlots && i < mainState->inventory.size(); i++) {
+ const SaveGameSwappableState::InventoryItem &saveItem = mainState->inventory[i];
+
+ _inventory[i].itemID = saveItem.itemID;
+ _inventory[i].highlighted = saveItem.highlighted;
+
+ if (saveItem.itemID) {
+ Common::String itemFileName;
+ Common::String alphaFileName;
+ getFileNamesForItemGraphic(saveItem.itemID, itemFileName, alphaFileName);
+ _inventory[i].graphic = loadGraphic(itemFileName, alphaFileName, false);
+ } else {
+ _inventory[i].graphic.reset();
+ }
}
}
@@ -6348,9 +6642,25 @@ Common::SharedPtr<SaveGameSnapshot> Runtime::generateNewGameSnapshot() const {
// AD2044 new game normally loads a pre-packaged save. Unlike Reah and Schizm,
// it doesn't appear to have a startup script, so we need to set up everything
// that it needs here.
- if (_gameID == GID_AD2044)
+ if (_gameID == GID_AD2044) {
mainState->animDisplayingFrame = 345;
+ SaveGameSnapshot::PagedInventoryItem item;
+ item.page = 0;
+ item.slot = 1;
+ item.itemID = 54; // Electronic goaler (sic)
+
+ for (const InitialItemPlacement &itemPlacement : g_ad2044InitialItemPlacements) {
+ SaveGameSnapshot::PlacedInventoryItem placedItem;
+ placedItem.locationID = getLocationForScreen(itemPlacement.roomNumber, itemPlacement.screenNumber);
+ placedItem.itemID = itemPlacement.itemID;
+
+ snapshot->placedItems.push_back(placedItem);
+ }
+
+ snapshot->pagedItems.push_back(item);
+ }
+
return snapshot;
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 60a4ff2cc23..f08a8560d98 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -22,6 +22,8 @@
#ifndef VCRUISE_RUNTIME_H
#define VCRUISE_RUNTIME_H
+#include "graphics/pixelformat.h"
+
#include "common/hashmap.h"
#include "common/keyboard.h"
#include "common/rect.h"
@@ -473,6 +475,27 @@ struct SaveGameSwappableState {
};
struct SaveGameSnapshot {
+ struct PagedInventoryItem {
+ PagedInventoryItem();
+
+ uint8 page;
+ uint8 slot;
+ uint8 itemID;
+
+ void write(Common::WriteStream *stream) const;
+ void read(Common::ReadStream *stream, uint saveGameVersion);
+ };
+
+ struct PlacedInventoryItem {
+ PlacedInventoryItem();
+
+ uint32 locationID;
+ uint8 itemID;
+
+ void write(Common::WriteStream *stream) const;
+ void read(Common::ReadStream *stream, uint saveGameVersion);
+ };
+
SaveGameSnapshot();
void write(Common::WriteStream *stream) const;
@@ -490,6 +513,8 @@ struct SaveGameSnapshot {
uint swapOutRoom;
uint swapOutScreen;
uint swapOutDirection;
+ uint8 inventoryPage;
+ uint8 inventoryActiveItem;
uint numStates;
Common::SharedPtr<SaveGameSwappableState> states[kMaxStates];
@@ -508,6 +533,8 @@ struct SaveGameSnapshot {
Common::HashMap<uint32, int32> variables;
Common::HashMap<uint, uint32> timers;
+ Common::Array<PagedInventoryItem> pagedItems;
+ Common::Array<PlacedInventoryItem> placedItems;
};
enum OSEventType {
@@ -539,6 +566,8 @@ enum KeymappedEvent {
kKeymappedEventSoundVolumeUp,
kKeymappedEventSkipAnimation,
+
+ kKeymappedEventPutItem,
};
struct OSEvent {
@@ -674,6 +703,7 @@ private:
struct RenderSection {
Common::SharedPtr<Graphics::ManagedSurface> surf;
Common::Rect rect;
+ Graphics::PixelFormat pixFmt;
void init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt);
};
@@ -775,6 +805,7 @@ private:
static const uint kPanoramaHorizFlags = (kPanoramaLeftFlag | kPanoramaRightFlag);
static const uint kNumInventorySlots = 6;
+ static const uint kNumInventoryPages = 8;
typedef int32 ScriptArg_t;
typedef int32 StackInt_t;
@@ -948,9 +979,16 @@ private:
void drawCompass();
bool isTrayVisible() const;
void resetInventoryHighlights();
-
- Common::String getFileNameForItemGraphic(uint itemID) const;
- Common::SharedPtr<Graphics::Surface> loadGraphic(const Common::String &graphicName, bool required);
+ void loadInventoryFromPage();
+ void copyInventoryToPage();
+ void cheatPutItem();
+ static uint32 getLocationForScreen(uint roomNumber, uint screenNumber);
+ void updatePlacedItemCache();
+ void drawPlacedItemGraphic();
+ void clearPlacedItemGraphic();
+
+ void getFileNamesForItemGraphic(uint itemID, Common::String &outGraphicFileName, Common::String &outAlphaFileName) const;
+ Common::SharedPtr<Graphics::Surface> loadGraphic(const Common::String &graphicName, const Common::String &alphaName, bool required);
Common::SharedPtr<Graphics::Surface> loadGraphicFromPath(const Common::Path &path, bool required);
bool loadSubtitles(Common::CodePage codePage, bool guessCodePage);
@@ -1186,6 +1224,12 @@ private:
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursorsShort; // Cursors indexed as CURSOR_#
InventoryItem _inventory[kNumInventorySlots];
+ InventoryItem _inventoryPages[kNumInventoryPages][kNumInventorySlots];
+ Common::HashMap<uint32, uint8> _placedItems;
+ uint8 _inventoryActivePage;
+ InventoryItem _inventoryActiveItem;
+ InventoryItem _inventoryPlacedItemCache;
+ Common::Rect _placedItemRect;
Common::SharedPtr<Graphics::Surface> _trayCompassGraphic;
Common::SharedPtr<Graphics::Surface> _trayBackgroundGraphic;
@@ -1360,6 +1404,7 @@ private:
RenderSection _traySection;
RenderSection _fullscreenMenuSection;
RenderSection _subtitleSection;
+ RenderSection _placedItemBackBufferSection;
Common::Point _mousePos;
Common::Point _lmbDownPos;
Commit: 63bcca8cc9257afa11c78bc3127080389c5f133a
https://github.com/scummvm/scummvm/commit/63bcca8cc9257afa11c78bc3127080389c5f133a
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Add some boilerplate for loading graphics and strings
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 829095fdd05..8db84757b51 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -21,6 +21,7 @@
#include "common/formats/winexe.h"
#include "common/config-manager.h"
+#include "common/crc.h"
#include "common/endian.h"
#include "common/events.h"
#include "common/file.h"
@@ -64,6 +65,89 @@
namespace VCruise {
+struct AD2044ItemInfo {
+ uint32 enNameCRC;
+ uint32 plNameCRC;
+ bool inspectable;
+};
+
+const AD2044ItemInfo g_ad2044ItemInfos[] = {
+ {0, 0, false}, // 0
+ {0, 0, false}, // 1
+ {0, 0, false}, // 2
+ {0, 0, false}, // 3
+ {0, 0, false}, // 4
+ {0, 0, false}, // 5
+ {0, 0, false}, // 6
+ {0, 0, false}, // 7
+ {0, 0, false}, // 8
+ {0, 0, false}, // 9
+ {0, 0, false}, // 10
+ {0, 0, false}, // 11
+ {0, 0, false}, // 12
+ {0, 0, false}, // 13
+ {0, 0, false}, // 14
+ {0, 0, false}, // 15
+ {0, 0, false}, // 16
+ {0, 0, false}, // 17
+ {0, 0, false}, // 18
+ {0, 0, false}, // 19
+ {0, 0, false}, // 20
+ {0, 0, false}, // 21
+ {0, 0, false}, // 22
+ {0, 0, false}, // 23
+ {0, 0, false}, // 24
+ {0, 0, false}, // 25
+ {0, 0, false}, // 26
+ {0, 0, false}, // 27
+ {0, 0, false}, // 28
+ {0, 0, false}, // 29
+ {0, 0, false}, // 30
+ {0, 0, false}, // 31
+ {0, 0, false}, // 32
+ {0, 0, false}, // 33
+ {0, 0, false}, // 34
+ {0, 0, false}, // 35
+ {0, 0, false}, // 36
+ {0, 0, false}, // 37
+ {0, 0, false}, // 38
+ {0, 0, false}, // 39
+ {0, 0, false}, // 40
+ {0, 0, false}, // 41
+ {0, 0, false}, // 42
+ {0, 0, false}, // 43
+ {0, 0, false}, // 44
+ {0, 0, false}, // 45
+ {0, 0, false}, // 46
+ {0, 0, false}, // 47
+ {0, 0, false}, // 48
+ {0, 0, false}, // 49
+ {0, 0, false}, // 50
+ {0, 0, false}, // 51
+ {0, 0, false}, // 52
+ {0, 0, false}, // 53
+ {0x83d54448, 0x839911EF, false}, // 54
+ {0, 0, false}, // 55
+ {0, 0, false}, // 56
+ {0, 0, false}, // 57
+ {0, 0, false}, // 58
+ {0, 0, false}, // 59
+ {0, 0, false}, // 60
+ {0, 0, false}, // 61
+ {0, 0, false}, // 62
+ {0, 0, false}, // 63
+ {0, 0, false}, // 64
+ {0, 0, false}, // 65
+ {0, 0, false}, // 66
+ {0, 0, false}, // 67
+ {0, 0, false}, // 68
+ {0, 0, false}, // 69
+ {0, 0, false}, // 70
+ {0, 0, false}, // 71
+ {0, 0, false}, // 72
+ {0, 0, false}, // 73
+};
+
struct InitialItemPlacement {
uint roomNumber;
uint screenNumber;
@@ -75,6 +159,90 @@ const InitialItemPlacement g_ad2044InitialItemPlacements[] = {
{1, 0xac, 27}, // Matches
};
+struct AD2044Graphics {
+ explicit AD2044Graphics(const Common::SharedPtr<Common::WinResources> &resources, bool lowQuality, const Graphics::PixelFormat &pixFmt);
+
+ Common::SharedPtr<Graphics::Surface> invDownClicked;
+ Common::SharedPtr<Graphics::Surface> invUpClicked;
+ Common::SharedPtr<Graphics::Surface> musicClicked;
+ Common::SharedPtr<Graphics::Surface> musicClickedDeep;
+ Common::SharedPtr<Graphics::Surface> soundClicked;
+ Common::SharedPtr<Graphics::Surface> soundClickedDeep;
+ Common::SharedPtr<Graphics::Surface> exitClicked;
+ Common::SharedPtr<Graphics::Surface> loadClicked;
+ Common::SharedPtr<Graphics::Surface> saveClicked;
+ Common::SharedPtr<Graphics::Surface> resizeClicked;
+ Common::SharedPtr<Graphics::Surface> musicVolUpClicked;
+ Common::SharedPtr<Graphics::Surface> musicVolDownClicked;
+ Common::SharedPtr<Graphics::Surface> music;
+ Common::SharedPtr<Graphics::Surface> musicVol;
+ Common::SharedPtr<Graphics::Surface> sound;
+ Common::SharedPtr<Graphics::Surface> soundVol;
+ Common::SharedPtr<Graphics::Surface> musicDisabled;
+ Common::SharedPtr<Graphics::Surface> musicVolDisabled;
+ Common::SharedPtr<Graphics::Surface> soundDisabled;
+ Common::SharedPtr<Graphics::Surface> soundVolDisabled;
+ Common::SharedPtr<Graphics::Surface> examine;
+ Common::SharedPtr<Graphics::Surface> examineDisabled;
+ Common::SharedPtr<Graphics::Surface> invPage[8];
+
+
+ void loadGraphic(Common::SharedPtr<Graphics::Surface> AD2044Graphics::*field, const Common::String &resName);
+ Common::SharedPtr<Graphics::Surface> loadGraphic(const Common::String &resName) const;
+ void finishLoading();
+
+private:
+ AD2044Graphics() = delete;
+
+ bool _lowQuality;
+ Common::SharedPtr<Common::WinResources> _resources;
+ Common::Array<Common::WinResourceID> _resourceIDs;
+ const Graphics::PixelFormat _pixFmt;
+};
+
+AD2044Graphics::AD2044Graphics(const Common::SharedPtr<Common::WinResources> &resources, bool lowQuality, const Graphics::PixelFormat &pixFmt)
+ : _resources(resources), _lowQuality(lowQuality), _pixFmt(pixFmt) {
+
+ _resourceIDs = resources->getIDList(Common::kWinBitmap);
+}
+
+void AD2044Graphics::loadGraphic(Common::SharedPtr<Graphics::Surface> AD2044Graphics::*field, const Common::String &resNameBase) {
+ this->*field = loadGraphic(resNameBase);
+}
+
+Common::SharedPtr<Graphics::Surface> AD2044Graphics::loadGraphic(const Common::String &resNameBase) const {
+ Common::String resName = _lowQuality ? (Common::String("D") + resNameBase) : resNameBase;
+
+ const Common::WinResourceID *resID = nullptr;
+ for (const Common::WinResourceID &resIDCandidate : _resourceIDs) {
+ if (resIDCandidate.getString() == resName) {
+ resID = &resIDCandidate;
+ break;
+ }
+ }
+
+ if (!resID)
+ error("Couldn't find bitmap graphic %s", resName.c_str());
+
+ Common::ScopedPtr<Common::SeekableReadStream> bmpResource(_resources->getResource(Common::kWinBitmap, *resID));
+
+ if (!bmpResource)
+ error("Couldn't open bitmap graphic %s", resName.c_str());
+
+ Image::BitmapDecoder decoder;
+ if (!decoder.loadStream(*bmpResource))
+ error("Couldn't load bitmap graphic %s", resName.c_str());
+
+ const Graphics::Surface *bmpSurf = decoder.getSurface();
+
+ Common::SharedPtr<Graphics::Surface> surf(bmpSurf->convertTo(_pixFmt, decoder.getPalette(), decoder.getPaletteColorCount()), Graphics::SurfaceDeleter());
+ return surf;
+}
+
+void AD2044Graphics::finishLoading() {
+ _resources.reset();
+}
+
struct CodePageGuess {
Common::CodePage codePage;
Runtime::CharSet charSet;
@@ -1627,7 +1795,7 @@ bool Runtime::bootGame(bool newGame) {
debug(1, "Booting V-Cruise game...");
if (_gameID == GID_AD2044)
- loadAD2044Index();
+ loadAD2044ExecutableResources();
else
loadReahSchizmIndex();
@@ -2933,7 +3101,7 @@ void Runtime::loadReahSchizmIndex() {
}
}
-void Runtime::loadAD2044Index() {
+void Runtime::loadAD2044ExecutableResources() {
const byte searchPattern[] = {0x01, 0x01, 0xa1, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x00, 0xc7, 0x00, 0x00, 0x00};
Common::File f;
@@ -2990,6 +3158,81 @@ void Runtime::loadAD2044Index() {
_ad2044AnimationDefs.push_back(animDef);
}
+
+ f.seek(0);
+
+ Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(&f));
+
+ if (!winRes)
+ error("Couldn't open executable resources");
+
+ _ad2044Graphics.reset(new AD2044Graphics(winRes, _lowQualityGraphicsMode, _gameSection.pixFmt));
+
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::invDownClicked, "GDOL");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::invUpClicked, "GGORA");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicClickedDeep, "GUZ03");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::soundClickedDeep, "GUZ06");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::exitClicked, "GUZ1");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::saveClicked, "GUZ10");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::resizeClicked, "GUZ2");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicClicked, "GUZ3");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::soundClicked, "GUZ6");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicVolUpClicked, "GUZ7");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicVolDownClicked, "GUZ8");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::loadClicked, "GUZ9");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::music, "GUZN3");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicVol, "GUZN4");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::sound, "GUZN6");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::soundVol, "GUZN7");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicDisabled, "NIC3");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::musicVolDisabled, "NIC4");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::soundDisabled, "NIC6");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::soundVolDisabled, "NIC7");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::examine, "OKO");
+ _ad2044Graphics->loadGraphic(&AD2044Graphics::examineDisabled, "OKOZ");
+
+ for (int i = 0; i < 8; i++)
+ _ad2044Graphics->invPage[i] = _ad2044Graphics->loadGraphic(Common::String::format("POJ%i", static_cast<int>(i + 1)));
+
+ _ad2044Graphics->finishLoading();
+
+ Common::HashMap<uint32, uint32> stringHashToFilePos;
+
+ for (const AD2044ItemInfo &itemInfo : g_ad2044ItemInfos) {
+ stringHashToFilePos[itemInfo.enNameCRC] = 0;
+ stringHashToFilePos[itemInfo.plNameCRC] = 0;
+ }
+
+ stringHashToFilePos.erase(0);
+
+ // Scan for strings
+ Common::CRC32 crc;
+
+ uint32 strStartPos = 0;
+ uint32 rollingCRC = crc.getInitRemainder();
+
+ for (uint i = 0; i < exeContents.size(); i++) {
+ byte b = exeContents[i];
+ if (b == 0) {
+ uint32 strLength = i - strStartPos;
+ rollingCRC = crc.finalize(rollingCRC);
+ if (strLength != 0) {
+ Common::HashMap<uint32, uint32>::iterator it = stringHashToFilePos.find(rollingCRC);
+ if (it != stringHashToFilePos.end())
+ it->_value = strStartPos;
+ }
+
+#if 1
+ if (strStartPos == 100460) {
+ debug(1, "Check CRC was %u", rollingCRC);
+ }
+#endif
+
+ rollingCRC = crc.getInitRemainder();
+ strStartPos = i + 1;
+ } else
+ rollingCRC = crc.processByte(b, rollingCRC);
+ }
}
void Runtime::findWaves() {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index f08a8560d98..5fef682b119 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -97,6 +97,7 @@ struct Instruction;
struct RoomScriptSet;
struct SoundLoopInfo;
class SampleLoopAudioStream;
+struct AD2044Graphics;
enum GameState {
kGameStateBoot, // Booting the game
@@ -893,7 +894,7 @@ private:
void processUniversalKeymappedEvents(KeymappedEvent evt);
void loadReahSchizmIndex();
- void loadAD2044Index();
+ void loadAD2044ExecutableResources();
void findWaves();
void loadConfig(const char *cfgPath);
void loadScore();
@@ -1450,6 +1451,7 @@ private:
static const uint kSoundCacheSize = 16;
static const uint kHeroChangeInteractionID = 0xffffffffu;
+ static const uint kObjectInteractionID = 0xfffffffeu;
Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
uint _soundCacheIndex;
@@ -1494,6 +1496,8 @@ private:
// AD2044 tooltips
Common::String _tooltipText;
+
+ Common::SharedPtr<AD2044Graphics> _ad2044Graphics;
};
} // End of namespace VCruise
Commit: 712481918ebf40a04c9b33a272252db88f9753ed
https://github.com/scummvm/scummvm/commit/712481918ebf40a04c9b33a272252db88f9753ed
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Fix a bunch of things to get bathroom working
Changed paths:
A engines/vcruise/ad2044_items.cpp
A engines/vcruise/ad2044_items.h
A engines/vcruise/ad2044_ui.cpp
A engines/vcruise/ad2044_ui.h
engines/vcruise/module.mk
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
engines/vcruise/script.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/ad2044_items.cpp b/engines/vcruise/ad2044_items.cpp
new file mode 100644
index 00000000000..3fc142a1de6
--- /dev/null
+++ b/engines/vcruise/ad2044_items.cpp
@@ -0,0 +1,103 @@
+/* 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/ad2044_items.h"
+
+namespace VCruise {
+
+const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items] = {
+ {0, 0, 0, 0}, // 0
+ {0, 0, 0, 0}, // 1
+ {0, 0, 0, 0}, // 2
+ {0, 0, 0, 0}, // 3
+ {0, 0, 0, 0}, // 4
+ {0, 0, 0, 0}, // 5
+ {0, 0, 0, 0}, // 6
+ {0, 0, 0, 0}, // 7
+ {0, 0, 0, 0}, // 8
+ {0, 0, 0, 0}, // 9
+ {0, 0, 0, 0}, // 10
+ {0, 0, 0, 0}, // 11
+ {0, 0, 0, 0}, // 12
+ {0, 0, 0, 0}, // 13
+ {0, 0, 0, 0}, // 14
+ {0, 0, 0, 0}, // 15
+ {0, 0, 0, 0}, // 16
+ {0, 0, 0, 0}, // 17
+ {0, 0, 0x18, 0x128}, // 18
+ {0, 0, 0, 0}, // 19
+ {0, 0, 0, 0}, // 20
+ {0, 0, 0, 0}, // 21
+ {0, 0, 0, 0}, // 22
+ {0, 0, 0, 0}, // 23
+ {0, 0, 0, 0}, // 24
+ {0, 0, 0, 0}, // 25
+ {0, 0, 0, 0}, // 26
+ {0, 0, 0, 0}, // 27
+ {0, 0, 0, 0}, // 28
+ {0, 0, 0, 0}, // 29
+ {0, 0, 0, 0}, // 30
+ {0, 0, 0, 0}, // 31
+ {0, 0, 0, 0}, // 32
+ {0, 0, 0, 0}, // 33
+ {0, 0, 0, 0}, // 34
+ {0, 0, 0, 0}, // 35
+ {0, 0, 0, 0}, // 36
+ {0, 0, 0, 0}, // 37
+ {0, 0, 0, 0}, // 38
+ {0, 0, 0, 0}, // 39
+ {0, 0, 0, 0}, // 40
+ {0, 0, 0, 0}, // 41
+ {0, 0, 0, 0}, // 42
+ {0, 0, 0, 0}, // 43
+ {0, 0, 0, 0}, // 44
+ {0, 0, 0, 0}, // 45
+ {0, 0, 0, 0}, // 46
+ {0, 0, 0, 0}, // 47
+ {0, 0, 0, 0}, // 48
+ {0, 0, 0, 0}, // 49
+ {0, 0, 0, 0}, // 50
+ {0, 0, 0, 0}, // 51
+ {0, 0, 0, 0}, // 52
+ {0, 0, 0, 0}, // 53
+ {0x83d54448, 0x839911EF, 0, 0}, // 54
+ {0, 0, 0, 0}, // 55
+ {0, 0, 0, 0}, // 56
+ {0, 0, 0, 0}, // 57
+ {0, 0, 0, 0}, // 58
+ {0, 0, 0, 0}, // 59
+ {0, 0, 0, 0}, // 60
+ {0, 0, 0, 0}, // 61
+ {0, 0, 0, 0}, // 62
+ {0, 0, 0, 0}, // 63
+ {0, 0, 0, 0}, // 64
+ {0, 0, 0, 0}, // 65
+ {0, 0, 0, 0}, // 66
+ {0, 0, 0, 0}, // 67
+ {0, 0, 0, 0}, // 68
+ {0, 0, 0, 0}, // 69
+ {0, 0, 0, 0}, // 70
+ {0, 0, 0, 0}, // 71
+ {0, 0, 0, 0}, // 72
+ {0, 0, 0, 0}, // 73
+};
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/ad2044_items.h b/engines/vcruise/ad2044_items.h
new file mode 100644
index 00000000000..fbb6dcb86eb
--- /dev/null
+++ b/engines/vcruise/ad2044_items.h
@@ -0,0 +1,43 @@
+/* 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_AD2044_ITEMS_H
+#define VCRUISE_AD2044_ITEMS_H
+
+#include "common/scummsys.h"
+
+namespace VCruise {
+
+struct AD2044ItemInfo {
+ uint32 enNameCRC;
+ uint32 plNameCRC;
+ uint16 inspectionScreenID;
+ uint16 scriptItemID;
+};
+
+static const uint kNumAD2044Items = 74;
+
+extern const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items];
+
+} // End of namespace VCruise
+
+
+#endif
diff --git a/engines/vcruise/ad2044_ui.cpp b/engines/vcruise/ad2044_ui.cpp
new file mode 100644
index 00000000000..d6572441f51
--- /dev/null
+++ b/engines/vcruise/ad2044_ui.cpp
@@ -0,0 +1,41 @@
+/* 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/ad2044_ui.h"
+
+namespace VCruise {
+
+namespace AD2044Interface {
+
+Common::Rect getRectForUI(AD2044InterfaceRectID rectID) {
+ switch (rectID) {
+ case AD2044InterfaceRectID::ActiveItemRender:
+ return Common::Rect(512, 150, 588, 217);
+ case AD2044InterfaceRectID::ExamineButton:
+ return Common::Rect(495, 248, 595, 318);
+ default:
+ return Common::Rect();
+ }
+}
+
+} // End of namespace AD2044Interface
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/ad2044_ui.h b/engines/vcruise/ad2044_ui.h
new file mode 100644
index 00000000000..53f21fdb796
--- /dev/null
+++ b/engines/vcruise/ad2044_ui.h
@@ -0,0 +1,42 @@
+/* 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_AD2044_UI_H
+#define VCRUISE_AD2044_UI_H
+
+#include "common/rect.h"
+
+namespace VCruise {
+
+enum class AD2044InterfaceRectID {
+ ActiveItemRender,
+ ExamineButton,
+};
+
+namespace AD2044Interface {
+
+Common::Rect getRectForUI(AD2044InterfaceRectID rectID);
+
+} // End of namespace AD2044Interface
+
+} // End of namespace VCruise
+
+#endif
diff --git a/engines/vcruise/module.mk b/engines/vcruise/module.mk
index 32086488026..11fa090293d 100644
--- a/engines/vcruise/module.mk
+++ b/engines/vcruise/module.mk
@@ -1,6 +1,8 @@
MODULE := engines/vcruise
MODULE_OBJS = \
+ ad2044_items.o \
+ ad2044_ui.o \
audio_player.o \
circuitpuzzle.o \
metaengine.o \
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 8db84757b51..7bca07f29de 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -53,6 +53,8 @@
#include "gui/message.h"
+#include "vcruise/ad2044_items.h"
+#include "vcruise/ad2044_ui.h"
#include "vcruise/audio_player.h"
#include "vcruise/circuitpuzzle.h"
#include "vcruise/sampleloop.h"
@@ -65,89 +67,6 @@
namespace VCruise {
-struct AD2044ItemInfo {
- uint32 enNameCRC;
- uint32 plNameCRC;
- bool inspectable;
-};
-
-const AD2044ItemInfo g_ad2044ItemInfos[] = {
- {0, 0, false}, // 0
- {0, 0, false}, // 1
- {0, 0, false}, // 2
- {0, 0, false}, // 3
- {0, 0, false}, // 4
- {0, 0, false}, // 5
- {0, 0, false}, // 6
- {0, 0, false}, // 7
- {0, 0, false}, // 8
- {0, 0, false}, // 9
- {0, 0, false}, // 10
- {0, 0, false}, // 11
- {0, 0, false}, // 12
- {0, 0, false}, // 13
- {0, 0, false}, // 14
- {0, 0, false}, // 15
- {0, 0, false}, // 16
- {0, 0, false}, // 17
- {0, 0, false}, // 18
- {0, 0, false}, // 19
- {0, 0, false}, // 20
- {0, 0, false}, // 21
- {0, 0, false}, // 22
- {0, 0, false}, // 23
- {0, 0, false}, // 24
- {0, 0, false}, // 25
- {0, 0, false}, // 26
- {0, 0, false}, // 27
- {0, 0, false}, // 28
- {0, 0, false}, // 29
- {0, 0, false}, // 30
- {0, 0, false}, // 31
- {0, 0, false}, // 32
- {0, 0, false}, // 33
- {0, 0, false}, // 34
- {0, 0, false}, // 35
- {0, 0, false}, // 36
- {0, 0, false}, // 37
- {0, 0, false}, // 38
- {0, 0, false}, // 39
- {0, 0, false}, // 40
- {0, 0, false}, // 41
- {0, 0, false}, // 42
- {0, 0, false}, // 43
- {0, 0, false}, // 44
- {0, 0, false}, // 45
- {0, 0, false}, // 46
- {0, 0, false}, // 47
- {0, 0, false}, // 48
- {0, 0, false}, // 49
- {0, 0, false}, // 50
- {0, 0, false}, // 51
- {0, 0, false}, // 52
- {0, 0, false}, // 53
- {0x83d54448, 0x839911EF, false}, // 54
- {0, 0, false}, // 55
- {0, 0, false}, // 56
- {0, 0, false}, // 57
- {0, 0, false}, // 58
- {0, 0, false}, // 59
- {0, 0, false}, // 60
- {0, 0, false}, // 61
- {0, 0, false}, // 62
- {0, 0, false}, // 63
- {0, 0, false}, // 64
- {0, 0, false}, // 65
- {0, 0, false}, // 66
- {0, 0, false}, // 67
- {0, 0, false}, // 68
- {0, 0, false}, // 69
- {0, 0, false}, // 70
- {0, 0, false}, // 71
- {0, 0, false}, // 72
- {0, 0, false}, // 73
-};
-
struct InitialItemPlacement {
uint roomNumber;
uint screenNumber;
@@ -155,6 +74,7 @@ struct InitialItemPlacement {
};
const InitialItemPlacement g_ad2044InitialItemPlacements[] = {
+ {1, 0xb0, 18}, // Spoon
{1, 0xb8, 24}, // Cigarette pack
{1, 0xac, 27}, // Matches
};
@@ -534,6 +454,17 @@ const AD2044MapLoader::ScreenOverride AD2044MapLoader::sk_screenOverrides[] = {
{1, 0xb6, 145}, // After pushing the button to open the capsule
{1, 0x6a, 142}, // Opening an apple on the table
{1, 0x6b, 143}, // Clicking the tablet in the apple
+ {1, 0x6c, 144}, // Table facing the center of the room with soup bowl empty
+
+ // Room 23
+ {23, 0xbb, 127}, // Bathroom entry point
+ {23, 0xbc, 128}, // Sink
+ {23, 0xbd, 129}, // Looking at toilet, seat down
+ {23, 0xbe, 130}, // Looking at toilet, seat up
+ {23, 0x61, 133}, // Bathroom looking at boots
+ {23, 0x62, 134}, // Looking behind boots
+ {23, 0x63, 135}, // Standing behind toilet looking at sink
+ {23, 0x64, 136}, // Looking under toilet
};
AD2044MapLoader::AD2044MapLoader() : _roomNumber(0), _screenNumber(0), _isLoaded(false) {
@@ -589,7 +520,7 @@ void AD2044MapLoader::load() {
debug(1, "Loading screen map %s", mapFileName.toString(Common::Path::kNativeSeparator).c_str());
if (!mapFile.open(mapFileName)) {
- error("Couldn't resolve map file for room %u screen %u", _roomNumber, _screenNumber);
+ error("Couldn't resolve map file for room %u screen %x", _roomNumber, _screenNumber);
}
_currentMap = loadScreenDirectionDef(mapFile);
@@ -1519,7 +1450,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_listenerX(0), _listenerY(0), _listenerAngle(0), _soundCacheIndex(0),
_isInGame(false),
_subtitleFont(nullptr), _isDisplayingSubtitles(false), _isSubtitleSourceAnimation(false),
- _languageIndex(0), _defaultLanguageIndex(0), _defaultLanguage(defaultLanguage), _charSet(kCharSetLatin),
+ _languageIndex(0), _defaultLanguageIndex(0), _defaultLanguage(defaultLanguage), _language(defaultLanguage), _charSet(kCharSetLatin),
_isCDVariant(false), _currentAnimatedCursor(nullptr), _currentCursor(nullptr), _cursorTimeBase(0), _cursorCycleLength(0),
_inventoryActivePage(0) {
@@ -1931,6 +1862,8 @@ bool Runtime::bootGame(bool newGame) {
}
}
+ _language = lang;
+
debug(2, "Language index: %u Default language index: %u", _languageIndex, _defaultLanguageIndex);
Common::CodePage codePage = Common::CodePage::kASCII;
@@ -3918,9 +3851,24 @@ bool Runtime::dischargeIdleMouseMove() {
uint interactionID = 0;
if (sdDef && !_idleLockInteractions) {
for (const InteractionDef &idef : sdDef->interactions) {
- if (idef.objectType == 1 && idef.rect.contains(relMouse)) {
+ if (idef.objectType == 1 && interactionID == 0 && idef.rect.contains(relMouse)) {
isOnInteraction = true;
interactionID = idef.interactionID;
+ }
+
+ if (_gameID == GID_AD2044 && idef.objectType == 3 && idef.rect.contains(relMouse)) {
+ uint32 locationID = getLocationForScreen(_roomNumber, _screenNumber);
+ if (_placedItems.find(locationID) == _placedItems.end()) {
+ if (_inventoryActiveItem.itemID != 0) {
+ isOnInteraction = true;
+ interactionID = kObjectDropInteractionID;
+ }
+ } else {
+ if (_inventoryActiveItem.itemID == 0) {
+ isOnInteraction = true;
+ interactionID = kObjectPickupInteractionID;
+ }
+ }
break;
}
}
@@ -3995,6 +3943,12 @@ bool Runtime::dischargeIdleMouseMove() {
if (interactionID == kHeroChangeInteractionID) {
changeToCursor(_cursors[16]);
_idleHaveClickInteraction = true;
+ } else if (interactionID == kObjectDropInteractionID) {
+ changeToCursor(_cursors[7]);
+ _idleHaveClickInteraction = true;
+ } else if (interactionID == kObjectPickupInteractionID) {
+ changeToCursor(_cursors[8]);
+ _idleHaveClickInteraction = true;
} else {
// New interaction, is there a script?
Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
@@ -4047,11 +4001,21 @@ bool Runtime::dischargeIdleClick() {
if (_gameID == GID_SCHIZM && _idleInteractionID == kHeroChangeInteractionID) {
changeHero();
return true;
+ } else if (_gameID == GID_AD2044 && _idleInteractionID == kObjectDropInteractionID) {
+ dropActiveItem();
+ recordSaveGameSnapshot();
+ _havePendingReturnToIdleState = true;
+ return true;
+ } else if (_gameID == GID_AD2044 && _idleInteractionID == kObjectPickupInteractionID) {
+ pickupPlacedItem();
+ recordSaveGameSnapshot();
+ _havePendingReturnToIdleState = true;
+ return true;
} else {
// Interaction, is there a script?
Common::SharedPtr<Script> script = findScriptForInteraction(_idleInteractionID);
- _idleIsOnInteraction = false; // ?
+ _idleIsOnInteraction = false; // Clear so new interactions at the same mouse coord are detected
if (script) {
ScriptEnvironmentVars vars;
@@ -5749,6 +5713,71 @@ void Runtime::clearPlacedItemGraphic() {
}
}
+void Runtime::drawActiveItemGraphic() {
+ if (_inventoryActiveItem.graphic) {
+ Common::Rect itemRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ActiveItemRender);
+
+ _fullscreenMenuSection.surf->blitFrom(*_inventoryActiveItem.graphic, Common::Point(itemRect.left, itemRect.top));
+ drawSectionToScreen(_fullscreenMenuSection, itemRect);
+ }
+
+ if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].inspectionScreenID != 0) {
+ Common::Rect examineRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton);
+
+ _fullscreenMenuSection.surf->blitFrom(*_ad2044Graphics->examine, Common::Point(examineRect.left, examineRect.top));
+ drawSectionToScreen(_fullscreenMenuSection, examineRect);
+ }
+}
+
+void Runtime::clearActiveItemGraphic() {
+ Common::Rect rectsToClear[] = {
+ AD2044Interface::getRectForUI(AD2044InterfaceRectID::ActiveItemRender),
+ AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton),
+ };
+
+ for (const Common::Rect &rectToClear : rectsToClear) {
+ _fullscreenMenuSection.surf->blitFrom(*_backgroundGraphic, rectToClear, rectToClear);
+ drawSectionToScreen(_fullscreenMenuSection, rectToClear);
+ }
+}
+
+void Runtime::dropActiveItem() {
+ if (_inventoryActiveItem.itemID == 0)
+ return;
+
+ uint32 locationID = getLocationForScreen(_roomNumber, _screenNumber);
+ uint8 &placedItemIDRef = _placedItems[locationID];
+
+ if (placedItemIDRef == 0) {
+ placedItemIDRef = static_cast<uint8>(_inventoryActiveItem.itemID);
+ _inventoryPlacedItemCache = _inventoryActiveItem;
+ _inventoryActiveItem = InventoryItem();
+ }
+
+ drawPlacedItemGraphic();
+ clearActiveItemGraphic();
+}
+
+void Runtime::pickupPlacedItem() {
+ if (_inventoryActiveItem.itemID != 0)
+ return;
+
+ uint32 locationID = getLocationForScreen(_roomNumber, _screenNumber);
+ Common::HashMap<uint32, uint8>::iterator placedItemIt = _placedItems.find(locationID);
+ if (placedItemIt == _placedItems.end())
+ return;
+
+ if (placedItemIt->_value != _inventoryPlacedItemCache.itemID)
+ error("Placed item cache desynced somehow, please report this as a bug");
+
+ _placedItems.erase(placedItemIt);
+ _inventoryActiveItem = _inventoryPlacedItemCache;
+ _inventoryPlacedItemCache = InventoryItem();
+
+ clearPlacedItemGraphic();
+ drawActiveItemGraphic();
+}
+
void Runtime::getFileNamesForItemGraphic(uint itemID, Common::String &outFileName, Common::String &outAlphaFileName) const {
if (_gameID == GID_REAH)
outFileName = Common::String::format("Thing%u", itemID);
@@ -6507,7 +6536,7 @@ void Runtime::onKeymappedEvent(KeymappedEvent kme) {
bool Runtime::canSave(bool onCurrentScreen) const {
if (onCurrentScreen) {
- return (_mostRecentlyRecordedSaveState.get() != nullptr && (_haveHorizPanAnimations || _forceAllowSaves));
+ return (_mostRecentlyRecordedSaveState.get() != nullptr && (_haveHorizPanAnimations || _forceAllowSaves || _gameID == GID_AD2044));
} else {
return _mostRecentValidSaveState.get() != nullptr && _isInGame;
}
@@ -6902,6 +6931,10 @@ Common::SharedPtr<SaveGameSnapshot> Runtime::generateNewGameSnapshot() const {
}
snapshot->pagedItems.push_back(item);
+
+ // Alt state is for item inspection
+ snapshot->numStates = 2;
+ snapshot->states[1].reset(new SaveGameSwappableState());
}
return snapshot;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 5fef682b119..6787604567a 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -987,6 +987,10 @@ private:
void updatePlacedItemCache();
void drawPlacedItemGraphic();
void clearPlacedItemGraphic();
+ void drawActiveItemGraphic();
+ void clearActiveItemGraphic();
+ void dropActiveItem();
+ void pickupPlacedItem();
void getFileNamesForItemGraphic(uint itemID, Common::String &outGraphicFileName, Common::String &outAlphaFileName) const;
Common::SharedPtr<Graphics::Surface> loadGraphic(const Common::String &graphicName, const Common::String &alphaName, bool required);
@@ -1220,6 +1224,7 @@ private:
void scriptOpSay2K(ScriptArg_t arg);
void scriptOpSay3K(ScriptArg_t arg);
void scriptOpRGet(ScriptArg_t arg);
+ void scriptOpRSet(ScriptArg_t arg);
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursorsShort; // Cursors indexed as CURSOR_#
@@ -1451,7 +1456,8 @@ private:
static const uint kSoundCacheSize = 16;
static const uint kHeroChangeInteractionID = 0xffffffffu;
- static const uint kObjectInteractionID = 0xfffffffeu;
+ static const uint kObjectDropInteractionID = 0xfffffffeu;
+ static const uint kObjectPickupInteractionID = 0xfffffffdu;
Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
uint _soundCacheIndex;
@@ -1465,6 +1471,7 @@ private:
Common::SharedPtr<Graphics::Font> _subtitleFontKeepalive;
uint _defaultLanguageIndex;
uint _languageIndex;
+ Common::Language _language;
CharSet _charSet;
bool _isCDVariant;
StartConfigDef _startConfigs[kNumStartConfigs];
@@ -1496,6 +1503,7 @@ private:
// AD2044 tooltips
Common::String _tooltipText;
+ Common::String _subtitleText;
Common::SharedPtr<AD2044Graphics> _ad2044Graphics;
};
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 937d0be55a8..6fa373be2e1 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -21,6 +21,7 @@
#include "common/random.h"
+#include "vcruise/ad2044_items.h"
#include "vcruise/audio_player.h"
#include "vcruise/circuitpuzzle.h"
#include "vcruise/runtime.h"
@@ -2042,7 +2043,28 @@ void Runtime::scriptOpPuzzleDone(ScriptArg_t arg) {
}
// AD2044 ops
-OPCODE_STUB(AnimT)
+void Runtime::scriptOpAnimT(ScriptArg_t arg) {
+ TAKE_STACK_INT(1);
+
+ StackInt_t animationID = stackArgs[0];
+
+ Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _animIDToFrameRange.find(animationID);
+ if (animRangeIt == _animIDToFrameRange.end())
+ error("Couldn't resolve animation ID %i", static_cast<int>(animationID));
+
+ AnimationDef animDef;
+ animDef.animNum = animRangeIt->_value.animationNum;
+ animDef.firstFrame = animRangeIt->_value.firstFrame;
+ animDef.lastFrame = animRangeIt->_value.lastFrame;
+
+ _haveIdleAnimations[0] = true;
+
+ StaticAnimation &outAnim = _idleAnimations[0];
+
+ outAnim = StaticAnimation();
+ outAnim.animDefs[0] = animDef;
+ outAnim.animDefs[1] = animDef;
+}
void Runtime::scriptOpAnimAD2044(bool isForward) {
TAKE_STACK_INT(2);
@@ -2142,15 +2164,37 @@ void Runtime::scriptOpM(ScriptArg_t arg) {
void Runtime::scriptOpEM(ScriptArg_t arg) {
}
-OPCODE_STUB(SE)
-OPCODE_STUB(SDot)
+void Runtime::scriptOpSE(ScriptArg_t arg) {
+ // English subtitle
+ if (_language == Common::PL_POL)
+ return;
+
+ _subtitleText = _scriptSet->strings[arg];
+}
+
+void Runtime::scriptOpSDot(ScriptArg_t arg) {
+ // Polish subtitle
+ if (_language == Common::PL_POL)
+ return;
+
+ _subtitleText = _scriptSet->strings[arg];
+}
void Runtime::scriptOpE(ScriptArg_t arg) {
+ if (_language == Common::PL_POL)
+ return;
+
_tooltipText = _scriptSet->strings[arg];
redrawSubtitleSection();
}
-OPCODE_STUB(Dot)
+void Runtime::scriptOpDot(ScriptArg_t arg) {
+ if (_language != Common::PL_POL)
+ return;
+
+ _tooltipText = _scriptSet->strings[arg];
+ redrawSubtitleSection();
+}
void Runtime::scriptOpSound(ScriptArg_t arg) {
TAKE_STACK_INT(2);
@@ -2161,7 +2205,23 @@ void Runtime::scriptOpISound(ScriptArg_t arg) {
}
OPCODE_STUB(USound)
-OPCODE_STUB(RGet)
+
+void Runtime::scriptOpRGet(ScriptArg_t arg) {
+ StackInt_t itemID = 0x2000;
+
+ if (_inventoryActiveItem.itemID < kNumAD2044Items) {
+ itemID = g_ad2044ItemInfos[_inventoryActiveItem.itemID].scriptItemID;
+ if (itemID == 0) {
+ warning("No script item ID for item type %i", static_cast<int>(_inventoryActiveItem.itemID));
+ itemID = 0x2000;
+ }
+ } else
+ error("Invalid item ID");
+
+ _scriptStack.push_back(StackValue(itemID));
+}
+
+OPCODE_STUB(RSet)
// Unused Schizm ops
@@ -2414,10 +2474,14 @@ bool Runtime::runScript() {
DISPATCH_OP(SE);
DISPATCH_OP(SDot);
DISPATCH_OP(E);
+ DISPATCH_OP(Dot);
DISPATCH_OP(Sound);
DISPATCH_OP(ISound);
+ DISPATCH_OP(RGet);
+ DISPATCH_OP(RSet);
+
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
}
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 92d6abdbd71..f7ceec3c806 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -156,11 +156,11 @@ public:
void compileScriptSet(ScriptSet *ss);
private:
- bool parseNumber(const Common::String &token, uint32 &outNumber) const;
+ bool parseNumber(const Common::String &token, uint32 &outNumber, bool mayStartWithNonZeroDigit) const;
static bool parseDecNumber(const Common::String &token, uint start, uint32 &outNumber);
static bool parseHexNumber(const Common::String &token, uint start, uint32 &outNumber);
static bool parseBinNumber(const Common::String &token, uint start, uint32 &outNumber);
- void expectNumber(uint32 &outNumber);
+ void expectNumber(uint32 &outNumber, bool mayStartWithNonZeroDigit);
void compileRoomScriptSet(RoomScriptSet *rss);
void compileReahOrAD2044ScreenScriptSet(ScreenScriptSet *sss);
@@ -232,7 +232,7 @@ ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::Path &blamePath
_scrToken(nullptr), _eroomToken(nullptr) {
}
-bool ScriptCompiler::parseNumber(const Common::String &token, uint32 &outNumber) const {
+bool ScriptCompiler::parseNumber(const Common::String &token, uint32 &outNumber, bool mayStartWithNonZeroDigit) const {
if (token.size() == 0)
return false;
@@ -240,7 +240,16 @@ bool ScriptCompiler::parseNumber(const Common::String &token, uint32 &outNumber)
return parseDecNumber(token, 1, outNumber);
if (_dialect == kScriptDialectReah || _dialect == kScriptDialectAD2044) {
- if (token[0] == '0') {
+ bool isValidNumber = false;
+
+ char firstChar = token[0];
+
+ if (_dialect == kScriptDialectReah || !mayStartWithNonZeroDigit)
+ isValidNumber = (firstChar == '0');
+ else
+ isValidNumber = (firstChar >= '0' && firstChar <= '9'); // Some room numbers lack a 0 prefix
+
+ if (isValidNumber) {
switch (_numberParsingMode) {
case kNumberParsingDec:
return parseDecNumber(token, 0, outNumber);
@@ -329,11 +338,11 @@ bool ScriptCompiler::parseBinNumber(const Common::String &token, uint start, uin
return true;
}
-void ScriptCompiler::expectNumber(uint32 &outNumber) {
+void ScriptCompiler::expectNumber(uint32 &outNumber, bool mayStartWithNonZeroDigit) {
TextParserState state;
Common::String token;
if (_parser.parseToken(token, state)) {
- if (!parseNumber(token, outNumber))
+ if (!parseNumber(token, outNumber, mayStartWithNonZeroDigit))
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));
@@ -373,7 +382,7 @@ void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
uint32 roomNumber = 0;
if (_parser.parseToken(token, state)) {
- if (!parseNumber(token, roomNumber))
+ if (!parseNumber(token, roomNumber, true))
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());
ss->roomScripts[roomNumber] = roomScript;
@@ -406,7 +415,7 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
return;
} else if (token == _scrToken) {
uint32 screenNumber = 0;
- expectNumber(screenNumber);
+ expectNumber(screenNumber, false);
Common::SharedPtr<ScreenScriptSet> sss(new ScreenScriptSet());
if (_dialect == kScriptDialectReah || _dialect == kScriptDialectAD2044)
@@ -438,7 +447,7 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
}
uint32 number = 0;
- if (!parseNumber(value, number))
+ if (!parseNumber(value, number, false))
error("Error compiling script at line %i col %i: Expected number", static_cast<int>(state._lineNum), static_cast<int>(state._col));
int32 signedNumber = static_cast<int32>(number);
@@ -492,7 +501,7 @@ void ScriptCompiler::compileReahOrAD2044ScreenScriptSet(ScreenScriptSet *sss) {
return;
} else if (token == "~*") {
uint32 interactionNumber = 0;
- expectNumber(interactionNumber);
+ expectNumber(interactionNumber, false);
codeGenScript(protoScript, *currentScript);
@@ -535,7 +544,7 @@ void ScriptCompiler::compileSchizmScreenScriptSet(ScreenScriptSet *sss) {
return;
} else if (token == "~*") {
uint32 interactionNumber = 0;
- expectNumber(interactionNumber);
+ expectNumber(interactionNumber, false);
codeGenScript(protoScript, *currentScript);
@@ -601,6 +610,7 @@ static ScriptNamedInstruction g_ad2044NamedInstructions[] = {
//{"r?", ProtoOp::kProtoOpScript, ScriptOps::kItemHaveSpace},
//{"r!", ProtoOp::kProtoOpScript, ScriptOps::kItemAdd},
{"r@", ProtoOp::kProtoOpScript, ScriptOps::kRGet},
+ {"r!", ProtoOp::kProtoOpScript, ScriptOps::kRSet},
//{"clearPocket", ProtoOp::kProtoOpScript, ScriptOps::kItemClear},
{"cursor!", ProtoOp::kProtoOpScript, ScriptOps::kSetCursor},
{"room!", ProtoOp::kProtoOpScript, ScriptOps::kSetRoom},
@@ -964,15 +974,15 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
}
if (_dialect == kScriptDialectSchizm && token.hasPrefix("-")) {
- uint32 unumber = 0;
- if (parseNumber(token.substr(1), unumber)) {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, -static_cast<int32>(unumber)));
+ uint32 number = 0;
+ if (parseNumber(token.substr(1), number, false)) {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, -static_cast<int32>(number)));
return true;
}
}
uint32 number = 0;
- if (parseNumber(token, number)) {
+ if (parseNumber(token, number, false)) {
script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, number));
return true;
}
@@ -1016,7 +1026,7 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
if (token == "#case") {
uint32 caseNumber = 0;
_parser.expect(":", _blamePath);
- expectNumber(caseNumber);
+ expectNumber(caseNumber, false);
script.instrs.push_back(ProtoInstruction(kProtoOpCase, ScriptOps::kInvalid, caseNumber));
return true;
@@ -1024,7 +1034,7 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
if (token == "#case:") {
uint32 caseNumber = 0;
- expectNumber(caseNumber);
+ expectNumber(caseNumber, false);
script.instrs.push_back(ProtoInstruction(kProtoOpCase, ScriptOps::kInvalid, caseNumber));
return true;
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index a758183d59c..0900991c8fa 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -241,6 +241,7 @@ enum ScriptOp {
kSay2K,
kSay3K,
kRGet,
+ kRSet,
kNumOps,
};
Commit: 3b1bebf0a2b4ba98a45dd51f4fca921146eca058
https://github.com/scummvm/scummvm/commit/3b1bebf0a2b4ba98a45dd51f4fca921146eca058
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Fix up bathroom mirror behavior
Changed paths:
engines/vcruise/ad2044_items.cpp
engines/vcruise/runtime.cpp
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/ad2044_items.cpp b/engines/vcruise/ad2044_items.cpp
index 3fc142a1de6..216ef7f86e9 100644
--- a/engines/vcruise/ad2044_items.cpp
+++ b/engines/vcruise/ad2044_items.cpp
@@ -84,7 +84,7 @@ const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items] = {
{0, 0, 0, 0}, // 57
{0, 0, 0, 0}, // 58
{0, 0, 0, 0}, // 59
- {0, 0, 0, 0}, // 60
+ {0, 0, 0, 0x170}, // 60
{0, 0, 0, 0}, // 61
{0, 0, 0, 0}, // 62
{0, 0, 0, 0}, // 63
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 7bca07f29de..cb3b1aa3e18 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -457,6 +457,11 @@ const AD2044MapLoader::ScreenOverride AD2044MapLoader::sk_screenOverrides[] = {
{1, 0x6c, 144}, // Table facing the center of the room with soup bowl empty
// Room 23
+ {23, 0xa3, 103}, // Looking at high mirror
+ {23, 0xa4, 104}, // After taking mirror
+
+ {23, 0xb9, 125}, // Bathroom looking down the stairs
+ //{23, 0xb9, 126}, // ???
{23, 0xbb, 127}, // Bathroom entry point
{23, 0xbc, 128}, // Sink
{23, 0xbd, 129}, // Looking at toilet, seat down
@@ -1606,6 +1611,8 @@ void Runtime::loadCursors(const char *exeName) {
_namedCursors["CUR_NAC"] = 5; // Nac = top? Not sure. But this is the finger pointer.
_namedCursors["CUR_TYL"] = 2; // Tyl = back
_namedCursors["CUR_OTWORZ"] = 11; // Otworz = open
+ _namedCursors["CUR_WEZ"] = 8; // Wez = Pick up
+ _namedCursors["CUR_ZOSTAW"] = 7; // Put down
}
_panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 6fa373be2e1..98108b184a6 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2211,7 +2211,7 @@ void Runtime::scriptOpRGet(ScriptArg_t arg) {
if (_inventoryActiveItem.itemID < kNumAD2044Items) {
itemID = g_ad2044ItemInfos[_inventoryActiveItem.itemID].scriptItemID;
- if (itemID == 0) {
+ if (itemID == 0 && _inventoryActiveItem.itemID != 0) {
warning("No script item ID for item type %i", static_cast<int>(_inventoryActiveItem.itemID));
itemID = 0x2000;
}
@@ -2221,7 +2221,30 @@ void Runtime::scriptOpRGet(ScriptArg_t arg) {
_scriptStack.push_back(StackValue(itemID));
}
-OPCODE_STUB(RSet)
+void Runtime::scriptOpRSet(ScriptArg_t arg) {
+ TAKE_STACK_INT(1);
+
+ for (uint itemID = 0; itemID < kNumAD2044Items; itemID++) {
+ if (static_cast<StackInt_t>(g_ad2044ItemInfos[itemID].scriptItemID) == stackArgs[0]) {
+
+ if (_inventoryActiveItem.itemID != itemID) {
+ Common::String itemFileName;
+ Common::String alphaFileName;
+
+ _inventoryActiveItem.itemID = itemID;
+ getFileNamesForItemGraphic(itemID, itemFileName, alphaFileName);
+ _inventoryActiveItem.graphic = loadGraphic(itemFileName, alphaFileName, false);
+
+ clearActiveItemGraphic();
+ drawActiveItemGraphic();
+ }
+ return;
+ }
+ }
+
+ // NYI
+ error("Couldn't resolve item ID for script item %i", static_cast<int>(stackArgs[0]));
+}
// Unused Schizm ops
Commit: a90317c90db0b1b49c0f6a1f1af040ab1908e1d1
https://github.com/scummvm/scummvm/commit/a90317c90db0b1b49c0f6a1f1af040ab1908e1d1
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Stub out inventory interactions
Changed paths:
engines/vcruise/ad2044_ui.cpp
engines/vcruise/ad2044_ui.h
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/ad2044_ui.cpp b/engines/vcruise/ad2044_ui.cpp
index d6572441f51..e46da440698 100644
--- a/engines/vcruise/ad2044_ui.cpp
+++ b/engines/vcruise/ad2044_ui.cpp
@@ -31,11 +31,31 @@ Common::Rect getRectForUI(AD2044InterfaceRectID rectID) {
return Common::Rect(512, 150, 588, 217);
case AD2044InterfaceRectID::ExamineButton:
return Common::Rect(495, 248, 595, 318);
+ case AD2044InterfaceRectID::InventoryRender0:
+ return Common::Rect(24, 394, 100, 461);
+ case AD2044InterfaceRectID::InventoryRender1:
+ return Common::Rect(119, 395, 195, 462);
+ case AD2044InterfaceRectID::InventoryRender2:
+ return Common::Rect(209, 393, 285, 460);
+ case AD2044InterfaceRectID::InventoryRender3:
+ return Common::Rect(302, 393, 378, 460);
+ case AD2044InterfaceRectID::InventoryRender4:
+ return Common::Rect(393, 394, 469, 461);
+ case AD2044InterfaceRectID::InventoryRender5:
+ return Common::Rect(481, 393, 557, 460);
default:
return Common::Rect();
}
}
+Common::Rect getFirstInvSlotRect() {
+ return Common::Rect(21, 392, 96, 460);
+}
+
+uint getInvSlotSpacing() {
+ return 92;
+}
+
} // End of namespace AD2044Interface
} // End of namespace VCruise
diff --git a/engines/vcruise/ad2044_ui.h b/engines/vcruise/ad2044_ui.h
index 53f21fdb796..aaf7cd911b0 100644
--- a/engines/vcruise/ad2044_ui.h
+++ b/engines/vcruise/ad2044_ui.h
@@ -29,11 +29,22 @@ namespace VCruise {
enum class AD2044InterfaceRectID {
ActiveItemRender,
ExamineButton,
+
+ InventoryRender0,
+ InventoryRender1,
+ InventoryRender2,
+ InventoryRender3,
+ InventoryRender4,
+ InventoryRender5,
+
};
namespace AD2044Interface {
Common::Rect getRectForUI(AD2044InterfaceRectID rectID);
+Common::Rect getFirstInvSlotRect();
+uint getInvSlotSpacing();
+
} // End of namespace AD2044Interface
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index cb3b1aa3e18..c12db4875c7 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -3888,6 +3888,29 @@ bool Runtime::dischargeIdleMouseMove() {
}
}
+ if (_gameID == GID_AD2044 && !isOnInteraction) {
+ Common::Rect invSlotRect = AD2044Interface::getFirstInvSlotRect();
+
+ for (uint i = 0; i < kNumInventorySlots; i++) {
+ bool isItemInInventory = (_inventory[i].itemID != 0);
+ bool isItemActive = (_inventoryActiveItem.itemID != 0);
+
+ if (invSlotRect.contains(_mousePos)) {
+ if (isItemInInventory && !isItemActive) {
+ isOnInteraction = true;
+ interactionID = kPickupInventorySlot0InteractionID + i;
+ } else if (!isItemInInventory && isItemActive) {
+ isOnInteraction = true;
+ interactionID = kReturnInventorySlot0InteractionID + i;
+ }
+
+ break;
+ }
+
+ invSlotRect.translate(static_cast<int16>(AD2044Interface::getInvSlotSpacing()), 0);
+ }
+ }
+
if (_idleIsOnInteraction && (!isOnInteraction || interactionID != _idleInteractionID)) {
// Mouse left the previous interaction
_idleIsOnInteraction = false;
@@ -3950,10 +3973,10 @@ bool Runtime::dischargeIdleMouseMove() {
if (interactionID == kHeroChangeInteractionID) {
changeToCursor(_cursors[16]);
_idleHaveClickInteraction = true;
- } else if (interactionID == kObjectDropInteractionID) {
+ } else if (interactionID == kObjectDropInteractionID || (interactionID >= kReturnInventorySlot0InteractionID && interactionID <= kReturnInventorySlot5InteractionID)) {
changeToCursor(_cursors[7]);
_idleHaveClickInteraction = true;
- } else if (interactionID == kObjectPickupInteractionID) {
+ } else if (interactionID == kObjectPickupInteractionID || (interactionID >= kPickupInventorySlot0InteractionID && interactionID <= kPickupInventorySlot5InteractionID)) {
changeToCursor(_cursors[8]);
_idleHaveClickInteraction = true;
} else {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 6787604567a..547ea72d40a 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -1459,6 +1459,20 @@ private:
static const uint kObjectDropInteractionID = 0xfffffffeu;
static const uint kObjectPickupInteractionID = 0xfffffffdu;
+ static const uint kReturnInventorySlot0InteractionID = 0xfffffff1u;
+ static const uint kReturnInventorySlot1InteractionID = 0xfffffff2u;
+ static const uint kReturnInventorySlot2InteractionID = 0xfffffff3u;
+ static const uint kReturnInventorySlot3InteractionID = 0xfffffff4u;
+ static const uint kReturnInventorySlot4InteractionID = 0xfffffff5u;
+ static const uint kReturnInventorySlot5InteractionID = 0xfffffff6u;
+
+ static const uint kPickupInventorySlot0InteractionID = 0xfffffff7u;
+ static const uint kPickupInventorySlot1InteractionID = 0xfffffff8u;
+ static const uint kPickupInventorySlot2InteractionID = 0xfffffff9u;
+ static const uint kPickupInventorySlot3InteractionID = 0xfffffffau;
+ static const uint kPickupInventorySlot4InteractionID = 0xfffffffbu;
+ static const uint kPickupInventorySlot5InteractionID = 0xfffffffcu;
+
Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
uint _soundCacheIndex;
Commit: 68925452137538a051b0bcd749fda6704a484834
https://github.com/scummvm/scummvm/commit/68925452137538a051b0bcd749fda6704a484834
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Add inventory pickup/stash
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index c12db4875c7..19bb2e508a7 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -4041,6 +4041,16 @@ bool Runtime::dischargeIdleClick() {
recordSaveGameSnapshot();
_havePendingReturnToIdleState = true;
return true;
+ } else if (_gameID == GID_AD2044 && _idleInteractionID >= kPickupInventorySlot0InteractionID && _idleInteractionID <= kPickupInventorySlot5InteractionID) {
+ pickupInventoryItem(_idleInteractionID - kPickupInventorySlot0InteractionID);
+ recordSaveGameSnapshot();
+ _havePendingReturnToIdleState = true;
+ return true;
+ } else if (_gameID == GID_AD2044 && _idleInteractionID >= kReturnInventorySlot0InteractionID && _idleInteractionID <= kReturnInventorySlot5InteractionID) {
+ stashActiveItemToInventory(_idleInteractionID - kReturnInventorySlot0InteractionID);
+ recordSaveGameSnapshot();
+ _havePendingReturnToIdleState = true;
+ return true;
} else {
// Interaction, is there a script?
Common::SharedPtr<Script> script = findScriptForInteraction(_idleInteractionID);
@@ -5771,6 +5781,22 @@ void Runtime::clearActiveItemGraphic() {
}
}
+void Runtime::drawInventoryItemGraphic(uint slot) {
+ if (_inventory[slot].graphic) {
+ Common::Rect rect = AD2044Interface::getRectForUI(static_cast<AD2044InterfaceRectID>(static_cast<uint>(AD2044InterfaceRectID::InventoryRender0) + slot));
+
+ _fullscreenMenuSection.surf->blitFrom(*_inventory[slot].graphic, Common::Point(rect.left, rect.top));
+ drawSectionToScreen(_fullscreenMenuSection, rect);
+ }
+}
+
+void Runtime::clearInventoryItemGraphic(uint slot) {
+ Common::Rect rect = AD2044Interface::getRectForUI(static_cast<AD2044InterfaceRectID>(static_cast<uint>(AD2044InterfaceRectID::InventoryRender0) + slot));
+
+ _fullscreenMenuSection.surf->blitFrom(*_backgroundGraphic, rect, rect);
+ drawSectionToScreen(_fullscreenMenuSection, rect);
+}
+
void Runtime::dropActiveItem() {
if (_inventoryActiveItem.itemID == 0)
return;
@@ -5808,6 +5834,35 @@ void Runtime::pickupPlacedItem() {
drawActiveItemGraphic();
}
+void Runtime::stashActiveItemToInventory(uint slot) {
+ if (_inventoryActiveItem.itemID == 0)
+ return;
+
+ if (_inventory[slot].itemID != 0)
+ return;
+
+ _inventory[slot] = _inventoryActiveItem;
+ _inventoryActiveItem = InventoryItem();
+
+
+ clearActiveItemGraphic();
+ drawInventoryItemGraphic(slot);
+}
+
+void Runtime::pickupInventoryItem(uint slot) {
+ if (_inventoryActiveItem.itemID != 0)
+ return;
+
+ if (_inventory[slot].itemID == 0)
+ return;
+
+ _inventoryActiveItem = _inventory[slot];
+ _inventory[slot] = InventoryItem();
+
+ clearInventoryItemGraphic(slot);
+ drawActiveItemGraphic();
+}
+
void Runtime::getFileNamesForItemGraphic(uint itemID, Common::String &outFileName, Common::String &outAlphaFileName) const {
if (_gameID == GID_REAH)
outFileName = Common::String::format("Thing%u", itemID);
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 547ea72d40a..85a280cb60f 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -989,8 +989,12 @@ private:
void clearPlacedItemGraphic();
void drawActiveItemGraphic();
void clearActiveItemGraphic();
+ void drawInventoryItemGraphic(uint slot);
+ void clearInventoryItemGraphic(uint slot);
void dropActiveItem();
void pickupPlacedItem();
+ void stashActiveItemToInventory(uint slot);
+ void pickupInventoryItem(uint slot);
void getFileNamesForItemGraphic(uint itemID, Common::String &outGraphicFileName, Common::String &outAlphaFileName) const;
Common::SharedPtr<Graphics::Surface> loadGraphic(const Common::String &graphicName, const Common::String &alphaName, bool required);
Commit: 00c7323818446f6afc890fec7700842c8a76fd71
https://github.com/scummvm/scummvm/commit/00c7323818446f6afc890fec7700842c8a76fd71
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Stub Say3K op
Changed paths:
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 98108b184a6..6bc31bb9ed9 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2505,6 +2505,8 @@ bool Runtime::runScript() {
DISPATCH_OP(RGet);
DISPATCH_OP(RSet);
+ DISPATCH_OP(Say3K);
+
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
}
Commit: 965e7afab6a17c6634ac170afb7a2567c8028596
https://github.com/scummvm/scummvm/commit/965e7afab6a17c6634ac170afb7a2567c8028596
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Fix static looping animations not persisting through reload
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 19bb2e508a7..6936eccd0a9 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -77,6 +77,8 @@ const InitialItemPlacement g_ad2044InitialItemPlacements[] = {
{1, 0xb0, 18}, // Spoon
{1, 0xb8, 24}, // Cigarette pack
{1, 0xac, 27}, // Matches
+ {1, 0x62, 2}, // "A" tag
+ {1, 0x64, 58}, // Newspaper
};
struct AD2044Graphics {
@@ -452,6 +454,7 @@ private:
const AD2044MapLoader::ScreenOverride AD2044MapLoader::sk_screenOverrides[] = {
// Room 1
{1, 0xb6, 145}, // After pushing the button to open the capsule
+ {1, 0x66, 138}, // Looking at banner
{1, 0x6a, 142}, // Opening an apple on the table
{1, 0x6b, 143}, // Clicking the tablet in the apple
{1, 0x6c, 144}, // Table facing the center of the room with soup bowl empty
@@ -1093,7 +1096,8 @@ void SaveGameSwappableState::Sound::read(Common::ReadStream *stream, uint saveGa
SaveGameSwappableState::SaveGameSwappableState() : roomNumber(0), screenNumber(0), direction(0), disc(0), havePendingPostSwapScreenReset(false),
musicTrack(0), musicVolume(100), musicActive(true), musicMuteDisabled(false), animVolume(100),
- loadedAnimation(0), animDisplayingFrame(0) {
+ loadedAnimation(0), animDisplayingFrame(0), haveIdleAnimationLoop(false), idleAnimNum(0), idleFirstFrame(0), idleLastFrame(0)
+{
}
SaveGameSnapshot::PagedInventoryItem::PagedInventoryItem() : page(0), slot(0), itemID(0) {
@@ -1140,6 +1144,12 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeUint32BE(states[sti]->direction);
stream->writeUint32BE(states[sti]->disc);
stream->writeByte(states[sti]->havePendingPostSwapScreenReset ? 1 : 0);
+ stream->writeByte(states[sti]->haveIdleAnimationLoop ? 1 : 0);
+ if (states[sti]->haveIdleAnimationLoop) {
+ stream->writeUint32BE(states[sti]->idleAnimNum);
+ stream->writeUint32BE(states[sti]->idleFirstFrame);
+ stream->writeUint32BE(states[sti]->idleLastFrame);
+ }
}
stream->writeUint32BE(hero);
@@ -1262,6 +1272,17 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
if (saveVersion >= 7)
states[sti]->havePendingPostSwapScreenReset = (stream->readByte() != 0);
+
+ if (saveVersion >= 10)
+ states[sti]->haveIdleAnimationLoop = (stream->readByte() != 0);
+ else
+ states[sti]->haveIdleAnimationLoop = false;
+
+ if (states[sti]->haveIdleAnimationLoop) {
+ states[sti]->idleAnimNum = stream->readUint32BE();
+ states[sti]->idleFirstFrame = stream->readUint32BE();
+ states[sti]->idleLastFrame = stream->readUint32BE();
+ }
}
if (saveVersion >= 6) {
@@ -1457,7 +1478,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_subtitleFont(nullptr), _isDisplayingSubtitles(false), _isSubtitleSourceAnimation(false),
_languageIndex(0), _defaultLanguageIndex(0), _defaultLanguage(defaultLanguage), _language(defaultLanguage), _charSet(kCharSetLatin),
_isCDVariant(false), _currentAnimatedCursor(nullptr), _currentCursor(nullptr), _cursorTimeBase(0), _cursorCycleLength(0),
- _inventoryActivePage(0) {
+ _inventoryActivePage(0), _keepStaticAnimationInIdle(false) {
for (uint i = 0; i < kNumDirections; i++) {
_haveIdleAnimations[i] = false;
@@ -3598,19 +3619,16 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
_havePanDownFromDirection[i] = false;
}
- clearIdleAnimations();
-
- for (uint i = 0; i < kNumDirections; i++)
- _haveIdleAnimations[i] = false;
+ if (!_keepStaticAnimationInIdle)
+ clearIdleAnimations();
- _havePendingPreIdleActions = true;
- _haveIdleStaticAnimation = false;
- _idleCurrentStaticAnimation.clear();
- _havePendingPlayAmbientSounds = true;
_forceAllowSaves = false;
recordSaveGameSnapshot();
+ if (_keepStaticAnimationInIdle)
+ _keepStaticAnimationInIdle = false;
+
_placedItemRect = Common::Rect();
if (_gameID == GID_AD2044) {
const MapScreenDirectionDef *screenDef = _mapLoader->getScreenDirection(_screenNumber, _direction);
@@ -3644,9 +3662,9 @@ void Runtime::clearIdleAnimations() {
for (uint i = 0; i < kNumDirections; i++)
_haveIdleAnimations[i] = false;
- _havePendingPreIdleActions = true;
_haveIdleStaticAnimation = false;
_idleCurrentStaticAnimation.clear();
+ _havePendingPreIdleActions = true;
_havePendingPlayAmbientSounds = true;
}
@@ -5451,7 +5469,7 @@ void Runtime::clearTray() {
_traySection.surf->fillRect(trayRect, blackColor);
}
- this->commitSectionToScreen(_traySection, trayRect);
+ drawSectionToScreen(_traySection, trayRect);
}
void Runtime::redrawSubtitleSection() {
@@ -5500,7 +5518,7 @@ void Runtime::drawSubtitleText(const Common::Array<Common::U32String> &lines, co
Graphics::ManagedSurface *surf = stSection.surf.get();
if (_subtitleFont) {
- int lineHeight = _subtitleFont->getFontHeight();
+ int lineHeight = (_gameID == GID_AD2044) ? 24 : _subtitleFont->getFontHeight();
int xOffset = 0;
int topY = 0;
@@ -5531,8 +5549,10 @@ void Runtime::drawInventory(uint slot) {
if (!isTrayVisible())
return;
- if (_gameID == GID_AD2044)
+ if (_gameID == GID_AD2044) {
+ drawInventoryItemGraphic(slot);
return;
+ }
Common::Rect trayRect = _traySection.rect;
trayRect.translate(-trayRect.left, -trayRect.top);
@@ -6698,6 +6718,17 @@ void Runtime::recordSaveGameSnapshot() {
mainState->loadedAnimation = _loadedAnimation;
mainState->animDisplayingFrame = _animDisplayingFrame;
+ if (_gameID == GID_AD2044) {
+ mainState->haveIdleAnimationLoop = _haveIdleAnimations[_direction];
+
+ if (mainState->haveIdleAnimationLoop) {
+ mainState->idleAnimNum = _idleAnimations[0].animDefs[0].animNum;
+ mainState->idleFirstFrame = _idleAnimations[0].animDefs[0].firstFrame;
+ mainState->idleLastFrame = _idleAnimations[0].animDefs[0].lastFrame;
+ }
+ } else
+ mainState->haveIdleAnimationLoop = false;
+
recordSounds(*mainState);
snapshot->pendingSoundParams3D = _pendingSoundParams3D;
@@ -6849,6 +6880,7 @@ void Runtime::restoreSaveGameSnapshot() {
}
}
+ _keepStaticAnimationInIdle = false;
_roomNumber = mainState->roomNumber;
_screenNumber = mainState->screenNumber;
_direction = mainState->direction;
@@ -6966,6 +6998,32 @@ void Runtime::restoreSaveGameSnapshot() {
stopSubtitles();
clearScreen();
redrawTray();
+
+ if (_gameID == GID_AD2044) {
+ drawActiveItemGraphic();
+
+ clearIdleAnimations();
+
+ if (mainState->haveIdleAnimationLoop) {
+ _keepStaticAnimationInIdle = true;
+
+
+ AnimationDef idleAnimDef;
+ idleAnimDef.animNum = mainState->idleAnimNum;
+ idleAnimDef.firstFrame = mainState->idleFirstFrame;
+ idleAnimDef.lastFrame = mainState->idleLastFrame;
+
+ _keepStaticAnimationInIdle = true;
+
+ _haveIdleAnimations[0] = true;
+
+ StaticAnimation staticAnim;
+ staticAnim.animDefs[0] = idleAnimDef;
+ staticAnim.animDefs[1] = idleAnimDef;
+
+ _idleAnimations[0] = staticAnim;
+ }
+ }
}
Common::SharedPtr<SaveGameSnapshot> Runtime::generateNewGameSnapshot() const {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 85a280cb60f..bad38036954 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -459,6 +459,10 @@ struct SaveGameSwappableState {
uint loadedAnimation;
uint animDisplayingFrame;
+ bool haveIdleAnimationLoop;
+ uint idleAnimNum;
+ uint idleFirstFrame;
+ uint idleLastFrame;
int musicTrack;
@@ -1276,6 +1280,7 @@ private:
StaticAnimation _idleAnimations[kNumDirections];
bool _haveIdleAnimations[kNumDirections];
bool _haveIdleStaticAnimation;
+ bool _keepStaticAnimationInIdle;
Common::String _idleCurrentStaticAnimation;
StaticAnimParams _pendingStaticAnimParams;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 6bc31bb9ed9..0fb60010c22 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2057,6 +2057,8 @@ void Runtime::scriptOpAnimT(ScriptArg_t arg) {
animDef.firstFrame = animRangeIt->_value.firstFrame;
animDef.lastFrame = animRangeIt->_value.lastFrame;
+ _keepStaticAnimationInIdle = true;
+
_haveIdleAnimations[0] = true;
StaticAnimation &outAnim = _idleAnimations[0];
Commit: d1b08c9960810f81bcba9e67218acbe623a66778
https://github.com/scummvm/scummvm/commit/d1b08c9960810f81bcba9e67218acbe623a66778
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Add MIDI playback
Changed paths:
A engines/vcruise/midi_player.cpp
A engines/vcruise/midi_player.h
engines/vcruise/module.mk
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
engines/vcruise/vcruise.cpp
engines/vcruise/vcruise.h
diff --git a/engines/vcruise/midi_player.cpp b/engines/vcruise/midi_player.cpp
new file mode 100644
index 00000000000..cf464122cbd
--- /dev/null
+++ b/engines/vcruise/midi_player.cpp
@@ -0,0 +1,79 @@
+/* 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 "audio/mididrv.h"
+#include "audio/midiparser_smf.h"
+
+#include "vcruise/midi_player.h"
+
+namespace VCruise {
+
+MidiPlayer::MidiPlayer(MidiDriver *midiDrv, Common::Array<byte> &&musicData, int volume)
+ : _midiDrv(midiDrv), _data(Common::move(musicData)), _volume(-1) {
+ _parser.reset(MidiParser::createParser_SMF());
+
+ if (_data.size() > 0u && _parser->loadMusic(&_data[0], _data.size())) {
+ _parser->setTrack(0);
+ _parser->setMidiDriver(_midiDrv);
+ _parser->startPlaying();
+ _parser->property(MidiParser::mpAutoLoop, 1);
+ _parser->setTimerRate(_midiDrv->getBaseTempo());
+
+ setVolume(volume);
+ } else
+ _parser.reset();
+}
+
+MidiPlayer::~MidiPlayer() {
+ if (_parser)
+ _parser->stopPlaying();
+}
+
+void MidiPlayer::setVolume(int volume) {
+ if (!_parser)
+ return;
+
+ if (volume > 100)
+ volume = 100;
+ else if (volume < 0)
+ volume = 0;
+
+ volume = 51;
+
+ //uint32 effectiveValue = static_cast<uint32>(floor(sqrt(sqrt(volume)) * 5180.76));
+ uint32 effectiveValue = static_cast<uint32>(volume * 0x3fff / 100);
+
+ if (effectiveValue > 0x3fffu)
+ effectiveValue = 0x3fffu;
+
+ byte masterVolMessage[6] = {
+ 0x7f, 0x00, 0x04, 0x01, (effectiveValue & 0x7f), ((effectiveValue >> 7) & 0x7f)
+ };
+
+ _midiDrv->sysEx(masterVolMessage, 6);
+}
+
+void MidiPlayer::onMidiTimer() {
+ if (_parser)
+ _parser->onTimer();
+}
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/midi_player.h b/engines/vcruise/midi_player.h
new file mode 100644
index 00000000000..67db4ce251f
--- /dev/null
+++ b/engines/vcruise/midi_player.h
@@ -0,0 +1,57 @@
+/* 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_MIDI_PLAYER_H
+#define VCRUISE_MIDI_PLAYER_H
+
+#include "common/array.h"
+#include "common/mutex.h"
+#include "common/ptr.h"
+
+class MidiDriver;
+class MidiParser;
+
+namespace Common {
+
+class SeekableReadStream;
+
+} // End of namespace Common
+
+namespace VCruise {
+
+class MidiPlayer {
+public:
+ MidiPlayer(MidiDriver *midiDrv, Common::Array<byte> &&musicData, int volume);
+ ~MidiPlayer();
+
+ void setVolume(int volume);
+ void onMidiTimer();
+
+private:
+ MidiDriver *_midiDrv;
+ Common::SharedPtr<MidiParser> _parser;
+ Common::Array<byte> _data;
+ int _volume;
+};
+
+} // End of namespace VCruise
+
+#endif
diff --git a/engines/vcruise/module.mk b/engines/vcruise/module.mk
index 11fa090293d..e29593a95ce 100644
--- a/engines/vcruise/module.mk
+++ b/engines/vcruise/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS = \
ad2044_ui.o \
audio_player.o \
circuitpuzzle.o \
+ midi_player.o \
metaengine.o \
menu.o \
runtime.o \
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 6936eccd0a9..bd6bd3b5069 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -58,6 +58,7 @@
#include "vcruise/audio_player.h"
#include "vcruise/circuitpuzzle.h"
#include "vcruise/sampleloop.h"
+#include "vcruise/midi_player.h"
#include "vcruise/menu.h"
#include "vcruise/runtime.h"
#include "vcruise/script.h"
@@ -1455,8 +1456,8 @@ void SaveGameSnapshot::writeString(Common::WriteStream *stream, const Common::St
FontCacheItem::FontCacheItem() : font(nullptr), size(0) {
}
-Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, Common::Language defaultLanguage)
- : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _disc(0), _swapOutRoom(0), _swapOutScreen(0), _swapOutDirection(0),
+Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, MidiDriver *midiDrv, const Common::FSNode &rootFSNode, VCruiseGameID gameID, Common::Language defaultLanguage)
+ : _system(system), _mixer(mixer), _midiDrv(midiDrv), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _disc(0), _swapOutRoom(0), _swapOutScreen(0), _swapOutDirection(0),
_haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingPreIdleActions(false), _havePendingReturnToIdleState(false), _havePendingPostSwapScreenReset(false),
_havePendingCompletionCheck(false), _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _lowQualityGraphicsMode(false),
@@ -2065,6 +2066,14 @@ void Runtime::drawLabel(Graphics::ManagedSurface *surface, const Common::String
font->drawString(surface, text, textPos.x, textPos.y, strWidth, realTextColor);
}
+void Runtime::onMidiTimer() {
+ if (_musicMidiPlayer) {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer->onMidiTimer();
+ }
+}
+
+
bool Runtime::runIdle() {
if (_havePendingScreenChange) {
_havePendingScreenChange = false;
@@ -4267,10 +4276,15 @@ void Runtime::loadTabData(uint animNumber, Common::SeekableReadStream *stream) {
}
void Runtime::changeMusicTrack(int track) {
- if (track == _musicTrack && _musicPlayer.get() != nullptr)
+ if (track == _musicTrack && _musicWavePlayer.get() != nullptr && _musicMidiPlayer.get() != nullptr)
return;
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
+ if (_musicMidiPlayer)
+ {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset();
+ }
_musicTrack = track;
if (!_musicActive)
@@ -4279,23 +4293,46 @@ void Runtime::changeMusicTrack(int track) {
if (_musicMute && !_musicMuteDisabled)
return;
- Common::Path 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));
+ Common::String musicPathStr;
+
+ if (_gameID == GID_AD2044)
+ musicPathStr = Common::String::format("sfx/music%02i.mid", static_cast<int>(track));
+ else
+ musicPathStr = Common::String::format("Sfx/Music-%02i.wav", static_cast<int>(track));
+
+ Common::Path musicFileName(musicPathStr);
+ Common::File *musicFile = new Common::File();
+ if (musicFile->open(musicFileName)) {
+ if (_gameID == GID_AD2044) {
+ Common::ScopedPtr<Common::File> fileHolder(musicFile);
+
+ uint musicFileSize = static_cast<uint>(musicFile->size());
+
+ if (musicFileSize > 0) {
+ Common::Array<byte> musicData;
+ musicData.resize(musicFileSize);
- _musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, Audio::Mixer::kMusicSoundType));
- _musicPlayer->play(applyVolumeScale(_musicVolume), 0);
+ musicFile->read(&musicData[0], musicFileSize);
+
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset(new MidiPlayer(_midiDrv, Common::move(musicData), _musicVolume));
+ }
+ } else {
+ if (Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(musicFile, DisposeAfterUse::YES)) {
+ Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
+
+ _musicWavePlayer.reset(new AudioPlayer(_mixer, loopingStream, Audio::Mixer::kMusicSoundType));
+ _musicWavePlayer->play(applyVolumeScale(_musicVolume), 0);
+ }
}
} else {
- warning("Music file '%s' is missing", wavFileName.toString(Common::Path::kNativeSeparator).c_str());
- delete wavFile;
+ warning("Music file '%s' is missing", musicFileName.toString(Common::Path::kNativeSeparator).c_str());
+ delete musicFile;
}
}
void Runtime::startScoreSection() {
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
_scoreSectionEndTime = 0;
if (!_musicActive)
@@ -4322,8 +4359,8 @@ void Runtime::startScoreSection() {
Common::File *trackFile = new Common::File();
if (trackFile->open(trackFileName)) {
if (Audio::SeekableAudioStream *audioStream = Audio::makeVorbisStream(trackFile, DisposeAfterUse::YES)) {
- _musicPlayer.reset(new AudioPlayer(_mixer, Common::SharedPtr<Audio::AudioStream>(audioStream), Audio::Mixer::kMusicSoundType));
- _musicPlayer->play(applyVolumeScale(sectionDef.volumeOrDurationInSeconds), 0);
+ _musicWavePlayer.reset(new AudioPlayer(_mixer, Common::SharedPtr<Audio::AudioStream>(audioStream), Audio::Mixer::kMusicSoundType));
+ _musicWavePlayer->play(applyVolumeScale(sectionDef.volumeOrDurationInSeconds), 0);
_scoreSectionEndTime = static_cast<uint32>(audioStream->getLength().msecs()) + g_system->getMillis();
} else {
@@ -4352,11 +4389,16 @@ void Runtime::setMusicMute(bool muted) {
if (prevIsActuallyMuted != isActuallyMuted) {
if (isActuallyMuted) {
// Became muted
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
+ if (_musicMidiPlayer)
+ {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset();
+ }
_scoreSectionEndTime = 0;
} else {
// Became unmuted
- if (_gameID == GID_REAH)
+ if (_gameID == GID_REAH || _gameID == GID_AD2044)
changeMusicTrack(_musicTrack);
else if (_gameID == GID_SCHIZM)
startScoreSection();
@@ -4791,8 +4833,13 @@ void Runtime::updateSounds(uint32 timestamp) {
newVolume += static_cast<int32>(ramp);
if (newVolume != _musicVolume) {
- if (_musicPlayer)
- _musicPlayer->setVolume(applyVolumeScale(newVolume));
+ if (_musicWavePlayer)
+ _musicWavePlayer->setVolume(applyVolumeScale(newVolume));
+
+ if (_musicMidiPlayer) {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer->setVolume(newVolume);
+ }
_musicVolume = newVolume;
}
@@ -6921,7 +6968,7 @@ void Runtime::restoreSaveGameSnapshot() {
_musicMuteDisabled = mainState->musicMuteDisabled;
_scoreTrack = mainState->scoreTrack;
- if (_gameID == GID_REAH)
+ if (_gameID == GID_REAH || _gameID == GID_AD2044)
changeMusicTrack(mainState->musicTrack);
if (_gameID == GID_SCHIZM) {
// Only restart music if a new track is playing
@@ -6929,13 +6976,21 @@ void Runtime::restoreSaveGameSnapshot() {
_scoreSection = mainState->scoreSection;
if (!musicMutedBeforeRestore && musicMutedAfterRestore) {
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
+ {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset();
+ }
_scoreSectionEndTime = 0;
} else if (!musicMutedAfterRestore && (isNewTrack || musicMutedBeforeRestore))
startScoreSection();
}
} else {
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
+ if (_musicMidiPlayer) {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset();
+ }
_scoreSectionEndTime = 0;
}
@@ -7059,6 +7114,8 @@ Common::SharedPtr<SaveGameSnapshot> Runtime::generateNewGameSnapshot() const {
// that it needs here.
if (_gameID == GID_AD2044) {
mainState->animDisplayingFrame = 345;
+ mainState->musicActive = true;
+ mainState->musicTrack = 1;
SaveGameSnapshot::PagedInventoryItem item;
item.page = 0;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index bad38036954..a681e327d61 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -26,11 +26,13 @@
#include "common/hashmap.h"
#include "common/keyboard.h"
+#include "common/mutex.h"
#include "common/rect.h"
#include "vcruise/detection.h"
class OSystem;
+class MidiDriver;
namespace Common {
@@ -86,6 +88,7 @@ enum StartConfig {
class AudioPlayer;
class CircuitPuzzle;
+class MidiPlayer;
class MenuInterface;
class MenuPage;
class RuntimeMenuInterface;
@@ -645,7 +648,7 @@ public:
kCharSetChineseSimplified,
};
- Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, Common::Language defaultLanguage);
+ Runtime(OSystem *system, Audio::Mixer *mixer, MidiDriver *midiDrv, const Common::FSNode &rootFSNode, VCruiseGameID gameID, Common::Language defaultLanguage);
virtual ~Runtime();
void initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &subtitleRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt);
@@ -681,6 +684,8 @@ public:
void drawLabel(Graphics::ManagedSurface *surface, const Common::String &labelID, const Common::Rect &contentRect);
void getLabelDef(const Common::String &labelID, const Graphics::Font *&outFont, const Common::String *&outTextUTF8, uint32 &outColor, uint32 &outShadowColor, uint32 &outShadowOffset);
+ void onMidiTimer();
+
private:
enum IndexParseType {
kIndexParseTypeNone,
@@ -1343,7 +1348,9 @@ private:
Common::SharedPtr<Common::RandomSource> _rng;
- Common::SharedPtr<AudioPlayer> _musicPlayer;
+ Common::SharedPtr<AudioPlayer> _musicWavePlayer;
+ Common::Mutex _midiPlayerMutex;
+ Common::SharedPtr<MidiPlayer> _musicMidiPlayer;
int _musicTrack;
int32 _musicVolume;
bool _musicActive;
@@ -1410,6 +1417,7 @@ private:
bool _inGameMenuButtonActive[5];
Audio::Mixer *_mixer;
+ MidiDriver *_midiDrv;
Common::SharedPtr<MapLoader> _mapLoader;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 0fb60010c22..f1765337d8b 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -744,8 +744,8 @@ void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
if (duration == 0) {
_musicVolume = newVolume;
- if (_musicPlayer)
- _musicPlayer->setVolume(newVolume);
+ if (_musicWavePlayer)
+ _musicWavePlayer->setVolume(newVolume);
} else {
if (newVolume != _musicVolume) {
uint32 timestamp = g_system->getMillis();
@@ -1184,8 +1184,8 @@ void Runtime::scriptOpExit(ScriptArg_t arg) {
terminateScript();
changeMusicTrack(0);
- if (_musicPlayer)
- _musicPlayer->setVolumeAndBalance(applyVolumeScale(getDefaultSoundVolume()), 0);
+ if (_musicWavePlayer)
+ _musicWavePlayer->setVolumeAndBalance(applyVolumeScale(getDefaultSoundVolume()), 0);
} else {
error("Don't know what screen to go to on exit");
}
@@ -1486,7 +1486,8 @@ void Runtime::scriptOpJump(ScriptArg_t arg) {
}
void Runtime::scriptOpMusicStop(ScriptArg_t arg) {
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
+ _musicMidiPlayer.reset();
_musicActive = false;
}
@@ -1514,7 +1515,8 @@ void Runtime::scriptOpScoreNormal(ScriptArg_t arg) {
_musicMuteDisabled = false;
if (_musicMute) {
- _musicPlayer.reset();
+ _musicWavePlayer.reset();
+ _musicMidiPlayer.reset();
_scoreSectionEndTime = 0;
}
}
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 7565d3219f5..e88bce1c440 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -32,6 +32,7 @@
#include "common/algorithm.h"
#include "common/translation.h"
+#include "audio/mididrv.h"
#include "audio/mixer.h"
#include "gui/message.h"
@@ -75,6 +76,14 @@ void VCruiseEngine::handleEvents() {
}
}
+void VCruiseEngine::staticHandleMidiTimer(void *refCon) {
+ static_cast<VCruiseEngine *>(refCon)->handleMidiTimer();
+}
+
+void VCruiseEngine::handleMidiTimer() {
+ _runtime->onMidiTimer();
+}
+
Common::Error VCruiseEngine::run() {
Common::List<Graphics::PixelFormat> pixelFormats = _system->getSupportedFormats();
@@ -105,6 +114,17 @@ Common::Error VCruiseEngine::run() {
}
#endif
+ Common::ScopedPtr<MidiDriver> midiDrv;
+ if (_gameDescription->gameID == GID_AD2044) {
+ MidiDriver::DeviceHandle midiDevHdl = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ if (midiDevHdl) {
+ midiDrv.reset(MidiDriver::createMidi(midiDevHdl));
+
+ if (midiDrv->open() != 0)
+ midiDrv.reset();
+ }
+ }
+
if (_gameDescription->desc.flags & VCRUISE_GF_GENTEE_PACKAGE) {
Common::File *f = new Common::File();
@@ -194,9 +214,12 @@ Common::Error VCruiseEngine::run() {
_system->fillScreen(0);
- _runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID, _gameDescription->defaultLanguage));
+ _runtime.reset(new Runtime(_system, _mixer, midiDrv.get(), _rootFSNode, _gameDescription->gameID, _gameDescription->defaultLanguage));
_runtime->initSections(_videoRect, _menuBarRect, _trayRect, _subtitleRect, Common::Rect(640, 480), _system->getScreenFormat());
+ if (midiDrv)
+ midiDrv->setTimerCallback(this, VCruiseEngine::staticHandleMidiTimer);
+
const char *exeName = _gameDescription->desc.filesDescriptions[0].fileName;
if (_gameDescription->desc.flags & VCRUISE_GF_GENTEE_PACKAGE)
@@ -238,6 +261,10 @@ Common::Error VCruiseEngine::run() {
_system->delayMillis(5);
}
+
+ if (midiDrv)
+ midiDrv->setTimerCallback(nullptr, nullptr);
+
_runtime.reset();
if (_gameDescription->desc.flags & VCRUISE_GF_GENTEE_PACKAGE)
diff --git a/engines/vcruise/vcruise.h b/engines/vcruise/vcruise.h
index 4dee4b73aba..3d1fff0e6f1 100644
--- a/engines/vcruise/vcruise.h
+++ b/engines/vcruise/vcruise.h
@@ -72,6 +72,8 @@ protected:
private:
void handleEvents();
+ static void staticHandleMidiTimer(void *refCon);
+ void handleMidiTimer();
Common::Rect _videoRect;
Common::Rect _menuBarRect;
Commit: 1d7c33e89384c993cf10db92cb654ad9dcc31ca2
https://github.com/scummvm/scummvm/commit/1d7c33e89384c993cf10db92cb654ad9dcc31ca2
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Fix some missing mutex locks
Changed paths:
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index f1765337d8b..8778d61ea26 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -1487,7 +1487,10 @@ void Runtime::scriptOpJump(ScriptArg_t arg) {
void Runtime::scriptOpMusicStop(ScriptArg_t arg) {
_musicWavePlayer.reset();
- _musicMidiPlayer.reset();
+ if (_musicMidiPlayer) {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset();
+ }
_musicActive = false;
}
@@ -1516,7 +1519,10 @@ void Runtime::scriptOpScoreNormal(ScriptArg_t arg) {
if (_musicMute) {
_musicWavePlayer.reset();
- _musicMidiPlayer.reset();
+ if (_musicMidiPlayer) {
+ Common::StackLock lock(_midiPlayerMutex);
+ _musicMidiPlayer.reset();
+ }
_scoreSectionEndTime = 0;
}
}
Commit: c8d2d34644ec74908513921b67ff540ccf41ff18
https://github.com/scummvm/scummvm/commit/c8d2d34644ec74908513921b67ff540ccf41ff18
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Add item examination
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
engines/vcruise/script.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index bd6bd3b5069..90baa6261ba 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -511,6 +511,13 @@ void AD2044MapLoader::load() {
}
}
+ if (_roomNumber == 87) {
+ uint highDigit = (_screenNumber & 0xf0) >> 4;
+ uint lowDigit = _screenNumber & 0x0f;
+
+ scrFileID = 8700 + static_cast<int>(highDigit * 10u + lowDigit);
+ }
+
if (scrFileID < 0) {
if (_screenNumber < kFirstScreen)
return;
@@ -1754,9 +1761,16 @@ bool Runtime::bootGame(bool newGame) {
debug(1, "Booting V-Cruise game...");
- if (_gameID == GID_AD2044)
+ if (_gameID == GID_AD2044) {
loadAD2044ExecutableResources();
- else
+
+ Common::File tabFile;
+
+ if (tabFile.open(Common::Path("anims/ANIM0087.TAB")))
+ loadTabData(_examineAnimIDToFrameRange, 87, &tabFile);
+ else
+ error("Failed to load inspection animations");
+ } else
loadReahSchizmIndex();
debug(1, "Index loaded OK");
@@ -2067,10 +2081,9 @@ void Runtime::drawLabel(Graphics::ManagedSurface *surface, const Common::String
}
void Runtime::onMidiTimer() {
- if (_musicMidiPlayer) {
- Common::StackLock lock(_midiPlayerMutex);
+ Common::StackLock lock(_midiPlayerMutex);
+ if (_musicMidiPlayer)
_musicMidiPlayer->onMidiTimer();
- }
}
@@ -3709,6 +3722,58 @@ void Runtime::changeHero() {
restoreSaveGameSnapshot();
}
+void Runtime::changeToExamineItem() {
+ assert(canSave(true));
+
+ InventoryItem itemToExamine = _inventoryActiveItem;
+
+ _inventoryActiveItem = InventoryItem();
+
+ recordSaveGameSnapshot();
+
+ SaveGameSnapshot *snapshot = _mostRecentlyRecordedSaveState.get();
+
+ Common::SharedPtr<SaveGameSwappableState> currentState = snapshot->states[0];
+ Common::SharedPtr<SaveGameSwappableState> alternateState = snapshot->states[1];
+
+ // Move inventory into the new state
+ alternateState->inventory.clear();
+ alternateState->inventory = Common::move(currentState->inventory);
+
+ // For some reason the screen number translation converts decimal to hex
+ uint highDigit = itemToExamine.itemID / 10u;
+ uint lowDigit = itemToExamine.itemID % 10u;
+
+ Common::HashMap<int, AnimFrameRange>::const_iterator frameRangeIt = _examineAnimIDToFrameRange.find(8700 + static_cast<int>(itemToExamine.itemID));
+
+ if (frameRangeIt == _examineAnimIDToFrameRange.end())
+ error("Couldn't resolve animation frame range to examine item %u", static_cast<uint>(itemToExamine.itemID));
+
+ alternateState->loadedAnimation = frameRangeIt->_value.animationNum;
+ alternateState->animDisplayingFrame = frameRangeIt->_value.firstFrame;
+
+ alternateState->havePendingPostSwapScreenReset = true;
+ alternateState->roomNumber = 87;
+ alternateState->screenNumber = (highDigit * 0x10u) + lowDigit;
+ alternateState->direction = 0;
+
+ alternateState->musicActive = currentState->musicActive;
+ alternateState->musicMuteDisabled = currentState->musicMuteDisabled;
+ alternateState->musicTrack = currentState->musicTrack;
+ alternateState->musicVolume = currentState->musicVolume;
+
+ snapshot->states[0] = alternateState;
+ snapshot->states[1] = currentState;
+
+ snapshot->hero ^= 1u;
+
+ changeToCursor(_cursors[kCursorArrow]);
+
+ _mostRecentValidSaveState = _mostRecentlyRecordedSaveState;
+
+ restoreSaveGameSnapshot();
+}
+
bool Runtime::triggerPreIdleActions() {
debug(1, "Triggering pre-idle actions in room %u screen 0%x facing direction %u", _roomNumber, _screenNumber, _direction);
@@ -3936,6 +4001,17 @@ bool Runtime::dischargeIdleMouseMove() {
invSlotRect.translate(static_cast<int16>(AD2044Interface::getInvSlotSpacing()), 0);
}
+
+ if (_inventoryActiveItem.itemID != 0) {
+ if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].inspectionScreenID != 0) {
+ Common::Rect examineRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton);
+
+ if (examineRect.contains(_mousePos)) {
+ isOnInteraction = true;
+ interactionID = kExamineItemInteractionID;
+ }
+ }
+ }
}
if (_idleIsOnInteraction && (!isOnInteraction || interactionID != _idleInteractionID)) {
@@ -4000,6 +4076,9 @@ bool Runtime::dischargeIdleMouseMove() {
if (interactionID == kHeroChangeInteractionID) {
changeToCursor(_cursors[16]);
_idleHaveClickInteraction = true;
+ } else if (interactionID == kExamineItemInteractionID) {
+ changeToCursor(_cursors[5]);
+ _idleHaveClickInteraction = true;
} else if (interactionID == kObjectDropInteractionID || (interactionID >= kReturnInventorySlot0InteractionID && interactionID <= kReturnInventorySlot5InteractionID)) {
changeToCursor(_cursors[7]);
_idleHaveClickInteraction = true;
@@ -4068,6 +4147,9 @@ bool Runtime::dischargeIdleClick() {
recordSaveGameSnapshot();
_havePendingReturnToIdleState = true;
return true;
+ } else if (_gameID == GID_AD2044 && _idleInteractionID == kExamineItemInteractionID) {
+ changeToExamineItem();
+ return true;
} else if (_gameID == GID_AD2044 && _idleInteractionID >= kPickupInventorySlot0InteractionID && _idleInteractionID <= kPickupInventorySlot5InteractionID) {
pickupInventoryItem(_idleInteractionID - kPickupInventorySlot0InteractionID);
recordSaveGameSnapshot();
@@ -4182,7 +4264,9 @@ void Runtime::loadFrameData2(Common::SeekableReadStream *stream) {
}
}
-void Runtime::loadTabData(uint animNumber, Common::SeekableReadStream *stream) {
+void Runtime::loadTabData(Common::HashMap<int, AnimFrameRange> &animIDToFrameRangeMap, uint animNumber, Common::SeekableReadStream *stream) {
+ animIDToFrameRangeMap.clear();
+
int64 size64 = stream->size() - stream->pos();
if (size64 > UINT_MAX || size64 < 0)
@@ -4268,8 +4352,8 @@ void Runtime::loadTabData(uint animNumber, Common::SeekableReadStream *stream) {
frameRange.lastFrame = static_cast<uint>(parsedNumbers[2]);
// Animation ID 9099 is duplicated but it doesn't really matter since the duplicate is identical
- if (_animIDToFrameRange.find(parsedNumbers[0]) == _animIDToFrameRange.end())
- _animIDToFrameRange[parsedNumbers[0]] = frameRange;
+ if (animIDToFrameRangeMap.find(parsedNumbers[0]) == animIDToFrameRangeMap.end())
+ animIDToFrameRangeMap[parsedNumbers[0]] = frameRange;
else
warning("Animation ID %i was duplicated", parsedNumbers[0]);
}
@@ -4476,14 +4560,11 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
}
if (isAD2044) {
- _animIDToFrameRange.clear();
-
Common::Path tabFileName(Common::String::format("anims/ANIM%04i.TAB", animFile));
Common::File tabFile;
if (tabFile.open(tabFileName))
- loadTabData(animFile, &tabFile);
- tabFile.close();
+ loadTabData(_currentRoomAnimIDToFrameRange, animFile, &tabFile);
}
_loadedAnimationHasSound = (_animDecoder->getAudioTrackCount() > 0);
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index a681e327d61..8ace933bc32 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -918,6 +918,7 @@ private:
void changeToScreen(uint roomNumber, uint screenNumber);
void clearIdleAnimations();
void changeHero();
+ void changeToExamineItem();
bool triggerPreIdleActions();
void returnToIdleState();
void changeToCursor(const Common::SharedPtr<AnimatedCursor> &cursor);
@@ -927,7 +928,7 @@ private:
bool dischargeIdleClick();
void loadFrameData(Common::SeekableReadStream *stream);
void loadFrameData2(Common::SeekableReadStream *stream);
- void loadTabData(uint animNumber, Common::SeekableReadStream *stream);
+ void loadTabData(Common::HashMap<int, AnimFrameRange> &animIDToFrameRangeMap, uint animNumber, Common::SeekableReadStream *stream);
void changeMusicTrack(int musicID);
void startScoreSection();
@@ -1238,6 +1239,7 @@ private:
void scriptOpSay3K(ScriptArg_t arg);
void scriptOpRGet(ScriptArg_t arg);
void scriptOpRSet(ScriptArg_t arg);
+ void scriptOpEndRSet(ScriptArg_t arg);
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursorsShort; // Cursors indexed as CURSOR_#
@@ -1396,7 +1398,8 @@ private:
Common::HashMap<Common::String, uint> _animDefNameToIndex;
// AD2044 animation map
- Common::HashMap<int, AnimFrameRange> _animIDToFrameRange;
+ Common::HashMap<int, AnimFrameRange> _currentRoomAnimIDToFrameRange;
+ Common::HashMap<int, AnimFrameRange> _examineAnimIDToFrameRange;
bool _idleLockInteractions;
bool _idleIsOnInteraction;
@@ -1472,9 +1475,7 @@ private:
static const uint kSoundCacheSize = 16;
- static const uint kHeroChangeInteractionID = 0xffffffffu;
- static const uint kObjectDropInteractionID = 0xfffffffeu;
- static const uint kObjectPickupInteractionID = 0xfffffffdu;
+ static const uint kExamineItemInteractionID = 0xfffffff0u;
static const uint kReturnInventorySlot0InteractionID = 0xfffffff1u;
static const uint kReturnInventorySlot1InteractionID = 0xfffffff2u;
@@ -1490,6 +1491,11 @@ private:
static const uint kPickupInventorySlot4InteractionID = 0xfffffffbu;
static const uint kPickupInventorySlot5InteractionID = 0xfffffffcu;
+ static const uint kObjectPickupInteractionID = 0xfffffffdu;
+ static const uint kObjectDropInteractionID = 0xfffffffeu;
+
+ static const uint kHeroChangeInteractionID = 0xffffffffu;
+
Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
uint _soundCacheIndex;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 8778d61ea26..9a1e3535557 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2056,8 +2056,8 @@ void Runtime::scriptOpAnimT(ScriptArg_t arg) {
StackInt_t animationID = stackArgs[0];
- Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _animIDToFrameRange.find(animationID);
- if (animRangeIt == _animIDToFrameRange.end())
+ Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _currentRoomAnimIDToFrameRange.find(animationID);
+ if (animRangeIt == _currentRoomAnimIDToFrameRange.end())
error("Couldn't resolve animation ID %i", static_cast<int>(animationID));
AnimationDef animDef;
@@ -2094,8 +2094,8 @@ void Runtime::scriptOpAnimAD2044(bool isForward) {
if (!found)
error("Couldn't resolve animation lookup ID %i", static_cast<int>(stackArgs[0]));
- Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _animIDToFrameRange.find(animationID);
- if (animRangeIt == _animIDToFrameRange.end())
+ Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _currentRoomAnimIDToFrameRange.find(animationID);
+ if (animRangeIt == _currentRoomAnimIDToFrameRange.end())
error("Couldn't resolve animation ID %i", static_cast<int>(animationID));
AnimationDef animDef;
@@ -2252,10 +2252,11 @@ void Runtime::scriptOpRSet(ScriptArg_t arg) {
}
}
- // NYI
error("Couldn't resolve item ID for script item %i", static_cast<int>(stackArgs[0]));
}
+OPCODE_STUB(EndRSet)
+
// Unused Schizm ops
// Only used in fnRandomBirds and fnRandomMachines in Room 60, both of which are unused
@@ -2514,6 +2515,7 @@ bool Runtime::runScript() {
DISPATCH_OP(RGet);
DISPATCH_OP(RSet);
+ DISPATCH_OP(EndRSet);
DISPATCH_OP(Say3K);
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index f7ceec3c806..af21395dbe3 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -597,7 +597,9 @@ static ScriptNamedInstruction g_ad2044NamedInstructions[] = {
{"animT", ProtoOp::kProtoOpScript, ScriptOps::kAnimT},
{"ani+", ProtoOp::kProtoOpScript, ScriptOps::kAnimForward},
{"ani-", ProtoOp::kProtoOpScript, ScriptOps::kAnimReverse},
- {"kani+", ProtoOp::kProtoOpScript, ScriptOps::kAnimForward},
+ {"kani+", ProtoOp::kProtoOpScript, ScriptOps::kAnimForward}, // FIXME: Do we really want to use this op?
+ {"anis+", ProtoOp::kProtoOpScript, ScriptOps::kAnimForward}, // FIXME: Do we really want to use this op?
+ {"anis-", ProtoOp::kProtoOpScript, ScriptOps::kAnimReverse}, // FIXME: Do we really want to use this op?
//{"static", ProtoOp::kProtoOpScript, ScriptOps::kStatic},
{"yes@", ProtoOp::kProtoOpScript, ScriptOps::kVarLoad},
{"yes!", ProtoOp::kProtoOpScript, ScriptOps::kVarStore},
@@ -611,6 +613,7 @@ static ScriptNamedInstruction g_ad2044NamedInstructions[] = {
//{"r!", ProtoOp::kProtoOpScript, ScriptOps::kItemAdd},
{"r@", ProtoOp::kProtoOpScript, ScriptOps::kRGet},
{"r!", ProtoOp::kProtoOpScript, ScriptOps::kRSet},
+ {"endr!", ProtoOp::kProtoOpScript, ScriptOps::kEndRSet},
//{"clearPocket", ProtoOp::kProtoOpScript, ScriptOps::kItemClear},
{"cursor!", ProtoOp::kProtoOpScript, ScriptOps::kSetCursor},
{"room!", ProtoOp::kProtoOpScript, ScriptOps::kSetRoom},
@@ -685,6 +688,8 @@ static ScriptNamedInstruction g_ad2044NamedInstructions[] = {
//{"goto", ProtoOp::kProtoOpScript, ScriptOps::kGoto},
+ {"stop", ProtoOp::kProtoOpScript, ScriptOps::kStop},
+
{"#if", ProtoOp::kProtoOpIf, ScriptOps::kInvalid},
{"#eif", ProtoOp::kProtoOpEndIf, ScriptOps::kInvalid},
{"#else", ProtoOp::kProtoOpElse, ScriptOps::kInvalid},
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 0900991c8fa..0d2070e6aa0 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -242,6 +242,8 @@ enum ScriptOp {
kSay3K,
kRGet,
kRSet,
+ kEndRSet,
+ kStop,
kNumOps,
};
Commit: ffa36e18982df8930b01d63685a166d684e10f8d
https://github.com/scummvm/scummvm/commit/ffa36e18982df8930b01d63685a166d684e10f8d
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:29-04:00
Commit Message:
VCRUISE: Fix MIDI crash when restarting the game
Changed paths:
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index e88bce1c440..5267899759f 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -267,6 +267,9 @@ Common::Error VCruiseEngine::run() {
_runtime.reset();
+ if (midiDrv)
+ midiDrv->close();
+
if (_gameDescription->desc.flags & VCRUISE_GF_GENTEE_PACKAGE)
SearchMan.remove("VCruiseInstallerPackage");
Commit: 774a7dfac325aa8a373aaad78b7abbf65fcbedf6
https://github.com/scummvm/scummvm/commit/774a7dfac325aa8a373aaad78b7abbf65fcbedf6
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:30-04:00
Commit Message:
VCRUISE: Avoid restarting music if the track didn't change
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 90baa6261ba..466669657ad 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -4360,7 +4360,7 @@ void Runtime::loadTabData(Common::HashMap<int, AnimFrameRange> &animIDToFrameRan
}
void Runtime::changeMusicTrack(int track) {
- if (track == _musicTrack && _musicWavePlayer.get() != nullptr && _musicMidiPlayer.get() != nullptr)
+ if (track == _musicTrack && (_musicWavePlayer.get() != nullptr || _musicMidiPlayer.get() != nullptr))
return;
_musicWavePlayer.reset();
@@ -4379,9 +4379,12 @@ void Runtime::changeMusicTrack(int track) {
Common::String musicPathStr;
- if (_gameID == GID_AD2044)
+ if (_gameID == GID_AD2044) {
+ if (!_midiDrv)
+ return;
+
musicPathStr = Common::String::format("sfx/music%02i.mid", static_cast<int>(track));
- else
+ } else
musicPathStr = Common::String::format("Sfx/Music-%02i.wav", static_cast<int>(track));
Common::Path musicFileName(musicPathStr);
Commit: e9000d2a8ea06121fd0b1f90c855b4a58fb3151f
https://github.com/scummvm/scummvm/commit/e9000d2a8ea06121fd0b1f90c855b4a58fb3151f
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:30-04:00
Commit Message:
VCRUISE: Add return from item examination
Changed paths:
engines/vcruise/ad2044_items.cpp
engines/vcruise/ad2044_items.h
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/ad2044_items.cpp b/engines/vcruise/ad2044_items.cpp
index 216ef7f86e9..d69fc298610 100644
--- a/engines/vcruise/ad2044_items.cpp
+++ b/engines/vcruise/ad2044_items.cpp
@@ -24,80 +24,80 @@
namespace VCruise {
const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items] = {
- {0, 0, 0, 0}, // 0
- {0, 0, 0, 0}, // 1
- {0, 0, 0, 0}, // 2
- {0, 0, 0, 0}, // 3
- {0, 0, 0, 0}, // 4
- {0, 0, 0, 0}, // 5
- {0, 0, 0, 0}, // 6
- {0, 0, 0, 0}, // 7
- {0, 0, 0, 0}, // 8
- {0, 0, 0, 0}, // 9
- {0, 0, 0, 0}, // 10
- {0, 0, 0, 0}, // 11
- {0, 0, 0, 0}, // 12
- {0, 0, 0, 0}, // 13
- {0, 0, 0, 0}, // 14
- {0, 0, 0, 0}, // 15
- {0, 0, 0, 0}, // 16
- {0, 0, 0, 0}, // 17
- {0, 0, 0x18, 0x128}, // 18
- {0, 0, 0, 0}, // 19
- {0, 0, 0, 0}, // 20
- {0, 0, 0, 0}, // 21
- {0, 0, 0, 0}, // 22
- {0, 0, 0, 0}, // 23
- {0, 0, 0, 0}, // 24
- {0, 0, 0, 0}, // 25
- {0, 0, 0, 0}, // 26
- {0, 0, 0, 0}, // 27
- {0, 0, 0, 0}, // 28
- {0, 0, 0, 0}, // 29
- {0, 0, 0, 0}, // 30
- {0, 0, 0, 0}, // 31
- {0, 0, 0, 0}, // 32
- {0, 0, 0, 0}, // 33
- {0, 0, 0, 0}, // 34
- {0, 0, 0, 0}, // 35
- {0, 0, 0, 0}, // 36
- {0, 0, 0, 0}, // 37
- {0, 0, 0, 0}, // 38
- {0, 0, 0, 0}, // 39
- {0, 0, 0, 0}, // 40
- {0, 0, 0, 0}, // 41
- {0, 0, 0, 0}, // 42
- {0, 0, 0, 0}, // 43
- {0, 0, 0, 0}, // 44
- {0, 0, 0, 0}, // 45
- {0, 0, 0, 0}, // 46
- {0, 0, 0, 0}, // 47
- {0, 0, 0, 0}, // 48
- {0, 0, 0, 0}, // 49
- {0, 0, 0, 0}, // 50
- {0, 0, 0, 0}, // 51
- {0, 0, 0, 0}, // 52
- {0, 0, 0, 0}, // 53
+ {0, 0, false, 0}, // 0
+ {0, 0, false, 0}, // 1
+ {0, 0, false, 0}, // 2
+ {0, 0, false, 0}, // 3
+ {0, 0, false, 0}, // 4
+ {0, 0, false, 0}, // 5
+ {0, 0, false, 0}, // 6
+ {0, 0, false, 0}, // 7
+ {0, 0, false, 0}, // 8
+ {0, 0, false, 0}, // 9
+ {0, 0, false, 0}, // 10
+ {0, 0, false, 0}, // 11
+ {0, 0, false, 0}, // 12
+ {0, 0, false, 0}, // 13
+ {0, 0, false, 0}, // 14
+ {0, 0, false, 0}, // 15
+ {0, 0, false, 0}, // 16
+ {0, 0, false, 0}, // 17
+ {0, 0, true, 0x128}, // 18
+ {0, 0, false, 0}, // 19
+ {0, 0, false, 0}, // 20
+ {0, 0, false, 0}, // 21
+ {0, 0, false, 0}, // 22
+ {0, 0, false, 0}, // 23
+ {0, 0, false, 0}, // 24
+ {0, 0, false, 0}, // 25
+ {0, 0, false, 0}, // 26
+ {0, 0, false, 0}, // 27
+ {0, 0, false, 0}, // 28
+ {0, 0, false, 0}, // 29
+ {0, 0, false, 0}, // 30
+ {0, 0, false, 0}, // 31
+ {0, 0, false, 0}, // 32
+ {0, 0, false, 0}, // 33
+ {0, 0, false, 0}, // 34
+ {0, 0, false, 0}, // 35
+ {0, 0, false, 0}, // 36
+ {0, 0, false, 0}, // 37
+ {0, 0, false, 0}, // 38
+ {0, 0, false, 0}, // 39
+ {0, 0, false, 0}, // 40
+ {0, 0, false, 0}, // 41
+ {0, 0, false, 0}, // 42
+ {0, 0, false, 0}, // 43
+ {0, 0, false, 0}, // 44
+ {0, 0, false, 0}, // 45
+ {0, 0, false, 0}, // 46
+ {0, 0, false, 0}, // 47
+ {0, 0, false, 0}, // 48
+ {0, 0, false, 0}, // 49
+ {0, 0, false, 0}, // 50
+ {0, 0, false, 0}, // 51
+ {0, 0, false, 0}, // 52
+ {0, 0, false, 0}, // 53
{0x83d54448, 0x839911EF, 0, 0}, // 54
- {0, 0, 0, 0}, // 55
- {0, 0, 0, 0}, // 56
- {0, 0, 0, 0}, // 57
- {0, 0, 0, 0}, // 58
- {0, 0, 0, 0}, // 59
- {0, 0, 0, 0x170}, // 60
- {0, 0, 0, 0}, // 61
- {0, 0, 0, 0}, // 62
- {0, 0, 0, 0}, // 63
- {0, 0, 0, 0}, // 64
- {0, 0, 0, 0}, // 65
- {0, 0, 0, 0}, // 66
- {0, 0, 0, 0}, // 67
- {0, 0, 0, 0}, // 68
- {0, 0, 0, 0}, // 69
- {0, 0, 0, 0}, // 70
- {0, 0, 0, 0}, // 71
- {0, 0, 0, 0}, // 72
- {0, 0, 0, 0}, // 73
+ {0, 0, false, 0}, // 55
+ {0, 0, false, 0}, // 56
+ {0, 0, false, 0}, // 57
+ {0, 0, false, 0}, // 58
+ {0, 0, false, 0}, // 59
+ {0, 0, false, 0x170}, // 60
+ {0, 0, false, 0}, // 61
+ {0, 0, false, 0}, // 62
+ {0, 0, false, 0}, // 63
+ {0, 0, false, 0}, // 64
+ {0, 0, false, 0}, // 65
+ {0, 0, false, 0}, // 66
+ {0, 0, false, 0}, // 67
+ {0, 0, false, 0}, // 68
+ {0, 0, false, 0}, // 69
+ {0, 0, false, 0}, // 70
+ {0, 0, false, 0}, // 71
+ {0, 0, false, 0}, // 72
+ {0, 0, false, 0}, // 73
};
} // End of namespace VCruise
diff --git a/engines/vcruise/ad2044_items.h b/engines/vcruise/ad2044_items.h
index fbb6dcb86eb..a7a506f959e 100644
--- a/engines/vcruise/ad2044_items.h
+++ b/engines/vcruise/ad2044_items.h
@@ -29,7 +29,7 @@ namespace VCruise {
struct AD2044ItemInfo {
uint32 enNameCRC;
uint32 plNameCRC;
- uint16 inspectionScreenID;
+ bool canBeExamined;
uint16 scriptItemID;
};
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 466669657ad..fa56a98c317 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -3724,6 +3724,7 @@ void Runtime::changeHero() {
void Runtime::changeToExamineItem() {
assert(canSave(true));
+ assert(_hero == 0);
InventoryItem itemToExamine = _inventoryActiveItem;
@@ -3774,6 +3775,39 @@ void Runtime::changeToExamineItem() {
restoreSaveGameSnapshot();
}
+void Runtime::returnFromExaminingItem() {
+ assert(canSave(true));
+ assert(_hero == 1);
+
+ InventoryItem itemToReturnWith = _inventoryActiveItem;
+
+ _inventoryActiveItem = InventoryItem();
+
+ recordSaveGameSnapshot();
+
+ SaveGameSnapshot *snapshot = _mostRecentlyRecordedSaveState.get();
+
+ Common::SharedPtr<SaveGameSwappableState> currentState = snapshot->states[0];
+ Common::SharedPtr<SaveGameSwappableState> alternateState = snapshot->states[1];
+
+ // Move inventory into the new state
+ alternateState->inventory.clear();
+ alternateState->inventory = Common::move(currentState->inventory);
+
+ snapshot->inventoryActiveItem = itemToReturnWith.itemID;
+
+ snapshot->states[0] = alternateState;
+ snapshot->states[1] = currentState;
+
+ snapshot->hero ^= 1u;
+
+ changeToCursor(_cursors[kCursorArrow]);
+
+ _mostRecentValidSaveState = _mostRecentlyRecordedSaveState;
+
+ restoreSaveGameSnapshot();
+}
+
bool Runtime::triggerPreIdleActions() {
debug(1, "Triggering pre-idle actions in room %u screen 0%x facing direction %u", _roomNumber, _screenNumber, _direction);
@@ -4003,7 +4037,7 @@ bool Runtime::dischargeIdleMouseMove() {
}
if (_inventoryActiveItem.itemID != 0) {
- if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].inspectionScreenID != 0) {
+ if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].canBeExamined) {
Common::Rect examineRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton);
if (examineRect.contains(_mousePos)) {
@@ -5912,7 +5946,7 @@ void Runtime::drawActiveItemGraphic() {
drawSectionToScreen(_fullscreenMenuSection, itemRect);
}
- if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].inspectionScreenID != 0) {
+ if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].canBeExamined) {
Common::Rect examineRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton);
_fullscreenMenuSection.surf->blitFrom(*_ad2044Graphics->examine, Common::Point(examineRect.left, examineRect.top));
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 8ace933bc32..c7befb00feb 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -919,6 +919,7 @@ private:
void clearIdleAnimations();
void changeHero();
void changeToExamineItem();
+ void returnFromExaminingItem();
bool triggerPreIdleActions();
void returnToIdleState();
void changeToCursor(const Common::SharedPtr<AnimatedCursor> &cursor);
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 9a1e3535557..a3836df6667 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -2255,7 +2255,11 @@ void Runtime::scriptOpRSet(ScriptArg_t arg) {
error("Couldn't resolve item ID for script item %i", static_cast<int>(stackArgs[0]));
}
-OPCODE_STUB(EndRSet)
+void Runtime::scriptOpEndRSet(ScriptArg_t arg) {
+ scriptOpRSet(arg);
+
+ returnFromExaminingItem();
+}
// Unused Schizm ops
Commit: 0867fd912639e56ee9761e630d7ee47ef2e53940
https://github.com/scummvm/scummvm/commit/0867fd912639e56ee9761e630d7ee47ef2e53940
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:30-04:00
Commit Message:
VCRUISE: Add say cycle ops and some item infos
Changed paths:
engines/vcruise/ad2044_items.cpp
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/ad2044_items.cpp b/engines/vcruise/ad2044_items.cpp
index d69fc298610..dc043cb18a6 100644
--- a/engines/vcruise/ad2044_items.cpp
+++ b/engines/vcruise/ad2044_items.cpp
@@ -41,17 +41,17 @@ const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items] = {
{0, 0, false, 0}, // 14
{0, 0, false, 0}, // 15
{0, 0, false, 0}, // 16
- {0, 0, false, 0}, // 17
- {0, 0, true, 0x128}, // 18
+ {0, 0, false, 0}, // 17
+ {0x3D70D2EC, 0x98382A38, true, 0x128}, // 18 spoon
{0, 0, false, 0}, // 19
{0, 0, false, 0}, // 20
{0, 0, false, 0}, // 21
{0, 0, false, 0}, // 22
{0, 0, false, 0}, // 23
- {0, 0, false, 0}, // 24
+ {0, 0, true, 0x134}, // 24 cigarettes (filled)
{0, 0, false, 0}, // 25
{0, 0, false, 0}, // 26
- {0, 0, false, 0}, // 27
+ {0, 0, true, 0x137}, // 27 matches
{0, 0, false, 0}, // 28
{0, 0, false, 0}, // 29
{0, 0, false, 0}, // 30
@@ -78,13 +78,13 @@ const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items] = {
{0, 0, false, 0}, // 51
{0, 0, false, 0}, // 52
{0, 0, false, 0}, // 53
- {0x83d54448, 0x839911EF, 0, 0}, // 54
+ {0x83d54448, 0x839911EF, 0, 0}, // 54 goaler (sic)
{0, 0, false, 0}, // 55
{0, 0, false, 0}, // 56
{0, 0, false, 0}, // 57
{0, 0, false, 0}, // 58
{0, 0, false, 0}, // 59
- {0, 0, false, 0x170}, // 60
+ {0, 0, false, 0x170}, // 60 mirror
{0, 0, false, 0}, // 61
{0, 0, false, 0}, // 62
{0, 0, false, 0}, // 63
@@ -92,12 +92,12 @@ const AD2044ItemInfo g_ad2044ItemInfos[kNumAD2044Items] = {
{0, 0, false, 0}, // 65
{0, 0, false, 0}, // 66
{0, 0, false, 0}, // 67
- {0, 0, false, 0}, // 68
- {0, 0, false, 0}, // 69
+ {0, 0, true, 0x178}, // 68 cigarette
+ {0, 0, true, 0x179}, // 69 cigarette (lit)
{0, 0, false, 0}, // 70
{0, 0, false, 0}, // 71
{0, 0, false, 0}, // 72
- {0, 0, false, 0}, // 73
+ {0, 0, true, 0x183}, // 73 cigarettes (1 missing)
};
} // End of namespace VCruise
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index fa56a98c317..b1bdfcbfd2f 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -3179,14 +3179,16 @@ void Runtime::loadAD2044ExecutableResources() {
_ad2044Graphics->finishLoading();
- Common::HashMap<uint32, uint32> stringHashToFilePos;
+ Common::HashMap<uint32, Common::Pair<uint32, uint> > stringHashToFilePosAndLength;
for (const AD2044ItemInfo &itemInfo : g_ad2044ItemInfos) {
- stringHashToFilePos[itemInfo.enNameCRC] = 0;
- stringHashToFilePos[itemInfo.plNameCRC] = 0;
+ if (_language == Common::PL_POL)
+ stringHashToFilePosAndLength[itemInfo.plNameCRC] = Common::Pair<uint32, uint>(0, 0);
+ else
+ stringHashToFilePosAndLength[itemInfo.enNameCRC] = Common::Pair<uint32, uint>(0, 0);
}
- stringHashToFilePos.erase(0);
+ stringHashToFilePosAndLength.erase(0);
// Scan for strings
Common::CRC32 crc;
@@ -3200,9 +3202,9 @@ void Runtime::loadAD2044ExecutableResources() {
uint32 strLength = i - strStartPos;
rollingCRC = crc.finalize(rollingCRC);
if (strLength != 0) {
- Common::HashMap<uint32, uint32>::iterator it = stringHashToFilePos.find(rollingCRC);
- if (it != stringHashToFilePos.end())
- it->_value = strStartPos;
+ Common::HashMap<uint32, Common::Pair<uint32, uint> >::iterator it = stringHashToFilePosAndLength.find(rollingCRC);
+ if (it != stringHashToFilePosAndLength.end())
+ it->_value = Common::Pair<uint32, uint> (strStartPos, strLength);
}
#if 1
@@ -3216,6 +3218,34 @@ void Runtime::loadAD2044ExecutableResources() {
} else
rollingCRC = crc.processByte(b, rollingCRC);
}
+
+ _ad2044ItemNames.clear();
+ _ad2044ItemNames.reserve(ARRAYSIZE(g_ad2044ItemInfos));
+
+ for (const AD2044ItemInfo &itemInfo : g_ad2044ItemInfos) {
+ Common::String itemInfoUTF8;
+ uint32 hash = itemInfo.enNameCRC;
+
+ if (_language == Common::PL_POL)
+ hash = itemInfo.plNameCRC;
+
+ if (hash != 0) {
+ Common::HashMap<uint32, Common::Pair<uint32, uint> >::const_iterator strIt = stringHashToFilePosAndLength.find(hash);
+
+ if (strIt != stringHashToFilePosAndLength.end()) {
+ uint32 filePos = strIt->_value.first;
+ uint length = strIt->_value.second;
+
+ if (length > 0) {
+ Common::String str(reinterpret_cast<const char *>(&exeContents[filePos]), length);
+
+ itemInfoUTF8 = str.decode(Common::CodePage::kWindows1250).encode(Common::kUtf8);
+ }
+ }
+ }
+
+ _ad2044ItemNames.push_back(itemInfoUTF8);
+ }
}
void Runtime::findWaves() {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index c7befb00feb..1dad02e42d8 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -1236,11 +1236,13 @@ private:
void scriptOpSound(ScriptArg_t arg);
void scriptOpISound(ScriptArg_t arg);
void scriptOpUSound(ScriptArg_t arg);
+ void scriptOpSayCycle_AD2044(const StackInt_t *values, uint numValues);
void scriptOpSay2K(ScriptArg_t arg);
void scriptOpSay3K(ScriptArg_t arg);
void scriptOpRGet(ScriptArg_t arg);
void scriptOpRSet(ScriptArg_t arg);
void scriptOpEndRSet(ScriptArg_t arg);
+ void scriptOpStop(ScriptArg_t arg);
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<AnimatedCursor> > _cursorsShort; // Cursors indexed as CURSOR_#
@@ -1544,6 +1546,7 @@ private:
Common::String _subtitleText;
Common::SharedPtr<AD2044Graphics> _ad2044Graphics;
+ Common::Array<Common::String> _ad2044ItemNames;
};
} // End of namespace VCruise
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index a3836df6667..5eb06e961d8 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -30,6 +30,23 @@
namespace VCruise {
+struct AD2044UnusualAnimationRules {
+ enum Type {
+ kTypeLoop, // Loop the animation
+ kTypePlayFirstFrameOnly,
+ };
+
+ uint roomNumber;
+ uint screenNumber;
+ uint interactionID;
+ uint8 animLookupID;
+ Type ruleType;
+};
+
+AD2044UnusualAnimationRules g_unusualAnimationRules[] = {
+ {87, 0x69, 0xa3, 0xa5, AD2044UnusualAnimationRules::kTypePlayFirstFrameOnly},
+};
+
#ifdef PEEK_STACK
#error "PEEK_STACK is already defined"
#endif
@@ -2084,7 +2101,7 @@ void Runtime::scriptOpAnimAD2044(bool isForward) {
bool found = false;
for (const AD2044AnimationDef &def : _ad2044AnimationDefs) {
- if (static_cast<StackInt_t>(def.lookupID) == stackArgs[0]) {
+ if (def.roomID == _roomNumber && static_cast<StackInt_t>(def.lookupID) == stackArgs[0]) {
animationID = isForward ? def.fwdAnimationID : def.revAnimationID;
found = true;
break;
@@ -2127,13 +2144,46 @@ void Runtime::scriptOpAnimReverse(ScriptArg_t arg) {
}
OPCODE_STUB(AnimKForward)
-OPCODE_STUB(Say2K)
-OPCODE_STUB(Say3K)
void Runtime::scriptOpNoUpdate(ScriptArg_t arg) {
}
-OPCODE_STUB(NoClear)
+void Runtime::scriptOpNoClear(ScriptArg_t arg) {
+}
+
+void Runtime::scriptOpSayCycle_AD2044(const StackInt_t *values, uint numValues) {
+ // Checking the scripts, there don't appear to be any cycles that can't be tracked from
+ // the first value, so just use that.
+ uint &cyclePosRef = _sayCycles[static_cast<uint32>(values[0])];
+
+ Common::String soundName = Common::String::format("%02i-%08i", static_cast<int>(_disc * 10u + 1u), static_cast<int>(values[cyclePosRef]));
+
+ cyclePosRef = (cyclePosRef + 1u) % numValues;
+
+ StackInt_t soundID = 0;
+ SoundInstance *cachedSound = nullptr;
+ resolveSoundByName(soundName, true, soundID, cachedSound);
+
+ if (cachedSound) {
+ TriggeredOneShot oneShot;
+ oneShot.soundID = soundID;
+ oneShot.uniqueSlot = _disc;
+
+ triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
+ }
+}
+
+void Runtime::scriptOpSay2K(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ scriptOpSayCycle_AD2044(stackArgs, 2);
+}
+
+void Runtime::scriptOpSay3K(ScriptArg_t arg) {
+ TAKE_STACK_INT(3);
+
+ scriptOpSayCycle_AD2044(stackArgs, 3);
+}
void Runtime::scriptOpSay1_AD2044(ScriptArg_t arg) {
TAKE_STACK_INT(1);
@@ -2252,7 +2302,7 @@ void Runtime::scriptOpRSet(ScriptArg_t arg) {
}
}
- error("Couldn't resolve item ID for script item %i", static_cast<int>(stackArgs[0]));
+ error("Couldn't resolve item ID for script item 0x%x", static_cast<int>(stackArgs[0]));
}
void Runtime::scriptOpEndRSet(ScriptArg_t arg) {
@@ -2261,6 +2311,9 @@ void Runtime::scriptOpEndRSet(ScriptArg_t arg) {
returnFromExaminingItem();
}
+void Runtime::scriptOpStop(ScriptArg_t arg) {
+ terminateScript();
+}
// Unused Schizm ops
// Only used in fnRandomBirds and fnRandomMachines in Room 60, both of which are unused
@@ -2521,7 +2574,9 @@ bool Runtime::runScript() {
DISPATCH_OP(RSet);
DISPATCH_OP(EndRSet);
+ DISPATCH_OP(Say2K);
DISPATCH_OP(Say3K);
+ DISPATCH_OP(Stop);
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
Commit: fa2dfbcd7be897498759ea800a4628cd5a4fd123
https://github.com/scummvm/scummvm/commit/fa2dfbcd7be897498759ea800a4628cd5a4fd123
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:30-04:00
Commit Message:
VCRUISE: Disallow examining while already examining
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index b1bdfcbfd2f..7e49602c0b0 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -4066,7 +4066,7 @@ bool Runtime::dischargeIdleMouseMove() {
invSlotRect.translate(static_cast<int16>(AD2044Interface::getInvSlotSpacing()), 0);
}
- if (_inventoryActiveItem.itemID != 0) {
+ if (_inventoryActiveItem.itemID != 0 && _hero == 0) {
if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].canBeExamined) {
Common::Rect examineRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton);
@@ -5976,7 +5976,7 @@ void Runtime::drawActiveItemGraphic() {
drawSectionToScreen(_fullscreenMenuSection, itemRect);
}
- if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].canBeExamined) {
+ if (g_ad2044ItemInfos[_inventoryActiveItem.itemID].canBeExamined && _hero == 0) {
Common::Rect examineRect = AD2044Interface::getRectForUI(AD2044InterfaceRectID::ExamineButton);
_fullscreenMenuSection.surf->blitFrom(*_ad2044Graphics->examine, Common::Point(examineRect.left, examineRect.top));
Commit: b66044f2e7bea2f18c2305eb53f2013f97edbe55
https://github.com/scummvm/scummvm/commit/b66044f2e7bea2f18c2305eb53f2013f97edbe55
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T14:39:30-04:00
Commit Message:
VCRUISE: Add some more handling of unusual animations
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/runtime_scriptexec.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 7e49602c0b0..c25e9f94169 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -549,7 +549,7 @@ void AD2044MapLoader::unload() {
ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), lmbDrag(false), esc(false), exitToMenu(false), animChangeSet(false), isEntryScript(false), puzzleWasSet(false),
- panInteractionID(0), fpsOverride(0), lastHighlightedItem(0), animChangeFrameOffset(0), animChangeNumFrames(0) {
+ panInteractionID(0), clickInteractionID(0), fpsOverride(0), lastHighlightedItem(0), animChangeFrameOffset(0), animChangeNumFrames(0) {
}
OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)), keymappedEvent(kKeymappedEventNone), timestamp(0) {
@@ -4154,7 +4154,9 @@ bool Runtime::dischargeIdleMouseMove() {
Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
if (script) {
- activateScript(script, false, ScriptEnvironmentVars());
+ ScriptEnvironmentVars envVars;
+ envVars.clickInteractionID = interactionID;
+ activateScript(script, false, envVars);
return true;
}
}
@@ -4186,6 +4188,7 @@ bool Runtime::dischargeIdleMouseDown() {
if (script) {
ScriptEnvironmentVars vars;
vars.lmbDrag = true;
+ vars.clickInteractionID = _idleInteractionID;
activateScript(script, false, vars);
return true;
@@ -4233,6 +4236,7 @@ bool Runtime::dischargeIdleClick() {
if (script) {
ScriptEnvironmentVars vars;
vars.lmb = true;
+ vars.clickInteractionID = _idleInteractionID;
activateScript(script, false, vars);
return true;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 1dad02e42d8..d720952dc7e 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -179,6 +179,7 @@ struct ScriptEnvironmentVars {
ScriptEnvironmentVars();
uint panInteractionID;
+ uint clickInteractionID;
uint fpsOverride;
uint lastHighlightedItem;
uint animChangeFrameOffset;
diff --git a/engines/vcruise/runtime_scriptexec.cpp b/engines/vcruise/runtime_scriptexec.cpp
index 5eb06e961d8..71873636418 100644
--- a/engines/vcruise/runtime_scriptexec.cpp
+++ b/engines/vcruise/runtime_scriptexec.cpp
@@ -34,6 +34,7 @@ struct AD2044UnusualAnimationRules {
enum Type {
kTypeLoop, // Loop the animation
kTypePlayFirstFrameOnly,
+ kTypeSkip,
};
uint roomNumber;
@@ -44,7 +45,10 @@ struct AD2044UnusualAnimationRules {
};
AD2044UnusualAnimationRules g_unusualAnimationRules[] = {
- {87, 0x69, 0xa3, 0xa5, AD2044UnusualAnimationRules::kTypePlayFirstFrameOnly},
+ // Room, screen, interaction, animation lookup ID, rule
+ {87, 0x24, 0xa4, 0xa7, AD2044UnusualAnimationRules::kTypePlayFirstFrameOnly}, // Taking cigarette, don't play box spin
+ {87, 0x68, 0xa3, 0xa5, AD2044UnusualAnimationRules::kTypeLoop}, // Loop lit cigarette animation
+ {87, 0x69, 0xa3, 0xa5, AD2044UnusualAnimationRules::kTypeSkip}, // Taking lit cigarette, don't play cycling animation
};
#ifdef PEEK_STACK
@@ -2120,6 +2124,19 @@ void Runtime::scriptOpAnimAD2044(bool isForward) {
animDef.firstFrame = animRangeIt->_value.firstFrame;
animDef.lastFrame = animRangeIt->_value.lastFrame;
+ for (const AD2044UnusualAnimationRules &unusualAnimRule : g_unusualAnimationRules) {
+ if (static_cast<StackInt_t>(unusualAnimRule.animLookupID) == stackArgs[0] && unusualAnimRule.interactionID == _scriptEnv.clickInteractionID && unusualAnimRule.roomNumber == _roomNumber && unusualAnimRule.screenNumber == _screenNumber) {
+ switch (unusualAnimRule.ruleType) {
+ case AD2044UnusualAnimationRules::kTypePlayFirstFrameOnly:
+ animDef.lastFrame = animDef.firstFrame;
+ break;
+ default:
+ error("Unknown unusual animation rule");
+ }
+ break;
+ }
+ }
+
changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
_gameState = kGameStateWaitingForAnimation;
Commit: 05da459107a838cec49e4b2d6e446b2f11eed309
https://github.com/scummvm/scummvm/commit/05da459107a838cec49e4b2d6e446b2f11eed309
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-03-31T15:12:05-04:00
Commit Message:
VCRUISE: Fix C++11 narrowing conversion warning
Changed paths:
engines/vcruise/midi_player.cpp
diff --git a/engines/vcruise/midi_player.cpp b/engines/vcruise/midi_player.cpp
index cf464122cbd..4d104719770 100644
--- a/engines/vcruise/midi_player.cpp
+++ b/engines/vcruise/midi_player.cpp
@@ -65,7 +65,7 @@ void MidiPlayer::setVolume(int volume) {
effectiveValue = 0x3fffu;
byte masterVolMessage[6] = {
- 0x7f, 0x00, 0x04, 0x01, (effectiveValue & 0x7f), ((effectiveValue >> 7) & 0x7f)
+ 0x7f, 0x00, 0x04, 0x01, static_cast<byte>(effectiveValue & 0x7f), static_cast<byte>((effectiveValue >> 7) & 0x7f)
};
_midiDrv->sysEx(masterVolMessage, 6);
More information about the Scummvm-git-logs
mailing list