[Scummvm-git-logs] scummvm master -> 7faac192b0d78c44ba805ed9e1ac57a359d7d815
Helco
noreply at scummvm.org
Wed Feb 4 15:56:56 UTC 2026
This automated email contains information about 62 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
6a9b4e8d9b ALCACHOFA: Add detection entry for disk-copied edicion original
83b27e9efc ALCACHOFA: Add abstraction for game startup
48e68a9a71 ALCACHOFA: Add support for V1 world files
b6ad37d4b1 ALCACHOFA: Use SeekableReadStream for object reads
6f74da160b ALCACHOFA: Add support for V1 rooms
81ddc5c1e0 ALCACHOFA: Support reading V1 objects
8e3a73a757 ALCACHOFA: Support V1 text files
a16216b86a ALCACHOFA: Simplify room reading with V1 vs V3
7000df5f76 ALCACHOFA: Support V1 script files
ed18828503 ALCACHOFA: Add script translation maps
c6c0723605 ALCACHOFA: Add reading of V1 images
0d9d8e1efa ALCACHOFA: Support reading V1 animations
b800ace239 ALCACHOFA: Fix V1 animation reading
f89eb3f6a4 ALCACHOFA: Fix script variable count
294faec0cb ALCACHOFA: Implement differences in script instructions
81d9c9c6d2 ALCACHOFA: Fix script variable count
2a92c67e26 ALCACHOFA: Fix V1 backgrounds and empty animations
ab1cbeb172 ALCACHOFA: Replace dependencies on version-dependent script variables
7ff8d738bb ALCACHOFA: Add kernel task Disguise
4d269d4ac1 ALCACHOFA: Fix regression with file-based animation refs
d368ed8354 ALCACHOFA: Use game-specific video paths
5108587f16 ALCACHOFA: Use game-specific sound paths
e8e352c20b ALCACHOFA: Fix V1 image rendering
1d69d82ed2 ALCACHOFA: Disable cursor frame update on V1
ff4195f43e ALCACHOFA: Adapt V1 isAllowedToOpenMenu
1deb9a1156 ALCACHOFA: Adapt different MainCharacterKind values in V1
f880658010 ALCACHOFA: Adapt V1 text layout and rendering
783301dbcf ALCACHOFA: Fix room bounds for V1
d182cef022 ALCACHOFA: Fix faulty rebase
d10f4db574 ALCACHOFA: Remove unused isGameLoaded variable
f27397c0c0 ALCACHOFA: Fix disguise kernel proc and prepare locking changes
1d7468a6cc ALCACHOFA: Adapt interaction and menu conditions for V1
1cdfcdbfe5 ALCACHOFA: Fix room transitions when examining items
cb07e9349d ALCACHOFA: V1: Fix change character button
030df0392a ALCACHOFA: V1: Add inventory button
6cb669d0e9 ALCACHOFA: Add script debug command
ce073b3bec ALCACHOFA: V1: Fix stack handling of ReturnVoid
e8c9400dca ALCACHOFA: V1: Fix animation sprite order
04fc32f14c ALCACHOFA: V1: Fix room bounds and 3D draw positions
c5d17a19f4 ALCACHOFA: V1: Implement V1-style menu
a304228f6f ALCACHOFA: Add subtitles toggle key
f5d7db4017 ALCACHOFA: V1: Fix subtitle position
72f6a63534 ALCACHOFA: Add call address stack
6ab60b8648 ALCACHOFA: V1: Fix missing character icon during dialog menus
f6f596eb1e ALCACHOFA: V1: Fix various original bugs in POBLADO_INDIO
ff43ad36dc ALCACHOFA: V1: Fix object ref from other room
b0f962d68c ALCACHOFA: V1: Downport fix for Puerta_Casa_Freddy_Intermedia
23c14147b2 ALCACHOFA: Fix misuse of variable address for kernel args
aff06afe3d ALCACHOFA: V1: Fix sound paths with extension and mixed-casing
07805df596 ALCACHOFA: Fix invalid music when changing chars
5fdf0dfd99 ALCACHOFA: V1: Adapt ScriptTimerTask
afa758a8e8 ALCACHOFA: V1: Add support for Terror and Vaqueros
92530947ce ALCACHOFA: V1: Fix loading aventuradecine saves
080caa0613 ALCACHOFA: Fix display of untranslated object names
7020ad213a ALCACHOFA: V1: Fix character icon during input waiting
c7a29913bb ALCACHOFA: V1: Fix CamDisguiseTask not moving camera
8fe6f1d12f ALCACHOFA: V1: Fix load being blocked after getting card deck in CARROMATO
0c8ded9504 ALCACHOFA: V1: Disable collision avoidance
e784fb80b9 ALCACHOFA: V1: Fix size of main character
2dc75afa6d ALCACHOFA: V1: Add option to enable texture filtering
25a0e0e193 ALCACHOFA: V1: Fix sound issues in Intro_Terror
7faac192b0 ALCACHOFA: Fix compilation on ScopedPtr conversion
Commit: 6a9b4e8d9be5f3af7615187a5fb59b7f562b2e43
https://github.com/scummvm/scummvm/commit/6a9b4e8d9be5f3af7615187a5fb59b7f562b2e43
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Add detection entry for disk-copied edicion original
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 065d3e542f2..a83aae6d5a4 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -310,6 +310,8 @@ void AlcachofaEngine::pauseEngineIntern(bool pause) {
}
bool AlcachofaEngine::canLoadGameStateCurrently(U32String *msg) {
+ if (_menu == nullptr)
+ return false; // the autosave wants to trigger even during error() while starting the game
if (!_eventLoopSemaphore.isReleased())
return false;
return
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 63ed05870c2..eb77e0043a7 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -127,6 +127,22 @@ const AlcachofaGameDescription gameDescriptions[] = {
EngineVersion::V3_0
},
+ {
+ // Disk files copied into disk1/disk2
+ {
+ "aventuradecine",
+ "Mortadelo y Filemón: Una Aventura de Cine - Edición Original",
+ AD_ENTRY2s(
+ "disk1/Install/oeste.emc", "b4c1084557d4cfbae336f0e741ec9e9f", 183099320,
+ "disk2/Install/terror.emc", "dc9357ee618bff160e2e2afa168ba913", 170113868
+ ),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD
+ },
+ EngineVersion::V1_0
+ },
+
{ AD_TABLE_END_MARKER, EngineVersion::V1_0 }
};
Commit: 83b27e9efc766865a719a9e10712698ebaea721c
https://github.com/scummvm/scummvm/commit/83b27e9efc766865a719a9e10712698ebaea721c
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Add abstraction for game startup
Changed paths:
A engines/alcachofa/game-movie-adventure-original.cpp
A engines/alcachofa/game-movie-adventure-special.cpp
R engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/detection_tables.h
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/module.mk
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index a83aae6d5a4..17b43f1c126 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -78,8 +78,8 @@ Common::String AlcachofaEngine::getGameId() const {
Common::Error AlcachofaEngine::run() {
g_system->showMouse(false);
setDebugger(_console);
- _game.reset(Game::createForMovieAdventure());
- _renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
+ _game.reset(Game::create());
+ _renderer.reset(IRenderer::createOpenGLRenderer(game().getResolution()));
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
_script.reset(new Script());
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index ea68e463690..05123797ebe 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -117,6 +117,10 @@ public:
inline bool isV3() const { return gameDescription().isVersionBetween(30, 39); }
inline const AlcachofaGameDescription &gameDescription() const { return *_gameDescription; }
+ inline bool isV1() const { return _gameDescription->engineVersion == EngineVersion::V1; }
+ inline bool isV2() const { return _gameDescription->engineVersion == EngineVersion::V2; }
+ inline bool isV3() const { return _gameDescription->engineVersion == EngineVersion::V3; }
+
inline IRenderer &renderer() { return *_renderer; }
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index eb77e0043a7..bee3943db5b 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -28,7 +28,7 @@ const PlainGameDescriptor alcachofaGames[] = {
const AlcachofaGameDescription gameDescriptions[] = {
//
- // A Movie Adventure
+ // A Movie Adventure - Edicion Especial
//
{
{
@@ -127,6 +127,9 @@ const AlcachofaGameDescription gameDescriptions[] = {
EngineVersion::V3_0
},
+ //
+ // A Movie Adventure - Edicion Original
+ //
{
// Disk files copied into disk1/disk2
{
@@ -143,7 +146,7 @@ const AlcachofaGameDescription gameDescriptions[] = {
EngineVersion::V1_0
},
- { AD_TABLE_END_MARKER, EngineVersion::V1_0 }
+ { AD_TABLE_END_MARKER }
};
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
new file mode 100644
index 00000000000..a9e404bcd4b
--- /dev/null
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -0,0 +1,62 @@
+/* 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"
+
+#include "common/config-manager.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+class GameMovieAdventureOriginal : public Game {
+public:
+ GameMovieAdventureOriginal() {
+ const auto &desc = g_engine->gameDescription();
+ if (desc.desc.flags & ADGF_CD) {
+ const Path gameDir = ConfMan.getPath("path");
+ SearchMan.addDirectory(gameDir.append("disk1/Install"));
+ SearchMan.addDirectory(gameDir.append("disk2/Install"));
+ }
+ }
+
+ Point getResolution() {
+ return Point(800, 600);
+ }
+
+ static constexpr const char *kMapFiles[] = {
+ "oeste.emc",
+ "terror.emc",
+ "global.emc",
+ nullptr
+ };
+ const char *const *getMapFiles() {
+ return kMapFiles;
+ }
+};
+
+Game *Game::createForMovieAdventureOriginal() {
+ return new GameMovieAdventureOriginal();
+}
+
+}
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
similarity index 99%
rename from engines/alcachofa/game-movie-adventure.cpp
rename to engines/alcachofa/game-movie-adventure-special.cpp
index 54611ba097c..215ab5a6bec 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -4,7 +4,7 @@
* 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
+ * This program is free software: you can redistribute it and/or modifyhttps://store.steampowered.com/app/3012980/Moorhuhn_Kart_4/
* 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.
@@ -458,7 +458,7 @@ public:
}
};
-Game *Game::createForMovieAdventure() {
+Game *Game::createForMovieAdventureSpecial() {
if (g_engine->version() == EngineVersion::V3_0)
return new GameMovieAdventureSpecialV30();
else
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index a69f6543798..36c2abbf59c 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -199,4 +199,17 @@ void Game::invalidVideo(int32 videoId, const char *context) {
_message("Could not play video %d (%s)", videoId, context);
}
+Game *Game::create() {
+ const auto &desc = g_engine->gameDescription();
+ switch (desc.engineVersion) {
+ case EngineVersion::V1:
+ return createForMovieAdventureOriginal();
+ case EngineVersion::V3:
+ return createForMovieAdventureSpecial();
+ default:
+ error("Unsupported or invalid engine version: %d", (int)desc.engineVersion);
+ break;
+ }
+}
+
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 6f258055afc..0207edd36b6 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -100,7 +100,9 @@ public:
virtual bool isKnownBadVideo(int32 videoId);
virtual void invalidVideo(int32 videoId, const char *context);
- static Game *createForMovieAdventure();
+ static Game *create();
+ static Game *createForMovieAdventureSpecial();
+ static Game *createForMovieAdventureOriginal();
const Message _message;
};
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 294ea76ce07..7921ffcc97d 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -6,7 +6,8 @@ MODULE_OBJS = \
common.o \
console.o \
game.o \
- game-movie-adventure.o \
+ game-movie-adventure-original.o \
+ game-movie-adventure-special.o \
game-objects.o \
general-objects.o \
global-ui.o \
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c9c8372506a..2bd251f81ff 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -493,18 +493,9 @@ void Room::debugPrint(bool withObjects) const {
}
}
-static constexpr const char *kMapFiles[] = {
- "MAPAS/MAPA5.EMC",
- "MAPAS/MAPA4.EMC",
- "MAPAS/MAPA3.EMC",
- "MAPAS/MAPA2.EMC",
- "MAPAS/MAPA1.EMC",
- "MAPAS/GLOBAL.EMC",
- nullptr
-};
-
World::World() {
- for (auto *itMapFile = kMapFiles; *itMapFile != nullptr; itMapFile++) {
+ const char *const *mapFiles = g_engine->game().getMapFiles();
+ for (auto *itMapFile = mapFiles; *itMapFile != nullptr; itMapFile++) {
if (loadWorldFile(*itMapFile))
_loadedMapCount++;
}
Commit: 48e68a9a715dccb6aa91e1980bcfedb8eb4024de
https://github.com/scummvm/scummvm/commit/48e68a9a715dccb6aa91e1980bcfedb8eb4024de
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Add support for V1 world files
Changed paths:
engines/alcachofa/common.h
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 8cb8adad5b3..a274df1c0d9 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -166,6 +166,40 @@ inline void syncEnum(Common::Serializer &serializer, T &enumValue) {
enumValue = static_cast<T>(intValue);
}
+/**
+ * @brief References a game file either as path or as embedded byte range
+ *
+ * In V1 all files (except videos) are stored within the EMC file. Some of
+ * those are within an embedded archive, most animations however are stored
+ * at their graphics and have their original filenames. We reference them
+ * by their byte range.
+ *
+ * V2/V3 store files outside with normal paths to use
+ */
+struct GameFileReference {
+ Common::String _path;
+ uint32
+ _fileIndex = UINT32_MAX,
+ _position = UINT32_MAX,
+ _size = 0;
+
+ GameFileReference() {}
+
+ GameFileReference(const Common::String &path)
+ : _path(path) {}
+
+ // in this case, path is only for debugging purposes
+ GameFileReference(const Common::String &path, uint32 fileIndex, int64 position, uint32 size)
+ : _path(path)
+ , _fileIndex(fileIndex)
+ , _position(position)
+ , _size(size) {}
+
+ inline bool isValid() const {
+ return !_path.empty() || _position >= 0;
+ }
+};
+
}
#endif // ALCACHOFA_COMMON_H
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index a9e404bcd4b..b51490f38e7 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -53,6 +53,11 @@ public:
const char *const *getMapFiles() {
return kMapFiles;
}
+
+ GameFileReference getScriptFileRef() {
+ // V1 embeds the script into global.emc, it is overridden during world load
+ return {};
+ }
};
Game *Game::createForMovieAdventureOriginal() {
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 215ab5a6bec..51fb99d3d52 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -208,6 +208,10 @@ public:
return kMapFiles;
}
+ GameFileReference getScriptFileRef() {
+ return { "Script/SCRIPT.COD" };
+ }
+
Span<const ScriptOp> getScriptOpMap() override {
return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 0207edd36b6..fa64a1dea70 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -52,6 +52,7 @@ public:
virtual void onLoadedGameFiles();
virtual Common::Point getResolution() = 0;
virtual const char *const *getMapFiles() = 0; ///< Returns a nullptr-terminated list
+ virtual GameFileReference getScriptFileRef() = 0;
virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
virtual void updateScriptVariables() = 0;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 2bd251f81ff..f4090100bab 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -26,6 +26,7 @@
#include "alcachofa/menu.h"
#include "common/file.h"
+#include "common/substream.h"
using namespace Common;
@@ -494,13 +495,19 @@ void Room::debugPrint(bool withObjects) const {
}
World::World() {
+ _scriptFileRef = g_engine->game().getScriptFileRef();
+
+ auto loadWorldFile =
+ g_engine->isV1() ? &World::loadWorldFileV1
+ : &World::loadWorldFileV3;
const char *const *mapFiles = g_engine->game().getMapFiles();
for (auto *itMapFile = mapFiles; *itMapFile != nullptr; itMapFile++) {
- if (loadWorldFile(*itMapFile))
+ if ((*this.*loadWorldFile)(*itMapFile))
_loadedMapCount++;
}
loadLocalizedNames();
loadDialogLines();
+ _isLoading = false;
_globalRoom = getRoomByName("GLOBAL");
if (_globalRoom == nullptr)
@@ -608,7 +615,8 @@ void World::toggleObject(MainCharacterKind character, const char *objName, bool
const Common::String &World::getGlobalAnimationName(GlobalAnimationKind kind) const {
int kindI = (int)kind;
assert(kindI >= 0 && kindI < (int)GlobalAnimationKind::Count);
- return _globalAnimationNames[kindI];
+ error("You broke this, remember?");
+ //return _globalAnimationNames[kindI];
}
const char *World::getLocalizedName(const String &name) const {
@@ -624,7 +632,7 @@ const char *World::getDialogLine(int32 dialogId) const {
return _dialogLines[dialogId];
}
-static Room *readRoom(World *world, SeekableReadStream &stream) {
+static Room *readRoomV3(World *world, SeekableReadStream &stream) {
const auto type = readVarString(stream);
if (type == Room::kClassName)
return new Room(world, stream);
@@ -642,45 +650,98 @@ static Room *readRoom(World *world, SeekableReadStream &stream) {
}
}
-bool World::loadWorldFile(const char *path) {
- Common::File file;
+/* World files start with a self-description of the data format (after 1-2 offsets)
+ * We ignore the self-description and just read the actual data
+ * The first offset points to the room room data
+ * (Only V1) The second offset points to the script file
+ */
+
+bool World::loadWorldFileV3(const char *path) {
+ File file;
if (!file.open(path)) {
- // this is not necessarily an error, apparently the demos just have less
- // chapter files. Being a demo is then also stored in some script vars
+ // 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;
}
- // the first chunk seems to be debug symbols and/or info about the file structure
- // it is ignored in the published game.
- auto startOffset = file.readUint32LE();
+ uint32 startOffset = file.readUint32LE();
file.seek(startOffset, SEEK_SET);
- skipVarString(file); // some more unused strings related to development files?
- skipVarString(file);
- skipVarString(file);
- skipVarString(file);
- skipVarString(file);
- skipVarString(file);
+ 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
+ skipVarString(file); // path to masks (static object animations) files
_initScriptName = readVarString(file);
skipVarString(file); // would be _updateScriptName, but it is never called
for (int i = 0; i < (int)GlobalAnimationKind::Count; i++)
- _globalAnimationNames[i] = readVarString(file);
+ _globalAnimations[i] = readFileRef(file);
+
+ readRooms(readRoomV3, file);
+
+ return true;
+}
+
+static void readEmbeddedArchive(SharedPtr<File> file);
+
+bool World::loadWorldFileV1(const char *path) {
+ auto file = SharedPtr<File>(new 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();
+ uint32 scriptOffset = file->readUint32LE();
+ file->seek(scriptOffset, SEEK_SET);
+ if (file->readByte() == 1)
+ _scriptFileRef = { "SCRIPT", _files.size(), file->pos(), (uint32)(file->size() - file->pos())};
+
+ file->seek(startOffset, SEEK_SET);
+ skipVarString(*file); // always "CMundo"
+ skipVarString(*file); // name of the CMundo object
+ skipVarString(*file); // *second* name of the CMundo object
+ file->readByte(); // would be "isFinalFile", but always one for released games
+ readEmbeddedArchive(file);
+ _initScriptName = readVarString(*file);
+ skipVarString(*file); // _updateScriptName
+
+ const auto readGlobalAnim = [&] (
+ GlobalAnimationKind kind1,
+ GlobalAnimationKind kind2 = GlobalAnimationKind::Count) {
+ auto fileRef = readFileRef(*file);
+ _globalAnimations[(int)kind1] = fileRef;
+ if (kind2 != GlobalAnimationKind::Count)
+ _globalAnimations[(int)kind2] = fileRef;
+ };
+ readGlobalAnim(GlobalAnimationKind::GeneralFont, GlobalAnimationKind::DialogFont);
+ readGlobalAnim(GlobalAnimationKind::Cursor);
+ readGlobalAnim(GlobalAnimationKind::MortadeloIcon, GlobalAnimationKind::MortadeloDisabledIcon);
+ readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon);
+ readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
+
+ readRooms(readRoomV3, *file);
+ _files.emplace_back(move(file));
+ return true;
+}
+void World::readRooms(World::ReadRoomFunc readRoom, File &file) {
uint32 roomEnd = file.readUint32LE();
while (roomEnd > 0) {
Room *room = readRoom(this, file);
if (room != nullptr)
_rooms.push_back(room);
if (file.pos() < roomEnd) {
- g_engine->game().notEnoughRoomDataRead(path, file.pos(), roomEnd);
+ g_engine->game().notEnoughRoomDataRead(file.getName(), file.pos(), roomEnd);
file.seek(roomEnd, SEEK_SET);
} else if (file.pos() > roomEnd) // this surely is not recoverable
- error("Read past the room data for world %s", path);
+ error("Read past the room data for world %s", file.getName());
roomEnd = file.readUint32LE();
}
-
- return true;
}
/**
@@ -790,4 +851,128 @@ void World::syncGame(Serializer &s) {
room->syncGame(s);
}
+GameFileReference World::readFileRef(SeekableReadStream &stream) const {
+ assert(_isLoading);
+ auto name = readVarString(stream);
+ if (g_engine->isV1()) {
+ uint32 size = stream.readUint32LE();
+ stream.skip(size);
+ return { name, (uint32)_files.size(), (uint32)stream.pos(), size };
+ } else
+ return { name };
+}
+
+ScopedPtr<SeekableReadStream> World::openFileRef(const GameFileReference &ref) const {
+ assert(!_isLoading);
+ if (!ref.isValid())
+ return nullptr;
+ else if (ref._fileIndex != UINT32_MAX) {
+ assert(ref._fileIndex < _files.size());
+ auto &file = _files[ref._fileIndex];
+ if (!file->seek(ref._position, SEEK_SET))
+ error("Could not seek to inline file %s at %u", ref._path.c_str(), ref._position);
+ return ScopedPtr<SeekableReadStream>(
+ new SeekableSubReadStream(file.get(), ref._position, ref._position + ref._size, DisposeAfterUse::NO));
+ } else {
+ ScopedPtr<File> file(new File());
+ if (!file->open(Path(ref._path)))
+ return nullptr;
+ return file;
+ }
+}
+
+class EmbeddedArchive;
+class EmbeddedArchiveMember : public GenericArchiveMember {
+ friend class EmbeddedArchive;
+ uint32 _offset;
+ uint32 _end;
+
+ EmbeddedArchiveMember(const String &pathStr, const EmbeddedArchive &parent, uint32 offset, uint32 end);
+};
+
+class EmbeddedArchive : public Archive {
+public:
+ EmbeddedArchive(SharedPtr<File> file) : _file(move(file)) {
+ // the stored filenames have their original full paths,
+ // e.g. c:\myf2000\textos\dialogos.txt
+ // but the filenames alone do not clash so we flatten everything
+
+ skipVarString(*file);
+ uint32 totalSize = _file->readUint32LE();
+ int64 endPosition = _file->pos() + totalSize;
+ _file->skip(2);
+ uint32 fileCount = _file->readUint32LE();
+ _members.reserve((uint)fileCount);
+
+ for (uint32 i = 0; i < fileCount; i++) {
+ auto fullPath = readVarString(*_file);
+ auto extension = readVarString(*_file);
+ uint32 fileSize = _file->readUint32LE();
+ uint32 fileOffset = (uint32)_file->pos();
+ _file->skip(fileSize);
+
+ uint32 lastSep = fullPath.findLastOf('\\');
+ if (lastSep == String::npos)
+ lastSep = 0;
+ auto fileName = String::format("%s.%s", fullPath.c_str() + lastSep, extension.c_str());
+ _members.emplace_back(
+ new EmbeddedArchiveMember(fileName, *this, fileOffset, fileOffset + fileSize));
+ }
+
+ if (_file->pos() > endPosition)
+ error("Read past the specified archive total size in %s", _file->getName());
+ _file->seek(endPosition, SEEK_SET);
+ scumm_assert(!_file->err());
+ }
+
+ bool hasFile(const Path &path) const override {
+ return getMember(path) != nullptr;
+ }
+
+ int listMembers(ArchiveMemberList &list) const override {
+ for (const auto &member : _members)
+ list.emplace_back(member);
+ return (int)_members.size();
+ }
+
+ const ArchiveMemberPtr getMember(const Path &path) const {
+ return getMemberInternal(path);
+ }
+
+ SeekableReadStream *createReadStreamForMember(const Path &path) const {
+ auto member = getMemberInternal(path);
+ if (member == nullptr)
+ return nullptr;
+ if (!_file->seek(member->_offset, SEEK_SET))
+ error("Could not seek to embedded file: %s at %u", path.toString().c_str(), member->_offset);
+ return new SeekableSubReadStream(_file.get(), member->_offset, member->_end, DisposeAfterUse::NO);
+ }
+
+private:
+ const SharedPtr<EmbeddedArchiveMember> getMemberInternal(const Path &path) const {
+ for (const auto &member : _members) {
+ if (member->getPathInArchive() == path)
+ return member;
+ }
+ return nullptr;
+ }
+
+ SharedPtr<File> _file;
+ Array<SharedPtr<EmbeddedArchiveMember>> _members;
+};
+
+EmbeddedArchiveMember::EmbeddedArchiveMember(
+ const String &pathStr,
+ const EmbeddedArchive &parent,
+ uint32 offset,
+ uint32 end)
+ : GenericArchiveMember(pathStr, parent)
+ , _offset(offset)
+ , _end(end) {}
+
+static void readEmbeddedArchive(SharedPtr<File> file) {
+ auto archive = new EmbeddedArchive(file);
+ SearchMan.add(file->getName(), archive);
+}
+
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 2ca7ee43cb1..df73b4f47fc 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -164,6 +164,7 @@ public:
inline Inventory &inventory() const { assert(_inventory != nullptr); return *_inventory; }
inline MainCharacter &filemon() const { assert(_filemon != nullptr); return *_filemon; }
inline MainCharacter &mortadelo() const { assert(_mortadelo != nullptr); return *_mortadelo; }
+ inline GameFileReference scriptFileRef() const { return _scriptFileRef; }
inline const Common::String &initScriptName() const { return _initScriptName; }
inline uint8 loadedMapCount() const { return _loadedMapCount; }
@@ -185,8 +186,14 @@ public:
void toggleObject(MainCharacterKind character, const char *objName, bool isEnabled);
void syncGame(Common::Serializer &s);
+ GameFileReference readFileRef(Common::SeekableReadStream &stream) const;
+ Common::ScopedPtr<Common::SeekableReadStream> openFileRef(const GameFileReference &ref) const;
+
private:
- bool loadWorldFile(const char *path);
+ using ReadRoomFunc = Room *(*)(World *, Common::SeekableReadStream &);
+ bool loadWorldFileV3(const char *path);
+ bool loadWorldFileV1(const char *path);
+ void readRooms(ReadRoomFunc readRoom, Common::File &file);
void loadLocalizedNames();
void loadDialogLines();
@@ -195,9 +202,11 @@ private:
bool operator()(const char *a, const char *b) const { return strcmp(a, b) == 0; }
};
+ Common::Array<Common::SharedPtr<Common::File>> _files; ///< only used in V1 to read embedded files
Common::Array<Room *> _rooms;
- Common::String _globalAnimationNames[(int)GlobalAnimationKind::Count];
+ GameFileReference _globalAnimations[(int)GlobalAnimationKind::Count];
Common::String _initScriptName;
+ GameFileReference _scriptFileRef;
Room *_globalRoom;
Inventory *_inventory;
MainCharacter *_filemon, *_mortadelo;
@@ -207,6 +216,7 @@ private:
StringEqualTo> _localizedNames;
Common::Array<const char *> _dialogLines;
Common::Array<char> _namesChunk, _dialogChunk; ///< holds the memory for localizedNames / dialogLines
+ bool _isLoading = true;
};
}
Commit: b6ad37d4b174593b82e4d28d63126e275d922511
https://github.com/scummvm/scummvm/commit/b6ad37d4b174593b82e4d28d63126e275d922511
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Use SeekableReadStream for object reads
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 8bfd05ca866..8883704b68e 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -32,7 +32,7 @@ namespace Alcachofa {
const char *Item::typeName() const { return "Item"; }
-Item::Item(Room *room, ReadStream &stream)
+Item::Item(Room *room, SeekableReadStream &stream)
: GraphicObject(room, stream) {
stream.readByte(); // unused and ignored byte
}
@@ -70,7 +70,7 @@ void Item::trigger() {
player.triggerObject(heldItem, name().c_str());
}
-ITriggerableObject::ITriggerableObject(ReadStream &stream)
+ITriggerableObject::ITriggerableObject(SeekableReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
, _interactionDirection((Direction)stream.readSint32LE()) {}
@@ -86,7 +86,7 @@ void ITriggerableObject::onClick() {
const char *InteractableObject::typeName() const { return "InteractableObject"; }
-InteractableObject::InteractableObject(Room *room, ReadStream &stream)
+InteractableObject::InteractableObject(Room *room, SeekableReadStream &stream)
: PhysicalObject(room, stream)
, ITriggerableObject(stream)
, _relatedObject(readVarString(stream)) {
@@ -120,7 +120,7 @@ void InteractableObject::toggle(bool isEnabled) {
const char *Door::typeName() const { return "Door"; }
-Door::Door(Room *room, ReadStream &stream)
+Door::Door(Room *room, SeekableReadStream &stream)
: InteractableObject(room, stream)
, _targetRoom(readVarString(stream))
, _targetObject(readVarString(stream))
@@ -162,7 +162,7 @@ void Door::trigger(const char *_) {
const char *Character::typeName() const { return "Character"; }
-Character::Character(Room *room, ReadStream &stream)
+Character::Character(Room *room, SeekableReadStream &stream)
: ShapeObject(room, stream)
, ITriggerableObject(stream)
, _graphicNormal(stream)
@@ -498,7 +498,7 @@ Task *Character::lerpLodBias(Process &process, float targetLodBias, int32 durati
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
-WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
+WalkingCharacter::WalkingCharacter(Room *room, SeekableReadStream &stream)
: Character(room, stream) {
for (int32 i = 0; i < kDirectionCount; i++) {
auto fileName = readVarString(stream);
@@ -803,7 +803,7 @@ Task *WalkingCharacter::waitForArrival(Process &process) {
const char *MainCharacter::typeName() const { return "MainCharacter"; }
-MainCharacter::MainCharacter(Room *room, ReadStream &stream)
+MainCharacter::MainCharacter(Room *room, SeekableReadStream &stream)
: WalkingCharacter(room, stream)
, _semaphore(name().firstChar() == 'M' ? "mortadelo" : "filemon") {
stream.readByte(); // unused byte
@@ -1155,7 +1155,7 @@ Background::Background(Room *room, const String &animationFileName, int16 scale)
const char *FloorColor::typeName() const { return "FloorColor"; }
-FloorColor::FloorColor(Room *room, ReadStream &stream)
+FloorColor::FloorColor(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream) {}
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 8ef4fe55f02..91252e99794 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -41,7 +41,7 @@ ObjectBase::ObjectBase(Room *room, const char *name)
assert(room != nullptr);
}
-ObjectBase::ObjectBase(Room *room, ReadStream &stream)
+ObjectBase::ObjectBase(Room *room, SeekableReadStream &stream)
: _room(room) {
assert(room != nullptr);
_name = readVarString(stream);
@@ -76,14 +76,14 @@ Shape *ObjectBase::shape() {
const char *PointObject::typeName() const { return "PointObject"; }
-PointObject::PointObject(Room *room, ReadStream &stream)
+PointObject::PointObject(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream) {
_pos = Shape(stream).firstPoint();
}
const char *GraphicObject::typeName() const { return "GraphicObject"; }
-GraphicObject::GraphicObject(Room *room, ReadStream &stream)
+GraphicObject::GraphicObject(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
, _graphic(stream)
, _type((GraphicObjectType)stream.readSint32LE())
@@ -203,7 +203,7 @@ Task *GraphicObject::animate(Process &process) {
const char *SpecialEffectObject::typeName() const { return "SpecialEffectObject"; }
-SpecialEffectObject::SpecialEffectObject(Room *room, ReadStream &stream)
+SpecialEffectObject::SpecialEffectObject(Room *room, SeekableReadStream &stream)
: GraphicObject(room, stream) {
_topLeft = Shape(stream).firstPoint();
_bottomRight = Shape(stream).firstPoint();
@@ -231,7 +231,7 @@ void SpecialEffectObject::draw() {
const char *ShapeObject::typeName() const { return "ShapeObject"; }
-ShapeObject::ShapeObject(Room *room, ReadStream &stream)
+ShapeObject::ShapeObject(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream)
, _cursorType((CursorType)stream.readSint32LE()) {}
@@ -302,7 +302,7 @@ void ShapeObject::updateSelection() {
const char *PhysicalObject::typeName() const { return "PhysicalObject"; }
-PhysicalObject::PhysicalObject(Room *room, ReadStream &stream)
+PhysicalObject::PhysicalObject(Room *room, SeekableReadStream &stream)
: ShapeObject(room, stream) {
_order = stream.readSByte();
}
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 56092156584..1bc625caa14 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -37,7 +37,7 @@ class ObjectBase {
public:
static constexpr const char *kClassName = "CObjetoBase";
ObjectBase(Room *room, const char *name);
- ObjectBase(Room *room, Common::ReadStream &stream);
+ ObjectBase(Room *room, Common::SeekableReadStream &stream);
virtual ~ObjectBase() {}
inline const Common::String &name() const { return _name; }
@@ -65,7 +65,7 @@ private:
class PointObject : public ObjectBase {
public:
static constexpr const char *kClassName = "CObjetoPunto";
- PointObject(Room *room, Common::ReadStream &stream);
+ PointObject(Room *room, Common::SeekableReadStream &stream);
inline Common::Point &position() { return _pos; }
inline Common::Point position() const { return _pos; }
@@ -85,7 +85,7 @@ enum class GraphicObjectType : byte
class GraphicObject : public ObjectBase {
public:
static constexpr const char *kClassName = "CObjetoGrafico";
- GraphicObject(Room *room, Common::ReadStream &stream);
+ GraphicObject(Room *room, Common::SeekableReadStream &stream);
~GraphicObject() override {}
void draw() override;
@@ -109,7 +109,7 @@ protected:
class SpecialEffectObject final : public GraphicObject {
public:
static constexpr const char *kClassName = "CObjetoGraficoMuare";
- SpecialEffectObject(Room *room, Common::ReadStream &stream);
+ SpecialEffectObject(Room *room, Common::SeekableReadStream &stream);
void draw() override;
const char *typeName() const override;
@@ -122,7 +122,7 @@ private:
class ShapeObject : public ObjectBase {
public:
- ShapeObject(Room *room, Common::ReadStream &stream);
+ ShapeObject(Room *room, Common::SeekableReadStream &stream);
~ShapeObject() override {}
inline int8 order() const { return _order; }
@@ -154,14 +154,14 @@ private:
class PhysicalObject : public ShapeObject {
public:
- PhysicalObject(Room *room, Common::ReadStream &stream);
+ PhysicalObject(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
};
class MenuButton : public PhysicalObject {
public:
static constexpr const char *kClassName = "CBotonMenu";
- MenuButton(Room *room, Common::ReadStream &stream);
+ MenuButton(Room *room, Common::SeekableReadStream &stream);
~MenuButton() override {}
inline int32 actionId() const { return _actionId; }
@@ -196,7 +196,7 @@ private:
class InternetMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuInternet";
- InternetMenuButton(Room *room, Common::ReadStream &stream);
+ InternetMenuButton(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
};
@@ -204,7 +204,7 @@ public:
class OptionsMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuOpciones";
- OptionsMenuButton(Room *room, Common::ReadStream &stream);
+ OptionsMenuButton(Room *room, Common::SeekableReadStream &stream);
void update() override;
void trigger() override;
@@ -214,7 +214,7 @@ public:
class MainMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuPrincipal";
- MainMenuButton(Room *room, Common::ReadStream &stream);
+ MainMenuButton(Room *room, Common::SeekableReadStream &stream);
void update() override;
void trigger() override;
@@ -224,7 +224,7 @@ public:
class PushButton final : public PhysicalObject {
public:
static constexpr const char *kClassName = "CPushButton";
- PushButton(Room *room, Common::ReadStream &stream);
+ PushButton(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
@@ -237,7 +237,7 @@ private:
class EditBox final : public PhysicalObject {
public:
static constexpr const char *kClassName = "CEditBox";
- EditBox(Room *room, Common::ReadStream &stream);
+ EditBox(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
@@ -253,7 +253,7 @@ private:
class CheckBox : public PhysicalObject {
public:
static constexpr const char *kClassName = "CCheckBox";
- CheckBox(Room *room, Common::ReadStream &stream);
+ CheckBox(Room *room, Common::SeekableReadStream &stream);
~CheckBox() override {}
inline bool &isChecked() { return _isChecked; }
@@ -284,7 +284,7 @@ private:
class SlideButton final : public ObjectBase {
public:
static constexpr const char *kClassName = "CSlideButton";
- SlideButton(Room *room, Common::ReadStream &stream);
+ SlideButton(Room *room, Common::SeekableReadStream &stream);
~SlideButton() override {}
inline float &value() { return _value; }
@@ -310,7 +310,7 @@ private:
class CheckBoxAutoAdjustNoise final : public CheckBox {
public:
static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
- CheckBoxAutoAdjustNoise(Room *room, Common::ReadStream &stream);
+ CheckBoxAutoAdjustNoise(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
};
@@ -318,7 +318,7 @@ public:
class IRCWindow final : public ObjectBase {
public:
static constexpr const char *kClassName = "CVentanaIRC";
- IRCWindow(Room *room, Common::ReadStream &stream);
+ IRCWindow(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
@@ -329,7 +329,7 @@ private:
class MessageBox final : public ObjectBase {
public:
static constexpr const char *kClassName = "CMessageBox";
- MessageBox(Room *room, Common::ReadStream &stream);
+ MessageBox(Room *room, Common::SeekableReadStream &stream);
~MessageBox() override {}
const char *typeName() const override;
@@ -346,7 +346,7 @@ private:
class VoiceMeter final : public GraphicObject {
public:
static constexpr const char *kClassName = "CVuMeter";
- VoiceMeter(Room *room, Common::ReadStream &stream);
+ VoiceMeter(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
};
@@ -354,7 +354,7 @@ public:
class Item : public GraphicObject { //-V690
public:
static constexpr const char *kClassName = "CObjetoInventario";
- Item(Room *room, Common::ReadStream &stream);
+ Item(Room *room, Common::SeekableReadStream &stream);
Item(const Item &other);
// no copy-assign operator as it is non-sensical, the copy ctor is a special case for item-handling
@@ -365,7 +365,7 @@ public:
class ITriggerableObject {
public:
- ITriggerableObject(Common::ReadStream &stream);
+ ITriggerableObject(Common::SeekableReadStream &stream);
virtual ~ITriggerableObject() {}
inline Direction interactionDirection() const { return _interactionDirection; }
@@ -383,7 +383,7 @@ protected:
class InteractableObject : public PhysicalObject, public ITriggerableObject {
public:
static constexpr const char *kClassName = "CObjetoTipico";
- InteractableObject(Room *room, Common::ReadStream &stream);
+ InteractableObject(Room *room, Common::SeekableReadStream &stream);
~InteractableObject() override {}
void drawDebug() override;
@@ -399,7 +399,7 @@ private:
class Door final : public InteractableObject {
public:
static constexpr const char *kClassName = "CPuerta";
- Door(Room *room, Common::ReadStream &stream);
+ Door(Room *room, Common::SeekableReadStream &stream);
inline const Common::String &targetRoom() const { return _targetRoom; }
inline const Common::String &targetObject() const { return _targetObject; }
@@ -419,7 +419,7 @@ private:
class Character : public ShapeObject, public ITriggerableObject {
public:
static constexpr const char *kClassName = "CPersonaje";
- Character(Room *room, Common::ReadStream &stream);
+ Character(Room *room, Common::SeekableReadStream &stream);
~Character() override {}
void update() override;
@@ -461,7 +461,7 @@ protected:
class WalkingCharacter : public Character {
public:
static constexpr const char *kClassName = "CPersonajeAnda";
- WalkingCharacter(Room *room, Common::ReadStream &stream);
+ WalkingCharacter(Room *room, Common::SeekableReadStream &stream);
~WalkingCharacter() override {}
inline bool isWalking() const { return _isWalking; }
@@ -528,7 +528,7 @@ struct DialogMenuLine {
class MainCharacter final : public WalkingCharacter {
public:
static constexpr const char *kClassName = "CPersonajePrincipal";
- MainCharacter(Room *room, Common::ReadStream &stream);
+ MainCharacter(Room *room, Common::SeekableReadStream &stream);
~MainCharacter() override;
inline MainCharacterKind kind() const { return _kind; }
@@ -588,7 +588,7 @@ public:
class FloorColor final : public ObjectBase {
public:
static constexpr const char *kClassName = "CSueloColor";
- FloorColor(Room *room, Common::ReadStream &stream);
+ FloorColor(Room *room, Common::SeekableReadStream &stream);
~FloorColor() override {}
void update() override;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index f4090100bab..96c00505166 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -34,7 +34,7 @@ namespace Alcachofa {
Room::Room(World *world, SeekableReadStream &stream) : Room(world, stream, false) {}
-static ObjectBase *readRoomObject(Room *room, const String &type, ReadStream &stream) {
+static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadStream &stream) {
if (type == ObjectBase::kClassName)
return new ObjectBase(room, stream);
else if (type == PointObject::kClassName)
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index e0e17424048..c3745935556 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -32,7 +32,7 @@ namespace Alcachofa {
const char *MenuButton::typeName() const { return "MenuButton"; }
-MenuButton::MenuButton(Room *room, ReadStream &stream)
+MenuButton::MenuButton(Room *room, SeekableReadStream &stream)
: PhysicalObject(room, stream)
, _actionId(stream.readSint32LE())
, _graphicNormal(stream)
@@ -105,12 +105,12 @@ void MenuButton::trigger() {
const char *InternetMenuButton::typeName() const { return "InternetMenuButton"; }
-InternetMenuButton::InternetMenuButton(Room *room, ReadStream &stream)
+InternetMenuButton::InternetMenuButton(Room *room, SeekableReadStream &stream)
: MenuButton(room, stream) {}
const char *OptionsMenuButton::typeName() const { return "OptionsMenuButton"; }
-OptionsMenuButton::OptionsMenuButton(Room *room, ReadStream &stream)
+OptionsMenuButton::OptionsMenuButton(Room *room, SeekableReadStream &stream)
: MenuButton(room, stream) {}
void OptionsMenuButton::update() {
@@ -126,7 +126,7 @@ void OptionsMenuButton::trigger() {
const char *MainMenuButton::typeName() const { return "MainMenuButton"; }
-MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
+MainMenuButton::MainMenuButton(Room *room, SeekableReadStream &stream)
: MenuButton(room, stream) {}
void MainMenuButton::update() {
@@ -143,7 +143,7 @@ void MainMenuButton::trigger() {
const char *PushButton::typeName() const { return "PushButton"; }
-PushButton::PushButton(Room *room, ReadStream &stream)
+PushButton::PushButton(Room *room, SeekableReadStream &stream)
: PhysicalObject(room, stream)
, _alwaysVisible(readBool(stream))
, _graphic1(stream)
@@ -152,7 +152,7 @@ PushButton::PushButton(Room *room, ReadStream &stream)
const char *EditBox::typeName() const { return "EditBox"; }
-EditBox::EditBox(Room *room, ReadStream &stream)
+EditBox::EditBox(Room *room, SeekableReadStream &stream)
: PhysicalObject(room, stream)
, i1(stream.readSint32LE())
, p1(Shape(stream).firstPoint())
@@ -169,7 +169,7 @@ EditBox::EditBox(Room *room, ReadStream &stream)
const char *CheckBox::typeName() const { return "CheckBox"; }
-CheckBox::CheckBox(Room *room, ReadStream &stream)
+CheckBox::CheckBox(Room *room, SeekableReadStream &stream)
: PhysicalObject(room, stream)
, _isChecked(readBool(stream))
, _graphicUnchecked(stream)
@@ -234,14 +234,14 @@ void CheckBox::trigger() {
const char *CheckBoxAutoAdjustNoise::typeName() const { return "CheckBoxAutoAdjustNoise"; }
-CheckBoxAutoAdjustNoise::CheckBoxAutoAdjustNoise(Room *room, ReadStream &stream)
+CheckBoxAutoAdjustNoise::CheckBoxAutoAdjustNoise(Room *room, SeekableReadStream &stream)
: CheckBox(room, stream) {
stream.readByte(); // unused and ignored byte
}
const char *SlideButton::typeName() const { return "SlideButton"; }
-SlideButton::SlideButton(Room *room, ReadStream &stream)
+SlideButton::SlideButton(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
, _valueId(stream.readSint32LE())
, _minPos(Shape(stream).firstPoint())
@@ -313,14 +313,14 @@ bool SlideButton::isMouseOver() const {
const char *IRCWindow::typeName() const { return "IRCWindow"; }
-IRCWindow::IRCWindow(Room *room, ReadStream &stream)
+IRCWindow::IRCWindow(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
, _p1(Shape(stream).firstPoint())
, _p2(Shape(stream).firstPoint()) {}
const char *MessageBox::typeName() const { return "MessageBox"; }
-MessageBox::MessageBox(Room *room, ReadStream &stream)
+MessageBox::MessageBox(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
, _graph1(stream)
, _graph2(stream)
@@ -336,7 +336,7 @@ MessageBox::MessageBox(Room *room, ReadStream &stream)
const char *VoiceMeter::typeName() const { return "VoiceMeter"; }
-VoiceMeter::VoiceMeter(Room *room, ReadStream &stream)
+VoiceMeter::VoiceMeter(Room *room, SeekableReadStream &stream)
: GraphicObject(room, stream) {
stream.readByte(); // unused and ignored byte
}
Commit: 6f74da160b15a687c88f29da53e8a39f46f79fee
https://github.com/scummvm/scummvm/commit/6f74da160b15a687c88f29da53e8a39f46f79fee
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Add support for V1 rooms
Changed paths:
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 1bc625caa14..db974ef6808 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -563,7 +563,7 @@ protected:
void onArrived() override;
private:
- friend class Inventory;
+ friend class InventoryV3;
friend struct DialogMenuTask;
Item *getItemByName(const Common::String &name) const;
void drawInner();
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 96c00505166..2c5e3d3304a 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -32,7 +32,9 @@ using namespace Common;
namespace Alcachofa {
-Room::Room(World *world, SeekableReadStream &stream) : Room(world, stream, false) {}
+Room::Room(World *world) : _world(world) {}
+
+RoomV3::RoomV3(World *world, SeekableReadStream &stream) : RoomV3(world, stream, false) {}
static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadStream &stream) {
if (type == ObjectBase::kClassName)
@@ -85,9 +87,10 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return nullptr; // handled in Room::Room
}
-Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
- : _world(world) {
+RoomV3::RoomV3(World *world, SeekableReadStream &stream, bool hasUselessByte)
+ : Room(world) {
_name = readVarString(stream);
+ _backgroundName = _name;
_musicId = (int)stream.readByte();
_characterAlphaTint = stream.readByte();
auto backgroundScale = stream.readSint16LE();
@@ -99,6 +102,10 @@ Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
if (hasUselessByte)
stream.readByte();
+ readObjectsAndBackground(stream, backgroundScale);
+}
+
+void Room::readObjectsAndBackground(SeekableReadStream &stream, int16 backgroundScale) {
uint32 objectEnd = stream.readUint32LE();
while (objectEnd > 0) {
const auto type = readVarString(stream);
@@ -117,12 +124,32 @@ Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
objectEnd = stream.readUint32LE();
}
if (g_engine->game().doesRoomHaveBackground(this))
- _objects.push_back(new Background(this, _name, backgroundScale));
+ _objects.push_back(new Background(this, _backgroundName, backgroundScale));
if (!_floors[0].empty())
_activeFloorI = 0;
}
+RoomV1::RoomV1(World *world, SeekableReadStream &stream, bool readObjects)
+ : Room(world) {
+ _name = readVarString(stream);
+ _backgroundName = readVarString(stream);
+ _musicId = (int)stream.readByte();
+ _characterAlphaTint = stream.readByte();
+ skipVarString(stream);
+
+ if (readObjects)
+ readObjectsAndBackground(stream, kBaseScale);
+}
+
+RoomV1WithFloor::RoomV1WithFloor(World *world, SeekableReadStream &stream)
+ : RoomV1(world, stream, false) {
+ _floors[0] = PathFindingShape(stream);
+ _floors[1] = PathFindingShape(stream);
+
+ readObjectsAndBackground(stream, kBaseScale);
+}
+
Room::~Room() {
for (auto *object : _objects)
delete object;
@@ -322,7 +349,7 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
}
OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {}
+ : RoomV3(world, stream, true) {}
bool OptionsMenu::updateInput() {
if (!Room::updateInput())
@@ -356,19 +383,19 @@ void OptionsMenu::clearLastSelectedObject() {
}
ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {}
+ : RoomV3(world, stream, true) {}
ListenMenu::ListenMenu(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {}
+ : RoomV3(world, stream, true) {}
-Inventory::Inventory(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {}
+InventoryV3::InventoryV3(World *world, SeekableReadStream &stream)
+ : RoomV3(world, stream, true) {}
-Inventory::~Inventory() {
+InventoryV3::~InventoryV3() {
// No need to delete items, they are room objects and thus deleted in Room::~Room
}
-bool Inventory::updateInput() {
+bool InventoryV3::updateInput() {
auto &player = g_engine->player();
auto &input = g_engine->input();
auto *hoveredItem = getHoveredItem();
@@ -411,7 +438,7 @@ bool Inventory::updateInput() {
return player.currentRoom() == this;
}
-Item *Inventory::getHoveredItem() {
+Item *InventoryV3::getHoveredItem() {
auto mousePos = g_engine->input().mousePos2D();
for (auto item : _items) {
if (!item->isEnabled())
@@ -431,7 +458,7 @@ Item *Inventory::getHoveredItem() {
return nullptr;
}
-void Inventory::initItems() {
+void InventoryV3::initItems() {
auto &mortadelo = world().mortadelo();
auto &filemon = world().filemon();
for (auto object : _objects) {
@@ -444,14 +471,14 @@ void Inventory::initItems() {
}
}
-void Inventory::updateItemsByActiveCharacter() {
+void InventoryV3::updateItemsByActiveCharacter() {
auto *character = g_engine->player().activeCharacter();
assert(character != nullptr);
for (auto *item : _items)
item->toggle(character->hasItem(item->name()));
}
-void Inventory::drawAsOverlay(int32 scrollY) {
+void InventoryV3::drawAsOverlay(int32 scrollY) {
for (auto object : _objects) {
auto graphic = object->graphic();
if (graphic == nullptr)
@@ -469,17 +496,23 @@ void Inventory::drawAsOverlay(int32 scrollY) {
}
}
-void Inventory::open() {
+void InventoryV3::open() {
g_engine->camera().backup(1);
g_engine->player().changeRoom(name(), true);
updateItemsByActiveCharacter();
}
-void Inventory::close() {
+void InventoryV3::close() {
g_engine->camera().restore(1);
g_engine->globalUI().startClosingInventory();
}
+InventoryV1::InventoryV1(World *world, SeekableReadStream &stream)
+ : RoomV1(world, stream, false) {
+ stream.skip(1); // denoted as "sinusar" but unused
+ readObjectsAndBackground(stream, kBaseScale);
+}
+
void Room::debugPrint(bool withObjects) const {
auto &console = g_engine->console();
console.debugPrintf(" %s\n", _name.c_str());
@@ -512,7 +545,7 @@ World::World() {
_globalRoom = getRoomByName("GLOBAL");
if (_globalRoom == nullptr)
error("Could not find GLOBAL room");
- _inventory = dynamic_cast<Inventory *>(getRoomByName("INVENTARIO"));
+ _inventory = dynamic_cast<InventoryV3 *>(getRoomByName("INVENTARIO"));
if (_inventory == nullptr)
error("Could not find INVENTARIO");
_filemon = dynamic_cast<MainCharacter *>(_globalRoom->getObjectByName("FILEMON"));
@@ -635,15 +668,29 @@ const char *World::getDialogLine(int32 dialogId) const {
static Room *readRoomV3(World *world, SeekableReadStream &stream) {
const auto type = readVarString(stream);
if (type == Room::kClassName)
- return new Room(world, stream);
+ return new RoomV3(world, stream);
else if (type == OptionsMenu::kClassName)
return new OptionsMenu(world, stream);
else if (type == ConnectMenu::kClassName)
return new ConnectMenu(world, stream);
else if (type == ListenMenu::kClassName)
return new ListenMenu(world, stream);
- else if (type == Inventory::kClassName)
- return new Inventory(world, stream);
+ else if (type == InventoryV3::kClassName)
+ return new InventoryV3(world, stream);
+ else {
+ g_engine->game().unknownRoomType(type);
+ return nullptr;
+ }
+}
+
+static Room *readRoomV1(World *world, SeekableReadStream &stream) {
+ const auto type = readVarString(stream);
+ if (type == Room::kClassName)
+ return new RoomV1(world, stream);
+ else if (type == RoomV1WithFloor::kClassName)
+ return new RoomV1WithFloor(world, stream);
+ else if (type == InventoryV1::kClassName)
+ return new InventoryV1(world, stream);
else {
g_engine->game().unknownRoomType(type);
return nullptr;
@@ -724,7 +771,7 @@ bool World::loadWorldFileV1(const char *path) {
readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon);
readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
- readRooms(readRoomV3, *file);
+ readRooms(readRoomV1, *file);
_files.emplace_back(move(file));
return true;
}
@@ -742,6 +789,7 @@ void World::readRooms(World::ReadRoomFunc readRoom, File &file) {
error("Read past the room data for world %s", file.getName());
roomEnd = file.readUint32LE();
}
+ scumm_assert(!file.err());
}
/**
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index df73b4f47fc..dab0da4641b 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -31,7 +31,6 @@ class World;
class Room {
public:
static constexpr const char *kClassName = "CHabitacion";
- Room(World *world, Common::SeekableReadStream &stream);
virtual ~Room();
inline World &world() { return *_world; }
@@ -65,7 +64,8 @@ public:
void debugPrint(bool withObjects) const;
protected:
- Room(World *world, Common::SeekableReadStream &stream, bool hasUselessByte);
+ Room(World *world);
+ void readObjectsAndBackground(Common::SeekableReadStream &stream, int16 backgroundScale);
void updateScripts();
void updateRoomBounds();
void updateInteraction();
@@ -75,19 +75,38 @@ protected:
ShapeObject *getSelectedObject(ShapeObject *best = nullptr) const;
World *_world;
- Common::String _name;
+ Common::String _name, _backgroundName;
PathFindingShape _floors[2];
bool _fixedCameraOnEntering;
int8 _activeFloorI = -1;
int _musicId = -1;
uint8
- _characterAlphaTint,
- _characterAlphaPremultiplier; ///< for some reason in percent instead of 0-255
+ _characterAlphaTint = 0,
+ _characterAlphaPremultiplier = 100; ///< for some reason in percent instead of 0-255
Common::Array<ObjectBase *> _objects;
};
-class OptionsMenu final : public Room {
+class RoomV3 : public Room {
+public:
+ RoomV3(World *world, Common::SeekableReadStream &stream);
+
+protected:
+ RoomV3(World *world, Common::SeekableReadStream &stream, bool hasUselessByte);
+};
+
+class RoomV1 : public Room {
+public:
+ RoomV1(World *world, Common::SeekableReadStream &stream, bool readObjects = true);
+};
+
+class RoomV1WithFloor final : public RoomV1 {
+public:
+ static constexpr const char *kClassName = "CHabitacionConSuelo";
+ RoomV1WithFloor(World *world, Common::SeekableReadStream &stream);
+};
+
+class OptionsMenu final : public RoomV3 {
public:
static constexpr const char *kClassName = "CHabitacionMenuOpciones";
OptionsMenu(World *world, Common::SeekableReadStream &stream);
@@ -104,23 +123,23 @@ private:
SlideButton *_currentSlideButton = nullptr;
};
-class ConnectMenu final : public Room {
+class ConnectMenu final : public RoomV3 {
public:
static constexpr const char *kClassName = "CHabitacionConectar";
ConnectMenu(World *world, Common::SeekableReadStream &stream);
};
-class ListenMenu final : public Room {
+class ListenMenu final : public RoomV3 {
public:
static constexpr const char *kClassName = "CHabitacionEsperar";
ListenMenu(World *world, Common::SeekableReadStream &stream);
};
-class Inventory final : public Room {
+class InventoryV3 final : public RoomV3 {
public:
static constexpr const char *kClassName = "CInventario";
- Inventory(World *world, Common::SeekableReadStream &stream);
- ~Inventory() override;
+ InventoryV3(World *world, Common::SeekableReadStream &stream);
+ ~InventoryV3() override;
bool updateInput() override;
@@ -136,6 +155,12 @@ private:
Common::Array<Item *> _items;
};
+class InventoryV1 final : public RoomV1 {
+public:
+ static constexpr const char *kClassName = "CInventario";
+ InventoryV1(World *world, Common::SeekableReadStream &stream);
+};
+
enum class GlobalAnimationKind {
GeneralFont = 0,
DialogFont,
@@ -161,7 +186,7 @@ public:
inline RoomIterator beginRooms() const { return _rooms.begin(); }
inline RoomIterator endRooms() const { return _rooms.end(); }
inline Room &globalRoom() const { assert(_globalRoom != nullptr); return *_globalRoom; }
- inline Inventory &inventory() const { assert(_inventory != nullptr); return *_inventory; }
+ inline InventoryV3 &inventory() const { assert(_inventory != nullptr); return *_inventory; }
inline MainCharacter &filemon() const { assert(_filemon != nullptr); return *_filemon; }
inline MainCharacter &mortadelo() const { assert(_mortadelo != nullptr); return *_mortadelo; }
inline GameFileReference scriptFileRef() const { return _scriptFileRef; }
@@ -208,7 +233,7 @@ private:
Common::String _initScriptName;
GameFileReference _scriptFileRef;
Room *_globalRoom;
- Inventory *_inventory;
+ InventoryV3 *_inventory;
MainCharacter *_filemon, *_mortadelo;
uint8 _loadedMapCount = 0;
Common::HashMap<const char *, const char *,
Commit: 81ddc5c1e0e1043aab52884031ab8cb42a5c26f5
https://github.com/scummvm/scummvm/commit/81ddc5c1e0e1043aab52884031ab8cb42a5c26f5
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Support reading V1 objects
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/script.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 17b43f1c126..016804dce9a 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -79,9 +79,9 @@ Common::Error AlcachofaEngine::run() {
g_system->showMouse(false);
setDebugger(_console);
_game.reset(Game::create());
+ _world.load();
_renderer.reset(IRenderer::createOpenGLRenderer(game().getResolution()));
_drawQueue.reset(new DrawQueue(_renderer.get()));
- _world.reset(new World());
_script.reset(new Script());
_player.reset(new Player());
_globalUI.reset(new GlobalUI());
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 05123797ebe..04386c5e1af 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -127,7 +127,7 @@ public:
inline Input &input() { return _input; }
inline Sounds &sounds() { return _sounds; }
inline Player &player() { return *_player; }
- inline World &world() { return *_world; }
+ inline World &world() { return _world; }
inline Script &script() { return *_script; }
inline GlobalUI &globalUI() { return *_globalUI; }
inline Menu &menu() { return *_menu; }
@@ -182,12 +182,12 @@ private:
Common::ScopedPtr<IDebugHandler> _debugHandler;
Common::ScopedPtr<IRenderer> _renderer;
Common::ScopedPtr<DrawQueue> _drawQueue;
- Common::ScopedPtr<World> _world;
Common::ScopedPtr<Script> _script;
Common::ScopedPtr<Player> _player;
Common::ScopedPtr<GlobalUI> _globalUI;
Common::ScopedPtr<Menu> _menu;
Common::ScopedPtr<Game> _game;
+ World _world;
Camera _camera;
Input _input;
Sounds _sounds;
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 771dd1605ea..c37ff0ca330 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -19,8 +19,7 @@
*
*/
-#include "alcachofa/common.h"
-#include "alcachofa/detection.h"
+#include "alcachofa/alcachofa.h"
using namespace Common;
using namespace Math;
@@ -200,4 +199,30 @@ void syncPoint(Serializer &serializer, Point &point) {
serializer.syncAsSint32LE(point.y);
}
+static constexpr Direction kV1Directions[] = {
+ Direction::Up,
+ Direction::UpRight,
+ Direction::Right,
+ Direction::DownRight,
+ Direction::Down,
+ Direction::DownLeft,
+ Direction::Left,
+ Direction::UpLeft
+};
+
+Direction intToDirection(int32 rawValue) {
+ if (g_engine->isV1()) {
+ scumm_assert(rawValue >= 0 && rawValue < kFullDirectionCount);
+ return kV1Directions[rawValue];
+ } else {
+ scumm_assert(rawValue >= 0 && rawValue < kDirectionCount);
+ return (Direction)rawValue;
+ }
+}
+
+Direction readDirection(Common::ReadStream &stream) {
+ int32 rawValue = stream.readSint32LE();
+ return intToDirection(rawValue);
+}
+
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index a274df1c0d9..cbef429d523 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -47,6 +47,13 @@ enum class Direction {
Down,
Left,
+ // V1 has some fields with these additional directions
+ // but they are largely irrelevant so we map them to the main four
+ UpRight,
+ DownRight,
+ DownLeft,
+ UpLeft,
+
Invalid = -1
};
@@ -64,6 +71,7 @@ enum class EasingType {
};
constexpr const int32 kDirectionCount = 4;
+constexpr const int32 kFullDirectionCount = 8; ///< only to be used for IO or mapping V1 values
constexpr const int8 kOrderCount = 70;
constexpr const int8 kForegroundOrderCount = 10;
@@ -133,6 +141,8 @@ Common::Point readPoint(Common::ReadStream &stream);
Common::String readVarString(Common::ReadStream &stream);
void skipVarString(Common::SeekableReadStream &stream);
void syncPoint(Common::Serializer &serializer, Common::Point &point);
+Direction intToDirection(int32 value);
+Direction readDirection(Common::ReadStream &stream);
template<typename T>
inline void syncArray(Common::Serializer &serializer, Common::Array<T> &array, void (*serializeFunction)(Common::Serializer &, T &)) {
@@ -185,7 +195,7 @@ struct GameFileReference {
GameFileReference() {}
- GameFileReference(const Common::String &path)
+ explicit GameFileReference(const Common::String &path)
: _path(path) {}
// in this case, path is only for debugging purposes
@@ -196,7 +206,11 @@ struct GameFileReference {
, _size(size) {}
inline bool isValid() const {
- return !_path.empty() || _position >= 0;
+ return !_path.empty() || _fileIndex != UINT32_MAX;
+ }
+
+ inline bool isEmbedded() const {
+ return _fileIndex != UINT32_MAX;
}
};
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 51fb99d3d52..02b54199a59 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -209,7 +209,7 @@ public:
}
GameFileReference getScriptFileRef() {
- return { "Script/SCRIPT.COD" };
+ return GameFileReference("Script/SCRIPT.COD");
}
Span<const ScriptOp> getScriptOpMap() override {
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 8883704b68e..cd2e9f08160 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -34,7 +34,11 @@ const char *Item::typeName() const { return "Item"; }
Item::Item(Room *room, SeekableReadStream &stream)
: GraphicObject(room, stream) {
- stream.readByte(); // unused and ignored byte
+ // unused data in any case
+ if (g_engine->isV1())
+ skipVarString(stream);
+ else
+ stream.skip(1);
}
Item::Item(const Item &other)
@@ -70,9 +74,14 @@ void Item::trigger() {
player.triggerObject(heldItem, name().c_str());
}
-ITriggerableObject::ITriggerableObject(SeekableReadStream &stream)
- : _interactionPoint(Shape(stream).firstPoint())
- , _interactionDirection((Direction)stream.readSint32LE()) {}
+ITriggerableObject::ITriggerableObject(SeekableReadStream &stream) {
+ read(stream);
+}
+
+void ITriggerableObject::read(SeekableReadStream &stream) {
+ _interactionPoint = Shape(stream).firstPoint();
+ _interactionDirection = readDirection(stream);
+}
void ITriggerableObject::onClick() {
auto heldItem = g_engine->player().heldItem();
@@ -91,6 +100,9 @@ InteractableObject::InteractableObject(Room *room, SeekableReadStream &stream)
, ITriggerableObject(stream)
, _relatedObject(readVarString(stream)) {
_relatedObject.toUppercase();
+
+ if (g_engine->isV1())
+ skipVarString(stream); // unused script field
}
void InteractableObject::drawDebug() {
@@ -124,7 +136,7 @@ Door::Door(Room *room, SeekableReadStream &stream)
: InteractableObject(room, stream)
, _targetRoom(readVarString(stream))
, _targetObject(readVarString(stream))
- , _characterDirection((Direction)stream.readSint32LE()) {
+ , _characterDirection(readDirection(stream)) {
_targetRoom.replace(' ', '_');
}
@@ -163,13 +175,28 @@ void Door::trigger(const char *_) {
const char *Character::typeName() const { return "Character"; }
Character::Character(Room *room, SeekableReadStream &stream)
- : ShapeObject(room, stream)
- , ITriggerableObject(stream)
- , _graphicNormal(stream)
- , _graphicTalking(stream) {
+ : ShapeObject(room, stream) {
+ if (g_engine->isV1()) {
+ // the structure in V1 is quite redundant
+ toggle(readBool(stream));
+ _order = stream.readByte();
+ ITriggerableObject::read(stream);
+ skipVarString(stream); // unused "relatedGraphicObject" field
+ skipVarString(stream); // unused "script" field
+ toggle(readBool(stream));
+ _graphicNormal = Graphic(stream);
+ auto talkingFileRef = g_engine->world().readFileRef(stream);
+ _graphicTalking = _graphicNormal;
+ _graphicTalking.setAnimation(talkingFileRef, AnimationFolder::Animations);
+ } else {
+ ITriggerableObject::read(stream);
+ _graphicNormal = Graphic(stream);
+ _graphicTalking = Graphic(stream);
+ _order = _graphicNormal.order();
+ }
+
_graphicNormal.start(true);
_graphicNormal.frameI() = _graphicTalking.frameI() = 0;
- _order = _graphicNormal.order();
}
static Graphic *graphicOf(ObjectBase *object, Graphic *fallback = nullptr) {
@@ -500,13 +527,18 @@ const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
WalkingCharacter::WalkingCharacter(Room *room, SeekableReadStream &stream)
: Character(room, stream) {
+ // every other animation is for an unused direction
+
+ const auto &world = g_engine->world();
for (int32 i = 0; i < kDirectionCount; i++) {
- auto fileName = readVarString(stream);
- _walkingAnimations[i].reset(new Animation(Common::move(fileName)));
+ auto fileRef = world.readFileRef(stream);
+ _walkingAnimations[i].reset(new Animation(move(fileRef)));
+ world.readFileRef(stream);
}
for (int32 i = 0; i < kDirectionCount; i++) {
- auto fileName = readVarString(stream);
- _talkingAnimations[i].reset(new Animation(Common::move(fileName)));
+ auto fileRef = world.readFileRef(stream);
+ _talkingAnimations[i].reset(new Animation(move(fileRef)));
+ world.readFileRef(stream);
}
}
@@ -1148,7 +1180,7 @@ const char *Background::typeName() const { return "Background"; }
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
toggle(true);
- _graphic.setAnimation(animationFileName, AnimationFolder::Backgrounds);
+ _graphic.setAnimation(GameFileReference(animationFileName), AnimationFolder::Backgrounds);
_graphic.scale() = scale;
_graphic.order() = 59;
}
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 91252e99794..4ba9981d7fa 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -45,7 +45,7 @@ ObjectBase::ObjectBase(Room *room, SeekableReadStream &stream)
: _room(room) {
assert(room != nullptr);
_name = readVarString(stream);
- _isEnabled = readBool(stream);
+ _isEnabled = g_engine->isV1() ? true : readBool(stream);
}
void ObjectBase::toggle(bool isEnabled) {
@@ -84,10 +84,16 @@ PointObject::PointObject(Room *room, SeekableReadStream &stream)
const char *GraphicObject::typeName() const { return "GraphicObject"; }
GraphicObject::GraphicObject(Room *room, SeekableReadStream &stream)
- : ObjectBase(room, stream)
- , _graphic(stream)
- , _type((GraphicObjectType)stream.readSint32LE())
- , _posterizeAlpha(100 - stream.readSint32LE()) {
+ : ObjectBase(room, stream) {
+ if (g_engine->isV1()) {
+ toggle(readBool(stream));
+ _graphic = Graphic(stream);
+ } else {
+ _graphic = Graphic(stream);
+ _type = (GraphicObjectType)stream.readSint32LE();
+ _posterizeAlpha = 100 - stream.readSint32LE();
+ }
+
_graphic.start(true);
}
@@ -233,8 +239,10 @@ const char *ShapeObject::typeName() const { return "ShapeObject"; }
ShapeObject::ShapeObject(Room *room, SeekableReadStream &stream)
: ObjectBase(room, stream)
- , _shape(stream)
- , _cursorType((CursorType)stream.readSint32LE()) {}
+ , _shape(stream) {
+ if (g_engine->isV2() || g_engine->isV3())
+ _cursorType = (CursorType)stream.readSint32LE();
+}
void ShapeObject::update() {
if (isEnabled())
@@ -304,6 +312,8 @@ const char *PhysicalObject::typeName() const { return "PhysicalObject"; }
PhysicalObject::PhysicalObject(Room *room, SeekableReadStream &stream)
: ShapeObject(room, stream) {
+ if (g_engine->isV1())
+ toggle(readBool(stream));
_order = stream.readSByte();
}
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 7f7ab5740cc..4164dec0fc0 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -46,11 +46,11 @@ Rect closeInventoryTriggerBounds() {
GlobalUI::GlobalUI() {
auto &world = g_engine->world();
- _generalFont.reset(new Font(world.getGlobalAnimationName(GlobalAnimationKind::GeneralFont)));
- _dialogFont.reset(new Font(world.getGlobalAnimationName(GlobalAnimationKind::DialogFont)));
- _iconMortadelo.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::MortadeloIcon)));
- _iconFilemon.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::FilemonIcon)));
- _iconInventory.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::InventoryIcon)));
+ _generalFont.reset(new Font(world.getGlobalAnimation(GlobalAnimationKind::GeneralFont)));
+ _dialogFont.reset(new Font(world.getGlobalAnimation(GlobalAnimationKind::DialogFont)));
+ _iconMortadelo.reset(new Animation(world.getGlobalAnimation(GlobalAnimationKind::MortadeloIcon)));
+ _iconFilemon.reset(new Animation(world.getGlobalAnimation(GlobalAnimationKind::FilemonIcon)));
+ _iconInventory.reset(new Animation(world.getGlobalAnimation(GlobalAnimationKind::InventoryIcon)));
_generalFont->load();
_dialogFont->load();
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 45e2d3ce8b1..42fe7a9eecf 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -59,8 +59,8 @@ void IDebugRenderer::debugShape(const Shape &shape, Color color) {
}
}
-AnimationBase::AnimationBase(String fileName, AnimationFolder folder)
- : _fileName(reencode(fileName))
+AnimationBase::AnimationBase(GameFileReference fileRef, AnimationFolder folder)
+ : _fileRef(move(fileRef))
, _folder(folder) {}
AnimationBase::~AnimationBase() {
@@ -71,6 +71,8 @@ void AnimationBase::load() {
if (_isLoaded)
return;
+ assert(!_fileRef.isEmbedded());
+
String fullPath;
switch (_folder) {
case AnimationFolder::Animations:
@@ -86,14 +88,14 @@ void AnimationBase::load() {
assert(false && "Invalid AnimationFolder");
break;
}
- if (_fileName.size() < 4 || scumm_strnicmp(_fileName.end() - 4, ".AN0", 4) != 0)
- _fileName += ".AN0";
- fullPath += _fileName;
+ if (_fileRef._path.size() < 4 || scumm_strnicmp(_fileRef._path.end() - 4, ".AN0", 4) != 0)
+ _fileRef._path += ".AN0";
+ fullPath += _fileRef._path;
File file;
if (!file.open(fullPath.c_str())) {
// original fallback
- fullPath = "Mascaras/" + _fileName;
+ fullPath = "Mascaras/" + _fileRef._path;
if (!file.open(fullPath.c_str())) {
loadMissingAnimation();
return;
@@ -170,7 +172,7 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
SeekableSubReadStream subStream(&stream, stream.pos(), stream.size());
TGADecoder decoder;
if (!decoder.loadStream(subStream))
- error("Failed to load TGA from animation %s", _fileName.c_str());
+ error("Failed to load TGA from animation %s", _fileRef._path.c_str());
// The length of the image is unknown but TGADecoder does not read
// the end marker, so let's search for it.
@@ -183,7 +185,7 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
if (potentialStart < buffer + kMarkerLength)
memmove(buffer, potentialStart, kMarkerLength - nextRead);
if (stream.read(buffer + kMarkerLength - nextRead, nextRead) != nextRead)
- error("Unexpected end-of-file in animation %s", _fileName.c_str());
+ error("Unexpected end-of-file in animation %s", _fileRef._path.c_str());
potentialStart = find(buffer + 1, buffer + kMarkerLength, kExpectedMarker[0]);
} while (strncmp(buffer, kExpectedMarker, kMarkerLength) != 0);
@@ -202,7 +204,7 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
void AnimationBase::loadMissingAnimation() {
// only allow missing animations we know are faulty in the original game
- g_engine->game().missingAnimation(_fileName);
+ g_engine->game().missingAnimation(_fileRef._path);
// otherwise setup a functioning but empty animation
_isLoaded = true;
@@ -248,8 +250,8 @@ Point AnimationBase::imageSize(int32 imageI) const {
return image == nullptr ? Point() : Point(image->w, image->h);
}
-Animation::Animation(String fileName, AnimationFolder folder)
- : AnimationBase(fileName, folder) {}
+Animation::Animation(GameFileReference fileRef, AnimationFolder folder)
+ : AnimationBase(move(fileRef), folder) {}
void Animation::load() {
if (_isLoaded)
@@ -447,7 +449,7 @@ void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d size, Vector
renderer.quad(as2D(topLeft), size, kWhite, rotation, texMin + texOffset, texMax + texOffset);
}
-Font::Font(String fileName) : AnimationBase(fileName) {}
+Font::Font(GameFileReference fileRef) : AnimationBase(move(fileRef)) {}
void Font::load() {
if (_isLoaded)
@@ -487,7 +489,7 @@ void Font::load() {
}
_texture = g_engine->renderer().createTexture(atlasSurface.w, atlasSurface.h, false);
_texture->update(atlasSurface);
- debugCN(1, kDebugGraphics, "Rendered font atlas %s at %dx%d", _fileName.c_str(), atlasSurface.w, atlasSurface.h);
+ debugCN(1, kDebugGraphics, "Rendered font atlas %s at %dx%d", _fileRef._path.c_str(), atlasSurface.w, atlasSurface.h);
}
void Font::freeImages() {
@@ -512,29 +514,27 @@ void Font::drawCharacter(int32 imageI, Point centerPoint, Color color) {
Graphic::Graphic() {}
-Graphic::Graphic(ReadStream &stream) {
- _topLeft.x = stream.readSint16LE();
- _topLeft.y = stream.readSint16LE();
- _scale = stream.readSint16LE();
- _order = stream.readSByte();
- auto animationName = readVarString(stream);
- if (!animationName.empty())
- setAnimation(animationName, AnimationFolder::Animations);
-}
-
-Graphic::Graphic(const Graphic &other)
- : _animation(other._animation)
- , _topLeft(other._topLeft)
- , _scale(other._scale)
- , _order(other._order)
- , _color(other._color)
- , _isPaused(other._isPaused)
- , _isLooping(other._isLooping)
- , _lastTime(other._lastTime)
- , _frameI(other._frameI)
- , _depthScale(other._depthScale) {}
-
-Graphic &Graphic::operator= (const Graphic &other) {
+Graphic::Graphic(SeekableReadStream &stream) {
+ if (g_engine->isV1()) {
+ _topLeft = readPoint(stream);
+ _scale = stream.readSint16LE();
+ _order = (int8)stream.readSint16LE();
+ } else {
+ _topLeft.x = stream.readSint16LE();
+ _topLeft.y = stream.readSint16LE();
+ _scale = stream.readSint16LE();
+ _order = stream.readSByte();
+ }
+ auto animationRef = g_engine->world().readFileRef(stream);
+ if (animationRef.isValid())
+ setAnimation(animationRef, AnimationFolder::Animations);
+}
+
+Graphic::Graphic(const Graphic &other) {
+ *this = other;
+}
+
+Graphic &Graphic::operator=(const Graphic &other) {
_ownedAnimation.reset();
_animation = other._animation;
_topLeft = other._topLeft;
@@ -602,8 +602,8 @@ void Graphic::reset() {
_lastTime = _isPaused ? 0 : g_engine->getMillis();
}
-void Graphic::setAnimation(const Common::String &fileName, AnimationFolder folder) {
- _ownedAnimation.reset(new Animation(fileName, folder));
+void Graphic::setAnimation(const GameFileReference &fileRef, AnimationFolder folder) {
+ _ownedAnimation.reset(new Animation(fileRef, folder));
_animation = _ownedAnimation.get();
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 7a751491da4..1cb5da94f8f 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -148,7 +148,7 @@ struct AnimationFrame {
*/
class AnimationBase {
protected:
- AnimationBase(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations);
+ AnimationBase(GameFileReference fileRef, AnimationFolder folder = AnimationFolder::Animations);
~AnimationBase();
void load();
@@ -165,7 +165,7 @@ protected:
int offsetY);
static constexpr const uint kMaxSpriteIDs = 256;
- Common::String _fileName;
+ GameFileReference _fileRef;
AnimationFolder _folder;
bool _isLoaded = false;
uint32 _totalDuration = 0;
@@ -185,7 +185,7 @@ protected:
*/
class Animation : private AnimationBase {
public:
- Animation(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations);
+ Animation(GameFileReference fileRef, AnimationFolder folder = AnimationFolder::Animations);
void load();
void freeImages();
@@ -241,7 +241,7 @@ private:
class Font : private AnimationBase {
public:
- Font(Common::String fileName);
+ Font(GameFileReference fileRef);
void load();
void freeImages();
@@ -259,9 +259,11 @@ private:
class Graphic {
public:
Graphic();
- Graphic(Common::ReadStream &stream);
+ Graphic(Common::SeekableReadStream &stream);
Graphic(const Graphic &other); // animation reference is taken, so keep other alive
- Graphic &operator= (const Graphic &other);
+ Graphic(Graphic &&other) = default;
+ Graphic &operator=(const Graphic &other);
+ Graphic &operator=(Graphic &&other) = default;
inline Common::Point &topLeft() { return _topLeft; }
inline int8 &order() { return _order; }
@@ -287,7 +289,7 @@ public:
void start(bool looping);
void pause();
void reset();
- void setAnimation(const Common::String &fileName, AnimationFolder folder);
+ void setAnimation(const GameFileReference &fileRef, AnimationFolder folder);
void setAnimation(Animation *animation); ///< no memory ownership is given, but for prerendering it has to be mutable
void syncGame(Common::Serializer &serializer);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index db974ef6808..0e870ea333c 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -102,8 +102,8 @@ protected:
GraphicObject(Room *room, const char *name);
Graphic _graphic;
- GraphicObjectType _type;
- int32 _posterizeAlpha;
+ GraphicObjectType _type = GraphicObjectType::Normal;
+ int32 _posterizeAlpha = 100;
};
class SpecialEffectObject final : public GraphicObject {
@@ -147,17 +147,33 @@ protected:
int8 _order = 0;
private:
Shape _shape;
- CursorType _cursorType;
+ CursorType _cursorType = CursorType::Point;
bool _isNewlySelected = false,
_wasSelected = false;
};
class PhysicalObject : public ShapeObject {
public:
+ static constexpr const char *kClassName = "CObjetoFisico";
PhysicalObject(Room *room, Common::SeekableReadStream &stream);
const char *typeName() const override;
};
+class ButtonV1 : public PhysicalObject {
+public:
+ static constexpr const char *kClassName = "CBoton";
+ ButtonV1(Room *room, Common::SeekableReadStream &stream);
+
+ inline byte actionId() const { return _actionId; }
+ inline const Common::String &actionName() const { return _actionName; }
+
+ const char *typeName() const override;
+
+private:
+ byte _actionId;
+ Common::String _actionName;
+};
+
class MenuButton : public PhysicalObject {
public:
static constexpr const char *kClassName = "CBotonMenu";
@@ -374,6 +390,8 @@ public:
virtual void trigger(const char *action) = 0;
protected:
+ ITriggerableObject() = default;
+ void read(Common::SeekableReadStream &stream);
void onClick();
Common::Point _interactionPoint;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 2daf48b5fa6..fe0e4651abe 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -31,8 +31,8 @@ namespace Alcachofa {
Player::Player()
: _activeCharacter(&g_engine->world().mortadelo())
, _semaphore("player") {
- const auto &cursorPath = g_engine->world().getGlobalAnimationName(GlobalAnimationKind::Cursor);
- _cursorAnimation.reset(new Animation(cursorPath));
+ const auto &cursorRef = g_engine->world().getGlobalAnimation(GlobalAnimationKind::Cursor);
+ _cursorAnimation.reset(new Animation(cursorRef));
_cursorAnimation->load();
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 2c5e3d3304a..e3063ad9292 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -83,6 +83,8 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return new MainCharacter(room, stream);
else if (type == FloorColor::kClassName)
return new FloorColor(room, stream);
+ else if (type == ButtonV1::kClassName)
+ return new ButtonV1(room, stream);
else
return nullptr; // handled in Room::Room
}
@@ -117,7 +119,7 @@ void Room::readObjectsAndBackground(SeekableReadStream &stream, int16 background
g_engine->game().notEnoughObjectDataRead(_name.c_str(), stream.pos(), objectEnd);
stream.seek(objectEnd, SEEK_SET);
} else if (stream.pos() > objectEnd) // this is probably not recoverable
- error("Read past the object data (%u > %lld) in room %s", objectEnd, (long long int)stream.pos(), _name.c_str());
+ error("Read past the object data (%u < %lld) in room %s", objectEnd, (long long int)stream.pos(), _name.c_str());
if (object != nullptr)
_objects.push_back(object);
@@ -527,7 +529,7 @@ void Room::debugPrint(bool withObjects) const {
}
}
-World::World() {
+void World::load() {
_scriptFileRef = g_engine->game().getScriptFileRef();
auto loadWorldFile =
@@ -645,11 +647,10 @@ void World::toggleObject(MainCharacterKind character, const char *objName, bool
object->toggle(isEnabled);
}
-const Common::String &World::getGlobalAnimationName(GlobalAnimationKind kind) const {
+const GameFileReference &World::getGlobalAnimation(GlobalAnimationKind kind) const {
int kindI = (int)kind;
assert(kindI >= 0 && kindI < (int)GlobalAnimationKind::Count);
- error("You broke this, remember?");
- //return _globalAnimationNames[kindI];
+ return _globalAnimations[kindI];
}
const char *World::getLocalizedName(const String &name) const {
@@ -904,10 +905,11 @@ GameFileReference World::readFileRef(SeekableReadStream &stream) const {
auto name = readVarString(stream);
if (g_engine->isV1()) {
uint32 size = stream.readUint32LE();
+ uint32 offset = (uint32)stream.pos();
stream.skip(size);
- return { name, (uint32)_files.size(), (uint32)stream.pos(), size };
+ return { name, (uint32)_files.size(), offset, size };
} else
- return { name };
+ return GameFileReference(reencode(name));
}
ScopedPtr<SeekableReadStream> World::openFileRef(const GameFileReference &ref) const {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index dab0da4641b..f53042d256c 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -177,8 +177,8 @@ enum class GlobalAnimationKind {
class World final {
public:
- World();
~World();
+ void load(); ///< unfortunately has to be split from ctor, so g_engine->world() is already set during load
// reference-returning queries will error if the object does not exist
@@ -204,7 +204,7 @@ public:
ObjectBase *getObjectByName(const char *name) const;
ObjectBase *getObjectByName(MainCharacterKind character, const char *name) const;
ObjectBase *getObjectByNameFromAnyRoom(const char *name) const;
- const Common::String &getGlobalAnimationName(GlobalAnimationKind kind) const;
+ const GameFileReference &getGlobalAnimation(GlobalAnimationKind kind) const;
const char *getLocalizedName(const Common::String &name) const;
const char *getDialogLine(int32 dialogId) const;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index c7665435cef..f6b277c84ed 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -677,11 +677,11 @@ private:
if (character == nullptr)
g_engine->game().unknownScriptCharacter("stop-and-turn", getStringArg(0));
else
- character->stopWalking((Direction)getNumberArg(1));
+ character->stopWalking(intToDirection(getNumberArg(1)));
return TaskReturn::finish(1);
}
case ScriptKernelTask::StopAndTurnMe: {
- relatedCharacter().stopWalking((Direction)getNumberArg(0));
+ relatedCharacter().stopWalking(intToDirection(getNumberArg(0)));
return TaskReturn::finish(1);
}
case ScriptKernelTask::Go: {
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index c3745935556..79b35bba11c 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -30,6 +30,13 @@ using namespace Common;
namespace Alcachofa {
+const char *ButtonV1::typeName() const { return "ButtonV1"; }
+
+ButtonV1::ButtonV1(Room *room, Common::SeekableReadStream &stream)
+ : PhysicalObject(room, stream)
+ , _actionId(stream.readByte())
+ , _actionName(readVarString(stream)) {}
+
const char *MenuButton::typeName() const { return "MenuButton"; }
MenuButton::MenuButton(Room *room, SeekableReadStream &stream)
Commit: 8e3a73a757d1de3499edd812fa04f91d3f6329be
https://github.com/scummvm/scummvm/commit/8e3a73a757d1de3499edd812fa04f91d3f6329be
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Support V1 text files
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index b51490f38e7..eef4837838e 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -40,7 +40,7 @@ public:
}
}
- Point getResolution() {
+ Point getResolution() override {
return Point(800, 600);
}
@@ -50,14 +50,26 @@ public:
"global.emc",
nullptr
};
- const char *const *getMapFiles() {
+ const char *const *getMapFiles() override {
return kMapFiles;
}
- GameFileReference getScriptFileRef() {
+ GameFileReference getScriptFileRef() override {
// V1 embeds the script into global.emc, it is overridden during world load
return {};
}
+
+ const char *getDialogFileName() override {
+ return "TEXTOS.TXT";
+ }
+
+ const char *getObjectFileName() override {
+ return "OBJETOS.TXT";
+ }
+
+ char getTextFileKey() override {
+ return kNoXORKey;
+ }
};
Game *Game::createForMovieAdventureOriginal() {
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 02b54199a59..66a5f6f8e5b 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -208,7 +208,7 @@ public:
return kMapFiles;
}
- GameFileReference getScriptFileRef() {
+ GameFileReference getScriptFileRef() override {
return GameFileReference("Script/SCRIPT.COD");
}
@@ -216,6 +216,18 @@ public:
return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
}
+ const char *getDialogFileName() override {
+ return "Textos/DIALOGOS.nkr";
+ }
+
+ const char *getObjectFileName() override {
+ return "Textos/OBJETOS.nkr";
+ }
+
+ char getTextFileKey() override {
+ return kEmbeddedXORKey;
+ }
+
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)
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index cd2e9f08160..8cfc74ce801 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -533,12 +533,14 @@ WalkingCharacter::WalkingCharacter(Room *room, SeekableReadStream &stream)
for (int32 i = 0; i < kDirectionCount; i++) {
auto fileRef = world.readFileRef(stream);
_walkingAnimations[i].reset(new Animation(move(fileRef)));
- world.readFileRef(stream);
+ if (g_engine->isV1())
+ world.readFileRef(stream);
}
for (int32 i = 0; i < kDirectionCount; i++) {
auto fileRef = world.readFileRef(stream);
_talkingAnimations[i].reset(new Animation(move(fileRef)));
- world.readFileRef(stream);
+ if (g_engine->isV1())
+ world.readFileRef(stream);
}
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index fa64a1dea70..d559c3b1336 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -58,6 +58,9 @@ public:
virtual void updateScriptVariables() = 0;
virtual bool shouldClipCamera() = 0;
virtual void drawScreenStates();
+ virtual const char *getDialogFileName() = 0;
+ virtual const char *getObjectFileName() = 0;
+ virtual char getTextFileKey() = 0;
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index e3063ad9292..003fcef4203 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -794,26 +794,39 @@ void World::readRooms(World::ReadRoomFunc readRoom, File &file) {
}
/**
- * @brief Behold the incredible encryption of text files:
+ * @brief Behold the incredible encryption of text files (for V3):
* - first 32 bytes are cipher
* - next byte is the XOR key
* - next 4 bytes are garbage
* - rest of the file is cipher
+ *
+ * V1 is unencrypted
+ * V2 uses a constant key
*/
-static void loadEncryptedFile(const char *path, Array<char> &output) {
+static void loadEncryptedFile(const char *path, char key, Array<char> &output) {
constexpr uint kHeaderSize = 32;
File file;
if (!file.open(path))
error("Could not open text file %s", path);
- output.resize(file.size() - 4 - 1 + 1); // garbage bytes, key and we add a zero terminator for safety
- if (file.read(output.data(), kHeaderSize) != kHeaderSize)
- error("Could not read text file header");
- char key = file.readSByte();
- uint remainingSize = output.size() - kHeaderSize - 1;
- if (!file.skip(4) || file.read(output.data() + kHeaderSize, remainingSize) != remainingSize)
- error("Could not read text file body");
- for (auto &ch : output)
- ch ^= key;
+
+ if (key == kEmbeddedXORKey) {
+ output.resize(file.size() - 4 - 1 + 1); // garbage bytes, key and we add a zero terminator for safety
+ if (file.read(output.data(), kHeaderSize) != kHeaderSize)
+ error("Could not read text file header");
+ key = file.readSByte();
+ uint remainingSize = output.size() - kHeaderSize - 1;
+ if (!file.skip(4) || file.read(output.data() + kHeaderSize, remainingSize) != remainingSize)
+ error("Could not read text file body");
+ } else {
+ output.resize(file.size() + 1);
+ if (file.read(output.data(), output.size() - 1) != output.size() - 1)
+ error("Could not read text file");
+ }
+
+ if (key != kNoXORKey) {
+ for (auto &ch : output)
+ ch ^= key;
+ }
output.back() = '\0'; // one for good measure and a zero-terminator
}
@@ -836,24 +849,50 @@ static char *trimTrailing(char *start, char *end) {
}
void World::loadLocalizedNames() {
- loadEncryptedFile("Textos/OBJETOS.nkr", _namesChunk);
+ loadEncryptedFile(
+ g_engine->game().getObjectFileName(),
+ g_engine->game().getTextFileKey(),
+ _namesChunk);
char *lineStart = _namesChunk.begin(), *fileEnd = _namesChunk.end() - 1;
- while (lineStart < fileEnd) {
- char *lineEnd = find(lineStart, fileEnd, '\n');
- char *keyEnd = find(lineStart, lineEnd, '#');
- if (keyEnd == lineStart || keyEnd == lineEnd || keyEnd + 1 == lineEnd)
- error("Invalid localized name line separator");
- char *valueEnd = trimTrailing(keyEnd + 1, lineEnd);
-
- *keyEnd = 0;
- *valueEnd = 0;
- if (valueEnd == keyEnd + 1) {
- // happens in the english version of Movie Adventure
- warning("Empty localized name for %s", lineStart);
+
+ if (*lineStart == '\"') {
+ // "key" "value"
+ while (lineStart < fileEnd) {
+ char *lineEnd = find(lineStart, fileEnd, '\n');
+ char *keyStart = find(lineStart, lineEnd, '\"');
+ char *keyEnd = find(MIN(keyStart + 1, lineEnd), lineEnd, '\"');
+ char *valueStart = find(MIN(keyEnd + 1, lineEnd), lineEnd, '\"');
+ char *valueEnd = find(MIN(valueStart + 1, lineEnd), lineEnd, '\"');
+ if (keyStart == lineEnd || keyEnd == lineEnd || keyStart + 1 == keyEnd ||
+ valueStart == lineEnd || valueEnd == lineEnd)
+ error("Invalid localized name line");
+
+ keyStart++;
+ *keyEnd = 0;
+ valueStart++;
+ *valueEnd = 0;
+ _localizedNames[keyStart] = valueStart;
+ lineStart = lineEnd + 1;
}
+ } else {
+ // key#value
+ while (lineStart < fileEnd) {
+ char *lineEnd = find(lineStart, fileEnd, '\n');
+ char *keyEnd = find(lineStart, lineEnd, '#');
+ if (keyEnd == lineStart || keyEnd == lineEnd || keyEnd + 1 == lineEnd)
+ error("Invalid localized name line separator");
+ char *valueEnd = trimTrailing(keyEnd + 1, lineEnd);
+
+ *keyEnd = 0;
+ *valueEnd = 0;
+ if (valueEnd == keyEnd + 1) {
+ // happens in the english version of Movie Adventure
+ warning("Empty localized name for %s", lineStart);
+ }
- _localizedNames[lineStart] = keyEnd + 1;
- lineStart = lineEnd + 1;
+ _localizedNames[lineStart] = keyEnd + 1;
+ lineStart = lineEnd + 1;
+ }
}
}
@@ -866,7 +905,10 @@ void World::loadDialogLines() {
* - The ID does not have to be correct, it is ignored by the original engine.
* - We only need the dialog line and insert null-terminators where appropriate.
*/
- loadEncryptedFile("Textos/DIALOGOS.nkr", _dialogChunk);
+ loadEncryptedFile(
+ g_engine->game().getDialogFileName(),
+ g_engine->game().getTextFileKey(),
+ _dialogChunk);
char *lineStart = _dialogChunk.begin(), *fileEnd = _dialogChunk.end() - 1;
while (lineStart < fileEnd) {
char *lineEnd = find(lineStart, fileEnd, '\n');
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index f53042d256c..8ad415d0e33 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -77,7 +77,7 @@ protected:
World *_world;
Common::String _name, _backgroundName;
PathFindingShape _floors[2];
- bool _fixedCameraOnEntering;
+ bool _fixedCameraOnEntering = false;
int8 _activeFloorI = -1;
int _musicId = -1;
uint8
@@ -175,6 +175,9 @@ enum class GlobalAnimationKind {
Count
};
+constexpr char kNoXORKey = 0;
+constexpr char kEmbeddedXORKey = -128;
+
class World final {
public:
~World();
Commit: a16216b86af4be99cc18274075ecbe514eec5be0
https://github.com/scummvm/scummvm/commit/a16216b86af4be99cc18274075ecbe514eec5be0
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:25+01:00
Commit Message:
ALCACHOFA: Simplify room reading with V1 vs V3
Changed paths:
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 0e870ea333c..ae5125b352d 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -581,7 +581,7 @@ protected:
void onArrived() override;
private:
- friend class InventoryV3;
+ friend class Inventory;
friend struct DialogMenuTask;
Item *getItemByName(const Common::String &name) const;
void drawInner();
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 003fcef4203..2e61097e8d5 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -34,7 +34,12 @@ namespace Alcachofa {
Room::Room(World *world) : _world(world) {}
-RoomV3::RoomV3(World *world, SeekableReadStream &stream) : RoomV3(world, stream, false) {}
+Room::Room(World *world, SeekableReadStream &stream) : _world(world) {
+ if (g_engine->isV1())
+ readRoomV1(stream, true);
+ else
+ readRoomV3(stream, false);
+}
static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadStream &stream) {
if (type == ObjectBase::kClassName)
@@ -89,8 +94,18 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return nullptr; // handled in Room::Room
}
-RoomV3::RoomV3(World *world, SeekableReadStream &stream, bool hasUselessByte)
- : Room(world) {
+void Room::readRoomV1(SeekableReadStream &stream, bool readObjects) {
+ _name = readVarString(stream);
+ _backgroundName = readVarString(stream);
+ _musicId = (int)stream.readByte();
+ _characterAlphaTint = stream.readByte();
+ skipVarString(stream);
+
+ if (readObjects)
+ readObjectsAndBackground(stream, kBaseScale);
+}
+
+void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
_name = readVarString(stream);
_backgroundName = _name;
_musicId = (int)stream.readByte();
@@ -132,23 +147,10 @@ void Room::readObjectsAndBackground(SeekableReadStream &stream, int16 background
_activeFloorI = 0;
}
-RoomV1::RoomV1(World *world, SeekableReadStream &stream, bool readObjects)
- : Room(world) {
- _name = readVarString(stream);
- _backgroundName = readVarString(stream);
- _musicId = (int)stream.readByte();
- _characterAlphaTint = stream.readByte();
- skipVarString(stream);
-
- if (readObjects)
- readObjectsAndBackground(stream, kBaseScale);
-}
-
-RoomV1WithFloor::RoomV1WithFloor(World *world, SeekableReadStream &stream)
- : RoomV1(world, stream, false) {
+RoomWithFloor::RoomWithFloor(World *world, SeekableReadStream &stream) : Room(world) {
+ readRoomV1(stream, false);
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
-
readObjectsAndBackground(stream, kBaseScale);
}
@@ -350,8 +352,9 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
return best;
}
-OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream)
- : RoomV3(world, stream, true) {}
+OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream) : Room(world) {
+ readRoomV3(stream, true);
+}
bool OptionsMenu::updateInput() {
if (!Room::updateInput())
@@ -384,20 +387,28 @@ void OptionsMenu::clearLastSelectedObject() {
_lastSelectedObject = nullptr;
}
-ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream)
- : RoomV3(world, stream, true) {}
+ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream) : Room(world) {
+ readRoomV3(stream, true);
+}
-ListenMenu::ListenMenu(World *world, SeekableReadStream &stream)
- : RoomV3(world, stream, true) {}
+ListenMenu::ListenMenu(World *world, SeekableReadStream &stream) : Room(world) {
+ readRoomV3(stream, true);
+}
-InventoryV3::InventoryV3(World *world, SeekableReadStream &stream)
- : RoomV3(world, stream, true) {}
+Inventory::Inventory(World *world, SeekableReadStream &stream) : Room(world) {
+ if (g_engine->isV1()) {
+ readRoomV1(stream, false);
+ stream.skip(1); // denoted as "sinusar" but unused
+ readObjectsAndBackground(stream, kBaseScale);
+ } else
+ readRoomV3(stream, true);
+}
-InventoryV3::~InventoryV3() {
+Inventory::~Inventory() {
// No need to delete items, they are room objects and thus deleted in Room::~Room
}
-bool InventoryV3::updateInput() {
+bool Inventory::updateInput() {
auto &player = g_engine->player();
auto &input = g_engine->input();
auto *hoveredItem = getHoveredItem();
@@ -440,7 +451,7 @@ bool InventoryV3::updateInput() {
return player.currentRoom() == this;
}
-Item *InventoryV3::getHoveredItem() {
+Item *Inventory::getHoveredItem() {
auto mousePos = g_engine->input().mousePos2D();
for (auto item : _items) {
if (!item->isEnabled())
@@ -460,7 +471,7 @@ Item *InventoryV3::getHoveredItem() {
return nullptr;
}
-void InventoryV3::initItems() {
+void Inventory::initItems() {
auto &mortadelo = world().mortadelo();
auto &filemon = world().filemon();
for (auto object : _objects) {
@@ -473,14 +484,14 @@ void InventoryV3::initItems() {
}
}
-void InventoryV3::updateItemsByActiveCharacter() {
+void Inventory::updateItemsByActiveCharacter() {
auto *character = g_engine->player().activeCharacter();
assert(character != nullptr);
for (auto *item : _items)
item->toggle(character->hasItem(item->name()));
}
-void InventoryV3::drawAsOverlay(int32 scrollY) {
+void Inventory::drawAsOverlay(int32 scrollY) {
for (auto object : _objects) {
auto graphic = object->graphic();
if (graphic == nullptr)
@@ -498,7 +509,7 @@ void InventoryV3::drawAsOverlay(int32 scrollY) {
}
}
-void InventoryV3::open() {
+void Inventory::open() {
g_engine->camera().backup(1);
g_engine->player().changeRoom(name(), true);
updateItemsByActiveCharacter();
@@ -509,12 +520,6 @@ void InventoryV3::close() {
g_engine->globalUI().startClosingInventory();
}
-InventoryV1::InventoryV1(World *world, SeekableReadStream &stream)
- : RoomV1(world, stream, false) {
- stream.skip(1); // denoted as "sinusar" but unused
- readObjectsAndBackground(stream, kBaseScale);
-}
-
void Room::debugPrint(bool withObjects) const {
auto &console = g_engine->console();
console.debugPrintf(" %s\n", _name.c_str());
@@ -547,7 +552,7 @@ void World::load() {
_globalRoom = getRoomByName("GLOBAL");
if (_globalRoom == nullptr)
error("Could not find GLOBAL room");
- _inventory = dynamic_cast<InventoryV3 *>(getRoomByName("INVENTARIO"));
+ _inventory = dynamic_cast<Inventory *>(getRoomByName("INVENTARIO"));
if (_inventory == nullptr)
error("Could not find INVENTARIO");
_filemon = dynamic_cast<MainCharacter *>(_globalRoom->getObjectByName("FILEMON"));
@@ -666,32 +671,20 @@ const char *World::getDialogLine(int32 dialogId) const {
return _dialogLines[dialogId];
}
-static Room *readRoomV3(World *world, SeekableReadStream &stream) {
+static Room *readRoom(World *world, SeekableReadStream &stream) {
const auto type = readVarString(stream);
if (type == Room::kClassName)
- return new RoomV3(world, stream);
+ return new Room(world, stream);
+ else if (type == RoomWithFloor::kClassName)
+ return new RoomWithFloor(world, stream);
else if (type == OptionsMenu::kClassName)
return new OptionsMenu(world, stream);
else if (type == ConnectMenu::kClassName)
return new ConnectMenu(world, stream);
else if (type == ListenMenu::kClassName)
return new ListenMenu(world, stream);
- else if (type == InventoryV3::kClassName)
- return new InventoryV3(world, stream);
- else {
- g_engine->game().unknownRoomType(type);
- return nullptr;
- }
-}
-
-static Room *readRoomV1(World *world, SeekableReadStream &stream) {
- const auto type = readVarString(stream);
- if (type == Room::kClassName)
- return new RoomV1(world, stream);
- else if (type == RoomV1WithFloor::kClassName)
- return new RoomV1WithFloor(world, stream);
- else if (type == InventoryV1::kClassName)
- return new InventoryV1(world, stream);
+ else if (type == Inventory::kClassName)
+ return new Inventory(world, stream);
else {
g_engine->game().unknownRoomType(type);
return nullptr;
@@ -727,7 +720,7 @@ bool World::loadWorldFileV3(const char *path) {
for (int i = 0; i < (int)GlobalAnimationKind::Count; i++)
_globalAnimations[i] = readFileRef(file);
- readRooms(readRoomV3, file);
+ readRooms(file);
return true;
}
@@ -772,12 +765,12 @@ bool World::loadWorldFileV1(const char *path) {
readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon);
readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
- readRooms(readRoomV1, *file);
+ readRooms(*file);
_files.emplace_back(move(file));
return true;
}
-void World::readRooms(World::ReadRoomFunc readRoom, File &file) {
+void World::readRooms(File &file) {
uint32 roomEnd = file.readUint32LE();
while (roomEnd > 0) {
Room *room = readRoom(this, file);
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 8ad415d0e33..442f21c75f3 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -31,6 +31,7 @@ class World;
class Room {
public:
static constexpr const char *kClassName = "CHabitacion";
+ Room(World *world, Common::SeekableReadStream &stream);
virtual ~Room();
inline World &world() { return *_world; }
@@ -65,6 +66,8 @@ public:
protected:
Room(World *world);
+ void readRoomV1(Common::SeekableReadStream &stream, bool readObjects);
+ void readRoomV3(Common::SeekableReadStream &stream, bool hasUselessByte);
void readObjectsAndBackground(Common::SeekableReadStream &stream, int16 backgroundScale);
void updateScripts();
void updateRoomBounds();
@@ -87,26 +90,14 @@ protected:
Common::Array<ObjectBase *> _objects;
};
-class RoomV3 : public Room {
-public:
- RoomV3(World *world, Common::SeekableReadStream &stream);
-
-protected:
- RoomV3(World *world, Common::SeekableReadStream &stream, bool hasUselessByte);
-};
-
-class RoomV1 : public Room {
-public:
- RoomV1(World *world, Common::SeekableReadStream &stream, bool readObjects = true);
-};
-
-class RoomV1WithFloor final : public RoomV1 {
+// only used for V1 where Rooms by default have no floor
+class RoomWithFloor final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionConSuelo";
- RoomV1WithFloor(World *world, Common::SeekableReadStream &stream);
+ RoomWithFloor(World *world, Common::SeekableReadStream &stream);
};
-class OptionsMenu final : public RoomV3 {
+class OptionsMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionMenuOpciones";
OptionsMenu(World *world, Common::SeekableReadStream &stream);
@@ -123,23 +114,23 @@ private:
SlideButton *_currentSlideButton = nullptr;
};
-class ConnectMenu final : public RoomV3 {
+class ConnectMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionConectar";
ConnectMenu(World *world, Common::SeekableReadStream &stream);
};
-class ListenMenu final : public RoomV3 {
+class ListenMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionEsperar";
ListenMenu(World *world, Common::SeekableReadStream &stream);
};
-class InventoryV3 final : public RoomV3 {
+class Inventory final : public Room {
public:
static constexpr const char *kClassName = "CInventario";
- InventoryV3(World *world, Common::SeekableReadStream &stream);
- ~InventoryV3() override;
+ Inventory(World *world, Common::SeekableReadStream &stream);
+ ~Inventory() override;
bool updateInput() override;
@@ -155,12 +146,6 @@ private:
Common::Array<Item *> _items;
};
-class InventoryV1 final : public RoomV1 {
-public:
- static constexpr const char *kClassName = "CInventario";
- InventoryV1(World *world, Common::SeekableReadStream &stream);
-};
-
enum class GlobalAnimationKind {
GeneralFont = 0,
DialogFont,
@@ -189,7 +174,7 @@ public:
inline RoomIterator beginRooms() const { return _rooms.begin(); }
inline RoomIterator endRooms() const { return _rooms.end(); }
inline Room &globalRoom() const { assert(_globalRoom != nullptr); return *_globalRoom; }
- inline InventoryV3 &inventory() const { assert(_inventory != nullptr); return *_inventory; }
+ inline Inventory &inventory() const { assert(_inventory != nullptr); return *_inventory; }
inline MainCharacter &filemon() const { assert(_filemon != nullptr); return *_filemon; }
inline MainCharacter &mortadelo() const { assert(_mortadelo != nullptr); return *_mortadelo; }
inline GameFileReference scriptFileRef() const { return _scriptFileRef; }
@@ -218,10 +203,9 @@ public:
Common::ScopedPtr<Common::SeekableReadStream> openFileRef(const GameFileReference &ref) const;
private:
- using ReadRoomFunc = Room *(*)(World *, Common::SeekableReadStream &);
bool loadWorldFileV3(const char *path);
bool loadWorldFileV1(const char *path);
- void readRooms(ReadRoomFunc readRoom, Common::File &file);
+ void readRooms(Common::File &file);
void loadLocalizedNames();
void loadDialogLines();
@@ -236,7 +220,7 @@ private:
Common::String _initScriptName;
GameFileReference _scriptFileRef;
Room *_globalRoom;
- InventoryV3 *_inventory;
+ Inventory *_inventory;
MainCharacter *_filemon, *_mortadelo;
uint8 _loadedMapCount = 0;
Common::HashMap<const char *, const char *,
Commit: 7000df5f76dc469843a153a131602ae8dff27414
https://github.com/scummvm/scummvm/commit/7000df5f76dc469843a153a131602ae8dff27414
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Support V1 script files
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index f6b277c84ed..5d777730431 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -44,57 +44,62 @@ ScriptInstruction::ScriptInstruction(ReadStream &stream)
, _arg(stream.readSint32LE()) {}
Script::Script() {
- File file;
- if (!file.open("script/SCRIPT.COD"))
+ auto file = g_engine->world().openFileRef(g_engine->world().scriptFileRef());
+ if (!file)
error("Could not open script");
- uint32 stringBlobSize = file.readUint32LE();
- uint32 memorySize = file.readUint32LE();
+ uint32 stringBlobSize = file->readUint32LE();
+ uint32 memorySize = g_engine->isV1() ? 0 : file->readUint32LE();
_strings = SpanOwner<Span<char>>({ new char[stringBlobSize], stringBlobSize });
- if (file.read(&_strings[0], stringBlobSize) != stringBlobSize)
+ if (file->read(&_strings[0], stringBlobSize) != stringBlobSize)
error("Could not read script string blob");
if (_strings[stringBlobSize - 1] != 0)
error("String blob does not end with null terminator");
- if (memorySize % sizeof(int32) != 0)
- error("Unexpected size of script memory");
- _variables.resize(memorySize / sizeof(int32), 0);
-
- uint32 variableCount = file.readUint32LE();
+ uint32 variableCount = file->readUint32LE();
for (uint32 i = 0; i < variableCount; i++) {
- String name = readVarString(file);
- uint32 offset = file.readUint32LE();
+ String name = readVarString(*file);
+ uint32 offset = file->readUint32LE();
if (offset % sizeof(int32) != 0)
error("Unaligned variable offset");
_variableNames[name] = offset / 4;
}
- uint32 procedureCount = file.readUint32LE();
+ if (memorySize == 0) {
+ // in V1 the memory size is implicitly stored in the variables
+ for (const auto &variable : _variableNames)
+ memorySize = MAX(memorySize, variable._value + (uint32)sizeof(int32));
+ }
+ if (memorySize % sizeof(int32) != 0)
+ error("Unexpected size of script memory");
+ _variables.resize(memorySize / sizeof(int32), 0);
+
+ uint32 procedureCount = file->readUint32LE();
for (uint32 i = 0; i < procedureCount; i++) {
- String name = readVarString(file);
- uint32 offset = file.readUint32LE();
- file.skip(sizeof(uint32));
+ String name = readVarString(*file);
+ uint32 offset = file->readUint32LE();
+ file->skip(sizeof(uint32));
_procedures[name] = offset - 1; // originally one-based, but let's not.
}
- uint32 behaviorCount = file.readUint32LE();
+ uint32 behaviorCount = file->readUint32LE();
for (uint32 i = 0; i < behaviorCount; i++) {
- String behaviorName = readVarString(file) + '/';
- variableCount = file.readUint32LE(); // not used by the original game
+ String behaviorName = readVarString(*file) + '/';
+ variableCount = file->readUint32LE(); // not used by the original game
assert(variableCount == 0);
- procedureCount = file.readUint32LE();
+ procedureCount = file->readUint32LE();
for (uint32 j = 0; j < procedureCount; j++) {
- String name = behaviorName + readVarString(file);
- uint32 offset = file.readUint32LE();
- file.skip(sizeof(uint32));
+ String name = behaviorName + readVarString(*file);
+ uint32 offset = file->readUint32LE();
+ file->skip(sizeof(uint32));
_procedures[name] = offset - 1;
}
}
- uint32 instructionCount = file.readUint32LE();
+ uint32 instructionCount = file->readUint32LE();
_instructions.reserve(instructionCount);
for (uint32 i = 0; i < instructionCount; i++)
- _instructions.push_back(ScriptInstruction(file));
+ _instructions.push_back(ScriptInstruction(*file));
}
static void syncAsSint32LE(Serializer &s, int32 &value) {
Commit: ed18828503109501514135c65b93a61379b203ed
https://github.com/scummvm/scummvm/commit/ed18828503109501514135c65b93a61379b203ed
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Add script translation maps
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index eef4837838e..d249c3bce22 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -29,6 +29,78 @@ using namespace Common;
namespace Alcachofa {
+static constexpr const ScriptOp kScriptOpMap[] = {
+ ScriptOp::Nop,
+ ScriptOp::Dup,
+ ScriptOp::PushAddr,
+ ScriptOp::PushValue,
+ ScriptOp::Deref,
+ ScriptOp::Nop,
+ ScriptOp::Pop1,
+ ScriptOp::Store,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::PushDynAddr,
+ ScriptOp::Nop,
+ ScriptOp::ScriptCall,
+ ScriptOp::KernelCall,
+ ScriptOp::JumpIfFalse,
+ ScriptOp::JumpIfTrue,
+ ScriptOp::Jump,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Add,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Equals,
+ ScriptOp::NotEquals,
+ ScriptOp::BitAnd,
+ ScriptOp::BitOr,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::Nop,
+ ScriptOp::ReturnVoid
+};
+
+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::Animate,
+ ScriptKernelTask::SheriffTakesCharacter,
+ ScriptKernelTask::ChangeCharacter,
+ ScriptKernelTask::LerpCamToObjectKeepingZ,
+ ScriptKernelTask::Drop,
+ ScriptKernelTask::ChangeDoor,
+ ScriptKernelTask::Disguise,
+ ScriptKernelTask::ToggleRoomFloor,
+ ScriptKernelTask::SetDialogLineReturn,
+ ScriptKernelTask::DialogMenu,
+ ScriptKernelTask::ChangeCharacterRoom,
+ ScriptKernelTask::PlayMusic,
+ ScriptKernelTask::StopMusic,
+ ScriptKernelTask::WaitForMusicToEnd
+};
+
class GameMovieAdventureOriginal : public Game {
public:
GameMovieAdventureOriginal() {
@@ -70,6 +142,14 @@ public:
char getTextFileKey() override {
return kNoXORKey;
}
+
+ Span<const ScriptOp> getScriptOpMap() override {
+ return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
+ }
+
+ Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
+ return { kScriptKernelTaskMap, ARRAYSIZE(kScriptKernelTaskMap) };
+ }
};
Game *Game::createForMovieAdventureOriginal() {
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index d559c3b1336..bd562331bf5 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -61,6 +61,8 @@ public:
virtual const char *getDialogFileName() = 0;
virtual const char *getObjectFileName() = 0;
virtual char getTextFileKey() = 0;
+ 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);
Commit: c6c0723605ac3a07a768d84535db22903c00ef72
https://github.com/scummvm/scummvm/commit/c6c0723605ac3a07a768d84535db22903c00ef72
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Add reading of V1 images
Changed paths:
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/shape.cpp
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index c37ff0ca330..cf8e6ab65e9 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -160,7 +160,11 @@ bool readBool(ReadStream &stream) {
return stream.readByte() != 0;
}
-Point readPoint(ReadStream &stream) {
+Point readPoint16(ReadStream &stream) {
+ return { stream.readSint16LE(), stream.readSint16LE() };
+}
+
+Point readPoint32(ReadStream &stream) {
return { (int16)stream.readSint32LE(), (int16)stream.readSint32LE() };
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index cbef429d523..c30e05631ed 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -137,7 +137,8 @@ Math::Vector2d as2D(const Math::Vector3d &v);
Math::Vector2d as2D(Common::Point p);
bool readBool(Common::ReadStream &stream);
-Common::Point readPoint(Common::ReadStream &stream);
+Common::Point readPoint16(Common::ReadStream &stream);
+Common::Point readPoint32(Common::ReadStream &stream);
Common::String readVarString(Common::ReadStream &stream);
void skipVarString(Common::SeekableReadStream &stream);
void syncPoint(Common::Serializer &serializer, Common::Point &point);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 42fe7a9eecf..0206e2f890c 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -59,6 +59,92 @@ void IDebugRenderer::debugShape(const Shape &shape, Color color) {
}
}
+ImageV1::ImageV1(SeekableReadStream &stream) {
+ _drawOffset = readPoint16(stream);
+ stream.skip(4); // another point
+ _size = readPoint16(stream);
+ uint32 pixelCount = stream.readUint32LE();
+ stream.skip(5); // yet another point and some flag
+ bool isPaletted = readBool(stream);
+
+ // for segments we could either scan the file first to sum the total segment count
+ // or we can not use reserve and rely on the capacity doubling as heuristic
+ // using reserve will guarantee we always reallocate _segments
+ scumm_assert(_size.x >= 0 && _size.y >= 0);
+ _lines.reserve(_size.y);
+ for (int16 y = 0; y < _size.y; y++) {
+ uint16 segmentCount = stream.readUint16LE();
+ _lines.push_back({ _segments.size(), _segments.size() + segmentCount });
+ for (uint16 i = 0; i < segmentCount; i++) {
+ _segments.push_back({
+ stream.readUint16LE(),
+ stream.readUint16LE(),
+ stream.readUint32LE() });
+ }
+ }
+
+ _pixels.resize(pixelCount);
+ stream.read(_pixels.data(), pixelCount);
+ if (isPaletted) {
+ _palette.resize(PALETTE_SIZE);
+ stream.read(_palette.data(), PALETTE_SIZE);
+ }
+ scumm_assert(!stream.err());
+}
+
+ManagedSurface *ImageV1::render() const {
+ ManagedSurface *surface = new ManagedSurface(_size.x, _size.y, PixelFormat::createFormatARGB32());
+ render(*surface, {});
+ return surface;
+}
+
+void ImageV1::render(ManagedSurface &target, Point dstOffset) const {
+ assert(target.format == PixelFormat::createFormatARGB32());
+ Point srcOffset = {};
+ if (dstOffset.x < 0) {
+ srcOffset.x = -dstOffset.x;
+ dstOffset.x = 0;
+ }
+ if (dstOffset.y < 0) {
+ srcOffset.y = -dstOffset.y;
+ dstOffset.y = 0;
+ }
+ if (dstOffset.x >= target.w || dstOffset.y >= target.h ||
+ srcOffset.x >= _size.x || srcOffset.y >= _size.y)
+ return;
+ const int maxSrcY = MIN((int)_size.y, target.h - dstOffset.y);
+ const uint bpp = _palette.empty() ? 3 : 1;
+
+ int dstY = dstOffset.y;
+ for (int srcY = srcOffset.y; srcY < maxSrcY; srcY++, dstY++) {
+ const auto line = _lines[srcY];
+ int dstX = dstOffset.x;
+ for (uint segmentI = line._start; segmentI < line._end; segmentI++) {
+ const auto segment = _segments[segmentI];
+ dstX += segment._xOffset;
+ if (dstX >= target.w)
+ break;
+ if (dstX + segment._width < 0)
+ continue;
+ uint srcPixelI = segment._dataOffset;
+ if (dstX < 0)
+ srcPixelI += -dstX * bpp;
+ uint width = MIN((int)segment._width, target.w - dstX);
+
+ byte* dstPixel = (byte*)target.getBasePtr(dstX, dstY);
+ for (uint x = 0; x < width; x++) {
+ const byte* srcPixel = _palette.empty()
+ ? _pixels.data() + srcPixelI
+ : _palette.data() + 3 * _pixels[srcPixelI];
+ memcpy(dstPixel, srcPixel, 3);
+ dstPixel[3] = 0xff;
+ dstPixel += 4;
+ srcPixelI += bpp;
+ }
+ }
+ }
+}
+
AnimationBase::AnimationBase(GameFileReference fileRef, AnimationFolder folder)
: _fileRef(move(fileRef))
, _folder(folder) {}
@@ -103,16 +189,28 @@ void AnimationBase::load() {
}
// Reading the images is a major bottleneck in loading, buffering helps a lot with that
ScopedPtr<SeekableReadStream> stream(wrapBufferedSeekableReadStream(&file, file.size(), DisposeAfterUse::NO));
+ if (g_engine->isV1())
+ readV1(*stream);
+ else
+ readV3(*stream);
- uint spriteCount = stream->readUint32LE();
+ _isLoaded = true;
+}
+
+void AnimationBase::readV1(SeekableReadStream &stream) {
+ assert(false);
+}
+
+void AnimationBase::readV3(SeekableReadStream &stream) {
+ uint spriteCount = stream.readUint32LE();
assert(spriteCount < kMaxSpriteIDs);
_spriteBases.reserve(spriteCount);
- uint imageCount = stream->readUint32LE();
+ uint imageCount = stream.readUint32LE();
_images.reserve(imageCount);
_imageOffsets.reserve(imageCount);
for (uint i = 0; i < imageCount; i++) {
- _images.push_back(readImage(*stream));
+ _images.push_back(readImageV3(stream));
}
// an inconsistency, maybe a historical reason:
@@ -120,37 +218,35 @@ void AnimationBase::load() {
// have to be contiguous we do not need to do that ourselves.
// but let's check in Debug to be sure
for (uint i = 0; i < spriteCount; i++) {
- _spriteBases.push_back(stream->readUint32LE());
+ _spriteBases.push_back(stream.readUint32LE());
assert(_spriteBases.back() < imageCount);
}
#ifdef ALCACHOFA_DEBUG
for (uint i = spriteCount; i < kMaxSpriteIDs; i++)
- assert(stream->readSint32LE() == 0);
+ assert(stream.readSint32LE() == 0);
#else
- stream->skip(sizeof(int32) * (kMaxSpriteIDs - spriteCount));
+ stream.skip(sizeof(int32) * (kMaxSpriteIDs - spriteCount));
#endif
for (uint i = 0; i < imageCount; i++)
- _imageOffsets.push_back(readPoint(*stream));
+ _imageOffsets.push_back(readPoint32(stream));
for (uint i = 0; i < kMaxSpriteIDs; i++)
- _spriteIndexMapping[i] = stream->readSint32LE();
+ _spriteIndexMapping[i] = stream.readSint32LE();
- uint frameCount = stream->readUint32LE();
+ uint frameCount = stream.readUint32LE();
_frames.reserve(frameCount);
_spriteOffsets.reserve(frameCount * spriteCount);
_totalDuration = 0;
for (uint i = 0; i < frameCount; i++) {
for (uint j = 0; j < spriteCount; j++)
- _spriteOffsets.push_back(stream->readUint32LE());
+ _spriteOffsets.push_back(stream.readUint32LE());
AnimationFrame frame;
- frame._center = readPoint(*stream);
- frame._offset = readPoint(*stream);
- frame._duration = stream->readUint32LE();
+ frame._center = readPoint32(stream);
+ frame._offset = readPoint32(stream);
+ frame._duration = stream.readUint32LE();
_frames.push_back(frame);
_totalDuration += frame._duration;
}
-
- _isLoaded = true;
}
void AnimationBase::freeImages() {
@@ -168,7 +264,7 @@ void AnimationBase::freeImages() {
_isLoaded = false;
}
-ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
+ManagedSurface *AnimationBase::readImageV3(SeekableReadStream &stream) const {
SeekableSubReadStream subStream(&stream, stream.pos(), stream.size());
TGADecoder decoder;
if (!decoder.loadStream(subStream))
@@ -516,7 +612,7 @@ Graphic::Graphic() {}
Graphic::Graphic(SeekableReadStream &stream) {
if (g_engine->isV1()) {
- _topLeft = readPoint(stream);
+ _topLeft = readPoint32(stream);
_scale = stream.readSint16LE();
_order = (int8)stream.readSint16LE();
} else {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 1cb5da94f8f..b2c86c7e8d5 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -122,6 +122,31 @@ public:
}
};
+// as futureoptimization one could actually store these instead of converting to a surface
+// this will reduce memory usage and might reduce render times on software renderers
+class ImageV1 {
+public:
+ ImageV1(Common::SeekableReadStream &stream);
+
+ void render(Graphics::ManagedSurface &target, Common::Point offset) const;
+ Graphics::ManagedSurface *render() const;
+
+private:
+ struct Segment {
+ uint16 _xOffset, _width;
+ uint32 _dataOffset;
+ };
+ struct Line { // indices into _segments
+ uint _start, _end;
+ };
+
+ Common::Point _drawOffset, _size;
+ Common::Array<byte> _pixels;
+ Common::Array<byte> _palette;
+ Common::Array<Segment> _segments;
+ Common::Array<Line> _lines;
+};
+
enum class AnimationFolder {
Animations,
Masks,
@@ -154,7 +179,9 @@ protected:
void load();
void loadMissingAnimation();
void freeImages();
- Graphics::ManagedSurface *readImage(Common::SeekableReadStream &stream) const;
+ void readV1(Common::SeekableReadStream &stream);
+ void readV3(Common::SeekableReadStream &stream);
+ Graphics::ManagedSurface *readImageV3(Common::SeekableReadStream &stream) const;
Common::Point imageSize(int32 imageI) const;
inline bool isLoaded() const { return _isLoaded; }
@@ -170,10 +197,11 @@ protected:
bool _isLoaded = false;
uint32 _totalDuration = 0;
- int32 _spriteIndexMapping[kMaxSpriteIDs] = { 0 };
+ int32 _spriteIndexMapping[kMaxSpriteIDs] = { 0 }; ///< establishes render order back-to-front
Common::Array<uint32>
_spriteOffsets, ///< index offset per sprite and animation frame
_spriteBases; ///< base index per sprite
+ Common::Array<bool> _spriteEnabled;
Common::Array<AnimationFrame> _frames;
Common::Array<Graphics::ManagedSurface *> _images; ///< will contain nullptr for fake images
Common::Array<Common::Point> _imageOffsets;
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 3a2c1fda964..a0bde018bd1 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -270,7 +270,7 @@ Shape::Shape(ReadStream &stream) {
? stream.readByte()
: pointsPerPolygon;
for (int j = 0; j < pointCount; j++)
- _points.push_back(readPoint(stream));
+ _points.push_back(readPoint32(stream));
addPolygon(pointCount);
}
}
@@ -348,7 +348,7 @@ PathFindingShape::PathFindingShape(ReadStream &stream) {
for (int i = 0; i < polygonCount; i++) {
for (uint j = 0; j < kPointsPerPolygon; j++)
- _points.push_back(readPoint(stream));
+ _points.push_back(readPoint32(stream));
_polygonOrders.push_back(stream.readSByte());
for (uint j = 0; j < kPointsPerPolygon; j++)
_pointDepths.push_back(stream.readByte());
@@ -638,7 +638,7 @@ FloorColorShape::FloorColorShape(ReadStream &stream) {
for (int i = 0; i < polygonCount; i++) {
for (uint j = 0; j < kPointsPerPolygon; j++)
- _points.push_back(readPoint(stream));
+ _points.push_back(readPoint32(stream));
// For the colors the alpha channel is not used so we store the brightness into it instead
// Brightness is store 0-100, but we can scale it up here
Commit: 0d9d8e1efa21122f06d7e335f2e8b5ecc2e28864
https://github.com/scummvm/scummvm/commit/0d9d8e1efa21122f06d7e335f2e8b5ecc2e28864
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Support reading V1 animations
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 0206e2f890c..de97be576f6 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -157,48 +157,118 @@ void AnimationBase::load() {
if (_isLoaded)
return;
- assert(!_fileRef.isEmbedded());
-
- String fullPath;
- switch (_folder) {
- case AnimationFolder::Animations:
- fullPath = "Animaciones/";
- break;
- case AnimationFolder::Masks:
- fullPath = "Mascaras/";
- break;
- case AnimationFolder::Backgrounds:
- fullPath = "Fondos/";
- break;
- default:
- assert(false && "Invalid AnimationFolder");
- break;
- }
- if (_fileRef._path.size() < 4 || scumm_strnicmp(_fileRef._path.end() - 4, ".AN0", 4) != 0)
- _fileRef._path += ".AN0";
- fullPath += _fileRef._path;
-
- File file;
- if (!file.open(fullPath.c_str())) {
- // original fallback
- fullPath = "Mascaras/" + _fileRef._path;
- if (!file.open(fullPath.c_str())) {
- loadMissingAnimation();
- return;
+ ScopedPtr<SeekableReadStream> rawStream;
+ if (_fileRef.isEmbedded())
+ rawStream = g_engine->world().openFileRef(_fileRef);
+ else {
+ // for real file paths we have to apply the folder and do some fallback
+ String fullPath;
+ switch (_folder) {
+ case AnimationFolder::Animations:
+ fullPath = "Animaciones/";
+ break;
+ case AnimationFolder::Masks:
+ fullPath = "Mascaras/";
+ break;
+ case AnimationFolder::Backgrounds:
+ fullPath = "Fondos/";
+ break;
+ default:
+ assert(false && "Invalid AnimationFolder");
+ break;
+ }
+ fullPath += _fileRef._path;
+ if (_fileRef._path.size() < 4 || scumm_strnicmp(_fileRef._path.end() - 4, ".AN0", 4) != 0)
+ fullPath += ".AN0";
+
+ File *file = new File();
+ if (!file->open(fullPath.c_str())) {
+ // original fallback
+ fullPath = "Mascaras/" + _fileRef._path;
+ if (!file->open(fullPath.c_str())) {
+ delete file;
+ file = nullptr;
+ }
}
+ rawStream.reset(file);
}
+
+ if (rawStream == nullptr) {
+ loadMissingAnimation();
+ return;
+ }
+
// Reading the images is a major bottleneck in loading, buffering helps a lot with that
- ScopedPtr<SeekableReadStream> stream(wrapBufferedSeekableReadStream(&file, file.size(), DisposeAfterUse::NO));
+ ScopedPtr<SeekableReadStream> stream(
+ wrapBufferedSeekableReadStream(rawStream.get(), rawStream->size(), DisposeAfterUse::NO));
if (g_engine->isV1())
readV1(*stream);
else
readV3(*stream);
-
_isLoaded = true;
}
void AnimationBase::readV1(SeekableReadStream &stream) {
- assert(false);
+ skipVarString(stream); // another internal, unused name
+ uint spriteCount = stream.readUint32LE();
+ uint frameCount = stream.readUint32LE();
+ assert(spriteCount < kMaxSpriteIDsV1);
+ _spriteBases.reserve(spriteCount);
+ _spriteEnabled.reserve(spriteCount);
+ _spriteOffsets.reserve(spriteCount * frameCount);
+
+ readPoint16(stream); // "totalSize", unused for now
+ stream.skip(4); // unknown
+ _totalDuration = stream.readUint32LE();
+ stream.skip(4);
+ byte alpha = stream.readByte(); // TODO: We *should* use this
+ 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());
+ _spriteEnabled.push_back(!readBool(stream));
+ 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());
+ }
+ }
+
+ // Sprite order is setup by setting up reverse index sequence and
+ // then stable sort descending by order (here: Bubblesort)
+ for (uint i = 0; i < spriteCount; i++)
+ _spriteIndexMapping[i] = (spriteCount - 1 - i);
+ for (uint i = 0; i < spriteCount; i++) {
+ bool hadChange = false;
+ for (uint j = 0; j < spriteCount - i - 1; j++) {
+ if (spriteOrder[_spriteIndexMapping[j]] < spriteOrder[_spriteIndexMapping[j + 1]])
+ SWAP(_spriteIndexMapping[j], _spriteIndexMapping[j + 1]);
+ }
+ if (!hadChange)
+ break;
+ }
+
+ for (uint i = 0; i < frameCount; i++) {
+ for (uint j = 0; j < spriteCount; j++) {
+ int imageI = stream.readSint32LE();
+ if (imageI <= 0) // we make sure that spriteBases + imageI <= 0 if the local imageI <= 0
+ imageI = -(int)_spriteOffsets.size();
+ _spriteOffsets.push_back(imageI);
+ }
+ stream.skip((kMaxSpriteIDsV1 - spriteCount) * 4);
+
+ AnimationFrame frame;
+ frame._center = readPoint16(stream);
+ frame._offset = readPoint16(stream);
+ frame._duration = stream.readUint16LE();
+ _frames.push_back(frame);
+ }
}
void AnimationBase::readV3(SeekableReadStream &stream) {
@@ -219,7 +289,7 @@ void AnimationBase::readV3(SeekableReadStream &stream) {
// but let's check in Debug to be sure
for (uint i = 0; i < spriteCount; i++) {
_spriteBases.push_back(stream.readUint32LE());
- assert(_spriteBases.back() < imageCount);
+ assert((uint)_spriteBases.back() < imageCount);
}
#ifdef ALCACHOFA_DEBUG
for (uint i = spriteCount; i < kMaxSpriteIDs; i++)
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index b2c86c7e8d5..30569469658 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -128,6 +128,8 @@ class ImageV1 {
public:
ImageV1(Common::SeekableReadStream &stream);
+ inline Common::Point drawOffset() const { return _drawOffset; }
+
void render(Graphics::ManagedSurface &target, Common::Point offset) const;
Graphics::ManagedSurface *render() const;
@@ -192,13 +194,14 @@ protected:
int offsetY);
static constexpr const uint kMaxSpriteIDs = 256;
+ static constexpr const uint kMaxSpriteIDsV1 = 8;
GameFileReference _fileRef;
AnimationFolder _folder;
bool _isLoaded = false;
uint32 _totalDuration = 0;
int32 _spriteIndexMapping[kMaxSpriteIDs] = { 0 }; ///< establishes render order back-to-front
- Common::Array<uint32>
+ Common::Array<int32>
_spriteOffsets, ///< index offset per sprite and animation frame
_spriteBases; ///< base index per sprite
Common::Array<bool> _spriteEnabled;
Commit: b800ace2399489b18a5b3ba3f35afccbe89e034d
https://github.com/scummvm/scummvm/commit/b800ace2399489b18a5b3ba3f35afccbe89e034d
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Fix V1 animation reading
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index de97be576f6..1b0fae7d4c8 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -83,8 +83,9 @@ ImageV1::ImageV1(SeekableReadStream &stream) {
}
}
- _pixels.resize(pixelCount);
- stream.read(_pixels.data(), pixelCount);
+ const uint bpp = isPaletted ? 1 : 3;
+ _pixels.resize(pixelCount * bpp);
+ stream.read(_pixels.data(), pixelCount * bpp);
if (isPaletted) {
_palette.resize(PALETTE_SIZE);
stream.read(_palette.data(), PALETTE_SIZE);
@@ -92,14 +93,14 @@ ImageV1::ImageV1(SeekableReadStream &stream) {
scumm_assert(!stream.err());
}
-ManagedSurface *ImageV1::render() const {
- ManagedSurface *surface = new ManagedSurface(_size.x, _size.y, PixelFormat::createFormatARGB32());
- render(*surface, {});
+ManagedSurface *ImageV1::render(byte alpha) const {
+ ManagedSurface *surface = new ManagedSurface(_size.x, _size.y, BlendBlit::getSupportedPixelFormat());
+ render(*surface, {}, alpha);
return surface;
}
-void ImageV1::render(ManagedSurface &target, Point dstOffset) const {
- assert(target.format == PixelFormat::createFormatARGB32());
+void ImageV1::render(ManagedSurface &target, Point dstOffset, byte alpha) const {
+ assert(target.format.bytesPerPixel == 4);
Point srcOffset = {};
if (dstOffset.x < 0) {
srcOffset.x = -dstOffset.x;
@@ -131,14 +132,13 @@ void ImageV1::render(ManagedSurface &target, Point dstOffset) const {
srcPixelI += -dstX * bpp;
uint width = MIN((int)segment._width, target.w - dstX);
- byte* dstPixel = (byte*)target.getBasePtr(dstX, dstY);
+ uint32* dstPixel = (uint32*)target.getBasePtr(dstX, dstY);
for (uint x = 0; x < width; x++) {
const byte* srcPixel = _palette.empty()
? _pixels.data() + srcPixelI
: _palette.data() + 3 * _pixels[srcPixelI];
- memcpy(dstPixel, srcPixel, 3);
- dstPixel[3] = 0xff;
- dstPixel += 4;
+ *dstPixel = target.format.ARGBToColor(alpha, srcPixel[0], srcPixel[1], srcPixel[2]);
+ dstPixel++;
srcPixelI += bpp;
}
}
@@ -209,10 +209,14 @@ void AnimationBase::load() {
}
void AnimationBase::readV1(SeekableReadStream &stream) {
+ char magic[4];
+ stream.read(magic, sizeof(magic));
+ scumm_assert(memcmp(magic, "ANI", 4) == 0);
+
skipVarString(stream); // another internal, unused name
uint spriteCount = stream.readUint32LE();
uint frameCount = stream.readUint32LE();
- assert(spriteCount < kMaxSpriteIDsV1);
+ scumm_assert(spriteCount < kMaxSpriteIDsV1);
_spriteBases.reserve(spriteCount);
_spriteEnabled.reserve(spriteCount);
_spriteOffsets.reserve(spriteCount * frameCount);
@@ -221,7 +225,7 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
stream.skip(4); // unknown
_totalDuration = stream.readUint32LE();
stream.skip(4);
- byte alpha = stream.readByte(); // TODO: We *should* use this
+ byte alpha = stream.readByte();
stream.skip(8);
Array<byte> spriteOrder;
@@ -236,7 +240,7 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
for (uint j = 0; j < imageCount; j++) {
ImageV1 image(stream);
_imageOffsets.push_back(image.drawOffset());
- _images.push_back(image.render());
+ _images.push_back(image.render(alpha));
}
}
@@ -256,12 +260,12 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
for (uint i = 0; i < frameCount; i++) {
for (uint j = 0; j < spriteCount; j++) {
- int imageI = stream.readSint32LE();
+ int imageI = stream.readSByte();
if (imageI <= 0) // we make sure that spriteBases + imageI <= 0 if the local imageI <= 0
imageI = -(int)_spriteOffsets.size();
_spriteOffsets.push_back(imageI);
}
- stream.skip((kMaxSpriteIDsV1 - spriteCount) * 4);
+ stream.skip(kMaxSpriteIDsV1 - spriteCount);
AnimationFrame frame;
frame._center = readPoint16(stream);
@@ -622,13 +626,13 @@ void Font::load() {
return;
AnimationBase::load();
// We now render all frames into a 16x16 atlas and fill up to power of two size just because it is easy here
- // However in two out of three fonts the character 128 is massive, it looks like a bug
+ // However for V3 in two out of three fonts the character 128 is massive, it looks like a bug
// as we want easy regular-sized characters it is ignored
Point cellSize;
for (auto image : _images) {
assert(image != nullptr); // no fake pictures in fonts please
- if (image == _images[128])
+ if (g_engine->isV3() && image == _images[128])
continue;
cellSize.x = MAX(cellSize.x, image->w);
cellSize.y = MAX(cellSize.y, image->h);
@@ -642,7 +646,7 @@ void Font::load() {
const float invWidth = 1.0f / atlasSurface.w;
const float invHeight = 1.0f / atlasSurface.h;
for (uint i = 0; i < _images.size(); i++) {
- if (i == 128) continue;
+ if (g_engine->isV3() && i == 128) continue;
int offsetX = (i % 16) * cellSize.x + (cellSize.x - _images[i]->w) / 2;
int offsetY = (i / 16) * cellSize.y + (cellSize.y - _images[i]->h) / 2;
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 30569469658..b436e37223b 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -130,8 +130,8 @@ public:
inline Common::Point drawOffset() const { return _drawOffset; }
- void render(Graphics::ManagedSurface &target, Common::Point offset) const;
- Graphics::ManagedSurface *render() const;
+ void render(Graphics::ManagedSurface &target, Common::Point offset, byte alpha = 255) const;
+ Graphics::ManagedSurface *render(byte alpha = 255) const;
private:
struct Segment {
Commit: f89eb3f6a498fc48066b1656c59c827ef054b3f2
https://github.com/scummvm/scummvm/commit/f89eb3f6a498fc48066b1656c59c827ef054b3f2
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Fix script variable count
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 016804dce9a..32d56690453 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -94,6 +94,7 @@ Common::Error AlcachofaEngine::run() {
_scheduler.run();
// we run once to set the initial room, otherwise we could run into currentRoom == nullptr
}
+ assert(_player->currentRoom() != nullptr);
Common::Event e;
Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 5d777730431..59a34026ec1 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -69,6 +69,7 @@ Script::Script() {
// in V1 the memory size is implicitly stored in the variables
for (const auto &variable : _variableNames)
memorySize = MAX(memorySize, variable._value + (uint32)sizeof(int32));
+ memorySize *= sizeof(int32); // _variableNames already works with indices
}
if (memorySize % sizeof(int32) != 0)
error("Unexpected size of script memory");
Commit: 294faec0cb660999fa0ce60a1dc76ba0115d05ef
https://github.com/scummvm/scummvm/commit/294faec0cb660999fa0ce60a1dc76ba0115d05ef
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Implement differences in script instructions
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/script-debug.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 32d56690453..65a4a1c1e0c 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -90,7 +90,7 @@ Common::Error AlcachofaEngine::run() {
game().onLoadedGameFiles();
if (!tryLoadFromLauncher()) {
- _script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
+ _script->createProcess(MainCharacterKind::None, _game->getInitScriptName());
_scheduler.run();
// we run once to set the initial room, otherwise we could run into currentRoom == nullptr
}
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index d249c3bce22..3a11df7deaa 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -101,6 +101,38 @@ static constexpr const ScriptKernelTask kScriptKernelTaskMap[] = {
ScriptKernelTask::WaitForMusicToEnd
};
+static constexpr const int kKernelTaskArgCounts[] = {
+ 0,
+ 2,
+ 3,
+ 1,
+ 2,
+ 1,
+ 1,
+ 2,
+ 1,
+ 1,
+ 2,
+ 1,
+ 1,
+ 1,
+ 2,
+ 2,
+ 0,
+ 1,
+ 2,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 0,
+ 2,
+ 1,
+ 0,
+ 0
+};
+
class GameMovieAdventureOriginal : public Game {
public:
GameMovieAdventureOriginal() {
@@ -143,6 +175,10 @@ public:
return kNoXORKey;
}
+ const char *getInitScriptName() override {
+ return "INICIALIZAR_MUNDO";
+ }
+
Span<const ScriptOp> getScriptOpMap() override {
return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
}
@@ -150,6 +186,11 @@ public:
Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
return { kScriptKernelTaskMap, ARRAYSIZE(kScriptKernelTaskMap) };
}
+
+ int32 getKernelTaskArgCount(int32 taskI) override {
+ assert(taskI >= 0 && taskI < ARRAYSIZE(kKernelTaskArgCounts));
+ return kKernelTaskArgCounts[taskI];
+ }
};
Game *Game::createForMovieAdventureOriginal() {
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 66a5f6f8e5b..3295cd9439c 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -228,6 +228,10 @@ public:
return kEmbeddedXORKey;
}
+ const char *getInitScriptName() override {
+ return "CREDITOS_INICIALES";
+ }
+
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)
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 36c2abbf59c..a77b0ca7f73 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -39,6 +39,11 @@ void Game::onLoadedGameFiles() {}
void Game::drawScreenStates() {}
+int32 Game::getKernelTaskArgCount(int32 kernelTaskI) {
+ (void)kernelTaskI;
+ return 0;
+}
+
bool Game::doesRoomHaveBackground(const Room *room) {
return true;
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index bd562331bf5..bfb54423058 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -61,8 +61,10 @@ public:
virtual const char *getDialogFileName() = 0;
virtual const char *getObjectFileName() = 0;
virtual char getTextFileKey() = 0;
+ virtual const char *getInitScriptName() = 0;
virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
+ virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
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 f6e6d1fa807..96bab4d7232 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -28,8 +28,10 @@ static const char *const ScriptOpNames[] = {
"Nop",
"Dup",
"PushAddr",
+ "PushDynAddr",
"PushValue",
"Deref",
+ "Pop1",
"PopN",
"Store",
"LoadString",
@@ -110,7 +112,11 @@ static const char *const KernelCallNames[] = {
"FadeIn2",
"FadeOut2",
"LerpCamXYZ",
- "LerpCamToObjectKeepingZ"
+ "LerpCamToObjectKeepingZ",
+
+ "SheriffTakesCharacter",
+ "ChangeDoor",
+ "Disguise"
};
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 59a34026ec1..61b5b87c67d 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -250,8 +250,12 @@ struct ScriptTask final : public Task {
error("Script process reached instruction out-of-bounds");
const auto &instruction = _script._instructions[_pc++];
if (debugChannelSet(SCRIPT_DEBUG_LVL_INSTRUCTIONS, kDebugScript)) {
+ const char *opName = "<invalid>";
+ if (instruction._op >= 0 && (uint32)instruction._op < opMap.size())
+ opName = ScriptOpNames[(int)opMap[(uint32)instruction._op]];
+
debugN("%u: %5u %-12s %8d Stack: ",
- process().pid(), _pc - 1, ScriptOpNames[(int)instruction._op], instruction._arg);
+ process().pid(), _pc - 1, opName, instruction._arg);
if (_stack.empty())
debug("empty");
else {
@@ -290,12 +294,24 @@ struct ScriptTask final : public Task {
case ScriptOp::PushAddr:
pushVariable(instruction._arg);
break;
+ case ScriptOp::PushDynAddr: {
+ int32 address = popNumber();
+ if (address < 0 || (uint32)address >= _script._strings->size())
+ error("Script tried to push invalid address: %d", address);
+ else if ((uint32)address < _script._variables.size() * 4)
+ pushVariable(address);
+ else
+ pushString((uint32)address);
+ }break;
case ScriptOp::PushValue:
pushNumber(instruction._arg);
break;
case ScriptOp::Deref:
pushNumber(popVariable());
break;
+ case ScriptOp::Pop1:
+ popN(1);
+ break;
case ScriptOp::PopN:
popN(instruction._arg);
break;
@@ -370,6 +386,11 @@ struct ScriptTask final : public Task {
case ScriptOp::BitOr:
pushNumber(popNumber() | popNumber()); //-V501
break;
+ case ScriptOp::ReturnVoid: {
+ _pc = popInstruction();
+ if (_pc == UINT_MAX)
+ return TaskReturn::finish(0);
+ }break;
case ScriptOp::ReturnValue: {
int32 returnValue = popNumber();
_pc = popInstruction();
@@ -418,16 +439,25 @@ struct ScriptTask final : public Task {
private:
void setCharacterVariables() {
_script.variable("m_o_f") = (int32)process().character();
- _script.variable("m_o_f_real") = (int32)g_engine->player().activeCharacterKind();
+ if (g_engine->isV3())
+ _script.variable("m_o_f_real") = (int32)g_engine->player().activeCharacterKind();
}
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() &&
- g_engine->game().getScriptOpMap()[_script._instructions[_pc]._op] == ScriptOp::PopN);
- popN(_script._instructions[_pc++]._arg);
+ // before pushing the return value the arguments have to be popped
+ // 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()) {
+ 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);
}
@@ -558,6 +588,7 @@ private:
g_engine->game().unknownKernelTask(taskI);
return TaskReturn::finish(-1);
}
+ _lastKernelTaskI = taskI;
const auto task = taskMap[taskI];
debugC(SCRIPT_DEBUG_LVL_KERNELCALLS, kDebugScript, "%u: %5u Kernel %-25s",
@@ -975,6 +1006,7 @@ private:
Stack<StackEntry> _stack;
String _name;
uint32 _pc = 0;
+ int32 _lastKernelTaskI = 0;
bool _returnsFromKernelCall = false;
bool _isFirstExecution = true;
FakeLock _lock;
Commit: 81d9c9c6d20d4f46d287c80449e144df63c3ee8c
https://github.com/scummvm/scummvm/commit/81d9c9c6d20d4f46d287c80449e144df63c3ee8c
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Fix script variable count
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 61b5b87c67d..d077f9e0229 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -68,7 +68,7 @@ Script::Script() {
if (memorySize == 0) {
// in V1 the memory size is implicitly stored in the variables
for (const auto &variable : _variableNames)
- memorySize = MAX(memorySize, variable._value + (uint32)sizeof(int32));
+ memorySize = MAX(memorySize, variable._value + 1);
memorySize *= sizeof(int32); // _variableNames already works with indices
}
if (memorySize % sizeof(int32) != 0)
@@ -438,9 +438,11 @@ struct ScriptTask final : public Task {
private:
void setCharacterVariables() {
- _script.variable("m_o_f") = (int32)process().character();
- if (g_engine->isV3())
+ if (g_engine->isV3()) {
+ _script.variable("m_o_f") = (int32)process().character();
_script.variable("m_o_f_real") = (int32)g_engine->player().activeCharacterKind();
+ } else
+ _script.variable("m_o_f") = (int32)g_engine->player().activeCharacterKind();
}
void handleReturnFromKernelCall(int32 returnValue) {
@@ -572,7 +574,13 @@ private:
}
MainCharacter &relatedCharacter() {
- if (process().character() == MainCharacterKind::None)
+ if (g_engine->isV1()) {
+ auto character = g_engine->player().activeCharacter();
+ if (character == nullptr)
+ error("Script tried to use character before setting an active character");
+ return *character;
+ }
+ if (process().character() == MainCharacterKind::None)
error("Script tried to use character from non-character-related process");
return g_engine->world().getMainCharacterByKind(process().character());
}
Commit: 2a92c67e265cbf6548a3cc1c72ebf20ece3f631a
https://github.com/scummvm/scummvm/commit/2a92c67e265cbf6548a3cc1c72ebf20ece3f631a
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Fix V1 backgrounds and empty animations
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 3a11df7deaa..3521da5998f 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -191,6 +191,25 @@ public:
assert(taskI >= 0 && taskI < ARRAYSIZE(kKernelTaskArgCounts));
return kKernelTaskArgCounts[taskI];
}
+
+ void missingAnimation(const String &fileName) override {
+ static const char *exemptions[] = {
+ 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);
+ }
};
Game *Game::createForMovieAdventureOriginal() {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 1b0fae7d4c8..a8f054dd000 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -158,9 +158,15 @@ void AnimationBase::load() {
return;
ScopedPtr<SeekableReadStream> rawStream;
- if (_fileRef.isEmbedded())
- rawStream = g_engine->world().openFileRef(_fileRef);
- else {
+ if (_fileRef.isEmbedded()) {
+ if (_fileRef._size == 0) {
+ // this happens for some special cases in original movie adventure
+ // we cannot really check against an allowlist here, so we don't
+ setToEmpty();
+ return;
+ } else
+ rawStream = g_engine->world().openFileRef(_fileRef);
+ } else {
// for real file paths we have to apply the folder and do some fallback
String fullPath;
switch (_folder) {
@@ -194,7 +200,9 @@ void AnimationBase::load() {
}
if (rawStream == nullptr) {
- loadMissingAnimation();
+ // only allow missing animations we know are faulty in the original game
+ g_engine->game().missingAnimation(_fileRef._path);
+ setToEmpty();
return;
}
@@ -372,11 +380,8 @@ ManagedSurface *AnimationBase::readImageV3(SeekableReadStream &stream) const {
return target;
}
-void AnimationBase::loadMissingAnimation() {
- // only allow missing animations we know are faulty in the original game
- g_engine->game().missingAnimation(_fileRef._path);
-
- // otherwise setup a functioning but empty animation
+void AnimationBase::setToEmpty() {
+ assert(!_isLoaded);
_isLoaded = true;
_totalDuration = 1;
_spriteIndexMapping[0] = 0;
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index b436e37223b..384f35a312f 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -179,8 +179,8 @@ protected:
~AnimationBase();
void load();
- void loadMissingAnimation();
void freeImages();
+ void setToEmpty();
void readV1(Common::SeekableReadStream &stream);
void readV3(Common::SeekableReadStream &stream);
Graphics::ManagedSurface *readImageV3(Common::SeekableReadStream &stream) const;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 2e61097e8d5..8c133bea73f 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -94,15 +94,20 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return nullptr; // handled in Room::Room
}
-void Room::readRoomV1(SeekableReadStream &stream, bool readObjects) {
+void Room::readRoomV1(SeekableReadStream &stream, bool shouldReadObjects) {
_name = readVarString(stream);
_backgroundName = readVarString(stream);
_musicId = (int)stream.readByte();
_characterAlphaTint = stream.readByte();
skipVarString(stream);
- if (readObjects)
- readObjectsAndBackground(stream, kBaseScale);
+ if (shouldReadObjects)
+ readObjects(stream, kBaseScale);
+
+ if (!_backgroundName.empty() && g_engine->game().doesRoomHaveBackground(this)) {
+ // in V1 _backgroundName refers to an object which represents the background
+ _backgroundObject = getObjectByName(_backgroundName.c_str());
+ }
}
void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
@@ -119,10 +124,16 @@ void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
if (hasUselessByte)
stream.readByte();
- readObjectsAndBackground(stream, backgroundScale);
+ readObjects(stream, backgroundScale);
+
+ if (g_engine->game().doesRoomHaveBackground(this)) {
+ // in V3 _background is the name of an animation, we have to create the object
+ _backgroundObject = new Background(this, _backgroundName, backgroundScale);
+ _objects.push_back(_backgroundObject);
+ }
}
-void Room::readObjectsAndBackground(SeekableReadStream &stream, int16 backgroundScale) {
+void Room::readObjects(SeekableReadStream &stream, int16 backgroundScale) {
uint32 objectEnd = stream.readUint32LE();
while (objectEnd > 0) {
const auto type = readVarString(stream);
@@ -140,8 +151,6 @@ void Room::readObjectsAndBackground(SeekableReadStream &stream, int16 background
_objects.push_back(object);
objectEnd = stream.readUint32LE();
}
- if (g_engine->game().doesRoomHaveBackground(this))
- _objects.push_back(new Background(this, _backgroundName, backgroundScale));
if (!_floors[0].empty())
_activeFloorI = 0;
@@ -151,7 +160,7 @@ RoomWithFloor::RoomWithFloor(World *world, SeekableReadStream &stream) : Room(wo
readRoomV1(stream, false);
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
- readObjectsAndBackground(stream, kBaseScale);
+ readObjects(stream, kBaseScale);
}
Room::~Room() {
@@ -252,8 +261,7 @@ void Room::updateInteraction() {
}
void Room::updateRoomBounds() {
- auto background = getObjectByName("Background");
- auto graphic = background == nullptr ? nullptr : background->graphic();
+ auto graphic = _backgroundObject == nullptr ? nullptr : _backgroundObject->graphic();
if (graphic != nullptr) {
auto bgSize = graphic->animation().imageSize(0);
/* This fixes a bug where if the background image is invalid the original engine
@@ -399,7 +407,7 @@ Inventory::Inventory(World *world, SeekableReadStream &stream) : Room(world) {
if (g_engine->isV1()) {
readRoomV1(stream, false);
stream.skip(1); // denoted as "sinusar" but unused
- readObjectsAndBackground(stream, kBaseScale);
+ readObjects(stream, kBaseScale);
} else
readRoomV3(stream, true);
}
@@ -501,7 +509,7 @@ void Inventory::drawAsOverlay(int32 scrollY) {
int8 oldOrder = graphic->order();
graphic->topLeft().y += scrollY;
graphic->order() = -kForegroundOrderCount;
- if (object->name().equalsIgnoreCase("Background"))
+ if (object == _backgroundObject)
graphic->order()++;
object->draw();
graphic->topLeft().y = oldY;
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 442f21c75f3..93e91aaba07 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -68,7 +68,7 @@ protected:
Room(World *world);
void readRoomV1(Common::SeekableReadStream &stream, bool readObjects);
void readRoomV3(Common::SeekableReadStream &stream, bool hasUselessByte);
- void readObjectsAndBackground(Common::SeekableReadStream &stream, int16 backgroundScale);
+ void readObjects(Common::SeekableReadStream &stream, int16 backgroundScale);
void updateScripts();
void updateRoomBounds();
void updateInteraction();
@@ -78,6 +78,7 @@ protected:
ShapeObject *getSelectedObject(ShapeObject *best = nullptr) const;
World *_world;
+ ObjectBase *_backgroundObject = nullptr;
Common::String _name, _backgroundName;
PathFindingShape _floors[2];
bool _fixedCameraOnEntering = false;
Commit: ab1cbeb1722278cd8ab5995acbad5ea906b60112
https://github.com/scummvm/scummvm/commit/ab1cbeb1722278cd8ab5995acbad5ea906b60112
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Replace dependencies on version-dependent script variables
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/global-ui.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 3521da5998f..26da9514d3d 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -192,6 +192,15 @@ public:
return kKernelTaskArgCounts[taskI];
}
+ void updateScriptVariables() override {
+ g_engine->script().variable("EstanAmbos") =
+ g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
+ }
+
+ bool shouldClipCamera() override {
+ return true;
+ }
+
void missingAnimation(const String &fileName) override {
static const char *exemptions[] = {
nullptr
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 3295cd9439c..d8650a6cad2 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -241,6 +241,21 @@ public:
script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
}
+ 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.setScriptTimer(!script.variable("CalcularTiempoSinPulsarRaton"));
+ script.variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
+ script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
+ script.variable("modored") = 0; // this is signalling whether a network connection is established
+ }
+
+ bool shouldClipCamera() override {
+ return g_engine->script().variable("EncuadrarCamara");
+ }
+
void onLoadedGameFiles() override {
// this notifies the script whether we are a demo
if (g_engine->world().loadedMapCount() == 2)
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index bfb54423058..fee0bd54216 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -65,6 +65,8 @@ public:
virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
+ virtual void updateScriptVariables() = 0;
+ virtual bool shouldClipCamera() = 0;
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 4164dec0fc0..67415976a1e 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -252,6 +252,7 @@ void GlobalUI::drawScreenStates() {
return;
auto &drawQueue = g_engine->drawQueue();
+ int32 borderWidth;
if (_isPermanentFaded)
drawQueue.add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
else
Commit: 7ff8d738bbc46682822dc45ae5fc4bfe47e10ca9
https://github.com/scummvm/scummvm/commit/7ff8d738bbc46682822dc45ae5fc4bfe47e10ca9
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Add kernel task Disguise
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/script.cpp
engines/alcachofa/tasks.h
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 4977bbb0ee6..565025839ab 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -668,4 +668,65 @@ Task *Camera::shake(Process &process, Math::Vector2d amplitude, Math::Vector2d f
return new CamShakeTask(process, amplitude, frequency, duration);
}
+// The original name for this task is "disfraza" which I can only translate as "disguise"
+// It is a slightly bouncing vertical camera movement with fixed distance
+
+struct CamDisguiseTask final : public Task {
+ CamDisguiseTask(Process &process, int32 durationMs)
+ : Task(process)
+ , _camera(g_engine->camera())
+ , _durationMs(durationMs) {}
+
+ CamDisguiseTask(Process &process, Serializer &s)
+ : Task(process)
+ , _camera(g_engine->camera()) {
+ CamDisguiseTask::syncGame(s);
+ }
+
+ TaskReturn run() override {
+ if (_startTime == 0) {
+ _startPosition = { _camera.usedCenter().x(), _camera.usedCenter().y() };
+ _startTime = g_engine->getMillis();
+ }
+ if (_durationMs <= 0 || g_engine->getMillis() - _startTime >= (uint32)_durationMs)
+ return TaskReturn::finish(0);
+
+ Vector2d newPosition = _startPosition;
+ uint32 t = (g_engine->getMillis() - _startTime) * 5;
+ if (t <= 50)
+ newPosition.setY(_startPosition.getY() + t);
+ else if (t <= 150)
+ newPosition.setY(_startPosition.getY() - t + 100);
+ else if (t <= 200)
+ newPosition.setY(_startPosition.getY() + t - 200);
+ _camera.setPosition(newPosition);
+
+ return TaskReturn::yield();
+ }
+
+ void debugPrint() override {
+ g_engine->getDebugger()->debugPrintf("\"Disguise\" camera for %dms", _durationMs);
+ }
+
+ void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ s.syncAsSint32LE(_durationMs);
+ s.syncAsUint32LE(_startTime);
+ syncVector(s, _startPosition);
+ }
+
+ const char *taskName() const override;
+
+private:
+ Camera &_camera;
+ int32 _durationMs;
+ uint32 _startTime = 0;
+ Vector2d _startPosition;
+};
+DECLARE_TASK(CamDisguiseTask)
+
+Task *Camera::disguise(Process &process, int32 duration) {
+ return new CamDisguiseTask(process, duration);
+}
+
} // namespace Alcachofa
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 5a091cf2a06..f22a44bb6a2 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -36,6 +36,7 @@ static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
class Camera {
public:
+ inline Math::Vector3d usedCenter() const { return _cur._usedCenter; }
inline Math::Angle rotation() const { return _cur._rotation; }
inline Math::Vector2d &shake() { return _shake; }
inline WalkingCharacter *followTarget() { return _followTarget; }
@@ -73,6 +74,7 @@ public:
int32 duration, EasingType moveEasingType, EasingType scaleEasingType);
Task *waitToStop(Process &process);
Task *shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration);
+ Task *disguise(Process &process, int32 duration);
private:
friend struct CamLerpTask;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index d077f9e0229..405e16608a2 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -945,6 +945,9 @@ private:
as3D(pointObject->position()), targetScale,
getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
}
+ case ScriptKernelTask::Disguise:
+ // a somewhat bouncy vertical camera movement used in V1
+ return TaskReturn::waitFor(g_engine->camera().disguise(process(), getNumberArg(0)));
// Fades
case ScriptKernelTask::FadeType0:
diff --git a/engines/alcachofa/tasks.h b/engines/alcachofa/tasks.h
index 8c95f719033..976b2e38cab 100644
--- a/engines/alcachofa/tasks.h
+++ b/engines/alcachofa/tasks.h
@@ -38,6 +38,7 @@ DEFINE_TASK(CamLerpRotationTask)
DEFINE_TASK(CamShakeTask)
DEFINE_TASK(CamWaitToStopTask)
DEFINE_TASK(CamSetInactiveAttributeTask)
+DEFINE_TASK(CamDisguiseTask)
DEFINE_TASK(SayTextTask)
DEFINE_TASK(AnimateCharacterTask)
DEFINE_TASK(LerpLodBiasTask)
@@ -51,6 +52,6 @@ DEFINE_TASK(ScriptTimerTask)
DEFINE_TASK(ScriptTask)
DEFINE_TASK(PlaySoundTask)
DEFINE_TASK(WaitForMusicTask)
-DEFINE_TASK(DelayTask)
+DEFINE_TASK(DelayTask)
#undef DEFINE_TASK
Commit: 4d269d4ac13cf8004b3fa7c068599cbe50668336
https://github.com/scummvm/scummvm/commit/4d269d4ac13cf8004b3fa7c068599cbe50668336
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Fix regression with file-based animation refs
Changed paths:
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index d8650a6cad2..0214a41fd6c 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -418,34 +418,36 @@ public:
void missingAnimation(const String &fileName) override {
static const char *exemptions[] = {
- "ANIMACION.AN0",
- "DESPACHO_SUPER2_OL_SOMBRAS2.AN0",
- "PP_MORTA.AN0",
- "DESPACHO_SUPER2___FONDO_PP_SUPER.AN0",
- "ESTOMAGO.AN0",
- "CREDITOS.AN0",
- "MONITOR___OL_EFECTO_FONDO.AN0",
+ "ANIMACION",
+ "DESPACHO_SUPER2_OL_SOMBRAS2",
+ "PP_MORTA",
+ "DESPACHO_SUPER2___FONDO_PP_SUPER",
+ "ESTOMAGO",
+ "CREDITOS",
+ "MONITOR___OL_EFECTO_FONDO",
nullptr
};
// these only happen in the german demo
static const char *demoExemptions[] = {
- "TROZO_1.AN0",
- "TROZO_2.AN0",
- "TROZO_3.AN0",
- "TROZO_4.AN0",
- "TROZO_5.AN0",
- "TROZO_6.AN0",
- "NOTA_CINE_NEGRO.AN0",
- "PP_JOHN_WAYNE_2.AN0",
- "ARQUEOLOGO_ESTATICO_TIA.AN0",
- "ARQUEOLOGO_HABLANDO_TIA.AN0",
+ "TROZO_1",
+ "TROZO_2",
+ "TROZO_3",
+ "TROZO_4",
+ "TROZO_5",
+ "TROZO_6",
+ "NOTA_CINE_NEGRO",
+ "PP_JOHN_WAYNE_2",
+ "ARQUEOLOGO_ESTATICO_TIA",
+ "ARQUEOLOGO_HABLANDO_TIA",
nullptr
};
+ bool hasExtension = fileName.hasSuffixIgnoreCase(".AN0");
const auto isInExemptions = [&] (const char *const *const list) {
for (const char *const *exemption = list; *exemption != nullptr; exemption++) {
- if (fileName.equalsIgnoreCase(*exemption))
+ if ((hasExtension && fileName.hasPrefixIgnoreCase(*exemption)) ||
+ (!hasExtension && fileName.equalsIgnoreCase(*exemption)))
return true;
}
return false;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index a8f054dd000..600e98f1be3 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -169,28 +169,31 @@ void AnimationBase::load() {
} else {
// for real file paths we have to apply the folder and do some fallback
String fullPath;
- switch (_folder) {
- case AnimationFolder::Animations:
- fullPath = "Animaciones/";
- break;
- case AnimationFolder::Masks:
- fullPath = "Mascaras/";
- break;
- case AnimationFolder::Backgrounds:
- fullPath = "Fondos/";
- break;
- default:
- assert(false && "Invalid AnimationFolder");
- break;
- }
- fullPath += _fileRef._path;
- if (_fileRef._path.size() < 4 || scumm_strnicmp(_fileRef._path.end() - 4, ".AN0", 4) != 0)
- fullPath += ".AN0";
+ const auto getFullPath = [&] (AnimationFolder folder) {
+ switch (folder) {
+ case AnimationFolder::Animations:
+ fullPath = "Animaciones/";
+ break;
+ case AnimationFolder::Masks:
+ fullPath = "Mascaras/";
+ break;
+ case AnimationFolder::Backgrounds:
+ fullPath = "Fondos/";
+ break;
+ default:
+ assert(false && "Invalid AnimationFolder");
+ break;
+ }
+ fullPath += _fileRef._path;
+ if (_fileRef._path.size() < 4 || scumm_strnicmp(_fileRef._path.end() - 4, ".AN0", 4) != 0)
+ fullPath += ".AN0";
+ };
+ getFullPath(_folder);
File *file = new File();
if (!file->open(fullPath.c_str())) {
// original fallback
- fullPath = "Mascaras/" + _fileRef._path;
+ getFullPath(AnimationFolder::Masks);
if (!file->open(fullPath.c_str())) {
delete file;
file = nullptr;
Commit: d368ed8354ea9d90363d334cef85c37eed6b0269
https://github.com/scummvm/scummvm/commit/d368ed8354ea9d90363d334cef85c37eed6b0269
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Use game-specific video paths
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 65a4a1c1e0c..dbbbf8da484 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -145,7 +145,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
// Video files are either MPEG PS or AVI
FakeLock lock("playVideo", _eventLoopSemaphore);
File *file = new File();
- if (!file->open(Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1)))) {
+ if (!file->open(game().getVideoPath(videoId))) {
delete file;
game().invalidVideo(videoId, "open file");
return;
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 26da9514d3d..313993980bc 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -197,6 +197,10 @@ public:
g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
}
+ Path getVideoPath(int32 videoId) override {
+ return Path(String::format("disk1/Install/bin/data%02d.bin", videoId));
+ }
+
bool shouldClipCamera() override {
return true;
}
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 0214a41fd6c..1336aaefd1e 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -252,6 +252,10 @@ public:
script.variable("modored") = 0; // this is signalling whether a network connection is established
}
+ Path getVideoPath(int32 videoId) override {
+ return Path(String::format("Data/DATA%02d.BIN", videoId + 1));
+ }
+
bool shouldClipCamera() override {
return g_engine->script().variable("EncuadrarCamara");
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index fee0bd54216..ce7d6b1ac36 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -66,6 +66,7 @@ public:
virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
virtual void updateScriptVariables() = 0;
+ virtual Common::Path getVideoPath(int32 videoId) = 0;
virtual bool shouldClipCamera() = 0;
virtual bool doesRoomHaveBackground(const Room *room);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 600e98f1be3..02ffe98a899 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -94,7 +94,7 @@ ImageV1::ImageV1(SeekableReadStream &stream) {
}
ManagedSurface *ImageV1::render(byte alpha) const {
- ManagedSurface *surface = new ManagedSurface(_size.x, _size.y, BlendBlit::getSupportedPixelFormat());
+ ManagedSurface *surface = new ManagedSurface(_size.x, _size.y, g_engine->renderer().getPixelFormat());
render(*surface, {}, alpha);
return surface;
}
Commit: 5108587f16ca346400ac6eb861749777149a6ec2
https://github.com/scummvm/scummvm/commit/5108587f16ca346400ac6eb861749777149a6ec2
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Use game-specific sound paths
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 313993980bc..e12889f7e5c 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -201,6 +201,16 @@ public:
return Path(String::format("disk1/Install/bin/data%02d.bin", videoId));
}
+ String getSoundPath(const char *filename) override {
+ return filename;
+ }
+
+ String getMusicPath(int32 trackId) override {
+ const Room *room = g_engine->player().currentRoom();
+ const int diskId = room != nullptr && room->mapIndex() == 1 ? 2 : 1;
+ return String::format("disk%d/track%02d", diskId, trackId);
+ }
+
bool shouldClipCamera() override {
return true;
}
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 1336aaefd1e..8991eb8ead7 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -256,6 +256,14 @@ public:
return Path(String::format("Data/DATA%02d.BIN", videoId + 1));
}
+ String getSoundPath(const char *filename) override {
+ return String("Sonidos/") + filename;
+ }
+
+ String getMusicPath(int32 trackId) override {
+ return String::format("Sonidos/T%d", trackId);
+ }
+
bool shouldClipCamera() override {
return g_engine->script().variable("EncuadrarCamara");
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index ce7d6b1ac36..6f94414c396 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -67,6 +67,8 @@ public:
virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
virtual void updateScriptVariables() = 0;
virtual Common::Path getVideoPath(int32 videoId) = 0;
+ virtual Common::String getSoundPath(const char *filename) = 0; ///< Without file-extension
+ virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
virtual bool shouldClipCamera() = 0;
virtual bool doesRoomHaveBackground(const Room *room);
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 8c133bea73f..29e4569c981 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -32,9 +32,13 @@ using namespace Common;
namespace Alcachofa {
-Room::Room(World *world) : _world(world) {}
+Room::Room(World *world)
+ : _world(world)
+ , _mapIndex(world->loadedMapCount()) {}
-Room::Room(World *world, SeekableReadStream &stream) : _world(world) {
+Room::Room(World *world, SeekableReadStream &stream)
+ : _world(world)
+ , _mapIndex(world->loadedMapCount()) {
if (g_engine->isV1())
readRoomV1(stream, true);
else
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 93e91aaba07..a07acce1567 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -35,6 +35,7 @@ public:
virtual ~Room();
inline World &world() { return *_world; }
+ inline uint8 mapIndex() const { return _mapIndex; }
inline const Common::String &name() const { return _name; }
inline const PathFindingShape *activeFloor() const {
return _activeFloorI < 0 ? nullptr : &_floors[_activeFloorI];
@@ -85,6 +86,7 @@ protected:
int8 _activeFloorI = -1;
int _musicId = -1;
uint8
+ _mapIndex,
_characterAlphaTint = 0,
_characterAlphaPremultiplier = 100; ///< for some reason in percent instead of 0-255
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index b26795fc006..50a028ff5d1 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -112,26 +112,27 @@ static AudioStream *loadSND(File *file) {
}
}
-static AudioStream *openAudio(const char *fileName) {
- String path = String::format("Sonidos/%s.SND", fileName);
+static AudioStream *openAudio(const String &basePath) {
+ String path = basePath + ".SND";
File *file = new File();
if (file->open(path.c_str()))
return file->size() == 0 // Movie Adventure has some null-size audio files, they are treated like infinite silence
? makeSilentAudioStream(8000, false)
: loadSND(file);
+
path.setChar('W', path.size() - 3);
path.setChar('A', path.size() - 2);
path.setChar('V', path.size() - 1);
if (file->open(path.c_str()))
return makeWAVStream(file, DisposeAfterUse::YES);
- delete file;
- g_engine->game().missingSound(fileName);
+ delete file;
+ g_engine->game().missingSound(basePath);
return nullptr;
}
-SoundHandle Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::SoundType type) {
- AudioStream *stream = openAudio(fileName);
+SoundHandle Sounds::playSoundInternal(const String &path, byte volume, Mixer::SoundType type) {
+ AudioStream *stream = openAudio(path);
if (stream == nullptr && (type == Mixer::kSpeechSoundType || type == Mixer::kMusicSoundType)) {
/* If voice files are missing, the player could still read the subtitle
* For this we return infinite silent audio which the user has to skip
@@ -199,12 +200,14 @@ SoundHandle Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::
SoundHandle Sounds::playVoice(const String &fileName, byte volume) {
debugC(1, kDebugSounds, "Play voice: %s at %d", fileName.c_str(), (int)volume);
- return playSoundInternal(fileName.c_str(), volume, Mixer::kSpeechSoundType);
+ auto path = g_engine->game().getSoundPath(fileName.c_str());
+ return playSoundInternal(path.c_str(), volume, Mixer::kSpeechSoundType);
}
SoundHandle Sounds::playSFX(const String &fileName, byte volume) {
debugC(1, kDebugSounds, "Play SFX: %s at %d", fileName.c_str(), (int)volume);
- return playSoundInternal(fileName.c_str(), volume, Mixer::kSFXSoundType);
+ auto path = g_engine->game().getSoundPath(fileName.c_str());
+ return playSoundInternal(path.c_str(), volume, Mixer::kSFXSoundType);
}
void Sounds::stopAll() {
@@ -305,10 +308,8 @@ void Sounds::startMusic(int musicId) {
debugC(2, kDebugSounds, "startMusic %d", musicId);
assert(musicId >= 0);
fadeMusic();
- constexpr size_t kBufferSize = 16;
- char filenameBuffer[kBufferSize];
- snprintf(filenameBuffer, kBufferSize, "T%d", musicId);
- _musicSoundID = playSoundInternal(filenameBuffer, Mixer::kMaxChannelVolume, Mixer::kMusicSoundType);
+ auto path = g_engine->game().getMusicPath(musicId);
+ _musicSoundID = playSoundInternal(path, Mixer::kMaxChannelVolume, Mixer::kMusicSoundType);
_isMusicPlaying = true;
_nextMusicID = musicId;
}
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 693fd1a934a..a6577b0b9ee 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -72,7 +72,7 @@ private:
Common::Array<int16> _samples; ///< might not be filled, only voice samples are preloaded for lip-sync
};
Playback *getPlaybackById(SoundHandle id);
- SoundHandle playSoundInternal(const char *fileName, byte volume, Audio::Mixer::SoundType type);
+ SoundHandle playSoundInternal(const Common::String &path, byte volume, Audio::Mixer::SoundType type);
Common::Array<Playback> _playbacks;
Audio::Mixer *_mixer;
Commit: e8e352c20beb00849f314da5da3adbe4cd5661b5
https://github.com/scummvm/scummvm/commit/e8e352c20beb00849f314da5da3adbe4cd5661b5
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Fix V1 image rendering
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 02ffe98a899..b431223d5fd 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -125,8 +125,10 @@ void ImageV1::render(ManagedSurface &target, Point dstOffset, byte alpha) const
dstX += segment._xOffset;
if (dstX >= target.w)
break;
- if (dstX + segment._width < 0)
+ if (dstX + segment._width < 0) {
+ dstX += segment._width;
continue;
+ }
uint srcPixelI = segment._dataOffset;
if (dstX < 0)
srcPixelI += -dstX * bpp;
@@ -137,10 +139,11 @@ void ImageV1::render(ManagedSurface &target, Point dstOffset, byte alpha) const
const byte* srcPixel = _palette.empty()
? _pixels.data() + srcPixelI
: _palette.data() + 3 * _pixels[srcPixelI];
- *dstPixel = target.format.ARGBToColor(alpha, srcPixel[0], srcPixel[1], srcPixel[2]);
+ *dstPixel = target.format.ARGBToColor(alpha, srcPixel[2], srcPixel[1], srcPixel[0]);
dstPixel++;
srcPixelI += bpp;
}
+ dstX += segment._width;
}
}
}
Commit: 1d69d82ed2f99b98201484ab750dbf6947737e2b
https://github.com/scummvm/scummvm/commit/1d69d82ed2f99b98201484ab750dbf6947737e2b
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:26+01:00
Commit Message:
ALCACHOFA: Disable cursor frame update on V1
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index fe0e4651abe..208df698250 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -51,7 +51,9 @@ void Player::resetCursor() {
}
void Player::updateCursor() {
- if (g_engine->menu().isOpen() || !_isGameLoaded)
+ if (g_engine->isV1())
+ _cursorFrameI = 0;
+ else if (g_engine->menu().isOpen() || !_isGameLoaded)
_cursorFrameI = 0;
else if (_selectedObject == nullptr)
_cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
Commit: ff4195f43ec18448ed3786b9288351b28e6a406e
https://github.com/scummvm/scummvm/commit/ff4195f43ec18448ed3786b9288351b28e6a406e
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:27+01:00
Commit Message:
ALCACHOFA: Adapt V1 isAllowedToOpenMenu
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index e12889f7e5c..dd9c92fb060 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -215,6 +215,10 @@ public:
return true;
}
+ bool isAllowedToOpenMenu() override {
+ return dynamic_cast<RoomWithFloor *>(g_engine->player().currentRoom()) != nullptr;
+ }
+
void missingAnimation(const String &fileName) override {
static const char *exemptions[] = {
nullptr
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 8991eb8ead7..7f1e4469592 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -268,6 +268,10 @@ public:
return g_engine->script().variable("EncuadrarCamara");
}
+ bool isAllowedToOpenMenu() override {
+ return !g_engine->script().variable("prohibirESC");
+ }
+
void onLoadedGameFiles() override {
// this notifies the script whether we are a demo
if (g_engine->world().loadedMapCount() == 2)
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 6f94414c396..63d3833238a 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -70,6 +70,7 @@ public:
virtual Common::String getSoundPath(const char *filename) = 0; ///< Without file-extension
virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
virtual bool shouldClipCamera() = 0;
+ virtual bool isAllowedToOpenMenu() = 0; ///< only the game-specific condition
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 208df698250..6d87fff0e7b 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -352,7 +352,7 @@ bool Player::isAllowedToOpenMenu() {
isGameLoaded() &&
!g_engine->menu().isOpen() &&
g_engine->sounds().musicSemaphore().isReleased() &&
- !g_engine->script().variable("prohibirESC") &&
+ g_engine->game().isAllowedToOpenMenu() &&
!_isInTemporaryRoom; // we cannot reliably store this state across multiple room changes
}
Commit: 1deb9a115627afbebea271238fe6029ebac80eb9
https://github.com/scummvm/scummvm/commit/1deb9a115627afbebea271238fe6029ebac80eb9
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:27+01:00
Commit Message:
ALCACHOFA: Adapt different MainCharacterKind values in V1
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 405e16608a2..63b5fe7adb6 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -442,7 +442,7 @@ private:
_script.variable("m_o_f") = (int32)process().character();
_script.variable("m_o_f_real") = (int32)g_engine->player().activeCharacterKind();
} else
- _script.variable("m_o_f") = (int32)g_engine->player().activeCharacterKind();
+ _script.variable("m_o_f") = (int32)g_engine->player().activeCharacterKind() - 1; // there is no "None" character kind in V1
}
void handleReturnFromKernelCall(int32 returnValue) {
@@ -540,6 +540,23 @@ private:
return entry._number;
}
+ MainCharacterKind getMainCharacterKindArg(uint argI) {
+ int32 value = getNumberArg(argI);
+ if (g_engine->isV3()) {
+ if (value < 0 || value > 2)
+ error("Unexpected value for main character kind: %d", value);
+ else
+ return (MainCharacterKind)value;
+ }
+ else {
+ if (value < 0 || value > 1)
+ error("Unexpected value for main character kind: %d", value);
+ return value == 0
+ ? MainCharacterKind::Mortadelo
+ : MainCharacterKind::Filemon;
+ }
+ }
+
const char *getStringArg(uint argI) {
auto entry = getArg(argI);
if (entry._type != StackEntryType::String)
@@ -638,12 +655,12 @@ private:
g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
return TaskReturn::finish(0); // 0 means this is the forking process
case ScriptKernelTask::KillProcesses:
- killProcessesFor((MainCharacterKind)getNumberArg(0));
+ killProcessesFor(getMainCharacterKindArg(0));
return TaskReturn::finish(1);
// player/world state changes
case ScriptKernelTask::ChangeCharacter: {
- MainCharacterKind kind = (MainCharacterKind)getNumberArg(0);
+ MainCharacterKind kind = getMainCharacterKindArg(0);
killProcessesFor(MainCharacterKind::None); // yes, kill for all characters
auto &camera = g_engine->camera();
auto &player = g_engine->player();
@@ -846,7 +863,7 @@ private:
relatedCharacter().pickup(getStringArg(0), !getNumberArg(1));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterPickup: {
- auto &character = g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(1));
+ auto &character = g_engine->world().getMainCharacterByKind(getMainCharacterKindArg(1));
character.pickup(getStringArg(0), !getNumberArg(2));
return TaskReturn::finish(1);
}
@@ -854,12 +871,12 @@ private:
relatedCharacter().drop(getStringArg(0));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterDrop: {
- auto &character = g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(1));
+ auto &character = g_engine->world().getMainCharacterByKind(getMainCharacterKindArg(1));
character.drop(getOptionalStringArg(0));
return TaskReturn::finish(1);
}
case ScriptKernelTask::ClearInventory:
- switch ((MainCharacterKind)getNumberArg(0)) {
+ switch (getMainCharacterKindArg(0)) {
case MainCharacterKind::Mortadelo:
g_engine->world().mortadelo().clearInventory();
break;
@@ -877,8 +894,9 @@ private:
return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
case ScriptKernelTask::CamFollow: {
WalkingCharacter *target = nullptr;
- if (getNumberArg(0) != 0)
- target = &g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(0));
+ auto kind = getMainCharacterKindArg(0);
+ if (kind != MainCharacterKind::None)
+ target = &g_engine->world().getMainCharacterByKind(kind);
g_engine->camera().setFollow(target, getNumberArg(1) != 0);
return TaskReturn::finish(1);
}
Commit: f880658010ab8a3f2e3985f6474bcc73443bc8ba
https://github.com/scummvm/scummvm/commit/f880658010ab8a3f2e3985f6474bcc73443bc8ba
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:27+01:00
Commit Message:
ALCACHOFA: Adapt V1 text layout and rendering
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index b431223d5fd..afbf83b70b5 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -630,7 +630,33 @@ void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d size, Vector
renderer.quad(as2D(topLeft), size, kWhite, rotation, texMin + texOffset, texMax + texOffset);
}
-Font::Font(GameFileReference fileRef) : AnimationBase(move(fileRef)) {}
+Font::Font(GameFileReference fileRef)
+ : AnimationBase(move(fileRef))
+ , _charToImage(g_engine->isV1() ? 33 : 32)
+ , _spaceImageI(g_engine->isV1() ? 94 : 0)
+ , _charSpacing(g_engine->isV1() ? 3 : 0) {}
+
+static void fixFontAtlasColors(ManagedSurface &surface) {
+ // In V1 the font contains green and black pixels where
+ // - black pixels should stay black
+ // - green pixels should be the text color
+ // The image segments determine the alpha.
+ // For rendering in OpenGL we want the green pixels to be white
+ // so multiplication with the text color still works (BlendMode::Tinted)
+ assert(surface.format.bytesPerPixel == 4);
+ const uint32 alphaMask = uint32(255) << surface.format.aShift;
+ const uint32 black = alphaMask;
+ const uint32 white = ~0;
+
+ for (int16 y = 0; y < surface.h; y++) {
+ uint32 *pixel = (uint32 *)surface.getBasePtr(0, y);
+ for (int16 x = 0; x < surface.w; x++, pixel++) {
+ auto alpha = *pixel & alphaMask;
+ *pixel = !alpha ? *pixel
+ : (*pixel & ~alphaMask) ? white : black;
+ }
+ }
+}
void Font::load() {
if (_isLoaded)
@@ -668,6 +694,9 @@ void Font::load() {
_texMaxs[i].setX((offsetX + _images[i]->w) * invWidth);
_texMaxs[i].setY((offsetY + _images[i]->h) * invHeight);
}
+ if (g_engine->isV1())
+ fixFontAtlasColors(atlasSurface);
+
_texture = g_engine->renderer().createTexture(atlasSurface.w, atlasSurface.h, false);
_texture->update(atlasSurface);
debugCN(1, kDebugGraphics, "Rendered font atlas %s at %dx%d", _fileRef._path.c_str(), atlasSurface.w, atlasSurface.h);
@@ -682,9 +711,15 @@ void Font::freeImages() {
_texMaxs.clear();
}
-void Font::drawCharacter(int32 imageI, Point centerPoint, Color color) {
- assert(imageI >= 0 && (uint)imageI < _images.size());
- Vector2d center = as2D(centerPoint + _imageOffsets[imageI]);
+void Font::drawCharacter(byte ch, Point centerPoint, Color color) {
+ if (!isVisibleChar(ch))
+ return;
+
+ int32 imageI = ch - _charToImage;
+ Point offset = g_engine->isV1()
+ ? Point(0, spaceSize().y - _images[imageI]->h)
+ : _imageOffsets[imageI];
+ Vector2d center = as2D(centerPoint + offset);
Vector2d size(_images[imageI]->w, _images[imageI]->h);
auto &renderer = g_engine->renderer();
@@ -693,6 +728,22 @@ void Font::drawCharacter(int32 imageI, Point centerPoint, Color color) {
renderer.quad(center, size, color, Angle(), _texMins[imageI], _texMaxs[imageI]);
}
+bool Font::isVisibleChar(byte ch) const {
+ return ch >= _charToImage &&
+ (uint)(ch - _charToImage) < _images.size() &&
+ ch - _charToImage != _spaceImageI;
+}
+
+Point Font::characterSize(byte ch) const {
+ return isVisibleChar(ch)
+ ? imageSize(ch - _charToImage) + Point(_charSpacing, 0)
+ : imageSize(_spaceImageI);
+}
+
+Point Font::spaceSize() const {
+ return imageSize(_spaceImageI);
+}
+
Graphic::Graphic() {}
Graphic::Graphic(SeekableReadStream &stream) {
@@ -870,14 +921,6 @@ static const byte *trimTrailing(const byte *text, const byte *begin, bool trimSp
return text;
}
-static Point characterSize(const Font &font, byte ch) {
- if (ch <= ' ' || (uint)(ch - ' ') >= font.imageCount())
- ch = 0;
- else
- ch -= ' ';
- return font.imageSize(ch);
-}
-
TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos, int maxWidth, bool centered, Color color, int8 order)
: IDrawRequest(order)
, _font(font)
@@ -904,7 +947,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
}
if (*itChar != '\r' && *itChar)
- lineWidth += characterSize(font, *itChar).x;
+ lineWidth += font.characterSize(*itChar).x;
if (lineWidth <= maxWidth && *itChar != '\r' && *itChar) {
itChar++;
continue;
@@ -936,7 +979,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
lineWidth = 0;
for (auto ch : _lines[i]) {
if (ch != '\r' && ch)
- lineWidth += characterSize(font, ch).x;
+ lineWidth += font.characterSize(ch).x;
}
_posX[i] = lineWidth;
_width = MAX(_width, lineWidth);
@@ -954,7 +997,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
fill(_posX.begin(), _posX.end(), pos.x);
// setup height and y position
- _height = (int)lineCount * (font.imageSize(0).y * 4 / 3);
+ _height = (int)lineCount * (font.spaceSize().y * 4 / 3);
_posY = pos.y;
if (centered)
_posY -= _height / 2;
@@ -965,17 +1008,15 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
}
void TextDrawRequest::draw() {
- const Point spaceSize = _font.imageSize(0);
Point cursor(0, _posY);
for (uint i = 0; i < _lines.size(); i++) {
cursor.x = _posX[i];
for (auto ch : _lines[i]) {
- const Point charSize = characterSize(_font, ch);
- if (ch > ' ' && (uint)(ch - ' ') < _font.imageCount())
- _font.drawCharacter(ch - ' ', Point(cursor.x, cursor.y), _color);
+ const Point charSize = _font.characterSize(ch);
+ _font.drawCharacter(ch, Point(cursor.x, cursor.y), _color);
cursor.x += charSize.x;
}
- cursor.y += spaceSize.y * 4 / 3;
+ cursor.y += _font.spaceSize().y * 4 / 3;
}
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 384f35a312f..55649d22100 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -276,13 +276,17 @@ public:
void load();
void freeImages();
- void drawCharacter(int32 imageI, Common::Point center, Color color);
+ void drawCharacter(byte ch, Common::Point center, Color color);
using AnimationBase::isLoaded;
using AnimationBase::imageSize;
+ bool isVisibleChar(byte ch) const;
+ Common::Point characterSize(byte ch) const;
+ Common::Point spaceSize() const;
inline uint imageCount() const { return _images.size(); }
private:
+ const byte _charToImage, _spaceImageI, _charSpacing;
Common::Array<Math::Vector2d> _texMins, _texMaxs;
Common::ScopedPtr<ITexture> _texture;
};
Commit: 783301dbcf30b1d7f2c561448ba894530d214174
https://github.com/scummvm/scummvm/commit/783301dbcf30b1d7f2c561448ba894530d214174
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:27+01:00
Commit Message:
ALCACHOFA: Fix room bounds for V1
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 565025839ab..6e72525ed29 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -37,6 +37,13 @@ void Camera::resetRotationAndScale() {
_cur._usedCenter.z() = 0;
}
+void Camera::setRoomBounds(Point min, Point size, int16 bgScale) {
+ float scaleFactor = bgScale * kInvBaseScale;
+ _roomMin = as2D(min) * scaleFactor;
+ _roomMax = _roomMin + as2D(size) * scaleFactor;
+ _roomScale = 0.0f;
+}
+
void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
float scaleFactor = 1 - bgScale * kInvBaseScale;
_roomMin = Vector2d(
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index f22a44bb6a2..a5a1573e5a3 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -46,7 +46,8 @@ public:
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
Common::Point transform3Dto2D(Common::Point p) const;
void resetRotationAndScale();
- void setRoomBounds(Common::Point bgSize, int16 bgScale);
+ void setRoomBounds(Common::Point min, Common::Point size, int16 bgScale); ///< Used in V1
+ void setRoomBounds(Common::Point bgSize, int16 bgScale); ///< Used in V3
void setFollow(WalkingCharacter *target, bool catchUp = false);
void setPosition(Math::Vector2d v);
void setPosition(Math::Vector3d v);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index afbf83b70b5..076ac56be8b 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -568,6 +568,15 @@ void Animation::prerenderFrame(int32 frameI) {
_renderedPremultiplyAlpha = _premultiplyAlpha;
}
+struct TexCoords {
+ TexCoords(const Rect &inner, int16 outerW, int16 outerH) {
+ _min = Vector2d(0.5f / outerW, 0.5f / outerH);
+ _max = Vector2d((inner.width() - 0.5f) / outerW, (inner.height() - 0.5f) / outerH);
+ }
+
+ Vector2d _min, _max;
+};
+
void Animation::outputRect2D(int32 frameI, float scale, Vector2d &topLeft, Vector2d &size) const {
auto bounds = frameBounds(frameI);
topLeft += as2D(totalFrameOffset(frameI)) * scale;
@@ -577,8 +586,7 @@ void Animation::outputRect2D(int32 frameI, float scale, Vector2d &topLeft, Vecto
void Animation::draw2D(int32 frameI, Vector2d topLeft, float scale, BlendMode blendMode, Color color) {
prerenderFrame(frameI);
auto bounds = frameBounds(frameI);
- Vector2d texMin(0, 0);
- Vector2d texMax((float)bounds.width() / _renderedSurface.w, (float)bounds.height() / _renderedSurface.h);
+ TexCoords tex(bounds, _renderedSurface.w, _renderedSurface.h);
Vector2d size;
outputRect2D(frameI, scale, topLeft, size);
@@ -586,7 +594,7 @@ void Animation::draw2D(int32 frameI, Vector2d topLeft, float scale, BlendMode bl
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
renderer.setBlendMode(blendMode);
- renderer.quad(topLeft, size, color, Angle(), texMin, texMax);
+ renderer.quad(topLeft, size, color, Angle(), tex._min, tex._max);
}
void Animation::outputRect3D(int32 frameI, float scale, Vector3d &topLeft, Vector2d &size) const {
@@ -599,8 +607,7 @@ void Animation::outputRect3D(int32 frameI, float scale, Vector3d &topLeft, Vecto
void Animation::draw3D(int32 frameI, Vector3d topLeft, float scale, BlendMode blendMode, Color color) {
prerenderFrame(frameI);
auto bounds = frameBounds(frameI);
- Vector2d texMin(0, 0);
- Vector2d texMax((float)bounds.width() / _renderedSurface.w, (float)bounds.height() / _renderedSurface.h);
+ TexCoords tex(bounds, _renderedSurface.w, _renderedSurface.h);
Vector2d size;
outputRect3D(frameI, scale, topLeft, size);
@@ -609,14 +616,13 @@ void Animation::draw3D(int32 frameI, Vector3d topLeft, float scale, BlendMode bl
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
renderer.setBlendMode(blendMode);
- renderer.quad(as2D(topLeft), size, color, rotation, texMin, texMax);
+ renderer.quad(as2D(topLeft), size, color, rotation, tex._min, tex._max);
}
void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d size, Vector2d texOffset, BlendMode blendMode) {
prerenderFrame(frameI);
auto bounds = frameBounds(frameI);
- Vector2d texMin(0, 0);
- Vector2d texMax((float)bounds.width() / _renderedSurface.w, (float)bounds.height() / _renderedSurface.h);
+ TexCoords tex(bounds, _renderedSurface.w, _renderedSurface.h);
topLeft += as3D(totalFrameOffset(frameI));
topLeft = g_engine->camera().transform3Dto2D(topLeft);
@@ -627,7 +633,7 @@ void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d size, Vector
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
renderer.setBlendMode(blendMode);
- renderer.quad(as2D(topLeft), size, kWhite, rotation, texMin + texOffset, texMax + texOffset);
+ renderer.quad(as2D(topLeft), size, kWhite, rotation, tex._min + texOffset, tex._max + texOffset);
}
Font::Font(GameFileReference fileRef)
@@ -646,7 +652,7 @@ static void fixFontAtlasColors(ManagedSurface &surface) {
assert(surface.format.bytesPerPixel == 4);
const uint32 alphaMask = uint32(255) << surface.format.aShift;
const uint32 black = alphaMask;
- const uint32 white = ~0;
+ const uint32 white = ~uint32(0);
for (int16 y = 0; y < surface.h; y++) {
uint32 *pixel = (uint32 *)surface.getBasePtr(0, y);
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 29e4569c981..90ce2e9f843 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -39,10 +39,13 @@ Room::Room(World *world)
Room::Room(World *world, SeekableReadStream &stream)
: _world(world)
, _mapIndex(world->loadedMapCount()) {
- if (g_engine->isV1())
- readRoomV1(stream, true);
+ if (g_engine->isV1()) {
+ readRoomV1(stream);
+ readObjects(stream);
+ }
else
readRoomV3(stream, false);
+ initBackground();
}
static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadStream &stream) {
@@ -98,20 +101,12 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return nullptr; // handled in Room::Room
}
-void Room::readRoomV1(SeekableReadStream &stream, bool shouldReadObjects) {
+void Room::readRoomV1(SeekableReadStream &stream) {
_name = readVarString(stream);
_backgroundName = readVarString(stream);
_musicId = (int)stream.readByte();
_characterAlphaTint = stream.readByte();
skipVarString(stream);
-
- if (shouldReadObjects)
- readObjects(stream, kBaseScale);
-
- if (!_backgroundName.empty() && g_engine->game().doesRoomHaveBackground(this)) {
- // in V1 _backgroundName refers to an object which represents the background
- _backgroundObject = getObjectByName(_backgroundName.c_str());
- }
}
void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
@@ -119,7 +114,7 @@ void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
_backgroundName = _name;
_musicId = (int)stream.readByte();
_characterAlphaTint = stream.readByte();
- auto backgroundScale = stream.readSint16LE();
+ _backgroundScale = stream.readSint16LE();
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
_fixedCameraOnEntering = readBool(stream);
@@ -128,16 +123,25 @@ void Room::readRoomV3(SeekableReadStream &stream, bool hasUselessByte) {
if (hasUselessByte)
stream.readByte();
- readObjects(stream, backgroundScale);
+ readObjects(stream);
+}
- if (g_engine->game().doesRoomHaveBackground(this)) {
- // in V3 _background is the name of an animation, we have to create the object
- _backgroundObject = new Background(this, _backgroundName, backgroundScale);
+void Room::initBackground() {
+ if (!g_engine->game().doesRoomHaveBackground(this))
+ return;
+
+ if (g_engine->isV1()) {
+ // in V1 _backgroundName refers to an object which represents the background
+ if (!_backgroundName.empty())
+ _backgroundObject = getObjectByName(_backgroundName.c_str());
+ } else {
+ // in V3 _backgroundName is the name of an animation, we have to create the object
+ _backgroundObject = new Background(this, _backgroundName, _backgroundScale);
_objects.push_back(_backgroundObject);
}
}
-void Room::readObjects(SeekableReadStream &stream, int16 backgroundScale) {
+void Room::readObjects(SeekableReadStream &stream) {
uint32 objectEnd = stream.readUint32LE();
while (objectEnd > 0) {
const auto type = readVarString(stream);
@@ -161,10 +165,11 @@ void Room::readObjects(SeekableReadStream &stream, int16 backgroundScale) {
}
RoomWithFloor::RoomWithFloor(World *world, SeekableReadStream &stream) : Room(world) {
- readRoomV1(stream, false);
+ readRoomV1(stream);
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
- readObjects(stream, kBaseScale);
+ readObjects(stream);
+ initBackground();
}
Room::~Room() {
@@ -268,13 +273,20 @@ void Room::updateRoomBounds() {
auto graphic = _backgroundObject == nullptr ? nullptr : _backgroundObject->graphic();
if (graphic != nullptr) {
auto bgSize = graphic->animation().imageSize(0);
- /* This fixes a bug where if the background image is invalid the original engine
- * would not update the background size. This would be around 1024,768 due to
- * previous rooms in the bug instances I found.
- */
- if (bgSize == Point(0, 0))
- bgSize = Point(1024, 768);
- g_engine->camera().setRoomBounds(bgSize, graphic->scale());
+ if (g_engine->isV1()) {
+ g_engine->camera().setRoomBounds(
+ graphic->topLeft() + Point(400, 300),
+ bgSize - Point(800, 600),
+ graphic->scale());
+ } else {
+ /* The fallback fixes a bug where if the background image is invalid the original engine
+ * would not update the background size. This would be around 1024,768 due to
+ * previous rooms in the bug instances I found.
+ */
+ if (bgSize == Point(0, 0))
+ bgSize = Point(1024, 768);
+ g_engine->camera().setRoomBounds(bgSize, graphic->scale());
+ }
}
}
@@ -366,6 +378,7 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream) : Room(world) {
readRoomV3(stream, true);
+ initBackground();
}
bool OptionsMenu::updateInput() {
@@ -401,19 +414,22 @@ void OptionsMenu::clearLastSelectedObject() {
ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream) : Room(world) {
readRoomV3(stream, true);
+ initBackground();
}
ListenMenu::ListenMenu(World *world, SeekableReadStream &stream) : Room(world) {
readRoomV3(stream, true);
+ initBackground();
}
Inventory::Inventory(World *world, SeekableReadStream &stream) : Room(world) {
if (g_engine->isV1()) {
- readRoomV1(stream, false);
+ readRoomV1(stream);
stream.skip(1); // denoted as "sinusar" but unused
- readObjects(stream, kBaseScale);
+ readObjects(stream);
} else
readRoomV3(stream, true);
+ initBackground();
}
Inventory::~Inventory() {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index a07acce1567..3d3f9fda9ca 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -67,9 +67,10 @@ public:
protected:
Room(World *world);
- void readRoomV1(Common::SeekableReadStream &stream, bool readObjects);
+ void readRoomV1(Common::SeekableReadStream &stream);
void readRoomV3(Common::SeekableReadStream &stream, bool hasUselessByte);
- void readObjects(Common::SeekableReadStream &stream, int16 backgroundScale);
+ void readObjects(Common::SeekableReadStream &stream);
+ void initBackground();
void updateScripts();
void updateRoomBounds();
void updateInteraction();
@@ -85,6 +86,7 @@ protected:
bool _fixedCameraOnEntering = false;
int8 _activeFloorI = -1;
int _musicId = -1;
+ int16 _backgroundScale = kBaseScale;
uint8
_mapIndex,
_characterAlphaTint = 0,
Commit: d182cef022b91b836040f928befa2c10ef0849a3
https://github.com/scummvm/scummvm/commit/d182cef022b91b836040f928befa2c10ef0849a3
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:27+01:00
Commit Message:
ALCACHOFA: Fix faulty rebase
Changed paths:
engines/alcachofa/alcachofa.h
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/global-ui.cpp
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 04386c5e1af..8e25ea0a865 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -117,10 +117,6 @@ public:
inline bool isV3() const { return gameDescription().isVersionBetween(30, 39); }
inline const AlcachofaGameDescription &gameDescription() const { return *_gameDescription; }
- inline bool isV1() const { return _gameDescription->engineVersion == EngineVersion::V1; }
- inline bool isV2() const { return _gameDescription->engineVersion == EngineVersion::V2; }
- inline bool isV3() const { return _gameDescription->engineVersion == EngineVersion::V3; }
-
inline IRenderer &renderer() { return *_renderer; }
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 7f1e4469592..79f7fb6fe6f 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -241,17 +241,6 @@ public:
script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
}
- 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.setScriptTimer(!script.variable("CalcularTiempoSinPulsarRaton"));
- script.variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
- script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
- script.variable("modored") = 0; // this is signalling whether a network connection is established
- }
-
Path getVideoPath(int32 videoId) override {
return Path(String::format("Data/DATA%02d.BIN", videoId + 1));
}
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index a77b0ca7f73..46767b7b393 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -207,9 +207,10 @@ void Game::invalidVideo(int32 videoId, const char *context) {
Game *Game::create() {
const auto &desc = g_engine->gameDescription();
switch (desc.engineVersion) {
- case EngineVersion::V1:
+ case EngineVersion::V1_0:
return createForMovieAdventureOriginal();
- case EngineVersion::V3:
+ case EngineVersion::V3_0:
+ case EngineVersion::V3_1:
return createForMovieAdventureSpecial();
default:
error("Unsupported or invalid engine version: %d", (int)desc.engineVersion);
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 63d3833238a..9cfd282cd96 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -56,16 +56,12 @@ public:
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 const char *getDialogFileName() = 0;
virtual const char *getObjectFileName() = 0;
virtual char getTextFileKey() = 0;
virtual const char *getInitScriptName() = 0;
- virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
- virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
- virtual void updateScriptVariables() = 0;
virtual Common::Path getVideoPath(int32 videoId) = 0;
virtual Common::String getSoundPath(const char *filename) = 0; ///< Without file-extension
virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 67415976a1e..4164dec0fc0 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -252,7 +252,6 @@ void GlobalUI::drawScreenStates() {
return;
auto &drawQueue = g_engine->drawQueue();
- int32 borderWidth;
if (_isPermanentFaded)
drawQueue.add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
else
Commit: d10f4db574f7a6108937b0971c2ecd5baac81635
https://github.com/scummvm/scummvm/commit/d10f4db574f7a6108937b0971c2ecd5baac81635
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:27+01:00
Commit Message:
ALCACHOFA: Remove unused isGameLoaded variable
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 4164dec0fc0..f58cfc42839 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -79,7 +79,7 @@ void GlobalUI::updateClosingInventory() {
bool GlobalUI::updateOpeningInventory() {
static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
- if (g_engine->menu().isOpen() || !g_engine->player().isGameLoaded())
+ if (g_engine->menu().isOpen())
return false;
const bool userWantsToOpenInventory =
@@ -127,7 +127,6 @@ bool GlobalUI::isHoveringChangeButton() const {
bool GlobalUI::updateChangingCharacter() {
auto &player = g_engine->player();
if (g_engine->menu().isOpen() ||
- !player.isGameLoaded() ||
_isOpeningInventory)
return false;
_changeButton.frameI() = 0;
@@ -164,7 +163,6 @@ bool GlobalUI::updateChangingCharacter() {
void GlobalUI::drawChangingButton() {
auto &player = g_engine->player();
if (g_engine->menu().isOpen() ||
- !player.isGameLoaded() ||
!player.semaphore().isReleased() ||
_isOpeningInventory ||
_isClosingInventory)
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 7749c49bab0..75ff0c669d7 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -215,7 +215,6 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
break;
case MainMenuAction::NewGame:
// this action might be unused just like the only room it would appear: MENUPRINCIPALINICIO
- g_engine->player().isGameLoaded() = true;
g_engine->script().createProcess(MainCharacterKind::None, g_engine->world().initScriptName());
break;
default:
@@ -360,10 +359,7 @@ void Menu::triggerOptionsValue(OptionsMenuValue valueId, float value) {
void Menu::continueMainMenu() {
g_engine->config().saveToScummVM();
g_engine->syncSoundSettings();
- g_engine->player().changeRoom(
- g_engine->player().isGameLoaded() ? "MENUPRINCIPAL" : "MENUPRINCIPALINICIO",
- true
- );
+ g_engine->player().changeRoom("MENUPRINCIPAL", true);
updateSelectedSavefile(false);
}
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 6d87fff0e7b..7d51681b615 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -53,7 +53,7 @@ void Player::resetCursor() {
void Player::updateCursor() {
if (g_engine->isV1())
_cursorFrameI = 0;
- else if (g_engine->menu().isOpen() || !_isGameLoaded)
+ else if (g_engine->menu().isOpen())
_cursorFrameI = 0;
else if (_selectedObject == nullptr)
_cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
@@ -349,7 +349,6 @@ void Player::setActiveCharacter(MainCharacterKind kind) {
bool Player::isAllowedToOpenMenu() {
return
- isGameLoaded() &&
!g_engine->menu().isOpen() &&
g_engine->sounds().musicSemaphore().isReleased() &&
g_engine->game().isAllowedToOpenMenu() &&
@@ -386,7 +385,6 @@ void Player::syncGame(Serializer &s) {
_pressedObject = nullptr;
_heldItem = nullptr;
_nextLastDialogCharacter = 0;
- _isGameLoaded = true;
_roomBeforeInventory = nullptr;
_isInTemporaryRoom = false;
fill(_lastDialogCharacters, _lastDialogCharacters + kMaxLastDialogCharacters, nullptr);
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 99fc0476857..094f9b33cbd 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -39,8 +39,6 @@ public:
MainCharacter *inactiveCharacter() const;
FakeSemaphore &semaphoreFor(MainCharacterKind kind);
- inline bool &isGameLoaded() { return _isGameLoaded; }
-
inline MainCharacterKind activeCharacterKind() const {
return _activeCharacter == nullptr ? MainCharacterKind::None : _activeCharacter->kind();
}
@@ -72,9 +70,7 @@ private:
void *_pressedObject = nullptr; // terrible but GlobalUI wants to store a Graphic pointer
Item *_heldItem = nullptr;
int32 _cursorFrameI = 0;
- bool
- _isGameLoaded = true,
- _didLoadGlobalRooms = false,
+ bool _didLoadGlobalRooms = false,
_isInTemporaryRoom = false;
Character *_lastDialogCharacters[kMaxLastDialogCharacters] = { nullptr };
int _nextLastDialogCharacter = 0;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 90ce2e9f843..fff502f70c4 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -230,7 +230,7 @@ bool Room::updateInput() {
bool canInteract = !player.activeCharacter()->isBusy();
// A complicated network condition can prevent interaction at this point
- if (g_engine->menu().isOpen() || !player.isGameLoaded())
+ if (g_engine->menu().isOpen())
canInteract = true;
if (canInteract) {
player.resetCursor();
Commit: f27397c0c0d30f982f5b53558af01b823b34628d
https://github.com/scummvm/scummvm/commit/f27397c0c0d30f982f5b53558af01b823b34628d
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: Fix disguise kernel proc and prepare locking changes
The new game versions use different locking strategies. Also sometimes semaphores are *unlocked* during some section. This requires some infrastructure changes to support both worlds (and hopefully the next couple engine versions as well)
Changed paths:
engines/alcachofa/alcachofa.h
engines/alcachofa/detection_tables.h
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/input.cpp
engines/alcachofa/input.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/tasks.h
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 8e25ea0a865..cd9fa49ad57 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -60,10 +60,11 @@ static constexpr int16 kBigThumbnailWidth = 341; // for in-game
static constexpr int16 kBigThumbnailHeight = 256;
-enum class SaveVersion : Common::Serializer::Version {
- Initial = 0
+namespace SaveVersion {
+ static constexpr const Common::Serializer::Version kInitial = 0;
+ static constexpr const Common::Serializer::Version kWithEngineV10 = 1;
};
-static constexpr SaveVersion kCurrentSaveVersion = SaveVersion::Initial;
+static constexpr const Common::Serializer::Version kCurrentSaveVersion = SaveVersion::kInitial;
class MySerializer : public Common::Serializer {
public:
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index bee3943db5b..b907e020ed1 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -146,7 +146,7 @@ const AlcachofaGameDescription gameDescriptions[] = {
EngineVersion::V1_0
},
- { AD_TABLE_END_MARKER }
+ { AD_TABLE_END_MARKER, EngineVersion::V1_0 }
};
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index dd9c92fb060..e4c86af036b 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -219,6 +219,14 @@ public:
return dynamic_cast<RoomWithFloor *>(g_engine->player().currentRoom()) != nullptr;
}
+ bool shouldScriptLockInteraction() override {
+ return true;
+ }
+
+ bool shouldChangeCharacterUseGameLock() override {
+ return false;
+ }
+
void missingAnimation(const String &fileName) override {
static const char *exemptions[] = {
nullptr
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 79f7fb6fe6f..fc921390f5b 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -261,6 +261,14 @@ public:
return !g_engine->script().variable("prohibirESC");
}
+ bool shouldScriptLockInteraction() override {
+ return false;
+ }
+
+ bool shouldChangeCharacterUseGameLock() override {
+ return true;
+ }
+
void onLoadedGameFiles() override {
// this notifies the script whether we are a demo
if (g_engine->world().loadedMapCount() == 2)
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 9cfd282cd96..4001629b513 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -38,7 +38,7 @@ class Process;
struct ScriptInstruction;
/**
- * @brief Provides functionality specific to a game title.
+ * @brief Provides functionality specific to a game title / engine version.
* Also includes all exemptions to inconsistencies in the original games.
*
* If an error is truly unrecoverable or a warning never an engine bug, no method is necessary here
@@ -67,6 +67,8 @@ public:
virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
virtual bool shouldClipCamera() = 0;
virtual bool isAllowedToOpenMenu() = 0; ///< only the game-specific condition
+ virtual bool shouldScriptLockInteraction() = 0;
+ virtual bool shouldChangeCharacterUseGameLock() = 0;
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index bacbd725e26..bbf89cbfa5e 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -102,4 +102,31 @@ void Input::updateMousePos3D() {
_mousePos3D = { (int16)pos3D.x(), (int16)pos3D.y() };
}
+struct WaitForInputTask final : public Task {
+ WaitForInputTask(Process &process) : Task(process) {}
+
+ WaitForInputTask(Process &process, Serializer &s) : Task(process) {}
+
+ TaskReturn run() override {
+ TASK_BEGIN;
+ process().unlockInteraction();
+ do {
+ TASK_YIELD(1);
+ } while(!g_engine->input().wasAnyMousePressed());
+ process().lockInteraction();
+ TASK_END;
+ }
+
+ void debugPrint() override {
+ g_engine->getDebugger()->debugPrintf("Wait for input");
+ }
+
+ const char *taskName() const override;
+};
+DECLARE_TASK(WaitForInputTask)
+
+Task *Input::waitForInput(Process &process) {
+ return new WaitForInputTask(process);
+}
+
}
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index 4f02eb13d7e..71285556b7f 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -27,6 +27,9 @@
namespace Alcachofa {
+struct Task;
+class Process;
+
class Input {
public:
inline bool wasMouseLeftPressed() const { return _wasMouseLeftPressed; }
@@ -47,6 +50,7 @@ public:
void nextFrame();
bool handleEvent(const Common::Event &event);
void toggleDebugInput(bool debugMode); ///< Toggles input debug mode which blocks any input not retrieved with debugInput
+ Task *waitForInput(Process &process);
private:
void updateMousePos3D();
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 7266ca1b0be..4ff916198af 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -188,6 +188,25 @@ void Process::syncGame(Serializer &s) {
_tasks[i]->syncGame(s);
}
}
+
+ bool hasLock = !_interactionLock.isReleased();
+ s.syncAsByte(hasLock, SaveVersion::kWithEngineV10);
+ if (s.isLoading() && hasLock)
+ _interactionLock = FakeLock("process-interaction", g_engine->player().semaphore());
+}
+
+void Process::lockInteraction() {
+ if (!g_engine->game().shouldScriptLockInteraction())
+ return;
+ assert(_interactionLock.isReleased());
+ _interactionLock = FakeLock("process-interaction", g_engine->player().semaphore());
+}
+
+void Process::unlockInteraction() {
+ if (!g_engine->game().shouldScriptLockInteraction())
+ return;
+ assert(!_interactionLock.isReleased());
+ _interactionLock.release();
}
static void killProcessesForIn(MainCharacterKind characterKind, Array<Process *> &processes, uint firstIndex) {
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index fa88887be44..00eb07098f6 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -177,6 +177,8 @@ public:
TaskReturnType run();
void debugPrint();
void syncGame(Common::Serializer &s);
+ void lockInteraction();
+ void unlockInteraction();
private:
friend class Scheduler;
@@ -185,6 +187,7 @@ private:
Common::Stack<Task *> _tasks;
Common::String _name;
int32 _lastReturnValue = 0;
+ FakeLock _interactionLock;
};
class Scheduler {
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 63b5fe7adb6..b70a21d8162 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -213,7 +213,7 @@ struct ScriptTask final : public Task {
, _script(g_engine->script())
, _name(name)
, _pc(pc)
- , _lock(Common::move(lock)) {
+ , _characterLock(Common::move(lock)) {
pushInstruction(UINT_MAX);
debugC(SCRIPT_DEBUG_LVL_TASKS, kDebugScript, "%u: Script start at %u", process.pid(), pc);
}
@@ -223,7 +223,7 @@ struct ScriptTask final : public Task {
, _script(g_engine->script())
, _name(forkParent._name + " FORKED")
, _pc(forkParent._pc)
- , _lock(forkParent._lock) {
+ , _characterLock(forkParent._characterLock) {
for (uint i = 0; i < forkParent._stack.size(); i++)
_stack.push(forkParent._stack[i]);
pushNumber(1); // this task is the forked one
@@ -237,6 +237,8 @@ struct ScriptTask final : public Task {
}
TaskReturn run() override {
+ if (_isFirstExecution)
+ process().lockInteraction();
if (_isFirstExecution || _returnsFromKernelCall)
setCharacterVariables();
if (_returnsFromKernelCall) {
@@ -411,7 +413,7 @@ struct ScriptTask final : public Task {
}
void syncGame(Serializer &s) override {
- assert(s.isSaving() || (_lock.isReleased() && _stack.empty()));
+ assert(s.isSaving() || (_characterLock.isReleased() && _stack.empty()));
s.syncString(_name);
s.syncAsUint32LE(_pc);
@@ -428,10 +430,10 @@ struct ScriptTask final : public Task {
_stack[i].syncGame(s);
}
- bool hasLock = !_lock.isReleased();
+ bool hasLock = !_characterLock.isReleased();
s.syncAsByte(hasLock);
if (s.isLoading() && hasLock)
- _lock = FakeLock("script", g_engine->player().semaphoreFor(process().character()));
+ _characterLock = FakeLock("script-character", g_engine->player().semaphoreFor(process().character()));
}
const char *taskName() const override;
@@ -661,7 +663,6 @@ private:
// player/world state changes
case ScriptKernelTask::ChangeCharacter: {
MainCharacterKind kind = getMainCharacterKindArg(0);
- killProcessesFor(MainCharacterKind::None); // yes, kill for all characters
auto &camera = g_engine->camera();
auto &player = g_engine->player();
camera.resetRotationAndScale();
@@ -672,9 +673,14 @@ private:
camera.setFollow(player.activeCharacter());
camera.backup(0);
}
- process().character() = MainCharacterKind::None;
- assert(player.semaphore().isReleased());
- _lock = FakeLock("script", player.semaphore());
+
+ if (g_engine->game().shouldChangeCharacterUseGameLock()) {
+ killProcessesFor(MainCharacterKind::None); // yes, kill for all characters
+ kind = MainCharacterKind::None;
+ }
+ process().character() = kind;
+ _characterLock = FakeLock("script", player.semaphoreFor(kind));
+
return TaskReturn::finish(1);
}
case ScriptKernelTask::ChangeRoom:
@@ -963,9 +969,14 @@ private:
as3D(pointObject->position()), targetScale,
getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
}
- case ScriptKernelTask::Disguise:
+ case ScriptKernelTask::Disguise: {
// a somewhat bouncy vertical camera movement used in V1
- return TaskReturn::waitFor(g_engine->camera().disguise(process(), getNumberArg(0)));
+ // or waiting for user to click
+ const auto duration = getNumberArg(0);
+ return TaskReturn::waitFor(duration == 0
+ ? g_engine->input().waitForInput(process())
+ : g_engine->camera().disguise(process(), duration));
+ }
// Fades
case ScriptKernelTask::FadeType0:
@@ -1025,7 +1036,7 @@ private:
g_engine->scheduler().killAllProcessesFor(kind);
g_engine->sounds().fadeOutVoiceAndSFX(200);
g_engine->player().stopLastDialogCharacters();
- _lock.release(); // yes this seems dangerous, but it is original..
+ _characterLock.release(); // yes this seems dangerous, but it is original..
auto &character = g_engine->world().getMainCharacterByKind(kind);
character.resetUsingObjectAndDialogMenu();
assert(character.semaphore().isReleased()); // this process should be the last to hold a lock if at all...
@@ -1038,7 +1049,7 @@ private:
int32 _lastKernelTaskI = 0;
bool _returnsFromKernelCall = false;
bool _isFirstExecution = true;
- FakeLock _lock;
+ FakeLock _characterLock;
};
DECLARE_TASK(ScriptTask)
diff --git a/engines/alcachofa/tasks.h b/engines/alcachofa/tasks.h
index 976b2e38cab..b42a680103c 100644
--- a/engines/alcachofa/tasks.h
+++ b/engines/alcachofa/tasks.h
@@ -50,6 +50,7 @@ DEFINE_TASK(FadeTask)
DEFINE_TASK(DoorTask)
DEFINE_TASK(ScriptTimerTask)
DEFINE_TASK(ScriptTask)
+DEFINE_TASK(WaitForInputTask)
DEFINE_TASK(PlaySoundTask)
DEFINE_TASK(WaitForMusicTask)
DEFINE_TASK(DelayTask)
Commit: 1d7468a6cce64b3ce1a6bc00dc1237969ab0e2d7
https://github.com/scummvm/scummvm/commit/1d7468a6cce64b3ce1a6bc00dc1237969ab0e2d7
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: Adapt interaction and menu conditions for V1
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.h
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index e4c86af036b..638eeba49d2 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -216,7 +216,12 @@ public:
}
bool isAllowedToOpenMenu() override {
- return dynamic_cast<RoomWithFloor *>(g_engine->player().currentRoom()) != nullptr;
+ return g_engine->player().semaphore().isReleased() &&
+ dynamic_cast<RoomWithFloor *>(g_engine->player().currentRoom()) != nullptr;
+ }
+
+ bool isAllowedToInteract() override {
+ return true; // original would be checking an unused script variable "Ocupados"
}
bool shouldScriptLockInteraction() override {
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index fc921390f5b..a5dc92ee83b 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -258,7 +258,12 @@ public:
}
bool isAllowedToOpenMenu() override {
- return !g_engine->script().variable("prohibirESC");
+ return !g_engine->script().variable("prohibirESC") &&
+ g_engine->sounds().musicSemaphore().isReleased();
+ }
+
+ bool isAllowedToInteract() override {
+ return g_engine->player().semaphore().isReleased();
}
bool shouldScriptLockInteraction() override {
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 8cfc74ce801..4ea63b6bb76 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -855,7 +855,7 @@ MainCharacter::~MainCharacter() {
}
bool MainCharacter::isBusy() const {
- return !_semaphore.isReleased() || !g_engine->player().semaphore().isReleased();
+ return !_semaphore.isReleased() || !g_engine->game().isAllowedToInteract();
}
void MainCharacter::update() {
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 4001629b513..e94d0bcfb50 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -66,6 +66,7 @@ public:
virtual Common::String getSoundPath(const char *filename) = 0; ///< Without file-extension
virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
virtual bool shouldClipCamera() = 0;
+ virtual bool isAllowedToInteract() = 0;
virtual bool isAllowedToOpenMenu() = 0; ///< only the game-specific condition
virtual bool shouldScriptLockInteraction() = 0;
virtual bool shouldChangeCharacterUseGameLock() = 0;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 7d51681b615..e173b9a1a5a 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -350,7 +350,6 @@ void Player::setActiveCharacter(MainCharacterKind kind) {
bool Player::isAllowedToOpenMenu() {
return
!g_engine->menu().isOpen() &&
- g_engine->sounds().musicSemaphore().isReleased() &&
g_engine->game().isAllowedToOpenMenu() &&
!_isInTemporaryRoom; // we cannot reliably store this state across multiple room changes
}
Commit: 1cdfcdbfe5e2b49c861a7f8f36cbc1f452943036
https://github.com/scummvm/scummvm/commit/1cdfcdbfe5e2b49c861a7f8f36cbc1f452943036
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: Fix room transitions when examining items
Changed paths:
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index e173b9a1a5a..183a87ba814 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -348,10 +348,9 @@ void Player::setActiveCharacter(MainCharacterKind kind) {
}
bool Player::isAllowedToOpenMenu() {
- return
- !g_engine->menu().isOpen() &&
- g_engine->game().isAllowedToOpenMenu() &&
- !_isInTemporaryRoom; // we cannot reliably store this state across multiple room changes
+ return !g_engine->menu().isOpen() &&
+ g_engine->game().isAllowedToOpenMenu() &&
+ !_isInTemporaryRoom; // we cannot reliably store this state across multiple room changes
}
void Player::syncGame(Serializer &s) {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index fff502f70c4..efe93fadc35 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -543,7 +543,7 @@ void Inventory::open() {
updateItemsByActiveCharacter();
}
-void InventoryV3::close() {
+void Inventory::close() {
g_engine->camera().restore(1);
g_engine->globalUI().startClosingInventory();
}
Commit: cb07e9349d011dbce1c2cfcf8ef729292b5299c4
https://github.com/scummvm/scummvm/commit/cb07e9349d011dbce1c2cfcf8ef729292b5299c4
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: V1: Fix change character button
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index dbbbf8da484..7d07e0a11f0 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -84,7 +84,7 @@ Common::Error AlcachofaEngine::run() {
_drawQueue.reset(new DrawQueue(_renderer.get()));
_script.reset(new Script());
_player.reset(new Player());
- _globalUI.reset(new GlobalUI());
+ _globalUI.reset(isV1() ? static_cast<GlobalUI *>(new GlobalUIV1()) : new GlobalUIV3());
_menu.reset(new Menu());
setMillis(0);
game().onLoadedGameFiles();
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 638eeba49d2..fec007b11a9 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -211,6 +211,10 @@ public:
return String::format("disk%d/track%02d", diskId, trackId);
}
+ int32 getCharacterJingle(MainCharacterKind kind) override {
+ return kind == MainCharacterKind::Mortadelo ? 15 : 16;
+ }
+
bool shouldClipCamera() override {
return true;
}
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index a5dc92ee83b..7864c4c652a 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -253,6 +253,11 @@ public:
return String::format("Sonidos/T%d", trackId);
}
+ int32 getCharacterJingle(MainCharacterKind kind) override {
+ return g_engine->script().variable(
+ kind == MainCharacterKind::Mortadelo ? "PistaMorta" : "PistaFile");
+ }
+
bool shouldClipCamera() override {
return g_engine->script().variable("EncuadrarCamara");
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index e94d0bcfb50..821f539a544 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -36,6 +36,7 @@ class Door;
class Room;
class Process;
struct ScriptInstruction;
+class GlobalUI;
/**
* @brief Provides functionality specific to a game title / engine version.
@@ -65,6 +66,7 @@ public:
virtual Common::Path getVideoPath(int32 videoId) = 0;
virtual Common::String getSoundPath(const char *filename) = 0; ///< Without file-extension
virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
+ virtual int32 getCharacterJingle(MainCharacterKind kind) = 0;
virtual bool shouldClipCamera() = 0;
virtual bool isAllowedToInteract() = 0;
virtual bool isAllowedToOpenMenu() = 0; ///< only the game-specific condition
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index f58cfc42839..e91ce6b4160 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -113,17 +113,6 @@ Animation *GlobalUI::activeAnimation() const {
: _iconMortadelo.get();
}
-bool GlobalUI::isHoveringChangeButton() const {
- auto mousePos = g_engine->input().mousePos2D();
- auto anim = activeAnimation();
- auto offset = anim->totalFrameOffset(0);
- auto bounds = anim->frameBounds(0);
-
- const int minX = g_system->getWidth() + offset.x;
- const int maxY = bounds.height() + offset.y;
- return mousePos.x >= minX && mousePos.y <= maxY;
-}
-
bool GlobalUI::updateChangingCharacter() {
auto &player = g_engine->player();
if (g_engine->menu().isOpen() ||
@@ -147,12 +136,7 @@ bool GlobalUI::updateChangingCharacter() {
player.changeRoom(player.activeCharacter()->room()->name(), false);
g_engine->game().onUserChangedCharacter();
- int32 characterJingle = g_engine->script().variable(
- player.activeCharacterKind() == MainCharacterKind::Mortadelo
- ? "PistaMorta"
- : "PistaFile"
- );
- g_engine->sounds().startMusic(characterJingle);
+ g_engine->sounds().startMusic(g_engine->game().getCharacterJingle(player.activeCharacterKind()));
g_engine->sounds().queueMusic(player.currentRoom()->musicID());
_changeButton.setAnimation(activeAnimation());
@@ -160,7 +144,18 @@ bool GlobalUI::updateChangingCharacter() {
return true;
}
-void GlobalUI::drawChangingButton() {
+bool GlobalUIV3::isHoveringChangeButton() const {
+ auto mousePos = g_engine->input().mousePos2D();
+ auto anim = activeAnimation();
+ auto offset = anim->totalFrameOffset(0);
+ auto bounds = anim->frameBounds(0);
+
+ const int minX = g_system->getWidth() + offset.x;
+ const int maxY = bounds.height() + offset.y;
+ return mousePos.x >= minX && mousePos.y <= maxY;
+}
+
+void GlobalUIV3::drawChangingButton() {
auto &player = g_engine->player();
if (g_engine->menu().isOpen() ||
!player.semaphore().isReleased() ||
@@ -188,6 +183,26 @@ void GlobalUI::drawChangingButton() {
g_engine->drawQueue().add<AnimationDrawRequest>(_changeButton, false, BlendMode::AdditiveAlpha);
}
+bool GlobalUIV1::isHoveringChangeButton() const {
+ auto mousePos = g_engine->input().mousePos2D();
+ auto imageSize = activeAnimation()->imageSize(0);
+
+ return mousePos.x >= g_system->getWidth() - imageSize.x && mousePos.y < imageSize.y;
+}
+
+void GlobalUIV1::drawChangingButton() {
+ if (g_engine->menu().isOpen())
+ return;
+
+ auto anim = activeAnimation();
+ _changeButton.setAnimation(anim);
+ _changeButton.topLeft() = { (int16)(g_system->getWidth() - anim->imageSize(0).x), 0 };
+ _changeButton.reset(); // make sure frameI == 0
+ g_engine->drawQueue().add<AnimationDrawRequest>(_changeButton, false, BlendMode::AdditiveAlpha);
+
+ //g_engine->drawQueue().add<AnimationDrawRequest>(anim, 0, Math::Vector2d(x, 0), -9);
+}
+
struct CenterBottomTextTask final : public Task {
CenterBottomTextTask(Process &process, int32 dialogId, uint32 durationMs)
: Task(process)
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index dd3722a30cc..8204138d788 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -32,22 +32,23 @@ Common::Rect closeInventoryTriggerBounds();
class GlobalUI {
public:
GlobalUI();
+ virtual ~GlobalUI() {}
inline Font &generalFont() const { assert(_generalFont != nullptr); return *_generalFont; }
inline Font &dialogFont() const { assert(_dialogFont != nullptr); return *_dialogFont; }
inline bool &isPermanentFaded() { return _isPermanentFaded; }
bool updateChangingCharacter();
- void drawChangingButton();
+ virtual void drawChangingButton() = 0;
bool updateOpeningInventory();
void updateClosingInventory();
void startClosingInventory();
void drawScreenStates(); // black borders and/or permanent fade
void syncGame(Common::Serializer &s);
-private:
+protected:
Animation *activeAnimation() const;
- bool isHoveringChangeButton() const;
+ virtual bool isHoveringChangeButton() const = 0;
Graphic _changeButton;
Common::ScopedPtr<Font>
@@ -65,6 +66,22 @@ private:
uint32 _timeForInventory = 0;
};
+class GlobalUIV1 final : public GlobalUI {
+public:
+ void drawChangingButton() override;
+
+protected:
+ bool isHoveringChangeButton() const override;
+};
+
+class GlobalUIV3 final : public GlobalUI {
+public:
+ void drawChangingButton() override;
+
+protected:
+ bool isHoveringChangeButton() const override;
+};
+
Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs);
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index efe93fadc35..9a558ed2e80 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -789,8 +789,8 @@ bool World::loadWorldFileV1(const char *path) {
};
readGlobalAnim(GlobalAnimationKind::GeneralFont, GlobalAnimationKind::DialogFont);
readGlobalAnim(GlobalAnimationKind::Cursor);
+ readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon); // note this is swapped in V1
readGlobalAnim(GlobalAnimationKind::MortadeloIcon, GlobalAnimationKind::MortadeloDisabledIcon);
- readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon);
readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
readRooms(*file);
Commit: 030df0392a76bf66e9618c0876d196b6f62076bf
https://github.com/scummvm/scummvm/commit/030df0392a76bf66e9618c0876d196b6f62076bf
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: V1: Add inventory button
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index e91ce6b4160..044ee034d62 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -28,22 +28,6 @@ using namespace Common;
namespace Alcachofa {
-// originally the inventory only reacts to exactly top-left/bottom-right which is fine in
-// fullscreen when you just slam the mouse cursor into the corner.
-// In any other scenario this is cumbersome so I expand this area.
-// And it is still pretty bad, especially in windowed mode so there is a key to open/close as well
-static constexpr int16 kInventoryTriggerSize = 10;
-
-Rect openInventoryTriggerBounds() {
- int16 size = kInventoryTriggerSize * 1024 / g_system->getWidth();
- return Rect(0, 0, size, size);
-}
-
-Rect closeInventoryTriggerBounds() {
- int16 size = kInventoryTriggerSize * 1024 / g_system->getWidth();
- return Rect(g_system->getWidth() - size, g_system->getHeight() - size, g_system->getWidth(), g_system->getHeight());
-}
-
GlobalUI::GlobalUI() {
auto &world = g_engine->world();
_generalFont.reset(new Font(world.getGlobalAnimation(GlobalAnimationKind::GeneralFont)));
@@ -59,7 +43,11 @@ GlobalUI::GlobalUI() {
_iconInventory->load();
}
-void GlobalUI::startClosingInventory() {
+void GlobalUIV1::startClosingInventory() {
+ // nothing to do here, the inventory closes instantly
+}
+
+void GlobalUIV3::startClosingInventory() {
_isOpeningInventory = false;
_isClosingInventory = true;
_timeForInventory = g_engine->getMillis();
@@ -77,13 +65,20 @@ void GlobalUI::updateClosingInventory() {
g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed)));
}
-bool GlobalUI::updateOpeningInventory() {
+// originally the inventory only reacts to exactly top-left/bottom-right which is fine in
+// fullscreen when you just slam the mouse cursor into the corner.
+// In any other scenario this is cumbersome so I expand this area.
+// And it is still pretty bad, especially in windowed mode so there is a key to open/close as well
+static constexpr int16 kInventoryTriggerSizeV3 = 10;
+
+bool GlobalUIV3::updateOpeningInventory() {
static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
if (g_engine->menu().isOpen())
return false;
+ int16 size = kInventoryTriggerSizeV3 * 1024 / g_system->getWidth();
const bool userWantsToOpenInventory =
- openInventoryTriggerBounds().contains(g_engine->input().mousePos2D()) ||
+ Rect(0, 0, size, size).contains(g_engine->input().mousePos2D()) ||
g_engine->input().wasInventoryKeyPressed();
if (_isOpeningInventory) {
@@ -107,6 +102,23 @@ bool GlobalUI::updateOpeningInventory() {
return false;
}
+bool GlobalUIV1::updateOpeningInventory() {
+ auto mousePos = g_engine->input().mousePos2D();
+ auto imageSize = _iconInventory->imageSize(0);
+ const bool isHovering = mousePos.x < imageSize.x && mousePos.y < imageSize.y;
+ const bool userClickedOnButton = isHovering && g_engine->input().wasMouseLeftReleased();
+
+ if (g_engine->menu().isOpen())
+ return false;
+ if (userClickedOnButton || g_engine->input().wasInventoryKeyPressed()) {
+ g_engine->player().activeCharacter()->stopWalking();
+ g_engine->world().inventory().updateItemsByActiveCharacter();
+ g_engine->world().inventory().open();
+ return true;
+ }
+ return isHovering;
+}
+
Animation *GlobalUI::activeAnimation() const {
return g_engine->player().activeCharacterKind() == MainCharacterKind::Mortadelo
? _iconFilemon.get()
@@ -183,6 +195,16 @@ void GlobalUIV3::drawChangingButton() {
g_engine->drawQueue().add<AnimationDrawRequest>(_changeButton, false, BlendMode::AdditiveAlpha);
}
+bool GlobalUIV3::isHoveringInventoryExit() const {
+ int16 size = kInventoryTriggerSizeV3 * 1024 / g_system->getWidth();
+ auto bounds = Rect(g_system->getWidth() - size, g_system->getHeight() - size, g_system->getWidth(), g_system->getHeight());;
+ return bounds.contains(g_engine->input().mousePos2D());
+}
+
+void GlobalUIV3::drawInventoryButton() {
+ // while there are animations for the inventory button, they are unused in V3
+}
+
bool GlobalUIV1::isHoveringChangeButton() const {
auto mousePos = g_engine->input().mousePos2D();
auto imageSize = activeAnimation()->imageSize(0);
@@ -191,7 +213,7 @@ bool GlobalUIV1::isHoveringChangeButton() const {
}
void GlobalUIV1::drawChangingButton() {
- if (g_engine->menu().isOpen())
+ if (g_engine->menu().isOpen() || !g_engine->player().semaphore().isReleased())
return;
auto anim = activeAnimation();
@@ -203,6 +225,25 @@ void GlobalUIV1::drawChangingButton() {
//g_engine->drawQueue().add<AnimationDrawRequest>(anim, 0, Math::Vector2d(x, 0), -9);
}
+static constexpr int16 kInventoryTriggerSizeV1 = 70;
+
+bool GlobalUIV1::isHoveringInventoryExit() const {
+ auto mousePos = g_engine->input().mousePos2D();
+
+ return mousePos.x >= g_system->getWidth() - kInventoryTriggerSizeV1 &&
+ mousePos.y >= g_system->getHeight() - kInventoryTriggerSizeV1;
+}
+
+void GlobalUIV1::drawInventoryButton() {
+ if (g_engine->menu().isOpen())
+ return;
+
+ _inventoryButton.setAnimation(_iconInventory.get());
+ _inventoryButton.reset();
+ _inventoryButton.order() = -9;
+ g_engine->drawQueue().add<AnimationDrawRequest>(_inventoryButton, false, BlendMode::AdditiveAlpha);
+}
+
struct CenterBottomTextTask final : public Task {
CenterBottomTextTask(Process &process, int32 dialogId, uint32 durationMs)
: Task(process)
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 8204138d788..d45257bb02a 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -26,9 +26,6 @@
namespace Alcachofa {
-Common::Rect openInventoryTriggerBounds();
-Common::Rect closeInventoryTriggerBounds();
-
class GlobalUI {
public:
GlobalUI();
@@ -40,9 +37,11 @@ public:
bool updateChangingCharacter();
virtual void drawChangingButton() = 0;
- bool updateOpeningInventory();
+ virtual void drawInventoryButton() = 0;
+ virtual bool updateOpeningInventory() = 0; ///< returns true iff interaction is handled
+ virtual void startClosingInventory() = 0;
+ virtual bool isHoveringInventoryExit() const = 0;
void updateClosingInventory();
- void startClosingInventory();
void drawScreenStates(); // black borders and/or permanent fade
void syncGame(Common::Serializer &s);
@@ -50,7 +49,7 @@ protected:
Animation *activeAnimation() const;
virtual bool isHoveringChangeButton() const = 0;
- Graphic _changeButton;
+ Graphic _changeButton, _inventoryButton;
Common::ScopedPtr<Font>
_generalFont,
_dialogFont;
@@ -69,6 +68,10 @@ protected:
class GlobalUIV1 final : public GlobalUI {
public:
void drawChangingButton() override;
+ void drawInventoryButton() override;
+ bool updateOpeningInventory() override;
+ void startClosingInventory() override;
+ bool isHoveringInventoryExit() const override;
protected:
bool isHoveringChangeButton() const override;
@@ -77,6 +80,10 @@ protected:
class GlobalUIV3 final : public GlobalUI {
public:
void drawChangingButton() override;
+ void drawInventoryButton() override;
+ bool updateOpeningInventory() override;
+ void startClosingInventory() override;
+ bool isHoveringInventoryExit() const override;
protected:
bool isHoveringChangeButton() const override;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 9a558ed2e80..ac5a569621b 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -240,6 +240,7 @@ bool Room::updateInput() {
player.updateCursor();
}
player.drawCursor();
+ g_engine->globalUI().drawInventoryButton();
}
if (player.currentRoom() == this) {
@@ -440,11 +441,11 @@ bool Inventory::updateInput() {
auto &player = g_engine->player();
auto &input = g_engine->input();
auto *hoveredItem = getHoveredItem();
+ if (player.activeCharacter()->isBusy())
+ return true;
+ player.drawCursor(0);
- if (!player.activeCharacter()->isBusy())
- player.drawCursor(0);
-
- if (hoveredItem != nullptr && !player.activeCharacter()->isBusy()) {
+ if (hoveredItem != nullptr) {
if ((input.wasMouseLeftPressed() && player.heldItem() == nullptr) ||
(input.wasMouseLeftReleased() && player.heldItem() != nullptr) ||
input.wasMouseRightReleased()) {
@@ -460,18 +461,15 @@ bool Inventory::updateInput() {
}
const bool userWantsToCloseInventory =
- closeInventoryTriggerBounds().contains(input.mousePos2D()) ||
+ g_engine->globalUI().isHoveringInventoryExit() ||
input.wasMenuKeyPressed() ||
input.wasInventoryKeyPressed();
- if (!player.activeCharacter()->isBusy() &&
- userWantsToCloseInventory) {
+ if (userWantsToCloseInventory) {
player.changeRoomToBeforeInventory();
close();
}
- if (!player.activeCharacter()->isBusy() &&
- hoveredItem == nullptr &&
- input.wasMouseRightReleased()) {
+ if (hoveredItem == nullptr && input.wasMouseRightReleased()) {
player.heldItem() = nullptr;
return false;
}
Commit: 6cb669d0e9a59cc595f47db0fd806efec6f89496
https://github.com/scummvm/scummvm/commit/6cb669d0e9a59cc595f47db0fd806efec6f89496
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: Add script debug command
Changed paths:
engines/alcachofa/console.cpp
engines/alcachofa/console.h
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index a6228a67a6a..f3e702f449f 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -51,6 +51,7 @@ Console::Console() : GUI::Debugger() {
registerCmd("tp", WRAP_METHOD(Console, cmdTeleport));
registerCmd("toggleRoomFloor", WRAP_METHOD(Console, cmdToggleRoomFloor));
registerCmd("playVideo", WRAP_METHOD(Console, cmdPlayVideo));
+ registerCmd("script", WRAP_METHOD(Console, cmdScript));
}
Console::~Console() {}
@@ -294,4 +295,23 @@ bool Console::cmdPlayVideo(int argc, const char **args) {
return true;
}
+bool Console::cmdScript(int argc, const char **args) {
+ if (argc != 2) {
+ debugPrintf("usage: %s <procedure name>\n", args[0]);
+ return true;
+ }
+
+ auto process = g_engine->script().createProcess(
+ g_engine->player().activeCharacterKind(),
+ args[1],
+ ScriptFlags::AllowMissing);
+ if (process == nullptr) {
+ debugPrintf("No such procedure exists");
+ return true;
+ } else {
+ process->name() += " (Console)";
+ return false;
+ }
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index acfbb4fea6f..f7c843887f9 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -61,6 +61,7 @@ private:
bool cmdTeleport(int argc, const char **args);
bool cmdToggleRoomFloor(int argc, const char **args);
bool cmdPlayVideo(int argc, const char **args);
+ bool cmdScript(int argc, const char **args);
bool _showGraphics = false;
bool _showInteractables = false;
Commit: ce073b3bece7dc2d99dc79a38a8a68dfb3eecda9
https://github.com/scummvm/scummvm/commit/ce073b3bece7dc2d99dc79a38a8a68dfb3eecda9
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: V1: Fix stack handling of ReturnVoid
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index b70a21d8162..2b9d21114f4 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -390,6 +390,7 @@ struct ScriptTask final : public Task {
break;
case ScriptOp::ReturnVoid: {
_pc = popInstruction();
+ pushNumber(instruction._arg); // this does not seem to be original, but it works..
if (_pc == UINT_MAX)
return TaskReturn::finish(0);
}break;
Commit: e8c9400dca805b94d867aea21b89d057a921ebd5
https://github.com/scummvm/scummvm/commit/e8c9400dca805b94d867aea21b89d057a921ebd5
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:29+01:00
Commit Message:
ALCACHOFA: V1: Fix animation sprite order
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 076ac56be8b..e9ee1856c5a 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -230,7 +230,7 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
skipVarString(stream); // another internal, unused name
uint spriteCount = stream.readUint32LE();
uint frameCount = stream.readUint32LE();
- scumm_assert(spriteCount < kMaxSpriteIDsV1);
+ scumm_assert(spriteCount <= kMaxSpriteIDsV1);
_spriteBases.reserve(spriteCount);
_spriteEnabled.reserve(spriteCount);
_spriteOffsets.reserve(spriteCount * frameCount);
@@ -258,15 +258,17 @@ void AnimationBase::readV1(SeekableReadStream &stream) {
}
}
- // Sprite order is setup by setting up reverse index sequence and
+ // 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++)
- _spriteIndexMapping[i] = (spriteCount - 1 - i);
+ _spriteIndexMapping[i] = i;
for (uint i = 0; i < spriteCount; i++) {
bool hadChange = false;
for (uint j = 0; j < spriteCount - i - 1; j++) {
- if (spriteOrder[_spriteIndexMapping[j]] < spriteOrder[_spriteIndexMapping[j + 1]])
+ if (spriteOrder[_spriteIndexMapping[j]] < spriteOrder[_spriteIndexMapping[j + 1]]) {
SWAP(_spriteIndexMapping[j], _spriteIndexMapping[j + 1]);
+ hadChange = true;
+ }
}
if (!hadChange)
break;
Commit: 04fc32f14c51d6187fe1772aad3e37e575b47c3c
https://github.com/scummvm/scummvm/commit/04fc32f14c51d6187fe1772aad3e37e575b47c3c
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:30+01:00
Commit Message:
ALCACHOFA: V1: Fix room bounds and 3D draw positions
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 6e72525ed29..9037fe15f36 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -37,11 +37,11 @@ void Camera::resetRotationAndScale() {
_cur._usedCenter.z() = 0;
}
-void Camera::setRoomBounds(Point min, Point size, int16 bgScale) {
- float scaleFactor = bgScale * kInvBaseScale;
- _roomMin = as2D(min) * scaleFactor;
- _roomMax = _roomMin + as2D(size) * scaleFactor;
- _roomScale = 0.0f;
+void Camera::setRoomBounds(Point min, Point size) {
+ Point screenSize(g_system->getWidth(), g_system->getHeight());
+ _roomMin = as2D(min + screenSize / 2);
+ _roomMax = _roomMin + as2D(size - screenSize);
+ _roomScale = 0;
}
void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index a5a1573e5a3..c6774da47bf 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -46,7 +46,7 @@ public:
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
Common::Point transform3Dto2D(Common::Point p) const;
void resetRotationAndScale();
- void setRoomBounds(Common::Point min, Common::Point size, int16 bgScale); ///< Used in V1
+ void setRoomBounds(Common::Point min, Common::Point size); ///< Used in V1
void setRoomBounds(Common::Point bgSize, int16 bgScale); ///< Used in V3
void setFollow(WalkingCharacter *target, bool catchUp = false);
void setPosition(Math::Vector2d v);
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index f3e702f449f..9206f1c19d4 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -52,6 +52,7 @@ Console::Console() : GUI::Debugger() {
registerCmd("toggleRoomFloor", WRAP_METHOD(Console, cmdToggleRoomFloor));
registerCmd("playVideo", WRAP_METHOD(Console, cmdPlayVideo));
registerCmd("script", WRAP_METHOD(Console, cmdScript));
+ registerCmd("toggleObject", WRAP_METHOD(Console, cmdToggleObject));
}
Console::~Console() {}
@@ -306,7 +307,7 @@ bool Console::cmdScript(int argc, const char **args) {
args[1],
ScriptFlags::AllowMissing);
if (process == nullptr) {
- debugPrintf("No such procedure exists");
+ debugPrintf("No such procedure exists\n");
return true;
} else {
process->name() += " (Console)";
@@ -314,4 +315,22 @@ bool Console::cmdScript(int argc, const char **args) {
}
}
+bool Console::cmdToggleObject(int argc, const char **args) {
+ if (argc < 2) {
+ debugPrintf("usage: %s <object> [<object> ...]\n", args[0]);
+ return true;
+ }
+
+ for (int i = 1; i < argc; i++) {
+ auto object = g_engine->world().getObjectByName(args[i]);
+ if (object == nullptr)
+ object = g_engine->world().getObjectByNameFromAnyRoom(args[i]);
+ if (object == nullptr)
+ debugPrintf("No such object: %s\n", args[i]);
+ else
+ object->toggle(!object->isEnabled());
+ }
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index f7c843887f9..b288a8bfb68 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -62,6 +62,7 @@ private:
bool cmdToggleRoomFloor(int argc, const char **args);
bool cmdPlayVideo(int argc, const char **args);
bool cmdScript(int argc, const char **args);
+ bool cmdToggleObject(int argc, const char **args);
bool _showGraphics = false;
bool _showInteractables = false;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index e9ee1856c5a..d47b1d07cdd 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -601,7 +601,10 @@ void Animation::draw2D(int32 frameI, Vector2d topLeft, float scale, BlendMode bl
void Animation::outputRect3D(int32 frameI, float scale, Vector3d &topLeft, Vector2d &size) const {
auto bounds = frameBounds(frameI);
- topLeft += as3D(totalFrameOffset(frameI)) * scale;
+ if (g_engine->isV3())
+ topLeft += as3D(totalFrameOffset(frameI)) * scale;
+ else // scale by depth position is not used in V1
+ topLeft += as3D(totalFrameOffset(frameI)) * scale / (topLeft.z() * kInvBaseScale);
topLeft = g_engine->camera().transform3Dto2D(topLeft);
size = Vector2d(bounds.width(), bounds.height()) * scale * topLeft.z();
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index ac5a569621b..470264603b5 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -275,10 +275,7 @@ void Room::updateRoomBounds() {
if (graphic != nullptr) {
auto bgSize = graphic->animation().imageSize(0);
if (g_engine->isV1()) {
- g_engine->camera().setRoomBounds(
- graphic->topLeft() + Point(400, 300),
- bgSize - Point(800, 600),
- graphic->scale());
+ g_engine->camera().setRoomBounds(graphic->topLeft(), bgSize);
} else {
/* The fallback fixes a bug where if the background image is invalid the original engine
* would not update the background size. This would be around 1024,768 due to
Commit: c5d17a19f4092ba985e8c339117d008b0ab0af33
https://github.com/scummvm/scummvm/commit/c5d17a19f4092ba985e8c339117d008b0ab0af33
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:30+01:00
Commit Message:
ALCACHOFA: V1: Implement V1-style menu
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/objects.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 7d07e0a11f0..cbc232ecd90 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -85,7 +85,7 @@ Common::Error AlcachofaEngine::run() {
_script.reset(new Script());
_player.reset(new Player());
_globalUI.reset(isV1() ? static_cast<GlobalUI *>(new GlobalUIV1()) : new GlobalUIV3());
- _menu.reset(new Menu());
+ _menu.reset(isV1() ? static_cast<Menu *>(new MenuV1()) : new MenuV3());
setMillis(0);
game().onLoadedGameFiles();
@@ -415,7 +415,8 @@ void AlcachofaEngine::getSavegameThumbnail(Graphics::Surface &thumbnail) {
}
// otherwise we have to rerender
- thumbnail.create(kBigThumbnailWidth, kBigThumbnailHeight, g_engine->renderer().getPixelFormat());
+ auto size = g_engine->game().getThumbnailResolution();
+ thumbnail.create(size.x, size.y, g_engine->renderer().getPixelFormat());
if (g_engine->player().currentRoom() == nullptr)
return; // but without a room we would render only black anyway
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index cd9fa49ad57..406cd757732 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -56,9 +56,7 @@ struct AlcachofaGameDescription;
constexpr int16 kSmallThumbnailWidth = 160; // for ScummVM
constexpr int16 kSmallThumbnailHeight = 120;
-static constexpr int16 kBigThumbnailWidth = 341; // for in-game
-static constexpr int16 kBigThumbnailHeight = 256;
-
+// the in-game save thumbnail size is determined by engine verison
namespace SaveVersion {
static constexpr const Common::Serializer::Version kInitial = 0;
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index fec007b11a9..453db2c7c9a 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -148,6 +148,10 @@ public:
return Point(800, 600);
}
+ Point getThumbnailResolution() override {
+ return Point(266, 200);
+ }
+
static constexpr const char *kMapFiles[] = {
"oeste.emc",
"terror.emc",
@@ -175,6 +179,10 @@ public:
return kNoXORKey;
}
+ const char *getMenuRoom() override {
+ return "MENU";
+ }
+
const char *getInitScriptName() override {
return "INICIALIZAR_MUNDO";
}
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 7864c4c652a..da35b7a0ff8 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -204,6 +204,10 @@ public:
return Point(1024, 768);
}
+ Point getThumbnailResolution() override {
+ return Point(341, 256);
+ }
+
const char *const *getMapFiles() override {
return kMapFiles;
}
@@ -228,6 +232,10 @@ public:
return kEmbeddedXORKey;
}
+ const char *getMenuRoom() override {
+ return "MENUPRINCIPAL";
+ }
+
const char *getInitScriptName() override {
return "CREDITOS_INICIALES";
}
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 46767b7b393..9ed6a7dfefc 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -180,6 +180,10 @@ void Game::unknownScriptProcedure(const String &procedure) {
_message("Unknown required procedure: %s", procedure.c_str());
}
+void Game::unknownMenuAction(int32 actionId) {
+ _message("Unknown button action: %d", actionId);
+}
+
void Game::missingSound(const String &fileName) {
_message("Missing sound file: %s", fileName.c_str());
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 821f539a544..a626d0f8ec5 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -52,6 +52,7 @@ public:
virtual void onLoadedGameFiles();
virtual Common::Point getResolution() = 0;
+ virtual Common::Point getThumbnailResolution() = 0;
virtual const char *const *getMapFiles() = 0; ///< Returns a nullptr-terminated list
virtual GameFileReference getScriptFileRef() = 0;
virtual Common::Span<const ScriptOp> getScriptOpMap() = 0;
@@ -61,6 +62,7 @@ public:
virtual const char *getDialogFileName() = 0;
virtual const char *getObjectFileName() = 0;
virtual char getTextFileKey() = 0;
+ virtual const char *getMenuRoom() = 0;
virtual const char *getInitScriptName() = 0;
virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
virtual Common::Path getVideoPath(int32 videoId) = 0;
@@ -106,6 +108,7 @@ public:
virtual void unknownCamLerpTarget(const char *action, const char *name);
virtual void unknownKernelTask(int task);
virtual void unknownScriptProcedure(const Common::String &procedure);
+ virtual void unknownMenuAction(int32 actionId);
virtual void missingAnimation(const Common::String &fileName);
virtual void missingSound(const Common::String &fileName);
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 75ff0c669d7..5576f6fac1d 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/config-manager.h"
#include "gui/message.h"
#include "graphics/thumbnail.h"
@@ -34,7 +35,8 @@ using namespace Graphics;
namespace Alcachofa {
static void createThumbnail(ManagedSurface &surface) {
- surface.create(kBigThumbnailWidth, kBigThumbnailHeight, g_engine->renderer().getPixelFormat());
+ const auto size = g_engine->game().getThumbnailResolution();
+ surface.create(size.x, size.y, g_engine->renderer().getPixelFormat());
}
static void convertToGrayscale(ManagedSurface &surface) {
@@ -65,6 +67,8 @@ Menu::Menu()
: _interactionSemaphore("menu")
, _saveFileMgr(g_system->getSavefileManager()) {}
+Menu::~Menu() {}
+
void Menu::resetAfterLoad() {
_isOpen = false;
_openAtNextFrame = false;
@@ -88,7 +92,7 @@ void Menu::updateOpeningMenu() {
_millisBeforeMenu = g_engine->getMillis();
_previousRoom = g_engine->player().currentRoom();
_isOpen = true;
- g_engine->player().changeRoom("MENUPRINCIPAL", true);
+ g_engine->player().changeRoom(g_engine->game().getMenuRoom(), true);
_savefiles = _saveFileMgr->listSavefiles(g_engine->getSaveStatePattern());
sort(_savefiles.begin(), _savefiles.end()); // the pattern ensures that the last file has the greatest slot
_selectedSavefileI = _savefiles.size();
@@ -101,6 +105,13 @@ void Menu::updateOpeningMenu() {
g_system->getWidth() / 2.0f, g_system->getHeight() / 2.0f, 0.0f));
}
+void MenuV1::updateOpeningMenu() {
+ bool willOpen = _openAtNextFrame;
+ Menu::updateOpeningMenu();
+ if (willOpen)
+ switchToState(MainMenuAction::ConfirmSavestate);
+}
+
static int parseSavestateSlot(const String &filename) {
if (filename.size() < 5) // minimal name would be "t.###"
return 1;
@@ -108,22 +119,12 @@ static int parseSavestateSlot(const String &filename) {
}
void Menu::updateSelectedSavefile(bool hasJustSaved) {
- auto getButton = [] (const char *name) {
- MenuButton *button = dynamic_cast<MenuButton *>(g_engine->player().currentRoom()->getObjectByName(name));
- scumm_assert(button != nullptr);
- return button;
- };
-
- bool isOldSavefile = _selectedSavefileI < _savefiles.size();
- getButton("CARGAR")->isInteractable() = isOldSavefile;
- getButton("ANTERIOR")->toggle(_selectedSavefileI > 0);
- getButton("SIGUIENTE")->toggle(isOldSavefile);
-
if (hasJustSaved) {
// we just saved in-game so we also still have the correct thumbnail in memory
_selectedThumbnail.copyFrom(_bigThumbnail);
- } else if (isOldSavefile) {
+ } else if (!isOnNewSlot()) {
if (!tryReadOldSavefile()) {
+ // an old savefile we could not read, use blank thumbnail and default description
_selectedSavefileDescription = String::format("Savestate %d",
parseSavestateSlot(_savefiles[_selectedSavefileI]));
createThumbnail(_selectedThumbnail);
@@ -131,7 +132,8 @@ void Menu::updateSelectedSavefile(bool hasJustSaved) {
} else {
// the unsaved gamestate is shown as grayscale
_selectedThumbnail.copyFrom(_bigThumbnail);
- convertToGrayscale(_selectedThumbnail);
+ if (g_engine->isV3())
+ convertToGrayscale(_selectedThumbnail);
}
ObjectBase *captureObject = g_engine->player().currentRoom()->getObjectByName("Capture");
@@ -141,6 +143,29 @@ void Menu::updateSelectedSavefile(bool hasJustSaved) {
captureGraphic->animation().overrideTexture(_selectedThumbnail);
}
+void MenuV1::updateSelectedSavefile(bool hasJustSaved) {
+ Menu::updateSelectedSavefile(hasJustSaved);
+
+ ObjectBase *captureObject = g_engine->player().currentRoom()->getObjectByName("Capture");
+ scumm_assert(captureObject);
+ bool isInCorrectState = _currentState == MainMenuAction::Load || _currentState == MainMenuAction::Save;
+ captureObject->toggle(isInCorrectState && !isOnNewSlot());
+}
+
+void MenuV3::updateSelectedSavefile(bool hasJustSaved) {
+ Menu::updateSelectedSavefile(hasJustSaved);
+
+ auto getButton = [ ] (const char *name) {
+ MenuButton *button = dynamic_cast<MenuButton *>(g_engine->player().currentRoom()->getObjectByName(name));
+ scumm_assert(button != nullptr);
+ return button;
+ };
+
+ getButton("CARGAR")->isInteractable() = !isOnNewSlot();
+ getButton("ANTERIOR")->toggle(_selectedSavefileI > 0);
+ getButton("SIGUIENTE")->toggle(!isOnNewSlot());
+}
+
bool Menu::tryReadOldSavefile() {
auto savefile = ScopedPtr<InSaveFile>(
_saveFileMgr->openForLoading(_savefiles[_selectedSavefileI]));
@@ -176,7 +201,7 @@ void Menu::continueGame() {
void Menu::triggerMainMenuAction(MainMenuAction action) {
switch (action) {
case MainMenuAction::ContinueGame:
- g_engine->menu().continueGame();
+ continueGame();
break;
case MainMenuAction::Save:
triggerSave();
@@ -194,13 +219,67 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
dialog.runModal();
}break;
case MainMenuAction::OptionsMenu:
- g_engine->menu().openOptionsMenu();
+ openOptionsMenu();
break;
case MainMenuAction::Exit:
case MainMenuAction::AlsoExit:
// implemented in AlcachofaEngine as it has its own event loop
g_engine->fadeExit();
break;
+ case MainMenuAction::NewGame:
+ // this action might be unused just like the only room it would appear: MENUPRINCIPALINICIO
+ g_engine->script().createProcess(MainCharacterKind::None, g_engine->world().initScriptName());
+ break;
+ default:
+ g_engine->game().unknownMenuAction((int32)action);
+ break;
+ }
+}
+
+void MenuV1::triggerMainMenuAction(MainMenuAction action) {
+ auto updateMusicVolume = [&] (int delta) {
+ auto &config = g_engine->config();
+ int volume = config.musicVolume();
+ volume = volume + (delta * Audio::Mixer::kMaxChannelVolume / 100);
+ volume = CLIP(volume, 0, (int)Audio::Mixer::kMaxChannelVolume);
+ config.musicVolume() = volume;
+ ConfMan.setInt("music_volume", config.musicVolume());
+ g_engine->syncSoundSettings();
+ // we do not use saveToScummVM here to avoid frequent disk flushes
+ };
+
+ const uint maxSavegameI = _currentState == MainMenuAction::Load
+ ? _savefiles.size()
+ : _savefiles.size() + 1; // the "new savegame" slot
+ switch (action) {
+ case MainMenuAction::Load:
+ if (!isOnNewSlot())
+ Menu::triggerMainMenuAction(action);
+ break;
+ case MainMenuAction::PrevSave:
+ if (_currentState == MainMenuAction::OptionsMenu)
+ updateMusicVolume(-10);
+ else {
+ _selectedSavefileI = _selectedSavefileI == 0 ? maxSavegameI - 1 : _selectedSavefileI - 1;
+ updateSelectedSavefile(false);
+ }
+ break;
+ case MainMenuAction::NextSave:
+ if (_currentState == MainMenuAction::OptionsMenu)
+ updateMusicVolume(+10);
+ else {
+ _selectedSavefileI = maxSavegameI == 0 ? 0 : (_selectedSavefileI + 1) % maxSavegameI;
+ updateSelectedSavefile(false);
+ }
+ break;
+ default:
+ Menu::triggerMainMenuAction(action);
+ break;
+ }
+}
+
+void MenuV3::triggerMainMenuAction(MainMenuAction action) {
+ switch (action) {
case MainMenuAction::NextSave:
if (_selectedSavefileI < _savefiles.size()) {
_selectedSavefileI++;
@@ -213,12 +292,8 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
updateSelectedSavefile(false);
}
break;
- case MainMenuAction::NewGame:
- // this action might be unused just like the only room it would appear: MENUPRINCIPALINICIO
- g_engine->script().createProcess(MainCharacterKind::None, g_engine->world().initScriptName());
- break;
default:
- warning("Unknown main menu action: %d", (int32)action);
+ Menu::triggerMainMenuAction(action);
break;
}
}
@@ -236,16 +311,15 @@ void Menu::triggerLoad() {
void Menu::triggerSave() {
String fileName;
- if (_selectedSavefileI < _savefiles.size()) {
- fileName = _savefiles[_selectedSavefileI]; // overwrite a previous save
- } else {
+ if (isOnNewSlot()) {
// for a new savefile we figure out the next slot index
int nextSlot = _savefiles.empty()
? 1 // start at one to keep autosave alone
: parseSavestateSlot(_savefiles.back()) + 1;
fileName = g_engine->getSaveStateName(nextSlot);
_selectedSavefileDescription = String::format("Savestate %d", nextSlot);
- }
+ } else
+ fileName = _savefiles[_selectedSavefileI]; // overwrite a previous save
Error error(kNoError);
auto savefile = ScopedPtr<OutSaveFile>(_saveFileMgr->openForSaving(fileName));
@@ -255,7 +329,7 @@ void Menu::triggerSave() {
error = g_engine->saveGameStream(savefile.get());
if (error.getCode() == kNoError) {
g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), _selectedSavefileDescription, false);
- if (_selectedSavefileI >= _savefiles.size())
+ if (isOnNewSlot())
_savefiles.push_back(fileName);
updateSelectedSavefile(true);
} else {
@@ -269,7 +343,9 @@ void Menu::openOptionsMenu() {
g_engine->player().changeRoom("MENUOPCIONES", true);
}
-void Menu::setOptionsState() {
+void MenuV1::setOptionsState() {}
+
+void MenuV3::setOptionsState() {
Config &config = g_engine->config();
Room *optionsMenu = g_engine->world().getRoomByName("MENUOPCIONES");
scumm_assert(optionsMenu != nullptr);
@@ -359,7 +435,7 @@ void Menu::triggerOptionsValue(OptionsMenuValue valueId, float value) {
void Menu::continueMainMenu() {
g_engine->config().saveToScummVM();
g_engine->syncSoundSettings();
- g_engine->player().changeRoom("MENUPRINCIPAL", true);
+ g_engine->player().changeRoom(g_engine->game().getMenuRoom(), true);
updateSelectedSavefile(false);
}
@@ -368,4 +444,18 @@ const Graphics::Surface *Menu::getBigThumbnail() const {
return _bigThumbnail.empty() ? nullptr : &_bigThumbnail.rawSurface();
}
+void MenuV1::switchToState(MainMenuAction state) {
+ _currentState = state;
+
+ if (state == MainMenuAction::Load) {
+ if (isOnNewSlot()) {
+ _selectedSavefileI = _savefiles.empty() ? 0 : _savefiles.size() - 1;
+ updateSelectedSavefile(false);
+ }
+ } else if (state == MainMenuAction::Save) {
+ _selectedSavefileI = _savefiles.size();
+ updateSelectedSavefile(false);
+ }
+}
+
}
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 2a3dc7fd736..1194cb42f33 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -38,7 +38,9 @@ enum class MainMenuAction : int32 {
NextSave,
PrevSave,
NewGame,
- AlsoExit // there seems to be no difference to Exit
+ AlsoExit, // there seems to be no difference to Exit
+
+ ConfirmSavestate, // only used in V1
};
enum class OptionsMenuAction : int32 {
@@ -59,18 +61,18 @@ enum class OptionsMenuValue : int32 {
class Menu {
public:
Menu();
+ virtual ~Menu();
inline bool isOpen() const { return _isOpen; }
inline uint32 millisBeforeMenu() const { return _millisBeforeMenu; }
inline Room *previousRoom() { return _previousRoom; }
inline FakeSemaphore &interactionSemaphore() { return _interactionSemaphore; }
- void resetAfterLoad();
- void updateOpeningMenu();
- void triggerMainMenuAction(MainMenuAction action);
void triggerLoad();
+ void resetAfterLoad();
+ virtual void updateOpeningMenu();
- void openOptionsMenu();
+ virtual void triggerMainMenuAction(MainMenuAction action);
void triggerOptionsAction(OptionsMenuAction action);
void triggerOptionsValue(OptionsMenuValue valueId, float value);
@@ -79,13 +81,16 @@ public:
// as such - may return nullptr
const Graphics::Surface *getBigThumbnail() const;
-private:
+protected:
+ inline bool isOnNewSlot() const { return _selectedSavefileI >= _savefiles.size(); }
+ virtual void updateSelectedSavefile(bool hasJustSaved);
+ virtual void setOptionsState() = 0;
+
+ void openOptionsMenu();
void triggerSave();
- void updateSelectedSavefile(bool hasJustSaved);
bool tryReadOldSavefile();
void continueGame();
void continueMainMenu();
- void setOptionsState();
bool
_isOpen = false,
@@ -103,6 +108,31 @@ private:
Common::SaveFileManager *_saveFileMgr;
};
+class MenuV3 : public Menu {
+public:
+ void triggerMainMenuAction(MainMenuAction action) override;
+
+protected:
+ void updateSelectedSavefile(bool hasJustSaved) override;
+ void setOptionsState() override;
+};
+
+class MenuV1 : public Menu {
+public:
+ void updateOpeningMenu() override;
+ void triggerMainMenuAction(MainMenuAction action) override;
+
+protected:
+ void updateSelectedSavefile(bool hasJustSaved) override;
+ void setOptionsState() override;
+
+private:
+ friend class ButtonV1;
+ void switchToState(MainMenuAction state);
+
+ MainMenuAction _currentState = MainMenuAction::ConfirmSavestate;
+};
+
}
#endif // ALCACHOFA_MENU_H
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index ae5125b352d..003c7be738a 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -24,6 +24,7 @@
#include "alcachofa/shape.h"
#include "alcachofa/graphics.h"
+#include "alcachofa/menu.h"
#include "common/serializer.h"
@@ -164,14 +165,20 @@ public:
static constexpr const char *kClassName = "CBoton";
ButtonV1(Room *room, Common::SeekableReadStream &stream);
- inline byte actionId() const { return _actionId; }
- inline const Common::String &actionName() const { return _actionName; }
-
+ void loadResources() override;
+ void draw();
+ void update() override;
+ void onHoverUpdate() override;
+ void onClick() override;
const char *typeName() const override;
private:
- byte _actionId;
- Common::String _actionName;
+ MenuV1 &menu();
+
+ MainMenuAction _action = {};
+ Common::String _graphicName;
+ ObjectBase *_graphicObject = nullptr;
+ bool _isHovered = false;
};
class MenuButton : public PhysicalObject {
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 79b35bba11c..ca39d4c253f 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -33,9 +33,140 @@ namespace Alcachofa {
const char *ButtonV1::typeName() const { return "ButtonV1"; }
ButtonV1::ButtonV1(Room *room, Common::SeekableReadStream &stream)
- : PhysicalObject(room, stream)
- , _actionId(stream.readByte())
- , _actionName(readVarString(stream)) {}
+ : PhysicalObject(room, stream) {
+ byte actionId = stream.readByte();
+ _graphicName = readVarString(stream);
+
+ switch (actionId) {
+ case 0:
+ _action = MainMenuAction::Save;
+ break;
+ case 1:
+ _action = MainMenuAction::Load;
+ break;
+ case 2:
+ _action = MainMenuAction::ContinueGame;
+ break;
+ case 3:
+ _action = MainMenuAction::Exit;
+ break;
+ case 4:
+ _action = MainMenuAction::InternetMenu;
+ break;
+ case 5:
+ _action = MainMenuAction::OptionsMenu;
+ break;
+ case 6:
+ _action = MainMenuAction::PrevSave;
+ break;
+ case 7:
+ _action = MainMenuAction::NextSave;
+ break;
+ case 10:
+ _action = MainMenuAction::ConfirmSavestate;
+ break;
+ default:
+ g_engine->game().unknownMenuAction(actionId);
+ break;
+ }
+}
+
+void ButtonV1::loadResources() {
+ if (!_graphicName.empty()) {
+ _graphicObject = room()->getObjectByName(_graphicName.c_str());
+ scumm_assert(_graphicObject != nullptr);
+ _graphicObject->toggle(false);
+ }
+}
+
+void ButtonV1::draw() {
+ if (menu()._currentState != _action)
+ return;
+
+ static constexpr uint kBufferSize = 32;
+ static char buffer[kBufferSize];
+ const char *text = nullptr;
+ switch (_action) {
+ case MainMenuAction::Save:
+ if (menu().isOnNewSlot())
+ text = "ENTRAR VACIA";
+ break;
+ case MainMenuAction::OptionsMenu:
+ int volumePercent = g_engine->config().musicVolume() * 10 / Audio::Mixer::kMaxChannelVolume;
+ snprintf(buffer, kBufferSize, "%s: %d", "VOLUMEN", volumePercent * 10);
+ text = buffer;
+ break;
+ }
+ if (text != nullptr) {
+ g_engine->drawQueue().add<TextDrawRequest>(
+ g_engine->globalUI().dialogFont(),
+ text,
+ Point(300, 250),
+ 200,
+ true,
+ kWhite,
+ 1
+ );
+ }
+}
+
+void ButtonV1::update() {
+ PhysicalObject::update();
+
+ if (_graphicObject != nullptr)
+ _graphicObject->toggle(_isHovered || menu()._currentState == _action);
+ _isHovered = false;
+
+ if (_action == MainMenuAction::ContinueGame && g_engine->input().wasMenuKeyPressed())
+ onClick();
+}
+
+void ButtonV1::onHoverUpdate() {
+ // we mainly override this function to disable drawing the object name
+
+ // only savegame selection buttons highlight on hover
+ if (_action == MainMenuAction::PrevSave || _action == MainMenuAction::NextSave)
+ _isHovered = true;
+}
+
+void ButtonV1::onClick() {
+ auto &menuState = menu()._currentState;
+ switch (_action) {
+ case MainMenuAction::Save:
+ case MainMenuAction::Load:
+ case MainMenuAction::OptionsMenu:
+ g_engine->config().saveToScummVM();
+ menu().switchToState(_action);
+ break;
+ case MainMenuAction::NextSave:
+ case MainMenuAction::PrevSave:
+ if (menuState == MainMenuAction::Load ||
+ menuState == MainMenuAction::Save ||
+ menuState == MainMenuAction::OptionsMenu)
+ g_engine->menu().triggerMainMenuAction(_action);
+ break;
+ case MainMenuAction::ContinueGame:
+ case MainMenuAction::InternetMenu:
+ case MainMenuAction::Exit:
+ g_engine->config().saveToScummVM();
+ g_engine->menu().triggerMainMenuAction(_action);
+ break;
+ case MainMenuAction::ConfirmSavestate:
+ if (menuState == MainMenuAction::Load ||
+ menuState == MainMenuAction::Save)
+ g_engine->menu().triggerMainMenuAction(menuState);
+ break;
+ default:
+ assert(false && "Unimplemented ButtonV1 action");
+ break;
+ }
+}
+
+MenuV1 &ButtonV1::menu() {
+ auto menuPtr = dynamic_cast<MenuV1 *>(&g_engine->menu());
+ assert(menuPtr != nullptr);
+ return *menuPtr;
+}
const char *MenuButton::typeName() const { return "MenuButton"; }
Commit: a304228f6f9a70ab44edae439f04030947e38221
https://github.com/scummvm/scummvm/commit/a304228f6f9a70ab44edae439f04030947e38221
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:30+01:00
Commit Message:
ALCACHOFA: Add subtitles toggle key
Changed paths:
engines/alcachofa/input.cpp
engines/alcachofa/input.h
engines/alcachofa/metaengine.cpp
engines/alcachofa/metaengine.h
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index bbf89cbfa5e..a883907aca1 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -37,6 +37,7 @@ void Input::nextFrame() {
_wasMouseRightReleased = false;
_wasMenuKeyPressed = false;
_wasInventoryKeyPressed = false;
+ _wasSubtitlesKeyPressed = false;
updateMousePos3D(); // camera transformation might have changed
}
@@ -77,6 +78,9 @@ bool Input::handleEvent(const Common::Event &event) {
case EventAction::InputInventory:
_wasInventoryKeyPressed = true;
return true;
+ case EventAction::InputSubtitles:
+ _wasSubtitlesKeyPressed = true;
+ return true;
default:
return false;
}
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index 71285556b7f..11e03db9706 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -43,6 +43,7 @@ public:
inline bool isAnyMouseDown() const { return _isMouseLeftDown || _isMouseRightDown; }
inline bool wasMenuKeyPressed() const { return _wasMenuKeyPressed; }
inline bool wasInventoryKeyPressed() const { return _wasInventoryKeyPressed; }
+ inline bool wasSubtitlesKeyPressed() const { return _wasSubtitlesKeyPressed; }
inline Common::Point mousePos2D() const { return _mousePos2D; }
inline Common::Point mousePos3D() const { return _mousePos3D; }
const Input &debugInput() const { scumm_assert(_debugInput != nullptr); return *_debugInput; }
@@ -63,7 +64,8 @@ private:
_isMouseLeftDown = false,
_isMouseRightDown = false,
_wasMenuKeyPressed = false,
- _wasInventoryKeyPressed = false;
+ _wasInventoryKeyPressed = false,
+ _wasSubtitlesKeyPressed = false;
Common::Point
_mousePos2D,
_mousePos3D;
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index a8c57db2e06..c53bf7b5168 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -105,7 +105,12 @@ KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
act = new Action("INVENTORY", _("Inventory"));
act->setCustomEngineActionEvent((CustomEventType)EventAction::InputInventory);
act->addDefaultInputMapping("SPACE");
- act->addDefaultInputMapping("JOY_B");
+ act->addDefaultInputMapping("JOY_X");
+ keymap->addAction(act);
+
+ act = new Action("SUBTITLES", _("Toggle subtitles"));
+ act->setCustomEngineActionEvent((CustomEventType)EventAction::InputSubtitles);
+ act->addDefaultInputMapping("C+t");
keymap->addAction(act);
return Keymap::arrayOf(keymap);
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index a01f695668c..f5a3a73eadc 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -29,7 +29,8 @@ namespace Alcachofa {
enum class EventAction {
LoadFromMenu,
InputMenu,
- InputInventory
+ InputInventory,
+ InputSubtitles
};
}
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 183a87ba814..044ff6769dd 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -39,6 +39,13 @@ Player::Player()
void Player::preUpdate() {
_selectedObject = nullptr;
_cursorFrameI = 0;
+
+ if (g_engine->input().wasSubtitlesKeyPressed()) {
+ auto &config = g_engine->config();
+ config.subtitles() = !config.subtitles();
+ config.saveToScummVM();
+ debug(1, "Toggled subtitles to %s", config.subtitles() ? "on" : "off");
+ }
}
void Player::postUpdate() {
Commit: f5d7db401713c6858dc4b1f85bf0f752148ecda1
https://github.com/scummvm/scummvm/commit/f5d7db401713c6858dc4b1f85bf0f752148ecda1
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:30+01:00
Commit Message:
ALCACHOFA: V1: Fix subtitle position
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 453db2c7c9a..69a32a5691f 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -179,6 +179,10 @@ public:
return kNoXORKey;
}
+ Point getSubtitlePos() override {
+ return Point(g_system->getWidth() / 2, 150);
+ }
+
const char *getMenuRoom() override {
return "MENU";
}
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index da35b7a0ff8..3bfcd72d60f 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -232,6 +232,10 @@ public:
return kEmbeddedXORKey;
}
+ Point getSubtitlePos() override {
+ return Point(g_system->getWidth() / 2, g_system->getHeight() - 200);
+ }
+
const char *getMenuRoom() override {
return "MENUPRINCIPAL";
}
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 4ea63b6bb76..620d996c065 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -350,7 +350,7 @@ struct SayTextTask final : public Task {
g_engine->drawQueue().add<TextDrawRequest>(
g_engine->globalUI().dialogFont(),
g_engine->world().getDialogLine(_dialogId),
- Point(g_system->getWidth() / 2, g_system->getHeight() - 200),
+ g_engine->game().getSubtitlePos(),
-1, true, kWhite, -kForegroundOrderCount);
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index a626d0f8ec5..3cb30f64c19 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -62,6 +62,7 @@ public:
virtual const char *getDialogFileName() = 0;
virtual const char *getObjectFileName() = 0;
virtual char getTextFileKey() = 0;
+ virtual Common::Point getSubtitlePos() = 0;
virtual const char *getMenuRoom() = 0;
virtual const char *getInitScriptName() = 0;
virtual int32 getKernelTaskArgCount(int32 kernelTaskI); // only necessary for V1
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 470264603b5..13cdbeed73e 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -865,10 +865,15 @@ static char *trimTrailing(char *start, char *end) {
}
void World::loadLocalizedNames() {
- loadEncryptedFile(
- g_engine->game().getObjectFileName(),
- g_engine->game().getTextFileKey(),
- _namesChunk);
+ const char *filename = g_engine->game().getObjectFileName();
+ char textFileKey = g_engine->game().getTextFileKey();
+#ifdef ALCACHOFA_DEBUG
+ if (File::exists("OBJETOS.MOD.TXT")) {
+ filename = "OBJETOS.MOD.TXT";
+ textFileKey = 0;
+ }
+#endif
+ loadEncryptedFile(filename, textFileKey, _namesChunk);
char *lineStart = _namesChunk.begin(), *fileEnd = _namesChunk.end() - 1;
if (*lineStart == '\"') {
@@ -921,10 +926,15 @@ void World::loadDialogLines() {
* - The ID does not have to be correct, it is ignored by the original engine.
* - We only need the dialog line and insert null-terminators where appropriate.
*/
- loadEncryptedFile(
- g_engine->game().getDialogFileName(),
- g_engine->game().getTextFileKey(),
- _dialogChunk);
+ const char *filename = g_engine->game().getDialogFileName();
+ char textFileKey = g_engine->game().getTextFileKey();
+#ifdef ALCACHOFA_DEBUG
+ if (File::exists("TEXTOS.MOD.TXT")) {
+ filename = "TEXTOS.MOD.TXT";
+ textFileKey = 0;
+ }
+#endif
+ loadEncryptedFile(filename, textFileKey, _dialogChunk);
char *lineStart = _dialogChunk.begin(), *fileEnd = _dialogChunk.end() - 1;
while (lineStart < fileEnd) {
char *lineEnd = find(lineStart, fileEnd, '\n');
Commit: 72f6a63534ec26aff3ff8aaf0495e33c5fc8e4fc
https://github.com/scummvm/scummvm/commit/72f6a63534ec26aff3ff8aaf0495e33c5fc8e4fc
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:34+01:00
Commit Message:
ALCACHOFA: Add call address stack
In V1 instruction addresses are stored on a different stack, luckily a two-stack-solution is compatible to V3
Changed paths:
engines/alcachofa/common.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index c30e05631ed..b498a7b5bc7 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -154,7 +154,14 @@ inline void syncArray(Common::Serializer &serializer, Common::Array<T> &array, v
}
template<typename T>
-inline void syncStack(Common::Serializer &serializer, Common::Stack<T> &stack, void (*serializeFunction)(Common::Serializer &, T &)) {
+inline void syncStack(
+ Common::Serializer &serializer,
+ Common::Stack<T> &stack,
+ void (*serializeFunction)(Common::Serializer &, T &),
+ Common::Serializer::Version minVersion = 0) {
+ if (serializer.getVersion() < minVersion)
+ return;
+
auto size = stack.size();
serializer.syncAsUint32LE(size);
if (serializer.isLoading()) {
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 2b9d21114f4..0353cc011e9 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -107,6 +107,10 @@ static void syncAsSint32LE(Serializer &s, int32 &value) {
s.syncAsSint32LE(value);
}
+static void syncAsUint32LE(Serializer &s, uint32 &value) {
+ s.syncAsUint32LE(value);
+}
+
void Script::syncGame(Serializer &s) {
s.syncArray(_variables.data(), _variables.size(), syncAsSint32LE);
}
@@ -224,8 +228,8 @@ struct ScriptTask final : public Task {
, _name(forkParent._name + " FORKED")
, _pc(forkParent._pc)
, _characterLock(forkParent._characterLock) {
- for (uint i = 0; i < forkParent._stack.size(); i++)
- _stack.push(forkParent._stack[i]);
+ for (uint i = 0; i < forkParent._valueStack.size(); i++)
+ _valueStack.push(forkParent._valueStack[i]);
pushNumber(1); // this task is the forked one
debugC(SCRIPT_DEBUG_LVL_TASKS, kDebugScript, "%u: Script fork from %u at %u", process.pid(), forkParent.process().pid(), _pc);
}
@@ -258,10 +262,10 @@ struct ScriptTask final : public Task {
debugN("%u: %5u %-12s %8d Stack: ",
process().pid(), _pc - 1, opName, instruction._arg);
- if (_stack.empty())
+ if (_valueStack.empty())
debug("empty");
else {
- const auto &top = _stack.top();
+ const auto &top = _valueStack.top();
switch (top._type) {
case StackEntryType::Number:
debug("Number %d", top._number);
@@ -289,9 +293,9 @@ struct ScriptTask final : public Task {
switch (opMap[instruction._op]) {
case ScriptOp::Nop: break;
case ScriptOp::Dup:
- if (_stack.empty())
+ if (_valueStack.empty())
error("Script tried to duplicate stack top, but stack is empty");
- _stack.push(_stack.top());
+ _valueStack.push(_valueStack.top());
break;
case ScriptOp::PushAddr:
pushVariable(instruction._arg);
@@ -414,23 +418,32 @@ struct ScriptTask final : public Task {
}
void syncGame(Serializer &s) override {
- assert(s.isSaving() || (_characterLock.isReleased() && _stack.empty()));
+ assert(s.isSaving() || (
+ _characterLock.isReleased() && _valueStack.empty() && _callStack.empty()));
s.syncString(_name);
s.syncAsUint32LE(_pc);
s.syncAsByte(_returnsFromKernelCall);
s.syncAsByte(_isFirstExecution);
- uint count = _stack.size();
+ uint count = _valueStack.size();
s.syncAsUint32LE(count);
if (s.isLoading()) {
- for (uint i = 0; i < count; i++)
- _stack.push(StackEntry(s));
+ for (uint i = 0; i < count; i++) {
+ const StackEntry entry(s);
+ if (entry._type == StackEntryType::Instruction)
+ // we still have to support old saves
+ _callStack.push(entry._index);
+ else
+ _valueStack.push(entry);
+ }
} else {
for (uint i = 0; i < count; i++)
- _stack[i].syncGame(s);
+ _valueStack[i].syncGame(s);
}
+ syncStack(s, _callStack, syncAsUint32LE, SaveVersion::kWithEngineV10);
+
bool hasLock = !_characterLock.isReleased();
s.syncAsByte(hasLock);
if (s.isLoading() && hasLock)
@@ -467,7 +480,7 @@ private:
}
void pushNumber(int32 value) {
- _stack.push({ StackEntryType::Number, value });
+ _valueStack.push({ StackEntryType::Number, value });
}
// For the following methods error recovery is not really viable
@@ -476,23 +489,23 @@ private:
uint32 index = offset / sizeof(int32);
if (offset % sizeof(int32) != 0 || index >= _script._variables.size())
error("Script tried to push invalid variable offset");
- _stack.push({ StackEntryType::Variable, index });
+ _valueStack.push({ StackEntryType::Variable, index });
}
void pushString(uint32 offset) {
if (offset >= _script._strings->size())
error("Script tried to push invalid string offset");
- _stack.push({ StackEntryType::String, offset });
+ _valueStack.push({ StackEntryType::String, offset });
}
void pushInstruction(uint32 pc) {
- _stack.push({ StackEntryType::Instruction, pc });
+ _callStack.push(pc);
}
StackEntry pop() {
- if (_stack.empty())
+ if (_valueStack.empty())
error("Script tried to pop empty stack");
- return _stack.pop();
+ return _valueStack.pop();
}
int32 popNumber() {
@@ -517,23 +530,24 @@ private:
}
uint32 popInstruction() {
- auto entry = pop();
- if (entry._type != StackEntryType::Instruction)
- error("Script tried to pop but top of stack is not an instruction");
- return entry._index;
+ if (_callStack.empty()) {
+ warning("Script tried to pop empty call stack");
+ return UINT_MAX; // this will just exit script execution
+ }
+ return _callStack.pop();
}
void popN(int32 count) {
- if (count < 0 || (uint)count > _stack.size())
+ if (count < 0 || (uint)count > _valueStack.size())
error("Script tried to pop more entries than are available on the stack");
for (int32 i = 0; i < count; i++)
- _stack.pop();
+ _valueStack.pop();
}
StackEntry getArg(uint argI) {
- if (_stack.size() < argI + 1)
+ if (_valueStack.size() < argI + 1)
error("Script did not supply enough arguments for kernel call");
- return _stack[_stack.size() - 1 - argI];
+ return _valueStack[_valueStack.size() - 1 - argI];
}
int32 getNumberArg(uint argI) {
@@ -1044,7 +1058,10 @@ private:
}
Script &_script;
- Stack<StackEntry> _stack;
+ // in V3 value and call return addresses used the same stack
+ // in V1 they are separate, but the two-stack-solution should be compatible with V3
+ Stack<StackEntry> _valueStack;
+ Stack<uint32> _callStack;
String _name;
uint32 _pc = 0;
int32 _lastKernelTaskI = 0;
Commit: 6ab60b86483e4a6aa2e638e82d5a67abac7773b3
https://github.com/scummvm/scummvm/commit/6ab60b86483e4a6aa2e638e82d5a67abac7773b3
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:34+01:00
Commit Message:
ALCACHOFA: V1: Fix missing character icon during dialog menus
Changed paths:
engines/alcachofa/game-objects.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 620d996c065..207b618b147 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -1074,6 +1074,7 @@ struct DialogMenuTask final : public Task {
TaskReturn run() override {
TASK_BEGIN;
layoutLines();
+ process().unlockInteraction();
while (true) {
TASK_YIELD(1);
if (g_engine->player().activeCharacter() != _character) //-V779
@@ -1085,6 +1086,7 @@ struct DialogMenuTask final : public Task {
_clickedLineI = updateLines();
if (_clickedLineI != UINT_MAX) {
TASK_YIELD(2);
+ process().lockInteraction();
TASK_WAIT(3, _character->sayText(process(), _character->_dialogLines[_clickedLineI]._dialogId));
int32 returnValue = _character->_dialogLines[_clickedLineI]._returnValue;
_character->_dialogLines.clear();
Commit: f6f596eb1e3a91176b45f846777c423bda82e43d
https://github.com/scummvm/scummvm/commit/f6f596eb1e3a91176b45f846777c423bda82e43d
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:34+01:00
Commit Message:
ALCACHOFA: V1: Fix various original bugs in POBLADO_INDIO
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 69a32a5691f..f846eaa3d81 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -248,6 +248,13 @@ public:
return false;
}
+ Character *unknownSayTextCharacter(const char *name, int32 dialogId) override {
+ // an original bug in room POBLADO_INDIO, a dialog line would be skipped
+ if (!scumm_stricmp(name, "JEFE_INDIO_HABLA_POSTE"))
+ return dynamic_cast<Character *>(g_engine->player().currentRoom()->getObjectByName("JEFE_HABLA_POSTE"));
+ return Game::unknownSayTextCharacter(name, dialogId);
+ }
+
void missingAnimation(const String &fileName) override {
static const char *exemptions[] = {
nullptr
@@ -266,6 +273,39 @@ public:
else
Game::missingAnimation(fileName);
}
+
+ 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
+ // However if the user now changes character, the door is still enabled and filemon can
+ // enter a ghost state walking through a couple rooms and softlocking.
+ if (g_engine->player().currentRoom()->name().equalsIgnoreCase("POBLADO_INDIO"))
+ g_engine->script().createProcess(g_engine->player().activeCharacterKind(), "ENTRAR_POBLADO_INDIO");
+ }
+
+ 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;
+ }
+
+ return Game::unknownGoPutTarget(process, action, name);
+ }
+
+ void missingSound(const Common::String &fileName) override {
+ if (fileName == "CHAS")
+ return;
+ return Game::missingSound(fileName);
+ }
};
Game *Game::createForMovieAdventureOriginal() {
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 3bfcd72d60f..51cdea7e01f 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -501,10 +501,10 @@ public:
Game::unknownAnimateObject(name);
}
- void unknownSayTextCharacter(const char *name, int32 dialogId) override {
+ Character *unknownSayTextCharacter(const char *name, int32 dialogId) override {
if (!scumm_stricmp(name, "OFELIA") && dialogId == 3737)
- return;
- Game::unknownSayTextCharacter(name, dialogId);
+ return nullptr;
+ return Game::unknownSayTextCharacter(name, dialogId);
}
void missingSound(const String &fileName) override {
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 9ed6a7dfefc..32e87807a0f 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -148,8 +148,9 @@ void Game::missingAnimation(const String &fileName) {
_message("Could not open animation %s", fileName.c_str());
}
-void Game::unknownSayTextCharacter(const char *name, int32) {
+Character *Game::unknownSayTextCharacter(const char *name, int32) {
unknownScriptCharacter("say text", name);
+ return nullptr;
}
void Game::unknownChangeCharacterRoom(const char *name) {
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 3cb30f64c19..1d3cf800107 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -103,7 +103,7 @@ public:
virtual PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name); ///< May return an alternative target to use
virtual void unknownChangeCharacterRoom(const char *name);
virtual void unknownAnimateCharacterObject(const char *name);
- virtual void unknownSayTextCharacter(const char *name, int32 dialogId);
+ virtual Character *unknownSayTextCharacter(const char *name, int32 dialogId);
virtual void unknownAnimateTalkingObject(const char *name);
virtual void unknownClearInventoryTarget(int characterKind);
virtual void unknownCamLerpTarget(const char *action, const char *name);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 0353cc011e9..54acc48b28a 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -867,10 +867,10 @@ private:
Character *_character = strcmp(characterName, "AMBOS") == 0
? &relatedCharacter()
: getObjectArg<Character>(0);
- if (_character == nullptr) {
- g_engine->game().unknownSayTextCharacter(characterName, dialogId);
+ if (_character == nullptr)
+ _character = g_engine->game().unknownSayTextCharacter(characterName, dialogId);
+ if (_character == nullptr)
return TaskReturn::finish(1);
- }
return TaskReturn::waitFor(_character->sayText(process(), dialogId));
};
case ScriptKernelTask::SetDialogLineReturn:
Commit: ff43ad36dc2829b2f4dcfad787f12dbd9bfaa0b7
https://github.com/scummvm/scummvm/commit/ff43ad36dc2829b2f4dcfad787f12dbd9bfaa0b7
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix object ref from other room
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index f846eaa3d81..c1cddf8ba70 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -283,6 +283,25 @@ public:
g_engine->script().createProcess(g_engine->player().activeCharacterKind(), "ENTRAR_POBLADO_INDIO");
}
+ PointObject *getPointFromRoom(const char *roomName, const char *objectName, const char *action) {
+ const auto *room = g_engine->world().getRoomByName(roomName);
+ if (room == nullptr) {
+ _message("Could not find room %s to get %s for %s");
+ return nullptr;
+ }
+ auto *object = room->getObjectByName(objectName);
+ if (object == nullptr) {
+ _message("Could not find object %s in room %s for %s");
+ return nullptr;
+ }
+ auto *point = dynamic_cast<PointObject *>(object);
+ if (point == nullptr) {
+ _message("Object %s in room %s for %s is not a point object");
+ return nullptr;
+ }
+ return point;
+ }
+
PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
if (scumm_stricmp(action, "put"))
return Game::unknownGoPutTarget(process, action, name);
@@ -298,9 +317,24 @@ public:
return target;
}
+ // an original bug, Pos_Final_Morta/File is defined in room ENTRADA_PUEBLO
+ // but the current room is ENTRADA_PUEBLO_INTRO
+ if (!scumm_stricmp(name, "Pos_Final_Morta"))
+ return getPointFromRoom("ENTRADA_PUEBLO", "Pos_Final_Morta", action);
+ if (!scumm_stricmp(name, "Pos_Final_File"))
+ return getPointFromRoom("ENTRADA_PUEBLO", "Pos_Final_File", action);
+
return Game::unknownGoPutTarget(process, action, name);
}
+ PointObject *unknownCamLerpTarget(const char *action, const char *name) override {
+ // the same bug as in unknownGoPutTarget
+ if (!scumm_stricmp(name, "Pos_Final_Morta"))
+ return getPointFromRoom("ENTRADA_PUEBLO", "Pos_Final_Morta", action);
+
+ return Game::unknownCamLerpTarget(action, name);
+ }
+
void missingSound(const Common::String &fileName) override {
if (fileName == "CHAS")
return;
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 32e87807a0f..8e1e640b429 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -169,8 +169,9 @@ void Game::unknownClearInventoryTarget(int characterKind) {
_message("Invalid clear inventory character kind: %d", characterKind);
}
-void Game::unknownCamLerpTarget(const char *action, const char *name) {
+PointObject *Game::unknownCamLerpTarget(const char *action, const char *name) {
_message("Invalid target object for %s: %s", action, name);
+ return nullptr;
}
void Game::unknownKernelTask(int task) {
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 1d3cf800107..95c5c79653d 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -106,7 +106,7 @@ public:
virtual Character *unknownSayTextCharacter(const char *name, int32 dialogId);
virtual void unknownAnimateTalkingObject(const char *name);
virtual void unknownClearInventoryTarget(int characterKind);
- virtual void unknownCamLerpTarget(const char *action, const char *name);
+ virtual PointObject *unknownCamLerpTarget(const char *action, const char *name);
virtual void unknownKernelTask(int task);
virtual void unknownScriptProcedure(const Common::String &procedure);
virtual void unknownMenuAction(int32 actionId);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 54acc48b28a..52284b6e5ee 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -950,10 +950,10 @@ private:
if (!process().isActiveForPlayer())
return TaskReturn::finish(0); // contrary to ...ResettingZ this one does not delay if not active
auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr) {
- g_engine->game().unknownCamLerpTarget("LerpCamToObjectKeepingZ", getStringArg(0));
+ if (pointObject == nullptr)
+ pointObject = g_engine->game().unknownCamLerpTarget("LerpCamToObjectKeepingZ", getStringArg(0));
+ if (pointObject == nullptr)
return TaskReturn::finish(1);
- }
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
as2D(pointObject->position()),
getNumberArg(1), EasingType::Linear));
@@ -962,10 +962,10 @@ private:
if (!process().isActiveForPlayer())
return TaskReturn::waitFor(delay(getNumberArg(1)));
auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr) {
- g_engine->game().unknownCamLerpTarget("LerpCamToObjectResettingZ", getStringArg(0));
+ if (pointObject == nullptr)
+ pointObject = g_engine->game().unknownCamLerpTarget("LerpCamToObjectResettingZ", getStringArg(0));
+ if (pointObject == nullptr)
return TaskReturn::finish(1);
- }
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
as3D(pointObject->position()),
getNumberArg(1), (EasingType)getNumberArg(2)));
@@ -976,10 +976,10 @@ private:
// the scale will wait then snap the scale
return TaskReturn::waitFor(g_engine->camera().lerpScale(process(), targetScale, getNumberArg(2), EasingType::Linear));
auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr) {
- g_engine->game().unknownCamLerpTarget("LerpCamToObjectWithScale", getStringArg(0));
+ if (pointObject == nullptr)
+ pointObject = g_engine->game().unknownCamLerpTarget("LerpCamToObjectWithScale", getStringArg(0));
+ if (pointObject == nullptr)
return TaskReturn::finish(1);
- }
return TaskReturn::waitFor(g_engine->camera().lerpPosScale(process(),
as3D(pointObject->position()), targetScale,
getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
Commit: b0f962d68cc0917e4614d7816734b7cd1dc1ddce
https://github.com/scummvm/scummvm/commit/b0f962d68cc0917e4614d7816734b7cd1dc1ddce
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Downport fix for Puerta_Casa_Freddy_Intermedia
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index c1cddf8ba70..53314a620b4 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -317,6 +317,11 @@ public:
return target;
}
+ if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", name)) {
+ // Another case of a door being cast into a PointObject
+ return nullptr;
+ }
+
// an original bug, Pos_Final_Morta/File is defined in room ENTRADA_PUEBLO
// but the current room is ENTRADA_PUEBLO_INTRO
if (!scumm_stricmp(name, "Pos_Final_Morta"))
Commit: 23c14147b2a0777867870ce2bf855ae35ca59612
https://github.com/scummvm/scummvm/commit/23c14147b2a0777867870ce2bf855ae35ca59612
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: Fix misuse of variable address for kernel args
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 53314a620b4..7f82b8ead8d 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -303,6 +303,9 @@ public:
}
PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
+ if (!*name) // this can happen when a variable address is misused as string
+ return nullptr;
+
if (scumm_stricmp(action, "put"))
return Game::unknownGoPutTarget(process, action, name);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 52284b6e5ee..93bc6221ec4 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -552,9 +552,15 @@ private:
int32 getNumberArg(uint argI) {
auto entry = getArg(argI);
- if (entry._type != StackEntryType::Number)
+ switch (entry._type) {
+ case StackEntryType::Number:
+ return entry._number;
+ case StackEntryType::Variable:
+ warning("Misuse of variable address as number for arg %u at %u", argI, _pc);
+ return entry._number;
+ default:
error("Expected number in argument %u for kernel call", argI);
- return entry._number;
+ }
}
MainCharacterKind getMainCharacterKindArg(uint argI) {
@@ -576,9 +582,15 @@ private:
const char *getStringArg(uint argI) {
auto entry = getArg(argI);
- if (entry._type != StackEntryType::String)
+ switch (entry._type) {
+ case StackEntryType::String:
+ return &_script._strings[entry._index];
+ case StackEntryType::Variable:
+ warning("Misuse of variable address as string for arg %u at %u", argI, _pc);
+ return "";
+ default:
error("Expected string in argument %u for kernel call", argI);
- return &_script._strings[entry._index];
+ }
}
int32 getNumberOrStringArg(uint argI) {
Commit: aff06afe3d37ecab4343fe60492f3c16c86405b3
https://github.com/scummvm/scummvm/commit/aff06afe3d37ecab4343fe60492f3c16c86405b3
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix sound paths with extension and mixed-casing
Changed paths:
engines/alcachofa/rooms.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 13cdbeed73e..bb5956b18dc 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -1069,7 +1069,7 @@ public:
private:
const SharedPtr<EmbeddedArchiveMember> getMemberInternal(const Path &path) const {
for (const auto &member : _members) {
- if (member->getPathInArchive() == path)
+ if (member->getPathInArchive().equalsIgnoreCase(path))
return member;
}
return nullptr;
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 50a028ff5d1..64a2252e5df 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -113,8 +113,11 @@ static AudioStream *loadSND(File *file) {
}
static AudioStream *openAudio(const String &basePath) {
- String path = basePath + ".SND";
File *file = new File();
+ if (file->open(basePath.c_str())) // only very rarely the basePath already has the extension
+ return makeWAVStream(file, DisposeAfterUse::YES);
+
+ String path = basePath + ".SND";
if (file->open(path.c_str()))
return file->size() == 0 // Movie Adventure has some null-size audio files, they are treated like infinite silence
? makeSilentAudioStream(8000, false)
Commit: 07805df5966b9d233377299c52fca120948e01c9
https://github.com/scummvm/scummvm/commit/07805df5966b9d233377299c52fca120948e01c9
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: Fix invalid music when changing chars
Changed paths:
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 64a2252e5df..71559cc562e 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -311,6 +311,8 @@ void Sounds::startMusic(int musicId) {
debugC(2, kDebugSounds, "startMusic %d", musicId);
assert(musicId >= 0);
fadeMusic();
+ if (musicId == 0)
+ return;
auto path = g_engine->game().getMusicPath(musicId);
_musicSoundID = playSoundInternal(path, Mixer::kMaxChannelVolume, Mixer::kMusicSoundType);
_isMusicPlaying = true;
Commit: 5fdf0dfd9933cc03a72f5b02ae8a674fea540c80
https://github.com/scummvm/scummvm/commit/5fdf0dfd9933cc03a72f5b02ae8a674fea540c80
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Adapt ScriptTimerTask
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 7f82b8ead8d..38e91a6a665 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -86,7 +86,7 @@ static constexpr const ScriptKernelTask kScriptKernelTaskMap[] = {
ScriptKernelTask::Off,
ScriptKernelTask::Pickup,
ScriptKernelTask::Animate,
- ScriptKernelTask::SheriffTakesCharacter,
+ ScriptKernelTask::HadNoMousePressFor,
ScriptKernelTask::ChangeCharacter,
ScriptKernelTask::LerpCamToObjectKeepingZ,
ScriptKernelTask::Drop,
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 93bc6221ec4..99dfe140878 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -153,12 +153,20 @@ struct ScriptTimerTask final : public Task {
TaskReturn run() override {
TASK_BEGIN;
{
- uint32 timeSinceTimer = g_engine->script()._scriptTimer == 0 ? 0
- : (g_engine->getMillis() - g_engine->script()._scriptTimer) / 1000;
+ _didPressMouse |= g_engine->input().wasAnyMousePressed();
+
+ // only in V31 there is the variable "CalcularTiempoSinPulsarRaton" which controls
+ // resetting the timer, for all other versions we have to do it implicitly anyways
+ if (g_engine->script()._scriptTimer == 0)
+ g_engine->script()._scriptTimer = g_engine->getMillis();
+
+ uint32 timeSinceTimer = (g_engine->getMillis() - g_engine->script()._scriptTimer) / 1000;
if (_durationSec >= (int32)timeSinceTimer)
- _result = g_engine->script().variable("SeHaPulsadoRaton") ? 0 : 2;
- else
+ _result = _didPressMouse ? 0 : 2;
+ else {
_result = 1;
+ g_engine->script()._scriptTimer = 0;
+ }
g_engine->player().drawCursor();
}
TASK_YIELD(1); // Wait a frame to not produce an endless loop
@@ -174,6 +182,7 @@ struct ScriptTimerTask final : public Task {
Task::syncGame(s);
s.syncAsSint32LE(_durationSec);
s.syncAsSint32LE(_result);
+ s.syncAsByte(_didPressMouse, SaveVersion::kWithEngineV10);
}
const char *taskName() const override;
@@ -181,6 +190,7 @@ struct ScriptTimerTask final : public Task {
private:
int32 _durationSec = 0;
int32 _result = 1;
+ bool _didPressMouse = false;
};
DECLARE_TASK(ScriptTimerTask)
@@ -678,8 +688,10 @@ private:
return getNumberArg(0) <= 0
? TaskReturn::finish(0)
: TaskReturn::waitFor(delay((uint32)getNumberArg(0)));
- case ScriptKernelTask::HadNoMousePressFor:
- return TaskReturn::waitFor(new ScriptTimerTask(process(), getNumberArg(0)));
+ case ScriptKernelTask::HadNoMousePressFor: {
+ auto duration = g_engine->isV1() ? 60 : getNumberArg(0);
+ return TaskReturn::waitFor(new ScriptTimerTask(process(), duration));
+ }
case ScriptKernelTask::Fork:
g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
return TaskReturn::finish(0); // 0 means this is the forking process
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index e9cb9eb2846..e2595794fb1 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -126,8 +126,7 @@ enum class ScriptKernelTask {
LerpCamXYZ,
LerpCamToObjectKeepingZ,
- SheriffTakesCharacter, ///< some special-case V1 tasks, unknown yet
- ChangeDoor,
+ ChangeDoor, ///< some special-case V1 tasks, unknown yet
Disguise
};
Commit: afa758a8e8487872dc0e7e1d4617f820b93fa834
https://github.com/scummvm/scummvm/commit/afa758a8e8487872dc0e7e1d4617f820b93fa834
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Add support for Terror and Vaqueros
Changed paths:
engines/alcachofa/detection_tables.h
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index b907e020ed1..9a0bfe6aafd 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -23,6 +23,8 @@ 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"},
{ 0, 0 }
};
@@ -146,6 +148,38 @@ const AlcachofaGameDescription gameDescriptions[] = {
EngineVersion::V1_0
},
+ //
+ // Terror, Espanto y Pavor
+ //
+ {
+ // Install folder from the disk/the ISO within the Steam distribution
+ {
+ "terror",
+ "Mortadelo y Filemón: Terror, Espanto y Pavor",
+ AD_ENTRY1s("terror.emc", "dc9357ee618bff160e2e2afa168ba913", 170113868),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD
+ },
+ EngineVersion::V1_0
+ },
+
+ //
+ // Dos vaqueros chapuceros
+ //
+ {
+ // Install folder from the disk/the ISO within the Steam distribution
+ {
+ "vaqueros",
+ "Mortadelo y Filemón: Dos vaqueros chapuceros",
+ AD_ENTRY1s("oeste.emc", "b4c1084557d4cfbae336f0e741ec9e9f", 183099320),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD
+ },
+ EngineVersion::V1_0
+ },
+
{ AD_TABLE_END_MARKER, EngineVersion::V1_0 }
};
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 38e91a6a665..ea38e413173 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -133,17 +133,27 @@ static constexpr const int kKernelTaskArgCounts[] = {
0
};
-class GameMovieAdventureOriginal : public Game {
-public:
- GameMovieAdventureOriginal() {
- const auto &desc = g_engine->gameDescription();
- if (desc.desc.flags & ADGF_CD) {
- const Path gameDir = ConfMan.getPath("path");
- SearchMan.addDirectory(gameDir.append("disk1/Install"));
- SearchMan.addDirectory(gameDir.append("disk2/Install"));
- }
- }
+static constexpr const char *kMapFilesMovieAdventure[] = {
+ "oeste.emc",
+ "terror.emc",
+ "global.emc",
+ nullptr
+};
+
+static constexpr const char *kMapFilesTerror[] = {
+ "terror.emc",
+ "global.emc",
+ nullptr
+};
+
+static constexpr const char *kMapFilesVaqueros[] = {
+ "oeste.emc",
+ "global.emc",
+ nullptr
+};
+class GameWithVersion1 : public Game {
+public:
Point getResolution() override {
return Point(800, 600);
}
@@ -152,16 +162,6 @@ public:
return Point(266, 200);
}
- static constexpr const char *kMapFiles[] = {
- "oeste.emc",
- "terror.emc",
- "global.emc",
- nullptr
- };
- const char *const *getMapFiles() override {
- return kMapFiles;
- }
-
GameFileReference getScriptFileRef() override {
// V1 embeds the script into global.emc, it is overridden during world load
return {};
@@ -209,20 +209,10 @@ public:
g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
}
- Path getVideoPath(int32 videoId) override {
- return Path(String::format("disk1/Install/bin/data%02d.bin", videoId));
- }
-
String getSoundPath(const char *filename) override {
return filename;
}
- String getMusicPath(int32 trackId) override {
- const Room *room = g_engine->player().currentRoom();
- const int diskId = room != nullptr && room->mapIndex() == 1 ? 2 : 1;
- return String::format("disk%d/track%02d", diskId, trackId);
- }
-
int32 getCharacterJingle(MainCharacterKind kind) override {
return kind == MainCharacterKind::Mortadelo ? 15 : 16;
}
@@ -344,14 +334,100 @@ public:
}
void missingSound(const Common::String &fileName) override {
- if (fileName == "CHAS")
+ if (fileName == "CHAS" || fileName == "0563" || fileName == "M2137")
return;
return Game::missingSound(fileName);
}
};
+class GameMovieAdventureOriginal : public GameWithVersion1 {
+public:
+ GameMovieAdventureOriginal() {
+ const auto &desc = g_engine->gameDescription();
+ if (desc.desc.flags & ADGF_CD) {
+ const Path gameDir = ConfMan.getPath("path");
+ SearchMan.addDirectory(gameDir.append("disk1/Install"));
+ SearchMan.addDirectory(gameDir.append("disk2/Install"));
+ }
+ }
+
+ const char *const *getMapFiles() override {
+ return kMapFilesMovieAdventure;
+ }
+
+ Path getVideoPath(int32 videoId) override {
+ return Path(String::format("disk1/Install/bin/data%02d.bin", videoId));
+ }
+
+ String getMusicPath(int32 trackId) override {
+ const Room *room = g_engine->player().currentRoom();
+ const int diskId = room != nullptr && room->mapIndex() == 1 ? 2 : 1;
+ return String::format("disk%d/track%02d", diskId, trackId);
+ }
+
+ void onLoadedGameFiles() override {
+ g_engine->script().variable("EsJuegoCompleto") = 0;
+ }
+};
+
+class GameHalfMovieAdventure : public GameWithVersion1 {
+public:
+ Path getVideoPath(int32 videoId) override {
+ return Path(String::format("bin/data%02d.bin", videoId));
+ }
+
+ String getMusicPath(int32 trackId) override {
+ return String::format("track%02d", trackId);
+ }
+
+ // probably the original CDs have music, the Steam release has no music...
+ void missingSound(const Common::String &fileName) override {
+ if (fileName.contains("track")) {
+ if (!_warnedAboutMusic) {
+ _warnedAboutMusic = true;
+ warning("This release does not contain music or the music was not extracted.");
+ }
+ } else
+ GameWithVersion1::missingSound(fileName);
+ }
+
+private:
+ bool _warnedAboutMusic = false;
+};
+
+class GameVaqueros : public GameHalfMovieAdventure {
+public:
+ const char *const *getMapFiles() override {
+ return kMapFilesVaqueros;
+ }
+
+ void onLoadedGameFiles() override {
+ g_engine->script().variable("EsJuegoCompleto") = 1;
+ }
+};
+
+
+class GameTerror : public GameHalfMovieAdventure {
+public:
+ const char *const *getMapFiles() override {
+ return kMapFilesTerror;
+ }
+
+ void onLoadedGameFiles() override {
+ g_engine->script().variable("EsJuegoCompleto") = 2;
+ }
+};
+
Game *Game::createForMovieAdventureOriginal() {
return new GameMovieAdventureOriginal();
}
+Game *Game::createForVaqueros() {
+ return new GameVaqueros();
+}
+
+Game *Game::createForTerror() {
+ return new GameTerror();
+}
+
}
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 8e1e640b429..abd4e098082 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -214,14 +214,23 @@ Game *Game::create() {
const auto &desc = g_engine->gameDescription();
switch (desc.engineVersion) {
case EngineVersion::V1_0:
- return createForMovieAdventureOriginal();
+ switch (*desc.desc.gameId) {
+ case 'm':
+ return createForMovieAdventureOriginal();
+ case 't':
+ return createForTerror();
+ case 'v':
+ return createForVaqueros();
+ }
+ break;
case EngineVersion::V3_0:
case EngineVersion::V3_1:
return createForMovieAdventureSpecial();
default:
- error("Unsupported or invalid engine version: %d", (int)desc.engineVersion);
break;
}
+
+ error("Unsupported or invalid engine version: %d", (int)desc.engineVersion);
}
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 95c5c79653d..4e913aa0c0b 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -122,6 +122,8 @@ public:
static Game *create();
static Game *createForMovieAdventureSpecial();
static Game *createForMovieAdventureOriginal();
+ static Game *createForTerror();
+ static Game *createForVaqueros();
const Message _message;
};
Commit: 92530947cee9ef89d5ff6a7b6e8ed7515e2083a8
https://github.com/scummvm/scummvm/commit/92530947cee9ef89d5ff6a7b6e8ed7515e2083a8
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix loading aventuradecine saves
Changed paths:
engines/alcachofa/game.cpp
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index abd4e098082..72ca3c35e4c 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -215,7 +215,7 @@ Game *Game::create() {
switch (desc.engineVersion) {
case EngineVersion::V1_0:
switch (*desc.desc.gameId) {
- case 'm':
+ case 'a':
return createForMovieAdventureOriginal();
case 't':
return createForTerror();
Commit: 080caa0613eeb0f494a2352eabdf0467d4e121c0
https://github.com/scummvm/scummvm/commit/080caa0613eeb0f494a2352eabdf0467d4e121c0
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: Fix display of untranslated object names
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index d47b1d07cdd..cb751f5c932 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -944,6 +944,8 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
// allocate on drawQueue to prevent having destruct it
assert(originalText != nullptr);
auto textLen = strlen(originalText);
+ if (textLen == 0)
+ return;
char *text = (char *)g_engine->drawQueue().allocator().allocateRaw(textLen + 1, 1);
memcpy(text, originalText, textLen + 1);
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 55649d22100..21e7c4db04f 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -424,12 +424,12 @@ private:
using TextLine = Common::Span<const byte>; ///< byte to convert 128+ characters to image indices
Font &_font;
- int _posY, _height, _width;
+ int _posY = 0, _height = 0, _width = 0;
Color _color;
Common::Span<TextLine> _lines;
Common::Span<int> _posX;
TextLine _allLines[kMaxLines];
- int _allPosX[kMaxLines];
+ int _allPosX[kMaxLines] = { 0 };
};
enum class FadeType {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index bb5956b18dc..1a4cd60244c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -685,7 +685,11 @@ const char *World::getLocalizedName(const String &name) const {
const char *localizedName;
return _localizedNames.tryGetVal(name.c_str(), localizedName)
? localizedName
+#ifdef ALCACHOFA_DEBUG
: name.c_str();
+#else
+ : "";
+#endif
}
const char *World::getDialogLine(int32 dialogId) const {
Commit: 7020ad213a0798fc69bf2c1c0533b30c6b3e5670
https://github.com/scummvm/scummvm/commit/7020ad213a0798fc69bf2c1c0533b30c6b3e5670
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix character icon during input waiting
Changed paths:
engines/alcachofa/input.cpp
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index a883907aca1..6ac957052ef 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -113,11 +113,11 @@ struct WaitForInputTask final : public Task {
TaskReturn run() override {
TASK_BEGIN;
- process().unlockInteraction();
+
+ // originally this would unlock interaction
do {
TASK_YIELD(1);
} while(!g_engine->input().wasAnyMousePressed());
- process().lockInteraction();
TASK_END;
}
Commit: c7a29913bbac70dee66259a3f3cf0f6ecb7c222a
https://github.com/scummvm/scummvm/commit/c7a29913bbac70dee66259a3f3cf0f6ecb7c222a
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix CamDisguiseTask not moving camera
Changed paths:
engines/alcachofa/camera.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 9037fe15f36..00f2530dfcd 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -704,9 +704,10 @@ struct CamDisguiseTask final : public Task {
newPosition.setY(_startPosition.getY() + t);
else if (t <= 150)
newPosition.setY(_startPosition.getY() - t + 100);
- else if (t <= 200)
+ else if (t >= 200)
newPosition.setY(_startPosition.getY() + t - 200);
_camera.setPosition(newPosition);
+ _camera.setFollow(nullptr);
return TaskReturn::yield();
}
@@ -726,7 +727,7 @@ struct CamDisguiseTask final : public Task {
private:
Camera &_camera;
- int32 _durationMs;
+ int32 _durationMs = 0;
uint32 _startTime = 0;
Vector2d _startPosition;
};
Commit: 8fe6f1d12f8bd63ddf9754ddf1c74597d83f2d64
https://github.com/scummvm/scummvm/commit/8fe6f1d12f8bd63ddf9754ddf1c74597d83f2d64
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix load being blocked after getting card deck in CARROMATO
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 044ff6769dd..1f19b7e3070 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -116,7 +116,9 @@ void Player::drawCursor(bool forceDefaultCursor) {
}
void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera, bool isTemporary) {
- debugC(1, kDebugGameplay, "Change room to %s", targetRoomName.c_str());
+ debugC(1, kDebugGameplay, "Change room from %s%s to %s%s",
+ _currentRoom == nullptr ? "<none>" : _currentRoom->name().c_str(), _isInTemporaryRoom ? " (temp)" : "",
+ targetRoomName.c_str(), isTemporary ? " (temp)" : "");
// original would be to always free all resources from globalRoom, inventory, GlobalUI
// We don't do that, it is unnecessary, all resources would be loaded right after
@@ -126,6 +128,9 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera,
_currentRoom = nullptr;
return; // exiting game entirely
}
+ auto nextRoom = g_engine->world().getRoomByName(targetRoomName.c_str());
+ if (nextRoom == nullptr) // no good way to recover, leaving-the-room actions might already prevent further progress
+ error("Invalid room name: %s", targetRoomName.c_str());
if (_currentRoom != nullptr) {
g_engine->scheduler().killProcessByName("ACTUALIZAR_" + _currentRoom->name());
@@ -145,12 +150,11 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera,
// this fixes a bug with all original games where changing the room in the inventory (e.g. iFOTO in aventura de cine)
// would overwrite the actual game room thus returning from the inventory one would be stuck in the temporary room
// If we know that a transition is temporary we prevent that and only remember the real game room
+ if (isTemporary && _roomBeforeInventory == nextRoom)
+ isTemporary = false; // this only looked like a temporary room change, but is not
_isInTemporaryRoom = isTemporary;
- _currentRoom = g_engine->world().getRoomByName(targetRoomName.c_str());
- if (_currentRoom == nullptr) // no good way to recover, leaving-the-room actions might already prevent further progress
- error("Invalid room name: %s", targetRoomName.c_str());
-
+ _currentRoom = nextRoom;
if (!_didLoadGlobalRooms) {
_didLoadGlobalRooms = true;
g_engine->world().inventory().loadResources();
Commit: 0c8ded950469647b23986437c6b14c28d066eecc
https://github.com/scummvm/scummvm/commit/0c8ded950469647b23986437c6b14c28d066eecc
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Disable collision avoidance
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index ea38e413173..b021c6ca1bb 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -238,6 +238,10 @@ public:
return false;
}
+ bool shouldAvoidCollisions() override {
+ return false;
+ }
+
Character *unknownSayTextCharacter(const char *name, int32 dialogId) override {
// an original bug in room POBLADO_INDIO, a dialog line would be skipped
if (!scumm_stricmp(name, "JEFE_INDIO_HABLA_POSTE"))
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index 51cdea7e01f..d8e5e9604ee 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -291,6 +291,10 @@ public:
return true;
}
+ bool shouldAvoidCollisions() override {
+ return true;
+ }
+
void onLoadedGameFiles() override {
// this notifies the script whether we are a demo
if (g_engine->world().loadedMapCount() == 2)
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 207b618b147..40666a9b702 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -912,7 +912,9 @@ void MainCharacter::walkTo(
strcmp(_activateAction, "MIRAR") != 0 &&
otherCharacter->currentlyUsing() != dynamic_cast<ObjectBase *>(_activateObject);
- if (otherCharacter->room() == room() && evadeTarget.sqrDist(otherTarget) <= avoidanceDistSqr) {
+ if (otherCharacter->room() == room() &&
+ evadeTarget.sqrDist(otherTarget) <= avoidanceDistSqr &&
+ g_engine->game().shouldAvoidCollisions()) {
if (!otherCharacter->isBusy()) {
if (activeFloor != nullptr && activeFloor->findEvadeTarget(evadeTarget, activeDepthScale, avoidanceDistSqr, evadeTarget))
otherCharacter->WalkingCharacter::walkTo(evadeTarget);
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 4e913aa0c0b..3d5219b1393 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -75,6 +75,7 @@ public:
virtual bool isAllowedToOpenMenu() = 0; ///< only the game-specific condition
virtual bool shouldScriptLockInteraction() = 0;
virtual bool shouldChangeCharacterUseGameLock() = 0;
+ virtual bool shouldAvoidCollisions() = 0;
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
Commit: e784fb80b929f87ca0eb5cf8d4a3b0107d5a3448
https://github.com/scummvm/scummvm/commit/e784fb80b929f87ca0eb5cf8d4a3b0107d5a3448
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix size of main character
Changed paths:
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index b021c6ca1bb..8fdbee83a87 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -242,6 +242,10 @@ public:
return false;
}
+ Point getMainCharacterSize() override {
+ return { 40, 220 };
+ }
+
Character *unknownSayTextCharacter(const char *name, int32 dialogId) override {
// an original bug in room POBLADO_INDIO, a dialog line would be skipped
if (!scumm_stricmp(name, "JEFE_INDIO_HABLA_POSTE"))
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index d8e5e9604ee..f26e49f3435 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -295,6 +295,10 @@ public:
return true;
}
+ Point getMainCharacterSize() override {
+ return { 60, 310};
+ }
+
void onLoadedGameFiles() override {
// this notifies the script whether we are a demo
if (g_engine->world().loadedMapCount() == 2)
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 40666a9b702..ef0c4c85f5b 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -863,11 +863,10 @@ void MainCharacter::update() {
_currentlyUsingObject = nullptr;
WalkingCharacter::update();
- const int16 halfWidth = (int16)(60 * _graphicNormal.depthScale());
- const int16 height = (int16)(310 * _graphicNormal.depthScale());
+ const Point size = g_engine->game().getMainCharacterSize() * _graphicNormal.depthScale();
shape()->setAsRectangle(Rect(
- _currentPos.x - halfWidth, _currentPos.y - height,
- _currentPos.x + halfWidth, _currentPos.y));
+ _currentPos.x - size.x, _currentPos.y - size.y,
+ _currentPos.x + size.x, _currentPos.y));
// These are set as members as FloorColor might want to change them
_alphaPremultiplier = room()->characterAlphaPremultiplier();
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 3d5219b1393..2dd76ce3153 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -76,6 +76,7 @@ public:
virtual bool shouldScriptLockInteraction() = 0;
virtual bool shouldChangeCharacterUseGameLock() = 0;
virtual bool shouldAvoidCollisions() = 0;
+ virtual Common::Point getMainCharacterSize() = 0;
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
Commit: 2dc75afa6d8f25d17384e0884df1b6edd8cc5e53
https://github.com/scummvm/scummvm/commit/2dc75afa6d8f25d17384e0884df1b6edd8cc5e53
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Add option to enable texture filtering
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/detection.h
engines/alcachofa/detection_tables.h
engines/alcachofa/game-movie-adventure-original.cpp
engines/alcachofa/game-movie-adventure-special.cpp
engines/alcachofa/game.h
engines/alcachofa/graphics-opengl-shaders.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/metaengine.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index cbc232ecd90..530b908c736 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -79,6 +79,8 @@ Common::Error AlcachofaEngine::run() {
g_system->showMouse(false);
setDebugger(_console);
_game.reset(Game::create());
+ Config::registerDefaults();
+ _config.loadFromScummVM();
_world.load();
_renderer.reset(IRenderer::createOpenGLRenderer(game().getResolution()));
_drawQueue.reset(new DrawQueue(_renderer.get()));
@@ -441,8 +443,15 @@ bool AlcachofaEngine::tryLoadFromLauncher() {
return result;
}
-Config::Config() {
- loadFromScummVM();
+void Config::registerDefaults() {
+ Config c;
+ ConfMan.registerDefault("subtitles", c._subtitles);
+ ConfMan.registerDefault("high_quality", c._highQuality);
+ ConfMan.registerDefault("32_bits", c._bits32);
+ ConfMan.registerDefault("tex_filter", g_engine->game().shouldFilterTexturesByDefault());
+ ConfMan.registerDefault("music_volume", c._musicVolume);
+ ConfMan.registerDefault("speech_volume", c._speechVolume);
+ ConfMan.registerDefault("sfx_volume", c._speechVolume);
}
void Config::loadFromScummVM() {
@@ -451,12 +460,14 @@ void Config::loadFromScummVM() {
_subtitles = ConfMan.getBool("subtitles");
_highQuality = ConfMan.getBool("high_quality");
_bits32 = ConfMan.getBool("32_bits");
+ _texFilter = ConfMan.getBool("tex_filter");
}
void Config::saveToScummVM() {
ConfMan.setBool("subtitles", _subtitles);
ConfMan.setBool("high_quality", _highQuality);
ConfMan.setBool("32_bits", _bits32);
+ ConfMan.setBool("tex_filter", _texFilter);
ConfMan.setInt("music_volume", _musicVolume);
ConfMan.setInt("speech_volume", _speechVolume);
ConfMan.setInt("sfx_volume", _speechVolume);
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 406cd757732..45b2ca5b27f 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -62,7 +62,7 @@ namespace SaveVersion {
static constexpr const Common::Serializer::Version kInitial = 0;
static constexpr const Common::Serializer::Version kWithEngineV10 = 1;
};
-static constexpr const Common::Serializer::Version kCurrentSaveVersion = SaveVersion::kInitial;
+static constexpr const Common::Serializer::Version kCurrentSaveVersion = SaveVersion::kWithEngineV10;
class MySerializer : public Common::Serializer {
public:
@@ -81,14 +81,14 @@ public:
class Config {
public:
- Config();
-
inline bool &subtitles() { return _subtitles; }
inline bool &highQuality() { return _highQuality; }
inline bool &bits32() { return _bits32; }
+ inline bool &texFilter() { return _texFilter; }
inline uint8 &musicVolume() { return _musicVolume; }
inline uint8 &speechVolume() { return _speechVolume; }
+ static void registerDefaults();
void loadFromScummVM();
void saveToScummVM();
@@ -96,7 +96,8 @@ private:
bool
_subtitles = true,
_highQuality = true,
- _bits32 = true;
+ _bits32 = true,
+ _texFilter = true;
uint8
_musicVolume = 255,
_speechVolume = 255;
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 0c8ce953738..f505a2e55b8 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -58,8 +58,9 @@ extern const PlainGameDescriptor alcachofaGames[];
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
+#define GAMEOPTION_HIGH_QUALITY GUIO_GAMEOPTIONS1 // toggles some special effect objects
+#define GAMEOPTION_32BITS GUIO_GAMEOPTIONS2 // renders in 16/32 bit color
+#define GAMEOPTION_TEXTURE_FILTER GUIO_GAMEOPTIONS3 // toggles texture filters
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 9a0bfe6aafd..72c3d779612 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -143,7 +143,8 @@ const AlcachofaGameDescription gameDescriptions[] = {
),
Common::ES_ESP,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD,
+ GUIO1(GAMEOPTION_TEXTURE_FILTER)
},
EngineVersion::V1_0
},
@@ -159,7 +160,8 @@ const AlcachofaGameDescription gameDescriptions[] = {
AD_ENTRY1s("terror.emc", "dc9357ee618bff160e2e2afa168ba913", 170113868),
Common::ES_ESP,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD,
+ GUIO1(GAMEOPTION_TEXTURE_FILTER)
},
EngineVersion::V1_0
},
@@ -175,7 +177,8 @@ const AlcachofaGameDescription gameDescriptions[] = {
AD_ENTRY1s("oeste.emc", "b4c1084557d4cfbae336f0e741ec9e9f", 183099320),
Common::ES_ESP,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_CD,
+ GUIO1(GAMEOPTION_TEXTURE_FILTER)
},
EngineVersion::V1_0
},
diff --git a/engines/alcachofa/game-movie-adventure-original.cpp b/engines/alcachofa/game-movie-adventure-original.cpp
index 8fdbee83a87..d6904937a45 100644
--- a/engines/alcachofa/game-movie-adventure-original.cpp
+++ b/engines/alcachofa/game-movie-adventure-original.cpp
@@ -217,6 +217,10 @@ public:
return kind == MainCharacterKind::Mortadelo ? 15 : 16;
}
+ bool shouldFilterTexturesByDefault() override {
+ return false;
+ }
+
bool shouldClipCamera() override {
return true;
}
diff --git a/engines/alcachofa/game-movie-adventure-special.cpp b/engines/alcachofa/game-movie-adventure-special.cpp
index f26e49f3435..e2f03b387f1 100644
--- a/engines/alcachofa/game-movie-adventure-special.cpp
+++ b/engines/alcachofa/game-movie-adventure-special.cpp
@@ -270,6 +270,10 @@ public:
kind == MainCharacterKind::Mortadelo ? "PistaMorta" : "PistaFile");
}
+ bool shouldFilterTexturesByDefault() override {
+ return true;
+ }
+
bool shouldClipCamera() override {
return g_engine->script().variable("EncuadrarCamara");
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 2dd76ce3153..681d2bba447 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -70,6 +70,7 @@ public:
virtual Common::String getSoundPath(const char *filename) = 0; ///< Without file-extension
virtual Common::String getMusicPath(int32 trackId) = 0; ///< Without file-extension
virtual int32 getCharacterJingle(MainCharacterKind kind) = 0;
+ virtual bool shouldFilterTexturesByDefault() = 0;
virtual bool shouldClipCamera() = 0;
virtual bool isAllowedToInteract() = 0;
virtual bool isAllowedToOpenMenu() = 0; ///< only the game-specific condition
diff --git a/engines/alcachofa/graphics-opengl-shaders.cpp b/engines/alcachofa/graphics-opengl-shaders.cpp
index 6e348332b43..1cb30356b29 100644
--- a/engines/alcachofa/graphics-opengl-shaders.cpp
+++ b/engines/alcachofa/graphics-opengl-shaders.cpp
@@ -189,7 +189,7 @@ private:
setBlendFunc(_currentBlendMode);
_shader.setUniform("projection", _projection);
_shader.setUniform("blendMode", _currentTexture == nullptr ? 5 : (int)_currentBlendMode);
- _shader.setUniform("posterize", g_engine->config().highQuality() ? 1 : 0);
+ _shader.setUniform("posterize", g_engine->config().bits32() ? 0 : 1);
_shader.setUniform1f("lodBias", _currentLodBias);
_shader.setUniform("texture", 0);
_batchTexture = _currentTexture;
@@ -248,7 +248,7 @@ private:
gl_FragColor = var_color;
}
- if (posterize == 0) {
+ if (posterize == 1) {
// shave off 3 bits for that 16-bit look
gl_FragColor = floor(gl_FragColor * (256.0 / 8.0)) / (256.0 / 8.0);
}
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 044bacbc651..5dd55492dc4 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -48,8 +48,10 @@ OpenGLTexture::OpenGLTexture(int32 w, int32 h, bool withMipmaps)
OpenGL::clearGLError(); // we will just ignore it
GL_CALL(glGenTextures(1, &_handle));
GL_CALL(glBindTexture(GL_TEXTURE_2D, _handle));
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+
+ const bool filter = g_engine->config().texFilter();
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST));
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter ? GL_LINEAR : GL_NEAREST));
setMirrorWrap(false);
}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index cb751f5c932..ad3add55d5d 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -447,10 +447,10 @@ void Animation::load() {
texHeight = nextHigher2(maxBounds.height());
}
_renderedSurface.create(texWidth, texHeight, g_engine->renderer().getPixelFormat());
- _renderedTexture = g_engine->renderer().createTexture(texWidth, texHeight, true);
+ _renderedTexture = g_engine->renderer().createTexture(texWidth, texHeight, g_engine->config().texFilter());
// We always create mipmaps, even for the backgrounds that usually do not scale much,
- // the exception to this is the thumbnails for the savestates.
+ // the exception to this is the thumbnails for the savestates. (and disabling filter also disables mipmaps)
// If we need to reduce graphics memory usage in the future, we can change it right here
}
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index c53bf7b5168..ee44b373758 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -56,6 +56,17 @@ static const ADExtraGuiOptionsMap optionsList[] = {
0
}
},
+ {
+ GAMEOPTION_TEXTURE_FILTER,
+ {
+ _s("Filter textures"),
+ _s("Whether textures should be linearly filtered"),
+ "tex_filter",
+ false, // it is used for V1 where textures are normally not filtered, V3 originally always filters
+ 0,
+ 0
+ }
+ },
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
Commit: 25a0e0e1936235ebbe8098330d2bad73b2ff1500
https://github.com/scummvm/scummvm/commit/25a0e0e1936235ebbe8098330d2bad73b2ff1500
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: V1: Fix sound issues in Intro_Terror
Changed paths:
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 9206f1c19d4..33b463f6a06 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -53,6 +53,9 @@ Console::Console() : GUI::Debugger() {
registerCmd("playVideo", WRAP_METHOD(Console, cmdPlayVideo));
registerCmd("script", WRAP_METHOD(Console, cmdScript));
registerCmd("toggleObject", WRAP_METHOD(Console, cmdToggleObject));
+ registerCmd("playVoice", WRAP_METHOD(Console, cmdPlaySound));
+ registerCmd("playSFX", WRAP_METHOD(Console, cmdPlaySound));
+ registerCmd("dumpFile", WRAP_METHOD(Console, cmdDumpFile));
}
Console::~Console() {}
@@ -333,4 +336,46 @@ bool Console::cmdToggleObject(int argc, const char **args) {
return true;
}
+bool Console::cmdPlaySound(int argc, const char **args) {
+ if (argc != 2) {
+ debugPrintf("usage: %s <sound-name>\n", args[0]);
+ return true;
+ }
+
+ if (tolower(args[0][5]) == 'V')
+ g_engine->sounds().playVoice(args[1]);
+ else
+ g_engine->sounds().playSFX(args[1]);
+ return false;
+}
+
+bool Console::cmdDumpFile(int argc, const char **args) {
+ if (argc != 2 && argc != 3) {
+ debugPrintf("usage: %s <input-path> [<output-path>]\n", args[0]);
+ return true;
+ }
+
+ File input;
+ if (!input.open(args[1])) {
+ debugPrintf("Could not find input file: %s\n", args[1]);
+ return true;
+ }
+
+ const char *outputPath = argc == 2 ? args[1] : args[2];
+ DumpFile output;
+ if (!output.open(outputPath)) {
+ debugPrintf("Could not open output file: %s\n", outputPath);
+ return true;
+ }
+
+ constexpr const uint kBufferSize = 1024;
+ uint32 read;
+ byte buffer[kBufferSize];
+ do {
+ read = input.read(buffer, kBufferSize);
+ output.write(buffer, read);
+ } while (read == kBufferSize);
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index b288a8bfb68..d5e08a961fa 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -63,6 +63,8 @@ private:
bool cmdPlayVideo(int argc, const char **args);
bool cmdScript(int argc, const char **args);
bool cmdToggleObject(int argc, const char **args);
+ bool cmdPlaySound(int argc, const char **args);
+ bool cmdDumpFile(int argc, const char **args);
bool _showGraphics = false;
bool _showInteractables = false;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 1a4cd60244c..faa6538ac5b 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -1067,7 +1067,7 @@ public:
return nullptr;
if (!_file->seek(member->_offset, SEEK_SET))
error("Could not seek to embedded file: %s at %u", path.toString().c_str(), member->_offset);
- return new SeekableSubReadStream(_file.get(), member->_offset, member->_end, DisposeAfterUse::NO);
+ return new SafeSeekableSubReadStream(_file.get(), member->_offset, member->_end, DisposeAfterUse::NO);
}
private:
Commit: 7faac192b0d78c44ba805ed9e1ac57a359d7d815
https://github.com/scummvm/scummvm/commit/7faac192b0d78c44ba805ed9e1ac57a359d7d815
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-02-04T16:55:35+01:00
Commit Message:
ALCACHOFA: Fix compilation on ScopedPtr conversion
Changed paths:
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index faa6538ac5b..adb2ce6b8c1 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -999,7 +999,7 @@ ScopedPtr<SeekableReadStream> World::openFileRef(const GameFileReference &ref) c
ScopedPtr<File> file(new File());
if (!file->open(Path(ref._path)))
return nullptr;
- return file;
+ return ScopedPtr<SeekableReadStream>(file.release()); // Ubuntu 22 does allow the implicit conversion
}
}
More information about the Scummvm-git-logs
mailing list