[Scummvm-git-logs] scummvm master -> 639847ad8ca48f5797ef4d178673f61857771d7a
Helco
noreply at scummvm.org
Tue Mar 31 15:23:43 UTC 2026
This automated email contains information about 24 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
b855388571 ALCACHOFA: V2: Fix secta startup
6db3b5b2e4 ALCACHOFA: V2: Fix gameplay bugs
aa54aa519f ALCACHOFA: V2: Patch script errors with nested dialog menus
470ba13bb4 ALCACHOFA: Fix two coverity issues
b7b1ba88c7 ALCACHOFA: Fix bracket style with if-else
1829417aaf ALCACHOFA: Fix kernel proc names in debug traces
0b749d7819 ALCACHOFA: V2: Adapt camera to V2
fbf769a5ab ALCACHOFA: V2: Fix subtitle position
c656618583 ALCACHOFA: V2: Fix camera speed in large rooms
4e51eaf599 ALCACHOFA: V2: Fix long-running animations
8eeecc3708 ALCACHOFA: V2: Add moscu and escarabajo detection entries
dea78dd95f ALCACHOFA: Fix signed/unsigned comparison warning
973d5f0127 ALCACHOFA: V2: Add corvino, balones and mamelucos detection entries
471b404af5 ALCACHOFA: V2: Fix startup of escarabajo and moscu
ea37a7d5a8 ALCACHOFA: V2: Fix script error on entering DESPACHO_MOMIEZ twice
ae3cba18ed ALCACHOFA: Reversing draw order within one layer
657c8c1292 ALCACHOFA: Reenable DefectoObjeto script
3d113845f1 ALCACHOFA: V2: Disable texture filtering by default
d9e3e66dc2 ALCACHOFA: Fix script loading with duplicated procedures
73a82da4d1 ALCACHOFA: Add walk fallback if character is outside the floor shape
3fce6a3eaa ALCACHOFA: Allow characters as camera targets
c21da18d93 ALCACHOFA: Remove ScriptKernelTask::ChangeDoor
a5bd081174 ALCACHOFA: V2: Fix loading saves after teleporting
639847ad8c ALCACHOFA: V2: Fix pickup script from non-char process
Commit: b85538857143fd09146b06d21f7d23dcb62ea019
https://github.com/scummvm/scummvm/commit/b85538857143fd09146b06d21f7d23dcb62ea019
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:28+02:00
Commit Message:
ALCACHOFA: V2: Fix secta startup
- Allow mp3 sounds
- Stub CamShakeV2 kernel task
- Use V3 MainCharacterKind values
- Fix V2 font rendering
- Warn about unextracted videos
- Use player semaphore as interaction lock
Changed paths:
engines/alcachofa/game-v2.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/script.cpp
engines/alcachofa/script.h
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index ed1966a348f..3d86c847c60 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -19,6 +19,8 @@
*
*/
+#include "gui/message.h"
+
#include "alcachofa/alcachofa.h"
#include "alcachofa/game.h"
#include "alcachofa/script.h"
@@ -92,7 +94,7 @@ static constexpr const ScriptKernelTask kScriptKernelTaskMap[] = {
ScriptKernelTask::Drop,
ScriptKernelTask::CharacterDrop,
ScriptKernelTask::ChangeDoor,
- ScriptKernelTask::CamShake,
+ ScriptKernelTask::CamShakeV2,
ScriptKernelTask::ToggleRoomFloor,
ScriptKernelTask::SetDialogLineReturn,
ScriptKernelTask::DialogMenu,
@@ -181,7 +183,7 @@ public:
}
bool isAllowedToInteract() override {
- return true; // original would be checking an unused script variable "Ocupados"
+ return g_engine->player().semaphore().isReleased();
}
bool shouldScriptLockInteraction() override {
@@ -214,6 +216,20 @@ static constexpr const char *kMapFilesSecta[] = {
class GameSecta : public GameWithVersion2 {
public:
+ GameSecta() {
+ // only the Steam Release has only the Videos in an ISO...
+ if (!SearchMan.hasFile(getVideoPath(0)) && SearchMan.hasFile("VIDS.iso")) {
+ _videosAreExtracted = false;
+ GUI::MessageDialog dialog("Please extract VIDS.iso in order to play videos.");
+ dialog.runModal();
+ }
+ }
+
+ bool isKnownBadVideo(int32 videoId) override {
+ // all videos are known bad if they are not extracted
+ return !_videosAreExtracted;
+ }
+
const char *const *getMapFiles() override {
return kMapFilesSecta;
}
@@ -221,6 +237,9 @@ public:
char getTextFileKey() override {
return static_cast<char>(0xA3);
}
+
+private:
+ bool _videosAreExtracted = true;
};
Game *Game::createForSecta() {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index b476bbbe055..93322ee5aba 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -691,7 +691,7 @@ Font::Font(GameFileReference fileRef)
, _spaceImageI(g_engine->isV1() ? 94 : 0)
, _charSpacing(g_engine->isV1() ? 3 : 0) {}
-static void fixFontAtlasColors(ManagedSurface &surface) {
+static void fixFontAtlasColorsV1(ManagedSurface &surface) {
// In V1 the font contains green and black pixels where
// - black pixels should stay black
// - green pixels should be the text color
@@ -713,6 +713,20 @@ static void fixFontAtlasColors(ManagedSurface &surface) {
}
}
+static void fixFontAtlasColorsV2(ManagedSurface &surface) {
+ // In V2 the font contains grayscale pixels and magenta as color key
+ // We just remove the color key to transparent
+ assert(surface.format.bytesPerPixel == 4);
+ const uint32 magenta = surface.format.ARGBToColor(255, 255, 0, 255);
+
+ for (int16 y = 0; y < surface.h; y++) {
+ uint32 *pixel = (uint32 *)surface.getBasePtr(0, y);
+ for (int16 x = 0; x < surface.w; x++, pixel++) {
+ *pixel = *pixel == magenta ? 0 : *pixel;
+ }
+ }
+}
+
void Font::load() {
if (_isLoaded)
return;
@@ -752,7 +766,9 @@ void Font::load() {
_texMaxs[i].setY((offsetY + _images[i]->h) * invHeight);
}
if (g_engine->isV1())
- fixFontAtlasColors(atlasSurface);
+ fixFontAtlasColorsV1(atlasSurface);
+ else if (g_engine->isV2())
+ fixFontAtlasColorsV2(atlasSurface);
_texture = g_engine->renderer().createTexture(atlasSurface.w, atlasSurface.h, false);
_texture->update(atlasSurface);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 5063163d45f..1e8a800c64b 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -574,19 +574,19 @@ private:
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 (g_engine->isV1()) {
if (value < 0 || value > 1)
error("Unexpected value for main character kind: %d", value);
return value == 0
? MainCharacterKind::Mortadelo
: MainCharacterKind::Filemon;
}
+ else {
+ if (value < 0 || value > 2)
+ error("Unexpected value for main character kind: %d", value);
+ else
+ return (MainCharacterKind)value;
+ }
}
const char *getStringArg(uint argI) {
@@ -945,6 +945,9 @@ private:
Vector2d(getNumberArg(1), getNumberArg(2)),
Vector2d(getNumberArg(3), getNumberArg(4)),
getNumberArg(0)));
+ case ScriptKernelTask::CamShakeV2:
+ warning("STUB: CamShakeV2");
+ return TaskReturn::finish(0);
case ScriptKernelTask::LerpCamXY:
return TaskReturn::waitFor(g_engine->cameraV3().lerpPos(process(),
Vector2d(getNumberArg(0), getNumberArg(1)),
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 5da8a929170..5afd1db2914 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -114,6 +114,7 @@ enum class ScriptKernelTask {
WaitCamStopping,
CamFollow,
CamShake,
+ CamShakeV2,
LerpCamXY,
LerpCamZ,
LerpCamScale,
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 71559cc562e..d6789ab1289 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -30,6 +30,7 @@
#include "audio/decoders/wave.h"
#include "audio/decoders/adpcm.h"
#include "audio/decoders/raw.h"
+#include "audio/decoders/mp3.h"
using namespace Common;
using namespace Audio;
@@ -81,8 +82,47 @@ void Sounds::update() {
}
}
+class XORReadStream final : public SeekableReadStream {
+public:
+ XORReadStream(SeekableReadStream *parent, byte key, DisposeAfterUse::Flag disposeAfterUse)
+ : _parent(parent, disposeAfterUse)
+ , _key(key) {}
+
+ uint32 read(void *dataPtr, uint32 maxSize) override {
+ uint32 size = _parent->read(dataPtr, maxSize);
+ byte *bytePtr = (byte *)dataPtr;
+ for (uint32 i = 0; i < size; i++)
+ *(bytePtr++) ^= _key;
+ return size;
+ }
+
+ bool eos() const override {
+ return _parent->eos();
+ }
+
+ int64 pos() const override {
+ return _parent->pos();
+ }
+
+ int64 size() const override {
+ return _parent->size();
+ }
+
+ bool seek(int64 offset, int whence) override {
+ return _parent->seek(offset, whence);
+ }
+
+private:
+ DisposablePtr<SeekableReadStream> _parent;
+ const byte _key;
+};
+
static AudioStream *loadSND(File *file) {
- // SND files are just WAV files with removed headers
+ // in V2 SND files are raw U8 PCM in mono 22100 encrypted with XOR
+ if (g_engine->isV2())
+ return makeRawStream(new XORReadStream(file, 0x55, DisposeAfterUse::YES), 22100, FLAG_UNSIGNED);
+
+ // in V1/V3 SND files are just WAV files with removed headers
const uint32 endOfFormat = file->readUint32LE() + 2 * sizeof(uint32);
if (endOfFormat < 24)
error("Invalid SND format size");
@@ -129,6 +169,18 @@ static AudioStream *openAudio(const String &basePath) {
if (file->open(path.c_str()))
return makeWAVStream(file, DisposeAfterUse::YES);
+ // Steam releases of V2 games use mp3 files
+ path.setChar('M', path.size() - 3);
+ path.setChar('P', path.size() - 2);
+ path.setChar('3', path.size() - 1);
+ if (file->open(path.c_str())) {
+#ifdef USE_MAD
+ return makeMP3Stream(file, DisposeAfterUse::YES);
+#else
+ return nullptr;
+#endif
+ }
+
delete file;
g_engine->game().missingSound(basePath);
return nullptr;
Commit: 6db3b5b2e443691bd7859f769c3e68e6ccf5da82
https://github.com/scummvm/scummvm/commit/6db3b5b2e443691bd7859f769c3e68e6ccf5da82
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Fix gameplay bugs
- Temporary camera bounds fix
- GlobalUI is not visible
- Item lookup is case-sensitive
- Unknown voice line for MORTA_ATADO
- Map teleports do not work
- Drop item from none-character-process
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game-v2.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index c36a0da111c..d4098630113 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -84,10 +84,10 @@ Common::Error AlcachofaEngine::run() {
_world.load();
_renderer.reset(IRenderer::createOpenGLRenderer(game().getResolution()));
_drawQueue.reset(new DrawQueue(_renderer.get()));
- _camera.reset(isV1() ? static_cast<Camera *>(new CameraV1()) : new CameraV3());
+ _camera.reset(isV1() || isV2() ? static_cast<Camera *>(new CameraV1()) : new CameraV3());
_script.reset(new Script());
_player.reset(new Player());
- _globalUI.reset(isV1() ? static_cast<GlobalUI *>(new GlobalUIV1()) : new GlobalUIV3());
+ _globalUI.reset(isV1() || isV2() ? static_cast<GlobalUI *>(new GlobalUIV1()) : new GlobalUIV3());
_menu.reset(isV1() ? static_cast<Menu *>(new MenuV1()) : new MenuV3());
setMillis(0);
game().onLoadedGameFiles();
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 93296c4a163..e4152f28d15 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -996,7 +996,7 @@ void MainCharacter::clearInventory() {
Item *MainCharacter::getItemByName(const String &name) const {
for (auto *item : _items) {
- if (item->name() == name)
+ if (item->name().equalsIgnoreCase(name))
return item;
}
return nullptr;
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 3d86c847c60..9f9d64ea5f8 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -170,6 +170,11 @@ public:
kind == MainCharacterKind::Mortadelo ? "PistaMorta" : "PistaFile");
}
+ bool hasMortadeloVoice(const Character *character) override {
+ return Game::hasMortadeloVoice(character) ||
+ character->name().equalsIgnoreCase("MORTA_ATADO");
+ }
+
bool shouldFilterTexturesByDefault() override {
return true; // TODO: Check this!
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index fa5b6d42289..6888d73f3e3 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -780,8 +780,8 @@ bool World::loadWorldFileV2(const char *path) {
};
readGlobalAnim(GlobalAnimationKind::GeneralFont, GlobalAnimationKind::DialogFont);
readGlobalAnim(GlobalAnimationKind::Cursor, GlobalAnimationKind::Count);
- readGlobalAnim(GlobalAnimationKind::MortadeloIcon, GlobalAnimationKind::MortadeloDisabledIcon);
readGlobalAnim(GlobalAnimationKind::FilemonIcon, GlobalAnimationKind::FilemonDisabledIcon);
+ readGlobalAnim(GlobalAnimationKind::MortadeloIcon, GlobalAnimationKind::MortadeloDisabledIcon);
readGlobalAnim(GlobalAnimationKind::InventoryIcon, GlobalAnimationKind::InventoryDisabledIcon);
readRooms(file);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 1e8a800c64b..0eed880bac7 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -833,6 +833,12 @@ private:
}
character->resetTalking();
character->room() = targetRoom;
+ if (g_engine->isV2() && character == g_engine->player().activeCharacter()) {
+ // this mechanic also exists in V1 but does not seem to be used
+ // as the script also changes the room to the target when placing characters
+ g_engine->player().changeRoom(getStringArg(1), true);
+ g_engine->sounds().setMusicToRoom(targetRoom->musicID());
+ }
return TaskReturn::finish(1);
}
case ScriptKernelTask::LerpCharacterLodBias: {
@@ -908,6 +914,11 @@ private:
return TaskReturn::finish(1);
}
case ScriptKernelTask::Drop:
+ if (process().character() == MainCharacterKind::None) {
+ // This happens in Secta, the original game just ignores this case
+ warning("Tried to drop from none-character-process: %s at %u", getStringArg(0), _pc);
+ }
+ else
relatedCharacter().drop(getStringArg(0));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterDrop: {
Commit: aa54aa519f6266940ff39812d4e63d379841f19c
https://github.com/scummvm/scummvm/commit/aa54aa519f6266940ff39812d4e63d379841f19c
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Patch script errors with nested dialog menus
Changed paths:
engines/alcachofa/game-v2.cpp
engines/alcachofa/game-v3.cpp
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 9f9d64ea5f8..ee4be6202f5 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -212,6 +212,17 @@ public:
}
};
+class GameWithVersion2_0 : public GameWithVersion2 {
+public:
+ void onLoadedGameFiles() override {
+ GameWithVersion2::onLoadedGameFiles();
+
+ auto &script = g_engine->script();
+ script.fixNestedMenuPop(5921); // Mortadelo talking to ARQUEOLOGOS in CARRETERA
+ script.fixNestedMenuPop(20898); // Filemon talking to MANOLO in FILE_PIRAMIDE
+ }
+};
+
static constexpr const char *kMapFilesSecta[] = {
"Mapas/mapa1.emc",
"Mapas/mapa2.emc",
@@ -219,7 +230,7 @@ static constexpr const char *kMapFilesSecta[] = {
nullptr
};
-class GameSecta : public GameWithVersion2 {
+class GameSecta : public GameWithVersion2_0 {
public:
GameSecta() {
// only the Steam Release has only the Videos in an ISO...
diff --git a/engines/alcachofa/game-v3.cpp b/engines/alcachofa/game-v3.cpp
index 1ede881c22f..65b6bd740e8 100644
--- a/engines/alcachofa/game-v3.cpp
+++ b/engines/alcachofa/game-v3.cpp
@@ -392,6 +392,12 @@ public:
return { kScriptKernelTaskMapV30, ARRAYSIZE(kScriptKernelTaskMapV30) };
}
+ void onLoadedGameFiles() override {
+ GameWithVersion3::onLoadedGameFiles();
+ g_engine->script().fixNestedMenuPop(39038); // Mortadelo talking to MERACDER in BAZAAR
+ g_engine->script().fixNestedMenuPop(39130); // also ^
+ }
+
void updateScriptVariables() override {
GameWithVersion3::updateScriptVariables();
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 0eed880bac7..0393498baff 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -270,29 +270,35 @@ struct ScriptTask final : public Task {
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, opName, instruction._arg);
+ debugN("%u: %5u %-12s %8d Stack(%u): ",
+ process().pid(), _pc - 1, opName, instruction._arg, _valueStack.size());
if (_valueStack.empty())
debug("empty");
else {
- const auto &top = _valueStack.top();
- switch (top._type) {
- case StackEntryType::Number:
- debug("Number %d", top._number);
- break;
- case StackEntryType::Variable:
- debug("Var %u (%d)", top._index, _script._variables[top._index]);
- break;
- case StackEntryType::Instruction:
- debug("Instr %u", top._index);
- break;
- case StackEntryType::String:
- debug("String %u (\"%s\")", top._index, getStringArg(0));
- break;
- default:
- debug("INVALID");
- break;
+ uint count = MIN(uint(3), _valueStack.size());
+ for (uint i = 0; i < count; i++) {
+ if (i != 0)
+ debugN(", ");
+ const auto &top = _valueStack[_valueStack.size() - 1 - i];
+ switch (top._type) {
+ case StackEntryType::Number:
+ debugN("Number %d", top._number);
+ break;
+ case StackEntryType::Variable:
+ debugN("Var %u (%d)", top._index, _script._variables[top._index]);
+ break;
+ case StackEntryType::Instruction:
+ debugN("Instr %u", top._index);
+ break;
+ case StackEntryType::String:
+ debugN("String %u (\"%s\")", top._index, getStringArg(0));
+ break;
+ default:
+ debugN("INVALID");
+ break;
+ }
}
+ debug("");
}
}
@@ -919,7 +925,7 @@ private:
warning("Tried to drop from none-character-process: %s at %u", getStringArg(0), _pc);
}
else
- relatedCharacter().drop(getStringArg(0));
+ relatedCharacter().drop(getStringArg(0));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterDrop: {
auto &character = g_engine->world().getMainCharacterByKind(getMainCharacterKindArg(1));
@@ -1144,4 +1150,23 @@ void Script::setScriptTimer(bool reset) {
_scriptTimer = g_engine->getMillis();
}
+void Script::fixNestedMenuPop(uint32 pc) {
+ /* There seems to have been a script compiler bug related to nested dialog menus,
+ * where an additional PopN 1 is called underflowing the value stack.
+ * I see no good way to fix in the script interpreter so instead we patch the script
+ *
+ * This happens in:
+ * aventuradecine-cd-remastered-win-es
+ * secta-win-es
+ * escarabajo-win-es
+ * moscu-win-es
+ */
+ scumm_assert(pc < _instructions.size());
+ auto &instr = _instructions[pc];
+ scumm_assert(g_engine->game().getScriptOpMap()[instr._op] == ScriptOp::PopN && instr._arg == 1);
+
+ // the additional pop is a fallback for a switch that is never called, so just not popping is fine
+ instr._arg = 0;
+}
+
}
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 5afd1db2914..c4e39a7df8d 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -177,6 +177,7 @@ public:
inline bool hasVariable(const char *name) const { return _variableNames.contains(name); }
void setScriptTimer(bool reset);
+ void fixNestedMenuPop(uint32 pc);
private:
friend struct ScriptTask;
friend struct ScriptTimerTask;
Commit: 470ba13bb46d35b59c6e924aced62e1b0c7f373d
https://github.com/scummvm/scummvm/commit/470ba13bb46d35b59c6e924aced62e1b0c7f373d
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: Fix two coverity issues
- Access moved-from object
- Raw pointers not initialized in ctor
Changed paths:
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 6888d73f3e3..59860e26fce 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -1056,7 +1056,7 @@ public:
// e.g. c:\myf2000\textos\dialogos.txt
// but the filenames alone do not clash so we flatten everything
- skipVarString(*file);
+ skipVarString(*_file);
uint32 totalSize = _file->readUint32LE();
int64 endPosition = _file->pos() + totalSize;
_file->skip(2);
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 775267202dd..5d6235c7c82 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -225,9 +225,9 @@ private:
GameFileReference _globalAnimations[(int)GlobalAnimationKind::Count];
Common::String _initScriptName;
GameFileReference _scriptFileRef;
- Room *_globalRoom;
- Inventory *_inventory;
- MainCharacter *_filemon, *_mortadelo;
+ Room *_globalRoom = nullptr;
+ Inventory *_inventory = nullptr;
+ MainCharacter *_filemon = nullptr, *_mortadelo = nullptr;
uint8 _loadedMapCount = 0;
Common::HashMap<const char *, const char *,
Common::Hash<const char *>,
Commit: b7b1ba88c7ca28a8357bd40d2739b207ef9cb762
https://github.com/scummvm/scummvm/commit/b7b1ba88c7ca28a8357bd40d2739b207ef9cb762
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: Fix bracket style with if-else
Changed paths:
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 59860e26fce..c403deac30c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -42,8 +42,7 @@ Room::Room(World *world, SeekableReadStream &stream)
if (g_engine->isV1()) {
readRoomV1(stream);
readObjects(stream);
- }
- else
+ } else
readRoomV2and3(stream, false);
initBackground();
}
@@ -72,8 +71,7 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return new EditBoxV2(room, stream);
else
return new EditBoxV3(room, stream);
- }
- else if (type == PushButton::kClassName)
+ } else if (type == PushButton::kClassName)
return new PushButton(room, stream);
else if (type == CheckBox::kClassName)
return new CheckBox(room, stream);
@@ -84,8 +82,7 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
return new SlideButtonV2(room, stream);
else
return new SlideButtonV3(room, stream);
- }
- else if (type == IRCWindow::kClassName)
+ } else if (type == IRCWindow::kClassName)
return new IRCWindow(room, stream);
else if (type == MessageBox::kClassName)
return new MessageBox(room, stream);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 0393498baff..00d178e9816 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -484,8 +484,7 @@ private:
if (g_engine->isV1()) {
popN(g_engine->game().getKernelTaskArgCount(_lastKernelTaskI));
- }
- else {
+ } else {
scumm_assert(
_pc < _script._instructions.size() &&
g_engine->game().getScriptOpMap()[_script._instructions[_pc]._op] == ScriptOp::PopN);
@@ -586,8 +585,7 @@ private:
return value == 0
? MainCharacterKind::Mortadelo
: MainCharacterKind::Filemon;
- }
- else {
+ } else {
if (value < 0 || value > 2)
error("Unexpected value for main character kind: %d", value);
else
@@ -923,8 +921,7 @@ private:
if (process().character() == MainCharacterKind::None) {
// This happens in Secta, the original game just ignores this case
warning("Tried to drop from none-character-process: %s at %u", getStringArg(0), _pc);
- }
- else
+ } else
relatedCharacter().drop(getStringArg(0));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterDrop: {
Commit: 1829417aaf1578a872b9e6131f7488596b7cc06e
https://github.com/scummvm/scummvm/commit/1829417aaf1578a872b9e6131f7488596b7cc06e
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: Fix kernel proc names in debug traces
Changed paths:
engines/alcachofa/script-debug.h
engines/alcachofa/script.h
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
index 96bab4d7232..1580926390e 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -70,6 +70,7 @@ static const char *const KernelCallNames[] = {
"StopAndTurnMe",
"ChangeCharacter",
"SayText",
+ "SayTextV2",
"Go",
"Put",
"ChangeCharacterRoom",
@@ -101,12 +102,14 @@ static const char *const KernelCallNames[] = {
"WaitCamStopping",
"CamFollow",
"CamShake",
+ "CamShakeV2",
"LerpCamXY",
"LerpCamZ",
"LerpCamScale",
"LerpCamToObjectWithScale",
"LerpCamToObjectResettingZ",
"LerpCamRotation",
+ "LerpOrSetCam",
"FadeIn",
"FadeOut",
"FadeIn2",
@@ -114,7 +117,6 @@ static const char *const KernelCallNames[] = {
"LerpCamXYZ",
"LerpCamToObjectKeepingZ",
- "SheriffTakesCharacter",
"ChangeDoor",
"Disguise"
};
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index c4e39a7df8d..9383d3870f0 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -35,6 +35,7 @@ class Process;
// the ScriptOp and ScriptKernelTask enums represent the *implemented* order
// the specific Game instance maps the version-specific op codes to our order
+// keep the order in sync with ScriptOpNames/KernelCallNames in script-debug.h
enum class ScriptOp {
Nop,
@@ -129,7 +130,7 @@ enum class ScriptKernelTask {
LerpCamXYZ,
LerpCamToObjectKeepingZ,
- ChangeDoor, ///< some special-case V1 tasks, unknown yet
+ ChangeDoor, ///< NOOP
Disguise
};
Commit: 0b749d78195ec0ce81fd87ca00bdd763ccabd695
https://github.com/scummvm/scummvm/commit/0b749d78195ec0ce81fd87ca00bdd763ccabd695
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Adapt camera to V2
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/game-v2.cpp
engines/alcachofa/script-debug.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index d4098630113..beb81dd0a2e 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -84,7 +84,7 @@ Common::Error AlcachofaEngine::run() {
_world.load();
_renderer.reset(IRenderer::createOpenGLRenderer(game().getResolution()));
_drawQueue.reset(new DrawQueue(_renderer.get()));
- _camera.reset(isV1() || isV2() ? static_cast<Camera *>(new CameraV1()) : new CameraV3());
+ _camera.reset(Camera::create());
_script.reset(new Script());
_player.reset(new Player());
_globalUI.reset(isV1() || isV2() ? static_cast<GlobalUI *>(new GlobalUIV1()) : new GlobalUIV3());
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 3bf2a86d285..204b3f3e17b 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -34,6 +34,17 @@ namespace Alcachofa {
//
// Base camera
//
+Camera *Camera::create() {
+ if (g_engine->isV1())
+ return new CameraV1();
+ else if (g_engine->isV2())
+ return new CameraV2();
+ else if (g_engine->isV3())
+ return new CameraV3();
+ else
+ error("Camera is not implemented for this engine version");
+}
+
Camera::~Camera() {}
static Matrix4 scale2DMatrix(float scale) {
@@ -187,17 +198,40 @@ void CameraV1::update() {
_isLerping = true;
}
} else if (_isLerping) {
- auto distance = newCenter.getDistanceTo(_target);
- auto move = deltaTime * _lerpSpeed;
- _lastUpdateTime = g_engine->getMillis();
+ updateLerping(newCenter, deltaTime, _lerpSpeed);
+ }
- if (move < distance)
- newCenter += (_target - newCenter) / distance * move;
- else {
- newCenter = _target;
- _isLerping = false;
- }
+ setAppliedCenter(newCenter);
+}
+
+void CameraV1::updateLerping(Vector3d &newCenter, float deltaTime, float speed) {
+ auto distance = newCenter.getDistanceTo(_target);
+ auto move = deltaTime * speed;
+ _lastUpdateTime = g_engine->getMillis();
+
+ if (move < distance)
+ newCenter += (_target - newCenter) / distance * move;
+ else {
+ newCenter = _target;
+ _isLerping = false;
}
+}
+
+void CameraV2::update() {
+ auto deltaTime = (g_engine->getMillis() - _lastUpdateTime) / 1000.0f;
+ auto newCenter = _appliedCenter;
+
+ if (_followTarget != nullptr) {
+ _target = as3D(_followTarget->position());
+ auto delta = _target - _appliedCenter;
+ _isLerping |= MAX(fabsf(delta.x()), fabsf(delta.y())) > 35.0f;
+
+ if (_isLerping)
+ updateLerping(newCenter, deltaTime, _lerpSpeed * _followTarget->graphic()->depthScale());
+ else
+ _lastUpdateTime = g_engine->getMillis();
+ } else if (_isLerping)
+ updateLerping(newCenter, deltaTime, _lerpSpeed);
setAppliedCenter(newCenter);
}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 35d2c98ee4e..6abbde15376 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -37,6 +37,7 @@ static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
class Camera {
public:
+ static Camera *create();
virtual ~Camera();
virtual Math::Angle rotation() const = 0;
virtual float scale() const = 0;
@@ -93,8 +94,9 @@ public:
Task *disguise(Process &process, int32 duration);
-private:
+protected:
friend struct CamV1DisguiseTask;
+ void updateLerping(Math::Vector3d &newCenter, float deltaTime, float speed);
WalkingCharacter *_followTarget = nullptr;
Math::Vector3d _target;
@@ -103,7 +105,13 @@ private:
uint32 _lastUpdateTime = 0;
};
-class CameraV3 : public Camera {
+// V2 is so similar that only the update needs to be changed
+class CameraV2 final : public CameraV1 {
+public:
+ void update() override;
+};
+
+class CameraV3 final : public Camera {
public:
Math::Angle rotation() const override;
float scale() const override;
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index ee4be6202f5..4725848873c 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -94,7 +94,7 @@ static constexpr const ScriptKernelTask kScriptKernelTaskMap[] = {
ScriptKernelTask::Drop,
ScriptKernelTask::CharacterDrop,
ScriptKernelTask::ChangeDoor,
- ScriptKernelTask::CamShakeV2,
+ ScriptKernelTask::Disguise,
ScriptKernelTask::ToggleRoomFloor,
ScriptKernelTask::SetDialogLineReturn,
ScriptKernelTask::DialogMenu,
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
index 1580926390e..a908ea4f320 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -102,7 +102,6 @@ static const char *const KernelCallNames[] = {
"WaitCamStopping",
"CamFollow",
"CamShake",
- "CamShakeV2",
"LerpCamXY",
"LerpCamZ",
"LerpCamScale",
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 00d178e9816..611bae40ebb 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -959,9 +959,6 @@ private:
Vector2d(getNumberArg(1), getNumberArg(2)),
Vector2d(getNumberArg(3), getNumberArg(4)),
getNumberArg(0)));
- case ScriptKernelTask::CamShakeV2:
- warning("STUB: CamShakeV2");
- return TaskReturn::finish(0);
case ScriptKernelTask::LerpCamXY:
return TaskReturn::waitFor(g_engine->cameraV3().lerpPos(process(),
Vector2d(getNumberArg(0), getNumberArg(1)),
@@ -1028,11 +1025,13 @@ private:
if (pointObject == nullptr)
return TaskReturn::finish(1);
g_engine->cameraV1().lerpOrSet(pointObject->position(), getNumberArg(1));
+ // cameraV1 could also be the inherited cameraV2 here
}
return TaskReturn::finish(0);
}
case ScriptKernelTask::Disguise: {
// a somewhat bouncy vertical camera movement used in V1
+ // in V2 this would be a linear vertical shake, but only the waitForInput part is used
// or waiting for user to click
const auto duration = getNumberArg(0);
return TaskReturn::waitFor(duration == 0
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 9383d3870f0..1d388705db9 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -115,7 +115,6 @@ enum class ScriptKernelTask {
WaitCamStopping,
CamFollow,
CamShake,
- CamShakeV2,
LerpCamXY,
LerpCamZ,
LerpCamScale,
Commit: fbf769a5ab8f04adebad9c4c71e875934961f63f
https://github.com/scummvm/scummvm/commit/fbf769a5ab8f04adebad9c4c71e875934961f63f
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Fix subtitle position
Changed paths:
engines/alcachofa/game-v2.cpp
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 4725848873c..033c02304f6 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -128,7 +128,7 @@ public:
}
Point getSubtitlePos() override {
- return Point(g_system->getWidth() / 2, 150); // TODO: Check subtitle position
+ return Point(g_system->getWidth() / 2, g_system->getHeight() - 200);
}
const char *getMenuRoom() override {
Commit: c65661858335b8fc5b35b35b0f2ea2cc374eec48
https://github.com/scummvm/scummvm/commit/c65661858335b8fc5b35b35b0f2ea2cc374eec48
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Fix camera speed in large rooms
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 204b3f3e17b..9904ef42300 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -226,9 +226,14 @@ void CameraV2::update() {
auto delta = _target - _appliedCenter;
_isLerping |= MAX(fabsf(delta.x()), fabsf(delta.y())) > 35.0f;
- if (_isLerping)
- updateLerping(newCenter, deltaTime, _lerpSpeed * _followTarget->graphic()->depthScale());
- else
+ if (_isLerping) {
+ // The original code contains this formula to attenuate camera speed
+ // However timing experiments show that this is somehow negated or overwritten
+ //float scaleFactor = _followTarget->graphic()->depthScale();
+ //if (scaleFactor < 19660 / 65535.0f)
+ // scaleFactor = (13107 / 65535.0f) + scaleFactor / 3;
+ updateLerping(newCenter, deltaTime, _lerpSpeed);
+ } else
_lastUpdateTime = g_engine->getMillis();
} else if (_isLerping)
updateLerping(newCenter, deltaTime, _lerpSpeed);
@@ -244,6 +249,15 @@ void CameraV1::setRoomBounds(Graphic &background) {
_roomScale = 0;
}
+void CameraV2::setRoomBounds(Graphic &background) {
+ Point bgSize = background.animation().imageSize(0);
+ float scaleFactor = background.scale() / (float)kBaseScale;
+ Point screenSize(g_system->getWidth(), g_system->getHeight());
+ _roomMin = as2D(background.topLeft() + screenSize / 2) * scaleFactor;
+ _roomMax = _roomMin + as2D(bgSize - screenSize) * scaleFactor;
+ _roomScale = 0;
+}
+
void CameraV1::setFollow(WalkingCharacter *target) {
_lastUpdateTime = g_engine->getMillis();
_followTarget = target;
@@ -252,6 +266,11 @@ void CameraV1::setFollow(WalkingCharacter *target) {
setAppliedCenter(as3D(target->position()));
}
+void CameraV2::setFollow(WalkingCharacter *target) {
+ CameraV1::setFollow(target);
+ _lerpSpeed = 230.0f;
+}
+
void CameraV1::onChangedRoom(bool resetCamera) {
// nothing to do in V1
}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 6abbde15376..bb2aa608c9f 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -105,10 +105,12 @@ protected:
uint32 _lastUpdateTime = 0;
};
-// V2 is so similar that only the update needs to be changed
+// V2 is so similar that most can be reused from V1
class CameraV2 final : public CameraV1 {
public:
void update() override;
+ void setRoomBounds(Graphic &background) override;
+ void setFollow(WalkingCharacter *target) override;
};
class CameraV3 final : public Camera {
Commit: 4e51eaf599e29622b9f2375955d4a781570dff40
https://github.com/scummvm/scummvm/commit/4e51eaf599e29622b9f2375955d4a781570dff40
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Fix long-running animations
Previously animations with more than 127 images would disappear sometimes.
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 93322ee5aba..336d53b7567 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -320,8 +320,8 @@ void AnimationBase::createIndexMappingV1and2(const Array<byte> &spriteOrder) {
void AnimationBase::readFramesV1and2(Common::SeekableReadStream &stream, uint frameCount, uint spriteCount) {
for (uint i = 0; i < frameCount; i++) {
for (uint j = 0; j < spriteCount; j++) {
- int imageI = stream.readSByte();
- if (imageI <= 0) // we make sure that spriteBases + imageI <= 0 if the local imageI <= 0
+ int imageI = stream.readByte();
+ if (imageI <= 0 || imageI > _images.size()) // we make sure that spriteBases + imageI <= 0 if the local imageI is invalid
imageI = -(int)_spriteOffsets.size();
_spriteOffsets.push_back(imageI);
}
Commit: 8eeecc370894ea0348b3a3dae3e1ea7358d73f5a
https://github.com/scummvm/scummvm/commit/8eeecc370894ea0348b3a3dae3e1ea7358d73f5a
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Add moscu and escarabajo detection entries
Changed paths:
engines/alcachofa/detection_tables.h
engines/alcachofa/game-v1.cpp
engines/alcachofa/game-v2.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 0732593acfc..1d47614740e 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" },
+ { "escarabajo", "Mortadelo y Filemón: El escarabajo de Cleopatra" },
+ { "moscu", "Mortadelo y Filemón: Operación Moscú" },
{ "secta", "Mortadelo y Filemón: La Sexta Secta" },
{ "terror", "Mortadelo y Filemón: Terror, Espanto y Pavor" },
{ "vaqueros", "Mortadelo y Filemón: Dos vaqueros chapuceros" },
@@ -137,7 +139,61 @@ const AlcachofaGameDescription gameDescriptions[] = {
{
"secta",
"Mortadelo y Filemón: La Sexta Secta",
- AD_ENTRY1s("Fondos/MUSEO_O.ANI", "40a880c866aabbb5c09899d9b7ca66b6", 10630),
+ AD_ENTRY3s(
+ "Fondos/MUSEO_O.ANI", "40a880c866aabbb5c09899d9b7ca66b6", 10630,
+ "Mapas/mapa1.emc", "c04b7b6424c02d5da0719bdf648003a1", 36530,
+ "Mapas/mapa2.emc", "c04b7b6424c02d5da0719bdf648003a1", 67129),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO0()
+ },
+ EngineVersion::V2_0
+ },
+
+ //
+ // Operación Moscú
+ //
+ {
+ {
+ "moscu",
+ "Mortadelo y Filemón: Operación Moscú",
+ AD_ENTRY2s(
+ "Fondos/MUSEO_O.ANI", "40a880c866aabbb5c09899d9b7ca66b6", 10630,
+ "Mapas/mapa1.emc", "c04b7b6424c02d5da0719bdf648003a1", 36530),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO0()
+ },
+ EngineVersion::V2_0
+ },
+ // moscu had a variant with translated texts published on Steam
+ {
+ {
+ "moscu",
+ "Mortadelo y Filemón: Operación Moscú",
+ AD_ENTRY2s(
+ "Fondos/MUSEO_O.ANI", "479ae9100e1730ac03e6f3f84da91f32", 11265,
+ "Mapas/mapa1.emc", "c04b7b6424c02d5da0719bdf648003a1", 36530),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO0()
+ },
+ EngineVersion::V2_0
+ },
+
+ //
+ // El escarabajo de Cleopatra
+ //
+ {
+ {
+ "escarabajo",
+ "Mortadelo y Filemón: El escarabajo de Cleopatra",
+ AD_ENTRY2s(
+ "Fondos/MUSEO_O.ANI", "40a880c866aabbb5c09899d9b7ca66b6", 10630,
+ "Mapas/mapa2.emc", "c04b7b6424c02d5da0719bdf648003a1", 67129),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
diff --git a/engines/alcachofa/game-v1.cpp b/engines/alcachofa/game-v1.cpp
index af0a23c11e3..951119522e0 100644
--- a/engines/alcachofa/game-v1.cpp
+++ b/engines/alcachofa/game-v1.cpp
@@ -398,7 +398,7 @@ public:
}
String getMusicPath(int32 trackId) override {
- return String::format("track%02d", trackId);
+ return String::format("track%d", trackId);
}
// probably the original CDs have music, the Steam release has no music...
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 033c02304f6..460e220c550 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -161,10 +161,6 @@ public:
return String("Sonidos/") + filename;
}
- String getMusicPath(int32 trackId) override {
- return String::format("Music/Track%02d", trackId);
- }
-
int32 getCharacterJingle(MainCharacterKind kind) override {
return g_engine->script().variable(
kind == MainCharacterKind::Mortadelo ? "PistaMorta" : "PistaFile");
@@ -221,6 +217,10 @@ public:
script.fixNestedMenuPop(5921); // Mortadelo talking to ARQUEOLOGOS in CARRETERA
script.fixNestedMenuPop(20898); // Filemon talking to MANOLO in FILE_PIRAMIDE
}
+
+ char getTextFileKey() override {
+ return static_cast<char>(0xA3);
+ }
};
static constexpr const char *kMapFilesSecta[] = {
@@ -230,6 +230,18 @@ static constexpr const char *kMapFilesSecta[] = {
nullptr
};
+static constexpr const char *kMapFilesMoscu[] = {
+ "Mapas/mapa1.emc",
+ "Mapas/global.emc",
+ nullptr
+};
+
+static constexpr const char *kMapFilesEscarabajo[] = {
+ "Mapas/mapa2.emc",
+ "Mapas/global.emc",
+ nullptr
+};
+
class GameSecta : public GameWithVersion2_0 {
public:
GameSecta() {
@@ -246,20 +258,64 @@ public:
return !_videosAreExtracted;
}
+ void onLoadedGameFiles() override {
+ g_engine->script().variable("EsJuegoCompleto") = 0;
+ }
+
const char *const *getMapFiles() override {
return kMapFilesSecta;
}
- char getTextFileKey() override {
- return static_cast<char>(0xA3);
+ String getMusicPath(int32 trackId) override {
+ const Room *room = g_engine->player().currentRoom();
+ const char *dirName = room != nullptr && room->mapIndex() == 1 ? "Music_Cleopatra" : "Music";
+ return String::format("%s/Track%02d", dirName, trackId);
}
private:
bool _videosAreExtracted = true;
};
+class GameMoscu : public GameWithVersion2_0 {
+public:
+ void onLoadedGameFiles() override {
+ g_engine->script().variable("EsJuegoCompleto") = 1;
+ }
+
+ const char *const *getMapFiles() override {
+ return kMapFilesMoscu;
+ }
+
+ String getMusicPath(int32 trackId) override {
+ return String::format("track%d", trackId);
+ }
+};
+
+class GameEscarabajo : public GameWithVersion2_0 {
+public:
+ void onLoadedGameFiles() override {
+ g_engine->script().variable("EsJuegoCompleto") = 2;
+ }
+
+ const char *const *getMapFiles() override {
+ return kMapFilesEscarabajo;
+ }
+
+ String getMusicPath(int32 trackId) override {
+ return String::format("track%d", trackId);
+ }
+};
+
Game *Game::createForSecta() {
return new GameSecta();
}
+Game *Game::createForMoscu() {
+ return new GameMoscu();
+}
+
+Game *Game::createForEscarabajo() {
+ return new GameEscarabajo();
+}
+
}
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 02d61a657ed..c882e99e13b 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -227,6 +227,10 @@ Game *Game::create() {
switch (*desc.desc.gameId) {
case 's':
return createForSecta();
+ case 'm':
+ return createForMoscu();
+ case 'e':
+ return createForEscarabajo();
}
break;
case EngineVersion::V3_0:
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index f7a5a1ff51f..b7354842ad7 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -128,6 +128,8 @@ public:
static Game *createForTerror(); // V1
static Game *createForVaqueros(); // V1
static Game *createForSecta(); // V2
+ static Game *createForMoscu(); // V2
+ static Game *createForEscarabajo(); // V2
const Message _message;
};
Commit: dea78dd95f0ed1a5d750d5f1f7beb7c307579b03
https://github.com/scummvm/scummvm/commit/dea78dd95f0ed1a5d750d5f1f7beb7c307579b03
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: Fix signed/unsigned comparison warning
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 336d53b7567..f2e752a2921 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -321,7 +321,7 @@ void AnimationBase::readFramesV1and2(Common::SeekableReadStream &stream, uint fr
for (uint i = 0; i < frameCount; i++) {
for (uint j = 0; j < spriteCount; j++) {
int imageI = stream.readByte();
- if (imageI <= 0 || imageI > _images.size()) // we make sure that spriteBases + imageI <= 0 if the local imageI is invalid
+ if (imageI <= 0 || (uint)imageI > _images.size()) // we make sure that spriteBases + imageI <= 0 if the local imageI is invalid
imageI = -(int)_spriteOffsets.size();
_spriteOffsets.push_back(imageI);
}
Commit: 973d5f01279fb8c671ed05cb86d88081de2d1a49
https://github.com/scummvm/scummvm/commit/973d5f01279fb8c671ed05cb86d88081de2d1a49
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Add corvino, balones and mamelucos detection entries
Changed paths:
engines/alcachofa/detection.h
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index f505a2e55b8..2c45bb86ac0 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -36,7 +36,8 @@ enum AlcachofaDebugChannels {
enum class EngineVersion {
V1_0 = 10, // edicion orginal, vaqueros and terror
- V2_0 = 20, // the rest
+ V2_0 = 20, // secta, moscu and escarabajo
+ V2_1 = 21, // corvino, balones and mamelucos
V3_0 = 30, // Remastered movie adventure (used for original spanish release)
V3_1 = 31, // Remastered movie adventure (for german release and english/spanish steam release)
};
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 1d47614740e..ec6db19eed2 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -23,7 +23,10 @@ namespace Alcachofa {
const PlainGameDescriptor alcachofaGames[] = {
{ "aventuradecine", "Mort & Phil: A Movie Adventure" },
+ { "balones", "Mortadelo y Filemón: Balones y Patadones" },
+ { "corvino", "Mortadelo y Filemón: La Banda de Corvino" },
{ "escarabajo", "Mortadelo y Filemón: El escarabajo de Cleopatra" },
+ { "mamelucos", "Mortadelo y Filemón: Mamelucos a la Romana" },
{ "moscu", "Mortadelo y Filemón: Operación Moscú" },
{ "secta", "Mortadelo y Filemón: La Sexta Secta" },
{ "terror", "Mortadelo y Filemón: Terror, Espanto y Pavor" },
@@ -132,6 +135,61 @@ const AlcachofaGameDescription gameDescriptions[] = {
EngineVersion::V3_1
},
+ //
+ // La Banda de Corvino
+ //
+ {
+ {
+ "corvino",
+ "Mortadelo y Filemón: La Banda de Corvino",
+ AD_ENTRY3s(
+ "Fondos/MUSEO_O.ANI", "830443af2290a96a95703a17c1915c21", 9732, // this file contains object names, thus detects the language
+ "Mapas/mapa1.emc", "d0a8eb184e813cf337840bb0e5270ee8", 33515,
+ "Mapas/mapa2.emc", "d0a8eb184e813cf337840bb0e5270ee8", 40452),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO0()
+ },
+ EngineVersion::V2_1
+ },
+
+ //
+ // Balones y Patadones
+ //
+ {
+ {
+ "balones",
+ "Mortadelo y Filemón: Balones y Patadones",
+ AD_ENTRY2s(
+ "Fondos/MUSEO_O.ANI", "830443af2290a96a95703a17c1915c21", 9732,
+ "Mapas/mapa1.emc", "d0a8eb184e813cf337840bb0e5270ee8", 33515),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO0()
+ },
+ EngineVersion::V2_1
+ },
+
+ //
+ // Mamelucos a la Romana
+ //
+ {
+ {
+ "corvino",
+ "Mortadelo y Filemón: Mamelucos a la Romana",
+ AD_ENTRY2s(
+ "Fondos/MUSEO_O.ANI", "830443af2290a96a95703a17c1915c21", 9732,
+ "Mapas/mapa2.emc", "d0a8eb184e813cf337840bb0e5270ee8", 40452),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO0()
+ },
+ EngineVersion::V2_1
+ },
+
//
// La Sexta Secta
//
Commit: 471b404af57bc4ab105be4e3a507dc04e38d0fe5
https://github.com/scummvm/scummvm/commit/471b404af57bc4ab105be4e3a507dc04e38d0fe5
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:29+02:00
Commit Message:
ALCACHOFA: V2: Fix startup of escarabajo and moscu
Changed paths:
engines/alcachofa/game-v2.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 460e220c550..20e6eed0978 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -289,10 +289,18 @@ public:
String getMusicPath(int32 trackId) override {
return String::format("track%d", trackId);
}
+
+ bool isKnownBadVideo(int32 videoId) override {
+ return videoId == 0; // MPEG-4 codec is unsupported
+ }
};
class GameEscarabajo : public GameWithVersion2_0 {
public:
+ GameEscarabajo() {
+ _hasMessedUpEncoding = !SearchMan.hasFile(Path(reencode("Animaciones/M\xC1SCARA MUSEO_RECEPCI\xD3N.ANI")));
+ }
+
void onLoadedGameFiles() override {
g_engine->script().variable("EsJuegoCompleto") = 2;
}
@@ -304,6 +312,31 @@ public:
String getMusicPath(int32 trackId) override {
return String::format("track%d", trackId);
}
+
+ bool isKnownBadVideo(int32 videoId) override {
+ return videoId == 0; // MPEG-4 codec is unsupported
+ }
+
+ String reencodePath(const String &path) override {
+ if (!_hasMessedUpEncoding)
+ return Game::reencodePath(path);
+
+ // The Steam release has wrong characters due to some messed up UTF8 conversion
+ U32String u32String = path.decode(Common::CodePage::kISO8859_1);
+ for (uint i = 0; i < u32String.size(); i++) {
+ const auto ch = u32String[i];
+ if (ch == 0xC1) // Ã -> â¡
+ u32String[i] = 0x2561;
+ else if (ch == 0xD3) // à -> α
+ u32String[i] = 0x03B1;
+ else if (ch == 0xCD) // Ã -> â
+ u32String[i] = 0x2553;
+ }
+ return u32String.encode();
+ }
+
+private:
+ bool _hasMessedUpEncoding = false;
};
Game *Game::createForSecta() {
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index c882e99e13b..db0a7d79965 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -39,6 +39,11 @@ void Game::onLoadedGameFiles() {}
void Game::drawScreenStates() {}
+String Game::reencodePath(const String &path) {
+ // Except for the messed up Stream release of escarabajo, this suffices
+ return reencode(path);
+}
+
int32 Game::getKernelTaskArgCount(int32 kernelTaskI) {
(void)kernelTaskI;
return 0;
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index b7354842ad7..c91ef61f8ac 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -59,6 +59,7 @@ public:
virtual Common::Span<const ScriptKernelTask> getScriptKernelTaskMap() = 0;
virtual void updateScriptVariables() = 0;
virtual void drawScreenStates();
+ virtual Common::String reencodePath(const Common::String &path);
virtual const char *getDialogFileName() = 0;
virtual const char *getObjectFileName() = 0;
virtual char getTextFileKey() = 0;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c403deac30c..9781d35647c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -637,7 +637,9 @@ ObjectBase *World::getObjectByName(MainCharacterKind character, const char *name
return getObjectByName(name);
const auto &player = g_engine->player();
ObjectBase *result = nullptr;
- if (player.activeCharacterKind() == character && player.currentRoom() != player.activeCharacter()->room())
+ if (player.activeCharacterKind() == character &&
+ player.currentRoom() != nullptr &&
+ player.currentRoom() != player.activeCharacter()->room())
result = player.currentRoom()->getObjectByName(name);
if (result == nullptr)
result = player.activeCharacter()->room()->getObjectByName(name);
@@ -1015,7 +1017,7 @@ GameFileReference World::readFileRef(SeekableReadStream &stream) const {
stream.skip(size);
return { name, (uint32)_files.size(), offset, size };
} else
- return GameFileReference(reencode(name));
+ return GameFileReference(g_engine->game().reencodePath(name));
}
ScopedPtr<SeekableReadStream> World::openFileRef(const GameFileReference &ref) const {
Commit: ea37a7d5a856b005a875cb1405b5d4ec10f428cc
https://github.com/scummvm/scummvm/commit/ea37a7d5a856b005a875cb1405b5d4ec10f428cc
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: V2: Fix script error on entering DESPACHO_MOMIEZ twice
Changed paths:
engines/alcachofa/game-v2.cpp
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 20e6eed0978..40ceb67aee2 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -221,6 +221,13 @@ public:
char getTextFileKey() override {
return static_cast<char>(0xA3);
}
+
+ PointObject *unknownCamLerpTarget(const char *action, const char *name) override {
+ // Original bug: a main character being reinterpret_cast to a PointObject, undefined behavior ensues
+ if (scumm_stricmp(name, "FILEMON"))
+ return Game::unknownCamLerpTarget(action, name);
+ return nullptr;
+ }
};
static constexpr const char *kMapFilesSecta[] = {
Commit: ae3cba18ed1bb880a3b5ab2daa5f45b7cfa9fe45
https://github.com/scummvm/scummvm/commit/ae3cba18ed1bb880a3b5ab2daa5f45b7cfa9fe45
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: Reversing draw order within one layer
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index e4152f28d15..70d826eb3c9 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -930,7 +930,7 @@ void MainCharacter::walkTo(
void MainCharacter::draw() {
if (this == &g_engine->world().mortadelo()) {
- if (_currentPos.y <= g_engine->world().filemon()._currentPos.y) {
+ if (_currentPos.y > g_engine->world().filemon()._currentPos.y) {
g_engine->world().mortadelo().drawInner();
g_engine->world().filemon().drawInner();
} else {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index f2e752a2921..d01d4ea91a9 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -1248,8 +1248,8 @@ void DrawQueue::draw() {
for (int8 order = kOrderCount - 1; order >= 0; order--) {
_renderer->setLodBias(_lodBiasPerOrder[order]);
for (uint8 requestI = 0; requestI < _requestsPerOrderCount[order]; requestI++) {
- _requestsPerOrder[order][requestI]->draw();
- _requestsPerOrder[order][requestI]->~IDrawRequest();
+ _requestsPerOrder[order][_requestsPerOrderCount[order] - 1 - requestI]->draw();
+ _requestsPerOrder[order][_requestsPerOrderCount[order] - 1 - requestI]->~IDrawRequest();
}
}
_allocator.deallocateAll();
Commit: 657c8c1292fc95d415477fb3fa16f78c7e133aab
https://github.com/scummvm/scummvm/commit/657c8c1292fc95d415477fb3fa16f78c7e133aab
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: Reenable DefectoObjeto script
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index e3ed38daec9..745c140d715 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -211,10 +211,9 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
_activeCharacter->currentlyUsing() = nullptr;
if (scumm_stricmp(action, "MIRAR") == 0)
script.createProcess(activeCharacterKind(), "DefectoMirar");
- //else if (action[0] == 'i' && object->name()[0] == 'i')
- // This case can happen if you combine two objects without procedure, the original engine
- // would attempt to start the procedure "DefectoObjeto" which does not exist
- // (this should be revised when working on further games)
+ else if (action[0] == 'i' && object->name()[0] == 'i' && script.hasProcedure("DefectoObjeto"))
+ // This case can happen if you combine two objects without procedure. This does not happen in all games
+ script.createProcess(activeCharacterKind(), "DefectoObjeto");
else
script.createProcess(activeCharacterKind(), "DefectoUsar");
}
Commit: 3d113845f199dd35e2da32a27757963fecb1e1c5
https://github.com/scummvm/scummvm/commit/3d113845f199dd35e2da32a27757963fecb1e1c5
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: V2: Disable texture filtering by default
Changed paths:
engines/alcachofa/game-v2.cpp
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index 40ceb67aee2..a8bd65ce7a8 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -172,7 +172,7 @@ public:
}
bool shouldFilterTexturesByDefault() override {
- return true; // TODO: Check this!
+ return false;
}
bool shouldClipCamera() override {
Commit: d9e3e66dc207cf29fee43297a0fa1c3afd8f9927
https://github.com/scummvm/scummvm/commit/d9e3e66dc207cf29fee43297a0fa1c3afd8f9927
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: Fix script loading with duplicated procedures
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 611bae40ebb..3fac2bfe734 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -80,7 +80,7 @@ Script::Script() {
String name = readVarString(*file);
uint32 offset = file->readUint32LE();
file->skip(sizeof(uint32));
- _procedures[name] = offset - 1; // originally one-based, but let's not.
+ _procedures[name] = offset;
}
uint32 behaviorCount = file->readUint32LE();
@@ -93,7 +93,9 @@ Script::Script() {
String name = behaviorName + readVarString(*file);
uint32 offset = file->readUint32LE();
file->skip(sizeof(uint32));
- _procedures[name] = offset - 1;
+ uint32 &storedOffset = _procedures.getOrCreateVal(name);
+ if (storedOffset == 0) // keep the offset one-based so we can detect previous procedures and not override them
+ storedOffset = offset;
}
}
@@ -1130,10 +1132,11 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
g_engine->game().unknownScriptProcedure(procedure);
return nullptr;
}
+ assert(offset > 0); // offsets are stored one-based to simplify loading
FakeLock lock;
if (!(flags & ScriptFlags::IsBackground))
lock = FakeLock("script", g_engine->player().semaphoreFor(character));
- Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset, Common::move(lock));
+ Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset - 1, Common::move(lock));
process->name() = procedure;
return process;
}
Commit: 73a82da4d14417f53ceedcc6a28917efc2aad198
https://github.com/scummvm/scummvm/commit/73a82da4d14417f53ceedcc6a28917efc2aad198
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: Add walk fallback if character is outside the floor shape
Changed paths:
engines/alcachofa/game-objects.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 70d826eb3c9..eee8e4b2fb7 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -715,8 +715,10 @@ void WalkingCharacter::walkTo(
_pathPoints.clear();
auto floor = room()->activeFloor();
- if (floor != nullptr)
- floor->findPath(_sourcePos, target, _pathPoints);
+ if (floor != nullptr && !floor->findPath(_sourcePos, target, _pathPoints)) {
+ // just walk directly, ignoring the floor shape altogether
+ _pathPoints.push(target);
+ }
if (_pathPoints.empty()) {
_isWalking = false;
onArrived();
Commit: 3fce6a3eaa559e732fb6bc3c4baf5379270f58da
https://github.com/scummvm/scummvm/commit/3fce6a3eaa559e732fb6bc3c4baf5379270f58da
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: Allow characters as camera targets
Changed paths:
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 33b463f6a06..6dc40a3e592 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -56,6 +56,8 @@ Console::Console() : GUI::Debugger() {
registerCmd("playVoice", WRAP_METHOD(Console, cmdPlaySound));
registerCmd("playSFX", WRAP_METHOD(Console, cmdPlaySound));
registerCmd("dumpFile", WRAP_METHOD(Console, cmdDumpFile));
+ registerCmd("procedureAt", WRAP_METHOD(Console, cmdProcedureAt));
+ registerCmd("pa", WRAP_METHOD(Console, cmdProcedureAt));
}
Console::~Console() {}
@@ -378,4 +380,22 @@ bool Console::cmdDumpFile(int argc, const char **args) {
return true;
}
+bool Console::cmdProcedureAt(int argc, const char **args) {
+ if (argc != 2) {
+ debugPrintf("usage: %s pc\n", args[0]);
+ return true;
+ }
+
+ char *end = nullptr;
+ uint32 pc = (uint32)strtoul(args[1], &end, 10);
+ if (end == nullptr || *end != '\0') {
+ debugPrintf("pc has to be an unsigned integer\n");
+ return true;
+ }
+
+ auto procedure = g_engine->script().procedureAt(pc);
+ debugPrintf("%u is part of %s\n", pc, procedure.c_str());
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index d5e08a961fa..e4cb24c2eae 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -65,6 +65,7 @@ private:
bool cmdToggleObject(int argc, const char **args);
bool cmdPlaySound(int argc, const char **args);
bool cmdDumpFile(int argc, const char **args);
+ bool cmdProcedureAt(int argc, const char **args);
bool _showGraphics = false;
bool _showInteractables = false;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 3fac2bfe734..5516ec6d8d3 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -142,6 +142,30 @@ bool Script::hasProcedure(const Common::String &procedure) const {
return _procedures.contains(procedure);
}
+String Script::procedureAt(uint32 pc) const {
+ // this method is very inefficient but it is only used for debugging
+ typedef Pair<String, uint32> Node;
+ Array<Node> sorted;
+ sorted.reserve(_procedures.size());
+ for (const auto &proc : _procedures)
+ sorted.emplace_back(proc._key, proc._value);
+ sort(sorted.begin(), sorted.end(),
+ [ ] (const Node &a, const Node &b) { return a.second < b.second; });
+
+ // next lowest procedure
+ uint min = 0, max = sorted.size() - 1;
+ while (min < max) {
+ uint center = (min + max + 1) / 2;
+ if (sorted[center].second == pc)
+ min = max = center;
+ else if (sorted[center].second > pc)
+ max = center - 1;
+ else
+ min = center;
+ }
+ return sorted[min].first;
+}
+
struct ScriptTimerTask final : public Task {
ScriptTimerTask(Process &process, int32 durationSec)
: Task(process)
@@ -634,6 +658,21 @@ private:
return dynamic_cast<TObject *>(object);
}
+ const Point kInvalidPoint = { INT16_MIN, INT16_MIN };
+ Point getCamLerpTargetArg(const char *action, uint argI) {
+ auto *pointObject = getObjectArg<PointObject>(argI);
+ if (pointObject != nullptr)
+ return pointObject->position();
+
+ // in V2 a main character (we can reduce to walking character) can be used instead
+ auto *character = getObjectArg<WalkingCharacter>(argI);
+ if (character != nullptr)
+ return character->position();
+
+ pointObject = g_engine->game().unknownCamLerpTarget(action, getStringArg(argI));
+ return pointObject == nullptr ? kInvalidPoint : pointObject->position();
+ }
+
MainCharacter &relatedCharacter() {
if (g_engine->isV1()) {
auto character = g_engine->player().activeCharacter();
@@ -984,49 +1023,39 @@ private:
case ScriptKernelTask::LerpCamToObjectKeepingZ: {
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)
- pointObject = g_engine->game().unknownCamLerpTarget("LerpCamToObjectKeepingZ", getStringArg(0));
- if (pointObject == nullptr)
+ auto target = getCamLerpTargetArg("LerpCamToObjectKeepingZ", 0);
+ if (target == kInvalidPoint)
return TaskReturn::finish(1);
- return TaskReturn::waitFor(g_engine->cameraV3().lerpPos(process(),
- as2D(pointObject->position()),
- getNumberArg(1), EasingType::Linear));
+ return TaskReturn::waitFor(
+ g_engine->cameraV3().lerpPos(process(), as2D(target), getNumberArg(1), EasingType::Linear));
}
case ScriptKernelTask::LerpCamToObjectResettingZ: {
if (!process().isActiveForPlayer())
return TaskReturn::waitFor(delay(getNumberArg(1)));
- auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr)
- pointObject = g_engine->game().unknownCamLerpTarget("LerpCamToObjectResettingZ", getStringArg(0));
- if (pointObject == nullptr)
+ auto target = getCamLerpTargetArg("LerpCamToObjectResettingZ", 0);
+ if (target == kInvalidPoint)
return TaskReturn::finish(1);
- return TaskReturn::waitFor(g_engine->cameraV3().lerpPos(process(),
- as3D(pointObject->position()),
- getNumberArg(1), (EasingType)getNumberArg(2)));
+ return TaskReturn::waitFor(
+ g_engine->cameraV3().lerpPos(process(), as3D(target), getNumberArg(1), (EasingType)getNumberArg(2)));
}
case ScriptKernelTask::LerpCamToObjectWithScale: {
float targetScale = getNumberArg(1) * 0.01f;
if (!process().isActiveForPlayer())
// the scale will wait then snap the scale
return TaskReturn::waitFor(g_engine->cameraV3().lerpScale(process(), targetScale, getNumberArg(2), EasingType::Linear));
- auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr)
- pointObject = g_engine->game().unknownCamLerpTarget("LerpCamToObjectWithScale", getStringArg(0));
- if (pointObject == nullptr)
+ auto target = getCamLerpTargetArg("LerpCamToObjectWithScale", 0);
+ if (target == kInvalidPoint)
return TaskReturn::finish(1);
return TaskReturn::waitFor(g_engine->cameraV3().lerpPosScale(process(),
- as3D(pointObject->position()), targetScale,
+ as3D(target), targetScale,
getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
}
case ScriptKernelTask::LerpOrSetCam: {
if (process().isActiveForPlayer()) {
- auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr)
- pointObject = g_engine->game().unknownCamLerpTarget("LerpOrSetCam", getStringArg(0));
- if (pointObject == nullptr)
+ auto target = getCamLerpTargetArg("LerpOrSetCam", 0);
+ if (target == kInvalidPoint)
return TaskReturn::finish(1);
- g_engine->cameraV1().lerpOrSet(pointObject->position(), getNumberArg(1));
+ g_engine->cameraV1().lerpOrSet(target, getNumberArg(1));
// cameraV1 could also be the inherited cameraV2 here
}
return TaskReturn::finish(0);
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 1d388705db9..d57ea91a888 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -121,7 +121,7 @@ enum class ScriptKernelTask {
LerpCamToObjectWithScale,
LerpCamToObjectResettingZ,
LerpCamRotation,
- LerpOrSetCam, // only V1
+ LerpOrSetCam, // only V1 and V2
FadeIn,
FadeOut,
FadeIn2,
@@ -170,6 +170,7 @@ public:
ScriptFlags flags = ScriptFlags::None);
bool hasProcedure(const Common::String &behavior, const Common::String &action) const;
bool hasProcedure(const Common::String &procedure) const;
+ Common::String procedureAt(uint32 pc) const;
using VariableNameIterator = Common::HashMap<Common::String, uint32>::const_iterator;
inline VariableNameIterator beginVariables() const { return _variableNames.begin(); }
Commit: c21da18d93d618385a91c97c6984e90076685ed3
https://github.com/scummvm/scummvm/commit/c21da18d93d618385a91c97c6984e90076685ed3
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: Remove ScriptKernelTask::ChangeDoor
Turns out this kernel task was always a NOOP and is not used in any game
Changed paths:
engines/alcachofa/game-v1.cpp
engines/alcachofa/game-v2.cpp
engines/alcachofa/script-debug.h
engines/alcachofa/script.h
diff --git a/engines/alcachofa/game-v1.cpp b/engines/alcachofa/game-v1.cpp
index 951119522e0..4f056d77e21 100644
--- a/engines/alcachofa/game-v1.cpp
+++ b/engines/alcachofa/game-v1.cpp
@@ -90,7 +90,7 @@ static constexpr const ScriptKernelTask kScriptKernelTaskMap[] = {
ScriptKernelTask::ChangeCharacter,
ScriptKernelTask::LerpOrSetCam,
ScriptKernelTask::Drop,
- ScriptKernelTask::ChangeDoor,
+ ScriptKernelTask::Nop,
ScriptKernelTask::Disguise,
ScriptKernelTask::ToggleRoomFloor,
ScriptKernelTask::SetDialogLineReturn,
diff --git a/engines/alcachofa/game-v2.cpp b/engines/alcachofa/game-v2.cpp
index a8bd65ce7a8..3ebeed161be 100644
--- a/engines/alcachofa/game-v2.cpp
+++ b/engines/alcachofa/game-v2.cpp
@@ -93,7 +93,7 @@ static constexpr const ScriptKernelTask kScriptKernelTaskMap[] = {
ScriptKernelTask::LerpOrSetCam,
ScriptKernelTask::Drop,
ScriptKernelTask::CharacterDrop,
- ScriptKernelTask::ChangeDoor,
+ ScriptKernelTask::Nop,
ScriptKernelTask::Disguise,
ScriptKernelTask::ToggleRoomFloor,
ScriptKernelTask::SetDialogLineReturn,
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
index a908ea4f320..96af847cfbc 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -115,8 +115,6 @@ static const char *const KernelCallNames[] = {
"FadeOut2",
"LerpCamXYZ",
"LerpCamToObjectKeepingZ",
-
- "ChangeDoor",
"Disguise"
};
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index d57ea91a888..378a3391a36 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -128,8 +128,6 @@ enum class ScriptKernelTask {
FadeOut2,
LerpCamXYZ,
LerpCamToObjectKeepingZ,
-
- ChangeDoor, ///< NOOP
Disguise
};
Commit: a5bd081174c97297d9115a5dce21d754d25de198
https://github.com/scummvm/scummvm/commit/a5bd081174c97297d9115a5dce21d754d25de198
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: V2: Fix loading saves after teleporting
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 745c140d715..00017417cfc 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -377,9 +377,10 @@ void Player::syncGame(Serializer &s) {
String roomName;
if (s.isSaving()) {
+ bool isInInventory = currentRoom() == &g_engine->world().inventory();
roomName =
g_engine->menu().isOpen() ? g_engine->menu().previousRoom()->name() // save from in-game menu
- : _roomBeforeInventory != nullptr ? _roomBeforeInventory->name() // save from ScummVM while in inventory
+ : isInInventory && _roomBeforeInventory != nullptr ? _roomBeforeInventory->name() // save from ScummVM while in inventory
: currentRoom()->name(); // save from ScumnmVM global menu or autosave in normal gameplay
}
s.syncString(roomName);
Commit: 639847ad8ca48f5797ef4d178673f61857771d7a
https://github.com/scummvm/scummvm/commit/639847ad8ca48f5797ef4d178673f61857771d7a
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-03-31T17:22:30+02:00
Commit Message:
ALCACHOFA: V2: Fix pickup script from non-char process
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 5516ec6d8d3..f082b29e2ed 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -951,7 +951,12 @@ private:
// Inventory control
case ScriptKernelTask::Pickup:
- relatedCharacter().pickup(getStringArg(0), !getNumberArg(1));
+ if (process().character() == MainCharacterKind::None) {
+ // This happens in Secta, the original game just ignores this case
+ // A ChangeCharacter(0) is executed right before so this is actually just a script bug
+ warning("Tried to drop from none-character-process: %s at %u", getStringArg(0), _pc);
+ } else
+ relatedCharacter().pickup(getStringArg(0), !getNumberArg(1));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterPickup: {
auto &character = g_engine->world().getMainCharacterByKind(getMainCharacterKindArg(1));
More information about the Scummvm-git-logs
mailing list