[Scummvm-git-logs] scummvm master -> b5a067415a0e0d354603ed47966dedbff3e31c69

elasota noreply at scummvm.org
Sat Jul 6 04:46:32 UTC 2024


This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
dbc1f8f4cf MTROPOLIS: Add "section" attrib, force-load Sound subsection in SPQR
b5a067415a MTROPOLIS: Add object reparenting, cloning, and killing


Commit: dbc1f8f4cfb49e2fb380c910891aa30da11cf4b6
    https://github.com/scummvm/scummvm/commit/dbc1f8f4cfb49e2fb380c910891aa30da11cf4b6
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-07-06T00:45:58-04:00

Commit Message:
MTROPOLIS: Add "section" attrib, force-load Sound subsection in SPQR

Changed paths:
    engines/mtropolis/hacks.cpp
    engines/mtropolis/hacks.h
    engines/mtropolis/mtropolis.cpp
    engines/mtropolis/runtime.cpp
    engines/mtropolis/runtime.h


diff --git a/engines/mtropolis/hacks.cpp b/engines/mtropolis/hacks.cpp
index e3fddc92a5b..96164220414 100644
--- a/engines/mtropolis/hacks.cpp
+++ b/engines/mtropolis/hacks.cpp
@@ -1137,6 +1137,43 @@ void addMTIQuirks(const MTropolisGameDescription &desc, Hacks &hacks) {
 	hacks.addSceneTransitionHooks(Common::SharedPtr<SceneTransitionHooks>(new MTIMolassesSceneTransitionHooks(molassesHandler)));
 }
 
+class SPQRSoundPreloadHooks : public SceneTransitionHooks {
+public:
+	void onProjectStarted(Runtime *runtime) override;
+};
+
+void SPQRSoundPreloadHooks::onProjectStarted(Runtime *runtime) {
+	Project *project = runtime->getProject();
+
+	Structural *worldSection = nullptr;
+	Structural *soundSubsection = nullptr;
+
+	for (const Common::SharedPtr<Structural> &child : project->getChildren()) {
+		if (child->getName() == "World") {
+			worldSection = child.get();
+			break;
+		}
+	}
+
+	if (worldSection) {
+		for (const Common::SharedPtr<Structural> &child : worldSection->getChildren()) {
+			if (child->getName() == "Sound") {
+				soundSubsection = child.get();
+				break;
+			}
+		}
+	}
+
+	if (soundSubsection) {
+		for (const Common::SharedPtr<Structural> &child : soundSubsection->getChildren())
+			runtime->addSceneStateTransition(HighLevelSceneTransition(child, HighLevelSceneTransition::kTypeForceLoadScene, false, false));
+	}
+}
+
+void addSPQRQuirks(const MTropolisGameDescription &desc, Hacks &hacks) {
+	hacks.addSceneTransitionHooks(Common::SharedPtr<SceneTransitionHooks>(new SPQRSoundPreloadHooks()));
+}
+
 } // End of namespace HackSuites
 
 } // End of namespace MTropolis
diff --git a/engines/mtropolis/hacks.h b/engines/mtropolis/hacks.h
index 3c16e9bc69b..c4acd69ac58 100644
--- a/engines/mtropolis/hacks.h
+++ b/engines/mtropolis/hacks.h
@@ -81,6 +81,7 @@ void addObsidianSaveMechanism(const MTropolisGameDescription &desc, Hacks &hacks
 void addObsidianImprovedWidescreen(const MTropolisGameDescription &desc, Hacks &hacks);
 
 void addMTIQuirks(const MTropolisGameDescription &desc, Hacks &hacks);
+void addSPQRQuirks(const MTropolisGameDescription &desc, Hacks &hacks);
 
 } // End of namespace HackSuites
 
diff --git a/engines/mtropolis/mtropolis.cpp b/engines/mtropolis/mtropolis.cpp
index d15fd15f385..f42cded8eba 100644
--- a/engines/mtropolis/mtropolis.cpp
+++ b/engines/mtropolis/mtropolis.cpp
@@ -332,6 +332,8 @@ Common::Error MTropolisEngine::run() {
 		Palette pal;
 		pal.initDefaultPalette(2);
 		_runtime->setGlobalPalette(pal);
+	} else if (_gameDescription->gameID == GID_SPQR) {
+		HackSuites::addSPQRQuirks(*_gameDescription, _runtime->getHacks());
 	}
 
 
diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp
index d4bec6ea2f5..a6b69025d96 100644
--- a/engines/mtropolis/runtime.cpp
+++ b/engines/mtropolis/runtime.cpp
@@ -3151,9 +3151,7 @@ bool Structural::readAttribute(MiniscriptThread *thread, DynamicValue &result, c
 	} else if (attrib == "scene") {
 		result.clear();
 
-		// Scene returns the scene of the Miniscript modifier, even though it's looked up
-		// as if it's an element property, because it's treated like a keyword.
-		RuntimeObject *possibleScene = thread->getModifier();
+		RuntimeObject *possibleScene = this;
 		while (possibleScene) {
 			if (possibleScene->isModifier()) {
 				possibleScene = static_cast<Modifier *>(possibleScene)->getParent().lock().get();
@@ -3173,11 +3171,39 @@ bool Structural::readAttribute(MiniscriptThread *thread, DynamicValue &result, c
 			assert(false);
 			break;
 		}
+
 		if (possibleScene)
 			result.setObject(possibleScene->getSelfReference());
 		else
 			result.clear();
 		return true;
+	} else if (attrib == "section") {
+		result.clear();
+
+		RuntimeObject *possibleSection = this;
+		while (possibleSection) {
+			if (possibleSection->isSection())
+				break;
+
+			if (possibleSection->isModifier()) {
+				possibleSection = static_cast<Modifier *>(possibleSection)->getParent().lock().get();
+				continue;
+			}
+
+			if (possibleSection->isStructural()) {
+				possibleSection = static_cast<Structural *>(possibleSection)->getParent();
+				continue;
+			}
+
+			assert(false);
+			break;
+		}
+
+		if (possibleSection)
+			result.setObject(possibleSection->getSelfReference());
+		else
+			result.clear();
+		return true;
 	} else if (attrib == "subsection") {
 		result.clear();
 
@@ -4320,6 +4346,9 @@ void SceneTransitionHooks::onSceneTransitionSetup(Runtime *runtime, const Common
 void SceneTransitionHooks::onSceneTransitionEnded(Runtime *runtime, const Common::WeakPtr<Structural> &newScene) {
 }
 
+void SceneTransitionHooks::onProjectStarted(Runtime *runtime) {
+}
+
 
 Palette::Palette() {
 	initDefaultPalette(1);
@@ -4599,6 +4628,9 @@ bool Runtime::runFrame() {
 			Common::SharedPtr<MessageDispatch> psDispatch(new MessageDispatch(psProps, _project.get(), false, true, false));
 			queueMessage(psDispatch);
 
+			for (const Common::SharedPtr<SceneTransitionHooks> &hook : _hacks.sceneTransitionHooks)
+				hook->onProjectStarted(this);
+
 			_pendingSceneTransitions.push_back(HighLevelSceneTransition(firstSubsection->getChildren()[1], HighLevelSceneTransition::kTypeChangeToScene, false, false));
 			continue;
 		}
@@ -5219,6 +5251,11 @@ void Runtime::executeHighLevelSceneTransition(const HighLevelSceneTransition &tr
 
 			_sharedSceneWasSetExplicitly = true;
 		} break;
+	case HighLevelSceneTransition::kTypeForceLoadScene: {
+			_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(transition.scene, LowLevelSceneStateTransitionAction::kLoad));
+			queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), transition.scene.get(), true, true);
+			queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), transition.scene.get(), true, true);
+		} break;
 	default:
 		error("Unknown high-level scene transition type");
 		break;
diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h
index d66e251f3a1..ac6f2d1e8da 100644
--- a/engines/mtropolis/runtime.h
+++ b/engines/mtropolis/runtime.h
@@ -1359,6 +1359,7 @@ struct HighLevelSceneTransition {
 		kTypeReturn,
 		kTypeChangeToScene,
 		kTypeChangeSharedScene,
+		kTypeForceLoadScene,
 	};
 
 	HighLevelSceneTransition(const Common::SharedPtr<Structural> &hlst_scene, Type hlst_type, bool hlst_addToDestinationScene, bool hlst_addToReturnList);
