/* 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 . * * 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 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 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 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 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 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 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 room = g_vm->getVideoRoom(); Common::HashMap 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 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 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 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 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 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 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 _saves; Common::Array _filteredSaves; Common::Array _userNames; Common::U32String _chosenName; Common::U32String _typedSlotName; int _showPos; int _selectedSave; bool _savesLoaded; bool _isLast; }; Common::SharedPtr makeOptionsHandler() { return Common::SharedPtr(new OptionsHandler()); } }