[Scummvm-git-logs] scummvm master -> 007741ef41d3cce2d9ec09352c6e654f0c6cc3ff
sev-
noreply at scummvm.org
Tue Sep 2 20:07:02 UTC 2025
This automated email contains information about 202 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
2c9c7a16a7 ALCACHOFA: Add initial engine template
b0fda8ff19 ALCACHOFA: Add first detection entry
b2ab2453ae ALCACHOFA: Read world data
ea67e329f3 ALCACHOFA: Add initial animation and OpenGL support
51836a48cd ALCACHOFA: Add Graphic playback methods
1859ad6807 ALCACHOFA: Add DrawQueue and AnimationDrawRequest
15915b5407 ALCACHOFA: Add camera and 3D draw requests
872e5b3715 ALCACHOFA: Add basic room loop and GraphicObject drawing
c5e969cc8a ALCACHOFA: Add SpecialEffectGraphicObject rendering
afe0f02dba ALCACHOFA: Fix sprite prerendering with semi-transparent images
6bfee722ae ALCACHOFA: Fix shape loading and add debug drawing
f84c679788 ALCACHOFA: Add mouse input and Shape::contains
2e8b508a5f ALCACHOFA: Add shape interpolations
8518c4d275 ALCACHOFA: Add path finding
9fd0d74242 ALCACHOFA: Add drawing characters
bc3380d4ac ALCACHOFA: Add walking characters
b0fc780802 ALCACHOFA: Add parts of main characters
49e716eeb0 ALCACHOFA: Initial scheduler and script
adc4328047 ALCACHOFA: Add script execution
f561316bd8 ALCACHOFA: Move updateCommonVariables to Script
cb1c9873f0 ALCACHOFA: Implement easy kernel tasks
0e38466e35 ALCACHOFA: Fix kernel call animate not accepting string as boolean arg
bb2848b371 ALCACHOFA: Move changing state into Player instead of World
24dbc38528 ALCACHOFA: Initialize items
0cf432015c ALCACHOFA: Add game room interaction
4fff63d978 ALCACHOFA: Add door shortcuts
a13f5d8999 ALCACHOFA: Add camera following characters
7df9ae0993 ALCACHOFA: Add technical room changes
df52d04b70 ALCACHOFA: Add various debug commands
c2abfe5b56 ALCACHOFA: Lock characters during script processes
8a7d02239b ALCACHOFA: Fix door cursors
e8539aac3f ALCACHOFA: Add character interaction and fix crash on exit
e116042bc7 ALCACHOFA: Execute object and character scripts on triggering
21812391c8 ALCACHOFA: Add triggering of doors
ae53e521cd ALCACHOFA: Toggle related objects of interactable objects
e28435d1a8 ALCACHOFA: Let camera catch up on room changes
ae9c2d0d21 ALCACHOFA: Add text drawing
72b4c99bf3 ALCACHOFA: Load and display localized names
6938f1c87b ALCACHOFA: Load dialog lines
b4a467c892 ALCACHOFA: Add voice sounds and sayText kernel task
fff9c7cc09 ALCACHOFA: Refactor stream-helper into common
114d88a87e ALCACHOFA: Add dialog menu kernel task
e430e6d46c ALCACHOFA: Fix text blending mode
75a8d917aa ALCACHOFA: Add camera lerp kernel tasks
742c584885 ALCACHOFA: Add LerpCameraToObject kernel tasks
550f23ab4e ALCACHOFA: Reorder and group kernel tasks
97e9bc81b4 ALCACHOFA: Add fades for doors
206c749922 ALCACHOFA: Add inventory
509ebca2ee ALCACHOFA: Add three more kernel tasks
7f23983242 ALCACHOFA: Fix closing game during character processes
f18ced5d41 ALCACHOFA: Add PlayVideo kernel call
8ad4f2e58b ALCACHOFA: Add permanent fade state
30419251dc ALCACHOFA: Ignore additional missing animation
37aabca132 ALCACHOFA: Fix incompatibilities after rebase
d9450568a0 ALCACHOFA: Add ClosestFloorPoint debug mode
43c17c33d0 ALCACHOFA: Improve Polygon::closestPointTo
c52ad25703 ALCACHOFA: Add FloorIntersections debug handler
fc39a1344c ALCACHOFA: Fix floor polygon connections
013ea9e5d8 ALCACHOFA: Decrease texture size of fonts
af87f81354 ALCACHOFA: Add script debug tracing
84bcbbea4e ALCACHOFA: Disable alpha premultiplication, seems to be wrong
356122bb8e ALCACHOFA: Fix object query for some cutscenes
e157840815 ALCACHOFA: Fix loading OFELIA_QUIETA.AN0
c49f2cbfd6 ALCACHOFA: Add two original hard-coded special cases
5fdd0de637 ALCACHOFA: Fix loading room VIA_TREN_ATADOS_NOCHE
6c551fc9ca ALCACHOFA: Add teleport debug handler
01233d57cf ALCACHOFA: Add AnimateTalking kernel call
75201ef028 ALCACHOFA: Add AnimateCharacter kernel call
2bd64cd558 ALCACHOFA: Add PlaySound kernel call
ae5c98228e ALCACHOFA: Add LerpCharacterLodBias kernel call
313a811a40 ALCACHOFA: Add ChangeCharacter kernel call
14d6797687 ALCACHOFA: Refactor fonts into new GlobalUI component
e3625bf330 ALCACHOFA: Refactor inventory UI triggers into GlobalUI
17c4a38686 ALCACHOFA: Add character change button
c97572c371 ALCACHOFA: Speed up room transitions by buffering images
25e4aebd79 ALCACHOFA: Disable OpenGL debugging by default
381872dfda ALCACHOFA: Fix changing character during dialog
3cb5cebc18 ALCACHOFA: Fix Filemon not being rendered
cabcd49607 ALCACHOFA: Fix Drop kernel calls with nullptr item
9e2d1cfbc1 ALCACHOFA: Fix a couple corner cases
b5c5cfaef9 ALCACHOFA: Simplify and fix Player::changeRoom
ce65cf96c2 ALCACHOFA: Workaround bug with invalid PUT target objecgt
71988c9edf ALCACHOFA: Workaround bugs in CASA_FREDDY_ARRIBA
7b08ff53d7 ALCACHOFA: Workaround bugs in HABITACION_DRACULA and MOTEL_ENTRADA
a2266bd22c ALCACHOFA: Fix InteractableObject being door target
eced79308f ALCACHOFA: Use the helper functions more in the kernel procs
72391731e5 ALCACHOFA: Fix entering ESTOMAGO and DINOSAURIO
8e7069d366 ALCACHOFA: Fix return value of kernel calls
171e0af2af ALCACHOFA: Fix ScriptTimerTask
f8e15ea9f4 ALCACHOFA: Clear screen in order to fix ESTOMAGO
9bf99c3f6a ALCACHOFA: Fix text line array being too small
178faf9b47 ALCACHOFA: More exceptions to make game completable
23f316821e ALCACHOFA: Fix out-of-bounds heap access
e9c1594ea2 ALCACHOFA: Fix case of input.cpp and input.h
e2c661a407 ALCACHOFA: Fix texture wrapping
6644e4abde ALCACHOFA: Fix tiling on effect objects
6a7bd63da8 ALCACHOFA: Fix parameter name "center" to "topLeft"
d598530b45 ALCACHOFA: Fix black frames on opening/closing inventory
7e7163768f ALCACHOFA: Update 3D mouse pos always
d72e14505c ALCACHOFA: Fix exception for BACTERIO/PULSAR
bbfcb8f7f5 ALCACHOFA: Fix lens flares outside the western town
d1c56de460 ALCACHOFA: Add main character evasion
cd0dcde22c ALCACHOFA: Add ShowCenterBottomText kernel call
24453e1d01 ALCACHOFA: Reduce unnecessary string allocations
c107f98062 ALCACHOFA: Code conventions - Fix indentations
ea85359693 ALCACHOFA: Code conventions - Pass Point by value
54e1201f92 ALCACHOFA: Fix taking and combining inventory items
7bcb573207 ALCACHOFA: Fix blending modes
22bace3365 ALCACHOFA: Fix invalid door target in LABERINTO
a905192ce6 ALCACHOFA: Fix draw order of hovered object names
ffedb0376c ALCACHOFA: Fix invisible cursor in DINOSAURIO
26a1af2f93 ALCACHOFA: Fix crash with zero-size sound files
a80d819951 ALCACHOFA: Fix hieroglyphic display in 16
94dc9afec8 ALCACHOFA: Fix TELEFRUSKYMATIC in LAB_BACTERIO
181bede30e ALCACHOFA: Fix holding items after pickup
904577adb3 ALCACHOFA: Fix cursor directions for doors
d131608699 ALCACHOFA: Fix camera transitions on character change/inventory close
8f2345f2d9 ALCACHOFA: Fix some camera moves when scaled
0764c495ca ALCACHOFA: Refactor animation-related methods
1fca043e6b ALCACHOFA: Add letter-boxes
e1a0e6a159 ALCACHOFA: Fix floor shapes with lines
5c578daf71 ALCACHOFA: Fix TGADecoder changes after rebase
d2c05d444a ALCACHOFA: Add camera tasks for inactive player
e014523711 ALCACHOFA: Add kernel task CamShake
df191096d2 ALCACHOFA: Clear heldItem on clearInventory
2259614542 ALCACHOFA: Refactor error handling
4aea1d5bf9 ALCACHOFA: Replace TODO comment in WalkingCharacter::draw
dbfa3a5600 ALCACHOFA: Refactor room reading to assert object sizes
8f17fceba2 ALCACHOFA: Add lip-sync
0f167489ae ALCACHOFA: Fix character direction after triggering doors
0f8d9f70ba ALCACHOFA: Add FloorColorDebugHandler
536eb12185 ALCACHOFA: Fix reading floor brightness
39d0d54da0 ALCACHOFA: Increase read buffer for less latency on playing voice lines
2c48107288 ALCACHOFA: Add character lighting
1322ef52ca ALCACHOFA: Add music
4ad1f1f806 ALCACHOFA: Add keymap and input handling to open menu
446ca8765b ALCACHOFA: Open main menu and add MenuButtton
65a7d31541 ALCACHOFA: Add first main menu actions
ee1808a5d1 ALCACHOFA: Add Config class and GUI options
74512630d6 ALCACHOFA: Add CheckBox and initial options menu
563bc6e7cf ALCACHOFA: Implement options menu actions
5eaa3e56c6 ALCACHOFA: Fix options menu arm
cfa7f70811 ALCACHOFA: Add slide buttons
7165397f70 ALCACHOFA: Replace SoundID with SoundHandle
c9d1bd1725 ALCACHOFA: Fix pausing on game menu and ScummVM menus
7e819190f4 ALCACHOFA: Add most of syncGame subroutines
3fb9469d2a ALCACHOFA: Add syncGame for scheduler and all tasks
700eee9de4 ALCACHOFA: Remove virtual on overridden methods
e31e933cb3 ALCACHOFA: Fix various bugs related to saving/loading
6a49931fed ALCACHOFA: Implement canLoadGameStateCurrently
1650b7610c ALCACHOFA: Add loading from in-game menu
59303318d7 ALCACHOFA: Fix assert when loading while walking through door
3143836091 ALCACHOFA: Use MessageDialog for multiplayer warning
a8d936fcd2 ALCACHOFA: Add saving with in-game menu
c58bd40362 ALCACHOFA: Remove getRandomNumber from template
6458f3e871 ALCACHOFA: Replace std::move with Common::move
a73b70adde ALCACHOFA: Add showGraphics debug flag
3d3dda8d63 ALCACHOFA: Render-to-texture and initial savestate thumbnails
4495dc5d90 ALCACHOFA: Decrease savestate size
bb50a4b394 ALCACHOFA: Fix fadeExit
af7cc2c587 ALCACHOFA: Add high-quality check
2778159830 ALCACHOFA: Add key inputs for opening/closing inventory
f4bbb6551a ALCACHOFA: Mark typeName methods as override
1ec9365bd2 ALCACHOFA: Fix compilation warnings and CI errors
a22af48933 ALCACHOFA: Fix additional CI errors
6cd7965a9d ALCACHOFA: Fix even more CI warnings
344d8c9944 ALCACHOFA: Remove redundant semicolon after DECLARE_TASK
83c49b1a95 ALCACHOFA: Fix two CI warnings
8a2c772bcc ALCACHOFA: Remove remaining virtual keyword on overridden methods
395f1d9111 ALCACHOFA: Fix MSVC Analysis warnings
42fe7869d8 ALCACHOFA: Fix configure.engine
4a1b0a6473 ALCACHOFA: Fix black thumbnail after saving in-game
04240a15b7 ALCACHOFA: Fix filemon being able to leave POBALDO_INDIO
330d81a940 ALCACHOFA: Fix some objects being enabled after load
1adac7a86f ALCACHOFA: Add spanish and english steam versions
7cfe04ad78 ALCACHOFA: Handle more dialog line formats
268a6538bb ALCACHOFA: Fix draw order of benter bottom text
4fa0c5a590 ALCACHOFA: Handle video playback errors more gracefully
7275e191a9 ALCACHOFA: Fix loading empty dialog lines
e5400d1190 ALCACHOFA: Add support for german demo
45514217d9 ALCACHOFA: Rename header guards
d4fbf73b72 ALCACHOFA: Remove redundant semicolons
59960db114 ALCACHOFA: Fix crash in fadeExit with gcc builds
bdc4cd1d38 ALCACHOFA: Handle missing voice/music files better
d2e767e75b ALCACHOFA: Probably fix two compiler warnings
603264e965 ALCACHOFA: Use engine name in include paths
820ba7894b ALCACHOFA: Remove scummsys include
85897a520d ALCACHOFA: Fix bracing style
9f774019ae ALCACHOFA: Break up one-liners in switches
e5e42cc7b1 ALCACHOFA: Remove u8 string prefix
b3368f5742 ALCACHOFA: Rename gameid to aventuradecine
999a5b0167 ALCACHOFA: Add ADGF_REMASTERED in preparation for edicion original
07a39679dd ALCACHOFA: Remove temporary saveFileMgr variable
ac843c9fd0 ALCACHOFA: Remove redundant pragma once
917ceb5115 ALCACHOFA: Replace _DEBUG macro usage
32c4ebc08a ALCACHOFA: Fix end-of-file comment
92d0688429 ALCACHOFA: Use luminance for grayscaled thumbnails
17175d0de9 ALCACHOFA: Fix regression error when loading a DelayTask
e45cb342be ALCACHOFA: Remove feature 3d
eb8499e902 ALCACHOFA: Split up OpenGL renderer
4cd127c9ff ALCACHOFA: Add OpenGL shaders support for GLES2 platforms
007741ef41 ALCACHOFA: Fix invalid ODR-usage
Commit: 2c9c7a16a7813acecdebc63dcf74d9784f5f7cb7
https://github.com/scummvm/scummvm/commit/2c9c7a16a7813acecdebc63dcf74d9784f5f7cb7
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add initial engine template
Changed paths:
A engines/alcachofa/POTFILES
A engines/alcachofa/alcachofa.cpp
A engines/alcachofa/alcachofa.h
A engines/alcachofa/configure.engine
A engines/alcachofa/console.cpp
A engines/alcachofa/console.h
A engines/alcachofa/credits.pl
A engines/alcachofa/detection.cpp
A engines/alcachofa/detection.h
A engines/alcachofa/detection_tables.h
A engines/alcachofa/metaengine.cpp
A engines/alcachofa/metaengine.h
A engines/alcachofa/module.mk
diff --git a/engines/alcachofa/POTFILES b/engines/alcachofa/POTFILES
new file mode 100644
index 00000000000..271127f50bf
--- /dev/null
+++ b/engines/alcachofa/POTFILES
@@ -0,0 +1 @@
+engines/alcachofa/metaengine.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
new file mode 100644
index 00000000000..f8bc232747e
--- /dev/null
+++ b/engines/alcachofa/alcachofa.cpp
@@ -0,0 +1,109 @@
+/* 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 "graphics/framelimiter.h"
+#include "alcachofa/detection.h"
+#include "alcachofa/console.h"
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/paletteman.h"
+
+namespace Alcachofa {
+
+AlcachofaEngine *g_engine;
+
+AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
+ _gameDescription(gameDesc), _randomSource("Alcachofa") {
+ g_engine = this;
+}
+
+AlcachofaEngine::~AlcachofaEngine() {
+ delete _screen;
+}
+
+uint32 AlcachofaEngine::getFeatures() const {
+ return _gameDescription->flags;
+}
+
+Common::String AlcachofaEngine::getGameId() const {
+ return _gameDescription->gameId;
+}
+
+Common::Error AlcachofaEngine::run() {
+ // Initialize 320x200 paletted graphics mode
+ initGraphics(320, 200);
+ _screen = new Graphics::Screen();
+
+ // Set the engine's debugger console
+ setDebugger(new Console());
+
+ // If a savegame was selected from the launcher, load it
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot != -1)
+ (void)loadGameState(saveSlot);
+
+ // Draw a series of boxes on screen as a sample
+ for (int i = 0; i < 100; ++i)
+ _screen->frameRect(Common::Rect(i, i, 320 - i, 200 - i), i);
+ _screen->update();
+
+ // Simple event handling loop
+ byte pal[256 * 3] = { 0 };
+ Common::Event e;
+ int offset = 0;
+
+ Graphics::FrameLimiter limiter(g_system, 60);
+ while (!shouldQuit()) {
+ while (g_system->getEventManager()->pollEvent(e)) {
+ }
+
+ // Cycle through a simple palette
+ ++offset;
+ for (int i = 0; i < 256; ++i)
+ pal[i * 3 + 1] = (i + offset) % 256;
+ g_system->getPaletteManager()->setPalette(pal, 0, 256);
+ // Delay for a bit. All events loops should have a delay
+ // to prevent the system being unduly loaded
+ limiter.delayBeforeSwap();
+ _screen->update();
+ limiter.startFrame();
+ }
+
+ return Common::kNoError;
+}
+
+Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
+ // The Serializer has methods isLoading() and isSaving()
+ // if you need to specific steps; for example setting
+ // an array size after reading it's length, whereas
+ // for saving it would write the existing array's length
+ int dummy = 0;
+ s.syncAsUint32LE(dummy);
+
+ return Common::kNoError;
+}
+
+} // End of namespace Alcachofa
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
new file mode 100644
index 00000000000..ae6eb72cc92
--- /dev/null
+++ b/engines/alcachofa/alcachofa.h
@@ -0,0 +1,105 @@
+/* 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/>.
+ *
+ */
+
+#ifndef ALCACHOFA_H
+#define ALCACHOFA_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "common/random.h"
+#include "common/serializer.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "engines/savestate.h"
+#include "graphics/screen.h"
+
+#include "alcachofa/detection.h"
+
+namespace Alcachofa {
+
+struct AlcachofaGameDescription;
+
+class AlcachofaEngine : public Engine {
+private:
+ const ADGameDescription *_gameDescription;
+ Common::RandomSource _randomSource;
+protected:
+ // Engine APIs
+ Common::Error run() override;
+public:
+ Graphics::Screen *_screen = nullptr;
+public:
+ AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc);
+ ~AlcachofaEngine() override;
+
+ uint32 getFeatures() const;
+
+ /**
+ * Returns the game Id
+ */
+ Common::String getGameId() const;
+
+ /**
+ * Gets a random number
+ */
+ uint32 getRandomNumber(uint maxNum) {
+ return _randomSource.getRandomNumber(maxNum);
+ }
+
+ bool hasFeature(EngineFeature f) const override {
+ return
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime) ||
+ (f == kSupportsReturnToLauncher);
+ };
+
+ bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
+ return true;
+ }
+ bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
+ return true;
+ }
+
+ /**
+ * Uses a serializer to allow implementing savegame
+ * loading and saving using a single method
+ */
+ Common::Error syncGame(Common::Serializer &s);
+
+ Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
+ Common::Serializer s(nullptr, stream);
+ return syncGame(s);
+ }
+ Common::Error loadGameStream(Common::SeekableReadStream *stream) override {
+ Common::Serializer s(stream, nullptr);
+ return syncGame(s);
+ }
+};
+
+extern AlcachofaEngine *g_engine;
+#define SHOULD_QUIT ::Alcachofa::g_engine->shouldQuit()
+
+} // End of namespace Alcachofa
+
+#endif // ALCACHOFA_H
diff --git a/engines/alcachofa/configure.engine b/engines/alcachofa/configure.engine
new file mode 100644
index 00000000000..a5decb9ba59
--- /dev/null
+++ b/engines/alcachofa/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine alcachofa "Alcachofa" no "" "" ""
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
new file mode 100644
index 00000000000..a64a7fd216f
--- /dev/null
+++ b/engines/alcachofa/console.cpp
@@ -0,0 +1,38 @@
+/* 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/console.h"
+
+namespace Alcachofa {
+
+Console::Console() : GUI::Debugger() {
+ registerCmd("test", WRAP_METHOD(Console, Cmd_test));
+}
+
+Console::~Console() {
+}
+
+bool Console::Cmd_test(int argc, const char **argv) {
+ debugPrintf("Test\n");
+ return true;
+}
+
+} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
new file mode 100644
index 00000000000..f8b2028fefc
--- /dev/null
+++ b/engines/alcachofa/console.h
@@ -0,0 +1,40 @@
+
+/* 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/>.
+ *
+ */
+
+#ifndef ALCACHOFA_CONSOLE_H
+#define ALCACHOFA_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace Alcachofa {
+
+class Console : public GUI::Debugger {
+private:
+ bool Cmd_test(int argc, const char **argv);
+public:
+ Console();
+ ~Console() override;
+};
+
+} // End of namespace Alcachofa
+
+#endif // ALCACHOFA_CONSOLE_H
diff --git a/engines/alcachofa/credits.pl b/engines/alcachofa/credits.pl
new file mode 100644
index 00000000000..53df699a73d
--- /dev/null
+++ b/engines/alcachofa/credits.pl
@@ -0,0 +1,3 @@
+begin_section("Alcachofa");
+ add_person("Hermann Noll", "Helco", "");
+end_section();
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
new file mode 100644
index 00000000000..54d3b03454d
--- /dev/null
+++ b/engines/alcachofa/detection.cpp
@@ -0,0 +1,45 @@
+/* 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 "base/plugins.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "common/str-array.h"
+#include "common/translation.h"
+#include "common/util.h"
+#include "alcachofa/detection.h"
+#include "alcachofa/detection_tables.h"
+
+const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
+ { Alcachofa::kDebugGraphics, "Graphics", "Graphics debug level" },
+ { Alcachofa::kDebugPath, "Path", "Pathfinding debug level" },
+ { Alcachofa::kDebugFilePath, "FilePath", "File path debug level" },
+ { Alcachofa::kDebugScan, "Scan", "Scan for unrecognised games" },
+ { Alcachofa::kDebugScript, "Script", "Enable debug script dump" },
+ DEBUG_CHANNEL_END
+};
+
+AlcachofaMetaEngineDetection::AlcachofaMetaEngineDetection() : AdvancedMetaEngineDetection(Alcachofa::gameDescriptions,
+ sizeof(ADGameDescription), Alcachofa::alcachofaGames) {
+}
+
+REGISTER_PLUGIN_STATIC(ALCACHOFA_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, AlcachofaMetaEngineDetection);
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
new file mode 100644
index 00000000000..4d1c45fd311
--- /dev/null
+++ b/engines/alcachofa/detection.h
@@ -0,0 +1,69 @@
+/* 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/>.
+ *
+ */
+
+#ifndef ALCACHOFA_DETECTION_H
+#define ALCACHOFA_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace Alcachofa {
+
+enum AlcachofaDebugChannels {
+ kDebugGraphics = 1,
+ kDebugPath,
+ kDebugScan,
+ kDebugFilePath,
+ kDebugScript,
+};
+
+extern const PlainGameDescriptor alcachofaGames[];
+
+extern const ADGameDescription gameDescriptions[];
+
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+
+} // End of namespace Alcachofa
+
+class AlcachofaMetaEngineDetection : public AdvancedMetaEngineDetection {
+ static const DebugChannelDef debugFlagList[];
+
+public:
+ AlcachofaMetaEngineDetection();
+ ~AlcachofaMetaEngineDetection() override {}
+
+ const char *getName() const override {
+ return "alcachofa";
+ }
+
+ const char *getEngineName() const override {
+ return "Alcachofa";
+ }
+
+ const char *getOriginalCopyright() const override {
+ return "Alcachofa (C)";
+ }
+
+ const DebugChannelDef *getDebugChannels() const override {
+ return debugFlagList;
+ }
+};
+
+#endif // ALCACHOFA_DETECTION_H
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
new file mode 100644
index 00000000000..7b725a732cb
--- /dev/null
+++ b/engines/alcachofa/detection_tables.h
@@ -0,0 +1,43 @@
+/* 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/>.
+ *
+ */
+
+namespace Alcachofa {
+
+const PlainGameDescriptor alcachofaGames[] = {
+ { "alcachofa", "Alcachofa" },
+ { 0, 0 }
+};
+
+const ADGameDescription gameDescriptions[] = {
+ {
+ "alcachofa",
+ nullptr,
+ AD_ENTRY1s("file1.bin", "00000000000000000000000000000000", 11111),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO1(GUIO_NONE)
+ },
+
+ AD_TABLE_END_MARKER
+};
+
+} // End of namespace Alcachofa
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
new file mode 100644
index 00000000000..b00bbba2d43
--- /dev/null
+++ b/engines/alcachofa/metaengine.cpp
@@ -0,0 +1,69 @@
+/* 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 "common/translation.h"
+
+#include "alcachofa/metaengine.h"
+#include "alcachofa/detection.h"
+#include "alcachofa/alcachofa.h"
+
+namespace Alcachofa {
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ {
+ GAMEOPTION_ORIGINAL_SAVELOAD,
+ {
+ _s("Use original save/load screens"),
+ _s("Use the original save/load screens instead of the ScummVM ones"),
+ "original_menus",
+ false,
+ 0,
+ 0
+ }
+ },
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+} // End of namespace Alcachofa
+
+const char *AlcachofaMetaEngine::getName() const {
+ return "alcachofa";
+}
+
+const ADExtraGuiOptionsMap *AlcachofaMetaEngine::getAdvancedExtraGuiOptions() const {
+ return Alcachofa::optionsList;
+}
+
+Common::Error AlcachofaMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ *engine = new Alcachofa::AlcachofaEngine(syst, desc);
+ return Common::kNoError;
+}
+
+bool AlcachofaMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return checkExtendedSaves(f) ||
+ (f == kSupportsLoadingDuringStartup);
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(ALCACHOFA)
+REGISTER_PLUGIN_DYNAMIC(ALCACHOFA, PLUGIN_TYPE_ENGINE, AlcachofaMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(ALCACHOFA, PLUGIN_TYPE_ENGINE, AlcachofaMetaEngine);
+#endif
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
new file mode 100644
index 00000000000..e31ae83271a
--- /dev/null
+++ b/engines/alcachofa/metaengine.h
@@ -0,0 +1,43 @@
+/* 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/>.
+ *
+ */
+
+#ifndef ALCACHOFA_METAENGINE_H
+#define ALCACHOFA_METAENGINE_H
+
+#include "engines/advancedDetector.h"
+
+class AlcachofaMetaEngine : public AdvancedMetaEngine {
+public:
+ const char *getName() const override;
+
+ Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
+
+ /**
+ * Determine whether the engine supports the specified MetaEngine feature.
+ *
+ * Used by e.g. the launcher to determine whether to enable the Load button.
+ */
+ bool hasFeature(MetaEngineFeature f) const override;
+
+ const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+};
+
+#endif // ALCACHOFA_METAENGINE_H
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
new file mode 100644
index 00000000000..f32728feb12
--- /dev/null
+++ b/engines/alcachofa/module.mk
@@ -0,0 +1,17 @@
+MODULE := engines/alcachofa
+
+MODULE_OBJS = \
+ alcachofa.o \
+ console.o \
+ metaengine.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_ALCACHOFA), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o
Commit: b0fda8ff19860c526a0cd39f07feb97e604d6323
https://github.com/scummvm/scummvm/commit/b0fda8ff19860c526a0cd39f07feb97e604d6323
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add first detection entry
Changed paths:
engines/alcachofa/detection.cpp
engines/alcachofa/detection.h
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index 54d3b03454d..f743c0465da 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -40,6 +40,7 @@ const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
AlcachofaMetaEngineDetection::AlcachofaMetaEngineDetection() : AdvancedMetaEngineDetection(Alcachofa::gameDescriptions,
sizeof(ADGameDescription), Alcachofa::alcachofaGames) {
+ _flags |= kADFlagMatchFullPaths;
}
REGISTER_PLUGIN_STATIC(ALCACHOFA_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, AlcachofaMetaEngineDetection);
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 4d1c45fd311..62298ab9b14 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -58,7 +58,7 @@ public:
}
const char *getOriginalCopyright() const override {
- return "Alcachofa (C)";
+ return "Alcachofa Soft (C)";
}
const DebugChannelDef *getDebugChannels() const override {
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 7b725a732cb..49e2ad33df4 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -22,17 +22,17 @@
namespace Alcachofa {
const PlainGameDescriptor alcachofaGames[] = {
- { "alcachofa", "Alcachofa" },
+ { "mort_phil_adventura_de_cine", "Mort&Phil: A movie adventure" },
{ 0, 0 }
};
const ADGameDescription gameDescriptions[] = {
{
- "alcachofa",
+ "mort_phil_adventura_de_cine",
nullptr,
- AD_ENTRY1s("file1.bin", "00000000000000000000000000000000", 11111),
- Common::EN_ANY,
- Common::kPlatformDOS,
+ AD_ENTRY1s("Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO1(GUIO_NONE)
},
Commit: b2ab2453aef543390193e2c522c9520350f47828
https://github.com/scummvm/scummvm/commit/b2ab2453aef543390193e2c522c9520350f47828
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Read world data
Changed paths:
A engines/alcachofa/game-objects.cpp
A engines/alcachofa/general-objects.cpp
A engines/alcachofa/graphics.cpp
A engines/alcachofa/graphics.h
A engines/alcachofa/objects.h
A engines/alcachofa/rooms.cpp
A engines/alcachofa/rooms.h
A engines/alcachofa/shape.cpp
A engines/alcachofa/shape.h
A engines/alcachofa/stream-helper.cpp
A engines/alcachofa/stream-helper.h
A engines/alcachofa/ui-objects.cpp
engines/alcachofa/alcachofa.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index f8bc232747e..813f2a4c30a 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -31,6 +31,8 @@
#include "engines/util.h"
#include "graphics/paletteman.h"
+#include "rooms.h"
+
namespace Alcachofa {
AlcachofaEngine *g_engine;
@@ -57,6 +59,9 @@ Common::Error AlcachofaEngine::run() {
initGraphics(320, 200);
_screen = new Graphics::Screen();
+ auto world = new World();
+ delete world;
+
// Set the engine's debugger console
setDebugger(new Console());
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
new file mode 100644
index 00000000000..0aacd2510dc
--- /dev/null
+++ b/engines/alcachofa/game-objects.cpp
@@ -0,0 +1,171 @@
+/* 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 "objects.h"
+#include "rooms.h"
+#include "stream-helper.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+Item::Item(Room *room, ReadStream &stream)
+ : GraphicObject(room, stream) {
+ stream.readByte(); // unused and ignored byte
+}
+
+InteractableObject::InteractableObject(Room *room, ReadStream &stream)
+ : PhysicalObject(room, stream)
+ , _interactionPoint(Shape(stream).firstPoint())
+ , _cursorType((CursorType)stream.readSint32LE())
+ , _relatedObject(readVarString(stream)) {
+ _relatedObject.toUppercase();
+}
+
+Door::Door(Room *room, ReadStream &stream)
+ : InteractableObject(room, stream)
+ , _targetRoom(readVarString(stream))
+ , _targetObject(readVarString(stream))
+ , _characterDirection((Direction)stream.readSint32LE()) {
+ _targetRoom.replace(' ', '_');
+}
+
+Character::Character(Room *room, ReadStream &stream)
+ : ShapeObject(room, stream)
+ , _interactionPoint(Shape(stream).firstPoint())
+ , _direction((Direction)stream.readSint32LE())
+ , _graphicNormal(stream)
+ , _graphicTalking(stream) {
+ _graphicNormal.start(true);
+ _order = _graphicNormal.order();
+}
+
+void Character::serializeSave(Serializer &serializer) {
+ ShapeObject::serializeSave(serializer);
+ serializer.syncAsByte(_isTalking);
+ serializer.syncAsSint32LE(_curDialogId);
+ _graphicNormal.serializeSave(serializer);
+ _graphicTalking.serializeSave(serializer);
+ syncObjectAsString(serializer, _curAnimateObject);
+ syncObjectAsString(serializer, _curTalkingObject);
+ serializer.syncAsFloatLE(_lodBias);
+}
+
+void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object) {
+ String name;
+ if (serializer.isSaving() && object != nullptr)
+ name = object->name();
+
+ serializer.syncString(name);
+
+ if (serializer.isLoading()) {
+ if (name.empty())
+ object = nullptr;
+ else {
+ object = room()->getObjectByName(name);
+ if (object == nullptr)
+ object = room()->world()->getObjectByName(name);
+ if (object == nullptr)
+ error("Invalid object name \"%s\" saved for \"%s\" in \"%s\"",
+ name.c_str(), this->name().c_str(), room()->name().c_str());
+ }
+ }
+}
+
+WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
+ : Character(room, stream) {
+ for (int32 i = 0; i < kDirectionCount; i++) {
+ auto fileName = readVarString(stream);
+ _walkingAnimations[i].reset(new Animation(Common::move(fileName)));
+ }
+ for (int32 i = 0; i < kDirectionCount; i++) {
+ auto fileName = readVarString(stream);
+ _standingAnimations[i].reset(new Animation(Common::move(fileName)));
+ }
+}
+
+void WalkingCharacter::serializeSave(Serializer &serializer) {
+ Character::serializeSave(serializer);
+ serializer.syncAsSint32LE(_lastWalkAnimFrame);
+ serializer.syncAsSint32LE(_walkSpeed);
+ syncPoint(serializer, _sourcePos);
+ syncPoint(serializer, _targetPos);
+ serializer.syncAsByte(_isWalking);
+ syncArray(serializer, _pathPoints, syncPoint);
+ syncEnum(serializer, _direction);
+ _graphicWalking.serializeSave(serializer);
+}
+
+MainCharacter::MainCharacter(Room *room, ReadStream &stream)
+ : WalkingCharacter(room, stream) {
+ stream.readByte(); // unused byte
+ _order = 100;
+
+ _kind =
+ name().equalsIgnoreCase("MORTADELO") ? MainCharacterKind::Mortadelo
+ : name().equalsIgnoreCase("FILEMON") ? MainCharacterKind::Filemon
+ : MainCharacterKind::None;
+}
+
+MainCharacter::~MainCharacter() {
+ for (auto *item : _items)
+ delete item;
+}
+
+void syncDialogMenuLine(Serializer &serializer, DialogMenuLine &line) {
+ serializer.syncAsSint32LE(line._dialogId);
+ serializer.syncAsSint32LE(line._yPosition);
+ serializer.syncAsSint32LE(line._returnId);
+}
+
+void MainCharacter::serializeSave(Serializer &serializer) {
+ String roomName = room()->name();
+ serializer.syncString(roomName);
+ if (serializer.isLoading()) {
+ room() = room()->world()->getRoomByName(roomName);
+ if (room() == nullptr)
+ error("Invalid room name \"%s\" saved for \"%s\"", roomName.c_str(), name().c_str());
+ }
+
+ Character::serializeSave(serializer);
+ serializer.syncAsSint32LE(_relatedProcessCounter);
+ syncArray(serializer, _dialogMenuLines, syncDialogMenuLine);
+ syncObjectAsString(serializer, _currentlyUsingObject);
+
+ for (auto *item : _items) {
+ bool isEnabled = item->isEnabled();
+ serializer.syncAsByte(isEnabled);
+ item->toggle(isEnabled);
+ }
+}
+
+Background::Background(Room *room, const String &animationFileName, int16 scale)
+ : GraphicObject(room, "BACKGROUND") {
+ _graphic._animation.reset(new Animation(animationFileName, AnimationFolder::Fondos));
+ _graphic._scale = scale;
+ _graphic._order = 59;
+}
+
+FloorColor::FloorColor(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream)
+ , _shape(stream) {}
+
+}
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
new file mode 100644
index 00000000000..bc8a965c66c
--- /dev/null
+++ b/engines/alcachofa/general-objects.cpp
@@ -0,0 +1,134 @@
+/* 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 "objects.h"
+#include "rooms.h"
+#include "stream-helper.h"
+
+#include "common/system.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+ObjectBase::ObjectBase(Room *room, const char *name)
+ : _room(room)
+ , _name(name)
+ , _isEnabled(false) {
+ assert(room != nullptr);
+}
+
+ObjectBase::ObjectBase(Room *room, ReadStream &stream)
+ : _room(room) {
+ assert(room != nullptr);
+ _name = readVarString(stream);
+ _isEnabled = readBool(stream);
+}
+
+void ObjectBase::toggle(bool isEnabled) {
+ _isEnabled = isEnabled;
+}
+
+void ObjectBase::render() {
+}
+
+void ObjectBase::update() {
+}
+
+void ObjectBase::loadResources() {
+}
+
+void ObjectBase::freeResources() {
+}
+
+void ObjectBase::serializeSave(Serializer &serializer) {
+ serializer.syncAsByte(_isEnabled);
+}
+
+Graphic *ObjectBase::graphic() {
+ return nullptr;
+}
+
+Shape *ObjectBase::shape() {
+ return nullptr;
+}
+
+PointObject::PointObject(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream) {
+ _pos = Shape(stream).firstPoint();
+}
+
+GraphicObject::GraphicObject(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream)
+ , _graphic(stream)
+ , _type((GraphicObjectType)stream.readSint32LE())
+ , _posterizeAlpha(100 - stream.readSint32LE()) {
+ _graphic.start(true);
+}
+
+GraphicObject::GraphicObject(Room *room, const char *name)
+ : ObjectBase(room, name)
+ , _type(GraphicObjectType::Type0)
+ , _posterizeAlpha(0) {
+}
+
+void GraphicObject::serializeSave(Serializer &serializer) {
+ ObjectBase::serializeSave(serializer);
+ _graphic.serializeSave(serializer);
+}
+
+Graphic *GraphicObject::graphic() {
+ return &_graphic;
+}
+
+ShiftingGraphicObject::ShiftingGraphicObject(Room *room, ReadStream &stream)
+ : GraphicObject(room, stream) {
+ _pos = Shape(stream).firstPoint();
+ _size = Shape(stream).firstPoint();
+ _texShift.setX(stream.readSint32LE() / 256.0f);
+ _texShift.setY(stream.readSint32LE() / 256.0f);
+ _startTime = g_system->getMillis();
+}
+
+ShapeObject::ShapeObject(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream)
+ , _shape(stream)
+ , _cursorType((CursorType)stream.readSint32LE()) {
+}
+
+void ShapeObject::serializeSave(Serializer &serializer) {
+ serializer.syncAsSByte(_order);
+}
+
+Shape *ShapeObject::shape() {
+ return &_shape;
+}
+
+CursorType ShapeObject::cursorType() const {
+ return _cursorType;
+}
+
+PhysicalObject::PhysicalObject(Room *room, ReadStream &stream)
+ : ShapeObject(room, stream) {
+ _order = stream.readSByte();
+}
+
+}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
new file mode 100644
index 00000000000..6603ea99256
--- /dev/null
+++ b/engines/alcachofa/graphics.cpp
@@ -0,0 +1,69 @@
+/* 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 "graphics.h"
+#include "stream-helper.h"
+
+#include "common/system.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+Animation::Animation(String fileName, AnimationFolder folder)
+ : _fileName(move(fileName))
+ , _folder(folder) {
+}
+
+Graphic::Graphic() {
+}
+
+Graphic::Graphic(ReadStream &stream) {
+ _center.x = stream.readSint16LE();
+ _center.y = stream.readSint16LE();
+ _scale = stream.readSint16LE();
+ _order = stream.readSByte();
+ auto animationName = readVarString(stream);
+ _animation.reset(new Animation(std::move(animationName)));
+}
+
+void Graphic::start(bool isLooping) {
+ _isPaused = false;
+ _isLooping = isLooping;
+ _lastTime = g_system->getMillis();
+}
+
+void Graphic::stop() {
+ _isPaused = true;
+ _isLooping = false;
+ _lastTime = g_system->getMillis() - _lastTime;
+}
+
+void Graphic::serializeSave(Serializer &serializer) {
+ syncPoint(serializer, _center);
+ serializer.syncAsSint16LE(_scale);
+ serializer.syncAsUint32LE(_lastTime);
+ serializer.syncAsByte(_isPaused);
+ serializer.syncAsByte(_isLooping);
+ serializer.syncAsFloatLE(_camAcceleration);
+}
+
+}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
new file mode 100644
index 00000000000..d7e20a458fe
--- /dev/null
+++ b/engines/alcachofa/graphics.h
@@ -0,0 +1,98 @@
+/* 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/>.
+ *
+ */
+
+#ifndef GRAPHICS_H
+#define GRAPHICS_H
+
+#include "common/ptr.h"
+#include "common/stream.h"
+#include "common/serializer.h"
+#include "common/rect.h"
+
+namespace Alcachofa {
+
+enum class CursorType {
+ Normal,
+ LookAt,
+ Use,
+ GoTo,
+ LeaveUp,
+ LeaveRight,
+ LeaveDown,
+ LeaveLeft
+};
+
+enum class Direction {
+ Up,
+ Down,
+ Left,
+ Right
+};
+
+constexpr const int32 kDirectionCount = 4;
+
+enum class AnimationFolder {
+ Animations,
+ Maskaras,
+ Fondos
+};
+
+class Animation {
+public:
+ Animation(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations);
+
+private:
+ Common::String _fileName;
+ AnimationFolder _folder;
+};
+
+class Graphic {
+public:
+ Graphic();
+ Graphic(Common::ReadStream &stream);
+
+ inline int8 order() const { return _order; }
+
+ void start(bool looping);
+ void stop();
+ void serializeSave(Common::Serializer &serializer);
+
+public:
+ Common::SharedPtr<Animation> _animation;
+ Common::Point _center;
+ int16 _scale = 300;
+ int8 _order = 0;
+
+private:
+ bool _isPaused = true,
+ _isLooping = true;
+ uint32 _lastTime = 0;
+ float _camAcceleration = 1.0f;
+};
+
+class IGraphics {
+public:
+ virtual ~IGraphics() = default;
+};
+
+}
+
+#endif
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
new file mode 100644
index 00000000000..bf5797e71f6
--- /dev/null
+++ b/engines/alcachofa/objects.h
@@ -0,0 +1,388 @@
+/* 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/>.
+ *
+ */
+
+#ifndef OBJECTS_H
+#define OBJECTS_H
+
+#include "Shape.h"
+#include "Graphics.h"
+
+#include "common/serializer.h"
+
+namespace Alcachofa {
+
+class Room;
+
+class ObjectBase {
+public:
+ static constexpr const char *kClassName = "CObjetoBase";
+ ObjectBase(Room *room, const char *name);
+ ObjectBase(Room *room, Common::ReadStream &stream);
+ virtual ~ObjectBase() = default;
+
+ inline const Common::String &name() const { return _name; }
+ inline Room *&room() { return _room; }
+ inline Room *room() const { return _room; }
+ inline bool isEnabled() const { return _isEnabled; }
+
+ virtual void toggle(bool isEnabled);
+ virtual void render();
+ virtual void update();
+ virtual void loadResources();
+ virtual void freeResources();
+ virtual void serializeSave(Common::Serializer &serializer);
+ virtual Graphic *graphic();
+ virtual Shape *shape();
+
+private:
+ Common::String _name;
+ bool _isEnabled = true;
+ Room *_room = nullptr;
+};
+
+class PointObject : public ObjectBase {
+public:
+ static constexpr const char *kClassName = "CObjetoPunto";
+ PointObject(Room *room, Common::ReadStream &stream);
+
+ inline Common::Point &position() { return _pos; }
+ inline Common::Point position() const { return _pos; }
+
+private:
+ Common::Point _pos;
+};
+
+enum class GraphicObjectType : byte
+{
+ Type0,
+ Type1,
+ Type2
+};
+
+class GraphicObject : public ObjectBase {
+public:
+ static constexpr const char *kClassName = "CObjetoGrafico";
+ GraphicObject(Room *room, Common::ReadStream &stream);
+ virtual ~GraphicObject() override = default;
+
+ virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual Graphic *graphic() override;
+
+protected:
+ GraphicObject(Room *room, const char *name);
+
+ Graphic _graphic;
+ GraphicObjectType _type;
+ int32 _posterizeAlpha;
+};
+
+class ShiftingGraphicObject final : public GraphicObject {
+public:
+ static constexpr const char *kClassName = "CObjetoGraficoMuare";
+ ShiftingGraphicObject(Room *room, Common::ReadStream &stream);
+
+private:
+ Common::Point _pos, _size;
+ Math::Vector2d _texShift;
+ uint32 _startTime = 0;
+};
+
+class ShapeObject : public ObjectBase {
+public:
+ ShapeObject(Room *room, Common::ReadStream &stream);
+ virtual ~ShapeObject() override = default;
+
+ virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual Shape *shape() override;
+ virtual CursorType cursorType() const;
+
+private:
+ Shape _shape;
+ CursorType _cursorType;
+
+protected:
+ // original inconsistency: base class has member that is read by the sub classes
+ int8 _order = 0;
+};
+
+class PhysicalObject : public ShapeObject {
+public:
+ PhysicalObject(Room *room, Common::ReadStream &stream);
+};
+
+class MenuButton : public PhysicalObject {
+public:
+ static constexpr const char *kClassName = "CBotonMenu";
+ MenuButton(Room *room, Common::ReadStream &stream);
+ virtual ~MenuButton() override = default;
+
+ inline int32 actionId() const { return _actionId; }
+
+private:
+ int32 _actionId;
+ Graphic
+ _graphicNormal,
+ _graphicHovered,
+ _graphicClicked,
+ _graphicDisabled;
+};
+
+class InternetMenuButton final : public MenuButton {
+public:
+ static constexpr const char *kClassName = "CBotonMenuInternet";
+ InternetMenuButton(Room *room, Common::ReadStream &stream);
+};
+
+class OptionsMenuButton final : public MenuButton {
+public:
+ static constexpr const char *kClassName = "CBotonMenuOpciones";
+ OptionsMenuButton(Room *room, Common::ReadStream &stream);
+};
+
+class MainMenuButton final : public MenuButton {
+public:
+ static constexpr const char *kClassName = "CBotonMenuPrincipal";
+ MainMenuButton(Room *room, Common::ReadStream &stream);
+};
+
+class PushButton final : public PhysicalObject {
+public:
+ static constexpr const char *kClassName = "CPushButton";
+ PushButton(Room *room, Common::ReadStream &stream);
+
+private:
+ // TODO: Reverse engineer PushButton
+ bool _alwaysVisible;
+ Graphic _graphic1, _graphic2;
+ int32 _actionId;
+};
+
+class EditBox final : public PhysicalObject {
+public:
+ static constexpr const char *kClassName = "CEditBox";
+ EditBox(Room *room, Common::ReadStream &stream);
+
+private:
+ // TODO: Reverse engineer EditBox
+ int32 i1;
+ Common::Point p1;
+ Common::String _labelId;
+ bool b1;
+ int32 i3, i4, i5,
+ _fontId;
+};
+
+class CheckBox : public PhysicalObject {
+public:
+ static constexpr const char *kClassName = "CCheckBox";
+ CheckBox(Room *room, Common::ReadStream &stream);
+ virtual ~CheckBox() override = default;
+
+private:
+ // TODO: Reverse engineer CheckBox
+ bool b1;
+ Graphic
+ _graph1,
+ _graph2,
+ _graph3,
+ _graph4;
+ int32 _valueId;
+};
+
+class CheckBoxAutoAdjustNoise final : public CheckBox {
+public:
+ static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
+ CheckBoxAutoAdjustNoise(Room *room, Common::ReadStream &stream);
+};
+
+class SlideButton final : public ObjectBase {
+public:
+ static constexpr const char *kClassName = "CSlideButton";
+ SlideButton(Room *room, Common::ReadStream &stream);
+ virtual ~SlideButton() override = default;
+
+private:
+ // TODO: Reverse engineer SlideButton
+ int32 i1;
+ Common::Point p1, p2;
+ Graphic
+ _graph1,
+ _graph2,
+ _graph3;
+};
+
+class IRCWindow final : public ObjectBase {
+public:
+ static constexpr const char *kClassName = "CVentanaIRC";
+ IRCWindow(Room *room, Common::ReadStream &stream);
+
+private:
+ Common::Point _p1, _p2;
+};
+
+class MessageBox final : public ObjectBase {
+public:
+ static constexpr const char *kClassName = "CMessageBox";
+ MessageBox(Room *room, Common::ReadStream &stream);
+ virtual ~MessageBox() override = default;
+
+private:
+ // TODO: Reverse engineer MessageBox
+ Graphic
+ _graph1,
+ _graph2,
+ _graph3,
+ _graph4,
+ _graph5;
+};
+
+class VoiceMeter final : public GraphicObject {
+public:
+ static constexpr const char *kClassName = "CVuMeter";
+ VoiceMeter(Room *room, Common::ReadStream &stream);
+};
+
+class Item : public GraphicObject {
+public:
+ static constexpr const char *kClassName = "CObjetoInventario";
+ Item(Room *room, Common::ReadStream &stream);
+};
+
+class InteractableObject : public PhysicalObject {
+public:
+ static constexpr const char *kClassName = "CObjetoTipico";
+ InteractableObject(Room *room, Common::ReadStream &stream);
+ virtual ~InteractableObject() override = default;
+
+private:
+ Common::Point _interactionPoint;
+ CursorType _cursorType;
+ Common::String _relatedObject;
+};
+
+class Door final : public InteractableObject {
+public:
+ static constexpr const char *kClassName = "CPuerta";
+ Door(Room *room, Common::ReadStream &stream);
+
+private:
+ Common::String _targetRoom, _targetObject;
+ Direction _characterDirection;
+};
+
+class Character : public ShapeObject {
+public:
+ static constexpr const char *kClassName = "CPersonaje";
+ Character(Room *room, Common::ReadStream &stream);
+ virtual ~Character() override = default;
+
+ virtual void serializeSave(Common::Serializer &serializer) override;
+
+protected:
+ void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
+
+private:
+ Common::Point _interactionPoint;
+ Direction _direction;
+ Graphic _graphicNormal, _graphicTalking;
+
+ bool _isTalking = false;
+ int _curDialogId = -1;
+ float _lodBias = 0.0f;
+ ObjectBase
+ *_curAnimateObject = nullptr,
+ *_curTalkingObject = nullptr;
+};
+
+class WalkingCharacter : public Character {
+public:
+ static constexpr const char *kClassName = "CPersonajeAnda";
+ WalkingCharacter(Room *room, Common::ReadStream &stream);
+ virtual ~WalkingCharacter() override = default;
+
+ virtual void serializeSave(Common::Serializer &serializer) override;
+
+private:
+ Graphic _graphicWalking;
+ Common::SharedPtr<Animation>
+ _walkingAnimations[kDirectionCount],
+ _standingAnimations[kDirectionCount];
+
+ int32
+ _lastWalkAnimFrame = -1,
+ _walkSpeed = 0,
+ _curPathPointI = -1;
+ Common::Point
+ _sourcePos,
+ _targetPos;
+ bool _isWalking = false;
+ Direction _direction = Direction::Up;
+ Common::Array<Common::Point> _pathPoints;
+};
+
+enum class MainCharacterKind {
+ None,
+ Mortadelo,
+ Filemon
+};
+
+struct DialogMenuLine {
+ int32 _dialogId;
+ int32 _yPosition;
+ int32 _returnId;
+};
+
+class MainCharacter final : public WalkingCharacter {
+public:
+ static constexpr const char *kClassName = "CPersonajePrincipal";
+ MainCharacter(Room *room, Common::ReadStream &stream);
+ virtual ~MainCharacter() override;
+
+ inline MainCharacterKind kind() const { return _kind; }
+
+ virtual void serializeSave(Common::Serializer &serializer) override;
+
+private:
+ Common::Array<Item *> _items;
+ Common::Array<DialogMenuLine> _dialogMenuLines;
+ ObjectBase *_currentlyUsingObject = nullptr;
+ MainCharacterKind _kind;
+ int32_t _relatedProcessCounter = 0;
+};
+
+class Background final : public GraphicObject {
+public:
+ Background(Room *room, const Common::String &animationFileName, int16 scale);
+};
+
+class FloorColor final : public ObjectBase {
+public:
+ static constexpr const char *kClassName = "CSueloColor";
+ FloorColor(Room *room, Common::ReadStream &stream);
+ virtual ~FloorColor() override = default;
+
+private:
+ FloorColorShape _shape;
+};
+
+}
+
+#endif // OBJECTS_H
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
new file mode 100644
index 00000000000..0814d9c9934
--- /dev/null
+++ b/engines/alcachofa/rooms.cpp
@@ -0,0 +1,283 @@
+/* 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 "rooms.h"
+#include "stream-helper.h"
+
+#include "common/file.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+Room::Room(World *world, ReadStream &stream) : Room(world, stream, false) {
+}
+
+static ObjectBase *readRoomObject(Room *room, ReadStream &stream) {
+ const auto type = readVarString(stream);
+ if (type == ObjectBase::kClassName)
+ return new ObjectBase(room, stream);
+ else if (type == PointObject::kClassName)
+ return new PointObject(room, stream);
+ else if (type == GraphicObject::kClassName)
+ return new GraphicObject(room, stream);
+ else if (type == ShiftingGraphicObject::kClassName)
+ return new ShiftingGraphicObject(room, stream);
+ else if (type == Item::kClassName)
+ return new Item(room, stream);
+ else if (type == PhysicalObject::kClassName)
+ return new PhysicalObject(room, stream);
+ else if (type == MainMenuButton::kClassName)
+ return new MainMenuButton(room, stream);
+ else if (type == InternetMenuButton::kClassName)
+ return new InternetMenuButton(room, stream);
+ else if (type == OptionsMenuButton::kClassName)
+ return new OptionsMenuButton(room, stream);
+ else if (type == EditBox::kClassName)
+ return new EditBox(room, stream);
+ else if (type == PushButton::kClassName)
+ return new PushButton(room, stream);
+ else if (type == CheckBox::kClassName)
+ return new CheckBox(room, stream);
+ else if (type == CheckBoxAutoAdjustNoise::kClassName)
+ return new CheckBoxAutoAdjustNoise(room, stream);
+ else if (type == SlideButton::kClassName)
+ return new SlideButton(room, stream);
+ else if (type == IRCWindow::kClassName)
+ return new IRCWindow(room, stream);
+ else if (type == MessageBox::kClassName)
+ return new MessageBox(room, stream);
+ else if (type == VoiceMeter::kClassName)
+ return new VoiceMeter(room, stream);
+ else if (type == InteractableObject::kClassName)
+ return new InteractableObject(room, stream);
+ else if (type == Door::kClassName)
+ return new Door(room, stream);
+ else if (type == Character::kClassName)
+ return new Character(room, stream);
+ else if (type == WalkingCharacter::kClassName)
+ return new WalkingCharacter(room, stream);
+ else if (type == MainCharacter::kClassName)
+ return new MainCharacter(room, stream);
+ else if (type == FloorColor::kClassName)
+ return new FloorColor(room, stream);
+ else
+ error("Unknown type for room objects: %s", type.c_str());
+}
+
+Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
+ : _world(world) {
+ _name = readVarString(stream);
+ _musicId = stream.readSByte();
+ _characterAlpha = stream.readByte();
+ auto backgroundScale = stream.readSint16LE();
+ _floors[0] = PathFindingShape(stream);
+ _floors[1] = PathFindingShape(stream);
+ _cameraFollowsUponLeaving = readBool(stream);
+ PathFindingShape _(stream); // unused path finding area
+ _characterAlphaPercent = stream.readByte();
+ if (hasUselessByte)
+ stream.readByte();
+
+ uint32 objectSize = stream.readUint32LE(); // TODO: Maybe switch to seekablereadstream and assert objectSize?
+ while (objectSize > 0)
+ {
+ _objects.push_back(readRoomObject(this, stream));
+ objectSize = stream.readUint32LE();
+ }
+ _objects.push_back(new Background(this, _name, backgroundScale));
+
+ if (!_floors[0].empty())
+ _activeFloorI = 0;
+}
+
+Room::~Room() {
+ for (auto *object : _objects)
+ delete object;
+}
+
+ObjectBase *Room::getObjectByName(const Common::String &name) const {
+ for (auto *object : _objects) {
+ if (object->name().equalsIgnoreCase(name))
+ return object;
+ }
+ return nullptr;
+}
+
+void Room::loadResources() {
+ for (auto *object : _objects)
+ object->loadResources();
+}
+
+void Room::freeResources() {
+ for (auto *object : _objects)
+ object->freeResources();
+}
+
+void Room::serializeSave(Serializer &serializer) {
+ serializer.syncAsSByte(_musicId);
+ serializer.syncAsSByte(_activeFloorI);
+ for (auto *object : _objects)
+ object->serializeSave(serializer);
+}
+
+OptionsMenu::OptionsMenu(World *world, ReadStream &stream)
+ : Room(world, stream, true) {
+}
+
+ConnectMenu::ConnectMenu(World *world, ReadStream &stream)
+ : Room(world, stream, true) {
+}
+
+ListenMenu::ListenMenu(World *world, ReadStream &stream)
+ : Room(world, stream, true) {
+}
+
+Inventory::Inventory(World *world, ReadStream &stream)
+ : Room(world, stream, true) {
+}
+
+Inventory::~Inventory() {
+ for (auto *item : _items)
+ delete item;
+}
+
+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++) {
+ if (loadWorldFile(*itMapFile))
+ _loadedMapCount++;
+ }
+
+ _globalRoom = getRoomByName("GLOBAL");
+ if (_globalRoom == nullptr)
+ error("Could not find GLOBAL room");
+ _inventory = dynamic_cast<Inventory *>(getRoomByName("INVENTARIO"));
+ if (_inventory == nullptr)
+ error("Could not find INVENTARIO");
+ _filemon = dynamic_cast<MainCharacter *>(_globalRoom->getObjectByName("FILEMON"));
+ if (_filemon == nullptr)
+ error("Could not find FILEMON");
+ _mortadelo = dynamic_cast<MainCharacter *>(_globalRoom->getObjectByName("MORTADELO"));
+ if (_mortadelo == nullptr)
+ error("Could not find MORTADELO");
+}
+
+World::~World() {
+ for (auto *room : _rooms)
+ delete room;
+}
+
+MainCharacter &World::getMainCharacterByKind(MainCharacterKind kind) const {
+ switch (kind) {
+ case MainCharacterKind::Mortadelo: return *_mortadelo;
+ case MainCharacterKind::Filemon: return *_filemon;
+ default:
+ error("Invalid character kind given to getMainCharacterByKind");
+ }
+}
+
+Room *World::getRoomByName(const Common::String &name) const {
+ for (auto *room : _rooms) {
+ if (room->name().equalsIgnoreCase(name))
+ return room;
+ }
+ return nullptr;
+}
+
+ObjectBase *World::getObjectByName(const Common::String &name) const {
+ ObjectBase *result = nullptr;
+ if (result == nullptr && _currentRoom != nullptr)
+ result = _currentRoom->getObjectByName(name);
+ if (result == nullptr)
+ result = globalRoom().getObjectByName(name);
+ if (result == nullptr)
+ result = inventory().getObjectByName(name);
+ return result;
+}
+
+const Common::String &World::getGlobalAnimationName(GlobalAnimationKind kind) const {
+ int kindI = (int)kind;
+ assert(kindI >= 0 && kindI < (int)GlobalAnimationKind::Count);
+ return _globalAnimationNames[kindI];
+}
+
+static Room *readRoom(World *world, ReadStream &stream) {
+ const auto type = readVarString(stream);
+ if (type == Room::kClassName)
+ return new Room(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
+ error("Unknown type for room %s", type.c_str());
+}
+
+bool World::loadWorldFile(const char *path) {
+ Common::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
+ 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();
+ 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);
+
+ _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);
+
+ uint32 roomEnd = file.readUint32LE();
+ while (roomEnd > 0) {
+ _rooms.push_back(readRoom(this, file));
+ assert(file.pos() == roomEnd);
+ roomEnd = file.readUint32LE();
+ }
+
+ return true;
+}
+
+}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
new file mode 100644
index 00000000000..e311bc9362a
--- /dev/null
+++ b/engines/alcachofa/rooms.h
@@ -0,0 +1,141 @@
+/* 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/>.
+ *
+ */
+
+#ifndef ROOMS_H
+#define ROOMS_H
+
+#include "Objects.h"
+
+namespace Alcachofa {
+
+class World;
+
+class Room {
+public:
+ static constexpr const char *kClassName = "CHabitacion";
+ Room(World *world, Common::ReadStream &stream);
+ virtual ~Room();
+
+ inline World *world() { return _world; }
+ inline const Common::String &name() const { return _name; }
+
+ ObjectBase *getObjectByName(const Common::String &name) const;
+
+ virtual void loadResources();
+ virtual void freeResources();
+ virtual void serializeSave(Common::Serializer &serializer);
+
+protected:
+ Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
+
+ World *_world;
+ Common::String _name;
+ PathFindingShape _floors[2];
+ bool _cameraFollowsUponLeaving;
+ int8
+ _musicId,
+ _activeFloorI = -1;
+ uint8
+ _characterAlpha,
+ _characterAlphaPercent;
+
+ Common::Array<ObjectBase *> _objects;
+};
+
+class OptionsMenu final : public Room {
+public:
+ static constexpr const char *kClassName = "CHabitacionMenuOpciones";
+ OptionsMenu(World *world, Common::ReadStream &stream);
+};
+
+class ConnectMenu final : public Room {
+public:
+ static constexpr const char *kClassName = "CHabitacionConectar";
+ ConnectMenu(World *world, Common::ReadStream &stream);
+};
+
+class ListenMenu final : public Room {
+public:
+ static constexpr const char *kClassName = "CHabitacionEsperar";
+ ListenMenu(World *world, Common::ReadStream &stream);
+};
+
+class Inventory final : public Room {
+public:
+ static constexpr const char *kClassName = "CInventario";
+ Inventory(World *world, Common::ReadStream &stream);
+ virtual ~Inventory() override;
+
+private:
+ Common::Array<Item *> _items;
+};
+
+enum class GlobalAnimationKind {
+ GeneralFont = 0,
+ TextFont,
+ Cursor,
+ MortadeloIcon,
+ FilemonIcon,
+ InventoryIcon,
+ MortadeloDisabledIcon,
+ FilemonDisabledIcon,
+ InventoryDisabledIcon,
+
+ Count
+};
+
+class World final {
+public:
+ World();
+ ~World();
+
+ // reference-returning queries will error if the object does not exist
+
+ inline Room &globalRoom() const { return *_globalRoom; }
+ inline Inventory &inventory() const { return *_inventory; }
+ inline MainCharacter &filemon() const { return *_filemon; }
+ inline MainCharacter &mortadelo() const { return *_mortadelo; }
+ inline const Common::String &initScriptName() const { return _initScriptName; }
+ inline uint8 loadedMapCount() const { return _loadedMapCount; }
+
+ inline Room *¤tRoom() { return _currentRoom; }
+ inline Room *currentRoom() const { return _currentRoom; }
+
+ MainCharacter &getMainCharacterByKind(MainCharacterKind kind) const;
+ Room *getRoomByName(const Common::String &name) const;
+ ObjectBase *getObjectByName(const Common::String &name) const;
+ const Common::String &getGlobalAnimationName(GlobalAnimationKind kind) const;
+
+private:
+ bool loadWorldFile(const char *path);
+
+ Common::Array<Room *> _rooms;
+ Common::String _globalAnimationNames[(int)GlobalAnimationKind::Count];
+ Common::String _initScriptName;
+ Room *_globalRoom, *_currentRoom = nullptr;
+ Inventory *_inventory;
+ MainCharacter *_filemon, *_mortadelo;
+ uint8 _loadedMapCount = 0;
+};
+
+}
+
+#endif // ROOMS_H
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
new file mode 100644
index 00000000000..74fe921f75d
--- /dev/null
+++ b/engines/alcachofa/shape.cpp
@@ -0,0 +1,140 @@
+/* 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 "shape.h"
+#include "stream-helper.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+Shape::Shape() {}
+
+Shape::Shape(ReadStream &stream) {
+ auto complexity = stream.readByte();
+ uint8 pointsPerPolygon;
+ if (complexity < 0 || complexity > 3)
+ error("Invalid shape complexity %d", complexity);
+ else if (complexity == 3)
+ pointsPerPolygon = 0; // read in per polygon
+ else
+ pointsPerPolygon = 1 << complexity;
+
+ int polygonCount = stream.readUint16LE();
+ _polygons.reserve(polygonCount);
+ _points.reserve(MIN(3, (int)pointsPerPolygon) * polygonCount);
+ for (int i = 0; i < polygonCount; i++) {
+ auto pointCount = pointsPerPolygon == 0
+ ? stream.readByte()
+ : pointsPerPolygon;
+ for (int j = 0; j < pointCount; j++)
+ _points.push_back(readPoint(stream));
+ addPolygon(pointCount);
+ }
+}
+
+uint Shape::addPolygon(uint maxCount) {
+ // Common functionality of shapes is that polygons are reduced
+ // so that the first point is not duplicated
+ uint firstI = empty() ? 0 : _polygons.back().first + _polygons.back().second;
+ uint newCount = maxCount;
+ if (maxCount > 1) {
+ for (newCount = 1; newCount < maxCount; newCount++) {
+ if (_points[firstI + newCount] == _points[firstI])
+ break;
+ }
+ }
+ _polygons.push_back({ firstI, newCount });
+ return newCount;
+}
+
+Polygon Shape::at(uint index) {
+ auto range = _polygons[index];
+ Polygon p;
+ p._points = Span<Point>(_points.data() + range.first, range.second);
+ return p;
+}
+
+PathFindingShape::PathFindingShape() {}
+
+PathFindingShape::PathFindingShape(ReadStream &stream) {
+ auto polygonCount = stream.readUint16LE();
+ _polygons.reserve(polygonCount);
+ _polygonValues.reserve(polygonCount);
+ _points.reserve(polygonCount * kPointsPerPolygon);
+ _pointValues.reserve(polygonCount * kPointsPerPolygon);
+
+ for (int i = 0; i < polygonCount; i++) {
+ for (int j = 0; j < kPointsPerPolygon; j++)
+ _points.push_back(readPoint(stream));
+ _polygonValues.push_back(stream.readSByte());
+ for (int j = 0; j < kPointsPerPolygon; j++)
+ _pointValues.push_back(stream.readSByte());
+
+ addPolygon(kPointsPerPolygon);
+ }
+
+ // TODO: Implement the path finding
+}
+
+PathFindingPolygon PathFindingShape::at(uint index) {
+ auto range = _polygons[index];
+ PathFindingPolygon p;
+ p._points = Span<Point>(_points.data() + range.first, range.second);
+ p._pointValues = Span<int8>(_pointValues.data() + range.first, range.second);
+ p._polygonValue = _polygonValues[index];
+ return p;
+}
+
+FloorColorShape::FloorColorShape() {}
+
+FloorColorShape::FloorColorShape(ReadStream &stream) {
+ auto polygonCount = stream.readUint16LE();
+ _polygons.reserve(polygonCount);
+ _polygonValues.reserve(polygonCount);
+ _points.reserve(polygonCount * kPointsPerPolygon);
+ _pointColors.reserve(polygonCount * kPointsPerPolygon);
+ _pointWeights.reserve(polygonCount * kPointsPerPolygon);
+
+ for (int i = 0; i < polygonCount; i++) {
+ for (int j = 0; j < kPointsPerPolygon; j++)
+ _points.push_back(readPoint(stream));
+ for (int j = 0; j < kPointsPerPolygon; j++)
+ _pointWeights.push_back(stream.readSByte());
+ for (int j = 0; j < kPointsPerPolygon; j++)
+ _pointColors.push_back(stream.readUint32LE());
+ _polygonValues.push_back(stream.readSByte());
+
+ addPolygon(kPointsPerPolygon);
+ }
+}
+
+FloorColorPolygon FloorColorShape::at(uint index) {
+ auto range = _polygons[index];
+ FloorColorPolygon p;
+ p._points = Span<Point>(_points.data() + range.first, range.second);
+ p._pointWeights = Span<uint8>(_pointWeights.data() + range.first, range.second);
+ p._pointColors = Span<uint32>(_pointColors.data() + range.first, range.second);
+ p._polygonValue = _polygonValues[index];
+ return p;
+}
+
+}
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
new file mode 100644
index 00000000000..4bb3bd8e95d
--- /dev/null
+++ b/engines/alcachofa/shape.h
@@ -0,0 +1,156 @@
+/* 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/>.
+ *
+ */
+
+#ifndef SHAPE_H
+#define SHAPE_H
+
+#include "common/stream.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/span.h"
+#include "common/util.h"
+#include "math/vector2d.h"
+
+namespace Alcachofa {
+
+struct Polygon {
+ Common::Span<Common::Point> _points;
+};
+
+struct PathFindingPolygon : Polygon {
+ Common::Span<int8> _pointValues;
+ int8 _polygonValue;
+};
+
+struct FloorColorPolygon : Polygon {
+ Common::Span<uint32> _pointColors;
+ Common::Span<uint8> _pointWeights;
+ int8 _polygonValue;
+};
+
+template<class TShape, typename TPolygon>
+struct PolygonIterator {
+ using difference_type = uint;
+ using value_type = TPolygon;
+
+ inline value_type operator*() const {
+ return _shape.at(_index);
+ }
+
+ inline PolygonIterator<TShape, TPolygon> &operator++() {
+ assert(_index < _shape.polygonCount());
+ _index++;
+ return *this;
+ }
+
+ inline PolygonIterator<TShape, TPolygon> &operator++(int) {
+ assert(_index < _shape.polygonCount());
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+private:
+ friend typename Common::remove_const_t<TShape>;
+ PolygonIterator(TShape &shape, uint index = 0)
+ : _shape(shape)
+ , _index(index) {
+ }
+
+ TShape &_shape;
+ uint _index;
+};
+
+class Shape {
+public:
+ using iterator = PolygonIterator<Shape, Polygon>;
+
+ Shape();
+ Shape(Common::ReadStream &stream);
+
+ inline Common::Point firstPoint() const { return _points.empty() ? Common::Point() : _points[0]; }
+ inline uint polygonCount() const { return _polygons.size(); }
+ inline bool empty() const { return polygonCount() == 0; }
+ inline iterator begin() { return { *this, 0 }; }
+ inline iterator end() { return { *this, polygonCount() }; }
+
+ Polygon at(uint index);
+
+protected:
+ uint addPolygon(uint maxCount);
+
+ using PolygonRange = Common::Pair<uint, uint>;
+ Common::Array<PolygonRange> _polygons;
+ Common::Array<Common::Point> _points;
+};
+
+/**
+ * @brief Path finding is based on the Shape class with the invariant that
+ * every polygon is a convex quad.
+ * Equal points of different quads link them together, for edges we add an
+ * additional link point in the center of the edge.
+ *
+ * The resulting graph is processed using Floyd-Warshall to precalculate for
+ * the actual path finding. Additionally we check whether a character can
+ * walk straight through an edge instead of following the link points.
+ *
+ * None of this is implemented yet by the way ;)
+ */
+class PathFindingShape final : public Shape {
+public:
+ using iterator = PolygonIterator<PathFindingShape, PathFindingPolygon>;
+ static constexpr const uint kPointsPerPolygon = 4;
+
+ PathFindingShape();
+ PathFindingShape(Common::ReadStream &stream);
+
+ inline iterator begin() { return { *this, 0 }; }
+ inline iterator end() { return { *this, polygonCount() }; }
+
+ PathFindingPolygon at(uint index);
+
+private:
+ Common::Array<int8> _pointValues;
+ Common::Array<int8> _polygonValues;
+};
+
+class FloorColorShape final : public Shape {
+public:
+ using iterator = PolygonIterator<FloorColorShape, FloorColorPolygon>;
+ static constexpr const uint kPointsPerPolygon = 4;
+
+ FloorColorShape();
+ FloorColorShape(Common::ReadStream &stream);
+
+ inline iterator begin() { return { *this, 0 }; }
+ inline iterator end() { return { *this, polygonCount() }; }
+
+ FloorColorPolygon at(uint index);
+
+private:
+ Common::Array<uint32> _pointColors;
+ Common::Array<uint8> _pointWeights;
+ Common::Array<int8> _polygonValues;
+};
+
+}
+
+#endif // SHAPE_H
diff --git a/engines/alcachofa/stream-helper.cpp b/engines/alcachofa/stream-helper.cpp
new file mode 100644
index 00000000000..f849549f19a
--- /dev/null
+++ b/engines/alcachofa/stream-helper.cpp
@@ -0,0 +1,74 @@
+/* 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 "stream-helper.h"
+
+#include "common/textconsole.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+bool readBool(ReadStream &stream) {
+ return stream.readByte() != 0;
+}
+
+Point readPoint(ReadStream &stream) {
+ return { (int16)stream.readSint32LE(), (int16)stream.readSint32LE() };
+}
+
+static uint32 readVarInt(ReadStream &stream) {
+ uint32 length = stream.readByte();
+ if (length != 0xFF)
+ return length;
+ length = stream.readUint16LE();
+ if (length != 0xFFFF)
+ return length;
+ return stream.readUint32LE();
+}
+
+String readVarString(ReadStream &stream) {
+ uint32 length = readVarInt(stream);
+ if (length == 0)
+ return Common::String();
+
+ // TODO: Being able to resize a string would avoid the double-allocation :/
+ char *buffer = new char[length];
+ if (buffer == nullptr)
+ error("Out of memory in readVarString");
+ if (stream.read(buffer, length) != length)
+ error("Could not read all %u bytes in readVarString", length);
+
+ String result(buffer, buffer + length);
+ delete[] buffer;
+ return result;
+}
+
+void skipVarString(SeekableReadStream &stream) {
+ stream.skip(readVarInt(stream));
+}
+
+void syncPoint(Serializer &serializer, Point &point) {
+ serializer.syncAsSint32LE(point.x);
+ serializer.syncAsSint32LE(point.y);
+}
+
+}
diff --git a/engines/alcachofa/stream-helper.h b/engines/alcachofa/stream-helper.h
new file mode 100644
index 00000000000..07bab1338a4
--- /dev/null
+++ b/engines/alcachofa/stream-helper.h
@@ -0,0 +1,56 @@
+/* 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/>.
+ *
+ */
+
+#ifndef STREAM_HELPER_H
+#define STREAM_HELPER_H
+
+#include "common/stream.h"
+#include "common/serializer.h"
+#include "common/rect.h"
+
+namespace Alcachofa {
+
+bool readBool(Common::ReadStream &stream);
+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);
+
+template<typename T>
+inline void syncArray(Common::Serializer &serializer, Common::Array<T> &array, void (*serializeFunction)(Common::Serializer &, T &)) {
+ auto size = array.size();
+ serializer.syncAsUint32LE(size);
+ array.resize(size);
+ serializer.syncArray(array.data(), size, serializeFunction);
+}
+
+template<typename T>
+inline void syncEnum(Common::Serializer &serializer, T &enumValue) {
+ // syncAs does not have a cast for saving
+ int32 intValue = static_cast<int32>(enumValue);
+ serializer.syncAsSint32LE(intValue);
+ enumValue = static_cast<T>(intValue);
+}
+
+}
+
+#endif // STREAM_HELPER_H
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
new file mode 100644
index 00000000000..c845ba2492e
--- /dev/null
+++ b/engines/alcachofa/ui-objects.cpp
@@ -0,0 +1,121 @@
+/* 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 "objects.h"
+#include "rooms.h"
+#include "stream-helper.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+MenuButton::MenuButton(Room *room, ReadStream &stream)
+ : PhysicalObject(room, stream)
+ , _actionId(stream.readSint32LE())
+ , _graphicNormal(stream)
+ , _graphicHovered(stream)
+ , _graphicClicked(stream)
+ , _graphicDisabled(stream) {
+}
+
+InternetMenuButton::InternetMenuButton(Room *room, ReadStream &stream)
+ : MenuButton(room, stream) {
+}
+
+OptionsMenuButton::OptionsMenuButton(Room *room, ReadStream &stream)
+ : MenuButton(room, stream) {
+}
+
+MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
+ : MenuButton(room, stream) {
+}
+
+PushButton::PushButton(Room *room, ReadStream &stream)
+ : PhysicalObject(room, stream)
+ , _alwaysVisible(readBool(stream))
+ , _graphic1(stream)
+ , _graphic2(stream)
+ , _actionId(stream.readSint32LE()) {
+}
+
+EditBox::EditBox(Room *room, ReadStream &stream)
+ : PhysicalObject(room, stream)
+ , i1(stream.readSint32LE())
+ , p1(Shape(stream).firstPoint())
+ , _labelId(readVarString(stream))
+ , b1(readBool(stream))
+ , i3(stream.readSint32LE())
+ , i4(stream.readSint32LE())
+ , i5(stream.readSint32LE())
+ , _fontId(stream.readSint32LE()) {
+}
+
+CheckBox::CheckBox(Room *room, ReadStream &stream)
+ : PhysicalObject(room, stream)
+ , b1(readBool(stream))
+ , _graph1(stream)
+ , _graph2(stream)
+ , _graph3(stream)
+ , _graph4(stream)
+ , _valueId(stream.readSint32LE()) {
+}
+
+CheckBoxAutoAdjustNoise::CheckBoxAutoAdjustNoise(Room *room, ReadStream &stream)
+ : CheckBox(room, stream) {
+ stream.readByte(); // unused and ignored byte
+}
+
+SlideButton::SlideButton(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream)
+ , i1(stream.readSint32LE())
+ , p1(Shape(stream).firstPoint())
+ , p2(Shape(stream).firstPoint())
+ , _graph1(stream)
+ , _graph2(stream)
+ , _graph3(stream) {
+}
+
+IRCWindow::IRCWindow(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream)
+ , _p1(Shape(stream).firstPoint())
+ , _p2(Shape(stream).firstPoint()) {
+}
+
+MessageBox::MessageBox(Room *room, ReadStream &stream)
+ : ObjectBase(room, stream)
+ , _graph1(stream)
+ , _graph2(stream)
+ , _graph3(stream)
+ , _graph4(stream)
+ , _graph5(stream) {
+ _graph1.start(true);
+ _graph2.start(true);
+ _graph3.start(true);
+ _graph4.start(true);
+ _graph5.start(true);
+}
+
+VoiceMeter::VoiceMeter(Room *room, ReadStream &stream)
+ : GraphicObject(room, stream) {
+ stream.readByte(); // unused and ignored byte
+}
+
+}
Commit: ea67e329f380da83cbfe3d821deb69ee3a1a8132
https://github.com/scummvm/scummvm/commit/ea67e329f380da83cbfe3d821deb69ee3a1a8132
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add initial animation and OpenGL support
Changed paths:
A engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 813f2a4c30a..e09fe231768 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -56,11 +56,13 @@ Common::String AlcachofaEngine::getGameId() const {
Common::Error AlcachofaEngine::run() {
// Initialize 320x200 paletted graphics mode
- initGraphics(320, 200);
- _screen = new Graphics::Screen();
+ _renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
auto world = new World();
delete world;
+ auto animation = new Animation("MORTADELO_ACOSTANDOSE");
+ animation->load();
+ animation->draw2D(0, Math::Vector2d(512, 300), 1.0f, Math::Angle(), BlendMode::Alpha, { 255, 255, 255, 255 });
// Set the engine's debugger console
setDebugger(new Console());
@@ -70,21 +72,27 @@ Common::Error AlcachofaEngine::run() {
if (saveSlot != -1)
(void)loadGameState(saveSlot);
- // Draw a series of boxes on screen as a sample
- for (int i = 0; i < 100; ++i)
- _screen->frameRect(Common::Rect(i, i, 320 - i, 200 - i), i);
- _screen->update();
-
// Simple event handling loop
byte pal[256 * 3] = { 0 };
Common::Event e;
int offset = 0;
Graphics::FrameLimiter limiter(g_system, 60);
+ int32 frame = 0;
+ uint32 nextSecond = g_system->getMillis();
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(e)) {
}
+ _renderer->begin();
+
+ if (g_system->getMillis() >= nextSecond) {
+ frame = (frame + 1) % animation->frameCount();
+ nextSecond = g_system->getMillis() + animation->frameDuration(frame);
+ }
+
+ animation->draw2D(frame, Math::Vector2d(100, 100), 1.0f, Math::Angle(), BlendMode::Alpha, { 255, 255, 255, 255 });
+
// Cycle through a simple palette
++offset;
for (int i = 0; i < 256; ++i)
@@ -93,10 +101,12 @@ Common::Error AlcachofaEngine::run() {
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
limiter.delayBeforeSwap();
- _screen->update();
+ _renderer->end();
limiter.startFrame();
}
+ delete animation;
+
return Common::kNoError;
}
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index ae6eb72cc92..a703d18341b 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -35,6 +35,7 @@
#include "graphics/screen.h"
#include "alcachofa/detection.h"
+#include "alcachofa/graphics.h"
namespace Alcachofa {
@@ -47,12 +48,12 @@ private:
protected:
// Engine APIs
Common::Error run() override;
-public:
- Graphics::Screen *_screen = nullptr;
public:
AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc);
~AlcachofaEngine() override;
+ inline IRenderer &renderer() const { return *_renderer; }
+
uint32 getFeatures() const;
/**
@@ -95,6 +96,10 @@ public:
Common::Serializer s(stream, nullptr);
return syncGame(s);
}
+
+private:
+ Graphics::Screen *_screen = nullptr;
+ Common::ScopedPtr<IRenderer> _renderer;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 0aacd2510dc..b77281f9b76 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -159,7 +159,7 @@ void MainCharacter::serializeSave(Serializer &serializer) {
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
- _graphic._animation.reset(new Animation(animationFileName, AnimationFolder::Fondos));
+ _graphic._animation.reset(new Animation(animationFileName, AnimationFolder::Backgrounds));
_graphic._scale = scale;
_graphic._order = 59;
}
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
new file mode 100644
index 00000000000..4a376b1a40f
--- /dev/null
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -0,0 +1,297 @@
+/* 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 "graphics.h"
+
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/managed_surface.h"
+#include "graphics/opengl/system_headers.h"
+#include "graphics/opengl/debug.h"
+
+using namespace Common;
+using namespace Math;
+using namespace Graphics;
+
+namespace Alcachofa {
+
+struct OpenGLFormat {
+ GLenum _format, _type;
+ inline bool isValid() const { return _format != GL_NONE; }
+};
+
+static bool areComponentsInOrder(const PixelFormat &format, int r, int g, int b, int a) {
+ return format == (a < 0
+ ? PixelFormat(3, 8, 8, 8, 0, r * 8, g * 8, b * 8, 0)
+ : PixelFormat(4, 8, 8, 8, 8, r * 8, g * 8, b * 8, a * 8));
+}
+
+static OpenGLFormat getOpenGLFormatOf(const PixelFormat &format) {
+ if (areComponentsInOrder(format, 0, 1, 2, 3))
+ return { GL_RGBA, GL_UNSIGNED_BYTE };
+ else if (areComponentsInOrder(format, 3, 2, 1, 0))
+ return { GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 };
+ else if (areComponentsInOrder(format, 0, 1, 2, -1))
+ return { GL_RGB, GL_UNSIGNED_BYTE };
+ else if (areComponentsInOrder(format, 2, 1, 0, 3))
+ return { GL_BGRA, GL_UNSIGNED_BYTE };
+ else if (areComponentsInOrder(format, 2, 1, 0, -1))
+ return { GL_BGR, GL_UNSIGNED_BYTE };
+ // we could look for packed formats here as well in the future
+ else
+ return { GL_NONE, GL_NONE };
+}
+
+
+class OpenGLTexture : public ITexture {
+public:
+ OpenGLTexture(int32 w, int32 h, bool withMipmaps)
+ : ITexture({ (int16)w, (int16)h })
+ , _withMipmaps(withMipmaps) {
+ GL_CALL(glEnable(GL_TEXTURE_2D));
+ 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));
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
+ }
+
+ virtual ~OpenGLTexture() override {
+ if (_handle != 0)
+ GL_CALL(glDeleteTextures(1, &_handle));
+ }
+
+ virtual void update(const ManagedSurface &surface) {
+ OpenGLFormat format = getOpenGLFormatOf(surface.format);
+ assert(surface.w == size().x && surface.h == size().y);
+ assert(format.isValid());
+
+ GL_CALL(glEnable(GL_TEXTURE_2D));
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, _handle));
+ GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.w, surface.h, 0, format._format, format._type, surface.getPixels()));
+ if (_withMipmaps)
+ GL_CALL(glGenerateMipmap(GL_TEXTURE_2D));
+ else
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
+ }
+
+ inline GLuint handle() const { return _handle; }
+
+private:
+ GLuint _handle;
+ bool _withMipmaps;
+};
+
+class OpenGLRenderer : public IRenderer {
+public:
+ OpenGLRenderer(Point resolution)
+ : _resolution(resolution) {
+ initViewportAndMatrices();
+ GL_CALL(glDisable(GL_LIGHTING));
+ GL_CALL(glDisable(GL_DEPTH_TEST));
+ GL_CALL(glDisable(GL_SCISSOR_TEST));
+ GL_CALL(glDisable(GL_STENCIL_TEST));
+ GL_CALL(glEnable(GL_BLEND));
+ GL_CALL(glDepthMask(GL_FALSE));
+ }
+
+ virtual ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override {
+ assert(w > 0 && h > 0);
+ return ScopedPtr<ITexture>(new OpenGLTexture(w, h, withMipmaps));
+ }
+
+ virtual void begin() override {
+ GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
+ GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT));
+ _currentLodBias = -1000.0f;
+ _currentTexture = nullptr;
+ _currentBlendMode = (BlendMode)-1;
+
+#ifdef _DEBUG
+ glClearColor(0.5f, 0.0f, 0.5f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+#endif
+ }
+
+ virtual void end() override {
+ GL_CALL(glFlush());
+ g_system->updateScreen();
+ }
+
+ virtual void setTexture(const ITexture *texture) override {
+ if (texture == _currentTexture)
+ return;
+ else if (texture == nullptr) {
+ GL_CALL(glDisable(GL_TEXTURE_2D));
+ GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
+ }
+ else {
+ if (_currentTexture == nullptr) {
+ GL_CALL(glEnable(GL_TEXTURE_2D));
+ GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
+ }
+ auto glTexture = dynamic_cast<const OpenGLTexture *>(texture);
+ assert(glTexture != nullptr);
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, glTexture->handle()));
+ }
+ _currentTexture = texture;
+ }
+
+ virtual void setBlendMode(BlendMode blendMode) override {
+ if (blendMode == _currentBlendMode)
+ return;
+ // first the blend func
+ switch (blendMode) {
+ case BlendMode::AdditiveAlpha:
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ case BlendMode::Additive:
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE));
+ break;
+ case BlendMode::Multiply:
+ GL_CALL(glBlendFunc(GL_DST_COLOR, GL_ONE));
+ break;
+ case BlendMode::Alpha:
+ GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ case BlendMode::Tinted:
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ default: assert(false && "Invalid blend mode"); break;
+ }
+
+ /** now the texture stage, mind that this always applies:
+ * SRC0_RGB is TEXTURE
+ * SRC1_RGB/ALPHA is CONSTANT
+ * COMBINE_ALPHA is REPLACE
+ */
+ switch (blendMode) {
+ case BlendMode::AdditiveAlpha:
+ case BlendMode::Additive:
+ case BlendMode::Multiply:
+ // (1 - TintAlpha) * TexColor, TexAlpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_ONE_MINUS_SRC_ALPHA));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
+ break;
+ case BlendMode::Alpha:
+ // TexColor, TintAlpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT));
+ break;
+ case BlendMode::Tinted:
+ // (TintColor * TintAlpha) * TexColor, TexAlpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR)); // pre-multiplied with alpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
+ break;
+ default: assert(false && "Invalid blend mode"); break;
+ }
+ _currentBlendMode = blendMode;
+ }
+
+ virtual void setLodBias(float lodBias) override {
+ if (abs(_currentLodBias - lodBias) < epsilon)
+ return;
+ GL_CALL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, lodBias));
+ _currentLodBias = lodBias;
+ }
+
+ virtual void quad(
+ Vector2d center,
+ Vector2d size,
+ Color color,
+ Angle rotation,
+ Vector2d texMin,
+ Vector2d texMax) override {
+ size *= 0.5f;
+ center += size;
+ Vector2d positions[] = {
+ center + Vector2d(-size.getX(), -size.getY()),
+ center + Vector2d(-size.getX(), +size.getY()),
+ center + Vector2d(+size.getX(), +size.getY()),
+ center + Vector2d(+size.getX(), -size.getY()),
+ };
+ if (abs(rotation.getDegrees()) > epsilon) {
+ const Vector2d zero(0, 0);
+ for (int i = 0; i < 4; i++)
+ positions[i].rotateAround(zero, rotation);
+ }
+
+ Vector2d texCoords[] = {
+ { texMin.getX(), texMin.getY() },
+ { texMin.getX(), texMax.getY() },
+ { texMax.getX(), texMax.getY() },
+ { texMax.getX(), texMin.getY() }
+ };
+
+ float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
+
+ glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+ GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
+ if (_currentTexture != nullptr)
+ GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
+ GL_CALL(glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, colors));
+ GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
+
+#if DEBUG
+ // make sure we crash instead of someone using our stack arrays
+ GL_CALL(glVertexPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
+ GL_CALL(glTexCoordPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
+#endif
+ }
+
+private:
+ void initViewportAndMatrices() {
+ int32 screenWidth = g_system->getWidth();
+ int32 screenHeight = g_system->getHeight();
+ Rect viewport(
+ MIN<int32>(screenWidth, screenHeight * (float)_resolution.x / _resolution.y),
+ MIN<int32>(screenHeight, screenWidth * (float)_resolution.y / _resolution.x));
+ viewport.translate(
+ (screenWidth - viewport.width()) / 2,
+ (screenHeight - viewport.height()) / 2);
+
+ GL_CALL(glViewport(viewport.left, viewport.top, viewport.width(), viewport.height()));
+ GL_CALL(glMatrixMode(GL_PROJECTION));
+ GL_CALL(glLoadIdentity());
+ GL_CALL(glOrtho(0.0f, _resolution.x, _resolution.y, 0.0f, -1.0f, 1.0f));
+ GL_CALL(glMatrixMode(GL_MODELVIEW));
+ GL_CALL(glLoadIdentity());
+ }
+
+ Point _resolution;
+ const ITexture *_currentTexture = nullptr;
+ BlendMode _currentBlendMode = (BlendMode)-1;
+ float _currentLodBias = 0.0f;
+};
+
+IRenderer *IRenderer::createOpenGLRenderer(Point resolution) {
+ initGraphics3d(resolution.x, resolution.y);
+ return new OpenGLRenderer(resolution);
+}
+
+}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 6603ea99256..9230ba4fa0b 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -21,18 +21,236 @@
#include "graphics.h"
#include "stream-helper.h"
+#include "alcachofa.h"
#include "common/system.h"
+#include "common/file.h"
+#include "common/substream.h"
+#include "image/tga.h"
using namespace Common;
+using namespace Math;
+using namespace Image;
+using namespace Graphics;
namespace Alcachofa {
-Animation::Animation(String fileName, AnimationFolder folder)
+ITexture::ITexture(Point size) : _size(size) {}
+
+AnimationBase::AnimationBase(String fileName, AnimationFolder folder)
: _fileName(move(fileName))
, _folder(folder) {
}
+AnimationBase::~AnimationBase() {
+ freeImages();
+}
+
+void AnimationBase::load() {
+ if (_isLoaded)
+ return;
+
+ Common::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");
+ }
+ if (_fileName.size() < 4 || scumm_strnicmp(_fileName.end() - 4, ".AN0", 4) != 0)
+ _fileName += ".AN0";
+ fullPath += _fileName;
+ Common::File file;
+ if (!file.open(fullPath.c_str())) {
+ // original fallback
+ fullPath = "Mascaras/" + _fileName;
+ if (!file.open(fullPath.c_str()))
+ error("Could not open animation %s", _fileName.c_str());
+ }
+
+ uint spriteCount = file.readUint32LE();
+ assert(spriteCount < kMaxSpriteIDs);
+ _spriteBases.reserve(spriteCount);
+
+ uint imageCount = file.readUint32LE();
+ _images.reserve(imageCount);
+ _imageOffsets.reserve(imageCount);
+ for (uint i = 0; i < imageCount; i++) {
+ _images.push_back(readImage(file));
+ }
+
+ // an inconsistency, maybe a historical reason:
+ // the sprite bases are also stored as fixed 256 array, but as sprite *indices*
+ // 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(file.readUint32LE());
+ assert(_spriteBases.back() < imageCount);
+ }
+#ifdef _DEBUG
+ for (uint i = spriteCount; i < kMaxSpriteIDs; i++)
+ assert(file.readSint32LE() == 0);
+#else
+ file.skip(sizeof(int32) * (kMaxSpriteIDs - spriteCount));
+#endif
+
+ for (uint i = 0; i < imageCount; i++)
+ _imageOffsets.push_back(readPoint(file));
+ for (uint i = 0; i < kMaxSpriteIDs; i++)
+ _spriteIndexMapping[i] = file.readSint32LE();
+
+ uint frameCount = file.readUint32LE();
+ _frames.reserve(frameCount);
+ _spriteOffsets.reserve(frameCount * spriteCount);
+ for (uint i = 0; i < frameCount; i++) {
+ for (uint j = 0; j < spriteCount; j++)
+ _spriteOffsets.push_back(file.readUint32LE());
+ AnimationFrame frame;
+ frame._center = readPoint(file);
+ frame._offset = readPoint(file);
+ frame._duration = file.readUint32LE();
+ _frames.push_back(frame);
+ }
+
+ _isLoaded = true;
+}
+
+void AnimationBase::freeImages() {
+ if (!_isLoaded)
+ return;
+ for (auto *image : _images) {
+ if (image != nullptr)
+ delete image;
+ }
+ _isLoaded = false;
+}
+
+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());
+
+ // The length of the image is unknown but TGADecoder does not read
+ // the end marker, so let's search for it.
+ static const char *kExpectedMarker = "TRUEVISION-XFILE.";
+ static const uint kMarkerLength = 18;
+ char buffer[kMarkerLength] = { 0 };
+ char *potentialStart = buffer + kMarkerLength;
+ do {
+ uint nextRead = potentialStart - buffer;
+ 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());
+ potentialStart = find(buffer + 1, buffer + kMarkerLength, kExpectedMarker[0]);
+ } while (strncmp(buffer, kExpectedMarker, kMarkerLength) != 0);
+
+ // instead of not storing unused frame images the animation contains
+ // transparent 2x1 images. Let's just ignore them.
+ auto source = decoder.getSurface();
+ if (source->w == 2 && source->h == 1)
+ return nullptr;
+
+ auto target = source->convertTo(BlendBlit::getSupportedPixelFormat(), decoder.getPalette(), decoder.getPaletteColorCount());
+ return new ManagedSurface(target);
+}
+
+Animation::Animation(String fileName, AnimationFolder folder)
+ : AnimationBase(fileName, folder) {
+}
+
+void Animation::load() {
+ if (_isLoaded)
+ return;
+ AnimationBase::load();
+ const auto withMipmaps = _folder != AnimationFolder::Backgrounds;
+ Rect maxBounds = maxFrameBounds();
+ _renderedSurface.create(maxBounds.width(), maxBounds.height(), BlendBlit::getSupportedPixelFormat());
+ _renderedTexture = g_engine->renderer().createTexture(maxBounds.width(), maxBounds.height(), withMipmaps);
+}
+
+int32 Animation::imageIndex(int32 frameI, int32 spriteId) const {
+ assert(frameI >= 0 && (uint)frameI < frameCount());
+ assert(spriteId >= 0 && (uint)spriteId < spriteCount());
+ int32 spriteIndex = _spriteIndexMapping[spriteId];
+ int32 offset = _spriteOffsets[frameI * spriteCount() + spriteIndex];
+ return offset <= 0 ? -1
+ : offset + _spriteBases[spriteIndex] - 1;
+}
+
+Rect Animation::spriteBounds(int32 frameI, int32 spriteId) const {
+ int32 imageI = imageIndex(frameI, spriteId);
+ auto image = imageI < 0 ? nullptr : _images[imageI];
+ return image == nullptr ? Rect()
+ : Rect(_imageOffsets[imageI], image->w, image->h);
+}
+
+Rect Animation::frameBounds(int32 frameI) const {
+ if (spriteCount() == 0)
+ return Rect();
+ Rect bounds = spriteBounds(frameI, 0);
+ for (uint spriteI = 1; spriteI < spriteCount(); spriteI++)
+ bounds.extend(spriteBounds(frameI, spriteI));
+ return bounds;
+}
+
+Rect Animation::maxFrameBounds() const {
+ if (frameCount() == 0)
+ return Rect();
+ Rect bounds = frameBounds(0);
+ for (uint frameI = 1; frameI < frameCount(); frameI++)
+ bounds.extend(frameBounds(frameI));
+ return bounds;
+}
+
+void Animation::prerenderFrame(int32 frameI) {
+ assert(frameI >= 0 && (uint)frameI < frameCount());
+ if (frameI == _renderedFrameI)
+ return;
+ auto bounds = frameBounds(frameI);
+ _renderedSurface.clear();
+ for (uint spriteI = 0; spriteI < spriteCount(); spriteI++) {
+ int32 imageI = imageIndex(frameI, spriteI);
+ auto image = imageI < 0 ? nullptr : _images[imageI];
+ if (image == nullptr)
+ continue;
+ int offsetX = _imageOffsets[imageI].x - bounds.left;
+ int offsetY = _imageOffsets[imageI].y - bounds.top;
+ image->blendBlitTo(_renderedSurface, offsetX, offsetY);
+ }
+
+ if (_premultiplyAlpha != 100) {
+ byte *itPixel = (byte*)_renderedSurface.getPixels();
+ uint componentCount = _renderedSurface.w * _renderedSurface.h * 4;
+ for (uint32 i = 0; i < componentCount; i++, itPixel++)
+ *itPixel = *itPixel * _premultiplyAlpha / 100;
+ }
+
+ _renderedTexture->update(_renderedSurface);
+ _renderedFrameI = frameI;
+}
+
+void Animation::draw2D(int32 frameI, Vector2d center, float scale, Angle rotation, 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);
+
+ Vector2d size(bounds.width(), bounds.height());
+ Vector2d offset(
+ bounds.left - _frames[frameI]._center.x + _frames[frameI]._offset.x,
+ bounds.top - _frames[frameI]._center.y + _frames[frameI]._offset.y);
+ center += offset * scale;
+ size *= scale;
+
+ auto &renderer = g_engine->renderer();
+ renderer.setTexture(_renderedTexture.get());
+ //renderer.setTexture(nullptr);
+ renderer.setBlendMode(blendMode);
+ renderer.quad(center, size, color, rotation, texMin, texMax);
+}
+
Graphic::Graphic() {
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index d7e20a458fe..987416d6533 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -26,9 +26,31 @@
#include "common/stream.h"
#include "common/serializer.h"
#include "common/rect.h"
+#include "math/vector2d.h"
+#include "graphics/managed_surface.h"
namespace Alcachofa {
+/**
+ * Because this gets confusing fast, here in tabular form
+ *
+ * | BlendMode | SrcColor | SrcAlpha | SrcBlend | DstBlend |
+ * |:-------------:|:---------------------------------|:----------|:---------|:-------------|
+ * | AdditiveAlpha | (1 - TintAlpha) * TexColor | TexAlpha | One | 1 - SrcAlpha |
+ * | Additive | (1 - TintAlpha) * TexColor | TexAlpha | One | One |
+ * | Multiply | (1 - TintAlpha) * TexColor | TexAlpha | DstColor | One |
+ * | Alpha | TexColor | TintAlpha | SrcAlpha | 1 - SrcAlpha |
+ * | Tinted | TintColor * TintAlpha * TexColor | TexAlpha | One | 1 - SrcAlpha |
+ *
+ */
+enum class BlendMode {
+ AdditiveAlpha,
+ Additive,
+ Multiply,
+ Alpha,
+ Tinted
+};
+
enum class CursorType {
Normal,
LookAt,
@@ -49,21 +71,135 @@ enum class Direction {
constexpr const int32 kDirectionCount = 4;
+struct Color {
+ uint8 b, g, r, a;
+};
+
+class ITexture {
+public:
+ ITexture(Common::Point size);
+ virtual ~ITexture() = default;
+
+ virtual void update(const Graphics::ManagedSurface &surface) = 0;
+
+ inline const Common::Point &size() const { return _size; }
+
+private:
+ Common::Point _size;
+};
+
+class IRenderer {
+public:
+ virtual ~IRenderer() = default;
+
+ virtual Common::ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps = true) = 0;
+
+ virtual void begin() = 0;
+ virtual void setTexture(const ITexture *texture) = 0;
+ virtual void setBlendMode(BlendMode blendMode) = 0;
+ virtual void setLodBias(float lodBias) = 0;
+ virtual void quad(
+ Math::Vector2d center,
+ Math::Vector2d size,
+ Color color = { 255, 255, 255, 255 },
+ Math::Angle rotation = Math::Angle(),
+ Math::Vector2d texMin = Math::Vector2d(0, 0),
+ Math::Vector2d texMax = Math::Vector2d(1, 1)) = 0;
+ virtual void end() = 0;
+
+ static IRenderer *createOpenGLRenderer(Common::Point resolution);
+};
+
enum class AnimationFolder {
Animations,
- Maskaras,
- Fondos
+ Masks,
+ Backgrounds
};
-class Animation {
+struct AnimationFrame {
+ Common::Point
+ _center, ///< the center is used for more than just drawing the animation frame
+ _offset; ///< the offset is only used for drawing the animation frame
+ uint32 _duration;
+};
+
+/**
+ * An animation contains one or more sprites which change their position and image during playback.
+ *
+ * Internally there is a single list of images. Every sprite ID is mapped to an index
+ * (via _spriteIndexMapping) which points to:
+ * 1. The fixed image base for that sprite
+ * 2. The image offset for that sprite for the current frame
+ * Image indices are unfortunately one-based
+ *
+ * As fonts are handled very differently they are split into a second class
+ */
+class AnimationBase {
+protected:
+ AnimationBase(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations);
+ ~AnimationBase();
+
+ void load();
+ void freeImages();
+ Graphics::ManagedSurface *readImage(Common::SeekableReadStream &stream) const;
+
+ static constexpr const uint kMaxSpriteIDs = 256;
+ Common::String _fileName;
+ AnimationFolder _folder;
+ bool _isLoaded = false;
+
+ int32 _spriteIndexMapping[kMaxSpriteIDs] = { -1 };
+ Common::Array<uint32>
+ _spriteOffsets, ///< index offset per sprite and animation frame
+ _spriteBases; ///< base index per sprite
+ Common::Array<AnimationFrame> _frames;
+ Common::Array<Graphics::ManagedSurface *> _images; ///< will contain nullptr for fake images
+ Common::Array<Common::Point> _imageOffsets;
+};
+
+/**
+ * Animations prerenders its sprites into a single texture for a set frame.
+ * This prerendering can be customized with a alpha to be premultiplied
+ */
+class Animation : private AnimationBase {
public:
Animation(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations);
+ void load();
+ using AnimationBase::freeImages;
+
+ inline bool isLoaded() const { return _isLoaded; }
+ inline uint frameCount() const { return _frames.size(); }
+ inline uint spriteCount() const { return _spriteBases.size(); }
+ inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
+
+ void draw2D(
+ int32 frameI,
+ Math::Vector2d center,
+ float scale,
+ Math::Angle rotation,
+ BlendMode blendMode,
+ Color color);
+
private:
- Common::String _fileName;
- AnimationFolder _folder;
+ int32 imageIndex(int32 frameI, int32 spriteI) const;
+ Common::Rect spriteBounds(int32 frameI, int32 spriteI) const;
+ Common::Rect frameBounds(int32 frameI) const;
+ Common::Rect maxFrameBounds() const;
+ void prerenderFrame(int32 frameI);
+
+ int32_t _renderedFrameI = -1;
+ uint8 _premultiplyAlpha = 100; ///< in percent [0-100] not [0-255]
+
+ Graphics::ManagedSurface _renderedSurface;
+ Common::ScopedPtr<ITexture> _renderedTexture;
+};
+
+class Font : private AnimationBase {
+
};
+
class Graphic {
public:
Graphic();
@@ -88,11 +224,6 @@ private:
float _camAcceleration = 1.0f;
};
-class IGraphics {
-public:
- virtual ~IGraphics() = default;
-};
-
}
#endif
Commit: 51836a48cd6166c3ee40c19aeedc1ad99825d213
https://github.com/scummvm/scummvm/commit/51836a48cd6166c3ee40c19aeedc1ad99825d213
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add Graphic playback methods
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index e09fe231768..1d8d2fb793e 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -60,9 +60,10 @@ Common::Error AlcachofaEngine::run() {
auto world = new World();
delete world;
- auto animation = new Animation("MORTADELO_ACOSTANDOSE");
- animation->load();
- animation->draw2D(0, Math::Vector2d(512, 300), 1.0f, Math::Angle(), BlendMode::Alpha, { 255, 255, 255, 255 });
+ Graphic graphic;
+ graphic.setAnimation("MORTADELO_ACOSTANDOSE", AnimationFolder::Animations);
+ graphic.loadResources();
+ graphic.start(true);
// Set the engine's debugger console
setDebugger(new Console());
@@ -78,20 +79,15 @@ Common::Error AlcachofaEngine::run() {
int offset = 0;
Graphics::FrameLimiter limiter(g_system, 60);
- int32 frame = 0;
- uint32 nextSecond = g_system->getMillis();
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(e)) {
}
_renderer->begin();
- if (g_system->getMillis() >= nextSecond) {
- frame = (frame + 1) % animation->frameCount();
- nextSecond = g_system->getMillis() + animation->frameDuration(frame);
- }
+ graphic.update();
- animation->draw2D(frame, Math::Vector2d(100, 100), 1.0f, Math::Angle(), BlendMode::Alpha, { 255, 255, 255, 255 });
+ graphic.testDraw();
// Cycle through a simple palette
++offset;
@@ -105,8 +101,6 @@ Common::Error AlcachofaEngine::run() {
limiter.startFrame();
}
- delete animation;
-
return Common::kNoError;
}
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index b77281f9b76..c57ace585f2 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -159,9 +159,9 @@ void MainCharacter::serializeSave(Serializer &serializer) {
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
- _graphic._animation.reset(new Animation(animationFileName, AnimationFolder::Backgrounds));
- _graphic._scale = scale;
- _graphic._order = 59;
+ _graphic.setAnimation(animationFileName, AnimationFolder::Backgrounds);
+ _graphic.scale() = scale;
+ _graphic.order() = 59;
}
FloorColor::FloorColor(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 9230ba4fa0b..15c415f195c 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -110,6 +110,7 @@ void AnimationBase::load() {
frame._offset = readPoint(file);
frame._duration = file.readUint32LE();
_frames.push_back(frame);
+ _totalDuration += frame._duration;
}
_isLoaded = true;
@@ -251,6 +252,15 @@ void Animation::draw2D(int32 frameI, Vector2d center, float scale, Angle rotatio
renderer.quad(center, size, color, rotation, texMin, texMax);
}
+int32 Animation::frameAtTime(uint32 time) const {
+ for (int32 i = 0; (uint)i < _frames.size(); i++) {
+ if (time < _frames[i]._duration)
+ return i;
+ time -= _frames[i]._duration;
+ }
+ return -1;
+}
+
Graphic::Graphic() {
}
@@ -263,18 +273,57 @@ Graphic::Graphic(ReadStream &stream) {
_animation.reset(new Animation(std::move(animationName)));
}
+void Graphic::loadResources() {
+ assert(_animation != nullptr);
+ _animation->load();
+}
+
+void Graphic::freeResources() {
+ _animation.reset();
+}
+
+void Graphic::update() {
+ if (_animation == nullptr || _animation->frameCount() == 0)
+ return;
+
+ const uint32 totalDuration = _animation->totalDuration();
+ uint32 curTime = _lastTime;
+ if (!_isPaused)
+ curTime = g_system->getMillis() - curTime;
+ if (curTime > totalDuration) {
+ if (_isLooping && totalDuration > 0)
+ curTime %= totalDuration;
+ else {
+ pause();
+ _lastTime = totalDuration - 1;
+ }
+ }
+
+ _frameI = _animation->frameAtTime(curTime);
+ assert(_frameI >= 0);
+}
+
void Graphic::start(bool isLooping) {
_isPaused = false;
_isLooping = isLooping;
_lastTime = g_system->getMillis();
}
-void Graphic::stop() {
+void Graphic::pause() {
_isPaused = true;
_isLooping = false;
_lastTime = g_system->getMillis() - _lastTime;
}
+void Graphic::reset() {
+ _frameI = 0;
+ _lastTime = _isPaused ? 0 : g_system->getMillis();
+}
+
+void Graphic::setAnimation(const Common::String &fileName, AnimationFolder folder) {
+ _animation.reset(new Animation(fileName, folder));
+}
+
void Graphic::serializeSave(Serializer &serializer) {
syncPoint(serializer, _center);
serializer.syncAsSint16LE(_scale);
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 987416d6533..6e5d0f20b72 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -31,6 +31,9 @@
namespace Alcachofa {
+static constexpr const int16_t kBaseScale = 300; ///< this number pops up everywhere in the engine
+static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
+
/**
* Because this gets confusing fast, here in tabular form
*
@@ -74,6 +77,9 @@ constexpr const int32 kDirectionCount = 4;
struct Color {
uint8 b, g, r, a;
};
+static constexpr const Color kWhite = { 255, 255, 255, 255 };
+static constexpr const Color kBlack = { 0, 0, 0, 255 };
+static constexpr const Color kClear = { 0, 0, 0, 0 };
class ITexture {
public:
@@ -101,7 +107,7 @@ public:
virtual void quad(
Math::Vector2d center,
Math::Vector2d size,
- Color color = { 255, 255, 255, 255 },
+ Color color = kWhite,
Math::Angle rotation = Math::Angle(),
Math::Vector2d texMin = Math::Vector2d(0, 0),
Math::Vector2d texMax = Math::Vector2d(1, 1)) = 0;
@@ -147,6 +153,7 @@ protected:
Common::String _fileName;
AnimationFolder _folder;
bool _isLoaded = false;
+ uint32 _totalDuration = 0;
int32 _spriteIndexMapping[kMaxSpriteIDs] = { -1 };
Common::Array<uint32>
@@ -172,6 +179,8 @@ public:
inline uint frameCount() const { return _frames.size(); }
inline uint spriteCount() const { return _spriteBases.size(); }
inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
+ inline uint32 totalDuration() const { return _totalDuration; }
+ int32 frameAtTime(uint32 time) const;
void draw2D(
int32 frameI,
@@ -205,22 +214,36 @@ public:
Graphic();
Graphic(Common::ReadStream &stream);
- inline int8 order() const { return _order; }
+ inline int8 &order() { return _order; }
+ inline int16 &scale() { return _scale; }
+ inline Animation &animation() {
+ assert(_animation != nullptr && _animation->isLoaded());
+ return *_animation;
+ }
+ void loadResources();
+ void freeResources();
+ void update();
void start(bool looping);
- void stop();
+ void pause();
+ void reset();
+ void setAnimation(const Common::String &fileName, AnimationFolder folder);
void serializeSave(Common::Serializer &serializer);
-public:
+ inline void testDraw() {
+ animation().draw2D(_frameI, Math::Vector2d(100, 100), 1.0f, Math::Angle(), BlendMode::Alpha, { 255, 255, 255, 255 });
+ }
+
+private:
Common::SharedPtr<Animation> _animation;
Common::Point _center;
- int16 _scale = 300;
+ int16 _scale = kBaseScale;
int8 _order = 0;
-private:
bool _isPaused = true,
_isLooping = true;
- uint32 _lastTime = 0;
+ uint32 _lastTime = 0; ///< either start time or played duration at pause
+ int32 _frameI = -1;
float _camAcceleration = 1.0f;
};
Commit: 1859ad68072ca2a6f78f2f4d4fb85e9a43bdadb2
https://github.com/scummvm/scummvm/commit/1859ad68072ca2a6f78f2f4d4fb85e9a43bdadb2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add DrawQueue and AnimationDrawRequest
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 1d8d2fb793e..a6321123d91 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -30,6 +30,7 @@
#include "common/system.h"
#include "engines/util.h"
#include "graphics/paletteman.h"
+#include "graphics/framelimiter.h"
#include "rooms.h"
@@ -43,7 +44,6 @@ AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDes
}
AlcachofaEngine::~AlcachofaEngine() {
- delete _screen;
}
uint32 AlcachofaEngine::getFeatures() const {
@@ -57,6 +57,7 @@ Common::String AlcachofaEngine::getGameId() const {
Common::Error AlcachofaEngine::run() {
// Initialize 320x200 paletted graphics mode
_renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
+ _drawQueue.reset(new DrawQueue(_renderer.get()));
auto world = new World();
delete world;
@@ -73,31 +74,31 @@ Common::Error AlcachofaEngine::run() {
if (saveSlot != -1)
(void)loadGameState(saveSlot);
- // Simple event handling loop
- byte pal[256 * 3] = { 0 };
Common::Event e;
- int offset = 0;
-
Graphics::FrameLimiter limiter(g_system, 60);
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(e)) {
}
_renderer->begin();
+ _drawQueue->clear();
graphic.update();
+ graphic.center() = { 0, 0 };
+ _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+ graphic.center() = { 100, 0 };
+ _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+ graphic.center() = { 0, 100 };
+ _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+ graphic.center() = { 100, 100 };
+ _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+
+ _drawQueue->draw();
+ _renderer->end();
- graphic.testDraw();
-
- // Cycle through a simple palette
- ++offset;
- for (int i = 0; i < 256; ++i)
- pal[i * 3 + 1] = (i + offset) % 256;
- g_system->getPaletteManager()->setPalette(pal, 0, 256);
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
limiter.delayBeforeSwap();
- _renderer->end();
limiter.startFrame();
}
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index a703d18341b..ade684e18df 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -98,8 +98,8 @@ public:
}
private:
- Graphics::Screen *_screen = nullptr;
Common::ScopedPtr<IRenderer> _renderer;
+ Common::ScopedPtr<DrawQueue> _drawQueue;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 15c415f195c..15f4ffb61fb 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -254,7 +254,7 @@ void Animation::draw2D(int32 frameI, Vector2d center, float scale, Angle rotatio
int32 Animation::frameAtTime(uint32 time) const {
for (int32 i = 0; (uint)i < _frames.size(); i++) {
- if (time < _frames[i]._duration)
+ if (time <= _frames[i]._duration)
return i;
time -= _frames[i]._duration;
}
@@ -295,7 +295,7 @@ void Graphic::update() {
curTime %= totalDuration;
else {
pause();
- _lastTime = totalDuration - 1;
+ curTime = _lastTime = totalDuration - 1;
}
}
@@ -330,7 +330,123 @@ void Graphic::serializeSave(Serializer &serializer) {
serializer.syncAsUint32LE(_lastTime);
serializer.syncAsByte(_isPaused);
serializer.syncAsByte(_isLooping);
- serializer.syncAsFloatLE(_camAcceleration);
+ serializer.syncAsFloatLE(_depthScale);
+}
+
+static int8 shiftAndClampOrder(int8 order) {
+ return MAX<int8>(0, MIN<int8>(kOrderCount - 1, order + kForegroundOrderCount));
+}
+
+IDrawRequest::IDrawRequest(int8 order)
+ : _order(shiftAndClampOrder(order)) {
+}
+
+AnimationDrawRequest::AnimationDrawRequest(Graphic &graphic, bool is3D, BlendMode blendMode, float lodBias)
+ : IDrawRequest(graphic._order)
+ , _is3D(is3D)
+ , _animation(&graphic.animation())
+ , _frameI(graphic._frameI)
+ , _center(graphic._center.x, graphic._center.y)
+ , _scale(graphic._scale * graphic._depthScale)
+ , _color(graphic.color())
+ , _blendMode(blendMode)
+ , _lodBias(lodBias) {
+ assert(_frameI >= 0 && (uint)_frameI < _animation->frameCount());
+}
+
+AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, Vector2d center, int8 order)
+ : IDrawRequest(order)
+ , _is3D(false)
+ , _animation(animation)
+ , _frameI(frameI)
+ , _center(center)
+ , _scale(1.0f)
+ , _color(kWhite)
+ , _blendMode(BlendMode::AdditiveAlpha)
+ , _lodBias(0.0f) {
+ assert(animation != nullptr && animation->isLoaded());
+ assert(_frameI >= 0 && (uint)_frameI < _animation->frameCount());
+}
+
+void AnimationDrawRequest::draw() {
+ _animation->draw2D(_frameI, _center, _scale * kInvBaseScale, Angle(), _blendMode, _color);
+}
+
+DrawQueue::DrawQueue(IRenderer *renderer)
+ : _renderer(renderer)
+ , _allocator(1024) {
+ assert(renderer != nullptr);
+}
+
+void DrawQueue::clear() {
+ memset(_requestsPerOrderCount, 0, sizeof(_requestsPerOrderCount));
+ memset(_lodBiasPerOrder, 0, sizeof(_lodBiasPerOrder));
+}
+
+void DrawQueue::addRequest(IDrawRequest *drawRequest) {
+ assert(drawRequest != nullptr && drawRequest->order() >= 0 && drawRequest->order() < kOrderCount);
+ auto order = drawRequest->order();
+ if (_requestsPerOrderCount[order] < kMaxDrawRequestsPerOrder)
+ _requestsPerOrder[order][_requestsPerOrderCount[order]++] = drawRequest;
+ else
+ error("Too many draw requests in order %d", order);
+}
+
+void DrawQueue::setLodBias(int8 orderFrom, int8 orderTo, float newLodBias) {
+ orderFrom = shiftAndClampOrder(orderFrom);
+ orderTo = shiftAndClampOrder(orderTo);
+ if (orderFrom <= orderTo) {
+ Common::fill(_lodBiasPerOrder + orderFrom, _lodBiasPerOrder + orderTo + 1, newLodBias);
+ }
+}
+
+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();
+ }
+ }
+ _allocator.deallocateAll();
+}
+
+BumpAllocator::BumpAllocator(size_t pageSize) : _pageSize(pageSize) {
+ allocatePage();
+}
+
+BumpAllocator::~BumpAllocator() {
+ for (auto page : _pages)
+ free(page);
+}
+
+void *BumpAllocator::allocateRaw(size_t size, size_t align) {
+ assert(size <= _pageSize);
+ uintptr_t page = (uintptr_t)_pages[_pageI];
+ uintptr_t top = page + _used;
+ top = top + align - 1;
+ top = top - (top % align);
+ if (page + _pageSize - top >= size) {
+ _used = top + size - page;
+ return (void *)top;
+ }
+
+ _pageI++;
+ if (_pageI >= _pages.size())
+ allocatePage();
+ return allocateRaw(size, align);
+}
+
+void BumpAllocator::allocatePage() {
+ auto page = malloc(_pageSize);
+ if (page == nullptr)
+ error("Out of memory in BumpAllocator");
+ _pages.push_back(page);
+}
+
+void BumpAllocator::deallocateAll() {
+ _pageI = 0;
+ _used = 0;
}
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 6e5d0f20b72..618ec410961 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -73,6 +73,8 @@ enum class Direction {
};
constexpr const int32 kDirectionCount = 4;
+constexpr const int8 kOrderCount = 70;
+constexpr const int8 kForegroundOrderCount = 10;
struct Color {
uint8 b, g, r, a;
@@ -214,8 +216,10 @@ public:
Graphic();
Graphic(Common::ReadStream &stream);
+ inline Common::Point ¢er() { return _center; }
inline int8 &order() { return _order; }
inline int16 &scale() { return _scale; }
+ inline Color &color() { return _color; }
inline Animation &animation() {
assert(_animation != nullptr && _animation->isLoaded());
return *_animation;
@@ -230,21 +234,113 @@ public:
void setAnimation(const Common::String &fileName, AnimationFolder folder);
void serializeSave(Common::Serializer &serializer);
- inline void testDraw() {
- animation().draw2D(_frameI, Math::Vector2d(100, 100), 1.0f, Math::Angle(), BlendMode::Alpha, { 255, 255, 255, 255 });
- }
-
private:
+ friend class AnimationDrawRequest;
Common::SharedPtr<Animation> _animation;
Common::Point _center;
int16 _scale = kBaseScale;
int8 _order = 0;
+ Color _color = kWhite;
bool _isPaused = true,
_isLooping = true;
uint32 _lastTime = 0; ///< either start time or played duration at pause
int32 _frameI = -1;
- float _camAcceleration = 1.0f;
+ float _depthScale = 1.0f;
+};
+
+enum class DrawRequestType {
+ Animation2D,
+ Animation3D,
+ AnimationTiled,
+ Rectangle,
+ FadeToBlack,
+ FadeToWhite,
+ CrossFade,
+ Text
+};
+
+class IDrawRequest {
+public:
+ IDrawRequest(int8 order);
+ virtual ~IDrawRequest() = default;
+
+ inline int8 order() const { return _order; }
+ virtual void draw() = 0;
+
+private:
+ const int8 _order;
+};
+
+class AnimationDrawRequest : public IDrawRequest {
+public:
+ AnimationDrawRequest(
+ Graphic &graphic,
+ bool is3D,
+ BlendMode blendMode,
+ float lodBias = 0.0f);
+ AnimationDrawRequest(
+ Animation *animation,
+ int32 frameI,
+ Math::Vector2d center,
+ int8 order
+ );
+
+ virtual void draw() override;
+
+private:
+ bool _is3D;
+ Animation *_animation;
+ int32 _frameI;
+ Math::Vector2d _center;
+ float _scale;
+ Color _color;
+ BlendMode _blendMode;
+ float _lodBias;
+};
+
+class BumpAllocator {
+public:
+ BumpAllocator(size_t pageSize);
+ ~BumpAllocator();
+
+ template<typename T, typename... Args>
+ inline T *allocate(Args&&... args) {
+ return new(allocateRaw(sizeof(T), alignof(T))) T(Common::forward<Args>(args)...);
+ }
+ void *allocateRaw(size_t size, size_t align);
+ void deallocateAll();
+
+private:
+ void allocatePage();
+
+ const size_t _pageSize;
+ size_t _pageI = 0, _used = 0;
+ Common::Array<void *> _pages;
+};
+
+class DrawQueue {
+public:
+ DrawQueue(IRenderer *renderer);
+
+ template<typename T, typename... Args>
+ inline void add(Args&&... args) {
+ addRequest(_allocator.allocate<T>(Common::forward<Args>(args)...));
+ }
+
+ void clear();
+ void setLodBias(int8 orderFrom, int8 orderTo, float newLodBias);
+ void draw();
+
+private:
+ void addRequest(IDrawRequest *drawRequest);
+
+ static constexpr const uint kMaxDrawRequestsPerOrder = 50;
+ IRenderer *const _renderer;
+ BumpAllocator _allocator;
+ IDrawRequest *_requestsPerOrder[kOrderCount][kMaxDrawRequestsPerOrder] = { 0 };
+ uint8 _requestsPerOrderCount[kOrderCount] = { 0 };
+ float _lodBiasPerOrder[kOrderCount] = { 0 };
};
}
Commit: 15915b540732e8b0a18b7442f1b82eab18a0ace0
https://github.com/scummvm/scummvm/commit/15915b540732e8b0a18b7442f1b82eab18a0ace0
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add camera and 3D draw requests
Changed paths:
A engines/alcachofa/camera.cpp
A engines/alcachofa/camera.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index a6321123d91..3096263844e 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -34,6 +34,8 @@
#include "rooms.h"
+using namespace Math;
+
namespace Alcachofa {
AlcachofaEngine *g_engine;
@@ -55,12 +57,10 @@ Common::String AlcachofaEngine::getGameId() const {
}
Common::Error AlcachofaEngine::run() {
- // Initialize 320x200 paletted graphics mode
_renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
_drawQueue.reset(new DrawQueue(_renderer.get()));
+ _world.reset(new World());
- auto world = new World();
- delete world;
Graphic graphic;
graphic.setAnimation("MORTADELO_ACOSTANDOSE", AnimationFolder::Animations);
graphic.loadResources();
@@ -82,17 +82,12 @@ Common::Error AlcachofaEngine::run() {
_renderer->begin();
_drawQueue->clear();
+ _camera.shake() = Vector2d();
graphic.update();
- graphic.center() = { 0, 0 };
- _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
- graphic.center() = { 100, 0 };
- _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
- graphic.center() = { 0, 100 };
- _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
- graphic.center() = { 100, 100 };
- _drawQueue->add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+ drawQueue().add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+ _camera.update();
_drawQueue->draw();
_renderer->end();
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index ade684e18df..54a8cf57837 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -35,10 +35,13 @@
#include "graphics/screen.h"
#include "alcachofa/detection.h"
-#include "alcachofa/graphics.h"
+#include "alcachofa/camera.h"
namespace Alcachofa {
+class IRenderer;
+class DrawQueue;
+class World;
struct AlcachofaGameDescription;
class AlcachofaEngine : public Engine {
@@ -52,7 +55,10 @@ public:
AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc);
~AlcachofaEngine() override;
- inline IRenderer &renderer() const { return *_renderer; }
+ inline IRenderer &renderer() { return *_renderer; }
+ inline DrawQueue &drawQueue() { return *_drawQueue; }
+ inline Camera &camera() { return _camera; }
+ inline World &world() { return *_world; }
uint32 getFeatures() const;
@@ -100,6 +106,8 @@ public:
private:
Common::ScopedPtr<IRenderer> _renderer;
Common::ScopedPtr<DrawQueue> _drawQueue;
+ Common::ScopedPtr<World> _world;
+ Camera _camera;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
new file mode 100644
index 00000000000..7798f52a004
--- /dev/null
+++ b/engines/alcachofa/camera.cpp
@@ -0,0 +1,132 @@
+/* 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 "camera.h"
+#include "alcachofa.h"
+
+#include "common/system.h"
+#include "math/vector4d.h"
+
+using namespace Common;
+using namespace Math;
+
+namespace Alcachofa {
+
+void Camera::setRoomBounds(Rect bgBounds, float bgScale) {
+ float scaleFactor = 1 - bgScale * kInvBaseScale;
+ _roomMin = Vector2d(
+ g_system->getWidth() / 2 * scaleFactor,
+ g_system->getHeight() / 2 * scaleFactor);
+ _roomMax = _roomMin + Vector2d(
+ bgBounds.width() * bgScale * kInvBaseScale,
+ bgBounds.height() * bgScale * kInvBaseScale);
+}
+
+static Matrix4 scaleMatrix(float scale) {
+ Matrix4 m;
+ m(0, 0) = scale;
+ m(1, 1) = scale;
+ m(2, 2) = scale;
+ return m;
+}
+
+void Camera::setupMatricesAround(Vector3d center) {
+ Matrix4 matTemp;
+ matTemp.buildAroundZ(_rotation);
+ _mat3Dto2D.setToIdentity();
+ _mat3Dto2D.translate(-center);
+ _mat3Dto2D = matTemp * _mat3Dto2D;
+ _mat3Dto2D = _mat3Dto2D * scaleMatrix(_scale);
+
+ _mat2Dto3D.setToIdentity();
+ _mat2Dto3D.translate(center);
+ matTemp.buildAroundZ(-_rotation);
+ matTemp = scaleMatrix(1 / _scale) * matTemp;
+ _mat2Dto3D = matTemp * _mat2Dto3D;
+}
+
+void minmax(Vector3d &min, Vector3d &max, Vector3d val)
+{
+ min.set(
+ MIN(min.x(), val.x()),
+ MIN(min.y(), val.y()),
+ MIN(min.z(), val.z()));
+ max.set(
+ MAX(max.x(), val.x()),
+ MAX(max.y(), val.y()),
+ MAX(max.z(), val.z()));
+}
+
+Vector3d Camera::setAppliedCenter(Vector3d center) {
+ setupMatricesAround(center);
+ if (true) { // g_engine->script().getVariable("EncuadrarCamara")
+ const float screenW = g_system->getWidth(), screenH = g_system->getHeight();
+ Vector3d min, max;
+ min = max = transform2Dto3D(Vector3d(0, 0, _roomScale));
+ minmax(min, max, transform2Dto3D(Vector3d(screenW, 0, _roomScale)));
+ minmax(min, max, transform2Dto3D(Vector3d(screenW, screenH, _roomScale)));
+ minmax(min, max, transform2Dto3D(Vector3d(0, screenH, _roomScale)));
+ center.x() += MAX(0.0f, _roomMin.getX() - min.x());
+ center.y() += MAX(0.0f, _roomMin.getY() - min.y());
+ center.x() -= MAX(0.0f, max.x() - _roomMax.getX());
+ center.y() -= MAX(0.0f, max.y() - _roomMax.getY());
+ setupMatricesAround(center);
+ }
+ return _appliedCenter = center;
+}
+
+Vector3d Camera::transform2Dto3D(Vector3d v3d) const {
+ // if this looks like normal 3D math to *someone* please contact.
+ Vector4d vh;
+ vh.w() = 1.0f;
+ vh.z() = v3d.z() - _usedCenter.z();
+ vh.y() = (v3d.y() - g_system->getHeight() * 0.5f) * vh.z() * kInvBaseScale;
+ vh.x() = (v3d.x() - g_system->getWidth() * 0.5f) * vh.z() * kInvBaseScale;
+ vh = _mat2Dto3D * vh;
+ return Vector3d(vh.x(), vh.y(), 0.0f);
+}
+
+Vector3d Camera::transform3Dto2D(Vector3d v2d) const {
+ // I swear there is a better way than this. This is stupid. But it is original.
+ float depthScale = v2d.z() * kInvBaseScale;
+ Vector4d vh;
+ vh.x() = v2d.x() * depthScale + (1 - depthScale) * g_system->getWidth() * 0.5f;
+ vh.y() = v2d.y() * depthScale + (1 - depthScale) * g_system->getHeight() * 0.5f;
+ vh.z() = v2d.z();
+ vh.w() = 1.0f;
+ vh = _mat3Dto2D * vh;
+ return Vector3d(
+ g_system->getWidth() * 0.5f + vh.x() * kBaseScale / vh.z(),
+ g_system->getHeight() * 0.5f + vh.y() * kBaseScale / vh.z(),
+ _scale * kBaseScale / vh.z());
+}
+
+void Camera::update() {
+ // original would be some smoothing of delta times, let's not.
+ uint32 now = g_system->getMillis();
+ float deltaTime = now - _lastUpdateTime;
+ deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
+ _lastUpdateTime = now;
+
+ setAppliedCenter(_usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
+}
+
+}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
new file mode 100644
index 00000000000..811fa68b0cc
--- /dev/null
+++ b/engines/alcachofa/camera.h
@@ -0,0 +1,74 @@
+/* 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/>.
+ *
+ */
+
+#ifndef CAMERA_H
+#define CAMERA_H
+
+#include "common/serializer.h"
+#include "common/rect.h"
+#include "math/vector2d.h"
+#include "math/vector3d.h"
+#include "math/matrix4.h"
+
+namespace Alcachofa {
+
+class Personaje;
+
+static constexpr const int16_t kBaseScale = 300; ///< this number pops up everywhere in the engine
+static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
+
+class Camera {
+public:
+ inline Math::Angle rotation() const { return _rotation; }
+ inline Math::Vector2d &shake() { return _shake; }
+
+ void update();
+ Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
+ Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
+ void setRoomBounds(Common::Rect bgBounds, float bgScale);
+
+private:
+ static constexpr const float kAccelerationThreshold = 2.89062f;
+ static constexpr const float kAcceleration = 3.94922f;
+
+ Math::Vector3d setAppliedCenter(Math::Vector3d center);
+ void setupMatricesAround(Math::Vector3d center);
+
+ uint32 _lastUpdateTime = 0;
+ float
+ _scale = 1.0f,
+ _roomScale = 1.0f;
+ Math::Angle _rotation;
+ Math::Vector2d
+ _roomMin = Math::Vector2d(-10000, -10000),
+ _roomMax = Math::Vector2d(10000, 10000),
+ _shake;
+ Math::Vector3d
+ _usedCenter,
+ _appliedCenter;
+ Math::Matrix4
+ _mat3Dto2D,
+ _mat2Dto3D;
+};
+
+}
+
+#endif // CAMERA_H
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 15f4ffb61fb..ca735402080 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -205,6 +205,23 @@ Rect Animation::maxFrameBounds() const {
return bounds;
}
+Math::Vector2d Animation::totalFrameOffset(int32 frameI) const {
+ const auto &frame = _frames[frameI];
+ const auto bounds = frameBounds(frameI);
+ return Vector2d(
+ bounds.left - frame._center.x + frame._offset.x,
+ bounds.top - frame._center.y + frame._offset.y);
+}
+
+int32 Animation::frameAtTime(uint32 time) const {
+ for (int32 i = 0; (uint)i < _frames.size(); i++) {
+ if (time <= _frames[i]._duration)
+ return i;
+ time -= _frames[i]._duration;
+ }
+ return -1;
+}
+
void Animation::prerenderFrame(int32 frameI) {
assert(frameI >= 0 && (uint)frameI < frameCount());
if (frameI == _renderedFrameI)
@@ -232,33 +249,46 @@ void Animation::prerenderFrame(int32 frameI) {
_renderedFrameI = frameI;
}
-void Animation::draw2D(int32 frameI, Vector2d center, float scale, Angle rotation, BlendMode blendMode, Color color) {
+void Animation::draw2D(int32 frameI, Vector2d center, 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);
Vector2d size(bounds.width(), bounds.height());
- Vector2d offset(
- bounds.left - _frames[frameI]._center.x + _frames[frameI]._offset.x,
- bounds.top - _frames[frameI]._center.y + _frames[frameI]._offset.y);
- center += offset * scale;
+ center += totalFrameOffset(frameI) * scale;
size *= scale;
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
- //renderer.setTexture(nullptr);
renderer.setBlendMode(blendMode);
- renderer.quad(center, size, color, rotation, texMin, texMax);
+ renderer.quad(center, size, color, Angle(), texMin, texMax);
}
-int32 Animation::frameAtTime(uint32 time) const {
- for (int32 i = 0; (uint)i < _frames.size(); i++) {
- if (time <= _frames[i]._duration)
- return i;
- time -= _frames[i]._duration;
- }
- return -1;
+static Vector3d as3D(const Vector2d &v) {
+ return Vector3d(v.getX(), v.getY(), 0.0f);
+}
+
+static Vector2d as2D(const Vector3d &v) {
+ return Vector2d(v.x(), v.y());
+}
+
+void Animation::draw3D(int32 frameI, Vector3d center, 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);
+
+ center += as3D(totalFrameOffset(frameI)) * scale;
+ center = g_engine->camera().transform3Dto2D(center);
+ const auto rotation = -g_engine->camera().rotation();
+ Vector2d size(bounds.width(), bounds.height());
+ size *= scale * center.z();
+
+ auto &renderer = g_engine->renderer();
+ renderer.setTexture(_renderedTexture.get());
+ renderer.setBlendMode(blendMode);
+ renderer.quad(as2D(center), size, color, rotation, texMin, texMax);
}
Graphic::Graphic() {
@@ -346,7 +376,7 @@ AnimationDrawRequest::AnimationDrawRequest(Graphic &graphic, bool is3D, BlendMod
, _is3D(is3D)
, _animation(&graphic.animation())
, _frameI(graphic._frameI)
- , _center(graphic._center.x, graphic._center.y)
+ , _center(graphic._center.x, graphic._center.y, graphic._scale)
, _scale(graphic._scale * graphic._depthScale)
, _color(graphic.color())
, _blendMode(blendMode)
@@ -359,7 +389,7 @@ AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, V
, _is3D(false)
, _animation(animation)
, _frameI(frameI)
- , _center(center)
+ , _center(as3D(center))
, _scale(1.0f)
, _color(kWhite)
, _blendMode(BlendMode::AdditiveAlpha)
@@ -369,7 +399,10 @@ AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, V
}
void AnimationDrawRequest::draw() {
- _animation->draw2D(_frameI, _center, _scale * kInvBaseScale, Angle(), _blendMode, _color);
+ if (_is3D)
+ _animation->draw3D(_frameI, _center, _scale * kInvBaseScale, _blendMode, _color);
+ else
+ _animation->draw2D(_frameI, as2D(_center), _scale * kInvBaseScale, _blendMode, _color);
}
DrawQueue::DrawQueue(IRenderer *renderer)
@@ -431,6 +464,7 @@ void *BumpAllocator::allocateRaw(size_t size, size_t align) {
return (void *)top;
}
+ _used = 0;
_pageI++;
if (_pageI >= _pages.size())
allocatePage();
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 618ec410961..a3bfeed7804 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -29,10 +29,9 @@
#include "math/vector2d.h"
#include "graphics/managed_surface.h"
-namespace Alcachofa {
+#include "camera.h"
-static constexpr const int16_t kBaseScale = 300; ///< this number pops up everywhere in the engine
-static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
+namespace Alcachofa {
/**
* Because this gets confusing fast, here in tabular form
@@ -188,7 +187,12 @@ public:
int32 frameI,
Math::Vector2d center,
float scale,
- Math::Angle rotation,
+ BlendMode blendMode,
+ Color color);
+ void draw3D(
+ int32 frameI,
+ Math::Vector3d center,
+ float scale,
BlendMode blendMode,
Color color);
@@ -197,6 +201,7 @@ private:
Common::Rect spriteBounds(int32 frameI, int32 spriteI) const;
Common::Rect frameBounds(int32 frameI) const;
Common::Rect maxFrameBounds() const;
+ Math::Vector2d totalFrameOffset(int32 frameI) const;
void prerenderFrame(int32 frameI);
int32_t _renderedFrameI = -1;
@@ -210,7 +215,6 @@ class Font : private AnimationBase {
};
-
class Graphic {
public:
Graphic();
@@ -292,7 +296,7 @@ private:
bool _is3D;
Animation *_animation;
int32 _frameI;
- Math::Vector2d _center;
+ Math::Vector3d _center;
float _scale;
Color _color;
BlendMode _blendMode;
Commit: 872e5b371524cbc4ce200729ab06370b5f1504c6
https://github.com/scummvm/scummvm/commit/872e5b371524cbc4ce200729ab06370b5f1504c6
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add basic room loop and GraphicObject drawing
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 3096263844e..d96f822ee48 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -61,10 +61,12 @@ Common::Error AlcachofaEngine::run() {
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
- Graphic graphic;
- graphic.setAnimation("MORTADELO_ACOSTANDOSE", AnimationFolder::Animations);
- graphic.loadResources();
- graphic.start(true);
+ world().globalRoom().loadResources();
+
+ auto room = world().getRoomByName("CASA_FREDDY_ARRIBA");
+ assert(room != nullptr);
+ world().currentRoom() = room;
+ room->loadResources();
// Set the engine's debugger console
setDebugger(new Console());
@@ -84,11 +86,8 @@ Common::Error AlcachofaEngine::run() {
_drawQueue->clear();
_camera.shake() = Vector2d();
- graphic.update();
- drawQueue().add<AnimationDrawRequest>(graphic, false, BlendMode::AdditiveAlpha);
+ world().currentRoom()->update();
- _camera.update();
- _drawQueue->draw();
_renderer->end();
// Delay for a bit. All events loops should have a delay
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index c57ace585f2..55217e81faa 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -82,7 +82,7 @@ void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object)
else {
object = room()->getObjectByName(name);
if (object == nullptr)
- object = room()->world()->getObjectByName(name);
+ object = room()->world().getObjectByName(name);
if (object == nullptr)
error("Invalid object name \"%s\" saved for \"%s\" in \"%s\"",
name.c_str(), this->name().c_str(), room()->name().c_str());
@@ -140,7 +140,7 @@ void MainCharacter::serializeSave(Serializer &serializer) {
String roomName = room()->name();
serializer.syncString(roomName);
if (serializer.isLoading()) {
- room() = room()->world()->getRoomByName(roomName);
+ room() = room()->world().getRoomByName(roomName);
if (room() == nullptr)
error("Invalid room name \"%s\" saved for \"%s\"", roomName.c_str(), name().c_str());
}
@@ -159,6 +159,7 @@ void MainCharacter::serializeSave(Serializer &serializer) {
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
+ toggle(true);
_graphic.setAnimation(animationFileName, AnimationFolder::Backgrounds);
_graphic.scale() = scale;
_graphic.order() = 59;
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index bc8a965c66c..cf89e0bc5df 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -22,6 +22,7 @@
#include "objects.h"
#include "rooms.h"
#include "stream-helper.h"
+#include "alcachofa.h"
#include "common/system.h"
@@ -47,7 +48,7 @@ void ObjectBase::toggle(bool isEnabled) {
_isEnabled = isEnabled;
}
-void ObjectBase::render() {
+void ObjectBase::draw() {
}
void ObjectBase::update() {
@@ -86,10 +87,29 @@ GraphicObject::GraphicObject(Room *room, ReadStream &stream)
GraphicObject::GraphicObject(Room *room, const char *name)
: ObjectBase(room, name)
- , _type(GraphicObjectType::Type0)
+ , _type(GraphicObjectType::Normal)
, _posterizeAlpha(0) {
}
+void GraphicObject::draw() {
+ if (!isEnabled())
+ return;
+ const BlendMode blendMode = _type == GraphicObjectType::Alpha
+ ? BlendMode::Alpha
+ : BlendMode::AdditiveAlpha;
+ const bool is3D = room() == &g_engine->world().inventory();
+ _graphic.update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(_graphic, is3D, blendMode);
+}
+
+void GraphicObject::loadResources() {
+ _graphic.loadResources();
+}
+
+void GraphicObject::freeResources() {
+ _graphic.freeResources();
+}
+
void GraphicObject::serializeSave(Serializer &serializer) {
ObjectBase::serializeSave(serializer);
_graphic.serializeSave(serializer);
@@ -108,6 +128,9 @@ ShiftingGraphicObject::ShiftingGraphicObject(Room *room, ReadStream &stream)
_startTime = g_system->getMillis();
}
+void ShiftingGraphicObject::draw() {
+}
+
ShapeObject::ShapeObject(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream)
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index ca735402080..e26b08d4cb0 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -320,8 +320,8 @@ void Graphic::update() {
uint32 curTime = _lastTime;
if (!_isPaused)
curTime = g_system->getMillis() - curTime;
- if (curTime > totalDuration) {
- if (_isLooping && totalDuration > 0)
+ if (curTime > totalDuration && totalDuration > 0) {
+ if (_isLooping)
curTime %= totalDuration;
else {
pause();
@@ -329,7 +329,7 @@ void Graphic::update() {
}
}
- _frameI = _animation->frameAtTime(curTime);
+ _frameI = totalDuration == 0 ? 0 : _animation->frameAtTime(curTime);
assert(_frameI >= 0);
}
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index bf5797e71f6..dbe27be262d 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -44,7 +44,7 @@ public:
inline bool isEnabled() const { return _isEnabled; }
virtual void toggle(bool isEnabled);
- virtual void render();
+ virtual void draw();
virtual void update();
virtual void loadResources();
virtual void freeResources();
@@ -72,9 +72,9 @@ private:
enum class GraphicObjectType : byte
{
- Type0,
- Type1,
- Type2
+ Normal,
+ NormalPosterize, // the posterization is not actually applied in the original engine
+ Alpha
};
class GraphicObject : public ObjectBase {
@@ -83,6 +83,9 @@ public:
GraphicObject(Room *room, Common::ReadStream &stream);
virtual ~GraphicObject() override = default;
+ virtual void draw() override;
+ virtual void loadResources() override;
+ virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
@@ -99,6 +102,8 @@ public:
static constexpr const char *kClassName = "CObjetoGraficoMuare";
ShiftingGraphicObject(Room *room, Common::ReadStream &stream);
+ virtual void draw() override;
+
private:
Common::Point _pos, _size;
Math::Vector2d _texShift;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0814d9c9934..6d133cf530d 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "alcachofa.h"
#include "rooms.h"
#include "stream-helper.h"
@@ -103,7 +104,8 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
_objects.push_back(readRoomObject(this, stream));
objectSize = stream.readUint32LE();
}
- _objects.push_back(new Background(this, _name, backgroundScale));
+ if (!_name.equalsIgnoreCase("Global"))
+ _objects.push_back(new Background(this, _name, backgroundScale));
if (!_floors[0].empty())
_activeFloorI = 0;
@@ -122,6 +124,31 @@ ObjectBase *Room::getObjectByName(const Common::String &name) const {
return nullptr;
}
+void Room::update() {
+ if (world().currentRoom() == this)
+ updateObjects();
+ if (world().currentRoom() == this) {
+ g_engine->camera().update();
+ drawObjects();
+ world().globalRoom().drawObjects();
+ // TODO: Draw black borders
+ g_engine->drawQueue().draw();
+ }
+}
+
+void Room::updateObjects() {
+ for (auto *object : _objects) {
+ object->update();
+ if (world().currentRoom() != this)
+ return;
+ }
+}
+
+void Room::drawObjects() {
+ for (auto *object : _objects)
+ object->draw();
+}
+
void Room::loadResources() {
for (auto *object : _objects)
object->loadResources();
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index e311bc9362a..0d95142cf7e 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -34,17 +34,19 @@ public:
Room(World *world, Common::ReadStream &stream);
virtual ~Room();
- inline World *world() { return _world; }
+ inline World &world() { return *_world; }
inline const Common::String &name() const { return _name; }
- ObjectBase *getObjectByName(const Common::String &name) const;
-
+ void update();
virtual void loadResources();
virtual void freeResources();
virtual void serializeSave(Common::Serializer &serializer);
+ ObjectBase *getObjectByName(const Common::String &name) const;
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
+ void updateObjects();
+ void drawObjects();
World *_world;
Common::String _name;
Commit: c5e969cc8a2870f457f3565418e3fadde812ae14
https://github.com/scummvm/scummvm/commit/c5e969cc8a2870f457f3565418e3fadde812ae14
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:44+02:00
Commit Message:
ALCACHOFA: Add SpecialEffectGraphicObject rendering
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 7798f52a004..67870f4434c 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -93,24 +93,24 @@ Vector3d Camera::setAppliedCenter(Vector3d center) {
return _appliedCenter = center;
}
-Vector3d Camera::transform2Dto3D(Vector3d v3d) const {
+Vector3d Camera::transform2Dto3D(Vector3d v2d) const {
// if this looks like normal 3D math to *someone* please contact.
Vector4d vh;
vh.w() = 1.0f;
- vh.z() = v3d.z() - _usedCenter.z();
- vh.y() = (v3d.y() - g_system->getHeight() * 0.5f) * vh.z() * kInvBaseScale;
- vh.x() = (v3d.x() - g_system->getWidth() * 0.5f) * vh.z() * kInvBaseScale;
+ vh.z() = v2d.z() - _usedCenter.z();
+ vh.y() = (v2d.y() - g_system->getHeight() * 0.5f) * vh.z() * kInvBaseScale;
+ vh.x() = (v2d.x() - g_system->getWidth() * 0.5f) * vh.z() * kInvBaseScale;
vh = _mat2Dto3D * vh;
return Vector3d(vh.x(), vh.y(), 0.0f);
}
-Vector3d Camera::transform3Dto2D(Vector3d v2d) const {
+Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
// I swear there is a better way than this. This is stupid. But it is original.
- float depthScale = v2d.z() * kInvBaseScale;
+ float depthScale = v3d.z() * kInvBaseScale;
Vector4d vh;
- vh.x() = v2d.x() * depthScale + (1 - depthScale) * g_system->getWidth() * 0.5f;
- vh.y() = v2d.y() * depthScale + (1 - depthScale) * g_system->getHeight() * 0.5f;
- vh.z() = v2d.z();
+ vh.x() = v3d.x() * depthScale + (1 - depthScale) * g_system->getWidth() * 0.5f;
+ vh.y() = v3d.y() * depthScale + (1 - depthScale) * g_system->getHeight() * 0.5f;
+ vh.z() = v3d.z();
vh.w() = 1.0f;
vh = _mat3Dto2D * vh;
return Vector3d(
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 811fa68b0cc..520655c558f 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -62,7 +62,7 @@ private:
_roomMax = Math::Vector2d(10000, 10000),
_shake;
Math::Vector3d
- _usedCenter,
+ _usedCenter = Math::Vector3d(512, 384, 0),
_appliedCenter;
Math::Matrix4
_mat3Dto2D,
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index cf89e0bc5df..5189793ec2c 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -94,10 +94,10 @@ GraphicObject::GraphicObject(Room *room, const char *name)
void GraphicObject::draw() {
if (!isEnabled())
return;
- const BlendMode blendMode = _type == GraphicObjectType::Alpha
+ const BlendMode blendMode = _type == GraphicObjectType::Effect
? BlendMode::Alpha
: BlendMode::AdditiveAlpha;
- const bool is3D = room() == &g_engine->world().inventory();
+ const bool is3D = room() != &g_engine->world().inventory();
_graphic.update();
g_engine->drawQueue().add<AnimationDrawRequest>(_graphic, is3D, blendMode);
}
@@ -119,16 +119,30 @@ Graphic *GraphicObject::graphic() {
return &_graphic;
}
-ShiftingGraphicObject::ShiftingGraphicObject(Room *room, ReadStream &stream)
+SpecialEffectObject::SpecialEffectObject(Room *room, ReadStream &stream)
: GraphicObject(room, stream) {
- _pos = Shape(stream).firstPoint();
- _size = Shape(stream).firstPoint();
- _texShift.setX(stream.readSint32LE() / 256.0f);
- _texShift.setY(stream.readSint32LE() / 256.0f);
- _startTime = g_system->getMillis();
+ _topLeft = Shape(stream).firstPoint();
+ _bottomRight = Shape(stream).firstPoint();
+ _texShift.setX(stream.readSint32LE());
+ _texShift.setY(stream.readSint32LE());
+ _texShift *= kShiftSpeed;
}
-void ShiftingGraphicObject::draw() {
+void SpecialEffectObject::draw() {
+ if (!isEnabled()) // TODO: Add high quality check
+ return;
+ const auto texOffset = g_system->getMillis() * 0.001f * _texShift;
+ const BlendMode blendMode = _type == GraphicObjectType::Effect
+ ? BlendMode::Additive
+ : BlendMode::AdditiveAlpha;
+ Point topLeft = _topLeft, bottomRight = _bottomRight;
+ if (topLeft.x == bottomRight.x || topLeft.y == bottomRight.y) {
+ topLeft = _graphic.center();
+ bottomRight = topLeft + _graphic.animation().imageSize(0);
+ }
+
+ _graphic.update();
+ g_engine->drawQueue().add<SpecialEffectDrawRequest>(_graphic, topLeft, bottomRight, texOffset, blendMode);
}
ShapeObject::ShapeObject(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index e26b08d4cb0..43b927f6ded 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -222,6 +222,11 @@ int32 Animation::frameAtTime(uint32 time) const {
return -1;
}
+Point Animation::imageSize(int32 imageI) const {
+ auto image = _images[imageI];
+ return image == nullptr ? Point() : Point(image->w, image->h);
+}
+
void Animation::prerenderFrame(int32 frameI) {
assert(frameI >= 0 && (uint)frameI < frameCount());
if (frameI == _renderedFrameI)
@@ -291,6 +296,27 @@ void Animation::draw3D(int32 frameI, Vector3d center, float scale, BlendMode ble
renderer.quad(as2D(center), size, color, rotation, texMin, texMax);
}
+void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d tiling, Vector2d texOffset, BlendMode blendMode) {
+ prerenderFrame(frameI);
+ auto bounds = frameBounds(frameI);
+ Vector2d texMin(0, 0);
+ Vector2d texMax(tiling.getX() / _renderedSurface.w, tiling.getY() / _renderedSurface.h);
+
+ topLeft += as3D(totalFrameOffset(frameI));
+ topLeft = g_engine->camera().transform3Dto2D(topLeft);
+ const auto rotation = -g_engine->camera().rotation();
+ Vector2d size(bounds.width(), bounds.height());
+ size *= topLeft.z();
+
+ if (abs(tiling.getX()) > epsilon)
+ size = size * texMax;
+
+ auto &renderer = g_engine->renderer();
+ renderer.setTexture(_renderedTexture.get());
+ renderer.setBlendMode(blendMode);
+ renderer.quad(as2D(topLeft), size, kWhite, rotation, texMin + texOffset, texMax + texOffset);
+}
+
Graphic::Graphic() {
}
@@ -405,6 +431,21 @@ void AnimationDrawRequest::draw() {
_animation->draw2D(_frameI, as2D(_center), _scale * kInvBaseScale, _blendMode, _color);
}
+SpecialEffectDrawRequest::SpecialEffectDrawRequest(Graphic &graphic, Point topLeft, Point bottomRight, Vector2d texOffset, BlendMode blendMode)
+ : IDrawRequest(graphic._order)
+ , _animation(&graphic.animation())
+ , _frameI(graphic._frameI)
+ , _topLeft(topLeft.x, topLeft.y, graphic._scale)
+ , _tiling(bottomRight.x - topLeft.x, bottomRight.y - topLeft.y)
+ , _texOffset(texOffset)
+ , _blendMode(blendMode) {
+ assert(_frameI >= 0 && (uint)_frameI < _animation->frameCount());
+}
+
+void SpecialEffectDrawRequest::draw() {
+ _animation->drawEffect(_frameI, _topLeft, _tiling, _texOffset, _blendMode);
+}
+
DrawQueue::DrawQueue(IRenderer *renderer)
: _renderer(renderer)
, _allocator(1024) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index a3bfeed7804..f6de8336212 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -177,11 +177,12 @@ public:
using AnimationBase::freeImages;
inline bool isLoaded() const { return _isLoaded; }
- inline uint frameCount() const { return _frames.size(); }
inline uint spriteCount() const { return _spriteBases.size(); }
+ inline uint frameCount() const { return _frames.size(); }
inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
inline uint32 totalDuration() const { return _totalDuration; }
int32 frameAtTime(uint32 time) const;
+ Common::Point imageSize(int32 imageI) const;
void draw2D(
int32 frameI,
@@ -195,6 +196,12 @@ public:
float scale,
BlendMode blendMode,
Color color);
+ void drawEffect(
+ int32 frameI,
+ Math::Vector3d center,
+ Math::Vector2d tiling,
+ Math::Vector2d texOffset,
+ BlendMode blendMode);
private:
int32 imageIndex(int32 frameI, int32 spriteI) const;
@@ -240,6 +247,7 @@ public:
private:
friend class AnimationDrawRequest;
+ friend class SpecialEffectDrawRequest;
Common::SharedPtr<Animation> _animation;
Common::Point _center;
int16 _scale = kBaseScale;
@@ -303,6 +311,27 @@ private:
float _lodBias;
};
+class SpecialEffectDrawRequest : public IDrawRequest {
+public:
+ SpecialEffectDrawRequest(
+ Graphic &graphic,
+ Common::Point topLeft,
+ Common::Point bottomRight,
+ Math::Vector2d texOffset,
+ BlendMode blendMode);
+
+ virtual void draw() override;
+
+private:
+ Animation *_animation;
+ int32 _frameI;
+ Math::Vector3d _topLeft;
+ Math::Vector2d
+ _tiling,
+ _texOffset;
+ BlendMode _blendMode;
+};
+
class BumpAllocator {
public:
BumpAllocator(size_t pageSize);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index dbe27be262d..0fde17ce3f6 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -74,7 +74,7 @@ enum class GraphicObjectType : byte
{
Normal,
NormalPosterize, // the posterization is not actually applied in the original engine
- Alpha
+ Effect
};
class GraphicObject : public ObjectBase {
@@ -97,17 +97,17 @@ protected:
int32 _posterizeAlpha;
};
-class ShiftingGraphicObject final : public GraphicObject {
+class SpecialEffectObject final : public GraphicObject {
public:
static constexpr const char *kClassName = "CObjetoGraficoMuare";
- ShiftingGraphicObject(Room *room, Common::ReadStream &stream);
+ SpecialEffectObject(Room *room, Common::ReadStream &stream);
virtual void draw() override;
private:
- Common::Point _pos, _size;
+ static constexpr const float kShiftSpeed = 1 / 256.0f;
+ Common::Point _topLeft, _bottomRight;
Math::Vector2d _texShift;
- uint32 _startTime = 0;
};
class ShapeObject : public ObjectBase {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 6d133cf530d..0cfb0207dbb 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -40,8 +40,8 @@ static ObjectBase *readRoomObject(Room *room, ReadStream &stream) {
return new PointObject(room, stream);
else if (type == GraphicObject::kClassName)
return new GraphicObject(room, stream);
- else if (type == ShiftingGraphicObject::kClassName)
- return new ShiftingGraphicObject(room, stream);
+ else if (type == SpecialEffectObject::kClassName)
+ return new SpecialEffectObject(room, stream);
else if (type == Item::kClassName)
return new Item(room, stream);
else if (type == PhysicalObject::kClassName)
Commit: afe0f02dbad2b256b41a28fce8cb491b8a2c5f01
https://github.com/scummvm/scummvm/commit/afe0f02dbad2b256b41a28fce8cb491b8a2c5f01
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Fix sprite prerendering with semi-transparent images
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 43b927f6ded..8a6301de810 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -227,6 +227,32 @@ Point Animation::imageSize(int32 imageI) const {
return image == nullptr ? Point() : Point(image->w, image->h);
}
+// unfortunately ScummVMs BLEND_NORMAL does not blend alpha
+// but this also bad, let's find/discuss a better solution later
+static void fullBlend(const ManagedSurface &source, ManagedSurface &destination, int offsetX, int offsetY) {
+ assert(source.format == BlendBlit::getSupportedPixelFormat());
+ assert(destination.format == BlendBlit::getSupportedPixelFormat());
+ assert(offsetX >= 0 && offsetX + source.w <= destination.w);
+ assert(offsetY >= 0 && offsetY + source.h <= destination.h);
+
+ const byte *sourceLine = (byte *)source.getPixels();
+ byte *destinationLine = (byte *)destination.getPixels() + offsetY * source.pitch + offsetX * 4;
+ for (int y = 0; y < source.h; y++) {
+ const byte *sourcePixel = sourceLine;
+ byte *destPixel = destinationLine;
+ for (int x = 0; x < source.w; x++) {
+ byte alpha = (*(const uint32 *)sourcePixel) & 0xff;
+ for (int i = 1; i < 4; i++)
+ destPixel[i] = ((byte)(alpha * sourcePixel[i] / 255)) + ((byte)((255 - alpha) * destPixel[i] / 255));
+ destPixel[0] = alpha + ((byte)((255 - alpha) * destPixel[0] / 255));
+ sourcePixel += 4;
+ destPixel += 4;
+ }
+ sourceLine += source.pitch;
+ destinationLine += destination.pitch;
+ }
+}
+
void Animation::prerenderFrame(int32 frameI) {
assert(frameI >= 0 && (uint)frameI < frameCount());
if (frameI == _renderedFrameI)
@@ -240,7 +266,7 @@ void Animation::prerenderFrame(int32 frameI) {
continue;
int offsetX = _imageOffsets[imageI].x - bounds.left;
int offsetY = _imageOffsets[imageI].y - bounds.top;
- image->blendBlitTo(_renderedSurface, offsetX, offsetY);
+ fullBlend(*image, _renderedSurface, offsetX, offsetY);
}
if (_premultiplyAlpha != 100) {
Commit: 6bfee722ae08996d69cf47f29c24153cb43792af
https://github.com/scummvm/scummvm/commit/6bfee722ae08996d69cf47f29c24153cb43792af
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Fix shape loading and add debug drawing
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index d96f822ee48..fda27aa2c04 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -57,6 +57,7 @@ Common::String AlcachofaEngine::getGameId() const {
}
Common::Error AlcachofaEngine::run() {
+ setDebugger(&_console);
_renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
@@ -68,9 +69,6 @@ Common::Error AlcachofaEngine::run() {
world().currentRoom() = room;
room->loadResources();
- // Set the engine's debugger console
- setDebugger(new Console());
-
// If a savegame was selected from the launcher, load it
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot != -1)
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 54a8cf57837..9f37babf13b 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -36,6 +36,7 @@
#include "alcachofa/detection.h"
#include "alcachofa/camera.h"
+#include "alcachofa/console.h"
namespace Alcachofa {
@@ -59,6 +60,7 @@ public:
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
inline World &world() { return *_world; }
+ inline Console &console() { return _console; }
uint32 getFeatures() const;
@@ -108,6 +110,7 @@ private:
Common::ScopedPtr<DrawQueue> _drawQueue;
Common::ScopedPtr<World> _world;
Camera _camera;
+ Console _console;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index a64a7fd216f..b810f355886 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -24,15 +24,11 @@
namespace Alcachofa {
Console::Console() : GUI::Debugger() {
- registerCmd("test", WRAP_METHOD(Console, Cmd_test));
+ registerVar("showInteractables", &_showInteractables);
+ registerVar("showFloor", &_showFloor);
}
Console::~Console() {
}
-bool Console::Cmd_test(int argc, const char **argv) {
- debugPrintf("Test\n");
- return true;
-}
-
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index f8b2028fefc..0bf5bf6ef94 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -28,11 +28,22 @@
namespace Alcachofa {
class Console : public GUI::Debugger {
-private:
- bool Cmd_test(int argc, const char **argv);
public:
Console();
~Console() override;
+
+ inline bool showInteractables() const { return _showInteractables; }
+ inline bool showFloor() const { return _showFloor; }
+
+ inline bool isAnyDebugDrawingOn() const {
+ return
+ _showInteractables ||
+ _showFloor;
+ }
+
+private:
+ bool _showInteractables = true;
+ bool _showFloor = true;
};
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 55217e81faa..3e9be3137ca 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -22,8 +22,10 @@
#include "objects.h"
#include "rooms.h"
#include "stream-helper.h"
+#include "alcachofa.h"
using namespace Common;
+using namespace Math;
namespace Alcachofa {
@@ -40,6 +42,14 @@ InteractableObject::InteractableObject(Room *room, ReadStream &stream)
_relatedObject.toUppercase();
}
+void InteractableObject::drawDebug() {
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (!g_engine->console().showInteractables() || renderer == nullptr || !isEnabled())
+ return;
+
+ renderer->debugShape(*shape());
+}
+
Door::Door(Room *room, ReadStream &stream)
: InteractableObject(room, stream)
, _targetRoom(readVarString(stream))
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 5189793ec2c..c0ae21dbb84 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -51,6 +51,9 @@ void ObjectBase::toggle(bool isEnabled) {
void ObjectBase::draw() {
}
+void ObjectBase::drawDebug() {
+}
+
void ObjectBase::update() {
}
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 4a376b1a40f..0899d3f08e9 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -101,7 +101,7 @@ private:
bool _withMipmaps;
};
-class OpenGLRenderer : public IRenderer {
+class OpenGLRenderer : public IDebugRenderer {
public:
OpenGLRenderer(Point resolution)
: _resolution(resolution) {
@@ -110,6 +110,7 @@ public:
GL_CALL(glDisable(GL_DEPTH_TEST));
GL_CALL(glDisable(GL_SCISSOR_TEST));
GL_CALL(glDisable(GL_STENCIL_TEST));
+ GL_CALL(glDisable(GL_CULL_FACE));
GL_CALL(glEnable(GL_BLEND));
GL_CALL(glDepthMask(GL_FALSE));
}
@@ -250,7 +251,7 @@ public:
float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
- glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+ GL_CALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
if (_currentTexture != nullptr)
GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
@@ -264,6 +265,31 @@ public:
#endif
}
+ virtual void debugPolygon(
+ Span<Vector2d> points,
+ Color color
+ ) override {
+ setTexture(nullptr);
+ setBlendMode(BlendMode::Alpha);
+ GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
+ GL_CALL(glLineWidth(4.0f));
+ GL_CALL(glPointSize(8.0f));
+
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 2)
+ GL_CALL(glDrawArrays(GL_POLYGON, 0, points.size()));
+
+ color.a = (byte)(MIN(255.0f, color.a * 1.3f));
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 1)
+ GL_CALL(glDrawArrays(GL_LINE_LOOP, 0, points.size()));
+
+ color.a = (byte)(MIN(255.0f, color.a * 1.3f));
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 0)
+ GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
+ }
+
private:
void initViewportAndMatrices() {
int32 screenWidth = g_system->getWidth();
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 8a6301de810..ecded3d37c4 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -22,6 +22,7 @@
#include "graphics.h"
#include "stream-helper.h"
#include "alcachofa.h"
+#include "shape.h"
#include "common/system.h"
#include "common/file.h"
@@ -37,6 +38,22 @@ namespace Alcachofa {
ITexture::ITexture(Point size) : _size(size) {}
+void IDebugRenderer::debugShape(const Shape &shape, Color color) {
+ constexpr uint kMaxPoints = 16;
+ Vector2d points2d[kMaxPoints];
+ for (auto polygon : shape) {
+ // I don't think this will happen but let's be sure
+ assert(polygon._points.size() <= kMaxPoints);
+ for (uint i = 0; i < polygon._points.size(); i++) {
+ const auto p3d = polygon._points[i];
+ const auto p2d = g_engine->camera().transform3Dto2D(Vector3d(p3d.x, p3d.y, kBaseScale));
+ points2d[i] = Vector2d(p2d.x(), p2d.y());
+ }
+
+ debugPolygon({ points2d, polygon._points.size() }, color);
+ }
+}
+
AnimationBase::AnimationBase(String fileName, AnimationFolder folder)
: _fileName(move(fileName))
, _folder(folder) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index f6de8336212..e1028f6bf55 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -26,6 +26,7 @@
#include "common/stream.h"
#include "common/serializer.h"
#include "common/rect.h"
+#include "common/span.h"
#include "math/vector2d.h"
#include "graphics/managed_surface.h"
@@ -76,11 +77,15 @@ constexpr const int8 kOrderCount = 70;
constexpr const int8 kForegroundOrderCount = 10;
struct Color {
- uint8 b, g, r, a;
+ uint8 r, g, b, a;
};
static constexpr const Color kWhite = { 255, 255, 255, 255 };
static constexpr const Color kBlack = { 0, 0, 0, 255 };
static constexpr const Color kClear = { 0, 0, 0, 0 };
+static constexpr const Color kDebugRed = { 250, 0, 0, 70 };
+static constexpr const Color kDebugBlue = { 0, 0, 255, 110 };
+
+class Shape;
class ITexture {
public:
@@ -117,6 +122,19 @@ public:
static IRenderer *createOpenGLRenderer(Common::Point resolution);
};
+class IDebugRenderer : public IRenderer {
+public:
+ virtual void debugPolygon(
+ Common::Span<Math::Vector2d> points,
+ Color color = kDebugRed
+ ) = 0;
+
+ virtual void debugShape(
+ const Shape &shape,
+ Color color = kDebugRed
+ );
+};
+
enum class AnimationFolder {
Animations,
Masks,
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 0fde17ce3f6..75a9d747e69 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -45,6 +45,7 @@ public:
virtual void toggle(bool isEnabled);
virtual void draw();
+ virtual void drawDebug();
virtual void update();
virtual void loadResources();
virtual void freeResources();
@@ -277,6 +278,8 @@ public:
InteractableObject(Room *room, Common::ReadStream &stream);
virtual ~InteractableObject() override = default;
+ virtual void drawDebug() override;
+
private:
Common::Point _interactionPoint;
CursorType _cursorType;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0cfb0207dbb..2ba3e27ea91 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -133,6 +133,8 @@ void Room::update() {
world().globalRoom().drawObjects();
// TODO: Draw black borders
g_engine->drawQueue().draw();
+ drawDebug();
+ world().globalRoom().drawDebug();
}
}
@@ -149,6 +151,17 @@ void Room::drawObjects() {
object->draw();
}
+void Room::drawDebug() {
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (renderer == nullptr || !g_engine->console().isAnyDebugDrawingOn())
+ return;
+ for (auto *object : _objects)
+ object->drawDebug();
+ if (_activeFloorI >= 0 && g_engine->console().showFloor())
+ renderer->debugShape(_floors[_activeFloorI], kDebugBlue);
+
+}
+
void Room::loadResources() {
for (auto *object : _objects)
object->loadResources();
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 0d95142cf7e..c76189de741 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -47,6 +47,7 @@ protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
void updateObjects();
void drawObjects();
+ void drawDebug();
World *_world;
Common::String _name;
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 74fe921f75d..451b6ac6b5f 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -61,15 +61,16 @@ uint Shape::addPolygon(uint maxCount) {
if (_points[firstI + newCount] == _points[firstI])
break;
}
+ _points.resize(firstI + newCount);
}
_polygons.push_back({ firstI, newCount });
return newCount;
}
-Polygon Shape::at(uint index) {
+Polygon Shape::at(uint index) const {
auto range = _polygons[index];
Polygon p;
- p._points = Span<Point>(_points.data() + range.first, range.second);
+ p._points = Span<const Point>(_points.data() + range.first, range.second);
return p;
}
@@ -95,11 +96,11 @@ PathFindingShape::PathFindingShape(ReadStream &stream) {
// TODO: Implement the path finding
}
-PathFindingPolygon PathFindingShape::at(uint index) {
+PathFindingPolygon PathFindingShape::at(uint index) const {
auto range = _polygons[index];
PathFindingPolygon p;
- p._points = Span<Point>(_points.data() + range.first, range.second);
- p._pointValues = Span<int8>(_pointValues.data() + range.first, range.second);
+ p._points = Span<const Point>(_points.data() + range.first, range.second);
+ p._pointValues = Span<const int8>(_pointValues.data() + range.first, range.second);
p._polygonValue = _polygonValues[index];
return p;
}
@@ -127,12 +128,12 @@ FloorColorShape::FloorColorShape(ReadStream &stream) {
}
}
-FloorColorPolygon FloorColorShape::at(uint index) {
+FloorColorPolygon FloorColorShape::at(uint index) const {
auto range = _polygons[index];
FloorColorPolygon p;
- p._points = Span<Point>(_points.data() + range.first, range.second);
- p._pointWeights = Span<uint8>(_pointWeights.data() + range.first, range.second);
- p._pointColors = Span<uint32>(_pointColors.data() + range.first, range.second);
+ p._points = Span<const Point>(_points.data() + range.first, range.second);
+ p._pointWeights = Span<const uint8>(_pointWeights.data() + range.first, range.second);
+ p._pointColors = Span<const uint32>(_pointColors.data() + range.first, range.second);
p._polygonValue = _polygonValues[index];
return p;
}
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 4bb3bd8e95d..a28d9f90ced 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -32,17 +32,17 @@
namespace Alcachofa {
struct Polygon {
- Common::Span<Common::Point> _points;
+ Common::Span<const Common::Point> _points;
};
struct PathFindingPolygon : Polygon {
- Common::Span<int8> _pointValues;
+ Common::Span<const int8> _pointValues;
int8 _polygonValue;
};
struct FloorColorPolygon : Polygon {
- Common::Span<uint32> _pointColors;
- Common::Span<uint8> _pointWeights;
+ Common::Span<const uint32> _pointColors;
+ Common::Span<const uint8> _pointWeights;
int8 _polygonValue;
};
@@ -50,6 +50,7 @@ template<class TShape, typename TPolygon>
struct PolygonIterator {
using difference_type = uint;
using value_type = TPolygon;
+ using my_type = PolygonIterator<TShape, TPolygon>;
inline value_type operator*() const {
return _shape.at(_index);
@@ -68,14 +69,22 @@ struct PolygonIterator {
return tmp;
}
+ inline bool operator==(const my_type& it) const {
+ return &this->_shape == &it._shape && this->_index == it._index;
+ }
+
+ inline bool operator!=(const my_type& it) const {
+ return &this->_shape != &it._shape || this->_index != it._index;
+ }
+
private:
friend typename Common::remove_const_t<TShape>;
- PolygonIterator(TShape &shape, uint index = 0)
+ PolygonIterator(const TShape &shape, uint index = 0)
: _shape(shape)
, _index(index) {
}
- TShape &_shape;
+ const TShape &_shape;
uint _index;
};
@@ -89,10 +98,10 @@ public:
inline Common::Point firstPoint() const { return _points.empty() ? Common::Point() : _points[0]; }
inline uint polygonCount() const { return _polygons.size(); }
inline bool empty() const { return polygonCount() == 0; }
- inline iterator begin() { return { *this, 0 }; }
- inline iterator end() { return { *this, polygonCount() }; }
+ inline iterator begin() const { return { *this, 0 }; }
+ inline iterator end() const { return { *this, polygonCount() }; }
- Polygon at(uint index);
+ Polygon at(uint index) const;
protected:
uint addPolygon(uint maxCount);
@@ -122,10 +131,10 @@ public:
PathFindingShape();
PathFindingShape(Common::ReadStream &stream);
- inline iterator begin() { return { *this, 0 }; }
- inline iterator end() { return { *this, polygonCount() }; }
+ inline iterator begin() const { return { *this, 0 }; }
+ inline iterator end() const { return { *this, polygonCount() }; }
- PathFindingPolygon at(uint index);
+ PathFindingPolygon at(uint index) const;
private:
Common::Array<int8> _pointValues;
@@ -140,10 +149,10 @@ public:
FloorColorShape();
FloorColorShape(Common::ReadStream &stream);
- inline iterator begin() { return { *this, 0 }; }
- inline iterator end() { return { *this, polygonCount() }; }
+ inline iterator begin() const { return { *this, 0 }; }
+ inline iterator end() const { return { *this, polygonCount() }; }
- FloorColorPolygon at(uint index);
+ FloorColorPolygon at(uint index) const;
private:
Common::Array<uint32> _pointColors;
Commit: f84c67978821b5bedbd951774b3fc9c10c02fc5a
https://github.com/scummvm/scummvm/commit/f84c67978821b5bedbd951774b3fc9c10c02fc5a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add mouse input and Shape::contains
Changed paths:
A engines/alcachofa/Input.cpp
A engines/alcachofa/Input.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/Input.cpp b/engines/alcachofa/Input.cpp
new file mode 100644
index 00000000000..e227fc59492
--- /dev/null
+++ b/engines/alcachofa/Input.cpp
@@ -0,0 +1,61 @@
+/* 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 "input.h"
+#include "alcachofa.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+void Input::nextFrame() {
+ _wasMouseLeftPressed = false;
+ _wasMouseRightPressed = false;
+}
+
+bool Input::handleEvent(const Common::Event &event) {
+ switch (event.type) {
+ case EVENT_LBUTTONDOWN:
+ _wasMouseLeftPressed = true;
+ _isMouseLeftDown = true;
+ return true;
+ case EVENT_LBUTTONUP:
+ _isMouseLeftDown = false;
+ return true;
+ case EVENT_RBUTTONDOWN:
+ _wasMouseRightPressed = true;
+ _isMouseRightDown = true;
+ return true;
+ case EVENT_RBUTTONUP:
+ _isMouseRightDown = false;
+ return true;
+ case EVENT_MOUSEMOVE: {
+ _mousePos2D = event.mouse;
+ auto pos3D = g_engine->camera().transform2Dto3D({ (float)_mousePos2D.x, (float)_mousePos2D.y, kBaseScale });
+ _mousePos3D = { (int16)pos3D.x(), (int16)pos3D.y() };
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+}
diff --git a/engines/alcachofa/Input.h b/engines/alcachofa/Input.h
new file mode 100644
index 00000000000..a648d72b848
--- /dev/null
+++ b/engines/alcachofa/Input.h
@@ -0,0 +1,54 @@
+/* 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/>.
+ *
+ */
+
+#ifndef INPUT_H
+#define INPUT_H
+
+#include "common/events.h"
+
+namespace Alcachofa {
+
+class Input {
+public:
+ inline bool wasMouseLeftPressed() const { return _wasMouseLeftPressed; }
+ inline bool wasMouseRightPressed() const { return _wasMouseRightPressed; }
+ inline bool isMouseLeftDown() const { return _isMouseLeftDown; }
+ inline bool isMouseRightDown() const { return _isMouseRightDown; }
+ inline const Common::Point &mousePos2D() const { return _mousePos2D; }
+ inline const Common::Point &mousePos3D() const { return _mousePos3D; }
+
+ void nextFrame();
+ bool handleEvent(const Common::Event &event);
+
+private:
+ bool
+ _wasMouseLeftPressed,
+ _wasMouseRightPressed,
+ _isMouseLeftDown,
+ _isMouseRightDown;
+ Common::Point
+ _mousePos2D,
+ _mousePos3D;
+};
+
+}
+
+#endif // INPUT_H
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index fda27aa2c04..76d3c998b7d 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -57,7 +57,7 @@ Common::String AlcachofaEngine::getGameId() const {
}
Common::Error AlcachofaEngine::run() {
- setDebugger(&_console);
+ setDebugger(_console);
_renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
@@ -74,10 +74,15 @@ Common::Error AlcachofaEngine::run() {
if (saveSlot != -1)
(void)loadGameState(saveSlot);
+ g_system->showMouse(true);
+
Common::Event e;
Graphics::FrameLimiter limiter(g_system, 60);
while (!shouldQuit()) {
+ _input.nextFrame();
while (g_system->getEventManager()->pollEvent(e)) {
+ if (_input.handleEvent(e))
+ continue;
}
_renderer->begin();
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 9f37babf13b..3a183acd868 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -36,6 +36,7 @@
#include "alcachofa/detection.h"
#include "alcachofa/camera.h"
+#include "alcachofa/input.h"
#include "alcachofa/console.h"
namespace Alcachofa {
@@ -59,8 +60,9 @@ public:
inline IRenderer &renderer() { return *_renderer; }
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
+ inline Input &input() { return _input; }
inline World &world() { return *_world; }
- inline Console &console() { return _console; }
+ inline Console &console() { return *_console; }
uint32 getFeatures() const;
@@ -106,11 +108,12 @@ public:
}
private:
+ Console *_console = new Console();
Common::ScopedPtr<IRenderer> _renderer;
Common::ScopedPtr<DrawQueue> _drawQueue;
Common::ScopedPtr<World> _world;
Camera _camera;
- Console _console;
+ Input _input;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 67870f4434c..fabc394209b 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -30,14 +30,14 @@ using namespace Math;
namespace Alcachofa {
-void Camera::setRoomBounds(Rect bgBounds, float bgScale) {
+void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
float scaleFactor = 1 - bgScale * kInvBaseScale;
_roomMin = Vector2d(
g_system->getWidth() / 2 * scaleFactor,
g_system->getHeight() / 2 * scaleFactor);
_roomMax = _roomMin + Vector2d(
- bgBounds.width() * bgScale * kInvBaseScale,
- bgBounds.height() * bgScale * kInvBaseScale);
+ bgSize.x * bgScale * kInvBaseScale,
+ bgSize.y * bgScale * kInvBaseScale);
}
static Matrix4 scaleMatrix(float scale) {
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 520655c558f..68d79891524 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -43,7 +43,7 @@ public:
void update();
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
- void setRoomBounds(Common::Rect bgBounds, float bgScale);
+ void setRoomBounds(Common::Point bgSize, int16 bgScale);
private:
static constexpr const float kAccelerationThreshold = 2.89062f;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 2ba3e27ea91..2c94ae81b0c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -125,6 +125,10 @@ ObjectBase *Room::getObjectByName(const Common::String &name) const {
}
void Room::update() {
+ if (world().currentRoom() == this) {
+ updateRoomBounds();
+ updateInput();
+ }
if (world().currentRoom() == this)
updateObjects();
if (world().currentRoom() == this) {
@@ -138,6 +142,22 @@ void Room::update() {
}
}
+void Room::updateInput() {
+ if (g_engine->input().wasMouseLeftPressed()) {
+ Point p2d = g_engine->input().mousePos2D();
+ Point p3d = g_engine->input().mousePos3D();
+ bool contains = _floors[_activeFloorI].contains(p3d);
+ warning("Mouse 2D: %d, %d \t\t3D: %d, %d\t%s", p2d.x, p2d.y, p3d.x, p3d.y, contains ? "yes" : "no");
+ }
+}
+
+void Room::updateRoomBounds() {
+ auto background = getObjectByName("Background");
+ auto graphic = background == nullptr ? nullptr : background->graphic();
+ if (graphic != nullptr)
+ g_engine->camera().setRoomBounds(graphic->animation().imageSize(0), graphic->scale());
+}
+
void Room::updateObjects() {
for (auto *object : _objects) {
object->update();
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index c76189de741..7678552c69d 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -38,6 +38,7 @@ public:
inline const Common::String &name() const { return _name; }
void update();
+ virtual void updateInput();
virtual void loadResources();
virtual void freeResources();
virtual void serializeSave(Common::Serializer &serializer);
@@ -45,6 +46,7 @@ public:
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
+ void updateRoomBounds();
void updateObjects();
void drawObjects();
void drawDebug();
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 451b6ac6b5f..4ba213ba938 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -22,10 +22,31 @@
#include "shape.h"
#include "stream-helper.h"
+#include "math/line2d.h"
+
using namespace Common;
+using namespace Math;
namespace Alcachofa {
+static int sideOfLine(const Point &a, const Point &b, const Point &q) {
+ return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
+}
+
+bool Polygon::contains(const Point &query) const {
+ switch (_points.size()) {
+ case 0: return false;
+ case 1: return query == _points[0];
+ default:
+ // we assume that the polygon is convex
+ for (uint i = 1; i < _points.size(); i++) {
+ if (sideOfLine(_points[i - 1], _points[i], query) < 0)
+ return false;
+ }
+ return sideOfLine(_points[_points.size() - 1], _points[0], query) >= 0;
+ }
+}
+
Shape::Shape() {}
Shape::Shape(ReadStream &stream) {
@@ -74,6 +95,14 @@ Polygon Shape::at(uint index) const {
return p;
}
+bool Shape::contains(const Point &query) const {
+ for (auto polygon : *this) {
+ if (polygon.contains(query))
+ return true;
+ }
+ return false;
+}
+
PathFindingShape::PathFindingShape() {}
PathFindingShape::PathFindingShape(ReadStream &stream) {
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index a28d9f90ced..2734f37cd93 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -33,6 +33,8 @@ namespace Alcachofa {
struct Polygon {
Common::Span<const Common::Point> _points;
+
+ bool contains(const Common::Point &query) const;
};
struct PathFindingPolygon : Polygon {
@@ -102,6 +104,7 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
Polygon at(uint index) const;
+ bool contains(const Common::Point &query) const;
protected:
uint addPolygon(uint maxCount);
Commit: 2e8b508a5f86a0cb9847b61eb3e901eb26ca1516
https://github.com/scummvm/scummvm/commit/2e8b508a5f86a0cb9847b61eb3e901eb26ca1516
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add shape interpolations
Changed paths:
A engines/alcachofa/common.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
new file mode 100644
index 00000000000..8f9669d5ea2
--- /dev/null
+++ b/engines/alcachofa/common.h
@@ -0,0 +1,61 @@
+/* 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/>.
+ *
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+namespace Alcachofa {
+
+enum class CursorType {
+ Normal,
+ LookAt,
+ Use,
+ GoTo,
+ LeaveUp,
+ LeaveRight,
+ LeaveDown,
+ LeaveLeft
+};
+
+enum class Direction {
+ Up,
+ Down,
+ Left,
+ Right
+};
+
+constexpr const int32 kDirectionCount = 4;
+constexpr const int8 kOrderCount = 70;
+constexpr const int8 kForegroundOrderCount = 10;
+
+struct Color {
+ uint8 r, g, b, a;
+};
+static constexpr const Color kWhite = { 255, 255, 255, 255 };
+static constexpr const Color kBlack = { 0, 0, 0, 255 };
+static constexpr const Color kClear = { 0, 0, 0, 0 };
+static constexpr const Color kDebugRed = { 250, 0, 0, 70 };
+static constexpr const Color kDebugGreen = { 0, 255, 0, 85 };
+static constexpr const Color kDebugBlue = { 0, 0, 255, 110 };
+
+}
+
+#endif // ALCACHOFA_COMMON_H
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index b810f355886..aed5c63bded 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -26,6 +26,7 @@ namespace Alcachofa {
Console::Console() : GUI::Debugger() {
registerVar("showInteractables", &_showInteractables);
registerVar("showFloor", &_showFloor);
+ registerVar("showFloorColor", &_showFloorColor);
}
Console::~Console() {
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 0bf5bf6ef94..898605f2e0e 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -34,16 +34,19 @@ public:
inline bool showInteractables() const { return _showInteractables; }
inline bool showFloor() const { return _showFloor; }
+ inline bool showFloorColor() const { return _showFloorColor; }
inline bool isAnyDebugDrawingOn() const {
return
_showInteractables ||
- _showFloor;
+ _showFloor ||
+ _showFloorColor;
}
private:
bool _showInteractables = true;
- bool _showFloor = true;
+ bool _showFloor = false;
+ bool _showFloorColor = true;
};
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 3e9be3137ca..3b3085d79f3 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -179,4 +179,16 @@ FloorColor::FloorColor(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream) {}
+void FloorColor::drawDebug() {
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (!g_engine->console().showFloorColor() || renderer == nullptr || !isEnabled())
+ return;
+
+ renderer->debugShape(*shape(), kDebugGreen);
+}
+
+Shape *FloorColor::shape() {
+ return &_shape;
+}
+
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index e1028f6bf55..3f5954a9ff9 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -31,6 +31,7 @@
#include "graphics/managed_surface.h"
#include "camera.h"
+#include "common.h"
namespace Alcachofa {
@@ -54,37 +55,6 @@ enum class BlendMode {
Tinted
};
-enum class CursorType {
- Normal,
- LookAt,
- Use,
- GoTo,
- LeaveUp,
- LeaveRight,
- LeaveDown,
- LeaveLeft
-};
-
-enum class Direction {
- Up,
- Down,
- Left,
- Right
-};
-
-constexpr const int32 kDirectionCount = 4;
-constexpr const int8 kOrderCount = 70;
-constexpr const int8 kForegroundOrderCount = 10;
-
-struct Color {
- uint8 r, g, b, a;
-};
-static constexpr const Color kWhite = { 255, 255, 255, 255 };
-static constexpr const Color kBlack = { 0, 0, 0, 255 };
-static constexpr const Color kClear = { 0, 0, 0, 0 };
-static constexpr const Color kDebugRed = { 250, 0, 0, 70 };
-static constexpr const Color kDebugBlue = { 0, 0, 255, 110 };
-
class Shape;
class ITexture {
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 75a9d747e69..37d4de22251 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -387,6 +387,9 @@ public:
FloorColor(Room *room, Common::ReadStream &stream);
virtual ~FloorColor() override = default;
+ virtual void drawDebug() override;
+ virtual Shape *shape() override;
+
private:
FloorColorShape _shape;
};
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 4ba213ba938..c6e644a9457 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -29,6 +29,10 @@ using namespace Math;
namespace Alcachofa {
+static Vector2d asVec(const Point &p) {
+ return Vector2d((float)p.x, (float)p.y);
+}
+
static int sideOfLine(const Point &a, const Point &b, const Point &q) {
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
}
@@ -47,6 +51,110 @@ bool Polygon::contains(const Point &query) const {
}
}
+EdgeDistances Polygon::edgeDistances(uint startPointI, const Point &query) const {
+ assert(startPointI < _points.size());
+ uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
+ Vector2d
+ a = asVec(_points[startPointI]),
+ b = asVec(_points[endPointI]),
+ q = asVec(query);
+ float edgeLength = a.getDistanceTo(b);
+ Vector2d edgeDir = (b - a) / edgeLength;
+ Vector2d edgeNormal(-edgeDir.getY(), edgeDir.getX());
+ EdgeDistances distances;
+ distances._edgeLength = edgeLength;
+ distances._onEdge = edgeDir.dotProduct(q - a);
+ distances._toEdge = abs(edgeNormal.dotProduct(q) - edgeNormal.dotProduct(a));
+ return distances;
+}
+
+static float depthAtForLine(const Point &a, const Point &b, const Point &q, int8 depthA, int8 depthB) {
+ return (sqrtf(a.sqrDist(q)) / a.sqrDist(b) * depthB + depthA) * 0.01f;
+}
+
+static float depthAtForConvex(const PathFindingPolygon &p, const Point &q) {
+ float sumDepths = 0, sumDistances = 0;
+ for (uint i = 0; i < p._points.size(); i++) {
+ uint j = i + 1 == p._points.size() ? 0 : i + 1;
+ auto distances = p.edgeDistances(i, q);
+ float depthOnEdge = p._pointDepths[i] + distances._onEdge * (p._pointDepths[j] - p._pointDepths[i]) / distances._edgeLength;
+ if (distances._toEdge < epsilon) // q is directly on the edge
+ return depthOnEdge;
+ sumDepths += 1 / distances._toEdge * depthOnEdge;
+ sumDistances += 1 / distances._toEdge;
+ }
+ return sumDepths / sumDistances * 0.01f;
+}
+
+float PathFindingPolygon::depthAt(const Point &query) const {
+ switch (_points.size()) {
+ case 0:
+ case 1: return 1.0f;
+ case 2: return depthAtForLine(_points[0], _points[1], query, _pointDepths[0], _pointDepths[1]);
+ default: return depthAtForConvex(*this, query);
+ }
+}
+
+static Color colorAtForLine(const Point &a, const Point &b, const Point &q, Color colorA, Color colorB) {
+ // I highly suspect RGB calculation being very bugged, so for now I just ignore and only calc alpha
+ float phase = sqrtf(q.sqrDist(a)) / a.sqrDist(b);
+ colorA.a += phase * colorB.a;
+ return colorA;
+}
+
+static Color colorAtForConvex(const FloorColorPolygon &p, const Point &query) {
+ // This is a quite literal translation of the original engine
+ // There may very well be a better way than this...
+ float weights[FloorColorShape::kPointsPerPolygon];
+ memset(weights, 0, sizeof(weights));
+
+ for (uint i = 0; i < p._points.size(); i++) {
+ EdgeDistances distances = p.edgeDistances(i, query);
+ float edgeWeight = distances._toEdge * distances._onEdge / distances._edgeLength;
+ if (distances._edgeLength > 1) {
+ weights[i] += edgeWeight;
+ weights[i + 1 == p._points.size() ? 0 : i + 1] += edgeWeight;
+ }
+ }
+ float weightSum = 0;
+ for (uint i = 0; i < p._points.size(); i++)
+ weightSum += weights[i];
+ for (uint i = 0; i < p._points.size(); i++) {
+ if (weights[i] < epsilon)
+ return p._pointColors[i];
+ weights[i] = weightSum / weights[i];
+ }
+
+ weightSum = 0;
+ for (uint i = 0; i < p._points.size(); i++)
+ weightSum += weights[i];
+ for (uint i = 0; i < p._points.size(); i++)
+ weights[i] /= weightSum;
+
+ float r = 0, g = 0, b = 0, a = 0.5f;
+ for (uint i = 0; i < p._points.size(); i++) {
+ r += p._pointColors[i].r * weights[i];
+ g += p._pointColors[i].g * weights[i];
+ b += p._pointColors[i].b * weights[i];
+ a += p._pointColors[i].a * weights[i];
+ }
+ return {
+ (byte)MIN(255, MAX(0, (int)r)),
+ (byte)MIN(255, MAX(0, (int)g)),
+ (byte)MIN(255, MAX(0, (int)b)),
+ (byte)MIN(255, MAX(0, (int)a)),
+ };
+}
+
+Color FloorColorPolygon::colorAt(const Point &query) const {
+ switch (_points.size()) {
+ case 0: return kWhite;
+ case 1: return { 255, 255, 255, _pointColors[0].a };
+ case 2: return colorAtForLine(_points[0], _points[1], query, _pointColors[0], _pointColors[1]);
+ default: return colorAtForConvex(*this, query);
+ }
+}
+
Shape::Shape() {}
Shape::Shape(ReadStream &stream) {
@@ -95,12 +203,16 @@ Polygon Shape::at(uint index) const {
return p;
}
-bool Shape::contains(const Point &query) const {
- for (auto polygon : *this) {
- if (polygon.contains(query))
- return true;
+int32 Shape::polygonContaining(const Point &query) const {
+ for (uint i = 0; i < _polygons.size(); i++) {
+ if (at(i).contains(query))
+ return (int32)i;
}
- return false;
+ return -1;
+}
+
+bool Shape::contains(const Point &query) const {
+ return polygonContaining(query) >= 0;
}
PathFindingShape::PathFindingShape() {}
@@ -108,18 +220,20 @@ PathFindingShape::PathFindingShape() {}
PathFindingShape::PathFindingShape(ReadStream &stream) {
auto polygonCount = stream.readUint16LE();
_polygons.reserve(polygonCount);
- _polygonValues.reserve(polygonCount);
+ _polygonOrders.reserve(polygonCount);
_points.reserve(polygonCount * kPointsPerPolygon);
- _pointValues.reserve(polygonCount * kPointsPerPolygon);
+ _pointDepths.reserve(polygonCount * kPointsPerPolygon);
for (int i = 0; i < polygonCount; i++) {
for (int j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(stream));
- _polygonValues.push_back(stream.readSByte());
+ _polygonOrders.push_back(stream.readSByte());
for (int j = 0; j < kPointsPerPolygon; j++)
- _pointValues.push_back(stream.readSByte());
+ _pointDepths.push_back(stream.readByte());
- addPolygon(kPointsPerPolygon);
+ uint pointCount = addPolygon(kPointsPerPolygon);
+ assert(pointCount <= kPointsPerPolygon);
+ _pointDepths.resize(_points.size());
}
// TODO: Implement the path finding
@@ -129,31 +243,47 @@ PathFindingPolygon PathFindingShape::at(uint index) const {
auto range = _polygons[index];
PathFindingPolygon p;
p._points = Span<const Point>(_points.data() + range.first, range.second);
- p._pointValues = Span<const int8>(_pointValues.data() + range.first, range.second);
- p._polygonValue = _polygonValues[index];
+ p._pointDepths = Span<const uint8>(_pointDepths.data() + range.first, range.second);
+ p._order = _polygonOrders[index];
return p;
}
+int8 PathFindingShape::orderAt(const Point &query) const {
+ int32 polygon = polygonContaining(query);
+ return polygon < 0 ? 49 : _polygonOrders[polygon];
+}
+
+float PathFindingShape::depthAt(const Point &query) const {
+ int32 polygon = polygonContaining(query);
+ return polygon < 0 ? 1.0f : at(polygon).depthAt(query);
+}
+
FloorColorShape::FloorColorShape() {}
FloorColorShape::FloorColorShape(ReadStream &stream) {
auto polygonCount = stream.readUint16LE();
_polygons.reserve(polygonCount);
- _polygonValues.reserve(polygonCount);
_points.reserve(polygonCount * kPointsPerPolygon);
_pointColors.reserve(polygonCount * kPointsPerPolygon);
- _pointWeights.reserve(polygonCount * kPointsPerPolygon);
for (int i = 0; i < polygonCount; i++) {
for (int j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(stream));
+ Color color; // RGB and A components are stored separately
for (int j = 0; j < kPointsPerPolygon; j++)
- _pointWeights.push_back(stream.readSByte());
- for (int j = 0; j < kPointsPerPolygon; j++)
- _pointColors.push_back(stream.readUint32LE());
- _polygonValues.push_back(stream.readSByte());
+ color.a = stream.readByte();
+ for (int j = 0; j < kPointsPerPolygon; j++) {
+ color.r = stream.readByte();
+ color.g = stream.readByte();
+ color.b = stream.readByte();
+ stream.readByte(); // second alpha value is ignored
+ }
+ _pointColors.push_back(color);
+ stream.readByte(); // unused byte per polygon
- addPolygon(kPointsPerPolygon);
+ uint pointCount = addPolygon(kPointsPerPolygon);
+ assert(pointCount <= kPointsPerPolygon);
+ _pointColors.resize(_points.size());
}
}
@@ -161,10 +291,15 @@ FloorColorPolygon FloorColorShape::at(uint index) const {
auto range = _polygons[index];
FloorColorPolygon p;
p._points = Span<const Point>(_points.data() + range.first, range.second);
- p._pointWeights = Span<const uint8>(_pointWeights.data() + range.first, range.second);
- p._pointColors = Span<const uint32>(_pointColors.data() + range.first, range.second);
- p._polygonValue = _polygonValues[index];
+ p._pointColors = Span<const Color>(_pointColors.data() + range.first, range.second);
return p;
}
+OptionalColor FloorColorShape::colorAt(const Common::Point &query) const {
+ int32 polygon = polygonContaining(query);
+ return polygon < 0
+ ? OptionalColor(false, kClear)
+ : OptionalColor(true, at(polygon).colorAt(query));
+}
+
}
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 2734f37cd93..a74335763ca 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -29,23 +29,34 @@
#include "common/util.h"
#include "math/vector2d.h"
+#include "common.h"
+
namespace Alcachofa {
+struct EdgeDistances {
+ float _edgeLength;
+ float _onEdge;
+ float _toEdge;
+};
+
struct Polygon {
Common::Span<const Common::Point> _points;
bool contains(const Common::Point &query) const;
+ EdgeDistances edgeDistances(uint startPointI, const Common::Point &query) const;
};
struct PathFindingPolygon : Polygon {
- Common::Span<const int8> _pointValues;
- int8 _polygonValue;
+ Common::Span<const uint8> _pointDepths;
+ int8 _order;
+
+ float depthAt(const Common::Point &query) const;
};
struct FloorColorPolygon : Polygon {
- Common::Span<const uint32> _pointColors;
- Common::Span<const uint8> _pointWeights;
- int8 _polygonValue;
+ Common::Span<const Color> _pointColors;
+
+ Color colorAt(const Common::Point &query) const;
};
template<class TShape, typename TPolygon>
@@ -58,13 +69,13 @@ struct PolygonIterator {
return _shape.at(_index);
}
- inline PolygonIterator<TShape, TPolygon> &operator++() {
+ inline my_type &operator++() {
assert(_index < _shape.polygonCount());
_index++;
return *this;
}
- inline PolygonIterator<TShape, TPolygon> &operator++(int) {
+ inline my_type &operator++(int) {
assert(_index < _shape.polygonCount());
auto tmp = *this;
++*this;
@@ -104,6 +115,7 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
Polygon at(uint index) const;
+ int32 polygonContaining(const Common::Point &query) const;
bool contains(const Common::Point &query) const;
protected:
@@ -138,12 +150,15 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
PathFindingPolygon at(uint index) const;
+ int8 orderAt(const Common::Point &query) const;
+ float depthAt(const Common::Point &query) const;
private:
- Common::Array<int8> _pointValues;
- Common::Array<int8> _polygonValues;
+ Common::Array<uint8> _pointDepths;
+ Common::Array<int8> _polygonOrders;
};
+using OptionalColor = Common::Pair<bool, Color>;
class FloorColorShape final : public Shape {
public:
using iterator = PolygonIterator<FloorColorShape, FloorColorPolygon>;
@@ -156,11 +171,10 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
FloorColorPolygon at(uint index) const;
+ OptionalColor colorAt(const Common::Point &query) const;
private:
- Common::Array<uint32> _pointColors;
- Common::Array<uint8> _pointWeights;
- Common::Array<int8> _polygonValues;
+ Common::Array<Color> _pointColors;
};
}
Commit: 8518c4d275e96605990b81ecbfd1a1e84092a326
https://github.com/scummvm/scummvm/commit/8518c4d275e96605990b81ecbfd1a1e84092a326
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add path finding
Changed paths:
engines/alcachofa/console.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 898605f2e0e..77de4df1744 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -45,8 +45,8 @@ public:
private:
bool _showInteractables = true;
- bool _showFloor = false;
- bool _showFloorColor = true;
+ bool _showFloor = true;
+ bool _showFloorColor = false;
};
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 0899d3f08e9..b1998aa8f39 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -290,6 +290,26 @@ public:
GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
}
+ virtual void debugPolyline(
+ Span<Vector2d> points,
+ Color color
+ ) override {
+ setTexture(nullptr);
+ setBlendMode(BlendMode::Alpha);
+ GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
+ GL_CALL(glLineWidth(4.0f));
+ GL_CALL(glPointSize(8.0f));
+
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 1)
+ GL_CALL(glDrawArrays(GL_LINE_STRIP, 0, points.size()));
+
+ color.a = (byte)(MIN(255.0f, color.a * 1.3f));
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 0)
+ GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
+ }
+
private:
void initViewportAndMatrices() {
int32 screenWidth = g_system->getWidth();
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 3f5954a9ff9..56ee902df88 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -98,6 +98,10 @@ public:
Common::Span<Math::Vector2d> points,
Color color = kDebugRed
) = 0;
+ virtual void debugPolyline(
+ Common::Span<Math::Vector2d> points,
+ Color color = kDebugRed
+ ) = 0;
virtual void debugShape(
const Shape &shape,
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 2c94ae81b0c..f675738b950 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -141,13 +141,34 @@ void Room::update() {
world().globalRoom().drawDebug();
}
}
+using namespace Math;
+static Array<Vector2d> path;
+
+static Vector2d asVec(const Point &p) {
+ return Vector2d((float)p.x, (float)p.y);
+}
+
void Room::updateInput() {
+ static bool hasLastP3D = false;
+ static Point lastP3D;
+
+
if (g_engine->input().wasMouseLeftPressed()) {
Point p2d = g_engine->input().mousePos2D();
Point p3d = g_engine->input().mousePos3D();
- bool contains = _floors[_activeFloorI].contains(p3d);
- warning("Mouse 2D: %d, %d \t\t3D: %d, %d\t%s", p2d.x, p2d.y, p3d.x, p3d.y, contains ? "yes" : "no");
+
+ if (hasLastP3D) {
+ Stack<Point> pathi;
+ bool result = _floors[0].findPath(lastP3D, p3d, pathi);
+ path.clear();
+ path.push_back(asVec(lastP3D));
+ while (!pathi.empty())
+ path.push_back(asVec(pathi.pop()));
+ warning("Did %sfind a path in %d steps", result ? "" : "not ", path.size());
+ }
+ hasLastP3D = true;
+ lastP3D = p3d;
}
}
@@ -177,9 +198,19 @@ void Room::drawDebug() {
return;
for (auto *object : _objects)
object->drawDebug();
+ if (_activeFloorI < 0)
+ return;
if (_activeFloorI >= 0 && g_engine->console().showFloor())
renderer->debugShape(_floors[_activeFloorI], kDebugBlue);
+ renderer->debugPolyline({ path.begin(), path.size()}, kWhite);
+
+ Common::Array<Vector2d> asd;
+ for (auto p : _floors[0]._linkPoints)
+ {
+ auto v = asVec(p);
+ renderer->debugPolyline({ &v, 1 }, { 255, 0, 255, 255 });
+ }
}
void Room::loadResources() {
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index c6e644a9457..dd12ab65ca8 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -22,8 +22,6 @@
#include "shape.h"
#include "stream-helper.h"
-#include "math/line2d.h"
-
using namespace Common;
using namespace Math;
@@ -37,6 +35,31 @@ static int sideOfLine(const Point &a, const Point &b, const Point &q) {
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
}
+static bool segmentsIntersect(const Point &a1, const Point &b1, const Point &a2, const Point &b2) {
+ // as there are a number of special cases to consider, this method is a direct translation
+ // of the original engine
+
+ const auto sideOfLine = [](const Point &a, const Point &b, const Point q) {
+ return Alcachofa::sideOfLine(a, b, q) > 0;
+ };
+ const auto lineIntersects = [&](const Point &a1, const Point &b1, const Point &a2, const Point &b2) {
+ return sideOfLine(a1, b1, a2) != sideOfLine(a1, b1, b2);
+ };
+
+ if (a2.x > b2.x) {
+ if (a1.x > b1.x)
+ return lineIntersects(b1, a1, b2, a2) && lineIntersects(b2, a2, b1, a1);
+ else
+ return lineIntersects(a1, b1, b2, a2) && lineIntersects(b2, a2, a1, b1);
+ }
+ else {
+ if (a1.x > b1.x)
+ return lineIntersects(b1, a1, a2, b2) && lineIntersects(a2, b2, b1, a1);
+ else
+ return lineIntersects(a1, b1, a2, b2) && lineIntersects(a2, b2, a1, b1);
+ }
+}
+
bool Polygon::contains(const Point &query) const {
switch (_points.size()) {
case 0: return false;
@@ -95,6 +118,21 @@ float PathFindingPolygon::depthAt(const Point &query) const {
}
}
+uint PathFindingPolygon::findSharedPoints(
+ const PathFindingPolygon &other,
+ Common::Span<SharedPoint> sharedPoints) const {
+ uint count = 0;
+ for (uint outerI = 0; outerI < _points.size(); outerI++) {
+ for (uint innerI = 0; innerI < other._points.size(); innerI++) {
+ if (_points[outerI] == other._points[innerI]) {
+ assert(count < sharedPoints.size());
+ sharedPoints[count++] = { outerI, innerI };
+ }
+ }
+ }
+ return count;
+}
+
static Color colorAtForLine(const Point &a, const Point &b, const Point &q, Color colorA, Color colorB) {
// I highly suspect RGB calculation being very bugged, so for now I just ignore and only calc alpha
float phase = sqrtf(q.sqrDist(a)) / a.sqrDist(b);
@@ -199,6 +237,7 @@ uint Shape::addPolygon(uint maxCount) {
Polygon Shape::at(uint index) const {
auto range = _polygons[index];
Polygon p;
+ p._index = index;
p._points = Span<const Point>(_points.data() + range.first, range.second);
return p;
}
@@ -236,12 +275,15 @@ PathFindingShape::PathFindingShape(ReadStream &stream) {
_pointDepths.resize(_points.size());
}
- // TODO: Implement the path finding
+ setupLinks();
+ initializeFloydWarshall();
+ calculateFloydWarshall();
}
PathFindingPolygon PathFindingShape::at(uint index) const {
auto range = _polygons[index];
PathFindingPolygon p;
+ p._index = index;
p._points = Span<const Point>(_points.data() + range.first, range.second);
p._pointDepths = Span<const uint8>(_pointDepths.data() + range.first, range.second);
p._order = _polygonOrders[index];
@@ -258,6 +300,238 @@ float PathFindingShape::depthAt(const Point &query) const {
return polygon < 0 ? 1.0f : at(polygon).depthAt(query);
}
+PathFindingShape::LinkPolygonIndices::LinkPolygonIndices() {
+ Common::fill(_points, _points + kPointsPerPolygon, LinkIndex( -1, -1 ));
+}
+
+static Pair<int32, int32> orderPoints(const Polygon &polygon, int32 point1, int32 point2) {
+ if ((point1 > point2 && point1 + 1 != (int32)polygon._points.size()) ||
+ point2 + 1 == (int32)polygon._points.size()) {
+ int32 tmp = point1;
+ point1 = point2;
+ point2 = tmp;
+ }
+ return { point1, point2 };
+}
+
+void PathFindingShape::setupLinks() {
+ // just a heuristic, each polygon will be attached to at least one other
+ _linkPoints.reserve(polygonCount() * 3);
+ _linkIndices.resize(polygonCount());
+ _targetQuads.resize(polygonCount() * kPointsPerPolygon);
+ Common::fill(_targetQuads.begin(), _targetQuads.end(), -1);
+ Pair<uint, uint> sharedPoints[2];
+ for (uint outerI = 0; outerI < polygonCount(); outerI++) {
+ const auto outer = at(outerI);
+ for (uint innerI = outerI + 1; innerI < polygonCount(); innerI++) {
+ const auto inner = at(innerI);
+ uint sharedPointCount = outer.findSharedPoints(inner, { sharedPoints, 2 });
+ if (sharedPointCount > 0)
+ setupLinkPoint(outer, inner, sharedPoints[0]);
+ if (sharedPointCount > 1) {
+ auto outerPoints = orderPoints(outer, sharedPoints[0].first, sharedPoints[1].first);
+ auto innerPoints = orderPoints(inner, sharedPoints[0].second, sharedPoints[1].second);
+ setupLinkEdge(outer, inner, outerPoints.first, outerPoints.second, innerPoints.first);
+ setupLinkPoint(outer, inner, sharedPoints[1]);
+ }
+ }
+ }
+}
+
+void PathFindingShape::setupLinkPoint(
+ const PathFindingPolygon &outer,
+ const PathFindingPolygon &inner,
+ PathFindingPolygon::SharedPoint pointI) {
+ auto &outerLink = _linkIndices[outer._index]._points[pointI.first];
+ auto &innerLink = _linkIndices[inner._index]._points[pointI.second];
+ if (outerLink.first < 0) {
+ outerLink.first = _linkPoints.size();
+ _linkPoints.push_back(outer._points[pointI.first]);
+ }
+ innerLink.first = outerLink.first;
+}
+
+void PathFindingShape::setupLinkEdge(
+ const PathFindingPolygon &outer,
+ const PathFindingPolygon &inner,
+ int32 outerP1, int32 outerP2, int32 innerP) {
+ _targetQuads[outer._index * kPointsPerPolygon + outerP1] = inner._index;
+ _targetQuads[inner._index * kPointsPerPolygon + innerP] = outer._index;
+ auto &outerLink = _linkIndices[outer._index]._points[outerP1];
+ auto &innerLink = _linkIndices[inner._index]._points[innerP];
+ if (outerLink.second < 0) {
+ outerLink.second = _linkPoints.size();
+ _linkPoints.push_back((outer._points[outerP1] + outer._points[outerP2]) / 2);
+ }
+ innerLink.second = outerLink.second;
+}
+
+void PathFindingShape::initializeFloydWarshall() {
+ _distanceMatrix.resize(_linkPoints.size() * _linkPoints.size());
+ _previousTarget.resize(_linkPoints.size() * _linkPoints.size());
+ Common::fill(_distanceMatrix.begin(), _distanceMatrix.end(), UINT_MAX);
+ Common::fill(_previousTarget.begin(), _previousTarget.end(), -1);
+
+ // every linkpoint is the shortest path to itself
+ for (uint i = 0; i < _linkPoints.size(); i++) {
+ _distanceMatrix[i * _linkPoints.size() + i] = 0;
+ _previousTarget[i * _linkPoints.size() + i] = i;
+ }
+
+ // every linkpoint to linkpoint within the same polygon *is* the shortest path
+ // between them. Therefore these are our initial paths for Floyd-Warshall
+ for (const auto &linkPolygon : _linkIndices) {
+ for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
+ LinkIndex linkFrom = linkPolygon._points[i / 2];
+ int32 linkFromI = i % 2 ? linkFrom.second : linkFrom.first;
+ if (linkFromI < 0)
+ continue;
+ for (uint j = i + 1; j < 2 * kPointsPerPolygon; j++) {
+ LinkIndex linkTo = linkPolygon._points[j / 2];
+ int32 linkToI = j % 2 ? linkTo.second : linkTo.first;
+ if (linkToI >= 0) {
+ const int32 linkFromFullI = linkFromI * _linkPoints.size() + linkToI;
+ const int32 linkToFullI = linkToI * _linkPoints.size() + linkFromI;
+ _distanceMatrix[linkFromFullI] = _distanceMatrix[linkToFullI] =
+ (uint)sqrtf(_linkPoints[linkFromI].sqrDist(_linkPoints[linkToI]) + 0.5f);
+ _previousTarget[linkFromFullI] = linkFromI;
+ _previousTarget[linkToFullI] = linkToI;
+ }
+ }
+ }
+ }
+}
+
+void PathFindingShape::calculateFloydWarshall() {
+ const auto distance = [&](uint a, uint b) -> uint& {
+ return _distanceMatrix[a * _linkPoints.size() + b];
+ };
+ const auto previousTarget = [&](uint a, uint b) -> int32& {
+ return _previousTarget[a * _linkPoints.size() + b];
+ };
+ for (uint over = 0; over < _linkPoints.size(); over++) {
+ for (uint from = 0; from < _linkPoints.size(); from++) {
+ for (uint to = 0; to < _linkPoints.size(); to++) {
+ if (distance(from, over) != UINT_MAX && distance(over, to) != UINT_MAX &&
+ distance(from, over) + distance(over, to) < distance(from, to)) {
+ distance(from, to) = distance(from, over) + distance(over, to);
+ previousTarget(from, to) = previousTarget(over, to);
+ }
+ }
+ }
+ }
+
+ // in the game all floors should be fully connected
+ assert(find(_previousTarget.begin(), _previousTarget.end(), -1) == _previousTarget.end());
+}
+
+bool PathFindingShape::findPath(const Point &from, const Point &to_, Stack<Point> &path) const {
+ Point to = to_; // we might want to correct it
+ path.clear();
+
+ int32 fromContaining = polygonContaining(from);
+ if (fromContaining < 0)
+ return false;
+ int32 toContaining = polygonContaining(to);
+ if (toContaining < 0) {
+ to = getClosestPoint(to);
+ toContaining = polygonContaining(to);
+ assert(toContaining >= 0);
+ }
+ //if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
+ if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
+ path.push(to);
+ return true;
+ }
+ floydWarshallPath(from, to, fromContaining, toContaining, path);
+ return true;
+}
+
+bool PathFindingShape::canGoStraightThrough(
+ const Point &from, const Point &to,
+ int32 fromContainingI, int32 toContainingI) const {
+ int32 lastContainingI = -1;
+ while (fromContainingI != toContainingI) {
+ auto toContaining = at(toContainingI);
+ bool foundPortal = false;
+ for (uint i = 0; i < toContaining._points.size(); i++) {
+ uint fullI = toContainingI * kPointsPerPolygon + i;
+ if (_targetQuads[fullI] < 0 || _targetQuads[fullI] == lastContainingI)
+ continue;
+
+ uint j = i + 1 == toContaining._points.size() ? 0 : i + 1;
+ if (segmentsIntersect(from, to, toContaining._points[i], toContaining._points[j])) {
+ foundPortal = true;
+ lastContainingI = toContainingI;
+ toContainingI = _targetQuads[fullI];
+ break;
+ }
+ }
+ if (!foundPortal)
+ return false;
+ }
+ return true;
+}
+
+void PathFindingShape::floydWarshallPath(
+ const Point &from, const Point &to,
+ int32 fromContaining, int32 toContaining,
+ Stack<Point> &path) const {
+ path.push(to);
+ // first find the tuple of link points to be used
+ uint fromLink = UINT_MAX, toLink = UINT_MAX, bestDistance = UINT_MAX;
+ const auto &fromIndices = _linkIndices[fromContaining];
+ const auto &toIndices = _linkIndices[toContaining];
+ for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
+ const auto &curFromPoint = fromIndices._points[i / 2];
+ int32 curFromLink = i % 2 ? curFromPoint.second : curFromPoint.first;
+ if (curFromLink < 0)
+ continue;
+ uint curFromDistance = (uint)sqrtf(from.sqrDist(_linkPoints[curFromLink]) + 0.5f);
+
+ for (uint j = 0; j < 2 * kPointsPerPolygon; j++) {
+ const auto &curToPoint = toIndices._points[j / 2];
+ int32 curToLink = j % 2 ? curToPoint.second : curToPoint.first;
+ if (curToLink < 0)
+ continue;
+ uint totalDistance =
+ curFromDistance +
+ _distanceMatrix[curFromLink * _linkPoints.size() + curToLink] +
+ (uint)sqrtf(to.sqrDist(_linkPoints[curToLink]) + 0.5f);
+ if (totalDistance < bestDistance) {
+ bestDistance = totalDistance;
+ fromLink = curFromLink;
+ toLink = curToLink;
+ }
+ }
+ }
+ assert(fromLink != UINT_MAX && toLink != UINT_MAX);
+
+ // then walk the matrix back to reconstruct the path
+ while (fromLink != toLink) {
+ path.push(_linkPoints[toLink]);
+ toLink = _previousTarget[fromLink * _linkPoints.size() + toLink];
+ assert(toLink < _linkPoints.size());
+ }
+ path.push(_linkPoints[fromLink]);
+}
+
+Point PathFindingShape::getClosestPoint(const Point &query) const {
+ assert(!_points.empty());
+ Point bestPoint;
+ uint bestDistance = UINT_MAX;
+ for (auto p : _points) {
+ uint curDistance = query.sqrDist(p);
+ if (curDistance < bestDistance) {
+ bestDistance = curDistance;
+ bestPoint = p;
+ }
+ }
+
+ assert(bestDistance < UINT_MAX);
+ return bestPoint;
+}
+
FloorColorShape::FloorColorShape() {}
FloorColorShape::FloorColorShape(ReadStream &stream) {
@@ -290,6 +564,7 @@ FloorColorShape::FloorColorShape(ReadStream &stream) {
FloorColorPolygon FloorColorShape::at(uint index) const {
auto range = _polygons[index];
FloorColorPolygon p;
+ p._index = index;
p._points = Span<const Point>(_points.data() + range.first, range.second);
p._pointColors = Span<const Color>(_pointColors.data() + range.first, range.second);
return p;
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index a74335763ca..ca527fc8c17 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -24,6 +24,7 @@
#include "common/stream.h"
#include "common/array.h"
+#include "common/stack.h"
#include "common/rect.h"
#include "common/span.h"
#include "common/util.h"
@@ -40,6 +41,7 @@ struct EdgeDistances {
};
struct Polygon {
+ uint _index;
Common::Span<const Common::Point> _points;
bool contains(const Common::Point &query) const;
@@ -50,7 +52,10 @@ struct PathFindingPolygon : Polygon {
Common::Span<const uint8> _pointDepths;
int8 _order;
+ using SharedPoint = Common::Pair<uint, uint>;
+
float depthAt(const Common::Point &query) const;
+ uint findSharedPoints(const PathFindingPolygon &other, Common::Span<SharedPoint> sharedPoints) const;
};
struct FloorColorPolygon : Polygon {
@@ -128,15 +133,13 @@ protected:
/**
* @brief Path finding is based on the Shape class with the invariant that
- * every polygon is a convex quad.
- * Equal points of different quads link them together, for edges we add an
- * additional link point in the center of the edge.
+ * every polygon is a convex polygon with at most four points.
+ * Equal points of different quads link them together, for shared edges we
+ * add an additional link point in the center of the edge.
*
* The resulting graph is processed using Floyd-Warshall to precalculate for
* the actual path finding. Additionally we check whether a character can
* walk straight through an edge instead of following the link points.
- *
- * None of this is implemented yet by the way ;)
*/
class PathFindingShape final : public Shape {
public:
@@ -152,10 +155,63 @@ public:
PathFindingPolygon at(uint index) const;
int8 orderAt(const Common::Point &query) const;
float depthAt(const Common::Point &query) const;
+ bool findPath(
+ const Common::Point &from,
+ const Common::Point &to,
+ Common::Stack<Common::Point> &path) const;
+ Common::Point getClosestPoint(const Common::Point &query) const;
+
+private: //ASDJALSKDJALKXDJALSKDJALSKDJALKjs
+public:
+ void setupLinks();
+ void setupLinkPoint(
+ const PathFindingPolygon &outer,
+ const PathFindingPolygon &inner,
+ PathFindingPolygon::SharedPoint pointI);
+ void setupLinkEdge(
+ const PathFindingPolygon &outer,
+ const PathFindingPolygon &inner,
+ int32 outerP1, int32 outerP2, int32 innerP);
+ void initializeFloydWarshall();
+ void calculateFloydWarshall();
+ bool canGoStraightThrough(
+ const Common::Point &from,
+ const Common::Point &to,
+ int32 fromContaining, int32 toContaining) const;
+ void floydWarshallPath(
+ const Common::Point &from,
+ const Common::Point &to,
+ int32 fromContaining, int32 toContaining,
+ Common::Stack<Common::Point> &path) const;
-private:
Common::Array<uint8> _pointDepths;
Common::Array<int8> _polygonOrders;
+
+ /**
+ * These are the edges in the graph, they are either points
+ * that are shared by two polygons or artificial points in
+ * the center of a shared edge
+ */
+ Common::Array<Common::Point> _linkPoints;
+ /**
+ * For each point of each polygon the index (or -1) to
+ * the corresponding link point. The second point is the
+ * index to the artifical center point
+ */
+ using LinkIndex = Common::Pair<int32, int32>;
+ struct LinkPolygonIndices {
+ LinkPolygonIndices();
+ LinkIndex _points[kPointsPerPolygon];
+ };
+ Common::Array<LinkPolygonIndices> _linkIndices;
+ /**
+ * For the going-straight-through-edges check we need
+ * to know for each shared edge (defined by the starting point)
+ * into which quad we will walk.
+ */
+ Common::Array<int32> _targetQuads;
+ Common::Array<uint> _distanceMatrix; ///< for Floyd-Warshall
+ Common::Array<int32> _previousTarget; ///< for Floyd-Warshall
};
using OptionalColor = Common::Pair<bool, Color>;
Commit: 9fd0d742428835fabf0869f0c898d9ee38db0777
https://github.com/scummvm/scummvm/commit/9fd0d742428835fabf0869f0c898d9ee38db0777
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add drawing characters
Changed paths:
A engines/alcachofa/player.h
engines/alcachofa/Input.cpp
engines/alcachofa/Input.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/Input.cpp b/engines/alcachofa/Input.cpp
index e227fc59492..86c9181b86f 100644
--- a/engines/alcachofa/Input.cpp
+++ b/engines/alcachofa/Input.cpp
@@ -29,6 +29,8 @@ namespace Alcachofa {
void Input::nextFrame() {
_wasMouseLeftPressed = false;
_wasMouseRightPressed = false;
+ _wasMouseLeftReleased = false;
+ _wasMouseRightReleased = false;
}
bool Input::handleEvent(const Common::Event &event) {
@@ -38,6 +40,7 @@ bool Input::handleEvent(const Common::Event &event) {
_isMouseLeftDown = true;
return true;
case EVENT_LBUTTONUP:
+ _wasMouseLeftReleased = true;
_isMouseLeftDown = false;
return true;
case EVENT_RBUTTONDOWN:
@@ -45,6 +48,7 @@ bool Input::handleEvent(const Common::Event &event) {
_isMouseRightDown = true;
return true;
case EVENT_RBUTTONUP:
+ _wasMouseRightReleased = true;
_isMouseRightDown = false;
return true;
case EVENT_MOUSEMOVE: {
diff --git a/engines/alcachofa/Input.h b/engines/alcachofa/Input.h
index a648d72b848..ee6028ad355 100644
--- a/engines/alcachofa/Input.h
+++ b/engines/alcachofa/Input.h
@@ -30,6 +30,10 @@ class Input {
public:
inline bool wasMouseLeftPressed() const { return _wasMouseLeftPressed; }
inline bool wasMouseRightPressed() const { return _wasMouseRightPressed; }
+ inline bool wasAnyMousePressed() const { return _wasMouseLeftPressed || _wasMouseRightPressed; }
+ inline bool wasMouseLeftReleased() const { return _wasMouseLeftReleased; }
+ inline bool wasMouseRightReleased() const { return _wasMouseRightReleased; }
+ inline bool wasAnyMouseReleased() const { return _wasMouseLeftReleased || _wasMouseRightReleased; }
inline bool isMouseLeftDown() const { return _isMouseLeftDown; }
inline bool isMouseRightDown() const { return _isMouseRightDown; }
inline const Common::Point &mousePos2D() const { return _mousePos2D; }
@@ -42,6 +46,8 @@ private:
bool
_wasMouseLeftPressed,
_wasMouseRightPressed,
+ _wasMouseLeftReleased,
+ _wasMouseRightReleased,
_isMouseLeftDown,
_isMouseRightDown;
Common::Point
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 76d3c998b7d..25363ded13f 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -62,9 +62,9 @@ Common::Error AlcachofaEngine::run() {
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
- world().globalRoom().loadResources();
+ //world().globalRoom().loadResources();
- auto room = world().getRoomByName("CASA_FREDDY_ARRIBA");
+ auto room = world().getRoomByName("SALOON");
assert(room != nullptr);
world().currentRoom() = room;
room->loadResources();
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 3a183acd868..913da07cbea 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -37,6 +37,7 @@
#include "alcachofa/detection.h"
#include "alcachofa/camera.h"
#include "alcachofa/input.h"
+#include "alcachofa/player.h"
#include "alcachofa/console.h"
namespace Alcachofa {
@@ -61,6 +62,7 @@ public:
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
inline Input &input() { return _input; }
+ inline Player &player() { return _player; }
inline World &world() { return *_world; }
inline Console &console() { return *_console; }
@@ -114,6 +116,7 @@ private:
Common::ScopedPtr<World> _world;
Camera _camera;
Input _input;
+ Player _player;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index aed5c63bded..2a50d20e85e 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -25,6 +25,7 @@ namespace Alcachofa {
Console::Console() : GUI::Debugger() {
registerVar("showInteractables", &_showInteractables);
+ registerVar("showCharacters", &_showCharacters);
registerVar("showFloor", &_showFloor);
registerVar("showFloorColor", &_showFloorColor);
}
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 77de4df1744..cc250e38524 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -33,18 +33,21 @@ public:
~Console() override;
inline bool showInteractables() const { return _showInteractables; }
+ inline bool showCharacters() const { return _showCharacters; }
inline bool showFloor() const { return _showFloor; }
inline bool showFloorColor() const { return _showFloorColor; }
inline bool isAnyDebugDrawingOn() const {
return
_showInteractables ||
+ _showCharacters ||
_showFloor ||
_showFloorColor;
}
private:
bool _showInteractables = true;
+ bool _showCharacters = true;
bool _showFloor = true;
bool _showFloorColor = false;
};
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 3b3085d79f3..0d724001809 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -68,6 +68,69 @@ Character::Character(Room *room, ReadStream &stream)
_order = _graphicNormal.order();
}
+static Graphic *graphicOf(ObjectBase *object, Graphic *fallback = nullptr) {
+ auto objectGraphic = object == nullptr ? nullptr : object->graphic();
+ return objectGraphic == nullptr ? fallback : objectGraphic;
+}
+
+void Character::update() {
+ if (!isEnabled())
+ return;
+ updateSelection();
+
+ Graphic *animateGraphic = graphicOf(_curAnimateObject);
+ if (animateGraphic != nullptr) {
+ animateGraphic->center() = Point(0, 0);
+ animateGraphic->update();
+ }
+ else if (_isTalking)
+ updateTalkingAnimation();
+ else if (g_engine->world().somebodyUsing(this)) {
+ Graphic *talkGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ talkGraphic->start(true);
+ talkGraphic->pause();
+ talkGraphic->update();
+ }
+ else
+ _graphicNormal.update();
+}
+
+void Character::updateTalkingAnimation() {
+ Graphic *talkGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ if (!_isTalking) {
+ talkGraphic->reset();
+ return;
+ }
+ // TODO: Add lip-sync(?) animation behavior
+ talkGraphic->update();
+}
+
+void Character::draw() {
+ if (!isEnabled())
+ return;
+ Graphic *activeGraphic = graphic();
+ assert(activeGraphic != nullptr);
+ g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
+}
+
+void Character::drawDebug() {
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (!g_engine->console().showCharacters() || renderer == nullptr || !isEnabled())
+ return;
+
+ renderer->debugShape(*shape());
+}
+
+void Character::loadResources() {
+ _graphicNormal.loadResources();
+ _graphicTalking.loadResources();
+}
+
+void Character::freeResources() {
+ _graphicNormal.freeResources();
+ _graphicTalking.freeResources();
+}
+
void Character::serializeSave(Serializer &serializer) {
ShapeObject::serializeSave(serializer);
serializer.syncAsByte(_isTalking);
@@ -79,6 +142,15 @@ void Character::serializeSave(Serializer &serializer) {
serializer.syncAsFloatLE(_lodBias);
}
+Graphic *Character::graphic() {
+ Graphic *activeGraphic = graphicOf(_curAnimateObject);
+ if (activeGraphic == nullptr && (_isTalking || g_engine->world().somebodyUsing(this)))
+ activeGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ if (activeGraphic == nullptr)
+ activeGraphic = &_graphicNormal;
+ return activeGraphic;
+}
+
void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object) {
String name;
if (serializer.isSaving() && object != nullptr)
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index c0ae21dbb84..f16fc2c56ed 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -154,6 +154,15 @@ ShapeObject::ShapeObject(Room *room, ReadStream &stream)
, _cursorType((CursorType)stream.readSint32LE()) {
}
+void ShapeObject::update() {
+ if (isEnabled())
+ updateSelection();
+ else {
+ _isSelected = false;
+ _wasSelected = false;
+ }
+}
+
void ShapeObject::serializeSave(Serializer &serializer) {
serializer.syncAsSByte(_order);
}
@@ -166,6 +175,45 @@ CursorType ShapeObject::cursorType() const {
return _cursorType;
}
+void ShapeObject::onHoverStart() {
+ onHoverUpdate();
+}
+
+void ShapeObject::onHoverEnd() {
+}
+
+void ShapeObject::onHoverUpdate() {
+ // TODO: Add text request for name
+}
+
+void ShapeObject::onClick() {
+ onHoverUpdate();
+}
+
+void ShapeObject::markSelected() {
+ _isSelected = true;
+}
+
+void ShapeObject::updateSelection() {
+ if (_isSelected) {
+ _isSelected = false;
+ if (_wasSelected) {
+ if (g_engine->input().wasAnyMouseReleased() && g_engine->player().selectedObject() == this)
+ onClick();
+ else
+ onHoverUpdate();
+ }
+ else {
+ _wasSelected = true;
+ onHoverStart();
+ }
+ }
+ else if (_wasSelected) {
+ _wasSelected = false;
+ onHoverEnd();
+ }
+}
+
PhysicalObject::PhysicalObject(Room *room, ReadStream &stream)
: ShapeObject(room, stream) {
_order = stream.readSByte();
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index ecded3d37c4..28e08c12776 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -253,7 +253,7 @@ static void fullBlend(const ManagedSurface &source, ManagedSurface &destination,
assert(offsetY >= 0 && offsetY + source.h <= destination.h);
const byte *sourceLine = (byte *)source.getPixels();
- byte *destinationLine = (byte *)destination.getPixels() + offsetY * source.pitch + offsetX * 4;
+ byte *destinationLine = (byte *)destination.getPixels() + offsetY * destination.pitch + offsetX * 4;
for (int y = 0; y < source.h; y++) {
const byte *sourcePixel = sourceLine;
byte *destPixel = destinationLine;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 37d4de22251..4ce0412146e 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -116,17 +116,26 @@ public:
ShapeObject(Room *room, Common::ReadStream &stream);
virtual ~ShapeObject() override = default;
+ virtual void update() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Shape *shape() override;
virtual CursorType cursorType() const;
-
-private:
- Shape _shape;
- CursorType _cursorType;
+ virtual void onHoverStart();
+ virtual void onHoverEnd();
+ virtual void onHoverUpdate();
+ virtual void onClick();
+ void markSelected();
protected:
+ void updateSelection();
+
// original inconsistency: base class has member that is read by the sub classes
int8 _order = 0;
+private:
+ Shape _shape;
+ CursorType _cursorType;
+ bool _isSelected = false,
+ _wasSelected = false;
};
class PhysicalObject : public ShapeObject {
@@ -302,10 +311,17 @@ public:
Character(Room *room, Common::ReadStream &stream);
virtual ~Character() override = default;
+ virtual void update() override;
+ virtual void draw() override;
+ virtual void drawDebug() override;
+ virtual void loadResources() override;
+ virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual Graphic *graphic() override;
protected:
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
+ void updateTalkingAnimation();
private:
Common::Point _interactionPoint;
@@ -365,6 +381,7 @@ public:
virtual ~MainCharacter() override;
inline MainCharacterKind kind() const { return _kind; }
+ inline ObjectBase *currentlyUsing() const { return _currentlyUsingObject; }
virtual void serializeSave(Common::Serializer &serializer) override;
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
new file mode 100644
index 00000000000..b0abfa21f7e
--- /dev/null
+++ b/engines/alcachofa/player.h
@@ -0,0 +1,39 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PLAYER_H
+#define PLAYER_H
+
+namespace Alcachofa {
+
+class ShapeObject;
+
+class Player {
+public:
+ inline ShapeObject *selectedObject() { return _selectedObject; }
+
+private:
+ ShapeObject *_selectedObject = nullptr;
+};
+
+}
+
+#endif // PLAYER_H
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index f675738b950..e35fa824ac7 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -134,7 +134,7 @@ void Room::update() {
if (world().currentRoom() == this) {
g_engine->camera().update();
drawObjects();
- world().globalRoom().drawObjects();
+ // TODO: world().globalRoom().drawObjects();
// TODO: Draw black borders
g_engine->drawQueue().draw();
drawDebug();
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 7678552c69d..e50c10ceb8f 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -124,6 +124,11 @@ public:
inline Room *¤tRoom() { return _currentRoom; }
inline Room *currentRoom() const { return _currentRoom; }
+ inline bool somebodyUsing(ObjectBase *object) const {
+ return filemon().currentlyUsing() == object ||
+ mortadelo().currentlyUsing() == object;
+ }
+
MainCharacter &getMainCharacterByKind(MainCharacterKind kind) const;
Room *getRoomByName(const Common::String &name) const;
ObjectBase *getObjectByName(const Common::String &name) const;
Commit: bc3380d4aca21b4cbc22af6574368f5d8e3d4570
https://github.com/scummvm/scummvm/commit/bc3380d4aca21b4cbc22af6574368f5d8e3d4570
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add walking characters
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
engines/alcachofa/stream-helper.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 25363ded13f..a536098f100 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -62,7 +62,7 @@ Common::Error AlcachofaEngine::run() {
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
- //world().globalRoom().loadResources();
+ world().globalRoom().loadResources();
auto room = world().getRoomByName("SALOON");
assert(room != nullptr);
@@ -74,11 +74,11 @@ Common::Error AlcachofaEngine::run() {
if (saveSlot != -1)
(void)loadGameState(saveSlot);
- g_system->showMouse(true);
Common::Event e;
Graphics::FrameLimiter limiter(g_system, 60);
while (!shouldQuit()) {
+ g_system->showMouse(true);
_input.nextFrame();
while (g_system->getEventManager()->pollEvent(e)) {
if (_input.handleEvent(e))
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 8f9669d5ea2..92c8c636704 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -37,9 +37,11 @@ enum class CursorType {
enum class Direction {
Up,
+ Right,
Down,
Left,
- Right
+
+ Invalid = -1
};
constexpr const int32 kDirectionCount = 4;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 0d724001809..c44ea36f1f2 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -65,6 +65,7 @@ Character::Character(Room *room, ReadStream &stream)
, _graphicNormal(stream)
, _graphicTalking(stream) {
_graphicNormal.start(true);
+ _graphicNormal.frameI() = _graphicTalking.frameI() = 0;
_order = _graphicNormal.order();
}
@@ -180,20 +181,266 @@ WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
}
for (int32 i = 0; i < kDirectionCount; i++) {
auto fileName = readVarString(stream);
- _standingAnimations[i].reset(new Animation(Common::move(fileName)));
+ _talkingAnimations[i].reset(new Animation(Common::move(fileName)));
+ }
+}
+
+void WalkingCharacter::update() {
+ Character::update();
+ if (!isEnabled())
+ return;
+ updateWalking();
+
+ auto activeFloor = room()->activeFloor();
+ if (activeFloor != nullptr) {
+ if (activeFloor->polygonContaining(_sourcePos) < 0)
+ _sourcePos = _currentPos = activeFloor->getClosestPoint(_sourcePos);
+ if (activeFloor->polygonContaining(_currentPos) < 0)
+ _currentPos = activeFloor->getClosestPoint(_currentPos);
+ }
+
+ if (!_isWalking) {
+ _graphicTalking.setAnimation(talkingAnimation());
+ updateTalkingAnimation();
+ _currentPos = _sourcePos;
+ }
+
+ _graphicNormal.center() = _graphicTalking.center() = _currentPos;
+ auto animateGraphic = graphicOf(_curAnimateObject);
+ auto talkingGraphic = graphicOf(_curTalkingObject);
+ if (animateGraphic != nullptr)
+ animateGraphic->center() = _currentPos;
+ if (talkingGraphic != nullptr)
+ talkingGraphic->center() = _currentPos;
+ if (room() != &g_engine->world().globalRoom()) {
+ float depth = room()->depthAt(_currentPos);
+ int8 order = room()->orderAt(_currentPos);
+ _graphicNormal.order() = _graphicTalking.order() = order;
+ _graphicNormal.depthScale() = _graphicTalking.depthScale() = depth;
+ if (animateGraphic != nullptr) {
+ animateGraphic->order() = order;
+ animateGraphic->depthScale() = depth;
+ }
+ if (talkingGraphic != nullptr) {
+ talkingGraphic->order() = order;
+ talkingGraphic->depthScale() = depth;
+ }
+ }
+
+ _interactionPoint = _currentPos;
+ _interactionDirection1 = Direction::Right;
+ if (this != g_engine->world().activeCharacter()) {
+ int16 interactionOffset = (int16)(150 * _graphicNormal.depthScale());
+ _interactionPoint.x -= interactionOffset;
+ if (activeFloor != nullptr && activeFloor->polygonContaining(_interactionPoint) < 0) {
+ _interactionPoint.x = _currentPos.x + interactionOffset;
+ _interactionDirection1 = Direction::Left;
+ }
+ }
+}
+
+static Direction getDirection(const Point &from, const Point &to) {
+ Point delta = from - to;
+ if (from.x == to.x)
+ return from.y < to.y ? Direction::Up : Direction::Down;
+ else if (from.x < to.x) {
+ int slope = 1000 * delta.y / -delta.x;
+ return slope > 1000 ? Direction::Up
+ : slope < -1000 ? Direction::Down
+ : Direction::Right;
+ }
+ else { // from.x > to.x
+ int slope = 1000 * delta.y / delta.x;
+ return slope > 1000 ? Direction::Up
+ : slope < -1000 ? Direction::Down
+ : Direction::Left;
+ }
+}
+
+void WalkingCharacter::updateWalking() {
+ if (!_isWalking)
+ return;
+ static constexpr float kHigherStepSizeThreshold = 0x4CCC / 65535.0f;
+ static constexpr float kMinStepSizeFactor = 0x3333 / 65535.0f;
+ _stepSizeFactor = _graphicNormal.depthScale();
+ if (_stepSizeFactor < kHigherStepSizeThreshold)
+ _stepSizeFactor = _stepSizeFactor / 3.0f + kMinStepSizeFactor;
+
+ Point targetPos = _pathPoints.top();
+ if (_sourcePos == targetPos) {
+ _currentPos = targetPos;
+ _pathPoints.pop();
+ }
+ else {
+ updateWalkingAnimation();
+ const int32 distanceToTarget = (int32)(sqrtf(_sourcePos.sqrDist(targetPos)));
+ if (_walkedDistance < distanceToTarget) {
+ // separated because having only 16 bits and multiplications seems dangerous
+ _currentPos.x = _sourcePos.x + _walkedDistance * (targetPos.x - _sourcePos.x) / distanceToTarget;
+ _currentPos.y = _sourcePos.y + _walkedDistance * (targetPos.y - _sourcePos.y) / distanceToTarget;
+ }
+ else {
+ _sourcePos = _currentPos = targetPos;
+ _pathPoints.pop();
+ _walkedDistance = 1;
+ _lastWalkAnimFrame = 0;
+ }
+ }
+
+ if (_pathPoints.empty()) {
+ _isWalking = false;
+ _currentPos = _sourcePos = targetPos;
+ if (_endWalkingDirection != Direction::Invalid)
+ _direction = _endWalkingDirection;
+ onArrived();
+ }
+ _graphicNormal.center() = _currentPos;
+}
+
+void WalkingCharacter::updateWalkingAnimation()
+{
+ _direction = getDirection(_sourcePos, _pathPoints.top());
+ auto animation = walkingAnimation();
+ _graphicNormal.setAnimation(animation);
+
+ // this is very confusing. Let's see what it does
+ const int32 halfFrameCount = (int32)animation->frameCount() / 2;
+ int32 expectedFrame = (int32)(g_system->getMillis() - _graphicNormal.lastTime()) * 12 / 1000;
+ const bool isUnexpectedFrame = expectedFrame != _lastWalkAnimFrame;
+ int32 stepFrameFrom, stepFrameTo;
+ if (expectedFrame < halfFrameCount - 1) {
+ _lastWalkAnimFrame = expectedFrame;
+ stepFrameFrom = 2 * expectedFrame - 2;
+ stepFrameTo = 2 * expectedFrame;
+ }
+ else {
+ const int32 frameThreshold = _lastWalkAnimFrame <= halfFrameCount - 1
+ ? _lastWalkAnimFrame
+ : (_lastWalkAnimFrame - halfFrameCount + 1) % (halfFrameCount - 2) + 1;
+ _lastWalkAnimFrame = expectedFrame;
+ expectedFrame = (expectedFrame - halfFrameCount + 1) % (halfFrameCount - 2) + 1;
+ if (expectedFrame >= frameThreshold) {
+ stepFrameFrom = 2 * expectedFrame - 2;
+ stepFrameTo = 2 * expectedFrame;
+ }
+ else {
+ stepFrameFrom = 2 * halfFrameCount - 4;
+ stepFrameTo = 2 * halfFrameCount - 2;
+ }
+ }
+ if (isUnexpectedFrame) {
+ const uint stepSize = (uint)sqrtf(animation->frameCenter(stepFrameFrom).sqrDist(animation->frameCenter(stepFrameTo)));
+ _walkedDistance += (int32)(stepSize * _stepSizeFactor);
+ }
+ _graphicNormal.frameI() = 2 * expectedFrame; // especially this: wtf?
+}
+
+void WalkingCharacter::onArrived() {
+}
+
+void WalkingCharacter::stopWalkingAndTurn(Direction direction) {
+ _isWalking = false;
+ _direction = direction;
+}
+
+void WalkingCharacter::walkTo(
+ const Point &target, Direction endDirection,
+ ShapeObject *activateObject, const char *activateAction,
+ bool useAlternateObjectDirection) {
+ // all the activation parameters are only relevant for MainCharacter
+
+ if (_isWalking)
+ _sourcePos = _currentPos;
+ else {
+ _lastWalkAnimFrame = 0;
+ int32 prevWalkFrame = _graphicNormal.frameI();
+ _graphicNormal.reset();
+ _graphicNormal.frameI() = prevWalkFrame;
+ }
+
+ _pathPoints.clear();
+ auto floor = room()->activeFloor();
+ if (floor != nullptr)
+ floor->findPath(_sourcePos, target, _pathPoints);
+ if (_pathPoints.empty()) {
+ _isWalking = false;
+ onArrived();
+ return;
+ }
+
+ _isWalking = true;
+ _endWalkingDirection = endDirection;
+ _walkedDistance = 0;
+ updateWalking();
+}
+
+void WalkingCharacter::setPosition(const Point &target) {
+ _isWalking = false;
+ _sourcePos = _currentPos = target;
+}
+
+void WalkingCharacter::draw() {
+ if (!isEnabled())
+ return;
+
+ Graphic *currentGraphic = graphicOf(_curAnimateObject);
+ if (currentGraphic == nullptr && _isWalking)
+ currentGraphic = &_graphicNormal;
+ if (currentGraphic == nullptr && g_engine->world().somebodyUsing(this)) {
+ currentGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ currentGraphic->start(true);
+ currentGraphic->pause();
+ }
+ if (currentGraphic == nullptr) {
+ // TODO: draw dialog line
+ currentGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ }
+
+ assert(currentGraphic != nullptr);
+ g_engine->drawQueue().add<AnimationDrawRequest>(*currentGraphic, true, BlendMode::AdditiveAlpha);
+}
+
+void WalkingCharacter::drawDebug() {
+ Character::drawDebug();
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (!g_engine->console().showCharacters() || renderer == nullptr || !isEnabled() || _pathPoints.empty())
+ return;
+
+ Array<Vector2d> points2D(_pathPoints.size() + 1);
+ _pathPoints.push(_sourcePos);
+ for (uint i = 0; i < _pathPoints.size(); i++) {
+ auto v = g_engine->camera().transform3Dto2D({ (float)_pathPoints[i].x, (float)_pathPoints[i].y, kBaseScale });
+ points2D[i] = { v.x(), v.y() };
+ }
+ _pathPoints.pop();
+ renderer->debugPolyline({ points2D.data(), points2D.size() }, kWhite);
+}
+
+void WalkingCharacter::loadResources() {
+ Character::loadResources();
+ for (int i = 0; i < kDirectionCount; i++) {
+ _walkingAnimations[i]->load();
+ _talkingAnimations[i]->load();
+ }
+}
+
+void WalkingCharacter::freeResources() {
+ Character::freeResources();
+ for (int i = 0; i < kDirectionCount; i++) {
+ _walkingAnimations[i]->freeImages();
+ _talkingAnimations[i]->freeImages();
}
}
void WalkingCharacter::serializeSave(Serializer &serializer) {
Character::serializeSave(serializer);
serializer.syncAsSint32LE(_lastWalkAnimFrame);
- serializer.syncAsSint32LE(_walkSpeed);
+ serializer.syncAsSint32LE(_walkedDistance);
syncPoint(serializer, _sourcePos);
- syncPoint(serializer, _targetPos);
+ syncPoint(serializer, _currentPos);
serializer.syncAsByte(_isWalking);
- syncArray(serializer, _pathPoints, syncPoint);
+ syncStack(serializer, _pathPoints, syncPoint);
syncEnum(serializer, _direction);
- _graphicWalking.serializeSave(serializer);
}
MainCharacter::MainCharacter(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 28e08c12776..60af943f46a 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -369,16 +369,18 @@ Graphic::Graphic(ReadStream &stream) {
_scale = stream.readSint16LE();
_order = stream.readSByte();
auto animationName = readVarString(stream);
- _animation.reset(new Animation(std::move(animationName)));
+ if (!animationName.empty())
+ setAnimation(animationName, AnimationFolder::Animations);
}
void Graphic::loadResources() {
- assert(_animation != nullptr);
- _animation->load();
+ if (_animation != nullptr)
+ _animation->load();
}
void Graphic::freeResources() {
- _animation.reset();
+ _ownedAnimation.reset();
+ _animation = nullptr;
}
void Graphic::update() {
@@ -420,7 +422,12 @@ void Graphic::reset() {
}
void Graphic::setAnimation(const Common::String &fileName, AnimationFolder folder) {
- _animation.reset(new Animation(fileName, folder));
+ _ownedAnimation.reset(new Animation(fileName, folder));
+ _animation = _ownedAnimation.get();
+}
+
+void Graphic::setAnimation(Animation *animation) {
+ _animation = animation;
}
void Graphic::serializeSave(Serializer &serializer) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 56ee902df88..72c6ce28225 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -172,6 +172,7 @@ public:
inline uint spriteCount() const { return _spriteBases.size(); }
inline uint frameCount() const { return _frames.size(); }
inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
+ inline const Common::Point &frameCenter(int32 frameI) const { return _frames[frameI]._center; }
inline uint32 totalDuration() const { return _totalDuration; }
int32 frameAtTime(uint32 time) const;
Common::Point imageSize(int32 imageI) const;
@@ -222,7 +223,10 @@ public:
inline Common::Point ¢er() { return _center; }
inline int8 &order() { return _order; }
inline int16 &scale() { return _scale; }
+ inline float &depthScale() { return _depthScale; }
inline Color &color() { return _color; }
+ inline int32 &frameI() { return _frameI; }
+ inline uint32 lastTime() const { return _lastTime; }
inline Animation &animation() {
assert(_animation != nullptr && _animation->isLoaded());
return *_animation;
@@ -235,12 +239,14 @@ public:
void pause();
void reset();
void setAnimation(const Common::String &fileName, AnimationFolder folder);
+ void setAnimation(Animation *animation); ///< no memory ownership is given, but for prerendering it has to be mutable
void serializeSave(Common::Serializer &serializer);
private:
friend class AnimationDrawRequest;
friend class SpecialEffectDrawRequest;
- Common::SharedPtr<Animation> _animation;
+ Common::ScopedPtr<Animation> _ownedAnimation;
+ Animation *_animation = nullptr;
Common::Point _center;
int16 _scale = kBaseScale;
int8 _order = 0;
@@ -253,17 +259,6 @@ private:
float _depthScale = 1.0f;
};
-enum class DrawRequestType {
- Animation2D,
- Animation3D,
- AnimationTiled,
- Rectangle,
- FadeToBlack,
- FadeToWhite,
- CrossFade,
- Text
-};
-
class IDrawRequest {
public:
IDrawRequest(int8 order);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 4ce0412146e..87d16526953 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -22,8 +22,8 @@
#ifndef OBJECTS_H
#define OBJECTS_H
-#include "Shape.h"
-#include "Graphics.h"
+#include "shape.h"
+#include "graphics.h"
#include "common/serializer.h"
@@ -323,7 +323,6 @@ protected:
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
void updateTalkingAnimation();
-private:
Common::Point _interactionPoint;
Direction _direction;
Graphic _graphicNormal, _graphicTalking;
@@ -342,24 +341,58 @@ public:
WalkingCharacter(Room *room, Common::ReadStream &stream);
virtual ~WalkingCharacter() override = default;
+ virtual void update() override;
+ virtual void draw() override;
+ virtual void drawDebug() override;
+ virtual void loadResources() override;
+ virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void walkTo(
+ const Common::Point &target,
+ Direction endDirection = Direction::Invalid,
+ ShapeObject *activateObject = nullptr,
+ const char *activateAction = nullptr,
+ bool useAlternateObjectDirection = false
+ );
+ void stopWalkingAndTurn(Direction direction);
+ void setPosition(const Common::Point &target);
+
+protected:
+ virtual void onArrived();
private:
- Graphic _graphicWalking;
- Common::SharedPtr<Animation>
+ void updateWalking();
+ void updateWalkingAnimation();
+
+ inline Animation *currentAnimationOf(Common::ScopedPtr<Animation> *const animations) {
+ Animation *animation = animations[(int)_direction].get();
+ if (animation == nullptr)
+ animation = animations[0].get();
+ assert(animation != nullptr);
+ return animation;
+ }
+ inline Animation *walkingAnimation() { return currentAnimationOf(_walkingAnimations); }
+ inline Animation *talkingAnimation() { return currentAnimationOf(_talkingAnimations); }
+
+ Common::ScopedPtr<Animation>
_walkingAnimations[kDirectionCount],
- _standingAnimations[kDirectionCount];
+ _talkingAnimations[kDirectionCount];
int32
_lastWalkAnimFrame = -1,
- _walkSpeed = 0,
+ _walkedDistance = 0,
_curPathPointI = -1;
+ float _stepSizeFactor = 0.0f;
Common::Point
_sourcePos,
- _targetPos;
+ _currentPos;
bool _isWalking = false;
- Direction _direction = Direction::Up;
- Common::Array<Common::Point> _pathPoints;
+ Direction
+ _direction = Direction::Right,
+ _interactionDirection1 = Direction::Right,
+ _interactionDirection2 = Direction::Right,
+ _endWalkingDirection = Direction::Invalid;
+ Common::Stack<Common::Point> _pathPoints;
};
enum class MainCharacterKind {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index e35fa824ac7..a0fc1de786c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -129,25 +129,20 @@ void Room::update() {
updateRoomBounds();
updateInput();
}
+ // TODO: Add condition for global room update
+ world().globalRoom().updateObjects();
if (world().currentRoom() == this)
updateObjects();
if (world().currentRoom() == this) {
g_engine->camera().update();
drawObjects();
- // TODO: world().globalRoom().drawObjects();
+ world().globalRoom().drawObjects();
// TODO: Draw black borders
g_engine->drawQueue().draw();
drawDebug();
world().globalRoom().drawDebug();
}
}
-using namespace Math;
-static Array<Vector2d> path;
-
-static Vector2d asVec(const Point &p) {
- return Vector2d((float)p.x, (float)p.y);
-}
-
void Room::updateInput() {
static bool hasLastP3D = false;
@@ -157,18 +152,16 @@ void Room::updateInput() {
if (g_engine->input().wasMouseLeftPressed()) {
Point p2d = g_engine->input().mousePos2D();
Point p3d = g_engine->input().mousePos3D();
+ auto m = &g_engine->world().filemon();
- if (hasLastP3D) {
- Stack<Point> pathi;
- bool result = _floors[0].findPath(lastP3D, p3d, pathi);
- path.clear();
- path.push_back(asVec(lastP3D));
- while (!pathi.empty())
- path.push_back(asVec(pathi.pop()));
- warning("Did %sfind a path in %d steps", result ? "" : "not ", path.size());
+ if (!hasLastP3D) {
+ m->setPosition(p3d);
+ }
+ else {
+ m->room() = this;
+ m->walkTo(p3d);
}
hasLastP3D = true;
- lastP3D = p3d;
}
}
@@ -180,9 +173,10 @@ void Room::updateRoomBounds() {
}
void Room::updateObjects() {
+ const auto *previousRoom = world().currentRoom();
for (auto *object : _objects) {
object->update();
- if (world().currentRoom() != this)
+ if (world().currentRoom() != previousRoom)
return;
}
}
@@ -202,15 +196,6 @@ void Room::drawDebug() {
return;
if (_activeFloorI >= 0 && g_engine->console().showFloor())
renderer->debugShape(_floors[_activeFloorI], kDebugBlue);
-
- renderer->debugPolyline({ path.begin(), path.size()}, kWhite);
-
- Common::Array<Vector2d> asd;
- for (auto p : _floors[0]._linkPoints)
- {
- auto v = asVec(p);
- renderer->debugPolyline({ &v, 1 }, { 255, 0, 255, 255 });
- }
}
void Room::loadResources() {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index e50c10ceb8f..d174620618e 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -36,6 +36,15 @@ public:
inline World &world() { return *_world; }
inline const Common::String &name() const { return _name; }
+ inline const PathFindingShape *activeFloor() const {
+ return _activeFloorI < 0 ? nullptr : &_floors[_activeFloorI];
+ }
+ inline int8 orderAt(const Common::Point &query) const {
+ return _activeFloorI < 0 ? 49 : activeFloor()->orderAt(query);
+ }
+ inline float depthAt(const Common::Point &query) const {
+ return _activeFloorI < 0 ? 1 : activeFloor()->depthAt(query);
+ }
void update();
virtual void updateInput();
@@ -118,6 +127,7 @@ public:
inline Inventory &inventory() const { return *_inventory; }
inline MainCharacter &filemon() const { return *_filemon; }
inline MainCharacter &mortadelo() const { return *_mortadelo; }
+ inline MainCharacter *activeCharacter() const { return _activeCharacter; }
inline const Common::String &initScriptName() const { return _initScriptName; }
inline uint8 loadedMapCount() const { return _loadedMapCount; }
@@ -142,7 +152,7 @@ private:
Common::String _initScriptName;
Room *_globalRoom, *_currentRoom = nullptr;
Inventory *_inventory;
- MainCharacter *_filemon, *_mortadelo;
+ MainCharacter *_filemon, *_mortadelo, *_activeCharacter = nullptr;
uint8 _loadedMapCount = 0;
};
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index dd12ab65ca8..eb9019997da 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -38,6 +38,8 @@ static int sideOfLine(const Point &a, const Point &b, const Point &q) {
static bool segmentsIntersect(const Point &a1, const Point &b1, const Point &a2, const Point &b2) {
// as there are a number of special cases to consider, this method is a direct translation
// of the original engine
+ // TODO: It is still bad and does sometimes not work correctly. Check this. keep in mind
+ // it *could* also be a case of incorrect floor segments being passed into in the first place.
const auto sideOfLine = [](const Point &a, const Point &b, const Point q) {
return Alcachofa::sideOfLine(a, b, q) > 0;
@@ -102,7 +104,7 @@ static float depthAtForConvex(const PathFindingPolygon &p, const Point &q) {
auto distances = p.edgeDistances(i, q);
float depthOnEdge = p._pointDepths[i] + distances._onEdge * (p._pointDepths[j] - p._pointDepths[i]) / distances._edgeLength;
if (distances._toEdge < epsilon) // q is directly on the edge
- return depthOnEdge;
+ return depthOnEdge * 0.01f;
sumDepths += 1 / distances._toEdge * depthOnEdge;
sumDistances += 1 / distances._toEdge;
}
@@ -517,6 +519,8 @@ void PathFindingShape::floydWarshallPath(
}
Point PathFindingShape::getClosestPoint(const Point &query) const {
+ // TODO: Improve this function, it does not seem correct
+
assert(!_points.empty());
Point bestPoint;
uint bestDistance = UINT_MAX;
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index ca527fc8c17..20168647c6f 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -161,8 +161,7 @@ public:
Common::Stack<Common::Point> &path) const;
Common::Point getClosestPoint(const Common::Point &query) const;
-private: //ASDJALSKDJALKXDJALSKDJALSKDJALKjs
-public:
+private:
void setupLinks();
void setupLinkPoint(
const PathFindingPolygon &outer,
diff --git a/engines/alcachofa/stream-helper.h b/engines/alcachofa/stream-helper.h
index 07bab1338a4..7c41ca41376 100644
--- a/engines/alcachofa/stream-helper.h
+++ b/engines/alcachofa/stream-helper.h
@@ -25,6 +25,7 @@
#include "common/stream.h"
#include "common/serializer.h"
#include "common/rect.h"
+#include "common/stack.h"
namespace Alcachofa {
@@ -43,6 +44,23 @@ inline void syncArray(Common::Serializer &serializer, Common::Array<T> &array, v
serializer.syncArray(array.data(), size, serializeFunction);
}
+template<typename T>
+inline void syncStack(Common::Serializer &serializer, Common::Stack<T> &stack, void (*serializeFunction)(Common::Serializer &, T &)) {
+ auto size = stack.size();
+ serializer.syncAsUint32LE(size);
+ if (serializer.isLoading()) {
+ for (uint i = 0; i < size; i++) {
+ T value;
+ serializeFunction(serializer, value);
+ stack.push(value);
+ }
+ }
+ else {
+ for (uint i = 0; i < size; i++)
+ serializeFunction(serializer, stack[i]);
+ }
+}
+
template<typename T>
inline void syncEnum(Common::Serializer &serializer, T &enumValue) {
// syncAs does not have a cast for saving
Commit: b0fc780802b633e97011be5e21ba3afb27d27215
https://github.com/scummvm/scummvm/commit/b0fc780802b633e97011be5e21ba3afb27d27215
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add parts of main characters
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index c44ea36f1f2..b5c894997a8 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -34,10 +34,13 @@ Item::Item(Room *room, ReadStream &stream)
stream.readByte(); // unused and ignored byte
}
+ITriggerableObject::ITriggerableObject(ReadStream &stream)
+ : _interactionPoint(Shape(stream).firstPoint())
+ , _interactionDirection((Direction)stream.readSint32LE()) {}
+
InteractableObject::InteractableObject(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
- , _interactionPoint(Shape(stream).firstPoint())
- , _cursorType((CursorType)stream.readSint32LE())
+ , ITriggerableObject(stream)
, _relatedObject(readVarString(stream)) {
_relatedObject.toUppercase();
}
@@ -50,6 +53,10 @@ void InteractableObject::drawDebug() {
renderer->debugShape(*shape());
}
+void InteractableObject::trigger(const char *action) {
+ warning("stub: Trigger object %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
+}
+
Door::Door(Room *room, ReadStream &stream)
: InteractableObject(room, stream)
, _targetRoom(readVarString(stream))
@@ -60,8 +67,7 @@ Door::Door(Room *room, ReadStream &stream)
Character::Character(Room *room, ReadStream &stream)
: ShapeObject(room, stream)
- , _interactionPoint(Shape(stream).firstPoint())
- , _direction((Direction)stream.readSint32LE())
+ , ITriggerableObject(stream)
, _graphicNormal(stream)
, _graphicTalking(stream) {
_graphicNormal.start(true);
@@ -173,6 +179,10 @@ void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object)
}
}
+void Character::trigger(const char *action) {
+ warning("stub: Trigger character %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
+}
+
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
: Character(room, stream) {
for (int32 i = 0; i < kDirectionCount; i++) {
@@ -228,13 +238,13 @@ void WalkingCharacter::update() {
}
_interactionPoint = _currentPos;
- _interactionDirection1 = Direction::Right;
+ _interactionDirection = Direction::Right;
if (this != g_engine->world().activeCharacter()) {
int16 interactionOffset = (int16)(150 * _graphicNormal.depthScale());
_interactionPoint.x -= interactionOffset;
if (activeFloor != nullptr && activeFloor->polygonContaining(_interactionPoint) < 0) {
_interactionPoint.x = _currentPos.x + interactionOffset;
- _interactionDirection1 = Direction::Left;
+ _interactionDirection = Direction::Left;
}
}
}
@@ -345,8 +355,7 @@ void WalkingCharacter::stopWalkingAndTurn(Direction direction) {
void WalkingCharacter::walkTo(
const Point &target, Direction endDirection,
- ShapeObject *activateObject, const char *activateAction,
- bool useAlternateObjectDirection) {
+ ITriggerableObject *activateObject, const char *activateAction) {
// all the activation parameters are only relevant for MainCharacter
if (_isWalking)
@@ -459,6 +468,80 @@ MainCharacter::~MainCharacter() {
delete item;
}
+void MainCharacter::update() {
+ if (_relatedProcessCounter == 0)
+ _currentlyUsingObject = nullptr;
+ WalkingCharacter::update();
+
+ const int16 halfWidth = (int16)(60 * _graphicNormal.depthScale());
+ const int16 height = (int16)(310 * _graphicNormal.depthScale());
+ shape()->setAsRectangle(Rect(
+ _currentPos.x - halfWidth, _currentPos.y - height,
+ _currentPos.x + halfWidth, _currentPos.y));
+
+ // TODO: Update character alpha tint
+}
+
+void MainCharacter::onArrived() {
+ if (_activateObject == nullptr)
+ return;
+
+ ITriggerableObject *activateObject = _activateObject;
+ const char *activateAction = _activateAction;
+ _activateObject = nullptr;
+ _activateAction = nullptr;
+
+ stopWalkingAndTurn(activateObject->interactionDirection());
+ if (g_engine->world().activeCharacter() == this)
+ activateObject->trigger(activateAction);
+}
+
+void MainCharacter::walkTo(
+ const Point &target, Direction endDirection,
+ ITriggerableObject *activateObject, const char *activateAction) {
+ _activateObject = activateObject;
+ _activateAction = activateAction;
+
+ // TODO: Add collision avoidance
+
+ WalkingCharacter::walkTo(target, endDirection, activateObject, activateAction);
+ if (this == g_engine->world().activeCharacter()) {
+ // TODO: Add camera following character
+ }
+}
+
+void MainCharacter::draw() {
+ if (this == &g_engine->world().mortadelo()) {
+ if (_currentPos.y <= g_engine->world().filemon()._currentPos.y) {
+ g_engine->world().mortadelo().drawInner();
+ g_engine->world().filemon().drawInner();
+ }
+ else {
+ g_engine->world().filemon().drawInner();
+ g_engine->world().mortadelo().drawInner();
+ }
+ }
+}
+
+void MainCharacter::drawInner() {
+ if (room() != g_engine->world().currentRoom() || !isEnabled())
+ return;
+ Graphic *activeGraphic = graphicOf(_curAnimateObject);
+ if (activeGraphic == nullptr && _isWalking) {
+ activeGraphic = &_graphicNormal;
+ _graphicNormal.premultiplyAlpha() = room()->characterAlphaPremultiplier();
+ }
+ if (activeGraphic == nullptr) {
+ activeGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ _graphicTalking.premultiplyAlpha() = room()->characterAlphaPremultiplier();
+ }
+
+ assert(activeGraphic != nullptr);
+ activeGraphic->color() = kWhite; // TODO: Add and use character color
+ g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
+
+}
+
void syncDialogMenuLine(Serializer &serializer, DialogMenuLine &line) {
serializer.syncAsSint32LE(line._dialogId);
serializer.syncAsSint32LE(line._yPosition);
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 72c6ce28225..191c1ba9896 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -174,6 +174,7 @@ public:
inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
inline const Common::Point &frameCenter(int32 frameI) const { return _frames[frameI]._center; }
inline uint32 totalDuration() const { return _totalDuration; }
+ inline uint8 &premultiplyAlpha() { return _premultiplyAlpha; }
int32 frameAtTime(uint32 time) const;
Common::Point imageSize(int32 imageI) const;
@@ -231,6 +232,10 @@ public:
assert(_animation != nullptr && _animation->isLoaded());
return *_animation;
}
+ inline uint8 &premultiplyAlpha() {
+ assert(_animation != nullptr);
+ return _animation->premultiplyAlpha();
+ }
void loadResources();
void freeResources();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 87d16526953..0af7ed1c386 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -281,17 +281,30 @@ public:
Item(Room *room, Common::ReadStream &stream);
};
-class InteractableObject : public PhysicalObject {
+class ITriggerableObject {
+public:
+ ITriggerableObject(Common::ReadStream &stream);
+
+ inline Direction interactionDirection() const { return _interactionDirection; }
+ inline const Common::Point &interactionPoint() const { return _interactionPoint; }
+
+ virtual void trigger(const char *action) = 0;
+
+protected:
+ Common::Point _interactionPoint;
+ Direction _interactionDirection = Direction::Right;
+};
+
+class InteractableObject : public PhysicalObject, public ITriggerableObject {
public:
static constexpr const char *kClassName = "CObjetoTipico";
InteractableObject(Room *room, Common::ReadStream &stream);
virtual ~InteractableObject() override = default;
virtual void drawDebug() override;
+ virtual void trigger(const char *action) override;
private:
- Common::Point _interactionPoint;
- CursorType _cursorType;
Common::String _relatedObject;
};
@@ -305,7 +318,7 @@ private:
Direction _characterDirection;
};
-class Character : public ShapeObject {
+class Character : public ShapeObject, public ITriggerableObject {
public:
static constexpr const char *kClassName = "CPersonaje";
Character(Room *room, Common::ReadStream &stream);
@@ -318,12 +331,12 @@ public:
virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
+ virtual void trigger(const char *action) override;
protected:
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
void updateTalkingAnimation();
- Common::Point _interactionPoint;
Direction _direction;
Graphic _graphicNormal, _graphicTalking;
@@ -350,17 +363,13 @@ public:
virtual void walkTo(
const Common::Point &target,
Direction endDirection = Direction::Invalid,
- ShapeObject *activateObject = nullptr,
- const char *activateAction = nullptr,
- bool useAlternateObjectDirection = false
- );
+ ITriggerableObject *activateObject = nullptr,
+ const char *activateAction = nullptr);
void stopWalkingAndTurn(Direction direction);
void setPosition(const Common::Point &target);
protected:
virtual void onArrived();
-
-private:
void updateWalking();
void updateWalkingAnimation();
@@ -389,8 +398,6 @@ private:
bool _isWalking = false;
Direction
_direction = Direction::Right,
- _interactionDirection1 = Direction::Right,
- _interactionDirection2 = Direction::Right,
_endWalkingDirection = Direction::Invalid;
Common::Stack<Common::Point> _pathPoints;
};
@@ -416,14 +423,28 @@ public:
inline MainCharacterKind kind() const { return _kind; }
inline ObjectBase *currentlyUsing() const { return _currentlyUsingObject; }
+ virtual void update() override;
+ virtual void draw() override;
virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void walkTo(
+ const Common::Point &target,
+ Direction endDirection = Direction::Invalid,
+ ITriggerableObject *activateObject = nullptr,
+ const char *activateAction = nullptr) override;
+
+protected:
+ virtual void onArrived() override;
private:
+ void drawInner();
+
Common::Array<Item *> _items;
Common::Array<DialogMenuLine> _dialogMenuLines;
ObjectBase *_currentlyUsingObject = nullptr;
MainCharacterKind _kind;
int32_t _relatedProcessCounter = 0;
+ ITriggerableObject *_activateObject = nullptr;
+ const char *_activateAction = nullptr;
};
class Background final : public GraphicObject {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index a0fc1de786c..237849addb5 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -88,13 +88,13 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
: _world(world) {
_name = readVarString(stream);
_musicId = stream.readSByte();
- _characterAlpha = stream.readByte();
+ _characterAlphaTint = stream.readByte();
auto backgroundScale = stream.readSint16LE();
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
_cameraFollowsUponLeaving = readBool(stream);
PathFindingShape _(stream); // unused path finding area
- _characterAlphaPercent = stream.readByte();
+ _characterAlphaPremultiplier = stream.readByte();
if (hasUselessByte)
stream.readByte();
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index d174620618e..eb28fcc69ea 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -45,6 +45,8 @@ public:
inline float depthAt(const Common::Point &query) const {
return _activeFloorI < 0 ? 1 : activeFloor()->depthAt(query);
}
+ inline uint8 characterAlphaTint() const { return _characterAlphaTint; }
+ inline uint8 characterAlphaPremultiplier() const { return _characterAlphaPremultiplier; }
void update();
virtual void updateInput();
@@ -68,8 +70,8 @@ protected:
_musicId,
_activeFloorI = -1;
uint8
- _characterAlpha,
- _characterAlphaPercent;
+ _characterAlphaTint,
+ _characterAlphaPremultiplier; ///< for some reason in percent instead of 0-255
Common::Array<ObjectBase *> _objects;
};
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index eb9019997da..66e3b137096 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -256,6 +256,16 @@ bool Shape::contains(const Point &query) const {
return polygonContaining(query) >= 0;
}
+void Shape::setAsRectangle(const Rect &rect) {
+ _polygons.resize(1);
+ _polygons[0] = { 0, 4 };
+ _points.resize(4);
+ _points[0] = { rect.left, rect.top };
+ _points[1] = { rect.right, rect.top };
+ _points[2] = { rect.right, rect.bottom };
+ _points[3] = { rect.left, rect.bottom };
+}
+
PathFindingShape::PathFindingShape() {}
PathFindingShape::PathFindingShape(ReadStream &stream) {
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 20168647c6f..0452cd45d8f 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -122,6 +122,7 @@ public:
Polygon at(uint index) const;
int32 polygonContaining(const Common::Point &query) const;
bool contains(const Common::Point &query) const;
+ void setAsRectangle(const Common::Rect &rect);
protected:
uint addPolygon(uint maxCount);
Commit: 49e716eeb0cb824f9269ffe219f1df7a8bc7c6c8
https://github.com/scummvm/scummvm/commit/49e716eeb0cb824f9269ffe219f1df7a8bc7c6c8
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Initial scheduler and script
Changed paths:
A engines/alcachofa/scheduler.cpp
A engines/alcachofa/scheduler.h
A engines/alcachofa/script.cpp
A engines/alcachofa/script.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/common.h
engines/alcachofa/objects.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index a536098f100..e5aa732849f 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -33,6 +33,7 @@
#include "graphics/framelimiter.h"
#include "rooms.h"
+#include "script.h"
using namespace Math;
@@ -61,6 +62,7 @@ Common::Error AlcachofaEngine::run() {
_renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
+ _script.reset(new Script());
world().globalRoom().loadResources();
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 913da07cbea..7205cd1c9c6 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -38,6 +38,7 @@
#include "alcachofa/camera.h"
#include "alcachofa/input.h"
#include "alcachofa/player.h"
+#include "alcachofa/scheduler.h"
#include "alcachofa/console.h"
namespace Alcachofa {
@@ -45,6 +46,7 @@ namespace Alcachofa {
class IRenderer;
class DrawQueue;
class World;
+class Script;
struct AlcachofaGameDescription;
class AlcachofaEngine : public Engine {
@@ -64,6 +66,8 @@ public:
inline Input &input() { return _input; }
inline Player &player() { return _player; }
inline World &world() { return *_world; }
+ inline Script &script() { return *_script; }
+ inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
uint32 getFeatures() const;
@@ -114,9 +118,11 @@ private:
Common::ScopedPtr<IRenderer> _renderer;
Common::ScopedPtr<DrawQueue> _drawQueue;
Common::ScopedPtr<World> _world;
+ Common::ScopedPtr<Script> _script;
Camera _camera;
Input _input;
Player _player;
+ Scheduler _scheduler;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 92c8c636704..77d2c348afc 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -22,6 +22,8 @@
#ifndef COMMON_H
#define COMMON_H
+#include "common/scummsys.h"
+
namespace Alcachofa {
enum class CursorType {
@@ -44,6 +46,12 @@ enum class Direction {
Invalid = -1
};
+enum class MainCharacterKind {
+ None,
+ Mortadelo,
+ Filemon
+};
+
constexpr const int32 kDirectionCount = 4;
constexpr const int8 kOrderCount = 70;
constexpr const int8 kForegroundOrderCount = 10;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 0af7ed1c386..4ceb3f8f7a8 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -402,12 +402,6 @@ protected:
Common::Stack<Common::Point> _pathPoints;
};
-enum class MainCharacterKind {
- None,
- Mortadelo,
- Filemon
-};
-
struct DialogMenuLine {
int32 _dialogId;
int32 _yPosition;
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
new file mode 100644
index 00000000000..187d8d932c7
--- /dev/null
+++ b/engines/alcachofa/scheduler.cpp
@@ -0,0 +1,212 @@
+/* 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 "scheduler.h"
+
+#include "common/system.h"
+#include "alcachofa.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+struct DelayTask : public Task {
+ DelayTask(Process &process, uint32 millis)
+ : Task(process)
+ , _endTime(millis) {}
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ _endTime += g_system->getMillis();
+ while (g_system->getMillis() < _endTime)
+ TASK_YIELD;
+ TASK_END;
+ }
+
+ virtual void debugPrint() {
+ uint32 remaining = g_system->getMillis() <= _endTime ? _endTime - g_system->getMillis() : 0;
+ g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
+ }
+
+private:
+ uint32 _endTime;
+};
+
+TaskReturn::TaskReturn() {
+ _type = TaskReturnType::Yield;
+ _returnValue = 0;
+ _taskToWaitFor = nullptr;
+}
+
+TaskReturn TaskReturn::finish(int32 returnValue) {
+ TaskReturn r;
+ r._type = TaskReturnType::Finished;
+ r._returnValue = returnValue;
+ return r;
+}
+
+TaskReturn TaskReturn::waitFor(Task *task) {
+ assert(task != nullptr);
+ TaskReturn r;
+ r._type = TaskReturnType::Waiting;
+ r._taskToWaitFor = task;
+ return r;
+}
+
+Task::Task(Process &process) : _process(process) {}
+
+Task *Task::delay(uint32 millis) {
+ return new DelayTask(process(), millis);
+}
+
+Process::Process(ProcessId pid, MainCharacterKind characterKind)
+ : _pid(pid)
+ , _character(characterKind)
+ , _name("Unnamed process") {
+}
+
+Process::~Process() {
+ while (!_tasks.empty())
+ delete _tasks.pop();
+}
+
+TaskReturnType Process::run() {
+ while (!_tasks.empty()) {
+ TaskReturn ret = _tasks.top()->run();
+ switch (ret.type()) {
+ case TaskReturnType::Yield: return TaskReturnType::Yield;
+ case TaskReturnType::Waiting:
+ _tasks.push(ret.taskToWaitFor());
+ break;
+ case TaskReturnType::Finished:
+ _lastReturnValue = ret.returnValue();
+ _tasks.pop();
+ break;
+ default:
+ assert(false && "Invalid task return type");
+ return TaskReturnType::Finished;
+ }
+ }
+ return TaskReturnType::Finished;
+}
+
+void Process::debugPrint() {
+ auto *debugger = g_engine->getDebugger();
+ const char *characterName;
+ switch (_character) {
+ case MainCharacterKind::None: characterName = " <none>"; break;
+ case MainCharacterKind::Filemon: characterName = " Filemon"; break;
+ case MainCharacterKind::Mortadelo: characterName = "Mortadelo"; break;
+ default: characterName = "<invalid>"; break;
+ }
+ debugger->debugPrintf("pid: %3u char: %s ret: %2d \"%s\"\n", _pid, characterName, _lastReturnValue, _name.c_str());
+
+ for (uint i = 0; i < _tasks.size(); i++) {
+ debugger->debugPrintf("\t%u: ", i);
+ _tasks[i]->debugPrint();
+ }
+}
+
+static void killProcessesForIn(MainCharacterKind characterKind, Array<Process *> &processes, uint firstIndex) {
+ assert(firstIndex < processes.size());
+ uint count = processes.size() - 1 - firstIndex;
+ for (uint i = 0; i < count; i++) {
+ Process **process = &processes[processes.size() - 1 - i];
+ if ((*process)->character() == characterKind || characterKind == MainCharacterKind::None) {
+ delete *process;
+ processes.erase(process);
+ }
+ }
+}
+
+Scheduler::~Scheduler() {
+ killAllProcesses();
+ killProcessesForIn(MainCharacterKind::None, _backupProcesses, 0);
+}
+
+Process *Scheduler::createProcessInternal(MainCharacterKind character) {
+ Process *process = new Process(_nextPid++, character);
+ processesToRunNext().push_back(process);
+ return process;
+}
+
+void Scheduler::run() {
+ assert(processesToRun().empty()); // otherwise we somehow left normal flow
+ _currentArrayI = (_currentArrayI + 1) % 2;
+ // processesToRun() can be modified during loop so do not replace with iterators
+ for (_currentProcessI = 0; _currentProcessI < processesToRun().size(); _currentProcessI++) {
+ Process *process = processesToRun()[_currentProcessI];
+ auto ret = process->run();
+ if (ret == TaskReturnType::Finished)
+ delete process;
+ else
+ processesToRunNext().push_back(process);
+ }
+ processesToRun().clear();
+ _currentProcessI = UINT_MAX;
+}
+
+void Scheduler::backupContext() {
+ assert(processesToRun().empty());
+ _backupProcesses.push_back(processesToRunNext());
+ processesToRunNext().clear();
+}
+
+void Scheduler::restoreContext() {
+ assert(processesToRun().empty());
+ processesToRunNext().push_back(_backupProcesses);
+ _backupProcesses.clear();
+}
+
+void Scheduler::killAllProcesses() {
+ killProcessesForIn(MainCharacterKind::None, _processArrays[0], 0);
+ killProcessesForIn(MainCharacterKind::None, _processArrays[1], 0);
+}
+
+void Scheduler::killAllProcessesFor(MainCharacterKind characterKind) {
+ // this method can be called during run() so be careful
+ killProcessesForIn(characterKind, processesToRunNext(), 0);
+ killProcessesForIn(characterKind, processesToRun(), _currentProcessI == UINT_MAX ? 0 : _currentProcessI);
+}
+
+static Process **getProcessByName(Array<Process *> &_processes, const String &name) {
+ for (auto &process : _processes) {
+ if (process->name() == name)
+ return &process;
+ }
+ return nullptr;
+}
+
+void Scheduler::killProcessByName(const String &name) {
+ assert(processesToRun().empty());
+ Process **process = getProcessByName(processesToRunNext(), name);
+ if (process != nullptr) {
+ delete *process;
+ processesToRunNext().erase(process);
+ }
+}
+
+bool Scheduler::hasProcessWithName(const String &name) {
+ assert(processesToRun().empty());
+ return getProcessByName(processesToRunNext(), name) != nullptr;
+}
+
+}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
new file mode 100644
index 00000000000..8e3a74ab8d2
--- /dev/null
+++ b/engines/alcachofa/scheduler.h
@@ -0,0 +1,178 @@
+/* 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/>.
+ *
+ */
+
+#ifndef SCHEDULER_H
+#define SCHEDULER_H
+
+#include "common.h"
+
+#include "common/stack.h"
+#include "common/str.h"
+
+namespace Alcachofa {
+
+struct Task;
+class Process;
+
+
+enum class TaskReturnType {
+ Yield,
+ Finished,
+ Waiting
+};
+
+struct TaskReturn {
+ static inline TaskReturn yield() { return {}; }
+ static TaskReturn finish(int32 returnValue);
+ static TaskReturn waitFor(Task *task);
+
+ inline TaskReturnType type() const { return _type; }
+ inline int32 returnValue() const {
+ assert(_type == TaskReturnType::Finished);
+ return _returnValue;
+ }
+ inline Task *taskToWaitFor() const {
+ assert(_type == TaskReturnType::Waiting);
+ return _taskToWaitFor;
+ }
+
+private:
+ TaskReturn();
+ TaskReturnType _type;
+ union {
+ int32 _returnValue;
+ Task *_taskToWaitFor;
+ };
+};
+
+struct Task {
+ Task(Process &process);
+ virtual ~Task() = default;
+ virtual TaskReturn run() = 0;
+ virtual void debugPrint() = 0;
+
+ inline Process &process() const { return _process; }
+
+protected:
+ Task *delay(uint32 millis);
+
+ uint32 _line = 0;
+private:
+ Process &_process;
+};
+
+// TODO: This probably should be scummvm common
+#if __cplusplus >= 201703L
+#define TASK_BREAK_FALLTHROUGH [[fallthrough]];
+#else
+#define TASK_BREAK_FALLTHROUGH
+#endif
+
+#define TASK_BEGIN \
+ switch(_line) { \
+ case 0:; \
+
+#define TASK_END \
+ TASK_RETURN(0); \
+ TASK_BREAK_FALLTHROUGH \
+ default: assert(false && "Invalid line in task"); \
+ } return TaskReturn::finish(0)
+
+#define TASK_YIELD \
+ do { \
+ _line = __LINE__; \
+ return TaskReturn::yield(); \
+ TASK_BREAK_FALLTHROUGH \
+ case __LINE__:; \
+ } while(0);
+
+#define TASK_WAIT(task) \
+ do { \
+ _line = __LINE__; \
+ return TaskReturn::waitFor(task); \
+ TASK_BREAK_FALLTHROUGH \
+ case __LINE__:; \
+ } while(0);
+
+#define TASK_RETURN(value) \
+ do { \
+ return TaskReturn::finish(value); \
+ _line = UINT_MAX; \
+ } while(0)
+
+using ProcessId = uint;
+class Process {
+public:
+ Process(ProcessId pid, MainCharacterKind characterKind);
+ ~Process();
+
+ inline ProcessId pid() const { return _pid; }
+ inline MainCharacterKind character() const { return _character; }
+ inline int32 returnValue() const { return _lastReturnValue; }
+ inline Common::String &name() { return _name; }
+
+ TaskReturnType run();
+ void debugPrint();
+
+private:
+ friend class Scheduler;
+ ProcessId _pid;
+ MainCharacterKind _character;
+ Common::Stack<Task *> _tasks;
+ Common::String _name;
+ int32 _lastReturnValue = 0;
+};
+
+class Scheduler {
+public:
+ ~Scheduler();
+
+ void run();
+ void backupContext();
+ void restoreContext();
+ void killAllProcesses();
+ void killAllProcessesFor(MainCharacterKind characterKind);
+ void killProcessByName(const Common::String &name);
+ bool hasProcessWithName(const Common::String &name);
+
+ template<typename TTask, typename... TaskArgs>
+ Process *createProcess(MainCharacterKind character, TaskArgs&&... args) {
+ Process *process = createProcessInternal(character);
+ process->_tasks.push(new TTask(*process, Common::forward<TaskArgs>(args)...));
+ return process;
+ }
+
+private:
+ Process *createProcessInternal(MainCharacterKind character);
+
+ inline Common::Array<Process *> &processesToRun() { return _processArrays[_currentArrayI]; }
+ inline Common::Array<Process *> &processesToRunNext() { return _processArrays[!_currentArrayI]; }
+ Common::Array<Process *> _processArrays[2];
+ Common::Array<Process *> _backupProcesses;
+ uint8 _currentArrayI = 0;
+ ProcessId _nextPid = 1;
+ uint _currentProcessI = UINT_MAX;
+
+};
+
+}
+
+#endif // SCHEDULER_H
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
new file mode 100644
index 00000000000..be8f197645b
--- /dev/null
+++ b/engines/alcachofa/script.cpp
@@ -0,0 +1,156 @@
+/* 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 "script.h"
+#include "stream-helper.h"
+#include "alcachofa.h"
+
+#include "common/file.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+ScriptInstruction::ScriptInstruction(ReadStream &stream)
+ : _op((ScriptOp)stream.readSint32LE())
+ , _arg(stream.readSint32LE()) {}
+
+Script::Script() {
+ File file;
+ if (!file.open("script/SCRIPT.COD"))
+ error("Could not open script");
+
+ uint32 stringBlobSize = file.readUint32LE();
+ uint32 memorySize = file.readUint32LE();
+ _strings = SpanOwner<Span<char>>({ new char[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();
+ for (uint32 i = 0; i < variableCount; i++) {
+ 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();
+ for (uint32 i = 0; i < procedureCount; i++) {
+ 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();
+ for (uint32 i = 0; i < behaviorCount; i++) {
+ String behaviorName = readVarString(file) + '/';
+ variableCount = file.readUint32LE(); // not used by the original game
+ assert(variableCount == 0);
+ procedureCount = file.readUint32LE();
+ for (uint32 j = 0; j < procedureCount; j++) {
+ String name = behaviorName + readVarString(file);
+ uint32 offset = file.readUint32LE();
+ file.skip(sizeof(uint32));
+ _procedures[name] = offset - 1;
+ }
+ }
+
+ uint32 instructionCount = file.readUint32LE();
+ _instructions.reserve(instructionCount);
+ for (uint32 i = 0; i < instructionCount; i++)
+ _instructions.push_back(ScriptInstruction(file));
+}
+
+int32 Script::variable(const char *name) const {
+ uint32 index;
+ if (!_variableNames.tryGetVal(name, index))
+ error("Unknown variable: %s", name);
+ return _variables[index];
+}
+
+int32 &Script::variable(const char *name) {
+ uint32 index;
+ if (!_variableNames.tryGetVal(name, index))
+ error("Unknown variable: %s", name);
+ return _variables[index];
+}
+
+struct ScriptTask : public Task {
+ ScriptTask(Process &process, const String &name, uint32 pc)
+ : Task(process)
+ , _name(name)
+ , _pc(pc) {}
+
+ virtual TaskReturn run() override {
+ warning("STUB: Script execution at %u", _pc);
+ return TaskReturn::finish(0);
+ }
+
+ virtual void debugPrint() {
+ g_engine->getDebugger()->debugPrintf("\"%s\" at %u\n", _name.c_str(), _pc);
+ }
+
+private:
+ enum class StackEntryType {
+ Numeric,
+ Variable,
+ String
+ };
+
+ struct StackEntry {
+ StackEntryType _type;
+ union {
+ int32 _numeric;
+ int32 *_variable;
+ const char *_string;
+ };
+ };
+
+ Stack<StackEntry> _stack;
+ String _name;
+ uint32 _pc;
+};
+
+Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, bool allowMissing) {
+ return createProcess(character, behavior + '/' + action, allowMissing);
+}
+
+Process *Script::createProcess(MainCharacterKind character, const String &procedure, bool allowMissing) {
+ uint32 offset;
+ if (!_procedures.tryGetVal(procedure, offset)) {
+ if (allowMissing)
+ return nullptr;
+ error("Unknown required procedure: %s", procedure.c_str());
+ }
+ Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset);
+ process->name() = procedure;
+ return process;
+}
+
+}
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
new file mode 100644
index 00000000000..1ee23e37809
--- /dev/null
+++ b/engines/alcachofa/script.h
@@ -0,0 +1,170 @@
+/* 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/>.
+ *
+ */
+
+#ifndef SCRIPT_H
+#define SCRIPT_H
+
+#include "common.h"
+
+#include "common/hashmap.h"
+#include "common/span.h"
+#include "common/stream.h"
+#include "common/system.h"
+
+namespace Alcachofa {
+
+class Process;
+
+enum class ScriptOp {
+ Nop,
+ Dup,
+ PushAddr,
+ PushValue,
+ Deref,
+ Crash5, ///< would crash original engine by writing to read-only memory
+ PopN,
+ Store,
+ Crash8,
+ Crash9,
+ LoadString,
+ LoadString2, ///< exactly the same as LoadString
+ Crash12,
+ ScriptCall,
+ KernelCall,
+ JumpIfFalse,
+ JumpIfTrue,
+ Jump,
+ Negate,
+ BooleanNot,
+ Mul,
+ Crash21,
+ Crash22,
+ Add,
+ Sub,
+ Less,
+ Greater,
+ LessEquals,
+ GreaterEquals,
+ Equals,
+ NotEquals,
+ BitAnd,
+ BitOr,
+ Crash33,
+ Crash34,
+ Crash35,
+ Crash36,
+ Return
+};
+
+enum class ScriptKernelTask {
+ PlayVideo,
+ PlaySound,
+ PlayMusic,
+ StopMusic,
+ WaitForMusicToEnd,
+ ShowCenterBottomText,
+ StopAndTurn,
+ StopAndTurnMe,
+ ChangeCharacter,
+ SayText,
+ Nop10,
+ Go,
+ Put,
+ ChangeCharacterRoom,
+ KillProcesses,
+ Timer,
+ On,
+ Off,
+ Pickup,
+ CharacterPickup,
+ Drop,
+ CharacterDrop,
+ Delay,
+ HadNoMousePressFor,
+ Nop24,
+ Fork,
+ Animate,
+ AnimateCharacter,
+ AnimateTalking,
+ ChangeRoom,
+ ToggleRoomFloor,
+ SetDialogLineReturn,
+ DialogMenu,
+ ClearInventory,
+ Nop34,
+ FadeType0,
+ FadeType1,
+ SetLodBias,
+ FadeType2,
+ SetActiveTextureSet,
+ SetMaxCamSpeedFactor,
+ WaitCamStopping,
+ CamFollow,
+ CamShake,
+ LerpCamXY,
+ LerpCamZ,
+ LerpCamScale,
+ LerpCamToObjectWithScale,
+ LerpCamToObjectResettingZ,
+ LerpCamRotation,
+ FadeIn,
+ FadeOut,
+ FadeIn2,
+ FadeOut2,
+ LerpCamXYZ,
+ LerpCamToObjectKeepingZ
+};
+
+struct ScriptInstruction {
+ ScriptInstruction(Common::ReadStream &stream);
+
+ ScriptOp _op;
+ int32 _arg;
+};
+
+class Script {
+public:
+ Script();
+
+ int32 variable(const char *name) const;
+ int32 &variable(const char *name);
+ Process *createProcess(
+ MainCharacterKind character,
+ const Common::String &procedure,
+ bool allowMissing = false);
+ Process *createProcess(
+ MainCharacterKind character,
+ const Common::String &behavior,
+ const Common::String &action,
+ bool allowMissing = false);
+
+private:
+ friend struct ScriptTask;
+ Common::HashMap<Common::String, uint32> _variableNames;
+ Common::HashMap<Common::String, uint32> _procedures;
+ Common::Array<ScriptInstruction> _instructions;
+ Common::Array<int32> _variables;
+ Common::SpanOwner<Common::Span<char>> _strings;
+};
+
+}
+
+#endif // SCRIPT_H
Commit: adc43280472bcc3ae27f39bb05ced535e98e1837
https://github.com/scummvm/scummvm/commit/adc43280472bcc3ae27f39bb05ced535e98e1837
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Add script execution
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index e5aa732849f..bac8624a363 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -66,7 +66,7 @@ Common::Error AlcachofaEngine::run() {
world().globalRoom().loadResources();
- auto room = world().getRoomByName("SALOON");
+ auto room = world().getRoomByName("MAPA_TERROR");
assert(room != nullptr);
world().currentRoom() = room;
room->loadResources();
@@ -115,4 +115,20 @@ Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
return Common::kNoError;
}
+void AlcachofaEngine::updateScriptVariables() {
+ if (_input.wasAnyMousePressed()) // yes, this variable is never reset by the engine
+ _script->variable("SeHaPulsadoRaton") = 1;
+
+ if (_script->variable("CalcularTiempoSinPulsarRaton")) {
+ if (_scriptTimer == 0)
+ _scriptTimer = g_system->getMillis();
+ }
+ else
+ _scriptTimer = 0;
+
+ _script->variable("EstanAmbos") = _world->mortadelo().room() == _world->filemon().room();
+ _script->variable("textoson") = 1; // TODO: Add subtitle option
+ _script->variable("modored") = 1; // this is signalling whether a network connection is established
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 7205cd1c9c6..0246823d4c5 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -69,6 +69,8 @@ public:
inline Script &script() { return *_script; }
inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
+ inline uint32 scriptTimer() const { return _scriptTimer; }
+ void updateScriptVariables();
uint32 getFeatures() const;
@@ -123,6 +125,8 @@ private:
Input _input;
Player _player;
Scheduler _scheduler;
+
+ uint32 _scriptTimer = 0;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 237849addb5..31938e4c062 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -21,6 +21,7 @@
#include "alcachofa.h"
#include "rooms.h"
+#include "script.h"
#include "stream-helper.h"
#include "common/file.h"
@@ -125,6 +126,8 @@ ObjectBase *Room::getObjectByName(const Common::String &name) const {
}
void Room::update() {
+ updateScripts();
+
if (world().currentRoom() == this) {
updateRoomBounds();
updateInput();
@@ -144,6 +147,13 @@ void Room::update() {
}
}
+void Room::updateScripts() {
+ g_engine->updateScriptVariables();
+ if (!g_engine->scheduler().hasProcessWithName("ACTUALIZAR_" + _name))
+ g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, true);
+ g_engine->scheduler().run();
+}
+
void Room::updateInput() {
static bool hasLastP3D = false;
static Point lastP3D;
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index eb28fcc69ea..b7be6a583b0 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -57,6 +57,7 @@ public:
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
+ void updateScripts();
void updateRoomBounds();
void updateObjects();
void drawObjects();
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 8e3a74ab8d2..a045eb4ce4c 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -102,7 +102,7 @@ private:
return TaskReturn::yield(); \
TASK_BREAK_FALLTHROUGH \
case __LINE__:; \
- } while(0);
+ } while(0)
#define TASK_WAIT(task) \
do { \
@@ -110,7 +110,7 @@ private:
return TaskReturn::waitFor(task); \
TASK_BREAK_FALLTHROUGH \
case __LINE__:; \
- } while(0);
+ } while(0)
#define TASK_RETURN(value) \
do { \
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index be8f197645b..397bdb5a6e1 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -101,15 +101,169 @@ int32 &Script::variable(const char *name) {
return _variables[index];
}
+enum class StackEntryType {
+ Number,
+ Variable,
+ String,
+ Instruction
+};
+
+struct StackEntry {
+ StackEntry(StackEntryType type, int32 number) : _type(type), _number(number) {}
+ StackEntry(StackEntryType type, uint32 index) : _type(type), _index(index) {}
+
+ StackEntryType _type;
+ union {
+ int32 _number;
+ uint32 _index;
+ };
+};
+
struct ScriptTask : public Task {
ScriptTask(Process &process, const String &name, uint32 pc)
: Task(process)
+ , _script(g_engine->script())
, _name(name)
- , _pc(pc) {}
+ , _pc(pc) {
+ pushInstruction(UINT_MAX);
+ }
+
+ ScriptTask(Process &process, const ScriptTask &forkParent)
+ : Task(process)
+ , _script(g_engine->script())
+ , _name(forkParent._name + " FORKED")
+ , _pc(forkParent._pc) {
+ for (uint i = 0; i < forkParent._stack.size(); i++)
+ _stack.push(forkParent._stack[i]);
+ pushNumber(1); // this task is the forked one
+ }
virtual TaskReturn run() override {
- warning("STUB: Script execution at %u", _pc);
- return TaskReturn::finish(0);
+ if (_returnsFromKernelCall)
+ pushNumber(process().returnValue());
+ _returnsFromKernelCall = false;
+
+ while (true) {
+ if (_pc >= _script._instructions.size())
+ error("Script process reached instruction out-of-bounds");
+ const auto &instruction = _script._instructions[_pc++];
+ switch (instruction._op) {
+ case ScriptOp::Nop: break;
+ case ScriptOp::Dup:
+ if (_stack.empty())
+ error("Script tried to duplicate stack top, but stack is empty");
+ _stack.push(_stack.top());
+ break;
+ case ScriptOp::PushAddr:
+ pushVariable(instruction._arg);
+ break;
+ case ScriptOp::PushValue:
+ pushNumber(instruction._arg);
+ break;
+ case ScriptOp::Deref:
+ pushNumber(popVariable());
+ break;
+ case ScriptOp::PopN:
+ if (instruction._arg < 0 || (uint)instruction._arg > _stack.size())
+ error("Script tried to pop more entries than are available on the stack");
+ for (int32 i = 0; i < instruction._arg; i++)
+ _stack.pop();
+ break;
+ case ScriptOp::Store: {
+ int32 value = popNumber();
+ popVariable() = value;
+ pushNumber(value);
+ }break;
+ case ScriptOp::LoadString:
+ case ScriptOp::LoadString2:
+ pushString(popNumber());
+ break;
+ case ScriptOp::ScriptCall:
+ pushInstruction(_pc);
+ _pc = instruction._arg - 1;
+ break;
+ case ScriptOp::KernelCall: {
+ TaskReturn kernelReturn = kernelCall((ScriptKernelTask)instruction._arg);
+ if (kernelReturn.type() == TaskReturnType::Waiting) {
+ _returnsFromKernelCall = true;
+ return kernelReturn;
+ }
+ else
+ pushNumber(kernelReturn.returnValue());
+ }break;
+ case ScriptOp::JumpIfFalse:
+ if (popNumber() == 0)
+ _pc = _pc - 1 + instruction._arg;
+ break;
+ case ScriptOp::JumpIfTrue:
+ if (popNumber() != 0)
+ _pc = _pc - 1 + instruction._arg;
+ break;
+ case ScriptOp::Jump:
+ _pc = _pc - 1 + instruction._arg;
+ break;
+ case ScriptOp::Negate:
+ pushNumber(-popNumber());
+ break;
+ case ScriptOp::BooleanNot:
+ pushNumber(popNumber() == 0 ? 1 : 0);
+ break;
+ case ScriptOp::Mul:
+ pushNumber(popNumber() * popNumber());
+ break;
+ case ScriptOp::Add:
+ pushNumber(popNumber() + popNumber());
+ break;
+ case ScriptOp::Sub:
+ pushNumber(popNumber() - popNumber());
+ break;
+ case ScriptOp::Less:
+ pushNumber(popNumber() < popNumber());
+ break;
+ case ScriptOp::Greater:
+ pushNumber(popNumber() > popNumber());
+ break;
+ case ScriptOp::LessEquals:
+ pushNumber(popNumber() <= popNumber());
+ break;
+ case ScriptOp::GreaterEquals:
+ pushNumber(popNumber() >= popNumber());
+ break;
+ case ScriptOp::Equals:
+ pushNumber(popNumber() == popNumber());
+ break;
+ case ScriptOp::NotEquals:
+ pushNumber(popNumber() != popNumber());
+ break;
+ case ScriptOp::BitAnd:
+ pushNumber(popNumber() & popNumber());
+ break;
+ case ScriptOp::BitOr:
+ pushNumber(popNumber() | popNumber());
+ break;
+ case ScriptOp::Return: {
+ int32 returnValue = popNumber();
+ _pc = popInstruction();
+ if (_pc == UINT_MAX)
+ return TaskReturn::finish(returnValue);
+ else
+ pushNumber(returnValue);
+ }break;
+ case ScriptOp::Crash5:
+ case ScriptOp::Crash8:
+ case ScriptOp::Crash9:
+ case ScriptOp::Crash12:
+ case ScriptOp::Crash21:
+ case ScriptOp::Crash22:
+ case ScriptOp::Crash33:
+ case ScriptOp::Crash34:
+ case ScriptOp::Crash35:
+ case ScriptOp::Crash36:
+ error("Script reached crash instruction");
+ default:
+ error("Script reached invalid instruction");
+ }
+ }
}
virtual void debugPrint() {
@@ -117,24 +271,258 @@ struct ScriptTask : public Task {
}
private:
- enum class StackEntryType {
- Numeric,
- Variable,
- String
- };
+ void pushNumber(int32 value) {
+ _stack.push({ StackEntryType::Number, value });
+ }
- struct StackEntry {
- StackEntryType _type;
- union {
- int32 _numeric;
- int32 *_variable;
- const char *_string;
- };
- };
+ void pushVariable(uint32 offset) {
+ 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 });
+ }
+
+ void pushString(uint32 offset) {
+ if (offset >= _script._strings->size())
+ error("Script tried to push invalid string offset");
+ _stack.push({ StackEntryType::String, offset });
+ }
+
+ void pushInstruction(uint32 pc) {
+ _stack.push({ StackEntryType::Instruction, pc });
+ }
+
+ StackEntry pop() {
+ if (_stack.empty())
+ error("Script tried to pop empty stack");
+ return _stack.pop();
+ }
+
+ int32 popNumber() {
+ auto entry = pop();
+ if (entry._type != StackEntryType::Number)
+ error("Script tried to pop, but top of stack is not a number");
+ return entry._number;
+ }
+
+ int32 &popVariable() {
+ auto entry = pop();
+ if (entry._type != StackEntryType::Variable)
+ error("Script tried to pop, but top of stack is not a variable");
+ return _script._variables[entry._index];
+ }
+
+ const char *popString() {
+ auto entry = pop();
+ if (entry._type != StackEntryType::String)
+ error("Script tried to pop, but top of stack is not a string");
+ return _script._strings->data() + entry._index;
+ }
+
+ 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;
+ }
+
+ StackEntry getArg(uint argI) {
+ if (_stack.size() < argI + 1)
+ error("Script did not supply enough arguments for kernel call");
+ return _stack[_stack.size() - 1 - argI];
+ }
+
+ int32 getNumberArg(uint argI) {
+ auto entry = getArg(argI);
+ if (entry._type != StackEntryType::Number)
+ error("Expected number in argument %u for kernel call", argI);
+ return entry._number;
+ }
+
+ const char *getStringArg(uint argI) {
+ auto entry = getArg(argI);
+ if (entry._type != StackEntryType::String)
+ error("Expected string in argument %u for kernel call", argI);
+ return _script._strings->data() + entry._index;
+ }
+
+ TaskReturn kernelCall(ScriptKernelTask task) {
+ switch (task) {
+ case ScriptKernelTask::PlayVideo:
+ warning("STUB KERNEL CALL: PlayVideo");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::PlaySound:
+ warning("STUB KERNEL CALL: PlaySound");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::PlayMusic:
+ warning("STUB KERNEL CALL: PlayMusic");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::StopMusic:
+ warning("STUB KERNEL CALL: StopMusic");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::WaitForMusicToEnd:
+ warning("STUB KERNEL CALL: WaitForMusicToEnd");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ShowCenterBottomText:
+ warning("STUB KERNEL CALL: ShowCenterBottomText");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::StopAndTurn:
+ warning("STUB KERNEL CALL: StopAndTurn");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::StopAndTurnMe:
+ warning("STUB KERNEL CALL: StopAndTurnMe");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ChangeCharacter:
+ warning("STUB KERNEL CALL: ChangeCharacter");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::SayText:
+ warning("STUB KERNEL CALL: SayText");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Go:
+ warning("STUB KERNEL CALL: Go");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Put:
+ warning("STUB KERNEL CALL: Put");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ChangeCharacterRoom:
+ warning("STUB KERNEL CALL: ChangeCharacterRoom");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::KillProcesses:
+ warning("STUB KERNEL CALL: KillProcesses");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpLodBias:
+ warning("STUB KERNEL CALL: LerpLodBias");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::On:
+ warning("STUB KERNEL CALL: On");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Off:
+ warning("STUB KERNEL CALL: Off");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Pickup:
+ warning("STUB KERNEL CALL: Pickup");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::CharacterPickup:
+ warning("STUB KERNEL CALL: CharacterPickup");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Drop:
+ warning("STUB KERNEL CALL: Drop");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::CharacterDrop:
+ warning("STUB KERNEL CALL: CharacterDrop");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Delay:
+ return getNumberArg(0) <= 0
+ ? TaskReturn::finish(0)
+ : TaskReturn::waitFor(delay((uint32)getNumberArg(0)));
+ case ScriptKernelTask::HadNoMousePressFor:
+ warning("STUB KERNEL CALL: HadNoMousePressFor");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Fork:
+ g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
+ return TaskReturn::finish(0); // 0 means this is the forking process
+ case ScriptKernelTask::Animate:
+ warning("STUB KERNEL CALL: Animate");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::AnimateCharacter:
+ warning("STUB KERNEL CALL: AnimateCharacter");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::AnimateTalking:
+ warning("STUB KERNEL CALL: AnimateTalking");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ChangeRoom:
+ warning("STUB KERNEL CALL: ChangeRoom");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ToggleRoomFloor:
+ warning("STUB KERNEL CALL: ToggleRoomFloor");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::SetDialogLineReturn:
+ warning("STUB KERNEL CALL: SetDialogLineReturn");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::DialogMenu:
+ warning("STUB KERNEL CALL: DialogMenu");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ClearInventory:
+ warning("STUB KERNEL CALL: ClearInventory");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeType0:
+ warning("STUB KERNEL CALL: FadeType0");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeType1:
+ warning("STUB KERNEL CALL: FadeType1");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::SetLodBias:
+ warning("STUB KERNEL CALL: SetLodBias");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeType2:
+ warning("STUB KERNEL CALL: FadeType2");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::SetActiveTextureSet:
+ warning("STUB KERNEL CALL: SetActiveTextureSet");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::SetMaxCamSpeedFactor:
+ warning("STUB KERNEL CALL: SetMaxCamSpeedFactor");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::WaitCamStopping:
+ warning("STUB KERNEL CALL: WaitCamStopping");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::CamFollow:
+ warning("STUB KERNEL CALL: CamFollow");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::CamShake:
+ warning("STUB KERNEL CALL: CamShake");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamXY:
+ warning("STUB KERNEL CALL: LerpCamXY");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamZ:
+ warning("STUB KERNEL CALL: LerpCamZ");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamScale:
+ warning("STUB KERNEL CALL: LerpCamScale");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamToObjectWithScale:
+ warning("STUB KERNEL CALL: LerpCamToObjectWithScale");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamToObjectResettingZ:
+ warning("STUB KERNEL CALL: LerpCamToObjectResettingZ");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamRotation:
+ warning("STUB KERNEL CALL: LerpCamRotation");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeIn:
+ warning("STUB KERNEL CALL: FadeIn");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeOut:
+ warning("STUB KERNEL CALL: FadeOut");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeIn2:
+ warning("STUB KERNEL CALL: FadeIn2");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeOut2:
+ warning("STUB KERNEL CALL: FadeOut2");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamXYZ:
+ warning("STUB KERNEL CALL: LerpCamXYZ");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamToObjectKeepingZ:
+ warning("STUB KERNEL CALL: LerpCamToObjectKeepingZ");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Nop10:
+ case ScriptKernelTask::Nop24:
+ case ScriptKernelTask::Nop34:
+ return TaskReturn::finish(0);
+ default:
+ error("Invalid kernel call");
+ return TaskReturn::finish(0);
+ }
+ }
+ Script &_script;
Stack<StackEntry> _stack;
String _name;
uint32 _pc;
+ bool _returnsFromKernelCall = false;
};
Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, bool allowMissing) {
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 1ee23e37809..fa69a57dc2d 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -75,7 +75,7 @@ enum class ScriptOp {
};
enum class ScriptKernelTask {
- PlayVideo,
+ PlayVideo = 1,
PlaySound,
PlayMusic,
StopMusic,
@@ -90,7 +90,7 @@ enum class ScriptKernelTask {
Put,
ChangeCharacterRoom,
KillProcesses,
- Timer,
+ LerpLodBias,
On,
Off,
Pickup,
Commit: f561316bd8713612c2f4adc10d45504cf244eb60
https://github.com/scummvm/scummvm/commit/f561316bd8713612c2f4adc10d45504cf244eb60
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:45+02:00
Commit Message:
ALCACHOFA: Move updateCommonVariables to Script
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index bac8624a363..61fb9703ce3 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -115,20 +115,4 @@ Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
return Common::kNoError;
}
-void AlcachofaEngine::updateScriptVariables() {
- if (_input.wasAnyMousePressed()) // yes, this variable is never reset by the engine
- _script->variable("SeHaPulsadoRaton") = 1;
-
- if (_script->variable("CalcularTiempoSinPulsarRaton")) {
- if (_scriptTimer == 0)
- _scriptTimer = g_system->getMillis();
- }
- else
- _scriptTimer = 0;
-
- _script->variable("EstanAmbos") = _world->mortadelo().room() == _world->filemon().room();
- _script->variable("textoson") = 1; // TODO: Add subtitle option
- _script->variable("modored") = 1; // this is signalling whether a network connection is established
-}
-
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 0246823d4c5..98e3e48a3d6 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -68,9 +68,7 @@ public:
inline World &world() { return *_world; }
inline Script &script() { return *_script; }
inline Scheduler &scheduler() { return _scheduler; }
- inline Console &console() { return *_console; }
- inline uint32 scriptTimer() const { return _scriptTimer; }
- void updateScriptVariables();
+ inline Console &console() { return *_console; }
uint32 getFeatures() const;
@@ -125,8 +123,6 @@ private:
Input _input;
Player _player;
Scheduler _scheduler;
-
- uint32 _scriptTimer = 0;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 31938e4c062..cc885d66373 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -148,7 +148,7 @@ void Room::update() {
}
void Room::updateScripts() {
- g_engine->updateScriptVariables();
+ g_engine->script().updateCommonVariables();
if (!g_engine->scheduler().hasProcessWithName("ACTUALIZAR_" + _name))
g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, true);
g_engine->scheduler().run();
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 397bdb5a6e1..3c1105d1b0b 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -21,6 +21,7 @@
#include "script.h"
#include "stream-helper.h"
+#include "rooms.h"
#include "alcachofa.h"
#include "common/file.h"
@@ -541,4 +542,20 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
return process;
}
+void Script::updateCommonVariables() {
+ if (g_engine->input().wasAnyMousePressed()) // yes, this variable is never reset by the engine
+ variable("SeHaPulsadoRaton") = 1;
+
+ if (variable("CalcularTiempoSinPulsarRaton")) {
+ if (_scriptTimer == 0)
+ _scriptTimer = g_system->getMillis();
+ }
+ else
+ _scriptTimer = 0;
+
+ variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
+ variable("textoson") = 1; // TODO: Add subtitle option
+ variable("modored") = 1; // this is signalling whether a network connection is established
+}
+
}
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index fa69a57dc2d..7a08e79fc58 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -144,6 +144,7 @@ class Script {
public:
Script();
+ void updateCommonVariables();
int32 variable(const char *name) const;
int32 &variable(const char *name);
Process *createProcess(
@@ -163,6 +164,7 @@ private:
Common::Array<ScriptInstruction> _instructions;
Common::Array<int32> _variables;
Common::SpanOwner<Common::Span<char>> _strings;
+ uint32 _scriptTimer = 0;
};
}
Commit: cb1c9873f03667e6a24cd247fc7832f8a40d2fab
https://github.com/scummvm/scummvm/commit/cb1c9873f03667e6a24cd247fc7832f8a40d2fab
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Implement easy kernel tasks
Changed paths:
engines/alcachofa/camera.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 68d79891524..4e06640361a 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -31,6 +31,8 @@
namespace Alcachofa {
class Personaje;
+class Process;
+struct Task;
static constexpr const int16_t kBaseScale = 300; ///< this number pops up everywhere in the engine
static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index b5c894997a8..a0baef9e6f1 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -452,6 +452,28 @@ void WalkingCharacter::serializeSave(Serializer &serializer) {
syncEnum(serializer, _direction);
}
+struct ArriveTask : public Task {
+ ArriveTask(Process &process, const WalkingCharacter &character)
+ : Task(process)
+ , _character(character) {}
+
+ virtual TaskReturn run() override {
+ return _character._isWalking
+ ? TaskReturn::yield()
+ : TaskReturn::finish(1);
+ }
+
+ virtual void debugPrint() override {
+ g_engine->getDebugger()->debugPrintf("Wait for %s to arrive", _character.name().c_str());
+ }
+private:
+ const WalkingCharacter &_character;
+};
+
+Task *WalkingCharacter::waitForArrival(Process &process) {
+ return new ArriveTask(process, *this);
+}
+
MainCharacter::MainCharacter(Room *room, ReadStream &stream)
: WalkingCharacter(room, stream) {
stream.readByte(); // unused byte
@@ -569,6 +591,48 @@ void MainCharacter::serializeSave(Serializer &serializer) {
}
}
+void MainCharacter::clearInventory() {
+ for (auto *item : _items)
+ item->toggle(false);
+ // TODO: Clear held item on clearInventory
+ g_engine->world().inventory().updateItemsByActiveCharacter();
+}
+
+Item *MainCharacter::getItemByName(const String &name) const {
+ for (auto *item : _items) {
+ if (item->name() == name)
+ return item;
+ }
+ return nullptr;
+}
+
+bool MainCharacter::hasItem(const String &name) const {
+ auto item = getItemByName(name);
+ return item == nullptr || item->isEnabled();
+}
+
+void MainCharacter::pickup(const String &name, bool putInHand) {
+ auto item = getItemByName(name);
+ if (item == nullptr)
+ error("Tried to pickup unknown item: %s", name.c_str());
+ item->toggle(true);
+ if (g_engine->world().activeCharacter() == this) {
+ // TODO: Put item in hand for pickup
+ g_engine->world().inventory().updateItemsByActiveCharacter();
+ }
+}
+
+void MainCharacter::drop(const Common::String &name) {
+ auto item = getItemByName(name);
+ if (item == nullptr)
+ error("Tried to drop unknown item: %s", name.c_str());
+ item->toggle(false);
+ if (g_engine->world().activeCharacter() == this) {
+ // TODO: Clear held item for drop
+ g_engine->world().inventory().updateItemsByActiveCharacter();
+ }
+}
+
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
toggle(true);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index f16fc2c56ed..0756be39745 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -21,6 +21,7 @@
#include "objects.h"
#include "rooms.h"
+#include "scheduler.h"
#include "stream-helper.h"
#include "alcachofa.h"
@@ -122,6 +123,39 @@ Graphic *GraphicObject::graphic() {
return &_graphic;
}
+struct AnimateTask : public Task {
+ AnimateTask(Process &process, GraphicObject *object)
+ : Task(process)
+ , _object(object) {
+ assert(_object != nullptr);
+ _graphic = object->graphic();
+ assert(_graphic != nullptr);
+ _duration = _graphic->animation().totalDuration();
+ }
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ _object->toggle(true);
+ _graphic->start(false);
+ TASK_WAIT(delay(_duration));
+ _object->toggle(false);
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ g_engine->getDebugger()->debugPrintf("Animate \"%s\" for %ums", _object->name().c_str(), _duration);
+ }
+
+private:
+ GraphicObject *_object;
+ Graphic *_graphic;
+ uint32 _duration;
+};
+
+Task *GraphicObject::animate(Process &process) {
+ return new AnimateTask(process, this);
+}
+
SpecialEffectObject::SpecialEffectObject(Room *room, ReadStream &stream)
: GraphicObject(room, stream) {
_topLeft = Shape(stream).firstPoint();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 4ceb3f8f7a8..92f0158bd14 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -30,6 +30,8 @@
namespace Alcachofa {
class Room;
+class Process;
+struct Task;
class ObjectBase {
public:
@@ -90,6 +92,8 @@ public:
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
+ Task *animate(Process &process);
+
protected:
GraphicObject(Room *room, const char *name);
@@ -368,7 +372,10 @@ public:
void stopWalkingAndTurn(Direction direction);
void setPosition(const Common::Point &target);
+ Task *waitForArrival(Process &process);
+
protected:
+ friend struct ArriveTask;
virtual void onArrived();
void updateWalking();
void updateWalkingAnimation();
@@ -425,11 +432,16 @@ public:
Direction endDirection = Direction::Invalid,
ITriggerableObject *activateObject = nullptr,
const char *activateAction = nullptr) override;
+ void clearInventory();
+ bool hasItem(const Common::String &name) const;
+ void pickup(const Common::String &name, bool putInHand);
+ void drop(const Common::String &name);
protected:
virtual void onArrived() override;
private:
+ Item *getItemByName(const Common::String &name) const;
void drawInner();
Common::Array<Item *> _items;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index cc885d66373..c0f4359de05 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -225,6 +225,10 @@ void Room::serializeSave(Serializer &serializer) {
object->serializeSave(serializer);
}
+void Room::toggleActiveFloor() {
+ _activeFloorI ^= 1;
+}
+
OptionsMenu::OptionsMenu(World *world, ReadStream &stream)
: Room(world, stream, true) {
}
@@ -246,6 +250,13 @@ Inventory::~Inventory() {
delete item;
}
+void Inventory::updateItemsByActiveCharacter() {
+ auto *character = world().activeCharacter();
+ assert(character != nullptr);
+ for (auto *item : _items)
+ item->toggle(character->hasItem(item->name()));
+}
+
static constexpr const char *kMapFiles[] = {
"MAPAS/MAPA5.EMC",
"MAPAS/MAPA4.EMC",
@@ -309,6 +320,40 @@ ObjectBase *World::getObjectByName(const Common::String &name) const {
return result;
}
+ObjectBase *World::getObjectByName(MainCharacterKind character, const Common::String &name) const {
+ if (character == MainCharacterKind::None)
+ return getObjectByName(name);
+ ObjectBase *result = nullptr;
+ if (activeCharacterKind() == character && currentRoom() == activeCharacter()->room())
+ result = currentRoom()->getObjectByName(name);
+ if (result == nullptr)
+ result = activeCharacter()->room()->getObjectByName(name);
+ if (result == nullptr)
+ result = globalRoom().getObjectByName(name);
+ if (result == nullptr)
+ result = inventory().getObjectByName(name);
+ return result;
+}
+
+ObjectBase *World::getObjectByNameFromAnyRoom(const Common::String &name) const {
+ for (auto *room : _rooms) {
+ ObjectBase *result = room->getObjectByName(name);
+ if (result != nullptr)
+ return result;
+ }
+ return nullptr;
+}
+
+void World::toggleObject(MainCharacterKind character, const Common::String &objName, bool isEnabled) {
+ ObjectBase *object = getObjectByName(character, objName);
+ if (object == nullptr)
+ object = getObjectByNameFromAnyRoom(objName);
+ if (object == nullptr)
+ error("Tried to toggle unknown object: %s", objName.c_str());
+ else
+ object->toggle(isEnabled);
+}
+
const Common::String &World::getGlobalAnimationName(GlobalAnimationKind kind) const {
int kindI = (int)kind;
assert(kindI >= 0 && kindI < (int)GlobalAnimationKind::Count);
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index b7be6a583b0..605a7df9d6b 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -54,6 +54,7 @@ public:
virtual void freeResources();
virtual void serializeSave(Common::Serializer &serializer);
ObjectBase *getObjectByName(const Common::String &name) const;
+ void toggleActiveFloor();
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
@@ -101,6 +102,8 @@ public:
Inventory(World *world, Common::ReadStream &stream);
virtual ~Inventory() override;
+ void updateItemsByActiveCharacter();
+
private:
Common::Array<Item *> _items;
};
@@ -141,12 +144,19 @@ public:
return filemon().currentlyUsing() == object ||
mortadelo().currentlyUsing() == object;
}
+ inline MainCharacterKind activeCharacterKind() const {
+ return _activeCharacter == nullptr ? MainCharacterKind::None : _activeCharacter->kind();
+ }
MainCharacter &getMainCharacterByKind(MainCharacterKind kind) const;
Room *getRoomByName(const Common::String &name) const;
ObjectBase *getObjectByName(const Common::String &name) const;
+ ObjectBase *getObjectByName(MainCharacterKind character, const Common::String &name) const;
+ ObjectBase *getObjectByNameFromAnyRoom(const Common::String &name) const;
const Common::String &getGlobalAnimationName(GlobalAnimationKind kind) const;
+ void toggleObject(MainCharacterKind character, const Common::String &objName, bool isEnabled);
+
private:
bool loadWorldFile(const char *path);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 3c1105d1b0b..69e9659536a 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -102,6 +102,31 @@ int32 &Script::variable(const char *name) {
return _variables[index];
}
+struct ScriptTimerTask : public Task {
+ ScriptTimerTask(Process &process, int32 durationSec)
+ : Task(process)
+ , _durationSec(durationSec) {}
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ if (_durationSec >= (int32)((g_system->getMillis() - g_engine->script()._scriptTimer) / 1000) &&
+ g_engine->script().variable("SeHaPulsadoRaton"))
+ _result = 0;
+
+ // TODO: Add network behavior for script timer
+ TASK_YIELD;
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ g_engine->getDebugger()->debugPrintf("Check input timer for %dsecs", _durationSec);
+ }
+
+private:
+ int32 _durationSec;
+ int32 _result = 1;
+};
+
enum class StackEntryType {
Number,
Variable,
@@ -347,6 +372,17 @@ private:
return _script._strings->data() + entry._index;
}
+ MainCharacter &relatedCharacter() {
+ if (process().character() == MainCharacterKind::None)
+ error("Script tried to use character from non-character-related process");
+ return g_engine->world().getMainCharacterByKind(process().character());
+ }
+
+ bool shouldSkipCutscene() {
+ return process().character() != MainCharacterKind::None &&
+ g_engine->world().activeCharacterKind() != process().character();
+ }
+
TaskReturn kernelCall(ScriptKernelTask task) {
switch (task) {
case ScriptKernelTask::PlayVideo:
@@ -367,64 +403,107 @@ private:
case ScriptKernelTask::ShowCenterBottomText:
warning("STUB KERNEL CALL: ShowCenterBottomText");
return TaskReturn::finish(0);
- case ScriptKernelTask::StopAndTurn:
- warning("STUB KERNEL CALL: StopAndTurn");
- return TaskReturn::finish(0);
- case ScriptKernelTask::StopAndTurnMe:
- warning("STUB KERNEL CALL: StopAndTurnMe");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::StopAndTurn: {
+ auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto character = dynamic_cast<WalkingCharacter *>(object);
+ if (character == nullptr)
+ error("Script tried to stop-and-turn unknown character");
+ else
+ character->stopWalkingAndTurn((Direction)getNumberArg(1));
+ return TaskReturn::finish(1);
+ }
+ case ScriptKernelTask::StopAndTurnMe: {
+ relatedCharacter().stopWalkingAndTurn((Direction)getNumberArg(0));
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::ChangeCharacter:
warning("STUB KERNEL CALL: ChangeCharacter");
return TaskReturn::finish(0);
case ScriptKernelTask::SayText:
warning("STUB KERNEL CALL: SayText");
return TaskReturn::finish(0);
- case ScriptKernelTask::Go:
- warning("STUB KERNEL CALL: Go");
- return TaskReturn::finish(0);
- case ScriptKernelTask::Put:
- warning("STUB KERNEL CALL: Put");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::Go: {
+ auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto character = dynamic_cast<WalkingCharacter *>(characterObject);
+ if (character == nullptr)
+ error("Script tried to make invalid character go: %s", getStringArg(0));
+ auto targetObject = g_engine->world().getObjectByName(process().character(), getStringArg(1));
+ auto target = dynamic_cast<PointObject *>(targetObject);
+ if (target == nullptr)
+ error("Script tried to make character go to invalid object %s", getStringArg(1));
+ character->walkTo(target->position());
+
+ // TODO: if (flags & 2) g_engine->camera().setFollow(nullptr);
+
+ return (getNumberArg(2) & 1)
+ ? TaskReturn::finish(1)
+ : TaskReturn::waitFor(character->waitForArrival(process()));
+ }
+ case ScriptKernelTask::Put: {
+ auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto character = dynamic_cast<WalkingCharacter *>(characterObject);
+ if (character == nullptr)
+ error("Script tried to make invalid character go: %s", getStringArg(0));
+ auto targetObject = g_engine->world().getObjectByName(process().character(), getStringArg(1));
+ auto target = dynamic_cast<PointObject *>(targetObject);
+ if (target == nullptr)
+ error("Script tried to make character go to invalid object %s", getStringArg(1));
+ character->setPosition(target->position());
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::ChangeCharacterRoom:
warning("STUB KERNEL CALL: ChangeCharacterRoom");
return TaskReturn::finish(0);
case ScriptKernelTask::KillProcesses:
warning("STUB KERNEL CALL: KillProcesses");
return TaskReturn::finish(0);
- case ScriptKernelTask::LerpLodBias:
- warning("STUB KERNEL CALL: LerpLodBias");
+ case ScriptKernelTask::LerpCharacterLodBias:
+ warning("STUB KERNEL CALL: LerpCharacterLodBias");
return TaskReturn::finish(0);
case ScriptKernelTask::On:
- warning("STUB KERNEL CALL: On");
+ g_engine->world().toggleObject(process().character(), getStringArg(0), true);
return TaskReturn::finish(0);
case ScriptKernelTask::Off:
- warning("STUB KERNEL CALL: Off");
+ g_engine->world().toggleObject(process().character(), getStringArg(0), false);
return TaskReturn::finish(0);
case ScriptKernelTask::Pickup:
- warning("STUB KERNEL CALL: Pickup");
- return TaskReturn::finish(0);
- case ScriptKernelTask::CharacterPickup:
- warning("STUB KERNEL CALL: CharacterPickup");
- return TaskReturn::finish(0);
+ relatedCharacter().pickup(getStringArg(0), getNumberArg(1));
+ return TaskReturn::finish(1);
+ case ScriptKernelTask::CharacterPickup: {
+ auto &character = g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(1));
+ character.pickup(getStringArg(0), getNumberArg(2));
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::Drop:
- warning("STUB KERNEL CALL: Drop");
- return TaskReturn::finish(0);
- case ScriptKernelTask::CharacterDrop:
- warning("STUB KERNEL CALL: CharacterDrop");
- return TaskReturn::finish(0);
+ relatedCharacter().drop(getStringArg(0));
+ return TaskReturn::finish(1);
+ case ScriptKernelTask::CharacterDrop: {
+ auto &character = g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(1));
+ character.drop(getStringArg(0));
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::Delay:
return getNumberArg(0) <= 0
? TaskReturn::finish(0)
: TaskReturn::waitFor(delay((uint32)getNumberArg(0)));
case ScriptKernelTask::HadNoMousePressFor:
- warning("STUB KERNEL CALL: HadNoMousePressFor");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(new ScriptTimerTask(process(), getNumberArg(0)));
case ScriptKernelTask::Fork:
g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
return TaskReturn::finish(0); // 0 means this is the forking process
- case ScriptKernelTask::Animate:
- warning("STUB KERNEL CALL: Animate");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::Animate: {
+ auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto graphicObject = dynamic_cast<GraphicObject *>(object);
+ if (graphicObject == nullptr)
+ error("Script tried to animate invalid graphic object %s", getStringArg(0));
+ if (getNumberArg(1)) {
+ graphicObject->toggle(true);
+ graphicObject->graphic()->start(false);
+ return TaskReturn::finish(1);
+ }
+ else
+ return TaskReturn::waitFor(graphicObject->animate(process()));
+ }
case ScriptKernelTask::AnimateCharacter:
warning("STUB KERNEL CALL: AnimateCharacter");
return TaskReturn::finish(0);
@@ -435,8 +514,13 @@ private:
warning("STUB KERNEL CALL: ChangeRoom");
return TaskReturn::finish(0);
case ScriptKernelTask::ToggleRoomFloor:
- warning("STUB KERNEL CALL: ToggleRoomFloor");
- return TaskReturn::finish(0);
+ if (process().character() == MainCharacterKind::None) {
+ if (g_engine->world().currentRoom() != nullptr)
+ g_engine->world().currentRoom()->toggleActiveFloor();
+ }
+ else
+ g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
+ return TaskReturn::finish(1);
case ScriptKernelTask::SetDialogLineReturn:
warning("STUB KERNEL CALL: SetDialogLineReturn");
return TaskReturn::finish(0);
@@ -444,23 +528,24 @@ private:
warning("STUB KERNEL CALL: DialogMenu");
return TaskReturn::finish(0);
case ScriptKernelTask::ClearInventory:
- warning("STUB KERNEL CALL: ClearInventory");
- return TaskReturn::finish(0);
+ switch((MainCharacterKind)getNumberArg(0)) {
+ case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory();
+ case MainCharacterKind::Filemon: g_engine->world().filemon().clearInventory();
+ default: error("Script attempted to clear inventory with invalid character kind");
+ }
+ return TaskReturn::finish(1);
case ScriptKernelTask::FadeType0:
warning("STUB KERNEL CALL: FadeType0");
return TaskReturn::finish(0);
case ScriptKernelTask::FadeType1:
warning("STUB KERNEL CALL: FadeType1");
return TaskReturn::finish(0);
- case ScriptKernelTask::SetLodBias:
- warning("STUB KERNEL CALL: SetLodBias");
+ case ScriptKernelTask::LerpWorldLodBias:
+ warning("STUB KERNEL CALL: LerpWorldLodBias");
return TaskReturn::finish(0);
case ScriptKernelTask::FadeType2:
warning("STUB KERNEL CALL: FadeType2");
return TaskReturn::finish(0);
- case ScriptKernelTask::SetActiveTextureSet:
- warning("STUB KERNEL CALL: SetActiveTextureSet");
- return TaskReturn::finish(0);
case ScriptKernelTask::SetMaxCamSpeedFactor:
warning("STUB KERNEL CALL: SetMaxCamSpeedFactor");
return TaskReturn::finish(0);
@@ -509,6 +594,10 @@ private:
case ScriptKernelTask::LerpCamToObjectKeepingZ:
warning("STUB KERNEL CALL: LerpCamToObjectKeepingZ");
return TaskReturn::finish(0);
+ case ScriptKernelTask::SetActiveTextureSet:
+ // Fortunately this seems to be unused.
+ warning("STUB KERNEL CALL: SetActiveTextureSet");
+ return TaskReturn::finish(0);
case ScriptKernelTask::Nop10:
case ScriptKernelTask::Nop24:
case ScriptKernelTask::Nop34:
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 7a08e79fc58..827726f2018 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -90,7 +90,7 @@ enum class ScriptKernelTask {
Put,
ChangeCharacterRoom,
KillProcesses,
- LerpLodBias,
+ LerpCharacterLodBias,
On,
Off,
Pickup,
@@ -112,7 +112,7 @@ enum class ScriptKernelTask {
Nop34,
FadeType0,
FadeType1,
- SetLodBias,
+ LerpWorldLodBias,
FadeType2,
SetActiveTextureSet,
SetMaxCamSpeedFactor,
@@ -159,6 +159,7 @@ public:
private:
friend struct ScriptTask;
+ friend struct ScriptTimerTask;
Common::HashMap<Common::String, uint32> _variableNames;
Common::HashMap<Common::String, uint32> _procedures;
Common::Array<ScriptInstruction> _instructions;
Commit: 0e38466e3573387d32d473ce43902dff09bc8ce4
https://github.com/scummvm/scummvm/commit/0e38466e3573387d32d473ce43902dff09bc8ce4
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Fix kernel call animate not accepting string as boolean arg
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 69e9659536a..5a7c130c586 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -372,6 +372,15 @@ private:
return _script._strings->data() + entry._index;
}
+ int32 getNumberOrStringArg(uint argI) {
+ // Original inconsistency: sometimes a string is passed instead of a number
+ // as it will be interpreted as a boolean we only care about == 0 / != 0
+ auto entry = getArg(argI);
+ if (entry._type != StackEntryType::Number && entry._type != StackEntryType::String)
+ error("Expected number of string in argument %u for kernel call", argI);
+ return entry._number;
+ }
+
MainCharacter &relatedCharacter() {
if (process().character() == MainCharacterKind::None)
error("Script tried to use character from non-character-related process");
@@ -496,7 +505,7 @@ private:
auto graphicObject = dynamic_cast<GraphicObject *>(object);
if (graphicObject == nullptr)
error("Script tried to animate invalid graphic object %s", getStringArg(0));
- if (getNumberArg(1)) {
+ if (getNumberOrStringArg(1)) {
graphicObject->toggle(true);
graphicObject->graphic()->start(false);
return TaskReturn::finish(1);
Commit: bb2848b3715f3b5154796a2215bd09517cecb63d
https://github.com/scummvm/scummvm/commit/bb2848b3715f3b5154796a2215bd09517cecb63d
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Move changing state into Player instead of World
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 61fb9703ce3..cb7fe5cba19 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -68,7 +68,7 @@ Common::Error AlcachofaEngine::run() {
auto room = world().getRoomByName("MAPA_TERROR");
assert(room != nullptr);
- world().currentRoom() = room;
+ player().currentRoom() = room;
room->loadResources();
// If a savegame was selected from the launcher, load it
@@ -91,7 +91,7 @@ Common::Error AlcachofaEngine::run() {
_drawQueue->clear();
_camera.shake() = Vector2d();
- world().currentRoom()->update();
+ player().currentRoom()->update();
_renderer->end();
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index a0baef9e6f1..928f5017170 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -239,7 +239,7 @@ void WalkingCharacter::update() {
_interactionPoint = _currentPos;
_interactionDirection = Direction::Right;
- if (this != g_engine->world().activeCharacter()) {
+ if (this != g_engine->player().activeCharacter()) {
int16 interactionOffset = (int16)(150 * _graphicNormal.depthScale());
_interactionPoint.x -= interactionOffset;
if (activeFloor != nullptr && activeFloor->polygonContaining(_interactionPoint) < 0) {
@@ -514,7 +514,7 @@ void MainCharacter::onArrived() {
_activateAction = nullptr;
stopWalkingAndTurn(activateObject->interactionDirection());
- if (g_engine->world().activeCharacter() == this)
+ if (g_engine->player().activeCharacter() == this)
activateObject->trigger(activateAction);
}
@@ -527,7 +527,7 @@ void MainCharacter::walkTo(
// TODO: Add collision avoidance
WalkingCharacter::walkTo(target, endDirection, activateObject, activateAction);
- if (this == g_engine->world().activeCharacter()) {
+ if (this == g_engine->player().activeCharacter()) {
// TODO: Add camera following character
}
}
@@ -546,7 +546,7 @@ void MainCharacter::draw() {
}
void MainCharacter::drawInner() {
- if (room() != g_engine->world().currentRoom() || !isEnabled())
+ if (room() != g_engine->player().currentRoom() || !isEnabled())
return;
Graphic *activeGraphic = graphicOf(_curAnimateObject);
if (activeGraphic == nullptr && _isWalking) {
@@ -616,7 +616,7 @@ void MainCharacter::pickup(const String &name, bool putInHand) {
if (item == nullptr)
error("Tried to pickup unknown item: %s", name.c_str());
item->toggle(true);
- if (g_engine->world().activeCharacter() == this) {
+ if (g_engine->player().activeCharacter() == this) {
// TODO: Put item in hand for pickup
g_engine->world().inventory().updateItemsByActiveCharacter();
}
@@ -627,7 +627,7 @@ void MainCharacter::drop(const Common::String &name) {
if (item == nullptr)
error("Tried to drop unknown item: %s", name.c_str());
item->toggle(false);
- if (g_engine->world().activeCharacter() == this) {
+ if (g_engine->player().activeCharacter() == this) {
// TODO: Clear held item for drop
g_engine->world().inventory().updateItemsByActiveCharacter();
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index b0abfa21f7e..10b35706717 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -22,16 +22,27 @@
#ifndef PLAYER_H
#define PLAYER_H
-namespace Alcachofa {
+#include "rooms.h"
-class ShapeObject;
+namespace Alcachofa {
class Player {
public:
- inline ShapeObject *selectedObject() { return _selectedObject; }
+ inline Room *currentRoom() const { return _currentRoom; }
+ inline Room *¤tRoom() { return _currentRoom; }
+ inline MainCharacter *activeCharacter() const { return _activeCharacter; }
+ inline ShapeObject *selectedObject() const { return _selectedObject; }
+ inline Item *heldItem() const { return _heldItem; }
+
+ inline MainCharacterKind activeCharacterKind() const {
+ return _activeCharacter == nullptr ? MainCharacterKind::None : _activeCharacter->kind();
+ }
private:
+ Room *_currentRoom = nullptr;
+ MainCharacter *_activeCharacter;
ShapeObject *_selectedObject = nullptr;
+ Item *_heldItem = nullptr;
};
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c0f4359de05..80f96791948 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -128,15 +128,15 @@ ObjectBase *Room::getObjectByName(const Common::String &name) const {
void Room::update() {
updateScripts();
- if (world().currentRoom() == this) {
+ if (g_engine->player().currentRoom() == this) {
updateRoomBounds();
updateInput();
}
// TODO: Add condition for global room update
world().globalRoom().updateObjects();
- if (world().currentRoom() == this)
+ if (g_engine->player().currentRoom() == this)
updateObjects();
- if (world().currentRoom() == this) {
+ if (g_engine->player().currentRoom() == this) {
g_engine->camera().update();
drawObjects();
world().globalRoom().drawObjects();
@@ -183,10 +183,10 @@ void Room::updateRoomBounds() {
}
void Room::updateObjects() {
- const auto *previousRoom = world().currentRoom();
+ const auto *previousRoom = g_engine->player().currentRoom();
for (auto *object : _objects) {
object->update();
- if (world().currentRoom() != previousRoom)
+ if (g_engine->player().currentRoom() != previousRoom)
return;
}
}
@@ -251,7 +251,7 @@ Inventory::~Inventory() {
}
void Inventory::updateItemsByActiveCharacter() {
- auto *character = world().activeCharacter();
+ auto *character = g_engine->player().activeCharacter();
assert(character != nullptr);
for (auto *item : _items)
item->toggle(character->hasItem(item->name()));
@@ -311,8 +311,8 @@ Room *World::getRoomByName(const Common::String &name) const {
ObjectBase *World::getObjectByName(const Common::String &name) const {
ObjectBase *result = nullptr;
- if (result == nullptr && _currentRoom != nullptr)
- result = _currentRoom->getObjectByName(name);
+ if (result == nullptr && g_engine->player().currentRoom() != nullptr)
+ result = g_engine->player().currentRoom()->getObjectByName(name);
if (result == nullptr)
result = globalRoom().getObjectByName(name);
if (result == nullptr)
@@ -323,11 +323,12 @@ ObjectBase *World::getObjectByName(const Common::String &name) const {
ObjectBase *World::getObjectByName(MainCharacterKind character, const Common::String &name) const {
if (character == MainCharacterKind::None)
return getObjectByName(name);
+ const auto &player = g_engine->player();
ObjectBase *result = nullptr;
- if (activeCharacterKind() == character && currentRoom() == activeCharacter()->room())
- result = currentRoom()->getObjectByName(name);
+ if (player.activeCharacterKind() == character && player.currentRoom() == player.activeCharacter()->room())
+ result = player.currentRoom()->getObjectByName(name);
if (result == nullptr)
- result = activeCharacter()->room()->getObjectByName(name);
+ result = player.activeCharacter()->room()->getObjectByName(name);
if (result == nullptr)
result = globalRoom().getObjectByName(name);
if (result == nullptr)
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 605a7df9d6b..d06ca35d565 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -133,20 +133,13 @@ public:
inline Inventory &inventory() const { return *_inventory; }
inline MainCharacter &filemon() const { return *_filemon; }
inline MainCharacter &mortadelo() const { return *_mortadelo; }
- inline MainCharacter *activeCharacter() const { return _activeCharacter; }
inline const Common::String &initScriptName() const { return _initScriptName; }
inline uint8 loadedMapCount() const { return _loadedMapCount; }
- inline Room *¤tRoom() { return _currentRoom; }
- inline Room *currentRoom() const { return _currentRoom; }
-
inline bool somebodyUsing(ObjectBase *object) const {
return filemon().currentlyUsing() == object ||
mortadelo().currentlyUsing() == object;
}
- inline MainCharacterKind activeCharacterKind() const {
- return _activeCharacter == nullptr ? MainCharacterKind::None : _activeCharacter->kind();
- }
MainCharacter &getMainCharacterByKind(MainCharacterKind kind) const;
Room *getRoomByName(const Common::String &name) const;
@@ -163,9 +156,9 @@ private:
Common::Array<Room *> _rooms;
Common::String _globalAnimationNames[(int)GlobalAnimationKind::Count];
Common::String _initScriptName;
- Room *_globalRoom, *_currentRoom = nullptr;
+ Room *_globalRoom;
Inventory *_inventory;
- MainCharacter *_filemon, *_mortadelo, *_activeCharacter = nullptr;
+ MainCharacter *_filemon, *_mortadelo;
uint8 _loadedMapCount = 0;
};
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 5a7c130c586..d27be2671e2 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -389,7 +389,7 @@ private:
bool shouldSkipCutscene() {
return process().character() != MainCharacterKind::None &&
- g_engine->world().activeCharacterKind() != process().character();
+ g_engine->player().activeCharacterKind() != process().character();
}
TaskReturn kernelCall(ScriptKernelTask task) {
@@ -524,8 +524,8 @@ private:
return TaskReturn::finish(0);
case ScriptKernelTask::ToggleRoomFloor:
if (process().character() == MainCharacterKind::None) {
- if (g_engine->world().currentRoom() != nullptr)
- g_engine->world().currentRoom()->toggleActiveFloor();
+ if (g_engine->player().currentRoom() != nullptr)
+ g_engine->player().currentRoom()->toggleActiveFloor();
}
else
g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
@@ -538,9 +538,9 @@ private:
return TaskReturn::finish(0);
case ScriptKernelTask::ClearInventory:
switch((MainCharacterKind)getNumberArg(0)) {
- case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory();
- case MainCharacterKind::Filemon: g_engine->world().filemon().clearInventory();
- default: error("Script attempted to clear inventory with invalid character kind");
+ case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory(); break;
+ case MainCharacterKind::Filemon: g_engine->world().filemon().clearInventory(); break;
+ default: error("Script attempted to clear inventory with invalid character kind"); break;
}
return TaskReturn::finish(1);
case ScriptKernelTask::FadeType0:
Commit: 24dbc3852808912f3c66adb841e515c4c9d0ad3e
https://github.com/scummvm/scummvm/commit/24dbc3852808912f3c66adb841e515c4c9d0ad3e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Initialize items
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 928f5017170..9c3bff96b41 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -34,6 +34,14 @@ Item::Item(Room *room, ReadStream &stream)
stream.readByte(); // unused and ignored byte
}
+Item::Item(const Item &other)
+ : GraphicObject(other.room(), other.name().c_str()) {
+ _type = other._type;
+ _posterizeAlpha = other._posterizeAlpha;
+ _graphic.~Graphic();
+ new (&_graphic) Graphic(other._graphic);
+}
+
ITriggerableObject::ITriggerableObject(ReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
, _interactionDirection((Direction)stream.readSint32LE()) {}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 60af943f46a..baf21976bbc 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -373,6 +373,18 @@ Graphic::Graphic(ReadStream &stream) {
setAnimation(animationName, AnimationFolder::Animations);
}
+Graphic::Graphic(const Graphic &other)
+ : _animation(other._animation)
+ , _center(other._center)
+ , _scale(other._scale)
+ , _order(other._order)
+ , _color(other._color)
+ , _isPaused(other._isPaused)
+ , _isLooping(other._isLooping)
+ , _lastTime(other._lastTime)
+ , _frameI(other._frameI)
+ , _depthScale(other._depthScale) {}
+
void Graphic::loadResources() {
if (_animation != nullptr)
_animation->load();
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 191c1ba9896..7eb866456d0 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -220,6 +220,7 @@ class Graphic {
public:
Graphic();
Graphic(Common::ReadStream &stream);
+ Graphic(const Graphic &other); // animation reference is taken, so keep other alive
inline Common::Point ¢er() { return _center; }
inline int8 &order() { return _order; }
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 92f0158bd14..90db2a1ce80 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -283,6 +283,7 @@ class Item : public GraphicObject {
public:
static constexpr const char *kClassName = "CObjetoInventario";
Item(Room *room, Common::ReadStream &stream);
+ Item(const Item &other);
};
class ITriggerableObject {
@@ -341,7 +342,7 @@ protected:
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
void updateTalkingAnimation();
- Direction _direction;
+ Direction _direction = Direction::Right;
Graphic _graphicNormal, _graphicTalking;
bool _isTalking = false;
@@ -441,6 +442,7 @@ protected:
virtual void onArrived() override;
private:
+ friend class Inventory;
Item *getItemByName(const Common::String &name) const;
void drawInner();
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 80f96791948..3cf924a76d2 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -250,6 +250,19 @@ Inventory::~Inventory() {
delete item;
}
+void Inventory::initItems() {
+ auto &mortadelo = world().mortadelo();
+ auto &filemon = world().filemon();
+ for (auto object : _objects) {
+ auto item = dynamic_cast<Item *>(object);
+ if (item == nullptr)
+ continue;
+ _items.push_back(item);
+ mortadelo._items.push_back(new Item(*item));
+ filemon._items.push_back(new Item(*item));
+ }
+}
+
void Inventory::updateItemsByActiveCharacter() {
auto *character = g_engine->player().activeCharacter();
assert(character != nullptr);
@@ -285,6 +298,8 @@ World::World() {
_mortadelo = dynamic_cast<MainCharacter *>(_globalRoom->getObjectByName("MORTADELO"));
if (_mortadelo == nullptr)
error("Could not find MORTADELO");
+
+ _inventory->initItems();
}
World::~World() {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index d06ca35d565..d7b7f08ab9d 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -102,6 +102,7 @@ public:
Inventory(World *world, Common::ReadStream &stream);
virtual ~Inventory() override;
+ void initItems();
void updateItemsByActiveCharacter();
private:
Commit: 0cf432015cbf5ba9de28d04a081c73446f667af3
https://github.com/scummvm/scummvm/commit/0cf432015cbf5ba9de28d04a081c73446f667af3
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Add game room interaction
theoretically at least
Changed paths:
A engines/alcachofa/player.cpp
engines/alcachofa/Input.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/shape.cpp
diff --git a/engines/alcachofa/Input.h b/engines/alcachofa/Input.h
index ee6028ad355..248a40a62c2 100644
--- a/engines/alcachofa/Input.h
+++ b/engines/alcachofa/Input.h
@@ -36,6 +36,7 @@ public:
inline bool wasAnyMouseReleased() const { return _wasMouseLeftReleased || _wasMouseRightReleased; }
inline bool isMouseLeftDown() const { return _isMouseLeftDown; }
inline bool isMouseRightDown() const { return _isMouseRightDown; }
+ inline bool isAnyMouseDown() const { return _isMouseLeftDown || _isMouseRightDown; }
inline const Common::Point &mousePos2D() const { return _mousePos2D; }
inline const Common::Point &mousePos3D() const { return _mousePos3D; }
@@ -44,12 +45,12 @@ public:
private:
bool
- _wasMouseLeftPressed,
- _wasMouseRightPressed,
- _wasMouseLeftReleased,
- _wasMouseRightReleased,
- _isMouseLeftDown,
- _isMouseRightDown;
+ _wasMouseLeftPressed = false,
+ _wasMouseRightPressed = false,
+ _wasMouseLeftReleased = false,
+ _wasMouseRightReleased = false,
+ _isMouseLeftDown = false,
+ _isMouseRightDown = false;
Common::Point
_mousePos2D,
_mousePos3D;
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index cb7fe5cba19..5218047a42b 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -58,11 +58,13 @@ Common::String AlcachofaEngine::getGameId() const {
}
Common::Error AlcachofaEngine::run() {
+ g_system->showMouse(false);
setDebugger(_console);
_renderer.reset(IRenderer::createOpenGLRenderer(Common::Point(1024, 768)));
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
_script.reset(new Script());
+ _player.reset(new Player());
world().globalRoom().loadResources();
@@ -78,9 +80,8 @@ Common::Error AlcachofaEngine::run() {
Common::Event e;
- Graphics::FrameLimiter limiter(g_system, 60);
+ Graphics::FrameLimiter limiter(g_system, 120);
while (!shouldQuit()) {
- g_system->showMouse(true);
_input.nextFrame();
while (g_system->getEventManager()->pollEvent(e)) {
if (_input.handleEvent(e))
@@ -90,8 +91,9 @@ Common::Error AlcachofaEngine::run() {
_renderer->begin();
_drawQueue->clear();
_camera.shake() = Vector2d();
-
- player().currentRoom()->update();
+ _player->preUpdate();
+ _player->currentRoom()->update();
+ _player->postUpdate();
_renderer->end();
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 98e3e48a3d6..117b6b4ddab 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -64,7 +64,7 @@ public:
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
inline Input &input() { return _input; }
- inline Player &player() { return _player; }
+ inline Player &player() { return *_player; }
inline World &world() { return *_world; }
inline Script &script() { return *_script; }
inline Scheduler &scheduler() { return _scheduler; }
@@ -119,9 +119,9 @@ private:
Common::ScopedPtr<DrawQueue> _drawQueue;
Common::ScopedPtr<World> _world;
Common::ScopedPtr<Script> _script;
+ Common::ScopedPtr<Player> _player;
Camera _camera;
Input _input;
- Player _player;
Scheduler _scheduler;
};
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 77d2c348afc..9d41e4f9020 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -23,18 +23,19 @@
#define COMMON_H
#include "common/scummsys.h"
+#include "common/rect.h"
+#include "math/vector2d.h"
+#include "math/vector3d.h"
namespace Alcachofa {
enum class CursorType {
- Normal,
- LookAt,
- Use,
- GoTo,
+ Point,
LeaveUp,
LeaveRight,
LeaveDown,
- LeaveLeft
+ LeaveLeft,
+ WalkTo
};
enum class Direction {
@@ -66,6 +67,52 @@ static constexpr const Color kDebugRed = { 250, 0, 0, 70 };
static constexpr const Color kDebugGreen = { 0, 255, 0, 85 };
static constexpr const Color kDebugBlue = { 0, 0, 255, 110 };
+/**
+ * @brief This *fake* semaphore does not work in multi-threaded scenarios
+ * It is used as a safer option for a simple "isBusy" counter
+ */
+struct FakeSemaphore {
+ FakeSemaphore(uint initialCount = 0) : _counter(initialCount) {}
+ ~FakeSemaphore() {
+ assert(_counter == 0);
+ }
+
+ inline bool isReleased() const { return _counter == 0; }
+ inline uint counter() const { return _counter; }
+private:
+ friend struct FakeLock;
+ uint _counter = 0;
+};
+
+struct FakeLock {
+ FakeLock(FakeSemaphore &semaphore) : _semaphore(semaphore) {
+ semaphore._counter++;
+ }
+
+ ~FakeLock() {
+ assert(_semaphore._counter > 0);
+ _semaphore._counter--;
+ }
+private:
+ FakeSemaphore &_semaphore;
+};
+
+inline Math::Vector3d as3D(const Math::Vector2d &v) {
+ return Math::Vector3d(v.getX(), v.getY(), 0.0f);
+}
+
+inline Math::Vector3d as3D(const Common::Point &p) {
+ return Math::Vector3d((float)p.x, (float)p.y, 0.0f);
+}
+
+inline Math::Vector2d as2D(const Math::Vector3d &v) {
+ return Math::Vector2d(v.x(), v.y());
+}
+
+inline Math::Vector2d as2D(const Common::Point &p) {
+ return Math::Vector2d((float)p.x, (float)p.y);
+}
+
}
#endif // ALCACHOFA_COMMON_H
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 9c3bff96b41..4e9be05190e 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -498,8 +498,12 @@ MainCharacter::~MainCharacter() {
delete item;
}
+bool MainCharacter::isBusy() const {
+ return !_semaphore.isReleased() || !g_engine->player().semaphore().isReleased();
+}
+
void MainCharacter::update() {
- if (_relatedProcessCounter == 0)
+ if (_semaphore.isReleased())
_currentlyUsingObject = nullptr;
WalkingCharacter::update();
@@ -588,7 +592,9 @@ void MainCharacter::serializeSave(Serializer &serializer) {
}
Character::serializeSave(serializer);
- serializer.syncAsSint32LE(_relatedProcessCounter);
+ uint semaphoreCounter = _semaphore.counter();
+ serializer.syncAsSint32LE(semaphoreCounter);
+ _semaphore = FakeSemaphore(semaphoreCounter);
syncArray(serializer, _dialogMenuLines, syncDialogMenuLine);
syncObjectAsString(serializer, _currentlyUsingObject);
@@ -641,6 +647,20 @@ void MainCharacter::drop(const Common::String &name) {
}
}
+void MainCharacter::walkToMouse() {
+ Point targetPos = g_engine->input().mousePos3D();
+ if (room()->activeFloor() != nullptr) {
+ _pathPoints.clear();
+ room()->activeFloor()->findPath(_sourcePos, targetPos, _pathPoints);
+ if (!_pathPoints.empty())
+ targetPos = _pathPoints[0];
+ }
+
+ const uint minDistance = (uint)(50 * _graphicNormal.depthScale());
+ if (_sourcePos.sqrDist(targetPos) > minDistance * minDistance)
+ walkTo(targetPos);
+}
+
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
toggle(true);
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index b1998aa8f39..56062fb74fa 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -131,10 +131,7 @@ public:
_currentTexture = nullptr;
_currentBlendMode = (BlendMode)-1;
-#ifdef _DEBUG
- glClearColor(0.5f, 0.0f, 0.5f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
-#endif
+ // Do not clear the screen as the engine sometimes relies on the old frame to be reused
}
virtual void end() override {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index baf21976bbc..8c64748aa16 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -222,10 +222,10 @@ Rect Animation::maxFrameBounds() const {
return bounds;
}
-Math::Vector2d Animation::totalFrameOffset(int32 frameI) const {
+Point Animation::totalFrameOffset(int32 frameI) const {
const auto &frame = _frames[frameI];
const auto bounds = frameBounds(frameI);
- return Vector2d(
+ return Point(
bounds.left - frame._center.x + frame._offset.x,
bounds.top - frame._center.y + frame._offset.y);
}
@@ -297,6 +297,8 @@ void Animation::prerenderFrame(int32 frameI) {
_renderedFrameI = frameI;
}
+
+
void Animation::draw2D(int32 frameI, Vector2d center, float scale, BlendMode blendMode, Color color) {
prerenderFrame(frameI);
auto bounds = frameBounds(frameI);
@@ -304,7 +306,7 @@ void Animation::draw2D(int32 frameI, Vector2d center, float scale, BlendMode ble
Vector2d texMax((float)bounds.width() / _renderedSurface.w, (float)bounds.height() / _renderedSurface.h);
Vector2d size(bounds.width(), bounds.height());
- center += totalFrameOffset(frameI) * scale;
+ center += as2D(totalFrameOffset(frameI)) * scale;
size *= scale;
auto &renderer = g_engine->renderer();
@@ -313,14 +315,6 @@ void Animation::draw2D(int32 frameI, Vector2d center, float scale, BlendMode ble
renderer.quad(center, size, color, Angle(), texMin, texMax);
}
-static Vector3d as3D(const Vector2d &v) {
- return Vector3d(v.getX(), v.getY(), 0.0f);
-}
-
-static Vector2d as2D(const Vector3d &v) {
- return Vector2d(v.x(), v.y());
-}
-
void Animation::draw3D(int32 frameI, Vector3d center, float scale, BlendMode blendMode, Color color) {
prerenderFrame(frameI);
auto bounds = frameBounds(frameI);
@@ -478,7 +472,7 @@ AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, V
, _animation(animation)
, _frameI(frameI)
, _center(as3D(center))
- , _scale(1.0f)
+ , _scale(kBaseScale)
, _color(kWhite)
, _blendMode(BlendMode::AdditiveAlpha)
, _lodBias(0.0f) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 7eb866456d0..196e093a08f 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -175,7 +175,9 @@ public:
inline const Common::Point &frameCenter(int32 frameI) const { return _frames[frameI]._center; }
inline uint32 totalDuration() const { return _totalDuration; }
inline uint8 &premultiplyAlpha() { return _premultiplyAlpha; }
+ Common::Point totalFrameOffset(int32 frameI) const;
int32 frameAtTime(uint32 time) const;
+ int32 imageIndex(int32 frameI, int32 spriteI) const;
Common::Point imageSize(int32 imageI) const;
void draw2D(
@@ -198,11 +200,9 @@ public:
BlendMode blendMode);
private:
- int32 imageIndex(int32 frameI, int32 spriteI) const;
Common::Rect spriteBounds(int32 frameI, int32 spriteI) const;
Common::Rect frameBounds(int32 frameI) const;
Common::Rect maxFrameBounds() const;
- Math::Vector2d totalFrameOffset(int32 frameI) const;
void prerenderFrame(int32 frameI);
int32_t _renderedFrameI = -1;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 90db2a1ce80..241d31eb42a 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -120,6 +120,8 @@ public:
ShapeObject(Room *room, Common::ReadStream &stream);
virtual ~ShapeObject() override = default;
+ inline int8 order() const { return _order; }
+
virtual void update() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Shape *shape() override;
@@ -424,6 +426,8 @@ public:
inline MainCharacterKind kind() const { return _kind; }
inline ObjectBase *currentlyUsing() const { return _currentlyUsingObject; }
+ inline FakeSemaphore &semaphore() { return _semaphore; }
+ bool isBusy() const;
virtual void update() override;
virtual void draw() override;
@@ -433,6 +437,7 @@ public:
Direction endDirection = Direction::Invalid,
ITriggerableObject *activateObject = nullptr,
const char *activateAction = nullptr) override;
+ void walkToMouse();
void clearInventory();
bool hasItem(const Common::String &name) const;
void pickup(const Common::String &name, bool putInHand);
@@ -450,7 +455,7 @@ private:
Common::Array<DialogMenuLine> _dialogMenuLines;
ObjectBase *_currentlyUsingObject = nullptr;
MainCharacterKind _kind;
- int32_t _relatedProcessCounter = 0;
+ FakeSemaphore _semaphore;
ITriggerableObject *_activateObject = nullptr;
const char *_activateAction = nullptr;
};
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
new file mode 100644
index 00000000000..1aef10f1914
--- /dev/null
+++ b/engines/alcachofa/player.cpp
@@ -0,0 +1,87 @@
+/* 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 "player.h"
+#include "alcachofa.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+Player::Player()
+ : _activeCharacter(&g_engine->world().mortadelo()) {
+ const auto &cursorPath = g_engine->world().getGlobalAnimationName(GlobalAnimationKind::Cursor);
+ _cursorAnimation.reset(new Animation(cursorPath));
+ _cursorAnimation->load();
+}
+
+void Player::preUpdate() {
+ _selectedObject = nullptr;
+ _cursorFrameI = 0;
+}
+
+void Player::postUpdate() {
+ if (g_engine->input().wasAnyMouseReleased())
+ _pressedObject = nullptr;
+}
+
+void Player::updateCursor() {
+ if (_isOptionsMenuOpen || !_isGameLoaded)
+ _cursorFrameI = 0;
+ else if (_selectedObject == nullptr)
+ _cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
+ else {
+ auto type = _selectedObject->cursorType();
+ switch (type) {
+ case CursorType::Point: _cursorFrameI = 0; break;
+ case CursorType::LeaveUp: _cursorFrameI = 8; break;
+ case CursorType::LeaveRight: _cursorFrameI = 10; break;
+ case CursorType::LeaveDown: _cursorFrameI = 12; break;
+ case CursorType::LeaveLeft: _cursorFrameI = 14; break;
+ case CursorType::WalkTo: _cursorFrameI = 6; break;
+ default: error("Invalid cursor type %u", (uint)type); break;
+ }
+
+ if (_cursorFrameI != 0) {
+ if (g_engine->input().isAnyMouseDown() && _pressedObject == _selectedObject)
+ _cursorFrameI++;
+ }
+ else if (g_engine->input().isMouseLeftDown())
+ _cursorFrameI = 2;
+ else if (g_engine->input().isMouseRightDown())
+ _cursorFrameI = 4;
+ }
+
+ Point cursorPos = g_engine->input().mousePos2D();
+ if (_heldItem == nullptr)
+ g_engine->drawQueue().add<AnimationDrawRequest>(_cursorAnimation.get(), _cursorFrameI, as2D(cursorPos), -10);
+ else {
+ auto itemGraphic = _heldItem->graphic();
+ assert(itemGraphic != nullptr);
+ auto &animation = itemGraphic->animation();
+ auto frameOffset = animation.totalFrameOffset(0);
+ auto imageSize = animation.imageSize(animation.imageIndex(0, 0));
+ cursorPos -= frameOffset + imageSize / 2;
+ g_engine->drawQueue().add<AnimationDrawRequest>(&animation, 0, as2D(cursorPos), -10);
+ }
+}
+
+}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 10b35706717..bcbb0a64ced 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -28,21 +28,40 @@ namespace Alcachofa {
class Player {
public:
+ Player();
+
inline Room *currentRoom() const { return _currentRoom; }
inline Room *¤tRoom() { return _currentRoom; }
inline MainCharacter *activeCharacter() const { return _activeCharacter; }
- inline ShapeObject *selectedObject() const { return _selectedObject; }
- inline Item *heldItem() const { return _heldItem; }
+ inline ShapeObject *&selectedObject() { return _selectedObject; }
+ inline ShapeObject *&pressedObject() { return _pressedObject; }
+ inline Item *&heldItem() { return _heldItem; }
+ inline FakeSemaphore &semaphore() { return _semaphore; }
+
+ inline bool &isOptionsMenuOpen() { return _isOptionsMenuOpen; }
+ inline bool &isGameLoaded() { return _isGameLoaded; }
inline MainCharacterKind activeCharacterKind() const {
return _activeCharacter == nullptr ? MainCharacterKind::None : _activeCharacter->kind();
}
+ void preUpdate();
+ void postUpdate();
+ void updateCursor();
+
+
private:
+ Common::ScopedPtr<Animation> _cursorAnimation;
+ FakeSemaphore _semaphore;
Room *_currentRoom = nullptr;
MainCharacter *_activeCharacter;
ShapeObject *_selectedObject = nullptr;
+ ShapeObject *_pressedObject = nullptr;
Item *_heldItem = nullptr;
+ int32 _cursorFrameI = 0;
+ bool
+ _isOptionsMenuOpen = false,
+ _isGameLoaded = true;
};
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 3cf924a76d2..0e9344d524f 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -130,7 +130,8 @@ void Room::update() {
if (g_engine->player().currentRoom() == this) {
updateRoomBounds();
- updateInput();
+ if (!updateInput())
+ return;
}
// TODO: Add condition for global room update
world().globalRoom().updateObjects();
@@ -154,25 +155,49 @@ void Room::updateScripts() {
g_engine->scheduler().run();
}
-void Room::updateInput() {
- static bool hasLastP3D = false;
- static Point lastP3D;
+bool Room::updateInput() {
+ auto &player = g_engine->player();
+ auto &input = g_engine->input();
+ if (player.heldItem() != nullptr && !player.activeCharacter()->isBusy() && input.wasMouseRightPressed()) {
+ player.heldItem() = nullptr;
+ return false;
+ }
+ bool canInteract = !player.activeCharacter()->isBusy();
+ // A complicated network condition can prevent interaction at this point
+ if (player.isOptionsMenuOpen() || !player.isGameLoaded())
+ canInteract = true;
+ if (canInteract)
+ updateInteraction();
- if (g_engine->input().wasMouseLeftPressed()) {
- Point p2d = g_engine->input().mousePos2D();
- Point p3d = g_engine->input().mousePos3D();
- auto m = &g_engine->world().filemon();
+ // TODO: Add main menu and opening inventory handling
+ return player.currentRoom() == this;
+}
- if (!hasLastP3D) {
- m->setPosition(p3d);
- }
- else {
- m->room() = this;
- m->walkTo(p3d);
+void Room::updateInteraction() {
+ auto &player = g_engine->player();
+ auto &input = g_engine->input();
+ // TODO: Add interaction with change character button / opening inventory
+
+ if (player.activeCharacter()->room() != this) {
+ player.activeCharacter()->room() = this;
+ }
+
+ player.selectedObject() = world().globalRoom().getSelectedObject(getSelectedObject());
+ if (player.selectedObject() == nullptr) {
+ if (input.wasMouseLeftPressed() && _activeFloorI >= 0 &&
+ player.activeCharacter()->room() == this &&
+ player.pressedObject() == nullptr) {
+ player.activeCharacter()->walkToMouse();
+ // TODO: Activate camera following character
}
- hasLastP3D = true;
}
+ else {
+ player.selectedObject()->markSelected();
+ if (input.wasAnyMousePressed())
+ player.pressedObject() = player.selectedObject();
+ }
+ player.updateCursor();
}
void Room::updateRoomBounds() {
@@ -229,6 +254,20 @@ void Room::toggleActiveFloor() {
_activeFloorI ^= 1;
}
+ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
+ for (auto object : _objects) {
+ auto shape = object->shape();
+ auto shapeObject = dynamic_cast<ShapeObject *>(object);
+ if (!object->isEnabled() || shape == nullptr || shapeObject == nullptr ||
+ object->room() != this || // e.g. a main character that is in another room
+ !shape->contains(g_engine->input().mousePos3D()))
+ continue;
+ if (best == nullptr || shapeObject->order() < best->order())
+ best = shapeObject;
+ }
+ return best;
+}
+
OptionsMenu::OptionsMenu(World *world, ReadStream &stream)
: Room(world, stream, true) {
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index d7b7f08ab9d..ee5ee8a4717 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -49,7 +49,7 @@ public:
inline uint8 characterAlphaPremultiplier() const { return _characterAlphaPremultiplier; }
void update();
- virtual void updateInput();
+ virtual bool updateInput();
virtual void loadResources();
virtual void freeResources();
virtual void serializeSave(Common::Serializer &serializer);
@@ -60,9 +60,11 @@ protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
void updateScripts();
void updateRoomBounds();
+ void updateInteraction();
void updateObjects();
void drawObjects();
void drawDebug();
+ ShapeObject *getSelectedObject(ShapeObject *best = nullptr) const;
World *_world;
Common::String _name;
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 66e3b137096..568917385b4 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -27,10 +27,6 @@ using namespace Math;
namespace Alcachofa {
-static Vector2d asVec(const Point &p) {
- return Vector2d((float)p.x, (float)p.y);
-}
-
static int sideOfLine(const Point &a, const Point &b, const Point &q) {
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
}
@@ -80,9 +76,9 @@ EdgeDistances Polygon::edgeDistances(uint startPointI, const Point &query) const
assert(startPointI < _points.size());
uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
Vector2d
- a = asVec(_points[startPointI]),
- b = asVec(_points[endPointI]),
- q = asVec(query);
+ a = as2D(_points[startPointI]),
+ b = as2D(_points[endPointI]),
+ q = as2D(query);
float edgeLength = a.getDistanceTo(b);
Vector2d edgeDir = (b - a) / edgeLength;
Vector2d edgeNormal(-edgeDir.getY(), edgeDir.getX());
Commit: 4fff63d97889722bb3aa5e69aa0174c76ad667b5
https://github.com/scummvm/scummvm/commit/4fff63d97889722bb3aa5e69aa0174c76ad667b5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Add door shortcuts
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 4e9be05190e..c9dd61d1281 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -61,6 +61,17 @@ void InteractableObject::drawDebug() {
renderer->debugShape(*shape());
}
+void InteractableObject::onClick() {
+ auto heldItem = g_engine->player().heldItem();
+ const char *action;
+ if (heldItem == nullptr)
+ action = g_engine->input().wasMouseLeftReleased() ? "MIRAR" : "PULSAR";
+ else
+ action = heldItem->name().c_str();
+ g_engine->player().activeCharacter()->walkTo(_interactionPoint, Direction::Invalid, this, action);
+ onHoverUpdate();
+}
+
void InteractableObject::trigger(const char *action) {
warning("stub: Trigger object %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
}
@@ -73,6 +84,19 @@ Door::Door(Room *room, ReadStream &stream)
_targetRoom.replace(' ', '_');
}
+void Door::onClick() {
+ if (g_system->getMillis() - _lastClickTime < 500 && g_engine->player().activeCharacter()->clearTargetIf(this))
+ trigger(nullptr);
+ else {
+ InteractableObject::onClick();
+ _lastClickTime = g_system->getMillis();
+ }
+}
+
+void Door::trigger(const char *_) {
+ warning("STUB: Triggering door to %s", _targetRoom.c_str());
+}
+
Character::Character(Room *room, ReadStream &stream)
: ShapeObject(room, stream)
, ITriggerableObject(stream)
@@ -661,6 +685,15 @@ void MainCharacter::walkToMouse() {
walkTo(targetPos);
}
+bool MainCharacter::clearTargetIf(const ITriggerableObject *target) {
+ if (_activateObject == target) {
+ _activateObject = nullptr;
+ return true;
+ }
+ return false;
+}
+
+
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
toggle(true);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 241d31eb42a..01fc546cd24 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -309,6 +309,7 @@ public:
virtual ~InteractableObject() override = default;
virtual void drawDebug() override;
+ virtual void onClick() override;
virtual void trigger(const char *action) override;
private:
@@ -320,9 +321,13 @@ public:
static constexpr const char *kClassName = "CPuerta";
Door(Room *room, Common::ReadStream &stream);
+ virtual void onClick() override;
+ virtual void trigger(const char *action) override;
+
private:
Common::String _targetRoom, _targetObject;
Direction _characterDirection;
+ uint32 _lastClickTime = 0;
};
class Character : public ShapeObject, public ITriggerableObject {
@@ -438,6 +443,7 @@ public:
ITriggerableObject *activateObject = nullptr,
const char *activateAction = nullptr) override;
void walkToMouse();
+ bool clearTargetIf(const ITriggerableObject *target);
void clearInventory();
bool hasItem(const Common::String &name) const;
void pickup(const Common::String &name, bool putInHand);
Commit: a13f5d8999e979004be5506bac8566847e552204
https://github.com/scummvm/scummvm/commit/a13f5d8999e979004be5506bac8566847e552204
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Add camera following characters
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 5218047a42b..25de2090802 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -68,7 +68,7 @@ Common::Error AlcachofaEngine::run() {
world().globalRoom().loadResources();
- auto room = world().getRoomByName("MAPA_TERROR");
+ auto room = world().getRoomByName("SALOON");
assert(room != nullptr);
player().currentRoom() = room;
room->loadResources();
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index fabc394209b..47a0ccda58c 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -20,6 +20,7 @@
*/
#include "camera.h"
+#include "script.h"
#include "alcachofa.h"
#include "common/system.h"
@@ -38,6 +39,14 @@ void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
_roomMax = _roomMin + Vector2d(
bgSize.x * bgScale * kInvBaseScale,
bgSize.y * bgScale * kInvBaseScale);
+ _roomScale = bgScale;
+}
+
+void Camera::setFollow(WalkingCharacter *target) {
+ _followTarget = target;
+ _lastUpdateTime = g_system->getMillis();
+ if (target == nullptr)
+ _isChanging = false;
}
static Matrix4 scaleMatrix(float scale) {
@@ -77,7 +86,7 @@ void minmax(Vector3d &min, Vector3d &max, Vector3d val)
Vector3d Camera::setAppliedCenter(Vector3d center) {
setupMatricesAround(center);
- if (true) { // g_engine->script().getVariable("EncuadrarCamara")
+ if (g_engine->script().variable("EncuadrarCamara") || true) {
const float screenW = g_system->getWidth(), screenH = g_system->getHeight();
Vector3d min, max;
min = max = transform2Dto3D(Vector3d(0, 0, _roomScale));
@@ -122,11 +131,67 @@ Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
void Camera::update() {
// original would be some smoothing of delta times, let's not.
uint32 now = g_system->getMillis();
- float deltaTime = now - _lastUpdateTime;
+ float deltaTime = (now - _lastUpdateTime) / 1000.0f;
deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
_lastUpdateTime = now;
+ updateFollowing(deltaTime);
setAppliedCenter(_usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
}
+void Camera::updateFollowing(float deltaTime) {
+ if (_followTarget == nullptr)
+ return;
+ const float resolutionFactor = g_system->getWidth() * 0.00125f;
+ const float acceleration = 460 * resolutionFactor;
+ const float baseDeadZoneSize = 25 * resolutionFactor;
+ const float minSpeed = 20 * resolutionFactor;
+ const float maxSpeed = this->_maxSpeedFactor * resolutionFactor;
+ const float depthScale = _followTarget->graphic()->depthScale();
+ const auto characterPolygon = _followTarget->shape()->at(0);
+ const float halfHeight = ABS(characterPolygon._points[0].y - characterPolygon._points[2].y) / 2.0f;
+
+ Vector3d targetCenter = setAppliedCenter({
+ _shake.getX() + _followTarget->position().x,
+ _shake.getY() + _followTarget->position().y - depthScale * 85,
+ _usedCenter.z()});
+ targetCenter.y() -= halfHeight;
+ float distanceToTarget = as2D(_usedCenter - targetCenter).getMagnitude();
+ float moveDistance = _followTarget->stepSizeFactor() * _speed * deltaTime;
+
+ float deadZoneSize = baseDeadZoneSize / _scale;
+ if (_followTarget->isWalking() && depthScale > 0.8f)
+ deadZoneSize = (baseDeadZoneSize + (depthScale - 0.8f) * 200) / _scale;
+ bool isFarAway = false;
+ if (ABS(targetCenter.x() - _usedCenter.x()) > deadZoneSize ||
+ ABS(targetCenter.y() - _usedCenter.y()) > deadZoneSize) {
+ isFarAway = true;
+ _isBraking = false;
+ _isChanging = true;
+ }
+
+ if (_isBraking) {
+ _speed -= acceleration * 0.9f * deltaTime;
+ _speed = MAX(_speed, minSpeed);
+ }
+ if (_isChanging && !_isBraking) {
+ _speed += acceleration * deltaTime;
+ _speed = MIN(_speed, maxSpeed);
+ if (!isFarAway)
+ _isBraking = true;
+ }
+ if (_isChanging) {
+ if (distanceToTarget <= moveDistance) {
+ _usedCenter = targetCenter;
+ _isChanging = false;
+ _isBraking = false;
+ }
+ else {
+ Vector3d deltaCenter = targetCenter - _usedCenter;
+ deltaCenter.z() = 0.0f;
+ _usedCenter += deltaCenter * moveDistance / distanceToTarget;
+ }
+ }
+}
+
}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 4e06640361a..bd58ee6df75 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -30,7 +30,7 @@
namespace Alcachofa {
-class Personaje;
+class WalkingCharacter;
class Process;
struct Task;
@@ -46,18 +46,21 @@ public:
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
void setRoomBounds(Common::Point bgSize, int16 bgScale);
+ void setFollow(WalkingCharacter *target);
private:
- static constexpr const float kAccelerationThreshold = 2.89062f;
- static constexpr const float kAcceleration = 3.94922f;
-
Math::Vector3d setAppliedCenter(Math::Vector3d center);
void setupMatricesAround(Math::Vector3d center);
+ void updateFollowing(float deltaTime);
uint32 _lastUpdateTime = 0;
+ bool _isChanging = false,
+ _isBraking = false;
float
_scale = 1.0f,
- _roomScale = 1.0f;
+ _roomScale = 1.0f,
+ _maxSpeedFactor = 230.0f,
+ _speed = 0.0f;
Math::Angle _rotation;
Math::Vector2d
_roomMin = Math::Vector2d(-10000, -10000),
@@ -69,6 +72,7 @@ private:
Math::Matrix4
_mat3Dto2D,
_mat2Dto3D;
+ WalkingCharacter *_followTarget = nullptr;
};
}
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index c9dd61d1281..decb6b6961a 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -284,7 +284,7 @@ void WalkingCharacter::update() {
static Direction getDirection(const Point &from, const Point &to) {
Point delta = from - to;
if (from.x == to.x)
- return from.y < to.y ? Direction::Up : Direction::Down;
+ return from.y < to.y ? Direction::Down : Direction::Up;
else if (from.x < to.x) {
int slope = 1000 * delta.y / -delta.x;
return slope > 1000 ? Direction::Up
@@ -490,7 +490,7 @@ struct ArriveTask : public Task {
, _character(character) {}
virtual TaskReturn run() override {
- return _character._isWalking
+ return _character.isWalking()
? TaskReturn::yield()
: TaskReturn::finish(1);
}
@@ -564,7 +564,7 @@ void MainCharacter::walkTo(
WalkingCharacter::walkTo(target, endDirection, activateObject, activateAction);
if (this == g_engine->player().activeCharacter()) {
- // TODO: Add camera following character
+ g_engine->camera().setFollow(this);
}
}
@@ -674,10 +674,16 @@ void MainCharacter::drop(const Common::String &name) {
void MainCharacter::walkToMouse() {
Point targetPos = g_engine->input().mousePos3D();
if (room()->activeFloor() != nullptr) {
+ /* this would be original, but it can cause the character teleporting to the target
_pathPoints.clear();
room()->activeFloor()->findPath(_sourcePos, targetPos, _pathPoints);
if (!_pathPoints.empty())
- targetPos = _pathPoints[0];
+ targetPos = _pathPoints[0]; */
+
+ Stack<Point> tmpPath;
+ room()->activeFloor()->findPath(_sourcePos, targetPos, tmpPath);
+ if (!tmpPath.empty())
+ targetPos = tmpPath[0];
}
const uint minDistance = (uint)(50 * _graphicNormal.depthScale());
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 01fc546cd24..697f9ee2d61 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -366,6 +366,10 @@ public:
WalkingCharacter(Room *room, Common::ReadStream &stream);
virtual ~WalkingCharacter() override = default;
+ inline bool isWalking() const { return _isWalking; }
+ inline const Common::Point &position() const { return _currentPos; }
+ inline float stepSizeFactor() const { return _stepSizeFactor; }
+
virtual void update() override;
virtual void draw() override;
virtual void drawDebug() override;
@@ -383,7 +387,6 @@ public:
Task *waitForArrival(Process &process);
protected:
- friend struct ArriveTask;
virtual void onArrived();
void updateWalking();
void updateWalkingAnimation();
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0e9344d524f..abb704e3072 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -189,7 +189,7 @@ void Room::updateInteraction() {
player.activeCharacter()->room() == this &&
player.pressedObject() == nullptr) {
player.activeCharacter()->walkToMouse();
- // TODO: Activate camera following character
+ g_engine->camera().setFollow(player.activeCharacter());
}
}
else {
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index d27be2671e2..f12cd0d4881 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -442,7 +442,8 @@ private:
error("Script tried to make character go to invalid object %s", getStringArg(1));
character->walkTo(target->position());
- // TODO: if (flags & 2) g_engine->camera().setFollow(nullptr);
+ if (getNumberArg(2) & 2)
+ g_engine->camera().setFollow(nullptr);
return (getNumberArg(2) & 1)
? TaskReturn::finish(1)
Commit: 7df9ae09933819f678d181ba384e8155373ce2a2
https://github.com/scummvm/scummvm/commit/7df9ae09933819f678d181ba384e8155373ce2a2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Add technical room changes
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 25de2090802..a43ebf7d16f 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -66,18 +66,7 @@ Common::Error AlcachofaEngine::run() {
_script.reset(new Script());
_player.reset(new Player());
- world().globalRoom().loadResources();
-
- auto room = world().getRoomByName("SALOON");
- assert(room != nullptr);
- player().currentRoom() = room;
- room->loadResources();
-
- // If a savegame was selected from the launcher, load it
- int saveSlot = ConfMan.getInt("save_slot");
- if (saveSlot != -1)
- (void)loadGameState(saveSlot);
-
+ _player->changeRoom("MINA", true);
Common::Event e;
Graphics::FrameLimiter limiter(g_system, 120);
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 47a0ccda58c..89f6a3de561 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -31,6 +31,12 @@ using namespace Math;
namespace Alcachofa {
+void Camera::resetRotationAndScale() {
+ _scale = 1;
+ _rotation = 0;
+ _usedCenter.z() = 0;
+}
+
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 bd58ee6df75..091bf4a7312 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -45,6 +45,7 @@ public:
void update();
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
+ void resetRotationAndScale();
void setRoomBounds(Common::Point bgSize, int16 bgScale);
void setFollow(WalkingCharacter *target);
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index decb6b6961a..f129faedf6c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -674,11 +674,8 @@ void MainCharacter::drop(const Common::String &name) {
void MainCharacter::walkToMouse() {
Point targetPos = g_engine->input().mousePos3D();
if (room()->activeFloor() != nullptr) {
- /* this would be original, but it can cause the character teleporting to the target
- _pathPoints.clear();
- room()->activeFloor()->findPath(_sourcePos, targetPos, _pathPoints);
- if (!_pathPoints.empty())
- targetPos = _pathPoints[0]; */
+ // original would be overwriting the current path but this
+ // can cause the character teleporting to the new target
Stack<Point> tmpPath;
room()->activeFloor()->findPath(_sourcePos, targetPos, tmpPath);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 8c64748aa16..af8574fa1e4 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -81,8 +81,10 @@ void AnimationBase::load() {
if (!file.open(fullPath.c_str())) {
// original fallback
fullPath = "Mascaras/" + _fileName;
- if (!file.open(fullPath.c_str()))
- error("Could not open animation %s", _fileName.c_str());
+ if (!file.open(fullPath.c_str())) {
+ loadMissingAnimation();
+ return;
+ }
}
uint spriteCount = file.readUint32LE();
@@ -174,6 +176,22 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
return new ManagedSurface(target);
}
+void AnimationBase::loadMissingAnimation() {
+ // only allow missing animations we know are faulty in the original game
+ if (!_fileName.equalsIgnoreCase("ANIMACION.AN0"))
+ error("Could not open animation %s", _fileName.c_str());
+
+ // otherwise setup a functioning but empty animation
+ _isLoaded = true;
+ _totalDuration = 1;
+ _spriteIndexMapping[0] = 0;
+ _spriteOffsets.push_back(1);
+ _spriteBases.push_back(0);
+ _images.push_back(nullptr);
+ _imageOffsets.push_back(Point());
+ _frames.push_back({ Point(), Point(), 1 });
+}
+
Animation::Animation(String fileName, AnimationFolder folder)
: AnimationBase(fileName, folder) {
}
@@ -200,7 +218,8 @@ int32 Animation::imageIndex(int32 frameI, int32 spriteId) const {
Rect Animation::spriteBounds(int32 frameI, int32 spriteId) const {
int32 imageI = imageIndex(frameI, spriteId);
auto image = imageI < 0 ? nullptr : _images[imageI];
- return image == nullptr ? Rect()
+ return image == nullptr
+ ? Rect(imageI < 0 ? Point() : _imageOffsets[imageI], 2, 1)
: Rect(_imageOffsets[imageI], image->w, image->h);
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 196e093a08f..c12adb6077d 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -139,6 +139,7 @@ protected:
~AnimationBase();
void load();
+ void loadMissingAnimation();
void freeImages();
Graphics::ManagedSurface *readImage(Common::SeekableReadStream &stream) const;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 1aef10f1914..7ee6d4619b2 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -84,4 +84,37 @@ void Player::updateCursor() {
}
}
+void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera) {
+ // original would be to always free all resources from globalRoom, inventory, GlobalUI
+
+ Room &inventory = g_engine->world().inventory();
+ bool keepResources;
+ if (_currentRoom == &inventory)
+ keepResources = _roomBeforeInventory != nullptr && _roomBeforeInventory->name().equalsIgnoreCase(targetRoomName);
+ else {
+ keepResources = targetRoomName.equalsIgnoreCase("inventario") ||
+ (_currentRoom != nullptr && _currentRoom->name().equalsIgnoreCase(targetRoomName));
+ }
+
+ if (!keepResources && _currentRoom != nullptr) {
+ g_engine->scheduler().killProcessByName("ACTUALIZAR_" + _currentRoom->name());
+ _currentRoom->freeResources();
+ }
+ _currentRoom = g_engine->world().getRoomByName(targetRoomName);
+ if (_currentRoom == nullptr)
+ error("Invalid room name: %s", targetRoomName.c_str());
+
+ if (!_didLoadGlobalRooms) {
+ _didLoadGlobalRooms = true;
+ inventory.loadResources();
+ g_engine->world().globalRoom().loadResources();
+ }
+ if (!keepResources)
+ _currentRoom->loadResources();
+
+ if (resetCamera)
+ g_engine->camera().resetRotationAndScale();
+ _pressedObject = _selectedObject = nullptr;
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index bcbb0a64ced..e58fed5f053 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -48,12 +48,14 @@ public:
void preUpdate();
void postUpdate();
void updateCursor();
+ void changeRoom(const Common::String &targetRoomName, bool resetCamera);
private:
Common::ScopedPtr<Animation> _cursorAnimation;
FakeSemaphore _semaphore;
- Room *_currentRoom = nullptr;
+ Room *_currentRoom = nullptr,
+ *_roomBeforeInventory = nullptr;
MainCharacter *_activeCharacter;
ShapeObject *_selectedObject = nullptr;
ShapeObject *_pressedObject = nullptr;
@@ -61,7 +63,8 @@ private:
int32 _cursorFrameI = 0;
bool
_isOptionsMenuOpen = false,
- _isGameLoaded = true;
+ _isGameLoaded = true,
+ _didLoadGlobalRooms = false;
};
}
Commit: df52d04b70f215d2becfc7015d0017b1d7e778c7
https://github.com/scummvm/scummvm/commit/df52d04b70f215d2becfc7015d0017b1d7e778c7
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Add various debug commands
Changed paths:
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 2a50d20e85e..4c72539a084 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -19,7 +19,11 @@
*
*/
-#include "alcachofa/console.h"
+#include "console.h"
+#include "script.h"
+#include "alcachofa.h"
+
+using namespace Common;
namespace Alcachofa {
@@ -28,9 +32,109 @@ Console::Console() : GUI::Debugger() {
registerVar("showCharacters", &_showCharacters);
registerVar("showFloor", &_showFloor);
registerVar("showFloorColor", &_showFloorColor);
+
+ registerCmd("var", WRAP_METHOD(Console, cmdVar));
+ registerCmd("processes", WRAP_METHOD(Console, cmdProcesses));
+ registerCmd("room", WRAP_METHOD(Console, cmdRoom));
+ registerCmd("rooms", WRAP_METHOD(Console, cmdRooms));
+ registerCmd("changeRoom", WRAP_METHOD(Console, cmdChangeRoom));
+ registerCmd("disableDebugDraw", WRAP_METHOD(Console, cmdDisableDebugDraw));
}
Console::~Console() {
}
+bool Console::cmdVar(int argc, const char **args) {
+ auto &script = g_engine->script();
+ if (argc < 2 || argc > 3)
+ debugPrintf("usage: %s <name> [<value>]\n", args[0]);
+ else if (argc == 3) {
+ char *end = nullptr;
+ int32 value = (int32)strtol(args[2], &end, 10);
+ if (end == nullptr || *end != '\0')
+ debugPrintf("Invalid variable value: %s", args[2]);
+ else if (!script.hasVariable(args[1]))
+ debugPrintf("Invalid variable name: %s", args[1]);
+ else
+ script.variable(args[1]) = value;
+ }
+ else if (argc == 2) {
+ bool hadSomeMatch = false;
+ for (auto it = script.beginVariables(); it != script.endVariables(); it++) {
+ if (matchString(it->_key.c_str(), args[1], true)) {
+ hadSomeMatch = true;
+ debugPrintf(" %32s = %d\n", it->_key.c_str(), script.variable(it->_key.c_str()));
+ }
+ }
+ if (!hadSomeMatch)
+ debugPrintf("Could not find any variable with pattern: %s\n", args[1]);
+ }
+ return true;
+}
+
+bool Console::cmdProcesses(int argc, const char **args) {
+ g_engine->scheduler().debugPrint();
+ return true;
+}
+
+bool Console::cmdRoom(int argc, const char **args) {
+ if (argc > 2) {
+ debugPrintf("usage: %s [<name>]\n", args[0]);
+ return true;
+ }
+ Room *room = nullptr;
+ if (argc == 1) {
+ room = g_engine->player().currentRoom();
+ if (room == nullptr) {
+ debugPrintf("Player is currently in no room, cannot print details\n");
+ return true;
+ }
+ }
+ else {
+ room = g_engine->world().getRoomByName(args[1]);
+ if (room == nullptr) {
+ debugPrintf("Could not find room with exact name: %s\n", args[1]);
+ return cmdRooms(argc, args);
+ }
+ }
+ room->debugPrint(true);
+ return true;
+}
+
+bool Console::cmdRooms(int argc, const char **args) {
+ if (argc != 2) {
+ debugPrintf("usage: %s <pattern>\n", args[0]);
+ return true;
+ }
+ bool hadSomeMatch = false;
+ for (auto it = g_engine->world().beginRooms(); it != g_engine->world().endRooms(); it++) {
+ if ((*it)->name().matchString(args[1], true)) {
+ hadSomeMatch = true;
+ (*it)->debugPrint(false);
+ }
+ }
+ if (!hadSomeMatch)
+ debugPrintf("Could not find any room with pattern: %s\n", args[1]);
+ return true;
+}
+
+bool Console::cmdChangeRoom(int argc, const char **args) {
+ if (argc > 2)
+ debugPrintf("usage: %s <name>\n", args[0]);
+ else if (argc == 1) {
+ Room *current = g_engine->player().currentRoom();
+ debugPrintf("Current room: %s\n", current == nullptr ? "<null>" : current->name().c_str());
+ }
+ else if (g_engine->world().getRoomByName(args[1]) == nullptr)
+ debugPrintf("Invalid room name: %s\n", args[1]);
+ else
+ g_engine->player().changeRoom(args[1], true);
+ return true;
+}
+
+bool Console::cmdDisableDebugDraw(int argc, const char **args) {
+ _showInteractables = _showCharacters = _showFloor = _showFloorColor = false;
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index cc250e38524..771873d0617 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -46,6 +46,13 @@ public:
}
private:
+ bool cmdVar(int argc, const char **args);
+ bool cmdProcesses(int argc, const char **args);
+ bool cmdRoom(int argc, const char **args);
+ bool cmdRooms(int argc, const char **args);
+ bool cmdChangeRoom(int argc, const char **args);
+ bool cmdDisableDebugDraw(int argc, const char **args);
+
bool _showInteractables = true;
bool _showCharacters = true;
bool _showFloor = true;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index f129faedf6c..658bd94235c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -29,6 +29,8 @@ using namespace Math;
namespace Alcachofa {
+const char *Item::typeName() const { return "Item"; }
+
Item::Item(Room *room, ReadStream &stream)
: GraphicObject(room, stream) {
stream.readByte(); // unused and ignored byte
@@ -46,6 +48,8 @@ ITriggerableObject::ITriggerableObject(ReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
, _interactionDirection((Direction)stream.readSint32LE()) {}
+const char *InteractableObject::typeName() const { return "InteractableObject"; }
+
InteractableObject::InteractableObject(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, ITriggerableObject(stream)
@@ -76,6 +80,8 @@ void InteractableObject::trigger(const char *action) {
warning("stub: Trigger object %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
}
+const char *Door::typeName() const { return "Door"; }
+
Door::Door(Room *room, ReadStream &stream)
: InteractableObject(room, stream)
, _targetRoom(readVarString(stream))
@@ -97,6 +103,8 @@ void Door::trigger(const char *_) {
warning("STUB: Triggering door to %s", _targetRoom.c_str());
}
+const char *Character::typeName() const { return "Character"; }
+
Character::Character(Room *room, ReadStream &stream)
: ShapeObject(room, stream)
, ITriggerableObject(stream)
@@ -215,6 +223,8 @@ void Character::trigger(const char *action) {
warning("stub: Trigger character %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
}
+const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
+
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
: Character(room, stream) {
for (int32 i = 0; i < kDirectionCount; i++) {
@@ -506,6 +516,8 @@ Task *WalkingCharacter::waitForArrival(Process &process) {
return new ArriveTask(process, *this);
}
+const char *MainCharacter::typeName() const { return "MainCharacter"; }
+
MainCharacter::MainCharacter(Room *room, ReadStream &stream)
: WalkingCharacter(room, stream) {
stream.readByte(); // unused byte
@@ -696,6 +708,7 @@ bool MainCharacter::clearTargetIf(const ITriggerableObject *target) {
return false;
}
+const char *Background::typeName() const { return "Background"; }
Background::Background(Room *room, const String &animationFileName, int16 scale)
: GraphicObject(room, "BACKGROUND") {
@@ -705,6 +718,8 @@ Background::Background(Room *room, const String &animationFileName, int16 scale)
_graphic.order() = 59;
}
+const char *FloorColor::typeName() const { return "FloorColor"; }
+
FloorColor::FloorColor(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream) {}
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 0756be39745..3cbd3e0d210 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -31,6 +31,8 @@ using namespace Common;
namespace Alcachofa {
+const char *ObjectBase::typeName() const { return "ObjectBase"; }
+
ObjectBase::ObjectBase(Room *room, const char *name)
: _room(room)
, _name(name)
@@ -76,11 +78,15 @@ Shape *ObjectBase::shape() {
return nullptr;
}
+const char *PointObject::typeName() const { return "PointObject"; }
+
PointObject::PointObject(Room *room, ReadStream &stream)
: ObjectBase(room, stream) {
_pos = Shape(stream).firstPoint();
}
+const char *GraphicObject::typeName() const { return "GraphicObject"; }
+
GraphicObject::GraphicObject(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _graphic(stream)
@@ -156,6 +162,8 @@ Task *GraphicObject::animate(Process &process) {
return new AnimateTask(process, this);
}
+const char *SpecialEffectObject::typeName() const { return "SpecialEffectObject"; }
+
SpecialEffectObject::SpecialEffectObject(Room *room, ReadStream &stream)
: GraphicObject(room, stream) {
_topLeft = Shape(stream).firstPoint();
@@ -182,6 +190,8 @@ void SpecialEffectObject::draw() {
g_engine->drawQueue().add<SpecialEffectDrawRequest>(_graphic, topLeft, bottomRight, texOffset, blendMode);
}
+const char *ShapeObject::typeName() const { return "ShapeObject"; }
+
ShapeObject::ShapeObject(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream)
@@ -248,6 +258,8 @@ void ShapeObject::updateSelection() {
}
}
+const char *PhysicalObject::typeName() const { return "PhysicalObject"; }
+
PhysicalObject::PhysicalObject(Room *room, ReadStream &stream)
: ShapeObject(room, stream) {
_order = stream.readSByte();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 697f9ee2d61..5d36e5b3690 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -54,6 +54,7 @@ public:
virtual void serializeSave(Common::Serializer &serializer);
virtual Graphic *graphic();
virtual Shape *shape();
+ virtual const char *typeName() const;
private:
Common::String _name;
@@ -68,6 +69,7 @@ public:
inline Common::Point &position() { return _pos; }
inline Common::Point position() const { return _pos; }
+ virtual const char *typeName() const;
private:
Common::Point _pos;
@@ -91,6 +93,7 @@ public:
virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
+ virtual const char *typeName() const;
Task *animate(Process &process);
@@ -108,6 +111,7 @@ public:
SpecialEffectObject(Room *room, Common::ReadStream &stream);
virtual void draw() override;
+ virtual const char *typeName() const;
private:
static constexpr const float kShiftSpeed = 1 / 256.0f;
@@ -130,6 +134,7 @@ public:
virtual void onHoverEnd();
virtual void onHoverUpdate();
virtual void onClick();
+ virtual const char *typeName() const;
void markSelected();
protected:
@@ -147,6 +152,7 @@ private:
class PhysicalObject : public ShapeObject {
public:
PhysicalObject(Room *room, Common::ReadStream &stream);
+ virtual const char *typeName() const;
};
class MenuButton : public PhysicalObject {
@@ -156,6 +162,7 @@ public:
virtual ~MenuButton() override = default;
inline int32 actionId() const { return _actionId; }
+ virtual const char *typeName() const;
private:
int32 _actionId;
@@ -170,18 +177,24 @@ class InternetMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuInternet";
InternetMenuButton(Room *room, Common::ReadStream &stream);
+
+ virtual const char *typeName() const;
};
class OptionsMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuOpciones";
OptionsMenuButton(Room *room, Common::ReadStream &stream);
+
+ virtual const char *typeName() const;
};
class MainMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuPrincipal";
MainMenuButton(Room *room, Common::ReadStream &stream);
+
+ virtual const char *typeName() const;
};
class PushButton final : public PhysicalObject {
@@ -189,6 +202,8 @@ public:
static constexpr const char *kClassName = "CPushButton";
PushButton(Room *room, Common::ReadStream &stream);
+ virtual const char *typeName() const;
+
private:
// TODO: Reverse engineer PushButton
bool _alwaysVisible;
@@ -201,6 +216,8 @@ public:
static constexpr const char *kClassName = "CEditBox";
EditBox(Room *room, Common::ReadStream &stream);
+ virtual const char *typeName() const;
+
private:
// TODO: Reverse engineer EditBox
int32 i1;
@@ -217,6 +234,8 @@ public:
CheckBox(Room *room, Common::ReadStream &stream);
virtual ~CheckBox() override = default;
+ virtual const char *typeName() const;
+
private:
// TODO: Reverse engineer CheckBox
bool b1;
@@ -232,6 +251,8 @@ class CheckBoxAutoAdjustNoise final : public CheckBox {
public:
static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
CheckBoxAutoAdjustNoise(Room *room, Common::ReadStream &stream);
+
+ virtual const char *typeName() const;
};
class SlideButton final : public ObjectBase {
@@ -240,6 +261,8 @@ public:
SlideButton(Room *room, Common::ReadStream &stream);
virtual ~SlideButton() override = default;
+ virtual const char *typeName() const;
+
private:
// TODO: Reverse engineer SlideButton
int32 i1;
@@ -255,6 +278,8 @@ public:
static constexpr const char *kClassName = "CVentanaIRC";
IRCWindow(Room *room, Common::ReadStream &stream);
+ virtual const char *typeName() const;
+
private:
Common::Point _p1, _p2;
};
@@ -265,6 +290,8 @@ public:
MessageBox(Room *room, Common::ReadStream &stream);
virtual ~MessageBox() override = default;
+ virtual const char *typeName() const;
+
private:
// TODO: Reverse engineer MessageBox
Graphic
@@ -279,6 +306,8 @@ class VoiceMeter final : public GraphicObject {
public:
static constexpr const char *kClassName = "CVuMeter";
VoiceMeter(Room *room, Common::ReadStream &stream);
+
+ virtual const char *typeName() const;
};
class Item : public GraphicObject {
@@ -286,6 +315,8 @@ public:
static constexpr const char *kClassName = "CObjetoInventario";
Item(Room *room, Common::ReadStream &stream);
Item(const Item &other);
+
+ virtual const char *typeName() const;
};
class ITriggerableObject {
@@ -311,6 +342,7 @@ public:
virtual void drawDebug() override;
virtual void onClick() override;
virtual void trigger(const char *action) override;
+ virtual const char *typeName() const;
private:
Common::String _relatedObject;
@@ -323,6 +355,7 @@ public:
virtual void onClick() override;
virtual void trigger(const char *action) override;
+ virtual const char *typeName() const;
private:
Common::String _targetRoom, _targetObject;
@@ -344,6 +377,7 @@ public:
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
virtual void trigger(const char *action) override;
+ virtual const char *typeName() const;
protected:
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
@@ -383,6 +417,7 @@ public:
const char *activateAction = nullptr);
void stopWalkingAndTurn(Direction direction);
void setPosition(const Common::Point &target);
+ virtual const char *typeName() const;
Task *waitForArrival(Process &process);
@@ -451,6 +486,7 @@ public:
bool hasItem(const Common::String &name) const;
void pickup(const Common::String &name, bool putInHand);
void drop(const Common::String &name);
+ virtual const char *typeName() const;
protected:
virtual void onArrived() override;
@@ -472,6 +508,7 @@ private:
class Background final : public GraphicObject {
public:
Background(Room *room, const Common::String &animationFileName, int16 scale);
+ virtual const char *typeName() const;
};
class FloorColor final : public ObjectBase {
@@ -482,6 +519,7 @@ public:
virtual void drawDebug() override;
virtual Shape *shape() override;
+ virtual const char *typeName() const;
private:
FloorColorShape _shape;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index abb704e3072..eafa536a93b 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -309,6 +309,20 @@ void Inventory::updateItemsByActiveCharacter() {
item->toggle(character->hasItem(item->name()));
}
+void Room::debugPrint(bool withObjects) const {
+ auto &console = g_engine->console();
+ console.debugPrintf(" %s\n", _name.c_str());
+ if (!withObjects)
+ return;
+
+ for (auto *object : _objects) {
+ console.debugPrintf("\t%20s %-32s %s\n",
+ object->typeName(),
+ object->name().c_str(),
+ object->isEnabled() ? "" : "disabled");
+ }
+}
+
static constexpr const char *kMapFiles[] = {
"MAPAS/MAPA5.EMC",
"MAPAS/MAPA4.EMC",
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index ee5ee8a4717..313e602066c 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -55,6 +55,7 @@ public:
virtual void serializeSave(Common::Serializer &serializer);
ObjectBase *getObjectByName(const Common::String &name) const;
void toggleActiveFloor();
+ void debugPrint(bool withObjects) const;
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
@@ -132,6 +133,9 @@ public:
// reference-returning queries will error if the object does not exist
+ using RoomIterator = Common::Array<const Room *>::const_iterator;
+ inline RoomIterator beginRooms() const { return _rooms.begin(); }
+ inline RoomIterator endRooms() const { return _rooms.end(); }
inline Room &globalRoom() const { return *_globalRoom; }
inline Inventory &inventory() const { return *_inventory; }
inline MainCharacter &filemon() const { return *_filemon; }
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 187d8d932c7..ad39277adc4 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -126,8 +126,8 @@ void Process::debugPrint() {
}
static void killProcessesForIn(MainCharacterKind characterKind, Array<Process *> &processes, uint firstIndex) {
- assert(firstIndex < processes.size());
- uint count = processes.size() - 1 - firstIndex;
+ assert(firstIndex <= processes.size());
+ uint count = processes.size() - firstIndex;
for (uint i = 0; i < count; i++) {
Process **process = &processes[processes.size() - 1 - i];
if ((*process)->character() == characterKind || characterKind == MainCharacterKind::None) {
@@ -209,4 +209,48 @@ bool Scheduler::hasProcessWithName(const String &name) {
return getProcessByName(processesToRunNext(), name) != nullptr;
}
+void Scheduler::debugPrint() {
+ auto &console = g_engine->console();
+ bool didPrintSomething = false;
+
+ if (!processesToRun().empty()) {
+ console.debugPrintf("Currently running processes:\n");
+ for (uint32 i = 0; i < processesToRun().size(); i++) {
+ if (_currentProcessI == UINT_MAX || i > _currentProcessI)
+ console.debugPrintf(" ");
+ else if (i < _currentProcessI)
+ console.debugPrintf("# ");
+ else
+ console.debugPrintf("> ");
+ processesToRun()[i]->debugPrint();
+ }
+ didPrintSomething = true;
+ }
+
+ if (!processesToRunNext().empty()) {
+ if (didPrintSomething)
+ console.debugPrintf("\n");
+ console.debugPrintf("Scheduled processes:\n");
+ for (auto *process : processesToRunNext()) {
+ console.debugPrintf(" ");
+ process->debugPrint();
+ }
+ didPrintSomething = true;
+ }
+
+ if (!_backupProcesses.empty()) {
+ if (didPrintSomething)
+ console.debugPrintf("\n");
+ console.debugPrintf("Backed up processes:\n");
+ for (auto *process : _backupProcesses) {
+ console.debugPrintf(" ");
+ process->debugPrint();
+ }
+ didPrintSomething = true;
+ }
+
+ if (!didPrintSomething)
+ console.debugPrintf("No processes running or backed up\n");
+}
+
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index a045eb4ce4c..a5317482bb9 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -152,6 +152,7 @@ public:
void killAllProcessesFor(MainCharacterKind characterKind);
void killProcessByName(const Common::String &name);
bool hasProcessWithName(const Common::String &name);
+ void debugPrint();
template<typename TTask, typename... TaskArgs>
Process *createProcess(MainCharacterKind character, TaskArgs&&... args) {
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 827726f2018..6c4d328d6db 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -157,6 +157,11 @@ public:
const Common::String &action,
bool allowMissing = false);
+ using VariableNameIterator = Common::HashMap<Common::String, uint32>::const_iterator;
+ inline VariableNameIterator beginVariables() const { return _variableNames.begin(); }
+ inline VariableNameIterator endVariables() const { return _variableNames.end(); }
+ inline bool hasVariable(const char *name) const { return _variableNames.contains(name); }
+
private:
friend struct ScriptTask;
friend struct ScriptTimerTask;
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index c845ba2492e..1b78a192b04 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -27,6 +27,8 @@ using namespace Common;
namespace Alcachofa {
+const char *MenuButton::typeName() const { return "MenuButton"; }
+
MenuButton::MenuButton(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, _actionId(stream.readSint32LE())
@@ -36,18 +38,26 @@ MenuButton::MenuButton(Room *room, ReadStream &stream)
, _graphicDisabled(stream) {
}
+const char *InternetMenuButton::typeName() const { return "InternetMenuButton"; }
+
InternetMenuButton::InternetMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {
}
+const char *OptionsMenuButton::typeName() const { return "OptionsMenuButton"; }
+
OptionsMenuButton::OptionsMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {
}
+const char *MainMenuButton::typeName() const { return "MainMenuButton"; }
+
MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {
}
+const char *PushButton::typeName() const { return "PushButton"; }
+
PushButton::PushButton(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, _alwaysVisible(readBool(stream))
@@ -56,6 +66,8 @@ PushButton::PushButton(Room *room, ReadStream &stream)
, _actionId(stream.readSint32LE()) {
}
+const char *EditBox::typeName() const { return "EditBox"; }
+
EditBox::EditBox(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, i1(stream.readSint32LE())
@@ -68,6 +80,8 @@ EditBox::EditBox(Room *room, ReadStream &stream)
, _fontId(stream.readSint32LE()) {
}
+const char *CheckBox::typeName() const { return "CheckBox"; }
+
CheckBox::CheckBox(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, b1(readBool(stream))
@@ -78,11 +92,15 @@ CheckBox::CheckBox(Room *room, ReadStream &stream)
, _valueId(stream.readSint32LE()) {
}
+const char *CheckBoxAutoAdjustNoise::typeName() const { return "CheckBoxAutoAdjustNoise"; }
+
CheckBoxAutoAdjustNoise::CheckBoxAutoAdjustNoise(Room *room, ReadStream &stream)
: CheckBox(room, stream) {
stream.readByte(); // unused and ignored byte
}
+const char *SlideButton::typeName() const { return "SlideButton"; }
+
SlideButton::SlideButton(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, i1(stream.readSint32LE())
@@ -93,12 +111,16 @@ SlideButton::SlideButton(Room *room, ReadStream &stream)
, _graph3(stream) {
}
+const char *IRCWindow::typeName() const { return "IRCWindow"; }
+
IRCWindow::IRCWindow(Room *room, ReadStream &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)
: ObjectBase(room, stream)
, _graph1(stream)
@@ -113,6 +135,8 @@ MessageBox::MessageBox(Room *room, ReadStream &stream)
_graph5.start(true);
}
+const char *VoiceMeter::typeName() const { return "VoiceMeter"; }
+
VoiceMeter::VoiceMeter(Room *room, ReadStream &stream)
: GraphicObject(room, stream) {
stream.readByte(); // unused and ignored byte
Commit: c2abfe5b56e78843966c27054f001a2f44101eba
https://github.com/scummvm/scummvm/commit/c2abfe5b56e78843966c27054f001a2f44101eba
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Lock characters during script processes
Changed paths:
engines/alcachofa/common.h
engines/alcachofa/console.cpp
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 9d41e4f9020..e8d83db85b8 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -85,16 +85,29 @@ private:
};
struct FakeLock {
- FakeLock(FakeSemaphore &semaphore) : _semaphore(semaphore) {
- semaphore._counter++;
+ FakeLock() : _semaphore(nullptr) {}
+
+ FakeLock(FakeSemaphore &semaphore) : _semaphore(&semaphore) {
+ _semaphore->_counter++;
+ }
+
+ FakeLock(const FakeLock &other) : _semaphore(other._semaphore) {
+ assert(_semaphore != nullptr);
+ _semaphore->_counter++;
+ }
+
+ FakeLock(FakeLock &&other) noexcept : _semaphore(other._semaphore) {
+ other._semaphore = nullptr;
}
~FakeLock() {
- assert(_semaphore._counter > 0);
- _semaphore._counter--;
+ if (_semaphore == nullptr)
+ return;
+ assert(_semaphore->_counter > 0);
+ _semaphore->_counter--;
}
private:
- FakeSemaphore &_semaphore;
+ FakeSemaphore *_semaphore;
};
inline Math::Vector3d as3D(const Math::Vector2d &v) {
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 4c72539a084..ed7e1629771 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -127,8 +127,10 @@ bool Console::cmdChangeRoom(int argc, const char **args) {
}
else if (g_engine->world().getRoomByName(args[1]) == nullptr)
debugPrintf("Invalid room name: %s\n", args[1]);
- else
+ else {
g_engine->player().changeRoom(args[1], true);
+ return false;
+ }
return true;
}
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 7ee6d4619b2..8cb43714a21 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -117,4 +117,14 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
_pressedObject = _selectedObject = nullptr;
}
+FakeSemaphore &Player::semaphoreFor(MainCharacterKind kind) {
+ static FakeSemaphore dummySemaphore;
+ switch (kind) {
+ case MainCharacterKind::None: return _semaphore;
+ case MainCharacterKind::Mortadelo: return g_engine->world().mortadelo().semaphore();
+ case MainCharacterKind::Filemon: return g_engine->world().filemon().semaphore();
+ default: assert(false && "Invalid main character kind"); return dummySemaphore;
+ }
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index e58fed5f053..3760395e989 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -37,6 +37,7 @@ public:
inline ShapeObject *&pressedObject() { return _pressedObject; }
inline Item *&heldItem() { return _heldItem; }
inline FakeSemaphore &semaphore() { return _semaphore; }
+ FakeSemaphore &semaphoreFor(MainCharacterKind kind);
inline bool &isOptionsMenuOpen() { return _isOptionsMenuOpen; }
inline bool &isGameLoaded() { return _isGameLoaded; }
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index eafa536a93b..790da4c9041 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -151,7 +151,7 @@ void Room::update() {
void Room::updateScripts() {
g_engine->script().updateCommonVariables();
if (!g_engine->scheduler().hasProcessWithName("ACTUALIZAR_" + _name))
- g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, true);
+ g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, true, true);
g_engine->scheduler().run();
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index f12cd0d4881..8031ea86088 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -146,11 +146,12 @@ struct StackEntry {
};
struct ScriptTask : public Task {
- ScriptTask(Process &process, const String &name, uint32 pc)
+ ScriptTask(Process &process, const String &name, uint32 pc, FakeLock &&lock)
: Task(process)
, _script(g_engine->script())
, _name(name)
- , _pc(pc) {
+ , _pc(pc)
+ , _lock(Common::move(lock)) {
pushInstruction(UINT_MAX);
}
@@ -158,7 +159,8 @@ struct ScriptTask : public Task {
: Task(process)
, _script(g_engine->script())
, _name(forkParent._name + " FORKED")
- , _pc(forkParent._pc) {
+ , _pc(forkParent._pc)
+ , _lock(forkParent._lock) {
for (uint i = 0; i < forkParent._stack.size(); i++)
_stack.push(forkParent._stack[i]);
pushNumber(1); // this task is the forked one
@@ -623,20 +625,24 @@ private:
String _name;
uint32 _pc;
bool _returnsFromKernelCall = false;
+ FakeLock _lock;
};
-Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, bool allowMissing) {
- return createProcess(character, behavior + '/' + action, allowMissing);
+Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, bool allowMissing, bool isBackground) {
+ return createProcess(character, behavior + '/' + action, allowMissing, isBackground);
}
-Process *Script::createProcess(MainCharacterKind character, const String &procedure, bool allowMissing) {
+Process *Script::createProcess(MainCharacterKind character, const String &procedure, bool allowMissing, bool isBackground) {
uint32 offset;
if (!_procedures.tryGetVal(procedure, offset)) {
if (allowMissing)
return nullptr;
error("Unknown required procedure: %s", procedure.c_str());
}
- Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset);
+ FakeLock lock;
+ if (!isBackground)
+ new (&lock) FakeLock(g_engine->player().semaphoreFor(character));
+ Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset, Common::move(lock));
process->name() = procedure;
return process;
}
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 6c4d328d6db..fbcc05fda08 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -150,12 +150,14 @@ public:
Process *createProcess(
MainCharacterKind character,
const Common::String &procedure,
- bool allowMissing = false);
+ bool allowMissing = false,
+ bool isBackground = false);
Process *createProcess(
MainCharacterKind character,
const Common::String &behavior,
const Common::String &action,
- bool allowMissing = false);
+ bool allowMissing = false,
+ bool isBackground = false);
using VariableNameIterator = Common::HashMap<Common::String, uint32>::const_iterator;
inline VariableNameIterator beginVariables() const { return _variableNames.begin(); }
Commit: 8a7d02239ba421a614b825a6b710c57900a1a98e
https://github.com/scummvm/scummvm/commit/8a7d02239ba421a614b825a6b710c57900a1a98e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Fix door cursors
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/scheduler.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 658bd94235c..361c30641cd 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -90,6 +90,19 @@ Door::Door(Room *room, ReadStream &stream)
_targetRoom.replace(' ', '_');
}
+CursorType Door::cursorType() const {
+ CursorType fromObject = ShapeObject::cursorType();
+ if (fromObject != CursorType::Point)
+ return fromObject;
+ switch (_characterDirection) {
+ case Direction::Up: return CursorType::LeaveUp;
+ case Direction::Right: return CursorType::LeaveRight;
+ case Direction::Down: return CursorType::LeaveDown;
+ case Direction::Left: return CursorType::LeaveLeft;
+ default: assert(false && "Invalid door character direction"); return fromObject;
+ }
+}
+
void Door::onClick() {
if (g_system->getMillis() - _lastClickTime < 500 && g_engine->player().activeCharacter()->clearTargetIf(this))
trigger(nullptr);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 5d36e5b3690..2221e50ae2d 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -353,6 +353,7 @@ public:
static constexpr const char *kClassName = "CPuerta";
Door(Room *room, Common::ReadStream &stream);
+ virtual CursorType cursorType() const override;
virtual void onClick() override;
virtual void trigger(const char *action) override;
virtual const char *typeName() const;
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index ad39277adc4..e649779b621 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -120,7 +120,7 @@ void Process::debugPrint() {
debugger->debugPrintf("pid: %3u char: %s ret: %2d \"%s\"\n", _pid, characterName, _lastReturnValue, _name.c_str());
for (uint i = 0; i < _tasks.size(); i++) {
- debugger->debugPrintf("\t%u: ", i);
+ debugger->debugPrintf(" %u: ", i);
_tasks[i]->debugPrint();
}
}
Commit: e8539aac3fa733834d25d95222e6db37641d174b
https://github.com/scummvm/scummvm/commit/e8539aac3fa733834d25d95222e6db37641d174b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:46+02:00
Commit Message:
ALCACHOFA: Add character interaction and fix crash on exit
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 361c30641cd..77fc7c18b65 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -48,6 +48,16 @@ ITriggerableObject::ITriggerableObject(ReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
, _interactionDirection((Direction)stream.readSint32LE()) {}
+void ITriggerableObject::onClick() {
+ auto heldItem = g_engine->player().heldItem();
+ const char *action;
+ if (heldItem == nullptr)
+ action = g_engine->input().wasMouseLeftReleased() ? "MIRAR" : "PULSAR";
+ else
+ action = heldItem->name().c_str();
+ g_engine->player().activeCharacter()->walkTo(_interactionPoint, Direction::Invalid, this, action);
+}
+
const char *InteractableObject::typeName() const { return "InteractableObject"; }
InteractableObject::InteractableObject(Room *room, ReadStream &stream)
@@ -66,13 +76,7 @@ void InteractableObject::drawDebug() {
}
void InteractableObject::onClick() {
- auto heldItem = g_engine->player().heldItem();
- const char *action;
- if (heldItem == nullptr)
- action = g_engine->input().wasMouseLeftReleased() ? "MIRAR" : "PULSAR";
- else
- action = heldItem->name().c_str();
- g_engine->player().activeCharacter()->walkTo(_interactionPoint, Direction::Invalid, this, action);
+ ITriggerableObject::onClick();
onHoverUpdate();
}
@@ -232,6 +236,11 @@ void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object)
}
}
+void Character::onClick() {
+ ITriggerableObject::onClick();
+ onHoverUpdate();
+}
+
void Character::trigger(const char *action) {
warning("stub: Trigger character %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
}
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 2221e50ae2d..30737c00106 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -329,6 +329,8 @@ public:
virtual void trigger(const char *action) = 0;
protected:
+ void onClick();
+
Common::Point _interactionPoint;
Direction _interactionDirection = Direction::Right;
};
@@ -377,6 +379,7 @@ public:
virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
+ virtual void onClick() override;
virtual void trigger(const char *action) override;
virtual const char *typeName() const;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 790da4c9041..b5fb75ac652 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -285,8 +285,7 @@ Inventory::Inventory(World *world, ReadStream &stream)
}
Inventory::~Inventory() {
- for (auto *item : _items)
- delete item;
+ // No need to delete items, they are room objects and thus deleted in Room::~Room
}
void Inventory::initItems() {
Commit: e116042bc754e85588f092884f09589f57bd59ea
https://github.com/scummvm/scummvm/commit/e116042bc754e85588f092884f09589f57bd59ea
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Execute object and character scripts on triggering
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 77fc7c18b65..d7154af2bc5 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -81,7 +81,8 @@ void InteractableObject::onClick() {
}
void InteractableObject::trigger(const char *action) {
- warning("stub: Trigger object %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
+ g_engine->player().activeCharacter()->stopWalking();
+ g_engine->player().triggerObject(this, action);
}
const char *Door::typeName() const { return "Door"; }
@@ -242,7 +243,12 @@ void Character::onClick() {
}
void Character::trigger(const char *action) {
- warning("stub: Trigger character %s with %s", name().c_str(), action == nullptr ? "<null>" : action);
+ g_engine->player().activeCharacter()->stopWalking(_interactionDirection);
+ if (scumm_stricmp(action, "iSABANA") == 0 && // Original hack probably to fix some bug :)
+ dynamic_cast<MainCharacter *>(this) != nullptr &&
+ room()->name().equalsIgnoreCase("CASA_FREDDY_ARRIBA"))
+ error("Not sure what *should* happen. How do we get here?");
+ g_engine->player().triggerObject(this, action);
}
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
@@ -412,9 +418,13 @@ void WalkingCharacter::updateWalkingAnimation()
void WalkingCharacter::onArrived() {
}
-void WalkingCharacter::stopWalkingAndTurn(Direction direction) {
+void WalkingCharacter::stopWalking(Direction direction) {
+ // be careful, the original engine had two versions of this method
+ // one without resetting _sourcePos
_isWalking = false;
- _direction = direction;
+ _sourcePos = _currentPos;
+ if (direction != Direction::Invalid)
+ _direction = direction;
}
void WalkingCharacter::walkTo(
@@ -583,7 +593,7 @@ void MainCharacter::onArrived() {
_activateObject = nullptr;
_activateAction = nullptr;
- stopWalkingAndTurn(activateObject->interactionDirection());
+ stopWalking(activateObject->interactionDirection());
if (g_engine->player().activeCharacter() == this)
activateObject->trigger(activateAction);
}
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 30737c00106..a9104789198 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -419,7 +419,7 @@ public:
Direction endDirection = Direction::Invalid,
ITriggerableObject *activateObject = nullptr,
const char *activateAction = nullptr);
- void stopWalkingAndTurn(Direction direction);
+ void stopWalking(Direction direction = Direction::Invalid);
void setPosition(const Common::Point &target);
virtual const char *typeName() const;
@@ -472,6 +472,7 @@ public:
virtual ~MainCharacter() override;
inline MainCharacterKind kind() const { return _kind; }
+ inline ObjectBase *¤tlyUsing() { return _currentlyUsingObject; }
inline ObjectBase *currentlyUsing() const { return _currentlyUsingObject; }
inline FakeSemaphore &semaphore() { return _semaphore; }
bool isBusy() const;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 8cb43714a21..cd29d94f7e9 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -20,6 +20,7 @@
*/
#include "player.h"
+#include "script.h"
#include "alcachofa.h"
using namespace Common;
@@ -117,6 +118,12 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
_pressedObject = _selectedObject = nullptr;
}
+MainCharacter *Player::inactiveCharacter() const {
+ if (_activeCharacter == nullptr)
+ return nullptr;
+ return &g_engine->world().getOtherMainCharacterByKind(activeCharacterKind());
+}
+
FakeSemaphore &Player::semaphoreFor(MainCharacterKind kind) {
static FakeSemaphore dummySemaphore;
switch (kind) {
@@ -127,4 +134,29 @@ FakeSemaphore &Player::semaphoreFor(MainCharacterKind kind) {
}
}
+void Player::triggerObject(ObjectBase *object, const char *action) {
+ assert(object != nullptr && action != nullptr);
+ if (_activeCharacter->isBusy() || _activeCharacter->currentlyUsing() != nullptr)
+ return;
+ debug("Trigger object %s %s with %s", object->typeName(), object->name().c_str(), action);
+
+ if (inactiveCharacter()->currentlyUsing() == object) {
+ action = "MIRAR";
+ _activeCharacter->currentlyUsing() = nullptr;
+ }
+ else
+ _activeCharacter->currentlyUsing() = object;
+
+ auto &script = g_engine->script();
+ if (script.createProcess(activeCharacterKind(), object->name(), action, ScriptFlags::AllowMissing) != nullptr)
+ return;
+ else if (scumm_stricmp(action, "MIRAR") == 0)
+ script.createProcess(activeCharacterKind(), "DefectoMirar");
+ else if (action[0] == 'i' && object->name()[0] == 'i')
+ // TODO: Check if and how this can happen. I guess it crashes now but might be ignored by the original engine
+ script.createProcess(activeCharacterKind(), "DefectoObjeto");
+ else
+ script.createProcess(activeCharacterKind(), "DefectoUsar");
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 3760395e989..b68c3da585a 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -37,6 +37,7 @@ public:
inline ShapeObject *&pressedObject() { return _pressedObject; }
inline Item *&heldItem() { return _heldItem; }
inline FakeSemaphore &semaphore() { return _semaphore; }
+ MainCharacter *inactiveCharacter() const;
FakeSemaphore &semaphoreFor(MainCharacterKind kind);
inline bool &isOptionsMenuOpen() { return _isOptionsMenuOpen; }
@@ -50,7 +51,7 @@ public:
void postUpdate();
void updateCursor();
void changeRoom(const Common::String &targetRoomName, bool resetCamera);
-
+ void triggerObject(ObjectBase *object, const char *action);
private:
Common::ScopedPtr<Animation> _cursorAnimation;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index b5fb75ac652..e40cd5d083e 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -151,7 +151,7 @@ void Room::update() {
void Room::updateScripts() {
g_engine->script().updateCommonVariables();
if (!g_engine->scheduler().hasProcessWithName("ACTUALIZAR_" + _name))
- g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, true, true);
+ g_engine->script().createProcess(MainCharacterKind::None, "ACTUALIZAR_" + _name, ScriptFlags::AllowMissing | ScriptFlags::IsBackground);
g_engine->scheduler().run();
}
@@ -368,6 +368,15 @@ MainCharacter &World::getMainCharacterByKind(MainCharacterKind kind) const {
}
}
+MainCharacter &World::getOtherMainCharacterByKind(MainCharacterKind kind) const {
+ switch (kind) {
+ case MainCharacterKind::Mortadelo: return *_filemon;
+ case MainCharacterKind::Filemon: return *_mortadelo;
+ default:
+ error("Invalid character kind given to getOtherMainCharacterByKind");
+ }
+}
+
Room *World::getRoomByName(const Common::String &name) const {
for (auto *room : _rooms) {
if (room->name().equalsIgnoreCase(name))
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 313e602066c..c349ae5933e 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -149,6 +149,7 @@ public:
}
MainCharacter &getMainCharacterByKind(MainCharacterKind kind) const;
+ MainCharacter &getOtherMainCharacterByKind(MainCharacterKind kind) const;
Room *getRoomByName(const Common::String &name) const;
ObjectBase *getObjectByName(const Common::String &name) const;
ObjectBase *getObjectByName(MainCharacterKind character, const Common::String &name) const;
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index e649779b621..ae24ec1d6ae 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -98,7 +98,7 @@ TaskReturnType Process::run() {
break;
case TaskReturnType::Finished:
_lastReturnValue = ret.returnValue();
- _tasks.pop();
+ delete _tasks.pop();
break;
default:
assert(false && "Invalid task return type");
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 8031ea86088..6760473afe2 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -167,9 +167,11 @@ struct ScriptTask : public Task {
}
virtual TaskReturn run() override {
+ if (_isFirstExecution || _returnsFromKernelCall)
+ setCharacterVariables();
if (_returnsFromKernelCall)
pushNumber(process().returnValue());
- _returnsFromKernelCall = false;
+ _isFirstExecution = _returnsFromKernelCall = false;
while (true) {
if (_pc >= _script._instructions.size())
@@ -299,6 +301,11 @@ struct ScriptTask : public Task {
}
private:
+ void setCharacterVariables() {
+ _script.variable("m_o_f") = (int32)process().character();
+ _script.variable("m_o_f_real") = (int32)g_engine->player().activeCharacterKind();
+ }
+
void pushNumber(int32 value) {
_stack.push({ StackEntryType::Number, value });
}
@@ -420,11 +427,11 @@ private:
if (character == nullptr)
error("Script tried to stop-and-turn unknown character");
else
- character->stopWalkingAndTurn((Direction)getNumberArg(1));
+ character->stopWalking((Direction)getNumberArg(1));
return TaskReturn::finish(1);
}
case ScriptKernelTask::StopAndTurnMe: {
- relatedCharacter().stopWalkingAndTurn((Direction)getNumberArg(0));
+ relatedCharacter().stopWalking((Direction)getNumberArg(0));
return TaskReturn::finish(1);
}
case ScriptKernelTask::ChangeCharacter:
@@ -625,22 +632,23 @@ private:
String _name;
uint32 _pc;
bool _returnsFromKernelCall = false;
+ bool _isFirstExecution = false;
FakeLock _lock;
};
-Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, bool allowMissing, bool isBackground) {
- return createProcess(character, behavior + '/' + action, allowMissing, isBackground);
+Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, ScriptFlags flags) {
+ return createProcess(character, behavior + '/' + action, flags);
}
-Process *Script::createProcess(MainCharacterKind character, const String &procedure, bool allowMissing, bool isBackground) {
+Process *Script::createProcess(MainCharacterKind character, const String &procedure, ScriptFlags flags) {
uint32 offset;
if (!_procedures.tryGetVal(procedure, offset)) {
- if (allowMissing)
+ if (flags & ScriptFlags::AllowMissing)
return nullptr;
error("Unknown required procedure: %s", procedure.c_str());
}
FakeLock lock;
- if (!isBackground)
+ if (!(flags & ScriptFlags::IsBackground))
new (&lock) FakeLock(g_engine->player().semaphoreFor(character));
Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset, Common::move(lock));
process->name() = procedure;
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index fbcc05fda08..46102d58f52 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -133,6 +133,18 @@ enum class ScriptKernelTask {
LerpCamToObjectKeepingZ
};
+enum class ScriptFlags {
+ None = 0,
+ AllowMissing = (1 << 0),
+ IsBackground = (1 << 1)
+};
+inline ScriptFlags operator | (ScriptFlags a, ScriptFlags b) {
+ return (ScriptFlags)(((uint)a) | ((uint)b));
+}
+inline bool operator & (ScriptFlags a, ScriptFlags b) {
+ return ((uint)a) & ((uint)b);
+}
+
struct ScriptInstruction {
ScriptInstruction(Common::ReadStream &stream);
@@ -150,14 +162,12 @@ public:
Process *createProcess(
MainCharacterKind character,
const Common::String &procedure,
- bool allowMissing = false,
- bool isBackground = false);
+ ScriptFlags flags = ScriptFlags::None);
Process *createProcess(
MainCharacterKind character,
const Common::String &behavior,
const Common::String &action,
- bool allowMissing = false,
- bool isBackground = false);
+ ScriptFlags flags = ScriptFlags::None);
using VariableNameIterator = Common::HashMap<Common::String, uint32>::const_iterator;
inline VariableNameIterator beginVariables() const { return _variableNames.begin(); }
Commit: 21812391c8b8d4932cadff5fae5ea73c510cfe70
https://github.com/scummvm/scummvm/commit/21812391c8b8d4932cadff5fae5ea73c510cfe70
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Add triggering of doors
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 89f6a3de561..3ec3a456741 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -55,6 +55,12 @@ void Camera::setFollow(WalkingCharacter *target) {
_isChanging = false;
}
+void Camera::setPosition(Vector2d v) {
+ _usedCenter.x() = v.getX();
+ _usedCenter.y() = v.getY();
+ setFollow(nullptr);
+}
+
static Matrix4 scaleMatrix(float scale) {
Matrix4 m;
m(0, 0) = scale;
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 091bf4a7312..fc3dd924ea9 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -48,6 +48,7 @@ public:
void resetRotationAndScale();
void setRoomBounds(Common::Point bgSize, int16 bgScale);
void setFollow(WalkingCharacter *target);
+ void setPosition(Math::Vector2d v);
private:
Math::Vector3d setAppliedCenter(Math::Vector3d center);
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index d7154af2bc5..861d2b9925a 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -118,7 +118,7 @@ void Door::onClick() {
}
void Door::trigger(const char *_) {
- warning("STUB: Triggering door to %s", _targetRoom.c_str());
+ g_engine->player().triggerDoor(this);
}
const char *Character::typeName() const { return "Character"; }
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index af8574fa1e4..755c4d7a722 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -121,6 +121,7 @@ void AnimationBase::load() {
uint frameCount = file.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(file.readUint32LE());
@@ -142,6 +143,11 @@ void AnimationBase::freeImages() {
if (image != nullptr)
delete image;
}
+ _images.clear();
+ _spriteOffsets.clear();
+ _spriteBases.clear();
+ _frames.clear();
+ _imageOffsets.clear();
_isLoaded = false;
}
@@ -206,6 +212,16 @@ void Animation::load() {
_renderedTexture = g_engine->renderer().createTexture(maxBounds.width(), maxBounds.height(), withMipmaps);
}
+void Animation::freeImages() {
+ if (!_isLoaded)
+ return;
+ AnimationBase::freeImages();
+ _renderedSurface.free();
+ _renderedTexture.reset(nullptr);
+ _renderedFrameI = -1;
+ _premultiplyAlpha = 100;
+}
+
int32 Animation::imageIndex(int32 frameI, int32 spriteId) const {
assert(frameI >= 0 && (uint)frameI < frameCount());
assert(spriteId >= 0 && (uint)spriteId < spriteCount());
@@ -404,8 +420,12 @@ void Graphic::loadResources() {
}
void Graphic::freeResources() {
- _ownedAnimation.reset();
- _animation = nullptr;
+ if (_ownedAnimation == nullptr)
+ _animation = nullptr;
+ else {
+ _ownedAnimation->freeImages();
+ _animation = _ownedAnimation.get();
+ }
}
void Graphic::update() {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index c12adb6077d..2f2306ddd30 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -167,7 +167,7 @@ public:
Animation(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations);
void load();
- using AnimationBase::freeImages;
+ void freeImages();
inline bool isLoaded() const { return _isLoaded; }
inline uint spriteCount() const { return _spriteBases.size(); }
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index a9104789198..7e48f54b391 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -355,6 +355,10 @@ public:
static constexpr const char *kClassName = "CPuerta";
Door(Room *room, Common::ReadStream &stream);
+ inline const Common::String &targetRoom() const { return _targetRoom; }
+ inline const Common::String &targetObject() const { return _targetObject; }
+ inline Direction characterDirection() const { return _characterDirection; }
+
virtual CursorType cursorType() const override;
virtual void onClick() override;
virtual void trigger(const char *action) override;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index cd29d94f7e9..d57c9120706 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -159,4 +159,65 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
script.createProcess(activeCharacterKind(), "DefectoUsar");
}
+struct DoorTask : public Task {
+ DoorTask(Process &process, const Door *door, FakeLock &&lock)
+ : Task(process)
+ , _lock(move(lock))
+ , _sourceDoor(door)
+ , _character(g_engine->player().activeCharacter())
+ , _player(g_engine->player()) {
+ _targetRoom = g_engine->world().getRoomByName(door->targetRoom());
+ if (_targetRoom == nullptr)
+ error("Invalid door target room: %s", door->targetRoom().c_str());
+
+ _targetDoor = dynamic_cast<Door *>(_targetRoom->getObjectByName(door->targetObject()));
+ if (_targetDoor == nullptr)
+ error("Invalid door target door: %s", door->targetObject().c_str());
+
+ process.name() = String::format("Door to %s %s", _targetRoom->name().c_str(), _targetDoor->name().c_str());
+ }
+
+ virtual TaskReturn run() {
+ TASK_BEGIN;
+ // TODO: Fade out music on room change
+ // TODO: Fade out/in on room change instead of delay
+ TASK_WAIT(delay(500));
+ _player.changeRoom(_targetRoom->name(), true);
+
+ if (_targetRoom->fixedCameraOnEntering())
+ g_engine->camera().setPosition(as2D(_targetDoor->interactionPoint()));
+ else {
+ _character->room() = _targetRoom;
+ _character->setPosition(_targetDoor->interactionPoint());
+ _character->stopWalking(_targetDoor->characterDirection());
+ g_engine->camera().setFollow(_character);
+ }
+
+ // TODO: Start music on room change
+ if (g_engine->script().createProcess(_character->kind(), "ENTRAR_" + _targetRoom->name(), ScriptFlags::AllowMissing))
+ TASK_WAIT(delay(0));
+ else
+ TASK_WAIT(delay(500));
+ TASK_END;
+ }
+
+ virtual void debugPrint() {
+ g_engine->console().debugPrintf("%s\n", process().name().c_str());
+ }
+
+private:
+ FakeLock _lock;
+ const Door *_sourceDoor, *_targetDoor;
+ Room *_targetRoom;
+ MainCharacter *_character;
+ Player &_player;
+};
+
+void Player::triggerDoor(const Door *door) {
+ _heldItem = nullptr;
+
+ FakeLock lock(_activeCharacter->semaphore());
+ g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index b68c3da585a..9b6d9ea79aa 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -52,6 +52,7 @@ public:
void updateCursor();
void changeRoom(const Common::String &targetRoomName, bool resetCamera);
void triggerObject(ObjectBase *object, const char *action);
+ void triggerDoor(const Door *door);
private:
Common::ScopedPtr<Animation> _cursorAnimation;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index e40cd5d083e..a69e61ab2e6 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -93,7 +93,7 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
auto backgroundScale = stream.readSint16LE();
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
- _cameraFollowsUponLeaving = readBool(stream);
+ _fixedCameraOnEntering = readBool(stream);
PathFindingShape _(stream); // unused path finding area
_characterAlphaPremultiplier = stream.readByte();
if (hasUselessByte)
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index c349ae5933e..7c18b2df093 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -47,6 +47,7 @@ public:
}
inline uint8 characterAlphaTint() const { return _characterAlphaTint; }
inline uint8 characterAlphaPremultiplier() const { return _characterAlphaPremultiplier; }
+ inline bool fixedCameraOnEntering() const { return _fixedCameraOnEntering; }
void update();
virtual bool updateInput();
@@ -70,7 +71,7 @@ protected:
World *_world;
Common::String _name;
PathFindingShape _floors[2];
- bool _cameraFollowsUponLeaving;
+ bool _fixedCameraOnEntering;
int8
_musicId,
_activeFloorI = -1;
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index ae24ec1d6ae..c8776fa611c 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -196,7 +196,6 @@ static Process **getProcessByName(Array<Process *> &_processes, const String &na
}
void Scheduler::killProcessByName(const String &name) {
- assert(processesToRun().empty());
Process **process = getProcessByName(processesToRunNext(), name);
if (process != nullptr) {
delete *process;
Commit: ae53e521cdd115688edb92b2b25fb49b93c39f79
https://github.com/scummvm/scummvm/commit/ae53e521cdd115688edb92b2b25fb49b93c39f79
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Toggle related objects of interactable objects
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 861d2b9925a..d7051f7cbec 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -85,6 +85,13 @@ void InteractableObject::trigger(const char *action) {
g_engine->player().triggerObject(this, action);
}
+void InteractableObject::toggle(bool isEnabled) {
+ ObjectBase::toggle(isEnabled);
+ ObjectBase *related = room()->getObjectByName(_relatedObject);
+ if (related != nullptr)
+ related->toggle(isEnabled);
+}
+
const char *Door::typeName() const { return "Door"; }
Door::Door(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 7e48f54b391..0d5f7e26636 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -344,6 +344,7 @@ public:
virtual void drawDebug() override;
virtual void onClick() override;
virtual void trigger(const char *action) override;
+ virtual void toggle(bool isEnabled) override;
virtual const char *typeName() const;
private:
Commit: e28435d1a8280fa1f45a245e4255c3aca56e0a16
https://github.com/scummvm/scummvm/commit/e28435d1a8280fa1f45a245e4255c3aca56e0a16
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Let camera catch up on room changes
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 3ec3a456741..13f309bdb81 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -48,9 +48,10 @@ void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
_roomScale = bgScale;
}
-void Camera::setFollow(WalkingCharacter *target) {
+void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
_followTarget = target;
_lastUpdateTime = g_system->getMillis();
+ _catchUp = catchUp;
if (target == nullptr)
_isChanging = false;
}
@@ -147,7 +148,12 @@ void Camera::update() {
deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
_lastUpdateTime = now;
- updateFollowing(deltaTime);
+ if (_catchUp && _followTarget != nullptr) {
+ for (int i = 0; i < 4; i++)
+ updateFollowing(50.0f);
+ }
+ else
+ updateFollowing(deltaTime);
setAppliedCenter(_usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index fc3dd924ea9..ac04907493f 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -41,13 +41,14 @@ class Camera {
public:
inline Math::Angle rotation() const { return _rotation; }
inline Math::Vector2d &shake() { return _shake; }
+ inline WalkingCharacter *followTarget() { return _followTarget; }
void update();
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
void resetRotationAndScale();
void setRoomBounds(Common::Point bgSize, int16 bgScale);
- void setFollow(WalkingCharacter *target);
+ void setFollow(WalkingCharacter *target, bool catchUp = false);
void setPosition(Math::Vector2d v);
private:
@@ -57,7 +58,8 @@ private:
uint32 _lastUpdateTime = 0;
bool _isChanging = false,
- _isBraking = false;
+ _isBraking = false,
+ _catchUp = false;
float
_scale = 1.0f,
_roomScale = 1.0f,
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index d57c9120706..6639f8ef32d 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -115,6 +115,9 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
if (resetCamera)
g_engine->camera().resetRotationAndScale();
+ WalkingCharacter *followTarget = g_engine->camera().followTarget();
+ if (followTarget != nullptr)
+ g_engine->camera().setFollow(followTarget, true);
_pressedObject = _selectedObject = nullptr;
}
@@ -190,7 +193,7 @@ struct DoorTask : public Task {
_character->room() = _targetRoom;
_character->setPosition(_targetDoor->interactionPoint());
_character->stopWalking(_targetDoor->characterDirection());
- g_engine->camera().setFollow(_character);
+ g_engine->camera().setFollow(_character, true);
}
// TODO: Start music on room change
Commit: ae9c2d0d2155b3782cb8c1e63524d02aa9085f83
https://github.com/scummvm/scummvm/commit/ae9c2d0d2155b3782cb8c1e63524d02aa9085f83
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Add text drawing
Changed paths:
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 3cbd3e0d210..3f164c4b723 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -227,7 +227,11 @@ void ShapeObject::onHoverEnd() {
}
void ShapeObject::onHoverUpdate() {
- // TODO: Add text request for name
+ g_engine->drawQueue().add<TextDrawRequest>(
+ g_engine->world().generalFont(),
+ name().c_str(),
+ g_engine->input().mousePos2D() - Point(0, 35),
+ -1, true, kWhite, 0);
}
void ShapeObject::onClick() {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 755c4d7a722..bb7963eaf9d 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -198,6 +198,37 @@ void AnimationBase::loadMissingAnimation() {
_frames.push_back({ Point(), Point(), 1 });
}
+// unfortunately ScummVMs BLEND_NORMAL does not blend alpha
+// but this also bad, let's find/discuss a better solution later
+void AnimationBase::fullBlend(const ManagedSurface &source, ManagedSurface &destination, int offsetX, int offsetY) {
+ assert(source.format == BlendBlit::getSupportedPixelFormat());
+ assert(destination.format == BlendBlit::getSupportedPixelFormat());
+ assert(offsetX >= 0 && offsetX + source.w <= destination.w);
+ assert(offsetY >= 0 && offsetY + source.h <= destination.h);
+
+ const byte *sourceLine = (byte *)source.getPixels();
+ byte *destinationLine = (byte *)destination.getPixels() + offsetY * destination.pitch + offsetX * 4;
+ for (int y = 0; y < source.h; y++) {
+ const byte *sourcePixel = sourceLine;
+ byte *destPixel = destinationLine;
+ for (int x = 0; x < source.w; x++) {
+ byte alpha = (*(const uint32 *)sourcePixel) & 0xff;
+ for (int i = 1; i < 4; i++)
+ destPixel[i] = ((byte)(alpha * sourcePixel[i] / 255)) + ((byte)((255 - alpha) * destPixel[i] / 255));
+ destPixel[0] = alpha + ((byte)((255 - alpha) * destPixel[0] / 255));
+ sourcePixel += 4;
+ destPixel += 4;
+ }
+ sourceLine += source.pitch;
+ destinationLine += destination.pitch;
+ }
+}
+
+Point AnimationBase::imageSize(int32 imageI) const {
+ auto image = _images[imageI];
+ return image == nullptr ? Point() : Point(image->w, image->h);
+}
+
Animation::Animation(String fileName, AnimationFolder folder)
: AnimationBase(fileName, folder) {
}
@@ -274,37 +305,6 @@ int32 Animation::frameAtTime(uint32 time) const {
return -1;
}
-Point Animation::imageSize(int32 imageI) const {
- auto image = _images[imageI];
- return image == nullptr ? Point() : Point(image->w, image->h);
-}
-
-// unfortunately ScummVMs BLEND_NORMAL does not blend alpha
-// but this also bad, let's find/discuss a better solution later
-static void fullBlend(const ManagedSurface &source, ManagedSurface &destination, int offsetX, int offsetY) {
- assert(source.format == BlendBlit::getSupportedPixelFormat());
- assert(destination.format == BlendBlit::getSupportedPixelFormat());
- assert(offsetX >= 0 && offsetX + source.w <= destination.w);
- assert(offsetY >= 0 && offsetY + source.h <= destination.h);
-
- const byte *sourceLine = (byte *)source.getPixels();
- byte *destinationLine = (byte *)destination.getPixels() + offsetY * destination.pitch + offsetX * 4;
- for (int y = 0; y < source.h; y++) {
- const byte *sourcePixel = sourceLine;
- byte *destPixel = destinationLine;
- for (int x = 0; x < source.w; x++) {
- byte alpha = (*(const uint32 *)sourcePixel) & 0xff;
- for (int i = 1; i < 4; i++)
- destPixel[i] = ((byte)(alpha * sourcePixel[i] / 255)) + ((byte)((255 - alpha) * destPixel[i] / 255));
- destPixel[0] = alpha + ((byte)((255 - alpha) * destPixel[0] / 255));
- sourcePixel += 4;
- destPixel += 4;
- }
- sourceLine += source.pitch;
- destinationLine += destination.pitch;
- }
-}
-
void Animation::prerenderFrame(int32 frameI) {
assert(frameI >= 0 && (uint)frameI < frameCount());
if (frameI == _renderedFrameI)
@@ -332,8 +332,6 @@ void Animation::prerenderFrame(int32 frameI) {
_renderedFrameI = frameI;
}
-
-
void Animation::draw2D(int32 frameI, Vector2d center, float scale, BlendMode blendMode, Color color) {
prerenderFrame(frameI);
auto bounds = frameBounds(frameI);
@@ -389,6 +387,73 @@ void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d tiling, Vect
renderer.quad(as2D(topLeft), size, kWhite, rotation, texMin + texOffset, texMax + texOffset);
}
+Font::Font(String fileName) : AnimationBase(fileName) {}
+
+static int16 nextPowerOfTwo(int16 v) {
+ // adapted from https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+ assert(v > 0);
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ return v + 1;
+}
+
+void Font::load() {
+ if (_isLoaded)
+ 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
+ Point cellSize;
+ for (auto image : _images) {
+ assert(image != nullptr); // no fake pictures in fonts please
+ cellSize.x = MAX(cellSize.x, image->w);
+ cellSize.y = MAX(cellSize.y, image->h);
+ }
+
+ _texMins.resize(_images.size());
+ _texMaxs.resize(_images.size());
+ ManagedSurface atlasSurface(nextPowerOfTwo(cellSize.x * 16), nextPowerOfTwo(cellSize.y * 16), BlendBlit::getSupportedPixelFormat());
+ cellSize.x = atlasSurface.w / 16;
+ cellSize.y = atlasSurface.h / 16;
+ const float invWidth = 1.0f / atlasSurface.w;
+ const float invHeight = 1.0f / atlasSurface.h;
+ for (uint i = 0; i < _images.size(); i++) {
+ int offsetX = (i % 16) * cellSize.x + (cellSize.x - _images[i]->w) / 2;
+ int offsetY = (i / 16) * cellSize.y + (cellSize.y - _images[i]->h) / 2;
+ fullBlend(*_images[i], atlasSurface, offsetX, offsetY);
+
+ _texMins[i].setX(offsetX * invWidth);
+ _texMins[i].setY(offsetY * invHeight);
+ _texMaxs[i].setX((offsetX + _images[i]->w) * invWidth);
+ _texMaxs[i].setY((offsetY + _images[i]->h) * invHeight);
+ }
+ _texture = g_engine->renderer().createTexture(atlasSurface.w, atlasSurface.h, false);
+ _texture->update(atlasSurface);
+ debug("Rendered font atlas %s at %dx%d", _fileName.c_str(), atlasSurface.w, atlasSurface.h);
+}
+
+void Font::freeImages() {
+ if (!_isLoaded)
+ return;
+ AnimationBase::freeImages();
+ _texture.reset();
+ _texMins.clear();
+ _texMaxs.clear();
+}
+
+void Font::drawCharacter(int32 imageI, Point centerPoint, Color color) {
+ assert(imageI >= 0 && (uint)imageI < _images.size());
+ Vector2d center = as2D(centerPoint + _imageOffsets[imageI]);
+ Vector2d size(_images[imageI]->w, _images[imageI]->h);
+
+ auto &renderer = g_engine->renderer();
+ renderer.setTexture(_texture.get());
+ renderer.setBlendMode(BlendMode::Tinted);
+ renderer.quad(center, size, color, Angle(), _texMins[imageI], _texMaxs[imageI]);
+}
+
Graphic::Graphic() {
}
@@ -541,6 +606,124 @@ void SpecialEffectDrawRequest::draw() {
_animation->drawEffect(_frameI, _topLeft, _tiling, _texOffset, _blendMode);
}
+static const byte *trimLeading(const byte *text) {
+ while (*text && *text <= ' ')
+ text++;
+ return text;
+}
+
+static const byte *trimTrailing(const byte *text, const byte *begin) {
+ while (text != begin && *text <= ' ')
+ text--;
+ return text;
+}
+
+static Point characterSize(const Font &font, byte ch) {
+ if (ch <= ' ' || 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)
+ , _color(color) {
+ const int screenW = g_system->getWidth();
+ const int screenH = g_system->getHeight();
+ if (maxWidth < 0)
+ maxWidth = screenW;
+
+ // allocate on drawQueue to prevent having destruct it
+ assert(originalText != nullptr);
+ auto textLen = strlen(originalText);
+ char *text = (char*)g_engine->drawQueue().allocator().allocateRaw(textLen + 1, 1);
+ memcpy(text, originalText, textLen + 1);
+
+ // split into trimmed lines
+ uint lineCount = 0;
+ const byte *itChar = (byte*)text, *itLine = (byte*)text;
+ int lineWidth = 0;
+ while (true) {
+ if (lineCount >= kMaxLines)
+ error("Text to be rendered has too many lines, check text validity and max line count");
+
+ if (*itChar != '\r' && *itChar)
+ lineWidth += characterSize(font, *itChar).x;
+ if (lineWidth <= maxWidth && *itChar != '\r' && *itChar) {
+ itChar++;
+ continue;
+ }
+ // now we are in new-line territory
+
+ if (centered) {
+ auto itLineEnd = trimTrailing(itChar, itLine);
+ itLine = trimLeading(itLine);
+ _allLines[lineCount] = TextLine(itLine, itLineEnd - itLine + 1);
+ itChar = trimLeading(itChar);
+ }
+ else
+ _allLines[lineCount] = TextLine(itLine, itChar - itLine);
+ lineCount++;
+ itLine = itChar;
+
+ if (!*itChar)
+ break;
+ }
+ _lines = Span<TextLine>(_allLines, lineCount);
+ _posX = Span<int>(_allPosX, lineCount);
+
+ // calc line widths and max line width
+ _width = 0;
+ for (uint i = 0; i < lineCount; i++) {
+ lineWidth = 0;
+ for (auto ch : _lines[i]) {
+ if (ch != '\r' && ch)
+ lineWidth += characterSize(font, ch).x;
+ }
+ _posX[i] = lineWidth;
+ _width = MAX(_width, lineWidth);
+ }
+
+ // setup line positions
+ if (centered) {
+ if (pos.x - _width / 2 < 0)
+ pos.x = _width / 2 + 1;
+ if (pos.x + _width / 2 >= screenW)
+ pos.x = screenW - _width / 2 - 1;
+ for (auto &linePosX : _posX)
+ linePosX = pos.x - linePosX / 2;
+ }
+ else
+ fill(_posX.begin(), _posX.end(), pos.x);
+
+ // setup height and y position
+ _height = (int)lineCount * (font.imageSize(0).y * 4 / 3);
+ _posY = pos.y;
+ if (centered)
+ _posY -= _height / 2;
+ if (_posY < 0)
+ _posY = 0;
+ if (_posY + _height >= screenH)
+ _posY = screenH - _height;
+}
+
+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 > ' ' && ch < _font.imageCount())
+ _font.drawCharacter(ch - ' ', Point(cursor.x, cursor.y), _color);
+ cursor.x += charSize.x;
+ }
+ cursor.y += spaceSize.y * 4 / 3;
+ }
+}
+
DrawQueue::DrawQueue(IRenderer *renderer)
: _renderer(renderer)
, _allocator(1024) {
@@ -593,8 +776,8 @@ void *BumpAllocator::allocateRaw(size_t size, size_t align) {
assert(size <= _pageSize);
uintptr_t page = (uintptr_t)_pages[_pageI];
uintptr_t top = page + _used;
- top = top + align - 1;
- top = top - (top % align);
+ top += align - 1;
+ top -= top % align;
if (page + _pageSize - top >= size) {
_used = top + size - page;
return (void *)top;
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 2f2306ddd30..4395d269f40 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -142,6 +142,14 @@ protected:
void loadMissingAnimation();
void freeImages();
Graphics::ManagedSurface *readImage(Common::SeekableReadStream &stream) const;
+ Common::Point imageSize(int32 imageI) const;
+ inline bool isLoaded() const { return _isLoaded; }
+
+ static void fullBlend(
+ const Graphics::ManagedSurface &source,
+ Graphics::ManagedSurface &destination,
+ int offsetX,
+ int offsetY);
static constexpr const uint kMaxSpriteIDs = 256;
Common::String _fileName;
@@ -169,7 +177,7 @@ public:
void load();
void freeImages();
- inline bool isLoaded() const { return _isLoaded; }
+ using AnimationBase::isLoaded;
inline uint spriteCount() const { return _spriteBases.size(); }
inline uint frameCount() const { return _frames.size(); }
inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
@@ -179,7 +187,7 @@ public:
Common::Point totalFrameOffset(int32 frameI) const;
int32 frameAtTime(uint32 time) const;
int32 imageIndex(int32 frameI, int32 spriteI) const;
- Common::Point imageSize(int32 imageI) const;
+ using AnimationBase::imageSize;
void draw2D(
int32 frameI,
@@ -214,7 +222,20 @@ private:
};
class Font : private AnimationBase {
+public:
+ Font(Common::String fileName);
+
+ void load();
+ void freeImages();
+ void drawCharacter(int32 imageI, Common::Point center, Color color);
+
+ using AnimationBase::isLoaded;
+ using AnimationBase::imageSize;
+ inline uint imageCount() const { return _images.size(); }
+private:
+ Common::Array<Math::Vector2d> _texMins, _texMaxs;
+ Common::ScopedPtr<ITexture> _texture;
};
class Graphic {
@@ -326,6 +347,33 @@ private:
BlendMode _blendMode;
};
+class TextDrawRequest : public IDrawRequest {
+public:
+ TextDrawRequest(
+ Font &font,
+ const char *text,
+ Common::Point pos,
+ int maxWidth,
+ bool centered,
+ Color color,
+ int8 order);
+
+ inline Common::Point size() const { return { (int16)_width, (int16)_height }; }
+ virtual void draw() override;
+
+private:
+ static constexpr uint kMaxLines = 8;
+ using TextLine = Common::Span<const byte>; ///< byte to convert 128+ characters to image indices
+
+ Font &_font;
+ int _posY, _height, _width;
+ Color _color;
+ Common::Span<TextLine> _lines;
+ Common::Span<int> _posX;
+ TextLine _allLines[kMaxLines];
+ int _allPosX[kMaxLines];
+};
+
class BumpAllocator {
public:
BumpAllocator(size_t pageSize);
@@ -354,6 +402,7 @@ public:
inline void add(Args&&... args) {
addRequest(_allocator.allocate<T>(Common::forward<Args>(args)...));
}
+ inline BumpAllocator &allocator() { return _allocator; }
void clear();
void setLodBias(int8 orderFrom, int8 orderTo, float newLodBias);
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index a69e61ab2e6..b3580ec65a1 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -217,16 +217,20 @@ void Room::updateObjects() {
}
void Room::drawObjects() {
- for (auto *object : _objects)
- object->draw();
+ for (auto *object : _objects) {
+ if (object->room() == g_engine->player().currentRoom())
+ object->draw();
+ }
}
void Room::drawDebug() {
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
if (renderer == nullptr || !g_engine->console().isAnyDebugDrawingOn())
return;
- for (auto *object : _objects)
- object->drawDebug();
+ for (auto *object : _objects) {
+ if (object->room() == g_engine->player().currentRoom())
+ object->drawDebug();
+ }
if (_activeFloorI < 0)
return;
if (_activeFloorI >= 0 && g_engine->console().showFloor())
@@ -258,8 +262,9 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
for (auto object : _objects) {
auto shape = object->shape();
auto shapeObject = dynamic_cast<ShapeObject *>(object);
- if (!object->isEnabled() || shape == nullptr || shapeObject == nullptr ||
- object->room() != this || // e.g. a main character that is in another room
+ if (!object->isEnabled() ||
+ shape == nullptr || shapeObject == nullptr ||
+ object->room() != g_engine->player().currentRoom() || // e.g. a main character that is in another room
!shape->contains(g_engine->input().mousePos3D()))
continue;
if (best == nullptr || shapeObject->order() < best->order())
@@ -351,6 +356,11 @@ World::World() {
if (_mortadelo == nullptr)
error("Could not find MORTADELO");
+ _generalFont.reset(new Font(getGlobalAnimationName(GlobalAnimationKind::GeneralFont)));
+ _generalFont->load();
+ _dialogFont.reset(new Font(getGlobalAnimationName(GlobalAnimationKind::DialogFont)));
+ _dialogFont->load();
+
_inventory->initItems();
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 7c18b2df093..1f4d83fedaf 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -115,7 +115,7 @@ private:
enum class GlobalAnimationKind {
GeneralFont = 0,
- TextFont,
+ DialogFont,
Cursor,
MortadeloIcon,
FilemonIcon,
@@ -137,10 +137,12 @@ public:
using RoomIterator = Common::Array<const Room *>::const_iterator;
inline RoomIterator beginRooms() const { return _rooms.begin(); }
inline RoomIterator endRooms() const { return _rooms.end(); }
- inline Room &globalRoom() const { return *_globalRoom; }
- inline Inventory &inventory() const { return *_inventory; }
- inline MainCharacter &filemon() const { return *_filemon; }
- inline MainCharacter &mortadelo() const { return *_mortadelo; }
+ inline Room &globalRoom() const { assert(_globalRoom != nullptr); return *_globalRoom; }
+ 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 Font &generalFont() const { assert(_generalFont != nullptr); return *_generalFont; }
+ inline Font &dialogFont() const { assert(_dialogFont != nullptr); return *_dialogFont; }
inline const Common::String &initScriptName() const { return _initScriptName; }
inline uint8 loadedMapCount() const { return _loadedMapCount; }
@@ -168,6 +170,7 @@ private:
Room *_globalRoom;
Inventory *_inventory;
MainCharacter *_filemon, *_mortadelo;
+ Common::ScopedPtr<Font> _generalFont, _dialogFont;
uint8 _loadedMapCount = 0;
};
Commit: 72b4c99bf3238ff9c0ed4809885742961317b36c
https://github.com/scummvm/scummvm/commit/72b4c99bf3238ff9c0ed4809885742961317b36c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Load and display localized names
Changed paths:
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 3f164c4b723..47ed70df3a0 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -229,7 +229,7 @@ void ShapeObject::onHoverEnd() {
void ShapeObject::onHoverUpdate() {
g_engine->drawQueue().add<TextDrawRequest>(
g_engine->world().generalFont(),
- name().c_str(),
+ g_engine->world().getLocalizedName(name()),
g_engine->input().mousePos2D() - Point(0, 35),
-1, true, kWhite, 0);
}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index bb7963eaf9d..9ef040a0d7e 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -619,7 +619,7 @@ static const byte *trimTrailing(const byte *text, const byte *begin) {
}
static Point characterSize(const Font &font, byte ch) {
- if (ch <= ' ' || ch >= font.imageCount())
+ if (ch <= ' ' || ch - ' ' >= font.imageCount())
ch = 0;
else
ch -= ' ';
@@ -716,7 +716,7 @@ void TextDrawRequest::draw() {
cursor.x = _posX[i];
for (auto ch : _lines[i]) {
const Point charSize = characterSize(_font, ch);
- if (ch > ' ' && ch < _font.imageCount())
+ if (ch > ' ' && ch - ' ' < _font.imageCount())
_font.drawCharacter(ch - ' ', Point(cursor.x, cursor.y), _color);
cursor.x += charSize.x;
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index b3580ec65a1..267a4de29cf 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -342,6 +342,7 @@ World::World() {
if (loadWorldFile(*itMapFile))
_loadedMapCount++;
}
+ loadLocalizedNames();
_globalRoom = getRoomByName("GLOBAL");
if (_globalRoom == nullptr)
@@ -498,4 +499,61 @@ bool World::loadWorldFile(const char *path) {
return true;
}
+/**
+ * @brief Behold the incredible encryption of text files:
+ * - first 32 bytes are cipher
+ * - next byte is the XOR key
+ * - next 4 bytes are garbage
+ * - rest of the file is cipher
+ */
+static void loadEncryptedFile(const char *path, 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() - 5 + 1);
+ 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;
+ output.back() = ' '; // one for good measure and a zero-terminator
+}
+
+static char *trimTrailing(char *start, char *end) {
+ while (start < end && isSpace(end[-1]))
+ end--;
+ return end;
+}
+
+void World::loadLocalizedNames() {
+ loadEncryptedFile("Textos/OBJETOS.nkr", _namesChunk);
+ char *lineStart = _namesChunk.begin(), *fileEnd = _namesChunk.end();
+ 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);
+ if (valueEnd == keyEnd + 1)
+ error("Invalid localized name value");
+
+ *keyEnd = 0;
+ *valueEnd = 0;
+ _localizedNames[lineStart] = keyEnd + 1;
+
+ lineStart = lineEnd + 1;
+ }
+}
+
+const char *World::getLocalizedName(const String &name) const {
+ const char *localizedName;
+ return _localizedNames.tryGetVal(name.c_str(), localizedName)
+ ? localizedName
+ : name.c_str();
+}
+
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 1f4d83fedaf..b87e0dbbb1e 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -158,11 +158,20 @@ public:
ObjectBase *getObjectByName(MainCharacterKind character, const Common::String &name) const;
ObjectBase *getObjectByNameFromAnyRoom(const Common::String &name) const;
const Common::String &getGlobalAnimationName(GlobalAnimationKind kind) const;
+ const char *getLocalizedName(const Common::String &name) const;
+ const char *getDialogLine(int32 dialogId) const;
void toggleObject(MainCharacterKind character, const Common::String &objName, bool isEnabled);
private:
bool loadWorldFile(const char *path);
+ void loadLocalizedNames();
+ void loadDialogLines();
+
+ // the default Hash<const char*> works on the characters, but the default EqualTo compares pointers...
+ struct StringEqualTo {
+ bool operator()(const char *a, const char *b) const { return strcmp(a, b) == 0; }
+ };
Common::Array<Room *> _rooms;
Common::String _globalAnimationNames[(int)GlobalAnimationKind::Count];
@@ -172,6 +181,11 @@ private:
MainCharacter *_filemon, *_mortadelo;
Common::ScopedPtr<Font> _generalFont, _dialogFont;
uint8 _loadedMapCount = 0;
+ Common::HashMap<const char *, const char *,
+ Common::Hash<const char*>,
+ StringEqualTo> _localizedNames;
+ Common::Array<const char *> _dialogLines;
+ Common::Array<char> _namesChunk, _dialogChunk; ///< holds the memory for localizedNames / dialogLines
};
}
Commit: 6938f1c87bfe32b8aaa3c4060f33310c3f0c0625
https://github.com/scummvm/scummvm/commit/6938f1c87bfe32b8aaa3c4060f33310c3f0c0625
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Load dialog lines
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 9ef040a0d7e..a442c982945 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -619,7 +619,7 @@ static const byte *trimTrailing(const byte *text, const byte *begin) {
}
static Point characterSize(const Font &font, byte ch) {
- if (ch <= ' ' || ch - ' ' >= font.imageCount())
+ if (ch <= ' ' || (uint)(ch - ' ') >= font.imageCount())
ch = 0;
else
ch -= ' ';
@@ -716,7 +716,7 @@ void TextDrawRequest::draw() {
cursor.x = _posX[i];
for (auto ch : _lines[i]) {
const Point charSize = characterSize(_font, ch);
- if (ch > ' ' && ch - ' ' < _font.imageCount())
+ if (ch > ' ' && (uint)(ch - ' ') < _font.imageCount())
_font.drawCharacter(ch - ' ', Point(cursor.x, cursor.y), _color);
cursor.x += charSize.x;
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 267a4de29cf..c2fedbd6e3f 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -343,6 +343,7 @@ World::World() {
_loadedMapCount++;
}
loadLocalizedNames();
+ loadDialogLines();
_globalRoom = getRoomByName("GLOBAL");
if (_globalRoom == nullptr)
@@ -448,6 +449,19 @@ const Common::String &World::getGlobalAnimationName(GlobalAnimationKind kind) co
return _globalAnimationNames[kindI];
}
+const char *World::getLocalizedName(const String &name) const {
+ const char *localizedName;
+ return _localizedNames.tryGetVal(name.c_str(), localizedName)
+ ? localizedName
+ : name.c_str();
+}
+
+const char *World::getDialogLine(int32 dialogId) const {
+ if (dialogId < 0 || (uint)dialogId >= _dialogLines.size())
+ error("Invalid dialog line index %d", dialogId);
+ return _dialogLines[dialogId];
+}
+
static Room *readRoom(World *world, ReadStream &stream) {
const auto type = readVarString(stream);
if (type == Room::kClassName)
@@ -544,16 +558,31 @@ void World::loadLocalizedNames() {
*keyEnd = 0;
*valueEnd = 0;
_localizedNames[lineStart] = keyEnd + 1;
-
lineStart = lineEnd + 1;
}
}
-const char *World::getLocalizedName(const String &name) const {
- const char *localizedName;
- return _localizedNames.tryGetVal(name.c_str(), localizedName)
- ? localizedName
- : name.c_str();
+void World::loadDialogLines() {
+ loadEncryptedFile("Textos/DIALOGOS.nkr", _dialogChunk);
+ char *lineStart = _dialogChunk.begin(), *fileEnd = _dialogChunk.end();
+ while (lineStart < fileEnd) {
+ char *lineEnd = find(lineStart, fileEnd, '\n');
+ char *firstQuote = find(lineStart, lineEnd, '\"');
+ if (firstQuote == lineEnd)
+ error("Invalid dialog line - first quote");
+ char *secondQuote = find(firstQuote + 1, lineEnd, '\"');
+ if (secondQuote == lineEnd) {
+ // unfortunately one invalid line in the game
+ if (_dialogLines.size() != 4542)
+ error("Invalid dialog line - second quote");
+ firstQuote = lineStart; // for the invalid one save an empty string
+ secondQuote = firstQuote + 1;
+ }
+
+ *secondQuote = 0;
+ _dialogLines.push_back(firstQuote + 1);
+ lineStart = lineEnd + 1;
+ }
}
}
Commit: b4a467c892adb22df6338b7d44badad3c3a4b318
https://github.com/scummvm/scummvm/commit/b4a467c892adb22df6338b7d44badad3c3a4b318
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Add voice sounds and sayText kernel task
and fix a lot of bugs in the process
Changed paths:
A engines/alcachofa/sounds.cpp
A engines/alcachofa/sounds.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index a43ebf7d16f..959f57a1c83 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -66,6 +66,8 @@ Common::Error AlcachofaEngine::run() {
_script.reset(new Script());
_player.reset(new Player());
+ _script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
+
_player->changeRoom("MINA", true);
Common::Event e;
@@ -77,6 +79,7 @@ Common::Error AlcachofaEngine::run() {
continue;
}
+ _sounds.update();
_renderer->begin();
_drawQueue->clear();
_camera.shake() = Vector2d();
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 117b6b4ddab..2d643c1c8de 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -37,6 +37,7 @@
#include "alcachofa/detection.h"
#include "alcachofa/camera.h"
#include "alcachofa/input.h"
+#include "alcachofa/sounds.h"
#include "alcachofa/player.h"
#include "alcachofa/scheduler.h"
#include "alcachofa/console.h"
@@ -64,6 +65,7 @@ public:
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
inline Input &input() { return _input; }
+ inline Sounds &sounds() { return _sounds; }
inline Player &player() { return *_player; }
inline World &world() { return *_world; }
inline Script &script() { return *_script; }
@@ -122,6 +124,7 @@ private:
Common::ScopedPtr<Player> _player;
Camera _camera;
Input _input;
+ Sounds _sounds;
Scheduler _scheduler;
};
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index d7051f7cbec..e8053f9097e 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -258,6 +258,59 @@ void Character::trigger(const char *action) {
g_engine->player().triggerObject(this, action);
}
+struct SayTextTask : public Task {
+ SayTextTask(Process &process, Character *character, int32 dialogId)
+ : Task(process)
+ , _character(character)
+ , _dialogId(dialogId) { }
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ _character->_isTalking = true;
+ graphicOf(_character->_curTalkingObject, &_character->_graphicTalking)->start(true);
+ while (true) {
+ if (_soundId == kInvalidSoundID)
+ _soundId = g_engine->sounds().playVoice(
+ String::format(_character == &g_engine->world().mortadelo() ? "M%04d" : "%04d", _dialogId),
+ 0);
+ g_engine->sounds().setAppropriateVolume(_soundId, process().character(), _character);
+ if (!g_engine->sounds().isAlive(_soundId) || g_engine->input().wasAnyMouseReleased())
+ _character->_isTalking = false;
+
+ if (true && // TODO: Add game option for subtitles
+ process().isActiveForPlayer()) {
+ g_engine->drawQueue().add<TextDrawRequest>(
+ g_engine->world().dialogFont(),
+ g_engine->world().getDialogLine(_dialogId),
+ Point(g_system->getWidth() / 2, g_system->getHeight() - 200),
+ -1, true, kWhite, 0);
+ }
+ // TODO: Add lip syng for sayText
+
+ if (!_character->_isTalking) {
+ g_engine->sounds().fadeOut(_soundId, 100);
+ TASK_WAIT(delay(200));
+ TASK_RETURN(0);
+ }
+ TASK_YIELD;
+ }
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ g_engine->console().debugPrintf("SayText %s, %d\n", _character->name().c_str(), _dialogId);
+ }
+
+private:
+ Character *_character;
+ int32 _dialogId;
+ SoundID _soundId = kInvalidSoundID;
+};
+
+Task *Character::sayText(Process &process, int32 dialogId) {
+ return new SayTextTask(process, this, dialogId);
+}
+
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index a442c982945..dfe32d14778 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -606,14 +606,14 @@ void SpecialEffectDrawRequest::draw() {
_animation->drawEffect(_frameI, _topLeft, _tiling, _texOffset, _blendMode);
}
-static const byte *trimLeading(const byte *text) {
- while (*text && *text <= ' ')
+static const byte *trimLeading(const byte *text, const byte *end) {
+ while (*text && text < end && *text <= ' ')
text++;
return text;
}
-static const byte *trimTrailing(const byte *text, const byte *begin) {
- while (text != begin && *text <= ' ')
+static const byte *trimTrailing(const byte *text, const byte *begin, bool trimSpaces) {
+ while (text != begin && (*text <= ' ') == trimSpaces)
text--;
return text;
}
@@ -643,7 +643,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
// split into trimmed lines
uint lineCount = 0;
- const byte *itChar = (byte*)text, *itLine = (byte*)text;
+ const byte *itChar = (byte *)text, *itLine = (byte *)text, *textEnd = itChar + textLen + 1;
int lineWidth = 0;
while (true) {
if (lineCount >= kMaxLines)
@@ -658,14 +658,17 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
// now we are in new-line territory
if (centered) {
- auto itLineEnd = trimTrailing(itChar, itLine);
- itLine = trimLeading(itLine);
- _allLines[lineCount] = TextLine(itLine, itLineEnd - itLine + 1);
- itChar = trimLeading(itChar);
+ if (*itChar > ' ')
+ itChar = trimTrailing(itChar, itLine, false); // trim last word
+ itChar = trimTrailing(itChar, itLine, true) + 1;
+ itLine = trimLeading(itLine, itChar);
+ _allLines[lineCount] = TextLine(itLine, itChar - itLine);
+ itChar = trimLeading(itChar, textEnd);
}
else
_allLines[lineCount] = TextLine(itLine, itChar - itLine);
lineCount++;
+ lineWidth = 0;
itLine = itChar;
if (!*itChar)
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 0d5f7e26636..24cd9928ecb 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -388,7 +388,10 @@ public:
virtual void trigger(const char *action) override;
virtual const char *typeName() const;
+ Task *sayText(Process &process, int32 dialogId);
+
protected:
+ friend struct SayTextTask;
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
void updateTalkingAnimation();
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index b87e0dbbb1e..96ff4da9a02 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -22,7 +22,7 @@
#ifndef ROOMS_H
#define ROOMS_H
-#include "Objects.h"
+#include "objects.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index c8776fa611c..e692e2c3a93 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -88,6 +88,10 @@ Process::~Process() {
delete _tasks.pop();
}
+bool Process::isActiveForPlayer() const {
+ return _character == MainCharacterKind::None || _character == g_engine->player().activeCharacterKind();
+}
+
TaskReturnType Process::run() {
while (!_tasks.empty()) {
TaskReturn ret = _tasks.top()->run();
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index a5317482bb9..22c335c3602 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -128,6 +128,7 @@ public:
inline MainCharacterKind character() const { return _character; }
inline int32 returnValue() const { return _lastReturnValue; }
inline Common::String &name() { return _name; }
+ bool isActiveForPlayer() const; ///< and thus should e.g. draw subtitles or effects
TaskReturnType run();
void debugPrint();
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 6760473afe2..a689a179648 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -244,20 +244,21 @@ struct ScriptTask : public Task {
case ScriptOp::Add:
pushNumber(popNumber() + popNumber());
break;
+ // flipped operators to not use a temporary
case ScriptOp::Sub:
- pushNumber(popNumber() - popNumber());
+ pushNumber(-popNumber() + popNumber());
break;
case ScriptOp::Less:
- pushNumber(popNumber() < popNumber());
+ pushNumber(popNumber() >= popNumber());
break;
case ScriptOp::Greater:
- pushNumber(popNumber() > popNumber());
+ pushNumber(popNumber() <= popNumber());
break;
case ScriptOp::LessEquals:
- pushNumber(popNumber() <= popNumber());
+ pushNumber(popNumber() >= popNumber());
break;
case ScriptOp::GreaterEquals:
- pushNumber(popNumber() >= popNumber());
+ pushNumber(popNumber() <= popNumber());
break;
case ScriptOp::Equals:
pushNumber(popNumber() == popNumber());
@@ -437,9 +438,20 @@ private:
case ScriptKernelTask::ChangeCharacter:
warning("STUB KERNEL CALL: ChangeCharacter");
return TaskReturn::finish(0);
- case ScriptKernelTask::SayText:
- warning("STUB KERNEL CALL: SayText");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::SayText: {
+ const char *characterName = getStringArg(0);
+ int32 dialogId = getNumberArg(1);
+ if (strncmp(characterName, "MENU_", 5) == 0) {
+ warning("STUB: adding dialog menu line %d", dialogId);
+ return TaskReturn::finish(1);
+ }
+ Character *_character = strcmp(characterName, "AMBOS") == 0
+ ? &g_engine->world().getMainCharacterByKind(process().character())
+ : dynamic_cast<Character *>(g_engine->world().getObjectByName(characterName));
+ if (_character == nullptr)
+ error("Invalid character for sayText: %s", characterName);
+ return TaskReturn::waitFor(_character->sayText(process(), dialogId));
+ };
case ScriptKernelTask::Go: {
auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
auto character = dynamic_cast<WalkingCharacter *>(characterObject);
@@ -632,7 +644,7 @@ private:
String _name;
uint32 _pc;
bool _returnsFromKernelCall = false;
- bool _isFirstExecution = false;
+ bool _isFirstExecution = true;
FakeLock _lock;
};
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
new file mode 100644
index 00000000000..57952a47b44
--- /dev/null
+++ b/engines/alcachofa/sounds.cpp
@@ -0,0 +1,180 @@
+/* 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 "sounds.h"
+#include "rooms.h"
+#include "alcachofa.h"
+
+#include "common/file.h"
+#include "common/substream.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+#include "audio/decoders/adpcm.h"
+#include "audio/decoders/raw.h"
+
+using namespace Common;
+using namespace Audio;
+
+namespace Alcachofa {
+
+Sounds::Playback::Playback(uint32 id, SoundHandle handle, Mixer::SoundType type)
+ : _id(id), _handle(handle), _type(type) {}
+
+Sounds::Sounds() : _mixer(g_system->getMixer()) {
+ assert(_mixer != nullptr);
+}
+
+Sounds::~Sounds() {
+ _mixer->stopAll();
+}
+
+Sounds::Playback *Sounds::getPlaybackById(SoundID id) {
+ if (_playbacks.empty())
+ return nullptr;
+ uint first = 0, last = _playbacks.size() - 1;
+ while (first < last) {
+ uint mid = (first + last) / 2;
+ if (_playbacks[mid]._id == id)
+ return _playbacks.data() + mid;
+ else if (_playbacks[mid]._id < id)
+ first = mid + 1;
+ else
+ last = mid == 0 ? 0 : mid - 1;
+ }
+ return first == last && first < _playbacks.size()
+ ? _playbacks.data() + first
+ : nullptr;
+}
+
+void Sounds::update() {
+ for (uint i = _playbacks.size(); i > 0; i--) {
+ Playback &playback = _playbacks[i - 1];
+ if (!_mixer->isSoundHandleActive(playback._handle))
+ _playbacks.erase(_playbacks.begin() + i - 1);
+ else if (playback._fadeDuration != 0) {
+ if (g_system->getMillis() >= playback._fadeStart + playback._fadeDuration) {
+ _mixer->stopHandle(playback._handle);
+ _playbacks.erase(_playbacks.begin() + i - 1);
+ }
+ else {
+ byte newVolume = (g_system->getMillis() - playback._fadeStart) * Mixer::kMaxChannelVolume / playback._fadeDuration;
+ _mixer->setChannelVolume(playback._handle, Mixer::kMaxChannelVolume - newVolume);
+ }
+ }
+ }
+}
+
+static AudioStream *loadSND(File *file) {
+ // 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");
+ uint16 format = file->readUint16LE();
+ uint16 channels = file->readUint16LE();
+ uint32 freq = file->readUint32LE();
+ file->skip(sizeof(uint32)); // bytesPerSecond, unnecessary for us
+ uint16 bytesPerBlock = file->readUint16LE();
+ uint16 bitsPerSample = file->readUint16LE();
+ if (endOfFormat >= 2 * sizeof(uint32) + 20) {
+ file->skip(sizeof(uint16)); // size of extra data
+ uint16 extra = file->readUint16LE();
+ bytesPerBlock = 4 * channels * ((extra + 14) / 8);
+ }
+ file->seek(endOfFormat, SEEK_SET);
+ auto subStream = new SeekableSubReadStream(file, (uint32)file->pos(), (uint32)file->size(), DisposeAfterUse::YES);
+ if (format == 1 && channels <= 2 && (bitsPerSample == 8 || bitsPerSample == 16))
+ return makeRawStream(subStream, (int)freq,
+ (channels == 2 ? FLAG_STEREO : 0) | (bitsPerSample == 16 ? FLAG_16BITS | FLAG_LITTLE_ENDIAN : FLAG_UNSIGNED));
+ else if (format == 17 && channels <= 2)
+ return makeADPCMStream(subStream, DisposeAfterUse::YES, 0, kADPCMMSIma, (int)freq, (int)channels, (uint32)bytesPerBlock);
+ error("Invalid SND file, format: %u, channels: %u, freq: %u, bps: %u", format, channels, freq, bitsPerSample);
+}
+
+static AudioStream *openAudio(const String &fileName) {
+ String path = String::format("Sonidos/%s.SND", fileName.c_str());
+ File *file = new File();
+ if (file->open(path.c_str()))
+ return 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;
+ error("Could not open audio file: %s", fileName.c_str());
+}
+
+SoundID Sounds::playVoice(const String &fileName, byte volume) {
+ AudioStream *stream = openAudio(fileName);
+ SoundHandle handle;
+ _mixer->playStream(Mixer::kSpeechSoundType, &handle, stream, -1, volume);
+ SoundID id = _nextID++;
+ _playbacks.push_back({ id, handle, Mixer::kSpeechSoundType });
+ return id;
+}
+
+void Sounds::stopVoice() {
+ for (uint i = _playbacks.size(); i > 0; i--) {
+ if (_playbacks[i - 1]._type == Mixer::kSpeechSoundType) {
+ _mixer->stopHandle(_playbacks[i - 1]._handle);
+ _playbacks.erase(_playbacks.begin() + i - 1);
+ }
+ }
+}
+
+bool Sounds::isAlive(SoundID id) {
+ Playback *playback = getPlaybackById(id);
+ return playback != nullptr && _mixer->isSoundHandleActive(playback->_handle);
+}
+
+void Sounds::setVolume(SoundID id, byte volume) {
+ Playback *playback = getPlaybackById(id);
+ if (playback != nullptr)
+ _mixer->setChannelVolume(playback->_handle, volume);
+}
+
+void Sounds::setAppropriateVolume(SoundID id,
+ MainCharacterKind processCharacter,
+ Character *speakingCharacter) {
+ static constexpr byte kAlmostMaxVolume = Mixer::kMaxChannelVolume * 9 / 10;
+
+ auto &player = g_engine->player();
+ byte newVolume;
+ if (player.activeCharacter() == nullptr || player.activeCharacter() == speakingCharacter)
+ newVolume = Mixer::kMaxChannelVolume;
+ else if (speakingCharacter != nullptr && speakingCharacter->room() == player.currentRoom())
+ newVolume = kAlmostMaxVolume;
+ else if (g_engine->world().getMainCharacterByKind(processCharacter).room() == player.currentRoom())
+ newVolume = kAlmostMaxVolume;
+ else
+ newVolume = 0;
+ setVolume(id, newVolume);
+}
+
+void Sounds::fadeOut(SoundID id, uint32 duration) {
+ Playback *playback = getPlaybackById(id);
+ if (playback != nullptr) {
+ playback->_fadeStart = g_system->getMillis();
+ playback->_fadeDuration = MAX<uint32>(duration, 1);
+ }
+}
+
+}
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
new file mode 100644
index 00000000000..2a2629d2da7
--- /dev/null
+++ b/engines/alcachofa/sounds.h
@@ -0,0 +1,68 @@
+/* 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/>.
+ *
+ */
+
+#ifndef SOUNDS_H
+#define SOUNDS_H
+
+#include "common.h"
+#include "audio/mixer.h"
+
+namespace Alcachofa {
+
+class Character;
+
+using SoundID = uint32;
+static constexpr SoundID kInvalidSoundID = 0;
+class Sounds {
+public:
+ Sounds();
+ ~Sounds();
+
+ void update();
+ SoundID playVoice(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
+ void stopVoice();
+ void fadeOut(SoundID id, uint32 duration);
+ bool isAlive(SoundID id);
+ void setVolume(SoundID id, byte volume);
+ void setAppropriateVolume(SoundID id,
+ MainCharacterKind processCharacter,
+ Character *speakingCharacter);
+
+private:
+ struct Playback {
+ Playback(uint32 id, Audio::SoundHandle handle, Audio::Mixer::SoundType type);
+
+ SoundID _id;
+ Audio::SoundHandle _handle;
+ Audio::Mixer::SoundType _type;
+ uint32 _fadeStart = 0,
+ _fadeDuration = 0;
+ };
+ Playback *getPlaybackById(SoundID id);
+
+ Common::Array<Playback> _playbacks;
+ Audio::Mixer *_mixer;
+ SoundID _nextID = 1;
+};
+
+}
+
+#endif // SOUNDS_H
Commit: fff9c7cc09a845dfacd3cb1793a7e86b7f7a2261
https://github.com/scummvm/scummvm/commit/fff9c7cc09a845dfacd3cb1793a7e86b7f7a2261
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Refactor stream-helper into common
Changed paths:
A engines/alcachofa/common.cpp
R engines/alcachofa/stream-helper.cpp
R engines/alcachofa/stream-helper.h
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/stream-helper.cpp b/engines/alcachofa/common.cpp
similarity index 66%
rename from engines/alcachofa/stream-helper.cpp
rename to engines/alcachofa/common.cpp
index f849549f19a..f8bf519936d 100644
--- a/engines/alcachofa/stream-helper.cpp
+++ b/engines/alcachofa/common.cpp
@@ -19,14 +19,57 @@
*
*/
-#include "stream-helper.h"
-
-#include "common/textconsole.h"
+#include "common.h"
using namespace Common;
+using namespace Math;
namespace Alcachofa {
+FakeSemaphore::FakeSemaphore(uint initialCount) : _counter(initialCount) {}
+
+FakeSemaphore::~FakeSemaphore() {
+ assert(_counter == 0);
+}
+
+FakeLock::FakeLock() : _semaphore(nullptr) {}
+
+FakeLock::FakeLock(FakeSemaphore &semaphore) : _semaphore(&semaphore) {
+ _semaphore->_counter++;
+}
+
+FakeLock::FakeLock(const FakeLock &other) : _semaphore(other._semaphore) {
+ assert(_semaphore != nullptr);
+ _semaphore->_counter++;
+}
+
+FakeLock::FakeLock(FakeLock &&other) noexcept : _semaphore(other._semaphore) {
+ other._semaphore = nullptr;
+}
+
+FakeLock::~FakeLock() {
+ if (_semaphore == nullptr)
+ return;
+ assert(_semaphore->_counter > 0);
+ _semaphore->_counter--;
+}
+
+Vector3d as3D(const Vector2d &v) {
+ return Vector3d(v.getX(), v.getY(), 0.0f);
+}
+
+Vector3d as3D(const Common::Point &p) {
+ return Vector3d((float)p.x, (float)p.y, 0.0f);
+}
+
+Vector2d as2D(const Vector3d &v) {
+ return Vector2d(v.x(), v.y());
+}
+
+Vector2d as2D(const Point &p) {
+ return Vector2d((float)p.x, (float)p.y);
+}
+
bool readBool(ReadStream &stream) {
return stream.readByte() != 0;
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index e8d83db85b8..cc1efc2f96b 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -24,6 +24,9 @@
#include "common/scummsys.h"
#include "common/rect.h"
+#include "common/serializer.h"
+#include "common/stream.h"
+#include "common/stack.h"
#include "math/vector2d.h"
#include "math/vector3d.h"
@@ -72,10 +75,8 @@ static constexpr const Color kDebugBlue = { 0, 0, 255, 110 };
* It is used as a safer option for a simple "isBusy" counter
*/
struct FakeSemaphore {
- FakeSemaphore(uint initialCount = 0) : _counter(initialCount) {}
- ~FakeSemaphore() {
- assert(_counter == 0);
- }
+ FakeSemaphore(uint initialCount = 0);
+ ~FakeSemaphore();
inline bool isReleased() const { return _counter == 0; }
inline uint counter() const { return _counter; }
@@ -85,45 +86,57 @@ private:
};
struct FakeLock {
- FakeLock() : _semaphore(nullptr) {}
-
- FakeLock(FakeSemaphore &semaphore) : _semaphore(&semaphore) {
- _semaphore->_counter++;
- }
-
- FakeLock(const FakeLock &other) : _semaphore(other._semaphore) {
- assert(_semaphore != nullptr);
- _semaphore->_counter++;
- }
-
- FakeLock(FakeLock &&other) noexcept : _semaphore(other._semaphore) {
- other._semaphore = nullptr;
- }
-
- ~FakeLock() {
- if (_semaphore == nullptr)
- return;
- assert(_semaphore->_counter > 0);
- _semaphore->_counter--;
- }
+ FakeLock();
+ FakeLock(FakeSemaphore &semaphore);
+ FakeLock(const FakeLock &other);
+ FakeLock(FakeLock &&other) noexcept;
+ ~FakeLock();
private:
FakeSemaphore *_semaphore;
};
-inline Math::Vector3d as3D(const Math::Vector2d &v) {
- return Math::Vector3d(v.getX(), v.getY(), 0.0f);
-}
-
-inline Math::Vector3d as3D(const Common::Point &p) {
- return Math::Vector3d((float)p.x, (float)p.y, 0.0f);
+Math::Vector3d as3D(const Math::Vector2d &v);
+Math::Vector3d as3D(const Common::Point &p);
+Math::Vector2d as2D(const Math::Vector3d &v);
+Math::Vector2d as2D(const Common::Point &p);
+
+bool readBool(Common::ReadStream &stream);
+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);
+
+template<typename T>
+inline void syncArray(Common::Serializer &serializer, Common::Array<T> &array, void (*serializeFunction)(Common::Serializer &, T &)) {
+ auto size = array.size();
+ serializer.syncAsUint32LE(size);
+ array.resize(size);
+ serializer.syncArray(array.data(), size, serializeFunction);
}
-inline Math::Vector2d as2D(const Math::Vector3d &v) {
- return Math::Vector2d(v.x(), v.y());
+template<typename T>
+inline void syncStack(Common::Serializer &serializer, Common::Stack<T> &stack, void (*serializeFunction)(Common::Serializer &, T &)) {
+ auto size = stack.size();
+ serializer.syncAsUint32LE(size);
+ if (serializer.isLoading()) {
+ for (uint i = 0; i < size; i++) {
+ T value;
+ serializeFunction(serializer, value);
+ stack.push(value);
+ }
+ }
+ else {
+ for (uint i = 0; i < size; i++)
+ serializeFunction(serializer, stack[i]);
+ }
}
-inline Math::Vector2d as2D(const Common::Point &p) {
- return Math::Vector2d((float)p.x, (float)p.y);
+template<typename T>
+inline void syncEnum(Common::Serializer &serializer, T &enumValue) {
+ // syncAs does not have a cast for saving
+ int32 intValue = static_cast<int32>(enumValue);
+ serializer.syncAsSint32LE(intValue);
+ enumValue = static_cast<T>(intValue);
}
}
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index e8053f9097e..2be6d72fc6c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -21,7 +21,6 @@
#include "objects.h"
#include "rooms.h"
-#include "stream-helper.h"
#include "alcachofa.h"
using namespace Common;
@@ -285,7 +284,7 @@ struct SayTextTask : public Task {
Point(g_system->getWidth() / 2, g_system->getHeight() - 200),
-1, true, kWhite, 0);
}
- // TODO: Add lip syng for sayText
+ // TODO: Add lip sync for sayText
if (!_character->_isTalking) {
g_engine->sounds().fadeOut(_soundId, 100);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 47ed70df3a0..cca8b6c27f4 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -22,7 +22,6 @@
#include "objects.h"
#include "rooms.h"
#include "scheduler.h"
-#include "stream-helper.h"
#include "alcachofa.h"
#include "common/system.h"
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index dfe32d14778..2b07ab58f07 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -20,7 +20,6 @@
*/
#include "graphics.h"
-#include "stream-helper.h"
#include "alcachofa.h"
#include "shape.h"
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c2fedbd6e3f..75855459e4d 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -22,7 +22,6 @@
#include "alcachofa.h"
#include "rooms.h"
#include "script.h"
-#include "stream-helper.h"
#include "common/file.h"
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index a689a179648..82e9b992acd 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -20,7 +20,6 @@
*/
#include "script.h"
-#include "stream-helper.h"
#include "rooms.h"
#include "alcachofa.h"
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 568917385b4..a136888af8b 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -20,7 +20,6 @@
*/
#include "shape.h"
-#include "stream-helper.h"
using namespace Common;
using namespace Math;
diff --git a/engines/alcachofa/stream-helper.h b/engines/alcachofa/stream-helper.h
deleted file mode 100644
index 7c41ca41376..00000000000
--- a/engines/alcachofa/stream-helper.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* 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/>.
- *
- */
-
-#ifndef STREAM_HELPER_H
-#define STREAM_HELPER_H
-
-#include "common/stream.h"
-#include "common/serializer.h"
-#include "common/rect.h"
-#include "common/stack.h"
-
-namespace Alcachofa {
-
-bool readBool(Common::ReadStream &stream);
-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);
-
-template<typename T>
-inline void syncArray(Common::Serializer &serializer, Common::Array<T> &array, void (*serializeFunction)(Common::Serializer &, T &)) {
- auto size = array.size();
- serializer.syncAsUint32LE(size);
- array.resize(size);
- serializer.syncArray(array.data(), size, serializeFunction);
-}
-
-template<typename T>
-inline void syncStack(Common::Serializer &serializer, Common::Stack<T> &stack, void (*serializeFunction)(Common::Serializer &, T &)) {
- auto size = stack.size();
- serializer.syncAsUint32LE(size);
- if (serializer.isLoading()) {
- for (uint i = 0; i < size; i++) {
- T value;
- serializeFunction(serializer, value);
- stack.push(value);
- }
- }
- else {
- for (uint i = 0; i < size; i++)
- serializeFunction(serializer, stack[i]);
- }
-}
-
-template<typename T>
-inline void syncEnum(Common::Serializer &serializer, T &enumValue) {
- // syncAs does not have a cast for saving
- int32 intValue = static_cast<int32>(enumValue);
- serializer.syncAsSint32LE(intValue);
- enumValue = static_cast<T>(intValue);
-}
-
-}
-
-#endif // STREAM_HELPER_H
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 1b78a192b04..926d960d96b 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -21,7 +21,6 @@
#include "objects.h"
#include "rooms.h"
-#include "stream-helper.h"
using namespace Common;
Commit: 114d88a87e1dc342cf251150ac437e157d57d0e2
https://github.com/scummvm/scummvm/commit/114d88a87e1dc342cf251150ac437e157d57d0e2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Add dialog menu kernel task
although with broken text blending
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 2be6d72fc6c..3a85ea4d9e5 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -282,7 +282,7 @@ struct SayTextTask : public Task {
g_engine->world().dialogFont(),
g_engine->world().getDialogLine(_dialogId),
Point(g_system->getWidth() / 2, g_system->getHeight() - 200),
- -1, true, kWhite, 0);
+ -1, true, kWhite, -kForegroundOrderCount);
}
// TODO: Add lip sync for sayText
@@ -706,7 +706,7 @@ void MainCharacter::drawInner() {
void syncDialogMenuLine(Serializer &serializer, DialogMenuLine &line) {
serializer.syncAsSint32LE(line._dialogId);
serializer.syncAsSint32LE(line._yPosition);
- serializer.syncAsSint32LE(line._returnId);
+ serializer.syncAsSint32LE(line._returnValue);
}
void MainCharacter::serializeSave(Serializer &serializer) {
@@ -722,7 +722,7 @@ void MainCharacter::serializeSave(Serializer &serializer) {
uint semaphoreCounter = _semaphore.counter();
serializer.syncAsSint32LE(semaphoreCounter);
_semaphore = FakeSemaphore(semaphoreCounter);
- syncArray(serializer, _dialogMenuLines, syncDialogMenuLine);
+ syncArray(serializer, _dialogLines, syncDialogMenuLine);
syncObjectAsString(serializer, _currentlyUsingObject);
for (auto *item : _items) {
@@ -799,6 +799,103 @@ bool MainCharacter::clearTargetIf(const ITriggerableObject *target) {
return false;
}
+struct DialogMenuTask : public Task {
+ DialogMenuTask(Process &process, MainCharacter *character)
+ : Task(process)
+ , _input(g_engine->input())
+ , _character(character) {}
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ layoutLines();
+ while (true) {
+ TASK_YIELD;
+ if (g_engine->player().activeCharacter() != _character)
+ continue;
+ g_engine->player().heldItem() = nullptr;
+ g_engine->player().drawCursor();
+
+ _clickedLineI = updateLines();
+ if (_clickedLineI != UINT_MAX) {
+ TASK_YIELD;
+ TASK_WAIT(_character->sayText(process(), _character->_dialogLines[_clickedLineI]._dialogId));
+ int32 returnValue = _character->_dialogLines[_clickedLineI]._returnValue;
+ _character->_dialogLines.clear();
+ TASK_RETURN(returnValue);
+ }
+ }
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ g_engine->console().debugPrintf("DialogMenu for %s with %u lines\n",
+ _character->name().c_str(), _character->_dialogLines.size());
+ }
+
+private:
+ static constexpr int kTextXOffset = 5;
+ static constexpr int kTextYOffset = 10;
+ inline int maxTextWidth() const {
+ return g_system->getWidth() - 2 * kTextXOffset;
+ }
+
+ void layoutLines() {
+ auto &lines = _character->_dialogLines;
+ for (auto &itLine : lines) {
+ // we reuse the draw request to measure the actual height without using it to actually draw
+ TextDrawRequest request(
+ g_engine->world().dialogFont(),
+ g_engine->world().getDialogLine(itLine._dialogId),
+ Point(kTextXOffset, 0), maxTextWidth(), false, kWhite, 2);
+ itLine._yPosition = request.size().y; // briefly storing line height
+ }
+
+ lines.back()._yPosition = g_system->getHeight() - kTextYOffset - lines.back()._yPosition;
+ for (uint i = lines.size() - 1; i > 0; i--)
+ lines[i - 1]._yPosition = lines[i]._yPosition - kTextYOffset - lines[i - 1]._yPosition;
+ }
+
+ uint updateLines() {
+ bool isSomethingHovered = false;
+ for (uint i = _character->_dialogLines.size(); i > 0; i--) {
+ auto &itLine = _character->_dialogLines[i - 1];
+ bool isHovered = !isSomethingHovered && _input.mousePos2D().y >= itLine._yPosition - kTextYOffset;
+ g_engine->drawQueue().add<TextDrawRequest>(
+ g_engine->world().dialogFont(),
+ g_engine->world().getDialogLine(itLine._dialogId),
+ Point(kTextXOffset + (isHovered * 20), itLine._yPosition),
+ maxTextWidth(), false, isHovered ? Color{ 255, 0, 0, 255 } : kWhite, -kForegroundOrderCount + 2);
+ isSomethingHovered = isSomethingHovered || isHovered;
+ if (isHovered && _input.wasMouseLeftReleased())
+ return i - 1;
+ }
+ return UINT_MAX;
+ }
+
+ Input &_input;
+ MainCharacter *_character;
+ uint _clickedLineI = UINT_MAX;
+};
+
+void MainCharacter::addDialogLine(int32 dialogId) {
+ assert(dialogId >= 0);
+ DialogMenuLine line;
+ line._dialogId = dialogId;
+ _dialogLines.push_back(line);
+}
+
+void MainCharacter::setLastDialogReturnValue(int32 returnValue) {
+ if (_dialogLines.empty())
+ error("Tried to set return value of non-existent dialog line");
+ _dialogLines.back()._returnValue = returnValue;
+}
+
+Task *MainCharacter::dialogMenu(Process &process) {
+ if (_dialogLines.empty())
+ error("Tried to open dialog menu without any lines set");
+ return new DialogMenuTask(process, this);
+}
+
const char *Background::typeName() const { return "Background"; }
Background::Background(Room *room, const String &animationFileName, int16 scale)
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 24cd9928ecb..84d281178b1 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -469,8 +469,8 @@ protected:
struct DialogMenuLine {
int32 _dialogId;
- int32 _yPosition;
- int32 _returnId;
+ int32 _yPosition = 0;
+ int32 _returnValue = 0;
};
class MainCharacter final : public WalkingCharacter {
@@ -488,6 +488,7 @@ public:
virtual void update() override;
virtual void draw() override;
virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual const char *typeName() const;
virtual void walkTo(
const Common::Point &target,
Direction endDirection = Direction::Invalid,
@@ -499,18 +500,21 @@ public:
bool hasItem(const Common::String &name) const;
void pickup(const Common::String &name, bool putInHand);
void drop(const Common::String &name);
- virtual const char *typeName() const;
+ void addDialogLine(int32 dialogId);
+ void setLastDialogReturnValue(int32 returnValue);
+ Task *dialogMenu(Process &process);
protected:
virtual void onArrived() override;
private:
friend class Inventory;
+ friend struct DialogMenuTask;
Item *getItemByName(const Common::String &name) const;
void drawInner();
Common::Array<Item *> _items;
- Common::Array<DialogMenuLine> _dialogMenuLines;
+ Common::Array<DialogMenuLine> _dialogLines;
ObjectBase *_currentlyUsingObject = nullptr;
MainCharacterKind _kind;
FakeSemaphore _semaphore;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 6639f8ef32d..2e094dba117 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -71,6 +71,10 @@ void Player::updateCursor() {
_cursorFrameI = 4;
}
+ drawCursor();
+}
+
+void Player::drawCursor() {
Point cursorPos = g_engine->input().mousePos2D();
if (_heldItem == nullptr)
g_engine->drawQueue().add<AnimationDrawRequest>(_cursorAnimation.get(), _cursorFrameI, as2D(cursorPos), -10);
@@ -81,7 +85,7 @@ void Player::updateCursor() {
auto frameOffset = animation.totalFrameOffset(0);
auto imageSize = animation.imageSize(animation.imageIndex(0, 0));
cursorPos -= frameOffset + imageSize / 2;
- g_engine->drawQueue().add<AnimationDrawRequest>(&animation, 0, as2D(cursorPos), -10);
+ g_engine->drawQueue().add<AnimationDrawRequest>(&animation, 0, as2D(cursorPos), -kForegroundOrderCount);
}
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 9b6d9ea79aa..4841a448609 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -50,6 +50,7 @@ public:
void preUpdate();
void postUpdate();
void updateCursor();
+ void drawCursor();
void changeRoom(const Common::String &targetRoomName, bool resetCamera);
void triggerObject(ObjectBase *object, const char *action);
void triggerDoor(const Door *door);
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 22c335c3602..67984040254 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -87,6 +87,7 @@ private:
#endif
#define TASK_BEGIN \
+ enum { TASK_COUNTER_BASE = __COUNTER__ }; \
switch(_line) { \
case 0:; \
@@ -96,21 +97,17 @@ private:
default: assert(false && "Invalid line in task"); \
} return TaskReturn::finish(0)
-#define TASK_YIELD \
+#define TASK_INTERNAL_BREAK(ret) \
do { \
- _line = __LINE__; \
- return TaskReturn::yield(); \
+ enum { TASK_COUNTER = __COUNTER__ - TASK_COUNTER_BASE }; \
+ _line = TASK_COUNTER; \
+ return ret; \
TASK_BREAK_FALLTHROUGH \
- case __LINE__:; \
+ case TASK_COUNTER:; \
} while(0)
-#define TASK_WAIT(task) \
- do { \
- _line = __LINE__; \
- return TaskReturn::waitFor(task); \
- TASK_BREAK_FALLTHROUGH \
- case __LINE__:; \
- } while(0)
+#define TASK_YIELD TASK_INTERNAL_BREAK(TaskReturn::yield())
+#define TASK_WAIT(task) TASK_INTERNAL_BREAK(TaskReturn::waitFor(task))
#define TASK_RETURN(value) \
do { \
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 82e9b992acd..ba74a95d630 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -441,7 +441,7 @@ private:
const char *characterName = getStringArg(0);
int32 dialogId = getNumberArg(1);
if (strncmp(characterName, "MENU_", 5) == 0) {
- warning("STUB: adding dialog menu line %d", dialogId);
+ g_engine->world().getMainCharacterByKind(process().character()).addDialogLine(dialogId);
return TaskReturn::finish(1);
}
Character *_character = strcmp(characterName, "AMBOS") == 0
@@ -552,11 +552,10 @@ private:
g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
return TaskReturn::finish(1);
case ScriptKernelTask::SetDialogLineReturn:
- warning("STUB KERNEL CALL: SetDialogLineReturn");
+ g_engine->world().getMainCharacterByKind(process().character()).setLastDialogReturnValue(getNumberArg(0));
return TaskReturn::finish(0);
case ScriptKernelTask::DialogMenu:
- warning("STUB KERNEL CALL: DialogMenu");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(g_engine->world().getMainCharacterByKind(process().character()).dialogMenu(process()));
case ScriptKernelTask::ClearInventory:
switch((MainCharacterKind)getNumberArg(0)) {
case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory(); break;
Commit: e430e6d46cfa159762876f7045d7d0bd440695d7
https://github.com/scummvm/scummvm/commit/e430e6d46cfa159762876f7045d7d0bd440695d7
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:47+02:00
Commit Message:
ALCACHOFA: Fix text blending mode
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics-opengl.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 3a85ea4d9e5..a8607d2b872 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -863,8 +863,8 @@ private:
g_engine->drawQueue().add<TextDrawRequest>(
g_engine->world().dialogFont(),
g_engine->world().getDialogLine(itLine._dialogId),
- Point(kTextXOffset + (isHovered * 20), itLine._yPosition),
- maxTextWidth(), false, isHovered ? Color{ 255, 0, 0, 255 } : kWhite, -kForegroundOrderCount + 2);
+ Point(kTextXOffset, itLine._yPosition),
+ maxTextWidth(), false, isHovered ? Color{ 255, 255, 128, 255 } : kWhite, -kForegroundOrderCount + 2);
isSomethingHovered = isSomethingHovered || isHovered;
if (isHovered && _input.wasMouseLeftReleased())
return i - 1;
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 56062fb74fa..823f7f5f868 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -125,8 +125,8 @@ public:
GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PRIMARY_COLOR));
_currentLodBias = -1000.0f;
_currentTexture = nullptr;
_currentBlendMode = (BlendMode)-1;
@@ -183,7 +183,7 @@ public:
/** now the texture stage, mind that this always applies:
* SRC0_RGB is TEXTURE
- * SRC1_RGB/ALPHA is CONSTANT
+ * SRC1_RGB/ALPHA is PRIMARY COLOR
* COMBINE_ALPHA is REPLACE
*/
switch (blendMode) {
@@ -248,7 +248,8 @@ public:
float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
- GL_CALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
+ //GL_CALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
+ GL_CALL(glColor4f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f));
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
if (_currentTexture != nullptr)
GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
Commit: 75a8d917aa08526ccda714c40ae4d98c03235dc0
https://github.com/scummvm/scummvm/commit/75a8d917aa08526ccda714c40ae4d98c03235dc0
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add camera lerp kernel tasks
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 13f309bdb81..c6719fef539 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -57,8 +57,11 @@ void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
}
void Camera::setPosition(Vector2d v) {
- _usedCenter.x() = v.getX();
- _usedCenter.y() = v.getY();
+ setPosition({ v.getX(), v.getY(), _usedCenter.z() });
+}
+
+void Camera::setPosition(Vector3d v) {
+ _usedCenter = v;
setFollow(nullptr);
}
@@ -212,4 +215,173 @@ void Camera::updateFollowing(float deltaTime) {
}
}
+struct CamLerpTask : public Task {
+ CamLerpTask(Process &process, uint32 duration, EasingType easingType)
+ : Task(process)
+ , _camera(g_engine->camera())
+ , _duration(duration)
+ , _easingType(easingType) {}
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ _startTime = g_system->getMillis();
+ while (g_system->getMillis() - _startTime < _duration) {
+ update(ease((g_system->getMillis() - _startTime) / (float)_duration, _easingType));
+ _camera._isChanging = true;
+ TASK_YIELD;
+ }
+ update(1.0f);
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ uint32 remaining = g_system->getMillis() - _startTime <= _duration
+ ? _duration - (g_system->getMillis() - _startTime)
+ : 0;
+ g_engine->console().debugPrintf("Lerp camera with %ums remaining\n", remaining);
+ }
+
+protected:
+ virtual void update(float t) = 0;
+
+ Camera &_camera;
+ uint32 _startTime = 0, _duration;
+ EasingType _easingType;
+};
+
+struct CamLerpPosTask final : public CamLerpTask {
+ CamLerpPosTask(Process &process, Vector3d targetPos, int32 duration, EasingType easingType)
+ : CamLerpTask(process, duration, easingType)
+ , _fromPos(_camera._appliedCenter)
+ , _deltaPos(targetPos - _camera._appliedCenter) {}
+
+protected:
+ virtual void update(float t) override {
+ _camera.setPosition(_fromPos + _deltaPos * t);
+ }
+
+ Vector3d _fromPos, _deltaPos;
+};
+
+struct CamLerpScaleTask final : public CamLerpTask {
+ CamLerpScaleTask(Process &process, float targetScale, int32 duration, EasingType easingType)
+ : CamLerpTask(process, duration, easingType)
+ , _fromScale(_camera._scale)
+ , _deltaScale(targetScale - _camera._scale) {}
+
+protected:
+ virtual void update(float t) override {
+ _camera._scale = _fromScale + _deltaScale * t;
+ }
+
+ float _fromScale, _deltaScale;
+};
+
+struct CamLerpPosScaleTask final : public CamLerpTask {
+ CamLerpPosScaleTask(Process &process, Vector3d targetPos, float targetScale, int32 duration, EasingType easingType)
+ : CamLerpTask(process, duration, easingType)
+ , _fromPos(_camera._appliedCenter)
+ , _deltaPos(targetPos - _camera._appliedCenter)
+ , _fromScale(_camera._scale)
+ , _deltaScale(targetScale - _camera._scale) {}
+
+protected:
+ virtual void update(float t) override {
+ _camera.setPosition(_fromPos + _deltaPos * t);
+ _camera._scale = _fromScale + _deltaScale * t;
+ }
+
+ Vector3d _fromPos, _deltaPos;
+ float _fromScale, _deltaScale;
+};
+
+struct CamLerpRotationTask final : public CamLerpTask {
+ CamLerpRotationTask(Process &process, float targetRotation, int32 duration, EasingType easingType)
+ : CamLerpTask(process, duration, easingType)
+ , _fromRotation(_camera._rotation.getDegrees())
+ , _deltaRotation(targetRotation - _camera._rotation.getDegrees()) {}
+
+protected:
+ virtual void update(float t) override {
+ _camera._rotation = Angle(_fromRotation + _deltaRotation * t);
+ }
+
+ float _fromRotation, _deltaRotation;
+};
+
+struct CamWaitToStopTask final : public Task {
+ CamWaitToStopTask(Process &process)
+ : Task(process)
+ , _camera(g_engine->camera()) {}
+
+ virtual TaskReturn run() override {
+ return _camera._isChanging
+ ? TaskReturn::yield()
+ : TaskReturn::finish(1);
+ }
+
+ virtual void debugPrint() override {
+ g_engine->console().debugPrintf("Wait for camera to stop moving\n");
+ }
+
+private:
+ Camera &_camera;
+};
+
+Task *Camera::lerpPos(Process &process, Vector2d targetPos, int32 duration, EasingType easingType) {
+ if (!process.isActiveForPlayer()) {
+ warning("stub: non-active camera lerp script invoked");
+ return new DelayTask(process, duration);
+ }
+ Vector3d targetPos3d(targetPos.getX(), targetPos.getY(), _appliedCenter.z());
+ return new CamLerpPosTask(process, targetPos3d, duration, easingType);
+}
+
+Task *Camera::lerpPos(Process &process, Vector3d targetPos, int32 duration, EasingType easingType) {
+ if (!process.isActiveForPlayer()) {
+ warning("stub: non-active camera lerp script invoked");
+ return new DelayTask(process, duration);
+ }
+ setFollow(nullptr); // 3D position lerping is the only task that resets following
+ return new CamLerpPosTask(process, targetPos, duration, easingType);
+}
+
+Task *Camera::lerpPosZ(Process &process, float targetPosZ, int32 duration, EasingType easingType) {
+ if (!process.isActiveForPlayer()) {
+ warning("stub: non-active camera lerp script invoked");
+ return new DelayTask(process, duration);
+ }
+ Vector3d targetPos(_appliedCenter.x(), _appliedCenter.y(), targetPosZ);
+ return new CamLerpPosTask(process, targetPos, duration, easingType);
+}
+
+Task *Camera::lerpScale(Process &process, float targetScale, int32 duration, EasingType easingType) {
+ if (!process.isActiveForPlayer()) {
+ warning("stub: non-active camera lerp script invoked");
+ return new DelayTask(process, duration);
+ }
+ return new CamLerpScaleTask(process, targetScale, duration, easingType);
+}
+
+Task *Camera::lerpRotation(Process &process, float targetRotation, int32 duration, EasingType easingType) {
+ if (!process.isActiveForPlayer()) {
+ warning("stub: non-active camera lerp script invoked");
+ return new DelayTask(process, duration);
+ }
+ return new CamLerpRotationTask(process, targetRotation, duration, easingType);
+}
+
+Task *Camera::lerpPosScale(Process &process, Vector2d targetPos, float targetScale, int32 duration, EasingType easingType) {
+ if (!process.isActiveForPlayer()) {
+ warning("stub: non-active camera lerp script invoked");
+ return new DelayTask(process, duration);
+ }
+ Vector3d targetPos3d(targetPos.getX(), targetPos.getY(), _appliedCenter.z());
+ return new CamLerpPosScaleTask(process, targetPos3d, targetScale, duration, easingType);
+}
+
+Task *Camera::waitToStop(Process &process) {
+ return new CamWaitToStopTask(process);
+}
+
}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index ac04907493f..c8573673972 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -22,10 +22,7 @@
#ifndef CAMERA_H
#define CAMERA_H
-#include "common/serializer.h"
-#include "common/rect.h"
-#include "math/vector2d.h"
-#include "math/vector3d.h"
+#include "common.h"
#include "math/matrix4.h"
namespace Alcachofa {
@@ -34,7 +31,7 @@ class WalkingCharacter;
class Process;
struct Task;
-static constexpr const int16_t kBaseScale = 300; ///< this number pops up everywhere in the engine
+static constexpr const int16_t kBaseScale = 300;
static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
class Camera {
@@ -50,8 +47,25 @@ public:
void setRoomBounds(Common::Point bgSize, int16 bgScale);
void setFollow(WalkingCharacter *target, bool catchUp = false);
void setPosition(Math::Vector2d v);
+ void setPosition(Math::Vector3d v);
+
+ Task *lerpPos(Process &process, Math::Vector2d targetPos, int32 duration, EasingType easingType);
+ Task *lerpPos(Process &process, Math::Vector3d targetPos, int32 duration, EasingType easingType);
+ Task *lerpPosZ(Process &process, float targetPosZ, int32 duration, EasingType easingType);
+ Task *lerpScale(Process &process, float targetScale, int32 duration, EasingType easingType);
+ Task *lerpRotation(Process &process, float targetRotation, int32 duration, EasingType easingType);
+ Task *lerpPosScale(Process &process, Math::Vector2d targetPos, float targetScale, int32 duration, EasingType easingType);
+ //Task *shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration);
+ Task *waitToStop(Process &process);
private:
+ friend struct CamLerpTask;
+ friend struct CamLerpPosTask;
+ friend struct CamLerpScaleTask;
+ friend struct CamLerpPosScaleTask;
+ friend struct CamLerpRotationTask;
+ //friend struct CamShakeTask;
+ friend struct CamWaitToStopTask;
Math::Vector3d setAppliedCenter(Math::Vector3d center);
void setupMatricesAround(Math::Vector3d center);
void updateFollowing(float deltaTime);
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index f8bf519936d..3f7afe1cc31 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -26,6 +26,16 @@ using namespace Math;
namespace Alcachofa {
+float ease(float t, EasingType type) {
+ switch (type) {
+ case EasingType::Linear: return t;
+ case EasingType::InOut: return (1 - cosf(t * M_PI)) * 0.5f;
+ case EasingType::In: return 1 - cosf(t * M_PI * 0.5f);
+ case EasingType::Out: return sinf(t * M_PI * 0.5f);
+ default: return 0.0f;
+ }
+}
+
FakeSemaphore::FakeSemaphore(uint initialCount) : _counter(initialCount) {}
FakeSemaphore::~FakeSemaphore() {
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index cc1efc2f96b..783d6dbde86 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -56,6 +56,13 @@ enum class MainCharacterKind {
Filemon
};
+enum class EasingType {
+ Linear,
+ InOut,
+ In,
+ Out
+};
+
constexpr const int32 kDirectionCount = 4;
constexpr const int8 kOrderCount = 70;
constexpr const int8 kForegroundOrderCount = 10;
@@ -95,6 +102,8 @@ private:
FakeSemaphore *_semaphore;
};
+float ease(float t, EasingType type);
+
Math::Vector3d as3D(const Math::Vector2d &v);
Math::Vector3d as3D(const Common::Point &p);
Math::Vector2d as2D(const Math::Vector3d &v);
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index e692e2c3a93..61a7e1b783a 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -28,28 +28,6 @@ using namespace Common;
namespace Alcachofa {
-struct DelayTask : public Task {
- DelayTask(Process &process, uint32 millis)
- : Task(process)
- , _endTime(millis) {}
-
- virtual TaskReturn run() override {
- TASK_BEGIN;
- _endTime += g_system->getMillis();
- while (g_system->getMillis() < _endTime)
- TASK_YIELD;
- TASK_END;
- }
-
- virtual void debugPrint() {
- uint32 remaining = g_system->getMillis() <= _endTime ? _endTime - g_system->getMillis() : 0;
- g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
- }
-
-private:
- uint32 _endTime;
-};
-
TaskReturn::TaskReturn() {
_type = TaskReturnType::Yield;
_returnValue = 0;
@@ -77,6 +55,23 @@ Task *Task::delay(uint32 millis) {
return new DelayTask(process(), millis);
}
+DelayTask::DelayTask(Process &process, uint32 millis)
+ : Task(process)
+ , _endTime(millis) {}
+
+TaskReturn DelayTask::run(){
+ TASK_BEGIN;
+ _endTime += g_system->getMillis();
+ while (g_system->getMillis() < _endTime)
+ TASK_YIELD;
+ TASK_END;
+}
+
+void DelayTask::debugPrint() {
+ uint32 remaining = g_system->getMillis() <= _endTime ? _endTime - g_system->getMillis() : 0;
+ g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
+}
+
Process::Process(ProcessId pid, MainCharacterKind characterKind)
: _pid(pid)
, _character(characterKind)
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 67984040254..9ea21f9d415 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -79,6 +79,15 @@ private:
Process &_process;
};
+struct DelayTask : public Task {
+ DelayTask(Process &process, uint32 millis);
+ virtual TaskReturn run() override;
+ virtual void debugPrint() override;
+
+private:
+ uint32 _endTime;
+};
+
// TODO: This probably should be scummvm common
#if __cplusplus >= 201703L
#define TASK_BREAK_FALLTHROUGH [[fallthrough]];
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index ba74a95d630..01b2c10ea52 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -26,6 +26,7 @@
#include "common/file.h"
using namespace Common;
+using namespace Math;
namespace Alcachofa {
@@ -563,24 +564,16 @@ private:
default: error("Script attempted to clear inventory with invalid character kind"); break;
}
return TaskReturn::finish(1);
- case ScriptKernelTask::FadeType0:
- warning("STUB KERNEL CALL: FadeType0");
- return TaskReturn::finish(0);
- case ScriptKernelTask::FadeType1:
- warning("STUB KERNEL CALL: FadeType1");
- return TaskReturn::finish(0);
case ScriptKernelTask::LerpWorldLodBias:
warning("STUB KERNEL CALL: LerpWorldLodBias");
return TaskReturn::finish(0);
- case ScriptKernelTask::FadeType2:
- warning("STUB KERNEL CALL: FadeType2");
- return TaskReturn::finish(0);
+
+ // Camera tasks
case ScriptKernelTask::SetMaxCamSpeedFactor:
warning("STUB KERNEL CALL: SetMaxCamSpeedFactor");
return TaskReturn::finish(0);
case ScriptKernelTask::WaitCamStopping:
- warning("STUB KERNEL CALL: WaitCamStopping");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
case ScriptKernelTask::CamFollow:
warning("STUB KERNEL CALL: CamFollow");
return TaskReturn::finish(0);
@@ -588,22 +581,44 @@ private:
warning("STUB KERNEL CALL: CamShake");
return TaskReturn::finish(0);
case ScriptKernelTask::LerpCamXY:
- warning("STUB KERNEL CALL: LerpCamXY");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
+ Vector2d(getNumberArg(0), getNumberArg(1)),
+ getNumberArg(2), (EasingType)getNumberArg(3)));
+ case ScriptKernelTask::LerpCamXYZ:
+ return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
+ Vector3d(getNumberArg(0), getNumberArg(1), getNumberArg(2)),
+ getNumberArg(3), (EasingType)getNumberArg(4)));
case ScriptKernelTask::LerpCamZ:
- warning("STUB KERNEL CALL: LerpCamZ");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(g_engine->camera().lerpPosZ(process(),
+ getNumberArg(0),
+ getNumberArg(1), (EasingType)getNumberArg(2)));
case ScriptKernelTask::LerpCamScale:
- warning("STUB KERNEL CALL: LerpCamScale");
+ return TaskReturn::waitFor(g_engine->camera().lerpScale(process(),
+ getNumberArg(0) * 0.01f,
+ getNumberArg(1), (EasingType)getNumberArg(2)));
+ case ScriptKernelTask::LerpCamRotation:
+ return TaskReturn::waitFor(g_engine->camera().lerpRotation(process(),
+ getNumberArg(0),
+ getNumberArg(1), (EasingType)getNumberArg(2)));
+ case ScriptKernelTask::LerpCamToObjectKeepingZ:
+ warning("STUB KERNEL CALL: LerpCamToObjectKeepingZ");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamToObjectResettingZ:
+ warning("STUB KERNEL CALL: LerpCamToObjectResettingZ");
return TaskReturn::finish(0);
case ScriptKernelTask::LerpCamToObjectWithScale:
warning("STUB KERNEL CALL: LerpCamToObjectWithScale");
return TaskReturn::finish(0);
- case ScriptKernelTask::LerpCamToObjectResettingZ:
- warning("STUB KERNEL CALL: LerpCamToObjectResettingZ");
+
+ // Fades
+ case ScriptKernelTask::FadeType0:
+ warning("STUB KERNEL CALL: FadeType0");
return TaskReturn::finish(0);
- case ScriptKernelTask::LerpCamRotation:
- warning("STUB KERNEL CALL: LerpCamRotation");
+ case ScriptKernelTask::FadeType1:
+ warning("STUB KERNEL CALL: FadeType1");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeType2:
+ warning("STUB KERNEL CALL: FadeType2");
return TaskReturn::finish(0);
case ScriptKernelTask::FadeIn:
warning("STUB KERNEL CALL: FadeIn");
@@ -617,12 +632,7 @@ private:
case ScriptKernelTask::FadeOut2:
warning("STUB KERNEL CALL: FadeOut2");
return TaskReturn::finish(0);
- case ScriptKernelTask::LerpCamXYZ:
- warning("STUB KERNEL CALL: LerpCamXYZ");
- return TaskReturn::finish(0);
- case ScriptKernelTask::LerpCamToObjectKeepingZ:
- warning("STUB KERNEL CALL: LerpCamToObjectKeepingZ");
- return TaskReturn::finish(0);
+
case ScriptKernelTask::SetActiveTextureSet:
// Fortunately this seems to be unused.
warning("STUB KERNEL CALL: SetActiveTextureSet");
Commit: 742c5848854daa5235a7377d38b504f4e304cd4c
https://github.com/scummvm/scummvm/commit/742c5848854daa5235a7377d38b504f4e304cd4c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add LerpCameraToObject kernel tasks
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index c6719fef539..5dd3f2b2bcb 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -278,21 +278,26 @@ protected:
};
struct CamLerpPosScaleTask final : public CamLerpTask {
- CamLerpPosScaleTask(Process &process, Vector3d targetPos, float targetScale, int32 duration, EasingType easingType)
- : CamLerpTask(process, duration, easingType)
+ CamLerpPosScaleTask(Process &process,
+ Vector3d targetPos, float targetScale,
+ int32 duration, EasingType moveEasingType, EasingType scaleEasingType)
+ : CamLerpTask(process, duration, EasingType::Linear) // linear as we need different ones per component
, _fromPos(_camera._appliedCenter)
, _deltaPos(targetPos - _camera._appliedCenter)
, _fromScale(_camera._scale)
- , _deltaScale(targetScale - _camera._scale) {}
+ , _deltaScale(targetScale - _camera._scale)
+ , _moveEasingType(moveEasingType)
+ , _scaleEasingType(scaleEasingType) {}
protected:
virtual void update(float t) override {
- _camera.setPosition(_fromPos + _deltaPos * t);
- _camera._scale = _fromScale + _deltaScale * t;
+ _camera.setPosition(_fromPos + _deltaPos * ease(t, _moveEasingType));
+ _camera._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
}
Vector3d _fromPos, _deltaPos;
float _fromScale, _deltaScale;
+ EasingType _moveEasingType, _scaleEasingType;
};
struct CamLerpRotationTask final : public CamLerpTask {
@@ -328,7 +333,9 @@ private:
Camera &_camera;
};
-Task *Camera::lerpPos(Process &process, Vector2d targetPos, int32 duration, EasingType easingType) {
+Task *Camera::lerpPos(Process &process,
+ Vector2d targetPos,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -337,7 +344,9 @@ Task *Camera::lerpPos(Process &process, Vector2d targetPos, int32 duration, Easi
return new CamLerpPosTask(process, targetPos3d, duration, easingType);
}
-Task *Camera::lerpPos(Process &process, Vector3d targetPos, int32 duration, EasingType easingType) {
+Task *Camera::lerpPos(Process &process,
+ Vector3d targetPos,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -346,7 +355,9 @@ Task *Camera::lerpPos(Process &process, Vector3d targetPos, int32 duration, Easi
return new CamLerpPosTask(process, targetPos, duration, easingType);
}
-Task *Camera::lerpPosZ(Process &process, float targetPosZ, int32 duration, EasingType easingType) {
+Task *Camera::lerpPosZ(Process &process,
+ float targetPosZ,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -355,7 +366,9 @@ Task *Camera::lerpPosZ(Process &process, float targetPosZ, int32 duration, Easin
return new CamLerpPosTask(process, targetPos, duration, easingType);
}
-Task *Camera::lerpScale(Process &process, float targetScale, int32 duration, EasingType easingType) {
+Task *Camera::lerpScale(Process &process,
+ float targetScale,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -363,7 +376,9 @@ Task *Camera::lerpScale(Process &process, float targetScale, int32 duration, Eas
return new CamLerpScaleTask(process, targetScale, duration, easingType);
}
-Task *Camera::lerpRotation(Process &process, float targetRotation, int32 duration, EasingType easingType) {
+Task *Camera::lerpRotation(Process &process,
+ float targetRotation,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -371,13 +386,14 @@ Task *Camera::lerpRotation(Process &process, float targetRotation, int32 duratio
return new CamLerpRotationTask(process, targetRotation, duration, easingType);
}
-Task *Camera::lerpPosScale(Process &process, Vector2d targetPos, float targetScale, int32 duration, EasingType easingType) {
+Task *Camera::lerpPosScale(Process &process,
+ Vector3d targetPos, float targetScale,
+ int32 duration, EasingType moveEasingType, EasingType scaleEasingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
}
- Vector3d targetPos3d(targetPos.getX(), targetPos.getY(), _appliedCenter.z());
- return new CamLerpPosScaleTask(process, targetPos3d, targetScale, duration, easingType);
+ return new CamLerpPosScaleTask(process, targetPos, targetScale, duration, moveEasingType, scaleEasingType);
}
Task *Camera::waitToStop(Process &process) {
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index c8573673972..995521b7bd8 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -49,12 +49,24 @@ public:
void setPosition(Math::Vector2d v);
void setPosition(Math::Vector3d v);
- Task *lerpPos(Process &process, Math::Vector2d targetPos, int32 duration, EasingType easingType);
- Task *lerpPos(Process &process, Math::Vector3d targetPos, int32 duration, EasingType easingType);
- Task *lerpPosZ(Process &process, float targetPosZ, int32 duration, EasingType easingType);
- Task *lerpScale(Process &process, float targetScale, int32 duration, EasingType easingType);
- Task *lerpRotation(Process &process, float targetRotation, int32 duration, EasingType easingType);
- Task *lerpPosScale(Process &process, Math::Vector2d targetPos, float targetScale, int32 duration, EasingType easingType);
+ Task *lerpPos(Process &process,
+ Math::Vector2d targetPos,
+ int32 duration, EasingType easingType);
+ Task *lerpPos(Process &process,
+ Math::Vector3d targetPos,
+ int32 duration, EasingType easingType);
+ Task *lerpPosZ(Process &process,
+ float targetPosZ,
+ int32 duration, EasingType easingType);
+ Task *lerpScale(Process &process,
+ float targetScale,
+ int32 duration, EasingType easingType);
+ Task *lerpRotation(Process &process,
+ float targetRotation,
+ int32 duration, EasingType easingType);
+ Task *lerpPosScale(Process &process,
+ Math::Vector3d targetPos, float targetScale,
+ int32 duration, EasingType moveEasingType, EasingType scaleEasingType);
//Task *shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration);
Task *waitToStop(Process &process);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 01b2c10ea52..061dca0e653 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -600,15 +600,39 @@ private:
return TaskReturn::waitFor(g_engine->camera().lerpRotation(process(),
getNumberArg(0),
getNumberArg(1), (EasingType)getNumberArg(2)));
- case ScriptKernelTask::LerpCamToObjectKeepingZ:
- warning("STUB KERNEL CALL: LerpCamToObjectKeepingZ");
- return TaskReturn::finish(0);
- case ScriptKernelTask::LerpCamToObjectResettingZ:
- warning("STUB KERNEL CALL: LerpCamToObjectResettingZ");
- return TaskReturn::finish(0);
- case ScriptKernelTask::LerpCamToObjectWithScale:
- warning("STUB KERNEL CALL: LerpCamToObjectWithScale");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCamToObjectKeepingZ: {
+ if (!process().isActiveForPlayer())
+ return TaskReturn::finish(0); // contrary to ...ResettingZ this one does not delay if not active
+ auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto pointObject = dynamic_cast<PointObject *>(object);
+ if (pointObject == nullptr)
+ error("Invalid target object for LerpCamToObjectKeepingZ: %s", getStringArg(0));
+ return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
+ as2D(pointObject->position()),
+ getNumberArg(1), EasingType::Linear));
+ }
+ case ScriptKernelTask::LerpCamToObjectResettingZ: {
+ if (!process().isActiveForPlayer())
+ return TaskReturn::waitFor(delay(getNumberArg(1)));
+ auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto pointObject = dynamic_cast<PointObject *>(object);
+ if (pointObject == nullptr)
+ error("Invalid target object for LerpCamToObjectResettingZ: %s", getStringArg(0));
+ return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
+ as3D(pointObject->position()),
+ getNumberArg(1), (EasingType)getNumberArg(2)));
+ }
+ case ScriptKernelTask::LerpCamToObjectWithScale: {
+ if (!process().isActiveForPlayer())
+ return TaskReturn::waitFor(delay(getNumberArg(2)));
+ auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto pointObject = dynamic_cast<PointObject *>(object);
+ if (pointObject == nullptr)
+ error("Invalid target object for LerpCamToObjectWithScale: %s", getStringArg(0));
+ return TaskReturn::waitFor(g_engine->camera().lerpPosScale(process(),
+ as3D(pointObject->position()), getNumberArg(1) * 0.01f,
+ getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
+ }
// Fades
case ScriptKernelTask::FadeType0:
Commit: 550f23ab4e88fcbff5a57b3faf58fb8d03778cb9
https://github.com/scummvm/scummvm/commit/550f23ab4e88fcbff5a57b3faf58fb8d03778cb9
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Reorder and group kernel tasks
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 061dca0e653..d6b78cfd2fa 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -404,6 +404,7 @@ private:
TaskReturn kernelCall(ScriptKernelTask task) {
switch (task) {
+ // sound/video
case ScriptKernelTask::PlayVideo:
warning("STUB KERNEL CALL: PlayVideo");
return TaskReturn::finish(0);
@@ -419,9 +420,65 @@ private:
case ScriptKernelTask::WaitForMusicToEnd:
warning("STUB KERNEL CALL: WaitForMusicToEnd");
return TaskReturn::finish(0);
+
+ // Misc / control flow
case ScriptKernelTask::ShowCenterBottomText:
warning("STUB KERNEL CALL: ShowCenterBottomText");
return TaskReturn::finish(0);
+ case ScriptKernelTask::Delay:
+ 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::Fork:
+ g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
+ return TaskReturn::finish(0); // 0 means this is the forking process
+ case ScriptKernelTask::KillProcesses:
+ warning("STUB KERNEL CALL: KillProcesses");
+ return TaskReturn::finish(0);
+
+ // player/world state changes
+ case ScriptKernelTask::ChangeCharacter:
+ warning("STUB KERNEL CALL: ChangeCharacter");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ChangeRoom:
+ warning("STUB KERNEL CALL: ChangeRoom");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::ToggleRoomFloor:
+ if (process().character() == MainCharacterKind::None) {
+ if (g_engine->player().currentRoom() != nullptr)
+ g_engine->player().currentRoom()->toggleActiveFloor();
+ }
+ else
+ g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
+ return TaskReturn::finish(1);
+ case ScriptKernelTask::LerpWorldLodBias:
+ warning("STUB KERNEL CALL: LerpWorldLodBias");
+ return TaskReturn::finish(0);
+
+ // object control / animation
+ case ScriptKernelTask::On:
+ g_engine->world().toggleObject(process().character(), getStringArg(0), true);
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Off:
+ g_engine->world().toggleObject(process().character(), getStringArg(0), false);
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::Animate: {
+ auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
+ auto graphicObject = dynamic_cast<GraphicObject *>(object);
+ if (graphicObject == nullptr)
+ error("Script tried to animate invalid graphic object %s", getStringArg(0));
+ if (getNumberOrStringArg(1)) {
+ graphicObject->toggle(true);
+ graphicObject->graphic()->start(false);
+ return TaskReturn::finish(1);
+ }
+ else
+ return TaskReturn::waitFor(graphicObject->animate(process()));
+ }
+
+ // character control / animation
case ScriptKernelTask::StopAndTurn: {
auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
auto character = dynamic_cast<WalkingCharacter *>(object);
@@ -435,23 +492,6 @@ private:
relatedCharacter().stopWalking((Direction)getNumberArg(0));
return TaskReturn::finish(1);
}
- case ScriptKernelTask::ChangeCharacter:
- warning("STUB KERNEL CALL: ChangeCharacter");
- return TaskReturn::finish(0);
- case ScriptKernelTask::SayText: {
- const char *characterName = getStringArg(0);
- int32 dialogId = getNumberArg(1);
- if (strncmp(characterName, "MENU_", 5) == 0) {
- g_engine->world().getMainCharacterByKind(process().character()).addDialogLine(dialogId);
- return TaskReturn::finish(1);
- }
- Character *_character = strcmp(characterName, "AMBOS") == 0
- ? &g_engine->world().getMainCharacterByKind(process().character())
- : dynamic_cast<Character *>(g_engine->world().getObjectByName(characterName));
- if (_character == nullptr)
- error("Invalid character for sayText: %s", characterName);
- return TaskReturn::waitFor(_character->sayText(process(), dialogId));
- };
case ScriptKernelTask::Go: {
auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
auto character = dynamic_cast<WalkingCharacter *>(characterObject);
@@ -485,18 +525,36 @@ private:
case ScriptKernelTask::ChangeCharacterRoom:
warning("STUB KERNEL CALL: ChangeCharacterRoom");
return TaskReturn::finish(0);
- case ScriptKernelTask::KillProcesses:
- warning("STUB KERNEL CALL: KillProcesses");
- return TaskReturn::finish(0);
case ScriptKernelTask::LerpCharacterLodBias:
warning("STUB KERNEL CALL: LerpCharacterLodBias");
return TaskReturn::finish(0);
- case ScriptKernelTask::On:
- g_engine->world().toggleObject(process().character(), getStringArg(0), true);
+ case ScriptKernelTask::AnimateCharacter:
+ warning("STUB KERNEL CALL: AnimateCharacter");
return TaskReturn::finish(0);
- case ScriptKernelTask::Off:
- g_engine->world().toggleObject(process().character(), getStringArg(0), false);
+ case ScriptKernelTask::AnimateTalking:
+ warning("STUB KERNEL CALL: AnimateTalking");
return TaskReturn::finish(0);
+ case ScriptKernelTask::SayText: {
+ const char *characterName = getStringArg(0);
+ int32 dialogId = getNumberArg(1);
+ if (strncmp(characterName, "MENU_", 5) == 0) {
+ g_engine->world().getMainCharacterByKind(process().character()).addDialogLine(dialogId);
+ return TaskReturn::finish(1);
+ }
+ Character *_character = strcmp(characterName, "AMBOS") == 0
+ ? &g_engine->world().getMainCharacterByKind(process().character())
+ : dynamic_cast<Character *>(g_engine->world().getObjectByName(characterName));
+ if (_character == nullptr)
+ error("Invalid character for sayText: %s", characterName);
+ return TaskReturn::waitFor(_character->sayText(process(), dialogId));
+ };
+ case ScriptKernelTask::SetDialogLineReturn:
+ g_engine->world().getMainCharacterByKind(process().character()).setLastDialogReturnValue(getNumberArg(0));
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::DialogMenu:
+ return TaskReturn::waitFor(g_engine->world().getMainCharacterByKind(process().character()).dialogMenu(process()));
+
+ // Inventory control
case ScriptKernelTask::Pickup:
relatedCharacter().pickup(getStringArg(0), getNumberArg(1));
return TaskReturn::finish(1);
@@ -513,50 +571,6 @@ private:
character.drop(getStringArg(0));
return TaskReturn::finish(1);
}
- case ScriptKernelTask::Delay:
- 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::Fork:
- g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
- return TaskReturn::finish(0); // 0 means this is the forking process
- case ScriptKernelTask::Animate: {
- auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto graphicObject = dynamic_cast<GraphicObject *>(object);
- if (graphicObject == nullptr)
- error("Script tried to animate invalid graphic object %s", getStringArg(0));
- if (getNumberOrStringArg(1)) {
- graphicObject->toggle(true);
- graphicObject->graphic()->start(false);
- return TaskReturn::finish(1);
- }
- else
- return TaskReturn::waitFor(graphicObject->animate(process()));
- }
- case ScriptKernelTask::AnimateCharacter:
- warning("STUB KERNEL CALL: AnimateCharacter");
- return TaskReturn::finish(0);
- case ScriptKernelTask::AnimateTalking:
- warning("STUB KERNEL CALL: AnimateTalking");
- return TaskReturn::finish(0);
- case ScriptKernelTask::ChangeRoom:
- warning("STUB KERNEL CALL: ChangeRoom");
- return TaskReturn::finish(0);
- case ScriptKernelTask::ToggleRoomFloor:
- if (process().character() == MainCharacterKind::None) {
- if (g_engine->player().currentRoom() != nullptr)
- g_engine->player().currentRoom()->toggleActiveFloor();
- }
- else
- g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
- return TaskReturn::finish(1);
- case ScriptKernelTask::SetDialogLineReturn:
- g_engine->world().getMainCharacterByKind(process().character()).setLastDialogReturnValue(getNumberArg(0));
- return TaskReturn::finish(0);
- case ScriptKernelTask::DialogMenu:
- return TaskReturn::waitFor(g_engine->world().getMainCharacterByKind(process().character()).dialogMenu(process()));
case ScriptKernelTask::ClearInventory:
switch((MainCharacterKind)getNumberArg(0)) {
case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory(); break;
@@ -564,9 +578,6 @@ private:
default: error("Script attempted to clear inventory with invalid character kind"); break;
}
return TaskReturn::finish(1);
- case ScriptKernelTask::LerpWorldLodBias:
- warning("STUB KERNEL CALL: LerpWorldLodBias");
- return TaskReturn::finish(0);
// Camera tasks
case ScriptKernelTask::SetMaxCamSpeedFactor:
@@ -657,6 +668,7 @@ private:
warning("STUB KERNEL CALL: FadeOut2");
return TaskReturn::finish(0);
+ // Unused and useless
case ScriptKernelTask::SetActiveTextureSet:
// Fortunately this seems to be unused.
warning("STUB KERNEL CALL: SetActiveTextureSet");
Commit: 97e9bc81b4bd6a2b0eea72a50e52fad34b82b542
https://github.com/scummvm/scummvm/commit/97e9bc81b4bd6a2b0eea72a50e52fad34b82b542
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add fades for doors
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/player.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 2b07ab58f07..22c56ce5ec6 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -656,16 +656,16 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
}
// now we are in new-line territory
+ if (*itChar > ' ')
+ itChar = trimTrailing(itChar, itLine, false); // trim last word
if (centered) {
- if (*itChar > ' ')
- itChar = trimTrailing(itChar, itLine, false); // trim last word
itChar = trimTrailing(itChar, itLine, true) + 1;
itLine = trimLeading(itLine, itChar);
_allLines[lineCount] = TextLine(itLine, itChar - itLine);
- itChar = trimLeading(itChar, textEnd);
}
else
_allLines[lineCount] = TextLine(itLine, itChar - itLine);
+ itChar = trimLeading(itChar, textEnd);
lineCount++;
lineWidth = 0;
itLine = itChar;
@@ -726,6 +726,84 @@ void TextDrawRequest::draw() {
}
}
+FadeDrawRequest::FadeDrawRequest(FadeType type, float value, int8 order)
+ : IDrawRequest(order)
+ , _type(type)
+ , _value(value) {}
+
+void FadeDrawRequest::draw() {
+ Color color;
+ const byte valueAsByte = (byte)(_value * 255);
+ switch (_type) {
+ case FadeType::ToBlack:
+ color = { 0, 0, 0, valueAsByte };
+ g_engine->renderer().setBlendMode(BlendMode::AdditiveAlpha);
+ break;
+ case FadeType::ToWhite:
+ color = { valueAsByte, valueAsByte, valueAsByte, valueAsByte };
+ g_engine->renderer().setBlendMode(BlendMode::Additive);
+ break;
+ default:
+ assert(false && "Invalid fade type");
+ return;
+ }
+ g_engine->renderer().setTexture(nullptr);
+ g_engine->renderer().quad(Vector2d(0, 0), as2D(Point(g_system->getWidth(), g_system->getHeight())), color);
+}
+
+struct FadeTask : public Task {
+ FadeTask(Process &process, FadeType fadeType,
+ float from, float to,
+ uint32 duration, EasingType easingType,
+ int8 order)
+ : Task(process)
+ , _fadeType(fadeType)
+ , _from(from)
+ , _to(to)
+ , _duration(duration)
+ , _easingType(easingType)
+ , _order(order) {}
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ _startTime = g_system->getMillis();
+ while (g_system->getMillis() - _startTime < _duration) {
+ draw((g_system->getMillis() - _startTime) / (float)_duration);
+ TASK_YIELD;
+ }
+ draw(1.0f); // so that during a loading lag the screen is completly black/white
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ uint32 remaining = g_system->getMillis() - _startTime <= _duration
+ ? _duration - (g_system->getMillis() - _startTime)
+ : 0;
+ }
+
+private:
+ void draw(float t) {
+ g_engine->drawQueue().add<FadeDrawRequest>(_fadeType, _from + (_to - _from) * ease(t, _easingType), _order);
+ }
+
+ FadeType _fadeType;
+ float _from, _to;
+ uint32 _startTime = 0, _duration;
+ EasingType _easingType;
+ int8 _order;
+};
+
+Task *fade(Process &process, FadeType fadeType,
+ float from, float to,
+ int32 duration, EasingType easingType,
+ int8 order) {
+ if (duration <= 0)
+ return new DelayTask(process, 0);
+ if (!process.isActiveForPlayer())
+ return new DelayTask(process, (uint32)duration);
+ return new FadeTask(process, fadeType, from, to, duration, easingType, order);
+}
+
DrawQueue::DrawQueue(IRenderer *renderer)
: _renderer(renderer)
, _allocator(1024) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 4395d269f40..6f5b538a9d0 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -81,7 +81,7 @@ public:
virtual void setBlendMode(BlendMode blendMode) = 0;
virtual void setLodBias(float lodBias) = 0;
virtual void quad(
- Math::Vector2d center,
+ Math::Vector2d center, // TOOD: Use topLeft&size instead of center&size
Math::Vector2d size,
Color color = kWhite,
Math::Angle rotation = Math::Angle(),
@@ -374,6 +374,28 @@ private:
int _allPosX[kMaxLines];
};
+enum class FadeType {
+ ToBlack,
+ ToWhite
+ // TODO: Add CrossFade fade type
+};
+
+class FadeDrawRequest : public IDrawRequest {
+public:
+ FadeDrawRequest(FadeType type, float value, int8 order);
+
+ virtual void draw() override;
+
+private:
+ FadeType _type;
+ float _value;
+};
+
+Task *fade(Process &process, FadeType fadeType,
+ float from, float to,
+ int32 duration, EasingType easingType,
+ int8 order);
+
class BumpAllocator {
public:
BumpAllocator(size_t pageSize);
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 2e094dba117..ddff15a0e69 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -188,7 +188,7 @@ struct DoorTask : public Task {
TASK_BEGIN;
// TODO: Fade out music on room change
// TODO: Fade out/in on room change instead of delay
- TASK_WAIT(delay(500));
+ TASK_WAIT(fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
_player.changeRoom(_targetRoom->name(), true);
if (_targetRoom->fixedCameraOnEntering())
@@ -202,9 +202,9 @@ struct DoorTask : public Task {
// TODO: Start music on room change
if (g_engine->script().createProcess(_character->kind(), "ENTRAR_" + _targetRoom->name(), ScriptFlags::AllowMissing))
- TASK_WAIT(delay(0));
+ TASK_YIELD;
else
- TASK_WAIT(delay(500));
+ TASK_WAIT(fade(process(), FadeType::ToBlack, 1, 0, 500, EasingType::Out, -5));
TASK_END;
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index d6b78cfd2fa..de05df98fdb 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -647,32 +647,34 @@ private:
// Fades
case ScriptKernelTask::FadeType0:
- warning("STUB KERNEL CALL: FadeType0");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
+ getNumberArg(0) * 0.01f, getNumberArg(1) * 0.01f,
+ getNumberArg(2), (EasingType)getNumberArg(4), getNumberArg(3)));
case ScriptKernelTask::FadeType1:
- warning("STUB KERNEL CALL: FadeType1");
- return TaskReturn::finish(0);
- case ScriptKernelTask::FadeType2:
- warning("STUB KERNEL CALL: FadeType2");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(fade(process(), FadeType::ToWhite,
+ getNumberArg(0) * 0.01f, getNumberArg(1) * 0.01f,
+ getNumberArg(2), (EasingType)getNumberArg(4), getNumberArg(3)));
case ScriptKernelTask::FadeIn:
- warning("STUB KERNEL CALL: FadeIn");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
+ 1.0f, 0.0f, getNumberArg(0), EasingType::Out, -5));
case ScriptKernelTask::FadeOut:
- warning("STUB KERNEL CALL: FadeOut");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
+ 0.0f, 1.0f, getNumberArg(0), EasingType::Out, -5));
case ScriptKernelTask::FadeIn2:
- warning("STUB KERNEL CALL: FadeIn2");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
+ 0.0f, 1.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5));
case ScriptKernelTask::FadeOut2:
- warning("STUB KERNEL CALL: FadeOut2");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
+ 1.0f, 0.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5));
// Unused and useless
case ScriptKernelTask::SetActiveTextureSet:
// Fortunately this seems to be unused.
warning("STUB KERNEL CALL: SetActiveTextureSet");
return TaskReturn::finish(0);
+ case ScriptKernelTask::FadeType2:
+ warning("STUB KERNEL CALL: FadeType2"); // Crossfade, unused from script
+ return TaskReturn::finish(0);
case ScriptKernelTask::Nop10:
case ScriptKernelTask::Nop24:
case ScriptKernelTask::Nop34:
Commit: 206c749922ddbd29a01470ebd6b6488aa9732fb5
https://github.com/scummvm/scummvm/commit/206c749922ddbd29a01470ebd6b6488aa9732fb5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add inventory
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 5dd3f2b2bcb..282932f65f9 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -32,9 +32,9 @@ using namespace Math;
namespace Alcachofa {
void Camera::resetRotationAndScale() {
- _scale = 1;
- _rotation = 0;
- _usedCenter.z() = 0;
+ _cur._scale = 1;
+ _cur._rotation = 0;
+ _cur._usedCenter.z() = 0;
}
void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
@@ -49,7 +49,7 @@ void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
}
void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
- _followTarget = target;
+ _cur._followTarget = target;
_lastUpdateTime = g_system->getMillis();
_catchUp = catchUp;
if (target == nullptr)
@@ -57,14 +57,26 @@ void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
}
void Camera::setPosition(Vector2d v) {
- setPosition({ v.getX(), v.getY(), _usedCenter.z() });
+ setPosition({ v.getX(), v.getY(), _cur._usedCenter.z() });
}
void Camera::setPosition(Vector3d v) {
- _usedCenter = v;
+ _cur._usedCenter = v;
setFollow(nullptr);
}
+void Camera::backup(uint slot) {
+ assert(slot < kStateBackupCount);
+ _backups[slot] = _cur;
+}
+
+void Camera::restore(uint slot) {
+ assert(slot < kStateBackupCount);
+ auto backupState = _backups[slot];
+ _backups[slot] = _cur;
+ _cur = backupState;
+}
+
static Matrix4 scaleMatrix(float scale) {
Matrix4 m;
m(0, 0) = scale;
@@ -75,16 +87,16 @@ static Matrix4 scaleMatrix(float scale) {
void Camera::setupMatricesAround(Vector3d center) {
Matrix4 matTemp;
- matTemp.buildAroundZ(_rotation);
+ matTemp.buildAroundZ(_cur._rotation);
_mat3Dto2D.setToIdentity();
_mat3Dto2D.translate(-center);
_mat3Dto2D = matTemp * _mat3Dto2D;
- _mat3Dto2D = _mat3Dto2D * scaleMatrix(_scale);
+ _mat3Dto2D = _mat3Dto2D * scaleMatrix(_cur._scale);
_mat2Dto3D.setToIdentity();
_mat2Dto3D.translate(center);
- matTemp.buildAroundZ(-_rotation);
- matTemp = scaleMatrix(1 / _scale) * matTemp;
+ matTemp.buildAroundZ(-_cur._rotation);
+ matTemp = scaleMatrix(1 / _cur._scale) * matTemp;
_mat2Dto3D = matTemp * _mat2Dto3D;
}
@@ -122,7 +134,7 @@ Vector3d Camera::transform2Dto3D(Vector3d v2d) const {
// if this looks like normal 3D math to *someone* please contact.
Vector4d vh;
vh.w() = 1.0f;
- vh.z() = v2d.z() - _usedCenter.z();
+ vh.z() = v2d.z() - _cur._usedCenter.z();
vh.y() = (v2d.y() - g_system->getHeight() * 0.5f) * vh.z() * kInvBaseScale;
vh.x() = (v2d.x() - g_system->getWidth() * 0.5f) * vh.z() * kInvBaseScale;
vh = _mat2Dto3D * vh;
@@ -141,7 +153,7 @@ Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
return Vector3d(
g_system->getWidth() * 0.5f + vh.x() * kBaseScale / vh.z(),
g_system->getHeight() * 0.5f + vh.y() * kBaseScale / vh.z(),
- _scale * kBaseScale / vh.z());
+ _cur._scale * kBaseScale / vh.z());
}
void Camera::update() {
@@ -151,66 +163,66 @@ void Camera::update() {
deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
_lastUpdateTime = now;
- if (_catchUp && _followTarget != nullptr) {
+ if (_catchUp && _cur._followTarget != nullptr) {
for (int i = 0; i < 4; i++)
updateFollowing(50.0f);
}
else
updateFollowing(deltaTime);
- setAppliedCenter(_usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
+ setAppliedCenter(_cur._usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
}
void Camera::updateFollowing(float deltaTime) {
- if (_followTarget == nullptr)
+ if (_cur._followTarget == nullptr)
return;
const float resolutionFactor = g_system->getWidth() * 0.00125f;
const float acceleration = 460 * resolutionFactor;
const float baseDeadZoneSize = 25 * resolutionFactor;
const float minSpeed = 20 * resolutionFactor;
- const float maxSpeed = this->_maxSpeedFactor * resolutionFactor;
- const float depthScale = _followTarget->graphic()->depthScale();
- const auto characterPolygon = _followTarget->shape()->at(0);
+ const float maxSpeed = this->_cur._maxSpeedFactor * resolutionFactor;
+ const float depthScale = _cur._followTarget->graphic()->depthScale();
+ const auto characterPolygon = _cur._followTarget->shape()->at(0);
const float halfHeight = ABS(characterPolygon._points[0].y - characterPolygon._points[2].y) / 2.0f;
Vector3d targetCenter = setAppliedCenter({
- _shake.getX() + _followTarget->position().x,
- _shake.getY() + _followTarget->position().y - depthScale * 85,
- _usedCenter.z()});
+ _shake.getX() + _cur._followTarget->position().x,
+ _shake.getY() + _cur._followTarget->position().y - depthScale * 85,
+ _cur._usedCenter.z()});
targetCenter.y() -= halfHeight;
- float distanceToTarget = as2D(_usedCenter - targetCenter).getMagnitude();
- float moveDistance = _followTarget->stepSizeFactor() * _speed * deltaTime;
+ float distanceToTarget = as2D(_cur._usedCenter - targetCenter).getMagnitude();
+ float moveDistance = _cur._followTarget->stepSizeFactor() * _cur._speed * deltaTime;
- float deadZoneSize = baseDeadZoneSize / _scale;
- if (_followTarget->isWalking() && depthScale > 0.8f)
- deadZoneSize = (baseDeadZoneSize + (depthScale - 0.8f) * 200) / _scale;
+ float deadZoneSize = baseDeadZoneSize / _cur._scale;
+ if (_cur._followTarget->isWalking() && depthScale > 0.8f)
+ deadZoneSize = (baseDeadZoneSize + (depthScale - 0.8f) * 200) / _cur._scale;
bool isFarAway = false;
- if (ABS(targetCenter.x() - _usedCenter.x()) > deadZoneSize ||
- ABS(targetCenter.y() - _usedCenter.y()) > deadZoneSize) {
+ if (ABS(targetCenter.x() - _cur._usedCenter.x()) > deadZoneSize ||
+ ABS(targetCenter.y() - _cur._usedCenter.y()) > deadZoneSize) {
isFarAway = true;
- _isBraking = false;
+ _cur._isBraking = false;
_isChanging = true;
}
- if (_isBraking) {
- _speed -= acceleration * 0.9f * deltaTime;
- _speed = MAX(_speed, minSpeed);
+ if (_cur._isBraking) {
+ _cur._speed -= acceleration * 0.9f * deltaTime;
+ _cur._speed = MAX(_cur._speed, minSpeed);
}
- if (_isChanging && !_isBraking) {
- _speed += acceleration * deltaTime;
- _speed = MIN(_speed, maxSpeed);
+ if (_isChanging && !_cur._isBraking) {
+ _cur._speed += acceleration * deltaTime;
+ _cur._speed = MIN(_cur._speed, maxSpeed);
if (!isFarAway)
- _isBraking = true;
+ _cur._isBraking = true;
}
if (_isChanging) {
if (distanceToTarget <= moveDistance) {
- _usedCenter = targetCenter;
+ _cur._usedCenter = targetCenter;
_isChanging = false;
- _isBraking = false;
+ _cur._isBraking = false;
}
else {
- Vector3d deltaCenter = targetCenter - _usedCenter;
+ Vector3d deltaCenter = targetCenter - _cur._usedCenter;
deltaCenter.z() = 0.0f;
- _usedCenter += deltaCenter * moveDistance / distanceToTarget;
+ _cur._usedCenter += deltaCenter * moveDistance / distanceToTarget;
}
}
}
@@ -266,12 +278,12 @@ protected:
struct CamLerpScaleTask final : public CamLerpTask {
CamLerpScaleTask(Process &process, float targetScale, int32 duration, EasingType easingType)
: CamLerpTask(process, duration, easingType)
- , _fromScale(_camera._scale)
- , _deltaScale(targetScale - _camera._scale) {}
+ , _fromScale(_camera._cur._scale)
+ , _deltaScale(targetScale - _camera._cur._scale) {}
protected:
virtual void update(float t) override {
- _camera._scale = _fromScale + _deltaScale * t;
+ _camera._cur._scale = _fromScale + _deltaScale * t;
}
float _fromScale, _deltaScale;
@@ -284,15 +296,15 @@ struct CamLerpPosScaleTask final : public CamLerpTask {
: CamLerpTask(process, duration, EasingType::Linear) // linear as we need different ones per component
, _fromPos(_camera._appliedCenter)
, _deltaPos(targetPos - _camera._appliedCenter)
- , _fromScale(_camera._scale)
- , _deltaScale(targetScale - _camera._scale)
+ , _fromScale(_camera._cur._scale)
+ , _deltaScale(targetScale - _camera._cur._scale)
, _moveEasingType(moveEasingType)
, _scaleEasingType(scaleEasingType) {}
protected:
virtual void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * ease(t, _moveEasingType));
- _camera._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
+ _camera._cur._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
}
Vector3d _fromPos, _deltaPos;
@@ -303,12 +315,12 @@ protected:
struct CamLerpRotationTask final : public CamLerpTask {
CamLerpRotationTask(Process &process, float targetRotation, int32 duration, EasingType easingType)
: CamLerpTask(process, duration, easingType)
- , _fromRotation(_camera._rotation.getDegrees())
- , _deltaRotation(targetRotation - _camera._rotation.getDegrees()) {}
+ , _fromRotation(_camera._cur._rotation.getDegrees())
+ , _deltaRotation(targetRotation - _camera._cur._rotation.getDegrees()) {}
protected:
virtual void update(float t) override {
- _camera._rotation = Angle(_fromRotation + _deltaRotation * t);
+ _camera._cur._rotation = Angle(_fromRotation + _deltaRotation * t);
}
float _fromRotation, _deltaRotation;
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 995521b7bd8..cdca9db6852 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -36,9 +36,9 @@ static constexpr const float kInvBaseScale = 1.0f / kBaseScale;
class Camera {
public:
- inline Math::Angle rotation() const { return _rotation; }
+ inline Math::Angle rotation() const { return _cur._rotation; }
inline Math::Vector2d &shake() { return _shake; }
- inline WalkingCharacter *followTarget() { return _followTarget; }
+ inline WalkingCharacter *followTarget() { return _cur._followTarget; }
void update();
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
@@ -48,6 +48,8 @@ public:
void setFollow(WalkingCharacter *target, bool catchUp = false);
void setPosition(Math::Vector2d v);
void setPosition(Math::Vector3d v);
+ void backup(uint slot);
+ void restore(uint slot);
Task *lerpPos(Process &process,
Math::Vector2d targetPos,
@@ -82,27 +84,31 @@ private:
void setupMatricesAround(Math::Vector3d center);
void updateFollowing(float deltaTime);
+ struct State {
+ Math::Vector3d _usedCenter = Math::Vector3d(512, 384, 0);
+ float
+ _scale = 1.0f,
+ _speed = 0.0f,
+ _maxSpeedFactor = 230.0f;
+ Math::Angle _rotation;
+ bool _isBraking = false;
+ WalkingCharacter *_followTarget = nullptr;
+ };
+
+ static constexpr uint kStateBackupCount = 2;
+ State _cur, _backups[kStateBackupCount];
uint32 _lastUpdateTime = 0;
bool _isChanging = false,
- _isBraking = false,
_catchUp = false;
- float
- _scale = 1.0f,
- _roomScale = 1.0f,
- _maxSpeedFactor = 230.0f,
- _speed = 0.0f;
- Math::Angle _rotation;
+ float _roomScale = 1.0f;
Math::Vector2d
_roomMin = Math::Vector2d(-10000, -10000),
_roomMax = Math::Vector2d(10000, 10000),
_shake;
- Math::Vector3d
- _usedCenter = Math::Vector3d(512, 384, 0),
- _appliedCenter;
+ Math::Vector3d _appliedCenter;
Math::Matrix4
_mat3Dto2D,
_mat2Dto3D;
- WalkingCharacter *_followTarget = nullptr;
};
}
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index ed7e1629771..81206ab9779 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -39,6 +39,8 @@ Console::Console() : GUI::Debugger() {
registerCmd("rooms", WRAP_METHOD(Console, cmdRooms));
registerCmd("changeRoom", WRAP_METHOD(Console, cmdChangeRoom));
registerCmd("disableDebugDraw", WRAP_METHOD(Console, cmdDisableDebugDraw));
+ registerCmd("pickup", WRAP_METHOD(Console, cmdItem));
+ registerCmd("drop", WRAP_METHOD(Console, cmdItem));
}
Console::~Console() {
@@ -139,4 +141,56 @@ bool Console::cmdDisableDebugDraw(int argc, const char **args) {
return true;
}
+bool Console::cmdItem(int argc, const char **args) {
+ auto &inventory = g_engine->world().inventory();
+ auto &mortadelo = g_engine->world().mortadelo();
+ auto &filemon = g_engine->world().filemon();
+ auto *active = g_engine->player().activeCharacter();
+ if (argc < 2 || argc > 3) {
+ debugPrintf("usage: %s [Mortadelo/Filemon] [<item>]\n\n", args[0]);
+ debugPrintf("%20s%10s%10s\n", "Item", "Mortadelo", "Filemon");
+ for (auto itItem = inventory.beginObjects(); itItem != inventory.endObjects(); ++itItem) {
+ if (dynamic_cast<const Item *>(*itItem) == nullptr)
+ continue;
+ debugPrintf("%20s%10s%10s\n",
+ (*itItem)->name().c_str(),
+ mortadelo.hasItem((*itItem)->name()) ? "YES" : "no",
+ filemon.hasItem((*itItem)->name()) ? "YES" : "no");
+ }
+ return true;
+ }
+ if (argc == 2 && active == nullptr) {
+ debugPrintf("No character is active, name has to be specified\n");
+ return true;
+ }
+
+ const char *itemName = args[1];
+ if (argc == 3) {
+ itemName = args[2];
+ if (strcmpi(args[1], "mortadelo") == 0 || strcmpi(args[1], "m") == 0)
+ active = &mortadelo;
+ else if (strcmpi(args[1], "filemon") == 0 || strcmpi(args[1], "f") == 0)
+ active = &filemon;
+ else {
+ debugPrintf("Invalid character name \"%s\", has to be either \"mortadelo\" or \"filemon\"\n", args[1]);
+ return true;
+ }
+ }
+
+ bool hasMatchedSomething = false;
+ for (auto itItem = inventory.beginObjects(); itItem != inventory.endObjects(); ++itItem) {
+ if (dynamic_cast<const Item *>(*itItem) == nullptr ||
+ !(*itItem)->name().matchString(itemName, true))
+ continue;
+ hasMatchedSomething = true;
+ if (args[0][0] == 'p')
+ active->pickup((*itItem)->name(), false);
+ else
+ active->drop((*itItem)->name());
+ }
+ if (!hasMatchedSomething)
+ debugPrintf("Cannot find any item matching \"%s\"\n", itemName);
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 771873d0617..b59755b16f3 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -52,6 +52,7 @@ private:
bool cmdRooms(int argc, const char **args);
bool cmdChangeRoom(int argc, const char **args);
bool cmdDisableDebugDraw(int argc, const char **args);
+ bool cmdItem(int argc, const char **args);
bool _showInteractables = true;
bool _showCharacters = true;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index a8607d2b872..2e90023932c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -21,6 +21,7 @@
#include "objects.h"
#include "rooms.h"
+#include "script.h"
#include "alcachofa.h"
using namespace Common;
@@ -43,6 +44,24 @@ Item::Item(const Item &other)
new (&_graphic) Graphic(other._graphic);
}
+void Item::trigger() {
+ auto &player = g_engine->player();
+ auto &heldItem = player.heldItem();
+ if (g_engine->input().wasMouseRightReleased()) {
+ if (heldItem == nullptr)
+ player.triggerObject(this, "MIRAR");
+ else
+ heldItem = nullptr;
+ }
+ else if (heldItem == nullptr)
+ heldItem = this;
+ else if (g_engine->script().hasProcedure(name(), heldItem->name()) ||
+ !g_engine->script().hasProcedure(heldItem->name(), name()))
+ player.triggerObject(this, heldItem->name().c_str());
+ else
+ player.triggerObject(heldItem, name().c_str());
+}
+
ITriggerableObject::ITriggerableObject(ReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
, _interactionDirection((Direction)stream.readSint32LE()) {}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 22c56ce5ec6..2bde10577bb 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -779,6 +779,7 @@ struct FadeTask : public Task {
uint32 remaining = g_system->getMillis() - _startTime <= _duration
? _duration - (g_system->getMillis() - _startTime)
: 0;
+ g_engine->console().debugPrintf("Fade (%d) from %.2f to %.2f with %ums remaining\n", (int)_fadeType, _from, _to, remaining);
}
private:
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 6f5b538a9d0..24119792664 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -184,6 +184,7 @@ public:
inline const Common::Point &frameCenter(int32 frameI) const { return _frames[frameI]._center; }
inline uint32 totalDuration() const { return _totalDuration; }
inline uint8 &premultiplyAlpha() { return _premultiplyAlpha; }
+ Common::Rect frameBounds(int32 frameI) const;
Common::Point totalFrameOffset(int32 frameI) const;
int32 frameAtTime(uint32 time) const;
int32 imageIndex(int32 frameI, int32 spriteI) const;
@@ -210,7 +211,6 @@ public:
private:
Common::Rect spriteBounds(int32 frameI, int32 spriteI) const;
- Common::Rect frameBounds(int32 frameI) const;
Common::Rect maxFrameBounds() const;
void prerenderFrame(int32 frameI);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 84d281178b1..b3aba89d14c 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -317,6 +317,7 @@ public:
Item(const Item &other);
virtual const char *typeName() const;
+ void trigger();
};
class ITriggerableObject {
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index ddff15a0e69..a298634ffd6 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -74,10 +74,13 @@ void Player::updateCursor() {
drawCursor();
}
-void Player::drawCursor() {
+void Player::drawCursor(bool forceDefaultCursor) {
Point cursorPos = g_engine->input().mousePos2D();
- if (_heldItem == nullptr)
+ if (_heldItem == nullptr || forceDefaultCursor) {
+ if (forceDefaultCursor)
+ _cursorFrameI = 0;
g_engine->drawQueue().add<AnimationDrawRequest>(_cursorAnimation.get(), _cursorFrameI, as2D(cursorPos), -10);
+ }
else {
auto itemGraphic = _heldItem->graphic();
assert(itemGraphic != nullptr);
@@ -97,8 +100,12 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
if (_currentRoom == &inventory)
keepResources = _roomBeforeInventory != nullptr && _roomBeforeInventory->name().equalsIgnoreCase(targetRoomName);
else {
- keepResources = targetRoomName.equalsIgnoreCase("inventario") ||
- (_currentRoom != nullptr && _currentRoom->name().equalsIgnoreCase(targetRoomName));
+ keepResources = _currentRoom != nullptr && _currentRoom->name().equalsIgnoreCase(targetRoomName);
+ }
+ _roomBeforeInventory = nullptr;
+ if (targetRoomName.equalsIgnoreCase("inventario")) {
+ keepResources = true;
+ _roomBeforeInventory = _currentRoom;
}
if (!keepResources && _currentRoom != nullptr) {
@@ -125,6 +132,11 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
_pressedObject = _selectedObject = nullptr;
}
+void Player::changeRoomToBeforeInventory() {
+ assert(_roomBeforeInventory != nullptr);
+ changeRoom(_roomBeforeInventory->name(), true);
+}
+
MainCharacter *Player::inactiveCharacter() const {
if (_activeCharacter == nullptr)
return nullptr;
@@ -147,7 +159,7 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
return;
debug("Trigger object %s %s with %s", object->typeName(), object->name().c_str(), action);
- if (inactiveCharacter()->currentlyUsing() == object) {
+ if (strcmp(action, "MIRAR") == 0 || inactiveCharacter()->currentlyUsing() == object) {
action = "MIRAR";
_activeCharacter->currentlyUsing() = nullptr;
}
@@ -160,8 +172,9 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
else if (scumm_stricmp(action, "MIRAR") == 0)
script.createProcess(activeCharacterKind(), "DefectoMirar");
else if (action[0] == 'i' && object->name()[0] == 'i')
- // TODO: Check if and how this can happen. I guess it crashes now but might be ignored by the original engine
- script.createProcess(activeCharacterKind(), "DefectoObjeto");
+ // 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 and ignore
+ ;
else
script.createProcess(activeCharacterKind(), "DefectoUsar");
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 4841a448609..e8170185b78 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -31,7 +31,6 @@ public:
Player();
inline Room *currentRoom() const { return _currentRoom; }
- inline Room *¤tRoom() { return _currentRoom; }
inline MainCharacter *activeCharacter() const { return _activeCharacter; }
inline ShapeObject *&selectedObject() { return _selectedObject; }
inline ShapeObject *&pressedObject() { return _pressedObject; }
@@ -50,8 +49,9 @@ public:
void preUpdate();
void postUpdate();
void updateCursor();
- void drawCursor();
+ void drawCursor(bool forceDefaultCursor = false);
void changeRoom(const Common::String &targetRoomName, bool resetCamera);
+ void changeRoomToBeforeInventory();
void triggerObject(ObjectBase *object, const char *action);
void triggerDoor(const Door *door);
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 75855459e4d..0b7771844e9 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -29,6 +29,21 @@ 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.
+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());
+}
+
Room::Room(World *world, ReadStream &stream) : Room(world, stream, false) {
}
@@ -129,11 +144,13 @@ void Room::update() {
if (g_engine->player().currentRoom() == this) {
updateRoomBounds();
+ updateClosingInventory();
if (!updateInput())
return;
}
- // TODO: Add condition for global room update
- world().globalRoom().updateObjects();
+ if (!g_engine->player().isOptionsMenuOpen() &&
+ g_engine->player().currentRoom() != &g_engine->world().inventory())
+ world().globalRoom().updateObjects();
if (g_engine->player().currentRoom() == this)
updateObjects();
if (g_engine->player().currentRoom() == this) {
@@ -166,8 +183,10 @@ bool Room::updateInput() {
// A complicated network condition can prevent interaction at this point
if (player.isOptionsMenuOpen() || !player.isGameLoaded())
canInteract = true;
- if (canInteract)
+ if (canInteract) {
updateInteraction();
+ player.updateCursor();
+ }
// TODO: Add main menu and opening inventory handling
return player.currentRoom() == this;
@@ -176,9 +195,12 @@ bool Room::updateInput() {
void Room::updateInteraction() {
auto &player = g_engine->player();
auto &input = g_engine->input();
- // TODO: Add interaction with change character button / opening inventory
+ // TODO: Add interaction with change character button
+
+ if (updateOpeningInventory())
+ return;
- if (player.activeCharacter()->room() != this) {
+ if (player.activeCharacter()->room() != this) { // TODO: Remove active character hack
player.activeCharacter()->room() = this;
}
@@ -196,7 +218,6 @@ void Room::updateInteraction() {
if (input.wasAnyMousePressed())
player.pressedObject() = player.selectedObject();
}
- player.updateCursor();
}
void Room::updateRoomBounds() {
@@ -272,6 +293,51 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
return best;
}
+void Room::startClosingInventory() {
+ _isOpeningInventory = false;
+ _isClosingInventory = true;
+ _timeForInventory = g_system->getMillis();
+}
+
+void Room::updateClosingInventory() {
+ static constexpr uint32 kDuration = 300;
+ static constexpr float kSpeed = -10 / 3.0f / 1000.0f;
+
+ uint32 deltaTime = g_system->getMillis() - _timeForInventory;
+ if (!_isClosingInventory || deltaTime >= kDuration)
+ _isClosingInventory = false;
+ else
+ g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed)));
+}
+
+bool Room::updateOpeningInventory() {
+ static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
+ if (g_engine->player().isOptionsMenuOpen() || !g_engine->player().isGameLoaded())
+ return false;
+
+ if (_isOpeningInventory) {
+ uint32 deltaTime = g_system->getMillis() - _timeForInventory;
+ if (deltaTime >= 1000) {
+ _isOpeningInventory = false;
+ g_engine->world().inventory().open();
+ }
+ else {
+ deltaTime = MIN<uint32>(300, deltaTime);
+ g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed - 1)));
+ }
+ return true;
+ }
+ else if (openInventoryTriggerBounds().contains(g_engine->input().mousePos2D())) {
+ _isClosingInventory = false;
+ _isOpeningInventory = true;
+ _timeForInventory = g_system->getMillis();
+ g_engine->player().activeCharacter()->stopWalking();
+ g_engine->world().inventory().updateItemsByActiveCharacter();
+ return true;
+ }
+ return false;
+}
+
OptionsMenu::OptionsMenu(World *world, ReadStream &stream)
: Room(world, stream, true) {
}
@@ -292,6 +358,63 @@ Inventory::~Inventory() {
// No need to delete items, they are room objects and thus deleted in Room::~Room
}
+bool Inventory::updateInput() {
+ auto &player = g_engine->player();
+ auto &input = g_engine->input();
+ auto *hoveredItem = getHoveredItem();
+
+ if (!player.activeCharacter()->isBusy())
+ player.drawCursor(0);
+
+ if (hoveredItem != nullptr && !player.activeCharacter()->isBusy()) {
+ if (input.wasMouseLeftPressed() && player.heldItem() == nullptr ||
+ input.wasMouseLeftReleased() && player.heldItem() != nullptr ||
+ input.wasMouseRightReleased()) {
+ hoveredItem->trigger();
+ player.pressedObject() = nullptr;
+ }
+
+ g_engine->drawQueue().add<TextDrawRequest>(
+ g_engine->world().generalFont(),
+ g_engine->world().getLocalizedName(hoveredItem->name()),
+ input.mousePos2D() + Point(0, -50),
+ -1, true, kWhite, -kForegroundOrderCount + 1);
+ }
+
+ if (!player.activeCharacter()->isBusy() &&
+ closeInventoryTriggerBounds().contains(input.mousePos2D()))
+ close();
+
+ if (!player.activeCharacter()->isBusy() &&
+ hoveredItem == nullptr &&
+ input.wasMouseRightReleased()) {
+ player.heldItem() = nullptr;
+ return false;
+ }
+
+ return player.currentRoom() == this;
+}
+
+Item *Inventory::getHoveredItem() {
+ auto &mousePos = g_engine->input().mousePos2D();
+ for (auto item : _items) {
+ if (!item->isEnabled())
+ continue;
+ if (g_engine->player().heldItem() != nullptr &&
+ g_engine->player().heldItem()->name().equalsIgnoreCase(item->name()))
+ continue;
+
+ auto graphic = item->graphic();
+ assert(graphic != nullptr);
+ auto bounds = graphic->animation().frameBounds(0);
+ auto totalOffset = graphic->animation().totalFrameOffset(0);
+ auto delta = mousePos - graphic->center() - totalOffset;
+ if (delta.x >= 0 && delta.y >= 0 && delta.x <= bounds.width() && delta.y <= bounds.height())
+ return item;
+ }
+ return nullptr;
+}
+
void Inventory::initItems() {
auto &mortadelo = world().mortadelo();
auto &filemon = world().filemon();
@@ -312,6 +435,36 @@ void Inventory::updateItemsByActiveCharacter() {
item->toggle(character->hasItem(item->name()));
}
+void Inventory::drawAsOverlay(int32 scrollY) {
+ for (auto object : _objects) {
+ auto graphic = object->graphic();
+ if (graphic == nullptr)
+ continue;
+
+ int16 oldY = graphic->center().y;
+ int8 oldOrder = graphic->order();
+ graphic->center().y += scrollY;
+ graphic->order() = -kForegroundOrderCount;
+ if (object->name().equalsIgnoreCase("Background"))
+ graphic->order()++;
+ object->draw();
+ graphic->center().y = oldY;
+ graphic->order() = oldOrder;
+ }
+}
+
+void Inventory::open() {
+ g_engine->camera().backup(1);
+ g_engine->player().changeRoom(name(), true);
+ updateItemsByActiveCharacter();
+}
+
+void Inventory::close() {
+ g_engine->player().changeRoomToBeforeInventory();
+ g_engine->camera().restore(1);
+ g_engine->player().currentRoom()->startClosingInventory();
+}
+
void Room::debugPrint(bool withObjects) const {
auto &console = g_engine->console();
console.debugPrintf(" %s\n", _name.c_str());
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 96ff4da9a02..cdff4c62ee9 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -49,6 +49,10 @@ public:
inline uint8 characterAlphaPremultiplier() const { return _characterAlphaPremultiplier; }
inline bool fixedCameraOnEntering() const { return _fixedCameraOnEntering; }
+ using ObjectIterator = Common::Array<const ObjectBase *>::const_iterator;
+ inline ObjectIterator beginObjects() const { return _objects.begin(); }
+ inline ObjectIterator endObjects() const { return _objects.end(); }
+
void update();
virtual bool updateInput();
virtual void loadResources();
@@ -56,12 +60,15 @@ public:
virtual void serializeSave(Common::Serializer &serializer);
ObjectBase *getObjectByName(const Common::String &name) const;
void toggleActiveFloor();
+ void startClosingInventory();
void debugPrint(bool withObjects) const;
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
void updateScripts();
void updateRoomBounds();
+ bool updateOpeningInventory();
+ void updateClosingInventory();
void updateInteraction();
void updateObjects();
void drawObjects();
@@ -71,13 +78,17 @@ protected:
World *_world;
Common::String _name;
PathFindingShape _floors[2];
- bool _fixedCameraOnEntering;
+ bool
+ _fixedCameraOnEntering,
+ _isOpeningInventory = false,
+ _isClosingInventory = false;
int8
_musicId,
_activeFloorI = -1;
uint8
_characterAlphaTint,
_characterAlphaPremultiplier; ///< for some reason in percent instead of 0-255
+ uint32 _timeForInventory = 0;
Common::Array<ObjectBase *> _objects;
};
@@ -106,10 +117,17 @@ public:
Inventory(World *world, Common::ReadStream &stream);
virtual ~Inventory() override;
+ virtual bool updateInput() override;
+
void initItems();
void updateItemsByActiveCharacter();
+ void drawAsOverlay(int32 scrollY);
+ void open();
+ void close();
private:
+ Item *getHoveredItem();
+
Common::Array<Item *> _items;
};
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index de05df98fdb..3b807b4ec70 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -102,6 +102,14 @@ int32 &Script::variable(const char *name) {
return _variables[index];
}
+bool Script::hasProcedure(const Common::String &behavior, const Common::String &action) const {
+ return hasProcedure(behavior + '/' + action);
+}
+
+bool Script::hasProcedure(const Common::String &procedure) const {
+ return _procedures.contains(procedure);
+}
+
struct ScriptTimerTask : public Task {
ScriptTimerTask(Process &process, int32 durationSec)
: Task(process)
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 46102d58f52..e1b614a4999 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -168,6 +168,8 @@ public:
const Common::String &behavior,
const Common::String &action,
ScriptFlags flags = ScriptFlags::None);
+ bool hasProcedure(const Common::String &behavior, const Common::String &action) const;
+ bool hasProcedure(const Common::String &procedure) const;
using VariableNameIterator = Common::HashMap<Common::String, uint32>::const_iterator;
inline VariableNameIterator beginVariables() const { return _variableNames.begin(); }
Commit: 509ebca2ee73f1545ca3a3e198681cc85834b9df
https://github.com/scummvm/scummvm/commit/509ebca2ee73f1545ca3a3e198681cc85834b9df
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add three more kernel tasks
for changeRoom, changeCharacterRoom and camFollow
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 959f57a1c83..928bd49df44 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -66,9 +66,10 @@ Common::Error AlcachofaEngine::run() {
_script.reset(new Script());
_player.reset(new Player());
- _script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
-
- _player->changeRoom("MINA", true);
+ //_script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
+ //_player->changeRoom("MINA", true);
+ _script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
+ _scheduler.run();
Common::Event e;
Graphics::FrameLimiter limiter(g_system, 120);
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 2e90023932c..ec6d915801a 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -329,6 +329,12 @@ Task *Character::sayText(Process &process, int32 dialogId) {
return new SayTextTask(process, this, dialogId);
}
+void Character::resetTalking() {
+ _isTalking = false;
+ _curDialogId = -1;
+ _curTalkingObject = nullptr;
+}
+
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index b3aba89d14c..90f5c831d3d 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -390,6 +390,7 @@ public:
virtual const char *typeName() const;
Task *sayText(Process &process, int32 dialogId);
+ void resetTalking();
protected:
friend struct SayTextTask;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index a298634ffd6..02beea7a10e 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -94,6 +94,10 @@ void Player::drawCursor(bool forceDefaultCursor) {
void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera) {
// original would be to always free all resources from globalRoom, inventory, GlobalUI
+ if (targetRoomName.equalsIgnoreCase("SALIR")) {
+ _currentRoom = nullptr;
+ return;
+ }
Room &inventory = g_engine->world().inventory();
bool keepResources;
@@ -171,10 +175,9 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
return;
else 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 and ignore
- ;
+ //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
else
script.createProcess(activeCharacterKind(), "DefectoUsar");
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0b7771844e9..b15b3a003ac 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -119,7 +119,8 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
_objects.push_back(readRoomObject(this, stream));
objectSize = stream.readUint32LE();
}
- if (!_name.equalsIgnoreCase("Global"))
+ if (!_name.equalsIgnoreCase("Global") &&
+ !_name.equalsIgnoreCase("HABITACION_NEGRA"))
_objects.push_back(new Background(this, _name, backgroundScale));
if (!_floors[0].empty())
@@ -200,7 +201,7 @@ void Room::updateInteraction() {
if (updateOpeningInventory())
return;
- if (player.activeCharacter()->room() != this) { // TODO: Remove active character hack
+ if (false && player.activeCharacter()->room() != this) { // TODO: Remove active character hack
player.activeCharacter()->room() = this;
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 3b807b4ec70..a5229ab8588 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -451,8 +451,29 @@ private:
warning("STUB KERNEL CALL: ChangeCharacter");
return TaskReturn::finish(0);
case ScriptKernelTask::ChangeRoom:
- warning("STUB KERNEL CALL: ChangeRoom");
- return TaskReturn::finish(0);
+ if (strcmpi(getStringArg(0), "SALIR") == 0) {
+ g_engine->quitGame();
+ g_engine->player().changeRoom("SALIR", true);
+ }
+ else if (strcmpi(getStringArg(0), "MENUPRINCIPALINICIO") == 0)
+ warning("STUB: change room to MenuPrincipalInicio special case");
+ else {
+ auto targetRoom = g_engine->world().getRoomByName(getStringArg(0));
+ if (targetRoom == nullptr)
+ error("Invalid room name: %s\n", getStringArg(0));
+ if (process().isActiveForPlayer()) {
+ g_engine->player().heldItem() = nullptr;
+ if (g_engine->player().currentRoom() == &g_engine->world().inventory())
+ g_engine->world().inventory().close();
+ if (targetRoom == &g_engine->world().inventory())
+ g_engine->world().inventory().open();
+ else
+ g_engine->player().changeRoom(targetRoom->name(), true);
+ // TODO: Change music on kernel change room
+ }
+ g_engine->script().createProcess(process().character(), "ENTRAR_" + targetRoom->name(), ScriptFlags::AllowMissing);
+ }
+ return TaskReturn::finish(1);
case ScriptKernelTask::ToggleRoomFloor:
if (process().character() == MainCharacterKind::None) {
if (g_engine->player().currentRoom() != nullptr)
@@ -530,9 +551,17 @@ private:
character->setPosition(target->position());
return TaskReturn::finish(1);
}
- case ScriptKernelTask::ChangeCharacterRoom:
- warning("STUB KERNEL CALL: ChangeCharacterRoom");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::ChangeCharacterRoom: {
+ auto *character = dynamic_cast<Character *>(g_engine->world().globalRoom().getObjectByName(getStringArg(0)));
+ if (character == nullptr)
+ error("Invalid character name: %s", getStringArg(0));
+ auto *targetRoom = g_engine->world().getRoomByName(getStringArg(1));
+ if (targetRoom == nullptr)
+ error("Invalid room name: %s", getStringArg(1));
+ character->resetTalking();
+ character->room() = targetRoom;
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::LerpCharacterLodBias:
warning("STUB KERNEL CALL: LerpCharacterLodBias");
return TaskReturn::finish(0);
@@ -594,8 +623,10 @@ private:
case ScriptKernelTask::WaitCamStopping:
return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
case ScriptKernelTask::CamFollow:
- warning("STUB KERNEL CALL: CamFollow");
- return TaskReturn::finish(0);
+ g_engine->camera().setFollow(
+ &g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(0)),
+ getNumberArg(1) != 0);
+ return TaskReturn::finish(1);
case ScriptKernelTask::CamShake:
warning("STUB KERNEL CALL: CamShake");
return TaskReturn::finish(0);
Commit: 7f2398324200fad980ad2149b6bf2581df522f34
https://github.com/scummvm/scummvm/commit/7f2398324200fad980ad2149b6bf2581df522f34
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Fix closing game during character processes
Changed paths:
engines/alcachofa/scheduler.cpp
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 61a7e1b783a..8456a520649 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -126,12 +126,12 @@ void Process::debugPrint() {
static void killProcessesForIn(MainCharacterKind characterKind, Array<Process *> &processes, uint firstIndex) {
assert(firstIndex <= processes.size());
- uint count = processes.size() - firstIndex;
- for (uint i = 0; i < count; i++) {
+ for (uint i = 0; i < processes.size() - firstIndex; i++) {
Process **process = &processes[processes.size() - 1 - i];
if ((*process)->character() == characterKind || characterKind == MainCharacterKind::None) {
delete *process;
processes.erase(process);
+ i--; // underflow is fine here
}
}
}
Commit: f18ced5d41ecddd886ac8ce3d5cad5b59bb601da
https://github.com/scummvm/scummvm/commit/f18ced5d41ecddd886ac8ce3d5cad5b59bb601da
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add PlayVideo kernel call
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 928bd49df44..99730b2602f 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -31,6 +31,7 @@
#include "engines/util.h"
#include "graphics/paletteman.h"
#include "graphics/framelimiter.h"
+#include "video/mpegps_decoder.h"
#include "rooms.h"
#include "script.h"
@@ -99,6 +100,42 @@ Common::Error AlcachofaEngine::run() {
return Common::kNoError;
}
+void AlcachofaEngine::playVideo(int32 videoId) {
+ Video::MPEGPSDecoder decoder;
+ if (!decoder.loadFile(Common::Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1))))
+ error("Could not find video %d", videoId);
+ auto texture = _renderer->createTexture(decoder.getWidth(), decoder.getHeight(), false);
+ decoder.start();
+
+ Common::Event e;
+ while (!decoder.endOfVideo() && !shouldQuit()) {
+ if (decoder.needsUpdate())
+ {
+ auto surface = decoder.decodeNextFrame();
+ if (surface)
+ texture->update(*surface);
+ _renderer->begin();
+ _renderer->setBlendMode(BlendMode::Alpha);
+ _renderer->setLodBias(0.0f);
+ _renderer->setTexture(texture.get());
+ _renderer->quad({}, { (float)g_system->getWidth(), (float)g_system->getHeight() });
+ _renderer->end();
+ }
+
+ _input.nextFrame();
+ while (g_system->getEventManager()->pollEvent(e)) {
+ if (_input.handleEvent(e))
+ continue;
+ }
+ if (_input.wasAnyMouseReleased())
+ break;
+
+ g_system->updateScreen();
+ g_system->delayMillis(decoder.getTimeToNextFrame() / 2);
+ }
+ decoder.stop();
+}
+
Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
// The Serializer has methods isLoading() and isSaving()
// if you need to specific steps; for example setting
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 2d643c1c8de..a345a0b130b 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -72,6 +72,8 @@ public:
inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
+ void playVideo(int32 videoId);
+
uint32 getFeatures() const;
/**
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 823f7f5f868..f373b1980e7 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -80,7 +80,7 @@ public:
GL_CALL(glDeleteTextures(1, &_handle));
}
- virtual void update(const ManagedSurface &surface) {
+ virtual void update(const Surface &surface) {
OpenGLFormat format = getOpenGLFormatOf(surface.format);
assert(surface.w == size().x && surface.h == size().y);
assert(format.isValid());
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 24119792664..b65dcfdd53c 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -62,7 +62,8 @@ public:
ITexture(Common::Point size);
virtual ~ITexture() = default;
- virtual void update(const Graphics::ManagedSurface &surface) = 0;
+ virtual void update(const Graphics::Surface &surface) = 0;
+ inline void update(const Graphics::ManagedSurface &surface) { update(surface.rawSurface()); }
inline const Common::Point &size() const { return _size; }
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index a5229ab8588..575fd325b45 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -414,7 +414,7 @@ private:
switch (task) {
// sound/video
case ScriptKernelTask::PlayVideo:
- warning("STUB KERNEL CALL: PlayVideo");
+ g_engine->playVideo(getNumberArg(0));
return TaskReturn::finish(0);
case ScriptKernelTask::PlaySound:
warning("STUB KERNEL CALL: PlaySound");
Commit: 8ad4f2e58b3e5f54b9c2328e0d043c99e2e85932
https://github.com/scummvm/scummvm/commit/8ad4f2e58b3e5f54b9c2328e0d043c99e2e85932
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add permanent fade state
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 2bde10577bb..1d1d830b70a 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -755,23 +755,29 @@ struct FadeTask : public Task {
FadeTask(Process &process, FadeType fadeType,
float from, float to,
uint32 duration, EasingType easingType,
- int8 order)
+ int8 order,
+ PermanentFadeAction permanentFadeAction)
: Task(process)
, _fadeType(fadeType)
, _from(from)
, _to(to)
, _duration(duration)
, _easingType(easingType)
- , _order(order) {}
+ , _order(order)
+ , _permanentFadeAction(permanentFadeAction){}
virtual TaskReturn run() override {
TASK_BEGIN;
+ if (_permanentFadeAction == PermanentFadeAction::UnsetFaded)
+ g_engine->player().setPermanentFade(false);
_startTime = g_system->getMillis();
while (g_system->getMillis() - _startTime < _duration) {
draw((g_system->getMillis() - _startTime) / (float)_duration);
TASK_YIELD;
}
draw(1.0f); // so that during a loading lag the screen is completly black/white
+ if (_permanentFadeAction == PermanentFadeAction::SetFaded)
+ g_engine->player().setPermanentFade(true);
TASK_END;
}
@@ -792,17 +798,19 @@ private:
uint32 _startTime = 0, _duration;
EasingType _easingType;
int8 _order;
+ PermanentFadeAction _permanentFadeAction;
};
Task *fade(Process &process, FadeType fadeType,
float from, float to,
int32 duration, EasingType easingType,
- int8 order) {
+ int8 order,
+ PermanentFadeAction permanentFadeAction) {
if (duration <= 0)
return new DelayTask(process, 0);
if (!process.isActiveForPlayer())
return new DelayTask(process, (uint32)duration);
- return new FadeTask(process, fadeType, from, to, duration, easingType, order);
+ return new FadeTask(process, fadeType, from, to, duration, easingType, order, permanentFadeAction);
}
DrawQueue::DrawQueue(IRenderer *renderer)
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index b65dcfdd53c..ff8379cfdfb 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -381,6 +381,12 @@ enum class FadeType {
// TODO: Add CrossFade fade type
};
+enum class PermanentFadeAction {
+ Nothing,
+ SetFaded,
+ UnsetFaded
+};
+
class FadeDrawRequest : public IDrawRequest {
public:
FadeDrawRequest(FadeType type, float value, int8 order);
@@ -395,7 +401,8 @@ private:
Task *fade(Process &process, FadeType fadeType,
float from, float to,
int32 duration, EasingType easingType,
- int8 order);
+ int8 order,
+ PermanentFadeAction permanentFadeAction = PermanentFadeAction::Nothing);
class BumpAllocator {
public:
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 02beea7a10e..050e548cc6e 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -44,6 +44,11 @@ void Player::postUpdate() {
_pressedObject = nullptr;
}
+void Player::drawScreenStates() {
+ if (_isPermanentFaded && !_isOptionsMenuOpen)
+ g_engine->drawQueue().add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
+}
+
void Player::updateCursor() {
if (_isOptionsMenuOpen || !_isGameLoaded)
_cursorFrameI = 0;
@@ -93,6 +98,8 @@ void Player::drawCursor(bool forceDefaultCursor) {
}
void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera) {
+ debug("Change room to %s", targetRoomName.c_str());
+
// original would be to always free all resources from globalRoom, inventory, GlobalUI
if (targetRoomName.equalsIgnoreCase("SALIR")) {
_currentRoom = nullptr;
@@ -243,4 +250,8 @@ void Player::triggerDoor(const Door *door) {
g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
}
+void Player::setPermanentFade(bool isFaded) {
+ _isPermanentFaded = isFaded;
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index e8170185b78..3d65dce9947 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -48,12 +48,14 @@ public:
void preUpdate();
void postUpdate();
+ void drawScreenStates(); // black borders and/or permanent fade
void updateCursor();
void drawCursor(bool forceDefaultCursor = false);
void changeRoom(const Common::String &targetRoomName, bool resetCamera);
void changeRoomToBeforeInventory();
void triggerObject(ObjectBase *object, const char *action);
void triggerDoor(const Door *door);
+ void setPermanentFade(bool isFaded);
private:
Common::ScopedPtr<Animation> _cursorAnimation;
@@ -68,7 +70,8 @@ private:
bool
_isOptionsMenuOpen = false,
_isGameLoaded = true,
- _didLoadGlobalRooms = false;
+ _didLoadGlobalRooms = false,
+ _isPermanentFaded = false;
};
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index b15b3a003ac..4b64996246a 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -159,6 +159,7 @@ void Room::update() {
drawObjects();
world().globalRoom().drawObjects();
// TODO: Draw black borders
+ g_engine->player().drawScreenStates();
g_engine->drawQueue().draw();
drawDebug();
world().globalRoom().drawDebug();
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 575fd325b45..15701db2aa6 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -695,16 +695,20 @@ private:
getNumberArg(2), (EasingType)getNumberArg(4), getNumberArg(3)));
case ScriptKernelTask::FadeIn:
return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
- 1.0f, 0.0f, getNumberArg(0), EasingType::Out, -5));
+ 1.0f, 0.0f, getNumberArg(0), EasingType::Out, -5,
+ PermanentFadeAction::UnsetFaded));
case ScriptKernelTask::FadeOut:
return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
- 0.0f, 1.0f, getNumberArg(0), EasingType::Out, -5));
+ 0.0f, 1.0f, getNumberArg(0), EasingType::Out, -5,
+ PermanentFadeAction::SetFaded));
case ScriptKernelTask::FadeIn2:
return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
- 0.0f, 1.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5));
+ 0.0f, 1.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5,
+ PermanentFadeAction::UnsetFaded));
case ScriptKernelTask::FadeOut2:
return TaskReturn::waitFor(fade(process(), FadeType::ToBlack,
- 1.0f, 0.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5));
+ 1.0f, 0.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5,
+ PermanentFadeAction::SetFaded));
// Unused and useless
case ScriptKernelTask::SetActiveTextureSet:
Commit: 30419251dc9eb98ae33b383fc24b7a387be1b76c
https://github.com/scummvm/scummvm/commit/30419251dc9eb98ae33b383fc24b7a387be1b76c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Ignore additional missing animation
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 1d1d830b70a..0469ed8aefc 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -183,7 +183,8 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
void AnimationBase::loadMissingAnimation() {
// only allow missing animations we know are faulty in the original game
- if (!_fileName.equalsIgnoreCase("ANIMACION.AN0"))
+ if (!_fileName.equalsIgnoreCase("ANIMACION.AN0") &&
+ !_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0"))
error("Could not open animation %s", _fileName.c_str());
// otherwise setup a functioning but empty animation
Commit: 37aabca132feea4526ee6037e206c89554417290
https://github.com/scummvm/scummvm/commit/37aabca132feea4526ee6037e206c89554417290
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Fix incompatibilities after rebase
Changed paths:
engines/alcachofa/detection.cpp
engines/alcachofa/detection.h
engines/alcachofa/graphics.cpp
engines/alcachofa/metaengine.cpp
engines/alcachofa/metaengine.h
engines/alcachofa/module.mk
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index f743c0465da..0af2d47309a 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -38,8 +38,8 @@ const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
DEBUG_CHANNEL_END
};
-AlcachofaMetaEngineDetection::AlcachofaMetaEngineDetection() : AdvancedMetaEngineDetection(Alcachofa::gameDescriptions,
- sizeof(ADGameDescription), Alcachofa::alcachofaGames) {
+AlcachofaMetaEngineDetection::AlcachofaMetaEngineDetection() : AdvancedMetaEngineDetection<ADGameDescription>(
+ Alcachofa::gameDescriptions, Alcachofa::alcachofaGames) {
_flags |= kADFlagMatchFullPaths;
}
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 62298ab9b14..5012d77e2a6 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -42,7 +42,7 @@ extern const ADGameDescription gameDescriptions[];
} // End of namespace Alcachofa
-class AlcachofaMetaEngineDetection : public AdvancedMetaEngineDetection {
+class AlcachofaMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
static const DebugChannelDef debugFlagList[];
public:
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 0469ed8aefc..9bac7851553 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -177,8 +177,10 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
if (source->w == 2 && source->h == 1)
return nullptr;
- auto target = source->convertTo(BlendBlit::getSupportedPixelFormat(), decoder.getPalette(), decoder.getPaletteColorCount());
- return new ManagedSurface(target);
+ auto target = new ManagedSurface();
+ target->setPalette(decoder.getPalette(), 0, decoder.getPaletteColorCount());
+ target->convertFrom(*source, BlendBlit::getSupportedPixelFormat());
+ return target;
}
void AnimationBase::loadMissingAnimation() {
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index b00bbba2d43..8b45c140cf0 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -22,7 +22,6 @@
#include "common/translation.h"
#include "alcachofa/metaengine.h"
-#include "alcachofa/detection.h"
#include "alcachofa/alcachofa.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index e31ae83271a..e50800e10ca 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -24,7 +24,7 @@
#include "engines/advancedDetector.h"
-class AlcachofaMetaEngine : public AdvancedMetaEngine {
+class AlcachofaMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override;
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index f32728feb12..bca3bc181a8 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -2,8 +2,23 @@ MODULE := engines/alcachofa
MODULE_OBJS = \
alcachofa.o \
+ camera.cpp \
+ common.cpp \
console.o \
- metaengine.o
+ game-objects.cpp \
+ general-objects.cpp \
+ graphics.cpp \
+ graphics-opengl.cpp \
+ Input.cpp \
+ metaengine.o \
+ player.cpp \
+ rooms.cpp \
+ scheduler.cpp \
+ script.cpp \
+ shape.cpp \
+ sounds.cpp \
+ ui-objects.cpp \
+
# This module can be built as a plugin
ifeq ($(ENABLE_ALCACHOFA), DYNAMIC_PLUGIN)
Commit: d9450568a0ab9e1e2852ee7deb61afbd32b25668
https://github.com/scummvm/scummvm/commit/d9450568a0ab9e1e2852ee7deb61afbd32b25668
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:48+02:00
Commit Message:
ALCACHOFA: Add ClosestFloorPoint debug mode
Changed paths:
A engines/alcachofa/debug.h
engines/alcachofa/Input.cpp
engines/alcachofa/Input.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/Input.cpp b/engines/alcachofa/Input.cpp
index 86c9181b86f..fb22ea6f3e4 100644
--- a/engines/alcachofa/Input.cpp
+++ b/engines/alcachofa/Input.cpp
@@ -34,6 +34,9 @@ void Input::nextFrame() {
}
bool Input::handleEvent(const Common::Event &event) {
+ if (_debugInput != nullptr)
+ return _debugInput->handleEvent(event);
+
switch (event.type) {
case EVENT_LBUTTONDOWN:
_wasMouseLeftPressed = true;
@@ -62,4 +65,15 @@ bool Input::handleEvent(const Common::Event &event) {
}
}
+void Input::toggleDebugInput(bool debugMode) {
+ if (!debugMode) {
+ _debugInput.reset();
+ return;
+ }
+ if (_debugInput == nullptr)
+ _debugInput.reset(new Input());
+ nextFrame(); // resets frame-specific flags
+ _isMouseLeftDown = _isMouseRightDown = false;
+}
+
}
diff --git a/engines/alcachofa/Input.h b/engines/alcachofa/Input.h
index 248a40a62c2..9f1f6a5e31c 100644
--- a/engines/alcachofa/Input.h
+++ b/engines/alcachofa/Input.h
@@ -23,6 +23,7 @@
#define INPUT_H
#include "common/events.h"
+#include "common/ptr.h"
namespace Alcachofa {
@@ -39,9 +40,11 @@ public:
inline bool isAnyMouseDown() const { return _isMouseLeftDown || _isMouseRightDown; }
inline const Common::Point &mousePos2D() const { return _mousePos2D; }
inline const Common::Point &mousePos3D() const { return _mousePos3D; }
+ const Input &debugInput() const { scumm_assert(_debugInput != nullptr); return *_debugInput; }
void nextFrame();
bool handleEvent(const Common::Event &event);
+ void toggleDebugInput(bool debugMode); ///< Toggles input debug mode which blocks any input not retrieved with debugInput
private:
bool
@@ -54,6 +57,7 @@ private:
Common::Point
_mousePos2D,
_mousePos3D;
+ Common::ScopedPtr<Input> _debugInput;
};
}
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 99730b2602f..880b6448758 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -35,6 +35,7 @@
#include "rooms.h"
#include "script.h"
+#include "debug.h"
using namespace Math;
@@ -67,9 +68,9 @@ Common::Error AlcachofaEngine::run() {
_script.reset(new Script());
_player.reset(new Player());
- //_script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
- //_player->changeRoom("MINA", true);
- _script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
+ _script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
+ _player->changeRoom("MINA", true);
+ //_script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
_scheduler.run();
Common::Event e;
@@ -88,7 +89,8 @@ Common::Error AlcachofaEngine::run() {
_player->preUpdate();
_player->currentRoom()->update();
_player->postUpdate();
-
+ if (_debugHandler != nullptr)
+ _debugHandler->update();
_renderer->end();
// Delay for a bit. All events loops should have a delay
@@ -136,6 +138,16 @@ void AlcachofaEngine::playVideo(int32 videoId) {
decoder.stop();
}
+void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param)
+{
+ switch (mode)
+ {
+ case DebugMode::ClosestFloorPoint: _debugHandler.reset(new ClosestFloorPointDebugHandler(param)); break;
+ default: _debugHandler.reset(nullptr);
+ }
+ _input.toggleDebugInput(isDebugModeActive());
+}
+
Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
// The Serializer has methods isLoading() and isSaving()
// if you need to specific steps; for example setting
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index a345a0b130b..0b48f5c01a4 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -44,6 +44,7 @@
namespace Alcachofa {
+class IDebugHandler;
class IRenderer;
class DrawQueue;
class World;
@@ -70,9 +71,11 @@ public:
inline World &world() { return *_world; }
inline Script &script() { return *_script; }
inline Scheduler &scheduler() { return _scheduler; }
- inline Console &console() { return *_console; }
+ inline Console &console() { return *_console; }
+ inline bool isDebugModeActive() const { return _debugHandler != nullptr; }
void playVideo(int32 videoId);
+ void setDebugMode(DebugMode debugMode, int32 param);
uint32 getFeatures() const;
@@ -119,6 +122,7 @@ public:
private:
Console *_console = new Console();
+ Common::ScopedPtr<IDebugHandler> _debugHandler;
Common::ScopedPtr<IRenderer> _renderer;
Common::ScopedPtr<DrawQueue> _drawQueue;
Common::ScopedPtr<World> _world;
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 81206ab9779..571325b7749 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -41,11 +41,22 @@ Console::Console() : GUI::Debugger() {
registerCmd("disableDebugDraw", WRAP_METHOD(Console, cmdDisableDebugDraw));
registerCmd("pickup", WRAP_METHOD(Console, cmdItem));
registerCmd("drop", WRAP_METHOD(Console, cmdItem));
+ registerCmd("debugMode", WRAP_METHOD(Console, cmdDebugMode));
}
Console::~Console() {
}
+bool Console::isAnyDebugDrawingOn() const
+{
+ return
+ g_engine->isDebugModeActive() ||
+ _showInteractables ||
+ _showCharacters ||
+ _showFloor ||
+ _showFloorColor;
+}
+
bool Console::cmdVar(int argc, const char **args) {
auto &script = g_engine->script();
if (argc < 2 || argc > 3)
@@ -193,4 +204,31 @@ bool Console::cmdItem(int argc, const char **args) {
return true;
}
+bool Console::cmdDebugMode(int argc, const char **args)
+{
+ if (argc < 2 || argc > 3) {
+ debugPrintf("usage: debugMode <mode> [<param>]\n");
+ debugPrintf("modes:\n");
+ debugPrintf(" 0 - None, disables debug mode\n");
+ debugPrintf(" 1 - Closest floor point, param limits to polygon\n");
+ return true;
+ }
+
+ int32 param = -1;
+ if (argc > 2)
+ {
+ char *end = nullptr;
+ param = (int32)strtol(args[2], &end, 10);
+ if (end == nullptr || *end != '\0')
+ {
+ debugPrintf("Debug mode parameter can only be integers");
+ return true;
+ }
+ }
+
+ auto mode = (DebugMode)strtol(args[1], nullptr, 10);
+ g_engine->setDebugMode(mode, param);
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index b59755b16f3..ccf045de5fa 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -27,6 +27,11 @@
namespace Alcachofa {
+enum class DebugMode {
+ None,
+ ClosestFloorPoint
+};
+
class Console : public GUI::Debugger {
public:
Console();
@@ -36,14 +41,7 @@ public:
inline bool showCharacters() const { return _showCharacters; }
inline bool showFloor() const { return _showFloor; }
inline bool showFloorColor() const { return _showFloorColor; }
-
- inline bool isAnyDebugDrawingOn() const {
- return
- _showInteractables ||
- _showCharacters ||
- _showFloor ||
- _showFloorColor;
- }
+ bool isAnyDebugDrawingOn() const;
private:
bool cmdVar(int argc, const char **args);
@@ -53,6 +51,7 @@ private:
bool cmdChangeRoom(int argc, const char **args);
bool cmdDisableDebugDraw(int argc, const char **args);
bool cmdItem(int argc, const char **args);
+ bool cmdDebugMode(int argc, const char **args);
bool _showInteractables = true;
bool _showCharacters = true;
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
new file mode 100644
index 00000000000..a768fcbbd2b
--- /dev/null
+++ b/engines/alcachofa/debug.h
@@ -0,0 +1,64 @@
+/* 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/>.
+ *
+ */
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#include "alcachofa.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+class IDebugHandler {
+public:
+ virtual ~IDebugHandler() = default;
+
+ virtual void update() = 0;
+};
+
+class ClosestFloorPointDebugHandler final : public IDebugHandler {
+ int32 _polygonI;
+public:
+ ClosestFloorPointDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
+
+ virtual void update() final
+ {
+ auto mousePos2D = g_engine->input().debugInput().mousePos2D();
+ auto mousePos3D = g_engine->input().debugInput().mousePos3D();
+ auto* floor = g_engine->player().currentRoom()->activeFloor();
+ Point target3D;
+
+ if (_polygonI < 0 || (uint)_polygonI >= floor->polygonCount())
+ target3D = floor->getClosestPoint(mousePos3D);
+ else
+ target3D = floor->at((uint)_polygonI)._points[0];
+ auto target2Dv = g_engine->camera().transform3Dto2D(
+ { (float)target3D.x, (float)target3D.y, kBaseScale });
+
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ renderer->debugPolyline(mousePos2D, { (int16)target2Dv.x(), (int16)target2Dv.y() });
+ }
+};
+
+}
+
+#endif // DEBUG_H
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index ff8379cfdfb..4cb29f0fdcd 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -108,6 +108,12 @@ public:
const Shape &shape,
Color color = kDebugRed
);
+
+ inline void debugPolyline(Common::Point a, Common::Point b, Color color = kDebugRed)
+ {
+ Math::Vector2d points[] = { { (float)a.x, (float)a.y }, { (float)b.x, (float)b.y } };
+ debugPolygon({ points, 2 }, color);
+ }
};
enum class AnimationFolder {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 4b64996246a..f6c0e1b429e 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -141,19 +141,23 @@ ObjectBase *Room::getObjectByName(const Common::String &name) const {
}
void Room::update() {
- updateScripts();
+ if (!g_engine->isDebugModeActive())
+ {
+ updateScripts();
- if (g_engine->player().currentRoom() == this) {
- updateRoomBounds();
- updateClosingInventory();
- if (!updateInput())
- return;
+ if (g_engine->player().currentRoom() == this) {
+ updateRoomBounds();
+ updateClosingInventory();
+ if (!updateInput())
+ return;
+ }
+ if (!g_engine->player().isOptionsMenuOpen() &&
+ g_engine->player().currentRoom() != &g_engine->world().inventory())
+ world().globalRoom().updateObjects();
+ if (g_engine->player().currentRoom() == this)
+ updateObjects();
}
- if (!g_engine->player().isOptionsMenuOpen() &&
- g_engine->player().currentRoom() != &g_engine->world().inventory())
- world().globalRoom().updateObjects();
- if (g_engine->player().currentRoom() == this)
- updateObjects();
+
if (g_engine->player().currentRoom() == this) {
g_engine->camera().update();
drawObjects();
Commit: 43c17c33d02bf243d09c721c97395dd17b5c068e
https://github.com/scummvm/scummvm/commit/43c17c33d02bf243d09c721c97395dd17b5c068e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Improve Polygon::closestPointTo
Changed paths:
engines/alcachofa/debug.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index a768fcbbd2b..f01659ad5a1 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -48,9 +48,9 @@ public:
Point target3D;
if (_polygonI < 0 || (uint)_polygonI >= floor->polygonCount())
- target3D = floor->getClosestPoint(mousePos3D);
+ target3D = floor->closestPointTo(mousePos3D);
else
- target3D = floor->at((uint)_polygonI)._points[0];
+ target3D = floor->at((uint)_polygonI).closestPointTo(mousePos3D);
auto target2Dv = g_engine->camera().transform3Dto2D(
{ (float)target3D.x, (float)target3D.y, kBaseScale });
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index ec6d915801a..ac8cab2d6d6 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -358,9 +358,9 @@ void WalkingCharacter::update() {
auto activeFloor = room()->activeFloor();
if (activeFloor != nullptr) {
if (activeFloor->polygonContaining(_sourcePos) < 0)
- _sourcePos = _currentPos = activeFloor->getClosestPoint(_sourcePos);
+ _sourcePos = _currentPos = activeFloor->closestPointTo(_sourcePos);
if (activeFloor->polygonContaining(_currentPos) < 0)
- _currentPos = activeFloor->getClosestPoint(_currentPos);
+ _currentPos = activeFloor->closestPointTo(_currentPos);
}
if (!_isWalking) {
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index a136888af8b..89e51be1eff 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -88,6 +88,51 @@ EdgeDistances Polygon::edgeDistances(uint startPointI, const Point &query) const
return distances;
}
+static Point wiggleOnToLine(Point a, Point b, Point q)
+{
+ // due to rounding errors contains(bestPoint) might be false for on-edge closest points, let's fix that
+ // maybe there is a more mathematical solution to this, but it suffices for now
+ if (sideOfLine(a, b, q) >= 0) return q;
+ if (sideOfLine(a, b, q + Point(+1, 0)) >= 0) return q + Point(+1, 0);
+ if (sideOfLine(a, b, q + Point(-1, 0)) >= 0) return q + Point(-1, 0);
+ if (sideOfLine(a, b, q + Point(0, +1)) >= 0) return q + Point(0, +1);
+ if (sideOfLine(a, b, q + Point(0, -1)) >= 0) return q + Point(0, -1);
+ assert(false && "More than two pixels means some more serious math error occured");
+}
+
+Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) const
+{
+ assert(_points.size() > 0);
+ Common::Point bestPoint = {};
+ distanceSqr = std::numeric_limits<float>::infinity();
+ for (uint i = 0; i < _points.size(); i++)
+ {
+ auto edgeDists = edgeDistances(i, query);
+ if (edgeDists._onEdge < 0.0f)
+ {
+ float pointDistSqr = as2D(query - _points[i]).getSquareMagnitude();
+ if (pointDistSqr < distanceSqr)
+ {
+ bestPoint = _points[i];
+ distanceSqr = pointDistSqr;
+ }
+ }
+ if (edgeDists._onEdge >= 0.0f && edgeDists._onEdge <= edgeDists._edgeLength)
+ {
+ float edgeDistSqr = powf(edgeDists._toEdge , 2.0f);
+ if (edgeDistSqr < distanceSqr)
+ {
+ distanceSqr = edgeDistSqr;
+ uint j = (i + 1) % _points.size();
+ bestPoint = _points[i] + (_points[j] - _points[i]) * (edgeDists._onEdge / edgeDists._edgeLength);
+ bestPoint = wiggleOnToLine(_points[i], _points[j], bestPoint);
+ }
+ }
+ }
+ assert(contains(bestPoint));
+ return bestPoint;
+}
+
static float depthAtForLine(const Point &a, const Point &b, const Point &q, int8 depthA, int8 depthB) {
return (sqrtf(a.sqrDist(q)) / a.sqrDist(b) * depthB + depthA) * 0.01f;
}
@@ -251,6 +296,25 @@ bool Shape::contains(const Point &query) const {
return polygonContaining(query) >= 0;
}
+Point Shape::closestPointTo(const Point &query, int32 &polygonI) const
+{
+ assert(_polygons.size() > 0);
+ float bestDistanceSqr = std::numeric_limits<float>::infinity();
+ Point bestPoint = {};
+ for (uint i = 0; i < _polygons.size(); i++)
+ {
+ float curDistanceSqr = std::numeric_limits<float>::infinity();
+ Point curPoint = at(i).closestPointTo(query, curDistanceSqr);
+ if (curDistanceSqr < bestDistanceSqr)
+ {
+ bestDistanceSqr = curDistanceSqr;
+ bestPoint = curPoint;
+ polygonI = (int32)i;
+ }
+ }
+ return bestPoint;
+}
+
void Shape::setAsRectangle(const Rect &rect) {
_polygons.resize(1);
_polygons[0] = { 0, 4 };
@@ -441,8 +505,7 @@ bool PathFindingShape::findPath(const Point &from, const Point &to_, Stack<Point
return false;
int32 toContaining = polygonContaining(to);
if (toContaining < 0) {
- to = getClosestPoint(to);
- toContaining = polygonContaining(to);
+ to = closestPointTo(to, toContaining);
assert(toContaining >= 0);
}
//if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
@@ -523,24 +586,6 @@ void PathFindingShape::floydWarshallPath(
path.push(_linkPoints[fromLink]);
}
-Point PathFindingShape::getClosestPoint(const Point &query) const {
- // TODO: Improve this function, it does not seem correct
-
- assert(!_points.empty());
- Point bestPoint;
- uint bestDistance = UINT_MAX;
- for (auto p : _points) {
- uint curDistance = query.sqrDist(p);
- if (curDistance < bestDistance) {
- bestDistance = curDistance;
- bestPoint = p;
- }
- }
-
- assert(bestDistance < UINT_MAX);
- return bestPoint;
-}
-
FloorColorShape::FloorColorShape() {}
FloorColorShape::FloorColorShape(ReadStream &stream) {
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 0452cd45d8f..17d7aa4b16f 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -46,6 +46,11 @@ struct Polygon {
bool contains(const Common::Point &query) const;
EdgeDistances edgeDistances(uint startPointI, const Common::Point &query) const;
+ Common::Point closestPointTo(const Common::Point &query, float& distanceSqr) const;
+ inline Common::Point closestPointTo(const Common::Point &query) const {
+ float dummy;
+ return closestPointTo(query, dummy);
+ }
};
struct PathFindingPolygon : Polygon {
@@ -122,6 +127,11 @@ public:
Polygon at(uint index) const;
int32 polygonContaining(const Common::Point &query) const;
bool contains(const Common::Point &query) const;
+ Common::Point closestPointTo(const Common::Point &query, int32 &polygonI) const;
+ inline Common::Point closestPointTo(const Common::Point &query) const {
+ int32 dummy;
+ return closestPointTo(query, dummy);
+ }
void setAsRectangle(const Common::Rect &rect);
protected:
@@ -160,7 +170,6 @@ public:
const Common::Point &from,
const Common::Point &to,
Common::Stack<Common::Point> &path) const;
- Common::Point getClosestPoint(const Common::Point &query) const;
private:
void setupLinks();
Commit: c52ad2570337b3899c0f047a800edf937d7b94f2
https://github.com/scummvm/scummvm/commit/c52ad2570337b3899c0f047a800edf937d7b94f2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Add FloorIntersections debug handler
Changed paths:
engines/alcachofa/Input.cpp
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/debug.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/Input.cpp b/engines/alcachofa/Input.cpp
index fb22ea6f3e4..92c97325fbf 100644
--- a/engines/alcachofa/Input.cpp
+++ b/engines/alcachofa/Input.cpp
@@ -27,6 +27,9 @@ using namespace Common;
namespace Alcachofa {
void Input::nextFrame() {
+ if (_debugInput != nullptr)
+ return _debugInput->nextFrame();
+
_wasMouseLeftPressed = false;
_wasMouseRightPressed = false;
_wasMouseLeftReleased = false;
@@ -70,10 +73,10 @@ void Input::toggleDebugInput(bool debugMode) {
_debugInput.reset();
return;
}
- if (_debugInput == nullptr)
- _debugInput.reset(new Input());
nextFrame(); // resets frame-specific flags
_isMouseLeftDown = _isMouseRightDown = false;
+ if (_debugInput == nullptr)
+ _debugInput.reset(new Input());
}
}
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 880b6448758..427a9cf1d8d 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -142,7 +142,12 @@ void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param)
{
switch (mode)
{
- case DebugMode::ClosestFloorPoint: _debugHandler.reset(new ClosestFloorPointDebugHandler(param)); break;
+ case DebugMode::ClosestFloorPoint:
+ _debugHandler.reset(new ClosestFloorPointDebugHandler(param));
+ break;
+ case DebugMode::FloorIntersections:
+ _debugHandler.reset(new FloorIntersectionsDebugHandler(param));
+ break;
default: _debugHandler.reset(nullptr);
}
_input.toggleDebugInput(isDebugModeActive());
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 282932f65f9..3be4477e706 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -156,6 +156,11 @@ Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
_cur._scale * kBaseScale / vh.z());
}
+Point Camera::transform3Dto2D(Point p3d) const {
+ auto v2d = transform3Dto2D({ (float)p3d.x, (float)p3d.y, kBaseScale });
+ return { (int16)v2d.x(), (int16)v2d.y() };
+}
+
void Camera::update() {
// original would be some smoothing of delta times, let's not.
uint32 now = g_system->getMillis();
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index cdca9db6852..7d22cbef8c6 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -43,6 +43,7 @@ public:
void update();
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
Math::Vector3d transform3Dto2D(Math::Vector3d v) const;
+ Common::Point transform3Dto2D(Common::Point p) const;
void resetRotationAndScale();
void setRoomBounds(Common::Point bgSize, int16 bgScale);
void setFollow(WalkingCharacter *target, bool catchUp = false);
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 571325b7749..74afc720cf9 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -211,6 +211,7 @@ bool Console::cmdDebugMode(int argc, const char **args)
debugPrintf("modes:\n");
debugPrintf(" 0 - None, disables debug mode\n");
debugPrintf(" 1 - Closest floor point, param limits to polygon\n");
+ debugPrintf(" 2 - Floor edge intersections, param limits to polygon\n");
return true;
}
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index ccf045de5fa..5939f41a1c6 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -29,7 +29,8 @@ namespace Alcachofa {
enum class DebugMode {
None,
- ClosestFloorPoint
+ ClosestFloorPoint,
+ FloorIntersections
};
class Console : public GUI::Debugger {
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index f01659ad5a1..de9cba0997a 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -44,18 +44,72 @@ public:
{
auto mousePos2D = g_engine->input().debugInput().mousePos2D();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
- auto* floor = g_engine->player().currentRoom()->activeFloor();
- Point target3D;
+ auto floor = g_engine->player().currentRoom()->activeFloor();
+ auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (floor == nullptr || renderer == nullptr)
+ return;
+ Point target3D;
if (_polygonI < 0 || (uint)_polygonI >= floor->polygonCount())
target3D = floor->closestPointTo(mousePos3D);
else
target3D = floor->at((uint)_polygonI).closestPointTo(mousePos3D);
- auto target2Dv = g_engine->camera().transform3Dto2D(
- { (float)target3D.x, (float)target3D.y, kBaseScale });
+ renderer->debugPolyline(mousePos2D, g_engine->camera().transform3Dto2D(target3D));
+ }
+};
+class FloorIntersectionsDebugHandler final : public IDebugHandler {
+ int32 _polygonI;
+ Point _fromPos3D;
+public:
+ FloorIntersectionsDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
+
+ virtual void update() final
+ {
+ auto floor = g_engine->player().currentRoom()->activeFloor();
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
- renderer->debugPolyline(mousePos2D, { (int16)target2Dv.x(), (int16)target2Dv.y() });
+ if (floor == nullptr || renderer == nullptr)
+ return;
+
+ if (g_engine->input().debugInput().wasMouseLeftPressed())
+ _fromPos3D = g_engine->input().debugInput().mousePos3D();
+ renderer->debugPolyline(
+ g_engine->camera().transform3Dto2D(_fromPos3D),
+ g_engine->input().debugInput().mousePos2D(),
+ kDebugRed);
+
+ if (_polygonI >= 0 && (uint)_polygonI < floor->polygonCount())
+ drawIntersectionsFor(floor->at((uint)_polygonI), renderer);
+ else
+ {
+ for (uint i = 0; i < floor->polygonCount(); i++)
+ drawIntersectionsFor(floor->at(i), renderer);
+ }
+ }
+
+private:
+ static constexpr float kMarkerLength = 16;
+
+ void drawIntersectionsFor(const Polygon& polygon, IDebugRenderer* renderer)
+ {
+ auto &camera = g_engine->camera();
+ auto mousePos2D = g_engine->input().debugInput().mousePos2D();
+ auto mousePos3D = g_engine->input().debugInput().mousePos3D();
+ for (uint i = 0; i < polygon._points.size(); i++)
+ {
+ if (!polygon.intersectsEdge(i, _fromPos3D, mousePos3D))
+ continue;
+ auto a = camera.transform3Dto2D(polygon._points[i]);
+ auto b = camera.transform3Dto2D(polygon._points[(i + 1) % polygon._points.size()]);
+ auto mid = (a + b) / 2;
+ auto length = sqrtf(a.sqrDist(b));
+ auto normal = a - b;
+ normal = { normal.y, (int16)-normal.x};
+ auto inner = mid + normal * (kMarkerLength / length);
+
+ renderer->debugPolyline(a, b, kDebugGreen);
+ renderer->debugPolyline(mid, inner, kDebugGreen);
+ }
}
};
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 89e51be1eff..b4251aed8e8 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -71,6 +71,12 @@ bool Polygon::contains(const Point &query) const {
}
}
+bool Polygon::intersectsEdge(uint startPointI, Point a, Point b) const {
+ assert(startPointI < _points.size());
+ uint endPointI = (startPointI + 1) % _points.size();
+ return segmentsIntersect(_points[startPointI], _points[endPointI], a, b);
+}
+
EdgeDistances Polygon::edgeDistances(uint startPointI, const Point &query) const {
assert(startPointI < _points.size());
uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
@@ -98,6 +104,7 @@ static Point wiggleOnToLine(Point a, Point b, Point q)
if (sideOfLine(a, b, q + Point(0, +1)) >= 0) return q + Point(0, +1);
if (sideOfLine(a, b, q + Point(0, -1)) >= 0) return q + Point(0, -1);
assert(false && "More than two pixels means some more serious math error occured");
+ return q;
}
Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) const
@@ -529,8 +536,7 @@ bool PathFindingShape::canGoStraightThrough(
if (_targetQuads[fullI] < 0 || _targetQuads[fullI] == lastContainingI)
continue;
- uint j = i + 1 == toContaining._points.size() ? 0 : i + 1;
- if (segmentsIntersect(from, to, toContaining._points[i], toContaining._points[j])) {
+ if (toContaining.intersectsEdge(i, from, to)) {
foundPortal = true;
lastContainingI = toContainingI;
toContainingI = _targetQuads[fullI];
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 17d7aa4b16f..ac5d339ca5f 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -45,6 +45,7 @@ struct Polygon {
Common::Span<const Common::Point> _points;
bool contains(const Common::Point &query) const;
+ bool intersectsEdge(uint startPointI, Common::Point a, Common::Point b) const;
EdgeDistances edgeDistances(uint startPointI, const Common::Point &query) const;
Common::Point closestPointTo(const Common::Point &query, float& distanceSqr) const;
inline Common::Point closestPointTo(const Common::Point &query) const {
Commit: fc39a1344c790085ea973ced1b5a4ccdcf6ea81d
https://github.com/scummvm/scummvm/commit/fc39a1344c790085ea973ced1b5a4ccdcf6ea81d
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Fix floor polygon connections
Changed paths:
engines/alcachofa/common.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 783d6dbde86..559823d471b 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -76,6 +76,7 @@ static constexpr const Color kClear = { 0, 0, 0, 0 };
static constexpr const Color kDebugRed = { 250, 0, 0, 70 };
static constexpr const Color kDebugGreen = { 0, 255, 0, 85 };
static constexpr const Color kDebugBlue = { 0, 0, 255, 110 };
+static constexpr const Color kDebugLightBlue = { 80, 80, 255, 190 };
/**
* @brief This *fake* semaphore does not work in multi-threaded scenarios
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 74afc720cf9..1783cf9ecee 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -30,7 +30,8 @@ namespace Alcachofa {
Console::Console() : GUI::Debugger() {
registerVar("showInteractables", &_showInteractables);
registerVar("showCharacters", &_showCharacters);
- registerVar("showFloor", &_showFloor);
+ registerVar("showFloorShape", &_showFloor);
+ registerVar("showFloorEdges", &_showFloorEdges);
registerVar("showFloorColor", &_showFloorColor);
registerCmd("var", WRAP_METHOD(Console, cmdVar));
@@ -54,6 +55,7 @@ bool Console::isAnyDebugDrawingOn() const
_showInteractables ||
_showCharacters ||
_showFloor ||
+ _showFloorEdges ||
_showFloorColor;
}
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 5939f41a1c6..b21e06085a5 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -41,6 +41,7 @@ public:
inline bool showInteractables() const { return _showInteractables; }
inline bool showCharacters() const { return _showCharacters; }
inline bool showFloor() const { return _showFloor; }
+ inline bool showFloorEdges() const { return _showFloorEdges; }
inline bool showFloorColor() const { return _showFloorColor; }
bool isAnyDebugDrawingOn() const;
@@ -57,6 +58,7 @@ private:
bool _showInteractables = true;
bool _showCharacters = true;
bool _showFloor = true;
+ bool _showFloorEdges = false;
bool _showFloorColor = false;
};
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index f373b1980e7..dcde6af735e 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -248,7 +248,6 @@ public:
float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
- //GL_CALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
GL_CALL(glColor4f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f));
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
if (_currentTexture != nullptr)
@@ -256,7 +255,7 @@ public:
GL_CALL(glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, colors));
GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
-#if DEBUG
+#if _DEBUG
// make sure we crash instead of someone using our stack arrays
GL_CALL(glVertexPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
GL_CALL(glTexCoordPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index f6c0e1b429e..c5c6c55a20e 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -105,6 +105,8 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
_musicId = stream.readSByte();
_characterAlphaTint = stream.readByte();
auto backgroundScale = stream.readSint16LE();
+ if (_name == "MINA")
+ backgroundScale += 0;
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
_fixedCameraOnEntering = readBool(stream);
@@ -259,8 +261,28 @@ void Room::drawDebug() {
}
if (_activeFloorI < 0)
return;
- if (_activeFloorI >= 0 && g_engine->console().showFloor())
- renderer->debugShape(_floors[_activeFloorI], kDebugBlue);
+ auto &floor = _floors[_activeFloorI];
+ if (g_engine->console().showFloor())
+ renderer->debugShape(floor, kDebugBlue);
+
+ if (g_engine->console().showFloorEdges()) {
+ auto &camera = g_engine->camera();
+ for (uint polygonI = 0; polygonI < floor.polygonCount(); polygonI++)
+ {
+ auto polygon = floor.at(polygonI);
+ for (uint pointI = 0; pointI < polygon._points.size(); pointI++)
+ {
+ int32 targetI = floor.edgeTarget(polygonI, pointI);
+ if (targetI < 0)
+ continue;
+ Point a = camera.transform3Dto2D(polygon._points[pointI]);
+ Point b = camera.transform3Dto2D(polygon._points[(pointI + 1) % polygon._points.size()]);
+ Point source = (a + b) / 2;
+ Point target = camera.transform3Dto2D(floor.at((uint)targetI).midPoint());
+ renderer->debugPolyline(source, target, kDebugLightBlue);
+ }
+ }
+ }
}
void Room::loadResources() {
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index b4251aed8e8..5ff9a856ebd 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -31,11 +31,8 @@ static int sideOfLine(const Point &a, const Point &b, const Point &q) {
}
static bool segmentsIntersect(const Point &a1, const Point &b1, const Point &a2, const Point &b2) {
- // as there are a number of special cases to consider, this method is a direct translation
- // of the original engine
- // TODO: It is still bad and does sometimes not work correctly. Check this. keep in mind
- // it *could* also be a case of incorrect floor segments being passed into in the first place.
-
+ // as there are a number of special cases to consider,
+ // this method is a direct translation of the original engine
const auto sideOfLine = [](const Point &a, const Point &b, const Point q) {
return Alcachofa::sideOfLine(a, b, q) > 0;
};
@@ -140,6 +137,14 @@ Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) co
return bestPoint;
}
+Point Polygon::midPoint() const {
+ assert(_points.size() > 0);
+ Common::Point sum = {};
+ for (uint i = 0; i < _points.size(); i++)
+ sum += _points[i];
+ return sum / (int16)_points.size();
+}
+
static float depthAtForLine(const Point &a, const Point &b, const Point &q, int8 depthA, int8 depthB) {
return (sqrtf(a.sqrDist(q)) / a.sqrDist(b) * depthB + depthA) * 0.01f;
}
@@ -383,12 +388,14 @@ PathFindingShape::LinkPolygonIndices::LinkPolygonIndices() {
}
static Pair<int32, int32> orderPoints(const Polygon &polygon, int32 point1, int32 point2) {
- if ((point1 > point2 && point1 + 1 != (int32)polygon._points.size()) ||
- point2 + 1 == (int32)polygon._points.size()) {
+ if (point1 > point2) {
int32 tmp = point1;
point1 = point2;
point2 = tmp;
}
+ const int32 maxPointI = polygon._points.size() - 1;
+ if (point1 == 0 && point2 == maxPointI)
+ return { maxPointI, 0 };
return { point1, point2 };
}
@@ -409,7 +416,7 @@ void PathFindingShape::setupLinks() {
if (sharedPointCount > 1) {
auto outerPoints = orderPoints(outer, sharedPoints[0].first, sharedPoints[1].first);
auto innerPoints = orderPoints(inner, sharedPoints[0].second, sharedPoints[1].second);
- setupLinkEdge(outer, inner, outerPoints.first, outerPoints.second, innerPoints.first);
+ setupLinkEdge(outer, inner, outerPoints, innerPoints);
setupLinkPoint(outer, inner, sharedPoints[1]);
}
}
@@ -432,14 +439,15 @@ void PathFindingShape::setupLinkPoint(
void PathFindingShape::setupLinkEdge(
const PathFindingPolygon &outer,
const PathFindingPolygon &inner,
- int32 outerP1, int32 outerP2, int32 innerP) {
- _targetQuads[outer._index * kPointsPerPolygon + outerP1] = inner._index;
- _targetQuads[inner._index * kPointsPerPolygon + innerP] = outer._index;
- auto &outerLink = _linkIndices[outer._index]._points[outerP1];
- auto &innerLink = _linkIndices[inner._index]._points[innerP];
+ PathFindingShape::LinkIndex outerP,
+ PathFindingShape::LinkIndex innerP) {
+ _targetQuads[outer._index * kPointsPerPolygon + outerP.first] = inner._index;
+ _targetQuads[inner._index * kPointsPerPolygon + innerP.first] = outer._index;
+ auto &outerLink = _linkIndices[outer._index]._points[outerP.first];
+ auto &innerLink = _linkIndices[inner._index]._points[innerP.first];
if (outerLink.second < 0) {
outerLink.second = _linkPoints.size();
- _linkPoints.push_back((outer._points[outerP1] + outer._points[outerP2]) / 2);
+ _linkPoints.push_back((outer._points[outerP.first] + outer._points[outerP.second]) / 2);
}
innerLink.second = outerLink.second;
}
@@ -524,6 +532,12 @@ bool PathFindingShape::findPath(const Point &from, const Point &to_, Stack<Point
return true;
}
+int32 PathFindingShape::edgeTarget(uint polygonI, uint pointI) const {
+ assert(polygonI < polygonCount() && pointI < kPointsPerPolygon);
+ uint fullI = polygonI * kPointsPerPolygon + pointI;
+ return _targetQuads[fullI];
+}
+
bool PathFindingShape::canGoStraightThrough(
const Point &from, const Point &to,
int32 fromContainingI, int32 toContainingI) const {
@@ -532,14 +546,14 @@ bool PathFindingShape::canGoStraightThrough(
auto toContaining = at(toContainingI);
bool foundPortal = false;
for (uint i = 0; i < toContaining._points.size(); i++) {
- uint fullI = toContainingI * kPointsPerPolygon + i;
- if (_targetQuads[fullI] < 0 || _targetQuads[fullI] == lastContainingI)
+ int32 target = edgeTarget((uint)toContainingI, i);
+ if (target < 0 || target == lastContainingI)
continue;
if (toContaining.intersectsEdge(i, from, to)) {
foundPortal = true;
lastContainingI = toContainingI;
- toContainingI = _targetQuads[fullI];
+ toContainingI = target;
break;
}
}
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index ac5d339ca5f..b42239edce4 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -52,6 +52,7 @@ struct Polygon {
float dummy;
return closestPointTo(query, dummy);
}
+ Common::Point midPoint() const;
};
struct PathFindingPolygon : Polygon {
@@ -171,8 +172,11 @@ public:
const Common::Point &from,
const Common::Point &to,
Common::Stack<Common::Point> &path) const;
+ int32 edgeTarget(uint polygonI, uint pointI) const;
private:
+ using LinkIndex = Common::Pair<int32, int32>;
+
void setupLinks();
void setupLinkPoint(
const PathFindingPolygon &outer,
@@ -181,7 +185,7 @@ private:
void setupLinkEdge(
const PathFindingPolygon &outer,
const PathFindingPolygon &inner,
- int32 outerP1, int32 outerP2, int32 innerP);
+ LinkIndex outerP, LinkIndex innerP);
void initializeFloydWarshall();
void calculateFloydWarshall();
bool canGoStraightThrough(
@@ -208,7 +212,6 @@ private:
* the corresponding link point. The second point is the
* index to the artifical center point
*/
- using LinkIndex = Common::Pair<int32, int32>;
struct LinkPolygonIndices {
LinkPolygonIndices();
LinkIndex _points[kPointsPerPolygon];
Commit: 013ea9e5d8e159aa8e3118adc0df6cbdd964d73a
https://github.com/scummvm/scummvm/commit/013ea9e5d8e159aa8e3118adc0df6cbdd964d73a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Decrease texture size of fonts
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 9bac7851553..e64380c1d7b 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -407,9 +407,14 @@ 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
+ // 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])
+ continue;
cellSize.x = MAX(cellSize.x, image->w);
cellSize.y = MAX(cellSize.y, image->h);
}
@@ -422,6 +427,8 @@ 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;
+
int offsetX = (i % 16) * cellSize.x + (cellSize.x - _images[i]->w) / 2;
int offsetY = (i / 16) * cellSize.y + (cellSize.y - _images[i]->h) / 2;
fullBlend(*_images[i], atlasSurface, offsetX, offsetY);
Commit: af87f81354452f6895b62c6e91a28a7b68a12441
https://github.com/scummvm/scummvm/commit/af87f81354452f6895b62c6e91a28a7b68a12441
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Add script debug tracing
Changed paths:
A engines/alcachofa/script-debug.h
engines/alcachofa/detection.cpp
engines/alcachofa/detection.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index 0af2d47309a..1d0c5a94ad9 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -31,9 +31,6 @@
const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
{ Alcachofa::kDebugGraphics, "Graphics", "Graphics debug level" },
- { Alcachofa::kDebugPath, "Path", "Pathfinding debug level" },
- { Alcachofa::kDebugFilePath, "FilePath", "File path debug level" },
- { Alcachofa::kDebugScan, "Scan", "Scan for unrecognised games" },
{ Alcachofa::kDebugScript, "Script", "Enable debug script dump" },
DEBUG_CHANNEL_END
};
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 5012d77e2a6..96a07feed09 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -28,9 +28,6 @@ namespace Alcachofa {
enum AlcachofaDebugChannels {
kDebugGraphics = 1,
- kDebugPath,
- kDebugScan,
- kDebugFilePath,
kDebugScript,
};
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
new file mode 100644
index 00000000000..1e7d9a7a7c1
--- /dev/null
+++ b/engines/alcachofa/script-debug.h
@@ -0,0 +1,130 @@
+/* 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/>.
+ *
+ */
+
+#ifndef SCRIPT_DEBUG_H
+#define SCRIPT_DEBUG_H
+
+namespace Alcachofa {
+
+static const char* const ScriptOpNames[] = {
+ "Nop",
+ "Dup",
+ "PushAddr",
+ "PushValue",
+ "Deref",
+ "Crash5",
+ "PopN",
+ "Store",
+ "Crash8",
+ "Crash9",
+ "LoadString",
+ "LoadString2",
+ "Crash12",
+ "ScriptCall",
+ "KernelCall",
+ "JumpIfFalse",
+ "JumpIfTrue",
+ "Jump",
+ "Negate",
+ "BooleanNot",
+ "Mul",
+ "Crash21",
+ "Crash22",
+ "Add",
+ "Sub",
+ "Less",
+ "Greater",
+ "LessEquals",
+ "GreaterEquals",
+ "Equals",
+ "NotEquals",
+ "BitAnd",
+ "BitOr",
+ "Crash33",
+ "Crash34",
+ "Crash35",
+ "Crash36",
+ "Return"
+};
+
+static const char *const KernelCallNames[] = {
+ "<null>",
+ "PlayVideo",
+ "PlaySound",
+ "PlayMusic",
+ "StopMusic",
+ "WaitForMusicToEnd",
+ "ShowCenterBottomText",
+ "StopAndTurn",
+ "StopAndTurnMe",
+ "ChangeCharacter",
+ "SayText",
+ "Nop10",
+ "Go",
+ "Put",
+ "ChangeCharacterRoom",
+ "KillProcesses",
+ "LerpCharacterLodBias",
+ "On",
+ "Off",
+ "Pickup",
+ "CharacterPickup",
+ "Drop",
+ "CharacterDrop",
+ "Delay",
+ "HadNoMousePressFor",
+ "Nop24",
+ "Fork",
+ "Animate",
+ "AnimateCharacter",
+ "AnimateTalking",
+ "ChangeRoom",
+ "ToggleRoomFloor",
+ "SetDialogLineReturn",
+ "DialogMenu",
+ "ClearInventory",
+ "Nop34",
+ "FadeType0",
+ "FadeType1",
+ "LerpWorldLodBias",
+ "FadeType2",
+ "SetActiveTextureSet",
+ "SetMaxCamSpeedFactor",
+ "WaitCamStopping",
+ "CamFollow",
+ "CamShake",
+ "LerpCamXY",
+ "LerpCamZ",
+ "LerpCamScale",
+ "LerpCamToObjectWithScale",
+ "LerpCamToObjectResettingZ",
+ "LerpCamRotation",
+ "FadeIn",
+ "FadeOut",
+ "FadeIn2",
+ "FadeOut2",
+ "LerpCamXYZ",
+ "LerpCamToObjectKeepingZ"
+};
+
+}
+
+#endif // SCRIPT_DEBUG_H
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 15701db2aa6..f0281433c63 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -22,6 +22,7 @@
#include "script.h"
#include "rooms.h"
#include "alcachofa.h"
+#include "script-debug.h"
#include "common/file.h"
@@ -30,6 +31,13 @@ using namespace Math;
namespace Alcachofa {
+enum ScriptDebugLevel {
+ SCRIPT_DEBUG_LVL_NONE = 0,
+ SCRIPT_DEBUG_LVL_TASKS = 1,
+ SCRIPT_DEBUG_LVL_KERNELCALLS = 2,
+ SCRIPT_DEBUG_LVL_INSTRUCTIONS = 3
+};
+
ScriptInstruction::ScriptInstruction(ReadStream &stream)
: _op((ScriptOp)stream.readSint32LE())
, _arg(stream.readSint32LE()) {}
@@ -161,6 +169,7 @@ struct ScriptTask : public Task {
, _pc(pc)
, _lock(Common::move(lock)) {
pushInstruction(UINT_MAX);
+ debugC(SCRIPT_DEBUG_LVL_TASKS, kDebugScript, "%u: Script start at %u", process.pid(), pc);
}
ScriptTask(Process &process, const ScriptTask &forkParent)
@@ -172,6 +181,7 @@ struct ScriptTask : public Task {
for (uint i = 0; i < forkParent._stack.size(); i++)
_stack.push(forkParent._stack[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);
}
virtual TaskReturn run() override {
@@ -185,6 +195,9 @@ struct ScriptTask : public Task {
if (_pc >= _script._instructions.size())
error("Script process reached instruction out-of-bounds");
const auto &instruction = _script._instructions[_pc++];
+ debugC(SCRIPT_DEBUG_LVL_INSTRUCTIONS, kDebugScript, "%u: %5u %-12s %8d",
+ process().pid(), _pc - 1, ScriptOpNames[(int)instruction._op], instruction._arg);
+
switch (instruction._op) {
case ScriptOp::Nop: break;
case ScriptOp::Dup:
@@ -411,6 +424,9 @@ private:
}
TaskReturn kernelCall(ScriptKernelTask task) {
+ debugC(SCRIPT_DEBUG_LVL_KERNELCALLS, kDebugScript, "%u: %5u Kernel %-25s",
+ process().pid(), _pc - 1, KernelCallNames[(int)task]);
+
switch (task) {
// sound/video
case ScriptKernelTask::PlayVideo:
Commit: 84bcbbea4e7a2c0e91e8c2145ddead209a84185c
https://github.com/scummvm/scummvm/commit/84bcbbea4e7a2c0e91e8c2145ddead209a84185c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Disable alpha premultiplication, seems to be wrong
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index e64380c1d7b..6d3e2b1366a 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -323,12 +323,13 @@ void Animation::prerenderFrame(int32 frameI) {
fullBlend(*image, _renderedSurface, offsetX, offsetY);
}
+ /* TODO: Find a situation where this is actually used, otherwise this currently just produces bugs
if (_premultiplyAlpha != 100) {
byte *itPixel = (byte*)_renderedSurface.getPixels();
uint componentCount = _renderedSurface.w * _renderedSurface.h * 4;
for (uint32 i = 0; i < componentCount; i++, itPixel++)
*itPixel = *itPixel * _premultiplyAlpha / 100;
- }
+ }*/
_renderedTexture->update(_renderedSurface);
_renderedFrameI = frameI;
Commit: 356122bb8e732ba52647dc2998ef9331a799389b
https://github.com/scummvm/scummvm/commit/356122bb8e732ba52647dc2998ef9331a799389b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Fix object query for some cutscenes
Changed paths:
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c5c6c55a20e..c04ed7e7c09 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -593,7 +593,7 @@ ObjectBase *World::getObjectByName(MainCharacterKind character, const Common::St
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() != player.activeCharacter()->room())
result = player.currentRoom()->getObjectByName(name);
if (result == nullptr)
result = player.activeCharacter()->room()->getObjectByName(name);
Commit: e157840815c686842d8c831ed90d44c671123997
https://github.com/scummvm/scummvm/commit/e157840815c686842d8c831ed90d44c671123997
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Fix loading OFELIA_QUIETA.AN0
Changed paths:
engines/alcachofa/graphics-opengl.cpp
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index dcde6af735e..c3e882f222d 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -116,7 +116,7 @@ public:
}
virtual ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override {
- assert(w > 0 && h > 0);
+ assert(w >= 0 && h >= 0);
return ScopedPtr<ITexture>(new OpenGLTexture(w, h, withMipmaps));
}
Commit: c49f2cbfd665b3c515f6c4b50a5b2121fd4980a0
https://github.com/scummvm/scummvm/commit/c49f2cbfd665b3c515f6c4b50a5b2121fd4980a0
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Add two original hard-coded special cases
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 ac8cab2d6d6..0f73218cc6c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -288,9 +288,14 @@ struct SayTextTask : public Task {
graphicOf(_character->_curTalkingObject, &_character->_graphicTalking)->start(true);
while (true) {
if (_soundId == kInvalidSoundID)
+ {
+ bool isMortadeloVoice =
+ _character == &g_engine->world().mortadelo() ||
+ _character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
_soundId = g_engine->sounds().playVoice(
- String::format(_character == &g_engine->world().mortadelo() ? "M%04d" : "%04d", _dialogId),
+ String::format(isMortadeloVoice ? "M%04d" : "%04d", _dialogId),
0);
+ }
g_engine->sounds().setAppropriateVolume(_soundId, process().character(), _character);
if (!g_engine->sounds().isAlive(_soundId) || g_engine->input().wasAnyMouseReleased())
_character->_isTalking = false;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 6d3e2b1366a..c7cf2885dec 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -186,7 +186,8 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
void AnimationBase::loadMissingAnimation() {
// only allow missing animations we know are faulty in the original game
if (!_fileName.equalsIgnoreCase("ANIMACION.AN0") &&
- !_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0"))
+ !_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0") &&
+ !_fileName.equalsIgnoreCase("PP_MORTA.AN0"))
error("Could not open animation %s", _fileName.c_str());
// otherwise setup a functioning but empty animation
Commit: 5fdd0de63706dd491a1f5709e229506ecf2faca3
https://github.com/scummvm/scummvm/commit/5fdd0de63706dd491a1f5709e229506ecf2faca3
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Fix loading room VIA_TREN_ATADOS_NOCHE
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 0f73218cc6c..ed8968aabf8 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -200,7 +200,8 @@ void Character::draw() {
return;
Graphic *activeGraphic = graphic();
assert(activeGraphic != nullptr);
- g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
+ if (activeGraphic->hasAnimation())
+ g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
}
void Character::drawDebug() {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 4cb29f0fdcd..796488700c6 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -258,6 +258,7 @@ public:
inline Color &color() { return _color; }
inline int32 &frameI() { return _frameI; }
inline uint32 lastTime() const { return _lastTime; }
+ inline bool hasAnimation() const { return _animation != nullptr; }
inline Animation &animation() {
assert(_animation != nullptr && _animation->isLoaded());
return *_animation;
Commit: 6c551fc9ca25641fd01f9ad38b4bdf030a64d4d7
https://github.com/scummvm/scummvm/commit/6c551fc9ca25641fd01f9ad38b4bdf030a64d4d7
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:49+02:00
Commit Message:
ALCACHOFA: Add teleport debug handler
Changed paths:
engines/alcachofa/Input.cpp
engines/alcachofa/alcachofa.cpp
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/debug.h
diff --git a/engines/alcachofa/Input.cpp b/engines/alcachofa/Input.cpp
index 92c97325fbf..6f7e38aa7d6 100644
--- a/engines/alcachofa/Input.cpp
+++ b/engines/alcachofa/Input.cpp
@@ -38,7 +38,12 @@ void Input::nextFrame() {
bool Input::handleEvent(const Common::Event &event) {
if (_debugInput != nullptr)
- return _debugInput->handleEvent(event);
+ {
+ auto result = _debugInput->handleEvent(event);
+ _mousePos2D = _debugInput->mousePos2D(); // even for debug input we want to e.g. draw a cursor
+ _mousePos3D = _debugInput->mousePos3D();
+ return result;
+ }
switch (event.type) {
case EVENT_LBUTTONDOWN:
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 427a9cf1d8d..78f66ca0b8b 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -148,6 +148,9 @@ void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param)
case DebugMode::FloorIntersections:
_debugHandler.reset(new FloorIntersectionsDebugHandler(param));
break;
+ case DebugMode::TeleportCharacter:
+ _debugHandler.reset(new TeleportCharacterDebugHandler(param));
+ break;
default: _debugHandler.reset(nullptr);
}
_input.toggleDebugInput(isDebugModeActive());
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 1783cf9ecee..247035a44a4 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -43,6 +43,7 @@ Console::Console() : GUI::Debugger() {
registerCmd("pickup", WRAP_METHOD(Console, cmdItem));
registerCmd("drop", WRAP_METHOD(Console, cmdItem));
registerCmd("debugMode", WRAP_METHOD(Console, cmdDebugMode));
+ registerCmd("tp", WRAP_METHOD(Console, cmdTeleport));
}
Console::~Console() {
@@ -214,6 +215,7 @@ bool Console::cmdDebugMode(int argc, const char **args)
debugPrintf(" 0 - None, disables debug mode\n");
debugPrintf(" 1 - Closest floor point, param limits to polygon\n");
debugPrintf(" 2 - Floor edge intersections, param limits to polygon\n");
+ debugPrintf(" 3 - Teleport character to mouse click, param selects character\n");
return true;
}
@@ -234,4 +236,31 @@ bool Console::cmdDebugMode(int argc, const char **args)
return true;
}
+bool Console::cmdTeleport(int argc, const char **args)
+{
+ if (argc < 1 || argc > 2)
+ {
+ debugPrintf("usagge: tp [<character>]\n");
+ debugPrintf("characters:\n");
+ debugPrintf(" 0 - Both\n");
+ debugPrintf(" 1 - Mortadelo\n");
+ debugPrintf(" 2 - Filemon\n");
+ }
+
+ int32 param = 0;
+ if (argc > 1)
+ {
+ char *end = nullptr;
+ param = (int32)strtol(args[2], &end, 10);
+ if (end == nullptr || *end != '\0')
+ {
+ debugPrintf("Character kind can only be integer");
+ return true;
+ }
+ }
+
+ g_engine->setDebugMode(DebugMode::TeleportCharacter, param);
+ return false;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index b21e06085a5..236e842fdc9 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -30,7 +30,8 @@ namespace Alcachofa {
enum class DebugMode {
None,
ClosestFloorPoint,
- FloorIntersections
+ FloorIntersections,
+ TeleportCharacter,
};
class Console : public GUI::Debugger {
@@ -54,6 +55,7 @@ private:
bool cmdDisableDebugDraw(int argc, const char **args);
bool cmdItem(int argc, const char **args);
bool cmdDebugMode(int argc, const char **args);
+ bool cmdTeleport(int argc, const char **args);
bool _showInteractables = true;
bool _showCharacters = true;
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index de9cba0997a..be9196c9b2f 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -40,7 +40,7 @@ class ClosestFloorPointDebugHandler final : public IDebugHandler {
public:
ClosestFloorPointDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
- virtual void update() final
+ virtual void update() override
{
auto mousePos2D = g_engine->input().debugInput().mousePos2D();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
@@ -64,7 +64,7 @@ class FloorIntersectionsDebugHandler final : public IDebugHandler {
public:
FloorIntersectionsDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
- virtual void update() final
+ virtual void update() override
{
auto floor = g_engine->player().currentRoom()->activeFloor();
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
@@ -113,6 +113,54 @@ private:
}
};
+class TeleportCharacterDebugHandler final : public IDebugHandler {
+ MainCharacterKind _kind;
+public:
+ TeleportCharacterDebugHandler(int32 kindI) : _kind((MainCharacterKind)kindI) {}
+
+ virtual void update() override
+ {
+ g_engine->drawQueue().clear();
+ g_engine->player().drawCursor(true);
+ g_engine->drawQueue().draw();
+
+ auto &input = g_engine->input().debugInput();
+ if (input.wasMouseRightPressed())
+ {
+ g_engine->setDebugMode(DebugMode::None, 0);
+ return;
+ }
+
+ if (!input.wasMouseLeftPressed())
+ return;
+ auto floor = g_engine->player().currentRoom()->activeFloor();
+ if (floor == nullptr || !floor->contains(input.mousePos3D()))
+ return;
+
+ if (_kind == MainCharacterKind::Filemon)
+ teleport(g_engine->world().filemon(), input.mousePos3D());
+ else if (_kind == MainCharacterKind::Mortadelo)
+ teleport(g_engine->world().mortadelo(), input.mousePos3D());
+ else {
+ teleport(g_engine->world().filemon(), input.mousePos3D());
+ teleport(g_engine->world().mortadelo(), input.mousePos3D());
+ }
+ g_engine->setDebugMode(DebugMode::None, 0);
+ }
+
+private:
+ void teleport(MainCharacter &character, Point position)
+ {
+ auto currentRoom = g_engine->player().currentRoom();
+ if (character.room() != currentRoom)
+ {
+ character.resetTalking();
+ character.room() = currentRoom;
+ }
+ character.setPosition(position);
+ }
+};
+
}
#endif // DEBUG_H
Commit: 01233d57cf6df7d51b57cf621c7abb3812fb11bb
https://github.com/scummvm/scummvm/commit/01233d57cf6df7d51b57cf621c7abb3812fb11bb
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Add AnimateTalking kernel call
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index ed8968aabf8..b7f87c9dae6 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -341,6 +341,18 @@ void Character::resetTalking() {
_curTalkingObject = nullptr;
}
+void Character::talkUsing(ObjectBase *talkObject) {
+ _curTalkingObject = talkObject;
+ if (talkObject == nullptr)
+ return;
+ auto graphic = talkObject->graphic();
+ if (graphic == nullptr)
+ error("Talk object %s does not have a graphic", talkObject->name().c_str());
+ graphic->start(true);
+ if (room() == g_engine->player().currentRoom())
+ graphic->update();
+}
+
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 90f5c831d3d..4751c2be428 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -391,6 +391,7 @@ public:
Task *sayText(Process &process, int32 dialogId);
void resetTalking();
+ void talkUsing(ObjectBase *talkObject);
protected:
friend struct SayTextTask;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index f0281433c63..6b3adf5d77f 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -584,9 +584,21 @@ private:
case ScriptKernelTask::AnimateCharacter:
warning("STUB KERNEL CALL: AnimateCharacter");
return TaskReturn::finish(0);
- case ScriptKernelTask::AnimateTalking:
- warning("STUB KERNEL CALL: AnimateTalking");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::AnimateTalking: {
+ auto *character = dynamic_cast<Character *>(g_engine->world().getObjectByName(getStringArg(0)));
+ if (character == nullptr)
+ error("Invalid character name: %s", getStringArg(0));
+ const char *talkObjectName = getStringArg(1);
+ ObjectBase *talkObject = nullptr;
+ if (talkObjectName != nullptr && *talkObjectName)
+ {
+ talkObject = g_engine->world().getObjectByName(talkObjectName);
+ if (talkObject == nullptr)
+ error("Invalid talk object name: %s", talkObjectName);
+ }
+ character->talkUsing(talkObject);
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::SayText: {
const char *characterName = getStringArg(0);
int32 dialogId = getNumberArg(1);
Commit: 75201ef02858acad30c789a37bacf15256c51cdc
https://github.com/scummvm/scummvm/commit/75201ef02858acad30c789a37bacf15256c51cdc
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Add AnimateCharacter kernel call
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index b7f87c9dae6..2c1d084200d 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -277,7 +277,7 @@ void Character::trigger(const char *action) {
g_engine->player().triggerObject(this, action);
}
-struct SayTextTask : public Task {
+struct SayTextTask final : public Task {
SayTextTask(Process &process, Character *character, int32 dialogId)
: Task(process)
, _character(character)
@@ -353,6 +353,51 @@ void Character::talkUsing(ObjectBase *talkObject) {
graphic->update();
}
+struct AnimateCharacterTask final : public Task {
+ AnimateCharacterTask(Process &process, Character *character, ObjectBase *animateObject)
+ : Task(process)
+ , _character(character)
+ , _animateObject(animateObject)
+ , _graphic(animateObject->graphic()) {
+ scumm_assert(_graphic != nullptr);
+ }
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ while (_character->_curAnimateObject != nullptr)
+ TASK_YIELD;
+
+ _character->_curAnimateObject = _animateObject;
+ _graphic->start(false);
+ if (_character->room() == g_engine->player().currentRoom())
+ _graphic->update();
+ do
+ {
+ TASK_YIELD;
+ if (process().isActiveForPlayer() && g_engine->input().wasAnyMouseReleased())
+ _graphic->pause();
+ } while (!_graphic->isPaused());
+
+ _character->_curAnimateObject = nullptr;
+ _character->_curTalkingObject = nullptr;
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ g_engine->console().debugPrintf("AnimateCharacter %s, %s\n", _character->name().c_str(), _animateObject->name().c_str());
+ }
+
+private:
+ Character *_character;
+ ObjectBase *_animateObject;
+ Graphic *_graphic;
+};
+
+Task *Character::animate(Process &process, ObjectBase *animateObject) {
+ assert(animateObject != nullptr);
+ return new AnimateCharacterTask(process, this, animateObject);
+}
+
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 796488700c6..a394604ca41 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -258,6 +258,7 @@ public:
inline Color &color() { return _color; }
inline int32 &frameI() { return _frameI; }
inline uint32 lastTime() const { return _lastTime; }
+ inline bool isPaused() const { return _isPaused; }
inline bool hasAnimation() const { return _animation != nullptr; }
inline Animation &animation() {
assert(_animation != nullptr && _animation->isLoaded());
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 4751c2be428..6111ed4f450 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -392,9 +392,11 @@ public:
Task *sayText(Process &process, int32 dialogId);
void resetTalking();
void talkUsing(ObjectBase *talkObject);
+ Task *animate(Process &process, ObjectBase *animateObject);
protected:
friend struct SayTextTask;
+ friend struct AnimateCharacterTask;
void syncObjectAsString(Common::Serializer &serializer, ObjectBase *&object);
void updateTalkingAnimation();
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 6b3adf5d77f..bdc3ea554c6 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -581,9 +581,15 @@ private:
case ScriptKernelTask::LerpCharacterLodBias:
warning("STUB KERNEL CALL: LerpCharacterLodBias");
return TaskReturn::finish(0);
- case ScriptKernelTask::AnimateCharacter:
- warning("STUB KERNEL CALL: AnimateCharacter");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::AnimateCharacter: {
+ auto *character = dynamic_cast<Character *>(g_engine->world().getObjectByName(getStringArg(0)));
+ if (character == nullptr)
+ error("Invalid character name: %s", getStringArg(0));
+ auto *animObject = g_engine->world().getObjectByName(getStringArg(1));
+ if (animObject == nullptr)
+ error("Invalid animate object name: %s", getStringArg(1));
+ return TaskReturn::waitFor(character->animate(process(), animObject));
+ }
case ScriptKernelTask::AnimateTalking: {
auto *character = dynamic_cast<Character *>(g_engine->world().getObjectByName(getStringArg(0)));
if (character == nullptr)
Commit: 2bd64cd55849271b962f856f1cd2798fe5fb899b
https://github.com/scummvm/scummvm/commit/2bd64cd55849271b962f856f1cd2798fe5fb899b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Add PlaySound kernel call
Changed paths:
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index bdc3ea554c6..a1ccdb4800c 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -432,9 +432,13 @@ private:
case ScriptKernelTask::PlayVideo:
g_engine->playVideo(getNumberArg(0));
return TaskReturn::finish(0);
- case ScriptKernelTask::PlaySound:
- warning("STUB KERNEL CALL: PlaySound");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::PlaySound: {
+ auto soundID = g_engine->sounds().playSFX(getStringArg(0));
+ g_engine->sounds().setAppropriateVolume(soundID, process().character(), nullptr);
+ return getNumberArg(1) == 0
+ ? TaskReturn::waitFor(new PlaySoundTask(process(), soundID))
+ : TaskReturn::finish(1);
+ }
case ScriptKernelTask::PlayMusic:
warning("STUB KERNEL CALL: PlayMusic");
return TaskReturn::finish(0);
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 57952a47b44..adeb31f336f 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -122,15 +122,23 @@ static AudioStream *openAudio(const String &fileName) {
error("Could not open audio file: %s", fileName.c_str());
}
-SoundID Sounds::playVoice(const String &fileName, byte volume) {
+SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::SoundType type) {
AudioStream *stream = openAudio(fileName);
SoundHandle handle;
- _mixer->playStream(Mixer::kSpeechSoundType, &handle, stream, -1, volume);
+ _mixer->playStream(type, &handle, stream, -1, volume);
SoundID id = _nextID++;
- _playbacks.push_back({ id, handle, Mixer::kSpeechSoundType });
+ _playbacks.push_back({ id, handle, type });
return id;
}
+SoundID Sounds::playVoice(const String &fileName, byte volume) {
+ return playSoundInternal(fileName, volume, Mixer::kSpeechSoundType);
+}
+
+SoundID Sounds::playSFX(const String &fileName, byte volume) {
+ return playSoundInternal(fileName, volume, Mixer::kSFXSoundType);
+}
+
void Sounds::stopVoice() {
for (uint i = _playbacks.size(); i > 0; i--) {
if (_playbacks[i - 1]._type == Mixer::kSpeechSoundType) {
@@ -152,17 +160,19 @@ void Sounds::setVolume(SoundID id, byte volume) {
}
void Sounds::setAppropriateVolume(SoundID id,
- MainCharacterKind processCharacter,
+ MainCharacterKind processCharacterKind,
Character *speakingCharacter) {
static constexpr byte kAlmostMaxVolume = Mixer::kMaxChannelVolume * 9 / 10;
auto &player = g_engine->player();
+ auto processCharacter = processCharacterKind == MainCharacterKind::None ? nullptr
+ : &g_engine->world().getMainCharacterByKind(processCharacterKind);
byte newVolume;
- if (player.activeCharacter() == nullptr || player.activeCharacter() == speakingCharacter)
+ if (processCharacter == nullptr || processCharacter == player.activeCharacter())
newVolume = Mixer::kMaxChannelVolume;
else if (speakingCharacter != nullptr && speakingCharacter->room() == player.currentRoom())
newVolume = kAlmostMaxVolume;
- else if (g_engine->world().getMainCharacterByKind(processCharacter).room() == player.currentRoom())
+ else if (processCharacter->room() == player.currentRoom())
newVolume = kAlmostMaxVolume;
else
newVolume = 0;
@@ -177,4 +187,23 @@ void Sounds::fadeOut(SoundID id, uint32 duration) {
}
}
+PlaySoundTask::PlaySoundTask(Process &process, SoundID soundID)
+ : Task(process)
+ , _soundID(soundID) { }
+
+TaskReturn PlaySoundTask::run() {
+ auto &sounds = g_engine->sounds();
+ if (sounds.isAlive(_soundID))
+ {
+ sounds.setAppropriateVolume(_soundID, process().character(), nullptr);
+ return TaskReturn::yield();
+ }
+ else
+ return TaskReturn::finish(1);
+}
+
+void PlaySoundTask::debugPrint() {
+ g_engine->console().debugPrintf("PlaySound %u\n", _soundID);
+}
+
}
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 2a2629d2da7..35eb8ccde44 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -22,7 +22,7 @@
#ifndef SOUNDS_H
#define SOUNDS_H
-#include "common.h"
+#include "scheduler.h"
#include "audio/mixer.h"
namespace Alcachofa {
@@ -38,6 +38,7 @@ public:
void update();
SoundID playVoice(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
+ SoundID playSFX(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
void stopVoice();
void fadeOut(SoundID id, uint32 duration);
bool isAlive(SoundID id);
@@ -57,12 +58,21 @@ private:
_fadeDuration = 0;
};
Playback *getPlaybackById(SoundID id);
+ SoundID playSoundInternal(const Common::String &fileName, byte volume, Audio::Mixer::SoundType type);
Common::Array<Playback> _playbacks;
Audio::Mixer *_mixer;
SoundID _nextID = 1;
};
+struct PlaySoundTask final : public Task {
+ PlaySoundTask(Process &process, SoundID soundID);
+ virtual TaskReturn run() override;
+ virtual void debugPrint() override;
+private:
+ SoundID _soundID;
+};
+
}
#endif // SOUNDS_H
Commit: ae5c98228e21523873483f2a2c3bff4cff21c1fc
https://github.com/scummvm/scummvm/commit/ae5c98228e21523873483f2a2c3bff4cff21c1fc
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Add LerpCharacterLodBias kernel call
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 2c1d084200d..a1ff35aa985 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -398,6 +398,44 @@ Task *Character::animate(Process &process, ObjectBase *animateObject) {
return new AnimateCharacterTask(process, this, animateObject);
}
+struct LerpLodBiasTask final : public Task {
+ LerpLodBiasTask(Process &process, Character *character, float targetLodBias, uint32 durationMs)
+ : Task(process)
+ , _character(character)
+ , _targetLodBias(targetLodBias)
+ , _durationMs(durationMs) { }
+
+ virtual TaskReturn run() override {
+ TASK_BEGIN;
+ _startTime = g_system->getMillis();
+ _sourceLodBias = _character->lodBias();
+ while (g_system->getMillis() - _startTime < _durationMs) {
+ _character->lodBias() = _sourceLodBias + (_targetLodBias - _sourceLodBias) *
+ ((g_system->getMillis() - _startTime) / (float)_durationMs);
+ TASK_YIELD;
+ }
+ _character->lodBias() = _targetLodBias;
+ TASK_END;
+ }
+
+ virtual void debugPrint() override {
+ uint32 remaining = g_system->getMillis() - _startTime <= _durationMs
+ ? _durationMs - (g_system->getMillis() - _startTime)
+ : 0;
+ g_engine->console().debugPrintf("Lerp lod bias of %s to %f with %ums remaining\n",
+ _character->name().c_str(), _targetLodBias, remaining);
+ }
+
+private:
+ Character *_character;
+ float _sourceLodBias = 0, _targetLodBias;
+ uint32 _startTime = 0, _durationMs;
+};
+
+Task *Character::lerpLodBias(Process &process, float targetLodBias, int32 durationMs) {
+ return new LerpLodBiasTask(process, this, targetLodBias, durationMs);
+}
+
const char *WalkingCharacter::typeName() const { return "WalkingCharacter"; }
WalkingCharacter::WalkingCharacter(Room *room, ReadStream &stream)
@@ -627,7 +665,7 @@ void WalkingCharacter::draw() {
}
assert(currentGraphic != nullptr);
- g_engine->drawQueue().add<AnimationDrawRequest>(*currentGraphic, true, BlendMode::AdditiveAlpha);
+ g_engine->drawQueue().add<AnimationDrawRequest>(*currentGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
}
void WalkingCharacter::drawDebug() {
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 6111ed4f450..262017e3a45 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -393,6 +393,8 @@ public:
void resetTalking();
void talkUsing(ObjectBase *talkObject);
Task *animate(Process &process, ObjectBase *animateObject);
+ Task *lerpLodBias(Process &process, float targetLodBias, int32 durationMs);
+ inline float &lodBias() { return _lodBias; }
protected:
friend struct SayTextTask;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index a1ccdb4800c..809692c2480 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -582,9 +582,20 @@ private:
character->room() = targetRoom;
return TaskReturn::finish(1);
}
- case ScriptKernelTask::LerpCharacterLodBias:
- warning("STUB KERNEL CALL: LerpCharacterLodBias");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpCharacterLodBias: {
+ auto *character = dynamic_cast<Character *>(g_engine->world().globalRoom().getObjectByName(getStringArg(0)));
+ if (character == nullptr)
+ error("Invalid character name: %s", getStringArg(0));
+ float targetLodBias = getNumberArg(1) * 0.01f;
+ int32 durationMs = getNumberArg(2);
+ if (durationMs <= 0)
+ {
+ character->lodBias() = targetLodBias;
+ return TaskReturn::finish(1);
+ }
+ else
+ return TaskReturn::waitFor(character->lerpLodBias(process(), targetLodBias, durationMs));
+ }
case ScriptKernelTask::AnimateCharacter: {
auto *character = dynamic_cast<Character *>(g_engine->world().getObjectByName(getStringArg(0)));
if (character == nullptr)
Commit: 313a811a4007f2863cebd4197441beb1c5506777
https://github.com/scummvm/scummvm/commit/313a811a4007f2863cebd4197441beb1c5506777
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Add ChangeCharacter kernel call
Changed paths:
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 3f7afe1cc31..27290532b29 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -58,10 +58,20 @@ FakeLock::FakeLock(FakeLock &&other) noexcept : _semaphore(other._semaphore) {
}
FakeLock::~FakeLock() {
+ release();
+}
+
+void FakeLock::operator= (FakeLock &&other) noexcept {
+ _semaphore = other._semaphore;
+ other._semaphore = nullptr;
+}
+
+void FakeLock::release() {
if (_semaphore == nullptr)
return;
assert(_semaphore->_counter > 0);
_semaphore->_counter--;
+ _semaphore = nullptr;
}
Vector3d as3D(const Vector2d &v) {
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 559823d471b..733f609b58b 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -99,8 +99,10 @@ struct FakeLock {
FakeLock(const FakeLock &other);
FakeLock(FakeLock &&other) noexcept;
~FakeLock();
+ void operator = (FakeLock &&other) noexcept;
+ void release();
private:
- FakeSemaphore *_semaphore;
+ FakeSemaphore *_semaphore = nullptr;
};
float ease(float t, EasingType type);
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index a1ff35aa985..15e3600add9 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -288,6 +288,8 @@ struct SayTextTask final : public Task {
_character->_isTalking = true;
graphicOf(_character->_curTalkingObject, &_character->_graphicTalking)->start(true);
while (true) {
+ g_engine->player().addLastDialogCharacter(_character);
+
if (_soundId == kInvalidSoundID)
{
bool isMortadeloVoice =
@@ -1022,6 +1024,11 @@ Task *MainCharacter::dialogMenu(Process &process) {
return new DialogMenuTask(process, this);
}
+void MainCharacter::resetUsingObjectAndDialogMenu() {
+ _currentlyUsingObject = nullptr;
+ _dialogLines.clear();
+}
+
const char *Background::typeName() const { return "Background"; }
Background::Background(Room *room, const String &animationFileName, int16 scale)
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 262017e3a45..8738d4b6f39 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -510,6 +510,7 @@ public:
void addDialogLine(int32 dialogId);
void setLastDialogReturnValue(int32 returnValue);
Task *dialogMenu(Process &process);
+ void resetUsingObjectAndDialogMenu();
protected:
virtual void onArrived() override;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 050e548cc6e..479a30009f1 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -254,4 +254,30 @@ void Player::setPermanentFade(bool isFaded) {
_isPermanentFaded = isFaded;
}
+// the last dialog character mechanic seems like a hack in the original engine
+// all talking characters (see SayText kernel call) are added to a fixed-size
+// rolling queue and stopped upon killProcesses
+
+void Player::addLastDialogCharacter(Character *character) {
+ auto lastDialogCharactersEnd = _lastDialogCharacters + kMaxLastDialogCharacters;
+ if (Common::find(_lastDialogCharacters, lastDialogCharactersEnd, character) != lastDialogCharactersEnd)
+ return;
+ _lastDialogCharacters[_nextLastDialogCharacter++] = character;
+ _nextLastDialogCharacter %= kMaxLastDialogCharacters;
+}
+
+void Player::stopLastDialogCharacters() {
+ // originally only the isTalking flag is reset, but this seems a bit safer so unless we find a bug
+ for (int i = 0; i < kMaxLastDialogCharacters; i++) {
+ auto character = _lastDialogCharacters[i];
+ if (character != nullptr)
+ character->resetTalking();
+ }
+}
+
+void Player::setActiveCharacter(MainCharacterKind kind) {
+ scumm_assert(kind == MainCharacterKind::Mortadelo || kind == MainCharacterKind::Filemon);
+ _activeCharacter = &g_engine->world().getMainCharacterByKind(kind);
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 3d65dce9947..f63f95e1a3e 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -56,8 +56,13 @@ public:
void triggerObject(ObjectBase *object, const char *action);
void triggerDoor(const Door *door);
void setPermanentFade(bool isFaded);
+ void addLastDialogCharacter(Character *character);
+ void stopLastDialogCharacters();
+ void setActiveCharacter(MainCharacterKind kind);
private:
+ static constexpr const int kMaxLastDialogCharacters = 4;
+
Common::ScopedPtr<Animation> _cursorAnimation;
FakeSemaphore _semaphore;
Room *_currentRoom = nullptr,
@@ -72,6 +77,8 @@ private:
_isGameLoaded = true,
_didLoadGlobalRooms = false,
_isPermanentFaded = false;
+ Character *_lastDialogCharacters[kMaxLastDialogCharacters] = { nullptr };
+ int _nextLastDialogCharacter = 0;
};
}
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 8456a520649..631e63671c8 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -183,7 +183,7 @@ void Scheduler::killAllProcesses() {
void Scheduler::killAllProcessesFor(MainCharacterKind characterKind) {
// this method can be called during run() so be careful
killProcessesForIn(characterKind, processesToRunNext(), 0);
- killProcessesForIn(characterKind, processesToRun(), _currentProcessI == UINT_MAX ? 0 : _currentProcessI);
+ killProcessesForIn(characterKind, processesToRun(), _currentProcessI == UINT_MAX ? 0 : _currentProcessI + 1);
}
static Process **getProcessByName(Array<Process *> &_processes, const String &name) {
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 9ea21f9d415..d48b65faec2 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -131,7 +131,7 @@ public:
~Process();
inline ProcessId pid() const { return _pid; }
- inline MainCharacterKind character() const { return _character; }
+ inline MainCharacterKind &character() { return _character; } // is changed in changeCharacter
inline int32 returnValue() const { return _lastReturnValue; }
inline Common::String &name() { return _name; }
bool isActiveForPlayer() const; ///< and thus should e.g. draw subtitles or effects
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 809692c2480..380304b6c1e 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -463,13 +463,28 @@ private:
g_engine->scheduler().createProcess<ScriptTask>(process().character(), *this);
return TaskReturn::finish(0); // 0 means this is the forking process
case ScriptKernelTask::KillProcesses:
- warning("STUB KERNEL CALL: KillProcesses");
- return TaskReturn::finish(0);
+ killProcessesFor((MainCharacterKind)getNumberArg(0));
+ return TaskReturn::finish(1);
// player/world state changes
- case ScriptKernelTask::ChangeCharacter:
- warning("STUB KERNEL CALL: ChangeCharacter");
- return TaskReturn::finish(0);
+ case ScriptKernelTask::ChangeCharacter: {
+ MainCharacterKind kind = (MainCharacterKind)getNumberArg(0);
+ killProcessesFor(MainCharacterKind::None); // yes, kill for all characters
+ auto &camera = g_engine->camera();
+ auto &player = g_engine->player();
+ camera.resetRotationAndScale();
+ camera.backup(0);
+ if (kind != MainCharacterKind::None) {
+ player.setActiveCharacter(kind);
+ player.heldItem() = nullptr;
+ camera.setFollow(player.activeCharacter());
+ camera.backup(0);
+ }
+ process().character() = MainCharacterKind::None;
+ assert(player.semaphore().isReleased());
+ _lock = { player.semaphore() };
+ return TaskReturn::finish(1);
+ }
case ScriptKernelTask::ChangeRoom:
if (strcmpi(getStringArg(0), "SALIR") == 0) {
g_engine->quitGame();
@@ -777,6 +792,22 @@ private:
}
}
+ void killProcessesFor(MainCharacterKind kind) {
+ if (kind == MainCharacterKind::None) {
+ killProcessesFor(MainCharacterKind::Mortadelo);
+ killProcessesFor(MainCharacterKind::Filemon);
+ g_engine->scheduler().killAllProcessesFor(kind);
+ return;
+ }
+ g_engine->scheduler().killAllProcessesFor(kind);
+ g_engine->sounds().fadeOutVoiceAndSFX(200);
+ g_engine->player().stopLastDialogCharacters();
+ _lock.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...
+ }
+
Script &_script;
Stack<StackEntry> _stack;
String _name;
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index adeb31f336f..ec6ad549036 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -38,6 +38,11 @@ namespace Alcachofa {
Sounds::Playback::Playback(uint32 id, SoundHandle handle, Mixer::SoundType type)
: _id(id), _handle(handle), _type(type) {}
+void Sounds::Playback::fadeOut(uint32 duration) {
+ _fadeStart = g_system->getMillis();
+ _fadeDuration = MAX<uint32>(duration, 1);
+}
+
Sounds::Sounds() : _mixer(g_system->getMixer()) {
assert(_mixer != nullptr);
}
@@ -181,9 +186,14 @@ void Sounds::setAppropriateVolume(SoundID id,
void Sounds::fadeOut(SoundID id, uint32 duration) {
Playback *playback = getPlaybackById(id);
- if (playback != nullptr) {
- playback->_fadeStart = g_system->getMillis();
- playback->_fadeDuration = MAX<uint32>(duration, 1);
+ if (playback != nullptr)
+ playback->fadeOut(duration);
+}
+
+void Sounds::fadeOutVoiceAndSFX(uint32 duration) {
+ for (auto &playback : _playbacks) {
+ if (playback._type == Mixer::kSpeechSoundType || playback._type == Mixer::kSFXSoundType)
+ playback.fadeOut(duration);
}
}
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 35eb8ccde44..377d1313a59 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -41,6 +41,7 @@ public:
SoundID playSFX(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
void stopVoice();
void fadeOut(SoundID id, uint32 duration);
+ void fadeOutVoiceAndSFX(uint32 duration);
bool isAlive(SoundID id);
void setVolume(SoundID id, byte volume);
void setAppropriateVolume(SoundID id,
@@ -50,6 +51,7 @@ public:
private:
struct Playback {
Playback(uint32 id, Audio::SoundHandle handle, Audio::Mixer::SoundType type);
+ void fadeOut(uint32 duration);
SoundID _id;
Audio::SoundHandle _handle;
Commit: 14d6797687fc11ac3b931b177ae55a1e0fc36089
https://github.com/scummvm/scummvm/commit/14d6797687fc11ac3b931b177ae55a1e0fc36089
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Refactor fonts into new GlobalUI component
Changed paths:
A engines/alcachofa/global-ui.cpp
A engines/alcachofa/global-ui.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/module.mk
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 78f66ca0b8b..682484c2efe 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -35,6 +35,7 @@
#include "rooms.h"
#include "script.h"
+#include "global-ui.h"
#include "debug.h"
using namespace Math;
@@ -67,10 +68,11 @@ Common::Error AlcachofaEngine::run() {
_world.reset(new World());
_script.reset(new Script());
_player.reset(new Player());
+ _globalUI.reset(new GlobalUI());
- _script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
- _player->changeRoom("MINA", true);
- //_script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
+ //_script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
+ //_player->changeRoom("HORCA", true);
+ _script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
_scheduler.run();
Common::Event e;
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 0b48f5c01a4..d19634e4f06 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -49,6 +49,7 @@ class IRenderer;
class DrawQueue;
class World;
class Script;
+class GlobalUI;
struct AlcachofaGameDescription;
class AlcachofaEngine : public Engine {
@@ -70,6 +71,7 @@ public:
inline Player &player() { return *_player; }
inline World &world() { return *_world; }
inline Script &script() { return *_script; }
+ inline GlobalUI &globalUI() { return *_globalUI; }
inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
inline bool isDebugModeActive() const { return _debugHandler != nullptr; }
@@ -128,6 +130,7 @@ private:
Common::ScopedPtr<World> _world;
Common::ScopedPtr<Script> _script;
Common::ScopedPtr<Player> _player;
+ Common::ScopedPtr<GlobalUI> _globalUI;
Camera _camera;
Input _input;
Sounds _sounds;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 15e3600add9..0c46b0fb3b6 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -22,6 +22,7 @@
#include "objects.h"
#include "rooms.h"
#include "script.h"
+#include "global-ui.h"
#include "alcachofa.h"
using namespace Common;
@@ -306,7 +307,7 @@ struct SayTextTask final : public Task {
if (true && // TODO: Add game option for subtitles
process().isActiveForPlayer()) {
g_engine->drawQueue().add<TextDrawRequest>(
- g_engine->world().dialogFont(),
+ g_engine->globalUI().dialogFont(),
g_engine->world().getDialogLine(_dialogId),
Point(g_system->getWidth() / 2, g_system->getHeight() - 200),
-1, true, kWhite, -kForegroundOrderCount);
@@ -972,7 +973,7 @@ private:
for (auto &itLine : lines) {
// we reuse the draw request to measure the actual height without using it to actually draw
TextDrawRequest request(
- g_engine->world().dialogFont(),
+ g_engine->globalUI().dialogFont(),
g_engine->world().getDialogLine(itLine._dialogId),
Point(kTextXOffset, 0), maxTextWidth(), false, kWhite, 2);
itLine._yPosition = request.size().y; // briefly storing line height
@@ -989,7 +990,7 @@ private:
auto &itLine = _character->_dialogLines[i - 1];
bool isHovered = !isSomethingHovered && _input.mousePos2D().y >= itLine._yPosition - kTextYOffset;
g_engine->drawQueue().add<TextDrawRequest>(
- g_engine->world().dialogFont(),
+ g_engine->globalUI().dialogFont(),
g_engine->world().getDialogLine(itLine._dialogId),
Point(kTextXOffset, itLine._yPosition),
maxTextWidth(), false, isHovered ? Color{ 255, 255, 128, 255 } : kWhite, -kForegroundOrderCount + 2);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index cca8b6c27f4..98e75fa0c8c 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -22,6 +22,7 @@
#include "objects.h"
#include "rooms.h"
#include "scheduler.h"
+#include "global-ui.h"
#include "alcachofa.h"
#include "common/system.h"
@@ -227,7 +228,7 @@ void ShapeObject::onHoverEnd() {
void ShapeObject::onHoverUpdate() {
g_engine->drawQueue().add<TextDrawRequest>(
- g_engine->world().generalFont(),
+ g_engine->globalUI().generalFont(),
g_engine->world().getLocalizedName(name()),
g_engine->input().mousePos2D() - Point(0, 35),
-1, true, kWhite, 0);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
new file mode 100644
index 00000000000..67ac1fdadef
--- /dev/null
+++ b/engines/alcachofa/global-ui.cpp
@@ -0,0 +1,48 @@
+/* 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 "global-ui.h"
+#include "alcachofa.h"
+
+namespace Alcachofa {
+
+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)));
+ _iconMortadeloDisabled.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::MortadeloDisabledIcon)));
+ _iconFilemonDisabled.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::FilemonDisabledIcon)));
+ _iconInventoryDisabled.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::InventoryDisabledIcon)));
+
+ _generalFont->load();
+ _dialogFont->load();
+ _iconMortadelo->load();
+ _iconFilemon->load();
+ _iconInventory->load();
+ _iconMortadeloDisabled->load();
+ _iconFilemonDisabled->load();
+ _iconInventoryDisabled->load();
+}
+
+}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
new file mode 100644
index 00000000000..558ff157d9d
--- /dev/null
+++ b/engines/alcachofa/global-ui.h
@@ -0,0 +1,52 @@
+/* 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/>.
+ *
+ */
+
+#ifndef GLOBAL_UI_H
+#define GLOBAL_UI_H
+
+#include "objects.h"
+
+namespace Alcachofa {
+
+class GlobalUI {
+public:
+ GlobalUI();
+
+ inline Font &generalFont() const { assert(_generalFont != nullptr); return *_generalFont; }
+ inline Font &dialogFont() const { assert(_dialogFont != nullptr); return *_dialogFont; }
+
+private:
+ Common::ScopedPtr<Font>
+ _generalFont,
+ _dialogFont;
+ Common::ScopedPtr<Animation>
+ _iconMortadelo,
+ _iconFilemon,
+ _iconInventory,
+ _iconMortadeloDisabled,
+ _iconFilemonDisabled,
+ _iconInventoryDisabled;
+};
+
+}
+
+
+#endif // GLOBAL_UI_H
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index bca3bc181a8..9c8300c66ea 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -7,6 +7,7 @@ MODULE_OBJS = \
console.o \
game-objects.cpp \
general-objects.cpp \
+ global-ui.cpp \
graphics.cpp \
graphics-opengl.cpp \
Input.cpp \
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c04ed7e7c09..bc903af522b 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -22,6 +22,7 @@
#include "alcachofa.h"
#include "rooms.h"
#include "script.h"
+#include "global-ui.h"
#include "common/file.h"
@@ -208,10 +209,6 @@ void Room::updateInteraction() {
if (updateOpeningInventory())
return;
- if (false && player.activeCharacter()->room() != this) { // TODO: Remove active character hack
- player.activeCharacter()->room() = this;
- }
-
player.selectedObject() = world().globalRoom().getSelectedObject(getSelectedObject());
if (player.selectedObject() == nullptr) {
if (input.wasMouseLeftPressed() && _activeFloorI >= 0 &&
@@ -403,7 +400,7 @@ bool Inventory::updateInput() {
}
g_engine->drawQueue().add<TextDrawRequest>(
- g_engine->world().generalFont(),
+ g_engine->globalUI().generalFont(),
g_engine->world().getLocalizedName(hoveredItem->name()),
input.mousePos2D() + Point(0, -50),
-1, true, kWhite, -kForegroundOrderCount + 1);
@@ -538,11 +535,6 @@ World::World() {
if (_mortadelo == nullptr)
error("Could not find MORTADELO");
- _generalFont.reset(new Font(getGlobalAnimationName(GlobalAnimationKind::GeneralFont)));
- _generalFont->load();
- _dialogFont.reset(new Font(getGlobalAnimationName(GlobalAnimationKind::DialogFont)));
- _dialogFont->load();
-
_inventory->initItems();
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index cdff4c62ee9..424c6601aa6 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -159,8 +159,6 @@ 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 Font &generalFont() const { assert(_generalFont != nullptr); return *_generalFont; }
- inline Font &dialogFont() const { assert(_dialogFont != nullptr); return *_dialogFont; }
inline const Common::String &initScriptName() const { return _initScriptName; }
inline uint8 loadedMapCount() const { return _loadedMapCount; }
@@ -197,7 +195,6 @@ private:
Room *_globalRoom;
Inventory *_inventory;
MainCharacter *_filemon, *_mortadelo;
- Common::ScopedPtr<Font> _generalFont, _dialogFont;
uint8 _loadedMapCount = 0;
Common::HashMap<const char *, const char *,
Common::Hash<const char*>,
Commit: e3625bf330a14c21517e9efb922f8ca5056e41ef
https://github.com/scummvm/scummvm/commit/e3625bf330a14c21517e9efb922f8ca5056e41ef
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Refactor inventory UI triggers into GlobalUI
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 67ac1fdadef..8f7d8b35eb0 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -22,8 +22,26 @@
#include "global-ui.h"
#include "alcachofa.h"
+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 I should add key-based controls for it
+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.getGlobalAnimationName(GlobalAnimationKind::GeneralFont)));
@@ -45,4 +63,49 @@ GlobalUI::GlobalUI() {
_iconInventoryDisabled->load();
}
+void GlobalUI::startClosingInventory() {
+ _isOpeningInventory = false;
+ _isClosingInventory = true;
+ _timeForInventory = g_system->getMillis();
+}
+
+void GlobalUI::updateClosingInventory() {
+ static constexpr uint32 kDuration = 300;
+ static constexpr float kSpeed = -10 / 3.0f / 1000.0f;
+
+ uint32 deltaTime = g_system->getMillis() - _timeForInventory;
+ if (!_isClosingInventory || deltaTime >= kDuration)
+ _isClosingInventory = false;
+ else
+ g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed)));
+}
+
+bool GlobalUI::updateOpeningInventory() {
+ static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
+ if (g_engine->player().isOptionsMenuOpen() || !g_engine->player().isGameLoaded())
+ return false;
+
+ if (_isOpeningInventory) {
+ uint32 deltaTime = g_system->getMillis() - _timeForInventory;
+ if (deltaTime >= 1000) {
+ _isOpeningInventory = false;
+ g_engine->world().inventory().open();
+ }
+ else {
+ deltaTime = MIN<uint32>(300, deltaTime);
+ g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed - 1)));
+ }
+ return true;
+ }
+ else if (openInventoryTriggerBounds().contains(g_engine->input().mousePos2D())) {
+ _isClosingInventory = false;
+ _isOpeningInventory = true;
+ _timeForInventory = g_system->getMillis();
+ g_engine->player().activeCharacter()->stopWalking();
+ g_engine->world().inventory().updateItemsByActiveCharacter();
+ return true;
+ }
+ return false;
+}
+
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 558ff157d9d..c5c5ec7a9e7 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -26,6 +26,9 @@
namespace Alcachofa {
+Common::Rect openInventoryTriggerBounds();
+Common::Rect closeInventoryTriggerBounds();
+
class GlobalUI {
public:
GlobalUI();
@@ -33,6 +36,10 @@ public:
inline Font &generalFont() const { assert(_generalFont != nullptr); return *_generalFont; }
inline Font &dialogFont() const { assert(_dialogFont != nullptr); return *_dialogFont; }
+ bool updateOpeningInventory();
+ void updateClosingInventory();
+ void startClosingInventory();
+
private:
Common::ScopedPtr<Font>
_generalFont,
@@ -44,6 +51,11 @@ private:
_iconMortadeloDisabled,
_iconFilemonDisabled,
_iconInventoryDisabled;
+
+ bool
+ _isOpeningInventory = false,
+ _isClosingInventory = false;
+ uint32 _timeForInventory = 0;
};
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index bc903af522b..c01fc4554be 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -30,21 +30,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.
-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());
-}
-
Room::Room(World *world, ReadStream &stream) : Room(world, stream, false) {
}
@@ -150,7 +135,7 @@ void Room::update() {
if (g_engine->player().currentRoom() == this) {
updateRoomBounds();
- updateClosingInventory();
+ g_engine->globalUI().updateClosingInventory();
if (!updateInput())
return;
}
@@ -206,7 +191,7 @@ void Room::updateInteraction() {
auto &input = g_engine->input();
// TODO: Add interaction with change character button
- if (updateOpeningInventory())
+ if (g_engine->globalUI().updateOpeningInventory())
return;
player.selectedObject() = world().globalRoom().getSelectedObject(getSelectedObject());
@@ -318,51 +303,6 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
return best;
}
-void Room::startClosingInventory() {
- _isOpeningInventory = false;
- _isClosingInventory = true;
- _timeForInventory = g_system->getMillis();
-}
-
-void Room::updateClosingInventory() {
- static constexpr uint32 kDuration = 300;
- static constexpr float kSpeed = -10 / 3.0f / 1000.0f;
-
- uint32 deltaTime = g_system->getMillis() - _timeForInventory;
- if (!_isClosingInventory || deltaTime >= kDuration)
- _isClosingInventory = false;
- else
- g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed)));
-}
-
-bool Room::updateOpeningInventory() {
- static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
- if (g_engine->player().isOptionsMenuOpen() || !g_engine->player().isGameLoaded())
- return false;
-
- if (_isOpeningInventory) {
- uint32 deltaTime = g_system->getMillis() - _timeForInventory;
- if (deltaTime >= 1000) {
- _isOpeningInventory = false;
- g_engine->world().inventory().open();
- }
- else {
- deltaTime = MIN<uint32>(300, deltaTime);
- g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed - 1)));
- }
- return true;
- }
- else if (openInventoryTriggerBounds().contains(g_engine->input().mousePos2D())) {
- _isClosingInventory = false;
- _isOpeningInventory = true;
- _timeForInventory = g_system->getMillis();
- g_engine->player().activeCharacter()->stopWalking();
- g_engine->world().inventory().updateItemsByActiveCharacter();
- return true;
- }
- return false;
-}
-
OptionsMenu::OptionsMenu(World *world, ReadStream &stream)
: Room(world, stream, true) {
}
@@ -487,7 +427,7 @@ void Inventory::open() {
void Inventory::close() {
g_engine->player().changeRoomToBeforeInventory();
g_engine->camera().restore(1);
- g_engine->player().currentRoom()->startClosingInventory();
+ g_engine->globalUI().startClosingInventory();
}
void Room::debugPrint(bool withObjects) const {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 424c6601aa6..c53c2a1796b 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -60,15 +60,12 @@ public:
virtual void serializeSave(Common::Serializer &serializer);
ObjectBase *getObjectByName(const Common::String &name) const;
void toggleActiveFloor();
- void startClosingInventory();
void debugPrint(bool withObjects) const;
protected:
Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
void updateScripts();
void updateRoomBounds();
- bool updateOpeningInventory();
- void updateClosingInventory();
void updateInteraction();
void updateObjects();
void drawObjects();
@@ -78,17 +75,13 @@ protected:
World *_world;
Common::String _name;
PathFindingShape _floors[2];
- bool
- _fixedCameraOnEntering,
- _isOpeningInventory = false,
- _isClosingInventory = false;
+ bool _fixedCameraOnEntering;
int8
_musicId,
_activeFloorI = -1;
uint8
_characterAlphaTint,
_characterAlphaPremultiplier; ///< for some reason in percent instead of 0-255
- uint32 _timeForInventory = 0;
Common::Array<ObjectBase *> _objects;
};
Commit: 17c4a38686f96c0b74d05a20bfcc9a922e5c13ae
https://github.com/scummvm/scummvm/commit/17c4a38686f96c0b74d05a20bfcc9a922e5c13ae
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Add character change button
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/graphics.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 8f7d8b35eb0..3a9fb3005be 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -49,18 +49,12 @@ GlobalUI::GlobalUI() {
_iconMortadelo.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::MortadeloIcon)));
_iconFilemon.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::FilemonIcon)));
_iconInventory.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::InventoryIcon)));
- _iconMortadeloDisabled.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::MortadeloDisabledIcon)));
- _iconFilemonDisabled.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::FilemonDisabledIcon)));
- _iconInventoryDisabled.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::InventoryDisabledIcon)));
_generalFont->load();
_dialogFont->load();
_iconMortadelo->load();
_iconFilemon->load();
_iconInventory->load();
- _iconMortadeloDisabled->load();
- _iconFilemonDisabled->load();
- _iconInventoryDisabled->load();
}
void GlobalUI::startClosingInventory() {
@@ -108,4 +102,82 @@ bool GlobalUI::updateOpeningInventory() {
return false;
}
+Animation *GlobalUI::activeAnimation() const {
+ return g_engine->player().activeCharacterKind() == MainCharacterKind::Mortadelo
+ ? _iconFilemon.get()
+ : _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 (player.isOptionsMenuOpen() ||
+ !player.isGameLoaded() ||
+ _isOpeningInventory)
+ return false;
+ _changeButton.frameI() = 0;
+
+ if (!isHoveringChangeButton())
+ return false;
+ if (g_engine->input().wasMouseLeftPressed())
+ {
+ player.pressedObject() = &_changeButton;
+ return true;
+ }
+ if (player.pressedObject() != &_changeButton)
+ return true;
+
+ player.setActiveCharacter(player.inactiveCharacter()->kind());
+ player.heldItem() = nullptr;
+ g_engine->camera().setFollow(player.activeCharacter());
+ g_engine->camera().restore(0);
+ player.changeRoom(player.activeCharacter()->room()->name(), false);
+ // TODO: Queue character change jingle
+
+ _changeButton.setAnimation(activeAnimation());
+ _changeButton.start(false);
+ return true;
+}
+
+void GlobalUI::drawChangingButton() {
+ auto &player = g_engine->player();
+ if (player.isOptionsMenuOpen() ||
+ !player.isGameLoaded() ||
+ !player.semaphore().isReleased() ||
+ _isOpeningInventory ||
+ _isClosingInventory)
+ return;
+
+ auto anim = activeAnimation();
+ if (!_changeButton.hasAnimation() || &_changeButton.animation() != anim)
+ {
+ _changeButton.setAnimation(anim);
+ _changeButton.pause();
+ _changeButton.lastTime() = 42 * (anim->frameCount() - 1) + 1;
+ }
+
+ _changeButton.center() = { (int16)(g_system->getWidth() + 2), -2 };
+ if (isHoveringChangeButton() &&
+ g_engine->input().isMouseLeftDown() &&
+ player.pressedObject() == &_changeButton)
+ {
+ _changeButton.center().x -= 2;
+ _changeButton.center().y += 2;
+ }
+
+ _changeButton.order() = -9;
+ _changeButton.update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(_changeButton, false, BlendMode::AdditiveAlpha);
+}
+
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index c5c5ec7a9e7..d07dbf86f8d 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -36,21 +36,24 @@ public:
inline Font &generalFont() const { assert(_generalFont != nullptr); return *_generalFont; }
inline Font &dialogFont() const { assert(_dialogFont != nullptr); return *_dialogFont; }
+ bool updateChangingCharacter();
+ void drawChangingButton();
bool updateOpeningInventory();
void updateClosingInventory();
void startClosingInventory();
private:
+ Animation *activeAnimation() const;
+ bool isHoveringChangeButton() const;
+
+ Graphic _changeButton;
Common::ScopedPtr<Font>
_generalFont,
_dialogFont;
Common::ScopedPtr<Animation>
_iconMortadelo,
_iconFilemon,
- _iconInventory,
- _iconMortadeloDisabled,
- _iconFilemonDisabled,
- _iconInventoryDisabled;
+ _iconInventory;
bool
_isOpeningInventory = false,
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index a394604ca41..78749514ce2 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -257,7 +257,7 @@ public:
inline float &depthScale() { return _depthScale; }
inline Color &color() { return _color; }
inline int32 &frameI() { return _frameI; }
- inline uint32 lastTime() const { return _lastTime; }
+ inline uint32 &lastTime() { return _lastTime; }
inline bool isPaused() const { return _isPaused; }
inline bool hasAnimation() const { return _animation != nullptr; }
inline Animation &animation() {
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 479a30009f1..a873dd870a7 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -49,6 +49,10 @@ void Player::drawScreenStates() {
g_engine->drawQueue().add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
}
+void Player::resetCursor() {
+ _cursorFrameI = 0;
+}
+
void Player::updateCursor() {
if (_isOptionsMenuOpen || !_isGameLoaded)
_cursorFrameI = 0;
@@ -75,8 +79,6 @@ void Player::updateCursor() {
else if (g_engine->input().isMouseRightDown())
_cursorFrameI = 4;
}
-
- drawCursor();
}
void Player::drawCursor(bool forceDefaultCursor) {
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index f63f95e1a3e..1b7139bc0cd 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -33,7 +33,7 @@ public:
inline Room *currentRoom() const { return _currentRoom; }
inline MainCharacter *activeCharacter() const { return _activeCharacter; }
inline ShapeObject *&selectedObject() { return _selectedObject; }
- inline ShapeObject *&pressedObject() { return _pressedObject; }
+ inline void *&pressedObject() { return _pressedObject; }
inline Item *&heldItem() { return _heldItem; }
inline FakeSemaphore &semaphore() { return _semaphore; }
MainCharacter *inactiveCharacter() const;
@@ -51,6 +51,7 @@ public:
void drawScreenStates(); // black borders and/or permanent fade
void updateCursor();
void drawCursor(bool forceDefaultCursor = false);
+ void resetCursor();
void changeRoom(const Common::String &targetRoomName, bool resetCamera);
void changeRoomToBeforeInventory();
void triggerObject(ObjectBase *object, const char *action);
@@ -69,7 +70,7 @@ private:
*_roomBeforeInventory = nullptr;
MainCharacter *_activeCharacter;
ShapeObject *_selectedObject = nullptr;
- ShapeObject *_pressedObject = nullptr;
+ void *_pressedObject = nullptr; // terrible but GlobalUI wants to store a Graphic pointer
Item *_heldItem = nullptr;
int32 _cursorFrameI = 0;
bool
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index c01fc4554be..4e42b859d8f 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -178,21 +178,26 @@ bool Room::updateInput() {
if (player.isOptionsMenuOpen() || !player.isGameLoaded())
canInteract = true;
if (canInteract) {
- updateInteraction();
- player.updateCursor();
+ player.resetCursor();
+ if (!g_engine->globalUI().updateChangingCharacter() &&
+ !g_engine->globalUI().updateOpeningInventory()) {
+ updateInteraction();
+ player.updateCursor();
+ }
+ player.drawCursor();
+ }
+
+ if (player.currentRoom() == this) {
+ g_engine->globalUI().drawChangingButton();
+ // TODO: Add main menu handling
}
- // TODO: Add main menu and opening inventory handling
return player.currentRoom() == this;
}
void Room::updateInteraction() {
auto &player = g_engine->player();
auto &input = g_engine->input();
- // TODO: Add interaction with change character button
-
- if (g_engine->globalUI().updateOpeningInventory())
- return;
player.selectedObject() = world().globalRoom().getSelectedObject(getSelectedObject());
if (player.selectedObject() == nullptr) {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index c53c2a1796b..6a971bea562 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -131,7 +131,7 @@ enum class GlobalAnimationKind {
MortadeloIcon,
FilemonIcon,
InventoryIcon,
- MortadeloDisabledIcon,
+ MortadeloDisabledIcon, // only used for multiplayer
FilemonDisabledIcon,
InventoryDisabledIcon,
Commit: c97572c37164f80da3af4ff122c2655d70a5b884
https://github.com/scummvm/scummvm/commit/c97572c37164f80da3af4ff122c2655d70a5b884
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Speed up room transitions by buffering images
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index c7cf2885dec..d6a0cad019f 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -26,6 +26,7 @@
#include "common/system.h"
#include "common/file.h"
#include "common/substream.h"
+#include "common/bufferedstream.h"
#include "image/tga.h"
using namespace Common;
@@ -66,7 +67,7 @@ void AnimationBase::load() {
if (_isLoaded)
return;
- Common::String fullPath;
+ String fullPath;
switch (_folder) {
case AnimationFolder::Animations: fullPath = "Animaciones/"; break;
case AnimationFolder::Masks: fullPath = "Mascaras/"; break;
@@ -76,7 +77,8 @@ void AnimationBase::load() {
if (_fileName.size() < 4 || scumm_strnicmp(_fileName.end() - 4, ".AN0", 4) != 0)
_fileName += ".AN0";
fullPath += _fileName;
- Common::File file;
+
+ File file;
if (!file.open(fullPath.c_str())) {
// original fallback
fullPath = "Mascaras/" + _fileName;
@@ -85,16 +87,18 @@ void AnimationBase::load() {
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));
- uint spriteCount = file.readUint32LE();
+ uint spriteCount = stream->readUint32LE();
assert(spriteCount < kMaxSpriteIDs);
_spriteBases.reserve(spriteCount);
- uint imageCount = file.readUint32LE();
+ uint imageCount = stream->readUint32LE();
_images.reserve(imageCount);
_imageOffsets.reserve(imageCount);
for (uint i = 0; i < imageCount; i++) {
- _images.push_back(readImage(file));
+ _images.push_back(readImage(*stream));
}
// an inconsistency, maybe a historical reason:
@@ -102,32 +106,32 @@ 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(file.readUint32LE());
+ _spriteBases.push_back(stream->readUint32LE());
assert(_spriteBases.back() < imageCount);
}
#ifdef _DEBUG
for (uint i = spriteCount; i < kMaxSpriteIDs; i++)
- assert(file.readSint32LE() == 0);
+ assert(stream->readSint32LE() == 0);
#else
- file.skip(sizeof(int32) * (kMaxSpriteIDs - spriteCount));
+ stream->skip(sizeof(int32) * (kMaxSpriteIDs - spriteCount));
#endif
for (uint i = 0; i < imageCount; i++)
- _imageOffsets.push_back(readPoint(file));
+ _imageOffsets.push_back(readPoint(*stream));
for (uint i = 0; i < kMaxSpriteIDs; i++)
- _spriteIndexMapping[i] = file.readSint32LE();
+ _spriteIndexMapping[i] = stream->readSint32LE();
- uint frameCount = file.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(file.readUint32LE());
+ _spriteOffsets.push_back(stream->readUint32LE());
AnimationFrame frame;
- frame._center = readPoint(file);
- frame._offset = readPoint(file);
- frame._duration = file.readUint32LE();
+ frame._center = readPoint(*stream);
+ frame._offset = readPoint(*stream);
+ frame._duration = stream->readUint32LE();
_frames.push_back(frame);
_totalDuration += frame._duration;
}
Commit: 25e4aebd7988036f0cc24b5ea42e250fd7a6938f
https://github.com/scummvm/scummvm/commit/25e4aebd7988036f0cc24b5ea42e250fd7a6938f
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Disable OpenGL debugging by default
Changed paths:
engines/alcachofa/graphics-opengl.cpp
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index c3e882f222d..7b1682656eb 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -25,7 +25,12 @@
#include "engines/util.h"
#include "graphics/managed_surface.h"
#include "graphics/opengl/system_headers.h"
+
+#ifdef ALCACHOFA_DEBUG_OPENGL // clearing OpenGL errors are very slow, so we only activate the debugging wrapper if necessary
#include "graphics/opengl/debug.h"
+#else
+#define GL_CALL(call) call
+#endif
using namespace Common;
using namespace Math;
Commit: 381872dfda36bb0ec5f17e04e94ce128478166ba
https://github.com/scummvm/scummvm/commit/381872dfda36bb0ec5f17e04e94ce128478166ba
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:50+02:00
Commit Message:
ALCACHOFA: Fix changing character during dialog
Changed paths:
engines/alcachofa/game-objects.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 0c46b0fb3b6..4b43948f14b 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -941,6 +941,7 @@ struct DialogMenuTask : public Task {
TASK_YIELD;
if (g_engine->player().activeCharacter() != _character)
continue;
+ g_engine->globalUI().updateChangingCharacter();
g_engine->player().heldItem() = nullptr;
g_engine->player().drawCursor();
Commit: 3cb5cebc183109340b905c23c5f1070325f98234
https://github.com/scummvm/scummvm/commit/3cb5cebc183109340b905c23c5f1070325f98234
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Fix Filemon not being rendered
Changed paths:
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 4e42b859d8f..f8ff4192b56 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -233,8 +233,7 @@ void Room::updateObjects() {
void Room::drawObjects() {
for (auto *object : _objects) {
- if (object->room() == g_engine->player().currentRoom())
- object->draw();
+ object->draw();
}
}
Commit: cabcd496077a949ddf5cbd3a2f3bfa788212bf9c
https://github.com/scummvm/scummvm/commit/cabcd496077a949ddf5cbd3a2f3bfa788212bf9c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Fix Drop kernel calls with nullptr item
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 4b43948f14b..32973624829 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -893,12 +893,14 @@ void MainCharacter::pickup(const String &name, bool putInHand) {
}
void MainCharacter::drop(const Common::String &name) {
- auto item = getItemByName(name);
- if (item == nullptr)
- error("Tried to drop unknown item: %s", name.c_str());
- item->toggle(false);
+ if (!name.empty()) {
+ auto item = getItemByName(name);
+ if (item == nullptr)
+ error("Tried to drop unknown item: %s", name.c_str());
+ item->toggle(false);
+ }
if (g_engine->player().activeCharacter() == this) {
- // TODO: Clear held item for drop
+ g_engine->player().heldItem() = nullptr;
g_engine->world().inventory().updateItemsByActiveCharacter();
}
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 380304b6c1e..2ef9964be67 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -400,7 +400,7 @@ private:
auto entry = getArg(argI);
if (entry._type != StackEntryType::String)
error("Expected string in argument %u for kernel call", argI);
- return _script._strings->data() + entry._index;
+ return &_script._strings[entry._index];
}
int32 getNumberOrStringArg(uint argI) {
@@ -408,10 +408,20 @@ private:
// as it will be interpreted as a boolean we only care about == 0 / != 0
auto entry = getArg(argI);
if (entry._type != StackEntryType::Number && entry._type != StackEntryType::String)
- error("Expected number of string in argument %u for kernel call", argI);
+ error("Expected number or string in argument %u for kernel call", argI);
return entry._number;
}
+ const char *getOptionalStringArg(uint argI) {
+ // another special case: a string that may be zero which is passed as number
+ auto entry = getArg(argI);
+ if (entry._type == StackEntryType::String)
+ return &_script._strings[entry._index];
+ if (entry._type == StackEntryType::Number && entry._number == 0)
+ return nullptr;
+ error("Expected optional string in argument %u for kernel call", argI);
+ }
+
MainCharacter &relatedCharacter() {
if (process().character() == MainCharacterKind::None)
error("Script tried to use character from non-character-related process");
@@ -669,7 +679,7 @@ private:
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterDrop: {
auto &character = g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(1));
- character.drop(getStringArg(0));
+ character.drop(getOptionalStringArg(0));
return TaskReturn::finish(1);
}
case ScriptKernelTask::ClearInventory:
Commit: 9e2d1cfbc16aa60a6a16bf30c00f5dd576103e89
https://github.com/scummvm/scummvm/commit/9e2d1cfbc16aa60a6a16bf30c00f5dd576103e89
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Fix a couple corner cases
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index d6a0cad019f..4bdcc44e4aa 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -191,7 +191,8 @@ void AnimationBase::loadMissingAnimation() {
// only allow missing animations we know are faulty in the original game
if (!_fileName.equalsIgnoreCase("ANIMACION.AN0") &&
!_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0") &&
- !_fileName.equalsIgnoreCase("PP_MORTA.AN0"))
+ !_fileName.equalsIgnoreCase("PP_MORTA.AN0") &&
+ !_fileName.equalsIgnoreCase("DESPACHO_SUPER2___FONDO_PP_SUPER.AN0"))
error("Could not open animation %s", _fileName.c_str());
// otherwise setup a functioning but empty animation
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index f8ff4192b56..e5265abf315 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -554,7 +554,8 @@ void World::toggleObject(MainCharacterKind character, const Common::String &objN
if (object == nullptr)
object = getObjectByNameFromAnyRoom(objName);
if (object == nullptr)
- error("Tried to toggle unknown object: %s", objName.c_str());
+ warning("Tried to toggle unknown object: %s", objName.c_str());
+ // I would have liked an error for this, but original inconsistencies...
else
object->toggle(isEnabled);
}
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 5ff9a856ebd..9fc5f7e670d 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -133,7 +133,6 @@ Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) co
}
}
}
- assert(contains(bestPoint));
return bestPoint;
}
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index ec6ad549036..4eb45275469 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -124,11 +124,17 @@ static AudioStream *openAudio(const String &fileName) {
if (file->open(path.c_str()))
return makeWAVStream(file, DisposeAfterUse::YES);
delete file;
+
+ // Ignore the known, original wrong filenames given, report the rest
+ if (fileName == "CHAS")
+ return nullptr;
error("Could not open audio file: %s", fileName.c_str());
}
SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::SoundType type) {
AudioStream *stream = openAudio(fileName);
+ if (stream == nullptr)
+ return UINT32_MAX;
SoundHandle handle;
_mixer->playStream(type, &handle, stream, -1, volume);
SoundID id = _nextID++;
Commit: b5c5cfaef9159eb93fc7579f8478d6db1c2671e2
https://github.com/scummvm/scummvm/commit/b5c5cfaef9159eb93fc7579f8478d6db1c2671e2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Simplify and fix Player::changeRoom
Fixes invalid graphic object error rata_TIA in SERVICIOS_TIA
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index a873dd870a7..d6caf22e61f 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -103,39 +103,39 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
debug("Change room to %s", targetRoomName.c_str());
// 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
+ // Instead we just keep resources loaded for all global rooms and during inventory/room transitions
+
if (targetRoomName.equalsIgnoreCase("SALIR")) {
_currentRoom = nullptr;
- return;
+ return; // exiting game entirely
}
- Room &inventory = g_engine->world().inventory();
- bool keepResources;
- if (_currentRoom == &inventory)
- keepResources = _roomBeforeInventory != nullptr && _roomBeforeInventory->name().equalsIgnoreCase(targetRoomName);
- else {
- keepResources = _currentRoom != nullptr && _currentRoom->name().equalsIgnoreCase(targetRoomName);
- }
_roomBeforeInventory = nullptr;
- if (targetRoomName.equalsIgnoreCase("inventario")) {
- keepResources = true;
- _roomBeforeInventory = _currentRoom;
- }
-
- if (!keepResources && _currentRoom != nullptr) {
+ if (_currentRoom != nullptr) {
g_engine->scheduler().killProcessByName("ACTUALIZAR_" + _currentRoom->name());
- _currentRoom->freeResources();
+
+ bool keepResources =
+ _currentRoom->name().equalsIgnoreCase(targetRoomName) ||
+ _currentRoom->name().equalsIgnoreCase("inventario");
+ if (targetRoomName.equalsIgnoreCase("inventario")) {
+ keepResources = true;
+ _roomBeforeInventory = _currentRoom;
+ }
+ if (!keepResources)
+ _currentRoom->freeResources();
}
+
_currentRoom = g_engine->world().getRoomByName(targetRoomName);
if (_currentRoom == nullptr)
error("Invalid room name: %s", targetRoomName.c_str());
if (!_didLoadGlobalRooms) {
_didLoadGlobalRooms = true;
- inventory.loadResources();
+ g_engine->world().inventory().loadResources();
g_engine->world().globalRoom().loadResources();
}
- if (!keepResources)
- _currentRoom->loadResources();
+ _currentRoom->loadResources(); // if we kept resources we loop over a couple noops, that is fine.
if (resetCamera)
g_engine->camera().resetRotationAndScale();
Commit: ce65cf96c2c1f726b4aa85b5ab460d6f17df2daa
https://github.com/scummvm/scummvm/commit/ce65cf96c2c1f726b4aa85b5ab460d6f17df2daa
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Workaround bug with invalid PUT target objecgt
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 2ef9964be67..2a2b8f24a56 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -588,11 +588,16 @@ private:
auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
auto character = dynamic_cast<WalkingCharacter *>(characterObject);
if (character == nullptr)
- error("Script tried to make invalid character go: %s", getStringArg(0));
- auto targetObject = g_engine->world().getObjectByName(process().character(), getStringArg(1));
- auto target = dynamic_cast<PointObject *>(targetObject);
+ error("Script tried to make invalid character put: %s", getStringArg(0));
+ auto target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), getStringArg(1)));
+ if (target == nullptr && !scumm_stricmp("A_Poblado_Indio", getStringArg(1))) {
+ // An original bug, 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
+ target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), "A_Poblado_Indio1"));
+ }
if (target == nullptr)
- error("Script tried to make character go to invalid object %s", getStringArg(1));
+ error("Script tried to make character put to invalid object %s", getStringArg(1));
character->setPosition(target->position());
return TaskReturn::finish(1);
}
Commit: 71988c9edf298d17fa0c03b7a1313734c47dd5cc
https://github.com/scummvm/scummvm/commit/71988c9edf298d17fa0c03b7a1313734c47dd5cc
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Workaround bugs in CASA_FREDDY_ARRIBA
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 32973624829..a5028b8387f 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -271,10 +271,14 @@ void Character::onClick() {
void Character::trigger(const char *action) {
g_engine->player().activeCharacter()->stopWalking(_interactionDirection);
- if (scumm_stricmp(action, "iSABANA") == 0 && // Original hack probably to fix some bug :)
+ if (scumm_stricmp(action, "iSABANA") == 0 &&
dynamic_cast<MainCharacter *>(this) != nullptr &&
- room()->name().equalsIgnoreCase("CASA_FREDDY_ARRIBA"))
- error("Not sure what *should* happen. How do we get here?");
+ !room()->name().equalsIgnoreCase("CASA_FREDDY_ARRIBA")) {
+ // An original hack to check that we use the bed sheet on the main character only in the correct room
+ // There *is* another script variable (es_casa_freddy) that should check this
+ // but, I guess, Alcachofa Soft found a corner case where this does not work?
+ return;
+ }
g_engine->player().triggerObject(this, action);
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 2a2b8f24a56..1467914ea46 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -422,6 +422,13 @@ private:
error("Expected optional string in argument %u for kernel call", argI);
}
+ template<class TObject = ObjectBase>
+ TObject *getObjectArg(uint argI) {
+ const char *const name = getStringArg(argI);
+ auto *object = g_engine->world().getObjectByName(process().character(), name);
+ return dynamic_cast<TObject*>(object);
+ }
+
MainCharacter &relatedCharacter() {
if (process().character() == MainCharacterKind::None)
error("Script tried to use character from non-character-related process");
@@ -567,12 +574,10 @@ private:
return TaskReturn::finish(1);
}
case ScriptKernelTask::Go: {
- auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto character = dynamic_cast<WalkingCharacter *>(characterObject);
+ auto character = getObjectArg<WalkingCharacter>(0);
if (character == nullptr)
error("Script tried to make invalid character go: %s", getStringArg(0));
- auto targetObject = g_engine->world().getObjectByName(process().character(), getStringArg(1));
- auto target = dynamic_cast<PointObject *>(targetObject);
+ auto target= getObjectArg<PointObject>(1);
if (target == nullptr)
error("Script tried to make character go to invalid object %s", getStringArg(1));
character->walkTo(target->position());
@@ -588,14 +593,10 @@ private:
auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
auto character = dynamic_cast<WalkingCharacter *>(characterObject);
if (character == nullptr)
- error("Script tried to make invalid character put: %s", getStringArg(0));
+ error("Script tried to put invalid character: %s", getStringArg(0));
auto target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), getStringArg(1)));
- if (target == nullptr && !scumm_stricmp("A_Poblado_Indio", getStringArg(1))) {
- // An original bug, 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
- target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), "A_Poblado_Indio1"));
- }
+ if (target == nullptr && !exceptionsForPut(target, getStringArg(1)))
+ return TaskReturn::finish(2);
if (target == nullptr)
error("Script tried to make character put to invalid object %s", getStringArg(1));
character->setPosition(target->position());
@@ -823,6 +824,36 @@ private:
assert(character.semaphore().isReleased()); // this process should be the last to hold a lock if at all...
}
+ /**
+ * @brief Check for original bugs related to the Put kernel call and handle them
+ * @param target An out reference to the point object (maybe we can find an alternative one)
+ * @param targetName The given name of the target object
+ * @return false if the put kernel call should be ignored, true if we set target and want to continue with the kernel call
+ */
+ bool exceptionsForPut(PointObject *&target, const char *targetName) {
+ assert(target == nullptr); // if not, why did we check for exceptions?
+
+ if (!scumm_stricmp("A_Poblado_Indio", targetName)) {
+ // 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
+ target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), "A_Poblado_Indio1"));
+ }
+
+ if (!scumm_stricmp("PUNTO_VENTANA", targetName)) {
+ // The object is in the previous, now inactive room.
+ // Luckily Mortadelo already is at that point so not further action required
+ return false;
+ }
+
+ if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", targetName)) {
+ // Another case of a door being cast into a PointObject
+ return false;
+ }
+
+ return true;
+ }
+
Script &_script;
Stack<StackEntry> _stack;
String _name;
Commit: 7b08ff53d7e7ebb793d6d805f64559e36bbfff9b
https://github.com/scummvm/scummvm/commit/7b08ff53d7e7ebb793d6d805f64559e36bbfff9b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Workaround bugs in HABITACION_DRACULA and MOTEL_ENTRADA
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 1467914ea46..84ae89855f5 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -548,6 +548,8 @@ private:
case ScriptKernelTask::Animate: {
auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
auto graphicObject = dynamic_cast<GraphicObject *>(object);
+ if (graphicObject == nullptr && !scumm_stricmp("EXPLOSION DISFRAZ", getStringArg(0)))
+ return TaskReturn::finish(1);
if (graphicObject == nullptr)
error("Script tried to animate invalid graphic object %s", getStringArg(0));
if (getNumberOrStringArg(1)) {
@@ -632,6 +634,10 @@ private:
if (character == nullptr)
error("Invalid character name: %s", getStringArg(0));
auto *animObject = g_engine->world().getObjectByName(getStringArg(1));
+ if (animObject == nullptr && (
+ !scumm_stricmp("COGE F DCH", getStringArg(1)) ||
+ !scumm_stricmp("CHIQUITO_IZQ", getStringArg(1))))
+ return TaskReturn::finish(2); // original bug in MOTEL_ENTRADA
if (animObject == nullptr)
error("Invalid animate object name: %s", getStringArg(1));
return TaskReturn::waitFor(character->animate(process(), animObject));
Commit: a2266bd22c3bedd5835346cec15634d6f42c7b75
https://github.com/scummvm/scummvm/commit/a2266bd22c3bedd5835346cec15634d6f42c7b75
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Fix InteractableObject being door target
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index d6caf22e61f..4f822a5fcf9 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -202,11 +202,15 @@ struct DoorTask : public Task {
if (_targetRoom == nullptr)
error("Invalid door target room: %s", door->targetRoom().c_str());
- _targetDoor = dynamic_cast<Door *>(_targetRoom->getObjectByName(door->targetObject()));
- if (_targetDoor == nullptr)
+ _targetObject = dynamic_cast<InteractableObject *>(_targetRoom->getObjectByName(door->targetObject()));
+ if (_targetObject == nullptr)
error("Invalid door target door: %s", door->targetObject().c_str());
+ auto targetDoor = dynamic_cast<const Door *>(_targetObject);
+ _targetDirection = targetDoor == nullptr
+ ? _targetObject->interactionDirection()
+ : targetDoor->characterDirection();
- process.name() = String::format("Door to %s %s", _targetRoom->name().c_str(), _targetDoor->name().c_str());
+ process.name() = String::format("Door to %s %s", _targetRoom->name().c_str(), _targetObject->name().c_str());
}
virtual TaskReturn run() {
@@ -217,11 +221,11 @@ struct DoorTask : public Task {
_player.changeRoom(_targetRoom->name(), true);
if (_targetRoom->fixedCameraOnEntering())
- g_engine->camera().setPosition(as2D(_targetDoor->interactionPoint()));
+ g_engine->camera().setPosition(as2D(_targetObject->interactionPoint()));
else {
_character->room() = _targetRoom;
- _character->setPosition(_targetDoor->interactionPoint());
- _character->stopWalking(_targetDoor->characterDirection());
+ _character->setPosition(_targetObject->interactionPoint());
+ _character->stopWalking(_targetDirection);
g_engine->camera().setFollow(_character, true);
}
@@ -239,7 +243,9 @@ struct DoorTask : public Task {
private:
FakeLock _lock;
- const Door *_sourceDoor, *_targetDoor;
+ const Door *_sourceDoor;
+ const InteractableObject *_targetObject;
+ Direction _targetDirection;
Room *_targetRoom;
MainCharacter *_character;
Player &_player;
Commit: eced79308fb21e1b7172bb212ec0514d99e290ae
https://github.com/scummvm/scummvm/commit/eced79308fb21e1b7172bb212ec0514d99e290ae
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Use the helper functions more in the kernel procs
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 84ae89855f5..e26f517638a 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -546,8 +546,7 @@ private:
g_engine->world().toggleObject(process().character(), getStringArg(0), false);
return TaskReturn::finish(0);
case ScriptKernelTask::Animate: {
- auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto graphicObject = dynamic_cast<GraphicObject *>(object);
+ auto graphicObject = getObjectArg<GraphicObject>(0);
if (graphicObject == nullptr && !scumm_stricmp("EXPLOSION DISFRAZ", getStringArg(0)))
return TaskReturn::finish(1);
if (graphicObject == nullptr)
@@ -563,8 +562,7 @@ private:
// character control / animation
case ScriptKernelTask::StopAndTurn: {
- auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto character = dynamic_cast<WalkingCharacter *>(object);
+ auto character = getObjectArg<WalkingCharacter>(0);
if (character == nullptr)
error("Script tried to stop-and-turn unknown character");
else
@@ -592,11 +590,10 @@ private:
: TaskReturn::waitFor(character->waitForArrival(process()));
}
case ScriptKernelTask::Put: {
- auto characterObject = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto character = dynamic_cast<WalkingCharacter *>(characterObject);
+ auto character = getObjectArg<WalkingCharacter>(0);
if (character == nullptr)
error("Script tried to put invalid character: %s", getStringArg(0));
- auto target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), getStringArg(1)));
+ auto target = getObjectArg<PointObject>(1);
if (target == nullptr && !exceptionsForPut(target, getStringArg(1)))
return TaskReturn::finish(2);
if (target == nullptr)
@@ -605,7 +602,7 @@ private:
return TaskReturn::finish(1);
}
case ScriptKernelTask::ChangeCharacterRoom: {
- auto *character = dynamic_cast<Character *>(g_engine->world().globalRoom().getObjectByName(getStringArg(0)));
+ auto *character = getObjectArg<Character>(0);
if (character == nullptr)
error("Invalid character name: %s", getStringArg(0));
auto *targetRoom = g_engine->world().getRoomByName(getStringArg(1));
@@ -616,7 +613,7 @@ private:
return TaskReturn::finish(1);
}
case ScriptKernelTask::LerpCharacterLodBias: {
- auto *character = dynamic_cast<Character *>(g_engine->world().globalRoom().getObjectByName(getStringArg(0)));
+ auto *character = getObjectArg<Character>(0);
if (character == nullptr)
error("Invalid character name: %s", getStringArg(0));
float targetLodBias = getNumberArg(1) * 0.01f;
@@ -630,10 +627,10 @@ private:
return TaskReturn::waitFor(character->lerpLodBias(process(), targetLodBias, durationMs));
}
case ScriptKernelTask::AnimateCharacter: {
- auto *character = dynamic_cast<Character *>(g_engine->world().getObjectByName(getStringArg(0)));
+ auto *character = getObjectArg<Character>(0);
if (character == nullptr)
error("Invalid character name: %s", getStringArg(0));
- auto *animObject = g_engine->world().getObjectByName(getStringArg(1));
+ auto *animObject = getObjectArg(1);
if (animObject == nullptr && (
!scumm_stricmp("COGE F DCH", getStringArg(1)) ||
!scumm_stricmp("CHIQUITO_IZQ", getStringArg(1))))
@@ -643,14 +640,14 @@ private:
return TaskReturn::waitFor(character->animate(process(), animObject));
}
case ScriptKernelTask::AnimateTalking: {
- auto *character = dynamic_cast<Character *>(g_engine->world().getObjectByName(getStringArg(0)));
+ auto *character = getObjectArg<Character>(0);
if (character == nullptr)
error("Invalid character name: %s", getStringArg(0));
const char *talkObjectName = getStringArg(1);
ObjectBase *talkObject = nullptr;
if (talkObjectName != nullptr && *talkObjectName)
{
- talkObject = g_engine->world().getObjectByName(talkObjectName);
+ talkObject = getObjectArg(1);
if (talkObject == nullptr)
error("Invalid talk object name: %s", talkObjectName);
}
@@ -661,21 +658,21 @@ private:
const char *characterName = getStringArg(0);
int32 dialogId = getNumberArg(1);
if (strncmp(characterName, "MENU_", 5) == 0) {
- g_engine->world().getMainCharacterByKind(process().character()).addDialogLine(dialogId);
+ relatedCharacter().addDialogLine(dialogId);
return TaskReturn::finish(1);
}
Character *_character = strcmp(characterName, "AMBOS") == 0
- ? &g_engine->world().getMainCharacterByKind(process().character())
- : dynamic_cast<Character *>(g_engine->world().getObjectByName(characterName));
+ ? &relatedCharacter()
+ : getObjectArg<Character>(0);
if (_character == nullptr)
error("Invalid character for sayText: %s", characterName);
return TaskReturn::waitFor(_character->sayText(process(), dialogId));
};
case ScriptKernelTask::SetDialogLineReturn:
- g_engine->world().getMainCharacterByKind(process().character()).setLastDialogReturnValue(getNumberArg(0));
+ relatedCharacter().setLastDialogReturnValue(getNumberArg(0));
return TaskReturn::finish(0);
case ScriptKernelTask::DialogMenu:
- return TaskReturn::waitFor(g_engine->world().getMainCharacterByKind(process().character()).dialogMenu(process()));
+ return TaskReturn::waitFor(relatedCharacter().dialogMenu(process()));
// Inventory control
case ScriptKernelTask::Pickup:
@@ -739,8 +736,7 @@ private:
case ScriptKernelTask::LerpCamToObjectKeepingZ: {
if (!process().isActiveForPlayer())
return TaskReturn::finish(0); // contrary to ...ResettingZ this one does not delay if not active
- auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto pointObject = dynamic_cast<PointObject *>(object);
+ auto pointObject = getObjectArg<PointObject>(0);
if (pointObject == nullptr)
error("Invalid target object for LerpCamToObjectKeepingZ: %s", getStringArg(0));
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
@@ -750,8 +746,7 @@ private:
case ScriptKernelTask::LerpCamToObjectResettingZ: {
if (!process().isActiveForPlayer())
return TaskReturn::waitFor(delay(getNumberArg(1)));
- auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto pointObject = dynamic_cast<PointObject *>(object);
+ auto pointObject = getObjectArg<PointObject>(0);
if (pointObject == nullptr)
error("Invalid target object for LerpCamToObjectResettingZ: %s", getStringArg(0));
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
@@ -761,8 +756,7 @@ private:
case ScriptKernelTask::LerpCamToObjectWithScale: {
if (!process().isActiveForPlayer())
return TaskReturn::waitFor(delay(getNumberArg(2)));
- auto object = g_engine->world().getObjectByName(process().character(), getStringArg(0));
- auto pointObject = dynamic_cast<PointObject *>(object);
+ auto pointObject = getObjectArg<PointObject>(0);
if (pointObject == nullptr)
error("Invalid target object for LerpCamToObjectWithScale: %s", getStringArg(0));
return TaskReturn::waitFor(g_engine->camera().lerpPosScale(process(),
Commit: 72391731e51ed3265b198002fcea2bf337bf3039
https://github.com/scummvm/scummvm/commit/72391731e51ed3265b198002fcea2bf337bf3039
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Fix entering ESTOMAGO and DINOSAURIO
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/console.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 3be4477e706..5eb70636814 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -114,7 +114,7 @@ void minmax(Vector3d &min, Vector3d &max, Vector3d val)
Vector3d Camera::setAppliedCenter(Vector3d center) {
setupMatricesAround(center);
- if (g_engine->script().variable("EncuadrarCamara") || true) {
+ if (g_engine->script().variable("EncuadrarCamara")) {
const float screenW = g_system->getWidth(), screenH = g_system->getHeight();
Vector3d min, max;
min = max = transform2Dto3D(Vector3d(0, 0, _roomScale));
@@ -171,6 +171,7 @@ void Camera::update() {
if (_catchUp && _cur._followTarget != nullptr) {
for (int i = 0; i < 4; i++)
updateFollowing(50.0f);
+ _catchUp = false;
}
else
updateFollowing(deltaTime);
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 247035a44a4..7a2941151a9 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -251,7 +251,7 @@ bool Console::cmdTeleport(int argc, const char **args)
if (argc > 1)
{
char *end = nullptr;
- param = (int32)strtol(args[2], &end, 10);
+ param = (int32)strtol(args[1], &end, 10);
if (end == nullptr || *end != '\0')
{
debugPrintf("Character kind can only be integer");
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 98e75fa0c8c..7a4fb5c17dd 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -102,7 +102,7 @@ GraphicObject::GraphicObject(Room *room, const char *name)
}
void GraphicObject::draw() {
- if (!isEnabled())
+ if (!isEnabled() || !_graphic.hasAnimation())
return;
const BlendMode blendMode = _type == GraphicObjectType::Effect
? BlendMode::Alpha
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 4bdcc44e4aa..c458aad26bf 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -192,7 +192,8 @@ void AnimationBase::loadMissingAnimation() {
if (!_fileName.equalsIgnoreCase("ANIMACION.AN0") &&
!_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0") &&
!_fileName.equalsIgnoreCase("PP_MORTA.AN0") &&
- !_fileName.equalsIgnoreCase("DESPACHO_SUPER2___FONDO_PP_SUPER.AN0"))
+ !_fileName.equalsIgnoreCase("DESPACHO_SUPER2___FONDO_PP_SUPER.AN0") &&
+ !_fileName.equalsIgnoreCase("ESTOMAGO.AN0"))
error("Could not open animation %s", _fileName.c_str());
// otherwise setup a functioning but empty animation
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index e5265abf315..eaf33ebd210 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -218,8 +218,16 @@ void Room::updateInteraction() {
void Room::updateRoomBounds() {
auto background = getObjectByName("Background");
auto graphic = background == nullptr ? nullptr : background->graphic();
- if (graphic != nullptr)
- g_engine->camera().setRoomBounds(graphic->animation().imageSize(0), graphic->scale());
+ 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 but I find
+ * this very unstable. Instead a fixed value is used
+ */
+ if (bgSize == Point(0, 0))
+ bgSize = Point(1024, 768);
+ g_engine->camera().setRoomBounds(bgSize, graphic->scale());
+ }
}
void Room::updateObjects() {
Commit: 8e7069d3664801d7641de02ff7a3330bcea03159
https://github.com/scummvm/scummvm/commit/8e7069d3664801d7641de02ff7a3330bcea03159
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:51+02:00
Commit Message:
ALCACHOFA: Fix return value of kernel calls
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index e26f517638a..cba7f454c22 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -187,16 +187,35 @@ struct ScriptTask : public Task {
virtual TaskReturn run() override {
if (_isFirstExecution || _returnsFromKernelCall)
setCharacterVariables();
- if (_returnsFromKernelCall)
+ if (_returnsFromKernelCall) {
+ // this is also original done, every KernelCall is followed by a PopN of the arguments
+ // only *after* the PopN the return value is pushed so that the following script can use it
+ scumm_assert(_pc < _script._instructions.size() && _script._instructions[_pc]._op == ScriptOp::PopN);
+ popN(_script._instructions[_pc++]._arg);
pushNumber(process().returnValue());
+ }
_isFirstExecution = _returnsFromKernelCall = false;
while (true) {
if (_pc >= _script._instructions.size())
error("Script process reached instruction out-of-bounds");
const auto &instruction = _script._instructions[_pc++];
- debugC(SCRIPT_DEBUG_LVL_INSTRUCTIONS, kDebugScript, "%u: %5u %-12s %8d",
- process().pid(), _pc - 1, ScriptOpNames[(int)instruction._op], instruction._arg);
+ if (debugChannelSet(SCRIPT_DEBUG_LVL_INSTRUCTIONS, kDebugScript)) {
+ debugN("%u: %5u %-12s %8d Stack: ",
+ process().pid(), _pc - 1, ScriptOpNames[(int)instruction._op], instruction._arg);
+ if (_stack.empty())
+ debug("empty");
+ else {
+ const auto& top = _stack.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;
+ }
+ }
+ }
switch (instruction._op) {
case ScriptOp::Nop: break;
@@ -215,10 +234,7 @@ struct ScriptTask : public Task {
pushNumber(popVariable());
break;
case ScriptOp::PopN:
- if (instruction._arg < 0 || (uint)instruction._arg > _stack.size())
- error("Script tried to pop more entries than are available on the stack");
- for (int32 i = 0; i < instruction._arg; i++)
- _stack.pop();
+ popN(instruction._arg);
break;
case ScriptOp::Store: {
int32 value = popNumber();
@@ -383,6 +399,13 @@ private:
return entry._index;
}
+ void popN(int32 count) {
+ if (count < 0 || (uint)count > _stack.size())
+ error("Script tried to pop more entries than are available on the stack");
+ for (int32 i = 0; i < count; i++)
+ _stack.pop();
+ }
+
StackEntry getArg(uint argI) {
if (_stack.size() < argI + 1)
error("Script did not supply enough arguments for kernel call");
Commit: 171e0af2afb165346c17fa91925ae358cb8e0857
https://github.com/scummvm/scummvm/commit/171e0af2afb165346c17fa91925ae358cb8e0857
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix ScriptTimerTask
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index cba7f454c22..bb8ae236818 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -125,12 +125,16 @@ struct ScriptTimerTask : public Task {
virtual TaskReturn run() override {
TASK_BEGIN;
- if (_durationSec >= (int32)((g_system->getMillis() - g_engine->script()._scriptTimer) / 1000) &&
- g_engine->script().variable("SeHaPulsadoRaton"))
- _result = 0;
-
- // TODO: Add network behavior for script timer
+ {
+ uint32 timeSinceTimer = g_engine->script()._scriptTimer == 0 ? 0
+ : (g_system->getMillis() - g_engine->script()._scriptTimer) / 1000;
+ if (_durationSec >= (int32)timeSinceTimer)
+ _result = g_engine->script().variable("SeHaPulsadoRaton") ? 0 : 2;
+ else
+ _result = 1;
+ }
TASK_YIELD;
+ TASK_RETURN(_result);
TASK_END;
}
Commit: f8e15ea9f43b0fde6d6a3273a1ed9f213f5ad79a
https://github.com/scummvm/scummvm/commit/f8e15ea9f43b0fde6d6a3273a1ed9f213f5ad79a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Clear screen in order to fix ESTOMAGO
Changed paths:
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 7b1682656eb..f131bdcca15 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -136,7 +136,8 @@ public:
_currentTexture = nullptr;
_currentBlendMode = (BlendMode)-1;
- // Do not clear the screen as the engine sometimes relies on the old frame to be reused
+ GL_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+ GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
}
virtual void end() override {
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index bb8ae236818..1a7b8b80e9b 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -133,7 +133,7 @@ struct ScriptTimerTask : public Task {
else
_result = 1;
}
- TASK_YIELD;
+ TASK_YIELD; // Wait a frame to not produce an endless loop
TASK_RETURN(_result);
TASK_END;
}
Commit: 9bf99c3f6aed9b67752a50c3109e68bbeebc79e8
https://github.com/scummvm/scummvm/commit/9bf99c3f6aed9b67752a50c3109e68bbeebc79e8
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix text line array being too small
Changed paths:
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 78749514ce2..3795d4b486f 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -371,7 +371,7 @@ public:
virtual void draw() override;
private:
- static constexpr uint kMaxLines = 8;
+ static constexpr uint kMaxLines = 12;
using TextLine = Common::Span<const byte>; ///< byte to convert 128+ characters to image indices
Font &_font;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index eaf33ebd210..95ebec3eead 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -650,7 +650,7 @@ static void loadEncryptedFile(const char *path, Array<char> &output) {
File file;
if (!file.open(path))
error("Could not open text file %s", path);
- output.resize(file.size() - 5 + 1);
+ 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();
@@ -659,7 +659,7 @@ static void loadEncryptedFile(const char *path, Array<char> &output) {
error("Could not read text file body");
for (auto &ch : output)
ch ^= key;
- output.back() = ' '; // one for good measure and a zero-terminator
+ output.back() = '\0'; // one for good measure and a zero-terminator
}
static char *trimTrailing(char *start, char *end) {
Commit: 178faf9b47e3d6a51c1258331c4cb086ec56129c
https://github.com/scummvm/scummvm/commit/178faf9b47e3d6a51c1258331c4cb086ec56129c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: More exceptions to make game completable
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/player.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index c458aad26bf..cf027ebf9a3 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -193,7 +193,8 @@ void AnimationBase::loadMissingAnimation() {
!_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0") &&
!_fileName.equalsIgnoreCase("PP_MORTA.AN0") &&
!_fileName.equalsIgnoreCase("DESPACHO_SUPER2___FONDO_PP_SUPER.AN0") &&
- !_fileName.equalsIgnoreCase("ESTOMAGO.AN0"))
+ !_fileName.equalsIgnoreCase("ESTOMAGO.AN0") &&
+ !_fileName.equalsIgnoreCase("CREDITOS.AN0"))
error("Could not open animation %s", _fileName.c_str());
// otherwise setup a functioning but empty animation
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 4f822a5fcf9..c75defede95 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -61,13 +61,13 @@ void Player::updateCursor() {
else {
auto type = _selectedObject->cursorType();
switch (type) {
- case CursorType::Point: _cursorFrameI = 0; break;
case CursorType::LeaveUp: _cursorFrameI = 8; break;
case CursorType::LeaveRight: _cursorFrameI = 10; break;
case CursorType::LeaveDown: _cursorFrameI = 12; break;
case CursorType::LeaveLeft: _cursorFrameI = 14; break;
case CursorType::WalkTo: _cursorFrameI = 6; break;
- default: error("Invalid cursor type %u", (uint)type); break;
+ case CursorType::Point:
+ default: _cursorFrameI = 0; break;
}
if (_cursorFrameI != 0) {
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 4eb45275469..dbc583f3ed2 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -126,7 +126,7 @@ static AudioStream *openAudio(const String &fileName) {
delete file;
// Ignore the known, original wrong filenames given, report the rest
- if (fileName == "CHAS")
+ if (fileName == "CHAS" || fileName == "517")
return nullptr;
error("Could not open audio file: %s", fileName.c_str());
}
Commit: 23f316821e33dfd664f73a6c71073ad6f0ea7214
https://github.com/scummvm/scummvm/commit/23f316821e33dfd664f73a6c71073ad6f0ea7214
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix out-of-bounds heap access
Changed paths:
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 95ebec3eead..59c804206f3 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -670,7 +670,7 @@ static char *trimTrailing(char *start, char *end) {
void World::loadLocalizedNames() {
loadEncryptedFile("Textos/OBJETOS.nkr", _namesChunk);
- char *lineStart = _namesChunk.begin(), *fileEnd = _namesChunk.end();
+ char *lineStart = _namesChunk.begin(), *fileEnd = _namesChunk.end() - 1;
while (lineStart < fileEnd) {
char *lineEnd = find(lineStart, fileEnd, '\n');
char *keyEnd = find(lineStart, lineEnd, '#');
Commit: e9c1594ea2af867f55c3abfe822e38df2b4ab3a9
https://github.com/scummvm/scummvm/commit/e9c1594ea2af867f55c3abfe822e38df2b4ab3a9
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix case of input.cpp and input.h
Changed paths:
A engines/alcachofa/input.cpp
A engines/alcachofa/input.h
R engines/alcachofa/Input.cpp
R engines/alcachofa/Input.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/module.mk
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 682484c2efe..89c0daf9c0c 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -70,8 +70,6 @@ Common::Error AlcachofaEngine::run() {
_player.reset(new Player());
_globalUI.reset(new GlobalUI());
- //_script->createProcess(MainCharacterKind::None, "Inicializar_Variables");
- //_player->changeRoom("HORCA", true);
_script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
_scheduler.run();
diff --git a/engines/alcachofa/Input.cpp b/engines/alcachofa/input.cpp
similarity index 100%
rename from engines/alcachofa/Input.cpp
rename to engines/alcachofa/input.cpp
diff --git a/engines/alcachofa/Input.h b/engines/alcachofa/input.h
similarity index 100%
rename from engines/alcachofa/Input.h
rename to engines/alcachofa/input.h
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 9c8300c66ea..71e37f99a1a 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -10,7 +10,7 @@ MODULE_OBJS = \
global-ui.cpp \
graphics.cpp \
graphics-opengl.cpp \
- Input.cpp \
+ input.cpp \
metaengine.o \
player.cpp \
rooms.cpp \
Commit: e2c661a4076811b132f52e46dffc4ab122375dd5
https://github.com/scummvm/scummvm/commit/e2c661a4076811b132f52e46dffc4ab122375dd5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix texture wrapping
Changed paths:
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index f131bdcca15..1e32bb7a581 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -76,8 +76,7 @@ public:
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));
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
+ setMirrorWrap(false);
}
virtual ~OpenGLTexture() override {
@@ -99,11 +98,26 @@ public:
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
}
+ void setMirrorWrap(bool wrap) {
+ if (_mirrorWrap == wrap)
+ return;
+ _mirrorWrap = wrap;
+ GLint wrapMode;
+ if (wrap)
+ wrapMode = OpenGLContext.textureMirrorRepeatSupported ? GL_MIRRORED_REPEAT : GL_REPEAT;
+ else
+ wrapMode = OpenGLContext.textureEdgeClampSupported ? GL_CLAMP_TO_EDGE : GL_CLAMP;
+
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode));
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode));
+ }
+
inline GLuint handle() const { return _handle; }
private:
GLuint _handle;
bool _withMipmaps;
+ bool _mirrorWrap = true;
};
class OpenGLRenderer : public IDebugRenderer {
@@ -118,6 +132,10 @@ public:
GL_CALL(glDisable(GL_CULL_FACE));
GL_CALL(glEnable(GL_BLEND));
GL_CALL(glDepthMask(GL_FALSE));
+
+ if (!OpenGLContext.NPOTSupported || !OpenGLContext.textureMirrorRepeatSupported) {
+ g_system->messageBox(LogMessageType::kWarning, "Old OpenGL detected, some graphical errors will occur.");
+ }
}
virtual ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override {
@@ -145,23 +163,24 @@ public:
g_system->updateScreen();
}
- virtual void setTexture(const ITexture *texture) override {
+ virtual void setTexture(ITexture *texture) override {
if (texture == _currentTexture)
return;
else if (texture == nullptr) {
GL_CALL(glDisable(GL_TEXTURE_2D));
GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
+ _currentTexture = nullptr;
}
else {
if (_currentTexture == nullptr) {
GL_CALL(glEnable(GL_TEXTURE_2D));
GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
}
- auto glTexture = dynamic_cast<const OpenGLTexture *>(texture);
+ auto glTexture = dynamic_cast<OpenGLTexture *>(texture);
assert(glTexture != nullptr);
GL_CALL(glBindTexture(GL_TEXTURE_2D, glTexture->handle()));
+ _currentTexture = glTexture;
}
- _currentTexture = texture;
}
virtual void setBlendMode(BlendMode blendMode) override {
@@ -251,6 +270,10 @@ public:
{ texMax.getX(), texMax.getY() },
{ texMax.getX(), texMin.getY() }
};
+ if (_currentTexture != nullptr) {
+ // float equality is fine here, if it was calculated it was not a normal graphic
+ _currentTexture->setMirrorWrap(texMin != Vector2d() || texMax != Vector2d(1, 1));
+ }
float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
@@ -333,7 +356,7 @@ private:
}
Point _resolution;
- const ITexture *_currentTexture = nullptr;
+ OpenGLTexture *_currentTexture = nullptr;
BlendMode _currentBlendMode = (BlendMode)-1;
float _currentLodBias = 0.0f;
};
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 3795d4b486f..d7abaabf463 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -78,7 +78,7 @@ public:
virtual Common::ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps = true) = 0;
virtual void begin() = 0;
- virtual void setTexture(const ITexture *texture) = 0;
+ virtual void setTexture(ITexture *texture) = 0;
virtual void setBlendMode(BlendMode blendMode) = 0;
virtual void setLodBias(float lodBias) = 0;
virtual void quad(
Commit: 6644e4abde59921112ef824db9a1377a629e879c
https://github.com/scummvm/scummvm/commit/6644e4abde59921112ef824db9a1377a629e879c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix tiling on effect objects
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index cf027ebf9a3..8939a385840 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -377,20 +377,17 @@ void Animation::draw3D(int32 frameI, Vector3d center, float scale, BlendMode ble
renderer.quad(as2D(center), size, color, rotation, texMin, texMax);
}
-void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d tiling, Vector2d texOffset, BlendMode blendMode) {
+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(tiling.getX() / _renderedSurface.w, tiling.getY() / _renderedSurface.h);
+ Vector2d texMax((float)bounds.width() / _renderedSurface.w, (float)bounds.height() / _renderedSurface.h);
topLeft += as3D(totalFrameOffset(frameI));
topLeft = g_engine->camera().transform3Dto2D(topLeft);
const auto rotation = -g_engine->camera().rotation();
- Vector2d size(bounds.width(), bounds.height());
- size *= topLeft.z();
-
- if (abs(tiling.getX()) > epsilon)
- size = size * texMax;
+ size(0, 0) *= bounds.width() * topLeft.z() / _renderedSurface.w;
+ size(1, 0) *= bounds.height() * topLeft.z() / _renderedSurface.h;
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
@@ -614,14 +611,14 @@ SpecialEffectDrawRequest::SpecialEffectDrawRequest(Graphic &graphic, Point topLe
, _animation(&graphic.animation())
, _frameI(graphic._frameI)
, _topLeft(topLeft.x, topLeft.y, graphic._scale)
- , _tiling(bottomRight.x - topLeft.x, bottomRight.y - topLeft.y)
+ , _size(bottomRight.x - topLeft.x, bottomRight.y - topLeft.y)
, _texOffset(texOffset)
, _blendMode(blendMode) {
assert(_frameI >= 0 && (uint)_frameI < _animation->frameCount());
}
void SpecialEffectDrawRequest::draw() {
- _animation->drawEffect(_frameI, _topLeft, _tiling, _texOffset, _blendMode);
+ _animation->drawEffect(_frameI, _topLeft, _size, _texOffset, _blendMode);
}
static const byte *trimLeading(const byte *text, const byte *end) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index d7abaabf463..ad5dff1ebca 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -351,7 +351,7 @@ private:
int32 _frameI;
Math::Vector3d _topLeft;
Math::Vector2d
- _tiling,
+ _size,
_texOffset;
BlendMode _blendMode;
};
Commit: 6a7bd63da84e885f7256cf70b79a70833c2b9f48
https://github.com/scummvm/scummvm/commit/6a7bd63da84e885f7256cf70b79a70833c2b9f48
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix parameter name "center" to "topLeft"
Changed paths:
engines/alcachofa/console.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 236e842fdc9..da464885754 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -57,9 +57,9 @@ private:
bool cmdDebugMode(int argc, const char **args);
bool cmdTeleport(int argc, const char **args);
- bool _showInteractables = true;
- bool _showCharacters = true;
- bool _showFloor = true;
+ bool _showInteractables = false;
+ bool _showCharacters = false;
+ bool _showFloor = false;
bool _showFloorEdges = false;
bool _showFloorColor = false;
};
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index a5028b8387f..e7cc03bdf8e 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -171,7 +171,7 @@ void Character::update() {
Graphic *animateGraphic = graphicOf(_curAnimateObject);
if (animateGraphic != nullptr) {
- animateGraphic->center() = Point(0, 0);
+ animateGraphic->topLeft() = Point(0, 0);
animateGraphic->update();
}
else if (_isTalking)
@@ -477,13 +477,13 @@ void WalkingCharacter::update() {
_currentPos = _sourcePos;
}
- _graphicNormal.center() = _graphicTalking.center() = _currentPos;
+ _graphicNormal.topLeft() = _graphicTalking.topLeft() = _currentPos;
auto animateGraphic = graphicOf(_curAnimateObject);
auto talkingGraphic = graphicOf(_curTalkingObject);
if (animateGraphic != nullptr)
- animateGraphic->center() = _currentPos;
+ animateGraphic->topLeft() = _currentPos;
if (talkingGraphic != nullptr)
- talkingGraphic->center() = _currentPos;
+ talkingGraphic->topLeft() = _currentPos;
if (room() != &g_engine->world().globalRoom()) {
float depth = room()->depthAt(_currentPos);
int8 order = room()->orderAt(_currentPos);
@@ -566,7 +566,7 @@ void WalkingCharacter::updateWalking() {
_direction = _endWalkingDirection;
onArrived();
}
- _graphicNormal.center() = _currentPos;
+ _graphicNormal.topLeft() = _currentPos;
}
void WalkingCharacter::updateWalkingAnimation()
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 7a4fb5c17dd..77715e86c98 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -182,7 +182,7 @@ void SpecialEffectObject::draw() {
: BlendMode::AdditiveAlpha;
Point topLeft = _topLeft, bottomRight = _bottomRight;
if (topLeft.x == bottomRight.x || topLeft.y == bottomRight.y) {
- topLeft = _graphic.center();
+ topLeft = _graphic.topLeft();
bottomRight = topLeft + _graphic.animation().imageSize(0);
}
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 3a9fb3005be..f72a6d59c9a 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -166,13 +166,13 @@ void GlobalUI::drawChangingButton() {
_changeButton.lastTime() = 42 * (anim->frameCount() - 1) + 1;
}
- _changeButton.center() = { (int16)(g_system->getWidth() + 2), -2 };
+ _changeButton.topLeft() = { (int16)(g_system->getWidth() + 2), -2 };
if (isHoveringChangeButton() &&
g_engine->input().isMouseLeftDown() &&
player.pressedObject() == &_changeButton)
{
- _changeButton.center().x -= 2;
- _changeButton.center().y += 2;
+ _changeButton.topLeft().x -= 2;
+ _changeButton.topLeft().y += 2;
}
_changeButton.order() = -9;
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 1e32bb7a581..f341c49f42a 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -250,13 +250,11 @@ public:
Angle rotation,
Vector2d texMin,
Vector2d texMax) override {
- size *= 0.5f;
- center += size;
Vector2d positions[] = {
- center + Vector2d(-size.getX(), -size.getY()),
- center + Vector2d(-size.getX(), +size.getY()),
+ center + Vector2d(0, 0),
+ center + Vector2d(0, +size.getY()),
center + Vector2d(+size.getX(), +size.getY()),
- center + Vector2d(+size.getX(), -size.getY()),
+ center + Vector2d(+size.getX(), 0),
};
if (abs(rotation.getDegrees()) > epsilon) {
const Vector2d zero(0, 0);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 8939a385840..e53759fefe8 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -343,38 +343,38 @@ void Animation::prerenderFrame(int32 frameI) {
_renderedFrameI = frameI;
}
-void Animation::draw2D(int32 frameI, Vector2d center, float scale, BlendMode blendMode, Color color) {
+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);
Vector2d size(bounds.width(), bounds.height());
- center += as2D(totalFrameOffset(frameI)) * scale;
+ topLeft += as2D(totalFrameOffset(frameI)) * scale;
size *= scale;
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
renderer.setBlendMode(blendMode);
- renderer.quad(center, size, color, Angle(), texMin, texMax);
+ renderer.quad(topLeft, size, color, Angle(), texMin, texMax);
}
-void Animation::draw3D(int32 frameI, Vector3d center, float scale, BlendMode blendMode, Color color) {
+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);
- center += as3D(totalFrameOffset(frameI)) * scale;
- center = g_engine->camera().transform3Dto2D(center);
+ topLeft += as3D(totalFrameOffset(frameI)) * scale;
+ topLeft = g_engine->camera().transform3Dto2D(topLeft);
const auto rotation = -g_engine->camera().rotation();
Vector2d size(bounds.width(), bounds.height());
- size *= scale * center.z();
+ size *= scale * topLeft.z();
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
renderer.setBlendMode(blendMode);
- renderer.quad(as2D(center), size, color, rotation, texMin, texMax);
+ renderer.quad(as2D(topLeft), size, color, rotation, texMin, texMax);
}
void Animation::drawEffect(int32 frameI, Vector3d topLeft, Vector2d size, Vector2d texOffset, BlendMode blendMode) {
@@ -473,8 +473,8 @@ Graphic::Graphic() {
}
Graphic::Graphic(ReadStream &stream) {
- _center.x = stream.readSint16LE();
- _center.y = stream.readSint16LE();
+ _topLeft.x = stream.readSint16LE();
+ _topLeft.y = stream.readSint16LE();
_scale = stream.readSint16LE();
_order = stream.readSByte();
auto animationName = readVarString(stream);
@@ -484,7 +484,7 @@ Graphic::Graphic(ReadStream &stream) {
Graphic::Graphic(const Graphic &other)
: _animation(other._animation)
- , _center(other._center)
+ , _topLeft(other._topLeft)
, _scale(other._scale)
, _order(other._order)
, _color(other._color)
@@ -556,7 +556,7 @@ void Graphic::setAnimation(Animation *animation) {
}
void Graphic::serializeSave(Serializer &serializer) {
- syncPoint(serializer, _center);
+ syncPoint(serializer, _topLeft);
serializer.syncAsSint16LE(_scale);
serializer.syncAsUint32LE(_lastTime);
serializer.syncAsByte(_isPaused);
@@ -577,7 +577,7 @@ AnimationDrawRequest::AnimationDrawRequest(Graphic &graphic, bool is3D, BlendMod
, _is3D(is3D)
, _animation(&graphic.animation())
, _frameI(graphic._frameI)
- , _center(graphic._center.x, graphic._center.y, graphic._scale)
+ , _topLeft(graphic._topLeft.x, graphic._topLeft.y, graphic._scale)
, _scale(graphic._scale * graphic._depthScale)
, _color(graphic.color())
, _blendMode(blendMode)
@@ -590,7 +590,7 @@ AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, V
, _is3D(false)
, _animation(animation)
, _frameI(frameI)
- , _center(as3D(center))
+ , _topLeft(as3D(center))
, _scale(kBaseScale)
, _color(kWhite)
, _blendMode(BlendMode::AdditiveAlpha)
@@ -601,9 +601,9 @@ AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, V
void AnimationDrawRequest::draw() {
if (_is3D)
- _animation->draw3D(_frameI, _center, _scale * kInvBaseScale, _blendMode, _color);
+ _animation->draw3D(_frameI, _topLeft, _scale * kInvBaseScale, _blendMode, _color);
else
- _animation->draw2D(_frameI, as2D(_center), _scale * kInvBaseScale, _blendMode, _color);
+ _animation->draw2D(_frameI, as2D(_topLeft), _scale * kInvBaseScale, _blendMode, _color);
}
SpecialEffectDrawRequest::SpecialEffectDrawRequest(Graphic &graphic, Point topLeft, Point bottomRight, Vector2d texOffset, BlendMode blendMode)
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index ad5dff1ebca..8deecb306f5 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -251,7 +251,7 @@ public:
Graphic(Common::ReadStream &stream);
Graphic(const Graphic &other); // animation reference is taken, so keep other alive
- inline Common::Point ¢er() { return _center; }
+ inline Common::Point &topLeft() { return _topLeft; }
inline int8 &order() { return _order; }
inline int16 &scale() { return _scale; }
inline float &depthScale() { return _depthScale; }
@@ -284,7 +284,7 @@ private:
friend class SpecialEffectDrawRequest;
Common::ScopedPtr<Animation> _ownedAnimation;
Animation *_animation = nullptr;
- Common::Point _center;
+ Common::Point _topLeft;
int16 _scale = kBaseScale;
int8 _order = 0;
Color _color = kWhite;
@@ -328,7 +328,7 @@ private:
bool _is3D;
Animation *_animation;
int32 _frameI;
- Math::Vector3d _center;
+ Math::Vector3d _topLeft;
float _scale;
Color _color;
BlendMode _blendMode;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 59c804206f3..ba8cd2da7dd 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -385,7 +385,7 @@ Item *Inventory::getHoveredItem() {
assert(graphic != nullptr);
auto bounds = graphic->animation().frameBounds(0);
auto totalOffset = graphic->animation().totalFrameOffset(0);
- auto delta = mousePos - graphic->center() - totalOffset;
+ auto delta = mousePos - graphic->topLeft() - totalOffset;
if (delta.x >= 0 && delta.y >= 0 && delta.x <= bounds.width() && delta.y <= bounds.height())
return item;
}
@@ -418,14 +418,14 @@ void Inventory::drawAsOverlay(int32 scrollY) {
if (graphic == nullptr)
continue;
- int16 oldY = graphic->center().y;
+ int16 oldY = graphic->topLeft().y;
int8 oldOrder = graphic->order();
- graphic->center().y += scrollY;
+ graphic->topLeft().y += scrollY;
graphic->order() = -kForegroundOrderCount;
if (object->name().equalsIgnoreCase("Background"))
graphic->order()++;
object->draw();
- graphic->center().y = oldY;
+ graphic->topLeft().y = oldY;
graphic->order() = oldOrder;
}
}
Commit: d598530b45f57d2db39b050cdae2715d57b39c68
https://github.com/scummvm/scummvm/commit/d598530b45f57d2db39b050cdae2715d57b39c68
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix black frames on opening/closing inventory
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 89c0daf9c0c..f9a036d1a84 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -88,6 +88,8 @@ Common::Error AlcachofaEngine::run() {
_camera.shake() = Vector2d();
_player->preUpdate();
_player->currentRoom()->update();
+ if (_player->currentRoom() != nullptr)
+ _player->currentRoom()->draw();
_player->postUpdate();
if (_debugHandler != nullptr)
_debugHandler->update();
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index f72a6d59c9a..429a03db932 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -61,6 +61,7 @@ void GlobalUI::startClosingInventory() {
_isOpeningInventory = false;
_isClosingInventory = true;
_timeForInventory = g_system->getMillis();
+ updateClosingInventory(); // prevents the first frame of closing to not render the inventory overlay
}
void GlobalUI::updateClosingInventory() {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index ba8cd2da7dd..11122265235 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -129,33 +129,32 @@ ObjectBase *Room::getObjectByName(const Common::String &name) const {
}
void Room::update() {
- if (!g_engine->isDebugModeActive())
- {
- updateScripts();
-
- if (g_engine->player().currentRoom() == this) {
- updateRoomBounds();
- g_engine->globalUI().updateClosingInventory();
- if (!updateInput())
- return;
- }
- if (!g_engine->player().isOptionsMenuOpen() &&
- g_engine->player().currentRoom() != &g_engine->world().inventory())
- world().globalRoom().updateObjects();
- if (g_engine->player().currentRoom() == this)
- updateObjects();
- }
+ if (g_engine->isDebugModeActive())
+ return;
+ updateScripts();
if (g_engine->player().currentRoom() == this) {
- g_engine->camera().update();
- drawObjects();
- world().globalRoom().drawObjects();
- // TODO: Draw black borders
- g_engine->player().drawScreenStates();
- g_engine->drawQueue().draw();
- drawDebug();
- world().globalRoom().drawDebug();
+ updateRoomBounds();
+ g_engine->globalUI().updateClosingInventory();
+ if (!updateInput())
+ return;
}
+ if (!g_engine->player().isOptionsMenuOpen() &&
+ g_engine->player().currentRoom() != &g_engine->world().inventory())
+ world().globalRoom().updateObjects();
+ if (g_engine->player().currentRoom() == this)
+ updateObjects();
+}
+
+void Room::draw() {
+ g_engine->camera().update();
+ drawObjects();
+ world().globalRoom().drawObjects();
+ // TODO: Draw black borders
+ g_engine->player().drawScreenStates();
+ g_engine->drawQueue().draw();
+ drawDebug();
+ world().globalRoom().drawDebug();
}
void Room::updateScripts() {
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 6a971bea562..6d91c797c2b 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -54,6 +54,7 @@ public:
inline ObjectIterator endObjects() const { return _objects.end(); }
void update();
+ void draw();
virtual bool updateInput();
virtual void loadResources();
virtual void freeResources();
Commit: 7e7163768fc04468e253b047b93895d990c8ae1e
https://github.com/scummvm/scummvm/commit/7e7163768fc04468e253b047b93895d990c8ae1e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Update 3D mouse pos always
Changed paths:
engines/alcachofa/input.cpp
engines/alcachofa/input.h
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index 6f7e38aa7d6..0f72e747fc8 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -34,6 +34,7 @@ void Input::nextFrame() {
_wasMouseRightPressed = false;
_wasMouseLeftReleased = false;
_wasMouseRightReleased = false;
+ updateMousePos3D(); // camera transformation might have changed
}
bool Input::handleEvent(const Common::Event &event) {
@@ -64,8 +65,7 @@ bool Input::handleEvent(const Common::Event &event) {
return true;
case EVENT_MOUSEMOVE: {
_mousePos2D = event.mouse;
- auto pos3D = g_engine->camera().transform2Dto3D({ (float)_mousePos2D.x, (float)_mousePos2D.y, kBaseScale });
- _mousePos3D = { (int16)pos3D.x(), (int16)pos3D.y() };
+ updateMousePos3D();
return true;
}
default:
@@ -84,4 +84,9 @@ void Input::toggleDebugInput(bool debugMode) {
_debugInput.reset(new Input());
}
+void Input::updateMousePos3D() {
+ auto pos3D = g_engine->camera().transform2Dto3D({ (float)_mousePos2D.x, (float)_mousePos2D.y, kBaseScale });
+ _mousePos3D = { (int16)pos3D.x(), (int16)pos3D.y() };
+}
+
}
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index 9f1f6a5e31c..24af6e2ad4b 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -47,6 +47,8 @@ public:
void toggleDebugInput(bool debugMode); ///< Toggles input debug mode which blocks any input not retrieved with debugInput
private:
+ void updateMousePos3D();
+
bool
_wasMouseLeftPressed = false,
_wasMouseRightPressed = false,
Commit: d72e14505c9937a775e153b21c99900b6a325b07
https://github.com/scummvm/scummvm/commit/d72e14505c9937a775e153b21c99900b6a325b07
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:52+02:00
Commit Message:
ALCACHOFA: Fix exception for BACTERIO/PULSAR
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 1a7b8b80e9b..dde24d038c1 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -691,8 +691,11 @@ private:
Character *_character = strcmp(characterName, "AMBOS") == 0
? &relatedCharacter()
: getObjectArg<Character>(0);
- if (_character == nullptr)
+ if (_character == nullptr) {
+ if (strcmp(characterName, "OFELIA") == 0 && dialogId == 3737)
+ return TaskReturn::finish(1);
error("Invalid character for sayText: %s", characterName);
+ }
return TaskReturn::waitFor(_character->sayText(process(), dialogId));
};
case ScriptKernelTask::SetDialogLineReturn:
Commit: bbfcb8f7f5a2d9239797bf035906ef9e8dc1c4e9
https://github.com/scummvm/scummvm/commit/bbfcb8f7f5a2d9239797bf035906ef9e8dc1c4e9
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Fix lens flares outside the western town
Changed paths:
engines/alcachofa/general-objects.cpp
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 77715e86c98..f1e811f2dcd 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -105,7 +105,7 @@ void GraphicObject::draw() {
if (!isEnabled() || !_graphic.hasAnimation())
return;
const BlendMode blendMode = _type == GraphicObjectType::Effect
- ? BlendMode::Alpha
+ ? BlendMode::Additive
: BlendMode::AdditiveAlpha;
const bool is3D = room() != &g_engine->world().inventory();
_graphic.update();
Commit: d1c56de46033dccb558dc280d001c61f3b1e6354
https://github.com/scummvm/scummvm/commit/d1c56de46033dccb558dc280d001c61f3b1e6354
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Add main character evasion
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index e7cc03bdf8e..92ec03c29a2 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -791,17 +791,43 @@ void MainCharacter::onArrived() {
}
void MainCharacter::walkTo(
- const Point &target, Direction endDirection,
+ const Point &target_, Direction endDirection,
ITriggerableObject *activateObject, const char *activateAction) {
_activateObject = activateObject;
_activateAction = activateAction;
-
- // TODO: Add collision avoidance
+ Point target = target_;
+
+ Point evadeTarget = target;
+ const PathFindingShape *activeFloor = room()->activeFloor();
+ if (activeFloor != nullptr && activeFloor->findPath(_currentPos, target, _pathPoints))
+ evadeTarget = _pathPoints[0];
+
+ MainCharacter *otherCharacter = &g_engine->world().getOtherMainCharacterByKind(_kind);
+ Point otherTarget = otherCharacter->_currentPos;
+ if (otherCharacter->isWalking() && !otherCharacter->_pathPoints.empty())
+ otherTarget = otherCharacter->_pathPoints[0];
+
+ const float activeDepthScale = g_engine->player().activeCharacter()->_graphicNormal.depthScale();
+ const float avoidanceDistSqr = pow(75 * activeDepthScale, 2);
+ const bool willIBeBusy =
+ _activateObject != nullptr &&
+ strcmp(_activateAction, "MIRAR") != 0 &&
+ otherCharacter->currentlyUsing() != dynamic_cast<ObjectBase *>(_activateObject);
+
+ if (otherCharacter->room() == room() && evadeTarget.sqrDist(otherTarget) <= avoidanceDistSqr) {
+ if (!otherCharacter->isBusy()) {
+ if (activeFloor != nullptr && activeFloor->findEvadeTarget(evadeTarget, activeDepthScale, avoidanceDistSqr, evadeTarget))
+ otherCharacter->WalkingCharacter::walkTo(evadeTarget);
+ }
+ else if (!willIBeBusy) {
+ if (activeFloor != nullptr)
+ activeFloor->findEvadeTarget(evadeTarget, activeDepthScale, avoidanceDistSqr, target);
+ }
+ }
WalkingCharacter::walkTo(target, endDirection, activateObject, activateAction);
- if (this == g_engine->player().activeCharacter()) {
+ if (this == g_engine->player().activeCharacter())
g_engine->camera().setFollow(this);
- }
}
void MainCharacter::draw() {
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 9fc5f7e670d..c1c0f43574a 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -605,6 +605,29 @@ void PathFindingShape::floydWarshallPath(
path.push(_linkPoints[fromLink]);
}
+bool PathFindingShape::findEvadeTarget(
+ Point centerTarget,
+ float depthScale, float minDistSqr,
+ Point &evadeTarget) const {
+ // TODO: Check if minDistSqr should just modify tryDistBase
+
+ for (float tryDistBase = 60; tryDistBase < 250; tryDistBase += 10) {
+ for (int tryAngleI = 0; tryAngleI < 6; tryAngleI++) {
+ const float tryAngle = tryAngleI / 3.0f * M_PI + deg2rad(30.0f);
+ const float tryDist = tryDistBase * depthScale;
+ const Point tryPos = evadeTarget + Point(
+ (int16)(cosf(tryAngle) * tryDist),
+ (int16)(sinf(tryAngle) * tryDist));
+
+ if (contains(tryPos) && tryPos.sqrDist(centerTarget) > minDistSqr) {
+ evadeTarget = tryPos;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
FloorColorShape::FloorColorShape() {}
FloorColorShape::FloorColorShape(ReadStream &stream) {
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index b42239edce4..cb53495864e 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -173,6 +173,11 @@ public:
const Common::Point &to,
Common::Stack<Common::Point> &path) const;
int32 edgeTarget(uint polygonI, uint pointI) const;
+ bool findEvadeTarget(
+ Common::Point centerTarget,
+ float depthScale,
+ float minDistSqr,
+ Common::Point& evadeTarget) const;
private:
using LinkIndex = Common::Pair<int32, int32>;
Commit: cd0dcde22c46f0093728806620ac80ff4a188f9a
https://github.com/scummvm/scummvm/commit/cd0dcde22c46f0093728806620ac80ff4a188f9a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Add ShowCenterBottomText kernel call
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 429a03db932..a103461e91e 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -181,4 +181,49 @@ void GlobalUI::drawChangingButton() {
g_engine->drawQueue().add<AnimationDrawRequest>(_changeButton, false, BlendMode::AdditiveAlpha);
}
+struct CenterBottomTextTask : public Task {
+ CenterBottomTextTask(Process &process, int32 dialogId, uint32 durationMs)
+ : Task(process)
+ , _dialogId(dialogId)
+ , _durationMs(durationMs) {
+ }
+
+ TaskReturn run() override
+ {
+ Font &font = g_engine->globalUI().dialogFont();
+ const char *text = g_engine->world().getDialogLine(_dialogId);
+ const Point pos(
+ g_system->getWidth() / 2,
+ g_system->getHeight() - 200
+ );
+
+ TASK_BEGIN;
+ _startTime = g_system->getMillis();
+ while (g_system->getMillis() - _startTime < _durationMs) {
+ if (process().isActiveForPlayer()) {
+ g_engine->drawQueue().add<TextDrawRequest>(
+ font, text, pos, -1, true, kWhite, 1);
+ }
+ TASK_YIELD;
+ }
+ TASK_END;
+ }
+
+ void debugPrint() override
+ {
+ uint32 remaining = g_system->getMillis() - _startTime <= _durationMs
+ ? _durationMs - (g_system->getMillis() - _startTime)
+ : 0;
+ g_engine->console().debugPrintf("CenterBottomText (%d) with %ums remaining\n", _dialogId, remaining);
+ }
+
+private:
+ int32 _dialogId;
+ uint32 _startTime = 0, _durationMs;
+};
+
+Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs) {
+ return new CenterBottomTextTask(process, dialogId, durationMs);
+}
+
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index d07dbf86f8d..e5522bc97e3 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -61,6 +61,8 @@ private:
uint32 _timeForInventory = 0;
};
+Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs);
+
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index dde24d038c1..04724635019 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -21,6 +21,7 @@
#include "script.h"
#include "rooms.h"
+#include "global-ui.h"
#include "alcachofa.h"
#include "script-debug.h"
@@ -495,8 +496,7 @@ private:
// Misc / control flow
case ScriptKernelTask::ShowCenterBottomText:
- warning("STUB KERNEL CALL: ShowCenterBottomText");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(showCenterBottomText(process(), getNumberArg(0), (uint32)getNumberArg(1)));
case ScriptKernelTask::Delay:
return getNumberArg(0) <= 0
? TaskReturn::finish(0)
Commit: 24453e1d01109829263f2bf8042b29a3ea20b246
https://github.com/scummvm/scummvm/commit/24453e1d01109829263f2bf8042b29a3ea20b246
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Reduce unnecessary string allocations
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 92ec03c29a2..93d8153449c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -106,7 +106,7 @@ void InteractableObject::trigger(const char *action) {
void InteractableObject::toggle(bool isEnabled) {
ObjectBase::toggle(isEnabled);
- ObjectBase *related = room()->getObjectByName(_relatedObject);
+ ObjectBase *related = room()->getObjectByName(_relatedObject.c_str());
if (related != nullptr)
related->toggle(isEnabled);
}
@@ -254,9 +254,9 @@ void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object)
if (name.empty())
object = nullptr;
else {
- object = room()->getObjectByName(name);
+ object = room()->getObjectByName(name.c_str());
if (object == nullptr)
- object = room()->world().getObjectByName(name);
+ object = room()->world().getObjectByName(name.c_str());
if (object == nullptr)
error("Invalid object name \"%s\" saved for \"%s\" in \"%s\"",
name.c_str(), this->name().c_str(), room()->name().c_str());
@@ -872,7 +872,7 @@ void MainCharacter::serializeSave(Serializer &serializer) {
String roomName = room()->name();
serializer.syncString(roomName);
if (serializer.isLoading()) {
- room() = room()->world().getRoomByName(roomName);
+ room() = room()->world().getRoomByName(roomName.c_str());
if (room() == nullptr)
error("Invalid room name \"%s\" saved for \"%s\"", roomName.c_str(), name().c_str());
}
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index c75defede95..ac1721b3e06 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -126,7 +126,7 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
_currentRoom->freeResources();
}
- _currentRoom = g_engine->world().getRoomByName(targetRoomName);
+ _currentRoom = g_engine->world().getRoomByName(targetRoomName.c_str());
if (_currentRoom == nullptr)
error("Invalid room name: %s", targetRoomName.c_str());
@@ -198,11 +198,11 @@ struct DoorTask : public Task {
, _sourceDoor(door)
, _character(g_engine->player().activeCharacter())
, _player(g_engine->player()) {
- _targetRoom = g_engine->world().getRoomByName(door->targetRoom());
+ _targetRoom = g_engine->world().getRoomByName(door->targetRoom().c_str());
if (_targetRoom == nullptr)
error("Invalid door target room: %s", door->targetRoom().c_str());
- _targetObject = dynamic_cast<InteractableObject *>(_targetRoom->getObjectByName(door->targetObject()));
+ _targetObject = dynamic_cast<InteractableObject *>(_targetRoom->getObjectByName(door->targetObject().c_str()));
if (_targetObject == nullptr)
error("Invalid door target door: %s", door->targetObject().c_str());
auto targetDoor = dynamic_cast<const Door *>(_targetObject);
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 11122265235..a2a3f9ea2aa 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -120,7 +120,7 @@ Room::~Room() {
delete object;
}
-ObjectBase *Room::getObjectByName(const Common::String &name) const {
+ObjectBase *Room::getObjectByName(const char *name) const {
for (auto *object : _objects) {
if (object->name().equalsIgnoreCase(name))
return object;
@@ -512,7 +512,7 @@ MainCharacter &World::getOtherMainCharacterByKind(MainCharacterKind kind) const
}
}
-Room *World::getRoomByName(const Common::String &name) const {
+Room *World::getRoomByName(const char *name) const {
for (auto *room : _rooms) {
if (room->name().equalsIgnoreCase(name))
return room;
@@ -520,7 +520,7 @@ Room *World::getRoomByName(const Common::String &name) const {
return nullptr;
}
-ObjectBase *World::getObjectByName(const Common::String &name) const {
+ObjectBase *World::getObjectByName(const char *name) const {
ObjectBase *result = nullptr;
if (result == nullptr && g_engine->player().currentRoom() != nullptr)
result = g_engine->player().currentRoom()->getObjectByName(name);
@@ -531,7 +531,7 @@ ObjectBase *World::getObjectByName(const Common::String &name) const {
return result;
}
-ObjectBase *World::getObjectByName(MainCharacterKind character, const Common::String &name) const {
+ObjectBase *World::getObjectByName(MainCharacterKind character, const char *name) const {
if (character == MainCharacterKind::None)
return getObjectByName(name);
const auto &player = g_engine->player();
@@ -547,7 +547,7 @@ ObjectBase *World::getObjectByName(MainCharacterKind character, const Common::St
return result;
}
-ObjectBase *World::getObjectByNameFromAnyRoom(const Common::String &name) const {
+ObjectBase *World::getObjectByNameFromAnyRoom(const char *name) const {
for (auto *room : _rooms) {
ObjectBase *result = room->getObjectByName(name);
if (result != nullptr)
@@ -556,12 +556,12 @@ ObjectBase *World::getObjectByNameFromAnyRoom(const Common::String &name) const
return nullptr;
}
-void World::toggleObject(MainCharacterKind character, const Common::String &objName, bool isEnabled) {
+void World::toggleObject(MainCharacterKind character, const char *objName, bool isEnabled) {
ObjectBase *object = getObjectByName(character, objName);
if (object == nullptr)
object = getObjectByNameFromAnyRoom(objName);
if (object == nullptr)
- warning("Tried to toggle unknown object: %s", objName.c_str());
+ warning("Tried to toggle unknown object: %s", objName);
// I would have liked an error for this, but original inconsistencies...
else
object->toggle(isEnabled);
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 6d91c797c2b..039360255bc 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -59,7 +59,7 @@ public:
virtual void loadResources();
virtual void freeResources();
virtual void serializeSave(Common::Serializer &serializer);
- ObjectBase *getObjectByName(const Common::String &name) const;
+ ObjectBase *getObjectByName(const char *name) const;
void toggleActiveFloor();
void debugPrint(bool withObjects) const;
@@ -163,15 +163,15 @@ public:
MainCharacter &getMainCharacterByKind(MainCharacterKind kind) const;
MainCharacter &getOtherMainCharacterByKind(MainCharacterKind kind) const;
- Room *getRoomByName(const Common::String &name) const;
- ObjectBase *getObjectByName(const Common::String &name) const;
- ObjectBase *getObjectByName(MainCharacterKind character, const Common::String &name) const;
- ObjectBase *getObjectByNameFromAnyRoom(const Common::String &name) const;
+ Room *getRoomByName(const char *name) const;
+ 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 char *getLocalizedName(const Common::String &name) const;
const char *getDialogLine(int32 dialogId) const;
- void toggleObject(MainCharacterKind character, const Common::String &objName, bool isEnabled);
+ void toggleObject(MainCharacterKind character, const char *objName, bool isEnabled);
private:
bool loadWorldFile(const char *path);
Commit: c107f98062a588479ef9f4afd3ee9e0979258406
https://github.com/scummvm/scummvm/commit/c107f98062a588479ef9f4afd3ee9e0979258406
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Code conventions - Fix indentations
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/console.cpp
engines/alcachofa/debug.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script-debug.h
engines/alcachofa/script.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index f9a036d1a84..df88a8a776a 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -44,8 +44,10 @@ namespace Alcachofa {
AlcachofaEngine *g_engine;
-AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
- _gameDescription(gameDesc), _randomSource("Alcachofa") {
+AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc)
+ : Engine(syst)
+ , _gameDescription(gameDesc)
+ , _randomSource("Alcachofa") {
g_engine = this;
}
@@ -104,7 +106,7 @@ Common::Error AlcachofaEngine::run() {
return Common::kNoError;
}
-void AlcachofaEngine::playVideo(int32 videoId) {
+void AlcachofaEngine::playVideo(int32 videoId) {
Video::MPEGPSDecoder decoder;
if (!decoder.loadFile(Common::Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1))))
error("Could not find video %d", videoId);
@@ -140,8 +142,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
decoder.stop();
}
-void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param)
-{
+void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param) {
switch (mode)
{
case DebugMode::ClosestFloorPoint:
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index d19634e4f06..84c06c4ae10 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -95,9 +95,9 @@ public:
bool hasFeature(EngineFeature f) const override {
return
- (f == kSupportsLoadingDuringRuntime) ||
- (f == kSupportsSavingDuringRuntime) ||
- (f == kSupportsReturnToLauncher);
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime) ||
+ (f == kSupportsReturnToLauncher);
};
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 5eb70636814..dc9f0ced99f 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -20,8 +20,8 @@
*/
#include "camera.h"
-#include "script.h"
#include "alcachofa.h"
+#include "script.h"
#include "common/system.h"
#include "math/vector4d.h"
@@ -57,7 +57,7 @@ void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
}
void Camera::setPosition(Vector2d v) {
- setPosition({ v.getX(), v.getY(), _cur._usedCenter.z() });
+ setPosition({v.getX(), v.getY(), _cur._usedCenter.z()});
}
void Camera::setPosition(Vector3d v) {
@@ -100,8 +100,7 @@ void Camera::setupMatricesAround(Vector3d center) {
_mat2Dto3D = matTemp * _mat2Dto3D;
}
-void minmax(Vector3d &min, Vector3d &max, Vector3d val)
-{
+void minmax(Vector3d &min, Vector3d &max, Vector3d val) {
min.set(
MIN(min.x(), val.x()),
MIN(min.y(), val.y()),
@@ -157,8 +156,8 @@ Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
}
Point Camera::transform3Dto2D(Point p3d) const {
- auto v2d = transform3Dto2D({ (float)p3d.x, (float)p3d.y, kBaseScale });
- return { (int16)v2d.x(), (int16)v2d.y() };
+ auto v2d = transform3Dto2D({(float)p3d.x, (float)p3d.y, kBaseScale});
+ return {(int16)v2d.x(), (int16)v2d.y()};
}
void Camera::update() {
@@ -172,8 +171,7 @@ void Camera::update() {
for (int i = 0; i < 4; i++)
updateFollowing(50.0f);
_catchUp = false;
- }
- else
+ } else
updateFollowing(deltaTime);
setAppliedCenter(_cur._usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
}
@@ -224,8 +222,7 @@ void Camera::updateFollowing(float deltaTime) {
_cur._usedCenter = targetCenter;
_isChanging = false;
_cur._isBraking = false;
- }
- else {
+ } else {
Vector3d deltaCenter = targetCenter - _cur._usedCenter;
deltaCenter.z() = 0.0f;
_cur._usedCenter += deltaCenter * moveDistance / distanceToTarget;
@@ -297,8 +294,9 @@ protected:
struct CamLerpPosScaleTask final : public CamLerpTask {
CamLerpPosScaleTask(Process &process,
- Vector3d targetPos, float targetScale,
- int32 duration, EasingType moveEasingType, EasingType scaleEasingType)
+ Vector3d targetPos, float targetScale,
+ int32 duration,
+ EasingType moveEasingType, EasingType scaleEasingType)
: CamLerpTask(process, duration, EasingType::Linear) // linear as we need different ones per component
, _fromPos(_camera._appliedCenter)
, _deltaPos(targetPos - _camera._appliedCenter)
@@ -352,8 +350,8 @@ private:
};
Task *Camera::lerpPos(Process &process,
- Vector2d targetPos,
- int32 duration, EasingType easingType) {
+ Vector2d targetPos,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -363,8 +361,8 @@ Task *Camera::lerpPos(Process &process,
}
Task *Camera::lerpPos(Process &process,
- Vector3d targetPos,
- int32 duration, EasingType easingType) {
+ Vector3d targetPos,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -374,8 +372,8 @@ Task *Camera::lerpPos(Process &process,
}
Task *Camera::lerpPosZ(Process &process,
- float targetPosZ,
- int32 duration, EasingType easingType) {
+ float targetPosZ,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -385,8 +383,8 @@ Task *Camera::lerpPosZ(Process &process,
}
Task *Camera::lerpScale(Process &process,
- float targetScale,
- int32 duration, EasingType easingType) {
+ float targetScale,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -395,8 +393,8 @@ Task *Camera::lerpScale(Process &process,
}
Task *Camera::lerpRotation(Process &process,
- float targetRotation,
- int32 duration, EasingType easingType) {
+ float targetRotation,
+ int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -405,8 +403,9 @@ Task *Camera::lerpRotation(Process &process,
}
Task *Camera::lerpPosScale(Process &process,
- Vector3d targetPos, float targetScale,
- int32 duration, EasingType moveEasingType, EasingType scaleEasingType) {
+ Vector3d targetPos, float targetScale,
+ int32 duration,
+ EasingType moveEasingType, EasingType scaleEasingType) {
if (!process.isActiveForPlayer()) {
warning("stub: non-active camera lerp script invoked");
return new DelayTask(process, duration);
@@ -418,4 +417,4 @@ Task *Camera::waitToStop(Process &process) {
return new CamWaitToStopTask(process);
}
-}
+} // namespace Alcachofa
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 7d22cbef8c6..b93e1571385 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -55,7 +55,7 @@ public:
Task *lerpPos(Process &process,
Math::Vector2d targetPos,
int32 duration, EasingType easingType);
- Task *lerpPos(Process &process,
+ Task *lerpPos(Process &process,
Math::Vector3d targetPos,
int32 duration, EasingType easingType);
Task *lerpPosZ(Process &process,
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 7a2941151a9..44a3d70f88d 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -49,8 +49,7 @@ Console::Console() : GUI::Debugger() {
Console::~Console() {
}
-bool Console::isAnyDebugDrawingOn() const
-{
+bool Console::isAnyDebugDrawingOn() const {
return
g_engine->isDebugModeActive() ||
_showInteractables ||
@@ -207,8 +206,7 @@ bool Console::cmdItem(int argc, const char **args) {
return true;
}
-bool Console::cmdDebugMode(int argc, const char **args)
-{
+bool Console::cmdDebugMode(int argc, const char **args) {
if (argc < 2 || argc > 3) {
debugPrintf("usage: debugMode <mode> [<param>]\n");
debugPrintf("modes:\n");
@@ -236,8 +234,7 @@ bool Console::cmdDebugMode(int argc, const char **args)
return true;
}
-bool Console::cmdTeleport(int argc, const char **args)
-{
+bool Console::cmdTeleport(int argc, const char **args) {
if (argc < 1 || argc > 2)
{
debugPrintf("usagge: tp [<character>]\n");
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index be9196c9b2f..0e1eef52529 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -40,8 +40,7 @@ class ClosestFloorPointDebugHandler final : public IDebugHandler {
public:
ClosestFloorPointDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
- virtual void update() override
- {
+ virtual void update() override {
auto mousePos2D = g_engine->input().debugInput().mousePos2D();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
auto floor = g_engine->player().currentRoom()->activeFloor();
@@ -64,8 +63,7 @@ class FloorIntersectionsDebugHandler final : public IDebugHandler {
public:
FloorIntersectionsDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
- virtual void update() override
- {
+ virtual void update() override {
auto floor = g_engine->player().currentRoom()->activeFloor();
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
if (floor == nullptr || renderer == nullptr)
@@ -90,8 +88,7 @@ public:
private:
static constexpr float kMarkerLength = 16;
- void drawIntersectionsFor(const Polygon& polygon, IDebugRenderer* renderer)
- {
+ void drawIntersectionsFor(const Polygon &polygon, IDebugRenderer *renderer) {
auto &camera = g_engine->camera();
auto mousePos2D = g_engine->input().debugInput().mousePos2D();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
@@ -104,7 +101,7 @@ private:
auto mid = (a + b) / 2;
auto length = sqrtf(a.sqrDist(b));
auto normal = a - b;
- normal = { normal.y, (int16)-normal.x};
+ normal = { normal.y, (int16)-normal.x };
auto inner = mid + normal * (kMarkerLength / length);
renderer->debugPolyline(a, b, kDebugGreen);
@@ -118,8 +115,7 @@ class TeleportCharacterDebugHandler final : public IDebugHandler {
public:
TeleportCharacterDebugHandler(int32 kindI) : _kind((MainCharacterKind)kindI) {}
- virtual void update() override
- {
+ virtual void update() override {
g_engine->drawQueue().clear();
g_engine->player().drawCursor(true);
g_engine->drawQueue().draw();
@@ -149,8 +145,7 @@ public:
}
private:
- void teleport(MainCharacter &character, Point position)
- {
+ void teleport(MainCharacter &character, Point position) {
auto currentRoom = g_engine->player().currentRoom();
if (character.room() != currentRoom)
{
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 93d8153449c..ddcb400234c 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -65,7 +65,8 @@ void Item::trigger() {
ITriggerableObject::ITriggerableObject(ReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
- , _interactionDirection((Direction)stream.readSint32LE()) {}
+ , _interactionDirection((Direction)stream.readSint32LE()) {
+}
void ITriggerableObject::onClick() {
auto heldItem = g_engine->player().heldItem();
@@ -286,7 +287,8 @@ struct SayTextTask final : public Task {
SayTextTask(Process &process, Character *character, int32 dialogId)
: Task(process)
, _character(character)
- , _dialogId(dialogId) { }
+ , _dialogId(dialogId) {
+ }
virtual TaskReturn run() override {
TASK_BEGIN;
@@ -410,7 +412,8 @@ struct LerpLodBiasTask final : public Task {
: Task(process)
, _character(character)
, _targetLodBias(targetLodBias)
- , _durationMs(durationMs) { }
+ , _durationMs(durationMs) {
+ }
virtual TaskReturn run() override {
TASK_BEGIN;
@@ -558,7 +561,7 @@ void WalkingCharacter::updateWalking() {
_lastWalkAnimFrame = 0;
}
}
-
+
if (_pathPoints.empty()) {
_isWalking = false;
_currentPos = _sourcePos = targetPos;
@@ -569,8 +572,7 @@ void WalkingCharacter::updateWalking() {
_graphicNormal.topLeft() = _currentPos;
}
-void WalkingCharacter::updateWalkingAnimation()
-{
+void WalkingCharacter::updateWalkingAnimation() {
_direction = getDirection(_sourcePos, _pathPoints.top());
auto animation = walkingAnimation();
_graphicNormal.setAnimation(animation);
@@ -721,7 +723,8 @@ void WalkingCharacter::serializeSave(Serializer &serializer) {
struct ArriveTask : public Task {
ArriveTask(Process &process, const WalkingCharacter &character)
: Task(process)
- , _character(character) {}
+ , _character(character) {
+ }
virtual TaskReturn run() override {
return _character.isWalking()
@@ -964,7 +967,8 @@ struct DialogMenuTask : public Task {
DialogMenuTask(Process &process, MainCharacter *character)
: Task(process)
, _input(g_engine->input())
- , _character(character) {}
+ , _character(character) {
+ }
virtual TaskReturn run() override {
TASK_BEGIN;
@@ -1077,7 +1081,8 @@ const char *FloorColor::typeName() const { return "FloorColor"; }
FloorColor::FloorColor(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
- , _shape(stream) {}
+ , _shape(stream) {
+}
void FloorColor::drawDebug() {
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index a103461e91e..69c41caf53f 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -188,8 +188,7 @@ struct CenterBottomTextTask : public Task {
, _durationMs(durationMs) {
}
- TaskReturn run() override
- {
+ TaskReturn run() override {
Font &font = g_engine->globalUI().dialogFont();
const char *text = g_engine->world().getDialogLine(_dialogId);
const Point pos(
@@ -209,8 +208,7 @@ struct CenterBottomTextTask : public Task {
TASK_END;
}
- void debugPrint() override
- {
+ void debugPrint() override {
uint32 remaining = g_system->getMillis() - _startTime <= _durationMs
? _durationMs - (g_system->getMillis() - _startTime)
: 0;
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index f341c49f42a..b8929231948 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -210,7 +210,7 @@ public:
* SRC0_RGB is TEXTURE
* SRC1_RGB/ALPHA is PRIMARY COLOR
* COMBINE_ALPHA is REPLACE
- */
+ */
switch (blendMode) {
case BlendMode::AdditiveAlpha:
case BlendMode::Additive:
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index e53759fefe8..170278ff3c4 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -492,7 +492,8 @@ Graphic::Graphic(const Graphic &other)
, _isLooping(other._isLooping)
, _lastTime(other._lastTime)
, _frameI(other._frameI)
- , _depthScale(other._depthScale) {}
+ , _depthScale(other._depthScale) {
+}
void Graphic::loadResources() {
if (_animation != nullptr)
@@ -745,23 +746,24 @@ void TextDrawRequest::draw() {
FadeDrawRequest::FadeDrawRequest(FadeType type, float value, int8 order)
: IDrawRequest(order)
, _type(type)
- , _value(value) {}
+ , _value(value) {
+}
void FadeDrawRequest::draw() {
Color color;
const byte valueAsByte = (byte)(_value * 255);
switch (_type) {
- case FadeType::ToBlack:
- color = { 0, 0, 0, valueAsByte };
- g_engine->renderer().setBlendMode(BlendMode::AdditiveAlpha);
- break;
- case FadeType::ToWhite:
- color = { valueAsByte, valueAsByte, valueAsByte, valueAsByte };
- g_engine->renderer().setBlendMode(BlendMode::Additive);
- break;
- default:
- assert(false && "Invalid fade type");
- return;
+ case FadeType::ToBlack:
+ color = { 0, 0, 0, valueAsByte };
+ g_engine->renderer().setBlendMode(BlendMode::AdditiveAlpha);
+ break;
+ case FadeType::ToWhite:
+ color = { valueAsByte, valueAsByte, valueAsByte, valueAsByte };
+ g_engine->renderer().setBlendMode(BlendMode::Additive);
+ break;
+ default:
+ assert(false && "Invalid fade type");
+ return;
}
g_engine->renderer().setTexture(nullptr);
g_engine->renderer().quad(Vector2d(0, 0), as2D(Point(g_system->getWidth(), g_system->getHeight())), color);
@@ -780,7 +782,8 @@ struct FadeTask : public Task {
, _duration(duration)
, _easingType(easingType)
, _order(order)
- , _permanentFadeAction(permanentFadeAction){}
+ , _permanentFadeAction(permanentFadeAction) {
+ }
virtual TaskReturn run() override {
TASK_BEGIN;
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 8deecb306f5..866a65cde76 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -109,8 +109,7 @@ public:
Color color = kDebugRed
);
- inline void debugPolyline(Common::Point a, Common::Point b, Color color = kDebugRed)
- {
+ inline void debugPolyline(Common::Point a, Common::Point b, Color color = kDebugRed) {
Math::Vector2d points[] = { { (float)a.x, (float)a.y }, { (float)b.x, (float)b.y } };
debugPolygon({ points, 2 }, color);
}
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index ac1721b3e06..a28b1b17654 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -253,7 +253,7 @@ private:
void Player::triggerDoor(const Door *door) {
_heldItem = nullptr;
-
+
FakeLock lock(_activeCharacter->semaphore());
g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 1b7139bc0cd..5917cd76a00 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -32,7 +32,7 @@ public:
inline Room *currentRoom() const { return _currentRoom; }
inline MainCharacter *activeCharacter() const { return _activeCharacter; }
- inline ShapeObject *&selectedObject() { return _selectedObject; }
+ inline ShapeObject *&selectedObject() { return _selectedObject; }
inline void *&pressedObject() { return _pressedObject; }
inline Item *&heldItem() { return _heldItem; }
inline FakeSemaphore &semaphore() { return _semaphore; }
@@ -69,8 +69,8 @@ private:
Room *_currentRoom = nullptr,
*_roomBeforeInventory = nullptr;
MainCharacter *_activeCharacter;
- ShapeObject *_selectedObject = nullptr;
- void *_pressedObject = nullptr; // terrible but GlobalUI wants to store a Graphic pointer
+ ShapeObject *_selectedObject = nullptr;
+ void *_pressedObject = nullptr; // terrible but GlobalUI wants to store a Graphic pointer
Item *_heldItem = nullptr;
int32 _cursorFrameI = 0;
bool
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index a2a3f9ea2aa..6455aa904b7 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -560,9 +560,8 @@ void World::toggleObject(MainCharacterKind character, const char *objName, bool
ObjectBase *object = getObjectByName(character, objName);
if (object == nullptr)
object = getObjectByNameFromAnyRoom(objName);
- if (object == nullptr)
+ if (object == nullptr) // I would have liked an error for this, but original inconsistencies...
warning("Tried to toggle unknown object: %s", objName);
- // I would have liked an error for this, but original inconsistencies...
else
object->toggle(isEnabled);
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 039360255bc..84ab6a53102 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -191,7 +191,7 @@ private:
MainCharacter *_filemon, *_mortadelo;
uint8 _loadedMapCount = 0;
Common::HashMap<const char *, const char *,
- Common::Hash<const char*>,
+ Common::Hash<const char *>,
StringEqualTo> _localizedNames;
Common::Array<const char *> _dialogLines;
Common::Array<char> _namesChunk, _dialogChunk; ///< holds the memory for localizedNames / dialogLines
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 631e63671c8..822ba5487c9 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -57,9 +57,10 @@ Task *Task::delay(uint32 millis) {
DelayTask::DelayTask(Process &process, uint32 millis)
: Task(process)
- , _endTime(millis) {}
+ , _endTime(millis) {
+}
-TaskReturn DelayTask::run(){
+TaskReturn DelayTask::run() {
TASK_BEGIN;
_endTime += g_system->getMillis();
while (g_system->getMillis() < _endTime)
@@ -112,7 +113,7 @@ void Process::debugPrint() {
const char *characterName;
switch (_character) {
case MainCharacterKind::None: characterName = " <none>"; break;
- case MainCharacterKind::Filemon: characterName = " Filemon"; break;
+ case MainCharacterKind::Filemon: characterName = " Filemon"; break;
case MainCharacterKind::Mortadelo: characterName = "Mortadelo"; break;
default: characterName = "<invalid>"; break;
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index d48b65faec2..983aacb7b9d 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -81,7 +81,7 @@ private:
struct DelayTask : public Task {
DelayTask(Process &process, uint32 millis);
- virtual TaskReturn run() override;
+ virtual TaskReturn run() override;
virtual void debugPrint() override;
private:
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
index 1e7d9a7a7c1..168903c22f4 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -24,7 +24,7 @@
namespace Alcachofa {
-static const char* const ScriptOpNames[] = {
+static const char *const ScriptOpNames[] = {
"Nop",
"Dup",
"PushAddr",
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 04724635019..3647bbf2649 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -41,7 +41,8 @@ enum ScriptDebugLevel {
ScriptInstruction::ScriptInstruction(ReadStream &stream)
: _op((ScriptOp)stream.readSint32LE())
- , _arg(stream.readSint32LE()) {}
+ , _arg(stream.readSint32LE()) {
+}
Script::Script() {
File file;
@@ -122,7 +123,8 @@ bool Script::hasProcedure(const Common::String &procedure) const {
struct ScriptTimerTask : public Task {
ScriptTimerTask(Process &process, int32 durationSec)
: Task(process)
- , _durationSec(durationSec) {}
+ , _durationSec(durationSec) {
+ }
virtual TaskReturn run() override {
TASK_BEGIN;
@@ -211,7 +213,7 @@ struct ScriptTask : public Task {
if (_stack.empty())
debug("empty");
else {
- const auto& top = _stack.top();
+ const auto &top = _stack.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;
@@ -454,7 +456,7 @@ private:
TObject *getObjectArg(uint argI) {
const char *const name = getStringArg(argI);
auto *object = g_engine->world().getObjectByName(process().character(), name);
- return dynamic_cast<TObject*>(object);
+ return dynamic_cast<TObject *>(object);
}
MainCharacter &relatedCharacter() {
@@ -564,8 +566,8 @@ private:
case ScriptKernelTask::LerpWorldLodBias:
warning("STUB KERNEL CALL: LerpWorldLodBias");
return TaskReturn::finish(0);
-
- // object control / animation
+
+ // object control / animation
case ScriptKernelTask::On:
g_engine->world().toggleObject(process().character(), getStringArg(0), true);
return TaskReturn::finish(0);
@@ -604,7 +606,7 @@ private:
auto character = getObjectArg<WalkingCharacter>(0);
if (character == nullptr)
error("Script tried to make invalid character go: %s", getStringArg(0));
- auto target= getObjectArg<PointObject>(1);
+ auto target = getObjectArg<PointObject>(1);
if (target == nullptr)
error("Script tried to make character go to invalid object %s", getStringArg(1));
character->walkTo(target->position());
@@ -722,7 +724,7 @@ private:
return TaskReturn::finish(1);
}
case ScriptKernelTask::ClearInventory:
- switch((MainCharacterKind)getNumberArg(0)) {
+ switch ((MainCharacterKind)getNumberArg(0)) {
case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory(); break;
case MainCharacterKind::Filemon: g_engine->world().filemon().clearInventory(); break;
default: error("Script attempted to clear inventory with invalid character kind"); break;
@@ -855,7 +857,7 @@ private:
}
/**
- * @brief Check for original bugs related to the Put kernel call and handle them
+ * @brief Check for original bugs related to the Put kernel call and handle them
* @param target An out reference to the point object (maybe we can find an alternative one)
* @param targetName The given name of the target object
* @return false if the put kernel call should be ignored, true if we set target and want to continue with the kernel call
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index c1c0f43574a..9a651dd8f8b 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -91,8 +91,7 @@ EdgeDistances Polygon::edgeDistances(uint startPointI, const Point &query) const
return distances;
}
-static Point wiggleOnToLine(Point a, Point b, Point q)
-{
+static Point wiggleOnToLine(Point a, Point b, Point q) {
// due to rounding errors contains(bestPoint) might be false for on-edge closest points, let's fix that
// maybe there is a more mathematical solution to this, but it suffices for now
if (sideOfLine(a, b, q) >= 0) return q;
@@ -104,8 +103,7 @@ static Point wiggleOnToLine(Point a, Point b, Point q)
return q;
}
-Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) const
-{
+Point Polygon::closestPointTo(const Common::Point &query, float &distanceSqr) const {
assert(_points.size() > 0);
Common::Point bestPoint = {};
distanceSqr = std::numeric_limits<float>::infinity();
@@ -123,7 +121,7 @@ Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) co
}
if (edgeDists._onEdge >= 0.0f && edgeDists._onEdge <= edgeDists._edgeLength)
{
- float edgeDistSqr = powf(edgeDists._toEdge , 2.0f);
+ float edgeDistSqr = powf(edgeDists._toEdge, 2.0f);
if (edgeDistSqr < distanceSqr)
{
distanceSqr = edgeDistSqr;
@@ -131,7 +129,7 @@ Point Polygon::closestPointTo(const Common::Point& query, float &distanceSqr) co
bestPoint = _points[i] + (_points[j] - _points[i]) * (edgeDists._onEdge / edgeDists._edgeLength);
bestPoint = wiggleOnToLine(_points[i], _points[j], bestPoint);
}
- }
+ }
}
return bestPoint;
}
@@ -307,8 +305,7 @@ bool Shape::contains(const Point &query) const {
return polygonContaining(query) >= 0;
}
-Point Shape::closestPointTo(const Point &query, int32 &polygonI) const
-{
+Point Shape::closestPointTo(const Point &query, int32 &polygonI) const {
assert(_polygons.size() > 0);
float bestDistanceSqr = std::numeric_limits<float>::infinity();
Point bestPoint = {};
@@ -383,7 +380,7 @@ float PathFindingShape::depthAt(const Point &query) const {
}
PathFindingShape::LinkPolygonIndices::LinkPolygonIndices() {
- Common::fill(_points, _points + kPointsPerPolygon, LinkIndex( -1, -1 ));
+ Common::fill(_points, _points + kPointsPerPolygon, LinkIndex(-1, -1));
}
static Pair<int32, int32> orderPoints(const Polygon &polygon, int32 point1, int32 point2) {
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index cb53495864e..ad338b30b4f 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -47,7 +47,7 @@ struct Polygon {
bool contains(const Common::Point &query) const;
bool intersectsEdge(uint startPointI, Common::Point a, Common::Point b) const;
EdgeDistances edgeDistances(uint startPointI, const Common::Point &query) const;
- Common::Point closestPointTo(const Common::Point &query, float& distanceSqr) const;
+ Common::Point closestPointTo(const Common::Point &query, float &distanceSqr) const;
inline Common::Point closestPointTo(const Common::Point &query) const {
float dummy;
return closestPointTo(query, dummy);
@@ -94,14 +94,14 @@ struct PolygonIterator {
return tmp;
}
- inline bool operator==(const my_type& it) const {
+ inline bool operator==(const my_type &it) const {
return &this->_shape == &it._shape && this->_index == it._index;
}
- inline bool operator!=(const my_type& it) const {
+ inline bool operator!=(const my_type &it) const {
return &this->_shape != &it._shape || this->_index != it._index;
}
-
+
private:
friend typename Common::remove_const_t<TShape>;
PolygonIterator(const TShape &shape, uint index = 0)
@@ -177,7 +177,7 @@ public:
Common::Point centerTarget,
float depthScale,
float minDistSqr,
- Common::Point& evadeTarget) const;
+ Common::Point &evadeTarget) const;
private:
using LinkIndex = Common::Pair<int32, int32>;
@@ -213,7 +213,7 @@ private:
*/
Common::Array<Common::Point> _linkPoints;
/**
- * For each point of each polygon the index (or -1) to
+ * For each point of each polygon the index (or -1) to
* the corresponding link point. The second point is the
* index to the artifical center point
*/
@@ -223,7 +223,7 @@ private:
};
Common::Array<LinkPolygonIndices> _linkIndices;
/**
- * For the going-straight-through-edges check we need
+ * For the going-straight-through-edges check we need
* to know for each shared edge (defined by the starting point)
* into which quad we will walk.
*/
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index dbc583f3ed2..75e8e9eb0ba 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -36,7 +36,8 @@ using namespace Audio;
namespace Alcachofa {
Sounds::Playback::Playback(uint32 id, SoundHandle handle, Mixer::SoundType type)
- : _id(id), _handle(handle), _type(type) {}
+ : _id(id), _handle(handle), _type(type) {
+}
void Sounds::Playback::fadeOut(uint32 duration) {
_fadeStart = g_system->getMillis();
@@ -205,7 +206,8 @@ void Sounds::fadeOutVoiceAndSFX(uint32 duration) {
PlaySoundTask::PlaySoundTask(Process &process, SoundID soundID)
: Task(process)
- , _soundID(soundID) { }
+ , _soundID(soundID) {
+}
TaskReturn PlaySoundTask::run() {
auto &sounds = g_engine->sounds();
Commit: ea8535969395b192cfbf93643b10fcc778e9d63c
https://github.com/scummvm/scummvm/commit/ea8535969395b192cfbf93643b10fcc778e9d63c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Code conventions - Pass Point by value
Changed paths:
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.h
engines/alcachofa/input.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 27290532b29..4d418c3a230 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -78,7 +78,7 @@ Vector3d as3D(const Vector2d &v) {
return Vector3d(v.getX(), v.getY(), 0.0f);
}
-Vector3d as3D(const Common::Point &p) {
+Vector3d as3D(Common::Point p) {
return Vector3d((float)p.x, (float)p.y, 0.0f);
}
@@ -86,7 +86,7 @@ Vector2d as2D(const Vector3d &v) {
return Vector2d(v.x(), v.y());
}
-Vector2d as2D(const Point &p) {
+Vector2d as2D(Point p) {
return Vector2d((float)p.x, (float)p.y);
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 733f609b58b..5d7910e5ac9 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -108,9 +108,9 @@ private:
float ease(float t, EasingType type);
Math::Vector3d as3D(const Math::Vector2d &v);
-Math::Vector3d as3D(const Common::Point &p);
+Math::Vector3d as3D(Common::Point p);
Math::Vector2d as2D(const Math::Vector3d &v);
-Math::Vector2d as2D(const Common::Point &p);
+Math::Vector2d as2D(Common::Point p);
bool readBool(Common::ReadStream &stream);
Common::Point readPoint(Common::ReadStream &stream);
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index ddcb400234c..1d5a9b143d9 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -514,7 +514,7 @@ void WalkingCharacter::update() {
}
}
-static Direction getDirection(const Point &from, const Point &to) {
+static Direction getDirection(Point from, Point to) {
Point delta = from - to;
if (from.x == to.x)
return from.y < to.y ? Direction::Down : Direction::Up;
@@ -622,7 +622,7 @@ void WalkingCharacter::stopWalking(Direction direction) {
}
void WalkingCharacter::walkTo(
- const Point &target, Direction endDirection,
+ Point target, Direction endDirection,
ITriggerableObject *activateObject, const char *activateAction) {
// all the activation parameters are only relevant for MainCharacter
@@ -651,7 +651,7 @@ void WalkingCharacter::walkTo(
updateWalking();
}
-void WalkingCharacter::setPosition(const Point &target) {
+void WalkingCharacter::setPosition(Point target) {
_isWalking = false;
_sourcePos = _currentPos = target;
}
@@ -794,7 +794,7 @@ void MainCharacter::onArrived() {
}
void MainCharacter::walkTo(
- const Point &target_, Direction endDirection,
+ Point target_, Direction endDirection,
ITriggerableObject *activateObject, const char *activateAction) {
_activateObject = activateObject;
_activateAction = activateAction;
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 866a65cde76..45b644e1276 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -65,7 +65,7 @@ public:
virtual void update(const Graphics::Surface &surface) = 0;
inline void update(const Graphics::ManagedSurface &surface) { update(surface.rawSurface()); }
- inline const Common::Point &size() const { return _size; }
+ inline Common::Point size() const { return _size; }
private:
Common::Point _size;
@@ -187,7 +187,7 @@ public:
inline uint spriteCount() const { return _spriteBases.size(); }
inline uint frameCount() const { return _frames.size(); }
inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; }
- inline const Common::Point &frameCenter(int32 frameI) const { return _frames[frameI]._center; }
+ inline Common::Point frameCenter(int32 frameI) const { return _frames[frameI]._center; }
inline uint32 totalDuration() const { return _totalDuration; }
inline uint8 &premultiplyAlpha() { return _premultiplyAlpha; }
Common::Rect frameBounds(int32 frameI) const;
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index 24af6e2ad4b..e80287fd61e 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -38,8 +38,8 @@ public:
inline bool isMouseLeftDown() const { return _isMouseLeftDown; }
inline bool isMouseRightDown() const { return _isMouseRightDown; }
inline bool isAnyMouseDown() const { return _isMouseLeftDown || _isMouseRightDown; }
- inline const Common::Point &mousePos2D() const { return _mousePos2D; }
- inline const Common::Point &mousePos3D() const { return _mousePos3D; }
+ inline Common::Point mousePos2D() const { return _mousePos2D; }
+ inline Common::Point mousePos3D() const { return _mousePos3D; }
const Input &debugInput() const { scumm_assert(_debugInput != nullptr); return *_debugInput; }
void nextFrame();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 8738d4b6f39..831c99faf57 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -325,7 +325,7 @@ public:
ITriggerableObject(Common::ReadStream &stream);
inline Direction interactionDirection() const { return _interactionDirection; }
- inline const Common::Point &interactionPoint() const { return _interactionPoint; }
+ inline Common::Point interactionPoint() const { return _interactionPoint; }
virtual void trigger(const char *action) = 0;
@@ -420,7 +420,7 @@ public:
virtual ~WalkingCharacter() override = default;
inline bool isWalking() const { return _isWalking; }
- inline const Common::Point &position() const { return _currentPos; }
+ inline Common::Point position() const { return _currentPos; }
inline float stepSizeFactor() const { return _stepSizeFactor; }
virtual void update() override;
@@ -430,12 +430,12 @@ public:
virtual void freeResources() override;
virtual void serializeSave(Common::Serializer &serializer) override;
virtual void walkTo(
- const Common::Point &target,
+ Common::Point target,
Direction endDirection = Direction::Invalid,
ITriggerableObject *activateObject = nullptr,
const char *activateAction = nullptr);
void stopWalking(Direction direction = Direction::Invalid);
- void setPosition(const Common::Point &target);
+ void setPosition(Common::Point target);
virtual const char *typeName() const;
Task *waitForArrival(Process &process);
@@ -497,7 +497,7 @@ public:
virtual void serializeSave(Common::Serializer &serializer) override;
virtual const char *typeName() const;
virtual void walkTo(
- const Common::Point &target,
+ Common::Point target,
Direction endDirection = Direction::Invalid,
ITriggerableObject *activateObject = nullptr,
const char *activateAction = nullptr) override;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 6455aa904b7..a4fe4b3ddf5 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -372,7 +372,7 @@ bool Inventory::updateInput() {
}
Item *Inventory::getHoveredItem() {
- auto &mousePos = g_engine->input().mousePos2D();
+ auto mousePos = g_engine->input().mousePos2D();
for (auto item : _items) {
if (!item->isEnabled())
continue;
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 84ab6a53102..8fad2cf1d93 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -39,10 +39,10 @@ public:
inline const PathFindingShape *activeFloor() const {
return _activeFloorI < 0 ? nullptr : &_floors[_activeFloorI];
}
- inline int8 orderAt(const Common::Point &query) const {
+ inline int8 orderAt(Common::Point query) const {
return _activeFloorI < 0 ? 49 : activeFloor()->orderAt(query);
}
- inline float depthAt(const Common::Point &query) const {
+ inline float depthAt(Common::Point query) const {
return _activeFloorI < 0 ? 1 : activeFloor()->depthAt(query);
}
inline uint8 characterAlphaTint() const { return _characterAlphaTint; }
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 9a651dd8f8b..65dae596388 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -26,17 +26,17 @@ using namespace Math;
namespace Alcachofa {
-static int sideOfLine(const Point &a, const Point &b, const Point &q) {
+static int sideOfLine(Point a, Point b, Point q) {
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
}
-static bool segmentsIntersect(const Point &a1, const Point &b1, const Point &a2, const Point &b2) {
+static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
// as there are a number of special cases to consider,
// this method is a direct translation of the original engine
- const auto sideOfLine = [](const Point &a, const Point &b, const Point q) {
+ const auto sideOfLine = [](Point a, Point b, const Point q) {
return Alcachofa::sideOfLine(a, b, q) > 0;
};
- const auto lineIntersects = [&](const Point &a1, const Point &b1, const Point &a2, const Point &b2) {
+ const auto lineIntersects = [&](Point a1, Point b1, Point a2, Point b2) {
return sideOfLine(a1, b1, a2) != sideOfLine(a1, b1, b2);
};
@@ -54,7 +54,7 @@ static bool segmentsIntersect(const Point &a1, const Point &b1, const Point &a2,
}
}
-bool Polygon::contains(const Point &query) const {
+bool Polygon::contains(Point query) const {
switch (_points.size()) {
case 0: return false;
case 1: return query == _points[0];
@@ -74,7 +74,7 @@ bool Polygon::intersectsEdge(uint startPointI, Point a, Point b) const {
return segmentsIntersect(_points[startPointI], _points[endPointI], a, b);
}
-EdgeDistances Polygon::edgeDistances(uint startPointI, const Point &query) const {
+EdgeDistances Polygon::edgeDistances(uint startPointI, Point query) const {
assert(startPointI < _points.size());
uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
Vector2d
@@ -103,7 +103,7 @@ static Point wiggleOnToLine(Point a, Point b, Point q) {
return q;
}
-Point Polygon::closestPointTo(const Common::Point &query, float &distanceSqr) const {
+Point Polygon::closestPointTo(Point query, float &distanceSqr) const {
assert(_points.size() > 0);
Common::Point bestPoint = {};
distanceSqr = std::numeric_limits<float>::infinity();
@@ -142,11 +142,11 @@ Point Polygon::midPoint() const {
return sum / (int16)_points.size();
}
-static float depthAtForLine(const Point &a, const Point &b, const Point &q, int8 depthA, int8 depthB) {
+static float depthAtForLine(Point a, Point b, Point q, int8 depthA, int8 depthB) {
return (sqrtf(a.sqrDist(q)) / a.sqrDist(b) * depthB + depthA) * 0.01f;
}
-static float depthAtForConvex(const PathFindingPolygon &p, const Point &q) {
+static float depthAtForConvex(const PathFindingPolygon &p, Point q) {
float sumDepths = 0, sumDistances = 0;
for (uint i = 0; i < p._points.size(); i++) {
uint j = i + 1 == p._points.size() ? 0 : i + 1;
@@ -160,7 +160,7 @@ static float depthAtForConvex(const PathFindingPolygon &p, const Point &q) {
return sumDepths / sumDistances * 0.01f;
}
-float PathFindingPolygon::depthAt(const Point &query) const {
+float PathFindingPolygon::depthAt(Point query) const {
switch (_points.size()) {
case 0:
case 1: return 1.0f;
@@ -184,14 +184,14 @@ uint PathFindingPolygon::findSharedPoints(
return count;
}
-static Color colorAtForLine(const Point &a, const Point &b, const Point &q, Color colorA, Color colorB) {
+static Color colorAtForLine(Point a, Point b, Point q, Color colorA, Color colorB) {
// I highly suspect RGB calculation being very bugged, so for now I just ignore and only calc alpha
float phase = sqrtf(q.sqrDist(a)) / a.sqrDist(b);
colorA.a += phase * colorB.a;
return colorA;
}
-static Color colorAtForConvex(const FloorColorPolygon &p, const Point &query) {
+static Color colorAtForConvex(const FloorColorPolygon &p, Point query) {
// This is a quite literal translation of the original engine
// There may very well be a better way than this...
float weights[FloorColorShape::kPointsPerPolygon];
@@ -235,7 +235,7 @@ static Color colorAtForConvex(const FloorColorPolygon &p, const Point &query) {
};
}
-Color FloorColorPolygon::colorAt(const Point &query) const {
+Color FloorColorPolygon::colorAt(Point query) const {
switch (_points.size()) {
case 0: return kWhite;
case 1: return { 255, 255, 255, _pointColors[0].a };
@@ -293,7 +293,7 @@ Polygon Shape::at(uint index) const {
return p;
}
-int32 Shape::polygonContaining(const Point &query) const {
+int32 Shape::polygonContaining(Point query) const {
for (uint i = 0; i < _polygons.size(); i++) {
if (at(i).contains(query))
return (int32)i;
@@ -301,11 +301,11 @@ int32 Shape::polygonContaining(const Point &query) const {
return -1;
}
-bool Shape::contains(const Point &query) const {
+bool Shape::contains(Point query) const {
return polygonContaining(query) >= 0;
}
-Point Shape::closestPointTo(const Point &query, int32 &polygonI) const {
+Point Shape::closestPointTo(Point query, int32 &polygonI) const {
assert(_polygons.size() > 0);
float bestDistanceSqr = std::numeric_limits<float>::infinity();
Point bestPoint = {};
@@ -369,12 +369,12 @@ PathFindingPolygon PathFindingShape::at(uint index) const {
return p;
}
-int8 PathFindingShape::orderAt(const Point &query) const {
+int8 PathFindingShape::orderAt(Point query) const {
int32 polygon = polygonContaining(query);
return polygon < 0 ? 49 : _polygonOrders[polygon];
}
-float PathFindingShape::depthAt(const Point &query) const {
+float PathFindingShape::depthAt(Point query) const {
int32 polygon = polygonContaining(query);
return polygon < 0 ? 1.0f : at(polygon).depthAt(query);
}
@@ -507,7 +507,7 @@ void PathFindingShape::calculateFloydWarshall() {
assert(find(_previousTarget.begin(), _previousTarget.end(), -1) == _previousTarget.end());
}
-bool PathFindingShape::findPath(const Point &from, const Point &to_, Stack<Point> &path) const {
+bool PathFindingShape::findPath(Point from, Point to_, Stack<Point> &path) const {
Point to = to_; // we might want to correct it
path.clear();
@@ -535,7 +535,7 @@ int32 PathFindingShape::edgeTarget(uint polygonI, uint pointI) const {
}
bool PathFindingShape::canGoStraightThrough(
- const Point &from, const Point &to,
+ Point from, Point to,
int32 fromContainingI, int32 toContainingI) const {
int32 lastContainingI = -1;
while (fromContainingI != toContainingI) {
@@ -560,7 +560,7 @@ bool PathFindingShape::canGoStraightThrough(
}
void PathFindingShape::floydWarshallPath(
- const Point &from, const Point &to,
+ Point from, Point to,
int32 fromContaining, int32 toContaining,
Stack<Point> &path) const {
path.push(to);
@@ -663,7 +663,7 @@ FloorColorPolygon FloorColorShape::at(uint index) const {
return p;
}
-OptionalColor FloorColorShape::colorAt(const Common::Point &query) const {
+OptionalColor FloorColorShape::colorAt(Point query) const {
int32 polygon = polygonContaining(query);
return polygon < 0
? OptionalColor(false, kClear)
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index ad338b30b4f..2fae40a5ac9 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -44,11 +44,11 @@ struct Polygon {
uint _index;
Common::Span<const Common::Point> _points;
- bool contains(const Common::Point &query) const;
+ bool contains(Common::Point query) const;
bool intersectsEdge(uint startPointI, Common::Point a, Common::Point b) const;
- EdgeDistances edgeDistances(uint startPointI, const Common::Point &query) const;
- Common::Point closestPointTo(const Common::Point &query, float &distanceSqr) const;
- inline Common::Point closestPointTo(const Common::Point &query) const {
+ EdgeDistances edgeDistances(uint startPointI, Common::Point query) const;
+ Common::Point closestPointTo(Common::Point query, float &distanceSqr) const;
+ inline Common::Point closestPointTo(Common::Point query) const {
float dummy;
return closestPointTo(query, dummy);
}
@@ -61,14 +61,14 @@ struct PathFindingPolygon : Polygon {
using SharedPoint = Common::Pair<uint, uint>;
- float depthAt(const Common::Point &query) const;
+ float depthAt(Common::Point query) const;
uint findSharedPoints(const PathFindingPolygon &other, Common::Span<SharedPoint> sharedPoints) const;
};
struct FloorColorPolygon : Polygon {
Common::Span<const Color> _pointColors;
- Color colorAt(const Common::Point &query) const;
+ Color colorAt(Common::Point query) const;
};
template<class TShape, typename TPolygon>
@@ -127,10 +127,10 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
Polygon at(uint index) const;
- int32 polygonContaining(const Common::Point &query) const;
- bool contains(const Common::Point &query) const;
- Common::Point closestPointTo(const Common::Point &query, int32 &polygonI) const;
- inline Common::Point closestPointTo(const Common::Point &query) const {
+ int32 polygonContaining(Common::Point query) const;
+ bool contains(Common::Point query) const;
+ Common::Point closestPointTo(Common::Point query, int32 &polygonI) const;
+ inline Common::Point closestPointTo(Common::Point query) const {
int32 dummy;
return closestPointTo(query, dummy);
}
@@ -166,11 +166,11 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
PathFindingPolygon at(uint index) const;
- int8 orderAt(const Common::Point &query) const;
- float depthAt(const Common::Point &query) const;
+ int8 orderAt(Common::Point query) const;
+ float depthAt(Common::Point query) const;
bool findPath(
- const Common::Point &from,
- const Common::Point &to,
+ Common::Point from,
+ Common::Point to,
Common::Stack<Common::Point> &path) const;
int32 edgeTarget(uint polygonI, uint pointI) const;
bool findEvadeTarget(
@@ -194,12 +194,12 @@ private:
void initializeFloydWarshall();
void calculateFloydWarshall();
bool canGoStraightThrough(
- const Common::Point &from,
- const Common::Point &to,
+ Common::Point from,
+ Common::Point to,
int32 fromContaining, int32 toContaining) const;
void floydWarshallPath(
- const Common::Point &from,
- const Common::Point &to,
+ Common::Point from,
+ Common::Point to,
int32 fromContaining, int32 toContaining,
Common::Stack<Common::Point> &path) const;
@@ -245,7 +245,7 @@ public:
inline iterator end() const { return { *this, polygonCount() }; }
FloorColorPolygon at(uint index) const;
- OptionalColor colorAt(const Common::Point &query) const;
+ OptionalColor colorAt(Common::Point query) const;
private:
Common::Array<Color> _pointColors;
Commit: 54e1201f92e4d91c0b0d48318246fac184c86f22
https://github.com/scummvm/scummvm/commit/54e1201f92e4d91c0b0d48318246fac184c86f22
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Fix taking and combining inventory items
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 1d5a9b143d9..8cc4fc6a99a 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -45,6 +45,14 @@ Item::Item(const Item &other)
new (&_graphic) Graphic(other._graphic);
}
+void Item::draw() {
+ if (!isEnabled())
+ return;
+ Item* heldItem = g_engine->player().heldItem();
+ if (heldItem == nullptr || !heldItem->name().equalsIgnoreCase(name()))
+ GraphicObject::draw();
+}
+
void Item::trigger() {
auto &player = g_engine->player();
auto &heldItem = player.heldItem();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 831c99faf57..7ce235eac67 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -316,6 +316,7 @@ public:
Item(Room *room, Common::ReadStream &stream);
Item(const Item &other);
+ virtual void draw() override;
virtual const char *typeName() const;
void trigger();
};
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index a28b1b17654..81a06220279 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -182,7 +182,9 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
auto &script = g_engine->script();
if (script.createProcess(activeCharacterKind(), object->name(), action, ScriptFlags::AllowMissing) != nullptr)
return;
- else if (scumm_stricmp(action, "MIRAR") == 0)
+
+ _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
Commit: 7bcb5732070287479acce83fdb84c6b183c6939b
https://github.com/scummvm/scummvm/commit/7bcb5732070287479acce83fdb84c6b183c6939b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Fix blending modes
This also fixes the characters not being blacked silhouettes in the intro
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 8cc4fc6a99a..4f6e9e9f281 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -868,7 +868,7 @@ void MainCharacter::drawInner() {
}
assert(activeGraphic != nullptr);
- activeGraphic->color() = kWhite; // TODO: Add and use character color
+ activeGraphic->color().a = room()->characterAlphaTint() * 255 / 100;
g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
}
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index b8929231948..5d8428ba6ea 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -146,10 +146,6 @@ public:
virtual void begin() override {
GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PRIMARY_COLOR));
_currentLodBias = -1000.0f;
_currentTexture = nullptr;
_currentBlendMode = (BlendMode)-1;
@@ -206,30 +202,45 @@ public:
default: assert(false && "Invalid blend mode"); break;
}
- /** now the texture stage, mind that this always applies:
- * SRC0_RGB is TEXTURE
- * SRC1_RGB/ALPHA is PRIMARY COLOR
- * COMBINE_ALPHA is REPLACE
- */
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE));
switch (blendMode) {
case BlendMode::AdditiveAlpha:
case BlendMode::Additive:
case BlendMode::Multiply:
- // (1 - TintAlpha) * TexColor, TexAlpha
+ // TintAlpha * TexColor, TexAlpha
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_ONE_MINUS_SRC_ALPHA));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA)); // alpha replaces color
break;
case BlendMode::Alpha:
// TexColor, TintAlpha
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
break;
case BlendMode::Tinted:
// (TintColor * TintAlpha) * TexColor, TexAlpha
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR)); // pre-multiplied with alpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR)); // we have to pre-multiply
break;
default: assert(false && "Invalid blend mode"); break;
}
@@ -274,12 +285,17 @@ public:
}
float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
+ if (_currentBlendMode == BlendMode::Tinted)
+ {
+ colors[0] *= colors[3];
+ colors[1] *= colors[3];
+ colors[2] *= colors[3];
+ }
- GL_CALL(glColor4f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f));
+ GL_CALL(glColor4fv(colors));
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
if (_currentTexture != nullptr)
GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
- GL_CALL(glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, colors));
GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
#if _DEBUG
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 170278ff3c4..e242db5e87d 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -317,7 +317,7 @@ int32 Animation::frameAtTime(uint32 time) const {
void Animation::prerenderFrame(int32 frameI) {
assert(frameI >= 0 && (uint)frameI < frameCount());
- if (frameI == _renderedFrameI)
+ if (frameI == _renderedFrameI && _renderedPremultiplyAlpha == _premultiplyAlpha)
return;
auto bounds = frameBounds(frameI);
_renderedSurface.clear();
@@ -331,16 +331,11 @@ void Animation::prerenderFrame(int32 frameI) {
fullBlend(*image, _renderedSurface, offsetX, offsetY);
}
- /* TODO: Find a situation where this is actually used, otherwise this currently just produces bugs
- if (_premultiplyAlpha != 100) {
- byte *itPixel = (byte*)_renderedSurface.getPixels();
- uint componentCount = _renderedSurface.w * _renderedSurface.h * 4;
- for (uint32 i = 0; i < componentCount; i++, itPixel++)
- *itPixel = *itPixel * _premultiplyAlpha / 100;
- }*/
+ // Here was some alpha premultiplication, but it only produces bugs so is ignored
_renderedTexture->update(_renderedSurface);
_renderedFrameI = frameI;
+ _renderedPremultiplyAlpha = _premultiplyAlpha;
}
void Animation::draw2D(int32 frameI, Vector2d topLeft, float scale, BlendMode blendMode, Color color) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 45b644e1276..351f5508257 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -48,11 +48,11 @@ namespace Alcachofa {
*
*/
enum class BlendMode {
- AdditiveAlpha,
- Additive,
- Multiply,
- Alpha,
- Tinted
+ AdditiveAlpha, // Normal objects
+ Additive, // "Effect" objects, fades
+ Multiply, // Unused in Movie Adventure
+ Alpha, // Unused in Movie Adventure (used for debugging)
+ Tinted // Used for fonts
};
class Shape;
@@ -221,7 +221,8 @@ private:
void prerenderFrame(int32 frameI);
int32_t _renderedFrameI = -1;
- uint8 _premultiplyAlpha = 100; ///< in percent [0-100] not [0-255]
+ uint8 _premultiplyAlpha = 100, ///< in percent [0-100] not [0-255]
+ _renderedPremultiplyAlpha = 255;
Graphics::ManagedSurface _renderedSurface;
Common::ScopedPtr<ITexture> _renderedTexture;
Commit: 22bace3365b2139f7f5870af9f8d4cc0d29d0b2b
https://github.com/scummvm/scummvm/commit/22bace3365b2139f7f5870af9f8d4cc0d29d0b2b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Fix invalid door target in LABERINTO
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 81a06220279..a8a240fe1dd 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -256,6 +256,9 @@ private:
void Player::triggerDoor(const Door *door) {
_heldItem = nullptr;
+ if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
+ return; // Original exception
+
FakeLock lock(_activeCharacter->semaphore());
g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
}
Commit: a905192ce6a34cc56dbc9955d7789c1e53ee8ef9
https://github.com/scummvm/scummvm/commit/a905192ce6a34cc56dbc9955d7789c1e53ee8ef9
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Fix draw order of hovered object names
Changed paths:
engines/alcachofa/general-objects.cpp
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index f1e811f2dcd..c240db4a245 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -231,7 +231,7 @@ void ShapeObject::onHoverUpdate() {
g_engine->globalUI().generalFont(),
g_engine->world().getLocalizedName(name()),
g_engine->input().mousePos2D() - Point(0, 35),
- -1, true, kWhite, 0);
+ -1, true, kWhite, -kForegroundOrderCount);
}
void ShapeObject::onClick() {
Commit: ffedb0376c60fd0c1044545683cdca25a7f9ca0b
https://github.com/scummvm/scummvm/commit/ffedb0376c60fd0c1044545683cdca25a7f9ca0b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:53+02:00
Commit Message:
ALCACHOFA: Fix invisible cursor in DINOSAURIO
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 3647bbf2649..66d1f73ab8f 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -135,6 +135,7 @@ struct ScriptTimerTask : public Task {
_result = g_engine->script().variable("SeHaPulsadoRaton") ? 0 : 2;
else
_result = 1;
+ g_engine->player().drawCursor();
}
TASK_YIELD; // Wait a frame to not produce an endless loop
TASK_RETURN(_result);
Commit: 26a1af2f93165ede9d6550074e9023d0f8901552
https://github.com/scummvm/scummvm/commit/26a1af2f93165ede9d6550074e9023d0f8901552
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix crash with zero-size sound files
Changed paths:
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 75e8e9eb0ba..e9562bd07b0 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -118,7 +118,9 @@ static AudioStream *openAudio(const String &fileName) {
String path = String::format("Sonidos/%s.SND", fileName.c_str());
File *file = new File();
if (file->open(path.c_str()))
- return loadSND(file);
+ return file->size() == 0
+ ? makeSilentAudioStream(8000, false) // Movie Adventure has some null-size audio files, they are treated like infinite samples
+ : loadSND(file);
path.setChar('W', path.size() - 3);
path.setChar('A', path.size() - 2);
path.setChar('V', path.size() - 1);
Commit: a80d81995166bcf4faff08afeabf3e910fd6c0b4
https://github.com/scummvm/scummvm/commit/a80d81995166bcf4faff08afeabf3e910fd6c0b4
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix hieroglyphic display in 16
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 66d1f73ab8f..20632b49dcd 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -294,10 +294,10 @@ struct ScriptTask : public Task {
pushNumber(-popNumber() + popNumber());
break;
case ScriptOp::Less:
- pushNumber(popNumber() >= popNumber());
+ pushNumber(popNumber() > popNumber());
break;
case ScriptOp::Greater:
- pushNumber(popNumber() <= popNumber());
+ pushNumber(popNumber() < popNumber());
break;
case ScriptOp::LessEquals:
pushNumber(popNumber() >= popNumber());
Commit: 94dc9afec8c104da5ba12b8c9e8476e960ba7192
https://github.com/scummvm/scummvm/commit/94dc9afec8c104da5ba12b8c9e8476e960ba7192
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix TELEFRUSKYMATIC in LAB_BACTERIO
Changed paths:
engines/alcachofa/graphics.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index e242db5e87d..d3bcd839351 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -194,7 +194,8 @@ void AnimationBase::loadMissingAnimation() {
!_fileName.equalsIgnoreCase("PP_MORTA.AN0") &&
!_fileName.equalsIgnoreCase("DESPACHO_SUPER2___FONDO_PP_SUPER.AN0") &&
!_fileName.equalsIgnoreCase("ESTOMAGO.AN0") &&
- !_fileName.equalsIgnoreCase("CREDITOS.AN0"))
+ !_fileName.equalsIgnoreCase("CREDITOS.AN0") &&
+ !_fileName.equalsIgnoreCase("MONITOR___OL_EFECTO_FONDO.AN0"))
error("Could not open animation %s", _fileName.c_str());
// otherwise setup a functioning but empty animation
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 20632b49dcd..841bd126dcb 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -928,7 +928,7 @@ void Script::updateCommonVariables() {
variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
variable("textoson") = 1; // TODO: Add subtitle option
- variable("modored") = 1; // this is signalling whether a network connection is established
+ variable("modored") = 0; // this is signalling whether a network connection is established
}
}
Commit: 181bede30ef7d9994bda99f6c5338c1480f96ffe
https://github.com/scummvm/scummvm/commit/181bede30ef7d9994bda99f6c5338c1480f96ffe
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix holding items after pickup
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 4f6e9e9f281..8d356fb440b 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -928,7 +928,8 @@ void MainCharacter::pickup(const String &name, bool putInHand) {
error("Tried to pickup unknown item: %s", name.c_str());
item->toggle(true);
if (g_engine->player().activeCharacter() == this) {
- // TODO: Put item in hand for pickup
+ if (putInHand)
+ g_engine->player().heldItem() = item;
g_engine->world().inventory().updateItemsByActiveCharacter();
}
}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index d3bcd839351..3e5446fb281 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -248,7 +248,7 @@ void Animation::load() {
if (_isLoaded)
return;
AnimationBase::load();
- const auto withMipmaps = _folder != AnimationFolder::Backgrounds;
+ const bool withMipmaps = _folder != AnimationFolder::Backgrounds;
Rect maxBounds = maxFrameBounds();
_renderedSurface.create(maxBounds.width(), maxBounds.height(), BlendBlit::getSupportedPixelFormat());
_renderedTexture = g_engine->renderer().createTexture(maxBounds.width(), maxBounds.height(), withMipmaps);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 841bd126dcb..80810fd0c59 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -709,11 +709,11 @@ private:
// Inventory control
case ScriptKernelTask::Pickup:
- relatedCharacter().pickup(getStringArg(0), getNumberArg(1));
+ relatedCharacter().pickup(getStringArg(0), !getNumberArg(1));
return TaskReturn::finish(1);
case ScriptKernelTask::CharacterPickup: {
auto &character = g_engine->world().getMainCharacterByKind((MainCharacterKind)getNumberArg(1));
- character.pickup(getStringArg(0), getNumberArg(2));
+ character.pickup(getStringArg(0), !getNumberArg(2));
return TaskReturn::finish(1);
}
case ScriptKernelTask::Drop:
Commit: 904577adb378f5a682d9434d280a86b05001e5d9
https://github.com/scummvm/scummvm/commit/904577adb378f5a682d9434d280a86b05001e5d9
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix cursor directions for doors
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 8d356fb440b..36fb6796ff4 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -134,7 +134,7 @@ CursorType Door::cursorType() const {
CursorType fromObject = ShapeObject::cursorType();
if (fromObject != CursorType::Point)
return fromObject;
- switch (_characterDirection) {
+ switch (_interactionDirection) {
case Direction::Up: return CursorType::LeaveUp;
case Direction::Right: return CursorType::LeaveRight;
case Direction::Down: return CursorType::LeaveDown;
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index a8a240fe1dd..a78336acaa1 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -218,7 +218,6 @@ struct DoorTask : public Task {
virtual TaskReturn run() {
TASK_BEGIN;
// TODO: Fade out music on room change
- // TODO: Fade out/in on room change instead of delay
TASK_WAIT(fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
_player.changeRoom(_targetRoom->name(), true);
Commit: d131608699a36a4a62382eea0e678a23b0f191ab
https://github.com/scummvm/scummvm/commit/d131608699a36a4a62382eea0e678a23b0f191ab
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix camera transitions on character change/inventory close
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index dc9f0ced99f..0edd8a2b081 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -49,7 +49,8 @@ void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
}
void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
- _cur._followTarget = target;
+ _cur._isFollowingTarget = target != nullptr;
+ _followTarget = target;
_lastUpdateTime = g_system->getMillis();
_catchUp = catchUp;
if (target == nullptr)
@@ -167,7 +168,7 @@ void Camera::update() {
deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
_lastUpdateTime = now;
- if (_catchUp && _cur._followTarget != nullptr) {
+ if (_catchUp) {
for (int i = 0; i < 4; i++)
updateFollowing(50.0f);
_catchUp = false;
@@ -177,27 +178,27 @@ void Camera::update() {
}
void Camera::updateFollowing(float deltaTime) {
- if (_cur._followTarget == nullptr)
+ if (!_cur._isFollowingTarget || _followTarget == nullptr)
return;
const float resolutionFactor = g_system->getWidth() * 0.00125f;
const float acceleration = 460 * resolutionFactor;
const float baseDeadZoneSize = 25 * resolutionFactor;
const float minSpeed = 20 * resolutionFactor;
const float maxSpeed = this->_cur._maxSpeedFactor * resolutionFactor;
- const float depthScale = _cur._followTarget->graphic()->depthScale();
- const auto characterPolygon = _cur._followTarget->shape()->at(0);
+ const float depthScale = _followTarget->graphic()->depthScale();
+ const auto characterPolygon = _followTarget->shape()->at(0);
const float halfHeight = ABS(characterPolygon._points[0].y - characterPolygon._points[2].y) / 2.0f;
Vector3d targetCenter = setAppliedCenter({
- _shake.getX() + _cur._followTarget->position().x,
- _shake.getY() + _cur._followTarget->position().y - depthScale * 85,
+ _shake.getX() + _followTarget->position().x,
+ _shake.getY() + _followTarget->position().y - depthScale * 85,
_cur._usedCenter.z()});
targetCenter.y() -= halfHeight;
float distanceToTarget = as2D(_cur._usedCenter - targetCenter).getMagnitude();
- float moveDistance = _cur._followTarget->stepSizeFactor() * _cur._speed * deltaTime;
+ float moveDistance = _followTarget->stepSizeFactor() * _cur._speed * deltaTime;
float deadZoneSize = baseDeadZoneSize / _cur._scale;
- if (_cur._followTarget->isWalking() && depthScale > 0.8f)
+ if (_followTarget->isWalking() && depthScale > 0.8f)
deadZoneSize = (baseDeadZoneSize + (depthScale - 0.8f) * 200) / _cur._scale;
bool isFarAway = false;
if (ABS(targetCenter.x() - _cur._usedCenter.x()) > deadZoneSize ||
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index b93e1571385..e9f6eaa8411 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -38,7 +38,7 @@ class Camera {
public:
inline Math::Angle rotation() const { return _cur._rotation; }
inline Math::Vector2d &shake() { return _shake; }
- inline WalkingCharacter *followTarget() { return _cur._followTarget; }
+ inline WalkingCharacter *followTarget() { return _followTarget; }
void update();
Math::Vector3d transform2Dto3D(Math::Vector3d v) const;
@@ -93,11 +93,12 @@ private:
_maxSpeedFactor = 230.0f;
Math::Angle _rotation;
bool _isBraking = false;
- WalkingCharacter *_followTarget = nullptr;
+ bool _isFollowingTarget = false;
};
static constexpr uint kStateBackupCount = 2;
State _cur, _backups[kStateBackupCount];
+ WalkingCharacter *_followTarget = nullptr;
uint32 _lastUpdateTime = 0;
bool _isChanging = false,
_catchUp = false;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index a4fe4b3ddf5..4050227db52 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -281,6 +281,10 @@ void Room::drawDebug() {
void Room::loadResources() {
for (auto *object : _objects)
object->loadResources();
+
+ // this fixes some camera backups not working when closing the inventory
+ if (g_engine->player().currentRoom() == this)
+ updateRoomBounds();
}
void Room::freeResources() {
Commit: 8f2345f2d981141a2fc345e792cea0555d927152
https://github.com/scummvm/scummvm/commit/8f2345f2d981141a2fc345e792cea0555d927152
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix some camera moves when scaled
Changed paths:
engines/alcachofa/camera.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 0edd8a2b081..f40a803229f 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -78,11 +78,10 @@ void Camera::restore(uint slot) {
_cur = backupState;
}
-static Matrix4 scaleMatrix(float scale) {
+static Matrix4 scale2DMatrix(float scale) {
Matrix4 m;
m(0, 0) = scale;
m(1, 1) = scale;
- m(2, 2) = scale;
return m;
}
@@ -92,13 +91,13 @@ void Camera::setupMatricesAround(Vector3d center) {
_mat3Dto2D.setToIdentity();
_mat3Dto2D.translate(-center);
_mat3Dto2D = matTemp * _mat3Dto2D;
- _mat3Dto2D = _mat3Dto2D * scaleMatrix(_cur._scale);
+ _mat3Dto2D = scale2DMatrix(_cur._scale) * _mat3Dto2D;
_mat2Dto3D.setToIdentity();
_mat2Dto3D.translate(center);
matTemp.buildAroundZ(-_cur._rotation);
- matTemp = scaleMatrix(1 / _cur._scale) * matTemp;
- _mat2Dto3D = matTemp * _mat2Dto3D;
+ matTemp = matTemp * scale2DMatrix(1 / _cur._scale);
+ _mat2Dto3D = _mat2Dto3D * matTemp;
}
void minmax(Vector3d &min, Vector3d &max, Vector3d val) {
Commit: 0764c495ca0200f94fb341cbac3a6af364809e55
https://github.com/scummvm/scummvm/commit/0764c495ca0200f94fb341cbac3a6af364809e55
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Refactor animation-related methods
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 36fb6796ff4..8a75065f218 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -606,12 +606,12 @@ void WalkingCharacter::updateWalkingAnimation() {
stepFrameTo = 2 * expectedFrame;
}
else {
- stepFrameFrom = 2 * halfFrameCount - 4;
+ stepFrameFrom = 2 * (halfFrameCount - 2);
stepFrameTo = 2 * halfFrameCount - 2;
}
}
if (isUnexpectedFrame) {
- const uint stepSize = (uint)sqrtf(animation->frameCenter(stepFrameFrom).sqrDist(animation->frameCenter(stepFrameTo)));
+ const float stepSize = sqrtf(animation->frameCenter(stepFrameFrom).sqrDist(animation->frameCenter(stepFrameTo)));
_walkedDistance += (int32)(stepSize * _stepSizeFactor);
}
_graphicNormal.frameI() = 2 * expectedFrame; // especially this: wtf?
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 3e5446fb281..6aaf67d3dc4 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -510,15 +510,15 @@ void Graphic::update() {
return;
const uint32 totalDuration = _animation->totalDuration();
- uint32 curTime = _lastTime;
- if (!_isPaused)
- curTime = g_system->getMillis() - curTime;
- if (curTime > totalDuration && totalDuration > 0) {
- if (_isLooping)
+ uint32 curTime = _isPaused
+ ? _lastTime
+ : g_system->getMillis() - _lastTime;
+ if (curTime > totalDuration) {
+ if (_isLooping && totalDuration > 0)
curTime %= totalDuration;
else {
pause();
- curTime = _lastTime = totalDuration - 1;
+ curTime = _lastTime = totalDuration ? totalDuration - 1 : 0;
}
}
Commit: 1fca043e6baa9ad417977a1ca3094feddd3bbe4e
https://github.com/scummvm/scummvm/commit/1fca043e6baa9ad417977a1ca3094feddd3bbe4e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Add letter-boxes
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
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 69c41caf53f..dab2ecfe071 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -21,6 +21,7 @@
#include "global-ui.h"
#include "alcachofa.h"
+#include "script.h"
using namespace Common;
@@ -224,4 +225,19 @@ Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs)
return new CenterBottomTextTask(process, dialogId, durationMs);
}
+void GlobalUI::drawScreenStates() {
+ if (g_engine->player().isOptionsMenuOpen())
+ return;
+
+ auto &drawQueue = g_engine->drawQueue();
+ if (_isPermanentFaded)
+ drawQueue.add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
+ else if (int32 borderWidth = g_engine->script().variable("BordesNegros")) {
+ int16 width = g_system->getWidth();
+ int16 height = g_system->getHeight();
+ drawQueue.add<BorderDrawRequest>(Rect(0, 0, width, borderWidth), kBlack);
+ drawQueue.add<BorderDrawRequest>(Rect(0, height - borderWidth, width, height), kBlack);
+ }
+}
+
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index e5522bc97e3..8892ebf3e27 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -35,12 +35,14 @@ public:
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();
bool updateOpeningInventory();
void updateClosingInventory();
void startClosingInventory();
+ void drawScreenStates(); // black borders and/or permanent fade
private:
Animation *activeAnimation() const;
@@ -57,7 +59,8 @@ private:
bool
_isOpeningInventory = false,
- _isClosingInventory = false;
+ _isClosingInventory = false,
+ _isPermanentFaded = false;
uint32 _timeForInventory = 0;
};
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 6aaf67d3dc4..23074f63dd3 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -22,6 +22,7 @@
#include "graphics.h"
#include "alcachofa.h"
#include "shape.h"
+#include "global-ui.h"
#include "common/system.h"
#include "common/file.h"
@@ -784,7 +785,7 @@ struct FadeTask : public Task {
virtual TaskReturn run() override {
TASK_BEGIN;
if (_permanentFadeAction == PermanentFadeAction::UnsetFaded)
- g_engine->player().setPermanentFade(false);
+ g_engine->globalUI().isPermanentFaded() = false;
_startTime = g_system->getMillis();
while (g_system->getMillis() - _startTime < _duration) {
draw((g_system->getMillis() - _startTime) / (float)_duration);
@@ -792,7 +793,7 @@ struct FadeTask : public Task {
}
draw(1.0f); // so that during a loading lag the screen is completly black/white
if (_permanentFadeAction == PermanentFadeAction::SetFaded)
- g_engine->player().setPermanentFade(true);
+ g_engine->globalUI().isPermanentFaded() = true;
TASK_END;
}
@@ -828,6 +829,19 @@ Task *fade(Process &process, FadeType fadeType,
return new FadeTask(process, fadeType, from, to, duration, easingType, order, permanentFadeAction);
}
+BorderDrawRequest::BorderDrawRequest(Rect rect, Color color)
+ : IDrawRequest(-kForegroundOrderCount)
+ , _rect(rect)
+ , _color(color) {
+}
+
+void BorderDrawRequest::draw() {
+ auto &renderer = g_engine->renderer();
+ renderer.setTexture(nullptr);
+ renderer.setBlendMode(BlendMode::AdditiveAlpha);
+ renderer.quad({ (float)_rect.left, (float)_rect.top }, { (float)_rect.width(), (float)_rect.height() }, _color);
+}
+
DrawQueue::DrawQueue(IRenderer *renderer)
: _renderer(renderer)
, _allocator(1024) {
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 351f5508257..784ec361834 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -412,6 +412,17 @@ Task *fade(Process &process, FadeType fadeType,
int8 order,
PermanentFadeAction permanentFadeAction = PermanentFadeAction::Nothing);
+class BorderDrawRequest : public IDrawRequest {
+public:
+ BorderDrawRequest(Common::Rect rect, Color color);
+
+ virtual void draw() override;
+
+private:
+ Common::Rect _rect;
+ Color _color;
+};
+
class BumpAllocator {
public:
BumpAllocator(size_t pageSize);
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index a78336acaa1..54a1407007f 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -44,11 +44,6 @@ void Player::postUpdate() {
_pressedObject = nullptr;
}
-void Player::drawScreenStates() {
- if (_isPermanentFaded && !_isOptionsMenuOpen)
- g_engine->drawQueue().add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
-}
-
void Player::resetCursor() {
_cursorFrameI = 0;
}
@@ -262,10 +257,6 @@ void Player::triggerDoor(const Door *door) {
g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
}
-void Player::setPermanentFade(bool isFaded) {
- _isPermanentFaded = isFaded;
-}
-
// the last dialog character mechanic seems like a hack in the original engine
// all talking characters (see SayText kernel call) are added to a fixed-size
// rolling queue and stopped upon killProcesses
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 5917cd76a00..638f62bb207 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -48,7 +48,6 @@ public:
void preUpdate();
void postUpdate();
- void drawScreenStates(); // black borders and/or permanent fade
void updateCursor();
void drawCursor(bool forceDefaultCursor = false);
void resetCursor();
@@ -56,7 +55,6 @@ public:
void changeRoomToBeforeInventory();
void triggerObject(ObjectBase *object, const char *action);
void triggerDoor(const Door *door);
- void setPermanentFade(bool isFaded);
void addLastDialogCharacter(Character *character);
void stopLastDialogCharacters();
void setActiveCharacter(MainCharacterKind kind);
@@ -76,8 +74,7 @@ private:
bool
_isOptionsMenuOpen = false,
_isGameLoaded = true,
- _didLoadGlobalRooms = false,
- _isPermanentFaded = false;
+ _didLoadGlobalRooms = false;
Character *_lastDialogCharacters[kMaxLastDialogCharacters] = { nullptr };
int _nextLastDialogCharacter = 0;
};
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 4050227db52..14e0fb71de3 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -150,8 +150,7 @@ void Room::draw() {
g_engine->camera().update();
drawObjects();
world().globalRoom().drawObjects();
- // TODO: Draw black borders
- g_engine->player().drawScreenStates();
+ g_engine->globalUI().drawScreenStates();
g_engine->drawQueue().draw();
drawDebug();
world().globalRoom().drawDebug();
Commit: e1a0e6a15964d583e079eebf2ccd7ef880851b9b
https://github.com/scummvm/scummvm/commit/e1a0e6a15964d583e079eebf2ccd7ef880851b9b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix floor shapes with lines
Changed paths:
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 44a3d70f88d..020a1c6c59a 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -44,6 +44,7 @@ Console::Console() : GUI::Debugger() {
registerCmd("drop", WRAP_METHOD(Console, cmdItem));
registerCmd("debugMode", WRAP_METHOD(Console, cmdDebugMode));
registerCmd("tp", WRAP_METHOD(Console, cmdTeleport));
+ registerCmd("toggleRoomFloor", WRAP_METHOD(Console, cmdToggleRoomFloor));
}
Console::~Console() {
@@ -251,7 +252,7 @@ bool Console::cmdTeleport(int argc, const char **args) {
param = (int32)strtol(args[1], &end, 10);
if (end == nullptr || *end != '\0')
{
- debugPrintf("Character kind can only be integer");
+ debugPrintf("Character kind can only be integer\n");
return true;
}
}
@@ -260,4 +261,16 @@ bool Console::cmdTeleport(int argc, const char **args) {
return false;
}
+bool Console::cmdToggleRoomFloor(int argc, const char **args) {
+ auto room = g_engine->player().currentRoom();
+ if (room == nullptr)
+ {
+ debugPrintf("No room is active");
+ return true;
+ }
+
+ room->toggleActiveFloor();
+ return false;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index da464885754..39376311cbb 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -56,6 +56,7 @@ private:
bool cmdItem(int argc, const char **args);
bool cmdDebugMode(int argc, const char **args);
bool cmdTeleport(int argc, const char **args);
+ bool cmdToggleRoomFloor(int argc, const char **args);
bool _showInteractables = false;
bool _showCharacters = false;
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 65dae596388..e4ed671e3ad 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -54,10 +54,24 @@ static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
}
}
+EdgeDistances::EdgeDistances(Point edgeA, Point edgeB, Point query) {
+ Vector2d
+ a = as2D(edgeA),
+ b = as2D(edgeB),
+ q = as2D(query);
+ float edgeLength = a.getDistanceTo(b);
+ Vector2d edgeDir = (b - a) / edgeLength;
+ Vector2d edgeNormal(-edgeDir.getY(), edgeDir.getX());
+ _edgeLength = edgeLength;
+ _onEdge = edgeDir.dotProduct(q - a);
+ _toEdge = abs(edgeNormal.dotProduct(q) - edgeNormal.dotProduct(a));
+}
+
bool Polygon::contains(Point query) const {
switch (_points.size()) {
case 0: return false;
case 1: return query == _points[0];
+ case 2: return edgeDistances(0, query)._toEdge < 2.0f;
default:
// we assume that the polygon is convex
for (uint i = 1; i < _points.size(); i++) {
@@ -77,18 +91,7 @@ bool Polygon::intersectsEdge(uint startPointI, Point a, Point b) const {
EdgeDistances Polygon::edgeDistances(uint startPointI, Point query) const {
assert(startPointI < _points.size());
uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
- Vector2d
- a = as2D(_points[startPointI]),
- b = as2D(_points[endPointI]),
- q = as2D(query);
- float edgeLength = a.getDistanceTo(b);
- Vector2d edgeDir = (b - a) / edgeLength;
- Vector2d edgeNormal(-edgeDir.getY(), edgeDir.getX());
- EdgeDistances distances;
- distances._edgeLength = edgeLength;
- distances._onEdge = edgeDir.dotProduct(q - a);
- distances._toEdge = abs(edgeNormal.dotProduct(q) - edgeNormal.dotProduct(a));
- return distances;
+ return EdgeDistances(_points[startPointI], _points[endPointI], query);
}
static Point wiggleOnToLine(Point a, Point b, Point q) {
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 2fae40a5ac9..4174aabc140 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -35,6 +35,8 @@
namespace Alcachofa {
struct EdgeDistances {
+ EdgeDistances(Common::Point edgeA, Common::Point edgeB, Common::Point query);
+
float _edgeLength;
float _onEdge;
float _toEdge;
Commit: 5c578daf717145f8ac57b89db412577a3a209193
https://github.com/scummvm/scummvm/commit/5c578daf717145f8ac57b89db412577a3a209193
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Fix TGADecoder changes after rebase
Changed paths:
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 23074f63dd3..4c0a62dac62 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -182,8 +182,9 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
if (source->w == 2 && source->h == 1)
return nullptr;
+ const auto &palette = decoder.getPalette();
auto target = new ManagedSurface();
- target->setPalette(decoder.getPalette(), 0, decoder.getPaletteColorCount());
+ target->setPalette(palette.data(), 0, palette.size());
target->convertFrom(*source, BlendBlit::getSupportedPixelFormat());
return target;
}
Commit: d2c05d444ab69015c06d9686c19cfbc6dcd3c08d
https://github.com/scummvm/scummvm/commit/d2c05d444ab69015c06d9686c19cfbc6dcd3c08d
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:54+02:00
Commit Message:
ALCACHOFA: Add camera tasks for inactive player
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index f40a803229f..222b7a71e8c 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -349,12 +349,61 @@ private:
Camera &_camera;
};
+struct CamSetInactiveAttributeTask final : public Task {
+ enum Attribute {
+ kPosZ,
+ kScale,
+ kRotation
+ };
+
+ CamSetInactiveAttributeTask(Process &process, Attribute attribute, float value, int32 delay)
+ : Task(process)
+ , _camera(g_engine->camera())
+ , _attribute(attribute)
+ , _value(value)
+ , _delay(delay) {}
+
+ virtual TaskReturn run() override {
+ if (_delay > 0) {
+ uint32 delay = (uint32)_delay;
+ _delay = 0;
+ return TaskReturn::waitFor(new DelayTask(process(), delay));
+ }
+
+ auto &state = _camera._backups[0];
+ switch (_attribute) {
+ case kPosZ: state._usedCenter.z() = _value; break;
+ case kScale: state._scale = _value; break;
+ case kRotation: state._rotation = _value; break;
+ default:
+ warning("Unknown CamSetInactiveAttribute attribute: %d", (int)_attribute);
+ break;
+ }
+ }
+
+ virtual void debugPrint() override {
+ const char *attributeName;
+ switch (_attribute) {
+ case kPosZ: attributeName = "PosZ"; break;
+ case kScale: attributeName = "Scale"; break;
+ case kRotation: attributeName = "Rotation"; break;
+ default: attributeName = "<unknown>"; break;
+ }
+ g_engine->console().debugPrintf("Set inactive camera %s to %f after %dms\n", attributeName, _value, _delay);
+ }
+
+private:
+ Camera &_camera;
+ Attribute _attribute;
+ float _value;
+ int32 _delay;
+};
+
Task *Camera::lerpPos(Process &process,
Vector2d targetPos,
int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
- warning("stub: non-active camera lerp script invoked");
- return new DelayTask(process, duration);
+ return new DelayTask(process, duration); // lerpPos does not handle inactive players
}
Vector3d targetPos3d(targetPos.getX(), targetPos.getY(), _appliedCenter.z());
return new CamLerpPosTask(process, targetPos3d, duration, easingType);
@@ -364,8 +413,7 @@ Task *Camera::lerpPos(Process &process,
Vector3d targetPos,
int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
- warning("stub: non-active camera lerp script invoked");
- return new DelayTask(process, duration);
+ return new DelayTask(process, duration); // lerpPos does not handle inactive players
}
setFollow(nullptr); // 3D position lerping is the only task that resets following
return new CamLerpPosTask(process, targetPos, duration, easingType);
@@ -375,8 +423,7 @@ Task *Camera::lerpPosZ(Process &process,
float targetPosZ,
int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
- warning("stub: non-active camera lerp script invoked");
- return new DelayTask(process, duration);
+ return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kPosZ, targetPosZ, duration);
}
Vector3d targetPos(_appliedCenter.x(), _appliedCenter.y(), targetPosZ);
return new CamLerpPosTask(process, targetPos, duration, easingType);
@@ -386,8 +433,7 @@ Task *Camera::lerpScale(Process &process,
float targetScale,
int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
- warning("stub: non-active camera lerp script invoked");
- return new DelayTask(process, duration);
+ return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kScale, targetScale, duration);
}
return new CamLerpScaleTask(process, targetScale, duration, easingType);
}
@@ -396,8 +442,7 @@ Task *Camera::lerpRotation(Process &process,
float targetRotation,
int32 duration, EasingType easingType) {
if (!process.isActiveForPlayer()) {
- warning("stub: non-active camera lerp script invoked");
- return new DelayTask(process, duration);
+ return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kRotation, targetRotation, duration);
}
return new CamLerpRotationTask(process, targetRotation, duration, easingType);
}
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index e9f6eaa8411..a1f8eebe645 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -81,6 +81,7 @@ private:
friend struct CamLerpRotationTask;
//friend struct CamShakeTask;
friend struct CamWaitToStopTask;
+ friend struct CamSetInactiveAttributeTask;
Math::Vector3d setAppliedCenter(Math::Vector3d center);
void setupMatricesAround(Math::Vector3d center);
void updateFollowing(float deltaTime);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 80810fd0c59..1783b27bde2 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -564,9 +564,6 @@ private:
else
g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
return TaskReturn::finish(1);
- case ScriptKernelTask::LerpWorldLodBias:
- warning("STUB KERNEL CALL: LerpWorldLodBias");
- return TaskReturn::finish(0);
// object control / animation
case ScriptKernelTask::On:
@@ -733,9 +730,6 @@ private:
return TaskReturn::finish(1);
// Camera tasks
- case ScriptKernelTask::SetMaxCamSpeedFactor:
- warning("STUB KERNEL CALL: SetMaxCamSpeedFactor");
- return TaskReturn::finish(0);
case ScriptKernelTask::WaitCamStopping:
return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
case ScriptKernelTask::CamFollow:
@@ -787,13 +781,15 @@ private:
getNumberArg(1), (EasingType)getNumberArg(2)));
}
case ScriptKernelTask::LerpCamToObjectWithScale: {
+ float targetScale = getNumberArg(1) * 0.01f;
if (!process().isActiveForPlayer())
- return TaskReturn::waitFor(delay(getNumberArg(2)));
+ // 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)
error("Invalid target object for LerpCamToObjectWithScale: %s", getStringArg(0));
return TaskReturn::waitFor(g_engine->camera().lerpPosScale(process(),
- as3D(pointObject->position()), getNumberArg(1) * 0.01f,
+ as3D(pointObject->position()), targetScale,
getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
}
@@ -823,7 +819,13 @@ private:
1.0f, 0.0f, getNumberArg(0), (EasingType)getNumberArg(1), -5,
PermanentFadeAction::SetFaded));
- // Unused and useless
+ // Unused and/or useless
+ case ScriptKernelTask::SetMaxCamSpeedFactor:
+ warning("STUB KERNEL CALL: SetMaxCamSpeedFactor");
+ return TaskReturn::finish(0);
+ case ScriptKernelTask::LerpWorldLodBias:
+ warning("STUB KERNEL CALL: LerpWorldLodBias");
+ return TaskReturn::finish(0);
case ScriptKernelTask::SetActiveTextureSet:
// Fortunately this seems to be unused.
warning("STUB KERNEL CALL: SetActiveTextureSet");
Commit: e0145237117be364ea40a0197dcd661a0f5cae70
https://github.com/scummvm/scummvm/commit/e0145237117be364ea40a0197dcd661a0f5cae70
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Add kernel task CamShake
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 222b7a71e8c..5f9c6d6db4f 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -253,10 +253,11 @@ struct CamLerpTask : public Task {
uint32 remaining = g_system->getMillis() - _startTime <= _duration
? _duration - (g_system->getMillis() - _startTime)
: 0;
- g_engine->console().debugPrintf("Lerp camera with %ums remaining\n", remaining);
+ g_engine->console().debugPrintf("%s camera with %ums remaining\n", taskName(), remaining);
}
protected:
+ virtual const char *taskName() const = 0;
virtual void update(float t) = 0;
Camera &_camera;
@@ -271,6 +272,10 @@ struct CamLerpPosTask final : public CamLerpTask {
, _deltaPos(targetPos - _camera._appliedCenter) {}
protected:
+ virtual const char *taskName() const {
+ return "Lerp pos of";
+ }
+
virtual void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * t);
}
@@ -285,6 +290,10 @@ struct CamLerpScaleTask final : public CamLerpTask {
, _deltaScale(targetScale - _camera._cur._scale) {}
protected:
+ virtual const char *taskName() const {
+ return "Lerp scale of";
+ }
+
virtual void update(float t) override {
_camera._cur._scale = _fromScale + _deltaScale * t;
}
@@ -306,6 +315,10 @@ struct CamLerpPosScaleTask final : public CamLerpTask {
, _scaleEasingType(scaleEasingType) {}
protected:
+ virtual const char *taskName() const {
+ return "Lerp pos and scale of";
+ }
+
virtual void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * ease(t, _moveEasingType));
_camera._cur._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
@@ -323,6 +336,10 @@ struct CamLerpRotationTask final : public CamLerpTask {
, _deltaRotation(targetRotation - _camera._cur._rotation.getDegrees()) {}
protected:
+ virtual const char *taskName() const {
+ return "Lerp rotation of";
+ }
+
virtual void update(float t) override {
_camera._cur._rotation = Angle(_fromRotation + _deltaRotation * t);
}
@@ -330,6 +347,30 @@ protected:
float _fromRotation, _deltaRotation;
};
+struct CamShakeTask final : public CamLerpTask {
+ CamShakeTask(Process &process, Vector2d amplitude, Vector2d frequency, int32 duration)
+ : CamLerpTask(process, duration, EasingType::Linear)
+ , _amplitude(amplitude)
+ , _frequency(frequency) { }
+
+protected:
+ virtual const char *taskName() const {
+ return "Shake";
+ }
+
+ virtual void update(float t) override {
+ const Vector2d phase = _frequency * t * (float)M_PI * 2.0f;
+ const float amplTimeFactor = 1.0f / expf(t * 5.0f); // a curve starting at 1, depreciating towards 0
+ _camera.shake() = {
+ sinf(phase.getX()) * _amplitude.getX() * amplTimeFactor,
+ sinf(phase.getY()) * _amplitude.getY() * amplTimeFactor
+ };
+ }
+
+ Vector2d _amplitude, _frequency;
+
+};
+
struct CamWaitToStopTask final : public Task {
CamWaitToStopTask(Process &process)
: Task(process)
@@ -379,6 +420,7 @@ struct CamSetInactiveAttributeTask final : public Task {
warning("Unknown CamSetInactiveAttribute attribute: %d", (int)_attribute);
break;
}
+ return TaskReturn::finish(0);
}
virtual void debugPrint() override {
@@ -452,8 +494,7 @@ Task *Camera::lerpPosScale(Process &process,
int32 duration,
EasingType moveEasingType, EasingType scaleEasingType) {
if (!process.isActiveForPlayer()) {
- warning("stub: non-active camera lerp script invoked");
- return new DelayTask(process, duration);
+ return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kScale, targetScale, duration);
}
return new CamLerpPosScaleTask(process, targetPos, targetScale, duration, moveEasingType, scaleEasingType);
}
@@ -462,4 +503,11 @@ Task *Camera::waitToStop(Process &process) {
return new CamWaitToStopTask(process);
}
+Task *Camera::shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration) {
+ if (!process.isActiveForPlayer()) {
+ return new DelayTask(process, (uint32)duration);
+ }
+ return new CamShakeTask(process, amplitude, frequency, duration);
+}
+
} // namespace Alcachofa
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index a1f8eebe645..6e18e069003 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -70,8 +70,8 @@ public:
Task *lerpPosScale(Process &process,
Math::Vector3d targetPos, float targetScale,
int32 duration, EasingType moveEasingType, EasingType scaleEasingType);
- //Task *shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration);
Task *waitToStop(Process &process);
+ Task *shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration);
private:
friend struct CamLerpTask;
@@ -79,7 +79,7 @@ private:
friend struct CamLerpScaleTask;
friend struct CamLerpPosScaleTask;
friend struct CamLerpRotationTask;
- //friend struct CamShakeTask;
+ friend struct CamShakeTask;
friend struct CamWaitToStopTask;
friend struct CamSetInactiveAttributeTask;
Math::Vector3d setAppliedCenter(Math::Vector3d center);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 1783b27bde2..1164cded5a3 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -738,8 +738,10 @@ private:
getNumberArg(1) != 0);
return TaskReturn::finish(1);
case ScriptKernelTask::CamShake:
- warning("STUB KERNEL CALL: CamShake");
- return TaskReturn::finish(0);
+ return TaskReturn::waitFor(g_engine->camera().shake(process(),
+ Vector2d(getNumberArg(1), getNumberArg(2)),
+ Vector2d(getNumberArg(3), getNumberArg(4)),
+ getNumberArg(0)));
case ScriptKernelTask::LerpCamXY:
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
Vector2d(getNumberArg(0), getNumberArg(1)),
Commit: df191096d2176cbbdbbf2404e04ee488f8bb3658
https://github.com/scummvm/scummvm/commit/df191096d2176cbbdbbf2404e04ee488f8bb3658
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Clear heldItem on clearInventory
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 8a75065f218..55d947be7d8 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -905,7 +905,8 @@ void MainCharacter::serializeSave(Serializer &serializer) {
void MainCharacter::clearInventory() {
for (auto *item : _items)
item->toggle(false);
- // TODO: Clear held item on clearInventory
+ if (g_engine->player().activeCharacter() == this)
+ g_engine->player().heldItem() = nullptr;
g_engine->world().inventory().updateItemsByActiveCharacter();
}
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 784ec361834..ac8f80db2b7 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -386,7 +386,7 @@ private:
enum class FadeType {
ToBlack,
ToWhite
- // TODO: Add CrossFade fade type
+ // Originally there was a CrossFade, but it is unused for now and thus not implemented
};
enum class PermanentFadeAction {
Commit: 22596145424151ec1e222cff5dca21c2d79878a6
https://github.com/scummvm/scummvm/commit/22596145424151ec1e222cff5dca21c2d79878a6
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Refactor error handling
Changed paths:
A engines/alcachofa/game-movie-adventure.cpp
A engines/alcachofa/game.cpp
A engines/alcachofa/game.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/camera.cpp
engines/alcachofa/detection.cpp
engines/alcachofa/detection.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/metaengine.cpp
engines/alcachofa/module.mk
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index df88a8a776a..c2f55cd00e1 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -19,10 +19,6 @@
*
*/
-#include "alcachofa/alcachofa.h"
-#include "graphics/framelimiter.h"
-#include "alcachofa/detection.h"
-#include "alcachofa/console.h"
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
@@ -33,10 +29,14 @@
#include "graphics/framelimiter.h"
#include "video/mpegps_decoder.h"
+#include "alcachofa.h"
+#include "console.h"
+#include "detection.h"
#include "rooms.h"
#include "script.h"
#include "global-ui.h"
#include "debug.h"
+#include "game.h"
using namespace Math;
@@ -65,6 +65,7 @@ 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)));
_drawQueue.reset(new DrawQueue(_renderer.get()));
_world.reset(new World());
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 84c06c4ae10..5d6ceb64cbd 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -41,6 +41,7 @@
#include "alcachofa/player.h"
#include "alcachofa/scheduler.h"
#include "alcachofa/console.h"
+#include "alcachofa/game.h"
namespace Alcachofa {
@@ -50,6 +51,7 @@ class DrawQueue;
class World;
class Script;
class GlobalUI;
+class Game;
struct AlcachofaGameDescription;
class AlcachofaEngine : public Engine {
@@ -74,6 +76,7 @@ public:
inline GlobalUI &globalUI() { return *_globalUI; }
inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
+ inline Game &game() { return *_game; }
inline bool isDebugModeActive() const { return _debugHandler != nullptr; }
void playVideo(int32 videoId);
@@ -131,6 +134,7 @@ private:
Common::ScopedPtr<Script> _script;
Common::ScopedPtr<Player> _player;
Common::ScopedPtr<GlobalUI> _globalUI;
+ Common::ScopedPtr<Game> _game;
Camera _camera;
Input _input;
Sounds _sounds;
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 5f9c6d6db4f..caa76b97db8 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -417,7 +417,7 @@ struct CamSetInactiveAttributeTask final : public Task {
case kScale: state._scale = _value; break;
case kRotation: state._rotation = _value; break;
default:
- warning("Unknown CamSetInactiveAttribute attribute: %d", (int)_attribute);
+ g_engine->game().unknownCamSetInactiveAttribute((int)_attribute);
break;
}
return TaskReturn::finish(0);
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index 1d0c5a94ad9..ce292b94454 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -32,6 +32,7 @@
const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
{ Alcachofa::kDebugGraphics, "Graphics", "Graphics debug level" },
{ Alcachofa::kDebugScript, "Script", "Enable debug script dump" },
+ { Alcachofa::kDebugGameplay, "Gameplay", "Gameplay-related tracing" },
DEBUG_CHANNEL_END
};
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 96a07feed09..20a4f8bfa59 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -29,6 +29,7 @@ namespace Alcachofa {
enum AlcachofaDebugChannels {
kDebugGraphics = 1,
kDebugScript,
+ kDebugGameplay
};
extern const PlainGameDescriptor alcachofaGames[];
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
new file mode 100644
index 00000000000..281a45adf44
--- /dev/null
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -0,0 +1,143 @@
+/* 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.h"
+#include "game.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+class GameMovieAdventure : public Game {
+ virtual bool doesRoomHaveBackground(const Room *room) override {
+ return !room->name().equalsIgnoreCase("Global") &&
+ !room->name().equalsIgnoreCase("HABITACION_NEGRA");
+ }
+
+ virtual void invalidDialogLine(uint index) override {
+ if (index != 4542)
+ Game::invalidDialogLine(index);
+ }
+
+ virtual bool shouldCharacterTrigger(const Character *character, const char *action) override {
+ // An original hack to check that bed sheet is used on the other main character only in the correct room
+ // There *is* another script variable (es_casa_freddy) that should check this
+ // but, I guess, Alcachofa Soft found a corner case where this does not work?
+ if (scumm_stricmp(action, "iSABANA") == 0 &&
+ dynamic_cast<const MainCharacter *>(character) != nullptr &&
+ !character->room()->name().equalsIgnoreCase("CASA_FREDDY_ARRIBA")) {
+ return false;
+ }
+
+ return Game::shouldCharacterTrigger(character, action);
+ }
+
+ virtual bool shouldTriggerDoor(const Door *door) {
+ if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
+ return false;
+ return Game::shouldTriggerDoor(door);
+ }
+
+ virtual bool hasMortadeloVoice(const Character *character) override {
+ return Game::hasMortadeloVoice(character) ||
+ character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
+ }
+
+ virtual 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",
+ nullptr
+ };
+ for (const char **exemption = exemptions; *exemption != nullptr; exemption++) {
+ if (fileName.equalsIgnoreCase(*exemption)) {
+ debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
+ return;
+ }
+ }
+ Game::missingAnimation(fileName);
+ }
+
+ virtual void unknownAnimateObject(const char *name) override {
+ if (!scumm_stricmp("EXPLOSION DISFRAZ", name))
+ return;
+ Game::unknownAnimateObject(name);
+ }
+
+ virtual PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
+ if (scumm_stricmp(action, "put"))
+ return Game::unknownGoPutTarget(process, action, name);
+
+ if (!scumm_stricmp("A_Poblado_Indio", name)) {
+ // A_Poblado_Indio is a Door but is originally cast into a PointObject
+ // a pointer and the draw order is then interpreted as position and the character snapped onto the floor shape.
+ // Instead I just use the A_Poblado_Indio1 object which exists as counter-part for A_Poblado_Indio2 which should have been used
+ auto target = dynamic_cast<PointObject *>(
+ g_engine->world().getObjectByName(process.character(), "A_Poblado_Indio1"));
+ if (target == nullptr)
+ _message("Unknown put target A_Poblado_Indio1 during exemption for A_Poblado_Indio");
+ return target;
+ }
+
+ if (!scumm_stricmp("PUNTO_VENTANA", name)) {
+ // The object is in the previous, now inactive room.
+ // Luckily Mortadelo already is at that point so not further action required
+ return nullptr;
+ }
+
+ if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", name)) {
+ // Another case of a door being cast into a PointObject
+ return nullptr;
+ }
+
+ return Game::unknownGoPutTarget(process, action, name);
+ }
+
+ virtual void unknownSayTextCharacter(const char *name, int32 dialogId) override {
+ if (!scumm_stricmp(name, "OFELIA") && dialogId == 3737)
+ return;
+ Game::unknownSayTextCharacter(name, dialogId);
+ }
+
+ virtual void unknownAnimateCharacterObject(const char *name) override {
+ if (!scumm_stricmp(name, "COGE F DCH") || // original bug in MOTEL_ENTRADA
+ !scumm_stricmp(name, "CHIQUITO_IZQ"))
+ return;
+ Game::unknownAnimateCharacterObject(name);
+ }
+
+ virtual void missingSound(const String &fileName) override {
+ if (fileName == "CHAS" || fileName == "517")
+ return;
+ Game::missingSound(fileName);
+ }
+};
+
+Game *Game::createForMovieAdventure() {
+ return new GameMovieAdventure();
+}
+
+}
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 55d947be7d8..67263acaf65 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -267,7 +267,7 @@ void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object)
if (object == nullptr)
object = room()->world().getObjectByName(name.c_str());
if (object == nullptr)
- error("Invalid object name \"%s\" saved for \"%s\" in \"%s\"",
+ g_engine->game().unknownSerializedObject(
name.c_str(), this->name().c_str(), room()->name().c_str());
}
}
@@ -280,15 +280,8 @@ void Character::onClick() {
void Character::trigger(const char *action) {
g_engine->player().activeCharacter()->stopWalking(_interactionDirection);
- if (scumm_stricmp(action, "iSABANA") == 0 &&
- dynamic_cast<MainCharacter *>(this) != nullptr &&
- !room()->name().equalsIgnoreCase("CASA_FREDDY_ARRIBA")) {
- // An original hack to check that we use the bed sheet on the main character only in the correct room
- // There *is* another script variable (es_casa_freddy) that should check this
- // but, I guess, Alcachofa Soft found a corner case where this does not work?
- return;
- }
- g_engine->player().triggerObject(this, action);
+ if (g_engine->game().shouldCharacterTrigger(this, action))
+ g_engine->player().triggerObject(this, action);
}
struct SayTextTask final : public Task {
@@ -307,11 +300,9 @@ struct SayTextTask final : public Task {
if (_soundId == kInvalidSoundID)
{
- bool isMortadeloVoice =
- _character == &g_engine->world().mortadelo() ||
- _character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
+ bool hasMortadeloVoice = g_engine->game().hasMortadeloVoice(_character);
_soundId = g_engine->sounds().playVoice(
- String::format(isMortadeloVoice ? "M%04d" : "%04d", _dialogId),
+ String::format(hasMortadeloVoice ? "M%04d" : "%04d", _dialogId),
0);
}
g_engine->sounds().setAppropriateVolume(_soundId, process().character(), _character);
@@ -885,6 +876,7 @@ void MainCharacter::serializeSave(Serializer &serializer) {
if (serializer.isLoading()) {
room() = room()->world().getRoomByName(roomName.c_str());
if (room() == nullptr)
+ // no good way to recover from this
error("Invalid room name \"%s\" saved for \"%s\"", roomName.c_str(), name().c_str());
}
@@ -925,8 +917,10 @@ bool MainCharacter::hasItem(const String &name) const {
void MainCharacter::pickup(const String &name, bool putInHand) {
auto item = getItemByName(name);
- if (item == nullptr)
- error("Tried to pickup unknown item: %s", name.c_str());
+ if (item == nullptr) {
+ g_engine->game().unknownPickupItem(name.c_str());
+ return;
+ }
item->toggle(true);
if (g_engine->player().activeCharacter() == this) {
if (putInHand)
@@ -939,8 +933,9 @@ void MainCharacter::drop(const Common::String &name) {
if (!name.empty()) {
auto item = getItemByName(name);
if (item == nullptr)
- error("Tried to drop unknown item: %s", name.c_str());
- item->toggle(false);
+ g_engine->game().unknownDropItem(name.c_str());
+ else
+ item->toggle(false);
}
if (g_engine->player().activeCharacter() == this) {
g_engine->player().heldItem() = nullptr;
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
new file mode 100644
index 00000000000..59bcee82bf0
--- /dev/null
+++ b/engines/alcachofa/game.cpp
@@ -0,0 +1,182 @@
+/* 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.h"
+#include "game.h"
+#include "script.h"
+
+using namespace Common;
+
+namespace Alcachofa {
+
+Game::Game()
+#ifdef _DEBUG // During development let's check out these errors more carefully
+ : _message(error)
+#else // For release builds the game might still work or the user might still be able to save and restart
+ : _message(warning)
+#endif
+{
+}
+
+bool Game::doesRoomHaveBackground(const Room *room) {
+ return true;
+}
+
+void Game::unknownRoomObject(const String &type) {
+ _message("Unknown type for room object: %s", type.c_str());
+}
+
+void Game::unknownDoorTargetRoom(const String &name) {
+ _message("Unknown door target room: %s", name.c_str());
+}
+
+void Game::unknownDoorTargetDoor(const String &room, const String &door) {
+ _message("Unknown door target door: %s in %s", door.c_str(), room.c_str());
+}
+
+void Game::invalidDialogLine(uint index) {
+ _message("Invalid dialog line %u");
+}
+
+void Game::tooManyDialogLines(uint lineCount, uint maxLineCount) {
+ // we set max line count as constant, if some game uses more we just have to adapt the constant
+ // the bug will be not all dialog lines being rendered
+ _message("Text to be rendered has too many lines (%u), check text validity and max line count (%u)", lineCount, maxLineCount);
+}
+
+void Game::tooManyDrawRequests(int order) {
+ // similar, the bug will be some objects not being rendered
+ _message("Too many draw requests in order %d", order);
+}
+
+bool Game::shouldCharacterTrigger(const Character *character, const char *action) {
+ return true;
+}
+
+bool Game::shouldTriggerDoor(const Door *door) {
+ return true;
+}
+
+bool Game::hasMortadeloVoice(const Character *character) {
+ return character == &g_engine->world().mortadelo();
+}
+
+void Game::unknownCamSetInactiveAttribute(int attribute) {
+ // this will be a bug by us, but gameplay should not be affected, so don't error in release builds
+ // it could still happen if an attribute was added/removed in updates so we still want users to report this
+ _message("Unknown CamSetInactiveAttribute attribute: %d", attribute);
+}
+
+void Game::unknownFadeType(int fadeType) {
+ _message("Unknown fade type %d", fadeType);
+}
+
+void Game::unknownSerializedObject(const char *object, const char *owner, const char *room) {
+ // potentially game-breaking for _currentlyUsingObject but should otherwise be just a graphical bug
+ _message("Invalid object name \"%s\" saved for \"%s\" in \"%s\"", object, owner, room);
+}
+
+void Game::unknownPickupItem(const char *name) {
+ _message("Tried to pickup unknown item: %s", name);
+}
+
+void Game::unknownDropItem(const char *name) {
+ _message("Tried to drop unknown item: %s", name);
+}
+
+void Game::unknownVariable(const char *name) {
+ _message("Unknown script variable: %s", name);
+}
+
+void Game::unknownInstruction(const ScriptInstruction &instruction) {
+ const char *type =
+ instruction._op == ScriptOp::Crash5 || // these are defined in the game
+ instruction._op == ScriptOp::Crash8 || // but implemented as crashes
+ instruction._op == ScriptOp::Crash9 ||
+ instruction._op == ScriptOp::Crash12 ||
+ instruction._op == ScriptOp::Crash21 ||
+ instruction._op == ScriptOp::Crash22 ||
+ instruction._op == ScriptOp::Crash33 ||
+ instruction._op == ScriptOp::Crash34 ||
+ instruction._op == ScriptOp::Crash35 ||
+ instruction._op == ScriptOp::Crash36
+ ? "crash" : "invalid";
+ _message("Script reached %s instruction: %d %d", type, (int)instruction._op, instruction._arg);
+}
+
+void Game::unknownAnimateObject(const char *name) {
+ _message("Script tried to animated invalid graphic object: %s", name);
+}
+
+void Game::unknownScriptCharacter(const char *action, const char *name) {
+ _message("Script tried to %s using invalid character: %s", action, name);
+}
+
+PointObject *Game::unknownGoPutTarget(const Process &process, const char *action, const char *name) {
+ _message("Script tried to make character %s to invalid object %s", action, name);
+ return nullptr;
+}
+
+void Game::missingAnimation(const String &fileName) {
+ _message("Could not open animation %s", fileName.c_str());
+}
+
+void Game::unknownSayTextCharacter(const char *name, int32) {
+ unknownScriptCharacter("say text", name);
+}
+
+void Game::unknownChangeCharacterRoom(const char *name) {
+ _message("Invalid change character room name: %s", name);
+}
+
+void Game::unknownAnimateCharacterObject(const char *name) {
+ _message("Invalid animate character object: %s", name);
+}
+
+void Game::unknownAnimateTalkingObject(const char *name) {
+ _message("Invalid talk object name: %s", name);
+}
+
+void Game::unknownClearInventoryTarget(int characterKind) {
+ _message("Invalid clear inventory character kind: %d", characterKind);
+}
+
+void Game::unknownCamLerpTarget(const char *action, const char *name) {
+ _message("Invalid target object for %s: %s", action, name);
+}
+
+void Game::unknownKernelTask(int task) {
+ _message("Invalid kernel task: %d", task);
+}
+
+void Game::unknownScriptProcedure(const String &procedure) {
+ _message("Unknown required procedure: %s", procedure.c_str());
+}
+
+void Game::missingSound(const String &fileName) {
+ _message("Missing sound file: %s", fileName.c_str());
+}
+
+void Game::invalidSNDFormat(uint format, uint channels, uint freq, uint bps) {
+ _message("Invalid SND file, format: %u, channels: %u, freq: %u, bps: %u", format, channels, freq, bps);
+}
+
+}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
new file mode 100644
index 00000000000..94b6bc64c83
--- /dev/null
+++ b/engines/alcachofa/game.h
@@ -0,0 +1,94 @@
+#pragma once
+/* 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/>.
+ *
+ */
+
+#ifndef GAME_H
+#define GAME_H
+
+#include "common/textconsole.h"
+#include "common/file.h"
+
+namespace Alcachofa {
+
+class ObjectBase;
+class PointObject;
+class Character;
+class Door;
+class Room;
+class Process;
+struct ScriptInstruction;
+
+/**
+ * @brief Provides functionality specific to a game title.
+ * 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
+ */
+class Game {
+ typedef void (*Message)(const char *s, ...);
+public:
+ Game();
+ virtual ~Game() = default;
+
+ virtual bool doesRoomHaveBackground(const Room *room);
+ virtual void unknownRoomObject(const Common::String &type);
+ virtual void unknownDoorTargetRoom(const Common::String &name);
+ virtual void unknownDoorTargetDoor(const Common::String &room, const Common::String &door);
+
+ virtual void invalidDialogLine(uint index);
+ virtual void tooManyDialogLines(uint lineCount, uint maxLineCount);
+ virtual void tooManyDrawRequests(int order);
+
+ virtual bool shouldCharacterTrigger(const Character *character, const char *action);
+ virtual bool shouldTriggerDoor(const Door *door);
+ virtual bool hasMortadeloVoice(const Character *character);
+
+ virtual void unknownCamSetInactiveAttribute(int attribute);
+ virtual void unknownFadeType(int fadeType);
+ virtual void unknownSerializedObject(const char *object, const char *owner, const char *room);
+ virtual void unknownPickupItem(const char *name);
+ virtual void unknownDropItem(const char *name);
+ virtual void unknownVariable(const char *name);
+ virtual void unknownInstruction(const ScriptInstruction &instruction);
+ virtual void unknownAnimateObject(const char *name);
+ virtual void unknownScriptCharacter(const char *action, const char *name);
+ 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 void unknownAnimateTalkingObject(const char *name);
+ virtual void unknownClearInventoryTarget(int characterKind);
+ virtual void unknownCamLerpTarget(const char *action, const char *name);
+ virtual void unknownKernelTask(int task);
+ virtual void unknownScriptProcedure(const Common::String &procedure);
+
+ virtual void missingAnimation(const Common::String &fileName);
+ virtual void missingSound(const Common::String &fileName);
+ virtual void invalidSNDFormat(uint format, uint channels, uint freq, uint bps);
+
+ static Game *createForMovieAdventure();
+
+ const Message _message;
+};
+
+}
+
+#endif // GAME_H
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 4c0a62dac62..9102d10868b 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -191,14 +191,7 @@ ManagedSurface *AnimationBase::readImage(SeekableReadStream &stream) const {
void AnimationBase::loadMissingAnimation() {
// only allow missing animations we know are faulty in the original game
- if (!_fileName.equalsIgnoreCase("ANIMACION.AN0") &&
- !_fileName.equalsIgnoreCase("DESPACHO_SUPER2_OL_SOMBRAS2.AN0") &&
- !_fileName.equalsIgnoreCase("PP_MORTA.AN0") &&
- !_fileName.equalsIgnoreCase("DESPACHO_SUPER2___FONDO_PP_SUPER.AN0") &&
- !_fileName.equalsIgnoreCase("ESTOMAGO.AN0") &&
- !_fileName.equalsIgnoreCase("CREDITOS.AN0") &&
- !_fileName.equalsIgnoreCase("MONITOR___OL_EFECTO_FONDO.AN0"))
- error("Could not open animation %s", _fileName.c_str());
+ g_engine->game().missingAnimation(_fileName);
// otherwise setup a functioning but empty animation
_isLoaded = true;
@@ -444,7 +437,7 @@ void Font::load() {
}
_texture = g_engine->renderer().createTexture(atlasSurface.w, atlasSurface.h, false);
_texture->update(atlasSurface);
- debug("Rendered font atlas %s at %dx%d", _fileName.c_str(), atlasSurface.w, atlasSurface.h);
+ debugCN(1, kDebugGraphics, "Rendered font atlas %s at %dx%d", _fileName.c_str(), atlasSurface.w, atlasSurface.h);
}
void Font::freeImages() {
@@ -660,8 +653,10 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
const byte *itChar = (byte *)text, *itLine = (byte *)text, *textEnd = itChar + textLen + 1;
int lineWidth = 0;
while (true) {
- if (lineCount >= kMaxLines)
- error("Text to be rendered has too many lines, check text validity and max line count");
+ if (lineCount >= kMaxLines) {
+ g_engine->game().tooManyDialogLines(lineCount, kMaxLines);
+ break;
+ }
if (*itChar != '\r' && *itChar)
lineWidth += characterSize(font, *itChar).x;
@@ -760,7 +755,7 @@ void FadeDrawRequest::draw() {
g_engine->renderer().setBlendMode(BlendMode::Additive);
break;
default:
- assert(false && "Invalid fade type");
+ g_engine->game().unknownFadeType((int)_type);
return;
}
g_engine->renderer().setTexture(nullptr);
@@ -860,7 +855,7 @@ void DrawQueue::addRequest(IDrawRequest *drawRequest) {
if (_requestsPerOrderCount[order] < kMaxDrawRequestsPerOrder)
_requestsPerOrder[order][_requestsPerOrderCount[order]++] = drawRequest;
else
- error("Too many draw requests in order %d", order);
+ g_engine->game().tooManyDrawRequests(order);
}
void DrawQueue::setLodBias(int8 orderFrom, int8 orderTo, float newLodBias) {
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 8b45c140cf0..a31dbd032d3 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -28,7 +28,7 @@ namespace Alcachofa {
static const ADExtraGuiOptionsMap optionsList[] = {
{
- GAMEOPTION_ORIGINAL_SAVELOAD,
+ GAMEOPTION_ORIGINAL_SAVELOAD, // TODO: Remove, this is not really possible
{
_s("Use original save/load screens"),
_s("Use the original save/load screens instead of the ScummVM ones"),
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 71e37f99a1a..5d1eec4b4f8 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS = \
camera.cpp \
common.cpp \
console.o \
+ game.cpp \
game-objects.cpp \
general-objects.cpp \
global-ui.cpp \
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 54a1407007f..d8eecfc1faa 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -95,7 +95,7 @@ void Player::drawCursor(bool forceDefaultCursor) {
}
void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera) {
- debug("Change room to %s", targetRoomName.c_str());
+ debugC(1, kDebugGameplay, "Change room to %s", targetRoomName.c_str());
// 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
@@ -122,7 +122,7 @@ void Player::changeRoom(const Common::String &targetRoomName, bool resetCamera)
}
_currentRoom = g_engine->world().getRoomByName(targetRoomName.c_str());
- if (_currentRoom == nullptr)
+ 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());
if (!_didLoadGlobalRooms) {
@@ -165,7 +165,7 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
assert(object != nullptr && action != nullptr);
if (_activeCharacter->isBusy() || _activeCharacter->currentlyUsing() != nullptr)
return;
- debug("Trigger object %s %s with %s", object->typeName(), object->name().c_str(), action);
+ debugC(1, kDebugGameplay, "Trigger object %s %s with %s", object->typeName(), object->name().c_str(), action);
if (strcmp(action, "MIRAR") == 0 || inactiveCharacter()->currentlyUsing() == object) {
action = "MIRAR";
@@ -184,6 +184,7 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
//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
script.createProcess(activeCharacterKind(), "DefectoUsar");
}
@@ -196,12 +197,16 @@ struct DoorTask : public Task {
, _character(g_engine->player().activeCharacter())
, _player(g_engine->player()) {
_targetRoom = g_engine->world().getRoomByName(door->targetRoom().c_str());
- if (_targetRoom == nullptr)
- error("Invalid door target room: %s", door->targetRoom().c_str());
+ if (_targetRoom == nullptr) {
+ g_engine->game().unknownDoorTargetRoom(door->targetRoom());
+ return;
+ }
_targetObject = dynamic_cast<InteractableObject *>(_targetRoom->getObjectByName(door->targetObject().c_str()));
- if (_targetObject == nullptr)
- error("Invalid door target door: %s", door->targetObject().c_str());
+ if (_targetObject == nullptr) {
+ g_engine->game().unknownDoorTargetDoor(door->targetRoom(), door->targetObject());
+ return;
+ }
auto targetDoor = dynamic_cast<const Door *>(_targetObject);
_targetDirection = targetDoor == nullptr
? _targetObject->interactionDirection()
@@ -212,6 +217,9 @@ struct DoorTask : public Task {
virtual TaskReturn run() {
TASK_BEGIN;
+ if (_targetRoom == nullptr || _targetObject == nullptr)
+ return TaskReturn::finish(1);
+
// TODO: Fade out music on room change
TASK_WAIT(fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
_player.changeRoom(_targetRoom->name(), true);
@@ -250,11 +258,10 @@ private:
void Player::triggerDoor(const Door *door) {
_heldItem = nullptr;
- if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
- return; // Original exception
-
- FakeLock lock(_activeCharacter->semaphore());
- g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
+ if (g_engine->game().shouldTriggerDoor(door)) {
+ FakeLock lock(_activeCharacter->semaphore());
+ g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
+ }
}
// the last dialog character mechanic seems like a hack in the original engine
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 14e0fb71de3..a26244c58eb 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -33,8 +33,7 @@ namespace Alcachofa {
Room::Room(World *world, ReadStream &stream) : Room(world, stream, false) {
}
-static ObjectBase *readRoomObject(Room *room, ReadStream &stream) {
- const auto type = readVarString(stream);
+static ObjectBase *readRoomObject(Room *room, const String &type, ReadStream &stream) {
if (type == ObjectBase::kClassName)
return new ObjectBase(room, stream);
else if (type == PointObject::kClassName)
@@ -82,7 +81,7 @@ static ObjectBase *readRoomObject(Room *room, ReadStream &stream) {
else if (type == FloorColor::kClassName)
return new FloorColor(room, stream);
else
- error("Unknown type for room objects: %s", type.c_str());
+ return nullptr; // handled in Room::Room
}
Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
@@ -91,8 +90,6 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
_musicId = stream.readSByte();
_characterAlphaTint = stream.readByte();
auto backgroundScale = stream.readSint16LE();
- if (_name == "MINA")
- backgroundScale += 0;
_floors[0] = PathFindingShape(stream);
_floors[1] = PathFindingShape(stream);
_fixedCameraOnEntering = readBool(stream);
@@ -104,11 +101,15 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
uint32 objectSize = stream.readUint32LE(); // TODO: Maybe switch to seekablereadstream and assert objectSize?
while (objectSize > 0)
{
- _objects.push_back(readRoomObject(this, stream));
+ const auto type = readVarString(stream);
+ auto object = readRoomObject(this, type, stream);
+ if (object == nullptr)
+ // TODO: Make this a warning after using SeekableReadStream in Room::Room
+ g_engine->game().unknownRoomObject(type);
+ _objects.push_back(object);
objectSize = stream.readUint32LE();
}
- if (!_name.equalsIgnoreCase("Global") &&
- !_name.equalsIgnoreCase("HABITACION_NEGRA"))
+ if (g_engine->game().doesRoomHaveBackground(this))
_objects.push_back(new Background(this, _name, backgroundScale));
if (!_floors[0].empty())
@@ -219,8 +220,8 @@ void Room::updateRoomBounds() {
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 but I find
- * this very unstable. Instead a fixed value is used
+ * 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);
@@ -694,15 +695,12 @@ void World::loadDialogLines() {
while (lineStart < fileEnd) {
char *lineEnd = find(lineStart, fileEnd, '\n');
char *firstQuote = find(lineStart, lineEnd, '\"');
- if (firstQuote == lineEnd)
- error("Invalid dialog line - first quote");
- char *secondQuote = find(firstQuote + 1, lineEnd, '\"');
- if (secondQuote == lineEnd) {
- // unfortunately one invalid line in the game
- if (_dialogLines.size() != 4542)
- error("Invalid dialog line - second quote");
- firstQuote = lineStart; // for the invalid one save an empty string
- secondQuote = firstQuote + 1;
+ char *secondQuote = firstQuote == lineEnd ? lineEnd : find(firstQuote + 1, lineEnd, '\"');
+
+ if (firstQuote == lineEnd || secondQuote == lineEnd) {
+ g_engine->game().invalidDialogLine(_dialogLines.size());
+ firstQuote = lineStart; // store an empty string
+ secondQuote = lineStart + 1;
}
*secondQuote = 0;
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 983aacb7b9d..5299b029f61 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -132,6 +132,7 @@ public:
inline ProcessId pid() const { return _pid; }
inline MainCharacterKind &character() { return _character; } // is changed in changeCharacter
+ inline MainCharacterKind character() const { return _character; }
inline int32 returnValue() const { return _lastReturnValue; }
inline Common::String &name() { return _name; }
bool isActiveForPlayer() const; ///< and thus should e.g. draw subtitles or effects
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 1164cded5a3..67617c661f4 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -100,16 +100,19 @@ Script::Script() {
int32 Script::variable(const char *name) const {
uint32 index;
- if (!_variableNames.tryGetVal(name, index))
- error("Unknown variable: %s", name);
- return _variables[index];
+ if (_variableNames.tryGetVal(name, index))
+ return _variables[index];
+ g_engine->game().unknownVariable(name);
+ return 0;
}
int32 &Script::variable(const char *name) {
uint32 index;
- if (!_variableNames.tryGetVal(name, index))
- error("Unknown variable: %s", name);
- return _variables[index];
+ if (_variableNames.tryGetVal(name, index))
+ return _variables[index];
+ g_engine->game().unknownVariable(name);
+ static int dummy = 0;
+ return dummy;
}
bool Script::hasProcedure(const Common::String &behavior, const Common::String &action) const {
@@ -196,11 +199,7 @@ struct ScriptTask : public Task {
if (_isFirstExecution || _returnsFromKernelCall)
setCharacterVariables();
if (_returnsFromKernelCall) {
- // this is also original done, every KernelCall is followed by a PopN of the arguments
- // only *after* the PopN the return value is pushed so that the following script can use it
- scumm_assert(_pc < _script._instructions.size() && _script._instructions[_pc]._op == ScriptOp::PopN);
- popN(_script._instructions[_pc++]._arg);
- pushNumber(process().returnValue());
+ handleReturnFromKernelCall(process().returnValue());
}
_isFirstExecution = _returnsFromKernelCall = false;
@@ -264,7 +263,7 @@ struct ScriptTask : public Task {
return kernelReturn;
}
else
- pushNumber(kernelReturn.returnValue());
+ handleReturnFromKernelCall(kernelReturn.returnValue());
}break;
case ScriptOp::JumpIfFalse:
if (popNumber() == 0)
@@ -325,19 +324,8 @@ struct ScriptTask : public Task {
else
pushNumber(returnValue);
}break;
- case ScriptOp::Crash5:
- case ScriptOp::Crash8:
- case ScriptOp::Crash9:
- case ScriptOp::Crash12:
- case ScriptOp::Crash21:
- case ScriptOp::Crash22:
- case ScriptOp::Crash33:
- case ScriptOp::Crash34:
- case ScriptOp::Crash35:
- case ScriptOp::Crash36:
- error("Script reached crash instruction");
default:
- error("Script reached invalid instruction");
+ g_engine->game().unknownInstruction(instruction);
}
}
}
@@ -352,10 +340,20 @@ private:
_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() && _script._instructions[_pc]._op == ScriptOp::PopN);
+ popN(_script._instructions[_pc++]._arg);
+ pushNumber(returnValue);
+ }
+
void pushNumber(int32 value) {
_stack.push({ StackEntryType::Number, value });
}
+ // For the following methods error recovery is not really viable
+
void pushVariable(uint32 offset) {
uint32 index = offset / sizeof(int32);
if (offset % sizeof(int32) != 0 || index >= _script._variables.size())
@@ -574,10 +572,10 @@ private:
return TaskReturn::finish(0);
case ScriptKernelTask::Animate: {
auto graphicObject = getObjectArg<GraphicObject>(0);
- if (graphicObject == nullptr && !scumm_stricmp("EXPLOSION DISFRAZ", getStringArg(0)))
+ if (graphicObject == nullptr) {
+ g_engine->game().unknownAnimateObject(getStringArg(0));
return TaskReturn::finish(1);
- if (graphicObject == nullptr)
- error("Script tried to animate invalid graphic object %s", getStringArg(0));
+ }
if (getNumberOrStringArg(1)) {
graphicObject->toggle(true);
graphicObject->graphic()->start(false);
@@ -591,7 +589,7 @@ private:
case ScriptKernelTask::StopAndTurn: {
auto character = getObjectArg<WalkingCharacter>(0);
if (character == nullptr)
- error("Script tried to stop-and-turn unknown character");
+ g_engine->game().unknownScriptCharacter("stop-and-turn", getStringArg(0));
else
character->stopWalking((Direction)getNumberArg(1));
return TaskReturn::finish(1);
@@ -602,11 +600,15 @@ private:
}
case ScriptKernelTask::Go: {
auto character = getObjectArg<WalkingCharacter>(0);
- if (character == nullptr)
- error("Script tried to make invalid character go: %s", getStringArg(0));
+ if (character == nullptr) {
+ g_engine->game().unknownScriptCharacter("go", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
auto target = getObjectArg<PointObject>(1);
if (target == nullptr)
- error("Script tried to make character go to invalid object %s", getStringArg(1));
+ target = g_engine->game().unknownGoPutTarget(process(), "go", getStringArg(1));
+ if (target == nullptr)
+ return TaskReturn::finish(0);
character->walkTo(target->position());
if (getNumberArg(2) & 2)
@@ -618,31 +620,39 @@ private:
}
case ScriptKernelTask::Put: {
auto character = getObjectArg<WalkingCharacter>(0);
- if (character == nullptr)
- error("Script tried to put invalid character: %s", getStringArg(0));
+ if (character == nullptr) {
+ g_engine->game().unknownScriptCharacter("put", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
auto target = getObjectArg<PointObject>(1);
- if (target == nullptr && !exceptionsForPut(target, getStringArg(1)))
- return TaskReturn::finish(2);
if (target == nullptr)
- error("Script tried to make character put to invalid object %s", getStringArg(1));
+ target = g_engine->game().unknownGoPutTarget(process(), "put", getStringArg(1));
+ if (target == nullptr)
+ return TaskReturn::finish(0);
character->setPosition(target->position());
return TaskReturn::finish(1);
}
case ScriptKernelTask::ChangeCharacterRoom: {
auto *character = getObjectArg<Character>(0);
- if (character == nullptr)
- error("Invalid character name: %s", getStringArg(0));
+ if (character == nullptr) {
+ g_engine->game().unknownScriptCharacter("change character room", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
auto *targetRoom = g_engine->world().getRoomByName(getStringArg(1));
- if (targetRoom == nullptr)
- error("Invalid room name: %s", getStringArg(1));
+ if (targetRoom == nullptr) {
+ g_engine->game().unknownChangeCharacterRoom(getStringArg(1));
+ return TaskReturn::finish(1);
+ }
character->resetTalking();
character->room() = targetRoom;
return TaskReturn::finish(1);
}
case ScriptKernelTask::LerpCharacterLodBias: {
auto *character = getObjectArg<Character>(0);
- if (character == nullptr)
- error("Invalid character name: %s", getStringArg(0));
+ if (character == nullptr) {
+ g_engine->game().unknownScriptCharacter("lerp character LOD bias", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
float targetLodBias = getNumberArg(1) * 0.01f;
int32 durationMs = getNumberArg(2);
if (durationMs <= 0)
@@ -655,28 +665,28 @@ private:
}
case ScriptKernelTask::AnimateCharacter: {
auto *character = getObjectArg<Character>(0);
- if (character == nullptr)
- error("Invalid character name: %s", getStringArg(0));
+ if (character == nullptr) {
+ g_engine->game().unknownScriptCharacter("animate character", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
auto *animObject = getObjectArg(1);
- if (animObject == nullptr && (
- !scumm_stricmp("COGE F DCH", getStringArg(1)) ||
- !scumm_stricmp("CHIQUITO_IZQ", getStringArg(1))))
- return TaskReturn::finish(2); // original bug in MOTEL_ENTRADA
- if (animObject == nullptr)
- error("Invalid animate object name: %s", getStringArg(1));
+ if (animObject == nullptr) {
+ g_engine->game().unknownAnimateCharacterObject(getStringArg(1));
+ return TaskReturn::finish(1);
+ }
return TaskReturn::waitFor(character->animate(process(), animObject));
}
case ScriptKernelTask::AnimateTalking: {
auto *character = getObjectArg<Character>(0);
- if (character == nullptr)
- error("Invalid character name: %s", getStringArg(0));
- const char *talkObjectName = getStringArg(1);
- ObjectBase *talkObject = nullptr;
- if (talkObjectName != nullptr && *talkObjectName)
+ if (character == nullptr) {
+ g_engine->game().unknownScriptCharacter("talk", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
+ ObjectBase *talkObject = getObjectArg(1);
+ if (talkObject == nullptr && *getStringArg(1) != '\0')
{
- talkObject = getObjectArg(1);
- if (talkObject == nullptr)
- error("Invalid talk object name: %s", talkObjectName);
+ g_engine->game().unknownAnimateTalkingObject(getStringArg(1));
+ return TaskReturn::finish(1);
}
character->talkUsing(talkObject);
return TaskReturn::finish(1);
@@ -692,9 +702,8 @@ private:
? &relatedCharacter()
: getObjectArg<Character>(0);
if (_character == nullptr) {
- if (strcmp(characterName, "OFELIA") == 0 && dialogId == 3737)
- return TaskReturn::finish(1);
- error("Invalid character for sayText: %s", characterName);
+ g_engine->game().unknownSayTextCharacter(characterName, dialogId);
+ return TaskReturn::finish(1);
}
return TaskReturn::waitFor(_character->sayText(process(), dialogId));
};
@@ -725,7 +734,7 @@ private:
switch ((MainCharacterKind)getNumberArg(0)) {
case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory(); break;
case MainCharacterKind::Filemon: g_engine->world().filemon().clearInventory(); break;
- default: error("Script attempted to clear inventory with invalid character kind"); break;
+ default: g_engine->game().unknownClearInventoryTarget(getNumberArg(0)); break;
}
return TaskReturn::finish(1);
@@ -766,8 +775,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)
- error("Invalid target object for LerpCamToObjectKeepingZ: %s", getStringArg(0));
+ if (pointObject == nullptr) {
+ g_engine->game().unknownCamLerpTarget("LerpCamToObjectKeepingZ", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
as2D(pointObject->position()),
getNumberArg(1), EasingType::Linear));
@@ -776,8 +787,10 @@ private:
if (!process().isActiveForPlayer())
return TaskReturn::waitFor(delay(getNumberArg(1)));
auto pointObject = getObjectArg<PointObject>(0);
- if (pointObject == nullptr)
- error("Invalid target object for LerpCamToObjectResettingZ: %s", getStringArg(0));
+ if (pointObject == nullptr) {
+ g_engine->game().unknownCamLerpTarget("LerpCamToObjectResettingZ", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
return TaskReturn::waitFor(g_engine->camera().lerpPos(process(),
as3D(pointObject->position()),
getNumberArg(1), (EasingType)getNumberArg(2)));
@@ -788,8 +801,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)
- error("Invalid target object for LerpCamToObjectWithScale: %s", getStringArg(0));
+ if (pointObject == nullptr) {
+ g_engine->game().unknownCamLerpTarget("LerpCamToObjectWithScale", getStringArg(0));
+ return TaskReturn::finish(1);
+ }
return TaskReturn::waitFor(g_engine->camera().lerpPosScale(process(),
as3D(pointObject->position()), targetScale,
getNumberArg(2), (EasingType)getNumberArg(3), (EasingType)getNumberArg(4)));
@@ -840,7 +855,7 @@ private:
case ScriptKernelTask::Nop34:
return TaskReturn::finish(0);
default:
- error("Invalid kernel call");
+ g_engine->game().unknownKernelTask((int)task);
return TaskReturn::finish(0);
}
}
@@ -861,36 +876,6 @@ private:
assert(character.semaphore().isReleased()); // this process should be the last to hold a lock if at all...
}
- /**
- * @brief Check for original bugs related to the Put kernel call and handle them
- * @param target An out reference to the point object (maybe we can find an alternative one)
- * @param targetName The given name of the target object
- * @return false if the put kernel call should be ignored, true if we set target and want to continue with the kernel call
- */
- bool exceptionsForPut(PointObject *&target, const char *targetName) {
- assert(target == nullptr); // if not, why did we check for exceptions?
-
- if (!scumm_stricmp("A_Poblado_Indio", targetName)) {
- // 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
- target = dynamic_cast<PointObject *>(g_engine->world().getObjectByName(process().character(), "A_Poblado_Indio1"));
- }
-
- if (!scumm_stricmp("PUNTO_VENTANA", targetName)) {
- // The object is in the previous, now inactive room.
- // Luckily Mortadelo already is at that point so not further action required
- return false;
- }
-
- if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", targetName)) {
- // Another case of a door being cast into a PointObject
- return false;
- }
-
- return true;
- }
-
Script &_script;
Stack<StackEntry> _stack;
String _name;
@@ -909,7 +894,9 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
if (!_procedures.tryGetVal(procedure, offset)) {
if (flags & ScriptFlags::AllowMissing)
return nullptr;
- error("Unknown required procedure: %s", procedure.c_str());
+ // it is currently unnecessary but we could return an empty process to avoid returning nullptr here
+ g_engine->game().unknownScriptProcedure(procedure);
+ return nullptr;
}
FakeLock lock;
if (!(flags & ScriptFlags::IsBackground))
@@ -920,7 +907,7 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
}
void Script::updateCommonVariables() {
- if (g_engine->input().wasAnyMousePressed()) // yes, this variable is never reset by the engine
+ if (g_engine->input().wasAnyMousePressed()) // yes, this variable is never reset by the engine (only by script)
variable("SeHaPulsadoRaton") = 1;
if (variable("CalcularTiempoSinPulsarRaton")) {
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index e4ed671e3ad..82ed0baf7e8 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -609,7 +609,6 @@ bool PathFindingShape::findEvadeTarget(
Point centerTarget,
float depthScale, float minDistSqr,
Point &evadeTarget) const {
- // TODO: Check if minDistSqr should just modify tryDistBase
for (float tryDistBase = 60; tryDistBase < 250; tryDistBase += 10) {
for (int tryAngleI = 0; tryAngleI < 6; tryAngleI++) {
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index e9562bd07b0..bf004d8306a 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -108,10 +108,15 @@ static AudioStream *loadSND(File *file) {
auto subStream = new SeekableSubReadStream(file, (uint32)file->pos(), (uint32)file->size(), DisposeAfterUse::YES);
if (format == 1 && channels <= 2 && (bitsPerSample == 8 || bitsPerSample == 16))
return makeRawStream(subStream, (int)freq,
- (channels == 2 ? FLAG_STEREO : 0) | (bitsPerSample == 16 ? FLAG_16BITS | FLAG_LITTLE_ENDIAN : FLAG_UNSIGNED));
+ (channels == 2 ? FLAG_STEREO : 0) |
+ (bitsPerSample == 16 ? FLAG_16BITS | FLAG_LITTLE_ENDIAN : FLAG_UNSIGNED));
else if (format == 17 && channels <= 2)
return makeADPCMStream(subStream, DisposeAfterUse::YES, 0, kADPCMMSIma, (int)freq, (int)channels, (uint32)bytesPerBlock);
- error("Invalid SND file, format: %u, channels: %u, freq: %u, bps: %u", format, channels, freq, bitsPerSample);
+ else {
+ delete file;
+ g_engine->game().invalidSNDFormat(format, channels, freq, bitsPerSample);
+ return nullptr;
+ }
}
static AudioStream *openAudio(const String &fileName) {
@@ -128,10 +133,8 @@ static AudioStream *openAudio(const String &fileName) {
return makeWAVStream(file, DisposeAfterUse::YES);
delete file;
- // Ignore the known, original wrong filenames given, report the rest
- if (fileName == "CHAS" || fileName == "517")
- return nullptr;
- error("Could not open audio file: %s", fileName.c_str());
+ g_engine->game().missingSound(fileName);
+ return nullptr;
}
SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::SoundType type) {
Commit: 4aea1d5bf96e87470778d3b518126d94e8232a18
https://github.com/scummvm/scummvm/commit/4aea1d5bf96e87470778d3b518126d94e8232a18
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Replace TODO comment in WalkingCharacter::draw
Changed paths:
engines/alcachofa/game-objects.cpp
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 67263acaf65..a49fa21bb2a 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -668,7 +668,10 @@ void WalkingCharacter::draw() {
currentGraphic->pause();
}
if (currentGraphic == nullptr) {
- // TODO: draw dialog line
+ // The original game drew the current dialog line at this point,
+ // but I do not know of a scenario where this would be necessary
+ // As long as we cannot test this or have a bug report I rather not implement it
+
currentGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
}
Commit: dbfa3a5600aa0494f18302e6e345523cd30aba73
https://github.com/scummvm/scummvm/commit/dbfa3a5600aa0494f18302e6e345523cd30aba73
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Refactor room reading to assert object sizes
Changed paths:
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 59bcee82bf0..f9a041cec4c 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -44,6 +44,10 @@ void Game::unknownRoomObject(const String &type) {
_message("Unknown type for room object: %s", type.c_str());
}
+void Game::unknownRoomType(const String &type) {
+ _message("Unknown type for room: %s", type.c_str());
+}
+
void Game::unknownDoorTargetRoom(const String &name) {
_message("Unknown door target room: %s", name.c_str());
}
@@ -179,4 +183,12 @@ void Game::invalidSNDFormat(uint format, uint channels, uint freq, uint bps) {
_message("Invalid SND file, format: %u, channels: %u, freq: %u, bps: %u", format, channels, freq, bps);
}
+void Game::notEnoughRoomDataRead(const char *path, int64 filePos, int64 roomEnd) {
+ _message("Did not read enough data (%dll < %dll) for a room in %s", filePos, roomEnd, path);
+}
+
+void Game::notEnoughObjectDataRead(const char *room, int64 filePos, int64 objectEnd) {
+ _message("Did not read enough data (%dll < %dll) for an object in room %s", filePos, objectEnd, room);
+}
+
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 94b6bc64c83..33737d444af 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -50,6 +50,7 @@ public:
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
+ virtual void unknownRoomType(const Common::String &type);
virtual void unknownDoorTargetRoom(const Common::String &name);
virtual void unknownDoorTargetDoor(const Common::String &room, const Common::String &door);
@@ -83,6 +84,8 @@ public:
virtual void missingAnimation(const Common::String &fileName);
virtual void missingSound(const Common::String &fileName);
virtual void invalidSNDFormat(uint format, uint channels, uint freq, uint bps);
+ virtual void notEnoughRoomDataRead(const char *path, int64 filePos, int64 objectEnd);
+ virtual void notEnoughObjectDataRead(const char *room, int64 filePos, int64 objectEnd);
static Game *createForMovieAdventure();
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index a26244c58eb..96ee8d78744 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -30,7 +30,7 @@ using namespace Common;
namespace Alcachofa {
-Room::Room(World *world, ReadStream &stream) : Room(world, stream, false) {
+Room::Room(World *world, SeekableReadStream &stream) : Room(world, stream, false) {
}
static ObjectBase *readRoomObject(Room *room, const String &type, ReadStream &stream) {
@@ -84,7 +84,7 @@ static ObjectBase *readRoomObject(Room *room, const String &type, ReadStream &st
return nullptr; // handled in Room::Room
}
-Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
+Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
: _world(world) {
_name = readVarString(stream);
_musicId = stream.readSByte();
@@ -98,16 +98,25 @@ Room::Room(World *world, ReadStream &stream, bool hasUselessByte)
if (hasUselessByte)
stream.readByte();
- uint32 objectSize = stream.readUint32LE(); // TODO: Maybe switch to seekablereadstream and assert objectSize?
- while (objectSize > 0)
+ uint32 objectEnd = stream.readUint32LE();
+ while (objectEnd > 0)
{
const auto type = readVarString(stream);
auto object = readRoomObject(this, type, stream);
- if (object == nullptr)
- // TODO: Make this a warning after using SeekableReadStream in Room::Room
+ if (object == nullptr) {
g_engine->game().unknownRoomObject(type);
- _objects.push_back(object);
- objectSize = stream.readUint32LE();
+ stream.seek(objectEnd, SEEK_SET);
+ }
+ else if (stream.pos() < objectEnd) {
+ 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 > %dll) in room %s", objectEnd, stream.pos(), _name.c_str());
+
+ if (object != nullptr)
+ _objects.push_back(object);
+ objectEnd = stream.readUint32LE();
}
if (g_engine->game().doesRoomHaveBackground(this))
_objects.push_back(new Background(this, _name, backgroundScale));
@@ -318,19 +327,19 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
return best;
}
-OptionsMenu::OptionsMenu(World *world, ReadStream &stream)
+OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
-ConnectMenu::ConnectMenu(World *world, ReadStream &stream)
+ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
-ListenMenu::ListenMenu(World *world, ReadStream &stream)
+ListenMenu::ListenMenu(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
-Inventory::Inventory(World *world, ReadStream &stream)
+Inventory::Inventory(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
@@ -589,7 +598,7 @@ const char *World::getDialogLine(int32 dialogId) const {
return _dialogLines[dialogId];
}
-static Room *readRoom(World *world, ReadStream &stream) {
+static Room *readRoom(World *world, SeekableReadStream &stream) {
const auto type = readVarString(stream);
if (type == Room::kClassName)
return new Room(world, stream);
@@ -601,8 +610,10 @@ static Room *readRoom(World *world, ReadStream &stream) {
return new ListenMenu(world, stream);
else if (type == Inventory::kClassName)
return new Inventory(world, stream);
- else
- error("Unknown type for room %s", type.c_str());
+ else {
+ g_engine->game().unknownRoomType(type);
+ return nullptr;
+ }
}
bool World::loadWorldFile(const char *path) {
@@ -632,8 +643,15 @@ bool World::loadWorldFile(const char *path) {
uint32 roomEnd = file.readUint32LE();
while (roomEnd > 0) {
- _rooms.push_back(readRoom(this, file));
- assert(file.pos() == roomEnd);
+ Room *room = readRoom(this, file);
+ if (room != nullptr)
+ _rooms.push_back(room);
+ if (file.pos() < roomEnd) {
+ g_engine->game().notEnoughRoomDataRead(path, 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);
roomEnd = file.readUint32LE();
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 8fad2cf1d93..37c5527476d 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -31,7 +31,7 @@ class World;
class Room {
public:
static constexpr const char *kClassName = "CHabitacion";
- Room(World *world, Common::ReadStream &stream);
+ Room(World *world, Common::SeekableReadStream &stream);
virtual ~Room();
inline World &world() { return *_world; }
@@ -64,7 +64,7 @@ public:
void debugPrint(bool withObjects) const;
protected:
- Room(World *world, Common::ReadStream &stream, bool hasUselessByte);
+ Room(World *world, Common::SeekableReadStream &stream, bool hasUselessByte);
void updateScripts();
void updateRoomBounds();
void updateInteraction();
@@ -90,25 +90,25 @@ protected:
class OptionsMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionMenuOpciones";
- OptionsMenu(World *world, Common::ReadStream &stream);
+ OptionsMenu(World *world, Common::SeekableReadStream &stream);
};
class ConnectMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionConectar";
- ConnectMenu(World *world, Common::ReadStream &stream);
+ ConnectMenu(World *world, Common::SeekableReadStream &stream);
};
class ListenMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionEsperar";
- ListenMenu(World *world, Common::ReadStream &stream);
+ ListenMenu(World *world, Common::SeekableReadStream &stream);
};
class Inventory final : public Room {
public:
static constexpr const char *kClassName = "CInventario";
- Inventory(World *world, Common::ReadStream &stream);
+ Inventory(World *world, Common::SeekableReadStream &stream);
virtual ~Inventory() override;
virtual bool updateInput() override;
Commit: 8f17fceba22c181f6984540dda5a6deae55868f6
https://github.com/scummvm/scummvm/commit/8f17fceba22c181f6984540dda5a6deae55868f6
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Add lip-sync
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index a49fa21bb2a..90e3be850d2 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -201,7 +201,8 @@ void Character::updateTalkingAnimation() {
talkGraphic->reset();
return;
}
- // TODO: Add lip-sync(?) animation behavior
+ if (talkGraphic == &_graphicTalking && !_isSpeaking)
+ talkGraphic->reset();
talkGraphic->update();
}
@@ -292,6 +293,8 @@ struct SayTextTask final : public Task {
}
virtual TaskReturn run() override {
+ bool isSoundStillPlaying;
+
TASK_BEGIN;
_character->_isTalking = true;
graphicOf(_character->_curTalkingObject, &_character->_graphicTalking)->start(true);
@@ -305,8 +308,9 @@ struct SayTextTask final : public Task {
String::format(hasMortadeloVoice ? "M%04d" : "%04d", _dialogId),
0);
}
+ isSoundStillPlaying = g_engine->sounds().isAlive(_soundId);
g_engine->sounds().setAppropriateVolume(_soundId, process().character(), _character);
- if (!g_engine->sounds().isAlive(_soundId) || g_engine->input().wasAnyMouseReleased())
+ if (!isSoundStillPlaying || g_engine->input().wasAnyMouseReleased())
_character->_isTalking = false;
if (true && // TODO: Add game option for subtitles
@@ -317,13 +321,15 @@ struct SayTextTask final : public Task {
Point(g_system->getWidth() / 2, g_system->getHeight() - 200),
-1, true, kWhite, -kForegroundOrderCount);
}
- // TODO: Add lip sync for sayText
if (!_character->_isTalking) {
g_engine->sounds().fadeOut(_soundId, 100);
TASK_WAIT(delay(200));
TASK_RETURN(0);
}
+
+ _character->isSpeaking() = !isSoundStillPlaying ||
+ g_engine->sounds().isNoisy(_soundId, 80.0f, 150.0f);
TASK_YIELD;
}
TASK_END;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 7ce235eac67..f6f53adbc75 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -396,6 +396,7 @@ public:
Task *animate(Process &process, ObjectBase *animateObject);
Task *lerpLodBias(Process &process, float targetLodBias, int32 durationMs);
inline float &lodBias() { return _lodBias; }
+ inline bool &isSpeaking() { return _isSpeaking; }
protected:
friend struct SayTextTask;
@@ -406,7 +407,8 @@ protected:
Direction _direction = Direction::Right;
Graphic _graphicNormal, _graphicTalking;
- bool _isTalking = false;
+ bool _isTalking = false; ///< as in "in the process of saying a line"
+ bool _isSpeaking = true; ///< as in "actively moving their mouth to produce sounds", used in updateTalkingAnimation
int _curDialogId = -1;
float _lodBias = 0.0f;
ObjectBase
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index bf004d8306a..0425b35bf08 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -35,10 +35,6 @@ using namespace Audio;
namespace Alcachofa {
-Sounds::Playback::Playback(uint32 id, SoundHandle handle, Mixer::SoundType type)
- : _id(id), _handle(handle), _type(type) {
-}
-
void Sounds::Playback::fadeOut(uint32 duration) {
_fadeStart = g_system->getMillis();
_fadeDuration = MAX<uint32>(duration, 1);
@@ -141,11 +137,58 @@ SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::So
AudioStream *stream = openAudio(fileName);
if (stream == nullptr)
return UINT32_MAX;
- SoundHandle handle;
- _mixer->playStream(type, &handle, stream, -1, volume);
- SoundID id = _nextID++;
- _playbacks.push_back({ id, handle, type });
- return id;
+
+ Array<int16> samples;
+ SeekableAudioStream *seekStream = dynamic_cast<SeekableAudioStream *>(stream);
+ if (type == Mixer::kSpeechSoundType && seekStream != nullptr) {
+ // for lip-sync we need access to the samples so we decode the entire stream now
+ int sampleCount = seekStream->getLength().totalNumberOfFrames();
+ if (sampleCount > 0) {
+ // we actually got a length
+ samples.resize((uint)sampleCount);
+ sampleCount = seekStream->readBuffer(samples.data(), sampleCount);
+ if (sampleCount < 0)
+ samples.clear();
+ samples.resize((uint)sampleCount); // we might have gotten less samples
+ }
+ else {
+ // we did not, not it is getting inefficient
+ const int bufferSize = 512;
+ int16 buffer[bufferSize];
+ int chunkSampleCount;
+ do {
+ chunkSampleCount = seekStream->readBuffer(buffer, bufferSize);
+ if (chunkSampleCount <= 0)
+ break;
+ samples.resize(samples.size() + chunkSampleCount);
+ copy(buffer, buffer + chunkSampleCount, samples.data() + sampleCount);
+ sampleCount += chunkSampleCount;
+ } while (chunkSampleCount >= bufferSize);
+ }
+
+ if (sampleCount > 0) {
+ stream = makeRawStream(
+ (byte *)samples.data(),
+ samples.size() * sizeof(int16),
+ seekStream->getRate(),
+ FLAG_16BITS |
+#ifdef SCUMM_LITTLE_ENDIAN
+ FLAG_LITTLE_ENDIAN | // readBuffer returns native endian
+#endif
+ (seekStream->isStereo() ? FLAG_STEREO : 0),
+ DisposeAfterUse::NO);
+ delete seekStream;
+ }
+ }
+
+ Playback playback;
+ _mixer->playStream(type, &playback._handle, stream, -1, volume);
+ playback._id = _nextID++;
+ playback._type = type;
+ playback._inputRate = stream->getRate();
+ playback._samples = std::move(samples);
+ _playbacks.push_back(std::move(playback));
+ return playback._id;
}
SoundID Sounds::playVoice(const String &fileName, byte volume) {
@@ -209,6 +252,36 @@ void Sounds::fadeOutVoiceAndSFX(uint32 duration) {
}
}
+bool Sounds::isNoisy(SoundID id, float windowSize, float minDifferences) {
+ assert(windowSize > 0 && minDifferences > 0);
+ const Playback *playback = getPlaybackById(id);
+ if (playback == nullptr ||
+ playback->_samples.empty() ||
+ !_mixer->isSoundHandleActive(playback->_handle))
+ return false;
+
+ minDifferences *= windowSize;
+ uint windowSizeInSamples = (uint)(windowSize * 0.001f * playback->_inputRate);
+ uint samplePosition = (uint)_mixer->getElapsedTime(playback->_handle)
+ .convertToFramerate(playback->_inputRate)
+ .totalNumberOfFrames();
+ uint endPosition = MIN(playback->_samples.size(), samplePosition + windowSizeInSamples);
+ if (samplePosition >= endPosition)
+ return false;
+
+ /* While both ScummVM and the original engine use signed int16 samples
+ * For this noise detection the samples are reinterpret as uint16
+ * This causes changes going through zero to be much more significant.
+ */
+ float sumOfDifferences = 0;
+ const uint16 *samplePtr = (const uint16 *)playback->_samples.data();
+ for (uint i = samplePosition; i < endPosition - 1; i++)
+ // cast to int before to not be constrained by uint16
+ sumOfDifferences += ABS((int)samplePtr[i + 1] - samplePtr[i]);
+
+ return sumOfDifferences / 256.0f >= minDifferences;
+}
+
PlaySoundTask::PlaySoundTask(Process &process, SoundID soundID)
: Task(process)
, _soundID(soundID) {
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 377d1313a59..ca320fb427f 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -24,6 +24,7 @@
#include "scheduler.h"
#include "audio/mixer.h"
+#include "audio/audiostream.h"
namespace Alcachofa {
@@ -47,17 +48,19 @@ public:
void setAppropriateVolume(SoundID id,
MainCharacterKind processCharacter,
Character *speakingCharacter);
+ bool isNoisy(SoundID id, float windowSize, float minDifferences); ///< used for lip-sync
private:
- struct Playback {
- Playback(uint32 id, Audio::SoundHandle handle, Audio::Mixer::SoundType type);
+ struct Playback {;
void fadeOut(uint32 duration);
- SoundID _id;
+ SoundID _id = 0;
Audio::SoundHandle _handle;
- Audio::Mixer::SoundType _type;
+ Audio::Mixer::SoundType _type = Audio::Mixer::SoundType::kPlainSoundType;
uint32 _fadeStart = 0,
_fadeDuration = 0;
+ int _inputRate;
+ Common::Array<int16> _samples; ///< might not be filled, only voice samples are preloaded for lip-sync
};
Playback *getPlaybackById(SoundID id);
SoundID playSoundInternal(const Common::String &fileName, byte volume, Audio::Mixer::SoundType type);
Commit: 0f167489aee1882c2eefbfa075f6754c03dfb807
https://github.com/scummvm/scummvm/commit/0f167489aee1882c2eefbfa075f6754c03dfb807
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Fix character direction after triggering doors
Changed paths:
engines/alcachofa/player.cpp
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index d8eecfc1faa..4d264512982 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -207,10 +207,7 @@ struct DoorTask : public Task {
g_engine->game().unknownDoorTargetDoor(door->targetRoom(), door->targetObject());
return;
}
- auto targetDoor = dynamic_cast<const Door *>(_targetObject);
- _targetDirection = targetDoor == nullptr
- ? _targetObject->interactionDirection()
- : targetDoor->characterDirection();
+ _targetDirection = door->characterDirection();
process.name() = String::format("Door to %s %s", _targetRoom->name().c_str(), _targetObject->name().c_str());
}
Commit: 0f8d9f70bab9931fa9ed7a53dc25f13e8cd36b72
https://github.com/scummvm/scummvm/commit/0f8d9f70bab9931fa9ed7a53dc25f13e8cd36b72
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Add FloorColorDebugHandler
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/debug.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/rooms.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index c2f55cd00e1..fa9636fb0df 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -155,6 +155,12 @@ void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param) {
case DebugMode::TeleportCharacter:
_debugHandler.reset(new TeleportCharacterDebugHandler(param));
break;
+ case DebugMode::FloorAlpha:
+ _debugHandler.reset(FloorColorDebugHandler::create(param, false));
+ break;
+ case DebugMode::FloorColor:
+ _debugHandler.reset(FloorColorDebugHandler::create(param, true));
+ break;
default: _debugHandler.reset(nullptr);
}
_input.toggleDebugInput(isDebugModeActive());
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index 020a1c6c59a..dbb268fa43f 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -215,6 +215,8 @@ bool Console::cmdDebugMode(int argc, const char **args) {
debugPrintf(" 1 - Closest floor point, param limits to polygon\n");
debugPrintf(" 2 - Floor edge intersections, param limits to polygon\n");
debugPrintf(" 3 - Teleport character to mouse click, param selects character\n");
+ debugPrintf(" 4 - Show floor alpha, param selects index of floor color object\n");
+ debugPrintf(" 5 - Show floor color, param selects index of floor color object\n");
return true;
}
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 39376311cbb..b4d8af84a73 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -32,6 +32,8 @@ enum class DebugMode {
ClosestFloorPoint,
FloorIntersections,
TeleportCharacter,
+ FloorAlpha,
+ FloorColor
};
class Console : public GUI::Debugger {
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index 0e1eef52529..cdfc42c8c93 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -66,8 +66,11 @@ public:
virtual void update() override {
auto floor = g_engine->player().currentRoom()->activeFloor();
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
- if (floor == nullptr || renderer == nullptr)
+ if (floor == nullptr || renderer == nullptr) {
+ g_engine->console().attach("Either the room has no floor or the renderer is not a debug renderer");
+ g_engine->setDebugMode(DebugMode::None, 0);
return;
+ }
if (g_engine->input().debugInput().wasMouseLeftPressed())
_fromPos3D = g_engine->input().debugInput().mousePos3D();
@@ -156,6 +159,77 @@ private:
}
};
+class FloorColorDebugHandler final : public IDebugHandler {
+ const FloorColorShape &_shape;
+ const bool _useColor;
+ Color _curColor = kDebugGreen;
+ bool _isOnFloor = false;
+ static constexpr size_t kBufferSize = 64;
+ char _buffer[kBufferSize];
+
+ FloorColorDebugHandler(const FloorColorShape &shape, bool useColor)
+ : _shape(shape)
+ , _useColor(useColor) {}
+public:
+ static FloorColorDebugHandler *create(int32 objectI, bool useColor) {
+ const Room *room = g_engine->player().currentRoom();
+ uint floorCount = 0;
+ for (auto itObject = room->beginObjects(); itObject != room->endObjects(); ++itObject) {
+ FloorColor *floor = dynamic_cast<FloorColor*>(*itObject);
+ if (floor == nullptr)
+ continue;
+ if (objectI <= 0)
+ // dynamic_cast is not possible due to Shape not having virtual methods
+ return new FloorColorDebugHandler(*(FloorColorShape*)(floor->shape()), useColor);
+ floorCount++;
+ objectI--;
+ }
+
+ g_engine->console().debugPrintf("Invalid floor color index, there are %u floors in this room\n", floorCount);
+ return nullptr;
+ }
+
+ virtual void update() override {
+ auto &input = g_engine->input().debugInput();
+ if (input.wasMouseRightPressed()) {
+ g_engine->setDebugMode(DebugMode::None, 0);
+ return;
+ }
+
+ if (input.isMouseLeftDown()) {
+ auto optColor = _shape.colorAt(input.mousePos3D());
+ _isOnFloor = optColor.first;
+ if (!_isOnFloor) {
+ uint8 roomAlpha = (uint)(g_engine->player().currentRoom()->characterAlphaTint() * 255 / 100);
+ optColor.second = Color{ 255, 255, 255, roomAlpha };
+ }
+
+ _curColor = _useColor
+ ? Color{ optColor.second.r, optColor.second.g, optColor.second.b, 255 }
+ : Color{ optColor.second.a, optColor.second.a, optColor.second.a, 255 };
+ g_engine->world().mortadelo().color() =
+ g_engine->world().filemon().color() =
+ _useColor ? optColor.second : Color{ 255, 255, 255, optColor.second.a };
+ }
+
+ snprintf(_buffer, kBufferSize, "r:%3d g:%3d b:%3d a:%3d",
+ (int)_curColor.r, (int)_curColor.g, (int)_curColor.b, (int)_curColor.a);
+
+ auto *debugRenderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ g_engine->drawQueue().clear();
+ g_engine->player().drawCursor(true);
+ g_engine->renderer().setTexture(nullptr);
+ g_engine->renderer().quad({ 0, 0 }, { 50, 50 }, _isOnFloor ? _curColor : kDebugGreen);
+ g_engine->drawQueue().add<TextDrawRequest>(
+ g_engine->globalUI().dialogFont(), _buffer, Point { 70, 20 }, 500, false, kWhite, -kForegroundOrderCount + 1);
+ if (!_isOnFloor)
+ g_engine->renderer().quad({ 5, 5 }, { 40, 40 }, _curColor);
+ if (debugRenderer != nullptr)
+ debugRenderer->debugShape(_shape, kDebugBlue);
+ g_engine->drawQueue().draw();
+ }
+};
+
}
#endif // DEBUG_H
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 90e3be850d2..5b22b62a451 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -784,7 +784,9 @@ void MainCharacter::update() {
_currentPos.x - halfWidth, _currentPos.y - height,
_currentPos.x + halfWidth, _currentPos.y));
- // TODO: Update character alpha tint
+ // These are set as members as FloorColor might want to change them
+ _alphaPremultiplier = room()->characterAlphaPremultiplier();
+ _color = { 255, 255, 255, (uint8)(room()->characterAlphaTint() * 255 / 100) };
}
void MainCharacter::onArrived() {
@@ -860,17 +862,16 @@ void MainCharacter::drawInner() {
Graphic *activeGraphic = graphicOf(_curAnimateObject);
if (activeGraphic == nullptr && _isWalking) {
activeGraphic = &_graphicNormal;
- _graphicNormal.premultiplyAlpha() = room()->characterAlphaPremultiplier();
+ _graphicNormal.premultiplyAlpha() = _alphaPremultiplier;
}
if (activeGraphic == nullptr) {
activeGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
- _graphicTalking.premultiplyAlpha() = room()->characterAlphaPremultiplier();
+ _graphicTalking.premultiplyAlpha() = _alphaPremultiplier;
}
assert(activeGraphic != nullptr);
- activeGraphic->color().a = room()->characterAlphaTint() * 255 / 100;
+ activeGraphic->color() = _color;
g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha, _lodBias);
-
}
void syncDialogMenuLine(Serializer &serializer, DialogMenuLine &line) {
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 5d8428ba6ea..bee04e2338b 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -255,17 +255,17 @@ public:
}
virtual void quad(
- Vector2d center,
+ Vector2d topLeft,
Vector2d size,
Color color,
Angle rotation,
Vector2d texMin,
Vector2d texMax) override {
Vector2d positions[] = {
- center + Vector2d(0, 0),
- center + Vector2d(0, +size.getY()),
- center + Vector2d(+size.getX(), +size.getY()),
- center + Vector2d(+size.getX(), 0),
+ topLeft + Vector2d(0, 0),
+ topLeft + Vector2d(0, +size.getY()),
+ topLeft + Vector2d(+size.getX(), +size.getY()),
+ topLeft + Vector2d(+size.getX(), 0),
};
if (abs(rotation.getDegrees()) > epsilon) {
const Vector2d zero(0, 0);
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index ac8f80db2b7..788dc583f3b 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -82,7 +82,7 @@ public:
virtual void setBlendMode(BlendMode blendMode) = 0;
virtual void setLodBias(float lodBias) = 0;
virtual void quad(
- Math::Vector2d center, // TOOD: Use topLeft&size instead of center&size
+ Math::Vector2d topLeft,
Math::Vector2d size,
Color color = kWhite,
Math::Angle rotation = Math::Angle(),
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index f6f53adbc75..3af9ef3e4a2 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -492,6 +492,8 @@ public:
inline MainCharacterKind kind() const { return _kind; }
inline ObjectBase *¤tlyUsing() { return _currentlyUsingObject; }
inline ObjectBase *currentlyUsing() const { return _currentlyUsingObject; }
+ inline Color &color() { return _color; }
+ inline uint8 &alphaPremultiplier() { return _alphaPremultiplier; }
inline FakeSemaphore &semaphore() { return _semaphore; }
bool isBusy() const;
@@ -531,6 +533,8 @@ private:
FakeSemaphore _semaphore;
ITriggerableObject *_activateObject = nullptr;
const char *_activateAction = nullptr;
+ Color _color = kWhite;
+ uint8 _alphaPremultiplier = 255;
};
class Background final : public GraphicObject {
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 4d264512982..adcc5e26713 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -195,7 +195,9 @@ struct DoorTask : public Task {
, _lock(move(lock))
, _sourceDoor(door)
, _character(g_engine->player().activeCharacter())
- , _player(g_engine->player()) {
+ , _player(g_engine->player())
+ , _targetObject(nullptr)
+ , _targetDirection(Direction::Invalid) {
_targetRoom = g_engine->world().getRoomByName(door->targetRoom().c_str());
if (_targetRoom == nullptr) {
g_engine->game().unknownDoorTargetRoom(door->targetRoom());
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 37c5527476d..976ad66fd28 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -49,7 +49,7 @@ public:
inline uint8 characterAlphaPremultiplier() const { return _characterAlphaPremultiplier; }
inline bool fixedCameraOnEntering() const { return _fixedCameraOnEntering; }
- using ObjectIterator = Common::Array<const ObjectBase *>::const_iterator;
+ using ObjectIterator = Common::Array<ObjectBase *>::const_iterator;
inline ObjectIterator beginObjects() const { return _objects.begin(); }
inline ObjectIterator endObjects() const { return _objects.end(); }
Commit: 536eb12185871cacc1f12753e65da8320beeb8cd
https://github.com/scummvm/scummvm/commit/536eb12185871cacc1f12753e65da8320beeb8cd
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Fix reading floor brightness
Changed paths:
engines/alcachofa/shape.cpp
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 82ed0baf7e8..71b847550a6 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -198,11 +198,11 @@ static Color colorAtForConvex(const FloorColorPolygon &p, Point query) {
// This is a quite literal translation of the original engine
// There may very well be a better way than this...
float weights[FloorColorShape::kPointsPerPolygon];
- memset(weights, 0, sizeof(weights));
+ fill(weights, weights + FloorColorShape::kPointsPerPolygon, 0.0f);
for (uint i = 0; i < p._points.size(); i++) {
EdgeDistances distances = p.edgeDistances(i, query);
- float edgeWeight = distances._toEdge * distances._onEdge / distances._edgeLength;
+ float edgeWeight = distances._toEdge * ABS(distances._onEdge) / distances._edgeLength;
if (distances._edgeLength > 1) {
weights[i] += edgeWeight;
weights[i + 1 == p._points.size() ? 0 : i + 1] += edgeWeight;
@@ -638,16 +638,19 @@ FloorColorShape::FloorColorShape(ReadStream &stream) {
for (int i = 0; i < polygonCount; i++) {
for (int j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(stream));
- Color color; // RGB and A components are stored separately
+
+ // 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
+ int firstColorI = _pointColors.size();
+ _pointColors.resize(_pointColors.size() + kPointsPerPolygon);
for (int j = 0; j < kPointsPerPolygon; j++)
- color.a = stream.readByte();
+ _pointColors[firstColorI + j].a = (uint8)MIN(255, stream.readByte() * 255 / 100);
for (int j = 0; j < kPointsPerPolygon; j++) {
- color.r = stream.readByte();
- color.g = stream.readByte();
- color.b = stream.readByte();
+ _pointColors[firstColorI + j].r = stream.readByte();
+ _pointColors[firstColorI + j].g = stream.readByte();
+ _pointColors[firstColorI + j].b = stream.readByte();
stream.readByte(); // second alpha value is ignored
}
- _pointColors.push_back(color);
stream.readByte(); // unused byte per polygon
uint pointCount = addPolygon(kPointsPerPolygon);
Commit: 39d0d54da030bdb50c4836a02ab9dfa3e62c27c1
https://github.com/scummvm/scummvm/commit/39d0d54da030bdb50c4836a02ab9dfa3e62c27c1
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Increase read buffer for less latency on playing voice lines
Changed paths:
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 0425b35bf08..433eadf9326 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -152,8 +152,8 @@ SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::So
samples.resize((uint)sampleCount); // we might have gotten less samples
}
else {
- // we did not, not it is getting inefficient
- const int bufferSize = 512;
+ // we did not, now it is getting inefficient
+ const int bufferSize = 2048;
int16 buffer[bufferSize];
int chunkSampleCount;
do {
Commit: 2c4810728866f14f05d2ba8d8df0f88a1ebdb3a9
https://github.com/scummvm/scummvm/commit/2c4810728866f14f05d2ba8d8df0f88a1ebdb3a9
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:55+02:00
Commit Message:
ALCACHOFA: Add character lighting
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/objects.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 5b22b62a451..709d76053a9 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -1099,6 +1099,18 @@ FloorColor::FloorColor(Room *room, ReadStream &stream)
, _shape(stream) {
}
+void FloorColor::update() {
+ auto updateFor = [&] (MainCharacter &character) {
+ if (character.room() == room()) {
+ const auto result = _shape.colorAt(character.position());
+ if (result.first)
+ character.color() = { 255, 255, 255, result.second.a };
+ }
+ };
+ updateFor(g_engine->world().mortadelo());
+ updateFor(g_engine->world().filemon());
+}
+
void FloorColor::drawDebug() {
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
if (!g_engine->console().showFloorColor() || renderer == nullptr || !isEnabled())
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 3af9ef3e4a2..21bf8d042e0 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -549,6 +549,7 @@ public:
FloorColor(Room *room, Common::ReadStream &stream);
virtual ~FloorColor() override = default;
+ virtual void update() override;
virtual void drawDebug() override;
virtual Shape *shape() override;
virtual const char *typeName() const;
Commit: 1322ef52ca42c48082f1c9160ce012fbea2bc209
https://github.com/scummvm/scummvm/commit/1322ef52ca42c48082f1c9160ce012fbea2bc209
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Add music
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/detection.cpp
engines/alcachofa/detection.h
engines/alcachofa/global-ui.cpp
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index fa9636fb0df..b9437cac40c 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -111,6 +111,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
Video::MPEGPSDecoder decoder;
if (!decoder.loadFile(Common::Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1))))
error("Could not find video %d", videoId);
+ _sounds.stopAll();
auto texture = _renderer->createTexture(decoder.getWidth(), decoder.getHeight(), false);
decoder.start();
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index ce292b94454..23b2d7543d6 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -33,6 +33,7 @@ const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
{ Alcachofa::kDebugGraphics, "Graphics", "Graphics debug level" },
{ Alcachofa::kDebugScript, "Script", "Enable debug script dump" },
{ Alcachofa::kDebugGameplay, "Gameplay", "Gameplay-related tracing" },
+ { Alcachofa::kDebugSounds, "Sounds", "Sound- and Music-related tracing" },
DEBUG_CHANNEL_END
};
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 20a4f8bfa59..dfe83a6b8dc 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -29,7 +29,8 @@ namespace Alcachofa {
enum AlcachofaDebugChannels {
kDebugGraphics = 1,
kDebugScript,
- kDebugGameplay
+ kDebugGameplay,
+ kDebugSounds,
};
extern const PlainGameDescriptor alcachofaGames[];
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index dab2ecfe071..297a3598c54 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -144,7 +144,14 @@ bool GlobalUI::updateChangingCharacter() {
g_engine->camera().setFollow(player.activeCharacter());
g_engine->camera().restore(0);
player.changeRoom(player.activeCharacter()->room()->name(), false);
- // TODO: Queue character change jingle
+
+ int32 characterJingle = g_engine->script().variable(
+ player.activeCharacterKind() == MainCharacterKind::Mortadelo
+ ? "PistaMorta"
+ : "PistaFile"
+ );
+ g_engine->sounds().startMusic(characterJingle);
+ g_engine->sounds().queueMusic(player.currentRoom()->musicID());
_changeButton.setAnimation(activeAnimation());
_changeButton.start(false);
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index adcc5e26713..afd09b36646 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -215,11 +215,15 @@ struct DoorTask : public Task {
}
virtual TaskReturn run() {
+ FakeLock musicLock;
+
TASK_BEGIN;
if (_targetRoom == nullptr || _targetObject == nullptr)
return TaskReturn::finish(1);
- // TODO: Fade out music on room change
+ musicLock = FakeLock(g_engine->sounds().musicSemaphore());
+ if (g_engine->sounds().musicID() != _targetRoom->musicID())
+ g_engine->sounds().fadeMusic();
TASK_WAIT(fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
_player.changeRoom(_targetRoom->name(), true);
@@ -232,7 +236,9 @@ struct DoorTask : public Task {
g_engine->camera().setFollow(_character, true);
}
- // TODO: Start music on room change
+ g_engine->sounds().setMusicToRoom(_targetRoom->musicID());
+ musicLock.release();
+
if (g_engine->script().createProcess(_character->kind(), "ENTRAR_" + _targetRoom->name(), ScriptFlags::AllowMissing))
TASK_YIELD;
else
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 96ee8d78744..3d4d9bad547 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -87,7 +87,7 @@ static ObjectBase *readRoomObject(Room *room, const String &type, ReadStream &st
Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
: _world(world) {
_name = readVarString(stream);
- _musicId = stream.readSByte();
+ _musicId = (int)stream.readByte();
_characterAlphaTint = stream.readByte();
auto backgroundScale = stream.readSint16LE();
_floors[0] = PathFindingShape(stream);
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 976ad66fd28..a35b2293a9c 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -48,6 +48,7 @@ public:
inline uint8 characterAlphaTint() const { return _characterAlphaTint; }
inline uint8 characterAlphaPremultiplier() const { return _characterAlphaPremultiplier; }
inline bool fixedCameraOnEntering() const { return _fixedCameraOnEntering; }
+ inline int musicID() const { return _musicId; }
using ObjectIterator = Common::Array<ObjectBase *>::const_iterator;
inline ObjectIterator beginObjects() const { return _objects.begin(); }
@@ -77,9 +78,8 @@ protected:
Common::String _name;
PathFindingShape _floors[2];
bool _fixedCameraOnEntering;
- int8
- _musicId,
- _activeFloorI = -1;
+ int8 _activeFloorI = -1;
+ int _musicId = -1;
uint8
_characterAlphaTint,
_characterAlphaPremultiplier; ///< for some reason in percent instead of 0-255
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 67617c661f4..52cc49bb07c 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -486,10 +486,12 @@ private:
: TaskReturn::finish(1);
}
case ScriptKernelTask::PlayMusic:
- warning("STUB KERNEL CALL: PlayMusic");
+ if (process().isActiveForPlayer())
+ g_engine->sounds().startMusic((int)getNumberArg(0));
return TaskReturn::finish(0);
case ScriptKernelTask::StopMusic:
- warning("STUB KERNEL CALL: StopMusic");
+ if (process().isActiveForPlayer())
+ g_engine->sounds().fadeMusic();
return TaskReturn::finish(0);
case ScriptKernelTask::WaitForMusicToEnd:
warning("STUB KERNEL CALL: WaitForMusicToEnd");
@@ -549,7 +551,7 @@ private:
g_engine->world().inventory().open();
else
g_engine->player().changeRoom(targetRoom->name(), true);
- // TODO: Change music on kernel change room
+ g_engine->sounds().setMusicToRoom(targetRoom->musicID());
}
g_engine->script().createProcess(process().character(), "ENTRAR_" + targetRoom->name(), ScriptFlags::AllowMissing);
}
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 433eadf9326..90e5ae56b31 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -22,6 +22,7 @@
#include "sounds.h"
#include "rooms.h"
#include "alcachofa.h"
+#include "detection.h"
#include "common/file.h"
#include "common/substream.h"
@@ -49,24 +50,19 @@ Sounds::~Sounds() {
}
Sounds::Playback *Sounds::getPlaybackById(SoundID id) {
- if (_playbacks.empty())
- return nullptr;
- uint first = 0, last = _playbacks.size() - 1;
- while (first < last) {
- uint mid = (first + last) / 2;
- if (_playbacks[mid]._id == id)
- return _playbacks.data() + mid;
- else if (_playbacks[mid]._id < id)
- first = mid + 1;
- else
- last = mid == 0 ? 0 : mid - 1;
- }
- return first == last && first < _playbacks.size()
- ? _playbacks.data() + first
- : nullptr;
+ auto itPlayback = find_if(_playbacks.begin(), _playbacks.end(),
+ [&] (const Playback &playback) { return playback._id == id; });
+ return itPlayback == _playbacks.end() ? nullptr : itPlayback;
}
void Sounds::update() {
+ if (_isMusicPlaying && !isAlive(_musicSoundID)) {
+ if (_nextMusicID < 0)
+ fadeMusic();
+ else
+ startMusic(_nextMusicID);
+ }
+
for (uint i = _playbacks.size(); i > 0; i--) {
Playback &playback = _playbacks[i - 1];
if (!_mixer->isSoundHandleActive(playback._handle))
@@ -115,12 +111,12 @@ static AudioStream *loadSND(File *file) {
}
}
-static AudioStream *openAudio(const String &fileName) {
- String path = String::format("Sonidos/%s.SND", fileName.c_str());
+static AudioStream *openAudio(const char *fileName) {
+ String path = String::format("Sonidos/%s.SND", fileName);
File *file = new File();
if (file->open(path.c_str()))
- return file->size() == 0
- ? makeSilentAudioStream(8000, false) // Movie Adventure has some null-size audio files, they are treated like infinite samples
+ 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);
@@ -133,7 +129,7 @@ static AudioStream *openAudio(const String &fileName) {
return nullptr;
}
-SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::SoundType type) {
+SoundID Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::SoundType type) {
AudioStream *stream = openAudio(fileName);
if (stream == nullptr)
return UINT32_MAX;
@@ -192,14 +188,23 @@ SoundID Sounds::playSoundInternal(const String &fileName, byte volume, Mixer::So
}
SoundID Sounds::playVoice(const String &fileName, byte volume) {
- return playSoundInternal(fileName, volume, Mixer::kSpeechSoundType);
+ debugC(1, kDebugSounds, "Play voice: %s at %d", fileName.c_str(), (int)volume);
+ return playSoundInternal(fileName.c_str(), volume, Mixer::kSpeechSoundType);
}
SoundID Sounds::playSFX(const String &fileName, byte volume) {
- return playSoundInternal(fileName, volume, Mixer::kSFXSoundType);
+ debugC(1, kDebugSounds, "Play SFX: %s at %d", fileName.c_str(), (int)volume);
+ return playSoundInternal(fileName.c_str(), volume, Mixer::kSFXSoundType);
+}
+
+void Sounds::stopAll() {
+ debugC(1, kDebugSounds, "Stop all sounds");
+ _mixer->stopAll();
+ _playbacks.clear();
}
void Sounds::stopVoice() {
+ debugC(1, kDebugSounds, "Stop all voices");
for (uint i = _playbacks.size(); i > 0; i--) {
if (_playbacks[i - 1]._type == Mixer::kSpeechSoundType) {
_mixer->stopHandle(_playbacks[i - 1]._handle);
@@ -282,6 +287,49 @@ bool Sounds::isNoisy(SoundID id, float windowSize, float minDifferences) {
return sumOfDifferences / 256.0f >= minDifferences;
}
+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);
+ _isMusicPlaying = true;
+ _nextMusicID = musicId;
+}
+
+void Sounds::queueMusic(int musicId) {
+ debugC(2, kDebugSounds, "queueMusic %d", musicId);
+ _nextMusicID = musicId;
+}
+
+void Sounds::fadeMusic(uint32 duration) {
+ debugC(2, kDebugSounds, "fadeMusic");
+ fadeOut(_musicSoundID, duration);
+ _isMusicPlaying = false;
+ _nextMusicID = -1;
+ _musicSoundID = {};
+}
+
+void Sounds::setMusicToRoom(int roomMusicId) {
+ // Alcachofa Soft used IDs > 200 to mean "no change in music"
+ if (roomMusicId == _nextMusicID || roomMusicId > 200) {
+ debugC(1, kDebugSounds, "setMusicToRoom: from %d to %d, not executed", _nextMusicID, roomMusicId);
+ return;
+ }
+ debugC(1, kDebugSounds, "setMusicToRoom: from %d to %d", _nextMusicID, roomMusicId);
+ if (roomMusicId > 0)
+ startMusic(roomMusicId);
+ else
+ fadeMusic();
+}
+
+Task *Sounds::waitForMusicToEnd(Process &process) {
+ FakeLock lock(_musicSemaphore);
+ return new WaitForMusicTask(process, std::move(lock));
+}
+
PlaySoundTask::PlaySoundTask(Process &process, SoundID soundID)
: Task(process)
, _soundID(soundID) {
@@ -302,4 +350,19 @@ void PlaySoundTask::debugPrint() {
g_engine->console().debugPrintf("PlaySound %u\n", _soundID);
}
+WaitForMusicTask::WaitForMusicTask(Process &process, FakeLock &&lock)
+ : Task(process)
+ , _lock(std::move(lock)) {}
+
+TaskReturn WaitForMusicTask::run() {
+ g_engine->sounds().queueMusic(-1);
+ return g_engine->sounds().isMusicPlaying()
+ ? TaskReturn::yield()
+ : TaskReturn::finish(0);
+}
+
+void WaitForMusicTask::debugPrint() {
+ g_engine->console().debugPrintf("WaitForMusic\n");
+}
+
}
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index ca320fb427f..c34f1571d32 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -40,6 +40,7 @@ public:
void update();
SoundID playVoice(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
SoundID playSFX(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
+ void stopAll();
void stopVoice();
void fadeOut(SoundID id, uint32 duration);
void fadeOutVoiceAndSFX(uint32 duration);
@@ -50,6 +51,15 @@ public:
Character *speakingCharacter);
bool isNoisy(SoundID id, float windowSize, float minDifferences); ///< used for lip-sync
+ void startMusic(int musicId);
+ void queueMusic(int musicId);
+ void fadeMusic(uint32 duration = 500);
+ void setMusicToRoom(int roomMusicId);
+ Task *waitForMusicToEnd(Process &processd);
+ inline bool isMusicPlaying() const { return _isMusicPlaying; }
+ inline int musicID() const { return _nextMusicID; }
+ inline FakeSemaphore &musicSemaphore() { return _musicSemaphore; }
+
private:
struct Playback {;
void fadeOut(uint32 duration);
@@ -63,11 +73,16 @@ private:
Common::Array<int16> _samples; ///< might not be filled, only voice samples are preloaded for lip-sync
};
Playback *getPlaybackById(SoundID id);
- SoundID playSoundInternal(const Common::String &fileName, byte volume, Audio::Mixer::SoundType type);
+ SoundID playSoundInternal(const char *fileName, byte volume, Audio::Mixer::SoundType type);
Common::Array<Playback> _playbacks;
Audio::Mixer *_mixer;
SoundID _nextID = 1;
+
+ SoundID _musicSoundID = kInvalidSoundID; // we use another soundID to reuse fading
+ bool _isMusicPlaying = false;
+ int _nextMusicID = -1;
+ FakeSemaphore _musicSemaphore;
};
struct PlaySoundTask final : public Task {
@@ -78,6 +93,14 @@ private:
SoundID _soundID;
};
+struct WaitForMusicTask final : public Task {
+ WaitForMusicTask(Process &process, FakeLock &&lock);
+ virtual TaskReturn run() override;
+ virtual void debugPrint() override;
+private:
+ FakeLock _lock;
+};
+
}
#endif // SOUNDS_H
Commit: 4ad1f1f806f429ddaddbc3e1e6ff5a5d1793c3af
https://github.com/scummvm/scummvm/commit/4ad1f1f806f429ddaddbc3e1e6ff5a5d1793c3af
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Add keymap and input handling to open menu
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/input.cpp
engines/alcachofa/input.h
engines/alcachofa/metaengine.cpp
engines/alcachofa/metaengine.h
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 297a3598c54..7a3521addf4 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -247,4 +247,16 @@ void GlobalUI::drawScreenStates() {
}
}
+void GlobalUI::updateOpeningMenu() {
+ if (_openMenuAtNextFrame) {
+ _openMenuAtNextFrame = false;
+ debug("Open menu");
+ // TODO: Actually open menu
+ return;
+ }
+ _openMenuAtNextFrame =
+ g_engine->input().wasMenuKeyPressed() &&
+ g_engine->player().isAllowedToOpenMenu();
+}
+
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 8892ebf3e27..19fb164a242 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -42,6 +42,7 @@ public:
bool updateOpeningInventory();
void updateClosingInventory();
void startClosingInventory();
+ void updateOpeningMenu();
void drawScreenStates(); // black borders and/or permanent fade
private:
@@ -60,7 +61,8 @@ private:
bool
_isOpeningInventory = false,
_isClosingInventory = false,
- _isPermanentFaded = false;
+ _isPermanentFaded = false,
+ _openMenuAtNextFrame = false;
uint32 _timeForInventory = 0;
};
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index 0f72e747fc8..7d92310d54d 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -21,6 +21,7 @@
#include "input.h"
#include "alcachofa.h"
+#include "metaengine.h"
using namespace Common;
@@ -34,6 +35,7 @@ void Input::nextFrame() {
_wasMouseRightPressed = false;
_wasMouseLeftReleased = false;
_wasMouseRightReleased = false;
+ _wasMenuKeyPressed = false;
updateMousePos3D(); // camera transformation might have changed
}
@@ -67,6 +69,12 @@ bool Input::handleEvent(const Common::Event &event) {
_mousePos2D = event.mouse;
updateMousePos3D();
return true;
+ case EVENT_CUSTOM_ENGINE_ACTION_START:
+ switch ((InputAction)event.customType) {
+ case InputAction::Menu:
+ _wasMenuKeyPressed = true;
+ return true;
+ }
}
default:
return false;
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index e80287fd61e..770a0c213bd 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -38,6 +38,7 @@ public:
inline bool isMouseLeftDown() const { return _isMouseLeftDown; }
inline bool isMouseRightDown() const { return _isMouseRightDown; }
inline bool isAnyMouseDown() const { return _isMouseLeftDown || _isMouseRightDown; }
+ inline bool wasMenuKeyPressed() const { return _wasMenuKeyPressed; }
inline Common::Point mousePos2D() const { return _mousePos2D; }
inline Common::Point mousePos3D() const { return _mousePos3D; }
const Input &debugInput() const { scumm_assert(_debugInput != nullptr); return *_debugInput; }
@@ -55,7 +56,8 @@ private:
_wasMouseLeftReleased = false,
_wasMouseRightReleased = false,
_isMouseLeftDown = false,
- _isMouseRightDown = false;
+ _isMouseRightDown = false,
+ _wasMenuKeyPressed = false;
Common::Point
_mousePos2D,
_mousePos3D;
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index a31dbd032d3..d392c26959b 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -20,10 +20,16 @@
*/
#include "common/translation.h"
+#include "backends/keymapper/keymapper.h"
+#include "backends/keymapper/action.h"
+#include "backends/keymapper/standard-actions.h"
#include "alcachofa/metaengine.h"
#include "alcachofa/alcachofa.h"
+using namespace Common;
+using namespace Alcachofa;
+
namespace Alcachofa {
static const ADExtraGuiOptionsMap optionsList[] = {
@@ -51,9 +57,9 @@ const ADExtraGuiOptionsMap *AlcachofaMetaEngine::getAdvancedExtraGuiOptions() co
return Alcachofa::optionsList;
}
-Common::Error AlcachofaMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+Error AlcachofaMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
*engine = new Alcachofa::AlcachofaEngine(syst, desc);
- return Common::kNoError;
+ return kNoError;
}
bool AlcachofaMetaEngine::hasFeature(MetaEngineFeature f) const {
@@ -61,6 +67,32 @@ bool AlcachofaMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup);
}
+KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
+ Keymap *keymap = new Keymap(Keymap::kKeymapTypeGame, "alcachofa-default", _("Default keymappings"));
+
+ Action *act;
+
+ act = new Action(kStandardActionLeftClick, _("Activate"));
+ act->setLeftClickEvent();
+ act->addDefaultInputMapping("MOUSE_LEFT");
+ act->addDefaultInputMapping("JOY_A");
+ keymap->addAction(act);
+
+ act = new Action(kStandardActionRightClick, _("Look at"));
+ act->setRightClickEvent();
+ act->addDefaultInputMapping("MOUSE_RIGHT");
+ act->addDefaultInputMapping("JOY_B");
+ keymap->addAction(act);
+
+ act = new Action("MENU", _("Menu"));
+ act->setCustomEngineActionEvent((CustomEventType)InputAction::Menu);
+ act->addDefaultInputMapping("ESCAPE");
+ act->addDefaultInputMapping("JOY_START");
+ keymap->addAction(act);
+
+ return Keymap::arrayOf(keymap);
+}
+
#if PLUGIN_ENABLED_DYNAMIC(ALCACHOFA)
REGISTER_PLUGIN_DYNAMIC(ALCACHOFA, PLUGIN_TYPE_ENGINE, AlcachofaMetaEngine);
#else
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index e50800e10ca..58d9f56607f 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -24,6 +24,14 @@
#include "engines/advancedDetector.h"
+namespace Alcachofa {
+
+enum class InputAction {
+ Menu
+};
+
+}
+
class AlcachofaMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override;
@@ -38,6 +46,10 @@ public:
bool hasFeature(MetaEngineFeature f) const override;
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+
+ Common::KeymapArray initKeymaps(const char *target) const override;
+
+
};
#endif // ALCACHOFA_METAENGINE_H
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index afd09b36646..bb3201504ce 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -295,4 +295,12 @@ void Player::setActiveCharacter(MainCharacterKind kind) {
_activeCharacter = &g_engine->world().getMainCharacterByKind(kind);
}
+bool Player::isAllowedToOpenMenu() {
+ return
+ isGameLoaded() &&
+ !isOptionsMenuOpen() &&
+ g_engine->sounds().musicSemaphore().isReleased() &&
+ !g_engine->script().variable("prohibirESC");
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 638f62bb207..5de1eaeed63 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -58,6 +58,7 @@ public:
void addLastDialogCharacter(Character *character);
void stopLastDialogCharacters();
void setActiveCharacter(MainCharacterKind kind);
+ bool isAllowedToOpenMenu();
private:
static constexpr const int kMaxLastDialogCharacters = 4;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 3d4d9bad547..80910a5f47c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -197,7 +197,7 @@ bool Room::updateInput() {
if (player.currentRoom() == this) {
g_engine->globalUI().drawChangingButton();
- // TODO: Add main menu handling
+ g_engine->globalUI().updateOpeningMenu();
}
return player.currentRoom() == this;
Commit: 446ca8765ba2ae8b8f3d14244024b086599a291d
https://github.com/scummvm/scummvm/commit/446ca8765ba2ae8b8f3d14244024b086599a291d
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Open main menu and add MenuButtton
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 7a3521addf4..5b9055b89d8 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -78,7 +78,7 @@ void GlobalUI::updateClosingInventory() {
bool GlobalUI::updateOpeningInventory() {
static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
- if (g_engine->player().isOptionsMenuOpen() || !g_engine->player().isGameLoaded())
+ if (g_engine->player().isMenuOpen() || !g_engine->player().isGameLoaded())
return false;
if (_isOpeningInventory) {
@@ -123,7 +123,7 @@ bool GlobalUI::isHoveringChangeButton() const {
bool GlobalUI::updateChangingCharacter() {
auto &player = g_engine->player();
- if (player.isOptionsMenuOpen() ||
+ if (player.isMenuOpen() ||
!player.isGameLoaded() ||
_isOpeningInventory)
return false;
@@ -160,7 +160,7 @@ bool GlobalUI::updateChangingCharacter() {
void GlobalUI::drawChangingButton() {
auto &player = g_engine->player();
- if (player.isOptionsMenuOpen() ||
+ if (player.isMenuOpen() ||
!player.isGameLoaded() ||
!player.semaphore().isReleased() ||
_isOpeningInventory ||
@@ -233,7 +233,7 @@ Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs)
}
void GlobalUI::drawScreenStates() {
- if (g_engine->player().isOptionsMenuOpen())
+ if (g_engine->player().isMenuOpen())
return;
auto &drawQueue = g_engine->drawQueue();
@@ -248,15 +248,28 @@ void GlobalUI::drawScreenStates() {
}
void GlobalUI::updateOpeningMenu() {
- if (_openMenuAtNextFrame) {
- _openMenuAtNextFrame = false;
- debug("Open menu");
- // TODO: Actually open menu
+ if (!_openMenuAtNextFrame) {
+ _openMenuAtNextFrame =
+ g_engine->input().wasMenuKeyPressed() &&
+ g_engine->player().isAllowedToOpenMenu();
return;
}
- _openMenuAtNextFrame =
- g_engine->input().wasMenuKeyPressed() &&
- g_engine->player().isAllowedToOpenMenu();
+ _openMenuAtNextFrame = false;
+
+ g_engine->sounds().pauseAll(true);
+ // TODO: Add game time behaviour on opening menu
+ g_engine->player().isMenuOpen() = true;
+ // TODO: Render thumbnail
+ g_engine->player().changeRoom("MENUPRINCIPAL", true);
+ // TODO: Check original read lastSaveFileFileId and read options.cfg, we do not need that right?
+
+ g_engine->player().heldItem() = nullptr;
+ g_engine->scheduler().backupContext();
+ g_engine->camera().backup(1);
+ g_engine->camera().setPosition(Math::Vector3d(
+ g_system->getWidth() / 2.0f, g_system->getHeight() / 2.0f, 0.0f));
+
+ // TODO: Load thumbnail into capture graphic object
}
}
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 21bf8d042e0..7adb67c313b 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -162,9 +162,24 @@ public:
virtual ~MenuButton() override = default;
inline int32 actionId() const { return _actionId; }
+ inline bool &isInteractable() { return _isInteractable; }
+
+ virtual void draw() override;
+ virtual void update() override;
+ virtual void loadResources() override;
+ virtual void freeResources() override;
+ virtual void onHoverStart();
+ virtual void onHoverEnd();
+ virtual void onClick() override;
virtual const char *typeName() const;
+ virtual void trigger();
private:
+ bool
+ _isInteractable = true,
+ _isClicked = false,
+ _isHovered = false,
+ _triggerNextFrame = false;
int32 _actionId;
Graphic
_graphicNormal,
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index bb3201504ce..b87085c4834 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -49,7 +49,7 @@ void Player::resetCursor() {
}
void Player::updateCursor() {
- if (_isOptionsMenuOpen || !_isGameLoaded)
+ if (_isMenuOpen || !_isGameLoaded)
_cursorFrameI = 0;
else if (_selectedObject == nullptr)
_cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
@@ -298,7 +298,7 @@ void Player::setActiveCharacter(MainCharacterKind kind) {
bool Player::isAllowedToOpenMenu() {
return
isGameLoaded() &&
- !isOptionsMenuOpen() &&
+ !isMenuOpen() &&
g_engine->sounds().musicSemaphore().isReleased() &&
!g_engine->script().variable("prohibirESC");
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 5de1eaeed63..52f41016eb7 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -39,7 +39,7 @@ public:
MainCharacter *inactiveCharacter() const;
FakeSemaphore &semaphoreFor(MainCharacterKind kind);
- inline bool &isOptionsMenuOpen() { return _isOptionsMenuOpen; }
+ inline bool &isMenuOpen() { return _isMenuOpen; }
inline bool &isGameLoaded() { return _isGameLoaded; }
inline MainCharacterKind activeCharacterKind() const {
@@ -73,7 +73,7 @@ private:
Item *_heldItem = nullptr;
int32 _cursorFrameI = 0;
bool
- _isOptionsMenuOpen = false,
+ _isMenuOpen = false,
_isGameLoaded = true,
_didLoadGlobalRooms = false;
Character *_lastDialogCharacters[kMaxLastDialogCharacters] = { nullptr };
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 80910a5f47c..0159dc58cbb 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -149,7 +149,7 @@ void Room::update() {
if (!updateInput())
return;
}
- if (!g_engine->player().isOptionsMenuOpen() &&
+ if (!g_engine->player().isMenuOpen() &&
g_engine->player().currentRoom() != &g_engine->world().inventory())
world().globalRoom().updateObjects();
if (g_engine->player().currentRoom() == this)
@@ -183,7 +183,7 @@ bool Room::updateInput() {
bool canInteract = !player.activeCharacter()->isBusy();
// A complicated network condition can prevent interaction at this point
- if (player.isOptionsMenuOpen() || !player.isGameLoaded())
+ if (player.isMenuOpen() || !player.isGameLoaded())
canInteract = true;
if (canInteract) {
player.resetCursor();
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 90e5ae56b31..2646659c287 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -213,6 +213,10 @@ void Sounds::stopVoice() {
}
}
+void Sounds::pauseAll(bool paused) {
+ _mixer->pauseAll(paused);
+}
+
bool Sounds::isAlive(SoundID id) {
Playback *playback = getPlaybackById(id);
return playback != nullptr && _mixer->isSoundHandleActive(playback->_handle);
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index c34f1571d32..0cdf8250405 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -42,6 +42,7 @@ public:
SoundID playSFX(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
void stopAll();
void stopVoice();
+ void pauseAll(bool paused);
void fadeOut(SoundID id, uint32 duration);
void fadeOutVoiceAndSFX(uint32 duration);
bool isAlive(SoundID id);
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 926d960d96b..cc12494e87a 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "alcachofa.h"
#include "objects.h"
#include "rooms.h"
@@ -37,6 +38,75 @@ MenuButton::MenuButton(Room *room, ReadStream &stream)
, _graphicDisabled(stream) {
}
+void MenuButton::draw() {
+ if (!isEnabled())
+ return;
+ Graphic &graphic =
+ !_isInteractable ? _graphicDisabled
+ : _isClicked ? _graphicClicked
+ : _isHovered ? _graphicHovered
+ : _graphicNormal;
+ graphic.update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(graphic, true, BlendMode::AdditiveAlpha);
+}
+
+void MenuButton::update() {
+ PhysicalObject::update();
+ if (!_isClicked)
+ return;
+
+ _graphicClicked.update();
+ if (!_graphicClicked.isPaused())
+ return;
+
+ if (!_triggerNextFrame) {
+ // another delay probably to show the last frame of animation
+ _triggerNextFrame = true;
+ return;
+ }
+
+ _triggerNextFrame = false;
+ _isClicked = false;
+ trigger();
+}
+
+void MenuButton::loadResources() {
+ _graphicNormal.loadResources();
+ _graphicHovered.loadResources();
+ _graphicClicked.loadResources();
+ _graphicDisabled.loadResources();
+}
+
+void MenuButton::freeResources() {
+ _graphicNormal.freeResources();
+ _graphicHovered.freeResources();
+ _graphicClicked.freeResources();
+ _graphicDisabled.freeResources();
+}
+
+void MenuButton::onHoverStart() {
+ PhysicalObject::onHoverStart();
+ _isHovered = true;
+}
+
+void MenuButton::onHoverEnd() {
+ PhysicalObject::onHoverEnd();
+ _isHovered = false;
+}
+
+void MenuButton::onClick() {
+ if (_isInteractable) {
+ _isClicked = true;
+ _triggerNextFrame = false;
+ _graphicClicked.start(false);
+ }
+}
+
+void MenuButton::trigger() {
+ // all menu buttons should be inherited and override trigger
+ warning("Unimplemented %s %s action %d", typeName(), name().c_str(), _actionId);
+}
+
const char *InternetMenuButton::typeName() const { return "InternetMenuButton"; }
InternetMenuButton::InternetMenuButton(Room *room, ReadStream &stream)
Commit: 65a7d3154150491db72743129aeba866136eb224
https://github.com/scummvm/scummvm/commit/65a7d3154150491db72743129aeba866136eb224
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Add first main menu actions
- ContinueGame
- InternetMenu
- Exit
- NewGame
Changed paths:
A engines/alcachofa/menu.cpp
A engines/alcachofa/menu.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/module.mk
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index b9437cac40c..3dbb24b126c 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -32,9 +32,11 @@
#include "alcachofa.h"
#include "console.h"
#include "detection.h"
+#include "player.h"
#include "rooms.h"
#include "script.h"
#include "global-ui.h"
+#include "menu.h"
#include "debug.h"
#include "game.h"
@@ -42,6 +44,8 @@ using namespace Math;
namespace Alcachofa {
+constexpr uint kDefaultFramerate = 100; // the original target framerate, not critical
+
AlcachofaEngine *g_engine;
AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc)
@@ -72,12 +76,13 @@ Common::Error AlcachofaEngine::run() {
_script.reset(new Script());
_player.reset(new Player());
_globalUI.reset(new GlobalUI());
+ _menu.reset(new Menu());
_script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
_scheduler.run();
Common::Event e;
- Graphics::FrameLimiter limiter(g_system, 120);
+ Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
while (!shouldQuit()) {
_input.nextFrame();
while (g_system->getEventManager()->pollEvent(e)) {
@@ -135,7 +140,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
if (_input.handleEvent(e))
continue;
}
- if (_input.wasAnyMouseReleased())
+ if (_input.wasAnyMouseReleased() || _input.wasMenuKeyPressed())
break;
g_system->updateScreen();
@@ -144,6 +149,36 @@ void AlcachofaEngine::playVideo(int32 videoId) {
decoder.stop();
}
+void AlcachofaEngine::fadeExit() {
+ constexpr uint kFadeOutDuration = 1000;
+ Event e;
+ Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
+ uint32 startTime = g_system->getMillis();
+
+ _renderer->end(); // we were in a frame, let's exit
+ while (g_system->getMillis() - startTime < kFadeOutDuration && !shouldQuit()) {
+ _input.nextFrame();
+ while (g_system->getEventManager()->pollEvent(e)) {
+ if (_input.handleEvent(e))
+ continue;
+ }
+
+ _renderer->begin();
+ _drawQueue->clear();
+ float t = ((float)(g_system->getMillis() - startTime)) / kFadeOutDuration;
+ // TODO: Implement cross-fade and add to fadeExit
+ _drawQueue->add<FadeDrawRequest>(FadeType::ToBlack, t, -kForegroundOrderCount);
+ _drawQueue->draw();
+ _renderer->end();
+
+ limiter.delayBeforeSwap();
+ limiter.startFrame();
+ }
+
+ quitGame();
+ player().changeRoom("SALIR", false); // this skips some update steps along the way
+}
+
void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param) {
switch (mode)
{
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 5d6ceb64cbd..d01dd27d106 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -51,6 +51,7 @@ class DrawQueue;
class World;
class Script;
class GlobalUI;
+class Menu;
class Game;
struct AlcachofaGameDescription;
@@ -74,12 +75,14 @@ public:
inline World &world() { return *_world; }
inline Script &script() { return *_script; }
inline GlobalUI &globalUI() { return *_globalUI; }
+ inline Menu &menu() { return *_menu; }
inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
inline Game &game() { return *_game; }
inline bool isDebugModeActive() const { return _debugHandler != nullptr; }
void playVideo(int32 videoId);
+ void fadeExit();
void setDebugMode(DebugMode debugMode, int32 param);
uint32 getFeatures() const;
@@ -134,6 +137,7 @@ private:
Common::ScopedPtr<Script> _script;
Common::ScopedPtr<Player> _player;
Common::ScopedPtr<GlobalUI> _globalUI;
+ Common::ScopedPtr<Menu> _menu;
Common::ScopedPtr<Game> _game;
Camera _camera;
Input _input;
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 281a45adf44..2c5225a6219 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -51,6 +51,7 @@ class GameMovieAdventure : public Game {
}
virtual bool shouldTriggerDoor(const Door *door) {
+ // An invalid door target, the character will go to the door and then ignore it (also in original engine)
if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
return false;
return Game::shouldTriggerDoor(door);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 5b9055b89d8..9cf45228071 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -20,6 +20,7 @@
*/
#include "global-ui.h"
+#include "menu.h"
#include "alcachofa.h"
#include "script.h"
@@ -78,7 +79,7 @@ void GlobalUI::updateClosingInventory() {
bool GlobalUI::updateOpeningInventory() {
static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
- if (g_engine->player().isMenuOpen() || !g_engine->player().isGameLoaded())
+ if (g_engine->menu().isOpen() || !g_engine->player().isGameLoaded())
return false;
if (_isOpeningInventory) {
@@ -123,7 +124,7 @@ bool GlobalUI::isHoveringChangeButton() const {
bool GlobalUI::updateChangingCharacter() {
auto &player = g_engine->player();
- if (player.isMenuOpen() ||
+ if (g_engine->menu().isOpen() ||
!player.isGameLoaded() ||
_isOpeningInventory)
return false;
@@ -160,7 +161,7 @@ bool GlobalUI::updateChangingCharacter() {
void GlobalUI::drawChangingButton() {
auto &player = g_engine->player();
- if (player.isMenuOpen() ||
+ if (g_engine->menu().isOpen() ||
!player.isGameLoaded() ||
!player.semaphore().isReleased() ||
_isOpeningInventory ||
@@ -233,7 +234,7 @@ Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs)
}
void GlobalUI::drawScreenStates() {
- if (g_engine->player().isMenuOpen())
+ if (g_engine->menu().isOpen())
return;
auto &drawQueue = g_engine->drawQueue();
@@ -247,29 +248,4 @@ void GlobalUI::drawScreenStates() {
}
}
-void GlobalUI::updateOpeningMenu() {
- if (!_openMenuAtNextFrame) {
- _openMenuAtNextFrame =
- g_engine->input().wasMenuKeyPressed() &&
- g_engine->player().isAllowedToOpenMenu();
- return;
- }
- _openMenuAtNextFrame = false;
-
- g_engine->sounds().pauseAll(true);
- // TODO: Add game time behaviour on opening menu
- g_engine->player().isMenuOpen() = true;
- // TODO: Render thumbnail
- g_engine->player().changeRoom("MENUPRINCIPAL", true);
- // TODO: Check original read lastSaveFileFileId and read options.cfg, we do not need that right?
-
- g_engine->player().heldItem() = nullptr;
- g_engine->scheduler().backupContext();
- g_engine->camera().backup(1);
- g_engine->camera().setPosition(Math::Vector3d(
- g_system->getWidth() / 2.0f, g_system->getHeight() / 2.0f, 0.0f));
-
- // TODO: Load thumbnail into capture graphic object
-}
-
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 19fb164a242..8892ebf3e27 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -42,7 +42,6 @@ public:
bool updateOpeningInventory();
void updateClosingInventory();
void startClosingInventory();
- void updateOpeningMenu();
void drawScreenStates(); // black borders and/or permanent fade
private:
@@ -61,8 +60,7 @@ private:
bool
_isOpeningInventory = false,
_isClosingInventory = false,
- _isPermanentFaded = false,
- _openMenuAtNextFrame = false;
+ _isPermanentFaded = false;
uint32 _timeForInventory = 0;
};
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index bee04e2338b..91eebb10fa5 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -146,12 +146,11 @@ public:
virtual void begin() override {
GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
+ GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
_currentLodBias = -1000.0f;
_currentTexture = nullptr;
_currentBlendMode = (BlendMode)-1;
-
- GL_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
- GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+ _isFirstDrawCommand = true;
}
virtual void end() override {
@@ -292,6 +291,7 @@ public:
colors[2] *= colors[3];
}
+ checkFirstDrawCommand();
GL_CALL(glColor4fv(colors));
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
if (_currentTexture != nullptr)
@@ -309,6 +309,7 @@ public:
Span<Vector2d> points,
Color color
) override {
+ checkFirstDrawCommand();
setTexture(nullptr);
setBlendMode(BlendMode::Alpha);
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
@@ -334,6 +335,7 @@ public:
Span<Vector2d> points,
Color color
) override {
+ checkFirstDrawCommand();
setTexture(nullptr);
setBlendMode(BlendMode::Alpha);
GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
@@ -369,10 +371,21 @@ private:
GL_CALL(glLoadIdentity());
}
+ void checkFirstDrawCommand() {
+ // We delay clearing the screen. It is much easier for the game to switch to a
+ // framebuffer before
+ if (!_isFirstDrawCommand)
+ return;
+ _isFirstDrawCommand = false;
+ GL_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+ GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+ }
+
Point _resolution;
OpenGLTexture *_currentTexture = nullptr;
BlendMode _currentBlendMode = (BlendMode)-1;
float _currentLodBias = 0.0f;
+ bool _isFirstDrawCommand = false;
};
IRenderer *IRenderer::createOpenGLRenderer(Point resolution) {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 9102d10868b..4da9cba4a89 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -845,6 +845,7 @@ DrawQueue::DrawQueue(IRenderer *renderer)
}
void DrawQueue::clear() {
+ _allocator.deallocateAll();
memset(_requestsPerOrderCount, 0, sizeof(_requestsPerOrderCount));
memset(_lodBiasPerOrder, 0, sizeof(_lodBiasPerOrder));
}
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
new file mode 100644
index 00000000000..8f7c6f63dd3
--- /dev/null
+++ b/engines/alcachofa/menu.cpp
@@ -0,0 +1,72 @@
+/* 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.h"
+#include "menu.h"
+#include "player.h"
+#include "script.h"
+
+namespace Alcachofa {
+
+void Menu::updateOpeningMenu() {
+ if (!_openAtNextFrame) {
+ _openAtNextFrame =
+ g_engine->input().wasMenuKeyPressed() &&
+ g_engine->player().isAllowedToOpenMenu();
+ return;
+ }
+ _openAtNextFrame = false;
+
+ g_engine->sounds().pauseAll(true);
+ // TODO: Add game time behaviour on opening menu
+ _previousRoom = g_engine->player().currentRoom();
+ _isOpen = true;
+ // TODO: Render thumbnail
+ g_engine->player().changeRoom("MENUPRINCIPAL", true);
+ // TODO: Check original read lastSaveFileFileId and read options.cfg, we do not need that right?
+
+ g_engine->player().heldItem() = nullptr;
+ g_engine->scheduler().backupContext();
+ g_engine->camera().backup(1);
+ g_engine->camera().setPosition(Math::Vector3d(
+ g_system->getWidth() / 2.0f, g_system->getHeight() / 2.0f, 0.0f));
+
+ // TODO: Load thumbnail into capture graphic object
+}
+
+void Menu::continueGame() {
+ assert(_previousRoom != nullptr);
+ _isOpen = false;
+ g_engine->input().nextFrame(); // presumably to clear all was* flags
+ g_engine->player().changeRoom(_previousRoom->name(), true);
+ g_engine->sounds().pauseAll(false);
+ g_engine->camera().restore(1);
+ g_engine->scheduler().restoreContext();
+ // TODO: Reset time on continueing game
+}
+
+void Menu::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());
+}
+
+}
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
new file mode 100644
index 00000000000..2516257a30d
--- /dev/null
+++ b/engines/alcachofa/menu.h
@@ -0,0 +1,49 @@
+/* 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/>.
+ *
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+#include "common/scummsys.h"
+
+namespace Alcachofa {
+
+class Room;
+
+class Menu {
+public:
+ inline bool isOpen() const { return _isOpen; }
+
+ void updateOpeningMenu();
+ void continueGame();
+ void newGame();
+
+private:
+ bool
+ _isOpen = false,
+ _openAtNextFrame = false;
+
+ Room *_previousRoom = nullptr;
+};
+
+}
+
+#endif // MENU_H
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 5d1eec4b4f8..574ac87bd65 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -2,24 +2,25 @@ MODULE := engines/alcachofa
MODULE_OBJS = \
alcachofa.o \
- camera.cpp \
- common.cpp \
+ camera.o \
+ common.o \
console.o \
- game.cpp \
- game-objects.cpp \
- general-objects.cpp \
- global-ui.cpp \
- graphics.cpp \
- graphics-opengl.cpp \
- input.cpp \
+ game.o \
+ game-objects.o \
+ general-objects.o \
+ global-ui.o \
+ graphics.o \
+ graphics-opengl.o \
+ input.o \
+ menu.o \
metaengine.o \
- player.cpp \
- rooms.cpp \
- scheduler.cpp \
- script.cpp \
- shape.cpp \
- sounds.cpp \
- ui-objects.cpp \
+ player.o \
+ rooms.o \
+ scheduler.o \
+ script.o \
+ shape.o \
+ sounds.o \
+ ui-objects.o
# This module can be built as a plugin
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 7adb67c313b..c6739b0cc2b 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -171,8 +171,8 @@ public:
virtual void onHoverStart();
virtual void onHoverEnd();
virtual void onClick() override;
- virtual const char *typeName() const;
virtual void trigger();
+ virtual const char *typeName() const;
private:
bool
@@ -209,6 +209,8 @@ public:
static constexpr const char *kClassName = "CBotonMenuPrincipal";
MainMenuButton(Room *room, Common::ReadStream &stream);
+ virtual void update() override;
+ virtual void trigger() override;
virtual const char *typeName() const;
};
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index b87085c4834..4ec1cddde44 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -22,6 +22,7 @@
#include "player.h"
#include "script.h"
#include "alcachofa.h"
+#include "menu.h"
using namespace Common;
@@ -49,7 +50,7 @@ void Player::resetCursor() {
}
void Player::updateCursor() {
- if (_isMenuOpen || !_isGameLoaded)
+ if (g_engine->menu().isOpen() || !_isGameLoaded)
_cursorFrameI = 0;
else if (_selectedObject == nullptr)
_cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
@@ -298,7 +299,7 @@ void Player::setActiveCharacter(MainCharacterKind kind) {
bool Player::isAllowedToOpenMenu() {
return
isGameLoaded() &&
- !isMenuOpen() &&
+ !g_engine->menu().isOpen() &&
g_engine->sounds().musicSemaphore().isReleased() &&
!g_engine->script().variable("prohibirESC");
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 52f41016eb7..2541406d5f1 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -39,7 +39,6 @@ public:
MainCharacter *inactiveCharacter() const;
FakeSemaphore &semaphoreFor(MainCharacterKind kind);
- inline bool &isMenuOpen() { return _isMenuOpen; }
inline bool &isGameLoaded() { return _isGameLoaded; }
inline MainCharacterKind activeCharacterKind() const {
@@ -73,7 +72,6 @@ private:
Item *_heldItem = nullptr;
int32 _cursorFrameI = 0;
bool
- _isMenuOpen = false,
_isGameLoaded = true,
_didLoadGlobalRooms = false;
Character *_lastDialogCharacters[kMaxLastDialogCharacters] = { nullptr };
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0159dc58cbb..4dacd66371d 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -23,6 +23,7 @@
#include "rooms.h"
#include "script.h"
#include "global-ui.h"
+#include "menu.h"
#include "common/file.h"
@@ -149,7 +150,7 @@ void Room::update() {
if (!updateInput())
return;
}
- if (!g_engine->player().isMenuOpen() &&
+ if (!g_engine->menu().isOpen() &&
g_engine->player().currentRoom() != &g_engine->world().inventory())
world().globalRoom().updateObjects();
if (g_engine->player().currentRoom() == this)
@@ -183,7 +184,7 @@ bool Room::updateInput() {
bool canInteract = !player.activeCharacter()->isBusy();
// A complicated network condition can prevent interaction at this point
- if (player.isMenuOpen() || !player.isGameLoaded())
+ if (g_engine->menu().isOpen() || !player.isGameLoaded())
canInteract = true;
if (canInteract) {
player.resetCursor();
@@ -197,7 +198,7 @@ bool Room::updateInput() {
if (player.currentRoom() == this) {
g_engine->globalUI().drawChangingButton();
- g_engine->globalUI().updateOpeningMenu();
+ g_engine->menu().updateOpeningMenu();
}
return player.currentRoom() == this;
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index cc12494e87a..33fd81e73f3 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -20,6 +20,9 @@
*/
#include "alcachofa.h"
+#include "script.h"
+#include "global-ui.h"
+#include "menu.h"
#include "objects.h"
#include "rooms.h"
@@ -27,6 +30,19 @@ using namespace Common;
namespace Alcachofa {
+enum class MainMenuAction : int32 {
+ ContinueGame = 0,
+ Save,
+ Load,
+ InternetMenu,
+ OptionsMenu,
+ Exit,
+ NextSave,
+ PrevSave,
+ NewGame,
+ AlsoExit // there seems to be no difference to Exit
+};
+
const char *MenuButton::typeName() const { return "MenuButton"; }
MenuButton::MenuButton(Room *room, ReadStream &stream)
@@ -125,6 +141,51 @@ MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {
}
+void MainMenuButton::update() {
+ MenuButton::update();
+ const auto action = (MainMenuAction)actionId();
+ if (g_engine->input().wasMenuKeyPressed() &&
+ (action == MainMenuAction::ContinueGame || action == MainMenuAction::NewGame))
+ onClick();
+}
+
+void MainMenuButton::trigger() {
+ switch ((MainMenuAction)actionId()) {
+ case MainMenuAction::ContinueGame:
+ g_engine->menu().continueGame();
+ break;
+ case MainMenuAction::Save:
+ warning("STUB: MainMenuAction Save");
+ break;
+ case MainMenuAction::Load:
+ warning("STUB: MainMenuAction Load");
+ break;
+ case MainMenuAction::InternetMenu:
+ g_system->messageBox(LogMessageType::kWarning, "Multiplayer is not implemented in this ScummVM version.");
+ break;
+ case MainMenuAction::OptionsMenu:
+ //g_engine->menu().openOptionsMenu();
+ break;
+ case MainMenuAction::Exit:
+ case MainMenuAction::AlsoExit:
+ // implemented in AlcachofaEngine as it has its own event loop
+ g_engine->fadeExit();
+ break;
+ case MainMenuAction::NextSave:
+ warning("STUB: MainMenuAction NextSave");
+ break;
+ case MainMenuAction::PrevSave:
+ warning("STUB: MainMenuAction PrevSave");
+ break;
+ case MainMenuAction::NewGame:
+ g_engine->menu().newGame();
+ break;
+ default:
+ warning("Unknown main menu action: %d", actionId());
+ break;
+ }
+}
+
const char *PushButton::typeName() const { return "PushButton"; }
PushButton::PushButton(Room *room, ReadStream &stream)
Commit: ee1808a5d1c86ad1ab2eb3f46bbda34b767ecf6e
https://github.com/scummvm/scummvm/commit/ee1808a5d1c86ad1ab2eb3f46bbda34b767ecf6e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Add Config class and GUI options
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/detection.h
engines/alcachofa/detection_tables.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/metaengine.cpp
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 3dbb24b126c..70a6e159e59 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -213,4 +213,27 @@ Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
return Common::kNoError;
}
+Config::Config() {
+ loadFromScummVM();
+}
+
+void Config::loadFromScummVM() {
+ _musicVolume = (uint8)CLIP(ConfMan.getInt("music_volume"), 0, 255);
+ _speechVolume = (uint8)CLIP(ConfMan.getInt("speech_volume"), 0, 255);
+ _subtitles = ConfMan.getBool("subtitles");
+ _highQuality = ConfMan.getBool("high_quality");
+ _bits32 = ConfMan.getBool("32_bits");
+}
+
+void Config::saveToScummVM() {
+ ConfMan.setBool("subtitles", _subtitles);
+ ConfMan.setBool("high_quality", _highQuality);
+ ConfMan.setBool("32_bits", _bits32);
+ ConfMan.setInt("music_volume", _musicVolume);
+ ConfMan.setInt("speech_volume", _speechVolume);
+ ConfMan.setInt("sfx_volume", _speechVolume);
+ // ^ a bit unfortunate, that means if you change in-game it overrides.
+ // if you set it in ScummVMs dialog it sticks
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index d01dd27d106..812ac68ee86 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -55,6 +55,29 @@ class Menu;
class Game;
struct AlcachofaGameDescription;
+class Config {
+public:
+ Config();
+
+ inline bool &subtitles() { return _subtitles; }
+ inline bool &highQuality() { return _highQuality; }
+ inline bool &bits32() { return _bits32; }
+ inline uint8 &musicVolume() { return _musicVolume; }
+ inline uint8 &speechVolume() { return _speechVolume; }
+
+ void loadFromScummVM();
+ void saveToScummVM();
+
+private:
+ bool
+ _subtitles = true,
+ _highQuality = true,
+ _bits32 = true;
+ uint8
+ _musicVolume = 255,
+ _speechVolume = 255;
+};
+
class AlcachofaEngine : public Engine {
private:
const ADGameDescription *_gameDescription;
@@ -79,6 +102,7 @@ public:
inline Scheduler &scheduler() { return _scheduler; }
inline Console &console() { return *_console; }
inline Game &game() { return *_game; }
+ inline Config &config() { return _config; }
inline bool isDebugModeActive() const { return _debugHandler != nullptr; }
void playVideo(int32 videoId);
@@ -143,6 +167,7 @@ private:
Input _input;
Sounds _sounds;
Scheduler _scheduler;
+ Config _config;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index dfe83a6b8dc..8e37d4b99d3 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -37,7 +37,8 @@ extern const PlainGameDescriptor alcachofaGames[];
extern const ADGameDescription gameDescriptions[];
-#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+#define GAMEOPTION_HIGH_QUALITY GUIO_GAMEOPTIONS1 // I should comment what this does, but I don't know
+#define GAMEOPTION_32BITS GUIO_GAMEOPTIONS2
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 49e2ad33df4..5f577de7259 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -34,7 +34,7 @@ const ADGameDescription gameDescriptions[] = {
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_UNSTABLE,
- GUIO1(GUIO_NONE)
+ GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
AD_TABLE_END_MARKER
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 709d76053a9..2cba43125bd 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -313,7 +313,7 @@ struct SayTextTask final : public Task {
if (!isSoundStillPlaying || g_engine->input().wasAnyMouseReleased())
_character->_isTalking = false;
- if (true && // TODO: Add game option for subtitles
+ if (g_engine->config().subtitles() &&
process().isActiveForPlayer()) {
g_engine->drawQueue().add<TextDrawRequest>(
g_engine->globalUI().dialogFont(),
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index d392c26959b..336f589ea9f 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -34,12 +34,23 @@ namespace Alcachofa {
static const ADExtraGuiOptionsMap optionsList[] = {
{
- GAMEOPTION_ORIGINAL_SAVELOAD, // TODO: Remove, this is not really possible
+ GAMEOPTION_HIGH_QUALITY,
{
- _s("Use original save/load screens"),
- _s("Use the original save/load screens instead of the ScummVM ones"),
- "original_menus",
- false,
+ _s("High Quality"),
+ _s("TODO: Explain what this does"),
+ _s("high_quality"),
+ true,
+ 0,
+ 0
+ }
+ },
+ {
+ GAMEOPTION_32BITS,
+ {
+ _s("32 Bits"),
+ _s("TODO: Also explain this, and implement it maybe"),
+ _s("32_bits"),
+ true,
0,
0
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 52cc49bb07c..74efcdc26c0 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -920,7 +920,7 @@ void Script::updateCommonVariables() {
_scriptTimer = 0;
variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
- variable("textoson") = 1; // TODO: Add subtitle option
+ variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
variable("modored") = 0; // this is signalling whether a network connection is established
}
Commit: 74512630d6c1d9b3fe14bb83a3d567b4c4f4a421
https://github.com/scummvm/scummvm/commit/74512630d6c1d9b3fe14bb83a3d567b4c4f4a421
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Add CheckBox and initial options menu
Changed paths:
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/objects.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 8f7c6f63dd3..89d8b01e9b1 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -69,4 +69,39 @@ void Menu::newGame() {
g_engine->script().createProcess(MainCharacterKind::None, g_engine->world().initScriptName());
}
+void Menu::openOptionsMenu() {
+ setOptionsState();
+ g_engine->player().changeRoom("MENUOPCIONES", true);
+}
+
+void Menu::setOptionsState() {
+ Config &config = g_engine->config();
+ Room *optionsMenu = g_engine->world().getRoomByName("MENUOPCIONES");
+ scumm_assert(optionsMenu != nullptr);
+
+ // TODO: Set music/sound volume
+
+ if (!config.bits32())
+ config.highQuality() = false;
+ auto getCheckBox = [&] (const char *name) {
+ CheckBox *checkBox = dynamic_cast<CheckBox *>(optionsMenu->getObjectByName(name));
+ scumm_assert(checkBox != nullptr);
+ return checkBox;
+ };
+ CheckBox
+ *checkSubtitlesOn = getCheckBox("Boton ON"),
+ *checkSubtitlesOff = getCheckBox("Boton OFF"),
+ *check32Bits = getCheckBox("Boton 32 Bits"),
+ *check16Bits = getCheckBox("Boton 16 Bits"),
+ *checkHighQuality = getCheckBox("Boton Alta"),
+ *checkLowQuality = getCheckBox("Boton Baja");
+ checkSubtitlesOn->isChecked() = config.subtitles();
+ checkSubtitlesOff->isChecked() = !config.subtitles();
+ check32Bits->isChecked() = config.bits32();
+ check16Bits->isChecked() = !config.bits32();
+ checkHighQuality->isChecked() = config.highQuality();
+ checkLowQuality->isChecked() = !config.highQuality();
+ checkHighQuality->toggle(config.bits32());
+}
+
}
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 2516257a30d..143b6317401 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -36,11 +36,14 @@ public:
void continueGame();
void newGame();
+ void openOptionsMenu();
+
private:
+ void setOptionsState();
+
bool
_isOpen = false,
_openAtNextFrame = false;
-
Room *_previousRoom = nullptr;
};
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index c6739b0cc2b..d8ea6937ad7 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -125,6 +125,7 @@ public:
virtual ~ShapeObject() override = default;
inline int8 order() const { return _order; }
+ inline bool isSelected() const { return _isSelected; }
virtual void update() override;
virtual void serializeSave(Common::Serializer &serializer) override;
@@ -168,8 +169,8 @@ public:
virtual void update() override;
virtual void loadResources() override;
virtual void freeResources() override;
- virtual void onHoverStart();
- virtual void onHoverEnd();
+ virtual void onHoverStart() override;
+ virtual void onHoverEnd() override;
virtual void onClick() override;
virtual void trigger();
virtual const char *typeName() const;
@@ -251,25 +252,30 @@ public:
CheckBox(Room *room, Common::ReadStream &stream);
virtual ~CheckBox() override = default;
+ inline bool &isChecked() { return _isChecked; }
+ inline int32 actionId() const { return _actionId; }
+
+ virtual void update() override; // also takes the role of draw for some reason
+ virtual void loadResources() override;
+ virtual void freeResources() override;
+ virtual void onHoverStart() override;
+ virtual void onHoverEnd() override;
+ virtual void onClick() override;
+ virtual void trigger();
virtual const char *typeName() const;
private:
- // TODO: Reverse engineer CheckBox
- bool b1;
+ bool
+ _isChecked = false,
+ _wasClicked = false,
+ _isHovered = false;
Graphic
- _graph1,
- _graph2,
- _graph3,
- _graph4;
- int32 _valueId;
-};
-
-class CheckBoxAutoAdjustNoise final : public CheckBox {
-public:
- static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
- CheckBoxAutoAdjustNoise(Room *room, Common::ReadStream &stream);
-
- virtual const char *typeName() const;
+ _graphicUnchecked,
+ _graphicChecked,
+ _graphicHovered,
+ _graphicClicked;
+ int32 _actionId = 0;
+ uint32 _clickTime = 0;
};
class SlideButton final : public ObjectBase {
@@ -290,6 +296,17 @@ private:
_graph3;
};
+// the next UI elements are only used for the multiplayer menus
+// so are currently not needed
+
+class CheckBoxAutoAdjustNoise final : public CheckBox {
+public:
+ static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
+ CheckBoxAutoAdjustNoise(Room *room, Common::ReadStream &stream);
+
+ virtual const char *typeName() const;
+};
+
class IRCWindow final : public ObjectBase {
public:
static constexpr const char *kClassName = "CVentanaIRC";
@@ -310,7 +327,6 @@ public:
virtual const char *typeName() const;
private:
- // TODO: Reverse engineer MessageBox
Graphic
_graph1,
_graph2,
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 33fd81e73f3..089c52a3f0c 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -164,7 +164,7 @@ void MainMenuButton::trigger() {
g_system->messageBox(LogMessageType::kWarning, "Multiplayer is not implemented in this ScummVM version.");
break;
case MainMenuAction::OptionsMenu:
- //g_engine->menu().openOptionsMenu();
+ g_engine->menu().openOptionsMenu();
break;
case MainMenuAction::Exit:
case MainMenuAction::AlsoExit:
@@ -214,12 +214,71 @@ const char *CheckBox::typeName() const { return "CheckBox"; }
CheckBox::CheckBox(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
- , b1(readBool(stream))
- , _graph1(stream)
- , _graph2(stream)
- , _graph3(stream)
- , _graph4(stream)
- , _valueId(stream.readSint32LE()) {
+ , _isChecked(readBool(stream))
+ , _graphicUnchecked(stream)
+ , _graphicChecked(stream)
+ , _graphicHovered(stream)
+ , _graphicClicked(stream)
+ , _actionId(stream.readSint32LE()) {
+}
+
+void CheckBox::update() {
+ PhysicalObject::update();
+ if (!isEnabled())
+ return;
+ Graphic &baseGraphic = _isChecked ? _graphicChecked : _graphicUnchecked;
+ baseGraphic.update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(baseGraphic, true, BlendMode::AdditiveAlpha);
+
+ if (_wasClicked) {
+ if (g_system->getMillis() - _clickTime > 500) {
+ _wasClicked = false;
+ trigger();
+ }
+ }
+ if (_isHovered) {
+ Graphic &hoverGraphic = _wasClicked ? _graphicClicked : _graphicHovered;
+ hoverGraphic.update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(hoverGraphic, true, BlendMode::AdditiveAlpha);
+ }
+
+ // the original engine would stall the application as click delay.
+ // this would prevent bacterios arm in movie adventure being rendered twice for multiple checkboxes
+ // we can instead check the hovered state and prevent the arm (clicked/hovered graphic) being drawn
+}
+
+void CheckBox::loadResources() {
+ _wasClicked = _isHovered = false;
+ _graphicUnchecked.loadResources();
+ _graphicChecked.loadResources();
+ _graphicHovered.loadResources();
+ _graphicClicked.loadResources();
+}
+
+void CheckBox::freeResources() {
+ _graphicUnchecked.freeResources();
+ _graphicChecked.freeResources();
+ _graphicHovered.freeResources();
+ _graphicClicked.freeResources();
+}
+
+void CheckBox::onHoverStart() {
+ PhysicalObject::onHoverStart();
+ _isHovered = true;
+}
+
+void CheckBox::onHoverEnd() {
+ PhysicalObject::onHoverEnd();
+ _isHovered = false;
+}
+
+void CheckBox::onClick() {
+ _wasClicked = true;
+ _clickTime = g_system->getMillis();
+}
+
+void CheckBox::trigger() {
+ debug("CheckBox %d", _actionId);
}
const char *CheckBoxAutoAdjustNoise::typeName() const { return "CheckBoxAutoAdjustNoise"; }
Commit: 563bc6e7cf5749a1d119113a9a29ce0253ce0523
https://github.com/scummvm/scummvm/commit/563bc6e7cf5749a1d119113a9a29ce0253ce0523
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Implement options menu actions
Changed paths:
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/objects.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 89d8b01e9b1..0c50fb2e86c 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -63,10 +63,43 @@ void Menu::continueGame() {
// TODO: Reset time on continueing game
}
-void Menu::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());
+void Menu::triggerMainMenuAction(MainMenuAction action) {
+ switch (action) {
+ case MainMenuAction::ContinueGame:
+ g_engine->menu().continueGame();
+ break;
+ case MainMenuAction::Save:
+ warning("STUB: MainMenuAction Save");
+ break;
+ case MainMenuAction::Load:
+ warning("STUB: MainMenuAction Load");
+ break;
+ case MainMenuAction::InternetMenu:
+ g_system->messageBox(LogMessageType::kWarning, "Multiplayer is not implemented in this ScummVM version.");
+ break;
+ case MainMenuAction::OptionsMenu:
+ g_engine->menu().openOptionsMenu();
+ break;
+ case MainMenuAction::Exit:
+ case MainMenuAction::AlsoExit:
+ // implemented in AlcachofaEngine as it has its own event loop
+ g_engine->fadeExit();
+ break;
+ case MainMenuAction::NextSave:
+ warning("STUB: MainMenuAction NextSave");
+ break;
+ case MainMenuAction::PrevSave:
+ warning("STUB: MainMenuAction PrevSave");
+ 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:
+ warning("Unknown main menu action: %d", (int32)action);
+ break;
+ }
}
void Menu::openOptionsMenu() {
@@ -104,4 +137,45 @@ void Menu::setOptionsState() {
checkHighQuality->toggle(config.bits32());
}
+void Menu::triggerOptionsAction(OptionsMenuAction action) {
+ Config &config = g_engine->config();
+ switch (action) {
+ case OptionsMenuAction::SubtitlesOn:
+ config.subtitles() = true;
+ break;
+ case OptionsMenuAction::SubtitlesOff:
+ config.subtitles() = false;
+ break;
+ case OptionsMenuAction::HighQuality:
+ config.highQuality() = true;
+ break;
+ case OptionsMenuAction::LowQuality:
+ config.highQuality() = false;
+ break;
+ case OptionsMenuAction::Bits32:
+ config.bits32() = true;
+ config.highQuality() = true;
+ break;
+ case OptionsMenuAction::Bits16:
+ config.bits32() = false;
+ break;
+ case OptionsMenuAction::MainMenu:
+ continueMainMenu();
+ break;
+ default:
+ warning("Unknown check box action: %d", (int32)action);
+ }
+ setOptionsState();
+}
+
+void Menu::continueMainMenu() {
+ g_engine->config().saveToScummVM();
+ g_engine->syncSoundSettings();
+ g_engine->player().changeRoom(
+ g_engine->player().isGameLoaded() ? "MENUPRINCIPAL" : "MENUPRINCIPALINICIO",
+ true
+ );
+ // TODO: Update menu state and thumbanil
+}
+
}
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 143b6317401..e35a29e0cf5 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -28,17 +28,42 @@ namespace Alcachofa {
class Room;
+enum class MainMenuAction : int32 {
+ ContinueGame = 0,
+ Save,
+ Load,
+ InternetMenu,
+ OptionsMenu,
+ Exit,
+ NextSave,
+ PrevSave,
+ NewGame,
+ AlsoExit // there seems to be no difference to Exit
+};
+
+enum class OptionsMenuAction : int32 {
+ SubtitlesOn = 0,
+ SubtitlesOff,
+ HighQuality,
+ LowQuality,
+ Bits32,
+ Bits16,
+ MainMenu
+};
+
class Menu {
public:
inline bool isOpen() const { return _isOpen; }
void updateOpeningMenu();
- void continueGame();
- void newGame();
+ void triggerMainMenuAction(MainMenuAction action);
void openOptionsMenu();
+ void triggerOptionsAction(OptionsMenuAction action);
private:
+ void continueGame();
+ void continueMainMenu();
void setOptionsState();
bool
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index d8ea6937ad7..7a2f106e480 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -189,6 +189,9 @@ private:
_graphicDisabled;
};
+// some of the UI elements are only used for the multiplayer menus
+// so are currently not needed
+
class InternetMenuButton final : public MenuButton {
public:
static constexpr const char *kClassName = "CBotonMenuInternet";
@@ -202,6 +205,8 @@ public:
static constexpr const char *kClassName = "CBotonMenuOpciones";
OptionsMenuButton(Room *room, Common::ReadStream &stream);
+ virtual void update() override;
+ virtual void trigger() override;
virtual const char *typeName() const;
};
@@ -223,7 +228,6 @@ public:
virtual const char *typeName() const;
private:
- // TODO: Reverse engineer PushButton
bool _alwaysVisible;
Graphic _graphic1, _graphic2;
int32 _actionId;
@@ -237,7 +241,6 @@ public:
virtual const char *typeName() const;
private:
- // TODO: Reverse engineer EditBox
int32 i1;
Common::Point p1;
Common::String _labelId;
@@ -255,7 +258,8 @@ public:
inline bool &isChecked() { return _isChecked; }
inline int32 actionId() const { return _actionId; }
- virtual void update() override; // also takes the role of draw for some reason
+ virtual void draw() override;
+ virtual void update() override;
virtual void loadResources() override;
virtual void freeResources() override;
virtual void onHoverStart() override;
@@ -296,9 +300,6 @@ private:
_graph3;
};
-// the next UI elements are only used for the multiplayer menus
-// so are currently not needed
-
class CheckBoxAutoAdjustNoise final : public CheckBox {
public:
static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 089c52a3f0c..3750453467a 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -30,19 +30,6 @@ using namespace Common;
namespace Alcachofa {
-enum class MainMenuAction : int32 {
- ContinueGame = 0,
- Save,
- Load,
- InternetMenu,
- OptionsMenu,
- Exit,
- NextSave,
- PrevSave,
- NewGame,
- AlsoExit // there seems to be no difference to Exit
-};
-
const char *MenuButton::typeName() const { return "MenuButton"; }
MenuButton::MenuButton(Room *room, ReadStream &stream)
@@ -135,6 +122,17 @@ OptionsMenuButton::OptionsMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {
}
+void OptionsMenuButton::update() {
+ MenuButton::update();
+ const auto action = (OptionsMenuAction)actionId();
+ if (action == OptionsMenuAction::MainMenu && g_engine->input().wasMenuKeyPressed())
+ onClick();
+}
+
+void OptionsMenuButton::trigger() {
+ g_engine->menu().triggerOptionsAction((OptionsMenuAction)actionId());
+}
+
const char *MainMenuButton::typeName() const { return "MainMenuButton"; }
MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
@@ -150,40 +148,7 @@ void MainMenuButton::update() {
}
void MainMenuButton::trigger() {
- switch ((MainMenuAction)actionId()) {
- case MainMenuAction::ContinueGame:
- g_engine->menu().continueGame();
- break;
- case MainMenuAction::Save:
- warning("STUB: MainMenuAction Save");
- break;
- case MainMenuAction::Load:
- warning("STUB: MainMenuAction Load");
- break;
- case MainMenuAction::InternetMenu:
- g_system->messageBox(LogMessageType::kWarning, "Multiplayer is not implemented in this ScummVM version.");
- break;
- case MainMenuAction::OptionsMenu:
- g_engine->menu().openOptionsMenu();
- break;
- case MainMenuAction::Exit:
- case MainMenuAction::AlsoExit:
- // implemented in AlcachofaEngine as it has its own event loop
- g_engine->fadeExit();
- break;
- case MainMenuAction::NextSave:
- warning("STUB: MainMenuAction NextSave");
- break;
- case MainMenuAction::PrevSave:
- warning("STUB: MainMenuAction PrevSave");
- break;
- case MainMenuAction::NewGame:
- g_engine->menu().newGame();
- break;
- default:
- warning("Unknown main menu action: %d", actionId());
- break;
- }
+ g_engine->menu().triggerMainMenuAction((MainMenuAction)actionId());
}
const char *PushButton::typeName() const { return "PushButton"; }
@@ -222,25 +187,28 @@ CheckBox::CheckBox(Room *room, ReadStream &stream)
, _actionId(stream.readSint32LE()) {
}
-void CheckBox::update() {
- PhysicalObject::update();
+void CheckBox::draw() {
if (!isEnabled())
return;
Graphic &baseGraphic = _isChecked ? _graphicChecked : _graphicUnchecked;
baseGraphic.update();
g_engine->drawQueue().add<AnimationDrawRequest>(baseGraphic, true, BlendMode::AdditiveAlpha);
+ if (_isHovered) {
+ Graphic &hoverGraphic = _wasClicked ? _graphicClicked : _graphicHovered;
+ hoverGraphic.update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(hoverGraphic, true, BlendMode::AdditiveAlpha);
+ }
+}
+
+void CheckBox::update() {
+ PhysicalObject::update();
if (_wasClicked) {
if (g_system->getMillis() - _clickTime > 500) {
_wasClicked = false;
trigger();
}
}
- if (_isHovered) {
- Graphic &hoverGraphic = _wasClicked ? _graphicClicked : _graphicHovered;
- hoverGraphic.update();
- g_engine->drawQueue().add<AnimationDrawRequest>(hoverGraphic, true, BlendMode::AdditiveAlpha);
- }
// the original engine would stall the application as click delay.
// this would prevent bacterios arm in movie adventure being rendered twice for multiple checkboxes
@@ -278,7 +246,7 @@ void CheckBox::onClick() {
}
void CheckBox::trigger() {
- debug("CheckBox %d", _actionId);
+ g_engine->menu().triggerOptionsAction((OptionsMenuAction)actionId());
}
const char *CheckBoxAutoAdjustNoise::typeName() const { return "CheckBoxAutoAdjustNoise"; }
Commit: 5eaa3e56c6f491e1777ae60453a6484b81257c08
https://github.com/scummvm/scummvm/commit/5eaa3e56c6f491e1777ae60453a6484b81257c08
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Fix options menu arm
Changed paths:
engines/alcachofa/general-objects.cpp
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index c240db4a245..4c5e2448c23 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -202,7 +202,7 @@ void ShapeObject::update() {
if (isEnabled())
updateSelection();
else {
- _isSelected = false;
+ _isNewlySelected = false;
_wasSelected = false;
}
}
@@ -239,12 +239,12 @@ void ShapeObject::onClick() {
}
void ShapeObject::markSelected() {
- _isSelected = true;
+ _isNewlySelected = true;
}
void ShapeObject::updateSelection() {
- if (_isSelected) {
- _isSelected = false;
+ if (_isNewlySelected) {
+ _isNewlySelected = false;
if (_wasSelected) {
if (g_engine->input().wasAnyMouseReleased() && g_engine->player().selectedObject() == this)
onClick();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 7a2f106e480..29983523514 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -125,7 +125,8 @@ public:
virtual ~ShapeObject() override = default;
inline int8 order() const { return _order; }
- inline bool isSelected() const { return _isSelected; }
+ inline bool isNewlySelected() const { return _isNewlySelected; }
+ inline bool wasSelected() const { return _wasSelected; }
virtual void update() override;
virtual void serializeSave(Common::Serializer &serializer) override;
@@ -146,7 +147,7 @@ protected:
private:
Shape _shape;
CursorType _cursorType;
- bool _isSelected = false,
+ bool _isNewlySelected = false,
_wasSelected = false;
};
@@ -169,8 +170,7 @@ public:
virtual void update() override;
virtual void loadResources() override;
virtual void freeResources() override;
- virtual void onHoverStart() override;
- virtual void onHoverEnd() override;
+ virtual void onHoverUpdate() override;
virtual void onClick() override;
virtual void trigger();
virtual const char *typeName() const;
@@ -179,7 +179,6 @@ private:
bool
_isInteractable = true,
_isClicked = false,
- _isHovered = false,
_triggerNextFrame = false;
int32 _actionId;
Graphic
@@ -262,8 +261,7 @@ public:
virtual void update() override;
virtual void loadResources() override;
virtual void freeResources() override;
- virtual void onHoverStart() override;
- virtual void onHoverEnd() override;
+ virtual void onHoverUpdate() override;
virtual void onClick() override;
virtual void trigger();
virtual const char *typeName() const;
@@ -271,8 +269,7 @@ public:
private:
bool
_isChecked = false,
- _wasClicked = false,
- _isHovered = false;
+ _wasClicked = false;
Graphic
_graphicUnchecked,
_graphicChecked,
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 4dacd66371d..bcfaedff8ee 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -332,6 +332,33 @@ OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
+bool OptionsMenu::updateInput() {
+ if (!Room::updateInput())
+ return false;
+
+ auto currentSelectedObject = g_engine->player().selectedObject();
+ if (currentSelectedObject == nullptr) {
+ if (_lastSelectedObject == nullptr) {
+ if (_idleArm != nullptr)
+ _idleArm->toggle(true);
+ return true;
+ }
+
+ _lastSelectedObject->markSelected();
+ }
+ else
+ _lastSelectedObject = currentSelectedObject;
+ if (_idleArm != nullptr)
+ _idleArm->toggle(false);
+ return true;
+}
+
+void OptionsMenu::loadResources() {
+ Room::loadResources();
+ _lastSelectedObject = nullptr;
+ _idleArm = getObjectByName("Brazo");
+}
+
ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index a35b2293a9c..945d6b65cf2 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -91,6 +91,13 @@ class OptionsMenu final : public Room {
public:
static constexpr const char *kClassName = "CHabitacionMenuOpciones";
OptionsMenu(World *world, Common::SeekableReadStream &stream);
+
+ virtual bool updateInput() override;
+ virtual void loadResources() override;
+
+private:
+ ShapeObject *_lastSelectedObject = nullptr;
+ ObjectBase *_idleArm = nullptr;
};
class ConnectMenu final : public Room {
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 3750453467a..44aef0ba05e 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -47,7 +47,7 @@ void MenuButton::draw() {
Graphic &graphic =
!_isInteractable ? _graphicDisabled
: _isClicked ? _graphicClicked
- : _isHovered ? _graphicHovered
+ : wasSelected() ? _graphicHovered
: _graphicNormal;
graphic.update();
g_engine->drawQueue().add<AnimationDrawRequest>(graphic, true, BlendMode::AdditiveAlpha);
@@ -87,15 +87,7 @@ void MenuButton::freeResources() {
_graphicDisabled.freeResources();
}
-void MenuButton::onHoverStart() {
- PhysicalObject::onHoverStart();
- _isHovered = true;
-}
-
-void MenuButton::onHoverEnd() {
- PhysicalObject::onHoverEnd();
- _isHovered = false;
-}
+void MenuButton::onHoverUpdate() {}
void MenuButton::onClick() {
if (_isInteractable) {
@@ -194,7 +186,7 @@ void CheckBox::draw() {
baseGraphic.update();
g_engine->drawQueue().add<AnimationDrawRequest>(baseGraphic, true, BlendMode::AdditiveAlpha);
- if (_isHovered) {
+ if (wasSelected()) {
Graphic &hoverGraphic = _wasClicked ? _graphicClicked : _graphicHovered;
hoverGraphic.update();
g_engine->drawQueue().add<AnimationDrawRequest>(hoverGraphic, true, BlendMode::AdditiveAlpha);
@@ -216,7 +208,7 @@ void CheckBox::update() {
}
void CheckBox::loadResources() {
- _wasClicked = _isHovered = false;
+ _wasClicked = false;
_graphicUnchecked.loadResources();
_graphicChecked.loadResources();
_graphicHovered.loadResources();
@@ -230,15 +222,7 @@ void CheckBox::freeResources() {
_graphicClicked.freeResources();
}
-void CheckBox::onHoverStart() {
- PhysicalObject::onHoverStart();
- _isHovered = true;
-}
-
-void CheckBox::onHoverEnd() {
- PhysicalObject::onHoverEnd();
- _isHovered = false;
-}
+void CheckBox::onHoverUpdate() {}
void CheckBox::onClick() {
_wasClicked = true;
Commit: cfa7f7081149df22d991d7bcec432f3e9825a7f5
https://github.com/scummvm/scummvm/commit/cfa7f7081149df22d991d7bcec432f3e9825a7f5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Add slide buttons
Changed paths:
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 0c50fb2e86c..8dfac1bd136 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -112,7 +112,16 @@ void Menu::setOptionsState() {
Room *optionsMenu = g_engine->world().getRoomByName("MENUOPCIONES");
scumm_assert(optionsMenu != nullptr);
- // TODO: Set music/sound volume
+ auto getSlideButton = [&] (const char *name) {
+ SlideButton *slideButton = dynamic_cast<SlideButton *>(optionsMenu->getObjectByName(name));
+ scumm_assert(slideButton != nullptr);
+ return slideButton;
+ };
+ SlideButton
+ *slideMusicVolume = getSlideButton("Slider Musica"),
+ *slideSpeechVolume = getSlideButton("Slider Sonido");
+ slideMusicVolume->value() = config.musicVolume() / 255.0f;
+ slideSpeechVolume->value() = config.speechVolume() / 255.0f;
if (!config.bits32())
config.highQuality() = false;
@@ -163,7 +172,24 @@ void Menu::triggerOptionsAction(OptionsMenuAction action) {
continueMainMenu();
break;
default:
- warning("Unknown check box action: %d", (int32)action);
+ warning("Unknown options menu action: %d", (int32)action);
+ break;
+ }
+ setOptionsState();
+}
+
+void Menu::triggerOptionsValue(OptionsMenuValue valueId, float value) {
+ Config &config = g_engine->config();
+ switch (valueId) {
+ case OptionsMenuValue::Music:
+ config.musicVolume() = CLIP<uint8>((uint8)(value * 255), 0, 255);
+ break;
+ case OptionsMenuValue::Speech:
+ config.speechVolume() = CLIP<uint8>((uint8)(value * 255), 0, 255);
+ break;
+ default:
+ warning("Unknown options menu value: %d", (int32)valueId);
+ break;
}
setOptionsState();
}
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index e35a29e0cf5..f4293862852 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -51,6 +51,11 @@ enum class OptionsMenuAction : int32 {
MainMenu
};
+enum class OptionsMenuValue : int32 {
+ Music = 0,
+ Speech = 1
+};
+
class Menu {
public:
inline bool isOpen() const { return _isOpen; }
@@ -60,6 +65,7 @@ public:
void openOptionsMenu();
void triggerOptionsAction(OptionsMenuAction action);
+ void triggerOptionsValue(OptionsMenuValue valueId, float value);
private:
void continueGame();
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 29983523514..c47ba4fbd4c 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -285,16 +285,24 @@ public:
SlideButton(Room *room, Common::ReadStream &stream);
virtual ~SlideButton() override = default;
+ inline float &value() { return _value; }
+
+ virtual void draw() override;
+ virtual void update() override;
+ virtual void loadResources() override;
+ virtual void freeResources() override;
virtual const char *typeName() const;
private:
- // TODO: Reverse engineer SlideButton
- int32 i1;
- Common::Point p1, p2;
+ bool isMouseOver() const;
+
+ float _value = 0;
+ int32 _valueId;
+ Common::Point _minPos, _maxPos;
Graphic
- _graph1,
- _graph2,
- _graph3;
+ _graphicIdle,
+ _graphicHovered,
+ _graphicClicked;
};
class CheckBoxAutoAdjustNoise final : public CheckBox {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index bcfaedff8ee..0b2eb1d2dcf 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -356,9 +356,14 @@ bool OptionsMenu::updateInput() {
void OptionsMenu::loadResources() {
Room::loadResources();
_lastSelectedObject = nullptr;
+ _currentSlideButton = nullptr;
_idleArm = getObjectByName("Brazo");
}
+void OptionsMenu::clearLastSelectedObject() {
+ _lastSelectedObject = nullptr;
+}
+
ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream)
: Room(world, stream, true) {
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 945d6b65cf2..3f0e0535397 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -95,9 +95,13 @@ public:
virtual bool updateInput() override;
virtual void loadResources() override;
+ void clearLastSelectedObject(); // to reset arm animation
+ inline SlideButton *¤tSlideButton() { return _currentSlideButton; }
+
private:
ShapeObject *_lastSelectedObject = nullptr;
ObjectBase *_idleArm = nullptr;
+ SlideButton *_currentSlideButton;
};
class ConnectMenu final : public Room {
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 44aef0ba05e..10e733fcdf2 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -244,12 +244,75 @@ const char *SlideButton::typeName() const { return "SlideButton"; }
SlideButton::SlideButton(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
- , i1(stream.readSint32LE())
- , p1(Shape(stream).firstPoint())
- , p2(Shape(stream).firstPoint())
- , _graph1(stream)
- , _graph2(stream)
- , _graph3(stream) {
+ , _valueId(stream.readSint32LE())
+ , _minPos(Shape(stream).firstPoint())
+ , _maxPos(Shape(stream).firstPoint())
+ , _graphicIdle(stream)
+ , _graphicHovered(stream)
+ , _graphicClicked(stream) {
+}
+
+void SlideButton::draw() {
+ auto *optionsMenu = dynamic_cast<OptionsMenu *>(room());
+ assert(optionsMenu != nullptr);
+
+ Graphic *activeGraphic;
+ if (optionsMenu->currentSlideButton() == this && g_engine->input().isMouseLeftDown())
+ activeGraphic = &_graphicClicked;
+ else
+ activeGraphic = isMouseOver() ? &_graphicHovered : &_graphicIdle;
+ activeGraphic->update();
+ g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha);
+}
+
+void SlideButton::update() {
+ const auto mousePos = g_engine->input().mousePos2D();
+ auto *optionsMenu = dynamic_cast<OptionsMenu *>(room());
+ assert(optionsMenu != nullptr);
+
+ if (optionsMenu->currentSlideButton() == this) {
+ if (!g_engine->input().isMouseLeftDown()) {
+ optionsMenu->currentSlideButton() = nullptr;
+ g_engine->menu().triggerOptionsValue((OptionsMenuValue)_valueId, _value);
+ update(); // to update the position
+ }
+ else {
+ int clippedMousePosY = CLIP(mousePos.y, _minPos.y, _maxPos.y);
+ _value = (_maxPos.y - clippedMousePosY) / (float)(_maxPos.y - _minPos.y);
+ _graphicClicked.topLeft() = Point((_minPos.x + _maxPos.x) / 2, clippedMousePosY);
+ }
+ }
+ else {
+ _graphicIdle.topLeft() = Point(
+ (_minPos.x + _maxPos.x) / 2,
+ (int16)(_maxPos.y - _value * (_maxPos.y - _minPos.y)));
+ if (!isMouseOver())
+ return;
+ _graphicHovered.topLeft() = _graphicIdle.topLeft();
+ if (g_engine->input().wasMouseLeftPressed())
+ optionsMenu->currentSlideButton() = this;
+ optionsMenu->clearLastSelectedObject();
+ g_engine->player().selectedObject() = nullptr;
+ }
+}
+
+void SlideButton::loadResources() {
+ _graphicIdle.loadResources();
+ _graphicHovered.loadResources();
+ _graphicClicked.loadResources();
+}
+
+void SlideButton::freeResources() {
+ _graphicIdle.freeResources();
+ _graphicHovered.freeResources();
+ _graphicClicked.freeResources();
+}
+
+bool SlideButton::isMouseOver() const {
+ const auto mousePos = g_engine->input().mousePos2D();
+ return
+ mousePos.x >= _minPos.x && mousePos.y >= _minPos.y &&
+ mousePos.x <= _maxPos.x && mousePos.y <= _maxPos.y;
}
const char *IRCWindow::typeName() const { return "IRCWindow"; }
Commit: 7165397f7078b7d175a0379cf5cd5aabfa0fec25
https://github.com/scummvm/scummvm/commit/7165397f7078b7d175a0379cf5cd5aabfa0fec25
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Replace SoundID with SoundHandle
Changed paths:
engines/alcachofa/game-objects.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 2cba43125bd..903002568a1 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -301,15 +301,15 @@ struct SayTextTask final : public Task {
while (true) {
g_engine->player().addLastDialogCharacter(_character);
- if (_soundId == kInvalidSoundID)
+ if (_soundHandle == SoundHandle {})
{
bool hasMortadeloVoice = g_engine->game().hasMortadeloVoice(_character);
- _soundId = g_engine->sounds().playVoice(
+ _soundHandle = g_engine->sounds().playVoice(
String::format(hasMortadeloVoice ? "M%04d" : "%04d", _dialogId),
0);
}
- isSoundStillPlaying = g_engine->sounds().isAlive(_soundId);
- g_engine->sounds().setAppropriateVolume(_soundId, process().character(), _character);
+ isSoundStillPlaying = g_engine->sounds().isAlive(_soundHandle);
+ g_engine->sounds().setAppropriateVolume(_soundHandle, process().character(), _character);
if (!isSoundStillPlaying || g_engine->input().wasAnyMouseReleased())
_character->_isTalking = false;
@@ -323,13 +323,13 @@ struct SayTextTask final : public Task {
}
if (!_character->_isTalking) {
- g_engine->sounds().fadeOut(_soundId, 100);
+ g_engine->sounds().fadeOut(_soundHandle, 100);
TASK_WAIT(delay(200));
TASK_RETURN(0);
}
_character->isSpeaking() = !isSoundStillPlaying ||
- g_engine->sounds().isNoisy(_soundId, 80.0f, 150.0f);
+ g_engine->sounds().isNoisy(_soundHandle, 80.0f, 150.0f);
TASK_YIELD;
}
TASK_END;
@@ -342,7 +342,7 @@ struct SayTextTask final : public Task {
private:
Character *_character;
int32 _dialogId;
- SoundID _soundId = kInvalidSoundID;
+ SoundHandle _soundHandle = {};
};
Task *Character::sayText(Process &process, int32 dialogId) {
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 2646659c287..f4ad498203f 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -49,9 +49,9 @@ Sounds::~Sounds() {
_mixer->stopAll();
}
-Sounds::Playback *Sounds::getPlaybackById(SoundID id) {
+Sounds::Playback *Sounds::getPlaybackById(SoundHandle id) {
auto itPlayback = find_if(_playbacks.begin(), _playbacks.end(),
- [&] (const Playback &playback) { return playback._id == id; });
+ [&] (const Playback &playback) { return playback._handle == id; });
return itPlayback == _playbacks.end() ? nullptr : itPlayback;
}
@@ -129,10 +129,10 @@ static AudioStream *openAudio(const char *fileName) {
return nullptr;
}
-SoundID Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::SoundType type) {
+SoundHandle Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::SoundType type) {
AudioStream *stream = openAudio(fileName);
if (stream == nullptr)
- return UINT32_MAX;
+ return {};
Array<int16> samples;
SeekableAudioStream *seekStream = dynamic_cast<SeekableAudioStream *>(stream);
@@ -179,20 +179,19 @@ SoundID Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::Soun
Playback playback;
_mixer->playStream(type, &playback._handle, stream, -1, volume);
- playback._id = _nextID++;
playback._type = type;
playback._inputRate = stream->getRate();
playback._samples = std::move(samples);
_playbacks.push_back(std::move(playback));
- return playback._id;
+ return playback._handle;
}
-SoundID Sounds::playVoice(const String &fileName, byte volume) {
+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);
}
-SoundID Sounds::playSFX(const String &fileName, byte volume) {
+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);
}
@@ -217,18 +216,18 @@ void Sounds::pauseAll(bool paused) {
_mixer->pauseAll(paused);
}
-bool Sounds::isAlive(SoundID id) {
+bool Sounds::isAlive(SoundHandle id) {
Playback *playback = getPlaybackById(id);
return playback != nullptr && _mixer->isSoundHandleActive(playback->_handle);
}
-void Sounds::setVolume(SoundID id, byte volume) {
+void Sounds::setVolume(SoundHandle id, byte volume) {
Playback *playback = getPlaybackById(id);
if (playback != nullptr)
_mixer->setChannelVolume(playback->_handle, volume);
}
-void Sounds::setAppropriateVolume(SoundID id,
+void Sounds::setAppropriateVolume(SoundHandle id,
MainCharacterKind processCharacterKind,
Character *speakingCharacter) {
static constexpr byte kAlmostMaxVolume = Mixer::kMaxChannelVolume * 9 / 10;
@@ -248,7 +247,7 @@ void Sounds::setAppropriateVolume(SoundID id,
setVolume(id, newVolume);
}
-void Sounds::fadeOut(SoundID id, uint32 duration) {
+void Sounds::fadeOut(SoundHandle id, uint32 duration) {
Playback *playback = getPlaybackById(id);
if (playback != nullptr)
playback->fadeOut(duration);
@@ -261,7 +260,7 @@ void Sounds::fadeOutVoiceAndSFX(uint32 duration) {
}
}
-bool Sounds::isNoisy(SoundID id, float windowSize, float minDifferences) {
+bool Sounds::isNoisy(SoundHandle id, float windowSize, float minDifferences) {
assert(windowSize > 0 && minDifferences > 0);
const Playback *playback = getPlaybackById(id);
if (playback == nullptr ||
@@ -334,16 +333,16 @@ Task *Sounds::waitForMusicToEnd(Process &process) {
return new WaitForMusicTask(process, std::move(lock));
}
-PlaySoundTask::PlaySoundTask(Process &process, SoundID soundID)
+PlaySoundTask::PlaySoundTask(Process &process, SoundHandle SoundHandle)
: Task(process)
- , _soundID(soundID) {
+ , _soundHandle(SoundHandle) {
}
TaskReturn PlaySoundTask::run() {
auto &sounds = g_engine->sounds();
- if (sounds.isAlive(_soundID))
+ if (sounds.isAlive(_soundHandle))
{
- sounds.setAppropriateVolume(_soundID, process().character(), nullptr);
+ sounds.setAppropriateVolume(_soundHandle, process().character(), nullptr);
return TaskReturn::yield();
}
else
@@ -351,7 +350,7 @@ TaskReturn PlaySoundTask::run() {
}
void PlaySoundTask::debugPrint() {
- g_engine->console().debugPrintf("PlaySound %u\n", _soundID);
+ g_engine->console().debugPrintf("PlaySound %u\n", _soundHandle);
}
WaitForMusicTask::WaitForMusicTask(Process &process, FakeLock &&lock)
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 0cdf8250405..1d636cb32c7 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -29,28 +29,27 @@
namespace Alcachofa {
class Character;
+using ::Audio::SoundHandle;
-using SoundID = uint32;
-static constexpr SoundID kInvalidSoundID = 0;
class Sounds {
public:
Sounds();
~Sounds();
void update();
- SoundID playVoice(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
- SoundID playSFX(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
+ SoundHandle playVoice(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
+ SoundHandle playSFX(const Common::String &fileName, byte volume = Audio::Mixer::kMaxChannelVolume);
void stopAll();
void stopVoice();
void pauseAll(bool paused);
- void fadeOut(SoundID id, uint32 duration);
+ void fadeOut(SoundHandle id, uint32 duration);
void fadeOutVoiceAndSFX(uint32 duration);
- bool isAlive(SoundID id);
- void setVolume(SoundID id, byte volume);
- void setAppropriateVolume(SoundID id,
+ bool isAlive(SoundHandle id);
+ void setVolume(SoundHandle id, byte volume);
+ void setAppropriateVolume(SoundHandle id,
MainCharacterKind processCharacter,
Character *speakingCharacter);
- bool isNoisy(SoundID id, float windowSize, float minDifferences); ///< used for lip-sync
+ bool isNoisy(SoundHandle id, float windowSize, float minDifferences); ///< used for lip-sync
void startMusic(int musicId);
void queueMusic(int musicId);
@@ -65,7 +64,6 @@ private:
struct Playback {;
void fadeOut(uint32 duration);
- SoundID _id = 0;
Audio::SoundHandle _handle;
Audio::Mixer::SoundType _type = Audio::Mixer::SoundType::kPlainSoundType;
uint32 _fadeStart = 0,
@@ -73,25 +71,24 @@ private:
int _inputRate;
Common::Array<int16> _samples; ///< might not be filled, only voice samples are preloaded for lip-sync
};
- Playback *getPlaybackById(SoundID id);
- SoundID playSoundInternal(const char *fileName, byte volume, Audio::Mixer::SoundType type);
+ Playback *getPlaybackById(SoundHandle id);
+ SoundHandle playSoundInternal(const char *fileName, byte volume, Audio::Mixer::SoundType type);
Common::Array<Playback> _playbacks;
Audio::Mixer *_mixer;
- SoundID _nextID = 1;
- SoundID _musicSoundID = kInvalidSoundID; // we use another soundID to reuse fading
+ SoundHandle _musicSoundID = {}; // we use another soundID to reuse fading
bool _isMusicPlaying = false;
int _nextMusicID = -1;
FakeSemaphore _musicSemaphore;
};
struct PlaySoundTask final : public Task {
- PlaySoundTask(Process &process, SoundID soundID);
+ PlaySoundTask(Process &process, SoundHandle soundHandle);
virtual TaskReturn run() override;
virtual void debugPrint() override;
private:
- SoundID _soundID;
+ SoundHandle _soundHandle;
};
struct WaitForMusicTask final : public Task {
Commit: c9d1bd172543bb0ac921776515a9b027e3c58439
https://github.com/scummvm/scummvm/commit/c9d1bd172543bb0ac921776515a9b027e3c58439
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:56+02:00
Commit Message:
ALCACHOFA: Fix pausing on game menu and ScummVM menus
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/camera.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/script.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 70a6e159e59..0b651c154e4 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -77,6 +77,7 @@ Common::Error AlcachofaEngine::run() {
_player.reset(new Player());
_globalUI.reset(new GlobalUI());
_menu.reset(new Menu());
+ setMillis(0);
_script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
_scheduler.run();
@@ -202,6 +203,36 @@ void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param) {
_input.toggleDebugInput(isDebugModeActive());
}
+uint32 AlcachofaEngine::getMillis() const {
+ // Time is stored in savestate at various points e.g. to persist animation progress
+ // We wrap the system-provided time to offset it to the expected game-time
+ // This would also double as playtime
+ return g_system->getMillis() - _timeNegOffset + _timePosOffset;
+}
+
+void AlcachofaEngine::setMillis(uint32 newMillis) {
+ const uint32 sysMillis = g_system->getMillis();
+ if (newMillis > sysMillis) {
+ _timeNegOffset = 0;
+ _timePosOffset = newMillis - sysMillis;
+ }
+ else {
+ _timeNegOffset = sysMillis - newMillis;
+ _timePosOffset = 0;
+ }
+}
+
+void AlcachofaEngine::pauseEngineIntern(bool pause) {
+ // Audio::Mixer also implements recursive pausing,
+ // so ScummVM pausing and Menu pausing will not conflict
+ _sounds.pauseAll(pause);
+
+ if (pause)
+ _timeBeforePause = getMillis();
+ else
+ setMillis(_timeBeforePause);
+}
+
Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
// The Serializer has methods isLoading() and isSaving()
// if you need to specific steps; for example setting
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 812ac68ee86..5d923b3586d 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -105,6 +105,9 @@ public:
inline Config &config() { return _config; }
inline bool isDebugModeActive() const { return _debugHandler != nullptr; }
+ uint32 getMillis() const;
+ void setMillis(uint32 newMillis);
+ virtual void pauseEngineIntern(bool pause);
void playVideo(int32 videoId);
void fadeExit();
void setDebugMode(DebugMode debugMode, int32 param);
@@ -168,6 +171,9 @@ private:
Sounds _sounds;
Scheduler _scheduler;
Config _config;
+
+ uint32 _timeNegOffset = 0, _timePosOffset = 0;
+ uint32 _timeBeforePause = 0;
};
extern AlcachofaEngine *g_engine;
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index caa76b97db8..ec35e9ff70a 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -51,7 +51,7 @@ void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
_cur._isFollowingTarget = target != nullptr;
_followTarget = target;
- _lastUpdateTime = g_system->getMillis();
+ _lastUpdateTime = g_engine->getMillis();
_catchUp = catchUp;
if (target == nullptr)
_isChanging = false;
@@ -162,7 +162,7 @@ Point Camera::transform3Dto2D(Point p3d) const {
void Camera::update() {
// original would be some smoothing of delta times, let's not.
- uint32 now = g_system->getMillis();
+ uint32 now = g_engine->getMillis();
float deltaTime = (now - _lastUpdateTime) / 1000.0f;
deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
_lastUpdateTime = now;
@@ -239,9 +239,9 @@ struct CamLerpTask : public Task {
virtual TaskReturn run() override {
TASK_BEGIN;
- _startTime = g_system->getMillis();
- while (g_system->getMillis() - _startTime < _duration) {
- update(ease((g_system->getMillis() - _startTime) / (float)_duration, _easingType));
+ _startTime = g_engine->getMillis();
+ while (g_engine->getMillis() - _startTime < _duration) {
+ update(ease((g_engine->getMillis() - _startTime) / (float)_duration, _easingType));
_camera._isChanging = true;
TASK_YIELD;
}
@@ -250,8 +250,8 @@ struct CamLerpTask : public Task {
}
virtual void debugPrint() override {
- uint32 remaining = g_system->getMillis() - _startTime <= _duration
- ? _duration - (g_system->getMillis() - _startTime)
+ uint32 remaining = g_engine->getMillis() - _startTime <= _duration
+ ? _duration - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("%s camera with %ums remaining\n", taskName(), remaining);
}
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 903002568a1..0ae1d957338 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -144,11 +144,11 @@ CursorType Door::cursorType() const {
}
void Door::onClick() {
- if (g_system->getMillis() - _lastClickTime < 500 && g_engine->player().activeCharacter()->clearTargetIf(this))
+ if (g_engine->getMillis() - _lastClickTime < 500 && g_engine->player().activeCharacter()->clearTargetIf(this))
trigger(nullptr);
else {
InteractableObject::onClick();
- _lastClickTime = g_system->getMillis();
+ _lastClickTime = g_engine->getMillis();
}
}
@@ -422,11 +422,11 @@ struct LerpLodBiasTask final : public Task {
virtual TaskReturn run() override {
TASK_BEGIN;
- _startTime = g_system->getMillis();
+ _startTime = g_engine->getMillis();
_sourceLodBias = _character->lodBias();
- while (g_system->getMillis() - _startTime < _durationMs) {
+ while (g_engine->getMillis() - _startTime < _durationMs) {
_character->lodBias() = _sourceLodBias + (_targetLodBias - _sourceLodBias) *
- ((g_system->getMillis() - _startTime) / (float)_durationMs);
+ ((g_engine->getMillis() - _startTime) / (float)_durationMs);
TASK_YIELD;
}
_character->lodBias() = _targetLodBias;
@@ -434,8 +434,8 @@ struct LerpLodBiasTask final : public Task {
}
virtual void debugPrint() override {
- uint32 remaining = g_system->getMillis() - _startTime <= _durationMs
- ? _durationMs - (g_system->getMillis() - _startTime)
+ uint32 remaining = g_engine->getMillis() - _startTime <= _durationMs
+ ? _durationMs - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("Lerp lod bias of %s to %f with %ums remaining\n",
_character->name().c_str(), _targetLodBias, remaining);
@@ -584,7 +584,7 @@ void WalkingCharacter::updateWalkingAnimation() {
// this is very confusing. Let's see what it does
const int32 halfFrameCount = (int32)animation->frameCount() / 2;
- int32 expectedFrame = (int32)(g_system->getMillis() - _graphicNormal.lastTime()) * 12 / 1000;
+ int32 expectedFrame = (int32)(g_engine->getMillis() - _graphicNormal.lastTime()) * 12 / 1000;
const bool isUnexpectedFrame = expectedFrame != _lastWalkAnimFrame;
int32 stepFrameFrom, stepFrameTo;
if (expectedFrame < halfFrameCount - 1) {
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 4c5e2448c23..5e0e2d1060d 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -176,7 +176,7 @@ SpecialEffectObject::SpecialEffectObject(Room *room, ReadStream &stream)
void SpecialEffectObject::draw() {
if (!isEnabled()) // TODO: Add high quality check
return;
- const auto texOffset = g_system->getMillis() * 0.001f * _texShift;
+ const auto texOffset = g_engine->getMillis() * 0.001f * _texShift;
const BlendMode blendMode = _type == GraphicObjectType::Effect
? BlendMode::Additive
: BlendMode::AdditiveAlpha;
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 9cf45228071..5c2b5dbaa30 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -62,7 +62,7 @@ GlobalUI::GlobalUI() {
void GlobalUI::startClosingInventory() {
_isOpeningInventory = false;
_isClosingInventory = true;
- _timeForInventory = g_system->getMillis();
+ _timeForInventory = g_engine->getMillis();
updateClosingInventory(); // prevents the first frame of closing to not render the inventory overlay
}
@@ -70,7 +70,7 @@ void GlobalUI::updateClosingInventory() {
static constexpr uint32 kDuration = 300;
static constexpr float kSpeed = -10 / 3.0f / 1000.0f;
- uint32 deltaTime = g_system->getMillis() - _timeForInventory;
+ uint32 deltaTime = g_engine->getMillis() - _timeForInventory;
if (!_isClosingInventory || deltaTime >= kDuration)
_isClosingInventory = false;
else
@@ -83,7 +83,7 @@ bool GlobalUI::updateOpeningInventory() {
return false;
if (_isOpeningInventory) {
- uint32 deltaTime = g_system->getMillis() - _timeForInventory;
+ uint32 deltaTime = g_engine->getMillis() - _timeForInventory;
if (deltaTime >= 1000) {
_isOpeningInventory = false;
g_engine->world().inventory().open();
@@ -97,7 +97,7 @@ bool GlobalUI::updateOpeningInventory() {
else if (openInventoryTriggerBounds().contains(g_engine->input().mousePos2D())) {
_isClosingInventory = false;
_isOpeningInventory = true;
- _timeForInventory = g_system->getMillis();
+ _timeForInventory = g_engine->getMillis();
g_engine->player().activeCharacter()->stopWalking();
g_engine->world().inventory().updateItemsByActiveCharacter();
return true;
@@ -206,8 +206,8 @@ struct CenterBottomTextTask : public Task {
);
TASK_BEGIN;
- _startTime = g_system->getMillis();
- while (g_system->getMillis() - _startTime < _durationMs) {
+ _startTime = g_engine->getMillis();
+ while (g_engine->getMillis() - _startTime < _durationMs) {
if (process().isActiveForPlayer()) {
g_engine->drawQueue().add<TextDrawRequest>(
font, text, pos, -1, true, kWhite, 1);
@@ -218,8 +218,8 @@ struct CenterBottomTextTask : public Task {
}
void debugPrint() override {
- uint32 remaining = g_system->getMillis() - _startTime <= _durationMs
- ? _durationMs - (g_system->getMillis() - _startTime)
+ uint32 remaining = g_engine->getMillis() - _startTime <= _durationMs
+ ? _durationMs - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("CenterBottomText (%d) with %ums remaining\n", _dialogId, remaining);
}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 4da9cba4a89..3ccf06c6258 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -507,7 +507,7 @@ void Graphic::update() {
const uint32 totalDuration = _animation->totalDuration();
uint32 curTime = _isPaused
? _lastTime
- : g_system->getMillis() - _lastTime;
+ : g_engine->getMillis() - _lastTime;
if (curTime > totalDuration) {
if (_isLooping && totalDuration > 0)
curTime %= totalDuration;
@@ -524,18 +524,18 @@ void Graphic::update() {
void Graphic::start(bool isLooping) {
_isPaused = false;
_isLooping = isLooping;
- _lastTime = g_system->getMillis();
+ _lastTime = g_engine->getMillis();
}
void Graphic::pause() {
_isPaused = true;
_isLooping = false;
- _lastTime = g_system->getMillis() - _lastTime;
+ _lastTime = g_engine->getMillis() - _lastTime;
}
void Graphic::reset() {
_frameI = 0;
- _lastTime = _isPaused ? 0 : g_system->getMillis();
+ _lastTime = _isPaused ? 0 : g_engine->getMillis();
}
void Graphic::setAnimation(const Common::String &fileName, AnimationFolder folder) {
@@ -782,9 +782,9 @@ struct FadeTask : public Task {
TASK_BEGIN;
if (_permanentFadeAction == PermanentFadeAction::UnsetFaded)
g_engine->globalUI().isPermanentFaded() = false;
- _startTime = g_system->getMillis();
- while (g_system->getMillis() - _startTime < _duration) {
- draw((g_system->getMillis() - _startTime) / (float)_duration);
+ _startTime = g_engine->getMillis();
+ while (g_engine->getMillis() - _startTime < _duration) {
+ draw((g_engine->getMillis() - _startTime) / (float)_duration);
TASK_YIELD;
}
draw(1.0f); // so that during a loading lag the screen is completly black/white
@@ -794,8 +794,8 @@ struct FadeTask : public Task {
}
virtual void debugPrint() override {
- uint32 remaining = g_system->getMillis() - _startTime <= _duration
- ? _duration - (g_system->getMillis() - _startTime)
+ uint32 remaining = g_engine->getMillis() - _startTime <= _duration
+ ? _duration - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("Fade (%d) from %.2f to %.2f with %ums remaining\n", (int)_fadeType, _from, _to, remaining);
}
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 8dfac1bd136..b0abf19233c 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -36,7 +36,7 @@ void Menu::updateOpeningMenu() {
_openAtNextFrame = false;
g_engine->sounds().pauseAll(true);
- // TODO: Add game time behaviour on opening menu
+ _timeBeforeMenu = g_engine->getMillis();
_previousRoom = g_engine->player().currentRoom();
_isOpen = true;
// TODO: Render thumbnail
@@ -60,7 +60,7 @@ void Menu::continueGame() {
g_engine->sounds().pauseAll(false);
g_engine->camera().restore(1);
g_engine->scheduler().restoreContext();
- // TODO: Reset time on continueing game
+ g_engine->setMillis(_timeBeforeMenu);
}
void Menu::triggerMainMenuAction(MainMenuAction action) {
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index f4293862852..712a74ff219 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -75,6 +75,7 @@ private:
bool
_isOpen = false,
_openAtNextFrame = false;
+ uint32 _timeBeforeMenu = 0;
Room *_previousRoom = nullptr;
};
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 822ba5487c9..982515ea664 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -62,14 +62,14 @@ DelayTask::DelayTask(Process &process, uint32 millis)
TaskReturn DelayTask::run() {
TASK_BEGIN;
- _endTime += g_system->getMillis();
- while (g_system->getMillis() < _endTime)
+ _endTime += g_engine->getMillis();
+ while (g_engine->getMillis() < _endTime)
TASK_YIELD;
TASK_END;
}
void DelayTask::debugPrint() {
- uint32 remaining = g_system->getMillis() <= _endTime ? _endTime - g_system->getMillis() : 0;
+ uint32 remaining = g_engine->getMillis() <= _endTime ? _endTime - g_engine->getMillis() : 0;
g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 74efcdc26c0..61f05ba0833 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -133,7 +133,7 @@ struct ScriptTimerTask : public Task {
TASK_BEGIN;
{
uint32 timeSinceTimer = g_engine->script()._scriptTimer == 0 ? 0
- : (g_system->getMillis() - g_engine->script()._scriptTimer) / 1000;
+ : (g_engine->getMillis() - g_engine->script()._scriptTimer) / 1000;
if (_durationSec >= (int32)timeSinceTimer)
_result = g_engine->script().variable("SeHaPulsadoRaton") ? 0 : 2;
else
@@ -914,7 +914,7 @@ void Script::updateCommonVariables() {
if (variable("CalcularTiempoSinPulsarRaton")) {
if (_scriptTimer == 0)
- _scriptTimer = g_system->getMillis();
+ _scriptTimer = g_engine->getMillis();
}
else
_scriptTimer = 0;
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 10e733fcdf2..482312fb9ef 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -196,7 +196,7 @@ void CheckBox::draw() {
void CheckBox::update() {
PhysicalObject::update();
if (_wasClicked) {
- if (g_system->getMillis() - _clickTime > 500) {
+ if (g_engine->getMillis() - _clickTime > 500) {
_wasClicked = false;
trigger();
}
@@ -226,7 +226,7 @@ void CheckBox::onHoverUpdate() {}
void CheckBox::onClick() {
_wasClicked = true;
- _clickTime = g_system->getMillis();
+ _clickTime = g_engine->getMillis();
}
void CheckBox::trigger() {
Commit: 7e819190f4c6d0801a94f6b2087e51cba978fc4a
https://github.com/scummvm/scummvm/commit/7e819190f4c6d0801a94f6b2087e51cba978fc4a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Add most of syncGame subroutines
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 0b651c154e4..3dfc51f9e7d 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -233,13 +233,32 @@ void AlcachofaEngine::pauseEngineIntern(bool pause) {
setMillis(_timeBeforePause);
}
-Common::Error AlcachofaEngine::syncGame(Common::Serializer &s) {
- // The Serializer has methods isLoading() and isSaving()
- // if you need to specific steps; for example setting
- // an array size after reading it's length, whereas
- // for saving it would write the existing array's length
- int dummy = 0;
- s.syncAsUint32LE(dummy);
+Common::Error AlcachofaEngine::syncGame(Serializer &s) {
+ s.syncVersion((Serializer::Version)SaveVersion::Initial);
+
+ uint32 millis = getMillis();
+ s.syncAsUint32LE(millis);
+ if (s.isLoading())
+ setMillis(millis);
+
+ /* Some notes about the order:
+ * 1. The scheduler should come first due to our FakeSemaphore
+ * By destructing all previous processes we also release all locks and
+ * can assert that the semaphores are released on loading.
+ * 2. The player should come last as it changes the room
+ */
+
+ //scheduler().syncGame(s);
+ world().syncGame(s);
+ camera().syncGame(s);
+ script().syncGame(s);
+ globalUI().syncGame(s);
+ player().syncGame(s);
+
+ if (s.isLoading()) {
+ sounds().stopAll();
+ sounds().setMusicToRoom(player().currentRoom()->musicID());
+ }
return Common::kNoError;
}
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 5d923b3586d..2c30ae6496d 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -55,6 +55,10 @@ class Menu;
class Game;
struct AlcachofaGameDescription;
+enum class SaveVersion : Common::Serializer::Version {
+ Initial = 0
+};
+
class Config {
public:
Config();
@@ -140,12 +144,7 @@ public:
return true;
}
- /**
- * Uses a serializer to allow implementing savegame
- * loading and saving using a single method
- */
Common::Error syncGame(Common::Serializer &s);
-
Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
Common::Serializer s(nullptr, stream);
return syncGame(s);
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index ec35e9ff70a..109c62cdb74 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -230,6 +230,60 @@ void Camera::updateFollowing(float deltaTime) {
}
}
+static void syncMatrix(Serializer &s, Matrix4 &m) {
+ float *data = m.getData();
+ for (int i = 0; i < 16; i++)
+ s.syncAsFloatLE(data[i]);
+}
+
+static void syncVector(Serializer &s, Vector3d &v) {
+ s.syncAsFloatLE(v.x());
+ s.syncAsFloatLE(v.y());
+ s.syncAsFloatLE(v.z());
+}
+
+void Camera::State::syncGame(Serializer &s) {
+ syncVector(s, _usedCenter);
+ s.syncAsFloatLE(_scale);
+ s.syncAsFloatLE(_speed);
+ s.syncAsFloatLE(_maxSpeedFactor);
+ float rotationDegs = _rotation.getDegrees();
+ s.syncAsFloatLE(rotationDegs);
+ _rotation.setDegrees(rotationDegs);
+ s.syncAsByte(_isBraking);
+ s.syncAsByte(_isFollowingTarget);
+}
+
+void Camera::syncGame(Serializer &s) {
+ syncMatrix(s, _mat3Dto2D);
+ syncMatrix(s, _mat2Dto3D);
+ syncVector(s, _appliedCenter);
+ s.syncAsUint32LE(_lastUpdateTime);
+ s.syncAsByte(_isChanging);
+ _cur.syncGame(s);
+ for (uint i = 0; i < kStateBackupCount; i++)
+ _backups[i].syncGame(s);
+
+ // originally the follow object is also searched for before changing the room
+ // so that would practically mean only the main characters could be reasonably found
+ // instead we fall back to global search
+ String name;
+ if (_followTarget != nullptr)
+ name = _followTarget->name();
+ s.syncString(name);
+ if (s.isLoading()) {
+ if (name.empty())
+ _followTarget = nullptr;
+ else {
+ _followTarget = dynamic_cast<WalkingCharacter *>(g_engine->world().getObjectByName(name.c_str()));
+ if (_followTarget == nullptr)
+ _followTarget = dynamic_cast<WalkingCharacter *>(g_engine->world().getObjectByNameFromAnyRoom(name.c_str()));
+ if (_followTarget == nullptr)
+ warning("Camera follow target from savestate was not found: %s", name.c_str());
+ }
+ }
+}
+
struct CamLerpTask : public Task {
CamLerpTask(Process &process, uint32 duration, EasingType easingType)
: Task(process)
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 6e18e069003..f3099eef5d0 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -51,6 +51,7 @@ public:
void setPosition(Math::Vector3d v);
void backup(uint slot);
void restore(uint slot);
+ void syncGame(Common::Serializer &s);
Task *lerpPos(Process &process,
Math::Vector2d targetPos,
@@ -95,6 +96,8 @@ private:
Math::Angle _rotation;
bool _isBraking = false;
bool _isFollowingTarget = false;
+
+ void syncGame(Common::Serializer &s);
};
static constexpr uint kStateBackupCount = 2;
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 4d418c3a230..57be9a3d27e 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -42,6 +42,16 @@ FakeSemaphore::~FakeSemaphore() {
assert(_counter == 0);
}
+void FakeSemaphore::sync(Serializer &s, FakeSemaphore &semaphore) {
+ // if we are still holding locks during loading these locks will
+ // try to decrease the counter which will fail, let's find this out already here
+ assert(s.isSaving() || semaphore.isReleased());
+
+ uint semaphoreCounter = semaphore.counter();
+ s.syncAsSint32LE(semaphoreCounter);
+ semaphore = FakeSemaphore(semaphoreCounter);
+}
+
FakeLock::FakeLock() : _semaphore(nullptr) {}
FakeLock::FakeLock(FakeSemaphore &semaphore) : _semaphore(&semaphore) {
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 5d7910e5ac9..02f38cedc6c 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -88,6 +88,8 @@ struct FakeSemaphore {
inline bool isReleased() const { return _counter == 0; }
inline uint counter() const { return _counter; }
+
+ static void sync(Common::Serializer &s, FakeSemaphore &semaphore);
private:
friend struct FakeLock;
uint _counter = 0;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 0ae1d957338..42af33c97c2 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -233,26 +233,17 @@ void Character::freeResources() {
_graphicTalking.freeResources();
}
-void Character::serializeSave(Serializer &serializer) {
- ShapeObject::serializeSave(serializer);
+void Character::syncGame(Serializer &serializer) {
+ ShapeObject::syncGame(serializer);
serializer.syncAsByte(_isTalking);
serializer.syncAsSint32LE(_curDialogId);
- _graphicNormal.serializeSave(serializer);
- _graphicTalking.serializeSave(serializer);
+ _graphicNormal.syncGame(serializer);
+ _graphicTalking.syncGame(serializer);
syncObjectAsString(serializer, _curAnimateObject);
syncObjectAsString(serializer, _curTalkingObject);
serializer.syncAsFloatLE(_lodBias);
}
-Graphic *Character::graphic() {
- Graphic *activeGraphic = graphicOf(_curAnimateObject);
- if (activeGraphic == nullptr && (_isTalking || g_engine->world().somebodyUsing(this)))
- activeGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
- if (activeGraphic == nullptr)
- activeGraphic = &_graphicNormal;
- return activeGraphic;
-}
-
void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object) {
String name;
if (serializer.isSaving() && object != nullptr)
@@ -274,6 +265,15 @@ void Character::syncObjectAsString(Serializer &serializer, ObjectBase *&object)
}
}
+Graphic *Character::graphic() {
+ Graphic *activeGraphic = graphicOf(_curAnimateObject);
+ if (activeGraphic == nullptr && (_isTalking || g_engine->world().somebodyUsing(this)))
+ activeGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
+ if (activeGraphic == nullptr)
+ activeGraphic = &_graphicNormal;
+ return activeGraphic;
+}
+
void Character::onClick() {
ITriggerableObject::onClick();
onHoverUpdate();
@@ -717,8 +717,8 @@ void WalkingCharacter::freeResources() {
}
}
-void WalkingCharacter::serializeSave(Serializer &serializer) {
- Character::serializeSave(serializer);
+void WalkingCharacter::syncGame(Serializer &serializer) {
+ Character::syncGame(serializer);
serializer.syncAsSint32LE(_lastWalkAnimFrame);
serializer.syncAsSint32LE(_walkedDistance);
syncPoint(serializer, _sourcePos);
@@ -880,7 +880,7 @@ void syncDialogMenuLine(Serializer &serializer, DialogMenuLine &line) {
serializer.syncAsSint32LE(line._returnValue);
}
-void MainCharacter::serializeSave(Serializer &serializer) {
+void MainCharacter::syncGame(Serializer &serializer) {
String roomName = room()->name();
serializer.syncString(roomName);
if (serializer.isLoading()) {
@@ -890,10 +890,8 @@ void MainCharacter::serializeSave(Serializer &serializer) {
error("Invalid room name \"%s\" saved for \"%s\"", roomName.c_str(), name().c_str());
}
- Character::serializeSave(serializer);
- uint semaphoreCounter = _semaphore.counter();
- serializer.syncAsSint32LE(semaphoreCounter);
- _semaphore = FakeSemaphore(semaphoreCounter);
+ WalkingCharacter::syncGame(serializer);
+ FakeSemaphore::sync(serializer, _semaphore);
syncArray(serializer, _dialogLines, syncDialogMenuLine);
syncObjectAsString(serializer, _currentlyUsingObject);
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index f9a041cec4c..f55fc805246 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -94,7 +94,7 @@ void Game::unknownFadeType(int fadeType) {
}
void Game::unknownSerializedObject(const char *object, const char *owner, const char *room) {
- // potentially game-breaking for _currentlyUsingObject but should otherwise be just a graphical bug
+ // potentially game-breaking for _currentlyUsingObject but might otherwise be just a graphical bug
_message("Invalid object name \"%s\" saved for \"%s\" in \"%s\"", object, owner, room);
}
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 5e0e2d1060d..e55d08fd2ee 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -66,7 +66,7 @@ void ObjectBase::loadResources() {
void ObjectBase::freeResources() {
}
-void ObjectBase::serializeSave(Serializer &serializer) {
+void ObjectBase::syncGame(Serializer &serializer) {
serializer.syncAsByte(_isEnabled);
}
@@ -120,9 +120,9 @@ void GraphicObject::freeResources() {
_graphic.freeResources();
}
-void GraphicObject::serializeSave(Serializer &serializer) {
- ObjectBase::serializeSave(serializer);
- _graphic.serializeSave(serializer);
+void GraphicObject::syncGame(Serializer &serializer) {
+ ObjectBase::syncGame(serializer);
+ _graphic.syncGame(serializer);
}
Graphic *GraphicObject::graphic() {
@@ -207,8 +207,10 @@ void ShapeObject::update() {
}
}
-void ShapeObject::serializeSave(Serializer &serializer) {
+void ShapeObject::syncGame(Serializer &serializer) {
serializer.syncAsSByte(_order);
+ _isNewlySelected = false;
+ _wasSelected = false;
}
Shape *ShapeObject::shape() {
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 5c2b5dbaa30..fbb13b4d6a7 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -248,4 +248,8 @@ void GlobalUI::drawScreenStates() {
}
}
+void GlobalUI::syncGame(Serializer &s) {
+ s.syncAsByte(_isPermanentFaded);
+}
+
}
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 8892ebf3e27..44964edeb6b 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -43,6 +43,7 @@ public:
void updateClosingInventory();
void startClosingInventory();
void drawScreenStates(); // black borders and/or permanent fade
+ void syncGame(Common::Serializer &s);
private:
Animation *activeAnimation() const;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 3ccf06c6258..8a87cbb65e9 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -547,7 +547,7 @@ void Graphic::setAnimation(Animation *animation) {
_animation = animation;
}
-void Graphic::serializeSave(Serializer &serializer) {
+void Graphic::syncGame(Serializer &serializer) {
syncPoint(serializer, _topLeft);
serializer.syncAsSint16LE(_scale);
serializer.syncAsUint32LE(_lastTime);
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 788dc583f3b..a39763f6cfa 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -277,7 +277,7 @@ public:
void reset();
void setAnimation(const Common::String &fileName, AnimationFolder folder);
void setAnimation(Animation *animation); ///< no memory ownership is given, but for prerendering it has to be mutable
- void serializeSave(Common::Serializer &serializer);
+ void syncGame(Common::Serializer &serializer);
private:
friend class AnimationDrawRequest;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index c47ba4fbd4c..5b1f552eb27 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -51,7 +51,7 @@ public:
virtual void update();
virtual void loadResources();
virtual void freeResources();
- virtual void serializeSave(Common::Serializer &serializer);
+ virtual void syncGame(Common::Serializer &serializer);
virtual Graphic *graphic();
virtual Shape *shape();
virtual const char *typeName() const;
@@ -91,7 +91,7 @@ public:
virtual void draw() override;
virtual void loadResources() override;
virtual void freeResources() override;
- virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void syncGame(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
virtual const char *typeName() const;
@@ -129,7 +129,7 @@ public:
inline bool wasSelected() const { return _wasSelected; }
virtual void update() override;
- virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void syncGame(Common::Serializer &serializer) override;
virtual Shape *shape() override;
virtual CursorType cursorType() const;
virtual void onHoverStart();
@@ -423,7 +423,7 @@ public:
virtual void drawDebug() override;
virtual void loadResources() override;
virtual void freeResources() override;
- virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void syncGame(Common::Serializer &serializer) override;
virtual Graphic *graphic() override;
virtual void onClick() override;
virtual void trigger(const char *action) override;
@@ -470,7 +470,7 @@ public:
virtual void drawDebug() override;
virtual void loadResources() override;
virtual void freeResources() override;
- virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void syncGame(Common::Serializer &serializer) override;
virtual void walkTo(
Common::Point target,
Direction endDirection = Direction::Invalid,
@@ -538,7 +538,7 @@ public:
virtual void update() override;
virtual void draw() override;
- virtual void serializeSave(Common::Serializer &serializer) override;
+ virtual void syncGame(Common::Serializer &serializer) override;
virtual const char *typeName() const;
virtual void walkTo(
Common::Point target,
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 4ec1cddde44..015d60cb85f 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -304,4 +304,46 @@ bool Player::isAllowedToOpenMenu() {
!g_engine->script().variable("prohibirESC");
}
+void Player::syncGame(Serializer &s) {
+ auto characterKind = activeCharacterKind();
+ syncEnum(s, characterKind);
+ switch (characterKind) {
+ case MainCharacterKind::None:
+ _activeCharacter = nullptr;
+ break;
+ case MainCharacterKind::Mortadelo:
+ case MainCharacterKind::Filemon:
+ _activeCharacter = &g_engine->world().getMainCharacterByKind(characterKind);
+ break;
+ default:
+ error("Invalid character kind in savestate: %d", (int)characterKind);
+ }
+
+ FakeSemaphore::sync(s, _semaphore);
+
+ String roomName;
+ if (_roomBeforeInventory != nullptr)
+ roomName = _roomBeforeInventory->name();
+ s.syncString(roomName);
+ if (s.isLoading()) {
+ if (roomName.empty())
+ _roomBeforeInventory = nullptr;
+ else {
+ _roomBeforeInventory = g_engine->world().getRoomByName(roomName.c_str());
+ scumm_assert(_roomBeforeInventory != nullptr);
+ }
+ }
+
+ roomName = currentRoom()->name();
+ s.syncString(roomName);
+ if (s.isLoading()) {
+ _selectedObject = nullptr;
+ _pressedObject = nullptr;
+ _heldItem = nullptr;
+ _nextLastDialogCharacter = 0;
+ fill(_lastDialogCharacters, _lastDialogCharacters + kMaxLastDialogCharacters, nullptr);
+ changeRoom(roomName, true);
+ }
+}
+
}
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index 2541406d5f1..ee0259c2362 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -58,6 +58,7 @@ public:
void stopLastDialogCharacters();
void setActiveCharacter(MainCharacterKind kind);
bool isAllowedToOpenMenu();
+ void syncGame(Common::Serializer &s);
private:
static constexpr const int kMaxLastDialogCharacters = 4;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0b2eb1d2dcf..69cc16a4fde 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -302,11 +302,10 @@ void Room::freeResources() {
object->freeResources();
}
-void Room::serializeSave(Serializer &serializer) {
- serializer.syncAsSByte(_musicId);
+void Room::syncGame(Serializer &serializer) {
serializer.syncAsSByte(_activeFloorI);
for (auto *object : _objects)
- object->serializeSave(serializer);
+ object->syncGame(serializer);
}
void Room::toggleActiveFloor() {
@@ -760,4 +759,9 @@ void World::loadDialogLines() {
}
}
+void World::syncGame(Serializer &s) {
+ for (Room *room : _rooms)
+ room->syncGame(s);
+}
+
}
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 3f0e0535397..8d753f81aeb 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -59,7 +59,7 @@ public:
virtual bool updateInput();
virtual void loadResources();
virtual void freeResources();
- virtual void serializeSave(Common::Serializer &serializer);
+ virtual void syncGame(Common::Serializer &serializer);
ObjectBase *getObjectByName(const char *name) const;
void toggleActiveFloor();
void debugPrint(bool withObjects) const;
@@ -183,6 +183,7 @@ public:
const char *getDialogLine(int32 dialogId) const;
void toggleObject(MainCharacterKind character, const char *objName, bool isEnabled);
+ void syncGame(Common::Serializer &s);
private:
bool loadWorldFile(const char *path);
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 61f05ba0833..51184d13ba7 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -98,6 +98,14 @@ Script::Script() {
_instructions.push_back(ScriptInstruction(file));
}
+static void syncAsSint32LE(Serializer &s, int32 &value) {
+ s.syncAsSint32LE(value);
+}
+
+void Script::syncGame(Serializer &s) {
+ s.syncArray(_variables.data(), _variables.size(), syncAsSint32LE);
+}
+
int32 Script::variable(const char *name) const {
uint32 index;
if (_variableNames.tryGetVal(name, index))
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index e1b614a4999..0b8337a3430 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -156,6 +156,7 @@ class Script {
public:
Script();
+ void syncGame(Common::Serializer &s);
void updateCommonVariables();
int32 variable(const char *name) const;
int32 &variable(const char *name);
Commit: 3fb9469d2a449bc177a67113397c6f132b9f3615
https://github.com/scummvm/scummvm/commit/3fb9469d2a449bc177a67113397c6f132b9f3615
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Add syncGame for scheduler and all tasks
Changed paths:
A engines/alcachofa/tasks.h
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/common.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 3dfc51f9e7d..c9062244fd9 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -242,18 +242,20 @@ Common::Error AlcachofaEngine::syncGame(Serializer &s) {
setMillis(millis);
/* Some notes about the order:
- * 1. The scheduler should come first due to our FakeSemaphore
+ * 1. The scheduler should prepare due to our FakeSemaphores
* By destructing all previous processes we also release all locks and
* can assert that the semaphores are released on loading.
- * 2. The player should come last as it changes the room
+ * 2. The player should come late as it changes the room
+ * 3. With the room current, the tasks can now better find the referenced objects
*/
- //scheduler().syncGame(s);
+ scheduler().prepareSyncGame(s);
world().syncGame(s);
camera().syncGame(s);
script().syncGame(s);
globalUI().syncGame(s);
player().syncGame(s);
+ scheduler().syncGame(s);
if (s.isLoading()) {
sounds().stopAll();
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 109c62cdb74..fb4c4de1d87 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -285,7 +285,7 @@ void Camera::syncGame(Serializer &s) {
}
struct CamLerpTask : public Task {
- CamLerpTask(Process &process, uint32 duration, EasingType easingType)
+ CamLerpTask(Process &process, uint32 duration = 0, EasingType easingType = EasingType::Linear)
: Task(process)
, _camera(g_engine->camera())
, _duration(duration)
@@ -297,7 +297,7 @@ struct CamLerpTask : public Task {
while (g_engine->getMillis() - _startTime < _duration) {
update(ease((g_engine->getMillis() - _startTime) / (float)_duration, _easingType));
_camera._isChanging = true;
- TASK_YIELD;
+ TASK_YIELD(1);
}
update(1.0f);
TASK_END;
@@ -310,8 +310,14 @@ struct CamLerpTask : public Task {
g_engine->console().debugPrintf("%s camera with %ums remaining\n", taskName(), remaining);
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ s.syncAsUint32LE(_startTime);
+ s.syncAsUint32LE(_duration);
+ syncEnum(s, _easingType);
+ }
+
protected:
- virtual const char *taskName() const = 0;
virtual void update(float t) = 0;
Camera &_camera;
@@ -325,17 +331,27 @@ struct CamLerpPosTask final : public CamLerpTask {
, _fromPos(_camera._appliedCenter)
, _deltaPos(targetPos - _camera._appliedCenter) {}
-protected:
- virtual const char *taskName() const {
- return "Lerp pos of";
+ CamLerpPosTask(Process &process, Serializer &s)
+ : CamLerpTask(process) {
+ syncGame(s);
+ }
+
+ virtual void syncGame(Serializer &s) override {
+ CamLerpTask::syncGame(s);
+ syncVector(s, _fromPos);
+ syncVector(s, _deltaPos);
}
+ virtual const char *taskName() const override;
+
+protected:
virtual void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * t);
}
Vector3d _fromPos, _deltaPos;
};
+DECLARE_TASK(CamLerpPosTask);
struct CamLerpScaleTask final : public CamLerpTask {
CamLerpScaleTask(Process &process, float targetScale, int32 duration, EasingType easingType)
@@ -343,17 +359,27 @@ struct CamLerpScaleTask final : public CamLerpTask {
, _fromScale(_camera._cur._scale)
, _deltaScale(targetScale - _camera._cur._scale) {}
-protected:
- virtual const char *taskName() const {
- return "Lerp scale of";
+ CamLerpScaleTask(Process &process, Serializer &s)
+ : CamLerpTask(process) {
+ syncGame(s);
+ }
+
+ virtual void syncGame(Serializer &s) override {
+ CamLerpTask::syncGame(s);
+ s.syncAsFloatLE(_fromScale);
+ s.syncAsFloatLE(_deltaScale);
}
+ virtual const char *taskName() const override;
+
+protected:
virtual void update(float t) override {
_camera._cur._scale = _fromScale + _deltaScale * t;
}
float _fromScale, _deltaScale;
};
+DECLARE_TASK(CamLerpScaleTask);
struct CamLerpPosScaleTask final : public CamLerpTask {
CamLerpPosScaleTask(Process &process,
@@ -368,11 +394,24 @@ struct CamLerpPosScaleTask final : public CamLerpTask {
, _moveEasingType(moveEasingType)
, _scaleEasingType(scaleEasingType) {}
-protected:
- virtual const char *taskName() const {
- return "Lerp pos and scale of";
+ CamLerpPosScaleTask(Process &process, Serializer &s)
+ : CamLerpTask(process) {
+ syncGame(s);
+ }
+
+ virtual void syncGame(Serializer &s) override {
+ CamLerpTask::syncGame(s);
+ syncVector(s, _fromPos);
+ syncVector(s, _deltaPos);
+ s.syncAsFloatLE(_fromScale);
+ s.syncAsFloatLE(_deltaScale);
+ syncEnum(s, _moveEasingType);
+ syncEnum(s, _scaleEasingType);
}
+ virtual const char *taskName() const override;
+
+protected:
virtual void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * ease(t, _moveEasingType));
_camera._cur._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
@@ -382,6 +421,7 @@ protected:
float _fromScale, _deltaScale;
EasingType _moveEasingType, _scaleEasingType;
};
+DECLARE_TASK(CamLerpPosScaleTask);
struct CamLerpRotationTask final : public CamLerpTask {
CamLerpRotationTask(Process &process, float targetRotation, int32 duration, EasingType easingType)
@@ -389,17 +429,33 @@ struct CamLerpRotationTask final : public CamLerpTask {
, _fromRotation(_camera._cur._rotation.getDegrees())
, _deltaRotation(targetRotation - _camera._cur._rotation.getDegrees()) {}
-protected:
- virtual const char *taskName() const {
- return "Lerp rotation of";
+ CamLerpRotationTask(Process &process, Serializer &s)
+ : CamLerpTask(process) {
+ syncGame(s);
+ }
+
+ virtual void syncGame(Serializer &s) override {
+ CamLerpTask::syncGame(s);
+ s.syncAsFloatLE(_fromRotation);
+ s.syncAsFloatLE(_deltaRotation);
}
+ virtual const char *taskName() const override;
+
+protected:
virtual void update(float t) override {
_camera._cur._rotation = Angle(_fromRotation + _deltaRotation * t);
}
float _fromRotation, _deltaRotation;
};
+DECLARE_TASK(CamLerpRotationTask);
+
+static void syncVector(Serializer &s, Vector2d &v) {
+ float *data = v.getData();
+ s.syncAsFloatLE(data[0]);
+ s.syncAsFloatLE(data[1]);
+}
struct CamShakeTask final : public CamLerpTask {
CamShakeTask(Process &process, Vector2d amplitude, Vector2d frequency, int32 duration)
@@ -407,11 +463,20 @@ struct CamShakeTask final : public CamLerpTask {
, _amplitude(amplitude)
, _frequency(frequency) { }
-protected:
- virtual const char *taskName() const {
- return "Shake";
+ CamShakeTask(Process &process, Serializer &s)
+ : CamLerpTask(process) {
+ syncGame(s);
}
+ virtual void syncGame(Serializer &s) override {
+ CamLerpTask::syncGame(s);
+ syncVector(s, _amplitude);
+ syncVector(s, _frequency);
+ }
+
+ virtual const char *taskName() const override;
+
+protected:
virtual void update(float t) override {
const Vector2d phase = _frequency * t * (float)M_PI * 2.0f;
const float amplTimeFactor = 1.0f / expf(t * 5.0f); // a curve starting at 1, depreciating towards 0
@@ -422,14 +487,20 @@ protected:
}
Vector2d _amplitude, _frequency;
-
};
+DECLARE_TASK(CamShakeTask);
struct CamWaitToStopTask final : public Task {
CamWaitToStopTask(Process &process)
: Task(process)
, _camera(g_engine->camera()) {}
+ CamWaitToStopTask(Process &process, Serializer &s)
+ : Task(process)
+ , _camera(g_engine->camera()) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
return _camera._isChanging
? TaskReturn::yield()
@@ -440,9 +511,12 @@ struct CamWaitToStopTask final : public Task {
g_engine->console().debugPrintf("Wait for camera to stop moving\n");
}
+ virtual const char *taskName() const override;
+
private:
Camera &_camera;
};
+DECLARE_TASK(CamWaitToStopTask);
struct CamSetInactiveAttributeTask final : public Task {
enum Attribute {
@@ -458,6 +532,12 @@ struct CamSetInactiveAttributeTask final : public Task {
, _value(value)
, _delay(delay) {}
+ CamSetInactiveAttributeTask(Process &process, Serializer &s)
+ : Task(process)
+ , _camera(g_engine->camera()) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
if (_delay > 0) {
uint32 delay = (uint32)_delay;
@@ -488,12 +568,22 @@ struct CamSetInactiveAttributeTask final : public Task {
g_engine->console().debugPrintf("Set inactive camera %s to %f after %dms\n", attributeName, _value, _delay);
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ syncEnum(s, _attribute);
+ s.syncAsFloatLE(_value);
+ s.syncAsSint32LE(_delay);
+ }
+
+ virtual const char *taskName() const override;
+
private:
Camera &_camera;
Attribute _attribute;
float _value;
int32 _delay;
};
+DECLARE_TASK(CamSetInactiveAttributeTask);
Task *Camera::lerpPos(Process &process,
Vector2d targetPos,
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 02f38cedc6c..7631bf81838 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -103,6 +103,8 @@ struct FakeLock {
~FakeLock();
void operator = (FakeLock &&other) noexcept;
void release();
+
+ inline bool isReleased() const { return _semaphore == nullptr; }
private:
FakeSemaphore *_semaphore = nullptr;
};
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 42af33c97c2..5bbeefa21b0 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -292,6 +292,11 @@ struct SayTextTask final : public Task {
, _dialogId(dialogId) {
}
+ SayTextTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
bool isSoundStillPlaying;
@@ -324,13 +329,13 @@ struct SayTextTask final : public Task {
if (!_character->_isTalking) {
g_engine->sounds().fadeOut(_soundHandle, 100);
- TASK_WAIT(delay(200));
+ TASK_WAIT(1, delay(200));
TASK_RETURN(0);
}
_character->isSpeaking() = !isSoundStillPlaying ||
g_engine->sounds().isNoisy(_soundHandle, 80.0f, 150.0f);
- TASK_YIELD;
+ TASK_YIELD(2);
}
TASK_END;
}
@@ -339,11 +344,20 @@ struct SayTextTask final : public Task {
g_engine->console().debugPrintf("SayText %s, %d\n", _character->name().c_str(), _dialogId);
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ syncObjectAsString(s, _character);
+ s.syncAsSint32LE(_dialogId);
+ }
+
+ virtual const char *taskName() const override;
+
private:
Character *_character;
int32 _dialogId;
SoundHandle _soundHandle = {};
};
+DECLARE_TASK(SayTextTask);
Task *Character::sayText(Process &process, int32 dialogId) {
return new SayTextTask(process, this, dialogId);
@@ -376,10 +390,15 @@ struct AnimateCharacterTask final : public Task {
scumm_assert(_graphic != nullptr);
}
+ AnimateCharacterTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
TASK_BEGIN;
while (_character->_curAnimateObject != nullptr)
- TASK_YIELD;
+ TASK_YIELD(1);
_character->_curAnimateObject = _animateObject;
_graphic->start(false);
@@ -387,7 +406,7 @@ struct AnimateCharacterTask final : public Task {
_graphic->update();
do
{
- TASK_YIELD;
+ TASK_YIELD(2);
if (process().isActiveForPlayer() && g_engine->input().wasAnyMouseReleased())
_graphic->pause();
} while (!_graphic->isPaused());
@@ -401,11 +420,22 @@ struct AnimateCharacterTask final : public Task {
g_engine->console().debugPrintf("AnimateCharacter %s, %s\n", _character->name().c_str(), _animateObject->name().c_str());
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ syncObjectAsString(s, _character);
+ syncObjectAsString(s, _animateObject);
+ _graphic = _animateObject->graphic();
+ scumm_assert(_graphic != nullptr);
+ }
+
+ virtual const char *taskName() const override;
+
private:
Character *_character;
ObjectBase *_animateObject;
Graphic *_graphic;
};
+DECLARE_TASK(AnimateCharacterTask);
Task *Character::animate(Process &process, ObjectBase *animateObject) {
assert(animateObject != nullptr);
@@ -420,6 +450,11 @@ struct LerpLodBiasTask final : public Task {
, _durationMs(durationMs) {
}
+ LerpLodBiasTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
TASK_BEGIN;
_startTime = g_engine->getMillis();
@@ -427,7 +462,7 @@ struct LerpLodBiasTask final : public Task {
while (g_engine->getMillis() - _startTime < _durationMs) {
_character->lodBias() = _sourceLodBias + (_targetLodBias - _sourceLodBias) *
((g_engine->getMillis() - _startTime) / (float)_durationMs);
- TASK_YIELD;
+ TASK_YIELD(1);
}
_character->lodBias() = _targetLodBias;
TASK_END;
@@ -441,11 +476,23 @@ struct LerpLodBiasTask final : public Task {
_character->name().c_str(), _targetLodBias, remaining);
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ syncObjectAsString(s, _character);
+ s.syncAsFloatLE(_sourceLodBias);
+ s.syncAsFloatLE(_targetLodBias);
+ s.syncAsUint32LE(_startTime);
+ s.syncAsUint32LE(_durationMs);
+ }
+
+ virtual const char *taskName() const override;
+
private:
Character *_character;
float _sourceLodBias = 0, _targetLodBias;
uint32 _startTime = 0, _durationMs;
};
+DECLARE_TASK(LerpLodBiasTask);
Task *Character::lerpLodBias(Process &process, float targetLodBias, int32 durationMs) {
return new LerpLodBiasTask(process, this, targetLodBias, durationMs);
@@ -729,26 +776,38 @@ void WalkingCharacter::syncGame(Serializer &serializer) {
}
struct ArriveTask : public Task {
- ArriveTask(Process &process, const WalkingCharacter &character)
+ ArriveTask(Process &process, const WalkingCharacter *character)
: Task(process)
, _character(character) {
}
+ ArriveTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
- return _character.isWalking()
+ return _character->isWalking()
? TaskReturn::yield()
: TaskReturn::finish(1);
}
virtual void debugPrint() override {
- g_engine->getDebugger()->debugPrintf("Wait for %s to arrive", _character.name().c_str());
+ g_engine->getDebugger()->debugPrintf("Wait for %s to arrive", _character->name().c_str());
}
+
+ virtual void syncGame(Serializer &s) override {
+ syncObjectAsString(s, _character);
+ }
+
+ virtual const char *taskName() const override;
private:
- const WalkingCharacter &_character;
+ const WalkingCharacter *_character;
};
+DECLARE_TASK(ArriveTask);
Task *WalkingCharacter::waitForArrival(Process &process) {
- return new ArriveTask(process, *this);
+ return new ArriveTask(process, this);
}
const char *MainCharacter::typeName() const { return "MainCharacter"; }
@@ -983,11 +1042,17 @@ struct DialogMenuTask : public Task {
, _character(character) {
}
+ DialogMenuTask(Process &process, Serializer &s)
+ : Task(process)
+ , _input(g_engine->input()) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
TASK_BEGIN;
layoutLines();
while (true) {
- TASK_YIELD;
+ TASK_YIELD(1);
if (g_engine->player().activeCharacter() != _character)
continue;
g_engine->globalUI().updateChangingCharacter();
@@ -996,8 +1061,8 @@ struct DialogMenuTask : public Task {
_clickedLineI = updateLines();
if (_clickedLineI != UINT_MAX) {
- TASK_YIELD;
- TASK_WAIT(_character->sayText(process(), _character->_dialogLines[_clickedLineI]._dialogId));
+ TASK_YIELD(2);
+ TASK_WAIT(3, _character->sayText(process(), _character->_dialogLines[_clickedLineI]._dialogId));
int32 returnValue = _character->_dialogLines[_clickedLineI]._returnValue;
_character->_dialogLines.clear();
TASK_RETURN(returnValue);
@@ -1011,6 +1076,14 @@ struct DialogMenuTask : public Task {
_character->name().c_str(), _character->_dialogLines.size());
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ syncObjectAsString(s, _character);
+ s.syncAsUint32LE(_clickedLineI);
+ }
+
+ virtual const char *taskName() const override;
+
private:
static constexpr int kTextXOffset = 5;
static constexpr int kTextYOffset = 10;
@@ -1055,6 +1128,7 @@ private:
MainCharacter *_character;
uint _clickedLineI = UINT_MAX;
};
+DECLARE_TASK(DialogMenuTask);
void MainCharacter::addDialogLine(int32 dialogId) {
assert(dialogId >= 0);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index e55d08fd2ee..6795d6ebf05 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -135,15 +135,20 @@ struct AnimateTask : public Task {
, _object(object) {
assert(_object != nullptr);
_graphic = object->graphic();
- assert(_graphic != nullptr);
+ scumm_assert(_graphic != nullptr);
_duration = _graphic->animation().totalDuration();
}
+ AnimateTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
TASK_BEGIN;
_object->toggle(true);
_graphic->start(false);
- TASK_WAIT(delay(_duration));
+ TASK_WAIT(1, delay(_duration));
_object->toggle(false);
TASK_END;
}
@@ -152,11 +157,23 @@ struct AnimateTask : public Task {
g_engine->getDebugger()->debugPrintf("Animate \"%s\" for %ums", _object->name().c_str(), _duration);
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ s.syncAsUint32LE(_duration);
+ syncObjectAsString(s, _object);
+ _graphic = _object->graphic();
+ scumm_assert(_graphic != nullptr);
+
+ }
+
+ virtual const char *taskName() const override;
+
private:
GraphicObject *_object;
Graphic *_graphic;
uint32 _duration;
};
+DECLARE_TASK(AnimateTask);
Task *GraphicObject::animate(Process &process) {
return new AnimateTask(process, this);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index fbb13b4d6a7..06d7a09dee3 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -197,6 +197,11 @@ struct CenterBottomTextTask : public Task {
, _durationMs(durationMs) {
}
+ CenterBottomTextTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
TaskReturn run() override {
Font &font = g_engine->globalUI().dialogFont();
const char *text = g_engine->world().getDialogLine(_dialogId);
@@ -212,22 +217,32 @@ struct CenterBottomTextTask : public Task {
g_engine->drawQueue().add<TextDrawRequest>(
font, text, pos, -1, true, kWhite, 1);
}
- TASK_YIELD;
+ TASK_YIELD(1);
}
TASK_END;
}
- void debugPrint() override {
+ virtual void debugPrint() override {
uint32 remaining = g_engine->getMillis() - _startTime <= _durationMs
? _durationMs - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("CenterBottomText (%d) with %ums remaining\n", _dialogId, remaining);
}
+ virtual void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ s.syncAsSint32LE(_dialogId);
+ s.syncAsUint32LE(_startTime);
+ s.syncAsUint32LE(_durationMs);
+ }
+
+ virtual const char *taskName() const override;
+
private:
int32 _dialogId;
uint32 _startTime = 0, _durationMs;
};
+DECLARE_TASK(CenterBottomTextTask);
Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs) {
return new CenterBottomTextTask(process, dialogId, durationMs);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 8a87cbb65e9..cf47dac4ec5 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -778,6 +778,11 @@ struct FadeTask : public Task {
, _permanentFadeAction(permanentFadeAction) {
}
+ FadeTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
TASK_BEGIN;
if (_permanentFadeAction == PermanentFadeAction::UnsetFaded)
@@ -785,7 +790,7 @@ struct FadeTask : public Task {
_startTime = g_engine->getMillis();
while (g_engine->getMillis() - _startTime < _duration) {
draw((g_engine->getMillis() - _startTime) / (float)_duration);
- TASK_YIELD;
+ TASK_YIELD(1);
}
draw(1.0f); // so that during a loading lag the screen is completly black/white
if (_permanentFadeAction == PermanentFadeAction::SetFaded)
@@ -800,6 +805,20 @@ struct FadeTask : public Task {
g_engine->console().debugPrintf("Fade (%d) from %.2f to %.2f with %ums remaining\n", (int)_fadeType, _from, _to, remaining);
}
+ void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ syncEnum(s, _fadeType);
+ syncEnum(s, _easingType);
+ syncEnum(s, _permanentFadeAction);
+ s.syncAsFloatLE(_from);
+ s.syncAsFloatLE(_to);
+ s.syncAsUint32LE(_startTime);
+ s.syncAsUint32LE(_duration);
+ s.syncAsSByte(_order);
+ }
+
+ virtual const char *taskName() const override;
+
private:
void draw(float t) {
g_engine->drawQueue().add<FadeDrawRequest>(_fadeType, _from + (_to - _from) * ease(t, _easingType), _order);
@@ -812,6 +831,7 @@ private:
int8 _order;
PermanentFadeAction _permanentFadeAction;
};
+DECLARE_TASK(FadeTask);
Task *fade(Process &process, FadeType fadeType,
float from, float to,
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 015d60cb85f..a9fecdb049a 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -199,33 +199,25 @@ struct DoorTask : public Task {
, _player(g_engine->player())
, _targetObject(nullptr)
, _targetDirection(Direction::Invalid) {
- _targetRoom = g_engine->world().getRoomByName(door->targetRoom().c_str());
- if (_targetRoom == nullptr) {
- g_engine->game().unknownDoorTargetRoom(door->targetRoom());
- return;
- }
-
- _targetObject = dynamic_cast<InteractableObject *>(_targetRoom->getObjectByName(door->targetObject().c_str()));
- if (_targetObject == nullptr) {
- g_engine->game().unknownDoorTargetDoor(door->targetRoom(), door->targetObject());
- return;
- }
- _targetDirection = door->characterDirection();
-
+ findTarget();
process.name() = String::format("Door to %s %s", _targetRoom->name().c_str(), _targetObject->name().c_str());
}
- virtual TaskReturn run() {
- FakeLock musicLock;
+ DoorTask(Process &process, Serializer &s)
+ : Task(process)
+ , _player(g_engine->player()) {
+ syncGame(s);
+ }
+ virtual TaskReturn run() {
TASK_BEGIN;
if (_targetRoom == nullptr || _targetObject == nullptr)
return TaskReturn::finish(1);
- musicLock = FakeLock(g_engine->sounds().musicSemaphore());
+ _musicLock = FakeLock(g_engine->sounds().musicSemaphore());
if (g_engine->sounds().musicID() != _targetRoom->musicID())
g_engine->sounds().fadeMusic();
- TASK_WAIT(fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
+ TASK_WAIT(1, fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
_player.changeRoom(_targetRoom->name(), true);
if (_targetRoom->fixedCameraOnEntering())
@@ -238,12 +230,12 @@ struct DoorTask : public Task {
}
g_engine->sounds().setMusicToRoom(_targetRoom->musicID());
- musicLock.release();
+ _musicLock.release();
if (g_engine->script().createProcess(_character->kind(), "ENTRAR_" + _targetRoom->name(), ScriptFlags::AllowMissing))
- TASK_YIELD;
+ TASK_YIELD(2);
else
- TASK_WAIT(fade(process(), FadeType::ToBlack, 1, 0, 500, EasingType::Out, -5));
+ TASK_WAIT(3, fade(process(), FadeType::ToBlack, 1, 0, 500, EasingType::Out, -5));
TASK_END;
}
@@ -251,8 +243,41 @@ struct DoorTask : public Task {
g_engine->console().debugPrintf("%s\n", process().name().c_str());
}
+ void syncGame(Serializer &s) override {
+ assert(s.isSaving() || (_lock.isReleased() && _musicLock.isReleased()));
+
+ Task::syncGame(s);
+ syncObjectAsString(s, _sourceDoor);
+ syncObjectAsString(s, _character);
+ bool hasMusicLock = !_musicLock.isReleased();
+ s.syncAsByte(hasMusicLock);
+ if (s.isLoading() && hasMusicLock)
+ _musicLock = FakeLock(g_engine->sounds().musicSemaphore());
+
+ _lock = FakeLock(_character->semaphore());
+ findTarget();
+ }
+
+ virtual const char *taskName() const override;
+
private:
- FakeLock _lock;
+ void findTarget() {
+ _targetRoom = g_engine->world().getRoomByName(_sourceDoor->targetRoom().c_str());
+ if (_targetRoom == nullptr) {
+ g_engine->game().unknownDoorTargetRoom(_sourceDoor->targetRoom());
+ return;
+ }
+
+ _targetObject = dynamic_cast<InteractableObject *>(_targetRoom->getObjectByName(_sourceDoor->targetObject().c_str()));
+ if (_targetObject == nullptr) {
+ g_engine->game().unknownDoorTargetDoor(_sourceDoor->targetRoom(), _sourceDoor->targetObject());
+ return;
+ }
+
+ _targetDirection = _sourceDoor->characterDirection();
+ }
+
+ FakeLock _lock, _musicLock;
const Door *_sourceDoor;
const InteractableObject *_targetObject;
Direction _targetDirection;
@@ -260,6 +285,7 @@ private:
MainCharacter *_character;
Player &_player;
};
+DECLARE_TASK(DoorTask);
void Player::triggerDoor(const Door *door) {
_heldItem = nullptr;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 69cc16a4fde..e67f8575e86 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -558,6 +558,9 @@ MainCharacter &World::getOtherMainCharacterByKind(MainCharacterKind kind) const
}
Room *World::getRoomByName(const char *name) const {
+ assert(name != nullptr);
+ if (*name == '\0')
+ return nullptr;
for (auto *room : _rooms) {
if (room->name().equalsIgnoreCase(name))
return room;
@@ -593,6 +596,9 @@ ObjectBase *World::getObjectByName(MainCharacterKind character, const char *name
}
ObjectBase *World::getObjectByNameFromAnyRoom(const char *name) const {
+ assert(name != nullptr);
+ if (*name == '\0')
+ return nullptr;
for (auto *room : _rooms) {
ObjectBase *result = room->getObjectByName(name);
if (result != nullptr)
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 982515ea664..31ab38b8e1a 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -55,16 +55,42 @@ Task *Task::delay(uint32 millis) {
return new DelayTask(process(), millis);
}
+void Task::syncGame(Serializer &s) {
+ s.syncAsUint32LE(_stage);
+}
+
+void Task::syncObjectAsString(Serializer &s, ObjectBase *&object, bool optional) {
+ String objectName, roomName;
+ if (object != nullptr) {
+ roomName = object->room()->name();
+ objectName = object->name();
+ }
+ s.syncString(roomName);
+ s.syncString(objectName);
+ if (s.isSaving())
+ return;
+ Room *room = g_engine->world().getRoomByName(roomName.c_str());
+ object = room == nullptr ? nullptr : room->getObjectByName(objectName.c_str());
+ if ((object == nullptr && !optional) || !roomName.empty() || !objectName.empty())
+ error("Invalid object name \"%s\" in room \"%s\" in savestate for task %s",
+ objectName.c_str(), roomName.c_str(), taskName());
+}
+
DelayTask::DelayTask(Process &process, uint32 millis)
: Task(process)
, _endTime(millis) {
}
+DelayTask::DelayTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+}
+
TaskReturn DelayTask::run() {
TASK_BEGIN;
_endTime += g_engine->getMillis();
while (g_engine->getMillis() < _endTime)
- TASK_YIELD;
+ TASK_YIELD(1);
TASK_END;
}
@@ -73,12 +99,23 @@ void DelayTask::debugPrint() {
g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
}
+void DelayTask::syncGame(Serializer &s) {
+ Task::syncGame(s);
+ s.syncAsUint32LE(_endTime);
+}
+
+DECLARE_TASK(DelayTask);
+
Process::Process(ProcessId pid, MainCharacterKind characterKind)
: _pid(pid)
, _character(characterKind)
, _name("Unnamed process") {
}
+Process::Process(Serializer &s) {
+ syncGame(s);
+}
+
Process::~Process() {
while (!_tasks.empty())
delete _tasks.pop();
@@ -125,6 +162,42 @@ void Process::debugPrint() {
}
}
+#define DEFINE_TASK(TaskName) \
+ extern Task *constructTask_##TaskName(Process &process, Serializer &s);
+#include "tasks.h"
+
+static Task *readTask(Process &process, Serializer &s) {
+ assert(s.isLoading());
+ String taskName;
+ s.syncString(taskName);
+
+#define DEFINE_TASK(TaskName) \
+ if (taskName == #TaskName) \
+ return constructTask_##TaskName(process, s);
+#include "tasks.h"
+
+ error("Invalid task type in savestate: %s", taskName.c_str());
+}
+
+void Process::syncGame(Serializer &s) {
+ s.syncAsUint32LE(_pid);
+ syncEnum(s, _character);
+ s.syncString(_name);
+ s.syncAsSint32LE(_lastReturnValue);
+
+ uint count = _tasks.size();
+ s.syncAsUint32LE(count);
+ if (s.isLoading()) {
+ assert(_tasks.empty());
+ for (uint i = 0; i < count; i++)
+ _tasks.push(readTask(*this, s));
+ }
+ else {
+ for (uint i = 0; i < count; i++)
+ _tasks[i]->syncGame(s);
+ }
+}
+
static void killProcessesForIn(MainCharacterKind characterKind, Array<Process *> &processes, uint firstIndex) {
assert(firstIndex <= processes.size());
for (uint i = 0; i < processes.size() - firstIndex; i++) {
@@ -252,4 +325,32 @@ void Scheduler::debugPrint() {
console.debugPrintf("No processes running or backed up\n");
}
+void Scheduler::prepareSyncGame(Serializer &s) {
+ if (s.isLoading()) {
+ killAllProcesses();
+ killProcessesForIn(MainCharacterKind::None, _backupProcesses, 0);
+ }
+}
+
+void Scheduler::syncGame(Serializer &s) {
+ assert(_currentProcessI == UINT_MAX); // let's not sync during ::run
+ assert(s.isSaving() || _backupProcesses.empty());
+
+ // we only sync the backupProcesses as these are the ones pertaining to the gameplay
+ // the other arrays would be used for the menu
+
+ s.syncAsUint32LE(_nextPid);
+ uint32 count = _backupProcesses.size();
+ s.syncAsUint32LE(count);
+ if (s.isLoading()) {
+ _backupProcesses.reserve(count);
+ for (uint32 i = 0; i < count; i++)
+ _backupProcesses.push_back(new Process(s));
+ }
+ else {
+ for (Process *process : _backupProcesses)
+ process->syncGame(s);
+ }
+}
+
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 5299b029f61..3ba95f298fc 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -26,12 +26,30 @@
#include "common/stack.h"
#include "common/str.h"
+#include "common/type_traits.h"
namespace Alcachofa {
+/* Tasks are generally written as coroutines however the common coroutines
+ * cannot be used for two reasons:
+ * 1. The scheduler is too limited in managing when to run what coroutines
+ * E.g. for the inventory/menu we need to pause a set of coroutines and
+ * continue them later on
+ * 2. We need to save and load the state of coroutines
+ * For this we either write the state machine ourselves or we use
+ * the following careful macros where the state ID is explicitly written
+ * This way it is stable and if it has to change we can migrate
+ * savestates upon loading.
+ *
+ * Tasks are usually private, so in order to load them they:
+ * - need a constructor MyPrivateTask(Process &, Serializer &)
+ * - need call the macro DECLARE_TASK(MyPrivateTask)
+ * - they have to listed in tasks.h
+ */
+
struct Task;
class Process;
-
+class ObjectBase;
enum class TaskReturnType {
Yield,
@@ -68,26 +86,52 @@ struct Task {
virtual ~Task() = default;
virtual TaskReturn run() = 0;
virtual void debugPrint() = 0;
+ virtual void syncGame(Common::Serializer &s);
+ virtual const char *taskName() const = 0; // implemented by DECLARE_TASK
inline Process &process() const { return _process; }
protected:
Task *delay(uint32 millis);
- uint32 _line = 0;
+ void syncObjectAsString(Common::Serializer &s, ObjectBase *&object, bool optional = false);
+ template<class TObject>
+ void syncObjectAsString(Common::Serializer &s, TObject *&object, bool optional = false) {
+ // We could add is_const and therefore true_type, false_type, integral_constant
+ // or we could just use const_cast and promise that we won't modify
+ ObjectBase *base = const_cast<Common::remove_const_t<TObject> *>(object);
+ syncObjectAsString(s, base, optional);
+ object = dynamic_cast<TObject*>(base);
+ if (object == nullptr && base != nullptr)
+ error("Unexpected type of object %s in savestate for task %s (got a %s)",
+ base->name().c_str(), taskName(), base->typeName());
+ }
+
+ uint32 _stage = 0;
private:
Process &_process;
};
struct DelayTask : public Task {
DelayTask(Process &process, uint32 millis);
+ DelayTask(Process &process, Common::Serializer &s);
virtual TaskReturn run() override;
virtual void debugPrint() override;
+ virtual void syncGame(Common::Serializer &s) override;
+ virtual const char *taskName() const override;
private:
uint32 _endTime;
};
+#define DECLARE_TASK(TaskName) \
+ extern Task *constructTask_##TaskName(Process &process, Serializer &s) { \
+ return new TaskName(process, s); \
+ } \
+ const char *TaskName::taskName() const { \
+ return #TaskName; \
+ }
+
// TODO: This probably should be scummvm common
#if __cplusplus >= 201703L
#define TASK_BREAK_FALLTHROUGH [[fallthrough]];
@@ -96,8 +140,7 @@ private:
#endif
#define TASK_BEGIN \
- enum { TASK_COUNTER_BASE = __COUNTER__ }; \
- switch(_line) { \
+ switch(_stage) { \
case 0:; \
#define TASK_END \
@@ -106,28 +149,28 @@ private:
default: assert(false && "Invalid line in task"); \
} return TaskReturn::finish(0)
-#define TASK_INTERNAL_BREAK(ret) \
+#define TASK_INTERNAL_BREAK(stage, ret) \
do { \
- enum { TASK_COUNTER = __COUNTER__ - TASK_COUNTER_BASE }; \
- _line = TASK_COUNTER; \
+ _stage = stage; \
return ret; \
TASK_BREAK_FALLTHROUGH \
- case TASK_COUNTER:; \
+ case stage:; \
} while(0)
-#define TASK_YIELD TASK_INTERNAL_BREAK(TaskReturn::yield())
-#define TASK_WAIT(task) TASK_INTERNAL_BREAK(TaskReturn::waitFor(task))
+#define TASK_YIELD(stage) TASK_INTERNAL_BREAK((stage), TaskReturn::yield())
+#define TASK_WAIT(stage, task) TASK_INTERNAL_BREAK((stage), TaskReturn::waitFor(task))
#define TASK_RETURN(value) \
do { \
return TaskReturn::finish(value); \
- _line = UINT_MAX; \
+ _stage = UINT_MAX; \
} while(0)
-using ProcessId = uint;
+using ProcessId = uint32;
class Process {
public:
Process(ProcessId pid, MainCharacterKind characterKind);
+ Process(Common::Serializer &s);
~Process();
inline ProcessId pid() const { return _pid; }
@@ -139,6 +182,7 @@ public:
TaskReturnType run();
void debugPrint();
+ void syncGame(Common::Serializer &s);
private:
friend class Scheduler;
@@ -161,6 +205,8 @@ public:
void killProcessByName(const Common::String &name);
bool hasProcessWithName(const Common::String &name);
void debugPrint();
+ void prepareSyncGame(Common::Serializer &s);
+ void syncGame(Common::Serializer &s);
template<typename TTask, typename... TaskArgs>
Process *createProcess(MainCharacterKind character, TaskArgs&&... args) {
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 51184d13ba7..9cea4464da3 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -137,6 +137,11 @@ struct ScriptTimerTask : public Task {
, _durationSec(durationSec) {
}
+ ScriptTimerTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
TASK_BEGIN;
{
@@ -148,7 +153,7 @@ struct ScriptTimerTask : public Task {
_result = 1;
g_engine->player().drawCursor();
}
- TASK_YIELD; // Wait a frame to not produce an endless loop
+ TASK_YIELD(1); // Wait a frame to not produce an endless loop
TASK_RETURN(_result);
TASK_END;
}
@@ -157,10 +162,19 @@ struct ScriptTimerTask : public Task {
g_engine->getDebugger()->debugPrintf("Check input timer for %dsecs", _durationSec);
}
+ void syncGame(Serializer &s) override {
+ Task::syncGame(s);
+ s.syncAsSint32LE(_durationSec);
+ s.syncAsSint32LE(_result);
+ }
+
+ virtual const char *taskName() const override;
+
private:
int32 _durationSec;
int32 _result = 1;
};
+DECLARE_TASK(ScriptTimerTask);
enum class StackEntryType {
Number,
@@ -172,6 +186,15 @@ enum class StackEntryType {
struct StackEntry {
StackEntry(StackEntryType type, int32 number) : _type(type), _number(number) {}
StackEntry(StackEntryType type, uint32 index) : _type(type), _index(index) {}
+ StackEntry(Serializer &s) { syncGame(s); }
+
+ void syncGame(Serializer &s) {
+ syncEnum(s, _type);
+ if (_type == StackEntryType::Number)
+ s.syncAsSint32LE(_number);
+ else
+ s.syncAsUint32LE(_index);
+ }
StackEntryType _type;
union {
@@ -203,6 +226,12 @@ struct ScriptTask : public Task {
debugC(SCRIPT_DEBUG_LVL_TASKS, kDebugScript, "%u: Script fork from %u at %u", process.pid(), forkParent.process().pid(), _pc);
}
+ ScriptTask(Process &process, Serializer &s)
+ : Task(process)
+ , _script(g_engine->script()) {
+ syncGame(s);
+ }
+
virtual TaskReturn run() override {
if (_isFirstExecution || _returnsFromKernelCall)
setCharacterVariables();
@@ -342,6 +371,33 @@ struct ScriptTask : public Task {
g_engine->getDebugger()->debugPrintf("\"%s\" at %u\n", _name.c_str(), _pc);
}
+ void syncGame(Serializer &s) override {
+ assert(s.isSaving() || (_lock.isReleased() && _stack.empty()));
+
+ s.syncString(_name);
+ s.syncAsUint32LE(_pc);
+ s.syncAsByte(_returnsFromKernelCall);
+ s.syncAsByte(_isFirstExecution);
+
+ uint count = _stack.size();
+ s.syncAsUint32LE(count);
+ if (s.isLoading()) {
+ for (uint i = 0; i < count; i++)
+ _stack.push(StackEntry(s));
+ }
+ else {
+ for (uint i = 0; i < count; i++)
+ _stack[i].syncGame(s);
+ }
+
+ bool hasLock = !_lock.isReleased();
+ s.syncAsByte(hasLock);
+ if (hasLock)
+ _lock = FakeLock(g_engine->player().semaphoreFor(process().character()));
+ }
+
+ virtual const char *taskName() const override;
+
private:
void setCharacterVariables() {
_script.variable("m_o_f") = (int32)process().character();
@@ -894,6 +950,7 @@ private:
bool _isFirstExecution = true;
FakeLock _lock;
};
+DECLARE_TASK(ScriptTask);
Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, ScriptFlags flags) {
return createProcess(character, behavior + '/' + action, flags);
@@ -910,7 +967,7 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
}
FakeLock lock;
if (!(flags & ScriptFlags::IsBackground))
- new (&lock) FakeLock(g_engine->player().semaphoreFor(character));
+ lock = FakeLock(g_engine->player().semaphoreFor(character));
Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset, Common::move(lock));
process->name() = procedure;
return process;
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index f4ad498203f..6d719b44e16 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -329,8 +329,7 @@ void Sounds::setMusicToRoom(int roomMusicId) {
}
Task *Sounds::waitForMusicToEnd(Process &process) {
- FakeLock lock(_musicSemaphore);
- return new WaitForMusicTask(process, std::move(lock));
+ return new WaitForMusicTask(process);
}
PlaySoundTask::PlaySoundTask(Process &process, SoundHandle SoundHandle)
@@ -338,6 +337,14 @@ PlaySoundTask::PlaySoundTask(Process &process, SoundHandle SoundHandle)
, _soundHandle(SoundHandle) {
}
+PlaySoundTask::PlaySoundTask(Process &process, Serializer &s)
+ : Task(process)
+ , _soundHandle({}) {
+ // playing sounds are not persisted in the savestates,
+ // this task will stop at the next frame
+ syncGame(s);
+}
+
TaskReturn PlaySoundTask::run() {
auto &sounds = g_engine->sounds();
if (sounds.isAlive(_soundHandle))
@@ -353,9 +360,17 @@ void PlaySoundTask::debugPrint() {
g_engine->console().debugPrintf("PlaySound %u\n", _soundHandle);
}
-WaitForMusicTask::WaitForMusicTask(Process &process, FakeLock &&lock)
+DECLARE_TASK(PlaySoundTask);
+
+WaitForMusicTask::WaitForMusicTask(Process &process)
: Task(process)
- , _lock(std::move(lock)) {}
+ , _lock(g_engine->sounds().musicSemaphore()) {}
+
+WaitForMusicTask::WaitForMusicTask(Process &process, Serializer &s)
+ : Task(process)
+ , _lock(g_engine->sounds().musicSemaphore()) {
+ syncGame(s);
+}
TaskReturn WaitForMusicTask::run() {
g_engine->sounds().queueMusic(-1);
@@ -368,4 +383,6 @@ void WaitForMusicTask::debugPrint() {
g_engine->console().debugPrintf("WaitForMusic\n");
}
+DECLARE_TASK(WaitForMusicTask);
+
}
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 1d636cb32c7..2ebe71ad4b8 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -85,16 +85,20 @@ private:
struct PlaySoundTask final : public Task {
PlaySoundTask(Process &process, SoundHandle soundHandle);
+ PlaySoundTask(Process &process, Common::Serializer &s);
virtual TaskReturn run() override;
virtual void debugPrint() override;
+ virtual const char *taskName() const override;
private:
SoundHandle _soundHandle;
};
struct WaitForMusicTask final : public Task {
- WaitForMusicTask(Process &process, FakeLock &&lock);
+ WaitForMusicTask(Process &process);
+ WaitForMusicTask(Process &process, Common::Serializer &s);
virtual TaskReturn run() override;
virtual void debugPrint() override;
+ virtual const char *taskName() const override;
private:
FakeLock _lock;
};
diff --git a/engines/alcachofa/tasks.h b/engines/alcachofa/tasks.h
new file mode 100644
index 00000000000..163fed7166f
--- /dev/null
+++ b/engines/alcachofa/tasks.h
@@ -0,0 +1,56 @@
+/* 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/>.
+ *
+ */
+
+/* The task loading works as follows:
+ * - the task has a constructor MyPrivateTask(Process &, Serializer &)
+ * - DECLARE_TASK implements a global function to call that constructor
+ * void constructTask_MyPrivateTask(Process &, Serializer
+ * - in Process::syncGame we first forward-declare that function
+ * - we go through every task to compare task name and call the factory
+ */
+
+#ifndef DEFINE_TASK
+#define DEFINE_TASK(TaskName)
+#endif
+
+DEFINE_TASK(CamLerpPosTask);
+DEFINE_TASK(CamLerpScaleTask);
+DEFINE_TASK(CamLerpPosScaleTask);
+DEFINE_TASK(CamLerpRotationTask);
+DEFINE_TASK(CamShakeTask);
+DEFINE_TASK(CamWaitToStopTask);
+DEFINE_TASK(CamSetInactiveAttributeTask);
+DEFINE_TASK(SayTextTask);
+DEFINE_TASK(AnimateCharacterTask);
+DEFINE_TASK(LerpLodBiasTask);
+DEFINE_TASK(ArriveTask)
+DEFINE_TASK(DialogMenuTask)
+DEFINE_TASK(AnimateTask)
+DEFINE_TASK(CenterBottomTextTask)
+DEFINE_TASK(FadeTask)
+DEFINE_TASK(DoorTask)
+DEFINE_TASK(DelayTask)
+DEFINE_TASK(ScriptTimerTask)
+DEFINE_TASK(ScriptTask)
+DEFINE_TASK(PlaySoundTask)
+DEFINE_TASK(WaitForMusicTask)
+
+#undef DEFINE_TASK
Commit: 700eee9de451169e2c74cb2c2b9191616d963d68
https://github.com/scummvm/scummvm/commit/700eee9de451169e2c74cb2c2b9191616d963d68
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Remove virtual on overridden methods
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/debug.h
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index fb4c4de1d87..3c77a4d99f7 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -291,7 +291,7 @@ struct CamLerpTask : public Task {
, _duration(duration)
, _easingType(easingType) {}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
_startTime = g_engine->getMillis();
while (g_engine->getMillis() - _startTime < _duration) {
@@ -303,14 +303,14 @@ struct CamLerpTask : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
uint32 remaining = g_engine->getMillis() - _startTime <= _duration
? _duration - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("%s camera with %ums remaining\n", taskName(), remaining);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
s.syncAsUint32LE(_startTime);
s.syncAsUint32LE(_duration);
@@ -336,7 +336,7 @@ struct CamLerpPosTask final : public CamLerpTask {
syncGame(s);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
CamLerpTask::syncGame(s);
syncVector(s, _fromPos);
syncVector(s, _deltaPos);
@@ -345,7 +345,7 @@ struct CamLerpPosTask final : public CamLerpTask {
virtual const char *taskName() const override;
protected:
- virtual void update(float t) override {
+ void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * t);
}
@@ -364,7 +364,7 @@ struct CamLerpScaleTask final : public CamLerpTask {
syncGame(s);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
CamLerpTask::syncGame(s);
s.syncAsFloatLE(_fromScale);
s.syncAsFloatLE(_deltaScale);
@@ -373,7 +373,7 @@ struct CamLerpScaleTask final : public CamLerpTask {
virtual const char *taskName() const override;
protected:
- virtual void update(float t) override {
+ void update(float t) override {
_camera._cur._scale = _fromScale + _deltaScale * t;
}
@@ -399,7 +399,7 @@ struct CamLerpPosScaleTask final : public CamLerpTask {
syncGame(s);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
CamLerpTask::syncGame(s);
syncVector(s, _fromPos);
syncVector(s, _deltaPos);
@@ -412,7 +412,7 @@ struct CamLerpPosScaleTask final : public CamLerpTask {
virtual const char *taskName() const override;
protected:
- virtual void update(float t) override {
+ void update(float t) override {
_camera.setPosition(_fromPos + _deltaPos * ease(t, _moveEasingType));
_camera._cur._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
}
@@ -434,7 +434,7 @@ struct CamLerpRotationTask final : public CamLerpTask {
syncGame(s);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
CamLerpTask::syncGame(s);
s.syncAsFloatLE(_fromRotation);
s.syncAsFloatLE(_deltaRotation);
@@ -443,7 +443,7 @@ struct CamLerpRotationTask final : public CamLerpTask {
virtual const char *taskName() const override;
protected:
- virtual void update(float t) override {
+ void update(float t) override {
_camera._cur._rotation = Angle(_fromRotation + _deltaRotation * t);
}
@@ -468,7 +468,7 @@ struct CamShakeTask final : public CamLerpTask {
syncGame(s);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
CamLerpTask::syncGame(s);
syncVector(s, _amplitude);
syncVector(s, _frequency);
@@ -477,7 +477,7 @@ struct CamShakeTask final : public CamLerpTask {
virtual const char *taskName() const override;
protected:
- virtual void update(float t) override {
+ void update(float t) override {
const Vector2d phase = _frequency * t * (float)M_PI * 2.0f;
const float amplTimeFactor = 1.0f / expf(t * 5.0f); // a curve starting at 1, depreciating towards 0
_camera.shake() = {
@@ -501,13 +501,13 @@ struct CamWaitToStopTask final : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
return _camera._isChanging
? TaskReturn::yield()
: TaskReturn::finish(1);
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->console().debugPrintf("Wait for camera to stop moving\n");
}
@@ -538,7 +538,7 @@ struct CamSetInactiveAttributeTask final : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
if (_delay > 0) {
uint32 delay = (uint32)_delay;
_delay = 0;
@@ -557,7 +557,7 @@ struct CamSetInactiveAttributeTask final : public Task {
return TaskReturn::finish(0);
}
- virtual void debugPrint() override {
+ void debugPrint() override {
const char *attributeName;
switch (_attribute) {
case kPosZ: attributeName = "PosZ"; break;
@@ -568,7 +568,7 @@ struct CamSetInactiveAttributeTask final : public Task {
g_engine->console().debugPrintf("Set inactive camera %s to %f after %dms\n", attributeName, _value, _delay);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
syncEnum(s, _attribute);
s.syncAsFloatLE(_value);
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index cdfc42c8c93..561b3683f09 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -40,7 +40,7 @@ class ClosestFloorPointDebugHandler final : public IDebugHandler {
public:
ClosestFloorPointDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
- virtual void update() override {
+ void update() override {
auto mousePos2D = g_engine->input().debugInput().mousePos2D();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
auto floor = g_engine->player().currentRoom()->activeFloor();
@@ -63,7 +63,7 @@ class FloorIntersectionsDebugHandler final : public IDebugHandler {
public:
FloorIntersectionsDebugHandler(int32 polygonI) : _polygonI(polygonI) {}
- virtual void update() override {
+ void update() override {
auto floor = g_engine->player().currentRoom()->activeFloor();
auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
if (floor == nullptr || renderer == nullptr) {
@@ -118,7 +118,7 @@ class TeleportCharacterDebugHandler final : public IDebugHandler {
public:
TeleportCharacterDebugHandler(int32 kindI) : _kind((MainCharacterKind)kindI) {}
- virtual void update() override {
+ void update() override {
g_engine->drawQueue().clear();
g_engine->player().drawCursor(true);
g_engine->drawQueue().draw();
@@ -189,7 +189,7 @@ public:
return nullptr;
}
- virtual void update() override {
+ void update() override {
auto &input = g_engine->input().debugInput();
if (input.wasMouseRightPressed()) {
g_engine->setDebugMode(DebugMode::None, 0);
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 2c5225a6219..b8c4104a923 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -27,17 +27,17 @@ using namespace Common;
namespace Alcachofa {
class GameMovieAdventure : public Game {
- virtual bool doesRoomHaveBackground(const Room *room) override {
+ bool doesRoomHaveBackground(const Room *room) override {
return !room->name().equalsIgnoreCase("Global") &&
!room->name().equalsIgnoreCase("HABITACION_NEGRA");
}
- virtual void invalidDialogLine(uint index) override {
+ void invalidDialogLine(uint index) override {
if (index != 4542)
Game::invalidDialogLine(index);
}
- virtual bool shouldCharacterTrigger(const Character *character, const char *action) override {
+ bool shouldCharacterTrigger(const Character *character, const char *action) override {
// An original hack to check that bed sheet is used on the other main character only in the correct room
// There *is* another script variable (es_casa_freddy) that should check this
// but, I guess, Alcachofa Soft found a corner case where this does not work?
@@ -57,12 +57,12 @@ class GameMovieAdventure : public Game {
return Game::shouldTriggerDoor(door);
}
- virtual bool hasMortadeloVoice(const Character *character) override {
+ bool hasMortadeloVoice(const Character *character) override {
return Game::hasMortadeloVoice(character) ||
character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
}
- virtual void missingAnimation(const String &fileName) override {
+ void missingAnimation(const String &fileName) override {
static const char *exemptions[] = {
"ANIMACION.AN0",
"DESPACHO_SUPER2_OL_SOMBRAS2.AN0",
@@ -82,13 +82,13 @@ class GameMovieAdventure : public Game {
Game::missingAnimation(fileName);
}
- virtual void unknownAnimateObject(const char *name) override {
+ void unknownAnimateObject(const char *name) override {
if (!scumm_stricmp("EXPLOSION DISFRAZ", name))
return;
Game::unknownAnimateObject(name);
}
- virtual PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
+ PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
if (scumm_stricmp(action, "put"))
return Game::unknownGoPutTarget(process, action, name);
@@ -117,20 +117,20 @@ class GameMovieAdventure : public Game {
return Game::unknownGoPutTarget(process, action, name);
}
- virtual void unknownSayTextCharacter(const char *name, int32 dialogId) override {
+ void unknownSayTextCharacter(const char *name, int32 dialogId) override {
if (!scumm_stricmp(name, "OFELIA") && dialogId == 3737)
return;
Game::unknownSayTextCharacter(name, dialogId);
}
- virtual void unknownAnimateCharacterObject(const char *name) override {
+ void unknownAnimateCharacterObject(const char *name) override {
if (!scumm_stricmp(name, "COGE F DCH") || // original bug in MOTEL_ENTRADA
!scumm_stricmp(name, "CHIQUITO_IZQ"))
return;
Game::unknownAnimateCharacterObject(name);
}
- virtual void missingSound(const String &fileName) override {
+ void missingSound(const String &fileName) override {
if (fileName == "CHAS" || fileName == "517")
return;
Game::missingSound(fileName);
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 5bbeefa21b0..3dc0e585bbd 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -297,7 +297,7 @@ struct SayTextTask final : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
bool isSoundStillPlaying;
TASK_BEGIN;
@@ -340,11 +340,11 @@ struct SayTextTask final : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->console().debugPrintf("SayText %s, %d\n", _character->name().c_str(), _dialogId);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
syncObjectAsString(s, _character);
s.syncAsSint32LE(_dialogId);
@@ -395,7 +395,7 @@ struct AnimateCharacterTask final : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
while (_character->_curAnimateObject != nullptr)
TASK_YIELD(1);
@@ -416,11 +416,11 @@ struct AnimateCharacterTask final : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->console().debugPrintf("AnimateCharacter %s, %s\n", _character->name().c_str(), _animateObject->name().c_str());
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
syncObjectAsString(s, _character);
syncObjectAsString(s, _animateObject);
@@ -455,7 +455,7 @@ struct LerpLodBiasTask final : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
_startTime = g_engine->getMillis();
_sourceLodBias = _character->lodBias();
@@ -468,7 +468,7 @@ struct LerpLodBiasTask final : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
uint32 remaining = g_engine->getMillis() - _startTime <= _durationMs
? _durationMs - (g_engine->getMillis() - _startTime)
: 0;
@@ -476,7 +476,7 @@ struct LerpLodBiasTask final : public Task {
_character->name().c_str(), _targetLodBias, remaining);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
syncObjectAsString(s, _character);
s.syncAsFloatLE(_sourceLodBias);
@@ -786,17 +786,17 @@ struct ArriveTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
return _character->isWalking()
? TaskReturn::yield()
: TaskReturn::finish(1);
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->getDebugger()->debugPrintf("Wait for %s to arrive", _character->name().c_str());
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
syncObjectAsString(s, _character);
}
@@ -1048,7 +1048,7 @@ struct DialogMenuTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
layoutLines();
while (true) {
@@ -1071,12 +1071,12 @@ struct DialogMenuTask : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->console().debugPrintf("DialogMenu for %s with %u lines\n",
_character->name().c_str(), _character->_dialogLines.size());
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
syncObjectAsString(s, _character);
s.syncAsUint32LE(_clickedLineI);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 6795d6ebf05..be14b9c99b2 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -144,7 +144,7 @@ struct AnimateTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
_object->toggle(true);
_graphic->start(false);
@@ -153,11 +153,11 @@ struct AnimateTask : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->getDebugger()->debugPrintf("Animate \"%s\" for %ums", _object->name().c_str(), _duration);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
s.syncAsUint32LE(_duration);
syncObjectAsString(s, _object);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 06d7a09dee3..6e379c5622b 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -222,14 +222,14 @@ struct CenterBottomTextTask : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
uint32 remaining = g_engine->getMillis() - _startTime <= _durationMs
? _durationMs - (g_engine->getMillis() - _startTime)
: 0;
g_engine->console().debugPrintf("CenterBottomText (%d) with %ums remaining\n", _dialogId, remaining);
}
- virtual void syncGame(Serializer &s) override {
+ void syncGame(Serializer &s) override {
Task::syncGame(s);
s.syncAsSint32LE(_dialogId);
s.syncAsUint32LE(_startTime);
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 91eebb10fa5..e1e88278278 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -79,7 +79,7 @@ public:
setMirrorWrap(false);
}
- virtual ~OpenGLTexture() override {
+ ~OpenGLTexture() override {
if (_handle != 0)
GL_CALL(glDeleteTextures(1, &_handle));
}
@@ -138,12 +138,12 @@ public:
}
}
- virtual ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override {
+ ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override {
assert(w >= 0 && h >= 0);
return ScopedPtr<ITexture>(new OpenGLTexture(w, h, withMipmaps));
}
- virtual void begin() override {
+ void begin() override {
GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
@@ -153,12 +153,12 @@ public:
_isFirstDrawCommand = true;
}
- virtual void end() override {
+ void end() override {
GL_CALL(glFlush());
g_system->updateScreen();
}
- virtual void setTexture(ITexture *texture) override {
+ void setTexture(ITexture *texture) override {
if (texture == _currentTexture)
return;
else if (texture == nullptr) {
@@ -178,7 +178,7 @@ public:
}
}
- virtual void setBlendMode(BlendMode blendMode) override {
+ void setBlendMode(BlendMode blendMode) override {
if (blendMode == _currentBlendMode)
return;
// first the blend func
@@ -246,7 +246,7 @@ public:
_currentBlendMode = blendMode;
}
- virtual void setLodBias(float lodBias) override {
+ void setLodBias(float lodBias) override {
if (abs(_currentLodBias - lodBias) < epsilon)
return;
GL_CALL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, lodBias));
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index cf47dac4ec5..242ce7c67d9 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -783,7 +783,7 @@ struct FadeTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
if (_permanentFadeAction == PermanentFadeAction::UnsetFaded)
g_engine->globalUI().isPermanentFaded() = false;
@@ -798,7 +798,7 @@ struct FadeTask : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
uint32 remaining = g_engine->getMillis() - _startTime <= _duration
? _duration - (g_engine->getMillis() - _startTime)
: 0;
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index a39763f6cfa..8520938da57 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -322,7 +322,7 @@ public:
int8 order
);
- virtual void draw() override;
+ void draw() override;
private:
bool _is3D;
@@ -344,7 +344,7 @@ public:
Math::Vector2d texOffset,
BlendMode blendMode);
- virtual void draw() override;
+ void draw() override;
private:
Animation *_animation;
@@ -368,7 +368,7 @@ public:
int8 order);
inline Common::Point size() const { return { (int16)_width, (int16)_height }; }
- virtual void draw() override;
+ void draw() override;
private:
static constexpr uint kMaxLines = 12;
@@ -399,7 +399,7 @@ class FadeDrawRequest : public IDrawRequest {
public:
FadeDrawRequest(FadeType type, float value, int8 order);
- virtual void draw() override;
+ void draw() override;
private:
FadeType _type;
@@ -416,7 +416,7 @@ class BorderDrawRequest : public IDrawRequest {
public:
BorderDrawRequest(Common::Rect rect, Color color);
- virtual void draw() override;
+ void draw() override;
private:
Common::Rect _rect;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 5b1f552eb27..e225c64d48c 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -86,13 +86,13 @@ class GraphicObject : public ObjectBase {
public:
static constexpr const char *kClassName = "CObjetoGrafico";
GraphicObject(Room *room, Common::ReadStream &stream);
- virtual ~GraphicObject() override = default;
+ ~GraphicObject() override = default;
- virtual void draw() override;
- virtual void loadResources() override;
- virtual void freeResources() override;
- virtual void syncGame(Common::Serializer &serializer) override;
- virtual Graphic *graphic() override;
+ void draw() override;
+ void loadResources() override;
+ void freeResources() override;
+ void syncGame(Common::Serializer &serializer) override;
+ Graphic *graphic() override;
virtual const char *typeName() const;
Task *animate(Process &process);
@@ -110,7 +110,7 @@ public:
static constexpr const char *kClassName = "CObjetoGraficoMuare";
SpecialEffectObject(Room *room, Common::ReadStream &stream);
- virtual void draw() override;
+ void draw() override;
virtual const char *typeName() const;
private:
@@ -122,15 +122,15 @@ private:
class ShapeObject : public ObjectBase {
public:
ShapeObject(Room *room, Common::ReadStream &stream);
- virtual ~ShapeObject() override = default;
+ ~ShapeObject() override = default;
inline int8 order() const { return _order; }
inline bool isNewlySelected() const { return _isNewlySelected; }
inline bool wasSelected() const { return _wasSelected; }
- virtual void update() override;
- virtual void syncGame(Common::Serializer &serializer) override;
- virtual Shape *shape() override;
+ void update() override;
+ void syncGame(Common::Serializer &serializer) override;
+ Shape *shape() override;
virtual CursorType cursorType() const;
virtual void onHoverStart();
virtual void onHoverEnd();
@@ -161,17 +161,17 @@ class MenuButton : public PhysicalObject {
public:
static constexpr const char *kClassName = "CBotonMenu";
MenuButton(Room *room, Common::ReadStream &stream);
- virtual ~MenuButton() override = default;
+ ~MenuButton() override = default;
inline int32 actionId() const { return _actionId; }
inline bool &isInteractable() { return _isInteractable; }
- virtual void draw() override;
- virtual void update() override;
- virtual void loadResources() override;
- virtual void freeResources() override;
- virtual void onHoverUpdate() override;
- virtual void onClick() override;
+ void draw() override;
+ void update() override;
+ void loadResources() override;
+ void freeResources() override;
+ void onHoverUpdate() override;
+ void onClick() override;
virtual void trigger();
virtual const char *typeName() const;
@@ -204,8 +204,8 @@ public:
static constexpr const char *kClassName = "CBotonMenuOpciones";
OptionsMenuButton(Room *room, Common::ReadStream &stream);
- virtual void update() override;
- virtual void trigger() override;
+ void update() override;
+ void trigger() override;
virtual const char *typeName() const;
};
@@ -214,8 +214,8 @@ public:
static constexpr const char *kClassName = "CBotonMenuPrincipal";
MainMenuButton(Room *room, Common::ReadStream &stream);
- virtual void update() override;
- virtual void trigger() override;
+ void update() override;
+ void trigger() override;
virtual const char *typeName() const;
};
@@ -252,17 +252,17 @@ class CheckBox : public PhysicalObject {
public:
static constexpr const char *kClassName = "CCheckBox";
CheckBox(Room *room, Common::ReadStream &stream);
- virtual ~CheckBox() override = default;
+ ~CheckBox() override = default;
inline bool &isChecked() { return _isChecked; }
inline int32 actionId() const { return _actionId; }
- virtual void draw() override;
- virtual void update() override;
- virtual void loadResources() override;
- virtual void freeResources() override;
- virtual void onHoverUpdate() override;
- virtual void onClick() override;
+ void draw() override;
+ void update() override;
+ void loadResources() override;
+ void freeResources() override;
+ void onHoverUpdate() override;
+ void onClick() override;
virtual void trigger();
virtual const char *typeName() const;
@@ -283,14 +283,14 @@ class SlideButton final : public ObjectBase {
public:
static constexpr const char *kClassName = "CSlideButton";
SlideButton(Room *room, Common::ReadStream &stream);
- virtual ~SlideButton() override = default;
+ ~SlideButton() override = default;
inline float &value() { return _value; }
- virtual void draw() override;
- virtual void update() override;
- virtual void loadResources() override;
- virtual void freeResources() override;
+ void draw() override;
+ void update() override;
+ void loadResources() override;
+ void freeResources() override;
virtual const char *typeName() const;
private:
@@ -328,7 +328,7 @@ class MessageBox final : public ObjectBase {
public:
static constexpr const char *kClassName = "CMessageBox";
MessageBox(Room *room, Common::ReadStream &stream);
- virtual ~MessageBox() override = default;
+ ~MessageBox() override = default;
virtual const char *typeName() const;
@@ -355,7 +355,7 @@ public:
Item(Room *room, Common::ReadStream &stream);
Item(const Item &other);
- virtual void draw() override;
+ void draw() override;
virtual const char *typeName() const;
void trigger();
};
@@ -380,12 +380,12 @@ class InteractableObject : public PhysicalObject, public ITriggerableObject {
public:
static constexpr const char *kClassName = "CObjetoTipico";
InteractableObject(Room *room, Common::ReadStream &stream);
- virtual ~InteractableObject() override = default;
+ ~InteractableObject() override = default;
- virtual void drawDebug() override;
- virtual void onClick() override;
- virtual void trigger(const char *action) override;
- virtual void toggle(bool isEnabled) override;
+ void drawDebug() override;
+ void onClick() override;
+ void trigger(const char *action) override;
+ void toggle(bool isEnabled) override;
virtual const char *typeName() const;
private:
@@ -402,8 +402,8 @@ public:
inline Direction characterDirection() const { return _characterDirection; }
virtual CursorType cursorType() const override;
- virtual void onClick() override;
- virtual void trigger(const char *action) override;
+ void onClick() override;
+ void trigger(const char *action) override;
virtual const char *typeName() const;
private:
@@ -416,17 +416,17 @@ class Character : public ShapeObject, public ITriggerableObject {
public:
static constexpr const char *kClassName = "CPersonaje";
Character(Room *room, Common::ReadStream &stream);
- virtual ~Character() override = default;
-
- virtual void update() override;
- virtual void draw() override;
- virtual void drawDebug() override;
- virtual void loadResources() override;
- virtual void freeResources() override;
- virtual void syncGame(Common::Serializer &serializer) override;
- virtual Graphic *graphic() override;
- virtual void onClick() override;
- virtual void trigger(const char *action) override;
+ ~Character() override = default;
+
+ void update() override;
+ void draw() override;
+ void drawDebug() override;
+ void loadResources() override;
+ void freeResources() override;
+ void syncGame(Common::Serializer &serializer) override;
+ Graphic *graphic() override;
+ void onClick() override;
+ void trigger(const char *action) override;
virtual const char *typeName() const;
Task *sayText(Process &process, int32 dialogId);
@@ -459,18 +459,18 @@ class WalkingCharacter : public Character {
public:
static constexpr const char *kClassName = "CPersonajeAnda";
WalkingCharacter(Room *room, Common::ReadStream &stream);
- virtual ~WalkingCharacter() override = default;
+ ~WalkingCharacter() override = default;
inline bool isWalking() const { return _isWalking; }
inline Common::Point position() const { return _currentPos; }
inline float stepSizeFactor() const { return _stepSizeFactor; }
- virtual void update() override;
- virtual void draw() override;
- virtual void drawDebug() override;
- virtual void loadResources() override;
- virtual void freeResources() override;
- virtual void syncGame(Common::Serializer &serializer) override;
+ void update() override;
+ void draw() override;
+ void drawDebug() override;
+ void loadResources() override;
+ void freeResources() override;
+ void syncGame(Common::Serializer &serializer) override;
virtual void walkTo(
Common::Point target,
Direction endDirection = Direction::Invalid,
@@ -526,7 +526,7 @@ class MainCharacter final : public WalkingCharacter {
public:
static constexpr const char *kClassName = "CPersonajePrincipal";
MainCharacter(Room *room, Common::ReadStream &stream);
- virtual ~MainCharacter() override;
+ ~MainCharacter() override;
inline MainCharacterKind kind() const { return _kind; }
inline ObjectBase *¤tlyUsing() { return _currentlyUsingObject; }
@@ -536,9 +536,9 @@ public:
inline FakeSemaphore &semaphore() { return _semaphore; }
bool isBusy() const;
- virtual void update() override;
- virtual void draw() override;
- virtual void syncGame(Common::Serializer &serializer) override;
+ void update() override;
+ void draw() override;
+ void syncGame(Common::Serializer &serializer) override;
virtual const char *typeName() const;
virtual void walkTo(
Common::Point target,
@@ -557,7 +557,7 @@ public:
void resetUsingObjectAndDialogMenu();
protected:
- virtual void onArrived() override;
+ void onArrived() override;
private:
friend class Inventory;
@@ -586,11 +586,11 @@ class FloorColor final : public ObjectBase {
public:
static constexpr const char *kClassName = "CSueloColor";
FloorColor(Room *room, Common::ReadStream &stream);
- virtual ~FloorColor() override = default;
+ ~FloorColor() override = default;
- virtual void update() override;
- virtual void drawDebug() override;
- virtual Shape *shape() override;
+ void update() override;
+ void drawDebug() override;
+ Shape *shape() override;
virtual const char *typeName() const;
private:
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 8d753f81aeb..e08ae07ec4b 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -92,8 +92,8 @@ public:
static constexpr const char *kClassName = "CHabitacionMenuOpciones";
OptionsMenu(World *world, Common::SeekableReadStream &stream);
- virtual bool updateInput() override;
- virtual void loadResources() override;
+ bool updateInput() override;
+ void loadResources() override;
void clearLastSelectedObject(); // to reset arm animation
inline SlideButton *¤tSlideButton() { return _currentSlideButton; }
@@ -120,9 +120,9 @@ class Inventory final : public Room {
public:
static constexpr const char *kClassName = "CInventario";
Inventory(World *world, Common::SeekableReadStream &stream);
- virtual ~Inventory() override;
+ ~Inventory() override;
- virtual bool updateInput() override;
+ bool updateInput() override;
void initItems();
void updateItemsByActiveCharacter();
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 3ba95f298fc..1a83a620e76 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -115,9 +115,9 @@ private:
struct DelayTask : public Task {
DelayTask(Process &process, uint32 millis);
DelayTask(Process &process, Common::Serializer &s);
- virtual TaskReturn run() override;
- virtual void debugPrint() override;
- virtual void syncGame(Common::Serializer &s) override;
+ TaskReturn run() override;
+ void debugPrint() override;
+ void syncGame(Common::Serializer &s) override;
virtual const char *taskName() const override;
private:
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 9cea4464da3..e376644a7c6 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -142,7 +142,7 @@ struct ScriptTimerTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
TASK_BEGIN;
{
uint32 timeSinceTimer = g_engine->script()._scriptTimer == 0 ? 0
@@ -158,7 +158,7 @@ struct ScriptTimerTask : public Task {
TASK_END;
}
- virtual void debugPrint() override {
+ void debugPrint() override {
g_engine->getDebugger()->debugPrintf("Check input timer for %dsecs", _durationSec);
}
@@ -232,7 +232,7 @@ struct ScriptTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() override {
+ TaskReturn run() override {
if (_isFirstExecution || _returnsFromKernelCall)
setCharacterVariables();
if (_returnsFromKernelCall) {
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 2ebe71ad4b8..02380d7a5d0 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -86,8 +86,8 @@ private:
struct PlaySoundTask final : public Task {
PlaySoundTask(Process &process, SoundHandle soundHandle);
PlaySoundTask(Process &process, Common::Serializer &s);
- virtual TaskReturn run() override;
- virtual void debugPrint() override;
+ TaskReturn run() override;
+ void debugPrint() override;
virtual const char *taskName() const override;
private:
SoundHandle _soundHandle;
@@ -96,8 +96,8 @@ private:
struct WaitForMusicTask final : public Task {
WaitForMusicTask(Process &process);
WaitForMusicTask(Process &process, Common::Serializer &s);
- virtual TaskReturn run() override;
- virtual void debugPrint() override;
+ TaskReturn run() override;
+ void debugPrint() override;
virtual const char *taskName() const override;
private:
FakeLock _lock;
Commit: e31e933cb365114d7ffbbac7d25d30a722297c4e
https://github.com/scummvm/scummvm/commit/e31e933cb365114d7ffbbac7d25d30a722297c4e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Fix various bugs related to saving/loading
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/camera.cpp
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/detection.cpp
engines/alcachofa/detection.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/metaengine.cpp
engines/alcachofa/metaengine.h
engines/alcachofa/player.cpp
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index c9062244fd9..b1562fc6bb9 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -23,6 +23,7 @@
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
+#include "common/savefile.h"
#include "common/system.h"
#include "engines/util.h"
#include "graphics/paletteman.h"
@@ -79,8 +80,11 @@ Common::Error AlcachofaEngine::run() {
_menu.reset(new Menu());
setMillis(0);
- _script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
- _scheduler.run();
+ if (!tryLoadFromLauncher()) {
+ _script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
+ _scheduler.run();
+ // we run once to set the initial room, otherwise we could run into currentRoom == nullptr
+ }
Common::Event e;
Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
@@ -236,7 +240,9 @@ void AlcachofaEngine::pauseEngineIntern(bool pause) {
Common::Error AlcachofaEngine::syncGame(Serializer &s) {
s.syncVersion((Serializer::Version)SaveVersion::Initial);
- uint32 millis = getMillis();
+ uint32 millis = menu().isOpen()
+ ? menu().millisBeforeMenu()
+ : getMillis();
s.syncAsUint32LE(millis);
if (s.isLoading())
setMillis(millis);
@@ -247,6 +253,7 @@ Common::Error AlcachofaEngine::syncGame(Serializer &s) {
* can assert that the semaphores are released on loading.
* 2. The player should come late as it changes the room
* 3. With the room current, the tasks can now better find the referenced objects
+ * 4. Redundant: The world has to be synced before the tasks to reset the semaphores to 0
*/
scheduler().prepareSyncGame(s);
@@ -258,6 +265,7 @@ Common::Error AlcachofaEngine::syncGame(Serializer &s) {
scheduler().syncGame(s);
if (s.isLoading()) {
+ menu().resetAfterLoad();
sounds().stopAll();
sounds().setMusicToRoom(player().currentRoom()->musicID());
}
@@ -265,6 +273,19 @@ Common::Error AlcachofaEngine::syncGame(Serializer &s) {
return Common::kNoError;
}
+bool AlcachofaEngine::tryLoadFromLauncher() {
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (!ConfMan.hasKey("save_slot") || saveSlot < 0)
+ return false;
+ auto *saveFileMgr = g_system->getSavefileManager();
+ auto *saveFile = saveFileMgr->openForLoading(getSaveStateName(saveSlot));
+ if (saveFile == nullptr)
+ return false;
+ bool result = loadGameStream(saveFile).getCode() == kNoError;
+ delete saveFile;
+ return result;
+}
+
Config::Config() {
loadFromScummVM();
}
@@ -284,6 +305,7 @@ void Config::saveToScummVM() {
ConfMan.setInt("music_volume", _musicVolume);
ConfMan.setInt("speech_volume", _speechVolume);
ConfMan.setInt("sfx_volume", _speechVolume);
+ ConfMan.flushToDisk();
// ^ a bit unfortunate, that means if you change in-game it overrides.
// if you set it in ScummVMs dialog it sticks
}
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 2c30ae6496d..02ab4373a9c 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -138,9 +138,11 @@ public:
};
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
+ // TODO: Implement
return true;
}
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
+ // TODO: Implement
return true;
}
@@ -155,6 +157,8 @@ public:
}
private:
+ bool tryLoadFromLauncher();
+
Console *_console = new Console();
Common::ScopedPtr<IDebugHandler> _debugHandler;
Common::ScopedPtr<IRenderer> _renderer;
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 3c77a4d99f7..1b89a38c45c 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -377,7 +377,7 @@ protected:
_camera._cur._scale = _fromScale + _deltaScale * t;
}
- float _fromScale, _deltaScale;
+ float _fromScale = 0, _deltaScale = 0;
};
DECLARE_TASK(CamLerpScaleTask);
@@ -418,8 +418,8 @@ protected:
}
Vector3d _fromPos, _deltaPos;
- float _fromScale, _deltaScale;
- EasingType _moveEasingType, _scaleEasingType;
+ float _fromScale = 0, _deltaScale = 0;
+ EasingType _moveEasingType = {}, _scaleEasingType = {};
};
DECLARE_TASK(CamLerpPosScaleTask);
@@ -447,7 +447,7 @@ protected:
_camera._cur._rotation = Angle(_fromRotation + _deltaRotation * t);
}
- float _fromRotation, _deltaRotation;
+ float _fromRotation = 0, _deltaRotation = 0;
};
DECLARE_TASK(CamLerpRotationTask);
@@ -579,9 +579,9 @@ struct CamSetInactiveAttributeTask final : public Task {
private:
Camera &_camera;
- Attribute _attribute;
- float _value;
- int32 _delay;
+ Attribute _attribute = {};
+ float _value = 0;
+ int32 _delay = 0;
};
DECLARE_TASK(CamSetInactiveAttributeTask);
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 57be9a3d27e..1b1e5564466 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -20,6 +20,7 @@
*/
#include "common.h"
+#include "detection.h"
using namespace Common;
using namespace Math;
@@ -36,7 +37,9 @@ float ease(float t, EasingType type) {
}
}
-FakeSemaphore::FakeSemaphore(uint initialCount) : _counter(initialCount) {}
+FakeSemaphore::FakeSemaphore(const char *name, uint initialCount)
+ : _name(name)
+ , _counter(initialCount) {}
FakeSemaphore::~FakeSemaphore() {
assert(_counter == 0);
@@ -46,21 +49,23 @@ void FakeSemaphore::sync(Serializer &s, FakeSemaphore &semaphore) {
// if we are still holding locks during loading these locks will
// try to decrease the counter which will fail, let's find this out already here
assert(s.isSaving() || semaphore.isReleased());
+ (void)(s, semaphore);
- uint semaphoreCounter = semaphore.counter();
- s.syncAsSint32LE(semaphoreCounter);
- semaphore = FakeSemaphore(semaphoreCounter);
+ // We should not actually serialize the counter, just make sure it is empty
+ // When the locks are loaded, they will increase the counter themselves
}
FakeLock::FakeLock() : _semaphore(nullptr) {}
FakeLock::FakeLock(FakeSemaphore &semaphore) : _semaphore(&semaphore) {
_semaphore->_counter++;
+ debugC(kDebugSemaphores, "Lock ctor %s to %u", _semaphore->_name, _semaphore->_counter);
}
FakeLock::FakeLock(const FakeLock &other) : _semaphore(other._semaphore) {
assert(_semaphore != nullptr);
_semaphore->_counter++;
+ debugC(kDebugSemaphores, "Lock copy %s to %u", _semaphore->_name, _semaphore->_counter);
}
FakeLock::FakeLock(FakeLock &&other) noexcept : _semaphore(other._semaphore) {
@@ -80,6 +85,7 @@ void FakeLock::release() {
if (_semaphore == nullptr)
return;
assert(_semaphore->_counter > 0);
+ debugC(kDebugSemaphores, "Lock dtor %s to %u", _semaphore->_name, _semaphore->_counter - 1);
_semaphore->_counter--;
_semaphore = nullptr;
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 7631bf81838..0394653f657 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -83,7 +83,7 @@ static constexpr const Color kDebugLightBlue = { 80, 80, 255, 190 };
* It is used as a safer option for a simple "isBusy" counter
*/
struct FakeSemaphore {
- FakeSemaphore(uint initialCount = 0);
+ FakeSemaphore(const char *name, uint initialCount = 0);
~FakeSemaphore();
inline bool isReleased() const { return _counter == 0; }
@@ -92,6 +92,7 @@ struct FakeSemaphore {
static void sync(Common::Serializer &s, FakeSemaphore &semaphore);
private:
friend struct FakeLock;
+ const char *const _name;
uint _counter = 0;
};
diff --git a/engines/alcachofa/detection.cpp b/engines/alcachofa/detection.cpp
index 23b2d7543d6..f8e59c3ddf8 100644
--- a/engines/alcachofa/detection.cpp
+++ b/engines/alcachofa/detection.cpp
@@ -34,6 +34,7 @@ const DebugChannelDef AlcachofaMetaEngineDetection::debugFlagList[] = {
{ Alcachofa::kDebugScript, "Script", "Enable debug script dump" },
{ Alcachofa::kDebugGameplay, "Gameplay", "Gameplay-related tracing" },
{ Alcachofa::kDebugSounds, "Sounds", "Sound- and Music-related tracing" },
+ { Alcachofa::kDebugSemaphores, "Semaphores", "Tracing operations on semaphores" },
DEBUG_CHANNEL_END
};
diff --git a/engines/alcachofa/detection.h b/engines/alcachofa/detection.h
index 8e37d4b99d3..982cc618159 100644
--- a/engines/alcachofa/detection.h
+++ b/engines/alcachofa/detection.h
@@ -31,6 +31,7 @@ enum AlcachofaDebugChannels {
kDebugScript,
kDebugGameplay,
kDebugSounds,
+ kDebugSemaphores
};
extern const PlainGameDescriptor alcachofaGames[];
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 3dc0e585bbd..9a4f47128eb 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -353,8 +353,8 @@ struct SayTextTask final : public Task {
virtual const char *taskName() const override;
private:
- Character *_character;
- int32 _dialogId;
+ Character *_character = nullptr;
+ int32 _dialogId = 0;
SoundHandle _soundHandle = {};
};
DECLARE_TASK(SayTextTask);
@@ -431,9 +431,9 @@ struct AnimateCharacterTask final : public Task {
virtual const char *taskName() const override;
private:
- Character *_character;
- ObjectBase *_animateObject;
- Graphic *_graphic;
+ Character *_character = nullptr;
+ ObjectBase *_animateObject = nullptr;
+ Graphic *_graphic = nullptr;
};
DECLARE_TASK(AnimateCharacterTask);
@@ -488,9 +488,9 @@ struct LerpLodBiasTask final : public Task {
virtual const char *taskName() const override;
private:
- Character *_character;
- float _sourceLodBias = 0, _targetLodBias;
- uint32 _startTime = 0, _durationMs;
+ Character *_character = nullptr;
+ float _sourceLodBias = 0, _targetLodBias = 0;
+ uint32 _startTime = 0, _durationMs = 0;
};
DECLARE_TASK(LerpLodBiasTask);
@@ -802,7 +802,7 @@ struct ArriveTask : public Task {
virtual const char *taskName() const override;
private:
- const WalkingCharacter *_character;
+ const WalkingCharacter *_character = nullptr;
};
DECLARE_TASK(ArriveTask);
@@ -813,7 +813,8 @@ Task *WalkingCharacter::waitForArrival(Process &process) {
const char *MainCharacter::typeName() const { return "MainCharacter"; }
MainCharacter::MainCharacter(Room *room, ReadStream &stream)
- : WalkingCharacter(room, stream) {
+ : WalkingCharacter(room, stream)
+ , _semaphore(name().firstChar() == 'M' ? "mortadelo" : "filemon") {
stream.readByte(); // unused byte
_order = 100;
@@ -1125,7 +1126,7 @@ private:
}
Input &_input;
- MainCharacter *_character;
+ MainCharacter *_character = nullptr;
uint _clickedLineI = UINT_MAX;
};
DECLARE_TASK(DialogMenuTask);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index be14b9c99b2..ca99e56bb3a 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -169,9 +169,9 @@ struct AnimateTask : public Task {
virtual const char *taskName() const override;
private:
- GraphicObject *_object;
- Graphic *_graphic;
- uint32 _duration;
+ GraphicObject *_object = nullptr;
+ Graphic *_graphic = nullptr;
+ uint32 _duration = 0;
};
DECLARE_TASK(AnimateTask);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 6e379c5622b..0d842c56986 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -239,8 +239,8 @@ struct CenterBottomTextTask : public Task {
virtual const char *taskName() const override;
private:
- int32 _dialogId;
- uint32 _startTime = 0, _durationMs;
+ int32 _dialogId = 0;
+ uint32 _startTime = 0, _durationMs = 0;
};
DECLARE_TASK(CenterBottomTextTask);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 242ce7c67d9..86332477c09 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -824,12 +824,12 @@ private:
g_engine->drawQueue().add<FadeDrawRequest>(_fadeType, _from + (_to - _from) * ease(t, _easingType), _order);
}
- FadeType _fadeType;
- float _from, _to;
- uint32 _startTime = 0, _duration;
- EasingType _easingType;
- int8 _order;
- PermanentFadeAction _permanentFadeAction;
+ FadeType _fadeType = {};
+ float _from = 0, _to = 0;
+ uint32 _startTime = 0, _duration = 0;
+ EasingType _easingType = {};
+ int8 _order = 0;
+ PermanentFadeAction _permanentFadeAction = {};
};
DECLARE_TASK(FadeTask);
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index b0abf19233c..61369a3b747 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -26,6 +26,12 @@
namespace Alcachofa {
+void Menu::resetAfterLoad() {
+ _isOpen = false;
+ _openAtNextFrame = false;
+ _previousRoom = nullptr;
+}
+
void Menu::updateOpeningMenu() {
if (!_openAtNextFrame) {
_openAtNextFrame =
@@ -36,7 +42,7 @@ void Menu::updateOpeningMenu() {
_openAtNextFrame = false;
g_engine->sounds().pauseAll(true);
- _timeBeforeMenu = g_engine->getMillis();
+ _millisBeforeMenu = g_engine->getMillis();
_previousRoom = g_engine->player().currentRoom();
_isOpen = true;
// TODO: Render thumbnail
@@ -60,7 +66,7 @@ void Menu::continueGame() {
g_engine->sounds().pauseAll(false);
g_engine->camera().restore(1);
g_engine->scheduler().restoreContext();
- g_engine->setMillis(_timeBeforeMenu);
+ g_engine->setMillis(_millisBeforeMenu);
}
void Menu::triggerMainMenuAction(MainMenuAction action) {
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 712a74ff219..18e234e56fb 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -59,7 +59,10 @@ enum class OptionsMenuValue : int32 {
class Menu {
public:
inline bool isOpen() const { return _isOpen; }
+ inline uint32 millisBeforeMenu() const { return _millisBeforeMenu; }
+ inline Room *previousRoom() { return _previousRoom; }
+ void resetAfterLoad();
void updateOpeningMenu();
void triggerMainMenuAction(MainMenuAction action);
@@ -75,7 +78,7 @@ private:
bool
_isOpen = false,
_openAtNextFrame = false;
- uint32 _timeBeforeMenu = 0;
+ uint32 _millisBeforeMenu = 0;
Room *_previousRoom = nullptr;
};
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 336f589ea9f..15fbbfa5cb7 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -28,6 +28,7 @@
#include "alcachofa/alcachofa.h"
using namespace Common;
+using namespace Graphics;
using namespace Alcachofa;
namespace Alcachofa {
@@ -104,6 +105,11 @@ KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
return Keymap::arrayOf(keymap);
}
+void AlcachofaMetaEngine::getSavegameThumbnail(Surface &surf) {
+ // TODO: Implement
+ surf.create(160, 120, PixelFormat::createFormatRGBA32());
+}
+
#if PLUGIN_ENABLED_DYNAMIC(ALCACHOFA)
REGISTER_PLUGIN_DYNAMIC(ALCACHOFA, PLUGIN_TYPE_ENGINE, AlcachofaMetaEngine);
#else
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index 58d9f56607f..5edef077507 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -49,6 +49,8 @@ public:
Common::KeymapArray initKeymaps(const char *target) const override;
+ void getSavegameThumbnail(Graphics::Surface &thumb) override;
+
};
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index a9fecdb049a..2dc95a755da 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -29,7 +29,8 @@ using namespace Common;
namespace Alcachofa {
Player::Player()
- : _activeCharacter(&g_engine->world().mortadelo()) {
+ : _activeCharacter(&g_engine->world().mortadelo())
+ , _semaphore("player") {
const auto &cursorPath = g_engine->world().getGlobalAnimationName(GlobalAnimationKind::Cursor);
_cursorAnimation.reset(new Animation(cursorPath));
_cursorAnimation->load();
@@ -153,7 +154,7 @@ MainCharacter *Player::inactiveCharacter() const {
}
FakeSemaphore &Player::semaphoreFor(MainCharacterKind kind) {
- static FakeSemaphore dummySemaphore;
+ static FakeSemaphore dummySemaphore("dummy");
switch (kind) {
case MainCharacterKind::None: return _semaphore;
case MainCharacterKind::Mortadelo: return g_engine->world().mortadelo().semaphore();
@@ -278,11 +279,11 @@ private:
}
FakeLock _lock, _musicLock;
- const Door *_sourceDoor;
- const InteractableObject *_targetObject;
- Direction _targetDirection;
- Room *_targetRoom;
- MainCharacter *_character;
+ const Door *_sourceDoor = nullptr;
+ const InteractableObject *_targetObject = nullptr;
+ Direction _targetDirection = {};
+ Room *_targetRoom = nullptr;
+ MainCharacter *_character = nullptr;
Player &_player;
};
DECLARE_TASK(DoorTask);
@@ -346,27 +347,22 @@ void Player::syncGame(Serializer &s) {
}
FakeSemaphore::sync(s, _semaphore);
-
+
String roomName;
- if (_roomBeforeInventory != nullptr)
- roomName = _roomBeforeInventory->name();
- s.syncString(roomName);
- if (s.isLoading()) {
- if (roomName.empty())
- _roomBeforeInventory = nullptr;
- else {
- _roomBeforeInventory = g_engine->world().getRoomByName(roomName.c_str());
- scumm_assert(_roomBeforeInventory != nullptr);
- }
+ if (s.isSaving()) {
+ 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
+ : currentRoom()->name(); // save from ScumnmVM global menu or autosave in normal gameplay
}
-
- roomName = currentRoom()->name();
s.syncString(roomName);
if (s.isLoading()) {
_selectedObject = nullptr;
_pressedObject = nullptr;
_heldItem = nullptr;
_nextLastDialogCharacter = 0;
+ _isGameLoaded = true;
+ _roomBeforeInventory = nullptr;
fill(_lastDialogCharacters, _lastDialogCharacters + kMaxLastDialogCharacters, nullptr);
changeRoom(roomName, true);
}
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 31ab38b8e1a..b9feb436154 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -23,6 +23,7 @@
#include "common/system.h"
#include "alcachofa.h"
+#include "menu.h"
using namespace Common;
@@ -59,7 +60,7 @@ void Task::syncGame(Serializer &s) {
s.syncAsUint32LE(_stage);
}
-void Task::syncObjectAsString(Serializer &s, ObjectBase *&object, bool optional) {
+void Task::syncObjectAsString(Serializer &s, ObjectBase *&object, bool optional) const {
String objectName, roomName;
if (object != nullptr) {
roomName = object->room()->name();
@@ -71,7 +72,9 @@ void Task::syncObjectAsString(Serializer &s, ObjectBase *&object, bool optional)
return;
Room *room = g_engine->world().getRoomByName(roomName.c_str());
object = room == nullptr ? nullptr : room->getObjectByName(objectName.c_str());
- if ((object == nullptr && !optional) || !roomName.empty() || !objectName.empty())
+ if (object == nullptr) // main characters are not linked by the room they are in
+ object = g_engine->world().globalRoom().getObjectByName(objectName.c_str());
+ if (object == nullptr && !optional)
error("Invalid object name \"%s\" in room \"%s\" in savestate for task %s",
objectName.c_str(), roomName.c_str(), taskName());
}
@@ -193,8 +196,12 @@ void Process::syncGame(Serializer &s) {
_tasks.push(readTask(*this, s));
}
else {
- for (uint i = 0; i < count; i++)
+ String taskName;
+ for (uint i = 0; i < count; i++) {
+ taskName = _tasks[i]->taskName();
+ s.syncString(taskName);
_tasks[i]->syncGame(s);
+ }
}
}
@@ -336,19 +343,23 @@ void Scheduler::syncGame(Serializer &s) {
assert(_currentProcessI == UINT_MAX); // let's not sync during ::run
assert(s.isSaving() || _backupProcesses.empty());
+ Common::Array<Process *> *processes = s.isSaving() && g_engine->menu().isOpen()
+ ? &_backupProcesses
+ : &processesToRunNext();
+
// we only sync the backupProcesses as these are the ones pertaining to the gameplay
// the other arrays would be used for the menu
s.syncAsUint32LE(_nextPid);
- uint32 count = _backupProcesses.size();
+ uint32 count = processes->size();
s.syncAsUint32LE(count);
if (s.isLoading()) {
- _backupProcesses.reserve(count);
+ processes->reserve(count);
for (uint32 i = 0; i < count; i++)
- _backupProcesses.push_back(new Process(s));
+ processes->push_back(new Process(s));
}
else {
- for (Process *process : _backupProcesses)
+ for (Process *process : *processes)
process->syncGame(s);
}
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 1a83a620e76..bb8fc261bb5 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -94,11 +94,11 @@ struct Task {
protected:
Task *delay(uint32 millis);
- void syncObjectAsString(Common::Serializer &s, ObjectBase *&object, bool optional = false);
+ void syncObjectAsString(Common::Serializer &s, ObjectBase *&object, bool optional = false) const;
template<class TObject>
- void syncObjectAsString(Common::Serializer &s, TObject *&object, bool optional = false) {
+ void syncObjectAsString(Common::Serializer &s, TObject *&object, bool optional = false) const {
// We could add is_const and therefore true_type, false_type, integral_constant
- // or we could just use const_cast and promise that we won't modify
+ // or we could just use const_cast and promise that we won't modify the object itself
ObjectBase *base = const_cast<Common::remove_const_t<TObject> *>(object);
syncObjectAsString(s, base, optional);
object = dynamic_cast<TObject*>(base);
@@ -121,7 +121,7 @@ struct DelayTask : public Task {
virtual const char *taskName() const override;
private:
- uint32 _endTime;
+ uint32 _endTime = 0;
};
#define DECLARE_TASK(TaskName) \
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index e376644a7c6..2deddae1472 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -171,7 +171,7 @@ struct ScriptTimerTask : public Task {
virtual const char *taskName() const override;
private:
- int32 _durationSec;
+ int32 _durationSec = 0;
int32 _result = 1;
};
DECLARE_TASK(ScriptTimerTask);
@@ -392,7 +392,7 @@ struct ScriptTask : public Task {
bool hasLock = !_lock.isReleased();
s.syncAsByte(hasLock);
- if (hasLock)
+ if (s.isLoading() && hasLock)
_lock = FakeLock(g_engine->player().semaphoreFor(process().character()));
}
@@ -945,7 +945,7 @@ private:
Script &_script;
Stack<StackEntry> _stack;
String _name;
- uint32 _pc;
+ uint32 _pc = 0;
bool _returnsFromKernelCall = false;
bool _isFirstExecution = true;
FakeLock _lock;
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 6d719b44e16..f3451db1ccf 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -41,7 +41,9 @@ void Sounds::Playback::fadeOut(uint32 duration) {
_fadeDuration = MAX<uint32>(duration, 1);
}
-Sounds::Sounds() : _mixer(g_system->getMixer()) {
+Sounds::Sounds()
+ : _mixer(g_system->getMixer())
+ , _musicSemaphore("music") {
assert(_mixer != nullptr);
}
Commit: 6a49931fed2839c4b3812fe26f2bc8e64d41bcd8
https://github.com/scummvm/scummvm/commit/6a49931fed2839c4b3812fe26f2bc8e64d41bcd8
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Implement canLoadGameStateCurrently
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.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 b1562fc6bb9..53ece3b3834 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -52,7 +52,8 @@ AlcachofaEngine *g_engine;
AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc)
: Engine(syst)
, _gameDescription(gameDesc)
- , _randomSource("Alcachofa") {
+ , _randomSource("Alcachofa")
+ , _eventLoopSemaphore("engine") {
g_engine = this;
}
@@ -118,6 +119,7 @@ Common::Error AlcachofaEngine::run() {
}
void AlcachofaEngine::playVideo(int32 videoId) {
+ FakeLock lock(_eventLoopSemaphore);
Video::MPEGPSDecoder decoder;
if (!decoder.loadFile(Common::Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1))))
error("Could not find video %d", videoId);
@@ -156,6 +158,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
void AlcachofaEngine::fadeExit() {
constexpr uint kFadeOutDuration = 1000;
+ FakeLock lock(_eventLoopSemaphore);
Event e;
Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
uint32 startTime = g_system->getMillis();
@@ -237,6 +240,14 @@ void AlcachofaEngine::pauseEngineIntern(bool pause) {
setMillis(_timeBeforePause);
}
+bool AlcachofaEngine::canLoadGameStateCurrently(U32String *msg) {
+ if (!_eventLoopSemaphore.isReleased())
+ return false;
+ return
+ (menu().isOpen() && menu().interactionSemaphore().isReleased()) ||
+ player().isAllowedToOpenMenu();
+}
+
Common::Error AlcachofaEngine::syncGame(Serializer &s) {
s.syncVersion((Serializer::Version)SaveVersion::Initial);
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 02ab4373a9c..4508dba9ae3 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -137,13 +137,9 @@ public:
(f == kSupportsReturnToLauncher);
};
- bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
- // TODO: Implement
- return true;
- }
+ bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
- // TODO: Implement
- return true;
+ return canLoadGameStateCurrently(msg);
}
Common::Error syncGame(Common::Serializer &s);
@@ -175,6 +171,7 @@ private:
Scheduler _scheduler;
Config _config;
+ FakeSemaphore _eventLoopSemaphore; // for special states like playVideo and fadeExit
uint32 _timeNegOffset = 0, _timePosOffset = 0;
uint32 _timeBeforePause = 0;
};
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 61369a3b747..512ced75f15 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -26,6 +26,8 @@
namespace Alcachofa {
+Menu::Menu() : _interactionSemaphore("menu") {}
+
void Menu::resetAfterLoad() {
_isOpen = false;
_openAtNextFrame = false;
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 18e234e56fb..79fe44a8342 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -58,9 +58,12 @@ enum class OptionsMenuValue : int32 {
class Menu {
public:
+ 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();
@@ -80,6 +83,7 @@ private:
_openAtNextFrame = false;
uint32 _millisBeforeMenu = 0;
Room *_previousRoom = nullptr;
+ FakeSemaphore _interactionSemaphore; // to prevent ScummVM loading during button clicks
};
}
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index e225c64d48c..a0c133eb000 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -186,6 +186,7 @@ private:
_graphicHovered,
_graphicClicked,
_graphicDisabled;
+ FakeLock _interactionLock;
};
// some of the UI elements are only used for the multiplayer menus
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 482312fb9ef..dd310c81592 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -68,6 +68,7 @@ void MenuButton::update() {
return;
}
+ _interactionLock.release();
_triggerNextFrame = false;
_isClicked = false;
trigger();
@@ -90,7 +91,8 @@ void MenuButton::freeResources() {
void MenuButton::onHoverUpdate() {}
void MenuButton::onClick() {
- if (_isInteractable) {
+ if (_isInteractable && _interactionLock.isReleased()) {
+ _interactionLock = g_engine->menu().interactionSemaphore();
_isClicked = true;
_triggerNextFrame = false;
_graphicClicked.start(false);
Commit: 1650b7610c70bbf6af51eb73f10300d425349e59
https://github.com/scummvm/scummvm/commit/1650b7610c70bbf6af51eb73f10300d425349e59
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Add loading from in-game menu
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/input.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/metaengine.cpp
engines/alcachofa/metaengine.h
engines/alcachofa/scheduler.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 53ece3b3834..fcee80cff6f 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -31,6 +31,7 @@
#include "video/mpegps_decoder.h"
#include "alcachofa.h"
+#include "metaengine.h"
#include "console.h"
#include "detection.h"
#include "player.h"
@@ -94,6 +95,9 @@ Common::Error AlcachofaEngine::run() {
while (g_system->getEventManager()->pollEvent(e)) {
if (_input.handleEvent(e))
continue;
+ if (e.type == EVENT_CUSTOM_ENGINE_ACTION_START &&
+ e.customType == (CustomEventType)EventAction::LoadFromMenu)
+ menu().triggerLoad();
}
_sounds.update();
@@ -248,6 +252,10 @@ bool AlcachofaEngine::canLoadGameStateCurrently(U32String *msg) {
player().isAllowedToOpenMenu();
}
+Common::String AlcachofaEngine::getSaveStatePattern() {
+ return getMetaEngine()->getSavegameFilePattern(_targetName.c_str());
+}
+
Common::Error AlcachofaEngine::syncGame(Serializer &s) {
s.syncVersion((Serializer::Version)SaveVersion::Initial);
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 4508dba9ae3..975de0b50ce 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -142,6 +142,7 @@ public:
return canLoadGameStateCurrently(msg);
}
+ Common::String getSaveStatePattern();
Common::Error syncGame(Common::Serializer &s);
Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
Common::Serializer s(nullptr, stream);
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index 7d92310d54d..bfa2391c360 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -70,8 +70,8 @@ bool Input::handleEvent(const Common::Event &event) {
updateMousePos3D();
return true;
case EVENT_CUSTOM_ENGINE_ACTION_START:
- switch ((InputAction)event.customType) {
- case InputAction::Menu:
+ switch ((EventAction)event.customType) {
+ case EventAction::InputMenu:
_wasMenuKeyPressed = true;
return true;
}
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 512ced75f15..10f0ab1a8e9 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -20,13 +20,18 @@
*/
#include "alcachofa.h"
+#include "metaengine.h"
#include "menu.h"
#include "player.h"
#include "script.h"
+using namespace Common;
+
namespace Alcachofa {
-Menu::Menu() : _interactionSemaphore("menu") {}
+Menu::Menu()
+ : _interactionSemaphore("menu")
+ , _saveFileMgr(g_system->getSavefileManager()) {}
void Menu::resetAfterLoad() {
_isOpen = false;
@@ -49,15 +54,30 @@ void Menu::updateOpeningMenu() {
_isOpen = true;
// TODO: Render thumbnail
g_engine->player().changeRoom("MENUPRINCIPAL", true);
- // TODO: Check original read lastSaveFileFileId and read options.cfg, we do not need that right?
+ _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();
+ updateSelectedSavefile();
g_engine->player().heldItem() = nullptr;
g_engine->scheduler().backupContext();
g_engine->camera().backup(1);
g_engine->camera().setPosition(Math::Vector3d(
g_system->getWidth() / 2.0f, g_system->getHeight() / 2.0f, 0.0f));
+}
+
+void Menu::updateSelectedSavefile() {
+ auto getButton = [] (const char *name) {
+ MenuButton *button = dynamic_cast<MenuButton *>(g_engine->player().currentRoom()->getObjectByName(name));
+ scumm_assert(button != nullptr);
+ return button;
+ };
- // TODO: Load thumbnail into capture graphic object
+ getButton("CARGAR")->isInteractable() = _selectedSavefileI < _savefiles.size();
+ getButton("ANTERIOR")->toggle(_selectedSavefileI > 0);
+ getButton("SIGUIENTE")->toggle(_selectedSavefileI < _savefiles.size());
+
+ // TODO: Update thumbnail animation
}
void Menu::continueGame() {
@@ -79,9 +99,14 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
case MainMenuAction::Save:
warning("STUB: MainMenuAction Save");
break;
- case MainMenuAction::Load:
- warning("STUB: MainMenuAction Load");
- break;
+ case MainMenuAction::Load: {
+ // we are in some update loop, let's load next frame upon event handling
+ // that should be safer
+ Event ev;
+ ev.type = EVENT_CUSTOM_ENGINE_ACTION_START;
+ ev.customType = (CustomEventType)EventAction::LoadFromMenu;
+ g_system->getEventManager()->pushEvent(ev);
+ }break;
case MainMenuAction::InternetMenu:
g_system->messageBox(LogMessageType::kWarning, "Multiplayer is not implemented in this ScummVM version.");
break;
@@ -94,10 +119,16 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
g_engine->fadeExit();
break;
case MainMenuAction::NextSave:
- warning("STUB: MainMenuAction NextSave");
+ if (_selectedSavefileI < _savefiles.size()) {
+ _selectedSavefileI++;
+ updateSelectedSavefile();
+ }
break;
case MainMenuAction::PrevSave:
- warning("STUB: MainMenuAction PrevSave");
+ if (_selectedSavefileI > 0) {
+ _selectedSavefileI--;
+ updateSelectedSavefile();
+ }
break;
case MainMenuAction::NewGame:
// this action might be unused just like the only room it would appear: MENUPRINCIPALINICIO
@@ -110,6 +141,12 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
}
}
+void Menu::triggerLoad() {
+ auto *savefile = _saveFileMgr->openForLoading(_savefiles[_selectedSavefileI]);
+ g_engine->loadGameStream(savefile);
+ delete savefile;
+}
+
void Menu::openOptionsMenu() {
setOptionsState();
g_engine->player().changeRoom("MENUOPCIONES", true);
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 79fe44a8342..7665e2a6a56 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -23,6 +23,7 @@
#define MENU_H
#include "common/scummsys.h"
+#include "common/savefile.h"
namespace Alcachofa {
@@ -68,12 +69,14 @@ public:
void resetAfterLoad();
void updateOpeningMenu();
void triggerMainMenuAction(MainMenuAction action);
+ void triggerLoad();
void openOptionsMenu();
void triggerOptionsAction(OptionsMenuAction action);
void triggerOptionsValue(OptionsMenuValue valueId, float value);
private:
+ void updateSelectedSavefile();
void continueGame();
void continueMainMenu();
void setOptionsState();
@@ -81,9 +84,13 @@ private:
bool
_isOpen = false,
_openAtNextFrame = false;
- uint32 _millisBeforeMenu = 0;
+ uint32
+ _millisBeforeMenu = 0,
+ _selectedSavefileI = 0;
Room *_previousRoom = nullptr;
FakeSemaphore _interactionSemaphore; // to prevent ScummVM loading during button clicks
+ Common::Array<Common::String> _savefiles;
+ Common::SaveFileManager *_saveFileMgr;
};
}
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 15fbbfa5cb7..32dfe97f682 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -97,7 +97,7 @@ KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
keymap->addAction(act);
act = new Action("MENU", _("Menu"));
- act->setCustomEngineActionEvent((CustomEventType)InputAction::Menu);
+ act->setCustomEngineActionEvent((CustomEventType)EventAction::InputMenu);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_START");
keymap->addAction(act);
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index 5edef077507..5e40ccf00df 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -26,8 +26,9 @@
namespace Alcachofa {
-enum class InputAction {
- Menu
+enum class EventAction {
+ LoadFromMenu,
+ InputMenu
};
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index bb8fc261bb5..9a8fab8d396 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -132,7 +132,6 @@ private:
return #TaskName; \
}
-// TODO: This probably should be scummvm common
#if __cplusplus >= 201703L
#define TASK_BREAK_FALLTHROUGH [[fallthrough]];
#else
Commit: 59303318d7076d7ab006a3ff5b48c2389a138f0d
https://github.com/scummvm/scummvm/commit/59303318d7076d7ab006a3ff5b48c2389a138f0d
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Fix assert when loading while walking through door
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/player.cpp
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index fcee80cff6f..a434994a028 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -123,7 +123,7 @@ Common::Error AlcachofaEngine::run() {
}
void AlcachofaEngine::playVideo(int32 videoId) {
- FakeLock lock(_eventLoopSemaphore);
+ FakeLock lock("playVideo", _eventLoopSemaphore);
Video::MPEGPSDecoder decoder;
if (!decoder.loadFile(Common::Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1))))
error("Could not find video %d", videoId);
@@ -162,7 +162,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
void AlcachofaEngine::fadeExit() {
constexpr uint kFadeOutDuration = 1000;
- FakeLock lock(_eventLoopSemaphore);
+ FakeLock lock("fadeExit", _eventLoopSemaphore);
Event e;
Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
uint32 startTime = g_system->getMillis();
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 1b1e5564466..ecc2d061a45 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -55,21 +55,30 @@ void FakeSemaphore::sync(Serializer &s, FakeSemaphore &semaphore) {
// When the locks are loaded, they will increase the counter themselves
}
-FakeLock::FakeLock() : _semaphore(nullptr) {}
+FakeLock::FakeLock() {}
-FakeLock::FakeLock(FakeSemaphore &semaphore) : _semaphore(&semaphore) {
+FakeLock::FakeLock(const char *name, FakeSemaphore &semaphore)
+ : _name(name)
+ , _semaphore(&semaphore) {
_semaphore->_counter++;
- debugC(kDebugSemaphores, "Lock ctor %s to %u", _semaphore->_name, _semaphore->_counter);
+ debug("ctor");
}
-FakeLock::FakeLock(const FakeLock &other) : _semaphore(other._semaphore) {
+FakeLock::FakeLock(const FakeLock &other)
+ : _name(other._name)
+ , _semaphore(other._semaphore) {
assert(_semaphore != nullptr);
_semaphore->_counter++;
- debugC(kDebugSemaphores, "Lock copy %s to %u", _semaphore->_name, _semaphore->_counter);
+ debug("copy");
}
-FakeLock::FakeLock(FakeLock &&other) noexcept : _semaphore(other._semaphore) {
+FakeLock::FakeLock(FakeLock &&other) noexcept
+ : _name(other._name)
+ , _semaphore(other._semaphore) {
+ other._name = "<moved>";
other._semaphore = nullptr;
+ if (_semaphore != nullptr)
+ debug("move-ctor");
}
FakeLock::~FakeLock() {
@@ -77,19 +86,31 @@ FakeLock::~FakeLock() {
}
void FakeLock::operator= (FakeLock &&other) noexcept {
+ release();
+ _name = other._name;
_semaphore = other._semaphore;
+ other._name = "<moved>";
other._semaphore = nullptr;
+ debug("move-assign");
}
void FakeLock::release() {
if (_semaphore == nullptr)
return;
assert(_semaphore->_counter > 0);
- debugC(kDebugSemaphores, "Lock dtor %s to %u", _semaphore->_name, _semaphore->_counter - 1);
_semaphore->_counter--;
+ debug("release");
_semaphore = nullptr;
}
+void FakeLock::debug(const char *action) {
+ const char *myName = _name == nullptr ? "<null>" : _name;
+ if (_semaphore == nullptr)
+ debugC(kDebugSemaphores, "Lock %s %s nullptr", myName, action);
+ else
+ debugC(kDebugSemaphores, "Lock %s %s %s at %u", myName, action, _semaphore->_name, _semaphore->_counter);
+}
+
Vector3d as3D(const Vector2d &v) {
return Vector3d(v.getX(), v.getY(), 0.0f);
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index 0394653f657..dd793893f13 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -98,7 +98,7 @@ private:
struct FakeLock {
FakeLock();
- FakeLock(FakeSemaphore &semaphore);
+ FakeLock(const char *name, FakeSemaphore &semaphore);
FakeLock(const FakeLock &other);
FakeLock(FakeLock &&other) noexcept;
~FakeLock();
@@ -107,6 +107,9 @@ struct FakeLock {
inline bool isReleased() const { return _semaphore == nullptr; }
private:
+ void debug(const char *action);
+
+ const char *_name = "<uninitialized>";
FakeSemaphore *_semaphore = nullptr;
};
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 2dc95a755da..3af856cacb7 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -215,7 +215,7 @@ struct DoorTask : public Task {
if (_targetRoom == nullptr || _targetObject == nullptr)
return TaskReturn::finish(1);
- _musicLock = FakeLock(g_engine->sounds().musicSemaphore());
+ _musicLock = FakeLock("door-music", g_engine->sounds().musicSemaphore());
if (g_engine->sounds().musicID() != _targetRoom->musicID())
g_engine->sounds().fadeMusic();
TASK_WAIT(1, fade(process(), FadeType::ToBlack, 0, 1, 500, EasingType::Out, -5));
@@ -253,9 +253,9 @@ struct DoorTask : public Task {
bool hasMusicLock = !_musicLock.isReleased();
s.syncAsByte(hasMusicLock);
if (s.isLoading() && hasMusicLock)
- _musicLock = FakeLock(g_engine->sounds().musicSemaphore());
+ _musicLock = FakeLock("door-music", g_engine->sounds().musicSemaphore());
- _lock = FakeLock(_character->semaphore());
+ _lock = FakeLock("door", _character->semaphore());
findTarget();
}
@@ -292,7 +292,7 @@ void Player::triggerDoor(const Door *door) {
_heldItem = nullptr;
if (g_engine->game().shouldTriggerDoor(door)) {
- FakeLock lock(_activeCharacter->semaphore());
+ FakeLock lock("door", _activeCharacter->semaphore());
g_engine->scheduler().createProcess<DoorTask>(activeCharacterKind(), door, move(lock));
}
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 2deddae1472..861b8c61ee5 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -393,7 +393,7 @@ struct ScriptTask : public Task {
bool hasLock = !_lock.isReleased();
s.syncAsByte(hasLock);
if (s.isLoading() && hasLock)
- _lock = FakeLock(g_engine->player().semaphoreFor(process().character()));
+ _lock = FakeLock("script", g_engine->player().semaphoreFor(process().character()));
}
virtual const char *taskName() const override;
@@ -593,7 +593,7 @@ private:
}
process().character() = MainCharacterKind::None;
assert(player.semaphore().isReleased());
- _lock = { player.semaphore() };
+ _lock = FakeLock("script", player.semaphore());
return TaskReturn::finish(1);
}
case ScriptKernelTask::ChangeRoom:
@@ -967,7 +967,7 @@ Process *Script::createProcess(MainCharacterKind character, const String &proced
}
FakeLock lock;
if (!(flags & ScriptFlags::IsBackground))
- lock = FakeLock(g_engine->player().semaphoreFor(character));
+ lock = FakeLock("script", g_engine->player().semaphoreFor(character));
Process *process = g_engine->scheduler().createProcess<ScriptTask>(character, procedure, offset, Common::move(lock));
process->name() = procedure;
return process;
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index f3451db1ccf..6b8fa80620f 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -366,11 +366,11 @@ DECLARE_TASK(PlaySoundTask);
WaitForMusicTask::WaitForMusicTask(Process &process)
: Task(process)
- , _lock(g_engine->sounds().musicSemaphore()) {}
+ , _lock("wait-for-music", g_engine->sounds().musicSemaphore()) { }
WaitForMusicTask::WaitForMusicTask(Process &process, Serializer &s)
: Task(process)
- , _lock(g_engine->sounds().musicSemaphore()) {
+ , _lock("wait-for-music", g_engine->sounds().musicSemaphore()) {
syncGame(s);
}
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index dd310c81592..5ef0961504c 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -92,7 +92,7 @@ void MenuButton::onHoverUpdate() {}
void MenuButton::onClick() {
if (_isInteractable && _interactionLock.isReleased()) {
- _interactionLock = g_engine->menu().interactionSemaphore();
+ _interactionLock = FakeLock("button", g_engine->menu().interactionSemaphore());
_isClicked = true;
_triggerNextFrame = false;
_graphicClicked.start(false);
Commit: 3143836091d373767270a427f768db926332b0bd
https://github.com/scummvm/scummvm/commit/3143836091d373767270a427f768db926332b0bd
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Use MessageDialog for multiplayer warning
Changed paths:
engines/alcachofa/menu.cpp
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 10f0ab1a8e9..ed54a1c933a 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -19,6 +19,8 @@
*
*/
+#include "gui/message.h"
+
#include "alcachofa.h"
#include "metaengine.h"
#include "menu.h"
@@ -107,9 +109,10 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
ev.customType = (CustomEventType)EventAction::LoadFromMenu;
g_system->getEventManager()->pushEvent(ev);
}break;
- case MainMenuAction::InternetMenu:
- g_system->messageBox(LogMessageType::kWarning, "Multiplayer is not implemented in this ScummVM version.");
- break;
+ case MainMenuAction::InternetMenu: {
+ GUI::MessageDialog dialog("Multiplayer is not implemented in this ScummVM version.");
+ dialog.runModal();
+ }break;
case MainMenuAction::OptionsMenu:
g_engine->menu().openOptionsMenu();
break;
Commit: a8d936fcd20e16f55323efcff03a491357f9db6f
https://github.com/scummvm/scummvm/commit/a8d936fcd20e16f55323efcff03a491357f9db6f
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Add saving with in-game menu
Changed paths:
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index ed54a1c933a..a0afe911cca 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -68,6 +68,12 @@ void Menu::updateOpeningMenu() {
g_system->getWidth() / 2.0f, g_system->getHeight() / 2.0f, 0.0f));
}
+static int parseSavestateSlot(const String &filename) {
+ if (filename.size() < 5) // minimal name would be "t.###"
+ return 1;
+ return atoi(filename.c_str() + filename.size() - 3);
+}
+
void Menu::updateSelectedSavefile() {
auto getButton = [] (const char *name) {
MenuButton *button = dynamic_cast<MenuButton *>(g_engine->player().currentRoom()->getObjectByName(name));
@@ -75,11 +81,24 @@ void Menu::updateSelectedSavefile() {
return button;
};
- getButton("CARGAR")->isInteractable() = _selectedSavefileI < _savefiles.size();
+ bool isOldSavefile = _selectedSavefileI < _savefiles.size();
+ getButton("CARGAR")->isInteractable() = isOldSavefile;
getButton("ANTERIOR")->toggle(_selectedSavefileI > 0);
- getButton("SIGUIENTE")->toggle(_selectedSavefileI < _savefiles.size());
+ getButton("SIGUIENTE")->toggle(isOldSavefile);
+
+ if (isOldSavefile) {
+ ExtendedSavegameHeader header;
+ auto savefile = ScopedPtr<InSaveFile>(
+ _saveFileMgr->openForLoading(_savefiles[_selectedSavefileI]));
+ if (savefile != nullptr &&
+ g_engine->getMetaEngine()->readSavegameHeader(savefile.get(), &header, false))
+ _selectedSavefileDescription = header.description;
+ else // Fallback to generated description
+ _selectedSavefileDescription = String::format("Savestate %d",
+ parseSavestateSlot(_savefiles[_selectedSavefileI]));
+ }
- // TODO: Update thumbnail animation
+ // TODO: Update thumbnail animation;
}
void Menu::continueGame() {
@@ -99,7 +118,7 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
g_engine->menu().continueGame();
break;
case MainMenuAction::Save:
- warning("STUB: MainMenuAction Save");
+ triggerSave();
break;
case MainMenuAction::Load: {
// we are in some update loop, let's load next frame upon event handling
@@ -150,6 +169,34 @@ void Menu::triggerLoad() {
delete savefile;
}
+void Menu::triggerSave() {
+ String fileName, desc;
+ if (_selectedSavefileI < _savefiles.size()) {
+ fileName = _savefiles[_selectedSavefileI]; // overwrite a previous save
+ desc = _selectedSavefileDescription;
+ }
+ else {
+ // 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);
+ desc = String::format("Savestate %d", nextSlot);
+ _savefiles.push_back(fileName);
+ }
+
+ auto savefile = ScopedPtr<OutSaveFile>(_saveFileMgr->openForSaving(fileName));
+ if (savefile == nullptr) {
+ GUI::MessageDialog dialog("Could not open savefile");
+ dialog.runModal();
+ return;
+ }
+ if (g_engine->saveGameStream(savefile.get()).getCode() == kNoError)
+ g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), desc, false);
+
+ updateSelectedSavefile();
+}
+
void Menu::openOptionsMenu() {
setOptionsState();
g_engine->player().changeRoom("MENUOPCIONES", true);
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 7665e2a6a56..8b24bd7f25b 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -76,6 +76,7 @@ public:
void triggerOptionsValue(OptionsMenuValue valueId, float value);
private:
+ void triggerSave();
void updateSelectedSavefile();
void continueGame();
void continueMainMenu();
@@ -89,6 +90,7 @@ private:
_selectedSavefileI = 0;
Room *_previousRoom = nullptr;
FakeSemaphore _interactionSemaphore; // to prevent ScummVM loading during button clicks
+ Common::String _selectedSavefileDescription = "<unset>";
Common::Array<Common::String> _savefiles;
Common::SaveFileManager *_saveFileMgr;
};
Commit: c58bd40362527f1213b59c3321d03922dc8213ea
https://github.com/scummvm/scummvm/commit/c58bd40362527f1213b59c3321d03922dc8213ea
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Remove getRandomNumber from template
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index a434994a028..769aa77be8a 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -53,7 +53,6 @@ AlcachofaEngine *g_engine;
AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc)
: Engine(syst)
, _gameDescription(gameDesc)
- , _randomSource("Alcachofa")
, _eventLoopSemaphore("engine") {
g_engine = this;
}
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 975de0b50ce..5fc69bdc62a 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -83,9 +83,6 @@ private:
};
class AlcachofaEngine : public Engine {
-private:
- const ADGameDescription *_gameDescription;
- Common::RandomSource _randomSource;
protected:
// Engine APIs
Common::Error run() override;
@@ -117,19 +114,8 @@ public:
void setDebugMode(DebugMode debugMode, int32 param);
uint32 getFeatures() const;
-
- /**
- * Returns the game Id
- */
Common::String getGameId() const;
- /**
- * Gets a random number
- */
- uint32 getRandomNumber(uint maxNum) {
- return _randomSource.getRandomNumber(maxNum);
- }
-
bool hasFeature(EngineFeature f) const override {
return
(f == kSupportsLoadingDuringRuntime) ||
@@ -156,6 +142,7 @@ public:
private:
bool tryLoadFromLauncher();
+ const ADGameDescription *_gameDescription;
Console *_console = new Console();
Common::ScopedPtr<IDebugHandler> _debugHandler;
Common::ScopedPtr<IRenderer> _renderer;
Commit: 6458f3e8710094aa23e8d9e4f9f56715a5b8aabc
https://github.com/scummvm/scummvm/commit/6458f3e8710094aa23e8d9e4f9f56715a5b8aabc
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:57+02:00
Commit Message:
ALCACHOFA: Replace std::move with Common::move
Changed paths:
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 6b8fa80620f..3c141ca2fca 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -183,8 +183,8 @@ SoundHandle Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::
_mixer->playStream(type, &playback._handle, stream, -1, volume);
playback._type = type;
playback._inputRate = stream->getRate();
- playback._samples = std::move(samples);
- _playbacks.push_back(std::move(playback));
+ playback._samples = Common::move(samples);
+ _playbacks.push_back(Common::move(playback));
return playback._handle;
}
Commit: a73b70addef04ee2071f803fefd343dec326e07f
https://github.com/scummvm/scummvm/commit/a73b70addef04ee2071f803fefd343dec326e07f
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Add showGraphics debug flag
Changed paths:
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/general-objects.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/objects.h
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index dbb268fa43f..c0489bf8ab7 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -28,6 +28,7 @@ using namespace Common;
namespace Alcachofa {
Console::Console() : GUI::Debugger() {
+ registerVar("showGraphics", &_showGraphics);
registerVar("showInteractables", &_showInteractables);
registerVar("showCharacters", &_showCharacters);
registerVar("showFloorShape", &_showFloor);
@@ -53,6 +54,7 @@ Console::~Console() {
bool Console::isAnyDebugDrawingOn() const {
return
g_engine->isDebugModeActive() ||
+ _showGraphics ||
_showInteractables ||
_showCharacters ||
_showFloor ||
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index b4d8af84a73..3d6e5e0b6ac 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -41,6 +41,7 @@ public:
Console();
~Console() override;
+ inline bool showGraphics() const { return _showGraphics; }
inline bool showInteractables() const { return _showInteractables; }
inline bool showCharacters() const { return _showCharacters; }
inline bool showFloor() const { return _showFloor; }
@@ -60,6 +61,7 @@ private:
bool cmdTeleport(int argc, const char **args);
bool cmdToggleRoomFloor(int argc, const char **args);
+ bool _showGraphics = false;
bool _showInteractables = false;
bool _showCharacters = false;
bool _showFloor = false;
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index ca99e56bb3a..4b6674e8c1a 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -28,6 +28,7 @@
#include "common/system.h"
using namespace Common;
+using namespace Math;
namespace Alcachofa {
@@ -112,6 +113,34 @@ void GraphicObject::draw() {
g_engine->drawQueue().add<AnimationDrawRequest>(_graphic, is3D, blendMode);
}
+void GraphicObject::drawDebug() {
+ auto *renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
+ if (!isEnabled() || !_graphic.hasAnimation() || !g_engine->console().showGraphics() || renderer == nullptr)
+ return;
+
+ const bool is3D = room() != &g_engine->world().inventory();
+ Vector2d topLeft(as2D(_graphic.topLeft()));
+ float scale = _graphic.scale() * _graphic.depthScale() * kInvBaseScale;
+ Vector2d size;
+ if (is3D) {
+ Vector3d topLeftTmp = as3D(topLeft);
+ topLeftTmp.z() = _graphic.scale();
+ _graphic.animation().outputRect3D(_graphic.frameI(), scale, topLeftTmp, size);
+ topLeft = as2D(topLeftTmp);
+ }
+ else
+ _graphic.animation().outputRect2D(_graphic.frameI(), scale, topLeft, size);
+
+ Vector2d points[] = {
+ topLeft,
+ topLeft + Vector2d(size.getX(), 0.0f),
+ topLeft + Vector2d(size.getX(), size.getY()),
+ topLeft + Vector2d(0.0f, size.getY()),
+ topLeft
+ };
+ renderer->debugPolyline({ points, 5 }, kDebugGreen);
+}
+
void GraphicObject::loadResources() {
_graphic.loadResources();
}
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 86332477c09..46c9fc04845 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -334,15 +334,20 @@ void Animation::prerenderFrame(int32 frameI) {
_renderedPremultiplyAlpha = _premultiplyAlpha;
}
+void Animation::outputRect2D(int32 frameI, float scale, Vector2d &topLeft, Vector2d &size) const {
+ auto bounds = frameBounds(frameI);
+ topLeft += as2D(totalFrameOffset(frameI)) * scale;
+ size = Vector2d(bounds.width(), bounds.height()) * scale;
+}
+
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);
- Vector2d size(bounds.width(), bounds.height());
- topLeft += as2D(totalFrameOffset(frameI)) * scale;
- size *= scale;
+ Vector2d size;
+ outputRect2D(frameI, scale, topLeft, size);
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
@@ -350,17 +355,22 @@ void Animation::draw2D(int32 frameI, Vector2d topLeft, float scale, BlendMode bl
renderer.quad(topLeft, size, color, Angle(), texMin, texMax);
}
+void Animation::outputRect3D(int32 frameI, float scale, Vector3d &topLeft, Vector2d &size) const {
+ auto bounds = frameBounds(frameI);
+ topLeft += as3D(totalFrameOffset(frameI)) * scale;
+ topLeft = g_engine->camera().transform3Dto2D(topLeft);
+ size = Vector2d(bounds.width(), bounds.height()) * scale * topLeft.z();
+}
+
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);
- topLeft += as3D(totalFrameOffset(frameI)) * scale;
- topLeft = g_engine->camera().transform3Dto2D(topLeft);
+ Vector2d size;
+ outputRect3D(frameI, scale, topLeft, size);
const auto rotation = -g_engine->camera().rotation();
- Vector2d size(bounds.width(), bounds.height());
- size *= scale * topLeft.z();
auto &renderer = g_engine->renderer();
renderer.setTexture(_renderedTexture.get());
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 8520938da57..89d20f7578d 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -195,22 +195,26 @@ public:
int32 frameAtTime(uint32 time) const;
int32 imageIndex(int32 frameI, int32 spriteI) const;
using AnimationBase::imageSize;
+ void outputRect2D(int32 frameI, float scale, Math::Vector2d &topLeft, Math::Vector2d &size) const;
+ void outputRect3D(int32 frameI, float scale, Math::Vector3d &topLeft, Math::Vector2d &size) const;
+
+ void overrideTexture(const Graphics::ManagedSurface &surface);
void draw2D(
int32 frameI,
- Math::Vector2d center,
+ Math::Vector2d topLeft,
float scale,
BlendMode blendMode,
Color color);
void draw3D(
int32 frameI,
- Math::Vector3d center,
+ Math::Vector3d topLeft,
float scale,
BlendMode blendMode,
Color color);
void drawEffect(
int32 frameI,
- Math::Vector3d center,
+ Math::Vector3d topLeft,
Math::Vector2d tiling,
Math::Vector2d texOffset,
BlendMode blendMode);
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index a0c133eb000..e90353b233b 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -89,6 +89,7 @@ public:
~GraphicObject() override = default;
void draw() override;
+ void drawDebug() override;
void loadResources() override;
void freeResources() override;
void syncGame(Common::Serializer &serializer) override;
Commit: 3d3dda8d639928e9461740095e30dd2baae05030
https://github.com/scummvm/scummvm/commit/3d3dda8d639928e9461740095e30dd2baae05030
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Render-to-texture and initial savestate thumbnails
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
engines/alcachofa/metaengine.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 769aa77be8a..74d89522b5d 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -28,6 +28,8 @@
#include "engines/util.h"
#include "graphics/paletteman.h"
#include "graphics/framelimiter.h"
+#include "graphics/thumbnail.h"
+#include "image/png.h"
#include "video/mpegps_decoder.h"
#include "alcachofa.h"
@@ -114,7 +116,12 @@ Common::Error AlcachofaEngine::run() {
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
- limiter.delayBeforeSwap();
+ if (!_renderer->hasOutput()) {
+ limiter.delayBeforeSwap();
+ g_system->updateScreen();
+ }
+ // else we just rendered to some surface and will use it in the next frame
+ // no need to update the screen or wait
limiter.startFrame();
}
@@ -143,6 +150,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
_renderer->setTexture(texture.get());
_renderer->quad({}, { (float)g_system->getWidth(), (float)g_system->getHeight() });
_renderer->end();
+ g_system->updateScreen();
}
_input.nextFrame();
@@ -153,8 +161,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
if (_input.wasAnyMouseReleased() || _input.wasMenuKeyPressed())
break;
- g_system->updateScreen();
- g_system->delayMillis(decoder.getTimeToNextFrame() / 2);
+ g_system->delayMillis(decoder.getTimeToNextFrame());
}
decoder.stop();
}
@@ -255,8 +262,19 @@ Common::String AlcachofaEngine::getSaveStatePattern() {
return getMetaEngine()->getSavegameFilePattern(_targetName.c_str());
}
-Common::Error AlcachofaEngine::syncGame(Serializer &s) {
- s.syncVersion((Serializer::Version)SaveVersion::Initial);
+Common::Error AlcachofaEngine::syncGame(MySerializer &s) {
+ if (!s.syncVersion((Serializer::Version)kCurrentSaveVersion))
+ return { kUnknownError, "Gamestate version is higher than expected" };
+
+ Graphics::ManagedSurface *thumbnail = nullptr;
+ if (s.isSaving()) {
+ thumbnail = new Graphics::ManagedSurface();
+ getSavegameThumbnail(*thumbnail->surfacePtr());
+ }
+ if (!syncThumbnail(s, thumbnail))
+ return { kUnknownError, "Could not read thumbnail" };
+ if (thumbnail != nullptr)
+ delete thumbnail;
uint32 millis = menu().isOpen()
? menu().millisBeforeMenu()
@@ -291,6 +309,65 @@ Common::Error AlcachofaEngine::syncGame(Serializer &s) {
return Common::kNoError;
}
+static constexpr uint32 kNoThumbnailMagicValue = 0xBADBAD;
+
+bool AlcachofaEngine::syncThumbnail(MySerializer &s, Graphics::ManagedSurface *thumbnail) {
+ if (s.isLoading()) {
+ Graphics::Surface *readThumbnail = nullptr;
+ if (Graphics::loadThumbnail(s.readStream(), readThumbnail, thumbnail == nullptr) && readThumbnail != nullptr) {
+ if (thumbnail != nullptr) {
+ thumbnail->free();
+ *thumbnail->surfacePtr() = *readThumbnail;
+ }
+ }
+ else {
+ // If we do not get a thumbnail, maybe we get at least the marker that there is no thumbnail
+ uint32 magicValue = 0;
+ s.syncAsUint32LE(magicValue);
+ if (magicValue != kNoThumbnailMagicValue)
+ return false; // the savegame is not valid
+ else // this is not an error, just a pity
+ warning("No thumbnail stored in in-game savestate");
+ }
+ }
+ else {
+ if (thumbnail == nullptr ||
+ thumbnail->getPixels() == nullptr ||
+ !Graphics::saveThumbnail(s.writeStream(), *thumbnail)) {
+ // We were not able to get a thumbnail, save a value that denotes that situation
+ warning("Could not save in-game thumbnail");
+ uint32 magicValue = kNoThumbnailMagicValue;
+ s.syncAsUint32LE(magicValue);
+ }
+ }
+ return true;
+}
+
+void AlcachofaEngine::getSavegameThumbnail(Graphics::Surface &thumbnail) {
+ thumbnail.free();
+
+ auto *bigThumbnail = g_engine->menu().getBigThumbnail();
+ if (bigThumbnail != nullptr) {
+ // we still have a one from the in-game menu opening, reuse that
+ thumbnail.copyFrom(*bigThumbnail);
+ return;
+ }
+
+ // otherwise we have to rerender
+ thumbnail.create(kBigThumbnailWidth, kBigThumbnailHeight, Graphics::PixelFormat::createFormatRGBA32());
+ if (g_engine->player().currentRoom() == nullptr)
+ return; // but without a room we would render only black anyway
+
+ g_engine->drawQueue().clear();
+ g_engine->renderer().begin();
+ g_engine->renderer().setOutput(thumbnail);
+ g_engine->player().currentRoom()->draw();
+ g_engine->drawQueue().draw();
+ g_engine->renderer().end();
+
+ // we should be within the event loop. as such it is quite safe to mess with the drawQueue or renderer
+}
+
bool AlcachofaEngine::tryLoadFromLauncher() {
int saveSlot = ConfMan.getInt("save_slot");
if (!ConfMan.hasKey("save_slot") || saveSlot < 0)
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 5fc69bdc62a..6f828076d13 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -55,9 +55,31 @@ class Menu;
class Game;
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;
+
+
enum class SaveVersion : Common::Serializer::Version {
Initial = 0
};
+static constexpr SaveVersion kCurrentSaveVersion = SaveVersion::Initial;
+
+class MySerializer : public Common::Serializer {
+public:
+ using Common::Serializer::Serializer;
+
+ Common::SeekableReadStream &readStream() {
+ assert(isLoading() && _loadStream != nullptr);
+ return *_loadStream;
+ }
+
+ Common::WriteStream &writeStream() {
+ assert(isSaving() && _saveStream != nullptr);
+ return *_saveStream;
+ }
+};
class Config {
public:
@@ -129,15 +151,19 @@ public:
}
Common::String getSaveStatePattern();
- Common::Error syncGame(Common::Serializer &s);
+ Common::Error syncGame(MySerializer &s);
Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
- Common::Serializer s(nullptr, stream);
+ assert(stream != nullptr);
+ MySerializer s(nullptr, stream);
return syncGame(s);
}
Common::Error loadGameStream(Common::SeekableReadStream *stream) override {
- Common::Serializer s(stream, nullptr);
+ assert(stream != nullptr);
+ MySerializer s(stream, nullptr);
return syncGame(s);
}
+ bool syncThumbnail(MySerializer &s, Graphics::ManagedSurface *thumbnail);
+ void getSavegameThumbnail(Graphics::Surface &thumbnail);
private:
bool tryLoadFromLauncher();
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index e1e88278278..1cd4efd2ce6 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -20,6 +20,7 @@
*/
#include "graphics.h"
+#include "detection.h"
#include "common/system.h"
#include "engines/util.h"
@@ -124,7 +125,8 @@ class OpenGLRenderer : public IDebugRenderer {
public:
OpenGLRenderer(Point resolution)
: _resolution(resolution) {
- initViewportAndMatrices();
+ setViewportToScreen();
+
GL_CALL(glDisable(GL_LIGHTING));
GL_CALL(glDisable(GL_DEPTH_TEST));
GL_CALL(glDisable(GL_SCISSOR_TEST));
@@ -147,6 +149,8 @@ public:
GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
+ setViewportToScreen();
+ _currentOutput = nullptr;
_currentLodBias = -1000.0f;
_currentTexture = nullptr;
_currentBlendMode = (BlendMode)-1;
@@ -155,7 +159,20 @@ public:
void end() override {
GL_CALL(glFlush());
- g_system->updateScreen();
+
+ if (_currentOutput != nullptr) {
+ g_system->presentBuffer();
+ auto format = getOpenGLFormatOf(_currentOutput->format);
+ GL_CALL(glReadPixels(
+ 0,
+ 0,
+ _outputSize.x,
+ _outputSize.y,
+ format._format,
+ format._type,
+ _currentOutput->getPixels()
+ ));
+ }
}
void setTexture(ITexture *texture) override {
@@ -253,6 +270,37 @@ public:
_currentLodBias = lodBias;
}
+ void setOutput(Surface &output) override {
+ assert(_isFirstDrawCommand);
+ setViewportToRect(output.w, output.h);
+ _currentOutput = &output;
+
+ // just debug warnings as it will only produce a graphical glitch while
+ // there is some chance the resolution could change from here to ::end
+ // and this is per-frame so maybe don't spam the console with the same message
+
+ if (output.w > g_system->getWidth() || output.h > g_system->getHeight())
+ debugC(0, kDebugGraphics, "Output is larger than screen, output will be cropped (%d, %d) > (%d, %d)",
+ output.w, output.h, g_system->getWidth(), g_system->getHeight());
+
+ auto format = getOpenGLFormatOf(output.format);
+ if (format._format == GL_NONE) {
+ auto formatString = output.format.toString();
+ debugC(0, kDebugGraphics, "Cannot use pixelformat of given output surface: %s", formatString.c_str());
+ _currentOutput = nullptr;
+ }
+
+ if (output.pitch != output.format.bytesPerPixel * output.w) {
+ // Maybe there would be a way with glPixelStore
+ debugC(0, kDebugGraphics, "Incompatible output surface pitch");
+ _currentOutput = nullptr;
+ }
+ }
+
+ bool hasOutput() const override {
+ return _currentOutput != nullptr;
+ }
+
virtual void quad(
Vector2d topLeft,
Vector2d size,
@@ -353,7 +401,18 @@ public:
}
private:
- void initViewportAndMatrices() {
+ void setMatrices(bool flipped) {
+ float bottom = flipped ? _resolution.y : 0.0f;
+ float top = flipped ? 0.0f : _resolution.y;
+
+ GL_CALL(glMatrixMode(GL_PROJECTION));
+ GL_CALL(glLoadIdentity());
+ GL_CALL(glOrtho(0.0f, _resolution.x, bottom, top, -1.0f, 1.0f));
+ GL_CALL(glMatrixMode(GL_MODELVIEW));
+ GL_CALL(glLoadIdentity());
+ }
+
+ void setViewportToScreen() {
int32 screenWidth = g_system->getWidth();
int32 screenHeight = g_system->getHeight();
Rect viewport(
@@ -364,16 +423,19 @@ private:
(screenHeight - viewport.height()) / 2);
GL_CALL(glViewport(viewport.left, viewport.top, viewport.width(), viewport.height()));
- GL_CALL(glMatrixMode(GL_PROJECTION));
- GL_CALL(glLoadIdentity());
- GL_CALL(glOrtho(0.0f, _resolution.x, _resolution.y, 0.0f, -1.0f, 1.0f));
- GL_CALL(glMatrixMode(GL_MODELVIEW));
- GL_CALL(glLoadIdentity());
+ setMatrices(true);
+ }
+
+ void setViewportToRect(int16 outputWidth, int16 outputHeight) {
+ _outputSize.x = MIN(outputWidth, g_system->getWidth());
+ _outputSize.y = MIN(outputHeight, g_system->getHeight());
+ GL_CALL(glViewport(0, 0, _outputSize.x, _outputSize.y));
+ setMatrices(false);
}
void checkFirstDrawCommand() {
- // We delay clearing the screen. It is much easier for the game to switch to a
- // framebuffer before
+ // We delay clearing the screen. It is much easier for the game
+ // to switch to a framebuffer before
if (!_isFirstDrawCommand)
return;
_isFirstDrawCommand = false;
@@ -381,7 +443,8 @@ private:
GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
}
- Point _resolution;
+ Point _resolution, _outputSize;
+ Surface *_currentOutput = nullptr;
OpenGLTexture *_currentTexture = nullptr;
BlendMode _currentBlendMode = (BlendMode)-1;
float _currentLodBias = 0.0f;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 46c9fc04845..6f12ea7133e 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -243,10 +243,13 @@ void Animation::load() {
if (_isLoaded)
return;
AnimationBase::load();
- const bool withMipmaps = _folder != AnimationFolder::Backgrounds;
Rect maxBounds = maxFrameBounds();
_renderedSurface.create(maxBounds.width(), maxBounds.height(), BlendBlit::getSupportedPixelFormat());
- _renderedTexture = g_engine->renderer().createTexture(maxBounds.width(), maxBounds.height(), withMipmaps);
+ _renderedTexture = g_engine->renderer().createTexture(maxBounds.width(), maxBounds.height(), true);
+
+ // We always create mipmaps, even for the backgrounds that usually do not scale much,
+ // the exception to this is the thumbnails for the savestates.
+ // If we need to reduce graphics memory usage in the future, we can change it right here
}
void Animation::freeImages() {
@@ -311,6 +314,25 @@ int32 Animation::frameAtTime(uint32 time) const {
return -1;
}
+void Animation::overrideTexture(const ManagedSurface &surface) {
+ // In order to really use the overridden surface we have to override all
+ // values used for calculating the output size
+ _renderedFrameI = 0;
+ _renderedPremultiplyAlpha = _premultiplyAlpha;
+ _renderedSurface.free();
+ _renderedSurface.w = surface.w;
+ _renderedSurface.h = surface.h;
+ _images[0]->free();
+ _images[0]->w = surface.w;
+ _images[0]->h = surface.h;
+
+ if (_renderedTexture->size() != Point(surface.w, surface.h)) {
+ _renderedTexture = Common::move(
+ g_engine->renderer().createTexture(surface.w, surface.h, false));
+ }
+ _renderedTexture->update(surface);
+}
+
void Animation::prerenderFrame(int32 frameI) {
assert(frameI >= 0 && (uint)frameI < frameCount());
if (frameI == _renderedFrameI && _renderedPremultiplyAlpha == _premultiplyAlpha)
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 89d20f7578d..3c547dd9ee8 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -81,6 +81,8 @@ public:
virtual void setTexture(ITexture *texture) = 0;
virtual void setBlendMode(BlendMode blendMode) = 0;
virtual void setLodBias(float lodBias) = 0;
+ virtual void setOutput(Graphics::Surface &surface) = 0;
+ virtual bool hasOutput() const = 0;
virtual void quad(
Math::Vector2d topLeft,
Math::Vector2d size,
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index a0afe911cca..7606585b97a 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -20,6 +20,7 @@
*/
#include "gui/message.h"
+#include "graphics/thumbnail.h"
#include "alcachofa.h"
#include "metaengine.h"
@@ -28,8 +29,36 @@
#include "script.h"
using namespace Common;
+using namespace Graphics;
namespace Alcachofa {
+
+static void createThumbnail(ManagedSurface &surface) {
+ surface.create(kBigThumbnailWidth, kBigThumbnailHeight, PixelFormat::createFormatRGBA32());
+}
+
+static void convertToGrayscale(ManagedSurface &surface) {
+ assert(!surface.empty());
+ assert(surface.format == PixelFormat::createFormatRGBA32());
+ uint32 rgbMask = ~(uint32(0xff) << surface.format.aShift);
+
+ for (int y = 0; y < surface.h; y++) {
+ union {
+ uint32 *pixel;
+ uint8 *components;
+ };
+ pixel = (uint32 *)surface.getBasePtr(0, y);
+ for (int x = 0; x < surface.w; x++, pixel++) {
+ *pixel &= rgbMask;
+ byte gray = (components[0] + components[1] + components[2] + components[3]) / 3;
+ *pixel =
+ (uint32(gray) << surface.format.rShift) |
+ (uint32(gray) << surface.format.gShift) |
+ (uint32(gray) << surface.format.bShift) |
+ (uint32(0xff) << surface.format.aShift);
+ }
+ }
+}
Menu::Menu()
: _interactionSemaphore("menu")
@@ -39,13 +68,17 @@ void Menu::resetAfterLoad() {
_isOpen = false;
_openAtNextFrame = false;
_previousRoom = nullptr;
+ _bigThumbnail.free();
+ _selectedThumbnail.free();
}
void Menu::updateOpeningMenu() {
if (!_openAtNextFrame) {
- _openAtNextFrame =
- g_engine->input().wasMenuKeyPressed() &&
- g_engine->player().isAllowedToOpenMenu();
+ if (g_engine->input().wasMenuKeyPressed() && g_engine->player().isAllowedToOpenMenu()) {
+ _openAtNextFrame = true;
+ createThumbnail(_bigThumbnail);
+ g_engine->renderer().setOutput(*_bigThumbnail.surfacePtr());
+ }
return;
}
_openAtNextFrame = false;
@@ -54,7 +87,6 @@ void Menu::updateOpeningMenu() {
_millisBeforeMenu = g_engine->getMillis();
_previousRoom = g_engine->player().currentRoom();
_isOpen = true;
- // TODO: Render thumbnail
g_engine->player().changeRoom("MENUPRINCIPAL", true);
_savefiles = _saveFileMgr->listSavefiles(g_engine->getSaveStatePattern());
sort(_savefiles.begin(), _savefiles.end()); // the pattern ensures that the last file has the greatest slot
@@ -87,23 +119,48 @@ void Menu::updateSelectedSavefile() {
getButton("SIGUIENTE")->toggle(isOldSavefile);
if (isOldSavefile) {
- ExtendedSavegameHeader header;
- auto savefile = ScopedPtr<InSaveFile>(
- _saveFileMgr->openForLoading(_savefiles[_selectedSavefileI]));
- if (savefile != nullptr &&
- g_engine->getMetaEngine()->readSavegameHeader(savefile.get(), &header, false))
- _selectedSavefileDescription = header.description;
- else // Fallback to generated description
+ if (!tryReadOldSavefile()) {
_selectedSavefileDescription = String::format("Savestate %d",
parseSavestateSlot(_savefiles[_selectedSavefileI]));
+ createThumbnail(_selectedThumbnail);
+ }
}
+ else {
+ _selectedThumbnail.copyFrom(_bigThumbnail);
+ //convertToGrayscale(_selectedThumbnail);
+ }
+
+ ObjectBase *captureObject = g_engine->player().currentRoom()->getObjectByName("Capture");
+ scumm_assert(captureObject);
+ Graphic *captureGraphic = captureObject->graphic();
+ scumm_assert(captureGraphic);
+ captureGraphic->animation().overrideTexture(_selectedThumbnail);
+}
- // TODO: Update thumbnail animation;
+bool Menu::tryReadOldSavefile() {
+ auto savefile = ScopedPtr<InSaveFile>(
+ _saveFileMgr->openForLoading(_savefiles[_selectedSavefileI]));
+ if (savefile == nullptr)
+ return false;
+
+ ExtendedSavegameHeader header;
+ if (!g_engine->getMetaEngine()->readSavegameHeader(savefile.get(), &header, false))
+ return false;
+ _selectedSavefileDescription = header.description;
+
+ MySerializer serializer(savefile.get(), nullptr);
+ if (!serializer.syncVersion((Serializer::Version)kCurrentSaveVersion) ||
+ !g_engine->syncThumbnail(serializer, &_selectedThumbnail))
+ return false;
+
+ return true;
}
void Menu::continueGame() {
assert(_previousRoom != nullptr);
_isOpen = false;
+ _bigThumbnail.free();
+ _selectedThumbnail.free();
g_engine->input().nextFrame(); // presumably to clear all was* flags
g_engine->player().changeRoom(_previousRoom->name(), true);
g_engine->sounds().pauseAll(false);
@@ -165,8 +222,13 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
void Menu::triggerLoad() {
auto *savefile = _saveFileMgr->openForLoading(_savefiles[_selectedSavefileI]);
- g_engine->loadGameStream(savefile);
+ auto result = g_engine->loadGameStream(savefile);
delete savefile;
+ if (result.getCode() != kNoError) {
+ GUI::MessageDialog dialog(result.getTranslatedDesc());
+ dialog.runModal();
+ return;
+ }
}
void Menu::triggerSave() {
@@ -182,19 +244,23 @@ void Menu::triggerSave() {
: parseSavestateSlot(_savefiles.back()) + 1;
fileName = g_engine->getSaveStateName(nextSlot);
desc = String::format("Savestate %d", nextSlot);
- _savefiles.push_back(fileName);
}
+ Error error(kNoError);
auto savefile = ScopedPtr<OutSaveFile>(_saveFileMgr->openForSaving(fileName));
- if (savefile == nullptr) {
- GUI::MessageDialog dialog("Could not open savefile");
+ if (savefile == nullptr)
+ error = Error(kReadingFailed);
+ else
+ error = g_engine->saveGameStream(savefile.get());
+ if (error.getCode() == kNoError) {
+ g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), desc, false);
+ _savefiles.push_back(fileName);
+ updateSelectedSavefile();
+ }
+ else {
+ GUI::MessageDialog dialog(error.getTranslatedDesc());
dialog.runModal();
- return;
}
- if (g_engine->saveGameStream(savefile.get()).getCode() == kNoError)
- g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), desc, false);
-
- updateSelectedSavefile();
}
void Menu::openOptionsMenu() {
@@ -296,7 +362,12 @@ void Menu::continueMainMenu() {
g_engine->player().isGameLoaded() ? "MENUPRINCIPAL" : "MENUPRINCIPALINICIO",
true
);
- // TODO: Update menu state and thumbanil
+
+ updateSelectedSavefile();
+}
+
+const Graphics::Surface *Menu::getBigThumbnail() const {
+ return _bigThumbnail.empty() ? nullptr : &_bigThumbnail.rawSurface();
}
}
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 8b24bd7f25b..81f962f74c2 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -75,9 +75,15 @@ public:
void triggerOptionsAction(OptionsMenuAction action);
void triggerOptionsValue(OptionsMenuValue valueId, float value);
+ // if we do still have a big thumbnail, any autosaves, ScummVM-saves, ingame-saves
+ // do not have to render themselves, they can just reuse the one we have.
+ // as such - may return nullptr
+ const Graphics::Surface *getBigThumbnail() const;
+
private:
void triggerSave();
void updateSelectedSavefile();
+ bool tryReadOldSavefile();
void continueGame();
void continueMainMenu();
void setOptionsState();
@@ -92,6 +98,9 @@ private:
FakeSemaphore _interactionSemaphore; // to prevent ScummVM loading during button clicks
Common::String _selectedSavefileDescription = "<unset>";
Common::Array<Common::String> _savefiles;
+ Graphics::ManagedSurface
+ _bigThumbnail, // big because it is for the in-game menu, not for ScummVM
+ _selectedThumbnail;
Common::SaveFileManager *_saveFileMgr;
};
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 32dfe97f682..5c9a906abbb 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -106,8 +106,10 @@ KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
}
void AlcachofaMetaEngine::getSavegameThumbnail(Surface &surf) {
- // TODO: Implement
- surf.create(160, 120, PixelFormat::createFormatRGBA32());
+ if (Alcachofa::g_engine == nullptr)
+ surf.create(160, 120, PixelFormat::createFormatRGBA32());
+ else
+ Alcachofa::g_engine->getSavegameThumbnail(surf);
}
#if PLUGIN_ENABLED_DYNAMIC(ALCACHOFA)
Commit: 4495dc5d90ec79b4618884eec92f88dc9be95a02
https://github.com/scummvm/scummvm/commit/4495dc5d90ec79b4618884eec92f88dc9be95a02
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Decrease savestate size
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/metaengine.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 74d89522b5d..982198f34d8 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -313,17 +313,18 @@ static constexpr uint32 kNoThumbnailMagicValue = 0xBADBAD;
bool AlcachofaEngine::syncThumbnail(MySerializer &s, Graphics::ManagedSurface *thumbnail) {
if (s.isLoading()) {
- Graphics::Surface *readThumbnail = nullptr;
- if (Graphics::loadThumbnail(s.readStream(), readThumbnail, thumbnail == nullptr) && readThumbnail != nullptr) {
+ auto prevPosition = s.readStream().pos();
+ Image::PNGDecoder pngDecoder;
+ if (pngDecoder.loadStream(s.readStream()) && pngDecoder.getSurface () != nullptr) {
if (thumbnail != nullptr) {
thumbnail->free();
- *thumbnail->surfacePtr() = *readThumbnail;
+ thumbnail->copyFrom(*pngDecoder.getSurface());
}
}
else {
// If we do not get a thumbnail, maybe we get at least the marker that there is no thumbnail
- uint32 magicValue = 0;
- s.syncAsUint32LE(magicValue);
+ s.readStream().seek(prevPosition, SEEK_SET);
+ uint32 magicValue = s.readStream().readUint32LE();
if (magicValue != kNoThumbnailMagicValue)
return false; // the savegame is not valid
else // this is not an error, just a pity
@@ -333,11 +334,10 @@ bool AlcachofaEngine::syncThumbnail(MySerializer &s, Graphics::ManagedSurface *t
else {
if (thumbnail == nullptr ||
thumbnail->getPixels() == nullptr ||
- !Graphics::saveThumbnail(s.writeStream(), *thumbnail)) {
+ !Image::writePNG(s.writeStream(), *thumbnail)) {
// We were not able to get a thumbnail, save a value that denotes that situation
warning("Could not save in-game thumbnail");
- uint32 magicValue = kNoThumbnailMagicValue;
- s.syncAsUint32LE(magicValue);
+ s.writeStream().writeUint32LE(kNoThumbnailMagicValue);
}
}
return true;
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 7606585b97a..194d3d4b445 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -127,7 +127,7 @@ void Menu::updateSelectedSavefile() {
}
else {
_selectedThumbnail.copyFrom(_bigThumbnail);
- //convertToGrayscale(_selectedThumbnail);
+ convertToGrayscale(_selectedThumbnail);
}
ObjectBase *captureObject = g_engine->player().currentRoom()->getObjectByName("Capture");
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 5c9a906abbb..14e1f28a525 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -106,10 +106,15 @@ KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
}
void AlcachofaMetaEngine::getSavegameThumbnail(Surface &surf) {
- if (Alcachofa::g_engine == nullptr)
- surf.create(160, 120, PixelFormat::createFormatRGBA32());
- else
- Alcachofa::g_engine->getSavegameThumbnail(surf);
+ if (Alcachofa::g_engine != nullptr) {
+ Surface bigThumbnail;
+ Alcachofa::g_engine->getSavegameThumbnail(bigThumbnail);
+ if (bigThumbnail.getPixels() != nullptr) {
+ surf = *bigThumbnail.scale(kSmallThumbnailWidth, kSmallThumbnailHeight, true);
+ bigThumbnail.free();
+ }
+ }
+ // if not, ScummVM will output an appropriate warning
}
#if PLUGIN_ENABLED_DYNAMIC(ALCACHOFA)
Commit: bb50a4b39422c35d04e373ec07e5233892568d7c
https://github.com/scummvm/scummvm/commit/bb50a4b39422c35d04e373ec07e5233892568d7c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Fix fadeExit
Changed paths:
engines/alcachofa/alcachofa.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 982198f34d8..35431a6b226 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -173,6 +173,7 @@ void AlcachofaEngine::fadeExit() {
Graphics::FrameLimiter limiter(g_system, kDefaultFramerate, false);
uint32 startTime = g_system->getMillis();
+ Room *room = g_engine->player().currentRoom();
_renderer->end(); // we were in a frame, let's exit
while (g_system->getMillis() - startTime < kFadeOutDuration && !shouldQuit()) {
_input.nextFrame();
@@ -184,12 +185,14 @@ void AlcachofaEngine::fadeExit() {
_renderer->begin();
_drawQueue->clear();
float t = ((float)(g_system->getMillis() - startTime)) / kFadeOutDuration;
- // TODO: Implement cross-fade and add to fadeExit
+ if (room != nullptr)
+ room->draw();
_drawQueue->add<FadeDrawRequest>(FadeType::ToBlack, t, -kForegroundOrderCount);
_drawQueue->draw();
_renderer->end();
limiter.delayBeforeSwap();
+ g_system->updateScreen();
limiter.startFrame();
}
Commit: af7cc2c587965f74e620ab23dd6e121216b4cc7a
https://github.com/scummvm/scummvm/commit/af7cc2c587965f74e620ab23dd6e121216b4cc7a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Add high-quality check
Changed paths:
engines/alcachofa/common.cpp
engines/alcachofa/general-objects.cpp
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index ecc2d061a45..2ff0e05fad0 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -150,7 +150,6 @@ String readVarString(ReadStream &stream) {
if (length == 0)
return Common::String();
- // TODO: Being able to resize a string would avoid the double-allocation :/
char *buffer = new char[length];
if (buffer == nullptr)
error("Out of memory in readVarString");
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 4b6674e8c1a..7d33e19838d 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -220,7 +220,7 @@ SpecialEffectObject::SpecialEffectObject(Room *room, ReadStream &stream)
}
void SpecialEffectObject::draw() {
- if (!isEnabled()) // TODO: Add high quality check
+ if (!isEnabled() || !g_engine->config().highQuality())
return;
const auto texOffset = g_engine->getMillis() * 0.001f * _texShift;
const BlendMode blendMode = _type == GraphicObjectType::Effect
Commit: 27781598301a09132a6d21ade472b029695ce152
https://github.com/scummvm/scummvm/commit/27781598301a09132a6d21ade472b029695ce152
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Add key inputs for opening/closing inventory
Changed paths:
engines/alcachofa/global-ui.cpp
engines/alcachofa/input.cpp
engines/alcachofa/input.h
engines/alcachofa/metaengine.cpp
engines/alcachofa/metaengine.h
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 0d842c56986..bec45d35f52 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -31,7 +31,7 @@ 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 I should add key-based controls for it
+// 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() {
@@ -82,6 +82,10 @@ bool GlobalUI::updateOpeningInventory() {
if (g_engine->menu().isOpen() || !g_engine->player().isGameLoaded())
return false;
+ const bool userWantsToOpenInventory =
+ openInventoryTriggerBounds().contains(g_engine->input().mousePos2D()) ||
+ g_engine->input().wasInventoryKeyPressed();
+
if (_isOpeningInventory) {
uint32 deltaTime = g_engine->getMillis() - _timeForInventory;
if (deltaTime >= 1000) {
@@ -94,7 +98,7 @@ bool GlobalUI::updateOpeningInventory() {
}
return true;
}
- else if (openInventoryTriggerBounds().contains(g_engine->input().mousePos2D())) {
+ else if (userWantsToOpenInventory) {
_isClosingInventory = false;
_isOpeningInventory = true;
_timeForInventory = g_engine->getMillis();
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index bfa2391c360..41e10b91176 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -36,6 +36,7 @@ void Input::nextFrame() {
_wasMouseLeftReleased = false;
_wasMouseRightReleased = false;
_wasMenuKeyPressed = false;
+ _wasInventoryKeyPressed = false;
updateMousePos3D(); // camera transformation might have changed
}
@@ -74,6 +75,9 @@ bool Input::handleEvent(const Common::Event &event) {
case EventAction::InputMenu:
_wasMenuKeyPressed = true;
return true;
+ case EventAction::InputInventory:
+ _wasInventoryKeyPressed = true;
+ return true;
}
}
default:
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index 770a0c213bd..798f2e0369b 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -39,6 +39,7 @@ public:
inline bool isMouseRightDown() const { return _isMouseRightDown; }
inline bool isAnyMouseDown() const { return _isMouseLeftDown || _isMouseRightDown; }
inline bool wasMenuKeyPressed() const { return _wasMenuKeyPressed; }
+ inline bool wasInventoryKeyPressed() const { return _wasInventoryKeyPressed; }
inline Common::Point mousePos2D() const { return _mousePos2D; }
inline Common::Point mousePos3D() const { return _mousePos3D; }
const Input &debugInput() const { scumm_assert(_debugInput != nullptr); return *_debugInput; }
@@ -57,7 +58,8 @@ private:
_wasMouseRightReleased = false,
_isMouseLeftDown = false,
_isMouseRightDown = false,
- _wasMenuKeyPressed = false;
+ _wasMenuKeyPressed = false,
+ _wasInventoryKeyPressed = false;
Common::Point
_mousePos2D,
_mousePos3D;
diff --git a/engines/alcachofa/metaengine.cpp b/engines/alcachofa/metaengine.cpp
index 14e1f28a525..f76c34d7143 100644
--- a/engines/alcachofa/metaengine.cpp
+++ b/engines/alcachofa/metaengine.cpp
@@ -38,7 +38,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
GAMEOPTION_HIGH_QUALITY,
{
_s("High Quality"),
- _s("TODO: Explain what this does"),
+ _s("Toggles some optional graphical effects"),
_s("high_quality"),
true,
0,
@@ -49,7 +49,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
GAMEOPTION_32BITS,
{
_s("32 Bits"),
- _s("TODO: Also explain this, and implement it maybe"),
+ _s("Uses 32bit textures instead of 16bit ones (currently not implemented)"),
_s("32_bits"),
true,
0,
@@ -102,6 +102,12 @@ KeymapArray AlcachofaMetaEngine::initKeymaps(const char *target) const {
act->addDefaultInputMapping("JOY_START");
keymap->addAction(act);
+ act = new Action("INVENTORY", _("Inventory"));
+ act->setCustomEngineActionEvent((CustomEventType)EventAction::InputInventory);
+ act->addDefaultInputMapping("SPACE");
+ act->addDefaultInputMapping("JOY_B");
+ keymap->addAction(act);
+
return Keymap::arrayOf(keymap);
}
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index 5e40ccf00df..b79dd84a250 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -28,7 +28,8 @@ namespace Alcachofa {
enum class EventAction {
LoadFromMenu,
- InputMenu
+ InputMenu,
+ InputInventory
};
}
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index e67f8575e86..ac25469d4aa 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -402,8 +402,12 @@ bool Inventory::updateInput() {
-1, true, kWhite, -kForegroundOrderCount + 1);
}
+ const bool userWantsToCloseInventory =
+ closeInventoryTriggerBounds().contains(input.mousePos2D()) ||
+ input.wasMenuKeyPressed() ||
+ input.wasInventoryKeyPressed();
if (!player.activeCharacter()->isBusy() &&
- closeInventoryTriggerBounds().contains(input.mousePos2D()))
+ userWantsToCloseInventory)
close();
if (!player.activeCharacter()->isBusy() &&
Commit: f4bbb6551aa47f4d7acbc2b0203d1db045752b3f
https://github.com/scummvm/scummvm/commit/f4bbb6551aa47f4d7acbc2b0203d1db045752b3f
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Mark typeName methods as override
Changed paths:
engines/alcachofa/objects.h
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index e90353b233b..06bbea56b8b 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -69,7 +69,7 @@ public:
inline Common::Point &position() { return _pos; }
inline Common::Point position() const { return _pos; }
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
Common::Point _pos;
@@ -94,7 +94,7 @@ public:
void freeResources() override;
void syncGame(Common::Serializer &serializer) override;
Graphic *graphic() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
Task *animate(Process &process);
@@ -112,7 +112,7 @@ public:
SpecialEffectObject(Room *room, Common::ReadStream &stream);
void draw() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
static constexpr const float kShiftSpeed = 1 / 256.0f;
@@ -137,7 +137,7 @@ public:
virtual void onHoverEnd();
virtual void onHoverUpdate();
virtual void onClick();
- virtual const char *typeName() const;
+ const char *typeName() const override;
void markSelected();
protected:
@@ -155,7 +155,7 @@ private:
class PhysicalObject : public ShapeObject {
public:
PhysicalObject(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class MenuButton : public PhysicalObject {
@@ -174,7 +174,7 @@ public:
void onHoverUpdate() override;
void onClick() override;
virtual void trigger();
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
bool
@@ -198,7 +198,7 @@ public:
static constexpr const char *kClassName = "CBotonMenuInternet";
InternetMenuButton(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class OptionsMenuButton final : public MenuButton {
@@ -208,7 +208,7 @@ public:
void update() override;
void trigger() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class MainMenuButton final : public MenuButton {
@@ -218,7 +218,7 @@ public:
void update() override;
void trigger() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class PushButton final : public PhysicalObject {
@@ -226,7 +226,7 @@ public:
static constexpr const char *kClassName = "CPushButton";
PushButton(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
bool _alwaysVisible;
@@ -239,7 +239,7 @@ public:
static constexpr const char *kClassName = "CEditBox";
EditBox(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
int32 i1;
@@ -266,7 +266,7 @@ public:
void onHoverUpdate() override;
void onClick() override;
virtual void trigger();
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
bool
@@ -293,7 +293,7 @@ public:
void update() override;
void loadResources() override;
void freeResources() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
bool isMouseOver() const;
@@ -312,7 +312,7 @@ public:
static constexpr const char *kClassName = "CCheckBoxAutoAjustarRuido";
CheckBoxAutoAdjustNoise(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class IRCWindow final : public ObjectBase {
@@ -320,7 +320,7 @@ public:
static constexpr const char *kClassName = "CVentanaIRC";
IRCWindow(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
Common::Point _p1, _p2;
@@ -332,7 +332,7 @@ public:
MessageBox(Room *room, Common::ReadStream &stream);
~MessageBox() override = default;
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
Graphic
@@ -348,7 +348,7 @@ public:
static constexpr const char *kClassName = "CVuMeter";
VoiceMeter(Room *room, Common::ReadStream &stream);
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class Item : public GraphicObject {
@@ -358,7 +358,7 @@ public:
Item(const Item &other);
void draw() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
void trigger();
};
@@ -388,7 +388,7 @@ public:
void onClick() override;
void trigger(const char *action) override;
void toggle(bool isEnabled) override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
Common::String _relatedObject;
@@ -406,7 +406,7 @@ public:
virtual CursorType cursorType() const override;
void onClick() override;
void trigger(const char *action) override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
Common::String _targetRoom, _targetObject;
@@ -429,7 +429,7 @@ public:
Graphic *graphic() override;
void onClick() override;
void trigger(const char *action) override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
Task *sayText(Process &process, int32 dialogId);
void resetTalking();
@@ -480,7 +480,7 @@ public:
const char *activateAction = nullptr);
void stopWalking(Direction direction = Direction::Invalid);
void setPosition(Common::Point target);
- virtual const char *typeName() const;
+ const char *typeName() const override;
Task *waitForArrival(Process &process);
@@ -541,7 +541,7 @@ public:
void update() override;
void draw() override;
void syncGame(Common::Serializer &serializer) override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
virtual void walkTo(
Common::Point target,
Direction endDirection = Direction::Invalid,
@@ -581,7 +581,7 @@ private:
class Background final : public GraphicObject {
public:
Background(Room *room, const Common::String &animationFileName, int16 scale);
- virtual const char *typeName() const;
+ const char *typeName() const override;
};
class FloorColor final : public ObjectBase {
@@ -593,7 +593,7 @@ public:
void update() override;
void drawDebug() override;
Shape *shape() override;
- virtual const char *typeName() const;
+ const char *typeName() const override;
private:
FloorColorShape _shape;
Commit: 1ec9365bd2bc56da1ffa5d9b9e6b50466668ac3c
https://github.com/scummvm/scummvm/commit/1ec9365bd2bc56da1ffa5d9b9e6b50466668ac3c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Fix compilation warnings and CI errors
Changed paths:
engines/alcachofa/alcachofa.h
engines/alcachofa/debug.h
engines/alcachofa/graphics.h
engines/alcachofa/module.mk
engines/alcachofa/objects.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index 6f828076d13..df2a6e224f1 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -130,7 +130,7 @@ public:
uint32 getMillis() const;
void setMillis(uint32 newMillis);
- virtual void pauseEngineIntern(bool pause);
+ void pauseEngineIntern(bool pause) override;
void playVideo(int32 videoId);
void fadeExit();
void setDebugMode(DebugMode debugMode, int32 param);
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index 561b3683f09..abe6cc8ff02 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -93,7 +93,6 @@ private:
void drawIntersectionsFor(const Polygon &polygon, IDebugRenderer *renderer) {
auto &camera = g_engine->camera();
- auto mousePos2D = g_engine->input().debugInput().mousePos2D();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
for (uint i = 0; i < polygon._points.size(); i++)
{
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 3c547dd9ee8..cd6c681d7a8 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -469,7 +469,7 @@ private:
static constexpr const uint kMaxDrawRequestsPerOrder = 50;
IRenderer *const _renderer;
BumpAllocator _allocator;
- IDrawRequest *_requestsPerOrder[kOrderCount][kMaxDrawRequestsPerOrder] = { 0 };
+ IDrawRequest *_requestsPerOrder[kOrderCount][kMaxDrawRequestsPerOrder] = { { 0 } };
uint8 _requestsPerOrderCount[kOrderCount] = { 0 };
float _lodBiasPerOrder[kOrderCount] = { 0 };
};
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 574ac87bd65..71f29b4833f 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS = \
common.o \
console.o \
game.o \
+ game-movie-adventure.o \
game-objects.o \
general-objects.o \
global-ui.o \
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 06bbea56b8b..b539fc24fb3 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -365,6 +365,7 @@ public:
class ITriggerableObject {
public:
ITriggerableObject(Common::ReadStream &stream);
+ virtual ~ITriggerableObject() = default;
inline Direction interactionDirection() const { return _interactionDirection; }
inline Common::Point interactionPoint() const { return _interactionPoint; }
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index b9feb436154..a6aa9514df9 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -79,6 +79,12 @@ void Task::syncObjectAsString(Serializer &s, ObjectBase *&object, bool optional)
objectName.c_str(), roomName.c_str(), taskName());
}
+void Task::errorForUnexpectedObjectType(const ObjectBase *base) const {
+ // Implemented as separate function in order to access ObjectBase methods
+ error("Unexpected type of object %s in savestate for task %s (got a %s)",
+ base->name().c_str(), taskName(), base->typeName());
+}
+
DelayTask::DelayTask(Process &process, uint32 millis)
: Task(process)
, _endTime(millis) {
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 9a8fab8d396..2366a631d25 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -103,12 +103,13 @@ protected:
syncObjectAsString(s, base, optional);
object = dynamic_cast<TObject*>(base);
if (object == nullptr && base != nullptr)
- error("Unexpected type of object %s in savestate for task %s (got a %s)",
- base->name().c_str(), taskName(), base->typeName());
+ errorForUnexpectedObjectType(base);
}
uint32 _stage = 0;
private:
+ void errorForUnexpectedObjectType(const ObjectBase *base) const;
+
Process &_process;
};
@@ -132,19 +133,12 @@ private:
return #TaskName; \
}
-#if __cplusplus >= 201703L
-#define TASK_BREAK_FALLTHROUGH [[fallthrough]];
-#else
-#define TASK_BREAK_FALLTHROUGH
-#endif
-
#define TASK_BEGIN \
switch(_stage) { \
case 0:; \
#define TASK_END \
TASK_RETURN(0); \
- TASK_BREAK_FALLTHROUGH \
default: assert(false && "Invalid line in task"); \
} return TaskReturn::finish(0)
@@ -152,7 +146,6 @@ private:
do { \
_stage = stage; \
return ret; \
- TASK_BREAK_FALLTHROUGH \
case stage:; \
} while(0)
@@ -161,8 +154,8 @@ private:
#define TASK_RETURN(value) \
do { \
- return TaskReturn::finish(value); \
_stage = UINT_MAX; \
+ return TaskReturn::finish(value); \
} while(0)
using ProcessId = uint32;
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 02380d7a5d0..08570714586 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -61,7 +61,7 @@ public:
inline FakeSemaphore &musicSemaphore() { return _musicSemaphore; }
private:
- struct Playback {;
+ struct Playback {
void fadeOut(uint32 duration);
Audio::SoundHandle _handle;
Commit: a22af48933d3e99e1a38e9ab0ab7be19367d1f64
https://github.com/scummvm/scummvm/commit/a22af48933d3e99e1a38e9ab0ab7be19367d1f64
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Fix additional CI errors
Changed paths:
engines/alcachofa/common.cpp
engines/alcachofa/console.cpp
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 2ff0e05fad0..ec8f0fc565e 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -49,7 +49,8 @@ void FakeSemaphore::sync(Serializer &s, FakeSemaphore &semaphore) {
// if we are still holding locks during loading these locks will
// try to decrease the counter which will fail, let's find this out already here
assert(s.isSaving() || semaphore.isReleased());
- (void)(s, semaphore);
+ (void)s;
+ (void)semaphore;
// We should not actually serialize the counter, just make sure it is empty
// When the locks are loaded, they will increase the counter themselves
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index c0489bf8ab7..b2fa6b98f15 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -183,9 +183,9 @@ bool Console::cmdItem(int argc, const char **args) {
const char *itemName = args[1];
if (argc == 3) {
itemName = args[2];
- if (strcmpi(args[1], "mortadelo") == 0 || strcmpi(args[1], "m") == 0)
+ if (scumm_stricmp(args[1], "mortadelo") == 0 || scumm_stricmp(args[1], "m") == 0)
active = &mortadelo;
- else if (strcmpi(args[1], "filemon") == 0 || strcmpi(args[1], "f") == 0)
+ else if (scumm_stricmp(args[1], "filemon") == 0 || scumm_stricmp(args[1], "f") == 0)
active = &filemon;
else {
debugPrintf("Invalid character name \"%s\", has to be either \"mortadelo\" or \"filemon\"\n", args[1]);
Commit: 6cd7965a9d7acf4c0c36c4c9f9ed64f15c98c401
https://github.com/scummvm/scummvm/commit/6cd7965a9d7acf4c0c36c4c9f9ed64f15c98c401
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Fix even more CI warnings
Changed paths:
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/input.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/script.cpp
engines/alcachofa/shape.cpp
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index b8c4104a923..14b40a0954d 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -50,7 +50,7 @@ class GameMovieAdventure : public Game {
return Game::shouldCharacterTrigger(character, action);
}
- virtual bool shouldTriggerDoor(const Door *door) {
+ bool shouldTriggerDoor(const Door *door) override {
// An invalid door target, the character will go to the door and then ignore it (also in original engine)
if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
return false;
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 1cd4efd2ce6..fae918c52ea 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -85,7 +85,7 @@ public:
GL_CALL(glDeleteTextures(1, &_handle));
}
- virtual void update(const Surface &surface) {
+ void update(const Surface &surface) override {
OpenGLFormat format = getOpenGLFormatOf(surface.format);
assert(surface.w == size().x && surface.h == size().y);
assert(format.isValid());
@@ -301,7 +301,7 @@ public:
return _currentOutput != nullptr;
}
- virtual void quad(
+ void quad(
Vector2d topLeft,
Vector2d size,
Color color,
@@ -353,7 +353,7 @@ public:
#endif
}
- virtual void debugPolygon(
+ void debugPolygon(
Span<Vector2d> points,
Color color
) override {
@@ -379,7 +379,7 @@ public:
GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
}
- virtual void debugPolyline(
+ void debugPolyline(
Span<Vector2d> points,
Color color
) override {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 6f12ea7133e..d29ff6ab6ce 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -212,7 +212,7 @@ void AnimationBase::fullBlend(const ManagedSurface &source, ManagedSurface &dest
assert(offsetX >= 0 && offsetX + source.w <= destination.w);
assert(offsetY >= 0 && offsetY + source.h <= destination.h);
- const byte *sourceLine = (byte *)source.getPixels();
+ const byte *sourceLine = (const byte *)source.getPixels();
byte *destinationLine = (byte *)destination.getPixels() + offsetY * destination.pitch + offsetX * 4;
for (int y = 0; y < source.h; y++) {
const byte *sourcePixel = sourceLine;
@@ -624,6 +624,7 @@ AnimationDrawRequest::AnimationDrawRequest(Animation *animation, int32 frameI, V
}
void AnimationDrawRequest::draw() {
+ g_engine->renderer().setLodBias(_lodBias);
if (_is3D)
_animation->draw3D(_frameI, _topLeft, _scale * kInvBaseScale, _blendMode, _color);
else
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index 41e10b91176..00245b6c6a8 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -78,6 +78,8 @@ bool Input::handleEvent(const Common::Event &event) {
case EventAction::InputInventory:
_wasInventoryKeyPressed = true;
return true;
+ default:
+ return false;
}
}
default:
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index ac25469d4aa..09d044f18fa 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -113,7 +113,7 @@ Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
stream.seek(objectEnd, SEEK_SET);
}
else if (stream.pos() > objectEnd) // this is probably not recoverable
- error("Read past the object data (%u > %dll) in room %s", objectEnd, stream.pos(), _name.c_str());
+ error("Read past the object data (%u > %lld) in room %s", objectEnd, stream.pos(), _name.c_str());
if (object != nullptr)
_objects.push_back(object);
@@ -388,8 +388,8 @@ bool Inventory::updateInput() {
player.drawCursor(0);
if (hoveredItem != nullptr && !player.activeCharacter()->isBusy()) {
- if (input.wasMouseLeftPressed() && player.heldItem() == nullptr ||
- input.wasMouseLeftReleased() && player.heldItem() != nullptr ||
+ if ((input.wasMouseLeftPressed() && player.heldItem() == nullptr) ||
+ (input.wasMouseLeftReleased() && player.heldItem() != nullptr) ||
input.wasMouseRightReleased()) {
hoveredItem->trigger();
player.pressedObject() = nullptr;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 861b8c61ee5..22485121496 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -597,11 +597,11 @@ private:
return TaskReturn::finish(1);
}
case ScriptKernelTask::ChangeRoom:
- if (strcmpi(getStringArg(0), "SALIR") == 0) {
+ if (scumm_stricmp(getStringArg(0), "SALIR") == 0) {
g_engine->quitGame();
g_engine->player().changeRoom("SALIR", true);
}
- else if (strcmpi(getStringArg(0), "MENUPRINCIPALINICIO") == 0)
+ else if (scumm_stricmp(getStringArg(0), "MENUPRINCIPALINICIO") == 0)
warning("STUB: change room to MenuPrincipalInicio special case");
else {
auto targetRoom = g_engine->world().getRoomByName(getStringArg(0));
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 71b847550a6..c64c618f8b6 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -30,16 +30,14 @@ static int sideOfLine(Point a, Point b, Point q) {
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
}
+static bool lineIntersects(Point a1, Point b1, Point a2, Point b2) {
+ return (sideOfLine(a1, b1, a2) > 0) != (sideOfLine(a1, b1, b2) > 0);
+}
+
static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
// as there are a number of special cases to consider,
// this method is a direct translation of the original engine
- const auto sideOfLine = [](Point a, Point b, const Point q) {
- return Alcachofa::sideOfLine(a, b, q) > 0;
- };
- const auto lineIntersects = [&](Point a1, Point b1, Point a2, Point b2) {
- return sideOfLine(a1, b1, a2) != sideOfLine(a1, b1, b2);
- };
-
+ // rather than using common Math:: code
if (a2.x > b2.x) {
if (a1.x > b1.x)
return lineIntersects(b1, a1, b2, a2) && lineIntersects(b2, a2, b1, a1);
@@ -250,9 +248,9 @@ Color FloorColorPolygon::colorAt(Point query) const {
Shape::Shape() {}
Shape::Shape(ReadStream &stream) {
- auto complexity = stream.readByte();
+ byte complexity = stream.readByte();
uint8 pointsPerPolygon;
- if (complexity < 0 || complexity > 3)
+ if (complexity > 3)
error("Invalid shape complexity %d", complexity);
else if (complexity == 3)
pointsPerPolygon = 0; // read in per polygon
@@ -346,10 +344,10 @@ PathFindingShape::PathFindingShape(ReadStream &stream) {
_pointDepths.reserve(polygonCount * kPointsPerPolygon);
for (int i = 0; i < polygonCount; i++) {
- for (int j = 0; j < kPointsPerPolygon; j++)
+ for (uint j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(stream));
_polygonOrders.push_back(stream.readSByte());
- for (int j = 0; j < kPointsPerPolygon; j++)
+ for (uint j = 0; j < kPointsPerPolygon; j++)
_pointDepths.push_back(stream.readByte());
uint pointCount = addPolygon(kPointsPerPolygon);
@@ -636,16 +634,16 @@ FloorColorShape::FloorColorShape(ReadStream &stream) {
_pointColors.reserve(polygonCount * kPointsPerPolygon);
for (int i = 0; i < polygonCount; i++) {
- for (int j = 0; j < kPointsPerPolygon; j++)
+ for (uint j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(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
int firstColorI = _pointColors.size();
_pointColors.resize(_pointColors.size() + kPointsPerPolygon);
- for (int j = 0; j < kPointsPerPolygon; j++)
+ for (uint j = 0; j < kPointsPerPolygon; j++)
_pointColors[firstColorI + j].a = (uint8)MIN(255, stream.readByte() * 255 / 100);
- for (int j = 0; j < kPointsPerPolygon; j++) {
+ for (uint j = 0; j < kPointsPerPolygon; j++) {
_pointColors[firstColorI + j].r = stream.readByte();
_pointColors[firstColorI + j].g = stream.readByte();
_pointColors[firstColorI + j].b = stream.readByte();
Commit: 344d8c9944868bd263f7aaeaaa059012fd530773
https://github.com/scummvm/scummvm/commit/344d8c9944868bd263f7aaeaaa059012fd530773
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Remove redundant semicolon after DECLARE_TASK
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/player.cpp
engines/alcachofa/scheduler.cpp
engines/alcachofa/script.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 1b89a38c45c..3d0ddcaaaf2 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -351,7 +351,7 @@ protected:
Vector3d _fromPos, _deltaPos;
};
-DECLARE_TASK(CamLerpPosTask);
+DECLARE_TASK(CamLerpPosTask)
struct CamLerpScaleTask final : public CamLerpTask {
CamLerpScaleTask(Process &process, float targetScale, int32 duration, EasingType easingType)
@@ -379,7 +379,7 @@ protected:
float _fromScale = 0, _deltaScale = 0;
};
-DECLARE_TASK(CamLerpScaleTask);
+DECLARE_TASK(CamLerpScaleTask)
struct CamLerpPosScaleTask final : public CamLerpTask {
CamLerpPosScaleTask(Process &process,
@@ -421,7 +421,7 @@ protected:
float _fromScale = 0, _deltaScale = 0;
EasingType _moveEasingType = {}, _scaleEasingType = {};
};
-DECLARE_TASK(CamLerpPosScaleTask);
+DECLARE_TASK(CamLerpPosScaleTask)
struct CamLerpRotationTask final : public CamLerpTask {
CamLerpRotationTask(Process &process, float targetRotation, int32 duration, EasingType easingType)
@@ -449,7 +449,7 @@ protected:
float _fromRotation = 0, _deltaRotation = 0;
};
-DECLARE_TASK(CamLerpRotationTask);
+DECLARE_TASK(CamLerpRotationTask)
static void syncVector(Serializer &s, Vector2d &v) {
float *data = v.getData();
@@ -488,7 +488,7 @@ protected:
Vector2d _amplitude, _frequency;
};
-DECLARE_TASK(CamShakeTask);
+DECLARE_TASK(CamShakeTask)
struct CamWaitToStopTask final : public Task {
CamWaitToStopTask(Process &process)
@@ -516,7 +516,7 @@ struct CamWaitToStopTask final : public Task {
private:
Camera &_camera;
};
-DECLARE_TASK(CamWaitToStopTask);
+DECLARE_TASK(CamWaitToStopTask)
struct CamSetInactiveAttributeTask final : public Task {
enum Attribute {
@@ -583,7 +583,7 @@ private:
float _value = 0;
int32 _delay = 0;
};
-DECLARE_TASK(CamSetInactiveAttributeTask);
+DECLARE_TASK(CamSetInactiveAttributeTask)
Task *Camera::lerpPos(Process &process,
Vector2d targetPos,
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 9a4f47128eb..25f85320477 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -357,7 +357,7 @@ private:
int32 _dialogId = 0;
SoundHandle _soundHandle = {};
};
-DECLARE_TASK(SayTextTask);
+DECLARE_TASK(SayTextTask)
Task *Character::sayText(Process &process, int32 dialogId) {
return new SayTextTask(process, this, dialogId);
@@ -435,7 +435,7 @@ private:
ObjectBase *_animateObject = nullptr;
Graphic *_graphic = nullptr;
};
-DECLARE_TASK(AnimateCharacterTask);
+DECLARE_TASK(AnimateCharacterTask)
Task *Character::animate(Process &process, ObjectBase *animateObject) {
assert(animateObject != nullptr);
@@ -492,7 +492,7 @@ private:
float _sourceLodBias = 0, _targetLodBias = 0;
uint32 _startTime = 0, _durationMs = 0;
};
-DECLARE_TASK(LerpLodBiasTask);
+DECLARE_TASK(LerpLodBiasTask)
Task *Character::lerpLodBias(Process &process, float targetLodBias, int32 durationMs) {
return new LerpLodBiasTask(process, this, targetLodBias, durationMs);
@@ -804,7 +804,7 @@ struct ArriveTask : public Task {
private:
const WalkingCharacter *_character = nullptr;
};
-DECLARE_TASK(ArriveTask);
+DECLARE_TASK(ArriveTask)
Task *WalkingCharacter::waitForArrival(Process &process) {
return new ArriveTask(process, this);
@@ -1129,7 +1129,7 @@ private:
MainCharacter *_character = nullptr;
uint _clickedLineI = UINT_MAX;
};
-DECLARE_TASK(DialogMenuTask);
+DECLARE_TASK(DialogMenuTask)
void MainCharacter::addDialogLine(int32 dialogId) {
assert(dialogId >= 0);
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 7d33e19838d..799382d8019 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -202,7 +202,7 @@ private:
Graphic *_graphic = nullptr;
uint32 _duration = 0;
};
-DECLARE_TASK(AnimateTask);
+DECLARE_TASK(AnimateTask)
Task *GraphicObject::animate(Process &process) {
return new AnimateTask(process, this);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index bec45d35f52..334cf5c4d28 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -246,7 +246,7 @@ private:
int32 _dialogId = 0;
uint32 _startTime = 0, _durationMs = 0;
};
-DECLARE_TASK(CenterBottomTextTask);
+DECLARE_TASK(CenterBottomTextTask)
Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs) {
return new CenterBottomTextTask(process, dialogId, durationMs);
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index d29ff6ab6ce..d9d9d13e7bb 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -864,7 +864,7 @@ private:
int8 _order = 0;
PermanentFadeAction _permanentFadeAction = {};
};
-DECLARE_TASK(FadeTask);
+DECLARE_TASK(FadeTask)
Task *fade(Process &process, FadeType fadeType,
float from, float to,
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 3af856cacb7..d09a05a6d78 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -286,7 +286,7 @@ private:
MainCharacter *_character = nullptr;
Player &_player;
};
-DECLARE_TASK(DoorTask);
+DECLARE_TASK(DoorTask)
void Player::triggerDoor(const Door *door) {
_heldItem = nullptr;
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index a6aa9514df9..3a0c194c12e 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -113,7 +113,7 @@ void DelayTask::syncGame(Serializer &s) {
s.syncAsUint32LE(_endTime);
}
-DECLARE_TASK(DelayTask);
+DECLARE_TASK(DelayTask)
Process::Process(ProcessId pid, MainCharacterKind characterKind)
: _pid(pid)
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 22485121496..79c3ec5eba3 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -174,7 +174,7 @@ private:
int32 _durationSec = 0;
int32 _result = 1;
};
-DECLARE_TASK(ScriptTimerTask);
+DECLARE_TASK(ScriptTimerTask)
enum class StackEntryType {
Number,
@@ -950,7 +950,7 @@ private:
bool _isFirstExecution = true;
FakeLock _lock;
};
-DECLARE_TASK(ScriptTask);
+DECLARE_TASK(ScriptTask)
Process *Script::createProcess(MainCharacterKind character, const String &behavior, const String &action, ScriptFlags flags) {
return createProcess(character, behavior + '/' + action, flags);
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 3c141ca2fca..57edccce836 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -362,7 +362,7 @@ void PlaySoundTask::debugPrint() {
g_engine->console().debugPrintf("PlaySound %u\n", _soundHandle);
}
-DECLARE_TASK(PlaySoundTask);
+DECLARE_TASK(PlaySoundTask)
WaitForMusicTask::WaitForMusicTask(Process &process)
: Task(process)
@@ -385,6 +385,6 @@ void WaitForMusicTask::debugPrint() {
g_engine->console().debugPrintf("WaitForMusic\n");
}
-DECLARE_TASK(WaitForMusicTask);
+DECLARE_TASK(WaitForMusicTask)
}
Commit: 83c49b1a9508e711a52344ff95688fddd4c8334d
https://github.com/scummvm/scummvm/commit/83c49b1a9508e711a52344ff95688fddd4c8334d
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:58+02:00
Commit Message:
ALCACHOFA: Fix two CI warnings
Changed paths:
engines/alcachofa/rooms.cpp
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 09d044f18fa..ee4eb3fcf9c 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -113,7 +113,7 @@ Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
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, stream.pos(), _name.c_str());
+ error("Read past the object data (%u > %ld) in room %s", objectEnd, stream.pos(), _name.c_str());
if (object != nullptr)
_objects.push_back(object);
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 57edccce836..f13b8ca0188 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -359,7 +359,8 @@ TaskReturn PlaySoundTask::run() {
}
void PlaySoundTask::debugPrint() {
- g_engine->console().debugPrintf("PlaySound %u\n", _soundHandle);
+ // unfortunately SoundHandle is not castable to something we could display here safely
+ g_engine->console().debugPrintf("PlaySound\n");
}
DECLARE_TASK(PlaySoundTask)
Commit: 8a2c772bcc9fc4bf6edfaac499802666bed9eace
https://github.com/scummvm/scummvm/commit/8a2c772bcc9fc4bf6edfaac499802666bed9eace
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Remove remaining virtual keyword on overridden methods
Changed paths:
engines/alcachofa/camera.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 3d0ddcaaaf2..d611a2a9d04 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -342,7 +342,7 @@ struct CamLerpPosTask final : public CamLerpTask {
syncVector(s, _deltaPos);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
protected:
void update(float t) override {
@@ -370,7 +370,7 @@ struct CamLerpScaleTask final : public CamLerpTask {
s.syncAsFloatLE(_deltaScale);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
protected:
void update(float t) override {
@@ -409,7 +409,7 @@ struct CamLerpPosScaleTask final : public CamLerpTask {
syncEnum(s, _scaleEasingType);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
protected:
void update(float t) override {
@@ -440,7 +440,7 @@ struct CamLerpRotationTask final : public CamLerpTask {
s.syncAsFloatLE(_deltaRotation);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
protected:
void update(float t) override {
@@ -474,7 +474,7 @@ struct CamShakeTask final : public CamLerpTask {
syncVector(s, _frequency);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
protected:
void update(float t) override {
@@ -511,7 +511,7 @@ struct CamWaitToStopTask final : public Task {
g_engine->console().debugPrintf("Wait for camera to stop moving\n");
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
Camera &_camera;
@@ -575,7 +575,7 @@ struct CamSetInactiveAttributeTask final : public Task {
s.syncAsSint32LE(_delay);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
Camera &_camera;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 25f85320477..bbac1210204 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -350,7 +350,7 @@ struct SayTextTask final : public Task {
s.syncAsSint32LE(_dialogId);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
Character *_character = nullptr;
@@ -428,7 +428,7 @@ struct AnimateCharacterTask final : public Task {
scumm_assert(_graphic != nullptr);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
Character *_character = nullptr;
@@ -485,7 +485,7 @@ struct LerpLodBiasTask final : public Task {
s.syncAsUint32LE(_durationMs);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
Character *_character = nullptr;
@@ -800,7 +800,7 @@ struct ArriveTask : public Task {
syncObjectAsString(s, _character);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
const WalkingCharacter *_character = nullptr;
};
@@ -1083,7 +1083,7 @@ struct DialogMenuTask : public Task {
s.syncAsUint32LE(_clickedLineI);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
static constexpr int kTextXOffset = 5;
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 799382d8019..3bcc889d587 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -195,7 +195,7 @@ struct AnimateTask : public Task {
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
GraphicObject *_object = nullptr;
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 334cf5c4d28..f7b531f277d 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -240,7 +240,7 @@ struct CenterBottomTextTask : public Task {
s.syncAsUint32LE(_durationMs);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
int32 _dialogId = 0;
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index d9d9d13e7bb..a275ed99848 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -850,7 +850,7 @@ struct FadeTask : public Task {
s.syncAsSByte(_order);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
void draw(float t) {
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index b539fc24fb3..65210acc974 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -404,7 +404,7 @@ public:
inline const Common::String &targetObject() const { return _targetObject; }
inline Direction characterDirection() const { return _characterDirection; }
- virtual CursorType cursorType() const override;
+ CursorType cursorType() const override;
void onClick() override;
void trigger(const char *action) override;
const char *typeName() const override;
@@ -543,7 +543,7 @@ public:
void draw() override;
void syncGame(Common::Serializer &serializer) override;
const char *typeName() const override;
- virtual void walkTo(
+ void walkTo(
Common::Point target,
Direction endDirection = Direction::Invalid,
ITriggerableObject *activateObject = nullptr,
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index d09a05a6d78..65f5c2bba05 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -210,7 +210,7 @@ struct DoorTask : public Task {
syncGame(s);
}
- virtual TaskReturn run() {
+ TaskReturn run() override {
TASK_BEGIN;
if (_targetRoom == nullptr || _targetObject == nullptr)
return TaskReturn::finish(1);
@@ -240,7 +240,7 @@ struct DoorTask : public Task {
TASK_END;
}
- virtual void debugPrint() {
+ void debugPrint() override {
g_engine->console().debugPrintf("%s\n", process().name().c_str());
}
@@ -259,7 +259,7 @@ struct DoorTask : public Task {
findTarget();
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
void findTarget() {
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 2366a631d25..c8131e3fefa 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -119,7 +119,7 @@ struct DelayTask : public Task {
TaskReturn run() override;
void debugPrint() override;
void syncGame(Common::Serializer &s) override;
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
uint32 _endTime = 0;
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 79c3ec5eba3..4ccd9eecc2e 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -168,7 +168,7 @@ struct ScriptTimerTask : public Task {
s.syncAsSint32LE(_result);
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
int32 _durationSec = 0;
@@ -367,7 +367,7 @@ struct ScriptTask : public Task {
}
}
- virtual void debugPrint() {
+ void debugPrint() override {
g_engine->getDebugger()->debugPrintf("\"%s\" at %u\n", _name.c_str(), _pc);
}
@@ -396,7 +396,7 @@ struct ScriptTask : public Task {
_lock = FakeLock("script", g_engine->player().semaphoreFor(process().character()));
}
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
void setCharacterVariables() {
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 08570714586..99978cdfe45 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -88,7 +88,7 @@ struct PlaySoundTask final : public Task {
PlaySoundTask(Process &process, Common::Serializer &s);
TaskReturn run() override;
void debugPrint() override;
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
SoundHandle _soundHandle;
};
@@ -98,7 +98,7 @@ struct WaitForMusicTask final : public Task {
WaitForMusicTask(Process &process, Common::Serializer &s);
TaskReturn run() override;
void debugPrint() override;
- virtual const char *taskName() const override;
+ const char *taskName() const override;
private:
FakeLock _lock;
};
Commit: 395f1d91114d96e4380b8353b6b393cc19437fb6
https://github.com/scummvm/scummvm/commit/395f1d91114d96e4380b8353b6b393cc19437fb6
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix MSVC Analysis warnings
Changed paths:
engines/alcachofa/rooms.cpp
engines/alcachofa/shape.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index ee4eb3fcf9c..956b2a8dd9a 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -113,7 +113,7 @@ Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
stream.seek(objectEnd, SEEK_SET);
}
else if (stream.pos() > objectEnd) // this is probably not recoverable
- error("Read past the object data (%u > %ld) in room %s", objectEnd, 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);
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index c64c618f8b6..494b36cd654 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -466,12 +466,12 @@ void PathFindingShape::initializeFloydWarshall() {
for (const auto &linkPolygon : _linkIndices) {
for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
LinkIndex linkFrom = linkPolygon._points[i / 2];
- int32 linkFromI = i % 2 ? linkFrom.second : linkFrom.first;
+ int32 linkFromI = (i % 2) ? linkFrom.second : linkFrom.first;
if (linkFromI < 0)
continue;
for (uint j = i + 1; j < 2 * kPointsPerPolygon; j++) {
LinkIndex linkTo = linkPolygon._points[j / 2];
- int32 linkToI = j % 2 ? linkTo.second : linkTo.first;
+ int32 linkToI = (j % 2) ? linkTo.second : linkTo.first;
if (linkToI >= 0) {
const int32 linkFromFullI = linkFromI * _linkPoints.size() + linkToI;
const int32 linkToFullI = linkToI * _linkPoints.size() + linkFromI;
@@ -571,14 +571,14 @@ void PathFindingShape::floydWarshallPath(
const auto &toIndices = _linkIndices[toContaining];
for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
const auto &curFromPoint = fromIndices._points[i / 2];
- int32 curFromLink = i % 2 ? curFromPoint.second : curFromPoint.first;
+ int32 curFromLink = (i % 2) ? curFromPoint.second : curFromPoint.first;
if (curFromLink < 0)
continue;
uint curFromDistance = (uint)sqrtf(from.sqrDist(_linkPoints[curFromLink]) + 0.5f);
for (uint j = 0; j < 2 * kPointsPerPolygon; j++) {
const auto &curToPoint = toIndices._points[j / 2];
- int32 curToLink = j % 2 ? curToPoint.second : curToPoint.first;
+ int32 curToLink = (j % 2) ? curToPoint.second : curToPoint.first;
if (curToLink < 0)
continue;
uint totalDistance =
Commit: 42fe7869d8dee2e475177523f30a205ba5e90c09
https://github.com/scummvm/scummvm/commit/42fe7869d8dee2e475177523f30a205ba5e90c09
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix configure.engine
Changed paths:
engines/alcachofa/configure.engine
engines/alcachofa/graphics-opengl.cpp
diff --git a/engines/alcachofa/configure.engine b/engines/alcachofa/configure.engine
index a5decb9ba59..6e58d75f3ec 100644
--- a/engines/alcachofa/configure.engine
+++ b/engines/alcachofa/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine alcachofa "Alcachofa" no "" "" ""
+add_engine alcachofa "Alcachofa" no "" "" "highres opengl_game_classic mpeg2 3d"
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index fae918c52ea..befaa7b35e0 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -26,12 +26,7 @@
#include "engines/util.h"
#include "graphics/managed_surface.h"
#include "graphics/opengl/system_headers.h"
-
-#ifdef ALCACHOFA_DEBUG_OPENGL // clearing OpenGL errors are very slow, so we only activate the debugging wrapper if necessary
#include "graphics/opengl/debug.h"
-#else
-#define GL_CALL(call) call
-#endif
using namespace Common;
using namespace Math;
Commit: 4a1b0a647372e565ba64d9e85af47e34f9e7c829
https://github.com/scummvm/scummvm/commit/4a1b0a647372e565ba64d9e85af47e34f9e7c829
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix black thumbnail after saving in-game
Changed paths:
engines/alcachofa/menu.cpp
engines/alcachofa/menu.h
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 194d3d4b445..e7988c15f80 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -91,7 +91,7 @@ void Menu::updateOpeningMenu() {
_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();
- updateSelectedSavefile();
+ updateSelectedSavefile(false);
g_engine->player().heldItem() = nullptr;
g_engine->scheduler().backupContext();
@@ -106,7 +106,7 @@ static int parseSavestateSlot(const String &filename) {
return atoi(filename.c_str() + filename.size() - 3);
}
-void Menu::updateSelectedSavefile() {
+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);
@@ -118,7 +118,11 @@ void Menu::updateSelectedSavefile() {
getButton("ANTERIOR")->toggle(_selectedSavefileI > 0);
getButton("SIGUIENTE")->toggle(isOldSavefile);
- if (isOldSavefile) {
+ if (hasJustSaved) {
+ // we just saved in-game so we also still have the correct thumbnail in memory
+ _selectedThumbnail.copyFrom(_bigThumbnail);
+ }
+ else if (isOldSavefile) {
if (!tryReadOldSavefile()) {
_selectedSavefileDescription = String::format("Savestate %d",
parseSavestateSlot(_savefiles[_selectedSavefileI]));
@@ -126,6 +130,7 @@ void Menu::updateSelectedSavefile() {
}
}
else {
+ // the unsaved gamestate is shown as grayscale
_selectedThumbnail.copyFrom(_bigThumbnail);
convertToGrayscale(_selectedThumbnail);
}
@@ -200,13 +205,13 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
case MainMenuAction::NextSave:
if (_selectedSavefileI < _savefiles.size()) {
_selectedSavefileI++;
- updateSelectedSavefile();
+ updateSelectedSavefile(false);
}
break;
case MainMenuAction::PrevSave:
if (_selectedSavefileI > 0) {
_selectedSavefileI--;
- updateSelectedSavefile();
+ updateSelectedSavefile(false);
}
break;
case MainMenuAction::NewGame:
@@ -232,10 +237,9 @@ void Menu::triggerLoad() {
}
void Menu::triggerSave() {
- String fileName, desc;
+ String fileName;
if (_selectedSavefileI < _savefiles.size()) {
fileName = _savefiles[_selectedSavefileI]; // overwrite a previous save
- desc = _selectedSavefileDescription;
}
else {
// for a new savefile we figure out the next slot index
@@ -243,7 +247,7 @@ void Menu::triggerSave() {
? 1 // start at one to keep autosave alone
: parseSavestateSlot(_savefiles.back()) + 1;
fileName = g_engine->getSaveStateName(nextSlot);
- desc = String::format("Savestate %d", nextSlot);
+ _selectedSavefileDescription = String::format("Savestate %d", nextSlot);
}
Error error(kNoError);
@@ -253,9 +257,9 @@ void Menu::triggerSave() {
else
error = g_engine->saveGameStream(savefile.get());
if (error.getCode() == kNoError) {
- g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), desc, false);
+ g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), _selectedSavefileDescription, false);
_savefiles.push_back(fileName);
- updateSelectedSavefile();
+ updateSelectedSavefile(true);
}
else {
GUI::MessageDialog dialog(error.getTranslatedDesc());
@@ -363,7 +367,7 @@ void Menu::continueMainMenu() {
true
);
- updateSelectedSavefile();
+ updateSelectedSavefile(false);
}
const Graphics::Surface *Menu::getBigThumbnail() const {
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 81f962f74c2..5e51f724058 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -82,7 +82,7 @@ public:
private:
void triggerSave();
- void updateSelectedSavefile();
+ void updateSelectedSavefile(bool hasJustSaved);
bool tryReadOldSavefile();
void continueGame();
void continueMainMenu();
Commit: 04240a15b726afc222f93223c22c3b4f413305c8
https://github.com/scummvm/scummvm/commit/04240a15b726afc222f93223c22c3b4f413305c8
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix filemon being able to leave POBALDO_INDIO
Changed paths:
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
engines/alcachofa/global-ui.cpp
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 14b40a0954d..27508edf3e5 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -21,6 +21,7 @@
#include "alcachofa.h"
#include "game.h"
+#include "script.h"
using namespace Common;
@@ -57,6 +58,15 @@ class GameMovieAdventure : public Game {
return Game::shouldTriggerDoor(door);
}
+ void onUserChangedCharacter() override {
+ // An original bug in room POBLADO_INDIO if filemon is bound and mortadelo enters the room
+ // the door A_PUENTE which was disabled is reenabled to allow mortadelo leaving
+ // 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");
+ }
+
bool hasMortadeloVoice(const Character *character) override {
return Game::hasMortadeloVoice(character) ||
character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index f55fc805246..8134330820e 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -79,6 +79,8 @@ bool Game::shouldTriggerDoor(const Door *door) {
return true;
}
+void Game::onUserChangedCharacter() { }
+
bool Game::hasMortadeloVoice(const Character *character) {
return character == &g_engine->world().mortadelo();
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 33737d444af..e6de8e45c88 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -61,6 +61,7 @@ public:
virtual bool shouldCharacterTrigger(const Character *character, const char *action);
virtual bool shouldTriggerDoor(const Door *door);
virtual bool hasMortadeloVoice(const Character *character);
+ virtual void onUserChangedCharacter();
virtual void unknownCamSetInactiveAttribute(int attribute);
virtual void unknownFadeType(int fadeType);
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index f7b531f277d..855baa9b273 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -149,6 +149,7 @@ bool GlobalUI::updateChangingCharacter() {
g_engine->camera().setFollow(player.activeCharacter());
g_engine->camera().restore(0);
player.changeRoom(player.activeCharacter()->room()->name(), false);
+ g_engine->game().onUserChangedCharacter();
int32 characterJingle = g_engine->script().variable(
player.activeCharacterKind() == MainCharacterKind::Mortadelo
Commit: 330d81a940535e693748504a5bbfbad4c46f74c1
https://github.com/scummvm/scummvm/commit/330d81a940535e693748504a5bbfbad4c46f74c1
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix some objects being enabled after load
Changed paths:
engines/alcachofa/general-objects.cpp
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 3bcc889d587..c7fdd8000dd 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -254,6 +254,7 @@ void ShapeObject::update() {
}
void ShapeObject::syncGame(Serializer &serializer) {
+ ObjectBase::syncGame(serializer);
serializer.syncAsSByte(_order);
_isNewlySelected = false;
_wasSelected = false;
Commit: 1adac7a86fd1d23ab17e8668370fb238eef258b2
https://github.com/scummvm/scummvm/commit/1adac7a86fd1d23ab17e8668370fb238eef258b2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Add spanish and english steam versions
Changed paths:
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 5f577de7259..5c838a1aab5 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -22,18 +22,41 @@
namespace Alcachofa {
const PlainGameDescriptor alcachofaGames[] = {
- { "mort_phil_adventura_de_cine", "Mort&Phil: A movie adventure" },
+ { "mort_phil_adventura_de_cine", "Mort & Phil: A Movie Adventure" },
{ 0, 0 }
};
const ADGameDescription gameDescriptions[] = {
+ //
+ // A Movie Adventure
+ //
{
"mort_phil_adventura_de_cine",
- nullptr,
+ "Clever & Smart - A Movie Adventure",
AD_ENTRY1s("Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606),
Common::DE_DEU,
Common::kPlatformWindows,
- ADGF_UNSTABLE,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+ },
+
+ // The "english" version is just the spanish version with english subtitles...
+ {
+ "mort_phil_adventura_de_cine",
+ u8"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+ AD_ENTRY1s("Textos/Objetos.nkr", "ad3cb78ad7a51cfe63ee6f84768c7e66", 15895),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+ },
+ {
+ "mort_phil_adventura_de_cine",
+ u8"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+ AD_ENTRY1s("Textos/Objetos.nkr", "93331e4cc8d2f8f8a0007bfb5140dff5", 16403),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
Commit: 7cfe04ad780252f09b1b76aae1c7de39078d0d3f
https://github.com/scummvm/scummvm/commit/7cfe04ad780252f09b1b76aae1c7de39078d0d3f
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Handle more dialog line formats
Changed paths:
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 27508edf3e5..13793546476 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -33,11 +33,6 @@ class GameMovieAdventure : public Game {
!room->name().equalsIgnoreCase("HABITACION_NEGRA");
}
- void invalidDialogLine(uint index) override {
- if (index != 4542)
- Game::invalidDialogLine(index);
- }
-
bool shouldCharacterTrigger(const Character *character, const char *action) override {
// An original hack to check that bed sheet is used on the other main character only in the correct room
// There *is* another script variable (es_casa_freddy) that should check this
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 956b2a8dd9a..1252024ee50 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -724,6 +724,18 @@ static void loadEncryptedFile(const char *path, Array<char> &output) {
output.back() = '\0'; // one for good measure and a zero-terminator
}
+static char *trimLeading(char *start, char *end) {
+ while (start < end && isSpace(*start))
+ start++;
+ return start;
+}
+
+static char *skipWord(char *start, char *end) {
+ while (start < end && !isSpace(*start))
+ start++;
+ return start;
+}
+
static char *trimTrailing(char *start, char *end) {
while (start < end && isSpace(end[-1]))
end--;
@@ -739,32 +751,52 @@ void World::loadLocalizedNames() {
if (keyEnd == lineStart || keyEnd == lineEnd || keyEnd + 1 == lineEnd)
error("Invalid localized name line separator");
char *valueEnd = trimTrailing(keyEnd + 1, lineEnd);
- if (valueEnd == keyEnd + 1)
- error("Invalid localized name value");
*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;
}
}
void World::loadDialogLines() {
+ /* This "encrypted" file contains lines in any of the following formats:
+ * Name 123, "This is the dialog line"\r\n
+ * Name 123, "This is the dialog line\r\n
+ * Name 123 This is the dialog line \r\n
+ *
+ * - 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);
- char *lineStart = _dialogChunk.begin(), *fileEnd = _dialogChunk.end();
+ char *lineStart = _dialogChunk.begin(), *fileEnd = _dialogChunk.end() - 1;
while (lineStart < fileEnd) {
char *lineEnd = find(lineStart, fileEnd, '\n');
- char *firstQuote = find(lineStart, lineEnd, '\"');
- char *secondQuote = firstQuote == lineEnd ? lineEnd : find(firstQuote + 1, lineEnd, '\"');
- if (firstQuote == lineEnd || secondQuote == lineEnd) {
+ char *cursor = trimLeading(lineStart, lineEnd); // space before the name
+ cursor = skipWord(cursor, lineEnd); // the name
+ cursor = trimLeading(cursor, lineEnd); // space between dialog id
+ cursor = skipWord(cursor, lineEnd); // the dialog id
+ cursor = trimLeading(cursor, lineEnd); // space between id and line
+ char *dialogLineEnd = trimTrailing(cursor, lineEnd);
+ if (*cursor == '\"')
+ cursor++;
+ if (dialogLineEnd > cursor && dialogLineEnd[-1] == '\"')
+ dialogLineEnd--;
+
+ if (cursor >= dialogLineEnd) {
g_engine->game().invalidDialogLine(_dialogLines.size());
- firstQuote = lineStart; // store an empty string
- secondQuote = lineStart + 1;
+ cursor = lineStart; // store an empty string
+ dialogLineEnd = lineStart + 1;
}
- *secondQuote = 0;
- _dialogLines.push_back(firstQuote + 1);
+ *dialogLineEnd = 0;
+ _dialogLines.push_back(cursor);
lineStart = lineEnd + 1;
}
}
Commit: 268a6538bb83d8620faec576b9294ce38f7e1b5f
https://github.com/scummvm/scummvm/commit/268a6538bb83d8620faec576b9294ce38f7e1b5f
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix draw order of benter bottom text
Changed paths:
engines/alcachofa/global-ui.cpp
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 855baa9b273..88ff5fb292b 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -220,7 +220,7 @@ struct CenterBottomTextTask : public Task {
while (g_engine->getMillis() - _startTime < _durationMs) {
if (process().isActiveForPlayer()) {
g_engine->drawQueue().add<TextDrawRequest>(
- font, text, pos, -1, true, kWhite, 1);
+ font, text, pos, -1, true, kWhite, -kForegroundOrderCount + 1);
}
TASK_YIELD(1);
}
Commit: 4fa0c5a590ec29b64bee95ef798ea5f093418557
https://github.com/scummvm/scummvm/commit/4fa0c5a590ec29b64bee95ef798ea5f093418557
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Handle video playback errors more gracefully
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/console.cpp
engines/alcachofa/console.h
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 35431a6b226..46759091b71 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -30,6 +30,7 @@
#include "graphics/framelimiter.h"
#include "graphics/thumbnail.h"
#include "image/png.h"
+#include "video/avi_decoder.h"
#include "video/mpegps_decoder.h"
#include "alcachofa.h"
@@ -56,6 +57,7 @@ AlcachofaEngine::AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDes
: Engine(syst)
, _gameDescription(gameDesc)
, _eventLoopSemaphore("engine") {
+ assert(gameDesc != nullptr);
g_engine = this;
}
@@ -129,19 +131,37 @@ Common::Error AlcachofaEngine::run() {
}
void AlcachofaEngine::playVideo(int32 videoId) {
+ // Video files are either MPEG PS or AVI
FakeLock lock("playVideo", _eventLoopSemaphore);
- Video::MPEGPSDecoder decoder;
- if (!decoder.loadFile(Common::Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1))))
- error("Could not find video %d", videoId);
- _sounds.stopAll();
- auto texture = _renderer->createTexture(decoder.getWidth(), decoder.getHeight(), false);
- decoder.start();
+ File *file = new File();
+ if (!file->open(Path(Common::String::format("Data/DATA%02d.BIN", videoId + 1)))) {
+ game().invalidVideo(videoId, "open file");
+ return;
+ }
+ char magic[4];
+ if (file->read(magic, sizeof(magic)) != sizeof(magic) || !file->seek(0)) {
+ delete file;
+ game().invalidVideo(videoId, "read magic");
+ return;
+ }
+ ScopedPtr<Video::VideoDecoder> decoder;
+ if (memcmp(magic, "RIFF", sizeof(magic)) == 0)
+ decoder.reset(new Video::AVIDecoder());
+ else
+ decoder.reset(new Video::MPEGPSDecoder());
+ if (!decoder->loadStream(file)) {
+ game().invalidVideo(videoId, "decode video");
+ return;
+ }
+ _sounds.stopAll();
+ auto texture = _renderer->createTexture(decoder->getWidth(), decoder->getHeight(), false);
Common::Event e;
- while (!decoder.endOfVideo() && !shouldQuit()) {
- if (decoder.needsUpdate())
+ decoder->start();
+ while (!decoder->endOfVideo() && !shouldQuit()) {
+ if (decoder->needsUpdate())
{
- auto surface = decoder.decodeNextFrame();
+ auto surface = decoder->decodeNextFrame();
if (surface)
texture->update(*surface);
_renderer->begin();
@@ -161,9 +181,9 @@ void AlcachofaEngine::playVideo(int32 videoId) {
if (_input.wasAnyMouseReleased() || _input.wasMenuKeyPressed())
break;
- g_system->delayMillis(decoder.getTimeToNextFrame());
+ g_system->delayMillis(decoder->getTimeToNextFrame());
}
- decoder.stop();
+ decoder->stop();
}
void AlcachofaEngine::fadeExit() {
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index df2a6e224f1..a9e888dfb46 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -112,6 +112,7 @@ public:
AlcachofaEngine(OSystem *syst, const ADGameDescription *gameDesc);
~AlcachofaEngine() override;
+ inline const ADGameDescription &gameDescription() const { return *_gameDescription; }
inline IRenderer &renderer() { return *_renderer; }
inline DrawQueue &drawQueue() { return *_drawQueue; }
inline Camera &camera() { return _camera; }
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index b2fa6b98f15..cd298942bd1 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -19,6 +19,10 @@
*
*/
+#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
+#include "gui/console.h"
+#endif
+
#include "console.h"
#include "script.h"
#include "alcachofa.h"
@@ -46,6 +50,7 @@ Console::Console() : GUI::Debugger() {
registerCmd("debugMode", WRAP_METHOD(Console, cmdDebugMode));
registerCmd("tp", WRAP_METHOD(Console, cmdTeleport));
registerCmd("toggleRoomFloor", WRAP_METHOD(Console, cmdToggleRoomFloor));
+ registerCmd("playVideo", WRAP_METHOD(Console, cmdPlayVideo));
}
Console::~Console() {
@@ -223,12 +228,10 @@ bool Console::cmdDebugMode(int argc, const char **args) {
}
int32 param = -1;
- if (argc > 2)
- {
+ if (argc > 2) {
char *end = nullptr;
param = (int32)strtol(args[2], &end, 10);
- if (end == nullptr || *end != '\0')
- {
+ if (end == nullptr || *end != '\0') {
debugPrintf("Debug mode parameter can only be integers");
return true;
}
@@ -240,9 +243,8 @@ bool Console::cmdDebugMode(int argc, const char **args) {
}
bool Console::cmdTeleport(int argc, const char **args) {
- if (argc < 1 || argc > 2)
- {
- debugPrintf("usagge: tp [<character>]\n");
+ if (argc < 1 || argc > 2) {
+ debugPrintf("usage: tp [<character>]\n");
debugPrintf("characters:\n");
debugPrintf(" 0 - Both\n");
debugPrintf(" 1 - Mortadelo\n");
@@ -250,13 +252,11 @@ bool Console::cmdTeleport(int argc, const char **args) {
}
int32 param = 0;
- if (argc > 1)
- {
+ if (argc > 1) {
char *end = nullptr;
param = (int32)strtol(args[1], &end, 10);
- if (end == nullptr || *end != '\0')
- {
- debugPrintf("Character kind can only be integer\n");
+ if (end == nullptr || *end != '\0') {
+ debugPrintf("Character kind can only be an integer\n");
return true;
}
}
@@ -267,8 +267,7 @@ bool Console::cmdTeleport(int argc, const char **args) {
bool Console::cmdToggleRoomFloor(int argc, const char **args) {
auto room = g_engine->player().currentRoom();
- if (room == nullptr)
- {
+ if (room == nullptr) {
debugPrintf("No room is active");
return true;
}
@@ -277,4 +276,27 @@ bool Console::cmdToggleRoomFloor(int argc, const char **args) {
return false;
}
+bool Console::cmdPlayVideo(int argc, const char **args) {
+ if (argc == 2) {
+ char *end = nullptr;
+ int32 videoId = (int32)strtol(args[1], &end, 10);
+ if (end == nullptr || *end != '\0') {
+ debugPrintf("Video ID can only be an integer\n");
+ return true;
+ }
+
+#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
+ // we have to close the console *now* to properly see the video
+ _debuggerDialog->close();
+ g_system->clearOverlay();
+#endif
+
+ g_engine->playVideo(videoId);
+ return false;
+ }
+ else
+ debugPrintf("usage: playVideo <id>\n");
+ return true;
+}
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/console.h b/engines/alcachofa/console.h
index 3d6e5e0b6ac..acfbb4fea6f 100644
--- a/engines/alcachofa/console.h
+++ b/engines/alcachofa/console.h
@@ -60,6 +60,7 @@ private:
bool cmdDebugMode(int argc, const char **args);
bool cmdTeleport(int argc, const char **args);
bool cmdToggleRoomFloor(int argc, const char **args);
+ bool cmdPlayVideo(int argc, const char **args);
bool _showGraphics = false;
bool _showInteractables = false;
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 13793546476..57611258608 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -140,6 +140,14 @@ class GameMovieAdventure : public Game {
return;
Game::missingSound(fileName);
}
+
+ void invalidVideo(int32 videoId, const char *context) override {
+ // for the one, known AVI problem, let's not block development
+ if (videoId == 1 && g_engine->gameDescription().language != DE_DEU)
+ warning("Could not play video %d (%s) (known problem with AVI decoder)", videoId, context);
+ else
+ Game::invalidVideo(videoId, context);
+ }
};
Game *Game::createForMovieAdventure() {
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 8134330820e..b80749dbaf0 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -193,4 +193,8 @@ void Game::notEnoughObjectDataRead(const char *room, int64 filePos, int64 object
_message("Did not read enough data (%dll < %dll) for an object in room %s", filePos, objectEnd, room);
}
+void Game::invalidVideo(int32 videoId, const char *context) {
+ _message("Could not play video %d (%s)", videoId, context);
+}
+
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index e6de8e45c88..039bf12f99c 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -87,6 +87,7 @@ public:
virtual void invalidSNDFormat(uint format, uint channels, uint freq, uint bps);
virtual void notEnoughRoomDataRead(const char *path, int64 filePos, int64 objectEnd);
virtual void notEnoughObjectDataRead(const char *room, int64 filePos, int64 objectEnd);
+ virtual void invalidVideo(int32 videoId, const char *context);
static Game *createForMovieAdventure();
Commit: 7275e191a93ac355bfa44c8840413fc91ea424e5
https://github.com/scummvm/scummvm/commit/7275e191a93ac355bfa44c8840413fc91ea424e5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:04:59+02:00
Commit Message:
ALCACHOFA: Fix loading empty dialog lines
Changed paths:
engines/alcachofa/rooms.cpp
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 1252024ee50..0d1040282c8 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -790,7 +790,8 @@ void World::loadDialogLines() {
dialogLineEnd--;
if (cursor >= dialogLineEnd) {
- g_engine->game().invalidDialogLine(_dialogLines.size());
+ if (cursor > dialogLineEnd)
+ g_engine->game().invalidDialogLine(_dialogLines.size());
cursor = lineStart; // store an empty string
dialogLineEnd = lineStart + 1;
}
Commit: e5400d11909abb95e79fe232a9a285f645d0c558
https://github.com/scummvm/scummvm/commit/e5400d11909abb95e79fe232a9a285f645d0c558
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Add support for german demo
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/detection_tables.h
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/game.cpp
engines/alcachofa/game.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 46759091b71..0894808a9ec 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -84,6 +84,7 @@ Common::Error AlcachofaEngine::run() {
_globalUI.reset(new GlobalUI());
_menu.reset(new Menu());
setMillis(0);
+ game().onLoadedGameFiles();
if (!tryLoadFromLauncher()) {
_script->createProcess(MainCharacterKind::None, "CREDITOS_INICIALES");
@@ -131,6 +132,11 @@ Common::Error AlcachofaEngine::run() {
}
void AlcachofaEngine::playVideo(int32 videoId) {
+ if (game().isKnownBadVideo(videoId)) {
+ warning("Skipping known bad video %d", videoId);
+ return;
+ }
+
// Video files are either MPEG PS or AVI
FakeLock lock("playVideo", _eventLoopSemaphore);
File *file = new File();
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 5c838a1aab5..14c9ab54e4e 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -39,6 +39,16 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
+ {
+ "mort_phil_adventura_de_cine",
+ "Clever & Smart - A Movie Adventure",
+ AD_ENTRY1s("Textos/Objetos.nkr", "8dce25494470209d4882bf12f1a5ea42", 19208),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_DEMO,
+ GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
+ },
+
// The "english" version is just the spanish version with english subtitles...
{
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 57611258608..7c9b0190e8f 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -28,6 +28,14 @@ using namespace Common;
namespace Alcachofa {
class GameMovieAdventure : public Game {
+ void onLoadedGameFiles() override {
+ // this notifies the script whether we are a demo
+ if (g_engine->world().loadedMapCount() == 2)
+ g_engine->script().variable("EsJuegoCompleto") = 2;
+ else if (g_engine->world().loadedMapCount() == 3) // I don't know this demo
+ g_engine->script().variable("EsJuegoCompleto") = 1;
+ }
+
bool doesRoomHaveBackground(const Room *room) override {
return !room->name().equalsIgnoreCase("Global") &&
!room->name().equalsIgnoreCase("HABITACION_NEGRA");
@@ -78,13 +86,35 @@ class GameMovieAdventure : public Game {
"MONITOR___OL_EFECTO_FONDO.AN0",
nullptr
};
- for (const char **exemption = exemptions; *exemption != nullptr; exemption++) {
- if (fileName.equalsIgnoreCase(*exemption)) {
- debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
- return;
+
+ // 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",
+ nullptr
+ };
+
+ const auto isInExemptions = [&] (const char *const *const list) {
+ for (const char *const *exemption = list; *exemption != nullptr; exemption++) {
+ if (fileName.equalsIgnoreCase(*exemption))
+ return true;
}
- }
- Game::missingAnimation(fileName);
+ return false;
+ };
+
+ if (isInExemptions(exemptions) ||
+ ((g_engine->gameDescription().flags & ADGF_DEMO) && isInExemptions(demoExemptions)))
+ debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
+ else
+ Game::missingAnimation(fileName);
}
void unknownAnimateObject(const char *name) override {
@@ -138,13 +168,23 @@ class GameMovieAdventure : public Game {
void missingSound(const String &fileName) override {
if (fileName == "CHAS" || fileName == "517")
return;
+ if ((g_engine->gameDescription().flags & ADGF_DEMO) && (
+ fileName == "M4996" ||
+ fileName == "T40"))
+ return;
Game::missingSound(fileName);
}
+ bool isKnownBadVideo(int32 videoId) override {
+ return
+ (videoId == 3 && (g_engine->gameDescription().flags & ADGF_DEMO)) || // problem with MPEG PS decoding
+ Game::isKnownBadVideo(videoId);
+ }
+
void invalidVideo(int32 videoId, const char *context) override {
// for the one, known AVI problem, let's not block development
if (videoId == 1 && g_engine->gameDescription().language != DE_DEU)
- warning("Could not play video %d (%s) (known problem with AVI decoder)", videoId, context);
+ warning("Could not play video %d (%s) (WMV not supported)", videoId, context);
else
Game::invalidVideo(videoId, context);
}
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index b80749dbaf0..0201d5cec57 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -36,6 +36,8 @@ Game::Game()
{
}
+void Game::onLoadedGameFiles() {}
+
bool Game::doesRoomHaveBackground(const Room *room) {
return true;
}
@@ -193,6 +195,10 @@ void Game::notEnoughObjectDataRead(const char *room, int64 filePos, int64 object
_message("Did not read enough data (%dll < %dll) for an object in room %s", filePos, objectEnd, room);
}
+bool Game::isKnownBadVideo(int32 videoId) {
+ return false;
+}
+
void Game::invalidVideo(int32 videoId, const char *context) {
_message("Could not play video %d (%s)", videoId, context);
}
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 039bf12f99c..43a056abd5f 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -48,6 +48,8 @@ public:
Game();
virtual ~Game() = default;
+ virtual void onLoadedGameFiles();
+
virtual bool doesRoomHaveBackground(const Room *room);
virtual void unknownRoomObject(const Common::String &type);
virtual void unknownRoomType(const Common::String &type);
@@ -87,6 +89,7 @@ public:
virtual void invalidSNDFormat(uint format, uint channels, uint freq, uint bps);
virtual void notEnoughRoomDataRead(const char *path, int64 filePos, int64 objectEnd);
virtual void notEnoughObjectDataRead(const char *room, int64 filePos, int64 objectEnd);
+ virtual bool isKnownBadVideo(int32 videoId);
virtual void invalidVideo(int32 videoId, const char *context);
static Game *createForMovieAdventure();
Commit: 45514217d96a15d404a384355fb9ab5620166fb8
https://github.com/scummvm/scummvm/commit/45514217d96a15d404a384355fb9ab5620166fb8
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Rename header guards
Changed paths:
engines/alcachofa/camera.h
engines/alcachofa/common.h
engines/alcachofa/debug.h
engines/alcachofa/game.h
engines/alcachofa/global-ui.h
engines/alcachofa/graphics.h
engines/alcachofa/input.h
engines/alcachofa/menu.h
engines/alcachofa/objects.h
engines/alcachofa/player.h
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.h
engines/alcachofa/script-debug.h
engines/alcachofa/script.h
engines/alcachofa/shape.h
engines/alcachofa/sounds.h
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index f3099eef5d0..912b4fa14b3 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef CAMERA_H
-#define CAMERA_H
+#ifndef ALCACHOFA_CAMERA_H
+#define ALCACHOFA_CAMERA_H
#include "common.h"
#include "math/matrix4.h"
@@ -119,4 +119,4 @@ private:
}
-#endif // CAMERA_H
+#endif // ALCACHOFA_CAMERA_H
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index dd793893f13..f8c43c165d4 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef COMMON_H
-#define COMMON_H
+#ifndef ALCACHOFA_COMMON_H
+#define ALCACHOFA_COMMON_H
#include "common/scummsys.h"
#include "common/rect.h"
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index abe6cc8ff02..766da50f01a 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef DEBUG_H
-#define DEBUG_H
+#ifndef ALCACHOFA_DEBUG_H
+#define ALCACHOFA_DEBUG_H
#include "alcachofa.h"
@@ -231,4 +231,4 @@ public:
}
-#endif // DEBUG_H
+#endif // ALCACHOFA_DEBUG_H
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 43a056abd5f..65b99e52671 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef GAME_H
-#define GAME_H
+#ifndef ALCACHOFA_GAME_H
+#define ALCACHOFA_GAME_H
#include "common/textconsole.h"
#include "common/file.h"
@@ -99,4 +99,4 @@ public:
}
-#endif // GAME_H
+#endif // ALCACHOFA_GAME_H
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 44964edeb6b..1c0b786b8c8 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef GLOBAL_UI_H
-#define GLOBAL_UI_H
+#ifndef ALCACHOFA_GLOBAL_UI_H
+#define ALCACHOFA_GLOBAL_UI_H
#include "objects.h"
@@ -70,4 +70,4 @@ Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs);
}
-#endif // GLOBAL_UI_H
+#endif // ALCACHOFA_GLOBAL_UI_H
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index cd6c681d7a8..22d35d86454 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef GRAPHICS_H
-#define GRAPHICS_H
+#ifndef ALCACHOFA_GRAPHICS_H
+#define ALCACHOFA_GRAPHICS_H
#include "common/ptr.h"
#include "common/stream.h"
@@ -476,4 +476,4 @@ private:
}
-#endif
+#endif // ALCACHOFA_ENGINE_H
diff --git a/engines/alcachofa/input.h b/engines/alcachofa/input.h
index 798f2e0369b..4f02eb13d7e 100644
--- a/engines/alcachofa/input.h
+++ b/engines/alcachofa/input.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef INPUT_H
-#define INPUT_H
+#ifndef ALCACHOFA_INPUT_H
+#define ALCACHOFA_INPUT_H
#include "common/events.h"
#include "common/ptr.h"
@@ -68,4 +68,4 @@ private:
}
-#endif // INPUT_H
+#endif // ALCACHOFA_INPUT_H
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 5e51f724058..207ca2ee8f6 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef MENU_H
-#define MENU_H
+#ifndef ALCACHOFA_MENU_H
+#define ALCACHOFA_MENU_H
#include "common/scummsys.h"
#include "common/savefile.h"
@@ -106,4 +106,4 @@ private:
}
-#endif // MENU_H
+#endif // ALCACHOFA_MENU_H
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 65210acc974..d4fd2fbc48c 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef OBJECTS_H
-#define OBJECTS_H
+#ifndef ALCACHOFA_OBJECTS_H
+#define ALCACHOFA_OBJECTS_H
#include "shape.h"
#include "graphics.h"
@@ -602,4 +602,4 @@ private:
}
-#endif // OBJECTS_H
+#endif // ALCACHOFA_OBJECTS_H
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index ee0259c2362..c1d5baefe96 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef PLAYER_H
-#define PLAYER_H
+#ifndef ALCACHOFA_PLAYER_H
+#define ALCACHOFA_PLAYER_H
#include "rooms.h"
@@ -81,4 +81,4 @@ private:
}
-#endif // PLAYER_H
+#endif // ALCACHOFA_PLAYER_H
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index e08ae07ec4b..b62e0f656e0 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef ROOMS_H
-#define ROOMS_H
+#ifndef ALCACHOFA_ROOMS_H
+#define ALCACHOFA_ROOMS_H
#include "objects.h"
@@ -211,4 +211,4 @@ private:
}
-#endif // ROOMS_H
+#endif // ALCACHOFA_ROOMS_H
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index c8131e3fefa..d1739c3a9dc 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef SCHEDULER_H
-#define SCHEDULER_H
+#ifndef ALCACHOFA_SCHEDULER_H
+#define ALCACHOFA_SCHEDULER_H
#include "common.h"
@@ -222,4 +222,4 @@ private:
}
-#endif // SCHEDULER_H
+#endif // ALCACHOFA_SCHEDULER_H
diff --git a/engines/alcachofa/script-debug.h b/engines/alcachofa/script-debug.h
index 168903c22f4..573d502f4b4 100644
--- a/engines/alcachofa/script-debug.h
+++ b/engines/alcachofa/script-debug.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef SCRIPT_DEBUG_H
-#define SCRIPT_DEBUG_H
+#ifndef ALCACHOFA_SCRIPT_DEBUG_H
+#define ALCACHOFA_SCRIPT_DEBUG_H
namespace Alcachofa {
@@ -127,4 +127,4 @@ static const char *const KernelCallNames[] = {
}
-#endif // SCRIPT_DEBUG_H
+#endif // ALCACHOFA_SCRIPT_DEBUG_H
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index 0b8337a3430..bb93318b6b7 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef SCRIPT_H
-#define SCRIPT_H
+#ifndef ALCACHOFA_SCRIPT_H
+#define ALCACHOFA_SCRIPT_H
#include "common.h"
@@ -190,4 +190,4 @@ private:
}
-#endif // SCRIPT_H
+#endif // ALCACHOFA_SCRIPT_H
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 4174aabc140..0c6b1f5f62e 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef SHAPE_H
-#define SHAPE_H
+#ifndef ALCACHOFA_SHAPE_H
+#define ALCACHOFA_SHAPE_H
#include "common/stream.h"
#include "common/array.h"
@@ -255,4 +255,4 @@ private:
}
-#endif // SHAPE_H
+#endif // ALCACHOFA_SHAPE_H
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 99978cdfe45..305c1879f0f 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -19,8 +19,8 @@
*
*/
-#ifndef SOUNDS_H
-#define SOUNDS_H
+#ifndef ALCACHOFA_SOUNDS_H
+#define ALCACHOFA_SOUNDS_H
#include "scheduler.h"
#include "audio/mixer.h"
@@ -105,4 +105,4 @@ private:
}
-#endif // SOUNDS_H
+#endif // ALCACHOFA_SOUNDS_H
Commit: d4fbf73b72adf454e25351f60a4b83848cc8f7e5
https://github.com/scummvm/scummvm/commit/d4fbf73b72adf454e25351f60a4b83848cc8f7e5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Remove redundant semicolons
Changed paths:
engines/alcachofa/tasks.h
diff --git a/engines/alcachofa/tasks.h b/engines/alcachofa/tasks.h
index 163fed7166f..2145b14a01f 100644
--- a/engines/alcachofa/tasks.h
+++ b/engines/alcachofa/tasks.h
@@ -31,16 +31,16 @@
#define DEFINE_TASK(TaskName)
#endif
-DEFINE_TASK(CamLerpPosTask);
-DEFINE_TASK(CamLerpScaleTask);
-DEFINE_TASK(CamLerpPosScaleTask);
-DEFINE_TASK(CamLerpRotationTask);
-DEFINE_TASK(CamShakeTask);
-DEFINE_TASK(CamWaitToStopTask);
-DEFINE_TASK(CamSetInactiveAttributeTask);
-DEFINE_TASK(SayTextTask);
-DEFINE_TASK(AnimateCharacterTask);
-DEFINE_TASK(LerpLodBiasTask);
+DEFINE_TASK(CamLerpPosTask)
+DEFINE_TASK(CamLerpScaleTask)
+DEFINE_TASK(CamLerpPosScaleTask)
+DEFINE_TASK(CamLerpRotationTask)
+DEFINE_TASK(CamShakeTask)
+DEFINE_TASK(CamWaitToStopTask)
+DEFINE_TASK(CamSetInactiveAttributeTask)
+DEFINE_TASK(SayTextTask)
+DEFINE_TASK(AnimateCharacterTask)
+DEFINE_TASK(LerpLodBiasTask)
DEFINE_TASK(ArriveTask)
DEFINE_TASK(DialogMenuTask)
DEFINE_TASK(AnimateTask)
Commit: 59960db11405fc57e994d2034044b63b56a0fe24
https://github.com/scummvm/scummvm/commit/59960db11405fc57e994d2034044b63b56a0fe24
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Fix crash in fadeExit with gcc builds
Changed paths:
engines/alcachofa/alcachofa.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 0894808a9ec..1e9559c54f0 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -211,10 +211,9 @@ void AlcachofaEngine::fadeExit() {
_renderer->begin();
_drawQueue->clear();
float t = ((float)(g_system->getMillis() - startTime)) / kFadeOutDuration;
- if (room != nullptr)
- room->draw();
_drawQueue->add<FadeDrawRequest>(FadeType::ToBlack, t, -kForegroundOrderCount);
- _drawQueue->draw();
+ if (room != nullptr)
+ room->draw(); // this executes the drawQueue as well
_renderer->end();
limiter.delayBeforeSwap();
@@ -390,8 +389,7 @@ void AlcachofaEngine::getSavegameThumbnail(Graphics::Surface &thumbnail) {
g_engine->drawQueue().clear();
g_engine->renderer().begin();
g_engine->renderer().setOutput(thumbnail);
- g_engine->player().currentRoom()->draw();
- g_engine->drawQueue().draw();
+ g_engine->player().currentRoom()->draw(); // drawQueue is drawn here as well
g_engine->renderer().end();
// we should be within the event loop. as such it is quite safe to mess with the drawQueue or renderer
Commit: bdc4cd1d386192f71c398a092d6cb9bccc69c487
https://github.com/scummvm/scummvm/commit/bdc4cd1d386192f71c398a092d6cb9bccc69c487
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Handle missing voice/music files better
Changed paths:
engines/alcachofa/sounds.cpp
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index f13b8ca0188..79205fda38a 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -133,6 +133,15 @@ static AudioStream *openAudio(const char *fileName) {
SoundHandle Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::SoundType type) {
AudioStream *stream = openAudio(fileName);
+ 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
+ * But only do this for speech as there is no skipping for sound effects
+ * so those would live on forever and block up mixer channels
+ * Music is fine as well as we clean up the music playack explicitly
+ */
+ stream = makeSilentAudioStream(8000, false);
+ }
if (stream == nullptr)
return {};
Commit: d2e767e75b3c4463edcd9b69930b16896fa80682
https://github.com/scummvm/scummvm/commit/d2e767e75b3c4463edcd9b69930b16896fa80682
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Probably fix two compiler warnings
it is a never-ending battle...
Changed paths:
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/tasks.h
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index befaa7b35e0..658a34d916f 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -341,7 +341,7 @@ public:
GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
-#if _DEBUG
+#ifdef _DEBUG
// make sure we crash instead of someone using our stack arrays
GL_CALL(glVertexPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
GL_CALL(glTexCoordPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
diff --git a/engines/alcachofa/tasks.h b/engines/alcachofa/tasks.h
index 2145b14a01f..74cbabd9817 100644
--- a/engines/alcachofa/tasks.h
+++ b/engines/alcachofa/tasks.h
@@ -47,10 +47,13 @@ DEFINE_TASK(AnimateTask)
DEFINE_TASK(CenterBottomTextTask)
DEFINE_TASK(FadeTask)
DEFINE_TASK(DoorTask)
-DEFINE_TASK(DelayTask)
DEFINE_TASK(ScriptTimerTask)
DEFINE_TASK(ScriptTask)
DEFINE_TASK(PlaySoundTask)
DEFINE_TASK(WaitForMusicTask)
+// this one is special as the implementation is in the same TU as the signature
+// which causes a warning on some pedantic compiler
+//DEFINE_TASK(DelayTask)
+
#undef DEFINE_TASK
Commit: 603264e965ef794bcbb2bca5c9c00b14013d72d2
https://github.com/scummvm/scummvm/commit/603264e965ef794bcbb2bca5c9c00b14013d72d2
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Use engine name in include paths
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/camera.h
engines/alcachofa/common.cpp
engines/alcachofa/console.cpp
engines/alcachofa/debug.h
engines/alcachofa/game-movie-adventure.cpp
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/global-ui.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/graphics.h
engines/alcachofa/input.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/objects.h
engines/alcachofa/player.cpp
engines/alcachofa/player.h
engines/alcachofa/rooms.cpp
engines/alcachofa/rooms.h
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/script.h
engines/alcachofa/shape.cpp
engines/alcachofa/shape.h
engines/alcachofa/sounds.cpp
engines/alcachofa/sounds.h
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 1e9559c54f0..418fad1404e 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -33,17 +33,17 @@
#include "video/avi_decoder.h"
#include "video/mpegps_decoder.h"
-#include "alcachofa.h"
-#include "metaengine.h"
-#include "console.h"
-#include "detection.h"
-#include "player.h"
-#include "rooms.h"
-#include "script.h"
-#include "global-ui.h"
-#include "menu.h"
-#include "debug.h"
-#include "game.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/metaengine.h"
+#include "alcachofa/console.h"
+#include "alcachofa/detection.h"
+#include "alcachofa/player.h"
+#include "alcachofa/rooms.h"
+#include "alcachofa/script.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/menu.h"
+#include "alcachofa/debug.h"
+#include "alcachofa/game.h"
using namespace Math;
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index d611a2a9d04..484cb608dc4 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -19,9 +19,9 @@
*
*/
-#include "camera.h"
-#include "alcachofa.h"
-#include "script.h"
+#include "alcachofa/camera.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/script.h"
#include "common/system.h"
#include "math/vector4d.h"
diff --git a/engines/alcachofa/camera.h b/engines/alcachofa/camera.h
index 912b4fa14b3..5a091cf2a06 100644
--- a/engines/alcachofa/camera.h
+++ b/engines/alcachofa/camera.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_CAMERA_H
#define ALCACHOFA_CAMERA_H
-#include "common.h"
+#include "alcachofa/common.h"
#include "math/matrix4.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index ec8f0fc565e..57df1e46042 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -19,8 +19,8 @@
*
*/
-#include "common.h"
-#include "detection.h"
+#include "alcachofa/common.h"
+#include "alcachofa/detection.h"
using namespace Common;
using namespace Math;
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index cd298942bd1..bd22334c168 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -23,9 +23,9 @@
#include "gui/console.h"
#endif
-#include "console.h"
-#include "script.h"
-#include "alcachofa.h"
+#include "alcachofa/console.h"
+#include "alcachofa/script.h"
+#include "alcachofa/alcachofa.h"
using namespace Common;
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index 766da50f01a..91150d6285e 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_DEBUG_H
#define ALCACHOFA_DEBUG_H
-#include "alcachofa.h"
+#include "alcachofa/alcachofa.h"
using namespace Common;
diff --git a/engines/alcachofa/game-movie-adventure.cpp b/engines/alcachofa/game-movie-adventure.cpp
index 7c9b0190e8f..66073d08bb8 100644
--- a/engines/alcachofa/game-movie-adventure.cpp
+++ b/engines/alcachofa/game-movie-adventure.cpp
@@ -19,9 +19,9 @@
*
*/
-#include "alcachofa.h"
-#include "game.h"
-#include "script.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/game.h"
+#include "alcachofa/script.h"
using namespace Common;
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index bbac1210204..735c4bf7673 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -19,11 +19,11 @@
*
*/
-#include "objects.h"
-#include "rooms.h"
-#include "script.h"
-#include "global-ui.h"
-#include "alcachofa.h"
+#include "alcachofa/objects.h"
+#include "alcachofa/rooms.h"
+#include "alcachofa/script.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/alcachofa.h"
using namespace Common;
using namespace Math;
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 0201d5cec57..f7bc9bd7c8e 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -19,9 +19,9 @@
*
*/
-#include "alcachofa.h"
-#include "game.h"
-#include "script.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/game.h"
+#include "alcachofa/script.h"
using namespace Common;
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index c7fdd8000dd..997f550649e 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -19,11 +19,11 @@
*
*/
-#include "objects.h"
-#include "rooms.h"
-#include "scheduler.h"
-#include "global-ui.h"
-#include "alcachofa.h"
+#include "alcachofa/objects.h"
+#include "alcachofa/rooms.h"
+#include "alcachofa/scheduler.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/alcachofa.h"
#include "common/system.h"
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 88ff5fb292b..0d5d1004b23 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -19,10 +19,10 @@
*
*/
-#include "global-ui.h"
-#include "menu.h"
-#include "alcachofa.h"
-#include "script.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/menu.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/script.h"
using namespace Common;
diff --git a/engines/alcachofa/global-ui.h b/engines/alcachofa/global-ui.h
index 1c0b786b8c8..dd3722a30cc 100644
--- a/engines/alcachofa/global-ui.h
+++ b/engines/alcachofa/global-ui.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_GLOBAL_UI_H
#define ALCACHOFA_GLOBAL_UI_H
-#include "objects.h"
+#include "alcachofa/objects.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 658a34d916f..bc27347e6d4 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -19,8 +19,8 @@
*
*/
-#include "graphics.h"
-#include "detection.h"
+#include "alcachofa/graphics.h"
+#include "alcachofa/detection.h"
#include "common/system.h"
#include "engines/util.h"
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index a275ed99848..40b5375797f 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -19,10 +19,10 @@
*
*/
-#include "graphics.h"
-#include "alcachofa.h"
-#include "shape.h"
-#include "global-ui.h"
+#include "alcachofa/graphics.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/shape.h"
+#include "alcachofa/global-ui.h"
#include "common/system.h"
#include "common/file.h"
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 22d35d86454..4f9ce4f3901 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -30,8 +30,8 @@
#include "math/vector2d.h"
#include "graphics/managed_surface.h"
-#include "camera.h"
-#include "common.h"
+#include "alcachofa/camera.h"
+#include "alcachofa/common.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index 00245b6c6a8..56ad040d213 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -19,9 +19,9 @@
*
*/
-#include "input.h"
-#include "alcachofa.h"
-#include "metaengine.h"
+#include "alcachofa/input.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/metaengine.h"
using namespace Common;
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index e7988c15f80..3a23778f670 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -22,11 +22,11 @@
#include "gui/message.h"
#include "graphics/thumbnail.h"
-#include "alcachofa.h"
-#include "metaengine.h"
-#include "menu.h"
-#include "player.h"
-#include "script.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/metaengine.h"
+#include "alcachofa/menu.h"
+#include "alcachofa/player.h"
+#include "alcachofa/script.h"
using namespace Common;
using namespace Graphics;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index d4fd2fbc48c..d5150d8bddf 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -22,8 +22,8 @@
#ifndef ALCACHOFA_OBJECTS_H
#define ALCACHOFA_OBJECTS_H
-#include "shape.h"
-#include "graphics.h"
+#include "alcachofa/shape.h"
+#include "alcachofa/graphics.h"
#include "common/serializer.h"
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 65f5c2bba05..8360d51bab2 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -19,10 +19,10 @@
*
*/
-#include "player.h"
-#include "script.h"
-#include "alcachofa.h"
-#include "menu.h"
+#include "alcachofa/player.h"
+#include "alcachofa/script.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/menu.h"
using namespace Common;
diff --git a/engines/alcachofa/player.h b/engines/alcachofa/player.h
index c1d5baefe96..72651ff016d 100644
--- a/engines/alcachofa/player.h
+++ b/engines/alcachofa/player.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_PLAYER_H
#define ALCACHOFA_PLAYER_H
-#include "rooms.h"
+#include "alcachofa/rooms.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0d1040282c8..0d162b797cc 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -19,11 +19,11 @@
*
*/
-#include "alcachofa.h"
-#include "rooms.h"
-#include "script.h"
-#include "global-ui.h"
-#include "menu.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/rooms.h"
+#include "alcachofa/script.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/menu.h"
#include "common/file.h"
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index b62e0f656e0..649c1944c40 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_ROOMS_H
#define ALCACHOFA_ROOMS_H
-#include "objects.h"
+#include "alcachofa/objects.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index 3a0c194c12e..fbc83ea14df 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -19,11 +19,11 @@
*
*/
-#include "scheduler.h"
+#include "alcachofa/scheduler.h"
#include "common/system.h"
-#include "alcachofa.h"
-#include "menu.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/menu.h"
using namespace Common;
@@ -173,7 +173,7 @@ void Process::debugPrint() {
#define DEFINE_TASK(TaskName) \
extern Task *constructTask_##TaskName(Process &process, Serializer &s);
-#include "tasks.h"
+#include "alcachofa/tasks.h"
static Task *readTask(Process &process, Serializer &s) {
assert(s.isLoading());
@@ -183,7 +183,7 @@ static Task *readTask(Process &process, Serializer &s) {
#define DEFINE_TASK(TaskName) \
if (taskName == #TaskName) \
return constructTask_##TaskName(process, s);
-#include "tasks.h"
+#include "alcachofa/tasks.h"
error("Invalid task type in savestate: %s", taskName.c_str());
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index d1739c3a9dc..1f9c79b34f0 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_SCHEDULER_H
#define ALCACHOFA_SCHEDULER_H
-#include "common.h"
+#include "alcachofa/common.h"
#include "common/stack.h"
#include "common/str.h"
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 4ccd9eecc2e..1a246fc1801 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -19,11 +19,11 @@
*
*/
-#include "script.h"
-#include "rooms.h"
-#include "global-ui.h"
-#include "alcachofa.h"
-#include "script-debug.h"
+#include "alcachofa/script.h"
+#include "alcachofa/rooms.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/script-debug.h"
#include "common/file.h"
diff --git a/engines/alcachofa/script.h b/engines/alcachofa/script.h
index bb93318b6b7..8122ec2954a 100644
--- a/engines/alcachofa/script.h
+++ b/engines/alcachofa/script.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_SCRIPT_H
#define ALCACHOFA_SCRIPT_H
-#include "common.h"
+#include "alcachofa/common.h"
#include "common/hashmap.h"
#include "common/span.h"
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 494b36cd654..0459a6bfa99 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -19,7 +19,7 @@
*
*/
-#include "shape.h"
+#include "alcachofa/shape.h"
using namespace Common;
using namespace Math;
diff --git a/engines/alcachofa/shape.h b/engines/alcachofa/shape.h
index 0c6b1f5f62e..3ac64f41e19 100644
--- a/engines/alcachofa/shape.h
+++ b/engines/alcachofa/shape.h
@@ -30,7 +30,7 @@
#include "common/util.h"
#include "math/vector2d.h"
-#include "common.h"
+#include "alcachofa/common.h"
namespace Alcachofa {
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 79205fda38a..1e2a70edea7 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -19,10 +19,10 @@
*
*/
-#include "sounds.h"
-#include "rooms.h"
-#include "alcachofa.h"
-#include "detection.h"
+#include "alcachofa/sounds.h"
+#include "alcachofa/rooms.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/detection.h"
#include "common/file.h"
#include "common/substream.h"
diff --git a/engines/alcachofa/sounds.h b/engines/alcachofa/sounds.h
index 305c1879f0f..895b0bf5e3c 100644
--- a/engines/alcachofa/sounds.h
+++ b/engines/alcachofa/sounds.h
@@ -22,7 +22,7 @@
#ifndef ALCACHOFA_SOUNDS_H
#define ALCACHOFA_SOUNDS_H
-#include "scheduler.h"
+#include "alcachofa/scheduler.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 5ef0961504c..6e68b87fac7 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -19,12 +19,12 @@
*
*/
-#include "alcachofa.h"
-#include "script.h"
-#include "global-ui.h"
-#include "menu.h"
-#include "objects.h"
-#include "rooms.h"
+#include "alcachofa/alcachofa.h"
+#include "alcachofa/script.h"
+#include "alcachofa/global-ui.h"
+#include "alcachofa/menu.h"
+#include "alcachofa/objects.h"
+#include "alcachofa/rooms.h"
using namespace Common;
Commit: 820ba7894b7150ff176a04fcb37358bdffee9c92
https://github.com/scummvm/scummvm/commit/820ba7894b7150ff176a04fcb37358bdffee9c92
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Remove scummsys include
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/alcachofa.h
engines/alcachofa/common.h
engines/alcachofa/menu.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 418fad1404e..1d147ab2c80 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -19,7 +19,6 @@
*
*/
-#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
diff --git a/engines/alcachofa/alcachofa.h b/engines/alcachofa/alcachofa.h
index a9e888dfb46..c5e29336707 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -22,7 +22,6 @@
#ifndef ALCACHOFA_H
#define ALCACHOFA_H
-#include "common/scummsys.h"
#include "common/system.h"
#include "common/error.h"
#include "common/fs.h"
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index f8c43c165d4..d94fa196718 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -22,7 +22,6 @@
#ifndef ALCACHOFA_COMMON_H
#define ALCACHOFA_COMMON_H
-#include "common/scummsys.h"
#include "common/rect.h"
#include "common/serializer.h"
#include "common/stream.h"
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 207ca2ee8f6..2a3dc7fd736 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -22,7 +22,6 @@
#ifndef ALCACHOFA_MENU_H
#define ALCACHOFA_MENU_H
-#include "common/scummsys.h"
#include "common/savefile.h"
namespace Alcachofa {
Commit: 85897a520d9581b31ab986b66acb3fd94539bcef
https://github.com/scummvm/scummvm/commit/85897a520d9581b31ab986b66acb3fd94539bcef
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Fix bracing style
Changed paths:
engines/alcachofa/script.cpp
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 1a246fc1801..64b6437d85e 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -804,7 +804,7 @@ private:
}
return TaskReturn::finish(1);
- // Camera tasks
+ // Camera tasks
case ScriptKernelTask::WaitCamStopping:
return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
case ScriptKernelTask::CamFollow:
Commit: 9f774019ae7afb16a34bb2c951cbb4705c8a02b1
https://github.com/scummvm/scummvm/commit/9f774019ae7afb16a34bb2c951cbb4705c8a02b1
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Break up one-liners in switches
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/camera.cpp
engines/alcachofa/common.cpp
engines/alcachofa/common.h
engines/alcachofa/console.cpp
engines/alcachofa/debug.h
engines/alcachofa/game-objects.cpp
engines/alcachofa/game.cpp
engines/alcachofa/general-objects.cpp
engines/alcachofa/global-ui.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
engines/alcachofa/input.cpp
engines/alcachofa/menu.cpp
engines/alcachofa/metaengine.h
engines/alcachofa/player.cpp
engines/alcachofa/rooms.cpp
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/script.cpp
engines/alcachofa/shape.cpp
engines/alcachofa/sounds.cpp
engines/alcachofa/ui-objects.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 1d147ab2c80..27a8d94803c 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -164,8 +164,7 @@ void AlcachofaEngine::playVideo(int32 videoId) {
Common::Event e;
decoder->start();
while (!decoder->endOfVideo() && !shouldQuit()) {
- if (decoder->needsUpdate())
- {
+ if (decoder->needsUpdate()) {
auto surface = decoder->decodeNextFrame();
if (surface)
texture->update(*surface);
@@ -225,8 +224,7 @@ void AlcachofaEngine::fadeExit() {
}
void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param) {
- switch (mode)
- {
+ switch (mode) {
case DebugMode::ClosestFloorPoint:
_debugHandler.reset(new ClosestFloorPointDebugHandler(param));
break;
@@ -242,7 +240,9 @@ void AlcachofaEngine::setDebugMode(DebugMode mode, int32 param) {
case DebugMode::FloorColor:
_debugHandler.reset(FloorColorDebugHandler::create(param, true));
break;
- default: _debugHandler.reset(nullptr);
+ default:
+ _debugHandler.reset(nullptr);
+ break;
}
_input.toggleDebugInput(isDebugModeActive());
}
@@ -259,8 +259,7 @@ void AlcachofaEngine::setMillis(uint32 newMillis) {
if (newMillis > sysMillis) {
_timeNegOffset = 0;
_timePosOffset = newMillis - sysMillis;
- }
- else {
+ } else {
_timeNegOffset = sysMillis - newMillis;
_timePosOffset = 0;
}
@@ -342,13 +341,12 @@ bool AlcachofaEngine::syncThumbnail(MySerializer &s, Graphics::ManagedSurface *t
if (s.isLoading()) {
auto prevPosition = s.readStream().pos();
Image::PNGDecoder pngDecoder;
- if (pngDecoder.loadStream(s.readStream()) && pngDecoder.getSurface () != nullptr) {
+ if (pngDecoder.loadStream(s.readStream()) && pngDecoder.getSurface() != nullptr) {
if (thumbnail != nullptr) {
thumbnail->free();
thumbnail->copyFrom(*pngDecoder.getSurface());
}
- }
- else {
+ } else {
// If we do not get a thumbnail, maybe we get at least the marker that there is no thumbnail
s.readStream().seek(prevPosition, SEEK_SET);
uint32 magicValue = s.readStream().readUint32LE();
@@ -357,8 +355,7 @@ bool AlcachofaEngine::syncThumbnail(MySerializer &s, Graphics::ManagedSurface *t
else // this is not an error, just a pity
warning("No thumbnail stored in in-game savestate");
}
- }
- else {
+ } else {
if (thumbnail == nullptr ||
thumbnail->getPixels() == nullptr ||
!Image::writePNG(s.writeStream(), *thumbnail)) {
diff --git a/engines/alcachofa/camera.cpp b/engines/alcachofa/camera.cpp
index 484cb608dc4..5f9752717e9 100644
--- a/engines/alcachofa/camera.cpp
+++ b/engines/alcachofa/camera.cpp
@@ -58,7 +58,7 @@ void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
}
void Camera::setPosition(Vector2d v) {
- setPosition({v.getX(), v.getY(), _cur._usedCenter.z()});
+ setPosition({ v.getX(), v.getY(), _cur._usedCenter.z() });
}
void Camera::setPosition(Vector3d v) {
@@ -156,8 +156,8 @@ Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
}
Point Camera::transform3Dto2D(Point p3d) const {
- auto v2d = transform3Dto2D({(float)p3d.x, (float)p3d.y, kBaseScale});
- return {(int16)v2d.x(), (int16)v2d.y()};
+ auto v2d = transform3Dto2D({ (float)p3d.x, (float)p3d.y, kBaseScale });
+ return { (int16)v2d.x(), (int16)v2d.y() };
}
void Camera::update() {
@@ -191,7 +191,7 @@ void Camera::updateFollowing(float deltaTime) {
Vector3d targetCenter = setAppliedCenter({
_shake.getX() + _followTarget->position().x,
_shake.getY() + _followTarget->position().y - depthScale * 85,
- _cur._usedCenter.z()});
+ _cur._usedCenter.z() });
targetCenter.y() -= halfHeight;
float distanceToTarget = as2D(_cur._usedCenter - targetCenter).getMagnitude();
float moveDistance = _followTarget->stepSizeFactor() * _cur._speed * deltaTime;
@@ -461,7 +461,7 @@ struct CamShakeTask final : public CamLerpTask {
CamShakeTask(Process &process, Vector2d amplitude, Vector2d frequency, int32 duration)
: CamLerpTask(process, duration, EasingType::Linear)
, _amplitude(amplitude)
- , _frequency(frequency) { }
+ , _frequency(frequency) {}
CamShakeTask(Process &process, Serializer &s)
: CamLerpTask(process) {
@@ -547,9 +547,15 @@ struct CamSetInactiveAttributeTask final : public Task {
auto &state = _camera._backups[0];
switch (_attribute) {
- case kPosZ: state._usedCenter.z() = _value; break;
- case kScale: state._scale = _value; break;
- case kRotation: state._rotation = _value; break;
+ case kPosZ:
+ state._usedCenter.z() = _value;
+ break;
+ case kScale:
+ state._scale = _value;
+ break;
+ case kRotation:
+ state._rotation = _value;
+ break;
default:
g_engine->game().unknownCamSetInactiveAttribute((int)_attribute);
break;
@@ -560,10 +566,18 @@ struct CamSetInactiveAttributeTask final : public Task {
void debugPrint() override {
const char *attributeName;
switch (_attribute) {
- case kPosZ: attributeName = "PosZ"; break;
- case kScale: attributeName = "Scale"; break;
- case kRotation: attributeName = "Rotation"; break;
- default: attributeName = "<unknown>"; break;
+ case kPosZ:
+ attributeName = "PosZ";
+ break;
+ case kScale:
+ attributeName = "Scale";
+ break;
+ case kRotation:
+ attributeName = "Rotation";
+ break;
+ default:
+ attributeName = "<unknown>";
+ break;
}
g_engine->console().debugPrintf("Set inactive camera %s to %f after %dms\n", attributeName, _value, _delay);
}
diff --git a/engines/alcachofa/common.cpp b/engines/alcachofa/common.cpp
index 57df1e46042..1f202eee319 100644
--- a/engines/alcachofa/common.cpp
+++ b/engines/alcachofa/common.cpp
@@ -29,11 +29,16 @@ namespace Alcachofa {
float ease(float t, EasingType type) {
switch (type) {
- case EasingType::Linear: return t;
- case EasingType::InOut: return (1 - cosf(t * M_PI)) * 0.5f;
- case EasingType::In: return 1 - cosf(t * M_PI * 0.5f);
- case EasingType::Out: return sinf(t * M_PI * 0.5f);
- default: return 0.0f;
+ case EasingType::Linear:
+ return t;
+ case EasingType::InOut:
+ return (1 - cosf(t * M_PI)) * 0.5f;
+ case EasingType::In:
+ return 1 - cosf(t * M_PI * 0.5f);
+ case EasingType::Out:
+ return sinf(t * M_PI * 0.5f);
+ default:
+ return 0.0f;
}
}
diff --git a/engines/alcachofa/common.h b/engines/alcachofa/common.h
index d94fa196718..8690dd66241 100644
--- a/engines/alcachofa/common.h
+++ b/engines/alcachofa/common.h
@@ -103,7 +103,7 @@ struct FakeLock {
~FakeLock();
void operator = (FakeLock &&other) noexcept;
void release();
-
+
inline bool isReleased() const { return _semaphore == nullptr; }
private:
void debug(const char *action);
@@ -143,8 +143,7 @@ inline void syncStack(Common::Serializer &serializer, Common::Stack<T> &stack, v
serializeFunction(serializer, value);
stack.push(value);
}
- }
- else {
+ } else {
for (uint i = 0; i < size; i++)
serializeFunction(serializer, stack[i]);
}
diff --git a/engines/alcachofa/console.cpp b/engines/alcachofa/console.cpp
index bd22334c168..a6228a67a6a 100644
--- a/engines/alcachofa/console.cpp
+++ b/engines/alcachofa/console.cpp
@@ -53,8 +53,7 @@ Console::Console() : GUI::Debugger() {
registerCmd("playVideo", WRAP_METHOD(Console, cmdPlayVideo));
}
-Console::~Console() {
-}
+Console::~Console() {}
bool Console::isAnyDebugDrawingOn() const {
return
@@ -80,8 +79,7 @@ bool Console::cmdVar(int argc, const char **args) {
debugPrintf("Invalid variable name: %s", args[1]);
else
script.variable(args[1]) = value;
- }
- else if (argc == 2) {
+ } else if (argc == 2) {
bool hadSomeMatch = false;
for (auto it = script.beginVariables(); it != script.endVariables(); it++) {
if (matchString(it->_key.c_str(), args[1], true)) {
@@ -112,8 +110,7 @@ bool Console::cmdRoom(int argc, const char **args) {
debugPrintf("Player is currently in no room, cannot print details\n");
return true;
}
- }
- else {
+ } else {
room = g_engine->world().getRoomByName(args[1]);
if (room == nullptr) {
debugPrintf("Could not find room with exact name: %s\n", args[1]);
@@ -147,8 +144,7 @@ bool Console::cmdChangeRoom(int argc, const char **args) {
else if (argc == 1) {
Room *current = g_engine->player().currentRoom();
debugPrintf("Current room: %s\n", current == nullptr ? "<null>" : current->name().c_str());
- }
- else if (g_engine->world().getRoomByName(args[1]) == nullptr)
+ } else if (g_engine->world().getRoomByName(args[1]) == nullptr)
debugPrintf("Invalid room name: %s\n", args[1]);
else {
g_engine->player().changeRoom(args[1], true);
@@ -284,7 +280,7 @@ bool Console::cmdPlayVideo(int argc, const char **args) {
debugPrintf("Video ID can only be an integer\n");
return true;
}
-
+
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
// we have to close the console *now* to properly see the video
_debuggerDialog->close();
@@ -293,8 +289,7 @@ bool Console::cmdPlayVideo(int argc, const char **args) {
g_engine->playVideo(videoId);
return false;
- }
- else
+ } else
debugPrintf("usage: playVideo <id>\n");
return true;
}
diff --git a/engines/alcachofa/debug.h b/engines/alcachofa/debug.h
index 91150d6285e..027ed96a6b8 100644
--- a/engines/alcachofa/debug.h
+++ b/engines/alcachofa/debug.h
@@ -81,8 +81,7 @@ public:
if (_polygonI >= 0 && (uint)_polygonI < floor->polygonCount())
drawIntersectionsFor(floor->at((uint)_polygonI), renderer);
- else
- {
+ else {
for (uint i = 0; i < floor->polygonCount(); i++)
drawIntersectionsFor(floor->at(i), renderer);
}
@@ -94,8 +93,7 @@ private:
void drawIntersectionsFor(const Polygon &polygon, IDebugRenderer *renderer) {
auto &camera = g_engine->camera();
auto mousePos3D = g_engine->input().debugInput().mousePos3D();
- for (uint i = 0; i < polygon._points.size(); i++)
- {
+ for (uint i = 0; i < polygon._points.size(); i++) {
if (!polygon.intersectsEdge(i, _fromPos3D, mousePos3D))
continue;
auto a = camera.transform3Dto2D(polygon._points[i]);
@@ -123,8 +121,7 @@ public:
g_engine->drawQueue().draw();
auto &input = g_engine->input().debugInput();
- if (input.wasMouseRightPressed())
- {
+ if (input.wasMouseRightPressed()) {
g_engine->setDebugMode(DebugMode::None, 0);
return;
}
@@ -149,8 +146,7 @@ public:
private:
void teleport(MainCharacter &character, Point position) {
auto currentRoom = g_engine->player().currentRoom();
- if (character.room() != currentRoom)
- {
+ if (character.room() != currentRoom) {
character.resetTalking();
character.room() = currentRoom;
}
@@ -174,12 +170,12 @@ public:
const Room *room = g_engine->player().currentRoom();
uint floorCount = 0;
for (auto itObject = room->beginObjects(); itObject != room->endObjects(); ++itObject) {
- FloorColor *floor = dynamic_cast<FloorColor*>(*itObject);
+ FloorColor *floor = dynamic_cast<FloorColor *>(*itObject);
if (floor == nullptr)
continue;
if (objectI <= 0)
// dynamic_cast is not possible due to Shape not having virtual methods
- return new FloorColorDebugHandler(*(FloorColorShape*)(floor->shape()), useColor);
+ return new FloorColorDebugHandler(*(FloorColorShape *)(floor->shape()), useColor);
floorCount++;
objectI--;
}
@@ -200,15 +196,15 @@ public:
_isOnFloor = optColor.first;
if (!_isOnFloor) {
uint8 roomAlpha = (uint)(g_engine->player().currentRoom()->characterAlphaTint() * 255 / 100);
- optColor.second = Color{ 255, 255, 255, roomAlpha };
+ optColor.second = Color { 255, 255, 255, roomAlpha };
}
_curColor = _useColor
- ? Color{ optColor.second.r, optColor.second.g, optColor.second.b, 255 }
- : Color{ optColor.second.a, optColor.second.a, optColor.second.a, 255 };
+ ? Color { optColor.second.r, optColor.second.g, optColor.second.b, 255 }
+ : Color { optColor.second.a, optColor.second.a, optColor.second.a, 255 };
g_engine->world().mortadelo().color() =
g_engine->world().filemon().color() =
- _useColor ? optColor.second : Color{ 255, 255, 255, optColor.second.a };
+ _useColor ? optColor.second : Color { 255, 255, 255, optColor.second.a };
}
snprintf(_buffer, kBufferSize, "r:%3d g:%3d b:%3d a:%3d",
diff --git a/engines/alcachofa/game-objects.cpp b/engines/alcachofa/game-objects.cpp
index 735c4bf7673..1954551be68 100644
--- a/engines/alcachofa/game-objects.cpp
+++ b/engines/alcachofa/game-objects.cpp
@@ -48,7 +48,7 @@ Item::Item(const Item &other)
void Item::draw() {
if (!isEnabled())
return;
- Item* heldItem = g_engine->player().heldItem();
+ Item *heldItem = g_engine->player().heldItem();
if (heldItem == nullptr || !heldItem->name().equalsIgnoreCase(name()))
GraphicObject::draw();
}
@@ -61,8 +61,7 @@ void Item::trigger() {
player.triggerObject(this, "MIRAR");
else
heldItem = nullptr;
- }
- else if (heldItem == nullptr)
+ } else if (heldItem == nullptr)
heldItem = this;
else if (g_engine->script().hasProcedure(name(), heldItem->name()) ||
!g_engine->script().hasProcedure(heldItem->name(), name()))
@@ -73,8 +72,7 @@ void Item::trigger() {
ITriggerableObject::ITriggerableObject(ReadStream &stream)
: _interactionPoint(Shape(stream).firstPoint())
- , _interactionDirection((Direction)stream.readSint32LE()) {
-}
+ , _interactionDirection((Direction)stream.readSint32LE()) {}
void ITriggerableObject::onClick() {
auto heldItem = g_engine->player().heldItem();
@@ -135,11 +133,17 @@ CursorType Door::cursorType() const {
if (fromObject != CursorType::Point)
return fromObject;
switch (_interactionDirection) {
- case Direction::Up: return CursorType::LeaveUp;
- case Direction::Right: return CursorType::LeaveRight;
- case Direction::Down: return CursorType::LeaveDown;
- case Direction::Left: return CursorType::LeaveLeft;
- default: assert(false && "Invalid door character direction"); return fromObject;
+ case Direction::Up:
+ return CursorType::LeaveUp;
+ case Direction::Right:
+ return CursorType::LeaveRight;
+ case Direction::Down:
+ return CursorType::LeaveDown;
+ case Direction::Left:
+ return CursorType::LeaveLeft;
+ default:
+ assert(false && "Invalid door character direction");
+ return fromObject;
}
}
@@ -182,16 +186,14 @@ void Character::update() {
if (animateGraphic != nullptr) {
animateGraphic->topLeft() = Point(0, 0);
animateGraphic->update();
- }
- else if (_isTalking)
+ } else if (_isTalking)
updateTalkingAnimation();
else if (g_engine->world().somebodyUsing(this)) {
Graphic *talkGraphic = graphicOf(_curTalkingObject, &_graphicTalking);
talkGraphic->start(true);
talkGraphic->pause();
talkGraphic->update();
- }
- else
+ } else
_graphicNormal.update();
}
@@ -289,8 +291,7 @@ struct SayTextTask final : public Task {
SayTextTask(Process &process, Character *character, int32 dialogId)
: Task(process)
, _character(character)
- , _dialogId(dialogId) {
- }
+ , _dialogId(dialogId) {}
SayTextTask(Process &process, Serializer &s)
: Task(process) {
@@ -306,9 +307,8 @@ struct SayTextTask final : public Task {
while (true) {
g_engine->player().addLastDialogCharacter(_character);
- if (_soundHandle == SoundHandle {})
- {
- bool hasMortadeloVoice = g_engine->game().hasMortadeloVoice(_character);
+ if (_soundHandle == SoundHandle {}) {
+ bool hasMortadeloVoice = g_engine->game().hasMortadeloVoice(_character);
_soundHandle = g_engine->sounds().playVoice(
String::format(hasMortadeloVoice ? "M%04d" : "%04d", _dialogId),
0);
@@ -404,8 +404,7 @@ struct AnimateCharacterTask final : public Task {
_graphic->start(false);
if (_character->room() == g_engine->player().currentRoom())
_graphic->update();
- do
- {
+ do {
TASK_YIELD(2);
if (process().isActiveForPlayer() && g_engine->input().wasAnyMouseReleased())
_graphic->pause();
@@ -447,8 +446,7 @@ struct LerpLodBiasTask final : public Task {
: Task(process)
, _character(character)
, _targetLodBias(targetLodBias)
- , _durationMs(durationMs) {
- }
+ , _durationMs(durationMs) {}
LerpLodBiasTask(Process &process, Serializer &s)
: Task(process) {
@@ -575,8 +573,7 @@ static Direction getDirection(Point from, Point to) {
return slope > 1000 ? Direction::Up
: slope < -1000 ? Direction::Down
: Direction::Right;
- }
- else { // from.x > to.x
+ } else { // from.x > to.x
int slope = 1000 * delta.y / delta.x;
return slope > 1000 ? Direction::Up
: slope < -1000 ? Direction::Down
@@ -597,16 +594,14 @@ void WalkingCharacter::updateWalking() {
if (_sourcePos == targetPos) {
_currentPos = targetPos;
_pathPoints.pop();
- }
- else {
+ } else {
updateWalkingAnimation();
const int32 distanceToTarget = (int32)(sqrtf(_sourcePos.sqrDist(targetPos)));
if (_walkedDistance < distanceToTarget) {
// separated because having only 16 bits and multiplications seems dangerous
_currentPos.x = _sourcePos.x + _walkedDistance * (targetPos.x - _sourcePos.x) / distanceToTarget;
_currentPos.y = _sourcePos.y + _walkedDistance * (targetPos.y - _sourcePos.y) / distanceToTarget;
- }
- else {
+ } else {
_sourcePos = _currentPos = targetPos;
_pathPoints.pop();
_walkedDistance = 1;
@@ -638,8 +633,7 @@ void WalkingCharacter::updateWalkingAnimation() {
_lastWalkAnimFrame = expectedFrame;
stepFrameFrom = 2 * expectedFrame - 2;
stepFrameTo = 2 * expectedFrame;
- }
- else {
+ } else {
const int32 frameThreshold = _lastWalkAnimFrame <= halfFrameCount - 1
? _lastWalkAnimFrame
: (_lastWalkAnimFrame - halfFrameCount + 1) % (halfFrameCount - 2) + 1;
@@ -648,8 +642,7 @@ void WalkingCharacter::updateWalkingAnimation() {
if (expectedFrame >= frameThreshold) {
stepFrameFrom = 2 * expectedFrame - 2;
stepFrameTo = 2 * expectedFrame;
- }
- else {
+ } else {
stepFrameFrom = 2 * (halfFrameCount - 2);
stepFrameTo = 2 * halfFrameCount - 2;
}
@@ -661,8 +654,7 @@ void WalkingCharacter::updateWalkingAnimation() {
_graphicNormal.frameI() = 2 * expectedFrame; // especially this: wtf?
}
-void WalkingCharacter::onArrived() {
-}
+void WalkingCharacter::onArrived() {}
void WalkingCharacter::stopWalking(Direction direction) {
// be careful, the original engine had two versions of this method
@@ -778,12 +770,11 @@ void WalkingCharacter::syncGame(Serializer &serializer) {
struct ArriveTask : public Task {
ArriveTask(Process &process, const WalkingCharacter *character)
: Task(process)
- , _character(character) {
- }
+ , _character(character) {}
ArriveTask(Process &process, Serializer &s)
: Task(process) {
- syncGame(s);
+ syncGame(s);
}
TaskReturn run() override {
@@ -891,8 +882,7 @@ void MainCharacter::walkTo(
if (!otherCharacter->isBusy()) {
if (activeFloor != nullptr && activeFloor->findEvadeTarget(evadeTarget, activeDepthScale, avoidanceDistSqr, evadeTarget))
otherCharacter->WalkingCharacter::walkTo(evadeTarget);
- }
- else if (!willIBeBusy) {
+ } else if (!willIBeBusy) {
if (activeFloor != nullptr)
activeFloor->findEvadeTarget(evadeTarget, activeDepthScale, avoidanceDistSqr, target);
}
@@ -908,8 +898,7 @@ void MainCharacter::draw() {
if (_currentPos.y <= g_engine->world().filemon()._currentPos.y) {
g_engine->world().mortadelo().drawInner();
g_engine->world().filemon().drawInner();
- }
- else {
+ } else {
g_engine->world().filemon().drawInner();
g_engine->world().mortadelo().drawInner();
}
@@ -1040,8 +1029,7 @@ struct DialogMenuTask : public Task {
DialogMenuTask(Process &process, MainCharacter *character)
: Task(process)
, _input(g_engine->input())
- , _character(character) {
- }
+ , _character(character) {}
DialogMenuTask(Process &process, Serializer &s)
: Task(process)
@@ -1117,7 +1105,7 @@ private:
g_engine->globalUI().dialogFont(),
g_engine->world().getDialogLine(itLine._dialogId),
Point(kTextXOffset, itLine._yPosition),
- maxTextWidth(), false, isHovered ? Color{ 255, 255, 128, 255 } : kWhite, -kForegroundOrderCount + 2);
+ maxTextWidth(), false, isHovered ? Color { 255, 255, 128, 255 } : kWhite, -kForegroundOrderCount + 2);
isSomethingHovered = isSomethingHovered || isHovered;
if (isHovered && _input.wasMouseLeftReleased())
return i - 1;
@@ -1169,8 +1157,7 @@ const char *FloorColor::typeName() const { return "FloorColor"; }
FloorColor::FloorColor(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
- , _shape(stream) {
-}
+ , _shape(stream) {}
void FloorColor::update() {
auto updateFor = [&] (MainCharacter &character) {
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index f7bc9bd7c8e..8ea1da03f07 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -33,8 +33,7 @@ Game::Game()
#else // For release builds the game might still work or the user might still be able to save and restart
: _message(warning)
#endif
-{
-}
+{}
void Game::onLoadedGameFiles() {}
@@ -81,7 +80,7 @@ bool Game::shouldTriggerDoor(const Door *door) {
return true;
}
-void Game::onUserChangedCharacter() { }
+void Game::onUserChangedCharacter() {}
bool Game::hasMortadeloVoice(const Character *character) {
return character == &g_engine->world().mortadelo();
diff --git a/engines/alcachofa/general-objects.cpp b/engines/alcachofa/general-objects.cpp
index 997f550649e..fde931008eb 100644
--- a/engines/alcachofa/general-objects.cpp
+++ b/engines/alcachofa/general-objects.cpp
@@ -52,20 +52,15 @@ void ObjectBase::toggle(bool isEnabled) {
_isEnabled = isEnabled;
}
-void ObjectBase::draw() {
-}
+void ObjectBase::draw() {}
-void ObjectBase::drawDebug() {
-}
+void ObjectBase::drawDebug() {}
-void ObjectBase::update() {
-}
+void ObjectBase::update() {}
-void ObjectBase::loadResources() {
-}
+void ObjectBase::loadResources() {}
-void ObjectBase::freeResources() {
-}
+void ObjectBase::freeResources() {}
void ObjectBase::syncGame(Serializer &serializer) {
serializer.syncAsByte(_isEnabled);
@@ -99,8 +94,7 @@ GraphicObject::GraphicObject(Room *room, ReadStream &stream)
GraphicObject::GraphicObject(Room *room, const char *name)
: ObjectBase(room, name)
, _type(GraphicObjectType::Normal)
- , _posterizeAlpha(0) {
-}
+ , _posterizeAlpha(0) {}
void GraphicObject::draw() {
if (!isEnabled() || !_graphic.hasAnimation())
@@ -127,8 +121,7 @@ void GraphicObject::drawDebug() {
topLeftTmp.z() = _graphic.scale();
_graphic.animation().outputRect3D(_graphic.frameI(), scale, topLeftTmp, size);
topLeft = as2D(topLeftTmp);
- }
- else
+ } else
_graphic.animation().outputRect2D(_graphic.frameI(), scale, topLeft, size);
Vector2d points[] = {
@@ -241,8 +234,7 @@ const char *ShapeObject::typeName() const { return "ShapeObject"; }
ShapeObject::ShapeObject(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _shape(stream)
- , _cursorType((CursorType)stream.readSint32LE()) {
-}
+ , _cursorType((CursorType)stream.readSint32LE()) {}
void ShapeObject::update() {
if (isEnabled())
@@ -272,8 +264,7 @@ void ShapeObject::onHoverStart() {
onHoverUpdate();
}
-void ShapeObject::onHoverEnd() {
-}
+void ShapeObject::onHoverEnd() {}
void ShapeObject::onHoverUpdate() {
g_engine->drawQueue().add<TextDrawRequest>(
@@ -299,13 +290,11 @@ void ShapeObject::updateSelection() {
onClick();
else
onHoverUpdate();
- }
- else {
+ } else {
_wasSelected = true;
onHoverStart();
}
- }
- else if (_wasSelected) {
+ } else if (_wasSelected) {
_wasSelected = false;
onHoverEnd();
}
diff --git a/engines/alcachofa/global-ui.cpp b/engines/alcachofa/global-ui.cpp
index 0d5d1004b23..938fefd8fc1 100644
--- a/engines/alcachofa/global-ui.cpp
+++ b/engines/alcachofa/global-ui.cpp
@@ -91,14 +91,12 @@ bool GlobalUI::updateOpeningInventory() {
if (deltaTime >= 1000) {
_isOpeningInventory = false;
g_engine->world().inventory().open();
- }
- else {
+ } else {
deltaTime = MIN<uint32>(300, deltaTime);
g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed - 1)));
}
return true;
- }
- else if (userWantsToOpenInventory) {
+ } else if (userWantsToOpenInventory) {
_isClosingInventory = false;
_isOpeningInventory = true;
_timeForInventory = g_engine->getMillis();
@@ -136,8 +134,7 @@ bool GlobalUI::updateChangingCharacter() {
if (!isHoveringChangeButton())
return false;
- if (g_engine->input().wasMouseLeftPressed())
- {
+ if (g_engine->input().wasMouseLeftPressed()) {
player.pressedObject() = &_changeButton;
return true;
}
@@ -174,8 +171,7 @@ void GlobalUI::drawChangingButton() {
return;
auto anim = activeAnimation();
- if (!_changeButton.hasAnimation() || &_changeButton.animation() != anim)
- {
+ if (!_changeButton.hasAnimation() || &_changeButton.animation() != anim) {
_changeButton.setAnimation(anim);
_changeButton.pause();
_changeButton.lastTime() = 42 * (anim->frameCount() - 1) + 1;
@@ -184,8 +180,7 @@ void GlobalUI::drawChangingButton() {
_changeButton.topLeft() = { (int16)(g_system->getWidth() + 2), -2 };
if (isHoveringChangeButton() &&
g_engine->input().isMouseLeftDown() &&
- player.pressedObject() == &_changeButton)
- {
+ player.pressedObject() == &_changeButton) {
_changeButton.topLeft().x -= 2;
_changeButton.topLeft().y += 2;
}
@@ -199,8 +194,7 @@ struct CenterBottomTextTask : public Task {
CenterBottomTextTask(Process &process, int32 dialogId, uint32 durationMs)
: Task(process)
, _dialogId(dialogId)
- , _durationMs(durationMs) {
- }
+ , _durationMs(durationMs) {}
CenterBottomTextTask(Process &process, Serializer &s)
: Task(process) {
@@ -239,7 +233,7 @@ struct CenterBottomTextTask : public Task {
s.syncAsSint32LE(_dialogId);
s.syncAsUint32LE(_startTime);
s.syncAsUint32LE(_durationMs);
- }
+ }
const char *taskName() const override;
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index bc27347e6d4..e9881b189c7 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -177,8 +177,7 @@ public:
GL_CALL(glDisable(GL_TEXTURE_2D));
GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
_currentTexture = nullptr;
- }
- else {
+ } else {
if (_currentTexture == nullptr) {
GL_CALL(glEnable(GL_TEXTURE_2D));
GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
@@ -253,7 +252,9 @@ public:
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR)); // we have to pre-multiply
break;
- default: assert(false && "Invalid blend mode"); break;
+ default:
+ assert(false && "Invalid blend mode");
+ break;
}
_currentBlendMode = blendMode;
}
@@ -327,8 +328,7 @@ public:
}
float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
- if (_currentBlendMode == BlendMode::Tinted)
- {
+ if (_currentBlendMode == BlendMode::Tinted) {
colors[0] *= colors[3];
colors[1] *= colors[3];
colors[2] *= colors[3];
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 40b5375797f..593b617bf57 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -57,8 +57,7 @@ void IDebugRenderer::debugShape(const Shape &shape, Color color) {
AnimationBase::AnimationBase(String fileName, AnimationFolder folder)
: _fileName(move(fileName))
- , _folder(folder) {
-}
+ , _folder(folder) {}
AnimationBase::~AnimationBase() {
freeImages();
@@ -70,10 +69,18 @@ void AnimationBase::load() {
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");
+ 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 (_fileName.size() < 4 || scumm_strnicmp(_fileName.end() - 4, ".AN0", 4) != 0)
_fileName += ".AN0";
@@ -236,8 +243,7 @@ Point AnimationBase::imageSize(int32 imageI) const {
}
Animation::Animation(String fileName, AnimationFolder folder)
- : AnimationBase(fileName, folder) {
-}
+ : AnimationBase(fileName, folder) {}
void Animation::load() {
if (_isLoaded)
@@ -492,8 +498,7 @@ void Font::drawCharacter(int32 imageI, Point centerPoint, Color color) {
renderer.quad(center, size, color, Angle(), _texMins[imageI], _texMaxs[imageI]);
}
-Graphic::Graphic() {
-}
+Graphic::Graphic() {}
Graphic::Graphic(ReadStream &stream) {
_topLeft.x = stream.readSint16LE();
@@ -515,8 +520,7 @@ Graphic::Graphic(const Graphic &other)
, _isLooping(other._isLooping)
, _lastTime(other._lastTime)
, _frameI(other._frameI)
- , _depthScale(other._depthScale) {
-}
+ , _depthScale(other._depthScale) {}
void Graphic::loadResources() {
if (_animation != nullptr)
@@ -593,8 +597,7 @@ static int8 shiftAndClampOrder(int8 order) {
}
IDrawRequest::IDrawRequest(int8 order)
- : _order(shiftAndClampOrder(order)) {
-}
+ : _order(shiftAndClampOrder(order)) {}
AnimationDrawRequest::AnimationDrawRequest(Graphic &graphic, bool is3D, BlendMode blendMode, float lodBias)
: IDrawRequest(graphic._order)
@@ -678,7 +681,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
// allocate on drawQueue to prevent having destruct it
assert(originalText != nullptr);
auto textLen = strlen(originalText);
- char *text = (char*)g_engine->drawQueue().allocator().allocateRaw(textLen + 1, 1);
+ char *text = (char *)g_engine->drawQueue().allocator().allocateRaw(textLen + 1, 1);
memcpy(text, originalText, textLen + 1);
// split into trimmed lines
@@ -705,8 +708,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
itChar = trimTrailing(itChar, itLine, true) + 1;
itLine = trimLeading(itLine, itChar);
_allLines[lineCount] = TextLine(itLine, itChar - itLine);
- }
- else
+ } else
_allLines[lineCount] = TextLine(itLine, itChar - itLine);
itChar = trimLeading(itChar, textEnd);
lineCount++;
@@ -739,8 +741,7 @@ TextDrawRequest::TextDrawRequest(Font &font, const char *originalText, Point pos
pos.x = screenW - _width / 2 - 1;
for (auto &linePosX : _posX)
linePosX = pos.x - linePosX / 2;
- }
- else
+ } else
fill(_posX.begin(), _posX.end(), pos.x);
// setup height and y position
@@ -772,8 +773,7 @@ void TextDrawRequest::draw() {
FadeDrawRequest::FadeDrawRequest(FadeType type, float value, int8 order)
: IDrawRequest(order)
, _type(type)
- , _value(value) {
-}
+ , _value(value) {}
void FadeDrawRequest::draw() {
Color color;
@@ -808,8 +808,7 @@ struct FadeTask : public Task {
, _duration(duration)
, _easingType(easingType)
, _order(order)
- , _permanentFadeAction(permanentFadeAction) {
- }
+ , _permanentFadeAction(permanentFadeAction) {}
FadeTask(Process &process, Serializer &s)
: Task(process) {
@@ -881,8 +880,7 @@ Task *fade(Process &process, FadeType fadeType,
BorderDrawRequest::BorderDrawRequest(Rect rect, Color color)
: IDrawRequest(-kForegroundOrderCount)
, _rect(rect)
- , _color(color) {
-}
+ , _color(color) {}
void BorderDrawRequest::draw() {
auto &renderer = g_engine->renderer();
diff --git a/engines/alcachofa/input.cpp b/engines/alcachofa/input.cpp
index 56ad040d213..bacbd725e26 100644
--- a/engines/alcachofa/input.cpp
+++ b/engines/alcachofa/input.cpp
@@ -41,8 +41,7 @@ void Input::nextFrame() {
}
bool Input::handleEvent(const Common::Event &event) {
- if (_debugInput != nullptr)
- {
+ if (_debugInput != nullptr) {
auto result = _debugInput->handleEvent(event);
_mousePos2D = _debugInput->mousePos2D(); // even for debug input we want to e.g. draw a cursor
_mousePos3D = _debugInput->mousePos3D();
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index 3a23778f670..d9ebcb78606 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -32,7 +32,7 @@ using namespace Common;
using namespace Graphics;
namespace Alcachofa {
-
+
static void createThumbnail(ManagedSurface &surface) {
surface.create(kBigThumbnailWidth, kBigThumbnailHeight, PixelFormat::createFormatRGBA32());
}
@@ -121,15 +121,13 @@ void Menu::updateSelectedSavefile(bool hasJustSaved) {
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 (isOldSavefile) {
if (!tryReadOldSavefile()) {
_selectedSavefileDescription = String::format("Savestate %d",
parseSavestateSlot(_savefiles[_selectedSavefileI]));
createThumbnail(_selectedThumbnail);
}
- }
- else {
+ } else {
// the unsaved gamestate is shown as grayscale
_selectedThumbnail.copyFrom(_bigThumbnail);
convertToGrayscale(_selectedThumbnail);
@@ -240,8 +238,7 @@ void Menu::triggerSave() {
String fileName;
if (_selectedSavefileI < _savefiles.size()) {
fileName = _savefiles[_selectedSavefileI]; // overwrite a previous save
- }
- else {
+ } else {
// for a new savefile we figure out the next slot index
int nextSlot = _savefiles.empty()
? 1 // start at one to keep autosave alone
@@ -260,8 +257,7 @@ void Menu::triggerSave() {
g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), _selectedSavefileDescription, false);
_savefiles.push_back(fileName);
updateSelectedSavefile(true);
- }
- else {
+ } else {
GUI::MessageDialog dialog(error.getTranslatedDesc());
dialog.runModal();
}
diff --git a/engines/alcachofa/metaengine.h b/engines/alcachofa/metaengine.h
index b79dd84a250..ada41267045 100644
--- a/engines/alcachofa/metaengine.h
+++ b/engines/alcachofa/metaengine.h
@@ -53,7 +53,7 @@ public:
void getSavegameThumbnail(Graphics::Surface &thumb) override;
-
+
};
#endif // ALCACHOFA_METAENGINE_H
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 8360d51bab2..13757fd92e8 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -58,20 +58,31 @@ void Player::updateCursor() {
else {
auto type = _selectedObject->cursorType();
switch (type) {
- case CursorType::LeaveUp: _cursorFrameI = 8; break;
- case CursorType::LeaveRight: _cursorFrameI = 10; break;
- case CursorType::LeaveDown: _cursorFrameI = 12; break;
- case CursorType::LeaveLeft: _cursorFrameI = 14; break;
- case CursorType::WalkTo: _cursorFrameI = 6; break;
+ case CursorType::LeaveUp:
+ _cursorFrameI = 8;
+ break;
+ case CursorType::LeaveRight:
+ _cursorFrameI = 10;
+ break;
+ case CursorType::LeaveDown:
+ _cursorFrameI = 12;
+ break;
+ case CursorType::LeaveLeft:
+ _cursorFrameI = 14;
+ break;
+ case CursorType::WalkTo:
+ _cursorFrameI = 6;
+ break;
case CursorType::Point:
- default: _cursorFrameI = 0; break;
+ default:
+ _cursorFrameI = 0;
+ break;
}
if (_cursorFrameI != 0) {
if (g_engine->input().isAnyMouseDown() && _pressedObject == _selectedObject)
_cursorFrameI++;
- }
- else if (g_engine->input().isMouseLeftDown())
+ } else if (g_engine->input().isMouseLeftDown())
_cursorFrameI = 2;
else if (g_engine->input().isMouseRightDown())
_cursorFrameI = 4;
@@ -84,8 +95,7 @@ void Player::drawCursor(bool forceDefaultCursor) {
if (forceDefaultCursor)
_cursorFrameI = 0;
g_engine->drawQueue().add<AnimationDrawRequest>(_cursorAnimation.get(), _cursorFrameI, as2D(cursorPos), -10);
- }
- else {
+ } else {
auto itemGraphic = _heldItem->graphic();
assert(itemGraphic != nullptr);
auto &animation = itemGraphic->animation();
@@ -156,10 +166,15 @@ MainCharacter *Player::inactiveCharacter() const {
FakeSemaphore &Player::semaphoreFor(MainCharacterKind kind) {
static FakeSemaphore dummySemaphore("dummy");
switch (kind) {
- case MainCharacterKind::None: return _semaphore;
- case MainCharacterKind::Mortadelo: return g_engine->world().mortadelo().semaphore();
- case MainCharacterKind::Filemon: return g_engine->world().filemon().semaphore();
- default: assert(false && "Invalid main character kind"); return dummySemaphore;
+ case MainCharacterKind::None:
+ return _semaphore;
+ case MainCharacterKind::Mortadelo:
+ return g_engine->world().mortadelo().semaphore();
+ case MainCharacterKind::Filemon:
+ return g_engine->world().filemon().semaphore();
+ default:
+ assert(false && "Invalid main character kind");
+ return dummySemaphore;
}
}
@@ -172,8 +187,7 @@ void Player::triggerObject(ObjectBase *object, const char *action) {
if (strcmp(action, "MIRAR") == 0 || inactiveCharacter()->currentlyUsing() == object) {
action = "MIRAR";
_activeCharacter->currentlyUsing() = nullptr;
- }
- else
+ } else
_activeCharacter->currentlyUsing() = object;
auto &script = g_engine->script();
@@ -254,7 +268,7 @@ struct DoorTask : public Task {
s.syncAsByte(hasMusicLock);
if (s.isLoading() && hasMusicLock)
_musicLock = FakeLock("door-music", g_engine->sounds().musicSemaphore());
-
+
_lock = FakeLock("door", _character->semaphore());
findTarget();
}
@@ -347,7 +361,7 @@ void Player::syncGame(Serializer &s) {
}
FakeSemaphore::sync(s, _semaphore);
-
+
String roomName;
if (s.isSaving()) {
roomName =
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 0d162b797cc..3764a82664e 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -31,8 +31,7 @@ using namespace Common;
namespace Alcachofa {
-Room::Room(World *world, SeekableReadStream &stream) : Room(world, stream, false) {
-}
+Room::Room(World *world, SeekableReadStream &stream) : Room(world, stream, false) {}
static ObjectBase *readRoomObject(Room *room, const String &type, ReadStream &stream) {
if (type == ObjectBase::kClassName)
@@ -100,19 +99,16 @@ Room::Room(World *world, SeekableReadStream &stream, bool hasUselessByte)
stream.readByte();
uint32 objectEnd = stream.readUint32LE();
- while (objectEnd > 0)
- {
+ while (objectEnd > 0) {
const auto type = readVarString(stream);
auto object = readRoomObject(this, type, stream);
if (object == nullptr) {
g_engine->game().unknownRoomObject(type);
stream.seek(objectEnd, SEEK_SET);
- }
- else if (stream.pos() < objectEnd) {
+ } else if (stream.pos() < objectEnd) {
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
+ } 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());
if (object != nullptr)
@@ -216,8 +212,7 @@ void Room::updateInteraction() {
player.activeCharacter()->walkToMouse();
g_engine->camera().setFollow(player.activeCharacter());
}
- }
- else {
+ } else {
player.selectedObject()->markSelected();
if (input.wasAnyMousePressed())
player.pressedObject() = player.selectedObject();
@@ -270,11 +265,9 @@ void Room::drawDebug() {
if (g_engine->console().showFloorEdges()) {
auto &camera = g_engine->camera();
- for (uint polygonI = 0; polygonI < floor.polygonCount(); polygonI++)
- {
+ for (uint polygonI = 0; polygonI < floor.polygonCount(); polygonI++) {
auto polygon = floor.at(polygonI);
- for (uint pointI = 0; pointI < polygon._points.size(); pointI++)
- {
+ for (uint pointI = 0; pointI < polygon._points.size(); pointI++) {
int32 targetI = floor.edgeTarget(polygonI, pointI);
if (targetI < 0)
continue;
@@ -328,8 +321,7 @@ ShapeObject *Room::getSelectedObject(ShapeObject *best) const {
}
OptionsMenu::OptionsMenu(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {
-}
+ : Room(world, stream, true) {}
bool OptionsMenu::updateInput() {
if (!Room::updateInput())
@@ -344,8 +336,7 @@ bool OptionsMenu::updateInput() {
}
_lastSelectedObject->markSelected();
- }
- else
+ } else
_lastSelectedObject = currentSelectedObject;
if (_idleArm != nullptr)
_idleArm->toggle(false);
@@ -364,16 +355,13 @@ void OptionsMenu::clearLastSelectedObject() {
}
ConnectMenu::ConnectMenu(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {
-}
+ : Room(world, stream, true) {}
ListenMenu::ListenMenu(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {
-}
+ : Room(world, stream, true) {}
Inventory::Inventory(World *world, SeekableReadStream &stream)
- : Room(world, stream, true) {
-}
+ : Room(world, stream, true) {}
Inventory::~Inventory() {
// No need to delete items, they are room objects and thus deleted in Room::~Room
@@ -545,8 +533,10 @@ World::~World() {
MainCharacter &World::getMainCharacterByKind(MainCharacterKind kind) const {
switch (kind) {
- case MainCharacterKind::Mortadelo: return *_mortadelo;
- case MainCharacterKind::Filemon: return *_filemon;
+ case MainCharacterKind::Mortadelo:
+ return *_mortadelo;
+ case MainCharacterKind::Filemon:
+ return *_filemon;
default:
error("Invalid character kind given to getMainCharacterByKind");
}
@@ -554,8 +544,10 @@ MainCharacter &World::getMainCharacterByKind(MainCharacterKind kind) const {
MainCharacter &World::getOtherMainCharacterByKind(MainCharacterKind kind) const {
switch (kind) {
- case MainCharacterKind::Mortadelo: return *_filemon;
- case MainCharacterKind::Filemon: return *_mortadelo;
+ case MainCharacterKind::Mortadelo:
+ return *_filemon;
+ case MainCharacterKind::Filemon:
+ return *_mortadelo;
default:
error("Invalid character kind given to getOtherMainCharacterByKind");
}
@@ -691,8 +683,7 @@ bool World::loadWorldFile(const char *path) {
if (file.pos() < roomEnd) {
g_engine->game().notEnoughRoomDataRead(path, file.pos(), roomEnd);
file.seek(roomEnd, SEEK_SET);
- }
- else if (file.pos() > roomEnd) // this surely is not recoverable
+ } else if (file.pos() > roomEnd) // this surely is not recoverable
error("Read past the room data for world %s", path);
roomEnd = file.readUint32LE();
}
@@ -770,7 +761,7 @@ void World::loadDialogLines() {
* Name 123, "This is the dialog line\r\n
* Name 123 This is the dialog line \r\n
*
- * - The ID does not have to be correct, it is ignored by the original engine.
+ * - 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);
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index fbc83ea14df..d58cb44f681 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -87,8 +87,7 @@ void Task::errorForUnexpectedObjectType(const ObjectBase *base) const {
DelayTask::DelayTask(Process &process, uint32 millis)
: Task(process)
- , _endTime(millis) {
-}
+ , _endTime(millis) {}
DelayTask::DelayTask(Process &process, Serializer &s)
: Task(process) {
@@ -118,8 +117,7 @@ DECLARE_TASK(DelayTask)
Process::Process(ProcessId pid, MainCharacterKind characterKind)
: _pid(pid)
, _character(characterKind)
- , _name("Unnamed process") {
-}
+ , _name("Unnamed process") {}
Process::Process(Serializer &s) {
syncGame(s);
@@ -138,7 +136,8 @@ TaskReturnType Process::run() {
while (!_tasks.empty()) {
TaskReturn ret = _tasks.top()->run();
switch (ret.type()) {
- case TaskReturnType::Yield: return TaskReturnType::Yield;
+ case TaskReturnType::Yield:
+ return TaskReturnType::Yield;
case TaskReturnType::Waiting:
_tasks.push(ret.taskToWaitFor());
break;
@@ -158,10 +157,18 @@ void Process::debugPrint() {
auto *debugger = g_engine->getDebugger();
const char *characterName;
switch (_character) {
- case MainCharacterKind::None: characterName = " <none>"; break;
- case MainCharacterKind::Filemon: characterName = " Filemon"; break;
- case MainCharacterKind::Mortadelo: characterName = "Mortadelo"; break;
- default: characterName = "<invalid>"; break;
+ case MainCharacterKind::None:
+ characterName = " <none>";
+ break;
+ case MainCharacterKind::Filemon:
+ characterName = " Filemon";
+ break;
+ case MainCharacterKind::Mortadelo:
+ characterName = "Mortadelo";
+ break;
+ default:
+ characterName = "<invalid>";
+ break;
}
debugger->debugPrintf("pid: %3u char: %s ret: %2d \"%s\"\n", _pid, characterName, _lastReturnValue, _name.c_str());
@@ -200,8 +207,7 @@ void Process::syncGame(Serializer &s) {
assert(_tasks.empty());
for (uint i = 0; i < count; i++)
_tasks.push(readTask(*this, s));
- }
- else {
+ } else {
String taskName;
for (uint i = 0; i < count; i++) {
taskName = _tasks[i]->taskName();
@@ -363,8 +369,7 @@ void Scheduler::syncGame(Serializer &s) {
processes->reserve(count);
for (uint32 i = 0; i < count; i++)
processes->push_back(new Process(s));
- }
- else {
+ } else {
for (Process *process : *processes)
process->syncGame(s);
}
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 1f9c79b34f0..50c685c5a65 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -101,7 +101,7 @@ protected:
// or we could just use const_cast and promise that we won't modify the object itself
ObjectBase *base = const_cast<Common::remove_const_t<TObject> *>(object);
syncObjectAsString(s, base, optional);
- object = dynamic_cast<TObject*>(base);
+ object = dynamic_cast<TObject *>(base);
if (object == nullptr && base != nullptr)
errorForUnexpectedObjectType(base);
}
diff --git a/engines/alcachofa/script.cpp b/engines/alcachofa/script.cpp
index 64b6437d85e..9d505bd0217 100644
--- a/engines/alcachofa/script.cpp
+++ b/engines/alcachofa/script.cpp
@@ -41,8 +41,7 @@ enum ScriptDebugLevel {
ScriptInstruction::ScriptInstruction(ReadStream &stream)
: _op((ScriptOp)stream.readSint32LE())
- , _arg(stream.readSint32LE()) {
-}
+ , _arg(stream.readSint32LE()) {}
Script::Script() {
File file;
@@ -134,8 +133,7 @@ bool Script::hasProcedure(const Common::String &procedure) const {
struct ScriptTimerTask : public Task {
ScriptTimerTask(Process &process, int32 durationSec)
: Task(process)
- , _durationSec(durationSec) {
- }
+ , _durationSec(durationSec) {}
ScriptTimerTask(Process &process, Serializer &s)
: Task(process) {
@@ -252,11 +250,21 @@ struct ScriptTask : public Task {
else {
const auto &top = _stack.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;
+ 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;
}
}
}
@@ -298,8 +306,7 @@ struct ScriptTask : public Task {
if (kernelReturn.type() == TaskReturnType::Waiting) {
_returnsFromKernelCall = true;
return kernelReturn;
- }
- else
+ } else
handleReturnFromKernelCall(kernelReturn.returnValue());
}break;
case ScriptOp::JumpIfFalse:
@@ -363,6 +370,7 @@ struct ScriptTask : public Task {
}break;
default:
g_engine->game().unknownInstruction(instruction);
+ break;
}
}
}
@@ -384,8 +392,7 @@ struct ScriptTask : public Task {
if (s.isLoading()) {
for (uint i = 0; i < count; i++)
_stack.push(StackEntry(s));
- }
- else {
+ } else {
for (uint i = 0; i < count; i++)
_stack[i].syncGame(s);
}
@@ -600,8 +607,7 @@ private:
if (scumm_stricmp(getStringArg(0), "SALIR") == 0) {
g_engine->quitGame();
g_engine->player().changeRoom("SALIR", true);
- }
- else if (scumm_stricmp(getStringArg(0), "MENUPRINCIPALINICIO") == 0)
+ } else if (scumm_stricmp(getStringArg(0), "MENUPRINCIPALINICIO") == 0)
warning("STUB: change room to MenuPrincipalInicio special case");
else {
auto targetRoom = g_engine->world().getRoomByName(getStringArg(0));
@@ -624,8 +630,7 @@ private:
if (process().character() == MainCharacterKind::None) {
if (g_engine->player().currentRoom() != nullptr)
g_engine->player().currentRoom()->toggleActiveFloor();
- }
- else
+ } else
g_engine->world().getMainCharacterByKind(process().character()).room()->toggleActiveFloor();
return TaskReturn::finish(1);
@@ -646,8 +651,7 @@ private:
graphicObject->toggle(true);
graphicObject->graphic()->start(false);
return TaskReturn::finish(1);
- }
- else
+ } else
return TaskReturn::waitFor(graphicObject->animate(process()));
}
@@ -721,12 +725,10 @@ private:
}
float targetLodBias = getNumberArg(1) * 0.01f;
int32 durationMs = getNumberArg(2);
- if (durationMs <= 0)
- {
+ if (durationMs <= 0) {
character->lodBias() = targetLodBias;
return TaskReturn::finish(1);
- }
- else
+ } else
return TaskReturn::waitFor(character->lerpLodBias(process(), targetLodBias, durationMs));
}
case ScriptKernelTask::AnimateCharacter: {
@@ -749,8 +751,7 @@ private:
return TaskReturn::finish(1);
}
ObjectBase *talkObject = getObjectArg(1);
- if (talkObject == nullptr && *getStringArg(1) != '\0')
- {
+ if (talkObject == nullptr && *getStringArg(1) != '\0') {
g_engine->game().unknownAnimateTalkingObject(getStringArg(1));
return TaskReturn::finish(1);
}
@@ -798,13 +799,19 @@ private:
}
case ScriptKernelTask::ClearInventory:
switch ((MainCharacterKind)getNumberArg(0)) {
- case MainCharacterKind::Mortadelo: g_engine->world().mortadelo().clearInventory(); break;
- case MainCharacterKind::Filemon: g_engine->world().filemon().clearInventory(); break;
- default: g_engine->game().unknownClearInventoryTarget(getNumberArg(0)); break;
+ case MainCharacterKind::Mortadelo:
+ g_engine->world().mortadelo().clearInventory();
+ break;
+ case MainCharacterKind::Filemon:
+ g_engine->world().filemon().clearInventory();
+ break;
+ default:
+ g_engine->game().unknownClearInventoryTarget(getNumberArg(0));
+ break;
}
return TaskReturn::finish(1);
- // Camera tasks
+ // Camera tasks
case ScriptKernelTask::WaitCamStopping:
return TaskReturn::waitFor(g_engine->camera().waitToStop(process()));
case ScriptKernelTask::CamFollow:
@@ -980,8 +987,7 @@ void Script::updateCommonVariables() {
if (variable("CalcularTiempoSinPulsarRaton")) {
if (_scriptTimer == 0)
_scriptTimer = g_engine->getMillis();
- }
- else
+ } else
_scriptTimer = 0;
variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
diff --git a/engines/alcachofa/shape.cpp b/engines/alcachofa/shape.cpp
index 0459a6bfa99..8d52dd036dd 100644
--- a/engines/alcachofa/shape.cpp
+++ b/engines/alcachofa/shape.cpp
@@ -31,7 +31,7 @@ static int sideOfLine(Point a, Point b, Point q) {
}
static bool lineIntersects(Point a1, Point b1, Point a2, Point b2) {
- return (sideOfLine(a1, b1, a2) > 0) != (sideOfLine(a1, b1, b2) > 0);
+ return (sideOfLine(a1, b1, a2) > 0) != (sideOfLine(a1, b1, b2) > 0);
}
static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
@@ -43,8 +43,7 @@ static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
return lineIntersects(b1, a1, b2, a2) && lineIntersects(b2, a2, b1, a1);
else
return lineIntersects(a1, b1, b2, a2) && lineIntersects(b2, a2, a1, b1);
- }
- else {
+ } else {
if (a1.x > b1.x)
return lineIntersects(b1, a1, a2, b2) && lineIntersects(a2, b2, b1, a1);
else
@@ -67,9 +66,12 @@ EdgeDistances::EdgeDistances(Point edgeA, Point edgeB, Point query) {
bool Polygon::contains(Point query) const {
switch (_points.size()) {
- case 0: return false;
- case 1: return query == _points[0];
- case 2: return edgeDistances(0, query)._toEdge < 2.0f;
+ case 0:
+ return false;
+ case 1:
+ return query == _points[0];
+ case 2:
+ return edgeDistances(0, query)._toEdge < 2.0f;
default:
// we assume that the polygon is convex
for (uint i = 1; i < _points.size(); i++) {
@@ -108,23 +110,18 @@ Point Polygon::closestPointTo(Point query, float &distanceSqr) const {
assert(_points.size() > 0);
Common::Point bestPoint = {};
distanceSqr = std::numeric_limits<float>::infinity();
- for (uint i = 0; i < _points.size(); i++)
- {
+ for (uint i = 0; i < _points.size(); i++) {
auto edgeDists = edgeDistances(i, query);
- if (edgeDists._onEdge < 0.0f)
- {
+ if (edgeDists._onEdge < 0.0f) {
float pointDistSqr = as2D(query - _points[i]).getSquareMagnitude();
- if (pointDistSqr < distanceSqr)
- {
+ if (pointDistSqr < distanceSqr) {
bestPoint = _points[i];
distanceSqr = pointDistSqr;
}
}
- if (edgeDists._onEdge >= 0.0f && edgeDists._onEdge <= edgeDists._edgeLength)
- {
+ if (edgeDists._onEdge >= 0.0f && edgeDists._onEdge <= edgeDists._edgeLength) {
float edgeDistSqr = powf(edgeDists._toEdge, 2.0f);
- if (edgeDistSqr < distanceSqr)
- {
+ if (edgeDistSqr < distanceSqr) {
distanceSqr = edgeDistSqr;
uint j = (i + 1) % _points.size();
bestPoint = _points[i] + (_points[j] - _points[i]) * (edgeDists._onEdge / edgeDists._edgeLength);
@@ -164,9 +161,12 @@ static float depthAtForConvex(const PathFindingPolygon &p, Point q) {
float PathFindingPolygon::depthAt(Point query) const {
switch (_points.size()) {
case 0:
- case 1: return 1.0f;
- case 2: return depthAtForLine(_points[0], _points[1], query, _pointDepths[0], _pointDepths[1]);
- default: return depthAtForConvex(*this, query);
+ case 1:
+ return 1.0f;
+ case 2:
+ return depthAtForLine(_points[0], _points[1], query, _pointDepths[0], _pointDepths[1]);
+ default:
+ return depthAtForConvex(*this, query);
}
}
@@ -238,10 +238,14 @@ static Color colorAtForConvex(const FloorColorPolygon &p, Point query) {
Color FloorColorPolygon::colorAt(Point query) const {
switch (_points.size()) {
- case 0: return kWhite;
- case 1: return { 255, 255, 255, _pointColors[0].a };
- case 2: return colorAtForLine(_points[0], _points[1], query, _pointColors[0], _pointColors[1]);
- default: return colorAtForConvex(*this, query);
+ case 0:
+ return kWhite;
+ case 1:
+ return { 255, 255, 255, _pointColors[0].a };
+ case 2:
+ return colorAtForLine(_points[0], _points[1], query, _pointColors[0], _pointColors[1]);
+ default:
+ return colorAtForConvex(*this, query);
}
}
@@ -310,12 +314,10 @@ Point Shape::closestPointTo(Point query, int32 &polygonI) const {
assert(_polygons.size() > 0);
float bestDistanceSqr = std::numeric_limits<float>::infinity();
Point bestPoint = {};
- for (uint i = 0; i < _polygons.size(); i++)
- {
+ for (uint i = 0; i < _polygons.size(); i++) {
float curDistanceSqr = std::numeric_limits<float>::infinity();
Point curPoint = at(i).closestPointTo(query, curDistanceSqr);
- if (curDistanceSqr < bestDistanceSqr)
- {
+ if (curDistanceSqr < bestDistanceSqr) {
bestDistanceSqr = curDistanceSqr;
bestPoint = curPoint;
polygonI = (int32)i;
@@ -486,10 +488,10 @@ void PathFindingShape::initializeFloydWarshall() {
}
void PathFindingShape::calculateFloydWarshall() {
- const auto distance = [&](uint a, uint b) -> uint& {
+ const auto distance = [&] (uint a, uint b) -> uint &{
return _distanceMatrix[a * _linkPoints.size() + b];
};
- const auto previousTarget = [&](uint a, uint b) -> int32& {
+ const auto previousTarget = [&] (uint a, uint b) -> int32 &{
return _previousTarget[a * _linkPoints.size() + b];
};
for (uint over = 0; over < _linkPoints.size(); over++) {
diff --git a/engines/alcachofa/sounds.cpp b/engines/alcachofa/sounds.cpp
index 1e2a70edea7..dd14db3e5ab 100644
--- a/engines/alcachofa/sounds.cpp
+++ b/engines/alcachofa/sounds.cpp
@@ -73,8 +73,7 @@ void Sounds::update() {
if (g_system->getMillis() >= playback._fadeStart + playback._fadeDuration) {
_mixer->stopHandle(playback._handle);
_playbacks.erase(_playbacks.begin() + i - 1);
- }
- else {
+ } else {
byte newVolume = (g_system->getMillis() - playback._fadeStart) * Mixer::kMaxChannelVolume / playback._fadeDuration;
_mixer->setChannelVolume(playback._handle, Mixer::kMaxChannelVolume - newVolume);
}
@@ -157,8 +156,7 @@ SoundHandle Sounds::playSoundInternal(const char *fileName, byte volume, Mixer::
if (sampleCount < 0)
samples.clear();
samples.resize((uint)sampleCount); // we might have gotten less samples
- }
- else {
+ } else {
// we did not, now it is getting inefficient
const int bufferSize = 2048;
int16 buffer[bufferSize];
@@ -345,8 +343,7 @@ Task *Sounds::waitForMusicToEnd(Process &process) {
PlaySoundTask::PlaySoundTask(Process &process, SoundHandle SoundHandle)
: Task(process)
- , _soundHandle(SoundHandle) {
-}
+ , _soundHandle(SoundHandle) {}
PlaySoundTask::PlaySoundTask(Process &process, Serializer &s)
: Task(process)
@@ -358,12 +355,10 @@ PlaySoundTask::PlaySoundTask(Process &process, Serializer &s)
TaskReturn PlaySoundTask::run() {
auto &sounds = g_engine->sounds();
- if (sounds.isAlive(_soundHandle))
- {
+ if (sounds.isAlive(_soundHandle)) {
sounds.setAppropriateVolume(_soundHandle, process().character(), nullptr);
return TaskReturn::yield();
- }
- else
+ } else
return TaskReturn::finish(1);
}
@@ -376,7 +371,7 @@ DECLARE_TASK(PlaySoundTask)
WaitForMusicTask::WaitForMusicTask(Process &process)
: Task(process)
- , _lock("wait-for-music", g_engine->sounds().musicSemaphore()) { }
+ , _lock("wait-for-music", g_engine->sounds().musicSemaphore()) {}
WaitForMusicTask::WaitForMusicTask(Process &process, Serializer &s)
: Task(process)
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index 6e68b87fac7..ca0b73b67f8 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -38,8 +38,7 @@ MenuButton::MenuButton(Room *room, ReadStream &stream)
, _graphicNormal(stream)
, _graphicHovered(stream)
, _graphicClicked(stream)
- , _graphicDisabled(stream) {
-}
+ , _graphicDisabled(stream) {}
void MenuButton::draw() {
if (!isEnabled())
@@ -107,14 +106,12 @@ void MenuButton::trigger() {
const char *InternetMenuButton::typeName() const { return "InternetMenuButton"; }
InternetMenuButton::InternetMenuButton(Room *room, ReadStream &stream)
- : MenuButton(room, stream) {
-}
+ : MenuButton(room, stream) {}
const char *OptionsMenuButton::typeName() const { return "OptionsMenuButton"; }
OptionsMenuButton::OptionsMenuButton(Room *room, ReadStream &stream)
- : MenuButton(room, stream) {
-}
+ : MenuButton(room, stream) {}
void OptionsMenuButton::update() {
MenuButton::update();
@@ -130,8 +127,7 @@ void OptionsMenuButton::trigger() {
const char *MainMenuButton::typeName() const { return "MainMenuButton"; }
MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
- : MenuButton(room, stream) {
-}
+ : MenuButton(room, stream) {}
void MainMenuButton::update() {
MenuButton::update();
@@ -152,8 +148,7 @@ PushButton::PushButton(Room *room, ReadStream &stream)
, _alwaysVisible(readBool(stream))
, _graphic1(stream)
, _graphic2(stream)
- , _actionId(stream.readSint32LE()) {
-}
+ , _actionId(stream.readSint32LE()) {}
const char *EditBox::typeName() const { return "EditBox"; }
@@ -166,8 +161,7 @@ EditBox::EditBox(Room *room, ReadStream &stream)
, i3(stream.readSint32LE())
, i4(stream.readSint32LE())
, i5(stream.readSint32LE())
- , _fontId(stream.readSint32LE()) {
-}
+ , _fontId(stream.readSint32LE()) {}
const char *CheckBox::typeName() const { return "CheckBox"; }
@@ -178,8 +172,7 @@ CheckBox::CheckBox(Room *room, ReadStream &stream)
, _graphicChecked(stream)
, _graphicHovered(stream)
, _graphicClicked(stream)
- , _actionId(stream.readSint32LE()) {
-}
+ , _actionId(stream.readSint32LE()) {}
void CheckBox::draw() {
if (!isEnabled())
@@ -251,8 +244,7 @@ SlideButton::SlideButton(Room *room, ReadStream &stream)
, _maxPos(Shape(stream).firstPoint())
, _graphicIdle(stream)
, _graphicHovered(stream)
- , _graphicClicked(stream) {
-}
+ , _graphicClicked(stream) {}
void SlideButton::draw() {
auto *optionsMenu = dynamic_cast<OptionsMenu *>(room());
@@ -277,14 +269,12 @@ void SlideButton::update() {
optionsMenu->currentSlideButton() = nullptr;
g_engine->menu().triggerOptionsValue((OptionsMenuValue)_valueId, _value);
update(); // to update the position
- }
- else {
+ } else {
int clippedMousePosY = CLIP(mousePos.y, _minPos.y, _maxPos.y);
_value = (_maxPos.y - clippedMousePosY) / (float)(_maxPos.y - _minPos.y);
_graphicClicked.topLeft() = Point((_minPos.x + _maxPos.x) / 2, clippedMousePosY);
}
- }
- else {
+ } else {
_graphicIdle.topLeft() = Point(
(_minPos.x + _maxPos.x) / 2,
(int16)(_maxPos.y - _value * (_maxPos.y - _minPos.y)));
@@ -322,8 +312,7 @@ const char *IRCWindow::typeName() const { return "IRCWindow"; }
IRCWindow::IRCWindow(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _p1(Shape(stream).firstPoint())
- , _p2(Shape(stream).firstPoint()) {
-}
+ , _p2(Shape(stream).firstPoint()) {}
const char *MessageBox::typeName() const { return "MessageBox"; }
Commit: e5e42cc7b1d1e3d4e8379d83f5508417ac41688a
https://github.com/scummvm/scummvm/commit/e5e42cc7b1d1e3d4e8379d83f5508417ac41688a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Remove u8 string prefix
Changed paths:
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index 14c9ab54e4e..cfc7bc04132 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -53,7 +53,7 @@ const ADGameDescription gameDescriptions[] = {
// The "english" version is just the spanish version with english subtitles...
{
"mort_phil_adventura_de_cine",
- u8"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+ "Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
AD_ENTRY1s("Textos/Objetos.nkr", "ad3cb78ad7a51cfe63ee6f84768c7e66", 15895),
Common::EN_ANY,
Common::kPlatformWindows,
@@ -62,7 +62,7 @@ const ADGameDescription gameDescriptions[] = {
},
{
"mort_phil_adventura_de_cine",
- u8"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
+ "Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
AD_ENTRY1s("Textos/Objetos.nkr", "93331e4cc8d2f8f8a0007bfb5140dff5", 16403),
Common::ES_ESP,
Common::kPlatformWindows,
Commit: b3368f57429e37ec632743002221ff6f8efbf58b
https://github.com/scummvm/scummvm/commit/b3368f57429e37ec632743002221ff6f8efbf58b
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:00+02:00
Commit Message:
ALCACHOFA: Rename gameid to aventuradecine
Changed paths:
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index cfc7bc04132..ad4ad6940ac 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -22,7 +22,7 @@
namespace Alcachofa {
const PlainGameDescriptor alcachofaGames[] = {
- { "mort_phil_adventura_de_cine", "Mort & Phil: A Movie Adventure" },
+ { "aventuradecine", "Mort & Phil: A Movie Adventure" },
{ 0, 0 }
};
@@ -31,7 +31,7 @@ const ADGameDescription gameDescriptions[] = {
// A Movie Adventure
//
{
- "mort_phil_adventura_de_cine",
+ "aventuradecine",
"Clever & Smart - A Movie Adventure",
AD_ENTRY1s("Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606),
Common::DE_DEU,
@@ -40,7 +40,7 @@ const ADGameDescription gameDescriptions[] = {
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
{
- "mort_phil_adventura_de_cine",
+ "aventuradecine",
"Clever & Smart - A Movie Adventure",
AD_ENTRY1s("Textos/Objetos.nkr", "8dce25494470209d4882bf12f1a5ea42", 19208),
Common::DE_DEU,
@@ -52,7 +52,7 @@ const ADGameDescription gameDescriptions[] = {
// The "english" version is just the spanish version with english subtitles...
{
- "mort_phil_adventura_de_cine",
+ "aventuradecine",
"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
AD_ENTRY1s("Textos/Objetos.nkr", "ad3cb78ad7a51cfe63ee6f84768c7e66", 15895),
Common::EN_ANY,
@@ -61,7 +61,7 @@ const ADGameDescription gameDescriptions[] = {
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
{
- "mort_phil_adventura_de_cine",
+ "aventuradecine",
"Mortadelo y Filemón: Una Aventura de Cine - Edición Especial",
AD_ENTRY1s("Textos/Objetos.nkr", "93331e4cc8d2f8f8a0007bfb5140dff5", 16403),
Common::ES_ESP,
Commit: 999a5b01677f31451b8bada4f1fb885b1941f689
https://github.com/scummvm/scummvm/commit/999a5b01677f31451b8bada4f1fb885b1941f689
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Add ADGF_REMASTERED in preparation for edicion original
Changed paths:
engines/alcachofa/detection_tables.h
diff --git a/engines/alcachofa/detection_tables.h b/engines/alcachofa/detection_tables.h
index ad4ad6940ac..32cca449889 100644
--- a/engines/alcachofa/detection_tables.h
+++ b/engines/alcachofa/detection_tables.h
@@ -36,7 +36,7 @@ const ADGameDescription gameDescriptions[] = {
AD_ENTRY1s("Textos/Objetos.nkr", "a2b1deff5ca7187f2ebf7f2ab20747e9", 17606),
Common::DE_DEU,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
{
@@ -45,7 +45,7 @@ const ADGameDescription gameDescriptions[] = {
AD_ENTRY1s("Textos/Objetos.nkr", "8dce25494470209d4882bf12f1a5ea42", 19208),
Common::DE_DEU,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_DEMO,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED | ADGF_DEMO,
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
@@ -57,7 +57,7 @@ const ADGameDescription gameDescriptions[] = {
AD_ENTRY1s("Textos/Objetos.nkr", "ad3cb78ad7a51cfe63ee6f84768c7e66", 15895),
Common::EN_ANY,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
{
@@ -66,7 +66,7 @@ const ADGameDescription gameDescriptions[] = {
AD_ENTRY1s("Textos/Objetos.nkr", "93331e4cc8d2f8f8a0007bfb5140dff5", 16403),
Common::ES_ESP,
Common::kPlatformWindows,
- ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE,
+ ADGF_UNSTABLE | ADGF_USEEXTRAASTITLE | ADGF_REMASTERED,
GUIO2(GAMEOPTION_32BITS, GAMEOPTION_HIGH_QUALITY)
},
Commit: 07a39679ddc731efcc592a3eef2eac4a99643f82
https://github.com/scummvm/scummvm/commit/07a39679ddc731efcc592a3eef2eac4a99643f82
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Remove temporary saveFileMgr variable
Changed paths:
engines/alcachofa/alcachofa.cpp
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 27a8d94803c..19805ed1ab1 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -395,8 +395,7 @@ bool AlcachofaEngine::tryLoadFromLauncher() {
int saveSlot = ConfMan.getInt("save_slot");
if (!ConfMan.hasKey("save_slot") || saveSlot < 0)
return false;
- auto *saveFileMgr = g_system->getSavefileManager();
- auto *saveFile = saveFileMgr->openForLoading(getSaveStateName(saveSlot));
+ auto *saveFile = g_system->getSavefileManager()->openForLoading(getSaveStateName(saveSlot));
if (saveFile == nullptr)
return false;
bool result = loadGameStream(saveFile).getCode() == kNoError;
Commit: ac843c9fd03a85d98429663186aa3efbec3f0d40
https://github.com/scummvm/scummvm/commit/ac843c9fd03a85d98429663186aa3efbec3f0d40
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Remove redundant pragma once
Changed paths:
engines/alcachofa/game.h
diff --git a/engines/alcachofa/game.h b/engines/alcachofa/game.h
index 65b99e52671..548935d43eb 100644
--- a/engines/alcachofa/game.h
+++ b/engines/alcachofa/game.h
@@ -1,4 +1,3 @@
-#pragma once
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
Commit: 917ceb51156acf6bbfd75078a685a6d5743e2dd5
https://github.com/scummvm/scummvm/commit/917ceb51156acf6bbfd75078a685a6d5743e2dd5
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Replace _DEBUG macro usage
Changed paths:
engines/alcachofa/game.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.cpp
diff --git a/engines/alcachofa/game.cpp b/engines/alcachofa/game.cpp
index 8ea1da03f07..b663a5380fc 100644
--- a/engines/alcachofa/game.cpp
+++ b/engines/alcachofa/game.cpp
@@ -28,7 +28,7 @@ using namespace Common;
namespace Alcachofa {
Game::Game()
-#ifdef _DEBUG // During development let's check out these errors more carefully
+#ifdef ALCACHOFA_DEBUG // During development let's check out these errors more carefully
: _message(error)
#else // For release builds the game might still work or the user might still be able to save and restart
: _message(warning)
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index e9881b189c7..6a3e1a0ad95 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -341,7 +341,7 @@ public:
GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
-#ifdef _DEBUG
+#ifdef ALCACHOFA_DEBUG
// make sure we crash instead of someone using our stack arrays
GL_CALL(glVertexPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
GL_CALL(glTexCoordPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index 593b617bf57..ebbf2e6e8c3 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -117,7 +117,7 @@ void AnimationBase::load() {
_spriteBases.push_back(stream->readUint32LE());
assert(_spriteBases.back() < imageCount);
}
-#ifdef _DEBUG
+#ifdef ALCACHOFA_DEBUG
for (uint i = spriteCount; i < kMaxSpriteIDs; i++)
assert(stream->readSint32LE() == 0);
#else
Commit: 32c4ebc08ac8cd294f9d20f1e7ba412af9454f41
https://github.com/scummvm/scummvm/commit/32c4ebc08ac8cd294f9d20f1e7ba412af9454f41
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Fix end-of-file comment
Changed paths:
engines/alcachofa/graphics.h
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index 4f9ce4f3901..a3b0727c8f2 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -476,4 +476,4 @@ private:
}
-#endif // ALCACHOFA_ENGINE_H
+#endif // ALCACHOFA_GRAPHICS_H
Commit: 92d06884297e446f17ae85389e484604d0400fb7
https://github.com/scummvm/scummvm/commit/92d06884297e446f17ae85389e484604d0400fb7
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Use luminance for grayscaled thumbnails
Changed paths:
engines/alcachofa/menu.cpp
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index d9ebcb78606..faed5b72891 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -50,7 +50,7 @@ static void convertToGrayscale(ManagedSurface &surface) {
pixel = (uint32 *)surface.getBasePtr(0, y);
for (int x = 0; x < surface.w; x++, pixel++) {
*pixel &= rgbMask;
- byte gray = (components[0] + components[1] + components[2] + components[3]) / 3;
+ byte gray = (byte)CLIP(0.29f * components[0] + 0.58f * components[1] + 0.11f * components[2], 0.0f, 255.0f);
*pixel =
(uint32(gray) << surface.format.rShift) |
(uint32(gray) << surface.format.gShift) |
Commit: 17175d0de9c3e0994d1f276de07ae5620adabc7a
https://github.com/scummvm/scummvm/commit/17175d0de9c3e0994d1f276de07ae5620adabc7a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Fix regression error when loading a DelayTask
Changed paths:
engines/alcachofa/alcachofa.cpp
engines/alcachofa/scheduler.cpp
engines/alcachofa/scheduler.h
engines/alcachofa/tasks.h
diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index 19805ed1ab1..fa25a74926b 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -427,4 +427,33 @@ void Config::saveToScummVM() {
// if you set it in ScummVMs dialog it sticks
}
+DelayTask::DelayTask(Process &process, uint32 millis)
+ : Task(process)
+ , _endTime(millis) {}
+
+DelayTask::DelayTask(Process &process, Serializer &s)
+ : Task(process) {
+ syncGame(s);
+}
+
+TaskReturn DelayTask::run() {
+ TASK_BEGIN;
+ _endTime += g_engine->getMillis();
+ while (g_engine->getMillis() < _endTime)
+ TASK_YIELD(1);
+ TASK_END;
+}
+
+void DelayTask::debugPrint() {
+ uint32 remaining = g_engine->getMillis() <= _endTime ? _endTime - g_engine->getMillis() : 0;
+ g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
+}
+
+void DelayTask::syncGame(Serializer &s) {
+ Task::syncGame(s);
+ s.syncAsUint32LE(_endTime);
+}
+
+DECLARE_TASK(DelayTask)
+
} // End of namespace Alcachofa
diff --git a/engines/alcachofa/scheduler.cpp b/engines/alcachofa/scheduler.cpp
index d58cb44f681..81b5f38bc9e 100644
--- a/engines/alcachofa/scheduler.cpp
+++ b/engines/alcachofa/scheduler.cpp
@@ -85,35 +85,6 @@ void Task::errorForUnexpectedObjectType(const ObjectBase *base) const {
base->name().c_str(), taskName(), base->typeName());
}
-DelayTask::DelayTask(Process &process, uint32 millis)
- : Task(process)
- , _endTime(millis) {}
-
-DelayTask::DelayTask(Process &process, Serializer &s)
- : Task(process) {
- syncGame(s);
-}
-
-TaskReturn DelayTask::run() {
- TASK_BEGIN;
- _endTime += g_engine->getMillis();
- while (g_engine->getMillis() < _endTime)
- TASK_YIELD(1);
- TASK_END;
-}
-
-void DelayTask::debugPrint() {
- uint32 remaining = g_engine->getMillis() <= _endTime ? _endTime - g_engine->getMillis() : 0;
- g_engine->getDebugger()->debugPrintf("Delay for further %ums\n", remaining);
-}
-
-void DelayTask::syncGame(Serializer &s) {
- Task::syncGame(s);
- s.syncAsUint32LE(_endTime);
-}
-
-DECLARE_TASK(DelayTask)
-
Process::Process(ProcessId pid, MainCharacterKind characterKind)
: _pid(pid)
, _character(characterKind)
diff --git a/engines/alcachofa/scheduler.h b/engines/alcachofa/scheduler.h
index 50c685c5a65..55bb113259c 100644
--- a/engines/alcachofa/scheduler.h
+++ b/engines/alcachofa/scheduler.h
@@ -113,6 +113,8 @@ private:
Process &_process;
};
+// implemented in alcachofa.cpp to prevent a compiler warning when
+// the declaration of the construct function comes after the definition
struct DelayTask : public Task {
DelayTask(Process &process, uint32 millis);
DelayTask(Process &process, Common::Serializer &s);
diff --git a/engines/alcachofa/tasks.h b/engines/alcachofa/tasks.h
index 74cbabd9817..8c95f719033 100644
--- a/engines/alcachofa/tasks.h
+++ b/engines/alcachofa/tasks.h
@@ -51,9 +51,6 @@ DEFINE_TASK(ScriptTimerTask)
DEFINE_TASK(ScriptTask)
DEFINE_TASK(PlaySoundTask)
DEFINE_TASK(WaitForMusicTask)
-
-// this one is special as the implementation is in the same TU as the signature
-// which causes a warning on some pedantic compiler
-//DEFINE_TASK(DelayTask)
+DEFINE_TASK(DelayTask)
#undef DEFINE_TASK
Commit: e45cb342be6e56ef301dc01fca3c0ac2dfe19e7a
https://github.com/scummvm/scummvm/commit/e45cb342be6e56ef301dc01fca3c0ac2dfe19e7a
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Remove feature 3d
Changed paths:
engines/alcachofa/configure.engine
diff --git a/engines/alcachofa/configure.engine b/engines/alcachofa/configure.engine
index 6e58d75f3ec..3904b6543c3 100644
--- a/engines/alcachofa/configure.engine
+++ b/engines/alcachofa/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine alcachofa "Alcachofa" no "" "" "highres opengl_game_classic mpeg2 3d"
+add_engine alcachofa "Alcachofa" no "" "" "highres opengl_game_classic mpeg2"
Commit: eb8499e9021732db00b0f9d40e0b02806dfb078e
https://github.com/scummvm/scummvm/commit/eb8499e9021732db00b0f9d40e0b02806dfb078e
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Split up OpenGL renderer
Changed paths:
A engines/alcachofa/graphics-opengl-classic.cpp
A engines/alcachofa/graphics-opengl.h
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics.h
engines/alcachofa/module.mk
diff --git a/engines/alcachofa/graphics-opengl-classic.cpp b/engines/alcachofa/graphics-opengl-classic.cpp
new file mode 100644
index 00000000000..578d1e067a9
--- /dev/null
+++ b/engines/alcachofa/graphics-opengl-classic.cpp
@@ -0,0 +1,238 @@
+/* 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/graphics-opengl.h"
+#include "alcachofa/detection.h"
+
+#include "common/system.h"
+#include "engines/util.h"
+
+using namespace Common;
+using namespace Math;
+using namespace Graphics;
+
+namespace Alcachofa {
+
+class OpenGLRendererClassic : public OpenGLRenderer, public virtual IDebugRenderer {
+public:
+ using OpenGLRenderer::OpenGLRenderer;
+
+ void begin() override {
+ GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
+ GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
+ GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
+ resetState();
+ }
+
+ void setTexture(ITexture *texture) override {
+ if (texture == _currentTexture)
+ return;
+ else if (texture == nullptr) {
+ GL_CALL(glDisable(GL_TEXTURE_2D));
+ GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
+ _currentTexture = nullptr;
+ } else {
+ if (_currentTexture == nullptr) {
+ GL_CALL(glEnable(GL_TEXTURE_2D));
+ GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
+ }
+ auto glTexture = dynamic_cast<OpenGLTexture *>(texture);
+ assert(glTexture != nullptr);
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, glTexture->handle()));
+ _currentTexture = glTexture;
+ }
+ }
+
+ void setBlendMode(BlendMode blendMode) override {
+ if (blendMode == _currentBlendMode)
+ return;
+ setBlendFunc(blendMode);
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE));
+ switch (blendMode) {
+ case BlendMode::AdditiveAlpha:
+ case BlendMode::Additive:
+ case BlendMode::Multiply:
+ // TintAlpha * TexColor, TexAlpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA)); // alpha replaces color
+ break;
+ case BlendMode::Alpha:
+ // TexColor, TintAlpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+ break;
+ case BlendMode::Tinted:
+ // (TintColor * TintAlpha) * TexColor, TexAlpha
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
+ GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR)); // we have to pre-multiply
+ break;
+ default:
+ assert(false && "Invalid blend mode");
+ break;
+ }
+ _currentBlendMode = blendMode;
+ }
+
+ void setLodBias(float lodBias) override {
+ if (abs(_currentLodBias - lodBias) < epsilon)
+ return;
+ GL_CALL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, lodBias));
+ _currentLodBias = lodBias;
+ }
+
+ void quad(
+ Vector2d topLeft,
+ Vector2d size,
+ Color color,
+ Angle rotation,
+ Vector2d texMin,
+ Vector2d texMax) override {
+ Vector2d positions[] = {
+ topLeft + Vector2d(0, 0),
+ topLeft + Vector2d(0, +size.getY()),
+ topLeft + Vector2d(+size.getX(), +size.getY()),
+ topLeft + Vector2d(+size.getX(), 0),
+ };
+ if (abs(rotation.getDegrees()) > epsilon) {
+ const Vector2d zero(0, 0);
+ for (int i = 0; i < 4; i++)
+ positions[i].rotateAround(zero, rotation);
+ }
+
+ Vector2d texCoords[] = {
+ { texMin.getX(), texMin.getY() },
+ { texMin.getX(), texMax.getY() },
+ { texMax.getX(), texMax.getY() },
+ { texMax.getX(), texMin.getY() }
+ };
+ if (_currentTexture != nullptr) {
+ // float equality is fine here, if it was calculated it was not a normal graphic
+ _currentTexture->setMirrorWrap(texMin != Vector2d() || texMax != Vector2d(1, 1));
+ }
+
+ float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
+ if (_currentBlendMode == BlendMode::Tinted) {
+ colors[0] *= colors[3];
+ colors[1] *= colors[3];
+ colors[2] *= colors[3];
+ }
+
+ checkFirstDrawCommand();
+ GL_CALL(glColor4fv(colors));
+ GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
+ if (_currentTexture != nullptr)
+ GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
+ GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
+
+#ifdef ALCACHOFA_DEBUG
+ // make sure we crash instead of someone using our stack arrays
+ GL_CALL(glVertexPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
+ GL_CALL(glTexCoordPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
+#endif
+ }
+
+ void debugPolygon(
+ Span<Vector2d> points,
+ Color color
+ ) override {
+ checkFirstDrawCommand();
+ setTexture(nullptr);
+ setBlendMode(BlendMode::Alpha);
+ GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
+ GL_CALL(glLineWidth(4.0f));
+ GL_CALL(glPointSize(8.0f));
+
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 2)
+ GL_CALL(glDrawArrays(GL_POLYGON, 0, points.size()));
+
+ color.a = (byte)(MIN(255.0f, color.a * 1.3f));
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 1)
+ GL_CALL(glDrawArrays(GL_LINE_LOOP, 0, points.size()));
+
+ color.a = (byte)(MIN(255.0f, color.a * 1.3f));
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 0)
+ GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
+ }
+
+ void debugPolyline(
+ Span<Vector2d> points,
+ Color color
+ ) override {
+ checkFirstDrawCommand();
+ setTexture(nullptr);
+ setBlendMode(BlendMode::Alpha);
+ GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
+ GL_CALL(glLineWidth(4.0f));
+ GL_CALL(glPointSize(8.0f));
+
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 1)
+ GL_CALL(glDrawArrays(GL_LINE_STRIP, 0, points.size()));
+
+ color.a = (byte)(MIN(255.0f, color.a * 1.3f));
+ GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
+ if (points.size() > 0)
+ GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
+ }
+
+ void setMatrices(bool flipped) override {
+ float bottom = flipped ? _resolution.y : 0.0f;
+ float top = flipped ? 0.0f : _resolution.y;
+
+ GL_CALL(glMatrixMode(GL_PROJECTION));
+ GL_CALL(glLoadIdentity());
+ GL_CALL(glOrtho(0.0f, _resolution.x, bottom, top, -1.0f, 1.0f));
+ GL_CALL(glMatrixMode(GL_MODELVIEW));
+ GL_CALL(glLoadIdentity());
+ }
+};
+
+IRenderer *IRenderer::createOpenGLRenderer(Point resolution) {
+ initGraphics3d(resolution.x, resolution.y);
+ return new OpenGLRendererClassic(resolution);
+}
+
+}
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 6a3e1a0ad95..8c51e92f1d7 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -21,12 +21,10 @@
#include "alcachofa/graphics.h"
#include "alcachofa/detection.h"
+#include "alcachofa/graphics-opengl.h"
#include "common/system.h"
#include "engines/util.h"
-#include "graphics/managed_surface.h"
-#include "graphics/opengl/system_headers.h"
-#include "graphics/opengl/debug.h"
using namespace Common;
using namespace Math;
@@ -34,18 +32,13 @@ using namespace Graphics;
namespace Alcachofa {
-struct OpenGLFormat {
- GLenum _format, _type;
- inline bool isValid() const { return _format != GL_NONE; }
-};
-
static bool areComponentsInOrder(const PixelFormat &format, int r, int g, int b, int a) {
return format == (a < 0
? PixelFormat(3, 8, 8, 8, 0, r * 8, g * 8, b * 8, 0)
: PixelFormat(4, 8, 8, 8, 8, r * 8, g * 8, b * 8, a * 8));
}
-static OpenGLFormat getOpenGLFormatOf(const PixelFormat &format) {
+OpenGLFormat getOpenGLFormatOf(const PixelFormat &format) {
if (areComponentsInOrder(format, 0, 1, 2, 3))
return { GL_RGBA, GL_UNSIGNED_BYTE };
else if (areComponentsInOrder(format, 3, 2, 1, 0))
@@ -61,394 +54,177 @@ static OpenGLFormat getOpenGLFormatOf(const PixelFormat &format) {
return { GL_NONE, GL_NONE };
}
+OpenGLTexture::OpenGLTexture(int32 w, int32 h, bool withMipmaps)
+ : ITexture({ (int16)w, (int16)h })
+ , _withMipmaps(withMipmaps) {
+ GL_CALL(glEnable(GL_TEXTURE_2D));
+ 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));
+ setMirrorWrap(false);
+}
-class OpenGLTexture : public ITexture {
-public:
- OpenGLTexture(int32 w, int32 h, bool withMipmaps)
- : ITexture({ (int16)w, (int16)h })
- , _withMipmaps(withMipmaps) {
- GL_CALL(glEnable(GL_TEXTURE_2D));
- 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));
- setMirrorWrap(false);
- }
-
- ~OpenGLTexture() override {
- if (_handle != 0)
- GL_CALL(glDeleteTextures(1, &_handle));
- }
-
- void update(const Surface &surface) override {
- OpenGLFormat format = getOpenGLFormatOf(surface.format);
- assert(surface.w == size().x && surface.h == size().y);
- assert(format.isValid());
-
- GL_CALL(glEnable(GL_TEXTURE_2D));
- GL_CALL(glBindTexture(GL_TEXTURE_2D, _handle));
- GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.w, surface.h, 0, format._format, format._type, surface.getPixels()));
- if (_withMipmaps)
- GL_CALL(glGenerateMipmap(GL_TEXTURE_2D));
- else
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
- }
-
- void setMirrorWrap(bool wrap) {
- if (_mirrorWrap == wrap)
- return;
- _mirrorWrap = wrap;
- GLint wrapMode;
- if (wrap)
- wrapMode = OpenGLContext.textureMirrorRepeatSupported ? GL_MIRRORED_REPEAT : GL_REPEAT;
- else
- wrapMode = OpenGLContext.textureEdgeClampSupported ? GL_CLAMP_TO_EDGE : GL_CLAMP;
-
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode));
- GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode));
- }
-
- inline GLuint handle() const { return _handle; }
-
-private:
- GLuint _handle;
- bool _withMipmaps;
- bool _mirrorWrap = true;
-};
-
-class OpenGLRenderer : public IDebugRenderer {
-public:
- OpenGLRenderer(Point resolution)
- : _resolution(resolution) {
- setViewportToScreen();
-
- GL_CALL(glDisable(GL_LIGHTING));
- GL_CALL(glDisable(GL_DEPTH_TEST));
- GL_CALL(glDisable(GL_SCISSOR_TEST));
- GL_CALL(glDisable(GL_STENCIL_TEST));
- GL_CALL(glDisable(GL_CULL_FACE));
- GL_CALL(glEnable(GL_BLEND));
- GL_CALL(glDepthMask(GL_FALSE));
-
- if (!OpenGLContext.NPOTSupported || !OpenGLContext.textureMirrorRepeatSupported) {
- g_system->messageBox(LogMessageType::kWarning, "Old OpenGL detected, some graphical errors will occur.");
- }
- }
-
- ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override {
- assert(w >= 0 && h >= 0);
- return ScopedPtr<ITexture>(new OpenGLTexture(w, h, withMipmaps));
- }
-
- void begin() override {
- GL_CALL(glEnableClientState(GL_VERTEX_ARRAY));
- GL_CALL(glDisableClientState(GL_INDEX_ARRAY));
- GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
- setViewportToScreen();
- _currentOutput = nullptr;
- _currentLodBias = -1000.0f;
- _currentTexture = nullptr;
- _currentBlendMode = (BlendMode)-1;
- _isFirstDrawCommand = true;
- }
-
- void end() override {
- GL_CALL(glFlush());
-
- if (_currentOutput != nullptr) {
- g_system->presentBuffer();
- auto format = getOpenGLFormatOf(_currentOutput->format);
- GL_CALL(glReadPixels(
- 0,
- 0,
- _outputSize.x,
- _outputSize.y,
- format._format,
- format._type,
- _currentOutput->getPixels()
- ));
- }
- }
-
- void setTexture(ITexture *texture) override {
- if (texture == _currentTexture)
- return;
- else if (texture == nullptr) {
- GL_CALL(glDisable(GL_TEXTURE_2D));
- GL_CALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
- _currentTexture = nullptr;
- } else {
- if (_currentTexture == nullptr) {
- GL_CALL(glEnable(GL_TEXTURE_2D));
- GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
- }
- auto glTexture = dynamic_cast<OpenGLTexture *>(texture);
- assert(glTexture != nullptr);
- GL_CALL(glBindTexture(GL_TEXTURE_2D, glTexture->handle()));
- _currentTexture = glTexture;
- }
- }
-
- void setBlendMode(BlendMode blendMode) override {
- if (blendMode == _currentBlendMode)
- return;
- // first the blend func
- switch (blendMode) {
- case BlendMode::AdditiveAlpha:
- GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
- break;
- case BlendMode::Additive:
- GL_CALL(glBlendFunc(GL_ONE, GL_ONE));
- break;
- case BlendMode::Multiply:
- GL_CALL(glBlendFunc(GL_DST_COLOR, GL_ONE));
- break;
- case BlendMode::Alpha:
- GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
- break;
- case BlendMode::Tinted:
- GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
- break;
- default: assert(false && "Invalid blend mode"); break;
- }
-
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE));
- switch (blendMode) {
- case BlendMode::AdditiveAlpha:
- case BlendMode::Additive:
- case BlendMode::Multiply:
- // TintAlpha * TexColor, TexAlpha
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+OpenGLTexture::~OpenGLTexture() {
+ if (_handle != 0)
+ GL_CALL(glDeleteTextures(1, &_handle));
+}
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+void OpenGLTexture::update(const Surface &surface) {
+ OpenGLFormat format = getOpenGLFormatOf(surface.format);
+ assert(surface.w == size().x && surface.h == size().y);
+ assert(format.isValid());
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA)); // alpha replaces color
- break;
- case BlendMode::Alpha:
- // TexColor, TintAlpha
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+ GL_CALL(glEnable(GL_TEXTURE_2D));
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, _handle));
+ GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.w, surface.h, 0, format._format, format._type, surface.getPixels()));
+ if (_withMipmaps)
+ GL_CALL(glGenerateMipmap(GL_TEXTURE_2D));
+ else
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
+}
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
- break;
- case BlendMode::Tinted:
- // (TintColor * TintAlpha) * TexColor, TexAlpha
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE));
+void OpenGLTexture::setMirrorWrap(bool wrap) {
+ if (_mirrorWrap == wrap)
+ return;
+ _mirrorWrap = wrap;
+ GLint wrapMode;
+ if (wrap)
+ wrapMode = OpenGLContext.textureMirrorRepeatSupported ? GL_MIRRORED_REPEAT : GL_REPEAT;
+ else
+ wrapMode = OpenGLContext.textureEdgeClampSupported ? GL_CLAMP_TO_EDGE : GL_CLAMP;
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA));
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode));
+ GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode));
+}
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR));
- GL_CALL(glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR)); // we have to pre-multiply
- break;
- default:
- assert(false && "Invalid blend mode");
- break;
- }
- _currentBlendMode = blendMode;
- }
+OpenGLRenderer::OpenGLRenderer(Point resolution) : _resolution(resolution) {
+ GL_CALL(glDisable(GL_LIGHTING));
+ GL_CALL(glDisable(GL_DEPTH_TEST));
+ GL_CALL(glDisable(GL_SCISSOR_TEST));
+ GL_CALL(glDisable(GL_STENCIL_TEST));
+ GL_CALL(glDisable(GL_CULL_FACE));
+ GL_CALL(glEnable(GL_BLEND));
+ GL_CALL(glDepthMask(GL_FALSE));
- void setLodBias(float lodBias) override {
- if (abs(_currentLodBias - lodBias) < epsilon)
- return;
- GL_CALL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, lodBias));
- _currentLodBias = lodBias;
+ if (!OpenGLContext.NPOTSupported || !OpenGLContext.textureMirrorRepeatSupported) {
+ g_system->messageBox(LogMessageType::kWarning, "Old OpenGL detected, some graphical errors will occur.");
}
+}
- void setOutput(Surface &output) override {
- assert(_isFirstDrawCommand);
- setViewportToRect(output.w, output.h);
- _currentOutput = &output;
-
- // just debug warnings as it will only produce a graphical glitch while
- // there is some chance the resolution could change from here to ::end
- // and this is per-frame so maybe don't spam the console with the same message
-
- if (output.w > g_system->getWidth() || output.h > g_system->getHeight())
- debugC(0, kDebugGraphics, "Output is larger than screen, output will be cropped (%d, %d) > (%d, %d)",
- output.w, output.h, g_system->getWidth(), g_system->getHeight());
-
- auto format = getOpenGLFormatOf(output.format);
- if (format._format == GL_NONE) {
- auto formatString = output.format.toString();
- debugC(0, kDebugGraphics, "Cannot use pixelformat of given output surface: %s", formatString.c_str());
- _currentOutput = nullptr;
- }
+ScopedPtr<ITexture> OpenGLRenderer::createTexture(int32 w, int32 h, bool withMipmaps) {
+ assert(w >= 0 && h >= 0);
+ return ScopedPtr<ITexture>(new OpenGLTexture(w, h, withMipmaps));
+}
- if (output.pitch != output.format.bytesPerPixel * output.w) {
- // Maybe there would be a way with glPixelStore
- debugC(0, kDebugGraphics, "Incompatible output surface pitch");
- _currentOutput = nullptr;
- }
- }
+void OpenGLRenderer::resetState() {
+ setViewportToScreen();
+ _currentOutput = nullptr;
+ _currentLodBias = -1000.0f;
+ _currentTexture = nullptr;
+ _currentBlendMode = (BlendMode)-1;
+ _isFirstDrawCommand = true;
+}
- bool hasOutput() const override {
- return _currentOutput != nullptr;
+void OpenGLRenderer::end() {
+ GL_CALL(glFlush());
+
+ if (_currentOutput != nullptr) {
+ g_system->presentBuffer();
+ auto format = getOpenGLFormatOf(_currentOutput->format);
+ GL_CALL(glReadPixels(
+ 0,
+ 0,
+ _outputSize.x,
+ _outputSize.y,
+ format._format,
+ format._type,
+ _currentOutput->getPixels()
+ ));
}
+}
- void quad(
- Vector2d topLeft,
- Vector2d size,
- Color color,
- Angle rotation,
- Vector2d texMin,
- Vector2d texMax) override {
- Vector2d positions[] = {
- topLeft + Vector2d(0, 0),
- topLeft + Vector2d(0, +size.getY()),
- topLeft + Vector2d(+size.getX(), +size.getY()),
- topLeft + Vector2d(+size.getX(), 0),
- };
- if (abs(rotation.getDegrees()) > epsilon) {
- const Vector2d zero(0, 0);
- for (int i = 0; i < 4; i++)
- positions[i].rotateAround(zero, rotation);
- }
-
- Vector2d texCoords[] = {
- { texMin.getX(), texMin.getY() },
- { texMin.getX(), texMax.getY() },
- { texMax.getX(), texMax.getY() },
- { texMax.getX(), texMin.getY() }
- };
- if (_currentTexture != nullptr) {
- // float equality is fine here, if it was calculated it was not a normal graphic
- _currentTexture->setMirrorWrap(texMin != Vector2d() || texMax != Vector2d(1, 1));
- }
-
- float colors[] = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f };
- if (_currentBlendMode == BlendMode::Tinted) {
- colors[0] *= colors[3];
- colors[1] *= colors[3];
- colors[2] *= colors[3];
- }
-
- checkFirstDrawCommand();
- GL_CALL(glColor4fv(colors));
- GL_CALL(glVertexPointer(2, GL_FLOAT, 0, positions));
- if (_currentTexture != nullptr)
- GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords));
- GL_CALL(glDrawArrays(GL_QUADS, 0, 4));
-
-#ifdef ALCACHOFA_DEBUG
- // make sure we crash instead of someone using our stack arrays
- GL_CALL(glVertexPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
- GL_CALL(glTexCoordPointer(2, GL_FLOAT, sizeof(Vector2d), nullptr));
-#endif
+void OpenGLRenderer::setBlendFunc(BlendMode blendMode) {
+ switch (blendMode) {
+ case BlendMode::AdditiveAlpha:
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ case BlendMode::Additive:
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE));
+ break;
+ case BlendMode::Multiply:
+ GL_CALL(glBlendFunc(GL_DST_COLOR, GL_ONE));
+ break;
+ case BlendMode::Alpha:
+ GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ case BlendMode::Tinted:
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ default: assert(false && "Invalid blend mode"); break;
}
+}
- void debugPolygon(
- Span<Vector2d> points,
- Color color
- ) override {
- checkFirstDrawCommand();
- setTexture(nullptr);
- setBlendMode(BlendMode::Alpha);
- GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
- GL_CALL(glLineWidth(4.0f));
- GL_CALL(glPointSize(8.0f));
-
- GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
- if (points.size() > 2)
- GL_CALL(glDrawArrays(GL_POLYGON, 0, points.size()));
-
- color.a = (byte)(MIN(255.0f, color.a * 1.3f));
- GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
- if (points.size() > 1)
- GL_CALL(glDrawArrays(GL_LINE_LOOP, 0, points.size()));
-
- color.a = (byte)(MIN(255.0f, color.a * 1.3f));
- GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
- if (points.size() > 0)
- GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
- }
+void OpenGLRenderer::setOutput(Surface &output) {
+ assert(_isFirstDrawCommand);
+ setViewportToRect(output.w, output.h);
+ _currentOutput = &output;
- void debugPolyline(
- Span<Vector2d> points,
- Color color
- ) override {
- checkFirstDrawCommand();
- setTexture(nullptr);
- setBlendMode(BlendMode::Alpha);
- GL_CALL(glVertexPointer(2, GL_FLOAT, 0, points.data()));
- GL_CALL(glLineWidth(4.0f));
- GL_CALL(glPointSize(8.0f));
+ // just debug warnings as it will only produce a graphical glitch while
+ // there is some chance the resolution could change from here to ::end
+ // and this is per-frame so maybe don't spam the console with the same message
- GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
- if (points.size() > 1)
- GL_CALL(glDrawArrays(GL_LINE_STRIP, 0, points.size()));
+ if (output.w > g_system->getWidth() || output.h > g_system->getHeight())
+ debugC(0, kDebugGraphics, "Output is larger than screen, output will be cropped (%d, %d) > (%d, %d)",
+ output.w, output.h, g_system->getWidth(), g_system->getHeight());
- color.a = (byte)(MIN(255.0f, color.a * 1.3f));
- GL_CALL(glColor4ub(color.r, color.g, color.b, color.a));
- if (points.size() > 0)
- GL_CALL(glDrawArrays(GL_POINTS, 0, points.size()));
+ auto format = getOpenGLFormatOf(output.format);
+ if (format._format == GL_NONE) {
+ auto formatString = output.format.toString();
+ debugC(0, kDebugGraphics, "Cannot use pixelformat of given output surface: %s", formatString.c_str());
+ _currentOutput = nullptr;
}
-private:
- void setMatrices(bool flipped) {
- float bottom = flipped ? _resolution.y : 0.0f;
- float top = flipped ? 0.0f : _resolution.y;
-
- GL_CALL(glMatrixMode(GL_PROJECTION));
- GL_CALL(glLoadIdentity());
- GL_CALL(glOrtho(0.0f, _resolution.x, bottom, top, -1.0f, 1.0f));
- GL_CALL(glMatrixMode(GL_MODELVIEW));
- GL_CALL(glLoadIdentity());
+ if (output.pitch != output.format.bytesPerPixel * output.w) {
+ // Maybe there would be a way with glPixelStore
+ debugC(0, kDebugGraphics, "Incompatible output surface pitch");
+ _currentOutput = nullptr;
}
+}
- void setViewportToScreen() {
- int32 screenWidth = g_system->getWidth();
- int32 screenHeight = g_system->getHeight();
- Rect viewport(
- MIN<int32>(screenWidth, screenHeight * (float)_resolution.x / _resolution.y),
- MIN<int32>(screenHeight, screenWidth * (float)_resolution.y / _resolution.x));
- viewport.translate(
- (screenWidth - viewport.width()) / 2,
- (screenHeight - viewport.height()) / 2);
+bool OpenGLRenderer::hasOutput() const {
+ return _currentOutput != nullptr;
+}
- GL_CALL(glViewport(viewport.left, viewport.top, viewport.width(), viewport.height()));
- setMatrices(true);
- }
+void OpenGLRenderer::setViewportToScreen() {
+ int32 screenWidth = g_system->getWidth();
+ int32 screenHeight = g_system->getHeight();
+ Rect viewport(
+ MIN<int32>(screenWidth, screenHeight * (float)_resolution.x / _resolution.y),
+ MIN<int32>(screenHeight, screenWidth * (float)_resolution.y / _resolution.x));
+ viewport.translate(
+ (screenWidth - viewport.width()) / 2,
+ (screenHeight - viewport.height()) / 2);
+
+ GL_CALL(glViewport(viewport.left, viewport.top, viewport.width(), viewport.height()));
+ setMatrices(true);
+}
- void setViewportToRect(int16 outputWidth, int16 outputHeight) {
- _outputSize.x = MIN(outputWidth, g_system->getWidth());
- _outputSize.y = MIN(outputHeight, g_system->getHeight());
- GL_CALL(glViewport(0, 0, _outputSize.x, _outputSize.y));
- setMatrices(false);
- }
+void OpenGLRenderer::setViewportToRect(int16 outputWidth, int16 outputHeight) {
+ _outputSize.x = MIN(outputWidth, g_system->getWidth());
+ _outputSize.y = MIN(outputHeight, g_system->getHeight());
+ GL_CALL(glViewport(0, 0, _outputSize.x, _outputSize.y));
+ setMatrices(false);
+}
- void checkFirstDrawCommand() {
- // We delay clearing the screen. It is much easier for the game
+void OpenGLRenderer::checkFirstDrawCommand() {
+ // We delay clearing the screen. It is much easier for the game
// to switch to a framebuffer before
- if (!_isFirstDrawCommand)
- return;
- _isFirstDrawCommand = false;
- GL_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
- GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
- }
-
- Point _resolution, _outputSize;
- Surface *_currentOutput = nullptr;
- OpenGLTexture *_currentTexture = nullptr;
- BlendMode _currentBlendMode = (BlendMode)-1;
- float _currentLodBias = 0.0f;
- bool _isFirstDrawCommand = false;
-};
-
-IRenderer *IRenderer::createOpenGLRenderer(Point resolution) {
- initGraphics3d(resolution.x, resolution.y);
- return new OpenGLRenderer(resolution);
+ if (!_isFirstDrawCommand)
+ return;
+ _isFirstDrawCommand = false;
+ GL_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+ GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
}
}
diff --git a/engines/alcachofa/graphics-opengl.h b/engines/alcachofa/graphics-opengl.h
new file mode 100644
index 00000000000..adafff8a8f9
--- /dev/null
+++ b/engines/alcachofa/graphics-opengl.h
@@ -0,0 +1,82 @@
+/* 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/>.
+ *
+ */
+
+#ifndef ALCACHOFA_GRAPHICS_OPENGL_H
+#define ALCACHOFA_GRAPHICS_OPENGL_H
+
+#include "alcachofa/graphics.h"
+
+#include "graphics/managed_surface.h"
+#include "graphics/opengl/system_headers.h"
+#include "graphics/opengl/debug.h"
+
+namespace Alcachofa {
+
+struct OpenGLFormat {
+ GLenum _format, _type;
+ inline bool isValid() const { return _format != GL_NONE; }
+};
+
+OpenGLFormat getOpenGLFormatOf(const Graphics::PixelFormat &format);
+
+class OpenGLTexture : public ITexture {
+public:
+ OpenGLTexture(int32 w, int32 h, bool withMipmaps);
+ ~OpenGLTexture() override;
+ void update(const Graphics::Surface &surface) override;
+ void setMirrorWrap(bool wrap);
+
+ inline GLuint handle() const { return _handle; }
+
+private:
+ GLuint _handle;
+ bool _withMipmaps;
+ bool _mirrorWrap = true;
+};
+
+class OpenGLRenderer : public virtual IRenderer {
+public:
+ OpenGLRenderer(Common::Point resolution);
+
+ Common::ScopedPtr<ITexture> createTexture(int32 w, int32 h, bool withMipmaps) override;
+ void end() override;
+ void setOutput(Graphics::Surface &output) override;
+ bool hasOutput() const override;
+
+protected:
+ void resetState();
+ void setBlendFunc(BlendMode blendMode); ///< just the blend-func, not texenv/shader uniform
+ void setViewportToScreen();
+ void setViewportToRect(int16 outputWidth, int16 outputHeight);
+ virtual void setMatrices(bool flipped) = 0;
+ void checkFirstDrawCommand();
+
+ Common::Point _resolution, _outputSize;
+ Graphics::Surface *_currentOutput = nullptr;
+ OpenGLTexture *_currentTexture = nullptr;
+ BlendMode _currentBlendMode = (BlendMode)-1;
+ float _currentLodBias = 0.0f;
+ bool _isFirstDrawCommand = false;
+};
+
+}
+
+#endif // ALCACHOFA_GRAPHICS_OPENGL_H
diff --git a/engines/alcachofa/graphics.h b/engines/alcachofa/graphics.h
index a3b0727c8f2..7687084cc48 100644
--- a/engines/alcachofa/graphics.h
+++ b/engines/alcachofa/graphics.h
@@ -95,7 +95,7 @@ public:
static IRenderer *createOpenGLRenderer(Common::Point resolution);
};
-class IDebugRenderer : public IRenderer {
+class IDebugRenderer : public virtual IRenderer {
public:
virtual void debugPolygon(
Common::Span<Math::Vector2d> points,
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 71f29b4833f..4b145a163cc 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -23,6 +23,11 @@ MODULE_OBJS = \
sounds.o \
ui-objects.o
+ifdef USE_OPENGL_GAME
+MODULE_OBJS += \
+ graphics-opengl-classic.o
+endif
+
# This module can be built as a plugin
ifeq ($(ENABLE_ALCACHOFA), DYNAMIC_PLUGIN)
Commit: 4cd127c9ff2c35bd5f7abb32a1fb0ef00830932c
https://github.com/scummvm/scummvm/commit/4cd127c9ff2c35bd5f7abb32a1fb0ef00830932c
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Add OpenGL shaders support for GLES2 platforms
Changed paths:
A engines/alcachofa/graphics-opengl-shaders.cpp
engines/alcachofa/graphics-opengl-classic.cpp
engines/alcachofa/graphics-opengl.cpp
engines/alcachofa/graphics-opengl.h
engines/alcachofa/module.mk
diff --git a/engines/alcachofa/graphics-opengl-classic.cpp b/engines/alcachofa/graphics-opengl-classic.cpp
index 578d1e067a9..53e21ce3bc1 100644
--- a/engines/alcachofa/graphics-opengl-classic.cpp
+++ b/engines/alcachofa/graphics-opengl-classic.cpp
@@ -127,24 +127,10 @@ public:
Angle rotation,
Vector2d texMin,
Vector2d texMax) override {
- Vector2d positions[] = {
- topLeft + Vector2d(0, 0),
- topLeft + Vector2d(0, +size.getY()),
- topLeft + Vector2d(+size.getX(), +size.getY()),
- topLeft + Vector2d(+size.getX(), 0),
- };
- if (abs(rotation.getDegrees()) > epsilon) {
- const Vector2d zero(0, 0);
- for (int i = 0; i < 4; i++)
- positions[i].rotateAround(zero, rotation);
- }
+ Vector2d positions[4], texCoords[4];
+ getQuadPositions(topLeft, size, rotation, positions);
+ getQuadTexCoords(texMin, texMax, texCoords);
- Vector2d texCoords[] = {
- { texMin.getX(), texMin.getY() },
- { texMin.getX(), texMax.getY() },
- { texMax.getX(), texMax.getY() },
- { texMax.getX(), texMin.getY() }
- };
if (_currentTexture != nullptr) {
// float equality is fine here, if it was calculated it was not a normal graphic
_currentTexture->setMirrorWrap(texMin != Vector2d() || texMax != Vector2d(1, 1));
@@ -230,8 +216,8 @@ public:
}
};
-IRenderer *IRenderer::createOpenGLRenderer(Point resolution) {
- initGraphics3d(resolution.x, resolution.y);
+IRenderer *createOpenGLRendererClassic(Point resolution) {
+ debug("Use OpenGL classic renderer");
return new OpenGLRendererClassic(resolution);
}
diff --git a/engines/alcachofa/graphics-opengl-shaders.cpp b/engines/alcachofa/graphics-opengl-shaders.cpp
new file mode 100644
index 00000000000..c3ff94009bb
--- /dev/null
+++ b/engines/alcachofa/graphics-opengl-shaders.cpp
@@ -0,0 +1,255 @@
+/* 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/graphics-opengl.h"
+#include "alcachofa/detection.h"
+
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/opengl/shader.h"
+
+using namespace Common;
+using namespace Math;
+using namespace Graphics;
+using namespace OpenGL;
+
+namespace Alcachofa {
+
+class OpenGLRendererShaders : public OpenGLRenderer {
+ struct Vertex {
+ Vector2d pos;
+ Vector2d uv;
+ Color color;
+ };
+
+ struct VBO {
+ VBO(GLuint bufferId) : _bufferId(bufferId) {}
+ ~VBO() {
+ Shader::freeBuffer(_bufferId);
+ }
+
+ GLuint _bufferId;
+ uint _capacity = 0;
+ };
+public:
+ OpenGLRendererShaders(Point resolution)
+ : OpenGLRenderer(resolution) {
+ if (!_shader.loadFromStrings("alcachofa", kVertexShader, kFragmentShader, kAttributes))
+ error("Could not load shader");
+
+ // we use more than one VBO to reduce implicit synchronization
+ for (int i = 0; i < 4; i++)
+ _vbos.emplace_back(Shader::createBuffer(GL_ARRAY_BUFFER, 0, nullptr, GL_STREAM_DRAW));
+
+ _vertices.resize(8 * 6);
+
+ _whiteTexture.reset(new OpenGLTexture(1, 1, false));
+ const byte whiteData[] = { 0xff, 0xff, 0xff, 0xff };
+ GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whiteData));
+ }
+
+ void begin() override {
+ resetState();
+ _needsNewBatch = true;
+ }
+
+ void end() override {
+ checkFirstDrawCommand();
+ OpenGLRenderer::end();
+ }
+
+ void setTexture(ITexture *texture) override {
+ if (texture == _currentTexture)
+ return;
+ _needsNewBatch = true;
+
+ if (texture == nullptr)
+ _currentTexture = nullptr;
+ else {
+ _currentTexture = dynamic_cast<OpenGLTexture *>(texture);
+ assert(_currentTexture != nullptr);
+ }
+ }
+
+ void setBlendMode(BlendMode blendMode) override {
+ if (blendMode == _currentBlendMode)
+ return;
+ _needsNewBatch = true;
+ _currentBlendMode = blendMode;
+ }
+
+ void setLodBias(float lodBias) override {
+ if (abs(_currentLodBias - lodBias) < epsilon)
+ return;
+ _needsNewBatch = true;
+ _currentLodBias = lodBias;
+ }
+
+ void quad(
+ Vector2d topLeft,
+ Vector2d size,
+ Color color,
+ Angle rotation,
+ Vector2d texMin,
+ Vector2d texMax) override {
+ if (_needsNewBatch) {
+ _needsNewBatch = false;
+ checkFirstDrawCommand();
+ }
+
+ if (_currentTexture != nullptr) {
+ // float equality is fine here, if it was calculated it was not a normal graphic
+ _currentTexture->setMirrorWrap(texMin != Vector2d() || texMax != Vector2d(1, 1));
+ }
+
+ Vector2d positions[4], texCoords[4];
+ getQuadPositions(topLeft, size, rotation, positions);
+ getQuadTexCoords(texMin, texMax, texCoords);
+ _vertices.push_back({ positions[0], texCoords[0], color });
+ _vertices.push_back({ positions[1], texCoords[1], color });
+ _vertices.push_back({ positions[2], texCoords[2], color });
+ _vertices.push_back({ positions[0], texCoords[0], color });
+ _vertices.push_back({ positions[2], texCoords[2], color });
+ _vertices.push_back({ positions[3], texCoords[3], color });
+ }
+
+ void setMatrices(bool flipped) override {
+ // adapted from https://en.wikipedia.org/wiki/Orthographic_projection
+ const float left = 0.0f;
+ const float right = _resolution.x;
+ const float bottom = flipped ? _resolution.y : 0.0f;
+ const float top = flipped ? 0.0f : _resolution.y;
+ const float near = -1.0f;
+ const float far = 1.0f;
+
+ _projection.setToIdentity();
+ _projection(0, 0) = 2.0f / (right - left);
+ _projection(1, 1) = 2.0f / (top - bottom);
+ _projection(2, 2) = -2.0f / (far - near);
+ _projection(3, 0) = -(right + left) / (right - left);
+ _projection(3, 1) = -(top + bottom) / (top - bottom);
+ _projection(3, 2) = -(far + near) / (far - near);
+ }
+
+private:
+ void checkFirstDrawCommand() {
+ OpenGLRenderer::checkFirstDrawCommand();
+
+ // submit batch
+ if (!_vertices.empty()) {
+ auto &vbo = _vbos[_curVBO];
+ _curVBO = (_curVBO + 1) % _vbos.size();
+
+ _shader.enableVertexAttribute("in_pos", vbo._bufferId, 2, GL_FLOAT, false, sizeof(Vertex), offsetof(Vertex, pos));
+ _shader.enableVertexAttribute("in_uv", vbo._bufferId, 2, GL_FLOAT, false, sizeof(Vertex), offsetof(Vertex, uv));
+ _shader.enableVertexAttribute("in_color", vbo._bufferId, 4, GL_UNSIGNED_BYTE, true, sizeof(Vertex), offsetof(Vertex, color));
+ _shader.use(true);
+
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, _batchTexture == nullptr
+ ? _whiteTexture->handle()
+ : _batchTexture->handle()));
+ GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo._bufferId));
+ if (vbo._capacity < _vertices.size()) {
+ vbo._capacity = _vertices.size();
+ glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * _vertices.size(), _vertices.data(), GL_STREAM_DRAW);
+ } else
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * _vertices.size(), _vertices.data());
+ glDrawArrays(GL_TRIANGLES, 0, _vertices.size());
+ _vertices.clear();
+ }
+
+ // setup next batch
+ setBlendFunc(_currentBlendMode);
+ _shader.setUniform("projection", _projection);
+ _shader.setUniform("blendMode", _currentTexture == nullptr ? 5 : (int)_currentBlendMode);
+ _shader.setUniform1f("lodBias", _currentLodBias);
+ _shader.setUniform("texture", 0);
+ _batchTexture = _currentTexture;
+ }
+
+ Matrix4 _projection;
+ Shader _shader;
+ Array<VBO> _vbos;
+ Array<Vertex> _vertices;
+ uint _curVBO = 0;
+ uint _usedTextureUnits = 0;
+ bool _needsNewBatch = false;
+ OpenGLTexture *_batchTexture = nullptr;
+ ScopedPtr<OpenGLTexture> _whiteTexture;
+
+ static constexpr const char *const kAttributes[] = {
+ "in_pos",
+ "in_uv",
+ "in_color",
+ nullptr
+ };
+
+ static constexpr const char *const kVertexShader = R"(
+ uniform mat4 projection;
+
+ attribute vec2 in_pos;
+ attribute vec2 in_uv;
+ attribute vec4 in_color;
+
+ varying vec2 var_uv;
+ varying vec4 var_color;
+
+ void main() {
+ gl_Position = projection * vec4(in_pos, 0.0, 1.0);
+ var_uv = in_uv;
+ var_color = in_color;
+ })";
+
+ static constexpr const char *const kFragmentShader = R"(
+ #ifdef GL_ES
+ precision mediump float;
+ #endif
+
+ uniform sampler2D texture;
+ uniform int blendMode;
+ uniform float lodBias;
+
+ varying vec2 var_uv;
+ varying vec4 var_color;
+
+ void main() {
+ vec4 tex_color = texture2D(texture, var_uv, lodBias);
+ if (blendMode <= 2) { // AdditiveAlpha, Additive and Multiply
+ gl_FragColor.rgb = tex_color.rgb * var_color.a;
+ gl_FragColor.a = tex_color.a;
+ } else if (blendMode == 3) { // Alpha
+ gl_FragColor.rgb = tex_color.rgb;
+ gl_FragColor.a = var_color.a;
+ } else if (blendMode == 4) { // Tinted
+ gl_FragColor.rgb = var_color.rgb * var_color.a * tex_color.rgb;
+ gl_FragColor.a = tex_color.a;
+ } else { // Disabled texture
+ gl_FragColor = var_color;
+ }
+ })";
+};
+
+IRenderer *createOpenGLRendererShaders(Point resolution) {
+ debug("Use OpenGL shaders renderer");
+ return new OpenGLRendererShaders(resolution);
+}
+
+}
diff --git a/engines/alcachofa/graphics-opengl.cpp b/engines/alcachofa/graphics-opengl.cpp
index 8c51e92f1d7..cb288c05155 100644
--- a/engines/alcachofa/graphics-opengl.cpp
+++ b/engines/alcachofa/graphics-opengl.cpp
@@ -24,7 +24,9 @@
#include "alcachofa/graphics-opengl.h"
#include "common/system.h"
+#include "common/config-manager.h"
#include "engines/util.h"
+#include "graphics/renderer.h"
using namespace Common;
using namespace Math;
@@ -33,31 +35,19 @@ using namespace Graphics;
namespace Alcachofa {
static bool areComponentsInOrder(const PixelFormat &format, int r, int g, int b, int a) {
- return format == (a < 0
- ? PixelFormat(3, 8, 8, 8, 0, r * 8, g * 8, b * 8, 0)
- : PixelFormat(4, 8, 8, 8, 8, r * 8, g * 8, b * 8, a * 8));
-}
-
-OpenGLFormat getOpenGLFormatOf(const PixelFormat &format) {
- if (areComponentsInOrder(format, 0, 1, 2, 3))
- return { GL_RGBA, GL_UNSIGNED_BYTE };
- else if (areComponentsInOrder(format, 3, 2, 1, 0))
- return { GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 };
- else if (areComponentsInOrder(format, 0, 1, 2, -1))
- return { GL_RGB, GL_UNSIGNED_BYTE };
- else if (areComponentsInOrder(format, 2, 1, 0, 3))
- return { GL_BGRA, GL_UNSIGNED_BYTE };
- else if (areComponentsInOrder(format, 2, 1, 0, -1))
- return { GL_BGR, GL_UNSIGNED_BYTE };
- // we could look for packed formats here as well in the future
- else
- return { GL_NONE, GL_NONE };
+ return format == PixelFormat(4, 8, 8, 8, 8, r * 8, g * 8, b * 8, a * 8);
+}
+
+static bool isCompatibleFormat(const PixelFormat &format) {
+ return areComponentsInOrder(format, 0, 1, 2, 3) ||
+ areComponentsInOrder(format, 3, 2, 1, 0);
}
OpenGLTexture::OpenGLTexture(int32 w, int32 h, bool withMipmaps)
: ITexture({ (int16)w, (int16)h })
, _withMipmaps(withMipmaps) {
- GL_CALL(glEnable(GL_TEXTURE_2D));
+ glEnable(GL_TEXTURE_2D); // will error on GLES2, but that is okay
+ 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));
@@ -71,17 +61,43 @@ OpenGLTexture::~OpenGLTexture() {
}
void OpenGLTexture::update(const Surface &surface) {
- OpenGLFormat format = getOpenGLFormatOf(surface.format);
+ assert(isCompatibleFormat(surface.format));
assert(surface.w == size().x && surface.h == size().y);
- assert(format.isValid());
- GL_CALL(glEnable(GL_TEXTURE_2D));
+ // GLES2 only supports GL_RGBA but we need BlendBlit::getSupportedPixelFormat to use blendBlit
+ // We also do not want to keep surface memory for textures that are not updated repeatedly
+ const void *pixels;
+ if (!areComponentsInOrder(surface.format, 0, 1, 2, 3)) {
+ if (_tmpSurface.empty())
+ _tmpSurface.create(surface.w, surface.h, PixelFormat::createFormatRGBA32());
+ crossBlit(
+ (byte *)_tmpSurface.getPixels(),
+ (const byte *)surface.getPixels(),
+ _tmpSurface.pitch,
+ surface.pitch,
+ surface.w,
+ surface.h,
+ _tmpSurface.format,
+ surface.format);
+ pixels = _tmpSurface.getPixels();
+ } else {
+ glEnable(GL_TEXTURE_2D);
+ OpenGL::clearGLError();
+ pixels = surface.getPixels();
+ }
+
GL_CALL(glBindTexture(GL_TEXTURE_2D, _handle));
- GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.w, surface.h, 0, format._format, format._type, surface.getPixels()));
+ GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.w, surface.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
if (_withMipmaps)
GL_CALL(glGenerateMipmap(GL_TEXTURE_2D));
else
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
+
+ if (!_tmpSurface.empty()) {
+ if (!_didConvertOnce)
+ _tmpSurface.free();
+ _didConvertOnce = true;
+ }
}
void OpenGLTexture::setMirrorWrap(bool wrap) {
@@ -94,12 +110,13 @@ void OpenGLTexture::setMirrorWrap(bool wrap) {
else
wrapMode = OpenGLContext.textureEdgeClampSupported ? GL_CLAMP_TO_EDGE : GL_CLAMP;
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, _handle));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode));
}
OpenGLRenderer::OpenGLRenderer(Point resolution) : _resolution(resolution) {
- GL_CALL(glDisable(GL_LIGHTING));
+ initGraphics3d(resolution.x, resolution.y);
GL_CALL(glDisable(GL_DEPTH_TEST));
GL_CALL(glDisable(GL_SCISSOR_TEST));
GL_CALL(glDisable(GL_STENCIL_TEST));
@@ -131,16 +148,20 @@ void OpenGLRenderer::end() {
if (_currentOutput != nullptr) {
g_system->presentBuffer();
- auto format = getOpenGLFormatOf(_currentOutput->format);
GL_CALL(glReadPixels(
0,
0,
_outputSize.x,
_outputSize.y,
- format._format,
- format._type,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
_currentOutput->getPixels()
));
+ if (_currentOutput->format != PixelFormat::createFormatRGBA32()) {
+ auto targetFormat = _currentOutput->format;
+ _currentOutput->format = PixelFormat::createFormatRGBA32();
+ _currentOutput->convertToInPlace(targetFormat);
+ }
}
}
@@ -178,8 +199,7 @@ void OpenGLRenderer::setOutput(Surface &output) {
debugC(0, kDebugGraphics, "Output is larger than screen, output will be cropped (%d, %d) > (%d, %d)",
output.w, output.h, g_system->getWidth(), g_system->getHeight());
- auto format = getOpenGLFormatOf(output.format);
- if (format._format == GL_NONE) {
+ if (!isCompatibleFormat(output.format)) {
auto formatString = output.format.toString();
debugC(0, kDebugGraphics, "Cannot use pixelformat of given output surface: %s", formatString.c_str());
_currentOutput = nullptr;
@@ -227,4 +247,64 @@ void OpenGLRenderer::checkFirstDrawCommand() {
GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
}
+void OpenGLRenderer::getQuadPositions(Vector2d topLeft, Vector2d size, Angle rotation, Vector2d positions[]) const {
+ positions[0] = topLeft + Vector2d(0, 0);
+ positions[1] = topLeft + Vector2d(0, +size.getY());
+ positions[2] = topLeft + Vector2d(+size.getX(), +size.getY());
+ positions[3] = topLeft + Vector2d(+size.getX(), 0);
+ if (abs(rotation.getDegrees()) > epsilon) {
+ const Vector2d zero(0, 0);
+ for (int i = 0; i < 4; i++)
+ positions[i].rotateAround(zero, rotation);
+ }
+}
+
+void OpenGLRenderer::getQuadTexCoords(Vector2d texMin, Vector2d texMax, Vector2d texCoords[]) const {
+ texCoords[0] = { texMin.getX(), texMin.getY() };
+ texCoords[1] = { texMin.getX(), texMax.getY() };
+ texCoords[2] = { texMax.getX(), texMax.getY() };
+ texCoords[3] = { texMax.getX(), texMin.getY() };
+}
+
+IRenderer *IRenderer::createOpenGLRenderer(Point resolution) {
+ const auto available = Renderer::getAvailableTypes() & ~kRendererTypeTinyGL;
+ const auto &rendererCode = ConfMan.get("renderer");
+ RendererType rendererType = Renderer::parseTypeCode(rendererCode);
+ rendererType = (RendererType)(rendererType & available);
+
+ IRenderer *renderer = nullptr;
+ switch (rendererType) {
+ case kRendererTypeOpenGLShaders:
+ renderer = createOpenGLRendererShaders(resolution);
+ break;
+ case kRendererTypeOpenGL:
+ renderer = createOpenGLRendererClassic(resolution);
+ break;
+ default:
+ if (available & kRendererTypeOpenGLShaders)
+ renderer = createOpenGLRendererShaders(resolution);
+ else if (available & kRendererTypeOpenGL)
+ renderer = createOpenGLRendererClassic(resolution);
+ break;
+ }
+
+ if (renderer == nullptr)
+ error("Could not create a renderer, GL context type: %d", (int)OpenGLContext.type);
+ return renderer;
+}
+
+#ifndef USE_OPENGL_SHADERS
+IRenderer *createOpenGLRendererShaders(Point _) {
+ (void)_;
+ return nullptr;
+}
+#endif
+
+#ifndef USE_OPENGL_GAME
+IRenderer *createOpenGLRendererClassic(Point _) {
+ (void)_;
+ return nullptr;
+}
+#endif
+
}
diff --git a/engines/alcachofa/graphics-opengl.h b/engines/alcachofa/graphics-opengl.h
index adafff8a8f9..92c842a4826 100644
--- a/engines/alcachofa/graphics-opengl.h
+++ b/engines/alcachofa/graphics-opengl.h
@@ -30,13 +30,6 @@
namespace Alcachofa {
-struct OpenGLFormat {
- GLenum _format, _type;
- inline bool isValid() const { return _format != GL_NONE; }
-};
-
-OpenGLFormat getOpenGLFormatOf(const Graphics::PixelFormat &format);
-
class OpenGLTexture : public ITexture {
public:
OpenGLTexture(int32 w, int32 h, bool withMipmaps);
@@ -50,6 +43,8 @@ private:
GLuint _handle;
bool _withMipmaps;
bool _mirrorWrap = true;
+ bool _didConvertOnce = false;
+ Graphics::ManagedSurface _tmpSurface;
};
class OpenGLRenderer : public virtual IRenderer {
@@ -68,6 +63,8 @@ protected:
void setViewportToRect(int16 outputWidth, int16 outputHeight);
virtual void setMatrices(bool flipped) = 0;
void checkFirstDrawCommand();
+ void getQuadPositions(Math::Vector2d topLeft, Math::Vector2d size, Math::Angle rotation, Math::Vector2d positions[]) const;
+ void getQuadTexCoords(Math::Vector2d texMin, Math::Vector2d texMax, Math::Vector2d texCoords[]) const;
Common::Point _resolution, _outputSize;
Graphics::Surface *_currentOutput = nullptr;
@@ -77,6 +74,9 @@ protected:
bool _isFirstDrawCommand = false;
};
+IRenderer *createOpenGLRendererClassic(Common::Point resolution);
+IRenderer *createOpenGLRendererShaders(Common::Point resolution);
+
}
#endif // ALCACHOFA_GRAPHICS_OPENGL_H
diff --git a/engines/alcachofa/module.mk b/engines/alcachofa/module.mk
index 4b145a163cc..575ad2fa363 100644
--- a/engines/alcachofa/module.mk
+++ b/engines/alcachofa/module.mk
@@ -28,6 +28,10 @@ MODULE_OBJS += \
graphics-opengl-classic.o
endif
+ifdef USE_OPENGL_SHADERS
+MODULE_OBJS += \
+ graphics-opengl-shaders.o
+endif
# This module can be built as a plugin
ifeq ($(ENABLE_ALCACHOFA), DYNAMIC_PLUGIN)
Commit: 007741ef41d3cce2d9ec09352c6e654f0c6cc3ff
https://github.com/scummvm/scummvm/commit/007741ef41d3cce2d9ec09352c6e654f0c6cc3ff
Author: Helco (hermann.noll at hotmail.com)
Date: 2025-09-02T22:05:01+02:00
Commit Message:
ALCACHOFA: Fix invalid ODR-usage
Changed paths:
engines/alcachofa/graphics-opengl-shaders.cpp
diff --git a/engines/alcachofa/graphics-opengl-shaders.cpp b/engines/alcachofa/graphics-opengl-shaders.cpp
index c3ff94009bb..ecec9016e4d 100644
--- a/engines/alcachofa/graphics-opengl-shaders.cpp
+++ b/engines/alcachofa/graphics-opengl-shaders.cpp
@@ -52,6 +52,12 @@ class OpenGLRendererShaders : public OpenGLRenderer {
public:
OpenGLRendererShaders(Point resolution)
: OpenGLRenderer(resolution) {
+ static constexpr const char *const kAttributes[] = {
+ "in_pos",
+ "in_uv",
+ "in_color",
+ nullptr
+ };
if (!_shader.loadFromStrings("alcachofa", kVertexShader, kFragmentShader, kAttributes))
error("Could not load shader");
@@ -195,13 +201,6 @@ private:
OpenGLTexture *_batchTexture = nullptr;
ScopedPtr<OpenGLTexture> _whiteTexture;
- static constexpr const char *const kAttributes[] = {
- "in_pos",
- "in_uv",
- "in_color",
- nullptr
- };
-
static constexpr const char *const kVertexShader = R"(
uniform mat4 projection;
More information about the Scummvm-git-logs
mailing list