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

Helco noreply at scummvm.org
Sun Oct 19 14:49:08 UTC 2025


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

Summary:
baf314f907 ALCACHOFA: Add engine version to detection entries
e99af42413 ALCACHOFA: Add script translation tables for V3.0
a94fdf73bf ALCACHOFA: Reencode animation paths to UTF8
c489c0f061 ALCACHOFA: Replace dependencies on version-dependent script variables


Commit: baf314f90777e2787d908baa418df2f86a918c5c
    https://github.com/scummvm/scummvm/commit/baf314f90777e2787d908baa418df2f86a918c5c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-10-19T16:47:21+02:00

Commit Message:
ALCACHOFA: Add engine version to detection entries

Changed paths:
    engines/alcachofa/alcachofa.cpp
    engines/alcachofa/alcachofa.h
    engines/alcachofa/detection.cpp
    engines/alcachofa/detection.h
    engines/alcachofa/detection_tables.h
    engines/alcachofa/game-movie-adventure.cpp
    engines/alcachofa/metaengine.cpp
    engines/alcachofa/metaengine.h


diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 1e5d6b664f6..a7f387871c6 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -54,7 +54,7 @@ constexpr uint kDefaultFramerate = 100; // the original target framerate, not cr
 
 AlcachofaEngine *g_engine;
 
-AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc)
+AlcachofaEngine::AlcachofaEngine(OSystem *syst, const AlcachofaGameDescription *gameDesc)
 	: Engine(syst)
 	, _gameDescription(gameDesc)
 	, _eventLoopSemaphore("engine") {
@@ -68,11 +68,11 @@ AlcachofaEngine::~AlcachofaEngine() {
 }
 
 uint32 AlcachofaEngine::getFeatures() const {
-	return _gameDescription->flags;
+	return _gameDescription->desc.flags;
 }
 
 Common::String AlcachofaEngine::getGameId() const {
-	return _gameDescription->gameId;
+	return _gameDescription->desc.gameId;
 }
 
 Common::Error AlcachofaEngine::run() {
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 4d089d8dc4e..06de0f30ab4 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -108,10 +108,14 @@ protected:
 	// Engine APIs
 	Common::Error run() override;
 public:
-	AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc);
+	AlcachofaEngine(OSystem *syst, const AlcachofaGameDescription *gameDesc);
 	~AlcachofaEngine() override;
 
-	inline const ADGameDescription &gameDescription() const { return *_gameDescription; }
+	inline bool isV1() const { return gameDescription().isVersionBetween(10, 19); }
+	inline bool isV2() const { return gameDescription().isVersionBetween(20, 29); }
+	inline bool isV3() const { return gameDescription().isVersionBetween(30, 39); }
+
+	inline const AlcachofaGameDescription &gameDescription() const { return *_gameDescription; }
 	inline IRenderer &renderer() { return *_renderer; }
 	inline DrawQueue &drawQueue() { return *_drawQueue; }
 	inline Camera &camera() { return _camera; }
@@ -168,7 +172,7 @@ public:
 private:
 	bool tryLoadFromLauncher();
 
-	const ADGameDescription *_gameDescription;
+	const AlcachofaGameDescription *_gameDescription;
 	Console *_console = new Console();
 	Common::ScopedPtr<IDebugHandler> _debugHandler;
 	Common::ScopedPtr<IRenderer> _renderer;
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index f8e59c3ddf8..1a000192ffc 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -38,7 +38,7 @@ const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
 	DEBUG_CHANNEL_END
 };
 
-AlcachofaMetaEngineDetection::AlcachofaMetaEngineDetection() : AdvancedMetaEngineDetection<ADGameDescription>(
+AlcachofaMetaEngineDetection::AlcachofaMetaEngineDetection() : AdvancedMetaEngineDetection<Alcachofa::AlcachofaGameDescription>(
 	Alcachofa::gameDescriptions, Alcachofa::alcachofaGames) {
 	_flags |= kADFlagMatchFullPaths;
 }
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 982cc618159..0c8ce953738 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -34,16 +34,36 @@ enum AlcachofaDebugChannels {
 	kDebugSemaphores
 };
 
+enum class EngineVersion {
+	V1_0 = 10, // edicion orginal, vaqueros and terror
+	V2_0 = 20, // the rest
+	V3_0 = 30, // Remastered movie adventure (used for original spanish release)
+	V3_1 = 31, // Remastered movie adventure (for german release and english/spanish steam release)
+};
+
+struct AlcachofaGameDescription {
+	AD_GAME_DESCRIPTION_HELPERS(desc);
+
+	ADGameDescription desc;
+
+	EngineVersion engineVersion;
+
+	inline bool isVersionBetween(int min, int max) const {
+		int intVersion = (int)engineVersion;
+		return intVersion >= min && intVersion <= max;
+	}
+};
+
 extern const PlainGameDescriptor alcachofaGames[];
 
-extern const ADGameDescription gameDescriptions[];
+extern const AlcachofaGameDescription gameDescriptions[];
 
 #define GAMEOPTION_HIGH_QUALITY GUIO_GAMEOPTIONS1 // I should comment what this does, but I don't know
 #define GAMEOPTION_32BITS GUIO_GAMEOPTIONS2
 
 } // End of namespace Alcachofa
 