@@ -1569,6 +1570,7 @@ public:
 
 	virtual void onSceneTransitionSetup(Runtime *runtime, const Common::WeakPtr<Structural> &oldScene, const Common::WeakPtr<Structural> &newScene);
 	virtual void onSceneTransitionEnded(Runtime *runtime, const Common::WeakPtr<Structural> &newScene);
+	virtual void onProjectStarted(Runtime *runtime);
 };
 
 class Palette {


Commit: b5a067415a0e0d354603ed47966dedbff3e31c69
    https://github.com/scummvm/scummvm/commit/b5a067415a0e0d354603ed47966dedbff3e31c69
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-07-06T00:45:58-04:00

Commit Message:
MTROPOLIS: Add object reparenting, cloning, and killing

Changed paths:
    engines/mtropolis/elements.cpp
    engines/mtropolis/elements.h
    engines/mtropolis/hacks.cpp
    engines/mtropolis/hacks.h
    engines/mtropolis/modifiers.cpp
    engines/mtropolis/modifiers.h
    engines/mtropolis/runtime.cpp
    engines/mtropolis/runtime.h


diff --git a/engines/mtropolis/elements.cpp b/engines/mtropolis/elements.cpp
index 488b6b3de83..69032e99d65 100644
--- a/engines/mtropolis/elements.cpp
+++ b/engines/mtropolis/elements.cpp
@@ -43,6 +43,10 @@ namespace MTropolis {
 GraphicElement::GraphicElement() : _cacheBitmap(false) {
 }
 
+GraphicElement::GraphicElement(const GraphicElement &other)
+	: VisualElement(other), _cacheBitmap(other._cacheBitmap), _mask(nullptr) {
+}
+
 GraphicElement::~GraphicElement() {
 }
 
@@ -493,6 +497,15 @@ void GraphicElement::render(Window *window) {
 	}
 }
 
+Common::SharedPtr<Structural> GraphicElement::shallowClone() const {
+	return Common::SharedPtr<Structural>(new GraphicElement(*this));
+}
+
+void GraphicElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	VisualElement::visitInternalReferences(visitor);
+
+}
+
 MovieResizeFilter::~MovieResizeFilter() {
 }
 
@@ -939,6 +952,17 @@ void MovieElement::setResizeFilter(const Common::SharedPtr<MovieResizeFilter> &f
 	_resizeFilter = filter;
 }
 
