[Scummvm-git-logs] scummvm master -> 6d0d9a570407287a9f1d85559d75c4f755488113

Helco noreply at scummvm.org
Wed May 27 17:12:12 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
6d0d9a5704 ALCACHOFA: Implement V2 menu variants


Commit: 6d0d9a570407287a9f1d85559d75c4f755488113
    https://github.com/scummvm/scummvm/commit/6d0d9a570407287a9f1d85559d75c4f755488113
Author: Helco (hermann.noll at hotmail.com)
Date: 2026-05-27T19:11:48+02:00

Commit Message:
ALCACHOFA: Implement V2 menu variants

Changed paths:
    engines/alcachofa/alcachofa.cpp
    engines/alcachofa/alcachofa.h
    engines/alcachofa/graphics.cpp
    engines/alcachofa/menu.cpp
    engines/alcachofa/menu.h
    engines/alcachofa/objects.h
    engines/alcachofa/player.cpp
    engines/alcachofa/rooms.cpp
    engines/alcachofa/rooms.h
    engines/alcachofa/ui-objects.cpp


diff --git a/engines/alcachofa/alcachofa.cpp b/engines/alcachofa/alcachofa.cpp
index e3b79fada02..0fb648f95c7 100644
--- a/engines/alcachofa/alcachofa.cpp
+++ b/engines/alcachofa/alcachofa.cpp
@@ -89,7 +89,7 @@ Common::Error AlcachofaEngine::run() {
 	_script.reset(new Script());
 	_player.reset(new Player());
 	_globalUI.reset(isV1() || isV2() ? static_cast<GlobalUI *>(new GlobalUIV1()) : new GlobalUIV3());
-	_menu.reset(isV1() ? static_cast<Menu *>(new MenuV1()) : new MenuV3());
+	_menu.reset(Menu::create());
 	setMillis(0);
 	game().onLoadedGameFiles();
 
@@ -454,11 +454,13 @@ void Config::registerDefaults() {
 	ConfMan.registerDefault("music_volume", c._musicVolume);
 	ConfMan.registerDefault("speech_volume", c._speechVolume);
 	ConfMan.registerDefault("sfx_volume", c._speechVolume);
+	ConfMan.registerDefault("cursor", c._cursor);
 }
 
 void Config::loadFromScummVM() {
 	_musicVolume = (uint8)CLIP(ConfMan.getInt("music_volume"), 0, 255);
 	_speechVolume = (uint8)CLIP(ConfMan.getInt("speech_volume"), 0, 255);
+	_cursor = (uint8)CLIP(ConfMan.getInt("cursor"), 0, (int)kMaxCursor);
 	_subtitles = ConfMan.getBool("subtitles");
 	_highQuality = ConfMan.getBool("high_quality");
 	_bits32 = ConfMan.getBool("32_bits");
@@ -473,6 +475,7 @@ void Config::saveToScummVM() {
 	ConfMan.setInt("music_volume", _musicVolume);
 	ConfMan.setInt("speech_volume", _speechVolume);
 	ConfMan.setInt("sfx_volume", _speechVolume);
+	ConfMan.setInt("cursor", _cursor);
 	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 91790b776e3..96008e8fc69 100644
--- a/engines/alcachofa/alcachofa.h
+++ b/engines/alcachofa/alcachofa.h
@@ -81,12 +81,15 @@ public:
 
 class Config {
 public:
+	static constexpr const uint8 kMaxCursor = 3;
+
 	inline bool &subtitles() { return _subtitles; }
 	inline bool &highQuality() { return _highQuality; }
 	inline bool &bits32() { return _bits32; }
 	inline bool &texFilter() { return _texFilter; }
 	inline uint8 &musicVolume() { return _musicVolume; }
 	inline uint8 &speechVolume() { return _speechVolume; }
+	inline uint8 &cursor() { return _cursor; } // only used in V2
 
 	static void registerDefaults();
 	void loadFromScummVM();
@@ -100,7 +103,8 @@ private:
 		_texFilter = true;
 	uint8
 		_musicVolume = 255,
-		_speechVolume = 255;
+		_speechVolume = 255,
+		_cursor = 0;
 };
 
 class AlcachofaEngine : public Engine {
diff --git a/engines/alcachofa/graphics.cpp b/engines/alcachofa/graphics.cpp
index d01d4ea91a9..913de0b1d40 100644
--- a/engines/alcachofa/graphics.cpp
+++ b/engines/alcachofa/graphics.cpp
@@ -616,8 +616,8 @@ void Animation::prerenderFrame(int32 frameI) {
 
 struct TexCoords {
 	TexCoords(const Rect &inner, int16 outerW, int16 outerH) {
-		_min = Vector2d(0.5f / outerW, 0.5f / outerH);
-		_max = Vector2d((inner.width() - 0.5f) / outerW, (inner.height() - 0.5f) / outerH);
+		_min = Vector2d(0.0f, 0.0f);
+		_max = Vector2d((inner.width()) / (float)outerW, (inner.height()) / (float)outerH);
 	}
 
 	Vector2d _min, _max;
diff --git a/engines/alcachofa/menu.cpp b/engines/alcachofa/menu.cpp
index a211ffdad47..cc000fe77cf 100644
--- a/engines/alcachofa/menu.cpp
+++ b/engines/alcachofa/menu.cpp
@@ -63,6 +63,17 @@ static void convertToGrayscale(ManagedSurface &surface) {
 	}
 }
 
+Menu *Menu::create() {
+	if (g_engine->isV1())
+		return new MenuV1();
+	else if (g_engine->isV2())
+		return new MenuV2();
+	else if (g_engine->isV3())
+		return new MenuV3();
+	else
+		error("Menu is not implemented for this engine version");
+}
+
 Menu::Menu()
 	: _interactionSemaphore("menu")
 	, _saveFileMgr(g_system->getSavefileManager()) {}
@@ -110,6 +121,13 @@ void MenuV1::updateOpeningMenu() {
 		switchToState(MainMenuAction::ConfirmSavestate);
 }
 
+void MenuV2::updateOpeningMenu() {
+	bool willOpen = _openAtNextFrame;
+	Menu::updateOpeningMenu();
+	if (willOpen)
+		toggleMessageBox(false);
+}
+
 static int parseSavestateSlot(const String &filename) {
 	if (filename.size() < 5) // minimal name would be "t.###"
 		return 1;
@@ -150,18 +168,28 @@ void MenuV1::updateSelectedSavefile(bool hasJustSaved) {
 	captureObject->toggle(isInCorrectState && !isOnNewSlot());
 }
 
+void MenuV2::updateSelectedSavefile(bool hasJustSaved) {
+	Menu::updateSelectedSavefile(hasJustSaved);
+
+	auto getButton = [ ] (const char *name) -> MenuButton &{
+		return g_engine->player().currentRoom()->getRequiredObjectByName<MenuButton>(name);
+	};
+
+	getButton("CARGAR").isInteractable() = !isOnNewSlot();
+	getButton("ANTERIOR").toggle(_selectedSavefileI > 0);
+	getButton("SIGUIENTE").toggle(!isOnNewSlot());
+}
+
 void MenuV3::updateSelectedSavefile(bool hasJustSaved) {
 	Menu::updateSelectedSavefile(hasJustSaved);
 
-	auto getButton = [ ] (const char *name) {
-		MenuButton *button = dynamic_cast<MenuButton *>(g_engine->player().currentRoom()->getObjectByName(name));
-		scumm_assert(button != nullptr);
-		return button;
+	auto getButton = [ ] (const char *name) -> MenuButton& {
+		return g_engine->player().currentRoom()->getRequiredObjectByName<MenuButton>(name);
 	};
 
-	getButton("CARGAR")->isInteractable() = !isOnNewSlot();
-	getButton("ANTERIOR")->toggle(_selectedSavefileI > 0);
-	getButton("SIGUIENTE")->toggle(!isOnNewSlot());
+	getButton("CARGAR").isInteractable() = !isOnNewSlot();
+	getButton("ANTERIOR").toggle(_selectedSavefileI > 0);
+	getButton("SIGUIENTE").toggle(!isOnNewSlot());
 }
 
 bool Menu::tryReadOldSavefile() {
@@ -225,8 +253,10 @@ void Menu::triggerMainMenuAction(MainMenuAction action) {
 		g_engine->fadeExit();
 		break;
 	case MainMenuAction::NewGame:
-		// this action might be unused just like the only room it would appear: MENUPRINCIPALINICIO
-		g_engine->script().createProcess(MainCharacterKind::None, g_engine->world().initScriptName());
+		// this action is unused just like the only room it would appear: MENUPRINCIPALINICIO
+		// it also breaks the engine in very funny ways so let's not do anything instead
+		// g_engine->script().createProcess(MainCharacterKind::None, g_engine->world().initScriptName());
+		warning("MainMenuAction::NewGame triggered!");
 		break;
 	default:
 		g_engine->game().unknownMenuAction((int32)action);
@@ -276,6 +306,35 @@ void MenuV1::triggerMainMenuAction(MainMenuAction action) {
 	}
 }
 
+void MenuV2::triggerMainMenuAction(MainMenuAction action) {
+	switch (action) {
+	case MainMenuAction::NextSave:
+		if (_selectedSavefileI < _savefiles.size()) {
+			_selectedSavefileI++;
+			updateSelectedSavefile(false);
+		}
+		break;
+	case MainMenuAction::PrevSave:
+		if (_selectedSavefileI > 0) {
+			_selectedSavefileI--;
+			updateSelectedSavefile(false);
+		}
+		break;
+	case MainMenuAction::Exit:
+		toggleMessageBox(true);
+		break;
+	case MainMenuAction::Cancel:
+		toggleMessageBox(false);
+		break;
+	case MainMenuAction::Accept:
+		Menu::triggerMainMenuAction(MainMenuAction::Exit);
+		break;
+	default:
+		Menu::triggerMainMenuAction(action);
+		break;
+	}
+}
+
 void MenuV3::triggerMainMenuAction(MainMenuAction action) {
 	switch (action) {
 	case MainMenuAction::NextSave:
@@ -337,49 +396,59 @@ void Menu::triggerSave() {
 }
 
 void Menu::openOptionsMenu() {
+	_currentSlideButton = nullptr;
 	setOptionsState();
 	g_engine->player().changeRoom("MENUOPCIONES", true);
 }
 
 void MenuV1::setOptionsState() {}
 
-void MenuV3::setOptionsState() {
+void MenuV2::setOptionsState() {
 	Config &config = g_engine->config();
 	Room *optionsMenu = g_engine->world().getRoomByName("MENUOPCIONES");
 	scumm_assert(optionsMenu != nullptr);
+	auto getSlideButton = [&] (const char *name) -> SlideButton& {
+		return optionsMenu->getRequiredObjectByName<SlideButton>(name);
+	};
+	auto getPushButton = [&] (const char *name) -> PushButton& {
+		return optionsMenu->getRequiredObjectByName<PushButton>(name);
+	};
 
-	auto getSlideButton = [&] (const char *name) {
-		SlideButton *slideButton = dynamic_cast<SlideButton *>(optionsMenu->getObjectByName(name));
-		scumm_assert(slideButton != nullptr);
-		return slideButton;
+	// there is a mouse sensitivity slider, this does not exist in ScummVM, so we ignore it
+	getSlideButton("VOLUMENCD").value() = config.musicVolume() / 255.0f;
+	getSlideButton("VOLUMENAUDIO").value() = config.speechVolume() / 255.0f;
+
+	getPushButton("CURSOR0").isChecked() = config.cursor() == 0;
+	getPushButton("CURSOR1").isChecked() = config.cursor() == 1;
+	getPushButton("CURSOR2").isChecked() = config.cursor() == 2;
+	getPushButton("CURSOR3").isChecked() = config.cursor() == 3;
+	getPushButton("TEXTOSON").isChecked() = config.subtitles();
+	getPushButton("TEXTOSOFF").isChecked() = !config.subtitles();
+}
+
+void MenuV3::setOptionsState() {
+	Config &config = g_engine->config();
+	Room *optionsMenu = g_engine->world().getRoomByName("MENUOPCIONES");
+	scumm_assert(optionsMenu != nullptr);
+	auto getSlideButton = [&] (const char *name) -> SlideButton& {
+		return optionsMenu->getRequiredObjectByName<SlideButton>(name);
 	};
-	SlideButton
-		*slideMusicVolume = getSlideButton("Slider Musica"),
-		*slideSpeechVolume = getSlideButton("Slider Sonido");
-	slideMusicVolume->value() = config.musicVolume() / 255.0f;
-	slideSpeechVolume->value() = config.speechVolume() / 255.0f;
+	auto getCheckBox = [&] (const char *name) -> CheckBox& {
+		return optionsMenu->getRequiredObjectByName<CheckBox>(name);
+	};
+
+	getSlideButton("Slider Musica").value() = config.musicVolume() / 255.0f;
+	getSlideButton("Slider Sonido").value() = config.speechVolume() / 255.0f;
 
 	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());
+	getCheckBox("Boton ON").isChecked() = config.subtitles();
+	getCheckBox("Boton OFF").isChecked() = !config.subtitles();
+	getCheckBox("Boton 32 Bits").isChecked() = config.bits32();
+	getCheckBox("Boton 16 Bits").isChecked() = !config.bits32();
+	getCheckBox("Boton Alta").isChecked() = config.highQuality();
+	getCheckBox("Boton Baja").isChecked() = !config.highQuality();
+	getCheckBox("Boton Alta").toggle(config.bits32());
 }
 
 void Menu::triggerOptionsAction(OptionsMenuAction action) {
@@ -404,6 +473,18 @@ void Menu::triggerOptionsAction(OptionsMenuAction action) {
 	case OptionsMenuAction::Bits16:
 		config.bits32() = false;
 		break;
+	case OptionsMenuAction::Cursor0:
+		config.cursor() = 0;
+		break;
+	case OptionsMenuAction::Cursor1:
+		config.cursor() = 1;
+		break;
+	case OptionsMenuAction::Cursor2:
+		config.cursor() = 2;
+		break;
+	case OptionsMenuAction::Cursor3:
+		config.cursor() = 3;
+		break;
 	case OptionsMenuAction::MainMenu:
 		continueMainMenu();
 		break;
@@ -423,6 +504,8 @@ void Menu::triggerOptionsValue(OptionsMenuValue valueId, float value) {
 	case OptionsMenuValue::Speech:
 		config.speechVolume() = CLIP<uint8>((uint8)(value * 255), 0, 255);
 		break;
+	case OptionsMenuValue::Sensitivity:
+		break;
 	default:
 		warning("Unknown options menu value: %d", (int32)valueId);
 		break;
@@ -456,4 +539,22 @@ void MenuV1::switchToState(MainMenuAction state) {
 	}
 }
 
+void MenuV2::toggleMessageBox(bool show) {
+	auto getButton = [&] (const char *name) -> MenuButton& {
+		return g_engine->player().currentRoom()->getRequiredObjectByName<MenuButton>(name);
+	};
+
+	getButton("MBACEPTAR").toggle(show);
+	getButton("MBCANCELAR").toggle(show);
+
+	getButton("ANTERIOR").toggle(!show);
+	getButton("SIGUIENTE").toggle(!show);
+	getButton("CARGAR").toggle(!show);
+	getButton("GRABAR").toggle(!show);
+	getButton("INTERNET").toggle(!show);
+	getButton("OPCIONES").toggle(!show);
+	getButton("JUGAR").toggle(!show);
+	getButton("SALIR").toggle(!show);
+}
+
 }
diff --git a/engines/alcachofa/menu.h b/engines/alcachofa/menu.h
index 1194cb42f33..1fa1e395cec 100644
--- a/engines/alcachofa/menu.h
+++ b/engines/alcachofa/menu.h
@@ -27,6 +27,10 @@
 namespace Alcachofa {
 
 class Room;
+class SlideButton;
+
+// the order of these enums are compatible to V3 the first to be developed
+// any other version needs some translation
 
 enum class MainMenuAction : int32 {
 	ContinueGame = 0,
@@ -37,10 +41,12 @@ enum class MainMenuAction : int32 {
 	Exit,
 	NextSave,
 	PrevSave,
-	NewGame,
+	NewGame, // unused in any game
 	AlsoExit, // there seems to be no difference to Exit
 
 	ConfirmSavestate, // only used in V1
+	Accept, // only used in V2
+	Cancel, // only used in V2
 };
 
 enum class OptionsMenuAction : int32 {
@@ -50,16 +56,23 @@ enum class OptionsMenuAction : int32 {
 	LowQuality,
 	Bits32,
 	Bits16,
-	MainMenu
+	MainMenu,
+
+	Cursor0, // only used in V2
+	Cursor1,
+	Cursor2,
+	Cursor3,
 };
 
 enum class OptionsMenuValue : int32 {
 	Music = 0,
-	Speech = 1
+	Speech = 1,
+	Sensitivity = 2 // only used in V2 and no effect for ScummVM
 };
 
 class Menu {
 public:
+	static Menu *create();
 	Menu();
 	virtual ~Menu();
 
@@ -67,6 +80,7 @@ public:
 	inline uint32 millisBeforeMenu() const { return _millisBeforeMenu; }
 	inline Room *previousRoom() { return _previousRoom; }
 	inline FakeSemaphore &interactionSemaphore() { return _interactionSemaphore; }
+	inline SlideButton *&currentSlideButton() { return _currentSlideButton; }
 
 	void triggerLoad();
 	void resetAfterLoad();
@@ -100,6 +114,7 @@ protected:
 		_selectedSavefileI = 0;
 	Room *_previousRoom = nullptr;
 	FakeSemaphore _interactionSemaphore; // to prevent ScummVM loading during button clicks
+	SlideButton *_currentSlideButton = nullptr; // due to V2 we cannot store this in OptionsMenu
 	Common::String _selectedSavefileDescription = "<unset>";
 	Common::Array<Common::String> _savefiles;
 	Graphics::ManagedSurface
@@ -117,6 +132,17 @@ protected:
 	void setOptionsState() override;
 };
 
+class MenuV2 : public Menu {
+public:
+	void updateOpeningMenu() override;
+	void triggerMainMenuAction(MainMenuAction action) override;
+
+protected:
+	void updateSelectedSavefile(bool hasJustSaved) override;
+	void setOptionsState() override;
+	void toggleMessageBox(bool show);
+};
+
 class MenuV1 : public Menu {
 public:
 	void updateOpeningMenu() override;
diff --git a/engines/alcachofa/objects.h b/engines/alcachofa/objects.h
index 04967481b23..33f4dccb95d 100644
--- a/engines/alcachofa/objects.h
+++ b/engines/alcachofa/objects.h
@@ -224,7 +224,7 @@ public:
 	const char *typeName() const override;
 };
 
-class OptionsMenuButton final : public MenuButton {
+class OptionsMenuButton : public MenuButton {
 public:
 	static constexpr const char *kClassName = "CBotonMenuOpciones";
 	OptionsMenuButton(Room *room, Common::SeekableReadStream &stream);
@@ -234,7 +234,15 @@ public:
 	const char *typeName() const override;
 };
 
-class MainMenuButton final : public MenuButton {
+class OptionsMenuButtonV2 : public OptionsMenuButton {
+public:
+	OptionsMenuButtonV2(Room *room, Common::SeekableReadStream &stream);
+
+	void update() override;
+	void trigger() override;
+};
+
+class MainMenuButton : public MenuButton {
 public:
 	static constexpr const char *kClassName = "CBotonMenuPrincipal";
 	MainMenuButton(Room *room, Common::SeekableReadStream &stream);
@@ -242,19 +250,40 @@ public:
 	void update() override;
 	void trigger() override;
 	const char *typeName() const override;
+protected:
+	virtual MainMenuAction action() const;
+};
+
+class MainMenuButtonV2 final : public MainMenuButton {
+public:
+	MainMenuButtonV2(Room *room, Common::SeekableReadStream &stream);
+
+protected:
+	MainMenuAction action() const override;
 };
 
-class PushButton final : public PhysicalObject {
+// this is a variant of a CheckBox only used in V2
+class PushButton : public PhysicalObject {
 public:
 	static constexpr const char *kClassName = "CPushButton";
 	PushButton(Room *room, Common::SeekableReadStream &stream);
 
+	inline bool &isChecked() { return _isChecked; }
+
+	void draw() override;
+	void update() override;
+	void loadResources() override;
+	void freeResources() override;
+	void onHoverUpdate() override;
+	void onClick() override;
+	virtual void trigger();
 	const char *typeName() const override;
 
 private:
-	bool _alwaysVisible;
-	Graphic _graphic1, _graphic2;
-	int32 _actionId;
+	bool _isChecked = false;
+	Graphic _graphicHovered, _graphicChecked;
+	int32 _actionId = -1;
+	uint32 _clickTime = 0;
 };
 
 class EditBox : public PhysicalObject {
diff --git a/engines/alcachofa/player.cpp b/engines/alcachofa/player.cpp
index 00017417cfc..0c77c33c2fd 100644
--- a/engines/alcachofa/player.cpp
+++ b/engines/alcachofa/player.cpp
@@ -58,8 +58,9 @@ void Player::resetCursor() {
 }
 
 void Player::updateCursor() {
-	// TODO: V2 has additional cursor frames. How are they used?
-	if (g_engine->isV1() || g_engine->isV2() || g_engine->menu().isOpen())
+	if (g_engine->isV2())
+		_cursorFrameI = g_engine->config().cursor();
+	else if (g_engine->isV1() || g_engine->menu().isOpen())
 		_cursorFrameI = 0;
 	else if (_selectedObject == nullptr)
 		_cursorFrameI = !g_engine->input().isMouseLeftDown() || _pressedObject != nullptr ? 6 : 7;
diff --git a/engines/alcachofa/rooms.cpp b/engines/alcachofa/rooms.cpp
index 9781d35647c..33bdd3392f7 100644
--- a/engines/alcachofa/rooms.cpp
+++ b/engines/alcachofa/rooms.cpp
@@ -60,12 +60,20 @@ static ObjectBase *readRoomObject(Room *room, const String &type, SeekableReadSt
 		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 == MainMenuButton::kClassName) {
+		if (g_engine->isV2())
+			return new MainMenuButtonV2(room, stream);
+		else
+			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 == OptionsMenuButton::kClassName) {
+		if (g_engine->isV2())
+			return new OptionsMenuButtonV2(room, stream);
+		else
+			return new OptionsMenuButton(room, stream);
+	}
 	else if (type == EditBox::kClassName) {
 		if (g_engine->isV2())
 			return new EditBoxV2(room, stream);
@@ -396,7 +404,6 @@ bool OptionsMenu::updateInput() {
 void OptionsMenu::loadResources() {
 	Room::loadResources();
 	_lastSelectedObject = nullptr;
-	_currentSlideButton = nullptr;
 	_idleArm = getObjectByName("Brazo");
 }
 
diff --git a/engines/alcachofa/rooms.h b/engines/alcachofa/rooms.h
index 5d6235c7c82..b8cd56c0b48 100644
--- a/engines/alcachofa/rooms.h
+++ b/engines/alcachofa/rooms.h
@@ -55,6 +55,13 @@ public:
 	inline ObjectIterator beginObjects() const { return _objects.begin(); }
 	inline ObjectIterator endObjects() const { return _objects.end(); }
 
+	template<class TObjectType>
+	TObjectType &getRequiredObjectByName(const char *name) const {
+		TObjectType *obj = dynamic_cast<TObjectType *>(getObjectByName(name));
+		scumm_assert(obj != nullptr);
+		return *obj;
+	}
+
 	void update();
 	void draw();
 	virtual bool updateInput();
@@ -111,12 +118,10 @@ public:
 	void loadResources() override;
 
 	void clearLastSelectedObject(); // to reset arm animation
-	inline SlideButton *&currentSlideButton() { return _currentSlideButton; }
 
 private:
 	ShapeObject *_lastSelectedObject = nullptr;
 	ObjectBase *_idleArm = nullptr;
-	SlideButton *_currentSlideButton = nullptr;
 };
 
 class ConnectMenu final : public Room {
diff --git a/engines/alcachofa/ui-objects.cpp b/engines/alcachofa/ui-objects.cpp
index c30c9a40007..67354c72653 100644
--- a/engines/alcachofa/ui-objects.cpp
+++ b/engines/alcachofa/ui-objects.cpp
@@ -192,6 +192,11 @@ void MenuButton::draw() {
 		: _isClicked ? _graphicClicked
 		: wasSelected() ? _graphicHovered
 		: _graphicNormal;
+
+	// In V2 the normal graphic has no animation and is just baked into the background
+	if (!graphic.hasAnimation())
+		return;
+
 	graphic.update();
 	g_engine->drawQueue().add<AnimationDrawRequest>(graphic, true, BlendMode::AdditiveAlpha);
 }
@@ -268,6 +273,20 @@ void OptionsMenuButton::trigger() {
 	g_engine->menu().triggerOptionsAction((OptionsMenuAction)actionId());
 }
 
+OptionsMenuButtonV2::OptionsMenuButtonV2(Room *room, SeekableReadStream &stream)
+	: OptionsMenuButton(room, stream) {}
+
+void OptionsMenuButtonV2::update() {
+	MenuButton::update();
+	if (g_engine->input().wasMenuKeyPressed())
+		onClick();
+}
+
+void OptionsMenuButtonV2::trigger() {
+	// there is only one button, but its ID is the same as SubtitlesOn, so we override it here
+	g_engine->menu().triggerOptionsAction(OptionsMenuAction::MainMenu);
+}
+
 const char *MainMenuButton::typeName() const { return "MainMenuButton"; }
 
 MainMenuButton::MainMenuButton(Room *room, SeekableReadStream &stream)
@@ -275,25 +294,117 @@ MainMenuButton::MainMenuButton(Room *room, SeekableReadStream &stream)
 
 void MainMenuButton::update() {
 	MenuButton::update();
-	const auto action = (MainMenuAction)actionId();
+	const auto action = this->action();
 	if (g_engine->input().wasMenuKeyPressed() &&
 		(action == MainMenuAction::ContinueGame || action == MainMenuAction::NewGame))
 		onClick();
 }
 
 void MainMenuButton::trigger() {
-	g_engine->menu().triggerMainMenuAction((MainMenuAction)actionId());
+	g_engine->menu().triggerMainMenuAction(action());
+}
+
+MainMenuAction MainMenuButton::action() const {
+	return (MainMenuAction)actionId();
+}
+
+MainMenuButtonV2::MainMenuButtonV2(Room *room, SeekableReadStream &stream)
+	: MainMenuButton(room, stream) {}
+
+MainMenuAction MainMenuButtonV2::action() const {
+	if (actionId() <= 7)
+		return (MainMenuAction)actionId();
+	else if (actionId() == 8)
+		return MainMenuAction::Accept;
+	else if (actionId() == 9)
+		return MainMenuAction::Cancel;
+	else {
+		warning("Unknown V2 main menu button: %d", (int)actionId());
+		return MainMenuAction::ContinueGame;
+	}
 }
 
 const char *PushButton::typeName() const { return "PushButton"; }
 
 PushButton::PushButton(Room *room, SeekableReadStream &stream)
 	: PhysicalObject(room, stream)
-	, _alwaysVisible(readBool(stream))
-	, _graphic1(stream)
-	, _graphic2(stream)
+	, _isChecked(readBool(stream))
+	, _graphicHovered(stream)
+	, _graphicChecked(stream)
 	, _actionId(stream.readSint32LE()) {}
 
+void PushButton::draw() {
+	if (!isEnabled())
+		return;
+
+	Graphic *graphic = nullptr;
+	if (_isChecked)
+		graphic = &_graphicChecked;
+	else if (wasSelected() && _graphicHovered.hasAnimation())
+		graphic = &_graphicHovered;
+
+	if (graphic != nullptr) {
+		graphic->update();
+		g_engine->drawQueue().add<AnimationDrawRequest>(*graphic, true, BlendMode::AdditiveAlpha);
+	}
+}
+
+void PushButton::update() {
+	PhysicalObject::update();
+	if (_clickTime != 0) {
+		if (g_engine->getMillis() - _clickTime > 5) {
+			_clickTime = 0;
+			trigger();
+		}
+	}
+}
+
+void PushButton::loadResources() {
+	_clickTime = 0;
+	_graphicChecked.loadResources();
+	_graphicHovered.loadResources();
+}
+
+void PushButton::freeResources() {
+	_graphicChecked.freeResources();
+	_graphicHovered.freeResources();
+}
+
+void PushButton::onHoverUpdate() {}
+
+void PushButton::onClick() {
+	_clickTime = g_engine->getMillis();
+}
+
+void PushButton::trigger() {
+	OptionsMenuAction action;
+	// these are V2 actions as CPushButton is only used in V2
+	switch (_actionId) {
+	case 0:
+		action = OptionsMenuAction::SubtitlesOn;
+		break;
+	case 1:
+		action = OptionsMenuAction::SubtitlesOff;
+		break;
+	case 2:
+		action = OptionsMenuAction::Cursor0;
+		break;
+	case 3:
+		action = OptionsMenuAction::Cursor1;
+		break;
+	case 4:
+		action = OptionsMenuAction::Cursor2;
+		break;
+	case 5:
+		action = OptionsMenuAction::Cursor3;
+		break;
+	default:
+		warning("Unknown push button action: %d", (int)_actionId);
+		return;
+	}
+	g_engine->menu().triggerOptionsAction(action);
+}
+
 const char *EditBox::typeName() const { return "EditBox"; }
 
 EditBox::EditBox(Room *room, SeekableReadStream &stream)
@@ -410,7 +521,6 @@ SlideButtonV2::SlideButtonV2(Room *room, SeekableReadStream &stream)
 	_minPos = Shape(stream).firstPoint();
 	_maxPos = Shape(stream).firstPoint();
 	_graphicIdle = Graphic(stream);
-	_graphicHovered = _graphicIdle;
 	_graphicClicked = Graphic(stream);
 }
 
@@ -425,14 +535,11 @@ SlideButtonV3::SlideButtonV3(Room *room, SeekableReadStream &stream)
 }
 
 void SlideButton::draw() {
-	auto *optionsMenu = dynamic_cast<OptionsMenu *>(room());
-	scumm_assert(optionsMenu != nullptr);
-
 	Graphic *activeGraphic;
-	if (optionsMenu->currentSlideButton() == this && g_engine->input().isMouseLeftDown())
+	if (g_engine->menu().currentSlideButton() == this && g_engine->input().isMouseLeftDown())
 		activeGraphic = &_graphicClicked;
 	else
-		activeGraphic = isMouseOver() ? &_graphicHovered : &_graphicIdle;
+		activeGraphic = isMouseOver() && _graphicHovered.hasAnimation() ? &_graphicHovered : &_graphicIdle;
 	activeGraphic->update();
 	g_engine->drawQueue().add<AnimationDrawRequest>(*activeGraphic, true, BlendMode::AdditiveAlpha);
 }
@@ -440,11 +547,11 @@ void SlideButton::draw() {
 void SlideButton::update() {
 	const auto mousePos = g_engine->input().mousePos2D();
 	auto *optionsMenu = dynamic_cast<OptionsMenu *>(room());
-	scumm_assert(optionsMenu != nullptr);
+	SlideButton *&currentSlideButton = g_engine->menu().currentSlideButton();
 
-	if (optionsMenu->currentSlideButton() == this) {
+	if (currentSlideButton == this) {
 		if (!g_engine->input().isMouseLeftDown()) {
-			optionsMenu->currentSlideButton() = nullptr;
+			currentSlideButton = nullptr;
 			g_engine->menu().triggerOptionsValue((OptionsMenuValue)_valueId, _value);
 			update(); // to update the position
 		} else {
@@ -460,8 +567,9 @@ void SlideButton::update() {
 			return;
 		_graphicHovered.topLeft() = _graphicIdle.topLeft();
 		if (g_engine->input().wasMouseLeftPressed())
-			optionsMenu->currentSlideButton() = this;
-		optionsMenu->clearLastSelectedObject();
+			currentSlideButton = this;
+		if (optionsMenu != nullptr) // in V2 this is a normal room not a OptionsMenu
+			optionsMenu->clearLastSelectedObject();
 		g_engine->player().selectedObject() = nullptr;
 	}
 }




More information about the Scummvm-git-logs mailing list