-class AlcachofaMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
+class AlcachofaMetaEngineDetection : public AdvancedMetaEngineDetection<Alcachofa::AlcachofaGameDescription> {
 	static const DebugChannelDef debugFlagList[];
 
 public:
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 6f52319c4ba..2869f4b0f93 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -26,74 +26,92 @@ const PlainGameDescriptor alcachofaGames[] = {
 	{ 0, 0 }
 };
 
-const ADGameDescription gameDescriptions[] = {
+const AlcachofaGameDescription gameDescriptions[] = {
 	//
 	// A Movie Adventure
 	//
 	{
-		"aventuradecine",
-		"Clever & Smart - A Movie Adventure",
-		AD_ENTRY2s(
-			"Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606,
-			"Data/DATA02.BIN", "ab6d8867585fbc0f555f5b13d8d1bdf3", 55906308
-		),
-		Common::DE_DEU,
-		Common::kPlatformWindows,
-		ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
-		GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		{
+			"aventuradecine",
+			"Clever & Smart - A Movie Adventure",
+			AD_ENTRY2s(
+				"Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606,
+				"Data/DATA02.BIN", "ab6d8867585fbc0f555f5b13d8d1bdf3", 55906308
+			),
+			Common::DE_DEU,
+			Common::kPlatformWindows,
+			ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
+			GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		},
+		EngineVersion::V3_1
 	},
 	{
-		"aventuradecine",
-		"Clever & Smart - A Movie Adventure",
-		AD_ENTRY2s(
-			"Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606,
-			"Data/DATA02.BIN", "4693e52835bad0c6deab63b60ead81fb", 38273192
-		),
-		Common::DE_DEU,
-		Common::kPlatformWindows,
-		ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_PIRATED,
-		GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		{
+			"aventuradecine",
+			"Clever & Smart - A Movie Adventure",
+			AD_ENTRY2s(
+				"Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606,
+				"Data/DATA02.BIN", "4693e52835bad0c6deab63b60ead81fb", 38273192
+			),
+			Common::DE_DEU,
+			Common::kPlatformWindows,
+			ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_PIRATED,
+			GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		},
+		EngineVersion::V3_1
 	},
 	{
-		"aventuradecine",
-		"Clever & Smart - A Movie Adventure",
-		AD_ENTRY1s("Textos/Objetos.nkr", "8dce25494470209d4882bf12f1a5ea42", 19208),
-		Common::DE_DEU,
-		Common::kPlatformWindows,
-		ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_DEMO,
-		GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		{
+			"aventuradecine",
+			"Clever & Smart - A Movie Adventure",
+			AD_ENTRY1s("Textos/Objetos.nkr", "8dce25494470209d4882bf12f1a5ea42", 19208),
+			Common::DE_DEU,
+			Common::kPlatformWindows,
+			ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_DEMO,
+			GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		},
+		EngineVersion::V3_1
 	},
 
 
 	// The "english" version is just the spanish version with english subtitles...
 	{
-		"aventuradecine",
-		"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
-		AD_ENTRY1s("Textos/Objetos.nkr", "ad3cb78ad7a51cfe63ee6f84768c7e66", 15895),
-		Common::EN_ANY,
-		Common::kPlatformWindows,
-		ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
-		GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		{
+			"aventuradecine",
+			"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+			AD_ENTRY1s("Textos/Objetos.nkr", "ad3cb78ad7a51cfe63ee6f84768c7e66", 15895),
+			Common::EN_ANY,
+			Common::kPlatformWindows,
+			ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
+			GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		},
+		EngineVersion::V3_1
 	},
 	// the spanish Steam variant
 	{
-		"aventuradecine",
-		"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
-		AD_ENTRY1s("Textos/Objetos.nkr", "93331e4cc8d2f8f8a0007bfb5140dff5", 16403),
-		Common::ES_ESP,
-		Common::kPlatformWindows,
-		ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
-		GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		{
+			"aventuradecine",
+			"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+			AD_ENTRY1s("Textos/Objetos.nkr", "93331e4cc8d2f8f8a0007bfb5140dff5", 16403),
+			Common::ES_ESP,
+			Common::kPlatformWindows,
+			ADGF_TESTING | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
+			GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		},
+		EngineVersion::V3_1
 	},
 	// the spanish CD variant
 	{
-		"aventuradecine",
-		"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
-		AD_ENTRY1s("Textos/Objetos.nkr", "8a8b23c04fdc4ced8070a7bccd0177bb", 24467),
-		Common::ES_ESP,
-		Common::kPlatformWindows,
-		ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_CD,
-		GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		{
+			"aventuradecine",
+			"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+			AD_ENTRY1s("Textos/Objetos.nkr", "8a8b23c04fdc4ced8070a7bccd0177bb", 24467),
+			Common::ES_ESP,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_CD,
+			GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+		},
+		EngineVersion::V3_0
 	},
 
 	AD_TABLE_END_MARKER
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 66073d08bb8..4be2c841918 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -111,7 +111,7 @@ class GameMovieAdventure : public Game {
 		};
 
 		if (isInExemptions(exemptions) ||
-			((g_engine->gameDescription().flags & ADGF_DEMO) && isInExemptions(demoExemptions)))
+			((g_engine->gameDescription().desc.flags & ADGF_DEMO) && isInExemptions(demoExemptions)))
 			debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
 		else
 			Game::missingAnimation(fileName);
@@ -168,7 +168,7 @@ class GameMovieAdventure : public Game {
 	void missingSound(const String &fileName) override {
 		if (fileName == "CHAS" || fileName == "517")
 			return;
-		if ((g_engine->gameDescription().flags & ADGF_DEMO) && (
+		if ((g_engine->gameDescription().desc.flags & ADGF_DEMO) && (
 			fileName == "M4996" ||
 			fileName == "T40"))
 			return;
@@ -177,13 +177,13 @@ class GameMovieAdventure : public Game {
 
 	bool isKnownBadVideo(int32 videoId) override {
 		return
-			(videoId == 3 && (g_engine->gameDescription().flags & ADGF_DEMO)) || // problem with MPEG PS decoding
+			(videoId == 3 && (g_engine->gameDescription().desc.flags & ADGF_DEMO)) || // problem with MPEG PS decoding
 			Game::isKnownBadVideo(videoId);
 	}
 
 	void invalidVideo(int32 videoId, const char *context) override {
 		// for the one, known AVI problem, let's not block development
-		if (videoId == 1 && g_engine->gameDescription().language != DE_DEU)
+		if (videoId == 1 && g_engine->gameDescription().desc.language != DE_DEU)
 			warning("Could not play video %d (%s) (WMV not supported)", videoId, context);
 		else
 			Game::invalidVideo(videoId, context);
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 60af8263525..a8c57db2e06 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -69,7 +69,7 @@ const ADExtraGuiOptionsMap *AlcachofaMetaEngine::getAdvancedExtraGuiOptions() co
 	return Alcachofa::optionsList;
 }
 
-Error AlcachofaMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+Error AlcachofaMetaEngine::createInstance(OSystem *syst, Engine **engine, const AlcachofaGameDescription *desc) const {
 	*engine = new Alcachofa::AlcachofaEngine(syst, desc);
 	return kNoError;
 }
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index ada41267045..a01f695668c 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -22,7 +22,7 @@
 #ifndef ALCACHOFA_METAENGINE_H
 #define ALCACHOFA_METAENGINE_H
 
-#include "engines/advancedDetector.h"
+#include "alcachofa/detection.h"
 
 namespace Alcachofa {
 
@@ -34,11 +34,11 @@ enum class EventAction {
 
 }
 
-class AlcachofaMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
+class AlcachofaMetaEngine : public AdvancedMetaEngine<Alcachofa::AlcachofaGameDescription> {
 public:
 	const char *getName() const override;
 
-	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
+	Common::Error createInstance(OSystem *syst, Engine **engine, const Alcachofa::AlcachofaGameDescription *desc) const override;
 
 	/**
 	 * Determine whether the engine supports the specified MetaEngine feature.


Commit: e99af42413b64f00c45a87a2ec0013b6556fc8fe
    https://github.com/scummvm/scummvm/commit/e99af42413b64f00c45a87a2ec0013b6556fc8fe
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-10-19T16:47:22+02:00

Commit Message:
ALCACHOFA: Add script translation tables for V3.0

Changed paths:
    engines/alcachofa/alcachofa.h
    engines/alcachofa/game-movie-adventure.cpp
    engines/alcachofa/game.cpp
    engines/alcachofa/game.h
    engines/alcachofa/script-debug.h
    engines/alcachofa/script.cpp
    engines/alcachofa/script.h
    engines/alcachofa/ui-objects.cpp


diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 06de0f30ab4..ea68e463690 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -111,6 +111,7 @@ public:
 	AlcachofaEngine(OSystem *syst, const AlcachofaGameDescription *gameDesc);
 	~AlcachofaEngine() override;
 
+	inline EngineVersion version() const { return gameDescription().engineVersion; }
 	inline bool isV1() const { return gameDescription().isVersionBetween(10, 19); }
 	inline bool isV2() const { return gameDescription().isVersionBetween(20, 29); }
 	inline bool isV3() const { return gameDescription().isVersionBetween(30, 39); }
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 4be2c841918..7564f60d435 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -27,7 +27,190 @@ using namespace Common;
 
 namespace Alcachofa {
 
-class GameMovieAdventure : public Game {
+static constexpr const ScriptOp kScriptOpMap[] = {
+	ScriptOp::Nop,
+	ScriptOp::Dup,
+	ScriptOp::PushAddr,
+	ScriptOp::PushValue,
+	ScriptOp::Deref,
+	ScriptOp::Crash, ///< would crash original engine by writing to read-only memory
+	ScriptOp::PopN,
+	ScriptOp::Store,
+	ScriptOp::Crash,
+	ScriptOp::Crash,
+	ScriptOp::LoadString,
+	ScriptOp::LoadString, ///< exactly the same as LoadString
+	ScriptOp::Crash,
+	ScriptOp::ScriptCall,
+	ScriptOp::KernelCall,
+	ScriptOp::JumpIfFalse,
+	ScriptOp::JumpIfTrue,
+	ScriptOp::Jump,
+	ScriptOp::Negate,
+	ScriptOp::BooleanNot,
+	ScriptOp::Mul,
+	ScriptOp::Crash,
+	ScriptOp::Crash,
+	ScriptOp::Add,
+	ScriptOp::Sub,
+	ScriptOp::Less,
+	ScriptOp::Greater,
+	ScriptOp::LessEquals,
+	ScriptOp::GreaterEquals,
+	ScriptOp::Equals,
+	ScriptOp::NotEquals,
+	ScriptOp::BitAnd,
+	ScriptOp::BitOr,
+	ScriptOp::Crash,
+	ScriptOp::Crash,
+	ScriptOp::Crash,
+	ScriptOp::Crash,
+	ScriptOp::ReturnValue
+};
+
+static constexpr const ScriptKernelTask kScriptKernelTaskMapV30[] = {
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::PlayVideo,
+	ScriptKernelTask::PlaySound,
+	ScriptKernelTask::PlayMusic,
+	ScriptKernelTask::StopMusic,
+	ScriptKernelTask::WaitForMusicToEnd,
+	ScriptKernelTask::ShowCenterBottomText,
+	ScriptKernelTask::StopAndTurn,
+	ScriptKernelTask::StopAndTurnMe,
+	ScriptKernelTask::ChangeCharacter,
+	ScriptKernelTask::SayText,
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::Go,
+	ScriptKernelTask::Put,
+	ScriptKernelTask::ChangeCharacterRoom,
+	ScriptKernelTask::KillProcesses,
+	ScriptKernelTask::On,
+	ScriptKernelTask::Off,
+	ScriptKernelTask::Pickup,
+	ScriptKernelTask::CharacterPickup,
+	ScriptKernelTask::Drop,
+	ScriptKernelTask::CharacterDrop,
+	ScriptKernelTask::Delay,
+	ScriptKernelTask::HadNoMousePressFor,
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::Fork,
+	ScriptKernelTask::Animate,
+	ScriptKernelTask::AnimateCharacter,
+	ScriptKernelTask::AnimateTalking,
+	ScriptKernelTask::ChangeRoom,
+	ScriptKernelTask::ToggleRoomFloor,
+	ScriptKernelTask::SetDialogLineReturn,
+	ScriptKernelTask::DialogMenu,
+	ScriptKernelTask::ClearInventory,
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::FadeType0,
+	ScriptKernelTask::FadeType1,
+	ScriptKernelTask::LerpWorldLodBias,
+	ScriptKernelTask::FadeType2,
+	ScriptKernelTask::SetActiveTextureSet,
+	ScriptKernelTask::SetMaxCamSpeedFactor,
+	ScriptKernelTask::WaitCamStopping,
+	ScriptKernelTask::CamFollow,
+	ScriptKernelTask::CamShake,
+	ScriptKernelTask::LerpCamXY,
+	ScriptKernelTask::LerpCamZ,
+	ScriptKernelTask::LerpCamScale,
+	ScriptKernelTask::LerpCamToObjectWithScale,
+	ScriptKernelTask::LerpCamToObjectResettingZ,
+	ScriptKernelTask::LerpCamRotation,
+	ScriptKernelTask::FadeIn,
+	ScriptKernelTask::FadeOut,
+	ScriptKernelTask::FadeIn2,
+	ScriptKernelTask::FadeOut2,
+	ScriptKernelTask::LerpCamToObjectKeepingZ
+};
+
+// in V3.1 there is the LerpCharacterLodBias and LerpCamXYZ tasks, no other differences
+
+static constexpr const ScriptKernelTask kScriptKernelTaskMapV31[] = {
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::PlayVideo,
+	ScriptKernelTask::PlaySound,
+	ScriptKernelTask::PlayMusic,
+	ScriptKernelTask::StopMusic,
+	ScriptKernelTask::WaitForMusicToEnd,
+	ScriptKernelTask::ShowCenterBottomText,
+	ScriptKernelTask::StopAndTurn,
+	ScriptKernelTask::StopAndTurnMe,
+	ScriptKernelTask::ChangeCharacter,
+	ScriptKernelTask::SayText,
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::Go,
+	ScriptKernelTask::Put,
+	ScriptKernelTask::ChangeCharacterRoom,
+	ScriptKernelTask::KillProcesses,
+	ScriptKernelTask::LerpCharacterLodBias,
+	ScriptKernelTask::On,
+	ScriptKernelTask::Off,
+	ScriptKernelTask::Pickup,
+	ScriptKernelTask::CharacterPickup,
+	ScriptKernelTask::Drop,
+	ScriptKernelTask::CharacterDrop,
+	ScriptKernelTask::Delay,
+	ScriptKernelTask::HadNoMousePressFor,
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::Fork,
+	ScriptKernelTask::Animate,
+	ScriptKernelTask::AnimateCharacter,
+	ScriptKernelTask::AnimateTalking,
+	ScriptKernelTask::ChangeRoom,
+	ScriptKernelTask::ToggleRoomFloor,
+	ScriptKernelTask::SetDialogLineReturn,
+	ScriptKernelTask::DialogMenu,
+	ScriptKernelTask::ClearInventory,
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::FadeType0,
+	ScriptKernelTask::FadeType1,
+	ScriptKernelTask::LerpWorldLodBias,
+	ScriptKernelTask::FadeType2,
+	ScriptKernelTask::SetActiveTextureSet,
+	ScriptKernelTask::SetMaxCamSpeedFactor,
+	ScriptKernelTask::WaitCamStopping,
+	ScriptKernelTask::CamFollow,
+	ScriptKernelTask::CamShake,
+	ScriptKernelTask::LerpCamXY,
+	ScriptKernelTask::LerpCamZ,
+	ScriptKernelTask::LerpCamScale,
+	ScriptKernelTask::LerpCamToObjectWithScale,
+	ScriptKernelTask::LerpCamToObjectResettingZ,
+	ScriptKernelTask::LerpCamRotation,
+	ScriptKernelTask::FadeIn,
+	ScriptKernelTask::FadeOut,
+	ScriptKernelTask::FadeIn2,
+	ScriptKernelTask::FadeOut2,
+	ScriptKernelTask::LerpCamXYZ,
+	ScriptKernelTask::LerpCamToObjectKeepingZ
+};
+
+class GameWithVersion3 : public Game {
+public:
+	Point getResolution() override {
+		return Point(1024, 768);
+	}
+
+	static constexpr const char *kMapFiles[] = { // not really inherent to V3 but holds true for all V3 games
+		"MAPAS/MAPA5.EMC",
+		"MAPAS/MAPA4.EMC",
+		"MAPAS/MAPA3.EMC",
+		"MAPAS/MAPA2.EMC",
+		"MAPAS/MAPA1.EMC",
+		"MAPAS/GLOBAL.EMC",
+		nullptr
+	};
+	const char *const *getMapFiles() override {
+		return kMapFiles;
+	}
+
+	Span<const ScriptOp> getScriptOpMap() override {
+		return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
+	}
+
 	void onLoadedGameFiles() override {
 		// this notifies the script whether we are a demo
 		if (g_engine->world().loadedMapCount() == 2)
@@ -54,13 +237,6 @@ class GameMovieAdventure : public Game {
 		return Game::shouldCharacterTrigger(character, action);
 	}
 
-	bool shouldTriggerDoor(const Door *door) override {
-		// An invalid door target, the character will go to the door and then ignore it (also in original engine)
-		if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
-			return false;
-		return Game::shouldTriggerDoor(door);
-	}
-
 	void onUserChangedCharacter() override {
 		// An original bug in room POBLADO_INDIO if filemon is bound and mortadelo enters the room
 		// the door A_PUENTE which was disabled is reenabled to allow mortadelo leaving
@@ -75,6 +251,94 @@ class GameMovieAdventure : public Game {
 			character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
 	}
 
+	PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
+		if (scumm_stricmp(action, "put"))
+			return Game::unknownGoPutTarget(process, action, name);
+
+		if (!scumm_stricmp("A_Poblado_Indio", name)) {
+			// A_Poblado_Indio is a Door but is originally cast into a PointObject
+			// a pointer and the draw order is then interpreted as position and the character snapped onto the floor shape.
+			// Instead I just use the A_Poblado_Indio1 object which exists as counter-part for A_Poblado_Indio2 which should have been used
+			auto target = dynamic_cast<PointObject *>(
+				g_engine->world().getObjectByName(process.character(), "A_Poblado_Indio1"));
+			if (target == nullptr)
+				_message("Unknown put target A_Poblado_Indio1 during exemption for A_Poblado_Indio");
+			return target;
+		}
+
+		if (!scumm_stricmp("PUNTO_VENTANA", name)) {
+			// The object is in the previous, now inactive room.
+			// Luckily Mortadelo already is at that point so not further action required
+			return nullptr;
+		}
+
+		if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", name)) {
+			// Another case of a door being cast into a PointObject
+			return nullptr;
+		}
+
+		return Game::unknownGoPutTarget(process, action, name);
+	}
+
+	void unknownAnimateCharacterObject(const char *name) override {
+		if (!scumm_stricmp(name, "COGE F DCH") || // original bug in MOTEL_ENTRADA
+			!scumm_stricmp(name, "CHIQUITO_IZQ"))
+			return;
+		Game::unknownAnimateCharacterObject(name);
+	}
+
+	void missingSound(const String &fileName) override {
+		if (fileName == "CHAS" || fileName == "517")
+			return;
+		Game::missingSound(fileName);
+	}
+};
+
+class GameMovieAdventureSpecialV30 : public GameWithVersion3 {
+public:
+	Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
+		return { kScriptKernelTaskMapV30, ARRAYSIZE(kScriptKernelTaskMapV30) };
+	}
+
+	void missingAnimation(const String &fileName) override {
+		static const char *exemptions[] = {
+			"ANIMACION.AN0",
+			"PP_MORTA.AN0",
+			"ESTOMAGO.AN0",
+			"CREDITOS.AN0",
+			"HABITACION NEGRA.AN0",
+			nullptr
+		};
+
+		const auto isInExemptions = [&] (const char *const *const list) {
+			for (const char *const *exemption = list; *exemption != nullptr; exemption++) {
+				if (fileName.equalsIgnoreCase(*exemption))
+					return true;
+			}
+			return false;
+		};
+
+		if (isInExemptions(exemptions))
+			debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
+		else
+			Game::missingAnimation(fileName);
+	}
+};
+
+class GameMovieAdventureSpecialV31 : public GameWithVersion3 {
+public:
+	Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
+		return { kScriptKernelTaskMapV31, ARRAYSIZE(kScriptKernelTaskMapV31) };
+	}
+
+	bool shouldTriggerDoor(const Door *door) override {
+		// An invalid door target, the character will go to the door and then ignore it (also in original engine)
+		// this is a bug introduced in V3.1
+		if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
+			return false;
+		return Game::shouldTriggerDoor(door);
+	}
+
 	void missingAnimation(const String &fileName) override {
 		static const char *exemptions[] = {
 			"ANIMACION.AN0",
@@ -123,66 +387,28 @@ class GameMovieAdventure : public Game {
 		Game::unknownAnimateObject(name);
 	}
 
-	PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
-		if (scumm_stricmp(action, "put"))
-			return Game::unknownGoPutTarget(process, action, name);
-
-		if (!scumm_stricmp("A_Poblado_Indio", name)) {
-			// A_Poblado_Indio is a Door but is originally cast into a PointObject
-			// a pointer and the draw order is then interpreted as position and the character snapped onto the floor shape.
-			// Instead I just use the A_Poblado_Indio1 object which exists as counter-part for A_Poblado_Indio2 which should have been used
-			auto target = dynamic_cast<PointObject *>(
-				g_engine->world().getObjectByName(process.character(), "A_Poblado_Indio1"));
-			if (target == nullptr)
-				_message("Unknown put target A_Poblado_Indio1 during exemption for A_Poblado_Indio");
-			return target;
-		}
-
-		if (!scumm_stricmp("PUNTO_VENTANA", name)) {
-			// The object is in the previous, now inactive room.
-			// Luckily Mortadelo already is at that point so not further action required
-			return nullptr;
-		}
-
-		if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", name)) {
-			// Another case of a door being cast into a PointObject
-			return nullptr;
-		}
-
-		return Game::unknownGoPutTarget(process, action, name);
-	}
-
 	void unknownSayTextCharacter(const char *name, int32 dialogId) override {
 		if (!scumm_stricmp(name, "OFELIA") && dialogId == 3737)
 			return;
 		Game::unknownSayTextCharacter(name, dialogId);
 	}
 
-	void unknownAnimateCharacterObject(const char *name) override {
-		if (!scumm_stricmp(name, "COGE F DCH") || // original bug in MOTEL_ENTRADA
-			!scumm_stricmp(name, "CHIQUITO_IZQ"))
-			return;
-		Game::unknownAnimateCharacterObject(name);
-	}
-
 	void missingSound(const String &fileName) override {
-		if (fileName == "CHAS" || fileName == "517")
-			return;
 		if ((g_engine->gameDescription().desc.flags & ADGF_DEMO) && (
 			fileName == "M4996" ||
 			fileName == "T40"))
 			return;
-		Game::missingSound(fileName);
+		GameWithVersion3::missingSound(fileName);
 	}
 
 	bool isKnownBadVideo(int32 videoId) override {
 		return
-			(videoId == 3 && (g_engine->gameDescription().desc.flags & ADGF_DEMO)) || // problem with MPEG PS decoding
+			(videoId == 3 && (g_engine->gameDescription().desc.flags & ADGF_DEMO)) || // The german trailer is WMV-encoded
 			Game::isKnownBadVideo(videoId);
 	}
 
 	void invalidVideo(int32 videoId, const char *context) override {
-		// for the one, known AVI problem, let's not block development
+		// the second intro-video is DV-encoded in the spanish steam version
 		if (videoId == 1 && g_engine->gameDescription().desc.language != DE_DEU)
 			warning("Could not play video %d (%s) (WMV not supported)", videoId, context);
 		else
@@ -191,7 +417,10 @@ class GameMovieAdventure : public Game {
 };
 
 Game *Game::createForMovieAdventure() {
-	return new GameMovieAdventure();
+	if (g_engine->version() == EngineVersion::V3_0)
+		return new GameMovieAdventureSpecialV30();
+	else
+		return new GameMovieAdventureSpecialV31();
 }
 
 }
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index b663a5380fc..1da96bcd901 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -114,18 +114,13 @@ void Game::unknownVariable(const char *name) {
 }
 
 void Game::unknownInstruction(const ScriptInstruction &instruction) {
-	const char *type =
-		instruction._op == ScriptOp::Crash5 || // these are defined in the game
-		instruction._op == ScriptOp::Crash8 || // but implemented as crashes
-		instruction._op == ScriptOp::Crash9 ||
-		instruction._op == ScriptOp::Crash12 ||
-		instruction._op == ScriptOp::Crash21 ||
-		instruction._op == ScriptOp::Crash22 ||
-		instruction._op == ScriptOp::Crash33 ||
-		instruction._op == ScriptOp::Crash34 ||
-		instruction._op == ScriptOp::Crash35 ||
-		instruction._op == ScriptOp::Crash36
-		? "crash" : "invalid";
+	const char *type;
+	if (instruction._op < 0 || (uint32)instruction._op >= getScriptOpMap().size())
+		type = "out-of-bounds";
+	else if (getScriptOpMap()[instruction._op] == ScriptOp::Crash)
+		type = "crash"; // these are defined in the game, but implemented as write to null-pointer
+	else
+		type = "unimplemented"; // we forgot to implement them
 	_message("Script reached %s instruction: %d %d", type, (int)instruction._op, instruction._arg);
 }
 
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 483cf63e603..a18896bccb8 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -22,6 +22,8 @@
 #ifndef ALCACHOFA_GAME_H
 #define ALCACHOFA_GAME_H
 
+#include "alcachofa/script.h"
+
 #include "common/textconsole.h"
 #include "common/file.h"
 
@@ -48,6 +50,10 @@ public:
 	virtual ~Game() {}
 
 	virtual void onLoadedGameFiles();
+	virtual Common::Point getResolution() = 0;
+	virtual const char *const *getMapFiles() = 0; ///< Returns a nullptr-terminated list
+	virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
+	virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
 
 	virtual bool doesRoomHaveBackground(const Room *room);
 	virtual void unknownRoomObject(const Common::String &type);
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
index 573d502f4b4..f6e6d1fa807 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -30,14 +30,9 @@ static const char *const ScriptOpNames[] = {
 	"PushAddr",
 	"PushValue",
 	"Deref",
-	"Crash5",
 	"PopN",
 	"Store",
-	"Crash8",
-	"Crash9",
 	"LoadString",
-	"LoadString2",
-	"Crash12",
 	"ScriptCall",
 	"KernelCall",
 	"JumpIfFalse",
@@ -46,8 +41,6 @@ static const char *const ScriptOpNames[] = {
 	"Negate",
 	"BooleanNot",
 	"Mul",
-	"Crash21",
-	"Crash22",
 	"Add",
 	"Sub",
 	"Less",
@@ -58,15 +51,13 @@ static const char *const ScriptOpNames[] = {
 	"NotEquals",
 	"BitAnd",
 	"BitOr",
-	"Crash33",
-	"Crash34",
-	"Crash35",
-	"Crash36",
-	"Return"
+	"ReturnValue",
+	"ReturnVoid",
+	"Crash"
 };
 
 static const char *const KernelCallNames[] = {
-	"<null>",
+	"Nop",
 	"PlayVideo",
 	"PlaySound",
 	"PlayMusic",
@@ -77,7 +68,6 @@ static const char *const KernelCallNames[] = {
 	"StopAndTurnMe",
 	"ChangeCharacter",
 	"SayText",
-	"Nop10",
 	"Go",
 	"Put",
 	"ChangeCharacterRoom",
@@ -91,7 +81,6 @@ static const char *const KernelCallNames[] = {
 	"CharacterDrop",
 	"Delay",
 	"HadNoMousePressFor",
-	"Nop24",
 	"Fork",
 	"Animate",
 	"AnimateCharacter",
@@ -101,7 +90,6 @@ static const char *const KernelCallNames[] = {
 	"SetDialogLineReturn",
 	"DialogMenu",
 	"ClearInventory",
-	"Nop34",
 	"FadeType0",
 	"FadeType1",
 	"LerpWorldLodBias",
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 6606971322d..e278d5c3bd3 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -40,7 +40,7 @@ enum ScriptDebugLevel {
 };
 
 ScriptInstruction::ScriptInstruction(ReadStream &stream)
-	: _op((ScriptOp)stream.readSint32LE())
+	: _op(stream.readSint32LE())
 	, _arg(stream.readSint32LE()) {}
 
 Script::Script() {
@@ -237,6 +237,7 @@ struct ScriptTask final : public Task {
 			handleReturnFromKernelCall(process().returnValue());
 		}
 		_isFirstExecution = _returnsFromKernelCall = false;
+		auto opMap = g_engine->game().getScriptOpMap();
 
 		while (true) {
 			if (_pc >= _script._instructions.size())
@@ -269,7 +270,11 @@ struct ScriptTask final : public Task {
 				}
 			}
 
-			switch (instruction._op) {
+			if (instruction._op < 0 || (uint32)instruction._op >= opMap.size()) {
+				g_engine->game().unknownInstruction(instruction);
+				continue;
+			}
+			switch (opMap[instruction._op]) {
 			case ScriptOp::Nop: break;
 			case ScriptOp::Dup:
 				if (_stack.empty())
@@ -294,7 +299,6 @@ struct ScriptTask final : public Task {
 				pushNumber(value);
 			}break;
 			case ScriptOp::LoadString:
-			case ScriptOp::LoadString2:
 				pushString(popNumber());
 				break;
 			case ScriptOp::ScriptCall:
@@ -302,7 +306,7 @@ struct ScriptTask final : public Task {
 				_pc = instruction._arg - 1;
 				break;
 			case ScriptOp::KernelCall: {
-				TaskReturn kernelReturn = kernelCall((ScriptKernelTask)instruction._arg);
+				TaskReturn kernelReturn = kernelCall(instruction._arg);
 				if (kernelReturn.type() == TaskReturnType::Waiting) {
 					_returnsFromKernelCall = true;
 					return kernelReturn;
@@ -360,7 +364,7 @@ struct ScriptTask final : public Task {
 			case ScriptOp::BitOr:
 				pushNumber(popNumber() | popNumber()); //-V501
 				break;
-			case ScriptOp::Return: {
+			case ScriptOp::ReturnValue: {
 				int32 returnValue = popNumber();
 				_pc = popInstruction();
 				if (_pc == UINT_MAX)
@@ -414,7 +418,9 @@ private:
 	void handleReturnFromKernelCall(int32 returnValue) {
 		// this is also original done, every KernelCall is followed by a PopN of the arguments
 		// only *after* the PopN the return value is pushed so that the following script can use it
-		scumm_assert(_pc < _script._instructions.size() && _script._instructions[_pc]._op == ScriptOp::PopN);
+		scumm_assert(
+			_pc < _script._instructions.size() &&
+			g_engine->game().getScriptOpMap()[_script._instructions[_pc]._op] == ScriptOp::PopN);
 		popN(_script._instructions[_pc++]._arg);
 		pushNumber(returnValue);
 	}
@@ -540,10 +546,16 @@ private:
 			g_engine->player().activeCharacterKind() != process().character();
 	}
 
-	TaskReturn kernelCall(ScriptKernelTask task) {
+	TaskReturn kernelCall(int32 taskI) {
+		const auto taskMap = g_engine->game().getScriptKernelTaskMap();
+		if (taskI < 0 || (uint32)taskI >= taskMap.size()) {
+			g_engine->game().unknownKernelTask(taskI);
+			return TaskReturn::finish(-1);
+		}
+		const auto task = taskMap[taskI];
+
 		debugC(SCRIPT_DEBUG_LVL_KERNELCALLS, kDebugScript, "%u: %5u Kernel %-25s",
 			process().pid(), _pc - 1, KernelCallNames[(int)task]);
-
 		switch (task) {
 		// sound/video
 		case ScriptKernelTask::PlayVideo:
@@ -923,12 +935,10 @@ private:
 		case ScriptKernelTask::FadeType2:
 			warning("STUB KERNEL CALL: FadeType2"); // Crossfade, unused from script
 			return TaskReturn::finish(0);
-		case ScriptKernelTask::Nop10:
-		case ScriptKernelTask::Nop24:
-		case ScriptKernelTask::Nop34:
+		case ScriptKernelTask::Nop:
 			return TaskReturn::finish(0);
 		default:
-			g_engine->game().unknownKernelTask((int)task);
+			g_engine->game().unknownKernelTask(taskI);
 			return TaskReturn::finish(0);
 		}
 	}
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 8122ec2954a..d0672570ca4 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -33,20 +33,20 @@ namespace Alcachofa {
 
 class Process;
 
+// the ScriptOp and ScriptKernelTask enums represent the *implemented* order
+// the specific Game instance maps the version-specific op codes to our order
+
 enum class ScriptOp {
 	Nop,
 	Dup,
 	PushAddr,
+	PushDynAddr,
 	PushValue,
 	Deref,
-	Crash5, ///< would crash original engine by writing to read-only memory
+	Pop1,
 	PopN,
 	Store,
-	Crash8,
-	Crash9,
 	LoadString,
-	LoadString2, ///< exactly the same as LoadString
-	Crash12,
 	ScriptCall,
 	KernelCall,
 	JumpIfFalse,
@@ -55,8 +55,6 @@ enum class ScriptOp {
 	Negate,
 	BooleanNot,
 	Mul,
-	Crash21,
-	Crash22,
 	Add,
 	Sub,
 	Less,
@@ -67,15 +65,14 @@ enum class ScriptOp {
 	NotEquals,
 	BitAnd,
 	BitOr,
-	Crash33,
-	Crash34,
-	Crash35,
-	Crash36,
-	Return
+	ReturnValue,
+	ReturnVoid,
+	Crash
 };
 
 enum class ScriptKernelTask {
-	PlayVideo = 1,
+	Nop = 0,
+	PlayVideo,
 	PlaySound,
 	PlayMusic,
 	StopMusic,
@@ -85,7 +82,6 @@ enum class ScriptKernelTask {
 	StopAndTurnMe,
 	ChangeCharacter,
 	SayText,
-	Nop10,
 	Go,
 	Put,
 	ChangeCharacterRoom,
@@ -99,7 +95,6 @@ enum class ScriptKernelTask {
 	CharacterDrop,
 	Delay,
 	HadNoMousePressFor,
-	Nop24,
 	Fork,
 	Animate,
 	AnimateCharacter,
@@ -109,7 +104,6 @@ enum class ScriptKernelTask {
 	SetDialogLineReturn,
 	DialogMenu,
 	ClearInventory,
-	Nop34,
 	FadeType0,
 	FadeType1,
 	LerpWorldLodBias,
@@ -130,7 +124,11 @@ enum class ScriptKernelTask {
 	FadeIn2,
 	FadeOut2,
 	LerpCamXYZ,
-	LerpCamToObjectKeepingZ
+	LerpCamToObjectKeepingZ,
+
+	SheriffTakesCharacter, ///< some special-case V1 tasks, unknown yet
+	ChangeDoor,
+	Disguise
 };
 
 enum class ScriptFlags {
@@ -148,7 +146,7 @@ inline bool operator & (ScriptFlags a, ScriptFlags b) {
 struct ScriptInstruction {
 	ScriptInstruction(Common::ReadStream &stream);
 
-	ScriptOp _op;
+	int32 _op; ///< int32 because it still has to be mapped using a game-specific translation table
 	int32 _arg;
 };
 
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 534115b1245..e0e17424048 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -161,7 +161,11 @@ EditBox::EditBox(Room *room, ReadStream &stream)
 	, i3(stream.readSint32LE())
 	, i4(stream.readSint32LE())
 	, i5(stream.readSint32LE())
-	, _fontId(stream.readSint32LE()) {}
+	, _fontId(0) {
+
+	if (g_engine->version() == EngineVersion::V3_1)
+		_fontId = stream.readSint32LE();
+}
 
 const char *CheckBox::typeName() const { return "CheckBox"; }
 


Commit: a94fdf73bff65bbaf78b3e6b09af3fc476af8290
    https://github.com/scummvm/scummvm/commit/a94fdf73bff65bbaf78b3e6b09af3fc476af8290
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-10-19T16:47:22+02:00

Commit Message:
ALCACHOFA: Reencode animation paths to UTF8

Changed paths:
    engines/alcachofa/common.cpp
    engines/alcachofa/common.h
    engines/alcachofa/graphics.cpp


diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 6c963840a02..e2f62482cbf 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -59,6 +59,16 @@ float ease(float t, EasingType type) {
 	}
 }
 
+String reencode(const String &string, CodePage from, CodePage to) {
+	// Some spanish releases contain special characters in paths but Path does not support U32String
+	// Instead we convert to UTF8 and let the filesystem backend choose the native target encoding
+	
+	auto it = Common::find_if(string.begin(), string.end(), [] (const char v) { return v < 0; });
+	if (it == string.end())
+		return string; // no need to reencode
+	return string.decode(from).encode(to);
+}
+
 FakeSemaphore::FakeSemaphore(const char *name, uint initialCount)
 	: _name(name)
 	, _counter(initialCount) {}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index cd8da0fae88..1bd5b5f7bff 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -25,6 +25,7 @@
 #include "common/rect.h"
 #include "common/serializer.h"
 #include "common/stream.h"
+#include "common/str-enc.h"
 #include "common/stack.h"
 #include "math/vector2d.h"
 #include "math/vector3d.h"
@@ -118,6 +119,11 @@ int16 nextPowerOfTwo(int16 v);
 
 float ease(float t, EasingType type);
 
+Common::String reencode(
+	const Common::String &string,
+	Common::CodePage from = Common::CodePage::kISO8859_1, // "Western European", used for the spanish special characters
+	Common::CodePage to = Common::CodePage::kUtf8);
+
 Math::Vector3d as3D(const Math::Vector2d &v);
 Math::Vector3d as3D(Common::Point p);
 Math::Vector2d as2D(const Math::Vector3d &v);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 06992694b7c..9f3705d888a 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -88,6 +88,7 @@ void AnimationBase::load() {
 	}
 	if (_fileName.size() < 4 || scumm_strnicmp(_fileName.end() - 4, ".AN0", 4) != 0)
 		_fileName += ".AN0";
+	_fileName = reencode(_fileName);
 	fullPath += _fileName;
 
 	File file;


Commit: c489c0f061b0a82c45b7a965266a5326949f2321
    https://github.com/scummvm/scummvm/commit/c489c0f061b0a82c45b7a965266a5326949f2321
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-10-19T16:47:22+02:00

Commit Message:
ALCACHOFA: Replace dependencies on version-dependent script variables

Changed paths:
    engines/alcachofa/camera.cpp
    engines/alcachofa/game-movie-adventure.cpp
    engines/alcachofa/game.cpp
    engines/alcachofa/game.h
    engines/alcachofa/global-ui.cpp
    engines/alcachofa/graphics.cpp
    engines/alcachofa/rooms.cpp
    engines/alcachofa/script.cpp
    engines/alcachofa/script.h


diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 5f9752717e9..4977bbb0ee6 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -113,7 +113,7 @@ void minmax(Vector3d &min, Vector3d &max, Vector3d val) {
 
 Vector3d Camera::setAppliedCenter(Vector3d center) {
 	setupMatricesAround(center);
-	if (g_engine->script().variable("EncuadrarCamara")) {
+	if (g_engine->game().shouldClipCamera()) {
 		const float screenW = g_system->getWidth(), screenH = g_system->getHeight();
 		Vector3d min, max;
 		min = max = transform2Dto3D(Vector3d(0, 0, _roomScale));
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 7564f60d435..a9a5662fba3 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -211,6 +211,15 @@ public:
 		return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
 	}
 
+	void updateScriptVariables() override {
+		Script &script = g_engine->script();
+		if (g_engine->input().wasAnyMousePressed()) // yes, this variable is never reset by the engine (only by script)
+			script.variable("SeHaPulsadoRaton") = 1;
+
+		script.variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
+		script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
+	}
+
 	void onLoadedGameFiles() override {
 		// this notifies the script whether we are a demo
 		if (g_engine->world().loadedMapCount() == 2)
@@ -300,6 +309,17 @@ public:
 		return { kScriptKernelTaskMapV30, ARRAYSIZE(kScriptKernelTaskMapV30) };
 	}
 
+	void updateScriptVariables() override {
+		GameWithVersion3::updateScriptVariables();
+
+		// in V3.0 there is no CalcularTiempoSinPulsarRaton variable to reset the timer
+		g_engine->script().setScriptTimer(g_engine->input().wasAnyMousePressed());
+	}
+
+	bool shouldClipCamera() override {
+		return true;
+	}
+
 	void missingAnimation(const String &fileName) override {
 		static const char *exemptions[] = {
 			"ANIMACION.AN0",
@@ -331,6 +351,27 @@ public:
 		return { kScriptKernelTaskMapV31, ARRAYSIZE(kScriptKernelTaskMapV31) };
 	}
 
+	void updateScriptVariables() override {
+		GameWithVersion3::updateScriptVariables();
+
+		Script &script = g_engine->script();
+		script.setScriptTimer(!script.variable("CalcularTiempoSinPulsarRaton"));
+		script.variable("modored") = 0; // this is signalling whether a network connection is established
+	}
+
+	bool shouldClipCamera() override {
+		return g_engine->script().variable("EncuadrarCamara") != 0;
+	}
+
+	void drawScreenStates() override {
+		if (int32 borderWidth = g_engine->script().variable("BordesNegros")) {
+			int16 width = g_system->getWidth();
+			int16 height = g_system->getHeight();
+			g_engine->drawQueue().add<BorderDrawRequest>(Rect(0, 0, width, borderWidth), kBlack);
+			g_engine->drawQueue().add<BorderDrawRequest>(Rect(0, height - borderWidth, width, height), kBlack);
+		}
+	}
+
 	bool shouldTriggerDoor(const Door *door) override {
 		// An invalid door target, the character will go to the door and then ignore it (also in original engine)
 		// this is a bug introduced in V3.1
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 1da96bcd901..a69f6543798 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -37,6 +37,8 @@ Game::Game()
 
 void Game::onLoadedGameFiles() {}
 
+void Game::drawScreenStates() {}
+
 bool Game::doesRoomHaveBackground(const Room *room) {
 	return true;
 }
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index a18896bccb8..6f258055afc 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -54,6 +54,9 @@ public:
 	virtual const char *const *getMapFiles() = 0; ///< Returns a nullptr-terminated list
 	virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
 	virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
+	virtual void updateScriptVariables() = 0;
+	virtual bool shouldClipCamera() = 0;
+	virtual void drawScreenStates();
 
 	virtual bool doesRoomHaveBackground(const Room *room);
 	virtual void unknownRoomObject(const Common::String &type);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 7d9f7654697..7f7ab5740cc 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -254,12 +254,8 @@ void GlobalUI::drawScreenStates() {
 	auto &drawQueue = g_engine->drawQueue();
 	if (_isPermanentFaded)
 		drawQueue.add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
-	else if (int32 borderWidth = g_engine->script().variable("BordesNegros")) {
-		int16 width = g_system->getWidth();
-		int16 height = g_system->getHeight();
-		drawQueue.add<BorderDrawRequest>(Rect(0, 0, width, borderWidth), kBlack);
-		drawQueue.add<BorderDrawRequest>(Rect(0, height - borderWidth, width, height), kBlack);
-	}
+	else
+		g_engine->game().drawScreenStates();
 }
 
 void GlobalUI::syncGame(Serializer &s) {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 9f3705d888a..b0fcb5c39d8 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -60,7 +60,7 @@ void IDebugRenderer::debugShape(const Shape &shape, Color color) {
 }
 
 AnimationBase::AnimationBase(String fileName, AnimationFolder folder)
-	: _fileName(move(fileName))
+	: _fileName(reencode(fileName))
 	, _folder(folder) {}
 
 AnimationBase::~AnimationBase() {
@@ -88,7 +88,6 @@ void AnimationBase::load() {
 	}
 	if (_fileName.size() < 4 || scumm_strnicmp(_fileName.end() - 4, ".AN0", 4) != 0)
 		_fileName += ".AN0";
-	_fileName = reencode(_fileName);
 	fullPath += _fileName;
 
 	File file;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 31311874cf0..b20dc6ab5ba 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -164,7 +164,7 @@ void Room::draw() {
 }
 
 void Room::updateScripts() {
-	g_engine->script().updateCommonVariables();
+	g_engine->game().updateScriptVariables();
 	if (!g_engine->scheduler().hasProcessWithName("ACTUALIZAR_" + _name))
 		g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, ScriptFlags::AllowMissing | ScriptFlags::IsBackground);
 	g_engine->scheduler().run();
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index e278d5c3bd3..54516675cdd 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -826,11 +826,13 @@ private:
 		// Camera tasks
 		case ScriptKernelTask::WaitCamStopping:
 			return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
-		case ScriptKernelTask::CamFollow:
-			g_engine->camera().setFollow(
-				&g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(0)),
-				getNumberArg(1) != 0);
+		case ScriptKernelTask::CamFollow: {
+			WalkingCharacter *target = nullptr;
+			if (getNumberArg(0) != 0)
+				target = &g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(0));
+			g_engine->camera().setFollow(target, getNumberArg(1) != 0);
 			return TaskReturn::finish(1);
+		}
 		case ScriptKernelTask::CamShake:
 			return TaskReturn::waitFor(g_engine->camera().shake(process(),
 				Vector2d(getNumberArg(1), getNumberArg(2)),
@@ -990,19 +992,12 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
 	return process;
 }
 
-void Script::updateCommonVariables() {
-	if (g_engine->input().wasAnyMousePressed()) // yes, this variable is never reset by the engine (only by script)
-		variable("SeHaPulsadoRaton") = 1;
-
-	if (variable("CalcularTiempoSinPulsarRaton")) {
-		if (_scriptTimer == 0)
-			_scriptTimer = g_engine->getMillis();
-	} else
+void Script::setScriptTimer(bool reset) {
+	// Used for the V3 exclusive kernel task HadNoMousePressFor
+	if (reset)
 		_scriptTimer = 0;
-
-	variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
-	variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
-	variable("modored") = 0; // this is signalling whether a network connection is established
+	else if (_scriptTimer == 0)
+		_scriptTimer = g_engine->getMillis();
 }
 
 }
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index d0672570ca4..e9cb9eb2846 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -155,7 +155,6 @@ public:
 	Script();
 
 	void syncGame(Common::Serializer &s);
-	void updateCommonVariables();
 	int32 variable(const char *name) const;
 	int32 &variable(const char *name);
 	Process *createProcess(
@@ -175,6 +174,7 @@ public:
 	inline VariableNameIterator endVariables() const { return _variableNames.end(); }
 	inline bool hasVariable(const char *name) const { return _variableNames.contains(name); }
 
+	void setScriptTimer(bool reset);
 private:
 	friend struct ScriptTask;
 	friend struct ScriptTimerTask;




More information about the Scummvm-git-logs mailing list