+Common::SharedPtr<Structural> MovieElement::shallowClone() const {
+	error("Cloning movie elements is not currently supported");
+	return nullptr;
+}
+
+void MovieElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	VisualElement::visitInternalReferences(visitor);
+
+	error("Cloning movie elements is not currently supported");
+}
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 void MovieElement::debugSkipMovies() {
 	if (_videoDecoder && !_videoDecoder->endOfVideo()) {
@@ -1273,6 +1297,14 @@ void ImageElement::render(Window *window) {
 	}
 }
 
+Common::SharedPtr<Structural> ImageElement::shallowClone() const {
+	return Common::SharedPtr<Structural>(new ImageElement(*this));
+}
+
+void ImageElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	VisualElement::visitInternalReferences(visitor);
+}
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 void ImageElement::debugInspect(IDebugInspectionReport *report) const {
 	VisualElement::debugInspect(report);
@@ -1292,6 +1324,14 @@ MToonElement::MToonElement()
 	  _isPlaying(false), _isStopped(false), _renderedFrame(0), _playRange(IntRange(1, 1)), _cel(1), _hasIssuedRenderWarning(false) {
 }
 
+MToonElement::MToonElement(const MToonElement &other)
+	: VisualElement(other), _cacheBitmap(other._cacheBitmap), _maintainRate(other._maintainRate), _assetID(other._assetID)
+	, _rateTimes100000(other._rateTimes100000), _flushPriority(other._flushPriority), _celStartTimeMSec(other._celStartTimeMSec)
+	, _isPlaying(other._isPlaying), _isStopped(other._isStopped), _renderSurface(nullptr), _renderedFrame(0), _metadata(other._metadata)
+	, _cachedMToon(other._cachedMToon), _playMediaSignaller(nullptr), _playRange(other._playRange), _cel(other._cel), _hasIssuedRenderWarning(false) {
+	_playMediaSignaller = other.getRuntime()->getProject()->notifyOnPlayMedia(this);
+}
+
 MToonElement::~MToonElement() {
 	if (_playMediaSignaller)
 		_playMediaSignaller->removeReceiver(this);
@@ -1568,6 +1608,15 @@ Common::Rect MToonElement::getRelativeCollisionRect() const {
 	colRect.translate(_rect.left, _rect.top);
 	return colRect;
 }
+
+Common::SharedPtr<Structural> MToonElement::shallowClone() const {
+	return Common::SharedPtr<Structural>(new MToonElement(*this));
+}
+
+void MToonElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	VisualElement::visitInternalReferences(visitor);
+}
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 void MToonElement::debugInspect(IDebugInspectionReport *report) const {
 	VisualElement::debugInspect(report);
@@ -1896,10 +1945,20 @@ MiniscriptInstructionOutcome MToonElement::scriptSetRate(MiniscriptThread *threa
 
 
 TextLabelElement::TextLabelElement()
-	: _cacheBitmap(false), _needsRender(false), /*_isBitmap(false), */_assetID(0),
+	: _cacheBitmap(false), _needsRender(false), _isBitmap(true), _assetID(0),
 	  _macFontID(0), _size(12), _alignment(kTextAlignmentLeft) {
 }
 
+TextLabelElement::TextLabelElement(const TextLabelElement &other)
+	: VisualElement(other), _cacheBitmap(other._cacheBitmap), _needsRender(other._needsRender), _isBitmap(other._isBitmap)
+	, _assetID(other._assetID), _text(other._text), _macFontID(other._macFontID), _fontFamilyName(other._fontFamilyName)
+	, _size(other._size), _alignment(other._alignment), _styleFlags(other._styleFlags), _macFormattingSpans(other._macFormattingSpans)
+	, _renderedText(nullptr) {
+
+	if (other._isBitmap)
+		_renderedText = other._renderedText;
+}
+
 TextLabelElement::~TextLabelElement() {
 }
 
@@ -1993,8 +2052,10 @@ void TextLabelElement::activate() {
 	if (textAsset->isBitmap()) {
 		_renderedText = textAsset->getBitmapSurface();
 		_needsRender = false;
+		_isBitmap = true;
 	} else {
 		_needsRender = true;
+		_isBitmap = false;
 		_text = textAsset->getString();
 		_macFormattingSpans = textAsset->getMacFormattingSpans();
 	}
@@ -2016,6 +2077,7 @@ void TextLabelElement::render(Window *window) {
 
 	if (_needsRender) {
 		_needsRender = false;
+		_isBitmap = false;
 
 		_renderedText.reset();
 		_renderedText.reset(new Graphics::ManagedSurface());
@@ -2165,6 +2227,7 @@ void TextLabelElement::render(Window *window) {
 void TextLabelElement::setTextStyle(uint16 macFontID, const Common::String &fontFamilyName, uint size, TextAlignment alignment, const TextStyleFlags &styleFlags) {
 	if (!_text.empty()) {
 		_needsRender = true;
+		_isBitmap = false;
 		_contentsDirty = true;
 	}
 
@@ -2202,6 +2265,14 @@ Graphics::FontManager::FontUsage TextLabelElement::getDefaultUsageForNamedFont(c
 	return Graphics::FontManager::kGUIFont;
 }
 
+Common::SharedPtr<Structural> TextLabelElement::shallowClone() const {
+	return Common::SharedPtr<Structural>(new TextLabelElement(*this));
+}
+
+void TextLabelElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	VisualElement::visitInternalReferences(visitor);
+}
+
 MiniscriptInstructionOutcome TextLabelElement::scriptSetText(MiniscriptThread *thread, const DynamicValue &value) {
 	if (value.getType() != DynamicValueTypes::kString) {
 		thread->error("Tried to set a text label element's text to something that wasn't a string");
@@ -2210,6 +2281,7 @@ MiniscriptInstructionOutcome TextLabelElement::scriptSetText(MiniscriptThread *t
 
 	_text = value.getString();
 	_needsRender = true;
+	_isBitmap = false;
 	_contentsDirty = true;
 	_macFormattingSpans.clear();
 
@@ -2239,6 +2311,7 @@ MiniscriptInstructionOutcome TextLabelElement::scriptSetLine(MiniscriptThread *t
 	}
 
 	_needsRender = true;
+	_isBitmap = false;
 	_contentsDirty = true;
 	_macFormattingSpans.clear();
 
@@ -2295,6 +2368,18 @@ SoundElement::SoundElement()
 	  _startTimestamp(0), _shouldPlayIfNotPaused(true), _needsReset(true) {
 }
 
+SoundElement::SoundElement(const SoundElement &other)
+	: NonVisualElement(other), _leftVolume(other._leftVolume), _rightVolume(other._rightVolume), _balance(other._balance)
+	, _assetID(other._assetID), _cachedAudio(other._cachedAudio), _metadata(other._metadata), _player(nullptr)
+	, _startTime(other._startTime), _finishTime(other._finishTime), _startTimestamp(other._startTimestamp)
+	, _cueCheckTime(other._cueCheckTime), _shouldPlayIfNotPaused(other._shouldPlayIfNotPaused), _needsReset(true)
+	, _playMediaSignaller(nullptr), _subtitlePlayer(nullptr) {
+
+	_playMediaSignaller = other.getRuntime()->getProject()->notifyOnPlayMedia(this);
+
+	initSubtitles();
+}
+
 SoundElement::~SoundElement() {
 	if (_playMediaSignaller)
 		_playMediaSignaller->removeReceiver(this);
@@ -2361,6 +2446,23 @@ VThreadState SoundElement::consumeCommand(Runtime *runtime, const Common::Shared
 	return NonVisualElement::consumeCommand(runtime, msg);
 }
 
+void SoundElement::initSubtitles() {
+	Project *project = getRuntime()->getProject();
+
+	const SubtitleTables &subTables = project->getSubtitles();
+	if (subTables.assetMapping) {
+		const Common::String *subtitleSetIDPtr = subTables.assetMapping->findSubtitleSetForAssetID(_assetID);
+		if (!subtitleSetIDPtr) {
+			Common::String assetName = project->getAssetNameByID(_assetID);
+			if (assetName.size() > 0)
+				subtitleSetIDPtr = subTables.assetMapping->findSubtitleSetForAssetName(assetName);
+		}
+
+		if (subtitleSetIDPtr)
+			_subtitlePlayer.reset(new SubtitlePlayer(getRuntime(), *subtitleSetIDPtr, subTables));
+	}
+}
+
 void SoundElement::activate() {
 	Project *project = getRuntime()->getProject();
 	Common::SharedPtr<Asset> asset = project->getAssetByID(_assetID).lock();
@@ -2383,18 +2485,7 @@ void SoundElement::activate() {
 	if (_name.empty())
 		_name = project->getAssetNameByID(_assetID);
 
-	const SubtitleTables &subTables = project->getSubtitles();
-	if (subTables.assetMapping) {
-		const Common::String *subtitleSetIDPtr = subTables.assetMapping->findSubtitleSetForAssetID(_assetID);
-		if (!subtitleSetIDPtr) {
-			Common::String assetName = project->getAssetNameByID(_assetID);
-			if (assetName.size() > 0)
-				subtitleSetIDPtr = subTables.assetMapping->findSubtitleSetForAssetName(assetName);
-		}
-
-		if (subtitleSetIDPtr)
-			_subtitlePlayer.reset(new SubtitlePlayer(getRuntime(), *subtitleSetIDPtr, subTables));
-	}
+	initSubtitles();
 }
 
 
@@ -2490,6 +2581,14 @@ bool SoundElement::resolveMediaMarkerLabel(const Label &label, int32 &outResolut
 	return false;
 }
 
+Common::SharedPtr<Structural> SoundElement::shallowClone() const {
+	return Common::SharedPtr<Structural>(new SoundElement(*this));
+}
+
+void SoundElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	NonVisualElement::visitInternalReferences(visitor);
+}
+
 void SoundElement::stopPlayer() {
 	_player.reset();
 	if (_subtitlePlayer)
diff --git a/engines/mtropolis/elements.h b/engines/mtropolis/elements.h
index 2ba0e30975e..1bcb9e44d42 100644
--- a/engines/mtropolis/elements.h
+++ b/engines/mtropolis/elements.h
@@ -64,12 +64,17 @@ public:
 
 	void render(Window *window) override;
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Graphic Element"; }
 	SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
 #endif
 
 private:
+	GraphicElement(const GraphicElement &other);
+
 	bool _cacheBitmap;
 
 	Common::SharedPtr<Graphics::ManagedSurface> _mask;
@@ -105,6 +110,9 @@ public:
 
 	void setResizeFilter(const Common::SharedPtr<MovieResizeFilter> &filter);
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Movie Element"; }
 	SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
@@ -195,6 +203,9 @@ public:
 
 	void render(Window *window) override;
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Image Element"; }
 	SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
@@ -235,6 +246,9 @@ public:
 
 	Common::Rect getRelativeCollisionRect() const override;
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "mToon Element"; }
 	SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
@@ -242,6 +256,8 @@ public:
 #endif
 
 private:
+	MToonElement(const MToonElement &other);
+
 	struct StartPlayingTaskData {
 		StartPlayingTaskData() : runtime(nullptr) {}
 
@@ -336,12 +352,17 @@ public:
 	Graphics::FontManager::FontUsage getDefaultUsageForMacFont(uint16 macFontID, uint size);
 	Graphics::FontManager::FontUsage getDefaultUsageForNamedFont(const Common::String &fontFamilyName, uint size);
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Text Label Element"; }
 	SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; }
 #endif
 
 private:
+	TextLabelElement(const TextLabelElement &other);
+
 	struct TextLabelLineWriteInterface {
 		static MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset);
 		static MiniscriptInstructionOutcome refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib);
@@ -357,7 +378,7 @@ private:
 	bool _cacheBitmap;
 	bool _needsRender;
 
-	//bool _isBitmap;
+	bool _isBitmap;
 	uint32 _assetID;
 
 	Common::String _text;
@@ -396,6 +417,9 @@ public:
 
 	bool resolveMediaMarkerLabel(const Label &label, int32 &outResolution) const override;
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Sound Element"; }
 	SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
@@ -403,6 +427,9 @@ public:
 #endif
 
 private:
+	SoundElement(const SoundElement &other);
+
+	void initSubtitles();
 	void stopPlayer();
 
 	MiniscriptInstructionOutcome scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value);
diff --git a/engines/mtropolis/hacks.cpp b/engines/mtropolis/hacks.cpp
index 96164220414..3b52204a134 100644
--- a/engines/mtropolis/hacks.cpp
+++ b/engines/mtropolis/hacks.cpp
@@ -45,6 +45,7 @@ Hacks::Hacks() {
 	mtiVariableReferencesHack = false;
 	mtiSceneReturnHack = false;
 	mtiHispaniolaDamagedStringHack = false;
+	ignoreSceneUnloads = false;
 }
 
 Hacks::~Hacks() {
@@ -1172,6 +1173,7 @@ void SPQRSoundPreloadHooks::onProjectStarted(Runtime *runtime) {
 
 void addSPQRQuirks(const MTropolisGameDescription &desc, Hacks &hacks) {
 	hacks.addSceneTransitionHooks(Common::SharedPtr<SceneTransitionHooks>(new SPQRSoundPreloadHooks()));
+	hacks.ignoreSceneUnloads = true;
 }
 
 } // End of namespace HackSuites
diff --git a/engines/mtropolis/hacks.h b/engines/mtropolis/hacks.h
index c4acd69ac58..96a05c73683 100644
--- a/engines/mtropolis/hacks.h
+++ b/engines/mtropolis/hacks.h
@@ -56,6 +56,8 @@ struct Hacks {
 	bool mtiSceneReturnHack;
 	bool mtiHispaniolaDamagedStringHack;
 
+	bool ignoreSceneUnloads;
+
 	uint midiVolumeScale;	// 256 = 1.0
 
 	uint32 minTransitionDuration;
diff --git a/engines/mtropolis/modifiers.cpp b/engines/mtropolis/modifiers.cpp
index 055839b80eb..be0470eaf79 100644
--- a/engines/mtropolis/modifiers.cpp
+++ b/engines/mtropolis/modifiers.cpp
@@ -103,6 +103,15 @@ void BehaviorModifier::appendModifier(const Common::SharedPtr<Modifier> &modifie
 	modifier->setParent(getSelfReference());
 }
 
+void BehaviorModifier::removeModifier(const Modifier *modifier) {
+	for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _children.begin(), itEnd = _children.end(); it != itEnd; ++it) {
+		if (it->get() == modifier) {
+			_children.erase(it);
+			return;
+		}
+	}
+}
+
 IModifierContainer *BehaviorModifier::getMessagePropagationContainer() {
 	if (_isEnabled)
 		return this;
@@ -2818,6 +2827,15 @@ void CompoundVariableModifier::appendModifier(const Common::SharedPtr<Modifier>
 	modifier->setParent(getSelfReference());
 }
 
+void CompoundVariableModifier::removeModifier(const Modifier *modifier) {
+	for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _children.begin(), itEnd = _children.end(); it != itEnd; ++it) {
+		if (it->get() == modifier) {
+			_children.erase(it);
+			return;
+		}
+	}
+}
+
 void CompoundVariableModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
 	for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _children.begin(), itEnd = _children.end(); it != itEnd; ++it) {
 		visitor->visitChildModifierRef(*it);
diff --git a/engines/mtropolis/modifiers.h b/engines/mtropolis/modifiers.h
index 1545c6a2ca3..ce3590b1c27 100644
--- a/engines/mtropolis/modifiers.h
+++ b/engines/mtropolis/modifiers.h
@@ -48,6 +48,7 @@ public:
 
 	const Common::Array<Common::SharedPtr<Modifier> > &getModifiers() const override;
 	void appendModifier(const Common::SharedPtr<Modifier> &modifier) override;
+	void removeModifier(const Modifier *modifier) override;
 
 	IModifierContainer *getMessagePropagationContainer() override;
 	IModifierContainer *getChildContainer() override;
@@ -1152,6 +1153,7 @@ private:
 
 	const Common::Array<Common::SharedPtr<Modifier> > &getModifiers() const override;
 	void appendModifier(const Common::SharedPtr<Modifier> &modifier) override;
+	void removeModifier(const Modifier *modifier) override;
 	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
 
 	bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp
index a6b69025d96..2f993e2d36d 100644
--- a/engines/mtropolis/runtime.cpp
+++ b/engines/mtropolis/runtime.cpp
@@ -21,6 +21,7 @@
 
 #include "common/debug.h"
 #include "common/file.h"
+#include "common/hash-ptr.h"
 #include "common/macresman.h"
 #include "common/random.h"
 #include "common/substream.h"
@@ -253,6 +254,130 @@ void ModifierChildCloner::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifi
 	// Do nothing
 }
 
+class ObjectCloner : public IStructuralReferenceVisitor {
+public:
+	ObjectCloner(Runtime *runtime, const Common::WeakPtr<RuntimeObject> &relinkParent, Common::HashMap<RuntimeObject *, RuntimeObject *> *objectRemaps);
+
+	void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
+	void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
+	void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
+	void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
+
+private:
+	Runtime *_runtime;
+	Common::WeakPtr<RuntimeObject> _relinkParent;
+
+	Common::HashMap<RuntimeObject *, RuntimeObject *> *_objectRemaps;
+};
+
+ObjectCloner::ObjectCloner(Runtime *runtime, const Common::WeakPtr<RuntimeObject> &relinkParent, Common::HashMap<RuntimeObject *, RuntimeObject *> *objectRemaps)
+	: _runtime(runtime), _relinkParent(relinkParent), _objectRemaps(objectRemaps) {
+}
+
+void ObjectCloner::visitChildStructuralRef(Common::SharedPtr<Structural> &structuralRef) {
+	uint32 oldGUID = structuralRef->getStaticGUID();
+	Common::SharedPtr<Structural> cloned = structuralRef->shallowClone();
+	assert(cloned->getStaticGUID() == oldGUID);
+
+	(void)oldGUID;
+
+	if (_objectRemaps)
+		(*_objectRemaps)[structuralRef.get()] = cloned.get();
+
+	assert(!_relinkParent.expired() && _relinkParent.lock()->isStructural());
+
+	cloned->setSelfReference(cloned);
+	cloned->setRuntimeGUID(_runtime->allocateRuntimeGUID());
+	cloned->setParent(static_cast<Structural *>(_relinkParent.lock().get()));
+
+	ObjectCloner recursiveCloner(_runtime, cloned, _objectRemaps);
+	cloned->visitInternalReferences(&recursiveCloner);
+
+	structuralRef = cloned;
+}
+
+void ObjectCloner::visitChildModifierRef(Common::SharedPtr<Modifier> &modifierRef) {
+	uint32 oldGUID = modifierRef->getStaticGUID();
+	Common::SharedPtr<Modifier> cloned = modifierRef->shallowClone();
+	assert(cloned->getStaticGUID() == oldGUID);
+
+	(void)oldGUID;
+
+	if (_objectRemaps)
+		(*_objectRemaps)[modifierRef.get()] = cloned.get();
+
+	cloned->setSelfReference(cloned);
+	cloned->setRuntimeGUID(_runtime->allocateRuntimeGUID());
+	cloned->setParent(_relinkParent);
+
+	ObjectCloner recursiveCloner(_runtime, cloned, _objectRemaps);
+	cloned->visitInternalReferences(&recursiveCloner);
+
+	modifierRef = cloned;
+}
+
+void ObjectCloner::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
+	// Do nothing
+}
+
+void ObjectCloner::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
+	// Do nothing
+}
+
+
+
+class ObjectRefRemapper : public IStructuralReferenceVisitor {
+public:
+	explicit ObjectRefRemapper(const Common::HashMap<RuntimeObject *, RuntimeObject *> &objectRemaps);
+
+	void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
+	void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
+	void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
+	void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
+
+private:
+	const Common::HashMap<RuntimeObject *, RuntimeObject *> &_objectRemaps;
+};
+
+ObjectRefRemapper::ObjectRefRemapper(const Common::HashMap<RuntimeObject *, RuntimeObject *> &objectRemaps) : _objectRemaps(objectRemaps) {
+}
+
+void ObjectRefRemapper::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
+	RuntimeObject *obj = structural.get();
+	if (obj) {
+		Common::HashMap<RuntimeObject *, RuntimeObject *> ::const_iterator it = _objectRemaps.find(obj);
+		if (it != _objectRemaps.end())
+			structural = it->_value->getSelfReference().lock().staticCast<Structural>();
+	}
+}
+
+void ObjectRefRemapper::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
+	RuntimeObject *obj = modifier.get();
+	if (obj) {
+		Common::HashMap<RuntimeObject *, RuntimeObject *>::const_iterator it = _objectRemaps.find(obj);
+		if (it != _objectRemaps.end())
+			modifier = it->_value->getSelfReference().lock().staticCast<Modifier>();
+	}
+}
+
+void ObjectRefRemapper::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
+	RuntimeObject *obj = structural.lock().get();
+	if (obj) {
+		Common::HashMap<RuntimeObject *, RuntimeObject *>::const_iterator it = _objectRemaps.find(obj);
+		if (it != _objectRemaps.end())
+			structural = it->_value->getSelfReference().staticCast<Structural>();
+	}
+}
+
+void ObjectRefRemapper::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
+	RuntimeObject *obj = modifier.lock().get();
+	if (obj) {
+		Common::HashMap<RuntimeObject *, RuntimeObject *>::const_iterator it = _objectRemaps.find(obj);
+		if (it != _objectRemaps.end())
+			modifier = it->_value->getSelfReference().staticCast<Modifier>();
+	}
+}
+
 char invariantToLower(char c) {
 	if (c >= 'A' && c <= 'Z')
 		return static_cast<char>(c - 'A' + 'a');
@@ -2645,6 +2770,15 @@ void SimpleModifierContainer::appendModifier(const Common::SharedPtr<Modifier> &
 		modifier->setParent(nullptr);
 }
 
+void SimpleModifierContainer::removeModifier(const Modifier *modifier) {
+	for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _modifiers.begin(), itEnd = _modifiers.end(); it != itEnd; ++it) {
+		if (it->get() == modifier) {
+			_modifiers.erase(it);
+			return;
+		}
+	}
+}
+
 void SimpleModifierContainer::clear() {
 	_modifiers.clear();
 }
@@ -2729,6 +2863,23 @@ MiniscriptInstructionOutcome RuntimeObject::writeRefAttribute(MiniscriptThread *
 		}
 	}
 
+	if (attrib == "clone") {
+		DynamicValueWriteFuncHelper<RuntimeObject, &RuntimeObject::scriptSetClone, false>::create(this, writeProxy);
+		return kMiniscriptInstructionOutcomeContinue;
+	}
+
+	if (attrib == "kill") {
+		DynamicValueWriteFuncHelper<RuntimeObject, &RuntimeObject::scriptSetKill, false>::create(this, writeProxy);
+		return kMiniscriptInstructionOutcomeContinue;
+	}
+
+	if (attrib == "parent") {
+		writeProxy.pod.ifc = DynamicValueWriteInterfaceGlue<ParentWriteProxyInterface>::getInstance();
+		writeProxy.pod.objectRef = this;
+		writeProxy.pod.ptrOrOffset = 0;
+		return kMiniscriptInstructionOutcomeContinue;
+	}
+
 	return kMiniscriptInstructionOutcomeFailed;
 }
 
@@ -2736,6 +2887,46 @@ MiniscriptInstructionOutcome RuntimeObject::writeRefAttributeIndexed(MiniscriptT
 	return kMiniscriptInstructionOutcomeFailed;
 }
 
+MiniscriptInstructionOutcome RuntimeObject::scriptSetClone(MiniscriptThread *thread, const DynamicValue &value) {
+	thread->getRuntime()->queueCloneObject(this->getSelfReference());
+	return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome RuntimeObject::scriptSetKill(MiniscriptThread *thread, const DynamicValue &value) {
+	thread->getRuntime()->queueKillObject(this->getSelfReference());
+	return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome RuntimeObject::scriptSetParent(MiniscriptThread *thread, const DynamicValue &value) {
+	if (value.getType() != DynamicValueTypes::kObject) {
+		thread->error("Object couldn't be re-parented to a non-object");
+		return kMiniscriptInstructionOutcomeFailed;
+	}
+
+	thread->getRuntime()->queueChangeObjectParent(this->getSelfReference(), value.getObject().object);
+
+	return kMiniscriptInstructionOutcomeContinue;
+}
+
+// Need special handling of "parent" property, assigns indirect the value but writes re-parent the object
+MiniscriptInstructionOutcome RuntimeObject::ParentWriteProxyInterface::write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset) {
+	return static_cast<RuntimeObject *>(objectRef)->scriptSetParent(thread, dest);
+}
+
+MiniscriptInstructionOutcome RuntimeObject::ParentWriteProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
+	DynamicValueWriteProxy tempProxy;
+	DynamicValueWriteObjectHelper::create(static_cast<RuntimeObject *>(objectRef), tempProxy);
+
+	return tempProxy.pod.ifc->refAttrib(thread, proxy, tempProxy.pod.objectRef, tempProxy.pod.ptrOrOffset, attrib);
+}
+
+MiniscriptInstructionOutcome RuntimeObject::ParentWriteProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
+	DynamicValueWriteProxy tempProxy;
+	DynamicValueWriteObjectHelper::create(static_cast<RuntimeObject *>(objectRef), tempProxy);
+
+	return tempProxy.pod.ifc->refAttribIndexed(thread, proxy, tempProxy.pod.objectRef, tempProxy.pod.ptrOrOffset, attrib, index);
+}
+
 MessageProperties::MessageProperties(const Event &evt, const DynamicValue &value, const Common::WeakPtr<RuntimeObject> &source)
 	: _evt(evt), _value(value), _source(source) {
 }
