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

elasota noreply at scummvm.org
Thu Jul 21 22:23:10 UTC 2022


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:
011c4ccd79 MTROPOLIS: Add element fade transitions.
d2bcab3b06 MTROPOLIS: Improve autosave behavior.


Commit: 011c4ccd79df803d9720a09f176e0d7a98ca49a2
    https://github.com/scummvm/scummvm/commit/011c4ccd79df803d9720a09f176e0d7a98ca49a2
Author: elasota (ejlasota at gmail.com)
Date: 2022-07-21T18:22:14-04:00

Commit Message:
MTROPOLIS: Add element fade transitions.

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


diff --git a/engines/mtropolis/elements.cpp b/engines/mtropolis/elements.cpp
index e818c039f91..6814df980ce 100644
--- a/engines/mtropolis/elements.cpp
+++ b/engines/mtropolis/elements.cpp
@@ -1040,11 +1040,18 @@ void ImageElement::render(Window *window) {
 		Common::Rect srcRect(optimized->w, optimized->h);
 		Common::Rect destRect(_cachedAbsoluteOrigin.x, _cachedAbsoluteOrigin.y, _cachedAbsoluteOrigin.x + _rect.width(), _cachedAbsoluteOrigin.y + _rect.height());
 
+		uint8 alpha = _transitionProps.getAlpha();
+
 		if (inkMode == VisualElementRenderProperties::kInkModeBackgroundMatte || inkMode == VisualElementRenderProperties::kInkModeBackgroundTransparent) {
 			const ColorRGB8 transColorRGB8 = _renderProps.getBackColor();
 			uint32 transColor = optimized->format.ARGBToColor(255, transColorRGB8.r, transColorRGB8.g, transColorRGB8.b);
-			window->getSurface()->transBlitFrom(*optimized, srcRect, destRect, transColor);
+			window->getSurface()->transBlitFrom(*optimized, srcRect, destRect, transColor, false, 0, alpha);
 		} else if (inkMode == VisualElementRenderProperties::kInkModeDefault || inkMode == VisualElementRenderProperties::kInkModeCopy) {
+			if (alpha != 255) {
+				warning("Alpha fade was applied to a default or copy image, this isn't supported yet");
+				_transitionProps.setAlpha(255);
+			}
+
 			window->getSurface()->blitFrom(*optimized, srcRect, destRect);
 		} else {
 			warning("Unimplemented image ink mode");
diff --git a/engines/mtropolis/modifiers.cpp b/engines/mtropolis/modifiers.cpp
index 10291c26020..8e623f50da8 100644
--- a/engines/mtropolis/modifiers.cpp
+++ b/engines/mtropolis/modifiers.cpp
@@ -953,6 +953,17 @@ const char *SceneTransitionModifier::getDefaultName() const {
 	return "Scene Transition Modifier";
 }
 
+ElementTransitionModifier::ElementTransitionModifier() : _enableWhen(Event::create()), _disableWhen(Event::create()), _rate(0), _steps(0),
+	_transitionType(kTransitionTypeFade), _revealType(kRevealTypeReveal), _transitionStartTime(0), _currentStep(0) {
+}
+
+ElementTransitionModifier::~ElementTransitionModifier() {
+	if (_scheduledEvent) {
+		_scheduledEvent->cancel();
+		_scheduledEvent.reset();
+	}
+}
+
 bool ElementTransitionModifier::load(ModifierLoaderContext &context, const Data::ElementTransitionModifier &data) {
 	if (!loadTypicalHeader(data.modHeader))
 		return false;
@@ -1017,6 +1028,9 @@ VThreadState ElementTransitionModifier::consumeMessage(Runtime *runtime, const C
 		}
 
 		_scheduledEvent = runtime->getScheduler().scheduleMethod<ElementTransitionModifier, &ElementTransitionModifier::continueTransition>(runtime->getPlayTime() + 1, this);
+		_transitionStartTime = runtime->getPlayTime();
+		_currentStep = 0;
+		setTransitionProgress(0, _steps);
 
 		// Pushed tasks, so these are executed in reverse order (Show -> Transition Started)
 		{
@@ -1060,13 +1074,28 @@ const char *ElementTransitionModifier::getDefaultName() const {
 }
 
 void ElementTransitionModifier::continueTransition(Runtime *runtime) {
-	// TODO: Make this functional
-	completeTransition(runtime);
+	_scheduledEvent.reset();
+
+	const uint64 playTime = runtime->getPlayTime();
+	const uint64 timeSinceStart = playTime - _transitionStartTime;
+
+	uint32 step = static_cast<uint32>(timeSinceStart * _rate / 1000);
+
+	if (step >= _steps || _rate == 0) {
+		completeTransition(runtime);
+		return;
+	}
+
+	if (step != _currentStep) {
+		setTransitionProgress(step, _steps);
+		_currentStep = step;
+	}
+
+	runtime->setSceneGraphDirty();
+	_scheduledEvent = runtime->getScheduler().scheduleMethod<ElementTransitionModifier, &ElementTransitionModifier::continueTransition>(playTime + 1, this);
 }
 
 void ElementTransitionModifier::completeTransition(Runtime *runtime) {
-	_scheduledEvent.reset();
-
 	// Pushed tasks, so these are executed in reverse order (Hide -> Transition Ended)
 	{
 		Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kTransitionEnded, 0), DynamicValue(), getSelfReference()));
@@ -1079,6 +1108,31 @@ void ElementTransitionModifier::completeTransition(Runtime *runtime) {
 		Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, findStructuralOwner(), false, false, true));
 		runtime->sendMessageOnVThread(dispatch);
 	}
+
+	setTransitionProgress(( _revealType == kRevealTypeReveal) ? 1 : 0, 1);
+	runtime->setSceneGraphDirty();
+}
+
+void ElementTransitionModifier::setTransitionProgress(uint32 step, uint32 maxSteps) {
+	Structural *structural = findStructuralOwner();
+	if (structural && structural->isElement() && static_cast<Element *>(structural)->isVisual()) {
+		VisualElement *visual = static_cast<VisualElement *>(structural);
+		VisualElementTransitionProperties props = visual->getTransitionProperties();
+
+		if (_transitionType == kTransitionTypeFade) {
+			if (step > maxSteps)
+				step = maxSteps;
+
+			uint32 alpha = step * 255 / maxSteps;
+			if (_revealType == kRevealTypeConceal)
+				alpha = 255 - alpha;
+
+			props.setAlpha(alpha);
+			visual->setTransitionProperties(props);
+		} else {
+			warning("Unsupported transition type");
+		}
+	}
 }
 
 bool IfMessengerModifier::load(ModifierLoaderContext &context, const Data::IfMessengerModifier &data) {
diff --git a/engines/mtropolis/modifiers.h b/engines/mtropolis/modifiers.h
index e6c14906870..9ba409b8754 100644
--- a/engines/mtropolis/modifiers.h
+++ b/engines/mtropolis/modifiers.h
@@ -383,6 +383,9 @@ private:
 
 class ElementTransitionModifier : public Modifier {
 public:
+	ElementTransitionModifier();
+	~ElementTransitionModifier();
+
 	bool load(ModifierLoaderContext &context, const Data::ElementTransitionModifier &data);
 
 	bool respondsToEvent(const Event &evt) const override;
@@ -412,14 +415,19 @@ private:
 	void continueTransition(Runtime *runtime);
 	void completeTransition(Runtime *runtime);
 
+	void setTransitionProgress(uint32 steps, uint32 maxSteps);
+
 	Event _enableWhen;
 	Event _disableWhen;
 
-	uint32 _rate;	// 1-100, higher is faster
+	uint32 _rate;	// Steps per second
 	uint16 _steps;
 	TransitionType _transitionType;
 	RevealType _revealType;
 
+	uint64 _transitionStartTime;
+	uint32 _currentStep;
+
 	Common::SharedPtr<ScheduledEvent> _scheduledEvent;
 };
 
diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp
index c3503483e32..cf56fdc93c4 100644
--- a/engines/mtropolis/runtime.cpp
+++ b/engines/mtropolis/runtime.cpp
@@ -5692,6 +5692,38 @@ void Runtime::setSaveScreenshotOverride(const Common::SharedPtr<Graphics::Surfac
 	_saveScreenshotOverride = screenshot;
 }
 
+bool Runtime::isIdle() const {
+	// The runtime is idle if nothing is happening except for scheduled events and the OS queue
+	if (_vthread->hasTasks())
+		return false;
+
+	if (_sceneTransitionState != kSceneTransitionStateNotTransitioning)
+		return false;
+
+	if (_forceCursorRefreshOnce)
+		return false;
+
+	if (_queuedProjectDesc)
+		return false;
+
+	if (_pendingTeardowns.size() > 0)
+		return false;
+
+	if (_pendingLowLevelTransitions.size() > 0)
+		return false;
+
+	if (_messageQueue.size() > 0)
+		return false;
+
+	if (_pendingSceneTransitions.size() > 0)
+		return false;
+
+	if (_isQuitting)
+		return false;
+
+	return true;
+}
+
 void Runtime::ensureMainWindowExists() {
 	// Maybe there's a better spot for this
 	if (_mainWindow.expired() && _project) {
@@ -7004,6 +7036,26 @@ bool Element::resolveMediaMarkerLabel(const Label& label, int32 &outResolution)
 	return false;
 }
 
+VisualElementTransitionProperties::VisualElementTransitionProperties() : _isDirty(true), _alpha(255) {
+}
+
+uint8 VisualElementTransitionProperties::getAlpha() const {
+	return _alpha;
+}
+
+void VisualElementTransitionProperties::setAlpha(uint8 alpha) {
+	_isDirty = true;
+	_alpha = alpha;
+}
+
+bool VisualElementTransitionProperties::isDirty() const {
+	return _isDirty;
+}
+
+void VisualElementTransitionProperties::clearDirty() {
+	_isDirty = false;
+}
+
 VisualElementRenderProperties::VisualElementRenderProperties()
 	: _inkMode(kInkModeDefault), _shape(kShapeRect), _foreColor(ColorRGB8::create(0, 0, 0)), _backColor(ColorRGB8::create(255, 255, 255)),
 	  _borderColor(ColorRGB8::create(0, 0, 0)), _shadowColor(ColorRGB8::create(0, 0, 0)), _borderSize(0), _shadowSize(0), _isDirty(true) {
@@ -7459,6 +7511,14 @@ VThreadState VisualElement::offsetTranslateTask(const OffsetTranslateTaskData &d
 	return kVThreadReturn;
 }
 
+void VisualElement::setTransitionProperties(const VisualElementTransitionProperties &props) {
+	_transitionProps = props;
+}
+
+const VisualElementTransitionProperties &VisualElement::getTransitionProperties() const {
+	return _transitionProps;
+}
+
 void VisualElement::setRenderProperties(const VisualElementRenderProperties &props, const Common::WeakPtr<GraphicModifier> &primaryGraphicModifier) {
 	_renderProps = props;
 	_primaryGraphicModifier = primaryGraphicModifier;
diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h
index 1cfcb235d73..8c4dd9f0b11 100644
--- a/engines/mtropolis/runtime.h
+++ b/engines/mtropolis/runtime.h
@@ -100,6 +100,7 @@ struct IPlugInModifierFactory;
 struct IPlugInModifierFactoryAndDataFactory;
 struct IPostEffect;
 struct ISaveUIProvider;
+struct ISaveWriter;
 struct IStructuralReferenceVisitor;
 struct MessageProperties;
 struct ModifierLoaderContext;
@@ -1601,6 +1602,8 @@ public:
 	const Common::SharedPtr<Graphics::Surface> &getSaveScreenshotOverride() const;
 	void setSaveScreenshotOverride(const Common::SharedPtr<Graphics::Surface> &screenshot);
 
+	bool isIdle() const;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	void debugSetEnabled(bool enabled);
 	void debugBreak();
@@ -2400,6 +2403,21 @@ protected:
 	bool _haveCheckedAutoPlay;
 };
 
+class VisualElementTransitionProperties {
+public:
+	VisualElementTransitionProperties();
+
+	uint8 getAlpha() const;
+	void setAlpha(uint8 alpha);
+
+	bool isDirty() const;
+	void clearDirty();
+
+private:
+	uint8 _alpha;
+	bool _isDirty;
+};
+
 class VisualElementRenderProperties {
 public:
 	VisualElementRenderProperties();
@@ -2536,6 +2554,9 @@ public:
 	const VisualElementRenderProperties &getRenderProperties() const;
 	const Common::WeakPtr<GraphicModifier> &getPrimaryGraphicModifier() const;
 
+	void setTransitionProperties(const VisualElementTransitionProperties &props);
+	const VisualElementTransitionProperties &getTransitionProperties() const;
+
 	bool needsRender() const;
 	virtual void render(Window *window) = 0;
 	void finalizeRender();
@@ -2583,10 +2604,13 @@ protected:
 
 	Common::SharedPtr<DragMotionProperties> _dragProps;
 
-	// Quirk: When a graphic modifier is applied, it needs to be
+	// Quirk: When a graphic modifier is applied, it becomes the primary graphic modifier, and disabling it
+	// will only take effect if it's the primary graphic modifier.
 	VisualElementRenderProperties _renderProps;
 	Common::WeakPtr<GraphicModifier> _primaryGraphicModifier;
 
+	VisualElementTransitionProperties _transitionProps;
+
 	Common::Rect _prevRect;
 	bool _contentsDirty;
 };
diff --git a/engines/mtropolis/vthread.cpp b/engines/mtropolis/vthread.cpp
index 3623fc7b8e3..33b80308122 100644
--- a/engines/mtropolis/vthread.cpp
+++ b/engines/mtropolis/vthread.cpp
@@ -74,6 +74,10 @@ VThreadState VThread::step() {
 	return kVThreadReturn;
 }
 
+bool VThread::hasTasks() const {
+	return _used > 0;
+}
+
 void VThread::reserveFrame(size_t size, size_t alignment, void *&outFramePtr, void *&outUnadjustedDataPtr, size_t &outPrevFrameOffset) {
 	const size_t frameAlignment = alignof(VThreadStackFrame);
 	const size_t frameAlignmentMask = frameAlignment - 1;
diff --git a/engines/mtropolis/vthread.h b/engines/mtropolis/vthread.h
index 99c88aef040..915c5367f57 100644
--- a/engines/mtropolis/vthread.h
+++ b/engines/mtropolis/vthread.h
@@ -138,6 +138,8 @@ public:
 
 	VThreadState step();
 
+	bool hasTasks() const;
+
 private:
 	template<typename TClass, typename TData>
 	TData *pushTaskWithFaultHandler(const VThreadFaultIdentifier *faultID, const char *name, TClass *obj, VThreadState (TClass::*method)(const TData &data));


Commit: d2bcab3b06dc1df81c35e02e20845083e858af0e
    https://github.com/scummvm/scummvm/commit/d2bcab3b06dc1df81c35e02e20845083e858af0e
Author: elasota (ejlasota at gmail.com)
Date: 2022-07-21T18:22:15-04:00

Commit Message:
MTROPOLIS: Improve autosave behavior.

Changed paths:
    engines/mtropolis/detection.cpp
    engines/mtropolis/detection_tables.h
    engines/mtropolis/hacks.cpp
    engines/mtropolis/hacks.h
    engines/mtropolis/metaengine.cpp
    engines/mtropolis/mtropolis.cpp
    engines/mtropolis/mtropolis.h
    engines/mtropolis/saveload.cpp
    engines/mtropolis/saveload.h


diff --git a/engines/mtropolis/detection.cpp b/engines/mtropolis/detection.cpp
index b67612ce9a3..388b979f15d 100644
--- a/engines/mtropolis/detection.cpp
+++ b/engines/mtropolis/detection.cpp
@@ -60,11 +60,11 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 		}
 	},
 	{
-		GAMEOPTION_AUTO_SAVE,
+		GAMEOPTION_AUTO_SAVE_AT_CHECKPOINTS,
 		{
-			_s("Save progress automatically"),
-			_s("Automatically saves the game at certain progress points."),
-			"mtropolis_mod_auto_save",
+			_s("Autosave at progress checkpoints"),
+			_s("Automatically saves the game at progress."),
+			"mtropolis_mod_auto_save_at_checkpoints",
 			true,
 			0,
 			0
diff --git a/engines/mtropolis/detection_tables.h b/engines/mtropolis/detection_tables.h
index 0cd0eeed3e0..c92897ee35b 100644
--- a/engines/mtropolis/detection_tables.h
+++ b/engines/mtropolis/detection_tables.h
@@ -30,7 +30,7 @@
 #define GAMEOPTION_DYNAMIC_MIDI				GUIO_GAMEOPTIONS2
 #define GAMEOPTION_LAUNCH_DEBUG				GUIO_GAMEOPTIONS3
 #define GAMEOPTION_LAUNCH_BREAK				GUIO_GAMEOPTIONS4
-#define GAMEOPTION_AUTO_SAVE				GUIO_GAMEOPTIONS5
+#define GAMEOPTION_AUTO_SAVE_AT_CHECKPOINTS	GUIO_GAMEOPTIONS5
 #define GAMEOPTION_ENABLE_SHORT_TRANSITIONS GUIO_GAMEOPTIONS6
 
 namespace MTropolis {
@@ -53,7 +53,7 @@ static const MTropolisGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformMacintosh,
 			ADGF_UNSTABLE,
-			GUIO2(GAMEOPTION_WIDESCREEN_MOD, GAMEOPTION_AUTO_SAVE)
+			GUIO2(GAMEOPTION_WIDESCREEN_MOD, GAMEOPTION_AUTO_SAVE_AT_CHECKPOINTS)
 		},
 		GID_OBSIDIAN,
 		0,
@@ -80,7 +80,7 @@ static const MTropolisGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformWindows,
 			ADGF_UNSTABLE,
-			GUIO2(GAMEOPTION_WIDESCREEN_MOD, GAMEOPTION_AUTO_SAVE)
+			GUIO2(GAMEOPTION_WIDESCREEN_MOD, GAMEOPTION_AUTO_SAVE_AT_CHECKPOINTS)
 		},
 		GID_OBSIDIAN,
 		0,
diff --git a/engines/mtropolis/hacks.cpp b/engines/mtropolis/hacks.cpp
index 2ab5807dcbf..d3ac763042e 100644
--- a/engines/mtropolis/hacks.cpp
+++ b/engines/mtropolis/hacks.cpp
@@ -64,6 +64,10 @@ void Hacks::addSaveLoadHooks(const Common::SharedPtr<SaveLoadHooks> &hooks) {
 	saveLoadHooks.push_back(hooks);
 }
 
+void Hacks::addSaveLoadMechanismHooks(const Common::SharedPtr<SaveLoadMechanismHooks> &hooks) {
+	saveLoadMechanismHooks.push_back(hooks);
+}
+
 namespace HackSuites {
 
 class ObsidianCorruptedAirTowerTransitionFix : public AssetHooks {
@@ -914,6 +918,100 @@ void addObsidianAutoSaves(const MTropolisGameDescription &desc, Hacks &hacks, IA
 	hacks.addSaveLoadHooks(Common::SharedPtr<SaveLoadHooks>(new ObsidianAutoSaveSaveLoadHooks(varsState)));
 }
 
+class ObsidianSaveLoadMechanism : public SaveLoadMechanismHooks {
+public:
+	bool canSaveNow(Runtime *runtime) override;
+	Common::SharedPtr<ISaveWriter> createSaveWriter(Runtime *runtime) override;
+};
+
+bool ObsidianSaveLoadMechanism::canSaveNow(Runtime *runtime) {
+	Project *project = runtime->getProject();
+
+	// Check that we're in a game section
+	Structural *mainScene = runtime->getActiveMainScene().get();
+
+	if (!mainScene)
+		return false;
+
+	const Common::String disallowedSections[] = {
+		Common::String("Start Obsidian"),	// Intro videos/screens
+		Common::String("End Obsidian"),		// Credits
+		Common::String("GUI"),				// Menus
+	};
+
+	Common::String sectionName = mainScene->getParent()->getParent()->getName();
+
+	for (const Common::String &disallowedSection : disallowedSections) {
+		if (caseInsensitiveEqual(disallowedSection, sectionName))
+			return false;
+	}
+
+	// Check that the g.bESC flag is set, meaning we can go to the menu
+	Common::String gName("g");
+	Common::String bEscName("bESC");
+
+	Modifier *gCompoundVar = nullptr;
+	for (const Common::SharedPtr<Modifier> &child : project->getModifiers()) {
+		if (caseInsensitiveEqual(child->getName(), gName)) {
+			gCompoundVar = child.get();
+			break;
+		}
+	}
+
+	if (!gCompoundVar)
+		return false;
+
+	IModifierContainer *container = gCompoundVar->getChildContainer();
+	if (!container)
+		return false;
+
+	Modifier *bEscVar = nullptr;
+	for (const Common::SharedPtr<Modifier> &child : container->getModifiers()) {
+		if (caseInsensitiveEqual(child->getName(), bEscName)) {
+			bEscVar = child.get();
+			break;
+		}
+	}
+
+	if (!bEscVar || !bEscVar->isVariable())
+		return false;
+
+	DynamicValue bEscValue;
+	static_cast<VariableModifier *>(bEscVar)->varGetValue(nullptr, bEscValue);
+
+	if (bEscValue.getType() != DynamicValueTypes::kBoolean || !bEscValue.getBool())
+		return false;
+
+	return true;
+}
+
+Common::SharedPtr<ISaveWriter> ObsidianSaveLoadMechanism::createSaveWriter(Runtime *runtime) {
+	Project *project = runtime->getProject();
+
+	Common::String cgstName("cGSt");
+
+	Modifier *cgstCompoundVar = nullptr;
+	for (const Common::SharedPtr<Modifier> &child : project->getModifiers()) {
+		if (caseInsensitiveEqual(child->getName(), cgstName)) {
+			cgstCompoundVar = child.get();
+			break;
+		}
+	}
+
+	if (!cgstCompoundVar)
+		return nullptr;
+
+	if (cgstCompoundVar->getSaveLoad())
+		return Common::SharedPtr<CompoundVarSaver>(new CompoundVarSaver(cgstCompoundVar));
+
+	return nullptr;
+}
+
+void addObsidianSaveMechanism(const MTropolisGameDescription &desc, Hacks &hacks) {
+	Common::SharedPtr<ObsidianSaveLoadMechanism> mechanism(new ObsidianSaveLoadMechanism());
+	hacks.addSaveLoadMechanismHooks(mechanism);
+}
+
 } // End of namespace HackSuites
 
 } // End of namespace MTropolis
diff --git a/engines/mtropolis/hacks.h b/engines/mtropolis/hacks.h
index 8df373e9a99..94683e1cae8 100644
--- a/engines/mtropolis/hacks.h
+++ b/engines/mtropolis/hacks.h
@@ -33,6 +33,7 @@ class ModifierHooks;
 class SaveLoadHooks;
 class SceneTransitionHooks;
 class StructuralHooks;
+class SaveLoadMechanismHooks;
 struct IAutoSaveProvider;
 struct MTropolisGameDescription;
 
@@ -45,6 +46,7 @@ struct Hacks {
 	void addAssetHooks(const Common::SharedPtr<AssetHooks> &hooks);
 	void addSceneTransitionHooks(const Common::SharedPtr<SceneTransitionHooks> &hooks);
 	void addSaveLoadHooks(const Common::SharedPtr<SaveLoadHooks> &hooks);
+	void addSaveLoadMechanismHooks(const Common::SharedPtr<SaveLoadMechanismHooks> &hooks);
 
 	bool ignoreMismatchedProjectNameInObjectLookups;
 	uint midiVolumeScale;	// 256 = 1.0
@@ -59,6 +61,7 @@ struct Hacks {
 	Common::Array<Common::SharedPtr<SceneTransitionHooks> > sceneTransitionHooks;
 	Common::Array<Common::SharedPtr<AssetHooks> > assetHooks;
 	Common::Array<Common::SharedPtr<SaveLoadHooks> > saveLoadHooks;
+	Common::Array<Common::SharedPtr<SaveLoadMechanismHooks> > saveLoadMechanismHooks;
 };
 
 namespace HackSuites {
@@ -66,6 +69,7 @@ namespace HackSuites {
 void addObsidianQuirks(const MTropolisGameDescription &desc, Hacks &hacks);
 void addObsidianBugFixes(const MTropolisGameDescription &desc, Hacks &hacks);
 void addObsidianAutoSaves(const MTropolisGameDescription &desc, Hacks &hacks, IAutoSaveProvider *autoSaveProvider);
+void addObsidianSaveMechanism(const MTropolisGameDescription &desc, Hacks &hacks);
 void addObsidianImprovedWidescreen(const MTropolisGameDescription &desc, Hacks &hacks);
 
 } // End of namespace HackSuites
diff --git a/engines/mtropolis/metaengine.cpp b/engines/mtropolis/metaengine.cpp
index 107b787d609..e98feb03602 100644
--- a/engines/mtropolis/metaengine.cpp
+++ b/engines/mtropolis/metaengine.cpp
@@ -88,15 +88,6 @@ bool MTropolisMetaEngine::hasFeature(MetaEngineFeature f) const {
 	}
 }
 
-bool MTropolis::MTropolisEngine::hasFeature(EngineFeature f) const {
-	switch (f) {
-	case kSupportsReturnToLauncher:
-		return true;
-	default:
-		return false;
-	};
-}
-
 Common::Error MTropolisMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
 	*engine = new MTropolis::MTropolisEngine(syst, reinterpret_cast<const MTropolis::MTropolisGameDescription *>(desc));
 	return Common::kNoError;
diff --git a/engines/mtropolis/mtropolis.cpp b/engines/mtropolis/mtropolis.cpp
index 0b9e8e0f5de..b979208316d 100644
--- a/engines/mtropolis/mtropolis.cpp
+++ b/engines/mtropolis/mtropolis.cpp
@@ -50,7 +50,7 @@
 
 namespace MTropolis {
 
-MTropolisEngine::MTropolisEngine(OSystem *syst, const MTropolisGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
+MTropolisEngine::MTropolisEngine(OSystem *syst, const MTropolisGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc), _saveWriter(nullptr), _isTriggeredAutosave(false) {
 	const Common::FSNode gameDataDir(ConfMan.get("path"));
 	SearchMan.addSubDirectoryMatching(gameDataDir, "Resource");
 
@@ -121,8 +121,9 @@ Common::Error MTropolisEngine::run() {
 
 		HackSuites::addObsidianQuirks(*_gameDescription, _runtime->getHacks());
 		HackSuites::addObsidianBugFixes(*_gameDescription, _runtime->getHacks());
+		HackSuites::addObsidianSaveMechanism(*_gameDescription, _runtime->getHacks());
 
-		if (ConfMan.getBool("mtropolis_mod_auto_save"))
+		if (ConfMan.getBool("mtropolis_mod_auto_save_at_checkpoints"))
 			HackSuites::addObsidianAutoSaves(*_gameDescription, _runtime->getHacks(), this);
 
 		if (ConfMan.getBool("mtropolis_mod_obsidian_widescreen")) {
@@ -262,4 +263,16 @@ void MTropolisEngine::pauseEngineIntern(bool pause) {
 	Engine::pauseEngineIntern(pause);
 }
 
+
+
+bool MTropolisEngine::hasFeature(EngineFeature f) const {
+	switch (f) {
+	case kSupportsReturnToLauncher:
+	case kSupportsSavingDuringRuntime:
+		return true;
+	default:
+		return false;
+	};
+}
+
 } // End of namespace MTropolis
diff --git a/engines/mtropolis/mtropolis.h b/engines/mtropolis/mtropolis.h
index c014eb939de..027e0966c35 100644
--- a/engines/mtropolis/mtropolis.h
+++ b/engines/mtropolis/mtropolis.h
@@ -65,6 +65,10 @@ public:
 
 	const Graphics::Surface *getSavegameScreenshot() const;
 
+	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave) override;
+	bool canSaveAutosaveCurrently() override;
+	bool canSaveGameStateCurrently() override;	
+
 public:
 	void handleEvents();
 
@@ -75,6 +79,9 @@ private:
 	static const uint kCurrentSaveFileVersion = 1;
 	static const uint kSavegameSignature = 0x6d545356;	// mTSV
 
+	ISaveWriter *_saveWriter;
+	bool _isTriggeredAutosave;
+
 	Common::ScopedPtr<Runtime> _runtime;
 };
 
diff --git a/engines/mtropolis/saveload.cpp b/engines/mtropolis/saveload.cpp
index 7c05c2eb361..75d458e9c9c 100644
--- a/engines/mtropolis/saveload.cpp
+++ b/engines/mtropolis/saveload.cpp
@@ -58,6 +58,17 @@ void SaveLoadHooks::onLoad(Runtime *runtime, Modifier *saveLoadModifier, Modifie
 void SaveLoadHooks::onSave(Runtime *runtime, Modifier *saveLoadModifier, Modifier *varModifier) {
 }
 
+SaveLoadMechanismHooks::~SaveLoadMechanismHooks() {
+}
+
+bool SaveLoadMechanismHooks::canSaveNow(Runtime *runtime) {
+	return false;
+}
+
+Common::SharedPtr<ISaveWriter> SaveLoadMechanismHooks::createSaveWriter(Runtime *runtime) {
+	return nullptr;
+}
+
 bool MTropolisEngine::promptSave(ISaveWriter *writer, const Graphics::Surface *screenshotOverride) {
 	Common::String desc;
 	int slot;
@@ -78,11 +89,13 @@ bool MTropolisEngine::promptSave(ISaveWriter *writer, const Graphics::Surface *s
 	Common::String saveFileName = getSaveStateName(slot);
 	Common::SharedPtr<Common::OutSaveFile> out(_saveFileMan->openForSaving(saveFileName, false));
 
-	out->writeUint32BE(kSavegameSignature);
-	out->writeUint32BE(kCurrentSaveFileVersion);
+	ISaveWriter *oldWriter = _saveWriter;
+
+	_saveWriter = writer;
+
+	saveGameStream(out.get(), false);
 
-	if (!writer->writeSave(out.get()) || out->err())
-		warning("An error occurred while writing file '%s'", saveFileName.c_str());
+	_saveWriter = oldWriter;
 
 	getMetaEngine()->appendExtendedSave(out.get(), getTotalPlayTime(), desc, false);
 
@@ -142,20 +155,16 @@ bool MTropolisEngine::promptLoad(ISaveReader *reader) {
 }
 
 bool MTropolisEngine::autoSave(ISaveWriter *writer) {
-	const int slot = 0;
+	ISaveWriter *oldWriter = _saveWriter;
+	bool oldIsTriggeredAutosave = _isTriggeredAutosave;
 
-	Common::String saveFileName = getSaveStateName(slot);
-	Common::SharedPtr<Common::OutSaveFile> out(_saveFileMan->openForSaving(saveFileName, false));
-
-	out->writeUint32BE(kSavegameSignature);
-	out->writeUint32BE(kCurrentSaveFileVersion);
-
-	if (!writer->writeSave(out.get()) || out->err())
-		warning("An error occurred while writing file '%s'", saveFileName.c_str());
+	_saveWriter = writer;
+	_isTriggeredAutosave = true;
 
-	getMetaEngine()->appendExtendedSave(out.get(), getTotalPlayTime(), "Auto Save", true);
+	saveAutosaveIfEnabled();
 
-	g_system->displayMessageOnOSD(_("Progress Saved"));
+	_saveWriter = oldWriter;
+	_isTriggeredAutosave = oldIsTriggeredAutosave;
 
 	return true;
 }
@@ -172,4 +181,52 @@ const Graphics::Surface *MTropolisEngine::getSavegameScreenshot() const {
 	}
 }
 
+Common::Error MTropolisEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
+	ISaveWriter *saveWriter = _saveWriter;
+
+	Common::SharedPtr<ISaveWriter> mechanismHookWriter;
+	if (!saveWriter) {
+		for (Common::SharedPtr<SaveLoadMechanismHooks> &hooks : _runtime->getHacks().saveLoadMechanismHooks) {
+			if (hooks->canSaveNow(_runtime.get())) {
+				mechanismHookWriter = hooks->createSaveWriter(_runtime.get());
+				saveWriter = mechanismHookWriter.get();
+				break;
+			}
+		}
+	}
+
+	if (!saveWriter)
+		return Common::Error(Common::kWritingFailed, Common::convertFromU32String(_("An internal error occurred while attempting to write save game data")));
+
+	assert(saveWriter);
+
+	stream->writeUint32BE(kSavegameSignature);
+	stream->writeUint32BE(kCurrentSaveFileVersion);
+
+	if (!saveWriter->writeSave(stream) || stream->err())
+		return Common::Error(Common::kWritingFailed, Common::convertFromU32String(_("An error occurred while writing the save game")));
+
+	return Common::kNoError;
+}
+
+bool MTropolisEngine::canSaveAutosaveCurrently() {
+	// Triggered autosaves are always safe
+	if (_isTriggeredAutosave)
+		return true;
+
+	return canSaveGameStateCurrently();
+}
+
+bool MTropolisEngine::canSaveGameStateCurrently() {
+	if (!_runtime->isIdle())
+		return false;
+
+	for (Common::SharedPtr<SaveLoadMechanismHooks> &hooks : _runtime->getHacks().saveLoadMechanismHooks) {
+		if (hooks->canSaveNow(_runtime.get()))
+			return true;
+	}
+
+	return false;
+}
+
 } // End of namespace MTropolis
diff --git a/engines/mtropolis/saveload.h b/engines/mtropolis/saveload.h
index ea9bed54f48..bc94ead80d4 100644
--- a/engines/mtropolis/saveload.h
+++ b/engines/mtropolis/saveload.h
@@ -81,6 +81,14 @@ public:
 	virtual void onSave(Runtime *runtime, Modifier *saveLoadModifier, Modifier *varModifier);
 };
 
+class SaveLoadMechanismHooks {
+public:
+	virtual ~SaveLoadMechanismHooks();
+
+	virtual bool canSaveNow(Runtime *runtime);
+	virtual Common::SharedPtr<ISaveWriter> createSaveWriter(Runtime *runtime);
+};
+
 } // End of namespace MTropolis
 
 #endif /* MTROPOLIS_SAVELOAD_H */




More information about the Scummvm-git-logs mailing list