Files
scummvm-cursorfix/engines/hadesch/rooms/options.cpp
2026-02-02 04:50:13 +01:00

737 lines
19 KiB
C++

/* 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/>.
*
* Copyright 2020 Google
*
*/
#include "hadesch/hadesch.h"
#include "hadesch/video.h"
#include "common/util.h"
namespace Hadesch {
enum {
kBackgroundZ = 10000,
kScrollZ = 9900,
kScrollBarZ = 9000,
kThumbnailZ = 5000,
kArrowsZ = 2900,
kThumbZ = 2800,
kButtonZ = 2000,
kTitleZ = 2000
};
static const struct {
const char *image;
const char *hotname;
} buttons[] = {
{"return", "returntogame"},
{"credits", "credits"},
{"quit", "quitgame"},
{"new", "new"},
{"savegame", "savegame"},
{"restoregame", "restoregame"},
{"cancel", "cancel"},
{"save", "save"},
{"delete", "delete"},
{"yes", "yes"},
{"no", "no"},
{"ok", "ok"}
};
static const char *saveDescs[] = {
"",
"%s in intro scene",
"%s on mount olympus",
"%s in wall of fame",
"%s on Seriphos",
"%s in Athena temple",
"%s on Medusa Isle",
"%s in Medusa fight",
"%s on Argo",
"%s in Troy",
"%s in Catacombs",
"%s in Priam's Castle",
"%s in Trojan horse puzzle",
"%s on Crete",
"%s in Minos' Palace",
"%s in Daedalus' Room",
"%s in Minotaur puzzle",
"%s on Volcano top",
"%s near river Styx",
"%s in Hades' throne room",
"%s in ferryman puzzle",
"%s in monster puzzle",
"%s in Hades' Challenge",
"%s in credits scene",
""
};
class OptionsHandler : public Handler {
public:
enum AlertType {
kAlertSaveBeforeLoad,
kAlertSaveBeforeExit,
kAlertSaveBeforeNew,
kDeleteFromLoad,
kDeleteFromSave,
kDeleteUser
};
enum SaveMenuVariant {
kSaveFromMainMenu,
kSaveBeforeLoad,
kSaveBeforeExit,
kSaveBeforeNew
};
OptionsHandler() {
_savesLoaded = false;
_showPos = 0;
_selectedSave = -1;
_isLast = false;
}
void handleClick(const Common::String &hotname) override {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (hotname == "returntogame") {
g_vm->exitOptions();
return;
}
if (hotname == "credits") {
g_vm->enterOptionsCredits();
return;
}
if (hotname == "savegame") {
g_vm->resetOptionsRoom();
saveMenu(kSaveFromMainMenu);
return;
}
if (hotname == "restoregame") {
g_vm->resetOptionsRoom();
if (gameInProgress())
alertMenu(kAlertSaveBeforeLoad);
else
loadMenuUser();
return;
}
if (hotname == "cancel") {
switch (_currentMenu) {
case kLoadSlotMenu:
g_vm->resetOptionsRoom();
loadMenuUser();
break;
case kSaveMenu:
case kLoadUserMenu:
if (gameInProgress()) {
g_vm->resetOptionsRoom();
gameMenu();
} else {
g_vm->exitOptions();
}
break;
// No cancel in game menu
case kGameMenu:
break;
}
return;
}
if (hotname == "save" && _currentMenu == kSaveMenu) {
performSave();
return;
}
if (hotname.matchString("nameslot#")) {
_selectedSave = _showPos + hotname.substr(8).asUint64();
renderUserNames();
return;
}
if (hotname.matchString("saveslot#")) {
_selectedSave = _showPos + hotname.substr(8).asUint64();
renderSaveSlots();
return;
}
if (hotname.matchString("restoreslot#")) {
_selectedSave = _showPos + hotname.substr(11).asUint64();
renderLoadSlots();
return;
}
if (hotname == "arrowup" && _currentMenu == kLoadSlotMenu) {
if (_showPos < 6)
_showPos = 0;
else
_showPos -= 6;
renderLoadSlots();
return;
}
if (hotname == "arrowdown" && _currentMenu == kLoadSlotMenu) {
if (_showPos + 6 < (int) _userNames.size())
_showPos += 6;
renderLoadSlots();
return;
}
if (hotname == "arrowup" && _currentMenu == kLoadUserMenu) {
if (_showPos < 6)
_showPos = 0;
else
_showPos -= 6;
renderUserNames();
return;
}
if (hotname == "arrowdown" && _currentMenu == kLoadUserMenu) {
if (_showPos + 6 < (int) _userNames.size())
_showPos += 6;
renderUserNames();
return;
}
if (hotname == "arrowup" && _currentMenu == kSaveMenu) {
if (_showPos < 3)
_showPos = 0;
else
_showPos -= 3;
renderSaveSlots();
return;
}
if (hotname == "arrowdown" && _currentMenu == kSaveMenu) {
if (_showPos + 3 < (int) _filteredSaves.size())
_showPos += 3;
renderSaveSlots();
return;
}
if (hotname == "no") {
switch (_alertType) {
case kAlertSaveBeforeLoad:
case kDeleteUser:
g_vm->resetOptionsRoom();
loadMenuUser();
break;
case kAlertSaveBeforeExit:
g_vm->quit();
break;
case kAlertSaveBeforeNew:
g_vm->newGame();
g_vm->exitOptions();
break;
case kDeleteFromLoad:
g_vm->resetOptionsRoom();
loadMenuSlot();
break;
case kDeleteFromSave:
g_vm->resetOptionsRoom();
saveMenu(_saveVariant);
break;
}
return;
}
if (hotname == "yes") {
switch (_alertType) {
case kAlertSaveBeforeLoad:
g_vm->resetOptionsRoom();
saveMenu(kSaveBeforeLoad);
break;
case kAlertSaveBeforeExit:
g_vm->resetOptionsRoom();
saveMenu(kSaveBeforeExit);
break;
case kAlertSaveBeforeNew:
g_vm->resetOptionsRoom();
saveMenu(kSaveBeforeNew);
break;
case kDeleteFromLoad:
g_vm->deleteSave(_filteredSaves[_selectedSave]._slot);
_savesLoaded = false; // Invalidate cache
g_vm->resetOptionsRoom();
loadMenuSlot();
break;
case kDeleteFromSave:
g_vm->deleteSave(_filteredSaves[_selectedSave]._slot);
_savesLoaded = false; // Invalidate cache
g_vm->resetOptionsRoom();
saveMenu(_saveVariant);
break;
case kDeleteUser: {
Common::U32String username = _userNames[_selectedSave];
for (unsigned i = 0; i < _saves.size(); i++)
if (_saves[i]._heroName == username)
g_vm->deleteSave(_saves[i]._slot);
_savesLoaded = false; // Invalidate cache
}
}
return;
}
if (hotname == "ok" && _currentMenu == kLoadUserMenu) {
g_vm->resetOptionsRoom();
_chosenName = _userNames[_selectedSave];
loadMenuSlot();
return;
}
if (hotname == "restore") {
int slot = _filteredSaves[_selectedSave]._slot;
g_vm->loadGameState(slot);
g_vm->exitOptions();
return;
}
if (hotname == "quitgame") {
g_vm->resetOptionsRoom();
if (gameInProgress())
alertMenu(kAlertSaveBeforeExit);
else
g_vm->quit();
return;
}
if (hotname == "new") {
g_vm->resetOptionsRoom();
if (gameInProgress())
alertMenu(kAlertSaveBeforeNew);
else
g_vm->newGame();
return;
}
if (hotname == "delete" && _currentMenu == kLoadUserMenu) {
g_vm->resetOptionsRoom();
alertMenu(kDeleteUser);
return;
}
if (hotname == "delete" && _currentMenu == kLoadSlotMenu) {
g_vm->resetOptionsRoom();
alertMenu(kDeleteFromLoad);
return;
}
if (hotname == "delete" && _currentMenu == kSaveMenu) {
g_vm->resetOptionsRoom();
alertMenu(kDeleteFromSave);
return;
}
}
void handleMouseOver(const Common::String &hotname) override {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
for (unsigned i = 0; i < ARRAYSIZE(buttons); i++)
if (hotname == buttons[i].hotname) {
room->selectFrame(buttons[i].image, kButtonZ, 1);
return;
}
if (hotname == "arrowup" && _showPos > 0) {
room->selectFrame("arrows", kArrowsZ, 1);
return;
}
if (hotname == "arrowdown" && !_isLast) {
room->selectFrame("arrows", kArrowsZ, 2);
return;
}
}
void handleMouseOut(const Common::String &hotname) override {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
for (unsigned i = 0; i < ARRAYSIZE(buttons); i++)
if (hotname == buttons[i].hotname) {
room->selectFrame(buttons[i].image, kButtonZ, 0);
return;
}
if (hotname == "arrowup" || hotname == "arrowdown") {
room->selectFrame("arrows", kArrowsZ, 0);
return;
}
}
void handleKeypress(uint32 ucode) override {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (_currentMenu == kSaveMenu) {
if (ucode == '\n' || ucode == '\r') {
performSave();
return;
}
if (ucode == '\b' && _typedSlotName.size() > 0) {
_typedSlotName.deleteLastChar();
room->playSFX("keyclick");
renderSaveName();
return;
}
if (ucode == '\0' || ucode < ' ')
return;
if (_typedSlotName.size() < 11) {
_typedSlotName += ucode;
room->playSFX("keyclick");
renderSaveName();
}
}
}
void handleEvent(int eventId) override {
}
~OptionsHandler() override {}
void prepareRoom() override {
Persistent *persistent = g_vm->getPersistent();
if (persistent->_currentRoomId == kOlympusRoom)
loadMenuUser();
else
gameMenu();
}
private:
void performSave() {
int slot = g_vm->firstAvailableSlot();
Persistent *persistent = g_vm->getPersistent();
Common::String heroNameUTF8 = persistent->_heroName.encode(Common::kUtf8);
// UTF-8
Common::String descPos = Common::String::format(
saveDescs[persistent->_currentRoomId],
heroNameUTF8.c_str());
// UTF-8
Common::String desc = _typedSlotName.empty() ? descPos
: _typedSlotName + " (" + descPos + ")";
persistent->_slotDescription = _typedSlotName;
Common::Error res = g_vm->saveGameState(slot, desc);
debug("%d, %s->[%d, %s]", slot, desc.c_str(), res.getCode(), res.getDesc().c_str());
_savesLoaded = false; // Invalidate cache
switch (_saveVariant) {
case kSaveFromMainMenu:
g_vm->exitOptions();
break;
case kSaveBeforeLoad:
g_vm->resetOptionsRoom();
loadMenuUser();
break;
case kSaveBeforeExit:
g_vm->quit();
break;
case kSaveBeforeNew:
g_vm->newGame();
g_vm->exitOptions();
break;
}
}
void renderSaveName() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
// One character more to handle possible backspace clicks.
room->hideString("smallascii", _typedSlotName.size() + 1);
room->renderString("smallascii", _typedSlotName, Common::Point(150, 266), 4000);
}
void gameMenu() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentMenu = kGameMenu;
room->loadHotZones("OPGame.HOT");
room->addStaticLayer("black", kBackgroundZ);
room->disableHeroBelt();
room->selectFrame("gamemenu", kScrollBarZ, 0);
room->selectFrame("return", kButtonZ, 0);
room->selectFrame("credits", kButtonZ, 0);
room->selectFrame("quit", kButtonZ, 0);
room->selectFrame("new", kButtonZ, 0);
room->selectFrame("savegame", kButtonZ, 0);
if (g_vm->hasAnySaves())
room->selectFrame("restoregame", kButtonZ, 0);
else
room->disableHotzone("restoregame");
}
bool gameInProgress() {
Persistent *persistent = g_vm->getPersistent();
return persistent->_currentRoomId != kOlympusRoom && persistent->_currentRoomId != kIntroRoom;
}
void loadMenuUser() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
Common::HashMap<Common::U32String, bool> userset;
loadSaves();
_currentMenu = kLoadUserMenu;
_userNames.clear();
for (unsigned i = 0; i < _saves.size(); i++)
if (!userset[_saves[i]._heroName]) {
userset[_saves[i]._heroName] = true;
_userNames.push_back(_saves[i]._heroName);
}
Common::sort(_userNames.begin(), _userNames.end());
room->loadHotZones("OPRest1.HOT");
room->addStaticLayer("black", kBackgroundZ);
room->disableHeroBelt();
room->selectFrame("scroll", kScrollZ, 0);
room->selectFrame("restorescroll", kScrollBarZ, 0);
room->selectFrame("cancel", kButtonZ, 0);
room->selectFrame("delete", kButtonZ, 0);
room->selectFrame("ok", kButtonZ, 0);
room->selectFrame("choosename", kTitleZ, 0);
if (_userNames.size() > 6)
room->selectFrame("arrows", kArrowsZ, 0);
else {
room->disableHotzone("arrowup");
room->disableHotzone("arrowdown");
}
_showPos = 0;
_selectedSave = -1;
renderUserNames();
}
void renderUserNames() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
bool selectedIsShown = false;
for (int i = 0; i < 6 && _showPos + i < (int) _userNames.size(); i++) {
Common::U32String name = _userNames[_showPos + i];
if (name == "")
name = "No name";
room->renderString("largeascii", name,
Common::Point(150, 134 + 36 * i), 4000, 0,
Common::String::format("username%d", i));
if (_showPos + i == _selectedSave) {
selectedIsShown = true;
room->selectFrame("thumb", kThumbZ, 0, Common::Point(109, 134 + 36 * i));
}
}
for (unsigned i = 0; i < 6; i++) {
room->setHotzoneEnabled(Common::String::format("nameslot%d", i), _showPos + i < _userNames.size());
}
_isLast = _showPos + 3 >= (int) _userNames.size();
room->setHotzoneEnabled("delete", selectedIsShown);
room->setHotzoneEnabled("ok", selectedIsShown);
room->setHotzoneEnabled("arrowdown", !_isLast);
room->setHotzoneEnabled("arrowup", _showPos > 0);
}
void loadMenuSlot() {
loadSaves();
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
loadFilteredSaves(_chosenName);
_currentMenu = kLoadSlotMenu;
room->loadHotZones("OPRest2.HOT");
room->addStaticLayer("black", kBackgroundZ);
room->disableHeroBelt();
room->selectFrame("scroll", kScrollZ, 0);
room->selectFrame("restore2scroll", kScrollBarZ, 0);
room->renderStringCentered("largeascii", _chosenName, Common::Point(320, 77), 4000);
if (_filteredSaves.size() > 6)
room->selectFrame("arrows", kArrowsZ, 0);
else {
room->disableHotzone("arrowup");
room->disableHotzone("arrowdown");
}
room->selectFrame("cancel", kButtonZ, 0);
room->selectFrame("restore", kButtonZ, 0);
room->selectFrame("delete", kButtonZ, 0);
room->disableHotzone("delete");
_selectedSave = -1;
_showPos = 0;
renderLoadSlots();
}
void alertMenu(AlertType alertType) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->loadHotZones("OPAlert.HOT");
room->addStaticLayer("black", kBackgroundZ);
room->disableHeroBelt();
// Original uses other Z values but it generates the same
// resulting image and we can keep button code consistent
room->selectFrame("alert", 4000, 0);
switch (alertType) {
case kAlertSaveBeforeLoad:
case kAlertSaveBeforeExit:
case kAlertSaveBeforeNew:
room->selectFrame("exit", 3800, 0);
break;
case kDeleteFromLoad:
case kDeleteFromSave:
room->selectFrame("deletegame", 3800, 0);
break;
case kDeleteUser:
room->selectFrame("deletename", 3800, 0);
break;
}
room->selectFrame("yes", kButtonZ, 0);
room->selectFrame("no", kButtonZ, 0);
_alertType = alertType;
}
void saveMenu(SaveMenuVariant saveVariant) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
Persistent *persistent = g_vm->getPersistent();
loadFilteredSaves(persistent->_heroName);
_currentMenu = kSaveMenu;
_saveVariant = saveVariant;
room->loadHotZones("OPSave.HOT");
room->addStaticLayer("black", kBackgroundZ);
room->disableHeroBelt();
room->selectFrame("scroll", kScrollZ, 0);
room->selectFrame("savescroll", kScrollBarZ, 0);
room->renderStringCentered("largeascii", persistent->_heroName, Common::Point(320, 77), 4000);
if (_filteredSaves.size() > 3)
room->selectFrame("arrows", kArrowsZ, 0);
else {
room->disableHotzone("arrowup");
room->disableHotzone("arrowdown");
}
room->selectFrame("cancel", kButtonZ, 0);
room->selectFrame("save", kButtonZ, 0);
room->selectFrame("delete", kButtonZ, 0);
room->disableHotzone("delete");
_selectedSave = -1;
_showPos = 0;
_typedSlotName = "";
room->selectFrame("saveas", kTitleZ, 0);
room->selectFrame(LayerId("thumbnails", 0, "save"), 5000,
persistent->_currentRoomId - 1, Common::Point(184, 204));
renderSaveSlots();
}
void renderSaveSlots() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
bool selectedIsShown = false;
for (int i = 0; i < 3; i++) {
Common::Point base = Common::Point(310, 128+76*i);
bool isValid = _showPos + i < (int)_filteredSaves.size();
room->hideString("smallascii", 30, Common::String::format("saveslots%d", i));
room->setHotzoneEnabled(Common::String::format("saveslot%d", i), isValid);
if (isValid) {
room->selectFrame(LayerId("thumbnails", i, "right"), 5000,
_filteredSaves[_showPos + i]._room - 1, base + Common::Point(31, 0));
room->renderString("smallascii", _filteredSaves[_showPos + i]._slotName,
base + Common::Point(31, 62), 5000,
0, Common::String::format("saveslots%d", i));
if (_showPos + i == _selectedSave) {
// TODO: original uses 300 - thumbWidth, I ust hardcode thumbWidth
room->selectFrame("thumb", kThumbZ, 0, base - Common::Point(31+10, 0));
selectedIsShown = true;
}
} else {
room->stopAnim(LayerId("thumbnails", i, "right"));
}
}
_isLast = _showPos + 3 >= (int) _filteredSaves.size();
room->setHotzoneEnabled("delete", selectedIsShown);
room->setHotzoneEnabled("arrowdown", !_isLast);
room->setHotzoneEnabled("arrowup", _showPos > 0);
}
void renderLoadSlots() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
bool selectedIsShown = false;
for (int i = 0; i < 6; i++) {
bool isValid = _showPos + i < (int) _filteredSaves.size();
room->hideString("smallascii", 30, Common::String::format("loadslots%d", i));
room->setHotzoneEnabled(Common::String::format("restoreslot%d", i), isValid);
if (isValid) {
Common::Point base = Common::Point(153 + 157 * (i % 2), 128 + 76 * (i / 2));
room->selectFrame(LayerId("thumbnails", i, "right"), 5000,
_filteredSaves[_showPos + i]._room - 1, base + Common::Point(31, 0));
room->renderString("smallascii", _filteredSaves[_showPos + i]._slotName,
base + Common::Point(31, 62), 5000,
0, Common::String::format("loadslots%d", i));
if (_showPos + i == _selectedSave) {
// TODO: original uses 300 - thumbWidth, I ust hardcode thumbWidth
room->selectFrame("thumb", kThumbZ, 0, base);
selectedIsShown = true;
}
} else {
room->stopAnim(LayerId("thumbnails", i, "right"));
}
}
_isLast = _showPos + 6 >= (int) _filteredSaves.size();
room->setHotzoneEnabled("arrowdown", !_isLast);
room->setHotzoneEnabled("arrowup", _showPos > 0);
room->setHotzoneEnabled("restore", selectedIsShown);
room->setHotzoneEnabled("delete", selectedIsShown);
}
void loadSaves() {
if (_savesLoaded)
return;
_saves = g_vm->getHadeschSavesList();
}
void loadFilteredSaves(const Common::U32String &heroname) {
loadSaves();
_filteredSaves.clear();
for (unsigned i = 0; i < _saves.size(); i++)
if (_saves[i]._heroName == heroname)
_filteredSaves.push_back(_saves[i]);
}
enum {
kGameMenu,
kSaveMenu,
kLoadUserMenu,
kLoadSlotMenu
} _currentMenu;
SaveMenuVariant _saveVariant;
AlertType _alertType;
Common::Array<HadeschSaveDescriptor> _saves;
Common::Array<HadeschSaveDescriptor> _filteredSaves;
Common::Array<Common::U32String> _userNames;
Common::U32String _chosenName;
Common::U32String _typedSlotName;
int _showPos;
int _selectedSave;
bool _savesLoaded;
bool _isLast;
};
Common::SharedPtr<Hadesch::Handler> makeOptionsHandler() {
return Common::SharedPtr<Hadesch::Handler>(new OptionsHandler());
}
}