@@ -3089,6 +3280,11 @@ Structural::Structural() : Structural(nullptr) {
 Structural::Structural(Runtime *runtime) : _parent(nullptr), _paused(false), _loop(false), _flushPriority(0), _runtime(runtime) {
 }
 
+Structural::Structural(const Structural &other)
+	: RuntimeObject(other), _parent(other._parent), _children(other._children), _modifiers(other._modifiers), _name(other._name), _assets(other._assets)
+	, _paused(other._paused), _loop(other._loop), _flushPriority(other._flushPriority)/*, _hooks(other._hooks)*/, _runtime(other._runtime) {
+}
+
 Structural::~Structural() {
 }
 
@@ -3100,6 +3296,14 @@ const Common::SharedPtr<StructuralHooks> &Structural::getHooks() const {
 	return _hooks;
 }
 
+void Structural::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	for (Common::SharedPtr<Structural> &child : _children)
+		visitor->visitChildStructuralRef(child);
+
+	for (Common::SharedPtr<Modifier> &child : _modifiers)
+		visitor->visitChildModifierRef(child);
+}
+
 bool Structural::isStructural() const {
 	return true;
 }
@@ -3298,15 +3502,6 @@ MiniscriptInstructionOutcome Structural::writeRefAttribute(MiniscriptThread *thr
 	} else if (attrib == "system") {
 		DynamicValueWriteObjectHelper::create(thread->getRuntime()->getSystemInterface(), result);
 		return kMiniscriptInstructionOutcomeContinue;
-	} else if (attrib == "parent") {
-		// NOTE: Re-parenting objects is allowed by mTropolis but we don't currently support that.
-		Structural *parent = getParent();
-		if (parent) {
-			DynamicValueWriteObjectHelper::create(getParent(), result);
-			return kMiniscriptInstructionOutcomeContinue;
-		} else {
-			return kMiniscriptInstructionOutcomeFailed;
-		}
 	} else if (attrib == "next") {
 		Structural *sibling = findNextSibling();
 		if (sibling) {
@@ -3488,6 +3683,15 @@ void Structural::appendModifier(const Common::SharedPtr<Modifier> &modifier) {
 	modifier->setParent(getSelfReference());
 }
 
+void Structural::removeModifier(const Modifier *modifier) {
+	for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _modifiers.begin(), itEnd = _modifiers.end(); it != itEnd; ++it) {
+		if (it->get() == modifier) {
+			_modifiers.erase(it);
+			return;
+		}
+	}
+}
+
 bool Structural::respondsToEvent(const Event &evt) const {
 	return false;
 }
@@ -3862,6 +4066,10 @@ HighLevelSceneTransition::HighLevelSceneTransition(const Common::SharedPtr<Struc
 	: scene(hlst_scene), type(hlst_type), addToDestinationScene(hlst_addToDestinationScene), addToReturnList(hlst_addToReturnList) {
 }
 
+ObjectParentChange::ObjectParentChange(const Common::WeakPtr<RuntimeObject> &object, const Common::WeakPtr<RuntimeObject> &newParent)
+	: _object(object), _newParent(newParent) {
+}
+
 SceneTransitionEffect::SceneTransitionEffect()
 	: _duration(100000), _steps(64), _transitionType(SceneTransitionTypes::kNone), _transitionDirection(SceneTransitionDirections::kUp) {
 }
@@ -4635,6 +4843,57 @@ bool Runtime::runFrame() {
 			continue;
 		}
 
+		// The order of operations for dynamic object behaviors is:
+		// - Parent changes
+		// - Parent Enabled -> Clone for each cloned object
+		// - Show for each cloned object that is visible
+		// - Hide -> Kill -> Parent Disabled for each killed object
+		if (_pendingParentChanges.size() > 0) {
+			ObjectParentChange parentChange = _pendingParentChanges.remove_at(0);
+
+			RuntimeObject *obj = parentChange._object.lock().get();
+			RuntimeObject *newParent = parentChange._newParent.lock().get();
+
+			if (obj) {
+				if (newParent)
+					executeChangeObjectParent(obj, newParent);
+				else
+					warning("Object re-parenting failed, the new parent was invalid!");
+			}
+
+			continue;
+		}
+
+		if (_pendingClones.size() > 0) {
+			RuntimeObject *objectToClone = _pendingClones.remove_at(0).lock().get();
+
+			if (objectToClone)
+				executeCloneObject(objectToClone);
+
+			continue;
+		}
+
+		if (_pendingShowClonedObject.size() > 0) {
+			Structural *objectToShow = _pendingShowClonedObject.remove_at(0).lock().get();
+
+			if (objectToShow && objectToShow->isElement() && static_cast<Element *>(objectToShow)->isVisual() && static_cast<VisualElement *>(objectToShow)->isVisible()) {
+				Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementShow, 0), DynamicValue(), objectToShow->getSelfReference()));
+				Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, objectToShow, false, true, false));
+				sendMessageOnVThread(dispatch);
+			}
+
+			continue;
+		}
+
+		if (_pendingKills.size() > 0) {
+			RuntimeObject *objectToKill = _pendingKills.remove_at(0).lock().get();
+
+			if (objectToKill)
+				executeKillObject(objectToKill);
+
+			continue;
+		}
+
 		// Teardowns must only occur during idle conditions where there are no VThread tasks
 		if (_pendingTeardowns.size() > 0) {
 			for (Common::Array<Teardown>::const_iterator it = _pendingTeardowns.begin(), itEnd = _pendingTeardowns.end(); it != itEnd; ++it) {
@@ -4945,29 +5204,42 @@ Common::SharedPtr<Structural> Runtime::findDefaultSharedSceneForScene(Structural
 }
 
 void Runtime::executeTeardown(const Teardown &teardown) {
-	Common::SharedPtr<Structural> structural = teardown.structural.lock();
-	if (!structural)
-		return;	// Already gone
+	if (Common::SharedPtr<Structural> structural = teardown.structural.lock()) {
+		recursiveDeactivateStructural(structural.get());
 
-	recursiveDeactivateStructural(structural.get());
+		if (teardown.onlyRemoveChildren) {
+			structural->removeAllChildren();
+			structural->removeAllModifiers();
+			structural->removeAllAssets();
+		} else {
+			Structural *parent = structural->getParent();
 
-	if (teardown.onlyRemoveChildren) {
-		structural->removeAllChildren();
-		structural->removeAllModifiers();
-		structural->removeAllAssets();
-	} else {
-		Structural *parent = structural->getParent();
+			// Nothing should be holding strong references to structural objects after they're removed from the project
+			assert(parent != nullptr);
 
-		// Nothing should be holding strong references to structural objects after they're removed from the project
-		assert(parent != nullptr);
+			if (!parent) {
+				return; // Already unlinked but still alive somehow
+			}
 
-		if (!parent) {
-			return; // Already unlinked but still alive somehow
+			parent->removeChild(structural.get());
+
+			structural->setParent(nullptr);
 		}
+	}
+
+	if (Common::SharedPtr<Modifier> modifier = teardown.modifier.lock()) {
+		IModifierContainer *container = nullptr;
+		RuntimeObject *parent = modifier->getParent().lock().get();
 
-		parent->removeChild(structural.get());
+		if (parent) {
+			if (parent->isStructural())
+				container = static_cast<Structural *>(parent);
+			else if (parent->isModifier())
+				container = static_cast<Modifier *>(parent)->getChildContainer();
+		}
 
-		structural->setParent(nullptr);
+		if (container)
+			container->removeModifier(modifier.get());
 	}
 }
 
@@ -5020,6 +5292,190 @@ void Runtime::executeSceneChangeRecursiveVisibilityChange(Structural *structural
 	}
 }
 
+void Runtime::executeChangeObjectParent(RuntimeObject *object, RuntimeObject *newParent) {
+	// TODO: Should do circularity checks
+
+	if (object->isModifier()) {
+		Common::SharedPtr<Modifier> modifier = object->getSelfReference().lock().staticCast<Modifier>();
+
+		IModifierContainer *oldParentContainer = nullptr;
+
+		RuntimeObject *oldParent = modifier->getParent().lock().get();
+
+		if (oldParent == newParent)
+			return;
+
+		if (oldParent->isStructural())
+			oldParentContainer = static_cast<Structural *>(oldParent);
+		else if (oldParent->isModifier())
+			oldParentContainer = static_cast<Modifier *>(oldParent)->getChildContainer();
+
+		IModifierContainer *newParentContainer = nullptr;
+		if (newParent->isStructural())
+			newParentContainer = static_cast<Structural *>(newParent);
+		else if (newParent->isModifier())
+			newParentContainer = static_cast<Modifier *>(newParent)->getChildContainer();
+
+		if (!newParentContainer) {
+			warning("Object re-parent failed, the new parent isn't a modifier container");
+			return;
+		}
+
+		oldParentContainer->removeModifier(modifier.get());
+		newParentContainer->appendModifier(modifier);
+
+		modifier->setParent(newParent->getSelfReference());
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentChanged, 0), DynamicValue(), modifier->getSelfReference()));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifier.get(), false, false, false));
+			sendMessageOnVThread(dispatch);
+		}
+	}
+
+	if (object->isStructural()) {
+		Common::SharedPtr<Structural> structural = object->getSelfReference().lock().staticCast<Structural>();
+
+		Structural *oldParent = structural->getParent();
+
+		if (oldParent == newParent)
+			return;
+
+		if (!newParent->isStructural()) {
+			warning("Object re-parent failed, the new parent isn't structural");
+			return;
+		}
+
+		Structural *newParentStructural = static_cast<Structural *>(newParent);
+
+		oldParent->removeChild(structural.get());
+		newParentStructural->addChild(structural);
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentChanged, 0), DynamicValue(), structural->getSelfReference()));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structural.get(), false, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+	}
+}
+
+void Runtime::executeCloneObject(RuntimeObject *object) {
+	Common::HashMap<RuntimeObject *, RuntimeObject *> objectRemaps;
+
+	if (object->isModifier()) {
+		Common::SharedPtr<Modifier> modifierRef = object->getSelfReference().lock().staticCast<Modifier>();
+		Common::WeakPtr<RuntimeObject> relinkParent = modifierRef->getParent();
+
+		ObjectCloner cloner(this, relinkParent, &objectRemaps);
+		cloner.visitChildModifierRef(modifierRef);
+
+		ObjectRefRemapper remapper(objectRemaps);
+		remapper.visitChildModifierRef(modifierRef);
+
+		IModifierContainer *container = nullptr;
+		Common::SharedPtr<RuntimeObject> parent = relinkParent.lock();
+		if (parent) {
+			if (parent->isStructural())
+				container = static_cast<Structural *>(parent.get());
+			else if (parent->isModifier())
+				container = static_cast<Modifier *>(parent.get())->getChildContainer();
+		}
+
+		if (container)
+			container->appendModifier(modifierRef);
+		else
+			error("Internal error: Cloned a modifier, but the parent isn't a modifeir container");
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kClone, 0), DynamicValue(), modifierRef));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifierRef.get(), true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentEnabled, 0), DynamicValue(), modifierRef));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifierRef.get(), true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+	} else if (object->isStructural()) {
+		Common::SharedPtr<Structural> structuralRef = object->getSelfReference().lock().staticCast<Structural>();
+		Common::WeakPtr<RuntimeObject> relinkParent = structuralRef->getParent()->getSelfReference();
+
+		ObjectCloner cloner(this, relinkParent, &objectRemaps);
+		cloner.visitChildStructuralRef(structuralRef);
+
+		ObjectRefRemapper remapper(objectRemaps);
+		remapper.visitChildStructuralRef(structuralRef);
+
+		structuralRef->getParent()->addChild(structuralRef);
+
+		_pendingPostCloneShowChecks.push_back(structuralRef);
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kClone, 0), DynamicValue(), structuralRef));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structuralRef.get(), true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentEnabled, 0), DynamicValue(), structuralRef));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structuralRef.get(), true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+	} else
+		error("Internal error: Cloned something unusual");
+}
+
+void Runtime::executeKillObject(RuntimeObject *object) {
+	// TODO: Should do circularity checks
+
+	if (object->isModifier()) {
+		Modifier *modifier = static_cast<Modifier *>(object);
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentDisabled, 0), DynamicValue(), modifier->getSelfReference()));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifier, true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kKill, 0), DynamicValue(), modifier->getSelfReference()));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifier, true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+
+		Teardown teardown;
+		teardown.modifier = modifier->getSelfReference().lock().staticCast<Modifier>();
+
+		_pendingTeardowns.push_back(teardown);
+	}
+
+	if (object->isStructural()) {
+		Structural *structural = static_cast<Structural *>(object);
+
+		// Task order is LIFO, so order is Hide -> Kill -> Parent Disabled
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentDisabled, 0), DynamicValue(), structural->getSelfReference()));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structural, true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+
+		{
+			Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kKill, 0), DynamicValue(), structural->getSelfReference()));
+			Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structural, true, true, false));
+			sendMessageOnVThread(dispatch);
+		}
+
+		if (structural->isElement() && static_cast<Element *>(structural)->isVisual())
+			static_cast<VisualElement *>(structural)->pushVisibilityChangeTask(this, false);
+
+		Teardown teardown;
+		teardown.structural = structural->getSelfReference().lock().staticCast<Structural>();
+
+		_pendingTeardowns.push_back(teardown);
+	}
+}
+
 void Runtime::executeCompleteTransitionToScene(const Common::SharedPtr<Structural> &targetScene) {
 	// NOTE: Transitioning to the same scene is allowed, Obsidian relies on this to avoid getting stuck
 	// after going up the wrong side in the Bureau chapter final area (i.e. after reaching the sky face).
@@ -6665,6 +7121,21 @@ bool Runtime::isIdle() const {
 	if (_pendingLowLevelTransitions.size() > 0)
 		return false;
 
+	if (_pendingClones.size() > 0)
+		return false;
+
+	if (_pendingPostCloneShowChecks.size() > 0)
+		return false;
+
+	if (_pendingShowClonedObject.size() > 0)
+		return false;
+
+	if (_pendingParentChanges.size() > 0)
+		return false;
+
+	if (_pendingKills.size() > 0)
+		return false;
+
 	if (_messageQueue.size() > 0)
 		return false;
 
@@ -6681,6 +7152,31 @@ const Common::SharedPtr<SubtitleRenderer> &Runtime::getSubtitleRenderer() const
 	return _subtitleRenderer;
 }
 
+void Runtime::queueCloneObject(const Common::WeakPtr<RuntimeObject> &obj) {
+	Common::SharedPtr<RuntimeObject> ptr = obj.lock();
+
+	// Cloning the same object multiple times doesn't work
+	for (const Common::WeakPtr<RuntimeObject> &candidate : _pendingClones)
+		if (candidate.lock() == ptr)
+			return;
+
+	_pendingClones.push_back(obj);
+}
+
+void Runtime::queueKillObject(const Common::WeakPtr<RuntimeObject> &obj) {
+	Common::SharedPtr<RuntimeObject> ptr = obj.lock();
+
+	for (const Common::WeakPtr<RuntimeObject> &candidate : _pendingKills)
+		if (candidate.lock() == ptr)
+			return;
+
+	_pendingKills.push_back(obj);
+}
+
+void Runtime::queueChangeObjectParent(const Common::WeakPtr<RuntimeObject> &obj, const Common::WeakPtr<RuntimeObject> &newParent) {
+	_pendingParentChanges.push_back(ObjectParentChange(obj, newParent));
+}
+
 void Runtime::ensureMainWindowExists() {
 	// Maybe there's a better spot for this
 	if (_mainWindow.expired() && _project) {
@@ -7709,6 +8205,15 @@ ProjectPlatform Project::getPlatform() const {
 	return _platform;
 }
 
+void Project::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	error("Cloning a project is not supported");
+}
+
+Common::SharedPtr<Structural> Project::shallowClone() const {
+	error("Cloning a project is not supported");
+	return nullptr;
+}
+
 void Project::loadPresentationSettings(const Data::PresentationSettings &presentationSettings) {
 	_presentationSettings.bitsPerPixel = presentationSettings.bitsPerPixel;
 	if (_presentationSettings.bitsPerPixel != 8 && _presentationSettings.bitsPerPixel != 16) {
@@ -8143,6 +8648,15 @@ bool Section::isSection() const {
 	return true;
 }
 
+Common::SharedPtr<Structural> Section::shallowClone() const {
+	error("Cloning sections is not supported");
+	return nullptr;
+}
+
+void Section::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	error("Cloning sections is not supported");
+}
+
 ObjectLinkingScope *Section::getPersistentStructuralScope() {
 	return &_structuralScope;
 }
@@ -8166,6 +8680,15 @@ bool Subsection::isSubsection() const {
 	return true;
 }
 
+Common::SharedPtr<Structural> Subsection::shallowClone() const {
+	error("Cloning subsections is not supported");
+	return nullptr;
+}
+
+void Subsection::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	error("Cloning subsections is not supported");
+}
+
 ObjectLinkingScope *Subsection::getSceneLoadMaterializeScope() {
 	return getPersistentStructuralScope();
 }
@@ -8181,6 +8704,12 @@ ObjectLinkingScope *Subsection::getPersistentModifierScope() {
 Element::Element() : _streamLocator(0), _sectionID(0), _haveCheckedAutoPlay(false) {
 }
 
+Element::Element(const Element &other)
+	: Structural(other), _streamLocator(other._streamLocator), _sectionID(other._sectionID)
+	// Don't copy checked autoplay or mediacues lists
+	, _haveCheckedAutoPlay(false), _mediaCues() {
+}
+
 bool Element::canAutoPlay() const {
 	return false;
 }
@@ -8227,6 +8756,10 @@ bool Element::resolveMediaMarkerLabel(const Label& label, int32 &outResolution)
 	return false;
 }
 
+void Element::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	Structural::visitInternalReferences(visitor);
+}
+
 VisualElementTransitionProperties::VisualElementTransitionProperties() : _isDirty(true), _alpha(255) {
 }
 
@@ -8363,6 +8896,14 @@ VisualElement::VisualElement()
 	  _topLeftBevelShading(0), _bottomRightBevelShading(0), _interiorShading(0), _bevelSize(0) {
 }
 
+VisualElement::VisualElement(const VisualElement &other)
+	: Element(other), _directToScreen(other._directToScreen), _visible(other._visible), _visibleByDefault(other._visibleByDefault)
+	, _rect(other._rect), _cachedAbsoluteOrigin(other._cachedAbsoluteOrigin), _layer(other._layer), _topLeftBevelShading(other._topLeftBevelShading)
+	, _bottomRightBevelShading(other._bottomRightBevelShading), _interiorShading(other._interiorShading), _bevelSize(other._bevelSize)
+	, _dragProps(nullptr), _renderProps(other._renderProps), _primaryGraphicModifier(nullptr), _transitionProps(other._transitionProps)
+	, _palette(other._palette), _prevRect(other._prevRect), _contentsDirty(true) {
+}
+
 bool VisualElement::isVisual() const {
 	return true;
 }
@@ -8655,6 +9196,12 @@ MiniscriptInstructionOutcome VisualElement::writeRefAttribute(MiniscriptThread *
 		// Not sure what this does, MTI uses it frequently
 		DynamicValueWriteDiscardHelper::create(writeProxy);
 		return kMiniscriptInstructionOutcomeContinue;
+	} else if (attrib == "unload") {
+		if (getRuntime()->getHacks().ignoreSceneUnloads) {
+			DynamicValueWriteDiscardHelper::create(writeProxy);
+			return kMiniscriptInstructionOutcomeContinue;
+		} else
+			return kMiniscriptInstructionOutcomeFailed;
 	}
 
 	return Element::writeRefAttribute(thread, writeProxy, attrib);
@@ -8811,6 +9358,17 @@ const Common::SharedPtr<Palette> &VisualElement::getPalette() const {
 	return _palette;
 }
 
+void VisualElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	Element::visitInternalReferences(visitor);
+}
+
+
+void VisualElement::pushVisibilityChangeTask(Runtime * runtime, bool desiredVisibility) {
+	ChangeFlagTaskData *changeVisibilityTask = runtime->getVThread().pushTask("VisualElement::changeVisibilityTask", this, &VisualElement::changeVisibilityTask);
+	changeVisibilityTask->desiredFlag = true;
+	changeVisibilityTask->runtime = runtime;
+}
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 void VisualElement::debugInspect(IDebugInspectionReport *report) const {
 	report->declareDynamic("layer", Common::String::format("%i", static_cast<int>(_layer)));
diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h
index ac6f2d1e8da..65e1730f5dc 100644
--- a/engines/mtropolis/runtime.h
+++ b/engines/mtropolis/runtime.h
@@ -1354,6 +1354,13 @@ private:
 	Common::SharedPtr<MessageDispatch> _msg;
 };
 
