[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