[Scummvm-git-logs] scummvm master -> 430293d3b6119c878526045ea5c18ebaf691dc4e

Helco noreply at scummvm.org
Thu Feb 12 14:41:56 UTC 2026


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:
7dae6579e5 ALCACHOFA: V2: Add detection and skeleton game for secta
f290c7bae0 ALCACHOFA: V2: Add support for V2 world files
dde4b70f72 ALCACHOFA: V2: Add support for V2 animations
430293d3b6 ALCACHOFA: V2: First pass to fix secta


Commit: 7dae6579e5f365baf916784f9c1b01eb4a9eaa3d
    https://github.com/scummvm/scummvm/commit/7dae6579e5f365baf916784f9c1b01eb4a9eaa3d
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-12T15:41:35+01:00

Commit Message:
ALCACHOFA: V2: Add detection and skeleton game for secta

Changed paths:
  A engines/alcachofa/game-v2.cpp
    engines/alcachofa/detection_tables.h
    engines/alcachofa/game.cpp
    engines/alcachofa/game.h
    engines/alcachofa/module.mk
    engines/alcachofa/script.cpp
    engines/alcachofa/script.h


diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index bddde5ce609..0732593acfc 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -23,8 +23,9 @@ namespace Alcachofa {
 
 const PlainGameDescriptor alcachofaGames[] = {
 	{ "aventuradecine", "Mort & Phil: A Movie Adventure" },
-	{ "terror", "Mortadelo y Filemón: Terror, Espanto y Pavor"},
-	{ "vaqueros", "Mortadelo y Filemón: Dos vaqueros chapuceros"},
+	{ "secta", "Mortadelo y Filemón: La Sexta Secta" },
+	{ "terror", "Mortadelo y Filemón: Terror, Espanto y Pavor" },
+	{ "vaqueros", "Mortadelo y Filemón: Dos vaqueros chapuceros" },
 	{ 0, 0 }
 };
 
@@ -129,6 +130,22 @@ const AlcachofaGameDescription gameDescriptions[] = {
 		EngineVersion::V3_1
 	},
 
+	//
+	// La Sexta Secta
+	//
+	{
+		{
+			"secta",
+			"Mortadelo y Filemón: La Sexta Secta",
+			AD_ENTRY1s("Fondos/MUSEO_O.ANI", "40a880c866aabbb5c09899d9b7ca66b6", 10630),
+			Common::ES_ESP,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+			GUIO0()
+		},
+		EngineVersion::V2_0
+	},
+
 	//
 	// A Movie Adventure - Edicion Original
 	//
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
new file mode 100644
index 00000000000..ff3e4369176
--- /dev/null
+++ b/engines/alcachofa/game-v2.cpp
@@ -0,0 +1,226 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/game.h"
+#include "alcachofa/script.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+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 kScriptKernelTaskMap[] = {
+	ScriptKernelTask::Nop,
+	ScriptKernelTask::SayText,
+	ScriptKernelTask::Go,
+	ScriptKernelTask::Delay,
+	ScriptKernelTask::PlaySound,
+	ScriptKernelTask::FadeIn,
+	ScriptKernelTask::FadeOut,
+	ScriptKernelTask::Put,
+	ScriptKernelTask::ChangeRoom,
+	ScriptKernelTask::PlayVideo,
+	ScriptKernelTask::StopAndTurn,
+	ScriptKernelTask::StopAndTurnMe,
+	ScriptKernelTask::On,
+	ScriptKernelTask::Off,
+	ScriptKernelTask::Pickup,
+	ScriptKernelTask::CharacterPickup,
+	ScriptKernelTask::Animate,
+	ScriptKernelTask::HadNoMousePressFor,
+	ScriptKernelTask::ChangeCharacter,
+	ScriptKernelTask::LerpOrSetCam,
+	ScriptKernelTask::Drop,
+	ScriptKernelTask::CharacterDrop,
+	ScriptKernelTask::ChangeDoor,
+	ScriptKernelTask::CamShake,
+	ScriptKernelTask::ToggleRoomFloor,
+	ScriptKernelTask::SetDialogLineReturn,
+	ScriptKernelTask::DialogMenu,
+	ScriptKernelTask::ChangeCharacterRoom,
+	ScriptKernelTask::PlayMusic,
+	ScriptKernelTask::StopMusic,
+	ScriptKernelTask::WaitForMusicToEnd,
+	ScriptKernelTask::SayTextV2
+};
+
+class GameWithVersion2 : public Game {
+public:
+	Point getResolution() override {
+		return Point(800, 600);
+	}
+
+	Point getThumbnailResolution() override {
+		return Point(266, 200); // TODO: Check this resolution value
+	}
+
+	GameFileReference getScriptFileRef() override {
+		return GameFileReference("Script/MORTADELO.COD");
+	}
+
+	const char *getDialogFileName() override {
+		return "Fondos/MUSEO_F.ANI";
+	}
+
+	const char *getObjectFileName() override {
+		return "Fondos/MUSEO_O.ANI";
+	}
+	
+	Point getSubtitlePos() override {
+		return Point(g_system->getWidth() / 2, 150); // TODO: Check subtitle position
+	}
+
+	const char *getMenuRoom() override {
+		return "MENUPRINCIPAL";
+	}
+
+	const char *getInitScriptName() override {
+		return "main";
+	}
+
+	Span<const ScriptOp> getScriptOpMap() override {
+		return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
+	}
+
+	Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
+		return { kScriptKernelTaskMap, ARRAYSIZE(kScriptKernelTaskMap) };
+	}
+
+	void updateScriptVariables() override {
+		Script &script = g_engine->script();
+		script.variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
+		script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
+	}
+
+	Path getVideoPath(int32 videoId) override {
+		return Path(String::format("Bin/DATA%02d.BIN", videoId));
+	}
+
+	String getSoundPath(const char *filename) override {
+		return String("Sonidos/") + filename;
+	}
+
+	String getMusicPath(int32 trackId) override {
+		return String::format("Music/Track%02d", trackId);
+	}
+
+	int32 getCharacterJingle(MainCharacterKind kind) override {
+		return g_engine->script().variable(
+			kind == MainCharacterKind::Mortadelo ? "PistaMorta" : "PistaFile");
+	}
+
+	bool shouldFilterTexturesByDefault() override {
+		return true; // TODO: Check this!
+	}
+
+	bool shouldClipCamera() override {
+		return true;
+	}
+
+	bool isAllowedToOpenMenu() override {
+		return g_engine->sounds().musicSemaphore().isReleased();
+	}
+
+	bool isAllowedToInteract() override {
+		return true; // original would be checking an unused script variable "Ocupados"
+	}
+
+	bool shouldScriptLockInteraction() override {
+		return false;
+	}
+
+	bool shouldChangeCharacterUseGameLock() override {
+		return false;
+	}
+
+	bool shouldAvoidCollisions() override {
+		return true;
+	}
+
+	Point getMainCharacterSize() override {
+		return { 40, 220 };
+	}
+};
+
+static constexpr const char *kMapFilesSecta[] = {
+	"Mapas/mapa1.emc",
+	"Mapas/mapa2.emc",
+	"Mapas/global.emc",
+	nullptr
+};
+
+class GameSecta : public GameWithVersion2 {
+public:
+	const char *const *getMapFiles() override {
+		return kMapFilesSecta;
+	}
+
+	char getTextFileKey() override {
+		return static_cast<char>(0xA3);
+	}
+};
+
+Game *Game::createForSecta() {
+	return new GameSecta();
+}
+
+}
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 72ca3c35e4c..02d61a657ed 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -223,6 +223,12 @@ Game *Game::create() {
 			return createForVaqueros();
 		}
 		break;
