From ea7eaa4a158bb8917eb78b4724eb84bf4fe038bc Mon Sep 17 00:00:00 2001 From: elasota Date: Fri, 6 May 2022 17:19:22 -0400 Subject: [PATCH] MTROPOLIS: Add mToon assets and elements and fix some things --- engines/mtropolis/asset_factory.cpp | 2 + engines/mtropolis/assets.cpp | 56 +++++++++ engines/mtropolis/assets.h | 50 ++++++++ engines/mtropolis/data.cpp | 110 ++++++++++++++++- engines/mtropolis/data.h | 159 ++++++++++++++++++++++--- engines/mtropolis/element_factory.cpp | 2 + engines/mtropolis/elements.cpp | 39 +++++- engines/mtropolis/elements.h | 31 +++++ engines/mtropolis/miniscript.cpp | 23 +++- engines/mtropolis/miniscript.h | 1 + engines/mtropolis/modifier_factory.cpp | 2 + engines/mtropolis/modifiers.cpp | 47 ++++++++ engines/mtropolis/modifiers.h | 26 ++++ engines/mtropolis/runtime.cpp | 20 +++- engines/mtropolis/runtime.h | 4 +- 15 files changed, 550 insertions(+), 22 deletions(-) diff --git a/engines/mtropolis/asset_factory.cpp b/engines/mtropolis/asset_factory.cpp index 95b3e9c8655..1441be568e9 100644 --- a/engines/mtropolis/asset_factory.cpp +++ b/engines/mtropolis/asset_factory.cpp @@ -65,6 +65,8 @@ IAssetFactory *getAssetFactoryForDataObjectType(const Data::DataObjectTypes::Dat return AssetFactory::getInstance(); case Data::DataObjectTypes::kImageAsset: return AssetFactory::getInstance(); + case Data::DataObjectTypes::kMToonAsset: + return AssetFactory::getInstance(); case Data::DataObjectTypes::kTextAsset: return AssetFactory::getInstance(); diff --git a/engines/mtropolis/assets.cpp b/engines/mtropolis/assets.cpp index 4fbb4e02695..b2180417a5e 100644 --- a/engines/mtropolis/assets.cpp +++ b/engines/mtropolis/assets.cpp @@ -194,6 +194,62 @@ ImageAsset::ImageFormat ImageAsset::getImageFormat() const { return _imageFormat; } +bool MToonAsset::load(AssetLoaderContext &context, const Data::MToonAsset &data) { + if (data.haveMacPart) + _imageFormat = kImageFormatMac; + else if (data.haveWinPart) + _imageFormat = kImageFormatWindows; + else + return false; + + _frameDataPosition = data.frameDataPosition; + _sizeOfFrameData = data.sizeOfFrameData; + + if (!_rect.load(data.rect)) + return false; + + _bitsPerPixel = data.bitsPerPixel; + _codecID = data.codecID; + + _frames.resize(data.frames.size()); + for (size_t i = 0; i < _frames.size(); i++) { + if (!_frames[i].load(context, data.frames[i])) + return false; + } + + _frameRanges.resize(data.frameRangesPart.frameRanges.size()); + for (size_t i = 0; i < _frameRanges.size(); i++) { + if (!_frameRanges[i].load(context, data.frameRangesPart.frameRanges[i])) + return false; + } + + _codecData = data.codecData; +} + +AssetType MToonAsset::getAssetType() const { + return kAssetTypeMToon; +} + +bool MToonAsset::FrameDef::load(AssetLoaderContext &context, const Data::MToonAsset::FrameDef &data) { + compressedSize = data.compressedSize; + dataOffset = data.dataOffset; + decompressedBytesPerRow = data.decompressedBytesPerRow; + decompressedSize = data.decompressedSize; + isKeyFrame = (data.keyframeFlag != 0); + if (!rect.load(data.rect1)) + return false; + + return true; +} + +bool MToonAsset::FrameRangeDef::load(AssetLoaderContext &context, const Data::MToonAsset::FrameRangeDef &data) { + name = data.name; + startFrame = data.startFrame; + endFrame = data.endFrame; + + return true; +} + bool TextAsset::load(AssetLoaderContext &context, const Data::TextAsset &data) { _assetID = data.assetID; diff --git a/engines/mtropolis/assets.h b/engines/mtropolis/assets.h index 0ed30d2aff5..ff8ce24fe5d 100644 --- a/engines/mtropolis/assets.h +++ b/engines/mtropolis/assets.h @@ -105,6 +105,8 @@ public: size_t getStreamIndex() const; ImageFormat getImageFormat() const; + const Common::SharedPtr &loadContent(); + private: Rect16 _rect; ColorDepthMode _colorDepth; @@ -112,6 +114,54 @@ private: uint32 _size; size_t _streamIndex; ImageFormat _imageFormat; + + Common::SharedPtr _surface; +}; + + +struct MToonAsset : public Asset { +public: + bool load(AssetLoaderContext &context, const Data::MToonAsset &data); + AssetType getAssetType() const override; + + enum ImageFormat { + kImageFormatMac, + kImageFormatWindows, + }; + + struct FrameDef { + Rect16 rect; + uint32 dataOffset; + uint32 compressedSize; + uint32 decompressedSize; + uint16 decompressedBytesPerRow; + bool isKeyFrame; + + bool load(AssetLoaderContext &context, const Data::MToonAsset::FrameDef &data); + }; + + struct FrameRangeDef { + uint32 startFrame; + uint32 endFrame; + + Common::String name; + + bool load(AssetLoaderContext &context, const Data::MToonAsset::FrameRangeDef &data); + }; + +private: + ImageFormat _imageFormat; + + uint32 _frameDataPosition; + uint32 _sizeOfFrameData; + + Rect16 _rect; + uint16 _bitsPerPixel; + uint32 _codecID; + + Common::Array _frames; + Common::Array _frameRanges; + Common::Array _codecData; }; class TextAsset : public Asset { diff --git a/engines/mtropolis/data.cpp b/engines/mtropolis/data.cpp index 8fd33eb4c2f..359311e1e70 100644 --- a/engines/mtropolis/data.cpp +++ b/engines/mtropolis/data.cpp @@ -853,7 +853,7 @@ DataReadErrorCode MToonElement::load(DataReader &reader) { if (!reader.readU32(structuralFlags) || !reader.readU32(sizeIncludingTag) || !reader.readU32(guid) || !reader.readU16(lengthOfName) || !reader.readU32(elementFlags) || !reader.readU16(layer) || !reader.readU32(animationFlags) || !reader.readBytes(unknown4) || !reader.readU16(sectionID) - || !rect1.load(reader) || !rect2.load(reader) || !reader.readU32(unknown5) + || !rect1.load(reader) || !rect2.load(reader) || !reader.readU32(assetID) || !reader.readU32(rateTimes10000) || !reader.readU32(streamLocator) || !reader.readU32(unknown6) || !reader.readTerminatedStr(name, lengthOfName)) return kDataReadErrorReadFailed; @@ -1032,6 +1032,20 @@ DataReadErrorCode MiniscriptModifier::load(DataReader &reader) { return kDataReadErrorNone; } +DataReadErrorCode SaveAndRestoreModifier::load(DataReader &reader) { + if (_revision != 0x3e9) + return kDataReadErrorUnsupportedRevision; + + if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !saveWhen.load(reader) || !restoreWhen.load(reader) + || !saveOrRestoreValue.load(reader) || !reader.readBytes(unknown5) || !reader.readU8(lengthOfFilePath) + || !reader.readU8(lengthOfFileName) || !reader.readU8(lengthOfVariableName) || !reader.readU8(lengthOfVariableString) + || !reader.readNonTerminatedStr(varName, lengthOfVariableName) || !reader.readNonTerminatedStr(varString, lengthOfVariableString) + || !reader.readNonTerminatedStr(filePath, lengthOfFilePath) || !reader.readNonTerminatedStr(fileName, lengthOfFileName)) + return kDataReadErrorReadFailed; + + return kDataReadErrorNone; +} + DataReadErrorCode MessengerModifier::load(DataReader &reader) { if (_revision != 0x3ea) return kDataReadErrorUnsupportedRevision; @@ -1629,6 +1643,95 @@ DataReadErrorCode ImageAsset::load(DataReader &reader) { return kDataReadErrorNone; } +DataReadErrorCode MToonAsset::load(DataReader &reader) { + if (_revision != 1) + return kDataReadErrorUnsupportedRevision; + + if (!reader.readU32(marker) || !reader.readBytes(unknown1) || !reader.readU32(assetID)) + return kDataReadErrorReadFailed; + + haveMacPart = false; + haveWinPart = false; + + if (reader.getProjectFormat() == kProjectFormatMacintosh) { + haveMacPart = true; + + if (!reader.readBytes(platform.mac.unknown10)) + return kDataReadErrorReadFailed; + } else if (reader.getProjectFormat() == kProjectFormatWindows) { + haveWinPart = true; + + if (!reader.readBytes(platform.win.unknown11)) + return kDataReadErrorReadFailed; + } else + return kDataReadErrorUnrecognized; + + if (!reader.readU32(frameDataPosition) || !reader.readU32(sizeOfFrameData) || !reader.readU32(mtoonHeader[0]) + || !reader.readU32(mtoonHeader[1]) || !reader.readU16(version) || !reader.readBytes(unknown2) + || !reader.readU32(encodingFlags) || !rect.load(reader) || !reader.readU16(numFrames) + || !reader.readBytes(unknown3) || !reader.readU16(bitsPerPixel) || !reader.readU32(codecID) + || !reader.readBytes(unknown4_1) || !reader.readU32(codecDataSize) || !reader.readBytes(unknown4_2)) + return kDataReadErrorReadFailed; + + if (mtoonHeader[0] != 0 || mtoonHeader[1] != 0x546f6f6e) + return kDataReadErrorUnrecognized; + + if (numFrames > 0) { + frames.resize(numFrames); + for (size_t i = 0; i < numFrames; i++) { + FrameDef &frame = frames[i]; + + if (!reader.readBytes(frame.unknown12) || !frame.rect1.load(reader) || !reader.readU32(frame.dataOffset) + || !reader.readBytes(frame.unknown13) || !reader.readU32(frame.compressedSize) || !reader.readU8(frame.unknown14) + || !reader.readU8(frame.keyframeFlag) || !reader.readU8(frame.platformBit) || !reader.readU8(frame.unknown15) + || !frame.rect2.load(reader) || !reader.readU32(frame.hdpiFixed) || !reader.readU32(frame.vdpiFixed) + || !reader.readU16(frame.bitsPerPixel) || !reader.readU32(frame.unknown16) || !reader.readU16(frame.decompressedBytesPerRow)) + return kDataReadErrorReadFailed; + + if (reader.getProjectFormat() == kProjectFormatMacintosh) { + if (!reader.readBytes(frame.platform.mac.unknown17)) + return kDataReadErrorReadFailed; + } else if (reader.getProjectFormat() == kProjectFormatWindows) { + if (!reader.readBytes(frame.platform.win.unknown18)) + return kDataReadErrorReadFailed; + } else + return kDataReadErrorUnrecognized; + + if (!reader.readU32(frame.decompressedSize)) + return kDataReadErrorReadFailed; + } + } + + if (codecDataSize > 0) { + codecData.resize(codecDataSize); + if (!reader.read(&codecData[0], codecDataSize)) + return kDataReadErrorReadFailed; + } + + if (encodingFlags & kEncodingFlag_HasRanges) { + if (!reader.readU32(frameRangesPart.tag) || !reader.readU32(frameRangesPart.sizeIncludingTag) || !reader.readU32(frameRangesPart.numFrameRanges)) + return kDataReadErrorReadFailed; + + if (frameRangesPart.tag != 1) + return kDataReadErrorUnrecognized; + + if (frameRangesPart.numFrameRanges > 0) { + frameRangesPart.frameRanges.resize(frameRangesPart.numFrameRanges); + for (size_t i = 0; i < frameRangesPart.numFrameRanges; i++) { + FrameRangeDef &frameRange = frameRangesPart.frameRanges[i]; + + if (!reader.readU32(frameRange.startFrame) || !reader.readU32(frameRange.endFrame) || !reader.readU8(frameRange.lengthOfName) || !reader.readU8(frameRange.unknown14)) + return kDataReadErrorReadFailed; + + if (!reader.readTerminatedStr(frameRange.name, frameRange.lengthOfName)) + return kDataReadErrorReadFailed; + } + } + } + + return kDataReadErrorNone; +} + DataReadErrorCode TextAsset::load(DataReader &reader) { if (_revision != 3) return kDataReadErrorReadFailed; @@ -1775,6 +1878,9 @@ DataReadErrorCode loadDataObject(const PlugInModifierRegistry ®istry, DataRea case DataObjectTypes::kMiniscriptModifier: dataObject = new MiniscriptModifier(); break; + case DataObjectTypes::kSaveAndRestoreModifier: + dataObject = new SaveAndRestoreModifier(); + break; case DataObjectTypes::kMessengerModifier: dataObject = new MessengerModifier(); break; @@ -1871,7 +1977,7 @@ DataReadErrorCode loadDataObject(const PlugInModifierRegistry ®istry, DataRea break; case DataObjectTypes::kMToonAsset: - //dataObject = new MToonAsset(); + dataObject = new MToonAsset(); break; case DataObjectTypes::kTextAsset: diff --git a/engines/mtropolis/data.h b/engines/mtropolis/data.h index a42f1e145c8..f0916d22c9e 100644 --- a/engines/mtropolis/data.h +++ b/engines/mtropolis/data.h @@ -122,7 +122,7 @@ enum DataObjectType { kCursorModifierV1 = 0x3ca, // NYI - Obsolete version kGradientModifier = 0x4b0, // NYI kColorTableModifier = 0x4c4, // NYI - kSaveAndRestoreModifier = 0x4d8, // NYI + kSaveAndRestoreModifier = 0x4d8, kCompoundVariableModifier = 0x2c7, kBooleanVariableModifier = 0x321, @@ -272,7 +272,7 @@ struct Label { // // InternalTypeTaggedValue is used by internal modifiers for messenger payloads and set modifiers // and seems to match Miniscript ops too. -// InternalTypeTaggedValue is always 44 bytes in size and stores string data elsewhere in the containing structure. +// InternalTypeTaggedValue is always 46 bytes in size and stores string data elsewhere in the containing structure. // // PlugInTypeTaggedValue is used by plug-ins and is fully self-contained. // @@ -394,7 +394,7 @@ struct ProjectLabelMap : public DataObject { Common::String name; - uint32_t numChildren; + uint32 numChildren; LabelTree *children; }; @@ -407,7 +407,7 @@ struct ProjectLabelMap : public DataObject { uint32 unknown2; Common::String name; - uint32_t numChildren; + uint32 numChildren; LabelTree *tree; }; @@ -678,7 +678,7 @@ struct MToonElement : public StructuralDef { uint16 sectionID; Rect rect1; Rect rect2; - uint32 unknown5; + uint32 assetID; uint32 rateTimes10000; uint32 streamLocator; uint32 unknown6; @@ -814,7 +814,6 @@ struct TypicalModifierHeader { }; struct MiniscriptModifier : public DataObject { - TypicalModifierHeader modHeader; Event enableWhen; uint8 unknown6[11]; @@ -826,6 +825,30 @@ protected: DataReadErrorCode load(DataReader &reader) override; }; + +struct SaveAndRestoreModifier : public DataObject { + TypicalModifierHeader modHeader; + uint8 unknown1[4]; + Event saveWhen; + Event restoreWhen; + InternalTypeTaggedValue saveOrRestoreValue; + + uint8 unknown5[8]; + + uint8 lengthOfFilePath; + uint8 lengthOfFileName; + uint8 lengthOfVariableName; + uint8 lengthOfVariableString; + + Common::String varName; + Common::String varString; + Common::String filePath; + Common::String fileName; + +protected: + DataReadErrorCode load(DataReader &reader) override; +}; + enum MessageFlags { kMessageFlagNoRelay = 0x20000000, kMessageFlagNoCascade = 0x40000000, @@ -936,15 +959,15 @@ struct DragMotionModifier : public DataObject { Event disableWhen; struct WinPart { - uint8_t unknown2; - uint8_t constrainHorizontal; - uint8_t constrainVertical; - uint8_t constrainToParent; + uint8 unknown2; + uint8 constrainHorizontal; + uint8 constrainVertical; + uint8 constrainToParent; }; struct MacPart { - uint8_t flags; - uint8_t unknown3; + uint8 flags; + uint8 unknown3; enum Flags { kConstrainToParent = 0x10, @@ -963,7 +986,7 @@ struct DragMotionModifier : public DataObject { bool haveMacPart; bool haveWinPart; Rect constraintMargin; - uint16_t unknown1; + uint16 unknown1; protected: DataReadErrorCode load(DataReader &reader) override; @@ -1205,7 +1228,7 @@ struct TextStyleModifier : public DataObject { uint16 unknown3; Event applyWhen; Event removeWhen; - uint16_t lengthOfFontFamilyName; + uint16 lengthOfFontFamilyName; Common::String fontFamilyName; @@ -1497,7 +1520,7 @@ struct ImageAsset : public DataObject { uint32 persistFlags; uint32 unknown1; - uint8_t unknown2[4]; + uint8 unknown2[4]; uint32 assetID; uint32 unknown3; @@ -1520,6 +1543,112 @@ protected: DataReadErrorCode load(DataReader &reader) override; }; +struct MToonAsset : public DataObject { + struct MacPart { + uint8 unknown10[88]; + }; + + struct WinPart { + uint8 unknown11[54]; + }; + + struct FrameDef { + struct MacPart { + uint8 unknown17[4]; + }; + + struct WinPart { + uint8 unknown18[2]; + }; + + union PlatformUnion { + MacPart mac; + WinPart win; + }; + + uint8 unknown12[4]; + Rect rect1; + uint32 dataOffset; + uint8 unknown13[2]; + uint32 compressedSize; + uint8 unknown14; + uint8 keyframeFlag; + uint8 platformBit; + uint8 unknown15; + Rect rect2; + uint32 hdpiFixed; + uint32 vdpiFixed; + uint16 bitsPerPixel; + uint32 unknown16; + uint16 decompressedBytesPerRow; + uint32 decompressedSize; + + PlatformUnion platform; + }; + + struct FrameRangeDef { + uint32 startFrame; + uint32 endFrame; + uint8 lengthOfName; + uint8 unknown14; + + Common::String name; // Null terminated + }; + + enum { + kEncodingFlag_TemporalCompression = 0x80, + kEncodingFlag_HasRanges = 0x20000000, + kEncodingFlag_Trimming = 0x08, + }; + + uint32 marker; + uint8 unknown1[8]; + uint32 assetID; + + bool haveMacPart; + bool haveWinPart; + + union PlatformUnion { + MacPart mac; + WinPart win; + } platform; + + uint32 frameDataPosition; + uint32 sizeOfFrameData; + + // mToon data + uint32 mtoonHeader[2]; + uint16 version; + uint8 unknown2[4]; + uint32 encodingFlags; + Rect rect; + + uint16 numFrames; + uint8 unknown3[14]; + uint16 bitsPerPixel; + uint32 codecID; + uint8 unknown4_1[8]; + uint32 codecDataSize; + uint8 unknown4_2[4]; + + Common::Array frames; + + Common::Array codecData; + + struct FrameRangePart { + uint32 tag; + uint32 sizeIncludingTag; + + uint32 numFrameRanges; + Common::Array frameRanges; + }; + + FrameRangePart frameRangesPart; + +protected: + DataReadErrorCode load(DataReader &reader) override; +}; + struct TextAsset : public DataObject { struct MacFormattingSpan { uint8 unknown9[2]; diff --git a/engines/mtropolis/element_factory.cpp b/engines/mtropolis/element_factory.cpp index c829cbc965d..c70afeaca9c 100644 --- a/engines/mtropolis/element_factory.cpp +++ b/engines/mtropolis/element_factory.cpp @@ -66,6 +66,8 @@ IElementFactory *getElementFactoryForDataObjectType(const Data::DataObjectTypes: return ElementFactory::getInstance(); case Data::DataObjectTypes::kImageElement: return ElementFactory::getInstance(); + case Data::DataObjectTypes::kMToonElement: + return ElementFactory::getInstance(); case Data::DataObjectTypes::kTextLabelElement: return ElementFactory::getInstance(); case Data::DataObjectTypes::kSoundElement: diff --git a/engines/mtropolis/elements.cpp b/engines/mtropolis/elements.cpp index 6d2eed097a7..71814f2f50f 100644 --- a/engines/mtropolis/elements.cpp +++ b/engines/mtropolis/elements.cpp @@ -284,7 +284,7 @@ void ImageElement::activate() { pixelFmt = Graphics::PixelFormat::createFormatCLUT8(); break; case kColorDepthMode16Bit: - bytesPerRow = width * 2; + bytesPerRow = (width * 2 + 3) / 4 * 4; pixelFmt = Graphics::createPixelFormat<1555>(); break; case kColorDepthMode32Bit: @@ -402,6 +402,43 @@ void ImageElement::render(Window *window) { } } +MToonElement::MToonElement() { +} + +MToonElement::~MToonElement() { +} + +bool MToonElement::load(ElementLoaderContext &context, const Data::MToonElement &data) { + if (!loadCommon(data.name, data.guid, data.rect1, data.elementFlags, data.layer, data.streamLocator, data.sectionID)) + return false; + + _cacheBitmap = ((data.elementFlags & Data::ElementFlags::kCacheBitmap) != 0); + _paused = ((data.elementFlags & Data::ElementFlags::kPaused) != 0); + _loop = ((data.animationFlags & Data::AnimationFlags::kLoop) != 0); + _maintainRate = ((data.elementFlags & Data::AnimationFlags::kPlayEveryFrame) == 0); // NOTE: Inverted intentionally + _assetID = data.assetID; + _runtime = context.runtime; + _rateTimes10000 = data.rateTimes10000; + + return true; +} + +void MToonElement::activate() { +} + +void MToonElement::deactivate() { + _renderSurface.reset(); +} + +void MToonElement::render(Window *window) { + if (_renderSurface) { + Common::Rect srcRect(_renderSurface->w, _renderSurface->h); + Common::Rect destRect(_cachedAbsoluteOrigin.x, _cachedAbsoluteOrigin.y, _cachedAbsoluteOrigin.x + _rect.getWidth(), _cachedAbsoluteOrigin.y + _rect.getHeight()); + window->getSurface()->blitFrom(*_renderSurface, srcRect, destRect); + } +} + + TextLabelElement::TextLabelElement() : _needsRender(false), _isBitmap(false) { } diff --git a/engines/mtropolis/elements.h b/engines/mtropolis/elements.h index 9923c7d53bb..dde51a4a7f1 100644 --- a/engines/mtropolis/elements.h +++ b/engines/mtropolis/elements.h @@ -128,6 +128,37 @@ private: Runtime *_runtime; }; +class MToonElement : public VisualElement { +public: + MToonElement(); + ~MToonElement(); + + bool load(ElementLoaderContext &context, const Data::MToonElement &data); + + void activate() override; + void deactivate() override; + + void render(Window *window) override; + +#ifdef MTROPOLIS_DEBUG_ENABLE + const char *debugGetTypeName() const override { return "mToon Element"; } + SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; } +#endif + +private: + bool _cacheBitmap; + bool _loop; + + // If set, then carry over residual frame time and display at the desired rate. If not set, reset residual each frame for smoother animation. + bool _maintainRate; + + uint32 _assetID; + Runtime *_runtime; + uint32 _rateTimes10000; + + Common::SharedPtr _renderSurface; +}; + class TextLabelElement : public VisualElement { public: TextLabelElement(); diff --git a/engines/mtropolis/miniscript.cpp b/engines/mtropolis/miniscript.cpp index 641fddca403..291cd0d6c56 100644 --- a/engines/mtropolis/miniscript.cpp +++ b/engines/mtropolis/miniscript.cpp @@ -56,6 +56,23 @@ void MiniscriptReferences::linkInternalReferences(ObjectLinkingScope *scope) { } } +void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor *visitor) { + for (LocalRef &ref : _localRefs) { + Common::SharedPtr obj = ref.resolution.lock(); + if (obj) { + if (obj->isModifier()) { + Common::WeakPtr mod = obj.staticCast(); + visitor->visitWeakModifierRef(mod); + ref.resolution = mod; + } else if (obj->isStructural()) { + Common::WeakPtr struc = obj.staticCast(); + visitor->visitWeakStructuralRef(struc); + ref.resolution = struc; + } + } + } +} + Common::WeakPtr MiniscriptReferences::getRefByIndex(uint index) const { if (index >= _localRefs.size()) return Common::WeakPtr(); @@ -1075,16 +1092,18 @@ MiniscriptInstructionOutcome BuiltinFunc::executeNum2Str(MiniscriptThread *threa const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value; switch (inputDynamicValue.getType()) { case DynamicValueTypes::kInteger: - result.format("%i", static_cast(inputDynamicValue.getInt())); + result = Common::String::format("%i", static_cast(inputDynamicValue.getInt())); break; case DynamicValueTypes::kFloat: - result.format("%g", static_cast(inputDynamicValue.getFloat())); + result = Common::String::format("%g", static_cast(inputDynamicValue.getFloat())); break; default: thread->error("Invalid input value to num2str"); return kMiniscriptInstructionOutcomeFailed; } + returnValue->setString(result); + return kMiniscriptInstructionOutcomeContinue; } diff --git a/engines/mtropolis/miniscript.h b/engines/mtropolis/miniscript.h index c65f6816d05..aab8e32eb6d 100644 --- a/engines/mtropolis/miniscript.h +++ b/engines/mtropolis/miniscript.h @@ -49,6 +49,7 @@ public: explicit MiniscriptReferences(const Common::Array &localRefs); void linkInternalReferences(ObjectLinkingScope *scope); + void visitInternalReferences(IStructuralReferenceVisitor *visitor); Common::WeakPtr getRefByIndex(uint index) const; diff --git a/engines/mtropolis/modifier_factory.cpp b/engines/mtropolis/modifier_factory.cpp index 7f1d0340f96..48934f8107c 100644 --- a/engines/mtropolis/modifier_factory.cpp +++ b/engines/mtropolis/modifier_factory.cpp @@ -67,6 +67,8 @@ IModifierFactory *getModifierFactoryForDataObjectType(const Data::DataObjectType return ModifierFactory::getInstance(); case Data::DataObjectTypes::kMiniscriptModifier: return ModifierFactory::getInstance(); + case Data::DataObjectTypes::kSaveAndRestoreModifier: + return ModifierFactory::getInstance(); case Data::DataObjectTypes::kAliasModifier: return ModifierFactory::getInstance(); case Data::DataObjectTypes::kChangeSceneModifier: diff --git a/engines/mtropolis/modifiers.cpp b/engines/mtropolis/modifiers.cpp index 8b907469e65..627ffc802cd 100644 --- a/engines/mtropolis/modifiers.cpp +++ b/engines/mtropolis/modifiers.cpp @@ -132,6 +132,10 @@ Common::SharedPtr BehaviorModifier::shallowClone() const { return Common::SharedPtr(new BehaviorModifier(*this)); } +void BehaviorModifier::linkInternalReferences(ObjectLinkingScope *scope) { + Modifier::linkInternalReferences(scope); +} + void BehaviorModifier::visitInternalReferences(IStructuralReferenceVisitor* visitor) { for (Common::Array >::iterator it = _children.begin(), itEnd = _children.end(); it != itEnd; ++it) { visitor->visitChildModifierRef(*it); @@ -176,6 +180,49 @@ void MiniscriptModifier::linkInternalReferences(ObjectLinkingScope* scope) { _references->linkInternalReferences(scope); } +void MiniscriptModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) { + _references->visitInternalReferences(visitor); +} + +bool SaveAndRestoreModifier::load(ModifierLoaderContext &context, const Data::SaveAndRestoreModifier &data) { + if (!loadTypicalHeader(data.modHeader)) + return false; + + if (!_saveWhen.load(data.saveWhen) || !_restoreWhen.load(data.restoreWhen)) + return false; + + if (!_saveOrRestoreValue.load(data.saveOrRestoreValue, data.varName, data.varString)) + return false; + + _filePath = data.filePath; + _fileName = data.fileName; + + return true; +} + +bool SaveAndRestoreModifier::respondsToEvent(const Event &evt) const { + if (_saveWhen.respondsTo(evt) || _restoreWhen.respondsTo(evt)) + return true; + + return false; +} + +VThreadState SaveAndRestoreModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { + if (_saveWhen.respondsTo(msg->getEvent())) { + error("Saves not implemented yet"); + return kVThreadReturn; + } else if (_restoreWhen.respondsTo(msg->getEvent())) { + error("Restores not implemented yet"); + return kVThreadReturn; + } + + return kVThreadError; +} + +Common::SharedPtr SaveAndRestoreModifier::shallowClone() const { + return Common::SharedPtr(new SaveAndRestoreModifier(*this)); +} + bool MessengerModifier::load(ModifierLoaderContext &context, const Data::MessengerModifier &data) { if (!loadTypicalHeader(data.modHeader)) return false; diff --git a/engines/mtropolis/modifiers.h b/engines/mtropolis/modifiers.h index b29fa6480c5..edec3cc85c0 100644 --- a/engines/mtropolis/modifiers.h +++ b/engines/mtropolis/modifiers.h @@ -71,6 +71,7 @@ private: VThreadState propagateTask(const PropagateTaskData &taskData); Common::SharedPtr shallowClone() const override; + void linkInternalReferences(ObjectLinkingScope *scope) override; void visitInternalReferences(IStructuralReferenceVisitor *visitor) override; Common::Array > _children; @@ -96,6 +97,7 @@ public: private: Common::SharedPtr shallowClone() const override; void linkInternalReferences(ObjectLinkingScope *scope) override; + void visitInternalReferences(IStructuralReferenceVisitor *visitor) override; Event _enableWhen; @@ -103,6 +105,30 @@ private: Common::SharedPtr _references; }; +class SaveAndRestoreModifier : public Modifier { +public: + bool load(ModifierLoaderContext &context, const Data::SaveAndRestoreModifier &data); + + bool respondsToEvent(const Event &evt) const override; + VThreadState consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) override; + +#ifdef MTROPOLIS_DEBUG_ENABLE + const char *debugGetTypeName() const override { return "Save And Restore Modifier"; } + SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; } +#endif + +private: + Common::SharedPtr shallowClone() const override; + + Event _saveWhen; + Event _restoreWhen; + + DynamicValue _saveOrRestoreValue; + + Common::String _filePath; + Common::String _fileName; +}; + class MessengerModifier : public Modifier { public: bool load(ModifierLoaderContext &context, const Data::MessengerModifier &data); diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp index 4cc245c337a..a6f2cb8b796 100644 --- a/engines/mtropolis/runtime.cpp +++ b/engines/mtropolis/runtime.cpp @@ -3391,7 +3391,7 @@ void Runtime::loadScene(const Common::SharedPtr& scene) { scene->materializeDescendents(this, subsection->getSceneLoadMaterializeScope()); debug(1, "Scene materialized OK"); recursiveActivateStructural(scene.get()); - debug(1, "Scene added to renderer OK"); + debug(1, "Structural elements activated OK"); #ifdef MTROPOLIS_DEBUG_ENABLE if (_debugger) { @@ -4767,6 +4767,9 @@ bool VisualElement::readAttribute(MiniscriptThread *thread, DynamicValue &result } else if (attrib == "height") { result.setInt(_rect.bottom - _rect.top); return true; + } else if (attrib == "globalposition") { + result.setPoint(getGlobalPosition()); + return true; } return Element::readAttribute(thread, result, attrib); @@ -4791,6 +4794,21 @@ const Rect16 &VisualElement::getRelativeRect() const { return _rect; } +Point16 VisualElement::getGlobalPosition() const { + Point16 pos = Point16::create(0, 0); + if (_parent && _parent->isElement()) { + Element *element = static_cast(_parent); + if (element->isVisual()) { + pos = static_cast(element)->getGlobalPosition(); + } + } + + pos.x += _rect.left; + pos.y += _rect.top; + + return pos; +} + const Point16 &VisualElement::getCachedAbsoluteOrigin() const { return _cachedAbsoluteOrigin; } diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h index 89694082674..32780db9673 100644 --- a/engines/mtropolis/runtime.h +++ b/engines/mtropolis/runtime.h @@ -1958,6 +1958,7 @@ public: bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override; MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) override; + Point16 getGlobalPosition() const; const Rect16 &getRelativeRect() const; // The cached absolute origin is from the last time the element was rendered. @@ -2055,7 +2056,7 @@ protected: bool loadTypicalHeader(const Data::TypicalModifierHeader &typicalHeader); // Links any references contained in the object, resolving static GUIDs to runtime object references. - // If you override this, you must override visitInternalReferences too. + // If you override this, you should override visitInternalReferences too virtual void linkInternalReferences(ObjectLinkingScope *scope); Structural *findStructuralOwner() const; @@ -2096,6 +2097,7 @@ enum AssetType { kAssetTypeColorTable, kAssetTypeImage, kAssetTypeText, + kAssetTypeMToon, }; class Asset {