+struct ObjectParentChange {
+	explicit ObjectParentChange(const Common::WeakPtr<RuntimeObject> &object, const Common::WeakPtr<RuntimeObject> &newParent);
+
+	Common::WeakPtr<RuntimeObject> _object;
+	Common::WeakPtr<RuntimeObject> _newParent;
+};
+
 struct HighLevelSceneTransition {
 	enum Type {
 		kTypeReturn,
@@ -1727,6 +1734,10 @@ public:
 
 	const Common::SharedPtr<SubtitleRenderer> &getSubtitleRenderer() const;
 
+	void queueCloneObject(const Common::WeakPtr<RuntimeObject> &obj);
+	void queueKillObject(const Common::WeakPtr<RuntimeObject> &obj);
+	void queueChangeObjectParent(const Common::WeakPtr<RuntimeObject> &obj, const Common::WeakPtr<RuntimeObject> &newParent);
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	void debugSetEnabled(bool enabled);
 	void debugBreak();
@@ -1752,6 +1763,7 @@ private:
 		Teardown();
 
 		Common::WeakPtr<Structural> structural;
+		Common::WeakPtr<Modifier> modifier;
 		bool onlyRemoveChildren;
 	};
 
@@ -1842,6 +1854,9 @@ private:
 	void executeCompleteTransitionToScene(const Common::SharedPtr<Structural> &scene);
 	void executeSharedScenePostSceneChangeActions();
 	void executeSceneChangeRecursiveVisibilityChange(Structural *structural, bool showing);
+	void executeChangeObjectParent(RuntimeObject *object, RuntimeObject *newParent);
+	void executeCloneObject(RuntimeObject *object);
+	void executeKillObject(RuntimeObject *object);
 
 	void recursiveAutoPlayMedia(Structural *structural);
 	void recursiveDeactivateStructural(Structural *structural);
@@ -1884,6 +1899,11 @@ private:
 
 	Common::Array<Teardown> _pendingTeardowns;
 	Common::Array<LowLevelSceneStateTransitionAction> _pendingLowLevelTransitions;
+	Common::Array<Common::WeakPtr<RuntimeObject> > _pendingKills;
+	Common::Array<Common::WeakPtr<RuntimeObject> > _pendingClones;
+	Common::Array<Common::WeakPtr<Structural> > _pendingPostCloneShowChecks;
+	Common::Array<Common::WeakPtr<Structural> > _pendingShowClonedObject;
+	Common::Array<ObjectParentChange> _pendingParentChanges;
 	Common::Array<HighLevelSceneTransition> _pendingSceneTransitions;
 	Common::Array<SceneStackEntry> _sceneStack;
 	Common::SharedPtr<Structural> _activeMainScene;
@@ -2005,12 +2025,14 @@ private:
 struct IModifierContainer : public IInterfaceBase {
 	virtual const Common::Array<Common::SharedPtr<Modifier> > &getModifiers() const = 0;
 	virtual void appendModifier(const Common::SharedPtr<Modifier> &modifier) = 0;
+	virtual void removeModifier(const Modifier *modifier) = 0;
 };
 
 class SimpleModifierContainer : public IModifierContainer {
 public:
 	const Common::Array<Common::SharedPtr<Modifier> > &getModifiers() const override;
 	void appendModifier(const Common::SharedPtr<Modifier> &modifier) override;
+	void removeModifier(const Modifier *modifier) override;
 
 	void clear();
 
@@ -2047,6 +2069,10 @@ public:
 	virtual MiniscriptInstructionOutcome writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib, const DynamicValue &index);
 
 protected:
+	MiniscriptInstructionOutcome scriptSetClone(MiniscriptThread *thread, const DynamicValue &value);
+	MiniscriptInstructionOutcome scriptSetKill(MiniscriptThread *thread, const DynamicValue &value);
+	MiniscriptInstructionOutcome scriptSetParent(MiniscriptThread *thread, const DynamicValue &value);
+
 	// This is the static GUID stored in the data, it is not guaranteed
 	// to be globally unique at runtime.  In particular, cloning an object
 	// and using aliased modifiers will cause multiple objects with the same
@@ -2054,6 +2080,12 @@ protected:
 	uint32 _guid;
 	uint32 _runtimeGUID;
 	Common::WeakPtr<RuntimeObject> _selfReference;
+
+	struct ParentWriteProxyInterface {
+		static MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset);
+		static MiniscriptInstructionOutcome refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib);
+		static MiniscriptInstructionOutcome refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index);
+	};
 };
 
 struct MessageProperties {
@@ -2181,6 +2213,7 @@ public:
 
 	const Common::Array<Common::SharedPtr<Modifier> > &getModifiers() const override;
 	void appendModifier(const Common::SharedPtr<Modifier> &modifier) override;
+	void removeModifier(const Modifier *modifier) override;
 
 	bool respondsToEvent(const Event &evt) const override;
 	VThreadState consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
@@ -2198,6 +2231,11 @@ public:
 	void setHooks(const Common::SharedPtr<StructuralHooks> &hooks);
 	const Common::SharedPtr<StructuralHooks> &getHooks() const;
 
+	// Shallow clones only need to copy the object.  Descendent copies are done using visitInternalReferences.
+	virtual Common::SharedPtr<Structural> shallowClone() const = 0;
+
+	virtual void visitInternalReferences(IStructuralReferenceVisitor *visitor);
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	SupportStatus debugGetSupportStatus() const override;
 	const Common::String &debugGetName() const override;
@@ -2207,6 +2245,8 @@ public:
 #endif
 
 protected:
+	explicit Structural(const Structural &other);
+
 	virtual ObjectLinkingScope *getPersistentStructuralScope();
 	virtual ObjectLinkingScope *getPersistentModifierScope();
 
@@ -2450,6 +2490,9 @@ public:
 	RuntimeVersion getRuntimeVersion() const;
 	ProjectPlatform getPlatform() const;
 
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+	Common::SharedPtr<Structural> shallowClone() const override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Project"; }
 #endif
@@ -2580,6 +2623,9 @@ public:
 
 	bool isSection() const override;
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Section"; }
 #endif
@@ -2602,6 +2648,9 @@ public:
 
 	bool isSubsection() const override;
 
+	Common::SharedPtr<Structural> shallowClone() const override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Subsection"; }
 #endif
@@ -2633,7 +2682,11 @@ public:
 
 	virtual bool resolveMediaMarkerLabel(const Label &label, int32 &outResolution) const;
 
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 protected:
+	Element(const Element &other);
+
 	uint32 _streamLocator;
 	uint16 _sectionID;
 
@@ -2813,11 +2866,17 @@ public:
 	void setPalette(const Common::SharedPtr<Palette> &palette);
 	const Common::SharedPtr<Palette> &getPalette() const;
 
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
+	void pushVisibilityChangeTask(Runtime *runtime, bool desiredVisibility);
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	void debugInspect(IDebugInspectionReport *report) const override;
 #endif
 
 protected:
+	VisualElement(const VisualElement &other);
+
 	bool loadCommon(const Common::String &name, uint32 guid, const Data::Rect &rect, uint32 elementFlags, uint16 layer, uint32 streamLocator, uint16 sectionID);
 
 	MiniscriptInstructionOutcome scriptSetDirect(MiniscriptThread *thread, const DynamicValue &dest);




More information about the Scummvm-git-logs mailing list