+	case EngineVersion::V2_0:
+		switch (*desc.desc.gameId) {
+		case 's':
+			return createForSecta();
+		}
+		break;
 	case EngineVersion::V3_0:
 	case EngineVersion::V3_1:
 		return createForMovieAdventureSpecial();
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index e6d3d5fe5f6..f7a5a1ff51f 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -127,6 +127,7 @@ public:
 	static Game *createForMovieAdventureOriginal(); // V1
 	static Game *createForTerror(); // V1
 	static Game *createForVaqueros(); // V1
+	static Game *createForSecta(); // V2
 
 	const Message _message;
 };
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 9e0bf09b809..61981ef86f7 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -7,6 +7,7 @@ MODULE_OBJS = \
 	console.o \
 	game.o \
 	game-v1.o \
+	game-v2.0 \
 	game-v3.o \
 	game-objects.o \
 	general-objects.o \
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 732f28353f6..7d3fd426191 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -464,7 +464,7 @@ struct ScriptTask final : public Task {
 
 private:
 	void setCharacterVariables() {
-		if (g_engine->isV3()) {
+		if (g_engine->isV3() || g_engine->isV2()) {
 			_script.variable("m_o_f") = (int32)process().character();
 			_script.variable("m_o_f_real") = (int32)g_engine->player().activeCharacterKind();
 		} else
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 6178d742549..5da8a929170 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -82,6 +82,7 @@ enum class ScriptKernelTask {
 	StopAndTurnMe,
 	ChangeCharacter,
 	SayText,
+	SayTextV2, // TODO: Reverse engineer this variant
 	Go,
 	Put,
 	ChangeCharacterRoom,


Commit: f290c7bae0088971b8741d8333d7ebf07d7af4eb
    https://github.com/scummvm/scummvm/commit/f290c7bae0088971b8741d8333d7ebf07d7af4eb
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-12T15:41:35+01:00

Commit Message:
ALCACHOFA: V2: Add support for V2 world files

Changed paths:
    engines/alcachofa/general-objects.cpp
    engines/alcachofa/objects.h
    engines/alcachofa/rooms.cpp
    engines/alcachofa/rooms.h
    engines/alcachofa/ui-objects.cpp


diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 4ba9981d7fa..7b09803d9dc 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -85,11 +85,12 @@ const char *GraphicObject::typeName() const { return "GraphicObject"; }
 
 GraphicObject::GraphicObject(Room *room, SeekableReadStream &stream)
 	: ObjectBase(room, stream) {
-	if (g_engine->isV1()) {
+	if (g_engine->isV1())
 		toggle(readBool(stream));
-		_graphic = Graphic(stream);
-	} else {
-		_graphic = Graphic(stream);
+
+	_graphic = Graphic(stream);
+
+	if (g_engine->isV3()) {
 		_type = (GraphicObjectType)stream.readSint32LE();
 		_posterizeAlpha = 100 - stream.readSint32LE();
 	}
@@ -240,7 +241,7 @@ const char *ShapeObject::typeName() const { return "ShapeObject"; }
 ShapeObject::ShapeObject(Room *room, SeekableReadStream &stream)
 	: ObjectBase(room, stream)
 	, _shape(stream) {
-	if (g_engine->isV2() || g_engine->isV3())
+	if (g_engine->isV3())
 		_cursorType = (CursorType)stream.readSint32LE();
 }
 
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 046d2baf603..964f1e19698 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -257,20 +257,30 @@ private:
 	int32 _actionId;
 };
 
-class EditBox final : public PhysicalObject {
+class EditBox : public PhysicalObject {
 public:
 	static constexpr const char *kClassName = "CEditBox";
-	EditBox(Room *room, Common::SeekableReadStream &stream);
 
 	const char *typeName() const override;
 
-private:
-	int32 i1;
+protected:
+	using PhysicalObject::PhysicalObject;
+	int32 i1 = 0;
 	Common::Point p1;
 	Common::String _labelId;
-	bool b1;
-	int32 i3, i4, i5,
-		_fontId;
+	bool b1 = false;
+	int32 i3 = 0, i4 = 0, i5 = 0,
+		_fontId = 0;
+};
+
+class EditBoxV2 final : public EditBox {
+public:
+	EditBoxV2(Room *room, Common::SeekableReadStream &stream);
+};
+
+class EditBoxV3 final : public EditBox {
+public:
+	EditBoxV3(Room *room, Common::SeekableReadStream &stream);
 };
 
 class CheckBox : public PhysicalObject {
@@ -304,11 +314,9 @@ private:
 	uint32 _clickTime = 0;
 };
 
-class SlideButton final : public ObjectBase {
+class SlideButton : public ObjectBase {
 public:
 	static constexpr const char *kClassName = "CSlideButton";
-	SlideButton(Room *room, Common::SeekableReadStream &stream);
-	~SlideButton() override {}
 
 	inline float &value() { return _value; }
 
@@ -318,11 +326,12 @@ public:
 	void freeResources() override;
 	const char *typeName() const override;
 
-private:
+protected:
+	using ObjectBase::ObjectBase;
 	bool isMouseOver() const;
 
 	float _value = 0;
-	int32 _valueId;
+	int32 _valueId = -1;
 	Common::Point _minPos, _maxPos;
 	Graphic
 		_graphicIdle,
@@ -330,6 +339,16 @@ private:
 		_graphicClicked;
 };
 
+class SlideButtonV2 final : public SlideButton {
+public:
+	SlideButtonV2(Room *room, Common::SeekableReadStream &stream);
+};
+
+class SlideButtonV3 final : public SlideButton {
+public:
+	SlideButtonV3(Room *room, Common::SeekableReadStream &stream);
+};
+
 class CheckBoxAutoAdjustNoise final : public CheckBox {
 public:
 	static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index bab88b1e590..fa5b6d42289 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -44,7 +44,7 @@ Room::Room(World *world, SeekableReadStream &stream)
 		readObjects(stream);
 	}
 	else
-		readRoomV3(stream, false);
+		readRoomV2and3(stream, false);
 	initBackground();
 }
 
@@ -67,16 +67,24 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
 		return new InternetMenuButton(room, stream);
 	else if (type == OptionsMenuButton::kClassName)
 		return new OptionsMenuButton(room, stream);
-	else if (type == EditBox::kClassName)
-		return new EditBox(room, stream);
+	else if (type == EditBox::kClassName) {
+		if (g_engine->isV2())
+			return new EditBoxV2(room, stream);
+		else
+			return new EditBoxV3(room, stream);
+	}
 	else if (type == PushButton::kClassName)
 		return new PushButton(room, stream);
 	else if (type == CheckBox::kClassName)
 		return new CheckBox(room, stream);
 	else if (type == CheckBoxAutoAdjustNoise::kClassName)
 		return new CheckBoxAutoAdjustNoise(room, stream);
-	else if (type == SlideButton::kClassName)
-		return new SlideButton(room, stream);
+	else if (type == SlideButton::kClassName) {
+		if (g_engine->isV2())
+			return new SlideButtonV2(room, stream);
+		else
+			return new SlideButtonV3(room, stream);
+	}
 	else if (type == IRCWindow::kClassName)
 		return new IRCWindow(room, stream);
 	else if (type == MessageBox::kClassName)
@@ -109,7 +117,7 @@ void Room::readRoomV1(SeekableReadStream &stream) {
 	skipVarString(stream);
 }
 
-void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
+void Room::readRoomV2and3(SeekableReadStream &stream, bool hasUselessByte) {
 	_name = readVarString(stream);
 	_backgroundName = _name;
 	_musicId = (int)stream.readByte();
@@ -118,8 +126,10 @@ void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
 	_floors[0] = PathFindingShape(stream);
 	_floors[1] = PathFindingShape(stream);
 	_fixedCameraOnEntering = readBool(stream);
-	PathFindingShape _(stream); // unused path finding area
-	_characterAlphaPremultiplier = stream.readByte();
+	if (g_engine->isV3()) {
+		PathFindingShape _(stream); // unused path finding area
+		_characterAlphaPremultiplier = stream.readByte();
+	}
 	if (hasUselessByte)
 		stream.readByte();
 
@@ -362,7 +372,7 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
 }
 
 OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream) : Room(world) {
-	readRoomV3(stream, true);
+	readRoomV2and3(stream, true);
 	initBackground();
 }
 
@@ -398,12 +408,12 @@ void OptionsMenu::clearLastSelectedObject() {
 }
 
 ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream) : Room(world) {
-	readRoomV3(stream, true);
+	readRoomV2and3(stream, true);
 	initBackground();
 }
 
 ListenMenu::ListenMenu(World *world, SeekableReadStream &stream) : Room(world) {
-	readRoomV3(stream, true);
+	readRoomV2and3(stream, true);
 	initBackground();
 }
 
@@ -413,7 +423,7 @@ Inventory::Inventory(World *world, SeekableReadStream &stream) : Room(world) {
 		stream.skip(1); // denoted as "sinusar" but unused
 		readObjects(stream);
 	} else
-		readRoomV3(stream, true);
+		readRoomV2and3(stream, true);
 	initBackground();
 }
 
@@ -549,6 +559,7 @@ void World::load() {
 
 	auto loadWorldFile =
 		g_engine->isV1() ? &World::loadWorldFileV1
+		: g_engine->isV2() ? &World::loadWorldFileV2
 		: &World::loadWorldFileV3;
 	const char *const *mapFiles = g_engine->game().getMapFiles();
 	for (auto *itMapFile = mapFiles; *itMapFile != nullptr; itMapFile++) {
@@ -739,6 +750,45 @@ bool World::loadWorldFileV3(const char *path) {
 	return true;
 }
 
+bool World::loadWorldFileV2(const char *path) {
+	File file;
+	if (!file.open(path)) {
+		// this is not necessarily an error, the demos just have less chapter files.
+		// Being a demo is then also stored in some script vars
+		warning("Could not open world file %s\n", path);
+		return false;
+	}
+
+	uint32 startOffset = file.readUint32LE();
+	file.seek(startOffset, SEEK_SET);
+	skipVarString(file); // always "CMundo"
+	skipVarString(file); // name of the CMundo object
+	skipVarString(file); // path to sound files
+	skipVarString(file); // path to animation files
+	skipVarString(file); // path to background files
+
+	_initScriptName = readVarString(file);
+	skipVarString(file); // would be _updateScriptName, but it is never called
+	
+	const auto readGlobalAnim = [&] (
+		GlobalAnimationKind kind1,
+		GlobalAnimationKind kind2) {
+		auto fileRef = readFileRef(file);
+		_globalAnimations[(int)kind1] = fileRef;
+		if (kind2 != GlobalAnimationKind::Count)
+			_globalAnimations[(int)kind2] = fileRef;
+	};
+	readGlobalAnim(GlobalAnimationKind::GeneralFont, GlobalAnimationKind::DialogFont);
+	readGlobalAnim(GlobalAnimationKind::Cursor, GlobalAnimationKind::Count);
+	readGlobalAnim(GlobalAnimationKind::MortadeloIcon, GlobalAnimationKind::MortadeloDisabledIcon);
+	readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon);
+	readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
+
+	readRooms(file);
+
+	return true;
+}
+
 static void readEmbeddedArchive(SharedPtr<File> file);
 
 bool World::loadWorldFileV1(const char *path) {
@@ -775,7 +825,7 @@ bool World::loadWorldFileV1(const char *path) {
 	};
 	readGlobalAnim(GlobalAnimationKind::GeneralFont, GlobalAnimationKind::DialogFont);
 	readGlobalAnim(GlobalAnimationKind::Cursor, GlobalAnimationKind::Count);
-	readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon); // note this is swapped in V1
+	readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon); // note file/morta are swapped in V1
 	readGlobalAnim(GlobalAnimationKind::MortadeloIcon, GlobalAnimationKind::MortadeloDisabledIcon);
 	readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
 
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 3d3f9fda9ca..775267202dd 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -68,7 +68,7 @@ public:
 protected:
 	Room(World *world);
 	void readRoomV1(Common::SeekableReadStream &stream);
-	void readRoomV3(Common::SeekableReadStream &stream, bool hasUselessByte);
+	void readRoomV2and3(Common::SeekableReadStream &stream, bool hasUselessByte);
 	void readObjects(Common::SeekableReadStream &stream);
 	void initBackground();
 	void updateScripts();
@@ -209,6 +209,7 @@ public:
 
 private:
 	bool loadWorldFileV3(const char *path);
+	bool loadWorldFileV2(const char *path);
 	bool loadWorldFileV1(const char *path);
 	void readRooms(Common::File &file);
 	void loadLocalizedNames();
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 342322f5e8b..1b96a2d8438 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -179,8 +179,10 @@ MenuButton::MenuButton(Room *room, SeekableReadStream &stream)
 	, _actionId(stream.readSint32LE())
 	, _graphicNormal(stream)
 	, _graphicHovered(stream)
-	, _graphicClicked(stream)
-	, _graphicDisabled(stream) {}
+	, _graphicClicked(stream) {
+	if (g_engine->isV3())
+		_graphicDisabled = Graphic(stream);
+}
 
 void MenuButton::draw() {
 	if (!isEnabled())
@@ -294,16 +296,27 @@ PushButton::PushButton(Room *room, SeekableReadStream &stream)
 
 const char *EditBox::typeName() const { return "EditBox"; }
 
-EditBox::EditBox(Room *room, SeekableReadStream &stream)
-	: PhysicalObject(room, stream)
-	, i1(stream.readSint32LE())
-	, p1(Shape(stream).firstPoint())
-	, _labelId(readVarString(stream))
-	, b1(readBool(stream))
-	, i3(stream.readSint32LE())
-	, i4(stream.readSint32LE())
-	, i5(stream.readSint32LE())
-	, _fontId(0) {
+EditBoxV2::EditBoxV2(Room *room, SeekableReadStream &stream)
+	: EditBox(room, stream) {
+	p1 = Shape(stream).firstPoint();
+	auto p2 = Shape(stream).firstPoint();
+	i1 = p2.x - p1.x;
+	_labelId = readVarString(stream);
+	b1 = readBool(stream);
+	i3 = stream.readSint32LE();
+	i4 = stream.readSint32LE();
+	i5 = stream.readSint32LE();
+}
+
+EditBoxV3::EditBoxV3(Room *room, SeekableReadStream &stream)
+	: EditBox(room, stream) {
+	i1 = stream.readSint32LE();
+	p1 = Shape(stream).firstPoint();
+	_labelId = readVarString(stream);
+	b1 = readBool(stream);
+	i3 = stream.readSint32LE();
+	i4 = stream.readSint32LE();
+	i5 = stream.readSint32LE();
 
 	if (g_engine->version() == EngineVersion::V3_1)
 		_fontId = stream.readSint32LE();
@@ -383,14 +396,25 @@ CheckBoxAutoAdjustNoise::CheckBoxAutoAdjustNoise(Room *room, SeekableReadStream
 
 const char *SlideButton::typeName() const { return "SlideButton"; }
 
-SlideButton::SlideButton(Room *room, SeekableReadStream &stream)
-	: ObjectBase(room, stream)
-	, _valueId(stream.readSint32LE())
-	, _minPos(Shape(stream).firstPoint())
-	, _maxPos(Shape(stream).firstPoint())
-	, _graphicIdle(stream)
-	, _graphicHovered(stream)
-	, _graphicClicked(stream) {}
+SlideButtonV2::SlideButtonV2(Room *room, SeekableReadStream &stream)
+	: SlideButton(room, stream) {
+	_valueId = stream.readSint32LE();
+	_minPos = Shape(stream).firstPoint();
+	_maxPos = Shape(stream).firstPoint();
+	_graphicIdle = Graphic(stream);
+	_graphicHovered = _graphicIdle;
+	_graphicClicked = Graphic(stream);
+}
+
+SlideButtonV3::SlideButtonV3(Room *room, SeekableReadStream &stream)
+	: SlideButton(room, stream) {
+	_valueId = stream.readSint32LE();
+	_minPos = Shape(stream).firstPoint();
+	_maxPos = Shape(stream).firstPoint();
+	_graphicIdle = Graphic(stream);
+	_graphicHovered = Graphic(stream);
+	_graphicClicked = Graphic(stream);
+}
 
 void SlideButton::draw() {
 	auto *optionsMenu = dynamic_cast<OptionsMenu *>(room());


Commit: dde4b70f72aada9beabba374eff79303f704c0a1
    https://github.com/scummvm/scummvm/commit/dde4b70f72aada9beabba374eff79303f704c0a1
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-12T15:41:35+01:00

Commit Message:
ALCACHOFA: V2: Add support for V2 animations

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


diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 5c50d72822c..b476bbbe055 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -171,6 +171,7 @@ void AnimationBase::load() {
 			rawStream = g_engine->world().openFileRef(_fileRef);
 	} else {
 		// for real file paths we have to apply the folder and do some fallback
+		const char *extension = g_engine->isV3() ? ".AN0" : ".ANI";
 		String fullPath;
 		const auto getFullPath = [&] (AnimationFolder folder) {
 			switch (folder) {
@@ -188,8 +189,8 @@ void AnimationBase::load() {
 				break;
 			}
 			fullPath += _fileRef._path;
-			if (_fileRef._path.size() < 4 || scumm_strnicmp(_fileRef._path.end() - 4, ".AN0", 4) != 0)
-				fullPath += ".AN0";
+			if (!_fileRef._path.hasSuffixIgnoreCase(extension))
+				fullPath += extension;
 		};
 		getFullPath(_folder);
 
@@ -217,6 +218,8 @@ void AnimationBase::load() {
 		wrapBufferedSeekableReadStream(rawStream.get(), rawStream->size(), DisposeAfterUse::NO));
 	if (g_engine->isV1())
 		readV1(*stream);
+	else if (g_engine->isV2())
+		readV2(*stream);
 	else
 		readV3(*stream);
 	_isLoaded = true;
@@ -257,14 +260,53 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
 			_images.push_back(image.render(alpha));
 		}
 	}
+	createIndexMappingV1and2(spriteOrder);
+	readFramesV1and2(stream, frameCount, spriteCount);
+}
+
+void AnimationBase::readV2(SeekableReadStream &stream) {
+	char magic[4];
+	stream.read(magic, sizeof(magic));
+	scumm_assert(memcmp(magic, "ANI", 4) == 0);
 
+	stream.skip(4); // unused and unknown
+	uint spriteCount = stream.readUint32LE();
+	uint frameCount = stream.readUint32LE();
+	scumm_assert(spriteCount <= kMaxSpriteIDsV1);
+	_spriteBases.reserve(spriteCount);
+	_spriteEnabled.resize(spriteCount, true); // all sprites are enabled
+	_spriteOffsets.reserve(spriteCount * frameCount);
+	
+	_totalDuration = stream.readUint32LE();
+	byte alpha = stream.readByte();
+	stream.skip(8);
+
+	Array<byte> spriteOrder;
+	spriteOrder.reserve(spriteCount);
+	for (uint i = 0; i < spriteCount; i++) {
+		uint imageCount = stream.readUint32LE();
+		spriteOrder.push_back(stream.readByte());
+		stream.skip(4);
+
+		_spriteBases.push_back(_images.size());
+		for (uint j = 0; j < imageCount; j++) {
+			ImageV1 image(stream);
+			_imageOffsets.push_back(image.drawOffset());
+			_images.push_back(image.render(alpha));
+		}
+	}
+	createIndexMappingV1and2(spriteOrder);
+	readFramesV1and2(stream, frameCount, spriteCount);
+}
+
+void AnimationBase::createIndexMappingV1and2(const Array<byte> &spriteOrder) {
 	// Sprite order is setup by setting up index sequence and
 	// then stable sort descending by order (here: Bubblesort)
-	for (uint i = 0; i < spriteCount; i++)
+	for (uint i = 0; i < spriteOrder.size(); i++)
 		_spriteIndexMapping[i] = i;
-	for (uint i = 0; i < spriteCount; i++) {
+	for (uint i = 0; i < spriteOrder.size(); i++) {
 		bool hadChange = false;
-		for (uint j = 0; j < spriteCount - i - 1; j++) {
+		for (uint j = 0; j < spriteOrder.size() - i - 1; j++) {
 			if (spriteOrder[_spriteIndexMapping[j]] < spriteOrder[_spriteIndexMapping[j + 1]]) {
 				SWAP(_spriteIndexMapping[j], _spriteIndexMapping[j + 1]);
 				hadChange = true;
@@ -273,7 +315,9 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
 		if (!hadChange)
 			break;
 	}
+}
 
+void AnimationBase::readFramesV1and2(Common::SeekableReadStream &stream, uint frameCount, uint spriteCount) {
 	for (uint i = 0; i < frameCount; i++) {
 		for (uint j = 0; j < spriteCount; j++) {
 			int imageI = stream.readSByte();
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 21e7c4db04f..678fd903937 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -182,7 +182,10 @@ protected:
 	void freeImages();
 	void setToEmpty();
 	void readV1(Common::SeekableReadStream &stream);
+	void readV2(Common::SeekableReadStream &stream);
 	void readV3(Common::SeekableReadStream &stream);
+	void createIndexMappingV1and2(const Common::Array<byte> &spriteOrder);
+	void readFramesV1and2(Common::SeekableReadStream &stream, uint frameCount, uint spriteCount);
 	Graphics::ManagedSurface *readImageV3(Common::SeekableReadStream &stream) const;
 	Common::Point imageSize(int32 imageI) const;
 	inline bool isLoaded() const { return _isLoaded; }


Commit: 430293d3b6119c878526045ea5c18ebaf691dc4e
    https://github.com/scummvm/scummvm/commit/430293d3b6119c878526045ea5c18ebaf691dc4e
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-12T15:41:35+01:00

Commit Message:
ALCACHOFA: V2: First pass to fix secta

Changed paths:
    engines/alcachofa/game-v2.cpp
    engines/alcachofa/player.cpp
    engines/alcachofa/script.cpp


diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index ff3e4369176..ed1966a348f 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -199,6 +199,10 @@ public:
 	Point getMainCharacterSize() override {
 		return { 40, 220 };
 	}
+
+	bool doesRoomHaveBackground(const Room *room) override {
+		return !room->name().equalsIgnoreCase("Global");
+	}
 };
 
 static constexpr const char *kMapFilesSecta[] = {
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index e4cdc817d84..e3ed38daec9 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -58,9 +58,8 @@ void Player::resetCursor() {
 }
 
 void Player::updateCursor() {
-	if (g_engine->isV1())
-		_cursorFrameI = 0;
-	else if (g_engine->menu().isOpen())
+	// TODO: V2 has additional cursor frames. How are they used?
+	if (g_engine->isV1() || g_engine->isV2() || g_engine->menu().isOpen())
 		_cursorFrameI = 0;
 	else if (_selectedObject == nullptr)
 		_cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 7d3fd426191..5063163d45f 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -476,16 +476,15 @@ private:
 		// in V3 this is "by the script" by having a special PopN op
 		// in V1 we have to know the proper number of arguments per kernel call, so we do it in kernelCall
 
-		if (g_engine->isV3()) {
+		if (g_engine->isV1()) {
+			popN(g_engine->game().getKernelTaskArgCount(_lastKernelTaskI));
+		}
+		else {
 			scumm_assert(
 				_pc < _script._instructions.size() &&
 				g_engine->game().getScriptOpMap()[_script._instructions[_pc]._op] == ScriptOp::PopN);
 			popN(_script._instructions[_pc++]._arg);
 		}
-		else {
-			assert(g_engine->isV1());
-			popN(g_engine->game().getKernelTaskArgCount(_lastKernelTaskI));
-		}
 		pushNumber(returnValue);
 	}
 




More information about the Scummvm-git-logs mailing list